Adding Report By option for Routing Reports #805

Adding Report By option for Routing Reports #805
pull/1017/head
Shahana Farooqui 2 years ago committed by ShahanaFarooqui
parent f3375191fc
commit af547f922e

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

@ -10,9 +10,9 @@
<link i18n-rel="" rel="mask-icon" href="assets/images/favicon-light/safari-pinned-tab.svg" color="#5bbad5">
<meta i18n-content="" name="msapplication-TileColor" content="#da532c">
<meta i18n-content="" name="theme-color" content="#ffffff">
<style>@font-face{font-family:Roboto;src:url(Roboto-Thin.f7a95c9c5999532c.woff2) format("woff2"),url(Roboto-Thin.c13c157cb81e8ebb.woff) format("woff");font-weight:100;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-ThinItalic.b0e084abf689f393.woff2) format("woff2"),url(Roboto-ThinItalic.1111028df6cea564.woff) format("woff");font-weight:100;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Light.0e01b6cd13b3857f.woff2) format("woff2"),url(Roboto-Light.603ca9a537b88428.woff) format("woff");font-weight:300;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-LightItalic.232ef4b20215f720.woff2) format("woff2"),url(Roboto-LightItalic.1b5e142f787151c8.woff) format("woff");font-weight:300;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Regular.475ba9e4e2d63456.woff2) format("woff2"),url(Roboto-Regular.bcefbfee882bc1cb.woff) format("woff");font-weight:400;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-RegularItalic.e3a9ebdaac06bbc4.woff2) format("woff2"),url(Roboto-RegularItalic.0668fae6af0cf8c2.woff) format("woff");font-weight:400;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Medium.457532032ceb0168.woff2) format("woff2"),url(Roboto-Medium.6e1ae5f0b324a0aa.woff) format("woff");font-weight:500;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-MediumItalic.872f7060602d55d2.woff2) format("woff2"),url(Roboto-MediumItalic.e06fb533801cbb08.woff) format("woff");font-weight:500;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Bold.447291a88c067396.woff2) format("woff2"),url(Roboto-Bold.fc482e6133cf5e26.woff) format("woff");font-weight:700;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-BoldItalic.1b15168ef6fa4e16.woff2) format("woff2"),url(Roboto-BoldItalic.e26ba339b06f09f7.woff) format("woff");font-weight:700;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Black.2eaa390d458c877d.woff2) format("woff2"),url(Roboto-Black.b25f67ad8583da68.woff) format("woff");font-weight:900;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-BlackItalic.7dc03ee444552bc5.woff2) format("woff2"),url(Roboto-BlackItalic.c8dc642467cb3099.woff) format("woff");font-weight:900;font-style:italic}html{width:100%;height:99%;line-height:1.5;overflow-x:hidden;font-family:Roboto,sans-serif!important;font-size:62.5%}body{box-sizing:border-box;height:100%;margin:0;overflow:hidden}*{margin:0;padding:0}</style><link rel="stylesheet" href="styles.b2848878546832b1.css" media="print" onload="this.media='all'"><noscript><link rel="stylesheet" href="styles.b2848878546832b1.css"></noscript></head>
<style>@font-face{font-family:Roboto;src:url(Roboto-Thin.f7a95c9c5999532c.woff2) format("woff2"),url(Roboto-Thin.c13c157cb81e8ebb.woff) format("woff");font-weight:100;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-ThinItalic.b0e084abf689f393.woff2) format("woff2"),url(Roboto-ThinItalic.1111028df6cea564.woff) format("woff");font-weight:100;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Light.0e01b6cd13b3857f.woff2) format("woff2"),url(Roboto-Light.603ca9a537b88428.woff) format("woff");font-weight:300;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-LightItalic.232ef4b20215f720.woff2) format("woff2"),url(Roboto-LightItalic.1b5e142f787151c8.woff) format("woff");font-weight:300;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Regular.475ba9e4e2d63456.woff2) format("woff2"),url(Roboto-Regular.bcefbfee882bc1cb.woff) format("woff");font-weight:400;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-RegularItalic.e3a9ebdaac06bbc4.woff2) format("woff2"),url(Roboto-RegularItalic.0668fae6af0cf8c2.woff) format("woff");font-weight:400;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Medium.457532032ceb0168.woff2) format("woff2"),url(Roboto-Medium.6e1ae5f0b324a0aa.woff) format("woff");font-weight:500;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-MediumItalic.872f7060602d55d2.woff2) format("woff2"),url(Roboto-MediumItalic.e06fb533801cbb08.woff) format("woff");font-weight:500;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Bold.447291a88c067396.woff2) format("woff2"),url(Roboto-Bold.fc482e6133cf5e26.woff) format("woff");font-weight:700;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-BoldItalic.1b15168ef6fa4e16.woff2) format("woff2"),url(Roboto-BoldItalic.e26ba339b06f09f7.woff) format("woff");font-weight:700;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Black.2eaa390d458c877d.woff2) format("woff2"),url(Roboto-Black.b25f67ad8583da68.woff) format("woff");font-weight:900;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-BlackItalic.7dc03ee444552bc5.woff2) format("woff2"),url(Roboto-BlackItalic.c8dc642467cb3099.woff) format("woff");font-weight:900;font-style:italic}html{width:100%;height:99%;line-height:1.5;overflow-x:hidden;font-family:Roboto,sans-serif!important;font-size:62.5%}body{box-sizing:border-box;height:100%;margin:0;overflow:hidden}*{margin:0;padding:0}</style><link rel="stylesheet" href="styles.2872d180f488fe2c.css" media="print" onload="this.media='all'"><noscript><link rel="stylesheet" href="styles.2872d180f488fe2c.css"></noscript></head>
<body>
<rtl-app></rtl-app>
<script src="runtime.94fab2e1f0b564e2.js" type="module"></script><script src="polyfills.c0773154203456c6.js" type="module"></script><script src="main.050e2f5dcd40ffe5.js" type="module"></script>
<script src="runtime.3e7825e3ce2242e1.js" type="module"></script><script src="polyfills.c0773154203456c6.js" type="module"></script><script src="main.d3ba2e1d1aeb41ef.js" type="module"></script>
</body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +1 @@
(()=>{"use strict";var e,v={},g={};function r(e){var n=g[e];if(void 0!==n)return n.exports;var t=g[e]={id:e,loaded:!1,exports:{}};return v[e].call(t.exports,t,t.exports,r),t.loaded=!0,t.exports}r.m=v,e=[],r.O=(n,t,f,o)=>{if(!t){var a=1/0;for(i=0;i<e.length;i++){for(var[t,f,o]=e[i],s=!0,u=0;u<t.length;u++)(!1&o||a>=o)&&Object.keys(r.O).every(b=>r.O[b](t[u]))?t.splice(u--,1):(s=!1,o<a&&(a=o));if(s){e.splice(i--,1);var l=f();void 0!==l&&(n=l)}}return n}o=o||0;for(var i=e.length;i>0&&e[i-1][2]>o;i--)e[i]=e[i-1];e[i]=[t,f,o]},r.n=e=>{var n=e&&e.__esModule?()=>e.default:()=>e;return r.d(n,{a:n}),n},r.d=(e,n)=>{for(var t in n)r.o(n,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:n[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((n,t)=>(r.f[t](e,n),n),[])),r.u=e=>e+"."+{634:"39c3fa8beeafbee3",637:"184190229071e513",667:"fdc0c1c2601cdf19",893:"1fd1e73cd8ce0100"}[e]+".js",r.miniCssF=e=>{},r.o=(e,n)=>Object.prototype.hasOwnProperty.call(e,n),(()=>{var e={},n="RTLApp:";r.l=(t,f,o,i)=>{if(e[t])e[t].push(f);else{var a,s;if(void 0!==o)for(var u=document.getElementsByTagName("script"),l=0;l<u.length;l++){var d=u[l];if(d.getAttribute("src")==t||d.getAttribute("data-webpack")==n+o){a=d;break}}a||(s=!0,(a=document.createElement("script")).type="module",a.charset="utf-8",a.timeout=120,r.nc&&a.setAttribute("nonce",r.nc),a.setAttribute("data-webpack",n+o),a.src=r.tu(t)),e[t]=[f];var c=(m,b)=>{a.onerror=a.onload=null,clearTimeout(p);var h=e[t];if(delete e[t],a.parentNode&&a.parentNode.removeChild(a),h&&h.forEach(_=>_(b)),m)return m(b)},p=setTimeout(c.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=c.bind(null,a.onerror),a.onload=c.bind(null,a.onload),s&&document.head.appendChild(a)}}})(),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{var e;r.tt=()=>(void 0===e&&(e={createScriptURL:n=>n},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("angular#bundler",e))),e)})(),r.tu=e=>r.tt().createScriptURL(e),r.p="",(()=>{var e={666:0};r.f.j=(f,o)=>{var i=r.o(e,f)?e[f]:void 0;if(0!==i)if(i)o.push(i[2]);else if(666!=f){var a=new Promise((d,c)=>i=e[f]=[d,c]);o.push(i[2]=a);var s=r.p+r.u(f),u=new Error;r.l(s,d=>{if(r.o(e,f)&&(0!==(i=e[f])&&(e[f]=void 0),i)){var c=d&&("load"===d.type?"missing":d.type),p=d&&d.target&&d.target.src;u.message="Loading chunk "+f+" failed.\n("+c+": "+p+")",u.name="ChunkLoadError",u.type=c,u.request=p,i[1](u)}},"chunk-"+f,f)}else e[f]=0},r.O.j=f=>0===e[f];var n=(f,o)=>{var u,l,[i,a,s]=o,d=0;if(i.some(p=>0!==e[p])){for(u in a)r.o(a,u)&&(r.m[u]=a[u]);if(s)var c=s(r)}for(f&&f(o);d<i.length;d++)r.o(e,l=i[d])&&e[l]&&e[l][0](),e[l]=0;return r.O(c)},t=self.webpackChunkRTLApp=self.webpackChunkRTLApp||[];t.forEach(n.bind(null,0)),t.push=n.bind(null,t.push.bind(t))})()})();
(()=>{"use strict";var e,v={},g={};function r(e){var n=g[e];if(void 0!==n)return n.exports;var t=g[e]={id:e,loaded:!1,exports:{}};return v[e].call(t.exports,t,t.exports,r),t.loaded=!0,t.exports}r.m=v,e=[],r.O=(n,t,f,o)=>{if(!t){var a=1/0;for(i=0;i<e.length;i++){for(var[t,f,o]=e[i],s=!0,d=0;d<t.length;d++)(!1&o||a>=o)&&Object.keys(r.O).every(b=>r.O[b](t[d]))?t.splice(d--,1):(s=!1,o<a&&(a=o));if(s){e.splice(i--,1);var l=f();void 0!==l&&(n=l)}}return n}o=o||0;for(var i=e.length;i>0&&e[i-1][2]>o;i--)e[i]=e[i-1];e[i]=[t,f,o]},r.n=e=>{var n=e&&e.__esModule?()=>e.default:()=>e;return r.d(n,{a:n}),n},r.d=(e,n)=>{for(var t in n)r.o(n,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:n[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((n,t)=>(r.f[t](e,n),n),[])),r.u=e=>e+"."+{404:"379e2f11c47085c0",636:"8cc2cbeb11e5c30d",893:"1fd1e73cd8ce0100",924:"ac30c296046b4cb4"}[e]+".js",r.miniCssF=e=>{},r.o=(e,n)=>Object.prototype.hasOwnProperty.call(e,n),(()=>{var e={},n="RTLApp:";r.l=(t,f,o,i)=>{if(e[t])e[t].push(f);else{var a,s;if(void 0!==o)for(var d=document.getElementsByTagName("script"),l=0;l<d.length;l++){var u=d[l];if(u.getAttribute("src")==t||u.getAttribute("data-webpack")==n+o){a=u;break}}a||(s=!0,(a=document.createElement("script")).type="module",a.charset="utf-8",a.timeout=120,r.nc&&a.setAttribute("nonce",r.nc),a.setAttribute("data-webpack",n+o),a.src=r.tu(t)),e[t]=[f];var c=(m,b)=>{a.onerror=a.onload=null,clearTimeout(p);var h=e[t];if(delete e[t],a.parentNode&&a.parentNode.removeChild(a),h&&h.forEach(_=>_(b)),m)return m(b)},p=setTimeout(c.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=c.bind(null,a.onerror),a.onload=c.bind(null,a.onload),s&&document.head.appendChild(a)}}})(),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{var e;r.tt=()=>(void 0===e&&(e={createScriptURL:n=>n},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("angular#bundler",e))),e)})(),r.tu=e=>r.tt().createScriptURL(e),r.p="",(()=>{var e={666:0};r.f.j=(f,o)=>{var i=r.o(e,f)?e[f]:void 0;if(0!==i)if(i)o.push(i[2]);else if(666!=f){var a=new Promise((u,c)=>i=e[f]=[u,c]);o.push(i[2]=a);var s=r.p+r.u(f),d=new Error;r.l(s,u=>{if(r.o(e,f)&&(0!==(i=e[f])&&(e[f]=void 0),i)){var c=u&&("load"===u.type?"missing":u.type),p=u&&u.target&&u.target.src;d.message="Loading chunk "+f+" failed.\n("+c+": "+p+")",d.name="ChunkLoadError",d.type=c,d.request=p,i[1](d)}},"chunk-"+f,f)}else e[f]=0},r.O.j=f=>0===e[f];var n=(f,o)=>{var d,l,[i,a,s]=o,u=0;if(i.some(p=>0!==e[p])){for(d in a)r.o(a,d)&&(r.m[d]=a[d]);if(s)var c=s(r)}for(f&&f(o);u<i.length;u++)r.o(e,l=i[u])&&e[l]&&e[l][0](),e[l]=0;return r.O(c)},t=self.webpackChunkRTLApp=self.webpackChunkRTLApp||[];t.forEach(n.bind(null,0)),t.push=n.bind(null,t.push.bind(t))})()})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -42,7 +42,7 @@ import { CLNSignVerifyMessageComponent } from './sign-verify-message/sign-verify
import { CLNSignComponent } from './sign-verify-message/sign/sign.component';
import { CLNVerifyComponent } from './sign-verify-message/verify/verify.component';
import { CLNReportsComponent } from './reports/reports.component';
import { CLNFeeReportComponent } from './reports/fee/fee-report.component';
import { CLNRoutingReportComponent } from './reports/routing/routing-report.component';
import { CLNTransactionsReportComponent } from './reports/transactions/transactions-report.component';
import { CLNOnChainSendComponent } from './on-chain/on-chain-send/on-chain-send.component';
import { CLNOpenChannelComponent } from './peers-channels/channels/open-channel-modal/open-channel.component';
@ -104,7 +104,7 @@ import { CLNUnlockedGuard } from '../shared/services/auth.guard';
CLNSignComponent,
CLNVerifyComponent,
CLNReportsComponent,
CLNFeeReportComponent,
CLNRoutingReportComponent,
CLNTransactionsReportComponent,
CLNOnChainSendComponent,
CLNInvoiceInformationComponent,

@ -26,7 +26,7 @@ import { CLNFailedTransactionsComponent } from './routing/failed-transactions/fa
import { CLNRoutingPeersComponent } from './routing/routing-peers/routing-peers.component';
import { CLNReportsComponent } from './reports/reports.component';
import { CLNFeeReportComponent } from './reports/fee/fee-report.component';
import { CLNRoutingReportComponent } from './reports/routing/routing-report.component';
import { CLNTransactionsReportComponent } from './reports/transactions/transactions-report.component';
import { CLNUnlockedGuard } from '../shared/services/auth.guard';
import { NotFoundComponent } from '../shared/components/not-found/not-found.component';
@ -89,8 +89,8 @@ export const ClnRoutes: Routes = [
},
{
path: 'reports', component: CLNReportsComponent, canActivate: [CLNUnlockedGuard], children: [
{ path: '', pathMatch: 'full', redirectTo: 'routingfees' },
{ path: 'routingfees', component: CLNFeeReportComponent, canActivate: [CLNUnlockedGuard] },
{ path: '', pathMatch: 'full', redirectTo: 'routingreport' },
{ path: 'routingreport', component: CLNRoutingReportComponent, canActivate: [CLNUnlockedGuard] },
{ path: 'transactions', component: CLNTransactionsReportComponent, canActivate: [CLNUnlockedGuard] }
]
},

@ -1,5 +1,5 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Router, ResolveEnd } from '@angular/router';
import { Router, ResolveEnd, Event } from '@angular/router';
import { Subject } from 'rxjs';
import { takeUntil, filter } from 'rxjs/operators';
import { faChartBar } from '@fortawesome/free-solid-svg-icons';
@ -12,7 +12,7 @@ import { faChartBar } from '@fortawesome/free-solid-svg-icons';
export class CLNReportsComponent implements OnInit, OnDestroy {
public faChartBar = faChartBar;
public links = [{ link: 'routingfees', name: 'Routing Fees' }, { link: 'transactions', name: 'Transactions' }];
public links = [{ link: 'routingreport', name: 'Routing' }, { link: 'transactions', name: 'Transactions' }];
public activeLink = this.links[0].link;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
@ -22,8 +22,8 @@ export class CLNReportsComponent implements OnInit, OnDestroy {
const linkFound = this.links.find((link) => this.router.url.includes(link.link));
this.activeLink = linkFound ? linkFound.link : this.links[0].link;
this.router.events.pipe(takeUntil(this.unSubs[0]), filter((e) => e instanceof ResolveEnd)).
subscribe((value: ResolveEnd) => {
const linkFound = this.links.find((link) => value.urlAfterRedirects.includes(link.link));
subscribe((value: ResolveEnd | Event) => {
const linkFound = this.links.find((link) => (<ResolveEnd>value).urlAfterRedirects.includes(link.link));
this.activeLink = linkFound ? linkFound.link : this.links[0].link;
});
}

@ -1,20 +1,27 @@
<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="center center" class="padding-gap-x">
<mat-radio-group class="my-1" color="primary" name="selReportBy" [(ngModel)]="selReportBy" (change)="onSelReportByChange()" fxFlex="100" fxLayoutAlign="start start">
<span class="mr-2">Report By: </span>
<mat-radio-button class="mr-2" tabindex="1" value="{{reportBy.FEES}}">Fees</mat-radio-button>
<mat-radio-button tabindex="2" value="{{reportBy.EVENTS}}">Events</mat-radio-button>
</mat-radio-group>
</div>
<div fxLayout="column" fxLayoutAlign="start stretch" fxFlex="100" class="padding-gap-x">
<div *ngIf="apiCallStatus?.status === apiCallStatusEnum.INITIATED" fxLayout="column" fxLayoutAlign="center center" fxFlex="100" class="font-size-120 mt-1">
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
<p>Getting fee report...</p>
<p>Getting Forwarding History...</p>
</div>
<div *ngIf="apiCallStatus?.status === apiCallStatusEnum.ERROR" fxLayout="column" fxLayoutAlign="center center" fxFlex="100" class="font-size-120 mt-1 error-border">{{errorMessage}}</div>
<div *ngIf="apiCallStatus?.status === apiCallStatusEnum.COMPLETED && feeReportData.length > 0 && filteredEventsBySelectedPeriod.length > 0" fxLayout="column" fxLayoutAlign="center center" fxFlex="100" class="font-size-120 font-bold-700 mt-1"
<div *ngIf="apiCallStatus?.status === apiCallStatusEnum.COMPLETED && routingReportData.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-2'}} Sats/{{(filteredEventsBySelectedPeriod.length || 0) | number}} Events</div>
<div *ngIf="apiCallStatus?.status === apiCallStatusEnum.COMPLETED && (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 *ngIf="apiCallStatus?.status === apiCallStatusEnum.COMPLETED && (routingReportData.length <= 0 || filteredEventsBySelectedPeriod.length <= 0)" fxLayout="column" fxLayoutAlign="center center" fxFlex="100" class="font-size-120 mt-1">No routing report for the selected period</div>
<div class="mt-1">
<ngx-charts-bar-vertical
*ngIf="feeReportData.length > 0 && filteredEventsBySelectedPeriod.length > 0"
*ngIf="routingReportData.length > 0 && filteredEventsBySelectedPeriod.length > 0"
class="one-color"
[view]="view"
[results]="feeReportData"
[results]="routingReportData"
[gradient]="false"
[xAxis]="true"
[yAxis]="true"
@ -28,8 +35,8 @@
(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-2'}}</span>
<span class="tooltip-label">Events: {{((selReportBy === reportBy.EVENTS ? model.value : model.extra.totalEvents) || 0) | number}}</span>
<span class="tooltip-label">Fee: {{((selReportBy === reportBy.EVENTS ? model.extra.totalFees : model.value) || 0) | number:'1.0-2'}}</span>
</span>
</ng-template>
</ngx-charts-bar-vertical>

@ -3,24 +3,24 @@ import { StoreModule } from '@ngrx/store';
import { RootReducer } from '../../../store/rtl.reducers';
import { LNDReducer } from '../../../lnd/store/lnd.reducers';
import { CLNReducer } from '../../../cln/store/cln.reducers';
import { CLNReducer } from '../../store/cln.reducers';
import { ECLReducer } from '../../../eclair/store/ecl.reducers';
import { CommonService } from '../../../shared/services/common.service';
import { LoggerService } from '../../../shared/services/logger.service';
import { CLNFeeReportComponent } from './fee-report.component';
import { CLNRoutingReportComponent } from './routing-report.component';
import { mockDataService, mockLoggerService } from '../../../shared/test-helpers/mock-services';
import { SharedModule } from '../../../shared/shared.module';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { DataService } from '../../../shared/services/data.service';
describe('CLNFeeReportComponent', () => {
let component: CLNFeeReportComponent;
let fixture: ComponentFixture<CLNFeeReportComponent>;
describe('CLNRoutingReportComponent', () => {
let component: CLNRoutingReportComponent;
let fixture: ComponentFixture<CLNRoutingReportComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [CLNFeeReportComponent],
declarations: [CLNRoutingReportComponent],
imports: [
BrowserAnimationsModule,
SharedModule,
@ -36,7 +36,7 @@ describe('CLNFeeReportComponent', () => {
}));
beforeEach(() => {
fixture = TestBed.createComponent(CLNFeeReportComponent);
fixture = TestBed.createComponent(CLNRoutingReportComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

@ -4,7 +4,7 @@ import { takeUntil } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { ForwardingEvent } from '../../../shared/models/clModels';
import { APICallStatusEnum, MONTHS, ScreenSizeEnum, SCROLL_RANGES } from '../../../shared/services/consts-enums-functions';
import { APICallStatusEnum, MONTHS, ReportBy, ScreenSizeEnum, SCROLL_RANGES } from '../../../shared/services/consts-enums-functions';
import { ApiCallStatusPayload } from '../../../shared/models/apiCallsPayload';
import { LoggerService } from '../../../shared/services/logger.service';
import { CommonService } from '../../../shared/services/common.service';
@ -14,23 +14,25 @@ import { RTLState } from '../../../store/rtl.state';
import { forwardingHistory } from '../../store/cln.selector';
@Component({
selector: 'rtl-cln-fee-report',
templateUrl: './fee-report.component.html',
styleUrls: ['./fee-report.component.scss'],
selector: 'rtl-cln-routing-report',
templateUrl: './routing-report.component.html',
styleUrls: ['./routing-report.component.scss'],
animations: [fadeIn]
})
export class CLNFeeReportComponent implements OnInit, OnDestroy {
export class CLNRoutingReportComponent implements OnInit, OnDestroy {
public reportPeriod = SCROLL_RANGES[0];
public secondsInADay = 24 * 60 * 60;
public events: ForwardingEvent[] = [];
public filteredEventsBySelectedPeriod: ForwardingEvent[] = [];
public eventFilterValue = '';
public reportBy = ReportBy;
public selReportBy = ReportBy.FEES;
public totalFeeMsat = null;
public today = new Date(Date.now());
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 routingReportData: any = [];
public view: [number, number] = [350, 350];
public screenPaddingX = 100;
public gradient = true;
@ -84,7 +86,7 @@ export class CLNFeeReportComponent implements OnInit, OnDestroy {
const startDateInSeconds = Math.round(start.getTime() / 1000);
const endDateInSeconds = Math.round(end.getTime() / 1000);
this.filteredEventsBySelectedPeriod = [];
this.feeReportData = [];
this.routingReportData = [];
this.totalFeeMsat = null;
if (this.events && this.events.length > 0) {
this.events.forEach((event) => {
@ -92,7 +94,7 @@ export class CLNFeeReportComponent implements OnInit, OnDestroy {
this.filteredEventsBySelectedPeriod.push(event);
}
});
this.feeReportData = this.prepareFeeReport(start);
this.routingReportData = this.selReportBy === this.reportBy.EVENTS ? this.prepareEventsReport(start) : this.prepareFeeReport(start);
}
}
@ -113,6 +115,7 @@ export class CLNFeeReportComponent implements OnInit, OnDestroy {
prepareFeeReport(start: Date) {
const startDateInSeconds = Math.round(start.getTime() / 1000);
const feeReport = [];
this.totalFeeMsat = 0;
if (this.reportPeriod === SCROLL_RANGES[1]) {
for (let i = 0; i < 12; i++) {
feeReport.push({ name: MONTHS[i].name, value: 0.0, extra: { totalEvents: 0 } });
@ -139,6 +142,36 @@ export class CLNFeeReportComponent implements OnInit, OnDestroy {
return feeReport;
}
prepareEventsReport(start: Date) {
const startDateInSeconds = Math.round(start.getTime() / 1000);
const eventsReport = [];
this.totalFeeMsat = 0;
if (this.reportPeriod === SCROLL_RANGES[1]) {
for (let i = 0; i < 12; i++) {
eventsReport.push({ name: MONTHS[i].name, value: 0, extra: { totalFees: 0.0 } });
}
this.filteredEventsBySelectedPeriod.map((event) => {
const monthNumber = new Date((+event.received_time) * 1000).getMonth();
eventsReport[monthNumber].value = eventsReport[monthNumber].value + 1;
eventsReport[monthNumber].extra.totalFees = eventsReport[monthNumber].extra.totalFees + (+event.fee / 1000);
this.totalFeeMsat = (this.totalFeeMsat ? this.totalFeeMsat : 0) + +event.fee;
return this.filteredEventsBySelectedPeriod;
});
} else {
for (let i = 0; i < this.getMonthDays(start.getMonth(), start.getFullYear()); i++) {
eventsReport.push({ name: i + 1, value: 0, extra: { totalFees: 0.0 } });
}
this.filteredEventsBySelectedPeriod.map((event) => {
const dateNumber = Math.floor((+event.received_time - startDateInSeconds) / this.secondsInADay);
eventsReport[dateNumber].value = eventsReport[dateNumber].value + 1;
eventsReport[dateNumber].extra.totalFees = eventsReport[dateNumber].extra.totalFees + (+event.fee / 1000);
this.totalFeeMsat = (this.totalFeeMsat ? this.totalFeeMsat : 0) + +event.fee;
return this.filteredEventsBySelectedPeriod;
});
}
return eventsReport;
}
onSelectionChange(selectedValues: { selDate: Date, selScrollRange: string }) {
const selMonth = selectedValues.selDate.getMonth();
const selYear = selectedValues.selDate.getFullYear();
@ -158,6 +191,11 @@ export class CLNFeeReportComponent implements OnInit, OnDestroy {
return (selMonth === 1 && selYear % 4 === 0) ? (MONTHS[selMonth].days + 1) : MONTHS[selMonth].days;
}
onSelReportByChange() {
this.yAxisLabel = this.selReportBy === this.reportBy.EVENTS ? 'Events' : 'Fee (Sats)';
this.routingReportData = this.selReportBy === this.reportBy.EVENTS ? this.prepareEventsReport(this.startDate) : this.prepareFeeReport(this.startDate);
}
ngOnDestroy() {
this.unSubs.forEach((completeSub) => {
completeSub.next(null);

@ -32,7 +32,7 @@ import { ECLLookupsComponent } from './graph/lookups/lookups.component';
import { ECLNodeLookupComponent } from './graph/lookups/node-lookup/node-lookup.component';
import { ECLGraphComponent } from './graph/graph.component';
import { ECLReportsComponent } from './reports/reports.component';
import { ECLFeeReportComponent } from './reports/fee/fee-report.component';
import { ECLRoutingReportComponent } from './reports/routing/routing-report.component';
import { ECLTransactionsReportComponent } from './reports/transactions/transactions-report.component';
import { ECLOnChainSendComponent } from './on-chain/on-chain-send/on-chain-send.component';
import { ECLInvoiceInformationComponent } from './transactions/invoice-information-modal/invoice-information.component';
@ -81,7 +81,7 @@ import { ECLUnlockedGuard } from '../shared/services/auth.guard';
ECLNodeLookupComponent,
ECLGraphComponent,
ECLReportsComponent,
ECLFeeReportComponent,
ECLRoutingReportComponent,
ECLTransactionsReportComponent,
ECLOnChainSendComponent,
ECLInvoiceInformationComponent,

@ -21,7 +21,7 @@ import { ECLChannelInactiveTableComponent } from './peers-channels/channels/chan
import { ECLForwardingHistoryComponent } from './routing/forwarding-history/forwarding-history.component';
import { ECLRoutingPeersComponent } from './routing/routing-peers/routing-peers.component';
import { ECLReportsComponent } from './reports/reports.component';
import { ECLFeeReportComponent } from './reports/fee/fee-report.component';
import { ECLRoutingReportComponent } from './reports/routing/routing-report.component';
import { ECLTransactionsReportComponent } from './reports/transactions/transactions-report.component';
import { ECLUnlockedGuard } from '../shared/services/auth.guard';
import { NotFoundComponent } from '../shared/components/not-found/not-found.component';
@ -70,8 +70,8 @@ export const EclRoutes: Routes = [
},
{
path: 'reports', component: ECLReportsComponent, canActivate: [ECLUnlockedGuard], children: [
{ path: '', pathMatch: 'full', redirectTo: 'routingfees' },
{ path: 'routingfees', component: ECLFeeReportComponent, canActivate: [ECLUnlockedGuard] },
{ path: '', pathMatch: 'full', redirectTo: 'routingreport' },
{ path: 'routingreport', component: ECLRoutingReportComponent, canActivate: [ECLUnlockedGuard] },
{ path: 'transactions', component: ECLTransactionsReportComponent, canActivate: [ECLUnlockedGuard] }
]
},

@ -1,36 +0,0 @@
<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]="totalFeeSat">{{(totalFeeSat || 0) | number:'1.0-2'}} 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"
class="one-color"
[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-2'}}</span>
</span>
</ng-template>
</ngx-charts-bar-vertical>
</div>
<div class="mt-1">
<rtl-ecl-forwarding-history *ngIf="filteredEventsBySelectedPeriod.length > 0" [eventsData]="filteredEventsBySelectedPeriod" [filterValue]="eventFilterValue"></rtl-ecl-forwarding-history>
</div>
</div>
</div>

@ -1,5 +1,5 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Router, ResolveEnd } from '@angular/router';
import { Router, ResolveEnd, Event } from '@angular/router';
import { Subject } from 'rxjs';
import { takeUntil, filter } from 'rxjs/operators';
import { faChartBar } from '@fortawesome/free-solid-svg-icons';
@ -12,18 +12,18 @@ import { faChartBar } from '@fortawesome/free-solid-svg-icons';
export class ECLReportsComponent implements OnInit, OnDestroy {
public faChartBar = faChartBar;
public links = [{ link: 'routingfees', name: 'Routing Fees' }, { link: 'transactions', name: 'Transactions' }];
public links = [{ link: 'routingreport', name: 'Routing' }, { link: 'transactions', name: 'Transactions' }];
public activeLink = this.links[0].link;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
constructor(private router: Router) {}
constructor(private router: Router) { }
ngOnInit() {
const linkFound = this.links.find((link) => this.router.url.includes(link.link));
this.activeLink = linkFound ? linkFound.link : this.links[0].link;
this.router.events.pipe(takeUntil(this.unSubs[0]), filter((e) => e instanceof ResolveEnd)).
subscribe((value: ResolveEnd) => {
const linkFound = this.links.find((link) => value.urlAfterRedirects.includes(link.link));
subscribe((value: ResolveEnd | Event) => {
const linkFound = this.links.find((link) => (<ResolveEnd>value).urlAfterRedirects.includes(link.link));
this.activeLink = linkFound ? linkFound.link : this.links[0].link;
});
}

@ -0,0 +1,43 @@
<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="center center" class="padding-gap-x">
<mat-radio-group class="my-1" color="primary" name="selReportBy" [(ngModel)]="selReportBy" (change)="onSelReportByChange()" fxFlex="100" fxLayoutAlign="start start">
<span class="mr-2">Report By: </span>
<mat-radio-button class="mr-2" tabindex="1" value="{{reportBy.FEES}}">Fees</mat-radio-button>
<mat-radio-button tabindex="2" value="{{reportBy.EVENTS}}">Events</mat-radio-button>
</mat-radio-group>
</div>
<div fxLayout="column" fxLayoutAlign="start stretch" fxFlex="100" class="padding-gap-x">
<div *ngIf="routingReportData.length > 0 && filteredEventsBySelectedPeriod.length > 0" fxLayout="column" fxLayoutAlign="center center" fxFlex="100" class="font-size-120 font-bold-700 mt-1"
[@fadeIn]="totalFeeSat">{{(totalFeeSat || 0) | number:'1.0-2'}} Sats/{{(filteredEventsBySelectedPeriod.length || 0) | number}} Events</div>
<div *ngIf="routingReportData.length <= 0 || filteredEventsBySelectedPeriod.length <= 0" fxLayout="column" fxLayoutAlign="center center" fxFlex="100" class="font-size-120 mt-1">No routing report for the selected period</div>
<div class="mt-1">
<ngx-charts-bar-vertical
*ngIf="routingReportData.length > 0 && filteredEventsBySelectedPeriod.length > 0"
class="one-color"
[view]="view"
[results]="routingReportData"
[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: {{((selReportBy === reportBy.EVENTS ? model.value : model.extra.totalEvents) || 0) | number}}</span>
<span class="tooltip-label">Fee: {{((selReportBy === reportBy.EVENTS ? model.extra.totalFees : model.value) || 0) | number:'1.0-2'}}</span>
</span>
</ng-template>
</ngx-charts-bar-vertical>
</div>
<div class="mt-1">
<rtl-ecl-forwarding-history *ngIf="filteredEventsBySelectedPeriod.length > 0" [eventsData]="filteredEventsBySelectedPeriod" [filterValue]="eventFilterValue"></rtl-ecl-forwarding-history>
</div>
</div>
</div>

@ -4,23 +4,23 @@ import { StoreModule } from '@ngrx/store';
import { RootReducer } from '../../../store/rtl.reducers';
import { LNDReducer } from '../../../lnd/store/lnd.reducers';
import { CLNReducer } from '../../../cln/store/cln.reducers';
import { ECLReducer } from '../../../eclair/store/ecl.reducers';
import { ECLReducer } from '../../store/ecl.reducers';
import { CommonService } from '../../../shared/services/common.service';
import { LoggerService } from '../../../shared/services/logger.service';
import { ECLFeeReportComponent } from './fee-report.component';
import { ECLRoutingReportComponent } from './routing-report.component';
import { mockDataService, mockLoggerService } from '../../../shared/test-helpers/mock-services';
import { SharedModule } from '../../../shared/shared.module';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { DataService } from '../../../shared/services/data.service';
describe('ECLFeeReportComponent', () => {
let component: ECLFeeReportComponent;
let fixture: ComponentFixture<ECLFeeReportComponent>;
describe('ECLRoutingReportComponent', () => {
let component: ECLRoutingReportComponent;
let fixture: ComponentFixture<ECLRoutingReportComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ECLFeeReportComponent],
declarations: [ECLRoutingReportComponent],
imports: [
BrowserAnimationsModule,
SharedModule,
@ -36,7 +36,7 @@ describe('ECLFeeReportComponent', () => {
}));
beforeEach(() => {
fixture = TestBed.createComponent(ECLFeeReportComponent);
fixture = TestBed.createComponent(ECLRoutingReportComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

@ -6,7 +6,7 @@ import { Store } from '@ngrx/store';
import { PaymentRelayed, Payments } from '../../../shared/models/eclModels';
import { CommonService } from '../../../shared/services/common.service';
import { LoggerService } from '../../../shared/services/logger.service';
import { MONTHS, ScreenSizeEnum, SCROLL_RANGES } from '../../../shared/services/consts-enums-functions';
import { MONTHS, ReportBy, ScreenSizeEnum, SCROLL_RANGES } from '../../../shared/services/consts-enums-functions';
import { fadeIn } from '../../../shared/animation/opacity-animation';
import { RTLState } from '../../../store/rtl.state';
@ -14,23 +14,25 @@ import { payments } from '../../store/ecl.selector';
import { ApiCallStatusPayload } from '../../../shared/models/apiCallsPayload';
@Component({
selector: 'rtl-ecl-fee-report',
templateUrl: './fee-report.component.html',
styleUrls: ['./fee-report.component.scss'],
selector: 'rtl-ecl-routing-report',
templateUrl: './routing-report.component.html',
styleUrls: ['./routing-report.component.scss'],
animations: [fadeIn]
})
export class ECLFeeReportComponent implements OnInit, OnDestroy {
export class ECLRoutingReportComponent implements OnInit, OnDestroy {
public reportPeriod = SCROLL_RANGES[0];
public secondsInADay = 24 * 60 * 60;
public events: PaymentRelayed[] = [];
public filteredEventsBySelectedPeriod: PaymentRelayed[] = [];
public eventFilterValue = '';
public reportBy = ReportBy;
public selReportBy = ReportBy.FEES;
public totalFeeSat = null;
public today = new Date(Date.now());
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 routingReportData: any = [];
public view: [number, number] = [350, 350];
public screenPaddingX = 100;
public gradient = true;
@ -77,7 +79,7 @@ export class ECLFeeReportComponent implements OnInit, OnDestroy {
const endDateInSeconds = Math.round(end.getTime() / 1000);
this.logger.info('Filtering Forwarding Events Starting at ' + new Date(Date.now()).toLocaleString() + ' From ' + start.toLocaleString() + ' To ' + end.toLocaleString());
this.filteredEventsBySelectedPeriod = [];
this.feeReportData = [];
this.routingReportData = [];
this.totalFeeSat = null;
if (this.events && this.events.length > 0) {
this.events.forEach((event) => {
@ -85,7 +87,7 @@ export class ECLFeeReportComponent implements OnInit, OnDestroy {
this.filteredEventsBySelectedPeriod.push(event);
}
});
this.feeReportData = this.prepareFeeReport(start);
this.routingReportData = this.selReportBy === this.reportBy.EVENTS ? this.prepareEventsReport(start) : this.prepareFeeReport(start);
}
this.logger.info('Filtering Forwarding Events Finished at ' + new Date(Date.now()).toLocaleString());
}
@ -107,6 +109,7 @@ export class ECLFeeReportComponent implements OnInit, OnDestroy {
prepareFeeReport(start: Date) {
const startDateInSeconds = Math.round(start.getTime() / 1000);
const feeReport = [];
this.totalFeeSat = 0;
this.logger.info('Fee Report Prepare Starting at ' + new Date(Date.now()).toLocaleString() + ' From ' + start.toLocaleString());
if (this.reportPeriod === SCROLL_RANGES[1]) {
for (let i = 0; i < 12; i++) {
@ -135,6 +138,38 @@ export class ECLFeeReportComponent implements OnInit, OnDestroy {
return feeReport;
}
prepareEventsReport(start: Date) {
const startDateInSeconds = Math.round(start.getTime() / 1000);
const eventsReport = [];
this.totalFeeSat = 0;
this.logger.info('Events Report Prepare Starting at ' + new Date(Date.now()).toLocaleString() + ' From ' + start.toLocaleString());
if (this.reportPeriod === SCROLL_RANGES[1]) {
for (let i = 0; i < 12; i++) {
eventsReport.push({ name: MONTHS[i].name, value: 0, extra: { totalFees: 0.0 } });
}
this.filteredEventsBySelectedPeriod.map((event) => {
const monthNumber = new Date(event.timestamp).getMonth();
eventsReport[monthNumber].value = eventsReport[monthNumber].value + 1;
eventsReport[monthNumber].extra.totalFees = eventsReport[monthNumber].extra.totalFees + (event.amountIn - event.amountOut);
this.totalFeeSat = (this.totalFeeSat ? this.totalFeeSat : 0) + (event.amountIn - event.amountOut);
return this.filteredEventsBySelectedPeriod;
});
} else {
for (let i = 0; i < this.getMonthDays(start.getMonth(), start.getFullYear()); i++) {
eventsReport.push({ name: i + 1, value: 0, extra: { totalFees: 0.0 } });
}
this.filteredEventsBySelectedPeriod.map((event) => {
const dateNumber = Math.floor((Math.floor(event.timestamp / 1000) - startDateInSeconds) / this.secondsInADay);
eventsReport[dateNumber].value = eventsReport[dateNumber].value + 1;
eventsReport[dateNumber].extra.totalFees = eventsReport[dateNumber].extra.totalFees + (event.amountIn - event.amountOut);
this.totalFeeSat = (this.totalFeeSat ? this.totalFeeSat : 0) + (event.amountIn - event.amountOut);
return this.filteredEventsBySelectedPeriod;
});
}
this.logger.info('Events Report Prepare Finished at ' + new Date(Date.now()).toLocaleString());
return eventsReport;
}
onSelectionChange(selectedValues: { selDate: Date, selScrollRange: string }) {
const selMonth = selectedValues.selDate.getMonth();
const selYear = selectedValues.selDate.getFullYear();
@ -154,6 +189,11 @@ export class ECLFeeReportComponent implements OnInit, OnDestroy {
return (selMonth === 1 && selYear % 4 === 0) ? (MONTHS[selMonth].days + 1) : MONTHS[selMonth].days;
}
onSelReportByChange() {
this.yAxisLabel = this.selReportBy === this.reportBy.EVENTS ? 'Events' : 'Fee (Sats)';
this.routingReportData = this.selReportBy === this.reportBy.EVENTS ? this.prepareEventsReport(this.startDate) : this.prepareFeeReport(this.startDate);
}
ngOnDestroy() {
this.unSubs.forEach((completeSub) => {
completeSub.next(null);

@ -26,7 +26,7 @@ import { ChannelClosedTableComponent } from './peers-channels/channels/channels-
import { TransactionsComponent } from './transactions/transactions.component';
import { LookupsComponent } from './graph/lookups/lookups.component';
import { ReportsComponent } from './reports/reports.component';
import { FeeReportComponent } from './reports/fee/fee-report.component';
import { RoutingReportComponent } from './reports/routing/routing-report.component';
import { TransactionsReportComponent } from './reports/transactions/transactions-report.component';
import { RoutingComponent } from './routing/routing.component';
import { ForwardingHistoryComponent } from './routing/forwarding-history/forwarding-history.component';
@ -103,7 +103,7 @@ import { LNDUnlockedGuard } from '../shared/services/auth.guard';
SignComponent,
VerifyComponent,
ReportsComponent,
FeeReportComponent,
RoutingReportComponent,
TransactionsReportComponent,
QueryRoutesComponent,
GraphComponent,

@ -21,7 +21,7 @@ import { RoutingComponent } from './routing/routing.component';
import { ForwardingHistoryComponent } from './routing/forwarding-history/forwarding-history.component';
import { RoutingPeersComponent } from './routing/routing-peers/routing-peers.component';
import { ReportsComponent } from './reports/reports.component';
import { FeeReportComponent } from './reports/fee/fee-report.component';
import { RoutingReportComponent } from './reports/routing/routing-report.component';
import { TransactionsReportComponent } from './reports/transactions/transactions-report.component';
import { OnChainComponent } from './on-chain/on-chain.component';
import { OnChainReceiveComponent } from './on-chain/on-chain-receive/on-chain-receive.component';
@ -101,8 +101,8 @@ export const LndRoutes: Routes = [
},
{
path: 'reports', component: ReportsComponent, canActivate: [LNDUnlockedGuard], children: [
{ path: '', pathMatch: 'full', redirectTo: 'routingfees' },
{ path: 'routingfees', component: FeeReportComponent, canActivate: [LNDUnlockedGuard] },
{ path: '', pathMatch: 'full', redirectTo: 'routingreport' },
{ path: 'routingreport', component: RoutingReportComponent, canActivate: [LNDUnlockedGuard] },
{ path: 'transactions', component: TransactionsReportComponent, canActivate: [LNDUnlockedGuard] }
]
},

@ -1,38 +0,0 @@
<div fxLayout="column" fxLayoutAlign="start stretch" fxFlex="100" class="padding-gap-x-large">
<rtl-horizontal-scroller (stepChanged)="onSelectionChange($event)"></rtl-horizontal-scroller>
<mat-progress-bar mode="indeterminate" *ngIf="errorMessage === 'Getting fee report...'" class="mt-2"></mat-progress-bar>
<div fxLayout="column" fxLayoutAlign="start stretch" fxFlex="100" class="padding-gap-x">
<div *ngIf="feeReportData.length > 0 && events.forwarding_events" fxLayout="column" fxLayoutAlign="center center" fxFlex="100" class="font-size-120 font-bold-700 mt-1"
[@fadeIn]="events.total_fee_msat">{{(events.total_fee_msat / 1000 || 0) | number:'1.0-2'}} Sats/{{(events?.forwarding_events?.length || 0) | number}} Events</div>
<div *ngIf="(feeReportData.length <= 0 || !events.forwarding_events) && errorMessage === ''" fxLayout="column" fxLayoutAlign="center center" fxFlex="100" class="font-size-120 mt-1">No fee report for the selected period</div>
<div *ngIf="errorMessage !== ''" fxLayout="column" fxLayoutAlign="center center" fxFlex="100" class="font-size-120 mt-1" [ngClass]="{'error-border': errorMessage !== 'Getting fee report...' && errorMessage !== ''}">{{errorMessage}}</div>
<div class="mt-1">
<ngx-charts-bar-vertical
*ngIf="feeReportData.length > 0 && events.forwarding_events"
class="one-color"
[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-2'}}</span>
</span>
</ng-template>
</ngx-charts-bar-vertical>
</div>
<div class="mt-1">
<rtl-forwarding-history *ngIf="events && events?.forwarding_events" [eventsData]="events?.forwarding_events" [filterValue]="eventFilterValue"></rtl-forwarding-history>
</div>
</div>
</div>

@ -1,5 +1,5 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Router, ResolveEnd } from '@angular/router';
import { Router, ResolveEnd, Event } from '@angular/router';
import { Subject } from 'rxjs';
import { takeUntil, filter } from 'rxjs/operators';
import { faChartBar } from '@fortawesome/free-solid-svg-icons';
@ -12,19 +12,21 @@ import { faChartBar } from '@fortawesome/free-solid-svg-icons';
export class ReportsComponent implements OnInit, OnDestroy {
public faChartBar = faChartBar;
public links = [{ link: 'routingfees', name: 'Routing Fees' }, { link: 'transactions', name: 'Transactions' }];
public links = [{ link: 'routingreport', name: 'Routing' }, { link: 'transactions', name: 'Transactions' }];
public activeLink = this.links[0].link;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
constructor(private router: Router) {}
constructor(private router: Router) { }
ngOnInit() {
const linkFound = this.links.find((link) => this.router.url.includes(link.link));
this.activeLink = linkFound ? linkFound.link : this.links[0].link;
this.router.events.pipe(takeUntil(this.unSubs[0]), filter((e) => e instanceof ResolveEnd)).
subscribe((value: ResolveEnd) => {
const linkFound = this.links.find((link) => value.urlAfterRedirects.includes(link.link));
this.activeLink = linkFound ? linkFound.link : this.links[0].link;
subscribe({
next: (value: ResolveEnd | Event) => {
const linkFound = this.links.find((link) => (<ResolveEnd>value).urlAfterRedirects.includes(link.link));
this.activeLink = linkFound ? linkFound.link : this.links[0].link;
}
});
}

@ -0,0 +1,47 @@
<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="center center" class="padding-gap-x">
<mat-radio-group class="my-1" color="primary" name="selReportBy" [(ngModel)]="selReportBy" (change)="onSelReportByChange()" fxFlex="100" fxLayoutAlign="start start">
<span class="mr-2">Report By: </span>
<mat-radio-button class="mr-2" tabindex="1" value="{{reportBy.FEES}}">Fees</mat-radio-button>
<mat-radio-button tabindex="2" value="{{reportBy.EVENTS}}">Events</mat-radio-button>
</mat-radio-group>
</div>
<mat-progress-bar mode="indeterminate" *ngIf="errorMessage === 'Getting Forwarding History...'" class="mt-2"></mat-progress-bar>
<div fxLayout="column" fxLayoutAlign="start stretch" fxFlex="100" class="padding-gap-x my-1">
<div *ngIf="routingReportData.length > 0 && events.forwarding_events && events.forwarding_events.length && events.forwarding_events.length > 0" fxLayout="column" fxLayoutAlign="center center" fxFlex="100" class="font-size-120 font-bold-700 mt-1"
[@fadeIn]="events.total_fee_msat">{{(events.total_fee_msat / 1000 || 0) | number:'1.0-2'}} Sats/{{(events?.forwarding_events?.length || 0) | number}} Events</div>
<div *ngIf="(routingReportData.length <= 0 || events.forwarding_events.length <= 0) && errorMessage === ''" fxLayout="column" fxLayoutAlign="center center" fxFlex="100" class="font-size-120 mt-1">No routing report for the selected period</div>
<div *ngIf="errorMessage !== ''" fxLayout="column" fxLayoutAlign="center center" fxFlex="100" class="font-size-120 mt-1" [ngClass]="{'error-border': errorMessage !== 'Getting Forwarding History...' && errorMessage !== ''}">{{errorMessage}}</div>
<div class="mt-1">
<ngx-charts-bar-vertical
*ngIf="routingReportData.length > 0 && events.forwarding_events && events.forwarding_events.length && events.forwarding_events.length > 0"
class="one-color"
[view]="view"
[results]="routingReportData"
[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: {{((selReportBy === reportBy.EVENTS ? model.value : model.extra.totalEvents) || 0) | number}}</span>
<span class="tooltip-label">Fee: {{((selReportBy === reportBy.EVENTS ? model.extra.totalFees : model.value) || 0) | number:'1.0-2'}}</span>
</span>
</ng-template>
</ngx-charts-bar-vertical>
</div>
</div>
<div fxLayout="column" fxLayoutAlign="start stretch" fxFlex="100" class="padding-gap-x">
<div class="mt-1">
<rtl-forwarding-history *ngIf="events && events?.forwarding_events && events.forwarding_events.length && events.forwarding_events.length > 0" [eventsData]="events?.forwarding_events" [filterValue]="eventFilterValue"></rtl-forwarding-history>
</div>
</div>
</div>

@ -2,24 +2,24 @@ import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
import { StoreModule } from '@ngrx/store';
import { RootReducer } from '../../../store/rtl.reducers';
import { LNDReducer } from '../../../lnd/store/lnd.reducers';
import { LNDReducer } from '../../store/lnd.reducers';
import { CLNReducer } from '../../../cln/store/cln.reducers';
import { ECLReducer } from '../../../eclair/store/ecl.reducers';
import { CommonService } from '../../../shared/services/common.service';
import { DataService } from '../../../shared/services/data.service';
import { FeeReportComponent } from './fee-report.component';
import { RoutingReportComponent } from './routing-report.component';
import { SharedModule } from '../../../shared/shared.module';
import { mockDataService } from '../../../shared/test-helpers/mock-services';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
describe('FeeReportComponent', () => {
let component: FeeReportComponent;
let fixture: ComponentFixture<FeeReportComponent>;
describe('RoutingReportComponent', () => {
let component: RoutingReportComponent;
let fixture: ComponentFixture<RoutingReportComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [FeeReportComponent],
declarations: [RoutingReportComponent],
imports: [
BrowserAnimationsModule,
SharedModule,
@ -34,7 +34,7 @@ describe('FeeReportComponent', () => {
}));
beforeEach(() => {
fixture = TestBed.createComponent(FeeReportComponent);
fixture = TestBed.createComponent(RoutingReportComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

@ -5,7 +5,7 @@ import { Store } from '@ngrx/store';
import { SwitchRes } from '../../../shared/models/lndModels';
import { CommonService } from '../../../shared/services/common.service';
import { MONTHS, ScreenSizeEnum, SCROLL_RANGES, UI_MESSAGES } from '../../../shared/services/consts-enums-functions';
import { MONTHS, ReportBy, ScreenSizeEnum, SCROLL_RANGES, UI_MESSAGES } from '../../../shared/services/consts-enums-functions';
import { DataService } from '../../../shared/services/data.service';
import { fadeIn } from '../../../shared/animation/opacity-animation';
@ -14,21 +14,23 @@ import { lndNodeInformation } from '../../store/lnd.selector';
import { LoggerService } from '../../../shared/services/logger.service';
@Component({
selector: 'rtl-fee-report',
templateUrl: './fee-report.component.html',
styleUrls: ['./fee-report.component.scss'],
selector: 'rtl-routing-report',
templateUrl: './routing-report.component.html',
styleUrls: ['./routing-report.component.scss'],
animations: [fadeIn]
})
export class FeeReportComponent implements OnInit, OnDestroy {
export class RoutingReportComponent implements OnInit, OnDestroy {
public reportPeriod = SCROLL_RANGES[0];
public secondsInADay = 24 * 60 * 60;
public events: SwitchRes = {};
public eventFilterValue = '';
public reportBy = ReportBy;
public selReportBy = ReportBy.FEES;
public today = new Date(Date.now());
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 routingReportData: any = [];
public view: [number, number] = [350, 350];
public screenPaddingX = 100;
public gradient = true;
@ -73,7 +75,7 @@ export class FeeReportComponent implements OnInit, OnDestroy {
}
fetchEvents(start: Date, end: Date) {
this.errorMessage = UI_MESSAGES.GET_FEE_REPORT;
this.errorMessage = UI_MESSAGES.GET_FORWARDING_HISTORY;
const startDateInSeconds = Math.round(start.getTime() / 1000).toString();
const endDateInSeconds = Math.round(end.getTime() / 1000).toString();
this.dataService.getForwardingHistory(startDateInSeconds, endDateInSeconds).
@ -83,10 +85,10 @@ export class FeeReportComponent implements OnInit, OnDestroy {
if (res.forwarding_events && res.forwarding_events.length) {
res.forwarding_events = res.forwarding_events.reverse();
this.events = res;
this.feeReportData = this.prepareFeeReport(start);
this.routingReportData = this.selReportBy === this.reportBy.EVENTS ? this.prepareEventsReport(start) : this.prepareFeeReport(start);
} else {
this.events = {};
this.feeReportData = [];
this.events = { forwarding_events: [], total_fee_msat: 0 };
this.routingReportData = [];
}
}, error: (err) => {
this.errorMessage = err;
@ -111,6 +113,7 @@ export class FeeReportComponent implements OnInit, OnDestroy {
prepareFeeReport(start: Date) {
const startDateInSeconds = Math.round(start.getTime() / 1000);
const feeReport = [];
this.events.total_fee_msat = 0;
if (this.reportPeriod === SCROLL_RANGES[1]) {
for (let i = 0; i < 12; i++) {
feeReport.push({ name: MONTHS[i].name, value: 0.0, extra: { totalEvents: 0 } });
@ -137,6 +140,36 @@ export class FeeReportComponent implements OnInit, OnDestroy {
return feeReport;
}
prepareEventsReport(start: Date) {
const startDateInSeconds = Math.round(start.getTime() / 1000);
const eventsReport = [];
this.events.total_fee_msat = 0;
if (this.reportPeriod === SCROLL_RANGES[1]) {
for (let i = 0; i < 12; i++) {
eventsReport.push({ name: MONTHS[i].name, value: 0, extra: { totalFees: 0.0 } });
}
this.events.forwarding_events.map((event) => {
const monthNumber = new Date((+event.timestamp) * 1000).getMonth();
eventsReport[monthNumber].value = eventsReport[monthNumber].value + 1;
eventsReport[monthNumber].extra.totalFees = eventsReport[monthNumber].extra.totalFees + (+event.fee_msat / 1000);
this.events.total_fee_msat = (this.events.total_fee_msat ? this.events.total_fee_msat : 0) + +event.fee_msat;
return this.events;
});
} else {
for (let i = 0; i < this.getMonthDays(start.getMonth(), start.getFullYear()); i++) {
eventsReport.push({ name: i + 1, value: 0, extra: { totalFees: 0.0 } });
}
this.events.forwarding_events.map((event) => {
const dateNumber = Math.floor((+event.timestamp - startDateInSeconds) / this.secondsInADay);
eventsReport[dateNumber].value = eventsReport[dateNumber].value + 1;
eventsReport[dateNumber].extra.totalFees = eventsReport[dateNumber].extra.totalFees + (+event.fee_msat / 1000);
this.events.total_fee_msat = (this.events.total_fee_msat ? this.events.total_fee_msat : 0) + +event.fee_msat;
return this.events;
});
}
return eventsReport;
}
onSelectionChange(selectedValues: { selDate: Date, selScrollRange: string }) {
const selMonth = selectedValues.selDate.getMonth();
const selYear = selectedValues.selDate.getFullYear();
@ -156,6 +189,11 @@ export class FeeReportComponent implements OnInit, OnDestroy {
return (selMonth === 1 && selYear % 4 === 0) ? (MONTHS[selMonth].days + 1) : MONTHS[selMonth].days;
}
onSelReportByChange() {
this.yAxisLabel = this.selReportBy === this.reportBy.EVENTS ? 'Events' : 'Fee (Sats)';
this.routingReportData = this.selReportBy === this.reportBy.EVENTS ? this.prepareEventsReport(this.startDate) : this.prepareFeeReport(this.startDate);
}
ngOnDestroy() {
this.unSubs.forEach((completeSub) => {
completeSub.next(null);

@ -310,8 +310,8 @@ export const UI_MESSAGES = {
BUMP_FEE: 'Bumping Fee...',
LEASE_UTXO: 'Leasing UTXO...',
GET_LOOP_SWAPS: 'Getting List Swaps...',
GET_FEE_REPORT: 'Getting fee report...',
GET_LOOKUP_DETAILS: 'Getting lookup details...',
GET_FORWARDING_HISTORY: 'Getting Forwarding History...',
GET_LOOKUP_DETAILS: 'Getting Lookup Details...',
GET_RTL_CONFIG: 'Getting RTL Config...',
VERIFY_TOKEN: 'Verify Token...',
DISABLE_OFFER: 'Disabling Offer...',
@ -326,6 +326,11 @@ export enum PaymentTypes {
KEYSEND = 'KEYSEND'
}
export enum ReportBy {
FEES = 'FEES',
EVENTS = 'EVENTS'
}
export enum RTLActions {
VOID = 'VOID',
SET_API_URL_ECL = 'SET_API_URL_ECL',

@ -199,7 +199,7 @@ export class DataService implements OnDestroy {
getForwardingHistory(start: string, end: string) {
const queryHeaders: SwitchReq = { end_time: end, start_time: start };
this.store.dispatch(openSpinner({ payload: UI_MESSAGES.GET_FEE_REPORT }));
this.store.dispatch(openSpinner({ payload: UI_MESSAGES.GET_FORWARDING_HISTORY }));
return this.httpClient.post(this.childAPIUrl + environment.SWITCH_API, queryHeaders).pipe(
takeUntil(this.unSubs[7]),
withLatestFrom(this.store.select(allChannels)),
@ -238,11 +238,11 @@ export class DataService implements OnDestroy {
} else {
res = {};
}
this.store.dispatch(closeSpinner({ payload: UI_MESSAGES.GET_FEE_REPORT }));
this.store.dispatch(closeSpinner({ payload: UI_MESSAGES.GET_FORWARDING_HISTORY }));
return of(res);
}),
catchError((err) => {
this.handleErrorWithAlert('getForwardingHistoryData', UI_MESSAGES.GET_FEE_REPORT, 'Forwarding History Failed', this.childAPIUrl + environment.SWITCH_API, err);
this.handleErrorWithAlert('getForwardingHistoryData', UI_MESSAGES.GET_FORWARDING_HISTORY, 'Forwarding History Failed', this.childAPIUrl + environment.SWITCH_API, err);
return throwError(() => new Error(this.extractErrorMessage(err)));
}));
}

@ -94,6 +94,18 @@
color: $primary-color;
}
.border-primary {
border: 1px solid $primary-color;
}
.border-accent {
border: 1px solid $accent-color;
}
.border-warn {
border: 1px solid $warn-color;
}
.material-icons.primary { color: $primary-color; }
.material-icons.accent { color: $accent-color; }

Loading…
Cancel
Save