mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-03-17 20:26:57 +00:00
205 lines
4.8 KiB
Go
205 lines
4.8 KiB
Go
package display
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
|
|
"github.com/DeBrosOfficial/network/pkg/cli/monitor"
|
|
)
|
|
|
|
// ClusterTable prints a cluster overview table to w.
|
|
func ClusterTable(snap *monitor.ClusterSnapshot, w io.Writer) error {
|
|
dur := snap.Duration.Seconds()
|
|
fmt.Fprintf(w, "%s\n", styleBold.Render(
|
|
fmt.Sprintf("Cluster Overview \u2014 %s (%d nodes, collected in %.1fs)",
|
|
snap.Environment, snap.TotalCount(), dur)))
|
|
fmt.Fprintln(w, strings.Repeat("\u2550", 60))
|
|
fmt.Fprintln(w)
|
|
|
|
// Header
|
|
fmt.Fprintf(w, "%-18s %-12s %-6s %-6s %-11s %-5s %s\n",
|
|
styleHeader.Render("NODE"),
|
|
styleHeader.Render("ROLE"),
|
|
styleHeader.Render("MEM"),
|
|
styleHeader.Render("DISK"),
|
|
styleHeader.Render("RQLITE"),
|
|
styleHeader.Render("WG"),
|
|
styleHeader.Render("SERVICES"))
|
|
fmt.Fprintln(w, separator(70))
|
|
|
|
// Healthy nodes
|
|
for _, cs := range snap.Nodes {
|
|
if cs.Error != nil {
|
|
continue
|
|
}
|
|
r := cs.Report
|
|
if r == nil {
|
|
continue
|
|
}
|
|
|
|
host := cs.Node.Host
|
|
role := cs.Node.Role
|
|
|
|
// Memory %
|
|
memStr := "--"
|
|
if r.System != nil {
|
|
memStr = fmt.Sprintf("%d%%", r.System.MemUsePct)
|
|
}
|
|
|
|
// Disk %
|
|
diskStr := "--"
|
|
if r.System != nil {
|
|
diskStr = fmt.Sprintf("%d%%", r.System.DiskUsePct)
|
|
}
|
|
|
|
// RQLite state
|
|
rqliteStr := "--"
|
|
if r.RQLite != nil && r.RQLite.Responsive {
|
|
rqliteStr = r.RQLite.RaftState
|
|
} else if r.RQLite != nil {
|
|
rqliteStr = styleRed.Render("DOWN")
|
|
}
|
|
|
|
// WireGuard
|
|
wgStr := statusIcon(r.WireGuard != nil && r.WireGuard.InterfaceUp)
|
|
|
|
// Services: active/total
|
|
svcStr := "--"
|
|
if r.Services != nil {
|
|
active := 0
|
|
total := len(r.Services.Services)
|
|
for _, svc := range r.Services.Services {
|
|
if svc.ActiveState == "active" {
|
|
active++
|
|
}
|
|
}
|
|
svcStr = fmt.Sprintf("%d/%d", active, total)
|
|
}
|
|
|
|
fmt.Fprintf(w, "%-18s %-12s %-6s %-6s %-11s %-5s %s\n",
|
|
host, role, memStr, diskStr, rqliteStr, wgStr, svcStr)
|
|
}
|
|
|
|
// Unreachable nodes
|
|
failed := snap.Failed()
|
|
if len(failed) > 0 {
|
|
fmt.Fprintln(w)
|
|
for _, cs := range failed {
|
|
fmt.Fprintf(w, "%-18s %-12s %s\n",
|
|
styleRed.Render(cs.Node.Host),
|
|
cs.Node.Role,
|
|
styleRed.Render("UNREACHABLE"))
|
|
}
|
|
}
|
|
|
|
// Alerts summary
|
|
critCount, warnCount := countAlerts(snap.Alerts)
|
|
fmt.Fprintln(w)
|
|
fmt.Fprintf(w, "Alerts: %s critical, %s warning\n",
|
|
alertCountStr(critCount, monitor.AlertCritical),
|
|
alertCountStr(warnCount, monitor.AlertWarning))
|
|
|
|
for _, a := range snap.Alerts {
|
|
if a.Severity == monitor.AlertCritical || a.Severity == monitor.AlertWarning {
|
|
tag := severityTag(a.Severity)
|
|
fmt.Fprintf(w, " %s %s: %s\n", tag, a.Node, a.Message)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ClusterJSON writes the cluster snapshot as JSON.
|
|
func ClusterJSON(snap *monitor.ClusterSnapshot, w io.Writer) error {
|
|
type clusterEntry struct {
|
|
Host string `json:"host"`
|
|
Role string `json:"role"`
|
|
MemPct int `json:"mem_pct"`
|
|
DiskPct int `json:"disk_pct"`
|
|
RQLite string `json:"rqlite_state"`
|
|
WGUp bool `json:"wg_up"`
|
|
Services string `json:"services"`
|
|
Status string `json:"status"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
var entries []clusterEntry
|
|
for _, cs := range snap.Nodes {
|
|
e := clusterEntry{
|
|
Host: cs.Node.Host,
|
|
Role: cs.Node.Role,
|
|
}
|
|
if cs.Error != nil {
|
|
e.Status = "unreachable"
|
|
e.Error = cs.Error.Error()
|
|
entries = append(entries, e)
|
|
continue
|
|
}
|
|
r := cs.Report
|
|
if r == nil {
|
|
e.Status = "unreachable"
|
|
entries = append(entries, e)
|
|
continue
|
|
}
|
|
e.Status = "ok"
|
|
if r.System != nil {
|
|
e.MemPct = r.System.MemUsePct
|
|
e.DiskPct = r.System.DiskUsePct
|
|
}
|
|
if r.RQLite != nil && r.RQLite.Responsive {
|
|
e.RQLite = r.RQLite.RaftState
|
|
}
|
|
e.WGUp = r.WireGuard != nil && r.WireGuard.InterfaceUp
|
|
if r.Services != nil {
|
|
active := 0
|
|
total := len(r.Services.Services)
|
|
for _, svc := range r.Services.Services {
|
|
if svc.ActiveState == "active" {
|
|
active++
|
|
}
|
|
}
|
|
e.Services = fmt.Sprintf("%d/%d", active, total)
|
|
}
|
|
entries = append(entries, e)
|
|
}
|
|
|
|
return writeJSON(w, entries)
|
|
}
|
|
|
|
// countAlerts returns the number of critical and warning alerts.
|
|
func countAlerts(alerts []monitor.Alert) (crit, warn int) {
|
|
for _, a := range alerts {
|
|
switch a.Severity {
|
|
case monitor.AlertCritical:
|
|
crit++
|
|
case monitor.AlertWarning:
|
|
warn++
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// severityTag returns a colored tag like [CRIT], [WARN], [INFO].
|
|
func severityTag(s monitor.AlertSeverity) string {
|
|
switch s {
|
|
case monitor.AlertCritical:
|
|
return styleRed.Render("[CRIT]")
|
|
case monitor.AlertWarning:
|
|
return styleYellow.Render("[WARN]")
|
|
case monitor.AlertInfo:
|
|
return styleMuted.Render("[INFO]")
|
|
default:
|
|
return styleMuted.Render("[????]")
|
|
}
|
|
}
|
|
|
|
// alertCountStr renders the count with appropriate color.
|
|
func alertCountStr(count int, sev monitor.AlertSeverity) string {
|
|
s := fmt.Sprintf("%d", count)
|
|
if count > 0 {
|
|
return severityColor(sev).Render(s)
|
|
}
|
|
return s
|
|
}
|