diff --git a/frontend/src/components/cronjob/Details.tsx b/frontend/src/components/cronjob/Details.tsx index 5687d52030..f576f43127 100644 --- a/frontend/src/components/cronjob/Details.tsx +++ b/frontend/src/components/cronjob/Details.tsx @@ -10,14 +10,13 @@ import { InputLabel, } from '@mui/material'; import _ from 'lodash'; -import { useEffect, useState } from 'react'; +import { useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useDispatch } from 'react-redux'; import { useParams } from 'react-router-dom'; import { apply } from '../../lib/k8s/apiProxy'; import CronJob from '../../lib/k8s/cronJob'; import Job from '../../lib/k8s/job'; -import { KubeObjectInterface } from '../../lib/k8s/KubeObject'; import { clusterAction } from '../../redux/clusterActionSlice'; import { AppDispatch } from '../../redux/stores/store'; import { ActionButton } from '../common'; @@ -26,68 +25,70 @@ import AuthVisible from '../common/Resource/AuthVisible'; import { JobsListRenderer } from '../job/List'; import { getLastScheduleTime, getSchedule } from './List'; -function SpawnJobDialog(props: { - cronJob: CronJob; - applyFunc: (newItem: KubeObjectInterface) => Promise; - openJobDialog: boolean; - setOpenJobDialog: (open: boolean) => void; -}) { - const { cronJob, openJobDialog, setOpenJobDialog, applyFunc } = props; +// method to generate a unique string +const uniqueString = () => { + const timestamp = Date.now().toString(36); + const randomNum = Math.random().toString(36).substr(2, 5); + return `${timestamp}-${randomNum}`; +}; + +function SpawnJobDialog(props: { cronJob: CronJob; onClose: () => void }) { + const { cronJob, onClose } = props; const { namespace } = useParams<{ namespace: string }>(); const { t } = useTranslation(['translation']); const dispatch: AppDispatch = useDispatch(); - // method to generate a unique string - const uniqueString = () => { - const timestamp = Date.now().toString(36); - const randomNum = Math.random().toString(36).substr(2, 5); - return `${timestamp}-${randomNum}`; - }; - - const job = _.cloneDeep(cronJob.spec.jobTemplate); const [jobName, setJobName] = useState( - `${cronJob?.metadata?.name}-manual-spawn-${uniqueString()}` + () => `${cronJob?.metadata?.name}-manual-spawn-${uniqueString()}` ); - // set all the fields that are assumed on the jobTemplate - job.kind = 'Job'; - job.metadata = _.cloneDeep(job.metadata) || {}; - job.metadata.namespace = namespace; - job.apiVersion = 'batch/v1'; - job.metadata.name = jobName; - job.metadata.annotations = { - ...job.metadata.annotations, - 'cronjob.kubernetes.io/instantiate': 'manual', - }; - if (!!cronJob.jsonData) { - job.metadata.ownerReferences = [ - { - apiVersion: cronJob.jsonData.apiVersion, - blockOwnerDeletion: true, - controller: true, - kind: cronJob.jsonData.kind, - name: cronJob.metadata.name, - uid: cronJob.metadata.uid, - }, - ]; - } - - function handleClose() { - setOpenJobDialog(false); + function handleSpawn() { + const job = _.cloneDeep(cronJob.spec.jobTemplate); + // set all the fields that are assumed on the jobTemplate + job.kind = 'Job'; + job.metadata = _.cloneDeep(job.metadata) || {}; + job.metadata.namespace = namespace; + job.apiVersion = 'batch/v1'; + job.metadata.name = jobName; + job.metadata.annotations = { + ...job.metadata.annotations, + 'cronjob.kubernetes.io/instantiate': 'manual', + }; + if (!!cronJob.jsonData) { + job.metadata.ownerReferences = [ + { + apiVersion: cronJob.jsonData.apiVersion, + blockOwnerDeletion: true, + controller: true, + kind: cronJob.jsonData.kind, + name: cronJob.metadata.name, + uid: cronJob.metadata.uid, + }, + ]; + } + onClose(); + dispatch( + clusterAction(() => apply(job), { + startMessage: t('translation|Spawning Job {{ newItemName }}…', { + newItemName: jobName, + }), + successMessage: t('translation|Job {{ newItemName }} spawned', { + newItemName: jobName, + }), + errorMessage: t('translation|Failed to spawn Job {{ newItemName }}', { + newItemName: jobName, + }), + }) + ); } return ( - + {t('translation|Spawn Job')} {t('translation|This will trigger a new Job based on the CronJob {{ name }}', { - name, + name: cronJob.getName(), })} @@ -105,28 +106,10 @@ function SpawnJobDialog(props: { /> - - @@ -137,138 +120,86 @@ function SpawnJobDialog(props: { export default function CronJobDetails(props: { name?: string; namespace?: string }) { const params = useParams<{ namespace: string; name: string }>(); const { name = params.name, namespace = params.namespace } = props; - const { t, i18n } = useTranslation('glossary'); - const [jobs, jobsError] = Job.useList({ namespace }); - const [cronJob, setCronJob] = useState(null); - const [isCronSuspended, setIsCronSuspended] = useState(false); - const [isCheckingCronSuspendStatus, setIsCheckingCronSuspendStatus] = useState(true); - const [openJobDialog, setOpenJobDialog] = useState(false); + const { t, i18n } = useTranslation('glossary'); const dispatch: AppDispatch = useDispatch(); - useEffect(() => { - if (cronJob) { - setIsCronSuspended(cronJob.spec.suspend); - setIsCheckingCronSuspendStatus(false); - } - }, [cronJob]); - - function filterOwnedJobs(jobs?: Job[] | null) { - if (!jobs) { - return null; - } - - return jobs.filter(job => { - type OwnerRef = { - name: string; - kind: string; - }; - return !!job.metadata?.ownerReferences?.find( - (ownerRef: OwnerRef) => ownerRef.kind === 'CronJob' && ownerRef.name === name - ); - }); - } - - const ownedJobs = filterOwnedJobs(jobs); - - function applyFunc(newItem: KubeObjectInterface): Promise { - if (newItem.kind === 'CronJob') { - setIsCheckingCronSuspendStatus(true); - } else if (newItem.kind === 'Job') { - setOpenJobDialog(false); - } - const result = apply(newItem).finally(() => { - setIsCheckingCronSuspendStatus(false); - }) as unknown; - - return result as Promise; - } - - function PauseResumeAction() { - if (!cronJob) { - return null; - } - return ( - { - handleCron(cronJob, !isCronSuspended); - }} - icon={isCronSuspended ? 'mdi:play-circle' : 'mdi:pause-circle'} - iconButtonProps={{ - disabled: isCheckingCronSuspendStatus, - }} - /> - ); - } + const [jobs, jobsError] = Job.useList({ namespace }); + const [cronJob] = CronJob.useGet(name, namespace); + const [isSpawnDialogOpen, setIsSpawnDialogOpen] = useState(false); + const [isPendingSuspend, setIsPendingSuspend] = useState(false); + const isCronSuspended = cronJob?.spec.suspend; + + const ownedJobs = useMemo( + () => + jobs?.filter(job => + job.metadata.ownerReferences?.find(ref => ref.kind === 'CronJob' && ref.name === name) + ) ?? [], + [jobs, name] + ); - function handleCron(cronJob: CronJob, suspend: boolean) { - const clonedCronJob = _.cloneDeep(cronJob); - clonedCronJob.spec.suspend = suspend; - setIsCheckingCronSuspendStatus(true); + function applySuspend(cronJob: CronJob, suspend: boolean) { + setIsPendingSuspend(true); dispatch( - clusterAction(() => applyFunc(clonedCronJob.jsonData), { - startMessage: suspend - ? t('translation|Suspending CronJob {{ newItemName }}…', { - newItemName: clonedCronJob.metadata.name, - }) - : t('translation|Resuming CronJob {{ newItemName }}…', { - newItemName: clonedCronJob.metadata.name, - }), - cancelledMessage: suspend - ? t('translation|Cancelled suspending CronJob {{ newItemName }}.', { - newItemName: clonedCronJob.metadata.name, - }) - : t('translation|Cancelled resuming CronJob {{ newItemName }}.', { - newItemName: clonedCronJob.metadata.name, - }), - successMessage: suspend - ? t('translation|Suspended CronJob {{ newItemName }}.', { - newItemName: clonedCronJob.metadata.name, - }) - : t('translation|Resumed CronJob {{ newItemName }}.', { - newItemName: clonedCronJob.metadata.name, - }), - errorMessage: suspend - ? t('translation|Failed to suspend CronJob {{ newItemName }}.', { - newItemName: clonedCronJob.metadata.name, - }) - : t('translation|Failed to resume CronJob {{ newItemName }}.', { - newItemName: clonedCronJob.metadata.name, - }), - }) + clusterAction( + () => cronJob.patch({ spec: { suspend } }).finally(() => setIsPendingSuspend(false)), + { + cancelCallback: () => setIsPendingSuspend(false), + startMessage: suspend + ? t('translation|Suspending CronJob {{ newItemName }}…', { + newItemName: cronJob.metadata.name, + }) + : t('translation|Resuming CronJob {{ newItemName }}…', { + newItemName: cronJob.metadata.name, + }), + cancelledMessage: suspend + ? t('translation|Cancelled suspending CronJob {{ newItemName }}.', { + newItemName: cronJob.metadata.name, + }) + : t('translation|Cancelled resuming CronJob {{ newItemName }}.', { + newItemName: cronJob.metadata.name, + }), + successMessage: suspend + ? t('translation|Suspended CronJob {{ newItemName }}.', { + newItemName: cronJob.metadata.name, + }) + : t('translation|Resumed CronJob {{ newItemName }}.', { + newItemName: cronJob.metadata.name, + }), + errorMessage: suspend + ? t('translation|Failed to suspend CronJob {{ newItemName }}.', { + newItemName: cronJob.metadata.name, + }) + : t('translation|Failed to resume CronJob {{ newItemName }}.', { + newItemName: cronJob.metadata.name, + }), + } + ) ); } - const actions = []; - - actions.push( - cronJob && ( - - { - setOpenJobDialog(true); - }} - icon="mdi:lightning-bolt-circle" - /> - {openJobDialog && ( - + setIsSpawnDialogOpen(true)} + icon="mdi:lightning-bolt-circle" /> - )} - - ) - ); - - actions.push( - - - - ); + {isSpawnDialogOpen && ( + setIsSpawnDialogOpen(false)} /> + )} + , + + applySuspend(cronJob, !isCronSuspended)} + icon={isCronSuspended ? 'mdi:play-circle' : 'mdi:pause-circle'} + iconButtonProps={{ disabled: isPendingSuspend }} + /> + , + ] + : []; return ( setCronJob(cronJob)} extraInfo={item => item && [ {