lnd multi part payment #445 (#446)

lnd multi part payment #445
pull/448/head
ShahanaFarooqui 4 years ago committed by GitHub
parent 4da3b66bc7
commit 25bdec8a70
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -15,5 +15,5 @@
<link rel="stylesheet" href="styles.1268069d095d5cf8e257.css"></head> <link rel="stylesheet" href="styles.1268069d095d5cf8e257.css"></head>
<body> <body>
<rtl-app></rtl-app> <rtl-app></rtl-app>
<script src="runtime.1cfb7a3d5c7630696d04.js" defer></script><script src="polyfills-es5.2ac0d98b22574ae745b1.js" nomodule defer></script><script src="polyfills.5ae721a6ae5ab597a53d.js" defer></script><script src="main.bac9de7c65f61812af23.js" defer></script></body> <script src="runtime.9e7465a615254b205f19.js" defer></script><script src="polyfills-es5.2ac0d98b22574ae745b1.js" nomodule defer></script><script src="polyfills.5ae721a6ae5ab597a53d.js" defer></script><script src="main.3d79a2643378c8aeda8b.js" defer></script></body>
</html> </html>

File diff suppressed because one or more lines are too long

@ -1 +1 @@
!function(e){function r(r){for(var n,a,i=r[0],c=r[1],f=r[2],p=0,d=[];p<i.length;p++)a=i[p],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&d.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);d.length;)d.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++)0!==o[t[i]]&&(n=!1);n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={0:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,i=document.createElement("script");i.charset="utf-8",i.timeout=120,a.nc&&i.setAttribute("nonce",a.nc),i.src=function(e){return a.p+""+({}[e]||e)+"."+{1:"9bb271dd8dffd2d994a5",6:"02d8d1e879c0be336222",7:"4a00e92294df28ac9ca1",8:"cbebc1d46c4bfbda8693"}[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()}([]); !function(e){function r(r){for(var n,a,i=r[0],c=r[1],f=r[2],p=0,s=[];p<i.length;p++)a=i[p],Object.prototype.hasOwnProperty.call(o,a)&&o[a]&&s.push(o[a][0]),o[a]=0;for(n in c)Object.prototype.hasOwnProperty.call(c,n)&&(e[n]=c[n]);for(l&&l(r);s.length;)s.shift()();return u.push.apply(u,f||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,i=1;i<t.length;i++)0!==o[t[i]]&&(n=!1);n&&(u.splice(r--,1),e=a(a.s=t[0]))}return e}var n={},o={0:0},u=[];function a(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,a),t.l=!0,t.exports}a.e=function(e){var r=[],t=o[e];if(0!==t)if(t)r.push(t[2]);else{var n=new Promise((function(r,n){t=o[e]=[r,n]}));r.push(t[2]=n);var u,i=document.createElement("script");i.charset="utf-8",i.timeout=120,a.nc&&i.setAttribute("nonce",a.nc),i.src=function(e){return a.p+""+({}[e]||e)+"."+{1:"9bb271dd8dffd2d994a5",6:"02d8d1e879c0be336222",7:"4a00e92294df28ac9ca1",8:"57420fc1ef9c4c3b9030"}[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()}([]);

@ -3,16 +3,15 @@ var common = require('../../common');
var logger = require('../logger'); var logger = require('../logger');
var options = {}; var options = {};
getAliasFromPubkey = (hop) => { getAliasFromPubkey = (pubkey) => {
return new Promise(function(resolve, reject) { return new Promise(function(resolve, reject) {
options.url = common.getSelLNServerUrl() + '/v1/graph/node/' + hop.pub_key; options.url = common.getSelLNServerUrl() + '/v1/graph/node/' + pubkey;
request(options) request(options)
.then(function(aliasBody) { .then(function(res) {
logger.info({fileName: 'Graph', msg: 'Alias: ' + JSON.stringify(aliasBody.node.alias)}); logger.info({fileName: 'Graph', msg: 'Alias: ' + JSON.stringify(res.node.alias)});
hop.pubkey_alias = aliasBody.node.alias; resolve(res.node.alias);
resolve(hop);
}) })
.catch(err => resolve('')); .catch(err => resolve(pubkey.substring(0, 17) + '...'));
}); });
} }
@ -169,35 +168,33 @@ exports.getQueryRoutes = (req, res, next) => {
error: (!body) ? 'Error From Server!' : body.error error: (!body) ? 'Error From Server!' : body.error
}); });
} }
if (body.routes && body.routes.length > 0) { if(body.routes && body.routes.length && body.routes.length > 0 && body.routes[0].hops && body.routes[0].hops.length && body.routes[0].hops.length > 0) {
body.routes.forEach(route => { Promise.all(body.routes[0].hops.map(hop => {return getAliasFromPubkey(hop.pub_key)}))
if ( route.hops) { .then(function(values) {
Promise.all( body.routes[0].hops.map((hop, i) => {
route.hops.map((hop, i) => { hop.hop_sequence = i + 1;
hop.hop_sequence = i + 1; hop.pubkey_alias = values[i];
return getAliasFromPubkey(hop); return hop;
}) });
) logger.info({fileName: 'Graph', msg: 'Hops with Alias: ' + JSON.stringify(body)});
.then(function(values) { res.status(200).json(body);
logger.info({fileName: 'Graph', msg: 'Hops with Alias: ' + JSON.stringify(body)}); })
res.status(200).json(body); .catch(errRes => {
}) let err = JSON.parse(JSON.stringify(errRes));
.catch(errRes => { if (err.options && err.options.headers && err.options.headers['Grpc-Metadata-macaroon']) {
let err = JSON.parse(JSON.stringify(errRes)); delete err.options.headers['Grpc-Metadata-macaroon'];
if (err.options && err.options.headers && err.options.headers['Grpc-Metadata-macaroon']) {
delete err.options.headers['Grpc-Metadata-macaroon'];
}
if (err.response && err.response.request && err.response.request.headers && err.response.request.headers['Grpc-Metadata-macaroon']) {
delete err.response.request.headers['Grpc-Metadata-macaroon'];
}
logger.error({fileName: 'Graph', lineNum: 187, msg: 'Fetch Query Routes Error: ' + JSON.stringify(err)});
return res.status(500).json({
message: "Fetching Query Routes Failed!",
error: err.error
});
});
} }
}); if (err.response && err.response.request && err.response.request.headers && err.response.request.headers['Grpc-Metadata-macaroon']) {
delete err.response.request.headers['Grpc-Metadata-macaroon'];
}
logger.error({fileName: 'Graph', lineNum: 196, msg: 'Fetch Query Routes Error: ' + JSON.stringify(err)});
return res.status(500).json({
message: "Fetching Query Routes Failed!",
error: err.error
});
});
} else {
res.status(200).json(body);
} }
}) })
.catch(errRes => { .catch(errRes => {
@ -208,7 +205,7 @@ exports.getQueryRoutes = (req, res, next) => {
if (err.response && err.response.request && err.response.request.headers && err.response.request.headers['Grpc-Metadata-macaroon']) { if (err.response && err.response.request && err.response.request.headers && err.response.request.headers['Grpc-Metadata-macaroon']) {
delete err.response.request.headers['Grpc-Metadata-macaroon']; delete err.response.request.headers['Grpc-Metadata-macaroon'];
} }
logger.error({fileName: 'Graph', lineNum: 204, msg: 'Fetch Query Routes Error: ' + JSON.stringify(err)}); logger.error({fileName: 'Graph', lineNum: 214, msg: 'Fetch Query Routes Error: ' + JSON.stringify(err)});
return res.status(500).json({ return res.status(500).json({
message: "Fetching Query Routes Failed!", message: "Fetching Query Routes Failed!",
error: err.error error: err.error
@ -262,3 +259,30 @@ exports.getRemoteFeePolicy = (req, res, next) => {
}); });
}); });
}; };
exports.getAliasesForPubkeys = (req, res, next) => {
options = common.getOptions();
if (req.params.pubKeys.length && req.params.pubKeys.length > 0) {
Promise.all(req.params.pubKeys.map(pubkey => {return getAliasFromPubkey(pubkey)}))
.then(function(values) {
logger.info({fileName: 'Graph', msg: 'Node Alias: ' + JSON.stringify(values)});
res.status(200).json(values);
})
.catch(errRes => {
let err = JSON.parse(JSON.stringify(errRes));
if (err.options && err.options.headers && err.options.headers['Grpc-Metadata-macaroon']) {
delete err.options.headers['Grpc-Metadata-macaroon'];
}
if (err.response && err.response.request && err.response.request.headers && err.response.request.headers['Grpc-Metadata-macaroon']) {
delete err.response.request.headers['Grpc-Metadata-macaroon'];
}
logger.error({fileName: 'Graph', lineNum: 279, msg: 'Get Aliases for Pubkeys Failed: ' + JSON.stringify(err)});
return res.status(500).json({
message: "Getting Aliases for Pubkeys Failed!",
error: err.error
});
});
} else {
res.status(200).json([]);
}
};

@ -9,7 +9,7 @@ exports.getPayments = (req, res, next) => {
request(options).then((body) => { request(options).then((body) => {
const body_str = (!body) ? '' : JSON.stringify(body); const body_str = (!body) ? '' : JSON.stringify(body);
const search_idx = (!body) ? -1 : body_str.search('Not Found'); const search_idx = (!body) ? -1 : body_str.search('Not Found');
logger.info({fileName: 'Payments', msg: 'Payment Decoded Received: ' + body_str}); logger.info({fileName: 'Payments', msg: 'Payment List Received: ' + body_str});
if(!body || search_idx > -1 || body.error) { if(!body || search_idx > -1 || body.error) {
logger.error({fileName: 'Payments', lineNum: 14, msg: 'List Payments Error: ' + ((!body || !body.error) ? 'Error From Server!' : JSON.stringify(body.error))}); logger.error({fileName: 'Payments', lineNum: 14, msg: 'List Payments Error: ' + ((!body || !body.error) ? 'Error From Server!' : JSON.stringify(body.error))});
res.status(500).json({ res.status(500).json({
@ -20,9 +20,14 @@ exports.getPayments = (req, res, next) => {
if ( body.payments && body.payments.length > 0) { if ( body.payments && body.payments.length > 0) {
body.payments.forEach(payment => { body.payments.forEach(payment => {
payment.creation_date_str = (!payment.creation_date) ? '' : common.convertTimestampToDate(payment.creation_date); payment.creation_date_str = (!payment.creation_date) ? '' : common.convertTimestampToDate(payment.creation_date);
payment.htlcs.forEach(htlc => {
htlc.attempt_time_str = (!htlc.attempt_time_ns) ? '' : common.convertTimestampToDate(Math.round(htlc.attempt_time_ns/1000000000));
htlc.resolve_time_str = (!htlc.resolve_time_ns) ? '' : common.convertTimestampToDate(Math.round(htlc.resolve_time_ns/1000000000));
});
}); });
body.payments = common.sortDescByKey(body.payments, 'creation_date'); body.payments = common.sortDescByKey(body.payments, 'creation_date');
} }
logger.info({fileName: 'Payments', msg: 'Payment List After Dates: ' + JSON.stringify(body.payments)});
res.status(200).json(body.payments); res.status(200).json(body.payments);
} }
}) })

@ -5,6 +5,7 @@ const authCheck = require("../authCheck");
router.get("/", authCheck, graphController.getDescribeGraph); router.get("/", authCheck, graphController.getDescribeGraph);
router.get("/info", authCheck, graphController.getGraphInfo); router.get("/info", authCheck, graphController.getGraphInfo);
router.get("/nodes/:pubKeys", authCheck, graphController.getAliasesForPubkeys);
router.get("/node/:pubKey", authCheck, graphController.getGraphNode); router.get("/node/:pubKey", authCheck, graphController.getGraphNode);
router.get("/edge/:chanid", authCheck, graphController.getGraphEdge); router.get("/edge/:chanid", authCheck, graphController.getGraphEdge);
router.get("/edge/:chanid/:localPubkey", authCheck, graphController.getRemoteFeePolicy); router.get("/edge/:chanid/:localPubkey", authCheck, graphController.getRemoteFeePolicy);

@ -28,26 +28,30 @@
<table mat-table #table fxFlex="100" [dataSource]="payments" matSort [ngClass]="{'overflow-auto error-border': flgLoading[0]==='error','overflow-auto': true}"> <table mat-table #table fxFlex="100" [dataSource]="payments" matSort [ngClass]="{'overflow-auto error-border': flgLoading[0]==='error','overflow-auto': true}">
<ng-container matColumnDef="creation_date"> <ng-container matColumnDef="creation_date">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Creation Date</th> <th mat-header-cell *matHeaderCellDef mat-sort-header>Creation Date</th>
<td mat-cell *matCellDef="let payment">{{payment?.creation_date_str}}</td> <td mat-cell *matCellDef="let payment">
<span *ngIf="payment.status === 'SUCCEEDED'" class="dot green" matTooltip="Succeeded" matTooltipPosition="right" [ngClass]="{'mr-0': screenSize === screenSizeEnum.XS}"></span>
<span *ngIf="payment.status !== 'SUCCEEDED'" class="dot red" matTooltip="Failed" matTooltipPosition="right" [ngClass]="{'mr-0': screenSize === screenSizeEnum.XS}"></span>
{{payment?.creation_date_str}}
</td>
</ng-container> </ng-container>
<ng-container matColumnDef="payment_hash"> <ng-container matColumnDef="payment_hash">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Payment Hash</th> <th mat-header-cell *matHeaderCellDef mat-sort-header>Payment Hash</th>
<td mat-cell *matCellDef="let payment" [ngStyle]="{'max-width': (screenSize === screenSizeEnum.XS) ? '10rem' : '35rem'}">{{payment?.payment_hash}}</td> <td mat-cell *matCellDef="let payment" [ngStyle]="{'max-width': (screenSize === screenSizeEnum.XS) ? '10rem' : '35rem'}">{{payment?.payment_hash}}</td>
</ng-container> </ng-container>
<ng-container matColumnDef="fee"> <ng-container matColumnDef="fee">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Fee</th> <th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Fee (Sats)</th>
<td mat-cell *matCellDef="let payment"><span fxLayoutAlign="end center">{{payment?.fee | number}}</span></td> <td mat-cell *matCellDef="let payment"><span fxLayoutAlign="end center">{{payment?.fee | number}}</span></td>
</ng-container> </ng-container>
<ng-container matColumnDef="value"> <ng-container matColumnDef="value">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Value (Sats)</th> <th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Value (Sats)</th>
<td mat-cell *matCellDef="let payment"><span fxLayoutAlign="end center">{{payment?.value | number}}</span></td> <td mat-cell *matCellDef="let payment"><span fxLayoutAlign="end center">{{payment?.value | number}}</span></td>
</ng-container> </ng-container>
<ng-container matColumnDef="path"> <ng-container matColumnDef="hops">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Path (Hops)</th> <th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">#Hops</th>
<td mat-cell *matCellDef="let payment"><span fxLayoutAlign="end center">{{payment?.path?.length || 0}}</span></td> <td mat-cell *matCellDef="let payment"><span fxLayoutAlign="end center">{{payment?.htlcs[0]?.route?.hops?.length || 0}}</span></td>
</ng-container> </ng-container>
<ng-container matColumnDef="actions"> <ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef class="pl-4 pr-3"> <th mat-header-cell *matHeaderCellDef class="px-3">
<div class="bordered-box table-actions-select"> <div class="bordered-box table-actions-select">
<mat-select placeholder="Actions" tabindex="1" class="mr-0"> <mat-select placeholder="Actions" tabindex="1" class="mr-0">
<mat-select-trigger></mat-select-trigger> <mat-select-trigger></mat-select-trigger>
@ -55,15 +59,88 @@
</mat-select> </mat-select>
</div> </div>
</th> </th>
<td mat-cell *matCellDef="let payment" class="pl-4" fxLayoutAlign="end center"> <td mat-cell *matCellDef="let payment" class="px-3" fxLayoutAlign="end center">
<button mat-stroked-button color="primary" type="button" tabindex="4" (click)="onPaymentClick(payment,$event)">View Info</button> <button mat-stroked-button color="primary" type="button" tabindex="4" (click)="onPaymentClick(payment)">View Info</button>
</td> </td>
</ng-container> </ng-container>
<ng-container matColumnDef="no_payment"> <ng-container matColumnDef="no_payment">
<td mat-footer-cell *matFooterCellDef colspan="4"> <td mat-footer-cell *matFooterCellDef colspan="4">
<p *ngIf="!payments.data || payments.data.length<1">No payments available.</p> <p *ngIf="!payments?.data || payments?.data?.length<1">No payments available.</p>
</td>
</ng-container>
<!-- Payment Group Row Start -->
<ng-container matColumnDef="groupTotal">
<td mat-cell *matCellDef="let payment">
<span fxLayoutAlign="start center" class="htlc-row-span">
<span *ngIf="payment.status === 'SUCCEEDED'" class="dot green" matTooltip="Succeeded" matTooltipPosition="right" [ngClass]="{'mr-0': screenSize === screenSizeEnum.XS}"></span>
<span *ngIf="payment.status !== 'SUCCEEDED'" class="dot red" matTooltip="Failed" matTooltipPosition="right" [ngClass]="{'mr-0': screenSize === screenSizeEnum.XS}"></span>
Total Attempts: {{payment?.htlcs?.length}}
</span>
<ng-container *ngIf="payment.is_expanded">
<span *ngFor="let htlc of payment?.htlcs" fxLayoutAlign="start center" class="htlc-row-span">
<span *ngIf="htlc.status === 'SUCCEEDED'" class="dot green" matTooltip="Succeeded" matTooltipPosition="right" [ngClass]="{'mr-0': screenSize === screenSizeEnum.XS}"></span>
<span *ngIf="htlc.status !== 'SUCCEEDED'" class="dot red" matTooltip="Failed" matTooltipPosition="right" [ngClass]="{'mr-0': screenSize === screenSizeEnum.XS}"></span>
{{htlc.attempt_time_str}}
</span>
</ng-container>
</td>
</ng-container>
<ng-container matColumnDef="groupHash">
<td mat-cell *matCellDef="let payment">
<span fxLayoutAlign="start center" class="htlc-row-span">{{payment?.payment_hash}}</span>
<span *ngIf="payment.is_expanded">
<span *ngFor="let htlc of payment?.htlcs; index as i" fxLayoutAlign="start center" class="htlc-row-span">
HTLC {{i + 1}}
</span>
</span>
</td>
</ng-container>
<ng-container matColumnDef="groupFee">
<td mat-cell *matCellDef="let payment">
<span fxLayoutAlign="end center" class="htlc-row-span">{{payment?.fee | number:'1.0-0'}}</span>
<span *ngIf="payment.is_expanded">
<span *ngFor="let htlc of payment?.htlcs" fxLayoutAlign="end center" class="htlc-row-span">
{{htlc.route?.total_fees | number:'1.0-0'}}
</span>
</span>
</td>
</ng-container>
<ng-container matColumnDef="groupValue">
<td mat-cell *matCellDef="let payment">
<span fxLayoutAlign="end center" class="htlc-row-span">{{payment?.value | number:'1.0-0'}}</span>
<span *ngIf="payment.is_expanded">
<span *ngFor="let htlc of payment?.htlcs" fxLayoutAlign="end center" class="htlc-row-span">
{{htlc.route?.total_amt | number:'1.0-0'}}
</span>
</span>
</td>
</ng-container>
<ng-container matColumnDef="groupHops">
<td mat-cell *matCellDef="let payment">
<span fxLayoutAlign="end center" class="htlc-row-span">-</span>
<span *ngIf="payment.is_expanded">
<span *ngFor="let htlc of payment?.htlcs" fxLayoutAlign="end center" class="htlc-row-span">
{{(htlc.route?.hops?.length || 0) | number:'1.0-0'}}
</span>
</span>
</td>
</ng-container>
<ng-container matColumnDef="groupAction">
<td mat-cell *matCellDef="let payment" class="px-3">
<span fxLayoutAlign="end center">
<button mat-flat-button class="btn-htlc-expand" color="primary" type="button" tabindex="5" (click)="payment.is_expanded = !payment.is_expanded">{{payment.is_expanded ? 'Hide' : 'Show'}}</button>
</span>
<div *ngIf="payment.is_expanded">
<div *ngFor="let htlc of payment?.htlcs; index as i" fxLayoutAlign="end center">
<button mat-stroked-button class="btn-htlc-info" color="primary" type="button" tabindex="6" (click)="onHTLCClick(htlc, payment)">View {{i + 1}}</button>
</div>
</div>
</td> </td>
</ng-container> </ng-container>
<tr mat-row *matRowDef="let row; columns: htlcColumns; when: is_group;" [@newlyAddedRowAnimation]="(row.payment_hash === newlyAddedPayment && flgAnimate) ? 'added' : 'notAdded'"></tr>
<!-- Payment Group Row End -->
<tr mat-footer-row *matFooterRowDef="['no_payment']" [ngClass]="{'display-none': payments.data && payments.data.length>0}"></tr> <tr mat-footer-row *matFooterRowDef="['no_payment']" [ngClass]="{'display-none': payments.data && payments.data.length>0}"></tr>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: flgSticky;"></tr> <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'"></tr> <tr mat-row *matRowDef="let row; columns: displayedColumns;" [@newlyAddedRowAnimation]="(row.payment_hash === newlyAddedPayment && flgAnimate) ? 'added' : 'notAdded'"></tr>

@ -7,4 +7,21 @@
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.mat-column-groupAction {
min-height: 4.8rem;
& .btn-htlc-expand {
width: 9rem;
}
& .btn-htlc-info {
margin-top: 0.5rem;
width: 9rem;
}
}
.htlc-row-span {
min-height: 4.2rem;
}

@ -1,7 +1,7 @@
import { Component, OnInit, OnDestroy, ViewChild, Input } from '@angular/core'; import { Component, OnInit, OnDestroy, ViewChild, Input } from '@angular/core';
import { DecimalPipe } from '@angular/common'; import { DecimalPipe } from '@angular/common';
import { Subject, forkJoin } from 'rxjs'; import { Subject, forkJoin } from 'rxjs';
import { takeUntil, take, filter } from 'rxjs/operators'; import { takeUntil, take } from 'rxjs/operators';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { Actions } from '@ngrx/effects'; import { Actions } from '@ngrx/effects';
import { faHistory } from '@fortawesome/free-solid-svg-icons'; import { faHistory } from '@fortawesome/free-solid-svg-icons';
@ -9,7 +9,7 @@ import { faHistory } from '@fortawesome/free-solid-svg-icons';
import { MatPaginator, MatPaginatorIntl } from '@angular/material/paginator'; import { MatPaginator, MatPaginatorIntl } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort'; import { MatSort } from '@angular/material/sort';
import { MatTableDataSource } from '@angular/material/table'; import { MatTableDataSource } from '@angular/material/table';
import { GetInfo, Payment, PayRequest, Channel } from '../../../shared/models/lndModels'; import { GetInfo, Payment, PayRequest, Channel, HTLC, Peer, Hop } from '../../../shared/models/lndModels';
import { PAGE_SIZE, PAGE_SIZE_OPTIONS, getPaginatorLabel, AlertTypeEnum, DataTypeEnum, ScreenSizeEnum, CurrencyUnitEnum, CURRENCY_UNIT_FORMATS, FEE_LIMIT_TYPES } from '../../../shared/services/consts-enums-functions'; import { PAGE_SIZE, PAGE_SIZE_OPTIONS, getPaginatorLabel, AlertTypeEnum, DataTypeEnum, ScreenSizeEnum, CurrencyUnitEnum, CURRENCY_UNIT_FORMATS, FEE_LIMIT_TYPES } from '../../../shared/services/consts-enums-functions';
import { LoggerService } from '../../../shared/services/logger.service'; import { LoggerService } from '../../../shared/services/logger.service';
import { CommonService } from '../../../shared/services/common.service'; import { CommonService } from '../../../shared/services/common.service';
@ -44,9 +44,11 @@ export class LightningPaymentsComponent implements OnInit, OnDestroy {
public selNode: SelNodeChild = {}; public selNode: SelNodeChild = {};
public flgLoading: Array<Boolean | 'error'> = [true]; public flgLoading: Array<Boolean | 'error'> = [true];
public information: GetInfo = {}; public information: GetInfo = {};
public peers: Peer[] = [];
public payments: any; public payments: any;
public paymentJSONArr: Payment[] = []; public paymentJSONArr: Payment[] = [];
public displayedColumns = []; public displayedColumns = [];
public htlcColumns = [];
public paymentDecoded: PayRequest = {}; public paymentDecoded: PayRequest = {};
public paymentRequest = ''; public paymentRequest = '';
public paymentDecodedHint = ''; public paymentDecodedHint = '';
@ -68,15 +70,19 @@ export class LightningPaymentsComponent implements OnInit, OnDestroy {
if(this.screenSize === ScreenSizeEnum.XS) { if(this.screenSize === ScreenSizeEnum.XS) {
this.flgSticky = false; this.flgSticky = false;
this.displayedColumns = ['creation_date', 'actions']; this.displayedColumns = ['creation_date', 'actions'];
this.htlcColumns = ['groupTotal', 'groupAction'];
} else if(this.screenSize === ScreenSizeEnum.SM) { } else if(this.screenSize === ScreenSizeEnum.SM) {
this.flgSticky = false; this.flgSticky = false;
this.displayedColumns = ['creation_date', 'value', 'actions']; this.displayedColumns = ['creation_date', 'value', 'actions'];
this.htlcColumns = ['groupTotal', 'groupValue', 'groupAction'];
} else if(this.screenSize === ScreenSizeEnum.MD) { } else if(this.screenSize === ScreenSizeEnum.MD) {
this.flgSticky = false; this.flgSticky = false;
this.displayedColumns = ['creation_date', 'fee', 'value', 'actions']; this.displayedColumns = ['creation_date', 'fee', 'value', 'actions'];
this.htlcColumns = ['groupTotal', 'groupFee', 'groupValue', 'groupAction'];
} else { } else {
this.flgSticky = true; this.flgSticky = true;
this.displayedColumns = ['creation_date', 'payment_hash', 'fee', 'value', 'path', 'actions']; this.displayedColumns = ['creation_date', 'payment_hash', 'fee', 'value', 'hops', 'actions'];
this.htlcColumns = ['groupTotal', 'groupHash', 'groupFee', 'groupValue', 'groupHops', 'groupAction'];
} }
} }
@ -91,6 +97,7 @@ export class LightningPaymentsComponent implements OnInit, OnDestroy {
}); });
this.information = rtlStore.information; this.information = rtlStore.information;
this.selNode = rtlStore.nodeSettings; this.selNode = rtlStore.nodeSettings;
this.peers = rtlStore.peers;
this.activeChannels = rtlStore.allChannels.filter(channel => channel.active); this.activeChannels = rtlStore.allChannels.filter(channel => channel.active);
this.paymentJSONArr = (rtlStore.payments && rtlStore.payments.length > 0) ? rtlStore.payments : []; this.paymentJSONArr = (rtlStore.payments && rtlStore.payments.length > 0) ? rtlStore.payments : [];
this.payments = (rtlStore.payments) ? new MatTableDataSource([]) : new MatTableDataSource<Payment>([...this.paymentJSONArr]); this.payments = (rtlStore.payments) ? new MatTableDataSource([]) : new MatTableDataSource<Payment>([...this.paymentJSONArr]);
@ -240,6 +247,10 @@ export class LightningPaymentsComponent implements OnInit, OnDestroy {
this.feeLimit = null; this.feeLimit = null;
this.selFeeLimitType = FEE_LIMIT_TYPES[0]; this.selFeeLimitType = FEE_LIMIT_TYPES[0];
} }
}
is_group(index: number, payment: Payment) {
return payment.htlcs && payment.htlcs.length > 1;
} }
resetData() { resetData() {
@ -251,31 +262,53 @@ export class LightningPaymentsComponent implements OnInit, OnDestroy {
this.form.resetForm(); this.form.resetForm();
} }
onPaymentClick(selPayment: Payment, event: any) { getHopDetails(hops: Hop[]) {
let pathAliases = ''; let self = this;
if (selPayment.path && selPayment.path.length > 0) { return hops.reduce(function (accumulator, currentHop) {
forkJoin(this.dataService.getAliasesFromPubkeys(selPayment.path)) let peerFound = self.peers.find(peer => peer.pub_key === currentHop.pub_key);
.pipe(takeUntil(this.unSubs[3])) if (peerFound && peerFound.alias) {
.subscribe((nodes: any) => { accumulator.push('<pre>Channel: ' + peerFound.alias.padEnd(20) + '&Tab;&Tab;&Tab;Amount (Sats): ' + self.decimalPipe.transform(currentHop.amt_to_forward) + '</pre>');
nodes.forEach(node => { } else {
pathAliases = pathAliases === '' ? node.node.alias : pathAliases + '\n' + node.node.alias; self.dataService.getAliasFromPubkey(currentHop.pub_key)
.pipe(takeUntil(self.unSubs[1]))
.subscribe((res: any) => {
accumulator.push('<pre>Channel: ' + (res.node && res.node.alias ? res.node.alias.padEnd(20) : (currentHop.pub_key.substring(0, 17) + '...')) + '&Tab;&Tab;&Tab;Amount (Sats): ' + self.decimalPipe.transform(currentHop.amt_to_forward) + '</pre>');
}); });
this.openPaymentInModal(selPayment, pathAliases); }
}); return accumulator;
} else { }, []);
this.openPaymentInModal(selPayment, pathAliases);
}
} }
openPaymentInModal(selPayment: Payment, pathAliases: string) { onHTLCClick(selHtlc: HTLC, selPayment: Payment) {
const reorderedHTLC = [
[{key: 'payment_hash', value: selPayment.payment_hash, title: 'Payment Hash', width: 100, type: DataTypeEnum.STRING}],
[{key: 'payment_request', value: selPayment.payment_request, title: 'Payment Request', width: 100, type: DataTypeEnum.STRING}],
[{key: 'preimage', value: selHtlc.preimage, title: 'Preimage', width: 100, type: DataTypeEnum.STRING}],
[{key: 'status', value: selHtlc.status, title: 'Status', width: 33, type: DataTypeEnum.STRING},
{key: 'attempt_time_str', value: selHtlc.attempt_time_str, title: 'Attempt Time', width: 33, type: DataTypeEnum.DATE_TIME},
{key: 'resolve_time_str', value: selHtlc.resolve_time_str, title: 'Resolve Time', width: 34, type: DataTypeEnum.DATE_TIME}],
[{key: 'total_amt', value: selHtlc.route.total_amt, title: 'Amount (Sats)', width: 33, type: DataTypeEnum.NUMBER},
{key: 'total_fees', value: selHtlc.route.total_fees, title: 'Fee (Sats)', width: 33, type: DataTypeEnum.NUMBER},
{key: 'total_time_lock', value: selHtlc.route.total_time_lock, title: 'Total Time Lock', width: 34, type: DataTypeEnum.NUMBER}],
[{key: 'hops', value: this.getHopDetails(selHtlc.route.hops), title: 'Hops', width: 100, type: DataTypeEnum.ARRAY}]
];
this.store.dispatch(new RTLActions.OpenAlert({ data: {
type: AlertTypeEnum.INFORMATION,
alertTitle: 'HTLC Information',
message: reorderedHTLC,
scrollable: selHtlc.route && selHtlc.route.hops && selHtlc.route.hops.length > 1
}}));
}
onPaymentClick(selPayment: Payment) {
const reorderedPayment = [ const reorderedPayment = [
[{key: 'payment_hash', value: selPayment.payment_hash, title: 'Payment Hash', width: 100, type: DataTypeEnum.STRING}], [{key: 'payment_hash', value: selPayment.payment_hash, title: 'Payment Hash', width: 100, type: DataTypeEnum.STRING}],
[{key: 'payment_preimage', value: selPayment.payment_preimage, title: 'Payment Preimage', width: 100, type: DataTypeEnum.STRING}], [{key: 'payment_preimage', value: selPayment.payment_preimage, title: 'Payment Preimage', width: 100, type: DataTypeEnum.STRING}],
[{key: 'path', value: pathAliases, title: 'Path', width: 100, type: DataTypeEnum.STRING}], [{key: 'payment_request', value: selPayment.payment_request, title: 'Payment Request', width: 100, type: DataTypeEnum.STRING}],
[{key: 'creation_date_str', value: selPayment.creation_date_str, title: 'Creation Date', width: 50, type: DataTypeEnum.DATE_TIME}, [{key: 'status', value: selPayment.status, title: 'Status', width: 50, type: DataTypeEnum.STRING},
{key: 'fee', value: selPayment.fee, title: 'Fee', width: 50, type: DataTypeEnum.NUMBER}], {key: 'creation_date_str', value: selPayment.creation_date_str, title: 'Creation Date', width: 50, type: DataTypeEnum.DATE_TIME}],
[{key: 'value_msat', value: selPayment.value_msat, title: 'Value (mSats)', width: 50, type: DataTypeEnum.NUMBER}, [{key: 'value_msat', value: selPayment.value_msat, title: 'Value (mSats)', width: 50, type: DataTypeEnum.NUMBER},
{key: 'value_sat', value: selPayment.value, title: 'Value (Sats)', width: 50, type: DataTypeEnum.NUMBER}] {key: 'fee_msat', value: selPayment.fee_msat, title: 'Fee (mSats)', width: 50, type: DataTypeEnum.NUMBER}]
]; ];
this.store.dispatch(new RTLActions.OpenAlert({ data: { this.store.dispatch(new RTLActions.OpenAlert({ data: {
type: AlertTypeEnum.INFORMATION, type: AlertTypeEnum.INFORMATION,
@ -290,7 +323,15 @@ export class LightningPaymentsComponent implements OnInit, OnDestroy {
onDownloadCSV() { onDownloadCSV() {
if(this.payments.data && this.payments.data.length > 0) { if(this.payments.data && this.payments.data.length > 0) {
this.commonService.downloadFile(this.payments.data, 'Payments'); let paymentsDataCopy = JSON.parse(JSON.stringify(this.payments.data));
let flattenedPayments = paymentsDataCopy.reduce((acc, curr) => {
if (curr.htlcs) {
return acc.concat(curr.htlcs);
} else {
return acc.concat(curr);
}
}, []);
this.commonService.downloadFile(flattenedPayments, 'Payments');
} }
} }

@ -49,9 +49,7 @@
<span *ngIf="obj && (!!obj.value || obj.value === 0); else emptyField"> <span *ngIf="obj && (!!obj.value || obj.value === 0); else emptyField">
<span [ngSwitch]="obj.type" class="foreground-secondary-text" fxLayout="column" fxFlex="100" fxLayoutAlign="start stretch"> <span [ngSwitch]="obj.type" class="foreground-secondary-text" fxLayout="column" fxFlex="100" fxLayoutAlign="start stretch">
<ng-container *ngSwitchCase="dataTypeEnum.ARRAY"> <ng-container *ngSwitchCase="dataTypeEnum.ARRAY">
<span *ngFor="let arrayObj of obj.value" class="display-block w-100"> <span *ngFor="let arrayObj of obj.value" class="display-block w-100" [innerHTML]="arrayObj"></span>
{{arrayObj | json}}
</span>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="dataTypeEnum.NUMBER">{{obj.value | number:'1.0-3'}}</ng-container> <ng-container *ngSwitchCase="dataTypeEnum.NUMBER">{{obj.value | number:'1.0-3'}}</ng-container>
<ng-container *ngSwitchCase="dataTypeEnum.BOOLEAN">{{obj.value ? 'True' : 'False'}}</ng-container> <ng-container *ngSwitchCase="dataTypeEnum.BOOLEAN">{{obj.value ? 'True' : 'False'}}</ng-container>

@ -13,7 +13,10 @@ import { AlertTypeEnum, DataTypeEnum, ScreenSizeEnum, SwapStateEnum } from '../.
styleUrls: ['./alert-message.component.scss'] styleUrls: ['./alert-message.component.scss']
}) })
export class AlertMessageComponent implements OnInit, AfterViewChecked { export class AlertMessageComponent implements OnInit, AfterViewChecked {
@ViewChild('scrollContainer', { static: true }) scrollContainer: ElementRef; private scrollContainer: ElementRef;
@ViewChild('scrollContainer') set container(containerContent: ElementRef) {
if(containerContent) { this.scrollContainer = containerContent; }
}
public swapStateEnum = SwapStateEnum; public swapStateEnum = SwapStateEnum;
public showQRField = ''; public showQRField = '';
public showQRName = ''; public showQRName = '';
@ -45,7 +48,9 @@ export class AlertMessageComponent implements OnInit, AfterViewChecked {
} }
ngAfterViewChecked() { ngAfterViewChecked() {
this.shouldScroll = this.scrollContainer && this.scrollContainer.nativeElement ? this.scrollContainer.nativeElement.classList.value.includes('ps--active-y') : false; setTimeout(() => {
this.shouldScroll = this.scrollContainer && this.scrollContainer.nativeElement ? this.scrollContainer.nativeElement.classList.value.includes('ps--active-y') : false;
});
} }
onScrollDown() { onScrollDown() {

@ -246,10 +246,14 @@ export interface HopHint {
} }
export interface HTLC { export interface HTLC {
incoming?: boolean; status?: string;
amount?: number; route?: Route;
hash_lock?: string; attempt_time_ns?: string;
expiration_height?: number; resolve_time_ns?: string;
failure?: any;
preimage?: string;
attempt_time_str?: string;
resolve_time_str?: string;
} }
export interface Invoice { export interface Invoice {
@ -320,12 +324,21 @@ export interface Payment {
creation_date?: number; creation_date?: number;
creation_date_str?: string; creation_date_str?: string;
payment_hash?: string; payment_hash?: string;
payment_request?: string;
status?: string;
path?: string[]; path?: string[];
fee?: number; fee?: number;
fee_sat?: number;
fee_msat?: number;
value_msat?: number; value_msat?: number;
value_sat?: number; value_sat?: number;
value?: number; value?: number;
payment_preimage?: string; payment_preimage?: string;
creation_time_ns?: string;
payment_index?: string;
failure_reason?: string;
htlcs: HTLC[];
is_expanded?: boolean;
} }
export interface PayRequest { export interface PayRequest {
@ -372,6 +385,9 @@ export interface Hop {
amt_to_forward_msat?: string; amt_to_forward_msat?: string;
fee_msat?: string; fee_msat?: string;
pub_key?: string; pub_key?: string;
tlv_payload?: boolean;
mpp_record?: { payment_addr?: string; total_amt_msat?: number; }
custom_records?: any;
} }
export interface Route { export interface Route {

@ -45,16 +45,12 @@ export class DataService implements OnInit, OnDestroy {
return this.httpClient.get(environment.CONF_API + '/rates'); return this.httpClient.get(environment.CONF_API + '/rates');
} }
getAliasesFromPubkeys(pubkeys: any) { getAliasesFromPubkeys(pubkeys: string[]) {
let nodes$: Array<Observable<any>> = []; return this.httpClient.get(this.childAPIUrl + environment.NETWORK_API + '/nodes/' + pubkeys)
pubkeys.forEach(pubkey => { }
nodes$.push(
this.httpClient.get(this.childAPIUrl + environment.NETWORK_API + '/node/' + pubkey) getAliasFromPubkey(pubkey: string) {
.pipe(takeUntil(this.unSubs[0]), return this.httpClient.get(this.childAPIUrl + environment.NETWORK_API + '/node/' + pubkey)
catchError(err => of({node: {alias: pubkey}})))
);
});
return nodes$;
} }
signMessage(msg: string) { signMessage(msg: string) {

Loading…
Cancel
Save