mirror of
https://github.com/Ride-The-Lightning/RTL.git
synced 2024-11-19 01:40:29 +01:00
Merge branch 'Release-0.14.0' into copy-fix
This commit is contained in:
commit
23ff7ac1ec
@ -200,6 +200,7 @@
|
||||
"@angular-eslint/template/no-autofocus": "off",
|
||||
"@angular-eslint/template/no-call-expression": "off",
|
||||
"@angular-eslint/template/no-inline-styles": "off",
|
||||
"@angular-eslint/template/no-interpolation-in-attributes": "off",
|
||||
"@angular-eslint/template/no-positive-tabindex": "off",
|
||||
"@angular-eslint/template/use-track-by-function": "off"
|
||||
}
|
||||
|
2
.github/README.md
vendored
2
.github/README.md
vendored
@ -35,6 +35,7 @@ RTL is available on the below platforms/services:
|
||||
* [BCubium](https://bgeometrics.com)
|
||||
* [Start9Labs](https://start9labs.com)
|
||||
* [Umbrel](https://github.com/getumbrel/umbrel)
|
||||
* [Sovran Systems](https://sovransystems.com)
|
||||
|
||||
Docker Image: https://hub.docker.com/r/shahanafarooqui/rtl
|
||||
|
||||
@ -83,6 +84,7 @@ Example RTL-Config.json:
|
||||
"multiPass": "password",
|
||||
"port": "3000",
|
||||
"defaultNodeIndex": 1,
|
||||
"dbDirectoryPath": "<Complete path of the folder where rtl's database file should be saved>",
|
||||
"SSO": {
|
||||
"rtlSSO": 0,
|
||||
"rtlCookiePath": "",
|
||||
|
26
.github/docs/Application_configurations.md
vendored
26
.github/docs/Application_configurations.md
vendored
@ -9,16 +9,17 @@ parameters have `default` values for initial setup and can be updated after RTL
|
||||
"port": "<port number for the rtl node server, default '3000', Required>",
|
||||
"host": "<host for the rtl node server, default 'all IPs', Optional>",
|
||||
"defaultNodeIndex": <Default index to load when rtl server starts, default 1, Optional>,
|
||||
"dbDirectoryPath": "<Complete path of the folder where rtl database file should be saved, defults to RTL root, Optional>",
|
||||
"SSO": {
|
||||
"rtlSSO": <parameter to turn SSO off/on. Allowed values - 1 (single sign on via an external cookie), 0 (stand alone RTL authentication), default 0, Required>,
|
||||
"rtlSSO": <parameter to turn SSO off/on. Allowed values - 1 (single sign on via an external cookie), 0 (stand alone RTL authentication), Required>,
|
||||
"rtlCookiePath": "<Full path of the cookie file including the file name. The application url needs to pass the value from this cookie file as query param 'access-key' for the SSO authentication to work, Required if SSO=1 else empty (Optional)>",
|
||||
"logoutRedirectLink": "<URL to re-direct to after logout/timeout from RTL, Required if SSO=1 else empty (Optional)>"
|
||||
},
|
||||
"nodes": [
|
||||
{
|
||||
"index": <Incremental node indices starting from 1, Required>,
|
||||
"lnNode": "<Node name to uniquely identify the node in the UI, Default 'Node 1', Required>",
|
||||
"lnImplementation": "<LNP implementation, Allowed values LND/CLN/ECL. Default 'LND', Required>",
|
||||
"lnNode": "<Node name to uniquely identify the node in the UI, Required>",
|
||||
"lnImplementation": "<LNP implementation, Allowed values LND/CLN/ECL, Required>",
|
||||
"Authentication": {
|
||||
"macaroonPath": "<Path for the folder containing 'admin.macaroon' (LND)/'access.macaroon' (CLN) file, Required for LND & CLN>",
|
||||
"swapMacaroonPath": "<Path for the folder containing 'loop.macaroon' (LND), Required for LND Loop>",
|
||||
@ -27,16 +28,16 @@ parameters have `default` values for initial setup and can be updated after RTL
|
||||
"lnApiPassword": "<Password to be used for ECL API authentication. Mandatory only for ECL if the configPath is missing>"
|
||||
},
|
||||
"Settings": {
|
||||
"userPersona": "<User persona to tailor the data on UI. Allowed values MERCHANT, OPERATOR. Default MERCHANT, Required>",
|
||||
"themeMode": "<Theme modes, Allowed values DAY, NIGHT. Default DAY, Required>",
|
||||
"themeColor": "<Theme colors, Allowed values PURPLE, TEAL, INDIGO, PINK, YELLOW. Default PURPLE, Required>",
|
||||
"userPersona": "<User persona to tailor the data on UI. Allowed values MERCHANT/OPERATOR. Default MERCHANT, Optional>",
|
||||
"themeMode": "<Theme modes, Allowed values DAY, NIGHT. Default DAY, Optional>",
|
||||
"themeColor": "<Theme colors, Allowed values PURPLE, TEAL, INDIGO, PINK, YELLOW. Default PURPLE, Optional>",
|
||||
"channelBackupPath": "<Path to save channel backup file. Only for LND implementation, Default <RTL root>\backup\node-1, Optional>",
|
||||
"bitcoindConfigPath": "<Path of bitcoind.conf path if available locally>",
|
||||
"logLevel": <logging levels, will log in accordance with the logLevel value provided, Allowed values ERROR, WARN, INFO, DEBUG>,
|
||||
"fiatConversion": <parameter to turn fiat conversion off/on. Allowed values - true, false, default false, Required>,
|
||||
"currencyUnit": "<Optional: Fiat current Unit for currency conversion, default 'USD' If fiatConversion is true, Required if fiatConversion is true>",
|
||||
"fiatConversion": <parameter to turn fiat conversion off/on. Allowed values - true, false, default false, Optional>,
|
||||
"currencyUnit": "<Optional: Fiat current Unit for currency conversion, default 'USD', Optional>",
|
||||
"unannouncedChannels": <parameter to turn off/on setting for opening announced Channels, default false, Optional>
|
||||
"lnServerUrl": "<Service url for LND/Core Lightning REST APIs for the node, e.g. https://192.168.0.1:8080 OR https://192.168.0.1:3001 OR http://192.168.0.1:8080. Default 'https://127.0.0.1:8080', Required",
|
||||
"lnServerUrl": "<Service url for LND/Core Lightning REST APIs for the node, e.g. https://192.168.0.1:8080 OR https://192.168.0.1:3001 OR http://192.168.0.1:8080. Default 'https://127.0.0.1:8080', Optional>
|
||||
"swapServerUrl": "<Service url for swap server REST APIs for the node, e.g. https://127.0.0.1:8081, Optional>",
|
||||
"boltzServerUrl": "<Service url for boltz server REST APIs for the node, e.g. https://127.0.0.1:9003, Optional>"
|
||||
}
|
||||
@ -49,11 +50,12 @@ parameters have `default` values for initial setup and can be updated after RTL
|
||||
The environment variable can also be used for all of the above configurations except the UI settings.<br />
|
||||
If the environment variables are set, it will take precedence over the parameters in the RTL-Config.json file.<br />
|
||||
<br />
|
||||
PORT (port number for the rtl node server, default 3000, Required)<br />
|
||||
PORT (port number for the rtl node server, default 3000, Optional)<br />
|
||||
HOST (host for the rtl node server, default localhost, Optional)<br />
|
||||
DB_DIRECTORY_PATH (Path for the folder where rtl database file should be saved, default RTL root directory, Optional)
|
||||
APP_PASSWORD (Plaintext password to be provided by the parent container, NOT suggested for standalone RTL applications, to be used by Umbrel) (Optional)<br />
|
||||
LN_IMPLEMENTATION (LND/CLN/ECL. Default 'LND', Required)<br />
|
||||
LN_SERVER_URL (LN server URL for LNP REST APIs, default https://127.0.0.1:8080) (Required)<br />
|
||||
LN_IMPLEMENTATION (LND/CLN/ECL. Default 'LND', Optional)<br />
|
||||
LN_SERVER_URL (LN server URL for LNP REST APIs, default https://127.0.0.1:8080) (Optional)<br />
|
||||
SWAP_SERVER_URL (Swap server URL for REST APIs, default http://127.0.0.1:8081) (Optional)<br />
|
||||
BOLTZ_SERVER_URL (Boltz server URL for REST APIs, default http://127.0.0.1:9003) (Optional)<br />
|
||||
CONFIG_PATH (Full path of the LNP .conf file including the file name) (Optional for LND & CLN, Mandatory for ECL if LN_API_PASSWORD is undefined)<br />
|
||||
|
1
.github/docs/Core_lightning_setup.md
vendored
1
.github/docs/Core_lightning_setup.md
vendored
@ -65,6 +65,7 @@ Ensure that the follow values are correct per your config:
|
||||
"multiPass": <password required for accessing RTL>,
|
||||
"port": "3000",
|
||||
"defaultNodeIndex": 1,
|
||||
"dbDirectoryPath": "<Complete path of the folder where rtl's database file should be saved>",
|
||||
"SSO": {
|
||||
"rtlSSO": 0,
|
||||
"rtlCookiePath": "",
|
||||
|
1
.github/docs/Eclair_setup.md
vendored
1
.github/docs/Eclair_setup.md
vendored
@ -60,6 +60,7 @@ Ensure that the follow values are correct per your config:
|
||||
"multiPass": <password required for accessing RTL>,
|
||||
"port": "3000",
|
||||
"defaultNodeIndex": 1,
|
||||
"dbDirectoryPath": "<Complete path of the folder where rtl's database file should be saved>",
|
||||
"SSO": {
|
||||
"rtlSSO": 0,
|
||||
"rtlCookiePath": "",
|
||||
|
21
.github/docs/Multi_node_setup.md
vendored
21
.github/docs/Multi_node_setup.md
vendored
@ -20,16 +20,17 @@ This step is only required to configure the nodes, which will be remotely connec
|
||||
2. Set `multiPass` to the preferred password. This password will be used to authenticate the user for RTL. Once authenticated, the user will be able to access all the nodes configured in the json file
|
||||
3. Set the `port` to the preferred port number over which to run RTL
|
||||
4. Set the `defaultNodeIndex` to configure the default start up node at server restart
|
||||
5. `SSO` section can be used for single-sign-on from applications like BTCPayserver. If using RTL as a stand-alone app to connect with the nodes, keep the `rtlSSO=0` and ignore the rest of `SSO` section.
|
||||
6. `nodes` section is a json array, with each element of the array representing the specific parameters for the LND node to connect with. `index` must be a number and start with 1. This number must be unique for each node in the array. For each element, two items need to be configured for each node on the network (`macaroonPath` and `lnServerUrl`).
|
||||
7. `macaroonPath` should be set to the local path of the folder containing `admin.macaroon` file for each node. Each node must have a different folder for the `admin.macaroon` on the RTL server.
|
||||
8. `swapMacaroonPath` should be set to the local path of the folder containing `loop.macaroon` file for loop.
|
||||
9. `boltzMacaroonPath` should be set to the local path of the folder containing `admin.macaroon` file for boltz swaps.
|
||||
10. `lnServerUrl` must be set to the service url for LND/Core Lightining REST APIs for each node, with the unique ip address of the node hosting LND/Core Lightning e.g. https://192.168.0.1:8080 OR https://192.168.0.1:3001. In this case the ip address of the node hosting LND/Core Lightning is '192.168.0.1'
|
||||
11. `swapServerUrl` must be set to the swap service url. e.g. https://127.0.0.1:8081.
|
||||
12. `boltzServerUrl` must be set to the boltz service url. e.g. https://127.0.0.1:9003.
|
||||
13. `configPath` and `bitcoindConfigPath` are optional parameters which can be set only if the RTL is running locally on the same node. Else it can be set to "" or removed from the conf file all together.
|
||||
14. `lnApiPassword` is mandatory in the ln implementation is ECL and configPath is missing. It is used to provide password for API authentication. It will be ignored in other ln implementations.
|
||||
5. `dbDirectoryPath` should be set to the folder where RTL's database will be saved.
|
||||
6. `SSO` section can be used for single-sign-on from applications like BTCPayserver. If using RTL as a stand-alone app to connect with the nodes, keep the `rtlSSO=0` and ignore the rest of `SSO` section.
|
||||
7. `nodes` section is a json array, with each element of the array representing the specific parameters for the LND node to connect with. `index` must be a number and start with 1. This number must be unique for each node in the array. For each element, two items need to be configured for each node on the network (`macaroonPath` and `lnServerUrl`).
|
||||
8. `macaroonPath` should be set to the local path of the folder containing `admin.macaroon` file for each node. Each node must have a different folder for the `admin.macaroon` on the RTL server.
|
||||
9. `swapMacaroonPath` should be set to the local path of the folder containing `loop.macaroon` file for loop.
|
||||
10. `boltzMacaroonPath` should be set to the local path of the folder containing `admin.macaroon` file for boltz swaps.
|
||||
11. `lnServerUrl` must be set to the service url for LND/Core Lightining REST APIs for each node, with the unique ip address of the node hosting LND/Core Lightning e.g. https://192.168.0.1:8080 OR https://192.168.0.1:3001. In this case the ip address of the node hosting LND/Core Lightning is '192.168.0.1'
|
||||
12. `swapServerUrl` must be set to the swap service url. e.g. https://127.0.0.1:8081.
|
||||
13. `boltzServerUrl` must be set to the boltz service url. e.g. https://127.0.0.1:9003.
|
||||
14. `configPath` and `bitcoindConfigPath` are optional parameters which can be set only if the RTL is running locally on the same node. Else it can be set to "" or removed from the conf file all together.
|
||||
15. `lnApiPassword` is mandatory in the ln implementation is ECL and configPath is missing. It is used to provide password for API authentication. It will be ignored in other ln implementations.
|
||||
|
||||
#### 3. Restart RTL
|
||||
|
||||
|
1
.github/docs/RTL_setups.md
vendored
1
.github/docs/RTL_setups.md
vendored
@ -15,6 +15,7 @@ If your running RTL and LND on different devices on your local LAN, certain conf
|
||||
"multiPass": "<password in plain text, Default 'password'>",
|
||||
"port": "3000",
|
||||
"defaultNodeIndex": 1,
|
||||
"dbDirectoryPath": "<Complete path of the folder where rtl's database file should be saved>",
|
||||
"SSO": {
|
||||
"rtlSSO": 0,
|
||||
"rtlCookiePath": "",
|
||||
|
23
.github/workflows/stats.yml
vendored
23
.github/workflows/stats.yml
vendored
@ -1,23 +0,0 @@
|
||||
name: Pull Request Stats
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master, 'Release-*' ]
|
||||
tags: [ 'v*' ]
|
||||
release:
|
||||
types: [released]
|
||||
# Triggers the workflow only when merging pull request to the branches.
|
||||
pull_request:
|
||||
types: [opened, closed]
|
||||
branches: [ master, 'Release-*', '*' ]
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
stats:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Run pull request stats
|
||||
uses: flowwer-dev/pull-request-stats@master
|
||||
with:
|
||||
period: 365
|
@ -2,6 +2,7 @@
|
||||
"multiPass": "password",
|
||||
"port": "3000",
|
||||
"defaultNodeIndex": 1,
|
||||
"dbDirectoryPath": "C:\\Users\\xyz\\RTL",
|
||||
"SSO": {
|
||||
"rtlSSO": 0,
|
||||
"rtlCookiePath": "",
|
||||
|
16
angular.json
16
angular.json
@ -22,7 +22,8 @@
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"polyfills": [
|
||||
"zone.js"
|
||||
"zone.js",
|
||||
"src/polyfills.ts"
|
||||
],
|
||||
"tsConfig": "src/tsconfig.app.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
@ -34,6 +35,7 @@
|
||||
"node_modules/material-icons/iconfont/material-icons.css",
|
||||
"node_modules/roboto-fontface/css/roboto/roboto-fontface.css"
|
||||
],
|
||||
"scripts": [],
|
||||
"allowedCommonJsDependencies": [
|
||||
"buffer",
|
||||
"rfdc",
|
||||
@ -41,10 +43,8 @@
|
||||
"qrcode",
|
||||
"otplib",
|
||||
"pdfmake/build/pdfmake",
|
||||
"pdfmake/build/vfs_fonts",
|
||||
"clone-deep"
|
||||
],
|
||||
"scripts": []
|
||||
"pdfmake/build/vfs_fonts"
|
||||
]
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
@ -96,7 +96,8 @@
|
||||
"options": {
|
||||
"polyfills": [
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
"zone.js/testing",
|
||||
"src/polyfills.ts"
|
||||
],
|
||||
"tsConfig": "src/tsconfig.spec.json",
|
||||
"inlineStyleLanguage": "scss",
|
||||
@ -124,9 +125,6 @@
|
||||
}
|
||||
},
|
||||
"cli": {
|
||||
"schematicCollections": [
|
||||
"@angular-eslint/schematics"
|
||||
],
|
||||
"analytics": false
|
||||
}
|
||||
}
|
||||
|
@ -4,21 +4,48 @@ import { Common } from '../../utils/common.js';
|
||||
let options = null;
|
||||
const logger = Logger;
|
||||
const common = Common;
|
||||
export const listPeerChannels = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Peer Channels..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/listPeerChannels';
|
||||
request(options).then((body) => {
|
||||
body?.map((channel) => {
|
||||
if (!channel.alias || channel.alias === '') {
|
||||
channel.alias = channel.peer_id.substring(0, 20);
|
||||
}
|
||||
const local = channel.to_us_msat || 0;
|
||||
const remote = (channel.total_msat - local) || 0;
|
||||
const total = channel.total_msat || 0;
|
||||
channel.to_them_msat = remote;
|
||||
channel.balancedness = (total === 0) ? 1 : (1 - Math.abs((local - remote) / total)).toFixed(3);
|
||||
return channel;
|
||||
});
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Peer Channels List Received', data: body });
|
||||
res.status(200).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'List Peer Channels Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const listChannels = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Channels..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/listChannels';
|
||||
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/listPeerChannels';
|
||||
request(options).then((body) => {
|
||||
body?.map((channel) => {
|
||||
if (!channel.alias || channel.alias === '') {
|
||||
channel.alias = channel.id.substring(0, 20);
|
||||
}
|
||||
const local = (channel.msatoshi_to_us) ? channel.msatoshi_to_us : 0;
|
||||
const remote = (channel.msatoshi_to_them) ? channel.msatoshi_to_them : 0;
|
||||
const total = channel.msatoshi_total ? channel.msatoshi_total : 0;
|
||||
const local = channel.to_us_msat || 0;
|
||||
const remote = (channel.total_msat - local) || 0;
|
||||
const total = channel.total_msat || 0;
|
||||
channel.to_them_msat = remote;
|
||||
channel.balancedness = (total === 0) ? 1 : (1 - Math.abs((local - remote) / total)).toFixed(3);
|
||||
return channel;
|
||||
});
|
||||
|
@ -22,8 +22,8 @@ function paymentReducer(accumulator, currentPayment) {
|
||||
}
|
||||
function summaryReducer(accumulator, mpp) {
|
||||
if (mpp.status === 'complete') {
|
||||
accumulator.msatoshi = accumulator.msatoshi + mpp.msatoshi;
|
||||
accumulator.msatoshi_sent = accumulator.msatoshi_sent + mpp.msatoshi_sent;
|
||||
accumulator.amount_msat = accumulator.amount_msat + mpp.amount_msat;
|
||||
accumulator.amount_sent_msat = accumulator.amount_sent_msat + mpp.amount_sent_msat;
|
||||
accumulator.status = mpp.status;
|
||||
}
|
||||
if (mpp.bolt11) {
|
||||
@ -47,10 +47,10 @@ function groupBy(payments) {
|
||||
delete temp.partid;
|
||||
}
|
||||
else {
|
||||
const paySummary = curr?.reduce(summaryReducer, { msatoshi: 0, msatoshi_sent: 0, status: (curr[0] && curr[0].status) ? curr[0].status : 'failed' });
|
||||
const paySummary = curr?.reduce(summaryReducer, { amount_msat: 0, amount_sent_msat: 0, status: (curr[0] && curr[0].status) ? curr[0].status : 'failed' });
|
||||
temp = {
|
||||
is_group: true, is_expanded: false, total_parts: (curr.length ? curr.length : 0), status: paySummary.status, payment_hash: curr[0].payment_hash,
|
||||
destination: curr[0].destination, msatoshi: paySummary.msatoshi, msatoshi_sent: paySummary.msatoshi_sent, created_at: curr[0].created_at,
|
||||
destination: curr[0].destination, amount_msat: paySummary.amount_msat, amount_sent_msat: paySummary.amount_sent_msat, created_at: curr[0].created_at,
|
||||
mpps: curr
|
||||
};
|
||||
if (paySummary.bolt11) {
|
||||
@ -104,8 +104,8 @@ export const postPayment = (req, res, next) => {
|
||||
if (req.body.paymentType === 'OFFER') {
|
||||
if (req.body.saveToDB && req.body.bolt12) {
|
||||
const offerToUpdate = { bolt12: req.body.bolt12, amountMSat: (req.body.zeroAmtOffer ? 0 : req.body.amount), title: req.body.title, lastUpdatedAt: new Date(Date.now()).getTime() };
|
||||
if (req.body.vendor) {
|
||||
offerToUpdate['vendor'] = req.body.vendor;
|
||||
if (req.body.issuer) {
|
||||
offerToUpdate['issuer'] = req.body.issuer;
|
||||
}
|
||||
if (req.body.description) {
|
||||
offerToUpdate['description'] = req.body.description;
|
||||
|
@ -72,7 +72,6 @@ export class CLWebSocketClient {
|
||||
this.wsServer.sendEventsToAllLNClients(msgStr, clWsClt.selectedNode);
|
||||
};
|
||||
clWsClt.webSocketClient.onerror = (err) => {
|
||||
if (clWsClt.selectedNode.api_version === '' || !clWsClt.selectedNode.api_version || this.common.isVersionCompatible(clWsClt.selectedNode.api_version, '0.6.0')) {
|
||||
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'ERROR', fileName: 'CLWebSocket', msg: 'Web socket error', error: err });
|
||||
const errStr = ((typeof err === 'object' && err.message) ? JSON.stringify({ error: err.message }) : (typeof err === 'object') ? JSON.stringify({ error: err }) : ('{ "error": ' + err + ' }'));
|
||||
this.wsServer.sendErrorToAllLNClients(errStr, clWsClt.selectedNode);
|
||||
@ -80,10 +79,6 @@ export class CLWebSocketClient {
|
||||
if (clWsClt.reConnect) {
|
||||
this.reconnet(clWsClt);
|
||||
}
|
||||
}
|
||||
else {
|
||||
clWsClt.reConnect = false;
|
||||
}
|
||||
};
|
||||
};
|
||||
this.disconnect = (selectedNode) => {
|
||||
|
@ -1,6 +1,9 @@
|
||||
import request from 'request-promise';
|
||||
import { Logger } from '../../utils/logger.js';
|
||||
import { Common } from '../../utils/common.js';
|
||||
import { createInvoiceRequestCall, listPendingInvoicesRequestCall } from './invoices.js';
|
||||
import { findRouteBetweenNodesRequestCall } from './network.js';
|
||||
import { getSentInfoFromPaymentRequest, sendPaymentToRouteRequestCall } from './payments.js';
|
||||
let options = null;
|
||||
const logger = Logger;
|
||||
const common = Common;
|
||||
@ -156,3 +159,54 @@ export const closeChannel = (req, res, next) => {
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
// options.form = { sourceNodeId: req.params.source, targetNodeId: req.params.target, amountMsat: req.params.amount, ignoreNodeIds: req.params.ignore };
|
||||
export const circularRebalance = (req, res, next) => {
|
||||
const crInvDescription = 'Circular rebalancing invoice for ' + (req.body.amountMsat / 1000) + ' Sats';
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.form = req.body;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Rebalance Params', data: options.form });
|
||||
const tillToday = (Math.round(new Date(Date.now()).getTime() / 1000)).toString();
|
||||
// Check if unpaid Invoice exists already
|
||||
listPendingInvoicesRequestCall(req.session.selectedNode).then((callRes) => {
|
||||
const foundExistingInvoice = callRes.find((inv) => inv.description.includes(crInvDescription) && inv.amount === req.body.amountMsat && inv.expiry && inv.timestamp && ((inv.expiry + inv.timestamp) >= tillToday));
|
||||
// Create new invoice if doesn't exist already
|
||||
const requestCalls = foundExistingInvoice && foundExistingInvoice.serialized ?
|
||||
[findRouteBetweenNodesRequestCall(req.session.selectedNode, req.body.amountMsat, req.body.sourceNodeId, req.body.targetNodeId, req.body.ignoreNodeIds, req.body.format)] :
|
||||
[findRouteBetweenNodesRequestCall(req.session.selectedNode, req.body.amountMsat, req.body.sourceNodeId, req.body.targetNodeId, req.body.ignoreNodeIds, req.body.format), createInvoiceRequestCall(req.session.selectedNode, crInvDescription, req.body.amountMsat)];
|
||||
Promise.all(requestCalls).then((values) => {
|
||||
// eslint-disable-next-line arrow-body-style
|
||||
const routes = values[0]?.routes?.filter((route) => {
|
||||
return !((route.shortChannelIds[0] === req.body.sourceShortChannelId && route.shortChannelIds[1] === req.body.targetShortChannelId) ||
|
||||
(route.shortChannelIds[1] === req.body.sourceShortChannelId && route.shortChannelIds[0] === req.body.targetShortChannelId));
|
||||
});
|
||||
const firstRoute = routes[0].shortChannelIds.join() || '';
|
||||
const shortChannelIds = req.body.sourceShortChannelId + ',' + firstRoute + ',' + req.body.targetShortChannelId;
|
||||
const invoice = (foundExistingInvoice && foundExistingInvoice.serialized ? foundExistingInvoice.serialized : (values[1] ? values[1].serialized : '')) || '';
|
||||
const paymentHash = (foundExistingInvoice && foundExistingInvoice.paymentHash ? foundExistingInvoice.paymentHash : (values[1] ? values[1].paymentHash : '') || '');
|
||||
return sendPaymentToRouteRequestCall(req.session.selectedNode, shortChannelIds, invoice, req.body.amountMsat).then((payToRouteCallRes) => {
|
||||
// eslint-disable-next-line arrow-body-style
|
||||
setTimeout(() => {
|
||||
return getSentInfoFromPaymentRequest(req.session.selectedNode, paymentHash).then((sentInfoCallRes) => {
|
||||
const payStatus = sentInfoCallRes.length && sentInfoCallRes.length > 0 ? sentInfoCallRes[sentInfoCallRes.length - 1].status : sentInfoCallRes;
|
||||
return res.status(201).json({ flgReusingInvoice: !!foundExistingInvoice, invoice: invoice, paymentHash: paymentHash, paymentDetails: payToRouteCallRes, paymentStatus: payStatus });
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Channel Rebalance From Sent Info Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ flgReusingInvoice: !!foundExistingInvoice, invoice: invoice, paymentHash: paymentHash, paymentDetails: payToRouteCallRes, paymentStatus: err.error });
|
||||
});
|
||||
}, 3000);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Channel Rebalance From Send Payment To Route Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ flgReusingInvoice: !!foundExistingInvoice, invoice: invoice, paymentHash: paymentHash, paymentDetails: {}, paymentStatus: err.error });
|
||||
});
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Channel Rebalance From Find Routes Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ flgReusingInvoice: !!foundExistingInvoice, invoice: (foundExistingInvoice.serialized || ''), paymentHash: '', paymentDetails: {}, paymentStatus: err.error });
|
||||
});
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Channel Rebalance From List Pending Invoices Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ flgReusingInvoice: false, invoice: '', paymentHash: '', paymentDetails: {}, paymentStatus: err.error });
|
||||
});
|
||||
};
|
||||
|
@ -19,7 +19,7 @@ export const getReceivedPaymentInfo = (lnServerUrl, invoice) => {
|
||||
invoice.status = response.status.type;
|
||||
if (response.status && response.status.type === 'received') {
|
||||
invoice.amountSettled = response.status.amount ? Math.round(response.status.amount / 1000) : 0;
|
||||
invoice.receivedAt = response.status.receivedAt ? Math.round(response.status.receivedAt / 1000) : 0;
|
||||
invoice.receivedAt = response.status.receivedAt.unix ? response.status.receivedAt.unix : 0;
|
||||
}
|
||||
return invoice;
|
||||
}).catch((err) => {
|
||||
@ -53,6 +53,20 @@ export const getInvoice = (req, res, next) => {
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const listPendingInvoicesRequestCall = (selectedNode) => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'List Pending Invoices..' });
|
||||
options = selectedNode.options;
|
||||
options.url = selectedNode.ln_server_url + '/listpendinginvoices';
|
||||
options.form = { from: 0, to: (Math.round(new Date(Date.now()).getTime() / 1000)).toString() };
|
||||
return new Promise((resolve, reject) => {
|
||||
request.post(options).then((pendingInvoicesResponse) => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Pending Invoices List ', data: pendingInvoicesResponse });
|
||||
resolve(pendingInvoicesResponse);
|
||||
}).catch((errRes) => {
|
||||
reject(common.handleError(errRes, 'Invoices', 'List Pending Invoices Error', selectedNode));
|
||||
});
|
||||
});
|
||||
};
|
||||
export const listInvoices = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Getting List Invoices..' });
|
||||
options = common.getOptions(req);
|
||||
@ -103,22 +117,30 @@ export const listInvoices = (req, res, next) => {
|
||||
});
|
||||
}
|
||||
};
|
||||
export const createInvoiceRequestCall = (selectedNode, description, amount) => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Creating Invoice..' });
|
||||
options = selectedNode.options;
|
||||
options.url = selectedNode.ln_server_url + '/createinvoice';
|
||||
options.form = { description: description, amountMsat: amount };
|
||||
return new Promise((resolve, reject) => {
|
||||
request.post(options).then((invResponse) => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoice Created', data: invResponse });
|
||||
if (invResponse.amount) {
|
||||
invResponse.amount = Math.round(invResponse.amount / 1000);
|
||||
}
|
||||
resolve(invResponse);
|
||||
}).catch((errRes) => {
|
||||
reject(common.handleError(errRes, 'Invoices', 'Create Invoice Error', selectedNode));
|
||||
});
|
||||
});
|
||||
};
|
||||
export const createInvoice = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Creating Invoice..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
options.url = req.session.selectedNode.ln_server_url + '/createinvoice';
|
||||
options.form = req.body;
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoice Created', data: body });
|
||||
if (body.amount) {
|
||||
body.amount = Math.round(body.amount / 1000);
|
||||
}
|
||||
res.status(201).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Invoices', 'Create Invoice Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
createInvoiceRequestCall(req.session.selectedNode, req.body.description, req.body.amountMsat).then((invRes) => {
|
||||
res.status(201).json(invRes);
|
||||
}).catch((err) => res.status(err.statusCode).json({ message: err.message, error: err.error }));
|
||||
};
|
||||
|
@ -20,3 +20,26 @@ export const getNodes = (req, res, next) => {
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
export const findRouteBetweenNodesRequestCall = (selectedNode, amountMsat, sourceNodeId, targetNodeId, ignoreNodeIds = [], format = 'shortChannelId') => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Network', msg: 'Find Route Between Nodes..' });
|
||||
options = selectedNode.options;
|
||||
options.url = selectedNode.ln_server_url + '/findroutebetweennodes';
|
||||
options.form = { amountMsat: amountMsat, sourceNodeId: sourceNodeId, targetNodeId: targetNodeId, ignoreNodeIds: ignoreNodeIds, format: format };
|
||||
return new Promise((resolve, reject) => {
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Network', msg: 'Route Lookup Between Nodes Finished', data: body });
|
||||
resolve(body);
|
||||
}).catch((errRes) => {
|
||||
reject(common.handleError(errRes, 'Network', 'Route Lookup Between Nodes Error', selectedNode));
|
||||
});
|
||||
});
|
||||
};
|
||||
export const findRouteBetweenNodes = (req, res, next) => {
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
findRouteBetweenNodesRequestCall(req.session.selectedNode, req.body.amountMsat, req.body.sourceNodeId, req.body.targetNodeId, req.body.ignoreNodeIds, req.body.format).then((callRes) => {
|
||||
res.status(200).json(callRes);
|
||||
}).catch((err) => res.status(err.statusCode).json({ message: err.message, error: err.error }));
|
||||
};
|
||||
|
@ -126,3 +126,28 @@ export const getSentPaymentsInformation = (req, res, next) => {
|
||||
return res.status(200).json([]);
|
||||
}
|
||||
};
|
||||
export const sendPaymentToRouteRequestCall = (selectedNode, shortChannelIds, invoice, amountMsat) => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Creating Invoice..' });
|
||||
options = selectedNode.options;
|
||||
options.url = selectedNode.ln_server_url + '/sendtoroute';
|
||||
options.form = { shortChannelIds: shortChannelIds, amountMsat: amountMsat, invoice: invoice };
|
||||
return new Promise((resolve, reject) => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Send Payment To Route Options', data: options.form });
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Sent To Route', data: body });
|
||||
resolve(body);
|
||||
}).catch((errRes) => {
|
||||
reject(common.handleError(errRes, 'Payments', 'Send Payment To Route Error', selectedNode));
|
||||
});
|
||||
});
|
||||
};
|
||||
export const sendPaymentToRoute = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Send Payment To Route..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) {
|
||||
return res.status(options.statusCode).json({ message: options.message, error: options.error });
|
||||
}
|
||||
sendPaymentToRouteRequestCall(req.session.selectedNode, req.body.shortChannelIds, req.body.invoice, req.body.amountMsat).then((callRes) => {
|
||||
res.status(200).json(callRes);
|
||||
}).catch((err) => res.status(err.statusCode).json({ message: err.message, error: err.error }));
|
||||
};
|
||||
|
@ -110,10 +110,12 @@ export class ECLWebSocketClient {
|
||||
};
|
||||
this.heartbeat = (eclWsClt) => {
|
||||
this.logger.log({ selectedNode: eclWsClt.selectedNode, level: 'DEBUG', fileName: 'ECLWebSocket', msg: 'Websocket Server Heartbeat..' });
|
||||
if (!eclWsClt.webSocketClient)
|
||||
if (!eclWsClt.webSocketClient) {
|
||||
return;
|
||||
if (eclWsClt.webSocketClient.readyState !== 1)
|
||||
}
|
||||
if (eclWsClt.webSocketClient.readyState !== 1) {
|
||||
return;
|
||||
}
|
||||
eclWsClt.webSocketClient.ping();
|
||||
setTimeout(() => {
|
||||
this.heartbeat(eclWsClt);
|
||||
|
@ -176,6 +176,7 @@ export const postTransactions = (req, res, next) => {
|
||||
options.form = JSON.stringify(options.form);
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Send Payment Options', data: options.form });
|
||||
request.post(options).then((body) => {
|
||||
body = body.result ? body.result : body;
|
||||
if (body.payment_error) {
|
||||
const err = common.handleError(body.payment_error, 'Channels', 'Send Payment Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
|
@ -3,15 +3,15 @@ export var OfferFieldsEnum;
|
||||
OfferFieldsEnum["BOLT12"] = "bolt12";
|
||||
OfferFieldsEnum["AMOUNTMSAT"] = "amountMSat";
|
||||
OfferFieldsEnum["TITLE"] = "title";
|
||||
OfferFieldsEnum["VENDOR"] = "vendor";
|
||||
OfferFieldsEnum["ISSUER"] = "issuer";
|
||||
OfferFieldsEnum["DESCRIPTION"] = "description";
|
||||
})(OfferFieldsEnum || (OfferFieldsEnum = {}));
|
||||
export class Offer {
|
||||
constructor(bolt12, amountMSat, title, vendor, description, lastUpdatedAt) {
|
||||
constructor(bolt12, amountMSat, title, issuer, description, lastUpdatedAt) {
|
||||
this.bolt12 = bolt12;
|
||||
this.amountMSat = amountMSat;
|
||||
this.title = title;
|
||||
this.vendor = vendor;
|
||||
this.issuer = issuer;
|
||||
this.description = description;
|
||||
this.lastUpdatedAt = lastUpdatedAt;
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
import exprs from 'express';
|
||||
const { Router } = exprs;
|
||||
import { isAuthenticated } from '../../utils/authCheck.js';
|
||||
import { listChannels, openChannel, setChannelFee, closeChannel, getLocalRemoteBalance, listForwards, funderUpdatePolicy, listForwardsPaginated } from '../../controllers/cln/channels.js';
|
||||
import { listChannels, listPeerChannels, openChannel, setChannelFee, closeChannel, getLocalRemoteBalance, listForwards, funderUpdatePolicy, listForwardsPaginated } from '../../controllers/cln/channels.js';
|
||||
const router = Router();
|
||||
router.get('/listChannels', isAuthenticated, listChannels);
|
||||
router.get('/listPeerChannels', isAuthenticated, listPeerChannels);
|
||||
router.post('/', isAuthenticated, openChannel);
|
||||
router.post('/setChannelFee', isAuthenticated, setChannelFee);
|
||||
router.delete('/:channelId', isAuthenticated, closeChannel);
|
||||
|
@ -1,11 +1,12 @@
|
||||
import exprs from 'express';
|
||||
const { Router } = exprs;
|
||||
import { isAuthenticated } from '../../utils/authCheck.js';
|
||||
import { getChannels, getChannelStats, openChannel, updateChannelRelayFee, closeChannel } from '../../controllers/eclair/channels.js';
|
||||
import { getChannels, getChannelStats, openChannel, updateChannelRelayFee, closeChannel, circularRebalance } from '../../controllers/eclair/channels.js';
|
||||
const router = Router();
|
||||
router.get('/', isAuthenticated, getChannels);
|
||||
router.get('/stats', isAuthenticated, getChannelStats);
|
||||
router.post('/', isAuthenticated, openChannel);
|
||||
router.post('/updateRelayFee', isAuthenticated, updateChannelRelayFee);
|
||||
router.post('/circularRebalance', circularRebalance);
|
||||
router.delete('/', isAuthenticated, closeChannel);
|
||||
export default router;
|
||||
|
@ -1,7 +1,8 @@
|
||||
import exprs from 'express';
|
||||
const { Router } = exprs;
|
||||
import { isAuthenticated } from '../../utils/authCheck.js';
|
||||
import { getNodes } from '../../controllers/eclair/network.js';
|
||||
import { getNodes, findRouteBetweenNodes } from '../../controllers/eclair/network.js';
|
||||
const router = Router();
|
||||
router.get('/nodes/:id', isAuthenticated, getNodes);
|
||||
router.get('/routebetweennodes', isAuthenticated, findRouteBetweenNodes);
|
||||
export default router;
|
||||
|
@ -1,10 +1,11 @@
|
||||
import exprs from 'express';
|
||||
const { Router } = exprs;
|
||||
import { isAuthenticated } from '../../utils/authCheck.js';
|
||||
import { queryPaymentRoute, decodePayment, getSentPaymentsInformation, postPayment } from '../../controllers/eclair/payments.js';
|
||||
import { queryPaymentRoute, decodePayment, getSentPaymentsInformation, postPayment, sendPaymentToRoute } from '../../controllers/eclair/payments.js';
|
||||
const router = Router();
|
||||
router.get('/route/', isAuthenticated, queryPaymentRoute);
|
||||
router.get('/decode/:invoice', isAuthenticated, decodePayment);
|
||||
router.post('/getsentinfos', isAuthenticated, getSentPaymentsInformation);
|
||||
router.post('/sendtoroute', isAuthenticated, sendPaymentToRoute);
|
||||
router.post('/', isAuthenticated, postPayment);
|
||||
export default router;
|
||||
|
@ -12,7 +12,6 @@ import clnRoutes from '../routes/cln/index.js';
|
||||
import eclRoutes from '../routes/eclair/index.js';
|
||||
import { Common } from './common.js';
|
||||
import { Logger } from './logger.js';
|
||||
import { Config } from './config.js';
|
||||
import { CLWSClient } from '../controllers/cln/webSocketClient.js';
|
||||
import { ECLWSClient } from '../controllers/eclair/webSocketClient.js';
|
||||
import { LNDWSClient } from '../controllers/lnd/webSocketClient.js';
|
||||
@ -22,15 +21,11 @@ export class ExpressApplication {
|
||||
this.app = express();
|
||||
this.logger = Logger;
|
||||
this.common = Common;
|
||||
this.config = Config;
|
||||
this.eclWsClient = ECLWSClient;
|
||||
this.clWsClient = CLWSClient;
|
||||
this.lndWsClient = LNDWSClient;
|
||||
this.directoryName = dirname(fileURLToPath(import.meta.url));
|
||||
this.getApp = () => this.app;
|
||||
this.loadConfiguration = () => {
|
||||
this.config.setServerConfiguration();
|
||||
};
|
||||
this.setCORS = () => { CORS.mount(this.app); };
|
||||
this.setCSRF = () => { CSRF.mount(this.app); };
|
||||
this.setApplicationRoutes = () => {
|
||||
@ -81,7 +76,6 @@ export class ExpressApplication {
|
||||
this.app.use(cookieParser(this.common.secret_key));
|
||||
this.app.use(bodyParser.json({ limit: '25mb' }));
|
||||
this.app.use(bodyParser.urlencoded({ extended: false, limit: '25mb' }));
|
||||
this.loadConfiguration();
|
||||
this.setCORS();
|
||||
this.setCSRF();
|
||||
this.setApplicationRoutes();
|
||||
|
@ -11,7 +11,8 @@ export class CommonService {
|
||||
this.initSelectedNode = null;
|
||||
this.rtl_conf_file_path = '';
|
||||
this.port = 3000;
|
||||
this.host = null;
|
||||
this.host = '';
|
||||
this.db_directory_path = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
||||
this.rtl_pass = '';
|
||||
this.flg_allow_password_update = true;
|
||||
this.rtl_secret2fa = '';
|
||||
@ -219,7 +220,11 @@ export class CommonService {
|
||||
}
|
||||
};
|
||||
this.handleError = (errRes, fileName, errMsg, selectedNode) => {
|
||||
const err = JSON.parse(JSON.stringify(errRes));
|
||||
let err = JSON.parse(JSON.stringify(errRes));
|
||||
if (err && err.error && Object.keys(err.error).length === 0 && errRes.error && (errRes.error.stack || errRes.error.message)) {
|
||||
errRes.error = errRes.error.stack || errRes.error.message;
|
||||
err = JSON.parse(JSON.stringify(errRes));
|
||||
}
|
||||
if (!selectedNode) {
|
||||
selectedNode = this.initSelectedNode;
|
||||
}
|
||||
@ -254,7 +259,7 @@ export class CommonService {
|
||||
}
|
||||
break;
|
||||
}
|
||||
this.logger.log({ selectedNode: selectedNode, level: 'ERROR', fileName: fileName, msg: errMsg, error: (typeof err === 'object' ? JSON.stringify(err) : (typeof err === 'string') ? err : 'Unknown Error') });
|
||||
this.logger.log({ selectedNode: selectedNode, level: 'ERROR', fileName: fileName, msg: errMsg, error: (typeof err === 'object' ? JSON.stringify(err) : err) });
|
||||
let newErrorObj = { statusCode: 500, message: '', error: '' };
|
||||
if (err.code && err.code === 'ENOENT') {
|
||||
newErrorObj = {
|
||||
@ -316,7 +321,7 @@ export class CommonService {
|
||||
this.cookie_value = fs.readFileSync(this.rtl_cookie_path, 'utf-8');
|
||||
}
|
||||
catch (err) {
|
||||
this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while reading cookie: \n' + err });
|
||||
this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Something went wrong while reading cookie: \n' + err });
|
||||
throw new Error(err);
|
||||
}
|
||||
}
|
||||
@ -328,7 +333,7 @@ export class CommonService {
|
||||
this.cookie_value = fs.readFileSync(this.rtl_cookie_path, 'utf-8');
|
||||
}
|
||||
catch (err) {
|
||||
this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while reading the cookie: \n' + err });
|
||||
this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Something went wrong while reading the cookie: \n' + err });
|
||||
throw new Error(err);
|
||||
}
|
||||
}
|
||||
@ -429,6 +434,7 @@ export class CommonService {
|
||||
if (selNode && selNode.index) {
|
||||
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'PORT: ' + this.port });
|
||||
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'HOST: ' + this.host });
|
||||
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'DB_DIRECTORY_PATH: ' + this.db_directory_path });
|
||||
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'SSO: ' + this.rtl_sso });
|
||||
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'DEFAULT NODE INDEX: ' + selNode.index });
|
||||
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'INDEX: ' + selNode.index });
|
||||
|
@ -20,31 +20,37 @@ export class ConfigService {
|
||||
let macaroonPath = '';
|
||||
let configPath = '';
|
||||
let channelBackupPath = '';
|
||||
let dbPath = '';
|
||||
switch (this.platform) {
|
||||
case 'win32':
|
||||
macaroonPath = homeDir + '\\AppData\\Local\\Lnd\\data\\chain\\bitcoin\\mainnet';
|
||||
configPath = homeDir + '\\AppData\\Local\\Lnd\\lnd.conf';
|
||||
channelBackupPath = homeDir + '\\backup\\node-1';
|
||||
dbPath = homeDir + '\\database\\node-1';
|
||||
break;
|
||||
case 'darwin':
|
||||
macaroonPath = homeDir + '/Library/Application Support/Lnd/data/chain/bitcoin/mainnet';
|
||||
configPath = homeDir + '/Library/Application Support/Lnd/lnd.conf';
|
||||
channelBackupPath = homeDir + '/backup/node-1';
|
||||
dbPath = homeDir + '/database/node-1';
|
||||
break;
|
||||
case 'linux':
|
||||
macaroonPath = homeDir + '/.lnd/data/chain/bitcoin/mainnet';
|
||||
configPath = homeDir + '/.lnd/lnd.conf';
|
||||
channelBackupPath = homeDir + '/backup/node-1';
|
||||
dbPath = homeDir + '/database/node-1';
|
||||
break;
|
||||
default:
|
||||
macaroonPath = '';
|
||||
configPath = '';
|
||||
channelBackupPath = '';
|
||||
dbPath = '';
|
||||
break;
|
||||
}
|
||||
const configData = {
|
||||
port: '3000',
|
||||
defaultNodeIndex: 1,
|
||||
dbDirectoryPath: dbPath,
|
||||
SSO: {
|
||||
rtlSSO: 0,
|
||||
rtlCookiePath: '',
|
||||
@ -72,7 +78,7 @@ export class ConfigService {
|
||||
}
|
||||
]
|
||||
};
|
||||
if (+process.env.RTL_SSO === 0 || configData.SSO.rtlSSO === 0) {
|
||||
if ((process?.env?.RTL_SSO && +process?.env?.RTL_SSO === 0) || configData.SSO.rtlSSO === 0) {
|
||||
configData['multiPass'] = 'password';
|
||||
}
|
||||
return configData;
|
||||
@ -89,7 +95,7 @@ export class ConfigService {
|
||||
};
|
||||
this.updateLogByLevel = () => {
|
||||
let updateLogFlag = false;
|
||||
this.common.rtl_conf_file_path = process.env.RTL_CONFIG_PATH ? process.env.RTL_CONFIG_PATH : join(this.directoryName, '../..');
|
||||
this.common.rtl_conf_file_path = process?.env?.RTL_CONFIG_PATH ? process?.env?.RTL_CONFIG_PATH : join(this.directoryName, '../..');
|
||||
try {
|
||||
const RTLConfFile = this.common.rtl_conf_file_path + sep + 'RTL-Config.json';
|
||||
const config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8'));
|
||||
@ -109,9 +115,9 @@ export class ConfigService {
|
||||
}
|
||||
};
|
||||
this.validateNodeConfig = (config) => {
|
||||
if ((+process.env.RTL_SSO === 0) || (typeof process.env.RTL_SSO === 'undefined' && +config.SSO.rtlSSO === 0)) {
|
||||
if (process.env.APP_PASSWORD && process.env.APP_PASSWORD.trim() !== '') {
|
||||
this.common.rtl_pass = this.hash.update(process.env.APP_PASSWORD).digest('hex');
|
||||
if ((process?.env?.RTL_SSO && +process?.env?.RTL_SSO === 0) || (typeof process?.env?.RTL_SSO === 'undefined' && +config.SSO.rtlSSO === 0)) {
|
||||
if (process?.env?.APP_PASSWORD && process?.env?.APP_PASSWORD.trim() !== '') {
|
||||
this.common.rtl_pass = this.hash.update(process?.env?.APP_PASSWORD).digest('hex');
|
||||
this.common.flg_allow_password_update = false;
|
||||
}
|
||||
else if (config.multiPassHashed && config.multiPassHashed !== '') {
|
||||
@ -126,23 +132,24 @@ export class ConfigService {
|
||||
this.common.rtl_secret2fa = config.secret2fa;
|
||||
}
|
||||
else {
|
||||
if (process.env.APP_PASSWORD && process.env.APP_PASSWORD.trim() !== '') {
|
||||
if (process?.env?.APP_PASSWORD && process?.env?.APP_PASSWORD.trim() !== '') {
|
||||
this.errMsg = this.errMsg + '\nRTL Password cannot be set with SSO. Please set SSO as 0 or remove password.';
|
||||
}
|
||||
}
|
||||
this.common.port = (process.env.PORT) ? this.normalizePort(process.env.PORT) : (config.port) ? this.normalizePort(config.port) : 3000;
|
||||
this.common.host = (process.env.HOST) ? process.env.HOST : (config.host) ? config.host : null;
|
||||
this.common.port = (process?.env?.PORT) ? this.normalizePort(process?.env?.PORT) : (config.port) ? this.normalizePort(config.port) : 3000;
|
||||
this.common.host = (process?.env?.HOST) ? process?.env?.HOST : (config.host) ? config.host : null;
|
||||
this.common.db_directory_path = (process?.env?.DB_DIRECTORY_PATH) ? process?.env?.DB_DIRECTORY_PATH : (config.dbDirectoryPath) ? config.dbDirectoryPath : join(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
||||
if (config.nodes && config.nodes.length > 0) {
|
||||
config.nodes.forEach((node, idx) => {
|
||||
this.common.nodes[idx] = {};
|
||||
this.common.nodes[idx].index = node.index;
|
||||
this.common.nodes[idx].ln_node = node.lnNode;
|
||||
this.common.nodes[idx].ln_implementation = (process.env.LN_IMPLEMENTATION) ? process.env.LN_IMPLEMENTATION : node.lnImplementation ? node.lnImplementation : 'LND';
|
||||
this.common.nodes[idx].ln_implementation = (process?.env?.LN_IMPLEMENTATION) ? process?.env?.LN_IMPLEMENTATION : node.lnImplementation ? node.lnImplementation : 'LND';
|
||||
if (this.common.nodes[idx].ln_implementation === 'CLT') {
|
||||
this.common.nodes[idx].ln_implementation = 'CLN';
|
||||
}
|
||||
if (this.common.nodes[idx].ln_implementation !== 'ECL' && process.env.MACAROON_PATH && process.env.MACAROON_PATH.trim() !== '') {
|
||||
this.common.nodes[idx].macaroon_path = process.env.MACAROON_PATH;
|
||||
if (this.common.nodes[idx].ln_implementation !== 'ECL' && process?.env?.MACAROON_PATH && process?.env?.MACAROON_PATH.trim() !== '') {
|
||||
this.common.nodes[idx].macaroon_path = process?.env?.MACAROON_PATH;
|
||||
}
|
||||
else if (this.common.nodes[idx].ln_implementation !== 'ECL' && node.Authentication && node.Authentication.macaroonPath && node.Authentication.macaroonPath.trim() !== '') {
|
||||
this.common.nodes[idx].macaroon_path = node.Authentication.macaroonPath;
|
||||
@ -151,8 +158,8 @@ export class ConfigService {
|
||||
this.errMsg = 'Please set macaroon path for node index ' + node.index + ' in RTL-Config.json!';
|
||||
}
|
||||
if (this.common.nodes[idx].ln_implementation === 'ECL') {
|
||||
if (process.env.LN_API_PASSWORD) {
|
||||
this.common.nodes[idx].ln_api_password = process.env.LN_API_PASSWORD;
|
||||
if (process?.env?.LN_API_PASSWORD) {
|
||||
this.common.nodes[idx].ln_api_password = process?.env?.LN_API_PASSWORD;
|
||||
}
|
||||
else if (node.Authentication && node.Authentication.lnApiPassword) {
|
||||
this.common.nodes[idx].ln_api_password = node.Authentication.lnApiPassword;
|
||||
@ -161,8 +168,8 @@ export class ConfigService {
|
||||
this.common.nodes[idx].ln_api_password = '';
|
||||
}
|
||||
}
|
||||
if (process.env.CONFIG_PATH) {
|
||||
this.common.nodes[idx].config_path = process.env.CONFIG_PATH;
|
||||
if (process?.env?.CONFIG_PATH) {
|
||||
this.common.nodes[idx].config_path = process?.env?.CONFIG_PATH;
|
||||
}
|
||||
else if (node.Authentication && node.Authentication.configPath) {
|
||||
this.common.nodes[idx].config_path = node.Authentication.configPath;
|
||||
@ -172,10 +179,10 @@ export class ConfigService {
|
||||
}
|
||||
if (this.common.nodes[idx].ln_implementation === 'ECL' && this.common.nodes[idx].ln_api_password === '' && this.common.nodes[idx].config_path !== '') {
|
||||
try {
|
||||
const exists = fs.existsSync(this.common.nodes[idx].config_path);
|
||||
const exists = fs.existsSync(this.common.nodes[idx].config_path || '');
|
||||
if (exists) {
|
||||
try {
|
||||
const configFile = fs.readFileSync(this.common.nodes[idx].config_path, 'utf-8');
|
||||
const configFile = fs.readFileSync((this.common.nodes[idx].config_path || ''), 'utf-8');
|
||||
const iniParsed = ini.parse(configFile);
|
||||
this.common.nodes[idx].ln_api_password = iniParsed['eclair.api.password'] ? iniParsed['eclair.api.password'] : parseHocon(configFile).eclair.api.password;
|
||||
}
|
||||
@ -194,11 +201,11 @@ export class ConfigService {
|
||||
if (this.common.nodes[idx].ln_implementation === 'ECL' && this.common.nodes[idx].ln_api_password === '') {
|
||||
this.errMsg = this.errMsg + '\nPlease set config path Or api password for node index ' + node.index + ' in RTL-Config.json! It is mandatory for Eclair authentication!';
|
||||
}
|
||||
if (process.env.LN_SERVER_URL && process.env.LN_SERVER_URL.trim() !== '') {
|
||||
this.common.nodes[idx].ln_server_url = process.env.LN_SERVER_URL.endsWith('/v1') ? process.env.LN_SERVER_URL.slice(0, -3) : process.env.LN_SERVER_URL;
|
||||
if (process?.env?.LN_SERVER_URL && process?.env?.LN_SERVER_URL.trim() !== '') {
|
||||
this.common.nodes[idx].ln_server_url = process?.env?.LN_SERVER_URL.endsWith('/v1') ? process?.env?.LN_SERVER_URL.slice(0, -3) : process?.env?.LN_SERVER_URL;
|
||||
}
|
||||
else if (process.env.LND_SERVER_URL && process.env.LND_SERVER_URL.trim() !== '') {
|
||||
this.common.nodes[idx].ln_server_url = process.env.LND_SERVER_URL.endsWith('/v1') ? process.env.LND_SERVER_URL.slice(0, -3) : process.env.LND_SERVER_URL;
|
||||
else if (process?.env?.LND_SERVER_URL && process?.env?.LND_SERVER_URL.trim() !== '') {
|
||||
this.common.nodes[idx].ln_server_url = process?.env?.LND_SERVER_URL.endsWith('/v1') ? process?.env?.LND_SERVER_URL.slice(0, -3) : process?.env?.LND_SERVER_URL;
|
||||
}
|
||||
else if (node.Settings.lnServerUrl && node.Settings.lnServerUrl.trim() !== '') {
|
||||
this.common.nodes[idx].ln_server_url = node.Settings.lnServerUrl.endsWith('/v1') ? node.Settings.lnServerUrl.slice(0, -3) : node.Settings.lnServerUrl;
|
||||
@ -218,9 +225,9 @@ export class ConfigService {
|
||||
if (this.common.nodes[idx].fiat_conversion) {
|
||||
this.common.nodes[idx].currency_unit = node.Settings.currencyUnit ? node.Settings.currencyUnit : 'USD';
|
||||
}
|
||||
if (process.env.SWAP_SERVER_URL && process.env.SWAP_SERVER_URL.trim() !== '') {
|
||||
this.common.nodes[idx].swap_server_url = process.env.SWAP_SERVER_URL.endsWith('/v1') ? process.env.SWAP_SERVER_URL.slice(0, -3) : process.env.SWAP_SERVER_URL;
|
||||
this.common.nodes[idx].swap_macaroon_path = process.env.SWAP_MACAROON_PATH;
|
||||
if (process?.env?.SWAP_SERVER_URL && process?.env?.SWAP_SERVER_URL.trim() !== '') {
|
||||
this.common.nodes[idx].swap_server_url = process?.env?.SWAP_SERVER_URL.endsWith('/v1') ? process?.env?.SWAP_SERVER_URL.slice(0, -3) : process?.env?.SWAP_SERVER_URL;
|
||||
this.common.nodes[idx].swap_macaroon_path = process?.env?.SWAP_MACAROON_PATH;
|
||||
}
|
||||
else if (node.Settings.swapServerUrl && node.Settings.swapServerUrl.trim() !== '') {
|
||||
this.common.nodes[idx].swap_server_url = node.Settings.swapServerUrl.endsWith('/v1') ? node.Settings.swapServerUrl.slice(0, -3) : node.Settings.swapServerUrl;
|
||||
@ -230,9 +237,9 @@ export class ConfigService {
|
||||
this.common.nodes[idx].swap_server_url = '';
|
||||
this.common.nodes[idx].swap_macaroon_path = '';
|
||||
}
|
||||
if (process.env.BOLTZ_SERVER_URL && process.env.BOLTZ_SERVER_URL.trim() !== '') {
|
||||
this.common.nodes[idx].boltz_server_url = process.env.BOLTZ_SERVER_URL.endsWith('/v1') ? process.env.BOLTZ_SERVER_URL.slice(0, -3) : process.env.BOLTZ_SERVER_URL;
|
||||
this.common.nodes[idx].boltz_macaroon_path = process.env.BOLTZ_MACAROON_PATH;
|
||||
if (process?.env?.BOLTZ_SERVER_URL && process?.env?.BOLTZ_SERVER_URL.trim() !== '') {
|
||||
this.common.nodes[idx].boltz_server_url = process?.env?.BOLTZ_SERVER_URL.endsWith('/v1') ? process?.env?.BOLTZ_SERVER_URL.slice(0, -3) : process?.env?.BOLTZ_SERVER_URL;
|
||||
this.common.nodes[idx].boltz_macaroon_path = process?.env?.BOLTZ_MACAROON_PATH;
|
||||
}
|
||||
else if (node.Settings.boltzServerUrl && node.Settings.boltzServerUrl.trim() !== '') {
|
||||
this.common.nodes[idx].boltz_server_url = node.Settings.boltzServerUrl.endsWith('/v1') ? node.Settings.boltzServerUrl.slice(0, -3) : node.Settings.boltzServerUrl;
|
||||
@ -242,10 +249,10 @@ export class ConfigService {
|
||||
this.common.nodes[idx].boltz_server_url = '';
|
||||
this.common.nodes[idx].boltz_macaroon_path = '';
|
||||
}
|
||||
this.common.nodes[idx].enable_offers = process.env.ENABLE_OFFERS ? process.env.ENABLE_OFFERS : (node.Settings.enableOffers) ? node.Settings.enableOffers : false;
|
||||
this.common.nodes[idx].enable_peerswap = process.env.ENABLE_PEERSWAP ? process.env.ENABLE_PEERSWAP : (node.Settings.enablePeerswap) ? node.Settings.enablePeerswap : false;
|
||||
this.common.nodes[idx].bitcoind_config_path = process.env.BITCOIND_CONFIG_PATH ? process.env.BITCOIND_CONFIG_PATH : (node.Settings.bitcoindConfigPath) ? node.Settings.bitcoindConfigPath : '';
|
||||
this.common.nodes[idx].channel_backup_path = process.env.CHANNEL_BACKUP_PATH ? process.env.CHANNEL_BACKUP_PATH : (node.Settings.channelBackupPath) ? node.Settings.channelBackupPath : this.common.rtl_conf_file_path + sep + 'channels-backup' + sep + 'node-' + node.index;
|
||||
this.common.nodes[idx].enable_offers = process?.env?.ENABLE_OFFERS ? process?.env?.ENABLE_OFFERS : (node.Settings.enableOffers) ? node.Settings.enableOffers : false;
|
||||
this.common.nodes[idx].enable_peerswap = process?.env?.ENABLE_PEERSWAP ? process?.env?.ENABLE_PEERSWAP : (node.Settings.enablePeerswap) ? node.Settings.enablePeerswap : false;
|
||||
this.common.nodes[idx].bitcoind_config_path = process?.env?.BITCOIND_CONFIG_PATH ? process?.env?.BITCOIND_CONFIG_PATH : (node.Settings.bitcoindConfigPath) ? node.Settings.bitcoindConfigPath : '';
|
||||
this.common.nodes[idx].channel_backup_path = process?.env?.CHANNEL_BACKUP_PATH ? process?.env?.CHANNEL_BACKUP_PATH : (node.Settings.channelBackupPath) ? node.Settings.channelBackupPath : this.common.rtl_conf_file_path + sep + 'channels-backup' + sep + 'node-' + node.index;
|
||||
try {
|
||||
this.common.createDirectory(this.common.nodes[idx].channel_backup_path);
|
||||
const exists = fs.existsSync(this.common.nodes[idx].channel_backup_path + sep + 'channel-all.bak');
|
||||
@ -270,14 +277,14 @@ export class ConfigService {
|
||||
this.common.nodes[idx].log_file = this.common.rtl_conf_file_path + '/logs/RTL-Node-' + node.index + '.log';
|
||||
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'Config', msg: 'Node Config: ' + JSON.stringify(this.common.nodes[idx]) });
|
||||
const log_file = this.common.nodes[idx].log_file;
|
||||
if (fs.existsSync(log_file)) {
|
||||
fs.writeFile(log_file, '', () => { });
|
||||
if (fs.existsSync(log_file || '')) {
|
||||
fs.writeFile((log_file || ''), '', () => { });
|
||||
}
|
||||
else {
|
||||
try {
|
||||
const directoryName = dirname(log_file);
|
||||
const directoryName = dirname(log_file || '');
|
||||
this.common.createDirectory(directoryName);
|
||||
const createStream = fs.createWriteStream(log_file);
|
||||
const createStream = fs.createWriteStream(log_file || '');
|
||||
createStream.end();
|
||||
}
|
||||
catch (err) {
|
||||
@ -292,14 +299,14 @@ export class ConfigService {
|
||||
}
|
||||
};
|
||||
this.setSSOParams = (config) => {
|
||||
if (process.env.RTL_SSO) {
|
||||
this.common.rtl_sso = +process.env.RTL_SSO;
|
||||
if (process?.env?.RTL_SSO) {
|
||||
this.common.rtl_sso = +process?.env?.RTL_SSO;
|
||||
}
|
||||
else if (config.SSO && config.SSO.rtlSSO) {
|
||||
this.common.rtl_sso = config.SSO.rtlSSO;
|
||||
}
|
||||
if (process.env.RTL_COOKIE_PATH) {
|
||||
this.common.rtl_cookie_path = process.env.RTL_COOKIE_PATH;
|
||||
if (process?.env?.RTL_COOKIE_PATH) {
|
||||
this.common.rtl_cookie_path = process?.env?.RTL_COOKIE_PATH;
|
||||
}
|
||||
else if (config.SSO && config.SSO.rtlCookiePath) {
|
||||
this.common.rtl_cookie_path = config.SSO.rtlCookiePath;
|
||||
@ -307,8 +314,8 @@ export class ConfigService {
|
||||
else {
|
||||
this.common.rtl_cookie_path = '';
|
||||
}
|
||||
if (process.env.LOGOUT_REDIRECT_LINK) {
|
||||
this.common.logout_redirect_link = process.env.LOGOUT_REDIRECT_LINK;
|
||||
if (process?.env?.LOGOUT_REDIRECT_LINK) {
|
||||
this.common.logout_redirect_link = process?.env?.LOGOUT_REDIRECT_LINK;
|
||||
}
|
||||
else if (config.SSO && config.SSO.logoutRedirectLink) {
|
||||
this.common.logout_redirect_link = config.SSO.logoutRedirectLink;
|
||||
@ -324,15 +331,15 @@ export class ConfigService {
|
||||
};
|
||||
this.setSelectedNode = (config) => {
|
||||
if (config.defaultNodeIndex) {
|
||||
this.common.initSelectedNode = this.common.findNode(config.defaultNodeIndex);
|
||||
this.common.initSelectedNode = this.common.findNode(config.defaultNodeIndex) || {};
|
||||
}
|
||||
else {
|
||||
this.common.initSelectedNode = this.common.findNode(this.common.nodes[0].index);
|
||||
this.common.initSelectedNode = this.common.findNode(this.common.nodes[0].index) || {};
|
||||
}
|
||||
};
|
||||
this.setServerConfiguration = () => {
|
||||
try {
|
||||
this.common.rtl_conf_file_path = (process.env.RTL_CONFIG_PATH) ? process.env.RTL_CONFIG_PATH : join(this.directoryName, '../..');
|
||||
this.common.rtl_conf_file_path = (process?.env?.RTL_CONFIG_PATH) ? process?.env?.RTL_CONFIG_PATH : join(this.directoryName, '../..');
|
||||
const confFileFullPath = this.common.rtl_conf_file_path + sep + 'RTL-Config.json';
|
||||
if (!fs.existsSync(confFileFullPath)) {
|
||||
fs.writeFileSync(confFileFullPath, JSON.stringify(this.setDefaultConfig()));
|
||||
@ -347,6 +354,7 @@ export class ConfigService {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
this.setServerConfiguration();
|
||||
}
|
||||
}
|
||||
export const Config = new ConfigService();
|
||||
|
@ -1,6 +1,5 @@
|
||||
import * as fs from 'fs';
|
||||
import { join, dirname, sep } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { join, sep } from 'path';
|
||||
import { Common } from '../utils/common.js';
|
||||
import { Logger } from '../utils/logger.js';
|
||||
import { validateDocument, LNDCollection, ECLCollection, CLNCollection } from '../models/database.model.js';
|
||||
@ -8,7 +7,7 @@ export class DatabaseService {
|
||||
constructor() {
|
||||
this.common = Common;
|
||||
this.logger = Logger;
|
||||
this.dbDirectory = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'database');
|
||||
this.dbDirectory = join(this.common.db_directory_path, 'database');
|
||||
this.nodeDatabase = {};
|
||||
}
|
||||
loadDatabase(session) {
|
||||
|
@ -8,7 +8,7 @@ WORKDIR /RTL
|
||||
COPY package.json /RTL/package.json
|
||||
COPY package-lock.json /RTL/package-lock.json
|
||||
|
||||
RUN npm install
|
||||
RUN npm install --legacy-peer-deps
|
||||
|
||||
# ---------------
|
||||
# Build App
|
||||
@ -22,7 +22,7 @@ RUN npm run buildfrontend
|
||||
RUN npm run buildbackend
|
||||
|
||||
# Remove non production necessary modules
|
||||
RUN npm prune --production
|
||||
RUN npm prune --production --legacy-peer-deps
|
||||
|
||||
# ---------------
|
||||
# Release App
|
||||
|
@ -12,7 +12,7 @@ WORKDIR /RTL
|
||||
COPY package.json /RTL/package.json
|
||||
COPY package-lock.json /RTL/package-lock.json
|
||||
|
||||
RUN npm install
|
||||
RUN npm install --legacy-peer-deps
|
||||
|
||||
# ---------------
|
||||
# Build App
|
||||
@ -26,7 +26,7 @@ RUN npm run buildfrontend
|
||||
RUN npm run buildbackend
|
||||
|
||||
# Remove non production necessary modules
|
||||
RUN npm prune --production
|
||||
RUN npm prune --production --legacy-peer-deps
|
||||
|
||||
# ---------------
|
||||
# Release App
|
||||
|
@ -11,7 +11,7 @@ WORKDIR /RTL
|
||||
COPY package.json /RTL/package.json
|
||||
COPY package-lock.json /RTL/package-lock.json
|
||||
|
||||
RUN npm install
|
||||
RUN npm install --legacy-peer-deps
|
||||
|
||||
# ---------------
|
||||
# Build App
|
||||
@ -25,7 +25,7 @@ RUN npm run buildfrontend
|
||||
RUN npm run buildbackend
|
||||
|
||||
# Remove non production necessary modules
|
||||
RUN npm prune --production
|
||||
RUN npm prune --production --legacy-peer-deps
|
||||
|
||||
# ---------------
|
||||
# Release App
|
||||
|
1
frontend/167.bba0ab48235f9054.js
Normal file
1
frontend/167.bba0ab48235f9054.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
frontend/267.5508f97536cb5708.js
Normal file
1
frontend/267.5508f97536cb5708.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
frontend/315.d20113f8d2f54786.js
Normal file
1
frontend/315.d20113f8d2f54786.js
Normal file
File diff suppressed because one or more lines are too long
@ -5,7 +5,7 @@ MIT
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2022 Google LLC.
|
||||
Copyright (c) 2023 Google LLC.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@ -42,7 +42,7 @@ MIT
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2022 Google LLC.
|
||||
Copyright (c) 2023 Google LLC.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@ -121,7 +121,7 @@ as SVG and JS file types.
|
||||
In the Font Awesome Free download, the SIL OFL license applies to all icons
|
||||
packaged as web and desktop font files.
|
||||
|
||||
Copyright (c) 2022 Fonticons, Inc. (https://fontawesome.com)
|
||||
Copyright (c) 2023 Fonticons, Inc. (https://fontawesome.com)
|
||||
with Reserved Font Name: "Font Awesome".
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
@ -221,7 +221,7 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
In the Font Awesome Free download, the MIT license applies to all non-font and
|
||||
non-icon files.
|
||||
|
||||
Copyright 2022 Fonticons, Inc.
|
||||
Copyright 2023 Fonticons, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in the
|
||||
@ -290,7 +290,7 @@ as SVG and JS file types.
|
||||
In the Font Awesome Free download, the SIL OFL license applies to all icons
|
||||
packaged as web and desktop font files.
|
||||
|
||||
Copyright (c) 2022 Fonticons, Inc. (https://fontawesome.com)
|
||||
Copyright (c) 2023 Fonticons, Inc. (https://fontawesome.com)
|
||||
with Reserved Font Name: "Font Awesome".
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
@ -390,7 +390,7 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
In the Font Awesome Free download, the MIT license applies to all non-font and
|
||||
non-icon files.
|
||||
|
||||
Copyright 2022 Fonticons, Inc.
|
||||
Copyright 2023 Fonticons, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in the
|
||||
@ -459,7 +459,7 @@ as SVG and JS file types.
|
||||
In the Font Awesome Free download, the SIL OFL license applies to all icons
|
||||
packaged as web and desktop font files.
|
||||
|
||||
Copyright (c) 2022 Fonticons, Inc. (https://fontawesome.com)
|
||||
Copyright (c) 2023 Fonticons, Inc. (https://fontawesome.com)
|
||||
with Reserved Font Name: "Font Awesome".
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
@ -559,7 +559,7 @@ OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
In the Font Awesome Free download, the MIT license applies to all non-font and
|
||||
non-icon files.
|
||||
|
||||
Copyright 2022 Fonticons, Inc.
|
||||
Copyright 2023 Fonticons, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
this software and associated documentation files (the "Software"), to deal in the
|
||||
@ -601,31 +601,6 @@ Awesome, nor vice versa. **Please do not use brand logos for any purpose except
|
||||
to represent the company, product, or service to which they refer.**
|
||||
|
||||
|
||||
@material/dialog
|
||||
MIT
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2014-2020 Google, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
@ngrx/effects
|
||||
MIT
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
1
frontend/636.8e6af702364d1f11.js
Normal file
1
frontend/636.8e6af702364d1f11.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -10,10 +10,9 @@
|
||||
<link i18n-rel="" rel="mask-icon" href="assets/images/favicon-light/safari-pinned-tab.svg" color="#5bbad5">
|
||||
<meta i18n-content="" name="msapplication-TileColor" content="#da532c">
|
||||
<meta i18n-content="" name="theme-color" content="#ffffff">
|
||||
<style>html{width:100%;height:99%;line-height:1.5;overflow-x:hidden;font-family:Roboto,sans-serif!important;font-size:100%}@media only screen and (max-width: 56.25em){html{font-size:90%}}@media only screen and (max-width: 37.5em){html{font-size:80%}}body{box-sizing:border-box;height:100%;margin:0;overflow:hidden}*{margin:0;padding:0}@font-face{font-family:Roboto;src:url(Roboto-Thin.f7a95c9c5999532c.woff2) format("woff2"),url(Roboto-Thin.c13c157cb81e8ebb.woff) format("woff");font-weight:100;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-ThinItalic.b0e084abf689f393.woff2) format("woff2"),url(Roboto-ThinItalic.1111028df6cea564.woff) format("woff");font-weight:100;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Light.0e01b6cd13b3857f.woff2) format("woff2"),url(Roboto-Light.603ca9a537b88428.woff) format("woff");font-weight:300;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-LightItalic.232ef4b20215f720.woff2) format("woff2"),url(Roboto-LightItalic.1b5e142f787151c8.woff) format("woff");font-weight:300;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Regular.475ba9e4e2d63456.woff2) format("woff2"),url(Roboto-Regular.bcefbfee882bc1cb.woff) format("woff");font-weight:400;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-RegularItalic.e3a9ebdaac06bbc4.woff2) format("woff2"),url(Roboto-RegularItalic.0668fae6af0cf8c2.woff) format("woff");font-weight:400;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Medium.457532032ceb0168.woff2) format("woff2"),url(Roboto-Medium.6e1ae5f0b324a0aa.woff) format("woff");font-weight:500;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-MediumItalic.872f7060602d55d2.woff2) format("woff2"),url(Roboto-MediumItalic.e06fb533801cbb08.woff) format("woff");font-weight:500;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Bold.447291a88c067396.woff2) format("woff2"),url(Roboto-Bold.fc482e6133cf5e26.woff) format("woff");font-weight:700;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-BoldItalic.1b15168ef6fa4e16.woff2) format("woff2"),url(Roboto-BoldItalic.e26ba339b06f09f7.woff) format("woff");font-weight:700;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Black.2eaa390d458c877d.woff2) format("woff2"),url(Roboto-Black.b25f67ad8583da68.woff) format("woff");font-weight:900;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-BlackItalic.7dc03ee444552bc5.woff2) format("woff2"),url(Roboto-BlackItalic.c8dc642467cb3099.woff) format("woff");font-weight:900;font-style:italic}</style><link rel="stylesheet" href="styles.3f91aa9da8de48ca.css" media="print" onload="this.media='all'"><noscript><link rel="stylesheet" href="styles.3f91aa9da8de48ca.css"></noscript></head>
|
||||
<body>
|
||||
<style>html{width:100%;height:99%;line-height:1.5;overflow-x:hidden;font-family:Roboto,sans-serif!important;font-size:95%}@media only screen and (max-width: 56.25em){html{font-size:90%}}@media only screen and (max-width: 37.5em){html{font-size:80%}}body{box-sizing:border-box;height:100%;margin:0;overflow:hidden}*{margin:0;padding:0}@font-face{font-family:Roboto;src:url(Roboto-Thin.f7a95c9c5999532c.woff2) format("woff2"),url(Roboto-Thin.c13c157cb81e8ebb.woff) format("woff");font-weight:100;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-ThinItalic.b0e084abf689f393.woff2) format("woff2"),url(Roboto-ThinItalic.1111028df6cea564.woff) format("woff");font-weight:100;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Light.0e01b6cd13b3857f.woff2) format("woff2"),url(Roboto-Light.603ca9a537b88428.woff) format("woff");font-weight:300;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-LightItalic.232ef4b20215f720.woff2) format("woff2"),url(Roboto-LightItalic.1b5e142f787151c8.woff) format("woff");font-weight:300;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Regular.475ba9e4e2d63456.woff2) format("woff2"),url(Roboto-Regular.bcefbfee882bc1cb.woff) format("woff");font-weight:400;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-RegularItalic.e3a9ebdaac06bbc4.woff2) format("woff2"),url(Roboto-RegularItalic.0668fae6af0cf8c2.woff) format("woff");font-weight:400;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Medium.457532032ceb0168.woff2) format("woff2"),url(Roboto-Medium.6e1ae5f0b324a0aa.woff) format("woff");font-weight:500;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-MediumItalic.872f7060602d55d2.woff2) format("woff2"),url(Roboto-MediumItalic.e06fb533801cbb08.woff) format("woff");font-weight:500;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Bold.447291a88c067396.woff2) format("woff2"),url(Roboto-Bold.fc482e6133cf5e26.woff) format("woff");font-weight:700;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-BoldItalic.1b15168ef6fa4e16.woff2) format("woff2"),url(Roboto-BoldItalic.e26ba339b06f09f7.woff) format("woff");font-weight:700;font-style:italic}@font-face{font-family:Roboto;src:url(Roboto-Black.2eaa390d458c877d.woff2) format("woff2"),url(Roboto-Black.b25f67ad8583da68.woff) format("woff");font-weight:900;font-style:normal}@font-face{font-family:Roboto;src:url(Roboto-BlackItalic.7dc03ee444552bc5.woff2) format("woff2"),url(Roboto-BlackItalic.c8dc642467cb3099.woff) format("woff");font-weight:900;font-style:italic}</style><link rel="stylesheet" href="styles.a04c018645a5044a.css" media="print" onload="this.media='all'"><noscript><link rel="stylesheet" href="styles.a04c018645a5044a.css"></noscript></head>
|
||||
<body>
|
||||
<rtl-app></rtl-app>
|
||||
<script src="runtime.5ce8adc19f44fff4.js" type="module"></script><script src="polyfills.08e0233279c8a187.js" type="module"></script><script src="main.5a72aaa7ca7fb8a9.js" type="module"></script>
|
||||
<script>window.global = window;</script>
|
||||
<script src="runtime.2136ac2986261ec4.js" type="module"></script><script src="polyfills.aa01d8f6b94657cb.js" type="module"></script><script src="main.3ccfe42677016a42.js" type="module"></script>
|
||||
|
||||
</body></html>
|
1
frontend/main.3ccfe42677016a42.js
Normal file
1
frontend/main.3ccfe42677016a42.js
Normal file
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
frontend/polyfills.aa01d8f6b94657cb.js
Normal file
1
frontend/polyfills.aa01d8f6b94657cb.js
Normal file
File diff suppressed because one or more lines are too long
1
frontend/runtime.2136ac2986261ec4.js
Normal file
1
frontend/runtime.2136ac2986261ec4.js
Normal file
@ -0,0 +1 @@
|
||||
(()=>{"use strict";var e,v={},m={};function r(e){var o=m[e];if(void 0!==o)return o.exports;var t=m[e]={id:e,loaded:!1,exports:{}};return v[e].call(t.exports,t,t.exports,r),t.loaded=!0,t.exports}r.m=v,e=[],r.O=(o,t,i,f)=>{if(!t){var a=1/0;for(n=0;n<e.length;n++){for(var[t,i,f]=e[n],s=!0,l=0;l<t.length;l++)(!1&f||a>=f)&&Object.keys(r.O).every(b=>r.O[b](t[l]))?t.splice(l--,1):(s=!1,f<a&&(a=f));if(s){e.splice(n--,1);var d=i();void 0!==d&&(o=d)}}return o}f=f||0;for(var n=e.length;n>0&&e[n-1][2]>f;n--)e[n]=e[n-1];e[n]=[t,i,f]},r.d=(e,o)=>{for(var t in o)r.o(o,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:o[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((o,t)=>(r.f[t](e,o),o),[])),r.u=e=>e+"."+{167:"bba0ab48235f9054",267:"5508f97536cb5708",315:"d20113f8d2f54786",636:"8e6af702364d1f11"}[e]+".js",r.miniCssF=e=>{},r.o=(e,o)=>Object.prototype.hasOwnProperty.call(e,o),(()=>{var e={},o="RTLApp:";r.l=(t,i,f,n)=>{if(e[t])e[t].push(i);else{var a,s;if(void 0!==f)for(var l=document.getElementsByTagName("script"),d=0;d<l.length;d++){var u=l[d];if(u.getAttribute("src")==t||u.getAttribute("data-webpack")==o+f){a=u;break}}a||(s=!0,(a=document.createElement("script")).type="module",a.charset="utf-8",a.timeout=120,r.nc&&a.setAttribute("nonce",r.nc),a.setAttribute("data-webpack",o+f),a.src=r.tu(t)),e[t]=[i];var c=(g,b)=>{a.onerror=a.onload=null,clearTimeout(p);var h=e[t];if(delete e[t],a.parentNode&&a.parentNode.removeChild(a),h&&h.forEach(y=>y(b)),g)return g(b)},p=setTimeout(c.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=c.bind(null,a.onerror),a.onload=c.bind(null,a.onload),s&&document.head.appendChild(a)}}})(),r.r=e=>{typeof Symbol<"u"&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{var e;r.tt=()=>(void 0===e&&(e={createScriptURL:o=>o},typeof trustedTypes<"u"&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("angular#bundler",e))),e)})(),r.tu=e=>r.tt().createScriptURL(e),r.p="",(()=>{var e={666:0};r.f.j=(i,f)=>{var n=r.o(e,i)?e[i]:void 0;if(0!==n)if(n)f.push(n[2]);else if(666!=i){var a=new Promise((u,c)=>n=e[i]=[u,c]);f.push(n[2]=a);var s=r.p+r.u(i),l=new Error;r.l(s,u=>{if(r.o(e,i)&&(0!==(n=e[i])&&(e[i]=void 0),n)){var c=u&&("load"===u.type?"missing":u.type),p=u&&u.target&&u.target.src;l.message="Loading chunk "+i+" failed.\n("+c+": "+p+")",l.name="ChunkLoadError",l.type=c,l.request=p,n[1](l)}},"chunk-"+i,i)}else e[i]=0},r.O.j=i=>0===e[i];var o=(i,f)=>{var l,d,[n,a,s]=f,u=0;if(n.some(p=>0!==e[p])){for(l in a)r.o(a,l)&&(r.m[l]=a[l]);if(s)var c=s(r)}for(i&&i(f);u<n.length;u++)r.o(e,d=n[u])&&e[d]&&e[d][0](),e[d]=0;return r.O(c)},t=self.webpackChunkRTLApp=self.webpackChunkRTLApp||[];t.forEach(o.bind(null,0)),t.push=o.bind(null,t.push.bind(t))})()})();
|
@ -1 +0,0 @@
|
||||
(()=>{"use strict";var e,v={},m={};function r(e){var f=m[e];if(void 0!==f)return f.exports;var t=m[e]={id:e,loaded:!1,exports:{}};return v[e].call(t.exports,t,t.exports,r),t.loaded=!0,t.exports}r.m=v,e=[],r.O=(f,t,i,o)=>{if(!t){var a=1/0;for(n=0;n<e.length;n++){for(var[t,i,o]=e[n],s=!0,d=0;d<t.length;d++)(!1&o||a>=o)&&Object.keys(r.O).every(b=>r.O[b](t[d]))?t.splice(d--,1):(s=!1,o<a&&(a=o));if(s){e.splice(n--,1);var u=i();void 0!==u&&(f=u)}}return f}o=o||0;for(var n=e.length;n>0&&e[n-1][2]>o;n--)e[n]=e[n-1];e[n]=[t,i,o]},r.d=(e,f)=>{for(var t in f)r.o(f,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:f[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((f,t)=>(r.f[t](e,f),f),[])),r.u=e=>e+"."+{258:"f20f2eadd74bc704",267:"6c439858ec5d06c8",564:"dd8d5bd71c0f2cf9",636:"cb7b120de491d4ef"}[e]+".js",r.miniCssF=e=>{},r.o=(e,f)=>Object.prototype.hasOwnProperty.call(e,f),(()=>{var e={},f="RTLApp:";r.l=(t,i,o,n)=>{if(e[t])e[t].push(i);else{var a,s;if(void 0!==o)for(var d=document.getElementsByTagName("script"),u=0;u<d.length;u++){var l=d[u];if(l.getAttribute("src")==t||l.getAttribute("data-webpack")==f+o){a=l;break}}a||(s=!0,(a=document.createElement("script")).type="module",a.charset="utf-8",a.timeout=120,r.nc&&a.setAttribute("nonce",r.nc),a.setAttribute("data-webpack",f+o),a.src=r.tu(t)),e[t]=[i];var c=(g,b)=>{a.onerror=a.onload=null,clearTimeout(p);var h=e[t];if(delete e[t],a.parentNode&&a.parentNode.removeChild(a),h&&h.forEach(y=>y(b)),g)return g(b)},p=setTimeout(c.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=c.bind(null,a.onerror),a.onload=c.bind(null,a.onload),s&&document.head.appendChild(a)}}})(),r.r=e=>{typeof Symbol<"u"&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.nmd=e=>(e.paths=[],e.children||(e.children=[]),e),(()=>{var e;r.tt=()=>(void 0===e&&(e={createScriptURL:f=>f},typeof trustedTypes<"u"&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("angular#bundler",e))),e)})(),r.tu=e=>r.tt().createScriptURL(e),r.p="",(()=>{var e={666:0};r.f.j=(i,o)=>{var n=r.o(e,i)?e[i]:void 0;if(0!==n)if(n)o.push(n[2]);else if(666!=i){var a=new Promise((l,c)=>n=e[i]=[l,c]);o.push(n[2]=a);var s=r.p+r.u(i),d=new Error;r.l(s,l=>{if(r.o(e,i)&&(0!==(n=e[i])&&(e[i]=void 0),n)){var c=l&&("load"===l.type?"missing":l.type),p=l&&l.target&&l.target.src;d.message="Loading chunk "+i+" failed.\n("+c+": "+p+")",d.name="ChunkLoadError",d.type=c,d.request=p,n[1](d)}},"chunk-"+i,i)}else e[i]=0},r.O.j=i=>0===e[i];var f=(i,o)=>{var d,u,[n,a,s]=o,l=0;if(n.some(p=>0!==e[p])){for(d in a)r.o(a,d)&&(r.m[d]=a[d]);if(s)var c=s(r)}for(i&&i(o);l<n.length;l++)r.o(e,u=n[l])&&e[u]&&e[u][0](),e[u]=0;return r.O(c)},t=self.webpackChunkRTLApp=self.webpackChunkRTLApp||[];t.forEach(f.bind(null,0)),t.push=f.bind(null,t.push.bind(t))})()})();
|
File diff suppressed because one or more lines are too long
1
frontend/styles.a04c018645a5044a.css
Normal file
1
frontend/styles.a04c018645a5044a.css
Normal file
File diff suppressed because one or more lines are too long
18461
package-lock.json
generated
18461
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
22
package.json
22
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "rtl",
|
||||
"version": "0.13.3-beta",
|
||||
"version": "0.14.0-beta",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
@ -15,20 +15,12 @@
|
||||
"server": "set NODE_ENV=development&&nodemon --watch backend --watch server ./rtl.js",
|
||||
"serverUbuntu": "NODE_ENV=development nodemon --watch backend --watch server ./rtl.js",
|
||||
"testdev": "ng test --watch=true --code-coverage",
|
||||
"test": "ng test --watch=false",
|
||||
"test": "ng test --watch=false --browsers=ChromeHeadless",
|
||||
"lint": "ng lint",
|
||||
"lintServer": "eslint ./server/**/* --ext .ts"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@angular/animations": "^15.0.0",
|
||||
"@angular/common": "^15.0.0",
|
||||
"@angular/compiler": "^15.0.0",
|
||||
"@angular/core": "^15.0.0",
|
||||
"@angular/forms": "^15.0.0",
|
||||
"@angular/platform-browser": "^15.0.0",
|
||||
"@angular/platform-browser-dynamic": "^15.0.0",
|
||||
"@angular/router": "^15.0.0",
|
||||
"@ngrx/effects": "^15.0.0",
|
||||
"@ngrx/store": "^15.0.0",
|
||||
"@swimlane/ngx-charts": "^20.1.2",
|
||||
@ -41,7 +33,7 @@
|
||||
"express-session": "^1.17.3",
|
||||
"hocon-parser": "^1.0.1",
|
||||
"ini": "^3.0.1",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"ng-qrcode": "^8.0.1",
|
||||
"ngx-perfect-scrollbar-next": "^10.1.1",
|
||||
"otplib": "^12.0.1",
|
||||
@ -62,11 +54,19 @@
|
||||
"@angular-eslint/eslint-plugin-template": "^15.1.0",
|
||||
"@angular-eslint/schematics": "^15.1.0",
|
||||
"@angular-eslint/template-parser": "^15.1.0",
|
||||
"@angular/animations": "^15.0.0",
|
||||
"@angular/cdk": "^15.0.1",
|
||||
"@angular/cli": "~15.0.1",
|
||||
"@angular/common": "^15.0.0",
|
||||
"@angular/compiler": "^15.0.0",
|
||||
"@angular/compiler-cli": "^15.0.0",
|
||||
"@angular/core": "^15.0.0",
|
||||
"@angular/flex-layout": "^14.0.0-beta.41",
|
||||
"@angular/forms": "^15.0.0",
|
||||
"@angular/material": "^15.0.1",
|
||||
"@angular/platform-browser": "^15.0.0",
|
||||
"@angular/platform-browser-dynamic": "^15.0.0",
|
||||
"@angular/router": "^15.0.0",
|
||||
"@fortawesome/angular-fontawesome": "^0.12.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^6.2.1",
|
||||
"@fortawesome/free-regular-svg-icons": "^6.2.1",
|
||||
|
11
rtl.js
11
rtl.js
@ -1,8 +1,9 @@
|
||||
import http from 'http';
|
||||
import App from './backend/utils/app.js';
|
||||
import { Logger } from './backend/utils/logger.js';
|
||||
import { Common } from './backend/utils/common.js';
|
||||
import { Config } from './backend/utils/config.js'; // Follow sequence to set server configuration in time
|
||||
import { WSServer } from './backend/utils/webSocketServer.js';
|
||||
import App from './backend/utils/app.js';
|
||||
|
||||
const logger = Logger;
|
||||
const common = Common;
|
||||
@ -13,11 +14,11 @@ const onError = (error) => {
|
||||
if (error.syscall !== 'listen') { throw error; }
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
logger.log({ level: 'ERROR', fileName: 'RTL', msg: 'http://' + (common.host ? common.host : 'localhost') + ':' + common.port + ' requires elevated privileges' });
|
||||
logger.log({ level: 'ERROR', fileName: 'RTL', msg: 'http://' + ((common.host && common.host !== '') ? common.host : 'localhost') + ':' + common.port + ' requires elevated privileges' });
|
||||
process.exit(1);
|
||||
break;
|
||||
case 'EADDRINUSE':
|
||||
logger.log({ level: 'ERROR', fileName: 'RTL', msg: 'http://' + (common.host ? common.host : 'localhost') + ':' + common.port + ' is already in use' });
|
||||
logger.log({ level: 'ERROR', fileName: 'RTL', msg: 'http://' + ((common.host && common.host !== '') ? common.host : 'localhost') + ':' + common.port + ' is already in use' });
|
||||
process.exit(1);
|
||||
break;
|
||||
case 'ECONNREFUSED':
|
||||
@ -35,7 +36,7 @@ const onError = (error) => {
|
||||
};
|
||||
|
||||
const onListening = () => {
|
||||
logger.log({ level: 'INFO', fileName: 'RTL', msg: 'Server is up and running, please open the UI at http://' + (common.host ? common.host : 'localhost') + ':' + common.port + ' or your proxy configured url' });
|
||||
logger.log({ level: 'INFO', fileName: 'RTL', msg: 'Server is up and running, please open the UI at http://' + ((common.host && common.host !== '') ? common.host : 'localhost') + ':' + common.port + ' or your proxy configured url' });
|
||||
};
|
||||
|
||||
let server = http.createServer(app.getApp());
|
||||
@ -45,7 +46,7 @@ server.on('listening', onListening);
|
||||
|
||||
wsServer.mount(server);
|
||||
|
||||
if (common.host) {
|
||||
if (common.host && common.host !== '') {
|
||||
server.listen(common.port, common.host);
|
||||
} else {
|
||||
server.listen(common.port);
|
||||
|
@ -5,17 +5,41 @@ let options = null;
|
||||
const logger: LoggerService = Logger;
|
||||
const common: CommonService = Common;
|
||||
|
||||
export const listPeerChannels = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Peer Channels..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); }
|
||||
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/listPeerChannels';
|
||||
request(options).then((body) => {
|
||||
body?.map((channel) => {
|
||||
if (!channel.alias || channel.alias === '') { channel.alias = channel.peer_id.substring(0, 20); }
|
||||
const local = channel.to_us_msat || 0;
|
||||
const remote = (channel.total_msat - local) || 0;
|
||||
const total = channel.total_msat || 0;
|
||||
channel.to_them_msat = remote;
|
||||
channel.balancedness = (total === 0) ? 1 : (1 - Math.abs((local - remote) / total)).toFixed(3);
|
||||
return channel;
|
||||
});
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Peer Channels List Received', data: body });
|
||||
res.status(200).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'List Peer Channels Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
|
||||
export const listChannels = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Getting Channels..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); }
|
||||
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/listChannels';
|
||||
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/listPeerChannels';
|
||||
request(options).then((body) => {
|
||||
body?.map((channel) => {
|
||||
if (!channel.alias || channel.alias === '') { channel.alias = channel.id.substring(0, 20); }
|
||||
const local = (channel.msatoshi_to_us) ? channel.msatoshi_to_us : 0;
|
||||
const remote = (channel.msatoshi_to_them) ? channel.msatoshi_to_them : 0;
|
||||
const total = channel.msatoshi_total ? channel.msatoshi_total : 0;
|
||||
const local = channel.to_us_msat || 0;
|
||||
const remote = (channel.total_msat - local) || 0;
|
||||
const total = channel.total_msat || 0;
|
||||
channel.to_them_msat = remote;
|
||||
channel.balancedness = (total === 0) ? 1 : (1 - Math.abs((local - remote) / total)).toFixed(3);
|
||||
return channel;
|
||||
});
|
||||
|
@ -22,8 +22,8 @@ function paymentReducer(accumulator, currentPayment) {
|
||||
|
||||
function summaryReducer(accumulator, mpp) {
|
||||
if (mpp.status === 'complete') {
|
||||
accumulator.msatoshi = accumulator.msatoshi + mpp.msatoshi;
|
||||
accumulator.msatoshi_sent = accumulator.msatoshi_sent + mpp.msatoshi_sent;
|
||||
accumulator.amount_msat = accumulator.amount_msat + mpp.amount_msat;
|
||||
accumulator.amount_sent_msat = accumulator.amount_sent_msat + mpp.amount_sent_msat;
|
||||
accumulator.status = mpp.status;
|
||||
}
|
||||
if (mpp.bolt11) { accumulator.bolt11 = mpp.bolt11; }
|
||||
@ -43,10 +43,10 @@ function groupBy(payments) {
|
||||
temp.total_parts = 1;
|
||||
delete temp.partid;
|
||||
} else {
|
||||
const paySummary = curr?.reduce(summaryReducer, { msatoshi: 0, msatoshi_sent: 0, status: (curr[0] && curr[0].status) ? curr[0].status : 'failed' });
|
||||
const paySummary = curr?.reduce(summaryReducer, { amount_msat: 0, amount_sent_msat: 0, status: (curr[0] && curr[0].status) ? curr[0].status : 'failed' });
|
||||
temp = {
|
||||
is_group: true, is_expanded: false, total_parts: (curr.length ? curr.length : 0), status: paySummary.status, payment_hash: curr[0].payment_hash,
|
||||
destination: curr[0].destination, msatoshi: paySummary.msatoshi, msatoshi_sent: paySummary.msatoshi_sent, created_at: curr[0].created_at,
|
||||
destination: curr[0].destination, amount_msat: paySummary.amount_msat, amount_sent_msat: paySummary.amount_sent_msat, created_at: curr[0].created_at,
|
||||
mpps: curr
|
||||
};
|
||||
if (paySummary.bolt11) { temp.bolt11 = paySummary.bolt11; }
|
||||
@ -92,7 +92,7 @@ export const postPayment = (req, res, next) => {
|
||||
if (req.body.paymentType === 'OFFER') {
|
||||
if (req.body.saveToDB && req.body.bolt12) {
|
||||
const offerToUpdate: Offer = { bolt12: req.body.bolt12, amountMSat: (req.body.zeroAmtOffer ? 0 : req.body.amount), title: req.body.title, lastUpdatedAt: new Date(Date.now()).getTime() };
|
||||
if (req.body.vendor) { offerToUpdate['vendor'] = req.body.vendor; }
|
||||
if (req.body.issuer) { offerToUpdate['issuer'] = req.body.issuer; }
|
||||
if (req.body.description) { offerToUpdate['description'] = req.body.description; }
|
||||
// eslint-disable-next-line arrow-body-style
|
||||
return databaseService.validateDocument(CollectionsEnum.OFFERS, offerToUpdate).then((validated) => {
|
||||
|
@ -52,7 +52,7 @@ export class CLWebSocketClient {
|
||||
this.connectWithClient(clientExists);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
@ -85,15 +85,11 @@ export class CLWebSocketClient {
|
||||
};
|
||||
|
||||
clWsClt.webSocketClient.onerror = (err) => {
|
||||
if (clWsClt.selectedNode.api_version === '' || !clWsClt.selectedNode.api_version || this.common.isVersionCompatible(clWsClt.selectedNode.api_version, '0.6.0')) {
|
||||
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'ERROR', fileName: 'CLWebSocket', msg: 'Web socket error', error: err });
|
||||
const errStr = ((typeof err === 'object' && err.message) ? JSON.stringify({ error: err.message }) : (typeof err === 'object') ? JSON.stringify({ error: err }) : ('{ "error": ' + err + ' }'));
|
||||
this.wsServer.sendErrorToAllLNClients(errStr, clWsClt.selectedNode);
|
||||
clWsClt.webSocketClient.close();
|
||||
if (clWsClt.reConnect) { this.reconnet(clWsClt); }
|
||||
} else {
|
||||
clWsClt.reConnect = false;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -2,6 +2,10 @@ import request from 'request-promise';
|
||||
import { Logger, LoggerService } from '../../utils/logger.js';
|
||||
import { Common, CommonService } from '../../utils/common.js';
|
||||
import { CommonSelectedNode } from '../../models/config.model.js';
|
||||
import { createInvoiceRequestCall, listPendingInvoicesRequestCall } from './invoices.js';
|
||||
import { findRouteBetweenNodesRequestCall } from './network.js';
|
||||
import { getSentInfoFromPaymentRequest, sendPaymentToRouteRequestCall } from './payments.js';
|
||||
|
||||
let options = null;
|
||||
const logger: LoggerService = Logger;
|
||||
const common: CommonService = Common;
|
||||
@ -151,3 +155,54 @@ export const closeChannel = (req, res, next) => {
|
||||
});
|
||||
};
|
||||
|
||||
// options.form = { sourceNodeId: req.params.source, targetNodeId: req.params.target, amountMsat: req.params.amount, ignoreNodeIds: req.params.ignore };
|
||||
|
||||
export const circularRebalance = (req, res, next) => {
|
||||
const crInvDescription = 'Circular rebalancing invoice for ' + (req.body.amountMsat / 1000) + ' Sats';
|
||||
options = common.getOptions(req);
|
||||
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); }
|
||||
options.form = req.body;
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Rebalance Params', data: options.form });
|
||||
const tillToday = (Math.round(new Date(Date.now()).getTime() / 1000)).toString();
|
||||
// Check if unpaid Invoice exists already
|
||||
listPendingInvoicesRequestCall(req.session.selectedNode).then((callRes: any[]) => {
|
||||
const foundExistingInvoice = callRes.find((inv) => inv.description.includes(crInvDescription) && inv.amount === req.body.amountMsat && inv.expiry && inv.timestamp && ((inv.expiry + inv.timestamp) >= tillToday));
|
||||
// Create new invoice if doesn't exist already
|
||||
const requestCalls = foundExistingInvoice && foundExistingInvoice.serialized ?
|
||||
[findRouteBetweenNodesRequestCall(req.session.selectedNode, req.body.amountMsat, req.body.sourceNodeId, req.body.targetNodeId, req.body.ignoreNodeIds, req.body.format)] :
|
||||
[findRouteBetweenNodesRequestCall(req.session.selectedNode, req.body.amountMsat, req.body.sourceNodeId, req.body.targetNodeId, req.body.ignoreNodeIds, req.body.format), createInvoiceRequestCall(req.session.selectedNode, crInvDescription, req.body.amountMsat)];
|
||||
Promise.all(requestCalls).then((values: any[]) => {
|
||||
// eslint-disable-next-line arrow-body-style
|
||||
const routes = values[0]?.routes?.filter((route) => {
|
||||
return !((route.shortChannelIds[0] === req.body.sourceShortChannelId && route.shortChannelIds[1] === req.body.targetShortChannelId) ||
|
||||
(route.shortChannelIds[1] === req.body.sourceShortChannelId && route.shortChannelIds[0] === req.body.targetShortChannelId));
|
||||
});
|
||||
const firstRoute = routes[0].shortChannelIds.join() || '';
|
||||
const shortChannelIds = req.body.sourceShortChannelId + ',' + firstRoute + ',' + req.body.targetShortChannelId;
|
||||
const invoice = (foundExistingInvoice && foundExistingInvoice.serialized ? foundExistingInvoice.serialized : (values[1] ? values[1].serialized : '')) || '';
|
||||
const paymentHash = (foundExistingInvoice && foundExistingInvoice.paymentHash ? foundExistingInvoice.paymentHash : (values[1] ? values[1].paymentHash : '') || '');
|
||||
return sendPaymentToRouteRequestCall(req.session.selectedNode, shortChannelIds, invoice, req.body.amountMsat).then((payToRouteCallRes) => {
|
||||
// eslint-disable-next-line arrow-body-style
|
||||
setTimeout(() => {
|
||||
return getSentInfoFromPaymentRequest(req.session.selectedNode, paymentHash).then((sentInfoCallRes) => {
|
||||
const payStatus = sentInfoCallRes.length && sentInfoCallRes.length > 0 ? sentInfoCallRes[sentInfoCallRes.length - 1].status : sentInfoCallRes;
|
||||
return res.status(201).json({ flgReusingInvoice: !!foundExistingInvoice, invoice: invoice, paymentHash: paymentHash, paymentDetails: payToRouteCallRes, paymentStatus: payStatus });
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Channel Rebalance From Sent Info Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ flgReusingInvoice: !!foundExistingInvoice, invoice: invoice, paymentHash: paymentHash, paymentDetails: payToRouteCallRes, paymentStatus: err.error });
|
||||
});
|
||||
}, 3000);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Channel Rebalance From Send Payment To Route Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ flgReusingInvoice: !!foundExistingInvoice, invoice: invoice, paymentHash: paymentHash, paymentDetails: {}, paymentStatus: err.error });
|
||||
});
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Channel Rebalance From Find Routes Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ flgReusingInvoice: !!foundExistingInvoice, invoice: (foundExistingInvoice.serialized || ''), paymentHash: '', paymentDetails: {}, paymentStatus: err.error });
|
||||
});
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Channels', 'Channel Rebalance From List Pending Invoices Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ flgReusingInvoice: false, invoice: '', paymentHash: '', paymentDetails: {}, paymentStatus: err.error });
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import request from 'request-promise';
|
||||
import { Logger, LoggerService } from '../../utils/logger.js';
|
||||
import { Common, CommonService } from '../../utils/common.js';
|
||||
import { CommonSelectedNode } from 'server/models/config.model.js';
|
||||
let options = null;
|
||||
const logger: LoggerService = Logger;
|
||||
const common: CommonService = Common;
|
||||
@ -18,7 +19,7 @@ export const getReceivedPaymentInfo = (lnServerUrl, invoice) => {
|
||||
invoice.status = response.status.type;
|
||||
if (response.status && response.status.type === 'received') {
|
||||
invoice.amountSettled = response.status.amount ? Math.round(response.status.amount / 1000) : 0;
|
||||
invoice.receivedAt = response.status.receivedAt ? Math.round(response.status.receivedAt / 1000) : 0;
|
||||
invoice.receivedAt = response.status.receivedAt.unix ? response.status.receivedAt.unix : 0;
|
||||
}
|
||||
return invoice;
|
||||
}).catch((err) => {
|
||||
@ -51,6 +52,21 @@ export const getInvoice = (req, res, next) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const listPendingInvoicesRequestCall = (selectedNode: CommonSelectedNode) => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'List Pending Invoices..' });
|
||||
options = selectedNode.options;
|
||||
options.url = selectedNode.ln_server_url + '/listpendinginvoices';
|
||||
options.form = { from: 0, to: (Math.round(new Date(Date.now()).getTime() / 1000)).toString() };
|
||||
return new Promise((resolve, reject) => {
|
||||
request.post(options).then((pendingInvoicesResponse) => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Pending Invoices List ', data: pendingInvoicesResponse });
|
||||
resolve(pendingInvoicesResponse);
|
||||
}).catch((errRes) => {
|
||||
reject(common.handleError(errRes, 'Invoices', 'List Pending Invoices Error', selectedNode));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const listInvoices = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Getting List Invoices..' });
|
||||
options = common.getOptions(req);
|
||||
@ -98,18 +114,27 @@ export const listInvoices = (req, res, next) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const createInvoiceRequestCall = (selectedNode: CommonSelectedNode, description: string, amount: number) => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Creating Invoice..' });
|
||||
options = selectedNode.options;
|
||||
options.url = selectedNode.ln_server_url + '/createinvoice';
|
||||
options.form = { description: description, amountMsat: amount };
|
||||
return new Promise((resolve, reject) => {
|
||||
request.post(options).then((invResponse) => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoice Created', data: invResponse });
|
||||
if (invResponse.amount) { invResponse.amount = Math.round(invResponse.amount / 1000); }
|
||||
resolve(invResponse);
|
||||
}).catch((errRes) => {
|
||||
reject(common.handleError(errRes, 'Invoices', 'Create Invoice Error', selectedNode));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const createInvoice = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Creating Invoice..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); }
|
||||
options.url = req.session.selectedNode.ln_server_url + '/createinvoice';
|
||||
options.form = req.body;
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoice', msg: 'Invoice Created', data: body });
|
||||
if (body.amount) { body.amount = Math.round(body.amount / 1000); }
|
||||
res.status(201).json(body);
|
||||
}).catch((errRes) => {
|
||||
const err = common.handleError(errRes, 'Invoices', 'Create Invoice Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
createInvoiceRequestCall(req.session.selectedNode, req.body.description, req.body.amountMsat).then((invRes) => {
|
||||
res.status(201).json(invRes);
|
||||
}).catch((err) => res.status(err.statusCode).json({ message: err.message, error: err.error }));
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
import request from 'request-promise';
|
||||
import { Logger, LoggerService } from '../../utils/logger.js';
|
||||
import { Common, CommonService } from '../../utils/common.js';
|
||||
import { CommonSelectedNode } from 'server/models/config.model.js';
|
||||
let options = null;
|
||||
const logger: LoggerService = Logger;
|
||||
const common: CommonService = Common;
|
||||
@ -19,3 +20,26 @@ export const getNodes = (req, res, next) => {
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
});
|
||||
};
|
||||
|
||||
export const findRouteBetweenNodesRequestCall = (selectedNode: CommonSelectedNode, amountMsat: number, sourceNodeId: string, targetNodeId: string, ignoreNodeIds: string[] = [], format: string = 'shortChannelId') => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Network', msg: 'Find Route Between Nodes..' });
|
||||
options = selectedNode.options;
|
||||
options.url = selectedNode.ln_server_url + '/findroutebetweennodes';
|
||||
options.form = { amountMsat: amountMsat, sourceNodeId: sourceNodeId, targetNodeId: targetNodeId, ignoreNodeIds: ignoreNodeIds, format: format };
|
||||
return new Promise((resolve, reject) => {
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Network', msg: 'Route Lookup Between Nodes Finished', data: body });
|
||||
resolve(body);
|
||||
}).catch((errRes) => {
|
||||
reject(common.handleError(errRes, 'Network', 'Route Lookup Between Nodes Error', selectedNode));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const findRouteBetweenNodes = (req, res, next) => {
|
||||
options = common.getOptions(req);
|
||||
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); }
|
||||
findRouteBetweenNodesRequestCall(req.session.selectedNode, req.body.amountMsat, req.body.sourceNodeId, req.body.targetNodeId, req.body.ignoreNodeIds, req.body.format).then((callRes) => {
|
||||
res.status(200).json(callRes);
|
||||
}).catch((err) => res.status(err.statusCode).json({ message: err.message, error: err.error }));
|
||||
};
|
||||
|
@ -117,3 +117,28 @@ export const getSentPaymentsInformation = (req, res, next) => {
|
||||
return res.status(200).json([]);
|
||||
}
|
||||
};
|
||||
|
||||
export const sendPaymentToRouteRequestCall = (selectedNode: CommonSelectedNode, shortChannelIds: string, invoice: string, amountMsat: number) => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Creating Invoice..' });
|
||||
options = selectedNode.options;
|
||||
options.url = selectedNode.ln_server_url + '/sendtoroute';
|
||||
options.form = { shortChannelIds: shortChannelIds, amountMsat: amountMsat, invoice: invoice };
|
||||
return new Promise((resolve, reject) => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Send Payment To Route Options', data: options.form });
|
||||
request.post(options).then((body) => {
|
||||
logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Sent To Route', data: body });
|
||||
resolve(body);
|
||||
}).catch((errRes) => {
|
||||
reject(common.handleError(errRes, 'Payments', 'Send Payment To Route Error', selectedNode));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const sendPaymentToRoute = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Send Payment To Route..' });
|
||||
options = common.getOptions(req);
|
||||
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); }
|
||||
sendPaymentToRouteRequestCall(req.session.selectedNode, req.body.shortChannelIds, req.body.invoice, req.body.amountMsat).then((callRes) => {
|
||||
res.status(200).json(callRes);
|
||||
}).catch((err) => res.status(err.statusCode).json({ message: err.message, error: err.error }));
|
||||
};
|
||||
|
@ -51,7 +51,7 @@ export class ECLWebSocketClient {
|
||||
this.connectWithClient(clientExists);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
@ -97,7 +97,6 @@ export class ECLWebSocketClient {
|
||||
eclWsClt.reConnect = false;
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
public disconnect = (selectedNode: CommonSelectedNode) => {
|
||||
@ -121,13 +120,14 @@ export class ECLWebSocketClient {
|
||||
|
||||
public heartbeat = (eclWsClt) => {
|
||||
this.logger.log({ selectedNode: eclWsClt.selectedNode, level: 'DEBUG', fileName: 'ECLWebSocket', msg: 'Websocket Server Heartbeat..' });
|
||||
if (!eclWsClt.webSocketClient) return;
|
||||
if (eclWsClt.webSocketClient.readyState !== 1) return;
|
||||
if (!eclWsClt.webSocketClient) { return; }
|
||||
if (eclWsClt.webSocketClient.readyState !== 1) { return; }
|
||||
eclWsClt.webSocketClient.ping();
|
||||
setTimeout(() => {
|
||||
this.heartbeat(eclWsClt);
|
||||
}, 59 * 1000);
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
export const ECLWSClient = new ECLWebSocketClient();
|
||||
|
@ -166,6 +166,7 @@ export const postTransactions = (req, res, next) => {
|
||||
options.form = JSON.stringify(options.form);
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Send Payment Options', data: options.form });
|
||||
request.post(options).then((body) => {
|
||||
body = body.result ? body.result : body;
|
||||
if (body.payment_error) {
|
||||
const err = common.handleError(body.payment_error, 'Channels', 'Send Payment Error', req.session.selectedNode);
|
||||
return res.status(err.statusCode).json({ message: err.message, error: err.error });
|
||||
@ -196,7 +197,7 @@ export const closeChannel = (req, res, next) => {
|
||||
request.delete(options);
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Channel Close Requested' });
|
||||
res.status(202).json({ message: 'Close channel request has been submitted.' });
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'ERROR', fileName: 'Channels', msg: 'Close Channel Error', error: error.message });
|
||||
return res.status(500).json({ message: 'Close Channel Error', error: error.message });
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ export class LNDWebSocketClient {
|
||||
const newWebSocketClient = { selectedNode: selectedNode };
|
||||
this.webSocketClients.push(newWebSocketClient);
|
||||
}
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
throw new Error(err);
|
||||
}
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Database, DatabaseService } from '../../utils/database.js';
|
||||
import { Logger, LoggerService } from '../../utils/logger.js';
|
||||
import { Common, CommonService } from '../../utils/common.js';
|
||||
import { CollectionsEnum, PageSettings } from '../../models/database.model.js';
|
||||
import { CollectionsEnum } from '../../models/database.model.js';
|
||||
|
||||
const logger: LoggerService = Logger;
|
||||
const common: CommonService = Common;
|
||||
@ -9,7 +9,7 @@ const databaseService: DatabaseService = Database;
|
||||
|
||||
export const getPageSettings = (req, res, next) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Page Settings', msg: 'Getting Page Settings..' });
|
||||
databaseService.find(req.session.selectedNode, CollectionsEnum.PAGE_SETTINGS).then((settings: PageSettings) => {
|
||||
databaseService.find(req.session.selectedNode, CollectionsEnum.PAGE_SETTINGS).then((settings: any) => {
|
||||
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Page Settings', msg: 'Page Settings Received', data: settings });
|
||||
res.status(200).json(settings);
|
||||
}).catch((errRes) => {
|
||||
|
@ -2,7 +2,7 @@ export enum OfferFieldsEnum {
|
||||
BOLT12 = 'bolt12',
|
||||
AMOUNTMSAT = 'amountMSat',
|
||||
TITLE = 'title',
|
||||
VENDOR = 'vendor',
|
||||
ISSUER = 'issuer',
|
||||
DESCRIPTION = 'description'
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ export class Offer {
|
||||
public bolt12: string,
|
||||
public amountMSat: number,
|
||||
public title: string,
|
||||
public vendor?: string,
|
||||
public issuer?: string,
|
||||
public description?: string,
|
||||
public lastUpdatedAt?: number
|
||||
) { }
|
||||
|
@ -8,5 +8,4 @@ export enum ECLWSEventsEnum {
|
||||
CHANNEL_STATE_CHANGED = 'channel-state-changed',
|
||||
CHANNEL_CLOSED = 'channel-closed',
|
||||
ONION_MESSAGE_RECEIVED = 'onion-message-received'
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
import exprs from 'express';
|
||||
const { Router } = exprs;
|
||||
import { isAuthenticated } from '../../utils/authCheck.js';
|
||||
import { listChannels, openChannel, setChannelFee, closeChannel, getLocalRemoteBalance, listForwards, funderUpdatePolicy, listForwardsPaginated } from '../../controllers/cln/channels.js';
|
||||
import { listChannels, listPeerChannels, openChannel, setChannelFee, closeChannel, getLocalRemoteBalance, listForwards, funderUpdatePolicy, listForwardsPaginated } from '../../controllers/cln/channels.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get('/listChannels', isAuthenticated, listChannels);
|
||||
router.get('/listPeerChannels', isAuthenticated, listPeerChannels);
|
||||
router.post('/', isAuthenticated, openChannel);
|
||||
router.post('/setChannelFee', isAuthenticated, setChannelFee);
|
||||
router.delete('/:channelId', isAuthenticated, closeChannel);
|
||||
|
@ -1,7 +1,7 @@
|
||||
import exprs from 'express';
|
||||
const { Router } = exprs;
|
||||
import { isAuthenticated } from '../../utils/authCheck.js';
|
||||
import { getChannels, getChannelStats, openChannel, updateChannelRelayFee, closeChannel } from '../../controllers/eclair/channels.js';
|
||||
import { getChannels, getChannelStats, openChannel, updateChannelRelayFee, closeChannel, circularRebalance } from '../../controllers/eclair/channels.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
@ -9,6 +9,7 @@ router.get('/', isAuthenticated, getChannels);
|
||||
router.get('/stats', isAuthenticated, getChannelStats);
|
||||
router.post('/', isAuthenticated, openChannel);
|
||||
router.post('/updateRelayFee', isAuthenticated, updateChannelRelayFee);
|
||||
router.post('/circularRebalance', circularRebalance);
|
||||
router.delete('/', isAuthenticated, closeChannel);
|
||||
|
||||
export default router;
|
||||
|
@ -1,10 +1,10 @@
|
||||
import exprs from 'express';
|
||||
const { Router } = exprs;
|
||||
import { isAuthenticated } from '../../utils/authCheck.js';
|
||||
import { getNodes } from '../../controllers/eclair/network.js';
|
||||
import { getNodes, findRouteBetweenNodes } from '../../controllers/eclair/network.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get('/nodes/:id', isAuthenticated, getNodes);
|
||||
|
||||
router.get('/routebetweennodes', isAuthenticated, findRouteBetweenNodes);
|
||||
export default router;
|
||||
|
@ -1,13 +1,14 @@
|
||||
import exprs from 'express';
|
||||
const { Router } = exprs;
|
||||
import { isAuthenticated } from '../../utils/authCheck.js';
|
||||
import { queryPaymentRoute, decodePayment, getSentPaymentsInformation, postPayment } from '../../controllers/eclair/payments.js';
|
||||
import { queryPaymentRoute, decodePayment, getSentPaymentsInformation, postPayment, sendPaymentToRoute } from '../../controllers/eclair/payments.js';
|
||||
|
||||
const router = Router();
|
||||
|
||||
router.get('/route/', isAuthenticated, queryPaymentRoute);
|
||||
router.get('/decode/:invoice', isAuthenticated, decodePayment);
|
||||
router.post('/getsentinfos', isAuthenticated, getSentPaymentsInformation);
|
||||
router.post('/sendtoroute', isAuthenticated, sendPaymentToRoute);
|
||||
router.post('/', isAuthenticated, postPayment);
|
||||
|
||||
export default router;
|
||||
|
@ -13,7 +13,6 @@ import clnRoutes from '../routes/cln/index.js';
|
||||
import eclRoutes from '../routes/eclair/index.js';
|
||||
import { Common, CommonService } from './common.js';
|
||||
import { Logger, LoggerService } from './logger.js';
|
||||
import { Config, ConfigService } from './config.js';
|
||||
import { CLWSClient, CLWebSocketClient } from '../controllers/cln/webSocketClient.js';
|
||||
import { ECLWSClient, ECLWebSocketClient } from '../controllers/eclair/webSocketClient.js';
|
||||
import { LNDWSClient, LNDWebSocketClient } from '../controllers/lnd/webSocketClient.js';
|
||||
@ -25,7 +24,6 @@ export class ExpressApplication {
|
||||
public app = express();
|
||||
public logger: LoggerService = Logger;
|
||||
public common: CommonService = Common;
|
||||
public config: ConfigService = Config;
|
||||
public eclWsClient: ECLWebSocketClient = ECLWSClient;
|
||||
public clWsClient: CLWebSocketClient = CLWSClient;
|
||||
public lndWsClient: LNDWebSocketClient = LNDWSClient;
|
||||
@ -39,7 +37,6 @@ export class ExpressApplication {
|
||||
this.app.use(bodyParser.json({ limit: '25mb' }));
|
||||
this.app.use(bodyParser.urlencoded({ extended: false, limit: '25mb' }));
|
||||
|
||||
this.loadConfiguration();
|
||||
this.setCORS();
|
||||
this.setCSRF();
|
||||
this.setApplicationRoutes();
|
||||
@ -47,10 +44,6 @@ export class ExpressApplication {
|
||||
|
||||
public getApp = () => this.app;
|
||||
|
||||
public loadConfiguration = () => {
|
||||
this.config.setServerConfiguration();
|
||||
};
|
||||
|
||||
public setCORS = () => { CORS.mount(this.app); };
|
||||
|
||||
public setCSRF = () => { CSRF.mount(this.app); };
|
||||
|
@ -13,7 +13,8 @@ export class CommonService {
|
||||
public initSelectedNode: CommonSelectedNode = null;
|
||||
public rtl_conf_file_path = '';
|
||||
public port = 3000;
|
||||
public host = null;
|
||||
public host = '';
|
||||
public db_directory_path = join(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
||||
public rtl_pass = '';
|
||||
public flg_allow_password_update = true;
|
||||
public rtl_secret2fa = '';
|
||||
@ -236,7 +237,11 @@ export class CommonService {
|
||||
};
|
||||
|
||||
public handleError = (errRes, fileName, errMsg, selectedNode: CommonSelectedNode) => {
|
||||
const err = JSON.parse(JSON.stringify(errRes));
|
||||
let err = JSON.parse(JSON.stringify(errRes));
|
||||
if (err && err.error && Object.keys(err.error).length === 0 && errRes.error && (errRes.error.stack || errRes.error.message)) {
|
||||
errRes.error = errRes.error.stack || errRes.error.message;
|
||||
err = JSON.parse(JSON.stringify(errRes));
|
||||
}
|
||||
if (!selectedNode) { selectedNode = this.initSelectedNode; }
|
||||
switch (selectedNode.ln_implementation) {
|
||||
case 'LND':
|
||||
@ -270,7 +275,7 @@ export class CommonService {
|
||||
if (err.options && err.options.headers) { delete err.options.headers; }
|
||||
break;
|
||||
}
|
||||
this.logger.log({ selectedNode: selectedNode, level: 'ERROR', fileName: fileName, msg: errMsg, error: (typeof err === 'object' ? JSON.stringify(err) : (typeof err === 'string') ? err : 'Unknown Error') });
|
||||
this.logger.log({ selectedNode: selectedNode, level: 'ERROR', fileName: fileName, msg: errMsg, error: (typeof err === 'object' ? JSON.stringify(err) : err) });
|
||||
let newErrorObj = { statusCode: 500, message: '', error: '' };
|
||||
if (err.code && err.code === 'ENOENT') {
|
||||
newErrorObj = {
|
||||
@ -330,7 +335,7 @@ export class CommonService {
|
||||
try {
|
||||
this.cookie_value = fs.readFileSync(this.rtl_cookie_path, 'utf-8');
|
||||
} catch (err) {
|
||||
this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while reading cookie: \n' + err });
|
||||
this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Something went wrong while reading cookie: \n' + err });
|
||||
throw new Error(err);
|
||||
}
|
||||
} else {
|
||||
@ -340,7 +345,7 @@ export class CommonService {
|
||||
fs.writeFileSync(this.rtl_cookie_path, crypto.randomBytes(64).toString('hex'));
|
||||
this.cookie_value = fs.readFileSync(this.rtl_cookie_path, 'utf-8');
|
||||
} catch (err) {
|
||||
this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while reading the cookie: \n' + err });
|
||||
this.logger.log({ selectedNode: this.initSelectedNode, level: 'ERROR', fileName: 'Common', msg: 'Something went wrong while reading the cookie: \n' + err });
|
||||
throw new Error(err);
|
||||
}
|
||||
}
|
||||
@ -441,6 +446,7 @@ export class CommonService {
|
||||
if (selNode && selNode.index) {
|
||||
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'PORT: ' + this.port });
|
||||
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'HOST: ' + this.host });
|
||||
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'DB_DIRECTORY_PATH: ' + this.db_directory_path });
|
||||
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'SSO: ' + this.rtl_sso });
|
||||
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'DEFAULT NODE INDEX: ' + selNode.index });
|
||||
this.logger.log({ selectedNode: selNode, level: 'INFO', fileName: 'Config Setup Variable', msg: 'INDEX: ' + selNode.index });
|
||||
|
@ -17,38 +17,46 @@ export class ConfigService {
|
||||
private common: CommonService = Common;
|
||||
private logger: LoggerService = Logger;
|
||||
|
||||
constructor() { }
|
||||
constructor() {
|
||||
this.setServerConfiguration();
|
||||
}
|
||||
|
||||
private setDefaultConfig = () => {
|
||||
const homeDir = os.userInfo().homedir;
|
||||
let macaroonPath = '';
|
||||
let configPath = '';
|
||||
let channelBackupPath = '';
|
||||
let dbPath = '';
|
||||
switch (this.platform) {
|
||||
case 'win32':
|
||||
macaroonPath = homeDir + '\\AppData\\Local\\Lnd\\data\\chain\\bitcoin\\mainnet';
|
||||
configPath = homeDir + '\\AppData\\Local\\Lnd\\lnd.conf';
|
||||
channelBackupPath = homeDir + '\\backup\\node-1';
|
||||
dbPath = homeDir + '\\database\\node-1';
|
||||
break;
|
||||
case 'darwin':
|
||||
macaroonPath = homeDir + '/Library/Application Support/Lnd/data/chain/bitcoin/mainnet';
|
||||
configPath = homeDir + '/Library/Application Support/Lnd/lnd.conf';
|
||||
channelBackupPath = homeDir + '/backup/node-1';
|
||||
dbPath = homeDir + '/database/node-1';
|
||||
break;
|
||||
case 'linux':
|
||||
macaroonPath = homeDir + '/.lnd/data/chain/bitcoin/mainnet';
|
||||
configPath = homeDir + '/.lnd/lnd.conf';
|
||||
channelBackupPath = homeDir + '/backup/node-1';
|
||||
dbPath = homeDir + '/database/node-1';
|
||||
break;
|
||||
default:
|
||||
macaroonPath = '';
|
||||
configPath = '';
|
||||
channelBackupPath = '';
|
||||
dbPath = '';
|
||||
break;
|
||||
}
|
||||
const configData = {
|
||||
port: '3000',
|
||||
defaultNodeIndex: 1,
|
||||
dbDirectoryPath: dbPath,
|
||||
SSO: {
|
||||
rtlSSO: 0,
|
||||
rtlCookiePath: '',
|
||||
@ -76,7 +84,7 @@ export class ConfigService {
|
||||
}
|
||||
]
|
||||
};
|
||||
if (+process.env.RTL_SSO === 0 || configData.SSO.rtlSSO === 0) {
|
||||
if ((process?.env?.RTL_SSO && +process?.env?.RTL_SSO === 0) || configData.SSO.rtlSSO === 0) {
|
||||
configData['multiPass'] = 'password';
|
||||
}
|
||||
return configData;
|
||||
@ -95,7 +103,7 @@ export class ConfigService {
|
||||
|
||||
private updateLogByLevel = () => {
|
||||
let updateLogFlag = false;
|
||||
this.common.rtl_conf_file_path = process.env.RTL_CONFIG_PATH ? process.env.RTL_CONFIG_PATH : join(this.directoryName, '../..');
|
||||
this.common.rtl_conf_file_path = process?.env?.RTL_CONFIG_PATH ? process?.env?.RTL_CONFIG_PATH : join(this.directoryName, '../..');
|
||||
try {
|
||||
const RTLConfFile = this.common.rtl_conf_file_path + sep + 'RTL-Config.json';
|
||||
const config = JSON.parse(fs.readFileSync(RTLConfFile, 'utf-8'));
|
||||
@ -115,9 +123,9 @@ export class ConfigService {
|
||||
};
|
||||
|
||||
private validateNodeConfig = (config) => {
|
||||
if ((+process.env.RTL_SSO === 0) || (typeof process.env.RTL_SSO === 'undefined' && +config.SSO.rtlSSO === 0)) {
|
||||
if (process.env.APP_PASSWORD && process.env.APP_PASSWORD.trim() !== '') {
|
||||
this.common.rtl_pass = this.hash.update(process.env.APP_PASSWORD).digest('hex');
|
||||
if ((process?.env?.RTL_SSO && +process?.env?.RTL_SSO === 0) || (typeof process?.env?.RTL_SSO === 'undefined' && +config.SSO.rtlSSO === 0)) {
|
||||
if (process?.env?.APP_PASSWORD && process?.env?.APP_PASSWORD.trim() !== '') {
|
||||
this.common.rtl_pass = this.hash.update(process?.env?.APP_PASSWORD).digest('hex');
|
||||
this.common.flg_allow_password_update = false;
|
||||
} else if (config.multiPassHashed && config.multiPassHashed !== '') {
|
||||
this.common.rtl_pass = config.multiPassHashed;
|
||||
@ -128,21 +136,22 @@ export class ConfigService {
|
||||
}
|
||||
this.common.rtl_secret2fa = config.secret2fa;
|
||||
} else {
|
||||
if (process.env.APP_PASSWORD && process.env.APP_PASSWORD.trim() !== '') {
|
||||
if (process?.env?.APP_PASSWORD && process?.env?.APP_PASSWORD.trim() !== '') {
|
||||
this.errMsg = this.errMsg + '\nRTL Password cannot be set with SSO. Please set SSO as 0 or remove password.';
|
||||
}
|
||||
}
|
||||
this.common.port = (process.env.PORT) ? this.normalizePort(process.env.PORT) : (config.port) ? this.normalizePort(config.port) : 3000;
|
||||
this.common.host = (process.env.HOST) ? process.env.HOST : (config.host) ? config.host : null;
|
||||
this.common.port = (process?.env?.PORT) ? this.normalizePort(process?.env?.PORT) : (config.port) ? this.normalizePort(config.port) : 3000;
|
||||
this.common.host = (process?.env?.HOST) ? process?.env?.HOST : (config.host) ? config.host : null;
|
||||
this.common.db_directory_path = (process?.env?.DB_DIRECTORY_PATH) ? process?.env?.DB_DIRECTORY_PATH : (config.dbDirectoryPath) ? config.dbDirectoryPath : join(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
||||
if (config.nodes && config.nodes.length > 0) {
|
||||
config.nodes.forEach((node, idx) => {
|
||||
this.common.nodes[idx] = {};
|
||||
this.common.nodes[idx].index = node.index;
|
||||
this.common.nodes[idx].ln_node = node.lnNode;
|
||||
this.common.nodes[idx].ln_implementation = (process.env.LN_IMPLEMENTATION) ? process.env.LN_IMPLEMENTATION : node.lnImplementation ? node.lnImplementation : 'LND';
|
||||
this.common.nodes[idx].ln_implementation = (process?.env?.LN_IMPLEMENTATION) ? process?.env?.LN_IMPLEMENTATION : node.lnImplementation ? node.lnImplementation : 'LND';
|
||||
if (this.common.nodes[idx].ln_implementation === 'CLT') { this.common.nodes[idx].ln_implementation = 'CLN'; }
|
||||
if (this.common.nodes[idx].ln_implementation !== 'ECL' && process.env.MACAROON_PATH && process.env.MACAROON_PATH.trim() !== '') {
|
||||
this.common.nodes[idx].macaroon_path = process.env.MACAROON_PATH;
|
||||
if (this.common.nodes[idx].ln_implementation !== 'ECL' && process?.env?.MACAROON_PATH && process?.env?.MACAROON_PATH.trim() !== '') {
|
||||
this.common.nodes[idx].macaroon_path = process?.env?.MACAROON_PATH;
|
||||
} else if (this.common.nodes[idx].ln_implementation !== 'ECL' && node.Authentication && node.Authentication.macaroonPath && node.Authentication.macaroonPath.trim() !== '') {
|
||||
this.common.nodes[idx].macaroon_path = node.Authentication.macaroonPath;
|
||||
} else if (this.common.nodes[idx].ln_implementation !== 'ECL') {
|
||||
@ -150,16 +159,16 @@ export class ConfigService {
|
||||
}
|
||||
|
||||
if (this.common.nodes[idx].ln_implementation === 'ECL') {
|
||||
if (process.env.LN_API_PASSWORD) {
|
||||
this.common.nodes[idx].ln_api_password = process.env.LN_API_PASSWORD;
|
||||
if (process?.env?.LN_API_PASSWORD) {
|
||||
this.common.nodes[idx].ln_api_password = process?.env?.LN_API_PASSWORD;
|
||||
} else if (node.Authentication && node.Authentication.lnApiPassword) {
|
||||
this.common.nodes[idx].ln_api_password = node.Authentication.lnApiPassword;
|
||||
} else {
|
||||
this.common.nodes[idx].ln_api_password = '';
|
||||
}
|
||||
}
|
||||
if (process.env.CONFIG_PATH) {
|
||||
this.common.nodes[idx].config_path = process.env.CONFIG_PATH;
|
||||
if (process?.env?.CONFIG_PATH) {
|
||||
this.common.nodes[idx].config_path = process?.env?.CONFIG_PATH;
|
||||
} else if (node.Authentication && node.Authentication.configPath) {
|
||||
this.common.nodes[idx].config_path = node.Authentication.configPath;
|
||||
} else {
|
||||
@ -167,10 +176,10 @@ export class ConfigService {
|
||||
}
|
||||
if (this.common.nodes[idx].ln_implementation === 'ECL' && this.common.nodes[idx].ln_api_password === '' && this.common.nodes[idx].config_path !== '') {
|
||||
try {
|
||||
const exists = fs.existsSync(this.common.nodes[idx].config_path);
|
||||
const exists = fs.existsSync(this.common.nodes[idx].config_path || '');
|
||||
if (exists) {
|
||||
try {
|
||||
const configFile = fs.readFileSync(this.common.nodes[idx].config_path, 'utf-8');
|
||||
const configFile = fs.readFileSync((this.common.nodes[idx].config_path || ''), 'utf-8');
|
||||
const iniParsed = ini.parse(configFile);
|
||||
this.common.nodes[idx].ln_api_password = iniParsed['eclair.api.password'] ? iniParsed['eclair.api.password'] : parseHocon(configFile).eclair.api.password;
|
||||
} catch (err) {
|
||||
@ -187,10 +196,10 @@ export class ConfigService {
|
||||
this.errMsg = this.errMsg + '\nPlease set config path Or api password for node index ' + node.index + ' in RTL-Config.json! It is mandatory for Eclair authentication!';
|
||||
}
|
||||
|
||||
if (process.env.LN_SERVER_URL && process.env.LN_SERVER_URL.trim() !== '') {
|
||||
this.common.nodes[idx].ln_server_url = process.env.LN_SERVER_URL.endsWith('/v1') ? process.env.LN_SERVER_URL.slice(0, -3) : process.env.LN_SERVER_URL;
|
||||
} else if (process.env.LND_SERVER_URL && process.env.LND_SERVER_URL.trim() !== '') {
|
||||
this.common.nodes[idx].ln_server_url = process.env.LND_SERVER_URL.endsWith('/v1') ? process.env.LND_SERVER_URL.slice(0, -3) : process.env.LND_SERVER_URL;
|
||||
if (process?.env?.LN_SERVER_URL && process?.env?.LN_SERVER_URL.trim() !== '') {
|
||||
this.common.nodes[idx].ln_server_url = process?.env?.LN_SERVER_URL.endsWith('/v1') ? process?.env?.LN_SERVER_URL.slice(0, -3) : process?.env?.LN_SERVER_URL;
|
||||
} else if (process?.env?.LND_SERVER_URL && process?.env?.LND_SERVER_URL.trim() !== '') {
|
||||
this.common.nodes[idx].ln_server_url = process?.env?.LND_SERVER_URL.endsWith('/v1') ? process?.env?.LND_SERVER_URL.slice(0, -3) : process?.env?.LND_SERVER_URL;
|
||||
} else if (node.Settings.lnServerUrl && node.Settings.lnServerUrl.trim() !== '') {
|
||||
this.common.nodes[idx].ln_server_url = node.Settings.lnServerUrl.endsWith('/v1') ? node.Settings.lnServerUrl.slice(0, -3) : node.Settings.lnServerUrl;
|
||||
} else if (node.Settings.lndServerUrl && node.Settings.lndServerUrl.trim() !== '') {
|
||||
@ -207,9 +216,9 @@ export class ConfigService {
|
||||
if (this.common.nodes[idx].fiat_conversion) {
|
||||
this.common.nodes[idx].currency_unit = node.Settings.currencyUnit ? node.Settings.currencyUnit : 'USD';
|
||||
}
|
||||
if (process.env.SWAP_SERVER_URL && process.env.SWAP_SERVER_URL.trim() !== '') {
|
||||
this.common.nodes[idx].swap_server_url = process.env.SWAP_SERVER_URL.endsWith('/v1') ? process.env.SWAP_SERVER_URL.slice(0, -3) : process.env.SWAP_SERVER_URL;
|
||||
this.common.nodes[idx].swap_macaroon_path = process.env.SWAP_MACAROON_PATH;
|
||||
if (process?.env?.SWAP_SERVER_URL && process?.env?.SWAP_SERVER_URL.trim() !== '') {
|
||||
this.common.nodes[idx].swap_server_url = process?.env?.SWAP_SERVER_URL.endsWith('/v1') ? process?.env?.SWAP_SERVER_URL.slice(0, -3) : process?.env?.SWAP_SERVER_URL;
|
||||
this.common.nodes[idx].swap_macaroon_path = process?.env?.SWAP_MACAROON_PATH;
|
||||
} else if (node.Settings.swapServerUrl && node.Settings.swapServerUrl.trim() !== '') {
|
||||
this.common.nodes[idx].swap_server_url = node.Settings.swapServerUrl.endsWith('/v1') ? node.Settings.swapServerUrl.slice(0, -3) : node.Settings.swapServerUrl;
|
||||
this.common.nodes[idx].swap_macaroon_path = node.Authentication.swapMacaroonPath ? node.Authentication.swapMacaroonPath : '';
|
||||
@ -217,9 +226,9 @@ export class ConfigService {
|
||||
this.common.nodes[idx].swap_server_url = '';
|
||||
this.common.nodes[idx].swap_macaroon_path = '';
|
||||
}
|
||||
if (process.env.BOLTZ_SERVER_URL && process.env.BOLTZ_SERVER_URL.trim() !== '') {
|
||||
this.common.nodes[idx].boltz_server_url = process.env.BOLTZ_SERVER_URL.endsWith('/v1') ? process.env.BOLTZ_SERVER_URL.slice(0, -3) : process.env.BOLTZ_SERVER_URL;
|
||||
this.common.nodes[idx].boltz_macaroon_path = process.env.BOLTZ_MACAROON_PATH;
|
||||
if (process?.env?.BOLTZ_SERVER_URL && process?.env?.BOLTZ_SERVER_URL.trim() !== '') {
|
||||
this.common.nodes[idx].boltz_server_url = process?.env?.BOLTZ_SERVER_URL.endsWith('/v1') ? process?.env?.BOLTZ_SERVER_URL.slice(0, -3) : process?.env?.BOLTZ_SERVER_URL;
|
||||
this.common.nodes[idx].boltz_macaroon_path = process?.env?.BOLTZ_MACAROON_PATH;
|
||||
} else if (node.Settings.boltzServerUrl && node.Settings.boltzServerUrl.trim() !== '') {
|
||||
this.common.nodes[idx].boltz_server_url = node.Settings.boltzServerUrl.endsWith('/v1') ? node.Settings.boltzServerUrl.slice(0, -3) : node.Settings.boltzServerUrl;
|
||||
this.common.nodes[idx].boltz_macaroon_path = node.Authentication.boltzMacaroonPath ? node.Authentication.boltzMacaroonPath : '';
|
||||
@ -227,10 +236,10 @@ export class ConfigService {
|
||||
this.common.nodes[idx].boltz_server_url = '';
|
||||
this.common.nodes[idx].boltz_macaroon_path = '';
|
||||
}
|
||||
this.common.nodes[idx].enable_offers = process.env.ENABLE_OFFERS ? process.env.ENABLE_OFFERS : (node.Settings.enableOffers) ? node.Settings.enableOffers : false;
|
||||
this.common.nodes[idx].enable_peerswap = process.env.ENABLE_PEERSWAP ? process.env.ENABLE_PEERSWAP : (node.Settings.enablePeerswap) ? node.Settings.enablePeerswap : false;
|
||||
this.common.nodes[idx].bitcoind_config_path = process.env.BITCOIND_CONFIG_PATH ? process.env.BITCOIND_CONFIG_PATH : (node.Settings.bitcoindConfigPath) ? node.Settings.bitcoindConfigPath : '';
|
||||
this.common.nodes[idx].channel_backup_path = process.env.CHANNEL_BACKUP_PATH ? process.env.CHANNEL_BACKUP_PATH : (node.Settings.channelBackupPath) ? node.Settings.channelBackupPath : this.common.rtl_conf_file_path + sep + 'channels-backup' + sep + 'node-' + node.index;
|
||||
this.common.nodes[idx].enable_offers = process?.env?.ENABLE_OFFERS ? process?.env?.ENABLE_OFFERS : (node.Settings.enableOffers) ? node.Settings.enableOffers : false;
|
||||
this.common.nodes[idx].enable_peerswap = process?.env?.ENABLE_PEERSWAP ? process?.env?.ENABLE_PEERSWAP : (node.Settings.enablePeerswap) ? node.Settings.enablePeerswap : false;
|
||||
this.common.nodes[idx].bitcoind_config_path = process?.env?.BITCOIND_CONFIG_PATH ? process?.env?.BITCOIND_CONFIG_PATH : (node.Settings.bitcoindConfigPath) ? node.Settings.bitcoindConfigPath : '';
|
||||
this.common.nodes[idx].channel_backup_path = process?.env?.CHANNEL_BACKUP_PATH ? process?.env?.CHANNEL_BACKUP_PATH : (node.Settings.channelBackupPath) ? node.Settings.channelBackupPath : this.common.rtl_conf_file_path + sep + 'channels-backup' + sep + 'node-' + node.index;
|
||||
try {
|
||||
this.common.createDirectory(this.common.nodes[idx].channel_backup_path);
|
||||
const exists = fs.existsSync(this.common.nodes[idx].channel_backup_path + sep + 'channel-all.bak');
|
||||
@ -252,13 +261,13 @@ export class ConfigService {
|
||||
this.common.nodes[idx].log_file = this.common.rtl_conf_file_path + '/logs/RTL-Node-' + node.index + '.log';
|
||||
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'Config', msg: 'Node Config: ' + JSON.stringify(this.common.nodes[idx]) });
|
||||
const log_file = this.common.nodes[idx].log_file;
|
||||
if (fs.existsSync(log_file)) {
|
||||
fs.writeFile(log_file, '', () => { });
|
||||
if (fs.existsSync(log_file || '')) {
|
||||
fs.writeFile((log_file || ''), '', () => { });
|
||||
} else {
|
||||
try {
|
||||
const directoryName = dirname(log_file);
|
||||
const directoryName = dirname(log_file || '');
|
||||
this.common.createDirectory(directoryName);
|
||||
const createStream = fs.createWriteStream(log_file);
|
||||
const createStream = fs.createWriteStream(log_file || '');
|
||||
createStream.end();
|
||||
} catch (err) {
|
||||
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while creating log file ' + log_file + ': \n' + err });
|
||||
@ -271,22 +280,22 @@ export class ConfigService {
|
||||
};
|
||||
|
||||
private setSSOParams = (config) => {
|
||||
if (process.env.RTL_SSO) {
|
||||
this.common.rtl_sso = +process.env.RTL_SSO;
|
||||
if (process?.env?.RTL_SSO) {
|
||||
this.common.rtl_sso = +process?.env?.RTL_SSO;
|
||||
} else if (config.SSO && config.SSO.rtlSSO) {
|
||||
this.common.rtl_sso = config.SSO.rtlSSO;
|
||||
}
|
||||
|
||||
if (process.env.RTL_COOKIE_PATH) {
|
||||
this.common.rtl_cookie_path = process.env.RTL_COOKIE_PATH;
|
||||
if (process?.env?.RTL_COOKIE_PATH) {
|
||||
this.common.rtl_cookie_path = process?.env?.RTL_COOKIE_PATH;
|
||||
} else if (config.SSO && config.SSO.rtlCookiePath) {
|
||||
this.common.rtl_cookie_path = config.SSO.rtlCookiePath;
|
||||
} else {
|
||||
this.common.rtl_cookie_path = '';
|
||||
}
|
||||
|
||||
if (process.env.LOGOUT_REDIRECT_LINK) {
|
||||
this.common.logout_redirect_link = process.env.LOGOUT_REDIRECT_LINK;
|
||||
if (process?.env?.LOGOUT_REDIRECT_LINK) {
|
||||
this.common.logout_redirect_link = process?.env?.LOGOUT_REDIRECT_LINK;
|
||||
} else if (config.SSO && config.SSO.logoutRedirectLink) {
|
||||
this.common.logout_redirect_link = config.SSO.logoutRedirectLink;
|
||||
}
|
||||
@ -302,15 +311,15 @@ export class ConfigService {
|
||||
|
||||
private setSelectedNode = (config) => {
|
||||
if (config.defaultNodeIndex) {
|
||||
this.common.initSelectedNode = this.common.findNode(config.defaultNodeIndex);
|
||||
this.common.initSelectedNode = this.common.findNode(config.defaultNodeIndex) || {};
|
||||
} else {
|
||||
this.common.initSelectedNode = this.common.findNode(this.common.nodes[0].index);
|
||||
this.common.initSelectedNode = this.common.findNode(this.common.nodes[0].index) || {};
|
||||
}
|
||||
};
|
||||
|
||||
public setServerConfiguration = () => {
|
||||
try {
|
||||
this.common.rtl_conf_file_path = (process.env.RTL_CONFIG_PATH) ? process.env.RTL_CONFIG_PATH : join(this.directoryName, '../..');
|
||||
this.common.rtl_conf_file_path = (process?.env?.RTL_CONFIG_PATH) ? process?.env?.RTL_CONFIG_PATH : join(this.directoryName, '../..');
|
||||
const confFileFullPath = this.common.rtl_conf_file_path + sep + 'RTL-Config.json';
|
||||
if (!fs.existsSync(confFileFullPath)) {
|
||||
fs.writeFileSync(confFileFullPath, JSON.stringify(this.setDefaultConfig()));
|
||||
@ -319,7 +328,7 @@ export class ConfigService {
|
||||
this.updateLogByLevel();
|
||||
this.validateNodeConfig(config);
|
||||
this.setSelectedNode(config);
|
||||
} catch (err) {
|
||||
} catch (err: any) {
|
||||
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'ERROR', fileName: 'Config', msg: 'Something went wrong while configuring the node server: \n' + err });
|
||||
throw new Error(err);
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
import * as fs from 'fs';
|
||||
import { join, dirname, sep } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { join, sep } from 'path';
|
||||
import { Common, CommonService } from '../utils/common.js';
|
||||
import { Logger, LoggerService } from '../utils/logger.js';
|
||||
import { Collections, CollectionsEnum, validateDocument, LNDCollection, ECLCollection, CLNCollection } from '../models/database.model.js';
|
||||
@ -10,7 +9,7 @@ export class DatabaseService {
|
||||
|
||||
public common: CommonService = Common;
|
||||
public logger: LoggerService = Logger;
|
||||
public dbDirectory = join(dirname(fileURLToPath(import.meta.url)), '..', '..', 'database');
|
||||
public dbDirectory = join(this.common.db_directory_path, 'database');
|
||||
public nodeDatabase: { id?: { adapter: DatabaseAdapter, data: Collections } } = {};
|
||||
|
||||
constructor() { }
|
||||
|
@ -30,6 +30,7 @@ import { RootReducer } from './store/rtl.reducers';
|
||||
import { LNDReducer } from './lnd/store/lnd.reducers';
|
||||
import { CLNReducer } from './cln/store/cln.reducers';
|
||||
import { ECLReducer } from './eclair/store/ecl.reducers';
|
||||
import { HOUR_SECONDS } from './shared/services/consts-enums-functions';
|
||||
|
||||
let isDevEnvironemt = false;
|
||||
if (isDevMode()) { isDevEnvironemt = true; }
|
||||
@ -41,7 +42,7 @@ if (isDevMode()) { isDevEnvironemt = true; }
|
||||
routing,
|
||||
LayoutModule,
|
||||
HammerModule,
|
||||
UserIdleModule.forRoot({ idle: 3590, timeout: 10, ping: 12000 }), // One hour => 3590 + 10 = 3600
|
||||
UserIdleModule.forRoot({ idle: (HOUR_SECONDS - 10), timeout: 10, ping: 12000 }),
|
||||
StoreModule.forRoot(
|
||||
{ root: RootReducer, lnd: LNDReducer, cln: CLNReducer, ecl: ECLReducer },
|
||||
{
|
||||
@ -56,7 +57,7 @@ if (isDevMode()) { isDevEnvironemt = true; }
|
||||
declarations: [AppComponent],
|
||||
providers: [
|
||||
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
|
||||
AuthGuard, SessionService, DataService, WebSocketClientService, LoopService, CommonService, BoltzService
|
||||
SessionService, DataService, WebSocketClientService, LoopService, CommonService, BoltzService
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
|
@ -26,35 +26,35 @@ type PathMatch = 'full' | 'prefix' | undefined;
|
||||
|
||||
export const routes: Routes = [
|
||||
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'login' },
|
||||
{ path: 'lnd', loadChildren: () => import('./lnd/lnd.module').then((childModule) => childModule.LNDModule), canActivate: [AuthGuard] },
|
||||
{ path: 'cln', loadChildren: () => import('./cln/cln.module').then((childModule) => childModule.CLNModule), canActivate: [AuthGuard] },
|
||||
{ path: 'ecl', loadChildren: () => import('./eclair/ecl.module').then((childModule) => childModule.ECLModule), canActivate: [AuthGuard] },
|
||||
{ path: 'lnd', loadChildren: () => import('./lnd/lnd.module').then((childModule) => childModule.LNDModule), canActivate: [AuthGuard()] },
|
||||
{ path: 'cln', loadChildren: () => import('./cln/cln.module').then((childModule) => childModule.CLNModule), canActivate: [AuthGuard()] },
|
||||
{ path: 'ecl', loadChildren: () => import('./eclair/ecl.module').then((childModule) => childModule.ECLModule), canActivate: [AuthGuard()] },
|
||||
{
|
||||
path: 'settings', component: SettingsComponent, canActivate: [AuthGuard], children: [
|
||||
path: 'settings', component: SettingsComponent, canActivate: [AuthGuard()], children: [
|
||||
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'app' },
|
||||
{ path: 'app', component: AppSettingsComponent, canActivate: [AuthGuard] },
|
||||
{ path: 'auth', component: AuthSettingsComponent, canActivate: [AuthGuard] },
|
||||
{ path: 'bconfig', component: BitcoinConfigComponent, canActivate: [AuthGuard] }
|
||||
{ path: 'app', component: AppSettingsComponent, canActivate: [AuthGuard()] },
|
||||
{ path: 'auth', component: AuthSettingsComponent, canActivate: [AuthGuard()] },
|
||||
{ path: 'bconfig', component: BitcoinConfigComponent, canActivate: [AuthGuard()] }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'config', component: NodeConfigComponent, canActivate: [AuthGuard], children: [
|
||||
path: 'config', component: NodeConfigComponent, canActivate: [AuthGuard()], children: [
|
||||
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'nodesettings' },
|
||||
{ path: 'nodesettings', component: NodeSettingsComponent, canActivate: [AuthGuard] },
|
||||
{ path: 'pglayout', component: PageSettingsComponent, canActivate: [AuthGuard] },
|
||||
{ path: 'nodesettings', component: NodeSettingsComponent, canActivate: [AuthGuard()] },
|
||||
{ path: 'pglayout', component: PageSettingsComponent, canActivate: [AuthGuard()] },
|
||||
{
|
||||
path: 'services', component: ServicesSettingsComponent, canActivate: [AuthGuard], children: [
|
||||
path: 'services', component: ServicesSettingsComponent, canActivate: [AuthGuard()], children: [
|
||||
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'loop' },
|
||||
{ path: 'loop', component: LoopServiceSettingsComponent, canActivate: [AuthGuard] },
|
||||
{ path: 'boltz', component: BoltzServiceSettingsComponent, canActivate: [AuthGuard] }
|
||||
{ path: 'loop', component: LoopServiceSettingsComponent, canActivate: [AuthGuard()] },
|
||||
{ path: 'boltz', component: BoltzServiceSettingsComponent, canActivate: [AuthGuard()] }
|
||||
]
|
||||
},
|
||||
{ path: 'experimental', component: ExperimentalSettingsComponent, canActivate: [AuthGuard] },
|
||||
{ path: 'lnconfig', component: LNPConfigComponent, canActivate: [AuthGuard] }
|
||||
{ path: 'experimental', component: ExperimentalSettingsComponent, canActivate: [AuthGuard()] },
|
||||
{ path: 'lnconfig', component: LNPConfigComponent, canActivate: [AuthGuard()] }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'services', component: LNServicesComponent, canActivate: [AuthGuard], children: [
|
||||
path: 'services', component: LNServicesComponent, canActivate: [AuthGuard()], children: [
|
||||
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'loop' },
|
||||
{ path: 'loop', pathMatch: <PathMatch>'full', redirectTo: 'loop/loopout' },
|
||||
{ path: 'loop/:selTab', component: LoopComponent },
|
||||
@ -69,4 +69,4 @@ export const routes: Routes = [
|
||||
];
|
||||
|
||||
// Export const routing: ModuleWithProviders<RouterModule> = RouterModule.forRoot(routes, { enableTracing: true });
|
||||
export const routing: ModuleWithProviders<RouterModule> = RouterModule.forRoot(routes, { scrollPositionRestoration: 'enabled' });
|
||||
export const routing: ModuleWithProviders<RouterModule> = RouterModule.forRoot(routes, { onSameUrlNavigation: 'reload', scrollPositionRestoration: 'enabled' });
|
||||
|
@ -58,6 +58,7 @@ import { CLNOffersTableComponent } from './transactions/offers/offers-table/offe
|
||||
import { CLNOfferBookmarksTableComponent } from './transactions/offers/offer-bookmarks-table/offer-bookmarks-table.component';
|
||||
import { CLNLiquidityAdsListComponent } from './liquidity-ads/liquidity-ads-list/liquidity-ads-list.component';
|
||||
import { CLNOpenLiquidityChannelComponent } from './liquidity-ads/open-liquidity-channel-modal/open-liquidity-channel-modal.component';
|
||||
import { CLNChannelActiveHTLCsTableComponent } from './peers-channels/channels/channels-tables/channel-active-htlcs-table/channel-active-htlcs-table.component';
|
||||
|
||||
import { CLNUnlockedGuard } from '../shared/services/auth.guard';
|
||||
|
||||
@ -121,11 +122,10 @@ import { CLNUnlockedGuard } from '../shared/services/auth.guard';
|
||||
CLNOffersTableComponent,
|
||||
CLNOfferBookmarksTableComponent,
|
||||
CLNLiquidityAdsListComponent,
|
||||
CLNOpenLiquidityChannelComponent
|
||||
],
|
||||
providers: [
|
||||
CLNUnlockedGuard
|
||||
CLNOpenLiquidityChannelComponent,
|
||||
CLNChannelActiveHTLCsTableComponent
|
||||
],
|
||||
providers: [],
|
||||
bootstrap: [CLNRootComponent]
|
||||
})
|
||||
export class CLNModule { }
|
||||
|
@ -24,6 +24,7 @@ import { CLNVerifyComponent } from './sign-verify-message/verify/verify.componen
|
||||
import { CLNForwardingHistoryComponent } from './routing/forwarding-history/forwarding-history.component';
|
||||
import { CLNFailedTransactionsComponent } from './routing/failed-transactions/failed-transactions.component';
|
||||
import { CLNRoutingPeersComponent } from './routing/routing-peers/routing-peers.component';
|
||||
import { CLNChannelActiveHTLCsTableComponent } from './peers-channels/channels/channels-tables/channel-active-htlcs-table/channel-active-htlcs-table.component';
|
||||
|
||||
import { CLNReportsComponent } from './reports/reports.component';
|
||||
import { CLNRoutingReportComponent } from './reports/routing/routing-report.component';
|
||||
@ -43,69 +44,70 @@ export const ClnRoutes: Routes = [
|
||||
path: '', component: CLNRootComponent,
|
||||
children: [
|
||||
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'home' },
|
||||
{ path: 'home', component: CLNHomeComponent, canActivate: [CLNUnlockedGuard] },
|
||||
{ path: 'home', component: CLNHomeComponent, canActivate: [CLNUnlockedGuard()] },
|
||||
{
|
||||
path: 'onchain', component: CLNOnChainComponent, canActivate: [CLNUnlockedGuard], children: [
|
||||
path: 'onchain', component: CLNOnChainComponent, canActivate: [CLNUnlockedGuard()], children: [
|
||||
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'receive/utxos' },
|
||||
{ path: 'receive/:selTab', component: CLNOnChainReceiveComponent, canActivate: [CLNUnlockedGuard] },
|
||||
{ path: 'send/:selTab', component: CLNOnChainSendComponent, data: { sweepAll: false }, canActivate: [CLNUnlockedGuard] },
|
||||
{ path: 'sweep/:selTab', component: CLNOnChainSendComponent, data: { sweepAll: true }, canActivate: [CLNUnlockedGuard] }
|
||||
{ path: 'receive/:selTab', component: CLNOnChainReceiveComponent, canActivate: [CLNUnlockedGuard()] },
|
||||
{ path: 'send/:selTab', component: CLNOnChainSendComponent, data: { sweepAll: false }, canActivate: [CLNUnlockedGuard()] },
|
||||
{ path: 'sweep/:selTab', component: CLNOnChainSendComponent, data: { sweepAll: true }, canActivate: [CLNUnlockedGuard()] }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'connections', component: CLNConnectionsComponent, canActivate: [CLNUnlockedGuard], children: [
|
||||
path: 'connections', component: CLNConnectionsComponent, canActivate: [CLNUnlockedGuard()], children: [
|
||||
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'channels' },
|
||||
{
|
||||
path: 'channels', component: CLNChannelsTablesComponent, canActivate: [CLNUnlockedGuard], children: [
|
||||
path: 'channels', component: CLNChannelsTablesComponent, canActivate: [CLNUnlockedGuard()], children: [
|
||||
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'open' },
|
||||
{ path: 'open', component: CLNChannelOpenTableComponent, canActivate: [CLNUnlockedGuard] },
|
||||
{ path: 'pending', component: CLNChannelPendingTableComponent, canActivate: [CLNUnlockedGuard] }
|
||||
{ path: 'open', component: CLNChannelOpenTableComponent, canActivate: [CLNUnlockedGuard()] },
|
||||
{ path: 'pending', component: CLNChannelPendingTableComponent, canActivate: [CLNUnlockedGuard()] },
|
||||
{ path: 'activehtlcs', component: CLNChannelActiveHTLCsTableComponent, canActivate: [CLNUnlockedGuard()] }
|
||||
]
|
||||
},
|
||||
{ path: 'peers', component: CLNPeersComponent, data: { sweepAll: false }, canActivate: [CLNUnlockedGuard] }
|
||||
{ path: 'peers', component: CLNPeersComponent, data: { sweepAll: false }, canActivate: [CLNUnlockedGuard()] }
|
||||
]
|
||||
},
|
||||
{ path: 'liquidityads', component: CLNLiquidityAdsListComponent, canActivate: [CLNUnlockedGuard] },
|
||||
{ path: 'liquidityads', component: CLNLiquidityAdsListComponent, canActivate: [CLNUnlockedGuard()] },
|
||||
{
|
||||
path: 'transactions', component: CLNTransactionsComponent, canActivate: [CLNUnlockedGuard], children: [
|
||||
path: 'transactions', component: CLNTransactionsComponent, canActivate: [CLNUnlockedGuard()], children: [
|
||||
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'payments' },
|
||||
{ path: 'payments', component: CLNLightningPaymentsComponent, canActivate: [CLNUnlockedGuard] },
|
||||
{ path: 'invoices', component: CLNLightningInvoicesTableComponent, canActivate: [CLNUnlockedGuard] },
|
||||
{ path: 'offers', component: CLNOffersTableComponent, canActivate: [CLNUnlockedGuard] },
|
||||
{ path: 'offrBookmarks', component: CLNOfferBookmarksTableComponent, canActivate: [CLNUnlockedGuard] }
|
||||
{ path: 'payments', component: CLNLightningPaymentsComponent, canActivate: [CLNUnlockedGuard()] },
|
||||
{ path: 'invoices', component: CLNLightningInvoicesTableComponent, canActivate: [CLNUnlockedGuard()] },
|
||||
{ path: 'offers', component: CLNOffersTableComponent, canActivate: [CLNUnlockedGuard()] },
|
||||
{ path: 'offrBookmarks', component: CLNOfferBookmarksTableComponent, canActivate: [CLNUnlockedGuard()] }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'messages', component: CLNSignVerifyMessageComponent, canActivate: [CLNUnlockedGuard], children: [
|
||||
path: 'messages', component: CLNSignVerifyMessageComponent, canActivate: [CLNUnlockedGuard()], children: [
|
||||
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'sign' },
|
||||
{ path: 'sign', component: CLNSignComponent, canActivate: [CLNUnlockedGuard] },
|
||||
{ path: 'verify', component: CLNVerifyComponent, canActivate: [CLNUnlockedGuard] }
|
||||
{ path: 'sign', component: CLNSignComponent, canActivate: [CLNUnlockedGuard()] },
|
||||
{ path: 'verify', component: CLNVerifyComponent, canActivate: [CLNUnlockedGuard()] }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'routing', component: CLNRoutingComponent, canActivate: [CLNUnlockedGuard], children: [
|
||||
path: 'routing', component: CLNRoutingComponent, canActivate: [CLNUnlockedGuard()], children: [
|
||||
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'forwardinghistory' },
|
||||
{ path: 'forwardinghistory', component: CLNForwardingHistoryComponent, canActivate: [CLNUnlockedGuard] },
|
||||
{ path: 'failedtransactions', component: CLNFailedTransactionsComponent, canActivate: [CLNUnlockedGuard] },
|
||||
{ path: 'localfail', component: CLNLocalFailedTransactionsComponent, canActivate: [CLNUnlockedGuard] },
|
||||
{ path: 'routingpeers', component: CLNRoutingPeersComponent, canActivate: [CLNUnlockedGuard] }
|
||||
{ path: 'forwardinghistory', component: CLNForwardingHistoryComponent, canActivate: [CLNUnlockedGuard()] },
|
||||
{ path: 'failedtransactions', component: CLNFailedTransactionsComponent, canActivate: [CLNUnlockedGuard()] },
|
||||
{ path: 'localfail', component: CLNLocalFailedTransactionsComponent, canActivate: [CLNUnlockedGuard()] },
|
||||
{ path: 'routingpeers', component: CLNRoutingPeersComponent, canActivate: [CLNUnlockedGuard()] }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'reports', component: CLNReportsComponent, canActivate: [CLNUnlockedGuard], children: [
|
||||
path: 'reports', component: CLNReportsComponent, canActivate: [CLNUnlockedGuard()], children: [
|
||||
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'routingreport' },
|
||||
{ path: 'routingreport', component: CLNRoutingReportComponent, canActivate: [CLNUnlockedGuard] },
|
||||
{ path: 'transactions', component: CLNTransactionsReportComponent, canActivate: [CLNUnlockedGuard] }
|
||||
{ path: 'routingreport', component: CLNRoutingReportComponent, canActivate: [CLNUnlockedGuard()] },
|
||||
{ path: 'transactions', component: CLNTransactionsReportComponent, canActivate: [CLNUnlockedGuard()] }
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'graph', component: CLNGraphComponent, canActivate: [CLNUnlockedGuard], children: [
|
||||
path: 'graph', component: CLNGraphComponent, canActivate: [CLNUnlockedGuard()], children: [
|
||||
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'lookups' },
|
||||
{ path: 'lookups', component: CLNLookupsComponent, canActivate: [CLNUnlockedGuard] },
|
||||
{ path: 'queryroutes', component: CLNQueryRoutesComponent, canActivate: [CLNUnlockedGuard] }
|
||||
{ path: 'lookups', component: CLNLookupsComponent, canActivate: [CLNUnlockedGuard()] },
|
||||
{ path: 'queryroutes', component: CLNQueryRoutesComponent, canActivate: [CLNUnlockedGuard()] }
|
||||
]
|
||||
},
|
||||
{ path: 'rates', component: CLNNetworkInfoComponent, canActivate: [CLNUnlockedGuard] },
|
||||
{ path: 'rates', component: CLNNetworkInfoComponent, canActivate: [CLNUnlockedGuard()] },
|
||||
{ path: '**', component: NotFoundComponent },
|
||||
{ path: 'network', redirectTo: 'rates' },
|
||||
{ path: 'wallet', redirectTo: 'home' },
|
||||
|
@ -19,12 +19,12 @@
|
||||
<mat-divider class="my-1"></mat-divider>
|
||||
<div fxLayout="column">
|
||||
<h4 class="font-bold-500">Last Update</h4>
|
||||
<span class="foreground-secondary-text">{{lookupResult[0]?.last_update }}</span>
|
||||
<span class="foreground-secondary-text">{{(lookupResult[0]?.last_update * 1000) | date:'dd/MMM/y HH:mm'}}</span>
|
||||
</div>
|
||||
<mat-divider class="my-1"></mat-divider>
|
||||
<div fxLayout="column">
|
||||
<h4 class="font-bold-500">Amount (mSats)</h4>
|
||||
<span class="foreground-secondary-text">{{lookupResult[0]?.amount_msat}}</span>
|
||||
<h4 class="font-bold-500">Amount (Sats)</h4>
|
||||
<span class="foreground-secondary-text">{{lookupResult[0]?.amount_msat / 1000 | number:'1.0-0'}}</span>
|
||||
</div>
|
||||
<mat-divider class="my-1"></mat-divider>
|
||||
<div fxLayout="column">
|
||||
@ -32,6 +32,11 @@
|
||||
<span class="foreground-secondary-text">{{lookupResult[0]?.base_fee_millisatoshi | number}}</span>
|
||||
</div>
|
||||
<mat-divider class="my-1"></mat-divider>
|
||||
<div fxLayout="column">
|
||||
<h4 class="font-bold-500">Fee/Millionth</h4>
|
||||
<span class="foreground-secondary-text">{{lookupResult[0]?.fee_per_millionth | number}}</span>
|
||||
</div>
|
||||
<mat-divider class="my-1"></mat-divider>
|
||||
<div fxLayout="column">
|
||||
<h4 class="font-bold-500">Channel Flags</h4>
|
||||
<span class="foreground-secondary-text">{{lookupResult[0]?.channel_flags | number}}</span>
|
||||
@ -42,24 +47,14 @@
|
||||
<span class="foreground-secondary-text">{{lookupResult[0]?.delay | number}}</span>
|
||||
</div>
|
||||
<mat-divider class="my-1"></mat-divider>
|
||||
<div fxLayout="column">
|
||||
<h4 class="font-bold-500">Destination</h4>
|
||||
<span class="foreground-secondary-text">{{lookupResult[0]?.destination}}</span>
|
||||
</div>
|
||||
<mat-divider class="my-1"></mat-divider>
|
||||
<div fxLayout="column">
|
||||
<h4 class="font-bold-500">Fee/Millionth</h4>
|
||||
<span class="foreground-secondary-text">{{lookupResult[0]?.fee_per_millionth | number}}</span>
|
||||
</div>
|
||||
<mat-divider class="my-1"></mat-divider>
|
||||
<div fxLayout="column">
|
||||
<h4 class="font-bold-500">Max Htlc (mSat)</h4>
|
||||
<span class="foreground-secondary-text">{{lookupResult[0]?.htlc_maximum_msat}}</span>
|
||||
<span class="foreground-secondary-text">{{lookupResult[0]?.htlc_maximum_msat | number}}</span>
|
||||
</div>
|
||||
<mat-divider class="my-1"></mat-divider>
|
||||
<div fxLayout="column">
|
||||
<h4 class="font-bold-500">Min Htlc (mSat)</h4>
|
||||
<span class="foreground-secondary-text">{{lookupResult[0]?.htlc_minimum_msat}}</span>
|
||||
<span class="foreground-secondary-text">{{lookupResult[0]?.htlc_minimum_msat | number}}</span>
|
||||
</div>
|
||||
<mat-divider class="my-1"></mat-divider>
|
||||
<div fxLayout="column">
|
||||
@ -73,13 +68,13 @@
|
||||
</div>
|
||||
<mat-divider class="my-1"></mat-divider>
|
||||
<div fxLayout="column">
|
||||
<h4 class="font-bold-500">Satoshis</h4>
|
||||
<span class="foreground-secondary-text">{{lookupResult[0]?.satoshis | number}}</span>
|
||||
<h4 class="font-bold-500">Source</h4>
|
||||
<span class="foreground-secondary-text">{{lookupResult[0]?.source}}</span>
|
||||
</div>
|
||||
<mat-divider class="my-1"></mat-divider>
|
||||
<div fxLayout="column">
|
||||
<h4 class="font-bold-500">Source</h4>
|
||||
<span class="foreground-secondary-text">{{lookupResult[0]?.source}}</span>
|
||||
<h4 class="font-bold-500">Destination</h4>
|
||||
<span class="foreground-secondary-text">{{lookupResult[0]?.destination}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div fxLayout="column" fxFlex="49" fxLayoutAlign="start stretch" class="mt-1 bordered-box padding-gap-large">
|
||||
@ -104,8 +99,8 @@
|
||||
</div>
|
||||
<mat-divider class="my-1"></mat-divider>
|
||||
<div fxLayout="column">
|
||||
<h4 class="font-bold-500">Amount (mSats)</h4>
|
||||
<span class="foreground-secondary-text">{{lookupResult[1]?.amount_msat}}</span>
|
||||
<h4 class="font-bold-500">Amount (Sats)</h4>
|
||||
<span class="foreground-secondary-text">{{lookupResult[1]?.amount_msat / 1000 | number:'1.0-0'}}</span>
|
||||
</div>
|
||||
<mat-divider class="my-1"></mat-divider>
|
||||
<div fxLayout="column">
|
||||
@ -113,6 +108,11 @@
|
||||
<span class="foreground-secondary-text">{{lookupResult[1]?.base_fee_millisatoshi | number}}</span>
|
||||
</div>
|
||||
<mat-divider class="my-1"></mat-divider>
|
||||
<div fxLayout="column">
|
||||
<h4 class="font-bold-500">Fee/Millionth</h4>
|
||||
<span class="foreground-secondary-text">{{lookupResult[1]?.fee_per_millionth | number}}</span>
|
||||
</div>
|
||||
<mat-divider class="my-1"></mat-divider>
|
||||
<div fxLayout="column">
|
||||
<h4 class="font-bold-500">Channel Flags</h4>
|
||||
<span class="foreground-secondary-text">{{lookupResult[1]?.channel_flags | number}}</span>
|
||||
@ -123,24 +123,14 @@
|
||||
<span class="foreground-secondary-text">{{lookupResult[1]?.delay | number}}</span>
|
||||
</div>
|
||||
<mat-divider class="my-1"></mat-divider>
|
||||
<div fxLayout="column">
|
||||
<h4 class="font-bold-500">Destination</h4>
|
||||
<span class="foreground-secondary-text">{{lookupResult[1]?.destination}}</span>
|
||||
</div>
|
||||
<mat-divider class="my-1"></mat-divider>
|
||||
<div fxLayout="column">
|
||||
<h4 class="font-bold-500">Fee/Millionth</h4>
|
||||
<span class="foreground-secondary-text">{{lookupResult[1]?.fee_per_millionth | number}}</span>
|
||||
</div>
|
||||
<mat-divider class="my-1"></mat-divider>
|
||||
<div fxLayout="column">
|
||||
<h4 class="font-bold-500">Max Htlc (mSat)</h4>
|
||||
<span class="foreground-secondary-text">{{lookupResult[1]?.htlc_maximum_msat}}</span>
|
||||
<span class="foreground-secondary-text">{{lookupResult[1]?.htlc_maximum_msat | number}}</span>
|
||||
</div>
|
||||
<mat-divider class="my-1"></mat-divider>
|
||||
<div fxLayout="column">
|
||||
<h4 class="font-bold-500">Min Htlc (mSat)</h4>
|
||||
<span class="foreground-secondary-text">{{lookupResult[1]?.htlc_minimum_msat}}</span>
|
||||
<span class="foreground-secondary-text">{{lookupResult[1]?.htlc_minimum_msat | number}}</span>
|
||||
</div>
|
||||
<mat-divider class="my-1"></mat-divider>
|
||||
<div fxLayout="column">
|
||||
@ -154,13 +144,13 @@
|
||||
</div>
|
||||
<mat-divider class="my-1"></mat-divider>
|
||||
<div fxLayout="column">
|
||||
<h4 class="font-bold-500">Satoshis</h4>
|
||||
<span class="foreground-secondary-text">{{lookupResult[1]?.satoshis | number}}</span>
|
||||
<h4 class="font-bold-500">Source</h4>
|
||||
<span class="foreground-secondary-text">{{lookupResult[1]?.source}}</span>
|
||||
</div>
|
||||
<mat-divider class="my-1"></mat-divider>
|
||||
<div fxLayout="column">
|
||||
<h4 class="font-bold-500">Source</h4>
|
||||
<span class="foreground-secondary-text">{{lookupResult[1]?.source}}</span>
|
||||
<h4 class="font-bold-500">Destination</h4>
|
||||
<span class="foreground-secondary-text">{{lookupResult[1]?.destination}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -34,17 +34,18 @@ export class CLNLookupsComponent implements OnInit, OnDestroy {
|
||||
public faSearch = faSearch;
|
||||
public screenSize = '';
|
||||
public screenSizeEnum = ScreenSizeEnum;
|
||||
private unSubs: Array<Subject<void>> = [new Subject()];
|
||||
private unSubs: Array<Subject<void>> = [new Subject(), new Subject()];
|
||||
|
||||
constructor(private logger: LoggerService, private commonService: CommonService, private store: Store<RTLState>, private actions: Actions) {
|
||||
this.screenSize = this.commonService.getScreenSize();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.actions.
|
||||
pipe(
|
||||
takeUntil(this.unSubs[0]),
|
||||
filter((action) => (action.type === CLNActions.SET_LOOKUP_CLN || action.type === CLNActions.UPDATE_API_CALL_STATUS_CLN))
|
||||
if (window.history.state && window.history.state.lookupType) {
|
||||
this.selectedFieldId = +window.history.state.lookupType || 0;
|
||||
this.lookupKey = window.history.state.lookupValue || '';
|
||||
}
|
||||
this.actions.pipe(takeUntil(this.unSubs[0]), filter((action) => (action.type === CLNActions.SET_LOOKUP_CLN || action.type === CLNActions.UPDATE_API_CALL_STATUS_CLN))
|
||||
).subscribe((resLookup: any) => {
|
||||
if (resLookup.type === CLNActions.SET_LOOKUP_CLN) {
|
||||
this.flgLoading[0] = true;
|
||||
|
@ -42,10 +42,14 @@
|
||||
<th *matHeaderCellDef mat-header-cell>
|
||||
<div class="bordered-box table-actions-select btn-action" fxLayoutAlign="center center">Actions</div>
|
||||
</th>
|
||||
<td *matCellDef="let address" mat-cell>
|
||||
<span fxLayoutAlign="end center">
|
||||
<button mat-stroked-button class="btn-action-copy" color="primary" type="button" tabindex="1" rtlClipboard [payload]="lookupResult?.nodeid + '@' + address.address + ':' + address.port" (copied)="onCopyNodeURI($event)">Copy Node URI</button>
|
||||
</span>
|
||||
<td *matCellDef="let address" mat-cell fxLayoutAlign="end center">
|
||||
<div class="bordered-box table-actions-select" fxLayoutAlign="center center">
|
||||
<mat-select placeholder="Actions" tabindex="1" class="mr-0">
|
||||
<mat-select-trigger></mat-select-trigger>
|
||||
<mat-option (click)="onConnectNode(address)">Connect</mat-option>
|
||||
<mat-option rtlClipboard [payload]="lookupResult?.nodeid + '@' + address.address + ':' + address.port" (copied)="onCopyNodeURI($event)">Copy URI</mat-option>
|
||||
</mat-select>
|
||||
</div>
|
||||
</td>
|
||||
</ng-container>
|
||||
<tr *matHeaderRowDef="displayedColumns;" mat-header-row></tr>
|
||||
|
@ -1,4 +1,10 @@
|
||||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { StoreModule } from '@ngrx/store';
|
||||
|
||||
import { RootReducer } from '../../../../store/rtl.reducers';
|
||||
import { LNDReducer } from '../../../../lnd/store/lnd.reducers';
|
||||
import { CLNReducer } from '../../../../cln/store/cln.reducers';
|
||||
import { ECLReducer } from '../../../../eclair/store/ecl.reducers';
|
||||
import { LoggerService } from '../../../../shared/services/logger.service';
|
||||
import { SharedModule } from '../../../../shared/shared.module';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
@ -14,7 +20,8 @@ describe('CLNNodeLookupComponent', () => {
|
||||
declarations: [CLNNodeLookupComponent],
|
||||
imports: [
|
||||
SharedModule,
|
||||
BrowserAnimationsModule
|
||||
BrowserAnimationsModule,
|
||||
StoreModule.forRoot({ root: RootReducer, lnd: LNDReducer, cln: CLNReducer, ecl: ECLReducer })
|
||||
],
|
||||
providers: [LoggerService]
|
||||
}).
|
||||
|
@ -1,26 +1,36 @@
|
||||
import { Component, OnInit, Input, ViewChild } from '@angular/core';
|
||||
import { Component, OnInit, Input, ViewChild, OnDestroy } from '@angular/core';
|
||||
import { Subject } from 'rxjs';
|
||||
import { takeUntil } from 'rxjs/operators';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { MatSort } from '@angular/material/sort';
|
||||
import { MatTableDataSource } from '@angular/material/table';
|
||||
|
||||
import { LookupNode } from '../../../../shared/models/clnModels';
|
||||
import { Address, Balance, GetInfo, LookupNode } from '../../../../shared/models/clnModels';
|
||||
import { NODE_FEATURES_CLN } from '../../../../shared/services/consts-enums-functions';
|
||||
import { LoggerService } from '../../../../shared/services/logger.service';
|
||||
import { RTLState } from '../../../../store/rtl.state';
|
||||
import { openAlert } from '../../../../store/rtl.actions';
|
||||
import { CLNConnectPeerComponent } from '../../../peers-channels/connect-peer/connect-peer.component';
|
||||
import { nodeInfoAndBalance } from '../../../store/cln.selector';
|
||||
|
||||
@Component({
|
||||
selector: 'rtl-cln-node-lookup',
|
||||
templateUrl: './node-lookup.component.html',
|
||||
styleUrls: ['./node-lookup.component.scss']
|
||||
})
|
||||
export class CLNNodeLookupComponent implements OnInit {
|
||||
export class CLNNodeLookupComponent implements OnInit, OnDestroy {
|
||||
|
||||
@ViewChild(MatSort, { static: false }) sort: MatSort | undefined;
|
||||
@Input() lookupResult: LookupNode;
|
||||
public featureDescriptions: string[] = [];
|
||||
public addresses: any = new MatTableDataSource([]);
|
||||
public displayedColumns = ['type', 'address', 'port', 'actions'];
|
||||
public information: GetInfo = {};
|
||||
public availableBalance = 0;
|
||||
private unSubs: Array<Subject<void>> = [new Subject()];
|
||||
|
||||
constructor(private logger: LoggerService, private snackBar: MatSnackBar) { }
|
||||
constructor(private logger: LoggerService, private snackBar: MatSnackBar, private store: Store<RTLState>) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.addresses = this.lookupResult && this.lookupResult.addresses ? new MatTableDataSource<any>([...this.lookupResult.addresses]) : new MatTableDataSource([]);
|
||||
@ -28,13 +38,32 @@ export class CLNNodeLookupComponent implements OnInit {
|
||||
this.addresses.sort = this.sort;
|
||||
this.addresses.sortingDataAccessor = (data: any, sortHeaderId: string) => ((data[sortHeaderId] && isNaN(data[sortHeaderId])) ? data[sortHeaderId].toLocaleLowerCase() : data[sortHeaderId] ? +data[sortHeaderId] : null);
|
||||
if (this.lookupResult.features && this.lookupResult.features.trim() !== '') {
|
||||
this.lookupResult.features = this.lookupResult.features.substring(this.lookupResult.features.length - 40);
|
||||
const featureHex = parseInt(this.lookupResult.features, 16);
|
||||
NODE_FEATURES_CLN.forEach((feature) => {
|
||||
if (!!(featureHex & ((1 << feature.range.min) | (1 << feature.range.max)))) {
|
||||
this.featureDescriptions.push(feature.description + '\n');
|
||||
if (featureHex & (1 << feature.range.min)) {
|
||||
this.featureDescriptions.push('Mandatory: ' + feature.description + '\n');
|
||||
} else if (featureHex & (1 << feature.range.max)) {
|
||||
this.featureDescriptions.push('Optional: ' + feature.description + '\n');
|
||||
}
|
||||
});
|
||||
}
|
||||
this.store.select(nodeInfoAndBalance).pipe(takeUntil(this.unSubs[0])).
|
||||
subscribe((infoBalSelector: { information: GetInfo, balance: Balance }) => {
|
||||
this.information = infoBalSelector.information;
|
||||
this.availableBalance = infoBalSelector.balance.totalBalance || 0;
|
||||
});
|
||||
}
|
||||
|
||||
onConnectNode(address: Address) {
|
||||
this.store.dispatch(openAlert({
|
||||
payload: {
|
||||
data: {
|
||||
message: { peer: { id: this.lookupResult.nodeid + '@' + address.address + ':' + address.port }, information: this.information, balance: this.availableBalance },
|
||||
component: CLNConnectPeerComponent
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
onCopyNodeURI(payload: string) {
|
||||
@ -42,4 +71,11 @@ export class CLNNodeLookupComponent implements OnInit {
|
||||
this.logger.info('Copied Text: ' + payload);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.unSubs.forEach((completeSub) => {
|
||||
completeSub.next(<any>null);
|
||||
completeSub.complete();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -58,7 +58,7 @@
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="msatoshi">
|
||||
<th *matHeaderCellDef mat-header-cell mat-sort-header arrowPosition="before">Amount (Sats)</th>
|
||||
<td *matCellDef="let hop" mat-cell><span fxLayoutAlign="end center">{{hop?.msatoshi/1000 | number}}</span></td>
|
||||
<td *matCellDef="let hop" mat-cell><span fxLayoutAlign="end center">{{hop?.amount_msat/1000 | number}}</span></td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="actions">
|
||||
<th *matHeaderCellDef mat-header-cell>
|
||||
|
@ -90,10 +90,9 @@ export class CLNQueryRoutesComponent implements OnInit, OnDestroy {
|
||||
[{ key: 'id', value: selHop.id, title: 'ID', width: 100, type: DataTypeEnum.STRING }],
|
||||
[{ key: 'channel', value: selHop.channel, title: 'Channel', width: 50, type: DataTypeEnum.STRING },
|
||||
{ key: 'alias', value: selHop.alias, title: 'Peer Alias', width: 50, type: DataTypeEnum.STRING }],
|
||||
[{ key: 'msatoshi', value: selHop.msatoshi, title: 'mSatoshi', width: 50, type: DataTypeEnum.NUMBER },
|
||||
{ key: 'amount_msat', value: selHop.amount_msat, title: 'Amount mSat', width: 50, type: DataTypeEnum.STRING }],
|
||||
[{ key: 'direction', value: selHop.direction, title: 'Direction', width: 50, type: DataTypeEnum.STRING },
|
||||
{ key: 'delay', value: selHop.delay, title: 'Delay', width: 50, type: DataTypeEnum.NUMBER }]
|
||||
[{ key: 'amount_msat', value: selHop.amount_msat, title: 'Amount (mSat)', width: 34, type: DataTypeEnum.NUMBER },
|
||||
{ key: 'direction', value: selHop.direction, title: 'Direction', width: 33, type: DataTypeEnum.STRING },
|
||||
{ key: 'delay', value: selHop.delay, title: 'Delay', width: 33, type: DataTypeEnum.NUMBER }]
|
||||
];
|
||||
this.store.dispatch(openAlert({
|
||||
payload: {
|
||||
|
@ -1,17 +1,17 @@
|
||||
<div *ngIf="errorMessage?.trim() === ''; else errorBlock" class="mt-1" fxLayout="column" fxFlex="100"fxLayoutAlign="space-between stretch">
|
||||
<div>
|
||||
<h4 fxLayoutAlign="start" class="dashboard-info-title">Lightning</h4>
|
||||
<div class="overflow-wrap dashboard-info-value">{{balances.lightning | number}} Sats</div>
|
||||
<div class="overflow-wrap dashboard-info-value">{{balances.lightning | number:'1.0-0'}} Sats</div>
|
||||
<mat-progress-bar class="dashboard-progress-bar" mode="determinate" value="{{balances.lightning/balances.total*100}}"></mat-progress-bar>
|
||||
</div>
|
||||
<div>
|
||||
<h4 fxLayoutAlign="start" class="dashboard-info-title">On-chain</h4>
|
||||
<div class="overflow-wrap dashboard-info-value">{{balances.onchain | number}} Sats</div>
|
||||
<div class="overflow-wrap dashboard-info-value">{{balances.onchain | number:'1.0-0'}} Sats</div>
|
||||
<mat-progress-bar class="dashboard-progress-bar" mode="determinate" value="{{balances.onchain/balances.total*100}}"></mat-progress-bar>
|
||||
</div>
|
||||
<div>
|
||||
<h4 fxLayoutAlign="start" class="dashboard-info-title">Total</h4>
|
||||
<div class="overflow-wrap dashboard-info-value">{{balances.total | number}} Sats</div>
|
||||
<div class="overflow-wrap dashboard-info-value">{{balances.total | number:'1.0-0'}} Sats</div>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #errorBlock>
|
||||
|
@ -15,18 +15,18 @@
|
||||
<div class="channels-capacity-scroll" [perfectScrollbar]>
|
||||
<div *ngIf="activeChannels && activeChannels.length > 0; else noChannelBlock" fxLayout="column"fxFlex="100">
|
||||
<div *ngFor="let channel of activeChannels" class="mt-2">
|
||||
<a class="dashboard-capacity-header" [routerLink]="['../connections/channels/open']" [state]="{filter: channel.id}" matTooltip="{{channel.alias || channel.id}}" matTooltipDisabled="{{(channel.alias || channel.id).length < 26}}">
|
||||
{{(channel.alias || channel.id) | slice:0:24}}{{(channel.alias || channel.id).length > 25 ? '...' : ''}}
|
||||
<a class="dashboard-capacity-header" [routerLink]="['../connections/channels/open']" [state]="{filterColumn: channel.alias ? 'alias' : 'peer_id', filterValue: channel.alias || channel.peer_id}" matTooltip="{{channel.alias || channel.peer_id}}" matTooltipDisabled="{{(channel.alias || channel.peer_id).length < 26}}">
|
||||
{{(channel.alias || channel.peer_id) | slice:0:24}}{{(channel.alias || channel.peer_id).length > 25 ? '...' : ''}}
|
||||
</a>
|
||||
<div fxLayout="row" fxLayoutAlign="space-between start" class="w-100">
|
||||
<mat-hint fxFlex="40" fxLayoutAlign="start center" class="font-size-90 color-primary"><strong class="font-weight-900 mr-5px">Local:</strong>{{channel.msatoshi_to_us/1000 || 0 | number:'1.0-0'}} Sats</mat-hint>
|
||||
<mat-hint fxFlex="40" fxLayoutAlign="start center" class="font-size-90 color-primary"><strong class="font-weight-900 mr-5px">Local:</strong>{{channel.to_us_msat/1000 || 0 | number:'1.0-0'}} Sats</mat-hint>
|
||||
<mat-hint fxFlex="20" fxLayoutAlign="center center" class="font-size-90 color-primary">
|
||||
<fa-icon class="color-primary mr-3px" matTooltip="Balance Score" [icon]="faBalanceScale"></fa-icon>
|
||||
({{channel.balancedness || 0 | number}})
|
||||
</mat-hint>
|
||||
<mat-hint fxFlex="40" fxLayoutAlign="end center" class="font-size-90 color-primary"><strong class="font-weight-900 mr-5px">Remote:</strong>{{channel.msatoshi_to_them/1000 || 0 | number:'1.0-0'}} Sats</mat-hint>
|
||||
<mat-hint fxFlex="40" fxLayoutAlign="end center" class="font-size-90 color-primary"><strong class="font-weight-900 mr-5px">Remote:</strong>{{channel.to_them_msat/1000 || 0 | number:'1.0-0'}} Sats</mat-hint>
|
||||
</div>
|
||||
<mat-progress-bar class="dashboard-progress-bar" mode="determinate" value="{{channel.msatoshi_to_us && channel.msatoshi_to_us > 0 ? ((+channel.msatoshi_to_us/((+channel.msatoshi_to_us)+(+channel.msatoshi_to_them)))*100) : 0}}"></mat-progress-bar>
|
||||
<mat-progress-bar class="dashboard-progress-bar" mode="determinate" value="{{channel.to_us_msat && channel.to_us_msat > 0 ? ((channel.to_us_msat/((channel.to_us_msat)+(channel.to_them_msat)))*100) : 0}}"></mat-progress-bar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -8,15 +8,15 @@
|
||||
<div fxLayout="column" fxFlex.gt-sm="88" fxFlex="84" fxLayoutAlign="start start" [perfectScrollbar]>
|
||||
<div *ngIf="activeChannels && activeChannels.length > 0; else noChannelBlock" fxLayout="column" fxFlex="100"class="w-100">
|
||||
<div *ngFor="let channel of activeChannels" class="mt-2">
|
||||
<a class="dashboard-capacity-header" [routerLink]="['../connections/channels/open']" [state]="{filter: channel.id}" matTooltip="{{channel.alias || channel.id}}" matTooltipDisabled="{{(channel.alias || channel.id).length < 26}}">
|
||||
{{(channel.alias || channel.id) | slice:0:24}}{{(channel.alias || channel.id).length > 25 ? '...' : ''}}
|
||||
<a class="dashboard-capacity-header" [routerLink]="['../connections/channels/open']" [state]="{filterColumn: channel.alias ? 'alias' : 'peer_id', filterValue: channel.alias || channel.peer_id}" matTooltip="{{channel.alias || channel.peer_id}}" matTooltipDisabled="{{(channel.alias || channel.peer_id).length < 26}}">
|
||||
{{(channel.alias || channel.peer_id) | slice:0:24}}{{(channel.alias || channel.peer_id).length > 25 ? '...' : ''}}
|
||||
</a>
|
||||
<div fxLayout="row" fxLayoutAlign="space-between start" class="w-100">
|
||||
<mat-hint *ngIf="direction === 'In'" fxFlex="100" fxLayoutAlign="start center" class="font-size-90 color-primary"><strong class="font-weight-900 mr-5px">Capacity: </strong>{{channel.msatoshi_to_them/1000 || 0 | number:'1.0-0'}} Sats</mat-hint>
|
||||
<mat-hint *ngIf="direction === 'Out'" fxFlex="100" fxLayoutAlign="start center" class="font-size-90 color-primary"><strong class="font-weight-900 mr-5px">Capacity: </strong>{{channel.msatoshi_to_us/1000 || 0 | number:'1.0-0'}} Sats</mat-hint>
|
||||
<mat-hint *ngIf="direction === 'In'" fxFlex="100" fxLayoutAlign="start center" class="font-size-90 color-primary"><strong class="font-weight-900 mr-5px">Capacity: </strong>{{channel.to_them_msat/1000 || 0 | number:'1.0-0'}} Sats</mat-hint>
|
||||
<mat-hint *ngIf="direction === 'Out'" fxFlex="100" fxLayoutAlign="start center" class="font-size-90 color-primary"><strong class="font-weight-900 mr-5px">Capacity: </strong>{{channel.to_us_msat/1000 || 0 | number:'1.0-0'}} Sats</mat-hint>
|
||||
</div>
|
||||
<mat-progress-bar *ngIf="direction === 'In'" class="dashboard-progress-bar" mode="determinate" value="{{(totalLiquidity > 0) ? ((+channel.msatoshi_to_them/1000 || 0)/(totalLiquidity) * 100) : 0}}"></mat-progress-bar>
|
||||
<mat-progress-bar *ngIf="direction === 'Out'" class="dashboard-progress-bar" mode="determinate" value="{{(totalLiquidity > 0) ? ((+channel.msatoshi_to_us/1000 || 0)/(totalLiquidity) * 100) : 0}}"></mat-progress-bar>
|
||||
<mat-progress-bar *ngIf="direction === 'In'" class="dashboard-progress-bar" mode="determinate" value="{{(totalLiquidity > 0) ? ((channel.to_them_msat/1000 || 0)/(totalLiquidity) * 100) : 0}}"></mat-progress-bar>
|
||||
<mat-progress-bar *ngIf="direction === 'Out'" class="dashboard-progress-bar" mode="determinate" value="{{(totalLiquidity > 0) ? ((channel.to_us_msat/1000 || 0)/(totalLiquidity) * 100) : 0}}"></mat-progress-bar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -16,15 +16,15 @@
|
||||
<div fxLayout="column" fxFlex="50" fxLayoutAlign="space-between stretch">
|
||||
<div>
|
||||
<h4 fxLayoutAlign="start" class="dashboard-info-title">Capacity</h4>
|
||||
<div class="overflow-wrap dashboard-info-value">{{(channelsStatus?.active?.capacity || 0) | number}} Sats</div>
|
||||
<div class="overflow-wrap dashboard-info-value">{{(channelsStatus?.active?.capacity || 0) | number:'1.0-0'}} Sats</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 fxLayoutAlign="start" class="dashboard-info-title">Capacity</h4>
|
||||
<div class="overflow-wrap dashboard-info-value">{{(channelsStatus?.pending?.capacity || 0) | number}} Sats</div>
|
||||
<div class="overflow-wrap dashboard-info-value">{{(channelsStatus?.pending?.capacity || 0) | number:'1.0-0'}} Sats</div>
|
||||
</div>
|
||||
<div>
|
||||
<h4 fxLayoutAlign="start" class="dashboard-info-title">Capacity</h4>
|
||||
<div class="overflow-wrap dashboard-info-value">{{(channelsStatus?.inactive?.capacity || 0) | number}} Sats</div>
|
||||
<div class="overflow-wrap dashboard-info-value">{{(channelsStatus?.inactive?.capacity || 0) | number:'1.0-0'}} Sats</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -155,11 +155,11 @@ export class CLNHomeComponent implements OnInit, OnDestroy {
|
||||
this.totalOutboundLiquidity = 0;
|
||||
this.activeChannels = channelsSeletor.activeChannels;
|
||||
this.activeChannelsCapacity = JSON.parse(JSON.stringify(this.commonService.sortDescByKey(this.activeChannels, 'balancedness'))) || [];
|
||||
this.allInboundChannels = JSON.parse(JSON.stringify(this.commonService.sortDescByKey(this.activeChannels?.filter((channel) => (channel.msatoshi_to_them ? channel.msatoshi_to_them > 0 : false)), 'msatoshi_to_them'))) || [];
|
||||
this.allOutboundChannels = JSON.parse(JSON.stringify(this.commonService.sortDescByKey(this.activeChannels?.filter((channel) => (channel.msatoshi_to_us ? channel.msatoshi_to_us > 0 : false)), 'msatoshi_to_us'))) || [];
|
||||
this.allInboundChannels = JSON.parse(JSON.stringify(this.commonService.sortDescByKey(this.activeChannels?.filter((channel) => (channel.to_them_msat ? channel.to_them_msat > 0 : false)), 'to_them_msat'))) || [];
|
||||
this.allOutboundChannels = JSON.parse(JSON.stringify(this.commonService.sortDescByKey(this.activeChannels?.filter((channel) => (channel.to_us_msat ? channel.to_us_msat > 0 : false)), 'to_us_msat'))) || [];
|
||||
this.activeChannels.forEach((channel) => {
|
||||
this.totalInboundLiquidity = this.totalInboundLiquidity + Math.ceil((channel.msatoshi_to_them || 0) / 1000);
|
||||
this.totalOutboundLiquidity = this.totalOutboundLiquidity + Math.floor((channel.msatoshi_to_us || 0) / 1000);
|
||||
this.totalInboundLiquidity = this.totalInboundLiquidity + Math.ceil((channel.to_them_msat || 0) / 1000);
|
||||
this.totalOutboundLiquidity = this.totalOutboundLiquidity + Math.floor((channel.to_us_msat || 0) / 1000);
|
||||
});
|
||||
this.channelsStatus.active.channels = channelsSeletor.activeChannels.length || 0;
|
||||
this.channelsStatus.pending.channels = channelsSeletor.pendingChannels.length || 0;
|
||||
@ -205,8 +205,8 @@ export class CLNHomeComponent implements OnInit, OnDestroy {
|
||||
if (this.sortField === 'Balance Score') {
|
||||
this.sortField = 'Capacity';
|
||||
this.activeChannelsCapacity = this.activeChannels.sort((a, b) => {
|
||||
const x = (a.msatoshi_to_us ? +a.msatoshi_to_us : 0) + (a.msatoshi_to_them ? +a.msatoshi_to_them : 0);
|
||||
const y = (b.msatoshi_to_them ? +b.msatoshi_to_them : 0) + (b.msatoshi_to_them ? +b.msatoshi_to_them : 0);
|
||||
const x = (a.to_us_msat ? +a.to_us_msat : 0) + (a.to_them_msat ? +a.to_them_msat : 0);
|
||||
const y = (b.to_them_msat ? +b.to_them_msat : 0) + (b.to_them_msat ? +b.to_them_msat : 0);
|
||||
return ((x > y) ? -1 : ((x < y) ? 1 : 0));
|
||||
});
|
||||
} else {
|
||||
|
@ -141,7 +141,7 @@ export class CLNLiquidityAdsListComponent implements OnInit, OnDestroy {
|
||||
|
||||
getLabel(column: string) {
|
||||
const returnColumn: ColumnDefinition = this.nodePageDefs[this.PAGE_ID][this.tableSetting.tableId].allowedColumns.find((col) => col.column === column);
|
||||
return returnColumn ? returnColumn.label ? returnColumn.label : this.camelCaseWithReplace.transform(returnColumn.column, '_') : this.commonService.titleCase(column);
|
||||
return returnColumn ? returnColumn.label ? returnColumn.label : this.camelCaseWithReplace.transform((returnColumn.column || ''), '_') : this.commonService.titleCase(column);
|
||||
}
|
||||
|
||||
setFilterPredicate() {
|
||||
@ -230,8 +230,10 @@ export class CLNLiquidityAdsListComponent implements OnInit, OnDestroy {
|
||||
if (lqNode.features && lqNode.features.trim() !== '') {
|
||||
const featureHex = parseInt(lqNode.features, 16);
|
||||
NODE_FEATURES_CLN.forEach((feature) => {
|
||||
if (!!(featureHex & ((1 << feature.range.min) | (1 << feature.range.max)))) {
|
||||
featureDescriptions.push(feature.description);
|
||||
if (featureHex & (1 << feature.range.min)) {
|
||||
featureDescriptions.push('Mandatory: ' + feature.description);
|
||||
} else if (featureHex & (1 << feature.range.max)) {
|
||||
featureDescriptions.push('Optional: ' + feature.description);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
<div *ngIf="errorMessage?.trim() === ''; else errorBlock" fxLayout="column" fxLayout.gt-xs="row" fxFlex="100" fxLayoutAlign="stretch stretch">
|
||||
<div *ngIf="errorMessage?.trim() === ''; else errorBlock" fxLayout="column" fxFlex="100" fxLayoutAlign="stretch stretch">
|
||||
<div fxLayout="column" fxLayout.gt-xs="row" fxFlex="100" fxLayoutAlign="stretch stretch">
|
||||
<div fxLayout="column" fxFlex="50" fxLayoutAlign="space-between stretch" class="mt-2">
|
||||
<div>
|
||||
<h4 fxLayoutAlign="start start" class="dashboard-info-title">
|
||||
@ -28,8 +29,6 @@
|
||||
</h4>
|
||||
<div class="overflow-wrap dashboard-info-value">{{perkbw?.delayed_to_us | number}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div fxLayout="column" fxFlex="50" fxLayoutAlign="space-between stretch" class="my-2">
|
||||
<div>
|
||||
<h4 fxLayoutAlign="start start" class="dashboard-info-title">
|
||||
Minimum Acceptable
|
||||
@ -44,6 +43,8 @@
|
||||
</h4>
|
||||
<div class="overflow-wrap dashboard-info-value">{{perkbw?.max_acceptable | number}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div fxLayout="column" fxFlex="50" fxLayoutAlign="space-between stretch" class="mt-2">
|
||||
<div>
|
||||
<h4 fxLayoutAlign="start start" class="dashboard-info-title">
|
||||
HTLC Resolution
|
||||
@ -58,6 +59,35 @@
|
||||
</h4>
|
||||
<div class="overflow-wrap dashboard-info-value">{{perkbw?.penalty | number}}</div>
|
||||
</div>
|
||||
<div *ngIf="perkbw?.estimates && perkbw?.estimates.length && perkbw?.estimates.length > 3">
|
||||
<h4 fxLayoutAlign="start start" class="dashboard-info-title">
|
||||
2 Blocks
|
||||
<mat-icon matTooltip="Fee rate estimate for 2 blocks" matTooltipPosition="below" class="info-icon info-icon-primary">info_outline</mat-icon>
|
||||
</h4>
|
||||
<div class="overflow-wrap dashboard-info-value">{{perkbw?.estimates[0].smoothed_feerate | number}}</div>
|
||||
</div>
|
||||
<div *ngIf="perkbw?.estimates && perkbw?.estimates.length && perkbw?.estimates.length > 3">
|
||||
<h4 fxLayoutAlign="start start" class="dashboard-info-title">
|
||||
6 Blocks
|
||||
<mat-icon matTooltip="Fee rate estimate for 6 blocks" matTooltipPosition="below" class="info-icon info-icon-primary">info_outline</mat-icon>
|
||||
</h4>
|
||||
<div class="overflow-wrap dashboard-info-value">{{perkbw?.estimates[1].smoothed_feerate | number}}</div>
|
||||
</div>
|
||||
<div *ngIf="perkbw?.estimates && perkbw?.estimates.length && perkbw?.estimates.length > 3">
|
||||
<h4 fxLayoutAlign="start start" class="dashboard-info-title">
|
||||
12 Blocks
|
||||
<mat-icon matTooltip="Fee rate estimate for 12 blocks" matTooltipPosition="below" class="info-icon info-icon-primary">info_outline</mat-icon>
|
||||
</h4>
|
||||
<div class="overflow-wrap dashboard-info-value">{{perkbw?.estimates[2].smoothed_feerate | number}}</div>
|
||||
</div>
|
||||
<div *ngIf="perkbw?.estimates && perkbw?.estimates.length && perkbw?.estimates.length > 3">
|
||||
<h4 fxLayoutAlign="start start" class="dashboard-info-title">
|
||||
100 Blocks
|
||||
<mat-icon matTooltip="Fee rate estimate for 100 blocks" matTooltipPosition="below" class="info-icon info-icon-primary">info_outline</mat-icon>
|
||||
</h4>
|
||||
<div class="overflow-wrap dashboard-info-value">{{perkbw?.estimates[3].smoothed_feerate | number}}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #errorBlock>
|
||||
|
@ -13,14 +13,15 @@ export class CLNFeeRatesComponent implements AfterContentChecked {
|
||||
@Input() feeRates: FeeRates;
|
||||
@Input() errorMessage: string;
|
||||
perkbw: FeeRatePerObj = {};
|
||||
displayedColumns: string[] = ['blockcount', 'feerate'];
|
||||
|
||||
constructor() { }
|
||||
|
||||
ngAfterContentChecked() {
|
||||
if (this.feeRateStyle === feeRateStyle.KB) {
|
||||
this.perkbw = this.feeRates.perkb!;
|
||||
this.perkbw = this.feeRates.perkb || {};
|
||||
} else if (this.feeRateStyle === feeRateStyle.KW) {
|
||||
this.perkbw = this.feeRates.perkw!;
|
||||
this.perkbw = this.feeRates.perkw || {};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -54,28 +54,28 @@ export class CLNNetworkInfoComponent implements OnInit, OnDestroy {
|
||||
{ id: 'node', icon: this.faServer, title: 'Node Information', cols: 6, rows: 3 },
|
||||
{ id: 'status', icon: this.faNetworkWired, title: 'Channels', cols: 6, rows: 3 },
|
||||
{ id: 'fee', icon: this.faBolt, title: 'Routing Fee', cols: 6, rows: 1 },
|
||||
{ id: 'feeRatesKB', icon: this.faServer, title: 'Fee Rate Per KB', cols: 4, rows: 4 },
|
||||
{ id: 'feeRatesKW', icon: this.faNetworkWired, title: 'Fee Rate Per KW', cols: 4, rows: 4 },
|
||||
{ id: 'onChainFeeEstimates', icon: this.faLink, title: 'Onchain Fee Estimates (Sats)', cols: 4, rows: 4 }
|
||||
{ id: 'feeRatesKB', icon: this.faServer, title: 'Fee Rate Per KB', cols: 4, rows: 6 },
|
||||
{ id: 'feeRatesKW', icon: this.faNetworkWired, title: 'Fee Rate Per KW', cols: 4, rows: 6 },
|
||||
{ id: 'onChainFeeEstimates', icon: this.faLink, title: 'Onchain Fee Estimates (Sats)', cols: 4, rows: 6 }
|
||||
];
|
||||
this.nodeCardsOperator = [
|
||||
{ id: 'feeRatesKB', icon: this.faServer, title: 'Fee Rate Per KB', cols: 4, rows: 4 },
|
||||
{ id: 'feeRatesKW', icon: this.faNetworkWired, title: 'Fee Rate Per KW', cols: 4, rows: 4 },
|
||||
{ id: 'onChainFeeEstimates', icon: this.faLink, title: 'Onchain Fee Estimates (Sats)', cols: 4, rows: 4 }
|
||||
{ id: 'feeRatesKB', icon: this.faServer, title: 'Fee Rate Per KB', cols: 4, rows: 6 },
|
||||
{ id: 'feeRatesKW', icon: this.faNetworkWired, title: 'Fee Rate Per KW', cols: 4, rows: 6 },
|
||||
{ id: 'onChainFeeEstimates', icon: this.faLink, title: 'Onchain Fee Estimates (Sats)', cols: 4, rows: 6 }
|
||||
];
|
||||
} else {
|
||||
this.nodeCardsMerchant = [
|
||||
{ id: 'node', icon: this.faServer, title: 'Node Information', cols: 2, rows: 3 },
|
||||
{ id: 'status', icon: this.faNetworkWired, title: 'Channels', cols: 2, rows: 3 },
|
||||
{ id: 'fee', icon: this.faBolt, title: 'Routing Fee', cols: 2, rows: 3 },
|
||||
{ id: 'feeRatesKB', icon: this.faServer, title: 'Fee Rate Per KB', cols: 2, rows: 4 },
|
||||
{ id: 'feeRatesKW', icon: this.faNetworkWired, title: 'Fee Rate Per KW', cols: 2, rows: 4 },
|
||||
{ id: 'onChainFeeEstimates', icon: this.faLink, title: 'Onchain Fee Estimates (Sats)', cols: 2, rows: 4 }
|
||||
{ id: 'feeRatesKB', icon: this.faServer, title: 'Fee Rate Per KB', cols: 2, rows: 6 },
|
||||
{ id: 'feeRatesKW', icon: this.faNetworkWired, title: 'Fee Rate Per KW', cols: 2, rows: 6 },
|
||||
{ id: 'onChainFeeEstimates', icon: this.faLink, title: 'Onchain Fee Estimates (Sats)', cols: 2, rows: 6 }
|
||||
];
|
||||
this.nodeCardsOperator = [
|
||||
{ id: 'feeRatesKB', icon: this.faServer, title: 'Fee Rate Per KB', cols: 2, rows: 4 },
|
||||
{ id: 'feeRatesKW', icon: this.faNetworkWired, title: 'Fee Rate Per KW', cols: 2, rows: 4 },
|
||||
{ id: 'onChainFeeEstimates', icon: this.faLink, title: 'Onchain Fee Estimates (Sats)', cols: 2, rows: 4 }
|
||||
{ id: 'feeRatesKB', icon: this.faServer, title: 'Fee Rate Per KB', cols: 2, rows: 6 },
|
||||
{ id: 'feeRatesKW', icon: this.faNetworkWired, title: 'Fee Rate Per KW', cols: 2, rows: 6 },
|
||||
{ id: 'onChainFeeEstimates', icon: this.faLink, title: 'Onchain Fee Estimates (Sats)', cols: 2, rows: 6 }
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
<div *ngIf="errorMessage?.trim() === ''; else errorBlock" fxLayout="column" fxLayout.gt-xs="row" fxFlex="100" fxLayoutAlign="stretch stretch">
|
||||
<div fxLayout="column" fxFlex="100" fxLayoutAlign="stretch stretch">
|
||||
<div *ngIf="errorMessage?.trim() === ''; else errorBlock" fxLayout="column" fxLayout.gt-xs="row" fxFlex="62" fxLayoutAlign="stretch stretch">
|
||||
<div fxLayout="column" fxFlex="50" fxLayoutAlign="space-between stretch" class="mt-2">
|
||||
<div>
|
||||
<h4 fxLayoutAlign="start start" class="dashboard-info-title">
|
||||
@ -50,9 +51,10 @@
|
||||
<div class="overflow-wrap dashboard-info-value"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ng-template #errorBlock>
|
||||
</div>
|
||||
<ng-template #errorBlock>
|
||||
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between" class="p-2">
|
||||
<p>{{errorMessage}}</p>
|
||||
</div>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
@ -51,7 +51,6 @@
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="isCompatibleVersion" fxLayout="column" fxLayoutAlign="space-between stretch" fxLayoutAlign.gt-sm="space-between center" fxLayout.gt-sm="row wrap">
|
||||
<mat-expansion-panel fxLayout="column" fxFlex="100" class="flat-expansion-panel mt-2" expanded="false" (closed)="onAdvancedPanelToggle(true)" (opened)="onAdvancedPanelToggle(false)">
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>
|
||||
@ -64,7 +63,7 @@
|
||||
<mat-label>Coin Selection</mat-label>
|
||||
<mat-select tabindex="8" multiple [(value)]="selUTXOs" (selectionChange)="onUTXOSelectionChange($event)">
|
||||
<mat-select-trigger>{{totalSelectedUTXOAmount | number}} Sats ({{selUTXOs.length > 1 ? selUTXOs.length + ' UTXOs' : '1 UTXO'}})</mat-select-trigger>
|
||||
<mat-option *ngFor="let utxo of utxos" [value]="utxo">{{utxo.value | number}} Sats</mat-option>
|
||||
<mat-option *ngFor="let utxo of utxos" [value]="utxo">{{utxo.amount_msat/1000 | number:'1.0-0'}} Sats</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<div fxFlex="60" fxLayout="row" fxLayoutAlign="start center">
|
||||
@ -76,7 +75,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</mat-expansion-panel>
|
||||
</div>
|
||||
<div fxLayout="column" fxFlex="100" fxLayoutAlign="start stretch"></div>
|
||||
<div *ngIf="sendFundError !== ''" fxFlex="100" class="alert alert-danger mt-1">
|
||||
<fa-icon class="mr-1 alert-icon" [icon]="faExclamationTriangle"></fa-icon>
|
||||
|
@ -48,7 +48,6 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
|
||||
public selectedAddress = ADDRESS_TYPES[1];
|
||||
public blockchainBalance: Balance = {};
|
||||
public information: GetInfo = {};
|
||||
public isCompatibleVersion = false;
|
||||
public newAddress = '';
|
||||
public transaction: OnChain | any = {};
|
||||
public feeRateTypes = FEE_RATE_TYPES;
|
||||
@ -140,9 +139,6 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
|
||||
this.store.select(clnNodeInformation).pipe(takeUntil(this.unSubs[2])).
|
||||
subscribe((nodeInfo: GetInfo) => {
|
||||
this.information = nodeInfo;
|
||||
this.isCompatibleVersion =
|
||||
this.commonService.isVersionCompatible(this.information.version, '0.9.0') &&
|
||||
this.commonService.isVersionCompatible(this.information.api_version, '0.4.0');
|
||||
});
|
||||
this.store.select(utxos).pipe(takeUntil(this.unSubs[3])).
|
||||
subscribe((utxosSeletor: { utxos: UTXO[], apiCallStatus: ApiCallStatusPayload }) => {
|
||||
@ -285,7 +281,7 @@ export class CLNOnChainSendModalComponent implements OnInit, OnDestroy {
|
||||
|
||||
onUTXOSelectionChange(event: any) {
|
||||
if (this.selUTXOs.length && this.selUTXOs.length > 0) {
|
||||
this.totalSelectedUTXOAmount = this.selUTXOs?.reduce((total, curr) => (total + (curr.value || 0)), 0);
|
||||
this.totalSelectedUTXOAmount = this.selUTXOs?.reduce((total, curr) => (total + ((curr.amount_msat || 0) / 1000)), 0);
|
||||
if (this.flgUseAllBalance) {
|
||||
this.onUTXOAllBalanceChange();
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ export class CLNUTXOTablesComponent implements OnInit, OnDestroy {
|
||||
subscribe((utxosSeletor: { utxos: UTXO[], apiCallStatus: ApiCallStatusPayload }) => {
|
||||
if (utxosSeletor.utxos && utxosSeletor.utxos.length > 0) {
|
||||
this.numUtxos = utxosSeletor.utxos.length || 0;
|
||||
this.numDustUtxos = utxosSeletor.utxos?.filter((utxo) => +(utxo.value || 0) < this.DUST_AMOUNT).length || 0;
|
||||
this.numDustUtxos = utxosSeletor.utxos?.filter((utxo) => (+(utxo.amount_msat || 0) / 1000) < this.DUST_AMOUNT).length || 0;
|
||||
}
|
||||
this.logger.info(utxosSeletor);
|
||||
});
|
||||
|
@ -21,7 +21,7 @@
|
||||
<ng-container matColumnDef="is_dust">
|
||||
<th *matHeaderCellDef mat-header-cell mat-sort-header arrowPosition="before" matTooltip="Dust/Nondust"></th>
|
||||
<td *matCellDef="let utxo" mat-cell>
|
||||
<span *ngIf="numDustUTXOs > 0 && !isDustUTXO && utxo.value < dustAmount; else emptySpace" matTooltip="Risk of dust attack" matTooltipPosition="right">
|
||||
<span *ngIf="numDustUTXOs > 0 && !isDustUTXO && (utxo?.amount_msat / 1000) < dustAmount; else emptySpace" matTooltip="Risk of dust attack" matTooltipPosition="right">
|
||||
<mat-icon fxLayoutAlign="start center" color="warn" class="small-icon">warning</mat-icon>
|
||||
</span>
|
||||
</td>
|
||||
@ -65,8 +65,8 @@
|
||||
<ng-container matColumnDef="value">
|
||||
<th *matHeaderCellDef mat-header-cell mat-sort-header arrowPosition="before">Value (Sats)</th>
|
||||
<td *matCellDef="let utxo" mat-cell>
|
||||
<span *ngIf="utxo.value > 0 || utxo.value === 0" fxLayoutAlign="end center">{{utxo.value | number}}</span>
|
||||
<span *ngIf="utxo.value < 0" fxLayoutAlign="end center" class="red">({{utxo.value * -1 | number}})</span>
|
||||
<span *ngIf="utxo.amount_msat > 0 || utxo.amount_msat === 0" fxLayoutAlign="end center">{{utxo.amount_msat / 1000 | number:'1.0-0'}}</span>
|
||||
<span *ngIf="utxo.amount_msat < 0" fxLayoutAlign="end center" class="red">({{utxo.amount_msat / 1000 * -1 | number:'1.0-0'}})</span>
|
||||
</td>
|
||||
</ng-container>
|
||||
<ng-container matColumnDef="blockheight">
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user