From 4f0614f0ba737d9a4ff0623177f0cccb19b8ff82 Mon Sep 17 00:00:00 2001 From: Mohamed Mansour Date: Sat, 14 Aug 2021 00:49:24 -0700 Subject: [PATCH] Remove London Dashboard --- frontend/src/config.ts | 20 +- frontend/src/data/DataContext.tsx | 59 ----- frontend/src/organisms/Header.tsx | 9 +- frontend/src/pages/London.tsx | 225 ---------------- frontend/src/setupProxy.js | 399 +---------------------------- frontend/src/templates/Routing.tsx | 6 +- 6 files changed, 5 insertions(+), 713 deletions(-) delete mode 100644 frontend/src/data/DataContext.tsx delete mode 100644 frontend/src/pages/London.tsx diff --git a/frontend/src/config.ts b/frontend/src/config.ts index 6cb7ce2..ed2735b 100644 --- a/frontend/src/config.ts +++ b/frontend/src/config.ts @@ -4,11 +4,6 @@ import { FilterGroup, generateQueryStringFromFilterGroups } from "./data/FilterU export const colors = scaleOrdinal(schemeCategory10).range(); -export const normalizeClientNames: { [key: string]: string } = { - 'turbogeth': 'erigon', - 'turbo-geth': 'erigon', -} - export const knownNodesFilter: FilterGroup[] = [ [{ name: 'name', value: 'geth' }], [{ name: 'name', value: 'nethermind' }], @@ -20,21 +15,8 @@ export const knownNodesFilter: FilterGroup[] = [ [{ name: 'name', value: 'ethereum-js' }] ] -export const londonFilter: FilterGroup[] = [ - [{ name: 'name', value: 'geth' }, { name: 'version_major', value: '1', operator: 'gte' }, { name: 'version_minor', value: '10', operator: 'gte' }, { name: 'version_patch', value: '6', operator: 'gte' }], - [{ name: 'name', value: 'nethermind' }, { name: 'version_major', value: '1', operator: 'gte' }, { name: 'version_minor', value: '10', operator: 'gte' }, { name: 'version_patch', value: '79', operator: 'gte' }], - [{ name: 'name', value: 'turbogeth' }, { name: 'version_major', value: '2021', operator: 'gte' }, { name: 'version_minor', value: '7', operator: 'gte' }, { name: 'version_patch', value: '4', operator: 'gte' }], - [{ name: 'name', value: 'turbo-geth' }, { name: 'version_major', value: '2021', operator: 'gte' }, { name: 'version_minor', value: '7', operator: 'gte' }, { name: 'version_patch', value: '4', operator: 'gte' }], - [{ name: 'name', value: 'erigon' }, { name: 'version_major', value: '2021', operator: 'gte' }, { name: 'version_minor', value: '7', operator: 'gte' }, { name: 'version_patch', value: '4', operator: 'gte' }], - [{ name: 'name', value: 'besu' }, { name: 'version_major', value: '21', operator: 'gte' }, { name: 'version_minor', value: '7', operator: 'gte' }, { name: 'version_patch', value: '1', operator: 'gte' }], - [{ name: 'name', value: 'openethereum' }, { name: 'version_major', value: '3', operator: 'gte' }, { name: 'version_minor', value: '3', operator: 'gte' }, { name: 'version_patch', value: '0', operator: 'gte' }], - [{ name: 'name', value: 'ethereum-js' }, { name: 'version_major', value: '5', operator: 'gte' }, { name: 'version_minor', value: '5', operator: 'gte' }, { name: 'version_patch', value: '0', operator: 'gte' }], -] - -export const londonFilterString = generateQueryStringFromFilterGroups(londonFilter) export const knownNodesFilterString = generateQueryStringFromFilterGroups(knownNodesFilter) - export const LayoutEightPadding = [4, 4, 4, 8] export const LayoutTwoColumn = ["repeat(1, 1fr)", "repeat(1, 1fr)", "repeat(1, 1fr)", "repeat(2, 1fr)"] -export const LayoutTwoColSpan = [1, 1, 1, 2] \ No newline at end of file +export const LayoutTwoColSpan = [1, 1, 1, 2] diff --git a/frontend/src/data/DataContext.tsx b/frontend/src/data/DataContext.tsx deleted file mode 100644 index 987741d..0000000 --- a/frontend/src/data/DataContext.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import React, { createContext, useContext, useEffect, useState } from 'react'; -import { Loader } from '../organisms/Loader'; -import { ClientApiResponse, ClientDatabase, ClientsProcessor, EmptyDatabase, LoadingResponse } from './DataProcessor'; - -interface DataContextType extends ClientDatabase { - isReady: boolean -} - -const DataContext = createContext({ - isReady: false, - ...EmptyDatabase -}) - -const useData = () => useContext(DataContext); - -const DataProvider = ({ - children, - url, -}: { - children: React.ReactNode - url: string -}) => { - const [data, setData] = useState({ - isReady: false, - ...EmptyDatabase - }) - - useEffect(() => { - if (!url) - return; - - const onErrorRecieved = (entity: string, data: string, raw: string) => console.error(entity, data, raw) - - const run = async () => { - const response = await fetch(url); - const jsonResponse : ClientApiResponse[] | LoadingResponse = await response.json() - - if ((jsonResponse as LoadingResponse).status === 'loading') { - return; - } - - const rawClients = jsonResponse as ClientApiResponse[] - const db = ClientsProcessor(rawClients, onErrorRecieved) - setData({...db, isReady: true}) - } - - run() - - return () => {} - }, [url]) - - return ( - - {data.isReady ? children : loading data} - - ) -} - -export { useData, DataProvider } \ No newline at end of file diff --git a/frontend/src/organisms/Header.tsx b/frontend/src/organisms/Header.tsx index 46a3caf..9dede67 100644 --- a/frontend/src/organisms/Header.tsx +++ b/frontend/src/organisms/Header.tsx @@ -1,7 +1,6 @@ -import { Alert, AlertIcon, Button, Flex, Grid, GridItem, HStack, Link, Spacer, Text} from "@chakra-ui/react"; +import { Alert, AlertIcon, Button, Flex, Grid, GridItem, HStack, Spacer, Text} from "@chakra-ui/react"; import { useState } from "react"; import { VscClose } from "react-icons/vsc"; -import { Link as ReactLink } from "react-router-dom" import { Logo } from "../atoms/Logo"; import { LayoutEightPadding } from "../config"; import { ColorModeSwitcher } from "./ColorModeSwitcher"; @@ -35,10 +34,6 @@ export function Header() { Ethereum Nodes - - Home - London - @@ -46,4 +41,4 @@ export function Header() { ) -} \ No newline at end of file +} diff --git a/frontend/src/pages/London.tsx b/frontend/src/pages/London.tsx deleted file mode 100644 index b9d1b0c..0000000 --- a/frontend/src/pages/London.tsx +++ /dev/null @@ -1,225 +0,0 @@ -import { Grid, GridItem, useColorModeValue, Thead, Tbody, Tr, Box, Center } from "@chakra-ui/react"; -import { useEffect, useState } from "react"; -import { Cell, Pie, PieChart } from "recharts"; -import { Card } from "../atoms/Card"; -import { CustomResponsiveContainer } from "../atoms/CustomResponsiveContainer"; -import { TablePlus, TdPlus, ThPlus } from "../atoms/TablePlus"; -import { londonFilterString, knownNodesFilterString, normalizeClientNames, londonFilter, colors, LayoutEightPadding, LayoutTwoColSpan, LayoutTwoColumn } from "../config"; -import { Filtering } from "../organisms/Filtering"; -import { Loader } from "../organisms/Loader"; - -interface Distribution { - name: string; - count: number; - percentage: number; - color: string; -} - -interface NamedCount { - name: string; - count: number; - total: number; - readyPercentage: number; - notReadyPercentage: number; -} - -interface ClientData { - clients: NamedCount[]; - operatingSystems: NamedCount[]; - languages: NamedCount[]; - distribution: Distribution[] -} - -type NamedCountMap = { [name: string]: NamedCount } - -function convertListToMap(list: NamedCount[]): NamedCountMap { - if (!list) { - return {} - } - - return list.reduce((map: NamedCountMap, item) => { - map[normalizeClientNames[item.name] || item.name] = item; - return map; - }, {}); -} - -function processItem(readyMap: NamedCountMap, item: NamedCount) { - item.total = item.count - item.count = item.name in readyMap ? readyMap[item.name].count : 0 - item.readyPercentage = Math.ceil(item.count / item.total * 100) - item.notReadyPercentage = Math.ceil((item.total - item.count) / item.total * 100) - return item -} - -export function London() { - const color = useColorModeValue("gray.800", "gray") - const [data, setData] = useState() - - useEffect(() => { - const run = async () => { - const responseAll = await fetch(`/v1/dashboard${knownNodesFilterString}`) - const responseReady = await fetch(`/v1/dashboard${londonFilterString}`) - const allJson: ClientData = await responseAll.json() - const readyJson: ClientData = await responseReady.json() - - const readyClientsMap = convertListToMap(readyJson.clients) - - allJson.clients = allJson.clients.map((item) => processItem(readyClientsMap, item)) - allJson.languages = readyJson.languages - allJson.operatingSystems = readyJson.operatingSystems - - let readyCount = 0 - let notReadyCount = 0 - allJson.clients.forEach(c => { - readyCount += c.count - notReadyCount += c.total - c.count - }) - - let totalCount = readyCount + notReadyCount - - allJson.distribution = [ - { name: 'Ready', count: readyCount, percentage: Math.ceil(readyCount / totalCount * 100), color: '#7fda91' }, - { name: 'Not ready', count: notReadyCount, percentage: Math.ceil(notReadyCount / totalCount * 100), color: '#ff6c6c' } - ] - - setData(allJson) - } - - run() - }, []) - - if (!data) { - return Loading data... - } - - const renderLabelContent = (props: any): any => { - const { name, value, percent, x, y, midAngle } = props; - return ( - = 90) ? 'end' : 'start'} fill={color}> - {`${name || "unknown"}`} - {`${value} (${(percent * 100).toFixed(2)}%)`} - - ); - }; - - return ( - - - - - - - - - - Client - Ready - Not-Ready - - - - {data.clients.map((item, index) => ( - - {item.name} - {item.count} ({item.readyPercentage}%) - {item.total - item.count} ({item.notReadyPercentage}%) - - ))} - - - - - - {!data.distribution && (
No data
)} - {data.distribution && ( - - - - { - data.distribution.map((entry, index) => ( - - )) - } - - - - )} -
- - - {!data.operatingSystems && (
No data
)} - {data.operatingSystems && ( - - - - { - data.operatingSystems.map((entry, index) => ( - - )) - } - - - - )} -
- - - {!data.languages && (
No data
)} - {data.languages && ( - - - - { - data.languages.map((entry, index) => ( - - )) - } - - - - )} -
-
- ) -} diff --git a/frontend/src/setupProxy.js b/frontend/src/setupProxy.js index caf0d85..ebadb25 100644 --- a/frontend/src/setupProxy.js +++ b/frontend/src/setupProxy.js @@ -1,400 +1,3 @@ -// const fetch = require('node-fetch'); -const fs = require('fs'); - -const matchAll = require('string.prototype.matchall') - -function SortedMap(comparator) { - const obj = new Map(); - obj[Symbol.iterator] = function* () { - yield* [...this.entries()].sort(comparator); - }; - return { - size: () => obj.size, - clear: () => obj.clear(), - delete: (key) => obj.delete(key), - forEach: (callbackfn, thisArg) => obj.forEach(callbackfn, thisArg), - get: (key) => obj.get(key), - has: (key) => obj.has(key), - set: (key, value) => obj.set(key, value), - map: (callbackfn, thisArg) => { - return [...obj].map(callbackfn, thisArg); - }, - iterator: obj - }; -} - -const matchesFilter = (client, filter) => { - const entries = Object.entries(filter); - let matchesAll = entries.length; - for (const [key, value] of entries) { - if (typeof value === 'object') { - if (matchesFilter(client[key], value)) { - matchesAll--; - } - } - const clientValue = client[key]; - const filterValue = value; - if (typeof clientValue === 'number' && typeof filterValue === 'string') { - const numberMatches = filterValue.match(/(?\w+)\s*(?\d+)/); - if (numberMatches === null || numberMatches === void 0 ? void 0 : numberMatches.groups) { - const operator = numberMatches.groups.operator; - const value = parseFloat(numberMatches.groups.value); - switch (operator) { - case 'gt': { - if (clientValue > value) - matchesAll--; - break; - } - case 'gte': { - if (clientValue >= value) - matchesAll--; - break; - } - case 'lt': { - if (clientValue < value) - matchesAll--; - break; - } - case 'lte': { - if (clientValue <= value) - matchesAll--; - break; - } - default: { - console.warn(`Invalid conditional operator: "${operator}"`); - break; - } - } - } - } - else if (value === clientValue) { - matchesAll--; - } - } - return (matchesAll === 0); -}; - -const osMapping = { - linux: "vendor", - windows: "vendor", - darwin: "vendor", - x86_64: "architecture", - x64: "architecture", - amd64: "architecture" -}; - -function tryParseNumber(input) { - return parseInt(input); -} - -function parseVersion(version, parseOpt) { - const matches = version.match(/v?(?\d+)(?:.(?\d+).(?\d+)(?:-(?\w+)(?:-(?[a-zA-Z0-9]+)(?:-(?\d+))?)?)?)?/); - if (!(matches === null || matches === void 0 ? void 0 : matches.groups)) { - parseOpt.errorCallback('version', version, parseOpt.primaryKey); - return undefined; - } - const minor = tryParseNumber(matches.groups.minor); - const patch = tryParseNumber(matches.groups.patch); - const tag = matches.groups.tag; - const build = matches.groups.build; - const date = matches.groups.date; - return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ major: parseInt(matches.groups.major) }, minor !== undefined && { minor }), patch !== undefined && { patch }), tag && { tag }), build && { build }), date && { date }); -} - -function parseOs(os, parseOpt) { - const match = matchAll(os, /(linux|windows|darwin|x86_64|x64|amd64)/g); - const matches = Array.from(match); - if (matches.length) { - const result = {}; - matches.forEach((match) => { - const value = match[1]; - const key = osMapping[value]; - result[key] = value; - }); - return result; - } - parseOpt.errorCallback('os', os, parseOpt.primaryKey); - return undefined; -} - -function parseRuntime(runtime, parseOpt) { - const matches = runtime.match(/(?[a-zA-Z]+)?-?(?[\d+.?]+)/); - if (matches === null || matches === void 0 ? void 0 : matches.groups) { - const name = matches.groups['name']; - const version = parseVersion(matches.groups['version'], parseOpt); - return Object.assign(Object.assign({}, name && { name }), { version }); - } - parseOpt.errorCallback('runtime', runtime, parseOpt.primaryKey); - return undefined; -} - -function parseRaw(raw, parseOpt) { - const tokenize = raw.split('/'); - let label; - let version; - let os; - let runtime; - if (tokenize.length === 5) { - label = tokenize[1]; - version = parseVersion(tokenize[2], parseOpt); - os = parseOs(tokenize[3], parseOpt); - runtime = parseRuntime(tokenize[4], parseOpt); - } - else if (tokenize.length === 4) { - version = parseVersion(tokenize[1], parseOpt); - os = parseOs(tokenize[2], parseOpt); - runtime = parseRuntime(tokenize[3], parseOpt); - } - else if (tokenize.length === 3) { - version = parseVersion(tokenize[0], parseOpt); - os = parseOs(tokenize[1], parseOpt); - runtime = parseRuntime(tokenize[2], parseOpt); - } - else if (tokenize.length === 2) { - os = parseOs(tokenize[0], parseOpt); - runtime = parseRuntime(tokenize[1], parseOpt); - } - if (label || version || os || runtime) { - return Object.assign(Object.assign(Object.assign(Object.assign({}, label && { label }), version && { version }), os && { os }), runtime && { runtime }); - } - parseOpt.errorCallback('raw', raw, parseOpt.primaryKey); - return undefined; -} - -function ClientsProcessor(data, errorCallback) { - const obj = {}; - const topRuntimes = new Map(); - const topOs = new Map(); - let primaryKey = 0; - - const parse = (item) => { - primaryKey++; - const clientId = item.clientId.toLowerCase(); - if (!clientId) { - errorCallback('parse', 'empty client id', ''); - return undefined; - } - - const matches = clientId.match(/(?\w+)\/(?.+)/); - if (matches === null || matches === void 0 ? void 0 : matches.groups) { - const raw = parseRaw(clientId, { primaryKey, errorCallback: (entity, data, pk) => { - errorCallback(entity, data, `${pk}: "${clientId}"`); - }, raw: clientId }); - - if (!raw) { - return undefined; - } - - if (raw.runtime) { - const runtimeName = raw.runtime.name || 'Unknown'; - topRuntimes.set(runtimeName, (topRuntimes.get(runtimeName) || 0) + item.count); - } - - if (raw.os) { - const osName = raw.os.vendor || 'Unknown'; - topOs.set(osName, (topOs.get(osName) || 0) + item.count); - } - - return Object.assign({ primaryKey, name: matches.groups.name, count: item.count }, raw); - } - - errorCallback('parse', clientId, ''); - return undefined; - }; - - const matchesFilters = (client, filters) => { - if (!filters || !filters.length) { - return true; - } - - return filters.some(f => matchesFilter(client, f)); - }; - - const queryData = (options = {}, filters) => { - const convert = (a) => ({ - name: a[0], - count: a[1] - }); - - const versionToString = (version) => { - let versionString = '' + version.major; - if (version.minor !== undefined) { - versionString += '.' + version.minor; - } - - if (version.patch !== undefined) { - versionString += '.' + version.patch; - } - - if (version.tag !== undefined) { - versionString += '-' + version.tag; - } - - return versionString; - }; - - const runtimeToString = (runtime) => { - let runtimeString = ''; - if (runtime.name) { - runtimeString += runtime.name; - } - - if (options.showRuntimeVersion && runtime.version) { - runtimeString += versionToString(runtime.version); - } - - return runtimeString; - }; - - const operatingSystemToString = (os) => { - let osString = []; - if (os.vendor) { - osString.push(os.vendor); - } - - if (options.showOperatingSystemArchitecture && os.architecture) { - osString.push(os.architecture); - } - return osString.join('-'); - }; - - const cache = { - clients: SortedMap((a, b) => b[1] - a[1]), - versions: SortedMap((a, b) => b[1] - a[1]), - runtimes: SortedMap((a, b) => b[1] - a[1]), - operatingSystems: SortedMap((a, b) => b[1] - a[1]) - }; - for (let a in obj) { - const client = obj[a]; - - if (matchesFilters(client, filters)) { - cache.clients.set(client.name, (cache.clients.get(client.name) || 0) + client.count); - - if (client.version) { - const versionString = versionToString(client.version); - cache.versions.set(versionString, (cache.versions.get(versionString) || 0) + client.count); - } - - if (client.runtime) { - const runtimeString = runtimeToString(client.runtime); - cache.runtimes.set(runtimeString, (cache.runtimes.get(runtimeString) || 0) + client.count); - } - - if (client.os) { - const osString = operatingSystemToString(client.os); - cache.operatingSystems.set(osString, (cache.operatingSystems.get(osString) || 0) + client.count); - } - } - } - - return { - clients: cache.clients.map(convert), - versions: cache.versions.map(convert), - languages: cache.runtimes.map(convert), - operatingSystems: cache.operatingSystems.map(convert) - }; - }; - const getRaw = () => { - return Object.keys(obj).map(o => obj[parseInt(o)]); - }; - data.forEach((item) => { - const parsedItem = parse(item); - if (parsedItem) { - obj[parsedItem.primaryKey] = parsedItem; - } - }); - return { - obj, - getRaw, - queryData - }; -} - -let clients; - -async function writeCache(data) { - return new Promise((resolve, reject) => { - fs.writeFile('./clients.json', data, 'utf8', (err) => { - if (err) { - reject(err) - } else { - resolve() - } - }) - }) -} - -async function cacheCall() { - return new Promise((resolve, reject) => { - fs.readFile('./clients.json', 'utf-8', (err, data) => { - if (err) { - reject(err) - } else { - clients = JSON.parse(data) - resolve(clients) - } - }) - }) -} - -async function apiCall() { - try { - const response = await fetch(process.env.CRAWLER_API_URL); - clients = await response.json() - if (clients && clients.length) { - await writeCache(clients) - return clients - } else { - console.info('Clients was empty, read from cache.'); - return cacheCall() - } - } catch (e) { - console.info('Tuweni API is down, read from cache.'); - return cacheCall() - } -} - -async function fetchClients() { - if (clients && clients.length) { - console.info('Clients read from cache.'); - return clients - } - - return cacheCall() -} - -// module.exports = async (app) => { -// app.get('/v1/dashboard', async (req, resp) => { -// const response = await fetchClients() -// const proc = ClientsProcessor(response, (err) => { -// console.error(err) -// }) - -// const filters = [] -// let foundAtMostOneName = 0 -// if (req.query.filters) { - -// const parsedFilter = JSON.parse(req.query.filters.toLowerCase()) -// if (parsedFilter.length) { -// parsedFilter.forEach((filter) => { -// const filterObj = {} -// filter.forEach(filterItem => { -// const [key, value, operator] = filterItem.split(':') -// if (key === 'name') { -// foundAtMostOneName += 1 -// } -// filterObj[key] = value -// }) -// filters.push(filterObj) -// }) -// } -// } -// return resp.json(proc.queryData({ -// showRuntimeVersion: foundAtMostOneName === 1 ? true : false -// }, filters)) -// }) -// }; - const createProxyMiddleware = require('http-proxy-middleware'); module.exports = function(app) { @@ -405,4 +8,4 @@ module.exports = function(app) { changeOrigin: true, }) ); -}; \ No newline at end of file +}; diff --git a/frontend/src/templates/Routing.tsx b/frontend/src/templates/Routing.tsx index b7c1772..1d8f1e7 100644 --- a/frontend/src/templates/Routing.tsx +++ b/frontend/src/templates/Routing.tsx @@ -1,6 +1,5 @@ import { BrowserRouter, Route, Switch } from 'react-router-dom'; import Home from "../pages/Home"; -import { London } from '../pages/London'; import { Layout } from './Layout'; export function Routing() { @@ -11,11 +10,8 @@ export function Routing() { - - - ) -} \ No newline at end of file +}