初始提交:OCI 面板前端(含 GenAI 网关一期)

This commit is contained in:
Wang Defa
2026-07-09 15:31:29 +08:00
commit db45a669e3
135 changed files with 25220 additions and 0 deletions
+113
View File
@@ -0,0 +1,113 @@
<script setup lang="ts">
import { NButton, NSpin, useDialog, useMessage } from 'naive-ui'
import { computed, onUnmounted, ref } from 'vue'
import { deleteTask, listTasks, runTask, updateTask } from '@/api/tasks'
import EmptyCard from '@/components/EmptyCard.vue'
import TaskCard from '@/components/task/TaskCard.vue'
import TaskFormModal from '@/components/task/TaskFormModal.vue'
import { parseSnatch } from '@/components/task/taskDisplay'
import { useAsync } from '@/composables/useAsync'
import { useScopeStore } from '@/stores/scope'
import type { Task } from '@/types/api'
const message = useMessage()
const dialog = useDialog()
const scope = useScopeStore()
const tasks = useAsync(listTasks)
const showCreate = ref(false)
const editingTask = ref<Task | null>(null)
// 每 5 秒静默刷新(不闪 loading)
const refreshTimer = setInterval(() => void tasks.run({ silent: true }), 5000)
onUnmounted(() => clearInterval(refreshTimer))
const cfgAliasById = computed(
() => new Map((scope.configs.data ?? []).map((c) => [c.id, c.alias])),
)
function aliasFor(task: Task): string | undefined {
const s = parseSnatch(task)
return s ? cfgAliasById.value.get(s.cfgId) : undefined
}
function openEdit(task: Task) {
editingTask.value = task
showCreate.value = true
}
function openCreate() {
editingTask.value = null
showCreate.value = true
}
async function act(fn: () => Promise<unknown>, ok: string) {
try {
await fn()
message.success(ok)
void tasks.run({ silent: true })
} catch (e) {
message.error(e instanceof Error ? e.message : '操作失败')
}
}
/** active ↔ paused 切换;failed(熔断停止)同走恢复交互重新启用 */
function toggle(task: Task) {
const resume = task.status !== 'active'
void act(
() => updateTask(task.id, { status: resume ? 'active' : 'paused' }),
resume ? (task.status === 'failed' ? '已重新启用' : '已恢复调度') : '已暂停',
)
}
function confirmRemove(task: Task) {
dialog.warning({
title: '删除任务',
content: `删除任务「${task.name}」及其全部日志?`,
positiveText: '删除',
negativeText: '取消',
onPositiveClick: () => act(() => deleteTask(task.id), '已删除任务及其日志'),
})
}
</script>
<template>
<div class="flex flex-col gap-4 p-6 pb-8 max-md:p-3">
<div class="flex flex-wrap items-center justify-between gap-2">
<div class="flex flex-wrap items-baseline gap-2.5">
<h1 class="page-title">任务</h1>
<span class="text-[13px] text-ink-3">
{{ tasks.data.value?.length ?? '…' }} 个任务 · cron 为服务器本地时区
</span>
</div>
<NButton size="small" type="primary" @click="openCreate">新建任务</NButton>
</div>
<div
v-if="tasks.loading.value && !tasks.data.value"
class="panel flex items-center justify-center py-20"
>
<NSpin size="small" />
</div>
<div v-else class="flex flex-col gap-3">
<TaskCard
v-for="task in tasks.data.value ?? []"
:key="task.id"
:task="task"
:cfg-alias="aliasFor(task)"
@run="act(() => runTask(task.id), `已触发执行:${task.name}`)"
@toggle="toggle(task)"
@edit="openEdit(task)"
@remove="confirmRemove(task)"
/>
<EmptyCard
v-if="tasks.data.value && !tasks.data.value.length"
title="暂无任务"
note="点击右上角「新建任务」创建测活 / 抢机 / 成本任务"
/>
</div>
<TaskFormModal v-model:show="showCreate" :task="editingTask" @created="tasks.run()" />
</div>
</template>