diff --git a/.github/workflows/deploy-jungle-testnet.yaml b/.github/workflows/deploy-jungle-testnet.yaml index 12217ce8..14fc580f 100644 --- a/.github/workflows/deploy-jungle-testnet.yaml +++ b/.github/workflows/deploy-jungle-testnet.yaml @@ -52,7 +52,7 @@ jobs: REACT_APP_SYNC_TOLERANCE_INTERVAL: 180000 REACT_APP_TOKEN_SYMBOL: 'EOS' REACT_APP_NETWORK_URL: '[{"label":"EOS","value":"https://eos.antelope.tools","mainnet":true,"pair":"eos","icon":"eos","order":5},{"label":"Proton","value":"https://proton.antelope.tools","mainnet":true,"pair":"proton","icon":"proton","order":3},{"label":"WAX","value":"https://wax.antelope.tools","mainnet":true,"pair":"wax","icon":"wax","order":4},{"label":"Telos","value":"https://telos.antelope.tools","mainnet":true,"pair":"telos","icon":"telos","order":1},{"label":"Libre","value":"https://libre.antelope.tools","mainnet":true,"pair":"libre","icon":"libre","order":2},{"label":"LACChain EOSIO","value":"https://lacchain.antelope.tools","mainnet":true,"pair":null,"icon":"lacchain","order":6},{"label":"Jungle4 Testnet","value":"https://jungle.antelope.tools","mainnet":false,"pair":"eos","icon":"jungle","order":5},{"label":"Proton Testnet","value":"https://proton-testnet.antelope.tools","mainnet":false,"pair":"proton","icon":"proton","order":3},{"label":"WAX Testnet","value":"https://wax-testnet.antelope.tools","mainnet":false,"pair":"wax","icon":"wax","order":4},{"label":"Telos Testnet","value":"https://telos-testnet.antelope.tools","mainnet":false,"pair":"telos","icon":"telos","order":1},{"label":"Libre Testnet","value":"https://libre-testnet.antelope.tools","mainnet":false,"pair":"libre","icon":"libre","order":2},{"label":"Ultra Testnet","value":"https://ultra-testnet.antelope.tools","mainnet":false,"pair":"ultra","icon":"ultra","order":6}]' - REACT_APP_DISABLED_MENU_ITEMS: '["/missed-blocks","/block-distribution","/cpu-benchmark","/stress-test","/evm","/evm-rpc-endpoints"]' + REACT_APP_DISABLED_MENU_ITEMS: '["/missed-blocks","/block-distribution","/stress-test","/evm","/evm-rpc-endpoints"]' REACT_APP_BLOCK_EXPLORER_URL: 'https://jungle4.eosq.eosnation.io/tx/(transaction)' REACT_APP_STATE_HISTORY_ENABLED: 'false' REACT_APP_GOOGLE_ANALITIC_PAGE_ID: 'G-E6Y0EC9FT8' @@ -83,6 +83,7 @@ jobs: HAPI_EOS_BASE_ACCOUNT: ${{ secrets.HAPI_EOS_BASE_ACCOUNT }} HAPI_EOS_BASE_ACCOUNT_PASSWORD: ${{ secrets.HAPI_EOS_BASE_ACCOUNT_PASSWORD }} HAPI_EOS_MECHANICS_ACCOUNT: ${{ secrets.HAPI_EOS_MECHANICS_ACCOUNT }} + HAPI_EOS_MECHANICS_CUSTOM_PERMISSION: 'benchmark' HAPI_EOS_MECHANICS_PASSWORD: ${{ secrets.HAPI_EOS_MECHANICS_PASSWORD }} HAPI_EOS_WALLET_URL: http://dashboard-wallet:8888 HAPI_EOS_BP_JSON_ON_CHAIN: false diff --git a/webapp/public/sitemap.jungle.xml b/webapp/public/sitemap.jungle.xml index d6ecb1fc..b1d13bc2 100644 --- a/webapp/public/sitemap.jungle.xml +++ b/webapp/public/sitemap.jungle.xml @@ -31,6 +31,10 @@ https://jungle.antelope.tools/undiscoverable-bps 2023-09-07 + + https://jungle.antelope.tools/cpu-benchmark + 2023-09-10 + https://jungle.antelope.tools/ricardian-contract 2023-09-07 diff --git a/webapp/public/sitemap.proton.xml b/webapp/public/sitemap.proton.xml index bac35e1c..d90a0607 100644 --- a/webapp/public/sitemap.proton.xml +++ b/webapp/public/sitemap.proton.xml @@ -32,6 +32,10 @@ https://proton.antelope.tools/nodes-distribution 2023-09-07 + + https://proton.antelope.tools/cpu-benchmark + 2023-09-07 + https://proton.antelope.tools/ricardian-contract 2023-09-07 diff --git a/webapp/src/components/CopyToClipboard/index.js b/webapp/src/components/CopyToClipboard/index.js index 491fea0d..203a8f24 100644 --- a/webapp/src/components/CopyToClipboard/index.js +++ b/webapp/src/components/CopyToClipboard/index.js @@ -9,7 +9,7 @@ import styles from './styles' const useStyles = makeStyles(styles) -const CopyToClipboard = ({ text }) => { +const CopyToClipboard = ({ text, helperText }) => { const classes = useStyles() const { t } = useTranslation('copyToClipboardComponent') const [status, setStatus] = useState('copyToClipboard') @@ -21,17 +21,19 @@ const CopyToClipboard = ({ text }) => { return ( { setStatus('copyToClipboard') }} - arrow + classes={{ popper: classes.tooltip }} > } > diff --git a/webapp/src/components/CopyToClipboard/styles.js b/webapp/src/components/CopyToClipboard/styles.js index b840c0aa..abae6a31 100644 --- a/webapp/src/components/CopyToClipboard/styles.js +++ b/webapp/src/components/CopyToClipboard/styles.js @@ -3,4 +3,11 @@ export default (theme) => ({ alignSelf: 'center', marginBottom: `${theme.spacing(2)} !important`, }, + tooltip: { + maxWidth: '150px', + textAlign: 'center', + '& .MuiTooltip-tooltip': { + fontSize: '0.8em !important', + }, + }, }) diff --git a/webapp/src/components/EndpointsTable/index.js b/webapp/src/components/EndpointsTable/index.js index 1fa71123..d3199139 100644 --- a/webapp/src/components/EndpointsTable/index.js +++ b/webapp/src/components/EndpointsTable/index.js @@ -1,8 +1,7 @@ -import React, { useState } from 'react' +import React from 'react' import PropTypes from 'prop-types' import { useTranslation } from 'react-i18next' import { makeStyles } from '@mui/styles' -import Button from '@mui/material/Button' import Table from '@mui/material/Table' import TableBody from '@mui/material/TableBody' import TableCell from '@mui/material/TableCell' @@ -11,35 +10,22 @@ import TableHead from '@mui/material/TableHead' import TableRow from '@mui/material/TableRow' import Typography from '@mui/material/Typography' import { Tooltip as MUITooltip } from '@mui/material' -import ListAltIcon from '@mui/icons-material/ListAlt' import { Link as RouterLink } from 'react-router-dom' import Link from '@mui/material/Link' import QueryStatsIcon from '@mui/icons-material/QueryStats' +import CopyToClipboard from '../CopyToClipboard' import HealthCheck from '../HealthCheck' -import HealthCheckInfo from 'components/HealthCheck/HealthCheckInfo' +import HealthCheckInfo from '../HealthCheck/HealthCheckInfo' import { getStatus } from 'utils' import styles from './styles' -import Tooltip from '../Tooltip' -import EndpointsTextList from '../EndpointsTextList' const useStyles = makeStyles(styles) -const EndpointsTable = ({ producers }) => { +const EndpointsTable = ({ producers, textLists }) => { const classes = useStyles() const { t } = useTranslation('endpointsListRoute') - const [anchorEl, setAnchorEl] = useState(null) - const [type, setType] = useState('') - - const handlePopoverOpen = (target, type) => { - setAnchorEl(target) - setType(type) - } - - const handlePopoverClose = () => { - setAnchorEl(null) - } const CellList = ({ producer, endpointType }) => { return ( @@ -65,17 +51,6 @@ const EndpointsTable = ({ producers }) => { return ( <> - - - @@ -84,49 +59,19 @@ const EndpointsTable = ({ producers }) => { {t('api')} - } - onClick={(e) => { - handlePopoverOpen(e.target, 'api') - }} - > - {t('showList')} - + {t('ssl')} - } - onClick={(e) => { - handlePopoverOpen(e.target, 'ssl') - }} - > - {t('showList')} - + {t('p2p')} - } - onClick={(e) => { - handlePopoverOpen(e.target, 'p2p') - }} - > - {t('showList')} - + @@ -167,6 +112,7 @@ const EndpointsTable = ({ producers }) => { EndpointsTable.propTypes = { producers: PropTypes.array, + textLists: PropTypes.object, } export default EndpointsTable diff --git a/webapp/src/components/EndpointsTable/styles.js b/webapp/src/components/EndpointsTable/styles.js index 1b56861f..9d311bb0 100644 --- a/webapp/src/components/EndpointsTable/styles.js +++ b/webapp/src/components/EndpointsTable/styles.js @@ -18,7 +18,7 @@ export default (theme) => ({ alignItems: 'center', '& .MuiButton-root': { alignSelf: 'center', - marginLeft: theme.spacing(2), + margin: `${theme.spacing(0, 0, 0, 2)} !important`, } }, }) diff --git a/webapp/src/components/EndpointsTextList/index.js b/webapp/src/components/EndpointsTextList/index.js deleted file mode 100644 index 51429991..00000000 --- a/webapp/src/components/EndpointsTextList/index.js +++ /dev/null @@ -1,58 +0,0 @@ -import React, { useEffect, useState } from 'react' -import { useTranslation } from 'react-i18next' -import { makeStyles } from '@mui/styles' - -import useEndpointsState from '../../hooks/customHooks/useEndpointsState' -import CopyToClipboard from '../CopyToClipboard' - -import styles from './styles' - -const useStyles = makeStyles(styles) - -const EndpointsTextList = ({ type }) => { - const classes = useStyles() - const { t } = useTranslation('endpointsListRoute') - const [textList, setTextList] = useState('') - const [{ loading, producers }, { handleFilter, setPagination }] = - useEndpointsState({ useCache: true }) - - // Gets all responding endpoints - useEffect(() => { - setPagination(prev => ({ ...prev, limit: null })) - handleFilter(true) - }, [type, handleFilter, setPagination]) - - useEffect(() => { - if (!producers?.length) return - - let endpointsList = `# List of available ${type.toUpperCase()} endpoints \n` - - producers.forEach(producer => { - if (!!producer?.endpoints[type]?.length) { - endpointsList += `# ${producer.name} \n` - producer.endpoints[type].forEach(endpoint => { - endpointsList += `${type === 'p2p' ? 'p2p-peer-address = ' : ''} ${ - endpoint.value - } \n` - }) - endpointsList += '\n' - } - }) - - setTextList(endpointsList) - }, [producers, type]) - - return ( - <> - {!loading && !!textList?.length && ( - - {t('listOfResponding')} - - {textList} - - )} - > - ) -} - -export default EndpointsTextList diff --git a/webapp/src/components/EndpointsTextList/styles.js b/webapp/src/components/EndpointsTextList/styles.js deleted file mode 100644 index 85699ad6..00000000 --- a/webapp/src/components/EndpointsTextList/styles.js +++ /dev/null @@ -1,16 +0,0 @@ -export default (theme) => ({ - container: { - display: 'flex', - flexDirection: 'column', - padding: theme.spacing(0, 2, 2), - whiteSpace: 'pre-line', - minWidth: '300px', - '& p': { - margin: theme.spacing(0, 0, 2, 0), - fontWeight: 'bold', - [theme.breakpoints.up('md')]: { - textAlign: 'center', - }, - }, - }, -}) diff --git a/webapp/src/components/HealthCheck/HealthCheckInfo.js b/webapp/src/components/HealthCheck/HealthCheckInfo.js index b03d4555..3d455058 100644 --- a/webapp/src/components/HealthCheck/HealthCheckInfo.js +++ b/webapp/src/components/HealthCheck/HealthCheckInfo.js @@ -16,11 +16,11 @@ const HealthCheckInfo = ({ healthCheck }) => { {healthCheck.head_block_time && healthCheck.response?.status === 200 && ( <> - {t('headBlockTime')} + {t('headBlockTime')}: {moment(healthCheck.head_block_time).format('lll') || 'N/A'} > )} - {t('updatedAt')} + {t('updatedAt')}: {moment(healthCheck.updated_at).format('lll') || 'N/A'} > ) diff --git a/webapp/src/components/NodeCard/EndpointsChips.js b/webapp/src/components/NodeCard/EndpointsChips.js index 8c8e1cfd..c0653284 100644 --- a/webapp/src/components/NodeCard/EndpointsChips.js +++ b/webapp/src/components/NodeCard/EndpointsChips.js @@ -63,11 +63,11 @@ const EndpointsChips = ({ node }) => { {`${workingEndpoints}/${totalEndpoints}`} )} - {t('status')} + {t('status')}: {getStatusMessage(healthStatus, failingEndpoints)} - {t('updatedAt')} + {t('updatedAt')}: {moment(updatedAt).format('lll') || 'N/A'} diff --git a/webapp/src/gql/producer.gql.js b/webapp/src/gql/producer.gql.js index 888e3d34..1e17debe 100644 --- a/webapp/src/gql/producer.gql.js +++ b/webapp/src/gql/producer.gql.js @@ -38,6 +38,19 @@ export const PRODUCERS_QUERY = gql` } } ` + +export const PRODUCERS_COUNT_QUERY = gql` + query producer( + $where: producer_bool_exp + ) { + info: producer_aggregate(where: $where) { + producers: aggregate { + count + } + } + } +` + export const NODES_SUBSCRIPTION = gql` subscription ($offset: Int = 0, $limit: Int = 21, $where: producer_bool_exp) { producers: producer( diff --git a/webapp/src/hooks/customHooks/useBlockProducerState.js b/webapp/src/hooks/customHooks/useBlockProducerState.js index 271c785b..3acb8081 100644 --- a/webapp/src/hooks/customHooks/useBlockProducerState.js +++ b/webapp/src/hooks/customHooks/useBlockProducerState.js @@ -26,15 +26,26 @@ const CHIPS_FILTERS = [ const CHIPS_NAMES = ['all', ...eosConfig.producerTypes] const useBlockProducerState = () => { - const [ - { filters, pagination, producers }, - { handleOnSearch, handleOnPageChange, setPagination }, - ] = useSearchState({ query: PRODUCERS_QUERY }) + const defaultVariables = { + limit: 20, + offset: 0, + endpointFilter: undefined, + where: { + owner: { _like: '%%' }, + nodes: { endpoints: { value: { _gt: '' } } }, + } + } + const [variables, setVariables] = useState(defaultVariables) + const [loadProducers, { data: { info, producers } = {} }] = useLazyQuery(PRODUCERS_QUERY, { variables }) const { data: dataHistory } = useSubscription(MISSED_BLOCKS_SUBSCRIPTION) const [loadStats, { loading = true, data: { eosrate_stats: stats } = {} }] = useLazyQuery(EOSRATE_STATS_QUERY) const [items, setItems] = useState([]) const [missedBlocks, setMissedBlocks] = useState({}) + const [ + { filters, pagination }, + { handleOnSearch, handleOnPageChange, setPagination }, + ] = useSearchState({ loadProducers, info, variables, setVariables }) useEffect(() => { loadStats({}) diff --git a/webapp/src/hooks/customHooks/useEndpointsState.js b/webapp/src/hooks/customHooks/useEndpointsState.js index ee1f8422..2bf0a7e2 100644 --- a/webapp/src/hooks/customHooks/useEndpointsState.js +++ b/webapp/src/hooks/customHooks/useEndpointsState.js @@ -1,48 +1,99 @@ import { useState, useEffect, useCallback } from 'react' -import { useSubscription } from '@apollo/client' +import { useSubscription, useLazyQuery } from '@apollo/client' -import { PRODUCERS_QUERY, ENDPOINTS_SUBSCRIPTION } from '../../gql' +import { ENDPOINTS_SUBSCRIPTION, PRODUCERS_COUNT_QUERY } from '../../gql' import useSearchState from './useSearchState' const useEndpointsState = () => { + const defaultVariables = { + limit: 20, + offset: 0, + endpointFilter: undefined, + where: { + owner: { _like: '%%' }, + nodes: { endpoints: { value: { _gt: '' } } }, + }, + } + const [variables, setVariables] = useState(defaultVariables) + const [loadProducers, { data: { info } = {} }] = useLazyQuery(PRODUCERS_COUNT_QUERY) + const { data, loading } = useSubscription(ENDPOINTS_SUBSCRIPTION, { variables }) + const [items, setItems] = useState() + const [textLists, setTextLists] = useState() + const [ - { filters, pagination, variables }, + { filters, pagination }, { handleOnSearch, handleOnPageChange, setPagination }, ] = useSearchState({ - query: PRODUCERS_QUERY, - where: { nodes: { endpoints: { value: { _gt: '' } } } }, - limit: 20, + loadProducers, + info, + variables, + setVariables, }) - const { data, loading } = useSubscription(ENDPOINTS_SUBSCRIPTION, { variables }) - const [items, setItems] = useState() useEffect(() => { if (!data) return - setItems( - data.producers.map((producer) => { + const { newItems, workingEndpoints } = data.producers.reduce( + (state, producer) => { const endpoints = { api: [], ssl: [], p2p: [] } const inserted = [] + const bpName = + producer.bp_json?.org?.candidate_name || + producer?.bp_json?.org?.organization_name || + producer?.owner + producer.endpoints.forEach(({ type, ...endpoint }) => { if (!inserted.includes(endpoint.value)) { inserted.push(endpoint.value) endpoints[type].push(endpoint) + + if (endpoint?.response?.isWorking) { + state.workingEndpoints[type].push({ + value: endpoint.value, + name: bpName, + }) + } } }) - return { + state.newItems.push({ id: producer.id, - name: - producer.bp_json?.org?.candidate_name || - producer?.bp_json?.org?.organization_name || - producer?.owner, + name: bpName, endpoints, - } - }), + }) + + return state + }, + { newItems: [], workingEndpoints: { api: [], ssl: [], p2p: [] } }, + ) + + setItems(newItems) + + setTextLists( + Object.keys(workingEndpoints).reduce((state, type) => { + let previous = '' + let endpointsList = '' + + workingEndpoints[type].forEach(({ value, name }, index) => { + endpointsList += !index + ? `# List of available ${type.toUpperCase()} endpoints \n` + : '' + + endpointsList += previous !== name ? `# ${name} \n` : '' + endpointsList += `${ + type === 'p2p' ? 'p2p-peer-address = ' : '' + } ${value} \n` + endpointsList += '\n' + + previous = name + }) + + return { ...state, [type]: endpointsList } + }, {}), ) - }, [data]) + }, [data, info]) const handleFilter = useCallback(value => { const filter = value @@ -58,7 +109,13 @@ const useEndpointsState = () => { }, [setPagination]) return [ - { loading, pagination, producers: items, filters }, + { + loading: loading && !items?.length, + pagination, + producers: items, + textLists, + filters, + }, { handleFilter, handleOnPageChange, handleOnSearch, setPagination }, ] } diff --git a/webapp/src/hooks/customHooks/useNodeState.js b/webapp/src/hooks/customHooks/useNodeState.js index 02181fd1..8f4d1e50 100644 --- a/webapp/src/hooks/customHooks/useNodeState.js +++ b/webapp/src/hooks/customHooks/useNodeState.js @@ -1,21 +1,36 @@ import { useState, useEffect } from 'react' -import { useSubscription } from '@apollo/client' +import { useSubscription, useLazyQuery } from '@apollo/client' -import { PRODUCERS_QUERY, NODES_SUBSCRIPTION } from '../../gql' +import { NODES_SUBSCRIPTION, PRODUCERS_COUNT_QUERY } from '../../gql' import { eosConfig } from '../../config' import useSearchState from './useSearchState' const useNodeState = () => { + const defaultVariables = { + limit: 28, + offset: 0, + endpointFilter: undefined, + where: { + owner: { _like: '%%' }, + nodes: { type: { _neq: [] } }, + }, + } + const [variables, setVariables] = useState(defaultVariables) + const [loadProducers, { data: { info } = {} }] = useLazyQuery( + PRODUCERS_COUNT_QUERY, + ) + const { data, loading } = useSubscription(NODES_SUBSCRIPTION, { variables }) + const [items, setItems] = useState([]) const [ - { filters, pagination, variables }, + { filters, pagination }, { handleOnSearch, handleOnPageChange, setPagination }, ] = useSearchState({ - query: PRODUCERS_QUERY, - where: { nodes: { type: { _neq: [] } } }, + loadProducers, + info, + variables, + setVariables, }) - const { data, loading } = useSubscription(NODES_SUBSCRIPTION, { variables }) - const [items, setItems] = useState([]) const chips = [{ name: 'all' }, ...eosConfig.nodeChips] diff --git a/webapp/src/hooks/customHooks/useSearchState.js b/webapp/src/hooks/customHooks/useSearchState.js index c99639da..7ccd7ede 100644 --- a/webapp/src/hooks/customHooks/useSearchState.js +++ b/webapp/src/hooks/customHooks/useSearchState.js @@ -1,27 +1,14 @@ import { useState, useEffect, useCallback } from 'react' -import { useLazyQuery } from '@apollo/client' import { useLocation } from 'react-router-dom' import queryString from 'query-string' -const useSearchState = ({ query, where, limit }) => { - const [loadProducers, { loading = true, data: { producers, info } = {} }] = - useLazyQuery(query) +const useSearchState = ({ loadProducers, info, variables, setVariables }) => { const location = useLocation() - const defaultVariables = { - limit: limit ?? 28, - offset: 0, - endpointFilter: undefined, - where: { - owner: { _like: '%%' }, - ...(where ?? { bp_json: { _is_null: false } }), - } - } const [pagination, setPagination] = useState({ page: 1, pages: 1, - ...defaultVariables + ...variables, }) - const [variables, setVariables] = useState(defaultVariables) const [filters, setFilters] = useState({ name: 'all', owner: '' }) const handleOnSearch = useCallback(newFilters => { @@ -47,16 +34,15 @@ const useSearchState = ({ query, where, limit }) => { useEffect(() => { const variables = { - where: pagination.where, - offset: (pagination.page - 1) * pagination.limit, limit: pagination.limit, + offset: (pagination.page - 1) * pagination.limit, endpointFilter: pagination.endpointFilter, + where: pagination.where, } - loadProducers({ variables }) + loadProducers({ variables: { where: pagination.where } }) setVariables(variables) - // eslint-disable-next-line }, [ pagination.where, pagination.page, @@ -64,6 +50,7 @@ const useSearchState = ({ query, where, limit }) => { pagination.offset, pagination.endpointFilter, setVariables, + loadProducers, ]) useEffect(() => { @@ -78,6 +65,7 @@ const useSearchState = ({ query, where, limit }) => { })) setFilters(prev => ({ ...prev, owner: params.owner })) + // eslint-disable-next-line }, [location.search]) useEffect(() => { @@ -87,16 +75,15 @@ const useSearchState = ({ query, where, limit }) => { ...prev, pages: prev.limit ? Math.ceil(info.producers?.count / prev.limit) : 1, })) - }, [info]) + }, [info, pagination.limit]) return [ - { filters, pagination, loading, producers, info, variables }, + { filters, pagination }, { handleOnSearch, handleOnPageChange, setFilters, setPagination, - loadProducers, }, ] } diff --git a/webapp/src/language/en.json b/webapp/src/language/en.json index b4eb8700..064a731b 100644 --- a/webapp/src/language/en.json +++ b/webapp/src/language/en.json @@ -380,10 +380,9 @@ "endpointsListRoute": { "title": "Endpoints available by", "endpointsResponding": "Only endpoints responding", - "showList": "Show List", - "listOfResponding": "List of endpoints that are responding", "linkToStats": "Go to stats", - "filterEndpoints": "Filter responding endpoints" + "filterEndpoints": "Filter responding endpoints", + "copyToClipboard": "Copy responding endpoints from the column to the clipboard" }, "cpuBenchmarkRoute": { "title": "CPU Usage in Microseconds", @@ -411,7 +410,7 @@ "status": "Status", "response": "Response", "headBlockTime": "Head Block Time", - "updatedAt": "Updated at", + "updatedAt": "Last check", "error": "Error", "noResponse": "No response" }, diff --git a/webapp/src/language/es.json b/webapp/src/language/es.json index b0ba503a..b145780e 100644 --- a/webapp/src/language/es.json +++ b/webapp/src/language/es.json @@ -379,10 +379,9 @@ "title": "Endpoints disponibles por", "updatedAt": "Actualizado por última vez", "endpointsResponding": "Solo puntos finales que responden", - "showList": "Ver la lista", - "listOfResponding": "Lista de los puntos finales que responden", "linkToStats": "Ir a estadísticas", - "filterEndpoints": "Filtrar puntos finales" + "filterEndpoints": "Filtrar puntos finales", + "copyToClipboard": "Copiar los puntos finales que responden de la columna al portapapeles" }, "cpuBenchmarkRoute": { "title": "Resumen del uso de la CPU en Microsegundos", @@ -390,7 +389,7 @@ "highest": "Más alto", "average": "Promedio", "hideAll": "Ocultar Todas", - "showAll": "Muestrar Todas", + "showAll": "Mostrar Todas", "selectTo": "Seleccionar una cuenta para activar o", "zoomTo": "Arrastre o pellizque para ampliar el gráfico" }, @@ -410,7 +409,7 @@ "status": "Estado", "response": "Respuesta", "headBlockTime": "Fecha del último bloque", - "updatedAt": "Actualizado por última vez", + "updatedAt": "Comprobado por última vez", "error": "Error", "noResponse": "Sin respuesta", "Bad Gateway": "Mal puerta de enlace", diff --git a/webapp/src/routes/EVMEndpointsList/index.js b/webapp/src/routes/EVMEndpointsList/index.js index 51b02158..6f03ba26 100644 --- a/webapp/src/routes/EVMEndpointsList/index.js +++ b/webapp/src/routes/EVMEndpointsList/index.js @@ -11,23 +11,26 @@ import TableContainer from '@mui/material/TableContainer' import TableHead from '@mui/material/TableHead' import TableRow from '@mui/material/TableRow' import Typography from '@mui/material/Typography' +import Switch from '@mui/material/Switch' import AutorenewOutlinedIcon from '@mui/icons-material/AutorenewOutlined' -import HealthCheck from 'components/HealthCheck' -import HealthCheckInfo from 'components/HealthCheck/HealthCheckInfo' +import HealthCheck from '../../components/HealthCheck' +import HealthCheckInfo from '../../components/HealthCheck/HealthCheckInfo' import HealthInfoModal from '../../components/HealthCheck/InfoModal' import styles from './styles' import useRPCEndpointsState from './useRPCEndpointsState' import { getStatus, formatWithThousandSeparator } from 'utils' +import { generalConfig } from 'config' const useStyles = makeStyles(styles) const EVMEndpointsList = () => { const classes = useStyles() const { t } = useTranslation('evmEndpointsRoute') - - const [{ endpoints }, { runHealthCheck }] = useRPCEndpointsState() + const [{ endpoints, filter }, { runHealthCheck, handleFilter }] = + useRPCEndpointsState() + const { healthLights } = generalConfig return ( @@ -38,16 +41,30 @@ const EVMEndpointsList = () => { - - } - > - {t('rerun')} - + + + } + > + {t('rerun')} + + + + + {t('filterEndpoints')} + + { + handleFilter(event.target?.checked) + }} + /> + @@ -61,24 +78,26 @@ const EVMEndpointsList = () => { {endpoints?.map((endpoint, index) => ( - - - - {endpoint.url} - - - - - - - {formatWithThousandSeparator(endpoint.height) || 'N/A'} - - - {endpoint.latency - ? `${formatWithThousandSeparator(endpoint.latency)} ms` - : 'N/A'} - - + (!filter || getStatus(endpoint) !== healthLights.redLight) && ( + + + + {endpoint.url} + + + + + + + {formatWithThousandSeparator(endpoint.height) || 'N/A'} + + + {endpoint.latency + ? `${formatWithThousandSeparator(endpoint.latency)} ms` + : 'N/A'} + + + ) ))} diff --git a/webapp/src/routes/EVMEndpointsList/styles.js b/webapp/src/routes/EVMEndpointsList/styles.js index aa5b25f6..b8a291f0 100644 --- a/webapp/src/routes/EVMEndpointsList/styles.js +++ b/webapp/src/routes/EVMEndpointsList/styles.js @@ -4,7 +4,7 @@ export default (theme) => ({ }, healthContainer: { display: 'flex', - maxWidth: '300px', + width: '300px', justifyContent: 'space-between', }, titleContainer: { @@ -12,17 +12,38 @@ export default (theme) => ({ gap: theme.spacing(4), alignItems: 'center', }, - buttonContainer: { - padding: '0 0 0 25%', + formContainer: { + padding: '0 25% 0', marginTop: theme.spacing(4), + display: 'flex', + justifyContent: 'space-between', + flexWrap: 'wrap', + [theme.breakpoints.down('lg')]: { + padding: '0 12% 0', + }, [theme.breakpoints.down('md')]: { padding: 0, }, }, + buttonContainer: { + marginTop: theme.spacing(4), + [theme.breakpoints.down('sm')]: { + paddingRight: theme.spacing(4), + }, + }, + switchContainer: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + [theme.breakpoints.down('sm')]: { + paddingTop: theme.spacing(4), + }, + }, tableContainer: { display: 'flex', flexDirection: 'column', alignItems: 'center', + minHeight: '80vh', '& .MuiTableContainer-root': { width: '80%', [theme.breakpoints.down('md')]: { @@ -30,7 +51,7 @@ export default (theme) => ({ }, [theme.breakpoints.up('lg')]: { width: '50%', - } + }, }, }, }) diff --git a/webapp/src/routes/EVMEndpointsList/useRPCEndpointsState.js b/webapp/src/routes/EVMEndpointsList/useRPCEndpointsState.js index 2df17ed7..8a1940e8 100644 --- a/webapp/src/routes/EVMEndpointsList/useRPCEndpointsState.js +++ b/webapp/src/routes/EVMEndpointsList/useRPCEndpointsState.js @@ -5,6 +5,11 @@ import { evmConfig } from '../../config' const useRPCEndpointsState = () => { const [endpoints, setEndpoints] = useState(evmConfig.endpoints.map(url => ({ url, response: {} }))) + const [filter, setFilter] = useState(true) + + const handleFilter = () => { + setFilter(prev => !prev) + } const customAxios = axios.create() @@ -72,10 +77,12 @@ const useRPCEndpointsState = () => { setEndpoints(prev => prev.map(endpoint => ({ url: endpoint.url, response: {} }))) const rpcList = JSON.parse(JSON.stringify(endpoints)) + let checked = 0 rpcList.forEach(async ({ url }, index) => { const endpoint = await getEndpointHealthCheck(url) + checked += 1 setEndpoints(prev => { const newEndpoints = [ ...prev.slice(0, index), @@ -83,7 +90,10 @@ const useRPCEndpointsState = () => { ...prev.slice(index + 1), ] - if (newEndpoints.every(endpoint => !!endpoint.response.statusText)) { + if ( + newEndpoints.length === checked || + newEndpoints.every(endpoint => !!endpoint.response.statusText) + ) { newEndpoints.sort((e1, e2) => e1?.height === e2?.height ? e1?.latency - e2?.latency @@ -99,10 +109,13 @@ const useRPCEndpointsState = () => { useEffect(() => { runHealthCheck() - // eslint-disable-next-line + // eslint-disable-next-line }, []) - return [{ endpoints }, { runHealthCheck }] + return [ + { endpoints, filter }, + { runHealthCheck, handleFilter }, + ] } export default useRPCEndpointsState diff --git a/webapp/src/routes/EndpointsList/index.js b/webapp/src/routes/EndpointsList/index.js index cfc42a07..5f3644ad 100644 --- a/webapp/src/routes/EndpointsList/index.js +++ b/webapp/src/routes/EndpointsList/index.js @@ -24,15 +24,15 @@ import styles from './styles' const useStyles = makeStyles(styles) -const limitOptions = [10, 20, 30, 50, 80, 130] +const limitOptions = [10, 20, 30, 50, 80, 150] const EndpointsList = () => { const classes = useStyles() const { t } = useTranslation('endpointsListRoute') const [ - { loading, pagination, producers, filters }, + { loading, pagination, producers, textLists, filters }, { handleFilter, handleOnSearch, handleOnPageChange, setPagination }, - ] = useEndpointsState({ useCache: false }) + ] = useEndpointsState() return ( @@ -95,7 +95,7 @@ const EndpointsList = () => { ) : ( <> {!!producers?.length ? ( - + ) : (
{t('listOfResponding')}