Skip to content

Commit

Permalink
frontend: Add last state to container info
Browse files Browse the repository at this point in the history
Users want to know e.g. why a container was restarted.

Signed-off-by: Joaquim Rocha <joaquim.rocha@microsoft.com>
  • Loading branch information
joaquimrocha committed May 29, 2024
1 parent b258c00 commit 126869e
Show file tree
Hide file tree
Showing 10 changed files with 389 additions and 50 deletions.
171 changes: 132 additions & 39 deletions frontend/src/components/common/Resource/Resource.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import {
import Pod, { KubePod, KubeVolume } from '../../../lib/k8s/pod';
import { createRouteURL, RouteURLProps } from '../../../lib/router';
import { getThemeName } from '../../../lib/themes';
import { localeDate } from '../../../lib/util';
import { localeDate, useId } from '../../../lib/util';
import { HeadlampEventType, useEventCallback } from '../../../redux/headlampEventSlice';
import { useTypedSelector } from '../../../redux/reducers/reducers';
import { useHasPreviousRoute } from '../../App/RouteSwitcher';
Expand Down Expand Up @@ -645,60 +645,87 @@ export function ContainerInfo(props: ContainerInfoProps) {
const { container, status, resource } = props;
const { t } = useTranslation(['glossary', 'translation']);

const [startedDate, finishDate] = React.useMemo(() => {
let startedDate =
status?.state?.running?.startedAt || status?.state?.terminated?.startedAt || '';
if (!!startedDate) {
startedDate = localeDate(startedDate);
const [startedDate, finishDate, lastStateStartedDate, lastStateFinishDate] = React.useMemo(() => {
function getStartedDate(state?: KubeContainerStatus['state']) {
let startedDate = state?.running?.startedAt || state?.terminated?.startedAt || '';
if (!!startedDate) {
startedDate = localeDate(startedDate);
}
return startedDate;
}

let finishDate = status?.state?.terminated?.finishedAt ?? '';
if (!!finishDate) {
finishDate = localeDate(finishDate);
function getFinishDate(state?: KubeContainerStatus['state']) {
let finishDate = state?.terminated?.finishedAt || '';
if (!!finishDate) {
finishDate = localeDate(finishDate);
}
return finishDate;
}

return [startedDate, finishDate];
return [
getStartedDate(status?.state),
getFinishDate(status?.state),
getStartedDate(status?.lastState),
getFinishDate(status?.lastState),
];
}, [status]);

function getContainerStatusLabel() {
if (!status || !container) {
return undefined;
}
function ContainerStatusLabel(props: {
state?: KubeContainerStatus['state'] | null;
container?: KubeContainer;
}) {
const { state, container } = props;

const [stateDetails, label, statusType] = React.useMemo(() => {
let stateDetails: KubeContainerStatus['state']['waiting' | 'terminated'] | null = null;
let label = t('translation|Ready');
let statusType: StatusLabelProps['status'] = '';

let state: KubeContainerStatus['state']['waiting' | 'terminated'] | null = null;
let label = t('translation|Ready');
let statusType: StatusLabelProps['status'] = '';

if (!!status.state.waiting) {
state = status.state.waiting;
statusType = 'warning';
label = t('translation|Waiting');
} else if (!!status.state.running) {
statusType = 'success';
label = t('translation|Running');
} else if (!!status.state.terminated) {
if (status.state.terminated.exitCode === 0) {
statusType = '';
label = status.state.terminated.reason;
} else {
statusType = 'error';
label = t('translation|Error');
if (!state) {
return [stateDetails, label, statusType];
}
}

const tooltipID = 'container-state-message-' + container.name;
if (!!state.waiting) {
stateDetails = state.waiting;
statusType = 'warning';
label = t('translation|Waiting');
} else if (!!state.running) {
statusType = 'success';
label = t('translation|Running');
} else if (!!state.terminated) {
stateDetails = state.terminated;
if (state.terminated.exitCode === 0) {
statusType = '';
label = state.terminated.reason;
} else {
statusType = 'error';
label = t('translation|Error');
}
}

return [stateDetails, label, statusType];
}, [state]);

const tooltipID = React.useMemo(
() => 'container-state-message-' + (container?.name ?? ''),
[container]
);

if (!state || !container) {
return null;
}

return (
<Box>
<Box>
<StatusLabel
status={statusType}
aria-describedby={!!state?.message ? tooltipID : undefined}
aria-describedby={!!stateDetails?.message ? tooltipID : undefined}
>
{label + (state?.reason ? ` (${state.reason})` : '')}
{label + (stateDetails?.reason ? ` (${stateDetails.reason})` : '')}
</StatusLabel>
{!!state && state.message && (
<LightTooltip role="tooltip" title={state.message} interactive id={tooltipID}>
{!!stateDetails && stateDetails.message && (
<LightTooltip role="tooltip" title={stateDetails.message} interactive id={tooltipID}>
<Box aria-label="hidden" display="inline" px={1} style={{ verticalAlign: 'bottom' }}>
<Icon icon="mdi:alert-outline" width="1.3rem" height="1.3rem" aria-label="hidden" />
</Box>
Expand All @@ -709,6 +736,40 @@ export function ContainerInfo(props: ContainerInfoProps) {
);
}

function StatusValue(props: {
rows: { name: string; value: string | number; hide?: boolean }[];
}) {
const { rows } = props;

const rowsToDisplay = React.useMemo(() => {
return rows.filter(({ hide }) => !hide);
}, [rows]);

if (rowsToDisplay.length === 0) {
return null;
}

return (
<Box>
{rowsToDisplay.map(({ name, value }) => {
const id = useId('status-value-');
return (
<Grid container spacing={2} direction="row" key={id}>
<Grid item>
<Typography id={id} color="textSecondary">
{name}
</Typography>
</Grid>
<Grid item>
<Typography aria-labelledby={id}>{value}</Typography>
</Grid>
</Grid>
);
})}
</Box>
);
}

function containerRows() {
const env: { [name: string]: string } = {};
(container.env || []).forEach(envVar => {
Expand All @@ -734,7 +795,7 @@ export function ContainerInfo(props: ContainerInfoProps) {
},
{
name: t('translation|Status'),
value: getContainerStatusLabel(),
value: <ContainerStatusLabel state={status?.state} container={container} />,
hide: !status,
},
{
Expand All @@ -757,6 +818,38 @@ export function ContainerInfo(props: ContainerInfoProps) {
value: status?.restartCount,
hide: !status,
},
{
name: t('translation|Last State'),
value: (
<Grid container direction="column" spacing={1}>
<Grid item>
<ContainerStatusLabel state={status?.lastState} container={container} />
</Grid>
<Grid item>
<StatusValue
rows={[
{
name: t('translation|Exit Code'),
value: status?.lastState?.terminated?.exitCode,
hide: !status?.lastState?.terminated,
},
{
name: t('translation|Started'),
value: lastStateStartedDate,
hide: !lastStateStartedDate,
},
{
name: t('translation|Finished'),
value: lastStateFinishDate,
hide: !lastStateFinishDate,
},
]}
/>
</Grid>
</Grid>
),
hide: Object.keys(status?.lastState ?? {}).length === 0,
},
{
name: t('Container ID'),
value: status?.containerID,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1847,12 +1847,12 @@ exports[`Storyshots Namespace/DetailsView Active 1`] = `
Restarts
</div>
<span
aria-label="Sort by Restarts descending"
aria-label="Sort by Restarts ascending"
class="MuiBadge-root css-1c32n2y-MuiBadge-root"
data-mui-internal-clone-element="true"
>
<span
aria-label="Sort by Restarts descending"
aria-label="Sort by Restarts ascending"
class="MuiButtonBase-root MuiTableSortLabel-root Mui-active css-118d58w-MuiButtonBase-root-MuiTableSortLabel-root"
role="button"
tabindex="0"
Expand Down Expand Up @@ -2314,7 +2314,7 @@ exports[`Storyshots Namespace/DetailsView Active 1`] = `
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignLeft MuiTableCell-sizeMedium css-ia5azj-MuiTableCell-root"
data-index="1"
>
0
1 (3mo ago)
</td>
<td
class="MuiTableCell-root MuiTableCell-body MuiTableCell-alignLeft MuiTableCell-sizeMedium css-l8pc90-MuiTableCell-root"
Expand Down
Loading

0 comments on commit 126869e

Please sign in to comment.