diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7ea678f268..ad73ae4a45 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -18,6 +18,7 @@ "@patternfly/react-code-editor": "^5.1.1", "@patternfly/react-core": "^5.1.1", "@patternfly/react-icons": "^5.1.1", + "@patternfly/react-log-viewer": "^5.0.0", "@patternfly/react-styles": "^5.1.1", "@patternfly/react-table": "^5.1.1", "@patternfly/react-topology": "^5.1.0", @@ -4459,6 +4460,21 @@ "react-dom": "^17 || ^18" } }, + "node_modules/@patternfly/react-log-viewer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@patternfly/react-log-viewer/-/react-log-viewer-5.0.0.tgz", + "integrity": "sha512-N4E1HAfXVlPDsvCvXhh7ycO/pu8h7nGBjQgGV7mUWQb23Q8djuzPU3aK/QODVEP6ZCnlvq2AbaKUpaLiE5z4ww==", + "dependencies": { + "@patternfly/react-core": "^5.0.0", + "@patternfly/react-icons": "^5.0.0", + "@patternfly/react-styles": "^5.0.0", + "memoize-one": "^5.1.0" + }, + "peerDependencies": { + "react": "^17 || ^18", + "react-dom": "^17 || ^18" + } + }, "node_modules/@patternfly/react-styles": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/@patternfly/react-styles/-/react-styles-5.1.1.tgz", @@ -25245,6 +25261,11 @@ "node": ">= 4.0.0" } }, + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==" + }, "node_modules/memoizerific": { "version": "1.11.3", "resolved": "https://registry.npmjs.org/memoizerific/-/memoizerific-1.11.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index c2d9e4ad7f..915f363951 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -50,6 +50,7 @@ "@patternfly/react-code-editor": "^5.1.1", "@patternfly/react-core": "^5.1.1", "@patternfly/react-icons": "^5.1.1", + "@patternfly/react-log-viewer": "^5.0.0", "@patternfly/react-styles": "^5.1.1", "@patternfly/react-table": "^5.1.1", "@patternfly/react-topology": "^5.1.0", diff --git a/frontend/src/concepts/dashboard/DashboardLogViewer.tsx b/frontend/src/concepts/dashboard/DashboardLogViewer.tsx new file mode 100644 index 0000000000..50845e3f25 --- /dev/null +++ b/frontend/src/concepts/dashboard/DashboardLogViewer.tsx @@ -0,0 +1,23 @@ +import { LogViewer } from '@patternfly/react-log-viewer'; +import React from 'react'; + +const DashboardLogViewer: React.FC<{ + data: string; + logViewerRef: React.MutableRefObject<{ scrollToBottom: () => void } | undefined>; + toolbar: JSX.Element; + footer: JSX.Element | false; + onScroll: React.ComponentProps['onScroll']; +}> = ({ data, logViewerRef, toolbar, footer, onScroll }) => ( +
+ +
+); + +export default DashboardLogViewer; diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDrawerRightContent.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDrawerRightContent.tsx index 3de11a3c5e..1dc32ef8c4 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDrawerRightContent.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDrawerRightContent.tsx @@ -25,12 +25,17 @@ const PipelineRunDrawerRightContent: React.FC { + const [, setWidth] = React.useState(0); + if (!task) { return null; } return ( { + setWidth(newWidth); + }} isResizable widths={{ default: 'width_33', lg: 'width_50' }} minSize="400px" diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDrawerRightTabs.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDrawerRightTabs.tsx index fffb1e7032..bbcc225598 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDrawerRightTabs.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/PipelineRunDrawerRightTabs.tsx @@ -29,7 +29,7 @@ const PipelineRunDrawerRightTabs: React.FC = ({ taskReferences, parameters, }) => { - const [selection, setSelection] = React.useState(PipelineRunNodeTabs.INPUT_OUTPUT); + const [selection, setSelection] = React.useState(PipelineRunNodeTabs.LOGS); const tabContentProps = (tab: PipelineRunNodeTabs): React.ComponentProps => ({ id: tab, diff --git a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/runLogs/LogsTab.tsx b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/runLogs/LogsTab.tsx index 9ce3637bfc..ec02e30f27 100644 --- a/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/runLogs/LogsTab.tsx +++ b/frontend/src/concepts/pipelines/content/pipelinesDetails/pipelineRun/runLogs/LogsTab.tsx @@ -23,6 +23,8 @@ import OutlinedPlayCircleIcon from '@patternfly/react-icons/dist/esm/icons/outli import PauseIcon from '@patternfly/react-icons/dist/esm/icons/pause-icon'; import PlayIcon from '@patternfly/react-icons/dist/esm/icons/play-icon'; import DownloadIcon from '@patternfly/react-icons/dist/esm/icons/download-icon'; +import { LogViewer, LogViewerSearch } from '@patternfly/react-log-viewer'; +import DashboardLogViewer from '~/concepts/dashboard/DashboardLogViewer'; import { PipelineRunTaskDetails } from '~/concepts/pipelines/content/types'; import SimpleDropdownSelect from '~/components/SimpleDropdownSelect'; import useFetchLogs from '~/concepts/k8s/pods/useFetchLogs'; @@ -30,8 +32,6 @@ import usePodContainerLogState from '~/concepts/pipelines/content/pipelinesDetai import { useWindowResize } from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/runLogs/useWindowResize'; import LogsTabStatus from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/runLogs/LogsTabStatus'; import { LOG_TAIL_LINES } from '~/concepts/pipelines/content/pipelinesDetails/pipelineRun/runLogs/const'; -import DashboardCodeEditor from '~/concepts/dashboard/codeEditor/DashboardCodeEditor'; -import useCodeEditorAsLogs from '~/concepts/dashboard/codeEditor/useCodeEditorAsLogs'; import { downloadFullPodLog, downloadAllStepLogs } from '~/concepts/k8s/pods/utils'; import { usePipelinesAPI } from '~/concepts/pipelines/context'; @@ -60,15 +60,30 @@ const LogsTabForPodName: React.FC<{ podName: string }> = ({ podName }) => { ); const [downloading, setDownloading] = React.useState(false); const [downloadError, setDownloadError] = React.useState(); - const { scrollToBottom, onMount, editorOptions } = useCodeEditorAsLogs(); const [isDownloadDropdownOpen, setIsDownloadDropdownOpen] = React.useState(false); const { isSmallScreen } = useWindowResize(); + const logViewerRef = React.useRef<{ scrollToBottom: () => void }>(); React.useEffect(() => { if (!isPaused && logs) { - scrollToBottom(); + if (logViewerRef && logViewerRef.current) { + logViewerRef.current.scrollToBottom(); + } } - }, [isPaused, logs, scrollToBottom]); + }, [isPaused, logs]); + + const onScroll: React.ComponentProps['onScroll'] = ({ + scrollOffsetToBottom, + scrollUpdateWasRequested, + }) => { + if (!scrollUpdateWasRequested) { + if (scrollOffsetToBottom > 0) { + setIsPaused(true); + } else { + setIsPaused(false); + } + } + }; const canDownloadAll = !!podContainers && !!podName && !downloading; const onDownloadAll = () => { @@ -109,135 +124,145 @@ const LogsTabForPodName: React.FC<{ podName: string }> = ({ podName }) => { } return ( - - - - - - - - - {isSmallScreen() ? null : ( - - - - Step - - - - )} + + + + + + - ({ - key: container.name, - label: container.name, - }))} - value={selectedContainer?.name ?? ''} - placeholder="Select container..." - onChange={(v) => { - setSelectedContainer(podContainers.find((c) => c.name === v) ?? null); - }} - width={150} - /> - - - - - - - - {downloading ? ( - <> - {' '} - - ) : null} - {podContainers.length !== 0 ? ( - setIsDownloadDropdownOpen(!isDownloadDropdownOpen)} - /> - ) : ( - setIsDownloadDropdownOpen(!isDownloadDropdownOpen)} - > - Download - - ) - } - isOpen={isDownloadDropdownOpen} - isPlain={isSmallScreen()} - dropdownItems={[ - - + {isSmallScreen() ? null : ( + + + + Step + + + + )} + + ({ + key: container.name, + label: container.name, + }))} + value={selectedContainer?.name ?? ''} + placeholder="Select container..." + onChange={(v) => { + setSelectedContainer(podContainers.find((c) => c.name === v) ?? null); + }} + width={150} /> - , - - + + setIsPaused(true)} + placeholder="Search" + minSearchChars={0} /> - , - ]} - /> - ) : ( - Download current step log}> + + + + - - )} - - - - - - {isPaused && ( + + + {downloading ? ( + <> + {' '} + + ) : null} + {podContainers.length !== 0 ? ( + setIsDownloadDropdownOpen(!isDownloadDropdownOpen)} + /> + ) : ( + setIsDownloadDropdownOpen(!isDownloadDropdownOpen)} + > + Download + + ) + } + isOpen={isDownloadDropdownOpen} + isPlain={isSmallScreen()} + dropdownItems={[ + + + , + + + , + ]} + /> + ) : ( + Download current step log}> + + + )} + + + + + } + footer={ + isPaused && ( - )} - - + ) + } + onScroll={onScroll} + /> ); };