Peers
pull/209/head
Shahana Farooqui 5 years ago
parent d05b0b36ce
commit e3668d2a37

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -9,5 +9,5 @@
<link rel="stylesheet" href="styles.13a9674cdbdfd014a4cf.css"></head>
<body>
<rtl-app></rtl-app>
<script src="runtime-es2015.c2339ff552442b5b783d.js" type="module"></script><script src="polyfills-es2015.af35579f5b57e97fcdea.js" type="module"></script><script src="runtime-es5.3cf369e62b41bc805002.js" nomodule></script><script src="polyfills-es5.64372fcf007b6e0e7247.js" nomodule></script><script src="main-es2015.e8595de1b22bb86c6d99.js" type="module"></script><script src="main-es5.8aac2c648af24da98478.js" nomodule></script></body>
<script src="runtime.57ae245e3c265cd0936f.js"></script><script src="polyfills-es5.763f4f23e8aee5ec234d.js" nomodule></script><script src="polyfills.e59b6f9dc696bd89cf7f.js"></script><script src="main.13ed415c7cb3709d892b.js"></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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -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++)o[a=i[p]]&&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)+"-es2015."+{1:"7e9111b7c3b5e28be38c",5:"eba8765707aa8a3bfc1f",6:"f1092df1bd789fbce299"}[e]+".js"}(e);var c=new Error;u=function(r){i.onerror=i.onload=null,clearTimeout(f);var t=o[e];if(0!==t){if(t){var n=r&&("load"===r.type?"missing":r.type),u=r&&r.target&&r.target.src;c.message="Loading chunk "+e+" failed.\n("+n+": "+u+")",c.name="ChunkLoadError",c.type=n,c.request=u,t[1](c)}o[e]=void 0}};var f=setTimeout(function(){u({type:"timeout",target:i})},12e4);i.onerror=i.onload=u,document.head.appendChild(i)}return Promise.all(r)},a.m=e,a.c=n,a.d=function(e,r,t){a.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,r){if(1&r&&(e=a(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(a.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)a.d(t,n,(function(r){return e[r]}).bind(null,n));return t},a.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(r,"a",r),r},a.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},a.p="",a.oe=function(e){throw console.error(e),e};var i=window.webpackJsonp=window.webpackJsonp||[],c=i.push.bind(i);i.push=r,i=i.slice();for(var f=0;f<i.length;f++)r(i[f]);var l=c;t()}([]);

@ -1 +0,0 @@
!function(e){function r(r){for(var n,i,a=r[0],c=r[1],f=r[2],p=0,s=[];p<a.length;p++)o[i=a[p]]&&s.push(o[i][0]),o[i]=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,a=1;a<t.length;a++)0!==o[t[a]]&&(n=!1);n&&(u.splice(r--,1),e=i(i.s=t[0]))}return e}var n={},o={0:0},u=[];function i(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,i),t.l=!0,t.exports}i.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,a=document.createElement("script");a.charset="utf-8",a.timeout=120,i.nc&&a.setAttribute("nonce",i.nc),a.src=function(e){return i.p+""+({}[e]||e)+"-es5."+{1:"25aced163e24b1852e25",4:"20db81847c2dde69946d",5:"e5a5e954e6d78b52fbf7"}[e]+".js"}(e);var c=new Error;u=function(r){a.onerror=a.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:a})},12e4);a.onerror=a.onload=u,document.head.appendChild(a)}return Promise.all(r)},i.m=e,i.c=n,i.d=function(e,r,t){i.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,r){if(1&r&&(e=i(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(i.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)i.d(t,n,(function(r){return e[r]}).bind(null,n));return t},i.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(r,"a",r),r},i.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},i.p="",i.oe=function(e){throw console.error(e),e};var a=window.webpackJsonp=window.webpackJsonp||[],c=a.push.bind(a);a.push=r,a=a.slice();for(var f=0;f<a.length;f++)r(a[f]);var l=c;t()}([]);

@ -0,0 +1 @@
!function(e){function r(r){for(var n,i,a=r[0],c=r[1],f=r[2],p=0,s=[];p<a.length;p++)o[i=a[p]]&&s.push(o[i][0]),o[i]=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,a=1;a<t.length;a++)0!==o[t[a]]&&(n=!1);n&&(u.splice(r--,1),e=i(i.s=t[0]))}return e}var n={},o={0:0},u=[];function i(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,i),t.l=!0,t.exports}i.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,a=document.createElement("script");a.charset="utf-8",a.timeout=120,i.nc&&a.setAttribute("nonce",i.nc),a.src=function(e){return i.p+""+({}[e]||e)+"."+{1:"54859a9ed4dbe675e082",6:"10cd8607c932f8229120",7:"2becbc02aff346bd8d1e"}[e]+".js"}(e);var c=new Error;u=function(r){a.onerror=a.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:a})},12e4);a.onerror=a.onload=u,document.head.appendChild(a)}return Promise.all(r)},i.m=e,i.c=n,i.d=function(e,r,t){i.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,r){if(1&r&&(e=i(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(i.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)i.d(t,n,(function(r){return e[r]}).bind(null,n));return t},i.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(r,"a",r),r},i.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},i.p="",i.oe=function(e){throw console.error(e),e};var a=window.webpackJsonp=window.webpackJsonp||[],c=a.push.bind(a);a.push=r,a=a.slice();for(var f=0;f<a.length;f++)r(a[f]);var l=c;t()}([]);

@ -30,6 +30,14 @@ const infoCLRoutes = require("./routes/c-lightning/getInfo");
const feesCLRoutes = require("./routes/c-lightning/fees");
const balanceCLRoutes = require("./routes/c-lightning/balance");
const channelsCLRoutes = require("./routes/c-lightning/channels");
const invoicesCLRoutes = require("./routes/c-lightning/invoices");
const newAddressCLRoutes = require("./routes/c-lightning/newAddress");
const paymentsCLRoutes = require("./routes/c-lightning/payments");
const payReqCLRoutes = require("./routes/c-lightning/payReq");
const peersCLRoutes = require("./routes/c-lightning/peers");
const switchCLRoutes = require("./routes/c-lightning/switch");
const transactionsCLRoutes = require("./routes/c-lightning/transactions");
const walletCLRoutes = require("./routes/c-lightning/wallet");
app.use(cookieParser(common.secret_key));
app.use(bodyParser.json());
@ -72,6 +80,14 @@ app.use(apiCLRoot + "getinfo", infoCLRoutes);
app.use(apiCLRoot + "fees", feesCLRoutes);
app.use(apiCLRoot + "balance", balanceCLRoutes);
app.use(apiCLRoot + "channels", channelsCLRoutes);
app.use(apiCLRoot + "invoices", invoicesCLRoutes);
app.use(apiCLRoot + "newaddress", newAddressCLRoutes);
app.use(apiCLRoot + "payments", paymentsCLRoutes);
app.use(apiCLRoot + "payreq", payReqCLRoutes);
app.use(apiCLRoot + "peers", peersCLRoutes);
app.use(apiCLRoot + "switch", switchCLRoutes);
app.use(apiCLRoot + "transactions", transactionsCLRoutes);
app.use(apiCLRoot + "wallet", walletCLRoutes);
app.use((req, res, next) => {
res.sendFile(path.join(__dirname, "angular", "index.html"));

@ -16,94 +16,95 @@ common.nodes = [];
common.selectedNode = {};
common.getSelLNServerUrl = () => {
return common.selectedNode.ln_server_url;
return common.selectedNode.ln_server_url;
};
common.getOptions = () => {
return common.selectedNode.options;
return common.selectedNode.options;
};
common.setOptions = () => {
if(undefined !== common.nodes[0].options && undefined !== common.nodes[0].options.headers) { return; }
try {
common.nodes.forEach(node => {
node.options = {
url: '',
rejectUnauthorized: false,
json: true,
form: ''
if (undefined !== common.nodes[0].options && undefined !== common.nodes[0].options.headers) { return; }
try {
common.nodes.forEach(node => {
node.options = {
url: '',
rejectUnauthorized: false,
json: true,
form: ''
};
if(node.ln_implementation.toLowerCase() !== 'clightning') {
node.options.headers = {'Grpc-Metadata-macaroon': fs.readFileSync(node.macaroon_path + '/admin.macaroon').toString('hex')};
if (node.ln_implementation.toLowerCase() !== 'clightning') {
node.options.headers = { 'Grpc-Metadata-macaroon': fs.readFileSync(node.macaroon_path + '/admin.macaroon').toString('hex') };
} else {
node.options.headers = {'macaroon': Buffer.from(fs.readFileSync(node.macaroon_path + '/access.macaroon')).toString("base64")};
node.options.headers = { 'macaroon': Buffer.from(fs.readFileSync(node.macaroon_path + '/access.macaroon')).toString("base64") };
}
});
// Options cannot be set before selected node initializes. Updating selected node's options separatly
common.selectedNode.options = {
url: '',
rejectUnauthorized: false,
json: true,
form: ''
});
// Options cannot be set before selected node initializes. Updating selected node's options separatly
common.selectedNode.options = {
url: '',
rejectUnauthorized: false,
json: true,
form: ''
};
if (common.selectedNode.ln_implementation.toLowerCase() !== 'clightning') {
common.selectedNode.options.headers = {'Grpc-Metadata-macaroon': fs.readFileSync(common.selectedNode.macaroon_path + '/admin.macaroon').toString('hex')};
common.selectedNode.options.headers = { 'Grpc-Metadata-macaroon': fs.readFileSync(common.selectedNode.macaroon_path + '/admin.macaroon').toString('hex') };
} else {
node.selectedNode.options.headers = {'macaroon': Buffer.from(fs.readFileSync(node.macaroon_path + '/access.macaroon')).toString("base64")};
}
} catch(err) {
console.error('Common Set Options Error:' + JSON.stringify(err));
common.nodes.forEach(node => {
node.options = {
url: '',
rejectUnauthorized: false,
json: true,
form: ''
};
});
// Options cannot be set before selected node initializes. Updating selected node's options separatly
common.selectedNode.options = {
url: '',
rejectUnauthorized: false,
json: true,
form: ''
};
}
common.selectedNode.options.headers = { 'macaroon': Buffer.from(fs.readFileSync(common.selectedNode.macaroon_path + '/access.macaroon')).toString("base64") };
}
} catch (err) {
console.error('Common Set Options Error:' + JSON.stringify(err));
common.nodes.forEach(node => {
node.options = {
url: '',
rejectUnauthorized: false,
json: true,
form: ''
};
});
// Options cannot be set before selected node initializes. Updating selected node's options separatly
common.selectedNode.options = {
url: '',
rejectUnauthorized: false,
json: true,
form: ''
};
}
}
common.findNode = (selNodeIndex) => {
return common.nodes.find(node => node.index == selNodeIndex);
return common.nodes.find(node => node.index == selNodeIndex);
}
common.convertToBTC = (num) => {
return (num / 100000000).toFixed(6);
return (num / 100000000).toFixed(6);
};
common.convertTimestampToDate = (num) => {
return new Date(+num * 1000).toUTCString();
return new Date(+num * 1000).toUTCString();
};
common.sortAscByKey = (array, key) => {
return array.sort(function (a, b) {
var x = a[key]; var y = b[key];
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
});
return array.sort(function (a, b) {
var x = a[key]; var y = b[key];
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
});
}
common.sortDescByKey = (array, key) => {
return array.sort(function (a, b) {
var x = a[key]; var y = b[key];
return ((x > y) ? -1 : ((x < y) ? 1 : 0));
});
const temp = array.sort(function (a, b) {
var x = a[key]; var y = b[key];
return ((x > y) ? -1 : ((x < y) ? 1 : 0));
});
return temp;
}
common.newestOnTop = (array, key, value) => {
var index = array.findIndex(function (item) {
return item[key] === value
});
var newlyAddedRecord = array.splice(index, 1);
array.unshift(newlyAddedRecord[0]);
return array;
var index = array.findIndex(function (item) {
return item[key] === value
});
var newlyAddedRecord = array.splice(index, 1);
array.unshift(newlyAddedRecord[0]);
return array;
}
module.exports = common;

@ -13,7 +13,6 @@ exports.getInfo = (req, res, next) => {
} else {
logger.info({fileName:'GetInfo', msg: 'Single Node Setup!'});
}
common.nodes.map(node => { if (node.lnImplementation === 'LND') { connect.getAllNodeAllChannelBackup(node); }});
logger.info({fileName: 'GetInfo', msg: 'Calling getinfo from c-lightning server url: ' + options.url});
request(options).then((body) => {
logger.info({fileName: 'GetInfo', msg: JSON.stringify(body)});

@ -0,0 +1,88 @@
var request = require('request-promise');
var common = require('../../common');
var logger = require('../logger');
var options = {};
exports.getInvoice = (req, res, next) => {
options = common.getOptions();
options.url = common.getSelLNServerUrl() + '/invoice/' + req.params.rHashStr;
request(options).then((body) => {
logger.info({fileName: 'Invoice', msg: 'Invoice Info Received: ' + JSON.stringify(body)});
if(undefined === body || body.error) {
res.status(500).json({
message: "Fetching Invoice Info Failed!",
error: (undefined === body) ? 'Error From Server!' : body.error
});
}
res.status(200).json(body);
})
.catch((err) => {
return res.status(500).json({
message: "Fetching Invoice Info Failed!",
error: err.error
});
});
};
exports.listInvoices = (req, res, next) => {
options = common.getOptions();
options.url = common.getSelLNServerUrl() + '/invoices?num_max_invoices=' + req.query.num_max_invoices + '&index_offset=' + req.query.index_offset +
'&reversed=' + req.query.reversed;
request(options).then((body) => {
const body_str = (undefined === body) ? '' : JSON.stringify(body);
const search_idx = (undefined === body) ? -1 : body_str.search('Not Found');
logger.info({fileName: 'Invoice', msg: 'Invoices List Received: ' + body_str});
if(undefined === body || search_idx > -1 || body.error) {
res.status(500).json({
message: "Fetching Invoice Info failed!",
error: (undefined === body || search_idx > -1) ? 'Error From Server!' : body.error
});
} else {
if (undefined !== body.invoices) {
body.invoices.forEach(invoice => {
invoice.creation_date_str = (undefined === invoice.creation_date) ? '' : common.convertTimestampToDate(invoice.creation_date);
invoice.settle_date_str = (undefined === invoice.settle_date) ? '' : common.convertTimestampToDate(invoice.settle_date);
invoice.btc_value = (undefined === invoice.value) ? 0 : common.convertToBTC(invoice.value);
invoice.btc_amt_paid_sat = (undefined === invoice.amt_paid_sat) ? 0 : common.convertToBTC(invoice.amt_paid_sat);
});
body.invoices = common.sortDescByKey(body.invoices, 'creation_date');
}
logger.info({fileName: 'Invoice', msg: 'Invoices List Received: ' + JSON.stringify(body)});
res.status(200).json(body);
}
})
.catch(function (err) {
return res.status(500).json({
message: "Fetching Invoice Info failed!",
error: err.error
});
});
};
exports.addInvoice = (req, res, next) => {
options = common.getOptions();
options.url = common.getSelLNServerUrl() + '/invoices';
options.form = JSON.stringify({
memo: req.body.memo,
value: req.body.amount,
private: req.body.private,
expiry: req.body.expiry
});
request.post(options).then((body) => {
logger.info({fileName: 'Invoice', msg: 'Add Invoice Responce: ' + JSON.stringify(body)});
if(undefined === body || body.error) {
res.status(500).json({
message: "Add Invoice Failed!",
error: (undefined === body) ? 'Error From Server!' : body.error
});
} else {
res.status(201).json(body);
}
})
.catch(function (err) {
return res.status(500).json({
message: "Add Invoice Failed!",
error: err.error
});
});
};

@ -0,0 +1,17 @@
var request = require('request-promise');
var common = require('../../common');
var options = {};
exports.getNewAddress = (req, res, next) => {
options = common.getOptions();
options.url = common.getSelLNServerUrl() + '/newaddr?addrType=' + req.query.type;
request(options).then((body) => {
res.status(200).json(body);
})
.catch(function (err) {
return res.status(500).json({
message: "Fetching new address failed!",
error: err.error
});
});
};

@ -0,0 +1,30 @@
var request = require('request-promise');
var common = require('../../common');
var logger = require('../logger');
var options = {};
exports.decodePayment = (req, res, next) => {
options = common.getOptions();
options.url = common.getSelLNServerUrl() + '/payreq/' + req.params.payRequest;
request(options).then((body) => {
const body_str = (undefined === body) ? '' : JSON.stringify(body);
const search_idx = (undefined === body) ? -1 : body_str.search('Not Found');
logger.info({fileName: 'PayReq', msg: 'Payment Decodd Received: ' + body_str});
if(undefined === body || search_idx > -1 || body.error) {
res.status(500).json({
message: "Payment Request Decode Failed!",
error: (undefined === body || search_idx > -1) ? 'Error From Server!' : body.error
});
} else {
body.btc_num_satoshis = (undefined === body.num_satoshis) ? 0 : common.convertToBTC(body.num_satoshis);
body.timestamp_str = (undefined === body.timestamp) ? '' : common.convertTimestampToDate(body.timestamp);
res.status(200).json(body);
}
})
.catch(function (err) {
return res.status(500).json({
message: "Payment Request Decode Failed!",
error: err.error
});
});
};

@ -0,0 +1,34 @@
var request = require('request-promise');
var common = require('../../common');
var logger = require('../logger');
var options = {};
exports.getPayments = (req, res, next) => {
options = common.getOptions();
options.url = common.getSelLNServerUrl() + '/payments';
request(options).then((body) => {
const body_str = (undefined === body) ? '' : JSON.stringify(body);
const search_idx = (undefined === body) ? -1 : body_str.search('Not Found');
logger.info({fileName: 'Payments', msg: 'Payment Decoded Received: ' + body_str});
if(undefined === body || search_idx > -1 || body.error) {
res.status(500).json({
message: "Payments List Failed!",
error: (undefined === body || search_idx > -1) ? 'Error From Server!' : body.error
});
} else {
if (undefined !== body.payments) {
body.payments.forEach(payment => {
payment.creation_date_str = (undefined === payment.creation_date) ? '' : common.convertTimestampToDate(payment.creation_date);
});
body.payments = common.sortDescByKey(body.payments, 'creation_date');
}
res.status(200).json(body.payments);
}
})
.catch(function (err) {
return res.status(500).json({
message: "Payments List Failed!",
error: err.error
});
});
};

@ -0,0 +1,72 @@
var request = require('request-promise');
var common = require('../../common');
var logger = require('../logger');
var options = {};
exports.getPeers = (req, res, next) => {
options = common.getOptions();
options.url = common.getSelLNServerUrl() + '/peer/listPeers';
request(options).then(function (body) {
let peers = (undefined !== body) ? common.sortDescByKey(body, 'alias') : [];
logger.info({fileName: 'Peers', msg: 'Peers with Alias: ' + JSON.stringify(peers)});
res.status(200).json(peers);
})
.catch((err) => {
return res.status(500).json({
message: "Peers Fetch Failed!",
error: err.error
});
});
};
exports.postPeer = (req, res, next) => {
options = common.getOptions();
options.url = common.getSelLNServerUrl() + '/peer/connect';
options.body = req.body;
request.post(options, (error, response, body) => {
if(undefined === body || body.error) {
res.status(500).json({
message: "Adding peer failed!",
error: (undefined === body) ? 'Error From Server!' : body.error
});
} else {
logger.info({fileName: 'Peers', msg: 'Peer Added: ' + JSON.stringify(body)});
options.url = common.getSelLNServerUrl() + '/peer/listPeers';
request(options).then(function (body) {
let peers = (undefined !== body) ? common.sortDescByKey(body, 'alias') : [];
peers = common.newestOnTop(peers, 'id', req.body.id);
logger.info({fileName: 'Peers', msg: 'Peer with Newest On Top: ' + JSON.stringify(peers)});
logger.info({fileName: 'Peers', msg: 'Peer Added Successfully'});
res.status(201).json(peers);
}).catch((err) => {
return res.status(500).json({
message: "Peer Add Failed!",
error: err.error
});
});
}
});
};
exports.deletePeer = (req, res, next) => {
options = common.getOptions();
options.url = common.getSelLNServerUrl() + '/peer/disconnect/' + req.params.peerId + '?force=' + req.query.force;
request.delete(options).then((body) => {
logger.info({fileName: 'Peers', msg: 'Detach Peer Response: ' + JSON.stringify(body)});
if(undefined === body || body.error) {
res.status(500).json({
message: "Detach peer failed!",
error: (undefined === body) ? 'Error From Server!' : body.error
});
} else {
logger.info({fileName: 'Peers', msg: 'Peer Detached: ' + req.params.peerId});
res.status(204).json({});
}
})
.catch((err) => {
return res.status(500).json({
message: "Detach Peer Failed!",
error: err.error
});
});
};

@ -0,0 +1,50 @@
var request = require('request-promise');
var common = require('../../common');
var logger = require('../logger');
var options = {};
exports.forwardingHistory = (req, res, next) => {
options = common.getOptions();
options.url = common.getSelLNServerUrl() + '/switch';
options.form = {};
if (undefined !== req.body.num_max_events) {
options.form.num_max_events = req.body.num_max_events;
}
if (undefined !== req.body.index_offset) {
options.form.index_offset = req.body.index_offset;
}
if (undefined !== req.body.end_time) {
options.form.end_time = req.body.end_time;
}
if (undefined !== req.body.start_time) {
options.form.start_time = req.body.start_time;
}
options.form = JSON.stringify(options.form);
logger.info({fileName: 'Switch', msg: 'Switch Post Options: ' + JSON.stringify(options)});
request.post(options).then((body) => {
logger.info({fileName: 'Switch', msg: 'Switch Post Response: ' + JSON.stringify(body)});
if(undefined === body || body.error) {
logger.error({fileName: 'Switch', lineNum: 27, msg: 'Switch Post Erroe: ' + JSON.stringify((undefined === body) ? 'Error From Server!' : body.error)});
res.status(500).json({
message: "Switch post failed!",
error: (undefined === body) ? 'Error From Server!' : body.error
});
} else {
if (undefined !== body.forwarding_events) {
body.forwarding_events.forEach(event => {
event.timestamp_str = (undefined === event.timestamp) ? '' : common.convertTimestampToDate(event.timestamp);
});
body.forwarding_events = common.sortDescByKey(body.forwarding_events, 'timestamp');
}
logger.info({fileName: 'Switch', msg: 'Forwarding History Received: ' + JSON.stringify(body)});
res.status(201).json(body);
}
})
.catch(function (err) {
logger.error({fileName: 'Switch', lineNum: 44, msg: 'Switch Post Error: ' + JSON.stringify(err)});
return res.status(500).json({
message: "Switch post failed!",
error: err.error
});
});
};

@ -0,0 +1,66 @@
var request = require('request-promise');
var common = require('../../common');
var logger = require('../logger');
var options = {};
exports.getTransactions = (req, res, next) => {
options = common.getOptions();
options.url = common.getSelLNServerUrl() + '/transactions';
request(options).then((body) => {
const body_str = (undefined === body) ? '' : JSON.stringify(body);
const search_idx = (undefined === body) ? -1 : body_str.search('Not Found');
logger.info({fileName: 'Transactions', msg: 'Transaction Received: ' + body_str});
if(undefined === body || search_idx > -1 || body.error) {
res.status(500).json({
message: "Fetching Transactions Failed!",
error: (undefined === body || search_idx > -1) ? 'Error From Server!' : body.error
});
} else {
if (undefined !== body.transactions) {
body.transactions.forEach(transaction => {
transaction.time_stamp_str = (undefined === transaction.time_stamp) ? '' : common.convertTimestampToDate(transaction.time_stamp);
});
body.transactions = common.sortDescByKey(body.transactions, 'time_stamp');
}
res.status(200).json(body.transactions);
}
})
.catch(function (err) {
return res.status(500).json({
message: "Fetching Transactions Failed!",
error: err.error
});
});
};
exports.postTransactions = (req, res, next) => {
options = common.getOptions();
options.url = common.getSelLNServerUrl() + '/transactions';
options.form = {
amount: req.body.amount,
addr: req.body.address,
sat_per_byte: req.body.fees,
target_conf: req.body.blocks
};
if (req.body.sendAll) {
options.form.send_all = req.body.sendAll;
}
options.form = JSON.stringify(options.form);
request.post(options).then((body) => {
logger.info({fileName: 'Transactions', msg: 'Transaction Post Response: ' + JSON.stringify(body)});
if(undefined === body || body.error) {
res.status(500).json({
message: "Transactions post failed!",
error: (undefined === body) ? 'Error From Server!' : body.error
});
} else {
res.status(201).json(body);
}
})
.catch(function (err) {
return res.status(500).json({
message: "Transactions post failed!",
error: err.error
});
});
};

@ -0,0 +1,93 @@
var request = require('request-promise');
var common = require('../../common');
var atob = require('atob');
var logger = require('../logger');
var options = {};
exports.genSeed = (req, res, next) => {
options = common.getOptions();
options.url = common.getSelLNServerUrl() + '/genseed';
if (undefined !== req.params.passphrase) {
options.form = JSON.stringify({aezeed_passphrase: atob(req.params.passphrase)});
}
request(options).then((body) => {
if(undefined === body || body.error) {
res.status(500).json({
message: "Genseed failed!",
error: (undefined === body) ? 'Error From Server!' : body.error
});
} else {
res.status(200).json(body);
}
})
.catch(function (err) {
return res.status(500).json({
message: "Genseed failed!",
error: err.error
});
});
}
exports.operateWallet = (req, res, next) => {
options = common.getOptions();
options.method = 'POST';
if (undefined === req.params.operation || req.params.operation === 'unlockwallet') {
options.url = common.getSelLNServerUrl() + '/unlockwallet';
options.form = JSON.stringify({
wallet_password: Buffer.from(atob(req.body.wallet_password)).toString('base64')
});
err_message = 'Unlocking wallet failed! Verify that lnd is running and the wallet is locked!';
} else {
options.url = common.getSelLNServerUrl() + '/initwallet';
if (undefined !== req.body.aezeed_passphrase && req.body.aezeed_passphrase !== '') {
options.form = JSON.stringify({
wallet_password: Buffer.from(atob(req.body.wallet_password)).toString('base64'),
cipher_seed_mnemonic: req.body.cipher_seed_mnemonic,
aezeed_passphrase: Buffer.from(atob(req.body.aezeed_passphrase)).toString('base64')
});
} else {
options.form = JSON.stringify({
wallet_password: Buffer.from(atob(req.body.wallet_password)).toString('base64'),
cipher_seed_mnemonic: req.body.cipher_seed_mnemonic
});
}
err_message = 'Initializing wallet failed!';
}
request(options).then((body) => {
logger.info({fileName: 'Wallet', msg: 'Wallet Response: ' + JSON.stringify(body)});
const body_str = (undefined === body) ? '' : JSON.stringify(body);
const search_idx = (undefined === body) ? -1 : body_str.search('Not Found');
if(undefined === body) {
res.status(500).json({
message: err_message,
error: (error) ? error : err_message
});
} else if(search_idx > -1) {
res.status(500).json({
message: err_message,
error: err_message
});
} else if(body.error) {
if((body.code === 1 && body.error === 'context canceled') || (body.code === 14 && body.error === 'transport is closing')) {
res.status(201).json('Successful');
} else {
res.status(500).json({
message: err_message,
error: body.error
});
}
} else {
res.status(201).json('Successful');
}
}).catch(error => {
logger.error({fileName: 'Wallet', lineNum: 83, msg: 'Wallet Response: ' + JSON.stringify(error.error)});
if((error.error.code === 1 && error.error.error === 'context canceled') || (error.error.code === 14 && error.error.error === 'transport is closing')) {
res.status(201).json('Successful');
} else {
res.status(500).json({
message: err_message,
error: error.message
});
}
});
};

@ -0,0 +1,10 @@
const invoicesController = require("../../controllers/c-lightning/invoices");
const express = require("express");
const router = express.Router();
const authCheck = require("../authCheck");
router.get("/", authCheck, invoicesController.listInvoices);
router.get("/:rHashStr", authCheck, invoicesController.getInvoice);
router.post("/", authCheck, invoicesController.addInvoice);
module.exports = router;

@ -0,0 +1,8 @@
const NewAddressController = require("../../controllers/c-lightning/newAddress");
const express = require("express");
const router = express.Router();
const authCheck = require("../authCheck");
router.get("/", authCheck, NewAddressController.getNewAddress);
module.exports = router;

@ -0,0 +1,8 @@
const PayRequestController = require("../../controllers/c-lightning/payReq");
const express = require("express");
const router = express.Router();
const authCheck = require("../authCheck");
router.get("/:payRequest", authCheck, PayRequestController.decodePayment);
module.exports = router;

@ -0,0 +1,8 @@
const PaymentsController = require("../../controllers/c-lightning/payments");
const express = require("express");
const router = express.Router();
const authCheck = require("../authCheck");
router.get("/", authCheck, PaymentsController.getPayments);
module.exports = router;

@ -0,0 +1,10 @@
const PeersController = require("../../controllers/c-lightning/peers");
const express = require("express");
const router = express.Router();
const authCheck = require("../authCheck");
router.get("/", authCheck, PeersController.getPeers);
router.post("/", authCheck, PeersController.postPeer);
router.delete("/:peerId", authCheck, PeersController.deletePeer);
module.exports = router;

@ -0,0 +1,8 @@
const SwitchController = require("../../controllers/c-lightning/switch");
const express = require("express");
const router = express.Router();
const authCheck = require("../authCheck");
router.post("/", authCheck, SwitchController.forwardingHistory);
module.exports = router;

@ -0,0 +1,9 @@
const TransactionsController = require("../../controllers/c-lightning/transactions");
const express = require("express");
const router = express.Router();
const authCheck = require("../authCheck");
router.get("/", authCheck, TransactionsController.getTransactions);
router.post("/", authCheck, TransactionsController.postTransactions);
module.exports = router;

@ -0,0 +1,9 @@
const WalletController = require("../../controllers/c-lightning/wallet");
const express = require("express");
const router = express.Router();
const authCheck = require("../authCheck");
router.get("/genseed/:passphrase?", authCheck, WalletController.genSeed);
router.post("/:operation", authCheck, WalletController.operateWallet);
module.exports = router;

@ -0,0 +1,131 @@
<!-- <div fxLayout="column">
<div class="padding-gap">
<mat-card [ngClass]="{'flip': redirectedWithPeer}">
<mat-card-header>
<mat-card-subtitle>
<h2>Open Channel</h2>
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<form fxLayout="column" fxLayout.gt-sm="row wrap" (ngSubmit)="openChannelForm.form.valid && onOpenChannel(openChannelForm)" #openChannelForm="ngForm">
<mat-form-field fxFlex="40" fxLayoutAlign="start end">
<mat-select [(ngModel)]="selectedPeer" placeholder="Alias" name="peer_alias" tabindex="1" required name="selPeer" #selPeer="ngModel">
<mat-option *ngFor="let peer of peers" [value]="peer.pub_key">
{{peer.alias}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex="25" fxLayoutAlign="start end">
<input matInput [(ngModel)]="fundingAmount" placeholder="Amount ({{information?.smaller_currency_unit}})" type="number" step="1000" min="1" tabindex="2" required name="amount" #amount="ngModel">
<mat-hint>(Wallet Bal: {{totalBalance}}, Remaining Bal: {{totalBalance - ((fundingAmount) ? fundingAmount : 0)}})</mat-hint>
</mat-form-field>
<div fxFlex="15" tabindex="3" fxLayoutAlign="start center" class="chkbox-options">
<mat-checkbox [(ngModel)]="moreOptions" name="moreOptions" (change)="onMoreOptionsChange($event)">Options</mat-checkbox>
</div>
<span *ngIf="moreOptions" fxLayout="column" fxLayout.gt-sm="row wrap" fxFlex="80" fxLayoutAlign.gt-sm="space-between center">
<mat-form-field fxFlex="25" fxLayoutAlign="start end">
<mat-select tabindex="4" [(value)]="selTransType">
<mat-option *ngFor="let transType of transTypes" [value]="transType.id">
{{transType.name}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex="25" *ngIf="selTransType=='0'">
<input matInput placeholder="Channel Opening Priority" disabled>
</mat-form-field>
<mat-form-field fxFlex="25" *ngIf="selTransType=='1'">
<input matInput [(ngModel)]="transTypeValue.blocks" placeholder="Target Confirmation Blocks" type="number" name="blocks" step="1" min="0" required tabindex="5" #blocks="ngModel">
</mat-form-field>
<mat-form-field fxFlex="25" *ngIf="selTransType=='2'">
<input matInput [(ngModel)]="transTypeValue.fees" placeholder="Fee ({{information?.smaller_currency_unit}}/Byte)" type="number" name="fees" step="1" min="0" required tabindex="6" #fees="ngModel">
</mat-form-field>
<mat-checkbox fxFlex="25" fxFlex.lt-lg="35" tabindex="7" [(ngModel)]="spendUnconfirmed" name="spendUnconfirmed">Spend Unconfirmed Output</mat-checkbox>
<mat-checkbox fxFlex="20" fxFlex.lt-lg="15" tabindex="8" [(ngModel)]="isPrivate" name="isPrivate">Private</mat-checkbox>
</span>
<div fxFlex="10" fxLayoutAlign="end start">
<button fxFlex="90" fxLayoutAlign="center center" mat-raised-button color="primary" [disabled]="selectedPeer === '' || fundingAmount == null || (totalBalance - ((fundingAmount) ? fundingAmount : 0) < 0)" type="submit" tabindex="8">
<p *ngIf="(selectedPeer === '' || fundingAmount == null) && (selPeer.touched || selPeer.dirty) && (amount.touched || amount.dirty); else openText">Invalid Values</p>
<ng-template #openText><p>Open</p></ng-template>
</button>
</div>
<div fxFlex="10" fxLayoutAlign="end start">
<button fxFlex="90" fxLayoutAlign="center center" mat-raised-button color="accent" tabindex="9" type="reset" (click)="resetData()">Clear</button>
</div>
</form>
</mat-card-content>
</mat-card>
</div>
<div class="padding-gap">
<mat-card>
<mat-card-content fxFlex="100" fxLayout="column">
<div fxLayout="row" fxLayoutAlign="start start">
<mat-form-field fxFlex="30">
<input matInput (keyup)="applyFilter()" [(ngModel)]="selFilter" name="filter" placeholder="Filter">
</mat-form-field>
</div>
<div perfectScrollbar class="table-container mat-elevation-z8">
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
<table mat-table #table [dataSource]="channels" matSort [ngClass]="{'mat-elevation-z8 overflow-auto error-border': flgLoading[0]==='error','mat-elevation-z8 overflow-auto': true}">
<ng-container matColumnDef="close">
<th mat-header-cell *matHeaderCellDef> Close Channel </th>
<td mat-cell *matCellDef="let channel"><mat-icon color="accent" (click)="onChannelClose(channel)">link_off</mat-icon></td>
</ng-container>
<ng-container matColumnDef="update">
<th mat-header-cell *matHeaderCellDef><mat-icon color="accent" (click)="onChannelUpdate('all')">edit</mat-icon></th>
<td mat-cell *matCellDef="let channel"><mat-icon color="accent" (click)="onChannelUpdate(channel)">edit</mat-icon></td>
</ng-container>
<ng-container matColumnDef="active">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Status </th>
<td mat-cell *matCellDef="let channel"> {{(channel.active) ? 'Active' : 'Inactive'}} </td>
</ng-container>
<ng-container matColumnDef="chan_id">
<th mat-header-cell *matHeaderCellDef mat-sort-header> ID </th>
<td mat-cell *matCellDef="let channel"> {{channel.chan_id}} </td>
</ng-container>
<ng-container matColumnDef="remote_pubkey">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Pub Key </th>
<td mat-cell *matCellDef="let channel">
<div>{{channel.remote_pubkey | slice:0:10}}...</div></td>
</ng-container>
<ng-container matColumnDef="remote_alias">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Alias </th>
<td mat-cell *matCellDef="let channel">{{channel.remote_alias}}</td>
</ng-container>
<ng-container matColumnDef="capacity">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Capacity </th>
<td mat-cell *matCellDef="let channel"><span fxLayoutAlign="end center"> {{channel.capacity | number}} </span></td>
</ng-container>
<ng-container matColumnDef="local_balance">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Local Bal </th>
<td mat-cell *matCellDef="let channel"><span fxLayoutAlign="end center"> {{channel.local_balance | number}} </span></td>
</ng-container>
<ng-container matColumnDef="remote_balance">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Remote Bal </th>
<td mat-cell *matCellDef="let channel"><span fxLayoutAlign="end center"> {{channel.remote_balance | number}} </span></td>
</ng-container>
<ng-container matColumnDef="total_satoshis_sent">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> {{information?.smaller_currency_unit}} Sent </th>
<td mat-cell *matCellDef="let channel"><span fxLayoutAlign="end center"> {{channel.total_satoshis_sent | number}} </span></td>
</ng-container>
<ng-container matColumnDef="total_satoshis_received">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> {{information?.smaller_currency_unit}} Recv </th>
<td mat-cell *matCellDef="let channel"><span fxLayoutAlign="end center"> {{channel.total_satoshis_received | number}} </span></td>
</ng-container>
<ng-container matColumnDef="commit_fee">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Fee </th>
<td mat-cell *matCellDef="let channel"><span fxLayoutAlign="end center"> {{channel.commit_fee | number}} </span></td>
</ng-container>
<ng-container matColumnDef="private">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Private </th>
<td mat-cell *matCellDef="let channel"> {{(channel.private ? 'Private' : 'Public')}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: flgSticky;"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" (click)="onChannelClick(row, $event)"></tr>
</table>
</div>
</mat-card-content>
</mat-card>
</div>
</div>
-->
<h3>CHANNELS</h3>

@ -0,0 +1,67 @@
.mat-column-close, .mat-column-update, .mat-column-active, .mat-column-private {
flex: 0 0 6%;
min-width: 60px;
}
.mat-column-private {
padding-left: 10px;
}
.mat-cell.mat-column-close, .mat-column-update {
cursor: pointer;
}
.mat-column-chan_id {
flex: 0 0 16%;
min-width: 160px;
}
.mat-checkbox-inner-container:focus, .mat-checkbox-input:focus {
outline: none !important;
}
.size-40 {
font-size: 40px;
margin-left: -30%;
}
.mat-button-text {
font-size: 24px;
padding-bottom: 20px;
}
.flex-ellipsis {
padding-right: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
table {
width:100%;
}
.chkbox-options:focus {
outline: none !important;
}
.flip {
position: relative;
backface-visibility: hidden;
animation: spin 1.5s cubic-bezier(.175, .885, .32, 1.275) 2;
transform-style: preserve-3d;
transform: rotateX(0deg);
}
@-moz-keyframes spin {
from { -moz-transform: rotateX(0deg); }
to { -moz-transform: rotateX(360deg); }
}
@-webkit-keyframes spin {
from { -webkit-transform: rotateX(0deg); }
to { -webkit-transform: rotateX(360deg); }
}
@keyframes spin {
from { transform:rotateX(0deg); }
to { transform:rotateX(360deg); }
}

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

@ -0,0 +1,266 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { Router, NavigationStart, ActivatedRoute } from '@angular/router';
import { Subject, Observable } from 'rxjs';
import { takeUntil, filter, map, subscribeOn } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { Actions } from '@ngrx/effects';
import { MatTableDataSource, MatSort } from '@angular/material';
import { ChannelCL, PeerCL, GetInfoCL } from '../../shared/models/clModels';
import { LoggerService } from '../../shared/services/logger.service';
import { CLEffects } from '../store/cl.effects';
import { RTLEffects } from '../../store/rtl.effects';
import * as RTLActions from '../../store/rtl.actions';
import * as fromRTLReducer from '../../store/rtl.reducers';
@Component({
selector: 'rtl-cl-channels',
templateUrl: './channels.component.html',
styleUrls: ['./channels.component.scss']
})
export class CLChannelsComponent implements OnInit, OnDestroy {
@ViewChild(MatSort, { static: true }) sort: MatSort;
public totalBalance = 0;
public selectedPeer = '';
public fundingAmount: number;
public displayedColumns = [];
public channels: any;
public peers: PeerCL[] = [];
public information: GetInfoCL = {};
public flgLoading: Array<Boolean | 'error'> = [true];
public selectedFilter = '';
public myChanPolicy: any = {};
public selFilter = '';
public transTypes = [{id: '0', name: 'Default Priority'}, {id: '1', name: 'Target Confirmation Blocks'}, {id: '2', name: 'Fee'}];
public selTransType = '0';
public transTypeValue = {blocks: '', fees: ''};
public spendUnconfirmed = false;
public isPrivate = false;
public moreOptions = false;
public flgSticky = false;
public redirectedWithPeer = false;
private unsub: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
ngOnInit() {}
// constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private rtlEffects: RTLEffects, private clEffects: CLEffects, private activatedRoute: ActivatedRoute) {
// switch (true) {
// case (window.innerWidth <= 415):
// this.displayedColumns = ['close', 'update', 'active', 'chan_id', 'remote_alias'];
// break;
// case (window.innerWidth > 415 && window.innerWidth <= 730):
// this.displayedColumns = ['close', 'update', 'active', 'chan_id', 'remote_alias', 'capacity'];
// break;
// case (window.innerWidth > 730 && window.innerWidth <= 1024):
// this.displayedColumns = ['close', 'update', 'active', 'chan_id', 'remote_alias', 'capacity', 'local_balance', 'remote_balance'];
// break;
// case (window.innerWidth > 1024 && window.innerWidth <= 1280):
// this.flgSticky = true;
// this.displayedColumns = ['close', 'update', 'active', 'chan_id', 'remote_alias', 'capacity', 'local_balance', 'remote_balance', 'total_satoshis_sent',
// 'total_satoshis_received', 'commit_fee', 'private'];
// break;
// default:
// this.flgSticky = true;
// this.displayedColumns = ['close', 'update', 'active', 'chan_id', 'remote_pubkey', 'remote_alias', 'capacity', 'local_balance', 'remote_balance',
// 'total_satoshis_sent', 'total_satoshis_received', 'commit_fee', 'private'];
// break;
// }
// }
// ngOnInit() {
// this.store.select('cl')
// .pipe(takeUntil(this.unsub[0]))
// .subscribe((rtlStore) => {
// rtlStore.effectErrorsCl.forEach(effectsErr => {
// if (effectsErr.action === 'FetchChannels/all') {
// this.flgLoading[0] = 'error';
// }
// });
// this.information = rtlStore.information;
// this.peers = rtlStore.peers;
// this.peers.forEach(peer => {
// if (undefined === peer.alias || peer.alias === '') {
// peer.alias = peer.pub_key.substring(0, 15) + '...';
// }
// });
// this.totalBalance = +rtlStore.blockchainBalance.total_balance;
// if (undefined !== rtlStore.allChannels) {
// this.loadChannelsTable(rtlStore.allChannels);
// }
// if (this.flgLoading[0] !== 'error') {
// this.flgLoading[0] = (undefined !== rtlStore.allChannels) ? false : true;
// }
// this.logger.info(rtlStore);
// });
// this.activatedRoute.paramMap.subscribe(() => {
// this.selectedPeer = window.history.state.peer;
// this.redirectedWithPeer = (window.history.state.peer) ? true : false;
// });
// }
// onOpenChannel(form: any) {
// this.store.dispatch(new RTLActions.OpenSpinner('Opening Channel...'));
// let transTypeValue = '0';
// if (this.selTransType === '1') {
// transTypeValue = this.transTypeValue.blocks;
// } else if (this.selTransType === '2') {
// transTypeValue = this.transTypeValue.fees;
// }
// this.store.dispatch(new RTLActions.SaveNewChannel({
// selectedPeerPubkey: this.selectedPeer, fundingAmount: this.fundingAmount, private: this.isPrivate,
// transType: this.selTransType, transTypeValue: transTypeValue, spendUnconfirmed: this.spendUnconfirmed
// }));
// }
// onChannelUpdate(channelToUpdate: any) {
// if (channelToUpdate === 'all') {
// const titleMsg = 'Updated Values for ALL Channels';
// const confirmationMsg = {};
// this.store.dispatch(new RTLActions.OpenConfirmation({ width: '70%', data: {
// type: 'CONFIRM', titleMessage: titleMsg, noBtnText: 'Cancel', yesBtnText: 'Update', message: JSON.stringify(confirmationMsg), flgShowInput: true, getInputs: [
// {placeholder: 'Base Fee msat', inputType: 'number', inputValue: 1000},
// {placeholder: 'Fee Rate mili msat', inputType: 'number', inputValue: 1, min: 1},
// {placeholder: 'Time Lock Delta', inputType: 'number', inputValue: 144}
// ]
// }}));
// this.rtlEffects.closeConfirm
// .pipe(takeUntil(this.unsub[2]))
// .subscribe(confirmRes => {
// if (confirmRes) {
// const base_fee = confirmRes[0].inputValue;
// const fee_rate = confirmRes[1].inputValue;
// const time_lock_delta = confirmRes[2].inputValue;
// this.store.dispatch(new RTLActions.OpenSpinner('Updating Channel Policy...'));
// this.store.dispatch(new RTLActions.UpdateChannels({baseFeeMsat: base_fee, feeRate: fee_rate, timeLockDelta: time_lock_delta, chanPoint: 'all'}));
// }
// });
// } else {
// this.myChanPolicy = {fee_base_msat: 0, fee_rate_milli_msat: 0, time_lock_delta: 0};
// this.store.dispatch(new RTLActions.OpenSpinner('Fetching Channel Policy...'));
// this.store.dispatch(new RTLActions.ChannelLookup(channelToUpdate.chan_id.toString()));
// this.clEffects.setLookup
// .pipe(takeUntil(this.unsub[3]))
// .subscribe(resLookup => {
// this.logger.info(resLookup);
// if (resLookup.node1_pub === this.information.id) {
// this.myChanPolicy = resLookup.node1_policy;
// } else if (resLookup.node2_pub === this.information.id) {
// this.myChanPolicy = resLookup.node2_policy;
// } else {
// this.myChanPolicy = {fee_base_msat: 0, fee_rate_milli_msat: 0, time_lock_delta: 0};
// }
// this.logger.info(this.myChanPolicy);
// this.store.dispatch(new RTLActions.CloseSpinner());
// const titleMsg = 'Updated Values for Channel Point: ' + channelToUpdate.channel_point;
// const confirmationMsg = {};
// this.store.dispatch(new RTLActions.OpenConfirmation({ width: '70%', data: {
// type: 'CONFIRM', titleMessage: titleMsg, noBtnText: 'Cancel', yesBtnText: 'Update', message: JSON.stringify(confirmationMsg), flgShowInput: true, getInputs: [
// {placeholder: 'Base Fee msat', inputType: 'number', inputValue: (this.myChanPolicy.fee_base_msat === '') ? 0 : this.myChanPolicy.fee_base_msat},
// {placeholder: 'Fee Rate mili msat', inputType: 'number', inputValue: this.myChanPolicy.fee_rate_milli_msat, min: 1},
// {placeholder: 'Time Lock Delta', inputType: 'number', inputValue: this.myChanPolicy.time_lock_delta}
// ]
// }}));
// });
// this.rtlEffects.closeConfirm
// .pipe(takeUntil(this.unsub[2]))
// .subscribe(confirmRes => {
// if (confirmRes) {
// const base_fee = confirmRes[0].inputValue;
// const fee_rate = confirmRes[1].inputValue;
// const time_lock_delta = confirmRes[2].inputValue;
// this.store.dispatch(new RTLActions.OpenSpinner('Updating Channel Policy...'));
// this.store.dispatch(new RTLActions.UpdateChannels({baseFeeMsat: base_fee, feeRate: fee_rate, timeLockDelta: time_lock_delta, chanPoint: channelToUpdate.channel_point}));
// }
// });
// }
// this.applyFilter();
// }
// onChannelClose(channelToClose: ChannelCL) {
// this.store.dispatch(new RTLActions.OpenConfirmation({
// width: '70%', data: { type: 'CONFIRM', titleMessage: 'Closing channel: ' + channelToClose.chan_id, noBtnText: 'Cancel', yesBtnText: 'Close Channel'
// }}));
// this.rtlEffects.closeConfirm
// .pipe(takeUntil(this.unsub[1]))
// .subscribe(confirmRes => {
// if (confirmRes) {
// this.store.dispatch(new RTLActions.OpenSpinner('Closing Channel...'));
// this.store.dispatch(new RTLActions.CloseChannel({channelPoint: channelToClose.channel_point, forcibly: !channelToClose.active}));
// }
// });
// }
// applyFilter() {
// this.selectedFilter = this.selFilter;
// this.channels.filter = this.selFilter;
// }
// onChannelClick(selRow: ChannelCL, event: any) {
// const flgCloseClicked =
// event.target.className.includes('mat-column-close')
// || event.target.className.includes('mat-column-update')
// || event.target.className.includes('mat-icon');
// if (flgCloseClicked) {
// return;
// }
// const selChannel = this.channels.data.filter(channel => {
// return channel.chan_id === selRow.chan_id;
// })[0];
// const reorderedChannel = JSON.parse(JSON.stringify(selChannel, [
// 'active', 'remote_pubkey', 'remote_alias', 'channel_point', 'chan_id', 'capacity', 'local_balance', 'remote_balance', 'commit_fee', 'commit_weight',
// 'fee_per_kw', 'unsettled_balance', 'total_satoshis_sent', 'total_satoshis_received', 'num_updates', 'pending_htlcs', 'csv_delay', 'private'
// ] , 2));
// this.store.dispatch(new RTLActions.OpenAlert({ width: '75%', data: {
// type: 'INFO',
// message: JSON.stringify(reorderedChannel)
// }}));
// }
// loadChannelsTable(channels) {
// channels.sort(function(a, b) {
// return (a.active === b.active) ? 0 : ((b.active) ? 1 : -1);
// });
// this.channels = new MatTableDataSource<ChannelCL>([...channels]);
// this.channels.sort = this.sort;
// this.channels.filterPredicate = (channel: ChannelCL, fltr: string) => {
// const newChannel = ((channel.active) ? 'active' : 'inactive') + (channel.chan_id ? channel.chan_id : '') +
// (channel.remote_pubkey ? channel.remote_pubkey : '') + (channel.remote_alias ? channel.remote_alias : '') +
// (channel.capacity ? channel.capacity : '') + (channel.local_balance ? channel.local_balance : '') +
// (channel.remote_balance ? channel.remote_balance : '') + (channel.total_satoshis_sent ? channel.total_satoshis_sent : '') +
// (channel.total_satoshis_received ? channel.total_satoshis_received : '') + (channel.commit_fee ? channel.commit_fee : '') +
// (channel.private ? 'private' : 'public');
// return newChannel.includes(fltr);
// };
// this.logger.info(this.channels);
// }
// resetData() {
// this.selectedPeer = '';
// this.fundingAmount = 0;
// this.moreOptions = false;
// this.spendUnconfirmed = false;
// this.isPrivate = false;
// this.selTransType = '0';
// this.transTypeValue = {blocks: '', fees: ''};
// this.redirectedWithPeer = false;
// }
// onMoreOptionsChange(event: any) {
// if (!event.checked) {
// this.spendUnconfirmed = false;
// this.isPrivate = false;
// this.selTransType = '0';
// this.transTypeValue = {blocks: '', fees: ''};
// }
// }
ngOnDestroy() {
this.unsub.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

@ -19,19 +19,20 @@ export class CLRootComponent implements OnInit, OnDestroy {
constructor(private store: Store<fromRTLReducer.RTLState>, private actions$: Actions, private router: Router, private activatedRoute: ActivatedRoute) {}
ngOnInit() {
this.store.dispatch(new RTLActions.FetchInfoCL());
this.router.navigate(['./home'], {relativeTo: this.activatedRoute});
this.actions$.pipe(takeUntil(this.unsubs[0]), filter((action) => action.type === RTLActions.SET_CL_INFO))
.subscribe((infoData: RTLActions.SetCLInfo | RTLActions.InitAppData) => {
if(infoData.type === RTLActions.SET_CL_INFO && undefined !== infoData.payload.id) {
this.actions$.pipe(takeUntil(this.unsubs[0]), filter((action) => action.type === RTLActions.SET_INFO_CL))
.subscribe((infoData: RTLActions.SetInfoCL) => {
if(infoData.type === RTLActions.SET_INFO_CL && undefined !== infoData.payload.id) {
this.initializeRemainingData();
}
});
}
initializeRemainingData() {
this.store.dispatch(new RTLActions.FetchCLFees());
this.store.dispatch(new RTLActions.FetchCLBalance());
this.store.dispatch(new RTLActions.FetchCLLocalRemoteBalance());
this.store.dispatch(new RTLActions.FetchFeesCL());
this.store.dispatch(new RTLActions.FetchBalanceCL());
this.store.dispatch(new RTLActions.FetchLocalRemoteBalanceCL());
}
ngOnDestroy() {

@ -6,6 +6,16 @@ import { SharedModule } from '../shared/shared.module';
import { CLRootComponent } from './cl-root.component';
import { CLHomeComponent } from './home/home.component';
import { CLChannelsComponent } from './channels/channels.component';
import { CLInvoicesComponent } from './invoices/invoices.component';
import { CLLookupsComponent } from './lookups/lookups.component';
import { CLChannelLookupComponent } from './lookups/channel-lookup/channel-lookup.component';
import { CLNodeLookupComponent } from './lookups/node-lookup/node-lookup.component';
import { CLOnChainComponent } from './on-chain/on-chain.component';
import { CLQueryRoutesComponent } from './payments/query-routes/query-routes.component';
import { CLPaymentsComponent } from './payments/send-receive/payments.component';
import { CLPeersComponent } from './peers/peers.component';
import { CLForwardingHistoryComponent } from './switch/forwarding-history.component';
import { CommonService } from '../shared/services/common.service';
import { LoggerService, ConsoleLoggerService } from '../shared/services/logger.service';
@ -19,7 +29,17 @@ import { CLUnlockedGuard } from '../shared/services/auth.guard';
],
declarations: [
CLRootComponent,
CLHomeComponent
CLHomeComponent,
CLChannelsComponent,
CLInvoicesComponent,
CLLookupsComponent,
CLChannelLookupComponent,
CLNodeLookupComponent,
CLOnChainComponent,
CLQueryRoutesComponent,
CLPaymentsComponent,
CLPeersComponent,
CLForwardingHistoryComponent
],
providers: [
{ provide: LoggerService, useClass: ConsoleLoggerService },

@ -3,6 +3,16 @@ import { ModuleWithProviders } from '@angular/core';
import { CLRootComponent } from './cl-root.component';
import { CLHomeComponent } from './home/home.component';
import { CLChannelsComponent } from './channels/channels.component';
import { CLInvoicesComponent } from './invoices/invoices.component';
import { CLLookupsComponent } from './lookups/lookups.component';
import { CLChannelLookupComponent } from './lookups/channel-lookup/channel-lookup.component';
import { CLNodeLookupComponent } from './lookups/node-lookup/node-lookup.component';
import { CLOnChainComponent } from './on-chain/on-chain.component';
import { CLQueryRoutesComponent } from './payments/query-routes/query-routes.component';
import { CLPaymentsComponent } from './payments/send-receive/payments.component';
import { CLPeersComponent } from './peers/peers.component';
import { CLForwardingHistoryComponent } from './switch/forwarding-history.component';
import { CLUnlockedGuard } from '../shared/services/auth.guard';
import { NotFoundComponent } from '../shared/components/not-found/not-found.component';
@ -11,6 +21,14 @@ export const ClRoutes: Routes = [
{ path: '', component: CLRootComponent,
children: [
{ path: 'home', component: CLHomeComponent, canActivate: [CLUnlockedGuard] },
{ path: 'peers', component: CLPeersComponent, canActivate: [CLUnlockedGuard] },
{ path: 'chnlmanage', component: CLChannelsComponent, canActivate: [CLUnlockedGuard] },
{ path: 'onchain', component: CLOnChainComponent, canActivate: [CLUnlockedGuard] },
{ path: 'paymentsend', component: CLPaymentsComponent, canActivate: [CLUnlockedGuard] },
{ path: 'queryroutes', component: CLQueryRoutesComponent, canActivate: [CLUnlockedGuard] },
{ path: 'invoices', component: CLInvoicesComponent, canActivate: [CLUnlockedGuard] },
{ path: 'switch', component: CLForwardingHistoryComponent, canActivate: [CLUnlockedGuard] },
{ path: 'lookups', component: CLLookupsComponent, canActivate: [CLUnlockedGuard] },
{ path: '**', component: NotFoundComponent }
]}
];

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

@ -58,16 +58,16 @@ export class CLHomeComponent implements OnInit, OnDestroy {
.pipe(takeUntil(this.unsub[0]))
.subscribe((rtlStore) => {
rtlStore.effectErrorsCl.forEach(effectsErr => {
if (effectsErr.action === 'FetchCLInfo') {
if (effectsErr.action === 'FetchInfoCL') {
this.flgLoading[0] = 'error';
}
if (effectsErr.action === 'FetchCLFees') {
if (effectsErr.action === 'FetchFeesCL') {
this.flgLoading[1] = 'error';
}
if (effectsErr.action === 'FetchCLBalance') {
if (effectsErr.action === 'FetchBalanceCL') {
this.flgLoading[2] = 'error';
}
if (effectsErr.action === 'FetchCLLocalRemoteBalance') {
if (effectsErr.action === 'FetchLocalRemoteBalanceCL') {
this.flgLoading[3] = 'error';
}
});

@ -0,0 +1,85 @@
<!-- <div fxLayout="column">
<div class="padding-gap">
<mat-card>
<mat-card-header>
<mat-card-subtitle>
<h2>Invoices</h2>
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<form fxLayout="column" fxLayout.gt-sm="row wrap" fxLayoutAlign.gt-sm="space-between center" (ngSubmit)="addInvoiceForm.form.valid && onAddInvoice(addInvoiceForm)" #addInvoiceForm="ngForm">
<mat-form-field fxFlex="30" fxLayoutAlign="start end">
<input matInput [(ngModel)]="memo" placeholder="Memo" tabindex="1" name="memo">
</mat-form-field>
<mat-form-field fxFlex="15" fxLayoutAlign="start end">
<input matInput [(ngModel)]="invoiceValue" placeholder="Invoice Value ({{information?.smaller_currency_unit}})" type="number" step="100" min="1" tabindex="2" name="invoiceValue">
</mat-form-field>
<mat-form-field fxFlex="15" fxLayoutAlign="start end">
<input matInput [(ngModel)]="expiry" placeholder="Expiry (Sec)" type="number" step="100" min="1" tabindex="3" name="expiry">
</mat-form-field>
<div fxFlex="15" tabindex="4" fxLayoutAlign="start center" class="chkbox-private">
<mat-checkbox [(ngModel)]="private" matTooltip="Include routing hints for private channels" [matTooltipPosition]="'above'" name="private">Private</mat-checkbox>
</div>
<button fxFlex="10" fxLayoutAlign="center center" mat-raised-button color="primary" type="submit" tabindex="5">Add</button>
<button fxFlex="10" fxLayoutAlign="center center" mat-raised-button color="accent" tabindex="6" type="reset" (click)="resetData()">Clear</button>
</form>
</mat-card-content>
</mat-card>
</div>
<div class="padding-gap">
<mat-card>
<mat-card-content class="table-card-content">
<div fxLayout="row" fxLayoutAlign="start start">
<mat-form-field fxFlex="30">
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
</mat-form-field>
</div>
<div perfectScrollbar class="table-container mat-elevation-z8">
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
<table mat-table #table [dataSource]="invoices" matSort [ngClass]="{'mat-elevation-z8 overflow-auto error-border': flgLoading[0]==='error','mat-elevation-z8 overflow-auto': true}">
<ng-container matColumnDef="settled">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Settled </th>
<td mat-cell *matCellDef="let invoice">
<span *ngIf="invoice.settled"><i class="material-icons primary">done_all</i></span>
<span *ngIf="!invoice.settled"><i class="material-icons accent">done</i></span>
</td>
</ng-container>
<ng-container matColumnDef="creation_date">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Creation Date </th>
<td mat-cell *matCellDef="let invoice">{{invoice.creation_date_str}}</td>
</ng-container>
<ng-container matColumnDef="settle_date">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Settle Date </th>
<td mat-cell *matCellDef="let invoice">{{invoice.settle_date_str}}</td>
</ng-container>
<ng-container matColumnDef="memo">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Memo </th>
<td mat-cell *matCellDef="let invoice">{{invoice.memo}}</td>
</ng-container>
<ng-container matColumnDef="value">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Value ({{(selNode?.satsToBTC) ? information?.currency_unit : information?.smaller_currency_unit}}) </th>
<td mat-cell *matCellDef="let invoice"><span fxLayoutAlign="end center"> {{(selNode?.satsToBTC) ? (invoice?.btc_value | number:'1.0-3') : (invoice?.value | number)}} </span></td>
</ng-container>
<ng-container matColumnDef="expiry">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Expiry (Sec)</th>
<td mat-cell *matCellDef="let invoice"><span fxLayoutAlign="end center"> {{invoice.expiry | number}} </span></td>
</ng-container>
<ng-container matColumnDef="cltv_expiry">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> CLTV Expiry </th>
<td mat-cell *matCellDef="let invoice"><span fxLayoutAlign="end center"> {{invoice.cltv_expiry | number}} </span></td>
</ng-container>
<ng-container matColumnDef="amt_paid_sat">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Amount Paid ({{(selNode?.satsToBTC) ? information?.currency_unit : information?.smaller_currency_unit}})</th>
<td mat-cell *matCellDef="let invoice"><span fxLayoutAlign="end center"> {{(selNode?.satsToBTC) ? (invoice?.btc_amt_paid_sat | number:'1.0-3') : (invoice?.amt_paid_sat | number)}} </span></td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: flgSticky;"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [@newlyAddedRowAnimation]="(row.memo == newlyAddedInvoiceMemo && row.value == newlyAddedInvoiceValue && flgAnimate) ? 'added' : 'notAdded'" (click)="onInvoiceClick(row, $event)" class="row-invoices"
[ngClass]="{'settled': row.settled, 'unsettled': !row.settled}"></tr>
</table>
<mat-paginator [length]="totalInvoices" [pageSize]="pageSize" [pageSizeOptions]="pageSizeOptions" (page)="onPageChange($event)"></mat-paginator>
</div>
</mat-card-content>
</mat-card>
</div>
</div> -->
<h3>INVOICES</h3>

@ -0,0 +1,16 @@
.mat-column-value {
padding-right: 1rem;
}
.mat-column-settled {
padding-left: 1rem;
}
.chkbox-private:focus {
outline: none !important;
}
table {
width:100%;
border-collapse: collapse;
}

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

@ -0,0 +1,165 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { formatDate } from '@angular/common';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { Actions } from '@ngrx/effects';
import { MatTableDataSource, MatSort } from '@angular/material';
import { SelNodeChild } from '../../shared/models/RTLconfig';
import { GetInfoCL, InvoiceCL } from '../../shared/models/clModels';
import { LoggerService } from '../../shared/services/logger.service';
import { newlyAddedRowAnimation } from '../../shared/animation/row-animation';
import * as RTLActions from '../../store/rtl.actions';
import * as fromRTLReducer from '../../store/rtl.reducers';
@Component({
selector: 'rtl-cl-invoices',
templateUrl: './invoices.component.html',
styleUrls: ['./invoices.component.scss'],
animations: [newlyAddedRowAnimation]
})
export class CLInvoicesComponent implements OnInit, OnDestroy {
@ViewChild(MatSort, { static: true }) sort: MatSort;
public selNode: SelNodeChild = {};
public newlyAddedInvoiceMemo = '';
public newlyAddedInvoiceValue = 0;
public flgAnimate = true;
public memo = '';
public expiry: number;
public invoiceValue: number;
public displayedColumns = [];
public invoicePaymentReq = '';
public invoices: any;
public information: GetInfoCL = {};
public flgLoading: Array<Boolean | 'error'> = [true];
public flgSticky = false;
public private = false;
public totalInvoices = 100;
public pageSize = 25;
public pageSizeOptions = [5, 10, 25, 100];
private firstOffset = -1;
private lastOffset = -1;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
ngOnInit() {}
// constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private actions$: Actions) {
// switch (true) {
// case (window.innerWidth <= 415):
// this.displayedColumns = ['settled', 'creation_date', 'memo', 'value'];
// break;
// case (window.innerWidth > 415 && window.innerWidth <= 730):
// this.displayedColumns = ['settled', 'creation_date', 'settle_date', 'memo', 'value', 'amt_paid_sat'];
// break;
// case (window.innerWidth > 730 && window.innerWidth <= 1024):
// this.displayedColumns = ['settled', 'creation_date', 'settle_date', 'memo', 'value', 'amt_paid_sat'];
// break;
// case (window.innerWidth > 1024 && window.innerWidth <= 1280):
// this.flgSticky = true;
// this.displayedColumns = ['settled', 'creation_date', 'settle_date', 'memo', 'value', 'amt_paid_sat', 'expiry', 'cltv_expiry'];
// break;
// default:
// this.flgSticky = true;
// this.displayedColumns = ['settled', 'creation_date', 'settle_date', 'memo', 'value', 'amt_paid_sat', 'expiry', 'cltv_expiry'];
// break;
// }
// }
// ngOnInit() {
// this.store.select('cl')
// .pipe(takeUntil(this.unSubs[0]))
// .subscribe((rtlStore) => {
// rtlStore.effectErrorsCl.forEach(effectsErr => {
// if (effectsErr.action === 'FetchInvoices') {
// this.flgLoading[0] = 'error';
// }
// });
// this.selNode = rtlStore.nodeSettings;
// this.information = rtlStore.information;
// this.totalInvoices = rtlStore.totalInvoices;
// this.firstOffset = +rtlStore.invoices.first_index_offset;
// this.lastOffset = +rtlStore.invoices.last_index_offset;
// this.logger.info(rtlStore);
// this.loadInvoicesTable(rtlStore.invoices.invoices);
// if (this.flgLoading[0] !== 'error') {
// this.flgLoading[0] = (undefined !== rtlStore.invoices) ? false : true;
// }
// });
// }
// onAddInvoice(form: any) {
// this.flgAnimate = true;
// this.newlyAddedInvoiceMemo = this.memo;
// this.newlyAddedInvoiceValue = this.invoiceValue;
// this.store.dispatch(new RTLActions.OpenSpinner('Adding Invoice...'));
// this.store.dispatch(new RTLActions.SaveNewInvoice({
// memo: this.memo, invoiceValue: this.invoiceValue, private: this.private, expiry: (this.expiry ? this.expiry : 3600), pageSize: this.pageSize
// }));
// this.resetData();
// }
// onInvoiceClick(selRow: Invoice, event: any) {
// const selInvoice = this.invoices.data.filter(invoice => {
// return invoice.payment_request === selRow.payment_request;
// })[0];
// const reorderedInvoice = JSON.parse(JSON.stringify(selInvoice, [
// 'settled', 'creation_date_str', 'settle_date_str', 'memo', 'receipt', 'r_preimage', 'r_hash', 'value', 'payment_request',
// 'description_hash', 'expiry', 'fallback_addr', 'cltv_expiry', 'route_hints', 'private', 'add_index', 'settle_index',
// 'amt_paid', 'amt_paid_sat', 'amt_paid_msat'
// ] , 2));
// this.store.dispatch(new RTLActions.OpenAlert({ width: '75%', data: {
// type: 'INFO',
// message: JSON.stringify(reorderedInvoice)
// }}));
// }
// loadInvoicesTable(invoices) {
// this.invoices = new MatTableDataSource<Invoice>([...invoices]);
// this.invoices.sort = this.sort;
// this.invoices.data.forEach(invoice => {
// if (undefined !== invoice.creation_date_str) {
// invoice.creation_date_str = (invoice.creation_date_str === '') ? '' : formatDate(invoice.creation_date_str, 'MMM/dd/yy HH:mm:ss', 'en-US');
// }
// if (undefined !== invoice.settle_date_str) {
// invoice.settle_date_str = (invoice.settle_date_str === '') ? '' : formatDate(invoice.settle_date_str, 'MMM/dd/yy HH:mm:ss', 'en-US');
// }
// });
// setTimeout(() => { this.flgAnimate = false; }, 30000);
// this.logger.info(this.invoices);
// }
// resetData() {
// this.memo = '';
// this.invoiceValue = 0;
// this.private = false;
// this.expiry = undefined;
// }
// applyFilter(selFilter: string) {
// this.invoices.filter = selFilter;
// }
// onPageChange(event: any) {
// let reversed = true;
// let index_offset = this.firstOffset;
// if (event.pageIndex < event.previousPageIndex) {
// reversed = false;
// index_offset = this.lastOffset;
// }
// if (event.pageIndex === event.previousPageIndex) {
// reversed = true;
// index_offset = 0;
// }
// this.store.dispatch(new RTLActions.FetchInvoices({num_max_invoices: event.pageSize, index_offset: index_offset, reversed: reversed}));
// }
ngOnDestroy() {
this.unSubs.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

@ -0,0 +1,3 @@
.mat-list-base .mat-list-item, .mat-list-base .mat-list-option {
height: 38px !important;
}

@ -0,0 +1,110 @@
<!-- <div fxLayout="column" fxLayout.gt-sm="row wrap">
<mat-card fxFlex="100" fxLayoutAlign="start start">
<mat-card-content fxFlex="100" *ngIf="lookupResult">
<mat-list fxLayoutAlign="space-between start">
<mat-list-item fxFlex="30" fxLayoutAlign="start start">Channel Id</mat-list-item>
<mat-list-item fxFlex="68" fxLayoutAlign="start start">{{lookupResult.channel_id}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="space-between start">
<mat-list-item fxFlex="30" fxLayoutAlign="start start">Channel Point</mat-list-item>
<mat-list-item fxFlex="68" fxLayoutAlign="start start" class="word-break">{{lookupResult.chan_point}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="space-between start">
<mat-list-item fxFlex="30" fxLayoutAlign="start start">Last Update</mat-list-item>
<mat-list-item fxFlex="68" fxLayoutAlign="start start">{{lookupResult.last_update_str}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="space-between start">
<mat-list-item fxFlex="30" fxLayoutAlign="start start">Capacity (Sats)</mat-list-item>
<mat-list-item fxFlex="68" fxLayoutAlign="start start">{{lookupResult.capacity | number}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
</mat-card-content>
</mat-card>
</div>
<div fxLayout="column" fxLayoutAlign="space-between start" fxLayout.gt-sm="row wrap" class="mt-2">
<div fxFlex="48">
<mat-card class="custom-card mat-elevation-z12">
<mat-card-header class="bg-primary" fxLayoutAlign="center center">
<mat-card-title class="m-0 pt-2">
<h5 *ngIf="!node1_match">Node 1</h5>
<h5 *ngIf="node1_match">Node 1 (Your Node)</h5>
</mat-card-title>
</mat-card-header>
<mat-card-content class="px-2">
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="100" fxLayoutAlign="start start" class="word-break">{{lookupResult.node1_pub}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Time Lock Delta</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult.node1_policy.time_lock_delta}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Min HTLC</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult.node1_policy.min_htlc}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Fee Base Msat</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult.node1_policy.fee_base_msat}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Fee Rate Milli Msat</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult.node1_policy.fee_rate_milli_msat}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Disabled</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult.node1_policy.disabled}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
</mat-card-content>
</mat-card>
</div>
<div fxFlex="48">
<mat-card class="custom-card mat-elevation-z12">
<mat-card-header class="bg-primary" fxLayoutAlign="center center">
<mat-card-title class="m-0 pt-2">
<h5 *ngIf="!node2_match">Node 2</h5>
<h5 *ngIf="node2_match">Node 2 (Your Node)</h5>
</mat-card-title>
</mat-card-header>
<mat-card-content class="px-2">
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="100" fxLayoutAlign="start start" class="word-break">{{lookupResult.node2_pub}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Time Lock Delta</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult.node2_policy.time_lock_delta}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Min HTLC</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult.node2_policy.min_htlc}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Fee Base Msat</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult.node2_policy.fee_base_msat}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Fee Rate Milli Msat</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult.node2_policy.fee_rate_milli_msat}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Disabled</mat-list-item>
<mat-list-item fxFlex="50" fxLayoutAlign="start start">{{lookupResult.node2_policy.disabled}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
</mat-card-content>
</mat-card>
</div> -->
<h3>CHANNEL LOOKUP</h3>

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

@ -0,0 +1,40 @@
import { Component, OnInit, Input } from '@angular/core';
import { formatDate } from '@angular/common';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { ChannelEdgeCL } from '../../../shared/models/clModels';
import * as fromRTLReducer from '../../../store/rtl.reducers';
@Component({
selector: 'rtl-cl-channel-lookup',
templateUrl: './channel-lookup.component.html',
styleUrls: ['./channel-lookup.component.css']
})
export class CLChannelLookupComponent implements OnInit {
@Input() lookupResult: ChannelEdgeCL;
public node1_match = false;
public node2_match = false;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
constructor(private store: Store<fromRTLReducer.RTLState>) { }
ngOnInit() {
if (undefined !== this.lookupResult && undefined !== this.lookupResult.last_update_str) {
this.lookupResult.last_update_str = (this.lookupResult.last_update_str === '') ?
'' : formatDate(this.lookupResult.last_update_str, 'MMM/dd/yy HH:mm:ss', 'en-US');
}
this.store.select('cl')
.pipe(takeUntil(this.unSubs[0]))
.subscribe((rtlStore) => {
if (this.lookupResult.node1_pub === rtlStore.information.id) {
this.node1_match = true;
}
if (this.lookupResult.node2_pub === rtlStore.information.id) {
this.node2_match = true;
}
});
}
}

@ -0,0 +1,48 @@
<!-- <div fxLayout="column">
<div class="padding-gap">
<mat-card>
<mat-card-header>
<mat-card-subtitle>
<h2>Lookups</h2>
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<form fxLayout="column" fxLayout.gt-sm="row wrap" #form="ngForm">
<mat-form-field fxFlex="20" fxLayoutAlign="start end">
<mat-select [(ngModel)]="selectedField" placeholder="Lookup Field" (selectionChange)="onSelectChange($event)" tabindex="1" required name="lookupField">
<mat-option *ngFor="let lookupField of lookupFields" [value]="lookupField">
{{lookupField.name}}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field fxFlex="50" fxLayoutAlign="start end">
<input matInput name="lookupKey" [placeholder]="selectedField?.placeholder || 'Lookup Key'" (change)="clearLookupValue()" [(ngModel)]="lookupKey" tabindex="2" required>
</mat-form-field>
<div fxFlex="12" fxLayoutAlign="start start">
<button fxFlex="90" fxLayoutAlign="center center" mat-raised-button color="primary" tabindex="3" type="submit" (click)="onLookup()" [disabled]="!form.valid">Lookup</button>
</div>
<div fxFlex="12" fxLayoutAlign="start start">
<button fxFlex="90" fxLayoutAlign="center center" mat-raised-button color="accent" tabindex="4" type="reset" (click)="resetData()">Clear</button>
</div>
</form>
</mat-card-content>
</mat-card>
</div>
<div class="padding-gap" *ngIf="lookupValue && flgSetLookupValue">
<mat-card [ngClass]="{'error-border': flgLoading[0]==='error'}">
<mat-card-header>
<mat-card-subtitle>
<h2>{{selectedField.name}} Details</h2>
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<div [ngSwitch]="selectedField.id">
<span *ngSwitchCase="0"><rtl-node-lookup [lookupResult]="lookupValue"></rtl-node-lookup></span>
<span *ngSwitchCase="1"><rtl-channel-lookup [lookupResult]="lookupValue"></rtl-channel-lookup></span>
<span *ngSwitchDefault><h3>Error! Unable to find details!</h3></span>
</div>
</mat-card-content>
</mat-card>
</div>
</div> -->
<h3>LOOKUP</h3>

@ -0,0 +1,14 @@
.tree-invisible {
display: none;
}
.lookup-tree ul,
.lookup-tree li {
margin-top: 0;
margin-bottom: 0;
list-style-type: none;
}
.pl-3 {
padding-left: 3rem;
}

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

@ -0,0 +1,94 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil, filter } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { Actions } from '@ngrx/effects';
import { LoggerService } from '../../shared/services/logger.service';
import * as RTLActions from '../../store/rtl.actions';
import * as fromRTLReducer from '../../store/rtl.reducers';
@Component({
selector: 'rtl-cl-lookups',
templateUrl: './lookups.component.html',
styleUrls: ['./lookups.component.scss']
})
export class CLLookupsComponent implements OnInit, OnDestroy {
public lookupKey = '';
public lookupValue = {};
public flgSetLookupValue = false;
public temp: any;
public messageObj = [];
public selectedField = { id: '0', name: 'Node', placeholder: 'Pubkey'};
public lookupFields = [
{ id: '0', name: 'Node', placeholder: 'Pubkey'},
{ id: '1', name: 'Channel', placeholder: 'Channel ID'}
];
public flgLoading: Array<Boolean | 'error'> = [true];
private unSubs: Array<Subject<void>> = [new Subject()];
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private actions$: Actions) {}
ngOnInit() {
this.actions$
.pipe(
takeUntil(this.unSubs[0]),
filter((action) => (action.type === RTLActions.SET_LOOKUP || action.type === RTLActions.EFFECT_ERROR_CL))
).subscribe((resLookup: RTLActions.SetLookup) => {
if (resLookup.payload.action === 'Lookup') {
this.flgLoading[0] = 'error';
} else {
this.flgLoading[0] = true;
this.lookupValue = JSON.parse(JSON.stringify(resLookup.payload));
this.flgSetLookupValue = true;
this.logger.info(this.lookupValue);
}
});
}
onLookup() {
this.flgSetLookupValue = false;
this.lookupValue = {};
this.store.dispatch(new RTLActions.OpenSpinner('Searching ' + this.selectedField.name + '...'));
switch (this.selectedField.id) {
case '0':
this.store.dispatch(new RTLActions.PeerLookup(this.lookupKey.trim()));
break;
case '1':
this.store.dispatch(new RTLActions.ChannelLookup(this.lookupKey.trim()));
break;
default:
break;
}
}
onSelectChange(event: any) {
this.flgSetLookupValue = false;
this.lookupKey = '';
this.lookupValue = {};
}
resetData() {
this.flgSetLookupValue = false;
this.lookupKey = '';
this.selectedField = { id: '0', name: 'Node', placeholder: 'Pubkey'};
this.lookupValue = {};
this.flgLoading.forEach((flg, i) => {
this.flgLoading[i] = true;
});
}
clearLookupValue() {
this.lookupValue = {};
this.flgSetLookupValue = false;
}
ngOnDestroy() {
this.unSubs.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

@ -0,0 +1,7 @@
.mat-table {
width:99%;
}
.mat-list-base .mat-list-item, .mat-list-base .mat-list-option {
height: 38px !important;
}

@ -0,0 +1,57 @@
<!-- <div fxLayout="column">
<div class="padding-gap">
<mat-card>
<mat-card-content *ngIf="lookupResult">
<div fxLayout="column">
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Alias</mat-list-item>
<mat-list-item fxFlex="40" fxLayoutAlign="start start">{{lookupResult.node.alias}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Pub Key</mat-list-item>
<mat-list-item fxFlex="40" fxLayoutAlign="start start">{{lookupResult.node.pub_key}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Color</mat-list-item>
<mat-list-item fxFlex="40" fxLayoutAlign="start start"><span [ngStyle]="{'background-color': lookupResult.node?.color}">{{lookupResult.node?.color}}</span></mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Last Update</mat-list-item>
<mat-list-item fxFlex="40" fxLayoutAlign="start start">{{lookupResult.node.last_update_str}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Total Capacity (Sats)</mat-list-item>
<mat-list-item fxFlex="40" fxLayoutAlign="start start">{{lookupResult.total_capacity | number}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayoutAlign="start start">
<mat-list-item fxFlex="50" fxLayoutAlign="start start">Number of Channels</mat-list-item>
<mat-list-item fxFlex="40" fxLayoutAlign="start start">{{lookupResult.num_channels | number}}</mat-list-item>
<mat-divider></mat-divider>
</mat-list>
<mat-list fxLayout="column" fxLayoutAlign="start start">
<mat-divider></mat-divider>
<mat-list-item fxFlex="100" fxLayoutAlign="start start">Addresses</mat-list-item>
<mat-table [dataSource]="lookupResult.node.addresses" matSort class="mat-elevation-z8 overflow-auto">
<ng-container matColumnDef="network">
<mat-header-cell *matHeaderCellDef mat-sort-header>Network</mat-header-cell>
<mat-cell *matCellDef="let address"><div>{{address?.network}}</div></mat-cell>
</ng-container>
<ng-container matColumnDef="addr">
<mat-header-cell *matHeaderCellDef mat-sort-header>Address</mat-header-cell>
<mat-cell *matCellDef="let address"><div>{{address?.addr}}</div></mat-cell>
</ng-container>
<mat-header-row *matHeaderRowDef="displayedColumns;"></mat-header-row>
<mat-row fxLayoutAlign="stretch stretch" *matRowDef="let row; columns: displayedColumns;"></mat-row>
</mat-table>
</mat-list>
</div>
</mat-card-content>
</mat-card>
</div>
</div> -->
<h3>NODE LOOKUP</h3>

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

@ -0,0 +1,24 @@
import { Component, OnInit, Input } from '@angular/core';
import { formatDate } from '@angular/common';
import { GraphNodeCL } from '../../../shared/models/clModels';
@Component({
selector: 'rtl-cl-node-lookup',
templateUrl: './node-lookup.component.html',
styleUrls: ['./node-lookup.component.css']
})
export class CLNodeLookupComponent implements OnInit {
@Input() lookupResult: GraphNodeCL;
public displayedColumns = ['network', 'addr'];
constructor() { }
ngOnInit() {
if (undefined !== this.lookupResult && undefined !== this.lookupResult.node && undefined !== this.lookupResult.node.last_update_str) {
this.lookupResult.node.last_update_str = (this.lookupResult.node.last_update_str === '') ?
'' : formatDate(this.lookupResult.node.last_update_str, 'MMM/dd/yy HH:mm:ss', 'en-US');
}
}
}

@ -0,0 +1,167 @@
<div fxLayout="column" fxLayoutAlign="center center" class="test-banner mx-1">
<h5>Don't be #reckless. #craefulgang #craefulgang #craefulgang.</h5>
</div>
<div fxLayout="column" fxLayout.gt-sm="row wrap">
<div fxFlex="34" class="padding-gap">
<mat-card [ngClass]="{'custom-card error-border': flgLoadingWallet==='error','custom-card': true}">
<mat-card-header class="bg-primary p-1" fxLayoutAlign="center center">
<mat-card-title class="m-0 pt-1">
<h5>Total Balance</h5>
<mat-progress-bar *ngIf="flgLoadingWallet===true" mode="indeterminate"></mat-progress-bar>
</mat-card-title>
</mat-card-header>
<mat-card-content fxLayout="column" fxLayoutAlign="center center">
<mat-card-content class="mt-1">
<svg style="width:70px;height:70px" viewBox="0 0 24 24">
<path fill="currentColor" d="M10,2H14A2,2 0 0,1 16,4V6H20A2,2 0 0,1 22,8V19A2,2 0 0,1 20,21H4C2.89,21 2,20.1 2,19V8C2,6.89 2.89,6 4,6H8V4C8,2.89 8.89,2 10,2M14,6V4H10V6H14Z" />
</svg>
</mat-card-content>
<span *ngIf="information?.currency_unit; else withoutData">
<h3 *ngIf="selNode?.satsToBTC; else smallerUnit1">{{balance?.btc_totalBalance | number}} {{information?.currency_unit}}</h3>
<ng-template #smallerUnit1><h3>{{balance?.totalBalance | number}} {{information?.smaller_currency_unit}}</h3></ng-template>
</span>
</mat-card-content>
<mat-progress-bar class="mt-minus-5" *ngIf="flgLoadingWallet===true" mode="indeterminate"></mat-progress-bar>
<mat-divider></mat-divider>
</mat-card>
</div>
<div fxFlex="33" class="padding-gap">
<mat-card [ngClass]="{'custom-card error-border': flgLoadingWallet==='error','custom-card': true}">
<mat-card-header class="bg-primary p-1" fxLayoutAlign="center center">
<mat-card-title class="m-0 pt-1">
<h5>Confirmed Balance</h5>
<mat-progress-bar *ngIf="flgLoadingWallet===true" mode="indeterminate"></mat-progress-bar>
</mat-card-title>
</mat-card-header>
<mat-card-content fxLayout="column" fxLayoutAlign="center center">
<mat-card-content class="mt-1">
<svg style="width:70px;height:70px" viewBox="0 0 24 24">
<path fill="currentColor" d="M10,2H14A2,2 0 0,1 16,4V6H20A2,2 0 0,1 22,8V19A2,2 0 0,1 20,21H4A2,2 0 0,1 2,19V8A2,2 0 0,1 4,6H8V4A2,2 0 0,1 10,2M14,6V4H10V6H14M10.5,17.5L17.09,10.91L15.68,9.5L10.5,14.67L8.41,12.59L7,14L10.5,17.5Z" />
</svg>
</mat-card-content>
<span *ngIf="information?.currency_unit; else withoutData">
<h3 *ngIf="selNode?.satsToBTC; else smallerUnit2">{{balance?.btc_confBalance | number}} {{information?.currency_unit}}</h3>
<ng-template #smallerUnit2><h3>{{balance?.confBalance | number}} {{information?.smaller_currency_unit}}</h3></ng-template>
</span>
</mat-card-content>
<mat-progress-bar class="mt-minus-5" *ngIf="flgLoadingWallet===true" mode="indeterminate"></mat-progress-bar>
<mat-divider></mat-divider>
</mat-card>
</div>
<div fxFlex="33" class="padding-gap">
<mat-card [ngClass]="{'custom-card error-border': flgLoadingWallet==='error','custom-card': true}">
<mat-card-header class="bg-primary p-1" fxLayoutAlign="center center">
<mat-card-title class="m-0 pt-1">
<h5>Unconfirmed Balance</h5>
<mat-progress-bar *ngIf="flgLoadingWallet===true" mode="indeterminate"></mat-progress-bar>
</mat-card-title>
</mat-card-header>
<mat-card-content fxLayout="column" fxLayoutAlign="center center">
<mat-card-content class="mt-1">
<svg style="width:70px;height:70px" viewBox="0 0 24 24">
<path fill="currentColor" d="M14,2A2,2 0 0,1 16,4V6H20A2,2 0 0,1 22,8L10.85,19C10.85,20.1 10.85,19.5 10.85,21H4C2.89,21 2,20.1 2,19V8C2,6.89 2.89,6 4,6H8V4C8,2.89 8.89,2 10,2H14M14,6V4H10V6H14M21.04,12.13C20.9,12.13 20.76,12.19 20.65,12.3L19.65,13.3L21.7,15.35L22.7,14.35C22.92,14.14 22.92,13.79 22.7,13.58L21.42,12.3C21.31,12.19 21.18,12.13 21.04,12.13M19.07,13.88L13,19.94V22H15.06L21.12,15.93L19.07,13.88Z" />
</svg>
</mat-card-content>
<span *ngIf="information?.currency_unit; else withoutData">
<h3 *ngIf="selNode?.satsToBTC; else smallerUnit3">{{balance?.btc_unconfBalance | number}} {{information?.currency_unit}}</h3>
<ng-template #smallerUnit3><h3>{{balance?.unconfBalance | number}} {{information?.smaller_currency_unit}}</h3></ng-template>
</span>
</mat-card-content>
<mat-progress-bar class="mt-minus-5" *ngIf="flgLoadingWallet===true" mode="indeterminate"></mat-progress-bar>
<mat-divider></mat-divider>
</mat-card>
</div>
<div fxFlex="100" class="padding-gap">
<mat-card>
<mat-card-header>
<mat-card-subtitle>
<h2>Receive Funds</h2>
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<div fxLayout="column" fxFlex="100" fxLayout.lt-md="top-minus-25px">
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" fxLayout.gt-md="row wrap">
<div fxFlex="15" fxLayoutAlign="start end">
<mat-form-field fxFlex="99">
<mat-select [(ngModel)]="selectedAddress" placeholder="Address Type" name="address_type" tabindex="1">
<mat-option *ngFor="let addressType of addressTypes" [value]="addressType">
{{addressType.addressTp}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div fxFlex="25" fxLayoutAlign="space-between end">
<div fxFlex.gt-md="65" fxFlex.lt-lg="49" fxLayoutAlign="start end">
<button fxLayoutAlign="center center" mat-raised-button color="primary" [disabled]="undefined === selectedAddress.addressId" (click)="onGenerateAddress()" tabindex="2" class="top-minus-15px">Generate Address</button>
</div>
<div fxFlex.gt-md="30" fxFlex.lt-lg="49" fxLayoutAlign="start end">
<button fxLayoutAlign="center center" mat-raised-button color="accent" tabindex="3" (click)="resetReceiveData()" class="top-minus-15px">Clear</button>
</div>
</div>
<div fxFlex="42" fxLayoutAlign="start end">
<mat-form-field fxFlex="100">
<input matInput [value]="newAddress" placeholder="Generated Address" readonly>
</mat-form-field>
</div>
<div fxFlex.lt-lg="40" fxFlex.gt-md="14" fxLayoutAlign="center center">
<qrcode [qrdata]="newAddress" [size]="120" [level]="'L'" [allowEmptyString]="true" [ngStyle]="{'visibility': (newAddress === '') ? 'hidden' : 'visible'}" class="top-minus-5px qr-border"></qrcode>
</div>
</div>
</div>
</mat-card-content>
</mat-card>
</div>
<div fxFlex="100" class="padding-gap">
<mat-card>
<mat-card-header>
<mat-card-subtitle>
<h2>Send Funds</h2>
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<div fxLayout="column" fxLayout.gt-sm="row wrap">
<div fxFlex="62" fxLayoutAlign="start end">
<mat-form-field fxFlex="99">
<input matInput [(ngModel)]="transaction.address" placeholder="{{information?.currency_unit}} Address" tabindex="4" name="address" #address="ngModel">
</mat-form-field>
</div>
<div fxFlex="38" fxLayoutAlign="start end">
<mat-radio-group fxFlex="100" fxLayoutAlign="space-between center" (change)="onOptionChange($event)" [(ngModel)]="flgCustomAmount">
<mat-radio-button fxFlex="35" value="0">Sweep All</mat-radio-button>
<mat-radio-button fxFlex="60" value="1">
<mat-form-field fxFlex="70"><input matInput [(ngModel)]="transaction.amount" (click)="onCustomClicked()" placeholder="Amount ({{information?.smaller_currency_unit}})" name="amount" type="number" step="100" min="0" tabindex="5" #amount="ngModel"></mat-form-field>
</mat-radio-button>
</mat-radio-group>
</div>
</div>
<div fxLayout="column" fxLayout.gt-sm="row wrap">
<div fxFlex="30" fxLayoutAlign="start start">
<mat-form-field fxFlex="99">
<mat-select [(value)]="selTransType">
<mat-option *ngFor="let transType of transTypes" [value]="transType.id">
{{transType.name}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div fxFlex="30" fxLayoutAlign="start start">
<mat-form-field fxFlex="99" *ngIf="selTransType=='1'">
<input matInput [(ngModel)]="transaction.blocks" placeholder="Target Confirmation Blocks" type="number" name="blocks" step="1" min="0" required tabindex="6" #blocks="ngModel">
</mat-form-field>
<mat-form-field fxFlex="99" *ngIf="selTransType=='2'">
<input matInput [(ngModel)]="transaction.fees" placeholder="Fee ({{information?.smaller_currency_unit}}/Byte)" type="number" name="fees" step="1" min="0" required tabindex="7" #fees="ngModel">
</mat-form-field>
</div>
<div fxFlex="40" fxLayoutAlign="space-between start">
<button fxFlex="48" fxLayoutAlign="center center" mat-raised-button color="primary" [disabled]="invalidValues" type="submit" tabindex="8" (click)="onSendFunds()">
<p *ngIf="invalidValues && (address.touched || address.dirty) && (amount.touched || amount.dirty); else sendText">Invalid Values</p>
<ng-template #sendText><p>Send</p></ng-template>
</button>
<button fxFlex="48" fxLayoutAlign="center center" mat-raised-button color="accent" tabindex="9" type="reset" (click)="resetData()">Clear</button>
</div>
</div>
</mat-card-content>
</mat-card>
</div>
</div>
<ng-template #withoutData><h3>Sats</h3></ng-template>

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

@ -0,0 +1,183 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil, take } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { SelNodeChild } from '../../shared/models/RTLconfig';
import { GetInfoCL, BalanceCL, OnChainCL, AddressTypeCL } from '../../shared/models/clModels';
import { RTLConfiguration } from '../../shared/models/RTLconfig';
import { LoggerService } from '../../shared/services/logger.service';
import * as sha256 from 'sha256';
import { CLEffects } from '../store/cl.effects';
import { RTLEffects } from '../../store/rtl.effects';
import * as RTLActions from '../../store/rtl.actions';
import * as fromRTLReducer from '../../store/rtl.reducers';
@Component({
selector: 'rtl-cl-on-chain',
templateUrl: './on-chain.component.html',
styleUrls: ['./on-chain.component.scss']
})
export class CLOnChainComponent implements OnInit, OnDestroy {
public selNode: SelNodeChild = {};
public appConfig: RTLConfiguration;
public addressTypes = [];
public flgLoadingWallet: Boolean | 'error' = true;
public selectedAddress: AddressTypeCL = {};
public balance: BalanceCL = {};
public information: GetInfoCL = {};
public newAddress = '';
public transaction: OnChainCL = {};
public transTypes = [{id: '1', name: 'Target Confirmation Blocks'}, {id: '2', name: 'Fee'}];
public selTransType = '1';
public flgCustomAmount = '1';
private unsub: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject(), new Subject()];
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private rtlEffects: RTLEffects, private clEffects: CLEffects) {}
ngOnInit() {
this.store.select('root')
.pipe(takeUntil(this.unsub[0]))
.subscribe((rootStore) => {
this.appConfig = rootStore.appConfig;
this.logger.info(rootStore);
});
this.store.select('cl')
.pipe(takeUntil(this.unsub[0]))
.subscribe((rtlStore) => {
rtlStore.effectErrorsCl.forEach(effectsErr => {
if (effectsErr.action === 'FetchBalanceCL') {
this.flgLoadingWallet = 'error';
}
});
this.selNode = rtlStore.nodeSettings;
this.information = rtlStore.information;
this.addressTypes = rtlStore.addressTypes;
this.balance = rtlStore.balance;
if (undefined === this.balance.totalBalance) {
this.balance.totalBalance = '0';
}
if (undefined === this.balance.confBalance) {
this.balance.confBalance = '0';
}
if (undefined === this.balance.unconfBalance) {
this.balance.unconfBalance = '0';
}
if (this.flgLoadingWallet !== 'error') {
this.flgLoadingWallet = false;
}
this.logger.info(rtlStore);
});
}
onGenerateAddress() {
this.store.dispatch(new RTLActions.OpenSpinner('Getting New Address...'));
this.store.dispatch(new RTLActions.GetNewAddressCL(this.selectedAddress));
this.clEffects.setNewAddressCL
.pipe(takeUntil(this.unsub[1]))
.subscribe(newAddress => {
this.newAddress = newAddress;
});
}
onSendFunds() {
// const confirmationMsg = {
// 'BTC Address': this.transaction.address,
// };
// if (!+this.flgCustomAmount) {
// confirmationMsg['Sweep All'] = 'True';
// this.transaction.sendAll = true;
// } else {
// confirmationMsg['Amount (' + this.information.smaller_currency_unit + ')'] = this.transaction.amount;
// this.transaction.sendAll = false;
// }
// if (this.selTransType === '1') {
// delete this.transaction.fees;
// confirmationMsg['Target Confirmation Blocks'] = this.transaction.blocks;
// } else {
// delete this.transaction.blocks;
// confirmationMsg['Fee (' + this.information.smaller_currency_unit + '/Byte)'] = this.transaction.fees;
// }
// this.store.dispatch(new RTLActions.OpenConfirmation({ width: '70%', data:
// {type: 'CONFIRM', message: JSON.stringify(confirmationMsg), noBtnText: 'Cancel', yesBtnText: 'Send'}
// }));
// this.rtlEffects.closeConfirm
// .pipe(takeUntil(this.unsub[2]))
// .subscribe(confirmRes => {
// if (confirmRes) {
// if (this.transaction.sendAll && !+this.appConfig.sso.rtlSSO) {
// this.store.dispatch(new RTLActions.OpenConfirmation({ width: '70%', data:
// {type: 'CONFIRM', titleMessage: 'Enter Login Password', noBtnText: 'Cancel', yesBtnText: 'Authorize', flgShowInput: true, getInputs: [
// {placeholder: 'Enter Login Password', inputType: 'password', inputValue: ''}
// ]}
// }));
// this.rtlEffects.closeConfirm
// .pipe(takeUntil(this.unsub[3]))
// .subscribe(pwdConfirmRes => {
// if (pwdConfirmRes) {
// const pwd = pwdConfirmRes[0].inputValue;
// this.store.dispatch(new RTLActions.IsAuthorized(sha256(pwd)));
// this.rtlEffects.isAuthorizedRes
// .pipe(take(1))
// .subscribe(authRes => {
// if (authRes !== 'ERROR') {
// this.dispatchToSendFunds();
// }
// });
// }
// });
// } else {
// this.dispatchToSendFunds();
// }
// }
// });
}
dispatchToSendFunds() {
// this.store.dispatch(new RTLActions.OpenSpinner('Sending Funds...'));
// this.store.dispatch(new RTLActions.SetChannelTransaction(this.transaction));
// this.transaction = {address: '', amount: 0, blocks: 0, fees: 0};
}
get invalidValues(): boolean {
return (undefined === this.transaction.address || this.transaction.address === '')
|| (+this.flgCustomAmount && (undefined === this.transaction.amount || this.transaction.amount <= 0))
|| (this.selTransType === '1' && (undefined === this.transaction.blocks || this.transaction.blocks <= 0))
|| (this.selTransType === '2' && (undefined === this.transaction.fees || this.transaction.fees <= 0));
}
onCustomClicked() {
this.flgCustomAmount = '1';
}
onOptionChange(event) {
if (!+this.flgCustomAmount) {
delete this.transaction.amount;
}
}
resetData() {
this.transaction.address = '';
this.transaction.amount = 0;
this.transaction.blocks = 0;
this.transaction.fees = 0;
}
resetReceiveData() {
this.selectedAddress = {};
this.newAddress = '';
}
ngOnDestroy() {
this.unsub.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

@ -0,0 +1,80 @@
<!-- <div fxLayout="column">
<div class="padding-gap">
<mat-card>
<mat-card-header>
<mat-card-subtitle>
<h2>Query Routes</h2>
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<form fxLayout="column" fxLayoutAlign="space-between stretch" fxLayout.gt-md="row wrap"
(ngSubmit)="queryRoutesForm.form.valid && onQueryRoutes()" #queryRoutesForm="ngForm">
<mat-form-field fxFlex="50" fxLayoutAlign="start end">
<input matInput placeholder="Destination Pubkey" name="destinationPubkey" [(ngModel)]="destinationPubkey"
tabindex="1" required #destPubkey="ngModel">
</mat-form-field>
<mat-form-field fxFlex="20" fxLayoutAlign="start end">
<input matInput placeholder="Amount (Sats)" name="amount" [(ngModel)]="amount" tabindex="2" type="number"
step="1000" min="0" required #destAmount="ngModel">
</mat-form-field>
<div fxFlex="15" fxLayoutAlign="start start">
<button fxFlex="90" fxLayoutAlign="center center" mat-raised-button color="primary"
[disabled]="destPubkey.invalid || destAmount.invalid" type="submit" tabindex="3">
<p *ngIf="(destPubkey.invalid && (destPubkey.dirty || destPubkey.touched) || (destAmount.invalid && (destAmount.dirty || destAmount.touched))); else queryText">Invalid Pubkey/Amount
</p>
<ng-template #queryText>
<p>Query</p>
</ng-template>
</button>
</div>
<div fxFlex="15" fxLayoutAlign="start start">
<button fxFlex="90" fxLayoutAlign="center center" mat-raised-button color="accent" tabindex="4" type="reset"
(click)="resetData()">Clear</button>
</div>
</form>
</mat-card-content>
</mat-card>
</div>
<div class="padding-gap">
<mat-card>
<mat-card-content class="table-card-content">
<div perfectScrollbar class="table-container mat-elevation-z8">
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
<table mat-table #table [dataSource]="qrHops" matSort
[ngClass]="{'mat-elevation-z8 overflow-x-auto error-border': flgLoading[0]==='error','mat-elevation-z8 overflow-x-auto': true}">
<ng-container matColumnDef="hop_sequence">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Hop </th>
<td mat-cell *matCellDef="let hop"> {{hop?.hop_sequence}} </td>
</ng-container>
<ng-container matColumnDef="pubkey_alias">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Node </th>
<td mat-cell *matCellDef="let hop"> {{hop?.pubkey_alias}} </td>
</ng-container>
<ng-container matColumnDef="chan_id">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Channel </th>
<td mat-cell *matCellDef="let hop"> {{hop?.chan_id}} </td>
</ng-container>
<ng-container matColumnDef="chan_capacity">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Capacity (sats) </th>
<td mat-cell *matCellDef="let hop"><span fxLayoutAlign="end center"> {{hop?.chan_capacity | number}}
</span></td>
</ng-container>
<ng-container matColumnDef="amt_to_forward_msat">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Amount To Fwd (msats) </th>
<td mat-cell *matCellDef="let hop"><span fxLayoutAlign="end center"> {{hop?.amt_to_forward_msat | number}}
</span></td>
</ng-container>
<ng-container matColumnDef="fee_msat">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before"> Fee (msat) </th>
<td mat-cell *matCellDef="let hop"><span fxLayoutAlign="end center"> {{hop?.fee_msat | number}} </span>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: flgSticky;"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" (click)="onHopClick(row, $event)"></tr>
</table>
</div>
</mat-card-content>
</mat-card>
</div>
</div> -->
<h3>PAYMENTS QUERY ROUTES</h3>

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

@ -0,0 +1,98 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil, take } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { Actions } from '@ngrx/effects';
import { MatTableDataSource, MatSort } from '@angular/material';
import { HopCL } from '../../../shared/models/clModels';
import { LoggerService } from '../../../shared/services/logger.service';
import { CLEffects } from '../../store/cl.effects';
import * as RTLActions from '../../../store/rtl.actions';
import * as fromRTLReducer from '../../../store/rtl.reducers';
@Component({
selector: 'rtl-cl-query-routes',
templateUrl: './query-routes.component.html',
styleUrls: ['./query-routes.component.scss']
})
export class CLQueryRoutesComponent implements OnInit, OnDestroy {
@ViewChild(MatSort, { static: true }) sort: MatSort;
public destinationPubkey = '';
public amount = null;
public qrHops: any;
public flgSticky = false;
public displayedColumns = [];
public flgLoading: Array<Boolean | 'error'> = [false]; // 0: peers
private unSubs: Array<Subject<void>> = [new Subject(), new Subject()];
ngOnInit() {}
// constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private clEffects: CLEffects, private actions$: Actions) {
// switch (true) {
// case (window.innerWidth <= 415):
// this.displayedColumns = ['hop_sequence', 'pubkey_alias', 'fee_msat'];
// break;
// case (window.innerWidth > 415 && window.innerWidth <= 730):
// this.displayedColumns = ['hop_sequence', 'pubkey_alias', 'chan_id', 'fee_msat'];
// break;
// case (window.innerWidth > 730 && window.innerWidth <= 1024):
// this.displayedColumns = ['hop_sequence', 'pubkey_alias', 'chan_id', 'chan_capacity', 'amt_to_forward_msat', 'fee_msat'];
// break;
// case (window.innerWidth > 1024 && window.innerWidth <= 1280):
// this.flgSticky = true;
// this.displayedColumns = ['hop_sequence', 'pubkey_alias', 'chan_id', 'chan_capacity', 'amt_to_forward_msat', 'fee_msat'];
// break;
// default:
// this.flgSticky = true;
// this.displayedColumns = ['hop_sequence', 'pubkey_alias', 'chan_id', 'chan_capacity', 'amt_to_forward_msat', 'fee_msat'];
// break;
// }
// }
// ngOnInit() {
// this.clEffects.setQueryRoutes
// .pipe(takeUntil(this.unSubs[1]))
// .subscribe(queryRoute => {
// this.qrHops = new MatTableDataSource([]);
// this.qrHops.data = [];
// if (undefined !== queryRoute.routes && undefined !== queryRoute.routes[0].hops) {
// this.flgLoading[0] = false;
// this.qrHops = new MatTableDataSource<HopCL>([...queryRoute.routes[0].hops]);
// this.qrHops.data = queryRoute.routes[0].hops;
// } else {
// this.flgLoading[0] = 'error';
// }
// this.qrHops.sort = this.sort;
// });
// }
// onQueryRoutes() {
// this.flgLoading[0] = true;
// this.store.dispatch(new RTLActions.GetQueryRoutes({destPubkey: this.destinationPubkey, amount: this.amount}));
// }
// resetData() {
// this.destinationPubkey = '';
// this.amount = null;
// this.flgLoading[0] = false;
// }
// onHopClick(selRow: HopCL, event: any) {
// const selHop = this.qrHops.data.filter(hop => {
// return hop.hop_sequence === selRow.hop_sequence;
// })[0];
// const reorderedHop = JSON.parse(JSON.stringify(selHop, [
// 'hop_sequence', 'pubkey_alias', 'pub_key', 'chan_id', 'chan_capacity', 'expiry', 'amt_to_forward', 'amt_to_forward_msat', 'fee_msat'
// ] , 2));
// this.store.dispatch(new RTLActions.OpenAlert({ width: '75%', data: { type: 'INFO', message: JSON.stringify(reorderedHop)}}));
// }
ngOnDestroy() {
this.unSubs.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

@ -0,0 +1,82 @@
<!-- <div fxLayout="column">
<div class="padding-gap">
<mat-card>
<mat-card-header>
<mat-card-subtitle>
<h2>Verify and Send Payments</h2>
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<form fxLayout="column" fxLayoutAlign="space-between stretch" fxLayout.gt-md="row wrap" #sendPaymentForm="ngForm">
<div fxFlex="69" fxLayoutAlign="space-between stretch">
<mat-form-field class="w-100">
<input matInput placeholder="Payment Request" name="paymentRequest" [(ngModel)]="paymentRequest" tabindex="1" required #paymentReq="ngModel">
</mat-form-field>
</div>
<div fxFlex="30" fxLayoutAlign="space-between stretch">
<button fxFlex="48" fxLayoutAlign="center center" mat-raised-button color="primary" [disabled]="paymentReq.invalid" (click)="onSendPayment()" tabindex="2">
<p *ngIf="paymentReq.invalid && (paymentReq.dirty || paymentReq.touched); else sendText">Invalid Req</p>
<ng-template #sendText><p>Send Payment</p></ng-template>
</button>
<button fxFlex="48" mat-raised-button color="accent" type="reset" tabindex="3" type="reset" (click)="resetData()">Clear</button>
</div>
</form>
</mat-card-content>
</mat-card>
</div>
<div class="padding-gap">
<mat-card>
<mat-card-content class="table-card-content">
<div fxLayout="row" fxLayoutAlign="start start">
<mat-form-field fxFlex="30">
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
</mat-form-field>
</div>
<div perfectScrollbar class="table-container mat-elevation-z8">
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
<table mat-table #table [dataSource]="payments" matSort [ngClass]="{'mat-elevation-z8 overflow-auto error-border': flgLoading[0]==='error','mat-elevation-z8 overflow-auto': true}">
<ng-container matColumnDef="creation_date">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Creation Date</th>
<td mat-cell *matCellDef="let payment">{{payment?.creation_date_str}}</td>
</ng-container>
<ng-container matColumnDef="payment_hash">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Payment Hash</th>
<td mat-cell *matCellDef="let payment">
<div>{{payment?.payment_hash | slice:0:10}}...</div>
</td>
</ng-container>
<ng-container matColumnDef="fee">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Fee</th>
<td mat-cell *matCellDef="let payment"><span fxLayoutAlign="end center">{{payment?.fee | number}}</span></td>
</ng-container>
<ng-container matColumnDef="value">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Value</th>
<td mat-cell *matCellDef="let payment"><span fxLayoutAlign="end center">{{payment?.value | number}}</span></td>
</ng-container>
<ng-container matColumnDef="payment_preimage">
<th mat-header-cell class="pl-4" *matHeaderCellDef mat-sort-header>Payment Pre Image</th>
<td mat-cell class="pl-4" *matCellDef="let payment">
<div>{{payment?.payment_preimage | slice:0:10}}...</div>
</td>
</ng-container>
<ng-container matColumnDef="value_msat">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Value MSat</th>
<td mat-cell *matCellDef="let payment"><span fxLayoutAlign="end center">{{payment?.value_msat | number}}</span></td>
</ng-container>
<ng-container matColumnDef="value_sat">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Value Sat</th>
<td mat-cell *matCellDef="let payment"><span fxLayoutAlign="end center">{{payment?.value_sat | number}}</span></td>
</ng-container>
<ng-container matColumnDef="path">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Path</th>
<td mat-cell *matCellDef="let payment">{{payment?.path?.length || 0}} Hops</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: flgSticky;"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [@newlyAddedRowAnimation]="(row.payment_hash === newlyAddedPayment && flgAnimate) ? 'added' : 'notAdded'" (click)="onPaymentClick(row, $event)"></tr>
</table>
</div>
</mat-card-content>
</mat-card>
</div>
</div> -->
<h3>PAYMENTS SEND RECEIVE</h3>

@ -0,0 +1,31 @@
.mat-column-path {
padding-left: 10px;
}
.mat-expansion-panel-header {
padding: 0;
}
.mat-accordion .mat-expansion-panel {
padding: 0 10px;
}
.ml-minus-24px {
margin-left: -24px;
}
.info-column {
flex: 1 1 34%;
box-sizing: border-box;
max-width: 34%;
}
.info-value {
flex: 1 1 64%;
max-width: 64%;
word-break: break-word;
}
table {
width:100%;
}

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

@ -0,0 +1,194 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { formatDate } from '@angular/common';
import { Subject } from 'rxjs';
import { takeUntil, take } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { MatTableDataSource, MatSort } from '@angular/material';
import { GetInfoCL, PaymentCL, PayRequestCL } from '../../../shared/models/clModels';
import { LoggerService } from '../../../shared/services/logger.service';
import { newlyAddedRowAnimation } from '../../../shared/animation/row-animation';
import { CLEffects } from '../../store/cl.effects';
import { RTLEffects } from '../../../store/rtl.effects';
import * as RTLActions from '../../../store/rtl.actions';
import * as fromRTLReducer from '../../../store/rtl.reducers';
@Component({
selector: 'rtl-cl-payments',
templateUrl: './payments.component.html',
styleUrls: ['./payments.component.scss'],
animations: [newlyAddedRowAnimation]
})
export class CLPaymentsComponent implements OnInit, OnDestroy {
@ViewChild(MatSort, { static: true }) sort: MatSort;
@ViewChild('sendPaymentForm', { static: true }) form;
public newlyAddedPayment = '';
public flgAnimate = true;
public flgLoading: Array<Boolean | 'error'> = [true];
public information: GetInfoCL = {};
public payments: any;
public paymentJSONArr: PaymentCL[] = [];
public displayedColumns = [];
public paymentDecoded: PayRequestCL = {};
public paymentRequest = '';
public flgSticky = false;
private unsub: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
ngOnInit() {}
// constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private rtlEffects: RTLEffects, private clEffects: CLEffects) {
// switch (true) {
// case (window.innerWidth <= 415):
// this.displayedColumns = ['creation_date', 'fee', 'value'];
// break;
// case (window.innerWidth > 415 && window.innerWidth <= 730):
// this.displayedColumns = ['creation_date', 'payment_hash', 'fee', 'value', 'payment_preimage'];
// break;
// case (window.innerWidth > 730 && window.innerWidth <= 1024):
// this.displayedColumns = ['creation_date', 'payment_hash', 'fee', 'value', 'payment_preimage', 'path'];
// break;
// case (window.innerWidth > 1024 && window.innerWidth <= 1280):
// this.flgSticky = true;
// this.displayedColumns = ['creation_date', 'payment_hash', 'fee', 'value', 'payment_preimage', 'value_msat', 'value_sat', 'path'];
// break;
// default:
// this.flgSticky = true;
// this.displayedColumns = ['creation_date', 'payment_hash', 'fee', 'value', 'payment_preimage', 'value_msat', 'value_sat', 'path'];
// break;
// }
// }
// ngOnInit() {
// this.store.select('cl')
// .pipe(takeUntil(this.unsub[0]))
// .subscribe((rtlStore) => {
// rtlStore.effectErrorsCl.forEach(effectsErr => {
// if (effectsErr.action === 'FetchPayments') {
// this.flgLoading[0] = 'error';
// }
// });
// this.information = rtlStore.information;
// this.paymentJSONArr = (null !== rtlStore.payments && rtlStore.payments.length > 0) ? rtlStore.payments : [];
// this.payments = (undefined === rtlStore.payments || null == rtlStore.payments) ? new MatTableDataSource([]) : new MatTableDataSource<Payment>([...this.paymentJSONArr]);
// this.payments.data = this.paymentJSONArr;
// this.payments.sort = this.sort;
// this.payments.data.forEach(payment => {
// payment.creation_date_str = (payment.creation_date_str === '') ? '' : formatDate(payment.creation_date_str, 'MMM/dd/yy HH:mm:ss', 'en-US');
// });
// setTimeout(() => { this.flgAnimate = false; }, 3000);
// if (this.flgLoading[0] !== 'error') {
// this.flgLoading[0] = (undefined !== this.paymentJSONArr) ? false : true;
// }
// this.logger.info(rtlStore);
// });
// }
// onSendPayment() {
// if (undefined !== this.paymentDecoded.timestamp_str) {
// this.sendPayment();
// } else {
// this.store.dispatch(new RTLActions.OpenSpinner('Decoding Payment...'));
// this.store.dispatch(new RTLActions.DecodePayment(this.paymentRequest));
// this.clEffects.setDecodedPayment
// .pipe(take(1))
// .subscribe(decodedPayment => {
// this.paymentDecoded = decodedPayment;
// if (undefined !== this.paymentDecoded.timestamp_str) {
// this.paymentDecoded.timestamp_str = (this.paymentDecoded.timestamp_str === '') ? '' :
// formatDate(this.paymentDecoded.timestamp_str, 'MMM/dd/yy HH:mm:ss', 'en-US');
// if (undefined === this.paymentDecoded.num_satoshis) {
// this.paymentDecoded.num_satoshis = '0';
// }
// this.sendPayment();
// } else {
// this.resetData();
// }
// });
// }
// }
// sendPayment() {
// this.flgAnimate = true;
// this.newlyAddedPayment = this.paymentDecoded.payment_hash;
// if (undefined === this.paymentDecoded.num_satoshis || this.paymentDecoded.num_satoshis === '' || this.paymentDecoded.num_satoshis === '0') {
// const titleMsg = 'This is an empty invoice. Enter the amount (Sats) to pay.';
// this.store.dispatch(new RTLActions.OpenConfirmation({ width: '70%', data: {
// type: 'CONFIRM', titleMessage: titleMsg, message: JSON.stringify(this.paymentDecoded), noBtnText: 'Cancel', yesBtnText: 'Send', flgShowInput: true, getInputs: [
// {placeholder: 'Amount (Sats)', inputType: 'number', inputValue: ''}
// ]
// }}));
// this.rtlEffects.closeConfirm
// .pipe(take(1))
// .subscribe(confirmRes => {
// if (confirmRes) {
// this.paymentDecoded.num_satoshis = confirmRes[0].inputValue;
// this.store.dispatch(new RTLActions.OpenSpinner('Sending Payment...'));
// this.store.dispatch(new RTLActions.SendPayment([this.paymentRequest, this.paymentDecoded, true]));
// this.resetData();
// }
// });
// } else {
// this.store.dispatch(new RTLActions.OpenConfirmation({ width: '70%', data: {
// type: 'CONFIRM', titleMessage: 'Send Payment', noBtnText: 'Cancel', yesBtnText: 'Send', message: JSON.stringify(this.paymentDecoded)
// }}));
// this.rtlEffects.closeConfirm
// .pipe(take(1))
// .subscribe(confirmRes => {
// if (confirmRes) {
// this.store.dispatch(new RTLActions.OpenSpinner('Sending Payment...'));
// this.store.dispatch(new RTLActions.SendPayment([this.paymentRequest, this.paymentDecoded, false]));
// this.resetData();
// }
// });
// }
// }
// onVerifyPayment() {
// this.store.dispatch(new RTLActions.OpenSpinner('Decoding Payment...'));
// this.store.dispatch(new RTLActions.DecodePayment(this.paymentRequest));
// this.clEffects.setDecodedPayment.subscribe(decodedPayment => {
// this.paymentDecoded = decodedPayment;
// if (undefined !== this.paymentDecoded.timestamp_str) {
// this.paymentDecoded.timestamp_str = (this.paymentDecoded.timestamp_str === '') ? '' :
// formatDate(this.paymentDecoded.timestamp_str, 'MMM/dd/yy HH:mm:ss', 'en-US');
// } else {
// this.resetData();
// }
// });
// }
// resetData() {
// this.form.reset();
// this.paymentDecoded = {};
// }
// onPaymentClick(selRow: Payment, event: any) {
// const flgExpansionClicked = event.target.className.includes('mat-expansion-panel-header') || event.target.className.includes('mat-expansion-indicator');
// if (flgExpansionClicked) {
// return;
// }
// const selPayment = this.payments.data.filter(payment => {
// return payment.payment_hash === selRow.payment_hash;
// })[0];
// const reorderedPayment = JSON.parse(JSON.stringify(selPayment, [
// 'creation_date_str', 'payment_hash', 'fee', 'value_msat', 'value_sat', 'value', 'payment_preimage', 'path'
// ] , 2));
// this.store.dispatch(new RTLActions.OpenAlert({ width: '75%', data: {
// type: 'INFO',
// message: JSON.stringify(reorderedPayment)
// }}));
// }
// applyFilter(selFilter: string) {
// this.payments.filter = selFilter;
// }
ngOnDestroy() {
this.unsub.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

@ -0,0 +1,81 @@
<div fxLayout="column">
<div class="padding-gap">
<mat-card>
<mat-card-header>
<mat-card-subtitle>
<h2>Connect Peer</h2>
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<form fxLayout="column" fxLayout.gt-sm="row wrap" (ngSubmit)="connectPeerForm.form.valid && onConnectPeer()" #connectPeerForm="ngForm">
<mat-form-field fxFlex="70" fxLayoutAlign="start end">
<input matInput placeholder="Lightning Address (pubkey OR pubkey@ip:port)" name="peerAddress" [(ngModel)]="peerAddress" tabindex="1" required #peerAdd="ngModel">
</mat-form-field>
<div fxFlex="15" fxLayoutAlign="start start">
<button fxFlex="90" fxLayoutAlign="center center" mat-raised-button color="primary" [disabled]="peerAdd.invalid" type="submit" tabindex="2">
<p *ngIf="peerAdd.invalid && (peerAdd.dirty || peerAdd.touched); else connectText">Invalid Address</p>
<ng-template #connectText><p>Connect</p></ng-template>
</button>
</div>
<div fxFlex="15" fxLayoutAlign="start start">
<button fxFlex="90" fxLayoutAlign="center center" mat-raised-button color="accent" tabindex="2" type="reset" (click)="resetData()">Clear</button>
</div>
</form>
</mat-card-content>
</mat-card>
</div>
<div class="padding-gap">
<mat-card>
<mat-card-content class="table-card-content">
<div fxLayout="row" fxLayoutAlign="start start">
<mat-form-field fxFlex="30">
<input matInput (keyup)="applyFilter($event.target.value)" placeholder="Filter">
</mat-form-field>
</div>
<div perfectScrollbar class="table-container mat-elevation-z8">
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
<table mat-table #table [dataSource]="peers" matSort [ngClass]="{'mat-elevation-z8 overflow-x-auto error-border': flgLoading[0]==='error','mat-elevation-z8 overflow-x-auto': true}">
<ng-container matColumnDef="detach">
<th mat-header-cell *matHeaderCellDef>Detach</th>
<td mat-cell *matCellDef="let peer"><mat-icon color="accent" (click)="onPeerDetach(peer)">link_off</mat-icon></td>
</ng-container>
<ng-container matColumnDef="open_channel">
<th mat-header-cell *matHeaderCellDef>Open Channel</th>
<td mat-cell *matCellDef="let peer"><mat-icon color="accent" (click)="onOpenChannel(peer)">playlist_add</mat-icon></td>
</ng-container>
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef mat-sort-header> ID </th>
<td mat-cell *matCellDef="let peer">
<div> {{peer?.id | slice:0:10}}... </div>
</td>
</ng-container>
<ng-container matColumnDef="alias">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Alias </th>
<td mat-cell *matCellDef="let peer"> {{peer?.alias}} </td>
</ng-container>
<ng-container matColumnDef="connected">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Connected </th>
<td mat-cell *matCellDef="let peer"> {{peer?.connected}} </td>
</ng-container>
<ng-container matColumnDef="netaddr">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Network Address </th>
<td mat-cell *matCellDef="let peer">
<span *ngFor="let addr of peer?.netaddr; last as isLast">{{ addr}}<span *ngIf="!isLast">,<br></span></span>
</td>
</ng-container>
<ng-container matColumnDef="globalfeatures">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Global Features </th>
<td mat-cell *matCellDef="let peer"> {{peer?.globalfeatures}} </td>
</ng-container>
<ng-container matColumnDef="localfeatures">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Local Features </th>
<td mat-cell *matCellDef="let peer"> {{peer?.localfeatures}} </td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: flgSticky;"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;" [@newlyAddedRowAnimation]="(row.id === newlyAddedPeer && flgAnimate) ? 'added' : 'notAdded'" (click)="onPeerClick(row, $event)"></tr>
</table>
</div>
</mat-card-content>
</mat-card>
</div>
</div>

@ -0,0 +1,17 @@
.mat-column-detach {
flex: 0 0 5%;
min-width: 50px;
}
.mat-column-alias, .mat-column-address {
flex: 0 0 15%;
min-width: 100px;
}
.mat-cell.mat-column-detach {
cursor: pointer;
}
table {
width:100%;
}

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

@ -0,0 +1,145 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
import { Subject } from 'rxjs';
import { takeUntil, filter, take } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { Actions } from '@ngrx/effects';
import { MatTableDataSource, MatSort } from '@angular/material';
import { PeerCL, GetInfoCL } from '../../shared/models/clModels';
import { LoggerService } from '../../shared/services/logger.service';
import { newlyAddedRowAnimation } from '../../shared/animation/row-animation';
import { CLEffects } from '../store/cl.effects';
import { RTLEffects } from '../../store/rtl.effects';
import * as RTLActions from '../../store/rtl.actions';
import * as fromRTLReducer from '../../store/rtl.reducers';
@Component({
selector: 'rtl-cl-peers',
templateUrl: './peers.component.html',
styleUrls: ['./peers.component.scss'],
animations: [newlyAddedRowAnimation]
})
export class CLPeersComponent implements OnInit, OnDestroy {
@ViewChild(MatSort, { static: true }) sort: MatSort;
public newlyAddedPeer = '';
public flgAnimate = true;
public displayedColumns = [];
public peerAddress = '';
public peers: any;
public information: GetInfoCL = {};
public flgLoading: Array<Boolean | 'error'> = [true]; // 0: peers
public flgSticky = false;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private rtlEffects: RTLEffects, private clEffects: CLEffects, private actions$: Actions, private router: Router) {
switch (true) {
case (window.innerWidth <= 415):
this.displayedColumns = ['detach', 'id', 'alias', 'connected'];
break;
case (window.innerWidth > 415 && window.innerWidth <= 730):
this.displayedColumns = ['detach', 'id', 'alias', 'connected', 'netaddr'];
break;
case (window.innerWidth > 730 && window.innerWidth <= 1024):
this.displayedColumns = ['detach', 'open_channel', 'id', 'alias', 'connected', 'netaddr', 'globalfeatures', 'localfeatures'];
break;
case (window.innerWidth > 1024 && window.innerWidth <= 1280):
this.flgSticky = true;
this.displayedColumns = ['detach', 'open_channel', 'id', 'alias', 'connected', 'netaddr', 'globalfeatures', 'localfeatures'];
break;
default:
this.flgSticky = true;
this.displayedColumns = ['detach', 'open_channel', 'id', 'alias', 'connected', 'netaddr', 'globalfeatures', 'localfeatures'];
break;
}
}
ngOnInit() {
this.store.dispatch(new RTLActions.FetchPeersCL());
this.store.select('cl')
.pipe(takeUntil(this.unSubs[0]))
.subscribe((rtlStore) => {
rtlStore.effectErrorsCl.forEach(effectsErr => {
if (effectsErr.action === 'FetchPeersCL') {
this.flgLoading[0] = 'error';
}
});
this.information = rtlStore.information;
this.peers = new MatTableDataSource([]);
this.peers.data = [];
if (undefined !== rtlStore.peers) {
this.peers = new MatTableDataSource<PeerCL>([...rtlStore.peers]);
this.peers.data = rtlStore.peers;
setTimeout(() => { this.flgAnimate = false; }, 3000);
}
this.peers.sort = this.sort;
if (this.flgLoading[0] !== 'error') {
this.flgLoading[0] = false;
}
this.logger.info(rtlStore);
});
this.actions$
.pipe(
takeUntil(this.unSubs[1]),
filter((action) => action.type === RTLActions.SET_PEERS_CL)
).subscribe((setPeers: RTLActions.SetPeersCL) => {
this.peerAddress = undefined;
});
}
onConnectPeer() {
this.flgAnimate = true;
this.newlyAddedPeer = this.peerAddress;
this.store.dispatch(new RTLActions.OpenSpinner('Adding Peer...'));
this.store.dispatch(new RTLActions.SaveNewPeerCL({id: this.peerAddress}));
}
onPeerClick(selRow: PeerCL, event: any) {
const flgCloseClicked = event.target.className.includes('mat-column-detach') || event.target.className.includes('mat-icon');
if (flgCloseClicked) {
return;
}
const selPeer = this.peers.data.filter(peer => {
return peer.id === selRow.id;
})[0];
const reorderedPeer = JSON.parse(JSON.stringify(selPeer, [
'id', 'alias', 'connected', 'netaddr', 'globalfeatures', 'localfeatures'
] , 2));
this.store.dispatch(new RTLActions.OpenAlert({ width: '75%', data: { type: 'INFO', message: JSON.stringify(reorderedPeer)}}));
}
resetData() {
this.peerAddress = '';
}
onOpenChannel(peerToAddChannel: PeerCL) {
this.router.navigate(['cl/chnlmanage'], { state: { peer: peerToAddChannel.id }});
}
onPeerDetach(peerToDetach: PeerCL) {
const msg = 'Detach peer: ' + peerToDetach.id;
const msg_type = 'CONFIRM';
this.store.dispatch(new RTLActions.OpenConfirmation({ width: '70%', data: { type: msg_type, titleMessage: msg, noBtnText: 'Cancel', yesBtnText: 'Detach'}}));
this.rtlEffects.closeConfirm
.pipe(takeUntil(this.unSubs[3]))
.subscribe(confirmRes => {
if (confirmRes) {
this.store.dispatch(new RTLActions.OpenSpinner('Detaching Peer...'));
this.store.dispatch(new RTLActions.DetachPeerCL({id: peerToDetach.id, force: false}));
}
});
}
applyFilter(selFilter: string) {
this.peers.filter = selFilter;
}
ngOnDestroy() {
this.unSubs.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

@ -2,7 +2,7 @@ import { Injectable, OnDestroy } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Store } from '@ngrx/store';
import { Actions, Effect, ofType } from '@ngrx/effects';
import { of } from 'rxjs';
import { Subject, of } from 'rxjs';
import { map, mergeMap, catchError, withLatestFrom } from 'rxjs/operators';
import { environment, API_URL } from '../../../environments/environment';
@ -14,8 +14,8 @@ import * as RTLActions from '../../store/rtl.actions';
@Injectable()
export class CLEffects implements OnDestroy {
dialogRef: any;
CHILD_API_URL = API_URL + '/cl';
private unSubs: Array<Subject<void>> = [new Subject(), new Subject()];
constructor(
private actions$: Actions,
@ -25,10 +25,10 @@ export class CLEffects implements OnDestroy {
@Effect()
infoFetchCL = this.actions$.pipe(
ofType(RTLActions.FETCH_CL_INFO),
ofType(RTLActions.FETCH_INFO_CL),
withLatestFrom(this.store.select('root')),
mergeMap(([action, store]) => {
this.store.dispatch(new RTLActions.ClearEffectErrorCl('FetchCLInfo'));
this.store.dispatch(new RTLActions.ClearEffectErrorCl('FetchInfoCL'));
return this.httpClient.get<GetInfoCL>(this.CHILD_API_URL + environment.GETINFO_API)
.pipe(
map((info) => {
@ -60,14 +60,12 @@ export class CLEffects implements OnDestroy {
};
this.store.dispatch(new RTLActions.SetNodeData(node_data));
return {
type: RTLActions.SET_CL_INFO,
type: RTLActions.SET_INFO_CL,
payload: (undefined !== info) ? info : {}
};
}),
catchError((err) => {
this.logger.error(err);
this.store.dispatch(new RTLActions.EffectErrorCl({ action: 'FetchCLInfo', code: err.status, message: err.error.error }));
return of();
return this.handleErrorWithAlert('ERROR', 'Get Info Failed', this.CHILD_API_URL + environment.GETINFO_API, err);
})
);
}
@ -75,67 +73,193 @@ export class CLEffects implements OnDestroy {
@Effect()
fetchFeesCL = this.actions$.pipe(
ofType(RTLActions.FETCH_CL_FEES),
mergeMap((action: RTLActions.FetchCLFees) => {
this.store.dispatch(new RTLActions.ClearEffectErrorCl('FetchCLFees'));
ofType(RTLActions.FETCH_FEES_CL),
mergeMap((action: RTLActions.FetchFeesCL) => {
this.store.dispatch(new RTLActions.ClearEffectErrorCl('FetchFeesCL'));
return this.httpClient.get<FeesCL>(this.CHILD_API_URL + environment.FEES_API);
}),
map((fees) => {
this.logger.info(fees);
return {
type: RTLActions.SET_CL_FEES,
type: RTLActions.SET_FEES_CL,
payload: (undefined !== fees) ? fees : {}
};
}),
catchError((err: any) => {
this.logger.error(err);
this.store.dispatch(new RTLActions.EffectErrorCl({ action: 'FetchCLFees', code: err.status, message: err.error.error }));
return of();
return this.handleErrorWithoutAlert('FetchFeesCL', err);
}
));
@Effect()
fetchBalanceCL = this.actions$.pipe(
ofType(RTLActions.FETCH_CL_BALANCE),
mergeMap((action: RTLActions.FetchCLBalance) => {
this.store.dispatch(new RTLActions.ClearEffectErrorCl('FetchCLBalance'));
ofType(RTLActions.FETCH_BALANCE_CL),
mergeMap((action: RTLActions.FetchBalanceCL) => {
this.store.dispatch(new RTLActions.ClearEffectErrorCl('FetchBalanceCL'));
return this.httpClient.get<BalanceCL>(this.CHILD_API_URL + environment.BALANCE_API);
}),
map((balance) => {
this.logger.info(balance);
return {
type: RTLActions.SET_CL_BALANCE,
type: RTLActions.SET_BALANCE_CL,
payload: (undefined !== balance) ? balance : {}
};
}),
catchError((err: any) => {
this.logger.error(err);
this.store.dispatch(new RTLActions.EffectErrorCl({ action: 'FetchCLBalance', code: err.status, message: err.error.error }));
return of();
return this.handleErrorWithoutAlert('FetchBalanceCL', err);
}
));
@Effect()
fetchLocalRemoteBalanceCL = this.actions$.pipe(
ofType(RTLActions.FETCH_CL_LOCAL_REMOTE_BALANCE),
mergeMap((action: RTLActions.FetchCLLocalRemoteBalance) => {
this.store.dispatch(new RTLActions.ClearEffectErrorCl('FetchCLLocalRemoteBalance'));
ofType(RTLActions.FETCH_LOCAL_REMOTE_BALANCE_CL),
mergeMap((action: RTLActions.FetchLocalRemoteBalanceCL) => {
this.store.dispatch(new RTLActions.ClearEffectErrorCl('FetchLocalRemoteBalanceCL'));
return this.httpClient.get<LocalRemoteBalanceCL>(this.CHILD_API_URL + environment.CHANNELS_API + '/localremotebalance');
}),
map((lrBalance) => {
this.logger.info(lrBalance);
return {
type: RTLActions.SET_CL_LOCAL_REMOTE_BALANCE,
type: RTLActions.SET_LOCAL_REMOTE_BALANCE_CL,
payload: (undefined !== lrBalance) ? lrBalance : {}
};
}),
catchError((err: any) => {
this.logger.error(err);
this.store.dispatch(new RTLActions.EffectErrorCl({ action: 'FetchCLLocalRemoteBalance', code: err.status, message: err.error.error }));
return of();
return this.handleErrorWithoutAlert('FetchLocalRemoteBalanceCL', err);
}
));
@Effect()
getNewAddressCL = this.actions$.pipe(
ofType(RTLActions.GET_NEW_ADDRESS_CL),
mergeMap((action: RTLActions.GetNewAddressCL) => {
return this.httpClient.get(this.CHILD_API_URL + environment.NEW_ADDRESS_API + '?type=' + action.payload.addressId)
.pipe(map((newAddress: any) => {
this.logger.info(newAddress);
this.store.dispatch(new RTLActions.CloseSpinner());
return {
type: RTLActions.SET_NEW_ADDRESS_CL,
payload: (undefined !== newAddress && undefined !== newAddress.address) ? newAddress.address : {}
};
}),
catchError((err: any) => {
return this.handleErrorWithAlert('ERROR', 'Generate New Address Failed', this.CHILD_API_URL + environment.NEW_ADDRESS_API + '?type=' + action.payload.addressId, err);
}));
})
);
@Effect({ dispatch: false })
setNewAddressCL = this.actions$.pipe(
ofType(RTLActions.SET_NEW_ADDRESS_CL),
map((action: RTLActions.SetNewAddressCL) => {
this.logger.info(action.payload);
return action.payload;
})
);
@Effect()
peersFetchCL = this.actions$.pipe(
ofType(RTLActions.FETCH_PEERS_CL),
mergeMap((action: RTLActions.FetchPeersCL) => {
this.store.dispatch(new RTLActions.ClearEffectErrorCl('FetchPeersCL'));
return this.httpClient.get(this.CHILD_API_URL + environment.PEERS_API)
.pipe(
map((peers: any) => {
this.logger.info(peers);
return {
type: RTLActions.SET_PEERS_CL,
payload: (undefined !== peers) ? peers : []
};
}),
catchError((err: any) => {
return this.handleErrorWithoutAlert('FetchPeersCL', err);
})
);
}
));
@Effect()
saveNewPeerCL = this.actions$.pipe(
ofType(RTLActions.SAVE_NEW_PEER_CL),
mergeMap((action: RTLActions.SaveNewPeerCL) => {
return this.httpClient.post(this.CHILD_API_URL + environment.PEERS_API, { id: action.payload.id })
.pipe(
map((postRes: any) => {
this.logger.info(postRes);
this.store.dispatch(new RTLActions.CloseSpinner());
this.store.dispatch(new RTLActions.OpenAlert({ width: '70%', data: { type: 'SUCCESS', titleMessage: 'Peer Added Successfully!' } }));
return {
type: RTLActions.SET_PEERS_CL,
payload: (undefined !== postRes && postRes.length > 0) ? postRes : []
};
}),
catchError((err: any) => {
return this.handleErrorWithAlert('ERROR', 'Add Peer Failed', this.CHILD_API_URL + environment.PEERS_API, err);
})
);
}
));
@Effect()
detachPeerCL = this.actions$.pipe(
ofType(RTLActions.DETACH_PEER_CL),
mergeMap((action: RTLActions.DetachPeerCL) => {
return this.httpClient.delete(this.CHILD_API_URL + environment.PEERS_API + '/' + action.payload.id + '?force=' + action.payload.force)
.pipe(
map((postRes: any) => {
this.logger.info(postRes);
this.store.dispatch(new RTLActions.CloseSpinner());
this.store.dispatch(new RTLActions.OpenAlert({ width: '70%', data: { type: 'SUCCESS', titleMessage: 'Peer Detached Successfully!' } }));
return {
type: RTLActions.REMOVE_PEER_CL,
payload: { id: action.payload.id }
};
}),
catchError((err: any) => {
return this.handleErrorWithAlert('ERROR', 'Unable to Detach Peer. Try again later.', this.CHILD_API_URL + environment.PEERS_API + '/' + action.payload.id, err);
})
);
}
));
ngOnDestroy() { }
handleErrorWithoutAlert(actionName: string, err: {status: number, error: any}) {
this.logger.error(err);
if(err.status === 401) {
this.logger.info('Redirecting to Signin');
return of({ type: RTLActions.SIGNOUT });
} else {
this.store.dispatch(new RTLActions.EffectErrorCl({ action: actionName, code: err.status.toString(), message: err.error.error }));
this.logger.error(err);
return of({type:RTLActions.VOID});
}
}
handleErrorWithAlert(alerType: string, alertTitle: string, errURL: string, err: {status: number, error: any}) {
this.logger.error(err);
if(err.status === 401) {
this.logger.info('Redirecting to Signin');
return of({ type: RTLActions.SIGNOUT });
} else {
this.store.dispatch(new RTLActions.CloseSpinner());
this.logger.error(err);
return of(
{
type: RTLActions.OPEN_ALERT,
payload: {
width: '70%', data: {
type: alerType, titleMessage: alertTitle,
message: JSON.stringify({ code: err.status, Message: err.error.error, URL: errURL })
}
}
}
);
}
}
ngOnDestroy() {
this.unSubs.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

@ -1,5 +1,5 @@
import { SelNodeChild } from '../../shared/models/RTLconfig';
import { GetInfoCL, FeesCL, BalanceCL, LocalRemoteBalanceCL } from '../../shared/models/clModels';
import { GetInfoCL, FeesCL, BalanceCL, LocalRemoteBalanceCL, AddressTypeCL, PeerCL } from '../../shared/models/clModels';
import { ErrorPayload } from '../../shared/models/errorPayload';
import * as RTLActions from '../../store/rtl.actions';
@ -10,6 +10,8 @@ export interface CLState {
fees: FeesCL;
balance: BalanceCL;
localRemoteBalance: LocalRemoteBalanceCL;
peers: PeerCL[];
addressTypes: AddressTypeCL[];
}
export const initCLState: CLState = {
@ -18,7 +20,12 @@ export const initCLState: CLState = {
information: {},
fees: {},
balance: {},
localRemoteBalance: {}
localRemoteBalance: {},
peers: [],
addressTypes: [
{ addressId: '0', addressTp: 'bech32', addressDetails: 'bech32'},
{ addressId: '1', addressTp: 'p2sh-segwit', addressDetails: 'p2sh-segwit (default)'}
]
}
export function CLReducer(state = initCLState, action: RTLActions.RTLActions) {
@ -33,38 +40,60 @@ export function CLReducer(state = initCLState, action: RTLActions.RTLActions) {
}
return {
...state,
effectErrors: clearedEffectErrors
effectErrorsCl: clearedEffectErrors
};
case RTLActions.EFFECT_ERROR_CL:
return {
...state,
effectErrors: [...state.effectErrorsCl, action.payload]
effectErrorsCl: [...state.effectErrorsCl, action.payload]
};
case RTLActions.RESET_CL_STORE:
case RTLActions.RESET_STORE_CL:
return {
...initCLState,
nodeSettings: action.payload,
};
case RTLActions.SET_CL_INFO:
case RTLActions.SET_INFO_CL:
return {
...state,
information: action.payload
};
case RTLActions.SET_CL_FEES:
case RTLActions.SET_FEES_CL:
return {
...state,
fees: action.payload
};
case RTLActions.SET_CL_BALANCE:
case RTLActions.SET_BALANCE_CL:
return {
...state,
balance: action.payload
};
case RTLActions.SET_CL_LOCAL_REMOTE_BALANCE:
case RTLActions.SET_LOCAL_REMOTE_BALANCE_CL:
return {
...state,
localRemoteBalance: action.payload
};
case RTLActions.SET_PEERS_CL:
return {
...state,
peers: action.payload
};
case RTLActions.ADD_PEER_CL:
return {
...state,
peers: [...state.peers, action.payload]
};
case RTLActions.REMOVE_PEER_CL:
const modifiedPeers = [...state.peers];
const removePeerIdx = state.peers.findIndex(peer => {
return peer.id === action.payload.id;
});
if (removePeerIdx > -1) {
modifiedPeers.splice(removePeerIdx, 1);
}
return {
...state,
peers: modifiedPeers
};
default:
return state;
}

@ -0,0 +1,88 @@
<!-- <div fxLayout="column">
<div fxFlex="100" class="padding-gap">
<mat-card>
<mat-card-header>
<mat-card-subtitle>
<h2>Forwarding History</h2>
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<form fxLayout="column" fxLayoutAlign="space-between stretch" fxLayout.gt-md="row wrap"
(ngSubmit)="fhForm.form.valid && onForwardingHistoryFetch()" #fhForm="ngForm" class="padding-gap">
<div fxFlex="69" fxLayoutAlign="space-between stretch">
<mat-form-field fxFlex="49" fxLayoutAlign="start">
<input matInput [matDatepicker]="startDatepicker" placeholder="Start Date" [max]="yesterday"
name="startDate" [(ngModel)]="startDate" tabindex="1" #strtDate="ngModel">
<mat-datepicker-toggle matSuffix [for]="startDatepicker"></mat-datepicker-toggle>
<mat-datepicker #startDatepicker [startAt]="startDate"></mat-datepicker>
</mat-form-field>
<mat-form-field fxFlex="49" fxLayoutAlign="start">
<input matInput [matDatepicker]="endDatepicker" [max]="today" placeholder="End Date" name="endDate"
[(ngModel)]="endDate" tabindex="2" #enDate="ngModel">
<mat-datepicker-toggle matSuffix [for]="endDatepicker"></mat-datepicker-toggle>
<mat-datepicker #endDatepicker [startAt]="endDate"></mat-datepicker>
</mat-form-field>
</div>
<div fxFlex="30" fxLayoutAlign="space-between stretch">
<button fxFlex="50" fxLayoutAlign="center center" mat-raised-button color="primary"
[disabled]="fhForm.invalid" type="submit" tabindex="3">Fetch</button>
<button fxFlex="50" fxLayoutAlign="center center" mat-raised-button color="accent" class="ml-2" tabindex="4"
type="reset" (click)="resetData()">Clear</button>
</div>
</form>
</mat-card-content>
</mat-card>
</div>
<div class="padding-gap">
<mat-card>
<mat-card-content class="table-card-content">
<div perfectScrollbar class="table-container mat-elevation-z8">
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
<table mat-table #table [dataSource]="forwardingHistoryEvents" matSort
[ngClass]="{'mat-elevation-z8 overflow-auto error-border': flgLoading[0]==='error','mat-elevation-z8 overflow-auto': true}">
<ng-container matColumnDef="timestamp">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Timestamp</th>
<td mat-cell *matCellDef="let fhEvent">{{fhEvent.timestamp_str}}</td>
</ng-container>
<ng-container matColumnDef="chan_id_in">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Chan Id In</th>
<td mat-cell *matCellDef="let fhEvent">{{fhEvent.chan_id_in}}</td>
</ng-container>
<ng-container matColumnDef="alias_in">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Alias In</th>
<td mat-cell *matCellDef="let fhEvent">{{fhEvent.alias_in}}</td>
</ng-container>
<ng-container matColumnDef="chan_id_out">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Chan Id Out</th>
<td mat-cell *matCellDef="let fhEvent">{{fhEvent.chan_id_out}}</td>
</ng-container>
<ng-container matColumnDef="alias_out">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Alias Out</th>
<td mat-cell *matCellDef="let fhEvent">{{fhEvent.alias_out}}</td>
</ng-container>
<ng-container matColumnDef="amt_out">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Amount
Out (Sats)</th>
<td mat-cell *matCellDef="let fhEvent"><span fxLayoutAlign="end center">{{fhEvent.amt_out | number}}</span></td>
</ng-container>
<ng-container matColumnDef="amt_in">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Amount
In (Sats)</th>
<td mat-cell *matCellDef="let fhEvent"><span fxLayoutAlign="end center">{{fhEvent.amt_in | number}}</span></td>
</ng-container>
<ng-container matColumnDef="fee">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Fee
(Sats)</th>
<td mat-cell *matCellDef="let fhEvent"><span fxLayoutAlign="end center">{{fhEvent.fee | number}}</span></td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: flgSticky;"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"
(click)="onForwardingEventClick(row, $event)"></tr>
</table>
</div>
</mat-card-content>
</mat-card>
</div>
</div> -->
<h3>FORWARDING HISTORY</h3>

@ -0,0 +1,9 @@
.mat-column-amt_in {
flex: 0 0 15%;
min-width: 120px;
padding-right: 20px;
}
table {
width:100%;
}

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

@ -0,0 +1,135 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { formatDate } from '@angular/common';
import { Subject } from 'rxjs';
import { takeUntil, filter } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { Actions } from '@ngrx/effects';
import { MatTableDataSource, MatSort } from '@angular/material';
import { ForwardingEventCL } from '../../shared/models/clModels';
import { LoggerService } from '../../shared/services/logger.service';
import * as RTLActions from '../../store/rtl.actions';
import * as fromRTLReducer from '../../store/rtl.reducers';
@Component({
selector: 'rtl-cl-forwarding-history',
templateUrl: './forwarding-history.component.html',
styleUrls: ['./forwarding-history.component.scss']
})
export class CLForwardingHistoryComponent implements OnInit, OnDestroy {
@ViewChild(MatSort, { static: true }) sort: MatSort;
public displayedColumns = [];
public forwardingHistoryEvents: any;
public lastOffsetIndex = 0;
public flgLoading: Array<Boolean | 'error'> = [true];
public today = new Date(Date.now());
public yesterday = new Date(this.today.getFullYear(), this.today.getMonth(), this.today.getDate() - 1, this.today.getHours(), this.today.getMinutes(), this.today.getSeconds());
public endDate = this.today;
public startDate = this.yesterday;
public flgSticky = false;
private unsub: Array<Subject<void>> = [new Subject(), new Subject(), new Subject()];
ngOnInit() {}
// constructor(private logger: LoggerService, private store: Store<fromRTLReducer.RTLState>, private actions$: Actions) {
// switch (true) {
// case (window.innerWidth <= 415):
// this.displayedColumns = ['timestamp', 'amt_out', 'amt_in'];
// break;
// case (window.innerWidth > 415 && window.innerWidth <= 730):
// this.displayedColumns = ['timestamp', 'amt_out', 'amt_in', 'fee'];
// break;
// case (window.innerWidth > 730 && window.innerWidth <= 1024):
// this.displayedColumns = ['timestamp', 'chan_id_in', 'chan_id_out', 'amt_out', 'amt_in', 'fee'];
// break;
// case (window.innerWidth > 1024 && window.innerWidth <= 1280):
// this.flgSticky = true;
// this.displayedColumns = ['timestamp', 'chan_id_in', 'chan_id_out', 'amt_out', 'amt_in', 'fee'];
// break;
// default:
// this.flgSticky = true;
// this.displayedColumns = ['timestamp', 'chan_id_in', 'chan_id_out', 'amt_out', 'amt_in', 'fee'];
// break;
// }
// }
// ngOnInit() {
// this.onForwardingHistoryFetch();
// this.actions$.pipe(takeUntil(this.unsub[2]), filter((action) => action.type === RTLActions.RESET_LND_STORE)).subscribe((resetLndStore: RTLActions.ResetLNDStore) => {
// this.onForwardingHistoryFetch();
// });
// this.store.select('cl')
// .pipe(takeUntil(this.unsub[0]))
// .subscribe((rtlStore) => {
// rtlStore.effectErrorsCl.forEach(effectsErr => {
// if (effectsErr.action === 'GetForwardingHistory') {
// this.flgLoading[0] = 'error';
// }
// });
// if (undefined !== rtlStore.forwardingHistory && undefined !== rtlStore.forwardingHistory.forwarding_events) {
// this.lastOffsetIndex = rtlStore.forwardingHistory.last_offset_index;
// this.loadForwardingEventsTable(rtlStore.forwardingHistory.forwarding_events);
// } else {
// // To reset table after other Forwarding history calls
// this.lastOffsetIndex = 0;
// this.loadForwardingEventsTable([]);
// }
// if (this.flgLoading[0] !== 'error') {
// this.flgLoading[0] = (undefined !== rtlStore.forwardingHistory) ? false : true;
// }
// this.logger.info(rtlStore);
// });
// }
// onForwardingEventClick(selRow: ForwardingEventCL, event: any) {
// const selFEvent = this.forwardingHistoryEvents.data.filter(fhEvent => {
// return (fhEvent.chan_id_in === selRow.chan_id_in && fhEvent.timestamp === selRow.timestamp);
// })[0];
// const reorderedFHEvent = JSON.parse(JSON.stringify(selFEvent, ['timestamp_str', 'chan_id_in', 'alias_in', 'chan_id_out', 'alias_out', 'amt_out', 'amt_in', 'fee'] , 2));
// this.store.dispatch(new RTLActions.OpenAlert({ width: '75%', data: {
// type: 'INFO',
// message: JSON.stringify(reorderedFHEvent)
// }}));
// }
// loadForwardingEventsTable(forwardingEvents: ForwardingEventCL[]) {
// this.forwardingHistoryEvents = new MatTableDataSource<ForwardingEventCL>([...forwardingEvents]);
// this.forwardingHistoryEvents.sort = this.sort;
// this.forwardingHistoryEvents.data.forEach(event => {
// event.timestamp_str = (event.timestamp_str === '') ? '' : formatDate(event.timestamp_str, 'MMM/dd/yy HH:mm:ss', 'en-US');
// });
// this.logger.info(this.forwardingHistoryEvents);
// }
// onForwardingHistoryFetch() {
// if (undefined === this.endDate || this.endDate == null) {
// this.endDate = new Date();
// }
// if (undefined === this.startDate || this.startDate == null) {
// this.startDate = new Date(this.endDate.getFullYear(), this.endDate.getMonth(), this.endDate.getDate() - 1);
// }
// this.store.dispatch(new RTLActions.GetForwardingHistory({
// end_time: Math.round(this.endDate.getTime() / 1000).toString(),
// start_time: Math.round(this.startDate.getTime() / 1000).toString()
// }));
// }
resetData() {
this.endDate = new Date();
this.startDate = new Date(this.endDate.getFullYear(), this.endDate.getMonth(), this.endDate.getDate() - 1);
if (undefined !== this.forwardingHistoryEvents) {
this.forwardingHistoryEvents.data = [];
}
}
ngOnDestroy() {
this.resetData();
this.unsub.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

@ -19,15 +19,13 @@ export class LNDRootComponent implements OnInit, OnDestroy {
constructor(private store: Store<fromRTLReducer.RTLState>, private actions$: Actions, private router: Router, private activatedRoute: ActivatedRoute) {}
ngOnInit() {
this.store.dispatch(new RTLActions.FetchInfo());
this.router.navigate(['./home'], {relativeTo: this.activatedRoute});
this.actions$.pipe(takeUntil(this.unsubs[0]), filter((action) => action.type === RTLActions.SET_INFO || action.type === RTLActions.INIT_APP_DATA))
.subscribe((infoData: RTLActions.SetInfo | RTLActions.InitAppData) => {
this.actions$.pipe(takeUntil(this.unsubs[0]), filter((action) => action.type === RTLActions.SET_INFO))
.subscribe((infoData: RTLActions.SetInfo) => {
if(infoData.type === RTLActions.SET_INFO && undefined !== infoData.payload.identity_pubkey) {
this.initializeRemainingData();
}
if(infoData.type === RTLActions.INIT_APP_DATA) {
this.store.dispatch(new RTLActions.FetchInfo());
}
});
}

@ -84,19 +84,13 @@ export class LNDEffects implements OnDestroy {
}),
catchError((err) => {
this.logger.error(err);
this.store.dispatch(new RTLActions.EffectErrorLnd({ action: 'FetchInfo', code: err.status, message: err.error.error }));
if (+store.appConfig.sso.rtlSSO) {
this.router.navigate(['/ssoerror']);
if (err.status === 401) {
this.logger.info('Redirecting to Signin');
return of({ type: RTLActions.SIGNOUT });
} else {
if (err.status === 401) {
this.logger.info('Redirecting to Signin');
this.router.navigate([store.appConfig.sso.logoutRedirectLink]);
return of();
} else {
this.logger.info('Redirecting to Unlock');
this.router.navigate(['/lnd/unlocklnd']);
return of();
}
this.logger.info('Redirecting to Unlock');
this.router.navigate(['/lnd/unlocklnd']);
return of();
}
})
);

@ -70,12 +70,12 @@ export function LNDReducer(state = initLNDState, action: RTLActions.RTLActions)
}
return {
...state,
effectErrors: clearedEffectErrors
effectErrorsLnd: clearedEffectErrors
};
case RTLActions.EFFECT_ERROR_LND:
return {
...state,
effectErrors: [...state.effectErrorsLnd, action.payload]
effectErrorsLnd: [...state.effectErrorsLnd, action.payload]
};
case RTLActions.RESET_LND_STORE:
return {

@ -75,7 +75,7 @@ export class SettingsNavComponent implements OnInit, OnDestroy {
onSelectionChange(selNodeValue: LightningNode) {
this.selNode = selNodeValue;
this.store.dispatch(new RTLActions.OpenSpinner('Updating Selected Node...'));
this.store.dispatch(new RTLActions.SetSelelectedNode(selNodeValue));
this.store.dispatch(new RTLActions.SetSelelectedNode({ lnNode: selNodeValue, isInitialSetup: false }));
}
ngOnDestroy() {

@ -29,12 +29,12 @@ export interface FeesCL {
}
export interface BalanceCL {
totalBalance?: number;
confBalance?: number;
unconfBalance?: number;
btc_totalBalance?: number;
btc_confBalance?: number;
btc_unconfBalance?: number;
totalBalance?: string;
confBalance?: string;
unconfBalance?: string;
btc_totalBalance?: string;
btc_confBalance?: string;
btc_unconfBalance?: string;
}
export interface LocalRemoteBalanceCL {
@ -43,3 +43,144 @@ export interface LocalRemoteBalanceCL {
btc_localBalance?: number;
btc_remoteBalance?: number;
}
export interface ChannelCL {
active?: boolean;
remote_pubkey?: string;
remote_alias?: string;
channel_point?: string;
chan_id?: number;
capacity?: number;
local_balance?: number;
remote_balance?: number;
commit_fee?: number;
commit_weight?: number;
fee_per_kw?: number;
unsettled_balance?: number;
total_satoshis_sent?: number;
total_satoshis_received?: number;
num_updates?: number;
private?: boolean;
pending_htlcs?: any[];
csv_delay?: number;
}
export interface PeerCL {
id?: string;
connected?: boolean;
netaddr?: string[];
globalfeatures?: string;
localfeatures?: string;
alias?: string;
}
export interface InvoiceCL {
memo?: string;
receipt?: string;
r_preimage?: string;
r_hash?: string;
value?: string;
btc_value?: string;
settled?: boolean;
creation_date?: string;
creation_date_str?: string;
settle_date?: string;
settle_date_str?: string;
payment_request?: string;
description_hash?: string;
expiry?: string;
fallback_addr?: string;
cltv_expiry?: string;
route_hints?: any[];
private?: boolean;
add_index?: string;
settle_index?: string;
amt_paid?: string;
amt_paid_sat?: string;
btc_amt_paid_sat?: string;
amt_paid_msat?: string;
}
export interface ChannelEdgeCL {
channel_id?: string;
chan_point?: string;
last_update?: number;
last_update_str?: string;
node1_pub?: string;
node2_pub?: string;
capacity?: string;
node1_policy?: any;
node2_policy?: any;
}
export interface GraphNodeCL {
node?: any;
num_channels?: number;
total_capacity?: string;
}
export interface OnChainCL {
address?: string;
amount?: number;
sendAll?: boolean;
blocks?: number;
fees?: number;
}
export interface AddressTypeCL {
addressId?: string;
addressTp?: string;
addressDetails?: string;
}
export interface HopCL {
hop_sequence?: number;
pubkey_alias?: string;
chan_id?: string;
chan_capacity?: string;
amt_to_forward?: string;
fee?: string;
expiry?: number;
amt_to_forward_msat?: string;
fee_msat?: string;
pub_key?: string;
}
export interface PaymentCL {
creation_date?: number;
creation_date_str?: string;
payment_hash?: string;
path?: string[];
fee?: number;
value_msat?: number;
value_sat?: number;
value?: number;
payment_preimage?: string;
}
export interface PayRequestCL {
payment_hash?: string;
route_hints?: any[];
timestamp?: number;
timestamp_str?: string;
fallback_addr?: string;
cltv_expiry?: number;
description_hash?: string;
destination?: string;
expiry?: number;
description?: string;
num_satoshis?: string;
btc_num_satoshis?: string;
}
export interface ForwardingEventCL {
timestamp?: string;
timestamp_str?: string;
chan_id_out?: string;
alias_out?: string;
amt_out?: string;
amt_in?: string;
chan_id_in?: string;
alias_in?: string;
fee?: string;
}

@ -25,6 +25,16 @@ export const MENU_DATA: MenuRootNode = {
],
CLChildren: [
{id: 1, parentId: 0, name: 'Home', icon: 'home', link: '/cl/home'},
{id: 2, parentId: 0, name: 'On Chain', icon: 'account_balance_wallet', link: '/cl/onchain'},
{id: 3, parentId: 0, name: 'Peers', icon: 'group', link: '/cl/peers'},
{id: 4, parentId: 0, name: 'Channels', icon: 'settings_ethernet', link: '/cl/chnlmanage'},
{id: 5, parentId: 0, name: 'Payments', icon: 'payment', link: '/cl/paymentsend', children: [
{id: 51, parentId: 5, name: 'Send', icon: 'send', link: '/cl/paymentsend'},
{id: 52, parentId: 5, name: 'Query Routes', icon: 'explore', link: '/cl/queryroutes'}
]},
{id: 6, parentId: 0, name: 'Invoices', icon: 'receipt', link: '/cl/invoices'},
{id: 7, parentId: 0, name: 'Forwarding History', icon: 'timeline', link: '/cl/switch'},
{id: 9, parentId: 0, name: 'Lookups', icon: 'search', link: '/cl/lookups'},
{id: 10, parentId: 0, name: 'Node Config', icon: 'perm_data_setting', link: '../sconfig'},
{id: 11, parentId: 0, name: 'Help', icon: 'help', link: '../help'}
]

@ -1,7 +1,7 @@
import { MatDialogConfig } from '@angular/material';
import { Action } from '@ngrx/store';
import { GetInfoCL, FeesCL } from '../shared/models/clModels';
import { GetInfoCL, FeesCL, AddressTypeCL, PeerCL } from '../shared/models/clModels';
import { RTLConfiguration, Settings, LightningNode, GetInfoRoot, SelNodeChild } from '../shared/models/RTLconfig';
import { ErrorPayload } from '../shared/models/errorPayload';
@ -10,6 +10,7 @@ import {
PayRequest, ChannelsTransaction, PendingChannels, ClosedChannel, Transaction, SwitchReq, SwitchRes, QueryRoutes
} from '../shared/models/lndModels';
export const VOID = 'VOID';
export const RESET_ROOT_STORE = 'RESET_ROOT_STORE';
export const CLEAR_EFFECT_ERROR_ROOT = 'CLEAR_EFFECT_ERROR_ROOT';
export const EFFECT_ERROR_ROOT = 'EFFECT_ERROR_ROOT';
@ -95,17 +96,29 @@ export const SET_FORWARDING_HISTORY = 'SET_FORWARDING_HISTORY';
export const GET_QUERY_ROUTES = 'GET_QUERY_ROUTES';
export const SET_QUERY_ROUTES = 'SET_QUERY_ROUTES';
export const RESET_CL_STORE = 'RESET_CL_STORE';
export const RESET_STORE_CL = 'RESET_STORE_CL';
export const CLEAR_EFFECT_ERROR_CL = 'CLEAR_EFFECT_ERROR_CL';
export const EFFECT_ERROR_CL = 'EFFECT_ERROR_CL';
export const FETCH_CL_INFO = 'FETCH_CL_INFO';
export const SET_CL_INFO = 'SET_CL_INFO';
export const FETCH_CL_FEES = 'FETCH_CL_FEES';
export const SET_CL_FEES = 'SET_CL_FEES';
export const FETCH_CL_BALANCE = 'FETCH_CL_BALANCE';
export const SET_CL_BALANCE = 'SET_CL_BALANCE';
export const FETCH_CL_LOCAL_REMOTE_BALANCE = 'FETCH_CL_LOCAL_REMOTE_BALANCE';
export const SET_CL_LOCAL_REMOTE_BALANCE = 'SET_CL_LOCAL_REMOTE_BALANCE';
export const FETCH_INFO_CL = 'FETCH_INFO_CL';
export const SET_INFO_CL = 'SET_INFO_CL';
export const FETCH_FEES_CL = 'FETCH_FEES_CL';
export const SET_FEES_CL = 'SET_FEES_CL';
export const FETCH_BALANCE_CL = 'FETCH_BALANCE_CL';
export const SET_BALANCE_CL = 'SET_BALANCE_CL';
export const FETCH_LOCAL_REMOTE_BALANCE_CL = 'FETCH_LOCAL_REMOTE_BALANCE_CL';
export const SET_LOCAL_REMOTE_BALANCE_CL = 'SET_LOCAL_REMOTE_BALANCE_CL';
export const GET_NEW_ADDRESS_CL = 'GET_NEW_ADDRESS_CL';
export const SET_NEW_ADDRESS_CL = 'SET_NEW_ADDRESS_CL';
export const FETCH_PEERS_CL = 'FETCH_PEERS_CL';
export const SET_PEERS_CL = 'SET_PEERS_CL';
export const SAVE_NEW_PEER_CL = 'SAVE_NEW_PEER_CL';
export const ADD_PEER_CL = 'ADD_PEER_CL';
export const DETACH_PEER_CL = 'DETACH_PEER_CL';
export const REMOVE_PEER_CL = 'REMOVE_PEER_CL';
export class VoidAction implements Action {
readonly type = VOID;
}
export class ClearEffectErrorRoot implements Action {
readonly type = CLEAR_EFFECT_ERROR_ROOT;
@ -176,7 +189,7 @@ export class ResetLNDStore implements Action {
}
export class ResetCLStore implements Action {
readonly type = RESET_CL_STORE;
readonly type = RESET_STORE_CL;
constructor(public payload: SelNodeChild) {}
}
@ -196,7 +209,7 @@ export class SaveSettings implements Action {
export class SetSelelectedNode implements Action {
readonly type = SET_SELECTED_NODE;
constructor(public payload: LightningNode) {}
constructor(public payload: { lnNode: LightningNode, isInitialSetup: boolean }) {}
}
export class SetNodeData implements Action {
@ -517,45 +530,84 @@ export class InitAppData implements Action {
readonly type = INIT_APP_DATA;
}
export class FetchCLInfo implements Action {
readonly type = FETCH_CL_INFO;
export class FetchInfoCL implements Action {
readonly type = FETCH_INFO_CL;
}
export class SetCLInfo implements Action {
readonly type = SET_CL_INFO;
export class SetInfoCL implements Action {
readonly type = SET_INFO_CL;
constructor(public payload: GetInfoCL) {}
}
export class FetchCLFees implements Action {
readonly type = FETCH_CL_FEES;
export class FetchFeesCL implements Action {
readonly type = FETCH_FEES_CL;
}
export class SetCLFees implements Action {
readonly type = SET_CL_FEES;
export class SetFeesCL implements Action {
readonly type = SET_FEES_CL;
constructor(public payload: FeesCL) {}
}
export class FetchCLBalance implements Action {
readonly type = FETCH_CL_BALANCE;
export class FetchBalanceCL implements Action {
readonly type = FETCH_BALANCE_CL;
}
export class SetCLBalance implements Action {
readonly type = SET_CL_BALANCE;
export class SetBalanceCL implements Action {
readonly type = SET_BALANCE_CL;
constructor(public payload: {}) {}
}
export class FetchCLLocalRemoteBalance implements Action {
readonly type = FETCH_CL_LOCAL_REMOTE_BALANCE;
export class FetchLocalRemoteBalanceCL implements Action {
readonly type = FETCH_LOCAL_REMOTE_BALANCE_CL;
}
export class SetCLLocalRemoteBalance implements Action {
readonly type = SET_CL_LOCAL_REMOTE_BALANCE;
export class SetLocalRemoteBalanceCL implements Action {
readonly type = SET_LOCAL_REMOTE_BALANCE_CL;
constructor(public payload: {}) {}
}
export class GetNewAddressCL implements Action {
readonly type = GET_NEW_ADDRESS_CL;
constructor(public payload: AddressTypeCL) {}
}
export class SetNewAddressCL implements Action {
readonly type = SET_NEW_ADDRESS_CL;
constructor(public payload: string) {} // payload = newAddress
}
export class FetchPeersCL implements Action {
readonly type = FETCH_PEERS_CL;
}
export class SetPeersCL implements Action {
readonly type = SET_PEERS_CL;
constructor(public payload: PeerCL[]) {}
}
export class SaveNewPeerCL implements Action {
readonly type = SAVE_NEW_PEER_CL;
constructor(public payload: {id: string}) {}
}
export class AddPeerCL implements Action {
readonly type = ADD_PEER_CL;
constructor(public payload: PeerCL) {}
}
export class DetachPeerCL implements Action {
readonly type = DETACH_PEER_CL;
constructor(public payload: {id: string, force: boolean}) {}
}
export class RemovePeerCL implements Action {
readonly type = REMOVE_PEER_CL;
constructor(public payload: {id: string}) {}
}
export type RTLActions =
ClearEffectErrorRoot | EffectErrorRoot | ClearEffectErrorLnd | EffectErrorLnd | ClearEffectErrorCl | EffectErrorCl |
OpenSpinner | CloseSpinner | FetchRTLConfig | SetRTLConfig | SaveSettings |
VoidAction | OpenSpinner | CloseSpinner | FetchRTLConfig | SetRTLConfig | SaveSettings |
OpenAlert | CloseAlert | OpenConfirmation | CloseConfirmation |
ResetRootStore | ResetLNDStore | ResetCLStore |
SetSelelectedNode | SetNodeData | SetNodePendingChannelsData | FetchInfo | SetInfo |
@ -576,5 +628,7 @@ export type RTLActions =
GenSeed | GenSeedResponse | InitWallet | InitWalletResponse | UnlockWallet |
FetchConfig | ShowConfig | PeerLookup | ChannelLookup | InvoiceLookup | SetLookup |
IsAuthorized | IsAuthorizedRes | Signin | Signout | InitAppData |
FetchCLInfo | SetCLInfo | FetchCLFees | SetCLFees |
FetchCLBalance | SetCLBalance | FetchCLLocalRemoteBalance | SetCLLocalRemoteBalance;
FetchInfoCL | SetInfoCL | FetchFeesCL | SetFeesCL |
FetchBalanceCL | SetBalanceCL | FetchLocalRemoteBalanceCL | SetLocalRemoteBalanceCL |
GetNewAddressCL | SetNewAddressCL |
FetchPeersCL | SetPeersCL | AddPeerCL | DetachPeerCL | SaveNewPeerCL | RemovePeerCL;

@ -11,7 +11,6 @@ import { MatDialog } from '@angular/material';
import { environment, API_URL } from '../../environments/environment';
import { LoggerService } from '../shared/services/logger.service';
import { Settings, RTLConfiguration } from '../shared/models/RTLconfig';
import { GetInfo, Fees, Balance, NetworkInfo, Payment, GraphNode, Transaction, SwitchReq, ListInvoices } from '../shared/models/lndModels';
import { SpinnerDialogComponent } from '../shared/components/spinner-dialog/spinner-dialog.component';
import { AlertMessageComponent } from '../shared/components/alert-message/alert-message.component';
@ -95,7 +94,7 @@ export class RTLEffects implements OnDestroy {
map((rtlConfig: RTLConfiguration) => {
this.logger.info(rtlConfig);
if (+rtlConfig.sso.rtlSSO) { this.store.dispatch(new RTLActions.Signout()); }
this.store.dispatch(new RTLActions.SetSelelectedNode(rtlConfig.nodes.find(node => +node.index === rtlConfig.selectedNodeIndex)))
this.store.dispatch(new RTLActions.SetSelelectedNode({lnNode: rtlConfig.nodes.find(node => +node.index === rtlConfig.selectedNodeIndex), isInitialSetup: true}))
return {
type: RTLActions.SET_RTL_CONFIG,
payload: rtlConfig
@ -204,10 +203,8 @@ export class RTLEffects implements OnDestroy {
this.logger.info('Successfully Authorized!');
this.SetToken(postRes.token);
if(rootStore.selNode.lnImplementation.toLowerCase() === 'clightning') {
this.store.dispatch(new RTLActions.FetchCLInfo());
this.router.navigate(['/cl/home']);
} else {
this.store.dispatch(new RTLActions.FetchInfo());
this.router.navigate(['/lnd/home']);
}
}),
@ -247,34 +244,34 @@ export class RTLEffects implements OnDestroy {
ofType(RTLActions.SET_SELECTED_NODE),
mergeMap((action: RTLActions.SetSelelectedNode) => {
this.store.dispatch(new RTLActions.ClearEffectErrorRoot('UpdateSelNode'));
return this.httpClient.post(environment.CONF_API + '/updateSelNode', { selNodeIndex: action.payload.index })
return this.httpClient.post(environment.CONF_API + '/updateSelNode', { selNodeIndex: action.payload.lnNode.index })
.pipe(
map((postRes: any) => {
this.logger.info(postRes);
this.store.dispatch(new RTLActions.CloseSpinner());
if (sessionStorage.getItem('token')) {
let selNode = { channelBackupPath: action.payload.settings.channelBackupPath, satsToBTC: action.payload.settings.satsToBTC };
this.store.dispatch(new RTLActions.ResetRootStore(action.payload));
let selNode = { channelBackupPath: action.payload.lnNode.settings.channelBackupPath, satsToBTC: action.payload.lnNode.settings.satsToBTC };
this.store.dispatch(new RTLActions.ResetRootStore(action.payload.lnNode));
this.store.dispatch(new RTLActions.ResetLNDStore(selNode));
this.store.dispatch(new RTLActions.ResetCLStore(selNode));
if(action.payload.lnImplementation.toLowerCase() === 'clightning') {
if(action.payload.lnNode.lnImplementation.toLowerCase() === 'clightning') {
this.router.navigate(['/cl/home']);
this.CHILD_API_URL = API_URL + '/cl';
return {
type: RTLActions.FETCH_CL_INFO
}
return { type: RTLActions.VOID };
} else {
this.router.navigate(['/lnd/home']);
this.CHILD_API_URL = API_URL + '/lnd';
return {
type: RTLActions.FETCH_INFO
}
return { type: RTLActions.VOID };
}
} else {
return {
type: RTLActions.OPEN_ALERT,
payload: { width: '70%', data: {type: 'WARN', titleMessage: 'Authorization required to get the data from the node!' }}
};
if (!action.payload.isInitialSetup) {
return {
type: RTLActions.OPEN_ALERT,
payload: { width: '70%', data: {type: 'WARN', titleMessage: 'Authorization required to get the data from the node!' }}
};
} else {
return { type: RTLActions.VOID };
}
}
}),
catchError((err: any) => {

@ -39,12 +39,12 @@ export function RootReducer(state = initRootState, action: RTLActions.RTLActions
}
return {
...state,
effectErrors: clearedEffectErrors
effectErrorsRoot: clearedEffectErrors
};
case RTLActions.EFFECT_ERROR_ROOT:
return {
...state,
effectErrors: [...state.effectErrorsRoot, action.payload]
effectErrorsRoot: [...state.effectErrorsRoot, action.payload]
};
case RTLActions.RESET_ROOT_STORE:
return {
@ -55,7 +55,7 @@ export function RootReducer(state = initRootState, action: RTLActions.RTLActions
case RTLActions.SET_SELECTED_NODE:
return {
...state,
selNode: action.payload
selNode: action.payload.lnNode
};
case RTLActions.SET_NODE_DATA:
return {

@ -9,7 +9,7 @@
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"module": "esnext",
"target": "es2015",
"target": "es5",
"typeRoots": [
"node_modules/@types"
],

Loading…
Cancel
Save