diff --git a/web/src/views/agents/Agents.vue b/web/src/views/agents/Agents.vue index ea81ac2..af38692 100644 --- a/web/src/views/agents/Agents.vue +++ b/web/src/views/agents/Agents.vue @@ -2,30 +2,18 @@ import { ref, onMounted, computed, onUnmounted } from 'vue' import { Button } from '@/components/ui/button' import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { Switch } from '@/components/ui/switch' -import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from '@/components/ui/dialog' -import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' -import { - RefreshCw, Trash2, Pencil, Copy, Server, Search, Download, RotateCw, - Plus, Ticket, ListTodo, Eye, Wifi, WifiOff, - Zap, ZapOff, Check, X, MoreHorizontal -} from 'lucide-vue-next' -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuItem, - DropdownMenuTrigger, - DropdownMenuSeparator, -} from '@/components/ui/dropdown-menu' +import { RefreshCw, Server, Search, Download, Ticket } from 'lucide-vue-next' +import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from '@/components/ui/alert-dialog' 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() +import AgentListTab from './components/AgentListTab.vue' +import TokenListTab from './components/TokenListTab.vue' +import AgentDetailDialog from './components/AgentDetailDialog.vue' +import EditAgentDialog from './components/EditAgentDialog.vue' +import DownloadAgentDialog from './components/DownloadAgentDialog.vue' +import EditTokenDialog from './components/EditTokenDialog.vue' const agents = ref([]) const tokens = ref([]) @@ -34,30 +22,17 @@ const searchQuery = ref('') const activeTab = ref('agents') const agentVersion = ref('') const platforms = ref<{ os: string; arch: string; filename: string }[]>([]) -const showEditDialog = ref(false) + +// Dialog refs +const agentDetailDialogRef = ref | null>(null) +const editAgentDialogRef = ref | null>(null) +const downloadAgentDialogRef = ref | null>(null) +const editTokenDialogRef = ref | null>(null) + +// Delete Dialog state const showDeleteDialog = ref(false) -const showDownloadDialog = ref(false) -const showTokenDialog = ref(false) -const showEditTokenDialog = ref(false) -const showDetailDialog = ref(false) -const customScheduler = ref(false) -const formData = ref({ - name: '', - description: '', - scheduler_config: { - worker_count: 1, - queue_size: 100, - rate_interval: 200, - verbose: false, - strict_queue: false - } -}) -const tokenForm = ref({ remark: '', max_uses: 0, expires_at: '' }) -const editingToken = ref(null) -const editTokenForm = ref({ remark: '', max_uses: 0, expires_at: '' }) -const editingAgent = ref(null) const deletingAgent = ref(null) -const viewingAgent = ref(null) + let refreshTimer: ReturnType | null = null const filteredAgents = computed(() => { @@ -70,10 +45,6 @@ const filteredAgents = computed(() => { ) }) -function isOnline(agent: Agent): boolean { - return agent.status === AGENT_STATUS.ONLINE -} - async function loadAgents() { loading.value = true try { @@ -93,86 +64,20 @@ async function loadAgents() { } } +// Handler functions for Agent function viewDetail(agent: Agent) { - ;(document.activeElement as HTMLElement)?.blur() - viewingAgent.value = agent - showDetailDialog.value = true + agentDetailDialogRef.value?.openDialog(agent) } function openEditDialog(agent: Agent) { - ;(document.activeElement as HTMLElement)?.blur() - editingAgent.value = agent - customScheduler.value = !!agent.scheduler_config - formData.value = { - name: agent.name, - description: agent.description, - scheduler_config: agent.scheduler_config ? { - worker_count: agent.scheduler_config.worker_count, - queue_size: agent.scheduler_config.queue_size, - rate_interval: agent.scheduler_config.rate_interval, - verbose: agent.scheduler_config.verbose, - strict_queue: agent.scheduler_config.strict_queue - } : { - worker_count: 1, - queue_size: 100, - rate_interval: 200, - verbose: false, - strict_queue: false - } - } - showEditDialog.value = true -} - -async function updateAgent() { - if (!editingAgent.value || !formData.value.name.trim()) return - try { - const payload = { - name: formData.value.name, - description: formData.value.description, - enabled: editingAgent.value.enabled, - scheduler_config: customScheduler.value ? formData.value.scheduler_config : null - } - await api.agents.update(editingAgent.value.id, payload) - showEditDialog.value = false - await loadAgents() - toast.success('更新成功') - } catch (e: unknown) { - toast.error((e as Error).message || '更新失败') - } -} - -async function toggleEnabled(agent: Agent) { - try { - const newEnabled = !agent.enabled - await api.agents.update(agent.id, { - name: agent.name, - description: agent.description, - enabled: newEnabled, - scheduler_config: agent.scheduler_config - }) - await loadAgents() - toast.success(`${agent.name} 已${newEnabled ? '启用' : '禁用'}`) - } catch (e: unknown) { - toast.error((e as Error).message || '操作失败') - } + editAgentDialogRef.value?.openDialog(agent) } function confirmDelete(agent: Agent) { - ;(document.activeElement as HTMLElement)?.blur() deletingAgent.value = agent showDeleteDialog.value = true } -function openDownloadDialog() { - ;(document.activeElement as HTMLElement)?.blur() - showDownloadDialog.value = true -} - -function openTokenDialog() { - ;(document.activeElement as HTMLElement)?.blur() - showTokenDialog.value = true -} - async function deleteAgent() { if (!deletingAgent.value) return try { @@ -185,103 +90,17 @@ async function deleteAgent() { } } -async function forceUpdate(agent: Agent) { - try { - await api.agents.forceUpdate(agent.id) - toast.success('已标记强制更新') - } catch (e: unknown) { - toast.error((e as Error).message || '操作失败') - } +function openDownloadDialog() { + downloadAgentDialogRef.value?.openDialog() } -function viewTasks(agent: Agent) { - router.push({ path: '/tasks', query: { agent_id: String(agent.id) } }) -} - -function copyToken(token: string) { - navigator.clipboard.writeText(token) - toast.success('已复制') -} - -async function createToken() { - try { - let expiresAt = tokenForm.value.expires_at - if (expiresAt) { - // 适配后端格式: 2006-01-02 15:04:05 - expiresAt = expiresAt.replace('T', ' ') + ':00' - } - await api.agents.createToken({ - remark: tokenForm.value.remark, - max_uses: tokenForm.value.max_uses, - expires_at: expiresAt || undefined - }) - showTokenDialog.value = false - tokenForm.value = { remark: '', max_uses: 0, expires_at: '' } - await loadAgents() - toast.success('创建成功') - } catch (e: unknown) { - toast.error((e as Error).message || '创建失败') - } -} - -async function deleteToken(id: string) { - try { - await api.agents.deleteToken(id) - await loadAgents() - toast.success('删除成功') - } catch (e: unknown) { - toast.error((e as Error).message || '删除失败') - } +// Handler functions for Token +function openCreateToken() { + editTokenDialogRef.value?.openCreate() } function openEditToken(token: AgentToken) { - ;(document.activeElement as HTMLElement)?.blur() - editingToken.value = token - // expires_at 后端格式 "2006-01-02 15:04:05",转回 datetime-local 格式 - const rawExpires = token.expires_at?.replace(' ', 'T').slice(0, 16) || '' - editTokenForm.value = { remark: token.remark || '', max_uses: token.max_uses, expires_at: rawExpires } - showEditTokenDialog.value = true -} - -async function updateToken() { - if (!editingToken.value) return - try { - let expiresAt = editTokenForm.value.expires_at - if (expiresAt) { - expiresAt = expiresAt.replace('T', ' ') + ':00' - } - await api.agents.updateToken(editingToken.value.id, { - remark: editTokenForm.value.remark, - max_uses: editTokenForm.value.max_uses, - expires_at: expiresAt || undefined - }) - showEditTokenDialog.value = false - await loadAgents() - toast.success('更新成功') - } catch (e: unknown) { - toast.error((e as Error).message || '更新失败') - } -} - -function isTokenExpired(token: AgentToken) { - if (!token.expires_at) return false - // 将 "YYYY-MM-DD HH:mm:ss" 转换为 ISO 格式 "YYYY-MM-DDTHH:mm:ss" 以提高浏览器兼容性 - const dateStr = token.expires_at.replace(' ', 'T') - return new Date(dateStr) < new Date() -} - -function isTokenExhausted(token: AgentToken) { - return token.max_uses > 0 && token.used_count >= token.max_uses -} - -function downloadAgent(os: string, arch: string) { - window.open(api.agents.downloadUrl(os, arch), '_blank') -} - -function getPlatformLabel(os: string, arch: string) { - const osLabels: Record = { linux: 'Linux', windows: 'Windows', darwin: 'macOS' } - const archLabels: Record = { amd64: 'x64', arm64: 'ARM64', '386': 'x86' } - return `${osLabels[os] || os} ${archLabels[arch] || arch}` + editTokenDialogRef.value?.openEdit(token) } onMounted(() => { @@ -294,7 +113,6 @@ onUnmounted(() => { }) - diff --git a/web/src/views/agents/components/AgentDetailDialog.vue b/web/src/views/agents/components/AgentDetailDialog.vue new file mode 100644 index 0000000..af30cf7 --- /dev/null +++ b/web/src/views/agents/components/AgentDetailDialog.vue @@ -0,0 +1,99 @@ + + + diff --git a/web/src/views/agents/components/AgentListTab.vue b/web/src/views/agents/components/AgentListTab.vue new file mode 100644 index 0000000..321b0d3 --- /dev/null +++ b/web/src/views/agents/components/AgentListTab.vue @@ -0,0 +1,265 @@ + + + diff --git a/web/src/views/agents/components/DownloadAgentDialog.vue b/web/src/views/agents/components/DownloadAgentDialog.vue new file mode 100644 index 0000000..bd91920 --- /dev/null +++ b/web/src/views/agents/components/DownloadAgentDialog.vue @@ -0,0 +1,86 @@ + + + diff --git a/web/src/views/agents/components/EditAgentDialog.vue b/web/src/views/agents/components/EditAgentDialog.vue new file mode 100644 index 0000000..1393162 --- /dev/null +++ b/web/src/views/agents/components/EditAgentDialog.vue @@ -0,0 +1,139 @@ + + + diff --git a/web/src/views/agents/components/EditTokenDialog.vue b/web/src/views/agents/components/EditTokenDialog.vue new file mode 100644 index 0000000..e1af3d4 --- /dev/null +++ b/web/src/views/agents/components/EditTokenDialog.vue @@ -0,0 +1,97 @@ + + + diff --git a/web/src/views/agents/components/TokenListTab.vue b/web/src/views/agents/components/TokenListTab.vue new file mode 100644 index 0000000..038375a --- /dev/null +++ b/web/src/views/agents/components/TokenListTab.vue @@ -0,0 +1,110 @@ + + +