feat: finish project basic structure and core functions
This commit is contained in:
@@ -0,0 +1,303 @@
|
||||
package node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"nukumizu-backend/postLog"
|
||||
)
|
||||
|
||||
// Report represents the latest server status report from Komari WebSocket.
|
||||
type Report struct {
|
||||
CPU struct {
|
||||
Usage float64 `json:"usage"`
|
||||
} `json:"cpu"`
|
||||
RAM struct {
|
||||
Total int64 `json:"total"`
|
||||
Used int64 `json:"used"`
|
||||
} `json:"ram"`
|
||||
Swap struct {
|
||||
Total int64 `json:"total"`
|
||||
Used int64 `json:"used"`
|
||||
} `json:"swap"`
|
||||
Load struct {
|
||||
Load1 float64 `json:"load1"`
|
||||
Load5 float64 `json:"load5"`
|
||||
Load15 float64 `json:"load15"`
|
||||
} `json:"load"`
|
||||
Disk struct {
|
||||
Total int64 `json:"total"`
|
||||
Used int64 `json:"used"`
|
||||
} `json:"disk"`
|
||||
Network struct {
|
||||
Up int64 `json:"up"`
|
||||
Down int64 `json:"down"`
|
||||
TotalUp int64 `json:"totalUp"`
|
||||
TotalDown int64 `json:"totalDown"`
|
||||
} `json:"network"`
|
||||
Connections struct {
|
||||
TCP int `json:"tcp"`
|
||||
UDP int `json:"udp"`
|
||||
} `json:"connections"`
|
||||
Uptime int `json:"uptime"`
|
||||
Process int `json:"process"`
|
||||
Message string `json:"message"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
// Node holds all tracked information about a single server node.
|
||||
type Node struct {
|
||||
UUID string
|
||||
Name string
|
||||
Online bool
|
||||
LatestReport *Report
|
||||
LastUpdated time.Time
|
||||
}
|
||||
|
||||
// StatusChange represents a node status transition.
|
||||
type StatusChange struct {
|
||||
Event string // "Online" or "Offline"
|
||||
UUID string
|
||||
Name string
|
||||
Message string
|
||||
OldOnline bool
|
||||
NewOnline bool
|
||||
}
|
||||
|
||||
// StatusChangeCallback is called when a node's online status changes.
|
||||
type StatusChangeCallback func(change StatusChange)
|
||||
|
||||
// Tracker maintains the in-memory state of all monitored nodes.
|
||||
// All methods are thread-safe.
|
||||
type Tracker struct {
|
||||
mu sync.RWMutex
|
||||
nodes map[string]*Node // uuid → Node
|
||||
uuidToName map[string]string // uuid → name (from node list)
|
||||
onlineSet map[string]bool // which uuids are currently online
|
||||
callbacks []StatusChangeCallback
|
||||
}
|
||||
|
||||
var globalTracker *Tracker
|
||||
|
||||
// InitTracker initializes the global node tracker.
|
||||
func InitTracker() {
|
||||
globalTracker = &Tracker{
|
||||
nodes: make(map[string]*Node),
|
||||
uuidToName: make(map[string]string),
|
||||
onlineSet: make(map[string]bool),
|
||||
}
|
||||
postLog.Info("Node tracker initialized")
|
||||
}
|
||||
|
||||
// GetTracker returns the global tracker instance.
|
||||
func GetTracker() *Tracker {
|
||||
return globalTracker
|
||||
}
|
||||
|
||||
// OnStatusChange registers a callback for status change events.
|
||||
func (t *Tracker) OnStatusChange(cb StatusChangeCallback) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.callbacks = append(t.callbacks, cb)
|
||||
}
|
||||
|
||||
// fireCallbacks notifies all registered callbacks of a status change.
|
||||
// Must be called with the lock NOT held (it acquires RLocks as needed).
|
||||
func (t *Tracker) fireCallbacks(change StatusChange) {
|
||||
t.mu.RLock()
|
||||
cbs := make([]StatusChangeCallback, len(t.callbacks))
|
||||
copy(cbs, t.callbacks)
|
||||
t.mu.RUnlock()
|
||||
|
||||
for _, cb := range cbs {
|
||||
cb(change)
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateNodeList replaces the full node list and updates the uuid→name map.
|
||||
// nodeNames maps UUID → node name from the Komari node list.
|
||||
func (t *Tracker) UpdateNodeList(nodeNames map[string]string) {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
|
||||
t.uuidToName = make(map[string]string, len(nodeNames))
|
||||
for uuid, name := range nodeNames {
|
||||
t.uuidToName[uuid] = name
|
||||
// Ensure node entry exists.
|
||||
if _, exists := t.nodes[uuid]; !exists {
|
||||
t.nodes[uuid] = &Node{
|
||||
UUID: uuid,
|
||||
Name: name,
|
||||
Online: false,
|
||||
}
|
||||
} else {
|
||||
// Update name in case it changed.
|
||||
t.nodes[uuid].Name = name
|
||||
}
|
||||
}
|
||||
|
||||
postLog.Debug(fmt.Sprintf("Node list updated: %d nodes", len(nodeNames)))
|
||||
}
|
||||
|
||||
// UpdateStatus processes a WebSocket status update from Komari.
|
||||
// It returns a list of status changes (Online/Offline) that were detected.
|
||||
func (t *Tracker) UpdateStatus(onlineUUIDs []string, reports map[string]Report) []StatusChange {
|
||||
t.mu.Lock()
|
||||
|
||||
newOnlineSet := make(map[string]bool, len(onlineUUIDs))
|
||||
for _, uuid := range onlineUUIDs {
|
||||
newOnlineSet[uuid] = true
|
||||
}
|
||||
|
||||
var changes []StatusChange
|
||||
|
||||
// Detect newly online nodes.
|
||||
for uuid := range newOnlineSet {
|
||||
if !t.onlineSet[uuid] {
|
||||
name := t.uuidToName[uuid]
|
||||
changes = append(changes, StatusChange{
|
||||
Event: "Online",
|
||||
UUID: uuid,
|
||||
Name: name,
|
||||
Message: "Server came online",
|
||||
OldOnline: false,
|
||||
NewOnline: true,
|
||||
})
|
||||
}
|
||||
// Update or create node entry.
|
||||
node, exists := t.nodes[uuid]
|
||||
if !exists {
|
||||
node = &Node{UUID: uuid, Name: t.uuidToName[uuid]}
|
||||
t.nodes[uuid] = node
|
||||
}
|
||||
node.Online = true
|
||||
node.LastUpdated = time.Now()
|
||||
}
|
||||
|
||||
// Detect newly offline nodes.
|
||||
for uuid := range t.onlineSet {
|
||||
if !newOnlineSet[uuid] {
|
||||
name := t.uuidToName[uuid]
|
||||
message := "Server went offline"
|
||||
if node, exists := t.nodes[uuid]; exists {
|
||||
node.Online = false
|
||||
node.LastUpdated = time.Now()
|
||||
if node.LatestReport != nil && node.LatestReport.Message != "" {
|
||||
message = node.LatestReport.Message
|
||||
}
|
||||
}
|
||||
changes = append(changes, StatusChange{
|
||||
Event: "Offline",
|
||||
UUID: uuid,
|
||||
Name: name,
|
||||
Message: message,
|
||||
OldOnline: true,
|
||||
NewOnline: false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Update report data for online nodes.
|
||||
for uuid, report := range reports {
|
||||
reportCopy := report
|
||||
node, exists := t.nodes[uuid]
|
||||
if !exists {
|
||||
node = &Node{UUID: uuid, Name: t.uuidToName[uuid]}
|
||||
t.nodes[uuid] = node
|
||||
}
|
||||
node.LatestReport = &reportCopy
|
||||
node.LastUpdated = time.Now()
|
||||
}
|
||||
|
||||
t.onlineSet = newOnlineSet
|
||||
t.mu.Unlock()
|
||||
|
||||
// Fire callbacks for each change (outside the lock).
|
||||
for _, change := range changes {
|
||||
postLog.Info(fmt.Sprintf("Node status change: %s [%s] - %s", change.Name, change.UUID, change.Event))
|
||||
t.fireCallbacks(change)
|
||||
}
|
||||
|
||||
return changes
|
||||
}
|
||||
|
||||
// GetNode returns a copy of the node data for the given UUID.
|
||||
func (t *Tracker) GetNode(uuid string) (*Node, bool) {
|
||||
t.mu.RLock()
|
||||
defer t.mu.RUnlock()
|
||||
node, exists := t.nodes[uuid]
|
||||
if !exists {
|
||||
return nil, false
|
||||
}
|
||||
// Return a copy to avoid data races.
|
||||
nodeCopy := *node
|
||||
if node.LatestReport != nil {
|
||||
reportCopy := *node.LatestReport
|
||||
nodeCopy.LatestReport = &reportCopy
|
||||
}
|
||||
return &nodeCopy, true
|
||||
}
|
||||
|
||||
// GetAllNodes returns a copy of all tracked nodes.
|
||||
func (t *Tracker) GetAllNodes() []*Node {
|
||||
t.mu.RLock()
|
||||
defer t.mu.RUnlock()
|
||||
result := make([]*Node, 0, len(t.nodes))
|
||||
for _, node := range t.nodes {
|
||||
nodeCopy := *node
|
||||
if node.LatestReport != nil {
|
||||
reportCopy := *node.LatestReport
|
||||
nodeCopy.LatestReport = &reportCopy
|
||||
}
|
||||
result = append(result, &nodeCopy)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetNodeName returns the name for a given UUID.
|
||||
func (t *Tracker) GetNodeName(uuid string) string {
|
||||
t.mu.RLock()
|
||||
defer t.mu.RUnlock()
|
||||
return t.uuidToName[uuid]
|
||||
}
|
||||
|
||||
// GetOnlineServers returns a list of formatted strings for online servers.
|
||||
// Format: "- ServerName (uuid)" or "- uuid" if name is empty.
|
||||
func (t *Tracker) GetOnlineServers() []string {
|
||||
t.mu.RLock()
|
||||
defer t.mu.RUnlock()
|
||||
var result []string
|
||||
for uuid := range t.onlineSet {
|
||||
name := t.uuidToName[uuid]
|
||||
if name != "" {
|
||||
result = append(result, fmt.Sprintf("- %s (%s)", name, uuid))
|
||||
} else {
|
||||
result = append(result, fmt.Sprintf("- %s", uuid))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// GetOfflineServers returns a list of formatted strings for offline servers.
|
||||
func (t *Tracker) GetOfflineServers() []string {
|
||||
t.mu.RLock()
|
||||
defer t.mu.RUnlock()
|
||||
offlineSet := make(map[string]bool)
|
||||
for uuid, node := range t.nodes {
|
||||
if !node.Online {
|
||||
offlineSet[uuid] = true
|
||||
}
|
||||
}
|
||||
var result []string
|
||||
for uuid := range offlineSet {
|
||||
name := t.uuidToName[uuid]
|
||||
if name != "" {
|
||||
result = append(result, fmt.Sprintf("- %s (%s)", name, uuid))
|
||||
} else {
|
||||
result = append(result, fmt.Sprintf("- %s", uuid))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user