Skip to content

Commit

Permalink
feat: cronjob implement job (#5093)
Browse files Browse the repository at this point in the history
* feat: implement job

* fix: refresh

* fix: auto refresh

* feat: job active
  • Loading branch information
zijiren233 committed Sep 23, 2024
1 parent f3efe28 commit 582dd4f
Show file tree
Hide file tree
Showing 11 changed files with 206 additions and 60 deletions.
8 changes: 6 additions & 2 deletions frontend/providers/cronjob/public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -197,5 +197,9 @@
"The password cannot be empty": "The password cannot be empty",
"Deploy": "Deploy",
"Env Placeholder": "one per line, key and value separated by colon or equals sign, e.g.:\nmongoUrl=127.0.0.1:8000\nredisUrl:127.0.0.0:8001\n-env1 =test",
"Environment Variables": "Environment Variables"
}
"Environment Variables": "Environment Variables",
"implement": "Implement",
"job_implement_success": "Job has been executed",
"job_implement_error": "An error occurred while executing the job",
"Executing": "Executing"
}
8 changes: 6 additions & 2 deletions frontend/providers/cronjob/public/locales/zh/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -243,5 +243,9 @@
"The password cannot be empty": "密码不能为空",
"Env Placeholder": "环境变量,每行一个,可用冒号或等号分隔,例如:\nmongoUrl=127.0.0.1:8000\nredisUrl:127.0.0.0:8001\n- env1=test",
"Edit Environment Variables": "编辑环境变量",
"Environment Variables": "环境变量"
}
"Environment Variables": "环境变量",
"implement": "执行",
"job_implement_success": "Job 执行成功",
"job_implement_error": "Job 执行失败",
"Executing": "执行中"
}
3 changes: 3 additions & 0 deletions frontend/providers/cronjob/src/api/job.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ export const updateCronJobStatus = ({
type: 'Stop' | 'Start';
}) => POST('/api/cronjob/startAndStop', { jobName, type });

export const implementJob = ({ jobName }: { jobName: string }) =>
POST('/api/cronjob/implementJob', { jobName });

export const getJobList = (name: string) =>
GET(`/api/job/list?cronJobName=${name}`).then(adaptJobItemList);

Expand Down
40 changes: 40 additions & 0 deletions frontend/providers/cronjob/src/pages/api/cronjob/implementJob.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { authSession } from '@/services/backend/auth';
import { getK8s } from '@/services/backend/kubernetes';
import { jsonRes } from '@/services/backend/response';
import * as k8s from '@kubernetes/client-node';
import type { NextApiRequest, NextApiResponse } from 'next';

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
try {
const { jobName } = req.body;
const { namespace, k8sBatch } = await getK8s({
kubeconfig: await authSession(req)
});

const cronJob = await k8sBatch.readNamespacedCronJob(jobName, namespace);
if (!cronJob.body) {
throw new Error('CronJob not found');
}

const jobSpec = cronJob.body.spec?.jobTemplate.spec;
const job = {
metadata: {
name: `${jobName}-manual-${Date.now()}`,
namespace: namespace,
annotations: {
'cronjob.kubernetes.io/instantiate': 'manual'
}
},
spec: jobSpec
};

const response = await k8sBatch.createNamespacedJob(namespace, job);

jsonRes(res, { data: response.body });
} catch (err: any) {
jsonRes(res, {
code: 500,
error: err
});
}
}
Original file line number Diff line number Diff line change
@@ -1,29 +1,18 @@
import { getJobListEventsAndLogs } from '@/api/job';
import MyTooltip from '@/components/MyTooltip';
import { CronJobTypeList } from '@/constants/job';
import { useJobStore } from '@/store/job';
import { JobList } from '@/types/job';
import { useCopyData } from '@/utils/tools';
import { Box, Flex, Icon, Text } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'next-i18next';
import React, { useMemo } from 'react';

export default function AppBaseInfo({ appName }: { appName: string }) {
export default function AppBaseInfo({ data }: { data?: JobList }) {
const { t } = useTranslation();
const { JobDetail, loadJobDetail } = useJobStore();
const { copyData } = useCopyData();

const { data, isLoading } = useQuery(
['getJobListEventsAndLogs', appName],
() => getJobListEventsAndLogs(appName),
{
onError(err) {
console.log(err);
}
}
);

const [totalAmount, successAmount, failAmount] = useMemo(() => {
const [successAmount, failAmount] = useMemo(() => {
if (data?.total) {
return [data?.total, data?.successAmount, data.total - data.successAmount];
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,38 @@
import { getJobListEventsAndLogs, getPodLogs } from '@/api/job';
import MyIcon from '@/components/Icon';
import { JobList } from '@/types/job';
import { Box, Flex, Icon, Text } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'next-i18next';
import { useMemo, useState } from 'react';

export default function AppBaseInfo({ appName }: { appName: string }) {
export default function AppBaseInfo({
joblist,
isLoading
}: {
joblist?: JobList;
isLoading: boolean;
}) {
const { t } = useTranslation();
const [active, setActive] = useState(0);
const { data, isLoading } = useQuery(
['getJobListEventsAndLogs', appName],
() => getJobListEventsAndLogs(appName),
{
onError(err) {
console.log(err);
}
}
);
const ActivePod = useMemo(() => data?.history[active], [active, data]);
const [logs, setLogs] = useState('');
const ActivePod = useMemo(() => joblist?.history[active], [active, joblist]);
useQuery(
['getPodLogs', ActivePod?.podName],
() => ActivePod?.podName && getPodLogs(ActivePod.podName),
{
enabled: !!ActivePod?.podName,
onSuccess(data) {
if (ActivePod) {
ActivePod['logs'] = data || '';
setLogs(data || '');
}
},
onError(err) {
if (ActivePod) {
ActivePod['logs'] = typeof err === 'string' ? err : '';
setLogs(typeof err === 'string' ? err : '');
}
}
},
refetchInterval: ActivePod?.status === 'active' ? 1000 : false
}
);

Expand All @@ -53,11 +53,11 @@ export default function AppBaseInfo({ appName }: { appName: string }) {
</Icon>
<Text ml="12px">{t('Historical Mission')}</Text>
</Flex>
<Text>{data?.total}</Text>
<Text>{joblist?.total}</Text>
</Flex>
<Flex flex={1} overflow={'hidden'}>
<Box flex={'0 0 300px'} overflowY={'auto'} borderRight={'1px solid #EFF0F1'} pt="14px">
{data?.history?.map((jobItem, i) => (
{joblist?.history?.map((jobItem, i) => (
<Box
cursor={'pointer'}
px="20px"
Expand All @@ -76,7 +76,7 @@ export default function AppBaseInfo({ appName }: { appName: string }) {
left: '-1.5px',
w: '2px',
h: '100%',
backgroundColor: `${i === data.history.length - 1 ? 'transparent' : '#DCE7F1'}`
backgroundColor: `${i === joblist.history.length - 1 ? 'transparent' : '#DCE7F1'}`
}}
_before={{
content: '""',
Expand All @@ -89,11 +89,23 @@ export default function AppBaseInfo({ appName }: { appName: string }) {
backgroundColor: '#fff',
border: '2px solid',
zIndex: 2,
borderColor: jobItem.status ? '#33BABB' : '#FF8492'
borderColor:
jobItem.status === 'succeeded'
? '#33BABB'
: jobItem.status === 'failed'
? '#FF8492'
: '#FF8492'
}}
>
<Flex mb={2} alignItems={'center'} fontWeight={'bold'}>
{jobItem.status ? t('base.Success') : t('Pause Error')}, {t('Executed')}
{jobItem.status === 'succeeded'
? t('base.Success')
: jobItem.status === 'failed'
? t('Pause Error')
: jobItem.status === 'active'
? t('Executing')
: t('Running')}
, {t('Executed')}
{jobItem.startTime}
</Flex>
<Box color={'blackAlpha.700'}>
Expand Down Expand Up @@ -122,7 +134,7 @@ export default function AppBaseInfo({ appName }: { appName: string }) {
<Text>
{t('Log')} (pod: {ActivePod?.podName})
</Text>
<Text mt="12px">{ActivePod?.logs}</Text>
<Text mt="12px">{logs}</Text>
</Flex>
) : (
<Flex
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { updateCronJobStatus } from '@/api/job';
import { implementJob, updateCronJobStatus } from '@/api/job';
import MyIcon from '@/components/Icon';
import StatusTag from '@/components/StatusTag';
import { CronJobStatusMap } from '@/constants/job';
Expand All @@ -19,14 +19,16 @@ const Header = ({
isPause = false,
isLargeScreen = true,
setShowSlider,
refetch
refetchCronJob,
refetchJob
}: {
appStatus: CronJobStatusMapType;
appName?: string;
isPause?: boolean;
isLargeScreen: boolean;
setShowSlider: Dispatch<boolean>;
refetch: () => void;
refetchCronJob: () => void;
refetchJob: () => void;
}) => {
const { t } = useTranslation();
const router = useRouter();
Expand Down Expand Up @@ -64,8 +66,27 @@ const Header = ({
console.error(error);
}
setLoading(false);
refetch();
}, [appName, refetch, toast]);
refetchCronJob();
}, [appName, refetchCronJob, toast]);

const handleRunJob = useCallback(async () => {
try {
setLoading(true);
await implementJob({ jobName: appName });
toast({
title: t('job_implement_success'),
status: 'success'
});
} catch (error: any) {
toast({
title: typeof error === 'string' ? error : error.message || t('job_implement_error'),
status: 'error'
});
console.error(error);
}
setLoading(false);
refetchJob();
}, [appName, refetchJob, toast]);

const handleStartApp = useCallback(async () => {
try {
Expand All @@ -83,8 +104,8 @@ const Header = ({
console.error(error);
}
setLoading(false);
refetch();
}, [appName, refetch, toast]);
refetchCronJob();
}, [appName, refetchCronJob, toast]);

return (
<Flex h={'86px'} alignItems={'center'}>
Expand Down Expand Up @@ -113,6 +134,18 @@ const Header = ({
<Box flex={1} />

{/* btns */}
<Button
mr={5}
h={'40px'}
borderColor={'myGray.200'}
leftIcon={<MyIcon name="continue" w={'14px'} />}
isLoading={loading}
variant={'base'}
bg={'white'}
onClick={handleRunJob}
>
{t('implement')}
</Button>
{isPause ? (
<Button
mr={5}
Expand Down
44 changes: 34 additions & 10 deletions frontend/providers/cronjob/src/pages/job/detail/index.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { useLoading } from '@/hooks/useLoading';
import { getJobListEventsAndLogs } from '@/api/job';
import { useToast } from '@/hooks/useToast';
import { useGlobalStore } from '@/store/global';
import { useJobStore } from '@/store/job';
import { serviceSideProps } from '@/utils/i18n';
import { Box, Flex } from '@chakra-ui/react';
import { useQuery } from '@tanstack/react-query';
import { useMemo, useState } from 'react';
import { useMemo, useState, useCallback } from 'react';
import AppBaseInfo from './components/AppBaseInfo';
import AppMainInfo from './components/AppMainInfo';
import Header from './components/Header';
Expand All @@ -18,23 +19,42 @@ export default function DetailPage({ appName }: { appName: string }) {
const [showSlider, setShowSlider] = useState(false);
const isLargeScreen = useMemo(() => screenWidth > 1280, [screenWidth]);

const { refetch } = useQuery(['getCronJobDetail', appName], () => loadJobDetail(appName), {
const {
data,
isLoading,
refetch: refetchPods
} = useQuery(['getJobListEventsAndLogs', appName], () => getJobListEventsAndLogs(appName), {
onError(err) {
toast({
title: String(err),
status: 'error'
});
}
console.log(err);
},
refetchInterval: 3000
});

const { refetch: refetchJobDetail } = useQuery(
['getCronJobDetail', appName],
() => {
console.log('appName', appName);
return loadJobDetail(appName);
},
{
onError(err) {
toast({
title: String(err),
status: 'error'
});
}
}
);

return (
<Flex flexDirection={'column'} height={'100vh'} backgroundColor={'#F3F4F5'} px={9} pb={4}>
<Box>
<Header
appName={appName}
appStatus={JobDetail?.status}
isPause={JobDetail?.isPause}
refetch={refetch}
refetchCronJob={refetchJobDetail}
refetchJob={refetchPods}
setShowSlider={setShowSlider}
isLargeScreen={isLargeScreen}
/>
Expand All @@ -60,7 +80,7 @@ export default function DetailPage({ appName }: { appName: string }) {
transform: `translateX(${showSlider ? '0' : '-1000'}px)`
})}
>
{JobDetail ? <AppBaseInfo appName={appName} /> : <Loading loading={true} fixed={false} />}
{JobDetail ? <AppBaseInfo data={data} /> : <Loading loading={true} fixed={false} />}
</Box>
<Flex
border={'1px solid #DEE0E2'}
Expand All @@ -69,7 +89,11 @@ export default function DetailPage({ appName }: { appName: string }) {
flex={'1 1 740px'}
bg={'white'}
>
{JobDetail ? <AppMainInfo appName={appName} /> : <Loading loading={true} fixed={false} />}
{JobDetail ? (
<AppMainInfo joblist={data} isLoading={isLoading} />
) : (
<Loading loading={true} fixed={false} />
)}
</Flex>
</Flex>
{/* mask */}
Expand Down
Loading

0 comments on commit 582dd4f

Please sign in to comment.