add cronjobs/jobs to k8s tab

Signed-off-by: Andrew Tran <atran@brex.com>
This commit is contained in:
Andrew Tran
2021-11-23 09:32:59 -06:00
parent dd1faa49eb
commit c8f72b29eb
14 changed files with 455 additions and 22 deletions
+1
View File
@@ -42,6 +42,7 @@
"@material-ui/core": "^4.12.2",
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.57",
"cronstrue": "^1.122.0",
"js-yaml": "^4.0.0",
"lodash": "^4.17.21",
"luxon": "^2.0.2",
@@ -29,6 +29,7 @@ import { DeploymentsAccordions } from '../DeploymentsAccordions';
import { groupResponses } from '../../utils/response';
import { IngressesAccordions } from '../IngressesAccordions';
import { ServicesAccordions } from '../ServicesAccordions';
import { CronJobsAccordions } from '../CronJobsAccordions';
import { CustomResources } from '../CustomResources';
import {
ClusterContext,
@@ -133,6 +134,9 @@ export const Cluster = ({ clusterObjects, podsWithErrors }: ClusterProps) => {
<Grid item>
<ServicesAccordions />
</Grid>
<Grid item>
<CronJobsAccordions />
</Grid>
</Grid>
</AccordionDetails>
</Accordion>
@@ -0,0 +1,124 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useContext } from 'react';
import {
Accordion,
AccordionDetails,
AccordionSummary,
Divider,
Grid,
} from '@material-ui/core';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { V1CronJob, V1Job } from '@kubernetes/client-node';
import { JobsAccordions } from '../JobsAccordions';
import { CronJobDrawer } from './CronJobsDrawer';
import { getOwnedResources } from '../../utils/owner';
import { GroupedResponsesContext } from '../../hooks';
import { StatusError, StatusOK } from '@backstage/core-components';
import cronstrue from 'cronstrue';
type CronJobsAccordionsProps = {
children?: React.ReactNode;
};
type CronJobAccordionProps = {
cronJob: V1CronJob;
ownedJobs: V1Job[];
children?: React.ReactNode;
};
type CronJobSummaryProps = {
cronJob: V1CronJob;
children?: React.ReactNode;
};
const CronJobSummary = ({ cronJob }: CronJobSummaryProps) => {
return (
<Grid
container
direction="row"
justifyContent="flex-start"
alignItems="center"
>
<Grid xs={3} item>
<CronJobDrawer cronJob={cronJob} />
</Grid>
<Grid item xs={1}>
<Divider style={{ height: '5em' }} orientation="vertical" />
</Grid>
<Grid
item
container
xs={5}
direction="column"
justifyContent="flex-start"
alignItems="flex-start"
>
<Grid item>
{cronJob.spec?.suspend ? (
<StatusError>Suspended</StatusError>
) : (
<StatusOK>Active</StatusOK>
)}
</Grid>
<Grid item>
Schedule:{' '}
{cronJob.spec?.schedule
? `${cronJob.spec.schedule} (
${cronstrue.toString(cronJob.spec.schedule)})`
: '???'}
</Grid>
</Grid>
</Grid>
);
};
const CronJobAccordion = ({ cronJob, ownedJobs }: CronJobAccordionProps) => {
return (
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<CronJobSummary cronJob={cronJob} />
</AccordionSummary>
<AccordionDetails>
<JobsAccordions jobs={ownedJobs.reverse()} />
</AccordionDetails>
</Accordion>
);
};
export const CronJobsAccordions = ({}: CronJobsAccordionsProps) => {
const groupedResponses = useContext(GroupedResponsesContext);
return (
<Grid
container
direction="column"
justifyContent="flex-start"
alignItems="flex-start"
>
{groupedResponses.cronJobs.map((cronJob, i) => (
<Grid container item key={i} xs>
<Grid item xs>
<CronJobAccordion
ownedJobs={getOwnedResources(cronJob, groupedResponses.jobs)}
cronJob={cronJob}
/>
</Grid>
</Grid>
))}
</Grid>
);
};
@@ -0,0 +1,67 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { V1CronJob } from '@kubernetes/client-node';
import { KubernetesDrawer } from '../KubernetesDrawer/KubernetesDrawer';
import { Typography, Grid, Chip } from '@material-ui/core';
export const CronJobDrawer = ({
cronJob,
expanded,
}: {
cronJob: V1CronJob;
expanded?: boolean;
}) => {
const namespace = cronJob.metadata?.namespace;
return (
<KubernetesDrawer
object={cronJob}
expanded={expanded}
kind="CronJob"
renderObject={(cronJobObj: V1CronJob) => ({
schedule: cronJobObj.spec?.schedule ?? '???',
startingDeadlineSeconds:
cronJobObj.spec?.startingDeadlineSeconds ?? '???',
concurrencyPolicy: cronJobObj.spec?.concurrencyPolicy ?? '???',
lastScheduleTime: cronJobObj.status?.lastScheduleTime ?? '???',
})}
>
<Grid
container
direction="column"
justifyContent="flex-start"
alignItems="flex-start"
spacing={0}
>
<Grid item>
<Typography variant="h5">
{cronJob.metadata?.name ?? 'unknown object'}
</Typography>
</Grid>
<Grid item>
<Typography color="textSecondary" variant="body1">
CronJob
</Typography>
</Grid>
{namespace && (
<Grid item>
<Chip size="small" label={`namespace: ${namespace}`} />
</Grid>
)}
</Grid>
</KubernetesDrawer>
);
};
@@ -0,0 +1,16 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { CronJobsAccordions } from './CronJobsAccordions';
@@ -36,11 +36,12 @@ import {
getOwnedPodsThroughReplicaSets,
getMatchingHpa,
} from '../../utils/owner';
import { containersReady, totalRestarts } from '../../utils/pod';
import {
GroupedResponsesContext,
PodNamesWithErrorsContext,
} from '../../hooks';
import { StatusError, StatusOK } from '@backstage/core-components';
import { StatusError, StatusOK, TableColumn } from '@backstage/core-components';
type DeploymentsAccordionsProps = {
children?: React.ReactNode;
@@ -61,6 +62,20 @@ type DeploymentSummaryProps = {
children?: React.ReactNode;
};
const deploymentPodColumns: TableColumn<V1Pod>[] = [
{
title: 'containers ready',
align: 'center',
render: containersReady,
},
{
title: 'total restarts',
align: 'center',
render: totalRestarts,
type: 'numeric',
},
];
const DeploymentSummary = ({
deployment,
numberOfCurrentPods,
@@ -161,7 +176,7 @@ const DeploymentAccordion = ({
/>
</AccordionSummary>
<AccordionDetails>
<PodsTable pods={ownedPods} />
<PodsTable pods={ownedPods} extraColumns={deploymentPodColumns} />
</AccordionDetails>
</Accordion>
);
@@ -0,0 +1,123 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useContext } from 'react';
import {
Accordion,
AccordionDetails,
AccordionSummary,
Divider,
Grid,
} from '@material-ui/core';
import ExpandMoreIcon from '@material-ui/icons/ExpandMore';
import { V1Job, V1Pod } from '@kubernetes/client-node';
import { PodsTable } from '../Pods';
import { JobDrawer } from './JobsDrawer';
import { getOwnedResources } from '../../utils/owner';
import { GroupedResponsesContext } from '../../hooks';
import {
StatusError,
StatusOK,
StatusPending,
} from '@backstage/core-components';
type JobsAccordionsProps = {
jobs: V1Job[];
children?: React.ReactNode;
};
type JobAccordionProps = {
job: V1Job;
ownedPods: V1Pod[];
children?: React.ReactNode;
};
type JobSummaryProps = {
job: V1Job;
children?: React.ReactNode;
};
const JobSummary = ({ job }: JobSummaryProps) => {
return (
<Grid
container
direction="row"
justifyContent="flex-start"
alignItems="center"
>
<Grid xs={3} item>
<JobDrawer job={job} />
</Grid>
<Grid item xs={1}>
<Divider style={{ height: '5em' }} orientation="vertical" />
</Grid>
<Grid
item
container
xs={8}
direction="column"
justifyContent="flex-start"
alignItems="flex-start"
>
<Grid item>
{job.status?.succeeded && <StatusOK>Succeeded</StatusOK>}
{job.status?.active && <StatusPending>Running</StatusPending>}
{job.status?.failed && <StatusError>Failed</StatusError>}
</Grid>
<Grid item>Start time: {job.status?.startTime}</Grid>
{job.status?.completionTime && (
<Grid item>Completion time: {job.status.completionTime}</Grid>
)}
</Grid>
</Grid>
);
};
const JobAccordion = ({ job, ownedPods }: JobAccordionProps) => {
return (
<Accordion TransitionProps={{ unmountOnExit: true }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<JobSummary job={job} />
</AccordionSummary>
<AccordionDetails>
<PodsTable pods={ownedPods} />
</AccordionDetails>
</Accordion>
);
};
export const JobsAccordions = ({ jobs }: JobsAccordionsProps) => {
const groupedResponses = useContext(GroupedResponsesContext);
return (
<Grid
container
direction="column"
justifyContent="flex-start"
alignItems="flex-start"
>
{jobs.map((job, i) => (
<Grid container item key={i} xs>
<Grid item xs>
<JobAccordion
ownedPods={getOwnedResources(job, groupedResponses.pods)}
job={job}
/>
</Grid>
</Grid>
))}
</Grid>
);
};
@@ -0,0 +1,62 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { V1Job } from '@kubernetes/client-node';
import { KubernetesDrawer } from '../KubernetesDrawer/KubernetesDrawer';
import { Typography, Grid } from '@material-ui/core';
export const JobDrawer = ({
job,
expanded,
}: {
job: V1Job;
expanded?: boolean;
}) => {
return (
<KubernetesDrawer
object={job}
expanded={expanded}
kind="Job"
renderObject={(jobObj: V1Job) => {
return {
parallelism: jobObj.spec?.parallelism ?? '???',
completions: jobObj.spec?.completions ?? '???',
backOffLimit: jobObj.spec?.backoffLimit ?? '???',
startTime: jobObj.status?.startTime ?? '???',
};
}}
>
<Grid
container
direction="column"
justifyContent="flex-start"
alignItems="flex-start"
spacing={0}
>
<Grid item>
<Typography variant="h6">
{job.metadata?.name ?? 'unknown object'}
</Typography>
</Grid>
<Grid item>
<Typography color="textSecondary" variant="body1">
Job
</Typography>
</Grid>
</Grid>
</KubernetesDrawer>
);
};
@@ -0,0 +1,16 @@
/*
* Copyright 2021 The Backstage Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export { JobsAccordions } from './JobsAccordions';
+6 -20
View File
@@ -17,14 +17,10 @@
import React from 'react';
import { V1Pod } from '@kubernetes/client-node';
import { PodDrawer } from './PodDrawer';
import {
containersReady,
containerStatuses,
totalRestarts,
} from '../../utils/pod';
import { containerStatuses } from '../../utils/pod';
import { Table, TableColumn } from '@backstage/core-components';
const columns: TableColumn<V1Pod>[] = [
const DEFAULT_COLUMNS: TableColumn<V1Pod>[] = [
{
title: 'name',
highlight: true,
@@ -34,29 +30,19 @@ const columns: TableColumn<V1Pod>[] = [
title: 'phase',
render: (pod: V1Pod) => pod.status?.phase ?? 'unknown',
},
{
title: 'containers ready',
align: 'center',
render: containersReady,
},
{
title: 'total restarts',
align: 'center',
render: totalRestarts,
type: 'numeric',
},
{
title: 'status',
render: containerStatuses,
},
];
type DeploymentTablesProps = {
type PodsTablesProps = {
pods: V1Pod[];
extraColumns?: TableColumn<V1Pod>[];
children?: React.ReactNode;
};
export const PodsTable = ({ pods }: DeploymentTablesProps) => {
export const PodsTable = ({ pods, extraColumns = [] }: PodsTablesProps) => {
const tableStyle = {
minWidth: '0',
width: '100%',
@@ -67,7 +53,7 @@ export const PodsTable = ({ pods }: DeploymentTablesProps) => {
<Table
options={{ paging: true, search: false }}
data={pods}
columns={columns}
columns={DEFAULT_COLUMNS.concat(extraColumns)}
/>
</div>
);
@@ -24,5 +24,7 @@ export const GroupedResponsesContext = React.createContext<GroupedResponses>({
configMaps: [],
horizontalPodAutoscalers: [],
ingresses: [],
jobs: [],
cronJobs: [],
customResources: [],
});
+4
View File
@@ -22,6 +22,8 @@ import {
V1Service,
V1ConfigMap,
ExtensionsV1beta1Ingress,
V1Job,
V1CronJob,
} from '@kubernetes/client-node';
export interface DeploymentResources {
@@ -35,6 +37,8 @@ export interface GroupedResponses extends DeploymentResources {
services: V1Service[];
configMaps: V1ConfigMap[];
ingresses: ExtensionsV1beta1Ingress[];
jobs: V1Job[];
cronJobs: V1CronJob[];
customResources: any[];
}
+8
View File
@@ -45,6 +45,12 @@ export const groupResponses = (
case 'ingresses':
prev.ingresses.push(...next.resources);
break;
case 'jobs':
prev.jobs.push(...next.resources);
break;
case 'cronjobs':
prev.cronJobs.push(...next.resources);
break;
case 'customresources':
prev.customResources.push(...next.resources);
break;
@@ -60,6 +66,8 @@ export const groupResponses = (
configMaps: [],
horizontalPodAutoscalers: [],
ingresses: [],
jobs: [],
cronJobs: [],
customResources: [],
} as GroupedResponses,
);