Bisq markets dashboard: Market backend tracking. WIP.

This commit is contained in:
softsimon 2021-03-05 15:38:46 +07:00
parent 2fca34faaa
commit d99fd5d59a
No known key found for this signature in database
GPG Key ID: 488D7DCFB5A430D7
17 changed files with 101 additions and 78 deletions

View File

@ -96,6 +96,14 @@ class WebsocketHandler {
client['track-donation'] = parsedMessage['track-donation']; client['track-donation'] = parsedMessage['track-donation'];
} }
if (parsedMessage['track-bisq-market']) {
if (/^[a-z]{3}_[a-z]{3}$/.test(parsedMessage['track-bisq-market'])) {
client['track-bisq-market'] = parsedMessage['track-bisq-market'];
} else {
client['track-bisq-market'] = null;
}
}
if (Object.keys(response).length) { if (Object.keys(response).length) {
client.send(JSON.stringify(response)); client.send(JSON.stringify(response));
} }

View File

@ -122,6 +122,7 @@ export interface WebsocketResponse {
'track-tx': string; 'track-tx': string;
'track-address': string; 'track-address': string;
'watch-mempool': boolean; 'watch-mempool': boolean;
'track-bisq-market': string;
} }
export interface VbytesPerSecond { export interface VbytesPerSecond {

View File

@ -5,6 +5,7 @@ import { ParamMap, ActivatedRoute } from '@angular/router';
import { Subscription, of } from 'rxjs'; import { Subscription, of } from 'rxjs';
import { BisqTransaction } from '../bisq.interfaces'; import { BisqTransaction } from '../bisq.interfaces';
import { BisqApiService } from '../bisq-api.service'; import { BisqApiService } from '../bisq-api.service';
import { WebsocketService } from 'src/app/services/websocket.service';
@Component({ @Component({
selector: 'app-bisq-address', selector: 'app-bisq-address',
@ -22,12 +23,15 @@ export class BisqAddressComponent implements OnInit, OnDestroy {
totalSent = 0; totalSent = 0;
constructor( constructor(
private websocketService: WebsocketService,
private route: ActivatedRoute, private route: ActivatedRoute,
private seoService: SeoService, private seoService: SeoService,
private bisqApiService: BisqApiService, private bisqApiService: BisqApiService,
) { } ) { }
ngOnInit() { ngOnInit() {
this.websocketService.want(['blocks']);
this.mainSubscription = this.route.paramMap this.mainSubscription = this.route.paramMap
.pipe( .pipe(
switchMap((params: ParamMap) => { switchMap((params: ParamMap) => {

View File

@ -8,6 +8,7 @@ import { switchMap, catchError } from 'rxjs/operators';
import { SeoService } from 'src/app/services/seo.service'; import { SeoService } from 'src/app/services/seo.service';
import { ElectrsApiService } from 'src/app/services/electrs-api.service'; import { ElectrsApiService } from 'src/app/services/electrs-api.service';
import { HttpErrorResponse } from '@angular/common/http'; import { HttpErrorResponse } from '@angular/common/http';
import { WebsocketService } from 'src/app/services/websocket.service';
@Component({ @Component({
selector: 'app-bisq-block', selector: 'app-bisq-block',
@ -23,6 +24,7 @@ export class BisqBlockComponent implements OnInit, OnDestroy {
error: HttpErrorResponse | null; error: HttpErrorResponse | null;
constructor( constructor(
private websocketService: WebsocketService,
private bisqApiService: BisqApiService, private bisqApiService: BisqApiService,
private route: ActivatedRoute, private route: ActivatedRoute,
private seoService: SeoService, private seoService: SeoService,
@ -32,6 +34,8 @@ export class BisqBlockComponent implements OnInit, OnDestroy {
) { } ) { }
ngOnInit(): void { ngOnInit(): void {
this.websocketService.want(['blocks']);
this.subscription = this.route.paramMap this.subscription = this.route.paramMap
.pipe( .pipe(
switchMap((params: ParamMap) => { switchMap((params: ParamMap) => {

View File

@ -5,6 +5,7 @@ import { Observable } from 'rxjs';
import { BisqBlock, BisqOutput, BisqTransaction } from '../bisq.interfaces'; import { BisqBlock, BisqOutput, BisqTransaction } from '../bisq.interfaces';
import { SeoService } from 'src/app/services/seo.service'; import { SeoService } from 'src/app/services/seo.service';
import { ActivatedRoute, Router } from '@angular/router'; import { ActivatedRoute, Router } from '@angular/router';
import { WebsocketService } from 'src/app/services/websocket.service';
@Component({ @Component({
selector: 'app-bisq-blocks', selector: 'app-bisq-blocks',
@ -25,6 +26,7 @@ export class BisqBlocksComponent implements OnInit {
paginationMaxSize = 10; paginationMaxSize = 10;
constructor( constructor(
private websocketService: WebsocketService,
private bisqApiService: BisqApiService, private bisqApiService: BisqApiService,
private seoService: SeoService, private seoService: SeoService,
private route: ActivatedRoute, private route: ActivatedRoute,
@ -32,6 +34,7 @@ export class BisqBlocksComponent implements OnInit {
) { } ) { }
ngOnInit(): void { ngOnInit(): void {
this.websocketService.want(['blocks']);
this.seoService.setTitle($localize`:@@8a7b4bd44c0ac71b2e72de0398b303257f7d2f54:Blocks`); this.seoService.setTitle($localize`:@@8a7b4bd44c0ac71b2e72de0398b303257f7d2f54:Blocks`);
this.itemsPerPage = Math.max(Math.round(this.contentSpace / this.fiveItemsPxSize) * 5, 10); this.itemsPerPage = Math.max(Math.round(this.contentSpace / this.fiveItemsPxSize) * 5, 10);
this.loadingItems = Array(this.itemsPerPage); this.loadingItems = Array(this.itemsPerPage);

View File

@ -1,6 +1,7 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
import { Observable, combineLatest } from 'rxjs'; import { Observable, combineLatest } from 'rxjs';
import { map } from 'rxjs/operators'; import { map } from 'rxjs/operators';
import { WebsocketService } from 'src/app/services/websocket.service';
import { BisqApiService } from '../bisq-api.service'; import { BisqApiService } from '../bisq-api.service';
@Component({ @Component({
@ -13,10 +14,13 @@ export class BisqDashboardComponent implements OnInit {
tickers$: Observable<any>; tickers$: Observable<any>;
constructor( constructor(
private websocketService: WebsocketService,
private bisqApiService: BisqApiService, private bisqApiService: BisqApiService,
) { } ) { }
ngOnInit(): void { ngOnInit(): void {
this.websocketService.want(['blocks']);
this.tickers$ = combineLatest([ this.tickers$ = combineLatest([
this.bisqApiService.getMarketsTicker$(), this.bisqApiService.getMarketsTicker$(),
this.bisqApiService.getMarkets$(), this.bisqApiService.getMarkets$(),

View File

@ -1 +0,0 @@
<router-outlet></router-outlet>

View File

@ -1,18 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { WebsocketService } from 'src/app/services/websocket.service';
@Component({
selector: 'app-bisq-explorer',
templateUrl: './bisq-explorer.component.html',
styleUrls: ['./bisq-explorer.component.scss']
})
export class BisqExplorerComponent implements OnInit {
constructor(
private websocketService: WebsocketService,
) { }
ngOnInit(): void {
this.websocketService.want(['blocks']);
}
}

View File

@ -1,8 +1,9 @@
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms'; import { FormBuilder, FormGroup } from '@angular/forms';
import { ActivatedRoute } from '@angular/router'; import { ActivatedRoute } from '@angular/router';
import { combineLatest, merge, Observable, of } from 'rxjs'; import { combineLatest, merge, Observable, of } from 'rxjs';
import { filter, map, mergeAll, switchMap, tap } from 'rxjs/operators'; import { filter, map, mergeAll, switchMap, tap } from 'rxjs/operators';
import { WebsocketService } from 'src/app/services/websocket.service';
import { BisqApiService } from '../bisq-api.service'; import { BisqApiService } from '../bisq-api.service';
@Component({ @Component({
@ -11,7 +12,7 @@ import { BisqApiService } from '../bisq-api.service';
styleUrls: ['./bisq-market.component.scss'], styleUrls: ['./bisq-market.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class BisqMarketComponent implements OnInit { export class BisqMarketComponent implements OnInit, OnDestroy {
hlocData$: Observable<any>; hlocData$: Observable<any>;
currency$: Observable<any>; currency$: Observable<any>;
offers$: Observable<any>; offers$: Observable<any>;
@ -19,6 +20,7 @@ export class BisqMarketComponent implements OnInit {
defaultInterval = 'half_hour'; defaultInterval = 'half_hour';
constructor( constructor(
private websocketService: WebsocketService,
private route: ActivatedRoute, private route: ActivatedRoute,
private bisqApiService: BisqApiService, private bisqApiService: BisqApiService,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
@ -44,6 +46,7 @@ export class BisqMarketComponent implements OnInit {
this.offers$ = this.route.paramMap this.offers$ = this.route.paramMap
.pipe( .pipe(
map(routeParams => routeParams.get('pair')), map(routeParams => routeParams.get('pair')),
tap((marketPair) => this.websocketService.startTrackBisqMarket(marketPair)),
switchMap((marketPair) => this.bisqApiService.getMarketOffers$(marketPair)), switchMap((marketPair) => this.bisqApiService.getMarketOffers$(marketPair)),
map((offers) => { map((offers) => {
return offers[Object.keys(offers)[0]]; return offers[Object.keys(offers)[0]];
@ -68,4 +71,8 @@ export class BisqMarketComponent implements OnInit {
); );
} }
ngOnDestroy(): void {
this.websocketService.stopTrackingBisqMarket();
}
} }

View File

@ -3,6 +3,7 @@ import { BisqApiService } from '../bisq-api.service';
import { BisqStats } from '../bisq.interfaces'; import { BisqStats } from '../bisq.interfaces';
import { SeoService } from 'src/app/services/seo.service'; import { SeoService } from 'src/app/services/seo.service';
import { StateService } from 'src/app/services/state.service'; import { StateService } from 'src/app/services/state.service';
import { WebsocketService } from 'src/app/services/websocket.service';
@Component({ @Component({
selector: 'app-bisq-stats', selector: 'app-bisq-stats',
@ -15,12 +16,15 @@ export class BisqStatsComponent implements OnInit {
price: number; price: number;
constructor( constructor(
private websocketService: WebsocketService,
private bisqApiService: BisqApiService, private bisqApiService: BisqApiService,
private seoService: SeoService, private seoService: SeoService,
private stateService: StateService, private stateService: StateService,
) { } ) { }
ngOnInit() { ngOnInit() {
this.websocketService.want(['blocks']);
this.seoService.setTitle($localize`:@@2a30a4cdb123a03facc5ab8c5b3e6d8b8dbbc3d4:BSQ statistics`); this.seoService.setTitle($localize`:@@2a30a4cdb123a03facc5ab8c5b3e6d8b8dbbc3d4:BSQ statistics`);
this.stateService.bsqPrice$ this.stateService.bsqPrice$
.subscribe((bsqPrice) => { .subscribe((bsqPrice) => {

View File

@ -9,6 +9,7 @@ import { BisqApiService } from '../bisq-api.service';
import { SeoService } from 'src/app/services/seo.service'; import { SeoService } from 'src/app/services/seo.service';
import { ElectrsApiService } from 'src/app/services/electrs-api.service'; import { ElectrsApiService } from 'src/app/services/electrs-api.service';
import { HttpErrorResponse } from '@angular/common/http'; import { HttpErrorResponse } from '@angular/common/http';
import { WebsocketService } from 'src/app/services/websocket.service';
@Component({ @Component({
selector: 'app-bisq-transaction', selector: 'app-bisq-transaction',
@ -27,6 +28,7 @@ export class BisqTransactionComponent implements OnInit, OnDestroy {
subscription: Subscription; subscription: Subscription;
constructor( constructor(
private websocketService: WebsocketService,
private route: ActivatedRoute, private route: ActivatedRoute,
private bisqApiService: BisqApiService, private bisqApiService: BisqApiService,
private electrsApiService: ElectrsApiService, private electrsApiService: ElectrsApiService,
@ -36,6 +38,8 @@ export class BisqTransactionComponent implements OnInit, OnDestroy {
) { } ) { }
ngOnInit(): void { ngOnInit(): void {
this.websocketService.want(['blocks']);
this.subscription = this.route.paramMap.pipe( this.subscription = this.route.paramMap.pipe(
switchMap((params: ParamMap) => { switchMap((params: ParamMap) => {
this.isLoading = true; this.isLoading = true;

View File

@ -8,6 +8,7 @@ import { SeoService } from 'src/app/services/seo.service';
import { FormGroup, FormBuilder } from '@angular/forms'; import { FormGroup, FormBuilder } from '@angular/forms';
import { Router, ActivatedRoute } from '@angular/router'; import { Router, ActivatedRoute } from '@angular/router';
import { IMultiSelectOption, IMultiSelectSettings, IMultiSelectTexts } from 'ngx-bootrap-multiselect'; import { IMultiSelectOption, IMultiSelectSettings, IMultiSelectTexts } from 'ngx-bootrap-multiselect';
import { WebsocketService } from 'src/app/services/websocket.service';
@Component({ @Component({
selector: 'app-bisq-transactions', selector: 'app-bisq-transactions',
@ -65,6 +66,7 @@ export class BisqTransactionsComponent implements OnInit {
'PROOF_OF_BURN', 'PROPOSAL', 'REIMBURSEMENT_REQUEST', 'TRANSFER_BSQ', 'UNLOCK', 'VOTE_REVEAL', 'IRREGULAR']; 'PROOF_OF_BURN', 'PROPOSAL', 'REIMBURSEMENT_REQUEST', 'TRANSFER_BSQ', 'UNLOCK', 'VOTE_REVEAL', 'IRREGULAR'];
constructor( constructor(
private websocketService: WebsocketService,
private bisqApiService: BisqApiService, private bisqApiService: BisqApiService,
private seoService: SeoService, private seoService: SeoService,
private formBuilder: FormBuilder, private formBuilder: FormBuilder,
@ -74,6 +76,7 @@ export class BisqTransactionsComponent implements OnInit {
) { } ) { }
ngOnInit(): void { ngOnInit(): void {
this.websocketService.want(['blocks']);
this.seoService.setTitle($localize`:@@add4cd82e3e38a3110fe67b3c7df56e9602644ee:Transactions`); this.seoService.setTitle($localize`:@@add4cd82e3e38a3110fe67b3c7df56e9602644ee:Transactions`);
this.radioGroupForm = this.formBuilder.group({ this.radioGroupForm = this.formBuilder.group({

View File

@ -17,7 +17,6 @@ import { FontAwesomeModule, FaIconLibrary } from '@fortawesome/angular-fontaweso
import { faLeaf, faQuestion, faExclamationTriangle, faRocket, faRetweet, faFileAlt, faMoneyBill, import { faLeaf, faQuestion, faExclamationTriangle, faRocket, faRetweet, faFileAlt, faMoneyBill,
faEye, faEyeSlash, faLock, faLockOpen, faExclamationCircle } from '@fortawesome/free-solid-svg-icons'; faEye, faEyeSlash, faLock, faLockOpen, faExclamationCircle } from '@fortawesome/free-solid-svg-icons';
import { BisqBlocksComponent } from './bisq-blocks/bisq-blocks.component'; import { BisqBlocksComponent } from './bisq-blocks/bisq-blocks.component';
import { BisqExplorerComponent } from './bisq-explorer/bisq-explorer.component';
import { BisqApiService } from './bisq-api.service'; import { BisqApiService } from './bisq-api.service';
import { BisqAddressComponent } from './bisq-address/bisq-address.component'; import { BisqAddressComponent } from './bisq-address/bisq-address.component';
import { BisqStatsComponent } from './bisq-stats/bisq-stats.component'; import { BisqStatsComponent } from './bisq-stats/bisq-stats.component';
@ -33,7 +32,6 @@ import { BsqAmountComponent } from './bsq-amount/bsq-amount.component';
BisqTransactionDetailsComponent, BisqTransactionDetailsComponent,
BisqTransfersComponent, BisqTransfersComponent,
BisqBlocksComponent, BisqBlocksComponent,
BisqExplorerComponent,
BisqAddressComponent, BisqAddressComponent,
BisqStatsComponent, BisqStatsComponent,
BsqAmountComponent, BsqAmountComponent,

View File

@ -5,7 +5,6 @@ import { BisqTransactionsComponent } from './bisq-transactions/bisq-transactions
import { BisqTransactionComponent } from './bisq-transaction/bisq-transaction.component'; import { BisqTransactionComponent } from './bisq-transaction/bisq-transaction.component';
import { BisqBlockComponent } from './bisq-block/bisq-block.component'; import { BisqBlockComponent } from './bisq-block/bisq-block.component';
import { BisqBlocksComponent } from './bisq-blocks/bisq-blocks.component'; import { BisqBlocksComponent } from './bisq-blocks/bisq-blocks.component';
import { BisqExplorerComponent } from './bisq-explorer/bisq-explorer.component';
import { BisqAddressComponent } from './bisq-address/bisq-address.component'; import { BisqAddressComponent } from './bisq-address/bisq-address.component';
import { BisqStatsComponent } from './bisq-stats/bisq-stats.component'; import { BisqStatsComponent } from './bisq-stats/bisq-stats.component';
import { ApiDocsComponent } from '../components/api-docs/api-docs.component'; import { ApiDocsComponent } from '../components/api-docs/api-docs.component';
@ -14,56 +13,50 @@ import { BisqMarketComponent } from './bisq-market/bisq-market.component';
const routes: Routes = [ const routes: Routes = [
{ {
path: '', path: '',
component: BisqExplorerComponent, component: BisqDashboardComponent,
children: [ },
{ {
path: '', path: 'transactions',
component: BisqDashboardComponent, component: BisqTransactionsComponent
}, },
{ {
path: 'transactions', path: 'market/:pair',
component: BisqTransactionsComponent component: BisqMarketComponent,
}, },
{ {
path: 'market/:pair', path: 'tx/:id',
component: BisqMarketComponent, component: BisqTransactionComponent
}, },
{ {
path: 'tx/:id', path: 'blocks',
component: BisqTransactionComponent children: [],
}, component: BisqBlocksComponent
{ },
path: 'blocks', {
children: [], path: 'block/:id',
component: BisqBlocksComponent component: BisqBlockComponent,
}, },
{ {
path: 'block/:id', path: 'address/:id',
component: BisqBlockComponent, component: BisqAddressComponent,
}, },
{ {
path: 'address/:id', path: 'stats',
component: BisqAddressComponent, component: BisqStatsComponent,
}, },
{ {
path: 'stats', path: 'about',
component: BisqStatsComponent, component: AboutComponent,
}, },
{ {
path: 'about', path: 'api',
component: AboutComponent, component: ApiDocsComponent,
}, },
{ {
path: 'api', path: '**',
component: ApiDocsComponent, redirectTo: ''
}, }
{
path: '**',
redirectTo: ''
}
]
}
]; ];
@NgModule({ @NgModule({

View File

@ -20,6 +20,7 @@ export interface WebsocketResponse {
'track-address'?: string; 'track-address'?: string;
'track-asset'?: string; 'track-asset'?: string;
'watch-mempool'?: boolean; 'watch-mempool'?: boolean;
'track-bisq-market'?: string;
} }
export interface MempoolBlock { export interface MempoolBlock {

View File

@ -23,7 +23,7 @@ export class WebsocketService {
private websocketSubject: WebSocketSubject<WebsocketResponse>; private websocketSubject: WebSocketSubject<WebsocketResponse>;
private goneOffline = false; private goneOffline = false;
private lastWant: string[] | null = null; private lastWant: string | null = null;
private isTrackingTx = false; private isTrackingTx = false;
private latestGitCommit = ''; private latestGitCommit = '';
private onlineCheckTimeout: number; private onlineCheckTimeout: number;
@ -95,7 +95,7 @@ export class WebsocketService {
if (this.goneOffline === true) { if (this.goneOffline === true) {
this.goneOffline = false; this.goneOffline = false;
if (this.lastWant) { if (this.lastWant) {
this.want(this.lastWant, true); this.want(JSON.parse(this.lastWant), true);
} }
this.stateService.connectionState$.next(2); this.stateService.connectionState$.next(2);
} }
@ -150,6 +150,14 @@ export class WebsocketService {
this.websocketSubject.next({ 'track-asset': 'stop' }); this.websocketSubject.next({ 'track-asset': 'stop' });
} }
startTrackBisqMarket(market: string) {
this.websocketSubject.next({ 'track-bisq-market': market });
}
stopTrackingBisqMarket() {
this.websocketSubject.next({ 'track-bisq-market': 'stop' });
}
fetchStatistics(historicalDate: string) { fetchStatistics(historicalDate: string) {
this.websocketSubject.next({ historicalDate }); this.websocketSubject.next({ historicalDate });
} }
@ -158,11 +166,11 @@ export class WebsocketService {
if (!this.stateService.isBrowser) { if (!this.stateService.isBrowser) {
return; return;
} }
if (data === this.lastWant && !force) { if (JSON.stringify(data) === this.lastWant && !force) {
return; return;
} }
this.websocketSubject.next({action: 'want', data: data}); this.websocketSubject.next({action: 'want', data: data});
this.lastWant = data; this.lastWant = JSON.stringify(data);
} }
goOffline() { goOffline() {