LiveStatsWS.tsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. import './App.css';
  2. import { getValidatorStatistics, getChainState } from './get-status';
  3. import { Container, FormControlLabel, Grid, Switch, TextField } from '@material-ui/core';
  4. import { ColDef, DataGrid } from '@material-ui/data-grid';
  5. import { BootstrapButton } from './BootstrapButton';
  6. import { LinearProgressWithLabel } from './LinearProgressWithLabel';
  7. import { ActiveEra } from './Types';
  8. import Autocomplete from '@material-ui/lab/Autocomplete';
  9. import { useEffect, useState } from 'react';
  10. import { ValidatorsStats } from './ValidatorsStats';
  11. const LiveStatsWS = () => {
  12. const [shouldStop, setShouldStop] = useState(false);
  13. const [searchOptimized, setSearchOptimized] = useState(true);
  14. const [activeEras, setActiveEras] = useState([] as ActiveEra[]);
  15. const [columns] = useState(
  16. [
  17. { field: 'era', headerName: 'Era', width: 100, sortable: true },
  18. { field: 'block', headerName: 'Block', width: 100, sortable: true },
  19. { field: 'date', headerName: 'Date', width: 200, sortable: true },
  20. { field: 'points', headerName: 'Points', width: 100, sortable: true },
  21. { field: 'hash', headerName: 'Block Hash', width: 500, sortable: false },
  22. ]
  23. );
  24. const [stash, setStash] = useState('5EhDdcWm4TdqKp1ew1PqtSpoAELmjbZZLm5E34aFoVYkXdRW');
  25. const [startBlock, setStartBlock] = useState(1274283);
  26. const [endBlock, setEndBlock] = useState(1274383);
  27. const [isLoading, setIsLoading] = useState(false);
  28. const [lastBlock, setLastBlock] = useState(0);
  29. const [progress, setProgress] = useState({
  30. value: 0,
  31. min: 0,
  32. max: 0
  33. });
  34. const [activeValidators, setActiveValidators] = useState([]);
  35. useEffect(() => {
  36. updateChainState()
  37. const interval = setInterval(() => { updateChainState() }, 10000);
  38. return () => clearInterval(interval);
  39. }, []);
  40. const updateChainState = async () => {
  41. const chainState = await getChainState();
  42. setLastBlock(chainState.finalizedBlockHeight)
  43. setActiveValidators(chainState.validators.validators)
  44. }
  45. const nextBlockHeight = (currentBlock: number, isValidInCurrentEra: boolean) => {
  46. if (Number(startBlock) < Number(endBlock)) {
  47. if (searchOptimized && isValidInCurrentEra) {
  48. if ((currentBlock + 600) < Number(endBlock)) {
  49. return currentBlock + 600
  50. }
  51. return Number(endBlock)
  52. }
  53. return searchOptimized ? currentBlock + 10 : currentBlock + 1;
  54. } else {
  55. if (searchOptimized && isValidInCurrentEra) {
  56. if ((currentBlock - 600) < Number(endBlock)) {
  57. return currentBlock - 600
  58. }
  59. return Number(endBlock)
  60. }
  61. return searchOptimized ? currentBlock - 10 : currentBlock - 1;
  62. }
  63. }
  64. const startingBlockHeight = () => {
  65. if (Number(startBlock) < Number(endBlock)) {
  66. return searchOptimized ? (Number(startBlock) - 600 < 0) ? 0 : Number(startBlock) - 600 : Number(startBlock)
  67. }
  68. return searchOptimized ? Number(startBlock) + 600 : Number(startBlock)
  69. }
  70. const fetchBlocksData = async () => {
  71. resetDataBeforeLoading();
  72. let isValidInCurrentEra = false
  73. for (let blockHeight = startingBlockHeight(); ; blockHeight = nextBlockHeight(blockHeight, isValidInCurrentEra)) {
  74. let shouldStopLoading = false
  75. setShouldStop(prev => {
  76. shouldStopLoading = prev
  77. return shouldStopLoading
  78. })
  79. if (shouldStopLoading) {
  80. resetProgress();
  81. break;
  82. }
  83. isValidInCurrentEra = await fetchBlockData(Number(blockHeight));
  84. if (blockHeight === Number(endBlock)) {
  85. stopFetchingBlocksData()
  86. }
  87. }
  88. }
  89. const stopFetchingBlocksData = () => {
  90. if (!shouldStop) {
  91. setShouldStop(true)
  92. setIsLoading(false)
  93. }
  94. }
  95. const resetProgress = () => {
  96. setShouldStop(false)
  97. setIsLoading(false)
  98. setProgress({ value: 0, min: 0, max: 0 })
  99. }
  100. const fetchBlockData = async (blockHeight: number): Promise<boolean> => {
  101. updateProgress(blockHeight);
  102. let result = await getValidatorStatistics(stash, blockHeight);
  103. const isActiveBlock = result && result.status && activeEras.indexOf(result.status) < 0;
  104. if (isActiveBlock) {
  105. setActiveEras((prevEras) => [...prevEras, result.status])
  106. }
  107. stopLoadingOnLastBlock(blockHeight);
  108. return isActiveBlock
  109. }
  110. const stopLoadingOnLastBlock = (blockHeight: number) => {
  111. if (blockHeight.toString() === endBlock.toString()) {
  112. setIsLoading(false)
  113. }
  114. }
  115. const updateProgress = (blockHeight: number) => {
  116. setProgress({ value: blockHeight, min: (searchOptimized ? startBlock - 600 : startBlock), max: endBlock })
  117. }
  118. const resetDataBeforeLoading = () => {
  119. setIsLoading(true)
  120. setActiveEras([])
  121. }
  122. const shouldDisableButton = !stash || !startBlock || !endBlock;
  123. const endBlockLabel = lastBlock > 0 ? `End Block (Last block: ${lastBlock})` : 'End Block';
  124. const updateStartBlock = (e: { target: { value: unknown; }; }) => setStartBlock((e.target.value as unknown as number));
  125. const updateEndblock = (e: { target: { value: unknown; }; }) => setEndBlock((e.target.value as unknown as number));
  126. const startOrStopLoading = () => isLoading ? stopFetchingBlocksData() : fetchBlocksData();
  127. const updateSearchOptimized = (event: React.ChangeEvent<HTMLInputElement>) => setSearchOptimized(event.target.checked);
  128. return (
  129. <Container maxWidth="lg">
  130. <Grid
  131. container
  132. spacing={2}
  133. >
  134. <Grid item xs={12} lg={12}>
  135. <h1>Live Stats</h1>
  136. </Grid>
  137. <Grid item xs={12} lg={12}>
  138. <Autocomplete
  139. fullWidth
  140. freeSolo
  141. options={activeValidators}
  142. onChange={(e, value) => setStash(value || '')}
  143. value={stash}
  144. renderInput={(params) => <TextField {...params} label="Validator stash address" variant="filled" />} />
  145. </Grid>
  146. <Grid item xs={4} lg={5}>
  147. <TextField fullWidth type="number" onChange={updateStartBlock} id="block-start" label="Start Block" value={startBlock} variant="filled" />
  148. </Grid>
  149. <Grid item xs={4} lg={2}>
  150. <FormControlLabel
  151. control={
  152. <Switch
  153. checked={searchOptimized}
  154. onChange={updateSearchOptimized}
  155. name="searchOptimized"
  156. color="primary"
  157. />
  158. }
  159. disabled={isLoading}
  160. label={searchOptimized ? "Optimized search" : "Full search"}
  161. />
  162. </Grid>
  163. <Grid item xs={4} lg={5}>
  164. <TextField fullWidth type="number" onChange={updateEndblock} id="block-end" label={endBlockLabel} value={endBlock} variant="filled" />
  165. </Grid>
  166. <Grid item xs={12} lg={12}>
  167. <BootstrapButton size='large' style={{ minHeight: 56 }} disabled={shouldDisableButton} fullWidth onClick={startOrStopLoading} color="primary">{isLoading ? 'Stop loading' : 'Load data'}</BootstrapButton>
  168. </Grid>
  169. <Grid item xs={12} lg={12}>
  170. <LinearProgressWithLabel {...progress} />
  171. </Grid>
  172. <Grid item xs={12} lg={12}>
  173. <ValidatorsStats stash={stash} activeEras={activeEras} />
  174. </Grid>
  175. <Grid item xs={12} lg={12}>
  176. <div style={{ height: 600 }}>
  177. <DataGrid rows={activeEras} columns={columns as unknown as ColDef[]} pageSize={50} />
  178. </div>
  179. </Grid>
  180. </Grid>
  181. </Container>
  182. );
  183. }
  184. export default LiveStatsWS