Skip to content

Commit a0b8b1c

Browse files
authored
Frontend - Improved landing page, web node, won slots (#854)
1 parent b1e70d8 commit a0b8b1c

21 files changed

+252
-245
lines changed

frontend/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
"start:dev": "ng serve --configuration development",
88
"build": "ng build",
99
"build:prod": "ng build --configuration production",
10-
"watch": "ng build --watch --configuration development",
1110
"tests": "npx cypress open --config baseUrl=http://localhost:4200",
1211
"tests:headless": "npx cypress run --headless --config baseUrl=http://localhost:4200",
1312
"docker": "npm run build:prod && docker buildx build --platform linux/amd64 -t openmina/frontend:latest . && docker push openmina/frontend:latest",

frontend/src/app/core/services/web-node.service.ts

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { Injectable } from '@angular/core';
2-
import { BehaviorSubject, filter, from, fromEvent, map, merge, Observable, of, switchMap, tap } from 'rxjs';
2+
import { BehaviorSubject, catchError, filter, from, fromEvent, map, merge, Observable, of, switchMap, tap } from 'rxjs';
33
import base from 'base-x';
44
import { any } from '@openmina/shared';
55
import { HttpClient } from '@angular/common/http';
6+
import { sendSentryEvent } from '@shared/helpers/webnode.helper';
7+
import { DashboardPeerStatus } from '@shared/types/dashboard/dashboard.peer';
68

79
@Injectable({
810
providedIn: 'root',
@@ -11,6 +13,8 @@ export class WebNodeService {
1113

1214
private readonly webnode$: BehaviorSubject<any> = new BehaviorSubject<any>(null);
1315
private webNodeKeyPair: { publicKey: string, privateKey: string };
16+
private webNodeStartTime: number;
17+
private sentryEvents: any = {};
1418

1519
constructor(private http: HttpClient) {
1620
const basex = base('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz');
@@ -21,32 +25,39 @@ export class WebNodeService {
2125
}
2226

2327
loadWasm$(): Observable<void> {
24-
console.log('---LOADING WEBNODE---');
28+
sendSentryEvent('Loading WebNode JS');
2529
return merge(
2630
of(any(window).webnode).pipe(filter(Boolean)),
2731
fromEvent(window, 'webNodeLoaded'),
2832
).pipe(
2933
switchMap(() => this.http.get<{ publicKey: string, privateKey: string }>('assets/webnode/web-node-secrets.json')),
30-
tap(data => this.webNodeKeyPair = data),
34+
tap(data => {
35+
this.webNodeKeyPair = data;
36+
sendSentryEvent('WebNode JS Loaded. Loading WebNode Wasm');
37+
}),
3138
map(() => void 0),
3239
);
3340
}
3441

3542
startWasm$(): Observable<any> {
36-
console.log('---STARTING WEBNODE---');
3743
return of(any(window).webnode)
3844
.pipe(
3945
switchMap((wasm: any) => from(wasm.default('assets/webnode/pkg/openmina_node_web_bg.wasm')).pipe(map(() => wasm))),
4046
switchMap((wasm) => {
41-
console.log(wasm);
47+
sendSentryEvent('WebNode Wasm loaded. Starting WebNode');
4248
return from(wasm.run(this.webNodeKeyPair.privateKey));
4349
}),
4450
tap((webnode: any) => {
45-
console.log('----------------WEBNODE----------------');
46-
console.log(webnode);
47-
(window as any)["webnode"] = webnode;
51+
sendSentryEvent('WebNode Started');
52+
this.webNodeStartTime = Date.now();
53+
(window as any)['webnode'] = webnode;
4854
this.webnode$.next(webnode);
4955
}),
56+
catchError((error) => {
57+
sendSentryEvent('WebNode failed to start');
58+
console.error(error);
59+
return of(null);
60+
}),
5061
switchMap(() => this.webnode$.asObservable()),
5162
filter(Boolean),
5263
);
@@ -70,6 +81,23 @@ export class WebNodeService {
7081
return this.webnode$.asObservable().pipe(
7182
filter(Boolean),
7283
switchMap(handle => from(any(handle).state().peers())),
84+
tap((peers) => {
85+
if (!this.sentryEvents.sentNoPeersEvent && Date.now() - this.webNodeStartTime >= 5000 && peers.length === 0) {
86+
console.log('WebNode has no peers after 5 seconds from startup.');
87+
sendSentryEvent('WebNode has no peers after 5 seconds from startup.');
88+
this.sentryEvents.sentNoPeersEvent = true;
89+
}
90+
if (!this.sentryEvents.sentPeersEvent && peers.length > 0) {
91+
const seconds = (Date.now() - this.webNodeStartTime) / 1000;
92+
sendSentryEvent(`WebNode found its first peer after ${seconds}s`);
93+
this.sentryEvents.sentPeersEvent = true;
94+
}
95+
if (!this.sentryEvents.firstPeerConnected && peers.some((p: any) => p.connection_status === DashboardPeerStatus.CONNECTED)) {
96+
const seconds = (Date.now() - this.webNodeStartTime) / 1000;
97+
sendSentryEvent(`WebNode connected to its first peer after ${seconds}s`);
98+
this.sentryEvents.firstPeerConnected = true;
99+
}
100+
}),
73101
);
74102
}
75103

frontend/src/app/features/block-production/won-slots/block-production-won-slots.actions.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ const getSlotsSuccess = createAction(type('Get Slots Success'), props<{
2727
const changeFilters = createAction(type('Change Filters'), props<{ filters: BlockProductionWonSlotsFilters }>());
2828
const setActiveSlot = createAction(type('Set Active Slot'), props<{ slot: BlockProductionWonSlotsSlot }>());
2929
const sort = createAction(type('Sort'), props<{ sort: TableSort<BlockProductionWonSlotsSlot> }>());
30+
const toggleSidePanel = createAction(type('Toggle Side Panel'));
3031

3132
export const BlockProductionWonSlotsActions = {
3233
init,
@@ -36,4 +37,5 @@ export const BlockProductionWonSlotsActions = {
3637
changeFilters,
3738
setActiveSlot,
3839
sort,
40+
toggleSidePanel,
3941
};

frontend/src/app/features/block-production/won-slots/block-production-won-slots.component.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { ChangeDetectionStrategy, Component, ElementRef, OnDestroy, OnInit } from '@angular/core';
22
import { StoreDispatcher } from '@shared/base-classes/store-dispatcher.class';
33
import { getMergedRoute, isDesktop, isMobile, MergedRoute } from '@openmina/shared';
4-
import { debounceTime, filter, fromEvent, take, timer } from 'rxjs';
4+
import { debounceTime, filter, fromEvent, skip, take, timer } from 'rxjs';
55
import { untilDestroyed } from '@ngneat/until-destroy';
66
import { BlockProductionWonSlotsActions } from '@block-production/won-slots/block-production-won-slots.actions';
77
import { AppSelectors } from '@app/app.state';
8+
import { BlockProductionWonSlotsSelectors } from '@block-production/won-slots/block-production-won-slots.state';
89

910
@Component({
1011
selector: 'mina-block-production-won-slots',
@@ -37,16 +38,10 @@ export class BlockProductionWonSlotsComponent extends StoreDispatcher implements
3738
}
3839

3940
private listenToResize(): void {
40-
fromEvent(window, 'resize')
41-
.pipe(
42-
debounceTime(100),
43-
filter(() => this.showSidePanel === isMobile()),
44-
untilDestroyed(this),
45-
)
46-
.subscribe(() => {
47-
this.showSidePanel = isDesktop();
48-
this.detect();
49-
});
41+
this.select(BlockProductionWonSlotsSelectors.openSidePanel, (open: boolean) => {
42+
this.showSidePanel = open;
43+
this.detect();
44+
});
5045
}
5146

5247
override ngOnDestroy(): void {

frontend/src/app/features/block-production/won-slots/block-production-won-slots.reducer.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { createReducer, on } from '@ngrx/store';
22
import { BlockProductionWonSlotsState } from '@block-production/won-slots/block-production-won-slots.state';
3-
import { sort, SortDirection, TableSort } from '@openmina/shared';
3+
import { isMobile, sort, SortDirection, TableSort } from '@openmina/shared';
44
import { BlockProductionWonSlotsActions } from '@block-production/won-slots/block-production-won-slots.actions';
55
import {
66
BlockProductionWonSlotsSlot,
@@ -13,6 +13,7 @@ const initialState: BlockProductionWonSlotsState = {
1313
filteredSlots: [],
1414
activeSlot: undefined,
1515
activeSlotRoute: undefined,
16+
openSidePanel: !isMobile(),
1617
filters: {
1718
accepted: true,
1819
orphaned: true,
@@ -37,11 +38,13 @@ export const blockProductionWonSlotsReducer = createReducer(
3738
epoch,
3839
filteredSlots: filterSlots(sortSlots(slots, state.sort), state.filters),
3940
activeSlot,
41+
openSidePanel: !!activeSlot,
4042
})),
4143
on(BlockProductionWonSlotsActions.setActiveSlot, (state, { slot }) => ({
4244
...state,
4345
activeSlot: slot,
4446
activeSlotRoute: slot.globalSlot.toString(),
47+
openSidePanel: true,
4548
})),
4649
on(BlockProductionWonSlotsActions.sort, (state, { sort }) => ({
4750
...state,
@@ -53,6 +56,7 @@ export const blockProductionWonSlotsReducer = createReducer(
5356
filters,
5457
filteredSlots: filterSlots(sortSlots(state.slots, state.sort), filters),
5558
})),
59+
on(BlockProductionWonSlotsActions.toggleSidePanel, state => ({ ...state, openSidePanel: !state.openSidePanel })),
5660
on(BlockProductionWonSlotsActions.close, () => initialState),
5761
);
5862

frontend/src/app/features/block-production/won-slots/block-production-won-slots.state.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export interface BlockProductionWonSlotsState {
1818
filteredSlots: BlockProductionWonSlotsSlot[];
1919
activeSlot: BlockProductionWonSlotsSlot;
2020
activeSlotRoute: string;
21+
openSidePanel: boolean;
2122
filters: BlockProductionWonSlotsFilters;
2223
sort: TableSort<BlockProductionWonSlotsSlot>;
2324
}
@@ -34,6 +35,7 @@ const filteredSlots = select(state => state.filteredSlots);
3435
const activeSlot = select(state => state.activeSlot);
3536
const filters = select(state => state.filters);
3637
const sort = select(state => state.sort);
38+
const openSidePanel = select(state => state.openSidePanel);
3739

3840
export const BlockProductionWonSlotsSelectors = {
3941
epoch,
@@ -42,4 +44,5 @@ export const BlockProductionWonSlotsSelectors = {
4244
activeSlot,
4345
filters,
4446
sort,
47+
openSidePanel,
4548
};

frontend/src/app/features/block-production/won-slots/cards/block-production-won-slots-cards.component.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
import {
1212
BlockProductionWonSlotsEpoch,
1313
} from '@shared/types/block-production/won-slots/block-production-won-slots-epoch.type';
14+
import { BlockProductionWonSlotsActions } from '@block-production/won-slots/block-production-won-slots.actions';
1415

1516
@Component({
1617
selector: 'mina-block-production-won-slots-cards',
@@ -67,9 +68,7 @@ export class BlockProductionWonSlotsCardsComponent extends StoreDispatcher imple
6768
this.card4.lastBlockTime = getTimeDiff(lastItem(slots.filter(s => s.status === BlockProductionWonSlotsStatus.Canonical))?.slotTime).diff;
6869

6970
this.card6.totalRewards = slots
70-
.filter(
71-
s => [BlockProductionWonSlotsStatus.Canonical, BlockProductionWonSlotsStatus.Orphaned, BlockProductionWonSlotsStatus.Discarded].includes(s.status),
72-
)
71+
.filter(s => [BlockProductionWonSlotsStatus.Canonical].includes(s.status))
7372
.map(s => s.coinbaseRewards + s.txFeesRewards).reduce((a, b) => a + b, 0).toFixed(0);
7473

7574
this.card6.totalRewards = isNaN(+this.card6.totalRewards) ? '0' : this.card6.totalRewards;
@@ -81,4 +80,8 @@ export class BlockProductionWonSlotsCardsComponent extends StoreDispatcher imple
8180
const secondsToAdd = minutesToAdd * 60;
8281
return timestampInSeconds + secondsToAdd;
8382
}
83+
84+
toggleSidePanel(): void {
85+
this.dispatch2(BlockProductionWonSlotsActions.toggleSidePanel());
86+
}
8487
}

frontend/src/app/features/block-production/won-slots/side-panel/block-production-won-slots-side-panel.component.html

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
<div class="h-xl fx-row-vert-cent flex-between pl-12 pr-12 f-600 p-relative">
22
<span class="secondary">{{ title }}</span>
3-
<div class="percentage success-primary">{{ percentage }}%</div>
3+
<div class="fx-row-vert-cent">
4+
@if (percentage !== null && percentage !== undefined) {
5+
<div class="percentage success-primary">{{ percentage }}%</div>
6+
}
7+
@if (isMobile) {
8+
<span class="mina-icon pointer tertiary primary-hover f-18"
9+
(click)="closeSidePanel()">close</span>
10+
}
11+
</div>
412
<div *ngIf="percentage > 0" [style.width.%]="percentage"
513
class="progress-bar p-absolute">
614
<div class="highlight p-absolute" *ngIf="percentage < 100"></div>

frontend/src/app/features/block-production/won-slots/side-panel/block-production-won-slots-side-panel.component.ts

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,15 @@ import {
1515
BlockProductionWonSlotTimes,
1616
} from '@shared/types/block-production/won-slots/block-production-won-slots-slot.type';
1717
import { getTimeDiff } from '@shared/helpers/date.helper';
18-
import { any, hasValue, noMillisFormat, ONE_THOUSAND, SecDurationConfig, toReadableDate } from '@openmina/shared';
18+
import {
19+
any,
20+
hasValue,
21+
isMobile,
22+
noMillisFormat,
23+
ONE_THOUSAND,
24+
SecDurationConfig,
25+
toReadableDate,
26+
} from '@openmina/shared';
1927
import { filter } from 'rxjs';
2028
import { BlockProductionWonSlotsActions } from '@block-production/won-slots/block-production-won-slots.actions';
2129
import { AppSelectors } from '@app/app.state';
@@ -31,12 +39,13 @@ export class BlockProductionWonSlotsSidePanelComponent extends StoreDispatcher i
3139

3240
protected readonly BlockProductionWonSlotsStatus = BlockProductionWonSlotsStatus;
3341
protected readonly config: SecDurationConfig = {
34-
includeMinutes: true,
3542
color: false,
43+
includeMinutes: true,
3644
undefinedAlternative: undefined,
3745
valueIsZeroFn: () => '<1ms',
3846
};
3947
protected readonly noMillisFormat = noMillisFormat;
48+
isMobile = isMobile();
4049
title: string;
4150

4251
slot: BlockProductionWonSlotsSlot;
@@ -178,11 +187,6 @@ export class BlockProductionWonSlotsSidePanelComponent extends StoreDispatcher i
178187
}
179188
}
180189

181-
override ngOnDestroy(): void {
182-
super.ngOnDestroy();
183-
clearInterval(this.timer);
184-
}
185-
186190
private queryServerOftenToGetTheNewSlotState(): void {
187191
const timer = setInterval(() => {
188192
if (!this.stateWhenReachedZero) {
@@ -192,4 +196,13 @@ export class BlockProductionWonSlotsSidePanelComponent extends StoreDispatcher i
192196
this.dispatch2(BlockProductionWonSlotsActions.getSlots());
193197
}, 1000);
194198
}
199+
200+
closeSidePanel(): void {
201+
this.dispatch2(BlockProductionWonSlotsActions.toggleSidePanel());
202+
}
203+
204+
override ngOnDestroy(): void {
205+
super.ngOnDestroy();
206+
clearInterval(this.timer);
207+
}
195208
}

frontend/src/app/features/dashboard/dashboard.component.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@
55
}
66

77
div {
8-
gap: 24px;
8+
gap: 8px;
99
}

0 commit comments

Comments
 (0)