Initial commit
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.zkcoi.com/zkcoi/meshray/internal/ctr"
|
||||
"git.zkcoi.com/zkcoi/meshray/internal/model"
|
||||
"git.zkcoi.com/zkcoi/meshray/internal/store/sqlite"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
// ✅ 修复:限制 WebSocket 跨域,只允许特定来源
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
origin := r.Header.Get("Origin")
|
||||
allowedOrigins := map[string]bool{
|
||||
"http://localhost:9531": true,
|
||||
"http://127.0.0.1:9531": true,
|
||||
}
|
||||
return allowedOrigins[origin]
|
||||
},
|
||||
}
|
||||
|
||||
// WSMessage 与前端定义一致的结构
|
||||
type WSMessage struct {
|
||||
Channel string `json:"channel"`
|
||||
Type string `json:"type"`
|
||||
Payload interface{} `json:"payload"`
|
||||
}
|
||||
|
||||
// WSHandler 处理 WebSocket 连接
|
||||
type WSHandler struct {
|
||||
ctrClient *ctr.Ctr
|
||||
logger *zap.Logger
|
||||
store *sqlite.Store
|
||||
}
|
||||
|
||||
func NewWSHandler(ctrClient *ctr.Ctr, store *sqlite.Store, logger *zap.Logger) *WSHandler {
|
||||
return &WSHandler{
|
||||
ctrClient: ctrClient,
|
||||
logger: logger,
|
||||
store: store,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *WSHandler) ServeWS(c *gin.Context) {
|
||||
ws, err := upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
h.logger.Error("升级 WebSocket 失败", zap.Error(err))
|
||||
return
|
||||
}
|
||||
defer ws.Close()
|
||||
|
||||
h.logger.Info("客户端已连接 WebSocket")
|
||||
|
||||
// 这里暂时用简单轮询的方案,每秒向终端推送 core metrics 和 peering 状态
|
||||
// 在生产级应用中,可以使用 EventBus 订阅发布模式
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
// 启动一个 goroutine 读取客户端发来的控制指令 (ping/subscribe)
|
||||
go func() {
|
||||
for {
|
||||
_, msg, err := ws.ReadMessage()
|
||||
if err != nil {
|
||||
h.logger.Debug("WebSocket 客户端断开", zap.Error(err))
|
||||
break
|
||||
}
|
||||
|
||||
// 简单响应 ping
|
||||
var req map[string]interface{}
|
||||
if err := json.Unmarshal(msg, &req); err == nil {
|
||||
if req["type"] == "ping" {
|
||||
_ = ws.WriteJSON(map[string]interface{}{"type": "pong", "timestamp": time.Now().Unix()})
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
// 1. 获取所有组网
|
||||
var networks []model.Network
|
||||
if err := h.store.DB().Find(&networks).Error; err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// 聚合数据
|
||||
var totalBytesSent, totalBytesRecv uint64
|
||||
var totalFallbacks int64
|
||||
var totalActiveConnections int64
|
||||
|
||||
// 收集链路分布 (Layer -> Count)
|
||||
layerDistribution := make(map[string]int)
|
||||
|
||||
// wg 状态聚合(设备是否在运行)
|
||||
wgRunningCount := 0
|
||||
|
||||
// 2. 遍历所有组网聚合 Ctr Status
|
||||
for _, net := range networks {
|
||||
status, err := h.ctrClient.GetStatus(net.ID)
|
||||
if err != nil || status == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if status.WGStatus != nil && status.WGStatus.Running {
|
||||
wgRunningCount++
|
||||
}
|
||||
|
||||
if status.CoreStatus != nil {
|
||||
totalBytesSent += status.CoreStatus.BytesSent
|
||||
totalBytesRecv += status.CoreStatus.BytesReceived
|
||||
totalFallbacks += status.CoreStatus.StrategyFallbacks
|
||||
totalActiveConnections += status.CoreStatus.ActiveConnections
|
||||
|
||||
for _, peerStatus := range status.CoreStatus.Peers {
|
||||
if peerStatus.Connected && peerStatus.Layer != "" {
|
||||
layerDistribution[peerStatus.Layer]++
|
||||
} else if peerStatus.Connected {
|
||||
layerDistribution["Native"]++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 构建 payload 推送
|
||||
msg := WSMessage{
|
||||
Channel: "metrics",
|
||||
Type: "update",
|
||||
Payload: map[string]interface{}{
|
||||
"summary": map[string]interface{}{
|
||||
"networks_count": len(networks),
|
||||
"wg_running": wgRunningCount,
|
||||
},
|
||||
"core_status": map[string]interface{}{
|
||||
"bytes_sent": totalBytesSent,
|
||||
"bytes_received": totalBytesRecv,
|
||||
"strategy_fallbacks": totalFallbacks,
|
||||
"active_connections": totalActiveConnections,
|
||||
"layer_distribution": layerDistribution,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := ws.WriteJSON(msg); err != nil {
|
||||
return
|
||||
}
|
||||
case <-c.Request.Context().Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user