diff --git a/frontend/components.d.ts b/frontend/components.d.ts index 77c21ffc..66ed7237 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -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'] diff --git a/frontend/src/config/index.ts b/frontend/src/config/index.ts index b990ceec..a2ea3c9f 100644 --- a/frontend/src/config/index.ts +++ b/frontend/src/config/index.ts @@ -32,6 +32,7 @@ import UserStatusBlock from "@/widgets/UserStatusBlock.vue"; import UserInstanceList from "@/widgets/UserInstanceList.vue"; import ImageManager from "@/widgets/imageManager/index.vue"; import NewImage from "@/widgets/imageManager/NewImage.vue"; +import Schedule from "@/widgets/instance/Schedule.vue"; import { NEW_CARD_TYPE } from "../types/index"; import { ROLE } from "./router"; @@ -67,7 +68,8 @@ export const LAYOUT_CARD_TYPES: { [key: string]: any } = { UserStatusBlock, UserInstanceList, ImageManager, - NewImage + NewImage, + Schedule }; export interface NewCardItem extends LayoutCard { diff --git a/frontend/src/config/router.ts b/frontend/src/config/router.ts index bd908fe4..f23d494c 100644 --- a/frontend/src/config/router.ts +++ b/frontend/src/config/router.ts @@ -103,6 +103,14 @@ let originRouterConfig: RouterConfig[] = [ ] } ] + }, + { + path: "/instances/schedule", + name: t("计划任务"), + component: LayoutContainer, + meta: { + permission: ROLE.USER + } } ] }, diff --git a/frontend/src/services/apis/instance.ts b/frontend/src/services/apis/instance.ts index 8282aa52..648d50ba 100644 --- a/frontend/src/services/apis/instance.ts +++ b/frontend/src/services/apis/instance.ts @@ -1,5 +1,11 @@ import { useDefineApi } from "@/stores/useDefineApi"; -import type { InstanceDetail, NewInstanceForm, QuickStartTemplate } from "@/types"; +import type { + InstanceDetail, + NewInstanceForm, + QuickStartTemplate, + Schedule, + NewScheduleTask +} from "@/types"; import type { IGlobalInstanceConfig } from "../../../../common/global"; import type { InstanceMoreDetail } from "@/hooks/useInstance"; @@ -67,6 +73,53 @@ export const stopInstance = useDefineApi< method: "GET" }); +export const restartInstance = useDefineApi< + { + params: { + uuid: string; + remote_uuid: string; + }; + }, + { + instanceUuid: string; + } +>({ + url: "/api/protected_instance/restart", + method: "GET" +}); + +export const killInstance = useDefineApi< + { + params: { + uuid: string; + remote_uuid: string; + }; + }, + { + instanceUuid: string; + } +>({ + url: "/api/protected_instance/kill", + method: "GET" +}); + +export const updateInstance = useDefineApi< + { + params: { + uuid: string; + remote_uuid: string; + task_name: string; + }; + data: { + time: number; + }; + }, + boolean +>({ + url: "/api/protected_instance/asynchronous", + method: "POST" +}); + // 更新实例设置(普通用户) export const updateInstanceConfig = useDefineApi< { @@ -343,3 +396,47 @@ export const batchDelete = useDefineApi< method: "DELETE", url: "/api/instance" }); + +// 获取计划任务 +export const scheduleList = useDefineApi< + { + params: { + remote_uuid: string; + uuid: string; + }; + }, + Schedule[] +>({ + method: "GET", + url: "/api/protected_schedule" +}); + +// 删除计划任务 +export const scheduleDelete = useDefineApi< + { + params: { + remote_uuid: string; + uuid: string; + task_name: string; + }; + }, + boolean +>({ + 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" +}); diff --git a/frontend/src/types/const.ts b/frontend/src/types/const.ts index 0c83ff5a..02fd27ca 100644 --- a/frontend/src/types/const.ts +++ b/frontend/src/types/const.ts @@ -80,3 +80,23 @@ 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("指定时间性任务") +}; + +export enum ScheduleCreateType { + INTERVAL = "1", + CYCLE = "2", + SPECIFY = "3" +} diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts index 2746ab48..460d0c66 100644 --- a/frontend/src/types/index.ts +++ b/frontend/src/types/index.ts @@ -218,3 +218,21 @@ export interface MountComponent { destroyComponent: Function; emitResult: Function; } + +export interface Schedule { + instanceUuid: string; + name: string; + count: number; + time: string; + action: string; + payload: string; + type: number; +} + +export interface NewScheduleTask { + name: String; + count: String; + time: String; + action: String; + type: String; +} diff --git a/frontend/src/widgets/instance/ManagerBtns.vue b/frontend/src/widgets/instance/ManagerBtns.vue index 7cb5a24f..3b86abd6 100644 --- a/frontend/src/widgets/instance/ManagerBtns.vue +++ b/frontend/src/widgets/instance/ManagerBtns.vue @@ -3,7 +3,7 @@ import { ref, computed } from "vue"; import type { LayoutCard } from "@/types"; import { arrayFilter } from "../../tools/array"; import { t } from "@/lang/i18n"; -import { ArrowRightOutlined, CloudServerOutlined } from "@ant-design/icons-vue"; +import { ArrowRightOutlined, CloudServerOutlined, FieldTimeOutlined } from "@ant-design/icons-vue"; import InnerCard from "@/components/InnerCard.vue"; import { LayoutCardHeight } from "../../config/originLayoutConfig"; import { useAppRouters } from "@/hooks/useAppRouters"; @@ -80,9 +80,17 @@ const btns = computed(() => }, { title: t("TXT_CODE_b7d026f8"), - icon: CloudServerOutlined, + icon: FieldTimeOutlined, condition: () => !isGlobalTerminal.value, - click: () => {} + click: () => { + toPage({ + path: "/instances/schedule", + query: { + instanceId, + daemonId + } + }); + } }, // 暂时删除 // { diff --git a/frontend/src/widgets/instance/Schedule.vue b/frontend/src/widgets/instance/Schedule.vue new file mode 100644 index 00000000..ee4d72f8 --- /dev/null +++ b/frontend/src/widgets/instance/Schedule.vue @@ -0,0 +1,254 @@ + + + + + diff --git a/frontend/src/widgets/instance/Terminal.vue b/frontend/src/widgets/instance/Terminal.vue index aa281a49..a9a19152 100644 --- a/frontend/src/widgets/instance/Terminal.vue +++ b/frontend/src/widgets/instance/Terminal.vue @@ -18,7 +18,13 @@ import { onMounted, computed, ref } from "vue"; import { useLayoutCardTools } from "@/hooks/useCardTools"; import { getRandomId } from "../../tools/randId"; import IconBtn from "@/components/IconBtn.vue"; -import { openInstance, stopInstance } from "@/services/apis/instance"; +import { + openInstance, + stopInstance, + restartInstance, + killInstance, + updateInstance +} from "@/services/apis/instance"; import { CloseOutlined } from "@ant-design/icons-vue"; import { GLOBAL_INSTANCE_NAME } from "../../config/const"; import { INSTANCE_STATUS_TEXT } from "../../hooks/useInstance"; @@ -70,7 +76,8 @@ const quickOperations = computed(() => } }); }, - props: {} + props: {}, + condition: () => isStopped.value }, { title: t("TXT_CODE_b1dedda3"), @@ -85,34 +92,59 @@ const quickOperations = computed(() => }, props: { danger: true - } + }, + condition: () => isRunning.value } ]) ); -const instanceOperations = arrayFilter([ - { - title: t("TXT_CODE_47dcfa5"), - icon: ReconciliationOutlined, - click: () => { - console.log(3); +const instanceOperations = computed(() => + arrayFilter([ + { + title: t("TXT_CODE_47dcfa5"), + icon: ReconciliationOutlined, + click: () => { + restartInstance().execute({ + params: { + uuid: instanceId || "", + remote_uuid: daemonId || "" + } + }); + }, + condition: () => isRunning.value + }, + { + title: t("TXT_CODE_7b67813a"), + icon: CloseOutlined, + click: () => { + killInstance().execute({ + params: { + uuid: instanceId || "", + remote_uuid: daemonId || "" + } + }); + }, + condition: () => isRunning.value + }, + { + title: t("TXT_CODE_40ca4f2"), + icon: CloudDownloadOutlined, + click: () => { + updateInstance().execute({ + params: { + uuid: instanceId || "", + remote_uuid: daemonId || "", + task_name: "update" + }, + data: { + time: new Date().getTime() + } + }); + }, + condition: () => isStopped.value } - }, - { - title: t("TXT_CODE_7b67813a"), - icon: CloseOutlined, - click: () => { - console.log(3); - } - }, - { - title: t("TXT_CODE_40ca4f2"), - icon: CloudDownloadOutlined, - click: () => { - console.log(4); - } - } -]); + ]) +); const handleSendCommand = () => { sendCommand(commandInputValue.value); @@ -240,7 +272,7 @@ const innerTerminalType = viewType === "inner"; - +
diff --git a/frontend/src/widgets/instance/dialogs/NewSchedule.vue b/frontend/src/widgets/instance/dialogs/NewSchedule.vue new file mode 100644 index 00000000..7c85e7c1 --- /dev/null +++ b/frontend/src/widgets/instance/dialogs/NewSchedule.vue @@ -0,0 +1,246 @@ + + + diff --git a/panel/src/app/service/frontend_layout.ts b/panel/src/app/service/frontend_layout.ts index 7b92e6b7..55d2e6ec 100644 --- a/panel/src/app/service/frontend_layout.ts +++ b/panel/src/app/service/frontend_layout.ts @@ -256,6 +256,28 @@ function getDefaultFrontendLayoutConfig(): IPageLayoutConfig[] { } ] }, + { + page: "/instances/schedule", + items: [ + { + id: getRandomId(), + meta: {}, + type: "Schedule", + title: t("计划任务"), + width: 12, + height: LayoutCardHeight.AUTO, + disableDelete: true + }, + { + id: getRandomId(), + meta: {}, + type: "EmptyCard", + title: "", + width: 12, + height: LayoutCardHeight.MINI + } + ] + }, { page: "/users", items: [