diff --git a/plugins/analysis/client/plugin.go b/plugins/analysis/client/plugin.go index 6fd3f07c4771964506bf9712aadd72542dc93f32..369702a4ba73de559b1dbd69eac2dab54deacd81 100644 --- a/plugins/analysis/client/plugin.go +++ b/plugins/analysis/client/plugin.go @@ -1,6 +1,7 @@ package client import ( + "encoding/hex" "net" "time" @@ -57,19 +58,19 @@ func Run(plugin *node.Plugin) { func getEventDispatchers(conn *network.ManagedConnection) *EventDispatchers { return &EventDispatchers{ AddNode: func(nodeId []byte) { - log.Debugw("AddNode", "nodeId", nodeId) + log.Debugw("AddNode", "nodeId", hex.EncodeToString(nodeId)) _, _ = conn.Write((&addnode.Packet{NodeId: nodeId}).Marshal()) }, RemoveNode: func(nodeId []byte) { - log.Debugw("RemoveNode", "nodeId", nodeId) + log.Debugw("RemoveNode", "nodeId", hex.EncodeToString(nodeId)) _, _ = conn.Write((&removenode.Packet{NodeId: nodeId}).Marshal()) }, ConnectNodes: func(sourceId []byte, targetId []byte) { - log.Debugw("ConnectNodes", "sourceId", sourceId, "targetId", targetId) + log.Debugw("ConnectNodes", "sourceId", hex.EncodeToString(sourceId), "targetId", hex.EncodeToString(targetId)) _, _ = conn.Write((&connectnodes.Packet{SourceId: sourceId, TargetId: targetId}).Marshal()) }, DisconnectNodes: func(sourceId []byte, targetId []byte) { - log.Debugw("DisconnectNodes", "sourceId", sourceId, "targetId", targetId) + log.Debugw("DisconnectNodes", "sourceId", hex.EncodeToString(sourceId), "targetId", hex.EncodeToString(targetId)) _, _ = conn.Write((&disconnectnodes.Packet{SourceId: sourceId, TargetId: targetId}).Marshal()) }, } @@ -80,7 +81,9 @@ func reportCurrentStatus(eventDispatchers *EventDispatchers) { eventDispatchers.AddNode(local.GetInstance().ID().Bytes()) } + reportKnownPeers(eventDispatchers) reportChosenNeighbors(eventDispatchers) + reportAcceptedNeighbors(eventDispatchers) } func setupHooks(plugin *node.Plugin, conn *network.ManagedConnection, eventDispatchers *EventDispatchers) { @@ -123,6 +126,7 @@ func setupHooks(plugin *node.Plugin, conn *network.ManagedConnection, eventDispa var onClose *events.Closure onClose = events.NewClosure(func() { discover.Events.PeerDiscovered.Detach(onDiscoverPeer) + discover.Events.PeerDeleted.Detach(onDeletePeer) selection.Events.IncomingPeering.Detach(onAddAcceptedNeighbor) selection.Events.OutgoingPeering.Detach(onAddChosenNeighbor) selection.Events.Dropped.Detach(onRemoveNeighbor) @@ -132,15 +136,32 @@ func setupHooks(plugin *node.Plugin, conn *network.ManagedConnection, eventDispa conn.Events.Close.Attach(onClose) } +func reportKnownPeers(dispatchers *EventDispatchers) { + if autopeering.Discovery != nil { + for _, peer := range autopeering.Discovery.GetVerifiedPeers() { + dispatchers.AddNode(peer.ID().Bytes()) + } + } +} + func reportChosenNeighbors(dispatchers *EventDispatchers) { if autopeering.Selection != nil { for _, chosenNeighbor := range autopeering.Selection.GetOutgoingNeighbors() { - dispatchers.AddNode(chosenNeighbor.ID().Bytes()) + //dispatchers.AddNode(chosenNeighbor.ID().Bytes()) dispatchers.ConnectNodes(local.GetInstance().ID().Bytes(), chosenNeighbor.ID().Bytes()) } } } +func reportAcceptedNeighbors(dispatchers *EventDispatchers) { + if autopeering.Selection != nil { + for _, acceptedNeighbor := range autopeering.Selection.GetIncomingNeighbors() { + //dispatchers.AddNode(acceptedNeighbor.ID().Bytes()) + dispatchers.ConnectNodes(local.GetInstance().ID().Bytes(), acceptedNeighbor.ID().Bytes()) + } + } +} + func keepConnectionAlive(conn *network.ManagedConnection, shutdownSignal <-chan struct{}) bool { go conn.Read(make([]byte, 1)) diff --git a/plugins/analysis/webinterface/httpserver/index.go b/plugins/analysis/webinterface/httpserver/index.go index 15fea5145b07fc8ef35403cf0a69e6507635a3d7..e4009b1ded8855b564884c8c2067beca9eecc71e 100644 --- a/plugins/analysis/webinterface/httpserver/index.go +++ b/plugins/analysis/webinterface/httpserver/index.go @@ -7,15 +7,84 @@ import ( func index(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, `<head> - <style> body { margin: 0; } </style> + <style> + body { + text-align: center; + margin: 0; + overflow: hidden; + } + + #nfo{ + position:absolute; + right: 0; + padding:10px; + } + + #knownPeers{ + position:relative; + margin:0; + font-size:14px; + font-weight: bold; + text-align:right; + color:#aaa; + } + + #avgNeighbors{ + position:relative; + margin:0; + font-size:13px; + text-align:right; + color:grey; + } + + #graphc { + position: absolute; + top: 0px; + right: 0px; + margin:0; + right: 0px; + } + + #nodeId{ + position:relative; + margin:0; + padding:5px 0; + font-size:13px; + font-weight: bold; + text-align:right; + color:#aaa; + } + + #nodestat{ + position:relative; + margin:0; + font-size:12px; + text-align:right; + color:grey; + } + + #in, #out{ + margin:0; + padding: 3px 0; + } + + </style> <script src="https://unpkg.com/3d-force-graph"></script> <!--<script src="../../dist/3d-force-graph.js"></script>--> </head> <body> - <div id="3d-graph"></div> - + <div id="graphc"></div> + <div id="nfo"> + <p id="knownPeers"></p> + <p id="avgNeighbors"></p> + <div id="nodeId"></div> + <div id="nodestat"> + <p id="in"></p> + <p id="out"></p> + </div> + <script> var socket = new WebSocket(((window.location.protocol === "https:") ? "wss://" : "ws://") + window.location.host + "/datastream"); @@ -26,7 +95,8 @@ func index(w http.ResponseWriter, r *http.Request) { }; socket.onmessage = function (e) { - console.log("Len: ", data.nodes.length); + document.getElementById("knownPeers").innerHTML = "Known peers: " + data.nodes.length; + document.getElementById("avgNeighbors").innerHTML = "Average neighbors: " + parseFloat(((data.links.length * 2) / data.nodes.length).toFixed(2)); switch (e.data[0]) { case "_": // do nothing - its just a ping @@ -43,13 +113,13 @@ func index(w http.ResponseWriter, r *http.Request) { break; case "C": - connectNodes(e.data.substr(1, 64), e.data.substr(65, 128)); - console.log("Connect nodes:",e.data.substr(1, 64), " - ", e.data.substr(65, 128)); + connectNodes(e.data.substr(1, 64), e.data.substr(65, 64)); + console.log("Connect nodes:",e.data.substr(1, 64), " - ", e.data.substr(65, 64)); break; case "c": - disconnectNodes(e.data.substr(1, 64), e.data.substr(65, 128)); - console.log("Disconnect nodes:",e.data.substr(1, 64), " - ", e.data.substr(65, 128)); + disconnectNodes(e.data.substr(1, 64), e.data.substr(65, 64)); + console.log("Disconnect nodes:",e.data.substr(1, 64), " - ", e.data.substr(65, 64)); break; case "O": @@ -73,14 +143,45 @@ func index(w http.ResponseWriter, r *http.Request) { var existingLinks = {}; - const elem = document.getElementById("3d-graph"); + let highlightNodes = []; + let highlightLinks = []; + let highlightLink = null; + + const elem = document.getElementById("graphc"); + + //.onNodeHover(node => elem.style.cursor = node ? 'pointer' : null) const Graph = ForceGraph3D()(elem) + .graphData(data) .enableNodeDrag(false) - .onNodeHover(node => elem.style.cursor = node ? 'pointer' : null) - .onNodeClick(removeNodeX) - .nodeColor(node => node.online ? 'rgba(0,255,0,1)' : 'rgba(255,255,255,1)') - .graphData(data); + .onNodeClick(showNodeStat) + .nodeColor(node => highlightNodes.indexOf(node) === -1 ? 'rgba(0,255,255,0.6)' : 'rgb(255,0,0,1)') + .linkWidth(link => highlightLinks.indexOf(link) === -1 ? 1 : 4) + .linkDirectionalParticles(link => highlightLinks.indexOf(link) === -1 ? 0 : 4) + .linkDirectionalParticleWidth(4) + .onNodeHover(node => { + // no state change + if ((!node && !highlightNodes.length) || (highlightNodes.length === 1 && highlightNodes[0] === node)) return; + + highlightNodes = node ? [node] : []; + + highlightLinks = []; + clearNodeStat(); + if (node != null) { + highlightLinks = data.links.filter(l => (l.target.id == node.id) || (l.source.id == node.id)); + showNodeStat(node); + } + updateHighlight(); + }) + .onLinkHover(link => { + // no state change + if ((!link && !highlightLinks.length) || (highlightLinks.length === 1 && highlightLinks[0] === link)) return; + + highlightLinks = [link]; + highlightNodes = link ? [link.source, link.target] : []; + + updateHighlight(); + }); var updateRequired = true; @@ -92,6 +193,14 @@ func index(w http.ResponseWriter, r *http.Request) { } }, 500) + function updateHighlight() { + // trigger update of highlighted objects in scene + Graph + .nodeColor(Graph.nodeColor()) + .linkWidth(Graph.linkWidth()) + .linkDirectionalParticles(Graph.linkDirectionalParticles()); + } + updateGraph = function() { updateRequired = true; }; @@ -144,6 +253,7 @@ func index(w http.ResponseWriter, r *http.Request) { } nodesById[sourceNodeId].online = true; nodesById[targetNodeId].online = true; + existingLinks[sourceNodeId + targetNodeId] = true data.links = [...data.links, { source: sourceNodeId, target: targetNodeId }]; updateGraph(); @@ -158,10 +268,28 @@ func index(w http.ResponseWriter, r *http.Request) { updateGraph(); } - function removeNodeX(node) { - if (node.id in nodesById) { - removeNode(node.id); - } + function clearNodeStat() { + document.getElementById("nodeId").innerHTML = "" + document.getElementById("in").innerHTML = "" + document.getElementById("out").innerHTML = "" + } + + function showNodeStat(node) { + document.getElementById("nodeId").innerHTML = "ID: " + node.id.substr(0, 16); + + var incoming = data.links.filter(l => (l.target.id == node.id)); + document.getElementById("in").innerHTML = "IN: " + incoming.length + "<br>"; + incoming.forEach(function(link){ + document.getElementById("in").innerHTML += link.source.id.substr(0, 16) + " → NODE <br>"; + }); + + var outgoing = data.links.filter(l => (l.source.id == node.id)); + document.getElementById("out").innerHTML = "OUT: " + outgoing.length + "<br>"; + outgoing.forEach(function(link){ + document.getElementById("out").innerHTML += "NODE → " + link.target.id.substr(0, 16) + "<br>"; + }); + + nodesById[node.id].color = 'rgba(0,255,255,1)' } </script> </body>`) diff --git a/plugins/analysis/webinterface/recordedevents/recorded_events.go b/plugins/analysis/webinterface/recordedevents/recorded_events.go index ae6a5597c0eb6376bdb5f856d7d8077116a45398..398ca97082bbbd35db53d22f38ec76eeb1a1ad2b 100644 --- a/plugins/analysis/webinterface/recordedevents/recorded_events.go +++ b/plugins/analysis/webinterface/recordedevents/recorded_events.go @@ -16,6 +16,7 @@ var lock sync.Mutex func Configure(plugin *node.Plugin) { server.Events.AddNode.Attach(events.NewClosure(func(nodeId string) { + plugin.Node.Logger.Debugw("AddNode", "nodeID", nodeId) lock.Lock() defer lock.Unlock() @@ -25,6 +26,7 @@ func Configure(plugin *node.Plugin) { })) server.Events.RemoveNode.Attach(events.NewClosure(func(nodeId string) { + plugin.Node.Logger.Debugw("RemoveNode", "nodeID", nodeId) lock.Lock() defer lock.Unlock() @@ -32,6 +34,7 @@ func Configure(plugin *node.Plugin) { })) server.Events.NodeOnline.Attach(events.NewClosure(func(nodeId string) { + plugin.Node.Logger.Debugw("NodeOnline", "nodeID", nodeId) lock.Lock() defer lock.Unlock() @@ -39,6 +42,7 @@ func Configure(plugin *node.Plugin) { })) server.Events.NodeOffline.Attach(events.NewClosure(func(nodeId string) { + plugin.Node.Logger.Debugw("NodeOffline", "nodeID", nodeId) lock.Lock() defer lock.Unlock() @@ -46,6 +50,7 @@ func Configure(plugin *node.Plugin) { })) server.Events.ConnectNodes.Attach(events.NewClosure(func(sourceId string, targetId string) { + plugin.Node.Logger.Debugw("ConnectNodes", "sourceID", sourceId, "targetId", targetId) lock.Lock() defer lock.Unlock() @@ -59,6 +64,7 @@ func Configure(plugin *node.Plugin) { })) server.Events.DisconnectNodes.Attach(events.NewClosure(func(sourceId string, targetId string) { + plugin.Node.Logger.Debugw("DisconnectNodes", "sourceID", sourceId, "targetId", targetId) lock.Lock() defer lock.Unlock()