diff --git a/README.md b/README.md index 6972671913..3635ad7220 100644 --- a/README.md +++ b/README.md @@ -191,10 +191,10 @@ nvm install 20.11.1 Download [Node.js v20.11.1](https://nodejs.org/) from the official website, open the installer and follow the prompts to complete the installation. -### 2. Angular CLI v16.2.0 +### 2. Angular CLI v17.3.0 ```bash -npm install -g @angular/cli@16.2.0 +npm install -g @angular/cli@17.3.0 ``` ### 3. Installation diff --git a/frontend/firebase.json b/frontend/firebase.json index 7c5ecc4125..314966c493 100644 --- a/frontend/firebase.json +++ b/frontend/firebase.json @@ -34,6 +34,24 @@ "value": "require-corp" } ] + }, + { + "source": "**/*.@(js|wasm|html)", + "headers": [ + { + "key": "Cache-Control", + "value": "no-cache, no-store, must-revalidate" + } + ] + }, + { + "source": "/", + "headers": [ + { + "key": "Cache-Control", + "value": "no-cache, no-store, must-revalidate" + } + ] } ] } diff --git a/frontend/package.json b/frontend/package.json index 549ac8d225..494a0fab4f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "frontend", - "version": "0.8.20", + "version": "1.0.16", "scripts": { "install:deps": "npm install", "start": "npm install && ng serve --configuration local --open", @@ -12,14 +12,14 @@ "tests:headless": "npx cypress run --headless --config baseUrl=http://localhost:4200", "docker": "npm run build:prod && docker buildx build --platform linux/amd64 -t openmina/frontend:latest . && docker push openmina/frontend:latest", "start:bundle": "npx http-server dist/frontend -p 4200", - "prebuild": "node scripts/update-webnode-version.js", + "prebuild": "node scripts/update-frontend-version.js", "dev:ssr": "ng run frontend:serve-ssr", "serve:ssr": "node dist/frontend/server/main.js", "build:ssr": "ng build && ng run frontend:server", "prerender": "ng run frontend:prerender", "sentry:sourcemaps": "sentry-cli sourcemaps inject --org openmina-uv --project openmina ./dist/frontend/browser && sentry-cli sourcemaps upload --org openmina-uv --project openmina ./dist/frontend/browser", "copy-env": "cp dist/frontend/browser/assets/environments/webnode.js dist/frontend/browser/assets/environments/env.js", - "deploy": "npm run build:prod && npm run copy-env && firebase deploy" + "deploy": "npm run prebuild && npm run build:prod && npm run copy-env && firebase deploy" }, "private": true, "dependencies": { @@ -86,4 +86,4 @@ "webpack": "^5.88.2", "webpack-bundle-analyzer": "^4.9.0" } -} +} \ No newline at end of file diff --git a/frontend/scripts/download-webnode.sh b/frontend/scripts/download-webnode.sh index 791aa0103b..619cc97347 100644 --- a/frontend/scripts/download-webnode.sh +++ b/frontend/scripts/download-webnode.sh @@ -36,7 +36,7 @@ download_circuit_files() { "wrap-wrap-proving-key-transaction-snark-b9a01295c8cc9bda6d12142a581cd305_internal_vars.bin" "wrap-wrap-proving-key-transaction-snark-b9a01295c8cc9bda6d12142a581cd305_rows_rev.bin" ) - DOWNLOAD_DIR="/Users/teofil/teo/mina/openmina/frontend/dist/frontend/browser/assets/webnode/circuit-blobs/$CIRCUITS_VERSION" + DOWNLOAD_DIR="../src/assets/webnode/circuit-blobs/$CIRCUITS_VERSION" mkdir -p "$DOWNLOAD_DIR" diff --git a/frontend/scripts/update-frontend-version.js b/frontend/scripts/update-frontend-version.js new file mode 100644 index 0000000000..a186a2cc50 --- /dev/null +++ b/frontend/scripts/update-frontend-version.js @@ -0,0 +1,56 @@ +import {readFileSync, writeFileSync} from 'fs'; +import {platform, release} from 'os'; + +console.log('⏳ Updating Deployment Info...'); + +const packageJsonPath = './package.json'; +const packageJson = readFileSync(packageJsonPath, 'utf8'); +const packageJsonObj = JSON.parse(packageJson); +const version = packageJsonObj.version.split('.'); +const newVersion = `${version[0]}.${version[1]}.${parseInt(version[2]) + 1}`; +packageJsonObj.version = newVersion; +writeFileSync(packageJsonPath, JSON.stringify(packageJsonObj, null, 2)); + +const publicIp = await getPublicIpAddress(); +const deploymentInfo = { + dateUTC: new Date().toISOString(), + deviceIp: publicIp, + deviceOS: formatOS(platform()), + deviceOSVersion: release(), + version: newVersion, +}; + +const newDeploymentScript = ` +const deployment = { + dateUTC: '${deploymentInfo.dateUTC}', + deviceIp: '${deploymentInfo.deviceIp}', + deviceOS: '${deploymentInfo.deviceOS}', + deviceOSVersion: '${deploymentInfo.deviceOSVersion}', + version: '${deploymentInfo.version}', +}; +window.deployment = deployment; +`; +const deploymentScriptPattern = /const deployment = \{[\s\S]*?\};[\s\S]*?window\.deployment = deployment;/; + +// Read and update index.html +const indexPath = './src/index.html'; +let indexHtml = readFileSync(indexPath, 'utf8'); +indexHtml = indexHtml.replace(deploymentScriptPattern, newDeploymentScript.trim()); + +writeFileSync(indexPath, indexHtml); + +console.log('✅ Updated Deployment Info!'); + +async function getPublicIpAddress() { + const response = await fetch('https://api.ipify.org/'); + return await response.text(); +} + +function formatOS(platform) { + const osMap = { + 'darwin': 'macOS', + 'win32': 'Windows', + 'linux': 'Linux', + }; + return osMap[platform] || platform; +} diff --git a/frontend/scripts/update-webnode-version.js b/frontend/scripts/update-webnode-version.js deleted file mode 100644 index 8e18ab29dc..0000000000 --- a/frontend/scripts/update-webnode-version.js +++ /dev/null @@ -1,21 +0,0 @@ -const fs = require('fs'); -const crypto = require('crypto'); - -// Generate a random hash -const hash = crypto.randomBytes(16).toString('hex'); // Generates a 32-character random hex string - -// Read and update index.html -const indexPath = './src/index.html'; -let indexHtml = fs.readFileSync(indexPath, 'utf8'); - -// Enhanced Regex Pattern -// Match 'const WEBNODE_VERSION = ' with optional whitespace around the equals and between quotes -const versionRegex = /const\s+WEBNODE_VERSION\s*=\s*['"][^'"]*['"];/; - -// Perform replacement -indexHtml = indexHtml.replace(versionRegex, `const WEBNODE_VERSION = '${hash}';`); - -// Write updated content to index.html -fs.writeFileSync(indexPath, indexHtml); - -console.log(`Updated WEBNODE_VERSION in ${indexPath} to ${hash}`); diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index 8fd2baaa6e..4651b06105 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; -import { any, getMergedRoute, getWindow, MAX_WIDTH_700, MergedRoute, safelyExecuteInBrowser } from '@openmina/shared'; +import { any, getMergedRoute, getWindow, isBrowser, MAX_WIDTH_700, MergedRoute, safelyExecuteInBrowser } from '@openmina/shared'; import { AppMenu } from '@shared/types/app/app-menu.type'; import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; import { AppSelectors } from '@app/app.state'; @@ -38,14 +38,16 @@ export class AppComponent extends StoreDispatcher implements OnInit { any(window).config = CONFIG; any(window).store = this.store; } - }) + }); } ngOnInit(): void { - const args = new URLSearchParams(window.location.search).get("a"); - if (!!args) { - window.localStorage.setItem("webnodeArgs", args); - } + if (isBrowser()) { + const args = new URLSearchParams(window.location.search).get('a'); + if (!!args) { + localStorage.setItem('webnodeArgs', args); + } + } this.select( getMergedRoute, diff --git a/frontend/src/app/app.effects.ts b/frontend/src/app/app.effects.ts index 4c4539d715..e5b86fc7e3 100644 --- a/frontend/src/app/app.effects.ts +++ b/frontend/src/app/app.effects.ts @@ -101,6 +101,9 @@ export class AppEffects extends BaseEffect { upload: 0, transactions: 0, snarks: 0, + producingBlockAt: null, + producingBlockGlobalSlot: null, + producingBlockStatus: null, }, })), )); diff --git a/frontend/src/app/app.module.ts b/frontend/src/app/app.module.ts index 127d67b90b..0f4bb15523 100644 --- a/frontend/src/app/app.module.ts +++ b/frontend/src/app/app.module.ts @@ -39,6 +39,7 @@ import { Router } from '@angular/router'; import { initializeApp, provideFirebaseApp } from '@angular/fire/app'; import { getAnalytics, provideAnalytics, ScreenTrackingService } from '@angular/fire/analytics'; import { getPerformance, providePerformance } from '@angular/fire/performance'; +import { BlockProductionPillComponent } from '@app/layout/block-production-pill/block-production-pill.component'; registerLocaleData(localeFr, 'fr'); registerLocaleData(localeEn, 'en'); @@ -159,6 +160,7 @@ export class AppGlobalErrorhandler implements ErrorHandler { ReactiveFormsModule, CopyComponent, WebNodeLandingPageComponent, + BlockProductionPillComponent, ], providers: [ THEME_PROVIDER, diff --git a/frontend/src/app/app.reducer.ts b/frontend/src/app/app.reducer.ts index 8621f3b53a..93014aecf2 100644 --- a/frontend/src/app/app.reducer.ts +++ b/frontend/src/app/app.reducer.ts @@ -22,6 +22,9 @@ const initialState: AppState = { upload: 0, transactions: 0, snarks: 0, + producingBlockAt: null, + producingBlockGlobalSlot: null, + producingBlockStatus: null, }, }; diff --git a/frontend/src/app/app.service.ts b/frontend/src/app/app.service.ts index 829e0753e9..dd8bc34df1 100644 --- a/frontend/src/app/app.service.ts +++ b/frontend/src/app/app.service.ts @@ -5,7 +5,8 @@ import { CONFIG } from '@shared/constants/config'; import { RustService } from '@core/services/rust.service'; import { AppNodeDetails, AppNodeStatus } from '@shared/types/app/app-node-details.type'; import { getNetwork } from '@shared/helpers/mina.helper'; -import { getLocalStorage } from '@openmina/shared'; +import { getLocalStorage, ONE_MILLION } from '@openmina/shared'; +import { BlockProductionWonSlotsStatus } from '@shared/types/block-production/won-slots/block-production-won-slots-slot.type'; @Injectable({ providedIn: 'root', @@ -31,7 +32,7 @@ export class AppService { getActiveNodeDetails(): Observable { return this.rust.get('/status') .pipe( - map((data: NodeDetailsResponse) => ({ + map((data: NodeDetailsResponse): AppNodeDetails => ({ status: this.getStatus(data), blockHeight: data.transition_frontier.best_tip?.height, blockTime: data.transition_frontier.sync.time, @@ -42,6 +43,9 @@ export class AppService { transactions: data.transaction_pool.transactions, chainId: data.chain_id, network: getNetwork(data.chain_id), + producingBlockAt: data.current_block_production_attempt?.won_slot.slot_time / ONE_MILLION, + producingBlockGlobalSlot: data.current_block_production_attempt?.won_slot.global_slot, + producingBlockStatus: data.current_block_production_attempt?.status, } as AppNodeDetails)), ); } @@ -66,8 +70,38 @@ export interface NodeDetailsResponse { peers: Peer[]; snark_pool: SnarkPool; chain_id: string | undefined; + current_block_production_attempt: BlockProductionAttempt; } +export interface BlockProductionAttempt { + won_slot: WonSlot; + block: any; + times: Times; + status: BlockProductionWonSlotsStatus; +} + +export interface WonSlot { + slot_time: number; + global_slot: number; + epoch: number; + delegator: [string, number]; + value_with_threshold: number[]; +} + +export interface Times { + scheduled: number; + staged_ledger_diff_create_start: any; + staged_ledger_diff_create_end: any; + produced: any; + proof_create_start: any; + proof_create_end: any; + block_apply_start: any; + block_apply_end: any; + committed: any; + discarded: any; +} + + interface TransitionFrontier { best_tip: BestTip; sync: Sync; diff --git a/frontend/src/app/core/services/web-node.service.ts b/frontend/src/app/core/services/web-node.service.ts index c072096286..5deec9a9a7 100644 --- a/frontend/src/app/core/services/web-node.service.ts +++ b/frontend/src/app/core/services/web-node.service.ts @@ -47,29 +47,29 @@ export class WebNodeService { loadWasm$(): Observable { this.webNodeStartTime = Date.now(); - const args = (() => { - const raw = window.localStorage.getItem("webnodeArgs"); - if (raw == null) { - return null; - } - return JSON.parse(atob(raw)); - })(); if (isBrowser()) { + const args = (() => { + const raw = localStorage.getItem('webnodeArgs'); + if (raw === null) { + return null; + } + return JSON.parse(atob(raw)); + })(); return merge( of(any(window).webnode).pipe(filter(Boolean)), fromEvent(window, 'webNodeLoaded'), ).pipe( switchMap(() => { - const DEFAULT_NETWORK = "devnet"; + const DEFAULT_NETWORK = 'devnet'; if (!args) { return this.http.get<{ publicKey: string, privateKey: string }>('assets/webnode/web-node-secrets.json') - .pipe(map(blockProducer => ({blockProducer, network: DEFAULT_NETWORK}))); + .pipe(map(blockProducer => ({ blockProducer, network: DEFAULT_NETWORK }))); } - const data = { network: args["network"] || DEFAULT_NETWORK, blockProducer: {} as any }; - if (!!args["block_producer"]) { - data["blockProducer"] = { - privateKey: args["block_producer"].sec_key, - publicKey: args["block_producer"].pub_key, + const data = { network: args['network'] || DEFAULT_NETWORK, blockProducer: {} as any }; + if (!!args['block_producer']) { + data['blockProducer'] = { + privateKey: args['block_producer'].sec_key, + publicKey: args['block_producer'].pub_key, }; } return of(data); @@ -87,36 +87,36 @@ export class WebNodeService { startWasm$(): Observable { 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) => { - this.webnodeProgress$.next('Loaded'); - const urls = (() => { - if (typeof this.webNodeNetwork == 'number') { - const url = `${window.location.origin}/clusters/${this.webNodeNetwork}/`; - return { - seeds: url + 'seeds', - genesisConfig: url + 'genesis/config' + .pipe( + switchMap((wasm: any) => from(wasm.default(undefined, new WebAssembly.Memory(this.memory))).pipe(map(() => wasm))), + switchMap((wasm) => { + this.webnodeProgress$.next('Loaded'); + const urls = (() => { + if (typeof this.webNodeNetwork === 'number') { + const url = `${window.location.origin}/clusters/${this.webNodeNetwork}/`; + return { + seeds: url + 'seeds', + genesisConfig: url + 'genesis/config', + }; + } else { + return {}; } - } else { - return {}; - } - })(); - 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; - this.webnode$.next(webnode); - this.webnodeProgress$.next('Started'); - }), - catchError((error) => { - sendSentryEvent('WebNode failed to start: ' + error.message); - return throwError(() => new Error(error.message)); - }), - switchMap(() => this.webnode$.asObservable()), - filter(Boolean), - ); + })(); + 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; + this.webnode$.next(webnode); + this.webnodeProgress$.next('Started'); + }), + catchError((error) => { + sendSentryEvent('WebNode failed to start: ' + error.message); + return throwError(() => new Error(error.message)); + }), + switchMap(() => this.webnode$.asObservable()), + filter(Boolean), + ); } return EMPTY; } 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 ab027cf104..4cf0d04495 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 @@ -57,7 +57,11 @@ export class BlockProductionWonSlotsEffects extends BaseEffect { ?? null; } const routes: string[] = [Routes.BLOCK_PRODUCTION, Routes.WON_SLOTS]; - if (newActiveSlot && isDesktop() || (activeSlotRoute && !bpState.activeSlot) || (activeSlotRoute && bpState.openSidePanel)) { + if ( + newActiveSlot && isDesktop() + || (activeSlotRoute && !bpState.activeSlot) + || (activeSlotRoute && bpState.openSidePanel) + ) { routes.push(newActiveSlot.globalSlot.toString()); } return fromPromise(this.router.navigate(routes, { queryParamsHandling: 'merge' })).pipe(map(() => ({ 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 fb6e7bfbab..3fd23cc33b 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 @@ -14,7 +14,7 @@ import { } from '@shared/types/block-production/won-slots/block-production-won-slots-epoch.type'; @Injectable({ - providedIn: BlockProductionModule, + providedIn: 'root', }) export class BlockProductionWonSlotsService { diff --git a/frontend/src/app/features/webnode/web-node-initialization/web-node-initialization.component.ts b/frontend/src/app/features/webnode/web-node-initialization/web-node-initialization.component.ts index be3bf550ba..1c526a6950 100644 --- a/frontend/src/app/features/webnode/web-node-initialization/web-node-initialization.component.ts +++ b/frontend/src/app/features/webnode/web-node-initialization/web-node-initialization.component.ts @@ -80,6 +80,14 @@ export class WebNodeInitializationComponent extends StoreDispatcher implements O window.dispatchEvent(new CustomEvent('startWebNode')); }); this.stepsPercentages = this.getStepPercentages(); + if (this.webNodeService.isWebNodeLoaded()) { + this.loading.forEach((step: WebNodeLoadingStep) => { + step.loaded = true; + step.status = WebNodeStepStatus.DONE; + }); + this.ready = true; + return; + } this.fetchProgress(); this.listenToErrorIssuing(); this.checkWebNodeProgress(); @@ -197,6 +205,7 @@ export class WebNodeInitializationComponent extends StoreDispatcher implements O clearInterval(interval); } }, 30); + setTimeout(() => this.detect(), 500); } private fetchPeersInformation(): void { @@ -326,6 +335,7 @@ export class WebNodeInitializationComponent extends StoreDispatcher implements O this.ready = true; newProgress = 100; this.detect(); + setTimeout(() => this.detect(), 500); } this.progress = newProgress; this.arc.endAngle(Math.PI * 2 * (newProgress / 100)); diff --git a/frontend/src/app/layout/block-production-pill/block-production-pill.component.html b/frontend/src/app/layout/block-production-pill/block-production-pill.component.html new file mode 100644 index 0000000000..6073d1e688 --- /dev/null +++ b/frontend/src/app/layout/block-production-pill/block-production-pill.component.html @@ -0,0 +1,12 @@ +
+
+ schedule +
Block Production 
+
+ @if (text) { + {{ text }} + } @else { + {{ producingIn ? ('in ' + producingIn) : 'in ...' }} + }
+
+
diff --git a/frontend/src/app/layout/block-production-pill/block-production-pill.component.scss b/frontend/src/app/layout/block-production-pill/block-production-pill.component.scss new file mode 100644 index 0000000000..ae1d52e03f --- /dev/null +++ b/frontend/src/app/layout/block-production-pill/block-production-pill.component.scss @@ -0,0 +1,58 @@ +@import 'openmina'; + +$blue: #57d7ff; +$pink: #fda2ff; +$orange: #ff833d; + +:host { + min-width: 170px; + background-image: linear-gradient(25deg, rgba($blue, 0.8), rgba($pink, 0.8), rgba($orange, 0.8)); + padding: 1px; + $h-sm: 24px; + height: calc($h-sm - 2px); + @media (max-width: 768px) { + $h-sm: 32px; + height: calc($h-sm - 2px); + } + + &.hidden { + min-width: 0; + max-width: 0; + padding: 0; + + > * { + display: none; + } + } +} + +.pill { + background-color: $base-background; + height: 100%; + + .pill-inside { + padding-left: 3px; + background: linear-gradient(25deg, rgba($blue, 0.2), rgba($pink, 0.2), rgba($orange, 0.2)); + + .mina-icon, + .bp, + .time { + background: linear-gradient(12deg, $blue, $pink, $orange); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + } + + &:hover { + .mina-icon, + .bp, + .time { + background: linear-gradient(100deg, $blue, $pink, $orange); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + } + } + } + +} diff --git a/frontend/src/app/layout/block-production-pill/block-production-pill.component.ts b/frontend/src/app/layout/block-production-pill/block-production-pill.component.ts new file mode 100644 index 0000000000..2e460cbf11 --- /dev/null +++ b/frontend/src/app/layout/block-production-pill/block-production-pill.component.ts @@ -0,0 +1,74 @@ +import { ChangeDetectionStrategy, Component, HostBinding, OnDestroy, OnInit } from '@angular/core'; +import { AppSelectors } from '@app/app.state'; +import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class'; +import { AppNodeDetails } from '@shared/types/app/app-node-details.type'; +import { getTimeDiff } from '@shared/helpers/date.helper'; +import { Router } from '@angular/router'; +import { Routes } from '@shared/enums/routes.enum'; +import { BlockProductionWonSlotsStatus } from '@shared/types/block-production/won-slots/block-production-won-slots-slot.type'; +import { filter } from 'rxjs'; +import { isFeatureEnabled, isSubFeatureEnabled } from '@shared/constants/config'; + +@Component({ + selector: 'mina-block-production-pill', + standalone: true, + imports: [], + templateUrl: './block-production-pill.component.html', + styleUrl: './block-production-pill.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, + host: { class: 'mr-8 border-rad-6' }, +}) +export class BlockProductionPillComponent extends StoreDispatcher implements OnInit, OnDestroy { + text: string = null; + producingIn: string = null; + + private globalSlot: number = null; + private interval: any; + private producingValue: number = null; + + @HostBinding('class.hidden') private hideComponent = false; + + constructor(private router: Router) { super(); } + + ngOnInit(): void { + this.listenToActiveNode(); + } + + private listenToActiveNode(): void { + this.select(AppSelectors.activeNode, (node) => { + this.hideComponent = !isSubFeatureEnabled(node, 'block-production', 'won-slots'); + }); + this.select(AppSelectors.activeNodeDetails, (details: AppNodeDetails) => { + if (details.producingBlockStatus === BlockProductionWonSlotsStatus.Committed) { + this.text = 'active'; + } else if (details.producingBlockStatus === BlockProductionWonSlotsStatus.Produced) { + this.text = 'done'; + } else { + this.text = null; + } + this.globalSlot = details.producingBlockGlobalSlot; + this.producingValue = details.producingBlockAt; + this.producingIn = getTimeDiff(this.producingValue, { only1unit: true }).diff; + this.detect(); + }, filter((details: AppNodeDetails) => !!details)); + } + + private clearInterval(): void { + if (this.interval) { + clearInterval(this.interval); + this.interval = null; + } + } + + goToWonSlots(): void { + if (!this.globalSlot) { + return; + } + this.router.navigate([Routes.BLOCK_PRODUCTION, Routes.WON_SLOTS, this.globalSlot], { queryParamsHandling: 'merge' }); + } + + override ngOnDestroy(): void { + super.ngOnDestroy(); + this.clearInterval(); + } +} 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 a9b01e4f38..f5baafde0b 100644 --- a/frontend/src/app/layout/server-status/server-status.component.html +++ b/frontend/src/app/layout/server-status/server-status.component.html @@ -23,7 +23,7 @@ } -
diff --git a/frontend/src/app/layout/toolbar/toolbar.component.html b/frontend/src/app/layout/toolbar/toolbar.component.html index 9d2980871f..16072ce2b9 100644 --- a/frontend/src/app/layout/toolbar/toolbar.component.html +++ b/frontend/src/app/layout/toolbar/toolbar.component.html @@ -10,7 +10,8 @@
- + +
diff --git a/frontend/src/app/shared/helpers/date.helper.ts b/frontend/src/app/shared/helpers/date.helper.ts index cf11804011..e481923184 100644 --- a/frontend/src/app/shared/helpers/date.helper.ts +++ b/frontend/src/app/shared/helpers/date.helper.ts @@ -6,7 +6,7 @@ import { ONE_THOUSAND } from '@openmina/shared'; * @param time * @param config - withSecs: boolean, fromTime: number */ -export function getTimeDiff(time: number, config?: { withSecs: boolean, fromTime?: number }): { +export function getTimeDiff(time: number, config?: { withSecs?: boolean, only1unit?: boolean, fromTime?: number }): { diff: string, inFuture: boolean } { @@ -36,6 +36,23 @@ export function getTimeDiff(time: number, config?: { withSecs: boolean, fromTime const seconds = Math.floor(timeDifference / 1000); let timeAgo = ''; + + if (config?.only1unit) { + if (days > 0) { + timeAgo += `${days}d `; + } else if (hours > 0) { + timeAgo += `${hours}h `; + } else if (minutes > 0) { + timeAgo += `${minutes}m `; + } else { + if (config?.withSecs) { + timeAgo += `${seconds}s `; + } else { + timeAgo = '<1m '; + } + } + return { diff: timeAgo.trim(), inFuture }; + } if (days > 0) { timeAgo += `${days}d `; } @@ -57,3 +74,17 @@ export function getTimeDiff(time: number, config?: { withSecs: boolean, fromTime return { diff: timeAgo.trim(), inFuture }; } + +export function getSecondsDiff(time: number, config?: { fromTime?: number }): number { + if (!time) { + return 0; + } + if (time.toString().length === 10) { + time *= ONE_THOUSAND; + } + + const paramTime = new Date(time).getTime(); + const currentTime = config?.fromTime ? new Date(config.fromTime).getTime() : Date.now(); + + return Math.floor((paramTime - currentTime) / 1000); +} 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 eeb87cab61..fc6efd093e 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 @@ -1,4 +1,5 @@ import { MinaNetwork } from '@shared/types/core/mina/mina.type'; +import { BlockProductionWonSlotsStatus } from '@shared/types/block-production/won-slots/block-production-won-slots-slot.type'; export interface AppNodeDetails { status: AppNodeStatus; @@ -12,6 +13,10 @@ export interface AppNodeDetails { transactions: number; snarks: number; + producingBlockAt: number; + producingBlockGlobalSlot: number; + producingBlockStatus: BlockProductionWonSlotsStatus; + chainId?: string; network?: MinaNetwork; } diff --git a/frontend/src/environments/environment.ts b/frontend/src/environments/environment.ts index c7e94f9576..d0445cd6a3 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: true, globalConfig: { features: { dashboard: [], @@ -39,35 +39,34 @@ 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-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, + 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: 'http://65.109.105.40:3000', - // url: 'http://65.109.105.40:3000', + // name: 'Web Node 1', + // isWebNode: true, // }, // { - // name: 'Local rust node', - // url: 'http://127.0.0.1:3000', - // memoryProfiler: 'http://1.k8.openmina.com:31164', + // 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: 'feat/frontend-api-peers', // url: 'http://176.9.147.28:3000', @@ -79,10 +78,10 @@ export const environment: Readonly = { // resources: ['memory'], // }, // }, - // { - // name: 'Docker 11010', - // url: 'http://localhost:11010', - // }, + { + name: 'Docker 11010', + url: 'http://localhost:11010', + }, // { // name: 'Docker 11012', // url: 'http://localhost:11012', diff --git a/frontend/src/index.html b/frontend/src/index.html index b216014a4c..111a43de5d 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -28,11 +28,11 @@ - - - - - + + + + + Open Mina @@ -52,6 +52,16 @@ +