Release 0.10.0 (#571)
Channel backup download file bug fix #536 Added macaroon authentication for Loop (#543) Adding Label for Loop In & Loop Out #538 Fee Report & Routing Enhancements (#555) Payments report #559 Transactions Report #357 Material table sorting bug fix #556 CL & ECL ng Routing #551 & Hocon Read Fix #560 (#561) CLT & ECL Reports (#562) UI Bug fixes for tables group sort, pagination, dialog and spinner close Increased request body size #544 (#564) App lock after 5 attempts #542 & DatePicker default adapter #532 (#566) Upgade Angular 11 (#568) Loop amount validation #569 Loop https document updatespull/577/head v0.10.0
parent
6e036d8382
commit
5a38585b71
@ -0,0 +1,17 @@
|
||||
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
|
||||
# For additional information regarding the format and rule options, please see:
|
||||
# https://github.com/browserslist/browserslist#queries
|
||||
|
||||
# For the full list of supported browsers by the Angular framework, please see:
|
||||
# https://angular.io/guide/browser-support
|
||||
|
||||
# You can see what browsers were selected by your queries by running:
|
||||
# npx browserslist
|
||||
|
||||
last 1 Chrome version
|
||||
last 1 Firefox version
|
||||
last 2 Edge major versions
|
||||
last 2 Safari major versions
|
||||
last 2 iOS major versions
|
||||
Firefox ESR
|
||||
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1 @@
|
||||
!function(e){function r(r){for(var n,a,i=r[0],c=r[1],f=r[2],p=0,s=[];p<i.length;p++)a=i[p],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(l&&l(r);s.length;)s.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++)0!==o[t[i]]&&(n=!1);n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={0:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise(function(r,n){t=o[e]=[r,n]});r.push(t[2]=n);var u,i=document.createElement("script");i.charset="utf-8",i.timeout=120,a.nc&&i.setAttribute("nonce",a.nc),i.src=function(e){return a.p+""+({}[e]||e)+"."+{1:"d3191dc862bb96ad8a12",5:"3593974ed7a18cef9807",6:"a0ce69bc4fc32a59e836",7:"2a4fb50bff6654155ed9"}[e]+".js"}(e);var c=new Error;u=function(r){i.onerror=i.onload=null,clearTimeout(f);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var f=setTimeout(function(){u({type:"timeout",target:i})},12e4);i.onerror=i.onload=u,document.head.appendChild(i)}return Promise.all(r)},a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,(function(r){return e[r]}).bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="",a.oe=function(e){throw console.error(e),e};var i=window.webpackJsonp=window.webpackJsonp||[],c=i.push.bind(i);i.push=r,i=i.slice();for(var f=0;f<i.length;f++)r(i[f]);var l=c;t()}([]);
|
@ -1 +0,0 @@
|
||||
!function(e){function r(r){for(var n,a,i=r[0],c=r[1],f=r[2],p=0,s=[];p<i.length;p++)a=i[p],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(l&&l(r);s.length;)s.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++)0!==o[t[i]]&&(n=!1);n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={0:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,i=document.createElement("script");i.charset="utf-8",i.timeout=120,a.nc&&i.setAttribute("nonce",a.nc),i.src=function(e){return a.p+""+({}[e]||e)+"."+{1:"60ddf8569c2860edb59c",6:"d5f36502e36ce33775a8",7:"a355238233f02a06308f",8:"29ee25c8adea70ea041a"}[e]+".js"}(e);var c=new Error;u=function(r){i.onerror=i.onload=null,clearTimeout(f);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var f=setTimeout((function(){u({type:"timeout",target:i})}),12e4);i.onerror=i.onload=u,document.head.appendChild(i)}return Promise.all(r)},a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,(function(r){return e[r]}).bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="",a.oe=function(e){throw console.error(e),e};var i=window.webpackJsonp=window.webpackJsonp||[],c=i.push.bind(i);i.push=r,i=i.slice();for(var f=0;f<i.length;f++)r(i[f]);var l=c;t()}([]);
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,11 +1,11 @@
|
||||
import { browser, by, element } from 'protractor';
|
||||
|
||||
export class AppPage {
|
||||
navigateTo() {
|
||||
return browser.get('/');
|
||||
async navigateTo(): Promise<unknown> {
|
||||
return browser.get(browser.baseUrl);
|
||||
}
|
||||
|
||||
getParagraphText() {
|
||||
return element(by.css('app-root h1')).getText();
|
||||
async getTitleText(): Promise<string> {
|
||||
return element(by.css('app-root .content span')).getText();
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/app",
|
||||
"outDir": "../out-tsc/e2e",
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"target": "es2018",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"jasminewd2",
|
||||
"node"
|
||||
]
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,22 +1,30 @@
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
import { Channel } from '../../../shared/models/clModels';
|
||||
import { ScreenSizeEnum } from '../../../shared/services/consts-enums-functions';
|
||||
import { CommonService } from '../../../shared/services/common.service';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-cl-channel-liquidity-info',
|
||||
templateUrl: './channel-liquidity-info.component.html',
|
||||
styleUrls: ['./channel-liquidity-info.component.scss']
|
||||
})
|
||||
export class CLChannelLiquidityInfoComponent {
|
||||
export class CLChannelLiquidityInfoComponent implements OnInit {
|
||||
@Input() direction: string;
|
||||
@Input() totalLiquidity: number;
|
||||
@Input() allChannels: Channel[];
|
||||
public screenSize = '';
|
||||
public screenSizeEnum = ScreenSizeEnum;
|
||||
|
||||
constructor(private router: Router) {}
|
||||
constructor(private router: Router, private commonService: CommonService) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.screenSize = this.commonService.getScreenSize();
|
||||
}
|
||||
|
||||
goToChannels() {
|
||||
this.router.navigateByUrl('/cl/peerschannels');
|
||||
this.router.navigateByUrl('/cl/connections');
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,17 +0,0 @@
|
||||
.dashboard-card {
|
||||
position: absolute;
|
||||
top: 1rem;
|
||||
left: 1rem;
|
||||
right: 1rem;
|
||||
bottom: 1rem;
|
||||
}
|
||||
|
||||
.more-button {
|
||||
position: absolute;
|
||||
top: 7px;
|
||||
right: 7px;
|
||||
}
|
||||
|
||||
.dashboard-card-content {
|
||||
text-align: left;
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CLOnChainSendModalComponent } from './on-chain-send-modal.component';
|
||||
|
||||
describe('CLOnChainSendModalComponent', () => {
|
||||
let component: CLOnChainSendModalComponent;
|
||||
let fixture: ComponentFixture<CLOnChainSendModalComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CLOnChainSendModalComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CLOnChainSendModalComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,5 @@
|
||||
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="padding-gap-x-large">
|
||||
<div fxLayout="row">
|
||||
<button mat-flat-button color="primary" type="button" tabindex="1" (click)="openSendFundsModal()">{{sweepAll ? 'Sweep All' : 'Send Funds'}}</button>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,41 @@
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { CLOnChainSendModalComponent } from '../on-chain-send-modal/on-chain-send-modal.component';
|
||||
|
||||
import * as RTLActions from '../../../store/rtl.actions';
|
||||
import * as fromRTLReducer from '../../../store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-cl-on-chain-send',
|
||||
templateUrl: './on-chain-send.component.html',
|
||||
styleUrls: ['./on-chain-send.component.scss']
|
||||
})
|
||||
export class CLOnChainSendComponent implements OnInit, OnDestroy {
|
||||
public sweepAll = false;
|
||||
private unSubs: Array<Subject<void>> = [new Subject(), new Subject()];
|
||||
|
||||
constructor(private store: Store<fromRTLReducer.RTLState>, private activatedRoute: ActivatedRoute) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.activatedRoute.data.pipe(takeUntil(this.unSubs[0])).subscribe(routeData => this.sweepAll = routeData.sweepAll);
|
||||
}
|
||||
|
||||
openSendFundsModal() {
|
||||
this.store.dispatch(new RTLActions.OpenAlert({ data: {
|
||||
sweepAll: this.sweepAll,
|
||||
component: CLOnChainSendModalComponent
|
||||
}}));
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unSubs.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -1,16 +1,22 @@
|
||||
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="padding-gap-x">
|
||||
<div fxLayout="row">
|
||||
<button mat-flat-button color="primary" (click)="onOpenChannel()" type="submit" tabindex="1">Open Channel</button>
|
||||
</div>
|
||||
<div fxLayout="column" fxFlex="100" class="mt-2 bordered-box">
|
||||
<mat-tab-group>
|
||||
<mat-tab-group [(selectedIndex)]="activeLink" (selectedTabChange)="onSelectedTabChange($event)">
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<span matBadge="{{openChannels}}" matBadgeOverlap="false" class="tab-badge">Open</span>
|
||||
</ng-template>
|
||||
<rtl-cl-channel-open-table></rtl-cl-channel-open-table>
|
||||
</mat-tab>
|
||||
<mat-tab>
|
||||
<ng-template mat-tab-label>
|
||||
<span matBadge="{{pendingChannels}}" matBadgeOverlap="false" class="tab-badge">Pending/Inactive</span>
|
||||
</ng-template>
|
||||
<rtl-cl-channel-pending-table></rtl-cl-channel-pending-table>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="padding-gap-x-large">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,20 +1,20 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PeersChannelsComponent } from './peers-channels.component';
|
||||
import { CLConnectionsComponent } from './connections.component';
|
||||
|
||||
describe('PeersChannelsComponent', () => {
|
||||
let component: PeersChannelsComponent;
|
||||
let fixture: ComponentFixture<PeersChannelsComponent>;
|
||||
describe('CLConnectionsComponent', () => {
|
||||
let component: CLConnectionsComponent;
|
||||
let fixture: ComponentFixture<CLConnectionsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ PeersChannelsComponent ]
|
||||
declarations: [ CLConnectionsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(PeersChannelsComponent);
|
||||
fixture = TestBed.createComponent(CLConnectionsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
@ -1,68 +1,52 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Router, ResolveEnd } from '@angular/router';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { takeUntil, filter } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { faUsers, faChartPie } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import { GetInfo, Peer, Transaction } from '../../shared/models/clModels';
|
||||
import { CLOpenChannelComponent } from './channels/open-channel-modal/open-channel.component';
|
||||
import { SelNodeChild } from '../../shared/models/RTLconfig';
|
||||
import { LoggerService } from '../../shared/services/logger.service';
|
||||
import { CommonService } from '../../shared/services/common.service';
|
||||
|
||||
import * as RTLActions from '../../store/rtl.actions';
|
||||
import * as fromRTLReducer from '../../store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-cl-peers-channels',
|
||||
templateUrl: './peers-channels.component.html',
|
||||
styleUrls: ['./peers-channels.component.scss']
|
||||
selector: 'rtl-cl-connections',
|
||||
templateUrl: './connections.component.html',
|
||||
styleUrls: ['./connections.component.scss']
|
||||
})
|
||||
export class CLPeersChannelsComponent implements OnInit, OnDestroy {
|
||||
public selNode: SelNodeChild = {};
|
||||
public information: GetInfo = {};
|
||||
public peers: Peer[] = [];
|
||||
public transactions: Transaction[] = [];
|
||||
public totalBalance = 0;
|
||||
export class CLConnectionsComponent implements OnInit, OnDestroy {
|
||||
public activePeers = 0;
|
||||
public activeChannels = 0;
|
||||
public faUsers = faUsers;
|
||||
public faChartPie = faChartPie;
|
||||
public balances = [{title: 'Total Balance', dataValue: 0}, {title: 'Confirmed', dataValue: 0}, {title: 'Unconfirmed', dataValue: 0}];
|
||||
public links = [{link: 'channels', name: 'Channels'}, {link: 'peers', name: 'Peers'}];
|
||||
public activeLink = 0;
|
||||
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
|
||||
|
||||
constructor(private store: Store<fromRTLReducer.RTLState>, private logger: LoggerService, private commonService: CommonService) {}
|
||||
constructor(private store: Store<fromRTLReducer.RTLState>, private logger: LoggerService, private commonService: CommonService, private router: Router) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.activeLink = this.links.findIndex(link => link.link === this.router.url.substring(this.router.url.lastIndexOf('/') + 1));
|
||||
this.router.events.pipe(takeUntil(this.unSubs[0]), filter(e => e instanceof ResolveEnd))
|
||||
.subscribe((value: ResolveEnd) => {
|
||||
this.activeLink = this.links.findIndex(link => link.link === value.urlAfterRedirects.substring(value.urlAfterRedirects.lastIndexOf('/') + 1));
|
||||
});
|
||||
this.store.select('cl')
|
||||
.pipe(takeUntil(this.unSubs[1]))
|
||||
.subscribe((rtlStore) => {
|
||||
this.selNode = rtlStore.nodeSettings;
|
||||
this.information = rtlStore.information;
|
||||
this.peers = rtlStore.peers;
|
||||
this.transactions = this.commonService.sortAscByKey(rtlStore.transactions.filter(tran => tran.status === 'confirmed'), 'value');
|
||||
this.activePeers = (rtlStore.peers && rtlStore.peers.length) ? rtlStore.peers.length : 0;
|
||||
this.activeChannels = rtlStore.information.num_active_channels;
|
||||
this.totalBalance = rtlStore.balance.totalBalance;
|
||||
this.balances = [{title: 'Total Balance', dataValue: rtlStore.balance.totalBalance || 0}, {title: 'Confirmed', dataValue: rtlStore.balance.confBalance}, {title: 'Unconfirmed', dataValue: rtlStore.balance.unconfBalance}];
|
||||
this.logger.info(rtlStore);
|
||||
});
|
||||
}
|
||||
|
||||
onOpenChannel() {
|
||||
const peerToAddChannelMessage = {
|
||||
peers: this.peers,
|
||||
information: this.information,
|
||||
balance: this.totalBalance,
|
||||
transactions: this.transactions,
|
||||
isCompatibleVersion: this.commonService.isVersionCompatible(this.information.version, '0.9.0') &&
|
||||
this.commonService.isVersionCompatible(this.information.api_version, '0.4.0')
|
||||
};
|
||||
this.store.dispatch(new RTLActions.OpenAlert({ data: {
|
||||
alertTitle: 'Open Channel',
|
||||
message: peerToAddChannelMessage,
|
||||
component: CLOpenChannelComponent
|
||||
}}));
|
||||
onSelectedTabChange(event) {
|
||||
this.router.navigateByUrl('/cl/connections/' + this.links[event.index].link);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
@ -0,0 +1,35 @@
|
||||
<div fxLayout="column" fxLayoutAlign="start stretch" fxFlex="100" class="padding-gap-x-large">
|
||||
<rtl-horizontal-scroller (stepChanged)="onSelectionChange($event)"></rtl-horizontal-scroller>
|
||||
<div fxLayout="column" fxLayoutAlign="start stretch" fxFlex="100" class="padding-gap-x">
|
||||
<div *ngIf="feeReportData.length > 0 && filteredEventsBySelectedPeriod.length > 0" fxLayout="column" fxLayoutAlign="center center" fxFlex="100" class="font-size-120 font-bold-700 mt-1"
|
||||
[@fadeIn]="totalFeeMsat">{{(totalFeeMsat / 1000 || 0) | number:'1.0-0'}} Sats/{{(filteredEventsBySelectedPeriod.length || 0) | number}} Events</div>
|
||||
<div *ngIf="feeReportData.length <= 0 || filteredEventsBySelectedPeriod.length <= 0" fxLayout="column" fxLayoutAlign="center center" fxFlex="100" class="font-size-120 mt-1">No fee report for the selected period</div>
|
||||
<div class="mt-1">
|
||||
<ngx-charts-bar-vertical
|
||||
*ngIf="feeReportData.length > 0 && filteredEventsBySelectedPeriod.length > 0"
|
||||
[view]="view"
|
||||
[results]="feeReportData"
|
||||
[gradient]="false"
|
||||
[xAxis]="true"
|
||||
[yAxis]="true"
|
||||
[showXAxisLabel]="true"
|
||||
[showYAxisLabel]="showYAxisLabel"
|
||||
[xAxisLabel]="xAxisLabel"
|
||||
[yAxisLabel]="yAxisLabel"
|
||||
[showGridLines]="false"
|
||||
[showDataLabel]="false"
|
||||
(select)="onChartBarSelected($event)"
|
||||
(mouseup)="onChartMouseUp($event)">
|
||||
<ng-template #tooltipTemplate let-model="model">
|
||||
<span>
|
||||
<span class="tooltip-label">Events: {{(model.extra.totalEvents || 0) | number}}</span>
|
||||
<span class="tooltip-label">Fee: {{(model.value || 0) | number:'1.0-0'}}</span>
|
||||
</span>
|
||||
</ng-template>
|
||||
</ngx-charts-bar-vertical>
|
||||
</div>
|
||||
<div class="mt-1">
|
||||
<rtl-cl-forwarding-history *ngIf="filteredEventsBySelectedPeriod && filteredEventsBySelectedPeriod.length > 0" [eventsData]="filteredEventsBySelectedPeriod" [filterValue]="eventFilterValue"></rtl-cl-forwarding-history>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,31 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { DataService } from '../../../shared/services/data.service';
|
||||
|
||||
import { CLFeeReportComponent } from './fee-report.component';
|
||||
|
||||
describe('CLFeeReportComponent', () => {
|
||||
let component: CLFeeReportComponent;
|
||||
let fixture: ComponentFixture<CLFeeReportComponent>;
|
||||
const mockDataService = jasmine.createSpyObj("DataService", ["getChildAPIUrl","setChildAPIUrl","getFiatRates",
|
||||
"getAliasesFromPubkeys","signMessage","verifyMessage","handleErrorWithoutAlert","handleErrorWithAlert"]);
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CLFeeReportComponent ],
|
||||
providers: [
|
||||
{ provide: DataService, useValue: mockDataService }
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CLFeeReportComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,164 @@
|
||||
import { Component, OnInit, OnDestroy, HostListener, AfterViewInit } from '@angular/core';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { ForwardingHistoryRes, ForwardingEvent } from '../../../shared/models/clModels';
|
||||
import { MONTHS, ScreenSizeEnum, SCROLL_RANGES } from '../../../shared/services/consts-enums-functions';
|
||||
import { LoggerService } from '../../../shared/services/logger.service';
|
||||
import { CommonService } from '../../../shared/services/common.service';
|
||||
import { DataService } from '../../../shared/services/data.service';
|
||||
import { fadeIn } from '../../../shared/animation/opacity-animation';
|
||||
|
||||
import * as fromRTLReducer from '../../../store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-cl-fee-report',
|
||||
templateUrl: './fee-report.component.html',
|
||||
styleUrls: ['./fee-report.component.scss'],
|
||||
animations: [fadeIn]
|
||||
})
|
||||
export class CLFeeReportComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
public reportPeriod = SCROLL_RANGES[0];
|
||||
public secondsInADay = 24 * 60 * 60;
|
||||
public events: ForwardingHistoryRes = {};
|
||||
public filteredEventsBySelectedPeriod: ForwardingEvent[] = [];
|
||||
public eventFilterValue = '';
|
||||
public errorMessage = '';
|
||||
public totalFeeMsat = null;
|
||||
public today = new Date(Date.now());
|
||||
public timezoneOffset = this.today.getTimezoneOffset() * 60;
|
||||
public startDate = new Date(this.today.getFullYear(), this.today.getMonth(), 1, 0, 0, 0);
|
||||
public endDate = new Date(this.today.getFullYear(), this.today.getMonth(), this.getMonthDays(this.today.getMonth(), this.today.getFullYear()), 23, 59, 59);
|
||||
public feeReportData: any = [];
|
||||
public view: [number, number] = [350, 350];
|
||||
public screenPaddingX = 100;
|
||||
public gradient = true;
|
||||
public xAxisLabel = 'Date';
|
||||
public yAxisLabel = 'Fee (Sats)';
|
||||
public showYAxisLabel = true;
|
||||
public screenSize = '';
|
||||
public screenSizeEnum = ScreenSizeEnum;
|
||||
private unSubs: Array<Subject<void>> = [new Subject(), new Subject()];
|
||||
|
||||
constructor(private logger: LoggerService, private dataService: DataService, private commonService: CommonService, private store: Store<fromRTLReducer.RTLState>) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.screenSize = this.commonService.getScreenSize();
|
||||
this.showYAxisLabel = !(this.screenSize === ScreenSizeEnum.XS || this.screenSize === ScreenSizeEnum.SM);
|
||||
this.store.select('cl')
|
||||
.pipe(takeUntil(this.unSubs[0]))
|
||||
.subscribe((rtlStore) => {
|
||||
this.errorMessage = '';
|
||||
rtlStore.effectErrors.forEach(effectsErr => {
|
||||
if (effectsErr.action === 'GetForwardingHistory') {
|
||||
this.errorMessage = (typeof(effectsErr.message) === 'object') ? JSON.stringify(effectsErr.message) : effectsErr.message;
|
||||
}
|
||||
});
|
||||
this.events = (rtlStore.forwardingHistory && rtlStore.forwardingHistory.forwarding_events) ? rtlStore.forwardingHistory : {};
|
||||
this.filterForwardingEvents(this.startDate, this.endDate);
|
||||
this.logger.info(rtlStore);
|
||||
});
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
const CONTAINER_SIZE = this.commonService.getContainerSize();
|
||||
switch (this.screenSize) {
|
||||
case ScreenSizeEnum.MD:
|
||||
this.screenPaddingX = CONTAINER_SIZE.width/10;
|
||||
break;
|
||||
|
||||
case ScreenSizeEnum.LG:
|
||||
this.screenPaddingX = CONTAINER_SIZE.width/16;
|
||||
break;
|
||||
|
||||
default:
|
||||
this.screenPaddingX = CONTAINER_SIZE.width/20;
|
||||
break;
|
||||
}
|
||||
this.view = [CONTAINER_SIZE.width - this.screenPaddingX, CONTAINER_SIZE.height/2.2];
|
||||
}
|
||||
|
||||
filterForwardingEvents(start: Date, end: Date) {
|
||||
const startDateInSeconds = (Math.round(start.getTime()/1000) - this.timezoneOffset);
|
||||
const endDateInSeconds = (Math.round(end.getTime()/1000) - this.timezoneOffset);
|
||||
this.filteredEventsBySelectedPeriod = [];
|
||||
this.feeReportData = [];
|
||||
this.totalFeeMsat = null;
|
||||
if (this.events && this.events.forwarding_events && this.events.forwarding_events.length > 0) {
|
||||
this.events.forwarding_events.forEach(event => {
|
||||
if (event.status === 'settled' && event.received_time >= startDateInSeconds && event.received_time < endDateInSeconds) {
|
||||
this.filteredEventsBySelectedPeriod.push(event);
|
||||
}
|
||||
});
|
||||
this.feeReportData = this.prepareFeeReport(start);
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('mouseup', ['$event']) onChartMouseUp(e) {
|
||||
if (e.srcElement.tagName === 'svg' && e.srcElement.classList.length > 0 && e.srcElement.classList[0] === 'ngx-charts') {
|
||||
this.eventFilterValue = '';
|
||||
}
|
||||
}
|
||||
|
||||
onChartBarSelected(event) {
|
||||
if(this.reportPeriod === SCROLL_RANGES[1]) {
|
||||
this.eventFilterValue = event.name.toUpperCase() + '/' + this.startDate.getFullYear();
|
||||
} else {
|
||||
this.eventFilterValue = event.name.toString().padStart(2, '0') + '/' + MONTHS[this.startDate.getMonth()].name.toUpperCase() + '/' + this.startDate.getFullYear();
|
||||
}
|
||||
}
|
||||
|
||||
prepareFeeReport(start: Date) {
|
||||
const startDateInSeconds = Math.round(start.getTime()/1000) - this.timezoneOffset;
|
||||
let feeReport = [];
|
||||
if (this.reportPeriod === SCROLL_RANGES[1]) {
|
||||
for (let i = 0; i < 12; i++) {
|
||||
feeReport.push({name: MONTHS[i].name, value: 0.000000001, extra: {totalEvents: 0}});
|
||||
}
|
||||
this.filteredEventsBySelectedPeriod.map(event => {
|
||||
let monthNumber = new Date((+event.received_time + this.timezoneOffset)*1000).getMonth();
|
||||
feeReport[monthNumber].value = feeReport[monthNumber].value + (+event.fee / 1000);
|
||||
feeReport[monthNumber].extra.totalEvents = feeReport[monthNumber].extra.totalEvents + 1;
|
||||
this.totalFeeMsat = (this.totalFeeMsat ? this.totalFeeMsat : 0) + +event.fee;
|
||||
});
|
||||
} else {
|
||||
for (let i = 0; i < this.getMonthDays(start.getMonth(), start.getFullYear()); i++) {
|
||||
feeReport.push({name: i + 1, value: 0.000000001, extra: {totalEvents: 0}});
|
||||
}
|
||||
this.filteredEventsBySelectedPeriod.map(event => {
|
||||
let dateNumber = Math.floor((+event.received_time - startDateInSeconds) / this.secondsInADay);
|
||||
feeReport[dateNumber].value = feeReport[dateNumber].value + (+event.fee / 1000);
|
||||
feeReport[dateNumber].extra.totalEvents = feeReport[dateNumber].extra.totalEvents + 1;
|
||||
this.totalFeeMsat = (this.totalFeeMsat ? this.totalFeeMsat : 0) + +event.fee;
|
||||
});
|
||||
}
|
||||
return feeReport;
|
||||
}
|
||||
|
||||
onSelectionChange(selectedValues: {selDate: Date, selScrollRange: string}) {
|
||||
const selMonth = selectedValues.selDate.getMonth();
|
||||
const selYear = selectedValues.selDate.getFullYear();
|
||||
this.reportPeriod = selectedValues.selScrollRange;
|
||||
if (this.reportPeriod === SCROLL_RANGES[1]) {
|
||||
this.startDate = new Date(selYear, 0, 1, 0, 0, 0);
|
||||
this.endDate = new Date(selYear, 11, 31, 23, 59, 59);
|
||||
} else {
|
||||
this.startDate = new Date(selYear, selMonth, 1, 0, 0, 0);
|
||||
this.endDate = new Date(selYear, selMonth, this.getMonthDays(selMonth, selYear), 23, 59, 59);
|
||||
}
|
||||
this.filterForwardingEvents(this.startDate, this.endDate);
|
||||
this.eventFilterValue = '';
|
||||
}
|
||||
|
||||
getMonthDays(selMonth: number, selYear: number) {
|
||||
return (selMonth === 1 && selYear%4 === 0) ? (MONTHS[selMonth].days+1) : MONTHS[selMonth].days;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unSubs.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
<div fxLayout="row" fxLayoutAlign="start center" class="page-title-container">
|
||||
<fa-icon [icon]="faChartBar" class="page-title-img mr-1"></fa-icon>
|
||||
<span class="page-title">Reports</span>
|
||||
</div>
|
||||
<div fxLayout="column" class="padding-gap-x">
|
||||
<mat-card>
|
||||
<mat-card-content fxLayout="column">
|
||||
<nav mat-tab-nav-bar>
|
||||
<div role="tab" mat-tab-link *ngFor="let link of links" class="mat-tab-label" [active]="activeLink === link.link" (click)="activeLink = link.link" routerLink="{{link.link}}">{{link.name}}</div>
|
||||
</nav>
|
||||
<router-outlet></router-outlet>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue