From 3016121cc4c93c910d7246124ae2b5c1186fcc6d Mon Sep 17 00:00:00 2001 From: dushixiang Date: Thu, 8 Jan 2026 22:22:34 +0800 Subject: [PATCH] =?UTF-8?q?=E7=BB=A7=E7=BB=AD=E4=BC=98=E5=8C=96=E8=B0=83?= =?UTF-8?q?=E6=95=B4=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/handler/agent_admin_handler.go | 40 +++++++++++++ internal/handler/agent_query_handler.go | 58 +++---------------- web/src/portal/components/ServerCard.tsx | 19 ++++-- .../pages/{Public => }/MonitorDetail.tsx | 10 ++-- .../portal/pages/{Public => }/MonitorList.tsx | 4 +- .../pages/{Public => }/ServerDetail.tsx | 38 ++++++------ .../portal/pages/{Public => }/ServerList.tsx | 52 +++++++++++------ web/src/portal/utils/server.ts | 4 ++ web/src/router/index.tsx | 8 +-- web/src/types/index.ts | 14 ++--- 10 files changed, 137 insertions(+), 110 deletions(-) rename web/src/portal/pages/{Public => }/MonitorDetail.tsx (93%) rename web/src/portal/pages/{Public => }/MonitorList.tsx (99%) rename web/src/portal/pages/{Public => }/ServerDetail.tsx (90%) rename web/src/portal/pages/{Public => }/ServerList.tsx (90%) create mode 100644 web/src/portal/utils/server.ts diff --git a/internal/handler/agent_admin_handler.go b/internal/handler/agent_admin_handler.go index 26d1c32..1ee526a 100644 --- a/internal/handler/agent_admin_handler.go +++ b/internal/handler/agent_admin_handler.go @@ -12,6 +12,46 @@ import ( "go.uber.org/zap" ) +// Paging 探针分页查询 +func (h *AgentHandler) Paging(c echo.Context) error { + status := c.QueryParam("status") + + pr := orz.GetPageRequest(c, "name") + + builder := orz.NewPageBuilder(h.agentService.AgentRepo.Repository). + PageRequest(pr). + Contains("name", c.QueryParam("name")). + Contains("hostname", c.QueryParam("hostname")). + Contains("ip", c.QueryParam("ip")) + + // 处理状态筛选 + if status == "online" { + builder.Equal("status", "1") + } else if status == "offline" { + builder.Equal("status", "0") + } + + ctx := c.Request().Context() + page, err := builder.Execute(ctx) + if err != nil { + return err + } + return orz.Ok(c, page) +} + +// GetForAdmin 获取探针详情(管理员接口,显示完整信息) +func (h *AgentHandler) GetForAdmin(c echo.Context) error { + id := c.Param("id") + ctx := c.Request().Context() + + agent, err := h.agentService.GetAgent(ctx, id) + if err != nil { + return err + } + + return orz.Ok(c, agent) +} + // SendCommand 向探针发送指令 func (h *AgentHandler) SendCommand(c echo.Context) error { agentID := c.Param("id") diff --git a/internal/handler/agent_query_handler.go b/internal/handler/agent_query_handler.go index ef223b8..794e7d2 100644 --- a/internal/handler/agent_query_handler.go +++ b/internal/handler/agent_query_handler.go @@ -11,33 +11,6 @@ import ( "github.com/labstack/echo/v4" ) -// Paging 探针分页查询 -func (h *AgentHandler) Paging(c echo.Context) error { - status := c.QueryParam("status") - - pr := orz.GetPageRequest(c, "name") - - builder := orz.NewPageBuilder(h.agentService.AgentRepo.Repository). - PageRequest(pr). - Contains("name", c.QueryParam("name")). - Contains("hostname", c.QueryParam("hostname")). - Contains("ip", c.QueryParam("ip")) - - // 处理状态筛选 - if status == "online" { - builder.Equal("status", "1") - } else if status == "offline" { - builder.Equal("status", "0") - } - - ctx := c.Request().Context() - page, err := builder.Execute(ctx) - if err != nil { - return err - } - return orz.Ok(c, page) -} - // Get 获取探针详情(公开接口,已登录返回全部,未登录返回公开可见) func (h *AgentHandler) Get(c echo.Context) error { id := c.Param("id") @@ -59,19 +32,6 @@ func (h *AgentHandler) Get(c echo.Context) error { return orz.Ok(c, agent) } -// GetForAdmin 获取探针详情(管理员接口,显示完整信息) -func (h *AgentHandler) GetForAdmin(c echo.Context) error { - id := c.Param("id") - ctx := c.Request().Context() - - agent, err := h.agentService.GetAgent(ctx, id) - if err != nil { - return err - } - - return orz.Ok(c, agent) -} - // GetAgents 获取探针列表(公开接口,已登录返回全部,未登录返回公开可见) func (h *AgentHandler) GetAgents(c echo.Context) error { ctx := c.Request().Context() @@ -101,7 +61,7 @@ func (h *AgentHandler) GetAgents(c echo.Context) error { } func (h *AgentHandler) buildAgentListItem(agent models.Agent) map[string]interface{} { - item := map[string]interface{}{ + item := map[string]any{ "id": agent.ID, "name": agent.Name, "os": agent.OS, @@ -115,14 +75,14 @@ func (h *AgentHandler) buildAgentListItem(agent models.Agent) map[string]interfa } trafficStats := agent.TrafficStats.Data() - item["trafficLimit"] = trafficStats.Limit - item["trafficUsed"] = trafficStats.Used - item["trafficResetDay"] = trafficStats.ResetDay - item["trafficPeriodStart"] = trafficStats.PeriodStart - item["trafficBaselineRecv"] = trafficStats.BaselineRecv - item["trafficAlertSent80"] = trafficStats.AlertSent80 - item["trafficAlertSent90"] = trafficStats.AlertSent90 - item["trafficAlertSent100"] = trafficStats.AlertSent100 + if trafficStats.Limit > 0 { + item["traffic"] = map[string]any{ + "limit": trafficStats.Limit, + "used": trafficStats.Used, + } + } else { + item["traffic"] = map[string]any{} + } metrics, ok := h.metricService.GetLatestMetrics(agent.ID) if ok { diff --git a/web/src/portal/components/ServerCard.tsx b/web/src/portal/components/ServerCard.tsx index 69c5fe3..d922eec 100644 --- a/web/src/portal/components/ServerCard.tsx +++ b/web/src/portal/components/ServerCard.tsx @@ -17,6 +17,8 @@ import CompactResourceBar from '@portal/components/CompactResourceBar'; import {formatBytes, formatSpeed, formatUptime} from '@portal/utils/util'; import CyberCard from "@portal/components/CyberCard.tsx"; import {Link} from "react-router-dom"; +import {isExpired} from "@portal/utils/server.ts"; +import {cn} from "@/lib/utils.ts"; interface AgentWithMetrics extends Agent { metrics?: LatestMetrics; @@ -71,7 +73,10 @@ const ServerCard: FC = ({server}) => { {/* 顶部:名称和状态 */}
-
+
{server.name || server.hostname}
= ({server}) => {
{server.expireTime > 0 && (
+ className={cn( + `text-xs font-mono flex items-center gap-1 text-gray-600 dark:text-cyan-500`, + // 剩余时间小于 30 天时显示为红色 + isExpired(server.expireTime) ? 'text-red-600 dark:text-red-400' : '' + )}> {new Date(server.expireTime).toLocaleDateString('zh-CN')}
@@ -204,16 +213,16 @@ const ServerCard: FC = ({server}) => {
{/* 流量限制 */} - {server.trafficLimit > 0 && ( + {server.traffic?.limit > 0 && (
流量使用 - {Math.round((server.trafficUsed || 0) / server.trafficLimit * 100)}% + {Math.round((server.traffic?.used || 0) / server.traffic?.limit * 100)}%
diff --git a/web/src/portal/pages/Public/MonitorDetail.tsx b/web/src/portal/pages/MonitorDetail.tsx similarity index 93% rename from web/src/portal/pages/Public/MonitorDetail.tsx rename to web/src/portal/pages/MonitorDetail.tsx index 1562142..29e17a4 100644 --- a/web/src/portal/pages/Public/MonitorDetail.tsx +++ b/web/src/portal/pages/MonitorDetail.tsx @@ -2,11 +2,11 @@ import {useNavigate, useParams} from 'react-router-dom'; import {useQuery} from '@tanstack/react-query'; import {getMonitorAgentStats, getMonitorStatsById} from '@/api/monitor.ts'; import type {AgentMonitorStat, PublicMonitor} from '@/types'; -import {MonitorHero} from '@portal/components/monitor/MonitorHero'; -import {ResponseTimeChart} from '@portal/components/monitor/ResponseTimeChart'; -import {AgentStatsTable} from '@portal/components/monitor/AgentStatsTable'; -import {EmptyState} from '@portal/components/EmptyState'; -import {LoadingSpinner} from '@portal/components/LoadingSpinner'; +import {MonitorHero} from '@portal/components/monitor/MonitorHero.tsx'; +import {ResponseTimeChart} from '@portal/components/monitor/ResponseTimeChart.tsx'; +import {AgentStatsTable} from '@portal/components/monitor/AgentStatsTable.tsx'; +import {EmptyState} from '@portal/components/EmptyState.tsx'; +import {LoadingSpinner} from '@portal/components/LoadingSpinner.tsx'; /** * 监控详情页面 diff --git a/web/src/portal/pages/Public/MonitorList.tsx b/web/src/portal/pages/MonitorList.tsx similarity index 99% rename from web/src/portal/pages/Public/MonitorList.tsx rename to web/src/portal/pages/MonitorList.tsx index 523b503..ca5800a 100644 --- a/web/src/portal/pages/Public/MonitorList.tsx +++ b/web/src/portal/pages/MonitorList.tsx @@ -4,9 +4,9 @@ import {useQuery} from '@tanstack/react-query'; import {AlertTriangle, BarChart3, CheckCircle2, Globe, Loader2, Maximize2, Search, Shield, Zap} from 'lucide-react'; import {getPublicMonitors} from '@/api/monitor.ts'; import type {PublicMonitor} from '@/types'; -import {cn} from '@/lib/utils'; +import {cn} from '@/lib/utils.ts'; import StatBlock from "@portal/components/StatBlock.tsx"; -import MonitorCard, {type DisplayMode} from "@portal/components/monitor/MonitorCard"; +import MonitorCard, {type DisplayMode} from "@portal/components/monitor/MonitorCard.tsx"; const LoadingSpinner = () => (
diff --git a/web/src/portal/pages/Public/ServerDetail.tsx b/web/src/portal/pages/ServerDetail.tsx similarity index 90% rename from web/src/portal/pages/Public/ServerDetail.tsx rename to web/src/portal/pages/ServerDetail.tsx index 58fb75c..eda8c42 100644 --- a/web/src/portal/pages/Public/ServerDetail.tsx +++ b/web/src/portal/pages/ServerDetail.tsx @@ -1,24 +1,24 @@ import {useState} from 'react'; import {useNavigate, useParams} from 'react-router-dom'; -import {Card} from '@portal/components/Card'; -import {EmptyState} from '@portal/components/EmptyState'; -import {LoadingSpinner} from '@portal/components/LoadingSpinner'; -import {TimeRangeSelector} from '@portal/components/TimeRangeSelector'; -import {GpuMonitorSection} from '@portal/components/server/GpuMonitorSection'; -import {NetworkConnectionSection} from '@portal/components/server/NetworkConnectionSection'; -import {ServerHero} from '@portal/components/server/ServerHero'; -import {SystemInfoSection} from '@portal/components/server/SystemInfoSection'; -import {TemperatureMonitorSection} from '@portal/components/server/TemperatureMonitorSection'; -import {CpuChart} from '@portal/components/server/CpuChart'; -import {DiskIOChart} from '@portal/components/server/DiskIOChart'; -import {GpuChart} from '@portal/components/server/GpuChart'; -import {MemoryChart} from '@portal/components/server/MemoryChart'; -import {MonitorChart} from '@portal/components/server/MonitorChart'; -import {NetworkChart} from '@portal/components/server/NetworkChart'; -import {NetworkConnectionChart} from '@portal/components/server/NetworkConnectionChart'; -import {TemperatureChart} from '@portal/components/server/TemperatureChart'; -import {useAgentQuery, useLatestMetricsQuery} from '@portal/hooks/server'; -import {SERVER_TIME_RANGE_OPTIONS} from '@portal/constants/time'; +import {Card} from '@portal/components/Card.tsx'; +import {EmptyState} from '@portal/components/EmptyState.tsx'; +import {LoadingSpinner} from '@portal/components/LoadingSpinner.tsx'; +import {TimeRangeSelector} from '@portal/components/TimeRangeSelector.tsx'; +import {GpuMonitorSection} from '@portal/components/server/GpuMonitorSection.tsx'; +import {NetworkConnectionSection} from '@portal/components/server/NetworkConnectionSection.tsx'; +import {ServerHero} from '@portal/components/server/ServerHero.tsx'; +import {SystemInfoSection} from '@portal/components/server/SystemInfoSection.tsx'; +import {TemperatureMonitorSection} from '@portal/components/server/TemperatureMonitorSection.tsx'; +import {CpuChart} from '@portal/components/server/CpuChart.tsx'; +import {DiskIOChart} from '@portal/components/server/DiskIOChart.tsx'; +import {GpuChart} from '@portal/components/server/GpuChart.tsx'; +import {MemoryChart} from '@portal/components/server/MemoryChart.tsx'; +import {MonitorChart} from '@portal/components/server/MonitorChart.tsx'; +import {NetworkChart} from '@portal/components/server/NetworkChart.tsx'; +import {NetworkConnectionChart} from '@portal/components/server/NetworkConnectionChart.tsx'; +import {TemperatureChart} from '@portal/components/server/TemperatureChart.tsx'; +import {useAgentQuery, useLatestMetricsQuery} from '@portal/hooks/server.ts'; +import {SERVER_TIME_RANGE_OPTIONS} from '@portal/constants/time.ts'; /** * 服务器详情页面 diff --git a/web/src/portal/pages/Public/ServerList.tsx b/web/src/portal/pages/ServerList.tsx similarity index 90% rename from web/src/portal/pages/Public/ServerList.tsx rename to web/src/portal/pages/ServerList.tsx index 5cd8d7d..b4fe619 100644 --- a/web/src/portal/pages/Public/ServerList.tsx +++ b/web/src/portal/pages/ServerList.tsx @@ -8,7 +8,8 @@ import { ArrowUp, Clock, Cpu, - Filter, Globe, + Filter, + Globe, HardDrive, LinkIcon, Loader2, @@ -19,12 +20,13 @@ import { } from 'lucide-react'; import {getPublicTags, listAgents} from '@/api/agent.ts'; import type {Agent, LatestMetrics} from '@/types'; -import {cn} from '@/lib/utils'; +import {cn} from '@/lib/utils.ts'; import CompactResourceBar from "@portal/components/CompactResourceBar.tsx"; import StatBlock from "@portal/components/StatBlock.tsx"; import ServerCard from "@portal/components/ServerCard.tsx"; import NetworkStatCard from "@portal/components/NetworkStatCard.tsx"; import {formatBytes, formatSpeed, formatUptime} from "@portal/utils/util.ts"; +import {isExpired} from "@portal/utils/server.ts"; interface AgentWithMetrics extends Agent { metrics?: LatestMetrics; @@ -289,7 +291,10 @@ const ServerList = () => {
+ className={cn( + 'font-bold text-slate-800 dark:text-cyan-100 font-mono text-sm transition-colors', + isExpired(server.expireTime) ? 'text-red-600 dark:text-red-400' : '' + )}> {server.name}
{
{isOnline && server.metrics?.host && (
-
+
{formatUptime(server.metrics.host.uptime)}
-
+
{server.metrics.host.procs} 进程
@@ -347,7 +354,8 @@ const ServerList = () => { {temp.temperature?.toFixed(1)}°C - {temp.type} + {temp.type} {index < temperatures.length - 1 && |} @@ -367,25 +375,28 @@ const ServerList = () => { {/* Network */}
- + {formatSpeed(download)} - + {formatSpeed(upload)}
- {server.trafficLimit > 0 && ( + {server.traffic?.limit > 0 && (
流量使用 - {Math.round((server.trafficUsed || 0) / server.trafficLimit * 100)}% + {Math.round((server.traffic?.used || 0) / server.traffic?.limit * 100)}%
-
+
+ style={{width: `${((server.traffic?.used || 0) / server.traffic?.limit) * 100}%`}}>
)} @@ -396,20 +407,25 @@ const ServerList = () => { {isOnline && netConn ? (
- + {netConn.established || 0} - ESTABLISHED + ESTABLISHED
- {netConn.listen || 0} + {netConn.listen || 0} LISTEN
- {netConn.closeWait || 0} - CLOSE_WAIT + {netConn.closeWait || 0} + CLOSE_WAIT
) : ( @@ -432,7 +448,7 @@ const ServerList = () => { className={cn( `text-xs font-mono flex items-center gap-1 text-gray-600 dark:text-cyan-500`, // 剩余时间小于 30 天时显示为红色 - server.expireTime && server.expireTime > 0 && server.expireTime - Date.now() < 30 * 24 * 60 * 60 * 1000 ? 'text-red-600 dark:text-red-400' : '' + isExpired(server.expireTime) ? 'text-red-600 dark:text-red-400' : '' )}> {server.expireTime > 0 && diff --git a/web/src/portal/utils/server.ts b/web/src/portal/utils/server.ts new file mode 100644 index 0000000..6944c91 --- /dev/null +++ b/web/src/portal/utils/server.ts @@ -0,0 +1,4 @@ + +export const isExpired = (expireTime?: number) => { + return expireTime && expireTime > 0 && expireTime - Date.now() < 30 * 24 * 60 * 60 * 1000 +} \ No newline at end of file diff --git a/web/src/router/index.tsx b/web/src/router/index.tsx index a347601..22bde45 100644 --- a/web/src/router/index.tsx +++ b/web/src/router/index.tsx @@ -12,10 +12,10 @@ const AgentDetailPage = lazy(() => import('@admin/pages/Agents/AgentDetail')); const AgentInstallPage = lazy(() => import('@admin/pages/Agents/AgentInstall')); const ApiKeyListPage = lazy(() => import('@admin/pages/ApiKeys/ApiKeyList')); const SettingsPage = lazy(() => import('@admin/pages/Settings')); -const ServerListPage = lazy(() => import('@portal/pages/Public/ServerList')); -const ServerDetailPage = lazy(() => import('@portal/pages/Public/ServerDetail')); -const PublicMonitorListPage = lazy(() => import('@portal/pages/Public/MonitorList')); -const PublicMonitorDetailPage = lazy(() => import('@portal/pages/Public/MonitorDetail')); +const ServerListPage = lazy(() => import('@portal/pages/ServerList.tsx')); +const ServerDetailPage = lazy(() => import('@portal/pages/ServerDetail.tsx')); +const PublicMonitorListPage = lazy(() => import('@portal/pages/MonitorList.tsx')); +const PublicMonitorDetailPage = lazy(() => import('@portal/pages/MonitorDetail.tsx')); const MonitorListPage = lazy(() => import('@admin/pages/Monitors/MonitorList')); const DDNSPage = lazy(() => import('@admin/pages/DDNS')); const AlertRecordListPage = lazy(() => import('@admin/pages/AlertRecords')); diff --git a/web/src/types/index.ts b/web/src/types/index.ts index f2c685f..1532e43 100644 --- a/web/src/types/index.ts +++ b/web/src/types/index.ts @@ -30,14 +30,12 @@ export interface Agent { createdAt?: string; updatedAt?: string; // 流量统计相关字段 - trafficLimit?: number; // 流量限额(字节), 0表示不限制 - trafficUsed?: number; // 当前周期已使用流量(字节) - trafficResetDay?: number; // 流量重置日期(1-31), 0表示不自动重置 - trafficPeriodStart?: number; // 当前周期开始时间(时间戳毫秒) - trafficBaselineRecv?: number; // 当前周期流量基线(BytesRecvTotal) - trafficAlertSent80?: boolean; // 是否已发送80%告警 - trafficAlertSent90?: boolean; // 是否已发送90%告警 - trafficAlertSent100?: boolean;// 是否已发送100%告警 + traffic?: TrafficData; // 流量 +} + +export interface TrafficData { + limit?: number; + used?: number; } // 聚合指标数据(所有图表查询只返回聚合数据)