package display
import (
"fmt"
"html"
"net"
"os"
"sort"
"strings"
"time"
"forge.lthn.ai/core/gui/pkg/p2p"
)
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 NetworkPeerState struct {
ID string `json:"id"`
Topic string `json:"topic"`
Connected bool `json:"connected"`
SeenAt time.Time `json:"seen_at"`
}
type NetworkState struct {
Hostname string `json:"hostname"`
Interfaces []NetworkInterfaceState `json:"interfaces"`
Peers []NetworkPeerState `json:"peers,omitempty"`
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,
})
}
state.Peers = s.p2pPeers()
return state
}
type peerLister interface {
Peers() []p2p.Peer
}
func (s *Service) p2pPeers() []NetworkPeerState {
if s == nil || s.Core() == nil {
return nil
}
for _, serviceName := range []string{"p2p", "network"} {
serviceResult := s.Core().Service(serviceName)
if !serviceResult.OK || serviceResult.Value == nil {
continue
}
lister, ok := serviceResult.Value.(peerLister)
if !ok {
continue
}
peers := lister.Peers()
if len(peers) == 0 {
continue
}
peerStates := make([]NetworkPeerState, 0, len(peers))
for _, peer := range peers {
peerStates = append(peerStates, NetworkPeerState{
ID: peer.ID,
Topic: peer.Topic,
Connected: peer.Connected,
SeenAt: peer.SeenAt,
})
}
sort.Slice(peerStates, func(i, j int) bool {
if peerStates[i].SeenAt.Equal(peerStates[j].SeenAt) {
return strings.ToLower(peerStates[i].ID) < strings.ToLower(peerStates[j].ID)
}
return peerStates[i].SeenAt.After(peerStates[j].SeenAt)
})
return peerStates
}
return nil
}
func (s *Service) renderNetworkPage(state NetworkState) string {
var builder strings.Builder
builder.WriteString("
core://network")
if len(state.Interfaces) == 0 {
builder.WriteString("- No network interfaces were detected.
")
} else {
for _, iface := range state.Interfaces {
builder.WriteString("")
builder.WriteString(html.EscapeString(iface.Name))
builder.WriteString("
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("
")
if len(iface.Addresses) > 0 {
builder.WriteString("")
builder.WriteString(html.EscapeString(strings.Join(iface.Addresses, "\n")))
builder.WriteString("")
}
builder.WriteString(" ")
}
}
if len(state.Peers) > 0 {
builder.WriteString("
Registered peers
")
for _, peer := range state.Peers {
builder.WriteString("")
builder.WriteString(html.EscapeString(peer.ID))
builder.WriteString("
")
builder.WriteString(html.EscapeString(peer.Topic))
builder.WriteString(" · ")
if peer.Connected {
builder.WriteString("connected")
} else {
builder.WriteString("disconnected")
}
if !peer.SeenAt.IsZero() {
builder.WriteString(" · ")
builder.WriteString(html.EscapeString(peer.SeenAt.Format(time.RFC3339)))
}
builder.WriteString("
")
}
builder.WriteString("
")
} else {
builder.WriteString("")
}
builder.WriteString("")
return builder.String()
}
func (s *Service) renderNetworkInterfacePage(state NetworkState, iface NetworkInterfaceState) string {
var builder strings.Builder
builder.WriteString("core://network/")
builder.WriteString(html.EscapeString(iface.Name))
builder.WriteString("")
builder.WriteString(html.EscapeString(iface.Name))
builder.WriteString("
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("
")
builder.WriteString(html.EscapeString(strings.Join(iface.Addresses, "\n")))
builder.WriteString("Flags: ")
if len(iface.Flags) == 0 {
builder.WriteString("none")
} else {
builder.WriteString(html.EscapeString(strings.Join(iface.Flags, ", ")))
}
builder.WriteString("
")
if len(state.Peers) > 0 {
builder.WriteString("Registered peers
")
for _, peer := range state.Peers {
builder.WriteString("")
builder.WriteString(html.EscapeString(peer.ID))
builder.WriteString("
")
builder.WriteString(html.EscapeString(peer.Topic))
builder.WriteString(" · ")
if peer.Connected {
builder.WriteString("connected")
} else {
builder.WriteString("disconnected")
}
if !peer.SeenAt.IsZero() {
builder.WriteString(" · ")
builder.WriteString(html.EscapeString(peer.SeenAt.Format(time.RFC3339)))
}
builder.WriteString("
")
}
builder.WriteString("
")
}
builder.WriteString("")
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
}