feat: add qlrepo sync crontab
This commit is contained in:
+111
-27
@@ -6,8 +6,10 @@ import { Input } from '@/components/ui/input'
|
||||
import Pagination from '@/components/Pagination.vue'
|
||||
import TaskDialog from './TaskDialog.vue'
|
||||
import RepoDialog from './RepoDialog.vue'
|
||||
import LogViewer from '@/views/history/LogViewer.vue'
|
||||
import { Plus, Play, Pencil, Trash2, Search, ScrollText, GitBranch, Terminal, Server, Monitor, X, Loader2, Wifi, WifiOff, Zap, ZapOff, Copy, Tag } from 'lucide-vue-next'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { api, type Agent, type Task } from '@/api'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { useSiteSettings } from '@/composables/useSiteSettings'
|
||||
@@ -30,7 +32,7 @@ const deleteTaskId = ref<string | null>(null)
|
||||
|
||||
const filterName = ref('')
|
||||
const filterTags = ref('')
|
||||
const filterType = ref('all')
|
||||
const filterType = ref<string>(TASK_TYPE.NORMAL)
|
||||
const filterAgentId = ref<string | null>(null)
|
||||
const currentPage = ref(1)
|
||||
const total = ref(0)
|
||||
@@ -147,18 +149,41 @@ function duplicateTask(task: Task) {
|
||||
}
|
||||
}
|
||||
|
||||
const showBatchDeleteDialog = ref(false)
|
||||
|
||||
function confirmDelete(id: string) {
|
||||
deleteTaskId.value = id
|
||||
showDeleteDialog.value = true
|
||||
}
|
||||
|
||||
function confirmBatchDelete() {
|
||||
if (total.value === 0) return
|
||||
showBatchDeleteDialog.value = true
|
||||
}
|
||||
|
||||
async function batchDeleteTasks() {
|
||||
try {
|
||||
const res = await api.tasks.batchDeleteByQuery({
|
||||
name: filterName.value || undefined,
|
||||
tags: filterTags.value || undefined,
|
||||
type: filterType.value === 'all' ? undefined : filterType.value,
|
||||
agent_id: filterAgentId.value || undefined
|
||||
})
|
||||
toast.success(`成功删除 ${res.count} 个任务`)
|
||||
loadTasks()
|
||||
} catch {
|
||||
toast.error('批量删除失败')
|
||||
}
|
||||
showBatchDeleteDialog.value = false
|
||||
}
|
||||
|
||||
async function deleteTask() {
|
||||
if (!deleteTaskId.value) return
|
||||
try {
|
||||
await api.tasks.delete(deleteTaskId.value)
|
||||
toast.success('任务已删除')
|
||||
loadTasks()
|
||||
} catch { toast.error('删除失败') }
|
||||
} catch { toast.error('删除失败') }
|
||||
showDeleteDialog.value = false
|
||||
deleteTaskId.value = null
|
||||
}
|
||||
@@ -189,8 +214,27 @@ async function toggleTask(task: Task, enabled: boolean) {
|
||||
} catch { toast.error('操作失败') }
|
||||
}
|
||||
|
||||
function viewLogs(taskId: string) {
|
||||
router.push({ path: '/history', query: { task_id: taskId } })
|
||||
const showLogViewer = ref(false)
|
||||
const selectedLogId = ref<string | undefined>()
|
||||
const latestLogStatus = ref('')
|
||||
const latestLogTitle = ref('')
|
||||
|
||||
async function viewLogs(taskId: string) {
|
||||
try {
|
||||
const res = await api.logs.list({ task_id: taskId, page: 1, page_size: 1 })
|
||||
if (res.data && res.data.length > 0) {
|
||||
const latestLog = res.data[0]
|
||||
if (!latestLog) return
|
||||
latestLogTitle.value = latestLog.task_name || ''
|
||||
latestLogStatus.value = latestLog.status || ''
|
||||
selectedLogId.value = latestLog.id
|
||||
showLogViewer.value = true
|
||||
} else {
|
||||
toast.info('该任务暂无执行日志')
|
||||
}
|
||||
} catch {
|
||||
toast.error('获取日志失败')
|
||||
}
|
||||
}
|
||||
|
||||
function getTaskTypeTitle(type: string) {
|
||||
@@ -221,12 +265,12 @@ watch(() => route.query.agent_id, (newVal) => {
|
||||
<template>
|
||||
<div class="space-y-6">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||
<div>
|
||||
<div class="flex items-center gap-3">
|
||||
<h2 class="text-xl sm:text-2xl font-bold tracking-tight">定时任务</h2>
|
||||
<p class="text-muted-foreground text-sm">管理和调度自动化任务</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col sm:flex-row gap-2.5 w-full md:w-auto">
|
||||
<!-- 第1行: 搜索框 -->
|
||||
<!-- 搜索与标签 -->
|
||||
<div class="flex items-center gap-2 w-full sm:w-auto">
|
||||
<div class="relative flex-1 sm:flex-none">
|
||||
<Search class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
||||
@@ -239,34 +283,48 @@ watch(() => route.query.agent_id, (newVal) => {
|
||||
@input="handleSearch" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 第2行: 下拉框与按钮 -->
|
||||
<div class="flex items-center gap-2 w-full sm:w-auto mt-1 sm:mt-0">
|
||||
<div class="relative flex-1 sm:flex-none">
|
||||
<Select v-model="filterType" @update:model-value="handleTypeChange">
|
||||
<SelectTrigger class="h-9 w-full sm:w-28 text-sm">
|
||||
<SelectValue placeholder="所有类型" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="all">所有类型</SelectItem>
|
||||
<SelectItem :value="TASK_TYPE.NORMAL">定时任务</SelectItem>
|
||||
<SelectItem :value="TASK_TYPE.REPO">仓库同步</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
<div class="flex items-center gap-3 w-full sm:w-auto">
|
||||
<!-- 移动端类型切换 -->
|
||||
<div class="md:hidden flex-1 shrink-0">
|
||||
<Select v-model="filterType" @update:model-value="handleTypeChange">
|
||||
<SelectTrigger class="h-9 w-full text-sm">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem :value="TASK_TYPE.NORMAL">定时任务</SelectItem>
|
||||
<SelectItem :value="TASK_TYPE.REPO">仓库同步</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div v-if="filterAgentId"
|
||||
class="hidden sm:flex items-center gap-1 px-2 py-1 bg-primary/10 text-primary rounded-md text-sm shrink-0">
|
||||
<Server class="h-3.5 w-3.5" />
|
||||
<span>{{ filterAgentName }}</span>
|
||||
<X class="h-3.5 w-3.5 cursor-pointer hover:text-destructive" @click="clearAgentFilter" />
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2 shrink-0 justify-end">
|
||||
<Button variant="outline" @click="openCreateRepo" class="shrink-0 px-3 h-9">
|
||||
<GitBranch class="h-4 w-4 sm:mr-2" /> <span class="hidden sm:inline">仓库同步</span>
|
||||
<!-- 动态新增按钮 -->
|
||||
<Button variant="outline" class="shrink-0 px-3 h-9 shadow-sm text-destructive border-destructive/20 hover:bg-destructive/10" @click="confirmBatchDelete">
|
||||
<Trash2 class="h-4 w-4 sm:mr-2" /> <span class="hidden sm:inline">批量删除</span>
|
||||
</Button>
|
||||
<Button @click="openCreate" class="shrink-0 px-3 h-9">
|
||||
<Button v-if="filterType === TASK_TYPE.NORMAL" @click="openCreate" class="shrink-0 px-3 h-9 shadow-sm">
|
||||
<Plus class="h-4 w-4 sm:mr-2" /> <span class="hidden sm:inline">新建任务</span>
|
||||
</Button>
|
||||
<Button v-else-if="filterType === TASK_TYPE.REPO" @click="openCreateRepo" class="shrink-0 px-3 h-9 shadow-sm">
|
||||
<GitBranch class="h-4 w-4 sm:mr-2" /> <span class="hidden sm:inline">同步仓库</span>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- 桌面端类型切换移到后面 -->
|
||||
<Tabs :model-value="filterType" @update:model-value="v => { filterType = String(v); handleTypeChange() }" class="shrink-0 hidden md:block">
|
||||
<TabsList class="h-9 p-1 bg-muted/30 border">
|
||||
<TabsTrigger :value="TASK_TYPE.NORMAL" class="px-4 h-7 text-[13px]">定时任务</TabsTrigger>
|
||||
<TabsTrigger :value="TASK_TYPE.REPO" class="px-4 h-7 text-[13px]">仓库同步</TabsTrigger>
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</div>
|
||||
<!-- 移动端 agent 过滤标签 -->
|
||||
<div v-if="filterAgentId"
|
||||
@@ -282,7 +340,9 @@ watch(() => route.query.agent_id, (newVal) => {
|
||||
<!-- 表头 -->
|
||||
<div
|
||||
class="flex flex-wrap sm:flex-nowrap items-center gap-x-2 gap-y-2 sm:gap-4 px-3 sm:px-4 py-2 sm:py-1.5 border-b bg-muted/20 text-xs sm:text-sm text-muted-foreground font-medium min-w-0 sm:min-w-[1000px]">
|
||||
<span class="w-10 sm:w-12 shrink-0 max-sm:order-1">序号</span>
|
||||
<div class="w-10 sm:w-12 shrink-0 flex items-center gap-2 max-sm:order-1 pl-1">
|
||||
<span class="text-xs sm:text-sm">序号</span>
|
||||
</div>
|
||||
<span class="w-8 shrink-0 text-center max-sm:order-2">类型</span>
|
||||
<span class="flex-1 min-w-0 sm:flex-none sm:w-40 md:w-48 lg:w-56 shrink-0 max-sm:order-3">名称</span>
|
||||
<span class="w-24 sm:w-32 shrink-0 hidden md:block">执行位置</span>
|
||||
@@ -304,8 +364,10 @@ watch(() => route.query.agent_id, (newVal) => {
|
||||
</div>
|
||||
<div v-for="(task, index) in tasks" :key="task.id"
|
||||
class="flex flex-wrap sm:flex-nowrap items-center gap-x-2 gap-y-2 sm:gap-4 px-3 sm:px-4 py-2.5 sm:py-1.5 hover:bg-muted/30 transition-colors">
|
||||
<span class="w-10 sm:w-12 shrink-0 text-muted-foreground text-xs sm:text-sm max-sm:order-1">#{{ total -
|
||||
(currentPage - 1) * pageSize - index }}</span>
|
||||
<div class="w-10 sm:w-12 shrink-0 flex items-center gap-2 max-sm:order-1 pl-1">
|
||||
<span class="text-muted-foreground text-xs sm:text-sm">#{{ total -
|
||||
(currentPage - 1) * pageSize - index }}</span>
|
||||
</div>
|
||||
<span class="w-8 shrink-0 flex justify-center max-sm:order-2" :title="getTaskTypeTitle(task.type || 'task')">
|
||||
<GitBranch v-if="task.type === TASK_TYPE.REPO" class="h-3.5 w-3.5 sm:h-4 sm:w-4 text-primary" />
|
||||
<Terminal v-else class="h-3.5 w-3.5 sm:h-4 sm:w-4 text-primary" />
|
||||
@@ -397,7 +459,29 @@ watch(() => route.query.agent_id, (newVal) => {
|
||||
<!-- 仓库同步弹窗 -->
|
||||
<RepoDialog v-model:open="showRepoDialog" :task="editingTask" :is-edit="isEdit" @saved="loadTasks" />
|
||||
|
||||
<!-- 删除确认 -->
|
||||
<!-- 最新日志全屏查看 -->
|
||||
<LogViewer v-model:open="showLogViewer" :task-name="latestLogTitle"
|
||||
:log-id="selectedLogId" :initial-status="latestLogStatus" />
|
||||
|
||||
<!-- 删除确认 (批量) -->
|
||||
<AlertDialog v-model:open="showBatchDeleteDialog">
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>确认批量删除</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
将会删除当前所有过滤条件下匹配的 <b>{{ total }}</b> 个任务。操作不可撤销。
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>取消</AlertDialogCancel>
|
||||
<AlertDialogAction class="bg-destructive text-white hover:bg-destructive/90" @click="batchDeleteTasks">
|
||||
确认删除
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
|
||||
<!-- 删除确认 (单个) -->
|
||||
<AlertDialog v-model:open="showDeleteDialog">
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
|
||||
Reference in New Issue
Block a user