Add p2p peers to core network route
Some checks are pending
Security Scan / security (push) Waiting to run
Test / test (push) Waiting to run

This commit is contained in:
Snider 2026-04-15 19:16:45 +01:00
parent 5844d7ff0f
commit 65ccf50c2b
2 changed files with 143 additions and 2 deletions

View file

@ -8,6 +8,8 @@ import (
"sort"
"strings"
"time"
"forge.lthn.ai/core/gui/pkg/p2p"
)
type NetworkInterfaceState struct {
@ -21,9 +23,17 @@ type NetworkInterfaceState struct {
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"`
}
@ -64,9 +74,55 @@ func (s *Service) networkState() NetworkState {
})
}
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("<!doctype html><html><head><meta charset=\"utf-8\"><title>core://network</title><style>")
@ -110,7 +166,30 @@ func (s *Service) renderNetworkPage(state NetworkState) string {
}
}
builder.WriteString("</ul></section></main></body></html>")
if len(state.Peers) > 0 {
builder.WriteString("</ul></section><section><div class=\"meta\">Registered peers</div><ul>")
for _, peer := range state.Peers {
builder.WriteString("<li class=\"iface\"><div class=\"name\">")
builder.WriteString(html.EscapeString(peer.ID))
builder.WriteString("</div><div class=\"meta\">")
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("</div></li>")
}
builder.WriteString("</ul></section>")
} else {
builder.WriteString("</ul></section>")
}
builder.WriteString("</main></body></html>")
return builder.String()
}
@ -151,7 +230,29 @@ func (s *Service) renderNetworkInterfacePage(state NetworkState, iface NetworkIn
} else {
builder.WriteString(html.EscapeString(strings.Join(iface.Flags, ", ")))
}
builder.WriteString("</div></section></main></body></html>")
builder.WriteString("</div></section>")
if len(state.Peers) > 0 {
builder.WriteString("<section><div class=\"meta\">Registered peers</div><ul>")
for _, peer := range state.Peers {
builder.WriteString("<li class=\"iface\"><div class=\"name\">")
builder.WriteString(html.EscapeString(peer.ID))
builder.WriteString("</div><div class=\"meta\">")
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("</div></li>")
}
builder.WriteString("</ul></section>")
}
builder.WriteString("</main></body></html>")
return builder.String()
}

View file

@ -7,6 +7,7 @@ import (
core "dappco.re/go/core"
"forge.lthn.ai/core/gui/pkg/chat"
"forge.lthn.ai/core/gui/pkg/p2p"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/wailsapp/wails/v3/pkg/application"
@ -46,6 +47,14 @@ func (h *testApplicationHandler) ServeHTTP(_ application.ResponseWriter, _ *appl
h.called = true
}
type mockPeerRouter struct {
peers []p2p.Peer
}
func (m mockPeerRouter) Peers() []p2p.Peer {
return m.peers
}
func TestScheme_ResolveScheme_Good(t *testing.T) {
svc, c := newTestDisplayService(t)
svc.registerDefaultSchemes()
@ -163,6 +172,37 @@ func TestScheme_ResolveScheme_Ugly(t *testing.T) {
assert.Contains(t, searchPayload["body"].(string), "No matches found in Core storage.")
}
func TestScheme_ResolveScheme_NetworkPeers_Good(t *testing.T) {
c := core.New(
core.WithService(Register(nil)),
core.WithName("p2p", func(_ *core.Core) core.Result {
return core.Result{
Value: mockPeerRouter{
peers: []p2p.Peer{
{ID: "peer-2", Topic: "timeline", Connected: true},
{ID: "peer-1", Topic: "timeline", Connected: false},
},
},
OK: true,
}
}),
core.WithServiceLock(),
)
require.True(t, c.ServiceStartup(context.Background(), nil).OK)
svc := core.MustServiceFor[*Service](c, "display")
svc.registerDefaultSchemes()
result := svc.ResolveScheme(context.Background(), "core://network")
require.True(t, result.OK)
payload := result.Value.(map[string]any)
body := payload["body"].(string)
assert.Contains(t, body, "Registered peers")
assert.Contains(t, body, "peer-1")
assert.Contains(t, body, "peer-2")
assert.Contains(t, body, "timeline")
}
func TestScheme_AssetMiddleware_Good(t *testing.T) {
svc, _ := newTestDisplayService(t)
svc.registerDefaultSchemes()