feat: add dependent tasks view for environments (#126)
Add a dialog in the environment variables list to view which tasks depend on a specific variable/secret.
This commit is contained in:
@@ -3,7 +3,7 @@ import { ref, watch, onMounted } from 'vue'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import Pagination from '@/components/Pagination.vue'
|
||||
import { Plus, Pencil, Trash2, Eye, EyeOff, Search, AlertTriangle, Terminal, Zap, ZapOff, Shield, Tag } from 'lucide-vue-next'
|
||||
import { Plus, Pencil, Trash2, Eye, EyeOff, Search, AlertTriangle, Terminal, Zap, ZapOff, Shield, Tag, Link } from 'lucide-vue-next'
|
||||
import TextOverflow from '@/components/TextOverflow.vue'
|
||||
import TagInput from '@/components/TagInput.vue'
|
||||
import { api, type EnvVar } from '@/api'
|
||||
@@ -16,6 +16,7 @@ import { Badge } from '@/components/ui/badge'
|
||||
|
||||
import EditEnvDialog from './components/EditEnvDialog.vue'
|
||||
import DeleteEnvDialog from './components/DeleteEnvDialog.vue'
|
||||
import DependentTasksDialog from './components/DependentTasksDialog.vue'
|
||||
|
||||
function formatDate(dateStr?: string) {
|
||||
if (!dateStr) return '-'
|
||||
@@ -41,6 +42,7 @@ let searchTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
const editDialogRef = ref<InstanceType<typeof EditEnvDialog> | null>(null)
|
||||
const deleteDialogRef = ref<InstanceType<typeof DeleteEnvDialog> | null>(null)
|
||||
const dependentTasksDialogRef = ref<InstanceType<typeof DependentTasksDialog> | null>(null)
|
||||
|
||||
async function checkSecretStatus() {
|
||||
try {
|
||||
@@ -100,6 +102,10 @@ function openEdit(env: EnvVar) {
|
||||
editDialogRef.value?.openEdit(env)
|
||||
}
|
||||
|
||||
function openDependentTasks(env: EnvVar) {
|
||||
dependentTasksDialogRef.value?.open(env)
|
||||
}
|
||||
|
||||
function confirmDelete(id: string) {
|
||||
deleteDialogRef.value?.confirmDelete(id, activeTab.value)
|
||||
}
|
||||
@@ -239,16 +245,19 @@ onMounted(() => {
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="w-24 shrink-0 flex items-center justify-center gap-1">
|
||||
<Button variant="ghost" size="icon" class="h-7 w-7" @click="toggleShow(env.id)" :title="showValues[env.id] ? '隐藏' : '显示'">
|
||||
<Eye v-if="!showValues[env.id]" class="h-3.5 w-3.5" />
|
||||
<EyeOff v-else class="h-3.5 w-3.5" />
|
||||
<div class="w-24 shrink-0 flex justify-center">
|
||||
<Button variant="ghost" size="icon" class="h-6 w-6" @click="toggleShow(env.id)" :title="showValues[env.id] ? '隐藏' : '显示'">
|
||||
<Eye v-if="!showValues[env.id]" class="h-3 w-3" />
|
||||
<EyeOff v-else class="h-3 w-3" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" class="h-7 w-7" @click="openEdit(env)" title="编辑">
|
||||
<Pencil class="h-3.5 w-3.5" />
|
||||
<Button variant="ghost" size="icon" class="h-6 w-6" @click="openDependentTasks(env)" title="依赖任务">
|
||||
<Link class="h-3 w-3" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" class="h-7 w-7 text-destructive" @click="confirmDelete(env.id)" title="删除">
|
||||
<Trash2 class="h-3.5 w-3.5" />
|
||||
<Button variant="ghost" size="icon" class="h-6 w-6" @click="openEdit(env)" title="编辑">
|
||||
<Pencil class="h-3 w-3" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" class="h-6 w-6 text-destructive" @click="confirmDelete(env.id)" title="删除">
|
||||
<Trash2 class="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -296,16 +305,19 @@ onMounted(() => {
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="w-24 shrink-0 flex items-center justify-center gap-1">
|
||||
<Button variant="ghost" size="icon" class="h-7 w-7" @click="toggleShow(env.id)">
|
||||
<Eye v-if="!showValues[env.id]" class="h-3.5 w-3.5" />
|
||||
<EyeOff v-else class="h-3.5 w-3.5" />
|
||||
<div class="w-24 shrink-0 flex justify-center">
|
||||
<Button variant="ghost" size="icon" class="h-6 w-6" @click="toggleShow(env.id)">
|
||||
<Eye v-if="!showValues[env.id]" class="h-3 w-3" />
|
||||
<EyeOff v-else class="h-3 w-3" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" class="h-7 w-7" @click="openEdit(env)">
|
||||
<Pencil class="h-3.5 w-3.5" />
|
||||
<Button variant="ghost" size="icon" class="h-6 w-6" @click="openDependentTasks(env)" title="依赖任务">
|
||||
<Link class="h-3 w-3" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" class="h-7 w-7 text-destructive" @click="confirmDelete(env.id)">
|
||||
<Trash2 class="h-3.5 w-3.5" />
|
||||
<Button variant="ghost" size="icon" class="h-6 w-6" @click="openEdit(env)">
|
||||
<Pencil class="h-3 w-3" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon" class="h-6 w-6 text-destructive" @click="confirmDelete(env.id)">
|
||||
<Trash2 class="h-3 w-3" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -353,12 +365,15 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 items-center pt-2 mt-2 border-t border-border/40 -mx-1">
|
||||
<div class="grid grid-cols-4 items-center pt-2 mt-2 border-t border-border/40 -mx-1">
|
||||
<Button variant="ghost" class="h-9 px-0 text-xs gap-1.5 hover:bg-primary/5 rounded-none" @click="toggleShow(env.id)">
|
||||
<Eye v-if="!showValues[env.id]" class="h-3.5 w-3.5" />
|
||||
<EyeOff v-else class="h-3.5 w-3.5" />
|
||||
{{ showValues[env.id] ? '隐藏' : '显示' }}
|
||||
</Button>
|
||||
<Button variant="ghost" class="h-9 px-0 text-xs gap-1.5 hover:bg-primary/5 rounded-none border-l border-border/10" @click="openDependentTasks(env)">
|
||||
<Link class="h-3.5 w-3.5" />任务
|
||||
</Button>
|
||||
<Button variant="ghost" class="h-9 px-0 text-xs gap-1.5 hover:bg-primary/5 rounded-none border-l border-border/10" @click="openEdit(env)">
|
||||
<Pencil class="h-3.5 w-3.5" />编辑
|
||||
</Button>
|
||||
@@ -377,5 +392,6 @@ onMounted(() => {
|
||||
|
||||
<EditEnvDialog ref="editDialogRef" @saved="loadEnvVars" />
|
||||
<DeleteEnvDialog ref="deleteDialogRef" @deleted="loadEnvVars" />
|
||||
<DependentTasksDialog ref="dependentTasksDialogRef" />
|
||||
</Tabs>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from '@/components/ui/dialog'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { api, type Task, type EnvVar } from '@/api'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { AlertCircle, Terminal, RefreshCw, CalendarClock, Link } from 'lucide-vue-next'
|
||||
import { ENV_TYPE } from '@/constants'
|
||||
|
||||
const isOpen = ref(false)
|
||||
const isLoading = ref(false)
|
||||
const envVar = ref<EnvVar | null>(null)
|
||||
const tasks = ref<Task[]>([])
|
||||
|
||||
async function open(env: EnvVar) {
|
||||
envVar.value = env
|
||||
isOpen.value = true
|
||||
await loadTasks()
|
||||
}
|
||||
|
||||
async function loadTasks() {
|
||||
if (!envVar.value?.id) return
|
||||
|
||||
isLoading.value = true
|
||||
try {
|
||||
const res = await api.env.tasks(envVar.value.id)
|
||||
tasks.value = res || []
|
||||
} catch {
|
||||
toast.error('加载关联任务失败')
|
||||
} finally {
|
||||
isLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function getTriggerIcon(type: string) {
|
||||
if (type === 'cron') return CalendarClock
|
||||
if (type === 'startup') return RefreshCw
|
||||
return Terminal
|
||||
}
|
||||
|
||||
function getTriggerText(type: string) {
|
||||
if (type === 'cron') return '定时执行'
|
||||
if (type === 'startup') return '启动执行'
|
||||
return '终端任务'
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog v-model:open="isOpen">
|
||||
<DialogContent class="w-[calc(100vw-2rem)] max-w-md min-w-0 flex flex-col max-h-[85vh]">
|
||||
<DialogHeader class="shrink-0 text-left">
|
||||
<DialogTitle class="flex items-center gap-2">
|
||||
<Link class="h-4 w-4" />
|
||||
<span>依赖该{{ envVar?.type === ENV_TYPE.SECRET ? '机密' : '变量' }}的任务</span>
|
||||
</DialogTitle>
|
||||
<DialogDescription class="truncate" :title="envVar?.name">
|
||||
{{ envVar?.name }}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
|
||||
<div class="flex-1 overflow-y-auto min-h-[200px] -mx-1 px-1">
|
||||
<div v-if="isLoading" class="flex items-center justify-center h-full text-muted-foreground text-sm py-12">
|
||||
加载中...
|
||||
</div>
|
||||
|
||||
<div v-else-if="tasks.length === 0" class="flex flex-col items-center justify-center h-full text-muted-foreground text-sm py-12 gap-3">
|
||||
<div class="h-10 w-10 rounded-full bg-muted flex items-center justify-center">
|
||||
<AlertCircle class="h-5 w-5 opacity-50" />
|
||||
</div>
|
||||
<span>当前没有任务依赖此{{ envVar?.type === ENV_TYPE.SECRET ? '机密' : '变量' }}</span>
|
||||
</div>
|
||||
|
||||
<div v-else class="space-y-2">
|
||||
<div v-for="task in tasks" :key="task.id" class="flex flex-col p-3 rounded-lg border bg-card/50 hover:bg-muted/50 transition-colors">
|
||||
<div class="flex items-start justify-between gap-3">
|
||||
<div class="flex flex-col min-w-0">
|
||||
<span class="font-medium text-sm truncate" :title="task.name">{{ task.name }}</span>
|
||||
<div class="flex items-center gap-1.5 mt-1">
|
||||
<Badge variant="secondary" class="text-[9px] h-4 px-1 rounded uppercase font-medium">
|
||||
<component :is="getTriggerIcon(task.trigger_type)" class="h-2.5 w-2.5 mr-1 inline-block" />
|
||||
{{ getTriggerText(task.trigger_type) }}
|
||||
</Badge>
|
||||
<span v-if="task.schedule" class="text-[10px] text-muted-foreground bg-muted px-1 rounded truncate">
|
||||
{{ task.schedule }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="shrink-0 pt-0.5">
|
||||
<div v-if="task.enabled" class="px-1.5 py-0.5 rounded text-[10px] bg-green-500/10 text-green-600 dark:text-green-400 font-medium whitespace-nowrap">
|
||||
已启用
|
||||
</div>
|
||||
<div v-else class="px-1.5 py-0.5 rounded text-[10px] bg-muted text-muted-foreground font-medium whitespace-nowrap">
|
||||
已禁用
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="task.remark" class="mt-2 text-xs text-muted-foreground line-clamp-2">
|
||||
{{ task.remark }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<DialogFooter class="shrink-0 mt-4 sm:mt-4">
|
||||
<Button variant="outline" @click="isOpen = false">关闭</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
Reference in New Issue
Block a user