Refactor: schedule

This commit is contained in:
Lazy 2024-12-16 01:33:53 +08:00
parent 92e63ee44a
commit c6222a5f85
8 changed files with 343 additions and 200 deletions

View File

@ -28,6 +28,7 @@ declare module 'vue' {
AFormItem: typeof import('ant-design-vue/es')['FormItem']
AInput: typeof import('ant-design-vue/es')['Input']
AInputGroup: typeof import('ant-design-vue/es')['InputGroup']
AInputNumber: typeof import('ant-design-vue/es')['InputNumber']
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
AList: typeof import('ant-design-vue/es')['List']
AListItem: typeof import('ant-design-vue/es')['ListItem']

View File

@ -0,0 +1,173 @@
import { t } from "@/lang/i18n";
import { scheduleCreate, scheduleDelete, scheduleList } from "@/services/apis/instance";
import { reportErrorMsg } from "@/tools/validator";
import type { ScheduleTaskForm } from "@/types";
import { message, notification } from "ant-design-vue";
import dayjs from "dayjs";
export function useSchedule(instanceId: string, daemonId: string) {
const createTaskTypeInterval = async (newTask: ScheduleTaskForm) => {
const arr = newTask.cycle;
let ps = Number(arr[0]);
let pm = Number(arr[1]);
let ph = Number(arr[2]);
const rs = ps + pm * 60 + ph * 60 * 60;
newTask.time = rs.toString();
await createTask(newTask);
};
const createTaskTypeCycle = async (newTask: ScheduleTaskForm) => {
const weekend = newTask.weekend;
if (!newTask.objTime) throw new Error(t("TXT_CODE_349edc57"));
if (weekend.length === 0) throw new Error(t("TXT_CODE_2fe0cc84"));
const time = newTask.objTime;
const h = time.hour();
const m = time.minute();
const s = time.second();
newTask.time = `${s} ${m} ${h} * * ${weekend.join(",")}`;
await createTask(newTask);
};
const createTaskTypeSpecify = async (newTask: ScheduleTaskForm) => {
if (!newTask.objTime) throw new Error(t("TXT_CODE_349edc57"));
const time = newTask.objTime;
const mm = time.month() + 1;
const dd = time.date();
const h = time.hour();
const m = time.minute();
const s = time.second();
newTask.time = `${s} ${m} ${h} ${dd} ${mm} *`;
await createTask(newTask);
};
const calculateIntervalFromTime = (time: string): string[] => {
const totalSeconds = Number(time);
const ph = Math.floor(totalSeconds / 3600);
const pm = Math.floor((totalSeconds % 3600) / 60);
const ps = totalSeconds % 60;
return [ps.toString(), pm.toString(), ph.toString()];
};
const calculateTimeFromCycle = (time: string) => {
const regex = /(\d+) (\d+) (\d+) \* \* (.+)/;
const match = time.match(regex);
let objTime = dayjs(),
weekend: number[] = [];
if (match) {
const s = match[1];
const m = match[2];
const h = match[3];
const w = match[4].split(",").map(Number);
const now = new Date();
now.setSeconds(Number(s));
now.setMinutes(Number(m));
now.setHours(Number(h));
objTime = dayjs(now);
weekend = w;
} else {
notification.error({ message: t("解析时间失败") });
}
return {
objTime,
weekend
};
};
const parseTaskTime = (time: string) => {
const regex = /(\d+) (\d+) (\d+) (\d+) (\d+) \*/; // 匹配 time 格式
const match = time.match(regex);
let objTime = dayjs();
if (match) {
const s = Number(match[1]);
const m = Number(match[2]);
const h = Number(match[3]);
const dd = Number(match[4]);
const mm = Number(match[5]) - 1;
const now = new Date();
now.setSeconds(s);
now.setMinutes(m);
now.setHours(h);
now.setDate(dd);
now.setMonth(mm);
objTime = dayjs(now);
} else {
notification.error({ message: t("解析时间失败") });
}
return objTime;
};
const { state: createState, execute: create } = scheduleCreate();
const createTask = async (newTask: ScheduleTaskForm) => {
try {
if (!newTask.count) newTask.count = -1;
await create({
params: {
daemonId: daemonId,
uuid: instanceId
},
data: newTask
});
} catch (err: any) {
console.error(err);
reportErrorMsg(err.message);
}
};
const { state: schedules, execute: list, isLoading: scheduleListLoading } = scheduleList();
const getScheduleList = async () => {
try {
await list({
params: {
daemonId: daemonId ?? "",
uuid: instanceId ?? ""
}
});
} catch (err: any) {
console.error(err);
reportErrorMsg(err.message);
}
};
const deleteSchedule = async (name: string, showMsg: boolean = true) => {
const { execute, state } = scheduleDelete();
try {
await execute({
params: {
daemonId: daemonId ?? "",
uuid: instanceId ?? "",
task_name: name
}
});
if (state.value) {
showMsg && message.success(t("TXT_CODE_28190dbc"));
getScheduleList();
}
} catch (err: any) {
console.error(err);
reportErrorMsg(err.message);
}
};
return {
createTaskTypeInterval,
createTaskTypeCycle,
createTaskTypeSpecify,
calculateIntervalFromTime,
calculateTimeFromCycle,
parseTaskTime,
createTask,
createState,
getScheduleList,
scheduleListLoading,
schedules,
deleteSchedule
};
}

View File

@ -198,3 +198,5 @@ export const isInt = (x: any) => {
}
return true;
};
export const padZero = (num: string) => (num === "0" ? num : num.padStart(2, "0"));

View File

@ -105,7 +105,7 @@ export const ScheduleType = {
};
export enum ScheduleCreateType {
INTERVAL = "1",
CYCLE = "2",
SPECIFY = "3"
INTERVAL = 1,
CYCLE,
SPECIFY
}

View File

@ -1,4 +1,5 @@
/* eslint-disable no-unused-vars */
import type { Dayjs } from "dayjs";
import type {
IGlobalInstanceConfig,
ILayoutCard as GlobalLayoutCard,
@ -9,7 +10,7 @@ import type {
IQuickStartTemplate,
IQuickStartPackages
} from "../../../common/global";
import type { INSTANCE_STATUS_CODE } from "./const";
import type { INSTANCE_STATUS_CODE, ScheduleCreateType } from "./const";
export type JsonData = IJsonData;
export type MapData<T> = IMapData<T>;
@ -222,10 +223,17 @@ export interface Schedule {
export interface NewScheduleTask {
name: string;
count: string;
count: number;
time: string;
action: string;
type: string;
type: ScheduleCreateType;
}
export interface ScheduleTaskForm extends NewScheduleTask {
payload: string;
weekend: number[];
cycle: string[];
objTime: Dayjs;
}
export interface PanelStatus {

View File

@ -309,7 +309,11 @@ onUnmounted(() => {
</template>
<template #right>
<a-typography-text v-if="selectedRowKeys.length">
{{ t("TXT_CODE_7b2c5414") + ` ${String(selectedRowKeys.length)} ` + t("TXT_CODE_5cd3b4bd") }}
{{
t("TXT_CODE_7b2c5414") +
` ${String(selectedRowKeys.length)} ` +
t("TXT_CODE_5cd3b4bd")
}}
</a-typography-text>
<a-upload
@ -472,7 +476,8 @@ onUnmounted(() => {
}
"
@change="
(e) => handleTableChange({ current: e.current || 0, pageSize: e.pageSize || 0 })
(e: any) =>
handleTableChange({ current: e.current || 0, pageSize: e.pageSize || 0 })
"
>
<template #bodyCell="{ column, record }">

View File

@ -1,19 +1,19 @@
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { t } from "@/lang/i18n";
import { t, $t } from "@/lang/i18n";
import { message } from "ant-design-vue";
import { reportErrorMsg } from "@/tools/validator";
import { DeleteOutlined, FieldTimeOutlined } from "@ant-design/icons-vue";
import { DeleteOutlined, EditOutlined, FieldTimeOutlined } from "@ant-design/icons-vue";
import CardPanel from "@/components/CardPanel.vue";
import BetweenMenus from "@/components/BetweenMenus.vue";
import { useLayoutCardTools } from "@/hooks/useCardTools";
import { useAppRouters } from "@/hooks/useAppRouters";
import { scheduleList, scheduleDelete } from "@/services/apis/instance";
import type { LayoutCard, Schedule } from "@/types/index";
import { ScheduleAction, ScheduleType, ScheduleCreateType } from "@/types/const";
import NewSchedule from "@/widgets/instance/dialogs/NewSchedule.vue";
import type { AntColumnsType } from "../../types/ant";
import { useScreen } from "@/hooks/useScreen";
import { useSchedule } from "@/hooks/useSchedule";
import { padZero } from "@/tools/common";
const props = defineProps<{
card: LayoutCard;
@ -25,83 +25,36 @@ const instanceId = getMetaOrRouteValue("instanceId");
const daemonId = getMetaOrRouteValue("daemonId");
const { toPage } = useAppRouters();
const newScheduleDialog = ref<InstanceType<typeof NewSchedule>>();
const { state, execute, isLoading } = scheduleList();
const getScheduleList = async () => {
try {
await execute({
params: {
daemonId: daemonId ?? "",
uuid: instanceId ?? ""
}
});
} catch (err: any) {
console.error(err);
reportErrorMsg(err.message);
}
};
const deleteSchedule = async (name: string) => {
const { execute, state } = scheduleDelete();
try {
await execute({
params: {
daemonId: daemonId ?? "",
uuid: instanceId ?? "",
task_name: name
}
});
if (state.value) {
message.success(t("TXT_CODE_28190dbc"));
await getScheduleList();
}
} catch (err: any) {
console.error(err);
reportErrorMsg(err.message);
}
};
const { getScheduleList, schedules, scheduleListLoading, deleteSchedule } = useSchedule(
String(instanceId),
String(daemonId)
);
const rendTime = (text: string, schedule: Schedule) => {
switch (schedule.type.toString()) {
case ScheduleCreateType.INTERVAL: {
const time = Number(text);
let s = time;
let m = 0;
let h = 0;
while (s >= 60) {
s -= 60;
m += 1;
}
while (m >= 60) {
m -= 60;
h += 1;
}
return `${t("TXT_CODE_ec6d29f4")} ${h} ${t("TXT_CODE_e3db239d")} ${m} ${t(
const formatFunctions = {
[ScheduleCreateType.INTERVAL]: (t: string) => {
const time = Number(t);
const h = padZero(Math.floor(time / 3600).toString());
const m = padZero(Math.floor((time % 3600) / 60).toString());
const s = padZero((time % 60).toString());
return `${$t("TXT_CODE_ec6d29f4")} ${h} ${$t("TXT_CODE_e3db239d")} ${m} ${$t(
"TXT_CODE_3b1bb444"
)} ${s} ${t("TXT_CODE_acabc771")}`;
)} ${s} ${$t("TXT_CODE_acabc771")}`;
},
[ScheduleCreateType.CYCLE]: (time: string) => {
const [s, m, h, , , w] = time.split(" ");
return `${t("TXT_CODE_76750199")} ${w} / ${padZero(h)}:${padZero(m)}:${padZero(s)}`;
},
[ScheduleCreateType.SPECIFY]: (time: string) => {
const [s, m, h, dd, mm] = time.split(" ");
return `${mm} ${t("TXT_CODE_6cb9bb04")} ${dd} ${t("TXT_CODE_ca923eba")} ${padZero(
h
)}:${padZero(m)}:${padZero(s)}`;
}
case ScheduleCreateType.CYCLE: {
const time = text;
const timeArr = time.split(" ");
const h = timeArr[2];
const m = timeArr[1];
const s = timeArr[0];
const w = timeArr[5];
return `${t("TXT_CODE_76750199")} ${w} / ${h}:${m}:${s}`;
}
case ScheduleCreateType.SPECIFY: {
const time = text;
const timeArr = time.split(" ");
const h = timeArr[2];
const m = timeArr[1];
const s = timeArr[0];
const dd = timeArr[3];
const mm = timeArr[4];
return `${mm} ${t("TXT_CODE_6cb9bb04")} ${dd} ${t("TXT_CODE_ca923eba")} ${h}:${m}:${s}`;
}
default:
return "Unknown Time";
}
};
const formatFunction = formatFunctions[schedule.type as ScheduleCreateType];
return formatFunction(text) ?? "Unknown Time";
};
const columns: AntColumnsType[] = [
@ -205,9 +158,9 @@ onMounted(async () => {
<a-col :span="24">
<CardPanel style="height: 100%">
<template #body>
<a-spin :spinning="isLoading">
<a-spin :spinning="scheduleListLoading">
<a-table
:data-source="state"
:data-source="schedules"
:columns="columns"
:scroll="{ x: 'max-content' }"
:pagination="{
@ -216,6 +169,14 @@ onMounted(async () => {
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'actions'">
<a-button
class="mr-8"
size="large"
@click="newScheduleDialog?.openDialog(record as Schedule)"
>
{{ t("TXT_CODE_ad207008") }}
<EditOutlined />
</a-button>
<a-popconfirm
:title="t('TXT_CODE_6ff0668f')"
@confirm="deleteSchedule(record.name)"

View File

@ -3,23 +3,53 @@ import { ref, reactive } from "vue";
import { t } from "@/lang/i18n";
import { notification } from "ant-design-vue";
import { ScheduleAction, ScheduleType, ScheduleCreateType } from "@/types/const";
import type { NewScheduleTask } from "@/types";
import { scheduleCreate } from "@/services/apis/instance";
import { useScreen } from "@/hooks/useScreen";
import type { Schedule, ScheduleTaskForm } from "@/types";
import { reportErrorMsg } from "@/tools/validator";
import type { Dayjs } from "dayjs";
import dayjs from "dayjs";
import _ from "lodash";
import { useSchedule } from "@/hooks/useSchedule";
const props = defineProps<{
daemonId: string;
instanceId: string;
}>();
const isLoading = ref(false);
const editMode = ref(false);
const {
createTaskTypeInterval,
createTaskTypeCycle,
createTaskTypeSpecify,
calculateIntervalFromTime,
calculateTimeFromCycle,
parseTaskTime,
createState,
deleteSchedule
} = useSchedule(props.instanceId, props.daemonId);
const parseTime = {
[ScheduleCreateType.INTERVAL]: (time: string) =>
(newTask.cycle = calculateIntervalFromTime(time)),
[ScheduleCreateType.CYCLE]: (time: string) => {
const { objTime, weekend } = calculateTimeFromCycle(time);
newTask.objTime = objTime;
newTask.weekend = weekend;
},
[ScheduleCreateType.SPECIFY]: (time: string) => (newTask.objTime = parseTaskTime(time))
};
const emit = defineEmits(["getScheduleList"]);
const open = ref(false);
const openDialog = () => {
const openDialog = (task?: Schedule) => {
newTask = reactive({
..._.cloneDeep(newTaskOrigin),
...task
});
editMode.value = !!task;
if (editMode.value) parseTime[newTask.type](newTask.time);
open.value = true;
};
const { isPhone } = useScreen();
const weeks = [
{ label: t("TXT_CODE_fcbdcb34"), value: 1 },
@ -31,93 +61,43 @@ const weeks = [
{ label: t("TXT_CODE_a621f370"), value: 7 }
];
interface NewTask extends NewScheduleTask {
payload: string;
weekend: string[];
cycle: string[];
objTime: string;
}
const newTaskOrigin: NewTask = {
const newTaskOrigin: ScheduleTaskForm = {
name: "",
action: "command",
count: "",
count: 0,
type: ScheduleCreateType.INTERVAL,
time: "",
payload: "",
weekend: [],
cycle: ["0", "0", "0"],
objTime: ""
objTime: dayjs()
};
let newTask = reactive<NewTask>(_.cloneDeep(newTaskOrigin));
let newTask = reactive<ScheduleTaskForm>(_.cloneDeep(newTaskOrigin));
const createTaskTypeInterval = async () => {
const arr = newTask.cycle;
let ps = Number(arr[0]);
let pm = Number(arr[1]);
let ph = Number(arr[2]);
const rs = ps + pm * 60 + ph * 60 * 60;
newTask.time = rs.toString();
await createRequest();
const create = {
[ScheduleCreateType.INTERVAL]: (newTask: ScheduleTaskForm) => createTaskTypeInterval(newTask),
[ScheduleCreateType.CYCLE]: (newTask: ScheduleTaskForm) => createTaskTypeCycle(newTask),
[ScheduleCreateType.SPECIFY]: (newTask: ScheduleTaskForm) => createTaskTypeSpecify(newTask)
};
const createTaskTypeCycle = async () => {
const weekend = newTask.weekend;
if (newTask.objTime === "") throw new Error(t("TXT_CODE_349edc57"));
if (weekend.length === 0) throw new Error(t("TXT_CODE_2fe0cc84"));
const time = new Date(newTask.objTime);
const h = time.getHours();
const m = time.getMinutes();
const s = time.getSeconds();
newTask.time = `${s} ${m} ${h} * * ${weekend.join(",")}`;
await createRequest();
};
const createTaskTypeSpecify = async () => {
if (newTask.objTime === "") throw new Error(t("TXT_CODE_349edc57"));
const time = newTask.objTime as unknown as Dayjs;
const mm = time.month() + 1;
const dd = time.date();
const h = time.hour();
const m = time.minute();
const s = time.second();
newTask.time = `${s} ${m} ${h} ${dd} ${mm} *`;
await createRequest();
};
const { state, isLoading, execute } = scheduleCreate();
const createRequest = async () => {
const submit = async () => {
try {
if (!newTask.count) newTask.count = "-1";
await execute({
params: {
daemonId: props.daemonId,
uuid: props.instanceId
},
data: newTask
});
if (state.value) {
isLoading.value = true;
if (editMode.value) await deleteSchedule(newTask.name, false);
await create[newTask.type](newTask);
if (createState.value) {
emit("getScheduleList");
notification.success({
message: t("TXT_CODE_d28c05df")
message: editMode.value ? t("TXT_CODE_d3de39b4") : t("TXT_CODE_d28c05df")
});
newTask = reactive(_.cloneDeep(newTaskOrigin));
open.value = false;
}
} catch (err: any) {
console.error(err);
reportErrorMsg(err.message);
}
};
const submit = async () => {
try {
if (newTask.type === ScheduleCreateType.INTERVAL) await createTaskTypeInterval();
if (newTask.type === ScheduleCreateType.CYCLE) await createTaskTypeCycle();
if (newTask.type === ScheduleCreateType.SPECIFY) await createTaskTypeSpecify();
} catch (err: any) {
return reportErrorMsg(err.message);
} finally {
isLoading.value = false;
}
};
@ -130,13 +110,13 @@ defineExpose({
<a-modal
v-model:open="open"
centered
width="660px"
:mask-closable="false"
:title="t('TXT_CODE_3502273d')"
:title="editMode ? t('编辑计划任务') : t('TXT_CODE_3502273d')"
:confirm-loading="isLoading"
:destroy-on-close="true"
:ok-text="t('TXT_CODE_abfe9512')"
@ok="submit"
width="660px"
>
<a-form-item>
<a-typography-title :level="5">{{ t("TXT_CODE_b290a4b0") }}</a-typography-title>
@ -145,7 +125,7 @@ defineExpose({
{{ t("TXT_CODE_b72d638d") }}
</a-typography-text>
</a-typography-paragraph>
<a-input v-model:value="newTask.name" />
<a-input v-model:value="newTask.name" :disabled="editMode" />
</a-form-item>
<a-form-item>
@ -168,7 +148,7 @@ defineExpose({
:placeholder="t('TXT_CODE_3bb646e4')"
:dropdown-match-select-width="false"
>
<a-select-option v-for="(type, i) in ScheduleType" :key="i" :value="i">
<a-select-option v-for="(type, i) in ScheduleType" :key="i" :value="Number(i)">
{{ type }}
</a-select-option>
</a-select>
@ -176,42 +156,50 @@ defineExpose({
</a-row>
</a-form-item>
<a-form-item v-if="newTask.type === ScheduleCreateType.INTERVAL">
<a-typography-paragraph>
<a-typography-text>
{{ t("TXT_CODE_f17889f4") }}
</a-typography-text>
</a-typography-paragraph>
<a-row :gutter="[24, 24]">
<a-col :xs="24" :md="6" :offset="0">
<a-input
v-model:value="newTask.cycle[2]"
:placeholder="t('TXT_CODE_ba8ebc7')"
:addon-after="t('TXT_CODE_4e2c7f64')"
/>
</a-col>
<a-col :xs="24" :md="6" :offset="0">
<a-input
v-model:value="newTask.cycle[1]"
:placeholder="t('TXT_CODE_ba8ebc7')"
:addon-after="t('TXT_CODE_a7e9ff0f')"
/>
</a-col>
<a-col :xs="24" :md="6" :offset="0">
<a-input
v-model:value="newTask.cycle[0]"
:placeholder="t('TXT_CODE_ba8ebc7')"
:addon-after="t('TXT_CODE_acabc771')"
/>
</a-col>
<a-col :xs="24" :md="12" :offset="0">
<a-input v-model:value="newTask.count" :placeholder="t('TXT_CODE_9156fbc')" />
</a-col>
</a-row>
<a-row :gutter="[24, 24]"> </a-row>
</a-form-item>
<template v-if="newTask.type === ScheduleCreateType.INTERVAL">
<a-form-item>
<a-typography-paragraph>
<a-typography-title :level="5">{{ t("TXT_CODE_3554dac0") }}</a-typography-title>
<a-typography-text>
{{ t("TXT_CODE_f17889f4") }}
</a-typography-text>
</a-typography-paragraph>
<a-row :gutter="[24, 24]">
<a-col :xs="24" :md="8" :offset="0">
<a-input
v-model:value="newTask.cycle[2]"
:placeholder="t('TXT_CODE_ba8ebc7')"
:addon-after="t('TXT_CODE_4e2c7f64')"
/>
</a-col>
<a-col :xs="24" :md="8" :offset="0">
<a-input
v-model:value="newTask.cycle[1]"
:placeholder="t('TXT_CODE_ba8ebc7')"
:addon-after="t('TXT_CODE_a7e9ff0f')"
/>
</a-col>
<a-col :xs="24" :md="8" :offset="0">
<a-input
v-model:value="newTask.cycle[0]"
:placeholder="t('TXT_CODE_ba8ebc7')"
:addon-after="t('TXT_CODE_acabc771')"
/>
</a-col>
</a-row>
</a-form-item>
<a-form-item>
<a-typography-title :level="5">{{ t("TXT_CODE_d9cfab1b") }}</a-typography-title>
<a-input-number
v-model:value="newTask.count"
size="large"
class="w-100"
:placeholder="t('TXT_CODE_a59981f4')"
/>
</a-form-item>
</template>
<div v-if="newTask.type === ScheduleCreateType.CYCLE">
<template v-if="newTask.type === ScheduleCreateType.CYCLE">
<a-form-item>
<a-typography-title :level="5">{{ t("TXT_CODE_3554dac0") }}</a-typography-title>
<a-time-picker
@ -227,9 +215,14 @@ defineExpose({
<a-form-item>
<a-typography-title :level="5">{{ t("TXT_CODE_d9cfab1b") }}</a-typography-title>
<a-input v-model:value="newTask.count" :placeholder="t('TXT_CODE_a59981f4')" />
<a-input-number
v-model:value="newTask.count"
size="large"
class="w-100"
:placeholder="t('TXT_CODE_a59981f4')"
/>
</a-form-item>
</div>
</template>
<a-form-item v-if="newTask.type === ScheduleCreateType.SPECIFY">
<a-typography-title :level="5">{{ t("TXT_CODE_f3fe5c8e") }}</a-typography-title>