Add p2p peers to core network route
This commit is contained in:
parent
5844d7ff0f
commit
65ccf50c2b
2 changed files with 143 additions and 2 deletions
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue