diff --git a/frontend/cypress/e2e/dashboard/ledgers.cy.ts b/frontend/cypress/e2e/dashboard/ledgers.cy.ts index 21c8b8c663..22ecbdd6bd 100644 --- a/frontend/cypress/e2e/dashboard/ledgers.cy.ts +++ b/frontend/cypress/e2e/dashboard/ledgers.cy.ts @@ -88,7 +88,7 @@ describe('DASHBOARD LEDGERS', () => { cy.get('mina-dashboard-ledger > div:first-child > div.h-minus-xl > .group:nth-child(3) div.primary') .then((el: any) => { let progress; - progress = state.rpcStats.rootLedger?.fetched / state.rpcStats.rootLedger?.estimation * 100 || 0; + progress = state.rpcStats.snarkedRootLedger?.fetched / state.rpcStats.snarkedRootLedger?.estimation * 100 || 0; progress = Math.round(progress); if (state.nodes[0].ledgers.rootSnarked.state === 'success') { diff --git a/frontend/package-lock.json b/frontend/package-lock.json index f39bda174d..6c7b02ea18 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "frontend", - "version": "1.0.20", + "version": "1.0.43", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "frontend", - "version": "1.0.20", + "version": "1.0.43", "dependencies": { "@angular/animations": "^17.3.12", "@angular/cdk": "^17.3.10", @@ -27,7 +27,7 @@ "@ngrx/router-store": "^17.2.0", "@ngrx/store": "^17.2.0", "@nguniversal/express-engine": "^7.0.2", - "@openmina/shared": "^0.116.0", + "@openmina/shared": "^0.117.0", "@sentry/angular": "^8.35.0", "@sentry/cli": "^2.38.2", "@sentry/tracing": "^7.114.0", @@ -6204,9 +6204,9 @@ } }, "node_modules/@openmina/shared": { - "version": "0.116.0", - "resolved": "https://registry.npmjs.org/@openmina/shared/-/shared-0.116.0.tgz", - "integrity": "sha512-58wxJEI7T+0vTeBp+X1hn2xq9Y95dm+FiD+z40OuAeIDMpILjnWuhaiMXXdjv/CMfZRC2D42I7OJIwDChmVtvA==", + "version": "0.117.0", + "resolved": "https://registry.npmjs.org/@openmina/shared/-/shared-0.117.0.tgz", + "integrity": "sha512-ZGf3JWQKK0rEoFGdhSwUT/TPAljP0wq//1TmWd0uToEKgst+XsMiuT6vR1QcnK2ugTfXpKJf6adJ8ldIrD50vA==", "license": "Apache License, Version 2.0", "dependencies": { "tslib": ">=2.3.0" diff --git a/frontend/package.json b/frontend/package.json index 53028969a6..e0c8523cae 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "frontend", - "version": "1.0.21", + "version": "1.0.44", "scripts": { "install:deps": "npm install", "start": "npm install && ng serve --configuration local --open", @@ -42,7 +42,7 @@ "@ngrx/router-store": "^17.2.0", "@ngrx/store": "^17.2.0", "@nguniversal/express-engine": "^7.0.2", - "@openmina/shared": "^0.116.0", + "@openmina/shared": "^0.117.0", "@sentry/angular": "^8.35.0", "@sentry/cli": "^2.38.2", "@sentry/tracing": "^7.114.0", @@ -86,4 +86,4 @@ "webpack": "^5.88.2", "webpack-bundle-analyzer": "^4.9.0" } -} +} \ No newline at end of file diff --git a/frontend/src/app/app.actions.ts b/frontend/src/app/app.actions.ts index 0045b01454..59afd73900 100644 --- a/frontend/src/app/app.actions.ts +++ b/frontend/src/app/app.actions.ts @@ -2,6 +2,7 @@ import { MinaNode } from '@shared/types/core/environment/mina-env.type'; import { createType } from '@shared/constants/store-functions'; import { createAction, props } from '@ngrx/store'; import { AppNodeDetails } from '@shared/types/app/app-node-details.type'; +import { AppEnvBuild } from '@shared/types/app/app-env-build.type'; export const APP_KEY = 'app'; export const APP_PREFIX = 'App'; @@ -14,6 +15,8 @@ const initSuccess = createAction(type('Init Success'), props<{ activeNode: MinaN const changeActiveNode = createAction(type('Change Active Node'), props<{ node: MinaNode }>()); const getNodeDetails = createAction(type('Get Node Details')); const getNodeDetailsSuccess = createAction(type('Get Node Details Success'), props<{ details: AppNodeDetails }>()); +const getNodeEnvBuild = createAction(type('Get Node Env Build')); +const getNodeEnvBuildSuccess = createAction(type('Get Node Env Build Success'), props<{ envBuild: AppEnvBuild }>()); const deleteNode = createAction(type('Delete Node'), props<{ node: MinaNode }>()); const addNode = createAction(type('Add Node'), props<{ node: MinaNode }>()); @@ -28,6 +31,8 @@ export const AppActions = { changeActiveNode, getNodeDetails, getNodeDetailsSuccess, + getNodeEnvBuild, + getNodeEnvBuildSuccess, deleteNode, addNode, changeMenuCollapsing, diff --git a/frontend/src/app/app.component.html b/frontend/src/app/app.component.html index eb80c2f82e..bf25b5a9c8 100644 --- a/frontend/src/app/app.component.html +++ b/frontend/src/app/app.component.html @@ -21,7 +21,7 @@ @if (!hideToolbar) { } -
diff --git a/frontend/src/app/app.component.scss b/frontend/src/app/app.component.scss index 58810077a6..bd5100f44f 100644 --- a/frontend/src/app/app.component.scss +++ b/frontend/src/app/app.component.scss @@ -53,14 +53,10 @@ mat-sidenav-content { mat-sidenav-container, mat-sidenav-content { color: inherit; - background-color: $base-surface; - - @media (max-width: 767px) { - background-color: $base-background; - } + background-color: $base-background; } -.mina-content { +#mina-content { $toolbar: 40px; height: calc(100% - #{$toolbar}); border-top-left-radius: 6px; diff --git a/frontend/src/app/app.effects.ts b/frontend/src/app/app.effects.ts index fac206adf8..d72e583f36 100644 --- a/frontend/src/app/app.effects.ts +++ b/frontend/src/app/app.effects.ts @@ -2,8 +2,8 @@ import { Injectable } from '@angular/core'; import { MinaState, selectMinaState } from '@app/app.setup'; import { Actions, createEffect, ofType } from '@ngrx/effects'; import { Store } from '@ngrx/store'; -import { createNonDispatchableEffect, Effect, NonDispatchableEffect, removeParamsFromURL } from '@openmina/shared'; -import { filter, from, map, switchMap, tap } from 'rxjs'; +import { createNonDispatchableEffect, Effect, removeParamsFromURL } from '@openmina/shared'; +import { filter, map, mergeMap, of, switchMap, tap } from 'rxjs'; import { AppActions } from '@app/app.actions'; import { Router } from '@angular/router'; import { FeatureType, MinaNode } from '@shared/types/core/environment/mina-env.type'; @@ -22,9 +22,10 @@ import { AppNodeStatus } from '@shared/types/app/app-node-details.type'; export class AppEffects extends BaseEffect { readonly init$: Effect; - readonly initSuccess$: NonDispatchableEffect; + readonly initSuccess$: Effect; readonly onNodeChange$: Effect; readonly getNodeDetails$: Effect; + readonly getNodeEnvBuild$: Effect; private requestInProgress: boolean = false; @@ -46,7 +47,7 @@ export class AppEffects extends BaseEffect { map((payload: { activeNode: MinaNode, nodes: MinaNode[] }) => AppActions.initSuccess(payload)), )); - this.initSuccess$ = createNonDispatchableEffect(() => this.actions$.pipe( + this.initSuccess$ = createEffect(() => this.actions$.pipe( ofType(AppActions.initSuccess), this.latestActionState(), switchMap(({ state }) => { @@ -55,8 +56,9 @@ export class AppEffects extends BaseEffect { switchMap(() => this.webNodeService.startWasm$()), ); } - return from([]); + return of({}); }), + map(() => AppActions.getNodeEnvBuild()), )); this.onNodeChange$ = createNonDispatchableEffect(() => this.actions$.pipe( @@ -79,9 +81,16 @@ export class AppEffects extends BaseEffect { switchMap(() => this.webNodeService.startWasm$()), ); } - return from([]); + return of({}); }), - map(() => AppActions.getNodeDetails()), + switchMap(() => [AppActions.getNodeDetails(), AppActions.getNodeEnvBuild()]), + )); + + this.getNodeEnvBuild$ = createEffect(() => this.actions$.pipe( + ofType(AppActions.getNodeEnvBuild), + mergeMap(() => this.appService.getEnvBuild()), + map(envBuild => AppActions.getNodeEnvBuildSuccess({ envBuild })), + catchErrorAndRepeat2(MinaErrorType.RUST, AppActions.getNodeEnvBuildSuccess({ envBuild: undefined })), )); this.getNodeDetails$ = createEffect(() => this.actions$.pipe( @@ -95,9 +104,9 @@ export class AppEffects extends BaseEffect { status: AppNodeStatus.OFFLINE, blockHeight: null, blockTime: null, - peers: 0, - download: 0, - upload: 0, + peersConnected: 0, + peersDisconnected: 0, + peersConnecting: 0, transactions: 0, snarks: 0, producingBlockAt: null, diff --git a/frontend/src/app/app.reducer.ts b/frontend/src/app/app.reducer.ts index 93014aecf2..de4c8bb694 100644 --- a/frontend/src/app/app.reducer.ts +++ b/frontend/src/app/app.reducer.ts @@ -17,15 +17,16 @@ const initialState: AppState = { status: AppNodeStatus.PENDING, blockHeight: null, blockTime: null, - peers: 0, - download: 0, - upload: 0, + peersConnected: 0, + peersDisconnected: 0, + peersConnecting: 0, transactions: 0, snarks: 0, producingBlockAt: null, producingBlockGlobalSlot: null, producingBlockStatus: null, }, + envBuild: undefined, }; export const appReducer = createReducer( @@ -53,4 +54,5 @@ export const appReducer = createReducer( const nodes = state.nodes.filter(n => n.name !== node.name); return { ...state, nodes, activeNode: state.activeNode?.name === node.name ? nodes[0] : state.activeNode }; }), + on(AppActions.getNodeEnvBuildSuccess, (state, { envBuild }) => ({ ...state, envBuild })), ); diff --git a/frontend/src/app/app.service.ts b/frontend/src/app/app.service.ts index dd8bc34df1..e6fc2bbaf7 100644 --- a/frontend/src/app/app.service.ts +++ b/frontend/src/app/app.service.ts @@ -7,6 +7,7 @@ import { AppNodeDetails, AppNodeStatus } from '@shared/types/app/app-node-detail import { getNetwork } from '@shared/helpers/mina.helper'; import { getLocalStorage, ONE_MILLION } from '@openmina/shared'; import { BlockProductionWonSlotsStatus } from '@shared/types/block-production/won-slots/block-production-won-slots-slot.type'; +import { AppEnvBuild } from '@shared/types/app/app-env-build.type'; @Injectable({ providedIn: 'root', @@ -29,6 +30,10 @@ export class AppService { ]); } + getEnvBuild(): Observable { + return this.rust.get('/build_env'); + } + getActiveNodeDetails(): Observable { return this.rust.get('/status') .pipe( @@ -36,9 +41,9 @@ export class AppService { status: this.getStatus(data), blockHeight: data.transition_frontier.best_tip?.height, blockTime: data.transition_frontier.sync.time, - peers: data.peers.filter(p => p.connection_status === 'Connected').length, - download: 0, - upload: 0, + peersConnected: data.peers.filter(p => p.connection_status === 'Connected').length, + peersDisconnected: data.peers.filter(p => p.connection_status === 'Disconnected').length, + peersConnecting: data.peers.filter(p => p.connection_status === 'Connecting').length, snarks: data.snark_pool.snarks, transactions: data.transaction_pool.transactions, chainId: data.chain_id, diff --git a/frontend/src/app/app.state.ts b/frontend/src/app/app.state.ts index bacf1f859b..5899469521 100644 --- a/frontend/src/app/app.state.ts +++ b/frontend/src/app/app.state.ts @@ -3,12 +3,14 @@ import { AppMenu } from '@shared/types/app/app-menu.type'; import { createSelector, MemoizedSelector } from '@ngrx/store'; import { MinaNode } from '@shared/types/core/environment/mina-env.type'; import { AppNodeDetails } from '@shared/types/app/app-node-details.type'; +import { AppEnvBuild } from '@shared/types/app/app-env-build.type'; export interface AppState { menu: AppMenu; nodes: MinaNode[]; activeNode: MinaNode; activeNodeDetails: AppNodeDetails; + envBuild: AppEnvBuild | undefined; } const select = (selector: (state: AppState) => T): MemoizedSelector => createSelector( @@ -20,10 +22,12 @@ const menu = select(state => state.menu); const nodes = select(state => state.nodes); const activeNode = select(state => state.activeNode); const activeNodeDetails = select(state => state.activeNodeDetails); +const envBuild = select(state => state.envBuild); export const AppSelectors = { menu, nodes, activeNode, activeNodeDetails, + envBuild, }; diff --git a/frontend/src/app/core/services/rust.service.ts b/frontend/src/app/core/services/rust.service.ts index 6f428c7436..deda879343 100644 --- a/frontend/src/app/core/services/rust.service.ts +++ b/frontend/src/app/core/services/rust.service.ts @@ -68,6 +68,8 @@ export class RustService { return this.webNodeService.accounts$; case '/best-chain-user-commands': return this.webNodeService.bestChainUserCommands$; + case '/build_env': + return this.webNodeService.envBuildDetails$; default: throw new Error(`Web node doesn't support "${path}" path!`); } diff --git a/frontend/src/app/core/services/web-node.service.ts b/frontend/src/app/core/services/web-node.service.ts index 5deec9a9a7..5f5f0beca2 100644 --- a/frontend/src/app/core/services/web-node.service.ts +++ b/frontend/src/app/core/services/web-node.service.ts @@ -14,6 +14,7 @@ import { CONFIG } from '@shared/constants/config'; export class WebNodeService { private readonly webnode$: BehaviorSubject = new BehaviorSubject(null); + private readonly wasm$: BehaviorSubject = new BehaviorSubject(null); private webNodeKeyPair: { publicKey: string, privateKey: string }; private webNodeNetwork: String; private webNodeStartTime: number; @@ -27,7 +28,7 @@ export class WebNodeService { FileProgressHelper.initDownloadProgress(); const basex = base('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'); safelyExecuteInBrowser(() => { - any(window)['bs58btc'] = { + any(window).bs58btc = { encode: (buffer: Uint8Array | number[]) => 'z' + basex.encode(buffer), decode: (string: string) => basex.decode(string.substring(1)), }; @@ -47,6 +48,7 @@ export class WebNodeService { loadWasm$(): Observable { this.webNodeStartTime = Date.now(); + if (isBrowser()) { const args = (() => { const raw = localStorage.getItem('webnodeArgs'); @@ -88,7 +90,11 @@ export class WebNodeService { if (isBrowser()) { return of(any(window).webnode) .pipe( - switchMap((wasm: any) => from(wasm.default(undefined, new WebAssembly.Memory(this.memory))).pipe(map(() => wasm))), + switchMap((wasm: any) => { + this.wasm$.next(wasm); + return from(wasm.default(undefined, new WebAssembly.Memory(this.memory))) + .pipe(map(() => wasm)); + }), switchMap((wasm) => { this.webnodeProgress$.next('Loaded'); const urls = (() => { @@ -99,14 +105,16 @@ export class WebNodeService { genesisConfig: url + 'genesis/config', }; } else { - return {}; + return { + seeds: 'https://bootnodes.minaprotocol.com/networks/devnet-webrtc.txt', + }; } })(); console.log('webnode config:', !!this.webNodeKeyPair.privateKey, this.webNodeNetwork, urls); return from(wasm.run(this.webNodeKeyPair.privateKey, urls.seeds, urls.genesisConfig)); }), tap((webnode: any) => { - any(window)['webnode'] = webnode; + any(window).webnode = webnode; this.webnode$.next(webnode); this.webnodeProgress$.next('Started'); }), @@ -115,7 +123,6 @@ export class WebNodeService { return throwError(() => new Error(error.message)); }), switchMap(() => this.webnode$.asObservable()), - filter(Boolean), ); } return EMPTY; @@ -198,4 +205,11 @@ export class WebNodeService { switchMap(handle => from(any(handle).transaction_pool().get())), ); } + + get envBuildDetails$(): Observable { + return this.wasm$.asObservable().pipe( + filter(Boolean), + map(handle => handle.build_env()), + ); + } } diff --git a/frontend/src/app/features/block-production/overview/block-production-overview.component.ts b/frontend/src/app/features/block-production/overview/block-production-overview.component.ts index 770b06a8b1..dcf8ba1de0 100644 --- a/frontend/src/app/features/block-production/overview/block-production-overview.component.ts +++ b/frontend/src/app/features/block-production/overview/block-production-overview.component.ts @@ -2,16 +2,13 @@ import { ChangeDetectionStrategy, Component, ElementRef, OnDestroy, OnInit } fro import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; import { BlockProductionOverviewActions } from '@block-production/overview/block-production-overview.actions'; import { getMergedRoute, isDesktop, MergedRoute, safelyExecuteInBrowser } from '@openmina/shared'; -import { debounceTime, filter, fromEvent, skip, take } from 'rxjs'; +import { debounceTime, filter, fromEvent, take } from 'rxjs'; import { isNaN } from 'mathjs'; import { untilDestroyed } from '@ngneat/until-destroy'; import { BlockProductionOverviewSelectors } from '@block-production/overview/block-production-overview.state'; import { SLOTS_PER_EPOCH } from '@shared/constants/mina'; -import { - BlockProductionOverviewEpoch, -} from '@shared/types/block-production/overview/block-production-overview-epoch.type'; +import { BlockProductionOverviewEpoch } from '@shared/types/block-production/overview/block-production-overview-epoch.type'; import { AppSelectors } from '@app/app.state'; -import { MinaNode } from '@shared/types/core/environment/mina-env.type'; @Component({ selector: 'mina-block-production-overview', diff --git a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.component.html b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.component.html index 182a58a2e0..ee26f0da36 100644 --- a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.component.html +++ b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.component.html @@ -1,24 +1,55 @@ - - - - -
- - @if (isDesktop) { - - } -
- - +@if (isPending || nodeIsBootstrapping || isCalculatingVRF || isLoading) { +
+ +
+ @if (isPending || isLoading) { +
Loading
+ } @else if (nodeIsBootstrapping) { +
Waiting For Sync
+
Only a synced node can calculate block production rights
+ } @else { +
Calculating Block Producing Rights
+
+ Epoch {{ epoch }} - Slot {{ vrfStats?.evaluated }}/{{ vrfStats?.total }} +
+ }
- +} @else { + + + + +
+ + @if (isDesktop) { + + } +
+ + @if (!emptySlots) { + + } @else { +
+ cancel_presentation +
No won slots yet
+
+ New won slots will appear here when the node receives them +
+
+ } +
+
+
- - - + + + +} diff --git a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.component.scss b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.component.scss index c1d2d3ea65..053b423114 100644 --- a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.component.scss +++ b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.component.scss @@ -12,3 +12,7 @@ } } } + +.mina-icon { + font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' -25, 'opsz' 24 !important; +} diff --git a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.component.ts b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.component.ts index 81d78072d0..dc098d8085 100644 --- a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.component.ts +++ b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.component.ts @@ -1,38 +1,73 @@ import { ChangeDetectionStrategy, Component, ElementRef, OnDestroy, OnInit } from '@angular/core'; import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; -import { getMergedRoute, isDesktop, isMobile, MergedRoute } from '@openmina/shared'; -import { debounceTime, filter, fromEvent, skip, take, timer } from 'rxjs'; +import { getMergedRoute, isDesktop, MergedRoute } from '@openmina/shared'; +import { take, timer } from 'rxjs'; import { untilDestroyed } from '@ngneat/until-destroy'; import { BlockProductionWonSlotsActions } from '@block-production/won-slots/block-production-won-slots.actions'; import { AppSelectors } from '@app/app.state'; import { BlockProductionWonSlotsSelectors } from '@block-production/won-slots/block-production-won-slots.state'; +import { AppNodeDetails, AppNodeStatus } from '@shared/types/app/app-node-details.type'; +import { animate, style, transition, trigger } from '@angular/animations'; @Component({ selector: 'mina-block-production-won-slots', templateUrl: './block-production-won-slots.component.html', styleUrls: ['./block-production-won-slots.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush, + animations: [ + trigger('fadeInOut', [ + transition(':enter', [ + style({ opacity: 0 }), + animate('400ms ease-in', style({ opacity: 1 })), + ]), + transition(':leave', [ + animate('400ms ease-out', style({ opacity: 0 })), + ]), + ]), + ], }) export class BlockProductionWonSlotsComponent extends StoreDispatcher implements OnInit, OnDestroy { showSidePanel: boolean = isDesktop(); isDesktop: boolean = isDesktop(); + nodeIsBootstrapping: boolean = false; + isPending: boolean = true; + isCalculatingVRF: boolean = false; + vrfStats: { + evaluated: number; + total: number; + }; + epoch: number; + emptySlots: boolean = false; + isLoading: boolean = true; constructor(protected el: ElementRef) { super(); } ngOnInit(): void { this.listenToActiveNode(); + this.listenToNodeChange(); timer(10000, 10000) .pipe(untilDestroyed(this)) .subscribe(() => { this.dispatch2(BlockProductionWonSlotsActions.getSlots()); }); this.listenToResize(); + this.listenToActiveEpoch(); + this.listenToSlots(); + } + + private listenToNodeChange(): void { + this.select(AppSelectors.activeNodeDetails, (node: AppNodeDetails) => { + this.nodeIsBootstrapping = node?.status === AppNodeStatus.BOOTSTRAP; + this.isPending = node?.status === AppNodeStatus.PENDING; + this.detect(); + }); } private listenToActiveNode(): void { this.select(AppSelectors.activeNode, () => { this.select(getMergedRoute, (data: MergedRoute) => { + this.isLoading = true; this.dispatch2(BlockProductionWonSlotsActions.init({ activeSlotRoute: data.params['id'] })); }, take(1)); }); @@ -45,6 +80,28 @@ export class BlockProductionWonSlotsComponent extends StoreDispatcher implements }); } + private listenToActiveEpoch(): void { + this.select(BlockProductionWonSlotsSelectors.epoch, (activeEpoch) => { + this.epoch = activeEpoch.epochNumber; + this.vrfStats = activeEpoch.vrfStats; + this.detect(); + }); + } + + private listenToSlots(): void { + this.select(BlockProductionWonSlotsSelectors.slots, (slots) => { + const emptySlots = slots.length === 0; + if (emptySlots !== this.emptySlots) { + this.emptySlots = emptySlots; + this.detect(); + } + }); + this.select(BlockProductionWonSlotsSelectors.serverResponded, (responded) => { + this.isLoading = !responded; + this.detect(); + }); + } + override ngOnDestroy(): void { super.ngOnDestroy(); this.dispatch2(BlockProductionWonSlotsActions.close()); diff --git a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.effects.ts b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.effects.ts index bbccec2c35..68f2412866 100644 --- a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.effects.ts +++ b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.effects.ts @@ -62,7 +62,7 @@ export class BlockProductionWonSlotsEffects extends BaseEffect { || (activeSlotRoute && !bpState.activeSlot) || (activeSlotRoute && bpState.openSidePanel) ) { - routes.push(newActiveSlot.globalSlot.toString()); + routes.push(newActiveSlot?.globalSlot.toString() ?? ''); } return fromPromise(this.router.navigate(routes, { queryParamsHandling: 'merge' })).pipe(map(() => ({ slots, diff --git a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.reducer.ts b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.reducer.ts index d18bc61073..8cd720605e 100644 --- a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.reducer.ts +++ b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.reducer.ts @@ -24,6 +24,7 @@ const initialState: BlockProductionWonSlotsState = { sortBy: 'slotTime', sortDirection: SortDirection.ASC, }, + serverResponded: false, }; export const blockProductionWonSlotsReducer = createReducer( @@ -31,6 +32,7 @@ export const blockProductionWonSlotsReducer = createReducer( on(BlockProductionWonSlotsActions.init, (state, { activeSlotRoute }) => ({ ...state, activeSlotRoute, + serverResponded: false, })), on(BlockProductionWonSlotsActions.getSlotsSuccess, (state, { slots, epoch, activeSlot }) => ({ ...state, @@ -39,6 +41,7 @@ export const blockProductionWonSlotsReducer = createReducer( filteredSlots: filterSlots(sortSlots(slots, state.sort), state.filters), activeSlot, openSidePanel: state.activeSlot ? state.openSidePanel : isDesktop(), + serverResponded: true, })), on(BlockProductionWonSlotsActions.setActiveSlot, (state, { slot }) => ({ ...state, diff --git a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.service.ts b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.service.ts index f99d53354d..c7f0039640 100644 --- a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.service.ts +++ b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.service.ts @@ -22,7 +22,7 @@ export class BlockProductionWonSlotsService { .pipe( map((response: WonSlotResponse) => { if (!response) { - throw new Error('Empty response from /stats/block_producer'); + return { slots: [], epoch: undefined }; } const attemptsSlots = response.attempts.map((attempt: Attempt) => { attempt.won_slot.slot_time = Math.floor(attempt.won_slot.slot_time / ONE_MILLION); // converted to milliseconds @@ -99,10 +99,15 @@ export class BlockProductionWonSlotsService { return { slots: [...attemptsSlots, ...futureWonSlots], epoch: { + epochNumber: response.current_epoch, start: response.epoch_start, end: response.epoch_end, currentGlobalSlot: response.current_global_slot, currentTime: response.current_time, + vrfStats: { + evaluated: response.current_epoch_vrf_stats?.evaluated_slots, + total: response.current_epoch_vrf_stats?.total_slots, + }, }, }; }), @@ -155,6 +160,17 @@ export interface WonSlotResponse { current_time: number; epoch_end: number; epoch_start: number; + current_epoch: number; + current_epoch_vrf_stats: { + evaluated_slots: number; + total_slots: number; + }; + vrf_stats: { + [epochNumber: string]: { + evaluated_slots: number; + total_slots: number; + }; + }; } interface Attempt { diff --git a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.state.ts b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.state.ts index 1a0f211b2a..f97282a156 100644 --- a/frontend/src/app/features/block-production/won-slots/block-production-won-slots.state.ts +++ b/frontend/src/app/features/block-production/won-slots/block-production-won-slots.state.ts @@ -21,6 +21,7 @@ export interface BlockProductionWonSlotsState { openSidePanel: boolean; filters: BlockProductionWonSlotsFilters; sort: TableSort; + serverResponded: boolean; } @@ -36,6 +37,7 @@ const activeSlot = select(state => state.activeSlot); const filters = select(state => state.filters); const sort = select(state => state.sort); const openSidePanel = select(state => state.openSidePanel); +const serverResponded = select(state => state.serverResponded); export const BlockProductionWonSlotsSelectors = { epoch, @@ -45,4 +47,5 @@ export const BlockProductionWonSlotsSelectors = { filters, sort, openSidePanel, + serverResponded, }; diff --git a/frontend/src/app/features/block-production/won-slots/cards/block-production-won-slots-cards.component.html b/frontend/src/app/features/block-production/won-slots/cards/block-production-won-slots-cards.component.html index 0e8049dc34..e20de21d1f 100644 --- a/frontend/src/app/features/block-production/won-slots/cards/block-production-won-slots-cards.component.html +++ b/frontend/src/app/features/block-production/won-slots/cards/block-production-won-slots-cards.component.html @@ -26,7 +26,7 @@ class="mr-10"> diff --git a/frontend/src/app/features/dashboard/dashboard-blocks-sync/dashboard-blocks-sync.component.html b/frontend/src/app/features/dashboard/dashboard-blocks-sync/dashboard-blocks-sync.component.html index b8ec53bcfe..0e89fd94b9 100644 --- a/frontend/src/app/features/dashboard/dashboard-blocks-sync/dashboard-blocks-sync.component.html +++ b/frontend/src/app/features/dashboard/dashboard-blocks-sync/dashboard-blocks-sync.component.html @@ -8,7 +8,7 @@ }
Last 290 blocks
-
{{ syncProgress }}
+
{{ remaining ? 'ETA ~' + remaining + 's' : syncProgress }}
b.fetchEnd).length; + const blocksApplied = blocks.filter(b => b.applyEnd).length; + if (blocksApplied < 291) { + + const syncStart = Math.min(...blocks.map(b => b.fetchStart).filter(Boolean)) / ONE_MILLION; + const now = Date.now(); + const secondsPassed = (now - syncStart) / 1000; + + const fetchWeight = 1; + const applyWeight = 5; + + // Apply weights: 1 for each fetched block, and 5 for each applied block + const weightedBlocksTotal = (blocksFetched * fetchWeight) + (blocksApplied * applyWeight); + const blocksPerSecond = weightedBlocksTotal / secondsPassed; + + if (blocksPerSecond > 0) { + const weightedBlocksRemaining = (291 * fetchWeight) + (291 * applyWeight) - weightedBlocksTotal; + const secondsRemaining = weightedBlocksRemaining / blocksPerSecond; + this.remaining = Math.ceil(secondsRemaining); + } + } else { + this.remaining = null; + } + this.extractNodesData(nodes); this.extractPeersData(peers); } @@ -69,6 +96,9 @@ export class DashboardBlocksSyncComponent extends StoreDispatcher implements OnI this.bestTipBlock = blocks[0].height; this.bestTipBlockSyncedText = 'Fetched ' + this.calculateProgressTime(nodes[0].bestTipReceivedTimestamp * ONE_MILLION).slice(7); this.syncProgress = this.bestTipBlockSyncedText.slice(8); + if (lastItem(blocks).status !== NodesOverviewNodeBlockStatus.APPLIED) { + this.syncProgress = 'Pending'; + } } if (blocks.length === 291) { diff --git a/frontend/src/app/features/dashboard/dashboard-ledger/dashboard-ledger.component.html b/frontend/src/app/features/dashboard/dashboard-ledger/dashboard-ledger.component.html index 193765b65f..84703092f4 100644 --- a/frontend/src/app/features/dashboard/dashboard-ledger/dashboard-ledger.component.html +++ b/frontend/src/app/features/dashboard/dashboard-ledger/dashboard-ledger.component.html @@ -17,9 +17,11 @@ Staking ledger
-
{{ stakingProgress | number: '1.0-0' }}% -
+ @if (stakingProgress === 100) { +
100%
+ } @else if (remainingStakingLedger) { +
ETA ~{{ remainingStakingLedger }}s
+ }
Next epoch ledger
-
- {{ nextProgress | number: '1.0-0' }}% -
+ @if (nextProgress === 100) { +
100%
+ } @else if (remainingNextLedger) { +
ETA ~{{ remainingNextLedger }}s
+ }
Snarked ledger at the root
-
- {{ rootSnarkedProgress | number: '1.0-0' }}% -
+ @if (rootSnarkedProgress === 100) { +
100%
+ } @else if (remainingRootSnarkedLedger) { +
ETA ~{{ remainingRootSnarkedLedger }}s
+ }
Staged ledger at the root
-
- {{ rootStagedProgress | number: '1.0-0' }}% -
+ @if ((rootStagedProgress === 100 || !isWebNode) && rootSnarkedProgress === 100) { +
{{ rootStagedProgress }}%
+ } @else if (remainingReconstruct && remainingRootStagedLedgerFetchParts + remainingReconstruct) { +
ETA ~{{ remainingRootStagedLedgerFetchParts + remainingReconstruct }}s
+ }
diff --git a/frontend/src/app/features/dashboard/dashboard-ledger/dashboard-ledger.component.ts b/frontend/src/app/features/dashboard/dashboard-ledger/dashboard-ledger.component.ts index 51f008fb69..a18e20923c 100644 --- a/frontend/src/app/features/dashboard/dashboard-ledger/dashboard-ledger.component.ts +++ b/frontend/src/app/features/dashboard/dashboard-ledger/dashboard-ledger.component.ts @@ -12,6 +12,8 @@ import { ONE_MILLION, SecDurationConfig } from '@openmina/shared'; import { Overlay, OverlayRef } from '@angular/cdk/overlay'; import { TemplatePortal } from '@angular/cdk/portal'; import { DashboardRpcStats } from '@shared/types/dashboard/dashboard-rpc-stats.type'; +import { AppSelectors } from '@app/app.state'; +import { MinaNode } from '@shared/types/core/environment/mina-env.type'; type LedgerConfigMap = { stakingEpoch: SecDurationConfig, @@ -76,6 +78,7 @@ export class DashboardLedgerComponent extends StoreDispatcher implements OnInit, rootSnarkedProgress: number = 0; rootStagedProgress: number = 0; totalProgress: number; + isWebNode: boolean; @ViewChild('tooltipRef') private tooltipRef: TemplateRef<{ start: number, end: number }>; private overlayRef: OverlayRef; @@ -86,9 +89,27 @@ export class DashboardLedgerComponent extends StoreDispatcher implements OnInit, } ngOnInit(): void { + this.listenToActiveNode(); this.listenToNodesChanges(); } + private listenToActiveNode(): void { + this.select(AppSelectors.activeNode, (node: MinaNode) => { + this.isWebNode = node.isWebNode; + }); + } + + remainingStakingLedger: number; + private previousStakingLedgerDownloaded: number; + remainingNextLedger: number; + private previousNextLedgerDownloaded: number; + remainingRootSnarkedLedger: number; + private previousRootSnarkedLedgerDownloaded: number; + remainingRootStagedLedgerFetchParts: number; + private previousRootStagedLedgerDownloaded: number; + remainingReconstruct: number = 20; + private reconstructTimer: any; + private listenToNodesChanges(): void { this.select(selectDashboardNodesAndRpcStats, ([nodes, rpcStats]: [NodesOverviewNode[], DashboardRpcStats]) => { if (nodes.length === 0) { @@ -123,10 +144,77 @@ export class DashboardLedgerComponent extends StoreDispatcher implements OnInit, rootStaged: getConfig(this.ledgers.rootStaged.state), }; this.setProgressTime(); + + const stakingLedgerStartTime = this.ledgers.stakingEpoch.snarked.fetchHashesStart / ONE_MILLION; + const currentStakingLedgerDownloaded = rpcStats.stakingLedger?.fetched; + if ( + this.previousStakingLedgerDownloaded && currentStakingLedgerDownloaded && + stakingLedgerStartTime < Date.now() + ) { + const timeSinceDownloadStarted = Date.now() - stakingLedgerStartTime; + const remaining = rpcStats.stakingLedger?.estimation - currentStakingLedgerDownloaded; + const remainingTime = (remaining / currentStakingLedgerDownloaded) * timeSinceDownloadStarted; + this.remainingStakingLedger = Math.floor(remainingTime / 1000); + } + this.previousStakingLedgerDownloaded = currentStakingLedgerDownloaded; + + const nextLedgerStartTime = this.ledgers.nextEpoch.snarked.fetchHashesStart / ONE_MILLION; + const currentNextLedgerDownloaded = rpcStats.nextLedger?.fetched; + if ( + this.previousNextLedgerDownloaded && currentNextLedgerDownloaded && + nextLedgerStartTime < Date.now() + ) { + const timeSinceDownloadStarted = Date.now() - nextLedgerStartTime; + const remaining = rpcStats.nextLedger?.estimation - currentNextLedgerDownloaded; + const remainingTime = (remaining / currentNextLedgerDownloaded) * timeSinceDownloadStarted; + this.remainingNextLedger = Math.floor(remainingTime / 1000); + } + this.previousNextLedgerDownloaded = currentNextLedgerDownloaded; + + const rootSnarkedLedgerStartTime = this.ledgers.rootSnarked.snarked.fetchHashesStart / ONE_MILLION; + const currentRootSnarkedLedgerDownloaded = rpcStats.snarkedRootLedger?.fetched; + if ( + this.previousRootSnarkedLedgerDownloaded && currentRootSnarkedLedgerDownloaded && + rootSnarkedLedgerStartTime < Date.now() + ) { + const timeSinceDownloadStarted = Date.now() - rootSnarkedLedgerStartTime; + const remaining = rpcStats.snarkedRootLedger?.estimation - currentRootSnarkedLedgerDownloaded; + const remainingTime = (remaining / currentRootSnarkedLedgerDownloaded) * timeSinceDownloadStarted; + this.remainingRootSnarkedLedger = Math.floor(remainingTime / 1000); + } + this.previousRootSnarkedLedgerDownloaded = currentRootSnarkedLedgerDownloaded; + + if (this.isWebNode) { + const rootStagedLedgerStartTime = this.ledgers.rootStaged.staged.fetchPartsStart / ONE_MILLION; + const currentRootStagedLedgerDownloaded = rpcStats.stagedRootLedger?.fetched; + if ( + this.previousRootStagedLedgerDownloaded && currentRootStagedLedgerDownloaded && + rootStagedLedgerStartTime < Date.now() + ) { + const timeSinceDownloadStarted = Date.now() - rootStagedLedgerStartTime; + const remaining = rpcStats.stagedRootLedger?.estimation - currentRootStagedLedgerDownloaded; + const remainingTime = (remaining / currentRootStagedLedgerDownloaded) * timeSinceDownloadStarted; + this.remainingRootStagedLedgerFetchParts = Math.floor(remainingTime / 1000); + } + this.previousRootStagedLedgerDownloaded = currentRootStagedLedgerDownloaded; + } + this.stakingProgress = rpcStats.stakingLedger?.fetched / rpcStats.stakingLedger?.estimation * 100 || 0; this.nextProgress = rpcStats.nextLedger?.fetched / rpcStats.nextLedger?.estimation * 100 || 0; - this.rootSnarkedProgress = rpcStats.rootLedger?.fetched / rpcStats.rootLedger?.estimation * 100 || 0; - this.rootStagedProgress = this.ledgers.rootStaged.staged.fetchPartsEnd ? 50 : 0; + this.rootSnarkedProgress = rpcStats.snarkedRootLedger?.fetched / rpcStats.snarkedRootLedger?.estimation * 100 || 0; + + this.rootStagedProgress = 0; + if (this.ledgers.rootStaged.staged.fetchPartsEnd) { + this.rootStagedProgress += 50; + } + if (this.ledgers.rootStaged.staged.reconstructEnd) { + this.rootStagedProgress += 50; + } + if (this.rootStagedProgress < 100 && this.isWebNode && this.ledgers.rootStaged.staged.fetchPartsEnd && !this.reconstructTimer) { + this.startTimerForReconstruct(); + } else if (this.rootStagedProgress === 100) { + clearTimeout(this.reconstructTimer); + } if (this.ledgers.stakingEpoch.state === NodesOverviewLedgerStepState.SUCCESS) { this.stakingProgress = 100; @@ -146,6 +234,16 @@ export class DashboardLedgerComponent extends StoreDispatcher implements OnInit, }); } + startTimerForReconstruct(): void { + this.reconstructTimer = setInterval(() => { + this.remainingReconstruct = this.remainingReconstruct - 1; + this.detect(); + if (this.remainingReconstruct === Math.floor(Math.random() * 2) + 2) { + clearTimeout(this.reconstructTimer); + } + }, 1000); + } + show(event: MouseEvent, start: number, end: number): void { if (this.overlayRef?.hasAttached()) { this.overlayRef.detach(); diff --git a/frontend/src/app/features/dashboard/dashboard-transition-frontier/dashboard-transition-frontier.component.html b/frontend/src/app/features/dashboard/dashboard-transition-frontier/dashboard-transition-frontier.component.html deleted file mode 100644 index 549dcca28f..0000000000 --- a/frontend/src/app/features/dashboard/dashboard-transition-frontier/dashboard-transition-frontier.component.html +++ /dev/null @@ -1,16 +0,0 @@ -
- - Transition frontier -
-
- - -
- - - margin - - - - diff --git a/frontend/src/app/features/dashboard/dashboard-transition-frontier/dashboard-transition-frontier.component.scss b/frontend/src/app/features/dashboard/dashboard-transition-frontier/dashboard-transition-frontier.component.scss deleted file mode 100644 index 17d80cb52d..0000000000 --- a/frontend/src/app/features/dashboard/dashboard-transition-frontier/dashboard-transition-frontier.component.scss +++ /dev/null @@ -1,34 +0,0 @@ -@import 'openmina'; - -.tracing-container { - gap: 8px; - - mina-dashboard-ledger { - width: 100%; - max-width: 410px; - min-width: 250px; - } - - mina-dashboard-blocks-sync { - width: 100%; - max-width: 640px; - } - - @media (max-width: 1374px) { - display: flex; - flex-direction: column; - } -} - -.loading-icon { - animation: spin 2s linear infinite; -} - -@keyframes spin { - 0% { - transform: rotate(0); - } - 100% { - transform: rotate(360deg); - } -} diff --git a/frontend/src/app/features/dashboard/dashboard-transition-frontier/dashboard-transition-frontier.component.ts b/frontend/src/app/features/dashboard/dashboard-transition-frontier/dashboard-transition-frontier.component.ts deleted file mode 100644 index 7af4da1482..0000000000 --- a/frontend/src/app/features/dashboard/dashboard-transition-frontier/dashboard-transition-frontier.component.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; -import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; -import { selectDashboardNodes } from '@dashboard/dashboard.state'; -import { NodesOverviewNode, NodesOverviewNodeKindType } from '@shared/types/nodes/dashboard/nodes-overview-node.type'; - -@Component({ - selector: 'mina-dashboard-transition-frontier', - templateUrl: './dashboard-transition-frontier.component.html', - styleUrls: ['./dashboard-transition-frontier.component.scss'], - changeDetection: ChangeDetectionStrategy.OnPush, - host: { class: 'flex-column flex-1' }, -}) -export class DashboardTransitionFrontierComponent extends StoreDispatcher implements OnInit { - - loading: boolean = true; - - ngOnInit(): void { - this.listenToNodesChanges(); - } - - private listenToNodesChanges(): void { - this.select(selectDashboardNodes, (nodes: NodesOverviewNode[]) => { - this.loading = !nodes.length || nodes[0].kind !== NodesOverviewNodeKindType.SYNCED; - this.detect(); - }); - } -} diff --git a/frontend/src/app/features/dashboard/dashboard.component.scss b/frontend/src/app/features/dashboard/dashboard.component.scss index ceee98e524..6f38a175ae 100644 --- a/frontend/src/app/features/dashboard/dashboard.component.scss +++ b/frontend/src/app/features/dashboard/dashboard.component.scss @@ -7,10 +7,6 @@ .comps { gap: 8px; padding-right: 8px; - - @media (max-width: 767px) { - padding: 0 8px; - } } mina-dashboard-network { @@ -25,7 +21,7 @@ mina-dashboard-ledger { mina-dashboard-blocks-sync { width: 100%; - max-width: 640px; + max-width: 644px; } @media (max-width: 767px) { @@ -49,7 +45,7 @@ mina-dashboard-blocks-sync { height: 8px !important; } .primary { - padding-left: 8px; + padding-left: 4px; } } } diff --git a/frontend/src/app/features/dashboard/dashboard.component.ts b/frontend/src/app/features/dashboard/dashboard.component.ts index bcb8fc4605..edd17d58f3 100644 --- a/frontend/src/app/features/dashboard/dashboard.component.ts +++ b/frontend/src/app/features/dashboard/dashboard.component.ts @@ -1,17 +1,16 @@ -import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; +import { ChangeDetectionStrategy, Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; import { DashboardGetData, DashboardInit } from '@dashboard/dashboard.actions'; -import { filter, skip, tap, timer } from 'rxjs'; -import { untilDestroyed } from '@ngneat/until-destroy'; +import { filter, skip, Subscription, tap, timer } from 'rxjs'; import { AppSelectors } from '@app/app.state'; -import { selectDashboardNodesAndPeers, selectDashboardNodesAndRpcStats, selectDashboardPeersStats } from '@dashboard/dashboard.state'; +import { selectDashboardNodesAndRpcStats, selectDashboardPeersStats } from '@dashboard/dashboard.state'; import { DashboardPeersStats } from '@shared/types/dashboard/dashboard-peers-stats.type'; import { NodesOverviewNode } from '@shared/types/nodes/dashboard/nodes-overview-node.type'; import { DashboardRpcStats } from '@shared/types/dashboard/dashboard-rpc-stats.type'; import { NodesOverviewLedgerStepState } from '@shared/types/nodes/dashboard/nodes-overview-ledger.type'; -import { lastItem, ONE_MILLION, SecDurationConfig } from '@openmina/shared'; -import { DashboardPeer } from '@shared/types/dashboard/dashboard.peer'; import { NodesOverviewNodeBlockStatus } from '@shared/types/nodes/dashboard/nodes-overview-block.type'; +import { AppNodeDetails, AppNodeStatus } from '@shared/types/app/app-node-details.type'; +import { untilDestroyed } from '@ngneat/until-destroy'; @Component({ selector: 'mina-dashboard', @@ -20,7 +19,7 @@ import { NodesOverviewNodeBlockStatus } from '@shared/types/nodes/dashboard/node changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'h-100 w-100 flex-column' }, }) -export class DashboardComponent extends StoreDispatcher implements OnInit { +export class DashboardComponent extends StoreDispatcher implements OnInit, OnDestroy { updateAction: string; updateDetail: string; @@ -29,38 +28,52 @@ export class DashboardComponent extends StoreDispatcher implements OnInit { blockSyncProgress: number = 0; private connected: boolean = false; + timer: Subscription; + lastStatus: AppNodeStatus; + ngOnInit(): void { + // this.document.getElementById('mina-content').style.borderTopLeftRadius = '0'; this.updateAction = 'Connecting to peers'; this.listenToNodeChanging(); this.listenToPeersChanges(); this.getDashboardData(); this.listenToNodesChanges(); - this.listenToNodesChanges(); } private getDashboardData(): void { this.dispatch(DashboardInit); - timer(4000, 4000) - .pipe( - tap(() => this.dispatch(DashboardGetData)), - untilDestroyed(this), - ) - .subscribe(); + this.resetTimer(); } private listenToNodeChanging(): void { this.select(AppSelectors.activeNode, () => { this.dispatch(DashboardGetData, { force: true }); }, filter(Boolean), skip(1)); + this.select(AppSelectors.activeNodeDetails, (details: AppNodeDetails) => { + if (this.lastStatus !== details.status) { + this.lastStatus = details.status; + this.resetTimer(); + } + }); + } + + private resetTimer(): void { + this.timer?.unsubscribe(); + const timerInterval = this.lastStatus === AppNodeStatus.SYNCED || this.lastStatus === AppNodeStatus.OFFLINE ? 5000 : 1000; + this.timer = timer(timerInterval, timerInterval) + .pipe( + tap(() => this.dispatch(DashboardGetData)), + untilDestroyed(this), + ).subscribe(); } private listenToPeersChanges(): void { this.select(selectDashboardPeersStats, (stats: DashboardPeersStats) => { if (this.connected && stats.connected === 0 || !this.connected) { if (stats.connected > 0) { - this.updateAction = `Connected to ${stats.connected} peer${stats.connected !== 1 ? 's' : ''}`; + this.updateAction = 'Downloading Staking Epoch Ledger'; } else if (stats.connecting > 0) { this.updateAction = `Connecting to ${stats.connecting} peer${stats.connecting !== 1 ? 's' : ''}`; } else { @@ -86,8 +99,8 @@ export class DashboardComponent extends StoreDispatcher implements OnInit { let stakingProgress = rpcStats.stakingLedger?.fetched / rpcStats.stakingLedger?.estimation * 100 || 0; let nextProgress = rpcStats.nextLedger?.fetched / rpcStats.nextLedger?.estimation * 100 || 0; - let rootSnarkedProgress = rpcStats.rootLedger?.fetched / rpcStats.rootLedger?.estimation * 100 || 0; - let rootStagedProgress = ledgers.rootStaged.staged.fetchPartsEnd ? 50 : 0; + let rootSnarkedProgress = rpcStats.snarkedRootLedger?.fetched / rpcStats.snarkedRootLedger?.estimation * 100 || 0; + let rootStagedProgress = rpcStats.stagedRootLedger?.fetched / rpcStats.stagedRootLedger?.estimation * 100 || 0; if (ledgers.stakingEpoch.state === NodesOverviewLedgerStepState.SUCCESS) { stakingProgress = 100; @@ -107,6 +120,11 @@ export class DashboardComponent extends StoreDispatcher implements OnInit { } const ledgerProgressTotal = (stakingProgress + nextProgress + rootSnarkedProgress + rootStagedProgress) / 4; this.ledgerProgress = ledgerProgressTotal * 0.33; + if (ledgerProgressTotal !== 100) { + this.blockSyncProgress = 0; + this.detect(); + return; + } let blocks = nodes[0].blocks; @@ -134,4 +152,12 @@ export class DashboardComponent extends StoreDispatcher implements OnInit { this.detect(); }); } + + override ngOnDestroy(): void { + super.ngOnDestroy(); + } + + // cleanup() { + // document.getElementById('mina-content').style.borderTopLeftRadius = '6px'; + // } } diff --git a/frontend/src/app/features/dashboard/dashboard.module.ts b/frontend/src/app/features/dashboard/dashboard.module.ts index d97ee215f1..ed91563182 100644 --- a/frontend/src/app/features/dashboard/dashboard.module.ts +++ b/frontend/src/app/features/dashboard/dashboard.module.ts @@ -9,13 +9,8 @@ import { LoadingSpinnerComponent } from '@shared/loading-spinner/loading-spinner import { CopyComponent } from '@openmina/shared'; import { DashboardNetworkComponent } from './dashboard-network/dashboard-network.component'; import { DashboardLedgerComponent } from './dashboard-ledger/dashboard-ledger.component'; -import { - DashboardTransitionFrontierComponent, -} from './dashboard-transition-frontier/dashboard-transition-frontier.component'; import { DashboardBlocksSyncComponent } from './dashboard-blocks-sync/dashboard-blocks-sync.component'; -import { - DashboardPeersMinimalTableComponent, -} from './dashboard-peers-minimal-table/dashboard-peers-minimal-table.component'; +import { DashboardPeersMinimalTableComponent } from './dashboard-peers-minimal-table/dashboard-peers-minimal-table.component'; import { BlockProductionPillComponent } from '@app/layout/block-production-pill/block-production-pill.component'; @@ -24,7 +19,6 @@ import { BlockProductionPillComponent } from '@app/layout/block-production-pill/ DashboardComponent, DashboardNetworkComponent, DashboardLedgerComponent, - DashboardTransitionFrontierComponent, DashboardBlocksSyncComponent, DashboardPeersMinimalTableComponent, ], diff --git a/frontend/src/app/features/dashboard/dashboard.reducer.ts b/frontend/src/app/features/dashboard/dashboard.reducer.ts index 7ee9016523..0d9a23b56d 100644 --- a/frontend/src/app/features/dashboard/dashboard.reducer.ts +++ b/frontend/src/app/features/dashboard/dashboard.reducer.ts @@ -24,7 +24,8 @@ const initialState: DashboardState = { peerResponses: [], stakingLedger: null, nextLedger: null, - rootLedger: null, + snarkedRootLedger: null, + stagedRootLedger: null, }, nodeBootstrappingPercentage: 0, appliedBlocks: 0, diff --git a/frontend/src/app/features/dashboard/dashboard.service.ts b/frontend/src/app/features/dashboard/dashboard.service.ts index 889502a595..507f5c89c3 100644 --- a/frontend/src/app/features/dashboard/dashboard.service.ts +++ b/frontend/src/app/features/dashboard/dashboard.service.ts @@ -52,19 +52,23 @@ export class DashboardService { ); } - private mapMessageProgressResponse(response: MessageProgressResponse): DashboardRpcStats { - const peerResponses = Object.keys(response.messages_stats).map(peerId => ({ + private mapMessageProgressResponse(progress: MessageProgressResponse): DashboardRpcStats { + const peerResponses = Object.keys(progress.messages_stats).map(peerId => ({ peerId, requestsMade: Object - .keys(response.messages_stats[peerId].responses) - .reduce((sum: number, curr: string) => sum + response.messages_stats[peerId].responses[curr], 0), + .keys(progress.messages_stats[peerId].responses) + .reduce((sum: number, curr: string) => sum + progress.messages_stats[peerId].responses[curr], 0), } as DashboardPeerRpcResponses)); return { peerResponses, - stakingLedger: response.staking_ledger_sync, - nextLedger: response.next_epoch_ledger_sync, - rootLedger: response.root_ledger_sync, + stakingLedger: progress.staking_ledger_sync, + nextLedger: progress.next_epoch_ledger_sync, + snarkedRootLedger: progress.root_ledger_sync, + stagedRootLedger: { + fetched: progress.root_ledger_sync?.staged?.fetched, + estimation: progress.root_ledger_sync?.staged?.total, + }, }; } } @@ -84,7 +88,12 @@ export interface MessageProgressResponse { messages_stats: MessagesStats; staking_ledger_sync: Estimation; next_epoch_ledger_sync: Estimation; - root_ledger_sync: Estimation; + root_ledger_sync: Estimation & { + staged: { + fetched: number; + total: number; + } + }; } export interface MessagesStats { diff --git a/frontend/src/app/features/dashboard/dashboard.state.ts b/frontend/src/app/features/dashboard/dashboard.state.ts index 5e002c51e7..f84b91456c 100644 --- a/frontend/src/app/features/dashboard/dashboard.state.ts +++ b/frontend/src/app/features/dashboard/dashboard.state.ts @@ -3,7 +3,6 @@ import { createFeatureSelector, createSelector, MemoizedSelector } from '@ngrx/s import { MinaState } from '@app/app.setup'; import { DashboardPeersStats } from '@shared/types/dashboard/dashboard-peers-stats.type'; import { TableSort } from '@openmina/shared'; -import { DashboardPeersSort } from '@dashboard/dashboard.actions'; import { NodesOverviewNode } from '@shared/types/nodes/dashboard/nodes-overview-node.type'; import { DashboardRpcStats } from '@shared/types/dashboard/dashboard-rpc-stats.type'; @@ -33,5 +32,4 @@ export const selectDashboardPeersStats = select((state: DashboardState): Dashboa export const selectDashboardPeersSort = select((state: DashboardState): TableSort => state.peersSort); export const selectDashboardNodes = select((state: DashboardState): NodesOverviewNode[] => state.nodes); export const selectDashboardNodesAndPeers = select((state: DashboardState): [NodesOverviewNode[], DashboardPeer[]] => [state.nodes, state.peers]); -export const selectDashboardRpcStats = select((state: DashboardState): DashboardRpcStats => state.rpcStats); export const selectDashboardNodesAndRpcStats = select((state: DashboardState): [NodesOverviewNode[], DashboardRpcStats] => [state.nodes, state.rpcStats]); diff --git a/frontend/src/app/features/nodes/overview/nodes-overview-table/nodes-overview-table.component.scss b/frontend/src/app/features/nodes/overview/nodes-overview-table/nodes-overview-table.component.scss index 60c7f13cc0..d747628141 100644 --- a/frontend/src/app/features/nodes/overview/nodes-overview-table/nodes-overview-table.component.scss +++ b/frontend/src/app/features/nodes/overview/nodes-overview-table/nodes-overview-table.component.scss @@ -8,7 +8,7 @@ border-bottom: 1px solid $base-tertiary; &:last-child { - min-width: 600px; + min-width: 510px; } } } diff --git a/frontend/src/app/features/nodes/overview/nodes-overview-table/nodes-overview-table.component.ts b/frontend/src/app/features/nodes/overview/nodes-overview-table/nodes-overview-table.component.ts index efc1983514..d720f69351 100644 --- a/frontend/src/app/features/nodes/overview/nodes-overview-table/nodes-overview-table.component.ts +++ b/frontend/src/app/features/nodes/overview/nodes-overview-table/nodes-overview-table.component.ts @@ -37,11 +37,6 @@ export class NodesOverviewTableComponent extends MinaTableRustWrapper +
Build Details
+
+ close +
+
+
+ +
diff --git a/frontend/src/app/layout/env-build-modal/env-build-modal.component.scss b/frontend/src/app/layout/env-build-modal/env-build-modal.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frontend/src/app/layout/env-build-modal/env-build-modal.component.ts b/frontend/src/app/layout/env-build-modal/env-build-modal.component.ts new file mode 100644 index 0000000000..f88cc05b1b --- /dev/null +++ b/frontend/src/app/layout/env-build-modal/env-build-modal.component.ts @@ -0,0 +1,19 @@ +import { ChangeDetectionStrategy, Component, EventEmitter } from '@angular/core'; +import { AppEnvBuild } from '@shared/types/app/app-env-build.type'; +import { ManualDetection, MinaJsonViewerComponent } from '@openmina/shared'; + +@Component({ + selector: 'mina-env-build-modal', + standalone: true, + imports: [ + MinaJsonViewerComponent, + ], + templateUrl: './env-build-modal.component.html', + styleUrl: './env-build-modal.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + host: { class: 'flex-column w-100 bg-surface border-rad-6 border pb-12' }, +}) +export class EnvBuildModalComponent extends ManualDetection { + envBuild: AppEnvBuild; + close: EventEmitter = new EventEmitter(); +} diff --git a/frontend/src/app/layout/menu-tabs/menu-tabs.component.html b/frontend/src/app/layout/menu-tabs/menu-tabs.component.html index ec33cab6f3..cfa70df50e 100644 --- a/frontend/src/app/layout/menu-tabs/menu-tabs.component.html +++ b/frontend/src/app/layout/menu-tabs/menu-tabs.component.html @@ -7,11 +7,62 @@ } + + + + + +
+
+
+ commit + Build +
+
+ {{ envBuild ? envBuild.git.commit_hash.slice(0, 6) : '' }}.. + open_in_full +
+
+
+
+ wifi_tethering + {{ network }} +
+
+ {{ chainId?.slice(0, 6) }}.. + content_copy +
+
+
+
+ wb_sunny + Light +
+ @if (currentTheme === ThemeType.LIGHT) { + check + } +
+
+
+ dark_mode + Dark +
+ @if (currentTheme === ThemeType.DARK) { + check + } +
diff --git a/frontend/src/app/layout/menu-tabs/menu-tabs.component.scss b/frontend/src/app/layout/menu-tabs/menu-tabs.component.scss index ae543e9c51..19811948a3 100644 --- a/frontend/src/app/layout/menu-tabs/menu-tabs.component.scss +++ b/frontend/src/app/layout/menu-tabs/menu-tabs.component.scss @@ -22,3 +22,7 @@ } } } + +.theme { + font-variation-settings: 'FILL' 1, 'wght' 200 !important; +} diff --git a/frontend/src/app/layout/menu-tabs/menu-tabs.component.ts b/frontend/src/app/layout/menu-tabs/menu-tabs.component.ts index e5005f6a50..de338345d4 100644 --- a/frontend/src/app/layout/menu-tabs/menu-tabs.component.ts +++ b/frontend/src/app/layout/menu-tabs/menu-tabs.component.ts @@ -1,13 +1,26 @@ -import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; -import { getMergedRoute, HorizontalMenuComponent, MergedRoute, removeParamsFromURL } from '@openmina/shared'; +import { ChangeDetectionStrategy, Component, OnInit, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core'; +import { + getMergedRoute, + HorizontalMenuComponent, isDesktop, + MergedRoute, + OpenminaEagerSharedModule, + removeParamsFromURL, + ThemeSwitcherService, + ThemeType, +} from '@openmina/shared'; import { getAvailableFeatures } from '@shared/constants/config'; import { MENU_ITEMS, MenuItem } from '@app/layout/menu/menu.component'; -import { filter, map, tap } from 'rxjs'; +import { filter, map, merge, skip, take, tap } from 'rxjs'; import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { MinaNode } from '@shared/types/core/environment/mina-env.type'; import { AppSelectors } from '@app/app.state'; import { RouterLink } from '@angular/router'; import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; +import { AppEnvBuild } from '@shared/types/app/app-env-build.type'; +import { Overlay, OverlayRef } from '@angular/cdk/overlay'; +import { ComponentPortal, TemplatePortal } from '@angular/cdk/portal'; +import { MinaNetwork } from '@shared/types/core/mina/mina.type'; +import { EnvBuildModalComponent } from '@app/layout/env-build-modal/env-build-modal.component'; @UntilDestroy() @Component({ @@ -16,6 +29,7 @@ import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; imports: [ HorizontalMenuComponent, RouterLink, + OpenminaEagerSharedModule, ], templateUrl: './menu-tabs.component.html', styleUrl: './menu-tabs.component.scss', @@ -27,10 +41,28 @@ export class MenuTabsComponent extends StoreDispatcher implements OnInit { menuItems: MenuItem[] = this.allowedMenuItems; activeRoute: string; activeNode: MinaNode; + network?: MinaNetwork; + chainId?: string; + envBuild: AppEnvBuild; + isOpenMore: boolean; + currentTheme: ThemeType; readonly trackMenus = (_: number, item: MenuItem): string => item.name; + readonly ThemeType = ThemeType; + + @ViewChild('dropdown') private dropdown: TemplateRef; + + private overlayRef: OverlayRef; + + constructor(private overlay: Overlay, + private viewContainerRef: ViewContainerRef, + private themeService: ThemeSwitcherService) {super();} ngOnInit(): void { + this.currentTheme = this.themeService.activeTheme; this.listenToActiveNodeChange(); + this.listenToEnvBuild(); + this.listenToNetwork(); + let lastUrl: string; this.store.select(getMergedRoute) .pipe( @@ -46,6 +78,16 @@ export class MenuTabsComponent extends StoreDispatcher implements OnInit { }); } + changeTheme(type: ThemeType): void { + this.closeOverlay(); + if (type === this.currentTheme) { + return; + } + this.themeService.changeTheme(); + this.currentTheme = this.themeService.activeTheme; + } + + private listenToActiveNodeChange(): void { this.select(AppSelectors.activeNode, (node: MinaNode) => { this.activeNode = node; @@ -58,4 +100,75 @@ export class MenuTabsComponent extends StoreDispatcher implements OnInit { const features = getAvailableFeatures(this.activeNode || { features: {} } as any); return MENU_ITEMS.filter((opt: MenuItem) => features.find(f => f === opt.name.toLowerCase().split(' ').join('-'))); } + + private listenToEnvBuild(): void { + this.select(AppSelectors.envBuild, (env: AppEnvBuild) => { + this.envBuild = env; + this.detect(); + }); + } + + private listenToNetwork(): void { + this.select(AppSelectors.activeNodeDetails, ({ chainId, network }) => { + this.chainId = chainId; + this.network = network as MinaNetwork; + this.detect(); + }, filter(Boolean)); + } + + openMore(anchor: HTMLDivElement): void { + this.isOpenMore = true; + if (this.overlayRef?.hasAttached()) { + this.closeOverlay(); + return; + } + this.overlayRef = this.overlay.create({ + hasBackdrop: false, + width: window.innerWidth - 6, + positionStrategy: this.overlay.position() + .flexibleConnectedTo(anchor) + .withPositions([{ + originX: 'start', + originY: 'top', + overlayX: 'start', + overlayY: 'bottom', + offsetY: -10, + offsetX: -4, + }]), + }); + + const portal = new TemplatePortal(this.dropdown, this.viewContainerRef); + this.overlayRef.attach(portal); + } + + closeOverlay(): void { + this.overlayRef?.dispose(); + this.isOpenMore = false; + this.detect(); + } + + openEnvBuildModal(): void { + this.closeOverlay(); + this.overlayRef = this.overlay.create({ + hasBackdrop: true, + backdropClass: 'openmina-backdrop', + width: 'calc(100% - 8px)', + height: 'calc(100% - 8px)', + positionStrategy: this.overlay.position().global().centerVertically().centerHorizontally(), + }); + + const portal = new ComponentPortal(EnvBuildModalComponent); + const component = this.overlayRef.attach(portal); + component.instance.envBuild = this.envBuild; + component.instance.detect(); + + merge( + component.instance.close, + this.overlayRef.backdropClick(), + ) + .pipe(take(1)) + .subscribe(() => this.overlayRef.dispose()); + + } + } diff --git a/frontend/src/app/layout/menu/menu.component.html b/frontend/src/app/layout/menu/menu.component.html index 474dbd42f5..c4e13250c8 100644 --- a/frontend/src/app/layout/menu/menu.component.html +++ b/frontend/src/app/layout/menu/menu.component.html @@ -47,7 +47,22 @@ -
+
+
+
+ commit + Build +
+
+ {{ envBuild ? envBuild.git.commit_hash.slice(0, 6) : '' }}.. + open_in_full +
+
+
+
- {{ chainId.slice(0, 6) }}.. + {{ chainId?.slice(0, 6) }}.. content_copy
diff --git a/frontend/src/app/layout/menu/menu.component.ts b/frontend/src/app/layout/menu/menu.component.ts index c3ef94c0db..97486f0e83 100644 --- a/frontend/src/app/layout/menu/menu.component.ts +++ b/frontend/src/app/layout/menu/menu.component.ts @@ -6,7 +6,8 @@ import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'; import { AppMenu } from '@shared/types/app/app-menu.type'; import { AppActions } from '@app/app.actions'; import { - getMergedRoute, + getMergedRoute, isDesktop, + isMobile, ManualDetection, MergedRoute, removeParamsFromURL, @@ -15,9 +16,13 @@ import { TooltipPosition, } from '@openmina/shared'; import { MinaNode } from '@shared/types/core/environment/mina-env.type'; -import { filter, map, tap } from 'rxjs'; +import { filter, map, merge, take, tap } from 'rxjs'; import { CONFIG, getAvailableFeatures } from '@shared/constants/config'; import { MinaNetwork } from '@shared/types/core/mina/mina.type'; +import { AppEnvBuild } from '@shared/types/app/app-env-build.type'; +import { Overlay, OverlayRef } from '@angular/cdk/overlay'; +import { ComponentPortal } from '@angular/cdk/portal'; +import { EnvBuildModalComponent } from '@app/layout/env-build-modal/env-build-modal.component'; export interface MenuItem { name: string; @@ -57,14 +62,20 @@ export class MenuComponent extends ManualDetection implements OnInit { activeRoute: string; network?: MinaNetwork; chainId?: string; + envBuild: AppEnvBuild; - constructor(private store: Store, + private overlayRef: OverlayRef; + + constructor(private overlay: Overlay, + private store: Store, private themeService: ThemeSwitcherService) { super(); } ngOnInit(): void { this.currentTheme = this.themeService.activeTheme; this.listenToCollapsingMenu(); this.listenToActiveNodeChange(); + this.listenToEnvBuild(); + let lastUrl: string; this.store.select(getMergedRoute) .pipe( @@ -118,6 +129,18 @@ export class MenuComponent extends ManualDetection implements OnInit { }); } + private listenToEnvBuild(): void { + this.store.select(AppSelectors.envBuild) + .pipe( + filter(Boolean), + untilDestroyed(this), + ) + .subscribe((env: AppEnvBuild | undefined) => { + this.envBuild = env; + this.detect(); + }); + } + private get allowedMenuItems(): MenuItem[] { const features = getAvailableFeatures(this.activeNode || { features: {} } as any); return MENU_ITEMS.filter((opt: MenuItem) => features.find(f => f === opt.name.toLowerCase().split(' ').join('-'))); @@ -140,4 +163,28 @@ export class MenuComponent extends ManualDetection implements OnInit { collapseMenu(): void { this.store.dispatch(AppActions.changeMenuCollapsing({ isCollapsing: !this.menu.collapsed })); } + + openEnvBuildModal(): void { + this.overlayRef = this.overlay.create({ + hasBackdrop: true, + backdropClass: 'openmina-backdrop', + width: '99%', + height: '99%', + maxWidth: 600, + maxHeight: 460, + positionStrategy: this.overlay.position().global().centerVertically().centerHorizontally(), + }); + + const portal = new ComponentPortal(EnvBuildModalComponent); + const component = this.overlayRef.attach(portal); + component.instance.envBuild = this.envBuild; + component.instance.detect(); + + merge( + component.instance.close, + this.overlayRef.backdropClick(), + ) + .pipe(take(1)) + .subscribe(() => this.overlayRef.dispose()); + } } diff --git a/frontend/src/app/layout/server-status/server-status.component.html b/frontend/src/app/layout/server-status/server-status.component.html index e520b9fe26..5ebfaf93a3 100644 --- a/frontend/src/app/layout/server-status/server-status.component.html +++ b/frontend/src/app/layout/server-status/server-status.component.html @@ -7,19 +7,18 @@ (mouseenter)="openTooltipDropdown(mempoolAnchor, mempool)" (mouseleave)="detachTooltipOverlay()"> blur_circular -
{{ details.transactions }} Txs
-
{{ details.snarks }} SNARKs
+
{{ details.transactions }} Tx{{ details.transactions | plural }}
+
{{ details.snarks }} SNARK{{ details.snarks | plural }}
language -
{{ details.peers }} Peers
-
{{ details.download }} / {{ details.upload }} MBps
+
{{ details.peersConnected }} Peer{{ details.peersConnected | plural }}
-
@@ -63,53 +62,53 @@ diff --git a/frontend/src/app/shared/components/mina-card/mina-card.component.html b/frontend/src/app/shared/components/mina-card/mina-card.component.html index a83c6194b1..942df0661b 100644 --- a/frontend/src/app/shared/components/mina-card/mina-card.component.html +++ b/frontend/src/app/shared/components/mina-card/mina-card.component.html @@ -1,10 +1,11 @@
- {{ label }} + {{ label }} {{ icon }}
{{ value ?? '-' }}
-
{{ hint }}
+ [style.color]="color">{{ value ?? '-' }} +
+
{{ hint }}
diff --git a/frontend/src/app/shared/components/mina-card/mina-card.component.scss b/frontend/src/app/shared/components/mina-card/mina-card.component.scss index a5a87f8b45..1a5a5e6566 100644 --- a/frontend/src/app/shared/components/mina-card/mina-card.component.scss +++ b/frontend/src/app/shared/components/mina-card/mina-card.component.scss @@ -2,7 +2,7 @@ height: 128px; width: 128px; - @media (max-width: 700px) { + @media (max-width: 767px) { height: 140px; width: 140px; } diff --git a/frontend/src/app/shared/types/app/app-env-build.type.ts b/frontend/src/app/shared/types/app/app-env-build.type.ts new file mode 100644 index 0000000000..9e093455df --- /dev/null +++ b/frontend/src/app/shared/types/app/app-env-build.type.ts @@ -0,0 +1,28 @@ +export interface AppEnvBuild { + time: string; + git: GitEnvBuild; + cargo: CargoEnvBuild; + rustc: RustcEnvBuild; +} + +interface GitEnvBuild { + commit_time: string; + commit_hash: string; + branch: string; +} + +interface CargoEnvBuild { + features: string; + opt_level: number; + target: string; + is_debug: boolean; +} + +interface RustcEnvBuild { + channel: string; + commit_date: string; + commit_hash: string; + host: string; + version: string; + llvm_version: string; +} diff --git a/frontend/src/app/shared/types/app/app-node-details.type.ts b/frontend/src/app/shared/types/app/app-node-details.type.ts index fc6efd093e..3e333d84bf 100644 --- a/frontend/src/app/shared/types/app/app-node-details.type.ts +++ b/frontend/src/app/shared/types/app/app-node-details.type.ts @@ -6,9 +6,9 @@ export interface AppNodeDetails { blockHeight: number; blockTime: number; - peers: number; - download: number; - upload: number; + peersConnected: number; + peersDisconnected: number; + peersConnecting: number; transactions: number; snarks: number; diff --git a/frontend/src/app/shared/types/block-production/won-slots/block-production-won-slots-epoch.type.ts b/frontend/src/app/shared/types/block-production/won-slots/block-production-won-slots-epoch.type.ts index a6f285c728..432f7992ab 100644 --- a/frontend/src/app/shared/types/block-production/won-slots/block-production-won-slots-epoch.type.ts +++ b/frontend/src/app/shared/types/block-production/won-slots/block-production-won-slots-epoch.type.ts @@ -1,6 +1,11 @@ export interface BlockProductionWonSlotsEpoch { + epochNumber: number; start: number; end: number; currentGlobalSlot: number; currentTime: number; + vrfStats: { + evaluated: number; + total: number; + }; } diff --git a/frontend/src/app/shared/types/dashboard/dashboard-rpc-stats.type.ts b/frontend/src/app/shared/types/dashboard/dashboard-rpc-stats.type.ts index 407259349d..d34552507e 100644 --- a/frontend/src/app/shared/types/dashboard/dashboard-rpc-stats.type.ts +++ b/frontend/src/app/shared/types/dashboard/dashboard-rpc-stats.type.ts @@ -2,7 +2,8 @@ export interface DashboardRpcStats { peerResponses: DashboardPeerRpcResponses[]; stakingLedger: DashboardLedgerStepStats; nextLedger: DashboardLedgerStepStats; - rootLedger: DashboardLedgerStepStats; + snarkedRootLedger: DashboardLedgerStepStats; + stagedRootLedger: DashboardLedgerStepStats; } export interface DashboardPeerRpcResponses { diff --git a/frontend/src/environments/environment.ts b/frontend/src/environments/environment.ts index c4aeaa7a67..8737585db9 100644 --- a/frontend/src/environments/environment.ts +++ b/frontend/src/environments/environment.ts @@ -4,7 +4,7 @@ export const environment: Readonly = { production: false, identifier: 'Dev FE', canAddNodes: true, - // showWebNodeLandingPage: true, + showWebNodeLandingPage: false, globalConfig: { features: { dashboard: [], @@ -24,8 +24,8 @@ export const environment: Readonly = { }, configs: [ // { - // name: 'http://116.202.128.230:11010', - // url: 'http://116.202.128.230:11010', + // name: 'http://localhost:8070', + // url: 'http://127.0.0.1:8070/openmina-node', // }, // { // name: 'Producer-0', @@ -43,18 +43,18 @@ export const environment: Readonly = { name: 'staging-devnet-bp-0', url: 'https://staging-devnet-openmina-bp-0.minaprotocol.network', }, - { - name: 'staging-devnet-bp-1', - url: 'https://staging-devnet-openmina-bp-1.minaprotocol.network', - }, - { - name: 'staging-devnet-bp-2', - url: 'https://staging-devnet-openmina-bp-2.minaprotocol.network', - }, - { - name: 'staging-devnet-bp-3', - url: 'https://staging-devnet-openmina-bp-3.minaprotocol.network', - }, + // { + // name: 'staging-devnet-bp-1', + // url: 'https://staging-devnet-openmina-bp-1.minaprotocol.network', + // }, + // { + // name: 'staging-devnet-bp-2', + // url: 'https://staging-devnet-openmina-bp-2.minaprotocol.network', + // }, + // { + // name: 'staging-devnet-bp-3', + // url: 'https://staging-devnet-openmina-bp-3.minaprotocol.network', + // }, // { // name: 'Web Node 1', // isWebNode: true, diff --git a/frontend/src/index.html b/frontend/src/index.html index 528473d5e0..a1d71b7fda 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -4,7 +4,7 @@