refactor: extract StatusDot common component and unify status indicator styles across history, tasks, and agents pages

This commit is contained in:
duorameng
2026-05-21 15:34:33 +08:00
parent 05e38f08c6
commit 4076924704
4 changed files with 60 additions and 55 deletions
+39
View File
@@ -0,0 +1,39 @@
<script setup lang="ts">
import { computed } from 'vue'
const props = withDefaults(
defineProps<{
state: string
title?: string
}>(),
{
state: 'none'
}
)
const dotClasses = computed(() => {
switch (props.state) {
case 'running':
return 'h-2 w-2 bg-amber-500 animate-pulse shadow-[0_0_8px_rgba(245,158,11,0.5)]'
case 'pending':
return 'h-2 w-2 bg-blue-400 animate-pulse'
case 'success':
case 'online':
return 'h-1.5 w-1.5 bg-green-500'
case 'failed':
return 'h-1.5 w-1.5 bg-red-500'
case 'timeout':
return 'h-1.5 w-1.5 bg-orange-500'
case 'cancelled':
return 'h-1.5 w-1.5 bg-muted-foreground'
case 'offline':
case 'none':
default:
return 'h-1.5 w-1.5 bg-muted-foreground/20'
}
})
</script>
<template>
<div class="rounded-full shrink-0" :class="dotClasses" :title="title"></div>
</template>
+7 -25
View File
@@ -23,6 +23,7 @@ import { api, type Agent, type AgentToken } from '@/api'
import { toast } from 'vue-sonner'
import { useRouter } from 'vue-router'
import { AGENT_STATUS } from '@/constants'
import StatusDot from '@/components/StatusDot.vue'
const router = useRouter()
@@ -339,7 +340,6 @@ onUnmounted(() => {
<!-- 表头 -->
<div class="flex items-center gap-4 px-4 py-1.5 border-b bg-muted/20 text-xs text-muted-foreground font-medium">
<span class="w-12 shrink-0 pl-1">序号</span>
<span class="w-8 shrink-0 text-center">状态</span>
<span class="w-48 shrink-0">名称</span>
<span class="w-32 shrink-0">IP 地址</span>
<span class="w-32 shrink-0">主机名</span>
@@ -354,16 +354,9 @@ onUnmounted(() => {
{{ searchQuery ? '无匹配结果' : '暂无 Agent' }}
</div>
<div v-for="(agent, index) in filteredAgents" :key="`large-${agent.id}`"
class="flex items-center gap-4 px-4 py-1.5 hover:bg-muted/30 transition-colors">
<div class="w-12 shrink-0 pl-1 text-muted-foreground tabular-nums">#{{ filteredAgents.length - index }}</div>
<span class="w-8 shrink-0 flex justify-center" :title="isOnline(agent) ? '在线' : '离线'">
<div v-if="isOnline(agent)" class="h-5 w-5 rounded-full bg-green-500/10 flex items-center justify-center">
<Wifi class="h-3 w-3 text-green-500" />
</div>
<div v-else class="h-5 w-5 rounded-full bg-muted flex items-center justify-center">
<WifiOff class="h-3 w-3 text-muted-foreground" />
</div>
</span>
class="flex items-center gap-2 px-4 py-1.5 hover:bg-muted/30 transition-colors">
<StatusDot :state="isOnline(agent) ? 'online' : 'offline'" :title="isOnline(agent) ? '在线' : '离线'" />
<div class="w-12 shrink-0 pl-1 text-muted-foreground tabular-nums text-sm">#{{ filteredAgents.length - index }}</div>
<div class="w-48 shrink-0 flex flex-col justify-center gap-0.5 overflow-hidden">
<span class="font-medium truncate cursor-pointer hover:text-primary transition-colors" @click="viewDetail(agent)">{{ agent.name }}</span>
<div v-if="agent.description" class="text-[10px] text-muted-foreground truncate">{{ agent.description }}</div>
@@ -413,7 +406,6 @@ onUnmounted(() => {
<!-- 表头 -->
<div class="flex items-center gap-4 px-4 py-1.5 border-b bg-muted/20 text-xs text-muted-foreground font-medium">
<span class="w-12 shrink-0 pl-1">序号</span>
<span class="w-8 shrink-0 text-center">状态</span>
<span class="w-48 shrink-0">名称</span>
<span class="flex-1 min-w-0">IP 地址</span>
<span class="w-24 shrink-0 text-center">操作</span>
@@ -421,16 +413,9 @@ onUnmounted(() => {
<!-- 列表 -->
<div class="divide-y text-sm">
<div v-for="(agent, index) in filteredAgents" :key="`medium-${agent.id}`"
class="flex items-center gap-4 px-4 py-2.5 hover:bg-muted/30 transition-colors">
class="flex items-center gap-2 px-4 py-2.5 hover:bg-muted/30 transition-colors">
<StatusDot :state="isOnline(agent) ? 'online' : 'offline'" :title="isOnline(agent) ? '在线' : '离线'" />
<div class="w-12 shrink-0 pl-1 text-muted-foreground tabular-nums text-xs">#{{ filteredAgents.length - index }}</div>
<span class="w-8 shrink-0 flex justify-center" :title="isOnline(agent) ? '在线' : '离线'">
<div v-if="isOnline(agent)" class="h-5 w-5 rounded-full bg-green-500/10 flex items-center justify-center">
<Wifi class="h-3 w-3 text-green-500" />
</div>
<div v-else class="h-5 w-5 rounded-full bg-muted flex items-center justify-center">
<WifiOff class="h-3 w-3 text-muted-foreground" />
</div>
</span>
<div class="w-48 shrink-0 flex flex-col justify-center gap-0.5 overflow-hidden">
<span class="font-medium truncate">{{ agent.name }}</span>
<div v-if="agent.description" class="text-[10px] text-muted-foreground truncate">{{ agent.description }}</div>
@@ -465,11 +450,8 @@ onUnmounted(() => {
<div v-for="(agent, index) in filteredAgents" :key="`small-${agent.id}`" class="p-3 hover:bg-muted/50 transition-colors">
<div class="flex items-start justify-between mb-3 border-b border-border/40 pb-2">
<div class="flex items-center gap-2 flex-1 min-w-0 pr-2">
<StatusDot :state="isOnline(agent) ? 'online' : 'offline'" :title="isOnline(agent) ? '在线' : '离线'" />
<span class="text-xs text-muted-foreground tabular-nums flex-shrink-0">#{{ filteredAgents.length - index }}</span>
<span class="flex items-center shrink-0">
<Wifi v-if="isOnline(agent)" class="h-3.5 w-3.5 text-green-500" />
<WifiOff v-else class="h-3.5 w-3.5 text-muted-foreground" />
</span>
<div class="flex items-center gap-1.5 min-w-0 flex-1">
<span class="font-bold text-sm truncate" @click="viewDetail(agent)">{{ agent.name }}</span>
</div>
+9 -27
View File
@@ -8,8 +8,7 @@ import Pagination from '@/components/Pagination.vue'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import LogViewer from './LogViewer.vue'
import {
RefreshCw, X, Search, GitBranch, Terminal,
AlertCircle, Ban, Clock, Zap as ZapIcon, Check, Trash2
RefreshCw, Search, GitBranch, Terminal, Trash2
} from 'lucide-vue-next'
import { api, type TaskLog } from '@/api'
import LogDetailCard from '@/components/LogDetailCard.vue'
@@ -17,6 +16,7 @@ import BaihuDialog from '@/components/ui/BaihuDialog.vue'
import { toast } from 'vue-sonner'
import { useSiteSettings } from '@/composables/useSiteSettings'
import TextOverflow from '@/components/TextOverflow.vue'
import StatusDot from '@/components/StatusDot.vue'
import { useEventBus } from '@/composables/useEventBus'
const route = useRoute()
@@ -379,8 +379,7 @@ watch(() => route.query, (newQuery) => {
<!-- 小屏表头 -->
<div
class="flex sm:hidden items-center gap-2 px-3 py-2 border-b bg-muted/20 text-xs text-muted-foreground font-medium">
<span class="w-3 shrink-0"></span>
<span class="w-14 shrink-0">序号</span>
<span class="w-14 shrink-0 pl-1">序号</span>
<span class="w-8 shrink-0 text-center">类型</span>
<span class="flex-1 min-w-0">任务名称</span>
<span class="w-16 text-right shrink-0">耗时</span>
@@ -389,8 +388,7 @@ watch(() => route.query, (newQuery) => {
<!-- 大屏表头 -->
<div
class="hidden sm:flex items-center gap-4 px-4 h-11 border-b bg-muted/20 text-sm text-muted-foreground font-medium">
<span class="w-3 shrink-0"></span>
<span class="w-16 shrink-0">序号</span>
<span class="w-16 shrink-0 pl-1">序号</span>
<span class="w-12 shrink-0 text-center">类型</span>
<span class="w-36 shrink-0">任务名称</span>
<span class="flex-1 min-w-0">命令</span>
@@ -409,16 +407,8 @@ watch(() => route.query, (newQuery) => {
]" @click="selectLog(log)">
<!-- 小屏行 -->
<div class="flex sm:hidden items-center gap-2 px-3 py-2">
<span class="w-3 flex justify-center shrink-0">
<div v-if="log.status === TASK_STATUS.SUCCESS" class="h-1.5 w-1.5 rounded-full bg-green-500 shrink-0" title="成功"></div>
<div v-else-if="log.status === TASK_STATUS.FAILED" class="h-1.5 w-1.5 rounded-full bg-red-500 shrink-0" title="失败"></div>
<div v-else-if="log.status === TASK_STATUS.RUNNING" class="h-1.5 w-1.5 rounded-full bg-amber-500 animate-pulse shadow-[0_0_8px_rgba(245,158,11,0.5)] shrink-0" title="运行中"></div>
<div v-else-if="log.status === TASK_STATUS.PENDING" class="h-1.5 w-1.5 rounded-full bg-blue-400 animate-pulse shrink-0" title="等待中"></div>
<div v-else-if="log.status === TASK_STATUS.TIMEOUT" class="h-1.5 w-1.5 rounded-full bg-orange-500 shrink-0" title="超时"></div>
<div v-else-if="log.status === TASK_STATUS.CANCELLED" class="h-1.5 w-1.5 rounded-full bg-muted-foreground shrink-0" title="已取消"></div>
<div v-else class="h-1.5 w-1.5 rounded-full bg-muted-foreground/20 shrink-0"></div>
</span>
<span class="w-14 shrink-0 text-muted-foreground text-xs">#{{ total - (currentPage - 1) * pageSize - index
<StatusDot :state="log.status" />
<span class="w-14 shrink-0 text-muted-foreground text-xs tabular-nums">#{{ total - (currentPage - 1) * pageSize - index
}}</span>
<span class="w-8 shrink-0 flex justify-center" :title="getTaskTypeTitle(log.task_type || 'task')">
<GitBranch v-if="log.task_type === TASK_TYPE.REPO" class="h-3.5 w-3.5 text-primary" />
@@ -436,17 +426,9 @@ watch(() => route.query, (newQuery) => {
</span>
</div>
<!-- 大屏行 -->
<div class="hidden sm:flex items-center gap-4 px-4 py-2">
<span class="w-3 flex justify-center shrink-0">
<div v-if="log.status === TASK_STATUS.SUCCESS" class="h-2 w-2 rounded-full bg-green-500 shrink-0" title="成功"></div>
<div v-else-if="log.status === TASK_STATUS.FAILED" class="h-2 w-2 rounded-full bg-red-500 shrink-0" title="失败"></div>
<div v-else-if="log.status === TASK_STATUS.RUNNING" class="h-2 w-2 rounded-full bg-amber-500 animate-pulse shadow-[0_0_8px_rgba(245,158,11,0.5)] shrink-0" title="运行中"></div>
<div v-else-if="log.status === TASK_STATUS.PENDING" class="h-2 w-2 rounded-full bg-blue-400 animate-pulse shrink-0" title="等待中"></div>
<div v-else-if="log.status === TASK_STATUS.TIMEOUT" class="h-2 w-2 rounded-full bg-orange-500 shrink-0" title="超时"></div>
<div v-else-if="log.status === TASK_STATUS.CANCELLED" class="h-2 w-2 rounded-full bg-muted-foreground shrink-0" title="已取消"></div>
<div v-else class="h-2 w-2 rounded-full bg-muted-foreground/20 shrink-0"></div>
</span>
<span class="w-16 shrink-0 text-muted-foreground text-sm">#{{ total - (currentPage - 1) * pageSize - index
<div class="hidden sm:flex items-center gap-2 px-4 py-2">
<StatusDot :state="log.status" />
<span class="w-16 shrink-0 text-muted-foreground text-sm tabular-nums">#{{ total - (currentPage - 1) * pageSize - index
}}</span>
<span class="w-10 shrink-0 flex justify-center" :title="getTaskTypeTitle(log.task_type || 'task')">
<GitBranch v-if="log.task_type === TASK_TYPE.REPO" class="h-4 w-4 text-primary" />
+5 -3
View File
@@ -19,6 +19,7 @@ import {
DropdownMenuTrigger,
DropdownMenuSeparator,
} from '@/components/ui/dropdown-menu'
import StatusDot from '@/components/StatusDot.vue'
import { Checkbox } from '@/components/ui/checkbox'
import { Label } from '@/components/ui/label'
import { api, type Agent, type Task, type TaskLog } from '@/api'
@@ -664,9 +665,10 @@ watch(() => route.query.agent_id, (newVal: any) => {
<div class="divide-y text-sm">
<div v-for="(task, index) in tasks" :key="`large-${task.id}`"
class="flex items-center gap-2 px-4 py-1.5 hover:bg-muted/30 transition-colors">
<div v-if="task.running_status === 'running'" class="h-2 w-2 rounded-full bg-amber-500 animate-pulse shadow-[0_0_8px_rgba(245,158,11,0.5)] shrink-0" title="运行中" />
<div v-else-if="task.running_status === 'queued' || task.running_status === 'pending'" class="h-2 w-2 rounded-full bg-blue-400 animate-pulse shrink-0" title="排队中" />
<div v-else class="h-1.5 w-1.5 rounded-full bg-muted-foreground/20 shrink-0" />
<StatusDot
:state="task.running_status === 'running' ? 'running' : (task.running_status === 'queued' || task.running_status === 'pending' ? 'pending' : 'none')"
:title="task.running_status === 'running' ? '运行中' : (task.running_status === 'queued' || task.running_status === 'pending' ? '排队中' : '')"
/>
<div class="w-12 shrink-0 text-muted-foreground tabular-nums">#{{ total - (currentPage - 1) * pageSize - index }}</div>
<span class="w-8 shrink-0 flex justify-center" :title="getTaskTypeTitle(task.type || 'task')">
<div class="relative">