diff --git a/Dockerfile b/Dockerfile
index 82c4ab2a..80c4908a 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM epamedp/headlamp:0.22.34
+FROM epamedp/headlamp:0.22.35
COPY --chown=100:101 assets/ /headlamp/frontend
COPY --chown=100:101 dist/main.js /headlamp/plugins/edp/main.js
diff --git a/src/pages/pipeline-details/components/Details/components/TaskRunStepWrapper/hooks/useTabs.tsx b/src/pages/pipeline-details/components/Details/components/TaskRunStepWrapper/hooks/useTabs.tsx
index 1b67b92b..aa9052af 100644
--- a/src/pages/pipeline-details/components/Details/components/TaskRunStepWrapper/hooks/useTabs.tsx
+++ b/src/pages/pipeline-details/components/Details/components/TaskRunStepWrapper/hooks/useTabs.tsx
@@ -1,6 +1,6 @@
import React from 'react';
import { ViewYAML } from '../../../../../../../components/Editor';
-import { LogsViewer } from '../../../../../../../widgets/LogViewer';
+import { PodsLogViewer } from '../../../../../../../widgets/PodsLogViewer';
import { TabContent } from '../../TabContent';
export const useTabs = ({ taskRun, task, stepName, pods }) => {
@@ -21,7 +21,7 @@ export const useTabs = ({ taskRun, task, stepName, pods }) => {
label: 'Logs',
component: (
-
+
),
disabled: !pods?.length,
diff --git a/src/widgets/PodsLogViewer/index.tsx b/src/widgets/PodsLogViewer/index.tsx
new file mode 100644
index 00000000..9e22268a
--- /dev/null
+++ b/src/widgets/PodsLogViewer/index.tsx
@@ -0,0 +1,295 @@
+import { LightTooltip } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
+import { KubeContainerStatus } from '@kinvolk/headlamp-plugin/lib/lib/k8s/cluster';
+import {
+ FormControl,
+ FormControlLabel,
+ InputLabel,
+ ListSubheader,
+ MenuItem,
+ Select,
+ Switch,
+} from '@mui/material';
+import makeStyles from '@mui/styles/makeStyles';
+import _ from 'lodash';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+import { Terminal as XTerminal } from 'xterm';
+import { LogViewer } from '../../components/LogViewer';
+import { PodKubeObjectInterface } from '../../k8s/groups/default/Pod/types';
+import { PodsLogViewerInnerProps, PodsLogViewerProps } from './types';
+
+const useStyle = makeStyles((theme) => ({
+ containerFormControl: {
+ minWidth: '11rem',
+ },
+ linesFormControl: {
+ minWidth: '6rem',
+ },
+ switchControl: {
+ margin: 0,
+ paddingTop: theme.spacing(2),
+ paddingRight: theme.spacing(2),
+ },
+}));
+
+const PodsLogViewerInner: React.FC = ({
+ pods,
+ activePod,
+ container,
+ setContainer,
+ handlePodChange,
+}) => {
+ const classes = useStyle();
+ const [showPrevious, setShowPrevious] = React.useState(false);
+ const [showTimestamps, setShowTimestamps] = React.useState(false);
+ const [follow, setFollow] = React.useState(true);
+ const [lines, setLines] = React.useState(100);
+ const [logs, setLogs] = React.useState<{ logs: string[]; lastLineShown: number }>({
+ logs: [],
+ lastLineShown: -1,
+ });
+ const xtermRef = React.useRef(null);
+ const { t } = useTranslation('frequent');
+
+ const options = { leading: true, trailing: true, maxWait: 1000 };
+ const setLogsDebounced = React.useCallback(
+ (logLines: string[]) => {
+ setLogs((current) => {
+ if (current.lastLineShown >= logLines.length) {
+ xtermRef.current?.clear();
+ xtermRef.current?.write(logLines.join('').replaceAll('\n', '\r\n'));
+ } else {
+ xtermRef.current?.write(
+ logLines
+ .slice(current.lastLineShown + 1)
+ .join('')
+ .replaceAll('\n', '\r\n')
+ );
+ }
+
+ return {
+ logs: logLines,
+ lastLineShown: logLines.length - 1,
+ };
+ });
+ // If we stopped following the logs and we have logs already,
+ // then we don't need to fetch them again.
+ if (!follow && logs.logs.length > 0) {
+ xtermRef.current?.write(
+ '\n\n' +
+ t('translation|Logs are paused. Click the follow button to resume following them.') +
+ '\r\n'
+ );
+ return;
+ }
+ },
+ [follow, logs.logs.length, t]
+ );
+ const debouncedSetState = _.debounce(setLogsDebounced, 500, options);
+
+ React.useEffect(
+ () => {
+ let callback: any = null;
+
+ if (!!activePod) {
+ xtermRef.current?.clear();
+ setLogs({ logs: [], lastLineShown: -1 });
+
+ callback = activePod.getLogs(container, debouncedSetState, {
+ tailLines: lines,
+ showPrevious,
+ showTimestamps,
+ follow,
+ });
+ }
+
+ return function cleanup() {
+ if (callback) {
+ callback();
+ }
+ };
+ },
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ [container, lines, open, showPrevious, showTimestamps, follow]
+ );
+
+ const handleContainerChange = (event: any) => {
+ setContainer(event.target.value);
+ };
+
+ const handleLinesChange = (event: any) => {
+ setLines(event.target.value);
+ };
+
+ const handlePreviousChange = () => {
+ setShowPrevious((previous) => !previous);
+ };
+
+ const hasContainerRestarted = () => {
+ const cont = activePod?.status?.containerStatuses?.find(
+ (c: KubeContainerStatus) => c.name === container
+ );
+ if (!cont) {
+ return false;
+ }
+
+ return cont.restartCount > 0;
+ };
+
+ const handleTimestampsChange = () => {
+ setShowTimestamps((timestamps) => !timestamps);
+ };
+
+ const handleFollowChange = () => {
+ setFollow((follow) => !follow);
+ };
+
+ return (
+
+
+ {'Pod'}
+
+
+ ,
+
+
+ {'Container'}
+
+
+ ,
+
+
+ {'Lines'}
+
+
+ ,
+
+
+ }
+ />
+ ,
+
+ }
+ />,
+
+ }
+ />,
+ ]}
+ />
+ );
+};
+
+export const PodsLogViewer: React.FC = ({ pods, getDefaultContainer }) => {
+ const firstPod = pods?.[0];
+
+ const [activePod, setActivePod] = React.useState(firstPod);
+ const [container, setContainer] = React.useState(getDefaultContainer(firstPod));
+
+ const handlePodChange = React.useCallback(
+ (event: any) => {
+ const newPodName = event.target.value;
+ const newPod = pods.find(({ metadata: { name } }) => name === newPodName);
+ if (newPod) {
+ setActivePod(newPod);
+ setContainer(getDefaultContainer(newPod));
+ }
+ },
+ [getDefaultContainer, pods]
+ );
+
+ return (
+
+ );
+};
diff --git a/src/widgets/PodsLogViewer/types.ts b/src/widgets/PodsLogViewer/types.ts
new file mode 100644
index 00000000..5031fdb4
--- /dev/null
+++ b/src/widgets/PodsLogViewer/types.ts
@@ -0,0 +1,14 @@
+import { PodKubeObjectInterface } from "../../k8s/groups/default/Pod/types";
+
+export interface PodsLogViewerProps {
+ pods: PodKubeObjectInterface[];
+ getDefaultContainer: (pod: PodKubeObjectInterface) => string;
+}
+
+export interface PodsLogViewerInnerProps {
+ pods: PodKubeObjectInterface[];
+ activePod: PodKubeObjectInterface;
+ container: string;
+ setContainer: React.Dispatch>;
+ handlePodChange: (event: any) => void;
+}
diff --git a/src/widgets/dialogs/PodsLogViewer/index.tsx b/src/widgets/dialogs/PodsLogViewer/index.tsx
index a2e4c255..7c3e943b 100644
--- a/src/widgets/dialogs/PodsLogViewer/index.tsx
+++ b/src/widgets/dialogs/PodsLogViewer/index.tsx
@@ -54,7 +54,7 @@ export const PodsLogViewerDialog: React.FC = ({ props,
const classes = useStyle();
const [showPrevious, setShowPrevious] = React.useState(false);
const [showTimestamps, setShowTimestamps] = React.useState(false);
- const [follow, setFollow] = React.useState(false);
+ const [follow, setFollow] = React.useState(true);
const [lines, setLines] = React.useState(100);
const [logs, setLogs] = React.useState<{ logs: string[]; lastLineShown: number }>({
logs: [],
@@ -65,36 +65,39 @@ export const PodsLogViewerDialog: React.FC = ({ props,
const { t } = useTranslation('frequent');
const options = { leading: true, trailing: true, maxWait: 1000 };
- const setLogsDebounced = (logLines: string[]) => {
- setLogs((current) => {
- if (current.lastLineShown >= logLines.length) {
- xtermRef.current?.clear();
- xtermRef.current?.write(logLines.join('').replaceAll('\n', '\r\n'));
- } else {
+ const setLogsDebounced = React.useCallback(
+ (logLines: string[]) => {
+ setLogs((current) => {
+ if (current.lastLineShown >= logLines.length) {
+ xtermRef.current?.clear();
+ xtermRef.current?.write(logLines.join('').replaceAll('\n', '\r\n'));
+ } else {
+ xtermRef.current?.write(
+ logLines
+ .slice(current.lastLineShown + 1)
+ .join('')
+ .replaceAll('\n', '\r\n')
+ );
+ }
+
+ return {
+ logs: logLines,
+ lastLineShown: logLines.length - 1,
+ };
+ });
+ // If we stopped following the logs and we have logs already,
+ // then we don't need to fetch them again.
+ if (!follow && logs.logs.length > 0) {
xtermRef.current?.write(
- logLines
- .slice(current.lastLineShown + 1)
- .join('')
- .replaceAll('\n', '\r\n')
+ '\n\n' +
+ t('translation|Logs are paused. Click the follow button to resume following them.') +
+ '\r\n'
);
+ return;
}
-
- return {
- logs: logLines,
- lastLineShown: logLines.length - 1,
- };
- });
- // If we stopped following the logs and we have logs already,
- // then we don't need to fetch them again.
- if (!follow && logs.logs.length > 0) {
- xtermRef.current?.write(
- '\n\n' +
- t('logs|Logs are paused. Click the follow button to resume following them.') +
- '\r\n'
- );
- return;
- }
- };
+ },
+ [follow, logs.logs.length, t]
+ );
const debouncedSetState = _.debounce(setLogsDebounced, 500, options);
React.useEffect(