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 }} - +
- Accelerated + Accelerated {{ filter.label }} diff --git a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.scss b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.scss index 60e2f53a3..51edee1e8 100644 --- a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.scss +++ b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.scss @@ -9,8 +9,8 @@ justify-content: space-between; padding: 10px 15px; text-align: left; - min-width: 320px; - max-width: 320px; + min-width: 340px; + max-width: 340px; pointer-events: none; z-index: 11; diff --git a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts index a11a48ee3..f163e74fc 100644 --- a/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts +++ b/frontend/src/app/components/block-overview-tooltip/block-overview-tooltip.component.ts @@ -61,8 +61,8 @@ export class BlockOverviewTooltipComponent implements OnChanges { this.vsize = this.tx.vsize || 1; this.feeRate = this.fee / this.vsize; this.effectiveRate = this.tx.rate; - this.acceleration = this.tx.acc; 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 || (txFlags && (txFlags & (TransactionFlags.cpfp_child | TransactionFlags.cpfp_parent)) > 0n); this.filters = this.tx.flags ? toFilters(txFlags).filter(f => f.tooltip) : []; diff --git a/frontend/src/app/components/block/block.component.html b/frontend/src/app/components/block/block.component.html index f3cf45f0f..0c0655c01 100644 --- a/frontend/src/app/components/block/block.component.html +++ b/frontend/src/app/components/block/block.component.html @@ -338,14 +338,12 @@ -
-
+
+ Error loading data. -

- {{ transactionsError.status }}: {{ transactionsError.error }} -
-
-
+ +
+
@@ -378,11 +376,9 @@
-
+ Error loading data. -

- {{ error.status }}: {{ error.error }} -
+
diff --git a/frontend/src/app/components/difficulty-mining/difficulty-mining.component.html b/frontend/src/app/components/difficulty-mining/difficulty-mining.component.html index 1461b0c59..0865708af 100644 --- a/frontend/src/app/components/difficulty-mining/difficulty-mining.component.html +++ b/frontend/src/app/components/difficulty-mining/difficulty-mining.component.html @@ -56,7 +56,7 @@
- + 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
-
+
+ difficulty + | + halving +
+
@@ -76,6 +84,52 @@
+ +
+
+
+
+
+
+ {{ ((210000 - epochData.blocksUntilHalving) / 2100).toFixed(2) }}% +
+
+
+
+
+ +
+
+ New subsidy +
+
+
+
+ {{ epochData.blocksUntilHalving | number }} +
+
+ 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,