Feat: create schedule

This commit is contained in:
Lazy 2023-11-13 14:01:48 +08:00
parent f42ba4a819
commit d250c298bb
6 changed files with 323 additions and 21 deletions

View File

@ -51,6 +51,7 @@ declare module 'vue' {
ATabs: typeof import('ant-design-vue/es')['Tabs']
ATag: typeof import('ant-design-vue/es')['Tag']
ATextarea: typeof import('ant-design-vue/es')['Textarea']
ATimePicker: typeof import('ant-design-vue/es')['TimePicker']
ATooltip: typeof import('ant-design-vue/es')['Tooltip']
ATypography: typeof import('ant-design-vue/es')['Typography']
ATypographyParagraph: typeof import('ant-design-vue/es')['TypographyParagraph']

View File

@ -1,5 +1,11 @@
import { useDefineApi } from "@/stores/useDefineApi";
import type { InstanceDetail, NewInstanceForm, QuickStartTemplate, Schedule } from "@/types";
import type {
InstanceDetail,
NewInstanceForm,
QuickStartTemplate,
Schedule,
NewScheduleTask
} from "@/types";
import type { IGlobalInstanceConfig } from "../../../../common/global";
import type { InstanceMoreDetail } from "@/hooks/useInstance";
@ -372,3 +378,18 @@ export const scheduleDelete = useDefineApi<
method: "DELETE",
url: "/api/protected_schedule"
});
// 创建计划任务
export const scheduleCreate = useDefineApi<
{
params: {
remote_uuid: string;
uuid: string;
};
data: NewScheduleTask;
},
boolean
>({
url: "/api/protected_schedule",
method: "POST"
});

View File

@ -80,3 +80,17 @@ ENV LC_ALL=zh_CN.UTF-8
ENV TZ=Asia/Shanghai
WORKDIR /workspace
`;
export const ScheduleAction = {
command: t("发送命令"),
stop: t("停止实例"),
start: t("开启实例"),
restart: t("重启实例"),
kill: t("终止实例")
};
export const ScheduleType = {
1: t("间隔时间性任务"),
2: t("周期时间性任务"),
3: t("指定时间性任务")
};

View File

@ -228,3 +228,11 @@ export interface Schedule {
payload: string;
type: number;
}
export interface NewScheduleTask {
name: String;
count: String;
time: String;
action: String;
type: String;
}

View File

@ -11,6 +11,8 @@ import { useScreen } from "@/hooks/useScreen";
import { useAppRouters } from "@/hooks/useAppRouters";
import { scheduleList, scheduleDelete } from "@/services/apis/instance";
import type { LayoutCard, Schedule } from "@/types/index";
import { ScheduleAction, ScheduleType } from "@/types/const";
import NewSchedule from "@/widgets/instance/dialogs/NewSchedule.vue";
const props = defineProps<{
card: LayoutCard;
@ -21,6 +23,7 @@ const instanceId = getMetaOrRouteValue("instanceId");
const daemonId = getMetaOrRouteValue("daemonId");
const { toPage } = useAppRouters();
const screen = useScreen();
const newScheduleDialog = ref<InstanceType<typeof NewSchedule>>();
const { state, execute, isLoading } = scheduleList();
const getScheduleList = async () => {
@ -57,20 +60,6 @@ const deleteSchedule = async (name: string) => {
}
};
const action = {
command: t("发送命令"),
stop: t("停止实例"),
start: t("开启实例"),
restart: t("重启实例"),
kill: t("终止实例")
};
const type = {
1: t("间隔时间性任务"),
2: t("周期时间性任务"),
3: t("指定时间性任务")
};
const rendTime = (text: string, schedule: Schedule) => {
switch (schedule.type) {
case 1: {
@ -105,7 +94,7 @@ const rendTime = (text: string, schedule: Schedule) => {
const s = timeArr[0];
const dd = timeArr[3];
const mm = timeArr[4];
return `${mm} ${t("每隔")} ${dd} ${t("每隔")} ${h}:${m}:${s}`;
return `${mm} ${t("")} ${dd} ${t("")} ${h}:${m}:${s}`;
}
default:
return "Unknown Time";
@ -142,7 +131,7 @@ const columns = [
key: "action",
minWidth: "180px",
customRender: (e: { text: "command" | "stop" | "start" | "restart" | "kill" }) => {
return action[e.text];
return ScheduleAction[e.text];
}
},
{
@ -152,7 +141,7 @@ const columns = [
key: "type",
minWidth: "180px",
customRender: (e: { text: 1 | 2 | 3 }) => {
return type[e.text];
return ScheduleType[e.text];
}
},
{
@ -173,7 +162,17 @@ const columns = [
const refresh = throttle(() => {
getScheduleList();
}, 600);
}, 100);
const toConsole = () => {
toPage({
path: "/instances/terminal",
query: {
daemonId,
instanceId
}
});
};
onMounted(async () => {
getScheduleList();
@ -192,13 +191,13 @@ onMounted(async () => {
</a-typography-title>
</template>
<template #right>
<a-button class="mr-10">
<a-button class="mr-10" @click="toConsole()">
{{ t("返回控制台") }}
</a-button>
<a-button class="mr-10" @click="refresh">
{{ t("刷新") }}
</a-button>
<a-button type="primary">
<a-button type="primary" @click="newScheduleDialog?.openDialog()">
{{ t("新增计划任务") }}
</a-button>
</template>
@ -229,6 +228,13 @@ onMounted(async () => {
</a-col>
</a-row>
</div>
<NewSchedule
ref="newScheduleDialog"
:daemon-id="daemonId ?? ''"
:instance-id="instanceId ?? ''"
@get-schedule-list="getScheduleList()"
/>
</template>
<style lang="scss" scoped>

View File

@ -0,0 +1,252 @@
<script setup lang="ts">
import { ref, reactive } from "vue";
import { t } from "@/lang/i18n";
import { message, notification } from "ant-design-vue";
import { ScheduleAction, ScheduleType } from "@/types/const";
import type { NewScheduleTask } from "@/types";
import { scheduleCreate } from "@/services/apis/instance";
import { useScreen } from "@/hooks/useScreen";
import type { Dayjs } from "dayjs";
import _ from "lodash";
const props = defineProps<{
daemonId: string;
instanceId: string;
}>();
const emit = defineEmits(["getScheduleList"]);
const open = ref(false);
const openDialog = () => {
open.value = true;
};
const { isPhone } = useScreen();
const weeks = [
{ label: t("周一"), value: 1 },
{ label: t("周二"), value: 2 },
{ label: t("周三"), value: 3 },
{ label: t("周四"), value: 4 },
{ label: t("周五"), value: 5 },
{ label: t("周六"), value: 6 },
{ label: t("周日"), value: 7 }
];
enum TYPE {
INTERVAL = "1",
CYCLE = "2",
SPECIFY = "3"
}
interface NewTask extends NewScheduleTask {
payload: string;
weekend: string[];
cycle: string[];
objTime: string;
}
const newTaskOrigin = reactive<NewTask>({
name: "",
action: "command",
count: "",
type: TYPE.INTERVAL,
time: "",
payload: "",
weekend: [],
cycle: ["0", "0", "0"],
objTime: ""
});
let newTask = reactive(_.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 createTaskTypeCycle = async () => {
const weekend = newTask.weekend;
if (newTask.objTime === "") throw new Error(t("请选择时间"));
if (weekend.length === 0) throw new Error(t("请选择星期"));
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("请选择时间"));
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 () => {
try {
if (!newTask.count) newTask.count = "-1";
await execute({
params: {
remote_uuid: props.daemonId,
uuid: props.instanceId
},
data: newTask
});
if (state.value) {
emit("getScheduleList");
notification.success({
message: t("创建成功")
});
newTask = reactive(_.cloneDeep(newTaskOrigin));
open.value = false;
}
} catch (err: any) {
console.error(err);
message.error(err.message);
}
};
const submit = async () => {
try {
if (newTask.type === TYPE.INTERVAL) await createTaskTypeInterval();
if (newTask.type === TYPE.CYCLE) await createTaskTypeCycle();
if (newTask.type === TYPE.SPECIFY) await createTaskTypeSpecify();
} catch (err: any) {
return message.error(err.message);
}
};
defineExpose({
openDialog
});
</script>
<template>
<a-modal
v-model:open="open"
centered
:mask-closable="false"
:title="t('新增计划任务')"
:confirm-loading="isLoading"
:destroy-on-close="true"
:ok-text="t('保存')"
@ok="submit"
>
<a-form-item>
<a-typography-title :level="5">{{ t("计划任务名字") }}</a-typography-title>
<a-typography-paragraph>
<a-typography-text type="secondary">
{{ t("必填,且必须唯一") }}
</a-typography-text>
</a-typography-paragraph>
<a-input v-model:value="newTask.name" />
</a-form-item>
<a-form-item>
<a-typography-title :level="5">{{ t("任务动作 / 类型") }}</a-typography-title>
<a-row :gutter="20">
<a-col :xs="24" :md="12" :offset="0" :class="{ 'mb-10': isPhone }">
<a-select
v-model:value="newTask.action"
:placeholder="t('请选择')"
:dropdown-match-select-width="false"
>
<a-select-option v-for="(action, i) in ScheduleAction" :key="i" :value="i">
{{ action }}
</a-select-option>
</a-select>
</a-col>
<a-col :xs="24" :md="12" :offset="0">
<a-select
v-model:value="newTask.type"
:placeholder="t('请选择')"
:dropdown-match-select-width="false"
>
<a-select-option v-for="(type, i) in ScheduleType" :key="i" :value="i">
{{ type }}
</a-select-option>
</a-select>
</a-col>
</a-row>
</a-form-item>
<a-form-item v-if="newTask.type === TYPE.INTERVAL">
<a-typography-paragraph>
<a-typography-text>
{{ t("每隔一定时间将执行一次,具体间隔可以仔细设置") }}
</a-typography-text>
</a-typography-paragraph>
<a-row :gutter="20">
<a-col :xs="24" :md="6" :offset="0" :class="{ 'mb-10': isPhone }">
<a-input
v-model:value="newTask.cycle[2]"
:placeholder="t('不可为空,请写 0 代表每隔 0 时')"
:addon-after="t('时')"
/>
</a-col>
<a-col :xs="24" :md="6" :offset="0" :class="{ 'mb-10': isPhone }">
<a-input
v-model:value="newTask.cycle[1]"
:placeholder="t('不可为空,请写 0 代表每隔 0 时')"
:addon-after="t('分')"
/>
</a-col>
<a-col :xs="24" :md="6" :offset="0" :class="{ 'mb-10': isPhone }">
<a-input
v-model:value="newTask.cycle[0]"
:placeholder="t('不可为空,请写 0 代表每隔 0 时')"
:addon-after="t('秒')"
/>
</a-col>
<a-col :xs="24" :md="6" :offset="0">
<a-input v-model:value="newTask.count" :placeholder="t('执行次数,留空无限')" />
</a-col>
</a-row>
</a-form-item>
<div v-if="newTask.type === TYPE.CYCLE">
<a-form-item>
<a-typography-title :level="5">{{ t("触发时间") }}</a-typography-title>
<a-time-picker
v-model:value="newTask.objTime"
size="large"
:placeholder="$t('具体时间点')"
class="w-100"
/>
</a-form-item>
<a-form-item>
<a-checkbox-group v-model:value="newTask.weekend" :options="weeks" />
</a-form-item>
<a-form-item>
<a-typography-title :level="5">{{ t("执行次数") }}</a-typography-title>
<a-input v-model:value="newTask.count" :placeholder="t('留空无限')" />
</a-form-item>
</div>
<a-form-item v-if="newTask.type === TYPE.SPECIFY">
<a-typography-title :level="5">{{ t("请选择日期和时间") }}</a-typography-title>
<a-date-picker v-model:value="newTask.objTime" show-time size="large" class="w-100" />
</a-form-item>
<a-form-item>
<a-typography-title :level="5">{{ t("任务有效负载") }}</a-typography-title>
<a-typography-paragraph>
<a-typography-text type="secondary">
{{ t("比如命令,文件名或其他参数等") }}
</a-typography-text>
</a-typography-paragraph>
<a-input v-model:value="newTask.payload" />
</a-form-item>
</a-modal>
</template>