Initial CLightning UX

Initial CLightning UX
pull/260/head
Shahana Farooqui 4 years ago
parent 6084fa77b7
commit f22a20fe48

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

@ -12,5 +12,5 @@
<link rel="stylesheet" href="styles.5986f047fc96bc7e6342.css"></head>
<body>
<rtl-app></rtl-app>
<script src="runtime.50bd1337d57edd256ed9.js" defer></script><script src="polyfills-es5.b8e32dec482ae69710a2.js" nomodule defer></script><script src="polyfills.ebf9033c33aa4a5af12a.js" defer></script><script src="main.931d8ff4a287403e68f9.js" defer></script></body>
<script src="runtime.d75cd2c265dde80b73ba.js" defer></script><script src="polyfills-es5.b8e32dec482ae69710a2.js" nomodule defer></script><script src="polyfills.ebf9033c33aa4a5af12a.js" defer></script><script src="main.ea762e60e8d2e18a62f0.js" defer></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 +0,0 @@
!function(e){function r(r){for(var n,a,i=r[0],c=r[1],l=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(f&&f(r);s.length;)s.shift()();return u.push.apply(u,l||[]),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:"3de0a26964d460363d96",6:"3417a1a39867445d9b4f",7:"6517e5b751982b99bd2b"}[e]+".js"}(e);var c=new Error;u=function(r){i.onerror=i.onload=null,clearTimeout(l);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 l=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 l=0;l<i.length;l++)r(i[l]);var f=c;t()}([]);

@ -0,0 +1 @@
!function(e){function r(r){for(var n,i,a=r[0],c=r[1],f=r[2],p=0,s=[];p<a.length;p++)i=a[p],Object.prototype.hasOwnProperty.call(o,i)&&o[i]&&s.push(o[i][0]),o[i]=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,a=1;a<t.length;a++)0!==o[t[a]]&&(n=!1);n&&(u.splice(r--,1),e=i(i.s=t[0]))}return e}var n={},o={0:0},u=[];function i(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,i),t.l=!0,t.exports}i.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,a=document.createElement("script");a.charset="utf-8",a.timeout=120,i.nc&&a.setAttribute("nonce",i.nc),a.src=function(e){return i.p+""+({}[e]||e)+"."+{1:"77d491bf73b15683870f",6:"4c847e2e4a21ced9ec66",7:"631b04e7d1b0b93ef794"}[e]+".js"}(e);var c=new Error;u=function(r){a.onerror=a.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:a})}),12e4);a.onerror=a.onload=u,document.head.appendChild(a)}return Promise.all(r)},i.m=e,i.c=n,i.d=function(e,r,t){i.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,r){if(1&r&&(e=i(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(i.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)i.d(t,n,(function(r){return e[r]}).bind(null,n));return t},i.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(r,"a",r),r},i.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},i.p="",i.oe=function(e){throw console.error(e),e};var a=window.webpackJsonp=window.webpackJsonp||[],c=a.push.bind(a);a.push=r,a=a.slice();for(var f=0;f<a.length;f++)r(a[f]);var l=c;t()}([]);

@ -1,135 +0,0 @@
<div fxLayout="column">
<div class="padding-gap">
<mat-card class="mat-card-original" [ngClass]="{'flip': redirectedWithPeer}">
<mat-card-header>
<mat-card-subtitle>
<h2>Open Channel</h2>
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<form fxLayout="column" fxLayout.gt-sm="row wrap" (ngSubmit)="openChannelForm.form.valid && onOpenChannel(openChannelForm)" #openChannelForm="ngForm">
<mat-form-field fxFlex="40" fxLayoutAlign="start end">
<mat-select [(ngModel)]="selectedPeer" placeholder="Alias" name="peer_alias" tabindex="1" required
name="selPeer" #selPeer="ngModel">
<mat-option *ngFor="let peer of peers" [value]="peer.id">
{{peer.alias}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex="25" fxLayoutAlign="start end">
<input matInput [(ngModel)]="fundingAmount" placeholder="Amount (mSats)" type="number" step="1000" min="1"
tabindex="2" required name="amount" #amount="ngModel">
<mat-hint>(Wallet Bal: {{totalBalance}}, Remaining Bal:
{{totalBalance - ((fundingAmount) ? fundingAmount : 0)}})</mat-hint>
</mat-form-field>
<div fxFlex="15" tabindex="3" fxLayoutAlign="start center" class="chkbox-options">
<mat-checkbox [(ngModel)]="moreOptions" name="moreOptions" (change)="onMoreOptionsChange($event)">Options
</mat-checkbox>
</div>
<span *ngIf="moreOptions" fxLayout="column" fxLayout.gt-sm="row wrap" fxFlex="80"
fxLayoutAlign.gt-sm="start center">
<mat-form-field fxFlex="25" fxLayoutAlign="start end">
<mat-select tabindex="4" placeholder="Fee Rate" [(value)]="selFeeRate">
<mat-option *ngFor="let feeRateType of feeRateTypes" [value]="feeRateType.feeRateId">
{{feeRateType.feeRateType}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-checkbox fxFlex="25" fxFlex.lt-lg="35" tabindex="5" [(ngModel)]="flgMinConf" name="flgMinConf">
<mat-form-field fxFlex="100">
<input matInput [(ngModel)]="minConfValue" placeholder="Min Confirmation Blocks" type="number"
name="blocks" step="1" min="0" tabindex="6" #blocks="ngModel" [required]="flgMinConf"
[disabled]="!flgMinConf">
</mat-form-field>
</mat-checkbox>
</span>
<div fxFlex="10" fxLayoutAlign="end start">
<button fxFlex="90" fxLayoutAlign="center center" mat-raised-button color="primary"
[disabled]="selectedPeer === '' || fundingAmount == null || (totalBalance - ((fundingAmount) ? fundingAmount : 0) < 0) || !openChannelForm.valid"
type="submit" tabindex="7">
<p
*ngIf="(selectedPeer === '' || fundingAmount == null) && (selPeer.touched || selPeer.dirty) && (amount.touched || amount.dirty); else openText">
Invalid Values</p>
<ng-template #openText>
<p>Open</p>
</ng-template>
</button>
</div>
<div fxFlex="10" fxLayoutAlign="end start">
<button fxFlex="90" fxLayoutAlign="center center" mat-stroked-button color="primary" tabindex="9" type="reset"
(click)="resetData()">Clear</button>
</div>
</form>
</mat-card-content>
</mat-card>
</div>
<div class="padding-gap">
<mat-card class="mat-card-original">
<mat-card-content fxFlex="100" fxLayout="column">
<div fxLayout="column" fxLayout.gt-xs="row" fxLayoutAlign.gt-xs="start center" fxLayoutAlign="start stretch">
<mat-form-field fxFlex="30">
<input matInput (keyup)="applyFilter()" [(ngModel)]="selFilter" name="filter" placeholder="Filter">
</mat-form-field>
</div>
<div perfectScrollbar class="table-container">
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
<table mat-table #table [dataSource]="channels" matSort
[ngClass]="{'overflow-auto error-border': flgLoading[0]==='error','overflow-auto': true}">
<ng-container matColumnDef="close">
<th mat-header-cell *matHeaderCellDef> Close Channel </th>
<td mat-cell *matCellDef="let channel">
<mat-icon color="accent" (click)="onChannelClose(channel)">link_off</mat-icon>
</td>
</ng-container>
<ng-container matColumnDef="update">
<th mat-header-cell *matHeaderCellDef>
<mat-icon color="accent" (click)="onChannelUpdate('all')">edit</mat-icon>
</th>
<td mat-cell *matCellDef="let channel">
<mat-icon color="accent" (click)="onChannelUpdate(channel)">edit</mat-icon>
</td>
</ng-container>
<ng-container matColumnDef="short_channel_id">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Short Channel ID </th>
<td mat-cell *matCellDef="let channel"> {{channel?.short_channel_id}}</td>
</ng-container>
<ng-container matColumnDef="alias">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Alias </th>
<td mat-cell *matCellDef="let channel">{{channel?.alias}}</td>
</ng-container>
<ng-container matColumnDef="connected">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Connected </th>
<td mat-cell *matCellDef="let channel"> {{(channel?.connected) ? 'Connected' : 'Disconnected'}} </td>
</ng-container>
<ng-container matColumnDef="private">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Private </th>
<td mat-cell *matCellDef="let channel"> {{(channel?.private ? 'Private' : 'Public')}} </td>
</ng-container>
<ng-container matColumnDef="state">
<th mat-header-cell *matHeaderCellDef mat-sort-header> State </th>
<td mat-cell *matCellDef="let channel"> {{channel?.state}}</td>
</ng-container>
<ng-container matColumnDef="msatoshi_to_us">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> mSatoshi To Us </th>
<td mat-cell *matCellDef="let channel"><span fxLayoutAlign="end center">
{{channel?.msatoshi_to_us | number}} </span></td>
</ng-container>
<ng-container matColumnDef="msatoshi_total">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> mSatoshi Total </th>
<td mat-cell *matCellDef="let channel"><span fxLayoutAlign="end center">
{{channel?.msatoshi_total | number}} </span></td>
</ng-container>
<ng-container matColumnDef="spendable_msatoshi">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Spendable Satoshi </th>
<td mat-cell *matCellDef="let channel"><span fxLayoutAlign="end center">
{{channel?.spendable_msatoshi | number}} </span></td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: flgSticky;"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" (click)="onChannelClick(row, $event)"
[ngClass]="{'row-disabled': row.state === 'ONCHAIN'}"></tr>
</table>
</div>
</mat-card-content>
</mat-card>
</div>
</div>

@ -1,290 +0,0 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { MatTableDataSource, MatSort } from '@angular/material';
import { ChannelCL, PeerCL, GetInfoCL, ChannelEdgeCL } from '../../shared/models/clModels';
import { LoggerService } from '../../shared/services/logger.service';
import { CLEffects } from '../store/cl.effects';
import { RTLEffects } from '../../store/rtl.effects';
import * as RTLActions from '../../store/rtl.actions';
import * as fromRTLReducer from '../../store/rtl.reducers';
import { AlertTypeEnum, DataTypeEnum } from '../../shared/services/consts-enums-functions';
@Component({
selector: 'rtl-cl-channels',
templateUrl: './channels.component.html',
styleUrls: ['./channels.component.scss']
})
export class CLChannelsComponent implements OnInit, OnDestroy {
@ViewChild(MatSort, { static: true }) sort: MatSort;
public totalBalance = 0;
public selectedPeer = '';
public fundingAmount: number;
public displayedColumns = [];
public channels: any;
public peers: PeerCL[] = [];
public information: GetInfoCL = {};
public flgLoading: Array<Boolean | 'error'> = [true];
public selectedFilter = '';
public myChanPolicy: any = {};
public selFilter = '';
public feeRateTypes = [];
public selFeeRate = '';
public isPrivate = false;
public flgMinConf = false;
public minConfValue = null;
public moreOptions = false;
public flgSticky = false;
public redirectedWithPeer = false;
private unsub: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private rtlEffects: RTLEffects, private clEffects: CLEffects, private activatedRoute: ActivatedRoute) {
switch (true) {
case (window.innerWidth <= 415):
this.displayedColumns = ['close', 'update', 'short_channel_id', 'state', 'msatoshi_total'];
break;
case (window.innerWidth > 415 && window.innerWidth <= 730):
this.displayedColumns = ['close', 'update', 'short_channel_id', 'state', 'msatoshi_to_us', 'msatoshi_total', 'spendable_msatoshi'];
break;
case (window.innerWidth > 730 && window.innerWidth <= 1024):
this.displayedColumns = [
'close', 'update', 'short_channel_id', 'alias', 'connected', 'private', 'state', 'msatoshi_to_us', 'msatoshi_total', 'spendable_msatoshi'
];
break;
case (window.innerWidth > 1024 && window.innerWidth <= 1280):
this.flgSticky = true;
this.displayedColumns = [
'close', 'update', 'short_channel_id', 'alias', 'connected', 'private', 'state', 'msatoshi_to_us', 'msatoshi_total', 'spendable_msatoshi'
];
break;
default:
this.flgSticky = true;
this.displayedColumns = [
'close', 'update', 'short_channel_id', 'alias', 'connected', 'private', 'state', 'msatoshi_to_us', 'msatoshi_total', 'spendable_msatoshi'
];
break;
}
}
ngOnInit() {
this.store.dispatch(new RTLActions.FetchChannelsCL());
this.store.select('cl')
.pipe(takeUntil(this.unsub[0]))
.subscribe((rtlStore) => {
rtlStore.effectErrorsCl.forEach(effectsErr => {
if (effectsErr.action === 'FetchChannelsCL') {
this.flgLoading[0] = 'error';
}
});
this.information = rtlStore.information;
this.feeRateTypes = rtlStore.feeRateTypes;
this.peers = rtlStore.peers;
this.peers.forEach(peer => {
if (undefined === peer.alias || peer.alias === '') {
peer.alias = peer.id.substring(0, 15) + '...';
}
});
this.totalBalance = +rtlStore.balance.totalBalance;
if (undefined !== rtlStore.allChannels) {
this.loadChannelsTable(rtlStore.allChannels);
}
if (this.flgLoading[0] !== 'error') {
this.flgLoading[0] = (undefined !== rtlStore.allChannels) ? false : true;
}
this.logger.info(rtlStore);
});
this.activatedRoute.paramMap.subscribe(() => {
this.selectedPeer = window.history.state.peer;
this.redirectedWithPeer = (window.history.state.peer) ? true : false;
});
}
onOpenChannel(form: any) {
this.store.dispatch(new RTLActions.OpenSpinner('Opening Channel...'));
this.store.dispatch(new RTLActions.SaveNewChannelCL({
peerId: this.selectedPeer, satoshis: this.fundingAmount, announce: !this.isPrivate, feeRate: this.selFeeRate, minconf: this.flgMinConf ? this.minConfValue : null
}));
}
onChannelUpdate(channelToUpdate: ChannelCL | 'all') {
if(channelToUpdate !== 'all' && channelToUpdate.state === 'ONCHAIN') {
return;
}
if (channelToUpdate === 'all') {
const titleMsg = 'Updated Values for ALL Channels';
// const confirmationMsg = {};
const confirmationMsg = [[{key: '', value: '', title: '', width: 0, type: DataTypeEnum.NUMBER}]];
this.store.dispatch(new RTLActions.OpenConfirmation({ data: {
type: AlertTypeEnum.CONFIRM,
alertTitle: 'Confirm Channels Update',
titleMessage: titleMsg,
noBtnText: 'Cancel',
yesBtnText: 'Update',
message: confirmationMsg,
flgShowInput: true, getInputs: [
{placeholder: 'Base Fee msat', inputType: 'number', inputValue: 1000, width: 50},
{placeholder: 'Fee Rate mili msat', inputType: 'number', inputValue: 1, min: 1, width: 50}
]
}}));
this.rtlEffects.closeConfirm
.pipe(takeUntil(this.unsub[2]))
.subscribe(confirmRes => {
if (confirmRes) {
const base_fee = confirmRes[0].inputValue;
const fee_rate = confirmRes[1].inputValue;
this.store.dispatch(new RTLActions.OpenSpinner('Updating Channel Policy...'));
this.store.dispatch(new RTLActions.UpdateChannelsCL({baseFeeMsat: base_fee, feeRate: fee_rate, channelId: 'all'}));
}
});
} else {
this.myChanPolicy = {fee_base_msat: 0, fee_rate_milli_msat: 0};
this.store.dispatch(new RTLActions.OpenSpinner('Fetching Channel Policy...'));
this.store.dispatch(new RTLActions.ChannelLookupCL(channelToUpdate.short_channel_id));
this.clEffects.setLookupCL
.pipe(takeUntil(this.unsub[3]))
.subscribe((resLookup: ChannelEdgeCL[]) => {
this.logger.info(resLookup);
if (resLookup.length > 0 && resLookup[0].source === this.information.id) {
this.myChanPolicy = {fee_base_msat: resLookup[0].base_fee_millisatoshi, fee_rate_milli_msat: resLookup[0].fee_per_millionth};
} else if (resLookup.length > 1 && resLookup[1].source === this.information.id) {
this.myChanPolicy = {fee_base_msat: resLookup[1].base_fee_millisatoshi, fee_rate_milli_msat: resLookup[1].fee_per_millionth};
} else {
this.myChanPolicy = {fee_base_msat: 0, fee_rate_milli_msat: 0};
}
this.logger.info(this.myChanPolicy);
this.store.dispatch(new RTLActions.CloseSpinner());
const titleMsg = 'Updated Values for Channel: ' + channelToUpdate.channel_id;
// const confirmationMsg = {};
const confirmationMsg = [[{key: '', value: '', title: '', width: 0, type: DataTypeEnum.NUMBER}]];
this.store.dispatch(new RTLActions.OpenConfirmation({ data: {
type: AlertTypeEnum.CONFIRM,
alertTitle: 'Confirm Channel Update',
titleMessage: titleMsg,
noBtnText: 'Cancel',
yesBtnText: 'Update',
message: confirmationMsg,
flgShowInput: true,
getInputs: [
{placeholder: 'Base Fee msat', inputType: 'number', inputValue: (this.myChanPolicy.fee_base_msat === '') ? 0 : this.myChanPolicy.fee_base_msat, width: 50},
{placeholder: 'Fee Rate mili msat', inputType: 'number', inputValue: this.myChanPolicy.fee_rate_milli_msat, min: 1, width: 50}
]
}}));
});
this.rtlEffects.closeConfirm
.pipe(takeUntil(this.unsub[2]))
.subscribe(confirmRes => {
if (confirmRes) {
const base_fee = confirmRes[0].inputValue;
const fee_rate = confirmRes[1].inputValue;
this.store.dispatch(new RTLActions.OpenSpinner('Updating Channel Policy...'));
this.store.dispatch(new RTLActions.UpdateChannelsCL({baseFeeMsat: base_fee, feeRate: fee_rate, channelId: channelToUpdate.channel_id}));
}
});
}
this.applyFilter();
}
onChannelClose(channelToClose: ChannelCL) {
if(channelToClose.state === 'ONCHAIN') {
return;
}
if (channelToClose.state === 'AWAITING_UNILATERAL') {
this.store.dispatch(new RTLActions.OpenAlert({ data: {
type: AlertTypeEnum.WARNING,
alertTitle: 'Unable to Close Channel',
titleMessage: 'Channel can not be closed when it is in AWAITING UNILATERAL state.'
}}));
} else {
this.store.dispatch(new RTLActions.OpenConfirmation({
data: { type: AlertTypeEnum.CONFIRM, alertTitle: 'Confirm Channel Closing', titleMessage: 'Closing channel: ' + channelToClose.channel_id, noBtnText: 'Cancel', yesBtnText: 'Close Channel'
}}));
this.rtlEffects.closeConfirm
.pipe(takeUntil(this.unsub[1]))
.subscribe(confirmRes => {
if (confirmRes) {
this.store.dispatch(new RTLActions.OpenSpinner('Closing Channel...'));
this.store.dispatch(new RTLActions.CloseChannelCL({channelId: channelToClose.channel_id}));
}
});
}
}
applyFilter() {
this.selectedFilter = this.selFilter;
this.channels.filter = this.selFilter;
}
onChannelClick(selRow: ChannelCL, event: any) {
const flgCloseClicked =
event.target.className.includes('mat-column-close')
|| event.target.className.includes('mat-column-update')
|| event.target.className.includes('mat-icon');
if (flgCloseClicked) {
return;
}
const selChannel = this.channels.data.filter(channel => {
return channel.channel_id === selRow.channel_id;
})[0];
const reorderedChannel = [
[{key: 'status', value: selChannel.status, title: 'Status', width: 100, type: DataTypeEnum.NUMBER}]
// 'channel_id', 'short_channel_id', 'id', 'alias', 'connected', 'private', 'state', 'funding_txid', 'msatoshi_to_us', 'msatoshi_total', 'their_channel_reserve_satoshis', 'our_channel_reserve_satoshis', 'spendable_msatoshi'
];
this.store.dispatch(new RTLActions.OpenAlert({width: '75%', data: {
type: AlertTypeEnum.INFORMATION,
alertTitle: 'Channel Information',
message: reorderedChannel
}}));
}
loadChannelsTable(channels) {
channels.sort(function(a, b) {
return (a && a.active === b.active) ? 0 : ((b.active) ? 1 : -1);
});
this.channels = new MatTableDataSource<ChannelCL>([...channels]);
this.channels.sort = this.sort;
this.channels.filterPredicate = (channel: ChannelCL, fltr: string) => {
const newChannel = ((channel.connected) ? 'connected' : 'disconnected') + (channel.channel_id ? channel.channel_id : '') +
(channel.short_channel_id ? channel.short_channel_id : '') + (channel.id ? channel.id : '') + (channel.alias ? channel.alias : '') +
(channel.private ? 'private' : 'public') + (channel.state ? channel.state.toLowerCase() : '') +
(channel.funding_txid ? channel.funding_txid : '') + (channel.msatoshi_to_us ? channel.msatoshi_to_us : '') +
(channel.msatoshi_total ? channel.msatoshi_total : '') + (channel.their_channel_reserve_satoshis ? channel.their_channel_reserve_satoshis : '') +
(channel.our_channel_reserve_satoshis ? channel.our_channel_reserve_satoshis : '') + (channel.spendable_msatoshi ? channel.spendable_msatoshi : '');
return newChannel.includes(fltr.toLowerCase());
};
this.logger.info(this.channels);
}
resetData() {
this.selectedPeer = '';
this.fundingAmount = 0;
this.moreOptions = false;
this.flgMinConf = false;
this.isPrivate = false;
this.selFeeRate = '';
this.minConfValue = null;
this.redirectedWithPeer = false;
}
onMoreOptionsChange(event: any) {
if (!event.checked) {
this.flgMinConf = false;
this.isPrivate = false;
this.selFeeRate = '';
this.minConfValue = null;
}
}
ngOnDestroy() {
this.unsub.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

@ -6,19 +6,31 @@ import { SharedModule } from '../shared/shared.module';
import { CLRootComponent } from './cl-root.component';
import { CLHomeComponent } from './home/home.component';
import { CLFeeRatesComponent } from './home/fee-rates/fee-rates.component';
import { CLChannelsComponent } from './channels/channels.component';
import { CLInvoicesComponent } from './invoices/invoices.component';
import { CLPeersChannelsComponent } from './peers-channels/peers-channels.component';
import { CLChannelsTablesComponent } from './peers-channels/channels/channels-tables/channels-tables.component';
import { CLPeersComponent } from './peers-channels/peers/peers.component';
import { CLLightningInvoicesComponent } from './transactions/invoices/lightning-invoices.component';
import { CLOnChainSendComponent } from './on-chain/on-chain-send/on-chain-send.component';
import { CLOnChainReceiveComponent } from './on-chain/on-chain-receive/on-chain-receive.component';
import { CLOnChainComponent } from './on-chain/on-chain.component';
import { CLLightningPaymentsComponent } from './transactions/payments/lightning-payments.component';
import { CLChannelManageComponent } from './peers-channels/channels/channel-manage/channel-manage.component';
import { CLTransactionsComponent } from './transactions/transactions.component';
import { CLLookupsComponent } from './lookups/lookups.component';
import { CLRoutingComponent } from './routing/routing.component';
import { CLForwardingHistoryComponent } from './routing/forwarding-history/forwarding-history.component';
import { CLChannelLookupComponent } from './lookups/channel-lookup/channel-lookup.component';
import { CLNodeLookupComponent } from './lookups/node-lookup/node-lookup.component';
import { CLOnChainComponent } from './on-chain/on-chain.component';
import { CLQueryRoutesComponent } from './payments/query-routes/query-routes.component';
import { CLPaymentsComponent } from './payments/send-receive/payments.component';
import { CLPeersComponent } from './peers/peers.component';
import { CLForwardingHistoryComponent } from './forwarding-history/forwarding-history.component';
import { CLQueryRoutesComponent } from './transactions/query-routes/query-routes.component';
import { CLChannelOpenTableComponent } from './peers-channels/channels/channels-tables/channel-open-table/channel-open-table.component';
import { CLNodeInfoComponent } from './home/node-info/node-info.component';
import { CLBalancesInfoComponent } from './home/balances-info/balances-info.component';
import { CLFeeInfoComponent } from './home/fee-info/fee-info.component';
import { CLChannelStatusInfoComponent } from './home/channel-status-info/channel-status-info.component';
import { CLChannelCapacityInfoComponent } from './home/channel-capacity-info/channel-capacity-info.component';
import { CLChannelLiquidityInfoComponent } from './home/channel-liquidity-info/channel-liquidity-info.component';
import { CLFeeRatesComponent } from './home/fee-rates/fee-rates.component';
import { LoggerService, ConsoleLoggerService } from '../shared/services/logger.service';
import { CLUnlockedGuard } from '../shared/services/auth.guard';
@NgModule({
@ -30,20 +42,32 @@ import { CLUnlockedGuard } from '../shared/services/auth.guard';
declarations: [
CLRootComponent,
CLHomeComponent,
CLChannelsComponent,
CLInvoicesComponent,
CLPeersComponent,
CLPeersChannelsComponent,
CLLightningInvoicesComponent,
CLLightningPaymentsComponent,
CLChannelManageComponent,
CLTransactionsComponent,
CLLookupsComponent,
CLRoutingComponent,
CLForwardingHistoryComponent,
CLChannelLookupComponent,
CLNodeLookupComponent,
CLOnChainComponent,
CLQueryRoutesComponent,
CLPaymentsComponent,
CLPeersComponent,
CLForwardingHistoryComponent,
CLOnChainSendComponent,
CLOnChainReceiveComponent,
CLOnChainComponent,
CLChannelsTablesComponent,
CLChannelOpenTableComponent,
CLNodeInfoComponent,
CLBalancesInfoComponent,
CLFeeInfoComponent,
CLChannelStatusInfoComponent,
CLChannelCapacityInfoComponent,
CLChannelLiquidityInfoComponent,
CLFeeRatesComponent
],
providers: [
{ provide: LoggerService, useClass: ConsoleLoggerService },
CLUnlockedGuard
],
bootstrap: [CLRootComponent]

@ -3,15 +3,11 @@ import { ModuleWithProviders } from '@angular/core';
import { CLRootComponent } from './cl-root.component';
import { CLHomeComponent } from './home/home.component';
import { CLChannelsComponent } from './channels/channels.component';
import { CLInvoicesComponent } from './invoices/invoices.component';
import { CLLookupsComponent } from './lookups/lookups.component';
import { CLOnChainComponent } from './on-chain/on-chain.component';
import { CLQueryRoutesComponent } from './payments/query-routes/query-routes.component';
import { CLPaymentsComponent } from './payments/send-receive/payments.component';
import { CLPeersComponent } from './peers/peers.component';
import { CLForwardingHistoryComponent } from './forwarding-history/forwarding-history.component';
import { CLPeersChannelsComponent } from '../clightning/peers-channels/peers-channels.component';
import { CLTransactionsComponent } from '../clightning/transactions/transactions.component';
import { CLRoutingComponent } from '../clightning/routing/routing.component';
import { CLLookupsComponent } from './lookups/lookups.component';
import { CLUnlockedGuard } from '../shared/services/auth.guard';
import { NotFoundComponent } from '../shared/components/not-found/not-found.component';
@ -19,23 +15,14 @@ export const ClRoutes: Routes = [
{ path: '', component: CLRootComponent,
children: [
{ path: 'home', component: CLHomeComponent, canActivate: [CLUnlockedGuard] },
{ path: 'peers', component: CLPeersComponent, canActivate: [CLUnlockedGuard] },
{ path: 'chnlmanage', component: CLChannelsComponent, canActivate: [CLUnlockedGuard] },
{ path: 'onchain', component: CLOnChainComponent, canActivate: [CLUnlockedGuard] },
{ path: 'paymentsend', component: CLPaymentsComponent, canActivate: [CLUnlockedGuard] },
{ path: 'queryroutes', component: CLQueryRoutesComponent, canActivate: [CLUnlockedGuard] },
{ path: 'invoices', component: CLInvoicesComponent, canActivate: [CLUnlockedGuard] },
{ path: 'forwardinghistory', component: CLForwardingHistoryComponent, canActivate: [CLUnlockedGuard] },
{ path: 'peerschannels', component: CLPeersChannelsComponent, canActivate: [CLUnlockedGuard] },
{ path: 'transactions', component: CLTransactionsComponent, canActivate: [CLUnlockedGuard] },
{ path: 'routing', component: CLRoutingComponent, canActivate: [CLUnlockedGuard] },
{ path: 'lookups', component: CLLookupsComponent, canActivate: [CLUnlockedGuard] },
{ path: '**', component: NotFoundComponent },
{ path: 'wallet', redirectTo: 'home' },
{ path: 'chnlclosed', redirectTo: 'chnlmanage' },
{ path: 'chnlpending', redirectTo: 'chnlmanage' },
{ path: 'chnlbackup', redirectTo: 'chnlmanage' },
{ path: 'transsendreceive', redirectTo: 'onchain' },
{ path: 'translist', redirectTo: 'onchain' },
{ path: 'switch', redirectTo: 'forwardinghistory' },
{ path: 'routingpeers', redirectTo: 'home' },
{ path: '**', component: NotFoundComponent }
{ path: 'backup', redirectTo: 'home' }
]}
];

@ -1,95 +0,0 @@
<div fxLayout="column">
<!-- <div fxFlex="100" class="padding-gap">
<mat-card>
<mat-card-header>
<mat-card-subtitle>
<h2>Forwarding History</h2>
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<form fxLayout="column" fxLayoutAlign="space-between stretch" fxLayout.gt-md="row wrap"
(ngSubmit)="fhForm.form.valid && onForwardingHistoryFetch()" #fhForm="ngForm" class="padding-gap">
<div fxFlex="69" fxLayoutAlign="space-between stretch">
<mat-form-field fxFlex="49" fxLayoutAlign="start">
<input matInput [matDatepicker]="startDatepicker" placeholder="Start Date" [max]="yesterday"
name="startDate" [(ngModel)]="startDate" tabindex="1" #strtDate="ngModel">
<mat-datepicker-toggle matSuffix [for]="startDatepicker"></mat-datepicker-toggle>
<mat-datepicker #startDatepicker [startAt]="startDate"></mat-datepicker>
</mat-form-field>
<mat-form-field fxFlex="49" fxLayoutAlign="start">
<input matInput [matDatepicker]="endDatepicker" [max]="today" placeholder="End Date" name="endDate"
[(ngModel)]="endDate" tabindex="2" #enDate="ngModel">
<mat-datepicker-toggle matSuffix [for]="endDatepicker"></mat-datepicker-toggle>
<mat-datepicker #endDatepicker [startAt]="endDate"></mat-datepicker>
</mat-form-field>
</div>
<div fxFlex="30" fxLayoutAlign="space-between stretch">
<button fxFlex="50" fxLayoutAlign="center center" mat-raised-button color="primary"
[disabled]="fhForm.invalid" type="submit" tabindex="3">Fetch</button>
<button fxFlex="50" fxLayoutAlign="center center" mat-stroked-button color="primary" class="ml-2" tabindex="4"
type="reset" (click)="resetData()">Clear</button>
</div>
</form>
</mat-card-content>
</mat-card>
</div> -->
<div class="padding-gap">
<mat-card class="mat-card-original">
<mat-card-content class="table-card-content">
<div fxLayout="column" fxLayout.gt-xs="row" fxLayoutAlign.gt-xs="start center" fxLayoutAlign="start stretch">
<mat-form-field fxFlex="30">
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
</mat-form-field>
</div>
<div perfectScrollbar class="table-container">
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
<table mat-table #table [dataSource]="forwardingHistoryEvents" matSort
[ngClass]="{'overflow-auto error-border': flgLoading[0]==='error','overflow-auto': true}">
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Status</th>
<td mat-cell *matCellDef="let fhEvent">{{fhEvent?.status}}</td>
</ng-container>
<ng-container matColumnDef="received_time_str">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Received Time</th>
<td mat-cell *matCellDef="let fhEvent">{{fhEvent?.received_time_str}}</td>
</ng-container>
<ng-container matColumnDef="resolved_time_str">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Resolved Time</th>
<td mat-cell *matCellDef="let fhEvent">{{fhEvent?.resolved_time_str}}</td>
</ng-container>
<ng-container matColumnDef="in_channel">
<th mat-header-cell *matHeaderCellDef mat-sort-header>In Channel</th>
<td mat-cell *matCellDef="let fhEvent">{{fhEvent?.in_channel}}</td>
</ng-container>
<ng-container matColumnDef="out_channel">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Out Channel</th>
<td mat-cell *matCellDef="let fhEvent">{{fhEvent?.out_channel}}</td>
</ng-container>
<ng-container matColumnDef="in_msatoshi">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">In mSatoshi</th>
<td mat-cell *matCellDef="let fhEvent"><span fxLayoutAlign="end center">{{fhEvent?.in_msatoshi | number}}</span></td>
</ng-container>
<ng-container matColumnDef="out_msatoshi">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Out mSatoshi</th>
<td mat-cell *matCellDef="let fhEvent"><span fxLayoutAlign="end center">{{fhEvent?.out_msatoshi | number}}</span></td>
</ng-container>
<ng-container matColumnDef="fee">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Fee mSat</th>
<td mat-cell *matCellDef="let fhEvent"><span fxLayoutAlign="end center">{{fhEvent?.fee | number}}</span></td>
</ng-container>
<ng-container matColumnDef="payment_hash">
<th mat-header-cell class="pl-4" *matHeaderCellDef mat-sort-header>Payment Hash</th>
<td mat-cell class="pl-4" *matCellDef="let fhEvent">
<div>{{fhEvent?.payment_hash | slice:0:10}}...</div>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: flgSticky;"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"
(click)="onForwardingEventClick(row, $event)"></tr>
</table>
</div>
</mat-card-content>
</mat-card>
</div>
</div>

@ -1,135 +0,0 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil, filter } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { Actions } from '@ngrx/effects';
import { MatTableDataSource, MatSort } from '@angular/material';
import { ForwardingEventCL } from '../../shared/models/clModels';
import { LoggerService } from '../../shared/services/logger.service';
import * as RTLActions from '../../store/rtl.actions';
import * as fromRTLReducer from '../../store/rtl.reducers';
import { AlertTypeEnum, DataTypeEnum } from '../../shared/services/consts-enums-functions';
@Component({
selector: 'rtl-cl-forwarding-history',
templateUrl: './forwarding-history.component.html',
styleUrls: ['./forwarding-history.component.scss']
})
export class CLForwardingHistoryComponent implements OnInit, OnDestroy {
@ViewChild(MatSort, { static: true }) sort: MatSort;
public displayedColumns = [];
public forwardingHistoryEvents: any;
public lastOffsetIndex = 0;
public flgLoading: Array<Boolean | 'error'> = [true];
public today = new Date(Date.now());
public yesterday = new Date(this.today.getFullYear(), this.today.getMonth(), this.today.getDate() - 1, this.today.getHours(), this.today.getMinutes(), this.today.getSeconds());
public endDate = this.today;
public startDate = this.yesterday;
public flgSticky = false;
private unsub: Array<Subject<void>> = [new Subject(), new Subject(), new Subject()];
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private actions$: Actions) {
switch (true) {
case (window.innerWidth <= 415):
this.displayedColumns = ['status', 'in_msatoshi', 'out_msatoshi'];
break;
case (window.innerWidth > 415 && window.innerWidth <= 730):
this.displayedColumns = ['status', 'in_msatoshi', 'out_msatoshi', 'fee'];
break;
case (window.innerWidth > 730 && window.innerWidth <= 1024):
this.displayedColumns = ['status', 'received_time_str', 'resolved_time_str', 'in_channel', 'out_channel', 'in_msatoshi', 'out_msatoshi', 'fee', 'payment_hash'];
break;
case (window.innerWidth > 1024 && window.innerWidth <= 1280):
this.flgSticky = true;
this.displayedColumns = ['status', 'received_time_str', 'resolved_time_str', 'in_channel', 'out_channel', 'in_msatoshi', 'out_msatoshi', 'fee', 'payment_hash'];
break;
default:
this.flgSticky = true;
this.displayedColumns = ['status', 'received_time_str', 'resolved_time_str', 'in_channel', 'out_channel', 'in_msatoshi', 'out_msatoshi', 'fee', 'payment_hash'];
break;
}
}
ngOnInit() {
this.onForwardingHistoryFetchCL();
this.store.select('cl')
.pipe(takeUntil(this.unsub[0]))
.subscribe((rtlStore) => {
rtlStore.effectErrorsCl.forEach(effectsErr => {
if (effectsErr.action === 'GetForwardingHistoryCL') {
this.flgLoading[0] = 'error';
}
});
if (undefined !== rtlStore.forwardingHistory.forwarding_events && rtlStore.forwardingHistory.forwarding_events.length > 0) {
this.lastOffsetIndex = rtlStore.forwardingHistory.last_offset_index;
this.loadForwardingEventsTable(rtlStore.forwardingHistory.forwarding_events);
} else {
// To reset table after other Forwarding history calls
this.lastOffsetIndex = 0;
this.loadForwardingEventsTable([]);
}
if (this.flgLoading[0] !== 'error') {
this.flgLoading[0] = (undefined !== rtlStore.forwardingHistory.forwarding_events) ? false : true;
}
this.logger.info(rtlStore);
});
}
onForwardingEventClick(selRow: ForwardingEventCL, event: any) {
const selFEvent = this.forwardingHistoryEvents.data.filter(fhEvent => {
return (fhEvent.received_time === selRow.received_time && fhEvent.in_channel === selRow.in_channel);
})[0];
const reorderedFHEvent = [
[{key: 'status', value: selFEvent.status, title: 'Status', width: 100, type: DataTypeEnum.NUMBER}]
// 'status', 'received_time_str', 'resolved_time_str', 'in_channel', 'out_channel', 'in_msatoshi', 'in_msat', 'out_msatoshi', 'out_msat', 'fee', 'fee_msat', 'payment_hash'
];
this.store.dispatch(new RTLActions.OpenAlert({ data: {
type: AlertTypeEnum.INFORMATION,
alertTitle: 'Forwarding History',
message: reorderedFHEvent
}}));
}
loadForwardingEventsTable(forwardingEvents: ForwardingEventCL[]) {
this.forwardingHistoryEvents = new MatTableDataSource<ForwardingEventCL>([...forwardingEvents]);
this.forwardingHistoryEvents.sort = this.sort;
this.logger.info(this.forwardingHistoryEvents);
}
onForwardingHistoryFetchCL() {
if (undefined === this.endDate || this.endDate == null) {
this.endDate = new Date();
}
if (undefined === this.startDate || this.startDate == null) {
this.startDate = new Date(this.endDate.getFullYear(), this.endDate.getMonth(), this.endDate.getDate() - 1);
}
this.store.dispatch(new RTLActions.GetForwardingHistoryCL(
// { end_time: Math.round(this.endDate.getTime() / 1000).toString(), start_time: Math.round(this.startDate.getTime() / 1000).toString() }
));
}
resetData() {
// this.endDate = new Date();
// this.startDate = new Date(this.endDate.getFullYear(), this.endDate.getMonth(), this.endDate.getDate() - 1);
if (undefined !== this.forwardingHistoryEvents) {
this.forwardingHistoryEvents.data = [];
}
}
applyFilter(selFilter: string) {
this.forwardingHistoryEvents.filter = selFilter;
}
ngOnDestroy() {
this.resetData();
this.unsub.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

@ -0,0 +1,16 @@
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch">
<div>
<h4 fxLayoutAlign="start" class="dashboard-info-title">Lightning</h4>
<div class="overflow-wrap dashboard-info-value">{{balances.lightning | number}} Sats</div>
<mat-progress-bar class="dashboard-progress-bar" mode="determinate" value="{{balances.lightning/balances.total*100}}"></mat-progress-bar>
</div>
<div>
<h4 fxLayoutAlign="start" class="dashboard-info-title">On-chain</h4>
<div class="overflow-wrap dashboard-info-value">{{balances.onchain | number}} Sats</div>
<mat-progress-bar class="dashboard-progress-bar" mode="determinate" value="{{balances.onchain/balances.total*100}}"></mat-progress-bar>
</div>
<div>
<h4 fxLayoutAlign="start" class="dashboard-info-title">Total</h4>
<div class="overflow-wrap dashboard-info-value">{{balances.total | number}} Sats</div>
</div>
</div>

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CLBalancesInfoComponent } from './balances-info.component';
describe('CLBalancesInfoComponent', () => {
let component: CLBalancesInfoComponent;
let fixture: ComponentFixture<CLBalancesInfoComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CLBalancesInfoComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CLBalancesInfoComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,13 @@
import { Component, Input } from '@angular/core';
@Component({
selector: 'rtl-cl-balances-info',
templateUrl: './balances-info.component.html',
styleUrls: ['./balances-info.component.scss']
})
export class CLBalancesInfoComponent {
@Input() balances = { onchain: 0, lightning: 0, total: 0 };
constructor() {}
}

@ -0,0 +1,34 @@
<div fxLayout="column" fxLayoutAlign="space-between stretch" fxFlex="100">
<div fxLayout="column" fxFlex="9" fxLayoutAlign="end start">
<span class="dashboard-capacity-header this-channel-capacity">Total Capacity</span>
<div fxLayout="row" fxLayoutAlign="space-between start" class="w-100">
<mat-hint fxFlex="40" fxLayoutAlign="start center" class="font-size-90"><strong class="font-weight-900 mr-5px">Local:</strong>{{channelBalances.localBalance || 0 | number}} Sats</mat-hint>
<mat-hint fxFlex="20" fxLayoutAlign="center center" class="font-size-90">
<fa-icon [icon]="faBalanceScale" class="mr-3px" matTooltip="Balance Score"></fa-icon>
({{channelBalances.balancedness || 0 | number}})
</mat-hint>
<mat-hint fxFlex="40" fxLayoutAlign="end center" class="font-size-90"><strong class="font-weight-900 mr-5px">Remote:</strong>{{channelBalances.remoteBalance || 0 | number}} Sats</mat-hint>
</div>
<mat-progress-bar class="dashboard-progress-bar this-channel-bar" mode="determinate" color="accent" value="{{channelBalances.localBalance && channelBalances.localBalance > 0 ? ((+channelBalances.localBalance/((+channelBalances.localBalance)+(+channelBalances.remoteBalance)))*100) : 0}}"></mat-progress-bar>
</div>
<div fxLayout="column" fxFlex="3" fxLayoutAlign="end stretch"><mat-divider class="dashboard-divider"></mat-divider></div>
<div fxLayout="column" fxFlex.gt-sm="88" fxFlex="84" fxLayoutAlign="start start" class="channels-capacity-scroll" perfectScrollbar>
<div fxLayout="column" fxFlex="100" class="w-100" *ngIf="allChannels && allChannels.length > 0; else noChannelBlock">
<div *ngFor="let channel of allChannels" class="mt-2">
<span class="dashboard-capacity-header" matTooltip="{{channel.remote_alias || channel.remote_pubkey}}" matTooltipDisabled="{{(channel.remote_alias || channel.remote_pubkey).length < 26}}">{{(channel.remote_alias || channel.remote_pubkey) | slice:0:24}}{{(channel.remote_alias || channel.remote_pubkey).length > 25 ? '...' : ''}}</span>
<div fxLayout="row" fxLayoutAlign="space-between start" class="w-100">
<mat-hint fxFlex="40" fxLayoutAlign="start center" class="font-size-90 color-primary"><strong class="font-weight-900 mr-5px">Local:</strong>{{channel.local_balance || 0 | number}} Sats</mat-hint>
<mat-hint fxFlex="20" fxLayoutAlign="center center" class="font-size-90 color-primary">
<fa-icon [icon]="faBalanceScale" class="color-primary mr-3px" matTooltip="Balance Score"></fa-icon>
({{channel.balancedness || 0 | number}})
</mat-hint>
<mat-hint fxFlex="40" fxLayoutAlign="end center" class="font-size-90 color-primary"><strong class="font-weight-900 mr-5px">Remote:</strong>{{channel.remote_balance || 0 | number}} Sats</mat-hint>
</div>
<mat-progress-bar class="dashboard-progress-bar" mode="determinate" value="{{channel.local_balance && channel.local_balance > 0 ? ((+channel.local_balance/((+channel.local_balance)+(+channel.remote_balance)))*100) : 0}}"></mat-progress-bar>
</div>
</div>
</div>
</div>
<ng-template #noChannelBlock>
<div fxLayout="column" fxFlex="100" class="w-100 mt-2">No channels available.</div>
</ng-template>

@ -0,0 +1,5 @@
.channels-capacity-scroll {
width: 100%;
height: 100%;
overflow-y: hidden;
}

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CLChannelCapacityInfoComponent } from './channel-capacity-info.component';
describe('CLChannelCapacityInfoComponent', () => {
let component: CLChannelCapacityInfoComponent;
let fixture: ComponentFixture<CLChannelCapacityInfoComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CLChannelCapacityInfoComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CLChannelCapacityInfoComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,22 @@
import { Component, OnChanges, Input } from '@angular/core';
import { faBalanceScale, faDumbbell } from '@fortawesome/free-solid-svg-icons';
import { ChannelCL } from '../../../shared/models/clModels';
@Component({
selector: 'rtl-cl-channel-capacity-info',
templateUrl: './channel-capacity-info.component.html',
styleUrls: ['./channel-capacity-info.component.scss']
})
export class CLChannelCapacityInfoComponent implements OnChanges {
public faBalanceScale = faBalanceScale;
public faDumbbell = faDumbbell;
@Input() channelBalances: {localBalance: number, remoteBalance: number, balancedness: string};
@Input() allChannels: ChannelCL[];
@Input() sortBy: string = 'Balance Score';
constructor() {}
ngOnChanges() {}
}

@ -0,0 +1,24 @@
<div fxLayout="column" fxLayoutAlign="space-between stretch" fxFlex="100">
<div fxLayout="column" fxFlex="9" fxLayoutAlign="end start">
<span class="dashboard-capacity-header this-channel-capacity">Total Capacity</span>
<mat-hint class="font-size-90">{{totalLiquidity | number}} Sats</mat-hint>
<mat-progress-bar class="dashboard-progress-bar this-channel-bar" mode="determinate" color="accent" value="100"></mat-progress-bar>
</div>
<div fxLayout="column" fxFlex="3" fxLayoutAlign="end stretch"><mat-divider class="dashboard-divider"></mat-divider></div>
<div fxLayout="column" fxFlex.gt-sm="88" fxFlex="84" fxLayoutAlign="start start" class="channels-capacity-scroll" perfectScrollbar>
<div fxLayout="column" fxFlex="100" class="w-100" *ngIf="allChannels && allChannels.length > 0; else noChannelBlock">
<div *ngFor="let channel of allChannels" class="mt-2">
<span class="dashboard-capacity-header" matTooltip="{{channel.remote_alias || channel.remote_pubkey}}" matTooltipDisabled="{{(channel.remote_alias || channel.remote_pubkey).length < 26}}">{{(channel.remote_alias || channel.remote_pubkey) | slice:0:24}}{{(channel.remote_alias || channel.remote_pubkey).length > 25 ? '...' : ''}}</span>
<div fxLayout="row" fxLayoutAlign="space-between start" class="w-100">
<mat-hint *ngIf="direction === 'In'" fxFlex="100" fxLayoutAlign="start center" class="font-size-90 color-primary"><strong class="font-weight-900 mr-5px">Capacity: </strong>{{channel.remote_balance || 0 | number}} Sats</mat-hint>
<mat-hint *ngIf="direction === 'Out'" fxFlex="100" fxLayoutAlign="start center" class="font-size-90 color-primary"><strong class="font-weight-900 mr-5px">Capacity: </strong>{{channel.local_balance || 0 | number}} Sats</mat-hint>
</div>
<mat-progress-bar *ngIf="direction === 'In'" class="dashboard-progress-bar" mode="determinate" value="{{(totalLiquidity > 0) ? ((+channel.remote_balance || 0)/(totalLiquidity) * 100) : 0}}"></mat-progress-bar>
<mat-progress-bar *ngIf="direction === 'Out'" class="dashboard-progress-bar" mode="determinate" value="{{(totalLiquidity > 0) ? ((+channel.local_balance || 0)/(totalLiquidity) * 100) : 0}}"></mat-progress-bar>
</div>
</div>
</div>
</div>
<ng-template #noChannelBlock>
<div fxLayout="column" fxFlex="100" class="w-100 mt-2">No channels available.</div>
</ng-template>

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CLChannelLiquidityInfoComponent } from './channel-liquidity-info.component';
describe('CLChannelLiquidityInfoComponent', () => {
let component: CLChannelLiquidityInfoComponent;
let fixture: ComponentFixture<CLChannelLiquidityInfoComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CLChannelLiquidityInfoComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CLChannelLiquidityInfoComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,28 @@
import { Component, OnChanges, Input } from '@angular/core';
import { ChannelCL } from '../../../shared/models/clModels';
@Component({
selector: 'rtl-cl-channel-liquidity-info',
templateUrl: './channel-liquidity-info.component.html',
styleUrls: ['./channel-liquidity-info.component.scss']
})
export class CLChannelLiquidityInfoComponent implements OnChanges {
@Input() direction: string;
@Input() totalLiquidity: number;
@Input() allChannels: ChannelCL[];
public maxAmount = 0;
constructor() {}
ngOnChanges() {
if (this.allChannels && this.allChannels.length > 0) {
if(this.direction === 'In') {
this.maxAmount = +this.allChannels[0].their_channel_reserve_satoshis <= 4294967 ? +this.allChannels[0].their_channel_reserve_satoshis : 4294967;
} else {
this.maxAmount = +this.allChannels[0].our_channel_reserve_satoshis <= 4294967 ? +this.allChannels[0].our_channel_reserve_satoshis : 4294967;
}
}
}
}

@ -0,0 +1,28 @@
<div fxLayout="column" fxFlex="50" fxLayoutAlign="space-between stretch">
<div>
<h4 fxLayoutAlign="start" class="dashboard-info-title">Active</h4>
<div class="overflow-wrap dashboard-info-value"><span class="dot tiny-dot green"></span>{{(channelsStatus.active.channels || 0) | number}}</div>
</div>
<div>
<h4 fxLayoutAlign="start" class="dashboard-info-title">Pending</h4>
<div class="overflow-wrap dashboard-info-value"><span class="dot tiny-dot yellow"></span>{{(channelsStatus.pending.channels || 0) | number}}</div>
</div>
<div>
<h4 fxLayoutAlign="start" class="dashboard-info-title">Inactive</h4>
<div class="overflow-wrap dashboard-info-value"><span class="dot tiny-dot grey"></span>{{(channelsStatus.inactive.channels || 0) | number}}</div>
</div>
</div>
<div fxLayout="column" fxFlex="50" fxLayoutAlign="space-between stretch">
<div>
<h4 fxLayoutAlign="start" class="dashboard-info-title">Capacity</h4>
<div class="overflow-wrap dashboard-info-value">{{(channelsStatus.active.capacity || 0) | number}} Sats</div>
</div>
<div>
<h4 fxLayoutAlign="start" class="dashboard-info-title">Capacity</h4>
<div class="overflow-wrap dashboard-info-value">{{(channelsStatus.pending.capacity || 0) | number}} Sats</div>
</div>
<div>
<h4 fxLayoutAlign="start" class="dashboard-info-title">Capacity</h4>
<div class="overflow-wrap dashboard-info-value">{{(channelsStatus.inactive.capacity || 0) | number}} Sats</div>
</div>
</div>

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CLChannelStatusInfoComponent } from './channel-status-info.component';
describe('CLChannelStatusInfoComponent', () => {
let component: CLChannelStatusInfoComponent;
let fixture: ComponentFixture<CLChannelStatusInfoComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CLChannelStatusInfoComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CLChannelStatusInfoComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,16 @@
import { Component, OnChanges, Input } from '@angular/core';
import { ChannelsStatusCL } from '../../../shared/models/clModels';
@Component({
selector: 'rtl-cl-channel-status-info',
templateUrl: './channel-status-info.component.html',
styleUrls: ['./channel-status-info.component.scss']
})
export class CLChannelStatusInfoComponent implements OnChanges {
@Input() channelsStatus: ChannelsStatusCL = {};
constructor() {}
ngOnChanges() {}
}

@ -0,0 +1,36 @@
<div fxLayout="column" fxFlex="50" fxLayoutAlign="space-between stretch">
<div>
<h4 fxLayoutAlign="start" class="dashboard-info-title">Total</h4>
<div class="overflow-wrap dashboard-info-value">{{fees?.feeCollected | number}} Sats</div>
</div>
<!-- <div>
<h4 fxLayoutAlign="start" class="dashboard-info-title">Weekly</h4>
<div class="overflow-wrap dashboard-info-value">{{fees?.feeCollected | number}} Sats</div>
</div>
<div>
<h4 fxLayoutAlign="start" class="dashboard-info-title">Monthly</h4>
<div class="overflow-wrap dashboard-info-value">{{fees?.feeCollected | number}} Sats</div>
</div> -->
<div fxFlex="20">
<h4 class="dashboard-info-title"></h4>
<span class="overflow-wrap dashboard-info-value"></span>
</div>
</div>
<!-- <div fxLayout="column" fxFlex="50" fxLayoutAlign="space-between stretch">
<div>
<h4 fxLayoutAlign="start" class="dashboard-info-title">Transactions</h4>
<div class="overflow-wrap dashboard-info-value">{{fees?.daily_tx_count | number}}</div>
</div>
<div>
<h4 fxLayoutAlign="start" class="dashboard-info-title">Transactions</h4>
<div class="overflow-wrap dashboard-info-value">{{fees?.weekly_tx_count | number}}</div>
</div>
<div>
<h4 fxLayoutAlign="start" class="dashboard-info-title">Transactions</h4>
<div class="overflow-wrap dashboard-info-value">{{fees?.monthly_tx_count | number}}</div>
</div>
<div fxFlex="20">
<h4 class="dashboard-info-title"></h4>
<span class="overflow-wrap dashboard-info-value"></span>
</div>
</div> -->

@ -1,20 +1,20 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CLChannelsComponent } from './channels.component';
import { CLFeeInfoComponent } from './fee-info.component';
describe('CLChannelsComponent', () => {
let component: CLChannelsComponent;
let fixture: ComponentFixture<CLChannelsComponent>;
describe('CLFeeInfoComponent', () => {
let component: CLFeeInfoComponent;
let fixture: ComponentFixture<CLFeeInfoComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CLChannelsComponent ]
declarations: [ CLFeeInfoComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CLChannelsComponent);
fixture = TestBed.createComponent(CLFeeInfoComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

@ -0,0 +1,23 @@
import { Component, OnChanges, Input } from '@angular/core';
import { FeesCL } from '../../../shared/models/clModels';
@Component({
selector: 'rtl-cl-fee-info',
templateUrl: './fee-info.component.html',
styleUrls: ['./fee-info.component.scss']
})
export class CLFeeInfoComponent implements OnChanges {
@Input() fees: FeesCL;
totalFees = [{'name': 'Total', 'value': 0}];
maxFeeValue = 100;
constructor() {}
ngOnChanges() {
if(this.fees.feeCollected) {
this.totalFees = [{'name': 'Total', 'value': this.fees.feeCollected}];
Object.assign(this, this.totalFees);
}
}
}

@ -1,20 +1,20 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FeeRatesComponent } from './fee-rates.component';
import { CLFeeRatesComponent } from './fee-rates.component';
describe('FeeRatesComponent', () => {
let component: FeeRatesComponent;
let fixture: ComponentFixture<FeeRatesComponent>;
describe('CLFeeRatesComponent', () => {
let component: CLFeeRatesComponent;
let fixture: ComponentFixture<CLFeeRatesComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ FeeRatesComponent ]
declarations: [ CLFeeRatesComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FeeRatesComponent);
fixture = TestBed.createComponent(CLFeeRatesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

@ -1,155 +1,84 @@
<div fxLayout="column" fxLayout.gt-sm="row wrap">
<div fxFlex="20" class="padding-gap">
<mat-card [ngClass]="{'custom-card error-border': flgLoading[2]==='error','custom-card': true}">
<mat-card-header class="bg-primary" fxLayoutAlign="center end">
<mat-card-title class="m-0 pt-2">
<h5>Wallet Balance</h5>
</mat-card-title>
</mat-card-header>
<mat-card-content fxLayout="column" fxLayoutAlign="center center">
<mat-card-content class="mt-1">
<mat-icon class="icon-large">account_balance_wallet</mat-icon>
</mat-card-content>
<span *ngIf="information?.currency_unit; else withoutData">
<h3 *ngIf="selNode?.satsToBTC; else smallerUnit1">{{totalBalance.btc_confBalance | number}}
{{information?.currency_unit}}</h3>
<ng-template #smallerUnit1>
<h3>{{totalBalance.confBalance | number}} {{information?.smaller_currency_unit}}</h3>
</ng-template>
</span>
</mat-card-content>
<mat-progress-bar class="mt-minus-5" *ngIf="flgLoading[2]===true" mode="indeterminate"></mat-progress-bar>
<mat-divider></mat-divider>
</mat-card>
</div>
<div fxFlex="20" class="padding-gap">
<mat-card [ngClass]="{'custom-card error-border': flgLoading[0]==='error','custom-card': true}">
<mat-card-header class="bg-primary" fxLayoutAlign="center center">
<mat-card-title class="m-0 pt-2">
<h5>Peers</h5>
</mat-card-title>
</mat-card-header>
<mat-card-content fxLayout="column" fxLayoutAlign="center center">
<mat-card-content class="mt-1">
<mat-icon class="icon-large">group</mat-icon>
</mat-card-content>
<h3 *ngIf="information.num_peers; else zeroPeers">{{information?.num_peers | number}}</h3>
<ng-template #zeroPeers>
<h3>0</h3>
</ng-template>
</mat-card-content>
<mat-progress-bar class="mt-minus-5" *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
<mat-divider></mat-divider>
</mat-card>
<div fxLayout="column" *ngIf="selNode.userPersona === userPersonaEnum.OPERATOR; else merchantDashboard">
<div fxLayout="row" fxLayoutAlign="start end" class="padding-gap-x page-title-container mb-0">
<fa-icon [icon]="!flgLoading[0] ? faSmile : faFrown" class="page-title-img mr-1"></fa-icon>
<span class="page-title">{{!flgLoading[0] ? 'Welcome! Your node is up and running.' : 'Error! Please check the server connection.'}}</span>
</div>
<div fxFlex="20" class="padding-gap">
<mat-card [ngClass]="{'custom-card error-border': flgLoading[3]==='error','custom-card': true}">
<mat-card-header class="bg-primary" fxLayoutAlign="center center">
<mat-card-title class="m-0 pt-2">
<h5>Channel Balance</h5>
</mat-card-title>
</mat-card-header>
<mat-card-content fxLayout="column" fxLayoutAlign="center center">
<mat-card-content class="mt-1">
<mat-icon class="icon-large">linear_scale</mat-icon>
</mat-card-content>
<span *ngIf="information?.currency_unit; else withoutData">
<h3 *ngIf="selNode?.satsToBTC; else smallerUnit2">{{lrBalance.btc_localBalance | number}}
{{information?.currency_unit}}</h3>
<ng-template #smallerUnit2>
<h3>{{lrBalance.localBalance | number}} {{information?.smaller_currency_unit}}</h3>
</ng-template>
</span>
</mat-card-content>
<mat-progress-bar class="mt-minus-5" *ngIf="flgLoading[3]===true || flgLoading[0]===true" mode="indeterminate">
</mat-progress-bar>
<mat-divider></mat-divider>
</mat-card>
</div>
<div fxFlex="20" class="padding-gap">
<mat-card [ngClass]="{'custom-card error-border': flgLoading[1]==='error','custom-card': true}">
<mat-card-header class="bg-primary" fxLayoutAlign="center center">
<mat-card-title class="m-0 pt-2">
<h5>Fee Report</h5>
</mat-card-title>
</mat-card-header>
<mat-card-content fxLayout="column" fxLayoutAlign="center center">
<mat-card-content class="mt-1">
<mat-icon class="icon-large">redeem</mat-icon>
<mat-grid-list cols="10" [rowHeight]="operatorCardHeight">
<mat-grid-tile *ngFor="let card of operatorCards" [colspan]="card.cols" [rowspan]="card.rows">
<mat-card fxLayout="column" fxLayoutAlign="start start" class="dashboard-card p-24">
<mat-card-header>
<mat-card-title>
<fa-icon [icon]="card.icon" class="mr-1"></fa-icon>
<span>{{card.title}}</span>
<button *ngIf="card.link" mat-icon-button class="more-button mt-1" [matMenuTriggerFor]="menuOperator" aria-label="Toggle menu">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menuOperator="matMenu" class="dashboard-vert-menu" xPosition="before">
<button mat-menu-item (click)="onNavigateTo(card.link)">Go To {{card.goTo}}</button>
<button *ngIf="card.id === 'capacity'" (click)="onsortChannelsBy()" mat-menu-item>Sort By {{sortField === 'Balance Score' ? 'Capacity' : 'Balance Score'}}</button>
</mat-menu>
</mat-card-title>
</mat-card-header>
<mat-card-content class="dashboard-card-content w-100" fxFlex="95">
<div [ngSwitch]="card.id" fxLayout="column" fxFlex="100">
<rtl-cl-node-info fxFlex="100" *ngSwitchCase="'node'" [information]="information" [showColorFieldSeparately]="false" [ngClass]="{'error-border': flgLoading[0]==='error'}"></rtl-cl-node-info>
<rtl-cl-balances-info fxFlex="100" *ngSwitchCase="'balance'" [balances]="balances" [ngClass]="{'error-border': flgLoading[2]==='error' || flgLoading[5]==='error'}"></rtl-cl-balances-info>
<rtl-cl-channel-capacity-info fxFlex="100" *ngSwitchCase="'capacity'" [sortBy]="sortField" [channelBalances]="channelBalances" [allChannels]="allChannelsCapacity" [ngClass]="{'error-border': flgLoading[5]==='error'}"></rtl-cl-channel-capacity-info>
<rtl-cl-fee-info fxFlex="100" *ngSwitchCase="'fee'" [fees]="fees" [ngClass]="{'error-border': flgLoading[1]==='error'}"></rtl-cl-fee-info>
<rtl-cl-channel-status-info fxFlex="100" *ngSwitchCase="'status'" [channelsStatus]="channelsStatus" [ngClass]="{'error-border': flgLoading[5]==='error' || flgLoading[6]==='error'}"></rtl-cl-channel-status-info>
<h3 *ngSwitchDefault>Error! Unable to find information!</h3>
</div>
</mat-card-content>
<h3>{{fees?.feeCollected | number}} (mSats)</h3>
</mat-card-content>
<mat-progress-bar class="mt-minus-5" *ngIf="flgLoading[1]===true || flgLoading[0]===true" mode="indeterminate">
</mat-progress-bar>
<mat-divider></mat-divider>
</mat-card>
</div>
<div fxFlex="20" class="padding-gap">
<mat-card [ngClass]="{'custom-card error-border': flgLoading[0]==='error','custom-card': true}">
<mat-card-header class="bg-primary" fxLayoutAlign="center center">
<mat-card-title class="m-0 pt-2">
<h5>Channel Status</h5>
</mat-card-title>
</mat-card-header>
<mat-card-content>
<div fxLayout="column" class="pl-4">
<mat-list class="channel-status-list" fxFlex="100" fxLayoutAlign="start start">
<mat-list-item fxFlex="65" fxLayoutAlign="start start">Active</mat-list-item>
<mat-list-item fxFlex="25" fxLayoutAlign="start start">
<p class="mat-button-text">{{information?.num_active_channels}}</p>
</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list class="channel-status-list" fxFlex="100" fxLayoutAlign="start start">
<mat-list-item fxFlex="65" fxLayoutAlign="start start">Inactive</mat-list-item>
<mat-list-item fxFlex="25" fxLayoutAlign="start start">
<p class="mat-button-text">{{information?.num_inactive_channels}}</p>
</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list class="channel-status-list" fxFlex="100" fxLayoutAlign="start start">
<mat-list-item fxFlex="65" fxLayoutAlign="start start">Pending</mat-list-item>
<mat-list-item fxFlex="25" fxLayoutAlign="start start">
<p class="mat-button-text">{{information?.num_pending_channels}}</p>
</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
</div>
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate" class="mt-minus-5"></mat-progress-bar>
<mat-divider></mat-divider>
</mat-card-content>
</mat-card>
</div>
</mat-card>
</mat-grid-tile>
</mat-grid-list>
</div>
<div fxLayout="column" fxLayout.gt-sm="row wrap">
<div fxFlex="40" class="padding-gap">
<mat-card [ngClass]="{'custom-card error-border': flgLoading[3]==='error','custom-card': true}">
<mat-card-header class="bg-primary" fxLayoutAlign="center center">
<mat-card-title class="m-0 pt-2">
<h5>Local-Remote Channel Capacity</h5>
</mat-card-title>
</mat-card-header>
<mat-card-content>
<div fxLayout="row" class="card-chnl-balances">
<div fxFlex="100" fxLayoutAlign="center center">
<ngx-charts-bar-vertical [view]="view" [scheme]="colorScheme" [results]="lrBalances"
[yAxisLabel]="yAxisLabel" [yScaleMax]="maxBalanceValue" xAxis="false" yAxis="true" showYAxis="true"
showDataLabel="true" tooltipDisabled="true">
</ngx-charts-bar-vertical>
</div>
</div>
<mat-progress-bar *ngIf="flgLoading[5]===true" mode="indeterminate" class="mt-minus-5"></mat-progress-bar>
<mat-divider></mat-divider>
</mat-card-content>
</mat-card>
</div>
<div fxFlex="30" class="padding-gap">
<rtl-cl-fee-rates [flgLoading]="flgLoading[4]" [feeRates]="feeRatesPerKB" [feeRateStyle]="'KB'"></rtl-cl-fee-rates>
</div>
<div fxFlex="30" class="padding-gap">
<rtl-cl-fee-rates [flgLoading]="flgLoading[4]" [feeRates]="feeRatesPerKW" [feeRateStyle]="'KW'"></rtl-cl-fee-rates>
<ng-template #merchantDashboard>
<div fxLayout="row" fxLayoutAlign="start end" class="padding-gap-x page-title-container mb-0">
<fa-icon [icon]="faSmile" class="page-title-img mr-1"></fa-icon>
<span class="page-title">Welcome! Your node is up and running.</span>
</div>
</div>
<ng-template #withoutData>
<h3>Sats</h3>
<mat-grid-list cols="6" [rowHeight]="merchantCardHeight">
<mat-grid-tile *ngFor="let card of merchantCards" [colspan]="card.cols" [rowspan]="card.rows">
<mat-card fxLayout="column" fxLayoutAlign="start start" class="dashboard-card" [ngClass]="{'p-24': card.id !== 'transactions'}">
<mat-card-header *ngIf="card.id !== 'transactions'">
<mat-card-title>
<fa-icon [icon]="card.icon" class="mr-1"></fa-icon>
<span>{{card.title}}</span>
<button *ngIf="card.link" mat-icon-button class="more-button mt-1" [matMenuTriggerFor]="menuMerchant" aria-label="Toggle menu">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menuMerchant="matMenu" class="dashboard-vert-menu" xPosition="before">
<button mat-menu-item (click)="onNavigateTo(card.link)">Go To {{card.goTo}}</button>
</mat-menu>
</mat-card-title>
</mat-card-header>
<mat-card-content class="dashboard-card-content w-100" fxFlex="{{card.id !== 'transactions' ? 95 : 100}}">
<div [ngSwitch]="card.id" fxLayout="column" fxFlex="100">
<rtl-cl-node-info fxFlex="100" *ngSwitchCase="'node'" [information]="information" [ngClass]="{'error-border': flgLoading[0]==='error'}"></rtl-cl-node-info>
<rtl-cl-balances-info fxFlex="100" *ngSwitchCase="'balance'" [balances]="balances" [ngClass]="{'error-border': flgLoading[2]==='error' || flgLoading[5]==='error'}"></rtl-cl-balances-info>
<rtl-cl-channel-liquidity-info fxFlex="100" *ngSwitchCase="'inboundLiq'" [direction]="'In'" [totalLiquidity]="totalInboundLiquidity" [allChannels]="allInboundChannels" [ngClass]="{'error-border': flgLoading[5]==='error'}"></rtl-cl-channel-liquidity-info>
<rtl-cl-channel-liquidity-info fxFlex="100" *ngSwitchCase="'outboundLiq'" [direction]="'Out'" [totalLiquidity]="totalOutboundLiquidity" [allChannels]="allOutboundChannels" [ngClass]="{'error-border': flgLoading[5]==='error'}"></rtl-cl-channel-liquidity-info>
<span fxLayout="column" fxFlex="100" fxLayoutAlign="space-between start" *ngSwitchCase="'transactions'">
<mat-tab-group fxLayout="column" class="w-100 dashboard-tabs-group">
<mat-tab label="Receive"><rtl-cl-lightning-invoices class="h-100" [showDetails]="false"></rtl-cl-lightning-invoices></mat-tab>
<mat-tab label="Pay"><rtl-cl-lightning-payments [showDetails]="false"></rtl-cl-lightning-payments></mat-tab>
<mat-tab [disabled]="true">
<ng-template mat-tab-label>
<button mat-icon-button class="more-button" [matMenuTriggerFor]="menuTransactions" aria-label="Toggle menu" style="max-width: 20px;">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menuTransactions="matMenu" class="dashboard-vert-menu" xPosition="before">
<button mat-menu-item (click)="onNavigateTo(card.link)">Go To {{card.goTo}}</button>
</mat-menu>
</ng-template>
</mat-tab>
</mat-tab-group>
</span>
<h3 *ngSwitchDefault>Error! Unable to find information!</h3>
</div>
</mat-card-content>
</mat-card>
</mat-grid-tile>
</mat-grid-list>
</ng-template>

@ -1,16 +1,17 @@
.network-info-list .mat-list-item {
height: 44px;
.dashboard-card {
position: absolute;
top: 1rem;
left: 1rem;
right: 1rem;
bottom: 1rem;
}
.mat-column-bytes_sent, .mat-column-bytes_recv, .mat-column-sat_sent, .mat-column-sat_recv, .mat-column-inbound, .mat-column-ping_time {
flex: 0 0 8%;
min-width: 80px;
.more-button {
position: absolute;
top: 7px;
right: 7px;
}
.card-chnl-balances {
min-height: 354px;
.dashboard-card-content {
text-align: left;
}
.channel-status-list {
max-height: 46px !important;
}

@ -1,15 +1,19 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router } from '@angular/router';
import { Subject } from 'rxjs';
import { takeUntil, filter } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { Actions } from '@ngrx/effects';
import { faSmile, faFrown } from '@fortawesome/free-regular-svg-icons';
import { faAngleDoubleDown, faAngleDoubleUp, faChartPie, faBolt, faServer, faNetworkWired } from '@fortawesome/free-solid-svg-icons';
import { LoggerService } from '../../shared/services/logger.service';
import { GetInfoCL, FeesCL, BalanceCL, LocalRemoteBalanceCL, FeeRatesCL } from '../../shared/models/clModels';
import { CommonService } from '../../shared/services/common.service';
import { UserPersonaEnum, ScreenSizeEnum } from '../../shared/services/consts-enums-functions';
import { ChannelsStatusCL, GetInfoCL, FeesCL, ChannelCL, BalanceCL, LocalRemoteBalanceCL, FeeRatesCL } from '../../shared/models/clModels';
import { SelNodeChild } from '../../shared/models/RTLconfig';
import * as RTLActions from '../../store/rtl.actions';
import * as fromRTLReducer from '../../store/rtl.reducers';
import * as RTLActions from '../../store/rtl.actions';
@Component({
selector: 'rtl-cl-home',
@ -17,55 +21,96 @@ import * as fromRTLReducer from '../../store/rtl.reducers';
styleUrls: ['./home.component.scss']
})
export class CLHomeComponent implements OnInit, OnDestroy {
public faSmile = faSmile;
public faFrown = faFrown;
public faAngleDoubleDown = faAngleDoubleDown;
public faAngleDoubleUp = faAngleDoubleUp;
public faChartPie = faChartPie;
public faBolt = faBolt;
public faServer = faServer;
public faNetworkWired = faNetworkWired;
public flgChildInfoUpdated = false;
public userPersonaEnum = UserPersonaEnum;
public activeChannels = 0;
public inactiveChannels = 0;
public channelBalances = {localBalance: 0, remoteBalance: 0, balancedness: '0'};
public selNode: SelNodeChild = {};
public fees: FeesCL;
public information: GetInfoCL = {};
public totalBalance: BalanceCL = {};
public lrBalance: LocalRemoteBalanceCL = { localBalance: 0, remoteBalance: 0 };
public flgLoading: Array<Boolean | 'error'> = [true, true, true, true, true];
public balances = { onchain: -1, lightning: -1, total: 0 };
public allChannels: ChannelCL[] = [];
public channelsStatus: ChannelsStatusCL = {};
public allChannelsCapacity: ChannelCL[] = [];
public allInboundChannels: ChannelCL[] = [];
public allOutboundChannels: ChannelCL[] = [];
public totalInboundLiquidity = 0;
public totalOutboundLiquidity = 0;
public feeRatesPerKB: FeeRatesCL = {};
public feeRatesPerKW: FeeRatesCL = {};
public operatorCards = [];
public merchantCards = [];
public screenSize = '';
public operatorCardHeight = '330px';
public merchantCardHeight = '65px';
public sortField = 'Balance Score';
public flgLoading: Array<Boolean | 'error'> = [true, true, true, true, true, true, true, true]; // 0: Info, 1: Fee, 2: Wallet, 3: Channel, 4: Network
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject()];
private unsub: Array<Subject<void>> = [new Subject(), new Subject(), new Subject()];
public position = 'below';
barPadding = 0;
maxBalanceValue = 0;
lrBalances = [{'name': 'Local Balance', 'value': 0}, {'name': 'Remote Balance', 'value': 0}];
flgTotalCalculated = false;
view = [];
yAxisLabel = 'Balance';
colorScheme = {domain: ['#FF0000']};
feeRatesPerKB: FeeRatesCL = {};
feeRatesPerKW: FeeRatesCL = {};
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private actions$: Actions) {
switch (true) {
case (window.innerWidth <= 730):
this.view = [250, 352];
break;
case (window.innerWidth > 415 && window.innerWidth <= 730):
this.view = [280, 352];
break;
case (window.innerWidth > 730 && window.innerWidth <= 1024):
this.view = [300, 352];
break;
case (window.innerWidth > 1024 && window.innerWidth <= 1280):
this.view = [350, 352];
break;
default:
this.view = [300, 352];
break;
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private actions$: Actions, private commonService: CommonService, private router: Router) {
this.screenSize = this.commonService.getScreenSize();
if(this.screenSize === ScreenSizeEnum.XS) {
this.operatorCards = [
{ id: 'node', icon: this.faServer, title: 'Node Information', cols: 10, rows: 1 },
{ id: 'balance', goTo: 'On-Chain', link: '/lnd/onchain', icon: this.faChartPie, title: 'Balances', cols: 10, rows: 1 },
{ id: 'fee', goTo: 'Routing', link: '/lnd/routing', icon: this.faBolt, title: 'Routing Fee Report', cols: 10, rows: 1 },
{ id: 'status', goTo: 'Channels', link: '/lnd/peerschannels', icon: this.faNetworkWired, title: 'Channels', cols: 10, rows: 1 },
{ id: 'capacity', goTo: 'Channels', link: '/lnd/peerschannels', icon: this.faNetworkWired, title: 'Channels Capacity', cols: 10, rows: 2 }
];
this.merchantCards = [
{ id: 'balance', goTo: 'On-Chain', link: '/lnd/onchain', icon: this.faChartPie, title: 'Balances', cols: 6, rows: 4 },
{ id: 'transactions', goTo: 'Transactions', link: '/lnd/transactions', title: '', cols: 6, rows: 4 },
{ id: 'inboundLiq', goTo: 'Channels', link: '/lnd/peerschannels', icon: this.faAngleDoubleDown, title: 'In-Bound Liquidity', cols: 6, rows: 8 },
{ id: 'outboundLiq', goTo: 'Channels', link: '/lnd/peerschannels', icon: this.faAngleDoubleUp, title: 'Out-Bound Liquidity', cols: 6, rows: 8 }
];
} else if(this.screenSize === ScreenSizeEnum.SM || this.screenSize === ScreenSizeEnum.MD) {
this.operatorCards = [
{ id: 'node', icon: this.faServer, title: 'Node Information', cols: 5, rows: 1 },
{ id: 'balance', goTo: 'On-Chain', link: '/lnd/onchain', icon: this.faChartPie, title: 'Balances', cols: 5, rows: 1 },
{ id: 'fee', goTo: 'Routing', link: '/lnd/routing', icon: this.faBolt, title: 'Routing Fee Report', cols: 5, rows: 1 },
{ id: 'status', goTo: 'Channels', link: '/lnd/peerschannels', icon: this.faNetworkWired, title: 'Channels', cols: 5, rows: 1 },
{ id: 'capacity', goTo: 'Channels', link: '/lnd/peerschannels', icon: this.faNetworkWired, title: 'Channels Capacity', cols: 10, rows: 2 }
];
this.merchantCards = [
{ id: 'balance', goTo: 'On-Chain', link: '/lnd/onchain', icon: this.faChartPie, title: 'Balances', cols: 3, rows: 4 },
{ id: 'transactions', goTo: 'Transactions', link: '/lnd/transactions', title: '', cols: 3, rows: 4 },
{ id: 'inboundLiq', goTo: 'Channels', link: '/lnd/peerschannels', icon: this.faAngleDoubleDown, title: 'In-Bound Liquidity', cols: 3, rows: 8 },
{ id: 'outboundLiq', goTo: 'Channels', link: '/lnd/peerschannels', icon: this.faAngleDoubleUp, title: 'Out-Bound Liquidity', cols: 3, rows: 8 }
];
} else {
this.operatorCardHeight = ((window.screen.height - 200) / 2) + 'px';
this.merchantCardHeight = ((window.screen.height - 210) / 10) + 'px';
this.operatorCards = [
{ id: 'node', icon: this.faServer, title: 'Node Information', cols: 3, rows: 1 },
{ id: 'balance', goTo: 'On-Chain', link: '/lnd/onchain', icon: this.faChartPie, title: 'Balances', cols: 3, rows: 1 },
{ id: 'capacity', goTo: 'Channels', link: '/lnd/peerschannels', icon: this.faNetworkWired, title: 'Channels Capacity', cols: 4, rows: 2 },
{ id: 'fee', goTo: 'Routing', link: '/lnd/routing', icon: this.faBolt, title: 'Routing Fee Report', cols: 3, rows: 1 },
{ id: 'status', goTo: 'Channels', link: '/lnd/peerschannels', icon: this.faNetworkWired, title: 'Channels', cols: 3, rows: 1 }
];
this.merchantCards = [
{ id: 'balance', goTo: 'On-Chain', link: '/lnd/onchain', icon: this.faChartPie, title: 'Balances', cols: 2, rows: 5 },
{ id: 'inboundLiq', goTo: 'Channels', link: '/lnd/peerschannels', icon: this.faAngleDoubleDown, title: 'In-Bound Liquidity', cols: 2, rows: 10 },
{ id: 'outboundLiq', goTo: 'Channels', link: '/lnd/peerschannels', icon: this.faAngleDoubleUp, title: 'Out-Bound Liquidity', cols: 2, rows: 10 },
{ id: 'transactions', goTo: 'Transactions', link: '/lnd/transactions', title: '', cols: 2, rows: 5 }
];
}
Object.assign(this, this.lrBalances);
}
ngOnInit() {
this.actions$.pipe(takeUntil(this.unsub[0]),
filter(action => action.type === RTLActions.SET_SELECTED_NODE))
.subscribe((data) => {
this.flgTotalCalculated = false;
});
this.store.select('cl')
.pipe(takeUntil(this.unsub[0]))
.pipe(takeUntil(this.unSubs[1]))
.subscribe((rtlStore) => {
this.flgLoading = [true, true, true, true, true, true, true, true];
rtlStore.effectErrorsCl.forEach(effectsErr => {
if (effectsErr.action === 'FetchInfoCL') {
this.flgLoading[0] = 'error';
@ -84,8 +129,7 @@ export class CLHomeComponent implements OnInit, OnDestroy {
}
});
this.selNode = rtlStore.nodeSettings;
this.information = rtlStore.information
this.information = rtlStore.information;
if (this.flgLoading[0] !== 'error') {
this.flgLoading[0] = (undefined !== this.information.id) ? false : true;
}
@ -100,12 +144,10 @@ export class CLHomeComponent implements OnInit, OnDestroy {
this.flgLoading[2] = ('' !== this.totalBalance) ? false : true;
}
this.lrBalance = rtlStore.localRemoteBalance;
this.maxBalanceValue = (rtlStore.localRemoteBalance.localBalance > rtlStore.localRemoteBalance.remoteBalance) ? rtlStore.localRemoteBalance.localBalance : rtlStore.localRemoteBalance.remoteBalance;
this.lrBalances = [{'name': 'Local Balance', 'value': +rtlStore.localRemoteBalance.localBalance}, {'name': 'Remote Balance', 'value': +rtlStore.localRemoteBalance.remoteBalance}];
if (this.flgLoading[3] !== 'error') {
this.flgLoading[3] = (this.lrBalance.localBalance >= 0) ? false : true;
}
let local = (rtlStore.localRemoteBalance.localBalance) ? +rtlStore.localRemoteBalance.localBalance : 0;
let remote = (rtlStore.localRemoteBalance.remoteBalance) ? +rtlStore.localRemoteBalance.remoteBalance : 0;
let total = local + remote;
this.channelBalances = { localBalance: local, remoteBalance: remote, balancedness: (1 - Math.abs((local-remote)/total)).toFixed(3) };
this.feeRatesPerKB = rtlStore.feeRatesPerKB;
this.feeRatesPerKW = rtlStore.feeRatesPerKW;
@ -113,12 +155,74 @@ export class CLHomeComponent implements OnInit, OnDestroy {
this.flgLoading[4] = (undefined !== this.feeRatesPerKB && undefined !== this.feeRatesPerKW) ? false : true;
}
// this.balances.onchain = (+rtlStore.blockchainBalance.total_balance >= 0) ? +rtlStore.blockchainBalance.total_balance : 0;
// if (this.flgLoading[2] !== 'error') {
// this.flgLoading[2] = false;
// }
// this.balances.lightning = rtlStore.totalLocalBalance;
// if (this.flgLoading[5] !== 'error') {
// this.flgLoading[5] = false;
// }
// this.balances.total = this.balances.lightning + this.balances.onchain;
// this.balances = Object.assign({}, this.balances);
this.activeChannels = rtlStore.information.num_active_channels;
this.inactiveChannels = rtlStore.information.num_inactive_channels;
this.channelsStatus = {
active: { channels: rtlStore.information.num_active_channels, capacity: 0 },
inactive: { channels: rtlStore.information.num_inactive_channels, capacity: 0 },
pending: { channels: rtlStore.information.num_pending_channels, capacity: 0 }
};
this.totalInboundLiquidity = 0;
this.totalOutboundLiquidity = 0;
this.allChannels = rtlStore.allChannels.filter(channel => channel.connected === true);
this.allChannelsCapacity = JSON.parse(JSON.stringify(this.commonService.sortDescByKey(this.allChannels, 'balancedness')));
this.allInboundChannels = JSON.parse(JSON.stringify(this.commonService.sortDescByKey(this.allChannels.filter(channel => +channel.their_channel_reserve_satoshis > 0), 'remote_balance')));
this.allOutboundChannels = JSON.parse(JSON.stringify(this.commonService.sortDescByKey(this.allChannels.filter(channel => +channel.our_channel_reserve_satoshis > 0), 'local_balance')));
this.allChannels.forEach(channel => {
this.totalInboundLiquidity = this.totalInboundLiquidity + +channel.their_channel_reserve_satoshis;
this.totalOutboundLiquidity = this.totalOutboundLiquidity + +channel.our_channel_reserve_satoshis;
});
if (this.balances.lightning >= 0 && this.balances.onchain >= 0 && this.fees.feeCollected >= 0) {
this.flgChildInfoUpdated = true;
} else {
this.flgChildInfoUpdated = false;
}
this.logger.info(rtlStore);
});
this.actions$.pipe(takeUntil(this.unSubs[2]),
filter((action) => action.type === RTLActions.FETCH_FEES_CL || action.type === RTLActions.SET_FEES_CL))
.subscribe(action => {
if(action.type === RTLActions.FETCH_FEES_CL) {
this.flgChildInfoUpdated = false;
}
if(action.type === RTLActions.SET_FEES_CL) {
this.flgChildInfoUpdated = true;
}
});
}
onNavigateTo(link: string) {
this.router.navigateByUrl(link);
}
onsortChannelsBy() {
if (this.sortField === 'Balance Score') {
this.sortField = 'Capacity';
this.allChannelsCapacity = this.allChannels.sort(function (a, b) {
const x = +a.our_channel_reserve_satoshis + +a.their_channel_reserve_satoshis;
const y = +b.their_channel_reserve_satoshis + +b.their_channel_reserve_satoshis;
return ((x > y) ? -1 : ((x < y) ? 1 : 0));
});
} else {
this.sortField = 'Balance Score';
this.allChannelsCapacity = JSON.parse(JSON.stringify(this.commonService.sortDescByKey(this.allChannels, 'balancedness')));
}
}
ngOnDestroy() {
this.unsub.forEach(completeSub => {
this.unSubs.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});

@ -0,0 +1,24 @@
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch">
<div>
<h4 class="dashboard-info-title">Alias</h4>
<div class="overflow-wrap dashboard-info-value">
{{information.alias}}
<span *ngIf="!showColorFieldSeparately" class="dashboard-node-dot dot" [ngStyle]="{'backgroundColor': information.color}"></span>
</div>
</div>
<div *ngIf="showColorFieldSeparately">
<h4 class="dashboard-info-title">Color</h4>
<div class="overflow-wrap dashboard-info-value">
<span class="dashboard-node-square" [ngStyle]="{'backgroundColor': information.color}"></span>
{{information.color | uppercase}}
</div>
</div>
<div>
<h4 class="dashboard-info-title">Implementation</h4>
<div class="overflow-wrap dashboard-info-value">{{(information.lnImplementation || information.version) ? information.lnImplementation + ' v' + information.version : ''}}</div>
</div>
<div>
<h4 class="dashboard-info-title">Chain</h4>
<span class="overflow-wrap dashboard-info-value" *ngFor="let chain of chains">{{chain}}</span>
</div>
</div>

@ -1,20 +1,20 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CLPaymentsComponent } from './payments.component';
import { CLNodeInfoComponent } from './node-info.component';
describe('CLPaymentsComponent', () => {
let component: CLPaymentsComponent;
let fixture: ComponentFixture<CLPaymentsComponent>;
describe('CLNodeInfoComponent', () => {
let component: CLNodeInfoComponent;
let fixture: ComponentFixture<CLNodeInfoComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CLPaymentsComponent ]
declarations: [ CLNodeInfoComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CLPaymentsComponent);
fixture = TestBed.createComponent(CLNodeInfoComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

@ -0,0 +1,24 @@
import { Component, OnChanges, Input } from '@angular/core';
import { GetInfoCL } from '../../../shared/models/clModels';
import { CommonService } from '../../../shared/services/common.service';
@Component({
selector: 'rtl-cl-node-info',
templateUrl: './node-info.component.html',
styleUrls: ['./node-info.component.scss']
})
export class CLNodeInfoComponent implements OnChanges {
@Input() information: GetInfoCL;
@Input() showColorFieldSeparately: false;
public chains: Array<string> = [''];
constructor(private commonService: CommonService) { }
ngOnChanges() {
if(this.information && this.information.network) {
this.chains = [''];
this.chains.push(this.commonService.titleCase(this.information.network));
}
}
}

@ -1,101 +0,0 @@
<div fxLayout="column">
<div class="padding-gap">
<mat-card class="mat-card-original">
<mat-card-header>
<mat-card-subtitle>
<h2>Invoices</h2>
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<form fxLayout="column" fxLayout.gt-sm="row wrap" fxLayoutAlign.gt-sm="space-between center"
(ngSubmit)="addInvoiceForm.form.valid && onAddInvoice(addInvoiceForm)" #addInvoiceForm="ngForm">
<mat-form-field fxFlex="15" fxLayoutAlign="start end">
<input matInput [(ngModel)]="label" placeholder="Label" tabindex="1" name="label" required>
</mat-form-field>
<mat-form-field fxFlex="15" fxLayoutAlign="start end">
<input matInput [(ngModel)]="description" placeholder="Description" tabindex="2" name="description">
</mat-form-field>
<mat-form-field fxFlex="15" fxLayoutAlign="start end">
<input matInput [(ngModel)]="amount" placeholder="Amount (mSat)" type="number" step="100" min="1" tabindex="3"
name="amount" required>
</mat-form-field>
<mat-form-field fxFlex="10" fxLayoutAlign="start end">
<input matInput [(ngModel)]="expiry" placeholder="Expiry (Sec)" type="number" step="100" min="1"
tabindex="4" name="expiry">
</mat-form-field>
<div fxFlex="10" tabindex="5" fxLayoutAlign="start center" class="chkbox-private">
<mat-checkbox [(ngModel)]="private" matTooltip="Include routing hints for private channels"
[matTooltipPosition]="'above'" name="private">Private</mat-checkbox>
</div>
<button fxFlex="10" fxLayoutAlign="center center" mat-raised-button color="primary" type="submit"
tabindex="5" [disabled]="!addInvoiceForm.valid">Add</button>
<button fxFlex="10" fxLayoutAlign="center center" mat-stroked-button color="primary" tabindex="6" type="reset"
(click)="resetData()">Clear</button>
<button fxFlex="10" fxLayoutAlign="center center" mat-raised-button color="warn" tabindex="7" type="button"
(click)="onDeleteExpiredInvoices()">Delete Expired</button>
</form>
</mat-card-content>
</mat-card>
</div>
<div class="padding-gap">
<mat-card class="mat-card-original">
<mat-card-content class="table-card-content">
<div fxLayout="column" fxLayout.gt-xs="row" fxLayoutAlign.gt-xs="start center" fxLayoutAlign="start stretch">
<mat-form-field fxFlex="30">
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
</mat-form-field>
</div>
<div perfectScrollbar class="table-container">
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
<table mat-table #table [dataSource]="invoices" matSort
[ngClass]="{'overflow-auto error-border': flgLoading[0]==='error','overflow-auto': true}">
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Status </th>
<td mat-cell *matCellDef="let invoice">
<span *ngIf="invoice.status === 'paid'"><i class="material-icons primary">done_all</i></span>
<span *ngIf="invoice.status !== 'paid'"><i class="material-icons accent">done</i></span>
</td>
</ng-container>
<ng-container matColumnDef="expires_at_str">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Expiry Date </th>
<td mat-cell *matCellDef="let invoice">{{invoice.expires_at_str}}</td>
</ng-container>
<ng-container matColumnDef="paid_at_str">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Paid Date </th>
<td mat-cell *matCellDef="let invoice">{{invoice.paid_at_str}}</td>
</ng-container>
<ng-container matColumnDef="label">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Label </th>
<td mat-cell *matCellDef="let invoice">{{invoice.label}}</td>
</ng-container>
<ng-container matColumnDef="pay_index">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Pay Index </th>
<td mat-cell *matCellDef="let invoice"><span fxLayoutAlign="end center"> {{invoice?.pay_index | number}}
</span></td>
</ng-container>
<ng-container matColumnDef="msatoshi">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Amount </th>
<td mat-cell *matCellDef="let invoice"><span fxLayoutAlign="end center"> {{invoice.msatoshi | number}}
</span></td>
</ng-container>
<ng-container matColumnDef="msatoshi_received">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Received mSatoshi </th>
<td mat-cell *matCellDef="let invoice"><span fxLayoutAlign="end center">
{{invoice.msatoshi_received | number}} </span></td>
</ng-container>
<ng-container matColumnDef="description">
<th mat-header-cell class="pl-4" *matHeaderCellDef mat-sort-header> Description </th>
<td mat-cell class="pl-4" *matCellDef="let invoice">{{invoice.description}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: flgSticky;"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"
[@newlyAddedRowAnimation]="(row.label == newlyAddedLabel && row.msatoshi == newlyAddedAmount && flgAnimate) ? 'added' : 'notAdded'"
(click)="onInvoiceClick(row, $event)" class="row-invoices"
[ngClass]="{'settled': row.status === 'paid', 'unsettled': row.status !== 'paid'}"></tr>
</table>
<!-- <mat-paginator [length]="totalInvoices" [pageSize]="pageSize" [pageSizeOptions]="pageSizeOptions" (page)="onPageChange($event)"></mat-paginator> -->
</div>
</mat-card-content>
</mat-card>
</div>
</div>

@ -1,16 +0,0 @@
.mat-column-value {
padding-right: 1rem;
}
.mat-column-settled {
padding-left: 1rem;
}
.chkbox-private:focus {
outline: none !important;
}
table {
width:100%;
border-collapse: collapse;
}

@ -1,173 +0,0 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { MatTableDataSource, MatSort } from '@angular/material';
import { SelNodeChild } from '../../shared/models/RTLconfig';
import { GetInfoCL, InvoiceCL } from '../../shared/models/clModels';
import { PAGE_SIZE, PAGE_SIZE_OPTIONS, AlertTypeEnum, DataTypeEnum } from '../../shared/services/consts-enums-functions';
import { LoggerService } from '../../shared/services/logger.service';
import { newlyAddedRowAnimation } from '../../shared/animation/row-animation';
import { RTLEffects } from '../../store/rtl.effects';
import * as RTLActions from '../../store/rtl.actions';
import * as fromRTLReducer from '../../store/rtl.reducers';
@Component({
selector: 'rtl-cl-invoices',
templateUrl: './invoices.component.html',
styleUrls: ['./invoices.component.scss'],
animations: [newlyAddedRowAnimation]
})
export class CLInvoicesComponent implements OnInit, OnDestroy {
@ViewChild(MatSort, { static: true }) sort: MatSort;
public selNode: SelNodeChild = {};
public newlyAddedLabel = '';
public newlyAddedAmount = 0;
public flgAnimate = true;
public label = '';
public expiry: number;
public amount: number;
public description = '';
public displayedColumns = [];
public invoicePaymentReq = '';
public invoices: any;
public information: GetInfoCL = {};
public flgLoading: Array<Boolean | 'error'> = [true];
public flgSticky = false;
public private = false;
public totalInvoices = 100;
public pageSize = PAGE_SIZE;
public pageSizeOptions = PAGE_SIZE_OPTIONS;
private firstOffset = -1;
private lastOffset = -1;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private rtlEffects: RTLEffects) {
switch (true) {
case (window.innerWidth <= 415):
this.displayedColumns = ['status', 'expires_at_str', 'label', 'msatoshi'];
break;
case (window.innerWidth > 415 && window.innerWidth <= 730):
this.displayedColumns = ['status', 'expires_at_str', 'paid_at_str', 'label', 'msatoshi', 'msatoshi_received'];
break;
case (window.innerWidth > 730 && window.innerWidth <= 1024):
this.displayedColumns = ['status', 'expires_at_str', 'paid_at_str', 'label', 'pay_index', 'msatoshi', 'msatoshi_received', 'description'];
break;
case (window.innerWidth > 1024 && window.innerWidth <= 1280):
this.flgSticky = true;
this.displayedColumns = ['status', 'expires_at_str', 'paid_at_str', 'label', 'pay_index', 'msatoshi', 'msatoshi_received', 'description'];
break;
default:
this.flgSticky = true;
this.displayedColumns = ['status', 'expires_at_str', 'paid_at_str', 'label', 'pay_index', 'msatoshi', 'msatoshi_received', 'description'];
break;
}
}
ngOnInit() {
this.store.dispatch(new RTLActions.FetchInvoicesCL({num_max_invoices: 100, index_offset: 0, reversed: false}));
this.store.select('cl')
.pipe(takeUntil(this.unSubs[0]))
.subscribe((rtlStore) => {
rtlStore.effectErrorsCl.forEach(effectsErr => {
if (effectsErr.action === 'FetchInvoicesCL') {
this.flgLoading[0] = 'error';
}
});
this.selNode = rtlStore.nodeSettings;
this.information = rtlStore.information;
this.totalInvoices = rtlStore.totalInvoices;
this.firstOffset = +rtlStore.invoices.first_index_offset;
this.lastOffset = +rtlStore.invoices.last_index_offset;
this.logger.info(rtlStore);
this.loadInvoicesTable(rtlStore.invoices.invoices);
if (this.flgLoading[0] !== 'error') {
this.flgLoading[0] = (undefined !== rtlStore.invoices) ? false : true;
}
});
}
onAddInvoice(form: any) {
this.flgAnimate = true;
this.newlyAddedLabel = this.label;
this.newlyAddedAmount = this.amount;
this.store.dispatch(new RTLActions.OpenSpinner('Adding Invoice...'));
this.store.dispatch(new RTLActions.SaveNewInvoiceCL({
label: this.label, amount: this.amount, description: this.description, expiry: (this.expiry ? this.expiry : 3600), private: this.private
}));
this.resetData();
}
onDeleteExpiredInvoices() {
this.store.dispatch(new RTLActions.OpenConfirmation({
data: { type: AlertTypeEnum.CONFIRM, alertTitle: 'Confirm Delete Invoices', titleMessage: 'Delete Expired Invoices', noBtnText: 'Cancel', yesBtnText: 'Delete Invoices'
}}));
this.rtlEffects.closeConfirm
.pipe(takeUntil(this.unSubs[1]))
.subscribe(confirmRes => {
if (confirmRes) {
this.store.dispatch(new RTLActions.OpenSpinner('Deleting Invoices...'));
this.store.dispatch(new RTLActions.DeleteExpiredInvoiceCL());
}
});
}
onInvoiceClick(selRow: InvoiceCL, event: any) {
const selInvoice = this.invoices.data.filter(invoice => {
return invoice.bolt11 === selRow.bolt11;
})[0];
const reorderedInvoice = [
[{key: 'status', value: selInvoice.status, title: 'Status', width: 100, type: DataTypeEnum.NUMBER}]
// 'status', 'expires_at_str', 'paid_at_str', 'pay_index', 'label', 'bolt11', 'payment_hash', 'msatoshi', 'msatoshi_received', 'description'
];
this.store.dispatch(new RTLActions.OpenAlert({ data: {
type: AlertTypeEnum.INFORMATION,
alertTitle: 'Invoice Information',
message: reorderedInvoice
}}));
}
loadInvoicesTable(invoices) {
this.invoices = new MatTableDataSource<InvoiceCL>([...invoices]);
this.invoices.sort = this.sort;
setTimeout(() => { this.flgAnimate = false; }, 5000);
this.logger.info(this.invoices);
}
resetData() {
this.label = '';
this.description = '';
this.amount = 0;
this.private = false;
this.expiry = undefined;
}
applyFilter(selFilter: string) {
this.invoices.filter = selFilter;
}
// onPageChange(event: any) {
// let reversed = true;
// let index_offset = this.firstOffset;
// if (event.pageIndex < event.previousPageIndex) {
// reversed = false;
// index_offset = this.lastOffset;
// }
// if (event.pageIndex === event.previousPageIndex) {
// reversed = true;
// index_offset = 0;
// }
// this.store.dispatch(new RTLActions.FetchInvoices({num_max_invoices: event.pageSize, index_offset: index_offset, reversed: reversed}));
// }
ngOnDestroy() {
this.unSubs.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

@ -1,12 +0,0 @@
.mat-list-base .mat-list-item, .mat-list-base .mat-list-option {
height: 38px !important;
}
.word-break-all {
word-break: break-all !important;
padding-top: 8px;
}
.list-tall {
height: 50px !important;
}

@ -1,187 +1,167 @@
<div fxLayout="column" fxLayoutAlign="space-between start" fxLayout.gt-sm="row wrap" class="mt-2">
<div fxFlex="48">
<mat-card class="custom-card mat-elevation-z12">
<mat-card-header class="bg-primary" fxLayoutAlign="center center">
<mat-card-title class="m-0 pt-2">
<h5 *ngIf="!node1_match">Node 1</h5>
<h5 *ngIf="node1_match">Node 1 (Your Node)</h5>
</mat-card-title>
</mat-card-header>
<mat-card-content class="px-2" *ngIf="lookupResult.length > 0">
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Short Channel Id</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult[0].short_channel_id}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Active</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start" class="word-break">{{lookupResult[0].active}}
</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Last Update</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult[0].last_update_str}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Amount mSats</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult[0].amount_msat}}
</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Base Fee mSats</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult[0].base_fee_millisatoshi | number}}
</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Channel Flags</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult[0].channel_flags | number}}
</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Delay</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult[0].delay | number}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start" class="list-tall">
<mat-list-item fxFlex="50" fxLayoutAlign="start start" class="word-break-all">Destination</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start" class="word-break-all">{{lookupResult[0].destination}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Fee Per Millionth</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult[0].fee_per_millionth | number}}
</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Htlc Max mSat</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult[0].htlc_maximum_msat}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Htlc Min mSat</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult[0].htlc_minimum_msat}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Message Flags</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult[0].message_flags | number}}
</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Public</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult[0].public}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Satoshis</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult[0].satoshis | number}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start" class="list-tall">
<mat-list-item fxFlex="50" fxLayoutAlign="start start" class="word-break-all">Source</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start" class="word-break-all">{{lookupResult[0].source}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
</mat-card-content>
</mat-card>
<div fxLayout="column" *ngIf="lookupResult" class="mt-1">
<mat-divider></mat-divider>
<div fxLayout="column" fxLayoutAlign="space-between stretch" fxLayout.gt-sm="row">
<div fxLayout="column" fxFlex="49" fxLayoutAlign="start start" class="mt-1 bordered-box padding-gap-large">
<div fxLayout="column">
<h3 class="page-title font-bold-500" *ngIf="!node1_match">Node 1</h3>
<h3 class="page-title font-bold-500" *ngIf="node1_match">Node 1 (Your Node)</h3>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="20" class="my-1">
<h4 class="font-bold-500">Short Channel Id</h4>
<span class="foreground-secondary-text">{{lookupResult[0].short_channel_id}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Active</h4>
<span class="foreground-secondary-text">{{lookupResult[0].active}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Last Update</h4>
<span class="foreground-secondary-text">{{lookupResult[0].last_update_str}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Amount (mSats)</h4>
<span class="foreground-secondary-text">{{lookupResult[0].amount_msat}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Base Fee mSats</h4>
<span class="foreground-secondary-text">{{lookupResult[0].base_fee_millisatoshi | number}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Channel Flags</h4>
<span class="foreground-secondary-text">{{lookupResult[0].channel_flags | number}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Delay</h4>
<span class="foreground-secondary-text">{{lookupResult[0].delay | number}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Destination</h4>
<span class="foreground-secondary-text">{{lookupResult[0].destination}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Fee/Millionth</h4>
<span class="foreground-secondary-text">{{lookupResult[0].fee_per_millionth | number}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Max Htlc (mSat)</h4>
<span class="foreground-secondary-text">{{lookupResult[0].htlc_maximum_msat}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Min Htlc (mSat)</h4>
<span class="foreground-secondary-text">{{lookupResult[0].htlc_minimum_msat}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Message Flags</h4>
<span class="foreground-secondary-text">{{lookupResult[0].message_flags | number}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Public</h4>
<span class="foreground-secondary-text">{{lookupResult[0].public}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Satoshis</h4>
<span class="foreground-secondary-text">{{lookupResult[0].satoshis | number}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Source</h4>
<span class="foreground-secondary-text">{{lookupResult[0].source}}</span>
</div>
</div>
<div fxLayout="column" fxFlex="49" fxLayoutAlign="start start" class="mt-1 bordered-box padding-gap-large">
<div>
<h3 class="page-title font-bold-500" *ngIf="!node2_match">Node 2</h3>
<h3 class="page-title font-bold-500" *ngIf="node2_match">Node 2 (Your Node)</h3>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="20" class="my-1">
<h4 class="font-bold-500">Short Channel Id</h4>
<span class="foreground-secondary-text">{{lookupResult[1].short_channel_id}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Active</h4>
<span class="foreground-secondary-text">{{lookupResult[1].active}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Last Update</h4>
<span class="foreground-secondary-text">{{lookupResult[1].last_update_str}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Amount (mSats)</h4>
<span class="foreground-secondary-text">{{lookupResult[1].amount_msat}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Base Fee mSats</h4>
<span class="foreground-secondary-text">{{lookupResult[1].base_fee_millisatoshi | number}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Channel Flags</h4>
<span class="foreground-secondary-text">{{lookupResult[1].channel_flags | number}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Delay</h4>
<span class="foreground-secondary-text">{{lookupResult[1].delay | number}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Destination</h4>
<span class="foreground-secondary-text">{{lookupResult[1].destination}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Fee/Millionth</h4>
<span class="foreground-secondary-text">{{lookupResult[1].fee_per_millionth | number}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Max Htlc (mSat)</h4>
<span class="foreground-secondary-text">{{lookupResult[1].htlc_maximum_msat}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Min Htlc (mSat)</h4>
<span class="foreground-secondary-text">{{lookupResult[1].htlc_minimum_msat}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Message Flags</h4>
<span class="foreground-secondary-text">{{lookupResult[1].message_flags | number}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Public</h4>
<span class="foreground-secondary-text">{{lookupResult[1].public}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Satoshis</h4>
<span class="foreground-secondary-text">{{lookupResult[1].satoshis | number}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<h4 class="font-bold-500">Source</h4>
<span class="foreground-secondary-text">{{lookupResult[1].source}}</span>
</div>
</div>
</div>
<div fxFlex="48">
<mat-card class="custom-card mat-elevation-z12">
<mat-card-header class="bg-primary" fxLayoutAlign="center center">
<mat-card-title class="m-0 pt-2">
<h5 *ngIf="!node2_match">Node 2</h5>
<h5 *ngIf="node2_match">Node 2 (Your Node)</h5>
</mat-card-title>
</mat-card-header>
<mat-card-content class="px-2" *ngIf="lookupResult.length > 1">
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Short Channel Id</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult[1].short_channel_id}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Active</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start" class="word-break">{{lookupResult[1].active}}
</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Last Update</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult[1].last_update_str}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Amount mSats</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult[1].amount_msat}}
</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Base Fee mSats</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult[1].base_fee_millisatoshi | number}}
</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Channel Flags</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult[1].channel_flags | number}}
</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Delay</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult[1].delay | number}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start" class="list-tall">
<mat-list-item fxFlex="50" fxLayoutAlign="start start" class="word-break-all">Destination</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start" class="word-break-all">{{lookupResult[1].destination}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Fee Per Millionth</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult[1].fee_per_millionth | number}}
</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Htlc Max mSat</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult[1].htlc_maximum_msat}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Htlc Min mSat</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult[1].htlc_minimum_msat}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Message Flags</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult[1].message_flags | number}}
</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Public</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult[1].public}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Satoshis</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult[1].satoshis | number}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start" class="list-tall">
<mat-list-item fxFlex="50" fxLayoutAlign="start start" class="word-break-all">Source</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start" class="word-break-all">{{lookupResult[1].source}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
</mat-card-content>
</mat-card>
</div>
</div>

@ -0,0 +1,3 @@
.mat-list-base .mat-list-item, .mat-list-base .mat-list-option {
height: 38px !important;
}

@ -9,7 +9,7 @@ import * as fromRTLReducer from '../../../store/rtl.reducers';
@Component({
selector: 'rtl-cl-channel-lookup',
templateUrl: './channel-lookup.component.html',
styleUrls: ['./channel-lookup.component.css']
styleUrls: ['./channel-lookup.component.scss']
})
export class CLChannelLookupComponent implements OnInit {
@Input() lookupResult: ChannelEdgeCL[];

@ -1,46 +1,38 @@
<div fxLayout="column">
<div class="padding-gap">
<mat-card class="mat-card-original">
<mat-card-header>
<mat-card-subtitle>
<h2>Lookups</h2>
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<form fxLayout="column" fxLayout.gt-sm="row wrap" #form="ngForm">
<mat-form-field fxFlex="20" fxLayoutAlign="start end">
<mat-select [(ngModel)]="selectedField" placeholder="Lookup Field" (selectionChange)="onSelectChange($event)" tabindex="1" required name="lookupField">
<mat-option *ngFor="let lookupField of lookupFields" [value]="lookupField">
<div fxLayout="row" fxLayoutAlign="start end" class="padding-gap-x page-title-container">
<fa-icon [icon]="faSearch" class="page-title-img mr-1"></fa-icon>
<span class="page-title">Graph Lookups</span>
</div>
<div fxLayout="row" fxFlex="100" fxLayoutAlign="start start" class="padding-gap">
<mat-card fxLayout="row" fxFlex="100" fxLayoutAlign="start start">
<mat-card-content fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="card-content-gap mt-1">
<form fxFlex="100" fxLayout="column" fxLayout.gt-sm="row wrap" fxLayoutAlign.gt-sm="space-between center" fxLayoutAlign="start space-between" class="w-100" #form="ngForm">
<div fxFlex="35" fxFlex.gt-md="25" fxLayoutAlign="start end">
<mat-radio-group color="primary" [(ngModel)]="selectedFieldId" (change)="onSelectChange($event)" tabindex="1" name="lookupField">
<mat-radio-button *ngFor="let lookupField of lookupFields" [value]="lookupField.id" [checked]="selectedFieldId === lookupField.id" class="mr-4">
{{lookupField.name}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex="50" fxLayoutAlign="start end">
<input matInput name="lookupKey" [placeholder]="selectedField?.placeholder || 'Lookup Key'" (change)="clearLookupValue()" [(ngModel)]="lookupKey" tabindex="2" required>
</mat-radio-button>
</mat-radio-group>
</div>
<mat-form-field fxFlex="65" fxFlex.gt-md="75" fxLayoutAlign="start end" [ngClass]="{'mt-2': screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM}">
<input matInput name="lookupKey" [placeholder]="lookupFields[selectedFieldId]?.placeholder || 'Lookup Key'" (change)="clearLookupValue()" [(ngModel)]="lookupKey" tabindex="2" required #key>
<mat-error *ngIf="!lookupKey">{{lookupFields[selectedFieldId]?.placeholder}} is required.</mat-error>
</mat-form-field>
<div fxFlex="12" fxLayoutAlign="start start">
<button fxFlex="90" fxLayoutAlign="center center" mat-raised-button color="primary" tabindex="3" type="submit" (click)="onLookup()" [disabled]="!form.valid">Lookup</button>
<div fxFlex="30" fxLayoutAlign="space-between stretch" class="mt-2">
<button fxFlex="48" fxLayoutAlign="center center" mat-stroked-button color="primary" tabindex="3" type="button" (click)="resetData()">Clear</button>
<button fxFlex="48" fxLayoutAlign="center center" mat-raised-button color="primary" tabindex="4" type="submit" (click)="onLookup()">Lookup</button>
</div>
<div fxFlex="12" fxLayoutAlign="start start">
<button fxFlex="90" fxLayoutAlign="center center" mat-stroked-button color="primary" tabindex="4" type="reset" (click)="resetData()">Clear</button>
</form>
<div fxFlex="100" fxLayout="column" fxLayout.gt-sm="row wrap" fxLayoutAlign.gt-sm="space-between center" fxLayoutAlign="start stretch" *ngIf="lookupValue && flgSetLookupValue" class="w-100 mt-2">
<div fxLayout="row" fxFlex="100" fxLayoutAlign="start center">
<span class="page-title font-bold-500">{{lookupFields[selectedFieldId].name}} Details</span>
</div>
</form>
</mat-card-content>
</mat-card>
</div>
<div class="padding-gap" *ngIf="lookupValue && flgSetLookupValue">
<mat-card class="mat-card-original" [ngClass]="{'error-border': flgLoading[0]==='error'}">
<mat-card-header>
<mat-card-subtitle>
<h2>{{selectedField.name}} Details</h2>
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<div [ngSwitch]="selectedField.id">
<span *ngSwitchCase="0"><rtl-cl-node-lookup [lookupResult]="lookupValue[0]"></rtl-cl-node-lookup></span>
<span *ngSwitchCase="1"><rtl-cl-channel-lookup [lookupResult]="lookupValue"></rtl-cl-channel-lookup></span>
<span *ngSwitchDefault><h3>Error! Unable to find details!</h3></span>
</div>
<div [ngSwitch]="selectedFieldId" fxLayout="row" fxFlex="100" fxLayoutAlign="start center">
<span fxFlex="100" *ngSwitchCase="0"><rtl-cl-node-lookup [lookupResult]="lookupValue"></rtl-cl-node-lookup></span>
<span fxFlex="100" *ngSwitchCase="1"><rtl-cl-channel-lookup [lookupResult]="lookupValue"></rtl-cl-channel-lookup></span>
<span fxFlex="100" *ngSwitchDefault><h3>Error! Unable to find details!</h3></span>
</div>
</div>
</mat-card-content>
</mat-card>
</div>

@ -1,13 +1,16 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil, filter } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { Actions } from '@ngrx/effects';
import { faSearch } from '@fortawesome/free-solid-svg-icons';
import { LoggerService } from '../../shared/services/logger.service';
import * as RTLActions from '../../store/rtl.actions';
import * as fromRTLReducer from '../../store/rtl.reducers';
import { ScreenSizeEnum } from '../../shared/services/consts-enums-functions';
import { CommonService } from '../../shared/services/common.service';
@Component({
selector: 'rtl-cl-lookups',
@ -15,47 +18,55 @@ import * as fromRTLReducer from '../../store/rtl.reducers';
styleUrls: ['./lookups.component.scss']
})
export class CLLookupsComponent implements OnInit, OnDestroy {
@ViewChild('form', { static: false }) form: any;
public lookupKey = '';
public lookupValue = {};
public flgSetLookupValue = false;
public temp: any;
public messageObj = [];
public selectedField = { id: '0', name: 'Node', placeholder: 'ID'};
public selectedFieldId = 0;
public lookupFields = [
{ id: '0', name: 'Node', placeholder: 'ID'},
{ id: '1', name: 'Channel', placeholder: 'Short Channel ID'}
{ id: 0, name: 'Node', placeholder: 'Pubkey'},
{ id: 1, name: 'Channel', placeholder: 'Channel ID'}
];
public flgLoading: Array<Boolean | 'error'> = [true];
public faSearch = faSearch;
public screenSize = '';
public screenSizeEnum = ScreenSizeEnum;
private unSubs: Array<Subject<void>> = [new Subject()];
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private actions$: Actions) {}
constructor(private logger: LoggerService, private commonService: CommonService, private store: Store<fromRTLReducer.RTLState>, private actions$: Actions) {
this.screenSize = this.commonService.getScreenSize();
}
ngOnInit() {
this.actions$
.pipe(
takeUntil(this.unSubs[0]),
filter((action) => (action.type === RTLActions.SET_LOOKUP_CL || action.type === RTLActions.EFFECT_ERROR_CL))
).subscribe((resLookup: RTLActions.SetLookupCL) => {
if (resLookup.payload.action === 'LookupCL') {
this.flgLoading[0] = 'error';
} else {
).subscribe((resLookup: RTLActions.SetLookupCL | RTLActions.EffectErrorCl) => {
if(resLookup.type === RTLActions.SET_LOOKUP_CL) {
this.flgLoading[0] = true;
this.lookupValue = JSON.parse(JSON.stringify(resLookup.payload));
this.flgSetLookupValue = true;
this.logger.info(this.lookupValue);
}
if (resLookup.type === RTLActions.EFFECT_ERROR_CL && resLookup.payload.action === 'LookupCL') {
this.flgLoading[0] = 'error';
}
});
}
onLookup() {
if(!this.lookupKey) { return true; }
this.flgSetLookupValue = false;
this.lookupValue = {};
this.store.dispatch(new RTLActions.OpenSpinner('Searching ' + this.selectedField.name + '...'));
switch (this.selectedField.id) {
case '0':
this.store.dispatch(new RTLActions.OpenSpinner('Searching ' + this.lookupFields[this.selectedFieldId].name + '...'));
switch (this.selectedFieldId) {
case 0:
this.store.dispatch(new RTLActions.PeerLookupCL(this.lookupKey.trim()));
break;
case '1':
case 1:
this.store.dispatch(new RTLActions.ChannelLookupCL(this.lookupKey.trim()));
break;
default:
@ -64,15 +75,15 @@ export class CLLookupsComponent implements OnInit, OnDestroy {
}
onSelectChange(event: any) {
this.flgSetLookupValue = false;
this.lookupKey = '';
this.lookupValue = {};
this.form.resetForm();
this.selectedFieldId = event.value;
}
resetData() {
this.form.resetForm();
this.flgSetLookupValue = false;
this.selectedFieldId = 0;
this.lookupKey = '';
this.selectedField = { id: '0', name: 'Node', placeholder: 'ID'};
this.lookupValue = {};
this.flgLoading.forEach((flg, i) => {
this.flgLoading[i] = true;

@ -1,12 +0,0 @@
.mat-table {
width:99%;
}
.mat-list-base .mat-list-item, .mat-list-base .mat-list-option {
height: 38px !important;
}
.mat-column-type, .mat-column-port {
flex: 0 0 15%;
min-width: 100px;
}

@ -1,67 +1,59 @@
<div fxLayout="column">
<div class="padding-gap">
<mat-card>
<mat-card-content *ngIf="lookupResult">
<div fxLayout="column">
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">ID</mat-list-item>
<mat-list-item fxFlex="40" fxLayoutAlign="start start">{{lookupResult?.nodeid}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Alias</mat-list-item>
<mat-list-item fxFlex="40" fxLayoutAlign="start start">{{lookupResult?.alias}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Last Timestamp</mat-list-item>
<mat-list-item fxFlex="40" fxLayoutAlign="start start">{{lookupResult?.last_timestamp_str}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Color</mat-list-item>
<mat-list-item fxFlex="40" fxLayoutAlign="start start"><span
[ngStyle]="{'background-color': '#' + lookupResult.color}">{{lookupResult?.color}}</span></mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Global Features</mat-list-item>
<mat-list-item fxFlex="40" fxLayoutAlign="start start">{{lookupResult?.globalfeatures}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Global_Features</mat-list-item>
<mat-list-item fxFlex="40" fxLayoutAlign="start start">{{lookupResult?.global_features}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayout="column" fxLayoutAlign="start start">
<mat-divider></mat-divider>
<mat-list-item fxFlex="100" fxLayoutAlign="start start">Addresses</mat-list-item>
<mat-table [dataSource]="lookupResult?.addresses" matSort class="mat-elevation-z8 overflow-auto">
<ng-container matColumnDef="type">
<mat-header-cell *matHeaderCellDef mat-sort-header>Type</mat-header-cell>
<mat-cell *matCellDef="let address">
<div>{{address?.type}}</div>
</mat-cell>
</ng-container>
<ng-container matColumnDef="address">
<mat-header-cell *matHeaderCellDef mat-sort-header>Address</mat-header-cell>
<mat-cell *matCellDef="let address">
<div>{{address?.address}}</div>
</mat-cell>
</ng-container>
<ng-container matColumnDef="port">
<mat-header-cell *matHeaderCellDef mat-sort-header>Port</mat-header-cell>
<mat-cell *matCellDef="let address">
<div>{{address?.port}}</div>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns;"></mat-header-row>
<mat-row fxLayoutAlign="stretch stretch" *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>
</mat-list>
</div>
</mat-card-content>
</mat-card>
<div fxLayout="column" *ngIf="lookupResult" class="mt-1">
<mat-divider></mat-divider>
<div fxLayout="column" fxFlex="100" fxLayoutAlign="end start" class="my-1">
<h4 fxLayoutAlign="start" class="font-bold-500">Pub Key</h4>
<span class="foreground-secondary-text w-100">{{lookupResult.nodeid}}</span>
<mat-divider class="my-1"></mat-divider>
</div>
</div>
<div fxLayout="row">
<div fxLayout="column" fxFlex="50" fxLayoutAlign="end start" class="my-1">
<h4 fxLayoutAlign="start" class="font-bold-500">Alias</h4>
<span class="foreground-secondary-text">{{lookupResult.alias}}<span class="ml-2" [ngStyle]="{'background-color': lookupResult.color}">{{lookupResult.color}}</span></span>
<mat-divider class="my-1"></mat-divider>
</div>
<div fxLayout="column" fxFlex="50" fxLayoutAlign="end start" class="my-1">
<h4 fxLayoutAlign="start" class="font-bold-500">Last Update</h4>
<span class="foreground-secondary-text">{{lookupResult.last_timestamp_str}}</span>
<mat-divider class="my-1"></mat-divider>
</div>
</div>
<div fxLayout="row">
<div fxLayout="column" fxFlex="50" fxLayoutAlign="end start" class="my-1">
<h4 fxLayoutAlign="start" class="font-bold-500">Global Features</h4>
<span class="foreground-secondary-text">{{lookupResult.globalfeatures}}</span>
<mat-divider class="my-1"></mat-divider>
</div>
<div fxLayout="column" fxFlex="50" fxLayoutAlign="end start" class="my-1">
<h4 fxLayoutAlign="start" class="font-bold-500">Global_Features</h4>
<span class="foreground-secondary-text">{{lookupResult.global_features}}</span>
<mat-divider class="my-1"></mat-divider>
</div>
</div>
<div fxLayout="column" fxFlex="100" fxLayoutAlign="start" class="my-1">
<h4 fxFlex="100" fxLayoutAlign="start" class="font-bold-500">Addresses</h4>
<div perfectScrollbar class="table-container mt-2" fxFlex="100">
<table mat-table [dataSource]="lookupResult.addresses" matSort class="overflow-auto">
<ng-container matColumnDef="type">
<mat-header-cell *matHeaderCellDef mat-sort-header>Type</mat-header-cell>
<mat-cell *matCellDef="let address">
<div>{{address?.type}}</div>
</mat-cell>
</ng-container>
<ng-container matColumnDef="address">
<mat-header-cell *matHeaderCellDef mat-sort-header>Address</mat-header-cell>
<mat-cell *matCellDef="let address">
<div>{{address?.address}}</div>
</mat-cell>
</ng-container>
<ng-container matColumnDef="port">
<mat-header-cell *matHeaderCellDef mat-sort-header>Port</mat-header-cell>
<mat-cell *matCellDef="let address">
<div>{{address?.port}}</div>
</mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns;"></mat-header-row>
<mat-row fxLayoutAlign="stretch stretch" *matRowDef="let row; columns: displayedColumns;"></mat-row>
</table>
</div>
</div>
</div>

@ -1,18 +1,17 @@
import { Component, OnInit, Input } from '@angular/core';
import { LoggerService } from '../../../shared/services/logger.service';
import { LookupNodeCL } from '../../../shared/models/clModels';
@Component({
selector: 'rtl-cl-node-lookup',
templateUrl: './node-lookup.component.html',
styleUrls: ['./node-lookup.component.css']
styleUrls: ['./node-lookup.component.scss']
})
export class CLNodeLookupComponent implements OnInit {
@Input() lookupResult: LookupNodeCL;
public displayedColumns = ['type', 'address', 'port'];
constructor(private logger: LoggerService) { }
constructor() { }
ngOnInit() {}

@ -0,0 +1,14 @@
<div fxLayout="column">
<div fxLayout="row" fxLayoutAlign="space-between end" fxLayoutAlign.gt-sm="start end">
<mat-form-field fxFlex="48" fxFlex.gt-md="25" fxLayoutAlign="start end" class="mr-2">
<mat-select [(ngModel)]="selectedAddressType" placeholder="Address Type" name="address_type" tabindex="1">
<mat-option *ngFor="let addressType of addressTypes" [value]="addressType">
{{addressType.addressTp}}
</mat-option>
</mat-select>
</mat-form-field>
<div class="mt-2" fxFlex="48" fxFlex.gt-md="25">
<button fxFlex="100" fxLayoutAlign="center center" mat-raised-button color="primary" (click)="onGenerateAddress()" tabindex="2" class="top-minus-15px">Generate Address</button>
</div>
</div>
</div>

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CLOnChainReceiveComponent } from './on-chain-receive.component';
describe('CLOnChainReceiveComponent', () => {
let component: CLOnChainReceiveComponent;
let fixture: ComponentFixture<CLOnChainReceiveComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CLOnChainReceiveComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CLOnChainReceiveComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,53 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { ADDRESS_TYPES } from '../../../shared/services/consts-enums-functions';
import { OnChainGeneratedAddressComponent } from '../../../shared/components/data-modal/on-chain-generated-address/on-chain-generated-address.component';
import { CLEffects } from '../../store/cl.effects';
import * as RTLActions from '../../../store/rtl.actions';
import * as fromRTLReducer from '../../../store/rtl.reducers';
@Component({
selector: 'rtl-cl-on-chain-receive',
templateUrl: './on-chain-receive.component.html',
styleUrls: ['./on-chain-receive.component.scss']
})
export class CLOnChainReceiveComponent implements OnInit, OnDestroy {
public addressTypes = ADDRESS_TYPES;
public selectedAddressType = ADDRESS_TYPES[0];
public newAddress = '';
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
constructor(private store: Store<fromRTLReducer.RTLState>, private lndEffects: CLEffects) {}
ngOnInit() {}
onGenerateAddress() {
this.store.dispatch(new RTLActions.OpenSpinner('Getting New Address...'));
this.store.dispatch(new RTLActions.GetNewAddressCL(this.selectedAddressType));
this.lndEffects.setNewAddressCL
.pipe(takeUntil(this.unSubs[0]))
.subscribe(newAddress => {
this.newAddress = newAddress;
this.store.dispatch(new RTLActions.OpenAlert({
width: '58%',
data: {
address: this.newAddress,
addressType: this.selectedAddressType.addressTp,
component: OnChainGeneratedAddressComponent
}
}));
});
}
ngOnDestroy() {
this.unSubs.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

@ -0,0 +1,35 @@
<form fxLayout="column" fxFlex="98" fxLayout.gt-sm="row wrap" fxLayoutAlign="start stretch" fxLayoutAlign.gt-sm="space-between start" class="padding-gap overflow-x-hidden">
<mat-form-field fxFlex.gt-sm="55">
<input matInput [(ngModel)]="transaction.address" placeholder="Bitcoin Address" tabindex="1" name="address" required #address="ngModel">
<mat-error *ngIf="!transaction.address">Bitcoin address is required.</mat-error>
</mat-form-field>
<mat-form-field fxFlex.gt-sm="30">
<input matInput [(ngModel)]="transaction.satoshis" placeholder="Amount" name="amount" type="number" step="100" min="0" tabindex="2" required #amount="ngModel">
<span matSuffix> {{selAmountUnit}} </span>
<mat-error *ngIf="!transaction.satoshis">Amount is required.</mat-error>
</mat-form-field>
<mat-form-field fxFlex.gt-sm="10" fxLayoutAlign="start end">
<mat-select [value]="selAmountUnit" tabindex="3" required name="amountUnit" (selectionChange)="onAmountUnitChange($event)">
<mat-option *ngFor="let amountUnit of amountUnits" [value]="amountUnit">{{amountUnit}}</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex="10" fxLayoutAlign="start end">
<mat-select tabindex="6" placeholder="Fee Rate" [(value)]="transaction.feeRate">
<mat-option *ngFor="let feeRateType of feeRateTypes" [value]="feeRateType.feeRateId">
{{feeRateType.feeRateType}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-checkbox fxFlex="20" fxFlex.lt-lg="35" tabindex="7" [(ngModel)]="flgMinConf" name="flgMinConf">
<mat-form-field fxFlex="100">
<input matInput [(ngModel)]="transaction.minconf" placeholder="Min Confirmation Blocks" type="number"
name="blocks" step="1" min="0" tabindex="8" #blocks="ngModel" [required]="flgMinConf"
[disabled]="!flgMinConf">
</mat-form-field>
</mat-checkbox>
<div fxLayout="column" fxFlex="100" fxFlex.gt-sm="40" fxLayout.gt-sm="row wrap" fxLayoutAlign="start stretch" fxLayoutAlign.gt-sm="space-between start"></div>
<div fxLayout="row" fxFlex="100" fxFlex.gt-sm="30" fxLayoutAlign="space-between stretch" class="mt-2">
<button fxFlex="48" fxLayoutAlign="center center" mat-stroked-button color="primary" tabindex="7" type="reset" (click)="resetData()">Clear Fields</button>
<button fxFlex="48" fxLayoutAlign="center center" mat-raised-button color="primary" type="submit" tabindex="8" (click)="onSendFunds()">Send Funds</button>
</div>
</form>

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CLOnChainSendComponent } from './on-chain-send.component';
describe('CLOnChainSendComponent', () => {
let component: CLOnChainSendComponent;
let fixture: ComponentFixture<CLOnChainSendComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CLOnChainSendComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CLOnChainSendComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,132 @@
import { Component, Input, OnInit, OnDestroy } from '@angular/core';
import { DecimalPipe } from '@angular/common';
import { Subject } from 'rxjs';
import { takeUntil, take } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { MatSnackBar } from '@angular/material/snack-bar';
import { SelNodeChild, GetInfoRoot } from '../../../shared/models/RTLconfig';
import { GetInfoCL, BalanceCL, OnChainCL } from '../../../shared/models/clModels';
import { CURRENCY_UNITS, CurrencyUnitEnum, CURRENCY_UNIT_FORMATS, AlertTypeEnum, DataTypeEnum, ADDRESS_TYPES, FEE_RATE_TYPES } from '../../../shared/services/consts-enums-functions';
import { RTLConfiguration } from '../../../shared/models/RTLconfig';
import { CommonService } from '../../../shared/services/common.service';
import { LoggerService } from '../../../shared/services/logger.service';
import * as sha256 from 'sha256';
import { RTLEffects } from '../../../store/rtl.effects';
import * as RTLActions from '../../../store/rtl.actions';
import * as fromRTLReducer from '../../../store/rtl.reducers';
import { MessageDataField } from '../../../shared/models/alertData';
@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 selNode: SelNodeChild = {};
public appConfig: RTLConfiguration;
public nodeData: GetInfoRoot;
public addressTypes = [];
public flgLoadingWallet: Boolean | 'error' = true;
public selectedAddress = ADDRESS_TYPES[1];
public blockchainBalance: BalanceCL = {};
public information: GetInfoCL = {};
public newAddress = '';
public transaction: OnChainCL = {};
public feeRateTypes = FEE_RATE_TYPES;
public flgMinConf = false;
public amountUnits = CURRENCY_UNITS;
public selAmountUnit = CURRENCY_UNITS[0];
public currConvertorRate = {};
public unitConversionValue = 0;
public currencyUnitFormats = CURRENCY_UNIT_FORMATS;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private rtlEffects: RTLEffects, private commonService: CommonService, private decimalPipe: DecimalPipe, private snackBar: MatSnackBar) {}
ngOnInit() {
this.store.select('root')
.pipe(takeUntil(this.unSubs[0]))
.subscribe((rootStore) => {
this.amountUnits = rootStore.selNode.settings.currencyUnits;
this.appConfig = rootStore.appConfig;
this.nodeData = rootStore.nodeData;
this.logger.info(rootStore);
});
}
onSendFunds() {
if(this.invalidValues) { return true; }
if(this.transaction.satoshis && this.selAmountUnit !== CurrencyUnitEnum.SATS) {
this.commonService.convertCurrency(this.transaction.satoshis, this.selAmountUnit === this.amountUnits[2] ? CurrencyUnitEnum.OTHER : this.selAmountUnit, this.amountUnits[2])
.pipe(takeUntil(this.unSubs[1]))
.subscribe(data => {
this.transaction.satoshis = parseInt(data[CurrencyUnitEnum.SATS]);
this.confirmSend();
});
} else {
this.confirmSend();
}
this.rtlEffects.closeConfirm
.pipe(takeUntil(this.unSubs[2]))
.subscribe(pwdConfirmRes => {
if (pwdConfirmRes) {
this.dispatchToSendFunds();
}
});
}
confirmSend() {
const confirmationMsg: Array<Array<MessageDataField>> = [
[{key: 'address', value: this.transaction.address, title: 'BTC Address', width: 100}],
[{key: 'satoshi', value: this.transaction.satoshis, title: 'Amount (Sats)', width: 100}],
[{key: 'feeRate', value: this.transaction.feeRate, title: 'Fee Rate', width: 100}],
[{key: 'minconf', value: this.transaction.minconf, title: 'Min Confirmation Blocks', width: 100}]
];
this.store.dispatch(new RTLActions.OpenConfirmation({ data: {
type: AlertTypeEnum.CONFIRM,
alertTitle: 'Confirm Payment',
message: confirmationMsg,
noBtnText: 'Cancel',
yesBtnText: 'Send'
}}));
}
dispatchToSendFunds() {
this.store.dispatch(new RTLActions.OpenSpinner('Sending Funds...'));
this.store.dispatch(new RTLActions.SetChannelTransactionCL(this.transaction));
this.transaction = {};
}
get invalidValues(): boolean {
return (undefined === this.transaction.address || this.transaction.address === '')
|| ((undefined === this.transaction.satoshis || this.transaction.satoshis <= 0))
|| (this.flgMinConf && (undefined === this.transaction.minconf || this.transaction.minconf <= 0));
}
resetData() {
this.transaction = {};
this.flgMinConf = false;
}
onAmountUnitChange(event: any) {
let self = this;
if(this.transaction.satoshis && this.selAmountUnit !== event.value) {
this.commonService.convertCurrency(this.transaction.satoshis, CURRENCY_UNIT_FORMATS.Sats, this.amountUnits[2])
.pipe(takeUntil(this.unSubs[4]))
.subscribe(data => {
self.transaction.satoshis = +self.decimalPipe.transform(data[CURRENCY_UNIT_FORMATS.Sats], self.currencyUnitFormats[CURRENCY_UNIT_FORMATS.Sats]).replace(/,/g, '');
});
}
this.selAmountUnit = event.value;
}
ngOnDestroy() {
this.unSubs.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

@ -1,180 +1,29 @@
<div fxLayout="column" fxLayoutAlign="center center" class="test-banner mx-1">
<h5>Don't be #reckless. #craefulgang #craefulgang #craefulgang.</h5>
<div fxLayout="row" fxLayoutAlign="start center" class="padding-gap-x page-title-container">
<fa-icon [icon]="faChartPie" class="page-title-img mr-1"></fa-icon>
<span class="page-title">On-chain Balance</span>
</div>
<div fxLayout="column" fxLayout.gt-sm="row wrap">
<div fxFlex="34" class="padding-gap">
<mat-card [ngClass]="{'custom-card error-border': flgLoadingWallet==='error','custom-card': true}">
<mat-card-header class="bg-primary p-1" fxLayoutAlign="center center">
<mat-card-title class="m-0 pt-1">
<h5>Total Balance</h5>
<mat-progress-bar *ngIf="flgLoadingWallet===true" mode="indeterminate"></mat-progress-bar>
</mat-card-title>
</mat-card-header>
<mat-card-content fxLayout="column" fxLayoutAlign="center center">
<mat-card-content class="mt-1">
<svg style="width:70px;height:70px" viewBox="0 0 24 24">
<path fill="currentColor"
d="M10,2H14A2,2 0 0,1 16,4V6H20A2,2 0 0,1 22,8V19A2,2 0 0,1 20,21H4C2.89,21 2,20.1 2,19V8C2,6.89 2.89,6 4,6H8V4C8,2.89 8.89,2 10,2M14,6V4H10V6H14Z" />
</svg>
</mat-card-content>
<span *ngIf="information?.currency_unit; else withoutData">
<h3 *ngIf="selNode?.satsToBTC; else smallerUnit1">{{balance?.btc_totalBalance | number}}
{{information?.currency_unit}}</h3>
<ng-template #smallerUnit1>
<h3>{{balance?.totalBalance | number}} {{information?.smaller_currency_unit}}</h3>
</ng-template>
</span>
</mat-card-content>
<mat-progress-bar class="mt-minus-5" *ngIf="flgLoadingWallet===true" mode="indeterminate"></mat-progress-bar>
<mat-divider></mat-divider>
</mat-card>
</div>
<div fxFlex="33" class="padding-gap">
<mat-card [ngClass]="{'custom-card error-border': flgLoadingWallet==='error','custom-card': true}">
<mat-card-header class="bg-primary p-1" fxLayoutAlign="center center">
<mat-card-title class="m-0 pt-1">
<h5>Confirmed Balance</h5>
<mat-progress-bar *ngIf="flgLoadingWallet===true" mode="indeterminate"></mat-progress-bar>
</mat-card-title>
</mat-card-header>
<mat-card-content fxLayout="column" fxLayoutAlign="center center">
<mat-card-content class="mt-1">
<svg style="width:70px;height:70px" viewBox="0 0 24 24">
<path fill="currentColor"
d="M10,2H14A2,2 0 0,1 16,4V6H20A2,2 0 0,1 22,8V19A2,2 0 0,1 20,21H4A2,2 0 0,1 2,19V8A2,2 0 0,1 4,6H8V4A2,2 0 0,1 10,2M14,6V4H10V6H14M10.5,17.5L17.09,10.91L15.68,9.5L10.5,14.67L8.41,12.59L7,14L10.5,17.5Z" />
</svg>
</mat-card-content>
<span *ngIf="information?.currency_unit; else withoutData">
<h3 *ngIf="selNode?.satsToBTC; else smallerUnit2">{{balance?.btc_confBalance | number}}
{{information?.currency_unit}}</h3>
<ng-template #smallerUnit2>
<h3>{{balance?.confBalance | number}} {{information?.smaller_currency_unit}}</h3>
</ng-template>
</span>
</mat-card-content>
<mat-progress-bar class="mt-minus-5" *ngIf="flgLoadingWallet===true" mode="indeterminate"></mat-progress-bar>
<mat-divider></mat-divider>
</mat-card>
</div>
<div fxFlex="33" class="padding-gap">
<mat-card [ngClass]="{'custom-card error-border': flgLoadingWallet==='error','custom-card': true}">
<mat-card-header class="bg-primary p-1" fxLayoutAlign="center center">
<mat-card-title class="m-0 pt-1">
<h5>Unconfirmed Balance</h5>
<mat-progress-bar *ngIf="flgLoadingWallet===true" mode="indeterminate"></mat-progress-bar>
</mat-card-title>
</mat-card-header>
<mat-card-content fxLayout="column" fxLayoutAlign="center center">
<mat-card-content class="mt-1">
<svg style="width:70px;height:70px" viewBox="0 0 24 24">
<path fill="currentColor"
d="M14,2A2,2 0 0,1 16,4V6H20A2,2 0 0,1 22,8L10.85,19C10.85,20.1 10.85,19.5 10.85,21H4C2.89,21 2,20.1 2,19V8C2,6.89 2.89,6 4,6H8V4C8,2.89 8.89,2 10,2H14M14,6V4H10V6H14M21.04,12.13C20.9,12.13 20.76,12.19 20.65,12.3L19.65,13.3L21.7,15.35L22.7,14.35C22.92,14.14 22.92,13.79 22.7,13.58L21.42,12.3C21.31,12.19 21.18,12.13 21.04,12.13M19.07,13.88L13,19.94V22H15.06L21.12,15.93L19.07,13.88Z" />
</svg>
</mat-card-content>
<span *ngIf="information?.currency_unit; else withoutData">
<h3 *ngIf="selNode?.satsToBTC; else smallerUnit3">{{balance?.btc_unconfBalance | number}}
{{information?.currency_unit}}</h3>
<ng-template #smallerUnit3>
<h3>{{balance?.unconfBalance | number}} {{information?.smaller_currency_unit}}</h3>
</ng-template>
</span>
</mat-card-content>
<mat-progress-bar class="mt-minus-5" *ngIf="flgLoadingWallet===true" mode="indeterminate"></mat-progress-bar>
<mat-divider></mat-divider>
</mat-card>
</div>
<div fxFlex="100" class="padding-gap">
<mat-card class="mat-card-original">
<mat-card-header>
<mat-card-subtitle>
<h2>Receive Funds</h2>
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<div fxLayout="column" fxFlex="100" fxLayout.lt-md="top-minus-25px">
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" fxLayout.gt-md="row wrap">
<div fxFlex="15" fxLayoutAlign="start end">
<mat-form-field fxFlex="99">
<mat-select [(ngModel)]="selectedAddress" placeholder="Address Type" name="address_type" tabindex="1">
<mat-option *ngFor="let addressType of addressTypes" [value]="addressType">
{{addressType.addressTp}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div fxFlex="25" fxLayoutAlign="space-between end">
<div fxFlex.gt-md="65" fxFlex.lt-lg="49" fxLayoutAlign="start end">
<button fxLayoutAlign="center center" mat-raised-button color="primary"
[disabled]="undefined === selectedAddress.addressId" (click)="onGenerateAddress()" tabindex="2"
class="top-minus-15px">Generate Address</button>
</div>
<div fxFlex.gt-md="30" fxFlex.lt-lg="49" fxLayoutAlign="start end">
<button fxLayoutAlign="center center" mat-stroked-button color="primary" tabindex="3"
(click)="resetReceiveData()" class="top-minus-15px">Clear</button>
</div>
</div>
<div fxFlex="42" fxLayoutAlign="start end">
<mat-form-field fxFlex="100">
<input matInput [value]="newAddress" placeholder="Generated Address" readonly>
</mat-form-field>
</div>
<div fxFlex.lt-lg="40" fxFlex.gt-md="14" fxLayoutAlign="center center">
<qrcode [qrdata]="newAddress" [size]="120" [level]="'L'" [allowEmptyString]="true"
[ngStyle]="{'visibility': (newAddress === '') ? 'hidden' : 'visible'}" class="top-minus-5px qr-border">
</qrcode>
</div>
</div>
</div>
</mat-card-content>
</mat-card>
</div>
<div fxFlex="100" class="padding-gap">
<mat-card class="mat-card-original">
<mat-card-header>
<mat-card-subtitle>
<h2>Send Funds</h2>
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<form fxLayout="column" fxLayout.gt-sm="row wrap" fxLayoutAlign="space-between start"
(ngSubmit)="sendFundForm.form.valid && onSendFunds()" #sendFundForm="ngForm">
<mat-form-field fxFlex="35" fxLayoutAlign="start end">
<input matInput [(ngModel)]="transaction.address" placeholder="Address" name="address" tabindex="4" required
name="address" #address="ngModel">
</mat-form-field>
<mat-form-field fxFlex="10" fxLayoutAlign="start end">
<input matInput [(ngModel)]="transaction.satoshis" placeholder="Amount (mSats)" type="number" step="1000" min="1"
tabindex="5" required name="amount" #amount="ngModel">
</mat-form-field>
<mat-form-field fxFlex="10" fxLayoutAlign="start end">
<mat-select tabindex="6" placeholder="Fee Rate" [(value)]="transaction.feeRate">
<mat-option *ngFor="let feeRateType of feeRateTypes" [value]="feeRateType.feeRateId">
{{feeRateType.feeRateType}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-checkbox fxFlex="20" fxFlex.lt-lg="35" tabindex="7" [(ngModel)]="flgMinConf" name="flgMinConf">
<mat-form-field fxFlex="100">
<input matInput [(ngModel)]="transaction.minconf" placeholder="Min Confirmation Blocks" type="number"
name="blocks" step="1" min="0" tabindex="8" #blocks="ngModel" [required]="flgMinConf"
[disabled]="!flgMinConf">
</mat-form-field>
</mat-checkbox>
<div fxFlex="10" fxLayoutAlign="end start">
<button fxFlex="90" fxLayoutAlign="center center" mat-raised-button color="primary" [disabled]="invalidValues" type="submit" tabindex="8">
<p *ngIf="invalidValues && (address.touched || address.dirty) && (amount.touched || amount.dirty); else sendText">Invalid Values</p>
<ng-template #sendText><p>Send</p></ng-template>
</button>
</div>
<div fxFlex="10" fxLayoutAlign="end start">
<button fxFlex="90" fxLayoutAlign="center center" mat-stroked-button color="primary" tabindex="9" type="reset"
(click)="resetData()">Clear</button>
</div>
</form>
</mat-card-content>
</mat-card>
</div>
<div fxLayout="column" class="padding-gap-x mb-4">
<mat-card>
<mat-card-content fxLayout="column">
<rtl-currency-unit-converter [values]="balances" [currencyUnits]="selNode.currencyUnits"></rtl-currency-unit-converter>
</mat-card-content>
</mat-card>
</div>
<div fxLayout="row" fxLayoutAlign="start center" class="padding-gap-x page-title-container">
<fa-icon [icon]="faExchangeAlt" class="page-title-img mr-1"></fa-icon>
<span class="page-title">On-chain Transactions</span>
</div>
<div fxLayout="column" class="padding-gap-x">
<mat-card>
<mat-card-content fxLayout="column">
<mat-tab-group>
<mat-tab label="Send">
<rtl-cl-on-chain-send></rtl-cl-on-chain-send>
</mat-tab>
<mat-tab label="Receive">
<rtl-cl-on-chain-receive></rtl-cl-on-chain-receive>
</mat-tab>
</mat-tab-group>
</mat-card-content>
</mat-card>
</div>
<ng-template #withoutData>
<h3>Sats</h3>
</ng-template>

@ -1,19 +1,12 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil, take } from 'rxjs/operators';
import { takeUntil } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { Actions } from '@ngrx/effects';
import { faExchangeAlt, faChartPie } from '@fortawesome/free-solid-svg-icons';
import { SelNodeChild } from '../../shared/models/RTLconfig';
import { GetInfoCL, BalanceCL, OnChainCL, AddressTypeCL } from '../../shared/models/clModels';
import { RTLConfiguration } from '../../shared/models/RTLconfig';
import { LoggerService } from '../../shared/services/logger.service';
import * as sha256 from 'sha256';
import { CLEffects } from '../store/cl.effects';
import { RTLEffects } from '../../store/rtl.effects';
import * as RTLActions from '../../store/rtl.actions';
import * as fromRTLReducer from '../../store/rtl.reducers';
import { AlertTypeEnum, DataTypeEnum } from '../../shared/services/consts-enums-functions';
@Component({
selector: 'rtl-cl-on-chain',
@ -22,112 +15,27 @@ import { AlertTypeEnum, DataTypeEnum } from '../../shared/services/consts-enums-
})
export class CLOnChainComponent implements OnInit, OnDestroy {
public selNode: SelNodeChild = {};
public appConfig: RTLConfiguration;
public addressTypes = [];
public flgLoadingWallet: Boolean | 'error' = true;
public selectedAddress: AddressTypeCL = {};
public balance: BalanceCL = {};
public information: GetInfoCL = {};
public newAddress = '';
public transaction: OnChainCL = {};
public feeRateTypes = [];
public flgMinConf = false;
private unsub: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
public faExchangeAlt = faExchangeAlt;
public faChartPie = faChartPie;
public balances = [{title: 'Total Balance', dataValue: 0}, {title: 'Confirmed', dataValue: 0}, {title: 'Unconfirmed', dataValue: 0}];
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private rtlEffects: RTLEffects, private clEffects: CLEffects) {}
constructor(private store: Store<fromRTLReducer.RTLState>, private actions$: Actions) {}
ngOnInit() {
this.store.select('root')
.pipe(takeUntil(this.unsub[0]))
.subscribe((rootStore) => {
this.appConfig = rootStore.appConfig;
this.logger.info(rootStore);
});
this.store.select('cl')
.pipe(takeUntil(this.unsub[1]))
.pipe(takeUntil(this.unSubs[1]))
.subscribe((rtlStore) => {
rtlStore.effectErrorsCl.forEach(effectsErr => {
if (effectsErr.action === 'FetchBalanceCL') {
this.flgLoadingWallet = 'error';
}
});
this.selNode = rtlStore.nodeSettings;
this.information = rtlStore.information;
this.feeRateTypes = rtlStore.feeRateTypes;
this.addressTypes = rtlStore.addressTypes;
this.balance = rtlStore.balance;
if (undefined === this.balance.totalBalance) {
this.balance.totalBalance = '0';
}
if (undefined === this.balance.confBalance) {
this.balance.confBalance = '0';
}
if (undefined === this.balance.unconfBalance) {
this.balance.unconfBalance = '0';
}
if (this.flgLoadingWallet !== 'error') {
this.flgLoadingWallet = false;
}
this.logger.info(rtlStore);
this.balances = [{title: 'Total Balance', dataValue: rtlStore.balance.totalBalance || 0}, {title: 'Confirmed', dataValue: rtlStore.balance.confBalance}, {title: 'Unconfirmed', dataValue: rtlStore.balance.unconfBalance}];
});
}
onGenerateAddress() {
this.store.dispatch(new RTLActions.OpenSpinner('Getting New Address...'));
this.store.dispatch(new RTLActions.GetNewAddressCL(this.selectedAddress));
this.clEffects.setNewAddressCL
.pipe(takeUntil(this.unsub[2]))
.subscribe(newAddress => {
this.newAddress = newAddress;
});
}
onSendFunds() {
const reorderedTransaction = [
[{key: 'address', value: this.transaction.address, title: 'Address', width: 100, type: DataTypeEnum.NUMBER}]
];
this.store.dispatch(new RTLActions.OpenConfirmation({ data: {
type: AlertTypeEnum.CONFIRM,
alertTitle: 'Confirm Send Funds',
message: reorderedTransaction,
noBtnText: 'Cancel',
yesBtnText: 'Send'
}}));
this.rtlEffects.closeConfirm
.pipe(takeUntil(this.unsub[3]))
.subscribe(confirmRes => {
if (confirmRes) {
this.store.dispatch(new RTLActions.OpenSpinner('Sending Funds...'));
this.store.dispatch(new RTLActions.SetChannelTransactionCL(this.transaction));
this.transaction = {};
}
});
}
get invalidValues(): boolean {
return (undefined === this.transaction.address || this.transaction.address === '')
|| ((undefined === this.transaction.satoshis || this.transaction.satoshis <= 0))
|| (this.flgMinConf && (undefined === this.transaction.minconf || this.transaction.minconf <= 0));
}
resetData() {
this.transaction = {};
this.flgMinConf = false;
}
resetReceiveData() {
this.selectedAddress = {};
this.newAddress = '';
}
ngOnDestroy() {
this.unsub.forEach(completeSub => {
this.unSubs.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

@ -1,84 +0,0 @@
<div fxLayout="column">
<div class="padding-gap">
<mat-card class="mat-card-original">
<mat-card-header>
<mat-card-subtitle>
<h2>Query Routes</h2>
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<form fxLayout="column" fxLayoutAlign="space-between stretch" fxLayout.gt-md="row wrap"
(ngSubmit)="queryRoutesForm.form.valid && onQueryRoutes()" #queryRoutesForm="ngForm">
<mat-form-field fxFlex="50" fxLayoutAlign="start end">
<input matInput placeholder="Destination Pubkey" name="destinationPubkey" [(ngModel)]="destinationPubkey"
tabindex="1" required #destPubkey="ngModel">
</mat-form-field>
<mat-form-field fxFlex="20" fxLayoutAlign="start end">
<input matInput placeholder="Amount (mSats)" name="amount" [(ngModel)]="amount" tabindex="2" type="number"
step="1000" min="0" required #destAmount="ngModel">
</mat-form-field>
<div fxFlex="15" fxLayoutAlign="start start">
<button fxFlex="90" fxLayoutAlign="center center" mat-raised-button color="primary"
[disabled]="destPubkey.invalid || destAmount.invalid" type="submit" tabindex="3">
<p
*ngIf="(destPubkey.invalid && (destPubkey.dirty || destPubkey.touched) || (destAmount.invalid && (destAmount.dirty || destAmount.touched))); else queryText">
Invalid Pubkey/Amount
</p>
<ng-template #queryText>
<p>Query</p>
</ng-template>
</button>
</div>
<div fxFlex="15" fxLayoutAlign="start start">
<button fxFlex="90" fxLayoutAlign="center center" mat-stroked-button color="primary" tabindex="4" type="reset"
(click)="resetData()">Clear</button>
</div>
</form>
</mat-card-content>
</mat-card>
</div>
<div class="padding-gap">
<mat-card class="mat-card-original">
<mat-card-content class="table-card-content">
<div perfectScrollbar class="table-container">
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
<table mat-table #table [dataSource]="qRoutes" matSort
[ngClass]="{'overflow-x-auto error-border': flgLoading[0]==='error','overflow-x-auto': true}">
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef mat-sort-header> ID </th>
<td mat-cell *matCellDef="let route"> {{route?.id}} </td>
</ng-container>
<ng-container matColumnDef="alias">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Alias </th>
<td mat-cell *matCellDef="let route"> {{route?.alias}} </td>
</ng-container>
<ng-container matColumnDef="channel">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Channel </th>
<td mat-cell *matCellDef="let route"> {{route?.channel}} </td>
</ng-container>
<ng-container matColumnDef="direction">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Direction </th>
<td mat-cell *matCellDef="let route"> {{route?.direction}} </td>
</ng-container>
<ng-container matColumnDef="msatoshi">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> mSatoshi </th>
<td mat-cell *matCellDef="let route"><span fxLayoutAlign="end center"> {{route?.msatoshi | number}}
</span></td>
</ng-container>
<ng-container matColumnDef="amount_msat">
<th mat-header-cell class="pl-4" *matHeaderCellDef mat-sort-header> Amount mSat </th>
<td mat-cell class="pl-4" *matCellDef="let route"> {{route?.amount_msat}} </td>
</ng-container>
<ng-container matColumnDef="delay">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Delay </th>
<td mat-cell *matCellDef="let route"><span fxLayoutAlign="end center"> {{route?.delay | number}} </span>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: flgSticky;"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" (click)="onRouteClick(row, $event)"></tr>
</table>
</div>
</mat-card-content>
</mat-card>
</div>
</div>

@ -1,104 +0,0 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil, take } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { Actions } from '@ngrx/effects';
import { MatTableDataSource, MatSort } from '@angular/material';
import { RoutesCL } from '../../../shared/models/clModels';
import { LoggerService } from '../../../shared/services/logger.service';
import { CLEffects } from '../../store/cl.effects';
import * as RTLActions from '../../../store/rtl.actions';
import * as fromRTLReducer from '../../../store/rtl.reducers';
import { AlertTypeEnum, DataTypeEnum } from '../../../shared/services/consts-enums-functions';
@Component({
selector: 'rtl-cl-query-routes',
templateUrl: './query-routes.component.html',
styleUrls: ['./query-routes.component.scss']
})
export class CLQueryRoutesComponent implements OnInit, OnDestroy {
@ViewChild(MatSort, { static: true }) sort: MatSort;
public destinationPubkey = '';
public amount = null;
public qRoutes: any;
public flgSticky = false;
public displayedColumns = [];
public flgLoading: Array<Boolean | 'error'> = [false]; // 0: peers
private unSubs: Array<Subject<void>> = [new Subject(), new Subject()];
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private clEffects: CLEffects, private actions$: Actions) {
switch (true) {
case (window.innerWidth <= 415):
this.displayedColumns = ['alias', 'direction', 'msatoshi', 'delay'];
break;
case (window.innerWidth > 415 && window.innerWidth <= 730):
this.displayedColumns = ['alias', 'channel', 'direction', 'msatoshi', 'delay'];
break;
case (window.innerWidth > 730 && window.innerWidth <= 1024):
this.displayedColumns = ['id', 'alias', 'channel', 'direction', 'msatoshi', 'amount_msat', 'delay'];
break;
case (window.innerWidth > 1024 && window.innerWidth <= 1280):
this.flgSticky = true;
this.displayedColumns = ['id', 'alias', 'channel', 'direction', 'msatoshi', 'amount_msat', 'delay'];
break;
default:
this.flgSticky = true;
this.displayedColumns = ['id', 'alias', 'channel', 'direction', 'msatoshi', 'amount_msat', 'delay'];
break;
}
}
ngOnInit() {
this.clEffects.setQueryRoutesCL
.pipe(takeUntil(this.unSubs[1]))
.subscribe(queryRoute => {
this.qRoutes = new MatTableDataSource([]);
this.qRoutes.data = [];
if (undefined !== queryRoute && undefined !== queryRoute.routes) {
this.flgLoading[0] = false;
this.qRoutes = new MatTableDataSource<RoutesCL>([...queryRoute.routes]);
this.qRoutes.data = queryRoute.routes;
} else {
this.flgLoading[0] = 'error';
}
this.qRoutes.sort = this.sort;
});
}
onQueryRoutes() {
this.flgLoading[0] = true;
this.store.dispatch(new RTLActions.GetQueryRoutesCL({destPubkey: this.destinationPubkey, amount: this.amount}));
}
resetData() {
this.destinationPubkey = '';
this.amount = null;
this.flgLoading[0] = false;
}
onRouteClick(selRow: RoutesCL, event: any) {
const selRoute = this.qRoutes.data.filter(route => {
return route.id === route.id;
})[0];
const reorderedRoute = [
[{key: 'id', value: selRoute.id, title: 'ID', width: 100, type: DataTypeEnum.NUMBER}]
// 'id', 'alias', 'channel', 'direction', 'msatoshi', 'amount_msat', 'delay'
];
this.store.dispatch(new RTLActions.OpenAlert({ data: {
type: AlertTypeEnum.INFORMATION,
alertTitle: 'Route Information',
message: reorderedRoute
}}));
}
ngOnDestroy() {
this.unSubs.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

@ -1,104 +0,0 @@
<div fxLayout="column">
<div class="padding-gap">
<mat-card class="mat-card-original">
<mat-card-header>
<mat-card-subtitle>
<h2>Verify and Send Payments</h2>
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<form fxLayout="column" fxLayoutAlign="space-between stretch" fxLayout.gt-md="row wrap"
#sendPaymentForm="ngForm">
<div fxFlex="69" fxLayoutAlign="space-between stretch">
<mat-form-field class="w-100">
<input matInput placeholder="Payment Request" name="paymentRequest" [(ngModel)]="paymentRequest"
tabindex="1" required #paymentReq="ngModel">
</mat-form-field>
</div>
<div fxFlex="100" fxFlex.gt-sm="30" fxLayout="row" fxLayoutAlign="space-between stretch" class="mt-2">
<button fxFlex="48" fxLayoutAlign="center center" mat-raised-button color="primary"
[disabled]="paymentReq.invalid" (click)="onSendPayment()" tabindex="2">
<p *ngIf="paymentReq.invalid && (paymentReq.dirty || paymentReq.touched); else sendText">Invalid Req</p>
<ng-template #sendText>
<p>Send Payment</p>
</ng-template>
</button>
<button fxFlex="48" mat-stroked-button color="primary" type="reset" tabindex="3" type="reset"
(click)="resetData()">Clear</button>
</div>
</form>
</mat-card-content>
</mat-card>
</div>
<div class="padding-gap">
<mat-card class="mat-card-original">
<mat-card-content class="table-card-content">
<div fxLayout="column" fxLayout.gt-xs="row" fxLayoutAlign.gt-xs="start center" fxLayoutAlign="start stretch">
<mat-form-field fxFlex="30">
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
</mat-form-field>
</div>
<div perfectScrollbar class="table-container">
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
<table mat-table #table [dataSource]="payments" matSort
[ngClass]="{'overflow-auto error-border': flgLoading[0]==='error','overflow-auto': true}">
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef mat-sort-header>ID</th>
<td mat-cell *matCellDef="let payment">{{payment?.id}}</td>
</ng-container>
<ng-container matColumnDef="bolt11">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Bolt11</th>
<td mat-cell *matCellDef="let payment">{{payment?.bolt11 | slice:0:10}}...</td>
</ng-container>
<ng-container matColumnDef="created_at">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Created At</th>
<td mat-cell *matCellDef="let payment">{{payment?.created_at_str}}</td>
</ng-container>
<ng-container matColumnDef="destination">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Destination</th>
<td mat-cell *matCellDef="let payment">{{payment?.destination | slice:0:10}}...</td>
</ng-container>
<ng-container matColumnDef="status">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Status</th>
<td mat-cell *matCellDef="let payment">{{payment?.status}}</td>
</ng-container>
<ng-container matColumnDef="msatoshi">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">mSatoshi</th>
<td mat-cell *matCellDef="let payment"><span
fxLayoutAlign="end center">{{payment?.msatoshi | number}}</span></td>
</ng-container>
<ng-container matColumnDef="msatoshi_sent">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">mSatoshi Sent</th>
<td mat-cell *matCellDef="let payment"><span
fxLayoutAlign="end center">{{payment?.msatoshi_sent | number}}</span></td>
</ng-container>
<ng-container matColumnDef="payment_hash">
<th mat-header-cell class="pl-4" *matHeaderCellDef mat-sort-header>Payment Hash</th>
<td mat-cell class="pl-4" *matCellDef="let payment">
<div>{{payment?.payment_hash | slice:0:10}}...</div>
</td>
</ng-container>
<ng-container matColumnDef="payment_preimage">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Payment Pre Image</th>
<td mat-cell *matCellDef="let payment">
<div>{{payment?.payment_preimage | slice:0:10}}<span *ngIf="payment?.payment_preimage">...</span></div>
</td>
</ng-container>
<ng-container matColumnDef="amount_msat">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Amount mSat</th>
<td mat-cell *matCellDef="let payment">{{payment?.amount_msat}}</td>
</ng-container>
<ng-container matColumnDef="amount_sent_msat">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Amount Sent mSat</th>
<td mat-cell *matCellDef="let payment">{{payment?.amount_sent_msat}}</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: flgSticky;"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"
[@newlyAddedRowAnimation]="(row.payment_hash === newlyAddedPayment && flgAnimate) ? 'added' : 'notAdded'"
(click)="onPaymentClick(row, $event)"></tr>
</table>
</div>
</mat-card-content>
</mat-card>
</div>
</div>

@ -1,19 +0,0 @@
.ml-minus-24px {
margin-left: -24px;
}
.info-column {
flex: 1 1 34%;
box-sizing: border-box;
max-width: 34%;
}
.info-value {
flex: 1 1 64%;
max-width: 64%;
word-break: break-word;
}
table {
width:100%;
}

@ -1,198 +0,0 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil, take } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { MatTableDataSource, MatSort } from '@angular/material';
import { GetInfoCL, PaymentCL, PayRequestCL } from '../../../shared/models/clModels';
import { LoggerService } from '../../../shared/services/logger.service';
import { newlyAddedRowAnimation } from '../../../shared/animation/row-animation';
import { CLEffects } from '../../store/cl.effects';
import { RTLEffects } from '../../../store/rtl.effects';
import * as RTLActions from '../../../store/rtl.actions';
import * as fromRTLReducer from '../../../store/rtl.reducers';
import { AlertTypeEnum, DataTypeEnum } from '../../../shared/services/consts-enums-functions';
@Component({
selector: 'rtl-cl-payments',
templateUrl: './payments.component.html',
styleUrls: ['./payments.component.scss'],
animations: [newlyAddedRowAnimation]
})
export class CLPaymentsComponent implements OnInit, OnDestroy {
@ViewChild(MatSort, { static: true }) sort: MatSort;
@ViewChild('sendPaymentForm', { static: true }) form;
public newlyAddedPayment = '';
public flgAnimate = true;
public flgLoading: Array<Boolean | 'error'> = [true];
public information: GetInfoCL = {};
public payments: any;
public paymentJSONArr: PaymentCL[] = [];
public displayedColumns = [];
public paymentDecoded: PayRequestCL = {};
public paymentRequest = '';
public flgSticky = false;
private unsub: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private rtlEffects: RTLEffects, private clEffects: CLEffects) {
switch (true) {
case (window.innerWidth <= 415):
this.displayedColumns = ['created_at', 'status', 'msatoshi', 'msatoshi_sent'];
break;
case (window.innerWidth > 415 && window.innerWidth <= 730):
this.displayedColumns = ['bolt11', 'created_at', 'status', 'msatoshi', 'msatoshi_sent', 'payment_hash'];
break;
case (window.innerWidth > 730 && window.innerWidth <= 1024):
this.displayedColumns = ['bolt11', 'created_at', 'destination', 'status', 'msatoshi', 'msatoshi_sent', 'payment_hash'];
break;
case (window.innerWidth > 1024 && window.innerWidth <= 1280):
this.flgSticky = true;
this.displayedColumns = ['id', 'bolt11', 'created_at', 'destination', 'status', 'msatoshi', 'msatoshi_sent', 'payment_hash', 'payment_preimage','amount_msat', 'amount_sent_msat'];
break;
default:
this.flgSticky = true;
this.displayedColumns = ['id', 'bolt11', 'created_at', 'destination', 'status', 'msatoshi', 'msatoshi_sent', 'payment_hash', 'payment_preimage','amount_msat', 'amount_sent_msat'];
break;
}
}
ngOnInit() {
this.store.dispatch(new RTLActions.FetchPaymentsCL());
this.store.select('cl')
.pipe(takeUntil(this.unsub[0]))
.subscribe((rtlStore) => {
rtlStore.effectErrorsCl.forEach(effectsErr => {
if (effectsErr.action === 'FetchPaymentsCL') {
this.flgLoading[0] = 'error';
}
});
this.information = rtlStore.information;
this.paymentJSONArr = (null !== rtlStore.payments && rtlStore.payments.length > 0) ? rtlStore.payments : [];
this.payments = (undefined === rtlStore.payments || null == rtlStore.payments) ? new MatTableDataSource([]) : new MatTableDataSource<PaymentCL>([...this.paymentJSONArr]);
this.payments.data = this.paymentJSONArr;
this.payments.sort = this.sort;
setTimeout(() => { this.flgAnimate = false; }, 3000);
if (this.flgLoading[0] !== 'error') {
this.flgLoading[0] = (undefined !== this.paymentJSONArr) ? false : true;
}
this.logger.info(rtlStore);
});
}
onSendPayment() {
if (undefined !== this.paymentDecoded.created_at_str) {
this.sendPayment();
} else {
this.store.dispatch(new RTLActions.OpenSpinner('Decoding Payment...'));
this.store.dispatch(new RTLActions.DecodePaymentCL(this.paymentRequest));
this.clEffects.setDecodedPaymentCL
.pipe(take(1))
.subscribe(decodedPayment => {
this.paymentDecoded = decodedPayment;
if (undefined === this.paymentDecoded.msatoshi) {
this.paymentDecoded.msatoshi = 0;
}
this.sendPayment();
});
}
}
sendPayment() {
this.flgAnimate = true;
this.newlyAddedPayment = this.paymentDecoded.payment_hash;
const reorderedDecodedPayment = [
[{key: 'amount_msat', value: this.paymentDecoded.amount_msat, title: 'Amount (mSat)', width: 100, type: DataTypeEnum.NUMBER}]
];
if (undefined === this.paymentDecoded.msatoshi || this.paymentDecoded.msatoshi === 0) {
const titleMsg = 'It is a zero amount invoice. Enter the amount (Sats) to pay.';
this.store.dispatch(new RTLActions.OpenConfirmation({ data: {
type: AlertTypeEnum.CONFIRM,
alertTitle: 'Enter Amount and Confirm Send Payment',
titleMessage: titleMsg,
message: reorderedDecodedPayment,
noBtnText: 'Cancel',
yesBtnText: 'Send',
flgShowInput: true, getInputs: [{placeholder: 'Amount (mSats)', inputType: 'number', inputValue: '', width: 30}]
}}));
this.rtlEffects.closeConfirm
.pipe(take(1))
.subscribe(confirmRes => {
if (confirmRes) {
this.paymentDecoded.msatoshi = confirmRes[0].inputValue;
this.store.dispatch(new RTLActions.OpenSpinner('Sending Payment...'));
this.store.dispatch(new RTLActions.SendPaymentCL({invoice: this.paymentRequest, amount: confirmRes[0].inputValue}));
this.resetData();
}
});
} else {
const reorderedDecodedPayment = [
[{key: 'amount_msat', value: this.paymentDecoded.amount_msat, title: 'Amount (mSat)', width: 100, type: DataTypeEnum.NUMBER}]
];
this.store.dispatch(new RTLActions.OpenConfirmation({ data: {
type: AlertTypeEnum.CONFIRM,
alertTitle: 'Confirm Send Payment',
titleMessage: 'Send Payment',
noBtnText: 'Cancel',
yesBtnText: 'Send',
message: reorderedDecodedPayment
}}));
this.rtlEffects.closeConfirm
.pipe(take(1))
.subscribe(confirmRes => {
if (confirmRes) {
this.store.dispatch(new RTLActions.OpenSpinner('Sending Payment...'));
this.store.dispatch(new RTLActions.SendPaymentCL({invoice: this.paymentRequest}));
this.resetData();
}
});
}
}
// onVerifyPayment() {
// this.store.dispatch(new RTLActions.OpenSpinner('Decoding Payment...'));
// this.store.dispatch(new RTLActions.DecodePayment(this.paymentRequest));
// this.clEffects.setDecodedPayment.subscribe(decodedPayment => {
// this.paymentDecoded = decodedPayment;
// if (undefined !== this.paymentDecoded.timestamp_str) {
// this.paymentDecoded.timestamp_str = (this.paymentDecoded.timestamp_str === '') ? '' :
// formatDate(this.paymentDecoded.timestamp_str, 'dd/MMM/yyyy HH:mm', 'en-US');
// } else {
// this.resetData();
// }
// });
// }
resetData() {
this.form.resetForm();
this.paymentDecoded = {};
}
onPaymentClick(selRow: PaymentCL, event: any) {
const selPayment = this.payments.data.filter(payment => {
return payment.payment_hash === selRow.payment_hash;
})[0];
const reorderedPayment = [
[{key: 'id', value: selPayment.id, title: 'Status', width: 100, type: DataTypeEnum.NUMBER}]
// 'id', 'bolt11', 'created_at_str', 'created_at', 'destination', 'status', 'msatoshi', 'msatoshi_sent', 'payment_hash', 'payment_preimage','amount_msat', 'amount_sent_msat'
];
this.store.dispatch(new RTLActions.OpenAlert({ data: {
type: AlertTypeEnum.INFORMATION,
alertTitle: 'Payment Information',
message: reorderedPayment
}}));
}
applyFilter(selFilter: string) {
this.payments.filter = selFilter;
}
ngOnDestroy() {
this.unsub.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

@ -0,0 +1,53 @@
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="padding-gap">
<form fxLayout="column" fxLayoutAlign="space-between stretch" fxLayout.gt-sm="row wrap">
<mat-form-field fxFlex="40" fxFlex.gt-sm="30" fxLayoutAlign="start end">
<mat-select [(ngModel)]="selectedPeer" placeholder="Alias" name="peer_alias" tabindex="1" required name="selPeer" #selPeer="ngModel">
<mat-option (click)="addNewPeer()" [value]="'new'">
ADD PEER
</mat-option>
<mat-option *ngFor="let peer of peers" [value]="peer.pub_key">
{{peer.alias}}
</mat-option>
</mat-select>
<mat-error *ngIf="!selectedPeer">Alias is required.</mat-error>
</mat-form-field>
<mat-form-field fxFlex="25" fxFlex.gt-sm="30" fxLayoutAlign="start end">
<input matInput [(ngModel)]="fundingAmount" placeholder="Amount" type="number" step="1000" min="1" tabindex="2" required name="amount" #amount="ngModel" nonNegativeAmount="{{totalBalance}}">
<mat-hint>(Wallet Bal: {{totalBalance}}, Remaining Bal: {{totalBalance - ((fundingAmount) ? fundingAmount : 0)}})</mat-hint>
<span matSuffix> {{information?.smaller_currency_unit | titlecase}} </span>
<mat-error *ngIf="!fundingAmount">Amount is required.</mat-error>
<mat-error *ngIf="amount.errors?.negative">Amount must be less than or equal to {{totalBalance}}.</mat-error>
</mat-form-field>
<div fxFlex="15" fxFlex.gt-sm="20" fxLayoutAlign="start center" [ngClass]="{'mt-2': screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM}">
<mat-slide-toggle tabindex="3" color="primary" [(ngModel)]="isPrivate" name="isPrivate">Private Channel</mat-slide-toggle>
</div>
<div fxFlex="100" fxFlex.gt-sm="17" fxLayoutAlign="start center" [ngClass]="{'mt-2': screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM}">
<button fxFlex="48" fxFlex.gt-sm="100" fxLayoutAlign="center center" mat-stroked-button color="primary" type="reset" (click)="onShowAdvanced()" tabindex="4">
<p *ngIf="!showAdvanced; else hideAdvancedText">Show Advanced</p>
<ng-template #hideAdvancedText><p>Hide Advanced</p></ng-template>
</button>
</div>
<div *ngIf="showAdvanced" fxFlex="100" fxLayout="column" fxLayoutAlign="space-between stretch" fxLayout.gt-sm="row wrap">
<mat-form-field fxFlex="34" fxLayoutAlign="start end">
<mat-select tabindex="4" placeholder="Fee Rate" [(value)]="selFeeRate">
<mat-option *ngFor="let feeRateType of feeRateTypes" [value]="feeRateType.feeRateId">
{{feeRateType.feeRateType}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-checkbox fxFlex="34" tabindex="5" [(ngModel)]="flgMinConf" name="flgMinConf">
<mat-form-field fxFlex="100">
<input matInput [(ngModel)]="minConfValue" placeholder="Min Confirmation Blocks" type="number"
name="blocks" step="1" min="0" tabindex="6" #blocks="ngModel" [required]="flgMinConf"
[disabled]="!flgMinConf">
</mat-form-field>
</mat-checkbox>
</div>
<div fxLayout="row" fxFlex="100" fxFlex.gt-sm="30" fxLayoutAlign="space-between stretch" class="mt-2">
<button fxFlex="48" fxLayoutAlign="center center" mat-stroked-button color="primary" tabindex="10" type="reset" (click)="resetData()">Clear Field</button>
<button fxFlex="48" fxLayoutAlign="center center" mat-raised-button color="primary" (click)="onOpenChannel()" type="submit" tabindex="11">Open Channel</button>
</div>
</form>
<rtl-cl-channels-tables fxLayout="row" fxFlex="100"></rtl-cl-channels-tables>
</div>

@ -1,12 +1,21 @@
.mat-column-close, .mat-column-update, .mat-column-connected, .mat-column-private {
.mat-column-close, .mat-column-update, .mat-column-active, .mat-column-private {
flex: 0 0 6%;
min-width: 60px;
}
.mat-column-private {
padding-left: 10px;
}
.mat-cell.mat-column-close, .mat-column-update {
cursor: pointer;
}
.mat-column-chan_id {
flex: 0 0 16%;
min-width: 160px;
}
.mat-checkbox-inner-container:focus, .mat-checkbox-input:focus {
outline: none !important;
}

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CLChannelManageComponent } from './channel-manage.component';
describe('CLChannelManageComponent', () => {
let component: CLChannelManageComponent;
let fixture: ComponentFixture<CLChannelManageComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CLChannelManageComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CLChannelManageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,134 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { Subject } from 'rxjs';
import { take, takeUntil, filter } from 'rxjs/operators';
import { Actions } from '@ngrx/effects';
import { Store } from '@ngrx/store';
import { MatSort, MatSnackBar } from '@angular/material';
import { PeerCL, GetInfoCL } from '../../../../shared/models/clModels';
import { ScreenSizeEnum, AlertTypeEnum, DataTypeEnum, FEE_RATE_TYPES } from '../../../../shared/services/consts-enums-functions';
import { LoggerService } from '../../../../shared/services/logger.service';
import { CommonService } from '../../../../shared/services/common.service';
import { RTLEffects } from '../../../../store/rtl.effects';
import { CLEffects } from '../../../store/cl.effects';
import * as RTLActions from '../../../../store/rtl.actions';
import * as fromRTLReducer from '../../../../store/rtl.reducers';
@Component({
selector: 'rtl-cl-channel-manage',
templateUrl: './channel-manage.component.html',
styleUrls: ['./channel-manage.component.scss']
})
export class CLChannelManageComponent implements OnInit, OnDestroy {
@ViewChild(MatSort, { static: true }) sort: MatSort;
public totalBalance = 0;
public selectedPeer = '';
public fundingAmount: number;
public peers: PeerCL[] = [];
public information: GetInfoCL = {};
public myChanPolicy: any = {};
public isPrivate = false;
public feeRateTypes = FEE_RATE_TYPES;
public showAdvanced = false;
public peerAddress = '';
public newlyAddedPeer = '';
public selFeeRate = '';
public flgMinConf = false;
public minConfValue = null;
public moreOptions = false;
public screenSizeEnum = ScreenSizeEnum;
public screenSize = '';
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private rtlEffects: RTLEffects, private lndEffects: CLEffects, private commonService: CommonService, private actions$: Actions, private snackBar: MatSnackBar) {
this.screenSize = this.commonService.getScreenSize();
}
ngOnInit() {
this.store.select('cl')
.pipe(takeUntil(this.unSubs[0]))
.subscribe((rtlStore) => {
this.information = rtlStore.information;
this.peers = rtlStore.peers;
this.peers.forEach(peer => {
if (undefined === peer.alias || peer.alias === '') {
peer.alias = peer.id.substring(0, 15) + '...';
}
});
this.totalBalance = rtlStore.balance.totalBalance;
this.logger.info(rtlStore);
});
this.actions$.pipe(takeUntil(this.unSubs[1]),
filter((action) => action.type === RTLActions.SET_PEERS))
.subscribe((action: RTLActions.SetPeers) => {
if(this.newlyAddedPeer !== '') {
this.snackBar.open('Peer added successfully.');
this.selectedPeer = this.newlyAddedPeer;
this.newlyAddedPeer = '';
}
});
}
onOpenChannel() {
if (!this.selectedPeer || this.selectedPeer === '' || !this.fundingAmount || (this.totalBalance - ((this.fundingAmount) ? this.fundingAmount : 0) < 0)) { return true; }
this.store.dispatch(new RTLActions.OpenSpinner('Opening Channel...'));
this.store.dispatch(new RTLActions.SaveNewChannelCL({
peerId: this.selectedPeer, satoshis: this.fundingAmount, announce: !this.isPrivate, feeRate: this.selFeeRate, minconf: this.flgMinConf ? this.minConfValue : null
}));
}
resetData() {
this.selectedPeer = '';
this.fundingAmount = 0;
this.moreOptions = false;
this.flgMinConf = false;
this.isPrivate = false;
this.selFeeRate = '';
this.minConfValue = null;
}
onShowAdvanced() {
this.showAdvanced = !this.showAdvanced;
if (!this.showAdvanced) {
this.flgMinConf = false;
this.isPrivate = false;
this.selFeeRate = '';
this.minConfValue = null;
}
}
addNewPeer() {
this.store.dispatch(new RTLActions.OpenConfirmation({ data: {
type: AlertTypeEnum.CONFIRM,
alertTitle: 'Add peer',
message: '',
noBtnText: 'Do it Later',
yesBtnText: 'Add Peer',
flgShowInput: true,
getInputs: [
{placeholder: 'Lightning Address (pubkey OR pubkey@ip:port)', inputType: DataTypeEnum.STRING, inputValue: '', width: 100}
]
}}));
this.rtlEffects.closeConfirm
.pipe(take(1))
.subscribe(confirmRes => {
if (confirmRes) {
this.peerAddress = confirmRes[0].inputValue;
this.newlyAddedPeer = this.peerAddress;
this.store.dispatch(new RTLActions.OpenSpinner('Adding Peer...'));
this.store.dispatch(new RTLActions.SaveNewPeerCL({id: this.peerAddress}));
} else {
this.selectedPeer = '';
}
});
}
ngOnDestroy() {
this.unSubs.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

@ -0,0 +1,79 @@
<div fxLayout="column" class="padding-gap">
<div fxLayout="column" fxLayout.gt-xs="row" fxLayoutAlign.gt-xs="start center" fxLayoutAlign="start stretch" class="padding-gap-x page-sub-title-container">
<div fxFlex="70"></div>
<mat-form-field fxFlex="30">
<input matInput (keyup)="applyFilter()" [(ngModel)]="selFilter" name="filter" placeholder="Filter">
</mat-form-field>
</div>
<div perfectScrollbar fxLayout="row" fxLayoutAlign="start center" fxFlex="100" class="table-container w-100">
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
<table mat-table #table [dataSource]="channels" matSort [ngClass]="{'overflow-auto error-border': flgLoading[0]==='error','overflow-auto': true}">
<ng-container matColumnDef="remote_alias">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Peer </th>
<td mat-cell *matCellDef="let channel">
<div class="ellipsis-parent" [ngStyle]="{'max-width': (screenSize === screenSizeEnum.XS) ? '10rem' : '22rem'}">
<span *ngIf="channel.active" class="dot green" matTooltip="Active" matTooltipPosition="right"></span>
<span *ngIf="!channel.active" class="dot yellow" matTooltip="Inactive" matTooltipPosition="right"></span>
<span class="ellipsis-child">{{channel.remote_alias || channel.remote_pubkey}}</span>
</div>
</td>
</ng-container>
<ng-container matColumnDef="local_balance">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Local Balance </th>
<td mat-cell *matCellDef="let channel"><span fxLayoutAlign="end center">{{channel.local_balance | number}} </span></td>
</ng-container>
<ng-container matColumnDef="remote_balance">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Remote Balance </th>
<td mat-cell *matCellDef="let channel"><span fxLayoutAlign="end center">{{channel.remote_balance | number}} </span></td>
</ng-container>
<ng-container matColumnDef="total_satoshis_sent">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Sats Sent </th>
<td mat-cell *matCellDef="let channel"><span fxLayoutAlign="end center">{{channel.total_satoshis_sent | number}} </span></td>
</ng-container>
<ng-container matColumnDef="total_satoshis_received">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Sats Received </th>
<td mat-cell *matCellDef="let channel"><span fxLayoutAlign="end center">{{channel.total_satoshis_received | number}} </span></td>
</ng-container>
<ng-container matColumnDef="balancedness">
<th mat-header-cell *matHeaderCellDef mat-sort-header class="pl-3">Balance Score </th>
<td mat-cell *matCellDef="let channel" class="pl-3">
<div fxLayout="row">
<mat-hint fxFlex="100" fxLayoutAlign="center center" class="font-size-80">{{channel.balancedness || 0 | number}}</mat-hint>
</div>
<mat-progress-bar mode="determinate" value="{{channel.local_balance && channel.local_balance > 0 ? ((+channel.local_balance/((+channel.local_balance)+(+channel.remote_balance)))*100) : 0}}"></mat-progress-bar>
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef class="pl-1">
<div class="bordered-box table-actions-select">
<mat-select placeholder="Actions" tabindex="1" class="mr-0">
<mat-select-trigger></mat-select-trigger>
<mat-option (click)="onChannelUpdate('all')">Update Fee Policy</mat-option>
</mat-select>
</div>
</th>
<td mat-cell *matCellDef="let channel" fxLayoutAlign="end center" class="pl-1">
<div fxFlex="100" class="bordered-box table-actions-select" fxLayoutAlign="center center">
<mat-select placeholder="Actions" tabindex="2" class="mr-0">
<mat-select-trigger></mat-select-trigger>
<mat-option (click)="onChannelClick(channel, $event)">View Info</mat-option>
<mat-option (click)="onViewRemotePolicy(channel)">View Remote Fee </mat-option>
<mat-option (click)="onChannelUpdate(channel)">Update Fee Policy</mat-option>
<mat-option (click)="onChannelClose(channel)">Close Channel</mat-option>
</mat-select>
</div>
</td>
</ng-container>
<ng-container matColumnDef="no_peer">
<td mat-footer-cell *matFooterCellDef colspan="4">
<p *ngIf="numPeers<1">No peers connected. Add a peer in order to open a channel.</p>
<p *ngIf="numPeers>0 && (!channels.data || channels.data.length<1)">No channels available.</p>
</td>
</ng-container>
<tr mat-footer-row *matFooterRowDef="['no_peer']" [ngClass]="{'display-none': numPeers>1 && channels.data && channels.data.length>0}"></tr>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: flgSticky;"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
<mat-paginator [pageSize]="pageSize" [pageSizeOptions]="pageSizeOptions" [showFirstLastButtons]="screenSize === screenSizeEnum.XS ? false : true" class="mb-4"></mat-paginator>
</div>

@ -0,0 +1,28 @@
.mat-column-remote_alias {
flex: 0 0 20%;
width: 20%;
& .ellipsis-parent {
display: flex;
}
}
.mat-column-balancedness {
padding-left: 3rem;
flex: 0 0 25%;
width: 25%;
}
.mat-column-local_balance, .mat-column-remote_balance, .mat-column-total_satoshis_sent, .mat-column-total_satoshis_received {
flex: 0 0 10%;
width: 10%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.mat-column-actions {
width: 100%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CLChannelOpenTableComponent } from './channel-open-table.component';
describe('CLChannelOpenTableComponent', () => {
let component: CLChannelOpenTableComponent;
let fixture: ComponentFixture<CLChannelOpenTableComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CLChannelOpenTableComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CLChannelOpenTableComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,253 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { Subject } from 'rxjs';
import { take, takeUntil } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { MatTableDataSource, MatSort, MatPaginator, MatPaginatorIntl } from '@angular/material';
import { ChannelCL, GetInfoCL } from '../../../../../shared/models/clModels';
import { PAGE_SIZE, PAGE_SIZE_OPTIONS, getPaginatorLabel, AlertTypeEnum, DataTypeEnum, ScreenSizeEnum, FEE_RATE_TYPES } from '../../../../../shared/services/consts-enums-functions';
import { LoggerService } from '../../../../../shared/services/logger.service';
import { CommonService } from '../../../../../shared/services/common.service';
import { CLEffects } from '../../../../store/cl.effects';
import { RTLEffects } from '../../../../../store/rtl.effects';
import * as RTLActions from '../../../../../store/rtl.actions';
import * as fromRTLReducer from '../../../../../store/rtl.reducers';
@Component({
selector: 'rtl-cl-channel-open-table',
templateUrl: './channel-open-table.component.html',
styleUrls: ['./channel-open-table.component.scss'],
providers: [
{ provide: MatPaginatorIntl, useValue: getPaginatorLabel('Channels') }
]
})
export class CLChannelOpenTableComponent implements OnInit, OnDestroy {
@ViewChild(MatSort, { static: true }) sort: MatSort;
@ViewChild(MatPaginator, {static: true}) paginator: MatPaginator;
public totalBalance = 0;
public displayedColumns = [];
public channels: any;
public myChanPolicy: any = {};
public information: GetInfoCL = {};
public numPeers = -1;
public feeRateTypes = FEE_RATE_TYPES;
public flgLoading: Array<Boolean | 'error'> = [true];
public selectedFilter = '';
public selFilter = '';
public flgSticky = false;
public pageSize = PAGE_SIZE;
public pageSizeOptions = PAGE_SIZE_OPTIONS;
public screenSize = '';
public screenSizeEnum = ScreenSizeEnum;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private rtlEffects: RTLEffects, private clEffects: CLEffects, private commonService: CommonService) {
this.screenSize = this.commonService.getScreenSize();
if(this.screenSize === ScreenSizeEnum.XS) {
this.flgSticky = false;
this.displayedColumns = [ 'remote_alias', 'actions'];
} else if(this.screenSize === ScreenSizeEnum.SM) {
this.flgSticky = false;
this.displayedColumns = ['remote_alias', 'local_balance', 'remote_balance', 'actions'];
} else if(this.screenSize === ScreenSizeEnum.MD) {
this.flgSticky = false;
this.displayedColumns = ['remote_alias', 'local_balance', 'remote_balance', 'actions'];
} else {
this.flgSticky = true;
this.displayedColumns = ['remote_alias', 'total_satoshis_sent', 'total_satoshis_received', 'local_balance', 'remote_balance', 'balancedness', 'actions'];
}
}
ngOnInit() {
this.store.select('cl')
.pipe(takeUntil(this.unSubs[0]))
.subscribe((rtlStore) => {
rtlStore.effectErrorsCl.forEach(effectsErr => {
if (effectsErr.action === 'FetchChannelsCL') {
this.flgLoading[0] = 'error';
}
});
this.information = rtlStore.information;
this.numPeers = (rtlStore.peers && rtlStore.peers.length) ? rtlStore.peers.length : 0;
this.totalBalance = rtlStore.balance.totalBalance;
if (undefined !== rtlStore.allChannels) {
this.loadChannelsTable(rtlStore.allChannels);
}
if (this.flgLoading[0] !== 'error') {
this.flgLoading[0] = (undefined !== rtlStore.allChannels) ? false : true;
}
this.logger.info(rtlStore);
});
}
onViewRemotePolicy(selChannel: ChannelCL) {
console.warn(selChannel);
this.store.dispatch(new RTLActions.ChannelLookupCL(selChannel.short_channel_id));
this.clEffects.setLookupCL
.pipe(take(1))
.subscribe(resLookup => {
const reorderedChannelPolicy = [
[{key: 'fee_base_msat', value: resLookup.fee_base_msat, title: 'Base Fees (mSats)', width: 32, type: DataTypeEnum.NUMBER},
{key: 'fee_rate_milli_msat', value: resLookup.fee_rate_milli_msat, title: 'Fee Rate (milli mSats)', width: 32, type: DataTypeEnum.NUMBER},
{key: 'time_lock_delta', value: resLookup.time_lock_delta, title: 'Time Lock Delta', width: 32, type: DataTypeEnum.NUMBER}]
];
this.store.dispatch(new RTLActions.OpenAlert({ data: {
type: AlertTypeEnum.INFORMATION,
alertTitle: 'Remote Channel Policy',
message: reorderedChannelPolicy
}}));
});
}
onChannelUpdate(channelToUpdate: any) {
if (channelToUpdate === 'all') {
const confirmationMsg = [];
this.store.dispatch(new RTLActions.OpenConfirmation({ data: {
type: AlertTypeEnum.CONFIRM,
alertTitle: 'Update Fee Policy for all Channels',
noBtnText: 'Cancel',
yesBtnText: 'Update All Channels',
message: confirmationMsg,
flgShowInput: true,
getInputs: [
{placeholder: 'Base Fee (mSat)', inputType: DataTypeEnum.NUMBER.toLowerCase(), inputValue: 1000, width: 32},
{placeholder: 'Fee Rate (mili mSat)', inputType: DataTypeEnum.NUMBER.toLowerCase(), inputValue: 1, min: 1, width: 32},
{placeholder: 'Time Lock Delta', inputType: DataTypeEnum.NUMBER.toLowerCase(), inputValue: 40, width: 32}
]
}}));
this.rtlEffects.closeConfirm
.pipe(takeUntil(this.unSubs[2]))
.subscribe(confirmRes => {
if (confirmRes) {
const base_fee = confirmRes[0].inputValue;
const fee_rate = confirmRes[1].inputValue;
const time_lock_delta = confirmRes[2].inputValue;
this.store.dispatch(new RTLActions.OpenSpinner('Updating Channel Policy...'));
this.store.dispatch(new RTLActions.UpdateChannelsCL({baseFeeMsat: base_fee, feeRate: fee_rate, channelId: 'all'}));
}
});
} else {
this.myChanPolicy = {fee_base_msat: 0, fee_rate_milli_msat: 0, time_lock_delta: 0};
this.store.dispatch(new RTLActions.OpenSpinner('Fetching Channel Policy...'));
this.store.dispatch(new RTLActions.ChannelLookupCL(channelToUpdate.chan_id.toString()));
this.clEffects.setLookupCL
.pipe(take(1))
.subscribe(resLookup => {
if (resLookup.node1_pub === this.information.id) {
this.myChanPolicy = resLookup.node1_policy;
} else if (resLookup.node2_pub === this.information.id) {
this.myChanPolicy = resLookup.node2_policy;
} else {
this.myChanPolicy = {fee_base_msat: 0, fee_rate_milli_msat: 0, time_lock_delta: 0};
}
this.logger.info(this.myChanPolicy);
this.store.dispatch(new RTLActions.CloseSpinner());
const titleMsg = 'Update fee policy for channel point: ' + channelToUpdate.channel_point;
const confirmationMsg = [];
this.store.dispatch(new RTLActions.OpenConfirmation({ data: {
type: AlertTypeEnum.CONFIRM,
alertTitle: 'Update Fee Policy',
titleMessage: titleMsg,
noBtnText: 'Cancel',
yesBtnText: 'Update Channel',
message: confirmationMsg,
flgShowInput: true,
getInputs: [
{placeholder: 'Base Fee (mSat)', inputType: DataTypeEnum.NUMBER.toLowerCase(), inputValue: (this.myChanPolicy.fee_base_msat === '') ? 0 : this.myChanPolicy.fee_base_msat, width: 32},
{placeholder: 'Fee Rate (mili mSat)', inputType: DataTypeEnum.NUMBER.toLowerCase(), inputValue: this.myChanPolicy.fee_rate_milli_msat, min: 1, width: 32},
{placeholder: 'Time Lock Delta', inputType: DataTypeEnum.NUMBER.toLowerCase(), inputValue: this.myChanPolicy.time_lock_delta, width: 32}
]
}}));
});
this.rtlEffects.closeConfirm
.pipe(takeUntil(this.unSubs[4]))
.subscribe(confirmRes => {
if (confirmRes) {
const base_fee = confirmRes[0].inputValue;
const fee_rate = confirmRes[1].inputValue;
const time_lock_delta = confirmRes[2].inputValue;
this.store.dispatch(new RTLActions.OpenSpinner('Updating Channel Policy...'));
this.store.dispatch(new RTLActions.UpdateChannelsCL({baseFeeMsat: base_fee, feeRate: fee_rate, channelId: channelToUpdate.channel_id}));
}
});
}
this.applyFilter();
}
onChannelClose(channelToClose: ChannelCL) {
this.store.dispatch(new RTLActions.OpenConfirmation({ data: {
type: AlertTypeEnum.CONFIRM,
alertTitle: 'Close Channel',
titleMessage: 'Closing channel: ' + channelToClose.channel_id,
noBtnText: 'Cancel',
yesBtnText: 'Close Channel'
}}));
this.rtlEffects.closeConfirm
.pipe(takeUntil(this.unSubs[5]))
.subscribe(confirmRes => {
if (confirmRes) {
this.store.dispatch(new RTLActions.OpenSpinner('Closing Channel...'));
this.store.dispatch(new RTLActions.CloseChannelCL({channelId: channelToClose.channel_id}));
}
});
}
applyFilter() {
this.selectedFilter = this.selFilter;
this.channels.filter = this.selFilter;
}
onChannelClick(selChannel: ChannelCL, event: any) {
const reorderedChannel = [
[{key: 'alias', value: selChannel.alias, title: 'Peer Alias', width: 40},
{key: 'connected', value: selChannel.connected, title: 'Connected', width: 30, type: DataTypeEnum.BOOLEAN},
{key: 'private', value: selChannel.private, title: 'Private', width: 30, type: DataTypeEnum.BOOLEAN}],
[{key: 'id', value: selChannel.id, title: 'Peer Public Key', width: 100}],
[{key: 'short_channel_id', value: selChannel.short_channel_id, title: 'Short Channel ID', width: 100}],
[{key: 'channel_id', value: selChannel.channel_id, title: 'Channel ID', width: 50},
{key: 'state', value: selChannel.state, title: 'State', width: 50, type: DataTypeEnum.NUMBER}],
[{key: 'our_channel_reserve_satoshis', value: selChannel.our_channel_reserve_satoshis, title: 'Our Channel Reserve Satoshis', width: 50, type: DataTypeEnum.NUMBER},
{key: 'their_channel_reserve_satoshis', value: selChannel.their_channel_reserve_satoshis, title: 'Their Channel Reserve Satoshis', width: 50, type: DataTypeEnum.NUMBER}],
[{key: 'msatoshi_to_us', value: selChannel.msatoshi_to_us, title: 'mSatoshi to Us', width: 50, type: DataTypeEnum.NUMBER},
{key: 'spendable_msatoshi', value: selChannel.spendable_msatoshi, title: 'Spendable mSatoshi', width: 50, type: DataTypeEnum.NUMBER}],
[{key: 'msatoshi_total', value: selChannel.msatoshi_total, title: 'Total mSatoshi', width: 50, type: DataTypeEnum.NUMBER},
{key: 'funding_txid', value: selChannel.funding_txid, title: 'Funding Transaction Id', width: 50, type: DataTypeEnum.NUMBER}]
];
this.store.dispatch(new RTLActions.OpenAlert({ data: {
type: AlertTypeEnum.INFORMATION,
alertTitle: 'Channel Information',
showCopyName: 'Channel ID',
showCopyField: selChannel.short_channel_id,
message: reorderedChannel
}}));
}
loadChannelsTable(mychannels) {
mychannels.sort(function(a, b) {
return (a.active === b.active) ? 0 : ((b.active) ? 1 : -1);
});
this.channels = new MatTableDataSource<ChannelCL>([...mychannels]);
this.channels.filterPredicate = (channel: ChannelCL, fltr: string) => {
const newChannel = ((channel.connected) ? 'connected' : 'disconnected') + (channel.channel_id ? channel.channel_id : '') +
(channel.short_channel_id ? channel.short_channel_id : '') + (channel.id ? channel.id : '') + (channel.alias ? channel.alias : '') +
(channel.private ? 'private' : 'public') + (channel.state ? channel.state.toLowerCase() : '') +
(channel.funding_txid ? channel.funding_txid : '') + (channel.msatoshi_to_us ? channel.msatoshi_to_us : '') +
(channel.msatoshi_total ? channel.msatoshi_total : '') + (channel.their_channel_reserve_satoshis ? channel.their_channel_reserve_satoshis : '') +
(channel.our_channel_reserve_satoshis ? channel.our_channel_reserve_satoshis : '') + (channel.spendable_msatoshi ? channel.spendable_msatoshi : '');
return newChannel.includes(fltr.toLowerCase());
};
this.channels.sort = this.sort;
this.channels.paginator = this.paginator;
this.logger.info(this.channels);
}
ngOnDestroy() {
this.unSubs.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

@ -0,0 +1,10 @@
<div fxLayout="column" fxFlex="100" class="mt-2 bordered-box">
<mat-tab-group>
<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-group>
</div>

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CLChannelsTablesComponent } from './channels-tables.component';
describe('CLChannelsTablesComponent', () => {
let component: CLChannelsTablesComponent;
let fixture: ComponentFixture<CLChannelsTablesComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CLChannelsTablesComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CLChannelsTablesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,35 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { LoggerService } from '../../../../shared/services/logger.service';
import * as fromRTLReducer from '../../../../store/rtl.reducers';
@Component({
selector: 'rtl-cl-channels-tables',
templateUrl: './channels-tables.component.html',
styleUrls: ['./channels-tables.component.scss']
})
export class CLChannelsTablesComponent implements OnInit, OnDestroy {
public openChannels = 0;
private unSubs: Array<Subject<void>> = [new Subject()];
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>) {}
ngOnInit() {
this.store.select('cl')
.pipe(takeUntil(this.unSubs[0]))
.subscribe((rtlStore) => {
this.openChannels = (rtlStore.allChannels && rtlStore.allChannels.length) ? rtlStore.allChannels.length : 0;
this.logger.info(rtlStore);
});
}
ngOnDestroy() {
this.unSubs.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

@ -0,0 +1,35 @@
<div fxLayout="row" fxLayoutAlign="start center" class="padding-gap-x page-title-container">
<fa-icon [icon]="faChartPie" class="page-title-img mr-1"></fa-icon>
<span class="page-title">On-chain Balance</span>
</div>
<div fxLayout="column" class="padding-gap-x mb-4">
<mat-card>
<mat-card-content fxLayout="column">
<rtl-currency-unit-converter [values]="balances" [currencyUnits]="selNode.currencyUnits"></rtl-currency-unit-converter>
</mat-card-content>
</mat-card>
</div>
<div fxLayout="row" fxLayoutAlign="start center" class="padding-gap-x page-title-container">
<fa-icon [icon]="faUsers" class="page-title-img mr-1"></fa-icon>
<span class="page-title">Connections</span>
</div>
<div fxLayout="column" class="padding-gap-x">
<mat-card>
<mat-card-content fxLayout="column">
<mat-tab-group>
<mat-tab>
<ng-template mat-tab-label>
<span matBadge="{{activeChannels}}" matBadgeOverlap="false" class="tab-badge">Channels</span>
</ng-template>
<rtl-cl-channel-manage></rtl-cl-channel-manage>
</mat-tab>
<mat-tab>
<ng-template mat-tab-label>
<span matBadge="{{activePeers}}" matBadgeOverlap="false" class="tab-badge">Peers</span>
</ng-template>
<rtl-cl-peers></rtl-cl-peers>
</mat-tab>
</mat-tab-group>
</mat-card-content>
</mat-card>
</div>

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CLPeersChannelsComponent } from './peers-channels.component';
describe('CLPeersChannelsComponent', () => {
let component: CLPeersChannelsComponent;
let fixture: ComponentFixture<CLPeersChannelsComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CLPeersChannelsComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CLPeersChannelsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

@ -0,0 +1,45 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { Actions } from '@ngrx/effects';
import { faUsers, faChartPie } from '@fortawesome/free-solid-svg-icons';
import { SelNodeChild } from '../../shared/models/RTLconfig';
import * as fromRTLReducer from '../../store/rtl.reducers';
@Component({
selector: 'rtl-cl-peers-channels',
templateUrl: './peers-channels.component.html',
styleUrls: ['./peers-channels.component.scss']
})
export class CLPeersChannelsComponent implements OnInit, OnDestroy {
public selNode: SelNodeChild = {};
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}];
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
constructor(private store: Store<fromRTLReducer.RTLState>, private actions$: Actions) {}
ngOnInit() {
this.store.select('cl')
.pipe(takeUntil(this.unSubs[1]))
.subscribe((rtlStore) => {
this.selNode = rtlStore.nodeSettings;
this.activePeers = (rtlStore.peers && rtlStore.peers.length) ? rtlStore.peers.length : 0;
this.activeChannels = rtlStore.information.num_active_channels;
this.balances = [{title: 'Total Balance', dataValue: rtlStore.balance.totalBalance || 0}, {title: 'Confirmed', dataValue: rtlStore.balance.confBalance}, {title: 'Unconfirmed', dataValue: rtlStore.balance.unconfBalance}];
});
}
ngOnDestroy() {
this.unSubs.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

@ -0,0 +1,75 @@
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="padding-gap">
<form fxLayout="column" fxLayoutAlign="space-between stretch" fxLayout.gt-sm="row wrap" #peersForm="ngForm">
<mat-form-field fxFlex="100" fxLayoutAlign="start end">
<input matInput placeholder="Lightning Address (pubkey OR pubkey@ip:port)" name="peerAddress" [(ngModel)]="peerAddress" tabindex="1" required #peerAdd="ngModel">
<mat-error *ngIf="!peerAddress">Lightning address is required.</mat-error>
</mat-form-field>
<div fxLayout="row" fxFlex="100" fxFlex.gt-sm="30" fxLayoutAlign="space-between stretch" class="mt-2">
<button fxFlex="48" fxLayoutAlign="center center" mat-stroked-button color="primary" tabindex="2" type="reset" (click)="resetData()">Clear Field</button>
<button fxFlex="48" fxLayoutAlign="center center" mat-raised-button color="primary" type="submit" tabindex="3" (click)="onConnectPeer()">Add Peer</button>
</div>
</form>
<div fxLayout="column">
<div fxLayout="column" fxLayout.gt-xs="row" fxLayoutAlign.gt-xs="start center" fxLayoutAlign="start stretch" class="padding-gap-x page-sub-title-container mt-2">
<div fxFlex="70">
<fa-icon [icon]="faUsers" class="page-title-img mr-1"></fa-icon>
<span class="page-title">Connected Peers</span>
</div>
<mat-form-field fxFlex="30">
<div fxLayout="row" fxLayoutAlign="start start">
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
</div>
</mat-form-field>
</div>
<div perfectScrollbar fxLayout="row" fxLayoutAlign="start center" fxFlex="100" class="table-container">
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
<table mat-table #table [dataSource]="peers" matSort [ngClass]="{'overflow-auto error-border': flgLoading[0]==='error','overflow-auto': true}">
<ng-container matColumnDef="alias">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Alias </th>
<td mat-cell *matCellDef="let peer" [ngStyle]="{'max-width': (screenSize === screenSizeEnum.XS) ? '5rem' : '10rem'}"> {{peer?.alias}} </td>
</ng-container>
<ng-container matColumnDef="pub_key">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Public Key </th>
<td mat-cell *matCellDef="let peer" [ngStyle]="{'max-width': (screenSize === screenSizeEnum.XS) ? '5rem' : '20rem'}"> {{peer?.pub_key}} </td>
</ng-container>
<ng-container matColumnDef="sat_sent">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">
Sats Sent </th>
<td mat-cell *matCellDef="let peer"><span fxLayoutAlign="end center"> {{peer?.sat_sent | number}} </span></td>
</ng-container>
<ng-container matColumnDef="sat_recv">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">
Sats Received </th>
<td mat-cell *matCellDef="let peer"><span fxLayoutAlign="end center"> {{peer?.sat_recv | number}} </span></td>
</ng-container>
<ng-container matColumnDef="ping_time">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Ping </th>
<td mat-cell *matCellDef="let peer"><span fxLayoutAlign="end center"> {{peer?.ping_time | number}} </span>
</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef class="px-3"><span fxLayoutAlign="end center">Actions</span></th>
<td mat-cell *matCellDef="let peer" fxLayoutAlign="end center" class="pl-3">
<div fxFlex="100" class="bordered-box table-actions-select" fxLayoutAlign="center center">
<mat-select placeholder="Actions" tabindex="1" class="mr-0">
<mat-select-trigger></mat-select-trigger>
<mat-option (click)="onPeerClick(peer, $event)">View Info</mat-option>
<mat-option (click)="onOpenChannel(peer)">Open Channel</mat-option>
<mat-option (click)="onPeerDetach(peer)">Disconnect</mat-option>
</mat-select>
</div>
</td>
</ng-container>
<ng-container matColumnDef="no_peer">
<td mat-footer-cell *matFooterCellDef colspan="4">
<p *ngIf="!peers.data || peers.data.length<1">No connected peers.</p>
</td>
</ng-container>
<tr mat-footer-row *matFooterRowDef="['no_peer']" [ngClass]="{'display-none': peers.data && peers.data.length>1}"></tr>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: flgSticky;"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [@newlyAddedRowAnimation]="(row.pub_key === newlyAddedPeer && flgAnimate) ? 'added' : 'notAdded'"></tr>
</table>
</div>
<mat-paginator [pageSize]="pageSize" [pageSizeOptions]="pageSizeOptions" [showFirstLastButtons]="screenSize === screenSizeEnum.XS ? false : true" class="mb-4"></mat-paginator>
</div>
</div>

@ -0,0 +1,18 @@
.mat-column-alias {
flex: 1 1 10%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.mat-column-pub_key {
flex: 1 1 20%;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
padding-left: 2rem;
}
.mat-column-ping_time {
padding-right: 2rem;
}

@ -0,0 +1,172 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil, filter, take } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { Actions } from '@ngrx/effects';
import { faUsers } from '@fortawesome/free-solid-svg-icons';
import { MatTableDataSource, MatSort, MatPaginator, MatPaginatorIntl } from '@angular/material';
import { PeerCL, GetInfoCL } from '../../../shared/models/clModels';
import { PAGE_SIZE, PAGE_SIZE_OPTIONS, getPaginatorLabel, AlertTypeEnum, DataTypeEnum, ScreenSizeEnum } from '../../../shared/services/consts-enums-functions';
import { LoggerService } from '../../../shared/services/logger.service';
import { CommonService } from '../../../shared/services/common.service';
import { OpenChannelComponent } from '../../../shared/components/data-modal/open-channel/open-channel.component';
import { newlyAddedRowAnimation } from '../../../shared/animation/row-animation';
import { CLEffects } from '../../store/cl.effects';
import { RTLEffects } from '../../../store/rtl.effects';
import * as RTLActions from '../../../store/rtl.actions';
import * as fromRTLReducer from '../../../store/rtl.reducers';
@Component({
selector: 'rtl-cl-peers',
templateUrl: './peers.component.html',
styleUrls: ['./peers.component.scss'],
animations: [newlyAddedRowAnimation],
providers: [
{ provide: MatPaginatorIntl, useValue: getPaginatorLabel('Peers') },
]
})
export class CLPeersComponent implements OnInit, OnDestroy {
@ViewChild(MatSort, { static: true }) sort: MatSort;
@ViewChild(MatPaginator, {static: true}) paginator: MatPaginator;
public faUsers = faUsers;
public newlyAddedPeer = '';
public flgAnimate = true;
public displayedColumns = [];
public peerAddress = '';
public peers: any;
public information: GetInfoCL = {};
public availableBalance = 0;
public flgLoading: Array<Boolean | 'error'> = [true]; // 0: peers
public flgSticky = false;
public pageSize = PAGE_SIZE;
public pageSizeOptions = PAGE_SIZE_OPTIONS;
public screenSize = '';
public screenSizeEnum = ScreenSizeEnum;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private rtlEffects: RTLEffects, private clEffects: CLEffects, private actions$: Actions, private commonService: CommonService) {
this.screenSize = this.commonService.getScreenSize();
if(this.screenSize === ScreenSizeEnum.XS) {
this.flgSticky = false;
this.displayedColumns = [ 'alias', 'actions'];
} else if(this.screenSize === ScreenSizeEnum.SM) {
this.flgSticky = false;
this.displayedColumns = [ 'alias', 'sat_sent', 'sat_recv', 'actions'];
} else if(this.screenSize === ScreenSizeEnum.MD) {
this.flgSticky = false;
this.displayedColumns = ['alias', 'sat_sent', 'sat_recv', 'ping_time', 'actions'];
} else {
this.flgSticky = true;
this.displayedColumns = ['alias', 'pub_key', 'sat_sent', 'sat_recv', 'ping_time', 'actions'];
}
}
ngOnInit() {
this.store.select('cl')
.pipe(takeUntil(this.unSubs[0]))
.subscribe((rtlStore) => {
rtlStore.effectErrorsCl.forEach(effectsErr => {
if (effectsErr.action === 'FetchPeersCL') {
this.flgLoading[0] = 'error';
}
});
this.information = rtlStore.information;
this.availableBalance = rtlStore.balance.totalBalance || 0;
this.peers = new MatTableDataSource([]);
this.peers.data = [];
if (undefined !== rtlStore.peers) {
this.peers = new MatTableDataSource<PeerCL>([...rtlStore.peers]);
this.peers.data = rtlStore.peers;
setTimeout(() => { this.flgAnimate = false; }, 3000);
}
this.peers.sort = this.sort;
this.peers.paginator = this.paginator;
if (this.flgLoading[0] !== 'error') {
this.flgLoading[0] = false;
}
this.logger.info(rtlStore);
});
this.actions$
.pipe(
takeUntil(this.unSubs[1]),
filter((action) => action.type === RTLActions.SET_PEERS_CL)
).subscribe((setPeers: RTLActions.SetPeersCL) => {
this.peerAddress = undefined;
this.flgAnimate = true;
});
}
onConnectPeer() {
this.flgAnimate = true;
this.newlyAddedPeer = this.peerAddress;
this.store.dispatch(new RTLActions.OpenSpinner('Adding Peer...'));
this.store.dispatch(new RTLActions.SaveNewPeerCL({id: this.peerAddress}));
}
onPeerClick(selPeer: PeerCL, event: any) {
const reorderedPeer = [
[{key: 'id', value: selPeer.id, title: 'Public Key', width: 100}],
[{key: 'netaddr', value: selPeer.netaddr, title: 'Address', width: 100}],
[{key: 'alias', value: selPeer.alias, title: 'Alias', width: 50}, {key: 'connected', value: selPeer.connected ? 'True' : 'False', title: 'Connected', width: 50}],
[{key: 'globalfeatures', value: selPeer.globalfeatures, title: 'Global Features', width: 100}],
[{key: 'localfeatures', value: selPeer.localfeatures, title: 'Local Features', width: 100}]
];
this.store.dispatch(new RTLActions.OpenAlert({ data: {
type: AlertTypeEnum.INFORMATION,
alertTitle: 'Peer Information',
showQRName: 'Public Key',
showQRField: selPeer.id,
message: reorderedPeer
}}));
}
resetData() {
this.peerAddress = '';
}
onOpenChannel(peerToAddChannel: PeerCL) {
const peerToAddChannelMessage = {
peer: peerToAddChannel,
information: this.information,
balance: this.availableBalance
};
this.store.dispatch(new RTLActions.OpenAlert({ data: {
alertTitle: 'Open Channel',
message: peerToAddChannelMessage,
newlyAdded: false,
component: OpenChannelComponent
}}));
}
onPeerDetach(peerToDetach: PeerCL) {
const msg = 'Disconnect peer: ' + ((peerToDetach.alias) ? peerToDetach.alias : peerToDetach.id);
this.store.dispatch(new RTLActions.OpenConfirmation({ data: {
type: AlertTypeEnum.CONFIRM,
alertTitle: 'Disconnect Peer',
titleMessage: msg,
noBtnText: 'Cancel',
yesBtnText: 'Disconnect'
}}));
this.rtlEffects.closeConfirm
.pipe(takeUntil(this.unSubs[3]))
.subscribe(confirmRes => {
if (confirmRes) {
this.store.dispatch(new RTLActions.OpenSpinner('Disconnecting Peer...'));
this.store.dispatch(new RTLActions.DetachPeerCL({id: peerToDetach.id, force: false}));
}
});
}
applyFilter(selFilter: string) {
this.peers.filter = selFilter;
}
ngOnDestroy() {
this.unSubs.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

@ -1,81 +0,0 @@
<div fxLayout="column">
<div class="padding-gap">
<mat-card class="mat-card-original">
<mat-card-header>
<mat-card-subtitle>
<h2>Connect Peer</h2>
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<form fxLayout="column" fxLayout.gt-sm="row wrap" (ngSubmit)="connectPeerForm.form.valid && onConnectPeer()" #connectPeerForm="ngForm">
<mat-form-field fxFlex="70" fxLayoutAlign="start end">
<input matInput placeholder="Lightning Address (pubkey OR pubkey@ip:port)" name="peerAddress" [(ngModel)]="peerAddress" tabindex="1" required #peerAdd="ngModel">
</mat-form-field>
<div fxFlex="15" fxLayoutAlign="start start">
<button fxFlex="90" fxLayoutAlign="center center" mat-raised-button color="primary" [disabled]="peerAdd.invalid" type="submit" tabindex="2">
<p *ngIf="peerAdd.invalid && (peerAdd.dirty || peerAdd.touched); else connectText">Invalid Address</p>
<ng-template #connectText><p>Connect</p></ng-template>
</button>
</div>
<div fxFlex="15" fxLayoutAlign="start start">
<button fxFlex="90" fxLayoutAlign="center center" mat-stroked-button color="primary" tabindex="2" type="reset" (click)="resetData()">Clear</button>
</div>
</form>
</mat-card-content>
</mat-card>
</div>
<div class="padding-gap">
<mat-card class="mat-card-original">
<mat-card-content class="table-card-content">
<div fxLayout="column" fxLayout.gt-xs="row" fxLayoutAlign.gt-xs="start center" fxLayoutAlign="start stretch">
<mat-form-field fxFlex="30">
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
</mat-form-field>
</div>
<div perfectScrollbar class="table-container">
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
<table mat-table #table [dataSource]="peers" matSort [ngClass]="{'overflow-auto error-border': flgLoading[0]==='error','overflow-auto': true}">
<ng-container matColumnDef="detach">
<th mat-header-cell *matHeaderCellDef>Detach</th>
<td mat-cell *matCellDef="let peer"><mat-icon color="accent" (click)="onPeerDetach(peer)">link_off</mat-icon></td>
</ng-container>
<ng-container matColumnDef="open_channel">
<th mat-header-cell *matHeaderCellDef>Open Channel</th>
<td mat-cell *matCellDef="let peer"><mat-icon color="accent" (click)="onOpenChannel(peer)">playlist_add</mat-icon></td>
</ng-container>
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef mat-sort-header> ID </th>
<td mat-cell *matCellDef="let peer">
<div> {{peer?.id | slice:0:10}}... </div>
</td>
</ng-container>
<ng-container matColumnDef="alias">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Alias </th>
<td mat-cell *matCellDef="let peer"> {{peer?.alias}} </td>
</ng-container>
<ng-container matColumnDef="connected">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Connected </th>
<td mat-cell *matCellDef="let peer"> {{peer?.connected}} </td>
</ng-container>
<ng-container matColumnDef="netaddr">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Network Address </th>
<td mat-cell *matCellDef="let peer">
<span *ngFor="let addr of peer?.netaddr; last as isLast">{{ addr}}<span *ngIf="!isLast">,<br></span></span>
</td>
</ng-container>
<ng-container matColumnDef="globalfeatures">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Global Features </th>
<td mat-cell *matCellDef="let peer"> {{peer?.globalfeatures}} </td>
</ng-container>
<ng-container matColumnDef="localfeatures">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Local Features </th>
<td mat-cell *matCellDef="let peer"> {{peer?.localfeatures}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: flgSticky;"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [@newlyAddedRowAnimation]="(row.id === newlyAddedPeer && flgAnimate) ? 'added' : 'notAdded'" (click)="onPeerClick(row, $event)"></tr>
</table>
</div>
</mat-card-content>
</mat-card>
</div>
</div>

@ -1,17 +0,0 @@
.mat-column-detach {
flex: 0 0 5%;
min-width: 50px;
}
.mat-column-alias, .mat-column-address {
flex: 0 0 15%;
min-width: 100px;
}
.mat-cell.mat-column-detach {
cursor: pointer;
}
table {
width:100%;
}

@ -1,150 +0,0 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { Subject } from 'rxjs';
import { takeUntil, filter } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { Actions } from '@ngrx/effects';
import { MatTableDataSource, MatSort } from '@angular/material';
import { PeerCL, GetInfoCL } from '../../shared/models/clModels';
import { DialogConfig } from '../../shared/models/alertData';
import { AlertTypeEnum, DataTypeEnum } from '../../shared/services/consts-enums-functions';
import { LoggerService } from '../../shared/services/logger.service';
import { newlyAddedRowAnimation } from '../../shared/animation/row-animation';
import { CLEffects } from '../store/cl.effects';
import { RTLEffects } from '../../store/rtl.effects';
import * as RTLActions from '../../store/rtl.actions';
import * as fromRTLReducer from '../../store/rtl.reducers';
@Component({
selector: 'rtl-cl-peers',
templateUrl: './peers.component.html',
styleUrls: ['./peers.component.scss'],
animations: [newlyAddedRowAnimation]
})
export class CLPeersComponent implements OnInit, OnDestroy {
@ViewChild(MatSort, { static: true }) sort: MatSort;
public newlyAddedPeer = '';
public flgAnimate = true;
public displayedColumns = [];
public peerAddress = '';
public peers: any;
public information: GetInfoCL = {};
public flgLoading: Array<Boolean | 'error'> = [true]; // 0: peers
public flgSticky = false;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private rtlEffects: RTLEffects, private clEffects: CLEffects, private actions$: Actions, private router: Router) {
switch (true) {
case (window.innerWidth <= 415):
this.displayedColumns = ['detach', 'id', 'alias', 'connected'];
break;
case (window.innerWidth > 415 && window.innerWidth <= 730):
this.displayedColumns = ['detach', 'id', 'alias', 'connected', 'netaddr'];
break;
case (window.innerWidth > 730 && window.innerWidth <= 1024):
this.displayedColumns = ['detach', 'open_channel', 'id', 'alias', 'connected', 'netaddr', 'globalfeatures', 'localfeatures'];
break;
case (window.innerWidth > 1024 && window.innerWidth <= 1280):
this.flgSticky = true;
this.displayedColumns = ['detach', 'open_channel', 'id', 'alias', 'connected', 'netaddr', 'globalfeatures', 'localfeatures'];
break;
default:
this.flgSticky = true;
this.displayedColumns = ['detach', 'open_channel', 'id', 'alias', 'connected', 'netaddr', 'globalfeatures', 'localfeatures'];
break;
}
}
ngOnInit() {
this.store.select('cl')
.pipe(takeUntil(this.unSubs[0]))
.subscribe((rtlStore) => {
rtlStore.effectErrorsCl.forEach(effectsErr => {
if (effectsErr.action === 'FetchPeersCL') {
this.flgLoading[0] = 'error';
}
});
this.information = rtlStore.information;
this.peers = new MatTableDataSource([]);
this.peers.data = [];
if (undefined !== rtlStore.peers) {
this.peers = new MatTableDataSource<PeerCL>([...rtlStore.peers]);
this.peers.data = rtlStore.peers;
setTimeout(() => { this.flgAnimate = false; }, 3000);
}
this.peers.sort = this.sort;
if (this.flgLoading[0] !== 'error') {
this.flgLoading[0] = false;
}
this.logger.info(rtlStore);
});
this.actions$
.pipe(
takeUntil(this.unSubs[1]),
filter((action) => action.type === RTLActions.SET_PEERS_CL)
).subscribe((setPeers: RTLActions.SetPeersCL) => {
this.peerAddress = undefined;
});
}
onConnectPeer() {
this.flgAnimate = true;
this.newlyAddedPeer = this.peerAddress;
this.store.dispatch(new RTLActions.OpenSpinner('Adding Peer...'));
this.store.dispatch(new RTLActions.SaveNewPeerCL({id: this.peerAddress}));
}
onPeerClick(selRow: PeerCL, event: any) {
const flgCloseClicked = event.target.className.includes('mat-column-detach') || event.target.className.includes('mat-icon');
if (flgCloseClicked) {
return;
}
const selPeer = this.peers.data.filter(peer => {
return peer.id === selRow.id;
})[0];
const reorderedPeer = [
[{key: 'status', value: selPeer.status, title: 'Status', width: 100, type: DataTypeEnum.NUMBER}]
// 'id', 'alias', 'connected', 'netaddr', 'globalfeatures', 'localfeatures'
];
this.store.dispatch(new RTLActions.OpenAlert({ data: {
type: AlertTypeEnum.INFORMATION,
alertTitle: 'Peer Information',
message: reorderedPeer
}}));
}
resetData() {
this.peerAddress = '';
}
onOpenChannel(peerToAddChannel: PeerCL) {
this.router.navigate(['cl/chnlmanage'], { state: { peer: peerToAddChannel.id }});
}
onPeerDetach(peerToDetach: PeerCL) {
const msg = 'Detach peer: ' + peerToDetach.id;
this.store.dispatch(new RTLActions.OpenConfirmation({ data: { type: AlertTypeEnum.CONFIRM, alertTitle: 'Confirm Disconnect Peer', titleMessage: msg, noBtnText: 'Cancel', yesBtnText: 'Detach'}}));
this.rtlEffects.closeConfirm
.pipe(takeUntil(this.unSubs[3]))
.subscribe(confirmRes => {
if (confirmRes) {
this.store.dispatch(new RTLActions.OpenSpinner('Detaching Peer...'));
this.store.dispatch(new RTLActions.DetachPeerCL({id: peerToDetach.id, force: false}));
}
});
}
applyFilter(selFilter: string) {
this.peers.filter = selFilter;
}
ngOnDestroy() {
this.unSubs.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

@ -0,0 +1,51 @@
<div fxLayout="column" fxLayoutAlign="start stretch">
<div fxLayout="column" fxLayout.gt-xs="row" fxLayoutAlign.gt-xs="start center" fxLayoutAlign="start stretch" class="padding-gap-x page-sub-title-container">
<div fxFlex="70"></div>
<mat-form-field fxFlex="30">
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
</mat-form-field>
</div>
<div perfectScrollbar fxLayout="column" fxLayoutAlign="start center" fxFlex="100" class="table-container">
<table mat-table #table [dataSource]="forwardingHistoryEvents" fxFlex="100" matSort class="overflow-auto">
<ng-container matColumnDef="timestamp">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Timestamp</th>
<td mat-cell *matCellDef="let fhEvent">{{fhEvent.timestamp_str}}</td>
</ng-container>
<ng-container matColumnDef="chan_id_in">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Inbound Channel ID</th>
<td mat-cell *matCellDef="let fhEvent">{{fhEvent.chan_id_in}}</td>
</ng-container>
<ng-container matColumnDef="chan_id_out">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Outbound Channel ID</th>
<td mat-cell *matCellDef="let fhEvent">{{fhEvent.chan_id_out}}</td>
</ng-container>
<ng-container matColumnDef="amt_in">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Inbound Amount (Sats)</th>
<td mat-cell *matCellDef="let fhEvent"><span fxLayoutAlign="end center">{{fhEvent.amt_in | number}}</span></td>
</ng-container>
<ng-container matColumnDef="amt_out">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Outbound Amount (Sats)</th>
<td mat-cell *matCellDef="let fhEvent"><span fxLayoutAlign="end center">{{fhEvent.amt_out | number}}</span></td>
</ng-container>
<ng-container matColumnDef="fee_msat">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Fee (mSats)</th>
<td mat-cell *matCellDef="let fhEvent"><span fxLayoutAlign="end center">{{fhEvent.fee_msat | number}}</span></td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef class="px-3"><span fxLayoutAlign="end center">Actions</span></th>
<td mat-cell *matCellDef="let fhEvent" class="pl-3">
<button mat-stroked-button color="primary" type="button" tabindex="4" (click)="onForwardingEventClick(fhEvent,$event)">View Info</button>
</td>
</ng-container>
<ng-container matColumnDef="no_event">
<td mat-footer-cell *matFooterCellDef colspan="4">
<p *ngIf="!forwardingHistoryEvents.data || forwardingHistoryEvents.data.length<1">No forwarding event available.</p>
</td>
</ng-container>
<tr mat-footer-row *matFooterRowDef="['no_event']" [ngClass]="{'display-none': forwardingHistoryEvents.data && forwardingHistoryEvents.data.length>0}"></tr>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: flgSticky;"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
</table>
</div>
<mat-paginator [pageSize]="pageSize" [pageSizeOptions]="pageSizeOptions" [showFirstLastButtons]="screenSize === screenSizeEnum.XS ? false : true" class="mb-4"></mat-paginator>
</div>

@ -0,0 +1,4 @@
.mat-column-actions {
flex: 0 0 5%;
width: 5%;
}

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save