195 lines
4.5 KiB
Go

package display
import (
"fmt"
"io"
"strings"
"github.com/DeBrosOfficial/network/pkg/cli/monitor"
)
// MeshTable prints WireGuard mesh status to w.
func MeshTable(snap *monitor.ClusterSnapshot, w io.Writer) error {
fmt.Fprintf(w, "%s\n", styleBold.Render(
fmt.Sprintf("WireGuard Mesh \u2014 %s", snap.Environment)))
fmt.Fprintln(w, strings.Repeat("\u2550", 28))
fmt.Fprintln(w)
// Header
fmt.Fprintf(w, "%-18s %-12s %-7s %-7s %s\n",
styleHeader.Render("NODE"),
styleHeader.Render("WG IP"),
styleHeader.Render("PORT"),
styleHeader.Render("PEERS"),
styleHeader.Render("STATUS"))
fmt.Fprintln(w, separator(54))
// Collect mesh info for peer details
type meshNode struct {
host string
wgIP string
port int
peers int
total int
healthy bool
}
var meshNodes []meshNode
expectedPeers := snap.HealthyCount() - 1
for _, cs := range snap.Nodes {
if cs.Error != nil || cs.Report == nil {
continue
}
r := cs.Report
if r.WireGuard == nil {
fmt.Fprintf(w, "%-18s %s\n", cs.Node.Host, styleMuted.Render("no WireGuard"))
continue
}
wg := r.WireGuard
peerCount := wg.PeerCount
allOK := wg.InterfaceUp
if allOK {
for _, p := range wg.Peers {
if p.LatestHandshake == 0 || p.HandshakeAgeSec > 180 {
allOK = false
break
}
}
}
mn := meshNode{
host: cs.Node.Host,
wgIP: wg.WgIP,
port: wg.ListenPort,
peers: peerCount,
total: expectedPeers,
healthy: allOK,
}
meshNodes = append(meshNodes, mn)
peerStr := fmt.Sprintf("%d/%d", peerCount, expectedPeers)
statusStr := statusIcon(allOK)
if !wg.InterfaceUp {
statusStr = styleRed.Render("DOWN")
}
fmt.Fprintf(w, "%-18s %-12s %-7d %-7s %s\n",
cs.Node.Host, wg.WgIP, wg.ListenPort, peerStr, statusStr)
}
// Peer details
fmt.Fprintln(w)
fmt.Fprintln(w, styleBold.Render("Peer Details:"))
for _, cs := range snap.Nodes {
if cs.Error != nil || cs.Report == nil || cs.Report.WireGuard == nil {
continue
}
wg := cs.Report.WireGuard
if !wg.InterfaceUp {
continue
}
localIP := wg.WgIP
for _, p := range wg.Peers {
hsAge := formatDuration(p.HandshakeAgeSec)
rx := formatBytes(p.TransferRx)
tx := formatBytes(p.TransferTx)
peerIP := p.AllowedIPs
// Strip CIDR if present
if idx := strings.Index(peerIP, "/"); idx > 0 {
peerIP = peerIP[:idx]
}
hsColor := styleGreen
if p.LatestHandshake == 0 {
hsAge = "never"
hsColor = styleRed
} else if p.HandshakeAgeSec > 180 {
hsColor = styleYellow
}
fmt.Fprintf(w, " %s \u2194 %s: handshake %s, rx: %s, tx: %s\n",
localIP, peerIP, hsColor.Render(hsAge), rx, tx)
}
}
return nil
}
// MeshJSON writes the WireGuard mesh as JSON.
func MeshJSON(snap *monitor.ClusterSnapshot, w io.Writer) error {
type peerEntry struct {
AllowedIPs string `json:"allowed_ips"`
HandshakeAgeSec int64 `json:"handshake_age_sec"`
TransferRxBytes int64 `json:"transfer_rx_bytes"`
TransferTxBytes int64 `json:"transfer_tx_bytes"`
}
type meshEntry struct {
Host string `json:"host"`
WgIP string `json:"wg_ip"`
ListenPort int `json:"listen_port"`
PeerCount int `json:"peer_count"`
Up bool `json:"up"`
Peers []peerEntry `json:"peers,omitempty"`
}
var entries []meshEntry
for _, cs := range snap.Nodes {
if cs.Error != nil || cs.Report == nil || cs.Report.WireGuard == nil {
continue
}
wg := cs.Report.WireGuard
e := meshEntry{
Host: cs.Node.Host,
WgIP: wg.WgIP,
ListenPort: wg.ListenPort,
PeerCount: wg.PeerCount,
Up: wg.InterfaceUp,
}
for _, p := range wg.Peers {
e.Peers = append(e.Peers, peerEntry{
AllowedIPs: p.AllowedIPs,
HandshakeAgeSec: p.HandshakeAgeSec,
TransferRxBytes: p.TransferRx,
TransferTxBytes: p.TransferTx,
})
}
entries = append(entries, e)
}
return writeJSON(w, entries)
}
// formatDuration formats seconds into a human-readable string.
func formatDuration(sec int64) string {
if sec < 60 {
return fmt.Sprintf("%ds ago", sec)
}
if sec < 3600 {
return fmt.Sprintf("%dm ago", sec/60)
}
return fmt.Sprintf("%dh ago", sec/3600)
}
// formatBytes formats bytes into a human-readable string.
func formatBytes(b int64) string {
const (
kb = 1024
mb = 1024 * kb
gb = 1024 * mb
)
switch {
case b >= gb:
return fmt.Sprintf("%.1fGB", float64(b)/float64(gb))
case b >= mb:
return fmt.Sprintf("%.1fMB", float64(b)/float64(mb))
case b >= kb:
return fmt.Sprintf("%.1fKB", float64(b)/float64(kb))
default:
return fmt.Sprintf("%dB", b)
}
}