chore: sync local updates
This commit is contained in:
108
mengyamonitor-backend-server/internal/hub/hub.go
Normal file
108
mengyamonitor-backend-server/internal/hub/hub.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package hub
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
// WSMessage is sent to browsers (matches frontend ServerStatus shape).
|
||||
type WSMessage struct {
|
||||
Type string `json:"type"`
|
||||
Data map[string]json.RawMessage `json:"data"`
|
||||
}
|
||||
|
||||
type Hub struct {
|
||||
mu sync.RWMutex
|
||||
// serverId -> latest metrics JSON object (not wrapped)
|
||||
metrics map[string]json.RawMessage
|
||||
|
||||
subsMu sync.Mutex
|
||||
// subscribers for broadcast
|
||||
conns map[*websocket.Conn]struct{}
|
||||
}
|
||||
|
||||
func New() *Hub {
|
||||
return &Hub{
|
||||
metrics: make(map[string]json.RawMessage),
|
||||
conns: make(map[*websocket.Conn]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Hub) SetMetrics(serverID string, metricsJSON []byte) {
|
||||
h.mu.Lock()
|
||||
if len(metricsJSON) == 0 {
|
||||
delete(h.metrics, serverID)
|
||||
} else {
|
||||
h.metrics[serverID] = json.RawMessage(append([]byte(nil), metricsJSON...))
|
||||
}
|
||||
h.mu.Unlock()
|
||||
}
|
||||
|
||||
func (h *Hub) snapshotData() map[string]json.RawMessage {
|
||||
h.mu.RLock()
|
||||
defer h.mu.RUnlock()
|
||||
out := make(map[string]json.RawMessage, len(h.metrics))
|
||||
now := time.Now().UnixMilli()
|
||||
for id, raw := range h.metrics {
|
||||
// Wrap as ServerStatus for frontend compatibility
|
||||
wrap := map[string]any{
|
||||
"serverId": id,
|
||||
"online": true,
|
||||
"metrics": json.RawMessage(raw),
|
||||
"lastUpdate": now,
|
||||
}
|
||||
b, err := json.Marshal(wrap)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
out[id] = b
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (h *Hub) BuildSnapshotMessage() ([]byte, error) {
|
||||
msg := WSMessage{
|
||||
Type: "snapshot",
|
||||
Data: h.snapshotData(),
|
||||
}
|
||||
return json.Marshal(msg)
|
||||
}
|
||||
|
||||
// AddSubscriber registers for broadcast fan-out (call after sending the initial snapshot).
|
||||
func (h *Hub) AddSubscriber(c *websocket.Conn) {
|
||||
h.subsMu.Lock()
|
||||
h.conns[c] = struct{}{}
|
||||
h.subsMu.Unlock()
|
||||
}
|
||||
|
||||
func (h *Hub) Unregister(c *websocket.Conn) {
|
||||
h.subsMu.Lock()
|
||||
delete(h.conns, c)
|
||||
h.subsMu.Unlock()
|
||||
}
|
||||
|
||||
// BroadcastSnapshot pushes full snapshot to all subscribers.
|
||||
func (h *Hub) BroadcastSnapshot() {
|
||||
body, err := h.BuildSnapshotMessage()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
h.subsMu.Lock()
|
||||
defer h.subsMu.Unlock()
|
||||
for c := range h.conns {
|
||||
_ = c.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||
if err := c.WriteMessage(websocket.TextMessage, body); err != nil {
|
||||
_ = c.Close()
|
||||
delete(h.conns, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NotifyMetricsUpdate should be called after SetMetrics to push to browsers.
|
||||
func (h *Hub) NotifyMetricsUpdate(serverID string, metricsJSON []byte) {
|
||||
h.SetMetrics(serverID, metricsJSON)
|
||||
h.BroadcastSnapshot()
|
||||
}
|
||||
Reference in New Issue
Block a user