diff --git a/frontend/package-lock.json b/frontend/package-lock.json
index 6c7b02ea18..f7e1bb1481 100644
--- a/frontend/package-lock.json
+++ b/frontend/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "frontend",
- "version": "1.0.43",
+ "version": "1.0.50",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "frontend",
- "version": "1.0.43",
+ "version": "1.0.50",
"dependencies": {
"@angular/animations": "^17.3.12",
"@angular/cdk": "^17.3.10",
@@ -38,6 +38,7 @@
"eigen": "^0.2.2",
"express": "^4.18.2",
"firebase": "^11.0.1",
+ "jszip": "^3.10.1",
"mathjs": "^12.3.0",
"mina-signer": "^3.0.7",
"ngx-json-viewer": "^3.2.1",
@@ -9443,8 +9444,7 @@
"node_modules/core-util-is": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
- "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
- "dev": true
+ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
},
"node_modules/cors": {
"version": "2.8.5",
@@ -12237,6 +12237,12 @@
"node": ">=0.10.0"
}
},
+ "node_modules/immediate": {
+ "version": "3.0.6",
+ "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
+ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
+ "license": "MIT"
+ },
"node_modules/immutable": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz",
@@ -12637,8 +12643,7 @@
"node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
- "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
- "dev": true
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
},
"node_modules/isbinaryfile": {
"version": "4.0.10",
@@ -12937,6 +12942,48 @@
"verror": "1.10.0"
}
},
+ "node_modules/jszip": {
+ "version": "3.10.1",
+ "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
+ "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
+ "license": "(MIT OR GPL-3.0-or-later)",
+ "dependencies": {
+ "lie": "~3.3.0",
+ "pako": "~1.0.2",
+ "readable-stream": "~2.3.6",
+ "setimmediate": "^1.0.5"
+ }
+ },
+ "node_modules/jszip/node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "license": "MIT",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/jszip/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "license": "MIT"
+ },
+ "node_modules/jszip/node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "license": "MIT",
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
"node_modules/karma": {
"version": "6.4.2",
"dev": true,
@@ -13232,6 +13279,15 @@
}
}
},
+ "node_modules/lie": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
+ "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "immediate": "~3.0.5"
+ }
+ },
"node_modules/limiter": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz",
@@ -14742,6 +14798,12 @@
"node": "^16.14.0 || >=18.0.0"
}
},
+ "node_modules/pako": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
+ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==",
+ "license": "(MIT AND Zlib)"
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -15217,8 +15279,7 @@
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
- "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
- "dev": true
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
"node_modules/progress": {
"version": "2.0.3",
@@ -16205,6 +16266,12 @@
"node": ">= 0.4"
}
},
+ "node_modules/setimmediate": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
+ "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==",
+ "license": "MIT"
+ },
"node_modules/setprototypeof": {
"version": "1.2.0",
"license": "ISC"
diff --git a/frontend/package.json b/frontend/package.json
index e0c8523cae..f77c5d1ec6 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -1,6 +1,6 @@
{
"name": "frontend",
- "version": "1.0.44",
+ "version": "1.0.59",
"scripts": {
"install:deps": "npm install",
"start": "npm install && ng serve --configuration local --open",
@@ -53,6 +53,7 @@
"eigen": "^0.2.2",
"express": "^4.18.2",
"firebase": "^11.0.1",
+ "jszip": "^3.10.1",
"mathjs": "^12.3.0",
"mina-signer": "^3.0.7",
"ngx-json-viewer": "^3.2.1",
diff --git a/frontend/src/app/core/helpers/file-progress.helper.ts b/frontend/src/app/core/helpers/file-progress.helper.ts
index 4347a22a71..2c5652b6b9 100644
--- a/frontend/src/app/core/helpers/file-progress.helper.ts
+++ b/frontend/src/app/core/helpers/file-progress.helper.ts
@@ -32,7 +32,7 @@ class AssetMonitor {
url: resource.toString(),
startTime,
progress: 0,
- totalSize: 27355980,
+ totalSize: 30111552,
status: 'pending',
endTime: 0,
duration: 0,
diff --git a/frontend/src/app/core/services/rust.service.ts b/frontend/src/app/core/services/rust.service.ts
index deda879343..0a2d8a64a1 100644
--- a/frontend/src/app/core/services/rust.service.ts
+++ b/frontend/src/app/core/services/rust.service.ts
@@ -18,6 +18,10 @@ export class RustService {
this.node = node;
}
+ get activeNodeIsWebNode(): boolean {
+ return this.node.isWebNode;
+ }
+
get URL(): string {
return this.node.url;
}
diff --git a/frontend/src/app/core/services/sentry.service.ts b/frontend/src/app/core/services/sentry.service.ts
new file mode 100644
index 0000000000..18a3d1db70
--- /dev/null
+++ b/frontend/src/app/core/services/sentry.service.ts
@@ -0,0 +1,70 @@
+import { inject, Injectable } from '@angular/core';
+import { NodesOverviewLedger, NodesOverviewLedgerStepState } from '@shared/types/nodes/dashboard/nodes-overview-ledger.type';
+import * as Sentry from '@sentry/angular';
+import { NodesOverviewBlock, NodesOverviewNodeBlockStatus } from '@shared/types/nodes/dashboard/nodes-overview-block.type';
+import { lastItem, ONE_BILLION } from '@openmina/shared';
+import { RustService } from '@core/services/rust.service';
+
+@Injectable({
+ providedIn: 'root',
+})
+export class SentryService {
+
+ private ledgerIsSynced: boolean = false;
+ private blockIsSynced: boolean = false;
+ private rustService: RustService = inject(RustService);
+
+ updateLedgerSyncStatus(ledger: NodesOverviewLedger): void {
+ if (this.ledgerIsSynced) {
+ return;
+ }
+ if (ledger.rootStaged.state === NodesOverviewLedgerStepState.SUCCESS) {
+ this.ledgerIsSynced = true;
+ const syncDetails = {
+ stakingLedger: {
+ fetchHashes: ledger.stakingEpoch.snarked.fetchHashesDuration + 's',
+ fetchAccounts: ledger.stakingEpoch.snarked.fetchAccountsDuration + 's',
+ },
+ nextEpochLedger: {
+ fetchHashes: ledger.nextEpoch.snarked.fetchHashesDuration + 's',
+ fetchAccounts: ledger.nextEpoch.snarked.fetchAccountsDuration + 's',
+ },
+ snarkedRootLedger: {
+ fetchHashes: ledger.rootSnarked.snarked.fetchHashesDuration + 's',
+ fetchAccounts: ledger.rootSnarked.snarked.fetchAccountsDuration + 's',
+ },
+ stagedRootLedger: {
+ fetchParts: ledger.rootStaged.staged.fetchPartsDuration + 's',
+ reconstruct: ledger.rootStaged.staged.reconstructDuration + 's',
+ },
+ };
+
+ const syncedIn = Math.round((ledger.rootStaged.staged.reconstructEnd - ledger.stakingEpoch.snarked.fetchHashesStart) / ONE_BILLION);
+
+ Sentry.captureMessage(`Ledger synced in ${syncedIn}s`, {
+ level: 'info',
+ tags: { type: 'webnode', subType: 'sync.ledger' },
+ contexts: { ledger: syncDetails },
+ });
+ }
+ }
+
+ updateBlockSyncStatus(blocks: NodesOverviewBlock[], startTime: number): void {
+ if (this.blockIsSynced || !this.rustService.activeNodeIsWebNode) {
+ return;
+ }
+
+ const blocksSynced = blocks.every(b => b.status === NodesOverviewNodeBlockStatus.APPLIED);
+ if (blocksSynced && blocks[0]) {
+ this.blockIsSynced = true;
+ blocks = blocks.slice(1);
+ const bestTipBlock = blocks[0].height;
+ const root = lastItem(blocks).height;
+ Sentry.captureMessage(`Last 290 blocks synced in ${Math.round((Date.now() - startTime) / 1000)}s`, {
+ level: 'info',
+ tags: { type: 'webnode', subType: 'sync.block' },
+ contexts: { blocks: { bestTipBlock, root } },
+ });
+ }
+ }
+}
diff --git a/frontend/src/app/features/benchmarks/wallets/benchmarks-wallets.component.html b/frontend/src/app/features/benchmarks/wallets/benchmarks-wallets.component.html
index 57ae83e92c..a7a94c8d55 100644
--- a/frontend/src/app/features/benchmarks/wallets/benchmarks-wallets.component.html
+++ b/frontend/src/app/features/benchmarks/wallets/benchmarks-wallets.component.html
@@ -1,7 +1,9 @@
-
-
+ }
+
diff --git a/frontend/src/app/features/benchmarks/wallets/benchmarks-wallets.effects.ts b/frontend/src/app/features/benchmarks/wallets/benchmarks-wallets.effects.ts
index 54f5215991..d831c35149 100644
--- a/frontend/src/app/features/benchmarks/wallets/benchmarks-wallets.effects.ts
+++ b/frontend/src/app/features/benchmarks/wallets/benchmarks-wallets.effects.ts
@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { MinaState, selectMinaState } from '@app/app.setup';
import { Actions, createEffect, ofType } from '@ngrx/effects';
import { Effect } from '@openmina/shared';
-import { EMPTY, forkJoin, map, switchMap } from 'rxjs';
+import { combineLatest, EMPTY, map, switchMap } from 'rxjs';
import { Store } from '@ngrx/store';
import {
BENCHMARKS_WALLETS_CLOSE,
@@ -11,8 +11,11 @@ import {
BENCHMARKS_WALLETS_GET_WALLETS,
BENCHMARKS_WALLETS_GET_WALLETS_SUCCESS,
BENCHMARKS_WALLETS_SEND_TX_SUCCESS,
- BENCHMARKS_WALLETS_SEND_TXS, BENCHMARKS_WALLETS_SEND_ZKAPPS, BENCHMARKS_WALLETS_SEND_ZKAPPS_SUCCESS,
- BenchmarksWalletsActions, BenchmarksWalletsClose,
+ BENCHMARKS_WALLETS_SEND_TXS,
+ BENCHMARKS_WALLETS_SEND_ZKAPPS,
+ BENCHMARKS_WALLETS_SEND_ZKAPPS_SUCCESS,
+ BenchmarksWalletsActions,
+ BenchmarksWalletsClose,
BenchmarksWalletsGetWallets,
BenchmarksWalletsSendTxs,
} from '@benchmarks/wallets/benchmarks-wallets.actions';
@@ -74,7 +77,7 @@ export class BenchmarksWalletsEffects extends MinaRustBaseEffect
this.actions$.pipe(
ofType(BENCHMARKS_WALLETS_GET_ALL_TXS),
switchMap(() =>
- forkJoin([
+ combineLatest([
this.mempoolService.getTransactionPool(),
this.benchmarksService.getAllIncludedTransactions(),
]),
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 ee26f0da36..811b8e7310 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,4 +1,4 @@
-@if (isPending || nodeIsBootstrapping || isCalculatingVRF || isLoading) {
+@if ((isPending || nodeIsBootstrapping || isCalculatingVRF || isLoading) && emptySlots) {
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 dc098d8085..15951b189e 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
@@ -38,7 +38,7 @@ export class BlockProductionWonSlotsComponent extends StoreDispatcher implements
total: number;
};
epoch: number;
- emptySlots: boolean = false;
+ emptySlots: boolean = true;
isLoading: boolean = true;
constructor(protected el: ElementRef) { super(); }
@@ -82,8 +82,9 @@ export class BlockProductionWonSlotsComponent extends StoreDispatcher implements
private listenToActiveEpoch(): void {
this.select(BlockProductionWonSlotsSelectors.epoch, (activeEpoch) => {
- this.epoch = activeEpoch.epochNumber;
+ this.epoch = activeEpoch?.epochNumber;
this.vrfStats = activeEpoch.vrfStats;
+ this.isCalculatingVRF = activeEpoch.vrfStats?.evaluated < activeEpoch.vrfStats?.total;
this.detect();
});
}
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 c7f0039640..fa83219e63 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
@@ -26,7 +26,7 @@ export class BlockProductionWonSlotsService {
}
const attemptsSlots = response.attempts.map((attempt: Attempt) => {
attempt.won_slot.slot_time = Math.floor(attempt.won_slot.slot_time / ONE_MILLION); // converted to milliseconds
- attempt.active = BlockProductionWonSlotsService.getActive(attempt);
+ attempt.active = this.getActive(attempt);
let slot = {
epoch: attempt.won_slot.epoch,
message: this.getMessage(attempt),
@@ -114,7 +114,7 @@ export class BlockProductionWonSlotsService {
);
}
- private static getActive(attempt: Attempt): boolean {
+ private getActive(attempt: Attempt): boolean {
const slotTime = attempt.won_slot.slot_time;
const now = Date.now();
return slotTime <= now && (now < 3 * 60 * 1000 + slotTime) && !attempt.times?.discarded;
diff --git a/frontend/src/app/features/dashboard/dashboard-blocks-sync/dashboard-blocks-sync.component.ts b/frontend/src/app/features/dashboard/dashboard-blocks-sync/dashboard-blocks-sync.component.ts
index 8f972c2684..465b5bdbd4 100644
--- a/frontend/src/app/features/dashboard/dashboard-blocks-sync/dashboard-blocks-sync.component.ts
+++ b/frontend/src/app/features/dashboard/dashboard-blocks-sync/dashboard-blocks-sync.component.ts
@@ -5,6 +5,7 @@ import { NodesOverviewNode } from '@shared/types/nodes/dashboard/nodes-overview-
import { NodesOverviewNodeBlockStatus } from '@shared/types/nodes/dashboard/nodes-overview-block.type';
import { isDesktop, lastItem, ONE_MILLION } from '@openmina/shared';
import { DashboardPeer } from '@shared/types/dashboard/dashboard.peer';
+import { SentryService } from '@core/services/sentry.service';
const PENDING = 'Pending';
const SYNCED = 'Synced';
@@ -30,6 +31,10 @@ export class DashboardBlocksSyncComponent extends StoreDispatcher implements OnI
isDesktop: boolean = isDesktop();
remaining: number;
+ private syncStartTime: number = Date.now();
+
+ constructor(private sentryService: SentryService) {super();}
+
ngOnInit(): void {
this.listenToNodesChanges();
}
@@ -76,6 +81,8 @@ export class DashboardBlocksSyncComponent extends StoreDispatcher implements OnI
this.extractNodesData(nodes);
this.extractPeersData(peers);
+
+ this.sentryService.updateBlockSyncStatus(nodes[0].blocks, this.syncStartTime);
}
this.detect();
});
@@ -115,8 +122,8 @@ export class DashboardBlocksSyncComponent extends StoreDispatcher implements OnI
this.fetched = blocks.filter(b => ![NodesOverviewNodeBlockStatus.MISSING, NodesOverviewNodeBlockStatus.FETCHING].includes(b.status)).length;
this.applied = blocks.filter(b => b.status === NodesOverviewNodeBlockStatus.APPLIED).length;
- this.fetchedPercentage = Math.round(this.fetched * 100 / 291) + '%';
- this.appliedPercentage = Math.round(this.applied * 100 / 291);
+ this.fetchedPercentage = Math.round(this.fetched * 100 / 290) + '%';
+ this.appliedPercentage = Math.round(this.applied * 100 / 290);
}
private calculateProgressTime(timestamp: number): string {
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 a18e20923c..b0c46c1526 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
@@ -14,6 +14,7 @@ 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';
+import { SentryService } from '@core/services/sentry.service';
type LedgerConfigMap = {
stakingEpoch: SecDurationConfig,
@@ -80,11 +81,14 @@ export class DashboardLedgerComponent extends StoreDispatcher implements OnInit,
totalProgress: number;
isWebNode: boolean;
+ private startSync: number = Date.now();
+
@ViewChild('tooltipRef') private tooltipRef: TemplateRef<{ start: number, end: number }>;
private overlayRef: OverlayRef;
constructor(private overlay: Overlay,
- private viewContainerRef: ViewContainerRef) {
+ private viewContainerRef: ViewContainerRef,
+ private sentryService: SentryService) {
super();
}
@@ -229,6 +233,8 @@ export class DashboardLedgerComponent extends StoreDispatcher implements OnInit,
this.rootStagedProgress = 100;
}
this.totalProgress = (this.stakingProgress + this.nextProgress + this.rootSnarkedProgress + this.rootStagedProgress) / 4;
+
+ this.sentryService.updateLedgerSyncStatus(this.ledgers);
}
this.detect();
});
@@ -272,9 +278,7 @@ export class DashboardLedgerComponent extends StoreDispatcher implements OnInit,
}
hide(): void {
- if (this.overlayRef?.hasAttached()) {
- this.overlayRef.detach();
- }
+ this.overlayRef?.dispose();
}
private get emptyConfig(): SecDurationConfig {
@@ -294,11 +298,6 @@ export class DashboardLedgerComponent extends StoreDispatcher implements OnInit,
};
}
- override ngOnDestroy(): void {
- super.ngOnDestroy();
- this.hide();
- }
-
private setProgressTime(): void {
if (!this.ledgers.stakingEpoch.snarked.fetchHashesStart) {
return;
@@ -327,4 +326,9 @@ export class DashboardLedgerComponent extends StoreDispatcher implements OnInit,
return `${action} <1m ago`;
}
}
+
+ override ngOnDestroy(): void {
+ super.ngOnDestroy();
+ this.hide();
+ }
}
diff --git a/frontend/src/app/features/dashboard/dashboard.component.ts b/frontend/src/app/features/dashboard/dashboard.component.ts
index edd17d58f3..1ff872cf57 100644
--- a/frontend/src/app/features/dashboard/dashboard.component.ts
+++ b/frontend/src/app/features/dashboard/dashboard.component.ts
@@ -31,7 +31,6 @@ export class DashboardComponent extends StoreDispatcher implements OnInit, OnDes
timer: Subscription;
lastStatus: AppNodeStatus;
-
ngOnInit(): void {
// this.document.getElementById('mina-content').style.borderTopLeftRadius = '0';
this.updateAction = 'Connecting to peers';
diff --git a/frontend/src/app/features/mempool/mempool.service.ts b/frontend/src/app/features/mempool/mempool.service.ts
index fba1975c0b..94f302a8fb 100644
--- a/frontend/src/app/features/mempool/mempool.service.ts
+++ b/frontend/src/app/features/mempool/mempool.service.ts
@@ -31,7 +31,7 @@ export class MempoolService {
const memo = decodeMemo(tx.data[1].payload.common.memo);
return {
kind: MempoolTransactionKind.PAYMENT,
- txHash: tx.hash.join(''),
+ txHash: tx.hash,
sender: tx.data[1].payload.common.fee_payer_pk,
fee: Number(tx.data[1].payload.common.fee),
amount: Number(tx.data[1].payload.body[1].amount) / ONE_BILLION,
@@ -46,7 +46,7 @@ export class MempoolService {
const zkMemo = decodeMemo(zkapp.memo);
return {
kind: MempoolTransactionKind.ZK_APP,
- txHash: tx.hash.join(''),
+ txHash: tx.hash,
sender: zkapp.fee_payer.body.public_key,
fee: Number(zkapp.fee_payer.body.fee),
amount: null,
@@ -64,7 +64,7 @@ export class MempoolService {
export interface MempoolTransactionResponse {
data: [MempoolTransactionResponseKind, SignedCommand | ZkappCommand];
- hash: number[];
+ hash: string;
}
export enum MempoolTransactionResponseKind {
diff --git a/frontend/src/app/layout/parse-files/parse-files.component.html b/frontend/src/app/layout/parse-files/parse-files.component.html
new file mode 100644
index 0000000000..ad62091962
--- /dev/null
+++ b/frontend/src/app/layout/parse-files/parse-files.component.html
@@ -0,0 +1,12 @@
+
parse-files works!
+
+
+
0">
+
+
File Contents:
+
{{ fileContent }}
+
+
diff --git a/frontend/src/app/layout/parse-files/parse-files.component.scss b/frontend/src/app/layout/parse-files/parse-files.component.scss
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/frontend/src/app/layout/parse-files/parse-files.component.ts b/frontend/src/app/layout/parse-files/parse-files.component.ts
new file mode 100644
index 0000000000..d8a7725887
--- /dev/null
+++ b/frontend/src/app/layout/parse-files/parse-files.component.ts
@@ -0,0 +1,112 @@
+import { ChangeDetectionStrategy, Component } from '@angular/core';
+import { NgFor, NgIf } from '@angular/common';
+import * as JSZip from 'jszip';
+
+@Component({
+ selector: 'mina-parse-files',
+ standalone: true,
+ imports: [
+ NgIf, NgFor,
+ ],
+ templateUrl: './parse-files.component.html',
+ styleUrl: './parse-files.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class ParseFilesComponent {
+
+ fileContents: string[] = [];
+
+ onFileSelected(event: any) {
+ const files: FileList = event.target.files;
+
+ // Reset previous file contents
+ this.fileContents = [];
+
+ // Loop through selected files
+ for (let i = 0; i < files.length; i++) {
+ const file = files[i];
+
+ // Ensure it's a .txt file
+ }
+ this.handleFileUpload(event);
+ }
+
+ async processZipFile(zipFile: File) {
+ try {
+ // Load the ZIP file
+ const zip = await JSZip.loadAsync(zipFile);
+
+ // Array to store file contents
+ const fileContents: { name: string, content: string }[] = [];
+
+ // Iterate through each file in the ZIP
+ await Promise.all(Object.keys(zip.files).map(async (filename) => {
+ // Skip directories
+ if (!zip.files[filename].dir) {
+ try {
+ // Read file as text
+ const content = await zip.files[filename].async('string');
+ fileContents.push({
+ name: filename,
+ content: content,
+ });
+ } catch (readError) {
+ console.error(`Error reading file ${filename}:`, readError);
+ }
+ }
+ }));
+
+ // Return or process the file contents
+ return fileContents;
+ } catch (error) {
+ console.error('Error processing ZIP file:', error);
+ return [];
+ }
+ }
+
+// Usage example
+ handleFileUpload(event: Event) {
+ const input = event.target as HTMLInputElement;
+ if (input.files && input.files.length > 0) {
+ const zipFile = input.files[0];
+ this.processZipFile(zipFile).then(files => {
+ files.forEach(file => {
+ console.log(`File: ${file.name}`);
+ console.log(`Content: ${file.content.substring(0, 200)}...`);
+ });
+ });
+ }
+ }
+
+ private readFileContent(file: File) {
+ const reader = new FileReader();
+
+ reader.onload = (e: any) => {
+ const content = e.target.result as string;
+ this.fileContents.push(content);
+
+ // Perform operations on the content here
+ this.processFileContent(content);
+ };
+
+ reader.onerror = (e) => {
+ console.error('Error reading file', e);
+ };
+
+ // Read the file as text
+ reader.readAsText(file);
+ }
+
+ private processFileContent(content: string) {
+ // Example operations
+ const lines = content.split('\n');
+ const wordCount = content.split(/\s+/).length;
+ const characterCount = content.length;
+
+ console.log('Lines:', lines);
+ console.log('Word Count:', wordCount);
+ console.log('Character Count:', characterCount);
+
+ // Add your specific file processing logic here
+ }
+}
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 5ebfaf93a3..6b727bb5c2 100644
--- a/frontend/src/app/layout/server-status/server-status.component.html
+++ b/frontend/src/app/layout/server-status/server-status.component.html
@@ -2,7 +2,7 @@
*ngIf="details"
[ngClass]="switchForbidden ? AppNodeStatus.PENDING : details.status">
-
@@ -10,7 +10,7 @@
{{ details.transactions }} Tx{{ details.transactions | plural }}
{{ details.snarks }} SNARK{{ details.snarks | plural }}
-
@@ -18,7 +18,7 @@
{{ details.peersConnected }} Peer{{ details.peersConnected | plural }}
-
diff --git a/frontend/src/app/layout/server-status/server-status.component.scss b/frontend/src/app/layout/server-status/server-status.component.scss
index ce0fe36beb..a3de40e2d0 100644
--- a/frontend/src/app/layout/server-status/server-status.component.scss
+++ b/frontend/src/app/layout/server-status/server-status.component.scss
@@ -100,7 +100,6 @@
.chip {
gap: 4px;
- margin-left: 4px;
}
@media (max-width: 767px) {
diff --git a/frontend/src/app/layout/web-node-landing-page/web-node-landing-page.component.html b/frontend/src/app/layout/web-node-landing-page/web-node-landing-page.component.html
index 6263563da9..4214b1508e 100644
--- a/frontend/src/app/layout/web-node-landing-page/web-node-landing-page.component.html
+++ b/frontend/src/app/layout/web-node-landing-page/web-node-landing-page.component.html
@@ -30,6 +30,7 @@
The private key is pre-set, and the stake is delegated to it.
+
diff --git a/frontend/src/app/layout/web-node-landing-page/web-node-landing-page.component.ts b/frontend/src/app/layout/web-node-landing-page/web-node-landing-page.component.ts
index f8cd2143ec..0c56241e53 100644
--- a/frontend/src/app/layout/web-node-landing-page/web-node-landing-page.component.ts
+++ b/frontend/src/app/layout/web-node-landing-page/web-node-landing-page.component.ts
@@ -3,12 +3,14 @@ import { NgOptimizedImage } from '@angular/common';
import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class';
import { AppSelectors } from '@app/app.state';
import { filter } from 'rxjs';
+import { ParseFilesComponent } from '@app/layout/parse-files/parse-files.component';
@Component({
selector: 'mina-web-node-landing-page',
standalone: true,
imports: [
NgOptimizedImage,
+ ParseFilesComponent,
],
templateUrl: './web-node-landing-page.component.html',
styleUrl: './web-node-landing-page.component.scss',
diff --git a/frontend/src/app/shared/helpers/date.helper.ts b/frontend/src/app/shared/helpers/date.helper.ts
index e481923184..84289d0a8a 100644
--- a/frontend/src/app/shared/helpers/date.helper.ts
+++ b/frontend/src/app/shared/helpers/date.helper.ts
@@ -39,11 +39,23 @@ export function getTimeDiff(time: number, config?: { withSecs?: boolean, only1un
if (config?.only1unit) {
if (days > 0) {
- timeAgo += `${days}d `;
+ if (hours >= 12) {
+ timeAgo += `<${days + 1}d `;
+ } else {
+ timeAgo += `~${days}d `;
+ }
} else if (hours > 0) {
- timeAgo += `${hours}h `;
+ if (minutes >= 30) {
+ timeAgo += `<${hours + 1}h `;
+ } else {
+ timeAgo += `~${hours}h `;
+ }
} else if (minutes > 0) {
- timeAgo += `${minutes}m `;
+ if (seconds >= 30) {
+ timeAgo += `<${minutes + 1}m `;
+ } else {
+ timeAgo += `~${minutes}m `;
+ }
} else {
if (config?.withSecs) {
timeAgo += `${seconds}s `;
@@ -53,6 +65,7 @@ export function getTimeDiff(time: number, config?: { withSecs?: boolean, only1un
}
return { diff: timeAgo.trim(), inFuture };
}
+
if (days > 0) {
timeAgo += `${days}d `;
}
diff --git a/frontend/src/environments/environment.ts b/frontend/src/environments/environment.ts
index 8737585db9..a4fcf27a89 100644
--- a/frontend/src/environments/environment.ts
+++ b/frontend/src/environments/environment.ts
@@ -39,10 +39,10 @@ export const environment: Readonly = {
// name: 'Producer-2',
// url: 'https://staging-devnet-openmina-bp-2-dashboard.minaprotocol.network',
// },
- {
- name: 'staging-devnet-bp-0',
- url: 'https://staging-devnet-openmina-bp-0.minaprotocol.network',
- },
+ // {
+ // 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',
@@ -55,18 +55,18 @@ export const environment: Readonly = {
// name: 'staging-devnet-bp-3',
// url: 'https://staging-devnet-openmina-bp-3.minaprotocol.network',
// },
- // {
- // name: 'Web Node 1',
- // isWebNode: true,
- // },
+ {
+ name: 'Web Node 1',
+ isWebNode: true,
+ },
// {
// name: 'http://65.109.105.40:3000',
// url: 'http://65.109.105.40:3000',
// },
- {
- name: 'Local rust node',
- url: 'http://127.0.0.1:3000',
- },
+ // {
+ // name: 'Local rust node',
+ // url: 'http://127.0.0.1:3000',
+ // },
// {
// name: 'feat/frontend-api-peers',
// url: 'http://176.9.147.28:3000',
diff --git a/frontend/src/index.html b/frontend/src/index.html
index a1d71b7fda..2cbd41a14a 100644
--- a/frontend/src/index.html
+++ b/frontend/src/index.html
@@ -49,11 +49,11 @@