Channel backup & #157 custom expiry time for invoice

Channel backup & #157 custom expiry time for invoice
pull/163/head
ShahanaFarooqui 5 years ago
parent 9b2024ef29
commit c2c159dba5

1
.gitignore vendored

@ -42,3 +42,4 @@ RTL.conf
/cookies/*
RTL-Multi-Node-Conf.json
RTL-Multi-Node-Conf-1.json
/backup/*

@ -8,5 +8,5 @@
<link rel="stylesheet" href="styles.83644e00292bcd08f710.css"></head>
<body>
<rtl-app></rtl-app>
<script type="text/javascript" src="runtime.26209474bfa8dc87a77c.js"></script><script type="text/javascript" src="polyfills.d68e8f4f73dfaef206f1.js"></script><script type="text/javascript" src="main.8ef8abd4524d2def2d69.js"></script></body>
<script type="text/javascript" src="runtime.26209474bfa8dc87a77c.js"></script><script type="text/javascript" src="polyfills.d68e8f4f73dfaef206f1.js"></script><script type="text/javascript" src="main.e0a09a6455cf3f888aa6.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

@ -9,6 +9,7 @@ const app = express();
const authenticateRoutes = require("./routes/authenticate");
const infoRoutes = require("./routes/getInfo");
const channelsRoutes = require("./routes/channels");
const channelsBackupRoutes = require("./routes/channelsBackup");
const peersRoutes = require("./routes/peers");
const feesRoutes = require("./routes/fees");
const balanceRoutes = require("./routes/balance");
@ -48,6 +49,7 @@ app.use((req, res, next) => {
app.use(apiRoot + "authenticate", authenticateRoutes);
app.use(apiRoot + "getinfo", infoRoutes);
app.use(apiRoot + "channels", channelsRoutes);
app.use(apiRoot + "channels/backup", channelsBackupRoutes);
app.use(apiRoot + "peers", peersRoutes);
app.use(apiRoot + "fees", feesRoutes);
app.use(apiRoot + "balance", balanceRoutes);

@ -11,6 +11,7 @@ var upperCase = require('upper-case');
var logger = require('./controllers/logger');
var connect = {};
var errMsg = '';
var request = require('request');
connect.setDefaultConfig = () => {
var homeDir = os.userInfo().homedir;
@ -48,6 +49,7 @@ connect.setDefaultConfig = () => {
menuType: 'Regular',
theme: 'dark-blue',
satsToBTC: false,
channelBackupPath: homeDir + '/backup/node-0',
lndServerUrl: 'https://localhost:8080/v1',
enableLogging: false,
port: 3000
@ -171,7 +173,31 @@ connect.validateSingleNodeConfig = (config) => {
}
}
if (undefined !== process.env.RTL_PASS) {
if(undefined !== process.env.CHANNEL_BACKUP_PATH) {
common.nodes[0].channel_backup_path = process.env.CHANNEL_BACKUP_PATH;
} else {
if(config.Settings.channelBackupPath !== '' && undefined !== config.Settings.channelBackupPath) {
common.nodes[0].channel_backup_path = config.Settings.channelBackupPath;
} else {
common.nodes[0].channel_backup_path = common.rtl_conf_file_path + '\\backup';
}
try {
connect.createDirectory(common.nodes[0].channel_backup_path);
let exists = fs.existsSync(common.nodes[0].channel_backup_path + '/all-channels.bak');
if (!exists) {
try {
var createStream = fs.createWriteStream(common.nodes[0].channel_backup_path + '/all-channels.bak');
createStream.end();
} catch (err) {
console.error('Something went wrong while creating backup file: \n' + err);
}
}
} catch (err) {
console.error('Something went wrong while creating backup file: \n' + err);
}
}
if (undefined !== process.env.RTL_PASS) {
common.rtl_pass = process.env.RTL_PASS;
} else if (config.Authentication.rtlPassHashed !== '' && undefined !== config.Authentication.rtlPassHashed) {
common.rtl_pass = config.Authentication.rtlPassHashed;
@ -253,6 +279,21 @@ connect.validateMultiNodeConfig = (config) => {
common.nodes[idx].lnd_config_path = (undefined !== node.Authentication.lndConfigPath) ? node.Authentication.lndConfigPath : '';
common.nodes[idx].bitcoind_config_path = (undefined !== node.Settings.bitcoindConfigPath) ? node.Settings.bitcoindConfigPath : '';
common.nodes[idx].enable_logging = (undefined !== node.Settings.enableLogging) ? node.Settings.enableLogging : false;
common.nodes[idx].channel_backup_path = (undefined !== node.Settings.channelBackupPath) ? node.Settings.channelBackupPath : common.rtl_conf_file_path + '\\backup\\node-' + node.index;
try {
connect.createDirectory(common.nodes[idx].channel_backup_path);
let exists = fs.existsSync(common.nodes[idx].channel_backup_path + '/all-channels.bak');
if (!exists) {
try {
var createStream = fs.createWriteStream(common.nodes[idx].channel_backup_path + '/all-channels.bak');
createStream.end();
} catch (err) {
console.error('Something went wrong while creating backup file: \n' + err);
}
}
} catch (err) {
console.error('Something went wrong while creating backup file: \n' + err);
}
if (common.nodes[idx].enable_logging) {
common.nodes[idx].log_file = common.rtl_conf_file_path + '/logs/RTL-Node-' + node.index + '.log';
@ -304,7 +345,15 @@ connect.setSSOParams = (config) => {
connect.createDirectory = (dirname) => {
try {
fs.mkdirSync(dirname);
const sep = path.sep;
const initDir = path.isAbsolute(dirname) ? sep : '';
dirname.split(sep).reduce((parentDir, childDir) => {
const curDir = path.resolve(parentDir, childDir);
if (!fs.existsSync(curDir)) {
fs.mkdirSync(curDir);
}
return curDir;
}, initDir);
} catch (err) {
if (err.code === 'EEXIST') {
return dirname;
@ -361,6 +410,7 @@ connect.logEnvVariables = () => {
logger.info('\r\nConfig Setup Variable RTL_CONFIG_PATH: ' + node.rtl_conf_file_path, node);
logger.info('\r\nConfig Setup Variable LND_CONFIG_PATH: ' + node.lnd_config_path, node);
logger.info('\r\nConfig Setup Variable BITCOIND_CONFIG_PATH: ' + node.bitcoind_config_path, node);
logger.info('\r\nConfig Setup Variable CHANNEL_BACKUP_PATH: ' + node.channel_backup_path, node);
});
} else {
if (!common.nodes[0].enable_logging) { return; }
@ -372,6 +422,7 @@ connect.logEnvVariables = () => {
logger.info('\r\nConfig Setup Variable LND_CONFIG_PATH: ' + common.nodes[0].lnd_config_path);
logger.info('\r\nConfig Setup Variable RTL_CONFIG_PATH: ' + common.rtl_conf_file_path);
logger.info('\r\nConfig Setup Variable BITCOIND_CONFIG_PATH: ' + common.nodes[0].bitcoind_config_path);
logger.info('\r\nConfig Setup Variable CHANNEL_BACKUP_PATH: ' + common.nodes[0].channel_backup_path);
logger.info('\r\nConfig Setup Variable RTL_SSO: ' + common.rtl_sso);
logger.info('\r\nConfig Setup Variable RTL_COOKIE_PATH: ' + common.rtl_cookie_path);
logger.info('\r\nConfig Setup Variable LOGOUT_REDIRECT_LINK: ' + common.logout_redirect_link);
@ -427,6 +478,38 @@ connect.setServerConfiguration = () => {
connect.setMultiNodeConfiguration(multiNodeConfFile);
common.selectedNode = common.findNode(common.nodes[0].index);
}
common.nodes.map(node => { connect.getAllNodeAllChannelBackup(node); });
}
connect.getAllNodeAllChannelBackup = (node) => {
let channel_backup_file = node.channel_backup_path + '/all-channels.bak';
let options = {
url: node.lnd_server_url + '/channels/backup',
rejectUnauthorized: false,
json: true,
headers: {'Grpc-Metadata-macaroon': fs.readFileSync(node.macaroon_path + '/admin.macaroon').toString('hex')}
};
request(options, function (err, res, body) {
if (err) {
logger.error('\r\nConnect: 496: ' + new Date().toJSON().slice(0,19) + ': ERROR: Channel Backup Response Failed: ' + JSON.stringify(err));
} else {
fs.writeFile(channel_backup_file, JSON.stringify(body), function(err) {
if (err) {
if (node.ln_node) {
logger.error('\r\nConnect: 501: ' + new Date().toJSON().slice(0,19) + ': ERROR: Channel Backup Failed for Node ' + node.ln_node + ' with error response: ' + JSON.stringify(err));
} else {
logger.error('\r\nConnect: 503: ' + new Date().toJSON().slice(0,19) + ': ERROR: Channel Backup Failed: ' + JSON.stringify(err));
}
} else {
if (node.ln_node) {
logger.info('\r\nConnect: 507: ' + new Date().toJSON().slice(0,19) + ': INFO: Channel Backup Successful for Node: ' + node.ln_node);
} else {
logger.info('\r\nConnect: 509: ' + new Date().toJSON().slice(0,19) + ': INFO: Channel Backup Successful');
}
}
});
}
})
};
module.exports = connect;

@ -0,0 +1,100 @@
var request = require('request-promise');
var fs = require('fs');
var common = require('../common');
var logger = require('./logger');
var options = {};
exports.getBackup = (req, res, next) => {
options = common.getOptions();
let channel_backup_file = '';
let message = '';
if (req.params.channelPoint === 'ALL') {
message = 'All Channels Backup Successful!';
channel_backup_file = common.selectedNode.channel_backup_path + '/all-channels.bak';
options.url = common.getSelLNDServerUrl() + '/channels/backup';
} else {
message = 'Channel ' + req.params.channelPoint + ' Backup Successful!';
channel_backup_file = common.selectedNode.channel_backup_path + '/channel-' + req.params.channelPoint.replace(':', '-') + '.bak';
let channelpoint = req.params.channelPoint.replace(':', '/');
options.url = common.getSelLNDServerUrl() + '/channels/backup/' + channelpoint;
let exists = fs.existsSync(channel_backup_file);
if (exists) {
fs.writeFile(channel_backup_file, '', () => { });
} else {
try {
var createStream = fs.createWriteStream(channel_backup_file);
createStream.end();
}
catch (err) {
return res.status(500).json({ message: 'Channels Backup Failed!', error: err });
}
}
}
request(options).then(function (body) {
logger.info('\r\nChannels Backup: 16: ' + new Date().toJSON().slice(0,19) + ': INFO: Channel Backup: ' + JSON.stringify(body));
fs.writeFile(channel_backup_file, JSON.stringify(body), function(err) {
if (err) {
return res.status(500).json({ message: 'Channels Backup Failed!', error: err.error });
} else {
res.status(200).json({ message: message });
}
});
})
.catch(function (err) {
logger.info('\r\nChannels Backup: 27: ' + new Date().toJSON().slice(0,19) + ': ERROR: Channel Backup: ' + JSON.stringify(err));
return res.status(500).json({
message: 'Channels Backup Failed!',
error: err.error
});
});
};
exports.postBackupVerify = (req, res, next) => {
options = common.getOptions();
options.url = common.getSelLNDServerUrl() + '/channels/backup/verify';
let channel_verify_file = '';
let message = '';
let verify_backup = '';
if (req.params.channelPoint === 'ALL') {
message = 'All Channels Verify Successful!';
channel_verify_file = common.selectedNode.channel_backup_path + '/all-channels.bak';
let exists = fs.existsSync(channel_verify_file);
if (exists) {
verify_backup = fs.readFileSync(channel_verify_file, 'utf-8');
if (verify_backup !== '') {
verify_backup = JSON.parse(verify_backup);
delete verify_backup.single_chan_backups;
options.form = JSON.stringify(verify_backup);
} else {
res.status(404).json({ message: 'Channels backup to verify does not Exist!' });
}
} else {
verify_backup = '';
res.status(404).json({ message: 'Channels backup to verify does not Exist!' });
}
} else {
message = 'Channel ' + req.params.channelPoint + ' Verify Successful!';
channel_verify_file = common.selectedNode.channel_backup_path + '/channel-' + req.params.channelPoint.replace(':', '-') + '.bak';
let exists = fs.existsSync(channel_verify_file);
if (exists) {
verify_backup = fs.readFileSync(channel_verify_file, 'utf-8');
options.form = JSON.stringify({ single_chan_backups: { chan_backups: [JSON.parse(verify_backup)] } });
} else {
verify_backup = '';
res.status(404).json({ message: 'Channel backup to verify does not Exist!' });
}
}
if (verify_backup !== '') {
request.post(options).then(function (body) {
logger.info('\r\nChannels Backup Verify: 73: ' + new Date().toJSON().slice(0,19) + ': INFO: Channel Backup Verify: ' + JSON.stringify(body));
res.status(201).json({ message: message });
})
.catch(function (err) {
logger.info('\r\nChannels Backup Verify: 77: ' + new Date().toJSON().slice(0,19) + ': ERROR: Channel Backup Verify: ' + JSON.stringify(err));
return res.status(404).json({
message: 'Channel backup to Verify failed!',
error: err.error
});
});
}
};

@ -66,7 +66,7 @@ exports.addInvoice = (req, res, next) => {
memo: req.body.memo,
value: req.body.amount,
private: req.body.private,
expiry: req.body.expiry ? req.body.expiry : 3600
expiry: req.body.expiry
});
request.post(options).then((body) => {
logger.info('\r\nInvoice: 64: ' + JSON.stringify(Date.now()) + ': INFO: Add Invoice Responce: ' + JSON.stringify(body));

@ -0,0 +1,9 @@
const ChannelsBackupController = require("../controllers/channelsBackup");
const express = require("express");
const router = express.Router();
const authCheck = require("./authCheck");
router.get("/:channelPoint", authCheck, ChannelsBackupController.getBackup);
router.post("/verify/:channelPoint", authCheck, ChannelsBackupController.postBackupVerify);
module.exports = router;

@ -22,6 +22,7 @@
"menuType": "Regular",
"theme": "dark-blue",
"satsToBTC": "false",
"channelBackupPath": "C:\\RTL\\backup\\node-1",
"bitcoindConfigPath": "<Optional: path of bitcoind.conf path if available locally>",
"enableLogging": "true",
"lndServerUrl": "<Service url for LND REST APIs for node # 1 e.g. https://192.168.0.1:8080/v1"
@ -41,6 +42,7 @@
"menuType": "Regular",
"theme": "light-teal",
"satsToBTC": "false",
"channelBackupPath": "C:\\RTL\\backup\\node-2",
"bitcoindConfigPath": "",
"enableLogging": "true",
"lndServerUrl": "<Service url for LND REST APIs for node # 2 e.g. https://192.168.0.6:8080/v1"

@ -11,6 +11,7 @@ menu=Vertical
menuType=Regular
theme=dark-blue
satsToBTC=false
channelBackupPath=C:\RTL\backup
bitcoindConfigPath=
enableLogging=true
port=3000

@ -53,7 +53,7 @@ import { ForwardingHistoryComponent } from './pages/switch/forwarding-history.co
import { RoutingPeersComponent } from './pages/routing-peers/routing-peers.component';
import { ChannelLookupComponent } from './pages/lookups/channel-lookup/channel-lookup.component';
import { NodeLookupComponent } from './pages/lookups/node-lookup/node-lookup.component';
// import { ChannelBackupComponent } from './pages/channels/channel-backup/channel-backup.component';
import { ChannelBackupComponent } from './pages/channels/channel-backup/channel-backup.component';
@NgModule({
imports: [
@ -94,7 +94,7 @@ import { NodeLookupComponent } from './pages/lookups/node-lookup/node-lookup.com
RoutingPeersComponent,
ChannelLookupComponent,
NodeLookupComponent,
// ChannelBackupComponent
ChannelBackupComponent
],
providers: [
{ provide: LoggerService, useClass: ConsoleLoggerService },

@ -20,7 +20,7 @@ import { SigninComponent } from './pages/signin/signin.component';
import { ForwardingHistoryComponent } from './pages/switch/forwarding-history.component';
import { RoutingPeersComponent } from './pages/routing-peers/routing-peers.component';
import { SsoFailedComponent } from './shared/components/sso-failed/sso-failed.component';
// import { ChannelBackupComponent } from './pages/channels/channel-backup/channel-backup.component';
import { ChannelBackupComponent } from './pages/channels/channel-backup/channel-backup.component';
import { AuthGuard, LNDUnlockedGuard } from './shared/services/auth.guard';
@ -32,7 +32,7 @@ export const routes: Routes = [
{ path: 'chnlclosed', component: ChannelClosedComponent, canActivate: [AuthGuard, LNDUnlockedGuard] },
{ path: 'chnlmanage', component: ChannelManageComponent, canActivate: [AuthGuard, LNDUnlockedGuard] },
{ path: 'chnlpending', component: ChannelPendingComponent, canActivate: [AuthGuard, LNDUnlockedGuard] },
// { path: 'chnlbackup', component: ChannelBackupComponent, canActivate: [AuthGuard, LNDUnlockedGuard] },
{ path: 'chnlbackup', component: ChannelBackupComponent, canActivate: [AuthGuard, LNDUnlockedGuard] },
{ path: 'transsendreceive', component: SendReceiveTransComponent, canActivate: [AuthGuard, LNDUnlockedGuard] },
{ path: 'translist', component: ListTransactionsComponent, canActivate: [AuthGuard, LNDUnlockedGuard] },
{ path: 'payments', component: PaymentsComponent, canActivate: [AuthGuard, LNDUnlockedGuard] },
@ -47,4 +47,4 @@ export const routes: Routes = [
{ path: '**', component: NotFoundComponent }
];
export const routing: ModuleWithProviders = RouterModule.forRoot(routes, { enableTracing: true });
export const routing: ModuleWithProviders = RouterModule.forRoot(routes);

@ -0,0 +1,47 @@
<div fxLayout="column">
<div class="padding-gap">
<mat-card>
<mat-card-header>
<mat-card-subtitle>
<h2>All Channels Backup</h2>
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<div fxLayout="column" fxLayout.gt-sm="row wrap" fxFlex="100" fxLayoutAlign="space-between start">
<button fxFlex="49" fxLayoutAlign="center center" mat-raised-button color="primary" tabindex="1" (click)="onBackupChannels({})">Backup</button>
<button fxFlex="49" fxLayoutAlign="center center" mat-raised-button color="accent" tabindex="2" (click)="onVerifyChannels({})">Verify Backup</button>
</div>
</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]="channels" matSort [ngClass]="{'mat-elevation-z8 overflow-x-auto error-border': flgLoading[0]==='error','mat-elevation-z8 overflow-x-auto': true}">
<ng-container matColumnDef="chan_id">
<th mat-header-cell *matHeaderCellDef mat-sort-header> Channel ID </th>
<td mat-cell *matCellDef="let channel">{{channel?.chan_id}}</td>
</ng-container>
<ng-container matColumnDef="backup">
<th mat-header-cell *matHeaderCellDef>Backup</th>
<td mat-cell *matCellDef="let channel"><mat-icon color="primary" (click)="onBackupChannels(channel)">save_alt</mat-icon></td>
</ng-container>
<ng-container matColumnDef="verify">
<th mat-header-cell *matHeaderCellDef>Verify</th>
<td mat-cell *matCellDef="let channel"><mat-icon color="accent" (click)="onVerifyChannels(channel)">verified_user</mat-icon></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>

@ -0,0 +1,8 @@
.mat-column-chan_id {
flex: 0 0 25%;
min-width: 100px;
}
table {
width:100%;
}

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

@ -0,0 +1,102 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { Router } from '@angular/router';
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 { Channel } from '../../../shared/models/lndModels';
import { LoggerService } from '../../../shared/services/logger.service';
import { RTLEffects } from '../../../shared/store/rtl.effects';
import * as RTLActions from '../../../shared/store/rtl.actions';
import * as fromRTLReducer from '../../../shared/store/rtl.reducers';
@Component({
selector: 'rtl-channel-backup',
templateUrl: './channel-backup.component.html',
styleUrls: ['./channel-backup.component.scss']
})
export class ChannelBackupComponent implements OnInit, OnDestroy {
@ViewChild(MatSort) sort: MatSort;
public displayedColumns = ['chan_id', 'backup', 'verify'];
public selChannel: Channel;
public channels: any;
public flgLoading: Array<Boolean | 'error'> = [true]; // 0: channels
public flgSticky = false;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
constructor(private logger: LoggerService, private store: Store<fromRTLReducer.State>, private rtlEffects: RTLEffects, private actions$: Actions, private router: Router) {}
ngOnInit() {
this.store.select('rtlRoot')
.pipe(takeUntil(this.unSubs[0]))
.subscribe((rtlStore: fromRTLReducer.State) => {
rtlStore.effectErrors.forEach(effectsErr => {
if (effectsErr.action === 'Fetchchannels') {
this.flgLoading[0] = 'error';
}
});
this.channels = new MatTableDataSource([]);
this.channels.data = [];
if (undefined !== rtlStore.allChannels) {
this.channels = new MatTableDataSource<Channel>([...rtlStore.allChannels]);
this.channels.data = rtlStore.allChannels;
}
this.channels.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_CHANNELS)
).subscribe((setchannels: RTLActions.SetChannels) => {
this.selChannel = undefined;
});
}
onBackupChannels(selChannel: Channel) {
this.store.dispatch(new RTLActions.OpenSpinner('Backup Channels...'));
this.store.dispatch(new RTLActions.BackupChannels({channelPoint: (selChannel.channel_point) ? selChannel.channel_point : 'ALL', showMessage: ''}));
}
onVerifyChannels(selChannel: Channel) {
this.store.dispatch(new RTLActions.OpenSpinner('Verify Channels...'));
this.store.dispatch(new RTLActions.VerifyChannels({channelPoint: (selChannel.channel_point) ? selChannel.channel_point : 'ALL'}));
}
onChannelClick(selRow: Channel, event: any) {
const flgButtonsClicked = event.target.className.includes('mat-icon')
|| event.target.className.includes('mat-column-backup')
|| event.target.className.includes('mat-column-verify');
if (flgButtonsClicked) { 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)
}}));
}
applyFilter(selFilter: string) {
this.channels.filter = selFilter;
}
ngOnDestroy() {
this.unSubs.forEach(completeSub => {
completeSub.next();
completeSub.complete();
});
}
}

@ -90,7 +90,6 @@ export class ChannelManageComponent implements OnInit, OnDestroy {
if (this.flgLoading[0] !== 'error') {
this.flgLoading[0] = (undefined !== rtlStore.allChannels) ? false : true;
}
this.logger.info(rtlStore);
});
this.activatedRoute.paramMap.subscribe(() => {

@ -7,22 +7,21 @@
</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<form fxLayout="column" fxLayout.gt-sm="row wrap" (ngSubmit)="addInvoiceForm.form.valid && onAddInvoice(addInvoiceForm)" #addInvoiceForm="ngForm">
<mat-form-field fxFlex="50" fxLayoutAlign="start end">
<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>
<div fxFlex="15" tabindex="3" fxLayoutAlign="start center" class="chkbox-private">
<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>
<div fxFlex="10" fxLayoutAlign="start start">
<button fxFlex="90" fxLayoutAlign="center center" mat-raised-button color="primary" type="submit" tabindex="4">Add</button>
</div>
<div fxFlex="10" fxLayoutAlign="start start">
<button fxFlex="90" fxLayoutAlign="center center" mat-raised-button color="accent" tabindex="5" type="reset" (click)="resetData()">Clear</button>
</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>
@ -74,7 +73,7 @@
<td mat-cell *matCellDef="let invoice"><span fxLayoutAlign="end center"> {{(selNode?.settings?.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"
<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>

@ -27,6 +27,7 @@ export class InvoicesComponent implements OnInit, OnDestroy {
public newlyAddedInvoiceValue = 0;
public flgAnimate = true;
public memo = '';
public expiry: number;
public invoiceValue: number;
public displayedColumns = [];
public invoicePaymentReq = '';
@ -81,7 +82,7 @@ export class InvoicesComponent implements OnInit, OnDestroy {
this.logger.info(rtlStore);
this.loadInvoicesTable(rtlStore.invoices.invoices);
if (this.flgLoading[0] !== 'error') {
this.flgLoading[0] = (undefined !== rtlStore.invoices.invoices[0]) ? false : true;
this.flgLoading[0] = (undefined !== rtlStore.invoices) ? false : true;
}
});
@ -92,7 +93,9 @@ export class InvoicesComponent implements OnInit, OnDestroy {
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, pageSize: this.pageSize}));
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();
}
@ -122,7 +125,7 @@ export class InvoicesComponent implements OnInit, OnDestroy {
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; }, 3000);
setTimeout(() => { this.flgAnimate = false; }, 30000);
this.logger.info(this.invoices);
}
@ -130,6 +133,7 @@ export class InvoicesComponent implements OnInit, OnDestroy {
this.memo = '';
this.invoiceValue = 0;
this.private = false;
this.expiry = undefined;
}
applyFilter(selFilter: string) {

@ -69,8 +69,8 @@ export class PaymentsComponent implements OnInit, OnDestroy {
});
this.selNode = rtlStore.selNode;
this.information = rtlStore.information;
this.paymentJSONArr = (rtlStore.payments.length > 0) ? rtlStore.payments : [];
this.payments = (undefined === rtlStore.payments) ? new MatTableDataSource([]) : new MatTableDataSource<Payment>([...this.paymentJSONArr]);
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 => {
@ -78,7 +78,7 @@ export class PaymentsComponent implements OnInit, OnDestroy {
});
setTimeout(() => { this.flgAnimate = false; }, 3000);
if (this.flgLoading[0] !== 'error') {
this.flgLoading[0] = (undefined !== this.paymentJSONArr[0]) ? false : true;
this.flgLoading[0] = (undefined !== this.paymentJSONArr) ? false : true;
}
this.logger.info(rtlStore);
});

@ -12,7 +12,6 @@
cursor: pointer;
}
table {
width:100%;
}

@ -93,18 +93,18 @@ export class UnlockLNDComponent implements OnInit, OnDestroy {
.pipe(takeUntil(this.unsubs[3]))
.subscribe(genSeedRes => {
this.genSeedResponse = genSeedRes;
if (this.passphraseFormGroup.controls.enterPassphrase.value) {
this.store.dispatch(new RTLActions.InitWallet({
pwd: window.btoa(this.passwordFormGroup.controls.initWalletPassword.value),
cipher: this.genSeedResponse,
passphrase: window.btoa(this.passphraseFormGroup.controls.passphrase.value)
}));
} else {
// if (this.passphraseFormGroup.controls.enterPassphrase.value) {
// this.store.dispatch(new RTLActions.InitWallet({
// pwd: window.btoa(this.passwordFormGroup.controls.initWalletPassword.value),
// cipher: this.genSeedResponse,
// passphrase: window.btoa(this.passphraseFormGroup.controls.passphrase.value)
// }));
// } else {
this.store.dispatch(new RTLActions.InitWallet({
pwd: window.btoa(this.passwordFormGroup.controls.initWalletPassword.value),
cipher: this.genSeedResponse
}));
}
// }
});
}

@ -15,7 +15,7 @@ export const MENU_DATA: MenuNode = {
{id: 41, parentId: 4, name: 'Management', icon: 'subtitles', link: '/chnlmanage'},
{id: 42, parentId: 4, name: 'Pending', icon: 'watch', link: '/chnlpending'},
{id: 43, parentId: 4, name: 'Closed', icon: 'watch_later', link: '/chnlclosed'},
// {id: 44, parentId: 4, name: 'Backup', icon: 'backup', link: '/chnlbackup'}
{id: 44, parentId: 4, name: 'Backup', icon: 'cloud_circle', link: '/chnlbackup'}
]},
{id: 5, parentId: 0, name: 'Payments', icon: 'payment', link: '/payments'},
{id: 6, parentId: 0, name: 'Invoices', icon: 'receipt', link: '/invoices'},

@ -46,6 +46,10 @@ export const SET_CLOSED_CHANNELS = 'SET_CLOSED_CHANNELS';
export const SAVE_NEW_CHANNEL = 'SAVE_NEW_CHANNEL';
export const CLOSE_CHANNEL = 'CLOSE_CHANNEL';
export const REMOVE_CHANNEL = 'REMOVE_CHANNEL';
export const BACKUP_CHANNELS = 'BACKUP_CHANNELS';
export const VERIFY_CHANNELS = 'VERIFY_CHANNELS';
export const BACKUP_CHANNELS_RES = 'BACKUP_CHANNELS_RES';
export const VERIFY_CHANNELS_RES = 'VERIFY_CHANNELS_RES';
export const FETCH_INVOICES = 'FETCH_INVOICES';
export const SET_INVOICES = 'SET_INVOICES';
export const SET_TOTAL_INVOICES = 'SET_TOTAL_INVOICES';
@ -182,7 +186,7 @@ export class RemovePeer implements Action {
export class SaveNewInvoice implements Action {
readonly type = SAVE_NEW_INVOICE;
constructor(public payload: {memo: string, invoiceValue: number, private: boolean, pageSize: number}) {}
constructor(public payload: {memo: string, invoiceValue: number, private: boolean, expiry: number, pageSize: number}) {}
}
export class AddInvoice implements Action {
@ -258,6 +262,26 @@ export class RemoveChannel implements Action {
constructor(public payload: {channelPoint: string}) {}
}
export class BackupChannels implements Action {
readonly type = BACKUP_CHANNELS;
constructor(public payload: {channelPoint: string, showMessage: string}) {}
}
export class VerifyChannels implements Action {
readonly type = VERIFY_CHANNELS;
constructor(public payload: {channelPoint: string}) {}
}
export class BackupChannelsRes implements Action {
readonly type = BACKUP_CHANNELS_RES;
constructor(public payload: string) {}
}
export class VerifyChannelsRes implements Action {
readonly type = VERIFY_CHANNELS_RES;
constructor(public payload: string) {}
}
export class FetchInvoices implements Action {
readonly type = FETCH_INVOICES;
constructor(public payload: {num_max_invoices?: number, index_offset?: number, reversed?: boolean}) {}
@ -432,6 +456,7 @@ export type RTLActions =
FetchNetwork | SetNetwork |
FetchChannels | SetChannels | SetPendingChannels | SetClosedChannels | UpdateChannels |
SaveNewChannel | CloseChannel | RemoveChannel |
BackupChannels | VerifyChannels | BackupChannelsRes | VerifyChannelsRes |
FetchTransactions | SetTransactions |
FetchInvoices | SetInvoices | SetTotalInvoices |
FetchPayments | SetPayments | SendPayment |

@ -45,7 +45,7 @@ export class RTLEffects implements OnDestroy {
closeSpinner = this.actions$.pipe(
ofType(RTLActions.CLOSE_SPINNER),
map((action: RTLActions.CloseSpinner) => {
this.dialogRef.close();
if (this.dialogRef) { this.dialogRef.close(); }
}
));
@ -61,7 +61,7 @@ export class RTLEffects implements OnDestroy {
closeAlert = this.actions$.pipe(
ofType(RTLActions.CLOSE_ALERT),
map((action: RTLActions.CloseAlert) => {
this.dialogRef.close();
if (this.dialogRef) { this.dialogRef.close(); }
}
));
@ -249,12 +249,14 @@ export class RTLEffects implements OnDestroy {
saveNewInvoice = this.actions$.pipe(
ofType(RTLActions.SAVE_NEW_INVOICE),
mergeMap((action: RTLActions.SaveNewInvoice) => {
return this.httpClient.post(environment.INVOICES_API, {memo: action.payload.memo, amount: action.payload.invoiceValue, private: action.payload.private})
return this.httpClient.post(environment.INVOICES_API, {
memo: action.payload.memo, amount: action.payload.invoiceValue, private: action.payload.private, expiry: action.payload.expiry
})
.pipe(
map((postRes: any) => {
postRes.memo = action.payload.memo;
postRes.value = action.payload.invoiceValue;
postRes.expiry = '3600';
postRes.expiry = action.payload.expiry;
postRes.cltv_expiry = '144';
postRes.creation_date = Math.round(new Date().getTime() / 1000).toString();
postRes.creation_date_str = new Date(+postRes.creation_date * 1000).toUTCString();
@ -296,9 +298,9 @@ export class RTLEffects implements OnDestroy {
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: 'Channel Added Successfully!'}}));
this.store.dispatch(new RTLActions.FetchBalance('blockchain'));
this.store.dispatch(new RTLActions.FetchChannels({routeParam: 'all'}));
this.store.dispatch(new RTLActions.BackupChannels({channelPoint: 'ALL', showMessage: 'Channel Added Successfully!'}));
return {
type: RTLActions.FETCH_CHANNELS,
payload: {routeParam: 'pending', channelStatus: ''}
@ -361,7 +363,6 @@ export class RTLEffects implements OnDestroy {
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: 'Channel Closed Successfully!'}}));
this.store.dispatch(new RTLActions.FetchBalance('channels'));
this.store.dispatch(new RTLActions.FetchBalance('blockchain'));
this.store.dispatch(new RTLActions.FetchChannels({routeParam: 'all'}));
@ -370,6 +371,7 @@ export class RTLEffects implements OnDestroy {
} else {
this.store.dispatch(new RTLActions.FetchChannels({routeParam: 'closed'}));
}
this.store.dispatch(new RTLActions.BackupChannels({channelPoint: 'ALL', showMessage: 'Channel Closed Successfully!'}));
return {
type: RTLActions.REMOVE_CHANNEL,
payload: { channelPoint: action.payload.channelPoint }
@ -390,6 +392,70 @@ export class RTLEffects implements OnDestroy {
}
));
@Effect()
backupChannels = this.actions$.pipe(
ofType(RTLActions.BACKUP_CHANNELS),
mergeMap((action: RTLActions.BackupChannels) => {
this.store.dispatch(new RTLActions.ClearEffectError('BackupChannels'));
return this.httpClient.get(environment.CHANNELS_BACKUP_API + '/' + action.payload.channelPoint)
.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: action.payload.showMessage + ' ' + postRes.message}}));
return {
type: RTLActions.BACKUP_CHANNELS_RES,
payload: postRes.message
};
}),
catchError((err: any) => {
this.store.dispatch(new RTLActions.CloseSpinner());
this.logger.error(err);
this.store.dispatch(new RTLActions.EffectError({ action: 'BackupChannels', code: err.status, message: err.error.error }));
return of(
{
type: RTLActions.OPEN_ALERT,
payload: { width: '70%', data: {type: 'ERROR', titleMessage: action.payload.showMessage + ' ' + 'Unable to Backup Channel. Try again later.',
message: JSON.stringify({code: err.status, Message: err.error.message})}}
}
);
})
);
}
));
@Effect()
verifyChannels = this.actions$.pipe(
ofType(RTLActions.VERIFY_CHANNELS),
mergeMap((action: RTLActions.VerifyChannels) => {
this.store.dispatch(new RTLActions.ClearEffectError('VerifyChannels'));
return this.httpClient.post(environment.CHANNELS_BACKUP_API + '/verify/' + action.payload.channelPoint, {})
.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: postRes.message}}));
return {
type: RTLActions.VERIFY_CHANNELS_RES,
payload: postRes.message
};
}),
catchError((err: any) => {
this.store.dispatch(new RTLActions.CloseSpinner());
this.logger.error(err);
this.store.dispatch(new RTLActions.EffectError({ action: 'VerifyChannels', code: err.status, message: err.error.error }));
return of(
{
type: RTLActions.OPEN_ALERT,
payload: { width: '70%', data: {type: 'ERROR', titleMessage: 'Unable to Verify Channel. Try again later.',
message: JSON.stringify({code: err.status, Message: err.error.message})}}
}
);
})
);
}
));
@Effect()
fetchFees = this.actions$.pipe(
ofType(RTLActions.FETCH_FEES),
@ -552,7 +618,7 @@ export class RTLEffects implements OnDestroy {
this.logger.info(payments);
return {
type: RTLActions.SET_PAYMENTS,
payload: (undefined !== payments) ? payments : []
payload: (undefined !== payments && null != payments) ? payments : []
};
}),
catchError((err: any) => {
@ -1153,7 +1219,7 @@ export class RTLEffects implements OnDestroy {
}
));
SetToken(token: string) {
SetToken(token: string) {
if (token) {
sessionStorage.setItem('lndUnlocked', 'true');
sessionStorage.setItem('token', token);

@ -10,6 +10,7 @@ export const environment = {
FEES_API: API_URL + '/fees',
PEERS_API: API_URL + '/peers',
CHANNELS_API: API_URL + '/channels',
CHANNELS_BACKUP_API: API_URL + '/channels/backup',
GETINFO_API: API_URL + '/getinfo',
WALLET_API: API_URL + '/wallet',
NETWORK_API: API_URL + '/network',

@ -10,6 +10,7 @@ export const environment = {
FEES_API: API_URL + '/fees',
PEERS_API: API_URL + '/peers',
CHANNELS_API: API_URL + '/channels',
CHANNELS_BACKUP_API: API_URL + '/channels/backup',
GETINFO_API: API_URL + '/getinfo',
WALLET_API: API_URL + '/wallet',
NETWORK_API: API_URL + '/network',

Loading…
Cancel
Save