|
@@ -1,255 +1,288 @@
|
|
-import { getChainState } from './get-status';
|
|
|
|
|
|
+import { getChainState } from './get-status'
|
|
import moment from 'moment'
|
|
import moment from 'moment'
|
|
-import { Card, CardActions, CardContent, CircularProgress, Container, createStyles, Grid, makeStyles, TextField, Typography } from '@material-ui/core';
|
|
|
|
-import Button from '@material-ui/core/Button';
|
|
|
|
-import { BootstrapButton } from './BootstrapButton';
|
|
|
|
-import Autocomplete from '@material-ui/lab/Autocomplete';
|
|
|
|
-import { useEffect, useState } from 'react';
|
|
|
|
|
|
+import {
|
|
|
|
+ Card,
|
|
|
|
+ CardActions,
|
|
|
|
+ CardContent,
|
|
|
|
+ CircularProgress,
|
|
|
|
+ Container,
|
|
|
|
+ createStyles,
|
|
|
|
+ Grid,
|
|
|
|
+ makeStyles,
|
|
|
|
+ TextField,
|
|
|
|
+ Typography,
|
|
|
|
+} from '@material-ui/core'
|
|
|
|
+import Button from '@material-ui/core/Button'
|
|
|
|
+import { BootstrapButton } from './BootstrapButton'
|
|
|
|
+import Autocomplete from '@material-ui/lab/Autocomplete'
|
|
|
|
+import { useEffect, useState } from 'react'
|
|
import axios from 'axios'
|
|
import axios from 'axios'
|
|
-import { config } from "dotenv";
|
|
|
|
-import { Report, Reports } from './Types';
|
|
|
|
-import { ColDef, DataGrid, PageChangeParams, ValueFormatterParams } from '@material-ui/data-grid';
|
|
|
|
-import Alert from '@material-ui/lab/Alert';
|
|
|
|
|
|
+import { config } from 'dotenv'
|
|
|
|
+import { Report, Reports } from './Types'
|
|
|
|
+import { ColDef, DataGrid, PageChangeParams, ValueFormatterParams } from '@material-ui/data-grid'
|
|
|
|
+import Alert from '@material-ui/lab/Alert'
|
|
import './index.css'
|
|
import './index.css'
|
|
|
|
|
|
-config();
|
|
|
|
|
|
+config()
|
|
|
|
|
|
const useStyles = makeStyles(() =>
|
|
const useStyles = makeStyles(() =>
|
|
- createStyles({
|
|
|
|
- root: {
|
|
|
|
- flexGrow: 1,
|
|
|
|
- backgroundColor: '#ffffff'
|
|
|
|
- },
|
|
|
|
- }),
|
|
|
|
-);
|
|
|
|
|
|
+ createStyles({
|
|
|
|
+ root: {
|
|
|
|
+ flexGrow: 1,
|
|
|
|
+ backgroundColor: '#ffffff',
|
|
|
|
+ },
|
|
|
|
+ }),
|
|
|
|
+)
|
|
|
|
|
|
|
|
|
|
const ValidatorReport = () => {
|
|
const ValidatorReport = () => {
|
|
- const dateFormat = 'yyyy-MM-DD';
|
|
|
|
- const [activeValidators, setActiveValidators] = useState([]);
|
|
|
|
- const [lastBlock, setLastBlock] = useState(0);
|
|
|
|
- const [stash, setStash] = useState('5EhDdcWm4TdqKp1ew1PqtSpoAELmjbZZLm5E34aFoVYkXdRW');
|
|
|
|
- const [dateFrom, setDateFrom] = useState(moment().subtract(14, 'd').format(dateFormat));
|
|
|
|
- const [dateTo, setDateTo] = useState(moment().format(dateFormat));
|
|
|
|
- const [startBlock, setStartBlock] = useState('' as unknown as number);
|
|
|
|
- const [endBlock, setEndBlock] = useState('' as unknown as number);
|
|
|
|
- const [isLoading, setIsLoading] = useState(false);
|
|
|
|
- const [error, setError] = useState(undefined);
|
|
|
|
- const [backendUrl] = useState("https://validators.joystreamstats.live");
|
|
|
|
- const [columns] = useState(
|
|
|
|
- [
|
|
|
|
- { field: 'id', headerName: 'Era', width: 150, sortable: true },
|
|
|
|
- { field: 'stakeTotal', headerName: 'Total Stake', width: 150, sortable: true },
|
|
|
|
- { field: 'stakeOwn', headerName: 'Own Stake', width: 150, sortable: true },
|
|
|
|
- { field: 'points', headerName: 'Points', width: 150, sortable: true },
|
|
|
|
- { field: 'rewards', headerName: 'Rewards', width: 150, sortable: true },
|
|
|
|
- { field: 'commission', headerName: 'Commission', width: 150, sortable: true, valueFormatter: (params: ValueFormatterParams) => {
|
|
|
|
- if (isNaN(params.value as unknown as number)) {
|
|
|
|
- return `${params.value}%`
|
|
|
|
- }
|
|
|
|
- return `${Number(params.value).toFixed(0)}%`
|
|
|
|
- }},
|
|
|
|
- { field: 'blocksCount', headerName: 'Blocks Produced', width: 150, sortable: true },
|
|
|
|
- ]
|
|
|
|
- );
|
|
|
|
- const [report, setReport] = useState({
|
|
|
|
- pageSize: 0,
|
|
|
|
- totalCount: 0,
|
|
|
|
- totalBlocks: 0,
|
|
|
|
- startEra: -1,
|
|
|
|
- endEra: -1,
|
|
|
|
- startBlock: -1,
|
|
|
|
- endBlock: -1,
|
|
|
|
- startTime: -1,
|
|
|
|
- endTime: -1,
|
|
|
|
- report: [] as unknown as Report[]
|
|
|
|
- } as unknown as Reports );
|
|
|
|
-
|
|
|
|
- useEffect(() => {
|
|
|
|
- updateChainState()
|
|
|
|
- const interval = setInterval(() => { updateChainState() }, 10000);
|
|
|
|
- return () => clearInterval(interval);
|
|
|
|
- }, []);
|
|
|
|
-
|
|
|
|
- const updateChainState = () => {
|
|
|
|
- getChainState().then((chainState) => {
|
|
|
|
- setLastBlock(chainState.finalizedBlockHeight)
|
|
|
|
- setActiveValidators(chainState.validators.validators)
|
|
|
|
- })
|
|
|
|
- }
|
|
|
|
|
|
+ const dateFormat = 'yyyy-MM-DD'
|
|
|
|
+ const [activeValidators, setActiveValidators] = useState([])
|
|
|
|
+ const [lastBlock, setLastBlock] = useState(0)
|
|
|
|
+ const [stash, setStash] = useState('5EhDdcWm4TdqKp1ew1PqtSpoAELmjbZZLm5E34aFoVYkXdRW')
|
|
|
|
+ const [dateFrom, setDateFrom] = useState(moment().subtract(14, 'd').format(dateFormat))
|
|
|
|
+ const [dateTo, setDateTo] = useState(moment().format(dateFormat))
|
|
|
|
+ const [startBlock, setStartBlock] = useState('' as unknown as number)
|
|
|
|
+ const [endBlock, setEndBlock] = useState('' as unknown as number)
|
|
|
|
+ const [isLoading, setIsLoading] = useState(false)
|
|
|
|
+ const [error, setError] = useState(undefined)
|
|
|
|
+ const [backendUrl] = useState('https://validators.joystreamstats.live')
|
|
|
|
+ const [columns] = useState(
|
|
|
|
+ [
|
|
|
|
+ { field: 'id', headerName: 'Era', width: 150, sortable: true },
|
|
|
|
+ { field: 'stakeTotal', headerName: 'Total Stake', width: 150, sortable: true },
|
|
|
|
+ { field: 'stakeOwn', headerName: 'Own Stake', width: 150, sortable: true },
|
|
|
|
+ { field: 'points', headerName: 'Points', width: 150, sortable: true },
|
|
|
|
+ { field: 'rewards', headerName: 'Rewards', width: 150, sortable: true },
|
|
|
|
+ {
|
|
|
|
+ field: 'commission',
|
|
|
|
+ headerName: 'Commission',
|
|
|
|
+ width: 150,
|
|
|
|
+ sortable: true,
|
|
|
|
+ valueFormatter: (params: ValueFormatterParams) => {
|
|
|
|
+ if (isNaN(params.value as unknown as number)) {
|
|
|
|
+ return `${params.value}%`
|
|
|
|
+ }
|
|
|
|
+ return `${Number(params.value).toFixed(0)}%`
|
|
|
|
+ },
|
|
|
|
+ },
|
|
|
|
+ { field: 'blocksCount', headerName: 'Blocks Produced', width: 150, sortable: true },
|
|
|
|
+ ],
|
|
|
|
+ )
|
|
|
|
+ const [report, setReport] = useState({
|
|
|
|
+ pageSize: 0,
|
|
|
|
+ totalCount: 0,
|
|
|
|
+ totalBlocks: 0,
|
|
|
|
+ startEra: -1,
|
|
|
|
+ endEra: -1,
|
|
|
|
+ startBlock: -1,
|
|
|
|
+ endBlock: -1,
|
|
|
|
+ startTime: -1,
|
|
|
|
+ endTime: -1,
|
|
|
|
+ report: [] as unknown as Report[],
|
|
|
|
+ } as unknown as Reports)
|
|
|
|
|
|
- const handlePageChange = (params: PageChangeParams) => {
|
|
|
|
- if (report.totalCount > 0) {
|
|
|
|
- loadReport(params.page)
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
+ updateChainState()
|
|
|
|
+ const interval = setInterval(() => {
|
|
|
|
+ updateChainState()
|
|
|
|
+ }, 10000)
|
|
|
|
+ return () => clearInterval(interval)
|
|
|
|
+ }, [])
|
|
|
|
|
|
- const loadReport = (page: number) => {
|
|
|
|
- setIsLoading(true)
|
|
|
|
- const blockParam = startBlock && endBlock ? `&start_block=${startBlock}&end_block=${endBlock}` : ''
|
|
|
|
- const dateParam = !(startBlock && endBlock) && dateFrom && dateTo ? `&start_time=${moment(dateFrom, dateFormat).format(dateFormat)}&end_time=${moment(dateTo, dateFormat).format(dateFormat)}` : ''
|
|
|
|
- const apiUrl = `${backendUrl}/validator-report?addr=${stash}&page=${page}${blockParam}${dateParam}`
|
|
|
|
- axios.get(apiUrl).then((response) => {
|
|
|
|
- if (response.data.report !== undefined) {
|
|
|
|
- setReport(response.data);
|
|
|
|
- }
|
|
|
|
- setIsLoading(false)
|
|
|
|
- setError(undefined)
|
|
|
|
- }).catch((err) => {
|
|
|
|
- setIsLoading(false)
|
|
|
|
- setError(err)
|
|
|
|
- })
|
|
|
|
- }
|
|
|
|
|
|
+ const updateChainState = () => {
|
|
|
|
+ getChainState().then((chainState) => {
|
|
|
|
+ setLastBlock(chainState.finalizedBlockHeight)
|
|
|
|
+ setActiveValidators(chainState.validators.validators)
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
|
|
- const stopLoadingReport = () => {
|
|
|
|
- setIsLoading(false)
|
|
|
|
|
|
+ const handlePageChange = (params: PageChangeParams) => {
|
|
|
|
+ if (report.totalCount > 0) {
|
|
|
|
+ loadReport(params.page)
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
- const canLoadReport = () => stash && ((startBlock && endBlock) || (dateFrom && dateTo))
|
|
|
|
- const startOrStopLoading = () => isLoading ? stopLoadingReport() : loadReport(1);
|
|
|
|
- const updateStartBlock = (e: { target: { value: unknown; }; }) => setStartBlock((e.target.value as unknown as number));
|
|
|
|
- const updateEndblock = (e: { target: { value: unknown; }; }) => setEndBlock((e.target.value as unknown as number));
|
|
|
|
- const updateDateFrom = (e: { target: { value: unknown; }; }) => setDateFrom((e.target.value as unknown as string))
|
|
|
|
- const updateDateTo = (e: { target: { value: unknown; }; }) => setDateTo((e.target.value as unknown as string));
|
|
|
|
-
|
|
|
|
- const getButtonTitle = (isLoading: boolean) => {
|
|
|
|
- if (isLoading) {
|
|
|
|
- return (<div style={{ display: 'flex', alignItems: 'center' }}>Stop loading <CircularProgress style={ { color: '#fff', height: 20, width: 20, marginLeft: 12 } } /></div>)
|
|
|
|
- }
|
|
|
|
- if (startBlock && endBlock) {
|
|
|
|
- return `Load data between blocks ${startBlock} - ${endBlock}`
|
|
|
|
- }
|
|
|
|
- if (dateFrom && dateTo) {
|
|
|
|
- return `Load data between dates ${dateFrom} - ${dateTo}`;
|
|
|
|
- }
|
|
|
|
- return 'Choose dates or blocks range'
|
|
|
|
|
|
+ const loadReport = (page: number) => {
|
|
|
|
+ setIsLoading(true)
|
|
|
|
+ const blockParam = startBlock && endBlock ? `&start_block=${startBlock}&end_block=${endBlock}` : ''
|
|
|
|
+ const dateParam = !(startBlock && endBlock) && dateFrom && dateTo ? `&start_time=${moment(dateFrom, dateFormat).format(dateFormat)}&end_time=${moment(dateTo, dateFormat).format(dateFormat)}` : ''
|
|
|
|
+ const apiUrl = `${backendUrl}/validator-report?addr=${stash}&page=${page}${blockParam}${dateParam}`
|
|
|
|
+ axios.get(apiUrl).then((response) => {
|
|
|
|
+ if (response.data.report !== undefined) {
|
|
|
|
+ setReport(response.data)
|
|
|
|
+ }
|
|
|
|
+ setIsLoading(false)
|
|
|
|
+ setError(undefined)
|
|
|
|
+ }).catch((err) => {
|
|
|
|
+ setIsLoading(false)
|
|
|
|
+ setError(err)
|
|
|
|
+ })
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const stopLoadingReport = () => {
|
|
|
|
+ setIsLoading(false)
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ const canLoadReport = () => stash && ((startBlock && endBlock) || (dateFrom && dateTo))
|
|
|
|
+ const startOrStopLoading = () => isLoading ? stopLoadingReport() : loadReport(1)
|
|
|
|
+ const updateStartBlock = (e: { target: { value: unknown; }; }) => setStartBlock((e.target.value as unknown as number))
|
|
|
|
+ const updateEndblock = (e: { target: { value: unknown; }; }) => setEndBlock((e.target.value as unknown as number))
|
|
|
|
+ const updateDateFrom = (e: { target: { value: unknown; }; }) => setDateFrom((e.target.value as unknown as string))
|
|
|
|
+ const updateDateTo = (e: { target: { value: unknown; }; }) => setDateTo((e.target.value as unknown as string))
|
|
|
|
+
|
|
|
|
+ const getButtonTitle = (isLoading: boolean) => {
|
|
|
|
+ if (isLoading) {
|
|
|
|
+ return (<div style={{ display: 'flex', alignItems: 'center' }}>Stop loading <CircularProgress
|
|
|
|
+ style={{ color: '#fff', height: 20, width: 20, marginLeft: 12 }} /></div>)
|
|
|
|
+ }
|
|
|
|
+ if (startBlock && endBlock) {
|
|
|
|
+ return `Load data between blocks ${startBlock} - ${endBlock}`
|
|
}
|
|
}
|
|
- const classes = useStyles();
|
|
|
|
- return (
|
|
|
|
- <div className={classes.root}>
|
|
|
|
- <Container maxWidth="lg">
|
|
|
|
- <Grid container spacing={2}>
|
|
|
|
- <Grid item lg={12}>
|
|
|
|
- <div style={{ display: 'flex', justifyContent: 'flex-start' }}>
|
|
|
|
- <h1>Validator Report</h1>
|
|
|
|
- </div>
|
|
|
|
- </Grid>
|
|
|
|
- <Grid item xs={12} lg={12}>
|
|
|
|
- <Autocomplete
|
|
|
|
- freeSolo
|
|
|
|
- style={{ width: '100%' }}
|
|
|
|
- options={activeValidators}
|
|
|
|
- onChange={(e, value) => setStash(value || '')}
|
|
|
|
- value={stash}
|
|
|
|
- renderInput={(params) => <TextField {...params} label="Validator stash address" variant="filled" />} />
|
|
|
|
- </Grid>
|
|
|
|
- <Grid item xs={6} lg={3}>
|
|
|
|
- <TextField fullWidth type="date" onChange={updateDateFrom} id="block-start" InputLabelProps={{ shrink: true }} label="Date From" value={dateFrom} variant="filled" />
|
|
|
|
- </Grid>
|
|
|
|
- <Grid item xs={6} lg={3}>
|
|
|
|
- <BootstrapButton size='large' style={{ minHeight: 56 }} fullWidth onClick={() => setDateFrom(moment().subtract(2, 'w').format('yyyy-MM-DD'))}>2 weeks from today</BootstrapButton>
|
|
|
|
- </Grid>
|
|
|
|
- <Grid item xs={6} lg={3}>
|
|
|
|
- <TextField fullWidth type="date" onChange={updateDateTo} id="block-end" InputLabelProps={{ shrink: true }} label="Date To" value={dateTo} variant="filled" />
|
|
|
|
- </Grid>
|
|
|
|
- <Grid item xs={6} lg={3}>
|
|
|
|
- <BootstrapButton size='large' style={{ minHeight: 56 }} fullWidth onClick={() => setDateTo(moment().format('yyyy-MM-DD'))}>Today</BootstrapButton>
|
|
|
|
- </Grid>
|
|
|
|
- <Grid item xs={6} lg={3}>
|
|
|
|
- <TextField fullWidth type="number" onChange={updateStartBlock} id="block-start" label="Start Block" value={startBlock} variant="filled" />
|
|
|
|
- </Grid>
|
|
|
|
- <Grid item xs={6} lg={3}>
|
|
|
|
- <BootstrapButton size='large' style={{ minHeight: 56 }} fullWidth disabled={!lastBlock} onClick={() => setStartBlock(lastBlock - (600 * 24 * 14))}>{lastBlock ? `2 weeks before latest (${lastBlock - (600 * 24 * 14)})` : '2 weeks from latest'}</BootstrapButton>
|
|
|
|
- </Grid>
|
|
|
|
- <Grid item xs={6} lg={3}>
|
|
|
|
- <TextField fullWidth type="number" onChange={updateEndblock} id="block-end" label="End Block" value={endBlock} variant="filled" />
|
|
|
|
- </Grid>
|
|
|
|
- <Grid item xs={6} lg={3}>
|
|
|
|
- <BootstrapButton size='large' style={{ minHeight: 56 }} fullWidth disabled={!lastBlock} onClick={() => setEndBlock(lastBlock)}>{lastBlock ? `Pick latest block (${lastBlock})` : 'Use latest block'}</BootstrapButton>
|
|
|
|
- </Grid>
|
|
|
|
- <Grid item xs={12} lg={12}>
|
|
|
|
- <BootstrapButton size='large' style={{ minHeight: 56 }} fullWidth disabled={!canLoadReport()} onClick={startOrStopLoading}>{getButtonTitle(isLoading)}</BootstrapButton>
|
|
|
|
- <Alert style={ error !== undefined ? { marginTop: 12 } : { display: 'none'} } onClose={() => setError(undefined)} severity="error">Error loading validator report, please try again.</Alert>
|
|
|
|
- </Grid>
|
|
|
|
- <Grid item xs={12} lg={12}>
|
|
|
|
- <ValidatorReportCard stash={stash} report={report} />
|
|
|
|
- </Grid>
|
|
|
|
- <Grid item xs={12} lg={12}>
|
|
|
|
- <div style={{ height: 600 }}>
|
|
|
|
- <DataGrid
|
|
|
|
- rows={report.report}
|
|
|
|
- columns={columns as unknown as ColDef[]}
|
|
|
|
- rowCount={report.totalCount}
|
|
|
|
- paginationMode="server"
|
|
|
|
- onPageChange={handlePageChange}
|
|
|
|
- pageSize={report.pageSize}
|
|
|
|
- rowsPerPageOptions={[]}
|
|
|
|
- disableSelectionOnClick
|
|
|
|
- autoHeight
|
|
|
|
- />
|
|
|
|
- </div>
|
|
|
|
- </Grid>
|
|
|
|
- </Grid>
|
|
|
|
- </Container>
|
|
|
|
- </div>
|
|
|
|
- )
|
|
|
|
|
|
+ if (dateFrom && dateTo) {
|
|
|
|
+ return `Load data between dates ${dateFrom} - ${dateTo}`
|
|
|
|
+ }
|
|
|
|
+ return 'Choose dates or blocks range'
|
|
|
|
+ }
|
|
|
|
+ const classes = useStyles()
|
|
|
|
+ return (
|
|
|
|
+ <div className={classes.root}>
|
|
|
|
+ <Container maxWidth='lg'>
|
|
|
|
+ <Grid container spacing={2}>
|
|
|
|
+ <Grid item lg={12}>
|
|
|
|
+ <div style={{ display: 'flex', justifyContent: 'flex-start' }}>
|
|
|
|
+ <h1>Validator Report</h1>
|
|
|
|
+ </div>
|
|
|
|
+ </Grid>
|
|
|
|
+ <Grid item xs={12} lg={12}>
|
|
|
|
+ <Autocomplete
|
|
|
|
+ freeSolo
|
|
|
|
+ style={{ width: '100%' }}
|
|
|
|
+ options={activeValidators}
|
|
|
|
+ onChange={(e, value) => setStash(value || '')}
|
|
|
|
+ value={stash}
|
|
|
|
+ renderInput={(params) => <TextField {...params} label='Validator stash address' variant='filled' />} />
|
|
|
|
+ </Grid>
|
|
|
|
+ <Grid item xs={6} lg={3}>
|
|
|
|
+ <TextField fullWidth type='date' onChange={updateDateFrom} id='block-start'
|
|
|
|
+ InputLabelProps={{ shrink: true }} label='Date From' value={dateFrom} variant='filled' />
|
|
|
|
+ </Grid>
|
|
|
|
+ <Grid item xs={6} lg={3}>
|
|
|
|
+ <BootstrapButton size='large' style={{ minHeight: 56 }} fullWidth
|
|
|
|
+ onClick={() => setDateFrom(moment().subtract(2, 'w').format('yyyy-MM-DD'))}>2 weeks from
|
|
|
|
+ today</BootstrapButton>
|
|
|
|
+ </Grid>
|
|
|
|
+ <Grid item xs={6} lg={3}>
|
|
|
|
+ <TextField fullWidth type='date' onChange={updateDateTo} id='block-end' InputLabelProps={{ shrink: true }}
|
|
|
|
+ label='Date To' value={dateTo} variant='filled' />
|
|
|
|
+ </Grid>
|
|
|
|
+ <Grid item xs={6} lg={3}>
|
|
|
|
+ <BootstrapButton size='large' style={{ minHeight: 56 }} fullWidth
|
|
|
|
+ onClick={() => setDateTo(moment().format('yyyy-MM-DD'))}>Today</BootstrapButton>
|
|
|
|
+ </Grid>
|
|
|
|
+ <Grid item xs={6} lg={3}>
|
|
|
|
+ <TextField fullWidth type='number' onChange={updateStartBlock} id='block-start' label='Start Block'
|
|
|
|
+ value={startBlock} variant='filled' />
|
|
|
|
+ </Grid>
|
|
|
|
+ <Grid item xs={6} lg={3}>
|
|
|
|
+ <BootstrapButton size='large' style={{ minHeight: 56 }} fullWidth disabled={!lastBlock}
|
|
|
|
+ onClick={() => setStartBlock(lastBlock - (600 * 24 * 14))}>{lastBlock ? `2 weeks before latest (${lastBlock - (600 * 24 * 14)})` : '2 weeks from latest'}</BootstrapButton>
|
|
|
|
+ </Grid>
|
|
|
|
+ <Grid item xs={6} lg={3}>
|
|
|
|
+ <TextField fullWidth type='number' onChange={updateEndblock} id='block-end' label='End Block'
|
|
|
|
+ value={endBlock} variant='filled' />
|
|
|
|
+ </Grid>
|
|
|
|
+ <Grid item xs={6} lg={3}>
|
|
|
|
+ <BootstrapButton size='large' style={{ minHeight: 56 }} fullWidth disabled={!lastBlock}
|
|
|
|
+ onClick={() => setEndBlock(lastBlock)}>{lastBlock ? `Pick latest block (${lastBlock})` : 'Use latest block'}</BootstrapButton>
|
|
|
|
+ </Grid>
|
|
|
|
+ <Grid item xs={12} lg={12}>
|
|
|
|
+ <BootstrapButton size='large' style={{ minHeight: 56 }} fullWidth disabled={!canLoadReport()}
|
|
|
|
+ onClick={startOrStopLoading}>{getButtonTitle(isLoading)}</BootstrapButton>
|
|
|
|
+ <Alert style={error !== undefined ? { marginTop: 12 } : { display: 'none' }}
|
|
|
|
+ onClose={() => setError(undefined)} severity='error'>Error loading validator report, please try
|
|
|
|
+ again.</Alert>
|
|
|
|
+ </Grid>
|
|
|
|
+ <Grid item xs={12} lg={12}>
|
|
|
|
+ <ValidatorReportCard stash={stash} report={report} />
|
|
|
|
+ </Grid>
|
|
|
|
+ <Grid item xs={12} lg={12}>
|
|
|
|
+ <div style={{ height: 600 }}>
|
|
|
|
+ <DataGrid
|
|
|
|
+ rows={report.report}
|
|
|
|
+ columns={columns as unknown as ColDef[]}
|
|
|
|
+ rowCount={report.totalCount}
|
|
|
|
+ paginationMode='server'
|
|
|
|
+ onPageChange={handlePageChange}
|
|
|
|
+ pageSize={report.pageSize}
|
|
|
|
+ rowsPerPageOptions={[]}
|
|
|
|
+ disableSelectionOnClick
|
|
|
|
+ autoHeight
|
|
|
|
+ />
|
|
|
|
+ </div>
|
|
|
|
+ </Grid>
|
|
|
|
+ </Grid>
|
|
|
|
+ </Container>
|
|
|
|
+ </div>
|
|
|
|
+ )
|
|
}
|
|
}
|
|
|
|
|
|
const ValidatorReportCard = (props: { stash: string, report: Reports }) => {
|
|
const ValidatorReportCard = (props: { stash: string, report: Reports }) => {
|
|
- const copyValidatorStatistics = () => navigator.clipboard.writeText(scoringPeriodText)
|
|
|
|
- const [scoringPeriodText, setScoringPeriodText] = useState('')
|
|
|
|
- const useStyles = makeStyles({
|
|
|
|
- root: {
|
|
|
|
- minWidth: '100%',
|
|
|
|
- textAlign: 'left',
|
|
|
|
- color: '#343a40',
|
|
|
|
- },
|
|
|
|
- title: {
|
|
|
|
- fontSize: 18,
|
|
|
|
- },
|
|
|
|
- pos: {
|
|
|
|
- marginTop: 12,
|
|
|
|
- },
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- const classes = useStyles();
|
|
|
|
-
|
|
|
|
- useEffect(() => {
|
|
|
|
- updateScoringPeriodText()
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- const updateScoringPeriodText = () => {
|
|
|
|
- if (props.report.report.length > 0) {
|
|
|
|
- const scoringDateFormat = 'DD-MM-yyyy';
|
|
|
|
- const report = `Validator Date: ${moment(props.report.startTime).format(scoringDateFormat)} - ${moment(props.report.startTime).format(scoringDateFormat)}\nDescription: I was an active validator from era/block ${props.report.startEra}/${props.report.startBlock} to era/block ${props.report.endEra}/${props.report.endBlock}\nwith stash account ${props.stash}. (I was active in all the eras in this range and found a total of ${props.report.totalBlocks} blocks)`
|
|
|
|
- setScoringPeriodText(report)
|
|
|
|
- } else {
|
|
|
|
- setScoringPeriodText('')
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ const copyValidatorStatistics = () => navigator.clipboard.writeText(scoringPeriodText)
|
|
|
|
+ const [scoringPeriodText, setScoringPeriodText] = useState('')
|
|
|
|
+ const useStyles = makeStyles({
|
|
|
|
+ root: {
|
|
|
|
+ minWidth: '100%',
|
|
|
|
+ textAlign: 'left',
|
|
|
|
+ color: '#343a40',
|
|
|
|
+ },
|
|
|
|
+ title: {
|
|
|
|
+ fontSize: 18,
|
|
|
|
+ },
|
|
|
|
+ pos: {
|
|
|
|
+ marginTop: 12,
|
|
|
|
+ },
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ const classes = useStyles()
|
|
|
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
+ updateScoringPeriodText()
|
|
|
|
+ })
|
|
|
|
+
|
|
|
|
+ const updateScoringPeriodText = () => {
|
|
if (props.report.report.length > 0) {
|
|
if (props.report.report.length > 0) {
|
|
- return (<Card className={classes.root}>
|
|
|
|
- <CardContent>
|
|
|
|
- <Typography className={classes.title} color="textSecondary" gutterBottom>
|
|
|
|
- Validator Report:
|
|
|
|
- </Typography>
|
|
|
|
- { scoringPeriodText.split('\n').map((i, key) => <Typography key={key} className={classes.pos} color="textSecondary">{i}</Typography>) }
|
|
|
|
- </CardContent>
|
|
|
|
- <CardActions>
|
|
|
|
- <Button onClick={copyValidatorStatistics} size="small">Copy to clipboard</Button>
|
|
|
|
- </CardActions>
|
|
|
|
- </Card>)
|
|
|
|
|
|
+ const scoringDateFormat = 'DD-MM-yyyy'
|
|
|
|
+ const report = `Validator Date: ${moment(props.report.startTime).format(scoringDateFormat)} - ${moment(props.report.startTime).format(scoringDateFormat)}\nDescription: I was an active validator from era/block ${props.report.startEra}/${props.report.startBlock} to era/block ${props.report.endEra}/${props.report.endBlock}\nwith stash account ${props.stash}. (I was active in all the eras in this range and found a total of ${props.report.totalBlocks} blocks)`
|
|
|
|
+ setScoringPeriodText(report)
|
|
|
|
+ } else {
|
|
|
|
+ setScoringPeriodText('')
|
|
}
|
|
}
|
|
- return (
|
|
|
|
- <Card className={classes.root}>
|
|
|
|
- <CardContent>
|
|
|
|
- <Typography className={classes.pos} color="textSecondary">
|
|
|
|
- No Data Available
|
|
|
|
- </Typography>
|
|
|
|
- </CardContent>
|
|
|
|
- </Card>
|
|
|
|
- )
|
|
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (props.report.report.length > 0) {
|
|
|
|
+ return (<Card className={classes.root}>
|
|
|
|
+ <CardContent>
|
|
|
|
+ <Typography className={classes.title} color='textSecondary' gutterBottom>
|
|
|
|
+ Validator Report:
|
|
|
|
+ </Typography>
|
|
|
|
+ {scoringPeriodText.split('\n').map((i, key) => <Typography key={key} className={classes.pos}
|
|
|
|
+ color='textSecondary'>{i}</Typography>)}
|
|
|
|
+ </CardContent>
|
|
|
|
+ <CardActions>
|
|
|
|
+ <Button onClick={copyValidatorStatistics} size='small'>Copy to clipboard</Button>
|
|
|
|
+ </CardActions>
|
|
|
|
+ </Card>)
|
|
|
|
+ }
|
|
|
|
+ return (
|
|
|
|
+ <Card className={classes.root}>
|
|
|
|
+ <CardContent>
|
|
|
|
+ <Typography className={classes.pos} color='textSecondary'>
|
|
|
|
+ No Data Available
|
|
|
|
+ </Typography>
|
|
|
|
+ </CardContent>
|
|
|
|
+ </Card>
|
|
|
|
+ )
|
|
}
|
|
}
|
|
|
|
|
|
export default ValidatorReport
|
|
export default ValidatorReport
|