Merge branch 'master' into natsoni/hide-featured-assets-testnet
This commit is contained in:
commit
5537e79640
@ -98,11 +98,9 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template [ngIf]="error">
|
<ng-template [ngIf]="error">
|
||||||
<div class="text-center">
|
<app-http-error [error]="error">
|
||||||
Error loading address data.
|
<span i18n="address.error.loading-address-data">Error loading address data.</span>
|
||||||
<br>
|
</app-http-error>
|
||||||
<i>{{ error.error }}</i>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -156,7 +156,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
|||||||
let tooltip = `<b style="color: white; margin-left: 2px">${formatterXAxis(this.locale, this.timespan, parseInt(ticks[0].axisValue, 10))}</b><br>`;
|
let tooltip = `<b style="color: white; margin-left: 2px">${formatterXAxis(this.locale, this.timespan, parseInt(ticks[0].axisValue, 10))}</b><br>`;
|
||||||
|
|
||||||
if (ticks[0].data[1] > 10_000_000) {
|
if (ticks[0].data[1] > 10_000_000) {
|
||||||
tooltip += `${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(ticks[0].data[1] / 100_000_000, this.locale, '1.0-0')} BTC<br>`;
|
tooltip += `${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(ticks[0].data[1] / 100_000_000, this.locale, '1.0-8')} BTC<br>`;
|
||||||
} else {
|
} else {
|
||||||
tooltip += `${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(ticks[0].data[1], this.locale, '1.0-0')} sats<br>`;
|
tooltip += `${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(ticks[0].data[1], this.locale, '1.0-0')} sats<br>`;
|
||||||
}
|
}
|
||||||
|
@ -135,11 +135,10 @@
|
|||||||
|
|
||||||
<ng-template [ngIf]="error">
|
<ng-template [ngIf]="error">
|
||||||
<br>
|
<br>
|
||||||
<div class="text-center">
|
<ng-template [ngIf]="error.status === 413 || error.status === 405 || error.status === 504" [ngIfElse]="displayServerError">
|
||||||
<span i18n="address.error.loading-address-data">Error loading address data.</span>
|
<div class="text-center">
|
||||||
<br>
|
<span i18n="address.error.loading-address-data">Error loading address data.</span>
|
||||||
<ng-template #displayServerError><i class="small">({{ error.error }})</i></ng-template>
|
<br>
|
||||||
<ng-template [ngIf]="error.status === 413 || error.status === 405 || error.status === 504" [ngIfElse]="displayServerError">
|
|
||||||
<ng-container i18n="Electrum server limit exceeded error">
|
<ng-container i18n="Electrum server limit exceeded error">
|
||||||
<i>There many transactions on this address, more than your backend can handle. See more on <a href="/docs/faq#address-lookup-issues">setting up a stronger backend</a>.</i>
|
<i>There many transactions on this address, more than your backend can handle. See more on <a href="/docs/faq#address-lookup-issues">setting up a stronger backend</a>.</i>
|
||||||
<br><br>
|
<br><br>
|
||||||
@ -150,9 +149,14 @@
|
|||||||
<br>
|
<br>
|
||||||
<a href="http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/address/{{ addressString }}" target="_blank">http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/address/{{ addressString }}</a>
|
<a href="http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/address/{{ addressString }}" target="_blank">http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/address/{{ addressString }}</a>
|
||||||
<br><br>
|
<br><br>
|
||||||
<i class="small">({{ error.error }})</i>
|
<i class="small">({{ error | httpErrorMsg }})</i>
|
||||||
</ng-template>
|
</div>
|
||||||
</div>
|
</ng-template>
|
||||||
|
<ng-template #displayServerError>
|
||||||
|
<app-http-error [error]="error">
|
||||||
|
<span i18n="address.error.loading-address-data">Error loading address data.</span>
|
||||||
|
</app-http-error>
|
||||||
|
</ng-template>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -140,10 +140,22 @@ export class AddressComponent implements OnInit, OnDestroy {
|
|||||||
if (!fetchTxs.length) {
|
if (!fetchTxs.length) {
|
||||||
return of([]);
|
return of([]);
|
||||||
}
|
}
|
||||||
return this.apiService.getTransactionTimes$(fetchTxs);
|
return this.apiService.getTransactionTimes$(fetchTxs).pipe(
|
||||||
|
catchError((err) => {
|
||||||
|
this.isLoadingAddress = false;
|
||||||
|
this.isLoadingTransactions = false;
|
||||||
|
this.error = err;
|
||||||
|
this.seoService.logSoft404();
|
||||||
|
console.log(err);
|
||||||
|
return of([]);
|
||||||
|
})
|
||||||
|
);
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
.subscribe((times: number[]) => {
|
.subscribe((times: number[] | null) => {
|
||||||
|
if (!times) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
times.forEach((time, index) => {
|
times.forEach((time, index) => {
|
||||||
this.tempTransactions[this.timeTxIndexes[index]].firstSeen = time;
|
this.tempTransactions[this.timeTxIndexes[index]].firstSeen = time;
|
||||||
});
|
});
|
||||||
|
@ -146,13 +146,10 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template [ngIf]="error">
|
<ng-template [ngIf]="error">
|
||||||
<div class="text-center">
|
<app-http-error [error]="error">
|
||||||
<span i18n="asset.error.loading-asset-data">Error loading asset data.</span>
|
<span i18n="asset.error.loading-asset-data">Error loading asset data.</span>
|
||||||
<br>
|
</app-http-error>
|
||||||
<i>{{ error.error }}</i>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<br>
|
<br>
|
||||||
|
@ -46,9 +46,7 @@
|
|||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template [ngIf]="error">
|
<ng-template [ngIf]="error">
|
||||||
<div class="text-center">
|
<app-http-error [error]="error">
|
||||||
<ng-container i18n="Asset data load error">Error loading assets data.</ng-container>
|
<span i18n="Asset data load error">Error loading assets data.</span>
|
||||||
<br>
|
</app-http-error>
|
||||||
<i>{{ error.error }}</i>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@ -61,10 +61,10 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{{ activeFilters.rbf }}
|
{{ activeFilters.rbf }}
|
||||||
<tr *ngIf="(!auditEnabled && tx && tx.status === 'accelerated') || filters.length">
|
<tr *ngIf="(!auditEnabled && tx && (tx.status === 'accelerated' || tx.acc || acceleration)) || filters.length">
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<div class="tags mt-2" [class.any-mode]="filterMode === 'or'">
|
<div class="tags mt-2" [class.any-mode]="filterMode === 'or'">
|
||||||
<span *ngIf="!auditEnabled && tx && tx.status === 'accelerated'" class="badge badge-accelerated" i18n="transaction.audit.accelerated">Accelerated</span>
|
<span *ngIf="!auditEnabled && tx && (tx.status === 'accelerated' || tx.acc || acceleration)" class="badge badge-accelerated" i18n="transaction.audit.accelerated">Accelerated</span>
|
||||||
<ng-container *ngFor="let filter of filters;">
|
<ng-container *ngFor="let filter of filters;">
|
||||||
<span class="btn badge filter-tag" [class.matching]="activeFilters[filter.key]">{{ filter.label }}</span>
|
<span class="btn badge filter-tag" [class.matching]="activeFilters[filter.key]">{{ filter.label }}</span>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
@ -9,8 +9,8 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
min-width: 320px;
|
min-width: 340px;
|
||||||
max-width: 320px;
|
max-width: 340px;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: 11;
|
z-index: 11;
|
||||||
|
|
||||||
|
@ -61,8 +61,8 @@ export class BlockOverviewTooltipComponent implements OnChanges {
|
|||||||
this.vsize = this.tx.vsize || 1;
|
this.vsize = this.tx.vsize || 1;
|
||||||
this.feeRate = this.fee / this.vsize;
|
this.feeRate = this.fee / this.vsize;
|
||||||
this.effectiveRate = this.tx.rate;
|
this.effectiveRate = this.tx.rate;
|
||||||
this.acceleration = this.tx.acc;
|
|
||||||
const txFlags = BigInt(this.tx.flags) || 0n;
|
const txFlags = BigInt(this.tx.flags) || 0n;
|
||||||
|
this.acceleration = this.tx.acc || (txFlags & TransactionFlags.acceleration);
|
||||||
this.hasEffectiveRate = Math.abs((this.fee / this.vsize) - this.effectiveRate) > 0.05
|
this.hasEffectiveRate = Math.abs((this.fee / this.vsize) - this.effectiveRate) > 0.05
|
||||||
|| (txFlags && (txFlags & (TransactionFlags.cpfp_child | TransactionFlags.cpfp_parent)) > 0n);
|
|| (txFlags && (txFlags & (TransactionFlags.cpfp_child | TransactionFlags.cpfp_parent)) > 0n);
|
||||||
this.filters = this.tx.flags ? toFilters(txFlags).filter(f => f.tooltip) : [];
|
this.filters = this.tx.flags ? toFilters(txFlags).filter(f => f.tooltip) : [];
|
||||||
|
@ -338,14 +338,12 @@
|
|||||||
<app-transactions-list [transactions]="transactions" [paginated]="true" [blockTime]="block.timestamp"></app-transactions-list>
|
<app-transactions-list [transactions]="transactions" [paginated]="true" [blockTime]="block.timestamp"></app-transactions-list>
|
||||||
|
|
||||||
<ng-template [ngIf]="transactionsError">
|
<ng-template [ngIf]="transactionsError">
|
||||||
<div class="text-center">
|
<br>
|
||||||
<br>
|
<app-http-error [error]="transactionsError">
|
||||||
<span i18n="error.general-loading-data">Error loading data.</span>
|
<span i18n="error.general-loading-data">Error loading data.</span>
|
||||||
<br><br>
|
</app-http-error>
|
||||||
<i>{{ transactionsError.status }}: {{ transactionsError.error }}</i>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
<br>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template [ngIf]="isLoadingTransactions && !transactionsError">
|
<ng-template [ngIf]="isLoadingTransactions && !transactionsError">
|
||||||
@ -378,11 +376,9 @@
|
|||||||
<br>
|
<br>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<ng-template [ngIf]="error">
|
<ng-template [ngIf]="error">
|
||||||
<div class="text-center">
|
<app-http-error [error]="error">
|
||||||
<span i18n="error.general-loading-data">Error loading data.</span>
|
<span i18n="error.general-loading-data">Error loading data.</span>
|
||||||
<br><br>
|
</app-http-error>
|
||||||
<i>{{ error.status }}: {{ error.error }}</i>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #headerLoader>
|
<ng-template #headerLoader>
|
||||||
|
@ -56,7 +56,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,15 @@
|
|||||||
<div *ngIf="showTitle" class="main-title" i18n="dashboard.difficulty-adjustment">Difficulty Adjustment</div>
|
<div *ngIf="showTitle && mode === 'difficulty'" class="main-title" i18n="dashboard.difficulty-adjustment">Difficulty Adjustment</div>
|
||||||
|
<div *ngIf="showTitle && mode === 'halving'" class="main-title" i18n="dashboard.halving-countdown">Halving Countdown</div>
|
||||||
<div class="card-wrapper">
|
<div class="card-wrapper">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body more-padding">
|
<div class="widget-toggler">
|
||||||
|
<a href="" (click)="setMode('difficulty')" class="toggler-option"
|
||||||
|
[ngClass]="{'inactive': mode === 'difficulty'}"><small i18n="statistics.average-small">difficulty</small></a>
|
||||||
|
<span style="color: #ffffff66; font-size: 8px"> | </span>
|
||||||
|
<a href="" (click)="setMode('halving')" class="toggler-option"
|
||||||
|
[ngClass]="{'inactive': mode === 'halving'}"><small i18n="statistics.median-small">halving</small></a>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="mode === 'difficulty'; else halving" class="card-body more-padding">
|
||||||
<div class="difficulty-adjustment-container" *ngIf="(isLoadingWebSocket$ | async) === false && (difficultyEpoch$ | async) as epochData; else loadingDifficulty">
|
<div class="difficulty-adjustment-container" *ngIf="(isLoadingWebSocket$ | async) === false && (difficultyEpoch$ | async) as epochData; else loadingDifficulty">
|
||||||
<div class="epoch-progress">
|
<div class="epoch-progress">
|
||||||
<svg #epochSvg class="epoch-blocks" height="22px" width="100%" viewBox="0 0 224 9" shape-rendering="crispEdges" preserveAspectRatio="none">
|
<svg #epochSvg class="epoch-blocks" height="22px" width="100%" viewBox="0 0 224 9" shape-rendering="crispEdges" preserveAspectRatio="none">
|
||||||
@ -76,6 +84,52 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ng-template #halving>
|
||||||
|
<div class="card-body more-padding">
|
||||||
|
<div class="difficulty-adjustment-container halving" *ngIf="(isLoadingWebSocket$ | async) === false && (difficultyEpoch$ | async) as epochData; else loadingDifficulty">
|
||||||
|
<div class="halving-progress">
|
||||||
|
<div class="background"></div>
|
||||||
|
<div class="remaining" [style]="{ left: ((210000 - epochData.blocksUntilHalving) / 2100).toFixed(2) + '%' }"></div>
|
||||||
|
<div class="label">
|
||||||
|
{{ ((210000 - epochData.blocksUntilHalving) / 2100).toFixed(2) }}%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="difficulty-stats">
|
||||||
|
<div class="item">
|
||||||
|
<div class="card-text bigger">
|
||||||
|
<app-btc [satoshis]="312500000"></app-btc>
|
||||||
|
</div>
|
||||||
|
<div class="symbol">
|
||||||
|
<span i18n="difficulty-box.new-subsidy">New subsidy</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<div class="card-text">
|
||||||
|
{{ epochData.blocksUntilHalving | number }}
|
||||||
|
</div>
|
||||||
|
<div class="symbol">
|
||||||
|
<span *ngIf="epochData.blocksUntilHalving > 1" i18n="shared.blocks-remaining">Blocks remaining</span>
|
||||||
|
<span *ngIf="epochData.blocksUntilHalving === 1" i18n="shared.block-remaining">Block remaining</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="item">
|
||||||
|
<div class="card-text" i18n-ngbTooltip="mining.average-fee" placement="bottom">
|
||||||
|
<span>{{ epochData.timeUntilHalving | date }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="symbol" *ngIf="epochData.blocksUntilHalving === 1; else approxTime">
|
||||||
|
<app-time kind="until" [time]="epochData.adjustedTimeAvg + now" [fastRender]="false" [fixedRender]="true" [precision]="1" minUnit="minute"></app-time>
|
||||||
|
</div>
|
||||||
|
<ng-template #approxTime>
|
||||||
|
<div class="symbol">
|
||||||
|
<app-time kind="until" [time]="epochData.timeUntilHalving" [fastRender]="false" [fixedRender]="true" [precision]="0" [numUnits]="2" [units]="['year', 'day', 'hour', 'minute']"></app-time>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #loadingDifficulty>
|
<ng-template #loadingDifficulty>
|
||||||
<div class="epoch-progress">
|
<div class="epoch-progress">
|
||||||
<div class="skeleton-loader"></div>
|
<div class="skeleton-loader"></div>
|
||||||
|
@ -168,7 +168,7 @@
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.epoch-progress {
|
.epoch-progress, .halving-progress {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 22px;
|
height: 22px;
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
@ -212,4 +212,43 @@
|
|||||||
}
|
}
|
||||||
.blocks-behind {
|
.blocks-behind {
|
||||||
color: #D81B60;
|
color: #D81B60;
|
||||||
|
}
|
||||||
|
|
||||||
|
.halving-progress {
|
||||||
|
position: relative;
|
||||||
|
.background, .remaining {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
.background {
|
||||||
|
background: linear-gradient(to right, #105fb0, #9339f4);
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.remaining {
|
||||||
|
background: #2d3348;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
position: relative;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget-toggler {
|
||||||
|
font-size: 12px;
|
||||||
|
position: absolute;
|
||||||
|
top: -20px;
|
||||||
|
right: 3px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggler-option {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inactive {
|
||||||
|
color: #ffffff66;
|
||||||
}
|
}
|
@ -51,6 +51,10 @@ export class DifficultyComponent implements OnInit {
|
|||||||
isLoadingWebSocket$: Observable<boolean>;
|
isLoadingWebSocket$: Observable<boolean>;
|
||||||
difficultyEpoch$: Observable<EpochProgress>;
|
difficultyEpoch$: Observable<EpochProgress>;
|
||||||
|
|
||||||
|
mode: 'difficulty' | 'halving' = 'difficulty';
|
||||||
|
userSelectedMode: boolean = false;
|
||||||
|
|
||||||
|
now: number = Date.now();
|
||||||
epochStart: number;
|
epochStart: number;
|
||||||
currentHeight: number;
|
currentHeight: number;
|
||||||
currentIndex: number;
|
currentIndex: number;
|
||||||
@ -101,6 +105,11 @@ export class DifficultyComponent implements OnInit {
|
|||||||
const timeUntilHalving = new Date().getTime() + (blocksUntilHalving * 600000);
|
const timeUntilHalving = new Date().getTime() + (blocksUntilHalving * 600000);
|
||||||
const newEpochStart = Math.floor(this.stateService.latestBlockHeight / EPOCH_BLOCK_LENGTH) * EPOCH_BLOCK_LENGTH;
|
const newEpochStart = Math.floor(this.stateService.latestBlockHeight / EPOCH_BLOCK_LENGTH) * EPOCH_BLOCK_LENGTH;
|
||||||
const newExpectedHeight = Math.floor(newEpochStart + da.expectedBlocks);
|
const newExpectedHeight = Math.floor(newEpochStart + da.expectedBlocks);
|
||||||
|
this.now = new Date().getTime();
|
||||||
|
|
||||||
|
if (blocksUntilHalving < da.remainingBlocks && !this.userSelectedMode) {
|
||||||
|
this.mode = 'halving';
|
||||||
|
}
|
||||||
|
|
||||||
if (newEpochStart !== this.epochStart || newExpectedHeight !== this.expectedHeight || this.currentHeight !== this.stateService.latestBlockHeight) {
|
if (newEpochStart !== this.epochStart || newExpectedHeight !== this.expectedHeight || this.currentHeight !== this.stateService.latestBlockHeight) {
|
||||||
this.epochStart = newEpochStart;
|
this.epochStart = newEpochStart;
|
||||||
@ -194,6 +203,12 @@ export class DifficultyComponent implements OnInit {
|
|||||||
return shapes;
|
return shapes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setMode(mode: 'difficulty' | 'halving'): boolean {
|
||||||
|
this.mode = mode;
|
||||||
|
this.userSelectedMode = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@HostListener('pointerdown', ['$event'])
|
@HostListener('pointerdown', ['$event'])
|
||||||
onPointerDown(event): void {
|
onPointerDown(event): void {
|
||||||
if (this.epochSvgElement?.nativeElement?.contains(event.target)) {
|
if (this.epochSvgElement?.nativeElement?.contains(event.target)) {
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.chart {
|
.chart {
|
||||||
|
margin-top: 10px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
|
@ -65,7 +65,9 @@ export class PoolComponent implements OnInit {
|
|||||||
.pipe(
|
.pipe(
|
||||||
switchMap((data) => {
|
switchMap((data) => {
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
this.prepareChartOptions(data.map(val => [val.timestamp * 1000, val.avgHashrate]));
|
const hashrate = data.map(val => [val.timestamp * 1000, val.avgHashrate]);
|
||||||
|
const share = data.map(val => [val.timestamp * 1000, val.share * 100]);
|
||||||
|
this.prepareChartOptions(hashrate, share);
|
||||||
return [slug];
|
return [slug];
|
||||||
}),
|
}),
|
||||||
catchError(() => {
|
catchError(() => {
|
||||||
@ -130,9 +132,9 @@ export class PoolComponent implements OnInit {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareChartOptions(data) {
|
prepareChartOptions(hashrate, share) {
|
||||||
let title: object;
|
let title: object;
|
||||||
if (data.length <= 1) {
|
if (hashrate.length <= 1) {
|
||||||
title = {
|
title = {
|
||||||
textStyle: {
|
textStyle: {
|
||||||
color: 'grey',
|
color: 'grey',
|
||||||
@ -177,26 +179,57 @@ export class PoolComponent implements OnInit {
|
|||||||
},
|
},
|
||||||
borderColor: '#000',
|
borderColor: '#000',
|
||||||
formatter: function (ticks: any[]) {
|
formatter: function (ticks: any[]) {
|
||||||
let hashratePowerOfTen: any = selectPowerOfTen(1);
|
let hashrateString = '';
|
||||||
let hashrate = ticks[0].data[1];
|
let dominanceString = '';
|
||||||
|
|
||||||
hashratePowerOfTen = selectPowerOfTen(ticks[0].data[1], 10);
|
|
||||||
hashrate = ticks[0].data[1] / hashratePowerOfTen.divider;
|
|
||||||
|
|
||||||
|
for (const tick of ticks) {
|
||||||
|
if (tick.seriesIndex === 0) {
|
||||||
|
let hashratePowerOfTen = selectPowerOfTen(tick.data[1], 10);
|
||||||
|
let hashrateData = tick.data[1] / hashratePowerOfTen.divider;
|
||||||
|
hashrateString = `${tick.marker} ${tick.seriesName}: ${formatNumber(hashrateData, this.locale, '1.0-0')} ${hashratePowerOfTen.unit}H/s<br>`;
|
||||||
|
} else if (tick.seriesIndex === 1) {
|
||||||
|
dominanceString = `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.0-2')}%`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<b style="color: white; margin-left: 18px">${ticks[0].axisValueLabel}</b><br>
|
<b style="color: white; margin-left: 18px">${ticks[0].axisValueLabel}</b><br>
|
||||||
<span>${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(hashrate, this.locale, '1.0-0')} ${hashratePowerOfTen.unit}H/s</span><br>
|
<span>${hashrateString}</span>
|
||||||
|
<span>${dominanceString}</span>
|
||||||
`;
|
`;
|
||||||
}.bind(this)
|
}.bind(this)
|
||||||
},
|
},
|
||||||
xAxis: data.length <= 1 ? undefined : {
|
xAxis: hashrate.length <= 1 ? undefined : {
|
||||||
type: 'time',
|
type: 'time',
|
||||||
splitNumber: (this.isMobile()) ? 5 : 10,
|
splitNumber: (this.isMobile()) ? 5 : 10,
|
||||||
axisLabel: {
|
axisLabel: {
|
||||||
hideOverlap: true,
|
hideOverlap: true,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
yAxis: data.length <= 1 ? undefined : [
|
legend: {
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
name: $localize`:mining.hashrate:Hashrate`,
|
||||||
|
inactiveColor: 'rgb(110, 112, 121)',
|
||||||
|
textStyle: {
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
icon: 'roundRect',
|
||||||
|
itemStyle: {
|
||||||
|
color: '#FFB300',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: $localize`:mining.pool-dominance:Pool Dominance`,
|
||||||
|
inactiveColor: 'rgb(110, 112, 121)',
|
||||||
|
textStyle: {
|
||||||
|
color: 'white',
|
||||||
|
},
|
||||||
|
icon: 'roundRect',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
yAxis: hashrate.length <= 1 ? undefined : [
|
||||||
{
|
{
|
||||||
min: (value) => {
|
min: (value) => {
|
||||||
return value.min * 0.9;
|
return value.min * 0.9;
|
||||||
@ -214,21 +247,45 @@ export class PoolComponent implements OnInit {
|
|||||||
show: false,
|
show: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
],
|
|
||||||
series: data.length <= 1 ? undefined : [
|
|
||||||
{
|
{
|
||||||
zlevel: 0,
|
type: 'value',
|
||||||
name: 'Hashrate',
|
axisLabel: {
|
||||||
|
color: 'rgb(110, 112, 121)',
|
||||||
|
formatter: (val) => {
|
||||||
|
return `${val}%`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: hashrate.length <= 1 ? undefined : [
|
||||||
|
{
|
||||||
|
zlevel: 1,
|
||||||
|
name: $localize`:mining.hashrate:Hashrate`,
|
||||||
showSymbol: false,
|
showSymbol: false,
|
||||||
symbol: 'none',
|
symbol: 'none',
|
||||||
data: data,
|
data: hashrate,
|
||||||
type: 'line',
|
type: 'line',
|
||||||
lineStyle: {
|
lineStyle: {
|
||||||
width: 2,
|
width: 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
zlevel: 0,
|
||||||
|
name: $localize`:mining.pool-dominance:Pool Dominance`,
|
||||||
|
showSymbol: false,
|
||||||
|
symbol: 'none',
|
||||||
|
data: share,
|
||||||
|
type: 'line',
|
||||||
|
yAxisIndex: 1,
|
||||||
|
lineStyle: {
|
||||||
|
width: 2,
|
||||||
|
},
|
||||||
|
}
|
||||||
],
|
],
|
||||||
dataZoom: data.length <= 1 ? undefined : [{
|
dataZoom: hashrate.length <= 1 ? undefined : [{
|
||||||
type: 'inside',
|
type: 'inside',
|
||||||
realtime: true,
|
realtime: true,
|
||||||
zoomLock: true,
|
zoomLock: true,
|
||||||
|
@ -74,8 +74,7 @@
|
|||||||
<td class="td-width" i18n="transaction.audit">Audit</td>
|
<td class="td-width" i18n="transaction.audit">Audit</td>
|
||||||
<td *ngIf="pool" class="wrap-cell">
|
<td *ngIf="pool" class="wrap-cell">
|
||||||
<ng-container *ngIf="auditStatus">
|
<ng-container *ngIf="auditStatus">
|
||||||
<span *ngIf="auditStatus.coinbase; else accelerated" class="badge badge-primary mr-1" i18n="tx-features.tag.coinbase|Coinbase">Coinbase</span>
|
<span *ngIf="auditStatus.coinbase; else expected" class="badge badge-primary mr-1" i18n="tx-features.tag.coinbase|Coinbase">Coinbase</span>
|
||||||
<ng-template #accelerated><span *ngIf="auditStatus.accelerated || accelerationInfo || (tx && tx.acceleration) ; else expected" class="badge badge-accelerated mr-1" i18n="transaction.audit.accelerated">Accelerated</span></ng-template>
|
|
||||||
<ng-template #expected><span *ngIf="auditStatus.expected; else seen" class="badge badge-success mr-1" i18n-ngbTooltip="Expected in block tooltip" ngbTooltip="This transaction was projected to be included in the block" placement="bottom" i18n="tx-features.tag.expected|Expected in Block">Expected in Block</span></ng-template>
|
<ng-template #expected><span *ngIf="auditStatus.expected; else seen" class="badge badge-success mr-1" i18n-ngbTooltip="Expected in block tooltip" ngbTooltip="This transaction was projected to be included in the block" placement="bottom" i18n="tx-features.tag.expected|Expected in Block">Expected in Block</span></ng-template>
|
||||||
<ng-template #seen><span *ngIf="auditStatus.seen; else notSeen" class="badge badge-success mr-1" i18n-ngbTooltip="Seen in mempool tooltip" ngbTooltip="This transaction was seen in the mempool prior to mining" placement="bottom" i18n="tx-features.tag.seen|Seen in Mempool">Seen in Mempool</span></ng-template>
|
<ng-template #seen><span *ngIf="auditStatus.seen; else notSeen" class="badge badge-success mr-1" i18n-ngbTooltip="Seen in mempool tooltip" ngbTooltip="This transaction was seen in the mempool prior to mining" placement="bottom" i18n="tx-features.tag.seen|Seen in Mempool">Seen in Mempool</span></ng-template>
|
||||||
<ng-template #notSeen><span class="badge badge-warning mr-1" i18n-ngbTooltip="Not seen in mempool tooltip" ngbTooltip="This transaction was missing from our mempool prior to mining" placement="bottom" i18n="tx-features.tag.not-seen|Not seen in Mempool">Not seen in Mempool</span></ng-template>
|
<ng-template #notSeen><span class="badge badge-warning mr-1" i18n-ngbTooltip="Not seen in mempool tooltip" ngbTooltip="This transaction was missing from our mempool prior to mining" placement="bottom" i18n="tx-features.tag.not-seen|Not seen in Mempool">Not seen in Mempool</span></ng-template>
|
||||||
@ -517,9 +516,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template #errorTemplate>
|
<ng-template #errorTemplate>
|
||||||
<div class="text-center">
|
<app-http-error [error]="error">
|
||||||
<h3>{{ error.error }}</h3>
|
<span i18n="transaction.error.loading-transaction-data">Error loading transaction data.</span>
|
||||||
</div>
|
</app-http-error>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
|
@ -66,9 +66,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ng-template [ngIf]="error">
|
<ng-template [ngIf]="error">
|
||||||
<div class="text-center">
|
<app-http-error [error]="error">
|
||||||
<span i18n="error.general-loading-data">Error loading data.</span>
|
<span i18n="error.general-loading-data">Error loading data.</span>
|
||||||
<br><br>
|
</app-http-error>
|
||||||
<i>{{ error.status }}: {{ error.error }}</i>
|
|
||||||
</div>
|
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
|
import { Inject, Injectable, PLATFORM_ID } from '@angular/core';
|
||||||
import { HttpInterceptor, HttpEvent, HttpRequest, HttpHandler, HttpResponse, HttpHeaders } from '@angular/common/http';
|
import { HttpInterceptor, HttpEvent, HttpRequest, HttpHandler, HttpResponse, HttpErrorResponse, HttpHeaders } from '@angular/common/http';
|
||||||
import { Observable, of } from 'rxjs';
|
import { Observable, of } from 'rxjs';
|
||||||
import { tap } from 'rxjs/operators';
|
import { catchError, tap } from 'rxjs/operators';
|
||||||
import { TransferState, makeStateKey } from '@angular/platform-browser';
|
import { TransferState, makeStateKey } from '@angular/platform-browser';
|
||||||
import { isPlatformBrowser } from '@angular/common';
|
import { isPlatformBrowser } from '@angular/common';
|
||||||
|
|
||||||
@ -36,15 +36,41 @@ export class HttpCacheInterceptor implements HttpInterceptor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return next.handle(request)
|
return next.handle(request)
|
||||||
.pipe(tap((event: HttpEvent<any>) => {
|
.pipe(
|
||||||
if (!this.isBrowser && event instanceof HttpResponse) {
|
tap((event: HttpEvent<any>) => {
|
||||||
let keyId = request.url.split('/').slice(3).join('/');
|
if (!this.isBrowser && event instanceof HttpResponse) {
|
||||||
const headers = {};
|
let keyId = request.url.split('/').slice(3).join('/');
|
||||||
for (const k of event.headers.keys()) {
|
const headers = {};
|
||||||
headers[k] = event.headers.getAll(k);
|
for (const k of event.headers.keys()) {
|
||||||
|
headers[k] = event.headers.getAll(k);
|
||||||
|
}
|
||||||
|
this.transferState.set<any>(makeStateKey('/' + keyId), { response: event, headers });
|
||||||
}
|
}
|
||||||
this.transferState.set<any>(makeStateKey('/' + keyId), { response: event, headers });
|
}),
|
||||||
}
|
catchError((e) => {
|
||||||
}));
|
if (e instanceof HttpErrorResponse) {
|
||||||
|
if (e.status === 0) {
|
||||||
|
throw new HttpErrorResponse({
|
||||||
|
error: 'Unknown error',
|
||||||
|
headers: e.headers,
|
||||||
|
status: 0,
|
||||||
|
statusText: 'Unknown error',
|
||||||
|
url: e.url,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const msg = e?.['message'] || 'Unknown error';
|
||||||
|
throw new HttpErrorResponse({
|
||||||
|
error: msg,
|
||||||
|
headers: new HttpHeaders(),
|
||||||
|
status: 0,
|
||||||
|
statusText: msg,
|
||||||
|
url: '',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
<div class="http-error">
|
||||||
|
<p><b><ng-content></ng-content></b></p>
|
||||||
|
<i class="small">({{ error | httpErrorMsg }})</i>
|
||||||
|
</div>
|
@ -0,0 +1,5 @@
|
|||||||
|
.http-error {
|
||||||
|
width: 100%;
|
||||||
|
margin: 1em auto;
|
||||||
|
text-align: center;
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
|
import { Component, Input } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-http-error',
|
||||||
|
templateUrl: './http-error.component.html',
|
||||||
|
styleUrls: ['./http-error.component.scss']
|
||||||
|
})
|
||||||
|
export class HttpErrorComponent {
|
||||||
|
@Input() error: HttpErrorResponse | null;
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
import { HttpErrorResponse } from '@angular/common/http';
|
||||||
|
import { Pipe, PipeTransform } from '@angular/core';
|
||||||
|
|
||||||
|
@Pipe({ name: 'httpErrorMsg' })
|
||||||
|
export class HttpErrorPipe implements PipeTransform {
|
||||||
|
transform(e: HttpErrorResponse | null): string {
|
||||||
|
return e ? `${e.status}: ${e.statusText}` : '';
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,7 @@ import { ScriptpubkeyTypePipe } from './pipes/scriptpubkey-type-pipe/scriptpubke
|
|||||||
import { BytesPipe } from './pipes/bytes-pipe/bytes.pipe';
|
import { BytesPipe } from './pipes/bytes-pipe/bytes.pipe';
|
||||||
import { WuBytesPipe } from './pipes/bytes-pipe/wubytes.pipe';
|
import { WuBytesPipe } from './pipes/bytes-pipe/wubytes.pipe';
|
||||||
import { FiatCurrencyPipe } from './pipes/fiat-currency.pipe';
|
import { FiatCurrencyPipe } from './pipes/fiat-currency.pipe';
|
||||||
|
import { HttpErrorPipe } from './pipes/http-error-pipe/http-error.pipe';
|
||||||
import { BlockchainComponent } from '../components/blockchain/blockchain.component';
|
import { BlockchainComponent } from '../components/blockchain/blockchain.component';
|
||||||
import { TimeComponent } from '../components/time/time.component';
|
import { TimeComponent } from '../components/time/time.component';
|
||||||
import { ClipboardComponent } from '../components/clipboard/clipboard.component';
|
import { ClipboardComponent } from '../components/clipboard/clipboard.component';
|
||||||
@ -104,6 +105,7 @@ import { ClockFaceComponent } from '../components/clock-face/clock-face.componen
|
|||||||
import { ClockComponent } from '../components/clock/clock.component';
|
import { ClockComponent } from '../components/clock/clock.component';
|
||||||
import { CalculatorComponent } from '../components/calculator/calculator.component';
|
import { CalculatorComponent } from '../components/calculator/calculator.component';
|
||||||
import { BitcoinsatoshisPipe } from '../shared/pipes/bitcoinsatoshis.pipe';
|
import { BitcoinsatoshisPipe } from '../shared/pipes/bitcoinsatoshis.pipe';
|
||||||
|
import { HttpErrorComponent } from '../shared/components/http-error/http-error.component';
|
||||||
|
|
||||||
import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-directives/weight-directives';
|
import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-directives/weight-directives';
|
||||||
|
|
||||||
@ -133,6 +135,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
|
|||||||
Decimal2HexPipe,
|
Decimal2HexPipe,
|
||||||
FeeRoundingPipe,
|
FeeRoundingPipe,
|
||||||
FiatCurrencyPipe,
|
FiatCurrencyPipe,
|
||||||
|
HttpErrorPipe,
|
||||||
ColoredPriceDirective,
|
ColoredPriceDirective,
|
||||||
BrowserOnlyDirective,
|
BrowserOnlyDirective,
|
||||||
ServerOnlyDirective,
|
ServerOnlyDirective,
|
||||||
@ -208,6 +211,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
|
|||||||
AccelerationsListComponent,
|
AccelerationsListComponent,
|
||||||
AccelerationStatsComponent,
|
AccelerationStatsComponent,
|
||||||
PendingStatsComponent,
|
PendingStatsComponent,
|
||||||
|
HttpErrorComponent,
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
@ -262,6 +266,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
|
|||||||
VbytesPipe,
|
VbytesPipe,
|
||||||
WuBytesPipe,
|
WuBytesPipe,
|
||||||
FiatCurrencyPipe,
|
FiatCurrencyPipe,
|
||||||
|
HttpErrorPipe,
|
||||||
CeilPipe,
|
CeilPipe,
|
||||||
ShortenStringPipe,
|
ShortenStringPipe,
|
||||||
CapAddressPipe,
|
CapAddressPipe,
|
||||||
@ -327,6 +332,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
|
|||||||
AccelerationsListComponent,
|
AccelerationsListComponent,
|
||||||
AccelerationStatsComponent,
|
AccelerationStatsComponent,
|
||||||
PendingStatsComponent,
|
PendingStatsComponent,
|
||||||
|
HttpErrorComponent,
|
||||||
|
|
||||||
MempoolBlockOverviewComponent,
|
MempoolBlockOverviewComponent,
|
||||||
ClockchainComponent,
|
ClockchainComponent,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user