Fix db version conflicts
This commit is contained in:
@@ -23,6 +23,9 @@
|
||||
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '1m'">
|
||||
<input type="radio" [value]="'1m'" fragment="1m" [routerLink]="['/graphs/acceleration/fees' | relativeUrl]" formControlName="dateSpan"> 1M
|
||||
</label>
|
||||
<label class="btn btn-primary btn-sm" [class.active]="radioGroupForm.get('dateSpan').value === '3m'">
|
||||
<input type="radio" [value]="'3m'" fragment="3m" [routerLink]="['/graphs/acceleration/fees' | relativeUrl]" formControlName="dateSpan"> 3M
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -25,7 +25,8 @@
|
||||
flex-direction: column;
|
||||
padding: 0px 15px;
|
||||
width: 100%;
|
||||
height: calc(100vh - 250px);
|
||||
height: calc(100vh - 225px);
|
||||
min-height: 400px;
|
||||
@media (min-width: 992px) {
|
||||
height: calc(100vh - 150px);
|
||||
}
|
||||
@@ -35,6 +36,7 @@
|
||||
display: flex;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding-bottom: 20px;
|
||||
padding-right: 10px;
|
||||
@media (max-width: 992px) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Inject, Input, LOCALE_ID, OnDestroy, OnInit } from '@angular/core';
|
||||
import { EChartsOption, graphic } from 'echarts';
|
||||
import { EChartsOption } from 'echarts';
|
||||
import { Observable, Subscription, combineLatest, fromEvent } from 'rxjs';
|
||||
import { map, max, startWith, switchMap, tap } from 'rxjs/operators';
|
||||
import { startWith, switchMap, tap } from 'rxjs/operators';
|
||||
import { SeoService } from '../../../services/seo.service';
|
||||
import { formatNumber } from '@angular/common';
|
||||
import { UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||
@@ -11,7 +11,6 @@ import { MiningService } from '../../../services/mining.service';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Acceleration } from '../../../interfaces/node-api.interface';
|
||||
import { ServicesApiServices } from '../../../services/services-api.service';
|
||||
import { ApiService } from '../../../services/api.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-acceleration-fees-graph',
|
||||
@@ -29,7 +28,7 @@ import { ApiService } from '../../../services/api.service';
|
||||
})
|
||||
export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
||||
@Input() widget: boolean = false;
|
||||
@Input() height: number | string = '200';
|
||||
@Input() height: number = 300;
|
||||
@Input() right: number | string = 45;
|
||||
@Input() left: number | string = 75;
|
||||
@Input() accelerations$: Observable<Acceleration[]>;
|
||||
@@ -55,7 +54,6 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
||||
constructor(
|
||||
@Inject(LOCALE_ID) public locale: string,
|
||||
private seoService: SeoService,
|
||||
private apiService: ApiService,
|
||||
private servicesApiService: ServicesApiServices,
|
||||
private formBuilder: UntypedFormBuilder,
|
||||
private storageService: StorageService,
|
||||
@@ -69,104 +67,56 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.isLoading = true;
|
||||
if (this.widget) {
|
||||
this.miningWindowPreference = '1m';
|
||||
this.timespan = this.miningWindowPreference;
|
||||
|
||||
this.statsObservable$ = combineLatest([
|
||||
(this.accelerations$ || this.servicesApiService.getAccelerationHistory$({ timeframe: this.miningWindowPreference })),
|
||||
this.apiService.getHistoricalBlockFees$(this.miningWindowPreference),
|
||||
fromEvent(window, 'resize').pipe(startWith(null)),
|
||||
]).pipe(
|
||||
tap(([accelerations, blockFeesResponse]) => {
|
||||
this.prepareChartOptions(accelerations, blockFeesResponse.body);
|
||||
}),
|
||||
map(([accelerations, blockFeesResponse]) => {
|
||||
return {
|
||||
avgFeesPaid: accelerations.filter(acc => acc.status === 'completed').reduce((total, acc) => total + (acc.feePaid - acc.baseFee - acc.vsizeFee), 0) / accelerations.length
|
||||
};
|
||||
}),
|
||||
);
|
||||
this.miningWindowPreference = '3m';
|
||||
} else {
|
||||
this.seoService.setTitle($localize`:@@bcf34abc2d9ed8f45a2f65dd464c46694e9a181e:Acceleration Fees`);
|
||||
this.miningWindowPreference = this.miningService.getDefaultTimespan('1w');
|
||||
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
|
||||
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
|
||||
this.route.fragment.subscribe((fragment) => {
|
||||
if (['24h', '3d', '1w', '1m'].indexOf(fragment) > -1) {
|
||||
this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false });
|
||||
}
|
||||
});
|
||||
this.statsObservable$ = combineLatest([
|
||||
this.radioGroupForm.get('dateSpan').valueChanges.pipe(
|
||||
startWith(this.radioGroupForm.controls.dateSpan.value),
|
||||
switchMap((timespan) => {
|
||||
this.isLoading = true;
|
||||
this.storageService.setValue('miningWindowPreference', timespan);
|
||||
this.timespan = timespan;
|
||||
return this.servicesApiService.getAccelerationHistory$({});
|
||||
})
|
||||
),
|
||||
this.radioGroupForm.get('dateSpan').valueChanges.pipe(
|
||||
startWith(this.radioGroupForm.controls.dateSpan.value),
|
||||
switchMap((timespan) => {
|
||||
return this.apiService.getHistoricalBlockFees$(timespan);
|
||||
})
|
||||
)
|
||||
]).pipe(
|
||||
tap(([accelerations, blockFeesResponse]) => {
|
||||
this.prepareChartOptions(accelerations, blockFeesResponse.body);
|
||||
})
|
||||
);
|
||||
this.miningWindowPreference = this.miningService.getDefaultTimespan('3m');
|
||||
}
|
||||
this.statsSubscription = this.statsObservable$.subscribe(() => {
|
||||
this.isLoading = false;
|
||||
this.cd.markForCheck();
|
||||
this.radioGroupForm = this.formBuilder.group({ dateSpan: this.miningWindowPreference });
|
||||
this.radioGroupForm.controls.dateSpan.setValue(this.miningWindowPreference);
|
||||
|
||||
this.route.fragment.subscribe((fragment) => {
|
||||
if (['24h', '3d', '1w', '1m', '3m'].indexOf(fragment) > -1) {
|
||||
this.radioGroupForm.controls.dateSpan.setValue(fragment, { emitEvent: false });
|
||||
}
|
||||
});
|
||||
this.statsObservable$ = combineLatest([
|
||||
this.radioGroupForm.get('dateSpan').valueChanges.pipe(
|
||||
startWith(this.radioGroupForm.controls.dateSpan.value),
|
||||
switchMap((timespan) => {
|
||||
if (!this.widget) {
|
||||
this.storageService.setValue('miningWindowPreference', timespan);
|
||||
}
|
||||
this.isLoading = true;
|
||||
this.timespan = timespan;
|
||||
return this.servicesApiService.getAggregatedAccelerationHistory$({timeframe: this.timespan});
|
||||
})
|
||||
),
|
||||
fromEvent(window, 'resize').pipe(startWith(null)),
|
||||
]).pipe(
|
||||
tap(([history]) => {
|
||||
this.isLoading = false;
|
||||
this.prepareChartOptions(history);
|
||||
this.cd.markForCheck();
|
||||
})
|
||||
);
|
||||
|
||||
this.statsObservable$.subscribe();
|
||||
}
|
||||
|
||||
prepareChartOptions(accelerations, blockFees) {
|
||||
prepareChartOptions(data) {
|
||||
let title: object;
|
||||
|
||||
const blockAccelerations = {};
|
||||
|
||||
for (const acceleration of accelerations) {
|
||||
if (acceleration.status === 'completed') {
|
||||
if (!blockAccelerations[acceleration.blockHeight]) {
|
||||
blockAccelerations[acceleration.blockHeight] = [];
|
||||
}
|
||||
blockAccelerations[acceleration.blockHeight].push(acceleration);
|
||||
}
|
||||
}
|
||||
|
||||
let last = null;
|
||||
let minValue = Infinity;
|
||||
let maxValue = 0;
|
||||
const data = [];
|
||||
for (const val of blockFees) {
|
||||
if (last == null) {
|
||||
last = val.avgHeight;
|
||||
}
|
||||
let totalFeeDelta = 0;
|
||||
let totalFeePaid = 0;
|
||||
let totalCount = 0;
|
||||
let blockCount = 0;
|
||||
while (last <= val.avgHeight) {
|
||||
blockCount++;
|
||||
totalFeeDelta += (blockAccelerations[last] || []).reduce((total, acc) => total + acc.feeDelta, 0);
|
||||
totalFeePaid += (blockAccelerations[last] || []).reduce((total, acc) => total + (acc.feePaid - acc.baseFee - acc.vsizeFee), 0);
|
||||
totalCount += (blockAccelerations[last] || []).length;
|
||||
last++;
|
||||
}
|
||||
minValue = Math.min(minValue, val.avgFees);
|
||||
maxValue = Math.max(maxValue, val.avgFees);
|
||||
data.push({
|
||||
...val,
|
||||
feeDelta: totalFeeDelta,
|
||||
avgFeePaid: (totalFeePaid / blockCount),
|
||||
accelerations: totalCount / blockCount,
|
||||
});
|
||||
if (data.length === 0) {
|
||||
title = {
|
||||
textStyle: {
|
||||
color: 'grey',
|
||||
fontSize: 15
|
||||
},
|
||||
text: $localize`No accelerated transaction for this timeframe`,
|
||||
left: 'center',
|
||||
top: 'center'
|
||||
};
|
||||
}
|
||||
|
||||
this.chartOptions = {
|
||||
@@ -177,11 +127,11 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
||||
],
|
||||
animation: false,
|
||||
grid: {
|
||||
height: this.height,
|
||||
height: (this.widget && this.height) ? this.height - 30 : undefined,
|
||||
top: this.widget ? 20 : 40,
|
||||
bottom: this.widget ? 30 : 80,
|
||||
right: this.right,
|
||||
left: this.left,
|
||||
bottom: this.widget ? 30 : 80,
|
||||
top: this.widget ? 20 : (this.isMobile() ? 10 : 50),
|
||||
},
|
||||
tooltip: {
|
||||
show: !this.isMobile(),
|
||||
@@ -197,29 +147,23 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
||||
align: 'left',
|
||||
},
|
||||
borderColor: '#000',
|
||||
formatter: function (data) {
|
||||
if (data.length <= 0) {
|
||||
return '';
|
||||
}
|
||||
let tooltip = `<b style="color: white; margin-left: 2px">
|
||||
${formatterXAxis(this.locale, this.timespan, parseInt(data[0].axisValue, 10))}</b><br>`;
|
||||
formatter: (ticks) => {
|
||||
let tooltip = `<b style="color: white; margin-left: 2px">${formatterXAxis(this.locale, this.timespan, parseInt(ticks[0].axisValue, 10))}</b><br>`;
|
||||
|
||||
for (const tick of data.reverse()) {
|
||||
if (tick.data[1] >= 1_000_000) {
|
||||
tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1] / 100_000_000, this.locale, '1.0-3')} BTC<br>`;
|
||||
} else {
|
||||
tooltip += `${tick.marker} ${tick.seriesName}: ${formatNumber(tick.data[1], this.locale, '1.0-0')} sats<br>`;
|
||||
}
|
||||
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>`;
|
||||
} else {
|
||||
tooltip += `${ticks[0].marker} ${ticks[0].seriesName}: ${formatNumber(ticks[0].data[1], this.locale, '1.0-0')} sats<br>`;
|
||||
}
|
||||
|
||||
if (['24h', '3d'].includes(this.timespan)) {
|
||||
tooltip += `<small>` + $localize`At block: ${data[0].data[2]}` + `</small>`;
|
||||
tooltip += `<small>` + $localize`At block: ${ticks[0].data[2]}` + `</small>`;
|
||||
} else {
|
||||
tooltip += `<small>` + $localize`Around block: ${data[0].data[2]}` + `</small>`;
|
||||
tooltip += `<small>` + $localize`Around block: ${ticks[0].data[2]}` + `</small>`;
|
||||
}
|
||||
|
||||
return tooltip;
|
||||
}.bind(this)
|
||||
}
|
||||
},
|
||||
xAxis: data.length === 0 ? undefined :
|
||||
{
|
||||
@@ -228,7 +172,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
||||
nameTextStyle: {
|
||||
padding: [10, 0, 0, 0],
|
||||
},
|
||||
type: 'category',
|
||||
type: 'time',
|
||||
boundaryGap: false,
|
||||
axisLine: { onZero: true },
|
||||
axisLabel: {
|
||||
@@ -243,15 +187,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
||||
legend: {
|
||||
data: [
|
||||
{
|
||||
name: 'In-band fees per block',
|
||||
inactiveColor: 'rgb(110, 112, 121)',
|
||||
textStyle: {
|
||||
color: 'white',
|
||||
},
|
||||
icon: 'roundRect',
|
||||
},
|
||||
{
|
||||
name: 'Total bid boost per block',
|
||||
name: 'Total bid boost',
|
||||
inactiveColor: 'rgb(110, 112, 121)',
|
||||
textStyle: {
|
||||
color: 'white',
|
||||
@@ -260,8 +196,7 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
||||
},
|
||||
],
|
||||
selected: {
|
||||
'In-band fees per block': false,
|
||||
'Total bid boost per block': true,
|
||||
'Total bid boost': true,
|
||||
},
|
||||
show: !this.widget,
|
||||
},
|
||||
@@ -304,21 +239,13 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
||||
{
|
||||
legendHoverLink: false,
|
||||
zlevel: 1,
|
||||
name: 'Total bid boost per block',
|
||||
data: data.map(block => [block.timestamp * 1000, block.avgFeePaid, block.avgHeight]),
|
||||
name: 'Total bid boost',
|
||||
data: data.map(h => {
|
||||
return [h.timestamp * 1000, h.sumBidBoost, h.avgHeight]
|
||||
}),
|
||||
stack: 'Total',
|
||||
type: 'bar',
|
||||
barWidth: '100%',
|
||||
large: true,
|
||||
},
|
||||
{
|
||||
legendHoverLink: false,
|
||||
zlevel: 0,
|
||||
name: 'In-band fees per block',
|
||||
data: data.map(block => [block.timestamp * 1000, block.avgFees, block.avgHeight]),
|
||||
stack: 'Total',
|
||||
type: 'bar',
|
||||
barWidth: '100%',
|
||||
barWidth: '90%',
|
||||
large: true,
|
||||
},
|
||||
],
|
||||
@@ -347,17 +274,6 @@ export class AccelerationFeesGraphComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
},
|
||||
}],
|
||||
visualMap: {
|
||||
type: 'continuous',
|
||||
min: minValue,
|
||||
max: maxValue,
|
||||
dimension: 1,
|
||||
seriesIndex: 1,
|
||||
show: false,
|
||||
inRange: {
|
||||
color: ['#F4511E7f', '#FB8C007f', '#FFB3007f', '#FDD8357f', '#7CB3427f'].reverse() // Gradient color range
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="accelerator.requests">Requests</h5>
|
||||
<div class="card-text">
|
||||
<div>{{ stats.count }}</div>
|
||||
<div>{{ stats.totalRequested }}</div>
|
||||
<div class="symbol" i18n="accelerator.total-accelerated">accelerated</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="item">
|
||||
<h5 class="card-title" i18n="accelerator.total-boost">Total Bid Boost</h5>
|
||||
<div class="card-text">
|
||||
<div>{{ stats.totalFeesPaid / 100_000_000 | amountShortener: 4 }} <span class="symbol" i18n="shared.btc|BTC">BTC</span></div>
|
||||
<div>{{ stats.totalBidBoost / 100_000_000 | amountShortener: 4 }} <span class="symbol" i18n="shared.btc|BTC">BTC</span></div>
|
||||
<span class="fiat">
|
||||
<app-fiat [value]="stats.totalFeesPaid"></app-fiat>
|
||||
<app-fiat [value]="stats.totalBidBoost"></app-fiat>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { ChangeDetectionStrategy, Component, Input, OnInit } from '@angular/core';
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { switchMap } from 'rxjs/operators';
|
||||
import { ApiService } from '../../../services/api.service';
|
||||
import { StateService } from '../../../services/state.service';
|
||||
import { Acceleration } from '../../../interfaces/node-api.interface';
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { ServicesApiServices } from '../../../services/services-api.service';
|
||||
|
||||
export type AccelerationStats = {
|
||||
totalRequested: number;
|
||||
totalBidBoost: number;
|
||||
successRate: number;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-acceleration-stats',
|
||||
@@ -12,35 +15,13 @@ import { Acceleration } from '../../../interfaces/node-api.interface';
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AccelerationStatsComponent implements OnInit {
|
||||
@Input() timespan: '24h' | '1w' | '1m' = '24h';
|
||||
@Input() accelerations$: Observable<Acceleration[]>;
|
||||
public accelerationStats$: Observable<any>;
|
||||
accelerationStats$: Observable<AccelerationStats>;
|
||||
|
||||
constructor(
|
||||
private apiService: ApiService,
|
||||
private stateService: StateService,
|
||||
private servicesApiService: ServicesApiServices
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.accelerationStats$ = this.accelerations$.pipe(
|
||||
switchMap(accelerations => {
|
||||
let totalFeesPaid = 0;
|
||||
let totalSucceeded = 0;
|
||||
let totalCanceled = 0;
|
||||
for (const acc of accelerations) {
|
||||
if (acc.status === 'completed') {
|
||||
totalSucceeded++;
|
||||
totalFeesPaid += (acc.feePaid - acc.baseFee - acc.vsizeFee) || 0;
|
||||
} else if (acc.status === 'failed') {
|
||||
totalCanceled++;
|
||||
}
|
||||
}
|
||||
return of({
|
||||
count: totalSucceeded,
|
||||
totalFeesPaid,
|
||||
successRate: (totalSucceeded + totalCanceled > 0) ? ((totalSucceeded / (totalSucceeded + totalCanceled)) * 100) : 0.0,
|
||||
});
|
||||
})
|
||||
);
|
||||
this.accelerationStats$ = this.servicesApiService.getAccelerationStats$();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<div class="container-xl widget-container" [class.widget]="widget" [class.full-height]="!widget">
|
||||
<div class="container-lg widget-container" [class.widget]="widget" [class.full-height]="!widget">
|
||||
<h1 *ngIf="!widget" class="float-left" i18n="master-page.blocks">Accelerations</h1>
|
||||
<div *ngIf="!widget && isLoading" class="spinner-border ml-3" role="status"></div>
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<th class="fee text-right" i18n="transaction.bid-boost|Bid Boost">Bid Boost</th>
|
||||
<th class="block text-right" i18n="accelerator.block">Block</th>
|
||||
<th class="status text-right" i18n="transaction.status|Transaction Status">Status</th>
|
||||
<th class="date text-right" i18n="" *ngIf="!this.widget">Requested</th>
|
||||
</ng-container>
|
||||
</thead>
|
||||
<tbody *ngIf="accelerations; else skeleton" [style]="isLoading ? 'opacity: 0.75' : ''">
|
||||
@@ -49,9 +50,13 @@
|
||||
</td>
|
||||
<td class="status text-right">
|
||||
<span *ngIf="acceleration.status === 'accelerating'" class="badge badge-warning" i18n="accelerator.pending">Pending</span>
|
||||
<span *ngIf="acceleration.status === 'mined' || acceleration.status === 'completed'" class="badge badge-success" i18n="transaction.rbf.mined">Mined</span>
|
||||
<span *ngIf="acceleration.status === 'mined'" class="badge badge-info" i18n="transaction.rbf.mined">Mined</span>
|
||||
<span *ngIf="acceleration.status === 'completed'" class="badge badge-success" i18n="">Completed</span>
|
||||
<span *ngIf="acceleration.status === 'failed'" class="badge badge-danger" i18n="accelerator.canceled">Canceled</span>
|
||||
</td>
|
||||
<td class="date text-right" *ngIf="!this.widget">
|
||||
<app-time kind="since" [time]="acceleration.added" [fastRender]="true"></app-time>
|
||||
</td>
|
||||
</ng-container>
|
||||
</tr>
|
||||
</tbody>
|
||||
@@ -75,6 +80,11 @@
|
||||
</ng-template>
|
||||
</table>
|
||||
|
||||
<ngb-pagination *ngIf="!widget" class="pagination-container float-right mt-2" [class]="isLoading ? 'disabled' : ''"
|
||||
[collectionSize]="this.accelerationCount" [rotate]="true" [maxSize]="maxSize" [pageSize]="15" [(page)]="page"
|
||||
(pageChange)="pageChange(page)" [boundaryLinks]="true" [ellipses]="false">
|
||||
</ngb-pagination>
|
||||
|
||||
<ng-template [ngIf]="!widget">
|
||||
<div class="clearfix"></div>
|
||||
<br>
|
||||
|
||||
@@ -63,16 +63,28 @@ tr, td, th {
|
||||
}
|
||||
|
||||
.txid {
|
||||
@media (max-width: 500px) {
|
||||
width: 20%;
|
||||
}
|
||||
|
||||
.fee {
|
||||
width: 15%;
|
||||
}
|
||||
|
||||
.block {
|
||||
width: 15%;
|
||||
@media (max-width: 700px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.fee, .block, .status {
|
||||
width: 15%;
|
||||
.status {
|
||||
width: 13%;
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
width: 20%;
|
||||
.date {
|
||||
width: 20%;
|
||||
@media (max-width: 600px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,23 +95,12 @@ tr, td, th {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 30%;
|
||||
@media (max-width: 1060px) and (min-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
@media (max-width: 500px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.fee-rate {
|
||||
width: 20%;
|
||||
@media (max-width: 1060px) and (min-width: 768px) {
|
||||
text-align: start !important;
|
||||
}
|
||||
@media (max-width: 500px) {
|
||||
text-align: start !important;
|
||||
}
|
||||
@media (max-width: 840px) and (min-width: 768px) {
|
||||
text-align: end !important;
|
||||
@media (max-width: 975px) and (min-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
@media (max-width: 410px) {
|
||||
@@ -108,32 +109,31 @@ tr, td, th {
|
||||
}
|
||||
|
||||
.bid {
|
||||
text-align: end !important;
|
||||
width: 30%;
|
||||
min-width: 150px;
|
||||
@media (max-width: 840px) and (min-width: 768px) {
|
||||
text-align: start !important;
|
||||
}
|
||||
@media (max-width: 410px) {
|
||||
text-align: start !important;
|
||||
}
|
||||
}
|
||||
|
||||
.time {
|
||||
width: 25%;
|
||||
@media (max-width: 600px) {
|
||||
display: none;
|
||||
}
|
||||
@media (max-width: 1200px) and (min-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.fee {
|
||||
width: 30%;
|
||||
@media (max-width: 1060px) and (min-width: 768px) {
|
||||
text-align: start !important;
|
||||
}
|
||||
@media (max-width: 500px) {
|
||||
text-align: start !important;
|
||||
}
|
||||
text-align: end !important;
|
||||
}
|
||||
|
||||
.block {
|
||||
width: 20%;
|
||||
@media (max-width: 1200px) and (min-width: 768px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.status {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Component, OnInit, ChangeDetectionStrategy, Input, ChangeDetectorRef } from '@angular/core';
|
||||
import { Observable, catchError, of, switchMap, tap } from 'rxjs';
|
||||
import { combineLatest, BehaviorSubject, Observable, catchError, of, switchMap, tap } from 'rxjs';
|
||||
import { Acceleration, BlockExtended } from '../../../interfaces/node-api.interface';
|
||||
import { StateService } from '../../../services/state.service';
|
||||
import { WebsocketService } from '../../../services/websocket.service';
|
||||
@@ -21,9 +21,10 @@ export class AccelerationsListComponent implements OnInit {
|
||||
isLoading = true;
|
||||
paginationMaxSize: number;
|
||||
page = 1;
|
||||
lastPage = 1;
|
||||
accelerationCount: number;
|
||||
maxSize = window.innerWidth <= 767.98 ? 3 : 5;
|
||||
skeletonLines: number[] = [];
|
||||
pageSubject: BehaviorSubject<number> = new BehaviorSubject(this.page);
|
||||
|
||||
constructor(
|
||||
private servicesApiService: ServicesApiServices,
|
||||
@@ -40,34 +41,47 @@ export class AccelerationsListComponent implements OnInit {
|
||||
|
||||
this.skeletonLines = this.widget === true ? [...Array(6).keys()] : [...Array(15).keys()];
|
||||
this.paginationMaxSize = window.matchMedia('(max-width: 670px)').matches ? 3 : 5;
|
||||
|
||||
const accelerationObservable$ = this.accelerations$ || (this.pending ? this.servicesApiService.getAccelerations$() : this.servicesApiService.getAccelerationHistory$({ timeframe: '1m' }));
|
||||
this.accelerationList$ = accelerationObservable$.pipe(
|
||||
switchMap(accelerations => {
|
||||
if (this.pending) {
|
||||
for (const acceleration of accelerations) {
|
||||
acceleration.status = acceleration.status || 'accelerating';
|
||||
}
|
||||
}
|
||||
for (const acc of accelerations) {
|
||||
acc.boost = acc.feePaid - acc.baseFee - acc.vsizeFee;
|
||||
}
|
||||
if (this.widget) {
|
||||
return of(accelerations.slice(-6).reverse());
|
||||
} else {
|
||||
return of(accelerations.reverse());
|
||||
}
|
||||
}),
|
||||
catchError((err) => {
|
||||
this.isLoading = false;
|
||||
return of([]);
|
||||
}),
|
||||
tap(() => {
|
||||
this.isLoading = false;
|
||||
|
||||
this.accelerationList$ = this.pageSubject.pipe(
|
||||
switchMap((page) => {
|
||||
const accelerationObservable$ = this.accelerations$ || (this.pending ? this.servicesApiService.getAccelerations$() : this.servicesApiService.getAccelerationHistoryObserveResponse$({ timeframe: '1y', page: page }));
|
||||
return accelerationObservable$.pipe(
|
||||
switchMap(response => {
|
||||
let accelerations = response;
|
||||
if (response.body) {
|
||||
accelerations = response.body;
|
||||
this.accelerationCount = parseInt(response.headers.get('x-total-count'), 10);
|
||||
}
|
||||
if (this.pending) {
|
||||
for (const acceleration of accelerations) {
|
||||
acceleration.status = acceleration.status || 'accelerating';
|
||||
}
|
||||
}
|
||||
for (const acc of accelerations) {
|
||||
acc.boost = acc.feePaid - acc.baseFee - acc.vsizeFee;
|
||||
}
|
||||
if (this.widget) {
|
||||
return of(accelerations.slice(0, 6));
|
||||
} else {
|
||||
return of(accelerations);
|
||||
}
|
||||
}),
|
||||
catchError((err) => {
|
||||
this.isLoading = false;
|
||||
return of([]);
|
||||
}),
|
||||
tap(() => {
|
||||
this.isLoading = false;
|
||||
})
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
pageChange(page: number): void {
|
||||
this.pageSubject.next(page);
|
||||
}
|
||||
|
||||
trackByBlock(index: number, block: BlockExtended): number {
|
||||
return block.height;
|
||||
}
|
||||
|
||||
@@ -22,12 +22,12 @@
|
||||
<div class="col">
|
||||
<div class="main-title">
|
||||
<span [attr.data-cy]="'acceleration-stats'" i18n="accelerator.acceleration-stats">Acceleration stats</span>
|
||||
<span style="font-size: xx-small" i18n="mining.144-blocks">(1 month)</span>
|
||||
<span style="font-size: xx-small" i18n="mining.3-months">(3 months)</span>
|
||||
</div>
|
||||
<div class="card-wrapper">
|
||||
<div class="card">
|
||||
<div class="card-body more-padding">
|
||||
<app-acceleration-stats timespan="1m" [accelerations$]="minedAccelerations$"></app-acceleration-stats>
|
||||
<app-acceleration-stats></app-acceleration-stats>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -59,7 +59,6 @@
|
||||
[height]="graphHeight"
|
||||
[attr.data-cy]="'acceleration-fees'"
|
||||
[widget]=true
|
||||
[accelerations$]="accelerations$"
|
||||
></app-acceleration-fees-graph>
|
||||
</div>
|
||||
<div class="mt-1"><a [attr.data-cy]="'acceleration-fees-view-more'" [routerLink]="['/graphs/acceleration/fees' | relativeUrl]" i18n="dashboard.view-more">View more »</a></div>
|
||||
@@ -84,7 +83,7 @@
|
||||
<div class="title-link">
|
||||
<h5 class="card-title d-inline" i18n="accelerator.pending-accelerations">Active Accelerations</h5>
|
||||
</div>
|
||||
<app-accelerations-list [attr.data-cy]="'pending-accelerations'" [widget]=true [pending]="true" [accelerations$]="pendingAccelerations$"></app-accelerations-list>
|
||||
<app-accelerations-list [attr.data-cy]="'pending-accelerations'" [widget]=true [pending]=true [accelerations$]="pendingAccelerations$"></app-accelerations-list>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -60,7 +60,7 @@ export class AcceleratorDashboardComponent implements OnInit {
|
||||
this.accelerations$ = this.stateService.chainTip$.pipe(
|
||||
distinctUntilChanged(),
|
||||
switchMap(() => {
|
||||
return this.serviceApiServices.getAccelerationHistory$({ timeframe: '1m' }).pipe(
|
||||
return this.serviceApiServices.getAccelerationHistory$({ timeframe: '3m', page: 1, pageLength: 100}).pipe(
|
||||
catchError(() => {
|
||||
return of([]);
|
||||
}),
|
||||
@@ -71,7 +71,7 @@ export class AcceleratorDashboardComponent implements OnInit {
|
||||
|
||||
this.minedAccelerations$ = this.accelerations$.pipe(
|
||||
map(accelerations => {
|
||||
return accelerations.filter(acc => ['mined', 'completed', 'failed'].includes(acc.status));
|
||||
return accelerations.filter(acc => ['mined', 'completed'].includes(acc.status));
|
||||
})
|
||||
);
|
||||
|
||||
@@ -128,11 +128,11 @@ export class AcceleratorDashboardComponent implements OnInit {
|
||||
@HostListener('window:resize', ['$event'])
|
||||
onResize(): void {
|
||||
if (window.innerWidth >= 992) {
|
||||
this.graphHeight = 330;
|
||||
this.graphHeight = 380;
|
||||
} else if (window.innerWidth >= 768) {
|
||||
this.graphHeight = 245;
|
||||
this.graphHeight = 300;
|
||||
} else {
|
||||
this.graphHeight = 210;
|
||||
this.graphHeight = 270;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ export default class TxView implements TransactionStripped {
|
||||
this.acc = tx.acc;
|
||||
this.rate = tx.rate;
|
||||
this.status = tx.status;
|
||||
this.bigintFlags = tx.flags ? (BigInt(tx.flags) ^ (this.acc ? TransactionFlags.acceleration : 0n)): 0n;
|
||||
this.bigintFlags = tx.flags ? (BigInt(tx.flags) | (this.acc ? TransactionFlags.acceleration : 0n)): 0n;
|
||||
this.initialised = false;
|
||||
this.vertexArray = scene.vertexArray;
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<app-fee-rate [fee]="feeRate"></app-fee-rate>
|
||||
</td>
|
||||
</tr>
|
||||
<tr *ngIf="effectiveRate && effectiveRate !== feeRate">
|
||||
<tr *ngIf="hasEffectiveRate && effectiveRate != null">
|
||||
<td *ngIf="!this.acceleration" class="td-width" i18n="transaction.effective-fee-rate|Effective transaction fee rate">Effective fee rate</td>
|
||||
<td *ngIf="this.acceleration" class="td-width" i18n="transaction.effective-fee-rate|Effective transaction fee rate">Accelerated fee rate</td>
|
||||
<td>
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Component, ElementRef, ViewChild, Input, OnChanges, ChangeDetectionStra
|
||||
import { Position } from '../../components/block-overview-graph/sprite-types.js';
|
||||
import { Price } from '../../services/price.service';
|
||||
import { TransactionStripped } from '../../interfaces/node-api.interface.js';
|
||||
import { TransactionFlags } from '../../shared/filters.utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-block-overview-tooltip',
|
||||
@@ -22,6 +23,7 @@ export class BlockOverviewTooltipComponent implements OnChanges {
|
||||
feeRate = 0;
|
||||
effectiveRate;
|
||||
acceleration;
|
||||
hasEffectiveRate: boolean = false;
|
||||
|
||||
tooltipPosition: Position = { x: 0, y: 0 };
|
||||
|
||||
@@ -55,6 +57,8 @@ export class BlockOverviewTooltipComponent implements OnChanges {
|
||||
this.feeRate = this.fee / this.vsize;
|
||||
this.effectiveRate = tx.rate;
|
||||
this.acceleration = tx.acc;
|
||||
this.hasEffectiveRate = Math.abs((this.fee / this.vsize) - this.effectiveRate) > 0.05
|
||||
|| (tx.bigintFlags && (tx.bigintFlags & (TransactionFlags.cpfp_child | TransactionFlags.cpfp_parent)) > 0n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
[showFilters]="showFilters"
|
||||
[filterFlags]="filterFlags"
|
||||
[filterMode]="filterMode"
|
||||
[excludeFilters]="['nonstandard']"
|
||||
[overrideColors]="overrideColors"
|
||||
(txClickEvent)="onTxClick($event)"
|
||||
></app-block-overview-graph>
|
||||
|
||||
@@ -396,8 +396,11 @@ export interface Acceleration {
|
||||
}
|
||||
|
||||
export interface AccelerationHistoryParams {
|
||||
timeframe?: string,
|
||||
status?: string,
|
||||
pool?: string,
|
||||
blockHash?: string,
|
||||
status?: string;
|
||||
timeframe?: string;
|
||||
poolUniqueId?: number;
|
||||
blockHash?: string;
|
||||
blockHeight?: number;
|
||||
page?: number;
|
||||
pageLength?: number;
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { MenuGroup } from '../interfaces/services.interface';
|
||||
import { Observable, of, ReplaySubject, tap, catchError, share, filter, switchMap } from 'rxjs';
|
||||
import { IBackendInfo } from '../interfaces/websocket.interface';
|
||||
import { Acceleration, AccelerationHistoryParams } from '../interfaces/node-api.interface';
|
||||
import { AccelerationStats } from '../components/acceleration/acceleration-stats/acceleration-stats.component';
|
||||
|
||||
export type ProductType = 'enterprise' | 'community' | 'mining_pool' | 'custom';
|
||||
export interface IUser {
|
||||
@@ -144,7 +145,19 @@ export class ServicesApiServices {
|
||||
return this.httpClient.get<Acceleration[]>(`${SERVICES_API_PREFIX}/accelerator/accelerations`);
|
||||
}
|
||||
|
||||
getAggregatedAccelerationHistory$(params: AccelerationHistoryParams): Observable<Acceleration[]> {
|
||||
return this.httpClient.get<Acceleration[]>(`${SERVICES_API_PREFIX}/accelerator/accelerations/history/aggregated`, { params: { ...params } });
|
||||
}
|
||||
|
||||
getAccelerationHistory$(params: AccelerationHistoryParams): Observable<Acceleration[]> {
|
||||
return this.httpClient.get<Acceleration[]>(`${SERVICES_API_PREFIX}/accelerator/accelerations/history`, { params: { ...params } });
|
||||
}
|
||||
|
||||
getAccelerationHistoryObserveResponse$(params: AccelerationHistoryParams): Observable<any> {
|
||||
return this.httpClient.get<any>(`${SERVICES_API_PREFIX}/accelerator/accelerations/history`, { params: { ...params }, observe: 'response'});
|
||||
}
|
||||
|
||||
getAccelerationStats$(): Observable<AccelerationStats> {
|
||||
return this.httpClient.get<AccelerationStats>(`${SERVICES_API_PREFIX}/accelerator/accelerations/stats`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ export const TransactionFlags = {
|
||||
v1: 0b00000100n,
|
||||
v2: 0b00001000n,
|
||||
v3: 0b00010000n,
|
||||
nonstandard: 0b00100000n,
|
||||
// address types
|
||||
p2pk: 0b00000001_00000000n,
|
||||
p2ms: 0b00000010_00000000n,
|
||||
@@ -66,6 +67,7 @@ export const TransactionFilters: { [key: string]: Filter } = {
|
||||
v1: { key: 'v1', label: 'Version 1', flag: TransactionFlags.v1, toggle: 'version' },
|
||||
v2: { key: 'v2', label: 'Version 2', flag: TransactionFlags.v2, toggle: 'version' },
|
||||
v3: { key: 'v3', label: 'Version 3', flag: TransactionFlags.v3, toggle: 'version' },
|
||||
nonstandard: { key: 'nonstandard', label: 'Non-Standard', flag: TransactionFlags.nonstandard, important: true },
|
||||
/* address types */
|
||||
p2pk: { key: 'p2pk', label: 'P2PK', flag: TransactionFlags.p2pk, important: true },
|
||||
p2ms: { key: 'p2ms', label: 'Bare multisig', flag: TransactionFlags.p2ms, important: true },
|
||||
@@ -96,7 +98,7 @@ export const TransactionFilters: { [key: string]: Filter } = {
|
||||
};
|
||||
|
||||
export const FilterGroups: { label: string, filters: Filter[]}[] = [
|
||||
{ label: 'Features', filters: ['rbf', 'no_rbf', 'v1', 'v2', 'v3'] },
|
||||
{ label: 'Features', filters: ['rbf', 'no_rbf', 'v1', 'v2', 'v3', 'nonstandard'] },
|
||||
{ label: 'Address Types', filters: ['p2pk', 'p2ms', 'p2pkh', 'p2sh', 'p2wpkh', 'p2wsh', 'p2tr'] },
|
||||
{ label: 'Behavior', filters: ['cpfp_parent', 'cpfp_child', 'replacement', 'acceleration'] },
|
||||
{ label: 'Data', filters: ['op_return', 'fake_pubkey', 'inscription'] },
|
||||
|
||||
Reference in New Issue
Block a user