Merging Angular pre-build code
Merging Angular pre-build code
13
.editorconfig
Normal file
@ -0,0 +1,13 @@
|
||||
# Editor configuration, see http://editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = tab
|
||||
indent_size = 2
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
91
.gitignore
vendored
@ -1,60 +1,43 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
# compiled output
|
||||
/dist
|
||||
/tmp
|
||||
/out-tsc
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
# IDEs and editors
|
||||
/.idea
|
||||
.project
|
||||
.classpath
|
||||
.c9/
|
||||
*.launch
|
||||
.settings/
|
||||
*.sublime-workspace
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
# IDE - VSCode
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
# misc
|
||||
/.sass-cache
|
||||
/connect.lock
|
||||
/coverage
|
||||
/libpeerconnection.log
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
testem.log
|
||||
/typings
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
# System Files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Typescript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
|
||||
RTL.conf
|
||||
RTL.conf
|
||||
/logs
|
||||
/cookies
|
||||
RTL.log
|
||||
|
125
angular.json
Normal file
@ -0,0 +1,125 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"RTLApp": {
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"projectType": "application",
|
||||
"prefix": "rtl",
|
||||
"schematics": {},
|
||||
"targets": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "angular",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "src/tsconfig.app.json",
|
||||
"assets": [
|
||||
"src/assets"
|
||||
],
|
||||
"styles": [
|
||||
"src/app/shared/theme/styles/styles.scss"
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "RTLApp:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "RTLApp:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "RTLApp:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "src/tsconfig.spec.json",
|
||||
"karmaConfig": "src/karma.conf.js",
|
||||
"styles": [
|
||||
"src/app/shared/theme/styles/styles.scss"
|
||||
],
|
||||
"scripts": [],
|
||||
"assets": [
|
||||
"src/assets"
|
||||
]
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"src/tsconfig.app.json",
|
||||
"src/tsconfig.spec.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"RTLApp-e2e": {
|
||||
"root": "e2e/",
|
||||
"projectType": "application",
|
||||
"targets": {
|
||||
"e2e": {
|
||||
"builder": "@angular-devkit/build-angular:protractor",
|
||||
"options": {
|
||||
"protractorConfig": "e2e/protractor.conf.js",
|
||||
"devServerTarget": "RTLApp:serve"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "RTLApp:serve:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": "e2e/tsconfig.e2e.json",
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "RTLApp"
|
||||
}
|
28
e2e/protractor.conf.js
Normal file
@ -0,0 +1,28 @@
|
||||
// Protractor configuration file, see link for more information
|
||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
||||
|
||||
const { SpecReporter } = require('jasmine-spec-reporter');
|
||||
|
||||
exports.config = {
|
||||
allScriptsTimeout: 11000,
|
||||
specs: [
|
||||
'./src/**/*.e2e-spec.ts'
|
||||
],
|
||||
capabilities: {
|
||||
'browserName': 'chrome'
|
||||
},
|
||||
directConnect: true,
|
||||
baseUrl: 'http://localhost:4200/',
|
||||
framework: 'jasmine',
|
||||
jasmineNodeOpts: {
|
||||
showColors: true,
|
||||
defaultTimeoutInterval: 30000,
|
||||
print: function() {}
|
||||
},
|
||||
onPrepare() {
|
||||
require('ts-node').register({
|
||||
project: require('path').join(__dirname, './tsconfig.e2e.json')
|
||||
});
|
||||
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
|
||||
}
|
||||
};
|
14
e2e/src/app.e2e-spec.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { AppPage } from './app.po';
|
||||
|
||||
describe('workspace-project App', () => {
|
||||
let page: AppPage;
|
||||
|
||||
beforeEach(() => {
|
||||
page = new AppPage();
|
||||
});
|
||||
|
||||
it('should display welcome message', () => {
|
||||
page.navigateTo();
|
||||
expect(page.getParagraphText()).toEqual('Welcome to RTLApp!');
|
||||
});
|
||||
});
|
11
e2e/src/app.po.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { browser, by, element } from 'protractor';
|
||||
|
||||
export class AppPage {
|
||||
navigateTo() {
|
||||
return browser.get('/');
|
||||
}
|
||||
|
||||
getParagraphText() {
|
||||
return element(by.css('app-root h1')).getText();
|
||||
}
|
||||
}
|
13
e2e/tsconfig.e2e.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../out-tsc/app",
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"types": [
|
||||
"jasmine",
|
||||
"jasminewd2",
|
||||
"node"
|
||||
]
|
||||
}
|
||||
}
|
12
prebuild.js
Normal file
@ -0,0 +1,12 @@
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const appVersion = require('./package.json').version;
|
||||
const versionFilePath = path.join(__dirname + '/src/environments/version.ts');
|
||||
const versionStr = `export const VERSION = '${appVersion}';`;
|
||||
fs.writeFile(versionFilePath, versionStr, { flat: 'w' }, function (err) {
|
||||
if (err) {
|
||||
return console.log(err);
|
||||
}
|
||||
console.log(`Updating application version ${appVersion}`);
|
||||
console.log(`${'Writing version module to '}${versionFilePath}\n`);
|
||||
});
|
BIN
product management/Channels Details.png
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
product management/Channels Lookup.png
Normal file
After Width: | Height: | Size: 51 KiB |
BIN
product management/Channels.png
Normal file
After Width: | Height: | Size: 96 KiB |
BIN
product management/Home Page.png
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
product management/Node Lookup.png
Normal file
After Width: | Height: | Size: 53 KiB |
BIN
product management/Peers.png
Normal file
After Width: | Height: | Size: 70 KiB |
BIN
product management/Start.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
product management/Unlock Wallet.png
Normal file
After Width: | Height: | Size: 42 KiB |
44
product management/roadmap.md
Normal file
@ -0,0 +1,44 @@
|
||||
# Product Roadmap for RTL Application
|
||||
|
||||
## Version 0.0.1-alpha (Minimum Viable Product)
|
||||
|
||||
Start
|
||||
- Unlock Wallet
|
||||
|
||||
Home Page
|
||||
- Wallet Balance
|
||||
- Channel Balance
|
||||
- Channel Status
|
||||
- Chain Sync Status
|
||||
- Fee Report
|
||||
|
||||
Peer Management
|
||||
- Listing of Connected Peers
|
||||
- Initiate Connection with peers with the public key
|
||||
|
||||
Channel Management
|
||||
- Status of Channels (Active, Inactive, Pending)
|
||||
- Listing of Channels
|
||||
- Open Channel with connected peers
|
||||
|
||||
## Version 0.0.2
|
||||
Globalization - Allow for Language customization
|
||||
|
||||
## Feature Backlog
|
||||
Start
|
||||
- Create Wallet
|
||||
|
||||
Home Page
|
||||
- Network Status
|
||||
|
||||
Channel Management
|
||||
- Channel Detail - Bi-Directional channel balance view
|
||||
- Close Channel
|
||||
|
||||
LN Wallet
|
||||
- Generate pub key to recieve Bitcoin
|
||||
- Send Bitcoin to an address
|
||||
|
||||
Payments
|
||||
- Decode payment request
|
||||
- Send payment
|
28
protractor.conf.js
Normal file
@ -0,0 +1,28 @@
|
||||
// Protractor configuration file, see link for more information
|
||||
// https://github.com/angular/protractor/blob/master/lib/config.ts
|
||||
|
||||
const { SpecReporter } = require('jasmine-spec-reporter');
|
||||
|
||||
exports.config = {
|
||||
allScriptsTimeout: 11000,
|
||||
specs: [
|
||||
'./e2e/**/*.e2e-spec.ts'
|
||||
],
|
||||
capabilities: {
|
||||
'browserName': 'chrome'
|
||||
},
|
||||
directConnect: true,
|
||||
baseUrl: 'http://localhost:4200/',
|
||||
framework: 'jasmine',
|
||||
jasmineNodeOpts: {
|
||||
showColors: true,
|
||||
defaultTimeoutInterval: 30000,
|
||||
print: function() {}
|
||||
},
|
||||
onPrepare() {
|
||||
require('ts-node').register({
|
||||
project: 'e2e/tsconfig.e2e.json'
|
||||
});
|
||||
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
|
||||
}
|
||||
};
|
78
src/app/app.component.html
Normal file
@ -0,0 +1,78 @@
|
||||
<div fxLayout="column" id="rtl-container" class="rtl-container" [ngClass]="settings.theme" [class.horizontal]="settings.menu === 'Horizontal'" [class.compact]="settings.menuType === 'Compact'" [class.mini]="settings.menuType === 'Mini'">
|
||||
<mat-sidenav-container>
|
||||
<mat-sidenav perfectScrollbar *ngIf="settings.menu === 'Vertical'" [opened]="settings.flgSidenavOpened" [mode]="(settings.flgSidenavPinned) ? 'side' : 'over'"
|
||||
#sideNavigation class="sidenav mat-elevation-z6">
|
||||
<rtl-side-navigation (ChildNavClicked)="onNavigationClicked($event)"></rtl-side-navigation>
|
||||
</mat-sidenav>
|
||||
<mat-sidenav-content perfectScrollbar class="overflow-auto">
|
||||
<div class="top-bar">
|
||||
<mat-toolbar fxLayout="row" fxLayoutAlign="space-between center" color="primary" class="padding-gap-x sticky top-toolbar">
|
||||
<div fxLayoutAlign="center center">
|
||||
<button *ngIf="settings.menu === 'Vertical'" mat-icon-button (click)="sideNavToggle(sideNavigation)">
|
||||
<mat-icon>menu</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<h2>Ride The Lightning <span class="font-60-percent">(Beta)</span></h2>
|
||||
</div>
|
||||
<div>
|
||||
<rtl-top-menu></rtl-top-menu>
|
||||
</div>
|
||||
</mat-toolbar>
|
||||
<div fxLayout="row" fxLayoutAlign="center center" class="bg-primary pubkey-info-top sticky" rtlClipboard [payload]="information?.identity_pubkey" (copied)="copiedText($event)">
|
||||
<mat-icon [ngClass]="{'icon-smaller': smallScreen}">vpn_key</mat-icon>
|
||||
<div [ngClass]="{'word-break font-9px': smallScreen, 'word-break': !smallScreen}"> {{information?.identity_pubkey}}
|
||||
<mat-spinner [diameter]="20" *ngIf="flgLoading[0]" class="inline-spinner foreground"></mat-spinner>
|
||||
<mat-icon [ngClass]="{'icon-smaller cursor-pointer copy-icon-smaller': smallScreen, 'icon-small cursor-pointer copy-icon': !smallScreen}">file_copy</mat-icon><span [hidden]="!flgCopied">Copied</span>
|
||||
</div>
|
||||
</div>
|
||||
<mat-toolbar color="primary" *ngIf="settings.menu === 'Horizontal'" class="padding-gap-x horizontal-nav sticky">
|
||||
<div fxLayout="row" fxFlex="100" fxLayoutAlign="center center" class="h-100">
|
||||
<rtl-horizontal-navigation></rtl-horizontal-navigation>
|
||||
</div>
|
||||
</mat-toolbar>
|
||||
</div>
|
||||
|
||||
<!-- <div fxLayout="column" class="top-bar">
|
||||
<mat-toolbar fxLayout="row" fxLayoutAlign="space-between center" color="primary" class="padding-gap-x sticky top-toolbar">
|
||||
<div fxLayoutAlign="center center">
|
||||
<button *ngIf="settings.menu === 'Vertical'" mat-icon-button (click)="sideNavToggle(sideNavigation)">
|
||||
<mat-icon>menu</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<h2>Ride The Lightning <span class="font-60-percent">(Beta)</span></h2>
|
||||
</div>
|
||||
<div>
|
||||
<rtl-top-menu></rtl-top-menu>
|
||||
</div>
|
||||
</mat-toolbar>
|
||||
<div fxLayout="row" fxLayoutAlign="space-between center" class="bg-primary pubkey-info-top sticky" rtlClipboard [payload]="information?.identity_pubkey" (copied)="copiedText($event)">
|
||||
<mat-icon [ngClass]="{'icon-smaller': smallScreen}">vpn_key</mat-icon>
|
||||
<div [ngClass]="{'word-break font-9px': smallScreen, 'word-break': !smallScreen}"> {{information?.identity_pubkey}}
|
||||
<mat-spinner [diameter]="20" *ngIf="flgLoading[0]" class="inline-spinner foreground"></mat-spinner>
|
||||
<mat-icon [ngClass]="{'icon-smaller cursor-pointer copy-icon-smaller': smallScreen, 'icon-small cursor-pointer copy-icon': !smallScreen}">file_copy</mat-icon><span [hidden]="!flgCopied">Copied</span>
|
||||
</div>
|
||||
</div>
|
||||
<mat-toolbar color="primary" *ngIf="settings.menu === 'Horizontal'" class="padding-gap-x horizontal-nav sticky">
|
||||
<div fxLayout="row" fxFlex="100" fxLayoutAlign="center center" class="h-100">
|
||||
<rtl-horizontal-navigation></rtl-horizontal-navigation>
|
||||
</div>
|
||||
</mat-toolbar>
|
||||
</div> -->
|
||||
<div [ngClass]="{'mt-minus-1': smallScreen, 'inner-sidenav-content': true}">
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
<div fxLayout="row" fxLayoutAlign="center center" class="bg-primary settings-icon" (click)="settingSidenav.toggle()">
|
||||
<mat-icon class="animate-settings">settings</mat-icon>
|
||||
</div>
|
||||
</mat-sidenav-content>
|
||||
<mat-sidenav #settingSidenav position="end" class="settings mat-elevation-z6" mode="side">
|
||||
<rtl-settings-nav (done)="settingSidenav.toggle()"></rtl-settings-nav>
|
||||
</mat-sidenav>
|
||||
</mat-sidenav-container>
|
||||
<div class="rtl-spinner" *ngIf="undefined === settings.theme">
|
||||
<mat-spinner color="accent"></mat-spinner>
|
||||
<h4>Loading RTL...</h4>
|
||||
</div>
|
||||
</div>
|
4
src/app/app.component.scss
Normal file
@ -0,0 +1,4 @@
|
||||
.inline-spinner {
|
||||
display: inline-flex !important;
|
||||
top: 0px !important;
|
||||
}
|
166
src/app/app.component.ts
Normal file
@ -0,0 +1,166 @@
|
||||
import { Component, OnInit, AfterViewInit, OnDestroy, ViewChild, HostListener } 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 { UserIdleService } from 'angular-user-idle';
|
||||
|
||||
import { LoggerService } from './shared/services/logger.service';
|
||||
import { Settings, Authentication } from './shared/models/RTLconfig';
|
||||
import { GetInfo } from './shared/models/lndModels';
|
||||
|
||||
import * as RTLActions from './shared/store/rtl.actions';
|
||||
import * as fromRTLReducer from './shared/store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-app',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss']
|
||||
})
|
||||
export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
|
||||
@ViewChild('sideNavigation') sideNavigation: any;
|
||||
@ViewChild('settingSidenav') settingSidenav: any;
|
||||
public information: GetInfo = {};
|
||||
public flgLoading: Array<Boolean | 'error'> = [true]; // 0: Info
|
||||
public flgCopied = false;
|
||||
public settings: Settings;
|
||||
public authSettings: Authentication;
|
||||
public accessKey = '';
|
||||
public smallScreen = false;
|
||||
unsubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject()];
|
||||
|
||||
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.State>, private actions$: Actions,
|
||||
private userIdle: UserIdleService, private router: Router) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.store.dispatch(new RTLActions.FetchSettings());
|
||||
this.accessKey = this.readAccessKey();
|
||||
this.store.select('rtlRoot')
|
||||
.pipe(takeUntil(this.unsubs[0]))
|
||||
.subscribe(rtlStore => {
|
||||
this.settings = rtlStore.settings;
|
||||
this.information = rtlStore.information;
|
||||
this.authSettings = rtlStore.authSettings;
|
||||
this.flgLoading[0] = (undefined !== this.information.identity_pubkey) ? false : true;
|
||||
if (window.innerWidth <= 768) {
|
||||
this.settings.menu = 'Vertical';
|
||||
this.settings.flgSidenavOpened = false;
|
||||
this.settings.flgSidenavPinned = false;
|
||||
}
|
||||
if (window.innerWidth <= 414) {
|
||||
this.smallScreen = true;
|
||||
}
|
||||
this.logger.info(this.settings);
|
||||
if (!sessionStorage.getItem('token')) {
|
||||
this.flgLoading[0] = false;
|
||||
}
|
||||
});
|
||||
if (sessionStorage.getItem('token')) {
|
||||
this.store.dispatch(new RTLActions.FetchInfo());
|
||||
}
|
||||
this.actions$
|
||||
.pipe(
|
||||
takeUntil(this.unsubs[1]),
|
||||
filter((action) => action.type === RTLActions.INIT_APP_DATA || action.type === RTLActions.SET_SETTINGS || action.type === RTLActions.SET_AUTH_SETTINGS)
|
||||
).subscribe((actionPayload: (RTLActions.InitAppData | RTLActions.SetSettings | RTLActions.SetAuthSettings)) => {
|
||||
if (actionPayload.type === RTLActions.SET_AUTH_SETTINGS) {
|
||||
if (!sessionStorage.getItem('token')) {
|
||||
if (+actionPayload.payload.rtlSSO) {
|
||||
this.store.dispatch(new RTLActions.Signin(window.btoa(this.accessKey)));
|
||||
} else {
|
||||
this.router.navigate([this.authSettings.logoutRedirectLink]);
|
||||
}
|
||||
}
|
||||
} else if (actionPayload.type === RTLActions.INIT_APP_DATA) {
|
||||
this.store.dispatch(new RTLActions.FetchInfo());
|
||||
} else if (actionPayload.type === RTLActions.SET_SETTINGS) {
|
||||
if (this.settings.menu === 'Horizontal') {
|
||||
this.settingSidenav.toggle(); // To dynamically update the width to 100% after side nav is closed
|
||||
setTimeout(() => { this.settingSidenav.toggle(); }, 100);
|
||||
}
|
||||
}
|
||||
});
|
||||
this.actions$
|
||||
.pipe(
|
||||
takeUntil(this.unsubs[1]),
|
||||
filter((action) => action.type === RTLActions.SET_INFO)
|
||||
).subscribe((infoData: RTLActions.SetInfo) => {
|
||||
if (undefined !== infoData.payload.identity_pubkey) {
|
||||
this.initializeRemainingData();
|
||||
}
|
||||
});
|
||||
this.userIdle.startWatching();
|
||||
this.userIdle.onTimerStart().subscribe(count => {});
|
||||
this.userIdle.onTimeout().subscribe(() => {
|
||||
if (sessionStorage.getItem('token')) {
|
||||
this.logger.warn('Time limit exceeded for session inactivity! Logging out!');
|
||||
this.store.dispatch(new RTLActions.OpenAlert({ width: '75%', data: {
|
||||
type: 'WARN',
|
||||
titleMessage: 'Time limit exceeded for session inactivity! Logging out!'
|
||||
}}));
|
||||
this.store.dispatch(new RTLActions.Signout());
|
||||
this.userIdle.resetTimer();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private readAccessKey() {
|
||||
const url = window.location.href;
|
||||
return url.substring(url.lastIndexOf('/') + 1);
|
||||
}
|
||||
|
||||
initializeRemainingData() {
|
||||
this.store.dispatch(new RTLActions.FetchPeers());
|
||||
this.store.dispatch(new RTLActions.FetchBalance('channels'));
|
||||
this.store.dispatch(new RTLActions.FetchFees());
|
||||
this.store.dispatch(new RTLActions.FetchNetwork());
|
||||
this.store.dispatch(new RTLActions.FetchChannels({routeParam: 'all', channelStatus: ''}));
|
||||
this.store.dispatch(new RTLActions.FetchChannels({routeParam: 'pending', channelStatus: ''}));
|
||||
this.store.dispatch(new RTLActions.FetchInvoices());
|
||||
this.store.dispatch(new RTLActions.FetchPayments());
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
if (!this.settings.flgSidenavPinned) {
|
||||
this.sideNavigation.close();
|
||||
this.settingSidenav.toggle();
|
||||
}
|
||||
if (window.innerWidth <= 768) {
|
||||
this.sideNavigation.close();
|
||||
this.settingSidenav.toggle();
|
||||
}
|
||||
}
|
||||
|
||||
@HostListener('window:resize')
|
||||
public onWindowResize(): void {
|
||||
if (window.innerWidth <= 768) {
|
||||
this.settings.menu = 'Vertical';
|
||||
this.settings.flgSidenavOpened = false;
|
||||
this.settings.flgSidenavPinned = false;
|
||||
}
|
||||
}
|
||||
|
||||
sideNavToggle() {
|
||||
this.sideNavigation.toggle();
|
||||
}
|
||||
|
||||
onNavigationClicked(event: any) {
|
||||
if (window.innerWidth <= 414) {
|
||||
this.sideNavigation.close();
|
||||
}
|
||||
}
|
||||
|
||||
copiedText(payload) {
|
||||
this.flgCopied = true;
|
||||
setTimeout(() => {this.flgCopied = false; }, 5000);
|
||||
this.logger.info('Copied Text: ' + payload);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unsubs.forEach(unsub => {
|
||||
unsub.next();
|
||||
unsub.complete();
|
||||
});
|
||||
}
|
||||
}
|
103
src/app/app.module.ts
Normal file
@ -0,0 +1,103 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
import { EffectsModule } from '@ngrx/effects';
|
||||
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
|
||||
import { UserIdleModule } from 'angular-user-idle';
|
||||
|
||||
import { OverlayContainer } from '@angular/cdk/overlay';
|
||||
import { NgxChartsModule } from '@swimlane/ngx-charts';
|
||||
|
||||
import { PerfectScrollbarModule } from 'ngx-perfect-scrollbar';
|
||||
import { PERFECT_SCROLLBAR_CONFIG } from 'ngx-perfect-scrollbar';
|
||||
import { PerfectScrollbarConfigInterface } from 'ngx-perfect-scrollbar';
|
||||
|
||||
const DEFAULT_PERFECT_SCROLLBAR_CONFIG: PerfectScrollbarConfigInterface = {
|
||||
suppressScrollX: false
|
||||
};
|
||||
|
||||
import { environment } from '../environments/environment';
|
||||
import { routing } from './app.routing';
|
||||
import { SharedModule } from './shared/shared.module';
|
||||
import { ThemeOverlay } from './shared/theme/overlay-container/theme-overlay';
|
||||
import { AppComponent } from './app.component';
|
||||
import { HomeComponent } from './pages/home/home.component';
|
||||
import { PeersComponent } from './pages/peers/peers.component';
|
||||
import { SendReceiveTransComponent } from './pages/transactions/send-receive/send-receive-trans.component';
|
||||
import { InvoicesComponent } from './pages/invoices/invoices.component';
|
||||
import { ServerConfigComponent } from './pages/server-config/server-config.component';
|
||||
import { HelpComponent } from './pages/help/help.component';
|
||||
import { UnlockLNDComponent } from './pages/unlock-lnd/unlock-lnd.component';
|
||||
import { PaymentsComponent } from './pages/payments/payments.component';
|
||||
import { SideNavigationComponent } from './pages/navigation/side-navigation/side-navigation.component';
|
||||
import { TopMenuComponent } from './pages/navigation/top-menu/top-menu.component';
|
||||
import { HorizontalNavigationComponent } from './pages/navigation/horizontal-navigation/horizontal-navigation.component';
|
||||
import { ChannelManageComponent } from './pages/channels/channel-manage/channel-manage.component';
|
||||
import { ChannelPendingComponent } from './pages/channels/channel-pending/channel-pending.component';
|
||||
import { SigninComponent } from './pages/signin/signin.component';
|
||||
|
||||
import { RTLRootReducer } from './shared/store/rtl.reducers';
|
||||
import { RTLEffects } from './shared/store/rtl.effects';
|
||||
|
||||
import { LoggerService, ConsoleLoggerService } from './shared/services/logger.service';
|
||||
import { AuthGuard, LNDUnlockedGuard } from './shared/services/auth.guard';
|
||||
import { AuthInterceptor } from './shared/services/auth.interceptor';
|
||||
import { ChannelClosedComponent } from './pages/channels/channel-closed/channel-closed.component';
|
||||
import { ListTransactionsComponent } from './pages/transactions/list-transactions/list-transactions.component';
|
||||
import { LookupsComponent } from './pages/lookups/lookups.component';
|
||||
import { ForwardingHistoryComponent } from './pages/switch/forwarding-history.component';
|
||||
import { ChannelLookupComponent } from './pages/lookups/channel-lookup/channel-lookup.component';
|
||||
import { NodeLookupComponent } from './pages/lookups/node-lookup/node-lookup.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule,
|
||||
HttpClientModule,
|
||||
PerfectScrollbarModule,
|
||||
SharedModule,
|
||||
NgxChartsModule,
|
||||
routing,
|
||||
UserIdleModule.forRoot({idle: 60 * 60, timeout: 1, ping: null}),
|
||||
StoreModule.forRoot({rtlRoot: RTLRootReducer}),
|
||||
EffectsModule.forRoot([RTLEffects]),
|
||||
!environment.production ? StoreDevtoolsModule.instrument() : []
|
||||
],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
HomeComponent,
|
||||
PeersComponent,
|
||||
SendReceiveTransComponent,
|
||||
InvoicesComponent,
|
||||
ServerConfigComponent,
|
||||
HelpComponent,
|
||||
UnlockLNDComponent,
|
||||
PaymentsComponent,
|
||||
SideNavigationComponent,
|
||||
TopMenuComponent,
|
||||
HorizontalNavigationComponent,
|
||||
ChannelManageComponent,
|
||||
ChannelPendingComponent,
|
||||
SigninComponent,
|
||||
ChannelClosedComponent,
|
||||
ListTransactionsComponent,
|
||||
LookupsComponent,
|
||||
ForwardingHistoryComponent,
|
||||
ChannelLookupComponent,
|
||||
NodeLookupComponent
|
||||
],
|
||||
providers: [
|
||||
{ provide: LoggerService, useClass: ConsoleLoggerService },
|
||||
{ provide: PERFECT_SCROLLBAR_CONFIG, useValue: DEFAULT_PERFECT_SCROLLBAR_CONFIG },
|
||||
{ provide: OverlayContainer, useClass: ThemeOverlay },
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
|
||||
AuthGuard, LNDUnlockedGuard
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule {}
|
46
src/app/app.routing.ts
Normal file
@ -0,0 +1,46 @@
|
||||
import { Routes, RouterModule } from '@angular/router';
|
||||
import { ModuleWithProviders } from '@angular/core';
|
||||
|
||||
import { NotFoundComponent } from './shared/components/not-found/not-found.component';
|
||||
|
||||
import { HomeComponent } from './pages/home/home.component';
|
||||
import { UnlockLNDComponent } from './pages/unlock-lnd/unlock-lnd.component';
|
||||
import { ChannelClosedComponent } from './pages/channels/channel-closed/channel-closed.component';
|
||||
import { ChannelManageComponent } from './pages/channels/channel-manage/channel-manage.component';
|
||||
import { ChannelPendingComponent } from './pages/channels/channel-pending/channel-pending.component';
|
||||
import { PeersComponent } from './pages/peers/peers.component';
|
||||
import { SendReceiveTransComponent } from './pages/transactions/send-receive/send-receive-trans.component';
|
||||
import { ListTransactionsComponent } from './pages/transactions/list-transactions/list-transactions.component';
|
||||
import { PaymentsComponent } from './pages/payments/payments.component';
|
||||
import { ServerConfigComponent } from './pages/server-config/server-config.component';
|
||||
import { HelpComponent } from './pages/help/help.component';
|
||||
import { InvoicesComponent } from './pages/invoices/invoices.component';
|
||||
import { LookupsComponent } from './pages/lookups/lookups.component';
|
||||
import { SigninComponent } from './pages/signin/signin.component';
|
||||
import { ForwardingHistoryComponent } from './pages/switch/forwarding-history.component';
|
||||
import { SsoFailedComponent } from './shared/components/sso-failed/sso-failed.component';
|
||||
|
||||
import { AuthGuard, LNDUnlockedGuard } from './shared/services/auth.guard';
|
||||
|
||||
export const routes: Routes = [
|
||||
{ path: '', redirectTo: '/home', pathMatch: 'full', canActivate: [AuthGuard, LNDUnlockedGuard] },
|
||||
{ path: 'unlocklnd', component: UnlockLNDComponent, canActivate: [AuthGuard] },
|
||||
{ path: 'home', component: HomeComponent, canActivate: [AuthGuard, LNDUnlockedGuard] },
|
||||
{ path: 'peers', component: PeersComponent, canActivate: [AuthGuard, LNDUnlockedGuard] },
|
||||
{ path: 'chnlclosed', component: ChannelClosedComponent, canActivate: [AuthGuard, LNDUnlockedGuard] },
|
||||
{ path: 'chnlmanage', component: ChannelManageComponent, canActivate: [AuthGuard, LNDUnlockedGuard] },
|
||||
{ path: 'chnlpending', component: ChannelPendingComponent, canActivate: [AuthGuard, LNDUnlockedGuard] },
|
||||
{ path: 'transsendreceive', component: SendReceiveTransComponent, canActivate: [AuthGuard, LNDUnlockedGuard] },
|
||||
{ path: 'translist', component: ListTransactionsComponent, canActivate: [AuthGuard, LNDUnlockedGuard] },
|
||||
{ path: 'payments', component: PaymentsComponent, canActivate: [AuthGuard, LNDUnlockedGuard] },
|
||||
{ path: 'invoices', component: InvoicesComponent, canActivate: [AuthGuard, LNDUnlockedGuard] },
|
||||
{ path: 'switch', component: ForwardingHistoryComponent, canActivate: [AuthGuard, LNDUnlockedGuard] },
|
||||
{ path: 'lookups', component: LookupsComponent, canActivate: [AuthGuard, LNDUnlockedGuard] },
|
||||
{ path: 'sconfig', component: ServerConfigComponent, canActivate: [AuthGuard] },
|
||||
{ path: 'login', component: SigninComponent },
|
||||
{ path: 'help', component: HelpComponent },
|
||||
{ path: 'ssoerror', component: SsoFailedComponent },
|
||||
{ path: '**', component: NotFoundComponent }
|
||||
];
|
||||
|
||||
export const routing: ModuleWithProviders = RouterModule.forRoot(routes, { enableTracing: true });
|
@ -0,0 +1,64 @@
|
||||
<div fxLayout="column">
|
||||
<div class="padding-gap">
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-subtitle>
|
||||
<h2>Closed Channels</h2>
|
||||
</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="table-card-content">
|
||||
<div fxLayout="row" fxLayoutAlign="start start">
|
||||
<mat-form-field fxFlex="30">
|
||||
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div perfectScrollbar class="table-container mat-elevation-z8">
|
||||
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
|
||||
<table mat-table #table [dataSource]="closedChannels" matSort [ngClass]="{'mat-elevation-z8 overflow-auto error-border': flgLoading[0]==='error','mat-elevation-z8 overflow-auto': true}">
|
||||
<ng-container matColumnDef="close_type">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Close Type </th>
|
||||
<td mat-cell *matCellDef="let channel"> {{channel.close_type || 'COOPERATIVE_CLOSE'}} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="channel_point">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Channel Point </th>
|
||||
<td mat-cell *matCellDef="let channel"> {{channel.channel_point | slice:0:10}}...</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="chan_id">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> ID </th>
|
||||
<td mat-cell *matCellDef="let channel"> {{channel.chan_id}} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="closing_tx_hash">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Closing Txn Hash </th>
|
||||
<td mat-cell *matCellDef="let channel">
|
||||
<div>{{channel.closing_tx_hash | slice:0:10}}...</div></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="remote_pubkey">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Pub Key </th>
|
||||
<td mat-cell *matCellDef="let channel">
|
||||
<div>{{channel.remote_pubkey | slice:0:10}}...</div></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="capacity">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Capacity </th>
|
||||
<td mat-cell *matCellDef="let channel"><span fxLayoutAlign="end center"> {{channel.capacity | number}} </span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="close_height">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Close Height </th>
|
||||
<td mat-cell *matCellDef="let channel"><span fxLayoutAlign="end center"> {{channel.close_height | number}} </span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="settled_balance">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Settled Balance </th>
|
||||
<td mat-cell *matCellDef="let channel"><span fxLayoutAlign="end center"> {{channel.settled_balance | number}} </span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="time_locked_balance">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Time Locked Balance </th>
|
||||
<td mat-cell *matCellDef="let channel"><span fxLayoutAlign="end center"> {{channel.time_locked_balance | number}} </span></td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true;"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;" (click)="onClosedChannelClick(row, $event)"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -0,0 +1,25 @@
|
||||
.mat-column-close_type {
|
||||
flex: 0 0 16%;
|
||||
min-width: 160px;
|
||||
}
|
||||
|
||||
.mat-column-chan_id {
|
||||
flex: 0 0 17%;
|
||||
min-width: 170px;
|
||||
}
|
||||
|
||||
table {
|
||||
width:100%;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
height: 79vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 414px) {
|
||||
.table-container {
|
||||
height: 68vh;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ChannelClosedComponent } from './channel-closed.component';
|
||||
|
||||
describe('ChannelClosedComponent', () => {
|
||||
let component: ChannelClosedComponent;
|
||||
let fixture: ComponentFixture<ChannelClosedComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ChannelClosedComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ChannelClosedComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,104 @@
|
||||
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 { ClosedChannel } from '../../../shared/models/lndModels';
|
||||
import { LoggerService } from '../../../shared/services/logger.service';
|
||||
|
||||
import { RTLEffects } from '../../../shared/store/rtl.effects';
|
||||
import * as RTLActions from '../../../shared/store/rtl.actions';
|
||||
import * as fromRTLReducer from '../../../shared/store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-channel-closed',
|
||||
templateUrl: './channel-closed.component.html',
|
||||
styleUrls: ['./channel-closed.component.scss']
|
||||
})
|
||||
export class ChannelClosedComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(MatSort) sort: MatSort;
|
||||
public displayedColumns = [];
|
||||
public closedChannels: any;
|
||||
public flgLoading: Array<Boolean | 'error'> = [true];
|
||||
public selectedFilter = '';
|
||||
private unsub: Array<Subject<void>> = [new Subject(), new Subject()];
|
||||
|
||||
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.State>, private rtlEffects: RTLEffects) {
|
||||
switch (true) {
|
||||
case (window.innerWidth <= 415):
|
||||
this.displayedColumns = ['close_type', 'chan_id', 'settled_balance'];
|
||||
break;
|
||||
case (window.innerWidth > 415 && window.innerWidth <= 730):
|
||||
this.displayedColumns = ['close_type', 'channel_point', 'chan_id', 'settled_balance'];
|
||||
break;
|
||||
case (window.innerWidth > 730 && window.innerWidth <= 1024):
|
||||
this.displayedColumns = ['close_type', 'channel_point', 'chan_id', 'capacity', 'close_height', 'settled_balance'];
|
||||
break;
|
||||
case (window.innerWidth > 1024 && window.innerWidth <= 1280):
|
||||
this.displayedColumns = ['close_type', 'channel_point', 'chan_id', 'closing_tx_hash', 'remote_pubkey', 'capacity',
|
||||
'close_height', 'settled_balance', 'time_locked_balance'];
|
||||
break;
|
||||
default:
|
||||
this.displayedColumns = ['close_type', 'channel_point', 'chan_id', 'closing_tx_hash', 'remote_pubkey', 'capacity',
|
||||
'close_height', 'settled_balance', 'time_locked_balance'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.store.dispatch(new RTLActions.FetchChannels({routeParam: 'closed', channelStatus: ''}));
|
||||
this.store.select('rtlRoot')
|
||||
.pipe(takeUntil(this.unsub[0]))
|
||||
.subscribe((rtlStore: fromRTLReducer.State) => {
|
||||
rtlStore.effectErrors.forEach(effectsErr => {
|
||||
if (effectsErr.action === 'FetchChannels/closed') {
|
||||
this.flgLoading[0] = 'error';
|
||||
}
|
||||
});
|
||||
if (undefined !== rtlStore.closedChannels && rtlStore.closedChannels.length > 0) {
|
||||
this.loadClosedChannelsTable(rtlStore.closedChannels);
|
||||
}
|
||||
if (this.flgLoading[0] !== 'error') {
|
||||
this.flgLoading[0] = (undefined !== rtlStore.closedChannels) ? false : true;
|
||||
}
|
||||
this.logger.info(rtlStore);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
applyFilter(selFilter: string) {
|
||||
this.selectedFilter = selFilter;
|
||||
this.closedChannels.filter = selFilter;
|
||||
}
|
||||
|
||||
onClosedChannelClick(selRow: ClosedChannel, event: any) {
|
||||
const selChannel = this.closedChannels.data.filter(closedChannel => {
|
||||
return closedChannel.chan_id === selRow.chan_id;
|
||||
})[0];
|
||||
const reorderedChannel = JSON.parse(JSON.stringify(selChannel, ['close_type', 'channel_point', 'chan_id', 'closing_tx_hash', 'remote_pubkey', 'capacity',
|
||||
'close_height', 'settled_balance', 'time_locked_balance'] , 2));
|
||||
this.store.dispatch(new RTLActions.OpenAlert({ width: '75%', data: {
|
||||
type: 'INFO',
|
||||
message: JSON.stringify(reorderedChannel)
|
||||
}}));
|
||||
}
|
||||
|
||||
loadClosedChannelsTable(closedChannels) {
|
||||
this.closedChannels = new MatTableDataSource<ClosedChannel>([...closedChannels]);
|
||||
this.closedChannels.sort = this.sort;
|
||||
this.logger.info(this.closedChannels);
|
||||
}
|
||||
|
||||
resetData() {
|
||||
this.selectedFilter = '';
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unsub.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,131 @@
|
||||
<div fxLayout="column">
|
||||
<div class="padding-gap">
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-subtitle>
|
||||
<h2>Add 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.pub_key">
|
||||
{{peer.alias}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<mat-form-field fxFlex="25" fxLayoutAlign="start end">
|
||||
<input matInput [(ngModel)]="fundingAmount" placeholder="Amount ({{information?.smaller_currency_unit}})" 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">
|
||||
<mat-checkbox [(ngModel)]="moreOptions" name="moreOptions" (change)="onMoreOptionsChange($event)">Options</mat-checkbox>
|
||||
</div>
|
||||
<span *ngIf="moreOptions" fxLayout="column" fxLayout.gt-sm="row wrap" fxFlex="77">
|
||||
<div fxFlex="30" tabindex="4" fxLayoutAlign="start start">
|
||||
<mat-form-field fxFlex="99">
|
||||
<mat-select [(value)]="selTransType">
|
||||
<mat-option *ngFor="let transType of transTypes" [value]="transType.id">
|
||||
{{transType.name}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div fxFlex="30" fxLayoutAlign="start start">
|
||||
<mat-form-field fxFlex="90" fxFlex.lt-sm="100" *ngIf="selTransType=='0'">
|
||||
<input matInput placeholder="Channel Opening Priority" disabled>
|
||||
</mat-form-field>
|
||||
<mat-form-field fxFlex="90" fxFlex.lt-sm="100" *ngIf="selTransType=='1'">
|
||||
<input matInput [(ngModel)]="transTypeValue.blocks" placeholder="Target Confirmation Blocks" type="number" name="blocks" step="1" min="0" required tabindex="5" #blocks="ngModel">
|
||||
</mat-form-field>
|
||||
<mat-form-field fxFlex="90" fxFlex.lt-sm="100" *ngIf="selTransType=='2'">
|
||||
<input matInput [(ngModel)]="transTypeValue.fees" placeholder="Fee ({{information?.smaller_currency_unit}}/Byte)" type="number" name="fees" step="1" min="0" required tabindex="6" #fees="ngModel">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div fxFleax="40" fxLayoutAlign="start center">
|
||||
<mat-checkbox tabindex="7" [(ngModel)]="spendUnconfirmed" name="spendUnconfirmed">Spend Unconfirmed Output</mat-checkbox>
|
||||
</div>
|
||||
</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)" type="submit" tabindex="8">
|
||||
<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-raised-button color="accent" tabindex="9" type="reset" (click)="resetData()">Clear</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
<div class="padding-gap">
|
||||
<mat-card>
|
||||
<mat-card-content fxFlex="100" fxLayout="column">
|
||||
<div fxLayout="row" fxLayoutAlign="start start">
|
||||
<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-elevation-z8">
|
||||
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
|
||||
<table mat-table #table [dataSource]="channels" matSort [ngClass]="{'mat-elevation-z8 overflow-auto error-border': flgLoading[0]==='error','mat-elevation-z8 overflow-auto': true}">
|
||||
<ng-container matColumnDef="close">
|
||||
<th mat-header-cell *matHeaderCellDef> Disconnect </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="active">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Status </th>
|
||||
<td mat-cell *matCellDef="let channel"> {{channel.active}} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="chan_id">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> ID </th>
|
||||
<td mat-cell *matCellDef="let channel"> {{channel.chan_id}} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="remote_pubkey">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Pub Key </th>
|
||||
<td mat-cell *matCellDef="let channel">
|
||||
<div>{{channel.remote_pubkey | slice:0:10}}...</div></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="remote_alias">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Alias </th>
|
||||
<td mat-cell *matCellDef="let channel">{{channel.remote_alias}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="capacity">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Capacity </th>
|
||||
<td mat-cell *matCellDef="let channel"><span fxLayoutAlign="end center"> {{channel.capacity | number}} </span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="local_balance">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Local Bal </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 Bal </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"> {{information?.smaller_currency_unit}} 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"> {{information?.smaller_currency_unit}} Recv </th>
|
||||
<td mat-cell *matCellDef="let channel"><span fxLayoutAlign="end center"> {{channel.total_satoshis_received | number}} </span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="commit_fee">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Fee </th>
|
||||
<td mat-cell *matCellDef="let channel"><span fxLayoutAlign="end center"> {{channel.commit_fee | 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)"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -0,0 +1,50 @@
|
||||
.mat-column-close, .mat-column-update, .mat-column-active {
|
||||
flex: 0 0 6%;
|
||||
min-width: 50px;
|
||||
}
|
||||
|
||||
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%;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
height: 68vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 414px) {
|
||||
.table-container {
|
||||
height: 31vh;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ChannelManageComponent } from './channel-manage.component';
|
||||
|
||||
describe('ChannelManageComponent', () => {
|
||||
let component: ChannelManageComponent;
|
||||
let fixture: ComponentFixture<ChannelManageComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ChannelManageComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ChannelManageComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,251 @@
|
||||
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 { Channel, Peer, GetInfo } from '../../../shared/models/lndModels';
|
||||
import { LoggerService } from '../../../shared/services/logger.service';
|
||||
|
||||
import { RTLEffects } from '../../../shared/store/rtl.effects';
|
||||
import * as RTLActions from '../../../shared/store/rtl.actions';
|
||||
import * as fromRTLReducer from '../../../shared/store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-channel-manage',
|
||||
templateUrl: './channel-manage.component.html',
|
||||
styleUrls: ['./channel-manage.component.scss']
|
||||
})
|
||||
export class ChannelManageComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(MatSort) sort: MatSort;
|
||||
public totalBalance = 0;
|
||||
public selectedPeer = '';
|
||||
public fundingAmount: number;
|
||||
public displayedColumns = [];
|
||||
public channels: any;
|
||||
public peers: Peer[] = [];
|
||||
public information: GetInfo = {};
|
||||
public flgLoading: Array<Boolean | 'error'> = [true];
|
||||
public selectedFilter = '';
|
||||
public statusFilters = ['Active', 'Inactive'];
|
||||
public myChanPolicy: any = {};
|
||||
public selFilter = '';
|
||||
public flgSticky = true;
|
||||
public transTypes = [{id: '0', name: 'Default Priority'}, {id: '1', name: 'Target Confirmation Blocks'}, {id: '2', name: 'Fee'}];
|
||||
public selTransType = '0';
|
||||
public transTypeValue = {blocks: '', fees: ''};
|
||||
public spendUnconfirmed = false;
|
||||
public moreOptions = false;
|
||||
private unsub: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
|
||||
|
||||
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.State>, private rtlEffects: RTLEffects, private actions$: Actions) {
|
||||
switch (true) {
|
||||
case (window.innerWidth <= 415):
|
||||
this.displayedColumns = ['close', 'update', 'active', 'chan_id', 'remote_alias'];
|
||||
break;
|
||||
case (window.innerWidth > 415 && window.innerWidth <= 730):
|
||||
this.displayedColumns = ['close', 'update', 'active', 'chan_id', 'remote_alias', 'capacity'];
|
||||
break;
|
||||
case (window.innerWidth > 730 && window.innerWidth <= 1024):
|
||||
this.displayedColumns = ['close', 'update', 'active', 'chan_id', 'remote_alias', 'capacity', 'local_balance', 'remote_balance'];
|
||||
break;
|
||||
case (window.innerWidth > 1024 && window.innerWidth <= 1280):
|
||||
this.displayedColumns = ['close', 'update', 'active', 'chan_id', 'remote_alias', 'capacity', 'local_balance', 'remote_balance', 'total_satoshis_sent',
|
||||
'total_satoshis_received', 'commit_fee'];
|
||||
break;
|
||||
default:
|
||||
this.displayedColumns = ['close', 'update', 'active', 'chan_id', 'remote_pubkey', 'remote_alias', 'capacity', 'local_balance', 'remote_balance',
|
||||
'total_satoshis_sent', 'total_satoshis_received', 'commit_fee'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.store.select('rtlRoot')
|
||||
.pipe(takeUntil(this.unsub[0]))
|
||||
.subscribe((rtlStore: fromRTLReducer.State) => {
|
||||
rtlStore.effectErrors.forEach(effectsErr => {
|
||||
if (effectsErr.action === 'FetchChannels/all') {
|
||||
this.flgLoading[0] = 'error';
|
||||
}
|
||||
});
|
||||
this.information = rtlStore.information;
|
||||
this.peers = rtlStore.peers;
|
||||
this.peers.forEach(peer => {
|
||||
if (undefined === peer.alias || peer.alias === '') {
|
||||
peer.alias = peer.pub_key.substring(0, 15) + '...';
|
||||
}
|
||||
});
|
||||
|
||||
this.totalBalance = +rtlStore.blockchainBalance.total_balance;
|
||||
if (undefined !== rtlStore.allChannels && rtlStore.allChannels.length > 0) {
|
||||
this.loadChannelsTable(rtlStore.allChannels);
|
||||
}
|
||||
if (this.flgLoading[0] !== 'error') {
|
||||
this.flgLoading[0] = (undefined !== rtlStore.allChannels) ? false : true;
|
||||
}
|
||||
|
||||
this.logger.info(rtlStore);
|
||||
});
|
||||
}
|
||||
|
||||
onOpenChannel(form: any) {
|
||||
this.store.dispatch(new RTLActions.OpenSpinner('Opening Channel...'));
|
||||
let transTypeValue = '0';
|
||||
if (this.selTransType === '1') {
|
||||
transTypeValue = this.transTypeValue.blocks;
|
||||
} else if (this.selTransType === '2') {
|
||||
transTypeValue = this.transTypeValue.fees;
|
||||
}
|
||||
this.store.dispatch(new RTLActions.SaveNewChannel({
|
||||
selectedPeerPubkey: this.selectedPeer, fundingAmount: this.fundingAmount,
|
||||
transType: this.selTransType, transTypeValue: transTypeValue, spendUnconfirmed: this.spendUnconfirmed
|
||||
}));
|
||||
}
|
||||
|
||||
onChannelUpdate(channelToUpdate: any) {
|
||||
if (channelToUpdate === 'all') {
|
||||
const titleMsg = 'Updated Values for ALL Channels';
|
||||
const confirmationMsg = {};
|
||||
this.store.dispatch(new RTLActions.OpenConfirmation({ width: '70%', data: {
|
||||
type: 'CONFIRM', titleMessage: titleMsg, noBtnText: 'Cancel', yesBtnText: 'Update', message: JSON.stringify(confirmationMsg), flgShowInput: true, getInputs: [
|
||||
{placeholder: 'Base Fee msat', inputType: 'number', inputValue: 1000},
|
||||
{placeholder: 'Fee Rate mili msat', inputType: 'number', inputValue: 1, min: 1},
|
||||
{placeholder: 'Time Lock Delta', inputType: 'number', inputValue: 144}
|
||||
]
|
||||
}}));
|
||||
this.rtlEffects.closeConfirm
|
||||
.pipe(takeUntil(this.unsub[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.UpdateChannels({baseFeeMsat: base_fee, feeRate: fee_rate, timeLockDelta: time_lock_delta, chanPoint: '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.ChannelLookup(channelToUpdate.chan_id.toString()));
|
||||
this.rtlEffects.setLookup
|
||||
.pipe(takeUntil(this.unsub[3]))
|
||||
.subscribe(resLookup => {
|
||||
this.logger.info(resLookup);
|
||||
if (resLookup.node1_pub === this.information.identity_pubkey) {
|
||||
this.myChanPolicy = resLookup.node1_policy;
|
||||
} else if (resLookup.node2_pub === this.information.identity_pubkey) {
|
||||
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 = 'Updated Values for Channel Point: ' + channelToUpdate.channel_point;
|
||||
const confirmationMsg = {};
|
||||
this.store.dispatch(new RTLActions.OpenConfirmation({ width: '70%', data: {
|
||||
type: 'CONFIRM', titleMessage: titleMsg, noBtnText: 'Cancel', yesBtnText: 'Update', message: JSON.stringify(confirmationMsg), flgShowInput: true, getInputs: [
|
||||
{placeholder: 'Base Fee msat', inputType: 'number', inputValue: (this.myChanPolicy.fee_base_msat === '') ? 0 : this.myChanPolicy.fee_base_msat},
|
||||
{placeholder: 'Fee Rate mili msat', inputType: 'number', inputValue: this.myChanPolicy.fee_rate_milli_msat, min: 1},
|
||||
{placeholder: 'Time Lock Delta', inputType: 'number', inputValue: this.myChanPolicy.time_lock_delta}
|
||||
]
|
||||
}}));
|
||||
});
|
||||
this.rtlEffects.closeConfirm
|
||||
.pipe(takeUntil(this.unsub[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.UpdateChannels({baseFeeMsat: base_fee, feeRate: fee_rate, timeLockDelta: time_lock_delta, chanPoint: channelToUpdate.channel_point}));
|
||||
}
|
||||
});
|
||||
}
|
||||
this.applyFilter();
|
||||
}
|
||||
|
||||
onChannelClose(channelToClose: Channel) {
|
||||
this.store.dispatch(new RTLActions.OpenConfirmation({
|
||||
width: '70%', data: { type: 'CONFIRM', titleMessage: 'Closing channel: ' + channelToClose.chan_id, noBtnText: 'Cancel', yesBtnText: 'Disconnect'
|
||||
}}));
|
||||
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.CloseChannel({channelPoint: channelToClose.channel_point, forcibly: true, channelStatus: channelToClose.active}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
applyFilter() {
|
||||
this.selectedFilter = this.selFilter;
|
||||
this.channels.filter = this.selFilter;
|
||||
}
|
||||
|
||||
onChannelClick(selRow: Channel, 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.chan_id === selRow.chan_id;
|
||||
})[0];
|
||||
const reorderedChannel = JSON.parse(JSON.stringify(selChannel, [
|
||||
'active', 'remote_pubkey', 'remote_alias', 'channel_point', 'chan_id', 'capacity', 'local_balance', 'remote_balance', 'commit_fee', 'commit_weight',
|
||||
'fee_per_kw', 'unsettled_balance', 'total_satoshis_sent', 'total_satoshis_received', 'num_updates', 'pending_htlcs', 'csv_delay', 'private'
|
||||
] , 2));
|
||||
this.store.dispatch(new RTLActions.OpenAlert({ width: '75%', data: {
|
||||
type: 'INFO',
|
||||
message: JSON.stringify(reorderedChannel)
|
||||
}}));
|
||||
}
|
||||
|
||||
loadChannelsTable(channels) {
|
||||
channels.sort(function(a, b) {
|
||||
return (a.active === b.active) ? 0 : ((b.active) ? 1 : -1);
|
||||
});
|
||||
channels.forEach(channel => {
|
||||
if (channel.active === true || channel.active === 'Active') {
|
||||
channel.active = 'Active';
|
||||
} else {
|
||||
channel.active = 'Inactive';
|
||||
}
|
||||
});
|
||||
this.channels = new MatTableDataSource<Channel>([...channels]);
|
||||
this.channels.sort = this.sort;
|
||||
this.logger.info(this.channels);
|
||||
}
|
||||
|
||||
resetData() {
|
||||
this.selectedPeer = '';
|
||||
this.fundingAmount = 0;
|
||||
this.moreOptions = false;
|
||||
this.spendUnconfirmed = false;
|
||||
this.selTransType = '0';
|
||||
this.transTypeValue = {blocks: '', fees: ''};
|
||||
}
|
||||
|
||||
onMoreOptionsChange(event: any) {
|
||||
if (!event.checked) {
|
||||
this.spendUnconfirmed = false;
|
||||
this.selTransType = '0';
|
||||
this.transTypeValue = {blocks: '', fees: ''};
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unsub.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,252 @@
|
||||
<div fxLayout="column">
|
||||
<div fxFlex="100" class="padding-gap">
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-subtitle>
|
||||
<h2>Pending Channels</h2>
|
||||
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
|
||||
</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<div class="padding-gap">
|
||||
<h3 *ngIf="settings?.satsToBTC; else smallerUnit">Total Limbo Balance:
|
||||
{{pendingChannels.btc_total_limbo_balance | number}} {{information?.currency_unit}}</h3>
|
||||
<ng-template #smallerUnit>
|
||||
<h3>Total Limbo Balance: {{pendingChannels.total_limbo_balance | number}}
|
||||
{{information?.smaller_currency_unit}}</h3>
|
||||
</ng-template>
|
||||
</div>
|
||||
<div class="">
|
||||
<mat-accordion>
|
||||
<mat-expansion-panel displayMode="flat">
|
||||
<mat-expansion-panel-header class="pl-1 pr-1">
|
||||
<mat-panel-title>
|
||||
<h3>Pending Open Channels({{pendingOpenChannelsLength}})</h3>
|
||||
</mat-panel-title>
|
||||
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
|
||||
</mat-expansion-panel-header>
|
||||
<mat-table #table perfectScrollbar [dataSource]="pendingOpenChannels" matSort
|
||||
[ngClass]="{'mat-elevation-z8 overflow-auto error-border': flgLoading[0]==='error','mat-elevation-z8 overflow-auto': true}">
|
||||
<ng-container matColumnDef="remote_node_pub">
|
||||
<mat-header-cell class="pl-2" *matHeaderCellDef mat-sort-header> Remote Node Pub </mat-header-cell>
|
||||
<mat-cell class="pl-2" *matCellDef="let channel">{{channel.channel.remote_node_pub}}</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="local_balance">
|
||||
<mat-header-cell fxLayoutAlign="end center" *matHeaderCellDef mat-sort-header arrowPosition="before">
|
||||
Local Balance </mat-header-cell>
|
||||
<mat-cell fxLayoutAlign="end center" *matCellDef="let channel">{{channel.channel.local_balance |
|
||||
number}}</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="commit_fee">
|
||||
<mat-header-cell fxLayoutAlign="end center" *matHeaderCellDef mat-sort-header arrowPosition="before">
|
||||
Commit Fee </mat-header-cell>
|
||||
<mat-cell fxLayoutAlign="end center" *matCellDef="let channel">{{channel.commit_fee | number}}
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="remote_balance">
|
||||
<mat-header-cell fxLayoutAlign="end center" *matHeaderCellDef mat-sort-header arrowPosition="before">
|
||||
Remote Balance </mat-header-cell>
|
||||
<mat-cell fxLayoutAlign="end center" *matCellDef="let channel">{{channel.channel.remote_balance |
|
||||
number}}</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="capacity">
|
||||
<mat-header-cell fxLayoutAlign="end center" *matHeaderCellDef mat-sort-header arrowPosition="before">
|
||||
Capacity </mat-header-cell>
|
||||
<mat-cell fxLayoutAlign="end center" *matCellDef="let channel">{{channel.channel.capacity |
|
||||
number}}</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="commit_weight">
|
||||
<mat-header-cell fxLayoutAlign="end center" *matHeaderCellDef mat-sort-header arrowPosition="before">
|
||||
Commit Weight </mat-header-cell>
|
||||
<mat-cell fxLayoutAlign="end center" *matCellDef="let channel">{{channel.commit_weight | number}}
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="fee_per_kw">
|
||||
<mat-header-cell fxLayoutAlign="end center" *matHeaderCellDef mat-sort-header arrowPosition="before">
|
||||
Fee Per KW </mat-header-cell>
|
||||
<mat-cell fxLayoutAlign="end center" *matCellDef="let channel">{{channel.fee_per_kw | number}}
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="confirmation_height">
|
||||
<mat-header-cell fxLayoutAlign="end center" *matHeaderCellDef mat-sort-header arrowPosition="before">
|
||||
Confirmation Height </mat-header-cell>
|
||||
<mat-cell fxLayoutAlign="end center" *matCellDef="let channel">{{channel.confirmation_height |
|
||||
number}}</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="channel_point">
|
||||
<mat-header-cell class="pl-2" *matHeaderCellDef mat-sort-header> Channel Point </mat-header-cell>
|
||||
<mat-cell class="pl-2" *matCellDef="let channel">{{channel.channel.channel_point}}</mat-cell>
|
||||
</ng-container>
|
||||
<mat-header-row *matHeaderRowDef="displayedOpenColumns"></mat-header-row>
|
||||
<mat-row fxLayoutAlign="stretch stretch" *matRowDef="let row; columns: displayedOpenColumns;"
|
||||
(click)="onOpenClick(row, $event)"></mat-row>
|
||||
</mat-table>
|
||||
</mat-expansion-panel>
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header class="pl-1 pr-1">
|
||||
<mat-panel-title>
|
||||
<h3>Pending Force Closing Channels({{pendingForceClosingChannelsLength}})</h3>
|
||||
</mat-panel-title>
|
||||
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
|
||||
</mat-expansion-panel-header>
|
||||
<mat-table #table perfectScrollbar [dataSource]="pendingForceClosingChannels" matSort
|
||||
[ngClass]="{'mat-elevation-z8 overflow-auto error-border': flgLoading[0]==='error','mat-elevation-z8 overflow-auto': true}">
|
||||
<ng-container matColumnDef="remote_node_pub">
|
||||
<mat-header-cell class="pl-2" *matHeaderCellDef mat-sort-header> Remote Node Pub </mat-header-cell>
|
||||
<mat-cell class="pl-2" *matCellDef="let channel">{{channel.channel.remote_node_pub}}</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="recovered_balance">
|
||||
<mat-header-cell fxLayoutAlign="end center" *matHeaderCellDef mat-sort-header arrowPosition="before">
|
||||
Recovered Balance </mat-header-cell>
|
||||
<mat-cell fxLayoutAlign="end center" *matCellDef="let channel">{{channel.recovered_balance |
|
||||
number}}</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="limbo_balance">
|
||||
<mat-header-cell fxLayoutAlign="end center" *matHeaderCellDef mat-sort-header arrowPosition="before">
|
||||
Limbo Balance </mat-header-cell>
|
||||
<mat-cell fxLayoutAlign="end center" *matCellDef="let channel">{{channel.limbo_balance | number}}
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="blocks_til_maturity">
|
||||
<mat-header-cell fxLayoutAlign="end center" *matHeaderCellDef mat-sort-header arrowPosition="before">
|
||||
Block Till Maturity </mat-header-cell>
|
||||
<mat-cell fxLayoutAlign="end center" *matCellDef="let channel">{{channel.blocks_til_maturity |
|
||||
number}}</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="maturity_height">
|
||||
<mat-header-cell fxLayoutAlign="end center" *matHeaderCellDef mat-sort-header arrowPosition="before">
|
||||
Maturity Height </mat-header-cell>
|
||||
<mat-cell fxLayoutAlign="end center" *matCellDef="let channel">{{channel.maturity_height | number}}
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="local_balance">
|
||||
<mat-header-cell fxLayoutAlign="end center" *matHeaderCellDef mat-sort-header arrowPosition="before">
|
||||
Local Balance </mat-header-cell>
|
||||
<mat-cell fxLayoutAlign="end center" *matCellDef="let channel">{{channel.channel.local_balance |
|
||||
number}}</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="remote_balance">
|
||||
<mat-header-cell fxLayoutAlign="end center" *matHeaderCellDef mat-sort-header arrowPosition="before">
|
||||
Remote Balance </mat-header-cell>
|
||||
<mat-cell fxLayoutAlign="end center" *matCellDef="let channel">{{channel.channel.remote_balance |
|
||||
number}}</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="capacity">
|
||||
<mat-header-cell fxLayoutAlign="end center" *matHeaderCellDef mat-sort-header arrowPosition="before">
|
||||
Capacity </mat-header-cell>
|
||||
<mat-cell fxLayoutAlign="end center" *matCellDef="let channel">{{channel.channel.capacity |
|
||||
number}}</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="closing_txid">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header> Transaction Id </mat-header-cell>
|
||||
<mat-cell *matCellDef="let channel">
|
||||
<div class="flex-ellipsis">{{channel.closing_txid}}</div>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="channel_point">
|
||||
<mat-header-cell class="pl-2" *matHeaderCellDef mat-sort-header> Channel Point </mat-header-cell>
|
||||
<mat-cell class="pl-2" *matCellDef="let channel">{{channel.channel.channel_point}}</mat-cell>
|
||||
</ng-container>
|
||||
<mat-header-row *matHeaderRowDef="displayedForceClosingColumns"></mat-header-row>
|
||||
<mat-row fxLayoutAlign="stretch stretch" *matRowDef="let row; columns: displayedForceClosingColumns;"
|
||||
(click)="onForceClosingClick(row, $event)"></mat-row>
|
||||
</mat-table>
|
||||
</mat-expansion-panel>
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header class="pl-1 pr-1">
|
||||
<mat-panel-title>
|
||||
<h3>Pending Closing Channels({{pendingClosingChannelsLength}})</h3>
|
||||
</mat-panel-title>
|
||||
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
|
||||
</mat-expansion-panel-header>
|
||||
<mat-table #table perfectScrollbar [dataSource]="pendingClosingChannels" matSort
|
||||
[ngClass]="{'mat-elevation-z8 overflow-auto error-border': flgLoading[0]==='error','mat-elevation-z8 overflow-auto': true}">
|
||||
<ng-container matColumnDef="remote_node_pub">
|
||||
<mat-header-cell class="pl-2" *matHeaderCellDef mat-sort-header> Remote Node Pub </mat-header-cell>
|
||||
<mat-cell class="pl-2" *matCellDef="let channel">{{channel.channel.remote_node_pub}}</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="local_balance">
|
||||
<mat-header-cell fxLayoutAlign="end center" *matHeaderCellDef mat-sort-header arrowPosition="before">
|
||||
Local Balance </mat-header-cell>
|
||||
<mat-cell fxLayoutAlign="end center" *matCellDef="let channel">{{channel.channel.local_balance |
|
||||
number}}</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="remote_balance">
|
||||
<mat-header-cell fxLayoutAlign="end center" *matHeaderCellDef mat-sort-header arrowPosition="before">
|
||||
Remote Balance </mat-header-cell>
|
||||
<mat-cell fxLayoutAlign="end center" *matCellDef="let channel">{{channel.channel.remote_balance |
|
||||
number}}</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="capacity">
|
||||
<mat-header-cell fxLayoutAlign="end center" *matHeaderCellDef mat-sort-header arrowPosition="before">
|
||||
Capacity </mat-header-cell>
|
||||
<mat-cell fxLayoutAlign="end center" *matCellDef="let channel">{{channel.channel.capacity |
|
||||
number}}</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="closing_txid">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header> Transaction Id </mat-header-cell>
|
||||
<mat-cell *matCellDef="let channel">
|
||||
<div class="flex-ellipsis">{{channel.closing_txid}}</div>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="channel_point">
|
||||
<mat-header-cell class="pl-2" *matHeaderCellDef mat-sort-header> Channel Point </mat-header-cell>
|
||||
<mat-cell class="pl-2" *matCellDef="let channel">{{channel.channel.channel_point}}</mat-cell>
|
||||
</ng-container>
|
||||
<mat-header-row *matHeaderRowDef="displayedClosingColumns"></mat-header-row>
|
||||
<mat-row fxLayoutAlign="stretch stretch" *matRowDef="let row; columns: displayedClosingColumns;"
|
||||
(click)="onClosingClick(row, $event)"></mat-row>
|
||||
</mat-table>
|
||||
</mat-expansion-panel>
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header class="pl-1 pr-1">
|
||||
<mat-panel-title>
|
||||
<h3>Waiting Close Channels({{pendingWaitClosingChannelsLength}})</h3>
|
||||
</mat-panel-title>
|
||||
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
|
||||
</mat-expansion-panel-header>
|
||||
<mat-table #table perfectScrollbar [dataSource]="pendingWaitClosingChannels" matSort
|
||||
[ngClass]="{'mat-elevation-z8 overflow-auto error-border': flgLoading[0]==='error','mat-elevation-z8 overflow-auto': true}">
|
||||
<ng-container matColumnDef="remote_node_pub">
|
||||
<mat-header-cell class="pl-2" *matHeaderCellDef mat-sort-header> Remote Node Pub </mat-header-cell>
|
||||
<mat-cell class="pl-2" *matCellDef="let channel">{{channel.channel.remote_node_pub}}</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="limbo_balance">
|
||||
<mat-header-cell fxLayoutAlign="end center" *matHeaderCellDef mat-sort-header arrowPosition="before">
|
||||
Limbo Balance </mat-header-cell>
|
||||
<mat-cell fxLayoutAlign="end center" *matCellDef="let channel">{{channel.limbo_balance | number}}
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="local_balance">
|
||||
<mat-header-cell fxLayoutAlign="end center" *matHeaderCellDef mat-sort-header arrowPosition="before">
|
||||
Local Balance </mat-header-cell>
|
||||
<mat-cell fxLayoutAlign="end center" *matCellDef="let channel">{{channel.channel.local_balance |
|
||||
number}}</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="remote_balance">
|
||||
<mat-header-cell fxLayoutAlign="end center" *matHeaderCellDef mat-sort-header arrowPosition="before">
|
||||
Remote Balance </mat-header-cell>
|
||||
<mat-cell fxLayoutAlign="end center" *matCellDef="let channel">{{channel.channel.remote_balance |
|
||||
number}}</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="capacity">
|
||||
<mat-header-cell fxLayoutAlign="end center" *matHeaderCellDef mat-sort-header arrowPosition="before">
|
||||
Capacity </mat-header-cell>
|
||||
<mat-cell fxLayoutAlign="end center" *matCellDef="let channel">{{channel.channel.capacity |
|
||||
number}}</mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="channel_point">
|
||||
<mat-header-cell class="pl-2" *matHeaderCellDef mat-sort-header> Channel Point </mat-header-cell>
|
||||
<mat-cell class="pl-2" *matCellDef="let channel">{{channel.channel.channel_point}}</mat-cell>
|
||||
</ng-container>
|
||||
<mat-header-row *matHeaderRowDef="displayedWaitClosingColumns"></mat-header-row>
|
||||
<mat-row fxLayoutAlign="stretch stretch" *matRowDef="let row; columns: displayedWaitClosingColumns;"
|
||||
(click)="onWaitClosingClick(row, $event)"></mat-row>
|
||||
</mat-table>
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,6 @@
|
||||
.flex-ellipsis {
|
||||
padding-right: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ChannelPendingComponent } from './channel-pending.component';
|
||||
|
||||
describe('ChannelPendingComponent', () => {
|
||||
let component: ChannelPendingComponent;
|
||||
let fixture: ComponentFixture<ChannelPendingComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ChannelPendingComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ChannelPendingComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,241 @@
|
||||
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 { Channel, GetInfo, PendingChannels } from '../../../shared/models/lndModels';
|
||||
import { Settings } from '../../../shared/models/RTLconfig';
|
||||
import { LoggerService } from '../../../shared/services/logger.service';
|
||||
|
||||
import { RTLEffects } from '../../../shared/store/rtl.effects';
|
||||
import * as RTLActions from '../../../shared/store/rtl.actions';
|
||||
import * as fromRTLReducer from '../../../shared/store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-channel-pending',
|
||||
templateUrl: './channel-pending.component.html',
|
||||
styleUrls: ['./channel-pending.component.scss']
|
||||
})
|
||||
export class ChannelPendingComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(MatSort) sort: MatSort;
|
||||
public selectedFilter = 0;
|
||||
public settings: Settings;
|
||||
public information: GetInfo = {};
|
||||
public pendingChannels: PendingChannels = {};
|
||||
public displayedClosingColumns = [
|
||||
'closing_txid',
|
||||
'channel_point', 'remote_balance', 'local_balance', 'remote_node_pub', 'capacity'
|
||||
];
|
||||
public pendingClosingChannelsLength = 0;
|
||||
public pendingClosingChannels: any;
|
||||
public displayedForceClosingColumns = [
|
||||
'closing_txid', 'limbo_balance', 'maturity_height', 'blocks_til_maturity', 'recovered_balance',
|
||||
'channel_point', 'remote_balance', 'local_balance', 'remote_node_pub', 'capacity'
|
||||
];
|
||||
public pendingForceClosingChannelsLength = 0;
|
||||
public pendingForceClosingChannels: any;
|
||||
public displayedOpenColumns = [
|
||||
'commit_weight', 'confirmation_height', 'fee_per_kw', 'commit_fee',
|
||||
'channel_point', 'remote_balance', 'local_balance', 'remote_node_pub', 'capacity'
|
||||
];
|
||||
public pendingOpenChannelsLength = 0;
|
||||
public pendingOpenChannels: any;
|
||||
public displayedWaitClosingColumns = [
|
||||
'limbo_balance',
|
||||
'channel_point', 'remote_balance', 'local_balance', 'remote_node_pub', 'capacity'
|
||||
];
|
||||
public pendingWaitClosingChannelsLength = 0;
|
||||
public pendingWaitClosingChannels: any;
|
||||
public flgLoading: Array<Boolean | 'error'> = [true];
|
||||
private unsub: Array<Subject<void>> = [new Subject(), new Subject()];
|
||||
|
||||
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.State>, private rtlEffects: RTLEffects) {
|
||||
switch (true) {
|
||||
case (window.innerWidth <= 415):
|
||||
this.displayedClosingColumns = ['remote_node_pub', 'local_balance', 'remote_balance'];
|
||||
this.displayedForceClosingColumns = ['remote_node_pub', 'recovered_balance', 'limbo_balance'];
|
||||
this.displayedOpenColumns = ['remote_node_pub', 'local_balance', 'commit_fee'];
|
||||
this.displayedWaitClosingColumns = ['remote_node_pub', 'limbo_balance', 'local_balance'];
|
||||
break;
|
||||
case (window.innerWidth > 415 && window.innerWidth <= 730):
|
||||
this.displayedClosingColumns = ['remote_node_pub', 'local_balance', 'remote_balance', 'capacity'];
|
||||
this.displayedForceClosingColumns = ['remote_node_pub', 'recovered_balance', 'limbo_balance', 'blocks_til_maturity', 'maturity_height'];
|
||||
this.displayedOpenColumns = ['remote_node_pub', 'local_balance', 'commit_fee', 'remote_balance'];
|
||||
this.displayedWaitClosingColumns = ['remote_node_pub', 'limbo_balance', 'local_balance', 'remote_balance'];
|
||||
break;
|
||||
case (window.innerWidth > 730 && window.innerWidth <= 1024):
|
||||
this.displayedClosingColumns = ['remote_node_pub', 'local_balance', 'remote_balance', 'capacity', 'closing_txid'];
|
||||
this.displayedForceClosingColumns = ['remote_node_pub', 'recovered_balance', 'limbo_balance', 'blocks_til_maturity', 'maturity_height', 'local_balance'];
|
||||
this.displayedOpenColumns = ['remote_node_pub', 'local_balance', 'commit_fee', 'remote_balance', 'capacity'];
|
||||
this.displayedWaitClosingColumns = ['remote_node_pub', 'limbo_balance', 'local_balance', 'remote_balance', 'capacity', 'channel_point'];
|
||||
break;
|
||||
case (window.innerWidth > 1024 && window.innerWidth <= 1280):
|
||||
this.displayedClosingColumns = ['remote_node_pub', 'local_balance', 'remote_balance', 'capacity', 'closing_txid', 'channel_point'];
|
||||
this.displayedForceClosingColumns = [
|
||||
'remote_node_pub', 'recovered_balance', 'limbo_balance', 'blocks_til_maturity',
|
||||
'maturity_height', 'local_balance', 'remote_balance', 'capacity', 'closing_txid', 'channel_point'
|
||||
];
|
||||
this.displayedOpenColumns = [
|
||||
'remote_node_pub', 'local_balance', 'commit_fee', 'remote_balance', 'capacity', 'commit_weight', 'fee_per_kw', 'confirmation_height', 'channel_point'
|
||||
];
|
||||
this.displayedWaitClosingColumns = ['remote_node_pub', 'limbo_balance', 'local_balance', 'remote_balance', 'capacity', 'channel_point'];
|
||||
break;
|
||||
default:
|
||||
this.displayedClosingColumns = ['remote_node_pub', 'local_balance', 'remote_balance', 'capacity', 'closing_txid', 'channel_point'];
|
||||
this.displayedForceClosingColumns = [
|
||||
'remote_node_pub', 'recovered_balance', 'limbo_balance', 'blocks_til_maturity',
|
||||
'maturity_height', 'local_balance', 'remote_balance', 'capacity', 'closing_txid', 'channel_point'
|
||||
];
|
||||
this.displayedOpenColumns = [
|
||||
'remote_node_pub', 'local_balance', 'commit_fee', 'remote_balance', 'capacity', 'commit_weight', 'fee_per_kw', 'confirmation_height', 'channel_point'
|
||||
];
|
||||
this.displayedWaitClosingColumns = ['remote_node_pub', 'limbo_balance', 'local_balance', 'remote_balance', 'capacity', 'channel_point'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.store.select('rtlRoot')
|
||||
.pipe(takeUntil(this.unsub[0]))
|
||||
.subscribe((rtlStore: fromRTLReducer.State) => {
|
||||
rtlStore.effectErrors.forEach(effectsErr => {
|
||||
if (effectsErr.action === 'FetchChannels/pending') {
|
||||
this.flgLoading[0] = 'error';
|
||||
}
|
||||
});
|
||||
|
||||
this.settings = rtlStore.settings;
|
||||
this.information = rtlStore.information;
|
||||
this.pendingChannels = rtlStore.pendingChannels;
|
||||
if (undefined !== this.pendingChannels.total_limbo_balance) {
|
||||
this.flgLoading[1] = false;
|
||||
if (undefined !== this.pendingChannels.pending_closing_channels) {
|
||||
this.loadClosingChannelsTable(this.pendingChannels.pending_closing_channels);
|
||||
}
|
||||
if (undefined !== this.pendingChannels.pending_force_closing_channels) {
|
||||
this.loadForceClosingChannelsTable(this.pendingChannels.pending_force_closing_channels);
|
||||
}
|
||||
if (undefined !== this.pendingChannels.pending_open_channels) {
|
||||
this.loadOpenChannelsTable(this.pendingChannels.pending_open_channels);
|
||||
}
|
||||
if (undefined !== this.pendingChannels.waiting_close_channels) {
|
||||
this.loadWaitClosingChannelsTable(this.pendingChannels.waiting_close_channels);
|
||||
}
|
||||
}
|
||||
if (this.flgLoading[0] !== 'error') {
|
||||
this.flgLoading[0] = (undefined !== this.information.identity_pubkey) ? false : true;
|
||||
}
|
||||
this.logger.info(rtlStore);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
onOpenClick(selRow: any, event: any) {
|
||||
const selChannel = this.pendingOpenChannels.data.filter(channel => {
|
||||
return channel.channel.channel_point === selRow.channel.channel_point;
|
||||
})[0];
|
||||
const fcChannelObj1 = JSON.parse(JSON.stringify(selChannel, ['commit_weight', 'confirmation_height', 'fee_per_kw', 'commit_fee'], 2));
|
||||
const fcChannelObj2 = JSON.parse(JSON.stringify(selChannel.channel, ['channel_point', 'remote_balance', 'local_balance', 'remote_node_pub', 'capacity'], 2));
|
||||
const reorderedChannel = {};
|
||||
Object.assign(reorderedChannel, fcChannelObj1, fcChannelObj2);
|
||||
this.store.dispatch(new RTLActions.OpenAlert({ width: '75%', data: {
|
||||
type: 'INFO',
|
||||
message: JSON.stringify(reorderedChannel)
|
||||
}}));
|
||||
}
|
||||
|
||||
onForceClosingClick(selRow: any, event: any) {
|
||||
const selChannel = this.pendingForceClosingChannels.data.filter(channel => {
|
||||
return channel.channel.channel_point === selRow.channel.channel_point;
|
||||
})[0];
|
||||
const fcChannelObj1 = JSON.parse(JSON.stringify(selChannel, ['closing_txid', 'limbo_balance', 'maturity_height', 'blocks_til_maturity', 'recovered_balance'], 2));
|
||||
const fcChannelObj2 = JSON.parse(JSON.stringify(selChannel.channel, ['channel_point', 'remote_balance', 'local_balance', 'remote_node_pub', 'capacity'], 2));
|
||||
const reorderedChannel = {};
|
||||
Object.assign(reorderedChannel, fcChannelObj1, fcChannelObj2);
|
||||
this.store.dispatch(new RTLActions.OpenAlert({ width: '75%', data: {
|
||||
type: 'INFO',
|
||||
message: JSON.stringify(reorderedChannel)
|
||||
}}));
|
||||
}
|
||||
|
||||
onClosingClick(selRow: any, event: any) {
|
||||
const selChannel = this.pendingClosingChannels.data.filter(channel => {
|
||||
return channel.channel.channel_point === selRow.channel.channel_point;
|
||||
})[0];
|
||||
const fcChannelObj1 = JSON.parse(JSON.stringify(selChannel, ['closing_txid'], 2));
|
||||
const fcChannelObj2 = JSON.parse(JSON.stringify(selChannel.channel, ['channel_point', 'remote_balance', 'local_balance', 'remote_node_pub', 'capacity'], 2));
|
||||
const reorderedChannel = {};
|
||||
Object.assign(reorderedChannel, fcChannelObj1, fcChannelObj2);
|
||||
this.store.dispatch(new RTLActions.OpenAlert({ width: '75%', data: {
|
||||
type: 'INFO',
|
||||
message: JSON.stringify(reorderedChannel)
|
||||
}}));
|
||||
}
|
||||
|
||||
onWaitClosingClick(selRow: any, event: any) {
|
||||
const selChannel = this.pendingWaitClosingChannels.data.filter(channel => {
|
||||
return channel.channel.channel_point === selRow.channel.channel_point;
|
||||
})[0];
|
||||
const fcChannelObj1 = JSON.parse(JSON.stringify(selChannel, ['limbo_balance'], 2));
|
||||
const fcChannelObj2 = JSON.parse(JSON.stringify(selChannel.channel, ['channel_point', 'remote_balance', 'local_balance', 'remote_node_pub', 'capacity'], 2));
|
||||
const reorderedChannel = {};
|
||||
Object.assign(reorderedChannel, fcChannelObj1, fcChannelObj2);
|
||||
this.store.dispatch(new RTLActions.OpenAlert({ width: '75%', data: {
|
||||
type: 'INFO',
|
||||
message: JSON.stringify(reorderedChannel)
|
||||
}}));
|
||||
}
|
||||
|
||||
loadOpenChannelsTable(channels) {
|
||||
channels.sort(function(a, b) {
|
||||
return (a.active === b.active) ? 0 : ((b.active) ? -1 : 1);
|
||||
});
|
||||
this.pendingOpenChannelsLength = (undefined !== channels.length) ? channels.length : 0;
|
||||
this.pendingOpenChannels = new MatTableDataSource<Channel>([...channels]);
|
||||
this.pendingOpenChannels.sort = this.sort;
|
||||
this.logger.info(this.pendingOpenChannels);
|
||||
}
|
||||
|
||||
loadForceClosingChannelsTable(channels) {
|
||||
channels.sort(function(a, b) {
|
||||
return (a.active === b.active) ? 0 : ((b.active) ? -1 : 1);
|
||||
});
|
||||
this.pendingForceClosingChannelsLength = (undefined !== channels.length) ? channels.length : 0;
|
||||
this.pendingForceClosingChannels = new MatTableDataSource<Channel>([...channels]);
|
||||
this.pendingForceClosingChannels.sort = this.sort;
|
||||
this.logger.info(this.pendingForceClosingChannels);
|
||||
}
|
||||
|
||||
loadClosingChannelsTable(channels) {
|
||||
channels.sort(function(a, b) {
|
||||
return (a.active === b.active) ? 0 : ((b.active) ? -1 : 1);
|
||||
});
|
||||
this.pendingClosingChannelsLength = (undefined !== channels.length) ? channels.length : 0;
|
||||
this.pendingClosingChannels = new MatTableDataSource<Channel>([...channels]);
|
||||
this.pendingClosingChannels.sort = this.sort;
|
||||
this.logger.info(this.pendingClosingChannels);
|
||||
}
|
||||
|
||||
loadWaitClosingChannelsTable(channels) {
|
||||
channels.sort(function(a, b) {
|
||||
return (a.active === b.active) ? 0 : ((b.active) ? -1 : 1);
|
||||
});
|
||||
this.pendingWaitClosingChannelsLength = (undefined !== channels.length) ? channels.length : 0;
|
||||
this.pendingWaitClosingChannels = new MatTableDataSource<Channel>([...channels]);
|
||||
this.pendingWaitClosingChannels.sort = this.sort;
|
||||
this.logger.info(this.pendingWaitClosingChannels);
|
||||
}
|
||||
|
||||
applyFilter(selFilter: number) {
|
||||
this.selectedFilter = selFilter;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unsub.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
20
src/app/pages/help/help.component.html
Normal file
@ -0,0 +1,20 @@
|
||||
<div fxLayout="column">
|
||||
<div class="padding-gap">
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-subtitle>
|
||||
<h2>Help</h2>
|
||||
</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content *ngFor="let helpTopic of helpTopics">
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>{{helpTopic.question}}</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<mat-panel-description>{{helpTopic.answer}}</mat-panel-description>
|
||||
</mat-expansion-panel>
|
||||
<div class="divider"></div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
3
src/app/pages/help/help.component.scss
Normal file
@ -0,0 +1,3 @@
|
||||
.mat-card-content {
|
||||
margin-bottom: 4px;
|
||||
}
|
29
src/app/pages/help/help.component.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
export class HelpTopic {
|
||||
question: string;
|
||||
answer: string;
|
||||
|
||||
constructor(ques: string, ans: string) {
|
||||
this.question = ques;
|
||||
this.answer = ans;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-help',
|
||||
templateUrl: './help.component.html',
|
||||
styleUrls: ['./help.component.scss']
|
||||
})
|
||||
export class HelpComponent implements OnInit {
|
||||
public helpTopics: Array<HelpTopic> = [];
|
||||
|
||||
constructor() {}
|
||||
|
||||
ngOnInit() {
|
||||
// this.helpTopics.push(new HelpTopic('Set LND home directory?',
|
||||
// 'Pass the directroy information while getting the server up with --lndir "local-lnd-path".<br>Example: node rtl --lndir C:\lnd\dir\path'));
|
||||
this.helpTopics.push(new HelpTopic('Change theme?', 'Click on rotating setting icon on the right side of the screen and choose from the given options.'));
|
||||
}
|
||||
|
||||
}
|
237
src/app/pages/home/home.component.html
Normal file
@ -0,0 +1,237 @@
|
||||
<div fxLayout="column" fxLayout.gt-sm="row wrap">
|
||||
<div fxFlex="25" 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="settings?.satsToBTC; else smallerUnit1">{{BTCtotalBalance | number}} {{information?.currency_unit}}</h3>
|
||||
<ng-template #smallerUnit1><h3>{{totalBalance | 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="25" 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">{{totalPeers | 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>
|
||||
<div fxFlex="25" 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="settings?.satsToBTC; else smallerUnit2">{{BTCchannelBalance | number}} {{information?.currency_unit}}</h3>
|
||||
<ng-template #smallerUnit2><h3>{{channelBalance | 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="25" 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>Chain Sync Status</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">sync</mat-icon>
|
||||
</mat-card-content>
|
||||
<mat-icon *ngIf="information?.synced_to_chain; else notSynced" class="size-30 green sync-to-chain">check_circle</mat-icon>
|
||||
<ng-template #notSynced>
|
||||
<mat-icon class="size-30 red">cancel</mat-icon>
|
||||
</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>
|
||||
</div>
|
||||
<div fxLayout="column" fxLayout.gt-sm="row wrap">
|
||||
<div fxFlex="25" class="padding-gap">
|
||||
<div fxLayout="column">
|
||||
<mat-card fxFlex="100" [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>
|
||||
<div fxLayout="column" class="pl-4">
|
||||
<mat-list fxFlex="100" fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="65" fxLayoutAlign="start start">Daily ({{information?.smaller_currency_unit}})</mat-list-item>
|
||||
<mat-list-item fxFlex="25" fxLayoutAlign="end start">{{fees?.day_fee_sum}}</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
<mat-list fxFlex="100" fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="65" fxLayoutAlign="start start">Weekly ({{information?.smaller_currency_unit}})</mat-list-item>
|
||||
<mat-list-item fxFlex="25" fxLayoutAlign="end start">{{fees?.week_fee_sum}}</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
<mat-list fxFlex="100" fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="65" fxLayoutAlign="start start">Monthly ({{information?.smaller_currency_unit}})</mat-list-item>
|
||||
<mat-list-item fxFlex="25" fxLayoutAlign="end start">{{fees?.month_fee_sum}}</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
</div>
|
||||
<mat-progress-bar *ngIf="flgLoading[1]===true" mode="indeterminate"></mat-progress-bar>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<mat-card fxFlex="100" [ngClass]="{'mt-2 custom-card error-border': flgLoading[5]==='error','mt-2 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 fxFlex="100" fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="65" fxLayoutAlign="start start">Active</mat-list-item>
|
||||
<mat-list-item fxFlex="25" fxLayoutAlign="end start"><p class="mat-button-text pt-2">{{activeChannels}}</p></mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
<mat-list fxFlex="100" fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="65" fxLayoutAlign="start start">Inactive</mat-list-item>
|
||||
<mat-list-item fxFlex="25" fxLayoutAlign="end start"><p class="mat-button-text pt-2">{{inactiveChannels}}</p></mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
<mat-list fxFlex="100" fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="65" fxLayoutAlign="start start">Pending</mat-list-item>
|
||||
<mat-list-item fxFlex="25" fxLayoutAlign="end start"><p class="mat-button-text pt-2">{{pendingChannels}}</p></mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
</div>
|
||||
<mat-progress-bar *ngIf="flgLoading[6]===true" mode="indeterminate" class="mt-minus-5"></mat-progress-bar>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
<div fxFlex="40" class="padding-gap">
|
||||
<mat-card [ngClass]="{'custom-card error-border': flgLoading[5]==='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" *ngIf="flgTotalCalculated">
|
||||
<ngx-charts-bar-vertical
|
||||
[view]="view"
|
||||
[scheme]="colorScheme"
|
||||
[results]="totalBalances"
|
||||
[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="35" class="padding-gap">
|
||||
<mat-card [ngClass]="{'custom-card error-border': flgLoading[5]==='error','custom-card': true}">
|
||||
<mat-card-header class="bg-primary" fxLayoutAlign="center center">
|
||||
<mat-card-title class="m-0 pt-2">
|
||||
<h5>Network Information</h5>
|
||||
</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<div fxLayout="column" class="pl-4 network-info-list">
|
||||
<mat-list fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="65" fxLayoutAlign="start start" *ngIf="settings?.satsToBTC; else smallerUnit6">Network Capacity ({{information?.currency_unit}})</mat-list-item>
|
||||
<mat-list-item fxFlex="25" fxLayoutAlign="end start" *ngIf="settings?.satsToBTC; else smallerData6">{{networkInfo?.btc_total_network_capacity | number}}</mat-list-item>
|
||||
<ng-template #smallerUnit6><mat-list-item fxFlex="65" fxLayoutAlign="start start">Network Capacity ({{information?.smaller_currency_unit}})</mat-list-item></ng-template>
|
||||
<ng-template #smallerData6><mat-list-item fxFlex="25" fxLayoutAlign="end start">{{networkInfo?.total_network_capacity | number}}</mat-list-item></ng-template>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
<mat-list fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="65" fxLayoutAlign="start start">Number of Nodes</mat-list-item>
|
||||
<mat-list-item fxFlex="25" fxLayoutAlign="end start">{{networkInfo?.num_nodes | number}}</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
<mat-list fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="65" fxLayoutAlign="start start">Number of Channels</mat-list-item>
|
||||
<mat-list-item fxFlex="25" fxLayoutAlign="end start">{{networkInfo?.num_channels | number}}</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
<mat-list fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="65" fxLayoutAlign="start start">Max Out Degree</mat-list-item>
|
||||
<mat-list-item fxFlex="25" fxLayoutAlign="end start">{{networkInfo?.max_out_degree | number}}</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
<mat-list fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="65" fxLayoutAlign="start start">Avg Out Degree</mat-list-item>
|
||||
<mat-list-item fxFlex="25" fxLayoutAlign="end start">{{networkInfo?.avg_out_degree | number:'1.0-2'}}</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
<mat-list fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="65" fxLayoutAlign="start start" *ngIf="settings?.satsToBTC; else smallerUnit7">Max Channel Size ({{information?.currency_unit}})</mat-list-item>
|
||||
<ng-template #smallerUnit7><mat-list-item fxFlex="65" fxLayoutAlign="start start">Max Channel Size ({{information?.smaller_currency_unit}})</mat-list-item></ng-template>
|
||||
<mat-list-item fxFlex="25" fxLayoutAlign="end start" *ngIf="settings?.satsToBTC; else smallerData7">{{networkInfo?.btc_max_channel_size | number}}</mat-list-item>
|
||||
<ng-template #smallerData7><mat-list-item fxFlex="25" fxLayoutAlign="end start">{{networkInfo?.max_channel_size | number}}</mat-list-item></ng-template>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
<mat-list fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="65" fxLayoutAlign="start start" *ngIf="settings?.satsToBTC; else smallerUnit8">Avg Channel Size ({{information?.currency_unit}})</mat-list-item>
|
||||
<ng-template #smallerUnit8><mat-list-item fxFlex="65" fxLayoutAlign="start start">Avg Channel Size ({{information?.smaller_currency_unit}})</mat-list-item></ng-template>
|
||||
<mat-list-item fxFlex="25" fxLayoutAlign="end start" *ngIf="settings?.satsToBTC; else smallerData8">{{networkInfo?.btc_avg_channel_size | number}}</mat-list-item>
|
||||
<ng-template #smallerData8><mat-list-item fxFlex="25" fxLayoutAlign="end start">{{networkInfo?.avg_channel_size | number:'1.0-2'}}</mat-list-item></ng-template>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
<mat-list fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="65" fxLayoutAlign="start start" *ngIf="settings?.satsToBTC; else smallerUnit9">Min Channel Size ({{information?.currency_unit}})</mat-list-item>
|
||||
<ng-template #smallerUnit9><mat-list-item fxFlex="65" fxLayoutAlign="start start">Min Channel Size ({{information?.smaller_currency_unit}})</mat-list-item></ng-template>
|
||||
<mat-list-item fxFlex="25" fxLayoutAlign="end start" *ngIf="settings?.satsToBTC; else smallerData9">{{networkInfo?.btc_min_channel_size | number}}</mat-list-item>
|
||||
<ng-template #smallerData9><mat-list-item fxFlex="25" fxLayoutAlign="end start">{{networkInfo?.min_channel_size | number}}</mat-list-item></ng-template>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
</div>
|
||||
<mat-progress-bar *ngIf="flgLoading[4]===true" mode="indeterminate"></mat-progress-bar>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #withoutData><h3>Sats</h3></ng-template>
|
12
src/app/pages/home/home.component.scss
Normal file
@ -0,0 +1,12 @@
|
||||
.network-info-list .mat-list-item {
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.card-chnl-balances {
|
||||
min-height: 354px;
|
||||
}
|
25
src/app/pages/home/home.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { HomeComponent } from './home.component';
|
||||
|
||||
describe('HomeComponent', () => {
|
||||
let component: HomeComponent;
|
||||
let fixture: ComponentFixture<HomeComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ HomeComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(HomeComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
148
src/app/pages/home/home.component.ts
Normal file
@ -0,0 +1,148 @@
|
||||
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 { GetInfo, NetworkInfo, Fees, Peer } from '../../shared/models/lndModels';
|
||||
import { Settings } from '../../shared/models/RTLconfig';
|
||||
|
||||
import * as fromRTLReducer from '../../shared/store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-home',
|
||||
templateUrl: './home.component.html',
|
||||
styleUrls: ['./home.component.scss']
|
||||
})
|
||||
export class HomeComponent implements OnInit, OnDestroy {
|
||||
public settings: Settings;
|
||||
public fees: Fees;
|
||||
public information: GetInfo = {};
|
||||
public remainder = 0;
|
||||
public totalPeers = -1;
|
||||
public totalBalance = '';
|
||||
public channelBalance = '';
|
||||
public BTCtotalBalance = '';
|
||||
public BTCchannelBalance = '';
|
||||
public networkInfo: NetworkInfo = {};
|
||||
public flgLoading: Array<Boolean | 'error'> = [true, true, true, true, true, true, true, true]; // 0: Info, 1: Fee, 2: Wallet, 3: Channel, 4: Network
|
||||
private unsub: Array<Subject<void>> = [new Subject(), new Subject(), new Subject()];
|
||||
public channels: any;
|
||||
public position = 'below';
|
||||
public activeChannels = 0;
|
||||
public inactiveChannels = 0;
|
||||
public pendingChannels = 0;
|
||||
public peers: Peer[] = [];
|
||||
barPadding = 0;
|
||||
maxBalanceValue = 0;
|
||||
totalBalances = [...[{'name': 'Local Balance', 'value': 0}, {'name': 'Remote Balance', 'value': 0}]];
|
||||
flgTotalCalculated = false;
|
||||
view = [];
|
||||
yAxisLabel = 'Balance';
|
||||
colorScheme = {domain: ['#FFFFFF']};
|
||||
|
||||
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.State>) {
|
||||
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;
|
||||
}
|
||||
Object.assign(this, this.totalBalances);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.flgTotalCalculated = false;
|
||||
this.store.select('rtlRoot')
|
||||
.pipe(takeUntil(this.unsub[0]))
|
||||
.subscribe((rtlStore: fromRTLReducer.State) => {
|
||||
rtlStore.effectErrors.forEach(effectsErr => {
|
||||
if (effectsErr.action === 'FetchInfo') {
|
||||
this.flgLoading[0] = 'error';
|
||||
}
|
||||
if (effectsErr.action === 'FetchFees') {
|
||||
this.flgLoading[1] = 'error';
|
||||
}
|
||||
if (effectsErr.action === 'FetchBalance/blockchain') {
|
||||
this.flgLoading[2] = 'error';
|
||||
}
|
||||
if (effectsErr.action === 'FetchBalance/channels') {
|
||||
this.flgLoading[3] = 'error';
|
||||
}
|
||||
if (effectsErr.action === 'FetchNetwork') {
|
||||
this.flgLoading[4] = 'error';
|
||||
}
|
||||
if (effectsErr.action === 'FetchChannels/all') {
|
||||
this.flgLoading[5] = 'error';
|
||||
this.flgLoading[6] = 'error';
|
||||
}
|
||||
});
|
||||
this.settings = rtlStore.settings;
|
||||
this.information = rtlStore.information;
|
||||
if (this.flgLoading[0] !== 'error') {
|
||||
this.flgLoading[0] = (undefined !== this.information.identity_pubkey) ? false : true;
|
||||
}
|
||||
|
||||
this.fees = rtlStore.fees;
|
||||
if (this.flgLoading[1] !== 'error') {
|
||||
this.flgLoading[1] = (undefined !== this.fees.day_fee_sum) ? false : true;
|
||||
}
|
||||
|
||||
this.totalBalance = rtlStore.blockchainBalance.total_balance;
|
||||
this.BTCtotalBalance = rtlStore.blockchainBalance.btc_total_balance;
|
||||
if (this.flgLoading[2] !== 'error') {
|
||||
this.flgLoading[2] = ('' !== this.totalBalance) ? false : true;
|
||||
}
|
||||
|
||||
this.channelBalance = rtlStore.channelBalance.balance;
|
||||
this.BTCchannelBalance = rtlStore.channelBalance.btc_balance;
|
||||
if (this.flgLoading[3] !== 'error') {
|
||||
this.flgLoading[3] = ('' !== this.channelBalance) ? false : true;
|
||||
}
|
||||
|
||||
this.networkInfo = rtlStore.networkInfo;
|
||||
if (this.flgLoading[4] !== 'error') {
|
||||
this.flgLoading[4] = (undefined !== this.networkInfo.num_nodes) ? false : true;
|
||||
}
|
||||
|
||||
this.totalBalances = [...[{'name': 'Local Balance', 'value': +rtlStore.totalLocalBalance}, {'name': 'Remote Balance', 'value': +rtlStore.totalRemoteBalance}]];
|
||||
this.maxBalanceValue = (rtlStore.totalLocalBalance > rtlStore.totalRemoteBalance) ? rtlStore.totalLocalBalance : rtlStore.totalRemoteBalance;
|
||||
if (rtlStore.totalLocalBalance >= 0 && rtlStore.totalRemoteBalance >= 0) {
|
||||
this.flgTotalCalculated = true;
|
||||
if (this.flgLoading[5] !== 'error') {
|
||||
this.flgLoading[5] = false;
|
||||
}
|
||||
}
|
||||
|
||||
this.activeChannels = rtlStore.numberOfActiveChannels;
|
||||
this.inactiveChannels = rtlStore.numberOfInactiveChannels;
|
||||
this.pendingChannels = (undefined !== rtlStore.pendingChannels.pending_open_channels) ? rtlStore.pendingChannels.pending_open_channels.length : 0;
|
||||
if (rtlStore.totalLocalBalance >= 0 && rtlStore.totalRemoteBalance >= 0 && this.flgLoading[6] !== 'error') {
|
||||
this.flgLoading[6] = false;
|
||||
}
|
||||
|
||||
this.totalPeers = rtlStore.peers.length;
|
||||
|
||||
this.logger.info(rtlStore);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unsub.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
77
src/app/pages/invoices/invoices.component.html
Normal file
@ -0,0 +1,77 @@
|
||||
<div fxLayout="column">
|
||||
<div class="padding-gap">
|
||||
<mat-card>
|
||||
<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" (ngSubmit)="addInvoiceForm.form.valid && onAddInvoice(addInvoiceForm)" #addInvoiceForm="ngForm">
|
||||
<mat-form-field fxFlex="60" fxLayoutAlign="start end">
|
||||
<input matInput [(ngModel)]="memo" placeholder="Memo" tabindex="1" name="memo">
|
||||
</mat-form-field>
|
||||
<mat-form-field fxFlex="20" fxLayoutAlign="start end">
|
||||
<input matInput [(ngModel)]="invoiceValue" placeholder="Invoice Value ({{information?.smaller_currency_unit}})" type="number" step="100" min="1" tabindex="2" name="invoiceValue">
|
||||
</mat-form-field>
|
||||
<div fxFlex="10" fxLayoutAlign="start start">
|
||||
<button fxFlex="90" fxLayoutAlign="center center" mat-raised-button color="primary" type="submit" tabindex="3">Add</button>
|
||||
</div>
|
||||
<div fxFlex="10" fxLayoutAlign="start start">
|
||||
<button fxFlex="90" fxLayoutAlign="center center" mat-raised-button color="accent" tabindex="4" type="reset" (click)="resetData()">Clear</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
<div class="padding-gap">
|
||||
<mat-card>
|
||||
<mat-card-content class="table-card-content">
|
||||
<div fxLayout="row" fxLayoutAlign="start start">
|
||||
<mat-form-field fxFlex="30">
|
||||
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div perfectScrollbar class="table-container mat-elevation-z8">
|
||||
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
|
||||
<table mat-table #table [dataSource]="invoices" matSort [ngClass]="{'mat-elevation-z8 overflow-auto error-border': flgLoading[0]==='error','mat-elevation-z8 overflow-auto': true}">
|
||||
<ng-container matColumnDef="creation_date">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Creation Date </th>
|
||||
<td mat-cell *matCellDef="let invoice">{{invoice.creation_date_str}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="settle_date">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Settle Date </th>
|
||||
<td mat-cell *matCellDef="let invoice">{{invoice.settle_date_str}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="memo">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Memo </th>
|
||||
<td mat-cell *matCellDef="let invoice">{{invoice.memo}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="value">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Value ({{(settings?.satsToBTC) ? information?.currency_unit : information?.smaller_currency_unit}}) </th>
|
||||
<td mat-cell *matCellDef="let invoice"><span fxLayoutAlign="end center"> {{(settings?.satsToBTC) ? (invoice?.btc_value | number:'1.0-3') : (invoice?.value | number)}} </span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="settled">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Settled </th>
|
||||
<td mat-cell *matCellDef="let invoice"> {{invoice.settled}} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="expiry">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Expiry (Sec)</th>
|
||||
<td mat-cell *matCellDef="let invoice"><span fxLayoutAlign="end center"> {{invoice.expiry | number}} </span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="cltv_expiry">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> CLTV Expiry </th>
|
||||
<td mat-cell *matCellDef="let invoice"><span fxLayoutAlign="end center"> {{invoice.cltv_expiry | number}} </span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="amt_paid_sat">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Amount Paid ({{(settings?.satsToBTC) ? information?.currency_unit : information?.smaller_currency_unit}})</th>
|
||||
<td mat-cell *matCellDef="let invoice"><span fxLayoutAlign="end center"> {{(settings?.satsToBTC) ? (invoice?.btc_amt_paid_sat | number:'1.0-3') : (invoice?.amt_paid_sat | number)}} </span></td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true;"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [@newlyAddedRowAnimation]="(row.memo === newlyAddedInvoiceMemo && row.value === newlyAddedInvoiceValue && flgAnimate) ? 'added' : 'notAdded'" (click)="onInvoiceClick(row, $event)"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
23
src/app/pages/invoices/invoices.component.scss
Normal file
@ -0,0 +1,23 @@
|
||||
.mat-column-value {
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.mat-column-settled {
|
||||
padding-left: 1rem;
|
||||
}
|
||||
|
||||
table {
|
||||
width:100%;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
height: 68vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 414px) {
|
||||
.table-container {
|
||||
max-height: 31vh;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
141
src/app/pages/invoices/invoices.component.ts
Normal file
@ -0,0 +1,141 @@
|
||||
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { formatDate } from '@angular/common';
|
||||
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 { Settings } from '../../shared/models/RTLconfig';
|
||||
import { GetInfo, Invoice } from '../../shared/models/lndModels';
|
||||
import { LoggerService } from '../../shared/services/logger.service';
|
||||
|
||||
import { newlyAddedRowAnimation } from '../../shared/animation/row-animation';
|
||||
import * as RTLActions from '../../shared/store/rtl.actions';
|
||||
import * as fromRTLReducer from '../../shared/store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-invoices',
|
||||
templateUrl: './invoices.component.html',
|
||||
styleUrls: ['./invoices.component.scss'],
|
||||
animations: [newlyAddedRowAnimation]
|
||||
})
|
||||
export class InvoicesComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(MatSort) sort: MatSort;
|
||||
public newlyAddedInvoiceMemo = '';
|
||||
public newlyAddedInvoiceValue = 0;
|
||||
public flgAnimate = true;
|
||||
public settings: Settings;
|
||||
public memo = '';
|
||||
public invoiceValue: number;
|
||||
public displayedColumns = [];
|
||||
public invoicePaymentReq = '';
|
||||
public invoices: any;
|
||||
public information: GetInfo = {};
|
||||
public flgLoading: Array<Boolean | 'error'> = [true];
|
||||
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
|
||||
|
||||
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.State>, private actions$: Actions) {
|
||||
switch (true) {
|
||||
case (window.innerWidth <= 415):
|
||||
this.displayedColumns = ['creation_date', 'memo', 'value', 'settled'];
|
||||
break;
|
||||
case (window.innerWidth > 415 && window.innerWidth <= 730):
|
||||
this.displayedColumns = ['creation_date', 'settle_date', 'memo', 'value', 'settled', 'amt_paid_sat'];
|
||||
break;
|
||||
case (window.innerWidth > 730 && window.innerWidth <= 1024):
|
||||
this.displayedColumns = ['creation_date', 'settle_date', 'memo', 'value', 'settled', 'amt_paid_sat'];
|
||||
break;
|
||||
case (window.innerWidth > 1024 && window.innerWidth <= 1280):
|
||||
this.displayedColumns = ['creation_date', 'settle_date', 'memo', 'value', 'settled', 'amt_paid_sat', 'expiry', 'cltv_expiry'];
|
||||
break;
|
||||
default:
|
||||
this.displayedColumns = ['creation_date', 'settle_date', 'memo', 'value', 'settled', 'amt_paid_sat', 'expiry', 'cltv_expiry'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.store.select('rtlRoot')
|
||||
.pipe(takeUntil(this.unSubs[0]))
|
||||
.subscribe((rtlStore: fromRTLReducer.State) => {
|
||||
rtlStore.effectErrors.forEach(effectsErr => {
|
||||
if (effectsErr.action === 'FetchInvoices') {
|
||||
this.flgLoading[0] = 'error';
|
||||
}
|
||||
});
|
||||
this.settings = rtlStore.settings;
|
||||
this.information = rtlStore.information;
|
||||
this.logger.info(rtlStore);
|
||||
this.loadInvoicesTable(rtlStore.invoices);
|
||||
if (this.flgLoading[0] !== 'error') {
|
||||
this.flgLoading[0] = (undefined !== rtlStore.invoices[0]) ? false : true;
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
onAddInvoice(form: any) {
|
||||
this.flgAnimate = true;
|
||||
this.newlyAddedInvoiceMemo = this.memo;
|
||||
this.newlyAddedInvoiceValue = this.invoiceValue;
|
||||
this.store.dispatch(new RTLActions.OpenSpinner('Adding Invoice...'));
|
||||
this.store.dispatch(new RTLActions.SaveNewInvoice({memo: this.memo, invoiceValue: this.invoiceValue}));
|
||||
this.actions$
|
||||
.pipe(
|
||||
takeUntil(this.unSubs[1]),
|
||||
filter((action) => action.type === RTLActions.ADD_INVOICE)
|
||||
).subscribe((newInvoiceAction: RTLActions.AddInvoice) => {
|
||||
this.logger.info(newInvoiceAction.payload);
|
||||
this.invoicePaymentReq = newInvoiceAction.payload.payment_request;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
onInvoiceClick(selRow: Invoice, event: any) {
|
||||
const selInvoice = this.invoices.data.filter(invoice => {
|
||||
return invoice.payment_request === selRow.payment_request;
|
||||
})[0];
|
||||
const reorderedInvoice = JSON.parse(JSON.stringify(selInvoice, [
|
||||
'creation_date_str', 'settle_date_str', 'memo', 'receipt', 'r_preimage', 'r_hash', 'value', 'settled', 'payment_request',
|
||||
'description_hash', 'expiry', 'fallback_addr', 'cltv_expiry', 'route_hints', 'private', 'add_index', 'settle_index',
|
||||
'amt_paid', 'amt_paid_sat', 'amt_paid_msat'
|
||||
] , 2));
|
||||
this.store.dispatch(new RTLActions.OpenAlert({ width: '75%', data: {
|
||||
type: 'INFO',
|
||||
message: JSON.stringify(reorderedInvoice)
|
||||
}}));
|
||||
}
|
||||
|
||||
loadInvoicesTable(invoices) {
|
||||
this.invoices = new MatTableDataSource<Invoice>([...invoices]);
|
||||
this.invoices.sort = this.sort;
|
||||
this.invoices.data.forEach(invoice => {
|
||||
if (undefined !== invoice.creation_date_str) {
|
||||
invoice.creation_date_str = (invoice.creation_date_str === '') ? '' : formatDate(invoice.creation_date_str, 'MMM/dd/yy HH:mm:ss', 'en-US');
|
||||
}
|
||||
if (undefined !== invoice.settle_date_str) {
|
||||
invoice.settle_date_str = (invoice.settle_date_str === '') ? '' : formatDate(invoice.settle_date_str, 'MMM/dd/yy HH:mm:ss', 'en-US');
|
||||
}
|
||||
});
|
||||
setTimeout(() => { this.flgAnimate = false; }, 5000);
|
||||
this.logger.info(this.invoices);
|
||||
}
|
||||
|
||||
resetData() {
|
||||
this.memo = '';
|
||||
this.invoiceValue = 0;
|
||||
}
|
||||
|
||||
applyFilter(selFilter: string) {
|
||||
this.invoices.filter = selFilter;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unSubs.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
.mat-list-base .mat-list-item, .mat-list-base .mat-list-option {
|
||||
height: 38px !important;
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
<div fxLayout="column" fxLayout.gt-sm="row wrap">
|
||||
<mat-card fxFlex="100" fxLayoutAlign="start start">
|
||||
<mat-card-content fxFlex="100" *ngIf="lookupResult">
|
||||
<mat-list fxLayoutAlign="space-between start">
|
||||
<mat-list-item fxFlex="30" fxLayoutAlign="start start">Channel Id</mat-list-item>
|
||||
<mat-list-item fxFlex="68" fxLayoutAlign="start start">{{lookupResult.channel_id}}</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
<mat-list fxLayoutAlign="space-between start">
|
||||
<mat-list-item fxFlex="30" fxLayoutAlign="start start">Channel Point</mat-list-item>
|
||||
<mat-list-item fxFlex="68" fxLayoutAlign="start start" class="word-break">{{lookupResult.chan_point}}</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
<mat-list fxLayoutAlign="space-between start">
|
||||
<mat-list-item fxFlex="30" fxLayoutAlign="start start">Last Update</mat-list-item>
|
||||
<mat-list-item fxFlex="68" fxLayoutAlign="start start">{{lookupResult.last_update_str}}</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
<mat-list fxLayoutAlign="space-between start">
|
||||
<mat-list-item fxFlex="30" fxLayoutAlign="start start">Capacity (Sats)</mat-list-item>
|
||||
<mat-list-item fxFlex="68" fxLayoutAlign="start start">{{lookupResult.capacity | number}}</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
<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">
|
||||
<mat-list fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="100" fxLayoutAlign="start start" class="word-break">{{lookupResult.node1_pub}}</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
<mat-list fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Time Lock Delta</mat-list-item>
|
||||
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult.node1_policy.time_lock_delta}}</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
<mat-list fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Min HTLC</mat-list-item>
|
||||
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult.node1_policy.min_htlc}}</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
<mat-list fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Fee Base Msat</mat-list-item>
|
||||
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult.node1_policy.fee_base_msat}}</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
<mat-list fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Fee Rate Milli Msat</mat-list-item>
|
||||
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult.node1_policy.fee_rate_milli_msat}}</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
<mat-list fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Disabled</mat-list-item>
|
||||
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult.node1_policy.disabled}}</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</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">
|
||||
<mat-list fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="100" fxLayoutAlign="start start" class="word-break">{{lookupResult.node2_pub}}</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
<mat-list fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Time Lock Delta</mat-list-item>
|
||||
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult.node2_policy.time_lock_delta}}</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
<mat-list fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Min HTLC</mat-list-item>
|
||||
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult.node2_policy.min_htlc}}</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
<mat-list fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Fee Base Msat</mat-list-item>
|
||||
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult.node2_policy.fee_base_msat}}</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
<mat-list fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Fee Rate Milli Msat</mat-list-item>
|
||||
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult.node2_policy.fee_rate_milli_msat}}</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
<mat-list fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Disabled</mat-list-item>
|
||||
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult.node2_policy.disabled}}</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ChannelLookupComponent } from './channel-lookup.component';
|
||||
|
||||
describe('ChannelLookupComponent', () => {
|
||||
let component: ChannelLookupComponent;
|
||||
let fixture: ComponentFixture<ChannelLookupComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ChannelLookupComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ChannelLookupComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,40 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { formatDate } from '@angular/common';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { ChannelEdge } from '../../../shared/models/lndModels';
|
||||
import * as fromRTLReducer from '../../../shared/store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-channel-lookup',
|
||||
templateUrl: './channel-lookup.component.html',
|
||||
styleUrls: ['./channel-lookup.component.css']
|
||||
})
|
||||
export class ChannelLookupComponent implements OnInit {
|
||||
@Input() lookupResult: ChannelEdge;
|
||||
public node1_match = false;
|
||||
public node2_match = false;
|
||||
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
|
||||
|
||||
constructor(private store: Store<fromRTLReducer.State>) { }
|
||||
|
||||
ngOnInit() {
|
||||
if (undefined !== this.lookupResult && undefined !== this.lookupResult.last_update_str) {
|
||||
this.lookupResult.last_update_str = (this.lookupResult.last_update_str === '') ?
|
||||
'' : formatDate(this.lookupResult.last_update_str, 'MMM/dd/yy HH:mm:ss', 'en-US');
|
||||
}
|
||||
this.store.select('rtlRoot')
|
||||
.pipe(takeUntil(this.unSubs[0]))
|
||||
.subscribe((rtlStore: fromRTLReducer.State) => {
|
||||
if (this.lookupResult.node1_pub === rtlStore.information.identity_pubkey) {
|
||||
this.node1_match = true;
|
||||
}
|
||||
if (this.lookupResult.node2_pub === rtlStore.information.identity_pubkey) {
|
||||
this.node2_match = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
47
src/app/pages/lookups/lookups.component.html
Normal file
@ -0,0 +1,47 @@
|
||||
<div fxLayout="column">
|
||||
<div class="padding-gap">
|
||||
<mat-card>
|
||||
<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">
|
||||
{{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-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>
|
||||
<div fxFlex="12" fxLayoutAlign="start start">
|
||||
<button fxFlex="90" fxLayoutAlign="center center" mat-raised-button color="accent" tabindex="4" type="reset" (click)="resetData()">Clear</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
<div class="padding-gap" *ngIf="lookupValue && flgSetLookupValue">
|
||||
<mat-card [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-node-lookup [lookupResult]="lookupValue"></rtl-node-lookup></span>
|
||||
<span *ngSwitchCase="1"><rtl-channel-lookup [lookupResult]="lookupValue"></rtl-channel-lookup></span>
|
||||
<span *ngSwitchDefault><h3>Error! Unable to find details!</h3></span>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
14
src/app/pages/lookups/lookups.component.scss
Normal file
@ -0,0 +1,14 @@
|
||||
.tree-invisible {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.lookup-tree ul,
|
||||
.lookup-tree li {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.pl-3 {
|
||||
padding-left: 3rem;
|
||||
}
|
25
src/app/pages/lookups/lookups.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LookupsComponent } from './lookups.component';
|
||||
|
||||
describe('LookupsComponent', () => {
|
||||
let component: LookupsComponent;
|
||||
let fixture: ComponentFixture<LookupsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ LookupsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(LookupsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
94
src/app/pages/lookups/lookups.component.ts
Normal file
@ -0,0 +1,94 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil, filter } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Actions } from '@ngrx/effects';
|
||||
|
||||
import { LoggerService } from '../../shared/services/logger.service';
|
||||
|
||||
import * as RTLActions from '../../shared/store/rtl.actions';
|
||||
import * as fromRTLReducer from '../../shared/store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-lookups',
|
||||
templateUrl: './lookups.component.html',
|
||||
styleUrls: ['./lookups.component.scss']
|
||||
})
|
||||
export class LookupsComponent implements OnInit, OnDestroy {
|
||||
public lookupKey = '';
|
||||
public lookupValue = {};
|
||||
public flgSetLookupValue = false;
|
||||
public temp: any;
|
||||
public messageObj = [];
|
||||
public selectedField = { id: '0', name: 'Node', placeholder: 'Pubkey'};
|
||||
public lookupFields = [
|
||||
{ id: '0', name: 'Node', placeholder: 'Pubkey'},
|
||||
{ id: '1', name: 'Channel', placeholder: 'Channel ID'}
|
||||
];
|
||||
public flgLoading: Array<Boolean | 'error'> = [true];
|
||||
private unSubs: Array<Subject<void>> = [new Subject()];
|
||||
|
||||
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.State>, private actions$: Actions) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.actions$
|
||||
.pipe(
|
||||
takeUntil(this.unSubs[0]),
|
||||
filter((action) => (action.type === RTLActions.SET_LOOKUP || action.type === RTLActions.EFFECT_ERROR))
|
||||
).subscribe((resLookup: RTLActions.SetLookup) => {
|
||||
if (resLookup.payload.action === 'Lookup') {
|
||||
this.flgLoading[0] = 'error';
|
||||
} else {
|
||||
this.flgLoading[0] = true;
|
||||
this.lookupValue = JSON.parse(JSON.stringify(resLookup.payload));
|
||||
this.flgSetLookupValue = true;
|
||||
this.logger.info(this.lookupValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onLookup() {
|
||||
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.PeerLookup(this.lookupKey.trim()));
|
||||
break;
|
||||
case '1':
|
||||
this.store.dispatch(new RTLActions.ChannelLookup(this.lookupKey.trim()));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
onSelectChange(event: any) {
|
||||
this.flgSetLookupValue = false;
|
||||
this.lookupKey = '';
|
||||
this.lookupValue = {};
|
||||
}
|
||||
|
||||
resetData() {
|
||||
this.flgSetLookupValue = false;
|
||||
this.lookupKey = '';
|
||||
this.selectedField = { id: '0', name: 'Node', placeholder: 'Pubkey'};
|
||||
this.lookupValue = {};
|
||||
this.flgLoading.forEach((flg, i) => {
|
||||
this.flgLoading[i] = true;
|
||||
});
|
||||
}
|
||||
|
||||
clearLookupValue() {
|
||||
this.lookupValue = {};
|
||||
this.flgSetLookupValue = false;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unSubs.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
.mat-table {
|
||||
width:99%;
|
||||
}
|
||||
|
||||
.mat-list-base .mat-list-item, .mat-list-base .mat-list-option {
|
||||
height: 38px !important;
|
||||
}
|
56
src/app/pages/lookups/node-lookup/node-lookup.component.html
Normal file
@ -0,0 +1,56 @@
|
||||
<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">Alias</mat-list-item>
|
||||
<mat-list-item fxFlex="40" fxLayoutAlign="start start">{{lookupResult.node.alias}}</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
<mat-list fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Pub Key</mat-list-item>
|
||||
<mat-list-item fxFlex="40" fxLayoutAlign="start start">{{lookupResult.node.pub_key}}</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.node?.color}">{{lookupResult.node?.color}}</span></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="40" fxLayoutAlign="start start">{{lookupResult.node.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">Total Capacity (Sats)</mat-list-item>
|
||||
<mat-list-item fxFlex="40" fxLayoutAlign="start start">{{lookupResult.total_capacity | number}}</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list>
|
||||
<mat-list fxLayoutAlign="start start">
|
||||
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Number of Channels</mat-list-item>
|
||||
<mat-list-item fxFlex="40" fxLayoutAlign="start start">{{lookupResult.num_channels | number}}</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.node.addresses" matSort class="mat-elevation-z8 overflow-auto">
|
||||
<ng-container matColumnDef="network">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Network</mat-header-cell>
|
||||
<mat-cell *matCellDef="let address"><div>{{address?.network}}</div></mat-cell>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="addr">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header>Address</mat-header-cell>
|
||||
<mat-cell *matCellDef="let address"><div>{{address?.addr}}</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>
|
||||
</div>
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { NodeLookupComponent } from './node-lookup.component';
|
||||
|
||||
describe('NodeLookupComponent', () => {
|
||||
let component: NodeLookupComponent;
|
||||
let fixture: ComponentFixture<NodeLookupComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ NodeLookupComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(NodeLookupComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
24
src/app/pages/lookups/node-lookup/node-lookup.component.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { formatDate } from '@angular/common';
|
||||
|
||||
import { GraphNode } from '../../../shared/models/lndModels';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-node-lookup',
|
||||
templateUrl: './node-lookup.component.html',
|
||||
styleUrls: ['./node-lookup.component.css']
|
||||
})
|
||||
export class NodeLookupComponent implements OnInit {
|
||||
@Input() lookupResult: GraphNode;
|
||||
public displayedColumns = ['network', 'addr'];
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() {
|
||||
if (undefined !== this.lookupResult && undefined !== this.lookupResult.node && undefined !== this.lookupResult.node.last_update_str) {
|
||||
this.lookupResult.node.last_update_str = (this.lookupResult.node.last_update_str === '') ?
|
||||
'' : formatDate(this.lookupResult.node.last_update_str, 'MMM/dd/yy HH:mm:ss', 'en-US');
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<div fxLayout="row" fxLayoutAlign="start center">
|
||||
<div *ngFor="let menuNode of menuNodes">
|
||||
<button mat-button *ngIf="undefined === menuNode.children" class="horizontal-button" routerLinkActive="h-active-link" [routerLinkActiveOptions]="{exact: true}" routerLink="{{menuNode.link}}" matTooltip="{{menuNode.name}}" matTooltipPosition="above" (click)="onClick(menuNode)">
|
||||
<mat-icon class="mat-icon-36">{{menuNode.icon}}</mat-icon>
|
||||
</button>
|
||||
<div *ngIf="undefined !== menuNode.children" fxLayoutAlign="center center">
|
||||
<button mat-button class="horizontal-button" matTooltip="{{menuNode.name}}" matTooltipPosition="above">
|
||||
<mat-icon [matMenuTriggerFor]="childMenu" class="mat-icon-36">{{menuNode.icon}}</mat-icon>
|
||||
</button>
|
||||
<mat-menu #childMenu="matMenu" xPosition="after" overlapTrigger="false" class="child-menu">
|
||||
<div *ngFor="let childNode of menuNode.children">
|
||||
<button mat-button class="horizontal-button bg-primary p-0" fxFlex="100" [routerLinkActive]="'h-active-link'" routerLink="{{childNode.link}}" [routerLinkActiveOptions]="{exact: true}">
|
||||
<mat-icon matTooltip="{{childNode.name}}" matTooltipPosition="after" class="mat-icon-36">{{childNode.icon}}<span *ngIf="childNode.name === 'Pending'" [matBadgeHidden]="numPendingChannels<1" matBadge="{{numPendingChannels}}" matBadgeOverlap="false" matBadgeColor="accent"></span></mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</mat-menu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,19 @@
|
||||
.mat-menu-panel.child-menu {
|
||||
min-width: 88px;
|
||||
width:88px;
|
||||
border-radius: 0;
|
||||
margin-left: 30%;
|
||||
margin-top: 6%;
|
||||
.mat-menu-content {
|
||||
.mat-menu-item {
|
||||
padding: 0;
|
||||
margin-top: -3px;
|
||||
.mat-icon {
|
||||
margin-right: 0;
|
||||
}
|
||||
button {
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { HorizontalNavigationComponent } from './horizontal-navigation.component';
|
||||
|
||||
describe('HorizontalNavigationComponent', () => {
|
||||
let component: HorizontalNavigationComponent;
|
||||
let fixture: ComponentFixture<HorizontalNavigationComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ HorizontalNavigationComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(HorizontalNavigationComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,69 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil, filter } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Actions } from '@ngrx/effects';
|
||||
|
||||
import { LoggerService } from '../../../shared/services/logger.service';
|
||||
import { MENU_DATA } from '../../../shared/models/navMenu';
|
||||
|
||||
import { RTLEffects } from '../../../shared/store/rtl.effects';
|
||||
import * as RTLActions from '../../../shared/store/rtl.actions';
|
||||
import * as fromRTLReducer from '../../../shared/store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-horizontal-navigation',
|
||||
templateUrl: './horizontal-navigation.component.html',
|
||||
styleUrls: ['./horizontal-navigation.component.scss']
|
||||
})
|
||||
export class HorizontalNavigationComponent implements OnInit {
|
||||
public menuNodes = [];
|
||||
public logoutNode = [];
|
||||
public showLogout = false;
|
||||
public numPendingChannels = 0;
|
||||
private unSubs = [new Subject(), new Subject(), new Subject()];
|
||||
|
||||
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.State>, private actions$: Actions, private rtlEffects: RTLEffects) {
|
||||
this.menuNodes = MENU_DATA.children;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.store.select('rtlRoot')
|
||||
.pipe(takeUntil(this.unSubs[0]))
|
||||
.subscribe((rtlStore: fromRTLReducer.State) => {
|
||||
this.numPendingChannels = rtlStore.numberOfPendingChannels;
|
||||
});
|
||||
this.actions$
|
||||
.pipe(
|
||||
takeUntil(this.unSubs[2]),
|
||||
filter((action) => action.type === RTLActions.SIGNOUT || action.type === RTLActions.SIGNIN)
|
||||
).subscribe((action) => {
|
||||
this.logger.warn(action);
|
||||
if (action.type === RTLActions.SIGNIN) {
|
||||
this.menuNodes.push({id: 100, parentId: 0, name: 'Logout', icon: 'eject'});
|
||||
}
|
||||
if (action.type === RTLActions.SIGNOUT) {
|
||||
this.menuNodes.pop();
|
||||
}
|
||||
});
|
||||
if (sessionStorage.getItem('token')) {
|
||||
this.menuNodes.push({id: 100, parentId: 0, name: 'Logout', icon: 'eject'});
|
||||
}
|
||||
}
|
||||
|
||||
onClick(node) {
|
||||
if (node.name === 'Logout') {
|
||||
this.store.dispatch(new RTLActions.OpenConfirmation({
|
||||
width: '70%', data: { type: 'CONFIRM', titleMessage: 'Logout from this device?', noBtnText: 'Cancel', yesBtnText: 'Logout'
|
||||
}}));
|
||||
this.rtlEffects.closeConfirm
|
||||
.pipe(takeUntil(this.unSubs[1]))
|
||||
.subscribe(confirmRes => {
|
||||
if (confirmRes) {
|
||||
this.showLogout = false;
|
||||
this.store.dispatch(new RTLActions.Signout());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
<mat-toolbar color="primary" [fxLayoutAlign] = "settings.menuType === 'Mini' ? 'center center' : 'space-between center'">
|
||||
<a *ngIf="settings.menuType === 'Mini'" routerLink="/home" class="logo padding-gap-x mat-elevation-z6">R</a>
|
||||
<a *ngIf="settings.menuType !== 'Mini'" routerLink="/home" class="logo">RTL</a>
|
||||
<svg *ngIf="settings.menuType !== 'Mini' && !smallScreen" style="width:24px;height:24px;cursor:pointer;" viewBox="0 0 24 24"
|
||||
(click)="settings.flgSidenavPinned = !settings.flgSidenavPinned">
|
||||
<path fill="currentColor" *ngIf="!settings.flgSidenavPinned" d="M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z" />
|
||||
<path fill="currentColor" *ngIf="settings.flgSidenavPinned" d="M2,5.27L3.28,4L20,20.72L18.73,22L12.8,16.07V22H11.2V16H6V14L8,12V11.27L2,5.27M16,12L18,14V16H17.82L8,6.18V4H7V2H17V4H16V12Z" />
|
||||
</svg>
|
||||
</mat-toolbar>
|
||||
<div fxLayout="row" fxLayoutAlign="start center" class="lnd-info pl-2" *ngIf="settings.menuType !== 'Mini'">
|
||||
<div fxLayout="column">
|
||||
<p class="name">Alias: <mat-spinner [diameter]="20" *ngIf="flgLoading" class="inline-spinner"></mat-spinner>{{information?.alias}}</p>
|
||||
<p>Chain: <mat-spinner [diameter]="20" *ngIf="flgLoading" class="inline-spinner"></mat-spinner>{{informationChain.chain | titlecase}}<span> [{{informationChain.network | titlecase}}]</span></p>
|
||||
<p class="name">LND Version: <mat-spinner [diameter]="20" *ngIf="flgLoading" class="inline-spinner"></mat-spinner>{{information?.version}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-tree [dataSource]="navMenus" [treeControl]="treeControl" *ngIf="settings.menuType !== 'Compact'">
|
||||
<mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle (click)="onChildNavClicked(node)" matTreeNodePadding [matTreeNodePaddingIndent]="settings.menuType === 'Mini' ? 28 : 40" routerLinkActive="active-link" routerLink="{{node.link}}">
|
||||
<mat-icon class="mr-1" matTooltip="{{node.name}}" matTooltipPosition="right" [matTooltipDisabled]="settings.menuType !== 'Mini'">{{node.icon}}<span *ngIf="node.name === 'Pending' && settings.menuType === 'Mini'" [matBadgeHidden]="numPendingChannels<1" matBadge="{{numPendingChannels}}" matBadgeOverlap="false" matBadgeColor="accent"></span></mat-icon>
|
||||
<span *ngIf="settings.menuType !== 'Mini'">{{node.name}}<span *ngIf="node.name === 'Pending'" [matBadgeHidden]="numPendingChannels<1" matBadge="{{numPendingChannels}}" matBadgeOverlap="false" matBadgeColor="accent"></span></span>
|
||||
</mat-tree-node>
|
||||
|
||||
<mat-tree-node *matTreeNodeDef="let node;when: hasChild" matTreeNodePadding>
|
||||
<div fxLayout="row" fxFlex="100" fxLayoutAlign="start center" matTreeNodeToggle (click)="toggleTree(node)">
|
||||
<div fxFlex="89" fxLayoutAlign="start center">
|
||||
<mat-icon class="mr-1" matTooltip="{{node.name}}" matTooltipPosition="right" [matTooltipDisabled]="settings.menuType !== 'Mini'">{{node.icon}}</mat-icon><span *ngIf="settings.menuType !== 'Mini'">{{node.name}}</span>
|
||||
</div>
|
||||
<button fxFlex="11" fxLayoutAlign="end center" mat-icon-button matTreeNodeToggle [attr.aria-label]="'toggle ' + node.name" fxLayoutAlign="end center">
|
||||
<mat-icon class="mat-icon-rtl-mirror"> {{treeControl.isExpanded(node) ? 'arrow_drop_up' : 'arrow_drop_down'}}</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</mat-tree-node>
|
||||
</mat-tree>
|
||||
<mat-tree [dataSource]="navMenusLogout" [treeControl]="treeControlLogout" *ngIf="settings.menuType !== 'Compact' && showLogout" class="mt-minus-1">
|
||||
<mat-tree-node *matTreeNodeDef="let node" matTreeNodePadding [matTreeNodePaddingIndent]="settings.menuType === 'Mini' ? 28 : 40" (click)="onClick(node)">
|
||||
<mat-icon class="mr-1" matTooltip="{{node.name}}" matTooltipPosition="right" [matTooltipDisabled]="settings.menuType !== 'Mini'">{{node.icon}}</mat-icon>
|
||||
<span *ngIf="settings.menuType !== 'Mini'">{{node.name}}</span>
|
||||
</mat-tree-node>
|
||||
</mat-tree>
|
||||
|
||||
<mat-tree [dataSource]="navMenus" [treeControl]="treeControl" *ngIf="settings.menuType === 'Compact'">
|
||||
<mat-tree-node fxLayout="column" fxLayoutAlign="center center" *matTreeNodeDef="let node" matTreeNodeToggle (click)="onChildNavClicked(node)" matTreeNodePadding matTreeNodePaddingIndent="60" routerLinkActive="active-link" routerLink="{{node.link}}">
|
||||
<mat-icon class="mat-icon-36">{{node.icon}}</mat-icon>
|
||||
<span>{{node.name}}<span *ngIf="node.name === 'Pending'" [matBadgeHidden]="numPendingChannels<1" matBadge="{{numPendingChannels}}" matBadgeOverlap="false" matBadgeColor="accent"></span></span>
|
||||
</mat-tree-node>
|
||||
|
||||
<mat-tree-node fxLayout="row" *matTreeNodeDef="let node;when: hasChild" matTreeNodePadding>
|
||||
<div class="ml-8" fxLayout="column" fxLayoutAlign="center center" matTreeNodeToggle (click)="toggleTree(node)">
|
||||
<mat-icon class="mat-icon-36">{{node.icon}}</mat-icon>
|
||||
<span>{{node.name}}</span>
|
||||
</div>
|
||||
<div fxLayout="column" fxLayoutAlign="center center" matTreeNodeToggle (click)="toggleTree(node)">
|
||||
<button mat-icon-button [attr.aria-label]="'toggle ' + node.name" matTreeNodeToggle>
|
||||
<mat-icon class="mat-icon-rtl-mirror"> {{treeControl.isExpanded(node) ? 'arrow_drop_up' : 'arrow_drop_down'}}</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</mat-tree-node>
|
||||
</mat-tree>
|
||||
<mat-tree [dataSource]="navMenusLogout" [treeControl]="treeControlLogout" *ngIf="settings.menuType === 'Compact' && showLogout" class="mt-minus-1">
|
||||
<mat-tree-node fxLayout="column" fxLayoutAlign="center center" *matTreeNodeDef="let node" matTreeNodePadding matTreeNodePaddingIndent="60" (click)="onClick(node)">
|
||||
<mat-icon class="mat-icon-36">{{node.icon}}</mat-icon>
|
||||
<span>{{node.name}}</span>
|
||||
</mat-tree-node>
|
||||
</mat-tree>
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SideNavigationComponent } from './side-navigation.component';
|
||||
|
||||
describe('SideNavigationComponent', () => {
|
||||
let component: SideNavigationComponent;
|
||||
let fixture: ComponentFixture<SideNavigationComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ SideNavigationComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SideNavigationComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,153 @@
|
||||
import { Component, OnInit, OnDestroy, Output, EventEmitter } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Subject, Observable, of } from 'rxjs';
|
||||
import { takeUntil, filter } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Actions } from '@ngrx/effects';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
|
||||
import { FlatTreeControl } from '@angular/cdk/tree';
|
||||
import { MatTreeFlatDataSource, MatTreeFlattener } from '@angular/material/tree';
|
||||
|
||||
import { Settings } from '../../../shared/models/RTLconfig';
|
||||
import { LoggerService } from '../../../shared/services/logger.service';
|
||||
import { GetInfo, GetInfoChain } from '../../../shared/models/lndModels';
|
||||
import { MenuNode, FlatMenuNode, MENU_DATA } from '../../../shared/models/navMenu';
|
||||
|
||||
import { RTLEffects } from '../../../shared/store/rtl.effects';
|
||||
import * as RTLActions from '../../../shared/store/rtl.actions';
|
||||
import * as fromRTLReducer from '../../../shared/store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-side-navigation',
|
||||
templateUrl: './side-navigation.component.html',
|
||||
styleUrls: ['./side-navigation.component.scss']
|
||||
})
|
||||
export class SideNavigationComponent implements OnInit, OnDestroy {
|
||||
@Output() ChildNavClicked = new EventEmitter<any>();
|
||||
public version = '';
|
||||
public settings: Settings;
|
||||
public information: GetInfo = {};
|
||||
public informationChain: GetInfoChain = {};
|
||||
public flgLoading = true;
|
||||
public logoutNode = [{id: 100, parentId: 0, name: 'Logout', icon: 'eject'}];
|
||||
public showLogout = false;
|
||||
public numPendingChannels = 0;
|
||||
public smallScreen = false;
|
||||
private unSubs = [new Subject(), new Subject(), new Subject()];
|
||||
treeControl: FlatTreeControl<FlatMenuNode>;
|
||||
treeControlLogout: FlatTreeControl<FlatMenuNode>;
|
||||
treeFlattener: MatTreeFlattener<MenuNode, FlatMenuNode>;
|
||||
treeFlattenerLogout: MatTreeFlattener<MenuNode, FlatMenuNode>;
|
||||
navMenus: MatTreeFlatDataSource<MenuNode, FlatMenuNode>;
|
||||
navMenusLogout: MatTreeFlatDataSource<MenuNode, FlatMenuNode>;
|
||||
|
||||
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.State>, private actions$: Actions, private rtlEffects: RTLEffects, private router: Router) {
|
||||
this.version = environment.VERSION;
|
||||
if (MENU_DATA.children[MENU_DATA.children.length - 1].id === 100) {
|
||||
MENU_DATA.children.pop();
|
||||
}
|
||||
this.treeFlattener = new MatTreeFlattener(this.transformer, this.getLevel, this.isExpandable, this.getChildren);
|
||||
this.treeControl = new FlatTreeControl<FlatMenuNode>(this.getLevel, this.isExpandable);
|
||||
this.navMenus = new MatTreeFlatDataSource(this.treeControl, this.treeFlattener);
|
||||
this.navMenus.data = MENU_DATA.children;
|
||||
|
||||
this.treeFlattenerLogout = new MatTreeFlattener(this.transformer, this.getLevel, this.isExpandable, this.getChildren);
|
||||
this.treeControlLogout = new FlatTreeControl<FlatMenuNode>(this.getLevel, this.isExpandable);
|
||||
this.navMenusLogout = new MatTreeFlatDataSource(this.treeControlLogout, this.treeFlattenerLogout);
|
||||
this.navMenusLogout.data = this.logoutNode;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.store.select('rtlRoot')
|
||||
.pipe(takeUntil(this.unSubs[0]))
|
||||
.subscribe((rtlStore: fromRTLReducer.State) => {
|
||||
this.settings = rtlStore.settings;
|
||||
this.information = rtlStore.information;
|
||||
this.numPendingChannels = rtlStore.numberOfPendingChannels;
|
||||
|
||||
if (undefined !== this.information.identity_pubkey) {
|
||||
if (undefined !== this.information.chains && typeof this.information.chains[0] === 'string') {
|
||||
this.informationChain.chain = this.information.chains[0].toString();
|
||||
this.informationChain.network = (this.information.testnet) ? 'Testnet' : 'Mainnet';
|
||||
} else if (typeof this.information.chains[0] === 'object' && this.information.chains[0].hasOwnProperty('chain')) {
|
||||
const getInfoChain = <GetInfoChain>this.information.chains[0];
|
||||
this.informationChain.chain = getInfoChain.chain;
|
||||
this.informationChain.network = getInfoChain.network;
|
||||
}
|
||||
} else {
|
||||
this.informationChain.chain = '';
|
||||
this.informationChain.network = '';
|
||||
}
|
||||
|
||||
this.flgLoading = (undefined !== this.information.identity_pubkey) ? false : true;
|
||||
this.showLogout = (sessionStorage.getItem('token')) ? true : false;
|
||||
if (!sessionStorage.getItem('token')) {
|
||||
this.flgLoading = false;
|
||||
}
|
||||
if (window.innerWidth <= 414) {
|
||||
this.smallScreen = true;
|
||||
}
|
||||
this.logger.info(rtlStore);
|
||||
});
|
||||
this.actions$
|
||||
.pipe(
|
||||
takeUntil(this.unSubs[2]),
|
||||
filter((action) => action.type === RTLActions.SIGNOUT)
|
||||
).subscribe(() => {
|
||||
this.showLogout = false;
|
||||
});
|
||||
}
|
||||
|
||||
private transformer(node: MenuNode, level: number) { return new FlatMenuNode(!!node.children, level, node.id, node.parentId, node.name, node.icon, node.link); }
|
||||
|
||||
private getLevel(node: FlatMenuNode) { return node.level; }
|
||||
|
||||
private isExpandable(node: FlatMenuNode) { return node.expandable; }
|
||||
|
||||
private getChildren(node: MenuNode): Observable<MenuNode[]> { return of(node.children); }
|
||||
|
||||
hasChild(_: number, _nodeData: FlatMenuNode) { return _nodeData.expandable; }
|
||||
|
||||
toggleTree(node: FlatMenuNode) {
|
||||
this.treeControl.collapseAll();
|
||||
if (node.parentId === 0) {
|
||||
this.treeControl.expandDescendants(node);
|
||||
this.router.navigate([node.link]);
|
||||
} else {
|
||||
const parentNode = this.treeControl.dataNodes.filter(dataNode => {
|
||||
return dataNode.id === node.parentId;
|
||||
})[0];
|
||||
this.treeControl.expandDescendants(parentNode);
|
||||
}
|
||||
}
|
||||
|
||||
onClick(node: MenuNode) {
|
||||
if (node.name === 'Logout') {
|
||||
this.store.dispatch(new RTLActions.OpenConfirmation({
|
||||
width: '70%', data: { type: 'CONFIRM', titleMessage: 'Logout from this device?', noBtnText: 'Cancel', yesBtnText: 'Logout'
|
||||
}}));
|
||||
this.rtlEffects.closeConfirm
|
||||
.pipe(takeUntil(this.unSubs[1]))
|
||||
.subscribe(confirmRes => {
|
||||
if (confirmRes) {
|
||||
this.showLogout = false;
|
||||
this.store.dispatch(new RTLActions.Signout());
|
||||
}
|
||||
});
|
||||
}
|
||||
this.ChildNavClicked.emit(node);
|
||||
}
|
||||
|
||||
onChildNavClicked(node) {
|
||||
this.ChildNavClicked.emit(node);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unSubs.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
22
src/app/pages/navigation/top-menu/top-menu.component.html
Normal file
@ -0,0 +1,22 @@
|
||||
<mat-menu #topMenu="matMenu" [overlapTrigger]="false" class="top-menu">
|
||||
<p mat-menu-item>
|
||||
<mat-icon>publish</mat-icon>
|
||||
<span>Version: {{version}}</span>
|
||||
</p>
|
||||
<a mat-menu-item routerLink="/sconfig">
|
||||
<mat-icon>perm_data_setting</mat-icon>
|
||||
<span routerLink="/sconfig">Node Config</span>
|
||||
</a>
|
||||
<a mat-menu-item routerLink="/help">
|
||||
<mat-icon>help</mat-icon>
|
||||
<span routerLink="/help">Help</span>
|
||||
</a>
|
||||
<a *ngIf="showLogout" mat-menu-item (click)="onClick()">
|
||||
<mat-icon>eject</mat-icon>
|
||||
<span>Logout</span>
|
||||
</a>
|
||||
</mat-menu>
|
||||
|
||||
<button mat-icon-button [matMenuTriggerFor]="topMenu">
|
||||
<mat-icon>account_circle</mat-icon>
|
||||
</button>
|
26
src/app/pages/navigation/top-menu/top-menu.component.scss
Normal file
@ -0,0 +1,26 @@
|
||||
.mat-menu-panel.top-menu{
|
||||
.mat-toolbar, .mat-toolbar-row{
|
||||
height: 100px !important;
|
||||
padding: 0 16px !important;
|
||||
}
|
||||
.info-block{
|
||||
width: 230px;
|
||||
p{
|
||||
font-size: 16px;
|
||||
line-height: 22px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
.mat-menu-item{
|
||||
height: 36px;
|
||||
line-height: 36px;
|
||||
}
|
||||
.mat-menu-content {
|
||||
p{
|
||||
cursor: default;
|
||||
mat-icon, span, div {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
25
src/app/pages/navigation/top-menu/top-menu.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TopMenuComponent } from './top-menu.component';
|
||||
|
||||
describe('TopMenuComponent', () => {
|
||||
let component: TopMenuComponent;
|
||||
let fixture: ComponentFixture<TopMenuComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ TopMenuComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TopMenuComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
93
src/app/pages/navigation/top-menu/top-menu.component.ts
Normal file
@ -0,0 +1,93 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import { takeUntil, filter } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { Actions } from '@ngrx/effects';
|
||||
|
||||
import { Settings } from '../../../shared/models/RTLconfig';
|
||||
import { LoggerService } from '../../../shared/services/logger.service';
|
||||
import { GetInfo, GetInfoChain } from '../../../shared/models/lndModels';
|
||||
import { environment } from '../../../../environments/environment';
|
||||
|
||||
import { RTLEffects } from '../../../shared/store/rtl.effects';
|
||||
import * as fromRTLReducer from '../../../shared/store/rtl.reducers';
|
||||
import * as RTLActions from '../../../shared/store/rtl.actions';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-top-menu',
|
||||
templateUrl: './top-menu.component.html',
|
||||
styleUrls: ['./top-menu.component.scss']
|
||||
})
|
||||
export class TopMenuComponent implements OnInit, OnDestroy {
|
||||
public settings: Settings;
|
||||
public version = '';
|
||||
public information: GetInfo = {};
|
||||
public informationChain: GetInfoChain = {};
|
||||
public flgLoading = true;
|
||||
public showLogout = false;
|
||||
private unSubs = [new Subject(), new Subject(), new Subject()];
|
||||
|
||||
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.State>, private rtlEffects: RTLEffects, private actions$: Actions) {
|
||||
this.version = environment.VERSION;
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.store.select('rtlRoot')
|
||||
.pipe(takeUntil(this.unSubs[0]))
|
||||
.subscribe((rtlStore: fromRTLReducer.State) => {
|
||||
this.settings = rtlStore.settings;
|
||||
|
||||
this.information = rtlStore.information;
|
||||
this.flgLoading = (undefined !== this.information.identity_pubkey) ? false : true;
|
||||
|
||||
if (undefined !== this.information.identity_pubkey) {
|
||||
if (undefined !== this.information.chains && typeof this.information.chains[0] === 'string') {
|
||||
this.informationChain.chain = this.information.chains[0].toString();
|
||||
this.informationChain.network = (this.information.testnet) ? 'Testnet' : 'Mainnet';
|
||||
} else if (typeof this.information.chains[0] === 'object' && this.information.chains[0].hasOwnProperty('chain')) {
|
||||
const getInfoChain = <GetInfoChain>this.information.chains[0];
|
||||
this.informationChain.chain = getInfoChain.chain;
|
||||
this.informationChain.network = getInfoChain.network;
|
||||
}
|
||||
} else {
|
||||
this.informationChain.chain = '';
|
||||
this.informationChain.network = '';
|
||||
}
|
||||
this.showLogout = (sessionStorage.getItem('token')) ? true : false;
|
||||
|
||||
this.logger.info(rtlStore);
|
||||
if (!sessionStorage.getItem('token')) {
|
||||
this.flgLoading = false;
|
||||
}
|
||||
});
|
||||
this.actions$
|
||||
.pipe(
|
||||
takeUntil(this.unSubs[2]),
|
||||
filter((action) => action.type === RTLActions.SIGNOUT)
|
||||
).subscribe(() => {
|
||||
this.showLogout = false;
|
||||
});
|
||||
}
|
||||
|
||||
onClick() {
|
||||
this.store.dispatch(new RTLActions.OpenConfirmation({
|
||||
width: '70%', data: { type: 'CONFIRM', titleMessage: 'Logout from this device?', noBtnText: 'Cancel', yesBtnText: 'Logout'
|
||||
}}));
|
||||
this.rtlEffects.closeConfirm
|
||||
.pipe(takeUntil(this.unSubs[1]))
|
||||
.subscribe(confirmRes => {
|
||||
if (confirmRes) {
|
||||
this.showLogout = false;
|
||||
this.store.dispatch(new RTLActions.Signout());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unSubs.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
81
src/app/pages/payments/payments.component.html
Normal file
@ -0,0 +1,81 @@
|
||||
<div fxLayout="column">
|
||||
<div class="padding-gap">
|
||||
<mat-card>
|
||||
<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="30" fxLayoutAlign="space-between stretch">
|
||||
<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-raised-button color="accent" type="reset" tabindex="3" type="reset" (click)="resetData()">Clear</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
<div class="padding-gap">
|
||||
<mat-card>
|
||||
<mat-card-content class="table-card-content">
|
||||
<div fxLayout="row" fxLayoutAlign="start start">
|
||||
<mat-form-field fxFlex="30">
|
||||
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div perfectScrollbar class="table-container mat-elevation-z8">
|
||||
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
|
||||
<table mat-table #table [dataSource]="payments" matSort [ngClass]="{'mat-elevation-z8 overflow-auto error-border': flgLoading[0]==='error','mat-elevation-z8 overflow-auto': true}">
|
||||
<ng-container matColumnDef="creation_date">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Creation Date</th>
|
||||
<td mat-cell *matCellDef="let payment">{{payment?.creation_date_str}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="payment_hash">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Payment Hash</th>
|
||||
<td mat-cell *matCellDef="let payment">
|
||||
<div>{{payment?.payment_hash | slice:0:10}}...</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="fee">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Fee</th>
|
||||
<td mat-cell *matCellDef="let payment"><span fxLayoutAlign="end center">{{payment?.fee | number}}</span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="value">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Value</th>
|
||||
<td mat-cell *matCellDef="let payment"><span fxLayoutAlign="end center">{{payment?.value | number}}</span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="payment_preimage">
|
||||
<th mat-header-cell class="pl-4" *matHeaderCellDef mat-sort-header>Payment Pre Image</th>
|
||||
<td mat-cell class="pl-4" *matCellDef="let payment">
|
||||
<div>{{payment?.payment_preimage | slice:0:10}}...</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="value_msat">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Value MSat</th>
|
||||
<td mat-cell *matCellDef="let payment"><span fxLayoutAlign="end center">{{payment?.value_msat | number}}</span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="value_sat">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Value Sat</th>
|
||||
<td mat-cell *matCellDef="let payment"><span fxLayoutAlign="end center">{{payment?.value_sat | number}}</span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="path">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Path</th>
|
||||
<td mat-cell *matCellDef="let payment">{{payment?.path?.length || 0}} Hops</td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true;"></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>
|
43
src/app/pages/payments/payments.component.scss
Normal file
@ -0,0 +1,43 @@
|
||||
.mat-column-path {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.mat-expansion-panel-header {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mat-accordion .mat-expansion-panel {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.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%;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
height: 68vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 414px) {
|
||||
.table-container {
|
||||
max-height: 46vh;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
192
src/app/pages/payments/payments.component.ts
Normal file
@ -0,0 +1,192 @@
|
||||
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { formatDate } from '@angular/common';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { MatTableDataSource, MatSort } from '@angular/material';
|
||||
import { Settings } from '../../shared/models/RTLconfig';
|
||||
import { GetInfo, Payment, PayRequest } from '../../shared/models/lndModels';
|
||||
import { LoggerService } from '../../shared/services/logger.service';
|
||||
|
||||
import { newlyAddedRowAnimation } from '../../shared/animation/row-animation';
|
||||
import { RTLEffects } from '../../shared/store/rtl.effects';
|
||||
import * as RTLActions from '../../shared/store/rtl.actions';
|
||||
import * as fromRTLReducer from '../../shared/store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-payments',
|
||||
templateUrl: './payments.component.html',
|
||||
styleUrls: ['./payments.component.scss'],
|
||||
animations: [newlyAddedRowAnimation]
|
||||
})
|
||||
export class PaymentsComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(MatSort) sort: MatSort;
|
||||
@ViewChild('sendPaymentForm') form;
|
||||
public newlyAddedPayment = '';
|
||||
public flgAnimate = true;
|
||||
public settings: Settings;
|
||||
public flgLoading: Array<Boolean | 'error'> = [true];
|
||||
public information: GetInfo = {};
|
||||
public payments: any;
|
||||
public paymentJSONArr: Payment[] = [];
|
||||
public displayedColumns = [];
|
||||
public paymentDecoded: PayRequest = {};
|
||||
public paymentRequest = '';
|
||||
private unsub: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
|
||||
|
||||
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.State>, private rtlEffects: RTLEffects) {
|
||||
switch (true) {
|
||||
case (window.innerWidth <= 415):
|
||||
this.displayedColumns = ['creation_date', 'fee', 'value'];
|
||||
break;
|
||||
case (window.innerWidth > 415 && window.innerWidth <= 730):
|
||||
this.displayedColumns = ['creation_date', 'payment_hash', 'fee', 'value', 'payment_preimage'];
|
||||
break;
|
||||
case (window.innerWidth > 730 && window.innerWidth <= 1024):
|
||||
this.displayedColumns = ['creation_date', 'payment_hash', 'fee', 'value', 'payment_preimage', 'path'];
|
||||
break;
|
||||
case (window.innerWidth > 1024 && window.innerWidth <= 1280):
|
||||
this.displayedColumns = ['creation_date', 'payment_hash', 'fee', 'value', 'payment_preimage', 'value_msat', 'value_sat', 'path'];
|
||||
break;
|
||||
default:
|
||||
this.displayedColumns = ['creation_date', 'payment_hash', 'fee', 'value', 'payment_preimage', 'value_msat', 'value_sat', 'path'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.store.select('rtlRoot')
|
||||
.pipe(takeUntil(this.unsub[0]))
|
||||
.subscribe((rtlStore: fromRTLReducer.State) => {
|
||||
rtlStore.effectErrors.forEach(effectsErr => {
|
||||
if (effectsErr.action === 'FetchPayments') {
|
||||
this.flgLoading[0] = 'error';
|
||||
}
|
||||
});
|
||||
this.settings = rtlStore.settings;
|
||||
this.information = rtlStore.information;
|
||||
this.paymentJSONArr = (rtlStore.payments.length > 0) ? rtlStore.payments : [];
|
||||
this.payments = (undefined === rtlStore.payments) ? new MatTableDataSource([]) : new MatTableDataSource<Payment>([...this.paymentJSONArr]);
|
||||
this.payments.data = this.paymentJSONArr;
|
||||
this.payments.sort = this.sort;
|
||||
this.payments.data.forEach(payment => {
|
||||
payment.creation_date_str = (payment.creation_date_str === '') ? '' : formatDate(payment.creation_date_str, 'MMM/dd/yy HH:mm:ss', 'en-US');
|
||||
});
|
||||
setTimeout(() => { this.flgAnimate = false; }, 5000);
|
||||
if (this.flgLoading[0] !== 'error') {
|
||||
this.flgLoading[0] = (undefined !== this.paymentJSONArr[0]) ? false : true;
|
||||
}
|
||||
this.logger.info(rtlStore);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
onSendPayment() {
|
||||
if (undefined !== this.paymentDecoded.timestamp_str) {
|
||||
this.sendPayment();
|
||||
} else {
|
||||
this.store.dispatch(new RTLActions.OpenSpinner('Decoding Payment...'));
|
||||
this.store.dispatch(new RTLActions.DecodePayment(this.paymentRequest));
|
||||
this.rtlEffects.setDecodedPayment
|
||||
.pipe(takeUntil(this.unsub[1]))
|
||||
.subscribe(decodedPayment => {
|
||||
this.paymentDecoded = decodedPayment;
|
||||
if (undefined !== this.paymentDecoded.timestamp_str) {
|
||||
this.paymentDecoded.timestamp_str = (this.paymentDecoded.timestamp_str === '') ? '' :
|
||||
formatDate(this.paymentDecoded.timestamp_str, 'MMM/dd/yy HH:mm:ss', 'en-US');
|
||||
if (undefined === this.paymentDecoded.num_satoshis) {
|
||||
this.paymentDecoded.num_satoshis = '0';
|
||||
}
|
||||
this.sendPayment();
|
||||
} else {
|
||||
this.resetData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
sendPayment() {
|
||||
this.flgAnimate = true;
|
||||
this.newlyAddedPayment = this.paymentDecoded.payment_hash;
|
||||
if (undefined === this.paymentDecoded.num_satoshis || this.paymentDecoded.num_satoshis === '' || this.paymentDecoded.num_satoshis === '0') {
|
||||
const titleMsg = 'This is an empty invoice. Enter the amount (Sats) to pay.';
|
||||
this.store.dispatch(new RTLActions.OpenConfirmation({ width: '70%', data: {
|
||||
type: 'CONFIRM', titleMessage: titleMsg, message: JSON.stringify(this.paymentDecoded), noBtnText: 'Cancel', yesBtnText: 'Send', flgShowInput: true, getInputs: [
|
||||
{placeholder: 'Amount (Sats)', inputType: 'number', inputValue: ''}
|
||||
]
|
||||
}}));
|
||||
this.rtlEffects.closeConfirm
|
||||
.pipe(takeUntil(this.unsub[2]))
|
||||
.subscribe(confirmRes => {
|
||||
if (confirmRes) {
|
||||
this.paymentDecoded.num_satoshis = confirmRes[0].inputValue;
|
||||
this.store.dispatch(new RTLActions.OpenSpinner('Sending Payment...'));
|
||||
this.store.dispatch(new RTLActions.SendPayment([this.paymentRequest, this.paymentDecoded, true]));
|
||||
this.resetData();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.store.dispatch(new RTLActions.OpenConfirmation({ width: '70%', data: {
|
||||
type: 'CONFIRM', titleMessage: 'Send Payment', noBtnText: 'Cancel', yesBtnText: 'Send', message: JSON.stringify(this.paymentDecoded)
|
||||
}}));
|
||||
this.rtlEffects.closeConfirm
|
||||
.pipe(takeUntil(this.unsub[3]))
|
||||
.subscribe(confirmRes => {
|
||||
if (confirmRes) {
|
||||
this.store.dispatch(new RTLActions.OpenSpinner('Sending Payment...'));
|
||||
this.store.dispatch(new RTLActions.SendPayment([this.paymentRequest, this.paymentDecoded, false]));
|
||||
this.resetData();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onVerifyPayment() {
|
||||
this.store.dispatch(new RTLActions.OpenSpinner('Decoding Payment...'));
|
||||
this.store.dispatch(new RTLActions.DecodePayment(this.paymentRequest));
|
||||
this.rtlEffects.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, 'MMM/dd/yy HH:mm:ss', 'en-US');
|
||||
} else {
|
||||
this.resetData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
resetData() {
|
||||
this.form.reset();
|
||||
}
|
||||
|
||||
onPaymentClick(selRow: Payment, event: any) {
|
||||
const flgExpansionClicked = event.target.className.includes('mat-expansion-panel-header') || event.target.className.includes('mat-expansion-indicator');
|
||||
if (flgExpansionClicked) {
|
||||
return;
|
||||
}
|
||||
const selPayment = this.payments.data.filter(payment => {
|
||||
return payment.payment_hash === selRow.payment_hash;
|
||||
})[0];
|
||||
const reorderedPayment = JSON.parse(JSON.stringify(selPayment, [
|
||||
'creation_date_str', 'payment_hash', 'fee', 'value_msat', 'value_sat', 'value', 'payment_preimage', 'path'
|
||||
] , 2));
|
||||
this.store.dispatch(new RTLActions.OpenAlert({ width: '75%', data: {
|
||||
type: 'INFO',
|
||||
message: JSON.stringify(reorderedPayment)
|
||||
}}));
|
||||
}
|
||||
|
||||
applyFilter(selFilter: string) {
|
||||
this.payments.filter = selFilter;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unsub.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
87
src/app/pages/peers/peers.component.html
Normal file
@ -0,0 +1,87 @@
|
||||
<div fxLayout="column">
|
||||
<div class="padding-gap">
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-subtitle>
|
||||
<h2>Add Peer</h2>
|
||||
</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<form fxLayout="column" fxLayout.gt-sm="row wrap" (ngSubmit)="addPeerForm.form.valid && onAddPeer(addPeerForm)" #addPeerForm="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 addText">Invalid Address</p>
|
||||
<ng-template #addText><p>Add</p></ng-template>
|
||||
</button>
|
||||
</div>
|
||||
<div fxFlex="15" fxLayoutAlign="start start">
|
||||
<button fxFlex="90" fxLayoutAlign="center center" mat-raised-button color="accent" tabindex="2" type="reset" (click)="resetData()">Clear</button>
|
||||
</div>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
<div class="padding-gap">
|
||||
<mat-card>
|
||||
<mat-card-content class="table-card-content">
|
||||
<div fxLayout="row" fxLayoutAlign="start start">
|
||||
<mat-form-field fxFlex="30">
|
||||
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div perfectScrollbar class="table-container mat-elevation-z8">
|
||||
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
|
||||
<table mat-table #table [dataSource]="peers" matSort [ngClass]="{'mat-elevation-z8 overflow-x-auto error-border': flgLoading[0]==='error','mat-elevation-z8 overflow-x-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="pub_key">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Pub Key </th>
|
||||
<td mat-cell *matCellDef="let peer">
|
||||
<div> {{peer?.pub_key | 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="address">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Address </th>
|
||||
<td mat-cell *matCellDef="let peer"> {{peer?.address}} </td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="bytes_sent">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Bytes Sent </th>
|
||||
<td mat-cell *matCellDef="let peer"><span fxLayoutAlign="end center"> {{peer?.bytes_sent | number}} </span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="bytes_recv">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Bytes Recv </th>
|
||||
<td mat-cell *matCellDef="let peer"><span fxLayoutAlign="end center"> {{peer?.bytes_recv | number}} </span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="sat_sent">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> {{information?.smaller_currency_unit}} 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"> {{information?.smaller_currency_unit}} Recv </th>
|
||||
<td mat-cell *matCellDef="let peer"><span fxLayoutAlign="end center"> {{peer?.sat_recv | number}} </span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="inbound">
|
||||
<th mat-header-cell class="pl-4" *matHeaderCellDef mat-sort-header> Inbound </th>
|
||||
<td mat-cell class="pl-4" *matCellDef="let peer"> {{peer?.inbound}} </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>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true;"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [@newlyAddedRowAnimation]="(row.pub_key === newlyAddedPeer && flgAnimate) ? 'added' : 'notAdded'" (click)="onPeerClick(row, $event)"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
30
src/app/pages/peers/peers.component.scss
Normal file
@ -0,0 +1,30 @@
|
||||
.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%;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
height: 68vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 414px) {
|
||||
.table-container {
|
||||
height: 40vh;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
25
src/app/pages/peers/peers.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PeersComponent } from './peers.component';
|
||||
|
||||
describe('PeersComponent', () => {
|
||||
let component: PeersComponent;
|
||||
let fixture: ComponentFixture<PeersComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ PeersComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(PeersComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
157
src/app/pages/peers/peers.component.ts
Normal file
@ -0,0 +1,157 @@
|
||||
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 { MatTableDataSource, MatSort } from '@angular/material';
|
||||
import { Peer, GetInfo } from '../../shared/models/lndModels';
|
||||
import { LoggerService } from '../../shared/services/logger.service';
|
||||
|
||||
import { newlyAddedRowAnimation } from '../../shared/animation/row-animation';
|
||||
import { RTLEffects } from '../../shared/store/rtl.effects';
|
||||
import * as RTLActions from '../../shared/store/rtl.actions';
|
||||
import * as fromRTLReducer from '../../shared/store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-peers',
|
||||
templateUrl: './peers.component.html',
|
||||
styleUrls: ['./peers.component.scss'],
|
||||
animations: [newlyAddedRowAnimation]
|
||||
})
|
||||
export class PeersComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(MatSort) sort: MatSort;
|
||||
public newlyAddedPeer = '';
|
||||
public flgAnimate = true;
|
||||
public displayedColumns = [];
|
||||
public peerAddress = '';
|
||||
public peers: any;
|
||||
public information: GetInfo = {};
|
||||
public flgLoading: Array<Boolean | 'error'> = [true]; // 0: peers
|
||||
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
|
||||
|
||||
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.State>, private rtlEffects: RTLEffects, private actions$: Actions) {
|
||||
switch (true) {
|
||||
case (window.innerWidth <= 415):
|
||||
this.displayedColumns = ['detach', 'pub_key', 'alias'];
|
||||
break;
|
||||
case (window.innerWidth > 415 && window.innerWidth <= 730):
|
||||
this.displayedColumns = ['detach', 'pub_key', 'alias', 'address', 'sat_sent', 'sat_recv'];
|
||||
break;
|
||||
case (window.innerWidth > 730 && window.innerWidth <= 1024):
|
||||
this.displayedColumns = ['detach', 'pub_key', 'alias', 'address', 'sat_sent', 'sat_recv', 'inbound'];
|
||||
break;
|
||||
case (window.innerWidth > 1024 && window.innerWidth <= 1280):
|
||||
this.displayedColumns = ['detach', 'pub_key', 'alias', 'address', 'sat_sent', 'sat_recv', 'inbound', 'ping_time'];
|
||||
break;
|
||||
default:
|
||||
this.displayedColumns = ['detach', 'pub_key', 'alias', 'address', 'bytes_sent', 'bytes_recv', 'sat_sent', 'sat_recv', 'inbound', 'ping_time'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.store.select('rtlRoot')
|
||||
.pipe(takeUntil(this.unSubs[0]))
|
||||
.subscribe((rtlStore: fromRTLReducer.State) => {
|
||||
rtlStore.effectErrors.forEach(effectsErr => {
|
||||
if (effectsErr.action === 'FetchPeers') {
|
||||
this.flgLoading[0] = 'error';
|
||||
}
|
||||
});
|
||||
this.information = rtlStore.information;
|
||||
this.peers = new MatTableDataSource([]);
|
||||
this.peers.data = [];
|
||||
if (undefined !== rtlStore.peers) {
|
||||
this.peers = new MatTableDataSource<Peer>([...rtlStore.peers]);
|
||||
this.peers.data = rtlStore.peers;
|
||||
setTimeout(() => { this.flgAnimate = false; }, 5000);
|
||||
}
|
||||
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)
|
||||
).subscribe((setPeers: RTLActions.SetPeers) => {
|
||||
this.peerAddress = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
onAddPeer(form: any) {
|
||||
this.flgAnimate = true;
|
||||
const pattern = '^([a-zA-Z0-9]){1,66}@(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]):[0-9]+$';
|
||||
const deviderIndex = this.peerAddress.search('@');
|
||||
let pubkey = '';
|
||||
let host = '';
|
||||
|
||||
if (new RegExp(pattern).test(this.peerAddress)) {
|
||||
pubkey = this.peerAddress.substring(0, deviderIndex);
|
||||
host = this.peerAddress.substring(deviderIndex + 1);
|
||||
this.addPeerWithParams(pubkey, host);
|
||||
} else {
|
||||
pubkey = (deviderIndex > -1) ? this.peerAddress.substring(0, deviderIndex) : this.peerAddress;
|
||||
this.store.dispatch(new RTLActions.OpenSpinner('Getting Node Address...'));
|
||||
this.store.dispatch(new RTLActions.FetchGraphNode(pubkey));
|
||||
this.rtlEffects.setGraphNode
|
||||
.pipe(take(1))
|
||||
.subscribe(graphNode => {
|
||||
host = (undefined === graphNode.node.addresses || undefined === graphNode.node.addresses[0].addr) ? '' : graphNode.node.addresses[0].addr;
|
||||
this.addPeerWithParams(pubkey, host);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
addPeerWithParams(pubkey: string, host: string) {
|
||||
this.newlyAddedPeer = pubkey;
|
||||
this.store.dispatch(new RTLActions.OpenSpinner('Adding Peer...'));
|
||||
this.store.dispatch(new RTLActions.SaveNewPeer({pubkey: pubkey, host: host, perm: false}));
|
||||
}
|
||||
|
||||
onPeerClick(selRow: Peer, 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.pub_key === selRow.pub_key;
|
||||
})[0];
|
||||
const reorderedPeer = JSON.parse(JSON.stringify(selPeer, [
|
||||
'pub_key', 'alias', 'address', 'bytes_sent', 'bytes_recv', 'sat_sent', 'sat_recv', 'inbound', 'ping_time'
|
||||
] , 2));
|
||||
this.store.dispatch(new RTLActions.OpenAlert({ width: '75%', data: { type: 'INFO', message: JSON.stringify(reorderedPeer)}}));
|
||||
}
|
||||
|
||||
resetData() {
|
||||
this.peerAddress = '';
|
||||
}
|
||||
|
||||
onPeerDetach(peerToDetach: Peer) {
|
||||
const msg = 'Detach peer: ' + peerToDetach.pub_key;
|
||||
const msg_type = 'CONFIRM';
|
||||
this.store.dispatch(new RTLActions.OpenConfirmation({ width: '70%', data: { type: msg_type, 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.DetachPeer({pubkey: peerToDetach.pub_key}));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
applyFilter(selFilter: string) {
|
||||
this.peers.filter = selFilter;
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unSubs.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
}
|
37
src/app/pages/server-config/server-config.component.html
Normal file
@ -0,0 +1,37 @@
|
||||
<div fxLayout="column">
|
||||
<div class="padding-gap">
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-subtitle>
|
||||
<h2>Show Configurations</h2>
|
||||
</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<form fxLayout="column" fxLayoutAlign="space-between stretch" fxLayout.gt-md="row wrap">
|
||||
<mat-radio-group fxFlex="20" fxLayoutAlign="start" (change)="onSelectionChange($event)" class="mt-1 mb-1">
|
||||
<mat-radio-button class="pr-5" value="lnd" *ngIf="showLND" [checked]="selectedNodeType=='lnd'">LND</mat-radio-button>
|
||||
<mat-radio-button class="pr-5" value="bitcoind" *ngIf="showBitcoind" [checked]="selectedNodeType=='bitcoind'">BITCOIND</mat-radio-button>
|
||||
<mat-radio-button class="pr-5" value="rtl" [checked]="selectedNodeType=='rtl'">RTL</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
<div fxFlex="30" fxLayoutAlign="space-between stretch">
|
||||
<button fxFlex="50" fxLayoutAlign="center center" mat-raised-button color="primary" (click)="onShowConfig()" tabindex="2">Show Config</button>
|
||||
<button fxFlex="50" fxLayoutAlign="center center" mat-raised-button color="accent" tabindex="3" type="reset" class="ml-2" (click)="resetData()">Clear</button>
|
||||
</div>
|
||||
</form>
|
||||
<div *ngIf="configData !== ''">
|
||||
<mat-list>
|
||||
<mat-list-item *ngFor="let conf of configData; index as i;">
|
||||
<mat-card-subtitle class="my-1">
|
||||
<h2 *ngIf="conf.indexOf('[') >= 0">{{conf}}</h2>
|
||||
</mat-card-subtitle>
|
||||
<mat-card-subtitle class="m-0">
|
||||
<h4 *ngIf="conf.indexOf('[') < 0" class="ml-4">{{conf}}</h4>
|
||||
</mat-card-subtitle>
|
||||
<mat-divider [inset]="true" *ngIf="conf.indexOf('[') < 0"></mat-divider>
|
||||
</mat-list-item>
|
||||
</mat-list>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
3
src/app/pages/server-config/server-config.component.scss
Normal file
@ -0,0 +1,3 @@
|
||||
h4 {
|
||||
word-break: break-word;
|
||||
}
|
72
src/app/pages/server-config/server-config.component.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { RTLEffects } from '../../shared/store/rtl.effects';
|
||||
import * as RTLActions from '../../shared/store/rtl.actions';
|
||||
import * as fromRTLReducer from '../../shared/store/rtl.reducers';
|
||||
import { Authentication } from '../../shared/models/RTLconfig';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-server-config',
|
||||
templateUrl: './server-config.component.html',
|
||||
styleUrls: ['./server-config.component.scss']
|
||||
})
|
||||
export class ServerConfigComponent implements OnInit, OnDestroy {
|
||||
public selectedNodeType = 'lnd';
|
||||
public authSettings: Authentication = {};
|
||||
public showLND = false;
|
||||
public showBitcoind = false;
|
||||
public configData = '';
|
||||
private unsubs: Array<Subject<void>> = [new Subject(), new Subject()];
|
||||
|
||||
constructor(private store: Store<fromRTLReducer.State>, private rtlEffects: RTLEffects) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.store.select('rtlRoot')
|
||||
.pipe(takeUntil(this.unsubs[0]))
|
||||
.subscribe((rtlStore: fromRTLReducer.State) => {
|
||||
rtlStore.effectErrors.forEach(effectsErr => {
|
||||
if (effectsErr.action === 'fetchConfig') {
|
||||
this.resetData();
|
||||
}
|
||||
});
|
||||
this.authSettings = rtlStore.authSettings;
|
||||
if (undefined !== this.authSettings && this.authSettings.lndConfigPath !== '') {
|
||||
this.showLND = true;
|
||||
}
|
||||
if (undefined !== this.authSettings && undefined !== this.authSettings.bitcoindConfigPath && this.authSettings.bitcoindConfigPath !== '') {
|
||||
this.showBitcoind = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onSelectionChange(event) {
|
||||
this.selectedNodeType = event.value;
|
||||
this.configData = '';
|
||||
}
|
||||
|
||||
onShowConfig() {
|
||||
this.store.dispatch(new RTLActions.OpenSpinner('Opening Config File...'));
|
||||
this.store.dispatch(new RTLActions.FetchConfig(this.selectedNodeType));
|
||||
this.rtlEffects.showLNDConfig
|
||||
.pipe(takeUntil(this.unsubs[1]))
|
||||
.subscribe((configFile: any) => {
|
||||
this.configData = (configFile === '' || undefined === configFile) ? [] : configFile.split('\n');
|
||||
});
|
||||
}
|
||||
|
||||
resetData() {
|
||||
this.configData = '';
|
||||
this.selectedNodeType = 'lnd';
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unsubs.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
22
src/app/pages/signin/signin.component.html
Normal file
@ -0,0 +1,22 @@
|
||||
<div fxLayout="column">
|
||||
<div class="padding-gap">
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-subtitle>
|
||||
<h2>Login to RTL</h2>
|
||||
</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<form (ngSubmit)="onSignin()" #signinForm="ngForm">
|
||||
<mat-form-field fxFlex="50" fxLayoutAlign="start">
|
||||
<input matInput placeholder="Password" type="password" id="password" name="password" [(ngModel)]="password" tabindex="1" required>
|
||||
<mat-hint>{{hintStr}}</mat-hint>
|
||||
</mat-form-field>
|
||||
<button fxFlex="10" class="mr-2" fxLayoutAlign="center center" mat-raised-button color="primary" tabindex="2" type="submit" [disabled]="!password">Login</button>
|
||||
<button fxFlex="10" fxLayoutAlign="center center" mat-raised-button color="accent" tabindex="3" type="reset" (click)="resetData()">Clear</button>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
|
0
src/app/pages/signin/signin.component.scss
Normal file
59
src/app/pages/signin/signin.component.ts
Normal file
@ -0,0 +1,59 @@
|
||||
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 '../../shared/store/rtl.reducers';
|
||||
import * as RTLActions from '../../shared/store/rtl.actions';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-signin',
|
||||
templateUrl: './signin.component.html',
|
||||
styleUrls: ['./signin.component.scss']
|
||||
})
|
||||
export class SigninComponent implements OnInit, OnDestroy {
|
||||
password = '';
|
||||
nodeAuthType = '';
|
||||
rtlSSO = 0;
|
||||
rtlCookiePath = '';
|
||||
hintStr = '';
|
||||
accessKey = '';
|
||||
|
||||
private unsub: Array<Subject<void>> = [new Subject(), new Subject(), new Subject()];
|
||||
|
||||
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.State>) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.store.select('rtlRoot')
|
||||
.pipe(takeUntil(this.unsub[0]))
|
||||
.subscribe((rtlStore: fromRTLReducer.State) => {
|
||||
rtlStore.effectErrors.forEach(effectsErr => {
|
||||
this.logger.error(effectsErr);
|
||||
});
|
||||
this.nodeAuthType = rtlStore.authSettings.nodeAuthType;
|
||||
this.logger.info(rtlStore);
|
||||
if (this.nodeAuthType.toUpperCase() === 'DEFAULT') {
|
||||
this.hintStr = 'Enter RPC password';
|
||||
} else {
|
||||
this.hintStr = ''; // Do not remove, initial passowrd 'DEFAULT' is initilizing its value
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onSignin() {
|
||||
this.store.dispatch(new RTLActions.Signin(window.btoa(this.password)));
|
||||
}
|
||||
|
||||
resetData() {
|
||||
this.password = '';
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unsub.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
87
src/app/pages/switch/forwarding-history.component.html
Normal file
@ -0,0 +1,87 @@
|
||||
<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-raised-button color="accent" 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>
|
||||
<mat-card-content class="table-card-content">
|
||||
<div perfectScrollbar class="table-container mat-elevation-z8">
|
||||
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
|
||||
<table mat-table #table [dataSource]="forwardingHistoryEvents" matSort
|
||||
[ngClass]="{'mat-elevation-z8 overflow-auto error-border': flgLoading[0]==='error','mat-elevation-z8 overflow-auto': true}">
|
||||
<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>Chan Id In</th>
|
||||
<td mat-cell *matCellDef="let fhEvent">{{fhEvent.chan_id_in}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="alias_in">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Alias In</th>
|
||||
<td mat-cell *matCellDef="let fhEvent">{{fhEvent.alias_in}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="chan_id_out">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Chan Id Out</th>
|
||||
<td mat-cell *matCellDef="let fhEvent">{{fhEvent.chan_id_out}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="alias_out">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Alias Out</th>
|
||||
<td mat-cell *matCellDef="let fhEvent">{{fhEvent.alias_out}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="amt_out">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Amount
|
||||
Out (Sats)</th>
|
||||
<td mat-cell *matCellDef="let fhEvent"><span fxLayoutAlign="end center">{{fhEvent.amt_out | number}}</span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="amt_in">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Amount
|
||||
In (Sats)</th>
|
||||
<td mat-cell *matCellDef="let fhEvent"><span fxLayoutAlign="end center">{{fhEvent.amt_in | number}}</span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="fee">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Fee
|
||||
(Sats)</th>
|
||||
<td mat-cell *matCellDef="let fhEvent"><span fxLayoutAlign="end center">{{fhEvent.fee | number}}</span></td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true;"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;"
|
||||
(click)="onForwardingEventClick(row, $event)"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
21
src/app/pages/switch/forwarding-history.component.scss
Normal file
@ -0,0 +1,21 @@
|
||||
.mat-column-amt_in {
|
||||
flex: 0 0 15%;
|
||||
min-width: 120px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
table {
|
||||
width:100%;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
height: 72vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 414px) {
|
||||
.table-container {
|
||||
height: 53vh;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
25
src/app/pages/switch/forwarding-history.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ForwardingHistoryComponent } from './forwarding-history.component';
|
||||
|
||||
describe('ForwardingHistoryComponent', () => {
|
||||
let component: ForwardingHistoryComponent;
|
||||
let fixture: ComponentFixture<ForwardingHistoryComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ForwardingHistoryComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ForwardingHistoryComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
123
src/app/pages/switch/forwarding-history.component.ts
Normal file
@ -0,0 +1,123 @@
|
||||
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { formatDate } from '@angular/common';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { MatTableDataSource, MatSort } from '@angular/material';
|
||||
import { ForwardingEvent } from '../../shared/models/lndModels';
|
||||
import { LoggerService } from '../../shared/services/logger.service';
|
||||
|
||||
import * as RTLActions from '../../shared/store/rtl.actions';
|
||||
import * as fromRTLReducer from '../../shared/store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-forwarding-history',
|
||||
templateUrl: './forwarding-history.component.html',
|
||||
styleUrls: ['./forwarding-history.component.scss']
|
||||
})
|
||||
export class ForwardingHistoryComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(MatSort) 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;
|
||||
private unsub: Array<Subject<void>> = [new Subject(), new Subject()];
|
||||
|
||||
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.State>) {
|
||||
switch (true) {
|
||||
case (window.innerWidth <= 415):
|
||||
this.displayedColumns = ['timestamp', 'amt_out', 'amt_in'];
|
||||
break;
|
||||
case (window.innerWidth > 415 && window.innerWidth <= 730):
|
||||
this.displayedColumns = ['timestamp', 'amt_out', 'amt_in', 'fee'];
|
||||
break;
|
||||
case (window.innerWidth > 730 && window.innerWidth <= 1024):
|
||||
this.displayedColumns = ['timestamp', 'chan_id_in', 'chan_id_out', 'amt_out', 'amt_in', 'fee'];
|
||||
break;
|
||||
case (window.innerWidth > 1024 && window.innerWidth <= 1280):
|
||||
this.displayedColumns = ['timestamp', 'chan_id_in', 'chan_id_out', 'amt_out', 'amt_in', 'fee'];
|
||||
break;
|
||||
default:
|
||||
this.displayedColumns = ['timestamp', 'chan_id_in', 'chan_id_out', 'amt_out', 'amt_in', 'fee'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.store.dispatch(new RTLActions.GetForwardingHistory({}));
|
||||
this.store.select('rtlRoot')
|
||||
.pipe(takeUntil(this.unsub[0]))
|
||||
.subscribe((rtlStore: fromRTLReducer.State) => {
|
||||
rtlStore.effectErrors.forEach(effectsErr => {
|
||||
if (effectsErr.action === 'GetForwardingHistory') {
|
||||
this.flgLoading[0] = 'error';
|
||||
}
|
||||
});
|
||||
if (undefined !== rtlStore.forwardingHistory && undefined !== rtlStore.forwardingHistory.forwarding_events) {
|
||||
this.lastOffsetIndex = rtlStore.forwardingHistory.last_offset_index;
|
||||
this.loadForwardingEventsTable(rtlStore.forwardingHistory.forwarding_events);
|
||||
}
|
||||
if (this.flgLoading[0] !== 'error') {
|
||||
this.flgLoading[0] = (undefined !== rtlStore.forwardingHistory) ? false : true;
|
||||
}
|
||||
this.logger.info(rtlStore);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
onForwardingEventClick(selRow: ForwardingEvent, event: any) {
|
||||
const selFEvent = this.forwardingHistoryEvents.data.filter(fhEvent => {
|
||||
return fhEvent.chan_id_in === selRow.chan_id_in;
|
||||
})[0];
|
||||
const reorderedFHEvent = JSON.parse(JSON.stringify(selFEvent, ['timestamp_str', 'chan_id_in', 'alias_in', 'chan_id_out', 'alias_out', 'amt_out', 'amt_in', 'fee'] , 2));
|
||||
this.store.dispatch(new RTLActions.OpenAlert({ width: '75%', data: {
|
||||
type: 'INFO',
|
||||
message: JSON.stringify(reorderedFHEvent)
|
||||
}}));
|
||||
}
|
||||
|
||||
loadForwardingEventsTable(forwardingEvents: ForwardingEvent[]) {
|
||||
this.forwardingHistoryEvents = new MatTableDataSource<ForwardingEvent>([...forwardingEvents]);
|
||||
this.forwardingHistoryEvents.sort = this.sort;
|
||||
this.forwardingHistoryEvents.data.forEach(event => {
|
||||
event.timestamp_str = (event.timestamp_str === '') ? '' : formatDate(event.timestamp_str, 'MMM/dd/yy HH:mm:ss', 'en-US');
|
||||
});
|
||||
|
||||
this.logger.info(this.forwardingHistoryEvents);
|
||||
}
|
||||
|
||||
onForwardingHistoryFetch() {
|
||||
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.GetForwardingHistory({
|
||||
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 = [];
|
||||
}
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.resetData();
|
||||
this.unsub.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
<div fxLayout="column">
|
||||
<div class="padding-gap">
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-subtitle>
|
||||
<h2>Transactions</h2>
|
||||
</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="table-card-content">
|
||||
<div fxLayout="row" fxLayoutAlign="start start">
|
||||
<mat-form-field fxFlex="30">
|
||||
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div perfectScrollbar class="table-container mat-elevation-z8">
|
||||
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
|
||||
<table mat-table #table [dataSource]="listTransactions" matSort [ngClass]="{'mat-elevation-z8 overflow-auto error-border': flgLoading[0]==='error','mat-elevation-z8 overflow-auto': true}">
|
||||
<ng-container matColumnDef="dest_addresses">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header>Destination Addresses</th>
|
||||
<td mat-cell *matCellDef="let trans">{{trans?.dest_addresses?.length || 0}} Addr</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="time_stamp">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Timestamp </th>
|
||||
<td mat-cell *matCellDef="let trans"> {{trans.time_stamp_str}}</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="num_confirmations">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Num Confirmations </th>
|
||||
<td mat-cell *matCellDef="let trans"><span fxLayoutAlign="end center"> {{trans.num_confirmations | number}} </span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="total_fees">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Total Fees </th>
|
||||
<td mat-cell *matCellDef="let trans"><span fxLayoutAlign="end center"> {{trans.total_fees | number}} </span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="block_hash">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Block Hash </th>
|
||||
<td mat-cell *matCellDef="let trans"> {{trans.block_hash | slice:0:10}}...</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="block_height">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Block Height </th>
|
||||
<td mat-cell *matCellDef="let trans"><span fxLayoutAlign="end center"> {{trans.block_height | number}} </span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="tx_hash">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header> Txn Hash </th>
|
||||
<td mat-cell *matCellDef="let trans"><div>{{trans.tx_hash | slice:0:10}}...</div></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="amount">
|
||||
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Amount </th>
|
||||
<td mat-cell *matCellDef="let trans"><span fxLayoutAlign="end center"> {{trans.amount | number}} </span></td>
|
||||
</ng-container>
|
||||
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true;"></tr>
|
||||
<tr mat-row *matRowDef="let row; columns: displayedColumns;" (click)="onTransactionClick(row, $event)"></tr>
|
||||
</table>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,31 @@
|
||||
.mat-column-tx_hash, .mat-column-block_hash {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.mat-expansion-panel-header {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mat-accordion .mat-expansion-panel {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.ml-minus-24px {
|
||||
margin-left: -24px;
|
||||
}
|
||||
|
||||
table {
|
||||
width:100%;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
height: 78vh;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 414px) {
|
||||
.table-container {
|
||||
height: 68vh;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ListTransactionsComponent } from './list-transactions.component';
|
||||
|
||||
describe('ListTransactionsComponent', () => {
|
||||
let component: ListTransactionsComponent;
|
||||
let fixture: ComponentFixture<ListTransactionsComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ ListTransactionsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ListTransactionsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,110 @@
|
||||
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { formatDate } from '@angular/common';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { MatTableDataSource, MatSort } from '@angular/material';
|
||||
import { Transaction } from '../../../shared/models/lndModels';
|
||||
import { LoggerService } from '../../../shared/services/logger.service';
|
||||
|
||||
import { RTLEffects } from '../../../shared/store/rtl.effects';
|
||||
import * as RTLActions from '../../../shared/store/rtl.actions';
|
||||
import * as fromRTLReducer from '../../../shared/store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-list-transactions',
|
||||
templateUrl: './list-transactions.component.html',
|
||||
styleUrls: ['./list-transactions.component.scss']
|
||||
})
|
||||
export class ListTransactionsComponent implements OnInit, OnDestroy {
|
||||
@ViewChild(MatSort) sort: MatSort;
|
||||
public displayedColumns = [];
|
||||
public listTransactions: any;
|
||||
public flgLoading: Array<Boolean | 'error'> = [true];
|
||||
private unsub: Array<Subject<void>> = [new Subject(), new Subject()];
|
||||
|
||||
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.State>, private rtlEffects: RTLEffects) {
|
||||
switch (true) {
|
||||
case (window.innerWidth <= 415):
|
||||
this.displayedColumns = ['dest_addresses', 'total_fees', 'amount'];
|
||||
break;
|
||||
case (window.innerWidth > 415 && window.innerWidth <= 730):
|
||||
this.displayedColumns = ['dest_addresses', 'time_stamp', 'total_fees', 'amount'];
|
||||
break;
|
||||
case (window.innerWidth > 730 && window.innerWidth <= 1024):
|
||||
this.displayedColumns = ['dest_addresses', 'time_stamp', 'num_confirmations', 'total_fees', 'tx_hash', 'amount'];
|
||||
break;
|
||||
case (window.innerWidth > 1024 && window.innerWidth <= 1280):
|
||||
this.displayedColumns = ['dest_addresses', 'time_stamp', 'num_confirmations', 'total_fees', 'tx_hash', 'amount'];
|
||||
break;
|
||||
default:
|
||||
this.displayedColumns = ['dest_addresses', 'time_stamp', 'num_confirmations', 'total_fees', 'block_hash', 'block_height', 'tx_hash', 'amount'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.store.dispatch(new RTLActions.FetchTransactions());
|
||||
this.store.select('rtlRoot')
|
||||
.pipe(takeUntil(this.unsub[0]))
|
||||
.subscribe((rtlStore: fromRTLReducer.State) => {
|
||||
rtlStore.effectErrors.forEach(effectsErr => {
|
||||
if (effectsErr.action === 'FetchTransactions') {
|
||||
this.flgLoading[0] = 'error';
|
||||
}
|
||||
});
|
||||
if (undefined !== rtlStore.transactions && rtlStore.transactions.length > 0) {
|
||||
this.loadTransactionsTable(rtlStore.transactions);
|
||||
}
|
||||
if (this.flgLoading[0] !== 'error') {
|
||||
this.flgLoading[0] = (undefined !== rtlStore.transactions) ? false : true;
|
||||
}
|
||||
this.logger.info(rtlStore);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
applyFilter(selFilter: string) {
|
||||
this.listTransactions.filter = selFilter;
|
||||
}
|
||||
|
||||
onTransactionClick(selRow: Transaction, event: any) {
|
||||
const flgExpansionClicked = event.target.className.includes('mat-expansion-panel-header') || event.target.className.includes('mat-expansion-indicator');
|
||||
if (flgExpansionClicked) {
|
||||
return;
|
||||
}
|
||||
const selTransaction = this.listTransactions.data.filter(transaction => {
|
||||
return transaction.tx_hash === selRow.tx_hash;
|
||||
})[0];
|
||||
const reorderedTransactions = JSON.parse(JSON.stringify(selTransaction, [
|
||||
'dest_addresses', 'time_stamp_str', 'num_confirmations', 'total_fees', 'block_hash', 'block_height', 'tx_hash', 'amount'
|
||||
] , 2));
|
||||
this.store.dispatch(new RTLActions.OpenAlert({ width: '75%', data: {
|
||||
type: 'INFO',
|
||||
message: JSON.stringify(reorderedTransactions)
|
||||
}}));
|
||||
}
|
||||
|
||||
loadTransactionsTable(transactions) {
|
||||
this.listTransactions = new MatTableDataSource<Transaction>([...transactions]);
|
||||
this.listTransactions.sort = this.sort;
|
||||
this.listTransactions.data.forEach(transaction => {
|
||||
if (undefined !== transaction.time_stamp_str) {
|
||||
transaction.time_stamp_str = (transaction.time_stamp_str === '') ? '' : formatDate(transaction.time_stamp_str, 'MMM/dd/yy HH:mm:ss', 'en-US');
|
||||
}
|
||||
});
|
||||
this.logger.info(this.listTransactions);
|
||||
}
|
||||
|
||||
resetData() {
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unsub.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,163 @@
|
||||
<div fxLayout="column" fxLayoutAlign="center center" class="test-banner mx-1">
|
||||
<h5>Don't be #reckless. #craefulgang #craefulgang #craefulgang.</h5>
|
||||
</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="settings?.satsToBTC; else smallerUnit1">{{blockchainBalance?.btc_total_balance | number}} {{information?.currency_unit}}</h3>
|
||||
<ng-template #smallerUnit1><h3>{{blockchainBalance?.total_balance | 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="settings?.satsToBTC; else smallerUnit2">{{blockchainBalance?.btc_confirmed_balance | number}} {{information?.currency_unit}}</h3>
|
||||
<ng-template #smallerUnit2><h3>{{blockchainBalance?.confirmed_balance | 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="settings?.satsToBTC; else smallerUnit3">{{blockchainBalance?.btc_unconfirmed_balance | number}} {{information?.currency_unit}}</h3>
|
||||
<ng-template #smallerUnit3><h3>{{blockchainBalance?.unconfirmed_balance | 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>
|
||||
<mat-card-header>
|
||||
<mat-card-subtitle>
|
||||
<h2>Receive Funds</h2>
|
||||
</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content class="top-minus-25px">
|
||||
<div fxLayout="column" fxLayout.gt-sm="row wrap">
|
||||
<div fxFlex="26" 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="15" fxLayoutAlign="start end">
|
||||
<button fxFlex="99" 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="7" fxLayoutAlign="start end">
|
||||
<button fxFlex="99" fxLayoutAlign="center center" mat-raised-button color="accent" tabindex="3" (click)="resetReceiveData()" class="top-minus-15px">Clear</button>
|
||||
</div>
|
||||
<div fxFlex="34" fxLayoutAlign="start end">
|
||||
<mat-form-field fxFlex="100">
|
||||
<input matInput [value]="newAddress" readonly>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div fxFlex="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>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
<div fxFlex="100" class="padding-gap">
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-subtitle>
|
||||
<h2>Send Funds</h2>
|
||||
</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<div fxLayout="column" fxLayout.gt-sm="row wrap">
|
||||
<div fxFlex="62" fxLayoutAlign="start end">
|
||||
<mat-form-field fxFlex="99">
|
||||
<input matInput [(ngModel)]="transaction.address" placeholder="{{information?.currency_unit}} Address" tabindex="4" name="address" #address="ngModel">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div fxFleax="38" fxLayoutAlign="start center">
|
||||
<mat-radio-group fxFlex="100" fxLayoutAlign="space-between center" (change)="onOptionChange($event)" [(ngModel)]="flgCustomAmount">
|
||||
<mat-radio-button fxFlex="35" value="0">Sweep All</mat-radio-button>
|
||||
<mat-radio-button fxFlex="60" value="1">
|
||||
<mat-form-field fxFlex="70"><input matInput [(ngModel)]="transaction.amount" (click)="onCustomClicked()" placeholder="Amount ({{information?.smaller_currency_unit}})" name="amount" type="number" step="100" min="0" tabindex="5" #amount="ngModel"></mat-form-field>
|
||||
</mat-radio-button>
|
||||
</mat-radio-group>
|
||||
</div>
|
||||
</div>
|
||||
<div fxLayout="column" fxLayout.gt-sm="row wrap">
|
||||
<div fxFlex="30" fxLayoutAlign="start start">
|
||||
<mat-form-field fxFlex="99">
|
||||
<mat-select [(value)]="selTransType">
|
||||
<mat-option *ngFor="let transType of transTypes" [value]="transType.id">
|
||||
{{transType.name}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div fxFlex="30" fxLayoutAlign="start start">
|
||||
<mat-form-field fxFlex="99" *ngIf="selTransType=='1'">
|
||||
<input matInput [(ngModel)]="transaction.blocks" placeholder="Target Confirmation Blocks" type="number" name="blocks" step="1" min="0" required tabindex="6" #blocks="ngModel">
|
||||
</mat-form-field>
|
||||
<mat-form-field fxFlex="99" *ngIf="selTransType=='2'">
|
||||
<input matInput [(ngModel)]="transaction.fees" placeholder="Fee ({{information?.smaller_currency_unit}}/Byte)" type="number" name="fees" step="1" min="0" required tabindex="7" #fees="ngModel">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div fxFlex="40" fxLayoutAlign="space-between start">
|
||||
<button fxFlex="48" fxLayoutAlign="center center" mat-raised-button color="primary" [disabled]="invalidValues" type="submit" tabindex="8" (click)="onSendFunds()">
|
||||
<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>
|
||||
<button fxFlex="48" fxLayoutAlign="center center" mat-raised-button color="accent" tabindex="9" type="reset" (click)="resetData()">Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #withoutData><h3>Sats</h3></ng-template>
|
@ -0,0 +1,175 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil, take } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { Settings } from '../../../shared/models/RTLconfig';
|
||||
import { GetInfo, Balance, ChannelsTransaction, AddressType } from '../../../shared/models/lndModels';
|
||||
import { Authentication } from '../../../shared/models/RTLconfig';
|
||||
import { LoggerService } from '../../../shared/services/logger.service';
|
||||
|
||||
import { RTLEffects } from '../../../shared/store/rtl.effects';
|
||||
import * as RTLActions from '../../../shared/store/rtl.actions';
|
||||
import * as fromRTLReducer from '../../../shared/store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-send-receive-trans',
|
||||
templateUrl: './send-receive-trans.component.html',
|
||||
styleUrls: ['./send-receive-trans.component.scss']
|
||||
})
|
||||
export class SendReceiveTransComponent implements OnInit, OnDestroy {
|
||||
public settings: Settings;
|
||||
public addressTypes = [];
|
||||
public flgLoadingWallet: Boolean | 'error' = true;
|
||||
public selectedAddress: AddressType = {};
|
||||
public blockchainBalance: Balance = {};
|
||||
public information: GetInfo = {};
|
||||
public authSettings: Authentication = {};
|
||||
public newAddress = '';
|
||||
public transaction: ChannelsTransaction = {};
|
||||
public transTypes = [{id: '1', name: 'Target Confirmation Blocks'}, {id: '2', name: 'Fee'}];
|
||||
public selTransType = '1';
|
||||
public flgCustomAmount = '1';
|
||||
private unsub: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
|
||||
|
||||
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.State>, private rtlEffects: RTLEffects) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.store.select('rtlRoot')
|
||||
.pipe(takeUntil(this.unsub[0]))
|
||||
.subscribe((rtlStore: fromRTLReducer.State) => {
|
||||
rtlStore.effectErrors.forEach(effectsErr => {
|
||||
if (effectsErr.action === 'FetchBalance/blockchain') {
|
||||
this.flgLoadingWallet = 'error';
|
||||
}
|
||||
});
|
||||
this.settings = rtlStore.settings;
|
||||
this.information = rtlStore.information;
|
||||
this.addressTypes = rtlStore.addressTypes;
|
||||
this.authSettings = rtlStore.authSettings;
|
||||
|
||||
this.blockchainBalance = rtlStore.blockchainBalance;
|
||||
if (undefined === this.blockchainBalance.total_balance) {
|
||||
this.blockchainBalance.total_balance = '0';
|
||||
}
|
||||
if (undefined === this.blockchainBalance.confirmed_balance) {
|
||||
this.blockchainBalance.confirmed_balance = '0';
|
||||
}
|
||||
if (undefined === this.blockchainBalance.unconfirmed_balance) {
|
||||
this.blockchainBalance.unconfirmed_balance = '0';
|
||||
}
|
||||
if (this.flgLoadingWallet !== 'error') {
|
||||
this.flgLoadingWallet = false;
|
||||
}
|
||||
|
||||
this.logger.info(rtlStore);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
onGenerateAddress() {
|
||||
this.store.dispatch(new RTLActions.OpenSpinner('Getting New Address...'));
|
||||
this.store.dispatch(new RTLActions.GetNewAddress(this.selectedAddress));
|
||||
this.rtlEffects.setNewAddress
|
||||
.pipe(takeUntil(this.unsub[1]))
|
||||
.subscribe(newAddress => {
|
||||
this.newAddress = newAddress;
|
||||
});
|
||||
}
|
||||
|
||||
onSendFunds() {
|
||||
const confirmationMsg = {
|
||||
'BTC Address': this.transaction.address,
|
||||
};
|
||||
if (!+this.flgCustomAmount) {
|
||||
confirmationMsg['Sweep All'] = 'True';
|
||||
this.transaction.sendAll = true;
|
||||
} else {
|
||||
confirmationMsg['Amount (' + this.information.smaller_currency_unit + ')'] = this.transaction.amount;
|
||||
this.transaction.sendAll = false;
|
||||
}
|
||||
if (this.selTransType === '1') {
|
||||
delete this.transaction.fees;
|
||||
confirmationMsg['Target Confirmation Blocks'] = this.transaction.blocks;
|
||||
} else {
|
||||
delete this.transaction.blocks;
|
||||
confirmationMsg['Fee (' + this.information.smaller_currency_unit + '/Byte)'] = this.transaction.fees;
|
||||
}
|
||||
this.store.dispatch(new RTLActions.OpenConfirmation({ width: '70%', data:
|
||||
{type: 'CONFIRM', message: JSON.stringify(confirmationMsg), noBtnText: 'Cancel', yesBtnText: 'Send'}
|
||||
}));
|
||||
|
||||
this.rtlEffects.closeConfirm
|
||||
.pipe(takeUntil(this.unsub[2]))
|
||||
.subscribe(confirmRes => {
|
||||
if (confirmRes) {
|
||||
if (this.transaction.sendAll && !+this.authSettings.rtlSSO) {
|
||||
this.store.dispatch(new RTLActions.OpenConfirmation({ width: '70%', data:
|
||||
{type: 'CONFIRM', titleMessage: 'Enter Login Password', noBtnText: 'Cancel', yesBtnText: 'Authorize', flgShowInput: true, getInputs: [
|
||||
{placeholder: 'Enter Login Password', inputType: 'password', inputValue: ''}
|
||||
]}
|
||||
}));
|
||||
this.rtlEffects.closeConfirm
|
||||
.pipe(takeUntil(this.unsub[3]))
|
||||
.subscribe(pwdConfirmRes => {
|
||||
if (pwdConfirmRes) {
|
||||
const pwd = pwdConfirmRes[0].inputValue;
|
||||
this.store.dispatch(new RTLActions.IsAuthorized(window.btoa(pwd)));
|
||||
this.rtlEffects.isAuthorizedRes
|
||||
.pipe(take(1))
|
||||
.subscribe(authRes => {
|
||||
if (authRes !== 'ERROR') {
|
||||
this.dispatchToSendFunds();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.dispatchToSendFunds();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
dispatchToSendFunds() {
|
||||
this.store.dispatch(new RTLActions.OpenSpinner('Sending Funds...'));
|
||||
this.store.dispatch(new RTLActions.SetChannelTransaction(this.transaction));
|
||||
this.transaction = {address: '', amount: 0, blocks: 0, fees: 0};
|
||||
}
|
||||
|
||||
get invalidValues(): boolean {
|
||||
return (undefined === this.transaction.address || this.transaction.address === '')
|
||||
|| (+this.flgCustomAmount && (undefined === this.transaction.amount || this.transaction.amount <= 0))
|
||||
|| (this.selTransType === '1' && (undefined === this.transaction.blocks || this.transaction.blocks <= 0))
|
||||
|| (this.selTransType === '2' && (undefined === this.transaction.fees || this.transaction.fees <= 0));
|
||||
}
|
||||
|
||||
onCustomClicked() {
|
||||
this.flgCustomAmount = '1';
|
||||
}
|
||||
|
||||
onOptionChange(event) {
|
||||
if (!+this.flgCustomAmount) {
|
||||
delete this.transaction.amount;
|
||||
}
|
||||
}
|
||||
|
||||
resetData() {
|
||||
this.transaction.address = '';
|
||||
this.transaction.amount = 0;
|
||||
this.transaction.blocks = 0;
|
||||
this.transaction.fees = 0;
|
||||
}
|
||||
|
||||
resetReceiveData() {
|
||||
this.selectedAddress = {};
|
||||
this.newAddress = '';
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unsub.forEach(completeSub => {
|
||||
completeSub.next();
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
}
|
21
src/app/pages/unlock-lnd/unlock-lnd.component.html
Normal file
@ -0,0 +1,21 @@
|
||||
<div fxLayout="column">
|
||||
<div class="padding-gap">
|
||||
<mat-card>
|
||||
<mat-card-header>
|
||||
<mat-card-subtitle>
|
||||
<h2>Unlock LND Wallet</h2>
|
||||
</mat-card-subtitle>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<form fxLayout="row" fxFlex="70" fxLayoutAlign="space-between">
|
||||
<mat-form-field fxFlex="65" fxLayoutAlign="start">
|
||||
<input matInput type="password" placeholder="Password" name="walletPassword" [(ngModel)]="walletPassword" tabindex="1" required>
|
||||
<mat-hint>Enter LND Wallet Password</mat-hint>
|
||||
</mat-form-field>
|
||||
<button mat-raised-button fxFlex="15" color="primary" [disabled]="walletPassword == ''" (click)="onOperateWallet('unlock')" tabindex="2">Unlock Wallet</button>
|
||||
<button fxFlex="15" fxLayoutAlign="center center" mat-raised-button color="accent" tabindex="3" type="reset" (click)="resetData()">Clear</button>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
</div>
|
||||
</div>
|
0
src/app/pages/unlock-lnd/unlock-lnd.component.scss
Normal file
41
src/app/pages/unlock-lnd/unlock-lnd.component.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
|
||||
import { LoggerService } from '../../shared/services/logger.service';
|
||||
|
||||
import * as RTLActions from '../../shared/store/rtl.actions';
|
||||
import * as fromRTLReducer from '../../shared/store/rtl.reducers';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-unlock-lnd',
|
||||
templateUrl: './unlock-lnd.component.html',
|
||||
styleUrls: ['./unlock-lnd.component.scss']
|
||||
})
|
||||
export class UnlockLNDComponent implements OnInit, OnDestroy {
|
||||
walletPassword = '';
|
||||
private unsub = new Subject();
|
||||
|
||||
constructor(private store: Store<fromRTLReducer.State>) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.walletPassword = '';
|
||||
}
|
||||
|
||||
onOperateWallet(operation: string) {
|
||||
this.store.dispatch(new RTLActions.OpenSpinner('Unlocking...'));
|
||||
this.store.dispatch(new RTLActions.OperateWallet({operation: operation, pwd: this.walletPassword}));
|
||||
}
|
||||
|
||||
resetData() {
|
||||
this.walletPassword = '';
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unsub.next();
|
||||
this.unsub.complete();
|
||||
}
|
||||
|
||||
}
|
10
src/app/shared/animation/row-animation.ts
Normal file
@ -0,0 +1,10 @@
|
||||
import { trigger, state, animate, transition, style } from '@angular/animations';
|
||||
|
||||
export const newlyAddedRowAnimation = [
|
||||
trigger('newlyAddedRowAnimation', [
|
||||
state('notAdded, void', style({ transform: 'translateX(0)' })),
|
||||
state('added', style({ transform: 'translateX(1.5)', border: '1px solid' })),
|
||||
transition('added <=> notAdded', animate('2000ms ease-out')),
|
||||
transition('added <=> void', animate('2000ms ease-out'))
|
||||
])
|
||||
];
|
@ -0,0 +1,35 @@
|
||||
<div fxLayout="row">
|
||||
<div class="w-100">
|
||||
<mat-card-header [ngClass]="msgTypeBackground" fxLayoutAlign="end">
|
||||
<h2 fxFlex="91">{{data.type}}</h2>
|
||||
<mat-icon fxFlex="7" fxLayoutAlign="end" type="button" (click)="onClose()" class="cursor-pointer">close</mat-icon>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<div class="pb-2 p-2 wrap-text new-line">
|
||||
<p *ngIf="data.titleMessage" fxLayoutAlign="start center" class="title-message pb-1 pl-1">{{data.titleMessage | titlecase}}</p>
|
||||
<div *ngIf="messageObj?.length>0">
|
||||
<div *ngFor="let obj of messageObj" fxLayout="row" fxLayoutAlign="center flex-start">
|
||||
<div fxFlex="20">{{obj[0] | titlecase}}</div>
|
||||
<div fxFlex="2">:</div>
|
||||
<div [fxFlex]="showCopyOption(obj[0]) ? 60 : 75" class="pr-2">
|
||||
<div *ngIf="isNumber(obj[1], obj[0]);else notNumberTemplate">
|
||||
<span>{{obj[1] | number:'1.0-3'}}</span>
|
||||
</div>
|
||||
<ng-template #notNumberTemplate>
|
||||
<span>{{obj[1]}}</span>
|
||||
</ng-template>
|
||||
<mat-icon *ngIf="showCopyOption(obj[0])" class="icon-small cursor-pointer pl-1 top-5px" rtlClipboard [payload]="obj[1]" (copied)="copiedText($event)">file_copy</mat-icon><span *ngIf="showCopyOption(obj[0])" [hidden]="!flgCopied">Copied</span>
|
||||
</div>
|
||||
<div *ngIf="showCopyOption(obj[0])" fxFlex="15" fxLayoutAlign="center center">
|
||||
<qrcode [qrdata]="obj[1]" [size]="120" [level]="'L'" [allowEmptyString]="true" [ngStyle]="{'visibility': (obj[1] === '') ? 'hidden' : 'visible'}" class="mt-minus-40px qr-border"></qrcode>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<mat-divider class="pb-1"></mat-divider>
|
||||
<div fxLayoutAlign="center">
|
||||
<button mat-raised-button [color]="msgTypeForeground" fxFlex="30" class="mb-1" type="button" [mat-dialog-close]="false" default>OK</button>
|
||||
</div>
|
||||
</mat-card-content>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,40 @@
|
||||
.p-2 {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.pb-1 {
|
||||
padding-bottom: 0.3rem;
|
||||
}
|
||||
|
||||
.pb-2 {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.mb-1 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.wrap-text {
|
||||
-ms-word-break: break-all;
|
||||
word-break: break-all;
|
||||
word-break: break-word;
|
||||
-webkit-hyphens: auto;
|
||||
-moz-hyphens: auto;
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
.mat-icon[type="button"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.new-line {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.title-message {
|
||||
font-size: 110%;
|
||||
}
|
||||
|
||||
.mt-minus-40px {
|
||||
margin-top:-40px;
|
||||
}
|
@ -0,0 +1,106 @@
|
||||
import { Component, OnInit, Inject } from '@angular/core';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
|
||||
|
||||
import { LoggerService } from '../../../shared/services/logger.service';
|
||||
import { AlertData } from '../../../shared/models/alertData';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-alert-message',
|
||||
templateUrl: './alert-message.component.html',
|
||||
styleUrls: ['./alert-message.component.scss']
|
||||
})
|
||||
export class AlertMessageComponent implements OnInit {
|
||||
public msgTypeBackground = 'bg-primary p-1';
|
||||
public msgTypeForeground = 'primary';
|
||||
public messageObj = [];
|
||||
public flgCopied = false;
|
||||
|
||||
constructor(public dialogRef: MatDialogRef<AlertMessageComponent>, @Inject(MAT_DIALOG_DATA) public data: AlertData, private logger: LoggerService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.setStyleOnAlertType();
|
||||
this.convertJSONData();
|
||||
}
|
||||
|
||||
setStyleOnAlertType() {
|
||||
// INFO/WARN/ERROR/SUCCESS/CONFIRM
|
||||
if (this.data.type === 'WARN') {
|
||||
this.msgTypeBackground = 'bg-accent p-1';
|
||||
this.msgTypeForeground = 'accent';
|
||||
}
|
||||
if (this.data.type === 'ERROR') {
|
||||
this.msgTypeBackground = 'bg-warn p-1';
|
||||
this.msgTypeForeground = 'warn';
|
||||
if (undefined === this.data.message && undefined === this.data.titleMessage && this.messageObj.length <= 0 ) {
|
||||
this.data.titleMessage = 'Please Check Server Connection';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
convertJSONData() {
|
||||
this.data.message = (undefined === this.data.message) ? '' : this.data.message.replace(/{/g, '').replace(/"/g, '').replace(/}/g, '').replace(/\n/g, '');
|
||||
// Start: For Payment Path
|
||||
const arrayStartIdx = this.data.message.search('\\[');
|
||||
const arrayEndIdx = this.data.message.search('\\]');
|
||||
if (arrayStartIdx > -1 && arrayEndIdx > -1) {
|
||||
this.data.message = this.data.message.substring(0, arrayStartIdx).concat(
|
||||
this.data.message.substring(arrayStartIdx + 1, arrayEndIdx).replace(/,/g, '\n'),
|
||||
this.data.message.substring(arrayEndIdx + 1));
|
||||
}
|
||||
// End: For Payment Path
|
||||
this.messageObj = (this.data.message === '') ? [] : this.data.message.split(',');
|
||||
this.messageObj.forEach((obj, idx) => {
|
||||
this.messageObj[idx] = obj.split(':');
|
||||
this.messageObj[idx][0] = this.messageObj[idx][0].replace('_str', '');
|
||||
this.messageObj[idx][0] = this.messageObj[idx][0].replace(/_/g, ' '); // To replace Backend Data's '_'
|
||||
// Start: To Merge Time Value Again with ':', example Payment Creation Time
|
||||
if (this.messageObj[idx].length > 2) {
|
||||
this.messageObj[idx].forEach((dataValue, j) => {
|
||||
if (j === 0 || j === 1) {
|
||||
return;
|
||||
} else {
|
||||
this.messageObj[idx][1] = this.messageObj[idx][1] + ':' + this.messageObj[idx][j];
|
||||
}
|
||||
});
|
||||
}
|
||||
// End: To Merge Time Value Again with ':', example Payment Creation Time
|
||||
});
|
||||
}
|
||||
|
||||
showCopyOption(key): boolean {
|
||||
let flgFoundKey = false;
|
||||
const showCopyOnKeys = ['payment request'];
|
||||
showCopyOnKeys.filter((arrKey) => {
|
||||
if (arrKey === key) {
|
||||
flgFoundKey = true;
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return flgFoundKey;
|
||||
}
|
||||
|
||||
copiedText(payload) {
|
||||
this.flgCopied = true;
|
||||
setTimeout(() => {this.flgCopied = false; }, 5000);
|
||||
this.logger.info('Copied Text: ' + payload);
|
||||
}
|
||||
|
||||
isNumber(value, key): boolean {
|
||||
let flgFoundKey = false;
|
||||
const notNumberKeys = ['chan id', 'creation date', 'chan id out', 'chan id in'];
|
||||
notNumberKeys.filter((arrKey) => {
|
||||
if (arrKey === key) {
|
||||
flgFoundKey = true;
|
||||
}
|
||||
});
|
||||
if (!flgFoundKey) {
|
||||
return new RegExp(/^[0-9]+$/).test(value);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
onClose() {
|
||||
this.dialogRef.close(false);
|
||||
}
|
||||
}
|