diff --git a/pkg/agent/service/agent.go b/pkg/agent/service/agent.go index 317b7a9..6ca3500 100644 --- a/pkg/agent/service/agent.go +++ b/pkg/agent/service/agent.go @@ -190,28 +190,40 @@ func (a *Agent) runOnce(ctx context.Context, onConnected func()) error { a.setActiveConn(nil) }() - // 创建完成通道 + // 创建完成通道和错误通道 done := make(chan struct{}) - errChan := make(chan error, 3) + errChan := make(chan error, 1) // 只需要接收第一个错误 + // 创建 WaitGroup 用于等待所有 goroutine 退出 var wg conc.WaitGroup + // 启动读取循环(处理服务端的 Ping/Pong 等控制消息) wg.Go(func() { if err := a.readLoop(rawConn, done); err != nil { - errChan <- fmt.Errorf("读取失败: %w", err) + select { + case errChan <- fmt.Errorf("读取失败: %w", err): + default: + } } }) // 启动心跳和数据发送 wg.Go(func() { if err := a.heartbeatLoop(ctx, conn, done); err != nil { - errChan <- fmt.Errorf("心跳失败: %w", err) + select { + case errChan <- fmt.Errorf("心跳失败: %w", err): + default: + } } }) + // 启动指标采集循环 wg.Go(func() { if err := a.metricsLoop(ctx, conn, collectorManager, done); err != nil { - errChan <- fmt.Errorf("数据采集失败: %w", err) + select { + case errChan <- fmt.Errorf("数据采集失败: %w", err): + default: + } } }) @@ -225,23 +237,32 @@ func (a *Agent) runOnce(ctx context.Context, onConnected func()) error { a.tamperAlertLoop(ctx, conn, done) }) - // 等待错误或上下文取消 + // 等待第一个错误或上下文取消 + var returnErr error select { case err := <-errChan: - close(done) // 连接已建立,无论什么原因断开都标记为已建立状态 log.Printf("连接断开: %v", err) - return ErrConnectionEstablished + returnErr = ErrConnectionEstablished case <-ctx.Done(): - close(done) - // 优雅关闭连接 - closeMsg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "") - if err := conn.WriteMessage(websocket.CloseMessage, closeMsg); err != nil { - log.Printf("⚠️ 关闭连接失败: %v", err) - } - time.Sleep(time.Second) - return ctx.Err() // 返回上下文错误 + // 收到取消信号 + log.Println("收到停止信号,准备关闭连接") + returnErr = ctx.Err() } + + // 关闭 done channel,通知所有 goroutine 退出 + close(done) + + // 发送 WebSocket 关闭消息 + closeMsg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "") + if err := conn.WriteMessage(websocket.CloseMessage, closeMsg); err != nil { + log.Printf("⚠️ 发送关闭消息失败: %v", err) + } + + // 等待所有 goroutine 优雅退出 + wg.Wait() + + return returnErr } // readLoop 读取服务端消息(主要用于处理 Ping/Pong 和指令) diff --git a/web/src/components/CyberCard.tsx b/web/src/components/CyberCard.tsx new file mode 100644 index 0000000..c919d1c --- /dev/null +++ b/web/src/components/CyberCard.tsx @@ -0,0 +1,39 @@ +import React from 'react'; +import {cn} from "@/lib/utils.ts"; + +interface Props { + className?: string + children: React.ReactNode; + animation?: boolean + hover?: boolean +} + +const CyberCard = ({className, children, animation, hover}: Props) => { + return ( +
- {window.SystemConfig?.SystemNameZh} -
+ <> ++ {window.SystemConfig?.SystemNameZh} +
+各探针的当前状态和统计数据
+各探针的当前状态和统计数据
-| - 探针名称 - | -- 状态 - | -- 响应时间 - | -- 最后检测 - | - {monitorType === 'https' && ( -- 证书信息 - | - )} +
|---|
| + 探针名称 + | ++ 状态 + | ++ 响应时间 + | ++ 最后检测 + | + {monitorType === 'https' && (- 错误信息 + 证书信息 | -||||
|---|---|---|---|---|---|---|---|---|
|
-
+ )}
+
+ 错误信息
+ |
+ | |||||||
|
+
-
-
-
+
|
-
- |
-
-
-
+ |
+
+ |
+
+
+
- |
- - {formatDateTime(stat.checkedAt)} - | - {monitorType === 'https' && ( -
- {stat.certExpiryTime ? (
- |
- )}
+
+
+ + {formatDateTime(stat.checkedAt)} + | + {monitorType === 'https' && (
- {stat.status === 'down' && stat.message ? (
-
-
+ {stat.certExpiryTime ? (
+ |
-
- {monitor.showTargetPublic ? monitor.target : '******'} -
-+ {monitor.showTargetPublic ? monitor.target : '******'} +
{label}
-{subtitle}
-监控各探针的响应时间变化
-监控各探针的响应时间变化
来自最近一次探针上报的硬件与系统信息
关键时间与网络指标,帮助快速判断主机健康状况
- {displayLabel}
- {monitor.agentCount > 0 && (
-
+ {/* 指标信息 */}
+
+ {displayLabel}
+ {monitor.agentCount > 0 && (
+
{monitor.agentCount} 节点
- )}
- 200 ? 'text-amber-400' : 'text-cyan-100'
- )}>
- {displayValue} ms
- SSL 证书 上次检测
- {formatDateTime(monitor.lastCheckTime)}
- 200 ? 'text-amber-400' : 'text-cyan-100'
+ )}>
+ {displayValue} ms
+ SSL 证书 上次检测
+ {formatDateTime(monitor.lastCheckTime)}
+
| System Identity | -Resource Telemetry | -Network I/O | -Meta / Status | -
|---|
| System Identity | +Resource Telemetry | +Network I/O | +Meta / Status | +|
|---|---|---|---|---|
|
-
-
+ return (
+
+ |
+
+ {/* Resources */}
+
+
+
+
+
+ {server.name || server.hostname}
+
+
+ {server.os}
+
+ {server.arch}
+
+
+ {isOnline ? (
+ |
+
+ {/* Network */}
+
+
+ ) : (
- {server.name || server.hostname}
+ className="text-xs text-rose-500 font-mono flex items-center gap-2 py-4">
+
+ )}
+
+ |
+
+ {/* Meta */}
+
+
+
+ {server.trafficLimit > 0 ? (
+
+
+ ) : (
+
+ 流量使用
+ {Math.round((server.trafficUsed || 0) / server.trafficLimit * 100)}%
+
+
+
+
+ -- NO METERING --
+ )}
+
+ |
+
+
+
-
-
+ {server.tags && server.tags.length > 0 && server.tags.map(tag => (
+
+ #{tag}
+
+ ))}
- {server.os}
-
- {server.arch}
+ className={`text-[10px] font-mono flex items-center gap-1 ${server.expireTime && server.expireTime > 0 ? 'text-cyan-600' : 'text-emerald-500/60'}`}>
+
|