diff --git a/src/components/layout/AppLayout.tsx b/src/components/layout/AppLayout.tsx index 7106e64..f0f5f1d 100644 --- a/src/components/layout/AppLayout.tsx +++ b/src/components/layout/AppLayout.tsx @@ -4,6 +4,7 @@ import { Outlet } from 'react-router-dom'; import { useOnNavigate, useSSE } from '../../hooks'; import LoadingPage from '../../pages/LoadingPage'; import { TopNav, Footer } from '../nav'; +import HaltBanner from './HaltBanner'; const AppLayout: React.FC = () => { // Single SSE subscription for the whole app โ€” every routed page @@ -22,6 +23,7 @@ const AppLayout: React.FC = () => { }} > + { + const { data: halt } = useHaltState(); + if (!halt?.halted) return null; + + return ( + + + System is paused for maintenance. Swaps are not being initiated. + Emissions are recycling. Scoring will resume when the system is + unpaused. + {halt.asOfBlock != null && ( + + (as of block #{halt.asOfBlock.toLocaleString()}) + + )} + + + ); +}; + +export default HaltBanner; diff --git a/src/components/layout/index.ts b/src/components/layout/index.ts index b168660..0e93839 100644 --- a/src/components/layout/index.ts +++ b/src/components/layout/index.ts @@ -1,3 +1,4 @@ export { default as AppLayout } from './AppLayout'; +export { default as HaltBanner } from './HaltBanner'; export { default as Page } from './Page'; export type { PageProps } from './Page'; diff --git a/src/components/miners/StickyNetworkHeader.tsx b/src/components/miners/StickyNetworkHeader.tsx index b8cf919..3256217 100644 --- a/src/components/miners/StickyNetworkHeader.tsx +++ b/src/components/miners/StickyNetworkHeader.tsx @@ -5,9 +5,24 @@ import { useCurrentCrown, useHaltState } from '../../api'; import CrownIcon from './CrownIcon'; import { FONTS } from '../../theme'; +// Mirrors allways/constants.py SCORING_WINDOW_BLOCKS โ€” the validator +// flushes crown_holders / rate_history once per round, so most panels +// only refresh at that cadence. The top-right indicator surfaces the +// math (last/next) so we don't need to label every panel individually. +const SCORING_WINDOW_BLOCKS = 600; + const StickyNetworkHeader: React.FC = () => { const { data: crown } = useCurrentCrown(); const { data: halt } = useHaltState(); + const head = halt?.asOfBlock ?? 0; + const lastRefresh = + head > 0 + ? Math.floor(head / SCORING_WINDOW_BLOCKS) * SCORING_WINDOW_BLOCKS + : null; + const blocksUntilRefresh = + lastRefresh != null + ? Math.max(0, lastRefresh + SCORING_WINDOW_BLOCKS - head) + : null; const segments: React.ReactNode[] = []; if (crown) { @@ -90,18 +105,49 @@ const StickyNetworkHeader: React.FC = () => { {segments} - + - - {halted ? 'halted' : 'healthy'} - + {halted ? ( + + paused + + ) : lastRefresh != null ? ( + + last refresh #{lastRefresh.toLocaleString()} + + ยท + + next ~{blocksUntilRefresh} blocks + + ) : ( + + healthy + + )}