diff --git a/web/apps/web-ele/src/api/core/index.ts b/web/apps/web-ele/src/api/core/index.ts index a8cebc6..f55626b 100644 --- a/web/apps/web-ele/src/api/core/index.ts +++ b/web/apps/web-ele/src/api/core/index.ts @@ -19,6 +19,7 @@ export * from './post'; export * from './role'; export * from './resource-scope'; export * from './system-config'; +export * from './server-health'; export * from './ui-config'; export * from './user'; export * from './websocket'; diff --git a/web/apps/web-ele/src/api/core/server-health.ts b/web/apps/web-ele/src/api/core/server-health.ts new file mode 100644 index 0000000..6e936be --- /dev/null +++ b/web/apps/web-ele/src/api/core/server-health.ts @@ -0,0 +1,62 @@ +import { requestClient } from '#/api/request'; + +export interface ServerHealthOverview { + basic_info: { + hostname: string; + ip_address: string; + platform: string; + }; + boot_time: { + uptime_formatted: string; + }; + cpu_info: { + cpu_percent: number; + total_cores: number; + }; + disk_info: { + total_read_bytes: number; + total_write_bytes: number; + }; + memory_info: { + virtual: { + percent: number; + total: number; + used: number; + }; + }; + network_info: { + total: { + bytes_recv: number; + bytes_sent: number; + }; + }; +} + +export interface ServerRealtimeStats { + cpu_percent: number; + disk_io: { + read_speed: number; + write_speed: number; + }; + memory_details: { + total: number; + used: number; + }; + memory_percent: number; + network_io: { + download_speed: number; + upload_speed: number; + }; +} + +export async function getServerHealthOverviewApi() { + return requestClient.get( + '/api/core/server_monitor/overview', + ); +} + +export async function getServerHealthRealtimeApi() { + return requestClient.get( + '/api/core/server_monitor/realtime', + ); +} diff --git a/web/apps/web-ele/src/views/_core/control-center/index.vue b/web/apps/web-ele/src/views/_core/control-center/index.vue index 3e35fc5..e9ee2a6 100644 --- a/web/apps/web-ele/src/views/_core/control-center/index.vue +++ b/web/apps/web-ele/src/views/_core/control-center/index.vue @@ -58,6 +58,12 @@ import { getUserAnnouncementListApi, } from '#/api/core/announcement'; import { getMessageListApi, getUnreadCountApi } from '#/api/core/message'; +import { + getServerHealthOverviewApi, + getServerHealthRealtimeApi, + type ServerHealthOverview, + type ServerRealtimeStats, +} from '#/api/core/server-health'; defineOptions({ name: 'ControlCenter' }); @@ -73,6 +79,8 @@ const knowledgeBases = ref([]); const runs = ref([]); const announcements = ref([]); const messages = ref([]); +const serverOverview = ref(); +const realtimeStats = ref(); const stats = reactive({ agentTotal: 0, @@ -292,6 +300,45 @@ const resourceCards = computed(() => [ }, ]); +const serverCards = computed(() => { + const overview = serverOverview.value; + const realtime = realtimeStats.value; + const memory = realtime?.memory_details || overview?.memory_info.virtual; + return [ + { + desc: `${overview?.cpu_info.total_cores || '-'} 核心`, + icon: Cpu, + label: 'CPU', + tone: 'blue', + value: `${Math.round(realtime?.cpu_percent ?? overview?.cpu_info.cpu_percent ?? 0)}%`, + }, + { + desc: + memory?.used && memory?.total + ? `${formatBytes(memory.used)} / ${formatBytes(memory.total)}` + : '等待采集', + icon: Database, + label: '内存', + tone: 'green', + value: `${Math.round(realtime?.memory_percent ?? overview?.memory_info.virtual.percent ?? 0)}%`, + }, + { + desc: `累计 ${formatBytes(overview?.disk_info.total_read_bytes)} / ${formatBytes(overview?.disk_info.total_write_bytes)}`, + icon: Server, + label: '磁盘读/写', + tone: 'amber', + value: `${formatBytes(realtime?.disk_io.read_speed)}/s`, + }, + { + desc: `上传 ${formatBytes(realtime?.network_io.upload_speed)}/s`, + icon: Network, + label: '网络下载', + tone: 'cyan', + value: `${formatBytes(realtime?.network_io.download_speed)}/s`, + }, + ]; +}); + function go(path: string) { router.push(path); } @@ -301,6 +348,18 @@ function formatTime(value?: string) { return value.replace('T', ' ').slice(0, 16); } +function formatBytes(value?: number) { + if (!value || value <= 0) return '0 B'; + const units = ['B', 'KB', 'MB', 'GB', 'TB']; + let size = value; + let unitIndex = 0; + while (size >= 1024 && unitIndex < units.length - 1) { + size /= 1024; + unitIndex += 1; + } + return `${size >= 10 || unitIndex === 0 ? size.toFixed(0) : size.toFixed(1)} ${units[unitIndex]}`; +} + function getStatusType(status: string) { if (['completed', 'published', 'success'].includes(status)) return 'success'; if (status === 'failed') return 'danger'; @@ -321,6 +380,8 @@ async function loadDashboard() { announcementUnreadRes, messageRes, messageUnreadRes, + serverOverviewRes, + realtimeStatsRes, ] = await Promise.all([ getAgentListApi({ page: 1, pageSize: 6 }), getWorkflowListApi({ page: 1, pageSize: 6 }), @@ -330,6 +391,8 @@ async function loadDashboard() { getUnreadAnnouncementCountApi(), getMessageListApi({ page: 1, pageSize: 5 }), getUnreadCountApi(), + getServerHealthOverviewApi().catch(() => undefined), + getServerHealthRealtimeApi().catch(() => undefined), ]); agents.value = agentRes.items || []; @@ -338,6 +401,8 @@ async function loadDashboard() { runs.value = runRes.items || []; announcements.value = announcementRes.items || []; messages.value = messageRes.items || []; + serverOverview.value = serverOverviewRes; + realtimeStats.value = realtimeStatsRes; stats.agentTotal = agentRes.total || agents.value.length; stats.workflowTotal = workflowRes.total || workflows.value.length; @@ -482,12 +547,18 @@ onMounted(() => {
- AI 协作资源 - ai-agent-admin · lightweight runtime + 服务器信息 + + {{ + serverOverview + ? `${serverOverview.basic_info.hostname} (${serverOverview.basic_info.ip_address}) · 运行时间 ${serverOverview.boot_time.uptime_formatted}` + : '等待服务器采集数据' + }} +
@@ -559,6 +630,17 @@ onMounted(() => { 运行概览
+
+
+ + {{ item.label }} + {{ item.value }} +
+
智能体发布率 {{ publishedAgents }} / {{ stats.agentTotal }} @@ -980,6 +1062,33 @@ onMounted(() => { font-size: 16px; } +.runtime-summary { + display: grid; + gap: 8px !important; + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.runtime-chip { + align-items: center; + background: hsl(var(--muted) / 45%); + border-radius: 8px; + display: grid !important; + gap: 5px !important; + grid-template-columns: auto 1fr auto; + min-height: 42px; + padding: 8px 10px; +} + +.runtime-chip svg { + color: hsl(var(--primary)); +} + +.runtime-chip span { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + .token-row { align-items: center; display: flex !important;