diff --git a/webapp/src/components/HealthCheck/index.js b/webapp/src/components/HealthCheck/index.js new file mode 100644 index 00000000..437cbe2c --- /dev/null +++ b/webapp/src/components/HealthCheck/index.js @@ -0,0 +1,36 @@ +import React from 'react' +import { makeStyles } from '@mui/styles' + +import ReportIcon from '@mui/icons-material/Report' +import VerifiedIcon from '@mui/icons-material/Verified' +import TimerOffIcon from '@mui/icons-material/TimerOff' +import WarningIcon from '@mui/icons-material/Warning' + +import styles from './styles' + +const useStyles = makeStyles(styles) + +const HealthCheck = ({children, status}) => { + const classes = useStyles() + + const getIcon = (status) => { + switch (status) { + case 'updated': + return + case 'outdated': + return + case 'error': + return + default: + return + } + } + + return ( +
+ {status !== undefined && getIcon(status)} {children} +
+ ) +} + +export default HealthCheck \ No newline at end of file diff --git a/webapp/src/components/HealthCheck/styles.js b/webapp/src/components/HealthCheck/styles.js new file mode 100644 index 00000000..077afd05 --- /dev/null +++ b/webapp/src/components/HealthCheck/styles.js @@ -0,0 +1,20 @@ +export default (theme) => ({ + icon: { + '& svg': { + width: '16px !important', + height: '16px !important', + } + }, + success:{ + color: 'darkgreen', + }, + timerOff:{ + color: 'orange', + }, + warning:{ + color: 'orangered', + }, + fail:{ + color: 'darkred', + } +}) diff --git a/webapp/src/gql/producer.gql.js b/webapp/src/gql/producer.gql.js index 27960da0..c5bb9fde 100644 --- a/webapp/src/gql/producer.gql.js +++ b/webapp/src/gql/producer.gql.js @@ -55,6 +55,38 @@ export const NODES_QUERY = gql` } ` +export const ENDPOINTS_QUERY = gql` + query producer( + $offset: Int = 0 + $limit: Int = 21 + $where: producer_bool_exp + ) { + info: producer_aggregate(where: $where) { + producers: aggregate { + count + } + } + producers: producer( + where: $where + order_by: { total_votes_percent: desc } + offset: $offset + limit: $limit + ) { + id + owner + updated_at + nodes(where: {type: {_neq: ["producer"]}}){ + endpoints(order_by: {head_block_num: desc}){ + type + value + head_block_num + response + } + } + } + } +` + export const NODE_CPU_BENCHMARK = gql` query ($account: String) { cpu( diff --git a/webapp/src/hooks/customHooks/useEndpointsState.js b/webapp/src/hooks/customHooks/useEndpointsState.js index 3590189e..28a06c09 100644 --- a/webapp/src/hooks/customHooks/useEndpointsState.js +++ b/webapp/src/hooks/customHooks/useEndpointsState.js @@ -1,12 +1,13 @@ import { useState, useEffect } from 'react' import { useLazyQuery } from '@apollo/client' -import { PRODUCERS_QUERY } from '../../gql' +import { ENDPOINTS_QUERY } from '../../gql' const useEndpointsState = () => { - const [load, { loading, data }] = useLazyQuery(PRODUCERS_QUERY) + const [load, { loading, data }] = useLazyQuery(ENDPOINTS_QUERY) const [producers, setProducers] = useState([]) const [updatedAt, setUpdatedAt] = useState() + const [highestBlockNum, setHighestBlockNum] = useState(0) const [pagination, setPagination] = useState({ page: 1, limit: 80, @@ -18,7 +19,7 @@ const useEndpointsState = () => { variables: { offset: (pagination.page - 1) * pagination.limit, limit: pagination.limit, - where: { endpoints: { _neq: { api: [], ssl: [], p2p: [] } } }, + where: { bp_json: { _has_key: 'nodes' } }, }, }) // eslint-disable-next-line @@ -37,16 +38,36 @@ const useEndpointsState = () => { useEffect(() => { if (!data?.producers) return + let maxBlockNum = 0 + setProducers( - data.producers.map((producer) => ({ - name: - producer.bp_json?.org?.candidate_name || - producer?.bp_json?.org?.organization_name || - producer?.owner, - endpoints: producer.endpoints, - })), + data.producers.map((producer) => { + const endpoints = { api: [], ssl: [], p2p: [] } + + producer.nodes.forEach((node) => { + if (node.endpoints?.length) { + maxBlockNum = Math.max( + maxBlockNum, + node.endpoints[0]?.head_block_num, + ) + node.endpoints.forEach(({ type, ...endpoint }) => { + endpoints[type].push(endpoint) + }) + } + }) + + return { + name: + producer.bp_json?.org?.candidate_name || + producer?.bp_json?.org?.organization_name || + producer?.owner, + endpoints, + } + }), ) + setHighestBlockNum(maxBlockNum) + if (!data.producers?.[0]?.updated_at) return setUpdatedAt(data.producers[0].updated_at) @@ -61,7 +82,7 @@ const useEndpointsState = () => { } return [ - { loading, pagination, producers, updatedAt }, + { loading, pagination, producers, highestBlockNum, updatedAt }, { handleOnPageChange, setPagination }, ] } diff --git a/webapp/src/routes/EndpointsList/EndpointsTable.js b/webapp/src/routes/EndpointsList/EndpointsTable.js index fec1e0aa..2d319d86 100644 --- a/webapp/src/routes/EndpointsList/EndpointsTable.js +++ b/webapp/src/routes/EndpointsList/EndpointsTable.js @@ -9,20 +9,42 @@ import TableHead from '@mui/material/TableHead' import TableRow from '@mui/material/TableRow' import Typography from '@mui/material/Typography' -const EndpointsTable = ({ producers }) => { +import HealthCheck from '../../components/HealthCheck' + +const EndpointsTable = ({ producers, blockNum }) => { const { t } = useTranslation('endpointsListRoute') - const CellList = ({ producer, endpointType }) => { + const getStatus = (endpoint) => { + if(endpoint.response.status === undefined) return + if( endpoint.head_block_num === blockNum ){ + return 'updated' + }else{ + switch(Math.floor(endpoint.response.status / 100)){ + case 2: + return 'outdated' + case 4: + case 5: + return 'error' + default: + return 'not working' + } + } + } + + const CellList = ({ producer, endpointType }) => { return ( {!producer?.endpoints[endpointType].length ? ( N/A ) : ( producer.endpoints[endpointType].map((endpoint, index) => ( - - {endpoint} - + + {endpoint.value} + )) )} diff --git a/webapp/src/routes/EndpointsList/index.js b/webapp/src/routes/EndpointsList/index.js index 8b647f62..19944e20 100644 --- a/webapp/src/routes/EndpointsList/index.js +++ b/webapp/src/routes/EndpointsList/index.js @@ -26,7 +26,7 @@ const EndpointsList = () => { const classes = useStyles() const { t } = useTranslation('endpointsListRoute') const [ - { loading, pagination, producers, updatedAt }, + { loading, pagination, producers, highestBlockNum, updatedAt }, { handleOnPageChange, setPagination }, ] = useEndpointsState() @@ -71,7 +71,7 @@ const EndpointsList = () => { ) : ( <> - {!!producers.length && } + {!!producers.length && } {pagination.totalPages > 1 && (