orama/pkg/cli/monitor/display/service.go

132 lines
3.1 KiB
Go

package display
import (
"fmt"
"io"
"sort"
"strings"
"github.com/DeBrosOfficial/network/pkg/cli/monitor"
)
// ServiceTable prints a cross-node service status matrix to w.
func ServiceTable(snap *monitor.ClusterSnapshot, w io.Writer) error {
fmt.Fprintf(w, "%s\n", styleBold.Render(
fmt.Sprintf("Service Status Matrix \u2014 %s", snap.Environment)))
fmt.Fprintln(w, strings.Repeat("\u2550", 36))
fmt.Fprintln(w)
// Collect all service names and build per-host maps
type hostServices struct {
host string
shortIP string
services map[string]string // name -> active_state
}
var hosts []hostServices
serviceSet := map[string]bool{}
for _, cs := range snap.Nodes {
if cs.Error != nil || cs.Report == nil || cs.Report.Services == nil {
continue
}
hs := hostServices{
host: cs.Node.Host,
shortIP: shortIP(cs.Node.Host),
services: make(map[string]string),
}
for _, svc := range cs.Report.Services.Services {
hs.services[svc.Name] = svc.ActiveState
serviceSet[svc.Name] = true
}
hosts = append(hosts, hs)
}
// Sort service names
var svcNames []string
for name := range serviceSet {
svcNames = append(svcNames, name)
}
sort.Strings(svcNames)
if len(hosts) == 0 || len(svcNames) == 0 {
fmt.Fprintln(w, styleMuted.Render(" No service data available"))
return nil
}
// Header: SERVICE + each host short IP
hdr := fmt.Sprintf("%-22s", styleHeader.Render("SERVICE"))
for _, h := range hosts {
hdr += fmt.Sprintf("%-12s", styleHeader.Render(h.shortIP))
}
fmt.Fprintln(w, hdr)
fmt.Fprintln(w, separator(22+12*len(hosts)))
// Rows
for _, name := range svcNames {
row := fmt.Sprintf("%-22s", name)
for _, h := range hosts {
state, ok := h.services[name]
if !ok {
row += fmt.Sprintf("%-12s", styleMuted.Render("--"))
} else {
row += fmt.Sprintf("%-12s", colorServiceState(state))
}
}
fmt.Fprintln(w, row)
}
return nil
}
// ServiceJSON writes the service matrix as JSON.
func ServiceJSON(snap *monitor.ClusterSnapshot, w io.Writer) error {
type svcEntry struct {
Host string `json:"host"`
Services map[string]string `json:"services"`
}
var entries []svcEntry
for _, cs := range snap.Nodes {
if cs.Error != nil || cs.Report == nil || cs.Report.Services == nil {
continue
}
e := svcEntry{
Host: cs.Node.Host,
Services: make(map[string]string),
}
for _, svc := range cs.Report.Services.Services {
e.Services[svc.Name] = svc.ActiveState
}
entries = append(entries, e)
}
return writeJSON(w, entries)
}
// shortIP truncates an IP to the first 3 octets for compact display.
func shortIP(ip string) string {
parts := strings.Split(ip, ".")
if len(parts) == 4 {
return parts[0] + "." + parts[1] + "." + parts[2]
}
if len(ip) > 12 {
return ip[:12]
}
return ip
}
// colorServiceState renders a service state with appropriate color.
func colorServiceState(state string) string {
switch state {
case "active":
return styleGreen.Render("ACTIVE")
case "failed":
return styleRed.Render("FAILED")
case "inactive":
return styleMuted.Render("inactive")
default:
return styleYellow.Render(state)
}
}