refactor: extract StatusDot common component and unify status indicator styles across history, tasks, and agents pages
This commit is contained in:
@@ -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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user