mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-03-17 18:16:57 +00:00
195 lines
4.5 KiB
Go
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)
|
|
}
|
|
}
|