mirror of
https://github.com/MCSManager/MCSManager.git
synced 2024-11-27 06:59:54 +08:00
Feat: create schedule
This commit is contained in:
parent
f42ba4a819
commit
d250c298bb
1
frontend/components.d.ts
vendored
1
frontend/components.d.ts
vendored
@ -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']
|
||||
|
@ -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"
|
||||
});
|
||||
|
@ -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("指定时间性任务")
|
||||
};
|
||||
|
@ -228,3 +228,11 @@ export interface Schedule {
|
||||
payload: string;
|
||||
type: number;
|
||||
}
|
||||
|
||||
export interface NewScheduleTask {
|
||||
name: String;
|
||||
count: String;
|
||||
time: String;
|
||||
action: String;
|
||||
type: String;
|
||||
}
|
||||
|
@ -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>
|
||||
|
252
frontend/src/widgets/instance/dialogs/NewSchedule.vue
Normal file
252
frontend/src/widgets/instance/dialogs/NewSchedule.vue
Normal 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>
|
Loading…
Reference in New Issue
Block a user