diff --git a/frontend/src/app/bisq/bisq-address/bisq-address.component.html b/frontend/src/app/bisq/bisq-address/bisq-address.component.html
index c2cbbb76a..6b0d27c2c 100644
--- a/frontend/src/app/bisq/bisq-address/bisq-address.component.html
+++ b/frontend/src/app/bisq/bisq-address/bisq-address.component.html
@@ -98,11 +98,9 @@
-
- Error loading address data.
-
- {{ error.error }}
-
+
+ Error loading address data.
+
diff --git a/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts b/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts
index edc206407..91de5ca03 100644
--- a/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts
+++ b/frontend/src/app/components/acceleration/acceleration-fees-graph/acceleration-fees-graph.component.ts
@@ -156,7 +156,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
let tooltip = `${formatterXAxis(this.locale, this.timespan, parseInt(ticks[0].axisValue, 10))}
`;
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
`;
+ tooltip += `${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(ticks[0].data[1] / 100_000_000, this.locale, '1.0-8')} BTC
`;
} else {
tooltip += `${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(ticks[0].data[1], this.locale, '1.0-0')} sats
`;
}
diff --git a/frontend/src/app/components/address/address.component.html b/frontend/src/app/components/address/address.component.html
index 41a9c3061..e4c49d4c5 100644
--- a/frontend/src/app/components/address/address.component.html
+++ b/frontend/src/app/components/address/address.component.html
@@ -135,11 +135,10 @@
-
-
Error loading address data.
-
-
({{ error.error }})
-
+
+
+
Error loading address data.
+
There many transactions on this address, more than your backend can handle. See more on setting up a stronger backend.
@@ -150,9 +149,14 @@
http://mempoolhqx4isw62xs7abwphsq7ldayuidyx2v2oethdhhj6mlo2r6ad.onion/address/{{ addressString }}
- ({{ error.error }})
-
-
+ ({{ error | httpErrorMsg }})
+
+
+
+
+ Error loading address data.
+
+
diff --git a/frontend/src/app/components/address/address.component.ts b/frontend/src/app/components/address/address.component.ts
index 52a66c2be..8b325e653 100644
--- a/frontend/src/app/components/address/address.component.ts
+++ b/frontend/src/app/components/address/address.component.ts
@@ -140,10 +140,22 @@ export class AddressComponent implements OnInit, OnDestroy {
if (!fetchTxs.length) {
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) => {
this.tempTransactions[this.timeTxIndexes[index]].firstSeen = time;
});
diff --git a/frontend/src/app/components/asset/asset.component.html b/frontend/src/app/components/asset/asset.component.html
index 862055f22..a7f4d3118 100644
--- a/frontend/src/app/components/asset/asset.component.html
+++ b/frontend/src/app/components/asset/asset.component.html
@@ -146,13 +146,10 @@
-
+
Error loading asset data.
-
- {{ error.error }}
-
+
-
diff --git a/frontend/src/app/components/assets/assets.component.html b/frontend/src/app/components/assets/assets.component.html
index 51a2f7696..c279af2ab 100644
--- a/frontend/src/app/components/assets/assets.component.html
+++ b/frontend/src/app/components/assets/assets.component.html
@@ -46,9 +46,7 @@
-
- Error loading assets data.
-
- {{ error.error }}
-
+
+ Error loading assets data.
+
diff --git a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html
index f0b3b5499..1ef0d1686 100644
--- a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html
+++ b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.html
@@ -61,10 +61,10 @@
{{ activeFilters.rbf }}
-
-
+
diff --git a/frontend/src/app/components/difficulty/difficulty.component.html b/frontend/src/app/components/difficulty/difficulty.component.html
index 8011c7e6f..d1de5f076 100644
--- a/frontend/src/app/components/difficulty/difficulty.component.html
+++ b/frontend/src/app/components/difficulty/difficulty.component.html
@@ -1,7 +1,15 @@
- Difficulty Adjustment
+Difficulty Adjustment
+Halving Countdown
-
+
+
+
+
+
+
+
+
+
+ {{ ((210000 - epochData.blocksUntilHalving) / 2100).toFixed(2) }}%
+
+
+
+
+
+
+ {{ epochData.blocksUntilHalving | number }}
+
+
+ 1" i18n="shared.blocks-remaining">Blocks remaining
+ Block remaining
+
+
+
+
+ {{ epochData.timeUntilHalving | date }}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/app/components/difficulty/difficulty.component.scss b/frontend/src/app/components/difficulty/difficulty.component.scss
index 1da1591d0..3b591dc2d 100644
--- a/frontend/src/app/components/difficulty/difficulty.component.scss
+++ b/frontend/src/app/components/difficulty/difficulty.component.scss
@@ -168,7 +168,7 @@
white-space: nowrap;
}
-.epoch-progress {
+.epoch-progress, .halving-progress {
width: 100%;
height: 22px;
margin-bottom: 12px;
@@ -212,4 +212,43 @@
}
.blocks-behind {
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;
}
\ No newline at end of file
diff --git a/frontend/src/app/components/difficulty/difficulty.component.ts b/frontend/src/app/components/difficulty/difficulty.component.ts
index d37667312..13f61dc5e 100644
--- a/frontend/src/app/components/difficulty/difficulty.component.ts
+++ b/frontend/src/app/components/difficulty/difficulty.component.ts
@@ -51,6 +51,10 @@ export class DifficultyComponent implements OnInit {
isLoadingWebSocket$: Observable ;
difficultyEpoch$: Observable;
+ mode: 'difficulty' | 'halving' = 'difficulty';
+ userSelectedMode: boolean = false;
+
+ now: number = Date.now();
epochStart: number;
currentHeight: number;
currentIndex: number;
@@ -101,6 +105,11 @@ export class DifficultyComponent implements OnInit {
const timeUntilHalving = new Date().getTime() + (blocksUntilHalving * 600000);
const newEpochStart = Math.floor(this.stateService.latestBlockHeight / EPOCH_BLOCK_LENGTH) * EPOCH_BLOCK_LENGTH;
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) {
this.epochStart = newEpochStart;
@@ -194,6 +203,12 @@ export class DifficultyComponent implements OnInit {
return shapes;
}
+ setMode(mode: 'difficulty' | 'halving'): boolean {
+ this.mode = mode;
+ this.userSelectedMode = true;
+ return false;
+ }
+
@HostListener('pointerdown', ['$event'])
onPointerDown(event): void {
if (this.epochSvgElement?.nativeElement?.contains(event.target)) {
diff --git a/frontend/src/app/components/pool/pool.component.scss b/frontend/src/app/components/pool/pool.component.scss
index 92fdc2ef3..8bd6763e5 100644
--- a/frontend/src/app/components/pool/pool.component.scss
+++ b/frontend/src/app/components/pool/pool.component.scss
@@ -32,6 +32,7 @@
}
.chart {
+ margin-top: 10px;
margin-bottom: 20px;
@media (max-width: 768px) {
margin-bottom: 10px;
diff --git a/frontend/src/app/components/pool/pool.component.ts b/frontend/src/app/components/pool/pool.component.ts
index 27a705054..8274bf441 100644
--- a/frontend/src/app/components/pool/pool.component.ts
+++ b/frontend/src/app/components/pool/pool.component.ts
@@ -65,7 +65,9 @@ export class PoolComponent implements OnInit {
.pipe(
switchMap((data) => {
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];
}),
catchError(() => {
@@ -130,9 +132,9 @@ export class PoolComponent implements OnInit {
);
}
- prepareChartOptions(data) {
+ prepareChartOptions(hashrate, share) {
let title: object;
- if (data.length <= 1) {
+ if (hashrate.length <= 1) {
title = {
textStyle: {
color: 'grey',
@@ -177,26 +179,57 @@ export class PoolComponent implements OnInit {
},
borderColor: '#000',
formatter: function (ticks: any[]) {
- let hashratePowerOfTen: any = selectPowerOfTen(1);
- let hashrate = ticks[0].data[1];
-
- hashratePowerOfTen = selectPowerOfTen(ticks[0].data[1], 10);
- hashrate = ticks[0].data[1] / hashratePowerOfTen.divider;
+ let hashrateString = '';
+ let dominanceString = '';
+ 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 `;
+ } else if (tick.seriesIndex === 1) {
+ dominanceString = `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.0-2')}%`;
+ }
+ }
+
return `
${ticks[0].axisValueLabel}
- ${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(hashrate, this.locale, '1.0-0')} ${hashratePowerOfTen.unit}H/s
+ ${hashrateString}
+ ${dominanceString}
`;
}.bind(this)
},
- xAxis: data.length <= 1 ? undefined : {
+ xAxis: hashrate.length <= 1 ? undefined : {
type: 'time',
splitNumber: (this.isMobile()) ? 5 : 10,
axisLabel: {
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) => {
return value.min * 0.9;
@@ -214,21 +247,45 @@ export class PoolComponent implements OnInit {
show: false,
}
},
- ],
- series: data.length <= 1 ? undefined : [
{
- zlevel: 0,
- name: 'Hashrate',
+ type: 'value',
+ 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,
symbol: 'none',
- data: data,
+ data: hashrate,
type: 'line',
lineStyle: {
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',
realtime: true,
zoomLock: true,
diff --git a/frontend/src/app/components/transaction/transaction.component.html b/frontend/src/app/components/transaction/transaction.component.html
index 1e204f6be..0963014bd 100644
--- a/frontend/src/app/components/transaction/transaction.component.html
+++ b/frontend/src/app/components/transaction/transaction.component.html
@@ -74,8 +74,7 @@
Audit |
- Coinbase
- Accelerated
+ Coinbase
Expected in Block
Seen in Mempool
Not seen in Mempool
@@ -517,9 +516,9 @@
-
- {{ error.error }}
-
+
+ Error loading transaction data.
+
diff --git a/frontend/src/app/lightning/channel/channel-preview.component.html b/frontend/src/app/lightning/channel/channel-preview.component.html
index e59361e42..108fe2e95 100644
--- a/frontend/src/app/lightning/channel/channel-preview.component.html
+++ b/frontend/src/app/lightning/channel/channel-preview.component.html
@@ -66,9 +66,7 @@
-
+
Error loading data.
-
- {{ error.status }}: {{ error.error }}
-
+
diff --git a/frontend/src/app/services/http-cache.interceptor.ts b/frontend/src/app/services/http-cache.interceptor.ts
index c7624887a..c0f1646ad 100644
--- a/frontend/src/app/services/http-cache.interceptor.ts
+++ b/frontend/src/app/services/http-cache.interceptor.ts
@@ -1,7 +1,7 @@
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 { tap } from 'rxjs/operators';
+import { catchError, tap } from 'rxjs/operators';
import { TransferState, makeStateKey } from '@angular/platform-browser';
import { isPlatformBrowser } from '@angular/common';
@@ -36,15 +36,41 @@ export class HttpCacheInterceptor implements HttpInterceptor {
}
return next.handle(request)
- .pipe(tap((event: HttpEvent) => {
- if (!this.isBrowser && event instanceof HttpResponse) {
- let keyId = request.url.split('/').slice(3).join('/');
- const headers = {};
- for (const k of event.headers.keys()) {
- headers[k] = event.headers.getAll(k);
+ .pipe(
+ tap((event: HttpEvent) => {
+ if (!this.isBrowser && event instanceof HttpResponse) {
+ let keyId = request.url.split('/').slice(3).join('/');
+ const headers = {};
+ for (const k of event.headers.keys()) {
+ headers[k] = event.headers.getAll(k);
+ }
+ this.transferState.set(makeStateKey('/' + keyId), { response: event, headers });
}
- this.transferState.set(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: '',
+ });
+ }
+ })
+ );
}
}
diff --git a/frontend/src/app/shared/components/http-error/http-error.component.html b/frontend/src/app/shared/components/http-error/http-error.component.html
new file mode 100644
index 000000000..036a281da
--- /dev/null
+++ b/frontend/src/app/shared/components/http-error/http-error.component.html
@@ -0,0 +1,4 @@
+
+
+ ({{ error | httpErrorMsg }})
+
\ No newline at end of file
diff --git a/frontend/src/app/shared/components/http-error/http-error.component.scss b/frontend/src/app/shared/components/http-error/http-error.component.scss
new file mode 100644
index 000000000..fe7c4de5a
--- /dev/null
+++ b/frontend/src/app/shared/components/http-error/http-error.component.scss
@@ -0,0 +1,5 @@
+.http-error {
+ width: 100%;
+ margin: 1em auto;
+ text-align: center;
+}
\ No newline at end of file
diff --git a/frontend/src/app/shared/components/http-error/http-error.component.ts b/frontend/src/app/shared/components/http-error/http-error.component.ts
new file mode 100644
index 000000000..0e4c93e81
--- /dev/null
+++ b/frontend/src/app/shared/components/http-error/http-error.component.ts
@@ -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;
+}
diff --git a/frontend/src/app/shared/pipes/http-error-pipe/http-error.pipe.ts b/frontend/src/app/shared/pipes/http-error-pipe/http-error.pipe.ts
new file mode 100644
index 000000000..3cbb16df6
--- /dev/null
+++ b/frontend/src/app/shared/pipes/http-error-pipe/http-error.pipe.ts
@@ -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}` : '';
+ }
+}
diff --git a/frontend/src/app/shared/shared.module.ts b/frontend/src/app/shared/shared.module.ts
index cdf08635c..5e58ef080 100644
--- a/frontend/src/app/shared/shared.module.ts
+++ b/frontend/src/app/shared/shared.module.ts
@@ -21,6 +21,7 @@ import { ScriptpubkeyTypePipe } from './pipes/scriptpubkey-type-pipe/scriptpubke
import { BytesPipe } from './pipes/bytes-pipe/bytes.pipe';
import { WuBytesPipe } from './pipes/bytes-pipe/wubytes.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 { TimeComponent } from '../components/time/time.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 { CalculatorComponent } from '../components/calculator/calculator.component';
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';
@@ -133,6 +135,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
Decimal2HexPipe,
FeeRoundingPipe,
FiatCurrencyPipe,
+ HttpErrorPipe,
ColoredPriceDirective,
BrowserOnlyDirective,
ServerOnlyDirective,
@@ -208,6 +211,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
AccelerationsListComponent,
AccelerationStatsComponent,
PendingStatsComponent,
+ HttpErrorComponent,
],
imports: [
CommonModule,
@@ -262,6 +266,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
VbytesPipe,
WuBytesPipe,
FiatCurrencyPipe,
+ HttpErrorPipe,
CeilPipe,
ShortenStringPipe,
CapAddressPipe,
@@ -327,6 +332,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
AccelerationsListComponent,
AccelerationStatsComponent,
PendingStatsComponent,
+ HttpErrorComponent,
MempoolBlockOverviewComponent,
ClockchainComponent,
| |