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 @@
-
Mempools
+ class="bg-surface-top secondary border-rad-8 popup-box-shadow-weak p-8 pb-5 w-100 h-100">
+
Mempool
Transactions
-
{{ details.transactions }}
+
{{ details.transactions }}
SNARKs
-
{{ details.snarks }}
+
{{ details.snarks }}
+ class="bg-surface-top secondary border-rad-8 popup-box-shadow-weak p-8 pb-5 w-100 h-100">
Network Traffic
Connected Peers
-
{{ details.peers }}
+
{{ details.peersConnected }}
-
Download
-
{{ details.download }} MB/s
+
Connecting Peers
+
{{ details.peersConnecting }}
-
Upload
-
{{ details.upload }} MB/s
+
Disconnected Peers
+
{{ details.peersDisconnected }}
+ class="node-status-popup secondary bg-surface-top border-rad-8 popup-box-shadow-weak p-8 pb-5 w-100 h-100 text-nowrap">
Node Status
MINA short history
-
{{ details.status }}
+
{{ details.status }}
Latest applied block
-
{{ details.blockHeight }}
+
{{ details.blockHeight }}
Last updated
-
{{ blockTimeAgo ? blockTimeAgo + ' ago' : '' }}
+
{{ blockTimeAgo ? blockTimeAgo + ' ago' : '' }}
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 @@