From 3e434a57e1098aae7ed1a7d4a0d79295ab94fccf Mon Sep 17 00:00:00 2001 From: Rafael Matias Date: Tue, 27 Aug 2024 12:22:32 +0200 Subject: [PATCH 01/11] client pages: render identicons on client side --- cmd/dora-explorer/main.go | 2 -- handlers/clients_cl.go | 2 -- handlers/clients_el.go | 2 -- handlers/identicon.go | 46 ------------------------- static/css/clients.css | 4 +-- static/js/cytoscape-network-aux.js | 7 +++- static/js/vendor/jdenticon-3.3.0.min.js | 3 ++ templates/clients/clients_cl.html | 27 +++++++-------- templates/clients/clients_el.html | 27 +++++++-------- types/models/clients_cl.go | 1 - types/models/clients_el.go | 1 - 11 files changed, 37 insertions(+), 85 deletions(-) delete mode 100644 handlers/identicon.go create mode 100644 static/js/vendor/jdenticon-3.3.0.min.js diff --git a/cmd/dora-explorer/main.go b/cmd/dora-explorer/main.go index b593ae3..b9aff76 100644 --- a/cmd/dora-explorer/main.go +++ b/cmd/dora-explorer/main.go @@ -165,8 +165,6 @@ func startFrontend(webserver *http.Server) { router.HandleFunc("/validator/{idxOrPubKey}", handlers.Validator).Methods("GET") router.HandleFunc("/validator/{index}/slots", handlers.ValidatorSlots).Methods("GET") - router.HandleFunc("/identicon", handlers.Identicon).Methods("GET") - if utils.Config.Frontend.Pprof { // add pprof handler router.PathPrefix("/debug/pprof/").Handler(http.DefaultServeMux) diff --git a/handlers/clients_cl.go b/handlers/clients_cl.go index 32ca741..bdd1896 100644 --- a/handlers/clients_cl.go +++ b/handlers/clients_cl.go @@ -71,7 +71,6 @@ func buildCLPeerMapData() *models.ClientCLPageDataPeerMap { ID: peerID, Label: client.GetName(), Group: "internal", - Image: fmt.Sprintf("/identicon?key=%s", peerID), Shape: "circularImage", } nodes[peerID] = &node @@ -90,7 +89,6 @@ func buildCLPeerMapData() *models.ClientCLPageDataPeerMap { ID: peer.PeerID, Label: fmt.Sprintf("%s...%s", peer.PeerID[0:5], peer.PeerID[len(peer.PeerID)-5:]), Group: "external", - Image: fmt.Sprintf("/identicon?key=%s", peer.PeerID), Shape: "circularImage", } nodes[peer.PeerID] = &node diff --git a/handlers/clients_el.go b/handlers/clients_el.go index 1e2458e..0a5b297 100644 --- a/handlers/clients_el.go +++ b/handlers/clients_el.go @@ -85,7 +85,6 @@ func buildELPeerMapData() *models.ClientELPageDataPeerMap { ID: peerID, Label: client.GetName(), Group: "internal", - Image: fmt.Sprintf("/identicon?key=%s", peerID), Shape: "circularImage", } nodes[peerID] = &node @@ -121,7 +120,6 @@ func buildELPeerMapData() *models.ClientELPageDataPeerMap { ID: peerID, Label: fmt.Sprintf("%s...%s", peerID[0:5], peerID[len(peerID)-5:]), Group: "external", - Image: fmt.Sprintf("/identicon?key=%s", peerID), Shape: "circularImage", } nodes[peerID] = &node diff --git a/handlers/identicon.go b/handlers/identicon.go deleted file mode 100644 index ae87a34..0000000 --- a/handlers/identicon.go +++ /dev/null @@ -1,46 +0,0 @@ -package handlers - -import ( - "crypto/md5" - "math/big" - "net/http" - - "github.com/lucasb-eyer/go-colorful" - "github.com/stdatiks/jdenticon-go" -) - -func generateIdenticonSVG(s string) []byte { - c := jdenticon.DefaultConfig - c.Background = generateColorFromHash(s) - c.Width = 30 - c.Height = 30 - icon := jdenticon.NewWithConfig(s, c) - svg, _ := icon.SVG() - return svg -} - -func generateColorFromHash(s string) colorful.Color { - hash := md5.Sum([]byte(s)) - hashInt := new(big.Int).SetBytes(hash[:]) - hue := float64(hashInt.Int64() % 360) - saturation := 1.0 - brightness := 1.0 - - color := colorful.Hsv(hue, saturation, brightness) - return color -} - -func Identicon(w http.ResponseWriter, r *http.Request) { - keys, ok := r.URL.Query()["key"] - if !ok || len(keys[0]) < 1 { - http.Error(w, "Missing key parameter", http.StatusBadRequest) - return - } - key := keys[0] - - svg := generateIdenticonSVG(key[len(key)/2:]) - - w.Header().Set("Content-Type", "image/svg+xml") - w.WriteHeader(http.StatusOK) - w.Write(svg) -} diff --git a/static/css/clients.css b/static/css/clients.css index db31b18..438f122 100644 --- a/static/css/clients.css +++ b/static/css/clients.css @@ -10,7 +10,7 @@ Client peers table .client-node-icon { border-radius:50%; border:1px solid #0f0f0f; - background-color: #fafafa; + background-color: #111111; margin-right: 10px; height: 25px; width: 25px; @@ -20,7 +20,7 @@ Client peers table height: 25px; border-radius: 50%; border: 2px solid #0f0f0f; - background-color: #fafafa; + background-color: #111111; margin-right: 10px; } diff --git a/static/js/cytoscape-network-aux.js b/static/js/cytoscape-network-aux.js index 6406198..cc86b30 100644 --- a/static/js/cytoscape-network-aux.js +++ b/static/js/cytoscape-network-aux.js @@ -129,9 +129,14 @@ $_network.create = function (container, data){ classes: "bottom-center", } ); + svgIdenticon = jdenticon.toSvg(data.nodes[i].id, 80); // Add style to nodes stylesheet.selector('#' + data.nodes[i].id).css({ - 'background-image': '/identicon?key=' + data.nodes[i].id + 'shape': 'circle', + 'background-image': 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svgIdenticon), + 'background-fit': 'cover', + 'background-opacity': 1, + 'background-color': '#111111', }); } } diff --git a/static/js/vendor/jdenticon-3.3.0.min.js b/static/js/vendor/jdenticon-3.3.0.min.js new file mode 100644 index 0000000..99f4037 --- /dev/null +++ b/static/js/vendor/jdenticon-3.3.0.min.js @@ -0,0 +1,3 @@ +// Jdenticon 3.3.0 | jdenticon.com | MIT licensed | (c) 2014-2024 Daniel Mester Pirttijärvi +!function(t,n){var e=function(t){"use strict";function n(t,n,e){return parseInt(t.substr(n,e),16)}function e(t){return(t|=0)<0?"00":t<16?"0"+t.toString(16):t<256?t.toString(16):"ff"}function i(t,n,i){return e(255*((i=i<0?i+6:i>6?i-6:i)<1?t+(n-t)*i:i<3?n:i<4?t+(n-t)*(4-i):t))}function r(t){if(/^#[0-9a-f]{3,8}$/i.test(t)){var n,e=t.length;if(e<6){var i=t[1],r=t[2],o=t[3],u=t[4]||"";n="#"+i+i+r+r+o+o+u+u}return(7==e||e>8)&&(n=t),n}}function o(t){var e,i=n(t,7,2);isNaN(i)?e=t:e="rgba("+n(t,1,2)+","+n(t,3,2)+","+n(t,5,2)+","+(i/255).toFixed(2)+")";return e}function u(t,n,r){var o;if(0==n){var u=e(255*r);o=u+u+u}else{var f=r<=.5?r*(n+1):r+n-r*n,a=2*r-f;o=i(a,f,6*t+2)+i(a,f,6*t)+i(a,f,6*t-2)}return"#"+o}function f(t,n,e){var i=[.55,.5,.5,.46,.6,.55,.55][6*t+.5|0];return u(t,n,e=e<.5?e*i*2:i+(e-.5)*(1-i)*2)}var a=t,s={G:"jdenticon_config",n:"config"},h={};function c(t){h=t}function v(t){return arguments.length&&(h[s.n]=t),h[s.n]}function d(t,n){var e="object"==typeof t&&t||h[s.n]||a[s.G]||{},i=e.lightness||{},o=e.saturation||{},u="color"in o?o.color:o,f=o.grayscale,c=e.backColor,v=e.padding;function d(t,n){var e=i[t];return e&&e.length>1||(e=n),function(t){return(t=e[0]+t*(e[1]-e[0]))<0?0:t>1?1:t}}function l(t){var n,i=e.hues;return i&&i.length>0&&(n=i[0|.999*t*i.length]),"number"==typeof n?(n/360%1+1)%1:t}return{X:l,p:"number"==typeof u?u:.5,H:"number"==typeof f?f:0,q:d("color",[.4,.8]),I:d("grayscale",[.3,.9]),J:r(c),Y:"number"==typeof t?t:"number"==typeof v?v:n}}var l=1,g=2,p={t:"data-jdenticon-hash",o:"data-jdenticon-value"},y="jdenticonRendered",m="["+p.t+"],["+p.o+"]",w="undefined"!=typeof document&&document.querySelectorAll.bind(document);function b(t){if(t){var n=t.tagName;if(/^svg$/i.test(n))return l;if(/^canvas$/i.test(n)&&"getContext"in t)return g}}function x(t){function n(){document.removeEventListener("DOMContentLoaded",n),window.removeEventListener("load",n),setTimeout(t,0)}"undefined"!=typeof document&&"undefined"!=typeof window&&"undefined"!=typeof setTimeout&&("loading"===document.readyState?(document.addEventListener("DOMContentLoaded",n),window.addEventListener("load",n)):setTimeout(t,0))}function A(t){"undefined"!=typeof MutationObserver&&new MutationObserver((function(n){for(var e=0;e1?0|a:a>.5?1:a,n.i(s,s,e-a-s,e-a-s)):4==t?(o=0|.15*e,u=0|.5*e,n.h(e-u-o,e-u-o,u)):5==t?((s=4*(a=.1*e))>3&&(s|=0),n.i(0,0,e,e),n.g([s,s,e-a,s,s+(e-s-a)/2,e-a],!0)):6==t?n.g([0,0,e,0,e,.7*e,.4*e,.4*e,.7*e,e,0,e]):7==t?n.j(e/2,e/2,e/2,e/2,3):8==t?(n.i(0,0,e,e/2),n.i(0,e/2,e/2,e/2),n.j(e/2,e/2,e/2,e/2,1)):9==t?(a=.14*e,s=e<4?1:e<6?2:0|.35*e,a=e<8?a:0|a,n.i(0,0,e,e),n.i(s,s,e-s-a,e-s-a,!0)):10==t?(s=3*(a=.12*e),n.i(0,0,e,e),n.h(s,s,e-a-s,!0)):11==t?n.j(e/2,e/2,e/2,e/2,3):12==t?(o=.25*e,n.i(0,0,e,e),n.N(o,o,e-o,e-o,!0)):!i&&(o=.4*e,u=1.2*e,n.h(o,o,u)):(r=.42*e,n.g([0,0,e,0,e,e-2*r,e-r,e,0,e]))}function O(t,n,e){var i;(t%=4)?1==t?n.j(0,e/2,e,e/2,0):2==t?n.N(0,0,e,e):(i=e/6,n.h(i,i,e-2*i)):n.j(0,0,e,e,0)}function T(t,n){return[f(t=n.X(t),n.H,n.I(0)),f(t,n.p,n.q(.5)),f(t,n.H,n.I(1)),f(t,n.p,n.q(1)),f(t,n.p,n.q(0))]}function k(t,e,i){var r=d(i,.08);r.J&&t.m(r.J);var o=t.k,u=.5+o*r.Y|0;o-=2*u;var f=new M(t),a=0|o/4,s=0|u+o/2-2*a,h=0|u+o/2-2*a;function c(i,r,o,u,c){var v=n(e,o,1),d=u?n(e,u,1):0;t.O(l[g[i]]);for(var p=0;p=0)for(var n=0;n=0)return!0}for(var y=0;y<3;y++)v=n(e,8+y,1)%l.length,(p([0,4])||p([2,3]))&&(v=1),g.push(v);c(0,O,2,3,[[1,0],[2,0],[2,3],[1,3],[0,1],[3,1],[3,2],[0,2]]),c(1,O,4,5,[[0,0],[3,0],[3,3],[0,3]]),c(2,N,1,null,[[1,1],[2,1],[2,2],[1,2]]),t.finish()}function I(t){var n,e=40,i=16,r=0,o=0,u=encodeURI(t)+"%80",f=[],a=[],s=1732584193,h=4023233417,c=~s,v=~h,d=3285377520,l=[s,h,c,v,d],g=0,p="";function y(t,n){return t<>>32-n}for(;r>2]=f[o>>2]|("%"==u[r]?parseInt(u.substring(r+1,r+=3),16):u.charCodeAt(r++))<<8*(3-(3&o));for(f[(n=(1+(o+7>>6))*i)-1]=8*o-8;g>3]>>>4*(7-(7&r))&15).toString(16);return p}function P(t){return/^[0-9a-f]{11,}$/i.test(t)&&t}function R(t){return I(null==t?"":""+t)}function F(t,n){var e=t.canvas,i=e.width,r=e.height;t.save(),n||(n=Math.min(i,r),t.translate((i-n)/2|0,(r-n)/2|0)),this.l=t,this.k=n,t.clearRect(0,0,n,n)}L.g=function(t,n){for(var e=this,i=n?-2:2,r=[],o=n?t.length-2:0;o=0;o+=i)r.push(e.A.L(t[o],t[o+1]));this.M.g(r)},L.h=function(t,n,e,i){var r=this.A.L(t,n,e,e);this.M.h(r,e,i)},L.i=function(t,n,e,i,r){this.g([t,n,t+e,n,t+e,n+i,t,n+i],r)},L.j=function(t,n,e,i,r,o){var u=[t+e,n,t+e,n+i,t,n+i,t,n];u.splice((r||0)%4*2,2),this.g(u,o)},L.N=function(t,n,e,i,r){this.g([t+e/2,n,t+e,n+i/2,t+e/2,n+i,t,n+i/2],r)};var q=F.prototype;function B(t,n,e,i){if(!t)throw new Error("No canvas specified.");k(new F(t,e),P(n)||R(n),i);var r=t.canvas;r&&(r[y]=!0)}function D(t){return(10*t+.5|0)/10}function E(){this.B=""}q.m=function(t){var n=this.l,e=this.k;n.fillStyle=o(t),n.fillRect(0,0,e,e)},q.O=function(t){var n=this.l;n.fillStyle=o(t),n.beginPath()},q.P=function(){this.l.fill()},q.g=function(t){var n=this.l;n.moveTo(t[0].x,t[0].y);for(var e=1;e'}var K=J.prototype;function V(t,n,e){var i=new J(n);return k(new $(i),P(t)||R(t),e),i.toString()}function W(t,n){for(var e=[],i=arguments.length-2;i-- >0;)e[i]=arguments[i+2];for(var r=document.createElementNS(H.T,n),o=0;o+1')},K.S=function(t,n){this.F+=''},K.toString=function(){return this.F+""};var Z=Y.prototype;function X(){w&&_(m)}function Q(){if(w)for(var t=w(m),n=0;n {{ $client.Index }} - {{ $client.PeerID }} - - {{ $client.Name }} - + + + {{ $client.Name }} + @@ -122,7 +120,7 @@

{{ range $j, $peer := $client.Peers }} {{if eq "inbound" $peer.Direction}}
- {{ $peer.State }} + {{ $peer.Alias }} {{ if eq $peer.Type "internal" }} @@ -139,7 +137,7 @@

{{ range $j, $peer := $client.Peers }} {{if eq "outbound" $peer.Direction}}
- {{ $peer.State }} + {{ $peer.Alias }} {{ if eq $peer.Type "internal" }} @@ -179,6 +177,7 @@

{{ end }} {{ define "js" }} + diff --git a/templates/clients/clients_el.html b/templates/clients/clients_el.html index b742a2c..30df9fc 100644 --- a/templates/clients/clients_el.html +++ b/templates/clients/clients_el.html @@ -61,18 +61,16 @@

{{ $client.Index }} - {{ $client.PeerID }} - - {{ $client.Name }} - + + + {{ $client.Name }} + @@ -150,7 +148,7 @@

{{ range $j, $peer := $client.Peers }} {{if eq "inbound" $peer.Direction}}
- + {{ $peer.Alias }} {{ if eq $peer.Type "internal" }} @@ -167,7 +165,7 @@

{{ range $j, $peer := $client.Peers }} {{if eq "outbound" $peer.Direction}}
- {{ $peer.State }} + {{ $peer.Alias }} {{ if eq $peer.Type "internal" }} @@ -207,6 +205,7 @@

{{ end }} {{ define "js" }} + diff --git a/types/models/clients_cl.go b/types/models/clients_cl.go index ea40d97..e17b613 100644 --- a/types/models/clients_cl.go +++ b/types/models/clients_cl.go @@ -42,7 +42,6 @@ type ClientCLPageDataPeerMapNode struct { ID string `json:"id"` Label string `json:"label"` Group string `json:"group"` - Image string `json:"image"` Shape string `json:"shape"` Value int `json:"value"` } diff --git a/types/models/clients_el.go b/types/models/clients_el.go index 897ea29..10a2792 100644 --- a/types/models/clients_el.go +++ b/types/models/clients_el.go @@ -44,7 +44,6 @@ type ClientELPageDataPeerMapNode struct { ID string `json:"id"` Label string `json:"label"` Group string `json:"group"` - Image string `json:"image"` Shape string `json:"shape"` Value int `json:"value"` } From 772cde0611e6a422886447281459ced60174d1e8 Mon Sep 17 00:00:00 2001 From: Rafael Matias Date: Thu, 29 Aug 2024 18:01:59 +0200 Subject: [PATCH 02/11] client pages: wip work - show more CL peer data --- clients/consensus/client.go | 8 +- clients/consensus/clientlogic.go | 2 +- clients/consensus/rpc/beaconapi.go | 15 ++-- go.mod | 2 - go.sum | 6 -- handlers/clients_cl.go | 98 ++++++++++++++++----- static/css/clients.css | 33 +++++-- static/css/layout.css | 14 +++ templates/clients/clients_cl.html | 133 ++++++++++++++++++++++------- types/config.go | 2 + types/models/clients_cl.go | 51 ++++++----- types/nodeidentity.go | 12 +++ utils/enr.go | 106 +++++++++++++++++++++++ 13 files changed, 384 insertions(+), 98 deletions(-) create mode 100644 types/nodeidentity.go create mode 100644 utils/enr.go diff --git a/clients/consensus/client.go b/clients/consensus/client.go index 1b19aed..3d1e0a0 100644 --- a/clients/consensus/client.go +++ b/clients/consensus/client.go @@ -11,6 +11,7 @@ import ( "github.com/ethpandaops/dora/clients/consensus/rpc" "github.com/ethpandaops/dora/clients/sshtunnel" + "github.com/ethpandaops/dora/types" ) type ClientConfig struct { @@ -33,7 +34,8 @@ type Client struct { isSyncing bool isOptimistic bool versionStr string - peerId string + enr string + nodeIdentity *types.NodeIdentity clientType ClientType lastEvent time.Time retryCounter uint64 @@ -112,8 +114,8 @@ func (client *Client) GetVersion() string { return client.versionStr } -func (client *Client) GetPeerID() string { - return client.peerId +func (client *Client) GetNodeIdentity() *types.NodeIdentity { + return client.nodeIdentity } func (client *Client) GetEndpointConfig() *ClientConfig { diff --git a/clients/consensus/clientlogic.go b/clients/consensus/clientlogic.go index e361da1..09a9a28 100644 --- a/clients/consensus/clientlogic.go +++ b/clients/consensus/clientlogic.go @@ -264,7 +264,7 @@ func (client *Client) updateNodePeers(ctx context.Context) error { defer cancel() var err error - client.peerId, err = client.rpcClient.GetNodePeerId(ctx) + client.nodeIdentity, err = client.rpcClient.GetNodeIdentity(ctx) if err != nil { return fmt.Errorf("could not get node peer id: %v", err) } diff --git a/clients/consensus/rpc/beaconapi.go b/clients/consensus/rpc/beaconapi.go index cd49b3e..a40cefb 100644 --- a/clients/consensus/rpc/beaconapi.go +++ b/clients/consensus/rpc/beaconapi.go @@ -25,6 +25,7 @@ import ( "golang.org/x/crypto/ssh" "github.com/ethpandaops/dora/clients/sshtunnel" + "github.com/ethpandaops/dora/types" ) type BeaconClient struct { @@ -470,18 +471,16 @@ func (bc *BeaconClient) GetNodePeers(ctx context.Context) ([]*v1.Peer, error) { return result.Data, nil } -func (bc *BeaconClient) GetNodePeerId(ctx context.Context) (string, error) { - nodeIdentity := struct { - Data struct { - PeerId string `json:"peer_id"` - } `json:"data"` +func (bc *BeaconClient) GetNodeIdentity(ctx context.Context) (*types.NodeIdentity, error) { + response := struct { + Data *types.NodeIdentity `json:"data"` }{} - err := bc.getJSON(ctx, fmt.Sprintf("%s/eth/v1/node/identity", bc.endpoint), &nodeIdentity) + err := bc.getJSON(ctx, fmt.Sprintf("%s/eth/v1/node/identity", bc.endpoint), &response) if err != nil { - return "", fmt.Errorf("error retrieving node identity: %v", err) + return nil, fmt.Errorf("error retrieving node identity: %v", err) } - return nodeIdentity.Data.PeerId, nil + return response.Data, nil } func (bc *BeaconClient) SubmitBLSToExecutionChanges(ctx context.Context, blsChanges []*capella.SignedBLSToExecutionChange) error { diff --git a/go.mod b/go.mod index 597092c..d6f38d9 100644 --- a/go.mod +++ b/go.mod @@ -16,7 +16,6 @@ require ( github.com/juliangruber/go-intersect v1.1.0 github.com/kelseyhightower/envconfig v1.4.0 github.com/lib/pq v1.10.9 - github.com/lucasb-eyer/go-colorful v1.2.0 github.com/mashingan/smapping v0.1.19 github.com/mitchellh/mapstructure v1.5.0 github.com/pk910/dynamic-ssz v0.0.5 @@ -27,7 +26,6 @@ require ( github.com/prysmaticlabs/go-bitfield v0.0.0-20240618144021-706c95b2dd15 github.com/rs/zerolog v1.33.0 github.com/sirupsen/logrus v1.9.3 - github.com/stdatiks/jdenticon-go v0.1.0 github.com/tdewolff/minify v2.3.6+incompatible github.com/urfave/negroni v1.0.0 golang.org/x/crypto v0.26.0 diff --git a/go.sum b/go.sum index 5540658..e8c7839 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,6 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DataDog/zstd v1.4.5 h1:EndNeuB0l9syBZhut0wns3gV1hL8zX8LIu6ZiVHWLIQ= github.com/DataDog/zstd v1.4.5/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= @@ -273,9 +272,6 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/lightclient/go-ethereum v0.0.0-20240726203109-4a0622f95d30 h1:mhkIkcs3GhL1kZyyCko1gUlnwCpfYpfYckqiz20k8HU= github.com/lightclient/go-ethereum v0.0.0-20240726203109-4a0622f95d30/go.mod h1:RKrX5zEFmD/CQ8XLRxc3eOEcqqwN4no8ZzuNkVxEFFY= -github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s= -github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= -github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= github.com/mashingan/smapping v0.1.19 h1:SsEtuPn2UcM1croIupPtGLgWgpYRuS0rSQMvKD9g2BQ= github.com/mashingan/smapping v0.1.19/go.mod h1:FjfiwFxGOuNxL/OT1WcrNAwTPx0YJeg5JiXwBB1nyig= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= @@ -385,8 +381,6 @@ github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= -github.com/stdatiks/jdenticon-go v0.1.0 h1:yf0xbl3OIu1oafVmgqGaB6m7QMNOaNkwsW/omzSyN5g= -github.com/stdatiks/jdenticon-go v0.1.0/go.mod h1:hzZIjAw3ZhYi3S5IOjIvC7C/dsc17L8Kc3AtEnP0ucw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= diff --git a/handlers/clients_cl.go b/handlers/clients_cl.go index bdd1896..9616674 100644 --- a/handlers/clients_cl.go +++ b/handlers/clients_cl.go @@ -7,9 +7,11 @@ import ( "strings" "time" + "github.com/ethereum/go-ethereum/p2p/enr" "github.com/ethpandaops/dora/services" "github.com/ethpandaops/dora/templates" "github.com/ethpandaops/dora/types/models" + "github.com/ethpandaops/dora/utils" "github.com/sirupsen/logrus" ) @@ -65,13 +67,16 @@ func buildCLPeerMapData() *models.ClientCLPageDataPeerMap { edges := make(map[string]*models.ClientCLDataMapPeerMapEdge) for _, client := range services.GlobalBeaconService.GetConsensusClients() { - peerID := client.GetPeerID() + id := client.GetNodeIdentity() + if id == nil { + continue + } + peerID := id.PeerID if _, ok := nodes[peerID]; !ok { node := models.ClientCLPageDataPeerMapNode{ ID: peerID, Label: client.GetName(), Group: "internal", - Shape: "circularImage", } nodes[peerID] = &node peerMap.ClientPageDataMapNode = append(peerMap.ClientPageDataMapNode, &node) @@ -79,7 +84,11 @@ func buildCLPeerMapData() *models.ClientCLPageDataPeerMap { } for _, client := range services.GlobalBeaconService.GetConsensusClients() { - peerId := client.GetPeerID() + id := client.GetNodeIdentity() + if id == nil { + continue + } + peerId := id.PeerID peers := client.GetNodePeers() for _, peer := range peers { peerId := peerId @@ -89,7 +98,6 @@ func buildCLPeerMapData() *models.ClientCLPageDataPeerMap { ID: peer.PeerID, Label: fmt.Sprintf("%s...%s", peer.PeerID[0:5], peer.PeerID[len(peer.PeerID)-5:]), Group: "external", - Shape: "circularImage", } nodes[peer.PeerID] = &node peerMap.ClientPageDataMapNode = append(peerMap.ClientPageDataMapNode, &node) @@ -131,8 +139,9 @@ func buildCLPeerMapData() *models.ClientCLPageDataPeerMap { func buildCLClientsPageData() (*models.ClientsCLPageData, time.Duration) { logrus.Debugf("clients page called") pageData := &models.ClientsCLPageData{ - Clients: []*models.ClientsCLPageDataClient{}, - PeerMap: buildCLPeerMapData(), + Clients: []*models.ClientsCLPageDataClient{}, + PeerMap: buildCLPeerMapData(), + ShowSensitivePeerInfos: utils.Config.Frontend.ShowSensitivePeerInfos, } chainState := services.GlobalBeaconService.GetChainState() @@ -145,7 +154,11 @@ func buildCLClientsPageData() (*models.ClientsCLPageData, time.Duration) { aliases := map[string]string{} for _, client := range services.GlobalBeaconService.GetConsensusClients() { - aliases[client.GetPeerID()] = client.GetName() + id := client.GetNodeIdentity() + if id == nil { + continue + } + aliases[id.PeerID] = client.GetName() } for _, client := range services.GlobalBeaconService.GetConsensusClients() { @@ -162,12 +175,30 @@ func buildCLClientsPageData() (*models.ClientsCLPageData, time.Duration) { peerAlias = alias peerType = "internal" } + + enrKeyValues := map[string]interface{}{} + var nodeID string + + if peer.Enr != "" { // Some clients might not announce the ENR of their peers + parsedEnr, err := utils.DecodeENR(peer.Enr) + if err != nil { + logrus.WithFields(logrus.Fields{"client": client.GetName(), "peer_enr": peer.Enr}).Error("failed to decode peer enr. ", err) + parsedEnr = &enr.Record{} + } + enrKeyValues = utils.GetKeyValuesFromENR(parsedEnr) + nodeID = utils.GetNodeIDFromENR(parsedEnr) + } + resPeers = append(resPeers, &models.ClientCLPageDataClientPeers{ - ID: peer.PeerID, - State: peer.State, - Direction: peer.Direction, - Alias: peerAlias, - Type: peerType, + ID: peer.PeerID, + State: peer.State, + Direction: peer.Direction, + Alias: peerAlias, + Type: peerType, + ENR: peer.Enr, + ENRKeyValues: enrKeyValues, + NodeID: nodeID, + LastSeenP2PAddress: peer.LastSeenP2PAddress, }) if peer.Direction == "inbound" { @@ -183,18 +214,39 @@ func buildCLClientsPageData() (*models.ClientsCLPageData, time.Duration) { return resPeers[i].Type > resPeers[j].Type }) + id := client.GetNodeIdentity() + if id == nil { + continue + } + + rec, err := utils.DecodeENR(id.Enr) + if err != nil { + logrus.WithFields(logrus.Fields{"client": client.GetName(), "enr": id.Enr}).Error("failed to decode enr. ", err) + rec = &enr.Record{} + } + + enrkv := utils.GetKeyValuesFromENR(rec) + + nodeID := utils.GetNodeIDFromENR(rec) + resClient := &models.ClientsCLPageDataClient{ - Index: int(client.GetIndex()) + 1, - Name: client.GetName(), - Version: client.GetVersion(), - Peers: resPeers, - PeerID: client.GetPeerID(), - PeersInboundCounter: inPeerCount, - PeersOutboundCounter: outPeerCount, - HeadSlot: uint64(lastHeadSlot), - HeadRoot: lastHeadRoot[:], - Status: client.GetStatus().String(), - LastRefresh: client.GetLastEventTime(), + Index: int(client.GetIndex()) + 1, + Name: client.GetName(), + Version: client.GetVersion(), + Peers: resPeers, + PeerID: id.PeerID, + ENR: id.Enr, + ENRKeyValues: enrkv, + NodeID: nodeID, + P2PAddresses: id.P2PAddresses, + DisoveryAddresses: id.DiscoveryAddresses, + AttestationSubnetSubs: id.Metadata.Attnets, + PeersInboundCounter: inPeerCount, + PeersOutboundCounter: outPeerCount, + HeadSlot: uint64(lastHeadSlot), + HeadRoot: lastHeadRoot[:], + Status: client.GetStatus().String(), + LastRefresh: client.GetLastEventTime(), } lastError := client.GetLastClientError() diff --git a/static/css/clients.css b/static/css/clients.css index 438f122..1b73e1b 100644 --- a/static/css/clients.css +++ b/static/css/clients.css @@ -1,11 +1,34 @@ /*--------------------------------------------- Client peers table ---------------------------------------------*/ -.client-node-peerinfo { - text-align: center; - padding-bottom: 10px; +.client-row-divider { + margin: 0 auto; + padding: 0; + padding-top: 3px; + padding-bottom: 5px; + font-weight: 600; border-bottom: 1px dashed; + text-align: center; +} + +.client-row-divider:not(:first-child) { + border-top: 1px dashed; +} + +.client-table-info { + padding-left: 30px; + padding-right: 30px; + display: inline-block; +} + +.client-node-peerinfo { + text-align: left; + padding-top: 20px; margin-bottom: 5px; + text-wrap: pretty; +} +.client-node-enr-infos{ + text-align: left; } .client-node-icon { border-radius:50%; @@ -26,9 +49,9 @@ Client peers table .peer-table-column{ display: inline-block; - width: 50%; vertical-align: top; - text-align: center; + text-align: left; + width: 100%; } .peer-table-icon.connected { diff --git a/static/css/layout.css b/static/css/layout.css index 50e630c..8947b1d 100644 --- a/static/css/layout.css +++ b/static/css/layout.css @@ -308,3 +308,17 @@ span.validator-label { .ellipsis-copy-btn { float: right; } + + +.table-borderless > tbody > tr > td, +.table-borderless > tbody > tr > th, +.table-borderless > tfoot > tr > td, +.table-borderless > tfoot > tr > th, +.table-borderless > thead > tr > td, +.table-borderless > thead > tr > th { + border: none; +} + +.table-sm>:not(caption)>*>* { + padding: 1px .25rem; +} diff --git a/templates/clients/clients_cl.html b/templates/clients/clients_cl.html index 49c196a..852e138 100644 --- a/templates/clients/clients_cl.html +++ b/templates/clients/clients_cl.html @@ -57,6 +57,7 @@

+ {{ $root := . }} {{ range $i, $client := .Clients }} {{ $client.Index }} @@ -104,52 +105,124 @@

{{ end }} - {{ $client.Version }} + {{ $client.Version }} - + +
Node identity
- Peer ID: {{ $client.PeerID }} - -
-
-
-
Inbound
- {{ range $j, $peer := $client.Peers }} - {{if eq "inbound" $peer.Direction}} -
- - - {{ $peer.Alias }} - {{ if eq $peer.Type "internal" }} - {{ trunc (sub (len $peer.ID) (add (len $peer.Alias) 4) ) $peer.ID }}... - {{ end }} - - -
- {{end}} - {{ end }} + + + + + + + {{ if $root.ShowSensitivePeerInfos }} + + + + + + + + + {{ end }} + +
Peer ID + {{ $client.PeerID }} + +
Node ID + {{ $client.NodeID }} + +
ENR +
+ {{ $client.ENR }} + +
+
+ {{ if $root.ShowSensitivePeerInfos }} +
ENR fields
+ + + {{ range $k, $v := $client.ENRKeyValues }} + + + + + {{ end }} + +
{{ $k }} + {{ $v }} + +
+ {{ end }} +
Peers
+
-
Outbound
{{ range $j, $peer := $client.Peers }} - {{if eq "outbound" $peer.Direction}}
+ {{ if eq $peer.Direction "inbound" }} + + {{ else }} + + {{ end}} - {{ $peer.Alias }} - {{ if eq $peer.Type "internal" }} - {{ trunc (sub (len $peer.ID) (add (len $peer.Alias) 4) ) $peer.ID }}... - {{ end }} + {{ $peer.ID }} + {{ if eq $peer.Type "internal" }} + {{ $peer.Alias }} + {{ end }}
- {{end}} + {{ if $root.ShowSensitivePeerInfos }} + +
+ + + + + + + + + + + {{ if ne $peer.ENR "" }} + + + + + {{ range $k, $v := $peer.ENRKeyValues }} + + + + + {{ end }} + {{ end }} + +
P2P Addr + {{ $peer.LastSeenP2PAddress }} + +
ENR +
+ {{ if eq $peer.ENR "" }}Unknown{{ else }}{{ $peer.ENR }}{{ end }} + +
+
Node ID + {{ $peer.NodeID }} + +
{{ $k }} + {{ $v }} + +
+
+ {{ end}} {{ end }}
-
{{ end }} diff --git a/types/config.go b/types/config.go index 8d79fd2..557e948 100644 --- a/types/config.go +++ b/types/config.go @@ -47,6 +47,8 @@ type Config struct { HttpWriteTimeout time.Duration `yaml:"httpWriteTimeout" envconfig:"FRONTEND_HTTP_WRITE_TIMEOUT"` HttpIdleTimeout time.Duration `yaml:"httpIdleTimeout" envconfig:"FRONTEND_HTTP_IDLE_TIMEOUT"` AllowDutyLoading bool `yaml:"allowDutyLoading" envconfig:"FRONTEND_ALLOW_DUTY_LOADING"` + + ShowSensitivePeerInfos bool `yaml:"showSensitivePeerInfos" envconfig:"FRONTEND_SHOW_SENSITIVE_PEER_INFOS"` } `yaml:"frontend"` RateLimit struct { diff --git a/types/models/clients_cl.go b/types/models/clients_cl.go index e17b613..704aca7 100644 --- a/types/models/clients_cl.go +++ b/types/models/clients_cl.go @@ -6,32 +6,43 @@ import ( // ClientsCLPageData is a struct to hold info for the clients page type ClientsCLPageData struct { - Clients []*ClientsCLPageDataClient `json:"clients"` - ClientCount uint64 `json:"client_count"` - PeerMap *ClientCLPageDataPeerMap `json:"peer_map"` + Clients []*ClientsCLPageDataClient `json:"clients"` + ClientCount uint64 `json:"client_count"` + PeerMap *ClientCLPageDataPeerMap `json:"peer_map"` + ShowSensitivePeerInfos bool `json:"show_sensitive_peer_infos"` } type ClientsCLPageDataClient struct { - Index int `json:"index"` - Name string `json:"name"` - Version string `json:"version"` - HeadSlot uint64 `json:"head_slot"` - HeadRoot []byte `json:"head_root"` - Status string `json:"status"` - LastRefresh time.Time `json:"refresh"` - LastError string `json:"error"` - PeerID string `json:"peer_id"` - Peers []*ClientCLPageDataClientPeers `json:"peers"` - PeersInboundCounter uint32 `json:"peers_inbound_counter"` - PeersOutboundCounter uint32 `json:"peers_outbound_counter"` + Index int `json:"index"` + Name string `json:"name"` + Version string `json:"version"` + HeadSlot uint64 `json:"head_slot"` + HeadRoot []byte `json:"head_root"` + Status string `json:"status"` + LastRefresh time.Time `json:"refresh"` + LastError string `json:"error"` + PeerID string `json:"peer_id"` + ENR string `json:"enr"` + ENRKeyValues map[string]interface{} `json:"enr_kv"` + NodeID string `json:"node_id"` + P2PAddresses []string `json:"p2p_addresses"` + DisoveryAddresses []string `json:"discovery_addresses"` + AttestationSubnetSubs string `json:"attestation_subnets_subs"` + Peers []*ClientCLPageDataClientPeers `json:"peers"` + PeersInboundCounter uint32 `json:"peers_inbound_counter"` + PeersOutboundCounter uint32 `json:"peers_outbound_counter"` } type ClientCLPageDataClientPeers struct { - ID string `json:"id"` - Alias string `json:"alias"` - Type string `json:"type"` - State string `json:"state"` - Direction string `json:"direction"` + ID string `json:"id"` + Alias string `json:"alias"` + Type string `json:"type"` + State string `json:"state"` + Direction string `json:"direction"` + ENR string `json:"enr"` + ENRKeyValues map[string]interface{} `json:"enr_kv"` + NodeID string `json:"node_id"` + LastSeenP2PAddress string `json:"last_seen_p2p_address"` } type ClientCLPageDataPeerMap struct { ClientPageDataMapNode []*ClientCLPageDataPeerMapNode `json:"nodes"` diff --git a/types/nodeidentity.go b/types/nodeidentity.go new file mode 100644 index 0000000..d450b38 --- /dev/null +++ b/types/nodeidentity.go @@ -0,0 +1,12 @@ +package types + +type NodeIdentity struct { + PeerID string `json:"peer_id"` + Enr string `json:"enr"` + P2PAddresses []string `json:"p2p_addresses"` + DiscoveryAddresses []string `json:"discovery_addresses"` + Metadata struct { + Attnets string `json:"attnets"` + //SeqNumber string `json:"seq_number"` // BUG: Teku and Grandine have an int type for this field + } `json:"metadata"` +} diff --git a/utils/enr.go b/utils/enr.go new file mode 100644 index 0000000..fabbabc --- /dev/null +++ b/utils/enr.go @@ -0,0 +1,106 @@ +package utils + +import ( + "bytes" + "encoding/base64" + "encoding/hex" + "net" + "strconv" + + "github.com/ethereum/go-ethereum/p2p/enode" + "github.com/ethereum/go-ethereum/p2p/enr" + "github.com/ethereum/go-ethereum/rlp" +) + +func DecodeENR(raw string) (*enr.Record, error) { + b := []byte(raw) + if bytes.HasPrefix(b, []byte("enr:")) { + b = b[4:] + } + dec := make([]byte, base64.RawURLEncoding.DecodedLen(len(b))) + n, err := base64.RawURLEncoding.Decode(dec, b) + if err != nil { + return nil, err + } + var r enr.Record + err = rlp.DecodeBytes(dec[:n], &r) + return &r, err +} + +func GetKeyValuesFromENR(r *enr.Record) map[string]interface{} { + fields := make(map[string]interface{}) + // Get sequence number + fields["seq"] = r.Seq() + + // Get signature + n, err := enode.New(enode.ValidSchemes, r) + if err == nil { + record := n.Record() + if record != nil { + fields["signature"] = "0x" + hex.EncodeToString(record.Signature()) + } + } + + // Get all key value pairs + kv := r.AppendElements(nil)[1:] + for i := 0; i < len(kv); i += 2 { + key := kv[i].(string) + val := kv[i+1].(rlp.RawValue) + formatter := attrFormatters[key] + if formatter == nil { + formatter = formatAttrRaw + } + fmtval, ok := formatter(val) + if ok { + fields[key] = fmtval + } else { + fields[key] = "0x" + hex.EncodeToString(val) + " (!)" + } + } + return fields +} + +func GetNodeIDFromENR(r *enr.Record) string { + n, err := enode.New(enode.ValidSchemes, r) + if err != nil { + return "" + } + return n.ID().String() +} + +// attrFormatters contains formatting functions for well-known ENR keys. +var attrFormatters = map[string]func(rlp.RawValue) (string, bool){ + "id": formatAttrString, + "ip": formatAttrIP, + "ip6": formatAttrIP, + "tcp": formatAttrUint, + "tcp6": formatAttrUint, + "udp": formatAttrUint, + "udp6": formatAttrUint, +} + +func formatAttrRaw(v rlp.RawValue) (string, bool) { + content, _, err := rlp.SplitString(v) + return "0x" + hex.EncodeToString(content), err == nil +} + +func formatAttrString(v rlp.RawValue) (string, bool) { + content, _, err := rlp.SplitString(v) + return string(content), err == nil +} + +func formatAttrIP(v rlp.RawValue) (string, bool) { + content, _, err := rlp.SplitString(v) + if err != nil || len(content) != 4 && len(content) != 6 { + return "", false + } + return net.IP(content).String(), true +} + +func formatAttrUint(v rlp.RawValue) (string, bool) { + var x uint64 + if err := rlp.DecodeBytes(v, &x); err != nil { + return "", false + } + return strconv.FormatUint(x, 10), true +} From 146bdc719e3a5a77f0df21a8731bdd4e6b9706fb Mon Sep 17 00:00:00 2001 From: Rafael Matias Date: Mon, 2 Sep 2024 15:08:37 +0200 Subject: [PATCH 03/11] client pages: move some css --- static/css/clients.css | 17 +++++++++++++++++ templates/clients/clients_cl.html | 11 +++++------ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/static/css/clients.css b/static/css/clients.css index 1b73e1b..9f30942 100644 --- a/static/css/clients.css +++ b/static/css/clients.css @@ -30,6 +30,23 @@ Client peers table .client-node-enr-infos{ text-align: left; } + +.client-node-peer-details { + margin-left: 46px; + padding-top:3px; + margin-bottom: 10px; + border-left: 1px dotted; + border-bottom: 1px dotted; +} + +.client-node-peer-details > tr { + vertical-align: top; +} + +.client-node-peer-count { + width:30px; + display: inline-block; +} .client-node-icon { border-radius:50%; border:1px solid #0f0f0f; diff --git a/templates/clients/clients_cl.html b/templates/clients/clients_cl.html index 852e138..065d8e2 100644 --- a/templates/clients/clients_cl.html +++ b/templates/clients/clients_cl.html @@ -74,15 +74,15 @@

- + {{ $client.PeersInboundCounter }} - + {{ $client.PeersOutboundCounter}} - + ({{ len $client.Peers }}) @@ -179,8 +179,7 @@

{{ end }}

{{ if $root.ShowSensitivePeerInfos }} - -
+
@@ -190,7 +189,7 @@

- +
ENR
From df125f08d6b1152a61835bded89942c1988b44c1 Mon Sep 17 00:00:00 2001 From: Rafael Matias Date: Mon, 2 Sep 2024 16:53:02 +0200 Subject: [PATCH 04/11] client pages: add sensitive EL infos --- .hack/devnet/run.sh | 3 +- handlers/clients_el.go | 22 ++++- templates/clients/clients_cl.html | 9 +- templates/clients/clients_el.html | 135 ++++++++++++++++++++++-------- types/models/clients_el.go | 24 ++++-- 5 files changed, 145 insertions(+), 48 deletions(-) diff --git a/.hack/devnet/run.sh b/.hack/devnet/run.sh index 18f860a..29f5124 100755 --- a/.hack/devnet/run.sh +++ b/.hack/devnet/run.sh @@ -43,6 +43,7 @@ frontend: siteName: "Dora the Explorer" siteSubtitle: "$ENCLAVE_NAME - Kurtosis" ethExplorerLink: "" + showSensitivePeerInfos: true beaconapi: localCacheSize: 10 redisCacheAddr: "" @@ -81,4 +82,4 @@ Dora config at ${__dir}/generated-dora-config.yaml Chain config at ${__dir}/generated-chain-config.yaml Database at ${__dir}/generated-database.sqlite ============================================================================================================ -EOF \ No newline at end of file +EOF diff --git a/handlers/clients_el.go b/handlers/clients_el.go index 0a5b297..ac6cc24 100644 --- a/handlers/clients_el.go +++ b/handlers/clients_el.go @@ -11,6 +11,7 @@ import ( "github.com/ethpandaops/dora/services" "github.com/ethpandaops/dora/templates" "github.com/ethpandaops/dora/types/models" + "github.com/ethpandaops/dora/utils" "github.com/sirupsen/logrus" ) @@ -162,8 +163,9 @@ func buildELPeerMapData() *models.ClientELPageDataPeerMap { func buildELClientsPageData() (*models.ClientsELPageData, time.Duration) { logrus.Debugf("clients page called") pageData := &models.ClientsELPageData{ - Clients: []*models.ClientsELPageDataClient{}, - PeerMap: buildELPeerMapData(), + Clients: []*models.ClientsELPageDataClient{}, + PeerMap: buildELPeerMapData(), + ShowSensitivePeerInfos: utils.Config.Frontend.ShowSensitivePeerInfos, } chainState := services.GlobalBeaconService.GetChainState() specs := chainState.GetSpecs() @@ -196,10 +198,12 @@ func buildELClientsPageData() (*models.ClientsELPageData, time.Duration) { for _, peer := range peers { en, err := enode.ParseV4(peer.Enode) peerID := "unknown" + enoderaw := "unknown" if err != nil { logrus.WithFields(logrus.Fields{"client": client.GetName(), "enode": peer.Enode}).Error("failed to parse peer enode") } else { peerID = en.ID().String() + enoderaw = en.String() } peerAlias := peerID peerType := "external" @@ -211,11 +215,16 @@ func buildELClientsPageData() (*models.ClientsELPageData, time.Duration) { if peer.Network.Inbound { direction = "inbound" } + resPeers = append(resPeers, &models.ClientELPageDataClientPeers{ ID: peerID, State: peer.Name, Direction: direction, Alias: peerAlias, + Name: peer.Name, + Enode: enoderaw, + Caps: peer.Caps, + Protocols: peer.Protocols, Type: peerType, }) @@ -236,14 +245,20 @@ func buildELClientsPageData() (*models.ClientsELPageData, time.Duration) { peerID := "unknown" peerName := "unknown" + enoderaw := "unknown" + ipAddr := "unknown" + listenAddr := "unknown" if nodeInfo != nil { en, err := enode.ParseV4(nodeInfo.Enode) if err != nil { logrus.WithFields(logrus.Fields{"client": client.GetName(), "enode": nodeInfo.Enode}).Error("failed to parse peer enode") } else { peerID = en.ID().String() + enoderaw = en.String() } peerName = nodeInfo.Name + ipAddr = nodeInfo.IP + listenAddr = nodeInfo.ListenAddr } resClient := &models.ClientsELPageDataClient{ @@ -254,6 +269,9 @@ func buildELClientsPageData() (*models.ClientsELPageData, time.Duration) { Peers: resPeers, PeerID: peerID, PeerName: peerName, + Enode: enoderaw, + IPAddr: ipAddr, + ListenAddr: listenAddr, PeersInboundCounter: inPeerCount, PeersOutboundCounter: outPeerCount, HeadSlot: uint64(lastHeadSlot), diff --git a/templates/clients/clients_cl.html b/templates/clients/clients_cl.html index 065d8e2..e63c0d5 100644 --- a/templates/clients/clients_cl.html +++ b/templates/clients/clients_cl.html @@ -115,6 +115,13 @@

+ + + + + {{ $root := . }} {{ range $i, $client := .Clients }} @@ -119,62 +120,124 @@

-
Name + {{ $client.Name }} + +
Peer ID @@ -170,7 +177,7 @@

{{ end}} - + {{ $peer.ID }} diff --git a/templates/clients/clients_el.html b/templates/clients/clients_el.html index 30df9fc..8428af4 100644 --- a/templates/clients/clients_el.html +++ b/templates/clients/clients_el.html @@ -57,6 +57,7 @@

{{ $client.Index }}
+ +
Node identity
- +
- - + - - + + + {{ if $root.ShowSensitivePeerInfos }} + + + + + + + + + + + + + {{ end }} -
ID - {{ $client.PeerID }} - + Name + {{ $client.Name }} +
Name - {{ $client.PeerName }} - + Peer ID + {{ $client.PeerID }} + +
Enode +
+ {{ $client.Enode }} + +
+
IP Address +
+ {{ $client.IPAddr }} + +
+
Listen Address +
+ {{ $client.ListenAddr }} + +
+
Peers
-
-
Inbound
- {{ range $j, $peer := $client.Peers }} - {{if eq "inbound" $peer.Direction}} -
- - - {{ $peer.Alias }} - {{ if eq $peer.Type "internal" }} - {{ trunc (sub (len $peer.ID) (add (len $peer.Alias) 4) ) $peer.ID }}... - {{ end }} - - -
- {{end}} - {{ end }} -
-
Outbound
{{ range $j, $peer := $client.Peers }} - {{if eq "outbound" $peer.Direction}}
+ {{ if contains $client.Version "Nethermind"}} + + {{ else if eq $peer.Direction "inbound" }} + + {{ else }} + + {{ end}} - - {{ $peer.Alias }} - {{ if eq $peer.Type "internal" }} - {{ trunc (sub (len $peer.ID) (add (len $peer.Alias) 4) ) $peer.ID }}... - {{ end }} + + {{ $peer.ID }} + {{ if eq $peer.Type "internal" }} + {{ $peer.Alias }} + {{ end }}
- {{end}} + {{ if $root.ShowSensitivePeerInfos }} +
+ + + + + + + + + + + + + + + + + + + +
Enode +
+ {{ $peer.Enode }} + +
+
Name +
+ {{ $peer.Name }} + +
+
Capabilities +
+ {{ toJson $peer.Caps }} + +
+
Protocols +
+ {{ toPrettyJson $peer.Protocols }} + +
+
+
+ {{ end}} {{ end }}
diff --git a/types/models/clients_el.go b/types/models/clients_el.go index 10a2792..e47b9c3 100644 --- a/types/models/clients_el.go +++ b/types/models/clients_el.go @@ -6,9 +6,10 @@ import ( // ClientsELPageData is a struct to hold info for the clients page type ClientsELPageData struct { - Clients []*ClientsELPageDataClient `json:"clients"` - ClientCount uint64 `json:"client_count"` - PeerMap *ClientELPageDataPeerMap `json:"peer_map"` + Clients []*ClientsELPageDataClient `json:"clients"` + ClientCount uint64 `json:"client_count"` + PeerMap *ClientELPageDataPeerMap `json:"peer_map"` + ShowSensitivePeerInfos bool `json:"show_sensitive_peer_infos"` } type ClientsELPageDataClient struct { @@ -22,6 +23,9 @@ type ClientsELPageDataClient struct { LastError string `json:"error"` PeerID string `json:"peer_id"` PeerName string `json:"peer_name"` + Enode string `json:"enode"` + IPAddr string `json:"ip_addr"` + ListenAddr string `json:"listen_addr"` Peers []*ClientELPageDataClientPeers `json:"peers"` DidFetchPeers bool `json:"peers_fetched"` PeersInboundCounter uint32 `json:"peers_inbound_counter"` @@ -29,11 +33,15 @@ type ClientsELPageDataClient struct { } type ClientELPageDataClientPeers struct { - ID string `json:"id"` - Alias string `json:"alias"` - Type string `json:"type"` - State string `json:"state"` - Direction string `json:"direction"` + ID string `json:"id"` + Alias string `json:"alias"` + Enode string `json:"enode"` + Name string `json:"name"` + Type string `json:"type"` + State string `json:"state"` + Direction string `json:"direction"` + Caps []string `json:"caps"` + Protocols map[string]interface{} `json:"protocols"` } type ClientELPageDataPeerMap struct { ClientPageDataMapNode []*ClientELPageDataPeerMapNode `json:"nodes"` From 54d2a4c0f25e050cc3fb48d79b25eb34ce794b97 Mon Sep 17 00:00:00 2001 From: Rafael Matias Date: Mon, 2 Sep 2024 16:55:42 +0200 Subject: [PATCH 05/11] client pages: fix from linter --- clients/consensus/client.go | 1 - 1 file changed, 1 deletion(-) diff --git a/clients/consensus/client.go b/clients/consensus/client.go index 3d1e0a0..fe6b0f3 100644 --- a/clients/consensus/client.go +++ b/clients/consensus/client.go @@ -34,7 +34,6 @@ type Client struct { isSyncing bool isOptimistic bool versionStr string - enr string nodeIdentity *types.NodeIdentity clientType ClientType lastEvent time.Time From 09495eac169ca3d3cc316e111db592aef621ca23 Mon Sep 17 00:00:00 2001 From: Rafael Matias Date: Wed, 4 Sep 2024 14:04:39 +0200 Subject: [PATCH 06/11] client pages: improve styles and add concentric layout --- handlers/clients_cl.go | 5 ++-- handlers/clients_el.go | 5 ++-- static/css/clients.css | 5 ++++ static/js/cytoscape-network-aux.js | 46 ++++++++++++++++++++++++++---- templates/clients/clients_cl.html | 13 ++++++--- templates/clients/clients_el.html | 12 +++++--- types/models/clients_cl.go | 6 ++-- types/models/clients_el.go | 6 ++-- 8 files changed, 73 insertions(+), 25 deletions(-) diff --git a/handlers/clients_cl.go b/handlers/clients_cl.go index 9616674..ca00776 100644 --- a/handlers/clients_cl.go +++ b/handlers/clients_cl.go @@ -116,9 +116,8 @@ func buildCLPeerMapData() *models.ClientCLPageDataPeerMap { p2.Value++ if _, ok := edges[idx]; !ok { - edge := models.ClientCLDataMapPeerMapEdge{} - if nodes[peer.PeerID].Group == "external" { - edge.Dashes = true + edge := models.ClientCLDataMapPeerMapEdge{ + Interaction: nodes[peer.PeerID].Group, } if peer.Direction == "inbound" { edge.From = peer.PeerID diff --git a/handlers/clients_el.go b/handlers/clients_el.go index ac6cc24..160e280 100644 --- a/handlers/clients_el.go +++ b/handlers/clients_el.go @@ -140,9 +140,8 @@ func buildELPeerMapData() *models.ClientELPageDataPeerMap { p2.Value++ if _, ok := edges[idx]; !ok { - edge := models.ClientELDataMapPeerMapEdge{} - if nodes[peerID].Group == "external" { - edge.Dashes = true + edge := models.ClientELDataMapPeerMapEdge{ + Interaction: nodes[peerID].Group, } if peer.Network.Inbound { edge.From = peerID diff --git a/static/css/clients.css b/static/css/clients.css index 9f30942..6c98b7e 100644 --- a/static/css/clients.css +++ b/static/css/clients.css @@ -89,10 +89,15 @@ Client peers table .peer-nodemap { background: #0f0f0f9f url(/images/stars.png) repeat top center; + background-blend-mode: multiply; height: 850px; padding: 0; } +.peer-nodemap-menu{ + height: 40px; +} + @media only screen and (max-width: 768px) { .peer-nodemap { height: 50vh; diff --git a/static/js/cytoscape-network-aux.js b/static/js/cytoscape-network-aux.js index cc86b30..170c400 100644 --- a/static/js/cytoscape-network-aux.js +++ b/static/js/cytoscape-network-aux.js @@ -30,7 +30,7 @@ $_network.layouts = { fcose : function(nodeCount){ return { name: 'fcose', - idealEdgeLength: 5 * nodeCount, + idealEdgeLength: 3 * nodeCount, nestingFactor: 1.2, animate: false, stop: function() { @@ -50,6 +50,19 @@ $_network.layouts = { animate: false, } }, + concentric : function() { + return { + name: 'concentric', + animate: false, + concentric: function( node ){ + //return node.degree(); + return node.data('group') == 'internal' ? 2 : 1; + }, + levelWidth: function( nodes ){ + return 1; + } + } + }, } // Default layout @@ -66,6 +79,7 @@ $_network.defaultStylesheet = cytoscape.stylesheet() 'border-color': '#0077B6', 'border-width': 1, 'border-opacity': 1, + 'shape': 'ellipse', }) .selector('edge') .css({ @@ -87,6 +101,29 @@ $_network.defaultStylesheet = cytoscape.stylesheet() "color": "#ffffff", "font-size": 4, }) + .selector('edge[interaction = "external"]') + .css({ + 'line-style': 'dashed', + 'line-color': '#075e4d', + 'target-arrow-color': '#075e4d', + 'source-arrow-color': '#075e4d', + }) + .selector('node[group = "internal"]') + .css({ + 'border-color': '#FFA500', + 'background-color': '#FFA500', + 'opacity': 1 + }) + .selector('node[group = "external"]') + .css({ + 'border-color': '#075e4d', + 'background-color': '#075e4d', + 'opacity': 0.5, + 'height': 15, + 'width': 15, + 'border-style:': 'dashed', + 'shape': 'round-octagon', + }) .selector('node:selected, edge:selected') .css({ 'border-color': '#FFA500', @@ -100,7 +137,7 @@ $_network.defaultStylesheet = cytoscape.stylesheet() $_network.fitAnimated = function (cy, layout) { cy.animate({ - fit: { eles: cy.$() }, + fit: { padding: 20 }, duration: 500, complete: function () { setTimeout(function () { @@ -132,7 +169,6 @@ $_network.create = function (container, data){ svgIdenticon = jdenticon.toSvg(data.nodes[i].id, 80); // Add style to nodes stylesheet.selector('#' + data.nodes[i].id).css({ - 'shape': 'circle', 'background-image': 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(svgIdenticon), 'background-fit': 'cover', 'background-opacity': 1, @@ -146,7 +182,8 @@ $_network.create = function (container, data){ data: { id: data.edges[i].from + "-" + data.edges[i].to, source: data.edges[i].from, - target: data.edges[i].to + target: data.edges[i].to, + interaction: data.edges[i].interaction, } }); } @@ -156,7 +193,6 @@ $_network.create = function (container, data){ style: stylesheet, layout: $_network.defaultLayout(data.nodes.length), elements: cytoElements, - wheelSensitivity: 0.1, }); cy.on('tap', 'node', function(evt){ diff --git a/templates/clients/clients_cl.html b/templates/clients/clients_cl.html index e63c0d5..3fa4a04 100644 --- a/templates/clients/clients_cl.html +++ b/templates/clients/clients_cl.html @@ -23,10 +23,14 @@

Loading...
-
- - - +
+
+
+ + + + +
@@ -261,6 +265,7 @@

+