├── .env.template
├── .eslintignore
├── .eslintrc.js
├── .github
└── workflows
│ ├── actions.yml
│ └── cloudflare-deploy.yml
├── .gitignore
├── .prettierrc.js
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── copyright.txt
├── craco.config.js
├── graphql-schema.json
├── license-check-and-add-config.json
├── package.json
├── public
├── icon.png
├── index.html
├── manifest.json
└── robots.txt
├── src
├── App.tsx
├── Components
│ ├── Elements
│ │ ├── Icons
│ │ │ └── ToolTipIcon.tsx
│ │ ├── Pagination.tsx
│ │ └── StatTitles.tsx
│ ├── Layouts
│ │ ├── BodyLayout.tsx
│ │ ├── GridLayout
│ │ │ └── GridLayoutWrapper.tsx
│ │ └── SectionTile
│ │ │ ├── CardStat.tsx
│ │ │ ├── SectionBody.tsx
│ │ │ ├── SectionCard.tsx
│ │ │ └── SectionTile.tsx
│ ├── Modules
│ │ ├── CountryStats
│ │ │ ├── CountryBox.tsx
│ │ │ ├── StatsChartBox.tsx
│ │ │ └── index.tsx
│ │ ├── DemographicsStats
│ │ │ ├── ClientTypes.tsx
│ │ │ ├── NodeCount12.tsx
│ │ │ ├── NodeReadyForFork.tsx
│ │ │ └── StatusSync.tsx
│ │ ├── Footer.tsx
│ │ ├── HeatMap
│ │ │ └── MapLeaflet.tsx
│ │ ├── Navbar.tsx
│ │ ├── NodeStats
│ │ │ └── NodeStatsOverTime.tsx
│ │ └── SoftwareStats
│ │ │ ├── AltAirPercentage.tsx
│ │ │ ├── NetworkTypes.tsx
│ │ │ ├── OperatingSystems.tsx
│ │ │ ├── PercentageOfNodes.tsx
│ │ │ └── VersionVariance.tsx
│ ├── Pages
│ │ └── HomePage.tsx
│ └── Themes
│ │ ├── constants.ts
│ │ ├── theme.ts
│ │ └── types.ts
├── Contexts
│ └── Eth2CrawlerContext.tsx
├── GraphQL
│ ├── Queries.ts
│ └── types
│ │ ├── GetAltAirUpgradePercentage.ts
│ │ ├── GetClientCounts.ts
│ │ ├── GetClientVersions.ts
│ │ ├── GetHeatmap.ts
│ │ ├── GetNetworks.ts
│ │ ├── GetNodeStats.ts
│ │ ├── GetNodeStatsOverTime.ts
│ │ ├── GetNodesByCountries.ts
│ │ ├── GetOperatingSystems.ts
│ │ └── getRegionalStats.ts
├── assets
│ └── fonts
│ │ └── Neue-montreal
│ │ ├── NeueMontreal-Bold.otf
│ │ ├── NeueMontreal-BoldItalic.otf
│ │ ├── NeueMontreal-Italic.otf
│ │ ├── NeueMontreal-Light.otf
│ │ ├── NeueMontreal-LightItalic.otf
│ │ ├── NeueMontreal-Medium.otf
│ │ ├── NeueMontreal-MediumItalic.otf
│ │ └── NeueMontreal-Regular.otf
├── dummyData
│ ├── demographicsData.ts
│ └── mapData.ts
├── index.css
├── index.tsx
├── react-app-env.d.ts
├── reportWebVitals.ts
├── setupTests.ts
├── types
│ ├── graphql-global-types.ts
│ ├── index.d.ts
│ └── main.ts
├── utilHooks
│ └── useWindowDimensions.ts
└── utils
│ └── dateUtils.ts
├── tsconfig.json
└── yarn.lock
/.env.template:
--------------------------------------------------------------------------------
1 | REACT_APP_GRAPHQL_URL=http://localhost:6969/graphql
2 |
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # Copyright 2021 ChainSafe Systems
2 | # SPDX-License-Identifier: LGPL-3.0-only
3 | .eslintrc.js
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | module.exports = {
6 | env: {
7 | browser: true,
8 | es6: true,
9 | },
10 | extends: [
11 | "eslint:recommended",
12 | "plugin:react/recommended",
13 | "plugin:@typescript-eslint/eslint-recommended",
14 | "prettier",
15 | "plugin:react-hooks/recommended",
16 | ],
17 | globals: {
18 | Atomics: "readonly",
19 | SharedArrayBuffer: "readonly",
20 | },
21 | parser: "@typescript-eslint/parser",
22 | parserOptions: {
23 | ecmaFeatures: {
24 | jsx: true,
25 | },
26 | ecmaVersion: 2018,
27 | sourceType: "module",
28 | },
29 | settings: {
30 | react: {
31 | version: "detect",
32 | },
33 | },
34 | plugins: ["react", "@typescript-eslint", "prettier"],
35 | rules: {
36 | "linebreak-style": ["error", "unix"],
37 | "react/prop-types": 0,
38 | "no-unused-vars": "warn",
39 | "no-console": ["warn", { allow: ["warn", "error"] }],
40 | },
41 | }
42 |
--------------------------------------------------------------------------------
/.github/workflows/actions.yml:
--------------------------------------------------------------------------------
1 | name: React.TS install, lint, build, License
2 |
3 | on: [pull_request]
4 |
5 | jobs:
6 | build:
7 | runs-on: ubuntu-18.04
8 |
9 | strategy:
10 | matrix:
11 | node-version: [14.4.0]
12 |
13 | steps:
14 | - uses: actions/checkout@v2
15 | - name: Use Node.js ${{ matrix.node-version }}
16 | uses: actions/setup-node@v1
17 | with:
18 | node-version: ${{ matrix.node-version }}
19 | - name: yarn install, lint, build
20 | run: |
21 | yarn install --frozen-lockfile
22 | yarn run lint
23 | yarn run license-check
24 | yarn run build --if-present
25 | # yarn test
26 | env:
27 | CI: true
28 |
--------------------------------------------------------------------------------
/.github/workflows/cloudflare-deploy.yml:
--------------------------------------------------------------------------------
1 | name: CloudFlare Deploy
2 | on: [push]
3 |
4 | jobs:
5 | deploy:
6 | runs-on: ubuntu-18.04
7 | permissions:
8 | contents: read
9 | deployments: write
10 | steps:
11 | - uses: actions/checkout@v3
12 | - uses: actions/setup-node@v3
13 | with:
14 | cache: yarn
15 | node-version: '14.4.0'
16 | - run: yarn install --frozen-lockfile
17 | - run: yarn run build
18 | - uses: actions/setup-node@v3
19 | with:
20 | node-version: '16'
21 | - name: Publish to Cloudflare Pages
22 | uses: cloudflare/pages-action@1
23 | with:
24 | apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
25 | accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
26 | projectName: nodewatch
27 | directory: ./build
28 | gitHubToken: ${{ secrets.GITHUB_TOKEN }}
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
26 | .vscode
27 | .env
28 | .idea/
29 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | module.exports = {
6 | trailingComma: "es5",
7 | semi: false,
8 | singleQuote: false,
9 | printWidth: 100,
10 | }
11 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "cSpell.words": [
3 | "chartjs",
4 | "clsx",
5 | "craco",
6 | "getc",
7 | "HEATMAP",
8 | "multigeth",
9 | "nethermind",
10 | "nonhosted",
11 | "unsynced"
12 | ]
13 | }
14 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NodeWatch Frontend (UI)
2 |
3 | A web-based user interface will be developed to display the data collected by a [devp2p crawler](https://github.com/ChainSafe/eth2-crawler) targeted at Eth2 nodes. It will contain the following:
4 |
5 | - Client breakdown by agent type
6 | - Toggle connectable vs all seen in last month
7 | - Client-version breakdown
8 | - Regional breakdown with map
9 | - IP type where possible – hosted, residential, etc
10 |
11 | # Running the project
12 |
13 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app) and using [Craco](https://github.com/gsoft-inc/craco) for configuration.
14 |
15 | Provide the crawler graphql endpoint in a `.env` file in root as per `.env.template`.
16 |
17 | To run in development mode
18 |
19 | ```
20 | yarn install
21 | yarn start
22 | ```
23 |
24 | To build
25 |
26 | ```
27 | yarn install
28 | yarn build
29 | ```
30 |
31 | # LICENSE
32 |
33 | See the [LICENSE](https://github.com/ChainSafe/eth2-crawler-ui/blob/main/LICENSE) file for license rights and limitations (lgpl-3.0).
34 |
--------------------------------------------------------------------------------
/copyright.txt:
--------------------------------------------------------------------------------
1 | Copyright 2021 ChainSafe Systems
2 | SPDX-License-Identifier: LGPL-3.0-only
3 |
--------------------------------------------------------------------------------
/craco.config.js:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | /* eslint-disable no-undef */
6 | const path = require("path")
7 | const fs = require("fs")
8 |
9 | const cracoBabelLoader = require("craco-babel-loader")
10 |
11 | const appDirectory = fs.realpathSync(process.cwd())
12 | const resolveApp = (relativePath) => path.resolve(appDirectory, relativePath)
13 |
14 | // react-leaflet requires custom inclusion through babel
15 | module.exports = {
16 | plugins: [
17 | {
18 | plugin: cracoBabelLoader,
19 | options: {
20 | includes: [resolveApp("node_modules/@react-leaflet"), resolveApp("node_modules/react-leaflet")],
21 | },
22 | },
23 | ],
24 | }
25 |
--------------------------------------------------------------------------------
/graphql-schema.json:
--------------------------------------------------------------------------------
1 | {
2 | "__schema": {
3 | "description": null,
4 | "queryType": {
5 | "name": "Query"
6 | },
7 | "mutationType": null,
8 | "subscriptionType": null,
9 | "types": [
10 | {
11 | "kind": "OBJECT",
12 | "name": "AggregateData",
13 | "description": "",
14 | "specifiedByUrl": null,
15 | "fields": [
16 | {
17 | "name": "name",
18 | "description": "",
19 | "args": [],
20 | "type": {
21 | "kind": "NON_NULL",
22 | "name": null,
23 | "ofType": {
24 | "kind": "SCALAR",
25 | "name": "String",
26 | "ofType": null
27 | }
28 | },
29 | "isDeprecated": false,
30 | "deprecationReason": null
31 | },
32 | {
33 | "name": "count",
34 | "description": "",
35 | "args": [],
36 | "type": {
37 | "kind": "NON_NULL",
38 | "name": null,
39 | "ofType": {
40 | "kind": "SCALAR",
41 | "name": "Int",
42 | "ofType": null
43 | }
44 | },
45 | "isDeprecated": false,
46 | "deprecationReason": null
47 | }
48 | ],
49 | "inputFields": null,
50 | "interfaces": [],
51 | "enumValues": null,
52 | "possibleTypes": null
53 | },
54 | {
55 | "kind": "SCALAR",
56 | "name": "Boolean",
57 | "description": "The `Boolean` scalar type represents `true` or `false`.",
58 | "specifiedByUrl": null,
59 | "fields": null,
60 | "inputFields": null,
61 | "interfaces": null,
62 | "enumValues": null,
63 | "possibleTypes": null
64 | },
65 | {
66 | "kind": "OBJECT",
67 | "name": "ClientVersionAggregation",
68 | "description": "",
69 | "specifiedByUrl": null,
70 | "fields": [
71 | {
72 | "name": "client",
73 | "description": "",
74 | "args": [],
75 | "type": {
76 | "kind": "NON_NULL",
77 | "name": null,
78 | "ofType": {
79 | "kind": "SCALAR",
80 | "name": "String",
81 | "ofType": null
82 | }
83 | },
84 | "isDeprecated": false,
85 | "deprecationReason": null
86 | },
87 | {
88 | "name": "count",
89 | "description": "",
90 | "args": [],
91 | "type": {
92 | "kind": "NON_NULL",
93 | "name": null,
94 | "ofType": {
95 | "kind": "SCALAR",
96 | "name": "Int",
97 | "ofType": null
98 | }
99 | },
100 | "isDeprecated": false,
101 | "deprecationReason": null
102 | },
103 | {
104 | "name": "versions",
105 | "description": "",
106 | "args": [],
107 | "type": {
108 | "kind": "NON_NULL",
109 | "name": null,
110 | "ofType": {
111 | "kind": "LIST",
112 | "name": null,
113 | "ofType": {
114 | "kind": "NON_NULL",
115 | "name": null,
116 | "ofType": {
117 | "kind": "OBJECT",
118 | "name": "AggregateData",
119 | "ofType": null
120 | }
121 | }
122 | }
123 | },
124 | "isDeprecated": false,
125 | "deprecationReason": null
126 | }
127 | ],
128 | "inputFields": null,
129 | "interfaces": [],
130 | "enumValues": null,
131 | "possibleTypes": null
132 | },
133 | {
134 | "kind": "SCALAR",
135 | "name": "Float",
136 | "description": "The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](https://en.wikipedia.org/wiki/IEEE_floating_point).",
137 | "specifiedByUrl": null,
138 | "fields": null,
139 | "inputFields": null,
140 | "interfaces": null,
141 | "enumValues": null,
142 | "possibleTypes": null
143 | },
144 | {
145 | "kind": "OBJECT",
146 | "name": "HeatmapData",
147 | "description": "",
148 | "specifiedByUrl": null,
149 | "fields": [
150 | {
151 | "name": "networkType",
152 | "description": "",
153 | "args": [],
154 | "type": {
155 | "kind": "NON_NULL",
156 | "name": null,
157 | "ofType": {
158 | "kind": "SCALAR",
159 | "name": "String",
160 | "ofType": null
161 | }
162 | },
163 | "isDeprecated": false,
164 | "deprecationReason": null
165 | },
166 | {
167 | "name": "clientType",
168 | "description": "",
169 | "args": [],
170 | "type": {
171 | "kind": "NON_NULL",
172 | "name": null,
173 | "ofType": {
174 | "kind": "SCALAR",
175 | "name": "String",
176 | "ofType": null
177 | }
178 | },
179 | "isDeprecated": false,
180 | "deprecationReason": null
181 | },
182 | {
183 | "name": "syncStatus",
184 | "description": "",
185 | "args": [],
186 | "type": {
187 | "kind": "NON_NULL",
188 | "name": null,
189 | "ofType": {
190 | "kind": "SCALAR",
191 | "name": "String",
192 | "ofType": null
193 | }
194 | },
195 | "isDeprecated": false,
196 | "deprecationReason": null
197 | },
198 | {
199 | "name": "latitude",
200 | "description": "",
201 | "args": [],
202 | "type": {
203 | "kind": "NON_NULL",
204 | "name": null,
205 | "ofType": {
206 | "kind": "SCALAR",
207 | "name": "Float",
208 | "ofType": null
209 | }
210 | },
211 | "isDeprecated": false,
212 | "deprecationReason": null
213 | },
214 | {
215 | "name": "longitude",
216 | "description": "",
217 | "args": [],
218 | "type": {
219 | "kind": "NON_NULL",
220 | "name": null,
221 | "ofType": {
222 | "kind": "SCALAR",
223 | "name": "Float",
224 | "ofType": null
225 | }
226 | },
227 | "isDeprecated": false,
228 | "deprecationReason": null
229 | },
230 | {
231 | "name": "city",
232 | "description": "",
233 | "args": [],
234 | "type": {
235 | "kind": "NON_NULL",
236 | "name": null,
237 | "ofType": {
238 | "kind": "SCALAR",
239 | "name": "String",
240 | "ofType": null
241 | }
242 | },
243 | "isDeprecated": false,
244 | "deprecationReason": null
245 | },
246 | {
247 | "name": "country",
248 | "description": "",
249 | "args": [],
250 | "type": {
251 | "kind": "NON_NULL",
252 | "name": null,
253 | "ofType": {
254 | "kind": "SCALAR",
255 | "name": "String",
256 | "ofType": null
257 | }
258 | },
259 | "isDeprecated": false,
260 | "deprecationReason": null
261 | }
262 | ],
263 | "inputFields": null,
264 | "interfaces": [],
265 | "enumValues": null,
266 | "possibleTypes": null
267 | },
268 | {
269 | "kind": "SCALAR",
270 | "name": "ID",
271 | "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.",
272 | "specifiedByUrl": null,
273 | "fields": null,
274 | "inputFields": null,
275 | "interfaces": null,
276 | "enumValues": null,
277 | "possibleTypes": null
278 | },
279 | {
280 | "kind": "SCALAR",
281 | "name": "Int",
282 | "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.",
283 | "specifiedByUrl": null,
284 | "fields": null,
285 | "inputFields": null,
286 | "interfaces": null,
287 | "enumValues": null,
288 | "possibleTypes": null
289 | },
290 | {
291 | "kind": "OBJECT",
292 | "name": "NodeStats",
293 | "description": "",
294 | "specifiedByUrl": null,
295 | "fields": [
296 | {
297 | "name": "totalNodes",
298 | "description": "",
299 | "args": [],
300 | "type": {
301 | "kind": "NON_NULL",
302 | "name": null,
303 | "ofType": {
304 | "kind": "SCALAR",
305 | "name": "Int",
306 | "ofType": null
307 | }
308 | },
309 | "isDeprecated": false,
310 | "deprecationReason": null
311 | },
312 | {
313 | "name": "nodeSyncedPercentage",
314 | "description": "",
315 | "args": [],
316 | "type": {
317 | "kind": "NON_NULL",
318 | "name": null,
319 | "ofType": {
320 | "kind": "SCALAR",
321 | "name": "Float",
322 | "ofType": null
323 | }
324 | },
325 | "isDeprecated": false,
326 | "deprecationReason": null
327 | },
328 | {
329 | "name": "nodeUnsyncedPercentage",
330 | "description": "",
331 | "args": [],
332 | "type": {
333 | "kind": "NON_NULL",
334 | "name": null,
335 | "ofType": {
336 | "kind": "SCALAR",
337 | "name": "Float",
338 | "ofType": null
339 | }
340 | },
341 | "isDeprecated": false,
342 | "deprecationReason": null
343 | }
344 | ],
345 | "inputFields": null,
346 | "interfaces": [],
347 | "enumValues": null,
348 | "possibleTypes": null
349 | },
350 | {
351 | "kind": "OBJECT",
352 | "name": "NodeStatsOverTime",
353 | "description": "",
354 | "specifiedByUrl": null,
355 | "fields": [
356 | {
357 | "name": "time",
358 | "description": "",
359 | "args": [],
360 | "type": {
361 | "kind": "NON_NULL",
362 | "name": null,
363 | "ofType": {
364 | "kind": "SCALAR",
365 | "name": "Float",
366 | "ofType": null
367 | }
368 | },
369 | "isDeprecated": false,
370 | "deprecationReason": null
371 | },
372 | {
373 | "name": "totalNodes",
374 | "description": "",
375 | "args": [],
376 | "type": {
377 | "kind": "NON_NULL",
378 | "name": null,
379 | "ofType": {
380 | "kind": "SCALAR",
381 | "name": "Int",
382 | "ofType": null
383 | }
384 | },
385 | "isDeprecated": false,
386 | "deprecationReason": null
387 | },
388 | {
389 | "name": "syncedNodes",
390 | "description": "",
391 | "args": [],
392 | "type": {
393 | "kind": "NON_NULL",
394 | "name": null,
395 | "ofType": {
396 | "kind": "SCALAR",
397 | "name": "Int",
398 | "ofType": null
399 | }
400 | },
401 | "isDeprecated": false,
402 | "deprecationReason": null
403 | },
404 | {
405 | "name": "unsyncedNodes",
406 | "description": "",
407 | "args": [],
408 | "type": {
409 | "kind": "NON_NULL",
410 | "name": null,
411 | "ofType": {
412 | "kind": "SCALAR",
413 | "name": "Int",
414 | "ofType": null
415 | }
416 | },
417 | "isDeprecated": false,
418 | "deprecationReason": null
419 | }
420 | ],
421 | "inputFields": null,
422 | "interfaces": [],
423 | "enumValues": null,
424 | "possibleTypes": null
425 | },
426 | {
427 | "kind": "OBJECT",
428 | "name": "Query",
429 | "description": "",
430 | "specifiedByUrl": null,
431 | "fields": [
432 | {
433 | "name": "aggregateByAgentName",
434 | "description": "",
435 | "args": [],
436 | "type": {
437 | "kind": "NON_NULL",
438 | "name": null,
439 | "ofType": {
440 | "kind": "LIST",
441 | "name": null,
442 | "ofType": {
443 | "kind": "NON_NULL",
444 | "name": null,
445 | "ofType": {
446 | "kind": "OBJECT",
447 | "name": "AggregateData",
448 | "ofType": null
449 | }
450 | }
451 | }
452 | },
453 | "isDeprecated": false,
454 | "deprecationReason": null
455 | },
456 | {
457 | "name": "aggregateByCountry",
458 | "description": "",
459 | "args": [],
460 | "type": {
461 | "kind": "NON_NULL",
462 | "name": null,
463 | "ofType": {
464 | "kind": "LIST",
465 | "name": null,
466 | "ofType": {
467 | "kind": "NON_NULL",
468 | "name": null,
469 | "ofType": {
470 | "kind": "OBJECT",
471 | "name": "AggregateData",
472 | "ofType": null
473 | }
474 | }
475 | }
476 | },
477 | "isDeprecated": false,
478 | "deprecationReason": null
479 | },
480 | {
481 | "name": "aggregateByOperatingSystem",
482 | "description": "",
483 | "args": [],
484 | "type": {
485 | "kind": "NON_NULL",
486 | "name": null,
487 | "ofType": {
488 | "kind": "LIST",
489 | "name": null,
490 | "ofType": {
491 | "kind": "NON_NULL",
492 | "name": null,
493 | "ofType": {
494 | "kind": "OBJECT",
495 | "name": "AggregateData",
496 | "ofType": null
497 | }
498 | }
499 | }
500 | },
501 | "isDeprecated": false,
502 | "deprecationReason": null
503 | },
504 | {
505 | "name": "aggregateByNetwork",
506 | "description": "",
507 | "args": [],
508 | "type": {
509 | "kind": "NON_NULL",
510 | "name": null,
511 | "ofType": {
512 | "kind": "LIST",
513 | "name": null,
514 | "ofType": {
515 | "kind": "NON_NULL",
516 | "name": null,
517 | "ofType": {
518 | "kind": "OBJECT",
519 | "name": "AggregateData",
520 | "ofType": null
521 | }
522 | }
523 | }
524 | },
525 | "isDeprecated": false,
526 | "deprecationReason": null
527 | },
528 | {
529 | "name": "aggregateByClientVersion",
530 | "description": "",
531 | "args": [],
532 | "type": {
533 | "kind": "NON_NULL",
534 | "name": null,
535 | "ofType": {
536 | "kind": "LIST",
537 | "name": null,
538 | "ofType": {
539 | "kind": "NON_NULL",
540 | "name": null,
541 | "ofType": {
542 | "kind": "OBJECT",
543 | "name": "ClientVersionAggregation",
544 | "ofType": null
545 | }
546 | }
547 | }
548 | },
549 | "isDeprecated": false,
550 | "deprecationReason": null
551 | },
552 | {
553 | "name": "getHeatmapData",
554 | "description": "",
555 | "args": [],
556 | "type": {
557 | "kind": "NON_NULL",
558 | "name": null,
559 | "ofType": {
560 | "kind": "LIST",
561 | "name": null,
562 | "ofType": {
563 | "kind": "NON_NULL",
564 | "name": null,
565 | "ofType": {
566 | "kind": "OBJECT",
567 | "name": "HeatmapData",
568 | "ofType": null
569 | }
570 | }
571 | }
572 | },
573 | "isDeprecated": false,
574 | "deprecationReason": null
575 | },
576 | {
577 | "name": "getNodeStats",
578 | "description": "",
579 | "args": [],
580 | "type": {
581 | "kind": "NON_NULL",
582 | "name": null,
583 | "ofType": {
584 | "kind": "OBJECT",
585 | "name": "NodeStats",
586 | "ofType": null
587 | }
588 | },
589 | "isDeprecated": false,
590 | "deprecationReason": null
591 | },
592 | {
593 | "name": "getNodeStatsOverTime",
594 | "description": "",
595 | "args": [
596 | {
597 | "name": "start",
598 | "description": "",
599 | "type": {
600 | "kind": "NON_NULL",
601 | "name": null,
602 | "ofType": {
603 | "kind": "SCALAR",
604 | "name": "Float",
605 | "ofType": null
606 | }
607 | },
608 | "defaultValue": null,
609 | "isDeprecated": false,
610 | "deprecationReason": null
611 | },
612 | {
613 | "name": "end",
614 | "description": "",
615 | "type": {
616 | "kind": "NON_NULL",
617 | "name": null,
618 | "ofType": {
619 | "kind": "SCALAR",
620 | "name": "Float",
621 | "ofType": null
622 | }
623 | },
624 | "defaultValue": null,
625 | "isDeprecated": false,
626 | "deprecationReason": null
627 | }
628 | ],
629 | "type": {
630 | "kind": "NON_NULL",
631 | "name": null,
632 | "ofType": {
633 | "kind": "LIST",
634 | "name": null,
635 | "ofType": {
636 | "kind": "NON_NULL",
637 | "name": null,
638 | "ofType": {
639 | "kind": "OBJECT",
640 | "name": "NodeStatsOverTime",
641 | "ofType": null
642 | }
643 | }
644 | }
645 | },
646 | "isDeprecated": false,
647 | "deprecationReason": null
648 | },
649 | {
650 | "name": "getRegionalStats",
651 | "description": "",
652 | "args": [],
653 | "type": {
654 | "kind": "NON_NULL",
655 | "name": null,
656 | "ofType": {
657 | "kind": "OBJECT",
658 | "name": "RegionalStats",
659 | "ofType": null
660 | }
661 | },
662 | "isDeprecated": false,
663 | "deprecationReason": null
664 | },
665 | {
666 | "name": "getAltairUpgradePercentage",
667 | "description": "",
668 | "args": [],
669 | "type": {
670 | "kind": "NON_NULL",
671 | "name": null,
672 | "ofType": {
673 | "kind": "SCALAR",
674 | "name": "Float",
675 | "ofType": null
676 | }
677 | },
678 | "isDeprecated": false,
679 | "deprecationReason": null
680 | }
681 | ],
682 | "inputFields": null,
683 | "interfaces": [],
684 | "enumValues": null,
685 | "possibleTypes": null
686 | },
687 | {
688 | "kind": "OBJECT",
689 | "name": "RegionalStats",
690 | "description": "",
691 | "specifiedByUrl": null,
692 | "fields": [
693 | {
694 | "name": "totalParticipatingCountries",
695 | "description": "",
696 | "args": [],
697 | "type": {
698 | "kind": "NON_NULL",
699 | "name": null,
700 | "ofType": {
701 | "kind": "SCALAR",
702 | "name": "Int",
703 | "ofType": null
704 | }
705 | },
706 | "isDeprecated": false,
707 | "deprecationReason": null
708 | },
709 | {
710 | "name": "hostedNodePercentage",
711 | "description": "",
712 | "args": [],
713 | "type": {
714 | "kind": "NON_NULL",
715 | "name": null,
716 | "ofType": {
717 | "kind": "SCALAR",
718 | "name": "Float",
719 | "ofType": null
720 | }
721 | },
722 | "isDeprecated": false,
723 | "deprecationReason": null
724 | },
725 | {
726 | "name": "nonhostedNodePercentage",
727 | "description": "",
728 | "args": [],
729 | "type": {
730 | "kind": "NON_NULL",
731 | "name": null,
732 | "ofType": {
733 | "kind": "SCALAR",
734 | "name": "Float",
735 | "ofType": null
736 | }
737 | },
738 | "isDeprecated": false,
739 | "deprecationReason": null
740 | }
741 | ],
742 | "inputFields": null,
743 | "interfaces": [],
744 | "enumValues": null,
745 | "possibleTypes": null
746 | },
747 | {
748 | "kind": "SCALAR",
749 | "name": "String",
750 | "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.",
751 | "specifiedByUrl": null,
752 | "fields": null,
753 | "inputFields": null,
754 | "interfaces": null,
755 | "enumValues": null,
756 | "possibleTypes": null
757 | },
758 | {
759 | "kind": "OBJECT",
760 | "name": "__Schema",
761 | "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.",
762 | "specifiedByUrl": null,
763 | "fields": [
764 | {
765 | "name": "description",
766 | "description": null,
767 | "args": [],
768 | "type": {
769 | "kind": "SCALAR",
770 | "name": "String",
771 | "ofType": null
772 | },
773 | "isDeprecated": false,
774 | "deprecationReason": null
775 | },
776 | {
777 | "name": "types",
778 | "description": "A list of all types supported by this server.",
779 | "args": [],
780 | "type": {
781 | "kind": "NON_NULL",
782 | "name": null,
783 | "ofType": {
784 | "kind": "LIST",
785 | "name": null,
786 | "ofType": {
787 | "kind": "NON_NULL",
788 | "name": null,
789 | "ofType": {
790 | "kind": "OBJECT",
791 | "name": "__Type",
792 | "ofType": null
793 | }
794 | }
795 | }
796 | },
797 | "isDeprecated": false,
798 | "deprecationReason": null
799 | },
800 | {
801 | "name": "queryType",
802 | "description": "The type that query operations will be rooted at.",
803 | "args": [],
804 | "type": {
805 | "kind": "NON_NULL",
806 | "name": null,
807 | "ofType": {
808 | "kind": "OBJECT",
809 | "name": "__Type",
810 | "ofType": null
811 | }
812 | },
813 | "isDeprecated": false,
814 | "deprecationReason": null
815 | },
816 | {
817 | "name": "mutationType",
818 | "description": "If this server supports mutation, the type that mutation operations will be rooted at.",
819 | "args": [],
820 | "type": {
821 | "kind": "OBJECT",
822 | "name": "__Type",
823 | "ofType": null
824 | },
825 | "isDeprecated": false,
826 | "deprecationReason": null
827 | },
828 | {
829 | "name": "subscriptionType",
830 | "description": "If this server support subscription, the type that subscription operations will be rooted at.",
831 | "args": [],
832 | "type": {
833 | "kind": "OBJECT",
834 | "name": "__Type",
835 | "ofType": null
836 | },
837 | "isDeprecated": false,
838 | "deprecationReason": null
839 | },
840 | {
841 | "name": "directives",
842 | "description": "A list of all directives supported by this server.",
843 | "args": [],
844 | "type": {
845 | "kind": "NON_NULL",
846 | "name": null,
847 | "ofType": {
848 | "kind": "LIST",
849 | "name": null,
850 | "ofType": {
851 | "kind": "NON_NULL",
852 | "name": null,
853 | "ofType": {
854 | "kind": "OBJECT",
855 | "name": "__Directive",
856 | "ofType": null
857 | }
858 | }
859 | }
860 | },
861 | "isDeprecated": false,
862 | "deprecationReason": null
863 | }
864 | ],
865 | "inputFields": null,
866 | "interfaces": [],
867 | "enumValues": null,
868 | "possibleTypes": null
869 | },
870 | {
871 | "kind": "OBJECT",
872 | "name": "__Type",
873 | "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name, description and optional `specifiedByUrl`, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.",
874 | "specifiedByUrl": null,
875 | "fields": [
876 | {
877 | "name": "kind",
878 | "description": null,
879 | "args": [],
880 | "type": {
881 | "kind": "NON_NULL",
882 | "name": null,
883 | "ofType": {
884 | "kind": "ENUM",
885 | "name": "__TypeKind",
886 | "ofType": null
887 | }
888 | },
889 | "isDeprecated": false,
890 | "deprecationReason": null
891 | },
892 | {
893 | "name": "name",
894 | "description": null,
895 | "args": [],
896 | "type": {
897 | "kind": "SCALAR",
898 | "name": "String",
899 | "ofType": null
900 | },
901 | "isDeprecated": false,
902 | "deprecationReason": null
903 | },
904 | {
905 | "name": "description",
906 | "description": null,
907 | "args": [],
908 | "type": {
909 | "kind": "SCALAR",
910 | "name": "String",
911 | "ofType": null
912 | },
913 | "isDeprecated": false,
914 | "deprecationReason": null
915 | },
916 | {
917 | "name": "specifiedByUrl",
918 | "description": null,
919 | "args": [],
920 | "type": {
921 | "kind": "SCALAR",
922 | "name": "String",
923 | "ofType": null
924 | },
925 | "isDeprecated": false,
926 | "deprecationReason": null
927 | },
928 | {
929 | "name": "fields",
930 | "description": null,
931 | "args": [
932 | {
933 | "name": "includeDeprecated",
934 | "description": null,
935 | "type": {
936 | "kind": "SCALAR",
937 | "name": "Boolean",
938 | "ofType": null
939 | },
940 | "defaultValue": "false",
941 | "isDeprecated": false,
942 | "deprecationReason": null
943 | }
944 | ],
945 | "type": {
946 | "kind": "LIST",
947 | "name": null,
948 | "ofType": {
949 | "kind": "NON_NULL",
950 | "name": null,
951 | "ofType": {
952 | "kind": "OBJECT",
953 | "name": "__Field",
954 | "ofType": null
955 | }
956 | }
957 | },
958 | "isDeprecated": false,
959 | "deprecationReason": null
960 | },
961 | {
962 | "name": "interfaces",
963 | "description": null,
964 | "args": [],
965 | "type": {
966 | "kind": "LIST",
967 | "name": null,
968 | "ofType": {
969 | "kind": "NON_NULL",
970 | "name": null,
971 | "ofType": {
972 | "kind": "OBJECT",
973 | "name": "__Type",
974 | "ofType": null
975 | }
976 | }
977 | },
978 | "isDeprecated": false,
979 | "deprecationReason": null
980 | },
981 | {
982 | "name": "possibleTypes",
983 | "description": null,
984 | "args": [],
985 | "type": {
986 | "kind": "LIST",
987 | "name": null,
988 | "ofType": {
989 | "kind": "NON_NULL",
990 | "name": null,
991 | "ofType": {
992 | "kind": "OBJECT",
993 | "name": "__Type",
994 | "ofType": null
995 | }
996 | }
997 | },
998 | "isDeprecated": false,
999 | "deprecationReason": null
1000 | },
1001 | {
1002 | "name": "enumValues",
1003 | "description": null,
1004 | "args": [
1005 | {
1006 | "name": "includeDeprecated",
1007 | "description": null,
1008 | "type": {
1009 | "kind": "SCALAR",
1010 | "name": "Boolean",
1011 | "ofType": null
1012 | },
1013 | "defaultValue": "false",
1014 | "isDeprecated": false,
1015 | "deprecationReason": null
1016 | }
1017 | ],
1018 | "type": {
1019 | "kind": "LIST",
1020 | "name": null,
1021 | "ofType": {
1022 | "kind": "NON_NULL",
1023 | "name": null,
1024 | "ofType": {
1025 | "kind": "OBJECT",
1026 | "name": "__EnumValue",
1027 | "ofType": null
1028 | }
1029 | }
1030 | },
1031 | "isDeprecated": false,
1032 | "deprecationReason": null
1033 | },
1034 | {
1035 | "name": "inputFields",
1036 | "description": null,
1037 | "args": [
1038 | {
1039 | "name": "includeDeprecated",
1040 | "description": null,
1041 | "type": {
1042 | "kind": "SCALAR",
1043 | "name": "Boolean",
1044 | "ofType": null
1045 | },
1046 | "defaultValue": "false",
1047 | "isDeprecated": false,
1048 | "deprecationReason": null
1049 | }
1050 | ],
1051 | "type": {
1052 | "kind": "LIST",
1053 | "name": null,
1054 | "ofType": {
1055 | "kind": "NON_NULL",
1056 | "name": null,
1057 | "ofType": {
1058 | "kind": "OBJECT",
1059 | "name": "__InputValue",
1060 | "ofType": null
1061 | }
1062 | }
1063 | },
1064 | "isDeprecated": false,
1065 | "deprecationReason": null
1066 | },
1067 | {
1068 | "name": "ofType",
1069 | "description": null,
1070 | "args": [],
1071 | "type": {
1072 | "kind": "OBJECT",
1073 | "name": "__Type",
1074 | "ofType": null
1075 | },
1076 | "isDeprecated": false,
1077 | "deprecationReason": null
1078 | }
1079 | ],
1080 | "inputFields": null,
1081 | "interfaces": [],
1082 | "enumValues": null,
1083 | "possibleTypes": null
1084 | },
1085 | {
1086 | "kind": "ENUM",
1087 | "name": "__TypeKind",
1088 | "description": "An enum describing what kind of type a given `__Type` is.",
1089 | "specifiedByUrl": null,
1090 | "fields": null,
1091 | "inputFields": null,
1092 | "interfaces": null,
1093 | "enumValues": [
1094 | {
1095 | "name": "SCALAR",
1096 | "description": "Indicates this type is a scalar.",
1097 | "isDeprecated": false,
1098 | "deprecationReason": null
1099 | },
1100 | {
1101 | "name": "OBJECT",
1102 | "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.",
1103 | "isDeprecated": false,
1104 | "deprecationReason": null
1105 | },
1106 | {
1107 | "name": "INTERFACE",
1108 | "description": "Indicates this type is an interface. `fields`, `interfaces`, and `possibleTypes` are valid fields.",
1109 | "isDeprecated": false,
1110 | "deprecationReason": null
1111 | },
1112 | {
1113 | "name": "UNION",
1114 | "description": "Indicates this type is a union. `possibleTypes` is a valid field.",
1115 | "isDeprecated": false,
1116 | "deprecationReason": null
1117 | },
1118 | {
1119 | "name": "ENUM",
1120 | "description": "Indicates this type is an enum. `enumValues` is a valid field.",
1121 | "isDeprecated": false,
1122 | "deprecationReason": null
1123 | },
1124 | {
1125 | "name": "INPUT_OBJECT",
1126 | "description": "Indicates this type is an input object. `inputFields` is a valid field.",
1127 | "isDeprecated": false,
1128 | "deprecationReason": null
1129 | },
1130 | {
1131 | "name": "LIST",
1132 | "description": "Indicates this type is a list. `ofType` is a valid field.",
1133 | "isDeprecated": false,
1134 | "deprecationReason": null
1135 | },
1136 | {
1137 | "name": "NON_NULL",
1138 | "description": "Indicates this type is a non-null. `ofType` is a valid field.",
1139 | "isDeprecated": false,
1140 | "deprecationReason": null
1141 | }
1142 | ],
1143 | "possibleTypes": null
1144 | },
1145 | {
1146 | "kind": "OBJECT",
1147 | "name": "__Field",
1148 | "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.",
1149 | "specifiedByUrl": null,
1150 | "fields": [
1151 | {
1152 | "name": "name",
1153 | "description": null,
1154 | "args": [],
1155 | "type": {
1156 | "kind": "NON_NULL",
1157 | "name": null,
1158 | "ofType": {
1159 | "kind": "SCALAR",
1160 | "name": "String",
1161 | "ofType": null
1162 | }
1163 | },
1164 | "isDeprecated": false,
1165 | "deprecationReason": null
1166 | },
1167 | {
1168 | "name": "description",
1169 | "description": null,
1170 | "args": [],
1171 | "type": {
1172 | "kind": "SCALAR",
1173 | "name": "String",
1174 | "ofType": null
1175 | },
1176 | "isDeprecated": false,
1177 | "deprecationReason": null
1178 | },
1179 | {
1180 | "name": "args",
1181 | "description": null,
1182 | "args": [
1183 | {
1184 | "name": "includeDeprecated",
1185 | "description": null,
1186 | "type": {
1187 | "kind": "SCALAR",
1188 | "name": "Boolean",
1189 | "ofType": null
1190 | },
1191 | "defaultValue": "false",
1192 | "isDeprecated": false,
1193 | "deprecationReason": null
1194 | }
1195 | ],
1196 | "type": {
1197 | "kind": "NON_NULL",
1198 | "name": null,
1199 | "ofType": {
1200 | "kind": "LIST",
1201 | "name": null,
1202 | "ofType": {
1203 | "kind": "NON_NULL",
1204 | "name": null,
1205 | "ofType": {
1206 | "kind": "OBJECT",
1207 | "name": "__InputValue",
1208 | "ofType": null
1209 | }
1210 | }
1211 | }
1212 | },
1213 | "isDeprecated": false,
1214 | "deprecationReason": null
1215 | },
1216 | {
1217 | "name": "type",
1218 | "description": null,
1219 | "args": [],
1220 | "type": {
1221 | "kind": "NON_NULL",
1222 | "name": null,
1223 | "ofType": {
1224 | "kind": "OBJECT",
1225 | "name": "__Type",
1226 | "ofType": null
1227 | }
1228 | },
1229 | "isDeprecated": false,
1230 | "deprecationReason": null
1231 | },
1232 | {
1233 | "name": "isDeprecated",
1234 | "description": null,
1235 | "args": [],
1236 | "type": {
1237 | "kind": "NON_NULL",
1238 | "name": null,
1239 | "ofType": {
1240 | "kind": "SCALAR",
1241 | "name": "Boolean",
1242 | "ofType": null
1243 | }
1244 | },
1245 | "isDeprecated": false,
1246 | "deprecationReason": null
1247 | },
1248 | {
1249 | "name": "deprecationReason",
1250 | "description": null,
1251 | "args": [],
1252 | "type": {
1253 | "kind": "SCALAR",
1254 | "name": "String",
1255 | "ofType": null
1256 | },
1257 | "isDeprecated": false,
1258 | "deprecationReason": null
1259 | }
1260 | ],
1261 | "inputFields": null,
1262 | "interfaces": [],
1263 | "enumValues": null,
1264 | "possibleTypes": null
1265 | },
1266 | {
1267 | "kind": "OBJECT",
1268 | "name": "__InputValue",
1269 | "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.",
1270 | "specifiedByUrl": null,
1271 | "fields": [
1272 | {
1273 | "name": "name",
1274 | "description": null,
1275 | "args": [],
1276 | "type": {
1277 | "kind": "NON_NULL",
1278 | "name": null,
1279 | "ofType": {
1280 | "kind": "SCALAR",
1281 | "name": "String",
1282 | "ofType": null
1283 | }
1284 | },
1285 | "isDeprecated": false,
1286 | "deprecationReason": null
1287 | },
1288 | {
1289 | "name": "description",
1290 | "description": null,
1291 | "args": [],
1292 | "type": {
1293 | "kind": "SCALAR",
1294 | "name": "String",
1295 | "ofType": null
1296 | },
1297 | "isDeprecated": false,
1298 | "deprecationReason": null
1299 | },
1300 | {
1301 | "name": "type",
1302 | "description": null,
1303 | "args": [],
1304 | "type": {
1305 | "kind": "NON_NULL",
1306 | "name": null,
1307 | "ofType": {
1308 | "kind": "OBJECT",
1309 | "name": "__Type",
1310 | "ofType": null
1311 | }
1312 | },
1313 | "isDeprecated": false,
1314 | "deprecationReason": null
1315 | },
1316 | {
1317 | "name": "defaultValue",
1318 | "description": "A GraphQL-formatted string representing the default value for this input value.",
1319 | "args": [],
1320 | "type": {
1321 | "kind": "SCALAR",
1322 | "name": "String",
1323 | "ofType": null
1324 | },
1325 | "isDeprecated": false,
1326 | "deprecationReason": null
1327 | },
1328 | {
1329 | "name": "isDeprecated",
1330 | "description": null,
1331 | "args": [],
1332 | "type": {
1333 | "kind": "NON_NULL",
1334 | "name": null,
1335 | "ofType": {
1336 | "kind": "SCALAR",
1337 | "name": "Boolean",
1338 | "ofType": null
1339 | }
1340 | },
1341 | "isDeprecated": false,
1342 | "deprecationReason": null
1343 | },
1344 | {
1345 | "name": "deprecationReason",
1346 | "description": null,
1347 | "args": [],
1348 | "type": {
1349 | "kind": "SCALAR",
1350 | "name": "String",
1351 | "ofType": null
1352 | },
1353 | "isDeprecated": false,
1354 | "deprecationReason": null
1355 | }
1356 | ],
1357 | "inputFields": null,
1358 | "interfaces": [],
1359 | "enumValues": null,
1360 | "possibleTypes": null
1361 | },
1362 | {
1363 | "kind": "OBJECT",
1364 | "name": "__EnumValue",
1365 | "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.",
1366 | "specifiedByUrl": null,
1367 | "fields": [
1368 | {
1369 | "name": "name",
1370 | "description": null,
1371 | "args": [],
1372 | "type": {
1373 | "kind": "NON_NULL",
1374 | "name": null,
1375 | "ofType": {
1376 | "kind": "SCALAR",
1377 | "name": "String",
1378 | "ofType": null
1379 | }
1380 | },
1381 | "isDeprecated": false,
1382 | "deprecationReason": null
1383 | },
1384 | {
1385 | "name": "description",
1386 | "description": null,
1387 | "args": [],
1388 | "type": {
1389 | "kind": "SCALAR",
1390 | "name": "String",
1391 | "ofType": null
1392 | },
1393 | "isDeprecated": false,
1394 | "deprecationReason": null
1395 | },
1396 | {
1397 | "name": "isDeprecated",
1398 | "description": null,
1399 | "args": [],
1400 | "type": {
1401 | "kind": "NON_NULL",
1402 | "name": null,
1403 | "ofType": {
1404 | "kind": "SCALAR",
1405 | "name": "Boolean",
1406 | "ofType": null
1407 | }
1408 | },
1409 | "isDeprecated": false,
1410 | "deprecationReason": null
1411 | },
1412 | {
1413 | "name": "deprecationReason",
1414 | "description": null,
1415 | "args": [],
1416 | "type": {
1417 | "kind": "SCALAR",
1418 | "name": "String",
1419 | "ofType": null
1420 | },
1421 | "isDeprecated": false,
1422 | "deprecationReason": null
1423 | }
1424 | ],
1425 | "inputFields": null,
1426 | "interfaces": [],
1427 | "enumValues": null,
1428 | "possibleTypes": null
1429 | },
1430 | {
1431 | "kind": "OBJECT",
1432 | "name": "__Directive",
1433 | "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.",
1434 | "specifiedByUrl": null,
1435 | "fields": [
1436 | {
1437 | "name": "name",
1438 | "description": null,
1439 | "args": [],
1440 | "type": {
1441 | "kind": "NON_NULL",
1442 | "name": null,
1443 | "ofType": {
1444 | "kind": "SCALAR",
1445 | "name": "String",
1446 | "ofType": null
1447 | }
1448 | },
1449 | "isDeprecated": false,
1450 | "deprecationReason": null
1451 | },
1452 | {
1453 | "name": "description",
1454 | "description": null,
1455 | "args": [],
1456 | "type": {
1457 | "kind": "SCALAR",
1458 | "name": "String",
1459 | "ofType": null
1460 | },
1461 | "isDeprecated": false,
1462 | "deprecationReason": null
1463 | },
1464 | {
1465 | "name": "isRepeatable",
1466 | "description": null,
1467 | "args": [],
1468 | "type": {
1469 | "kind": "NON_NULL",
1470 | "name": null,
1471 | "ofType": {
1472 | "kind": "SCALAR",
1473 | "name": "Boolean",
1474 | "ofType": null
1475 | }
1476 | },
1477 | "isDeprecated": false,
1478 | "deprecationReason": null
1479 | },
1480 | {
1481 | "name": "locations",
1482 | "description": null,
1483 | "args": [],
1484 | "type": {
1485 | "kind": "NON_NULL",
1486 | "name": null,
1487 | "ofType": {
1488 | "kind": "LIST",
1489 | "name": null,
1490 | "ofType": {
1491 | "kind": "NON_NULL",
1492 | "name": null,
1493 | "ofType": {
1494 | "kind": "ENUM",
1495 | "name": "__DirectiveLocation",
1496 | "ofType": null
1497 | }
1498 | }
1499 | }
1500 | },
1501 | "isDeprecated": false,
1502 | "deprecationReason": null
1503 | },
1504 | {
1505 | "name": "args",
1506 | "description": null,
1507 | "args": [],
1508 | "type": {
1509 | "kind": "NON_NULL",
1510 | "name": null,
1511 | "ofType": {
1512 | "kind": "LIST",
1513 | "name": null,
1514 | "ofType": {
1515 | "kind": "NON_NULL",
1516 | "name": null,
1517 | "ofType": {
1518 | "kind": "OBJECT",
1519 | "name": "__InputValue",
1520 | "ofType": null
1521 | }
1522 | }
1523 | }
1524 | },
1525 | "isDeprecated": false,
1526 | "deprecationReason": null
1527 | }
1528 | ],
1529 | "inputFields": null,
1530 | "interfaces": [],
1531 | "enumValues": null,
1532 | "possibleTypes": null
1533 | },
1534 | {
1535 | "kind": "ENUM",
1536 | "name": "__DirectiveLocation",
1537 | "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.",
1538 | "specifiedByUrl": null,
1539 | "fields": null,
1540 | "inputFields": null,
1541 | "interfaces": null,
1542 | "enumValues": [
1543 | {
1544 | "name": "QUERY",
1545 | "description": "Location adjacent to a query operation.",
1546 | "isDeprecated": false,
1547 | "deprecationReason": null
1548 | },
1549 | {
1550 | "name": "MUTATION",
1551 | "description": "Location adjacent to a mutation operation.",
1552 | "isDeprecated": false,
1553 | "deprecationReason": null
1554 | },
1555 | {
1556 | "name": "SUBSCRIPTION",
1557 | "description": "Location adjacent to a subscription operation.",
1558 | "isDeprecated": false,
1559 | "deprecationReason": null
1560 | },
1561 | {
1562 | "name": "FIELD",
1563 | "description": "Location adjacent to a field.",
1564 | "isDeprecated": false,
1565 | "deprecationReason": null
1566 | },
1567 | {
1568 | "name": "FRAGMENT_DEFINITION",
1569 | "description": "Location adjacent to a fragment definition.",
1570 | "isDeprecated": false,
1571 | "deprecationReason": null
1572 | },
1573 | {
1574 | "name": "FRAGMENT_SPREAD",
1575 | "description": "Location adjacent to a fragment spread.",
1576 | "isDeprecated": false,
1577 | "deprecationReason": null
1578 | },
1579 | {
1580 | "name": "INLINE_FRAGMENT",
1581 | "description": "Location adjacent to an inline fragment.",
1582 | "isDeprecated": false,
1583 | "deprecationReason": null
1584 | },
1585 | {
1586 | "name": "VARIABLE_DEFINITION",
1587 | "description": "Location adjacent to a variable definition.",
1588 | "isDeprecated": false,
1589 | "deprecationReason": null
1590 | },
1591 | {
1592 | "name": "SCHEMA",
1593 | "description": "Location adjacent to a schema definition.",
1594 | "isDeprecated": false,
1595 | "deprecationReason": null
1596 | },
1597 | {
1598 | "name": "SCALAR",
1599 | "description": "Location adjacent to a scalar definition.",
1600 | "isDeprecated": false,
1601 | "deprecationReason": null
1602 | },
1603 | {
1604 | "name": "OBJECT",
1605 | "description": "Location adjacent to an object type definition.",
1606 | "isDeprecated": false,
1607 | "deprecationReason": null
1608 | },
1609 | {
1610 | "name": "FIELD_DEFINITION",
1611 | "description": "Location adjacent to a field definition.",
1612 | "isDeprecated": false,
1613 | "deprecationReason": null
1614 | },
1615 | {
1616 | "name": "ARGUMENT_DEFINITION",
1617 | "description": "Location adjacent to an argument definition.",
1618 | "isDeprecated": false,
1619 | "deprecationReason": null
1620 | },
1621 | {
1622 | "name": "INTERFACE",
1623 | "description": "Location adjacent to an interface definition.",
1624 | "isDeprecated": false,
1625 | "deprecationReason": null
1626 | },
1627 | {
1628 | "name": "UNION",
1629 | "description": "Location adjacent to a union definition.",
1630 | "isDeprecated": false,
1631 | "deprecationReason": null
1632 | },
1633 | {
1634 | "name": "ENUM",
1635 | "description": "Location adjacent to an enum definition.",
1636 | "isDeprecated": false,
1637 | "deprecationReason": null
1638 | },
1639 | {
1640 | "name": "ENUM_VALUE",
1641 | "description": "Location adjacent to an enum value definition.",
1642 | "isDeprecated": false,
1643 | "deprecationReason": null
1644 | },
1645 | {
1646 | "name": "INPUT_OBJECT",
1647 | "description": "Location adjacent to an input object type definition.",
1648 | "isDeprecated": false,
1649 | "deprecationReason": null
1650 | },
1651 | {
1652 | "name": "INPUT_FIELD_DEFINITION",
1653 | "description": "Location adjacent to an input object field definition.",
1654 | "isDeprecated": false,
1655 | "deprecationReason": null
1656 | }
1657 | ],
1658 | "possibleTypes": null
1659 | }
1660 | ],
1661 | "directives": [
1662 | {
1663 | "name": "deprecated",
1664 | "description": "The @deprecated directive is used within the type system definition language to indicate deprecated portions of a GraphQL service’s schema, such as deprecated fields on a type or deprecated enum values.",
1665 | "isRepeatable": false,
1666 | "locations": [
1667 | "FIELD_DEFINITION",
1668 | "ENUM_VALUE"
1669 | ],
1670 | "args": [
1671 | {
1672 | "name": "reason",
1673 | "description": "",
1674 | "type": {
1675 | "kind": "SCALAR",
1676 | "name": "String",
1677 | "ofType": null
1678 | },
1679 | "defaultValue": "\"No longer supported\"",
1680 | "isDeprecated": false,
1681 | "deprecationReason": null
1682 | }
1683 | ]
1684 | },
1685 | {
1686 | "name": "include",
1687 | "description": "The @include directive may be provided for fields, fragment spreads, and inline fragments, and allows for conditional inclusion during execution as described by the if argument.",
1688 | "isRepeatable": false,
1689 | "locations": [
1690 | "FIELD",
1691 | "FRAGMENT_SPREAD",
1692 | "INLINE_FRAGMENT"
1693 | ],
1694 | "args": [
1695 | {
1696 | "name": "if",
1697 | "description": "",
1698 | "type": {
1699 | "kind": "NON_NULL",
1700 | "name": null,
1701 | "ofType": {
1702 | "kind": "SCALAR",
1703 | "name": "Boolean",
1704 | "ofType": null
1705 | }
1706 | },
1707 | "defaultValue": null,
1708 | "isDeprecated": false,
1709 | "deprecationReason": null
1710 | }
1711 | ]
1712 | },
1713 | {
1714 | "name": "skip",
1715 | "description": "The @skip directive may be provided for fields, fragment spreads, and inline fragments, and allows for conditional exclusion during execution as described by the if argument.",
1716 | "isRepeatable": false,
1717 | "locations": [
1718 | "FIELD",
1719 | "FRAGMENT_SPREAD",
1720 | "INLINE_FRAGMENT"
1721 | ],
1722 | "args": [
1723 | {
1724 | "name": "if",
1725 | "description": "",
1726 | "type": {
1727 | "kind": "NON_NULL",
1728 | "name": null,
1729 | "ofType": {
1730 | "kind": "SCALAR",
1731 | "name": "Boolean",
1732 | "ofType": null
1733 | }
1734 | },
1735 | "defaultValue": null,
1736 | "isDeprecated": false,
1737 | "deprecationReason": null
1738 | }
1739 | ]
1740 | }
1741 | ]
1742 | }
1743 | }
--------------------------------------------------------------------------------
/license-check-and-add-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "ignore": [
3 | "LICENSE",
4 | "**/*.json",
5 | "**/*.txt",
6 | "**/*.md",
7 | ".github",
8 | "*.lock",
9 | ".env",
10 | ".env.template",
11 | "build",
12 | "src/assets"
13 | ],
14 | "ignoreFile": ".gitignore",
15 | "license": "copyright.txt",
16 | "licenseFormats": {
17 | "gitignore|npmignore|eslintignore|dockerignore|sh|py": {
18 | "eachLine": {
19 | "prepend": "# "
20 | }
21 | },
22 | "html|xml|svg": {
23 | "prepend": ""
25 | },
26 | "js|ts|css|scss": {
27 | "prepend": "/*",
28 | "append": "*/"
29 | }
30 | },
31 | "trailingWhitespace": "TRIM"
32 | }
33 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eth2-crawler-ui",
3 | "version": "0.1.0",
4 | "private": true,
5 | "engines": {
6 | "node": "14.x"
7 | },
8 | "dependencies": {
9 | "@apollo/client": "^3.3.21",
10 | "@chainsafe/common-components": "^1.0.29",
11 | "@chainsafe/common-theme": "^1.0.10",
12 | "@craco/craco": "^6.2.0",
13 | "@testing-library/jest-dom": "^5.11.4",
14 | "@testing-library/react": "^11.1.0",
15 | "@testing-library/user-event": "^12.1.10",
16 | "@types/jest": "^26.0.15",
17 | "@types/node": "^12.0.0",
18 | "@types/react": "^17.0.0",
19 | "@types/react-dom": "^17.0.0",
20 | "apollo-boost": "^0.4.9",
21 | "chart.js": "^3.4.1",
22 | "clsx": "^1.1.1",
23 | "craco-babel-loader": "^0.1.4",
24 | "dayjs": "^1.10.6",
25 | "formik": "^2.2.9",
26 | "graphql": "^15.5.1",
27 | "graphql-request": "^3.4.0",
28 | "leaflet": "^1.7.1",
29 | "leaflet.heat": "^0.2.0",
30 | "react": "^17.0.2",
31 | "react-chartjs-2": "^3.0.3",
32 | "react-dom": "^17.0.2",
33 | "react-leaflet": "^3.2.0",
34 | "react-scripts": "4.0.3",
35 | "react-toast-notifications": "^2.5.1",
36 | "react-tooltip": "^4.2.21",
37 | "recharts": "^2.0.10",
38 | "simpleheat": "^0.4.0",
39 | "typescript": "^4.1.2",
40 | "web-vitals": "^1.0.1"
41 | },
42 | "scripts": {
43 | "start": "craco start",
44 | "build": "craco build",
45 | "test": "craco test",
46 | "lint": "eslint './src/**/*.{ts,tsx}'",
47 | "lint:fix": "eslint --fix './src/**/*.{ts,tsx}'",
48 | "license-add": "license-check-and-add add -f license-check-and-add-config.json",
49 | "license-check": "license-check-and-add check -f license-check-and-add-config.json",
50 | "license-remove": "license-check-and-add remove -f license-check-and-add-config.json",
51 | "get-graph-schema": "yarn apollo schema:download --endpoint=https://crawler.imploy.site/query graphql-schema.json",
52 | "generate-graph-types": "yarn apollo codegen:generate --localSchemaFile=graphql-schema.json --target=typescript --includes=src/**/*.ts --tagName=gql --addTypename --globalTypesFile=src/types/graphql-global-types.ts types",
53 | "download-and-generate-graph-types": "yarn get-graph-schema && yarn generate-graph-types"
54 | },
55 | "eslintConfig": {
56 | "extends": [
57 | "react-app",
58 | "react-app/jest"
59 | ]
60 | },
61 | "browserslist": {
62 | "production": [
63 | ">0.2%",
64 | "not dead",
65 | "not op_mini all"
66 | ],
67 | "development": [
68 | "last 1 chrome version",
69 | "last 1 firefox version",
70 | "last 1 safari version"
71 | ]
72 | },
73 | "devDependencies": {
74 | "@types/graphql": "^14.5.0",
75 | "@types/leaflet": "^1.7.4",
76 | "apollo": "^2.33.4",
77 | "eslint-config-prettier": "^8.3.0",
78 | "eslint-plugin-prettier": "^3.4.0",
79 | "eslint-plugin-react-hooks": "^4.2.0",
80 | "license-check-and-add": "^4.0.2",
81 | "prettier": "^2.3.2"
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/public/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChainSafe/nodewatch-ui/423a1184c5961462f98497c87aed21934fb5f03e/public/icon.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
21 | Nodewatch - Eth2 Node Analytics
22 |
23 |
24 |
25 | You need to enable JavaScript to run this app.
26 |
27 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Eth 2 Crawler",
3 | "name": "Eth 2 Crawler",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React from "react"
6 | import HomePage from "./Components/Pages/HomePage"
7 | import { ThemeSwitcher, createStyles, makeStyles } from "@chainsafe/common-theme"
8 | import { theme } from "./Components/Themes/theme"
9 | import { Eth2CrawlerProvider } from "./Contexts/Eth2CrawlerContext"
10 | import BodyLayout from "./Components/Layouts/BodyLayout"
11 | import NavBar from "./Components/Modules/Navbar"
12 | import Footer from "./Components/Modules/Footer"
13 |
14 | const useStyles = makeStyles(() => {
15 | return createStyles({
16 | root: {
17 | backgroundColor: "#131825",
18 | },
19 | })
20 | })
21 |
22 | function App() {
23 | const classes = useStyles()
24 | return (
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | )
37 | }
38 |
39 | export default App
40 |
--------------------------------------------------------------------------------
/src/Components/Elements/Icons/ToolTipIcon.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React from "react"
6 |
7 | function ToolTipIcon(props: any) {
8 | return (
9 |
16 |
20 |
24 |
25 | )
26 | }
27 |
28 | export default ToolTipIcon
29 |
--------------------------------------------------------------------------------
/src/Components/Elements/Pagination.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React from "react"
6 | import { createStyles, makeStyles } from "@chainsafe/common-theme"
7 | import { ArrowLeftIcon, ArrowRightIcon, Typography } from "@chainsafe/common-components"
8 | import { ECTheme } from "../Themes/types"
9 | import clsx from "clsx"
10 |
11 | const useStyles = makeStyles(({ constants, palette }: ECTheme) => {
12 | return createStyles({
13 | root: {
14 | display: "flex",
15 | color: palette.text.primary,
16 | alignItems: "center",
17 | },
18 | icons: {
19 | fill: palette.text.primary,
20 | fontSize: 12,
21 | cursor: "pointer",
22 | padding: constants.generalUnit,
23 | },
24 | leftIcon: {
25 | marginRight: constants.generalUnit,
26 | },
27 | rightIcon: {
28 | marginLeft: constants.generalUnit,
29 | },
30 | })
31 | })
32 |
33 | interface IPaginationProps {
34 | pageNo: number
35 | totalPages: number
36 | onNextPage?: () => void
37 | onPreviousPage?: () => void
38 | }
39 |
40 | const Pagination: React.FC = ({
41 | pageNo,
42 | totalPages,
43 | onNextPage,
44 | onPreviousPage,
45 | }) => {
46 | const classes = useStyles()
47 |
48 | return (
49 |
50 |
55 |
56 | Page {pageNo} of {totalPages}
57 |
58 |
63 |
64 | )
65 | }
66 |
67 | export { Pagination }
68 |
--------------------------------------------------------------------------------
/src/Components/Elements/StatTitles.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React from "react"
6 | import { createStyles, makeStyles } from "@chainsafe/common-theme"
7 | import { Typography } from "@chainsafe/common-components"
8 | import { ECTheme } from "../Themes/types"
9 |
10 | const useStyles = makeStyles(({ palette, constants }: ECTheme) => {
11 | return createStyles({
12 | title: {
13 | fontSize: "32px",
14 | lineHeight: "40px",
15 | color: palette.additional["gray"][2],
16 | },
17 | line: {
18 | width: 24,
19 | backgroundColor: palette.primary.main,
20 | height: 2,
21 | marginBottom: constants.generalUnit * 0.5,
22 | },
23 | subtitle: {
24 | fontSize: "16px",
25 | lineHeight: "20px",
26 | color: palette.additional["gray"][2],
27 | marginBottom: constants.generalUnit * 2,
28 | },
29 | })
30 | })
31 |
32 | const StatTitleLarge: React.FC = ({ children }) => {
33 | const classes = useStyles()
34 |
35 | return (
36 |
37 |
38 | {children}
39 |
40 |
41 |
42 | )
43 | }
44 |
45 | const StatSubTitle: React.FC = ({ children }) => {
46 | const classes = useStyles()
47 |
48 | return (
49 |
50 | {children}
51 |
52 | )
53 | }
54 |
55 | export { StatTitleLarge, StatSubTitle }
56 |
--------------------------------------------------------------------------------
/src/Components/Layouts/BodyLayout.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React from "react"
6 | import { createStyles, makeStyles } from "@chainsafe/common-theme"
7 | import { ECTheme } from "../Themes/types"
8 | const useStyles = makeStyles(({ breakpoints, palette }: ECTheme) => {
9 | return createStyles({
10 | layout: {
11 | background: palette.background.default,
12 | fontFamily: "Neue Montreal",
13 | margin: "0 auto",
14 | display: "flex",
15 | flexDirection: "column",
16 | justifyContent: "center",
17 | maxWidth: breakpoints.values["lg"],
18 | },
19 | })
20 | })
21 |
22 | const BodyLayout: React.FC = ({ children }) => {
23 | const classes = useStyles()
24 | return {children}
25 | }
26 |
27 | export default BodyLayout
28 |
--------------------------------------------------------------------------------
/src/Components/Layouts/GridLayout/GridLayoutWrapper.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React, { ReactNode } from "react"
6 | import { createStyles, makeStyles } from "@chainsafe/common-theme"
7 | import { Typography } from "@chainsafe/common-components"
8 | import clsx from "clsx"
9 | import { ECTheme } from "../../Themes/types"
10 |
11 |
12 | const useStyles = makeStyles(({ constants, palette, }: ECTheme) => {
13 | return createStyles({
14 | root: {
15 | marginBottom: constants.generalUnit * 4,
16 | },
17 | heading: {
18 | marginBottom: constants.generalUnit * 3,
19 | color: palette.text.primary,
20 | },
21 | })
22 | })
23 |
24 | interface IGridLayoutWrapper {
25 | className?: string
26 | heading: string
27 | children: ReactNode | ReactNode[]
28 | }
29 |
30 | const GridLayoutWrapper = ({ className, heading, children }: IGridLayoutWrapper) => {
31 | const classes = useStyles()
32 |
33 | return (
34 |
35 | {heading}
36 |
37 |
38 | {children}
39 |
40 | )
41 | }
42 |
43 | export default GridLayoutWrapper
44 |
--------------------------------------------------------------------------------
/src/Components/Layouts/SectionTile/CardStat.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React from "react"
6 | import { createStyles, makeStyles } from "@chainsafe/common-theme"
7 | import { ECTheme } from "../../Themes/types"
8 | import clsx from "clsx"
9 | import { Typography } from "@chainsafe/common-components"
10 | import ToolTipIcon from "../../Elements/Icons/ToolTipIcon"
11 | import ReactTooltip from "react-tooltip"
12 |
13 | const useStyles = makeStyles(({ constants, palette }: ECTheme) => {
14 | return createStyles({
15 | root: {
16 | marginBottom: constants.generalUnit * 6,
17 | },
18 | heading: {
19 | color: palette.additional["gray"][2],
20 | "&.red": {
21 | color: constants.statColors.red,
22 | },
23 | "&.blue": {
24 | color: constants.statColors.blue,
25 | },
26 | "&.green": {
27 | color: constants.statColors.green,
28 | },
29 | },
30 | statColor: {
31 | "&.red": {
32 | color: constants.statColors.red,
33 | },
34 | "&.blue": {
35 | color: constants.statColors.blue,
36 | },
37 | "&.green": {
38 | color: constants.statColors.green,
39 | },
40 | },
41 | headingContainer: {
42 | display: "flex",
43 | alignItems: "center",
44 | },
45 | containerMargin: {
46 | marginBottom: constants.generalUnit * 1.5,
47 | },
48 | tooltipIcon: {
49 | width: 16,
50 | height: 16,
51 | marginLeft: constants.generalUnit,
52 | },
53 | })
54 | })
55 |
56 | export interface ISectionCard {
57 | heading: string
58 | stat: string
59 | className?: string
60 | isGreen?: boolean
61 | isRed?: boolean
62 | isBlue?: boolean
63 | tooltip?: React.ReactChild
64 | tooltipId?: string
65 | }
66 |
67 | const CardStat = ({
68 | className,
69 | heading,
70 | stat,
71 | isGreen,
72 | isBlue,
73 | isRed,
74 | tooltip,
75 | tooltipId,
76 | }: ISectionCard) => {
77 | const classes = useStyles()
78 |
79 | return (
80 |
81 |
82 |
83 |
88 | {heading}
89 |
90 | {tooltip && tooltipId && (
91 | <>
92 |
93 |
94 | {tooltip}
95 |
96 | >
97 | )}
98 |
99 |
100 |
101 |
106 | {stat}
107 |
108 |
109 | )
110 | }
111 |
112 | export default CardStat
113 |
--------------------------------------------------------------------------------
/src/Components/Layouts/SectionTile/SectionBody.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React, { ReactNode } from "react"
6 | import { createStyles, makeStyles } from "@chainsafe/common-theme"
7 | import { ECTheme } from "../../Themes/types"
8 | import clsx from "clsx"
9 |
10 | const useStyles = makeStyles(({ palette, constants }: ECTheme) => {
11 | return createStyles({
12 | root: {
13 | border: `1px solid ${palette.background.paper}`,
14 | borderRadius: constants.generalUnit / 2,
15 | flex: "1 1 0",
16 | },
17 | })
18 | })
19 |
20 | export interface ISectionCard {
21 | children: ReactNode | ReactNode[]
22 | className?: string
23 | }
24 |
25 | const SectionBody = ({ children, className }: ISectionCard) => {
26 | const classes = useStyles()
27 |
28 | return {children}
29 | }
30 |
31 | export default SectionBody
32 |
--------------------------------------------------------------------------------
/src/Components/Layouts/SectionTile/SectionCard.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React, { ReactNode } from "react"
6 | import { createStyles, makeStyles } from "@chainsafe/common-theme"
7 | import clsx from "clsx"
8 | import { ECTheme } from "../../Themes/types"
9 |
10 | const useStyles = makeStyles(({ constants, palette, breakpoints }: ECTheme) => {
11 | return createStyles({
12 | root: {
13 | border: `1px solid ${palette.background.paper}`,
14 | padding: constants.generalUnit * 2,
15 | borderRadius: constants.generalUnit / 2,
16 | display: "flex",
17 | flexDirection: "column",
18 | justifyContent: "space-between",
19 | color: palette.additional["gray"][2],
20 | "& > *:last-child": {
21 | marginBottom: 0,
22 | },
23 | [breakpoints.up("md")]: {
24 | width: "30%",
25 | marginRight: constants.generalUnit * 3,
26 | },
27 | [breakpoints.down("md")]: {
28 | marginBottom: constants.generalUnit * 3,
29 | },
30 | },
31 | })
32 | })
33 |
34 | export interface ISectionCard {
35 | children: ReactNode | ReactNode[]
36 | className?: string
37 | }
38 |
39 | const SectionCard = ({ children, className }: ISectionCard) => {
40 | const classes = useStyles()
41 |
42 | return {children}
43 | }
44 |
45 | export default SectionCard
46 |
--------------------------------------------------------------------------------
/src/Components/Layouts/SectionTile/SectionTile.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React, { ReactNode } from "react"
6 | import { createStyles, makeStyles } from "@chainsafe/common-theme"
7 | import { Typography } from "@chainsafe/common-components"
8 | import SectionCard from "./SectionCard"
9 | import SectionBody from "./SectionBody"
10 | import clsx from "clsx"
11 | import { ECTheme } from "../../Themes/types"
12 |
13 | const useStyles = makeStyles(({ constants, breakpoints, palette }: ECTheme) => {
14 | return createStyles({
15 | root: {
16 | marginBottom: constants.generalUnit * 6,
17 | },
18 | heading: {
19 | marginBottom: constants.generalUnit * 3,
20 | color: palette.text.primary,
21 | },
22 | content: {
23 | display: "flex",
24 | flexDirection: "row",
25 | justifyContent: "flex-start",
26 | [breakpoints.down("md")]: {
27 | flexDirection: "column",
28 | },
29 | },
30 | })
31 | })
32 |
33 | interface ISectionTile {
34 | className?: string
35 | heading: string
36 | cardContent: ReactNode
37 | children: ReactNode | ReactNode[]
38 | }
39 |
40 | const SectionTile = ({ className, heading, cardContent, children }: ISectionTile) => {
41 | const classes = useStyles()
42 |
43 | return (
44 |
45 |
46 | {heading}
47 |
48 |
49 | {cardContent}
50 | {children}
51 |
52 |
53 | )
54 | }
55 |
56 | export default SectionTile
57 |
--------------------------------------------------------------------------------
/src/Components/Modules/CountryStats/CountryBox.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React from "react"
6 | import { createStyles, makeStyles } from "@chainsafe/common-theme"
7 | import { ECTheme } from "../../Themes/types"
8 | import clsx from "clsx"
9 | import { Typography } from "@chainsafe/common-components"
10 |
11 | const useStyles = makeStyles(({ palette, constants }: ECTheme) => {
12 | return createStyles({
13 | root: {
14 | border: `1px solid ${palette.background.paper}`,
15 | borderRadius: "3px",
16 | padding: constants.generalUnit * 2,
17 | width: "inherit",
18 | height: "inherit",
19 | },
20 | countryTitle: {
21 | marginRight: constants.generalUnit * 6,
22 | },
23 | countRow: {
24 | display: "flex",
25 | justifyContent: "space-between",
26 | color: palette.text.primary,
27 | margin: `${constants.generalUnit * 2}px 0`,
28 | },
29 | })
30 | })
31 |
32 | interface ICountryBoxProps {
33 | countries: {
34 | rank: number
35 | name: string
36 | count: number
37 | percentage: string
38 | }[]
39 | className?: string
40 | }
41 |
42 | const CountryBox: React.FC = ({ countries, className }) => {
43 | const classes = useStyles()
44 | return (
45 |
46 | {countries.map((country, i) => (
47 |
48 |
49 | {country.rank}. {country.name}
50 |
51 |
52 | {country.count} ({country.percentage}%)
53 |
54 |
55 | ))}
56 |
57 | )
58 | }
59 |
60 | export default CountryBox
61 |
--------------------------------------------------------------------------------
/src/Components/Modules/CountryStats/StatsChartBox.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React, { useState } from "react"
6 | import { createStyles, makeStyles, useTheme } from "@chainsafe/common-theme"
7 | import { ECTheme } from "../../Themes/types"
8 | import { PieChart, Pie, Sector, ResponsiveContainer } from "recharts"
9 |
10 | const useStyles = makeStyles(({ constants }: ECTheme) => {
11 | return createStyles({
12 | root: {
13 | display: "flex",
14 | flexDirection: "column",
15 | justifyContent: "center",
16 | flex: 1,
17 | },
18 | chartContainer: {
19 | height: `${constants.chartSizes.chartHeight}px`,
20 | },
21 | })
22 | })
23 |
24 | interface IStatsChartBoxProps {
25 | countries: {
26 | rank: number
27 | name: string
28 | count: number
29 | percentage: string
30 | }[]
31 | }
32 |
33 | const renderActiveShape = (props: any, fill: string) => {
34 | const { cx, cy, innerRadius, outerRadius, startAngle, endAngle, payload } = props
35 |
36 | return (
37 |
38 |
39 | {payload.name}
40 |
41 |
50 |
59 |
60 | )
61 | }
62 |
63 | const CountryBox: React.FC = ({ countries }) => {
64 | const classes = useStyles()
65 | const theme: ECTheme = useTheme()
66 | const [activeIndex, setActiveIndex] = useState(0)
67 |
68 | const onPieEnter = (_: any, index: number) => {
69 | setActiveIndex(index)
70 | }
71 |
72 | const data = countries.map((country) => ({
73 | name: `${country.name}(${country.percentage}%)`,
74 | value: country.count,
75 | }))
76 |
77 | return (
78 |
79 |
80 |
81 |
82 |
85 | renderActiveShape(props, theme.constants.chartPrimaryColors.main)
86 | }
87 | data={data}
88 | cx="50%"
89 | cy="50%"
90 | innerRadius={110}
91 | outerRadius={130}
92 | fill={theme.constants.chartPrimaryColors.main}
93 | dataKey="value"
94 | onMouseEnter={onPieEnter}
95 | />
96 |
97 |
98 |
99 |
100 | )
101 | }
102 |
103 | export default CountryBox
104 |
--------------------------------------------------------------------------------
/src/Components/Modules/CountryStats/index.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React, { useCallback, useEffect, useMemo, useState } from "react"
6 | import { createStyles, makeStyles, useTheme } from "@chainsafe/common-theme"
7 | import { ECTheme } from "../../Themes/types"
8 | import { useEth2CrawlerApi } from "../../../Contexts/Eth2CrawlerContext"
9 | import CountryBox from "./CountryBox"
10 | import StatsChartBox from "./StatsChartBox"
11 | import { Typography } from "@chainsafe/common-components"
12 | import { Pagination } from "../../Elements/Pagination"
13 | import useWindowDimensions from "../../../utilHooks/useWindowDimensions"
14 |
15 | const useStyles = makeStyles(({ palette, constants, breakpoints }: ECTheme) => {
16 | return createStyles({
17 | root: {
18 | marginBottom: constants.generalUnit * 8,
19 | },
20 | container: {
21 | display: "grid",
22 | gridTemplateColumns: "2fr 1fr",
23 | gridColumnGap: constants.generalUnit * 4,
24 | minHeight: 430,
25 | [breakpoints.down("md")]: {
26 | gridTemplateColumns: "1fr",
27 | gridRowGap: constants.generalUnit * 2,
28 | },
29 | [breakpoints.down("sm")]: {
30 | gridTemplateColumns: "1fr",
31 | },
32 | },
33 | countriesContainer: {
34 | display: "grid",
35 | gridTemplateColumns: "1fr 1fr",
36 | gridColumnGap: constants.generalUnit * 4,
37 | minHeight: 430,
38 | [breakpoints.down("md")]: {
39 | gridTemplateColumns: "1fr 1fr",
40 | gridRowGap: constants.generalUnit * 4,
41 | },
42 | [breakpoints.down("sm")]: {
43 | gridTemplateColumns: "1fr",
44 | },
45 | },
46 | countryBox1: {
47 | flex: 1,
48 | },
49 | countryBox2: {
50 | flex: 1,
51 | },
52 | title: {
53 | marginBottom: constants.generalUnit * 3,
54 | color: palette.text.primary,
55 | },
56 | pagination: {
57 | marginTop: constants.generalUnit * 2,
58 | [breakpoints.down("md")]: {
59 | marginTop: 0,
60 | marginBottom: constants.generalUnit * 4,
61 | },
62 | [breakpoints.down("sm")]: {
63 | marginBottom: constants.generalUnit * 4,
64 | },
65 | },
66 | })
67 | })
68 |
69 | const PAGE_SIZE = 20
70 | const HALF_PAGE_SIZE = PAGE_SIZE / 2
71 |
72 | const CountryStats: React.FC = () => {
73 | const classes = useStyles()
74 | const { nodeCountByCountries } = useEth2CrawlerApi()
75 | const [pageNo, setPageNo] = useState(0)
76 |
77 | const { width } = useWindowDimensions()
78 | const theme: ECTheme = useTheme()
79 | const isDesktop = width > theme.breakpoints.values["md"]
80 | const isTab = width > theme.breakpoints.values["sm"] && width < theme.breakpoints.values["md"]
81 |
82 | const showTwoCountryBoxes = isDesktop || isTab
83 |
84 | useEffect(() => {
85 | if (nodeCountByCountries.length) {
86 | setPageNo(1)
87 | }
88 | }, [nodeCountByCountries, isDesktop])
89 |
90 | const totalPages = useMemo(
91 | () => Math.ceil(nodeCountByCountries.length / (isDesktop ? PAGE_SIZE : HALF_PAGE_SIZE)),
92 | [nodeCountByCountries, isDesktop]
93 | )
94 |
95 | const onPrevPage = useCallback(() => {
96 | if (pageNo > 1) {
97 | setPageNo(pageNo - 1)
98 | }
99 | }, [pageNo])
100 |
101 | const onNextPage = useCallback(() => {
102 | if (pageNo < totalPages) {
103 | setPageNo(pageNo + 1)
104 | }
105 | }, [pageNo, totalPages])
106 |
107 | const totalNodeCount = useMemo(
108 | () =>
109 | nodeCountByCountries.reduce((total, item) => {
110 | total += item.count
111 | return total
112 | }, 0),
113 | [nodeCountByCountries]
114 | )
115 |
116 | const sortedNodeCountByCountries = useMemo(
117 | () =>
118 | nodeCountByCountries
119 | .sort((a, b) => (a.count < b.count ? 1 : -1))
120 | .map((nodeByCountry, i) => ({
121 | ...nodeByCountry,
122 | rank: i + 1,
123 | percentage: ((nodeByCountry.count / totalNodeCount) * 100).toFixed(2),
124 | })),
125 | [nodeCountByCountries, totalNodeCount]
126 | )
127 |
128 | const first10Countries = sortedNodeCountByCountries.slice(
129 | (pageNo - 1) * (showTwoCountryBoxes ? PAGE_SIZE : HALF_PAGE_SIZE),
130 | showTwoCountryBoxes ? pageNo * PAGE_SIZE - HALF_PAGE_SIZE : pageNo * HALF_PAGE_SIZE
131 | )
132 | const second10Countries = sortedNodeCountByCountries.slice(
133 | pageNo * PAGE_SIZE - HALF_PAGE_SIZE,
134 | pageNo * PAGE_SIZE
135 | )
136 |
137 | return (
138 |
139 |
140 | Node count by countries
141 |
142 |
143 |
144 |
145 | {showTwoCountryBoxes && (
146 |
147 | )}
148 |
149 | {(!showTwoCountryBoxes || isTab) && (
150 |
158 | )}
159 |
160 |
161 | {pageNo > 0 && showTwoCountryBoxes && !isTab && (
162 |
170 | )}
171 |
172 | )
173 | }
174 |
175 | export default CountryStats
176 |
--------------------------------------------------------------------------------
/src/Components/Modules/DemographicsStats/ClientTypes.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React, { useMemo } from "react"
6 | import { createStyles, makeStyles, useTheme } from "@chainsafe/common-theme"
7 | import { Typography } from "@chainsafe/common-components"
8 | import { useEth2CrawlerApi } from "../../../Contexts/Eth2CrawlerContext"
9 | import { ECTheme } from "../../Themes/types"
10 | import { BarChart, Bar, Tooltip, XAxis, YAxis, ResponsiveContainer } from "recharts"
11 |
12 | const useStyles = makeStyles(({ palette, constants }: ECTheme) => {
13 | return createStyles({
14 | root: {
15 | border: `1px solid ${palette.background.paper}`,
16 | borderRadius: "3px",
17 | padding: constants.generalUnit * 2,
18 | width: "inherit",
19 | height: "inherit",
20 | },
21 | chartContainer: {
22 | height: `${constants.chartSizes.chartHeight}px`,
23 | },
24 | title: {
25 | marginBottom: constants.generalUnit * 2,
26 | color: palette.text.primary,
27 | },
28 | })
29 | })
30 |
31 | // const MIN_CLIENT_COUNT = 20
32 |
33 | const ClientTypes = () => {
34 | const classes = useStyles()
35 | const theme: ECTheme = useTheme()
36 |
37 | const { clients } = useEth2CrawlerApi()
38 |
39 | const chartData = useMemo(
40 | () =>
41 | clients
42 | .sort((first, second) => (first.count > second.count ? 1 : -1))
43 | // .filter((client) => client.count > MIN_CLIENT_COUNT)
44 | .map((client) => ({
45 | name: client.name || "unknown",
46 | count: client.count,
47 | })),
48 | [clients]
49 | )
50 |
51 | return (
52 |
53 |
54 | Client type distribution
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | )
68 | }
69 |
70 | export default ClientTypes
71 |
--------------------------------------------------------------------------------
/src/Components/Modules/DemographicsStats/NodeCount12.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React from "react"
6 | import { createStyles, makeStyles, useTheme } from "@chainsafe/common-theme"
7 | import { Line } from "react-chartjs-2"
8 | import { Typography } from "@chainsafe/common-components"
9 | import { ECTheme } from "../../Themes/types"
10 |
11 | const useStyles = makeStyles(({ palette, constants }: ECTheme) => {
12 | return createStyles({
13 | root: {
14 | border: `1px solid ${palette.additional["gray"][4]}`,
15 | borderRadius: "3px",
16 | padding: constants.generalUnit * 2,
17 | },
18 | title: {
19 | marginBottom: constants.generalUnit * 4,
20 | },
21 | })
22 | })
23 |
24 | const NodeCount12 = () => {
25 | const classes = useStyles()
26 |
27 | const theme: ECTheme = useTheme()
28 |
29 | const data = {
30 | labels: ["1", "2", "3", "4", "5", "6", "7"],
31 | datasets: [
32 | {
33 | label: "Node count: eth1",
34 | data: [65, 59, 80, 81, 56, 55, 40],
35 | fill: true,
36 | borderColor: theme.palette.primary.main,
37 | backgroundColor: theme.palette.primary.background,
38 | tension: 0.1,
39 | },
40 | {
41 | label: "Node count: eth2",
42 | data: [99, 56, 55, 40, 65, 59, 100],
43 | fill: true,
44 | borderColor: theme.palette.primary.main,
45 | backgroundColor: theme.palette.primary.background,
46 | tension: 0.1,
47 | },
48 | ],
49 | }
50 |
51 | const options = {
52 | scales: {
53 | y: {
54 | display: false,
55 | },
56 | x: {
57 | grid: {
58 | display: false,
59 | },
60 | },
61 | },
62 | plugins: {
63 | legend: {
64 | display: false,
65 | },
66 | },
67 | }
68 |
69 | return (
70 |
71 |
72 | node count eth1 and eth2
73 |
74 |
75 |
76 |
77 |
78 | )
79 | }
80 |
81 | export default NodeCount12
82 |
--------------------------------------------------------------------------------
/src/Components/Modules/DemographicsStats/NodeReadyForFork.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React from "react"
6 | import { createStyles, makeStyles, useTheme } from "@chainsafe/common-theme"
7 | import { Line } from "react-chartjs-2"
8 | import { Typography } from "@chainsafe/common-components"
9 | import { ECTheme } from "../../Themes/types"
10 |
11 | const useStyles = makeStyles(({ palette, constants }: ECTheme) => {
12 | return createStyles({
13 | root: {
14 | border: `1px solid ${palette.additional["gray"][4]}`,
15 | borderRadius: "3px",
16 | padding: constants.generalUnit * 2,
17 | },
18 | title: {
19 | marginBottom: constants.generalUnit * 4,
20 | },
21 | })
22 | })
23 |
24 | const NodeReadyForFork = () => {
25 | const classes = useStyles()
26 | const theme: ECTheme = useTheme()
27 |
28 | const data = {
29 | labels: ["1", "2", "3", "4", "5", "6", "7"],
30 | datasets: [
31 | {
32 | label: "Node count: eth1",
33 | data: [65, 59, 80, 81, 56, 55, 40],
34 | fill: false,
35 | borderColor: theme.palette.primary.main,
36 | lineTension: 0.3,
37 | },
38 | ],
39 | }
40 |
41 | const options = {
42 | scales: {
43 | y: {
44 | display: false,
45 | },
46 | x: {
47 | display: false,
48 | grid: {
49 | display: false,
50 | },
51 | },
52 | },
53 | plugins: {
54 | legend: {
55 | display: false,
56 | },
57 | },
58 | }
59 |
60 | return (
61 |
62 |
63 | node ready to fork
64 |
65 |
66 |
67 |
68 |
69 | )
70 | }
71 |
72 | export default NodeReadyForFork
73 |
--------------------------------------------------------------------------------
/src/Components/Modules/DemographicsStats/StatusSync.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React from "react"
6 | import { createStyles, makeStyles, useTheme } from "@chainsafe/common-theme"
7 | import { Typography } from "@chainsafe/common-components"
8 | import { Scatter } from "react-chartjs-2"
9 | import { ECTheme } from "../../Themes/types"
10 |
11 | const useStyles = makeStyles(({ palette, constants }: ECTheme) => {
12 | return createStyles({
13 | root: {
14 | border: `1px solid ${palette.additional["gray"][4]}`,
15 | borderRadius: "3px",
16 | padding: constants.generalUnit * 2,
17 | },
18 | title: {
19 | marginBottom: constants.generalUnit * 4,
20 | },
21 | })
22 | })
23 |
24 | const getRandomArr = (length: number) => {
25 | const arrXY: { x: number; y: number }[] = []
26 | for (let i = 0; i < length; i++) {
27 | arrXY.push({
28 | x: Math.floor(Math.random() * 100),
29 | y: Math.floor(Math.random() * 100),
30 | })
31 | }
32 | return arrXY
33 | }
34 |
35 | const StatusSync = () => {
36 | const classes = useStyles()
37 | const theme: ECTheme = useTheme()
38 |
39 | const data = {
40 | datasets: [
41 | {
42 | label: "Node sync",
43 | data: getRandomArr(50),
44 | backgroundColor: theme.palette.primary.main,
45 | },
46 | ],
47 | }
48 |
49 | const options = {
50 | scales: {
51 | y: {
52 | display: false,
53 | },
54 | x: {
55 | display: false,
56 | grid: {
57 | display: false,
58 | },
59 | },
60 | },
61 | plugins: {
62 | legend: {
63 | display: false,
64 | },
65 | },
66 | }
67 |
68 | return (
69 |
70 |
71 | Status sync over time
72 |
73 |
74 |
75 |
76 |
77 | )
78 | }
79 |
80 | export default StatusSync
81 |
--------------------------------------------------------------------------------
/src/Components/Modules/Footer.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 |
6 | import React from "react"
7 | import { createStyles, ITheme, makeStyles } from "@chainsafe/common-theme"
8 | import { Typography } from "@chainsafe/common-components"
9 |
10 | const useStyles = makeStyles(({ palette, constants, breakpoints }: ITheme) => {
11 | return createStyles({
12 | root: {
13 | padding: `${constants.generalUnit * 2}px ${constants.generalUnit}px`,
14 | background: palette.background.paper,
15 | display: "flex",
16 | [breakpoints.down("sm")]: {
17 | flexDirection: "column",
18 | },
19 | },
20 | bold: {
21 | fontWeight: 600,
22 | },
23 | copyright: {
24 | display: "flex",
25 | alignItems: "center",
26 | fontFamily: "Neue Montreal",
27 | color: palette.additional["gray"][4],
28 | marginRight: constants.generalUnit,
29 | [breakpoints.up("md")]: {
30 | marginLeft: constants.generalUnit * 2,
31 | },
32 | [breakpoints.up("xl")]: {
33 | textAlign: "left",
34 | fontSize: constants.generalUnit * 2,
35 | },
36 | },
37 | link: {
38 | color: palette.additional["gray"][4],
39 | transition: "all .25s ease-out",
40 | marginLeft: "2px",
41 | "&:hover": {
42 | color: palette.primary.main,
43 | },
44 | },
45 | })
46 | })
47 |
48 | const Footer: React.FC = () => {
49 | const currentYear = new Date().getFullYear()
50 | const classes = useStyles()
51 | return (
52 |
70 | )
71 | }
72 | export default Footer
73 |
--------------------------------------------------------------------------------
/src/Components/Modules/HeatMap/MapLeaflet.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React, { useEffect } from "react"
6 | // import { CircleMarker, MapContainer, TileLayer, Tooltip } from "react-leaflet"
7 | import L, { LatLngTuple } from "leaflet"
8 | // import { createStyles, makeStyles } from "@chainsafe/common-theme"
9 | import { useEth2CrawlerApi } from "../../../Contexts/Eth2CrawlerContext"
10 | // import useWindowDimensions from "../../../utilHooks/useWindowDimensions"
11 | import "leaflet/dist/leaflet.css"
12 | import "leaflet.heat"
13 | import { useState } from "react"
14 |
15 | // const useStyles = makeStyles(() => {
16 | // return createStyles({
17 | // mapContainer: {
18 | // height: "100%",
19 | // width: "100%",
20 | // },
21 | // mapContainerDefined: {
22 | // height: "inherit",
23 | // width: "100%",
24 | // },
25 | // })
26 | // })
27 |
28 | // attribution to put on map
29 | // const tilesAttribution =
30 | // "Map tiles by Stamen Design , under CC BY 3.0 . Data by OpenStreetMap , under CC BY SA ."
31 |
32 | // tiles source
33 | // const accessToken = "W4rWKMx2iiIF8SZAOjfFnuk4khsAjJJ2iwdTI8pKy4yN58BJHP02SiwIwVABnEmZ"
34 | // const tileUrl = `https://{s}.tile.jawg.io/jawg-dark/{z}/{x}/{y}{r}.png?access-token=${accessToken}`
35 |
36 | // const tileUrl = "https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png"
37 |
38 | const latLngCenter: LatLngTuple = [30, 0]
39 | // const latLngBounds: LatLngBoundsExpression = [
40 | // [-70.095513, -140.0225067],
41 | // [86.120628, 170.31769],
42 | // ]
43 | const maxZoom = 5
44 | const defaultZoom = 1.4
45 | const minZoom = 1.4
46 |
47 | const NodeMap = ({ rootClassName }: { rootClassName: string }) => {
48 | // const classes = useStyles()
49 | const { heatmap } = useEth2CrawlerApi()
50 | // const { width } = useWindowDimensions()
51 |
52 | // const circleRadius = width < 480 ? 1 : width < 720 ? 2 : width < 1280 ? 4 : 4
53 | // const circleOpacity = 0.3
54 |
55 | const [map, setMap] = useState(undefined)
56 | const [radius, setRadius] = useState(3)
57 |
58 | useEffect(() => {
59 | if (!map) {
60 | const newMap = L.map("map", {
61 | zoomControl: true,
62 | minZoom: minZoom,
63 | maxZoom: maxZoom,
64 | // maxBounds: latLngBounds,
65 | attributionControl: false,
66 | }).setView(latLngCenter, defaultZoom)
67 |
68 | L.tileLayer("https://{s}.basemaps.cartocdn.com/dark_nolabels/{z}/{x}/{y}{r}.png").addTo(
69 | newMap
70 | )
71 | newMap.on("zoomend", function(results: any) {
72 | if (results.target._zoom > 4) {
73 | setRadius(5)
74 | } else if (results.target._zoom > 3) {
75 | setRadius(4)
76 | } else {
77 | setRadius(3)
78 | }
79 | });
80 | setMap(newMap)
81 | }
82 | }, [map])
83 |
84 | useEffect(() => {
85 | if (map) {
86 | const points: any[] = heatmap
87 | ? heatmap.map((p) => {
88 | return [p.latitude, p.longitude]
89 | })
90 | : []
91 |
92 | // removing previous layer
93 | let heatlayer: any = undefined;
94 | map.eachLayer((layer: any) => {
95 | if(layer?.options?.radius) {
96 | heatlayer = layer
97 | }
98 | })
99 | if (heatlayer) {
100 | map.removeLayer(heatlayer)
101 | }
102 |
103 | ;(L as any)
104 | .heatLayer(points, {
105 | minOpacity: 1,
106 | radius: radius,
107 | max: 1,
108 | blur: 5,
109 | })
110 | .addTo(map)
111 | }
112 | }, [heatmap, map, radius])
113 |
114 | return
115 |
116 | // pure react leaflet map
117 | // in case we need it later
118 |
119 | // return (
120 | //
121 | //
132 | //
133 | // {heatmap.map((heatmapPoint, i) => (
134 | //
141 | //
142 | // {`${heatmapPoint.clientType} : ${heatmapPoint.networkType}`}
143 | //
144 | //
145 | // ))}
146 | //
147 | //
148 | // )
149 | }
150 |
151 | export default NodeMap
152 |
--------------------------------------------------------------------------------
/src/Components/Modules/Navbar.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 |
6 | import React from "react"
7 | import { createStyles, ITheme, makeStyles } from "@chainsafe/common-theme"
8 | import { Typography } from "@chainsafe/common-components"
9 |
10 | const useStyles = makeStyles(({ breakpoints, palette, zIndex, constants }: ITheme) => {
11 | return createStyles({
12 | container: {
13 | width: "100%",
14 | display: "flex",
15 | background: palette.background.default,
16 | position: "fixed",
17 | top: 0,
18 | zIndex: zIndex?.layer4,
19 | },
20 | box: {
21 | padding: `${constants.generalUnit * 3}px ${constants.generalUnit * 4}px`,
22 | display: "flex",
23 | flex: 1,
24 | justifyContent: "space-between",
25 | [breakpoints.down("sm")]: {
26 | padding: `${constants.generalUnit * 3}px ${constants.generalUnit * 1}px`,
27 | },
28 | },
29 | navLink: {
30 | color: palette.common.white.main,
31 | textDecoration: "none",
32 | fontFamily: "Neue Montreal",
33 | fontWeight: "bold",
34 | "&:hover": {
35 | color: palette.primary.main,
36 | transition: "ease-in 0.2s",
37 | },
38 | },
39 | })
40 | })
41 |
42 | const NavBar: React.FC = () => {
43 | const classes = useStyles()
44 | return (
45 |
64 | )
65 | }
66 |
67 | export default NavBar
68 |
--------------------------------------------------------------------------------
/src/Components/Modules/NodeStats/NodeStatsOverTime.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React, { useMemo } from "react"
6 | import { createStyles, makeStyles, useTheme } from "@chainsafe/common-theme"
7 | import { Typography } from "@chainsafe/common-components"
8 | import { useEth2CrawlerApi } from "../../../Contexts/Eth2CrawlerContext"
9 | import { ECTheme } from "../../Themes/types"
10 | import {
11 | XAxis,
12 | YAxis,
13 | ResponsiveContainer,
14 | LineChart,
15 | CartesianGrid,
16 | Line,
17 | Tooltip,
18 | } from "recharts"
19 | import ToolTipIcon from "../../Elements/Icons/ToolTipIcon"
20 | import ReactTooltip from "react-tooltip"
21 | import dayjs from "dayjs"
22 |
23 | const useStyles = makeStyles(({ constants, breakpoints, palette, typography }: ECTheme) => {
24 | return createStyles({
25 | root: {
26 | padding: constants.generalUnit * 2,
27 | width: "inherit",
28 | height: "inherit",
29 | },
30 | chartContainer: {
31 | height: "40vh",
32 | width: "100%",
33 | [breakpoints.down("sm")]: {
34 | height: "30vh",
35 | },
36 | },
37 | title: {
38 | color: palette.additional["gray"][2],
39 | },
40 | headingContainer: {
41 | display: "flex",
42 | alignItems: "center",
43 | color: palette.additional["gray"][2],
44 | },
45 | tooltipIcon: {
46 | width: 16,
47 | height: 16,
48 | marginLeft: constants.generalUnit,
49 | },
50 | containerMargin: {
51 | marginBottom: constants.generalUnit * 4,
52 | },
53 | tooltipBody: {
54 | ...typography.body1,
55 | backgroundColor: palette.additional["gray"][2],
56 | },
57 | })
58 | })
59 |
60 | const NodeStatusOverTime = () => {
61 | const classes = useStyles()
62 | const theme: ECTheme = useTheme()
63 |
64 | const { nodeStatsOverTime } = useEth2CrawlerApi()
65 |
66 | const chartData = useMemo(
67 | () =>
68 | nodeStatsOverTime.map(
69 | (nodeStat: { time: number; totalNodes: any; syncedNodes: any; unsyncedNodes: any }) => ({
70 | time: dayjs(nodeStat.time * 1000).format("DD MMM 'YY"),
71 | total: nodeStat.totalNodes,
72 | synced: nodeStat.syncedNodes,
73 | unsynced: nodeStat.unsyncedNodes,
74 | })
75 | ),
76 | [nodeStatsOverTime]
77 | )
78 |
79 | return (
80 |
81 |
82 |
83 |
84 | Node count over the past 7 days
85 |
86 |
87 |
88 |
89 | Shows node count over the past 7 days.
90 | The chart also shows number of nodes synced and unsynced over the time period.
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
109 |
115 |
121 |
122 |
123 |
124 |
125 | )
126 | }
127 |
128 | export default NodeStatusOverTime
129 |
--------------------------------------------------------------------------------
/src/Components/Modules/SoftwareStats/AltAirPercentage.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React, { useMemo, useState } from "react"
6 | import { createStyles, makeStyles, useTheme } from "@chainsafe/common-theme"
7 | import { ECTheme } from "../../Themes/types"
8 | import { PieChart, Pie, Sector, ResponsiveContainer } from "recharts"
9 | import { useEth2CrawlerApi } from "../../../Contexts/Eth2CrawlerContext"
10 | import { Typography } from "@chainsafe/common-components"
11 |
12 | const useStyles = makeStyles(({ constants, palette }: ECTheme) => {
13 | return createStyles({
14 | root: {
15 | border: `1px solid ${palette.background.paper}`,
16 | borderRadius: "3px",
17 | padding: constants.generalUnit * 2,
18 | width: "inherit",
19 | height: "inherit",
20 | },
21 | chartContainer: {
22 | height: `${constants.chartSizes.chartHeight}px`,
23 | },
24 | title: {
25 | marginBottom: constants.generalUnit * 4,
26 | color: palette.text.primary,
27 | },
28 | })
29 | })
30 |
31 | const renderActiveShape = (props: any, fill: string) => {
32 | const { cx, cy, innerRadius, outerRadius, startAngle, endAngle, payload } = props
33 |
34 | return (
35 |
36 |
37 | {payload.name}
38 |
39 |
48 |
57 |
58 | )
59 | }
60 |
61 | const AltAirPercentage: React.FC = () => {
62 | const classes = useStyles()
63 | const theme: ECTheme = useTheme()
64 | const [activeIndex, setActiveIndex] = useState(0)
65 |
66 | const onPieEnter = (_: any, index: number) => {
67 | setActiveIndex(index)
68 | }
69 |
70 | const { altAirPercentage } = useEth2CrawlerApi()
71 |
72 | const data = useMemo(() => {
73 | return altAirPercentage !== undefined
74 | ? [
75 | {
76 | name: `Nodes ready (${altAirPercentage.toFixed(1)})%`,
77 | value: altAirPercentage,
78 | },
79 | {
80 | name: `Nodes not ready (${(100 - altAirPercentage).toFixed(1)})%`,
81 | value: 100 - altAirPercentage,
82 | },
83 | ]
84 | : []
85 | }, [altAirPercentage])
86 |
87 | return (
88 |
89 |
90 | Nodes ready for Altair upgrade
91 |
92 |
93 |
94 |
95 |
98 | renderActiveShape(props, theme.constants.chartPrimaryColors.main)
99 | }
100 | data={data}
101 | cx="50%"
102 | cy="50%"
103 | innerRadius={100}
104 | outerRadius={120}
105 | fill={theme.constants.chartPrimaryColors.main}
106 | dataKey="value"
107 | onMouseEnter={onPieEnter}
108 | />
109 |
110 |
111 |
112 |
113 | )
114 | }
115 |
116 | export default AltAirPercentage
117 |
--------------------------------------------------------------------------------
/src/Components/Modules/SoftwareStats/NetworkTypes.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React, { useMemo } from "react"
6 | import { createStyles, makeStyles, useTheme } from "@chainsafe/common-theme"
7 | import { Typography } from "@chainsafe/common-components"
8 | import { useEth2CrawlerApi } from "../../../Contexts/Eth2CrawlerContext"
9 | import { ECTheme } from "../../Themes/types"
10 | import { BarChart, Bar, Tooltip, XAxis, YAxis, ResponsiveContainer } from "recharts"
11 |
12 | const useStyles = makeStyles(({ palette, constants }: ECTheme) => {
13 | return createStyles({
14 | root: {
15 | border: `1px solid ${palette.background.paper}`,
16 | borderRadius: "3px",
17 | padding: constants.generalUnit * 2,
18 | width: "inherit",
19 | height: "inherit",
20 | },
21 | chartContainer: {
22 | height: `${constants.chartSizes.chartHeight}px`,
23 | },
24 | title: {
25 | marginBottom: constants.generalUnit * 2,
26 | color: palette.text.primary,
27 | },
28 | })
29 | })
30 |
31 | // const MIN_NETWORK_TYPE_COUNT = 50
32 |
33 | const NetworkTypes = () => {
34 | const classes = useStyles()
35 | const theme: ECTheme = useTheme()
36 |
37 | const { networks } = useEth2CrawlerApi()
38 |
39 | const chartData = useMemo(
40 | () =>
41 | networks
42 | .sort((first, second) => (first.count > second.count ? 1 : -1))
43 | // .filter((network) => network.count > MIN_NETWORK_TYPE_COUNT)
44 | .map((network) => ({
45 | name: network.name || "unknown",
46 | count: network.count,
47 | })),
48 | [networks]
49 | )
50 |
51 | return (
52 |
53 |
54 | Network types distribution
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | )
68 | }
69 |
70 | export default NetworkTypes
71 |
--------------------------------------------------------------------------------
/src/Components/Modules/SoftwareStats/OperatingSystems.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React, { useMemo } from "react"
6 | import { createStyles, makeStyles, useTheme } from "@chainsafe/common-theme"
7 | import { Typography } from "@chainsafe/common-components"
8 | import { useEth2CrawlerApi } from "../../../Contexts/Eth2CrawlerContext"
9 | import { ECTheme } from "../../Themes/types"
10 | import { BarChart, Bar, Tooltip, XAxis, YAxis, ResponsiveContainer } from "recharts"
11 |
12 | const useStyles = makeStyles(({ palette, constants }: ECTheme) => {
13 | return createStyles({
14 | root: {
15 | border: `1px solid ${palette.background.paper}`,
16 | borderRadius: "3px",
17 | padding: constants.generalUnit * 2,
18 | width: "inherit",
19 | height: "inherit",
20 | },
21 | chartContainer: {
22 | height: `${constants.chartSizes.chartHeight}px`,
23 | },
24 | title: {
25 | marginBottom: constants.generalUnit * 2,
26 | color: palette.text.primary,
27 | },
28 | })
29 | })
30 |
31 | // const MIN_OPERATING_SYSTEM_COUNT = 5
32 |
33 | const OperatingSystems = () => {
34 | const classes = useStyles()
35 | const theme: ECTheme = useTheme()
36 |
37 | const { operatingSystems } = useEth2CrawlerApi()
38 |
39 | const chartData = useMemo(
40 | () =>
41 | operatingSystems
42 | .sort((first, second) => (first.count > second.count ? 1 : -1))
43 | // .filter((operatingSystem) => operatingSystem.count > MIN_OPERATING_SYSTEM_COUNT)
44 | .map((operatingSystem) => ({
45 | name: operatingSystem.name || "unknown",
46 | count: operatingSystem.count,
47 | })),
48 | [operatingSystems]
49 | )
50 |
51 | return (
52 |
53 |
54 | Operating systems distribution
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | )
68 | }
69 |
70 | export default OperatingSystems
71 |
--------------------------------------------------------------------------------
/src/Components/Modules/SoftwareStats/PercentageOfNodes.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React from "react"
6 | import { createStyles, makeStyles } from "@chainsafe/common-theme"
7 | import { ECTheme } from "../../Themes/types"
8 | import { Typography } from "@chainsafe/common-components"
9 |
10 | const useStyles = makeStyles(({ palette, constants }: ECTheme) => {
11 | return createStyles({
12 | root: {
13 | border: `1px solid ${palette.background.paper}`,
14 | borderRadius: "3px",
15 | padding: constants.generalUnit * 2,
16 | },
17 | title: {
18 | marginBottom: constants.generalUnit * 4,
19 | },
20 | statTitle: {
21 | color: palette.text.primary,
22 | },
23 | })
24 | })
25 |
26 | const PERCENT = 2.24
27 |
28 | const PercentageOfNodes = () => {
29 | const classes = useStyles()
30 |
31 | return (
32 |
33 |
34 | Nodes out of sync for the past 5 days
35 |
36 |
37 |
38 | {PERCENT}%
39 |
40 |
41 | of nodes contain out of sync nodes
42 |
43 |
44 |
45 | )
46 | }
47 |
48 | export default PercentageOfNodes
49 |
--------------------------------------------------------------------------------
/src/Components/Modules/SoftwareStats/VersionVariance.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React, { useMemo } from "react"
6 | import { createStyles, makeStyles, useTheme } from "@chainsafe/common-theme"
7 | import { ECTheme } from "../../Themes/types"
8 | import { Typography } from "@chainsafe/common-components"
9 | import { useEth2CrawlerApi } from "../../../Contexts/Eth2CrawlerContext"
10 | import { BarChart, Bar, Tooltip, XAxis, YAxis, ResponsiveContainer } from "recharts"
11 | import { useCallback } from "react"
12 | import ToolTipIcon from "../../Elements/Icons/ToolTipIcon"
13 | import ReactTooltip from "react-tooltip"
14 |
15 | const useStyles = makeStyles(({ palette, constants }: ECTheme) => {
16 | return createStyles({
17 | root: {
18 | border: `1px solid ${palette.background.paper}`,
19 | borderRadius: "3px",
20 | padding: constants.generalUnit * 2,
21 | width: "inherit",
22 | },
23 | chartContainer: {
24 | height: "280px",
25 | },
26 | title: {
27 | color: palette.text.primary,
28 | },
29 | charts: {
30 | display: "grid",
31 | gridTemplateColumns: "1fr 1fr",
32 | },
33 | eachChart: {
34 | width: "25%",
35 | height: "100%",
36 | },
37 | tooltipIcon: {
38 | width: 16,
39 | height: 16,
40 | marginLeft: constants.generalUnit,
41 | },
42 | headingContainer: {
43 | display: "flex",
44 | alignItems: "center",
45 | color: palette.additional["gray"][2],
46 | marginBottom: constants.generalUnit * 2,
47 | },
48 | })
49 | })
50 |
51 | const VersionVariance = () => {
52 | const classes = useStyles()
53 | const theme: ECTheme = useTheme()
54 | const backgroundColors = Object.values(theme.constants.chartColors)
55 |
56 | const { clientVersions } = useEth2CrawlerApi()
57 |
58 | const sortedClientVersions = useMemo(
59 | () =>
60 | clientVersions
61 | .sort((a, b) => (a.count < b.count ? -1 : 1))
62 | .map((clientVersion) => {
63 | const versions = clientVersion.versions.sort((a, b) => (a.count > b.count ? -1 : 1))
64 | if (versions.length > 5) {
65 | const first4Versions = []
66 | for (let i = 0; i < 4; i++) {
67 | first4Versions.push(versions[i])
68 | }
69 | let othersCount = 0
70 | for (let i = 4; i < versions.length; i++) {
71 | othersCount += versions[i].count
72 | }
73 | return {
74 | ...clientVersion,
75 | versions: [...first4Versions, { name: "others", count: othersCount }],
76 | }
77 | } else {
78 | return {
79 | ...clientVersion,
80 | versions,
81 | }
82 | }
83 | }),
84 | [clientVersions]
85 | )
86 |
87 | const chartData = useMemo(
88 | () =>
89 | sortedClientVersions
90 | .map((clientVersion) => {
91 | const stack: Record = {
92 | name: clientVersion.client,
93 | }
94 | clientVersion.versions.forEach((version) => {
95 | stack[`${clientVersion.client} ${version.name}`] = version.count
96 | })
97 | return stack
98 | })
99 | .flat(),
100 | [sortedClientVersions]
101 | )
102 |
103 | const getUniqueBars = useCallback(() => {
104 | const bars: any[] = []
105 | sortedClientVersions.forEach((clientVersion) => {
106 | clientVersion.versions.forEach((version, j) => {
107 | if (!bars.find((bar) => bar.key === `${clientVersion.client} ${version.name}`)) {
108 | bars.push({
109 | key: `${clientVersion.client} ${version.name}`,
110 | dataKey: `${clientVersion.client} ${version.name}`,
111 | stackId: clientVersion.client,
112 | fill: backgroundColors[j],
113 | count: version.count,
114 | })
115 | }
116 | })
117 | })
118 | return bars
119 | }, [sortedClientVersions, backgroundColors])
120 |
121 | const bars = getUniqueBars()
122 |
123 | return (
124 |
125 |
126 |
127 | Version variance across clients
128 |
129 |
130 |
131 |
132 | Shows variations in version of node clients
133 | Shows top 5 versions of known clients
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 | {bars.map((bar) => (
144 |
145 | ))}
146 |
147 |
148 |
149 |
150 | )
151 | }
152 |
153 | export default VersionVariance
154 |
--------------------------------------------------------------------------------
/src/Components/Pages/HomePage.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React from "react"
6 | import { createStyles, makeStyles } from "@chainsafe/common-theme"
7 | import { ECTheme } from "../Themes/types"
8 | import ClientTypes from "../Modules/DemographicsStats/ClientTypes"
9 | import HeatMap from "../Modules/HeatMap/MapLeaflet"
10 | import NetworkTypes from "../Modules/SoftwareStats/NetworkTypes"
11 | import OperatingSystems from "../Modules/SoftwareStats/OperatingSystems"
12 | import { useEth2CrawlerApi } from "../../Contexts/Eth2CrawlerContext"
13 | import VersionVariance from "../Modules/SoftwareStats/VersionVariance"
14 | import SectionTile from "../Layouts/SectionTile/SectionTile"
15 | import CardStat from "../Layouts/SectionTile/CardStat"
16 | import NodeStatusOverTime from "../Modules/NodeStats/NodeStatsOverTime"
17 | import GridLayoutWrapper from "../Layouts/GridLayout/GridLayoutWrapper"
18 | import { Typography } from "@chainsafe/common-components"
19 | import CountryStats from "../Modules/CountryStats"
20 | import AltAirPercentage from "../Modules/SoftwareStats/AltAirPercentage"
21 |
22 | const useStyles = makeStyles(({ constants, breakpoints, palette }: ECTheme) => {
23 | return createStyles({
24 | root: {
25 | margin: `${constants.generalUnit * 11}px 0 ${constants.generalUnit * 8}px`,
26 | [breakpoints.down("lg")]: {
27 | margin: `${constants.generalUnit * 11}px ${constants.generalUnit * 4}px`,
28 | },
29 | [breakpoints.down("md")]: {
30 | margin: `${constants.generalUnit * 11}px ${constants.generalUnit * 1}px`,
31 | },
32 | background: palette.background.default,
33 | },
34 | title: {
35 | marginRight: constants.generalUnit,
36 | marginBottom: constants.generalUnit * 3,
37 | },
38 | nodeDemographics: {},
39 | nodeMapRoot: {
40 | height: "50vh",
41 | width: "100%",
42 | [breakpoints.down("lg")]: {
43 | height: "50vh",
44 | },
45 | [breakpoints.down("md")]: {
46 | height: "45vh",
47 | },
48 | [breakpoints.down("sm")]: {
49 | height: "40vh",
50 | },
51 | },
52 | nodeStats: {
53 | display: "grid",
54 | gridColumnGap: constants.generalUnit,
55 | gridRowGap: constants.generalUnit * 3,
56 | gridTemplateColumns: "repeat(2, minmax(0,1fr))",
57 | maxWidth: "100%",
58 | [breakpoints.down(1099)]: {
59 | gridTemplateColumns: "repeat(1, minmax(0,1fr))",
60 | },
61 | marginBottom: constants.generalUnit * 4,
62 | },
63 | container: {
64 | marginBottom: constants.generalUnit * 4,
65 | },
66 | })
67 | })
68 |
69 | function HomePage() {
70 | const classes = useStyles()
71 | const { nodeStats, nodeRegionalStats } = useEth2CrawlerApi()
72 |
73 | return (
74 |
75 |
79 |
80 |
86 | If the head of a node is within 256 epochs or 1 day
87 | of the head of the chain, we consider it synced.
88 |
89 | }
90 | tooltipId="syncedPercentage"
91 | />
92 |
98 | If the head of a node is behind the head of the chain
99 | by 256 epochs or 1 day, we consider it unsynced.
100 |
101 | }
102 | tooltipId="unsyncedPercentage"
103 | />
104 | >
105 | }
106 | >
107 |
108 |
109 |
113 |
117 |
126 | If a node is running on a cloud service,
127 | we consider it hosted.
128 |
129 | }
130 | tooltipId="hostedPercentage"
131 | />
132 |
141 | If a node is running on a network other than a cloud service, such as
142 | residential or business, we consider it non-hosted.
143 |
144 | }
145 | tooltipId="nonHostedPercentage"
146 | />
147 | >
148 | }
149 | >
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 | )
166 | }
167 |
168 | export default HomePage
169 |
--------------------------------------------------------------------------------
/src/Components/Themes/constants.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import { IConstants } from "@chainsafe/common-theme"
6 |
7 | export interface EcConstants extends IConstants {
8 | chartPrimaryColors: {
9 | main: string
10 | light: string
11 | dark: string
12 | }
13 | chartColors: {
14 | color1: string
15 | color2: string
16 | color3: string
17 | color4: string
18 | color5: string
19 | }
20 | statColors: {
21 | red: string
22 | blue: string
23 | green: string
24 | }
25 | chartSizes: {
26 | chartBoxHeight: number
27 | chartHeight: number
28 | }
29 | headerHeight: number
30 | }
31 |
--------------------------------------------------------------------------------
/src/Components/Themes/theme.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import { createTheme } from "@chainsafe/common-theme"
6 | import { EcConstants } from "./constants"
7 |
8 | export const theme = createTheme({
9 | themeConfig: {
10 | palette: {
11 | primary: {
12 | main: "#B7C1FC",
13 | hover: "#F9B189",
14 | background: "#131825",
15 | },
16 | secondary: {
17 | main: "#E4665C",
18 | },
19 | background: {
20 | default: "#131825",
21 | paper: "#424F60",
22 | },
23 | text: {
24 | primary: "#f6f6f6",
25 | },
26 | },
27 | constants: {
28 | ...({
29 | chartPrimaryColors: {
30 | main: "#566BDF",
31 | light: "2E00B0",
32 | dark: "#000000",
33 | },
34 | chartColors: {
35 | color1: "#566BDF",
36 | color2: "#10B981",
37 | color3: "#E4665C",
38 | color4: "#FFA113",
39 | color5: "#8C14EB",
40 | },
41 | statColors: {
42 | red: "#E4665C",
43 | blue: "#B7C1FC",
44 | green: "#10B981",
45 | },
46 | chartSizes: {
47 | chartBoxHeight: 300,
48 | chartHeight: 280,
49 | },
50 | headerHeight: 50,
51 | } as EcConstants),
52 | },
53 | overrides: {
54 | Typography: {
55 | h1: {
56 | fontSize: 48,
57 | lineHeight: "58px",
58 | fontStyle: "normal",
59 | fontWeight: "normal",
60 | },
61 | h2: {
62 | fontSize: "38.0413px",
63 | lineHeight: "45px",
64 | fontStyle: "normal",
65 | fontWeight: "normal",
66 | },
67 | h3: {
68 | fontSize: "22.3773px",
69 | lineHeight: "31px",
70 | fontStyle: "normal",
71 | fontWeight: "normal",
72 | },
73 | h4: {
74 | fontSize: 20,
75 | lineHeight: "28px",
76 | fontStyle: "normal",
77 | fontWeight: "normal",
78 | },
79 | },
80 | },
81 | },
82 | })
83 |
--------------------------------------------------------------------------------
/src/Components/Themes/types.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import { ITheme } from "@chainsafe/common-theme"
6 | import { EcConstants } from "./constants"
7 |
8 | export type ECTheme = ITheme
9 |
--------------------------------------------------------------------------------
/src/Contexts/Eth2CrawlerContext.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React, { useState, useEffect } from "react"
6 | import { GraphQLClient } from "graphql-request"
7 | import {
8 | GetClientCounts,
9 | GetClientCounts_aggregateByAgentName,
10 | } from "../GraphQL/types/GetClientCounts"
11 | import {
12 | GetOperatingSystems,
13 | GetOperatingSystems_aggregateByOperatingSystem,
14 | } from "../GraphQL/types/GetOperatingSystems"
15 | import { GetNetworks, GetNetworks_aggregateByNetwork } from "../GraphQL/types/GetNetworks"
16 | import { GetHeatmap, GetHeatmap_getHeatmapData } from "../GraphQL/types/GetHeatmap"
17 | import {
18 | GetClientVersions,
19 | GetClientVersions_aggregateByClientVersion,
20 | } from "../GraphQL/types/GetClientVersions"
21 | import {
22 | LOAD_CLIENTS,
23 | LOAD_NETWORKS,
24 | LOAD_OPERATING_SYSTEMS,
25 | LOAD_HEATMAP,
26 | LOAD_CLIENT_VERSIONS,
27 | LOAD_NODE_COUNTS,
28 | LOAD_NODE_COUNT_OVER_TIME,
29 | LOAD_REGIONAL_STATS,
30 | LOAD_NODES_BY_COUNTRIES,
31 | LOAD_ALTAIR_UPGRADE_PERCENTAGE,
32 | } from "../GraphQL/Queries"
33 | import { GetNodeStats, GetNodeStats_getNodeStats } from "../GraphQL/types/GetNodeStats"
34 | import {
35 | GetNodeStatsOverTime,
36 | GetNodeStatsOverTime_getNodeStatsOverTime,
37 | } from "../GraphQL/types/GetNodeStatsOverTime"
38 | import { getUnixTimeStampCurrent, getUnixTimeStampFromDaysBefore } from "../utils/dateUtils"
39 | import {
40 | GetRegionalStats,
41 | GetRegionalStats_getRegionalStats,
42 | } from "../GraphQL/types/getRegionalStats"
43 | import {
44 | GetNodesByCountries,
45 | GetNodesByCountries_aggregateByCountry,
46 | } from "../GraphQL/types/GetNodesByCountries"
47 | import { GetAltAirUpgradePercentage } from "../GraphQL/types/GetAltAirUpgradePercentage"
48 |
49 | type Eth2CrawlerContextProps = {
50 | children: React.ReactNode | React.ReactNode[]
51 | }
52 |
53 | interface IEth2CrawlerContext {
54 | clients: GetClientCounts_aggregateByAgentName[]
55 | operatingSystems: GetOperatingSystems_aggregateByOperatingSystem[]
56 | networks: GetNetworks_aggregateByNetwork[]
57 | clientVersions: GetClientVersions_aggregateByClientVersion[]
58 | heatmap: GetHeatmap_getHeatmapData[]
59 | nodeStats: GetNodeStats_getNodeStats | undefined
60 | nodeStatsOverTime: GetNodeStatsOverTime_getNodeStatsOverTime[]
61 | nodeRegionalStats: GetRegionalStats_getRegionalStats | undefined
62 | nodeCountByCountries: GetNodesByCountries_aggregateByCountry[]
63 | altAirPercentage: number | undefined
64 | isLoadingClients: boolean
65 | isLoadingOperatingSystems: boolean
66 | isLoadingNetworks: boolean
67 | isLoadingHeatmap: boolean
68 | isLoadingClientVersions: boolean
69 | isLoadingNodeStats: boolean
70 | isLoadingNodeStatsOverTime: boolean
71 | isLoadingNodeRegionalStats: boolean
72 | isLoadingNodeCountByCountries: boolean
73 | isLoadingAltAirPercentage: boolean
74 | }
75 |
76 | const Eth2CrawlerContext = React.createContext(undefined)
77 |
78 | const subgraphUrl = process.env.REACT_APP_GRAPHQL_URL || ""
79 | const graphClient = new GraphQLClient(subgraphUrl)
80 |
81 | const Eth2CrawlerProvider = ({ children }: Eth2CrawlerContextProps) => {
82 | const [nodeStats, setNodeStats] = useState(undefined)
83 | const [nodeRegionalStats, setNodeRegionalStats] = useState<
84 | GetRegionalStats_getRegionalStats | undefined
85 | >(undefined)
86 | const [nodeStatsOverTime, setNodeStatsOverTime] = useState<
87 | GetNodeStatsOverTime_getNodeStatsOverTime[]
88 | >([])
89 | const [clients, setClients] = useState([])
90 | const [operatingSystems, setOperatingSystems] = useState<
91 | GetOperatingSystems_aggregateByOperatingSystem[]
92 | >([])
93 | const [networks, setNetworks] = useState([])
94 | const [clientVersions, setClientVersions] = useState<
95 | GetClientVersions_aggregateByClientVersion[]
96 | >([])
97 | const [heatmap, setHeatmap] = useState([])
98 | const [nodeCountByCountries, setNodeCountByCountries] = useState<
99 | GetNodesByCountries_aggregateByCountry[]
100 | >([])
101 | const [altAirPercentage, setAltAirPercentage] = useState(undefined)
102 |
103 | const [isLoadingClients, setIsLoadingClients] = useState(true)
104 | const [isLoadingOperatingSystems, setIsLoadingOperatingSystems] = useState(true)
105 | const [isLoadingNetworks, setIsLoadingNetworks] = useState(true)
106 | const [isLoadingHeatmap, setIsLoadingHeatmap] = useState(true)
107 | const [isLoadingClientVersions, setIsLoadingClientVersions] = useState(true)
108 | const [isLoadingNodeStats, setIsLoadingNodeStats] = useState(true)
109 | const [isLoadingNodeStatsOverTime, setIsLoadingNodeStatsOverTime] = useState(true)
110 | const [isLoadingNodeRegionalStats, setIsLoadingNodeRegionalStats] = useState(true)
111 | const [isLoadingNodeCountByCountries, setIsLoadingNodeCountByCountries] = useState(true)
112 | const [isLoadingAltAirPercentage, setIsLoadingAltAirPercentage] = useState(true)
113 |
114 | const getInitialData = async () => {
115 | graphClient
116 | .request(LOAD_NODE_COUNTS, {
117 | percentage: 15,
118 | })
119 | .then((result) => {
120 | setNodeStats(result.getNodeStats)
121 | })
122 | .catch(console.error)
123 | .finally(() => setIsLoadingNodeStats(false))
124 | graphClient
125 | .request(LOAD_NODE_COUNT_OVER_TIME, {
126 | start: getUnixTimeStampFromDaysBefore(7),
127 | end: getUnixTimeStampCurrent(),
128 | })
129 | .then((result) => {
130 | setNodeStatsOverTime(result.getNodeStatsOverTime)
131 | })
132 | .catch(console.error)
133 | .finally(() => setIsLoadingNodeStatsOverTime(false))
134 | graphClient
135 | .request(LOAD_REGIONAL_STATS)
136 | .then((result) => {
137 | setNodeRegionalStats(result.getRegionalStats)
138 | })
139 | .catch(console.error)
140 | .finally(() => setIsLoadingNodeRegionalStats(false))
141 | graphClient
142 | .request(LOAD_CLIENTS)
143 | .then((result) => {
144 | setClients(result.aggregateByAgentName)
145 | })
146 | .catch(console.error)
147 | .finally(() => setIsLoadingClients(false))
148 | graphClient
149 | .request(LOAD_OPERATING_SYSTEMS)
150 | .then((result) => {
151 | setOperatingSystems(result.aggregateByOperatingSystem)
152 | })
153 | .catch(console.error)
154 | .finally(() => setIsLoadingOperatingSystems(false))
155 | graphClient
156 | .request(LOAD_NETWORKS)
157 | .then((result) => {
158 | setNetworks(result.aggregateByNetwork)
159 | })
160 | .catch(console.error)
161 | .finally(() => setIsLoadingNetworks(false))
162 | graphClient
163 | .request(LOAD_HEATMAP)
164 | .then((result) => {
165 | setHeatmap(result.getHeatmapData)
166 | })
167 | .catch(console.error)
168 | .finally(() => setIsLoadingHeatmap(false))
169 | graphClient
170 | .request(LOAD_CLIENT_VERSIONS)
171 | .then((result) => {
172 | setClientVersions(result.aggregateByClientVersion)
173 | })
174 | .catch(console.error)
175 | .finally(() => setIsLoadingClientVersions(false))
176 | graphClient
177 | .request(LOAD_NODES_BY_COUNTRIES)
178 | .then((result) => {
179 | setNodeCountByCountries(result.aggregateByCountry)
180 | })
181 | .catch(console.error)
182 | .finally(() => setIsLoadingNodeCountByCountries(false))
183 | graphClient
184 | .request(LOAD_ALTAIR_UPGRADE_PERCENTAGE)
185 | .then((result) => {
186 | setAltAirPercentage(result.getAltairUpgradePercentage)
187 | })
188 | .catch(console.error)
189 | .finally(() => setIsLoadingAltAirPercentage(false))
190 | }
191 |
192 | useEffect(() => {
193 | getInitialData()
194 | }, [])
195 |
196 | return (
197 |
221 | {children}
222 |
223 | )
224 | }
225 |
226 | const useEth2CrawlerApi = () => {
227 | const context = React.useContext(Eth2CrawlerContext)
228 | if (context === undefined) {
229 | throw new Error("useEth2CrawlerApi must be used within a Eth2CrawlerProvider")
230 | }
231 | return context
232 | }
233 |
234 | export { Eth2CrawlerProvider, useEth2CrawlerApi }
235 |
--------------------------------------------------------------------------------
/src/GraphQL/Queries.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import { gql } from "apollo-boost"
6 |
7 | export const LOAD_NODE_COUNTS = gql`
8 | query GetNodeStats {
9 | getNodeStats {
10 | totalNodes
11 | nodeSyncedPercentage
12 | nodeUnsyncedPercentage
13 | }
14 | }
15 | `
16 |
17 | export const LOAD_NODE_COUNT_OVER_TIME = gql`
18 | query GetNodeStatsOverTime($start: Float!, $end: Float!) {
19 | getNodeStatsOverTime(start: $start, end: $end) {
20 | time
21 | totalNodes
22 | syncedNodes
23 | unsyncedNodes
24 | }
25 | }
26 | `
27 |
28 | export const LOAD_REGIONAL_STATS = gql`
29 | query GetRegionalStats {
30 | getRegionalStats {
31 | totalParticipatingCountries
32 | hostedNodePercentage
33 | nonhostedNodePercentage
34 | }
35 | }
36 | `
37 |
38 | export const LOAD_CLIENTS = gql`
39 | query GetClientCounts {
40 | aggregateByAgentName {
41 | name
42 | count
43 | }
44 | }
45 | `
46 |
47 | export const LOAD_OPERATING_SYSTEMS = gql`
48 | query GetOperatingSystems {
49 | aggregateByOperatingSystem {
50 | name
51 | count
52 | }
53 | }
54 | `
55 |
56 | export const LOAD_NETWORKS = gql`
57 | query GetNetworks {
58 | aggregateByNetwork {
59 | name
60 | count
61 | }
62 | }
63 | `
64 |
65 | export const LOAD_HEATMAP = gql`
66 | query GetHeatmap {
67 | getHeatmapData {
68 | networkType
69 | clientType
70 | syncStatus
71 | latitude
72 | longitude
73 | }
74 | }
75 | `
76 |
77 | export const LOAD_CLIENT_VERSIONS = gql`
78 | query GetClientVersions {
79 | aggregateByClientVersion {
80 | client
81 | count
82 | versions {
83 | name
84 | count
85 | }
86 | }
87 | }
88 | `
89 |
90 | export const LOAD_NODES_BY_COUNTRIES = gql`
91 | query GetNodesByCountries {
92 | aggregateByCountry {
93 | name
94 | count
95 | }
96 | }
97 | `
98 |
99 | export const LOAD_ALTAIR_UPGRADE_PERCENTAGE = gql`
100 | query GetAltAirUpgradePercentage {
101 | getAltairUpgradePercentage
102 | }
103 | `
104 |
--------------------------------------------------------------------------------
/src/GraphQL/types/GetAltAirUpgradePercentage.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | /* tslint:disable */
6 | /* eslint-disable */
7 | // @generated
8 | // This file was automatically generated and should not be edited.
9 |
10 | // ====================================================
11 | // GraphQL query operation: GetAltAirUpgradePercentage
12 | // ====================================================
13 |
14 | export interface GetAltAirUpgradePercentage {
15 | getAltairUpgradePercentage: number;
16 | }
17 |
--------------------------------------------------------------------------------
/src/GraphQL/types/GetClientCounts.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | /* tslint:disable */
6 | /* eslint-disable */
7 | // @generated
8 | // This file was automatically generated and should not be edited.
9 |
10 | // ====================================================
11 | // GraphQL query operation: GetClientCounts
12 | // ====================================================
13 |
14 | export interface GetClientCounts_aggregateByAgentName {
15 | __typename: "AggregateData";
16 | name: string;
17 | count: number;
18 | }
19 |
20 | export interface GetClientCounts {
21 | aggregateByAgentName: GetClientCounts_aggregateByAgentName[];
22 | }
23 |
--------------------------------------------------------------------------------
/src/GraphQL/types/GetClientVersions.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | /* tslint:disable */
6 | /* eslint-disable */
7 | // @generated
8 | // This file was automatically generated and should not be edited.
9 |
10 | // ====================================================
11 | // GraphQL query operation: GetClientVersions
12 | // ====================================================
13 |
14 | export interface GetClientVersions_aggregateByClientVersion_versions {
15 | __typename: "AggregateData";
16 | name: string;
17 | count: number;
18 | }
19 |
20 | export interface GetClientVersions_aggregateByClientVersion {
21 | __typename: "ClientVersionAggregation";
22 | client: string;
23 | count: number;
24 | versions: GetClientVersions_aggregateByClientVersion_versions[];
25 | }
26 |
27 | export interface GetClientVersions {
28 | aggregateByClientVersion: GetClientVersions_aggregateByClientVersion[];
29 | }
30 |
--------------------------------------------------------------------------------
/src/GraphQL/types/GetHeatmap.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | /* tslint:disable */
6 | /* eslint-disable */
7 | // @generated
8 | // This file was automatically generated and should not be edited.
9 |
10 | // ====================================================
11 | // GraphQL query operation: GetHeatmap
12 | // ====================================================
13 |
14 | export interface GetHeatmap_getHeatmapData {
15 | __typename: "HeatmapData";
16 | networkType: string;
17 | clientType: string;
18 | syncStatus: string;
19 | latitude: number;
20 | longitude: number;
21 | }
22 |
23 | export interface GetHeatmap {
24 | getHeatmapData: GetHeatmap_getHeatmapData[];
25 | }
26 |
--------------------------------------------------------------------------------
/src/GraphQL/types/GetNetworks.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | /* tslint:disable */
6 | /* eslint-disable */
7 | // @generated
8 | // This file was automatically generated and should not be edited.
9 |
10 | // ====================================================
11 | // GraphQL query operation: GetNetworks
12 | // ====================================================
13 |
14 | export interface GetNetworks_aggregateByNetwork {
15 | __typename: "AggregateData";
16 | name: string;
17 | count: number;
18 | }
19 |
20 | export interface GetNetworks {
21 | aggregateByNetwork: GetNetworks_aggregateByNetwork[];
22 | }
23 |
--------------------------------------------------------------------------------
/src/GraphQL/types/GetNodeStats.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | /* tslint:disable */
6 | /* eslint-disable */
7 | // @generated
8 | // This file was automatically generated and should not be edited.
9 |
10 | // ====================================================
11 | // GraphQL query operation: GetNodeStats
12 | // ====================================================
13 |
14 | export interface GetNodeStats_getNodeStats {
15 | __typename: "NodeStats";
16 | totalNodes: number;
17 | nodeSyncedPercentage: number;
18 | nodeUnsyncedPercentage: number;
19 | }
20 |
21 | export interface GetNodeStats {
22 | getNodeStats: GetNodeStats_getNodeStats;
23 | }
24 |
--------------------------------------------------------------------------------
/src/GraphQL/types/GetNodeStatsOverTime.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | /* tslint:disable */
6 | /* eslint-disable */
7 | // @generated
8 | // This file was automatically generated and should not be edited.
9 |
10 | // ====================================================
11 | // GraphQL query operation: GetNodeStatsOverTime
12 | // ====================================================
13 |
14 | export interface GetNodeStatsOverTime_getNodeStatsOverTime {
15 | __typename: "NodeStatsOverTime";
16 | time: number;
17 | totalNodes: number;
18 | syncedNodes: number;
19 | unsyncedNodes: number;
20 | }
21 |
22 | export interface GetNodeStatsOverTime {
23 | getNodeStatsOverTime: GetNodeStatsOverTime_getNodeStatsOverTime[];
24 | }
25 |
26 | export interface GetNodeStatsOverTimeVariables {
27 | start: number;
28 | end: number;
29 | }
30 |
--------------------------------------------------------------------------------
/src/GraphQL/types/GetNodesByCountries.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | /* tslint:disable */
6 | /* eslint-disable */
7 | // @generated
8 | // This file was automatically generated and should not be edited.
9 |
10 | // ====================================================
11 | // GraphQL query operation: GetNodesByCountries
12 | // ====================================================
13 |
14 | export interface GetNodesByCountries_aggregateByCountry {
15 | __typename: "AggregateData";
16 | name: string;
17 | count: number;
18 | }
19 |
20 | export interface GetNodesByCountries {
21 | aggregateByCountry: GetNodesByCountries_aggregateByCountry[];
22 | }
23 |
--------------------------------------------------------------------------------
/src/GraphQL/types/GetOperatingSystems.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | /* tslint:disable */
6 | /* eslint-disable */
7 | // @generated
8 | // This file was automatically generated and should not be edited.
9 |
10 | // ====================================================
11 | // GraphQL query operation: GetOperatingSystems
12 | // ====================================================
13 |
14 | export interface GetOperatingSystems_aggregateByOperatingSystem {
15 | __typename: "AggregateData";
16 | name: string;
17 | count: number;
18 | }
19 |
20 | export interface GetOperatingSystems {
21 | aggregateByOperatingSystem: GetOperatingSystems_aggregateByOperatingSystem[];
22 | }
23 |
--------------------------------------------------------------------------------
/src/GraphQL/types/getRegionalStats.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | /* tslint:disable */
6 | /* eslint-disable */
7 | // @generated
8 | // This file was automatically generated and should not be edited.
9 |
10 | // ====================================================
11 | // GraphQL query operation: GetRegionalStats
12 | // ====================================================
13 |
14 | export interface GetRegionalStats_getRegionalStats {
15 | __typename: "RegionalStats";
16 | totalParticipatingCountries: number;
17 | hostedNodePercentage: number;
18 | nonhostedNodePercentage: number;
19 | }
20 |
21 | export interface GetRegionalStats {
22 | getRegionalStats: GetRegionalStats_getRegionalStats;
23 | }
24 |
--------------------------------------------------------------------------------
/src/assets/fonts/Neue-montreal/NeueMontreal-Bold.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChainSafe/nodewatch-ui/423a1184c5961462f98497c87aed21934fb5f03e/src/assets/fonts/Neue-montreal/NeueMontreal-Bold.otf
--------------------------------------------------------------------------------
/src/assets/fonts/Neue-montreal/NeueMontreal-BoldItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChainSafe/nodewatch-ui/423a1184c5961462f98497c87aed21934fb5f03e/src/assets/fonts/Neue-montreal/NeueMontreal-BoldItalic.otf
--------------------------------------------------------------------------------
/src/assets/fonts/Neue-montreal/NeueMontreal-Italic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChainSafe/nodewatch-ui/423a1184c5961462f98497c87aed21934fb5f03e/src/assets/fonts/Neue-montreal/NeueMontreal-Italic.otf
--------------------------------------------------------------------------------
/src/assets/fonts/Neue-montreal/NeueMontreal-Light.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChainSafe/nodewatch-ui/423a1184c5961462f98497c87aed21934fb5f03e/src/assets/fonts/Neue-montreal/NeueMontreal-Light.otf
--------------------------------------------------------------------------------
/src/assets/fonts/Neue-montreal/NeueMontreal-LightItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChainSafe/nodewatch-ui/423a1184c5961462f98497c87aed21934fb5f03e/src/assets/fonts/Neue-montreal/NeueMontreal-LightItalic.otf
--------------------------------------------------------------------------------
/src/assets/fonts/Neue-montreal/NeueMontreal-Medium.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChainSafe/nodewatch-ui/423a1184c5961462f98497c87aed21934fb5f03e/src/assets/fonts/Neue-montreal/NeueMontreal-Medium.otf
--------------------------------------------------------------------------------
/src/assets/fonts/Neue-montreal/NeueMontreal-MediumItalic.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChainSafe/nodewatch-ui/423a1184c5961462f98497c87aed21934fb5f03e/src/assets/fonts/Neue-montreal/NeueMontreal-MediumItalic.otf
--------------------------------------------------------------------------------
/src/assets/fonts/Neue-montreal/NeueMontreal-Regular.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ChainSafe/nodewatch-ui/423a1184c5961462f98497c87aed21934fb5f03e/src/assets/fonts/Neue-montreal/NeueMontreal-Regular.otf
--------------------------------------------------------------------------------
/src/dummyData/demographicsData.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import { ClientType } from "../types/main"
6 |
7 | export const clients: {
8 | client: ClientType
9 | total: number
10 | }[] = [
11 | {
12 | client: "geth",
13 | total: 23,
14 | },
15 | {
16 | client: "parity",
17 | total: 80,
18 | },
19 | {
20 | client: "getc",
21 | total: 30,
22 | },
23 | {
24 | client: "ethereumjs",
25 | total: 40,
26 | },
27 | {
28 | client: "multigeth",
29 | total: 50,
30 | },
31 | ]
32 |
--------------------------------------------------------------------------------
/src/dummyData/mapData.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import { NodeInfo } from "../types/main"
6 |
7 | export const nodeLocations: NodeInfo[] = [
8 | {
9 | name: "california",
10 | weight: 100,
11 | client: "geth",
12 | coordinates: [37.1930977, -123.7969029],
13 | },
14 | {
15 | name: "tokyo",
16 | weight: 30,
17 | client: "getc",
18 | coordinates: [35.5090627, 139.2093774],
19 | },
20 | {
21 | name: "johannesburg",
22 | weight: 50,
23 | client: "ethereumjs",
24 | coordinates: [-26.1713505, 27.9699847],
25 | },
26 | {
27 | name: "madrid",
28 | weight: 40,
29 | client: "multigeth",
30 | coordinates: [40.4381311, -3.8196201],
31 | },
32 | {
33 | name: "dhaka",
34 | weight: 70,
35 | client: "parity",
36 | coordinates: [23.7807777, 90.3492856],
37 | },
38 | ]
39 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | @font-face {
6 | font-family: "Neue Montreal";
7 | src: local("Neue Montreal"),
8 | url(./assets/fonts/Neue-montreal/NeueMontreal-Medium.otf) format("opentype");
9 | }
10 |
11 | body {
12 | margin: 0;
13 | font-family: Neue Montreal;
14 | -webkit-font-smoothing: antialiased;
15 | -moz-osx-font-smoothing: grayscale;
16 | }
17 |
18 | code {
19 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
20 | }
21 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import React from "react"
6 | import ReactDOM from "react-dom"
7 | import App from "./App"
8 | import reportWebVitals from "./reportWebVitals"
9 | import "./index.css"
10 |
11 | ReactDOM.render(
12 |
13 |
14 | ,
15 | document.getElementById("root")
16 | )
17 |
18 | // If you want to start measuring performance in your app, pass a function
19 | // to log results (for example: reportWebVitals(console.log))
20 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
21 | reportWebVitals()
22 |
--------------------------------------------------------------------------------
/src/react-app-env.d.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | ///
6 |
--------------------------------------------------------------------------------
/src/reportWebVitals.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import { ReportHandler } from "web-vitals"
6 |
7 | const reportWebVitals = (onPerfEntry?: ReportHandler) => {
8 | if (onPerfEntry && onPerfEntry instanceof Function) {
9 | import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
10 | getCLS(onPerfEntry)
11 | getFID(onPerfEntry)
12 | getFCP(onPerfEntry)
13 | getLCP(onPerfEntry)
14 | getTTFB(onPerfEntry)
15 | })
16 | }
17 | }
18 |
19 | export default reportWebVitals
20 |
--------------------------------------------------------------------------------
/src/setupTests.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
6 | // allows you to do things like:
7 | // expect(element).toHaveTextContent(/react/i)
8 | // learn more: https://github.com/testing-library/jest-dom
9 | import "@testing-library/jest-dom"
10 |
--------------------------------------------------------------------------------
/src/types/graphql-global-types.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | /* tslint:disable */
6 | /* eslint-disable */
7 | // @generated
8 | // This file was automatically generated and should not be edited.
9 |
10 | //==============================================================
11 | // START Enums and Input Objects
12 | //==============================================================
13 |
14 | //==============================================================
15 | // END Enums and Input Objects
16 | //==============================================================
17 | export {}
18 |
--------------------------------------------------------------------------------
/src/types/index.d.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | declare module "leaflet.heat"
6 |
--------------------------------------------------------------------------------
/src/types/main.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | export type ClientType = "geth" | "parity" | "ethereumjs" | "getc" | "nethermind" | "multigeth"
6 |
7 | export type NetworkType = "mainnet" | "classic" | "ropsten" | "rinkeby" | "kovan" | "goerli"
8 |
9 | export interface NodeInfo {
10 | name: string
11 | weight: number
12 | client: ClientType
13 | coordinates: [number, number]
14 | }
15 |
--------------------------------------------------------------------------------
/src/utilHooks/useWindowDimensions.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | import { useState, useEffect } from "react"
6 |
7 | function getWindowDimensions() {
8 | const { innerWidth: width, innerHeight: height } = window
9 | return {
10 | width,
11 | height,
12 | }
13 | }
14 |
15 | export default function useWindowDimensions() {
16 | const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions())
17 |
18 | useEffect(() => {
19 | function handleResize() {
20 | setWindowDimensions(getWindowDimensions())
21 | }
22 |
23 | window.addEventListener("resize", handleResize)
24 | return () => window.removeEventListener("resize", handleResize)
25 | }, [])
26 |
27 | return windowDimensions
28 | }
29 |
--------------------------------------------------------------------------------
/src/utils/dateUtils.ts:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright 2021 ChainSafe Systems
3 | SPDX-License-Identifier: LGPL-3.0-only
4 | */
5 | export const getUnixTimeStampFromDaysBefore = (noOfDays: number) => {
6 | const date = new Date()
7 | date.setDate(date.getDate() - noOfDays)
8 | return Math.round(date.getTime() / 1000)
9 | }
10 |
11 | export const getUnixTimeStampCurrent = () => {
12 | return Math.round(new Date().getTime() / 1000)
13 | }
14 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "lib": [
5 | "dom",
6 | "dom.iterable",
7 | "esnext"
8 | ],
9 | "allowJs": true,
10 | "skipLibCheck": true,
11 | "esModuleInterop": true,
12 | "allowSyntheticDefaultImports": true,
13 | "strict": true,
14 | "forceConsistentCasingInFileNames": true,
15 | "noFallthroughCasesInSwitch": true,
16 | "module": "esnext",
17 | "moduleResolution": "node",
18 | "resolveJsonModule": true,
19 | "isolatedModules": true,
20 | "noEmit": true,
21 | "jsx": "react-jsx"
22 | },
23 | "include": [
24 | "src"
25 | ]
26 | }
27 |
--------------------------------------------------------------------------------