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
pull/577/head v0.10.0
ShahanaFarooqui 3 years ago committed by GitHub
parent 6e036d8382
commit 5a38585b71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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.

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

@ -94,6 +94,7 @@ Example RTL-Config.json:
"lnImplementation": "LND",
"Authentication": {
"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>",
"lnApiPassword": "<Optional:Can be used to provide password in ECL implementation>"
},
@ -106,7 +107,7 @@ Example RTL-Config.json:
"enableLogging": true,
"fiatConversion": false,
"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>"
}
}
]

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

@ -12,8 +12,8 @@
<link rel="mask-icon" href="assets/images/favicon-light/safari-pinned-tab.svg" color="#5bbad5">
<meta name="msapplication-TileColor" content="#da532c">
<meta name="theme-color" content="#ffffff">
<link rel="stylesheet" href="styles.bbe7b5fc38151212c389.css"></head>
<link rel="stylesheet" href="styles.ee9d4139f693ecb219aa.css"></head>
<body>
<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>

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

@ -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()}([]);

@ -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

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

@ -2,6 +2,7 @@ var fs = require('fs');
var crypto = require('crypto');
var path = require('path');
var common = {};
const MONTH_NAMES = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'];
common.rtl_conf_file_path = '';
common.rtl_pass = '';
@ -17,11 +18,22 @@ common.nodes = [];
common.selectedNode = {};
common.getSwapServerOptions = () => {
return {
let swapOptions = {
url: common.selectedNode.swap_server_url,
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 = () => {
@ -128,7 +140,27 @@ common.convertToBTC = (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) => {
@ -170,4 +202,13 @@ common.newestOnTop = (array, key, value) => {
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;

@ -65,7 +65,6 @@ connect.setDefaultConfig = () => {
channelBackupPath: channelBackupPath,
enableLogging: false,
lnServerUrl: "https://localhost:8080",
swapServerUrl: "http://localhost:8081",
fiatConversion: false
}
}
@ -188,10 +187,13 @@ connect.validateNodeConfig = (config) => {
}
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_macaroon_path = process.env.SWAP_MACAROON_PATH;
} 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_macaroon_path = node.Authentication.swapMacaroonPath ? node.Authentication.swapMacaroonPath : '';
} else {
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].enable_logging = (node.Settings.enableLogging) ? !!node.Settings.enableLogging : false;

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

@ -1,9 +1,29 @@
var common = require('../common');
var connect = require('../connect');
const jwt = require("jsonwebtoken");
var crypto = require('crypto');
var logger = require('./logger');
const jwt = require("jsonwebtoken");
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) => {
if(+common.rtl_sso) {
@ -24,20 +44,33 @@ exports.authenticateUser = (req, res, next) => {
});
}
} else {
const currentTime = new Date().getTime();
const reqIP = common.getRequestIP(req);
let failed = getFailedInfo(reqIP, currentTime);
const password = req.body.authenticationValue;
if (common.rtl_pass === password) {
var rpcUser = 'NODE_USER';
if (common.rtl_pass === password && failed.count < ALLOWED_LOGIN_ATTEMPTS) {
delete failedLoginAttempts[reqIP];
let rpcUser = 'NODE_USER';
const token = jwt.sign(
{ user: rpcUser, configPath: common.nodes[0].config_path, macaroonPath: common.nodes[0].macaroon_path },
common.secret_key
);
res.status(200).json({ token: token });
} else {
logger.error({fileName: 'Authenticate', lineNum: 36, msg: 'Invalid Password!'});
res.status(401).json({
message: "Authentication Failed!",
error: "Invalid Password!"
});
logger.error({fileName: 'Authenticate', lineNum: 61, msg: 'Invalid Password! Failed IP ' + reqIP});
failed.count = common.rtl_pass !== password ? (failed.count + 1) : failed.count;
failed.lastTried = common.rtl_pass !== password ? currentTime : failed.lastTried;
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!"
});
}
}
}
};

@ -6,8 +6,9 @@ var options = {};
exports.getFees = (req, res, next) => {
options = common.getOptions();
options.url = common.getSelLNServerUrl() + '/audit';
tillToday = Math.floor(Date.now() / 1000);
fromLastMonth = tillToday - (86400 * 30);
let today = new Date(Date.now());
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 = {
from: fromLastMonth,
to: tillToday

@ -1,5 +1,4 @@
var request = require('request-promise');
var upperCase = require('upper-case');
var common = require('../../common');
var logger = require('../logger');
var options = {};
@ -11,7 +10,7 @@ exports.getBalance = (req, res, next) => {
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)});
if (body) {
if (upperCase(req.params.source) === 'BLOCKCHAIN') {
if (req.params.source === 'blockchain') {
if (!body.total_balance) { body.total_balance = 0; }
if (!body.confirmed_balance) { body.confirmed_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_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.pending_open_balance) { body.pending_open_balance = 0; }
body.btc_balance = common.convertToBTC(body.balance);

@ -34,8 +34,9 @@ exports.getFees = (req, res, next) => {
} else {
body.btc_month_fee_sum = common.convertToBTC(body.month_fee_sum);
}
let current_time = Math.round((new Date().getTime()) / 1000);
let month_start_time = current_time - 2629743;
let today = new Date(Date.now());
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 day_start_time = current_time - 86400;
swtch.getAllForwardingEvents(month_start_time, current_time, 0, (history) => {

@ -15,7 +15,8 @@ exports.loopOut = (req, res, next) => {
max_prepay_routing_fee: req.body.prepayRoutingFee,
max_prepay_amt: req.body.prepayAmt,
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.destAddress !== '') { options.body['dest'] = req.body.destAddress; }
@ -157,8 +158,10 @@ exports.loopIn = (req, res, next) => {
options.body = {
amt: req.body.amount,
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) {
logger.info({fileName: 'Loop', msg: 'Loop In: ' + JSON.stringify(body)});
if(!body || body.error) {

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

@ -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>",
"Authentication": {
"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>",
"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>,
"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",
"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>",
}
}
]

@ -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.
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.
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'
9. `swapServerUrl` must be set to the swap service url. e.g. http://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. `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.
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'
10. `swapServerUrl` must be set to the swap service url. e.g. https://localhost:8081.
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.
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

@ -27,6 +27,7 @@ If your running RTL and LND on different devices on your local LAN, certain conf
"lnImplementation": "LND",
"Authentication": {
"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>"
},
"Settings": {
@ -38,7 +39,7 @@ If your running RTL and LND on different devices on your local LAN, certain conf
"enableLogging": false,
"fiatConversion": false,
"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>",
}
}
]

@ -1,17 +1,22 @@
// @ts-check
// Protractor configuration file, see link for more information
// 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 = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
'browserName': 'chrome'
browserName: 'chrome'
},
directConnect: true,
SELENIUM_PROMISE_MANAGER: false,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
@ -21,8 +26,12 @@ exports.config = {
},
onPrepare() {
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
}
}));
}
};

@ -1,4 +1,5 @@
import { AppPage } from './app.po';
import { browser, logging } from 'protractor';
describe('workspace-project App', () => {
let page: AppPage;
@ -7,8 +8,16 @@ describe('workspace-project App', () => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getParagraphText()).toEqual('Welcome to RTLApp!');
it('should display welcome message', async () => {
await page.navigateTo();
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));
});
});

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

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

@ -9,16 +9,28 @@ module.exports = function (config) {
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('karma-coverage'),
require('@angular-devkit/build-angular/plugins/karma')
],
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
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../coverage'),
reports: ['html', 'lcovonly'],
fixWebpackSourcePaths: true
jasmineHtmlReporter: {
suppressAll: true // removes the duplicated traces
},
coverageReporter: {
dir: require('path').join(__dirname, './coverage/RTL'),
subdir: '.',
reporters: [
{ type: 'html' },
{ type: 'text-summary' }
]
},
reporters: ['progress', 'kjhtml'],
port: 9876,
@ -26,6 +38,7 @@ module.exports = function (config) {
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
singleRun: false,
restartOnFileChange: true
});
};
};

6418
package-lock.json generated

File diff suppressed because it is too large Load Diff

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

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

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

@ -12,19 +12,19 @@
</button>
</div>
<div>
<span *ngIf="xSmallScreen">{{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 ? 'RTL - ' + information.alias : 'RTL'}}</span>
<span *ngIf="!smallScreen">{{information.alias ? 'Ride The Lightning - ' + information.alias : 'Ride The Lightning'}}</span>
</div>
<div>
<rtl-top-menu></rtl-top-menu>
</div>
</mat-toolbar>
<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>
</mat-sidenav>
<mat-sidenav-content perfectScrollbar #sideNavContent>
<div [ngClass]="{'mt-minus-1': smallScreen, 'inner-sidenav-content': true}">
<mat-sidenav-content [perfectScrollbar] #sideNavContent>
<div [ngClass]="{'inner-sidenav-content': true}">
<router-outlet></router-outlet>
</div>
</mat-sidenav-content>>

@ -46,29 +46,27 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
this.router.events.subscribe((evt) => {
if (!(evt instanceof NavigationEnd)) { return; }
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]))
.subscribe((matches) => {
if(matches.breakpoints[Breakpoints.XSmall]) {
this.commonService.setScreenSize(ScreenSizeEnum.XS);
this.xSmallScreen = true;
this.smallScreen = true;
} else if(matches.breakpoints[Breakpoints.TabletPortrait]) {
this.commonService.setScreenSize(ScreenSizeEnum.SM);
this.xSmallScreen = false;
this.smallScreen = true;
} else if(matches.breakpoints[Breakpoints.Small] || matches.breakpoints[Breakpoints.Medium]) {
this.commonService.setScreenSize(ScreenSizeEnum.MD);
this.xSmallScreen = false;
this.smallScreen = false;
} else {
} else if(matches.breakpoints[Breakpoints.Large]) {
this.commonService.setScreenSize(ScreenSizeEnum.LG);
this.xSmallScreen = false;
this.smallScreen = false;
} else {
this.commonService.setScreenSize(ScreenSizeEnum.XL);
this.smallScreen = false;
}
});
this.store.dispatch(new RTLActions.FetchRTLConfig());
this.accessKey = this.readAccessKey();
this.store.select('root')
@ -122,9 +120,11 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
ngAfterViewInit() {
if (this.smallScreen) {
this.sideNavigation.close();
this.commonService.setContainerSize(this.sideNavContent.elementRef.nativeElement.clientWidth, this.sideNavContent.elementRef.nativeElement.clientHeight);
} else {
setTimeout(() => {
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);
}
}
@ -140,7 +140,7 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
}
}
copiedText(payload) {
copiedText(payload: string) {
this.flgCopied = true;
setTimeout(() => {this.flgCopied = false; }, 5000);
this.logger.info('Copied Text: ' + payload);

@ -7,22 +7,18 @@ import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { MatDialogModule, MAT_DIALOG_DEFAULT_OPTIONS } from '@angular/material/dialog';
import { UserIdleModule } from 'angular-user-idle';
import { OverlayContainer } from '@angular/cdk/overlay';
import { routing } from './app.routing';
import { SharedModule } from './shared/shared.module';
import { ThemeOverlay } from './shared/theme/overlay-container/theme-overlay';
import { AppComponent } from './app.component';
import { environment } from '../environments/environment';
import { AuthGuard } from './shared/services/auth.guard';
import { AuthInterceptor } from './shared/services/auth.interceptor';
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 { AuthInterceptor } from './shared/services/auth.interceptor';
import { CommonService } from './shared/services/common.service';
import { RTLReducer } from './store/rtl.reducers';
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 { ECLEffects } from './eclair/store/ecl.effects';
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({
imports: [
@ -70,7 +33,9 @@ import { ECLChannelInformationComponent } from './eclair/peers-channels/channels
BrowserAnimationsModule,
SharedModule,
routing,
UserIdleModule.forRoot({idle: 60 * 60, timeout: 1, ping: null}),
LayoutModule,
HammerModule,
UserIdleModule.forRoot({idle: 60 * 60, timeout: 1}),
StoreModule.forRoot(RTLReducer, {
runtimeChecks: {
strictStateImmutability: false,
@ -78,89 +43,12 @@ import { ECLChannelInformationComponent } from './eclair/peers-channels/channels
}
}),
EffectsModule.forRoot([RTLEffects, LNDEffects, CLEffects, ECLEffects]),
!environment.production ? StoreDevtoolsModule.instrument() : [],
LayoutModule,
MatDialogModule,
HammerModule
!environment.production ? StoreDevtoolsModule.instrument() : []
],
declarations: [
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
],
declarations: [AppComponent],
providers: [
{ provide: LoggerService, useClass: ConsoleLoggerService },
{ provide: OverlayContainer, useClass: ThemeOverlay },
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
{ provide: MAT_DIALOG_DEFAULT_OPTIONS, useValue: { hasBackdrop: true, autoFocus: true, disableClose: true, role: 'dialog', width: '55%' } },
CommonService, AuthGuard, SessionService, DataService, LoopService
AuthGuard, SessionService, DataService, LoopService, CommonService
],
bootstrap: [AppComponent]
})

@ -2,6 +2,9 @@ import { Routes, RouterModule } from '@angular/router';
import { ModuleWithProviders } from '@angular/core';
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 { HelpComponent } from './shared/components/help/help.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';
export const routes: Routes = [
{ path: '', pathMatch: 'full', redirectTo: 'lnd' },
{ 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: '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: 'login', component: LoginComponent },
{ path: 'error', component: ErrorComponent },
{ path: '**', component: NotFoundComponent }
];
export const routing: ModuleWithProviders = RouterModule.forRoot(routes);
export const routing: ModuleWithProviders<RouterModule> = RouterModule.forRoot(routes);

@ -6,7 +6,7 @@ import { SharedModule } from '../shared/shared.module';
import { CLRootComponent } from './cl-root.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 { CLPeersComponent } from './peers-channels/peers/peers.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 { CLSignComponent } from './sign-verify-message/sign/sign.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';
@ -48,7 +59,7 @@ import { CLUnlockedGuard } from '../shared/services/auth.guard';
CLRootComponent,
CLHomeComponent,
CLPeersComponent,
CLPeersChannelsComponent,
CLConnectionsComponent,
CLLightningInvoicesComponent,
CLLightningPaymentsComponent,
CLTransactionsComponent,
@ -75,7 +86,18 @@ import { CLUnlockedGuard } from '../shared/services/auth.guard';
CLFeeRatesComponent,
CLSignVerifyMessageComponent,
CLSignComponent,
CLVerifyComponent
CLVerifyComponent,
CLReportsComponent,
CLFeeReportComponent,
CLTransactionsReportComponent,
CLOnChainSendComponent,
CLInvoiceInformationComponent,
CLOpenChannelComponent,
CLConnectPeerComponent,
CLLightningSendPaymentsComponent,
CLCreateInvoiceComponent,
CLOnChainSendModalComponent,
CLChannelInformationComponent
],
providers: [
CLUnlockedGuard

@ -4,31 +4,80 @@ import { ModuleWithProviders } from '@angular/core';
import { CLRootComponent } from './cl-root.component';
import { CLHomeComponent } from './home/home.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 { CLRoutingComponent } from '../clightning/routing/routing.component';
import { CLLookupsComponent } from './lookups/lookups.component';
import { CLNetworkInfoComponent } from './network-info/network-info.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 { NotFoundComponent } from '../shared/components/not-found/not-found.component';
export const ClRoutes: Routes = [
{ path: '', component: CLRootComponent,
children: [
{ path: 'home', component: CLHomeComponent, canActivate: [CLUnlockedGuard] },
{ path: 'onchain', component: CLOnChainComponent, canActivate: [CLUnlockedGuard] },
{ path: 'peerschannels', component: CLPeersChannelsComponent, canActivate: [CLUnlockedGuard] },
{ path: 'transactions', component: CLTransactionsComponent, canActivate: [CLUnlockedGuard] },
{ path: 'routing', component: CLRoutingComponent, canActivate: [CLUnlockedGuard] },
{ path: 'lookups', component: CLLookupsComponent, canActivate: [CLUnlockedGuard] },
{ path: 'rates', component: CLNetworkInfoComponent, canActivate: [CLUnlockedGuard] },
{ path: 'signverify', component: CLSignVerifyMessageComponent, canActivate: [CLUnlockedGuard] },
{ path: '**', component: NotFoundComponent },
{ path: 'network', redirectTo: 'rates' },
{ path: 'wallet', redirectTo: 'home' },
{ path: 'backup', redirectTo: 'home' }
]}
{ path: '', pathMatch: 'full', redirectTo: 'home' },
{ path: 'home', component: CLHomeComponent, canActivate: [CLUnlockedGuard] },
{ path: 'onchain', component: CLOnChainComponent, canActivate: [CLUnlockedGuard], children: [
{ path: '', pathMatch: 'full', redirectTo: 'receive' },
{ path: 'receive', component: CLOnChainReceiveComponent, canActivate: [CLUnlockedGuard] },
{ path: 'send', component: CLOnChainSendComponent, data : {sweepAll : false}, canActivate: [CLUnlockedGuard] },
{ path: 'sweep', component: CLOnChainSendComponent, data : {sweepAll : true}, canActivate: [CLUnlockedGuard] }
] },
{ path: 'connections', component: CLConnectionsComponent, canActivate: [CLUnlockedGuard], children: [
{ path: '', pathMatch: 'full', redirectTo: 'channels' },
{ path: 'channels', component: CLChannelsTablesComponent, canActivate: [CLUnlockedGuard], children: [
{ 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);

@ -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>
</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 *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>

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

@ -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">
<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-progress-bar class="dashboard-progress-bar this-channel-bar" mode="determinate" color="accent" value="100"></mat-progress-bar>
</div>
<div fxLayout="column" fxFlex="3" fxLayoutAlign="end stretch"><mat-divider class="dashboard-divider"></mat-divider></div>
<div fxLayout="column" fxFlex.gt-sm="88" fxFlex="84" fxLayoutAlign="start start" class="channels-capacity-scroll" perfectScrollbar>
<div [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 *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>

@ -1,22 +1,30 @@
import { Component, Input } from '@angular/core';
import { Component, Input, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Channel } from '../../../shared/models/clModels';
import { ScreenSizeEnum } from '../../../shared/services/consts-enums-functions';
import { CommonService } from '../../../shared/services/common.service';
@Component({
selector: 'rtl-cl-channel-liquidity-info',
templateUrl: './channel-liquidity-info.component.html',
styleUrls: ['./channel-liquidity-info.component.scss']
})
export class CLChannelLiquidityInfoComponent {
export class CLChannelLiquidityInfoComponent implements OnInit {
@Input() direction: string;
@Input() totalLiquidity: number;
@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() {
this.router.navigateByUrl('/cl/peerschannels');
this.router.navigateByUrl('/cl/connections');
}
}

@ -1,25 +1,30 @@
<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>
<span class="page-title">{{!flgLoading[0] ? 'Welcome ' + information.alias + '! Your node is up and running.' : 'Error! Please check the server connection.'}}</span>
</div>
<mat-grid-list cols="10" [rowHeight]="operatorCardHeight">
<mat-grid-tile *ngFor="let card of operatorCards" [colspan]="card.cols" [rowspan]="card.rows">
<mat-card fxLayout="column" fxLayoutAlign="start start" class="dashboard-card p-24">
<mat-card fxFlex="100" fxLayout="column" fxLayoutAlign="start stretch" class="h-100 dashboard-card mt-4">
<mat-card-header>
<mat-card-title>
<fa-icon [icon]="card.icon" class="mr-1"></fa-icon>
<span>{{card.title}}</span>
<button *ngIf="card.link" mat-icon-button class="more-button mt-1" [matMenuTriggerFor]="menuOperator" aria-label="Toggle menu">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menuOperator="matMenu" class="dashboard-vert-menu" xPosition="before">
<button mat-menu-item (click)="onNavigateTo(card.link)">Go To {{card.goTo}}</button>
<button *ngIf="card.id === 'capacity'" (click)="onsortChannelsBy()" mat-menu-item>Sort By {{sortField === 'Balance Score' ? 'Capacity' : 'Balance Score'}}</button>
</mat-menu>
<mat-card-title fxLayoutAlign="space-between center">
<div>
<fa-icon [icon]="card.icon" class="mr-1"></fa-icon>
<span>{{card.title}}</span>
</div>
<div>
<button *ngIf="card.link" mat-icon-button class="more-button" [matMenuTriggerFor]="menuOperator" aria-label="Toggle menu">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menuOperator="matMenu" class="dashboard-vert-menu" xPosition="before">
<button mat-menu-item (click)="onNavigateTo(card.link)">Go To {{card.goTo}}</button>
<button *ngIf="card.id === '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-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">
<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>
@ -34,26 +39,30 @@
</mat-grid-list>
</div>
<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>
<span class="page-title">Welcome {{information.alias}}! Your node is up and running.</span>
</div>
<mat-grid-list cols="6" [rowHeight]="merchantCardHeight">
<mat-grid-tile *ngFor="let card of merchantCards" [colspan]="card.cols" [rowspan]="card.rows">
<mat-card fxLayout="column" fxLayoutAlign="start start" class="dashboard-card" [ngClass]="{'p-24': card.id !== 'transactions'}">
<mat-card 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-title>
<fa-icon [icon]="card.icon" class="mr-1"></fa-icon>
<span>{{card.title}}</span>
<button *ngIf="card.link" mat-icon-button class="more-button mt-1" [matMenuTriggerFor]="menuMerchant" aria-label="Toggle menu">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menuMerchant="matMenu" class="dashboard-vert-menu" xPosition="before">
<button mat-menu-item (click)="onNavigateTo(card.link)">Go To {{card.goTo}}</button>
</mat-menu>
<mat-card-title fxLayoutAlign="space-between center">
<div>
<fa-icon [icon]="card.icon" class="mr-1"></fa-icon>
<span>{{card.title}}</span>
</div>
<div>
<button *ngIf="card.link" mat-icon-button class="more-button" [matMenuTriggerFor]="menuMerchant" aria-label="Toggle menu">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #menuMerchant="matMenu" class="dashboard-vert-menu" xPosition="before">
<button mat-menu-item (click)="onNavigateTo(card.link)">Go To {{card.goTo}}</button>
</mat-menu>
</div>
</mat-card-title>
</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">
<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>
@ -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>
<span fxLayout="column" fxFlex="100" fxLayoutAlign="space-between start" *ngSwitchCase="'transactions'">
<mat-tab-group fxLayout="column" class="w-100 dashboard-tabs-group">
<mat-tab label="Receive"><rtl-cl-lightning-invoices class="h-100" [showDetails]="false"></rtl-cl-lightning-invoices></mat-tab>
<mat-tab label="Pay"><rtl-cl-lightning-payments [showDetails]="false"></rtl-cl-lightning-payments></mat-tab>
<mat-tab 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 [calledFrom]="'home'"></rtl-cl-lightning-payments></mat-tab>
<mat-tab [disabled]="true">
<ng-template mat-tab-label>
<button mat-icon-button class="more-button" [matMenuTriggerFor]="menuTransactions" aria-label="Toggle menu" style="max-width: 20px;">
@ -70,6 +79,7 @@
</button>
<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('/cl/reports/transactions')">Transactions Summary</button>
</mat-menu>
</ng-template>
</mat-tab>

@ -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;
}

@ -13,7 +13,6 @@ import { UserPersonaEnum, ScreenSizeEnum } from '../../shared/services/consts-en
import { ChannelsStatus, GetInfo, Fees, Channel, Balance, FeeRates } from '../../shared/models/clModels';
import { SelNodeChild } from '../../shared/models/RTLconfig';
import * as CLActions from '../store/cl.actions';
import * as RTLActions from '../../store/rtl.actions';
import * as fromRTLReducer from '../../store/rtl.reducers';
@Component({
@ -63,28 +62,28 @@ export class CLHomeComponent implements OnInit, OnDestroy {
{ 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: '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: 'capacity', goTo: 'Channels', link: '/cl/peerschannels', icon: this.faNetworkWired, title: 'Channels Capacity', cols: 10, rows: 2 }
{ id: 'status', goTo: 'Channels', link: '/cl/connections', icon: this.faNetworkWired, title: 'Channels', cols: 10, rows: 1 },
{ id: 'capacity', goTo: 'Channels', link: '/cl/connections', icon: this.faNetworkWired, title: 'Channels Capacity', cols: 10, rows: 2 }
];
this.merchantCards = [
{ 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: 'inboundLiq', goTo: 'Channels', link: '/cl/peerschannels', 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: 'inboundLiq', goTo: 'Channels', link: '/cl/connections', icon: this.faAngleDoubleDown, title: 'In-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) {
this.operatorCards = [
{ 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: '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: 'capacity', goTo: 'Channels', link: '/cl/peerschannels', icon: this.faNetworkWired, title: 'Channels Capacity', cols: 10, rows: 2 }
{ id: 'status', goTo: 'Channels', link: '/cl/connections', icon: this.faNetworkWired, title: 'Channels', cols: 5, rows: 1 },
{ id: 'capacity', goTo: 'Channels', link: '/cl/connections', icon: this.faNetworkWired, title: 'Channels Capacity', cols: 10, rows: 2 }
];
this.merchantCards = [
{ 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: 'inboundLiq', goTo: 'Channels', link: '/cl/peerschannels', 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: 'inboundLiq', goTo: 'Channels', link: '/cl/connections', icon: this.faAngleDoubleDown, title: 'In-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 {
this.operatorCardHeight = ((window.screen.height * 0.77) / 2) + 'px';
@ -92,14 +91,14 @@ export class CLHomeComponent implements OnInit, OnDestroy {
this.operatorCards = [
{ 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: '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: '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 = [
{ 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: 'outboundLiq', goTo: 'Channels', link: '/cl/peerschannels', icon: this.faAngleDoubleUp, title: 'Out-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/connections', icon: this.faAngleDoubleUp, title: 'Out-Bound Liquidity', cols: 2, rows: 10 },
{ id: 'transactions', goTo: 'Transactions', link: '/cl/transactions', title: '', cols: 2, rows: 5 }
];
}

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

@ -1,5 +1,5 @@
<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>
<span class="page-title">Graph Lookups</span>
</div>

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

@ -25,7 +25,7 @@
<mat-divider [inset]="true" class="my-1"></mat-divider>
<div fxLayout="column" class="mt-2">
<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">
<ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Type</th>

@ -12,7 +12,7 @@ import { LoggerService } from '../../../shared/services/logger.service';
styleUrls: ['./node-lookup.component.scss']
})
export class CLNodeLookupComponent implements OnInit {
@ViewChild(MatSort, { static: true }) sort: MatSort;
@ViewChild(MatSort, { static: false }) sort: MatSort|undefined;
@Input() lookupResult: LookupNode;
public addresses: any;
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.data = this.lookupResult.addresses ? this.lookupResult.addresses : [];
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) {

@ -9,7 +9,7 @@
</div>
</div>
<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">
<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>
@ -34,7 +34,7 @@
</div>
</div>
<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">
<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>

@ -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">
<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">

@ -13,7 +13,7 @@
<mat-error *ngIf="!transaction.address">Bitcoin address is required.</mat-error>
</mat-form-field>
<mat-form-field fxFlex="30">
<input matInput [(ngModel)]="transaction.satoshis" placeholder="Amount" name="amount" [type]="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>
<span matSuffix> {{selAmountUnit}} </span>
<mat-error *ngIf="!transaction.satoshis">Amount is required.</mat-error>
@ -34,7 +34,7 @@
<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-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-form-field>
</div>
@ -112,7 +112,7 @@
<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-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-form-field>
</div>

@ -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();
});
});

@ -26,12 +26,12 @@ import * as fromRTLReducer from '../../../store/rtl.reducers';
import * as sha256 from 'sha256';
@Component({
selector: 'rtl-cl-on-chain-send',
templateUrl: './on-chain-send.component.html',
styleUrls: ['./on-chain-send.component.scss']
selector: 'rtl-cl-on-chain-send-modal',
templateUrl: './on-chain-send-modal.component.html',
styleUrls: ['./on-chain-send-modal.component.scss']
})
export class CLOnChainSendComponent implements OnInit, OnDestroy {
@ViewChild('form', { static: false }) form: any;
export class CLOnChainSendModalComponent implements OnInit, OnDestroy {
@ViewChild('form', { static: true }) form: any;
@ViewChild('formSweepAll', { static: false }) formSweepAll: any;
@ViewChild('stepper', { static: false }) stepper: MatVerticalStepper;
public faExclamationTriangle = faExclamationTriangle;
@ -71,7 +71,7 @@ export class CLOnChainSendComponent implements OnInit, OnDestroy {
confirmFormGroup: FormGroup;
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() {
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; }
this.flgValidated = false;
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; }
this.sendFundError = '';
if (this.flgUseAllBalance) {

@ -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>

@ -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();
});
}
}

@ -5,11 +5,11 @@
<span class="page-title">UTXOs</span>
</div>
<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>
</div>
<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>
<table mat-table #table [dataSource]="listTransactions" matSort
[ngClass]="{'overflow-auto error-border': flgLoading[0]==='error','overflow-auto': true}">

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

@ -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>
<span class="page-title">On-chain Balance</span>
</div>
@ -9,36 +9,21 @@
</mat-card-content>
</mat-card>
</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>
<span class="page-title">On-chain Transactions</span>
</div>
<div fxLayout="column" class="padding-gap-x">
<mat-card>
<mat-card-content fxLayout="column">
<mat-tab-group>
<mat-tab label="Receive">
<rtl-cl-on-chain-receive></rtl-cl-on-chain-receive>
</mat-tab>
<mat-tab label="Send">
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="padding-gap">
<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>
<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>
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="padding-gap-x-large mt-2">
<router-outlet></router-outlet>
</div>
<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>
</div>
<rtl-cl-on-chain-transaction-history fxLayout="row" fxFlex="100"></rtl-cl-on-chain-transaction-history>
</div>
</mat-card-content>
</mat-card>

@ -1,13 +1,14 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router, ResolveEnd } from '@angular/router';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { takeUntil, filter } from 'rxjs/operators';
import { Store } from '@ngrx/store';
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 * as RTLActions from '../../store/rtl.actions';
import * as fromRTLReducer from '../../store/rtl.reducers';
import { CLOnChainSendComponent } from './on-chain-send-modal/on-chain-send.component';
@Component({
selector: 'rtl-cl-on-chain',
@ -19,11 +20,20 @@ export class CLOnChainComponent implements OnInit, OnDestroy {
public faExchangeAlt = faExchangeAlt;
public faChartPie = faChartPie;
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()];
constructor(private store: Store<fromRTLReducer.RTLState>) {}
constructor(private store: Store<fromRTLReducer.RTLState>, private router: Router) {}
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')
.pipe(takeUntil(this.unSubs[1]))
.subscribe((rtlStore) => {
@ -35,7 +45,7 @@ export class CLOnChainComponent implements OnInit, OnDestroy {
openSendFundsModal(sweepAll: boolean) {
this.store.dispatch(new RTLActions.OpenAlert({ data: {
sweepAll: sweepAll,
component: CLOnChainSendComponent
component: CLOnChainSendModalComponent
}}));
}

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

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

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

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

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

@ -1,9 +1,16 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router, ResolveEnd } from '@angular/router';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { takeUntil, filter } from 'rxjs/operators';
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 { 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';
@Component({
@ -14,13 +21,25 @@ import * as fromRTLReducer from '../../../../store/rtl.reducers';
export class CLChannelsTablesComponent implements OnInit, OnDestroy {
public openChannels = 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() {
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')
.pipe(takeUntil(this.unSubs[0]))
.pipe(takeUntil(this.unSubs[1]))
.subscribe((rtlStore) => {
if (rtlStore.allChannels && rtlStore.allChannels.length) {
this.openChannels = 0;
@ -36,10 +55,35 @@ export class CLChannelsTablesComponent implements OnInit, OnDestroy {
this.openChannels = 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);
});
}
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() {
this.unSubs.forEach(completeSub => {
completeSub.next();

@ -19,7 +19,56 @@
</mat-form-field>
</div>
<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 !== ''">
<fa-icon [icon]="faExclamationTriangle" class="mr-1 alert-icon"></fa-icon>
<span *ngIf="channelConnectionError !== ''">{{channelConnectionError}}</span>
@ -61,55 +110,3 @@
</div>
</mat-expansion-panel>
</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>

@ -22,7 +22,7 @@ import * as fromRTLReducer from '../../../../store/rtl.reducers';
styleUrls: ['./open-channel.component.scss']
})
export class CLOpenChannelComponent implements OnInit, OnDestroy {
@ViewChild('form', { static: false }) form: any;
@ViewChild('form', { static: true }) form: any;
public selectedPeer = new FormControl();
public faExclamationTriangle = faExclamationTriangle;
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; }
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) {

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

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

@ -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>
<span class="page-title">On-chain Balance</span>
</div>
@ -9,32 +9,28 @@
</mat-card-content>
</mat-card>
</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>
<span class="page-title">Connections</span>
</div>
<div fxLayout="column" class="padding-gap-x">
<mat-card>
<mat-card-content fxLayout="column">
<mat-tab-group>
<mat-tab-group [(selectedIndex)]="activeLink" (selectedTabChange)="onSelectedTabChange($event)">
<mat-tab>
<ng-template mat-tab-label>
<span matBadge="{{activeChannels}}" matBadgeOverlap="false" class="tab-badge">Channels</span>
</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>
<ng-template mat-tab-label>
<span matBadge="{{activePeers}}" matBadgeOverlap="false" class="tab-badge">Peers</span>
</ng-template>
<rtl-peers></rtl-peers>
</mat-tab>
</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>
</div>

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

@ -1,68 +1,52 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router, ResolveEnd } from '@angular/router';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { takeUntil, filter } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { faUsers, faChartPie } from '@fortawesome/free-solid-svg-icons';
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 { LoggerService } from '../../shared/services/logger.service';
import { CommonService } from '../../shared/services/common.service';
import * as RTLActions from '../../store/rtl.actions';
import * as fromRTLReducer from '../../store/rtl.reducers';
@Component({
selector: 'rtl-cl-peers-channels',
templateUrl: './peers-channels.component.html',
styleUrls: ['./peers-channels.component.scss']
selector: 'rtl-cl-connections',
templateUrl: './connections.component.html',
styleUrls: ['./connections.component.scss']
})
export class CLPeersChannelsComponent implements OnInit, OnDestroy {
public selNode: SelNodeChild = {};
public information: GetInfo = {};
public peers: Peer[] = [];
public transactions: Transaction[] = [];
public totalBalance = 0;
export class CLConnectionsComponent implements OnInit, OnDestroy {
public activePeers = 0;
public activeChannels = 0;
public faUsers = faUsers;
public faChartPie = faChartPie;
public balances = [{title: 'Total Balance', dataValue: 0}, {title: 'Confirmed', dataValue: 0}, {title: 'Unconfirmed', dataValue: 0}];
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()];
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() {
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')
.pipe(takeUntil(this.unSubs[1]))
.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.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.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/' + this.links[event.index].link);
}
ngOnDestroy() {

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

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

@ -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 { takeUntil, filter } from 'rxjs/operators';
@ -32,14 +32,15 @@ import { CLConnectPeerComponent } from '../connect-peer/connect-peer.component';
{ provide: MatPaginatorIntl, useValue: getPaginatorLabel('Peers') },
]
})
export class CLPeersComponent implements OnInit, OnDestroy {
@ViewChild(MatSort, { static: true }) sort: MatSort;
@ViewChild(MatPaginator, {static: true}) paginator: MatPaginator;
export class CLPeersComponent implements OnInit, AfterViewInit, OnDestroy {
@ViewChild(MatSort, { static: false }) sort: MatSort|undefined;
@ViewChild(MatPaginator, {static: false}) paginator: MatPaginator|undefined;
public faUsers = faUsers;
public newlyAddedPeer = '';
public flgAnimate = true;
public displayedColumns = [];
public displayedColumns: any[] = [];
public peerAddress = '';
public peersData: Peer[] = [];
public peers: any;
public information: GetInfo = {};
public availableBalance = 0;
@ -79,16 +80,10 @@ export class CLPeersComponent implements OnInit, OnDestroy {
});
this.information = rtlStore.information;
this.availableBalance = rtlStore.balance.totalBalance || 0;
this.peers = new MatTableDataSource([]);
this.peers.data = [];
if ( rtlStore.peers) {
this.peers = new MatTableDataSource<Peer>([...rtlStore.peers]);
this.peers.data = rtlStore.peers;
setTimeout(() => { this.flgAnimate = false; }, 3000);
this.peersData = rtlStore.peers ? rtlStore.peers : [];
if (this.peersData.length > 0) {
this.loadPeersTable(this.peersData);
}
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') {
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) {
const reorderedPeer = [
[{key: 'id', value: selPeer.id, title: 'Public Key', width: 100}],
@ -160,8 +161,27 @@ export class CLPeersComponent implements OnInit, OnDestroy {
});
}
applyFilter(selFilter: string) {
this.peers.filter = selFilter;
applyFilter(selFilter: any) {
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() {

@ -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>

@ -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();
});
});

@ -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();
});
}
}

@ -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

Loading…
Cancel
Save