Add cluster stats tab to explorer (#11325)
This commit is contained in:
parent
9bcfc51df1
commit
54d36d2cfb
|
@ -2283,6 +2283,20 @@
|
|||
"ieee754": "^1.1.4"
|
||||
}
|
||||
},
|
||||
"kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="
|
||||
},
|
||||
"superstruct": {
|
||||
"version": "0.8.4",
|
||||
"resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.8.4.tgz",
|
||||
"integrity": "sha512-48Ors8IVWZm/tMr8r0Si6+mJiB7mkD7jqvIzktjJ4+EnP5tBp0qOpiM1J8sCUorKx+TXWrfb3i1UcjdD1YK/wA==",
|
||||
"requires": {
|
||||
"kind-of": "^6.0.2",
|
||||
"tiny-invariant": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"tweetnacl": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
|
||||
|
@ -3012,6 +3026,11 @@
|
|||
"@types/react-router": "*"
|
||||
}
|
||||
},
|
||||
"@types/socket.io-client": {
|
||||
"version": "1.4.33",
|
||||
"resolved": "https://registry.npmjs.org/@types/socket.io-client/-/socket.io-client-1.4.33.tgz",
|
||||
"integrity": "sha512-m4LnxkljsI9fMsjwpW5QhRpMixo2BeeLpFmg0AE+sS4H1pzAd/cs/ftTiL60FLZgfFa8PFRPx5KsHu8O0bADKQ=="
|
||||
},
|
||||
"@types/stack-utils": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz",
|
||||
|
@ -3366,6 +3385,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"after": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz",
|
||||
"integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8="
|
||||
},
|
||||
"aggregate-error": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.0.1.tgz",
|
||||
|
@ -3580,6 +3604,11 @@
|
|||
"es-abstract": "^1.17.0-next.1"
|
||||
}
|
||||
},
|
||||
"arraybuffer.slice": {
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz",
|
||||
"integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog=="
|
||||
},
|
||||
"arrify": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
|
||||
|
@ -4349,6 +4378,11 @@
|
|||
"resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz",
|
||||
"integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ=="
|
||||
},
|
||||
"backo2": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
|
||||
"integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc="
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
||||
|
@ -4417,6 +4451,11 @@
|
|||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"base64-arraybuffer": {
|
||||
"version": "0.1.5",
|
||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz",
|
||||
"integrity": "sha1-c5JncZI7Whl0etZmqlzUv5xunOg="
|
||||
},
|
||||
"base64-js": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
|
||||
|
@ -4435,6 +4474,14 @@
|
|||
"tweetnacl": "^0.14.3"
|
||||
}
|
||||
},
|
||||
"better-assert": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/better-assert/-/better-assert-1.0.2.tgz",
|
||||
"integrity": "sha1-QIZrnhueC1W0gYlDEeaPr/rrxSI=",
|
||||
"requires": {
|
||||
"callsite": "1.0.0"
|
||||
}
|
||||
},
|
||||
"big.js": {
|
||||
"version": "5.2.2",
|
||||
"resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz",
|
||||
|
@ -4454,6 +4501,11 @@
|
|||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"blob": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz",
|
||||
"integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig=="
|
||||
},
|
||||
"block-stream": {
|
||||
"version": "0.0.9",
|
||||
"resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz",
|
||||
|
@ -4833,6 +4885,11 @@
|
|||
"caller-callsite": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"callsite": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz",
|
||||
"integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA="
|
||||
},
|
||||
"callsites": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz",
|
||||
|
@ -5263,11 +5320,21 @@
|
|||
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
|
||||
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs="
|
||||
},
|
||||
"component-bind": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz",
|
||||
"integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E="
|
||||
},
|
||||
"component-emitter": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
|
||||
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
|
||||
},
|
||||
"component-inherit": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz",
|
||||
"integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM="
|
||||
},
|
||||
"compose-function": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/compose-function/-/compose-function-3.0.3.tgz",
|
||||
|
@ -5466,6 +5533,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"countup.js": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/countup.js/-/countup.js-1.9.3.tgz",
|
||||
"integrity": "sha1-zj5QzXFgRB5HjwfaMYle3MDxyd0="
|
||||
},
|
||||
"create-ecdh": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz",
|
||||
|
@ -6309,6 +6381,46 @@
|
|||
"once": "^1.4.0"
|
||||
}
|
||||
},
|
||||
"engine.io-client": {
|
||||
"version": "3.4.3",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.4.3.tgz",
|
||||
"integrity": "sha512-0NGY+9hioejTEJCaSJZfWZLk4FPI9dN+1H1C4+wj2iuFba47UgZbJzfWs4aNFajnX/qAaYKbe2lLTfEEWzCmcw==",
|
||||
"requires": {
|
||||
"component-emitter": "~1.3.0",
|
||||
"component-inherit": "0.0.3",
|
||||
"debug": "~4.1.0",
|
||||
"engine.io-parser": "~2.2.0",
|
||||
"has-cors": "1.1.0",
|
||||
"indexof": "0.0.1",
|
||||
"parseqs": "0.0.5",
|
||||
"parseuri": "0.0.5",
|
||||
"ws": "~6.1.0",
|
||||
"xmlhttprequest-ssl": "~1.5.4",
|
||||
"yeast": "0.1.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"ws": {
|
||||
"version": "6.1.4",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-6.1.4.tgz",
|
||||
"integrity": "sha512-eqZfL+NE/YQc1/ZynhojeV8q+H050oR8AZ2uIev7RU10svA9ZnJUddHcOUZTJLinZ9yEfdA2kSATS2qZK5fhJA==",
|
||||
"requires": {
|
||||
"async-limiter": "~1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"engine.io-parser": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.0.tgz",
|
||||
"integrity": "sha512-6I3qD9iUxotsC5HEMuuGsKA0cXerGz+4uGcXQEkfBidgKf0amsjrrtwcbwK/nzpZBxclXlV7gGl9dgWvu4LF6w==",
|
||||
"requires": {
|
||||
"after": "0.8.2",
|
||||
"arraybuffer.slice": "~0.0.7",
|
||||
"base64-arraybuffer": "0.1.5",
|
||||
"blob": "0.0.5",
|
||||
"has-binary2": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"enhanced-resolve": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.1.1.tgz",
|
||||
|
@ -7827,6 +7939,26 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"has-binary2": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz",
|
||||
"integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==",
|
||||
"requires": {
|
||||
"isarray": "2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"isarray": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
|
||||
"integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
|
||||
}
|
||||
}
|
||||
},
|
||||
"has-cors": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
|
||||
"integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk="
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
|
@ -8137,6 +8269,11 @@
|
|||
"resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
|
||||
"integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM="
|
||||
},
|
||||
"humanize-duration-ts": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/humanize-duration-ts/-/humanize-duration-ts-2.1.1.tgz",
|
||||
"integrity": "sha512-TibNF2/fkypjAfHdGpWL/dmWUS0G6Qi+3mKyiB6LDCowbMy+PtzbgPTnFMNTOVAJXDau01jYrJ3tFoz5AJSqhA=="
|
||||
},
|
||||
"iconv-lite": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||
|
@ -8235,6 +8372,11 @@
|
|||
"resolved": "https://registry.npmjs.org/indexes-of/-/indexes-of-1.0.1.tgz",
|
||||
"integrity": "sha1-8w9xbI4r00bHtn0985FVZqfAVgc="
|
||||
},
|
||||
"indexof": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz",
|
||||
"integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10="
|
||||
},
|
||||
"infer-owner": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz",
|
||||
|
@ -10920,6 +11062,11 @@
|
|||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
|
||||
},
|
||||
"object-component": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/object-component/-/object-component-0.0.3.tgz",
|
||||
"integrity": "sha1-8MaapQ78lbhmwYb0AKM3acsvEpE="
|
||||
},
|
||||
"object-copy": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz",
|
||||
|
@ -11348,6 +11495,22 @@
|
|||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz",
|
||||
"integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA=="
|
||||
},
|
||||
"parseqs": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz",
|
||||
"integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=",
|
||||
"requires": {
|
||||
"better-assert": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"parseuri": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.5.tgz",
|
||||
"integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=",
|
||||
"requires": {
|
||||
"better-assert": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"parseurl": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
|
||||
|
@ -12786,6 +12949,16 @@
|
|||
"semver": "^5.6.0"
|
||||
}
|
||||
},
|
||||
"react-countup": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/react-countup/-/react-countup-4.3.3.tgz",
|
||||
"integrity": "sha512-pWnxpwdPNRyJFha/YKKbyc4RLAw8PzmULdgCziGIgw6vxhT1VdccrvQgj38HBSoM2qF/MoLmn4M2klvDWVIdaw==",
|
||||
"requires": {
|
||||
"countup.js": "^1.9.3",
|
||||
"prop-types": "^15.7.2",
|
||||
"warning": "^4.0.3"
|
||||
}
|
||||
},
|
||||
"react-dev-utils": {
|
||||
"version": "10.2.1",
|
||||
"resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-10.2.1.tgz",
|
||||
|
@ -14408,6 +14581,69 @@
|
|||
"kind-of": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"socket.io-client": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-2.3.0.tgz",
|
||||
"integrity": "sha512-cEQQf24gET3rfhxZ2jJ5xzAOo/xhZwK+mOqtGRg5IowZsMgwvHwnf/mCRapAAkadhM26y+iydgwsXGObBB5ZdA==",
|
||||
"requires": {
|
||||
"backo2": "1.0.2",
|
||||
"base64-arraybuffer": "0.1.5",
|
||||
"component-bind": "1.0.0",
|
||||
"component-emitter": "1.2.1",
|
||||
"debug": "~4.1.0",
|
||||
"engine.io-client": "~3.4.0",
|
||||
"has-binary2": "~1.0.2",
|
||||
"has-cors": "1.1.0",
|
||||
"indexof": "0.0.1",
|
||||
"object-component": "0.0.3",
|
||||
"parseqs": "0.0.5",
|
||||
"parseuri": "0.0.5",
|
||||
"socket.io-parser": "~3.3.0",
|
||||
"to-array": "0.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"component-emitter": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
|
||||
"integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
|
||||
}
|
||||
}
|
||||
},
|
||||
"socket.io-parser": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.3.0.tgz",
|
||||
"integrity": "sha512-hczmV6bDgdaEbVqhAeVMM/jfUfzuEZHsQg6eOmLgJht6G3mPKMxYm75w2+qhAQZ+4X+1+ATZ+QFKeOZD5riHng==",
|
||||
"requires": {
|
||||
"component-emitter": "1.2.1",
|
||||
"debug": "~3.1.0",
|
||||
"isarray": "2.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"component-emitter": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
|
||||
"integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
|
||||
},
|
||||
"debug": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"isarray": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz",
|
||||
"integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
|
||||
}
|
||||
}
|
||||
},
|
||||
"sockjs": {
|
||||
"version": "0.3.19",
|
||||
"resolved": "https://registry.npmjs.org/sockjs/-/sockjs-0.3.19.tgz",
|
||||
|
@ -14962,20 +15198,9 @@
|
|||
}
|
||||
},
|
||||
"superstruct": {
|
||||
"version": "0.8.4",
|
||||
"resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.8.4.tgz",
|
||||
"integrity": "sha512-48Ors8IVWZm/tMr8r0Si6+mJiB7mkD7jqvIzktjJ4+EnP5tBp0qOpiM1J8sCUorKx+TXWrfb3i1UcjdD1YK/wA==",
|
||||
"requires": {
|
||||
"kind-of": "^6.0.2",
|
||||
"tiny-invariant": "^1.0.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"kind-of": {
|
||||
"version": "6.0.3",
|
||||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="
|
||||
}
|
||||
}
|
||||
"version": "0.10.12",
|
||||
"resolved": "https://registry.npmjs.org/superstruct/-/superstruct-0.10.12.tgz",
|
||||
"integrity": "sha512-FiNhfegyytDI0QxrrEoeGknFM28SnoHqCBpkWewUm8jRNj74NVxLpiiePvkOo41Ze/aKMSHa/twWjNF81mKaQQ=="
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
|
@ -15394,6 +15619,11 @@
|
|||
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz",
|
||||
"integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE="
|
||||
},
|
||||
"to-array": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz",
|
||||
"integrity": "sha1-F+bBH3PdTz10zaek/zI46a2b+JA="
|
||||
},
|
||||
"to-arraybuffer": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz",
|
||||
|
@ -15831,6 +16061,14 @@
|
|||
"makeerror": "1.0.x"
|
||||
}
|
||||
},
|
||||
"warning": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
|
||||
"integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
|
||||
"requires": {
|
||||
"loose-envify": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"wasm-dce": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wasm-dce/-/wasm-dce-1.0.2.tgz",
|
||||
|
@ -16776,6 +17014,11 @@
|
|||
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
|
||||
"integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="
|
||||
},
|
||||
"xmlhttprequest-ssl": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz",
|
||||
"integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4="
|
||||
},
|
||||
"xregexp": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.3.0.tgz",
|
||||
|
@ -16924,6 +17167,11 @@
|
|||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"yeast": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
|
||||
"integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,16 +14,21 @@
|
|||
"@types/react": "^16.9.43",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/react-router-dom": "^5.1.5",
|
||||
"@types/socket.io-client": "^1.4.33",
|
||||
"bootstrap": "^4.5.0",
|
||||
"bs58": "^4.0.1",
|
||||
"humanize-duration-ts": "^2.1.1",
|
||||
"node-sass": "^4.14.1",
|
||||
"prettier": "^2.0.5",
|
||||
"react": "^16.13.1",
|
||||
"react-app-rewired": "^2.1.6",
|
||||
"react-countup": "^4.3.3",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "3.4.1",
|
||||
"socket.io-client": "^2.3.0",
|
||||
"solana-sdk-wasm": "file:wasm/pkg",
|
||||
"superstruct": "^0.10.12",
|
||||
"typescript": "^3.9.7",
|
||||
"wasm-loader": "^1.3.0"
|
||||
},
|
||||
|
|
|
@ -12,6 +12,7 @@ import { ACCOUNT_ALIASES, ACCOUNT_ALIASES_PLURAL } from "./providers/accounts";
|
|||
import TabbedPage from "components/TabbedPage";
|
||||
import TopAccountsCard from "components/TopAccountsCard";
|
||||
import SupplyCard from "components/SupplyCard";
|
||||
import StatsCard from "components/StatsCard";
|
||||
import { pickCluster } from "utils/url";
|
||||
import Banner from "components/Banner";
|
||||
|
||||
|
@ -84,11 +85,11 @@ function App() {
|
|||
<AccountsCard />
|
||||
</TabbedPage>
|
||||
</Route>
|
||||
<Route
|
||||
render={({ location }) => (
|
||||
<Redirect to={{ ...location, pathname: "/transactions" }} />
|
||||
)}
|
||||
></Route>
|
||||
<Route>
|
||||
<TabbedPage tab="Stats">
|
||||
<StatsCard />
|
||||
</TabbedPage>
|
||||
</Route>
|
||||
</Switch>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
@ -0,0 +1,131 @@
|
|||
import React from "react";
|
||||
import CountUp from "react-countup";
|
||||
|
||||
import TableCardBody from "./common/TableCardBody";
|
||||
import {
|
||||
useDashboardInfo,
|
||||
usePerformanceInfo,
|
||||
useRootSlot,
|
||||
PERF_UPDATE_SEC,
|
||||
useSetActive,
|
||||
} from "providers/stats/solanaBeach";
|
||||
import { slotsToHumanString } from "utils";
|
||||
import { useCluster, Cluster } from "providers/cluster";
|
||||
|
||||
export default function StatsCard() {
|
||||
return (
|
||||
<div className="card">
|
||||
<div className="card-header">
|
||||
<div className="row align-items-center">
|
||||
<div className="col">
|
||||
<h4 className="card-header-title">Live Cluster Info</h4>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<StatsCardBody />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StatsCardBody() {
|
||||
const rootSlot = useRootSlot();
|
||||
const dashboardInfo = useDashboardInfo();
|
||||
const performanceInfo = usePerformanceInfo();
|
||||
const txTrackerRef = React.useRef({ old: 0, new: 0 });
|
||||
const txTracker = txTrackerRef.current;
|
||||
const setSocketActive = useSetActive();
|
||||
const { cluster } = useCluster();
|
||||
|
||||
React.useEffect(() => {
|
||||
setSocketActive(true);
|
||||
return () => setSocketActive(false);
|
||||
}, [setSocketActive, cluster]);
|
||||
|
||||
const statsAvailable =
|
||||
cluster === Cluster.MainnetBeta || cluster === Cluster.Testnet;
|
||||
if (!statsAvailable) {
|
||||
return (
|
||||
<div className="card-body text-center">
|
||||
<div className="text-muted">
|
||||
Stats are not available for this cluster
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (performanceInfo) {
|
||||
const { totalTransactionCount: txCount, avgTPS } = performanceInfo;
|
||||
|
||||
// Track last tx count to initialize count up
|
||||
if (txCount !== txTracker.new) {
|
||||
// If this is the first tx count value, estimate the previous one
|
||||
// in order to have a starting point for our animation
|
||||
txTracker.old = txTracker.new || txCount - PERF_UPDATE_SEC * avgTPS;
|
||||
txTracker.new = txCount;
|
||||
}
|
||||
} else {
|
||||
txTrackerRef.current = { old: 0, new: 0 };
|
||||
}
|
||||
|
||||
if (rootSlot === undefined || !dashboardInfo || !performanceInfo) {
|
||||
return (
|
||||
<div className="card-body text-center">
|
||||
<span className="spinner-grow spinner-grow-sm mr-2"></span>
|
||||
Loading
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const currentBlock = rootSlot.toLocaleString("en-US");
|
||||
const { avgBlockTime_1min, epochInfo } = dashboardInfo;
|
||||
const averageBlockTime = Math.round(1000 * avgBlockTime_1min) + "ms";
|
||||
const { slotIndex, slotsInEpoch } = epochInfo;
|
||||
const currentEpoch = epochInfo.epoch.toString();
|
||||
const epochProgress = ((100 * slotIndex) / slotsInEpoch).toFixed(1) + "%";
|
||||
const epochTimeRemaining = slotsToHumanString(slotsInEpoch - slotIndex);
|
||||
const transactionCount = (
|
||||
<CountUp
|
||||
start={txTracker.old}
|
||||
end={txTracker.new}
|
||||
duration={PERF_UPDATE_SEC + 2}
|
||||
delay={0}
|
||||
useEasing={false}
|
||||
preserveValue={true}
|
||||
separator=","
|
||||
/>
|
||||
);
|
||||
const averageTps = Math.round(performanceInfo.avgTPS);
|
||||
|
||||
return (
|
||||
<TableCardBody>
|
||||
<tr>
|
||||
<td className="w-100">Block</td>
|
||||
<td className="text-right text-monospace">{currentBlock}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="w-100">Block time</td>
|
||||
<td className="text-right text-monospace">{averageBlockTime}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="w-100">Epoch</td>
|
||||
<td className="text-right text-monospace">{currentEpoch} </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="w-100">Epoch progress</td>
|
||||
<td className="text-right text-monospace">{epochProgress} </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="w-100">Epoch time remaining</td>
|
||||
<td className="text-right text-monospace">{epochTimeRemaining} </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="w-100">Transaction count</td>
|
||||
<td className="text-right text-monospace">{transactionCount} </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="w-100">Transactions per second</td>
|
||||
<td className="text-right text-monospace">{averageTps} </td>
|
||||
</tr>
|
||||
</TableCardBody>
|
||||
);
|
||||
}
|
|
@ -4,7 +4,7 @@ import { useClusterModal } from "providers/cluster";
|
|||
import ClusterStatusButton from "components/ClusterStatusButton";
|
||||
import { pickCluster } from "utils/url";
|
||||
|
||||
export type Tab = "Transactions" | "Accounts" | "Supply";
|
||||
export type Tab = "Transactions" | "Accounts" | "Supply" | "Stats";
|
||||
|
||||
type Props = { children: React.ReactNode; tab: Tab };
|
||||
export default function TabbedPage({ children, tab }: Props) {
|
||||
|
@ -22,6 +22,9 @@ export default function TabbedPage({ children, tab }: Props) {
|
|||
<div className="row align-items-center">
|
||||
<div className="col">
|
||||
<ul className="nav nav-tabs nav-overflow header-tabs">
|
||||
<li className="nav-item">
|
||||
<NavLink href="/" tab="Stats" current={tab} />
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink
|
||||
href="/transactions"
|
||||
|
|
|
@ -9,10 +9,12 @@ import { RichListProvider } from "./providers/richList";
|
|||
import { SupplyProvider } from "./providers/supply";
|
||||
import { TransactionsProvider } from "./providers/transactions";
|
||||
import { AccountsProvider } from "./providers/accounts";
|
||||
import { StatsProvider } from "providers/stats";
|
||||
|
||||
ReactDOM.render(
|
||||
<Router>
|
||||
<ClusterProvider>
|
||||
<StatsProvider>
|
||||
<SupplyProvider>
|
||||
<RichListProvider>
|
||||
<AccountsProvider>
|
||||
|
@ -22,6 +24,7 @@ ReactDOM.render(
|
|||
</AccountsProvider>
|
||||
</RichListProvider>
|
||||
</SupplyProvider>
|
||||
</StatsProvider>
|
||||
</ClusterProvider>
|
||||
</Router>,
|
||||
document.getElementById("root")
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import React from "react";
|
||||
import { SolanaBeachProvider } from "./solanaBeach";
|
||||
|
||||
type Props = { children: React.ReactNode };
|
||||
export function StatsProvider({ children }: Props) {
|
||||
return <SolanaBeachProvider>{children}</SolanaBeachProvider>;
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
import React from "react";
|
||||
import io from "socket.io-client";
|
||||
|
||||
import {
|
||||
object,
|
||||
number,
|
||||
is,
|
||||
StructType,
|
||||
array,
|
||||
nullable,
|
||||
any,
|
||||
} from "superstruct";
|
||||
import { useCluster, Cluster } from "providers/cluster";
|
||||
|
||||
// TODO: use `partial` when it is fixed
|
||||
// https://github.com/ianstormtaylor/superstruct/issues/405
|
||||
const DashboardInfo = object({
|
||||
activatedStake: number(),
|
||||
avgBlockTime_1h: number(),
|
||||
avgBlockTime_1min: number(),
|
||||
circulatingSupply: number(),
|
||||
dailyPriceChange: number(),
|
||||
dailyVolume: number(),
|
||||
delinquentStake: number(),
|
||||
epochInfo: object({
|
||||
absoluteEpochStartSlot: number(),
|
||||
absoluteSlot: number(),
|
||||
blockHeight: number(),
|
||||
epoch: number(),
|
||||
slotIndex: number(),
|
||||
slotsInEpoch: number(),
|
||||
}),
|
||||
stakingYield: number(),
|
||||
tokenPrice: number(),
|
||||
totalDelegatedStake: number(),
|
||||
totalSupply: number(),
|
||||
});
|
||||
|
||||
// TODO: use `partial` when it is fixed
|
||||
// https://github.com/ianstormtaylor/superstruct/issues/405
|
||||
const RootInfo = object({
|
||||
currentLeader: any(),
|
||||
nextLeaders: any(),
|
||||
root: number(),
|
||||
servedSlots: any(),
|
||||
});
|
||||
|
||||
export const PERF_UPDATE_SEC = 5;
|
||||
|
||||
// TODO: use `partial` when it is fixed
|
||||
// https://github.com/ianstormtaylor/superstruct/issues/405
|
||||
const PerformanceInfo = object({
|
||||
avgTPS: number(),
|
||||
perfHistory: object({
|
||||
s: array(nullable(number())),
|
||||
m: array(nullable(number())),
|
||||
l: array(nullable(number())),
|
||||
}),
|
||||
totalTransactionCount: number(),
|
||||
});
|
||||
|
||||
type SetActive = React.Dispatch<React.SetStateAction<boolean>>;
|
||||
const SetActiveContext = React.createContext<
|
||||
{ setActive: SetActive } | undefined
|
||||
>(undefined);
|
||||
|
||||
type RootInfo = StructType<typeof RootInfo>;
|
||||
type RootState = { slot: number | undefined };
|
||||
const RootContext = React.createContext<RootState | undefined>(undefined);
|
||||
|
||||
type DashboardInfo = StructType<typeof DashboardInfo>;
|
||||
type DashboardState = { info: DashboardInfo | undefined };
|
||||
const DashboardContext = React.createContext<DashboardState | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
type PerformanceInfo = StructType<typeof PerformanceInfo>;
|
||||
type PerformanceState = { info: PerformanceInfo | undefined };
|
||||
const PerformanceContext = React.createContext<PerformanceState | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
const MAINNET_URL = "https://api.solanabeach.io:8443/mainnet";
|
||||
const TESTNET_URL = "https://api.solanabeach.io:8443/tds";
|
||||
|
||||
type Props = { children: React.ReactNode };
|
||||
export function SolanaBeachProvider({ children }: Props) {
|
||||
const { cluster } = useCluster();
|
||||
const [active, setActive] = React.useState(false);
|
||||
const [root, setRoot] = React.useState<number>();
|
||||
const [dashboardInfo, setDashboardInfo] = React.useState<DashboardInfo>();
|
||||
const [performanceInfo, setPerformanceInfo] = React.useState<
|
||||
PerformanceInfo
|
||||
>();
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!active) return;
|
||||
|
||||
let socket: SocketIOClient.Socket;
|
||||
if (cluster === Cluster.MainnetBeta) {
|
||||
socket = io(MAINNET_URL);
|
||||
} else if (cluster === Cluster.Testnet) {
|
||||
socket = io(TESTNET_URL);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
socket.on("connect", () => {
|
||||
socket.emit("request_dashboardInfo");
|
||||
socket.emit("request_performanceInfo");
|
||||
});
|
||||
socket.on("error", (err: any) => {
|
||||
console.error(err);
|
||||
});
|
||||
socket.on("dashboardInfo", (data: any) => {
|
||||
if (is(data, DashboardInfo)) {
|
||||
setDashboardInfo(data);
|
||||
}
|
||||
});
|
||||
socket.on("performanceInfo", (data: any) => {
|
||||
if (is(data, PerformanceInfo)) {
|
||||
setPerformanceInfo(data);
|
||||
}
|
||||
});
|
||||
socket.on("rootNotification", (data: any) => {
|
||||
if (is(data, RootInfo)) {
|
||||
setRoot(data.root);
|
||||
}
|
||||
});
|
||||
return () => {
|
||||
socket.disconnect();
|
||||
};
|
||||
}, [active, cluster]);
|
||||
|
||||
// Reset info whenever the cluster changes
|
||||
React.useEffect(() => {
|
||||
return () => {
|
||||
setDashboardInfo(undefined);
|
||||
setPerformanceInfo(undefined);
|
||||
setRoot(undefined);
|
||||
};
|
||||
}, [cluster]);
|
||||
|
||||
return (
|
||||
<SetActiveContext.Provider value={{ setActive }}>
|
||||
<DashboardContext.Provider value={{ info: dashboardInfo }}>
|
||||
<PerformanceContext.Provider value={{ info: performanceInfo }}>
|
||||
<RootContext.Provider value={{ slot: root }}>
|
||||
{children}
|
||||
</RootContext.Provider>
|
||||
</PerformanceContext.Provider>
|
||||
</DashboardContext.Provider>
|
||||
</SetActiveContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useSetActive() {
|
||||
const context = React.useContext(SetActiveContext);
|
||||
if (!context) {
|
||||
throw new Error(`useSetActive must be used within a StatsProvider`);
|
||||
}
|
||||
return context.setActive;
|
||||
}
|
||||
|
||||
export function useDashboardInfo() {
|
||||
const context = React.useContext(DashboardContext);
|
||||
if (!context) {
|
||||
throw new Error(`useDashboardInfo must be used within a StatsProvider`);
|
||||
}
|
||||
return context.info;
|
||||
}
|
||||
|
||||
export function usePerformanceInfo() {
|
||||
const context = React.useContext(PerformanceContext);
|
||||
if (!context) {
|
||||
throw new Error(`usePerformanceInfo must be used within a StatsProvider`);
|
||||
}
|
||||
return context.info;
|
||||
}
|
||||
|
||||
export function useRootSlot() {
|
||||
const context = React.useContext(RootContext);
|
||||
if (!context) {
|
||||
throw new Error(`useRootSlot must be used within a StatsProvider`);
|
||||
}
|
||||
return context.slot;
|
||||
}
|
|
@ -16,7 +16,7 @@ $path-to-fonts: "../../fonts" !default;
|
|||
$white: #ffffff;
|
||||
$gray-100: #f9fdfc;
|
||||
$gray-200: #f1f8f6;
|
||||
$gray-300: #d9efe7;
|
||||
$gray-300: #e5ebe9;
|
||||
$gray-400: #c6e6de;
|
||||
$gray-500: #abd5c6;
|
||||
$gray-600: #86b8b6;
|
||||
|
@ -42,6 +42,7 @@ $danger: #43b5c5;
|
|||
$light: $gray-100;
|
||||
$dark: $gray-900;
|
||||
|
||||
$card-border-color: $gray-300;
|
||||
$text-info-muted: $info-muted;
|
||||
$navbar-light-active-color: $primary;
|
||||
$theme-colors: (
|
||||
|
|
|
@ -1,4 +1,14 @@
|
|||
import { LAMPORTS_PER_SOL } from "@solana/web3.js";
|
||||
import {
|
||||
HumanizeDuration,
|
||||
HumanizeDurationLanguage,
|
||||
} from "humanize-duration-ts";
|
||||
|
||||
export const NUM_TICKS_PER_SECOND = 160;
|
||||
export const DEFAULT_TICKS_PER_SLOT = 64;
|
||||
export const NUM_SLOTS_PER_SECOND =
|
||||
NUM_TICKS_PER_SECOND / DEFAULT_TICKS_PER_SLOT;
|
||||
export const MS_PER_SLOT = 1000 / NUM_SLOTS_PER_SECOND;
|
||||
|
||||
export function assertUnreachable(x: never): never {
|
||||
throw new Error("Unreachable!");
|
||||
|
@ -13,3 +23,28 @@ export function lamportsToSolString(
|
|||
"◎" + new Intl.NumberFormat("en-US", { maximumFractionDigits }).format(sol)
|
||||
);
|
||||
}
|
||||
|
||||
const HUMANIZER = new HumanizeDuration(new HumanizeDurationLanguage());
|
||||
HUMANIZER.setOptions({
|
||||
language: "short",
|
||||
spacer: "",
|
||||
delimiter: " ",
|
||||
round: true,
|
||||
units: ["d", "h", "m", "s"],
|
||||
largest: 3,
|
||||
});
|
||||
HUMANIZER.addLanguage("short", {
|
||||
y: () => "y",
|
||||
mo: () => "mo",
|
||||
w: () => "w",
|
||||
d: () => "d",
|
||||
h: () => "h",
|
||||
m: () => "m",
|
||||
s: () => "s",
|
||||
ms: () => "ms",
|
||||
decimal: ".",
|
||||
});
|
||||
|
||||
export function slotsToHumanString(slots: number): string {
|
||||
return HUMANIZER.humanize(slots * MS_PER_SLOT);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue