gui/pkg/display/network.go
Snider 750f7d9f43
Some checks are pending
Security Scan / security (push) Waiting to run
Test / test (push) Waiting to run
Add core network route state
2026-04-15 18:56:12 +01:00

187 lines
6.7 KiB
Go

package display
import (
"fmt"
"html"
"net"
"os"
"sort"
"strings"
"time"
)
type NetworkInterfaceState struct {
Name string `json:"name"`
Index int `json:"index"`
MTU int `json:"mtu"`
HardwareAddr string `json:"hardware_addr,omitempty"`
Flags []string `json:"flags,omitempty"`
Addresses []string `json:"addresses,omitempty"`
Up bool `json:"up"`
Loopback bool `json:"loopback"`
}
type NetworkState struct {
Hostname string `json:"hostname"`
Interfaces []NetworkInterfaceState `json:"interfaces"`
ObservedAt time.Time `json:"observed_at"`
}
func (s *Service) networkState() NetworkState {
state := NetworkState{
Hostname: hostname(),
Interfaces: make([]NetworkInterfaceState, 0),
ObservedAt: time.Now().UTC(),
}
interfaces, err := net.Interfaces()
if err != nil {
return state
}
sort.Slice(interfaces, func(i, j int) bool {
return strings.ToLower(interfaces[i].Name) < strings.ToLower(interfaces[j].Name)
})
for _, iface := range interfaces {
addresses := make([]string, 0)
if addrs, err := iface.Addrs(); err == nil {
for _, addr := range addrs {
addresses = append(addresses, addr.String())
}
sort.Strings(addresses)
}
state.Interfaces = append(state.Interfaces, NetworkInterfaceState{
Name: iface.Name,
Index: iface.Index,
MTU: iface.MTU,
HardwareAddr: iface.HardwareAddr.String(),
Flags: interfaceFlags(iface.Flags),
Addresses: addresses,
Up: iface.Flags&net.FlagUp != 0,
Loopback: iface.Flags&net.FlagLoopback != 0,
})
}
return state
}
func (s *Service) renderNetworkPage(state NetworkState) string {
var builder strings.Builder
builder.WriteString("<!doctype html><html><head><meta charset=\"utf-8\"><title>core://network</title><style>")
builder.WriteString("body{font:14px/1.5 ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif;background:#07111f;color:#e2e8f0;margin:0}")
builder.WriteString("header{padding:20px;border-bottom:1px solid #203047;background:linear-gradient(180deg,#0f172a,#07111f)}")
builder.WriteString("main{padding:20px;display:grid;gap:16px}section{background:#0b1220;border:1px solid #203047;border-radius:16px;padding:16px}")
builder.WriteString("ul{list-style:none;padding:0;margin:0;display:grid;gap:12px}.iface{padding:12px;border:1px solid #203047;border-radius:12px;background:#020617}")
builder.WriteString(".meta{color:#94a3b8}.name{font-weight:700;color:#7dd3fc}code{background:#111827;border-radius:8px;padding:2px 6px}")
builder.WriteString("</style></head><body><header><strong>core://network</strong><div class=\"meta\">")
builder.WriteString(html.EscapeString(state.Hostname))
builder.WriteString(" · ")
builder.WriteString(html.EscapeString(state.ObservedAt.Format(time.RFC3339)))
builder.WriteString("</div></header><main><section><ul>")
if len(state.Interfaces) == 0 {
builder.WriteString("<li class=\"meta\">No network interfaces were detected.</li>")
} else {
for _, iface := range state.Interfaces {
builder.WriteString("<li class=\"iface\"><div class=\"name\">")
builder.WriteString(html.EscapeString(iface.Name))
builder.WriteString("</div><div class=\"meta\">Index ")
builder.WriteString(fmt.Sprintf("%d", iface.Index))
builder.WriteString(" · MTU ")
builder.WriteString(fmt.Sprintf("%d", iface.MTU))
builder.WriteString(" · ")
if iface.Up {
builder.WriteString("up")
} else {
builder.WriteString("down")
}
if iface.Loopback {
builder.WriteString(" · loopback")
}
builder.WriteString("</div>")
if len(iface.Addresses) > 0 {
builder.WriteString("<pre>")
builder.WriteString(html.EscapeString(strings.Join(iface.Addresses, "\n")))
builder.WriteString("</pre>")
}
builder.WriteString("</li>")
}
}
builder.WriteString("</ul></section></main></body></html>")
return builder.String()
}
func (s *Service) renderNetworkInterfacePage(state NetworkState, iface NetworkInterfaceState) string {
var builder strings.Builder
builder.WriteString("<!doctype html><html><head><meta charset=\"utf-8\"><title>core://network/")
builder.WriteString(html.EscapeString(iface.Name))
builder.WriteString("</title><style>")
builder.WriteString("body{font:14px/1.5 ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif;background:#07111f;color:#e2e8f0;margin:0}")
builder.WriteString("header{padding:20px;border-bottom:1px solid #203047;background:linear-gradient(180deg,#0f172a,#07111f)}")
builder.WriteString("main{padding:20px;display:grid;gap:16px}section{background:#0b1220;border:1px solid #203047;border-radius:16px;padding:16px}")
builder.WriteString("pre{margin:0;white-space:pre-wrap;word-break:break-word}code{background:#111827;border-radius:8px;padding:2px 6px}")
builder.WriteString(".meta{color:#94a3b8}.name{font-weight:700;color:#7dd3fc}")
builder.WriteString("</style></head><body><header><strong>core://network/")
builder.WriteString(html.EscapeString(iface.Name))
builder.WriteString("</strong><div class=\"meta\">")
builder.WriteString(html.EscapeString(state.Hostname))
builder.WriteString("</div></header><main><section><div class=\"name\">")
builder.WriteString(html.EscapeString(iface.Name))
builder.WriteString("</div><div class=\"meta\">Index ")
builder.WriteString(fmt.Sprintf("%d", iface.Index))
builder.WriteString(" · MTU ")
builder.WriteString(fmt.Sprintf("%d", iface.MTU))
builder.WriteString(" · ")
if iface.Up {
builder.WriteString("up")
} else {
builder.WriteString("down")
}
if iface.Loopback {
builder.WriteString(" · loopback")
}
builder.WriteString("</div><pre>")
builder.WriteString(html.EscapeString(strings.Join(iface.Addresses, "\n")))
builder.WriteString("</pre><div class=\"meta\">Flags: ")
if len(iface.Flags) == 0 {
builder.WriteString("none")
} else {
builder.WriteString(html.EscapeString(strings.Join(iface.Flags, ", ")))
}
builder.WriteString("</div></section></main></body></html>")
return builder.String()
}
func interfaceFlags(flags net.Flags) []string {
values := make([]string, 0, 4)
if flags&net.FlagUp != 0 {
values = append(values, "up")
}
if flags&net.FlagBroadcast != 0 {
values = append(values, "broadcast")
}
if flags&net.FlagLoopback != 0 {
values = append(values, "loopback")
}
if flags&net.FlagPointToPoint != 0 {
values = append(values, "point-to-point")
}
if flags&net.FlagMulticast != 0 {
values = append(values, "multicast")
}
if flags&net.FlagRunning != 0 {
values = append(values, "running")
}
return values
}
func hostname() string {
name, err := os.Hostname()
if err != nil || strings.TrimSpace(name) == "" {
return "localhost"
}
return name
}