- 在线 Agent
- {{ overview?.agent_online ?? '-' }}
+ 就绪 Agent
+ {{ overview?.agent_ready ?? '-' }}
diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts
index 1ca0ad5..d06d622 100644
--- a/frontend/src/api/index.ts
+++ b/frontend/src/api/index.ts
@@ -1,11 +1,31 @@
const BASE = '/api/admin'
+const ADMIN_AUTH_KEY = 'gw_admin_auth'
+
+export function getAdminAuth(): string {
+ return localStorage.getItem(ADMIN_AUTH_KEY) || ''
+}
+
+export function setAdminAuth(auth: string): void {
+ localStorage.setItem(ADMIN_AUTH_KEY, auth)
+}
+
+export function clearAdminAuth(): void {
+ localStorage.removeItem(ADMIN_AUTH_KEY)
+}
async function request
(path: string, options: RequestInit = {}): Promise {
const res = await fetch(`${BASE}${path}`, {
- headers: { 'Content-Type': 'application/json' },
+ headers: {
+ 'Content-Type': 'application/json',
+ 'X-Admin-Auth': getAdminAuth(),
+ },
...options,
})
if (!res.ok) {
+ if (res.status === 401) {
+ clearAdminAuth()
+ window.dispatchEvent(new CustomEvent('gw-auth-expired'))
+ }
const text = await res.text()
throw new Error(text || `HTTP ${res.status}`)
}
@@ -31,8 +51,9 @@ export interface AgentInfo {
agent_tags: string[]
max_concurrent: number
current_load: number
+ priority: number
last_heartbeat: number
- status: 'online' | 'offline'
+ status: 'offline' | 'unavailable' | 'ready' | 'processing' | 'stopping'
create_time: number
}
@@ -53,8 +74,11 @@ export interface Overview {
task_success: number
task_failed: number
agent_total: number
- agent_online: number
+ agent_ready: number
+ agent_processing: number
+ agent_stopping: number
agent_offline: number
+ agent_unavailable: number
}
export const api = {
@@ -66,7 +90,13 @@ export const api = {
cancelTask: (id: string) => request<{ ok: boolean }>(`/tasks/${id}/cancel`, { method: 'POST' }),
resetTask: (id: string) => request<{ ok: boolean }>(`/tasks/${id}/reset`, { method: 'POST' }),
listAgents: () => request('/agents'),
- offlineAgent: (id: string) => request<{ ok: boolean }>(`/agents/${id}/offline`, { method: 'POST' }),
+ unavailableAgent: (id: string) => request<{ ok: boolean }>(`/agents/${id}/unavailable`, { method: 'POST' }),
+ availableAgent: (id: string) => request<{ ok: boolean }>(`/agents/${id}/available`, { method: 'POST' }),
+ setAgentPriority: (id: string, priority: number) =>
+ request(`/agents/${id}/priority`, {
+ method: 'POST',
+ body: JSON.stringify(priority),
+ }),
listLogs: (params?: { request_id?: string; agent_id?: string }) => {
const q = new URLSearchParams()
if (params?.request_id) q.set('request_id', params.request_id)
diff --git a/frontend/src/views/AgentView.vue b/frontend/src/views/AgentView.vue
index 8fd5bf3..eebd93f 100644
--- a/frontend/src/views/AgentView.vue
+++ b/frontend/src/views/AgentView.vue
@@ -6,6 +6,18 @@ const agents = ref([])
const loading = ref(false)
let timer: number | undefined
+const STATUS_META: Record = {
+ ready: { label: '就绪', tag: 'success', color: '#10b981', dot: 'bg-[#10b981] pulse' },
+ processing: { label: '处理中', tag: 'warning', color: '#f59e0b', dot: 'bg-[#f59e0b]' },
+ stopping: { label: '正在停止', tag: 'info', color: '#3b82f6', dot: 'bg-[#3b82f6]' },
+ offline: { label: '离线', tag: 'danger', color: '#ef4444', dot: 'bg-[#ef4444]' },
+ unavailable: { label: '不可用', tag: 'danger', color: '#9ca3af', dot: 'bg-[#9ca3af]' },
+}
+
+function meta(a: AgentInfo) {
+ return STATUS_META[a.status] || STATUS_META.offline
+}
+
async function load() {
loading.value = true
try {
@@ -26,6 +38,34 @@ function loadPct(a: AgentInfo) {
return a.max_concurrent ? Math.round((a.current_load / a.max_concurrent) * 100) : 0
}
+async function setPriority(a: AgentInfo, priority: number) {
+ try {
+ await api.setAgentPriority(a.agent_id, priority)
+ a.priority = priority
+ } catch (e) {
+ console.error(e)
+ load()
+ }
+}
+
+async function setUnavailable(a: AgentInfo) {
+ try {
+ await api.unavailableAgent(a.agent_id)
+ await load()
+ } catch (e) {
+ console.error(e)
+ }
+}
+
+async function setAvailable(a: AgentInfo) {
+ try {
+ await api.availableAgent(a.agent_id)
+ await load()
+ } catch (e) {
+ console.error(e)
+ }
+}
+
onMounted(() => {
load()
timer = window.setInterval(load, 5000)
@@ -39,7 +79,13 @@ onUnmounted(() => {
Agent 池状态
- 在线 {{ agents.filter((a) => a.status === 'online').length }} / {{ agents.length }}
+
+ 就绪 {{ agents.filter((a) => a.status === 'ready').length }} /
+ 处理中 {{ agents.filter((a) => a.status === 'processing').length }} /
+ 停止 {{ agents.filter((a) => a.status === 'stopping').length }} /
+ 不可用 {{ agents.filter((a) => a.status === 'unavailable').length }} /
+ 离线 {{ agents.filter((a) => a.status === 'offline').length }} / 共 {{ agents.length }}
+
加载中...
@@ -48,19 +94,14 @@ onUnmounted(() => {
-
+
-
+
{{ a.agent_id }}
-
- {{ a.status === 'online' ? '在线' : '离线' }}
+
+ {{ meta(a).label }}
@@ -70,6 +111,19 @@ onUnmounted(() => {
{{ t }}
+
+ 调度优先级
+ setPriority(a, v)"
+ >
+
+
+
+
数值越小,越优先分配任务
+
负载 {{ a.current_load }}/{{ a.max_concurrent }}
@@ -83,9 +137,30 @@ onUnmounted(() => {
-
+
心跳 {{ fmtTime(a.last_heartbeat) }} · 注册 {{ fmtTime(a.create_time) }}
+
+
+
+ 置为不可用
+
+
+ 置为可用
+
+
diff --git a/frontend/src/views/ControlView.vue b/frontend/src/views/ControlView.vue
index d1790fe..72f4a4a 100644
--- a/frontend/src/views/ControlView.vue
+++ b/frontend/src/views/ControlView.vue
@@ -42,17 +42,36 @@ async function resetTask(id: string) {
}
}
-async function offlineAgent(id: string) {
+async function unavailableAgent(id: string) {
try {
- await ElMessageBox.confirm(`确认下线 Agent ${id} ?`, '下线 Agent', { type: 'warning' })
- await api.offlineAgent(id)
- ElMessage.success('Agent 已下线')
+ await ElMessageBox.confirm(`确认将 Agent ${id} 置为不可用?`, '置为不可用', { type: 'warning' })
+ await api.unavailableAgent(id)
+ ElMessage.success('Agent 已置为不可用')
load()
} catch (e) {
if (e !== 'cancel') console.error(e)
}
}
+async function availableAgent(id: string) {
+ try {
+ await ElMessageBox.confirm(`确认将 Agent ${id} 置为可用?`, '置为可用', { type: 'warning' })
+ await api.availableAgent(id)
+ ElMessage.success('Agent 已置为可用')
+ load()
+ } catch (e) {
+ if (e !== 'cancel') console.error(e)
+ }
+}
+
+function statusLabel(s: string) {
+ return ({ ready: '就绪', processing: '处理中', stopping: '正在停止', offline: '离线', unavailable: '不可用' } as Record)[s] || s
+}
+
+function statusType(s: string): 'success' | 'warning' | 'info' | 'danger' {
+ return ({ ready: 'success', processing: 'warning', stopping: 'info', offline: 'danger', unavailable: 'danger' } as Record)[s] || 'info'
+}
+
onMounted(load)
@@ -85,26 +104,34 @@ onMounted(load)
-
下线 Agent
+ Agent 状态管理
-
+
-
- {{ row.status === 'online' ? '在线' : '离线' }}
+
+ {{ statusLabel(row.status) }}
-
+
- 下线
+ 置为不可用
+
+
+ 置为可用