-
We(b) Node
Do You?
+
We Node
Do You?
@@ -44,7 +48,7 @@
Node-To-Earn
[style.background-image]="'url(assets/images/landing-page/cta-section-bg.png)'">
Run a web node on Testnet and enter a 1000 MINA lottery
@@ -56,13 +60,14 @@
Run a web node on Testnet and enter a 1000 MINA lottery
-
The Mina Web Node, part 1
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore .
-
-
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore .
+
Join the Mina Web Node Testing Program
+
Discover all the details about Mina's Web Node Testing Program. Learn how to apply, participate, and earn rewards in Mina Web Node Testing Round
+ 1.
diff --git a/frontend/src/app/features/leaderboard/leaderboard-landing-page/leaderboard-landing-page.component.scss b/frontend/src/app/features/leaderboard/leaderboard-landing-page/leaderboard-landing-page.component.scss
index 0ee7917bcf..63939df9ae 100644
--- a/frontend/src/app/features/leaderboard/leaderboard-landing-page/leaderboard-landing-page.component.scss
+++ b/frontend/src/app/features/leaderboard/leaderboard-landing-page/leaderboard-landing-page.component.scss
@@ -4,12 +4,26 @@
:host {
- padding-top: 52px;
background-color: $mina-cta-primary;
color: $mina-base-primary;
font-family: "IBM Plex Sans", sans-serif;
}
+.floating-banner {
+ position: fixed;
+ bottom: -100%;
+ left: 20px;
+ right: 20px;
+ transition: bottom 0.5s ease;
+ z-index: 1000;
+ border-radius: 12px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+
+ &.show {
+ bottom: 20px;
+ }
+}
+
main,
mina-leaderboard-header,
mina-leaderboard-footer {
@@ -34,6 +48,7 @@ mina-leaderboard-footer {
line-height: 20px;
font-weight: 300;
transition: .15s ease;
+ min-width: 240px;
&:hover {
background-color: $black;
@@ -49,6 +64,7 @@ mina-leaderboard-footer {
}
.overflow-y-scroll {
+ padding-bottom: 72px;
background-color: $mina-cta-primary;
&::-webkit-scrollbar-track {
diff --git a/frontend/src/app/features/leaderboard/leaderboard-landing-page/leaderboard-landing-page.component.ts b/frontend/src/app/features/leaderboard/leaderboard-landing-page/leaderboard-landing-page.component.ts
index afce1668e7..233518cf56 100644
--- a/frontend/src/app/features/leaderboard/leaderboard-landing-page/leaderboard-landing-page.component.ts
+++ b/frontend/src/app/features/leaderboard/leaderboard-landing-page/leaderboard-landing-page.component.ts
@@ -1,4 +1,7 @@
-import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+import { AfterViewInit, ChangeDetectionStrategy, Component, DestroyRef, ElementRef, ViewChild } from '@angular/core';
+import { ManualDetection } from '@openmina/shared';
+import { debounceTime, fromEvent } from 'rxjs';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'mina-leaderboard-landing-page',
@@ -7,9 +10,34 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'flex-column h-100 align-center' },
})
-export class LeaderboardLandingPageComponent implements OnInit {
+export class LeaderboardLandingPageComponent extends ManualDetection implements AfterViewInit {
+ showBanner: boolean = false;
- ngOnInit(): void {
+ private readonly SCROLL_THRESHOLD = 100;
+ @ViewChild('scrollContainer') private scrollContainer!: ElementRef;
+
+ constructor(private destroyRef: DestroyRef) {
+ super();
}
+ ngAfterViewInit(): void {
+ const container = this.scrollContainer.nativeElement;
+
+ fromEvent(container, 'scroll')
+ .pipe(
+ debounceTime(100),
+ takeUntilDestroyed(this.destroyRef),
+ )
+ .subscribe(() => {
+ const scrollPosition = container.scrollTop;
+
+ if (scrollPosition > this.SCROLL_THRESHOLD && !this.showBanner) {
+ this.showBanner = true;
+ this.detect();
+ } else if (scrollPosition <= this.SCROLL_THRESHOLD && this.showBanner) {
+ this.showBanner = false;
+ this.detect();
+ }
+ });
+ }
}
diff --git a/frontend/src/app/features/leaderboard/leaderboard-page/leaderboard-page.component.html b/frontend/src/app/features/leaderboard/leaderboard-page/leaderboard-page.component.html
index dcdc8794c2..cd5acc0ee7 100644
--- a/frontend/src/app/features/leaderboard/leaderboard-page/leaderboard-page.component.html
+++ b/frontend/src/app/features/leaderboard/leaderboard-page/leaderboard-page.component.html
@@ -1,11 +1,36 @@
-
+
+
+
-
+
+
+
+
+
+
+
+
+
+ - New blocks can reorganize the chain, changing past data
+ - Network nodes need time to reach consensus
+ - Block confirmations become more certain over time
+ - Final results will be published after the program ends and complete chain verification
+
+
To learn more about how uptime is tracked, please refer to the How Uptime Tracking Works section in the program details.
+
+
+
diff --git a/frontend/src/app/features/leaderboard/leaderboard-page/leaderboard-page.component.scss b/frontend/src/app/features/leaderboard/leaderboard-page/leaderboard-page.component.scss
index 557f63b4eb..5e7f19ef29 100644
--- a/frontend/src/app/features/leaderboard/leaderboard-page/leaderboard-page.component.scss
+++ b/frontend/src/app/features/leaderboard/leaderboard-page/leaderboard-page.component.scss
@@ -1,10 +1,41 @@
@import 'leaderboard-variables';
:host {
- padding-top: 52px;
background-color: $mina-cta-primary;
}
+.floating-banner {
+ position: fixed;
+ bottom: -100%;
+ left: 20px;
+ right: 20px;
+ transition: bottom 0.5s ease;
+ z-index: 1000;
+ border-radius: 12px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+
+ &.show {
+ bottom: 20px;
+ }
+}
+
+.overflow-y-scroll {
+ padding-bottom: 72px;
+ background-color: $mina-cta-primary;
+
+ &::-webkit-scrollbar-track {
+ background-color: transparent;
+ }
+
+ &::-webkit-scrollbar-thumb {
+ background-color: $white4;
+ }
+
+ &::-webkit-scrollbar-thumb:hover {
+ background-color: $mina-base-secondary;
+ }
+}
+
main,
mina-leaderboard-header,
mina-leaderboard-footer {
@@ -41,3 +72,17 @@ main {
background-color: $mina-base-secondary;
}
}
+
+.accordion {
+ color: $mina-base-primary;
+ font-size: 16px;
+ font-weight: 600;
+ background: rgba(248, 214, 17, 0.10);
+ cursor: pointer;
+ padding: 16px;
+}
+
+.accordion-content {
+ font-weight: 400;
+ line-height: 24px;
+}
diff --git a/frontend/src/app/features/leaderboard/leaderboard-page/leaderboard-page.component.ts b/frontend/src/app/features/leaderboard/leaderboard-page/leaderboard-page.component.ts
index 15e87823ce..db10f03c3f 100644
--- a/frontend/src/app/features/leaderboard/leaderboard-page/leaderboard-page.component.ts
+++ b/frontend/src/app/features/leaderboard/leaderboard-page/leaderboard-page.component.ts
@@ -1,8 +1,11 @@
-import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+import { AfterViewInit, ChangeDetectionStrategy, Component, DestroyRef, ElementRef, OnInit, ViewChild } from '@angular/core';
import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class';
import { LeaderboardActions } from '@leaderboard/leaderboard.actions';
-import { timer } from 'rxjs';
+import { debounceTime, fromEvent, timer } from 'rxjs';
import { untilDestroyed } from '@ngneat/until-destroy';
+import { trigger, state, style, animate, transition } from '@angular/animations';
+import { ManualDetection } from '@openmina/shared';
+import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@Component({
selector: 'mina-leaderboard-page',
@@ -10,8 +13,40 @@ import { untilDestroyed } from '@ngneat/until-destroy';
styleUrl: './leaderboard-page.component.scss',
changeDetection: ChangeDetectionStrategy.OnPush,
host: { class: 'flex-column h-100' },
+ animations: [
+ trigger('expandCollapse', [
+ state('false', style({
+ height: '0',
+ overflow: 'hidden',
+ opacity: '0',
+ })),
+ state('true', style({
+ height: '*',
+ opacity: '1',
+ })),
+ transition('false <=> true', [
+ animate('200ms ease-in-out'),
+ ]),
+ ]),
+ trigger('rotateIcon', [
+ state('false', style({ transform: 'rotate(0)' })),
+ state('true', style({ transform: 'rotate(90deg)' })),
+ transition('false <=> true', [
+ animate('200ms'),
+ ]),
+ ]),
+ ],
})
-export class LeaderboardPageComponent extends StoreDispatcher implements OnInit {
+export class LeaderboardPageComponent extends StoreDispatcher implements OnInit, AfterViewInit {
+ isExpanded = false;
+ showBanner: boolean = false;
+
+ private readonly SCROLL_THRESHOLD = 100;
+ @ViewChild('scrollContainer') private scrollContainer!: ElementRef;
+
+ constructor(private destroyRef: DestroyRef) {
+ super();
+ }
ngOnInit(): void {
timer(0, 5000)
@@ -21,4 +56,24 @@ export class LeaderboardPageComponent extends StoreDispatcher implements OnInit
});
}
+ ngAfterViewInit(): void {
+ const container = this.scrollContainer.nativeElement;
+
+ fromEvent(container, 'scroll')
+ .pipe(
+ debounceTime(100),
+ takeUntilDestroyed(this.destroyRef),
+ )
+ .subscribe(() => {
+ const scrollPosition = container.scrollTop;
+
+ if (scrollPosition > this.SCROLL_THRESHOLD && !this.showBanner) {
+ this.showBanner = true;
+ this.detect();
+ } else if (scrollPosition <= this.SCROLL_THRESHOLD && this.showBanner) {
+ this.showBanner = false;
+ this.detect();
+ }
+ });
+ }
}
diff --git a/frontend/src/app/features/leaderboard/leaderboard-privacy-policy/leaderboard-privacy-policy.component.scss b/frontend/src/app/features/leaderboard/leaderboard-privacy-policy/leaderboard-privacy-policy.component.scss
index b5c5a9483d..ca5b5b1ddb 100644
--- a/frontend/src/app/features/leaderboard/leaderboard-privacy-policy/leaderboard-privacy-policy.component.scss
+++ b/frontend/src/app/features/leaderboard/leaderboard-privacy-policy/leaderboard-privacy-policy.component.scss
@@ -36,6 +36,6 @@ h1 {
margin-bottom: 80px;
color: $mina-base-primary;
font-size: 80px;
- font-weight: 400;
+ font-weight: 300;
line-height: 80px;
}
diff --git a/frontend/src/app/features/leaderboard/leaderboard-table/leaderboard-table.component.html b/frontend/src/app/features/leaderboard/leaderboard-table/leaderboard-table.component.html
index 6c9f2f3686..054a40ce1b 100644
--- a/frontend/src/app/features/leaderboard/leaderboard-table/leaderboard-table.component.html
+++ b/frontend/src/app/features/leaderboard/leaderboard-table/leaderboard-table.component.html
@@ -14,7 +14,7 @@
circle
{{ row.publicKey | truncateMid: (desktop ? 15 : 6): 6 }}
-
+
{{ row.uptimePercentage }}%
@if (row.uptimePercentage > 33.33) {
bookmark_check
diff --git a/frontend/src/app/features/leaderboard/leaderboard-table/leaderboard-table.component.scss b/frontend/src/app/features/leaderboard/leaderboard-table/leaderboard-table.component.scss
index a4868d0b6c..0b6ca4bff0 100644
--- a/frontend/src/app/features/leaderboard/leaderboard-table/leaderboard-table.component.scss
+++ b/frontend/src/app/features/leaderboard/leaderboard-table/leaderboard-table.component.scss
@@ -36,13 +36,20 @@
font-size: 16px;
@media (max-width: 480px) {
- grid-template-columns: 48% 24% 1fr;
+ grid-template-columns: 43% 28% 1fr;
+ }
+
+ @media (max-width: 676px) {
+ grid-template-columns: 43% 28% 1fr;
}
span {
color: $black;
&:not(.mina-icon) {
+ @media (max-width: 676px) {
+ font-size: 2.5vw;
+ }
@media (max-width: 480px) {
font-size: 3vw;
}
@@ -50,13 +57,16 @@
}
.circle {
- color: $black4;
+ color: $mina-brand-gray;
}
.perc {
- width: 37px;
+ width: 58px;
+ @media (max-width: 676px) {
+ width: 55px;
+ }
@media (max-width: 480px) {
- width: 26px;
+ width: 48px;
}
}
diff --git a/frontend/src/app/features/leaderboard/leaderboard-table/leaderboard-table.component.ts b/frontend/src/app/features/leaderboard/leaderboard-table/leaderboard-table.component.ts
index a36b6fcc7a..898136a931 100644
--- a/frontend/src/app/features/leaderboard/leaderboard-table/leaderboard-table.component.ts
+++ b/frontend/src/app/features/leaderboard/leaderboard-table/leaderboard-table.component.ts
@@ -2,7 +2,7 @@ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { LeaderboardSelectors } from '@leaderboard/leaderboard.state';
import { HeartbeatSummary } from '@shared/types/leaderboard/heartbeat-summary.type';
import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class';
-import { isDesktop } from '@openmina/shared';
+import { isDesktop, TooltipPosition } from '@openmina/shared';
import { animate, style, transition, trigger } from '@angular/animations';
@Component({
@@ -47,4 +47,6 @@ export class LeaderboardTableComponent extends StoreDispatcher implements OnInit
this.detect();
});
}
+
+ protected readonly TooltipPosition = TooltipPosition;
}
diff --git a/frontend/src/app/features/leaderboard/leaderboard-terms-and-conditions/leaderboard-terms-and-conditions.component.scss b/frontend/src/app/features/leaderboard/leaderboard-terms-and-conditions/leaderboard-terms-and-conditions.component.scss
index b5c5a9483d..ca5b5b1ddb 100644
--- a/frontend/src/app/features/leaderboard/leaderboard-terms-and-conditions/leaderboard-terms-and-conditions.component.scss
+++ b/frontend/src/app/features/leaderboard/leaderboard-terms-and-conditions/leaderboard-terms-and-conditions.component.scss
@@ -36,6 +36,6 @@ h1 {
margin-bottom: 80px;
color: $mina-base-primary;
font-size: 80px;
- font-weight: 400;
+ font-weight: 300;
line-height: 80px;
}
diff --git a/frontend/src/app/features/leaderboard/leaderboard-title/leaderboard-title.component.html b/frontend/src/app/features/leaderboard/leaderboard-title/leaderboard-title.component.html
index 243f8138fe..7ce680fc1d 100644
--- a/frontend/src/app/features/leaderboard/leaderboard-title/leaderboard-title.component.html
+++ b/frontend/src/app/features/leaderboard/leaderboard-title/leaderboard-title.component.html
@@ -1 +1,2 @@
+Mina Web Node Testing Program
Leaderboard
diff --git a/frontend/src/app/features/leaderboard/leaderboard-title/leaderboard-title.component.scss b/frontend/src/app/features/leaderboard/leaderboard-title/leaderboard-title.component.scss
index e4872bf88f..bcca6c68f7 100644
--- a/frontend/src/app/features/leaderboard/leaderboard-title/leaderboard-title.component.scss
+++ b/frontend/src/app/features/leaderboard/leaderboard-title/leaderboard-title.component.scss
@@ -11,9 +11,19 @@ h1 {
font-weight: 300;
font-size: 80px;
color: $black6;
- margin: 80px 0;
+ margin-bottom: 80px;
+ margin-top: 0;
@media (max-width: 1023px) {
font-size: 10vw;
}
}
+
+h2 {
+ margin-top: 72px;
+ margin-bottom: 16px;
+ color: $mina-base-secondary;
+ font-size: 20px;
+ font-weight: 500;
+ line-height: 28px;
+}
diff --git a/frontend/src/app/features/leaderboard/leaderboard.service.ts b/frontend/src/app/features/leaderboard/leaderboard.service.ts
index 6c19937356..e9baaadf2f 100644
--- a/frontend/src/app/features/leaderboard/leaderboard.service.ts
+++ b/frontend/src/app/features/leaderboard/leaderboard.service.ts
@@ -2,6 +2,8 @@ import { Injectable } from '@angular/core';
import { combineLatest, map, Observable } from 'rxjs';
import { HeartbeatSummary } from '@shared/types/leaderboard/heartbeat-summary.type';
import { collection, collectionData, CollectionReference, Firestore } from '@angular/fire/firestore';
+import { WebNodeService } from '@core/services/web-node.service';
+import { getElapsedTimeInMinsAndHours } from '@shared/helpers/date.helper';
@Injectable({
providedIn: 'root',
@@ -11,7 +13,8 @@ export class LeaderboardService {
private scoresCollection: CollectionReference;
private maxScoreCollection: CollectionReference;
- constructor(private firestore: Firestore) {
+ constructor(private firestore: Firestore,
+ private webnodeService: WebNodeService) {
this.scoresCollection = collection(this.firestore, 'scores');
this.maxScoreCollection = collection(this.firestore, 'maxScore');
}
@@ -24,14 +27,18 @@ export class LeaderboardService {
map(([scores, maxScore]) => {
const maxScoreRightNow = maxScore.find(c => c.id === 'current')['value'];
- const items = scores.map(score => ({
- publicKey: score['publicKey'],
- blocksProduced: score['blocksProduced'],
- isActive: score['lastUpdated'] > Date.now() - 120000,
- uptimePercentage: Math.floor((score['score'] / maxScoreRightNow) * 100),
- uptimePrize: false,
- blocksPrize: false,
- } as HeartbeatSummary));
+ const items = scores.map(score => {
+ return ({
+ publicKey: score['publicKey'],
+ blocksProduced: score['blocksProduced'],
+ isActive: score['lastUpdated'] > Date.now() - 120000,
+ uptimePercentage: this.getUptimePercentage(score['score'], maxScoreRightNow),
+ uptimePrize: false,
+ blocksPrize: false,
+ score: score['score'],
+ maxScore: maxScoreRightNow,
+ } as HeartbeatSummary);
+ });
const sortedItemsByUptime = [...items].sort((a, b) => b.uptimePercentage - a.uptimePercentage);
const fifthPlacePercentageByUptime = sortedItemsByUptime[4]?.uptimePercentage ?? 0;
@@ -44,4 +51,30 @@ export class LeaderboardService {
}),
);
}
+
+ getUptime(): Observable {
+ const publicKey = this.webnodeService.privateStake.publicKey.replace('\n', '');
+
+ return combineLatest([
+ collectionData(this.scoresCollection, { idField: 'id' }),
+ collectionData(this.maxScoreCollection, { idField: 'id' }),
+ ]).pipe(
+ map(([scores, maxScore]) => {
+ const activeEntry = scores.find(score => score['publicKey'] === publicKey);
+
+ return {
+ uptimePercentage: this.getUptimePercentage(activeEntry['score'], maxScore[0]['value']),
+ uptimeTime: getElapsedTimeInMinsAndHours(activeEntry['score'] * 5),
+ };
+ }),
+ );
+ }
+
+ private getUptimePercentage(score: number, maxScore: number): number {
+ let uptimePercentage = Number(((score / maxScore) * 100).toFixed(2));
+ if (maxScore === 0) {
+ uptimePercentage = 0;
+ }
+ return uptimePercentage;
+ }
}
diff --git a/frontend/src/app/features/web-node/web-node-file-upload/web-node-file-upload.component.html b/frontend/src/app/features/web-node/web-node-file-upload/web-node-file-upload.component.html
index 0d5208f617..a072a18d09 100644
--- a/frontend/src/app/features/web-node/web-node-file-upload/web-node-file-upload.component.html
+++ b/frontend/src/app/features/web-node/web-node-file-upload/web-node-file-upload.component.html
@@ -9,7 +9,7 @@ Set Up Your Web Node
@if (!validFiles) {
@if (!error) {
-
+
Select configuration file (.zip)
-
-
Upload webnode-account-XY.zip we sent you
+
Upload webnode-account-XY.zip we sent you
} @else {
@@ -68,18 +68,17 @@
Set Up Your Web Node
}
diff --git a/frontend/src/app/features/web-node/web-node-file-upload/web-node-file-upload.component.ts b/frontend/src/app/features/web-node/web-node-file-upload/web-node-file-upload.component.ts
index b8425a6323..a52f0e9f93 100644
--- a/frontend/src/app/features/web-node/web-node-file-upload/web-node-file-upload.component.ts
+++ b/frontend/src/app/features/web-node/web-node-file-upload/web-node-file-upload.component.ts
@@ -54,7 +54,7 @@ export class WebNodeFileUploadComponent extends ManualDetection {
const publicKey = files.find(f => f.name.includes('.pub'))?.content;
const password = files.find(f => f.name.includes('password'))?.content.replace(/\r?\n|\r/g, '');
const stake = files.find(f => f.name.includes('stake') && !f.name.includes('.pub'))?.content;
- if (this.error || !publicKey || !password || !stake) {
+ if (this.error || !publicKey || !stake) {
this.error = true;
} else {
this.webnodeService.privateStake = { publicKey, password, stake: JSON.parse(stake) };
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 0198d5a15a..9cc8bd21a3 100644
--- a/frontend/src/app/layout/server-status/server-status.component.html
+++ b/frontend/src/app/layout/server-status/server-status.component.html
@@ -2,21 +2,25 @@
@if (!switchForbidden && !hideNodeStats && !isMobile) {
-
-
blur_circular
-
{{ details.transactions }} Tx{{ details.transactions | plural }}
-
{{ details.snarks }} SNARK{{ details.snarks | plural }}
-
-
-
language
-
{{ details.peersConnected }} Peer{{ details.peersConnected | plural }}
-
+ @if (!hideTx) {
+
+
blur_circular
+
{{ details.transactions }} Tx{{ details.transactions | plural }}
+
{{ details.snarks }} SNARK{{ details.snarks | plural }}
+
+ }
+ @if (!hidePeers) {
+
+
language
+
{{ details.peersConnected }} Peer{{ details.peersConnected | plural }}
+
+ }
}
;
diff --git a/frontend/src/app/layout/toolbar/toolbar.component.html b/frontend/src/app/layout/toolbar/toolbar.component.html
index 0bdd8f4ad4..cbe14e39be 100644
--- a/frontend/src/app/layout/toolbar/toolbar.component.html
+++ b/frontend/src/app/layout/toolbar/toolbar.component.html
@@ -10,10 +10,13 @@
}
-
- @if (!isMobile || (isMobile && errors.length)) {
+
+ @if (!isMobile) {
}
+ @if (showUptime) {
+
+ }
@if (haveNextBP && !isAllNodesPage) {
diff --git a/frontend/src/app/layout/toolbar/toolbar.component.scss b/frontend/src/app/layout/toolbar/toolbar.component.scss
index 71c8f6be53..fab767af7d 100644
--- a/frontend/src/app/layout/toolbar/toolbar.component.scss
+++ b/frontend/src/app/layout/toolbar/toolbar.component.scss
@@ -4,6 +4,9 @@
height: 40px;
@media (max-width: 767px) {
height: 96px;
+ &.uptime {
+ height: 130px;
+ }
}
}
@@ -50,6 +53,13 @@
}
}
}
+
+ .pills-holder {
+ &.is-mobile {
+ width: 100%;
+ flex-direction: column !important;
+ }
+ }
}
@keyframes loading {
diff --git a/frontend/src/app/layout/toolbar/toolbar.component.ts b/frontend/src/app/layout/toolbar/toolbar.component.ts
index 4c66ec1860..8d07dfdad3 100644
--- a/frontend/src/app/layout/toolbar/toolbar.component.ts
+++ b/frontend/src/app/layout/toolbar/toolbar.component.ts
@@ -1,5 +1,5 @@
-import { ChangeDetectionStrategy, Component, ElementRef, OnInit, ViewChild } from '@angular/core';
-import { filter, map } from 'rxjs';
+import { ChangeDetectionStrategy, Component, ElementRef, HostBinding, OnInit, ViewChild } from '@angular/core';
+import { catchError, filter, map, of, switchMap, timer } from 'rxjs';
import { AppSelectors } from '@app/app.state';
import { getMergedRoute, hasValue, MergedRoute, removeParamsFromURL, TooltipService } from '@openmina/shared';
import { AppMenu } from '@shared/types/app/app-menu.type';
@@ -10,6 +10,9 @@ import { selectErrorPreviewErrors } from '@error-preview/error-preview.state';
import { MinaError } from '@shared/types/error-preview/mina-error.type';
import { AppNodeStatus } from '@shared/types/app/app-node-details.type';
import { Routes } from '@shared/enums/routes.enum';
+import { CONFIG } from '@shared/constants/config';
+import { LeaderboardService } from '@leaderboard/leaderboard.service';
+import { untilDestroyed } from '@ngneat/until-destroy';
@Component({
selector: 'mina-toolbar',
@@ -26,6 +29,9 @@ export class ToolbarComponent extends StoreDispatcher implements OnInit {
haveNextBP: boolean;
isAllNodesPage: boolean;
+ @HostBinding('class.uptime')
+ showUptime: boolean = CONFIG.showLeaderboard;
+
@ViewChild('loadingRef') private loadingRef: ElementRef
;
constructor(private tooltipService: TooltipService) { super(); }
diff --git a/frontend/src/app/layout/uptime-pill/uptime-pill.component.html b/frontend/src/app/layout/uptime-pill/uptime-pill.component.html
new file mode 100644
index 0000000000..4aac49b5c2
--- /dev/null
+++ b/frontend/src/app/layout/uptime-pill/uptime-pill.component.html
@@ -0,0 +1,5 @@
+
+
beenhere
+
Uptime {{ uptime.uptimePercentage }}% {{ uptime.uptimeTime }}
+
diff --git a/frontend/src/app/layout/uptime-pill/uptime-pill.component.scss b/frontend/src/app/layout/uptime-pill/uptime-pill.component.scss
new file mode 100644
index 0000000000..36ace675f6
--- /dev/null
+++ b/frontend/src/app/layout/uptime-pill/uptime-pill.component.scss
@@ -0,0 +1,44 @@
+@import 'openmina';
+
+
+.chip {
+ gap: 4px;
+ background-color: $base-surface;
+
+ &::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ border-radius: 6px;
+ background-color: $success-container;
+ }
+
+ div {
+ color: $success-primary;
+ }
+
+ span {
+ color: $success-secondary;
+
+ &.mina-icon {
+ color: $success-primary;
+ }
+ }
+
+ @media (max-width: 767px) {
+ width: 100%;
+ margin-bottom: 5px;
+ font-size: 12px;
+
+ .mina-icon {
+ display: none;
+ }
+
+ &.h-sm {
+ height: 32px !important;
+ }
+ }
+}
diff --git a/frontend/src/app/layout/uptime-pill/uptime-pill.component.ts b/frontend/src/app/layout/uptime-pill/uptime-pill.component.ts
new file mode 100644
index 0000000000..86740d123e
--- /dev/null
+++ b/frontend/src/app/layout/uptime-pill/uptime-pill.component.ts
@@ -0,0 +1,38 @@
+import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
+import { catchError, of, switchMap, timer } from 'rxjs';
+import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
+import { LeaderboardService } from '@leaderboard/leaderboard.service';
+import { ManualDetection, OpenminaEagerSharedModule } from '@openmina/shared';
+
+@UntilDestroy()
+@Component({
+ selector: 'mina-uptime-pill',
+ standalone: true,
+ imports: [
+ OpenminaEagerSharedModule,
+ ],
+ templateUrl: './uptime-pill.component.html',
+ styleUrl: './uptime-pill.component.scss',
+ changeDetection: ChangeDetectionStrategy.OnPush,
+})
+export class UptimePillComponent extends ManualDetection implements OnInit {
+
+ uptime: { uptimePercentage: number, uptimeTime: string } = { uptimePercentage: 0, uptimeTime: '' };
+
+ constructor(private leaderboardService: LeaderboardService) { super(); }
+
+ ngOnInit(): void {
+ this.listenToUptime();
+ }
+
+ private listenToUptime(): void {
+ timer(0, 60000).pipe(
+ switchMap(() => this.leaderboardService.getUptime()),
+ catchError((err) => of({})),
+ untilDestroyed(this),
+ ).subscribe(uptime => {
+ this.uptime = uptime;
+ this.detect();
+ });
+ }
+}
diff --git a/frontend/src/app/shared/guards/landing-page.guard.ts b/frontend/src/app/shared/guards/landing-page.guard.ts
new file mode 100644
index 0000000000..d381a585a7
--- /dev/null
+++ b/frontend/src/app/shared/guards/landing-page.guard.ts
@@ -0,0 +1,46 @@
+import { CanActivateFn, Router } from '@angular/router';
+import { inject } from '@angular/core';
+import { Store } from '@ngrx/store';
+import { map, take } from 'rxjs/operators';
+import { CONFIG } from '@shared/constants/config';
+import { getMergedRoute } from '@openmina/shared';
+import { Routes } from '@shared/enums/routes.enum';
+
+let isFirstLoad = true;
+
+export const landingPageGuard: CanActivateFn = (route, state) => {
+
+ if (!isFirstLoad || !CONFIG.showWebNodeLandingPage) {
+ return true;
+ }
+ const router = inject(Router);
+ const store = inject(Store);
+ isFirstLoad = false;
+
+ return store.select(getMergedRoute).pipe(
+ take(1),
+ map(route => {
+ if (!route) return true;
+
+ const startsWith = (path: string) => route.url.startsWith(path);
+
+ if (
+ startsWith('/dashboard') ||
+ startsWith('/block-production') ||
+ startsWith('/state') ||
+ startsWith('/mempool') ||
+ startsWith('/loading-web-node')
+ ) {
+ return router.createUrlTree([Routes.LOADING_WEB_NODE], {
+ queryParamsHandling: 'preserve',
+ });
+ }
+
+ if (!startsWith('/') && !startsWith('/?') && !startsWith('/leaderboard')) {
+ return router.createUrlTree(['']);
+ }
+
+ return true;
+ }),
+ );
+};
diff --git a/frontend/src/app/shared/helpers/date.helper.ts b/frontend/src/app/shared/helpers/date.helper.ts
index 2dc15940dd..97243ff87a 100644
--- a/frontend/src/app/shared/helpers/date.helper.ts
+++ b/frontend/src/app/shared/helpers/date.helper.ts
@@ -98,3 +98,14 @@ export function getElapsedTime(timeInSeconds: number): string {
return `${minutes}m ${seconds}s`;
}
+
+export function getElapsedTimeInMinsAndHours(timeInMinutes: number): string {
+ if (timeInMinutes < 60) {
+ return `${timeInMinutes}m`;
+ }
+
+ const hours = Math.floor(timeInMinutes / 60);
+ const minutes = timeInMinutes % 60;
+
+ return `${hours}h ${minutes}m`;
+}
diff --git a/frontend/src/app/shared/types/core/environment/mina-env.type.ts b/frontend/src/app/shared/types/core/environment/mina-env.type.ts
index febcec9c4b..06d4dd4ab6 100644
--- a/frontend/src/app/shared/types/core/environment/mina-env.type.ts
+++ b/frontend/src/app/shared/types/core/environment/mina-env.type.ts
@@ -6,6 +6,9 @@ export interface MinaEnv {
hideNodeStats?: boolean;
canAddNodes?: boolean;
showWebNodeLandingPage?: boolean;
+ showLeaderboard?: boolean;
+ hidePeersPill?: boolean;
+ hideTxPill?: boolean;
sentry?: {
dsn: string;
tracingOrigins: string[];
@@ -14,6 +17,7 @@ export interface MinaEnv {
features?: FeaturesConfig;
graphQL?: string;
firebase?: any;
+ heartbeats?: boolean;
};
}
diff --git a/frontend/src/app/shared/types/leaderboard/heartbeat-summary.type.ts b/frontend/src/app/shared/types/leaderboard/heartbeat-summary.type.ts
index a134981c2f..9993c968b9 100644
--- a/frontend/src/app/shared/types/leaderboard/heartbeat-summary.type.ts
+++ b/frontend/src/app/shared/types/leaderboard/heartbeat-summary.type.ts
@@ -5,4 +5,6 @@ export interface HeartbeatSummary {
blocksProduced: number;
uptimePrize: boolean;
blocksPrize: boolean;
+ score: number;
+ maxScore: number;
}
diff --git a/frontend/src/assets/environments/leaderboard.js b/frontend/src/assets/environments/leaderboard.js
index 904531dd35..713f1e1ef4 100644
--- a/frontend/src/assets/environments/leaderboard.js
+++ b/frontend/src/assets/environments/leaderboard.js
@@ -6,12 +6,14 @@ export default {
production: true,
canAddNodes: false,
showWebNodeLandingPage: true,
+ showLeaderboard: true,
+ hidePeersPill: true,
+ hideTxPill: true,
globalConfig: {
features: {
'dashboard': [],
'block-production': ['won-slots'],
'mempool': [],
- 'benchmarks': ['wallets'],
'state': ['actions'],
},
firebase: {
@@ -23,6 +25,7 @@ export default {
appId: '1:1016673359357:web:bbd2cbf3f031756aec7594',
measurementId: 'G-ENDBL923XT',
},
+ heartbeats: true,
},
// sentry: {
// dsn: 'https://69aba72a6290383494290cf285ab13b3@o4508216158584832.ingest.de.sentry.io/4508216160616528',
diff --git a/frontend/src/assets/environments/webnode.js b/frontend/src/assets/environments/webnode.js
index 14ed05ebc1..99848ea84f 100644
--- a/frontend/src/assets/environments/webnode.js
+++ b/frontend/src/assets/environments/webnode.js
@@ -10,9 +10,6 @@ export default {
features: {
'dashboard': [],
'block-production': ['won-slots'],
- 'mempool': [],
- 'benchmarks': ['wallets'],
- 'state': ['actions'],
},
firebase: {
'projectId': 'openminawebnode',
diff --git a/frontend/src/environments/environment.prod.ts b/frontend/src/environments/environment.prod.ts
index 4325e3ef25..f1f0b8ef10 100644
--- a/frontend/src/environments/environment.prod.ts
+++ b/frontend/src/environments/environment.prod.ts
@@ -10,5 +10,8 @@ export const environment: Readonly = {
hideToolbar: env.hideToolbar,
canAddNodes: env.canAddNodes,
showWebNodeLandingPage: env.showWebNodeLandingPage,
+ showLeaderboard: env.showLeaderboard,
+ hidePeersPill: env.hidePeersPill,
+ hideTxPill: env.hideTxPill,
sentry: env.sentry,
};
diff --git a/frontend/src/environments/environment.ts b/frontend/src/environments/environment.ts
index 27fb7cddd8..c9d3c82a02 100644
--- a/frontend/src/environments/environment.ts
+++ b/frontend/src/environments/environment.ts
@@ -5,6 +5,9 @@ export const environment: Readonly = {
identifier: 'Dev FE',
canAddNodes: true,
showWebNodeLandingPage: true,
+ showLeaderboard: true,
+ hidePeersPill: true,
+ hideTxPill: true,
globalConfig: {
features: {
dashboard: [],
@@ -27,6 +30,7 @@ export const environment: Readonly = {
appId: '1:1016673359357:web:bbd2cbf3f031756aec7594',
measurementId: 'G-ENDBL923XT',
},
+ heartbeats: true,
graphQL: 'https://adonagy.com/graphql',
// graphQL: 'https://api.minascan.io/node/devnet/v1/graphql',
// graphQL: 'http://65.109.105.40:5000/graphql',
@@ -87,18 +91,18 @@ export const environment: Readonly = {
// resources: ['memory'],
// },
// },
- {
- name: 'Docker 11010',
- url: 'http://localhost:11010',
- },
- {
- name: 'Docker 11012',
- url: 'http://localhost:11012',
- },
- {
- name: 'Docker 11014',
- url: 'http://localhost:11014',
- },
+ // {
+ // name: 'Docker 11010',
+ // url: 'http://localhost:11010',
+ // },
+ // {
+ // name: 'Docker 11012',
+ // url: 'http://localhost:11012',
+ // },
+ // {
+ // name: 'Docker 11014',
+ // url: 'http://localhost:11014',
+ // },
// {
// name: 'Producer',
// url: 'http://65.109.105.40:3000',
diff --git a/frontend/src/index.html b/frontend/src/index.html
index e627af2266..b04ef77d1f 100644
--- a/frontend/src/index.html
+++ b/frontend/src/index.html
@@ -4,7 +4,7 @@
-