Release 0.10.0 (#571)

Channel backup download file bug fix #536
Added macaroon authentication for Loop (#543)
Adding Label for Loop In & Loop Out #538
Fee Report & Routing Enhancements (#555)
Payments report #559
Transactions Report #357
Material table sorting bug fix #556
CL & ECL ng Routing #551 & Hocon Read Fix #560 (#561)
CLT & ECL Reports (#562)
UI Bug fixes for tables group sort, pagination, dialog and spinner close
Increased request body size #544 (#564)
App lock after 5 attempts #542 & DatePicker default adapter #532 (#566)
Upgade Angular 11 (#568)
Loop amount validation #569
Loop https document updates
This commit is contained in:
ShahanaFarooqui 2020-12-20 18:36:04 -05:00 committed by GitHub
parent 6e036d8382
commit 5a38585b71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
378 changed files with 11718 additions and 5765 deletions

17
.browserslistrc Normal file
View File

@ -0,0 +1,17 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# For the full list of supported browsers by the Angular framework, please see:
# https://angular.io/guide/browser-support
# You can see what browsers were selected by your queries by running:
# npx browserslist
last 1 Chrome version
last 1 Firefox version
last 2 Edge major versions
last 2 Safari major versions
last 2 iOS major versions
Firefox ESR
not IE 11 # Angular supports IE 11 only as an opt-in. To opt-in, remove the 'not' prefix on this line.

View File

@ -1,4 +1,4 @@
# Editor configuration, see http://editorconfig.org # Editor configuration, see https://editorconfig.org
root = true root = true
[*] [*]
@ -8,6 +8,9 @@ indent_size = 2
insert_final_newline = true insert_final_newline = true
trim_trailing_whitespace = true trim_trailing_whitespace = true
[*.ts]
quote_type = single
[*.md] [*.md]
max_line_length = off max_line_length = off
trim_trailing_whitespace = false trim_trailing_whitespace = false

View File

@ -94,6 +94,7 @@ Example RTL-Config.json:
"lnImplementation": "LND", "lnImplementation": "LND",
"Authentication": { "Authentication": {
"macaroonPath": "<Complete path of the folder containing admin.macaroon for the node # 1>", "macaroonPath": "<Complete path of the folder containing admin.macaroon for the node # 1>",
"swapMacaroonPath": "<Complete path of the folder containing loop.macaroon for the node # 1>",
"configPath": "<Optional:Path of the .conf if present locally or empty>", "configPath": "<Optional:Path of the .conf if present locally or empty>",
"lnApiPassword": "<Optional:Can be used to provide password in ECL implementation>" "lnApiPassword": "<Optional:Can be used to provide password in ECL implementation>"
}, },
@ -106,7 +107,7 @@ Example RTL-Config.json:
"enableLogging": true, "enableLogging": true,
"fiatConversion": false, "fiatConversion": false,
"lnServerUrl": "<url for LND REST APIs for node #1 e.g. https://192.168.0.1:8080>", "lnServerUrl": "<url for LND REST APIs for node #1 e.g. https://192.168.0.1:8080>",
"swapServerUrl": "<url for swap server REST APIs for the node. e.g. http://localhost:8081>" "swapServerUrl": "<url for swap server REST APIs for the node. e.g. https://localhost:8081>"
} }
} }
] ]

View File

@ -4,24 +4,35 @@
"newProjectRoot": "projects", "newProjectRoot": "projects",
"projects": { "projects": {
"RTLApp": { "RTLApp": {
"root": "",
"sourceRoot": "src",
"projectType": "application", "projectType": "application",
"prefix": "rtl",
"schematics": { "schematics": {
"@schematics/angular:component": { "@schematics/angular:component": {
"style": "scss" "style": "scss"
},
"@schematics/angular:application": {
"strict": true
} }
}, },
"root": "",
"sourceRoot": "src",
"prefix": "rtl",
"architect": { "architect": {
"build": { "build": {
"builder": "@angular-devkit/build-angular:browser", "builder": "@angular-devkit/build-angular:browser",
"options": { "options": {
"baseHref": "/rtl/",
"outputPath": "angular", "outputPath": "angular",
"index": "src/index.html", "index": "src/index.html",
"main": "src/main.ts", "main": "src/main.ts",
"polyfills": "src/polyfills.ts", "polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.app.json", "tsConfig": "tsconfig.app.json",
"aot": true,
"allowedCommonJsDependencies": [
"hammerjs",
"sha256",
"qrcode",
"otplib"
],
"assets": [ "assets": [
"src/assets" "src/assets"
], ],
@ -41,19 +52,22 @@
"optimization": true, "optimization": true,
"outputHashing": "all", "outputHashing": "all",
"sourceMap": false, "sourceMap": false,
"extractCss": true,
"namedChunks": false, "namedChunks": false,
"aot": true,
"extractLicenses": true, "extractLicenses": true,
"vendorChunk": false, "vendorChunk": false,
"buildOptimizer": true, "buildOptimizer": true,
"budgets": [ "budgets": [
{ {
"type": "initial", "type": "initial",
"maximumWarning": "4mb", "maximumWarning": "5mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "1mb",
"maximumError": "5mb" "maximumError": "5mb"
} }
] ]
} }
} }
}, },
@ -79,35 +93,30 @@
"options": { "options": {
"main": "src/test.ts", "main": "src/test.ts",
"polyfills": "src/polyfills.ts", "polyfills": "src/polyfills.ts",
"tsConfig": "src/tsconfig.spec.json", "tsConfig": "tsconfig.spec.json",
"karmaConfig": "src/karma.conf.js", "karmaConfig": "karma.conf.js",
"assets": [
"src/assets"
],
"styles": [ "styles": [
"src/app/shared/theme/styles/styles.scss" "src/app/shared/theme/styles/styles.scss"
], ],
"scripts": [], "scripts": []
"assets": [
"src/assets"
]
} }
}, },
"lint": { "lint": {
"builder": "@angular-devkit/build-angular:tslint", "builder": "@angular-devkit/build-angular:tslint",
"options": { "options": {
"tsConfig": [ "tsConfig": [
"src/tsconfig.app.json", "tsconfig.app.json",
"src/tsconfig.spec.json" "tsconfig.spec.json",
"e2e/tsconfig.json"
], ],
"exclude": [ "exclude": [
"**/node_modules/**" "**/node_modules/**"
] ]
} }
} },
}
},
"RTLApp-e2e": {
"root": "e2e/",
"projectType": "application",
"architect": {
"e2e": { "e2e": {
"builder": "@angular-devkit/build-angular:protractor", "builder": "@angular-devkit/build-angular:protractor",
"options": { "options": {
@ -119,18 +128,9 @@
"devServerTarget": "RTLApp:serve:production" "devServerTarget": "RTLApp:serve:production"
} }
} }
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": "e2e/tsconfig.e2e.json",
"exclude": [
"**/node_modules/**"
]
}
} }
} }
} }
}, },
"defaultProject": "RTLApp" "defaultProject": "RTLApp"
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -12,8 +12,8 @@
<link rel="mask-icon" href="assets/images/favicon-light/safari-pinned-tab.svg" color="#5bbad5"> <link rel="mask-icon" href="assets/images/favicon-light/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#da532c"> <meta name="msapplication-TileColor" content="#da532c">
<meta name="theme-color" content="#ffffff"> <meta name="theme-color" content="#ffffff">
<link rel="stylesheet" href="styles.bbe7b5fc38151212c389.css"></head> <link rel="stylesheet" href="styles.ee9d4139f693ecb219aa.css"></head>
<body> <body>
<rtl-app></rtl-app> <rtl-app></rtl-app>
<script src="runtime.907d5114b7f55f73c01d.js" defer></script><script src="polyfills-es5.2ac0d98b22574ae745b1.js" nomodule defer></script><script src="polyfills.5ae721a6ae5ab597a53d.js" defer></script><script src="main.6b2c66a7af14112dcd7f.js" defer></script></body> <script src="runtime.4560fb3bcc56015af5b6.js" defer></script><script src="polyfills.ea991b800cfaf577eb9d.js" defer></script><script src="main.a9c3fed74cd736cbe7ae.js" defer></script></body>
</html> </html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
!function(e){function r(r){for(var n,a,i=r[0],c=r[1],f=r[2],p=0,s=[];p<i.length;p++)a=i[p],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(l&&l(r);s.length;)s.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++)0!==o[t[i]]&&(n=!1);n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={0:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise(function(r,n){t=o[e]=[r,n]});r.push(t[2]=n);var u,i=document.createElement("script");i.charset="utf-8",i.timeout=120,a.nc&&i.setAttribute("nonce",a.nc),i.src=function(e){return a.p+""+({}[e]||e)+"."+{1:"d3191dc862bb96ad8a12",5:"3593974ed7a18cef9807",6:"a0ce69bc4fc32a59e836",7:"2a4fb50bff6654155ed9"}[e]+".js"}(e);var c=new Error;u=function(r){i.onerror=i.onload=null,clearTimeout(f);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var f=setTimeout(function(){u({type:"timeout",target:i})},12e4);i.onerror=i.onload=u,document.head.appendChild(i)}return Promise.all(r)},a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,(function(r){return e[r]}).bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="",a.oe=function(e){throw console.error(e),e};var i=window.webpackJsonp=window.webpackJsonp||[],c=i.push.bind(i);i.push=r,i=i.slice();for(var f=0;f<i.length;f++)r(i[f]);var l=c;t()}([]);

View File

@ -1 +0,0 @@
!function(e){function r(r){for(var n,a,i=r[0],c=r[1],f=r[2],p=0,s=[];p<i.length;p++)a=i[p],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(l&&l(r);s.length;)s.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++)0!==o[t[i]]&&(n=!1);n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={0:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,i=document.createElement("script");i.charset="utf-8",i.timeout=120,a.nc&&i.setAttribute("nonce",a.nc),i.src=function(e){return a.p+""+({}[e]||e)+"."+{1:"60ddf8569c2860edb59c",6:"d5f36502e36ce33775a8",7:"a355238233f02a06308f",8:"29ee25c8adea70ea041a"}[e]+".js"}(e);var c=new Error;u=function(r){i.onerror=i.onload=null,clearTimeout(f);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var f=setTimeout((function(){u({type:"timeout",target:i})}),12e4);i.onerror=i.onload=u,document.head.appendChild(i)}return Promise.all(r)},a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,(function(r){return e[r]}).bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="",a.oe=function(e){throw console.error(e),e};var i=window.webpackJsonp=window.webpackJsonp||[],c=i.push.bind(i);i.push=r,i=i.slice();for(var f=0;f<i.length;f++)r(i[f]);var l=c;t()}([]);

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

5
app.js
View File

@ -50,9 +50,10 @@ const invoicesECLRoutes = require("./routes/eclair/invoices");
const paymentsECLRoutes = require("./routes/eclair/payments"); const paymentsECLRoutes = require("./routes/eclair/payments");
const networkECLRoutes = require("./routes/eclair/network"); const networkECLRoutes = require("./routes/eclair/network");
app.set('trust proxy', true);
app.use(cookieParser(common.secret_key)); app.use(cookieParser(common.secret_key));
app.use(bodyParser.json()); app.use(bodyParser.json({limit: '25mb'}));
app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.urlencoded({extended: false, limit: '25mb'}));
app.use(baseHref, express.static(path.join(__dirname, "angular"))); app.use(baseHref, express.static(path.join(__dirname, "angular")));
// CORS fix, Only required for developement due to separate backend and frontend servers // CORS fix, Only required for developement due to separate backend and frontend servers

View File

@ -2,6 +2,7 @@ var fs = require('fs');
var crypto = require('crypto'); var crypto = require('crypto');
var path = require('path'); var path = require('path');
var common = {}; var common = {};
const MONTH_NAMES = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'];
common.rtl_conf_file_path = ''; common.rtl_conf_file_path = '';
common.rtl_pass = ''; common.rtl_pass = '';
@ -17,11 +18,22 @@ common.nodes = [];
common.selectedNode = {}; common.selectedNode = {};
common.getSwapServerOptions = () => { common.getSwapServerOptions = () => {
return { let swapOptions = {
url: common.selectedNode.swap_server_url, url: common.selectedNode.swap_server_url,
rejectUnauthorized: false, rejectUnauthorized: false,
json: true json: true,
headers: {
'Grpc-Metadata-macaroon': ''
}
}; };
if (common.selectedNode.swap_macaroon_path) {
try {
swapOptions.headers = {'Grpc-Metadata-macaroon': fs.readFileSync(path.join(common.selectedNode.swap_macaroon_path, 'loop.macaroon')).toString('hex')};
} catch(err) {
console.error('Loop macaroon Error: ' + JSON.stringify(err));
}
}
return swapOptions;
}; };
common.getSelLNServerUrl = () => { common.getSelLNServerUrl = () => {
@ -128,7 +140,27 @@ common.convertToBTC = (num) => {
}; };
common.convertTimestampToDate = (num) => { common.convertTimestampToDate = (num) => {
return new Date(+num * 1000).toUTCString().substring(5, 22).replace(' ', '/').replace(' ', '/').toUpperCase(); let myDate = new Date(+num * 1000);
let days = myDate.getDate().toString();
days = +days < 10 ? '0' + days : days;
let hours = myDate.getHours().toString();
hours = +hours < 10 ? '0' + hours : hours;
let minutes = myDate.getMinutes().toString();
minutes = +minutes < 10 ? '0' + minutes : minutes;
return days + "/" + MONTH_NAMES[myDate.getMonth()] + "/" + myDate.getFullYear() + " " + hours + ":" + minutes;
};
common.convertTimestampToLocalDate = (num) => {
let myDate = new Date(+num * 1000);
let days = myDate.getDate().toString();
days = +days < 10 ? '0' + days : days;
let hours = myDate.getHours().toString();
hours = +hours < 10 ? '0' + hours : hours;
let minutes = myDate.getMinutes().toString();
minutes = +minutes < 10 ? '0' + minutes : minutes;
let seconds = myDate.getSeconds().toString();
seconds = +seconds < 10 ? '0' + seconds : seconds;
return days + "/" + (MONTH_NAMES[myDate.getMonth()]) + "/" + myDate.getFullYear() + " " + hours + ":" + minutes + ":" + seconds;
}; };
common.sortAscByKey = (array, key) => { common.sortAscByKey = (array, key) => {
@ -170,4 +202,13 @@ common.newestOnTop = (array, key, value) => {
return array; return array;
} }
common.getRequestIP = (req) => {
return (typeof req.headers['x-forwarded-for'] === 'string' && req.headers['x-forwarded-for'].split(',').shift())
|| req.ip
|| req.connection.remoteAddress
|| req.socket.remoteAddress
|| (req.connection.socket ? req.connection.socket.remoteAddress : null);
}
module.exports = common; module.exports = common;

View File

@ -65,7 +65,6 @@ connect.setDefaultConfig = () => {
channelBackupPath: channelBackupPath, channelBackupPath: channelBackupPath,
enableLogging: false, enableLogging: false,
lnServerUrl: "https://localhost:8080", lnServerUrl: "https://localhost:8080",
swapServerUrl: "http://localhost:8081",
fiatConversion: false fiatConversion: false
} }
} }
@ -188,10 +187,13 @@ connect.validateNodeConfig = (config) => {
} }
if(process.env.SWAP_SERVER_URL && process.env.SWAP_SERVER_URL.trim() !== '') { if(process.env.SWAP_SERVER_URL && process.env.SWAP_SERVER_URL.trim() !== '') {
common.nodes[idx].swap_server_url = process.env.SWAP_SERVER_URL.endsWith('/v1') ? process.env.SWAP_SERVER_URL.slice(0, -3) : process.env.SWAP_SERVER_URL; common.nodes[idx].swap_server_url = process.env.SWAP_SERVER_URL.endsWith('/v1') ? process.env.SWAP_SERVER_URL.slice(0, -3) : process.env.SWAP_SERVER_URL;
common.nodes[idx].swap_macaroon_path = process.env.SWAP_MACAROON_PATH;
} else if(node.Settings.swapServerUrl && node.Settings.swapServerUrl.trim() !== '') { } else if(node.Settings.swapServerUrl && node.Settings.swapServerUrl.trim() !== '') {
common.nodes[idx].swap_server_url = node.Settings.swapServerUrl.endsWith('/v1') ? node.Settings.swapServerUrl.slice(0, -3) : node.Settings.swapServerUrl; common.nodes[idx].swap_server_url = node.Settings.swapServerUrl.endsWith('/v1') ? node.Settings.swapServerUrl.slice(0, -3) : node.Settings.swapServerUrl;
common.nodes[idx].swap_macaroon_path = node.Authentication.swapMacaroonPath ? node.Authentication.swapMacaroonPath : '';
} else { } else {
common.nodes[idx].swap_server_url = ''; common.nodes[idx].swap_server_url = '';
common.nodes[idx].swap_macaroon_path = '';
} }
common.nodes[idx].bitcoind_config_path = process.env.BITCOIND_CONFIG_PATH ? process.env.BITCOIND_CONFIG_PATH : (node.Settings.bitcoindConfigPath) ? node.Settings.bitcoindConfigPath : ''; common.nodes[idx].bitcoind_config_path = process.env.BITCOIND_CONFIG_PATH ? process.env.BITCOIND_CONFIG_PATH : (node.Settings.bitcoindConfigPath) ? node.Settings.bitcoindConfigPath : '';
common.nodes[idx].enable_logging = (node.Settings.enableLogging) ? !!node.Settings.enableLogging : false; common.nodes[idx].enable_logging = (node.Settings.enableLogging) ? !!node.Settings.enableLogging : false;

View File

@ -1,4 +1,5 @@
var ini = require('ini'); var ini = require('ini');
var parseHocon = require('hocon-parser');
var fs = require('fs'); var fs = require('fs');
var logger = require('./logger'); var logger = require('./logger');
var common = require('../common'); var common = require('../common');
@ -146,22 +147,19 @@ exports.updateDefaultNode = (req, res, next) => {
exports.getConfig = (req, res, next) => { exports.getConfig = (req, res, next) => {
let confFile = ''; let confFile = '';
let JSONFormat = false; let fileFormat = 'INI';
switch (req.params.nodeType) { switch (req.params.nodeType) {
case 'ln': case 'ln':
JSONFormat = false;
confFile = common.selectedNode.config_path; confFile = common.selectedNode.config_path;
break; break;
case 'bitcoind': case 'bitcoind':
JSONFormat = false;
confFile = common.selectedNode.bitcoind_config_path; confFile = common.selectedNode.bitcoind_config_path;
break; break;
case 'rtl': case 'rtl':
JSONFormat = true; fileFormat = 'JSON';
confFile = common.rtl_conf_file_path + common.path_separator + 'RTL-Config.json'; confFile = common.rtl_conf_file_path + common.path_separator + 'RTL-Config.json';
break; break;
default: default:
JSONFormat = false;
confFile = ''; confFile = '';
break; break;
} }
@ -174,7 +172,39 @@ exports.getConfig = (req, res, next) => {
error: err error: err
}); });
} else { } else {
const jsonConfig = (JSONFormat) ? JSON.parse(data) : ini.parse(data); let jsonConfig = {};
if (fileFormat === 'JSON') {
jsonConfig = JSON.parse(data);
} else {
jsonConfig = ini.parse(data);
console.warn();
console.warn(jsonConfig);
switch (common.selectedNode.ln_implementation) {
case 'ECL':
if (jsonConfig['eclair.api.password']) {
if (jsonConfig['eclair.api.password']) {
jsonConfig['eclair.api.password'] = jsonConfig['eclair.api.password'].replace(/./g, '*');
}
if (jsonConfig['eclair.bitcoind.rpcpassword']) {
jsonConfig['eclair.bitcoind.rpcpassword'] = jsonConfig['eclair.bitcoind.rpcpassword'].replace(/./g, '*');
}
} else {
fileFormat = 'HOCON';
jsonConfig = parseHocon(data);
if (jsonConfig.eclair && jsonConfig.eclair.api && jsonConfig.eclair.api.password) {
jsonConfig.eclair.api.password = jsonConfig.eclair.api.password.replace(/./g, '*');
}
if (jsonConfig.eclair && jsonConfig.eclair.bitcoind && jsonConfig.eclair.bitcoind.rpcpassword) {
jsonConfig.eclair.bitcoind.rpcpassword = jsonConfig.eclair.bitcoind.rpcpassword.replace(/./g, '*');
}
}
break;
default:
fileFormat = 'INI';
break;
}
}
if (jsonConfig.Bitcoind && jsonConfig.Bitcoind['bitcoind.rpcpass']) { if (jsonConfig.Bitcoind && jsonConfig.Bitcoind['bitcoind.rpcpass']) {
jsonConfig.Bitcoind['bitcoind.rpcpass'] = jsonConfig.Bitcoind['bitcoind.rpcpass'].replace(/./g, '*'); jsonConfig.Bitcoind['bitcoind.rpcpass'] = jsonConfig.Bitcoind['bitcoind.rpcpass'].replace(/./g, '*');
} }
@ -184,17 +214,11 @@ exports.getConfig = (req, res, next) => {
if (jsonConfig['rpcpassword']) { if (jsonConfig['rpcpassword']) {
jsonConfig['rpcpassword'] = jsonConfig['rpcpassword'].replace(/./g, '*'); jsonConfig['rpcpassword'] = jsonConfig['rpcpassword'].replace(/./g, '*');
} }
if (jsonConfig['eclair.api.password']) {
jsonConfig['eclair.api.password'] = jsonConfig['eclair.api.password'].replace(/./g, '*');
}
if (jsonConfig['eclair.bitcoind.rpcpassword']) {
jsonConfig['eclair.bitcoind.rpcpassword'] = jsonConfig['eclair.bitcoind.rpcpassword'].replace(/./g, '*');
}
if (jsonConfig.multiPass) { if (jsonConfig.multiPass) {
jsonConfig.multiPass = jsonConfig.multiPass.replace(/./g, '*'); jsonConfig.multiPass = jsonConfig.multiPass.replace(/./g, '*');
} }
const responseJSON = (JSONFormat) ? jsonConfig : ini.stringify(jsonConfig); const responseJSON = (fileFormat === 'JSON') ? jsonConfig : ini.stringify(jsonConfig);
res.status(200).json({format: (JSONFormat) ? 'JSON' : 'INI', data: responseJSON}); res.status(200).json({format: fileFormat, data: responseJSON});
} }
}); });
}; };

View File

@ -1,9 +1,29 @@
var common = require('../common'); var common = require('../common');
var connect = require('../connect'); var connect = require('../connect');
const jwt = require("jsonwebtoken");
var crypto = require('crypto');
var logger = require('./logger'); var logger = require('./logger');
const jwt = require("jsonwebtoken");
const otplib = require("otplib"); const otplib = require("otplib");
var crypto = require('crypto');
var ONE_MINUTE = 60000;
var LOCKING_PERIOD = 30 * ONE_MINUTE; // HALF AN HOUR
var ALLOWED_LOGIN_ATTEMPTS = 5;
var failedLoginAttempts = {};
setInterval(() => {
for (var ip in failedLoginAttempts) {
if (new Date().getTime() > (failedLoginAttempts[ip].lastTried + LOCKING_PERIOD)) {
delete failedLoginAttempts[ip];
}
}
}, LOCKING_PERIOD);
getFailedInfo = (reqIP, currentTime) => {
let failed = failedLoginAttempts[reqIP] ? failedLoginAttempts[reqIP] : failedLoginAttempts[reqIP] = {count: 0, lastTried: currentTime};
if (currentTime > (failed.lastTried + LOCKING_PERIOD)) {
failed = failedLoginAttempts[reqIP] = {count: 0, lastTried: currentTime};
}
return failed;
}
exports.authenticateUser = (req, res, next) => { exports.authenticateUser = (req, res, next) => {
if(+common.rtl_sso) { if(+common.rtl_sso) {
@ -24,20 +44,33 @@ exports.authenticateUser = (req, res, next) => {
}); });
} }
} else { } else {
const currentTime = new Date().getTime();
const reqIP = common.getRequestIP(req);
let failed = getFailedInfo(reqIP, currentTime);
const password = req.body.authenticationValue; const password = req.body.authenticationValue;
if (common.rtl_pass === password) { if (common.rtl_pass === password && failed.count < ALLOWED_LOGIN_ATTEMPTS) {
var rpcUser = 'NODE_USER'; delete failedLoginAttempts[reqIP];
let rpcUser = 'NODE_USER';
const token = jwt.sign( const token = jwt.sign(
{ user: rpcUser, configPath: common.nodes[0].config_path, macaroonPath: common.nodes[0].macaroon_path }, { user: rpcUser, configPath: common.nodes[0].config_path, macaroonPath: common.nodes[0].macaroon_path },
common.secret_key common.secret_key
); );
res.status(200).json({ token: token }); res.status(200).json({ token: token });
} else { } else {
logger.error({fileName: 'Authenticate', lineNum: 36, msg: 'Invalid Password!'}); logger.error({fileName: 'Authenticate', lineNum: 61, msg: 'Invalid Password! Failed IP ' + reqIP});
res.status(401).json({ failed.count = common.rtl_pass !== password ? (failed.count + 1) : failed.count;
message: "Authentication Failed!", failed.lastTried = common.rtl_pass !== password ? currentTime : failed.lastTried;
error: "Invalid Password!" if (failed.count >= ALLOWED_LOGIN_ATTEMPTS && (currentTime <= (failed.lastTried + LOCKING_PERIOD))) {
}); res.status(401).json({
message: "Multiple Failed Login Attempts!",
error: "Application locked for " + (LOCKING_PERIOD/ONE_MINUTE) + " minutes due to multiple failed login attempts! Try again after " + common.convertTimestampToLocalDate((failed.lastTried + LOCKING_PERIOD)/1000) + "!"
});
} else {
res.status(401).json({
message: "Authentication Failed!",
error: "Invalid password! Application will be locked after " + (ALLOWED_LOGIN_ATTEMPTS - failed.count) + " more unsuccessful attempts!"
});
}
} }
} }
}; };

View File

@ -6,8 +6,9 @@ var options = {};
exports.getFees = (req, res, next) => { exports.getFees = (req, res, next) => {
options = common.getOptions(); options = common.getOptions();
options.url = common.getSelLNServerUrl() + '/audit'; options.url = common.getSelLNServerUrl() + '/audit';
tillToday = Math.floor(Date.now() / 1000); let today = new Date(Date.now());
fromLastMonth = tillToday - (86400 * 30); let tillToday = Math.floor(today / 1000);
let fromLastMonth = Math.round((new Date(today.getFullYear(), today.getMonth() - 1, today.getDate() + 1, 0, 0, 0).getTime()) / 1000);
options.form = { options.form = {
from: fromLastMonth, from: fromLastMonth,
to: tillToday to: tillToday

View File

@ -1,5 +1,4 @@
var request = require('request-promise'); var request = require('request-promise');
var upperCase = require('upper-case');
var common = require('../../common'); var common = require('../../common');
var logger = require('../logger'); var logger = require('../logger');
var options = {}; var options = {};
@ -11,7 +10,7 @@ exports.getBalance = (req, res, next) => {
request(options).then((body) => { request(options).then((body) => {
logger.info({fileName: 'Balance', msg: 'Request params: ' + JSON.stringify(req.params) + 'Request Query: ' + JSON.stringify(req.query) + ' Balance Received: ' + JSON.stringify(body)}); logger.info({fileName: 'Balance', msg: 'Request params: ' + JSON.stringify(req.params) + 'Request Query: ' + JSON.stringify(req.query) + ' Balance Received: ' + JSON.stringify(body)});
if (body) { if (body) {
if (upperCase(req.params.source) === 'BLOCKCHAIN') { if (req.params.source === 'blockchain') {
if (!body.total_balance) { body.total_balance = 0; } if (!body.total_balance) { body.total_balance = 0; }
if (!body.confirmed_balance) { body.confirmed_balance = 0; } if (!body.confirmed_balance) { body.confirmed_balance = 0; }
if (!body.unconfirmed_balance) { body.unconfirmed_balance = 0; } if (!body.unconfirmed_balance) { body.unconfirmed_balance = 0; }
@ -19,7 +18,7 @@ exports.getBalance = (req, res, next) => {
body.btc_confirmed_balance = common.convertToBTC(body.confirmed_balance); body.btc_confirmed_balance = common.convertToBTC(body.confirmed_balance);
body.btc_unconfirmed_balance = common.convertToBTC(body.unconfirmed_balance); body.btc_unconfirmed_balance = common.convertToBTC(body.unconfirmed_balance);
} }
if (upperCase(req.params.source) === 'CHANNELS') { if (req.params.source === 'channels') {
if (!body.balance) { body.balance = 0; } if (!body.balance) { body.balance = 0; }
if (!body.pending_open_balance) { body.pending_open_balance = 0; } if (!body.pending_open_balance) { body.pending_open_balance = 0; }
body.btc_balance = common.convertToBTC(body.balance); body.btc_balance = common.convertToBTC(body.balance);

View File

@ -34,8 +34,9 @@ exports.getFees = (req, res, next) => {
} else { } else {
body.btc_month_fee_sum = common.convertToBTC(body.month_fee_sum); body.btc_month_fee_sum = common.convertToBTC(body.month_fee_sum);
} }
let current_time = Math.round((new Date().getTime()) / 1000); let today = new Date(Date.now());
let month_start_time = current_time - 2629743; let current_time = Math.round((today.getTime()) / 1000);
let month_start_time = Math.round((new Date(today.getFullYear(), today.getMonth() - 1, today.getDate() + 1, 0, 0, 0).getTime()) / 1000);
let week_start_time = current_time - 604800; let week_start_time = current_time - 604800;
let day_start_time = current_time - 86400; let day_start_time = current_time - 86400;
swtch.getAllForwardingEvents(month_start_time, current_time, 0, (history) => { swtch.getAllForwardingEvents(month_start_time, current_time, 0, (history) => {

View File

@ -15,7 +15,8 @@ exports.loopOut = (req, res, next) => {
max_prepay_routing_fee: req.body.prepayRoutingFee, max_prepay_routing_fee: req.body.prepayRoutingFee,
max_prepay_amt: req.body.prepayAmt, max_prepay_amt: req.body.prepayAmt,
max_swap_fee: req.body.swapFee, max_swap_fee: req.body.swapFee,
swap_publication_deadline: req.body.swapPublicationDeadline swap_publication_deadline: req.body.swapPublicationDeadline,
label: 'RTL'
}; };
if (req.body.chanId !== '') { options.body['loop_out_channel'] = req.body.chanId; } if (req.body.chanId !== '') { options.body['loop_out_channel'] = req.body.chanId; }
if (req.body.destAddress !== '') { options.body['dest'] = req.body.destAddress; } if (req.body.destAddress !== '') { options.body['dest'] = req.body.destAddress; }
@ -157,8 +158,10 @@ exports.loopIn = (req, res, next) => {
options.body = { options.body = {
amt: req.body.amount, amt: req.body.amount,
max_swap_fee: req.body.swapFee, max_swap_fee: req.body.swapFee,
max_miner_fee: req.body.minerFee max_miner_fee: req.body.minerFee,
label: 'RTL'
}; };
logger.info({fileName: 'Loop', msg: 'Loop In Body: ' + JSON.stringify(options.body)});
request.post(options).then(function (body) { request.post(options).then(function (body) {
logger.info({fileName: 'Loop', msg: 'Loop In: ' + JSON.stringify(body)}); logger.info({fileName: 'Loop', msg: 'Loop In: ' + JSON.stringify(body)});
if(!body || body.error) { if(!body || body.error) {

View File

@ -85,6 +85,7 @@ services:
CONFIG_PATH: '' CONFIG_PATH: ''
LN_IMPLEMENTATION: LND LN_IMPLEMENTATION: LND
SWAP_SERVER_URL: http://${LIGHTNING_HOST}:${LIGHTNING_LOOP_PORT} SWAP_SERVER_URL: http://${LIGHTNING_HOST}:${LIGHTNING_LOOP_PORT}
SWAP_MACAROON_PATH: /shared
RTL_SSO: 0 RTL_SSO: 0
RTL_COOKIE_PATH: '' RTL_COOKIE_PATH: ''
LOGOUT_REDIRECT_LINK: '' LOGOUT_REDIRECT_LINK: ''

View File

@ -20,6 +20,7 @@ parameters have `default` values for initial setup and can be updated after RTL
"lnImplementation": "<LNP implementation, Allowed values LND/CLT/ECL. Default 'LND', Required>", "lnImplementation": "<LNP implementation, Allowed values LND/CLT/ECL. Default 'LND', Required>",
"Authentication": { "Authentication": {
"macaroonPath": "<Path for the folder containing 'admin.macaroon' (LND)/'access.macaroon' (CLT) file, Required for LND & CLT>", "macaroonPath": "<Path for the folder containing 'admin.macaroon' (LND)/'access.macaroon' (CLT) file, Required for LND & CLT>",
"swapMacaroonPath": "<Path for the folder containing 'loop.macaroon' (LND), Required for LND Loop>",
"configPath": "<Full path of the lnd.conf/c-lightning config/eclair.conf file including the file name, if present locally, Optional, only mandatory for ECL if the lnApiPassword is missing>", "configPath": "<Full path of the lnd.conf/c-lightning config/eclair.conf file including the file name, if present locally, Optional, only mandatory for ECL if the lnApiPassword is missing>",
"lnApiPassword": "<Password to be used for ECL API authentication. Mandatory only for ECL if the configPath is missing>" "lnApiPassword": "<Password to be used for ECL API authentication. Mandatory only for ECL if the configPath is missing>"
}, },
@ -33,7 +34,7 @@ parameters have `default` values for initial setup and can be updated after RTL
"fiatConversion": <parameter to turn fiat conversion off/on. Allowed values - true, false, default false, Required>, "fiatConversion": <parameter to turn fiat conversion off/on. Allowed values - true, false, default false, Required>,
"currencyUnit": "<Optional: Fiat current Unit for currency conversion, default 'USD' If fiatConversion is true, Required if fiatConversion is true>", "currencyUnit": "<Optional: Fiat current Unit for currency conversion, default 'USD' If fiatConversion is true, Required if fiatConversion is true>",
"lnServerUrl": "<Service url for LND/CLightning REST APIs for the node, e.g. https://192.168.0.1:8080 OR https://192.168.0.1:3001 OR http://192.168.0.1:8080. Default 'https://localhost:8080', Required", "lnServerUrl": "<Service url for LND/CLightning REST APIs for the node, e.g. https://192.168.0.1:8080 OR https://192.168.0.1:3001 OR http://192.168.0.1:8080. Default 'https://localhost:8080', Required",
"swapServerUrl": "<Service url for swap server REST APIs for the node, e.g. http://localhost:8081, Optional>", "swapServerUrl": "<Service url for swap server REST APIs for the node, e.g. https://localhost:8081, Optional>",
} }
} }
] ]

View File

@ -23,10 +23,10 @@ This step is only required to configure the nodes, which will be remotely connec
5. `SSO` section can be used for single-sign-on from applications like BTCPayserver. If using RTL as a stand-alone app to connect with the nodes, keep the `rtlSSO=0` and ignore the rest of `SSO` section. 5. `SSO` section can be used for single-sign-on from applications like BTCPayserver. If using RTL as a stand-alone app to connect with the nodes, keep the `rtlSSO=0` and ignore the rest of `SSO` section.
6. `nodes` section is a json array, with each element of the array representing the specific parameters for the LND node to connect with. `index` must be a number and start with 1. This number must be unique for each node in the array. For each element, two items need to be configured for each node on the network (`macaroonPath` and `lnServerUrl`). 6. `nodes` section is a json array, with each element of the array representing the specific parameters for the LND node to connect with. `index` must be a number and start with 1. This number must be unique for each node in the array. For each element, two items need to be configured for each node on the network (`macaroonPath` and `lnServerUrl`).
7. `macaroonPath` should be set to the local path of the folder containing `admin.macaroon` file for each node. Each node must have a different folder for the `admin.macaroon` on the RTL server. 7. `macaroonPath` should be set to the local path of the folder containing `admin.macaroon` file for each node. Each node must have a different folder for the `admin.macaroon` on the RTL server.
8. `lnServerUrl` must be set to the service url for LND/C Lightining REST APIs for each node, with the unique ip address of the node hosting lnd/clightning e.g. https://192.168.0.1:8080 OR https://192.168.0.1:3001. In this case the ip address of the node hosting lnd/clightning is '192.168.0.1' 8. `swapMacaroonPath` should be set to the local path of the folder containing `loop.macaroon` file for loop. 9. `lnServerUrl` must be set to the service url for LND/C Lightining REST APIs for each node, with the unique ip address of the node hosting lnd/clightning e.g. https://192.168.0.1:8080 OR https://192.168.0.1:3001. In this case the ip address of the node hosting lnd/clightning is '192.168.0.1'
9. `swapServerUrl` must be set to the swap service url. e.g. http://localhost:8081. 10. `swapServerUrl` must be set to the swap service url. e.g. https://localhost:8081.
10. `configPath` and `bitcoindConfigPath` are optional parameters which can be set only if the RTL is running locally on the same node. Else it can be set to "" or removed from the conf file all together. 11. `configPath` and `bitcoindConfigPath` are optional parameters which can be set only if the RTL is running locally on the same node. Else it can be set to "" or removed from the conf file all together.
11. `lnApiPassword` is mandatory in the ln implementation is ECL and configPath is missing. It is used to provide password for API authentication. It will be ignored in other ln implementations. 12. `lnApiPassword` is mandatory in the ln implementation is ECL and configPath is missing. It is used to provide password for API authentication. It will be ignored in other ln implementations.
#### 3. Restart RTL #### 3. Restart RTL

View File

@ -27,6 +27,7 @@ If your running RTL and LND on different devices on your local LAN, certain conf
"lnImplementation": "LND", "lnImplementation": "LND",
"Authentication": { "Authentication": {
"macaroonPath": "<Path of the folder containing 'admin.macaroon' on the device running RTL>", "macaroonPath": "<Path of the folder containing 'admin.macaroon' on the device running RTL>",
"swapMacaroonPath": "<Path of the folder containing 'loop.macaroon' on the device running RTL>",
"configPath": "<Optional:Path of the lnd.conf if present locally or empty>" "configPath": "<Optional:Path of the lnd.conf if present locally or empty>"
}, },
"Settings": { "Settings": {
@ -38,7 +39,7 @@ If your running RTL and LND on different devices on your local LAN, certain conf
"enableLogging": false, "enableLogging": false,
"fiatConversion": false, "fiatConversion": false,
"lnServerUrl": "<https://<ip-address-of-device-running-lnd>:8080; e.g. https://192.168.0.1:8080>", "lnServerUrl": "<https://<ip-address-of-device-running-lnd>:8080; e.g. https://192.168.0.1:8080>",
"swapServerUrl": "<http://<localhost>:8081>", "swapServerUrl": "<https://<localhost>:8081>",
} }
} }
] ]

View File

@ -1,17 +1,22 @@
// @ts-check
// Protractor configuration file, see link for more information // Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts // https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter'); const { SpecReporter, StacktraceOption } = require('jasmine-spec-reporter');
/**
* @type { import("protractor").Config }
*/
exports.config = { exports.config = {
allScriptsTimeout: 11000, allScriptsTimeout: 11000,
specs: [ specs: [
'./src/**/*.e2e-spec.ts' './src/**/*.e2e-spec.ts'
], ],
capabilities: { capabilities: {
'browserName': 'chrome' browserName: 'chrome'
}, },
directConnect: true, directConnect: true,
SELENIUM_PROMISE_MANAGER: false,
baseUrl: 'http://localhost:4200/', baseUrl: 'http://localhost:4200/',
framework: 'jasmine', framework: 'jasmine',
jasmineNodeOpts: { jasmineNodeOpts: {
@ -21,8 +26,12 @@ exports.config = {
}, },
onPrepare() { onPrepare() {
require('ts-node').register({ require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.e2e.json') project: require('path').join(__dirname, './tsconfig.json')
}); });
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); jasmine.getEnv().addReporter(new SpecReporter({
spec: {
displayStacktrace: StacktraceOption.PRETTY
}
}));
} }
}; };

View File

@ -1,4 +1,5 @@
import { AppPage } from './app.po'; import { AppPage } from './app.po';
import { browser, logging } from 'protractor';
describe('workspace-project App', () => { describe('workspace-project App', () => {
let page: AppPage; let page: AppPage;
@ -7,8 +8,16 @@ describe('workspace-project App', () => {
page = new AppPage(); page = new AppPage();
}); });
it('should display welcome message', () => { it('should display welcome message', async () => {
page.navigateTo(); await page.navigateTo();
expect(page.getParagraphText()).toEqual('Welcome to RTLApp!'); expect(await page.getTitleText()).toEqual('RTL app is running!');
});
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry));
}); });
}); });

View File

@ -1,11 +1,11 @@
import { browser, by, element } from 'protractor'; import { browser, by, element } from 'protractor';
export class AppPage { export class AppPage {
navigateTo() { async navigateTo(): Promise<unknown> {
return browser.get('/'); return browser.get(browser.baseUrl);
} }
getParagraphText() { async getTitleText(): Promise<string> {
return element(by.css('app-root h1')).getText(); return element(by.css('app-root .content span')).getText();
} }
} }

View File

@ -1,13 +1,13 @@
/* To learn more about this file see: https://angular.io/config/tsconfig. */
{ {
"extends": "../tsconfig.json", "extends": "../tsconfig.json",
"compilerOptions": { "compilerOptions": {
"outDir": "../out-tsc/app", "outDir": "../out-tsc/e2e",
"module": "commonjs", "module": "commonjs",
"target": "es5", "target": "es2018",
"types": [ "types": [
"jasmine", "jasmine",
"jasminewd2",
"node" "node"
] ]
} }
} }

View File

@ -9,16 +9,28 @@ module.exports = function (config) {
require('karma-jasmine'), require('karma-jasmine'),
require('karma-chrome-launcher'), require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'), require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'), require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma') require('@angular-devkit/build-angular/plugins/karma')
], ],
client: { client: {
jasmine: {
// you can add configuration options for Jasmine here
// the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html
// for example, you can disable the random execution with `random: false`
// or set a specific seed with `seed: 4321`
},
clearContext: false // leave Jasmine Spec Runner output visible in browser clearContext: false // leave Jasmine Spec Runner output visible in browser
}, },
coverageIstanbulReporter: { jasmineHtmlReporter: {
dir: require('path').join(__dirname, '../coverage'), suppressAll: true // removes the duplicated traces
reports: ['html', 'lcovonly'], },
fixWebpackSourcePaths: true coverageReporter: {
dir: require('path').join(__dirname, './coverage/RTL'),
subdir: '.',
reporters: [
{ type: 'html' },
{ type: 'text-summary' }
]
}, },
reporters: ['progress', 'kjhtml'], reporters: ['progress', 'kjhtml'],
port: 9876, port: 9876,
@ -26,6 +38,7 @@ module.exports = function (config) {
logLevel: config.LOG_INFO, logLevel: config.LOG_INFO,
autoWatch: true, autoWatch: true,
browsers: ['Chrome'], browsers: ['Chrome'],
singleRun: false singleRun: false,
restartOnFileChange: true
}); });
}; };

6382
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,12 @@
{ {
"name": "rtl", "name": "rtl",
"version": "0.9.3-beta", "version": "0.10.0-beta",
"license": "MIT", "license": "MIT",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "ng serve --base-href /rtl/ --open --aot", "start": "ng serve --open",
"prebuild": "node ./prebuild", "prebuild": "node ./prebuild",
"build": "ng analytics off && ng build --prod --base-href /rtl/ --aot", "build": "ng analytics off && ng build --prod",
"serve": "ng serve", "serve": "ng serve",
"server": "nodemon ./rtl.js", "server": "nodemon ./rtl.js",
"test": "ng test", "test": "ng test",
@ -16,63 +16,62 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "~9.1.9", "@angular/animations": "~11.0.4",
"@angular/cdk": "~9.2.4", "@angular/cdk": "^11.0.3",
"@angular/common": "~9.1.9", "@angular/common": "~11.0.4",
"@angular/compiler": "~9.1.9", "@angular/compiler": "~11.0.4",
"@angular/compiler-cli": "~9.1.9", "@angular/compiler-cli": "~11.0.4",
"@angular/core": "~9.1.9", "@angular/core": "~11.0.4",
"@angular/flex-layout": "^9.0.0-beta.31", "@angular/flex-layout": "^11.0.0-beta.33",
"@angular/forms": "~9.1.9", "@angular/forms": "~11.0.4",
"@angular/material": "^9.2.4", "@angular/material": "^11.0.3",
"@angular/platform-browser": "~9.1.9", "@angular/platform-browser": "~11.0.4",
"@angular/platform-browser-dynamic": "~9.1.9", "@angular/platform-browser-dynamic": "~11.0.4",
"@angular/router": "~9.1.9", "@angular/router": "~11.0.4",
"@fortawesome/angular-fontawesome": "^0.7.0", "@fortawesome/angular-fontawesome": "^0.8.1",
"@fortawesome/fontawesome-svg-core": "^1.2.29", "@fortawesome/fontawesome-svg-core": "^1.2.32",
"@fortawesome/free-regular-svg-icons": "^5.13.1", "@fortawesome/free-regular-svg-icons": "^5.15.1",
"@fortawesome/free-solid-svg-icons": "^5.12.1", "@fortawesome/free-solid-svg-icons": "^5.15.1",
"@ngrx/effects": "^9.2.0", "@ngrx/effects": "^10.1.0",
"@ngrx/store": "^9.2.0", "@ngrx/store": "^10.1.0",
"angular-user-idle": "^2.2.2", "@swimlane/ngx-charts": "^16.0.0",
"angularx-qrcode": "^10.0.3", "angular-user-idle": "^2.2.4",
"angularx-qrcode": "^10.0.11",
"atob": "^2.1.2", "atob": "^2.1.2",
"cookie-parser": "^1.4.4", "cookie-parser": "^1.4.5",
"express": "^4.17.1", "express": "^4.17.1",
"hammerjs": "^2.0.8", "hammerjs": "^2.0.8",
"hocon-parser": "1.0.1", "hocon-parser": "^1.0.1",
"ini": "1.3.5", "ini": "^2.0.0",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"material-design-icons": "^3.0.1", "material-design-icons": "^3.0.1",
"ngx-perfect-scrollbar": "^9.0.0", "ngx-perfect-scrollbar": "^10.0.1",
"otplib": "^11.0.1", "otplib": "^12.0.1",
"request": "^2.88.0", "request": "^2.88.2",
"request-promise": "^4.2.5", "request-promise": "^4.2.6",
"roboto-fontface": "^0.10.0", "roboto-fontface": "^0.10.0",
"rxjs": "~6.5.4", "rxjs": "~6.6.0",
"sha256": "^0.2.0", "sha256": "^0.2.0",
"tslib": "^1.10.0", "tslib": "^2.0.0",
"typescript": "~3.8.3", "typescript": "~4.0.2",
"upper-case": "^1.1.3",
"zone.js": "~0.10.2" "zone.js": "~0.10.2"
}, },
"devDependencies": { "devDependencies": {
"@angular-devkit/build-angular": "~0.901.7", "@angular-devkit/build-angular": "~0.1100.4",
"@angular/cli": "~9.1.7", "@angular/cli": "~11.0.4",
"@ngrx/store-devtools": "^9.2.0", "@ngrx/store-devtools": "^10.1.0",
"@types/jasmine": "~3.5.0", "@types/jasmine": "~3.6.0",
"@types/jasminewd2": "~2.0.3", "@types/node": "^12.19.9",
"@types/node": "^12.11.1", "codelyzer": "^6.0.0",
"codelyzer": "^5.1.2",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"jasmine-core": "~3.5.0", "jasmine-core": "~3.6.0",
"jasmine-spec-reporter": "~4.2.1", "jasmine-spec-reporter": "~5.0.0",
"karma": "~5.0.0", "karma": "~5.1.0",
"karma-chrome-launcher": "~3.1.0", "karma-chrome-launcher": "~3.1.0",
"karma-coverage-istanbul-reporter": "~2.1.0", "karma-coverage": "~2.0.3",
"karma-jasmine": "~3.0.1", "karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "^1.4.2", "karma-jasmine-html-reporter": "^1.5.0",
"nodemon": "^2.0.2", "nodemon": "^2.0.6",
"protractor": "~7.0.0", "protractor": "~7.0.0",
"ts-node": "~8.3.0", "ts-node": "~8.3.0",
"tslint": "~6.1.0" "tslint": "~6.1.0"

View File

@ -5,7 +5,7 @@ const versionFilePath = path.join(__dirname + '/src/environments/version.ts');
const versionStr = `export const VERSION = '${appVersion}';`; const versionStr = `export const VERSION = '${appVersion}';`;
fs.writeFile(versionFilePath, versionStr, { flat: 'w' }, function (err) { fs.writeFile(versionFilePath, versionStr, { flat: 'w' }, function (err) {
if (err) { if (err) {
return console.log(err); return console.error(err);
} }
console.log(`Updating application version ${appVersion}`); console.log(`Updating application version ${appVersion}`);
console.log(`${'Writing version module to '}${versionFilePath}\n`); console.log(`${'Writing version module to '}${versionFilePath}\n`);

View File

@ -13,17 +13,18 @@
"lnNode": "Node 1", "lnNode": "Node 1",
"lnImplementation": "LND", "lnImplementation": "LND",
"Authentication": { "Authentication": {
"macaroonPath": "C:\\Users\\shaha\\AppData\\Local\\Lnd\\data\\chain\\bitcoin\\mainnet", "macaroonPath": "C:\\Users\\xyz\\AppData\\Local\\Lnd\\data\\chain\\bitcoin\\mainnet",
"configPath": "C:\\Users\\shaha\\AppData\\Local\\Lnd\\lnd.conf" "configPath": "C:\\Users\\xyz\\AppData\\Local\\Lnd\\lnd.conf",
"swapMacaroonPath": "C:\\Users\\xyz\\AppData\\Local\\Loop\\mainnet"
}, },
"Settings": { "Settings": {
"userPersona": "MERCHANT", "userPersona": "MERCHANT",
"themeMode": "DAY", "themeMode": "DAY",
"themeColor": "PURPLE", "themeColor": "PURPLE",
"channelBackupPath": "C:\\Users\\shaha\\backup\\node-1", "channelBackupPath": "C:\\Users\\xyz\\backup\\node-1",
"enableLogging": false, "enableLogging": false,
"lnServerUrl": "https://localhost:8080", "lnServerUrl": "https://localhost:8080",
"swapServerUrl": "http://localhost:8081", "swapServerUrl": "https://localhost:8081",
"fiatConversion": false "fiatConversion": false
} }
} }

View File

@ -12,19 +12,19 @@
</button> </button>
</div> </div>
<div> <div>
<span *ngIf="xSmallScreen">{{information.alias ? 'RTL - ' + information.alias : 'RTL'}}</span> <span *ngIf="smallScreen">{{information.alias ? 'RTL - ' + information.alias : 'RTL'}}</span>
<span *ngIf="!xSmallScreen">{{information.alias ? 'Ride The Lightning - ' + information.alias : 'Ride The Lightning'}}</span> <span *ngIf="!smallScreen">{{information.alias ? 'Ride The Lightning - ' + information.alias : 'Ride The Lightning'}}</span>
</div> </div>
<div> <div>
<rtl-top-menu></rtl-top-menu> <rtl-top-menu></rtl-top-menu>
</div> </div>
</mat-toolbar> </mat-toolbar>
<mat-sidenav-container> <mat-sidenav-container>
<mat-sidenav perfectScrollbar [opened]="flgSideNavOpened" [mode]="(flgSidenavPinned && !smallScreen) ? 'side' : 'over'" #sideNavigation class="sidenav mat-elevation-z6"> <mat-sidenav [perfectScrollbar] [opened]="flgSideNavOpened" [mode]="(flgSidenavPinned && !smallScreen) ? 'side' : 'over'" #sideNavigation class="sidenav mat-elevation-z6">
<rtl-side-navigation (ChildNavClicked)="onNavigationClicked($event)" fxFlex="100"></rtl-side-navigation> <rtl-side-navigation (ChildNavClicked)="onNavigationClicked($event)" fxFlex="100"></rtl-side-navigation>
</mat-sidenav> </mat-sidenav>
<mat-sidenav-content perfectScrollbar #sideNavContent> <mat-sidenav-content [perfectScrollbar] #sideNavContent>
<div [ngClass]="{'mt-minus-1': smallScreen, 'inner-sidenav-content': true}"> <div [ngClass]="{'inner-sidenav-content': true}">
<router-outlet></router-outlet> <router-outlet></router-outlet>
</div> </div>
</mat-sidenav-content>> </mat-sidenav-content>>

View File

@ -46,29 +46,27 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
this.router.events.subscribe((evt) => { this.router.events.subscribe((evt) => {
if (!(evt instanceof NavigationEnd)) { return; } if (!(evt instanceof NavigationEnd)) { return; }
document.getElementsByTagName('mat-sidenav-content')[0].scrollTo(0, 0); document.getElementsByTagName('mat-sidenav-content')[0].scrollTo(0, 0);
}); });
this.breakpointObserver.observe([Breakpoints.XSmall, Breakpoints.TabletPortrait, Breakpoints.Small, Breakpoints.Medium]) this.breakpointObserver.observe([Breakpoints.XSmall, Breakpoints.TabletPortrait, Breakpoints.Small, Breakpoints.Medium, Breakpoints.Large, Breakpoints.XLarge])
.pipe(takeUntil(this.unSubs[5])) .pipe(takeUntil(this.unSubs[5]))
.subscribe((matches) => { .subscribe((matches) => {
if(matches.breakpoints[Breakpoints.XSmall]) { if(matches.breakpoints[Breakpoints.XSmall]) {
this.commonService.setScreenSize(ScreenSizeEnum.XS); this.commonService.setScreenSize(ScreenSizeEnum.XS);
this.xSmallScreen = true;
this.smallScreen = true; this.smallScreen = true;
} else if(matches.breakpoints[Breakpoints.TabletPortrait]) { } else if(matches.breakpoints[Breakpoints.TabletPortrait]) {
this.commonService.setScreenSize(ScreenSizeEnum.SM); this.commonService.setScreenSize(ScreenSizeEnum.SM);
this.xSmallScreen = false;
this.smallScreen = true; this.smallScreen = true;
} else if(matches.breakpoints[Breakpoints.Small] || matches.breakpoints[Breakpoints.Medium]) { } else if(matches.breakpoints[Breakpoints.Small] || matches.breakpoints[Breakpoints.Medium]) {
this.commonService.setScreenSize(ScreenSizeEnum.MD); this.commonService.setScreenSize(ScreenSizeEnum.MD);
this.xSmallScreen = false; this.smallScreen = false;
} else if(matches.breakpoints[Breakpoints.Large]) {
this.commonService.setScreenSize(ScreenSizeEnum.LG);
this.smallScreen = false; this.smallScreen = false;
} else { } else {
this.commonService.setScreenSize(ScreenSizeEnum.LG); this.commonService.setScreenSize(ScreenSizeEnum.XL);
this.xSmallScreen = false;
this.smallScreen = false; this.smallScreen = false;
} }
}); });
this.store.dispatch(new RTLActions.FetchRTLConfig()); this.store.dispatch(new RTLActions.FetchRTLConfig());
this.accessKey = this.readAccessKey(); this.accessKey = this.readAccessKey();
this.store.select('root') this.store.select('root')
@ -122,9 +120,11 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
ngAfterViewInit() { ngAfterViewInit() {
if (this.smallScreen) { if (this.smallScreen) {
this.sideNavigation.close(); this.sideNavigation.close();
this.commonService.setContainerSize(this.sideNavContent.elementRef.nativeElement.clientWidth, this.sideNavContent.elementRef.nativeElement.clientHeight);
} else { } else {
setTimeout(() => { setTimeout(() => {
this.renderer.setStyle(this.sideNavContent.elementRef.nativeElement, 'marginLeft', '22rem'); //$regular-sidenav-width this.renderer.setStyle(this.sideNavContent.elementRef.nativeElement, 'marginLeft', '22rem'); //$regular-sidenav-width
this.commonService.setContainerSize(this.sideNavContent.elementRef.nativeElement.clientWidth, this.sideNavContent.elementRef.nativeElement.clientHeight);
}, 100); }, 100);
} }
} }
@ -140,7 +140,7 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
} }
} }
copiedText(payload) { copiedText(payload: string) {
this.flgCopied = true; this.flgCopied = true;
setTimeout(() => {this.flgCopied = false; }, 5000); setTimeout(() => {this.flgCopied = false; }, 5000);
this.logger.info('Copied Text: ' + payload); this.logger.info('Copied Text: ' + payload);

View File

@ -7,22 +7,18 @@ import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects'; import { EffectsModule } from '@ngrx/effects';
import { StoreDevtoolsModule } from '@ngrx/store-devtools'; import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { MatDialogModule, MAT_DIALOG_DEFAULT_OPTIONS } from '@angular/material/dialog';
import { UserIdleModule } from 'angular-user-idle'; import { UserIdleModule } from 'angular-user-idle';
import { OverlayContainer } from '@angular/cdk/overlay';
import { routing } from './app.routing'; import { routing } from './app.routing';
import { SharedModule } from './shared/shared.module'; import { SharedModule } from './shared/shared.module';
import { ThemeOverlay } from './shared/theme/overlay-container/theme-overlay';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { environment } from '../environments/environment'; import { environment } from '../environments/environment';
import { SessionService } from './shared/services/session.service';
import { CommonService } from './shared/services/common.service';
import { LoopService } from './shared/services/loop.service';
import { DataService } from './shared/services/data.service';
import { LoggerService, ConsoleLoggerService } from './shared/services/logger.service';
import { AuthGuard } from './shared/services/auth.guard'; import { AuthGuard } from './shared/services/auth.guard';
import { AuthInterceptor } from './shared/services/auth.interceptor'; import { AuthInterceptor } from './shared/services/auth.interceptor';
import { SessionService } from './shared/services/session.service';
import { LoopService } from './shared/services/loop.service';
import { DataService } from './shared/services/data.service';
import { CommonService } from './shared/services/common.service';
import { RTLReducer } from './store/rtl.reducers'; import { RTLReducer } from './store/rtl.reducers';
import { RTLEffects } from './store/rtl.effects'; import { RTLEffects } from './store/rtl.effects';
@ -30,39 +26,6 @@ import { LNDEffects } from './lnd/store/lnd.effects';
import { CLEffects } from './clightning/store/cl.effects'; import { CLEffects } from './clightning/store/cl.effects';
import { ECLEffects } from './eclair/store/ecl.effects'; import { ECLEffects } from './eclair/store/ecl.effects';
import { LayoutModule } from '@angular/cdk/layout'; import { LayoutModule } from '@angular/cdk/layout';
import { CLOpenChannelComponent } from './clightning/peers-channels/channels/open-channel-modal/open-channel.component';
import { CLChannelInformationComponent } from './clightning/peers-channels/channels/channel-information-modal/channel-information.component';
import { CLInvoiceInformationComponent } from './clightning/transactions/invoice-information-modal/invoice-information.component';
import { CLConnectPeerComponent } from './clightning/peers-channels/connect-peer/connect-peer.component';
import { CLLightningSendPaymentsComponent } from './clightning/transactions/send-payment-modal/send-payment.component';
import { CLCreateInvoiceComponent } from './clightning/transactions/create-invoice-modal/create-invoice.component';
import { CLOnChainSendComponent } from './clightning/on-chain/on-chain-send-modal/on-chain-send.component';
import { InvoiceInformationComponent } from './lnd/transactions/invoice-information-modal/invoice-information.component';
import { ChannelRebalanceComponent } from './lnd/peers-channels/channels/channel-rebalance-modal/channel-rebalance.component';
import { CloseChannelComponent } from './lnd/peers-channels/channels/close-channel-modal/close-channel.component';
import { OpenChannelComponent } from './lnd/peers-channels/channels/open-channel-modal/open-channel.component';
import { ChannelInformationComponent } from './lnd/peers-channels/channels/channel-information-modal/channel-information.component';
import { OnChainSendComponent } from './lnd/on-chain/on-chain-send-modal/on-chain-send.component';
import { LightningSendPaymentsComponent } from './lnd/transactions/send-payment-modal/send-payment.component';
import { CreateInvoiceComponent } from './lnd/transactions/create-invoice-modal/create-invoice.component';
import { ConnectPeerComponent } from './lnd/peers-channels/connect-peer/connect-peer.component';
import { ShowPubkeyComponent } from './shared/components/data-modal/show-pubkey/show-pubkey.component';
import { OnChainGeneratedAddressComponent } from './shared/components/data-modal/on-chain-generated-address/on-chain-generated-address.component';
import { SpinnerDialogComponent } from './shared/components/data-modal/spinner-dialog/spinner-dialog.component';
import { AlertMessageComponent } from './shared/components/data-modal/alert-message/alert-message.component';
import { ConfirmationMessageComponent } from './shared/components/data-modal/confirmation-message/confirmation-message.component';
import { ErrorMessageComponent } from './shared/components/data-modal/error-message/error-message.component';
import { LoopModalComponent } from './lnd/loop/loop-modal/loop-modal.component';
import { TwoFactorAuthComponent } from './shared/components/data-modal/two-factor-auth/two-factor-auth.component';
import { LoginTokenComponent } from './shared/components/data-modal/login-2fa-token/login-2fa-token.component';
import { ECLInvoiceInformationComponent } from './eclair/transactions/invoice-information-modal/invoice-information.component';
import { ECLPaymentInformationComponent } from './eclair/transactions/payment-information-modal/payment-information.component';
import { ECLOpenChannelComponent } from './eclair/peers-channels/channels/open-channel-modal/open-channel.component';
import { ECLConnectPeerComponent } from './eclair/peers-channels/connect-peer/connect-peer.component';
import { ECLLightningSendPaymentsComponent } from './eclair/transactions/send-payment-modal/send-payment.component';
import { ECLCreateInvoiceComponent } from './eclair/transactions/create-invoice-modal/create-invoice.component';
import { ECLOnChainSendComponent } from './eclair/on-chain/on-chain-send-modal/on-chain-send.component';
import { ECLChannelInformationComponent } from './eclair/peers-channels/channels/channel-information-modal/channel-information.component';
@NgModule({ @NgModule({
imports: [ imports: [
@ -70,7 +33,9 @@ import { ECLChannelInformationComponent } from './eclair/peers-channels/channels
BrowserAnimationsModule, BrowserAnimationsModule,
SharedModule, SharedModule,
routing, routing,
UserIdleModule.forRoot({idle: 60 * 60, timeout: 1, ping: null}), LayoutModule,
HammerModule,
UserIdleModule.forRoot({idle: 60 * 60, timeout: 1}),
StoreModule.forRoot(RTLReducer, { StoreModule.forRoot(RTLReducer, {
runtimeChecks: { runtimeChecks: {
strictStateImmutability: false, strictStateImmutability: false,
@ -78,89 +43,12 @@ import { ECLChannelInformationComponent } from './eclair/peers-channels/channels
} }
}), }),
EffectsModule.forRoot([RTLEffects, LNDEffects, CLEffects, ECLEffects]), EffectsModule.forRoot([RTLEffects, LNDEffects, CLEffects, ECLEffects]),
!environment.production ? StoreDevtoolsModule.instrument() : [], !environment.production ? StoreDevtoolsModule.instrument() : []
LayoutModule,
MatDialogModule,
HammerModule
], ],
declarations: [ declarations: [AppComponent],
AppComponent,
InvoiceInformationComponent,
ChannelRebalanceComponent,
OnChainGeneratedAddressComponent,
OpenChannelComponent,
ChannelInformationComponent,
LightningSendPaymentsComponent,
ConnectPeerComponent,
ShowPubkeyComponent,
SpinnerDialogComponent,
AlertMessageComponent,
ConfirmationMessageComponent,
ErrorMessageComponent,
CloseChannelComponent,
LoopModalComponent,
TwoFactorAuthComponent,
LoginTokenComponent,
CreateInvoiceComponent,
OnChainSendComponent,
CLInvoiceInformationComponent,
CLOpenChannelComponent,
CLConnectPeerComponent,
CLLightningSendPaymentsComponent,
CLCreateInvoiceComponent,
CLOnChainSendComponent,
CLChannelInformationComponent,
ECLInvoiceInformationComponent,
ECLPaymentInformationComponent,
ECLOpenChannelComponent,
ECLConnectPeerComponent,
ECLLightningSendPaymentsComponent,
ECLCreateInvoiceComponent,
ECLOnChainSendComponent,
ECLChannelInformationComponent
],
entryComponents: [
SpinnerDialogComponent,
AlertMessageComponent,
ConfirmationMessageComponent,
ErrorMessageComponent,
ShowPubkeyComponent,
TwoFactorAuthComponent,
LoginTokenComponent,
OnChainGeneratedAddressComponent,
CloseChannelComponent,
LoopModalComponent,
InvoiceInformationComponent,
ChannelRebalanceComponent,
OpenChannelComponent,
ConnectPeerComponent,
LightningSendPaymentsComponent,
CreateInvoiceComponent,
OnChainSendComponent,
ChannelInformationComponent,
CLInvoiceInformationComponent,
CLOpenChannelComponent,
CLConnectPeerComponent,
CLLightningSendPaymentsComponent,
CLCreateInvoiceComponent,
CLOnChainSendComponent,
CLChannelInformationComponent,
ECLInvoiceInformationComponent,
ECLPaymentInformationComponent,
ECLOpenChannelComponent,
ECLConnectPeerComponent,
ECLLightningSendPaymentsComponent,
ECLCreateInvoiceComponent,
ECLOnChainSendComponent,
ECLChannelInformationComponent
],
providers: [ providers: [
{ provide: LoggerService, useClass: ConsoleLoggerService },
{ provide: OverlayContainer, useClass: ThemeOverlay },
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }, { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }, AuthGuard, SessionService, DataService, LoopService, CommonService
{ provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: { hasBackdrop: true, autoFocus: true, disableClose: true, role: 'dialog', width: '55%' } },
CommonService, AuthGuard, SessionService, DataService, LoopService
], ],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })

View File

@ -2,6 +2,9 @@ import { Routes, RouterModule } from '@angular/router';
import { ModuleWithProviders } from '@angular/core'; import { ModuleWithProviders } from '@angular/core';
import { SettingsComponent } from './shared/components/settings/settings.component'; import { SettingsComponent } from './shared/components/settings/settings.component';
import { AppSettingsComponent } from './shared/components/settings/app-settings/app-settings.component';
import { AuthSettingsComponent } from './shared/components/settings/auth-settings/auth-settings.component';
import { ServerConfigComponent } from './shared/components/settings/server-config/server-config.component';
import { NotFoundComponent } from './shared/components/not-found/not-found.component'; import { NotFoundComponent } from './shared/components/not-found/not-found.component';
import { HelpComponent } from './shared/components/help/help.component'; import { HelpComponent } from './shared/components/help/help.component';
import { LoginComponent } from './shared/components/login/login.component'; import { LoginComponent } from './shared/components/login/login.component';
@ -9,14 +12,21 @@ import { ErrorComponent } from './shared/components/error/error.component';
import { AuthGuard } from './shared/services/auth.guard'; import { AuthGuard } from './shared/services/auth.guard';
export const routes: Routes = [ export const routes: Routes = [
{ path: '', pathMatch: 'full', redirectTo: 'lnd' },
{ path: 'lnd', loadChildren: () => import('./lnd/lnd.module').then(childModule => childModule.LNDModule), canActivate: [AuthGuard] }, { path: 'lnd', loadChildren: () => import('./lnd/lnd.module').then(childModule => childModule.LNDModule), canActivate: [AuthGuard] },
{ path: 'cl', loadChildren: () => import('./clightning/cl.module').then(childModule => childModule.CLModule), canActivate: [AuthGuard] }, { path: 'cl', loadChildren: () => import('./clightning/cl.module').then(childModule => childModule.CLModule), canActivate: [AuthGuard] },
{ path: 'ecl', loadChildren: () => import('./eclair/ecl.module').then(childModule => childModule.ECLModule), canActivate: [AuthGuard] }, { path: 'ecl', loadChildren: () => import('./eclair/ecl.module').then(childModule => childModule.ECLModule), canActivate: [AuthGuard] },
{ path: 'settings', component: SettingsComponent, canActivate: [AuthGuard] }, { path: 'settings', component: SettingsComponent, canActivate: [AuthGuard], children: [
{ path: '', pathMatch: 'full', redirectTo: 'layout' },
{ path: 'layout', component: AppSettingsComponent, canActivate: [AuthGuard] },
{ path: 'auth', component: AuthSettingsComponent, canActivate: [AuthGuard] },
{ path: 'lnconfig', component: ServerConfigComponent, canActivate: [AuthGuard] },
{ path: 'bconfig', component: ServerConfigComponent, canActivate: [AuthGuard] }
] },
{ path: 'help', component: HelpComponent }, { path: 'help', component: HelpComponent },
{ path: 'login', component: LoginComponent }, { path: 'login', component: LoginComponent },
{ path: 'error', component: ErrorComponent }, { path: 'error', component: ErrorComponent },
{ path: '**', component: NotFoundComponent } { path: '**', component: NotFoundComponent }
]; ];
export const routing: ModuleWithProviders = RouterModule.forRoot(routes); export const routing: ModuleWithProviders<RouterModule> = RouterModule.forRoot(routes);

View File

@ -6,7 +6,7 @@ import { SharedModule } from '../shared/shared.module';
import { CLRootComponent } from './cl-root.component'; import { CLRootComponent } from './cl-root.component';
import { CLHomeComponent } from './home/home.component'; import { CLHomeComponent } from './home/home.component';
import { CLPeersChannelsComponent } from './peers-channels/peers-channels.component'; import { CLConnectionsComponent } from './peers-channels/connections.component';
import { CLChannelsTablesComponent } from './peers-channels/channels/channels-tables/channels-tables.component'; import { CLChannelsTablesComponent } from './peers-channels/channels/channels-tables/channels-tables.component';
import { CLPeersComponent } from './peers-channels/peers/peers.component'; import { CLPeersComponent } from './peers-channels/peers/peers.component';
import { CLLightningInvoicesComponent } from './transactions/invoices/lightning-invoices.component'; import { CLLightningInvoicesComponent } from './transactions/invoices/lightning-invoices.component';
@ -35,6 +35,17 @@ import { CLFeeRatesComponent } from './network-info/fee-rates/fee-rates.componen
import { CLSignVerifyMessageComponent } from './sign-verify-message/sign-verify-message.component'; import { CLSignVerifyMessageComponent } from './sign-verify-message/sign-verify-message.component';
import { CLSignComponent } from './sign-verify-message/sign/sign.component'; import { CLSignComponent } from './sign-verify-message/sign/sign.component';
import { CLVerifyComponent } from './sign-verify-message/verify/verify.component'; import { CLVerifyComponent } from './sign-verify-message/verify/verify.component';
import { CLReportsComponent } from './reports/reports.component';
import { CLFeeReportComponent } from './reports/fee/fee-report.component';
import { CLTransactionsReportComponent } from './reports/transactions/transactions-report.component';
import { CLOnChainSendComponent } from './on-chain/on-chain-send/on-chain-send.component';
import { CLOpenChannelComponent } from './peers-channels/channels/open-channel-modal/open-channel.component';
import { CLChannelInformationComponent } from './peers-channels/channels/channel-information-modal/channel-information.component';
import { CLInvoiceInformationComponent } from './transactions/invoice-information-modal/invoice-information.component';
import { CLConnectPeerComponent } from './peers-channels/connect-peer/connect-peer.component';
import { CLLightningSendPaymentsComponent } from './transactions/send-payment-modal/send-payment.component';
import { CLCreateInvoiceComponent } from './transactions/create-invoice-modal/create-invoice.component';
import { CLOnChainSendModalComponent } from './on-chain/on-chain-send-modal/on-chain-send-modal.component';
import { CLUnlockedGuard } from '../shared/services/auth.guard'; import { CLUnlockedGuard } from '../shared/services/auth.guard';
@ -48,7 +59,7 @@ import { CLUnlockedGuard } from '../shared/services/auth.guard';
CLRootComponent, CLRootComponent,
CLHomeComponent, CLHomeComponent,
CLPeersComponent, CLPeersComponent,
CLPeersChannelsComponent, CLConnectionsComponent,
CLLightningInvoicesComponent, CLLightningInvoicesComponent,
CLLightningPaymentsComponent, CLLightningPaymentsComponent,
CLTransactionsComponent, CLTransactionsComponent,
@ -75,7 +86,18 @@ import { CLUnlockedGuard } from '../shared/services/auth.guard';
CLFeeRatesComponent, CLFeeRatesComponent,
CLSignVerifyMessageComponent, CLSignVerifyMessageComponent,
CLSignComponent, CLSignComponent,
CLVerifyComponent CLVerifyComponent,
CLReportsComponent,
CLFeeReportComponent,
CLTransactionsReportComponent,
CLOnChainSendComponent,
CLInvoiceInformationComponent,
CLOpenChannelComponent,
CLConnectPeerComponent,
CLLightningSendPaymentsComponent,
CLCreateInvoiceComponent,
CLOnChainSendModalComponent,
CLChannelInformationComponent
], ],
providers: [ providers: [
CLUnlockedGuard CLUnlockedGuard

View File

@ -4,31 +4,80 @@ import { ModuleWithProviders } from '@angular/core';
import { CLRootComponent } from './cl-root.component'; import { CLRootComponent } from './cl-root.component';
import { CLHomeComponent } from './home/home.component'; import { CLHomeComponent } from './home/home.component';
import { CLOnChainComponent } from './on-chain/on-chain.component'; import { CLOnChainComponent } from './on-chain/on-chain.component';
import { CLPeersChannelsComponent } from '../clightning/peers-channels/peers-channels.component'; import { CLConnectionsComponent } from './peers-channels/connections.component';
import { CLTransactionsComponent } from '../clightning/transactions/transactions.component'; import { CLTransactionsComponent } from '../clightning/transactions/transactions.component';
import { CLRoutingComponent } from '../clightning/routing/routing.component'; import { CLRoutingComponent } from '../clightning/routing/routing.component';
import { CLLookupsComponent } from './lookups/lookups.component'; import { CLLookupsComponent } from './lookups/lookups.component';
import { CLNetworkInfoComponent } from './network-info/network-info.component'; import { CLNetworkInfoComponent } from './network-info/network-info.component';
import { CLSignVerifyMessageComponent } from './sign-verify-message/sign-verify-message.component'; import { CLSignVerifyMessageComponent } from './sign-verify-message/sign-verify-message.component';
import { CLOnChainReceiveComponent } from './on-chain/on-chain-receive/on-chain-receive.component';
import { CLOnChainSendComponent } from './on-chain/on-chain-send/on-chain-send.component';
import { CLChannelsTablesComponent } from './peers-channels/channels/channels-tables/channels-tables.component';
import { CLChannelOpenTableComponent } from './peers-channels/channels/channels-tables/channel-open-table/channel-open-table.component';
import { CLChannelPendingTableComponent } from './peers-channels/channels/channels-tables/channel-pending-table/channel-pending-table.component';
import { CLPeersComponent } from './peers-channels/peers/peers.component';
import { CLLightningPaymentsComponent } from './transactions/payments/lightning-payments.component';
import { CLLightningInvoicesComponent } from './transactions/invoices/lightning-invoices.component';
import { CLQueryRoutesComponent } from './transactions/query-routes/query-routes.component';
import { CLSignComponent } from './sign-verify-message/sign/sign.component';
import { CLVerifyComponent } from './sign-verify-message/verify/verify.component';
import { CLForwardingHistoryComponent } from './routing/forwarding-history/forwarding-history.component';
import { CLFailedTransactionsComponent } from './routing/failed-transactions/failed-transactions.component';
import { CLReportsComponent } from './reports/reports.component';
import { CLFeeReportComponent } from './reports/fee/fee-report.component';
import { CLTransactionsReportComponent } from './reports/transactions/transactions-report.component';
import { CLUnlockedGuard } from '../shared/services/auth.guard'; import { CLUnlockedGuard } from '../shared/services/auth.guard';
import { NotFoundComponent } from '../shared/components/not-found/not-found.component'; import { NotFoundComponent } from '../shared/components/not-found/not-found.component';
export const ClRoutes: Routes = [ export const ClRoutes: Routes = [
{ path: '', component: CLRootComponent, { path: '', component: CLRootComponent,
children: [ children: [
{ path: 'home', component: CLHomeComponent, canActivate: [CLUnlockedGuard] }, { path: '', pathMatch: 'full', redirectTo: 'home' },
{ path: 'onchain', component: CLOnChainComponent, canActivate: [CLUnlockedGuard] }, { path: 'home', component: CLHomeComponent, canActivate: [CLUnlockedGuard] },
{ path: 'peerschannels', component: CLPeersChannelsComponent, canActivate: [CLUnlockedGuard] }, { path: 'onchain', component: CLOnChainComponent, canActivate: [CLUnlockedGuard], children: [
{ path: 'transactions', component: CLTransactionsComponent, canActivate: [CLUnlockedGuard] }, { path: '', pathMatch: 'full', redirectTo: 'receive' },
{ path: 'routing', component: CLRoutingComponent, canActivate: [CLUnlockedGuard] }, { path: 'receive', component: CLOnChainReceiveComponent, canActivate: [CLUnlockedGuard] },
{ path: 'lookups', component: CLLookupsComponent, canActivate: [CLUnlockedGuard] }, { path: 'send', component: CLOnChainSendComponent, data : {sweepAll : false}, canActivate: [CLUnlockedGuard] },
{ path: 'rates', component: CLNetworkInfoComponent, canActivate: [CLUnlockedGuard] }, { path: 'sweep', component: CLOnChainSendComponent, data : {sweepAll : true}, canActivate: [CLUnlockedGuard] }
{ path: 'signverify', component: CLSignVerifyMessageComponent, canActivate: [CLUnlockedGuard] }, ] },
{ path: '**', component: NotFoundComponent }, { path: 'connections', component: CLConnectionsComponent, canActivate: [CLUnlockedGuard], children: [
{ path: 'network', redirectTo: 'rates' }, { path: '', pathMatch: 'full', redirectTo: 'channels' },
{ path: 'wallet', redirectTo: 'home' }, { path: 'channels', component: CLChannelsTablesComponent, canActivate: [CLUnlockedGuard], children: [
{ path: 'backup', redirectTo: 'home' } { path: '', pathMatch: 'full', redirectTo: 'open' },
]} { path: 'open', component: CLChannelOpenTableComponent, canActivate: [CLUnlockedGuard] },
{ path: 'pending', component: CLChannelPendingTableComponent, canActivate: [CLUnlockedGuard] }
] },
{ path: 'peers', component: CLPeersComponent, data : {sweepAll : false}, canActivate: [CLUnlockedGuard] }
] },
{ path: 'transactions', component: CLTransactionsComponent, canActivate: [CLUnlockedGuard], children: [
{ path: '', pathMatch: 'full', redirectTo: 'payments' },
{ path: 'payments', component: CLLightningPaymentsComponent, canActivate: [CLUnlockedGuard] },
{ path: 'invoices', component: CLLightningInvoicesComponent, canActivate: [CLUnlockedGuard] },
{ path: 'queryroutes', component: CLQueryRoutesComponent, canActivate: [CLUnlockedGuard] }
] },
{ path: 'messages', component: CLSignVerifyMessageComponent, canActivate: [CLUnlockedGuard], children: [
{ path: '', pathMatch: 'full', redirectTo: 'sign' },
{ path: 'sign', component: CLSignComponent, canActivate: [CLUnlockedGuard] },
{ path: 'verify', component: CLVerifyComponent, canActivate: [CLUnlockedGuard] }
] },
{ path: 'routing', component: CLRoutingComponent, canActivate: [CLUnlockedGuard], children: [
{ path: '', pathMatch: 'full', redirectTo: 'forwardinghistory' },
{ path: 'forwardinghistory', component: CLForwardingHistoryComponent, canActivate: [CLUnlockedGuard] },
{ path: 'failedtransactions', component: CLFailedTransactionsComponent, canActivate: [CLUnlockedGuard] }
] },
{ path: 'reports', component: CLReportsComponent, canActivate: [CLUnlockedGuard], children: [
{ path: '', pathMatch: 'full', redirectTo: 'routingfees' },
{ path: 'routingfees', component: CLFeeReportComponent, canActivate: [CLUnlockedGuard] },
{ path: 'transactions', component: CLTransactionsReportComponent, canActivate: [CLUnlockedGuard] }
] },
{ path: 'lookups', component: CLLookupsComponent, canActivate: [CLUnlockedGuard] },
{ path: 'rates', component: CLNetworkInfoComponent, canActivate: [CLUnlockedGuard] },
{ path: '**', component: NotFoundComponent },
{ path: 'network', redirectTo: 'rates' },
{ path: 'wallet', redirectTo: 'home' },
{ path: 'backup', redirectTo: 'home' }
]
}
]; ];
export const CLRouting: ModuleWithProviders = RouterModule.forChild(ClRoutes); export const CLRouting: ModuleWithProviders<RouterModule> = RouterModule.forChild(ClRoutes);

View File

@ -12,7 +12,7 @@
<mat-progress-bar class="dashboard-progress-bar this-channel-bar" mode="determinate" color="accent" value="{{channelBalances.localBalance && channelBalances.localBalance > 0 ? ((+channelBalances.localBalance/((+channelBalances.localBalance)+(+channelBalances.remoteBalance)))*100) : 0}}"></mat-progress-bar> <mat-progress-bar class="dashboard-progress-bar this-channel-bar" mode="determinate" color="accent" value="{{channelBalances.localBalance && channelBalances.localBalance > 0 ? ((+channelBalances.localBalance/((+channelBalances.localBalance)+(+channelBalances.remoteBalance)))*100) : 0}}"></mat-progress-bar>
</div> </div>
<div fxLayout="column" fxFlex="3" fxLayoutAlign="end stretch"><mat-divider class="dashboard-divider"></mat-divider></div> <div fxLayout="column" fxFlex="3" fxLayoutAlign="end stretch"><mat-divider class="dashboard-divider"></mat-divider></div>
<div class="channels-capacity-scroll" perfectScrollbar> <div class="channels-capacity-scroll" [perfectScrollbar]>
<div fxLayout="column" fxFlex="100" *ngIf="allChannels && allChannels.length > 0; else noChannelBlock"> <div fxLayout="column" fxFlex="100" *ngIf="allChannels && allChannels.length > 0; else noChannelBlock">
<div *ngFor="let channel of allChannels" class="mt-2"> <div *ngFor="let channel of allChannels" class="mt-2">
<span class="dashboard-capacity-header" matTooltip="{{channel.alias || channel.id}}" matTooltipDisabled="{{(channel.alias || channel.id).length < 26}}">{{(channel.alias || channel.id) | slice:0:24}}{{(channel.alias || channel.id).length > 25 ? '...' : ''}}</span> <span class="dashboard-capacity-header" matTooltip="{{channel.alias || channel.id}}" matTooltipDisabled="{{(channel.alias || channel.id).length < 26}}">{{(channel.alias || channel.id) | slice:0:24}}{{(channel.alias || channel.id).length > 25 ? '...' : ''}}</span>

View File

@ -19,7 +19,7 @@ export class CLChannelCapacityInfoComponent {
constructor(private router: Router) {} constructor(private router: Router) {}
goToChannels() { goToChannels() {
this.router.navigateByUrl('/cl/peerschannels'); this.router.navigateByUrl('/cl/connections');
} }
} }

View File

@ -1,11 +1,11 @@
<div fxLayout="column" fxLayoutAlign="space-between stretch" fxFlex="100"> <div fxLayout="column" fxLayoutAlign="space-between stretch" fxFlex="100" [ngClass]="{'mb-4': screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM, 'mb-2': screenSize === screenSizeEnum.MD, 'mb-1': screenSize === screenSizeEnum.LG || screenSize === screenSizeEnum.XL}">
<div fxLayout="column" fxFlex="9" fxLayoutAlign="end start"> <div fxLayout="column" fxFlex="9" fxLayoutAlign="end start">
<span class="dashboard-capacity-header this-channel-capacity">Total Capacity</span> <span class="dashboard-capacity-header this-channel-capacity">Total Capacity</span>
<mat-hint class="font-size-90">{{totalLiquidity | number:'1.0-0'}} Sats</mat-hint> <mat-hint class="font-size-90">{{totalLiquidity | number:'1.0-0'}} Sats</mat-hint>
<mat-progress-bar class="dashboard-progress-bar this-channel-bar" mode="determinate" color="accent" value="100"></mat-progress-bar> <mat-progress-bar class="dashboard-progress-bar this-channel-bar" mode="determinate" color="accent" value="100"></mat-progress-bar>
</div> </div>
<div fxLayout="column" fxFlex="3" fxLayoutAlign="end stretch"><mat-divider class="dashboard-divider"></mat-divider></div> <div fxLayout="column" fxFlex="3" fxLayoutAlign="end stretch"><mat-divider class="dashboard-divider"></mat-divider></div>
<div fxLayout="column" fxFlex.gt-sm="88" fxFlex="84" fxLayoutAlign="start start" class="channels-capacity-scroll" perfectScrollbar> <div [perfectScrollbar] fxLayout="column" fxFlex.gt-sm="88" fxFlex="84" fxLayoutAlign="start start">
<div fxLayout="column" fxFlex="100" class="w-100" *ngIf="allChannels && allChannels.length > 0; else noChannelBlock"> <div fxLayout="column" fxFlex="100" class="w-100" *ngIf="allChannels && allChannels.length > 0; else noChannelBlock">
<div *ngFor="let channel of allChannels" class="mt-2"> <div *ngFor="let channel of allChannels" class="mt-2">
<span class="dashboard-capacity-header" matTooltip="{{channel.alias || channel.id}}" matTooltipDisabled="{{(channel.alias || channel.id).length < 26}}">{{(channel.alias || channel.id) | slice:0:24}}{{(channel.alias || channel.id).length > 25 ? '...' : ''}}</span> <span class="dashboard-capacity-header" matTooltip="{{channel.alias || channel.id}}" matTooltipDisabled="{{(channel.alias || channel.id).length < 26}}">{{(channel.alias || channel.id) | slice:0:24}}{{(channel.alias || channel.id).length > 25 ? '...' : ''}}</span>

View File

@ -1,22 +1,30 @@
import { Component, Input } from '@angular/core'; import { Component, Input, OnInit } from '@angular/core';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { Channel } from '../../../shared/models/clModels'; import { Channel } from '../../../shared/models/clModels';
import { ScreenSizeEnum } from '../../../shared/services/consts-enums-functions';
import { CommonService } from '../../../shared/services/common.service';
@Component({ @Component({
selector: 'rtl-cl-channel-liquidity-info', selector: 'rtl-cl-channel-liquidity-info',
templateUrl: './channel-liquidity-info.component.html', templateUrl: './channel-liquidity-info.component.html',
styleUrls: ['./channel-liquidity-info.component.scss'] styleUrls: ['./channel-liquidity-info.component.scss']
}) })
export class CLChannelLiquidityInfoComponent { export class CLChannelLiquidityInfoComponent implements OnInit {
@Input() direction: string; @Input() direction: string;
@Input() totalLiquidity: number; @Input() totalLiquidity: number;
@Input() allChannels: Channel[]; @Input() allChannels: Channel[];
public screenSize = '';
public screenSizeEnum = ScreenSizeEnum;
constructor(private router: Router) {} constructor(private router: Router, private commonService: CommonService) {}
ngOnInit() {
this.screenSize = this.commonService.getScreenSize();
}
goToChannels() { goToChannels() {
this.router.navigateByUrl('/cl/peerschannels'); this.router.navigateByUrl('/cl/connections');
} }
} }

View File

@ -1,25 +1,30 @@
<div fxLayout="column" *ngIf="selNode.userPersona === userPersonaEnum.OPERATOR; else merchantDashboard"> <div fxLayout="column" *ngIf="selNode.userPersona === userPersonaEnum.OPERATOR; else merchantDashboard">
<div fxLayout="row" fxLayoutAlign="start end" class="padding-gap-x page-title-container mb-0"> <div fxLayout="row" fxLayoutAlign="start start" class="page-title-container mb-0">
<fa-icon [icon]="!flgLoading[0] ? faSmile : faFrown" class="page-title-img mr-1"></fa-icon> <fa-icon [icon]="!flgLoading[0] ? faSmile : faFrown" class="page-title-img mr-1"></fa-icon>
<span class="page-title">{{!flgLoading[0] ? 'Welcome ' + information.alias + '! Your node is up and running.' : 'Error! Please check the server connection.'}}</span> <span class="page-title">{{!flgLoading[0] ? 'Welcome ' + information.alias + '! Your node is up and running.' : 'Error! Please check the server connection.'}}</span>
</div> </div>
<mat-grid-list cols="10" [rowHeight]="operatorCardHeight"> <mat-grid-list cols="10" [rowHeight]="operatorCardHeight">
<mat-grid-tile *ngFor="let card of operatorCards" [colspan]="card.cols" [rowspan]="card.rows"> <mat-grid-tile *ngFor="let card of operatorCards" [colspan]="card.cols" [rowspan]="card.rows">
<mat-card fxLayout="column" fxLayoutAlign="start start" class="dashboard-card p-24"> <mat-card fxFlex="100" fxLayout="column" fxLayoutAlign="start stretch" class="h-100 dashboard-card mt-4">
<mat-card-header> <mat-card-header>
<mat-card-title> <mat-card-title fxLayoutAlign="space-between center">
<fa-icon [icon]="card.icon" class="mr-1"></fa-icon> <div>
<span>{{card.title}}</span> <fa-icon [icon]="card.icon" class="mr-1"></fa-icon>
<button *ngIf="card.link" mat-icon-button class="more-button mt-1" [matMenuTriggerFor]="menuOperator" aria-label="Toggle menu"> <span>{{card.title}}</span>
<mat-icon>more_vert</mat-icon> </div>
</button> <div>
<mat-menu #menuOperator="matMenu" class="dashboard-vert-menu" xPosition="before"> <button *ngIf="card.link" mat-icon-button class="more-button" [matMenuTriggerFor]="menuOperator" aria-label="Toggle menu">
<button mat-menu-item (click)="onNavigateTo(card.link)">Go To {{card.goTo}}</button> <mat-icon>more_vert</mat-icon>
<button *ngIf="card.id === 'capacity'" (click)="onsortChannelsBy()" mat-menu-item>Sort By {{sortField === 'Balance Score' ? 'Capacity' : 'Balance Score'}}</button> </button>
</mat-menu> <mat-menu #menuOperator="matMenu" class="dashboard-vert-menu" xPosition="before">
<button mat-menu-item (click)="onNavigateTo(card.link)">Go To {{card.goTo}}</button>
<button *ngIf="card.id === 'fee'" (click)="onNavigateTo('/cl/reports')" mat-menu-item>Fees Summary</button>
<button *ngIf="card.id === 'capacity'" (click)="onsortChannelsBy()" mat-menu-item>Sort By {{sortField === 'Balance Score' ? 'Capacity' : 'Balance Score'}}</button>
</mat-menu>
</div>
</mat-card-title> </mat-card-title>
</mat-card-header> </mat-card-header>
<mat-card-content class="dashboard-card-content w-100" fxFlex="95"> <mat-card-content class="dashboard-card-content" fxLayout="column" fxFlex="{{card.id === 'capacity' ? 90 : 70}}">
<div [ngSwitch]="card.id" fxLayout="column" fxFlex="100"> <div [ngSwitch]="card.id" fxLayout="column" fxFlex="100">
<rtl-cl-node-info fxFlex="100" *ngSwitchCase="'node'" [information]="information" [showColorFieldSeparately]="false" [ngClass]="{'error-border': flgLoading[0]==='error'}"></rtl-cl-node-info> <rtl-cl-node-info fxFlex="100" *ngSwitchCase="'node'" [information]="information" [showColorFieldSeparately]="false" [ngClass]="{'error-border': flgLoading[0]==='error'}"></rtl-cl-node-info>
<rtl-cl-balances-info fxFlex="100" *ngSwitchCase="'balance'" [balances]="balances" [ngClass]="{'error-border': flgLoading[2]==='error'}"></rtl-cl-balances-info> <rtl-cl-balances-info fxFlex="100" *ngSwitchCase="'balance'" [balances]="balances" [ngClass]="{'error-border': flgLoading[2]==='error'}"></rtl-cl-balances-info>
@ -34,26 +39,30 @@
</mat-grid-list> </mat-grid-list>
</div> </div>
<ng-template #merchantDashboard> <ng-template #merchantDashboard>
<div fxLayout="row" fxLayoutAlign="start end" class="padding-gap-x page-title-container mb-0"> <div fxLayout="row" fxLayoutAlign="start end" class="page-title-container mb-0">
<fa-icon [icon]="faSmile" class="page-title-img mr-1"></fa-icon> <fa-icon [icon]="faSmile" class="page-title-img mr-1"></fa-icon>
<span class="page-title">Welcome {{information.alias}}! Your node is up and running.</span> <span class="page-title">Welcome {{information.alias}}! Your node is up and running.</span>
</div> </div>
<mat-grid-list cols="6" [rowHeight]="merchantCardHeight"> <mat-grid-list cols="6" [rowHeight]="merchantCardHeight">
<mat-grid-tile *ngFor="let card of merchantCards" [colspan]="card.cols" [rowspan]="card.rows"> <mat-grid-tile *ngFor="let card of merchantCards" [colspan]="card.cols" [rowspan]="card.rows">
<mat-card fxLayout="column" fxLayoutAlign="start start" class="dashboard-card" [ngClass]="{'p-24': card.id !== 'transactions'}"> <mat-card fxFlex="100" fxLayout="column" fxLayoutAlign="start stretch" class="h-100 dashboard-card mt-4" [ngClass]="{'p-0': card.id === 'transactions'}">
<mat-card-header *ngIf="card.id !== 'transactions'"> <mat-card-header *ngIf="card.id !== 'transactions'">
<mat-card-title> <mat-card-title fxLayoutAlign="space-between center">
<fa-icon [icon]="card.icon" class="mr-1"></fa-icon> <div>
<span>{{card.title}}</span> <fa-icon [icon]="card.icon" class="mr-1"></fa-icon>
<button *ngIf="card.link" mat-icon-button class="more-button mt-1" [matMenuTriggerFor]="menuMerchant" aria-label="Toggle menu"> <span>{{card.title}}</span>
<mat-icon>more_vert</mat-icon> </div>
</button> <div>
<mat-menu #menuMerchant="matMenu" class="dashboard-vert-menu" xPosition="before"> <button *ngIf="card.link" mat-icon-button class="more-button" [matMenuTriggerFor]="menuMerchant" aria-label="Toggle menu">
<button mat-menu-item (click)="onNavigateTo(card.link)">Go To {{card.goTo}}</button> <mat-icon>more_vert</mat-icon>
</mat-menu> </button>
<mat-menu #menuMerchant="matMenu" class="dashboard-vert-menu" xPosition="before">
<button mat-menu-item (click)="onNavigateTo(card.link)">Go To {{card.goTo}}</button>
</mat-menu>
</div>
</mat-card-title> </mat-card-title>
</mat-card-header> </mat-card-header>
<mat-card-content class="dashboard-card-content w-100" fxFlex="{{card.id !== 'transactions' ? 95 : 100}}"> <mat-card-content class="dashboard-card-content" fxLayout="column" fxLayoutAlign="start stretch" fxFlex="{{card.id === 'transactions' ? 100 : card.id === 'balance' ? 70: 90}}">
<div [ngSwitch]="card.id" fxLayout="column" fxFlex="100"> <div [ngSwitch]="card.id" fxLayout="column" fxFlex="100">
<rtl-cl-node-info fxFlex="100" *ngSwitchCase="'node'" [information]="information" [ngClass]="{'error-border': flgLoading[0]==='error'}"></rtl-cl-node-info> <rtl-cl-node-info fxFlex="100" *ngSwitchCase="'node'" [information]="information" [ngClass]="{'error-border': flgLoading[0]==='error'}"></rtl-cl-node-info>
<rtl-cl-balances-info fxFlex="100" *ngSwitchCase="'balance'" [balances]="balances" [ngClass]="{'error-border': flgLoading[2]==='error'}"></rtl-cl-balances-info> <rtl-cl-balances-info fxFlex="100" *ngSwitchCase="'balance'" [balances]="balances" [ngClass]="{'error-border': flgLoading[2]==='error'}"></rtl-cl-balances-info>
@ -61,8 +70,8 @@
<rtl-cl-channel-liquidity-info fxFlex="100" *ngSwitchCase="'outboundLiq'" [direction]="'Out'" [totalLiquidity]="totalOutboundLiquidity" [allChannels]="allOutboundChannels" [ngClass]="{'error-border': flgLoading[5]==='error'}"></rtl-cl-channel-liquidity-info> <rtl-cl-channel-liquidity-info fxFlex="100" *ngSwitchCase="'outboundLiq'" [direction]="'Out'" [totalLiquidity]="totalOutboundLiquidity" [allChannels]="allOutboundChannels" [ngClass]="{'error-border': flgLoading[5]==='error'}"></rtl-cl-channel-liquidity-info>
<span fxLayout="column" fxFlex="100" fxLayoutAlign="space-between start" *ngSwitchCase="'transactions'"> <span fxLayout="column" fxFlex="100" fxLayoutAlign="space-between start" *ngSwitchCase="'transactions'">
<mat-tab-group fxLayout="column" class="w-100 dashboard-tabs-group"> <mat-tab-group fxLayout="column" class="w-100 dashboard-tabs-group">
<mat-tab label="Receive"><rtl-cl-lightning-invoices class="h-100" [showDetails]="false"></rtl-cl-lightning-invoices></mat-tab> <mat-tab label="Receive"><rtl-cl-lightning-invoices class="h-100" [calledFrom]="'home'"></rtl-cl-lightning-invoices></mat-tab>
<mat-tab label="Pay"><rtl-cl-lightning-payments [showDetails]="false"></rtl-cl-lightning-payments></mat-tab> <mat-tab label="Pay"><rtl-cl-lightning-payments [calledFrom]="'home'"></rtl-cl-lightning-payments></mat-tab>
<mat-tab [disabled]="true"> <mat-tab [disabled]="true">
<ng-template mat-tab-label> <ng-template mat-tab-label>
<button mat-icon-button class="more-button" [matMenuTriggerFor]="menuTransactions" aria-label="Toggle menu" style="max-width: 20px;"> <button mat-icon-button class="more-button" [matMenuTriggerFor]="menuTransactions" aria-label="Toggle menu" style="max-width: 20px;">
@ -70,6 +79,7 @@
</button> </button>
<mat-menu #menuTransactions="matMenu" class="dashboard-vert-menu" xPosition="before"> <mat-menu #menuTransactions="matMenu" class="dashboard-vert-menu" xPosition="before">
<button mat-menu-item (click)="onNavigateTo(card.link)">Go To {{card.goTo}}</button> <button mat-menu-item (click)="onNavigateTo(card.link)">Go To {{card.goTo}}</button>
<button mat-menu-item (click)="onNavigateTo('/cl/reports/transactions')">Transactions Summary</button>
</mat-menu> </mat-menu>
</ng-template> </ng-template>
</mat-tab> </mat-tab>

View File

@ -1,17 +0,0 @@
.dashboard-card {
position: absolute;
top: 1rem;
left: 1rem;
right: 1rem;
bottom: 1rem;
}
.more-button {
position: absolute;
top: 7px;
right: 7px;
}
.dashboard-card-content {
text-align: left;
}

View File

@ -13,7 +13,6 @@ import { UserPersonaEnum, ScreenSizeEnum } from '../../shared/services/consts-en
import { ChannelsStatus, GetInfo, Fees, Channel, Balance, FeeRates } from '../../shared/models/clModels'; import { ChannelsStatus, GetInfo, Fees, Channel, Balance, FeeRates } from '../../shared/models/clModels';
import { SelNodeChild } from '../../shared/models/RTLconfig'; import { SelNodeChild } from '../../shared/models/RTLconfig';
import * as CLActions from '../store/cl.actions'; import * as CLActions from '../store/cl.actions';
import * as RTLActions from '../../store/rtl.actions';
import * as fromRTLReducer from '../../store/rtl.reducers'; import * as fromRTLReducer from '../../store/rtl.reducers';
@Component({ @Component({
@ -63,28 +62,28 @@ export class CLHomeComponent implements OnInit, OnDestroy {
{ id: 'node', icon: this.faServer, title: 'Node Information', cols: 10, rows: 1 }, { id: 'node', icon: this.faServer, title: 'Node Information', cols: 10, rows: 1 },
{ id: 'balance', goTo: 'On-Chain', link: '/cl/onchain', icon: this.faChartPie, title: 'Balances', cols: 10, rows: 1 }, { id: 'balance', goTo: 'On-Chain', link: '/cl/onchain', icon: this.faChartPie, title: 'Balances', cols: 10, rows: 1 },
{ id: 'fee', goTo: 'Routing', link: '/cl/routing', icon: this.faBolt, title: 'Routing Fee', cols: 10, rows: 1 }, { id: 'fee', goTo: 'Routing', link: '/cl/routing', icon: this.faBolt, title: 'Routing Fee', cols: 10, rows: 1 },
{ id: 'status', goTo: 'Channels', link: '/cl/peerschannels', icon: this.faNetworkWired, title: 'Channels', cols: 10, rows: 1 }, { id: 'status', goTo: 'Channels', link: '/cl/connections', icon: this.faNetworkWired, title: 'Channels', cols: 10, rows: 1 },
{ id: 'capacity', goTo: 'Channels', link: '/cl/peerschannels', icon: this.faNetworkWired, title: 'Channels Capacity', cols: 10, rows: 2 } { id: 'capacity', goTo: 'Channels', link: '/cl/connections', icon: this.faNetworkWired, title: 'Channels Capacity', cols: 10, rows: 2 }
]; ];
this.merchantCards = [ this.merchantCards = [
{ id: 'balance', goTo: 'On-Chain', link: '/cl/onchain', icon: this.faChartPie, title: 'Balances', cols: 6, rows: 4 }, { id: 'balance', goTo: 'On-Chain', link: '/cl/onchain', icon: this.faChartPie, title: 'Balances', cols: 6, rows: 4 },
{ id: 'transactions', goTo: 'Transactions', link: '/cl/transactions', title: '', cols: 6, rows: 4 }, { id: 'transactions', goTo: 'Transactions', link: '/cl/transactions', title: '', cols: 6, rows: 4 },
{ id: 'inboundLiq', goTo: 'Channels', link: '/cl/peerschannels', icon: this.faAngleDoubleDown, title: 'In-Bound Liquidity', cols: 6, rows: 8 }, { id: 'inboundLiq', goTo: 'Channels', link: '/cl/connections', icon: this.faAngleDoubleDown, title: 'In-Bound Liquidity', cols: 6, rows: 8 },
{ id: 'outboundLiq', goTo: 'Channels', link: '/cl/peerschannels', icon: this.faAngleDoubleUp, title: 'Out-Bound Liquidity', cols: 6, rows: 8 } { id: 'outboundLiq', goTo: 'Channels', link: '/cl/connections', icon: this.faAngleDoubleUp, title: 'Out-Bound Liquidity', cols: 6, rows: 8 }
]; ];
} else if(this.screenSize === ScreenSizeEnum.SM || this.screenSize === ScreenSizeEnum.MD) { } else if(this.screenSize === ScreenSizeEnum.SM || this.screenSize === ScreenSizeEnum.MD) {
this.operatorCards = [ this.operatorCards = [
{ id: 'node', icon: this.faServer, title: 'Node Information', cols: 5, rows: 1 }, { id: 'node', icon: this.faServer, title: 'Node Information', cols: 5, rows: 1 },
{ id: 'balance', goTo: 'On-Chain', link: '/cl/onchain', icon: this.faChartPie, title: 'Balances', cols: 5, rows: 1 }, { id: 'balance', goTo: 'On-Chain', link: '/cl/onchain', icon: this.faChartPie, title: 'Balances', cols: 5, rows: 1 },
{ id: 'fee', goTo: 'Routing', link: '/cl/routing', icon: this.faBolt, title: 'Routing Fee', cols: 5, rows: 1 }, { id: 'fee', goTo: 'Routing', link: '/cl/routing', icon: this.faBolt, title: 'Routing Fee', cols: 5, rows: 1 },
{ id: 'status', goTo: 'Channels', link: '/cl/peerschannels', icon: this.faNetworkWired, title: 'Channels', cols: 5, rows: 1 }, { id: 'status', goTo: 'Channels', link: '/cl/connections', icon: this.faNetworkWired, title: 'Channels', cols: 5, rows: 1 },
{ id: 'capacity', goTo: 'Channels', link: '/cl/peerschannels', icon: this.faNetworkWired, title: 'Channels Capacity', cols: 10, rows: 2 } { id: 'capacity', goTo: 'Channels', link: '/cl/connections', icon: this.faNetworkWired, title: 'Channels Capacity', cols: 10, rows: 2 }
]; ];
this.merchantCards = [ this.merchantCards = [
{ id: 'balance', goTo: 'On-Chain', link: '/cl/onchain', icon: this.faChartPie, title: 'Balances', cols: 3, rows: 4 }, { id: 'balance', goTo: 'On-Chain', link: '/cl/onchain', icon: this.faChartPie, title: 'Balances', cols: 3, rows: 4 },
{ id: 'transactions', goTo: 'Transactions', link: '/cl/transactions', title: '', cols: 3, rows: 4 }, { id: 'transactions', goTo: 'Transactions', link: '/cl/transactions', title: '', cols: 3, rows: 4 },
{ id: 'inboundLiq', goTo: 'Channels', link: '/cl/peerschannels', icon: this.faAngleDoubleDown, title: 'In-Bound Liquidity', cols: 3, rows: 8 }, { id: 'inboundLiq', goTo: 'Channels', link: '/cl/connections', icon: this.faAngleDoubleDown, title: 'In-Bound Liquidity', cols: 3, rows: 8 },
{ id: 'outboundLiq', goTo: 'Channels', link: '/cl/peerschannels', icon: this.faAngleDoubleUp, title: 'Out-Bound Liquidity', cols: 3, rows: 8 } { id: 'outboundLiq', goTo: 'Channels', link: '/cl/connections', icon: this.faAngleDoubleUp, title: 'Out-Bound Liquidity', cols: 3, rows: 8 }
]; ];
} else { } else {
this.operatorCardHeight = ((window.screen.height * 0.77) / 2) + 'px'; this.operatorCardHeight = ((window.screen.height * 0.77) / 2) + 'px';
@ -92,14 +91,14 @@ export class CLHomeComponent implements OnInit, OnDestroy {
this.operatorCards = [ this.operatorCards = [
{ id: 'node', icon: this.faServer, title: 'Node Information', cols: 3, rows: 1 }, { id: 'node', icon: this.faServer, title: 'Node Information', cols: 3, rows: 1 },
{ id: 'balance', goTo: 'On-Chain', link: '/cl/onchain', icon: this.faChartPie, title: 'Balances', cols: 3, rows: 1 }, { id: 'balance', goTo: 'On-Chain', link: '/cl/onchain', icon: this.faChartPie, title: 'Balances', cols: 3, rows: 1 },
{ id: 'capacity', goTo: 'Channels', link: '/cl/peerschannels', icon: this.faNetworkWired, title: 'Channels Capacity', cols: 4, rows: 2 }, { id: 'capacity', goTo: 'Channels', link: '/cl/connections', icon: this.faNetworkWired, title: 'Channels Capacity', cols: 4, rows: 2 },
{ id: 'fee', goTo: 'Routing', link: '/cl/routing', icon: this.faBolt, title: 'Routing Fee', cols: 3, rows: 1 }, { id: 'fee', goTo: 'Routing', link: '/cl/routing', icon: this.faBolt, title: 'Routing Fee', cols: 3, rows: 1 },
{ id: 'status', goTo: 'Channels', link: '/cl/peerschannels', icon: this.faNetworkWired, title: 'Channels', cols: 3, rows: 1 } { id: 'status', goTo: 'Channels', link: '/cl/connections', icon: this.faNetworkWired, title: 'Channels', cols: 3, rows: 1 }
]; ];
this.merchantCards = [ this.merchantCards = [
{ id: 'balance', goTo: 'On-Chain', link: '/cl/onchain', icon: this.faChartPie, title: 'Balances', cols: 2, rows: 5 }, { id: 'balance', goTo: 'On-Chain', link: '/cl/onchain', icon: this.faChartPie, title: 'Balances', cols: 2, rows: 5 },
{ id: 'inboundLiq', goTo: 'Channels', link: '/cl/peerschannels', icon: this.faAngleDoubleDown, title: 'In-Bound Liquidity', cols: 2, rows: 10 }, { id: 'inboundLiq', goTo: 'Channels', link: '/cl/connections', icon: this.faAngleDoubleDown, title: 'In-Bound Liquidity', cols: 2, rows: 10 },
{ id: 'outboundLiq', goTo: 'Channels', link: '/cl/peerschannels', icon: this.faAngleDoubleUp, title: 'Out-Bound Liquidity', cols: 2, rows: 10 }, { id: 'outboundLiq', goTo: 'Channels', link: '/cl/connections', icon: this.faAngleDoubleUp, title: 'Out-Bound Liquidity', cols: 2, rows: 10 },
{ id: 'transactions', goTo: 'Transactions', link: '/cl/transactions', title: '', cols: 2, rows: 5 } { id: 'transactions', goTo: 'Transactions', link: '/cl/transactions', title: '', cols: 2, rows: 5 }
]; ];
} }

View File

@ -9,7 +9,7 @@ import { CommonService } from '../../../shared/services/common.service';
}) })
export class CLNodeInfoComponent implements OnChanges { export class CLNodeInfoComponent implements OnChanges {
@Input() information: GetInfo; @Input() information: GetInfo;
@Input() showColorFieldSeparately: false; @Input() showColorFieldSeparately: boolean;
public chains: Array<string> = ['']; public chains: Array<string> = [''];
constructor(private commonService: CommonService) { } constructor(private commonService: CommonService) { }

View File

@ -1,5 +1,5 @@
<div fxLayout="column"> <div fxLayout="column">
<div fxLayout="row" fxLayoutAlign="start end" class="padding-gap-x page-title-container"> <div fxLayout="row" fxLayoutAlign="start end" class="page-title-container">
<fa-icon [icon]="faSearch" class="page-title-img mr-1"></fa-icon> <fa-icon [icon]="faSearch" class="page-title-img mr-1"></fa-icon>
<span class="page-title">Graph Lookups</span> <span class="page-title">Graph Lookups</span>
</div> </div>

View File

@ -19,7 +19,7 @@ import { CommonService } from '../../shared/services/common.service';
styleUrls: ['./lookups.component.scss'] styleUrls: ['./lookups.component.scss']
}) })
export class CLLookupsComponent implements OnInit, OnDestroy { export class CLLookupsComponent implements OnInit, OnDestroy {
@ViewChild('form', { static: false }) form: any; @ViewChild('form', { static: true }) form: any;
public lookupKey = ''; public lookupKey = '';
public nodeLookupValue = {nodeid: ''}; public nodeLookupValue = {nodeid: ''};
public channelLookupValue = []; public channelLookupValue = [];
@ -69,7 +69,7 @@ export class CLLookupsComponent implements OnInit, OnDestroy {
}); });
} }
onLookup() { onLookup():boolean|void {
if(!this.lookupKey) { return true; } if(!this.lookupKey) { return true; }
this.flgSetLookupValue = false; this.flgSetLookupValue = false;
this.nodeLookupValue = {nodeid: ''}; this.nodeLookupValue = {nodeid: ''};

View File

@ -25,7 +25,7 @@
<mat-divider [inset]="true" class="my-1"></mat-divider> <mat-divider [inset]="true" class="my-1"></mat-divider>
<div fxLayout="column" class="mt-2"> <div fxLayout="column" class="mt-2">
<h4 fxFlex="100" fxLayoutAlign="start" class="font-bold-500 mb-1">Addresses</h4> <h4 fxFlex="100" fxLayoutAlign="start" class="font-bold-500 mb-1">Addresses</h4>
<div perfectScrollbar fxLayout="row" fxLayoutAlign="start center" fxFlex="100" class="table-container"> <div [perfectScrollbar] fxLayout="row" fxLayoutAlign="start center" fxFlex="100" class="table-container">
<table mat-table #table [dataSource]="addresses" matSort class="overflow-auto"> <table mat-table #table [dataSource]="addresses" matSort class="overflow-auto">
<ng-container matColumnDef="type"> <ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Type</th> <th mat-header-cell *matHeaderCellDef mat-sort-header>Type</th>

View File

@ -12,7 +12,7 @@ import { LoggerService } from '../../../shared/services/logger.service';
styleUrls: ['./node-lookup.component.scss'] styleUrls: ['./node-lookup.component.scss']
}) })
export class CLNodeLookupComponent implements OnInit { export class CLNodeLookupComponent implements OnInit {
@ViewChild(MatSort, { static: true }) sort: MatSort; @ViewChild(MatSort, { static: false }) sort: MatSort|undefined;
@Input() lookupResult: LookupNode; @Input() lookupResult: LookupNode;
public addresses: any; public addresses: any;
public displayedColumns = ['type', 'address', 'port', 'actions']; public displayedColumns = ['type', 'address', 'port', 'actions'];
@ -23,7 +23,7 @@ export class CLNodeLookupComponent implements OnInit {
this.addresses = this.lookupResult.addresses ? new MatTableDataSource<any>([...this.lookupResult.addresses]) : new MatTableDataSource([]); this.addresses = this.lookupResult.addresses ? new MatTableDataSource<any>([...this.lookupResult.addresses]) : new MatTableDataSource([]);
this.addresses.data = this.lookupResult.addresses ? this.lookupResult.addresses : []; this.addresses.data = this.lookupResult.addresses ? this.lookupResult.addresses : [];
this.addresses.sort = this.sort; this.addresses.sort = this.sort;
this.addresses.sortingDataAccessor = (data, sortHeaderId) => (data[sortHeaderId] && isNaN(data[sortHeaderId])) ? data[sortHeaderId].toLocaleLowerCase() : +data[sortHeaderId]; this.addresses.sortingDataAccessor = (data: any, sortHeaderId: string) => (data[sortHeaderId] && isNaN(data[sortHeaderId])) ? data[sortHeaderId].toLocaleLowerCase() : data[sortHeaderId] ? +data[sortHeaderId] : null;
} }
onCopyNodeURI(payload: string) { onCopyNodeURI(payload: string) {

View File

@ -9,7 +9,7 @@
</div> </div>
</div> </div>
<div fxLayout="column" fxLayoutAlign="stretch center" fxLayout.gt-sm="row" fxLayoutAlign.gt-sm="center stretch" class="w-100 h-93"> <div fxLayout="column" fxLayoutAlign="stretch center" fxLayout.gt-sm="row" fxLayoutAlign.gt-sm="center stretch" class="w-100 h-93">
<mat-card fxLayout="row" fxFlex="95" fxLayoutAlign="start stretch" class="dashboard-card p-24 w-96 h-93"> <mat-card fxLayout="row" fxFlex="95" fxLayoutAlign="start stretch" class="dashboard-card w-96 h-93">
<mat-card-content fxFlex="100" class="dashboard-card-content"> <mat-card-content fxFlex="100" class="dashboard-card-content">
<div [ngSwitch]="card.id" fxLayout="column" fxFlex="100"> <div [ngSwitch]="card.id" fxLayout="column" fxFlex="100">
<rtl-cl-node-info fxFlex="100" *ngSwitchCase="'node'" [information]="information" [showColorFieldSeparately]="false" [ngClass]="{'error-border': flgLoading[0]==='error'}"></rtl-cl-node-info> <rtl-cl-node-info fxFlex="100" *ngSwitchCase="'node'" [information]="information" [showColorFieldSeparately]="false" [ngClass]="{'error-border': flgLoading[0]==='error'}"></rtl-cl-node-info>
@ -34,7 +34,7 @@
</div> </div>
</div> </div>
<div fxLayout="column" fxLayoutAlign="stretch center" fxLayout.gt-sm="row" fxLayoutAlign.gt-sm="center stretch" class="w-100 h-93"> <div fxLayout="column" fxLayoutAlign="stretch center" fxLayout.gt-sm="row" fxLayoutAlign.gt-sm="center stretch" class="w-100 h-93">
<mat-card fxLayout="row" fxFlex="95" fxLayoutAlign="start stretch" class="dashboard-card p-24 w-96 h-93"> <mat-card fxLayout="row" fxFlex="95" fxLayoutAlign="start stretch" class="dashboard-card w-96 h-93">
<mat-card-content fxFlex="100" class="dashboard-card-content"> <mat-card-content fxFlex="100" class="dashboard-card-content">
<div [ngSwitch]="card.id" fxLayout="column" fxFlex="100"> <div [ngSwitch]="card.id" fxLayout="column" fxFlex="100">
<rtl-cl-node-info fxFlex="100" *ngSwitchCase="'node'" [information]="information" [showColorFieldSeparately]="false" [ngClass]="{'error-border': flgLoading[0]==='error'}"></rtl-cl-node-info> <rtl-cl-node-info fxFlex="100" *ngSwitchCase="'node'" [information]="information" [showColorFieldSeparately]="false" [ngClass]="{'error-border': flgLoading[0]==='error'}"></rtl-cl-node-info>

View File

@ -1,4 +1,4 @@
<div fxLayout="column"> <div fxLayout="column" class="padding-gap-x-large">
<div fxLayout="row" fxLayoutAlign="space-between end" fxLayoutAlign.gt-sm="start end"> <div fxLayout="row" fxLayoutAlign="space-between end" fxLayoutAlign.gt-sm="start end">
<mat-form-field fxFlex="48" fxFlex.gt-md="25" fxLayoutAlign="start end" class="mr-2"> <mat-form-field fxFlex="48" fxFlex.gt-md="25" fxLayoutAlign="start end" class="mr-2">
<mat-select [(ngModel)]="selectedAddressType" placeholder="Address Type" name="address_type" tabindex="1"> <mat-select [(ngModel)]="selectedAddressType" placeholder="Address Type" name="address_type" tabindex="1">

View File

@ -13,7 +13,7 @@
<mat-error *ngIf="!transaction.address">Bitcoin address is required.</mat-error> <mat-error *ngIf="!transaction.address">Bitcoin address is required.</mat-error>
</mat-form-field> </mat-form-field>
<mat-form-field fxFlex="30"> <mat-form-field fxFlex="30">
<input matInput [(ngModel)]="transaction.satoshis" placeholder="Amount" name="amount" [type]="flgUseAllBalance ? 'text' : 'number'" step="100" min="0" tabindex="2" required #amount="ngModel" [disabled]="flgUseAllBalance"> <input matInput [(ngModel)]="transaction.satoshis" placeholder="Amount" name="amount" [type]="flgUseAllBalance ? 'text' : 'number'" [step]="100" [min]="0" tabindex="2" required #amount="ngModel" [disabled]="flgUseAllBalance">
<mat-hint *ngIf="flgUseAllBalance">Amount replaced by UTXO balance</mat-hint> <mat-hint *ngIf="flgUseAllBalance">Amount replaced by UTXO balance</mat-hint>
<span matSuffix> {{selAmountUnit}} </span> <span matSuffix> {{selAmountUnit}} </span>
<mat-error *ngIf="!transaction.satoshis">Amount is required.</mat-error> <mat-error *ngIf="!transaction.satoshis">Amount is required.</mat-error>
@ -34,7 +34,7 @@
<div fxFlex="48" fxLayout="row" fxLayoutAlign="start center"> <div fxFlex="48" fxLayout="row" fxLayoutAlign="start center">
<mat-checkbox fxFlex="2" tabindex="7" color="primary" [(ngModel)]="flgMinConf" (change)="flgMinConf ? transaction.feeRate=null : transaction.minconf=null" name="flgMinConf" fxLayoutAlign="stretch start" class="mr-2"></mat-checkbox> <mat-checkbox fxFlex="2" tabindex="7" color="primary" [(ngModel)]="flgMinConf" (change)="flgMinConf ? transaction.feeRate=null : transaction.minconf=null" name="flgMinConf" fxLayoutAlign="stretch start" class="mr-2"></mat-checkbox>
<mat-form-field fxFlex="98"> <mat-form-field fxFlex="98">
<input matInput [(ngModel)]="transaction.minconf" placeholder="Min Confirmation Blocks" type="number" name="blocks" step="1" min="0" tabindex="8" #blocks="ngModel" [required]="flgMinConf" [disabled]="!flgMinConf"> <input matInput [(ngModel)]="transaction.minconf" placeholder="Min Confirmation Blocks" type="number" name="blocks" [step]="1" [min]="0" tabindex="8" #blocks="ngModel" [required]="flgMinConf" [disabled]="!flgMinConf">
<mat-error *ngIf="flgMinConf && !transaction.minconf">Min Confirmation Blocks is required.</mat-error> <mat-error *ngIf="flgMinConf && !transaction.minconf">Min Confirmation Blocks is required.</mat-error>
</mat-form-field> </mat-form-field>
</div> </div>
@ -112,7 +112,7 @@
<div fxFlex.gt-sm="48" fxLayout="row" fxLayoutAlign="start center"> <div fxFlex.gt-sm="48" fxLayout="row" fxLayoutAlign="start center">
<mat-checkbox fxFlex="2" tabindex="6" color="primary" formControlName="flgMinConf" name="flgMinCon" class="mr-2"></mat-checkbox> <mat-checkbox fxFlex="2" tabindex="6" color="primary" formControlName="flgMinConf" name="flgMinCon" class="mr-2"></mat-checkbox>
<mat-form-field fxFlex="98"> <mat-form-field fxFlex="98">
<input matInput formControlName="transactionBlocks" placeholder="Min Confirmation Blocks" type="number" name="blocks" step="1" min="0" tabindex="7" required> <input matInput formControlName="transactionBlocks" placeholder="Min Confirmation Blocks" type="number" name="blocks" [step]="1" [min]="0" tabindex="7" required>
<mat-error *ngIf="sendFundFormGroup.controls.transactionBlocks.errors?.required">Min confirmation blocks is required.</mat-error> <mat-error *ngIf="sendFundFormGroup.controls.transactionBlocks.errors?.required">Min confirmation blocks is required.</mat-error>
</mat-form-field> </mat-form-field>
</div> </div>

View File

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

View File

@ -26,12 +26,12 @@ import * as fromRTLReducer from '../../../store/rtl.reducers';
import * as sha256 from 'sha256'; import * as sha256 from 'sha256';
@Component({ @Component({
selector: 'rtl-cl-on-chain-send', selector: 'rtl-cl-on-chain-send-modal',
templateUrl: './on-chain-send.component.html', templateUrl: './on-chain-send-modal.component.html',
styleUrls: ['./on-chain-send.component.scss'] styleUrls: ['./on-chain-send-modal.component.scss']
}) })
export class CLOnChainSendComponent implements OnInit, OnDestroy { export class CLOnChainSendModalComponent implements OnInit, OnDestroy {
@ViewChild('form', { static: false }) form: any; @ViewChild('form', { static: true }) form: any;
@ViewChild('formSweepAll', { static: false }) formSweepAll: any; @ViewChild('formSweepAll', { static: false }) formSweepAll: any;
@ViewChild('stepper', { static: false }) stepper: MatVerticalStepper; @ViewChild('stepper', { static: false }) stepper: MatVerticalStepper;
public faExclamationTriangle = faExclamationTriangle; public faExclamationTriangle = faExclamationTriangle;
@ -71,7 +71,7 @@ export class CLOnChainSendComponent implements OnInit, OnDestroy {
confirmFormGroup: FormGroup; confirmFormGroup: FormGroup;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject()]; private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
constructor(public dialogRef: MatDialogRef<CLOnChainSendComponent>, @Inject(MAT_DIALOG_DATA) public data: CLOnChainSendFunds, private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private commonService: CommonService, private decimalPipe: DecimalPipe, private actions$: Actions, private formBuilder: FormBuilder, private rtlEffects: RTLEffects, private snackBar: MatSnackBar) {} constructor(public dialogRef: MatDialogRef<CLOnChainSendModalComponent>, @Inject(MAT_DIALOG_DATA) public data: CLOnChainSendFunds, private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private commonService: CommonService, private decimalPipe: DecimalPipe, private actions$: Actions, private formBuilder: FormBuilder, private rtlEffects: RTLEffects, private snackBar: MatSnackBar) {}
ngOnInit() { ngOnInit() {
this.sweepAll = this.data.sweepAll; this.sweepAll = this.data.sweepAll;
@ -133,7 +133,7 @@ export class CLOnChainSendComponent implements OnInit, OnDestroy {
} }
onAuthenticate() { onAuthenticate():boolean|void {
if (!this.passwordFormGroup.controls.password.value) { return true; } if (!this.passwordFormGroup.controls.password.value) { return true; }
this.flgValidated = false; this.flgValidated = false;
this.store.dispatch(new RTLActions.IsAuthorized(sha256(this.passwordFormGroup.controls.password.value))); this.store.dispatch(new RTLActions.IsAuthorized(sha256(this.passwordFormGroup.controls.password.value)));
@ -150,7 +150,7 @@ export class CLOnChainSendComponent implements OnInit, OnDestroy {
}); });
} }
onSendFunds() { onSendFunds():boolean|void {
if(this.invalidValues) { return true; } if(this.invalidValues) { return true; }
this.sendFundError = ''; this.sendFundError = '';
if (this.flgUseAllBalance) { if (this.flgUseAllBalance) {

View File

@ -0,0 +1,5 @@
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="padding-gap-x-large">
<div fxLayout="row">
<button mat-flat-button color="primary" type="button" tabindex="1" (click)="openSendFundsModal()">{{sweepAll ? 'Sweep All' : 'Send Funds'}}</button>
</div>
</div>

View File

@ -0,0 +1,41 @@
import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { CLOnChainSendModalComponent } from '../on-chain-send-modal/on-chain-send-modal.component';
import * as RTLActions from '../../../store/rtl.actions';
import * as fromRTLReducer from '../../../store/rtl.reducers';
@Component({
selector: 'rtl-cl-on-chain-send',
templateUrl: './on-chain-send.component.html',
styleUrls: ['./on-chain-send.component.scss']
})
export class CLOnChainSendComponent implements OnInit, OnDestroy {
public sweepAll = false;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject()];
constructor(private store: Store<fromRTLReducer.RTLState>, private activatedRoute: ActivatedRoute) {}
ngOnInit() {
this.activatedRoute.data.pipe(takeUntil(this.unSubs[0])).subscribe(routeData => this.sweepAll = routeData.sweepAll);
}
openSendFundsModal() {
this.store.dispatch(new RTLActions.OpenAlert({ data: {
sweepAll: this.sweepAll,
component: CLOnChainSendModalComponent
}}));
}
ngOnDestroy() {
this.unSubs.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

View File

@ -5,11 +5,11 @@
<span class="page-title">UTXOs</span> <span class="page-title">UTXOs</span>
</div> </div>
<mat-form-field fxFlex="30"> <mat-form-field fxFlex="30">
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter"> <input matInput (keyup)="applyFilter($event.target)" placeholder="Filter">
</mat-form-field> </mat-form-field>
</div> </div>
<div fxLayout="row" fxLayoutAlign="start start"> <div fxLayout="row" fxLayoutAlign="start start">
<div perfectScrollbar class="table-container" fxFlex="100"> <div [perfectScrollbar] class="table-container" fxFlex="100">
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar> <mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
<table mat-table #table [dataSource]="listTransactions" matSort <table mat-table #table [dataSource]="listTransactions" matSort
[ngClass]="{'overflow-auto error-border': flgLoading[0]==='error','overflow-auto': true}"> [ngClass]="{'overflow-auto error-border': flgLoading[0]==='error','overflow-auto': true}">

View File

@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; import { Component, OnInit, OnDestroy, ViewChild, AfterViewInit } from '@angular/core';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
@ -13,7 +13,6 @@ import { PAGE_SIZE, PAGE_SIZE_OPTIONS, getPaginatorLabel, AlertTypeEnum, DataTyp
import { LoggerService } from '../../../shared/services/logger.service'; import { LoggerService } from '../../../shared/services/logger.service';
import { CommonService } from '../../../shared/services/common.service'; import { CommonService } from '../../../shared/services/common.service';
import * as CLActions from '../../store/cl.actions';
import * as RTLActions from '../../../store/rtl.actions'; import * as RTLActions from '../../../store/rtl.actions';
import * as fromRTLReducer from '../../../store/rtl.reducers'; import * as fromRTLReducer from '../../../store/rtl.reducers';
@ -25,11 +24,12 @@ import * as fromRTLReducer from '../../../store/rtl.reducers';
{ provide: MatPaginatorIntl, useValue: getPaginatorLabel('Transactions') } { provide: MatPaginatorIntl, useValue: getPaginatorLabel('Transactions') }
] ]
}) })
export class CLOnChainTransactionHistoryComponent implements OnInit, OnDestroy { export class CLOnChainTransactionHistoryComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChild(MatSort, { static: true }) sort: MatSort; @ViewChild(MatSort, { static: false }) sort: MatSort|undefined;
@ViewChild(MatPaginator, {static: true}) paginator: MatPaginator; @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator|undefined;
faMoneyBillWave = faMoneyBillWave; faMoneyBillWave = faMoneyBillWave;
public displayedColumns = []; public displayedColumns: any[] = [];
public transactionsData: Transaction[] = [];
public listTransactions: any; public listTransactions: any;
public flgLoading: Array<Boolean | 'error'> = [true]; public flgLoading: Array<Boolean | 'error'> = [true];
public flgSticky = false; public flgSticky = false;
@ -57,7 +57,6 @@ export class CLOnChainTransactionHistoryComponent implements OnInit, OnDestroy {
} }
ngOnInit() { ngOnInit() {
// this.store.dispatch(new CLActions.FetchTransactions());
this.store.select('cl') this.store.select('cl')
.pipe(takeUntil(this.unsub[0])) .pipe(takeUntil(this.unsub[0]))
.subscribe((rtlStore) => { .subscribe((rtlStore) => {
@ -66,8 +65,9 @@ export class CLOnChainTransactionHistoryComponent implements OnInit, OnDestroy {
this.flgLoading[0] = 'error'; this.flgLoading[0] = 'error';
} }
}); });
if (rtlStore.transactions) { this.transactionsData = rtlStore.transactions;
this.loadTransactionsTable(rtlStore.transactions); if (this.transactionsData.length > 0) {
this.loadTransactionsTable(this.transactionsData);
} }
if (this.flgLoading[0] !== 'error') { if (this.flgLoading[0] !== 'error') {
this.flgLoading[0] = (rtlStore.transactions) ? false : true; this.flgLoading[0] = (rtlStore.transactions) ? false : true;
@ -77,8 +77,14 @@ export class CLOnChainTransactionHistoryComponent implements OnInit, OnDestroy {
} }
applyFilter(selFilter: string) { ngAfterViewInit() {
this.listTransactions.filter = selFilter; if (this.transactionsData.length > 0) {
this.loadTransactionsTable(this.transactionsData);
}
}
applyFilter(selFilter: any) {
this.listTransactions.filter = selFilter.value;
} }
onTransactionClick(selTransaction: Transaction, event: any) { onTransactionClick(selTransaction: Transaction, event: any) {
@ -100,7 +106,7 @@ export class CLOnChainTransactionHistoryComponent implements OnInit, OnDestroy {
loadTransactionsTable(transactions) { loadTransactionsTable(transactions) {
this.listTransactions = new MatTableDataSource<Transaction>([...transactions]); this.listTransactions = new MatTableDataSource<Transaction>([...transactions]);
this.listTransactions.sort = this.sort; this.listTransactions.sort = this.sort;
this.listTransactions.sortingDataAccessor = (data, sortHeaderId) => (data[sortHeaderId] && isNaN(data[sortHeaderId])) ? data[sortHeaderId].toLocaleLowerCase() : +data[sortHeaderId]; this.listTransactions.sortingDataAccessor = (data: any, sortHeaderId: string) => (data[sortHeaderId] && isNaN(data[sortHeaderId])) ? data[sortHeaderId].toLocaleLowerCase() : data[sortHeaderId] ? +data[sortHeaderId] : null;
this.listTransactions.paginator = this.paginator; this.listTransactions.paginator = this.paginator;
this.logger.info(this.listTransactions); this.logger.info(this.listTransactions);
} }

View File

@ -1,4 +1,4 @@
<div fxLayout="row" fxLayoutAlign="start center" class="padding-gap-x page-title-container"> <div fxLayout="row" fxLayoutAlign="start center" class="page-title-container">
<fa-icon [icon]="faChartPie" class="page-title-img mr-1"></fa-icon> <fa-icon [icon]="faChartPie" class="page-title-img mr-1"></fa-icon>
<span class="page-title">On-chain Balance</span> <span class="page-title">On-chain Balance</span>
</div> </div>
@ -9,36 +9,21 @@
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
</div> </div>
<div fxLayout="row" fxLayoutAlign="start center" class="padding-gap-x page-title-container"> <div fxLayout="row" fxLayoutAlign="start center" class="page-title-container">
<fa-icon [icon]="faExchangeAlt" class="page-title-img mr-1"></fa-icon> <fa-icon [icon]="faExchangeAlt" class="page-title-img mr-1"></fa-icon>
<span class="page-title">On-chain Transactions</span> <span class="page-title">On-chain Transactions</span>
</div> </div>
<div fxLayout="column" class="padding-gap-x"> <div fxLayout="column" class="padding-gap-x">
<mat-card> <mat-card>
<mat-card-content fxLayout="column"> <mat-card-content fxLayout="column">
<mat-tab-group> <nav mat-tab-nav-bar>
<mat-tab label="Receive"> <div role="tab" mat-tab-link *ngFor="let link of links" class="mat-tab-label" [active]="activeLink === link.link" (click)="activeLink = link.link" routerLink="{{link.link}}">{{link.name}}</div>
<rtl-cl-on-chain-receive></rtl-cl-on-chain-receive> </nav>
</mat-tab> <div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="padding-gap-x-large mt-2">
<mat-tab label="Send"> <router-outlet></router-outlet>
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="padding-gap"> </div>
<div fxLayout="row">
<button mat-flat-button color="primary" type="button" tabindex="1" (click)="openSendFundsModal(false)">Send Funds</button>
</div>
</div>
</mat-tab>
<mat-tab label="Sweep All">
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="padding-gap">
<div fxLayout="row">
<button mat-flat-button color="primary" type="button" tabindex="3" (click)="openSendFundsModal(true)">Sweep All</button>
</div>
</div>
</mat-tab>
</mat-tab-group>
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="padding-gap-x-large"> <div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="padding-gap-x-large">
<div fxLayout="row"> <rtl-cl-on-chain-transaction-history fxLayout="row" fxFlex="100"></rtl-cl-on-chain-transaction-history>
<rtl-cl-on-chain-transaction-history fxLayout="row" fxFlex="100"></rtl-cl-on-chain-transaction-history>
</div>
</div> </div>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>

View File

@ -1,13 +1,14 @@
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router, ResolveEnd } from '@angular/router';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil, filter } from 'rxjs/operators';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { faExchangeAlt, faChartPie } from '@fortawesome/free-solid-svg-icons'; import { faExchangeAlt, faChartPie } from '@fortawesome/free-solid-svg-icons';
import { CLOnChainSendModalComponent } from './on-chain-send-modal/on-chain-send-modal.component';
import { SelNodeChild } from '../../shared/models/RTLconfig'; import { SelNodeChild } from '../../shared/models/RTLconfig';
import * as RTLActions from '../../store/rtl.actions'; import * as RTLActions from '../../store/rtl.actions';
import * as fromRTLReducer from '../../store/rtl.reducers'; import * as fromRTLReducer from '../../store/rtl.reducers';
import { CLOnChainSendComponent } from './on-chain-send-modal/on-chain-send.component';
@Component({ @Component({
selector: 'rtl-cl-on-chain', selector: 'rtl-cl-on-chain',
@ -19,11 +20,20 @@ export class CLOnChainComponent implements OnInit, OnDestroy {
public faExchangeAlt = faExchangeAlt; public faExchangeAlt = faExchangeAlt;
public faChartPie = faChartPie; public faChartPie = faChartPie;
public balances = [{title: 'Total Balance', dataValue: 0}, {title: 'Confirmed', dataValue: 0}, {title: 'Unconfirmed', dataValue: 0}]; public balances = [{title: 'Total Balance', dataValue: 0}, {title: 'Confirmed', dataValue: 0}, {title: 'Unconfirmed', dataValue: 0}];
public links = [{link: 'receive', name: 'Receive'}, {link: 'send', name: 'Send'}, {link: 'sweep', name: 'Sweep All'}];
public activeLink = this.links[0].link;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()]; private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
constructor(private store: Store<fromRTLReducer.RTLState>) {} constructor(private store: Store<fromRTLReducer.RTLState>, private router: Router) {}
ngOnInit() { ngOnInit() {
let linkFound = this.links.find(link => this.router.url.includes(link.link));
this.activeLink = linkFound ? linkFound.link : this.links[0].link;
this.router.events.pipe(takeUntil(this.unSubs[0]), filter(e => e instanceof ResolveEnd))
.subscribe((value: ResolveEnd) => {
let linkFound = this.links.find(link => value.urlAfterRedirects.includes(link.link));
this.activeLink = linkFound ? linkFound.link : this.links[0].link;
});
this.store.select('cl') this.store.select('cl')
.pipe(takeUntil(this.unSubs[1])) .pipe(takeUntil(this.unSubs[1]))
.subscribe((rtlStore) => { .subscribe((rtlStore) => {
@ -35,7 +45,7 @@ export class CLOnChainComponent implements OnInit, OnDestroy {
openSendFundsModal(sweepAll: boolean) { openSendFundsModal(sweepAll: boolean) {
this.store.dispatch(new RTLActions.OpenAlert({ data: { this.store.dispatch(new RTLActions.OpenAlert({ data: {
sweepAll: sweepAll, sweepAll: sweepAll,
component: CLOnChainSendComponent component: CLOnChainSendModalComponent
}})); }}));
} }

View File

@ -5,7 +5,7 @@
<input matInput (keyup)="applyFilter()" [(ngModel)]="selFilter" name="filter" placeholder="Filter"> <input matInput (keyup)="applyFilter()" [(ngModel)]="selFilter" name="filter" placeholder="Filter">
</mat-form-field> </mat-form-field>
</div> </div>
<div perfectScrollbar fxLayout="row" fxLayoutAlign="start center" fxFlex="100" class="table-container w-100"> <div [perfectScrollbar] fxLayout="row" fxLayoutAlign="start center" fxFlex="100" class="table-container w-100">
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar> <mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
<table mat-table #table [dataSource]="channels" matSort [ngClass]="{'overflow-auto error-border': flgLoading[0]==='error','overflow-auto': true}"> <table mat-table #table [dataSource]="channels" matSort [ngClass]="{'overflow-auto error-border': flgLoading[0]==='error','overflow-auto': true}">
<ng-container matColumnDef="short_channel_id"> <ng-container matColumnDef="short_channel_id">

View File

@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; import { Component, OnInit, OnDestroy, ViewChild, AfterViewInit } from '@angular/core';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { take, takeUntil } from 'rxjs/operators'; import { take, takeUntil } from 'rxjs/operators';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
@ -27,20 +27,20 @@ import * as fromRTLReducer from '../../../../../store/rtl.reducers';
{ provide: MatPaginatorIntl, useValue: getPaginatorLabel('Channels') } { provide: MatPaginatorIntl, useValue: getPaginatorLabel('Channels') }
] ]
}) })
export class CLChannelOpenTableComponent implements OnInit, OnDestroy { export class CLChannelOpenTableComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChild(MatSort, { static: true }) sort: MatSort; @ViewChild(MatSort, { static: false }) sort: MatSort|undefined;
@ViewChild(MatPaginator, {static: true}) paginator: MatPaginator; @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator|undefined;
public faEye = faEye; public faEye = faEye;
public faEyeSlash = faEyeSlash public faEyeSlash = faEyeSlash
public totalBalance = 0; public totalBalance = 0;
public displayedColumns = []; public displayedColumns: any[] = [];
public channelsData: Channel[] = [];
public channels: any; public channels: any;
public myChanPolicy: any = {}; public myChanPolicy: any = {};
public information: GetInfo = {}; public information: GetInfo = {};
public numPeers = -1; public numPeers = -1;
public feeRateTypes = FEE_RATE_TYPES; public feeRateTypes = FEE_RATE_TYPES;
public flgLoading: Array<Boolean | 'error'> = [true]; public flgLoading: Array<Boolean | 'error'> = [true];
public selectedFilter = '';
public selFilter = ''; public selFilter = '';
public flgSticky = false; public flgSticky = false;
public pageSize = PAGE_SIZE; public pageSize = PAGE_SIZE;
@ -78,8 +78,9 @@ export class CLChannelOpenTableComponent implements OnInit, OnDestroy {
this.information = rtlStore.information; this.information = rtlStore.information;
this.numPeers = (rtlStore.peers && rtlStore.peers.length) ? rtlStore.peers.length : 0; this.numPeers = (rtlStore.peers && rtlStore.peers.length) ? rtlStore.peers.length : 0;
this.totalBalance = rtlStore.balance.totalBalance; this.totalBalance = rtlStore.balance.totalBalance;
if (rtlStore.allChannels) { this.channelsData = rtlStore.allChannels.filter(channel => channel.state === 'CHANNELD_NORMAL' && channel.connected);
this.loadChannelsTable(rtlStore.allChannels.filter(channel => channel.state === 'CHANNELD_NORMAL' && channel.connected)); if (this.channelsData.length > 0) {
this.loadChannelsTable(this.channelsData);
} }
if (this.flgLoading[0] !== 'error') { if (this.flgLoading[0] !== 'error') {
this.flgLoading[0] = (rtlStore.allChannels) ? false : true; this.flgLoading[0] = (rtlStore.allChannels) ? false : true;
@ -88,11 +89,17 @@ export class CLChannelOpenTableComponent implements OnInit, OnDestroy {
}); });
} }
ngAfterViewInit() {
if (this.channelsData.length > 0) {
this.loadChannelsTable(this.channelsData);
}
}
onViewRemotePolicy(selChannel: Channel) { onViewRemotePolicy(selChannel: Channel) {
this.store.dispatch(new CLActions.ChannelLookup({shortChannelID: selChannel.short_channel_id, showError: true})); this.store.dispatch(new CLActions.ChannelLookup({shortChannelID: selChannel.short_channel_id, showError: true}));
this.clEffects.setLookupCL this.clEffects.setLookupCL
.pipe(take(1)) .pipe(take(1))
.subscribe((resLookup: ChannelEdge[]) => { .subscribe((resLookup: ChannelEdge[]):boolean|void => {
if(resLookup.length === 0) { return false; } if(resLookup.length === 0) { return false; }
let remoteNode: ChannelEdge = {}; let remoteNode: ChannelEdge = {};
if(resLookup[0].source !== this.information.id) { if(resLookup[0].source !== this.information.id) {
@ -208,7 +215,6 @@ export class CLChannelOpenTableComponent implements OnInit, OnDestroy {
} }
applyFilter() { applyFilter() {
this.selectedFilter = this.selFilter;
this.channels.filter = this.selFilter; this.channels.filter = this.selFilter;
} }
@ -235,7 +241,7 @@ export class CLChannelOpenTableComponent implements OnInit, OnDestroy {
return newChannel.includes(fltr.toLowerCase()); return newChannel.includes(fltr.toLowerCase());
}; };
this.channels.sort = this.sort; this.channels.sort = this.sort;
this.channels.sortingDataAccessor = (data, sortHeaderId) => (data[sortHeaderId] && isNaN(data[sortHeaderId])) ? data[sortHeaderId].toLocaleLowerCase() : +data[sortHeaderId]; this.channels.sortingDataAccessor = (data: any, sortHeaderId: string) => (data[sortHeaderId] && isNaN(data[sortHeaderId])) ? data[sortHeaderId].toLocaleLowerCase() : data[sortHeaderId] ? +data[sortHeaderId] : null;
this.channels.paginator = this.paginator; this.channels.paginator = this.paginator;
this.logger.info(this.channels); this.logger.info(this.channels);
} }

View File

@ -5,7 +5,7 @@
<input matInput (keyup)="applyFilter()" [(ngModel)]="selFilter" name="filter" placeholder="Filter"> <input matInput (keyup)="applyFilter()" [(ngModel)]="selFilter" name="filter" placeholder="Filter">
</mat-form-field> </mat-form-field>
</div> </div>
<div perfectScrollbar fxLayout="row" fxLayoutAlign="start center" fxFlex="100" class="table-container w-100"> <div [perfectScrollbar] fxLayout="row" fxLayoutAlign="start center" fxFlex="100" class="table-container w-100">
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar> <mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
<table mat-table #table [dataSource]="channels" matSort [ngClass]="{'overflow-auto error-border': flgLoading[0]==='error','overflow-auto': true}"> <table mat-table #table [dataSource]="channels" matSort [ngClass]="{'overflow-auto error-border': flgLoading[0]==='error','overflow-auto': true}">
<ng-container matColumnDef="short_channel_id"> <ng-container matColumnDef="short_channel_id">

View File

@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; import { Component, OnInit, OnDestroy, ViewChild, AfterViewInit } from '@angular/core';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
@ -26,19 +26,19 @@ import * as fromRTLReducer from '../../../../../store/rtl.reducers';
{ provide: MatPaginatorIntl, useValue: getPaginatorLabel('Channels') } { provide: MatPaginatorIntl, useValue: getPaginatorLabel('Channels') }
] ]
}) })
export class CLChannelPendingTableComponent implements OnInit, OnDestroy { export class CLChannelPendingTableComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChild(MatSort, { static: true }) sort: MatSort; @ViewChild(MatSort, { static: false }) sort: MatSort|undefined;
@ViewChild(MatPaginator, {static: true}) paginator: MatPaginator; @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator|undefined;
public isCompatibleVersion = false; public isCompatibleVersion = false;
public totalBalance = 0; public totalBalance = 0;
public displayedColumns = []; public displayedColumns: any[] = [];
public channelsData: Channel[] = [];
public channels: any; public channels: any;
public myChanPolicy: any = {}; public myChanPolicy: any = {};
public information: GetInfo = {}; public information: GetInfo = {};
public numPeers = -1; public numPeers = -1;
public feeRateTypes = FEE_RATE_TYPES; public feeRateTypes = FEE_RATE_TYPES;
public flgLoading: Array<Boolean | 'error'> = [true]; public flgLoading: Array<Boolean | 'error'> = [true];
public selectedFilter = '';
public selFilter = ''; public selFilter = '';
public flgSticky = false; public flgSticky = false;
public pageSize = PAGE_SIZE; public pageSize = PAGE_SIZE;
@ -79,8 +79,9 @@ export class CLChannelPendingTableComponent implements OnInit, OnDestroy {
} }
this.numPeers = (rtlStore.peers && rtlStore.peers.length) ? rtlStore.peers.length : 0; this.numPeers = (rtlStore.peers && rtlStore.peers.length) ? rtlStore.peers.length : 0;
this.totalBalance = rtlStore.balance.totalBalance; this.totalBalance = rtlStore.balance.totalBalance;
if (rtlStore.allChannels) { this.channelsData = rtlStore.allChannels.filter(channel => !(channel.state === 'CHANNELD_NORMAL' && channel.connected));
this.loadChannelsTable(rtlStore.allChannels.filter(channel => !(channel.state === 'CHANNELD_NORMAL' && channel.connected))); if (this.channelsData.length > 0) {
this.loadChannelsTable(this.channelsData);
} }
if (this.flgLoading[0] !== 'error') { if (this.flgLoading[0] !== 'error') {
this.flgLoading[0] = (rtlStore.allChannels) ? false : true; this.flgLoading[0] = (rtlStore.allChannels) ? false : true;
@ -89,8 +90,13 @@ export class CLChannelPendingTableComponent implements OnInit, OnDestroy {
}); });
} }
ngAfterViewInit() {
if (this.channelsData.length > 0) {
this.loadChannelsTable(this.channelsData);
}
}
applyFilter() { applyFilter() {
this.selectedFilter = this.selFilter;
this.channels.filter = this.selFilter; this.channels.filter = this.selFilter;
} }
@ -135,7 +141,7 @@ export class CLChannelPendingTableComponent implements OnInit, OnDestroy {
return newChannel.includes(fltr.toLowerCase()); return newChannel.includes(fltr.toLowerCase());
}; };
this.channels.sort = this.sort; this.channels.sort = this.sort;
this.channels.sortingDataAccessor = (data, sortHeaderId) => (data[sortHeaderId] && isNaN(data[sortHeaderId])) ? data[sortHeaderId].toLocaleLowerCase() : +data[sortHeaderId]; this.channels.sortingDataAccessor = (data: any, sortHeaderId: string) => (data[sortHeaderId] && isNaN(data[sortHeaderId])) ? data[sortHeaderId].toLocaleLowerCase() : data[sortHeaderId] ? +data[sortHeaderId] : null;
this.channels.paginator = this.paginator; this.channels.paginator = this.paginator;
this.logger.info(this.channels); this.logger.info(this.channels);
} }

View File

@ -1,16 +1,22 @@
<div fxLayout="column" fxFlex="100" class="mt-2 bordered-box"> <div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="padding-gap-x">
<mat-tab-group> <div fxLayout="row">
<mat-tab> <button mat-flat-button color="primary" (click)="onOpenChannel()" type="submit" tabindex="1">Open Channel</button>
<ng-template mat-tab-label> </div>
<span matBadge="{{openChannels}}" matBadgeOverlap="false" class="tab-badge">Open</span> <div fxLayout="column" fxFlex="100" class="mt-2 bordered-box">
</ng-template> <mat-tab-group [(selectedIndex)]="activeLink" (selectedTabChange)="onSelectedTabChange($event)">
<rtl-cl-channel-open-table></rtl-cl-channel-open-table> <mat-tab>
</mat-tab> <ng-template mat-tab-label>
<mat-tab> <span matBadge="{{openChannels}}" matBadgeOverlap="false" class="tab-badge">Open</span>
<ng-template mat-tab-label> </ng-template>
<span matBadge="{{pendingChannels}}" matBadgeOverlap="false" class="tab-badge">Pending/Inactive</span> </mat-tab>
</ng-template> <mat-tab>
<rtl-cl-channel-pending-table></rtl-cl-channel-pending-table> <ng-template mat-tab-label>
</mat-tab> <span matBadge="{{pendingChannels}}" matBadgeOverlap="false" class="tab-badge">Pending/Inactive</span>
</mat-tab-group> </ng-template>
</mat-tab>
</mat-tab-group>
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="padding-gap-x-large">
<router-outlet></router-outlet>
</div>
</div>
</div> </div>

View File

@ -1,9 +1,16 @@
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router, ResolveEnd } from '@angular/router';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil, filter } from 'rxjs/operators';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { CLOpenChannelComponent } from '../open-channel-modal/open-channel.component';
import { CommonService } from '../../../../shared/services/common.service';
import { LoggerService } from '../../../../shared/services/logger.service'; import { LoggerService } from '../../../../shared/services/logger.service';
import { GetInfo, Peer, Transaction } from '../../../../shared/models/clModels';
import { SelNodeChild } from '../../../../shared/models/RTLconfig';
import * as RTLActions from '../../../../store/rtl.actions';
import * as fromRTLReducer from '../../../../store/rtl.reducers'; import * as fromRTLReducer from '../../../../store/rtl.reducers';
@Component({ @Component({
@ -14,13 +21,25 @@ import * as fromRTLReducer from '../../../../store/rtl.reducers';
export class CLChannelsTablesComponent implements OnInit, OnDestroy { export class CLChannelsTablesComponent implements OnInit, OnDestroy {
public openChannels = 0; public openChannels = 0;
public pendingChannels = 0; public pendingChannels = 0;
private unSubs: Array<Subject<void>> = [new Subject()]; public selNode: SelNodeChild = {};
public information: GetInfo = {};
public peers: Peer[] = [];
public transactions: Transaction[] = [];
public totalBalance = 0;
public links = [{link: 'open', name: 'Open'}, {link: 'pending', name: 'Pending/Inactive'}];
public activeLink = 0;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject()];
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>) {} constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private commonService: CommonService, private router: Router) {}
ngOnInit() { ngOnInit() {
this.activeLink = this.links.findIndex(link => link.link === this.router.url.substring(this.router.url.lastIndexOf('/') + 1));
this.router.events.pipe(takeUntil(this.unSubs[0]), filter(e => e instanceof ResolveEnd))
.subscribe((value: ResolveEnd) => {
this.activeLink = this.links.findIndex(link => link.link === value.urlAfterRedirects.substring(value.urlAfterRedirects.lastIndexOf('/') + 1));
});
this.store.select('cl') this.store.select('cl')
.pipe(takeUntil(this.unSubs[0])) .pipe(takeUntil(this.unSubs[1]))
.subscribe((rtlStore) => { .subscribe((rtlStore) => {
if (rtlStore.allChannels && rtlStore.allChannels.length) { if (rtlStore.allChannels && rtlStore.allChannels.length) {
this.openChannels = 0; this.openChannels = 0;
@ -36,10 +55,35 @@ export class CLChannelsTablesComponent implements OnInit, OnDestroy {
this.openChannels = 0; this.openChannels = 0;
this.pendingChannels = 0; this.pendingChannels = 0;
} }
this.selNode = rtlStore.nodeSettings;
this.information = rtlStore.information;
this.peers = rtlStore.peers;
this.transactions = this.commonService.sortAscByKey(rtlStore.transactions.filter(tran => tran.status === 'confirmed'), 'value');
this.totalBalance = rtlStore.balance.totalBalance;
this.logger.info(rtlStore); this.logger.info(rtlStore);
}); });
} }
onOpenChannel() {
const peerToAddChannelMessage = {
peers: this.peers,
information: this.information,
balance: this.totalBalance,
transactions: this.transactions,
isCompatibleVersion: this.commonService.isVersionCompatible(this.information.version, '0.9.0') &&
this.commonService.isVersionCompatible(this.information.api_version, '0.4.0')
};
this.store.dispatch(new RTLActions.OpenAlert({ data: {
alertTitle: 'Open Channel',
message: peerToAddChannelMessage,
component: CLOpenChannelComponent
}}));
}
onSelectedTabChange(event) {
this.router.navigateByUrl('/cl/connections/channels/' + this.links[event.index].link);
}
ngOnDestroy() { ngOnDestroy() {
this.unSubs.forEach(completeSub => { this.unSubs.forEach(completeSub => {
completeSub.next(); completeSub.next();

View File

@ -19,7 +19,56 @@
</mat-form-field> </mat-form-field>
</div> </div>
<ng-container *ngTemplateOutlet="peerDetailsExpansionBlock"></ng-container> <ng-container *ngTemplateOutlet="peerDetailsExpansionBlock"></ng-container>
<ng-container *ngTemplateOutlet="openChannelBlock"></ng-container> <div fxLayout="column">
<div fxLayout="row" fxFlex="100" fxLayoutAlign="space-between center">
<mat-form-field fxFlex="70" fxLayoutAlign="start end">
<input matInput [(ngModel)]="fundingAmount" placeholder="Amount" type="number" [step]="1000" [min]="1" [max]="totalBalance" tabindex="1" required name="amount" #amount="ngModel" [disabled]="flgUseAllBalance">
<mat-hint>Remaining Bal: {{totalBalance - ((fundingAmount) ? fundingAmount : 0) | number}}{{flgUseAllBalance ? '. Amount replaced by UTXO balance' : ''}}</mat-hint>
<span matSuffix> {{information?.smaller_currency_unit}} </span>
<mat-error *ngIf="amount.errors?.required || !fundingAmount">Amount is required.</mat-error>
<mat-error *ngIf="amount.errors?.max">Amount must be less than or equal to {{totalBalance}}.</mat-error>
</mat-form-field>
<div fxFlex="25" fxLayoutAlign="start center">
<mat-slide-toggle tabindex="2" color="primary" [(ngModel)]="isPrivate" name="isPrivate">Private Channel</mat-slide-toggle>
</div>
</div>
<mat-expansion-panel class="flat-expansion-panel mt-2" expanded="false" (closed)="onAdvancedPanelToggle(true)" (opened)="onAdvancedPanelToggle(false)">
<mat-expansion-panel-header>
<mat-panel-title>
<span>{{advancedTitle}}</span>
</mat-panel-title>
</mat-expansion-panel-header>
<div fxLayout="column" fxFlex="100" fxLayoutAlign="start stretch">
<div fxLayout="row" fxFlex="100" fxLayoutAlign="space-between center">
<mat-form-field fxFlex="48" fxLayoutAlign="start end">
<mat-select tabindex="4" placeholder="Fee Rate" [(value)]="selFeeRate" [disabled]="flgMinConf">
<mat-option *ngFor="let feeRateType of feeRateTypes" [value]="feeRateType.feeRateId">
{{feeRateType.feeRateType}}
</mat-option>
</mat-select>
</mat-form-field>
<div fxFlex="48" fxLayout="row" fxLayoutAlign="start center">
<mat-checkbox fxFlex="2" tabindex="5" color="primary" [(ngModel)]="flgMinConf" (change)="flgMinConf ? selFeeRate=null : minConfValue=null" name="flgMinConf" fxLayoutAlign="stretch start" class="mr-2"></mat-checkbox>
<mat-form-field fxFlex="98">
<input matInput [(ngModel)]="minConfValue" placeholder="Min Confirmation Blocks" type="number" name="blocks" [step]="1" [min]="0" tabindex="8" #blocks="ngModel" [required]="flgMinConf" [disabled]="!flgMinConf">
<mat-error *ngIf="flgMinConf && !minConfValue">Min Confirmation Blocks is required.</mat-error>
</mat-form-field>
</div>
</div>
<div fxLayout="row" fxFlex="100" fxLayoutAlign="space-between center" *ngIf="isCompatibleVersion">
<mat-form-field fxFlex="48" fxLayoutAlign="start end">
<mat-select tabindex="6" placeholder="Coin Selection" (selectionChange)="onUTXOSelectionChange($event)" [(value)]="selUTXOs" multiple>
<mat-select-trigger>{{totalSelectedUTXOAmount | number}} Sats ({{selUTXOs.length > 1 ? selUTXOs.length + ' UTXOs' : '1 UTXO'}})</mat-select-trigger>
<mat-option *ngFor="let transaction of transactions" [value]="transaction">{{transaction.value | number}} Sats</mat-option>
</mat-select>
</mat-form-field>
<mat-slide-toggle fxFlex="48" tabindex="7" color="primary" [(ngModel)]="flgUseAllBalance" (change)="onUTXOAllBalanceChange()" name="flgUseAllBalance" matTooltip="Use selected UTXOs balance as the amount to be sent. Final amount sent will be less the mining fee." matTooltipPosition="above" [disabled]="selUTXOs.length < 1">
Use selected UTXOs balance
</mat-slide-toggle>
</div>
</div>
</mat-expansion-panel>
</div>
<div fxFlex="100" class="alert alert-danger mt-1" *ngIf="channelConnectionError !== ''"> <div fxFlex="100" class="alert alert-danger mt-1" *ngIf="channelConnectionError !== ''">
<fa-icon [icon]="faExclamationTriangle" class="mr-1 alert-icon"></fa-icon> <fa-icon [icon]="faExclamationTriangle" class="mr-1 alert-icon"></fa-icon>
<span *ngIf="channelConnectionError !== ''">{{channelConnectionError}}</span> <span *ngIf="channelConnectionError !== ''">{{channelConnectionError}}</span>
@ -61,55 +110,3 @@
</div> </div>
</mat-expansion-panel> </mat-expansion-panel>
</ng-template> </ng-template>
<ng-template #openChannelBlock>
<form fxLayout="column" #form="ngForm">
<div fxLayout="row" fxFlex="100" fxLayoutAlign="space-between center">
<mat-form-field fxFlex="70" fxLayoutAlign="start end">
<input matInput [(ngModel)]="fundingAmount" placeholder="Amount" type="number" step="1000" min="1" max="{{totalBalance}}" tabindex="1" required name="amount" #amount="ngModel" [disabled]="flgUseAllBalance">
<mat-hint>Remaining Bal: {{totalBalance - ((fundingAmount) ? fundingAmount : 0) | number}}{{flgUseAllBalance ? '. Amount replaced by UTXO balance' : ''}}</mat-hint>
<span matSuffix> {{information?.smaller_currency_unit}} </span>
<mat-error *ngIf="amount.errors?.required || !fundingAmount">Amount is required.</mat-error>
<mat-error *ngIf="amount.errors?.max">Amount must be less than or equal to {{totalBalance}}.</mat-error>
</mat-form-field>
<div fxFlex="25" fxLayoutAlign="start center">
<mat-slide-toggle tabindex="2" color="primary" [(ngModel)]="isPrivate" name="isPrivate">Private Channel</mat-slide-toggle>
</div>
</div>
<mat-expansion-panel class="flat-expansion-panel mt-2" expanded="false" (closed)="onAdvancedPanelToggle(true)" (opened)="onAdvancedPanelToggle(false)">
<mat-expansion-panel-header>
<mat-panel-title>
<span>{{advancedTitle}}</span>
</mat-panel-title>
</mat-expansion-panel-header>
<div fxLayout="column" fxFlex="100" fxLayoutAlign="start stretch">
<div fxLayout="row" fxFlex="100" fxLayoutAlign="space-between center">
<mat-form-field fxFlex="48" fxLayoutAlign="start end">
<mat-select tabindex="4" placeholder="Fee Rate" [(value)]="selFeeRate" [disabled]="flgMinConf">
<mat-option *ngFor="let feeRateType of feeRateTypes" [value]="feeRateType.feeRateId">
{{feeRateType.feeRateType}}
</mat-option>
</mat-select>
</mat-form-field>
<div fxFlex="48" fxLayout="row" fxLayoutAlign="start center">
<mat-checkbox fxFlex="2" tabindex="5" color="primary" [(ngModel)]="flgMinConf" (change)="flgMinConf ? selFeeRate=null : minConfValue=null" name="flgMinConf" fxLayoutAlign="stretch start" class="mr-2"></mat-checkbox>
<mat-form-field fxFlex="98">
<input matInput [(ngModel)]="minConfValue" placeholder="Min Confirmation Blocks" type="number" name="blocks" step="1" min="0" tabindex="8" #blocks="ngModel" [required]="flgMinConf" [disabled]="!flgMinConf">
<mat-error *ngIf="flgMinConf && !minConfValue">Min Confirmation Blocks is required.</mat-error>
</mat-form-field>
</div>
</div>
<div fxLayout="row" fxFlex="100" fxLayoutAlign="space-between center" *ngIf="isCompatibleVersion">
<mat-form-field fxFlex="48" fxLayoutAlign="start end">
<mat-select tabindex="6" placeholder="Coin Selection" (selectionChange)="onUTXOSelectionChange($event)" [(value)]="selUTXOs" multiple>
<mat-select-trigger>{{totalSelectedUTXOAmount | number}} Sats ({{selUTXOs.length > 1 ? selUTXOs.length + ' UTXOs' : '1 UTXO'}})</mat-select-trigger>
<mat-option *ngFor="let transaction of transactions" [value]="transaction">{{transaction.value | number}} Sats</mat-option>
</mat-select>
</mat-form-field>
<mat-slide-toggle fxFlex="48" tabindex="7" color="primary" [(ngModel)]="flgUseAllBalance" (change)="onUTXOAllBalanceChange()" name="flgUseAllBalance" matTooltip="Use selected UTXOs balance as the amount to be sent. Final amount sent will be less the mining fee." matTooltipPosition="above" [disabled]="selUTXOs.length < 1">
Use selected UTXOs balance
</mat-slide-toggle>
</div>
</div>
</mat-expansion-panel>
</form>
</ng-template>

View File

@ -22,7 +22,7 @@ import * as fromRTLReducer from '../../../../store/rtl.reducers';
styleUrls: ['./open-channel.component.scss'] styleUrls: ['./open-channel.component.scss']
}) })
export class CLOpenChannelComponent implements OnInit, OnDestroy { export class CLOpenChannelComponent implements OnInit, OnDestroy {
@ViewChild('form', { static: false }) form: any; @ViewChild('form', { static: true }) form: any;
public selectedPeer = new FormControl(); public selectedPeer = new FormControl();
public faExclamationTriangle = faExclamationTriangle; public faExclamationTriangle = faExclamationTriangle;
public alertTitle: string; public alertTitle: string;
@ -159,7 +159,7 @@ export class CLOpenChannelComponent implements OnInit, OnDestroy {
} }
} }
onOpenChannel() { onOpenChannel():boolean|void {
if ((!this.peer && !this.selectedPubkey) || (!this.fundingAmount || ((this.totalBalance - this.fundingAmount) < 0) || (this.flgMinConf && !this.minConfValue))) { return true; } if ((!this.peer && !this.selectedPubkey) || (!this.fundingAmount || ((this.totalBalance - this.fundingAmount) < 0) || (this.flgMinConf && !this.minConfValue))) { return true; }
let newChannel = { peerId: ((!this.peer || !this.peer.id) ? this.selectedPubkey : this.peer.id), satoshis: (this.flgUseAllBalance) ? 'all' : this.fundingAmount.toString(), announce: !this.isPrivate, feeRate: this.selFeeRate, minconf: this.flgMinConf ? this.minConfValue : null }; let newChannel = { peerId: ((!this.peer || !this.peer.id) ? this.selectedPubkey : this.peer.id), satoshis: (this.flgUseAllBalance) ? 'all' : this.fundingAmount.toString(), announce: !this.isPrivate, feeRate: this.selFeeRate, minconf: this.flgMinConf ? this.minConfValue : null };
if (this.selUTXOs.length && this.selUTXOs.length > 0) { if (this.selUTXOs.length && this.selUTXOs.length > 0) {

View File

@ -31,7 +31,7 @@
<div fxLayout="column" fxLayout.gt-sm="row wrap" fxFlex="100" fxLayoutAlign="space-between stretch"> <div fxLayout="column" fxLayout.gt-sm="row wrap" fxFlex="100" fxLayoutAlign="space-between stretch">
<div fxLayout="row" fxFlex="100" fxLayoutAlign="space-between center"> <div fxLayout="row" fxFlex="100" fxLayoutAlign="space-between center">
<mat-form-field fxFlex="60" fxLayoutAlign="start end"> <mat-form-field fxFlex="60" fxLayoutAlign="start end">
<input matInput autoFocus formControlName="fundingAmount" placeholder="Amount" type="number" step="1000" tabindex="1" required> <input matInput autoFocus formControlName="fundingAmount" placeholder="Amount" type="number" [step]="1000" tabindex="1" required>
<mat-hint>Remaining Bal: {{totalBalance - ((channelFormGroup.controls.fundingAmount.value) ? channelFormGroup.controls.fundingAmount.value : 0)}}</mat-hint> <mat-hint>Remaining Bal: {{totalBalance - ((channelFormGroup.controls.fundingAmount.value) ? channelFormGroup.controls.fundingAmount.value : 0)}}</mat-hint>
<span matSuffix> Sats </span> <span matSuffix> Sats </span>
<mat-error *ngIf="channelFormGroup.controls.fundingAmount.errors?.required">Amount is required.</mat-error> <mat-error *ngIf="channelFormGroup.controls.fundingAmount.errors?.required">Amount is required.</mat-error>
@ -53,7 +53,7 @@
<div fxFlex="48" fxLayout="row" fxLayoutAlign="start center"> <div fxFlex="48" fxLayout="row" fxLayoutAlign="start center">
<mat-checkbox fxFlex="2" tabindex="5" color="primary" formControlName="flgMinConf" name="flgMinConf" fxLayoutAlign="stretch start" class="mr-2"></mat-checkbox> <mat-checkbox fxFlex="2" tabindex="5" color="primary" formControlName="flgMinConf" name="flgMinConf" fxLayoutAlign="stretch start" class="mr-2"></mat-checkbox>
<mat-form-field fxFlex="98"> <mat-form-field fxFlex="98">
<input matInput formControlName="minConfValue" placeholder="Min Confirmation Blocks" type="number" name="blocks" step="1" min="0" tabindex="8" [required]="channelFormGroup.controls.flgMinConf.value"> <input matInput formControlName="minConfValue" placeholder="Min Confirmation Blocks" type="number" name="blocks" [step]="1" [min]="0" tabindex="8" [required]="channelFormGroup.controls.flgMinConf.value">
<mat-error *ngIf="channelFormGroup.controls.flgMinConf.value && !channelFormGroup.controls.minConfValue.value">Min Confirmation Blocks is required.</mat-error> <mat-error *ngIf="channelFormGroup.controls.flgMinConf.value && !channelFormGroup.controls.minConfValue.value">Min Confirmation Blocks is required.</mat-error>
</mat-form-field> </mat-form-field>
</div> </div>

View File

@ -24,7 +24,7 @@ import * as fromRTLReducer from '../../../store/rtl.reducers';
styleUrls: ['./connect-peer.component.scss'] styleUrls: ['./connect-peer.component.scss']
}) })
export class CLConnectPeerComponent implements OnInit, OnDestroy { export class CLConnectPeerComponent implements OnInit, OnDestroy {
@ViewChild('peersForm', {static: true}) form: any; @ViewChild('peersForm', {static: false}) form: any;
@ViewChild('stepper', { static: false }) stepper: MatVerticalStepper; @ViewChild('stepper', { static: false }) stepper: MatVerticalStepper;
public faExclamationTriangle = faExclamationTriangle; public faExclamationTriangle = faExclamationTriangle;
public peerAddress = ''; public peerAddress = '';
@ -97,14 +97,14 @@ export class CLConnectPeerComponent implements OnInit, OnDestroy {
}); });
} }
onConnectPeer() { onConnectPeer():boolean|void {
if(!this.peerFormGroup.controls.peerAddress.value) { return true; } if(!this.peerFormGroup.controls.peerAddress.value) { return true; }
this.peerConnectionError = ''; this.peerConnectionError = '';
this.store.dispatch(new RTLActions.OpenSpinner('Adding Peer...')); this.store.dispatch(new RTLActions.OpenSpinner('Adding Peer...'));
this.store.dispatch(new CLActions.SaveNewPeer({id: this.peerFormGroup.controls.peerAddress.value})); this.store.dispatch(new CLActions.SaveNewPeer({id: this.peerFormGroup.controls.peerAddress.value}));
} }
onOpenChannel() { onOpenChannel():boolean|void {
if (!this.channelFormGroup.controls.fundingAmount.value || ((this.totalBalance - this.channelFormGroup.controls.fundingAmount.value) < 0) || (this.channelFormGroup.controls.flgMinConf.value && !this.channelFormGroup.controls.minConfValue.value)) { return true; } if (!this.channelFormGroup.controls.fundingAmount.value || ((this.totalBalance - this.channelFormGroup.controls.fundingAmount.value) < 0) || (this.channelFormGroup.controls.flgMinConf.value && !this.channelFormGroup.controls.minConfValue.value)) { return true; }
this.channelConnectionError = ''; this.channelConnectionError = '';
this.store.dispatch(new RTLActions.OpenSpinner('Opening Channel...')); this.store.dispatch(new RTLActions.OpenSpinner('Opening Channel...'));

View File

@ -1,4 +1,4 @@
<div fxLayout="row" fxLayoutAlign="start center" class="padding-gap-x page-title-container"> <div fxLayout="row" fxLayoutAlign="start center" class="page-title-container">
<fa-icon [icon]="faChartPie" class="page-title-img mr-1"></fa-icon> <fa-icon [icon]="faChartPie" class="page-title-img mr-1"></fa-icon>
<span class="page-title">On-chain Balance</span> <span class="page-title">On-chain Balance</span>
</div> </div>
@ -9,32 +9,28 @@
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
</div> </div>
<div fxLayout="row" fxLayoutAlign="start center" class="padding-gap-x page-title-container"> <div fxLayout="row" fxLayoutAlign="start center" class="page-title-container">
<fa-icon [icon]="faUsers" class="page-title-img mr-1"></fa-icon> <fa-icon [icon]="faUsers" class="page-title-img mr-1"></fa-icon>
<span class="page-title">Connections</span> <span class="page-title">Connections</span>
</div> </div>
<div fxLayout="column" class="padding-gap-x"> <div fxLayout="column" class="padding-gap-x">
<mat-card> <mat-card>
<mat-card-content fxLayout="column"> <mat-card-content fxLayout="column">
<mat-tab-group> <mat-tab-group [(selectedIndex)]="activeLink" (selectedTabChange)="onSelectedTabChange($event)">
<mat-tab> <mat-tab>
<ng-template mat-tab-label> <ng-template mat-tab-label>
<span matBadge="{{activeChannels}}" matBadgeOverlap="false" class="tab-badge">Channels</span> <span matBadge="{{activeChannels}}" matBadgeOverlap="false" class="tab-badge">Channels</span>
</ng-template> </ng-template>
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="padding-gap">
<div fxLayout="row">
<button mat-flat-button color="primary" (click)="onOpenChannel()" type="submit" tabindex="1">Open Channel</button>
</div>
<rtl-channels-tables fxLayout="row" fxFlex="100"></rtl-channels-tables>
</div>
</mat-tab> </mat-tab>
<mat-tab> <mat-tab>
<ng-template mat-tab-label> <ng-template mat-tab-label>
<span matBadge="{{activePeers}}" matBadgeOverlap="false" class="tab-badge">Peers</span> <span matBadge="{{activePeers}}" matBadgeOverlap="false" class="tab-badge">Peers</span>
</ng-template> </ng-template>
<rtl-peers></rtl-peers>
</mat-tab> </mat-tab>
</mat-tab-group> </mat-tab-group>
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="padding-gap-x-large">
<router-outlet></router-outlet>
</div>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
</div> </div>

View File

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

View File

@ -1,68 +1,52 @@
import { Component, OnInit, OnDestroy } from '@angular/core'; import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router, ResolveEnd } from '@angular/router';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil, filter } from 'rxjs/operators';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { faUsers, faChartPie } from '@fortawesome/free-solid-svg-icons'; import { faUsers, faChartPie } from '@fortawesome/free-solid-svg-icons';
import { GetInfo, Peer, Transaction } from '../../shared/models/clModels'; import { GetInfo, Peer, Transaction } from '../../shared/models/clModels';
import { CLOpenChannelComponent } from './channels/open-channel-modal/open-channel.component';
import { SelNodeChild } from '../../shared/models/RTLconfig'; import { SelNodeChild } from '../../shared/models/RTLconfig';
import { LoggerService } from '../../shared/services/logger.service'; import { LoggerService } from '../../shared/services/logger.service';
import { CommonService } from '../../shared/services/common.service'; import { CommonService } from '../../shared/services/common.service';
import * as RTLActions from '../../store/rtl.actions';
import * as fromRTLReducer from '../../store/rtl.reducers'; import * as fromRTLReducer from '../../store/rtl.reducers';
@Component({ @Component({
selector: 'rtl-cl-peers-channels', selector: 'rtl-cl-connections',
templateUrl: './peers-channels.component.html', templateUrl: './connections.component.html',
styleUrls: ['./peers-channels.component.scss'] styleUrls: ['./connections.component.scss']
}) })
export class CLPeersChannelsComponent implements OnInit, OnDestroy { export class CLConnectionsComponent implements OnInit, OnDestroy {
public selNode: SelNodeChild = {};
public information: GetInfo = {};
public peers: Peer[] = [];
public transactions: Transaction[] = [];
public totalBalance = 0;
public activePeers = 0; public activePeers = 0;
public activeChannels = 0; public activeChannels = 0;
public faUsers = faUsers; public faUsers = faUsers;
public faChartPie = faChartPie; public faChartPie = faChartPie;
public balances = [{title: 'Total Balance', dataValue: 0}, {title: 'Confirmed', dataValue: 0}, {title: 'Unconfirmed', dataValue: 0}]; public balances = [{title: 'Total Balance', dataValue: 0}, {title: 'Confirmed', dataValue: 0}, {title: 'Unconfirmed', dataValue: 0}];
public links = [{link: 'channels', name: 'Channels'}, {link: 'peers', name: 'Peers'}];
public activeLink = 0;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()]; private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
constructor(private store: Store<fromRTLReducer.RTLState>, private logger: LoggerService, private commonService: CommonService) {} constructor(private store: Store<fromRTLReducer.RTLState>, private logger: LoggerService, private commonService: CommonService, private router: Router) {}
ngOnInit() { ngOnInit() {
this.activeLink = this.links.findIndex(link => link.link === this.router.url.substring(this.router.url.lastIndexOf('/') + 1));
this.router.events.pipe(takeUntil(this.unSubs[0]), filter(e => e instanceof ResolveEnd))
.subscribe((value: ResolveEnd) => {
this.activeLink = this.links.findIndex(link => link.link === value.urlAfterRedirects.substring(value.urlAfterRedirects.lastIndexOf('/') + 1));
});
this.store.select('cl') this.store.select('cl')
.pipe(takeUntil(this.unSubs[1])) .pipe(takeUntil(this.unSubs[1]))
.subscribe((rtlStore) => { .subscribe((rtlStore) => {
this.selNode = rtlStore.nodeSettings;
this.information = rtlStore.information;
this.peers = rtlStore.peers;
this.transactions = this.commonService.sortAscByKey(rtlStore.transactions.filter(tran => tran.status === 'confirmed'), 'value');
this.activePeers = (rtlStore.peers && rtlStore.peers.length) ? rtlStore.peers.length : 0; this.activePeers = (rtlStore.peers && rtlStore.peers.length) ? rtlStore.peers.length : 0;
this.activeChannels = rtlStore.information.num_active_channels; this.activeChannels = rtlStore.information.num_active_channels;
this.totalBalance = rtlStore.balance.totalBalance;
this.balances = [{title: 'Total Balance', dataValue: rtlStore.balance.totalBalance || 0}, {title: 'Confirmed', dataValue: rtlStore.balance.confBalance}, {title: 'Unconfirmed', dataValue: rtlStore.balance.unconfBalance}]; this.balances = [{title: 'Total Balance', dataValue: rtlStore.balance.totalBalance || 0}, {title: 'Confirmed', dataValue: rtlStore.balance.confBalance}, {title: 'Unconfirmed', dataValue: rtlStore.balance.unconfBalance}];
this.logger.info(rtlStore); this.logger.info(rtlStore);
}); });
} }
onOpenChannel() { onSelectedTabChange(event) {
const peerToAddChannelMessage = { this.router.navigateByUrl('/cl/connections/' + this.links[event.index].link);
peers: this.peers,
information: this.information,
balance: this.totalBalance,
transactions: this.transactions,
isCompatibleVersion: this.commonService.isVersionCompatible(this.information.version, '0.9.0') &&
this.commonService.isVersionCompatible(this.information.api_version, '0.4.0')
};
this.store.dispatch(new RTLActions.OpenAlert({ data: {
alertTitle: 'Open Channel',
message: peerToAddChannelMessage,
component: CLOpenChannelComponent
}}));
} }
ngOnDestroy() { ngOnDestroy() {

View File

@ -3,38 +3,38 @@
<button mat-flat-button color="primary" type="submit" tabindex="1" (click)="onConnectPeer({})">Add Peer</button> <button mat-flat-button color="primary" type="submit" tabindex="1" (click)="onConnectPeer({})">Add Peer</button>
</form> </form>
<div fxLayout="column"> <div fxLayout="column">
<div fxLayout="column" fxLayout.gt-xs="row" fxLayoutAlign.gt-xs="start center" fxLayoutAlign="start stretch" class="padding-gap-x page-sub-title-container mt-2"> <div fxLayout="column" fxLayout.gt-xs="row" fxLayoutAlign.gt-xs="start center" fxLayoutAlign="start stretch" class="padding-gap-x page-sub-title-container">
<div fxFlex="70"> <div fxFlex="70">
<fa-icon [icon]="faUsers" class="page-title-img mr-1"></fa-icon> <fa-icon [icon]="faUsers" class="page-title-img mr-1"></fa-icon>
<span class="page-title">Connected Peers</span> <span class="page-title">Connected Peers</span>
</div> </div>
<mat-form-field fxFlex="30"> <mat-form-field fxFlex="30">
<div fxLayout="row" fxLayoutAlign="start start"> <div fxLayout="row" fxLayoutAlign="start start">
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter"> <input matInput (keyup)="applyFilter($event.target)" placeholder="Filter">
</div> </div>
</mat-form-field> </mat-form-field>
</div> </div>
<div perfectScrollbar fxLayout="row" fxLayoutAlign="start center" fxFlex="100" class="table-container"> <div [perfectScrollbar] fxLayout="row" fxLayoutAlign="start center" fxFlex="100" class="table-container">
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar> <mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
<table mat-table #table [dataSource]="peers" matSort [ngClass]="{'overflow-auto error-border': flgLoading[0]==='error','overflow-auto': true}"> <table mat-table #table [dataSource]="peers" matSort [ngClass]="{'overflow-auto error-border': flgLoading[0]==='error','overflow-auto': true}">
<ng-container matColumnDef="alias"> <ng-container matColumnDef="alias">
<th mat-header-cell *matHeaderCellDef mat-sort-header class="pr-3"> Alias </th> <th mat-header-cell *matHeaderCellDef mat-sort-header> Alias </th>
<td mat-cell *matCellDef="let peer" class="pr-3" [ngStyle]="{'max-width': (screenSize === screenSizeEnum.XS) ? '10rem' : '25rem'}"> <td mat-cell *matCellDef="let peer" [ngStyle]="{'max-width': (screenSize === screenSizeEnum.XS) ? '10rem' : '40rem'}">
<span *ngIf="peer?.connected" class="dot green" matTooltip="Connected" matTooltipPosition="right" [ngClass]="{'mr-0': screenSize === screenSizeEnum.XS}"></span> <span *ngIf="peer?.connected" class="dot green" matTooltip="Connected" matTooltipPosition="right" [ngClass]="{'mr-0': screenSize === screenSizeEnum.XS}"></span>
<span *ngIf="!peer?.connected" class="dot red" matTooltip="Disconnected" matTooltipPosition="right" [ngClass]="{'mr-0': screenSize === screenSizeEnum.XS}"></span> <span *ngIf="!peer?.connected" class="dot red" matTooltip="Disconnected" matTooltipPosition="right" [ngClass]="{'mr-0': screenSize === screenSizeEnum.XS}"></span>
{{peer?.alias}} {{peer?.alias}}
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="id"> <ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef mat-sort-header> ID </th> <th mat-header-cell *matHeaderCellDef class="px-3" mat-sort-header> ID </th>
<td mat-cell *matCellDef="let peer" [ngStyle]="{'max-width': (screenSize === screenSizeEnum.XS) ? '10rem' : '30rem'}"> <td mat-cell *matCellDef="let peer" class="px-3" [ngStyle]="{'max-width': (screenSize === screenSizeEnum.XS) ? '10rem' : '40rem'}">
{{peer?.id}} {{peer?.id}}
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="netaddr"> <ng-container matColumnDef="netaddr">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Network Address </th> <th mat-header-cell *matHeaderCellDef mat-sort-header> Network Address </th>
<td mat-cell *matCellDef="let peer" [ngStyle]="{'max-width': (screenSize === screenSizeEnum.XS) ? '10rem' : '20rem'}"> <td mat-cell *matCellDef="let peer" [ngStyle]="{'max-width': (screenSize === screenSizeEnum.XS) ? '10rem' : '20rem'}">
<span *ngFor="let addr of peer?.netaddr; last as isLast">{{ addr}}<span *ngIf="!isLast">,<br></span></span> <span *ngFor="let addr of peer?.netaddr; last as isLast">{{addr}}<span *ngIf="!isLast">,<br></span></span>
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="actions"> <ng-container matColumnDef="actions">

View File

@ -1,12 +1,12 @@
.mat-column-alias { .mat-column-alias {
flex: 1 1 10%; flex: 1 1 20%;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.mat-column-id { .mat-column-id {
flex: 1 1 10%; flex: 1 1 20%;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -14,8 +14,8 @@
} }
.mat-column-netaddr { .mat-column-netaddr {
flex: 1 1 15%; flex: 1 1 25%;
width: 15%; width: 25%;
} }
.mat-column-actions { .mat-column-actions {

View File

@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core'; import { Component, OnInit, OnDestroy, ViewChild, AfterViewInit } from '@angular/core';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil, filter } from 'rxjs/operators'; import { takeUntil, filter } from 'rxjs/operators';
@ -32,14 +32,15 @@ import { CLConnectPeerComponent } from '../connect-peer/connect-peer.component';
{ provide: MatPaginatorIntl, useValue: getPaginatorLabel('Peers') }, { provide: MatPaginatorIntl, useValue: getPaginatorLabel('Peers') },
] ]
}) })
export class CLPeersComponent implements OnInit, OnDestroy { export class CLPeersComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChild(MatSort, { static: true }) sort: MatSort; @ViewChild(MatSort, { static: false }) sort: MatSort|undefined;
@ViewChild(MatPaginator, {static: true}) paginator: MatPaginator; @ViewChild(MatPaginator, {static: false}) paginator: MatPaginator|undefined;
public faUsers = faUsers; public faUsers = faUsers;
public newlyAddedPeer = ''; public newlyAddedPeer = '';
public flgAnimate = true; public flgAnimate = true;
public displayedColumns = []; public displayedColumns: any[] = [];
public peerAddress = ''; public peerAddress = '';
public peersData: Peer[] = [];
public peers: any; public peers: any;
public information: GetInfo = {}; public information: GetInfo = {};
public availableBalance = 0; public availableBalance = 0;
@ -79,16 +80,10 @@ export class CLPeersComponent implements OnInit, OnDestroy {
}); });
this.information = rtlStore.information; this.information = rtlStore.information;
this.availableBalance = rtlStore.balance.totalBalance || 0; this.availableBalance = rtlStore.balance.totalBalance || 0;
this.peers = new MatTableDataSource([]); this.peersData = rtlStore.peers ? rtlStore.peers : [];
this.peers.data = []; if (this.peersData.length > 0) {
if ( rtlStore.peers) { this.loadPeersTable(this.peersData);
this.peers = new MatTableDataSource<Peer>([...rtlStore.peers]);
this.peers.data = rtlStore.peers;
setTimeout(() => { this.flgAnimate = false; }, 3000);
} }
this.peers.sort = this.sort;
this.peers.sortingDataAccessor = (data, sortHeaderId) => (data[sortHeaderId] && isNaN(data[sortHeaderId])) ? data[sortHeaderId].toLocaleLowerCase() : +data[sortHeaderId];
this.peers.paginator = this.paginator;
if (this.flgLoading[0] !== 'error') { if (this.flgLoading[0] !== 'error') {
this.flgLoading[0] = false; this.flgLoading[0] = false;
} }
@ -104,6 +99,12 @@ export class CLPeersComponent implements OnInit, OnDestroy {
}); });
} }
ngAfterViewInit() {
if (this.peersData.length > 0) {
this.loadPeersTable(this.peersData);
}
}
onPeerClick(selPeer: Peer, event: any) { onPeerClick(selPeer: Peer, event: any) {
const reorderedPeer = [ const reorderedPeer = [
[{key: 'id', value: selPeer.id, title: 'Public Key', width: 100}], [{key: 'id', value: selPeer.id, title: 'Public Key', width: 100}],
@ -160,8 +161,27 @@ export class CLPeersComponent implements OnInit, OnDestroy {
}); });
} }
applyFilter(selFilter: string) { applyFilter(selFilter: any) {
this.peers.filter = selFilter; this.peers.filter = selFilter.value;
}
loadPeersTable(peersArr: Peer[]) {
this.peers = new MatTableDataSource<Peer>([...peersArr]);
this.peers.sortingDataAccessor = (data: any, sortHeaderId: string) => {
switch (sortHeaderId) {
case 'netaddr':
if (data.netaddr && data.netaddr[0]) {
let firstSplit = data.netaddr[0].toString().split('.');
return (firstSplit[0]) ? +firstSplit[0] : data.netaddr[0];
} else {
return '';
}
default: return (data[sortHeaderId] && isNaN(data[sortHeaderId])) ? data[sortHeaderId].toLocaleLowerCase() : data[sortHeaderId] ? +data[sortHeaderId] : null;
}
}
this.peers.sort = this.sort;
this.peers.paginator = this.paginator;
} }
onDownloadCSV() { onDownloadCSV() {

View File

@ -0,0 +1,35 @@
<div fxLayout="column" fxLayoutAlign="start stretch" fxFlex="100" class="padding-gap-x-large">
<rtl-horizontal-scroller (stepChanged)="onSelectionChange($event)"></rtl-horizontal-scroller>
<div fxLayout="column" fxLayoutAlign="start stretch" fxFlex="100" class="padding-gap-x">
<div *ngIf="feeReportData.length > 0 && filteredEventsBySelectedPeriod.length > 0" fxLayout="column" fxLayoutAlign="center center" fxFlex="100" class="font-size-120 font-bold-700 mt-1"
[@fadeIn]="totalFeeMsat">{{(totalFeeMsat / 1000 || 0) | number:'1.0-0'}} Sats/{{(filteredEventsBySelectedPeriod.length || 0) | number}} Events</div>
<div *ngIf="feeReportData.length <= 0 || filteredEventsBySelectedPeriod.length <= 0" fxLayout="column" fxLayoutAlign="center center" fxFlex="100" class="font-size-120 mt-1">No fee report for the selected period</div>
<div class="mt-1">
<ngx-charts-bar-vertical
*ngIf="feeReportData.length > 0 && filteredEventsBySelectedPeriod.length > 0"
[view]="view"
[results]="feeReportData"
[gradient]="false"
[xAxis]="true"
[yAxis]="true"
[showXAxisLabel]="true"
[showYAxisLabel]="showYAxisLabel"
[xAxisLabel]="xAxisLabel"
[yAxisLabel]="yAxisLabel"
[showGridLines]="false"
[showDataLabel]="false"
(select)="onChartBarSelected($event)"
(mouseup)="onChartMouseUp($event)">
<ng-template #tooltipTemplate let-model="model">
<span>
<span class="tooltip-label">Events: {{(model.extra.totalEvents || 0) | number}}</span>
<span class="tooltip-label">Fee: {{(model.value || 0) | number:'1.0-0'}}</span>
</span>
</ng-template>
</ngx-charts-bar-vertical>
</div>
<div class="mt-1">
<rtl-cl-forwarding-history *ngIf="filteredEventsBySelectedPeriod && filteredEventsBySelectedPeriod.length > 0" [eventsData]="filteredEventsBySelectedPeriod" [filterValue]="eventFilterValue"></rtl-cl-forwarding-history>
</div>
</div>
</div>

View File

@ -0,0 +1,31 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DataService } from '../../../shared/services/data.service';
import { CLFeeReportComponent } from './fee-report.component';
describe('CLFeeReportComponent', () => {
let component: CLFeeReportComponent;
let fixture: ComponentFixture<CLFeeReportComponent>;
const mockDataService = jasmine.createSpyObj("DataService", ["getChildAPIUrl","setChildAPIUrl","getFiatRates",
"getAliasesFromPubkeys","signMessage","verifyMessage","handleErrorWithoutAlert","handleErrorWithAlert"]);
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CLFeeReportComponent ],
providers: [
{ provide: DataService, useValue: mockDataService }
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CLFeeReportComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,164 @@
import { Component, OnInit, OnDestroy, HostListener, AfterViewInit } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { ForwardingHistoryRes, ForwardingEvent } from '../../../shared/models/clModels';
import { MONTHS, ScreenSizeEnum, SCROLL_RANGES } from '../../../shared/services/consts-enums-functions';
import { LoggerService } from '../../../shared/services/logger.service';
import { CommonService } from '../../../shared/services/common.service';
import { DataService } from '../../../shared/services/data.service';
import { fadeIn } from '../../../shared/animation/opacity-animation';
import * as fromRTLReducer from '../../../store/rtl.reducers';
@Component({
selector: 'rtl-cl-fee-report',
templateUrl: './fee-report.component.html',
styleUrls: ['./fee-report.component.scss'],
animations: [fadeIn]
})
export class CLFeeReportComponent implements OnInit, AfterViewInit, OnDestroy {
public reportPeriod = SCROLL_RANGES[0];
public secondsInADay = 24 * 60 * 60;
public events: ForwardingHistoryRes = {};
public filteredEventsBySelectedPeriod: ForwardingEvent[] = [];
public eventFilterValue = '';
public errorMessage = '';
public totalFeeMsat = null;
public today = new Date(Date.now());
public timezoneOffset = this.today.getTimezoneOffset() * 60;
public startDate = new Date(this.today.getFullYear(), this.today.getMonth(), 1, 0, 0, 0);
public endDate = new Date(this.today.getFullYear(), this.today.getMonth(), this.getMonthDays(this.today.getMonth(), this.today.getFullYear()), 23, 59, 59);
public feeReportData: any = [];
public view: [number, number] = [350, 350];
public screenPaddingX = 100;
public gradient = true;
public xAxisLabel = 'Date';
public yAxisLabel = 'Fee (Sats)';
public showYAxisLabel = true;
public screenSize = '';
public screenSizeEnum = ScreenSizeEnum;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject()];
constructor(private logger: LoggerService, private dataService: DataService, private commonService: CommonService, private store: Store<fromRTLReducer.RTLState>) {}
ngOnInit() {
this.screenSize = this.commonService.getScreenSize();
this.showYAxisLabel = !(this.screenSize === ScreenSizeEnum.XS || this.screenSize === ScreenSizeEnum.SM);
this.store.select('cl')
.pipe(takeUntil(this.unSubs[0]))
.subscribe((rtlStore) => {
this.errorMessage = '';
rtlStore.effectErrors.forEach(effectsErr => {
if (effectsErr.action === 'GetForwardingHistory') {
this.errorMessage = (typeof(effectsErr.message) === 'object') ? JSON.stringify(effectsErr.message) : effectsErr.message;
}
});
this.events = (rtlStore.forwardingHistory && rtlStore.forwardingHistory.forwarding_events) ? rtlStore.forwardingHistory : {};
this.filterForwardingEvents(this.startDate, this.endDate);
this.logger.info(rtlStore);
});
}
ngAfterViewInit() {
const CONTAINER_SIZE = this.commonService.getContainerSize();
switch (this.screenSize) {
case ScreenSizeEnum.MD:
this.screenPaddingX = CONTAINER_SIZE.width/10;
break;
case ScreenSizeEnum.LG:
this.screenPaddingX = CONTAINER_SIZE.width/16;
break;
default:
this.screenPaddingX = CONTAINER_SIZE.width/20;
break;
}
this.view = [CONTAINER_SIZE.width - this.screenPaddingX, CONTAINER_SIZE.height/2.2];
}
filterForwardingEvents(start: Date, end: Date) {
const startDateInSeconds = (Math.round(start.getTime()/1000) - this.timezoneOffset);
const endDateInSeconds = (Math.round(end.getTime()/1000) - this.timezoneOffset);
this.filteredEventsBySelectedPeriod = [];
this.feeReportData = [];
this.totalFeeMsat = null;
if (this.events && this.events.forwarding_events && this.events.forwarding_events.length > 0) {
this.events.forwarding_events.forEach(event => {
if (event.status === 'settled' && event.received_time >= startDateInSeconds && event.received_time < endDateInSeconds) {
this.filteredEventsBySelectedPeriod.push(event);
}
});
this.feeReportData = this.prepareFeeReport(start);
}
}
@HostListener('mouseup', ['$event']) onChartMouseUp(e) {
if (e.srcElement.tagName === 'svg' && e.srcElement.classList.length > 0 && e.srcElement.classList[0] === 'ngx-charts') {
this.eventFilterValue = '';
}
}
onChartBarSelected(event) {
if(this.reportPeriod === SCROLL_RANGES[1]) {
this.eventFilterValue = event.name.toUpperCase() + '/' + this.startDate.getFullYear();
} else {
this.eventFilterValue = event.name.toString().padStart(2, '0') + '/' + MONTHS[this.startDate.getMonth()].name.toUpperCase() + '/' + this.startDate.getFullYear();
}
}
prepareFeeReport(start: Date) {
const startDateInSeconds = Math.round(start.getTime()/1000) - this.timezoneOffset;
let feeReport = [];
if (this.reportPeriod === SCROLL_RANGES[1]) {
for (let i = 0; i < 12; i++) {
feeReport.push({name: MONTHS[i].name, value: 0.000000001, extra: {totalEvents: 0}});
}
this.filteredEventsBySelectedPeriod.map(event => {
let monthNumber = new Date((+event.received_time + this.timezoneOffset)*1000).getMonth();
feeReport[monthNumber].value = feeReport[monthNumber].value + (+event.fee / 1000);
feeReport[monthNumber].extra.totalEvents = feeReport[monthNumber].extra.totalEvents + 1;
this.totalFeeMsat = (this.totalFeeMsat ? this.totalFeeMsat : 0) + +event.fee;
});
} else {
for (let i = 0; i < this.getMonthDays(start.getMonth(), start.getFullYear()); i++) {
feeReport.push({name: i + 1, value: 0.000000001, extra: {totalEvents: 0}});
}
this.filteredEventsBySelectedPeriod.map(event => {
let dateNumber = Math.floor((+event.received_time - startDateInSeconds) / this.secondsInADay);
feeReport[dateNumber].value = feeReport[dateNumber].value + (+event.fee / 1000);
feeReport[dateNumber].extra.totalEvents = feeReport[dateNumber].extra.totalEvents + 1;
this.totalFeeMsat = (this.totalFeeMsat ? this.totalFeeMsat : 0) + +event.fee;
});
}
return feeReport;
}
onSelectionChange(selectedValues: {selDate: Date, selScrollRange: string}) {
const selMonth = selectedValues.selDate.getMonth();
const selYear = selectedValues.selDate.getFullYear();
this.reportPeriod = selectedValues.selScrollRange;
if (this.reportPeriod === SCROLL_RANGES[1]) {
this.startDate = new Date(selYear, 0, 1, 0, 0, 0);
this.endDate = new Date(selYear, 11, 31, 23, 59, 59);
} else {
this.startDate = new Date(selYear, selMonth, 1, 0, 0, 0);
this.endDate = new Date(selYear, selMonth, this.getMonthDays(selMonth, selYear), 23, 59, 59);
}
this.filterForwardingEvents(this.startDate, this.endDate);
this.eventFilterValue = '';
}
getMonthDays(selMonth: number, selYear: number) {
return (selMonth === 1 && selYear%4 === 0) ? (MONTHS[selMonth].days+1) : MONTHS[selMonth].days;
}
ngOnDestroy() {
this.unSubs.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

View File

@ -0,0 +1,15 @@
<div fxLayout="row" fxLayoutAlign="start center" class="page-title-container">
<fa-icon [icon]="faChartBar" class="page-title-img mr-1"></fa-icon>
<span class="page-title">Reports</span>
</div>
<div fxLayout="column" class="padding-gap-x">
<mat-card>
<mat-card-content fxLayout="column">
<nav mat-tab-nav-bar>
<div role="tab" mat-tab-link *ngFor="let link of links" class="mat-tab-label" [active]="activeLink === link.link" (click)="activeLink = link.link" routerLink="{{link.link}}">{{link.name}}</div>
</nav>
<router-outlet></router-outlet>
</mat-card-content>
</mat-card>
</div>

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