Release 0.7.1 (#340)
* Peers And Channels Redesign (#294) * Send payment without confirmation * Removed vulnerabilities & added missing 2FAuth button * Invoice Redesign with invoice info modal opening * Firefox Payment Failure Issue #283 (#309) * On chain redesign (#315) * Bug fix #312 Bogus warning about unsecure connection (#316) * Peer Channels Redesign #290 (#317) * CLT transactions redesign #290 (#319) * CLT On-chain redesign #290 (#320) * Wallet error alert bug fix #298 & #299 (#321) * Configurable server host #303 (#322) * Receive tab on top #292 (#323) * Settings Layout Cleanup (#325) * Alias Autocomplete (#326) * Redirect to Dashboard after first RTL login * Bug fix Channel Uptime wrong #284 (#329) * Channel info redesigned Issue #328 (#331) * Fixed Transaction Info Scroll #324 (#333) * Remove macaroon info before logging #306 * Added Peer Alias for Closed And Pending Channels Tabs #275 & #285 (#336) * Show multiple URIs on show pubkey modal #337 (#338)pull/351/head
parent
685372fc5b
commit
a980d9c3fa
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
!function(e){function r(r){for(var n,a,i=r[0],c=r[1],f=r[2],p=0,s=[];p<i.length;p++)a=i[p],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(l&&l(r);s.length;)s.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++)0!==o[t[i]]&&(n=!1);n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={0:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,i=document.createElement("script");i.charset="utf-8",i.timeout=120,a.nc&&i.setAttribute("nonce",a.nc),i.src=function(e){return a.p+""+({}[e]||e)+"."+{1:"55621ac84bdf6c786e25",6:"3228fcaf0890e1d596e3",7:"6fecb498f36cf3efcace"}[e]+".js"}(e);var c=new Error;u=function(r){i.onerror=i.onload=null,clearTimeout(f);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var f=setTimeout((function(){u({type:"timeout",target:i})}),12e4);i.onerror=i.onload=u,document.head.appendChild(i)}return Promise.all(r)},a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,(function(r){return e[r]}).bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="",a.oe=function(e){throw console.error(e),e};var i=window.webpackJsonp=window.webpackJsonp||[],c=i.push.bind(i);i.push=r,i=i.slice();for(var f=0;f<i.length;f++)r(i[f]);var l=c;t()}([]);
|
||||
!function(e){function r(r){for(var n,a,i=r[0],c=r[1],f=r[2],p=0,s=[];p<i.length;p++)a=i[p],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(l&&l(r);s.length;)s.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++)0!==o[t[i]]&&(n=!1);n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={0:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,i=document.createElement("script");i.charset="utf-8",i.timeout=120,a.nc&&i.setAttribute("nonce",a.nc),i.src=function(e){return a.p+""+({}[e]||e)+"."+{1:"9f5a5551675b6e3c39b6",6:"23ff3e765459f2da33da",7:"417208cf5491f2b427d2"}[e]+".js"}(e);var c=new Error;u=function(r){i.onerror=i.onload=null,clearTimeout(f);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var f=setTimeout((function(){u({type:"timeout",target:i})}),12e4);i.onerror=i.onload=u,document.head.appendChild(i)}return Promise.all(r)},a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,(function(r){return e[r]}).bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="",a.oe=function(e){throw console.error(e),e};var i=window.webpackJsonp=window.webpackJsonp||[],c=i.push.bind(i);i.push=r,i=i.slice();for(var f=0;f<i.length;f++)r(i[f]);var l=c;t()}([]);
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -0,0 +1,53 @@
|
||||
<div fxLayout="row">
|
||||
<div fxFlex="100" class="padding-gap-large">
|
||||
<mat-card-header fxLayout="row" fxLayoutAlign="space-between center" class="modal-info-header">
|
||||
<div fxFlex="95" fxLayoutAlign="start start">
|
||||
<span class="page-title">Send Payment</span>
|
||||
</div>
|
||||
<button tabindex="8" fxFlex="5" fxLayoutAlign="center" class="btn-close-x p-0" [mat-dialog-close]="false" default mat-button>X</button>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="mt-5px">
|
||||
<form fxLayout="row wrap" fxFlex="100" fxLayoutAlign="space-between start" class="padding-gap overflow-x-hidden" (submit)="onSendFunds()" (reset)="resetData()" #form="ngForm">
|
||||
<mat-form-field fxFlex="55">
|
||||
<input matInput autoFocus [(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="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="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>
|
||||
<div fxFlex="60" fxLayoutAlign="space-between stretch" fxLayout="row wrap">
|
||||
<mat-form-field fxFlex="48" fxLayoutAlign="start end">
|
||||
<mat-select tabindex="6" placeholder="Fee Rate" [(value)]="transaction.feeRate" [disabled]="flgMinConf">
|
||||
<mat-option *ngFor="let feeRateType of feeRateTypes" [value]="feeRateType.feeRateId">
|
||||
{{feeRateType.feeRateType}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<div fxFlex="48" fxLayout="row" fxLayoutAlign="start center">
|
||||
<mat-checkbox fxFlex="2" tabindex="7" color="primary" [(ngModel)]="flgMinConf" (change)="transaction.feeRate=null" name="flgMinConf" fxLayoutAlign="stretch start" class="mr-2"></mat-checkbox>
|
||||
<mat-form-field fxFlex="98">
|
||||
<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-error *ngIf="flgMinConf && !transaction.minconf">Min Confirmation Blocks is required.</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<div fxLayout="column" fxFlex="100" fxLayoutAlign="start stretch"></div>
|
||||
<div fxFlex="100" class="alert alert-danger mt-1" *ngIf="sendFundError !== ''">
|
||||
<fa-icon [icon]="faExclamationTriangle" class="mr-1 alert-icon"></fa-icon>
|
||||
<span *ngIf="sendFundError !== ''">{{sendFundError}}</span>
|
||||
</div>
|
||||
<div fxLayout="row" fxFlex="100" fxLayoutAlign="end center">
|
||||
<button class="mr-1" mat-stroked-button color="primary" tabindex="7" type="reset">Clear Fields</button>
|
||||
<button mat-flat-button color="primary" type="submit" tabindex="8">Send Funds</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
</div>
|
||||
</div>
|
@ -1,37 +0,0 @@
|
||||
<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" (submit)="onSendFunds()" (reset)="resetData()" #form="ngForm">
|
||||
<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>
|
||||
<div fxFlex="60" fxLayout="column" fxLayoutAlign="space-between stretch" fxLayout.gt-sm="row wrap">
|
||||
<mat-form-field fxFlex="48" 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>
|
||||
<div fxFlex="48" fxLayout="row" fxLayoutAlign="start center">
|
||||
<mat-checkbox fxFlex="2" tabindex="7" color="primary" [(ngModel)]="flgMinConf" name="flgMinConf" fxLayoutAlign="stretch start" class="mr-2"></mat-checkbox>
|
||||
<mat-form-field fxFlex="98">
|
||||
<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-error *ngIf="flgMinConf && !transaction.minconf">Min Confirmation Blocks is required.</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<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">Clear Fields</button>
|
||||
<button fxFlex="48" fxLayoutAlign="center center" mat-flat-button color="primary" type="submit" tabindex="8">Send Funds</button>
|
||||
</div>
|
||||
</form>
|
@ -0,0 +1,95 @@
|
||||
<div fxLayout="column" fxLayout.gt-sm="row" fxLayoutAlign="space-between stretch">
|
||||
<div fxFlex="100" class="padding-gap-large pl-3">
|
||||
<mat-card-header fxLayout="row" fxLayoutAlign="space-between center" class="modal-info-header mb-1">
|
||||
<div fxFlex="95" fxLayoutAlign="start start">
|
||||
<fa-icon [icon]="faReceipt" class="page-title-img mr-1"></fa-icon>
|
||||
<span class="page-title">Channel Information</span>
|
||||
</div>
|
||||
<button tabindex="3" fxFlex="5" fxLayoutAlign="center" class="btn-close-x p-0" (click)="onClose()" mat-button>X</button>
|
||||
</mat-card-header>
|
||||
<mat-card-content [ngClass]="{'xs-scroll-y': screenSize === screenSizeEnum.XS}">
|
||||
<div fxLayout="column">
|
||||
<div fxLayout="row">
|
||||
<div fxFlex="50">
|
||||
<h4 fxLayoutAlign="start" class="font-bold-500">Short Channel ID</h4>
|
||||
<span class="foreground-secondary-text">{{channel.short_channel_id}}</span>
|
||||
</div>
|
||||
<div fxFlex="50">
|
||||
<h4 fxLayoutAlign="start" class="font-bold-500">Peer Alias</h4>
|
||||
<span class="foreground-secondary-text">{{channel.alias}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider [inset]="true" class="my-1"></mat-divider>
|
||||
<div fxLayout="row">
|
||||
<div fxFlex="100">
|
||||
<h4 fxLayoutAlign="start" class="font-bold-500">Channel ID</h4>
|
||||
<span class="foreground-secondary-text">{{channel.channel_id}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider [inset]="true" class="my-1"></mat-divider>
|
||||
<div fxLayout="row">
|
||||
<div fxFlex="100">
|
||||
<h4 fxLayoutAlign="start" class="font-bold-500">Peer Public Key</h4>
|
||||
<span class="foreground-secondary-text">{{channel.id}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider [inset]="true" class="my-1"></mat-divider>
|
||||
<div fxLayout="row">
|
||||
<div fxFlex="25">
|
||||
<h4 fxLayoutAlign="start" class="font-bold-500">mSatoshi to Us</h4>
|
||||
<span class="overflow-wrap foreground-secondary-text">{{channel.msatoshi_to_us | number}}</span>
|
||||
</div>
|
||||
<div fxFlex="25">
|
||||
<h4 fxLayoutAlign="start" class="font-bold-500">Spendable (mSats)</h4>
|
||||
<span class="overflow-wrap foreground-secondary-text">{{channel.spendable_msatoshi | number}}</span>
|
||||
</div>
|
||||
<div fxFlex="25">
|
||||
<h4 fxLayoutAlign="start" class="font-bold-500">Total (mSats)</h4>
|
||||
<span class="overflow-wrap foreground-secondary-text">{{channel.msatoshi_total | number}}</span>
|
||||
</div>
|
||||
<div fxFlex="25">
|
||||
<h4 fxLayoutAlign="start" class="font-bold-500">State</h4>
|
||||
<span class="overflow-wrap foreground-secondary-text">{{channel.state}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider [inset]="true" class="my-1"></mat-divider>
|
||||
<div fxLayout="row">
|
||||
<div fxFlex="25">
|
||||
<h4 fxLayoutAlign="start" class="font-bold-500">Our Reserve (Sats)</h4>
|
||||
<span class="overflow-wrap foreground-secondary-text">{{channel.our_channel_reserve_satoshis | number}}</span>
|
||||
</div>
|
||||
<div fxFlex="25">
|
||||
<h4 fxLayoutAlign="start" class="font-bold-500">Their Reserve (Sats)</h4>
|
||||
<span class="overflow-wrap foreground-secondary-text">{{channel.their_channel_reserve_satoshis | number}}</span>
|
||||
</div>
|
||||
<div fxFlex="25">
|
||||
<h4 fxLayoutAlign="start" class="font-bold-500">Connected</h4>
|
||||
<span class="overflow-wrap foreground-secondary-text">{{channel.connected ? 'Yes' : 'No'}}</span>
|
||||
</div>
|
||||
<div fxFlex="25">
|
||||
<h4 fxLayoutAlign="start" class="font-bold-500">Private</h4>
|
||||
<span class="overflow-wrap foreground-secondary-text">{{channel.private ? 'Yes' : 'No'}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="showAdvanced">
|
||||
<mat-divider [inset]="true" class="my-1"></mat-divider>
|
||||
<div fxLayout="row">
|
||||
<div fxFlex="100">
|
||||
<h4 fxLayoutAlign="start" class="font-bold-500">Funding Transaction Id</h4>
|
||||
<span class="foreground-secondary-text">{{channel.funding_txid}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider [inset]="true" class="my-1"></mat-divider>
|
||||
</div>
|
||||
<div [ngClass]="{'mt-2': !showAdvanced, 'mt-1': showAdvanced}" fxLayout="row" fxLayoutAlign="end center" fxFlex="100">
|
||||
<button mat-stroked-button color="primary" type="reset" (click)="onShowAdvanced()" tabindex="1" class="mr-1">
|
||||
<p *ngIf="!showAdvanced; else hideAdvancedText">Show Advanced</p>
|
||||
<ng-template #hideAdvancedText><p>Hide Advanced</p></ng-template>
|
||||
</button>
|
||||
<button *ngIf="showCopy" autoFocus mat-flat-button color="primary" tabindex="2" type="submit" rtlClipboard [payload]="channel.short_channel_id" (copied)="onCopyChanID($event)">Copy Short Channel ID</button>
|
||||
<button *ngIf="!showCopy" autoFocus mat-flat-button color="primary" tabindex="2" type="button" (click)="onClose()">OK</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CLChannelInformationComponent } from './channel-information.component';
|
||||
|
||||
describe('CLChannelInformationComponent', () => {
|
||||
let component: CLChannelInformationComponent;
|
||||
let fixture: ComponentFixture<CLChannelInformationComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ CLChannelInformationComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(CLChannelInformationComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,46 @@
|
||||
import { Component, OnInit, Inject } from '@angular/core';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
|
||||
import { faReceipt } from '@fortawesome/free-solid-svg-icons';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
|
||||
import { LoggerService } from '../../../../shared/services/logger.service';
|
||||
import { CommonService } from '../../../../shared/services/common.service';
|
||||
import { CLChannelInformation } from '../../../../shared/models/alertData';
|
||||
import { ChannelCL } from '../../../../shared/models/clModels';
|
||||
import { ScreenSizeEnum } from '../../../../shared/services/consts-enums-functions';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-cl-channel-information',
|
||||
templateUrl: './channel-information.component.html',
|
||||
styleUrls: ['./channel-information.component.scss']
|
||||
})
|
||||
export class CLChannelInformationComponent implements OnInit {
|
||||
public faReceipt = faReceipt;
|
||||
public showAdvanced = false;
|
||||
public showCopy = true;
|
||||
public showCopyField = null;
|
||||
public channel: ChannelCL;
|
||||
public screenSize = '';
|
||||
public screenSizeEnum = ScreenSizeEnum;
|
||||
|
||||
constructor(public dialogRef: MatDialogRef<CLChannelInformationComponent>, @Inject(MAT_DIALOG_DATA) public data: CLChannelInformation, private logger: LoggerService, private commonService: CommonService, private snackBar: MatSnackBar) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.channel = this.data.channel;
|
||||
this.showCopy = this.data.showCopy;
|
||||
this.screenSize = this.commonService.getScreenSize();
|
||||
}
|
||||
|
||||
onClose() {
|
||||
this.dialogRef.close(false);
|
||||
}
|
||||
|
||||
onShowAdvanced() {
|
||||
this.showAdvanced = !this.showAdvanced;
|
||||
}
|
||||
|
||||
onCopyChanID(payload: string) {
|
||||
this.snackBar.open('Short channel ID ' + payload + ' copied.');
|
||||
this.logger.info('Copied Text: ' + payload);
|
||||
}
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="padding-gap">
|
||||
<form fxLayout="column" fxLayoutAlign="space-between stretch" fxLayout.gt-sm="row wrap" #form="ngForm">
|
||||
<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.id">
|
||||
{{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" max="{{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?.max">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="button" (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="60" fxLayout="column" fxLayoutAlign="space-between stretch" fxLayout.gt-sm="row wrap">
|
||||
<mat-form-field fxFlex="48" 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>
|
||||
<div fxFlex="48" fxLayout="row" fxLayoutAlign="start center">
|
||||
<mat-checkbox fxFlex="2" tabindex="5" color="primary" [(ngModel)]="flgMinConf" name="flgMinConf" fxLayoutAlign="stretch start" class="mr-2"></mat-checkbox>
|
||||
<mat-form-field fxFlex="98">
|
||||
<input matInput [(ngModel)]="minConfValue" placeholder="Min Confirmation Blocks" type="number" name="blocks" step="1" min="0" tabindex="8" #blocks="ngModel" [required]="flgMinConf" [disabled]="!flgMinConf">
|
||||
<mat-error *ngIf="flgMinConf && !minConfValue">Min Confirmation Blocks is required.</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="showAdvanced" 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="10" type="reset" (click)="resetData()">Clear Field</button>
|
||||
<button fxFlex="48" fxLayoutAlign="center center" mat-flat-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,67 +0,0 @@
|
||||
.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;
|
||||
}
|
||||
|
||||
.size-40 {
|
||||
font-size: 40px;
|
||||
margin-left: -30%;
|
||||
}
|
||||
|
||||
.mat-button-text {
|
||||
font-size: 24px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.flex-ellipsis {
|
||||
padding-right: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
table {
|
||||
width:100%;
|
||||
}
|
||||
|
||||
.chkbox-options:focus {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.flip {
|
||||
position: relative;
|
||||
backface-visibility: hidden;
|
||||
animation: spin 1.5s cubic-bezier(.175, .885, .32, 1.275) 2;
|
||||
transform-style: preserve-3d;
|
||||
transform: rotateX(0deg);
|
||||
}
|
||||
|
||||
@-moz-keyframes spin {
|
||||
from { -moz-transform: rotateX(0deg); }
|
||||
to { -moz-transform: rotateX(360deg); }
|
||||
}
|
||||
@-webkit-keyframes spin {
|
||||
from { -webkit-transform: rotateX(0deg); }
|
||||
to { -webkit-transform: rotateX(360deg); }
|
||||
}
|
||||
@keyframes spin {
|
||||
from { transform:rotateX(0deg); }
|
||||
to { transform:rotateX(360deg); }
|
||||
}
|
@ -1,143 +0,0 @@
|
||||
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;
|
||||
@ViewChild('form', {static: true}) form: any;
|
||||
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 = null;
|
||||
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 (!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_CL || action.type === RTLActions.FETCH_CHANNELS_CL))
|
||||
.subscribe((action: RTLActions.SetPeersCL | RTLActions.FetchChannelsCL) => {
|
||||
if(action.type === RTLActions.SET_PEERS_CL) {
|
||||
if(this.newlyAddedPeer !== '') {
|
||||
this.snackBar.open('Peer added successfully. Proceed to open the channel.');
|
||||
this.selectedPeer = this.newlyAddedPeer;
|
||||
this.newlyAddedPeer = '';
|
||||
}
|
||||
}
|
||||
if(action.type === RTLActions.FETCH_CHANNELS_CL) {
|
||||
this.form.resetForm();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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 = null;
|
||||
this.minConfValue = null;
|
||||
this.showAdvanced = false;
|
||||
this.form.resetForm();
|
||||
}
|
||||
|
||||
onShowAdvanced() {
|
||||
this.showAdvanced = !this.showAdvanced;
|
||||
if (!this.showAdvanced) {
|
||||
this.flgMinConf = false;
|
||||
this.selFeeRate = null;
|
||||
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,
|
||||
titleMessage: 'Enter Lightning Address',
|
||||
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;
|
||||
const deviderIndex = this.peerAddress.search('@');
|
||||
this.newlyAddedPeer = (deviderIndex<0) ? 'none' : this.peerAddress.substring(0, deviderIndex);
|
||||
this.store.dispatch(new RTLActions.OpenSpinner('Adding Peer...'));
|
||||
this.store.dispatch(new RTLActions.SaveNewPeerCL({id: this.peerAddress, showOpenChannelModal: false}));
|
||||
} else {
|
||||
this.selectedPeer = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unSubs.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
<div fxLayout="row">
|
||||
<div fxFlex="100" class="padding-gap-large">
|
||||
<mat-card-header fxLayout="row" fxLayoutAlign="space-between center" class="modal-info-header">
|
||||
<div fxFlex="95" fxLayoutAlign="start start">
|
||||
<span class="page-title">Connect to a new peer</span>
|
||||
</div>
|
||||
<button tabindex="8" fxFlex="5" fxLayoutAlign="center" class="btn-close-x p-0" (click)="onClose()" mat-button>X</button>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="mt-5px">
|
||||
<div fxLayout="column">
|
||||
<mat-vertical-stepper [linear]="true" #stepper (selectionChange)="stepSelectionChanged($event)">
|
||||
<mat-step [stepControl]="peerFormGroup" [editable]="flgEditable">
|
||||
<form [formGroup]="peerFormGroup" fxLayout="column" fxLayout.gt-sm="row wrap" fxLayoutAlign="start" fxLayoutAlign.gt-sm="space-between" class="my-1">
|
||||
<ng-template matStepLabel>{{peerFormLabel}}</ng-template>
|
||||
<mat-form-field fxFlex="100">
|
||||
<input autoFocus matInput placeholder="Lightning Address (pubkey OR pubkey@ip:port)" formControlName="peerAddress" tabindex="1" required>
|
||||
<mat-error *ngIf="peerFormGroup.controls.peerAddress.errors?.required">Address is required.</mat-error>
|
||||
</mat-form-field>
|
||||
<div fxFlex="100" class="alert alert-danger mt-1" *ngIf="peerConnectionError !== ''">
|
||||
<fa-icon [icon]="faExclamationTriangle" class="mr-1 alert-icon"></fa-icon>
|
||||
<span>{{peerConnectionError}}</span>
|
||||
</div>
|
||||
<div class="mt-2" fxLayout="row" fxLayoutAlign="start center" fxFlex="100">
|
||||
<button mat-stroked-button color="primary" tabindex="3" type="button" (click)="onConnectPeer()">{{peerConnectionError !== '' ? 'Retry' : 'Add Peer'}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-step>
|
||||
<mat-step [stepControl]="channelFormGroup" [editable]="flgEditable">
|
||||
<form [formGroup]="channelFormGroup" fxLayout="column" fxLayout.gt-sm="row wrap" fxLayoutAlign="start" fxLayoutAlign.gt-sm="space-between" class="mb-1">
|
||||
<ng-template matStepLabel disabled="true">{{channelFormLabel}}</ng-template>
|
||||
<div fxLayout="column" fxLayout.gt-sm="row wrap" fxFlex="100" fxLayoutAlign="space-between stretch">
|
||||
<div fxLayout="row" fxFlex="100" fxLayoutAlign="space-between center">
|
||||
<mat-form-field fxFlex="60" fxLayoutAlign="start end">
|
||||
<input matInput autoFocus formControlName="fundingAmount" placeholder="Amount" type="number" step="1000" tabindex="1" required>
|
||||
<mat-hint>Remaining Bal: {{totalBalance - ((channelFormGroup.controls.fundingAmount.value) ? channelFormGroup.controls.fundingAmount.value : 0)}}</mat-hint>
|
||||
<span matSuffix> Sats </span>
|
||||
<mat-error *ngIf="channelFormGroup.controls.fundingAmount.errors?.required">Amount is required.</mat-error>
|
||||
<mat-error *ngIf="channelFormGroup.controls.fundingAmount.errors?.min">Amount must be a positive number.</mat-error>
|
||||
<mat-error *ngIf="channelFormGroup.controls.fundingAmount.errors?.max">Amount must be less than or equal to {{totalBalance}}.</mat-error>
|
||||
</mat-form-field>
|
||||
<div fxFlex="35" fxLayoutAlign="start center">
|
||||
<mat-slide-toggle tabindex="2" color="primary" formControlName="isPrivate" name="isPrivate">Private Channel</mat-slide-toggle>
|
||||
</div>
|
||||
</div>
|
||||
<div fxLayout="row" fxFlex="100" fxLayoutAlign="space-between center" class="mt-1">
|
||||
<mat-form-field fxFlex="48" fxLayoutAlign="start end">
|
||||
<mat-select tabindex="4" placeholder="Fee Rate" formControlName="selFeeRate">
|
||||
<mat-option *ngFor="let feeRateType of feeRateTypes" [value]="feeRateType.feeRateId">
|
||||
{{feeRateType.feeRateType}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<div fxFlex="48" fxLayout="row" fxLayoutAlign="start center">
|
||||
<mat-checkbox fxFlex="2" tabindex="5" color="primary" formControlName="flgMinConf" name="flgMinConf" fxLayoutAlign="stretch start" class="mr-2"></mat-checkbox>
|
||||
<mat-form-field fxFlex="98">
|
||||
<input matInput formControlName="minConfValue" placeholder="Min Confirmation Blocks" type="number" name="blocks" step="1" min="0" tabindex="8" [required]="channelFormGroup.controls.flgMinConf.value">
|
||||
<mat-error *ngIf="channelFormGroup.controls.flgMinConf.value && !channelFormGroup.controls.minConfValue.value">Min Confirmation Blocks is required.</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div fxFlex="100" class="alert alert-danger mt-1" *ngIf="channelConnectionError !== ''">
|
||||
<fa-icon [icon]="faExclamationTriangle" class="mr-1 alert-icon"></fa-icon>
|
||||
<span>{{channelConnectionError}}</span>
|
||||
</div>
|
||||
<div class="mt-2" fxLayout="row" fxLayoutAlign="start center" fxFlex="100">
|
||||
<button mat-stroked-button color="primary" tabindex="8" type="button" (click)="onOpenChannel()">{{channelConnectionError !== '' ? 'Retry' : 'Open Channel'}}</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-step>
|
||||
</mat-vertical-stepper>
|
||||
<div fxLayout="row" fxFlex="100" fxLayoutAlign="end center">
|
||||
<button mat-stroked-button color="primary" tabindex="12" type="button" [mat-dialog-close]="false" default>{{newlyAddedPeer?.id ? 'Do It Later' : 'Close'}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</div>
|
||||
</div>
|
@ -1,20 +1,20 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ChannelManageComponent } from './channel-manage.component';
|
||||
import { CLConnectPeerComponent } from './connect-peer.component';
|
||||
|
||||
describe('ChannelManageComponent', () => {
|
||||
let component: ChannelManageComponent;
|
||||
let fixture: ComponentFixture<ChannelManageComponent>;
|
||||
describe('CLConnectPeerComponent', () => {
|
||||
let component: CLConnectPeerComponent;
|
||||
let fixture: ComponentFixture<CLConnectPeerComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ChannelManageComponent ]
|
||||
declarations: [ CLConnectPeerComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ChannelManageComponent);
|
||||
fixture = TestBed.createComponent(CLConnectPeerComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
@ -0,0 +1,166 @@
|
||||
import { Component, OnInit, OnDestroy, ViewChild, Inject } from '@angular/core';
|
||||
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil, filter } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Actions } from '@ngrx/effects';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA, MatVerticalStepper } from '@angular/material';
|
||||
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import { LoggerService } from '../../../shared/services/logger.service';
|
||||
import { PeerCL } from '../../../shared/models/clModels';
|
||||
import { CLOpenChannelAlert } from '../../../shared/models/alertData';
|
||||
import { FEE_RATE_TYPES } from '../../../shared/services/consts-enums-functions';
|
||||
|
||||
import { CLEffects } from '../../store/cl.effects';
|
||||
import * as RTLActions from '../../../store/rtl.actions';
|
||||
import * as fromRTLReducer from '../../../store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-cl-connect-peer',
|
||||
templateUrl: './connect-peer.component.html',
|
||||
styleUrls: ['./connect-peer.component.scss']
|
||||
})
|
||||
export class CLConnectPeerComponent implements OnInit, OnDestroy {
|
||||
@ViewChild('peersForm', {static: true}) form: any;
|
||||
@ViewChild('stepper', { static: false }) stepper: MatVerticalStepper;
|
||||
public faExclamationTriangle = faExclamationTriangle;
|
||||
public peerAddress = '';
|
||||
public totalBalance = 0;
|
||||
public feeRateTypes = FEE_RATE_TYPES;
|
||||
public flgChannelOpened = false;
|
||||
public channelOpenStatus = null;
|
||||
public newlyAddedPeer: PeerCL = null;
|
||||
public flgEditable = true;
|
||||
public peerConnectionError = '';
|
||||
public channelConnectionError = '';
|
||||
public peerFormLabel = 'Peer Details';
|
||||
public channelFormLabel = 'Open Channel (Optional)';
|
||||
peerFormGroup: FormGroup;
|
||||
channelFormGroup: FormGroup;
|
||||
statusFormGroup: FormGroup;
|
||||
private unSubs: Array<Subject<void>> = [new Subject(), new Subject()];
|
||||
|
||||
constructor(public dialogRef: MatDialogRef<CLConnectPeerComponent>, @Inject(MAT_DIALOG_DATA) public data: CLOpenChannelAlert, private store: Store<fromRTLReducer.RTLState>, private clEffects: CLEffects, private formBuilder: FormBuilder, private actions$: Actions, private logger: LoggerService) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.totalBalance = this.data.message.balance;
|
||||
this.peerFormGroup = this.formBuilder.group({
|
||||
hiddenAddress: ['', [Validators.required]],
|
||||
peerAddress: ['', [Validators.required]]
|
||||
});
|
||||
this.channelFormGroup = this.formBuilder.group({
|
||||
fundingAmount: ['', [Validators.required, Validators.min(1), Validators.max(this.totalBalance)]],
|
||||
isPrivate: [false],
|
||||
selFeeRate: [null],
|
||||
flgMinConf: [false],
|
||||
minConfValue: [null],
|
||||
hiddenAmount: ['', [Validators.required]]
|
||||
});
|
||||
this.statusFormGroup = this.formBuilder.group({});
|
||||
this.channelFormGroup.controls.flgMinConf.valueChanges.pipe(takeUntil(this.unSubs[0])).subscribe(flg => {
|
||||
if (flg) {
|
||||
this.channelFormGroup.controls.selFeeRate.setValue(null);
|
||||
this.channelFormGroup.controls.selFeeRate.disable();
|
||||
this.channelFormGroup.controls.minConfValue.enable();
|
||||
this.channelFormGroup.controls.minConfValue.setValidators([Validators.required]);
|
||||
} else {
|
||||
this.channelFormGroup.controls.selFeeRate.enable();
|
||||
this.channelFormGroup.controls.minConfValue.disable();
|
||||
this.channelFormGroup.controls.minConfValue.setValidators(null);
|
||||
}
|
||||
});
|
||||
this.actions$.pipe(takeUntil(this.unSubs[1]),
|
||||
filter((action) => action.type === RTLActions.NEWLY_ADDED_PEER_CL || action.type === RTLActions.FETCH_CHANNELS_CL || action.type === RTLActions.EFFECT_ERROR_CL))
|
||||
.subscribe((action: (RTLActions.NewlyAddedPeerCL | RTLActions.FetchChannelsCL | RTLActions.EffectErrorCl)) => {
|
||||
if (action.type === RTLActions.NEWLY_ADDED_PEER_CL) {
|
||||
this.logger.info(action.payload);
|
||||
this.flgEditable = false;
|
||||
this.newlyAddedPeer = action.payload.peer;
|
||||
this.peerFormGroup.controls.hiddenAddress.setValue(this.peerFormGroup.controls.peerAddress.value);
|
||||
this.stepper.next();
|
||||
}
|
||||
if (action.type === RTLActions.FETCH_CHANNELS_CL) {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
if (action.type === RTLActions.EFFECT_ERROR_CL) {
|
||||
if (action.payload.action === 'SaveNewPeerCL') {
|
||||
this.peerConnectionError = action.payload.message;
|
||||
} else if (action.payload.action === 'SaveNewChannelCL') {
|
||||
this.channelConnectionError = action.payload.message;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onConnectPeer() {
|
||||
if(!this.peerFormGroup.controls.peerAddress.value) { return true; }
|
||||
this.peerConnectionError = '';
|
||||
this.store.dispatch(new RTLActions.OpenSpinner('Adding Peer...'));
|
||||
this.store.dispatch(new RTLActions.SaveNewPeerCL({id: this.peerFormGroup.controls.peerAddress.value}));
|
||||
}
|
||||
|
||||
onOpenChannel() {
|
||||
if (!this.channelFormGroup.controls.fundingAmount.value || ((this.totalBalance - this.channelFormGroup.controls.fundingAmount.value) < 0) || (this.channelFormGroup.controls.flgMinConf.value && !this.channelFormGroup.controls.minConfValue.value)) { return true; }
|
||||
this.channelConnectionError = '';
|
||||
this.store.dispatch(new RTLActions.OpenSpinner('Opening Channel...'));
|
||||
this.store.dispatch(new RTLActions.SaveNewChannelCL({
|
||||
peerId: this.newlyAddedPeer.id, satoshis: this.channelFormGroup.controls.fundingAmount.value, announce: !this.channelFormGroup.controls.isPrivate.value, feeRate: this.channelFormGroup.controls.selFeeRate.value, minconf: this.channelFormGroup.controls.flgMinConf.value ? this.channelFormGroup.controls.minConfValue.value : null
|
||||
}));
|
||||
}
|
||||
|
||||
onClose() {
|
||||
this.dialogRef.close(false);
|
||||
}
|
||||
|
||||
stepSelectionChanged(event: any) {
|
||||
switch (event.selectedIndex) {
|
||||
case 0:
|
||||
this.peerFormLabel = 'Peer Details';
|
||||
this.channelFormLabel = 'Open Channel (Optional)';
|
||||
break;
|
||||
|
||||
case 1:
|
||||
if (this.peerFormGroup.controls.peerAddress.value) {
|
||||
this.peerFormLabel = 'Peer Added: ' + (this.newlyAddedPeer.alias ? this.newlyAddedPeer.alias : this.newlyAddedPeer.id);
|
||||
} else {
|
||||
this.peerFormLabel = 'Peer Details';
|
||||
}
|
||||
this.channelFormLabel = 'Open Channel (Optional)';
|
||||
break;
|
||||
|
||||
case 2:
|
||||
if (this.peerFormGroup.controls.peerAddress.value) {
|
||||
this.peerFormLabel = 'Peer Added: ' + (this.newlyAddedPeer.alias ? this.newlyAddedPeer.alias : this.newlyAddedPeer.id);
|
||||
} else {
|
||||
this.peerFormLabel = 'Peer Details';
|
||||
}
|
||||
if (this.channelFormGroup.controls.fundingAmount.value) {
|
||||
this.channelFormLabel = 'Opening Channel for ' + this.channelFormGroup.controls.fundingAmount.value + ' Sats';
|
||||
} else {
|
||||
this.channelFormLabel = 'Open Channel (Optional)';
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
this.peerFormLabel = 'Peer Details';
|
||||
this.channelFormLabel = 'Open Channel (Optional)';
|
||||
break;
|
||||
}
|
||||
if (event.selectedIndex < event.previouslySelectedIndex) {
|
||||
if (event.selectedIndex === 0) {
|
||||
this.peerFormGroup.controls.hiddenAddress.setValue('');
|
||||
} else if (event.selectedIndex === 1) {
|
||||
this.channelFormGroup.controls.hiddenAmount.setValue('');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unSubs.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
<div fxLayout="row">
|
||||
<div fxFlex="100" class="padding-gap-large">
|
||||
<mat-card-header fxLayout="row" fxLayoutAlign="space-between center" class="modal-info-header">
|
||||
<div fxFlex="95" fxLayoutAlign="start start">
|
||||
<span class="page-title">Create Invoice</span>
|
||||
</div>
|
||||
<button tabindex="8" fxFlex="5" fxLayoutAlign="center" class="btn-close-x p-0" [mat-dialog-close]="false" default mat-button>X</button>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="mt-5px">
|
||||
<form fxLayout="row wrap" fxLayoutAlign="start space-between" fxFlex="100" #addInvoiceForm="ngForm">
|
||||
<mat-form-field fxFlex="100" fxLayoutAlign="start end">
|
||||
<input matInput autoFocus [(ngModel)]="description" placeholder="Description" tabindex="2" name="description">
|
||||
</mat-form-field>
|
||||
<div fxLayout="row" fxLayoutAlign="space-between start" fxFlex="100">
|
||||
<mat-form-field fxFlex="40">
|
||||
<input matInput [(ngModel)]="invoiceValue" (keyup)="onInvoiceValueChange()" placeholder="Amount" type="number" step="100" min="1" tabindex="3" name="invoiceValue" required>
|
||||
<span matSuffix> {{information?.smaller_currency_unit}} </span>
|
||||
<mat-hint>{{invoiceValueHint}}</mat-hint>
|
||||
<mat-error *ngIf="!invoiceValue">Amount is required.</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field fxFlex="30">
|
||||
<input matInput [(ngModel)]="expiry" placeholder="Expiry" type="number" step="{{selTimeUnit === timeUnitEnum.SECS ? 300 : selTimeUnit === timeUnitEnum.MINS ? 10 : selTimeUnit === timeUnitEnum.HOURS ? 2 : 1}}" min="1" tabindex="4" name="expiry">
|
||||
<span matSuffix> {{selTimeUnit | titlecase}} </span>
|
||||
</mat-form-field>
|
||||
<mat-form-field fxFlex="26">
|
||||
<mat-select [value]="selTimeUnit" tabindex="5" name="timeUnit" (selectionChange)="onTimeUnitChange($event)">
|
||||
<mat-option *ngFor="let timeUnit of timeUnits" [value]="timeUnit">{{timeUnit | titlecase}}</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div fxFlex="50" fxLayoutAlign="start center" class="mt-2">
|
||||
<mat-slide-toggle tabindex="6" color="primary" [(ngModel)]="private" matTooltip="Include routing hints for private channels" [matTooltipPosition]="'above'" name="private">Private Routing Hints</mat-slide-toggle>
|
||||
</div>
|
||||
<div fxFlex="100" class="alert alert-danger mt-1" *ngIf="invoiceError !== ''">
|
||||
<fa-icon [icon]="faExclamationTriangle" class="mr-1 alert-icon"></fa-icon>
|
||||
<span *ngIf="invoiceError !== ''">{{invoiceError}}</span>
|
||||
</div>
|
||||
<div fxLayout="row" fxFlex="100" class="mt-2" fxLayoutAlign="end center">
|
||||
<button class="mr-1" mat-stroked-button color="primary" tabindex="7" type="reset" (click)="resetData()">Clear Field</button>
|
||||
<button mat-flat-button color="primary" (click)="onAddInvoice(addInvoiceForm)" tabindex="8">Create Invoice</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,3 @@
|
||||
.mat-column-actions {
|
||||
min-height: 4.8rem;
|
||||
}
|
@ -0,0 +1,113 @@
|
||||
import { Component, OnInit, OnDestroy, Inject } from '@angular/core';
|
||||
import { DecimalPipe } from '@angular/common';
|
||||
import { Subject } from 'rxjs';
|
||||
import { filter, takeUntil } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Actions } from '@ngrx/effects';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
|
||||
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
|
||||
|
||||
import { InvoiceInformation } from '../../../shared/models/alertData';
|
||||
import { TimeUnitEnum, CurrencyUnitEnum, TIME_UNITS, CURRENCY_UNIT_FORMATS, PAGE_SIZE } from '../../../shared/services/consts-enums-functions';
|
||||
import { SelNodeChild } from '../../../shared/models/RTLconfig';
|
||||
import { GetInfoCL } from '../../../shared/models/clModels';
|
||||
import { CommonService } from '../../../shared/services/common.service';
|
||||
|
||||
import * as RTLActions from '../../../store/rtl.actions';
|
||||
import * as fromRTLReducer from '../../../store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-cl-create-invoices',
|
||||
templateUrl: './create-invoice.component.html',
|
||||
styleUrls: ['./create-invoice.component.scss']
|
||||
})
|
||||
export class CLCreateInvoiceComponent implements OnInit, OnDestroy {
|
||||
public faExclamationTriangle = faExclamationTriangle;
|
||||
public selNode: SelNodeChild = {};
|
||||
public description = '';
|
||||
public expiry: number;
|
||||
public invoiceValue: number;
|
||||
public invoiceValueHint = '';
|
||||
public invoicePaymentReq = '';
|
||||
public invoices: any;
|
||||
public information: GetInfoCL = {};
|
||||
public private = false;
|
||||
public expiryStep = 100;
|
||||
public pageSize = PAGE_SIZE;
|
||||
public timeUnitEnum = TimeUnitEnum;
|
||||
public timeUnits = TIME_UNITS;
|
||||
public selTimeUnit = TimeUnitEnum.SECS;
|
||||
public invoiceError = '';
|
||||
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
|
||||
|
||||
constructor(public dialogRef: MatDialogRef<CLCreateInvoiceComponent>, @Inject(MAT_DIALOG_DATA) public data: InvoiceInformation, private store: Store<fromRTLReducer.RTLState>, private decimalPipe: DecimalPipe, private commonService: CommonService, private actions$: Actions) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.pageSize = this.data.pageSize;
|
||||
this.store.select('cl')
|
||||
.pipe(takeUntil(this.unSubs[0]))
|
||||
.subscribe((rtlStore) => {
|
||||
this.selNode = rtlStore.nodeSettings;
|
||||
this.information = rtlStore.information;
|
||||
});
|
||||
this.actions$.pipe(takeUntil(this.unSubs[1]),
|
||||
filter(action => action.type === RTLActions.EFFECT_ERROR_CL || action.type === RTLActions.FETCH_INVOICES_CL))
|
||||
.subscribe((action: RTLActions.EffectErrorCl | RTLActions.FetchInvoicesCL) => {
|
||||
if (action.type === RTLActions.FETCH_INVOICES_CL) {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
if (action.type === RTLActions.EFFECT_ERROR_CL && action.payload.action === 'SaveNewInvoiceCL') {
|
||||
this.invoiceError = action.payload.message;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onAddInvoice(form: any) {
|
||||
this.invoiceError = '';
|
||||
if(!this.invoiceValue) { return true; }
|
||||
let expiryInSecs = (this.expiry ? this.expiry : 3600);
|
||||
if (this.selTimeUnit !== TimeUnitEnum.SECS) {
|
||||
expiryInSecs = this.commonService.convertTime(this.expiry, this.selTimeUnit, TimeUnitEnum.SECS);
|
||||
}
|
||||
this.store.dispatch(new RTLActions.OpenSpinner('Adding Invoice...'));
|
||||
this.store.dispatch(new RTLActions.SaveNewInvoiceCL({
|
||||
label: ('ulbl' + Math.random().toString(36).slice(2) + Date.now()), amount: this.invoiceValue*1000, description: this.description, expiry: expiryInSecs, private: this.private
|
||||
}));
|
||||
}
|
||||
|
||||
resetData() {
|
||||
this.description = '';
|
||||
this.invoiceValue = undefined;
|
||||
this.private = false;
|
||||
this.expiry = undefined;
|
||||
this.invoiceValueHint = '';
|
||||
this.selTimeUnit = TimeUnitEnum.SECS;
|
||||
this.invoiceError = '';
|
||||
}
|
||||
|
||||
onInvoiceValueChange() {
|
||||
if(this.selNode.fiatConversion && this.invoiceValue > 99) {
|
||||
this.invoiceValueHint = '';
|
||||
this.commonService.convertCurrency(this.invoiceValue, CurrencyUnitEnum.SATS, this.selNode.currencyUnits[2], this.selNode.fiatConversion)
|
||||
.pipe(takeUntil(this.unSubs[2]))
|
||||
.subscribe(data => {
|
||||
this.invoiceValueHint = '= ' + data.symbol + this.decimalPipe.transform(data.OTHER, CURRENCY_UNIT_FORMATS.OTHER) + ' ' + data.unit;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onTimeUnitChange(event: any) {
|
||||
if(this.expiry && this.selTimeUnit !== event.value) {
|
||||
this.expiry = this.commonService.convertTime(this.expiry, this.selTimeUnit, event.value);
|
||||
}
|
||||
this.selTimeUnit = event.value;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unSubs.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
<div fxLayout="row">
|
||||
<div fxFlex="100" class="padding-gap-large">
|
||||
<mat-card-header fxLayout="row" fxLayoutAlign="space-between center" class="modal-info-header">
|
||||
<div fxFlex="95" fxLayoutAlign="start start">
|
||||
<span class="page-title">Send Payment</span>
|
||||
</div>
|
||||
<button tabindex="8" fxFlex="5" fxLayoutAlign="center" class="btn-close-x p-0" [mat-dialog-close]="false" default mat-button>X</button>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="mt-5px">
|
||||
<form fxLayoutAlign="space-between stretch" fxLayout="column" #sendPaymentForm="ngForm">
|
||||
<mat-form-field fxFlex="100">
|
||||
<textarea autoFocus matInput placeholder="Payment Request" name="paymentRequest" tabindex="1" [ngModel]="paymentRequest" (ngModelChange)="onPaymentRequestEntry($event)" (matTextareaAutosize)="true" required #paymentReq="ngModel"></textarea>
|
||||
<mat-hint *ngIf="paymentRequest && paymentDecodedHint !== ''">{{paymentDecodedHint}}</mat-hint>
|
||||
<mat-error *ngIf="!paymentRequest">Payment request is required.</mat-error>
|
||||
<mat-error *ngIf="paymentReq.errors?.decodeError">{{paymentDecodedHint}}</mat-error>
|
||||
</mat-form-field>
|
||||
<mat-form-field fxFlex="100" *ngIf="zeroAmtInvoice">
|
||||
<input matInput placeholder="Amount (Sats)" name="amount" [(ngModel)]="paymentAmount" (change)="onAmountChange($event)" tabindex="2" required #paymentAmt="ngModel">
|
||||
<mat-hint>It is a zero amount invoice, enter amount to be paid.</mat-hint>
|
||||
<mat-error *ngIf="!paymentAmount">Payment amount is required.</mat-error>
|
||||
</mat-form-field>
|
||||
<div fxFlex="100" class="alert alert-danger mt-1" *ngIf="paymentError !== ''">
|
||||
<fa-icon [icon]="faExclamationTriangle" class="mr-1 alert-icon"></fa-icon>
|
||||
<span *ngIf="paymentError !== ''">{{paymentError}}</span>
|
||||
</div>
|
||||
<div class="mt-2" fxLayout="row" fxLayoutAlign="end center">
|
||||
<button class="mr-1" mat-stroked-button color="primary" tabindex="2" type="reset" (click)="resetData()">Clear Fields</button>
|
||||
<button mat-flat-button color="primary" (click)="onSendPayment();" tabindex="3">Send Payment</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,10 @@
|
||||
.mat-column-actions {
|
||||
min-height: 4.8rem;
|
||||
}
|
||||
|
||||
.mat-column-payment_hash {
|
||||
flex: 1 1 20%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue