Release 0.10.0 (#571)

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

17
.browserslistrc Normal file
View File

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

View File

@ -1,4 +1,4 @@
# Editor configuration, see http://editorconfig.org
# Editor configuration, see https://editorconfig.org
root = true
[*]
@ -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

View File

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

View File

@ -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,16 +52,19 @@
"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,15 +128,6 @@
"devServerTarget": "RTLApp:serve:production"
}
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": "e2e/tsconfig.e2e.json",
"exclude": [
"**/node_modules/**"
]
}
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -12,8 +12,8 @@
<link rel="mask-icon" href="assets/images/favicon-light/safari-pinned-tab.svg" color="#5bbad5">
<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

View File

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

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

5
app.js
View File

@ -50,9 +50,10 @@ const invoicesECLRoutes = require("./routes/eclair/invoices");
const paymentsECLRoutes = require("./routes/eclair/payments");
const 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

View File

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

View File

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

View File

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

View File

@ -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,22 +44,35 @@ 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!'});
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!"
error: "Invalid password! Application will be locked after " + (ALLOWED_LOGIN_ATTEMPTS - failed.count) + " more unsuccessful attempts!"
});
}
}
}
};
exports.resetPassword = (req, res, next) => {

View File

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

View File

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

View File

@ -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) => {

View File

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

View File

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

View File

@ -20,6 +20,7 @@ parameters have `default` values for initial setup and can be updated after RTL
"lnImplementation": "<LNP implementation, Allowed values LND/CLT/ECL. Default 'LND', Required>",
"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>",
}
}
]

View File

@ -23,10 +23,10 @@ This step is only required to configure the nodes, which will be remotely connec
5. `SSO` section can be used for single-sign-on from applications like BTCPayserver. If using RTL as a stand-alone app to connect with the nodes, keep the `rtlSSO=0` and ignore the rest of `SSO` section.
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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,12 @@
/* 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"
]
}

View File

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

6596
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -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`);

View File

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

View File

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

View File

@ -47,28 +47,26 @@ export class AppComponent implements OnInit, AfterViewInit, OnDestroy {
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 if(matches.breakpoints[Breakpoints.Large]) {
this.commonService.setScreenSize(ScreenSizeEnum.LG);
this.smallScreen = false;
} else {
this.commonService.setScreenSize(ScreenSizeEnum.LG);
this.xSmallScreen = false;
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);

View File

@ -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 { 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 { SessionService } from './shared/services/session.service';
import { LoopService } from './shared/services/loop.service';
import { DataService } from './shared/services/data.service';
import { CommonService } from './shared/services/common.service';
import { RTLReducer } from './store/rtl.reducers';
import { 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
],
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
!environment.production ? StoreDevtoolsModule.instrument() : []
],
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]
})

View File

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

View File

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

View File

@ -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: '', pathMatch: 'full', redirectTo: 'home' },
{ 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: '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: 'signverify', component: CLSignVerifyMessageComponent, canActivate: [CLUnlockedGuard] },
{ path: '**', component: NotFoundComponent },
{ path: 'network', redirectTo: 'rates' },
{ path: 'wallet', redirectTo: 'home' },
{ path: 'backup', redirectTo: 'home' }
]}
]
}
];
export const CLRouting: ModuleWithProviders = RouterModule.forChild(ClRoutes);
export const CLRouting: ModuleWithProviders<RouterModule> = RouterModule.forChild(ClRoutes);

View File

@ -12,7 +12,7 @@
<mat-progress-bar class="dashboard-progress-bar this-channel-bar" mode="determinate" color="accent" value="{{channelBalances.localBalance && channelBalances.localBalance > 0 ? ((+channelBalances.localBalance/((+channelBalances.localBalance)+(+channelBalances.remoteBalance)))*100) : 0}}"></mat-progress-bar>
</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>

View File

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

View File

@ -1,11 +1,11 @@
<div fxLayout="column" fxLayoutAlign="space-between stretch" fxFlex="100">
<div fxLayout="column" fxLayoutAlign="space-between stretch" fxFlex="100" [ngClass]="{'mb-4': screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM, 'mb-2': screenSize === screenSizeEnum.MD, 'mb-1': screenSize === screenSizeEnum.LG || screenSize === screenSizeEnum.XL}">
<div fxLayout="column" fxFlex="9" fxLayoutAlign="end start">
<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>

View File

@ -1,22 +1,30 @@
import { Component, Input } from '@angular/core';
import { Component, Input, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { 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');
}
}

View File

@ -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>
<mat-card-title fxLayoutAlign="space-between center">
<div>
<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">
</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>
<mat-card-title fxLayoutAlign="space-between center">
<div>
<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">
</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>

View File

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

View File

@ -13,7 +13,6 @@ import { UserPersonaEnum, ScreenSizeEnum } from '../../shared/services/consts-en
import { ChannelsStatus, GetInfo, Fees, Channel, Balance, FeeRates } from '../../shared/models/clModels';
import { 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 }
];
}

View File

@ -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) { }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
<div fxLayout="column">
<div fxLayout="column" class="padding-gap-x-large">
<div fxLayout="row" fxLayoutAlign="space-between end" fxLayoutAlign.gt-sm="start end">
<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">

View File

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

View File

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

View File

@ -26,12 +26,12 @@ import * as fromRTLReducer from '../../../store/rtl.reducers';
import * as sha256 from 'sha256';
@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) {

View File

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

View File

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

View File

@ -5,11 +5,11 @@
<span class="page-title">UTXOs</span>
</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}">

View File

@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { Component, OnInit, OnDestroy, ViewChild, AfterViewInit } from '@angular/core';
import { Subject } from 'rxjs';
import { 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);
}

View File

@ -1,4 +1,4 @@
<div fxLayout="row" fxLayoutAlign="start center" class="padding-gap-x page-title-container">
<div fxLayout="row" fxLayoutAlign="start center" class="page-title-container">
<fa-icon [icon]="faChartPie" class="page-title-img mr-1"></fa-icon>
<span class="page-title">On-chain Balance</span>
</div>
@ -9,37 +9,22 @@
</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>
<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>
</mat-tab>
<mat-tab label="Sweep All">
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="padding-gap">
<div fxLayout="row">
<button mat-flat-button color="primary" type="button" tabindex="3" (click)="openSendFundsModal(true)">Sweep All</button>
</div>
</div>
</mat-tab>
</mat-tab-group>
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="padding-gap-x-large">
<div fxLayout="row">
<rtl-cl-on-chain-transaction-history fxLayout="row" fxFlex="100"></rtl-cl-on-chain-transaction-history>
</div>
</div>
</mat-card-content>
</mat-card>
</div>

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { Component, OnInit, OnDestroy, ViewChild, AfterViewInit } from '@angular/core';
import { Subject } from 'rxjs';
import { 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);
}

View File

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

View File

@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { Component, OnInit, OnDestroy, ViewChild, AfterViewInit } from '@angular/core';
import { Subject } from 'rxjs';
import { 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);
}

View File

@ -1,16 +1,22 @@
<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>
<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>
<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-large">
<router-outlet></router-outlet>
</div>
</div>
</div>

View File

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

View File

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

View File

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

View File

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

View File

@ -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...'));

View File

@ -1,4 +1,4 @@
<div fxLayout="row" fxLayoutAlign="start center" class="padding-gap-x page-title-container">
<div fxLayout="row" fxLayoutAlign="start center" class="page-title-container">
<fa-icon [icon]="faChartPie" class="page-title-img mr-1"></fa-icon>
<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>

View File

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

View File

@ -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() {

View File

@ -3,31 +3,31 @@
<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>

View File

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

View File

@ -1,4 +1,4 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { Component, OnInit, OnDestroy, ViewChild, AfterViewInit } from '@angular/core';
import { Subject } from 'rxjs';
import { 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() {

View File

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

View File

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

View File

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

View File

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

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