Merge pull request #2899 from mempool/mononaut/audit-toggle

toggle to enable/disable block audits
This commit is contained in:
softsimon 2023-01-27 12:37:33 +04:00 committed by GitHub
commit dd3d047aa8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 88 additions and 39 deletions

View file

@ -28,8 +28,6 @@
<div class="clearfix"></div> <div class="clearfix"></div>
<div class="box" *ngIf="!error"> <div class="box" *ngIf="!error">
<div class="row"> <div class="row">
<div class="col-sm"> <div class="col-sm">
@ -54,7 +52,7 @@
<td i18n="block.weight">Weight</td> <td i18n="block.weight">Weight</td>
<td [innerHTML]="'&lrm;' + (block.weight | wuBytes: 2)"></td> <td [innerHTML]="'&lrm;' + (block.weight | wuBytes: 2)"></td>
</tr> </tr>
<tr *ngIf="!auditDataMissing && indexingAvailable"> <tr *ngIf="auditAvailable">
<td i18n="block.health">Block health</td> <td i18n="block.health">Block health</td>
<td> <td>
<span <span
@ -88,21 +86,21 @@
<tr> <tr>
<td colspan="2"><span class="skeleton-loader"></span></td> <td colspan="2"><span class="skeleton-loader"></span></td>
</tr> </tr>
<tr *ngIf="!auditDataMissing && indexingAvailable"> <tr *ngIf="showAudit">
<td colspan="2"><span class="skeleton-loader"></span></td> <td colspan="2"><span class="skeleton-loader"></span></td>
</tr> </tr>
</ng-template> </ng-template>
<ng-container *ngIf="isMobile || (webGlEnabled && (auditDataMissing || !indexingAvailable)); then restOfTable;"></ng-container> <ng-container *ngIf="isMobile || (webGlEnabled && !showAudit); then restOfTable;"></ng-container>
</tbody> </tbody>
</table> </table>
</div> </div>
<div class="col-sm"> <div class="col-sm">
<table class="table table-borderless table-striped" *ngIf="!isMobile && !(webGlEnabled && (auditDataMissing || !indexingAvailable))"> <table class="table table-borderless table-striped" *ngIf="!isMobile && !(webGlEnabled && !showAudit)">
<tbody> <tbody>
<ng-container *ngTemplateOutlet="restOfTable"></ng-container> <ng-container *ngTemplateOutlet="restOfTable"></ng-container>
</tbody> </tbody>
</table> </table>
<div class="col-sm chart-container" *ngIf="webGlEnabled && (!indexingAvailable || auditDataMissing)"> <div class="col-sm chart-container" *ngIf="webGlEnabled && !showAudit">
<app-block-overview-graph <app-block-overview-graph
#blockGraphActual #blockGraphActual
[isLoading]="isLoadingOverview" [isLoading]="isLoadingOverview"
@ -199,13 +197,14 @@
</ng-template> </ng-template>
</ng-template> </ng-template>
<ng-container *ngIf="showAudit">
<span id="overview"></span> <span id="overview"></span>
<br> <br>
</ng-container>
<!-- VISUALIZATIONS --> <!-- VISUALIZATIONS -->
<div class="box" *ngIf="!error && webGlEnabled && indexingAvailable && !auditDataMissing"> <div class="box" *ngIf="!error && webGlEnabled && showAudit">
<div class="nav nav-tabs" *ngIf="isMobile && auditEnabled"> <div class="nav nav-tabs" *ngIf="isMobile && showAudit">
<a class="nav-link" [class.active]="mode === 'projected'" i18n="block.projected" <a class="nav-link" [class.active]="mode === 'projected'" i18n="block.projected"
fragment="projected" (click)="changeMode('projected')">Projected</a> fragment="projected" (click)="changeMode('projected')">Projected</a>
<a class="nav-link" [class.active]="mode === 'actual'" i18n="block.actual" <a class="nav-link" [class.active]="mode === 'actual'" i18n="block.actual"
@ -217,7 +216,7 @@
<div class="block-graph-wrapper"> <div class="block-graph-wrapper">
<app-block-overview-graph #blockGraphProjected [isLoading]="isLoadingOverview" [resolution]="75" <app-block-overview-graph #blockGraphProjected [isLoading]="isLoadingOverview" [resolution]="75"
[blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" [blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx"
(txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="!isMobile && !auditEnabled"></app-block-overview-graph> (txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="!isMobile && !showAudit"></app-block-overview-graph>
<ng-container *ngIf="!isMobile || mode !== 'actual'; else emptyBlockInfo"></ng-container> <ng-container *ngIf="!isMobile || mode !== 'actual'; else emptyBlockInfo"></ng-container>
</div> </div>
</div> </div>
@ -226,7 +225,7 @@
<div class="block-graph-wrapper"> <div class="block-graph-wrapper">
<app-block-overview-graph #blockGraphActual [isLoading]="isLoadingOverview" [resolution]="75" <app-block-overview-graph #blockGraphActual [isLoading]="isLoadingOverview" [resolution]="75"
[blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" mode="mined" [blockLimit]="stateService.blockVSize" [orientation]="'top'" [flip]="false" [mirrorTxid]="hoverTx" mode="mined"
(txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="isMobile && !auditEnabled"></app-block-overview-graph> (txClickEvent)="onTxClick($event)" (txHoverEvent)="onTxHover($event)" [unavailable]="isMobile && !showAudit"></app-block-overview-graph>
<ng-container *ngTemplateOutlet="emptyBlockInfo"></ng-container> <ng-container *ngTemplateOutlet="emptyBlockInfo"></ng-container>
</div> </div>
</div> </div>
@ -280,8 +279,22 @@
</div> </div>
</div> </div>
<div class="text-right mt-3"> <div class="text-right mt-3 toggle-btns">
<button type="button" class="btn btn-outline-info btn-sm btn-details" (click)="toggleShowDetails()" i18n="transaction.details|Transaction Details">Details</button> <button
*ngIf="webGlEnabled && auditAvailable"
type="button"
class="btn btn-outline-info btn-sm btn-audit"
[class.active]="auditModeEnabled"
(click)="toggleAuditMode()"
i18n="block.toggle-audit|Toggle Audit"
>Audit</button>
<button
type="button"
class="btn btn-outline-info btn-sm btn-details"
[class.active]="showDetails"
(click)="toggleShowDetails()"
i18n="transaction.details|Transaction Details"
>Details</button>
</div> </div>
<div #blockTxTitle id="block-tx-title" class="block-tx-title"> <div #blockTxTitle id="block-tx-title" class="block-tx-title">

View file

@ -75,14 +75,19 @@ h1 {
} }
} }
.btn-details { .toggle-btns {
position: relative; position: relative;
z-index: 2;
top: 7px; top: 7px;
@media (min-width: 550px) { @media (min-width: 550px) {
top: 0px; top: 0px;
} }
} }
.btn-audit {
margin-right: .5em;
}
.block-tx-title { .block-tx-title {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;

View file

@ -58,8 +58,9 @@ export class BlockComponent implements OnInit, OnDestroy {
overviewError: any = null; overviewError: any = null;
webGlEnabled = true; webGlEnabled = true;
indexingAvailable = false; indexingAvailable = false;
auditEnabled = true; auditModeEnabled: boolean = !this.stateService.hideAudit.value;
auditDataMissing: boolean; auditAvailable = true;
showAudit: boolean;
isMobile = window.innerWidth <= 767.98; isMobile = window.innerWidth <= 767.98;
hoverTx: string; hoverTx: string;
numMissing: number = 0; numMissing: number = 0;
@ -79,6 +80,7 @@ export class BlockComponent implements OnInit, OnDestroy {
timeLtrSubscription: Subscription; timeLtrSubscription: Subscription;
timeLtr: boolean; timeLtr: boolean;
childChangeSubscription: Subscription; childChangeSubscription: Subscription;
auditPrefSubscription: Subscription;
@ViewChildren('blockGraphProjected') blockGraphProjected: QueryList<BlockOverviewGraphComponent>; @ViewChildren('blockGraphProjected') blockGraphProjected: QueryList<BlockOverviewGraphComponent>;
@ViewChildren('blockGraphActual') blockGraphActual: QueryList<BlockOverviewGraphComponent>; @ViewChildren('blockGraphActual') blockGraphActual: QueryList<BlockOverviewGraphComponent>;
@ -108,7 +110,12 @@ export class BlockComponent implements OnInit, OnDestroy {
}); });
this.indexingAvailable = (this.stateService.env.BASE_MODULE === 'mempool' && this.stateService.env.MINING_DASHBOARD === true); this.indexingAvailable = (this.stateService.env.BASE_MODULE === 'mempool' && this.stateService.env.MINING_DASHBOARD === true);
this.auditEnabled = this.indexingAvailable; this.setAuditAvailable(this.indexingAvailable);
this.auditPrefSubscription = this.stateService.hideAudit.subscribe((hide) => {
this.auditModeEnabled = !hide;
this.showAudit = this.auditAvailable && this.auditModeEnabled;
});
this.txsLoadingStatus$ = this.route.paramMap this.txsLoadingStatus$ = this.route.paramMap
.pipe( .pipe(
@ -138,11 +145,11 @@ export class BlockComponent implements OnInit, OnDestroy {
this.page = 1; this.page = 1;
this.error = undefined; this.error = undefined;
this.fees = undefined; this.fees = undefined;
this.auditDataMissing = false; this.stateService.markBlock$.next({});
if (history.state.data && history.state.data.blockHeight) { if (history.state.data && history.state.data.blockHeight) {
this.blockHeight = history.state.data.blockHeight; this.blockHeight = history.state.data.blockHeight;
this.updateAuditDataMissingFromBlockHeight(this.blockHeight); this.updateAuditAvailableFromBlockHeight(this.blockHeight);
} }
let isBlockHeight = false; let isBlockHeight = false;
@ -155,7 +162,7 @@ export class BlockComponent implements OnInit, OnDestroy {
if (history.state.data && history.state.data.block) { if (history.state.data && history.state.data.block) {
this.blockHeight = history.state.data.block.height; this.blockHeight = history.state.data.block.height;
this.updateAuditDataMissingFromBlockHeight(this.blockHeight); this.updateAuditAvailableFromBlockHeight(this.blockHeight);
return of(history.state.data.block); return of(history.state.data.block);
} else { } else {
this.isLoadingBlock = true; this.isLoadingBlock = true;
@ -217,7 +224,7 @@ export class BlockComponent implements OnInit, OnDestroy {
this.apiService.getBlockAudit$(block.previousblockhash); this.apiService.getBlockAudit$(block.previousblockhash);
}, 100); }, 100);
} }
this.updateAuditDataMissingFromBlockHeight(block.height); this.updateAuditAvailableFromBlockHeight(block.height);
this.block = block; this.block = block;
this.blockHeight = block.height; this.blockHeight = block.height;
this.lastBlockHeight = this.blockHeight; this.lastBlockHeight = this.blockHeight;
@ -369,10 +376,9 @@ export class BlockComponent implements OnInit, OnDestroy {
for (const tx of blockAudit.transactions) { for (const tx of blockAudit.transactions) {
inBlock[tx.txid] = true; inBlock[tx.txid] = true;
} }
this.auditEnabled = true; this.setAuditAvailable(true);
} else { } else {
this.auditEnabled = false; this.setAuditAvailable(false);
this.auditDataMissing = true;
} }
return blockAudit; return blockAudit;
}), }),
@ -381,6 +387,7 @@ export class BlockComponent implements OnInit, OnDestroy {
this.error = err; this.error = err;
this.isLoadingOverview = false; this.isLoadingOverview = false;
this.isLoadingAudit = false; this.isLoadingAudit = false;
this.setAuditAvailable(false);
return of(null); return of(null);
}), }),
).subscribe((blockAudit) => { ).subscribe((blockAudit) => {
@ -440,6 +447,7 @@ export class BlockComponent implements OnInit, OnDestroy {
this.networkChangedSubscription.unsubscribe(); this.networkChangedSubscription.unsubscribe();
this.queryParamsSubscription.unsubscribe(); this.queryParamsSubscription.unsubscribe();
this.timeLtrSubscription.unsubscribe(); this.timeLtrSubscription.unsubscribe();
this.auditSubscription.unsubscribe();
this.unsubscribeNextBlockSubscriptions(); this.unsubscribeNextBlockSubscriptions();
this.childChangeSubscription.unsubscribe(); this.childChangeSubscription.unsubscribe();
} }
@ -595,21 +603,30 @@ export class BlockComponent implements OnInit, OnDestroy {
} }
} }
updateAuditDataMissingFromBlockHeight(blockHeight: number): void { setAuditAvailable(available: boolean): void {
this.auditAvailable = available;
this.showAudit = this.auditAvailable && this.auditModeEnabled;
}
toggleAuditMode(): void {
this.stateService.hideAudit.next(this.auditModeEnabled);
}
updateAuditAvailableFromBlockHeight(blockHeight: number): void {
switch (this.stateService.network) { switch (this.stateService.network) {
case 'testnet': case 'testnet':
if (blockHeight < this.stateService.env.TESTNET_BLOCK_AUDIT_START_HEIGHT) { if (blockHeight < this.stateService.env.TESTNET_BLOCK_AUDIT_START_HEIGHT) {
this.auditDataMissing = true; this.setAuditAvailable(true);
} }
break; break;
case 'signet': case 'signet':
if (blockHeight < this.stateService.env.SIGNET_BLOCK_AUDIT_START_HEIGHT) { if (blockHeight < this.stateService.env.SIGNET_BLOCK_AUDIT_START_HEIGHT) {
this.auditDataMissing = true; this.setAuditAvailable(true);
} }
break; break;
default: default:
if (blockHeight < this.stateService.env.MAINNET_BLOCK_AUDIT_START_HEIGHT) { if (blockHeight < this.stateService.env.MAINNET_BLOCK_AUDIT_START_HEIGHT) {
this.auditDataMissing = true; this.setAuditAvailable(true);
} }
} }
} }

View file

@ -118,6 +118,7 @@ export class StateService {
blockScrolling$: Subject<boolean> = new Subject<boolean>(); blockScrolling$: Subject<boolean> = new Subject<boolean>();
timeLtr: BehaviorSubject<boolean>; timeLtr: BehaviorSubject<boolean>;
hideFlow: BehaviorSubject<boolean>; hideFlow: BehaviorSubject<boolean>;
hideAudit: BehaviorSubject<boolean>;
constructor( constructor(
@Inject(PLATFORM_ID) private platformId: any, @Inject(PLATFORM_ID) private platformId: any,
@ -177,6 +178,12 @@ export class StateService {
this.storageService.removeItem('flow-preference'); this.storageService.removeItem('flow-preference');
} }
}); });
const savedAuditPreference = this.storageService.getValue('audit-preference');
this.hideAudit = new BehaviorSubject<boolean>(savedAuditPreference === 'hide');
this.hideAudit.subscribe((hide) => {
this.storageService.setValue('audit-preference', hide ? 'hide' : 'show');
});
} }
setNetworkBasedonUrl(url: string) { setNetworkBasedonUrl(url: string) {

View file

@ -1,8 +1,8 @@
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<span style="margin-bottom: 0.5rem">{{ textLeft }}</span>&nbsp; <span>{{ textLeft }}</span>&nbsp;
<label class="switch"> <label class="switch" style="margin-bottom: 0;">
<input type="checkbox" [checked]="checked" (change)="onToggleStatusChanged($event)"> <input type="checkbox" [checked]="checked" (change)="onToggleStatusChanged($event)">
<span class="slider round"></span> <span class="slider round" [class.animate]="animate"></span>
</label> </label>
&nbsp;<span style="margin-bottom: 0.5rem">{{ textRight }}</span> &nbsp;<span>{{ textRight }}</span>
</div> </div>

View file

@ -22,8 +22,6 @@
right: 0; right: 0;
bottom: 0; bottom: 0;
background-color: #ccc; background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
} }
.slider:before { .slider:before {
@ -34,6 +32,9 @@
left: 2px; left: 2px;
bottom: 2px; bottom: 2px;
background-color: white; background-color: white;
}
.slider.animate, .slider.animate:before {
-webkit-transition: .4s; -webkit-transition: .4s;
transition: .4s; transition: .4s;
} }

View file

@ -1,4 +1,4 @@
import { Component, Input, Output, ChangeDetectionStrategy, EventEmitter, AfterViewInit } from '@angular/core'; import { Component, Input, Output, ChangeDetectionStrategy, EventEmitter, AfterViewInit, ChangeDetectorRef } from '@angular/core';
@Component({ @Component({
selector: 'app-toggle', selector: 'app-toggle',
@ -11,9 +11,15 @@ export class ToggleComponent implements AfterViewInit {
@Input() textLeft: string; @Input() textLeft: string;
@Input() textRight: string; @Input() textRight: string;
@Input() checked: boolean = false; @Input() checked: boolean = false;
animate: boolean = false;
constructor(
private cd: ChangeDetectorRef,
) { }
ngAfterViewInit(): void { ngAfterViewInit(): void {
this.toggleStatusChanged.emit(false); this.animate = true;
setTimeout(() => { this.cd.markForCheck()});
} }
onToggleStatusChanged(e): void { onToggleStatusChanged(e): void {