Merge pull request #1157 from Ride-The-Lightning/Release-0.13.3

Release 0.13.3
pr/1205
ShahanaFarooqui 1 year ago committed by GitHub
commit 27d210c295
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

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

@ -1,10 +1,7 @@
{
"root": true,
"ignorePatterns": [
"projects/**/*",
"rtl.js",
"/backend/**/*.js",
"/src/prebuild.js"
"src/**/*.js"
],
"overrides": [
{
@ -13,27 +10,24 @@
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2020,
"ecmaVersion": 2022,
"sourceType": "module",
"project": "./tsconfig.json",
"createDefaultProgram": true
},
"env": { "es2022": true },
"plugins": ["deprecation"],
"extends": [
"plugin:@angular-eslint/all",
"plugin:@angular-eslint/recommended",
"plugin:@angular-eslint/recommended--extra",
"plugin:@angular-eslint/template/process-inline-templates"
"plugin:@angular-eslint/recommended--extra"
],
"rules": {
"deprecation/deprecation": "error",
"@angular-eslint/use-injectable-provided-in": "off",
"@angular-eslint/prefer-on-push-component-change-detection": "off",
"@angular-eslint/use-component-view-encapsulation": "off",
"@angular-eslint/sort-ngmodule-metadata-arrays": "off",
"@angular-eslint/arrow-body-style": "off",
"@angular-eslint/component-selector": ["error", { "prefix": "rtl", "style": "kebab-case", "type": "element" }],
"@angular-eslint/directive-selector": ["error", { "style": "camelCase", "type": "attribute" }],
"@angular-eslint/use-component-view-encapsulation": "off",
"@angular-eslint/use-injectable-provided-in": "off",
"@typescript-eslint/member-delimiter-style": ["error", { "multiline": { "delimiter": "semi", "requireLast": true}, "singleline": { "delimiter": "comma", "requireLast": false }}],
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/type-annotation-spacing": "error",
@ -186,22 +180,29 @@
"files": [
"*.html"
],
"parser": "@angular-eslint/template-parser",
"extends": [
"plugin:@angular-eslint/template/all",
"plugin:@angular-eslint/template/recommended"
"plugin:@angular-eslint/template/recommended",
"plugin:@angular-eslint/template/process-inline-templates"
],
"rules": {
"@angular-eslint/arrow-body-style": "off",
"@angular-eslint/component-selector": ["error", { "prefix": "rtl", "style": "kebab-case", "type": "element" }],
"@angular-eslint/directive-selector": ["error", { "style": "camelCase", "type": "attribute" }],
"@angular-eslint/template/accessibility-elements-content": "off",
"@angular-eslint/template/accessibility-interactive-supports-focus": "off",
"@angular-eslint/template/button-has-type": "off",
"@angular-eslint/template/click-events-have-key-events": "off",
"@angular-eslint/template/conditional-complexity": "off",
"@angular-eslint/template/cyclomatic-complexity": "off",
"@angular-eslint/template/i18n": "off",
"@angular-eslint/template/no-autofocus": "off",
"@angular-eslint/template/no-call-expression": "off",
"@angular-eslint/template/no-inline-styles": "off",
"@angular-eslint/template/no-positive-tabindex": "off",
"@angular-eslint/template/use-track-by-function": "off"
}
}
]
}
// https://eslint.org/docs/rules/

8
.github/README.md vendored

@ -64,6 +64,10 @@ $ git clean -f -d
$ git pull
$ npm install --omit=dev
```
#### Error on npm install
If there is an error with `upstream dependency conflict` message then replace `npm install --omit=dev` with `npm install --omit=dev --legacy-peer-deps`.
### <a name="prep"></a>Prep for Execution
RTL requires its own config file `RTL-Config.json`, to start the server and provide user authentication on the app.
@ -106,8 +110,8 @@ Example RTL-Config.json:
"fiatConversion": false,
"unannouncedChannels": false,
"lnServerUrl": "<url for LND REST APIs for node #1 e.g. https://192.168.0.1:8080>",
"swapServerUrl": "<url for swap server REST APIs for the node. e.g. https://localhost:8081>",
"boltzServerUrl": "<url for boltz server REST APIs for the node. e.g. https://localhost:9003>"
"swapServerUrl": "<url for swap server REST APIs for the node. e.g. https://127.0.0.1:8081>",
"boltzServerUrl": "<url for boltz server REST APIs for the node. e.g. https://127.0.0.1:9003>"
}
}
]

@ -36,9 +36,9 @@ parameters have `default` values for initial setup and can be updated after RTL
"fiatConversion": <parameter to turn fiat conversion off/on. Allowed values - true, false, default false, Required>,
"currencyUnit": "<Optional: Fiat current Unit for currency conversion, default 'USD' If fiatConversion is true, Required if fiatConversion is true>",
"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://localhost:8080', Required",
"swapServerUrl": "<Service url for swap server REST APIs for the node, e.g. https://localhost:8081, Optional>",
"boltzServerUrl": "<Service url for boltz server REST APIs for the node, e.g. https://localhost:9003, 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",
"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>"
}
}
]
@ -53,9 +53,9 @@ PORT (port number for the rtl node server, default 3000, Required)<br />
HOST (host for the rtl node server, default localhost, Optional)<br />
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://localhost:8080) (Required)<br />
SWAP_SERVER_URL (Swap server URL for REST APIs, default http://localhost:8081) (Optional)<br />
BOLTZ_SERVER_URL (Boltz server URL for REST APIs, default http://localhost:9003) (Optional)<br />
LN_SERVER_URL (LN server URL for LNP REST APIs, default https://127.0.0.1:8080) (Required)<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 />
MACAROON_PATH (Path for the folder containing 'admin.macaroon' (LND)/'access.macaroon' (CLN) file, Required for LND & CLN)<br />
SWAP_MACAROON_PATH (Path for the folder containing Loop's 'loop.macaroon', optional)<br />

@ -44,6 +44,7 @@ Contributions via code is the most sought after contribution and something we en
##### Install Dependencies
* Assuming that nodejs (v14 & above) and npm are already installed on your local machine. Go into your RTL root folder and run `npm install`.
* Use `npm install --legacy-peer-deps` if there is any dependency conflict.
* Sometimes after installation, user receives a message from npm to fix dependency vulnerability by running `npm audit fix`. Please do not follow this step as it can break some of the working RTL code on your machine. We audit and fix these vulnerabilities as soon as possible at our end.
##### Node Backend Server for Development

@ -43,6 +43,10 @@ $ git clean -f -d
$ git pull
$ npm install --omit=dev
```
#### Error on npm install
If there is an error with `upstream dependency conflict` message then replace `npm install --omit=dev` with `npm install --omit=dev --legacy-peer-deps`.
### <a name="prep"></a>Prep for Execution
RTL requires its own config file `RTL-Config.json`, to start the server and provide user authentication on the app.
* Rename the file `Sample-RTL-Config.json` to `RTL-Config.json` located at`./RTL`..

@ -38,6 +38,10 @@ $ git clean -f -d
$ git pull
$ npm install --omit=dev
```
#### Error on npm install
If there is an error with `upstream dependency conflict` message then replace `npm install --omit=dev` with `npm install --omit=dev --legacy-peer-deps`.
### <a name="prep"></a>Prep for Execution
RTL requires its own config file `RTL-Config.json`, to start the server and provide user authentication on the app.
* Rename the file `Sample-RTL-Config.json` to `RTL-Config.json` located at`./RTL`..

@ -26,8 +26,8 @@ This step is only required to configure the nodes, which will be remotely connec
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://localhost:8081.
12. `boltzServerUrl` must be set to the boltz service url. e.g. https://localhost:9003.
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.

@ -3,20 +3,21 @@
Forward the ports 80 and 3002 on the router to the device running RTL.
Allow the ports through the firewall of the device.
Install Nginx:
https://www.nginx.com/resources/wiki/start/topics/tutorials/install/
On Debian based distros:
$> sudo apt install nginx
nginx default config file is at /etc/nginx/nginx.conf. You will need it.
Install, if needed, openssl
On Debian based distros:
$> sudo apt install openssl
Create a self certificate with openssl
$> openssl req -newkey rsa:4096 -x509 -sha512 -days 365 -nodes -out /path/to/some/folder/rtl-cert.crt -keyout /path/to/some/folder/rtl-cert.key
#### Nginx
Install Nginx:
https://www.nginx.com/resources/wiki/start/topics/tutorials/install/
On Debian based distros:
$> sudo apt install nginx
nginx default config file is at /etc/nginx/nginx.conf. You will need it.
Sample configuration to be inserted in the nginx.conf (adjust the path and filename of your certificate and key):
@ -41,3 +42,41 @@ Sample configuration to be inserted in the nginx.conf (adjust the path and filen
Restart Nginx with the new configuration and connect to RTL over https on the port 3002.
On Debian based distros:
$> sudo systemctl restart nginx
#### Apache2
Skip to step 5 if you already have Apache2 set up with HTTPS configured.
1. Install [Apache2](https://httpd.apache.org/download.cgi)
- On Debian-based distros: `sudo apt install apache2`
- On Fedora-based distros: `sudo yum install httpd`
2. Install [Let's Encrypt](https://letsencrypt.org) to get a free TLS certificate. The easiest way to install is to use Snap: `sudo snap install certbot`.
3. Run `certbot` to get a Let's Encrypt certificate: `sudo certbot`. Follow the instructions given to validate your domain name and install the certificate only. You may choose to redirect HTTP traffic to HTTPS instead (which attempts to secure every connection even when the client device does not request it). Let's Encrypt does not issue certificates for IP addresses. if you don't have a domain name, but you can use a service like NoIP.
4. Locate the Let's Encrypt Apache2 configuration file. It's usually in `/etc/apache2/sites-enabled` and called "000-default-le-ssl.conf" or similar.
5. Add the following lines to the Apache2 configuration file between the VIrtualHost 443 tags. This will redirect Apache's document root on your webserver to instead point to RTL. Change "/" to something like "/rtl" if you would instead like to redirect "/rtl" to RTL and do something else at the document root. Change "3002" to whatever port number you are using if it is not 3002.
ProxyPass "/" "http://127.0.0.1:3002/rtl"
ProxyPassReverse "/" "http://127.0.0.1:3002/rtl"
6. Restart Apache2.
- Debian-based distros: `sudo systemctl restart apache2`
- Fedora-based distros: `sudo systemctl restart httpd`
7. (Option) Edit ~/.rtl/rtl.js to disable insercure HTTP access to RTL entirely. Find these lines:
if (common.host) {
server.listen(common.port, common.host);
} else {
server.listen(common.port);
}
...and change them to:
if (common.host) {
server.listen(common.port, "127.0.0.1");
} else {
server.listen(common.port, "127.0.0.1");
}
This disables normal HTTP access to your server except if the client is on the same machine as the server. This will allow you to access http://localhost:3002 (or whatever port number you are using) on a browser that is on the same machine as RTL, but otherwise, you will have to access RTL through the Apache2 reverse proxy at https://yourdomain.tld/rtl, which will secure the connection with HTTPS.
Note: Occasionally you will receive "Invalid CSRF token, form tempered" when attempting to log in to RTL. If that happens, refresh the page and try again.

@ -33,7 +33,7 @@ jobs:
- name: Install NPM dependencies
if: steps.cache-npm-packages.outputs.cache-hit != 'true'
run: npm ci
run: npm ci --legacy-peer-deps
lint:
name: Lint
@ -57,7 +57,7 @@ jobs:
- name: Install NPM dependencies
if: steps.cache-npm-packages.outputs.cache-hit != 'true'
run: npm ci
run: npm ci --legacy-peer-deps
- name: Lint Scripts
run: npm run lint
@ -89,7 +89,7 @@ jobs:
- name: Install NPM dependencies
if: steps.cache-npm-packages.outputs.cache-hit != 'true'
run: npm ci
run: npm ci --legacy-peer-deps
- name: Run tests
run: npm run test

@ -34,7 +34,7 @@ jobs:
- name: Install NPM dependencies
if: steps.cache-npm-packages.outputs.cache-hit != 'true'
run: npm ci
run: npm ci --legacy-peer-deps
- name: Cache build frontend
uses: actions/cache@v2

@ -24,9 +24,9 @@
"themeColor": "PURPLE",
"channelBackupPath": "C:\\Users\\xyz\\backup\\node-1",
"logLevel": "ERROR",
"lnServerUrl": "https://localhost:8080",
"swapServerUrl": "https://localhost:8081",
"boltzServerUrl": "https://localhost:9003",
"lnServerUrl": "https://127.0.0.1:8080",
"swapServerUrl": "https://127.0.0.1:8081",
"boltzServerUrl": "https://127.0.0.1:9003",
"fiatConversion": false,
"unannouncedChannels": false
}

@ -8,9 +8,6 @@
"schematics": {
"@schematics/angular:component": {
"style": "scss"
},
"@schematics/angular:application": {
"strict": true
}
},
"root": "",
@ -24,9 +21,22 @@
"outputPath": "frontend",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"polyfills": [
"zone.js"
],
"tsConfig": "src/tsconfig.app.json",
"inlineStyleLanguage": "scss",
"assets": [
"src/assets"
],
"styles": [
"src/app/shared/theme/styles/styles.scss",
"node_modules/material-icons/iconfont/material-icons.css",
"node_modules/roboto-fontface/css/roboto/roboto-fontface.css"
],
"allowedCommonJsDependencies": [
"buffer",
"rfdc",
"sha256",
"qrcode",
"otplib",
@ -34,60 +44,46 @@
"pdfmake/build/vfs_fonts",
"clone-deep"
],
"assets": [
"src/assets"
],
"styles": [
"src/app/shared/theme/styles/styles.scss"
],
"scripts": [],
"vendorChunk": true,
"extractLicenses": false,
"buildOptimizer": false,
"sourceMap": true,
"optimization": false,
"namedChunks": true
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "12mb",
"maximumError": "12mb"
"maximumWarning": "20mb",
"maximumError": "50mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "20mb",
"maximumError": "50mb"
}
]
],
"outputHashing": "all"
},
"development": {
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"namedChunks": true
}
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "RTLApp:build"
},
"configurations": {
"production": {
"browserTarget": "RTLApp:build:production"
},
"development": {
"browserTarget": "RTLApp:build:development"
}
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
@ -98,15 +94,19 @@
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"polyfills": [
"zone.js",
"zone.js/testing"
],
"tsConfig": "src/tsconfig.spec.json",
"karmaConfig": "src/karma.conf.cjs",
"inlineStyleLanguage": "scss",
"assets": [
"src/assets"
],
"styles": [
"src/app/shared/theme/styles/styles.scss"
"src/app/shared/theme/styles/styles.scss",
"node_modules/material-icons/iconfont/material-icons.css",
"node_modules/roboto-fontface/css/roboto/roboto-fontface.css"
],
"scripts": []
}
@ -123,8 +123,10 @@
}
}
},
"defaultProject": "RTLApp",
"cli": {
"defaultCollection": "@angular-eslint/schematics"
"schematicCollections": [
"@angular-eslint/schematics"
],
"analytics": false
}
}

@ -12,7 +12,7 @@ export const listChannels = (req, res, next) => {
}
options.url = req.session.selectedNode.ln_server_url + '/v1/channel/listChannels';
request(options).then((body) => {
body === null || body === void 0 ? void 0 : body.map((channel) => {
body?.map((channel) => {
if (!channel.alias || channel.alias === '') {
channel.alias = channel.id.substring(0, 20);
}
@ -129,10 +129,9 @@ export const funderUpdatePolicy = (req, res, next) => {
}
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Funder Update Body', data: options.body });
request.post(options).then((body) => {
var _a, _b;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Funder Policy Received', data: body });
body.channel_fee_max_base_msat = (body.channel_fee_max_base_msat && typeof body.channel_fee_max_base_msat === 'string' && body.channel_fee_max_base_msat.includes('msat')) ? +((_a = body.channel_fee_max_base_msat) === null || _a === void 0 ? void 0 : _a.replace('msat', '')) : body.channel_fee_max_base_msat;
body.lease_fee_base_msat = (body.lease_fee_base_msat && typeof body.lease_fee_base_msat === 'string' && body.lease_fee_base_msat.includes('msat')) ? +((_b = body.lease_fee_base_msat) === null || _b === void 0 ? void 0 : _b.replace('msat', '')) : body.channel_fee_max_base_msat;
body.channel_fee_max_base_msat = (body.channel_fee_max_base_msat && typeof body.channel_fee_max_base_msat === 'string' && body.channel_fee_max_base_msat.includes('msat')) ? +body.channel_fee_max_base_msat?.replace('msat', '') : body.channel_fee_max_base_msat;
body.lease_fee_base_msat = (body.lease_fee_base_msat && typeof body.lease_fee_base_msat === 'string' && body.lease_fee_base_msat.includes('msat')) ? +body.lease_fee_base_msat?.replace('msat', '') : body.channel_fee_max_base_msat;
res.status(200).json(body);
}).catch((errRes) => {
const err = common.handleError(errRes, 'Channels', 'Funder Policy Error', req.session.selectedNode);

@ -76,12 +76,11 @@ export const listNodes = (req, res, next) => {
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Network', msg: 'List Nodes Finished', data: body });
body.forEach((node) => {
var _a, _b;
if (node.option_will_fund) {
node.option_will_fund.lease_fee_base_msat = (node.option_will_fund.lease_fee_base_msat && typeof node.option_will_fund.lease_fee_base_msat === 'string' &&
node.option_will_fund.lease_fee_base_msat.includes('msat')) ? (_a = node.option_will_fund.lease_fee_base_msat) === null || _a === void 0 ? void 0 : _a.replace('msat', '') : node.option_will_fund.lease_fee_base_msat;
node.option_will_fund.lease_fee_base_msat.includes('msat')) ? node.option_will_fund.lease_fee_base_msat?.replace('msat', '') : node.option_will_fund.lease_fee_base_msat;
node.option_will_fund.channel_fee_max_base_msat = (node.option_will_fund.channel_fee_max_base_msat && typeof node.option_will_fund.channel_fee_max_base_msat === 'string' &&
node.option_will_fund.channel_fee_max_base_msat.includes('msat')) ? (_b = node.option_will_fund.channel_fee_max_base_msat) === null || _b === void 0 ? void 0 : _b.replace('msat', '') : node.option_will_fund.channel_fee_max_base_msat;
node.option_will_fund.channel_fee_max_base_msat.includes('msat')) ? node.option_will_fund.channel_fee_max_base_msat?.replace('msat', '') : node.option_will_fund.channel_fee_max_base_msat;
}
return node;
});

@ -35,10 +35,9 @@ function summaryReducer(accumulator, mpp) {
return accumulator;
}
function groupBy(payments) {
var _a;
const paymentsInGroups = payments === null || payments === void 0 ? void 0 : payments.reduce(paymentReducer, {});
const paymentsGrpArray = (_a = Object.keys(paymentsInGroups)) === null || _a === void 0 ? void 0 : _a.map((key) => ((paymentsInGroups[key].length && paymentsInGroups[key].length > 1) ? common.sortDescByKey(paymentsInGroups[key], 'partid') : paymentsInGroups[key]));
return paymentsGrpArray === null || paymentsGrpArray === void 0 ? void 0 : paymentsGrpArray.reduce((acc, curr) => {
const paymentsInGroups = payments?.reduce(paymentReducer, {});
const paymentsGrpArray = Object.keys(paymentsInGroups)?.map((key) => ((paymentsInGroups[key].length && paymentsInGroups[key].length > 1) ? common.sortDescByKey(paymentsInGroups[key], 'partid') : paymentsInGroups[key]));
return paymentsGrpArray?.reduce((acc, curr) => {
let temp = {};
if (curr.length && curr.length === 1) {
temp = JSON.parse(JSON.stringify(curr[0]));
@ -48,7 +47,7 @@ function groupBy(payments) {
delete temp.partid;
}
else {
const paySummary = curr === null || curr === void 0 ? void 0 : curr.reduce(summaryReducer, { msatoshi: 0, msatoshi_sent: 0, status: (curr[0] && curr[0].status) ? curr[0].status : 'failed' });
const paySummary = curr?.reduce(summaryReducer, { msatoshi: 0, msatoshi_sent: 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,

@ -19,7 +19,7 @@ export const decodePayments = (req, res, next) => {
}
if (req.body.payments) {
const paymentsArr = req.body.payments.split(',');
return Promise.all(paymentsArr === null || paymentsArr === void 0 ? void 0 : paymentsArr.map((payment) => decodePaymentFromPaymentRequest(req.session.selectedNode, payment))).
return Promise.all(paymentsArr?.map((payment) => decodePaymentFromPaymentRequest(req.session.selectedNode, payment))).
then((values) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment List Decoded', data: values });
res.status(200).json(values);

@ -47,9 +47,8 @@ export class CLWebSocketClient {
}
};
this.connectWithClient = (clWsClt) => {
var _a;
this.logger.log({ selectedNode: clWsClt.selectedNode, level: 'INFO', fileName: 'CLWebSocket', msg: 'Connecting to the Core Lightning\'s Websocket Server..' });
const WS_LINK = ((_a = (clWsClt.selectedNode.ln_server_url)) === null || _a === void 0 ? void 0 : _a.replace(/^http/, 'ws')) + '/v1/ws';
const WS_LINK = (clWsClt.selectedNode.ln_server_url)?.replace(/^http/, 'ws') + '/v1/ws';
const mcrnHexEncoded = Buffer.from(fs.readFileSync(join(clWsClt.selectedNode.macaroon_path, 'access.macaroon'))).toString('hex');
clWsClt.webSocketClient = new WebSocket(WS_LINK, [mcrnHexEncoded, 'hex'], { rejectUnauthorized: false });
clWsClt.webSocketClient.onopen = () => {

@ -32,7 +32,7 @@ export const simplifyAllChannels = (selNode, channels) => {
return request.post(options).then((nodes) => {
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Channels', msg: 'Filtered Nodes Received', data: nodes });
let foundPeer = null;
simplifiedChannels === null || simplifiedChannels === void 0 ? void 0 : simplifiedChannels.map((channel) => {
simplifiedChannels?.map((channel) => {
foundPeer = nodes.find((channelWithAlias) => channel.nodeId === channelWithAlias.nodeId);
channel.alias = foundPeer ? foundPeer.alias : channel.nodeId.substring(0, 20);
return channel;
@ -83,7 +83,13 @@ export const getChannelStats = (req, res, next) => {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/channelstats';
options.form = {};
const today = new Date(Date.now());
const tillToday = (Math.round(today.getTime() / 1000)).toString();
const fromLastMonth = (Math.round(new Date(today.getFullYear(), today.getMonth() - 1, today.getDate() + 1, 0, 0, 0).getTime() / 1000)).toString();
options.form = {
from: fromLastMonth,
to: tillToday
};
request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Channel States Received', data: body });
res.status(201).json(body);

@ -126,7 +126,8 @@ export const getPayments = (req, res, next) => {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.url = req.session.selectedNode.ln_server_url + '/audit';
options.form = null;
const tillToday = (Math.round(new Date(Date.now()).getTime() / 1000)).toString();
options.form = { from: 0, to: tillToday };
if (common.read_dummy_data) {
common.getDummyData('Payments', req.session.selectedNode.ln_implementation).then((data) => { res.status(200).json(arrangePayments(req.session.selectedNode, data)); });
}

@ -59,18 +59,19 @@ export const listInvoices = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
options.form = {};
const tillToday = (Math.round(new Date(Date.now()).getTime() / 1000)).toString();
options.form = { from: 0, to: tillToday };
const options1 = JSON.parse(JSON.stringify(options));
options1.url = req.session.selectedNode.ln_server_url + '/listinvoices';
options1.form = {};
options1.form = { from: 0, to: tillToday };
const options2 = JSON.parse(JSON.stringify(options));
options2.url = req.session.selectedNode.ln_server_url + '/listpendinginvoices';
options2.form = {};
options2.form = { from: 0, to: tillToday };
if (common.read_dummy_data) {
return common.getDummyData('Invoices', req.session.selectedNode.ln_implementation).then((body) => {
const invoices = (!body[0] || body[0].length <= 0) ? [] : body[0];
pendingInvoices = (!body[1] || body[1].length <= 0) ? [] : body[1];
return Promise.all(invoices === null || invoices === void 0 ? void 0 : invoices.map((invoice) => getReceivedPaymentInfo(req.session.selectedNode.ln_server_url, invoice))).
return Promise.all(invoices?.map((invoice) => getReceivedPaymentInfo(req.session.selectedNode.ln_server_url, invoice))).
then((values) => res.status(200).json(invoices));
});
}
@ -81,7 +82,7 @@ export const listInvoices = (req, res, next) => {
const invoices = (!body[0] || body[0].length <= 0) ? [] : body[0];
pendingInvoices = (!body[1] || body[1].length <= 0) ? [] : body[1];
if (invoices && invoices.length > 0) {
return Promise.all(invoices === null || invoices === void 0 ? void 0 : invoices.map((invoice) => getReceivedPaymentInfo(req.session.selectedNode.ln_server_url, invoice))).
return Promise.all(invoices?.map((invoice) => getReceivedPaymentInfo(req.session.selectedNode.ln_server_url, invoice))).
then((values) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Sorted Invoices List Received', data: invoices });
return res.status(200).json(invoices);

@ -22,7 +22,7 @@ export const getSentInfoFromPaymentRequest = (selNode, payment) => {
};
export const getQueryNodes = (selNode, nodeIds) => {
options.url = selNode.ln_server_url + '/nodes';
options.form = { nodeIds: nodeIds === null || nodeIds === void 0 ? void 0 : nodeIds.reduce((acc, curr) => acc + ',' + curr) };
options.form = { nodeIds: nodeIds?.reduce((acc, curr) => acc + ',' + curr) };
return request.post(options).then((nodes) => {
logger.log({ selectedNode: selNode, level: 'DEBUG', fileName: 'Payments', msg: 'Query Nodes Received', data: nodes });
return nodes;
@ -77,16 +77,14 @@ export const queryPaymentRoute = (req, res, next) => {
};
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Query Payment Route Options', data: options.form });
request.post(options).then((body) => {
var _a;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Payments', msg: 'Query Payment Route Received', data: body });
if (body && body.routes && body.routes.length) {
let allRoutesNodeIds = [];
allRoutesNodeIds = (_a = body.routes) === null || _a === void 0 ? void 0 : _a.reduce((accRoutes, currRoute) => [...new Set([...accRoutes, ...currRoute.nodeIds])], []);
allRoutesNodeIds = body.routes?.reduce((accRoutes, currRoute) => [...new Set([...accRoutes, ...currRoute.nodeIds])], []);
return getQueryNodes(req.session.selectedNode, allRoutesNodeIds).then((nodesWithAlias) => {
let foundPeer = null;
body.routes.forEach((route, i) => {
var _a;
(_a = route.nodeIds) === null || _a === void 0 ? void 0 : _a.map((node, j) => {
route.nodeIds?.map((node, j) => {
foundPeer = nodesWithAlias.find((nodeWithAlias) => node === nodeWithAlias.nodeId);
body.routes[i].nodeIds[j] = { nodeId: node, alias: foundPeer ? foundPeer.alias : '' };
return node;
@ -113,7 +111,7 @@ export const getSentPaymentsInformation = (req, res, next) => {
}
if (req.body.payments) {
const paymentsArr = req.body.payments.split(',');
return Promise.all(paymentsArr === null || paymentsArr === void 0 ? void 0 : paymentsArr.map((payment) => getSentInfoFromPaymentRequest(req.session.selectedNode, payment))).
return Promise.all(paymentsArr?.map((payment) => getSentInfoFromPaymentRequest(req.session.selectedNode, payment))).
then((values) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Sent Information Received', data: values });
return res.status(200).json(values);

@ -32,7 +32,7 @@ export const getPeers = (req, res, next) => {
peersNodeIds = peersNodeIds.substring(1);
return getFilteredNodes(req.session.selectedNode, peersNodeIds).then((peersWithAlias) => {
let foundPeer = null;
body === null || body === void 0 ? void 0 : body.map((peer) => {
body?.map((peer) => {
foundPeer = peersWithAlias.find((peerWithAlias) => peer.nodeId === peerWithAlias.nodeId);
peer.alias = foundPeer ? foundPeer.alias : peer.nodeId.substring(0, 20);
return peer;
@ -84,7 +84,7 @@ export const connectPeer = (req, res, next) => {
peersNodeIds = peersNodeIds.substring(1);
return getFilteredNodes(req.session.selectedNode, peersNodeIds).then((peersWithAlias) => {
let foundPeer = null;
body === null || body === void 0 ? void 0 : body.map((peer) => {
body?.map((peer) => {
foundPeer = peersWithAlias.find((peerWithAlias) => peer.nodeId === peerWithAlias.nodeId);
peer.alias = foundPeer ? foundPeer.alias : peer.nodeId.substring(0, 20);
return peer;

@ -2,6 +2,7 @@ import WebSocket from 'ws';
import { Logger } from '../../utils/logger.js';
import { Common } from '../../utils/common.js';
import { WSServer } from '../../utils/webSocketServer.js';
import { ECLWSEventsEnum } from '../../models/ecl.model.js';
export class ECLWebSocketClient {
constructor() {
this.logger = Logger;
@ -45,15 +46,15 @@ export class ECLWebSocketClient {
}
};
this.connectWithClient = (eclWsClt) => {
var _a;
this.logger.log({ selectedNode: eclWsClt.selectedNode, level: 'INFO', fileName: 'ECLWebSocket', msg: 'Connecting to the Eclair\'s Websocket Server..' });
const UpdatedLNServerURL = (_a = (eclWsClt.selectedNode.ln_server_url)) === null || _a === void 0 ? void 0 : _a.replace(/^http/, 'ws');
const UpdatedLNServerURL = (eclWsClt.selectedNode.ln_server_url)?.replace(/^http/, 'ws');
const firstSubStrIndex = (UpdatedLNServerURL.indexOf('//') + 2);
const WS_LINK = UpdatedLNServerURL.slice(0, firstSubStrIndex) + ':' + eclWsClt.selectedNode.ln_api_password + '@' + UpdatedLNServerURL.slice(firstSubStrIndex) + '/ws';
eclWsClt.webSocketClient = new WebSocket(WS_LINK);
eclWsClt.webSocketClient.onopen = () => {
this.logger.log({ selectedNode: eclWsClt.selectedNode, level: 'INFO', fileName: 'ECLWebSocket', msg: 'Connected to the Eclair\'s Websocket Server..' });
this.waitTime = 0.5;
this.heartbeat(eclWsClt);
};
eclWsClt.webSocketClient.onclose = (e) => {
if (eclWsClt && eclWsClt.selectedNode && eclWsClt.selectedNode.ln_implementation === 'ECL') {
@ -67,9 +68,11 @@ export class ECLWebSocketClient {
eclWsClt.webSocketClient.onmessage = (msg) => {
this.logger.log({ selectedNode: eclWsClt.selectedNode, level: 'DEBUG', fileName: 'ECLWebSocket', msg: 'Received message from the server..', data: msg.data });
msg = (typeof msg.data === 'string') ? JSON.parse(msg.data) : msg.data;
msg['source'] = 'ECL';
const msgStr = JSON.stringify(msg);
this.wsServer.sendEventsToAllLNClients(msgStr, eclWsClt.selectedNode);
if (msg.type && msg.type !== ECLWSEventsEnum.PAY_RELAYED && msg.type !== ECLWSEventsEnum.PAY_SETTLING_ONCHAIN && msg.type !== ECLWSEventsEnum.ONION_MESSAGE_RECEIVED) {
msg['source'] = 'ECL';
const msgStr = JSON.stringify(msg);
this.wsServer.sendEventsToAllLNClients(msgStr, eclWsClt.selectedNode);
}
};
eclWsClt.webSocketClient.onerror = (err) => {
if (eclWsClt.selectedNode.ln_version === '' || !eclWsClt.selectedNode.ln_version || this.common.isVersionCompatible(eclWsClt.selectedNode.ln, '0.5.0')) {
@ -105,6 +108,17 @@ export class ECLWebSocketClient {
newClient.selectedNode = JSON.parse(JSON.stringify(newSelectedNode));
this.webSocketClients[clientIdx] = newClient;
};
this.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;
eclWsClt.webSocketClient.ping();
setTimeout(() => {
this.heartbeat(eclWsClt);
}, 59 * 1000);
};
this.wsServer.eventEmitterECL.on('CONNECT', (nodeIndex) => {
this.connect(this.common.findNode(+nodeIndex));
});

@ -28,10 +28,9 @@ export const getAllChannels = (req, res, next) => {
let remote = 0;
let total = 0;
request(options).then((body) => {
var _a;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Channels', msg: 'Channels List Received', data: body });
if (body.channels) {
return Promise.all((_a = body.channels) === null || _a === void 0 ? void 0 : _a.map((channel) => {
return Promise.all(body.channels?.map((channel) => {
local = (channel.local_balance) ? +channel.local_balance : 0;
remote = (channel.remote_balance) ? +channel.remote_balance : 0;
total = local + remote;
@ -64,22 +63,21 @@ export const getPendingChannels = (req, res, next) => {
options.url = req.session.selectedNode.ln_server_url + '/v1/channels/pending';
options.qs = req.query;
request(options).then((body) => {
var _a, _b, _c, _d;
if (!body.total_limbo_balance) {
body.total_limbo_balance = 0;
}
const promises = [];
if (body.pending_open_channels && body.pending_open_channels.length > 0) {
(_a = body.pending_open_channels) === null || _a === void 0 ? void 0 : _a.map((channel) => promises.push(getAliasForChannel(req.session.selectedNode, channel.channel)));
body.pending_open_channels?.map((channel) => promises.push(getAliasForChannel(req.session.selectedNode, channel.channel)));
}
if (body.pending_force_closing_channels && body.pending_force_closing_channels.length > 0) {
(_b = body.pending_force_closing_channels) === null || _b === void 0 ? void 0 : _b.map((channel) => promises.push(getAliasForChannel(req.session.selectedNode, channel.channel)));
body.pending_force_closing_channels?.map((channel) => promises.push(getAliasForChannel(req.session.selectedNode, channel.channel)));
}
if (body.pending_closing_channels && body.pending_closing_channels.length > 0) {
(_c = body.pending_closing_channels) === null || _c === void 0 ? void 0 : _c.map((channel) => promises.push(getAliasForChannel(req.session.selectedNode, channel.channel)));
body.pending_closing_channels?.map((channel) => promises.push(getAliasForChannel(req.session.selectedNode, channel.channel)));
}
if (body.waiting_close_channels && body.waiting_close_channels.length > 0) {
(_d = body.waiting_close_channels) === null || _d === void 0 ? void 0 : _d.map((channel) => promises.push(getAliasForChannel(req.session.selectedNode, channel.channel)));
body.waiting_close_channels?.map((channel) => promises.push(getAliasForChannel(req.session.selectedNode, channel.channel)));
}
return Promise.all(promises).then((values) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Pending Channels List Received', data: body });
@ -103,9 +101,8 @@ export const getClosedChannels = (req, res, next) => {
options.url = req.session.selectedNode.ln_server_url + '/v1/channels/closed';
options.qs = req.query;
request(options).then((body) => {
var _a;
if (body.channels && body.channels.length > 0) {
return Promise.all((_a = body.channels) === null || _a === void 0 ? void 0 : _a.map((channel) => {
return Promise.all(body.channels?.map((channel) => {
channel.close_type = (!channel.close_type) ? 'COOPERATIVE_CLOSE' : channel.close_type;
return getAliasForChannel(req.session.selectedNode, channel);
})).then((values) => {
@ -193,7 +190,6 @@ export const postTransactions = (req, res, next) => {
});
};
export const closeChannel = (req, res, next) => {
var _a;
try {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Closing Channel..' });
if (!req.session.selectedNode) {
@ -204,7 +200,7 @@ export const closeChannel = (req, res, next) => {
if (options.error) {
return res.status(options.statusCode).json({ message: options.message, error: options.error });
}
const channelpoint = (_a = req.params.channelPoint) === null || _a === void 0 ? void 0 : _a.replace(':', '/');
const channelpoint = req.params.channelPoint?.replace(':', '/');
options.url = req.session.selectedNode.ln_server_url + '/v1/channels/' + channelpoint + '?force=' + req.query.force;
if (req.query.target_conf) {
options.url = options.url + '&target_conf=' + req.query.target_conf;

@ -16,13 +16,12 @@ function getFilesList(channelBackupPath, callback) {
}
if (files && files.length > 0) {
files.forEach((file) => {
var _a;
if (!file.includes('.restored')) {
if (file.toLowerCase() === 'channel-all.bak' || file.toLowerCase() === 'backup-channel-all.bak') {
all_restore_exists = true;
}
else {
files_list.push({ channel_point: (_a = file.substring(8, file.length - 4)) === null || _a === void 0 ? void 0 : _a.replace('-', ':') });
files_list.push({ channel_point: file.substring(8, file.length - 4)?.replace('-', ':') });
}
}
});
@ -32,7 +31,6 @@ function getFilesList(channelBackupPath, callback) {
});
}
export const getBackup = (req, res, next) => {
var _a, _b;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'ChannelBackup', msg: 'Getting Channel Backup..' });
options = common.getOptions(req);
if (options.error) {
@ -46,9 +44,9 @@ export const getBackup = (req, res, next) => {
options.url = req.session.selectedNode.ln_server_url + '/v1/channels/backup';
}
else {
channel_backup_file = req.session.selectedNode.channel_backup_path + sep + 'channel-' + ((_a = req.params.channelPoint) === null || _a === void 0 ? void 0 : _a.replace(':', '-')) + '.bak';
channel_backup_file = req.session.selectedNode.channel_backup_path + sep + 'channel-' + req.params.channelPoint?.replace(':', '-') + '.bak';
message = 'Channel Backup Successful.';
const channelpoint = (_b = req.params.channelPoint) === null || _b === void 0 ? void 0 : _b.replace(':', '/');
const channelpoint = req.params.channelPoint?.replace(':', '/');
options.url = req.session.selectedNode.ln_server_url + '/v1/channels/backup/' + channelpoint;
const exists = fs.existsSync(channel_backup_file);
if (exists) {
@ -83,7 +81,6 @@ export const getBackup = (req, res, next) => {
});
};
export const postBackupVerify = (req, res, next) => {
var _a;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'ChannelBackup', msg: 'Verifying Channel Backup..' });
options = common.getOptions(req);
if (options.error) {
@ -119,7 +116,7 @@ export const postBackupVerify = (req, res, next) => {
}
else {
message = 'Channel Verify Successful.';
channel_verify_file = req.session.selectedNode.channel_backup_path + sep + 'channel-' + ((_a = req.params.channelPoint) === null || _a === void 0 ? void 0 : _a.replace(':', '-')) + '.bak';
channel_verify_file = req.session.selectedNode.channel_backup_path + sep + 'channel-' + req.params.channelPoint?.replace(':', '-') + '.bak';
const exists = fs.existsSync(channel_verify_file);
if (exists) {
verify_backup = fs.readFileSync(channel_verify_file, 'utf-8');
@ -144,7 +141,6 @@ export const postBackupVerify = (req, res, next) => {
}
};
export const postRestore = (req, res, next) => {
var _a;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'ChannelBackup', msg: 'Restoring Channel Backup..' });
options = common.getOptions(req);
if (options.error) {
@ -192,7 +188,7 @@ export const postRestore = (req, res, next) => {
}
else {
message = 'Channel Restore Successful.';
channel_restore_file = req.session.selectedNode.channel_backup_path + sep + 'restore' + sep + 'channel-' + ((_a = req.params.channelPoint) === null || _a === void 0 ? void 0 : _a.replace(':', '-')) + '.bak';
channel_restore_file = req.session.selectedNode.channel_backup_path + sep + 'restore' + sep + 'channel-' + req.params.channelPoint?.replace(':', '-') + '.bak';
const exists = fs.existsSync(channel_restore_file);
if (exists) {
restore_backup = fs.readFileSync(channel_restore_file, 'utf-8');

@ -21,11 +21,10 @@ export const getFees = (req, res, next) => {
const week_start_time = current_time - 604800;
const day_start_time = current_time - 86400;
return getAllForwardingEvents(req, month_start_time, current_time, 0, 'fees', (history) => {
var _a, _b, _c;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Fees', msg: 'Forwarding History Received', data: history });
const daily_sum = (_a = history.forwarding_events) === null || _a === void 0 ? void 0 : _a.reduce((acc, curr) => ((curr.timestamp >= day_start_time) ? [(acc[0] + 1), (acc[1] + +curr.fee_msat)] : acc), [0, 0]);
const weekly_sum = (_b = history.forwarding_events) === null || _b === void 0 ? void 0 : _b.reduce((acc, curr) => ((curr.timestamp >= week_start_time) ? [(acc[0] + 1), (acc[1] + +curr.fee_msat)] : acc), [0, 0]);
const monthly_sum = (_c = history.forwarding_events) === null || _c === void 0 ? void 0 : _c.reduce((acc, curr) => [(acc[0] + 1), (acc[1] + +curr.fee_msat)], [0, 0]);
const daily_sum = history.forwarding_events?.reduce((acc, curr) => ((curr.timestamp >= day_start_time) ? [(acc[0] + 1), (acc[1] + +curr.fee_msat)] : acc), [0, 0]);
const weekly_sum = history.forwarding_events?.reduce((acc, curr) => ((curr.timestamp >= week_start_time) ? [(acc[0] + 1), (acc[1] + +curr.fee_msat)] : acc), [0, 0]);
const monthly_sum = history.forwarding_events?.reduce((acc, curr) => [(acc[0] + 1), (acc[1] + +curr.fee_msat)], [0, 0]);
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Fees', msg: 'Daily Sum (Transactions, Fee)', data: daily_sum });
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Fees', msg: 'Weekly Sum (Transactions, Fee)', data: weekly_sum });
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Fees', msg: 'Monthly Sum (Transactions, Fee)', data: monthly_sum });

@ -9,7 +9,6 @@ const common = Common;
const lndWsClient = LNDWSClient;
const databaseService = Database;
export const getInfo = (req, res, next) => {
var _a;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'GetInfo', msg: 'Getting LND Node Information..' });
common.logEnvVariables(req);
common.setOptions(req);
@ -26,7 +25,7 @@ export const getInfo = (req, res, next) => {
return res.status(err.statusCode).json({ message: err.message, error: err.error });
}
else {
(_a = common.nodes) === null || _a === void 0 ? void 0 : _a.map((node) => {
common.nodes?.map((node) => {
if (node.ln_implementation === 'LND') {
common.getAllNodeAllChannelBackup(node);
}

@ -84,13 +84,11 @@ export const getQueryRoutes = (req, res, next) => {
}
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Graph', msg: 'Query Routes URL', data: options.url });
request(options).then((body) => {
var _a;
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Graph', msg: 'Query Routes Received', data: body });
if (body.routes && body.routes.length && body.routes.length > 0 && body.routes[0].hops && body.routes[0].hops.length && body.routes[0].hops.length > 0) {
return Promise.all((_a = body.routes[0].hops) === null || _a === void 0 ? void 0 : _a.map((hop) => getAliasFromPubkey(req.session.selectedNode, hop.pub_key))).
return Promise.all(body.routes[0].hops?.map((hop) => getAliasFromPubkey(req.session.selectedNode, hop.pub_key))).
then((values) => {
var _a;
(_a = body.routes[0].hops) === null || _a === void 0 ? void 0 : _a.map((hop, i) => {
body.routes[0].hops?.map((hop, i) => {
hop.hop_sequence = i + 1;
hop.pubkey_alias = values[i];
return hop;
@ -150,7 +148,7 @@ export const getAliasesForPubkeys = (req, res, next) => {
}
if (req.query.pubkeys) {
const pubkeyArr = req.query.pubkeys.split(',');
return Promise.all(pubkeyArr === null || pubkeyArr === void 0 ? void 0 : pubkeyArr.map((pubkey) => getAliasFromPubkey(req.session.selectedNode, pubkey))).
return Promise.all(pubkeyArr?.map((pubkey) => getAliasFromPubkey(req.session.selectedNode, pubkey))).
then((values) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Graph', msg: 'Node Alias', data: values });
res.status(200).json(values);

@ -34,7 +34,7 @@ export const decodePayments = (req, res, next) => {
}
if (req.body.payments) {
const paymentsArr = req.body.payments.split(',');
return Promise.all(paymentsArr === null || paymentsArr === void 0 ? void 0 : paymentsArr.map((payment) => decodePaymentFromPaymentRequest(req.session.selectedNode, payment))).
return Promise.all(paymentsArr?.map((payment) => decodePaymentFromPaymentRequest(req.session.selectedNode, payment))).
then((values) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'PayRequest', msg: 'Payment List Decoded', data: values });
res.status(200).json(values);

@ -25,7 +25,7 @@ export const getPeers = (req, res, next) => {
request(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'Peers', msg: 'Peers List Received', data: body });
const peers = !body.peers ? [] : body.peers;
return Promise.all(peers === null || peers === void 0 ? void 0 : peers.map((peer) => getAliasForPeers(req.session.selectedNode, peer))).then((values) => {
return Promise.all(peers?.map((peer) => getAliasForPeers(req.session.selectedNode, peer))).then((values) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Sorted Peers List Received', data: body.peers });
res.status(200).json(body.peers);
});
@ -50,7 +50,7 @@ export const postPeer = (req, res, next) => {
options.url = req.session.selectedNode.ln_server_url + '/v1/peers';
request(options).then((body) => {
const peers = (!body.peers) ? [] : body.peers;
return Promise.all(peers === null || peers === void 0 ? void 0 : peers.map((peer) => getAliasForPeers(req.session.selectedNode, peer))).then((values) => {
return Promise.all(peers?.map((peer) => getAliasForPeers(req.session.selectedNode, peer))).then((values) => {
if (body.peers) {
body.peers = common.newestOnTop(body.peers, 'pub_key', req.body.pubkey);
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Peers', msg: 'Peers List after Connect Received', data: body });

@ -42,8 +42,7 @@ export class LNDWebSocketClient {
});
};
this.subscribeToInvoice = (options, selectedNode, rHash) => {
var _a;
rHash = (_a = rHash === null || rHash === void 0 ? void 0 : rHash.replace(/\+/g, '-')) === null || _a === void 0 ? void 0 : _a.replace(/[/]/g, '_');
rHash = rHash?.replace(/\+/g, '-')?.replace(/[/]/g, '_');
this.logger.log({ selectedNode: selectedNode, level: 'INFO', fileName: 'WebSocketClient', msg: 'Subscribing to Invoice ' + rHash + ' ..' });
options.url = selectedNode.ln_server_url + '/v2/invoices/subscribe/' + rHash;
request(options).then((msg) => {

@ -231,7 +231,6 @@ export const getConfig = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'RTLConf', msg: 'Node Type', data: req.params.nodeType });
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'RTLConf', msg: 'File Path', data: confFile });
fs.readFile(confFile, 'utf8', (errRes, data) => {
var _a;
if (errRes) {
const errMsg = 'Reading Config Error';
const err = common.handleError({ statusCode: 500, message: errMsg, error: errRes }, 'RTLConf', errMsg, req.session.selectedNode);
@ -244,7 +243,7 @@ export const getConfig = (req, res, next) => {
}
else {
fileFormat = 'INI';
data = data === null || data === void 0 ? void 0 : data.replace('color=#', 'color=');
data = data?.replace('color=#', 'color=');
jsonConfig = ini.parse(data);
if (jsonConfig['Application Options'] && jsonConfig['Application Options'].color) {
jsonConfig['Application Options'].color = '#' + jsonConfig['Application Options'].color;
@ -255,16 +254,15 @@ export const getConfig = (req, res, next) => {
}
}
jsonConfig = maskPasswords(jsonConfig);
const responseJSON = (fileFormat === 'JSON') ? jsonConfig : (_a = ini.stringify(jsonConfig)) === null || _a === void 0 ? void 0 : _a.replace('color=\\#', 'color=#');
const responseJSON = (fileFormat === 'JSON') ? jsonConfig : ini.stringify(jsonConfig)?.replace('color=\\#', 'color=#');
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Configuration File Data Received', data: responseJSON });
res.status(200).json({ format: fileFormat, data: responseJSON });
}
});
};
export const getFile = (req, res, next) => {
var _a;
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'RTLConf', msg: 'Getting File..' });
const file = req.query.path ? req.query.path : (req.session.selectedNode.channel_backup_path + sep + 'channel-' + ((_a = req.query.channel) === null || _a === void 0 ? void 0 : _a.replace(':', '-')) + '.bak');
const file = req.query.path ? req.query.path : (req.session.selectedNode.channel_backup_path + sep + 'channel-' + req.query.channel?.replace(':', '-') + '.bak');
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'RTLConf', msg: 'Channel Point', data: req.query.channel });
logger.log({ selectedNode: req.session.selectedNode, level: 'DEBUG', fileName: 'RTLConf', msg: 'File Path', data: file });
fs.readFile(file, 'utf8', (errRes, data) => {

@ -133,7 +133,7 @@ export var CollectionsEnum;
CollectionsEnum["OFFERS"] = "Offers";
CollectionsEnum["PAGE_SETTINGS"] = "PageSettings";
})(CollectionsEnum || (CollectionsEnum = {}));
export const CollectionFieldsEnum = Object.assign(Object.assign(Object.assign({}, OfferFieldsEnum), PageSettingsFieldsEnum), TableSettingsFieldsEnum);
export const CollectionFieldsEnum = { ...OfferFieldsEnum, ...PageSettingsFieldsEnum, ...TableSettingsFieldsEnum };
export const LNDCollection = [CollectionsEnum.PAGE_SETTINGS];
export const ECLCollection = [CollectionsEnum.PAGE_SETTINGS];
export const CLNCollection = [CollectionsEnum.PAGE_SETTINGS, CollectionsEnum.OFFERS];

@ -0,0 +1,12 @@
export var ECLWSEventsEnum;
(function (ECLWSEventsEnum) {
ECLWSEventsEnum["PAY_RECEIVED"] = "payment-received";
ECLWSEventsEnum["PAY_RELAYED"] = "payment-relayed";
ECLWSEventsEnum["PAY_SENT"] = "payment-sent";
ECLWSEventsEnum["PAY_SETTLING_ONCHAIN"] = "payment-settling-onchain";
ECLWSEventsEnum["PAY_FAILED"] = "payment-failed";
ECLWSEventsEnum["CHANNEL_OPENED"] = "channel-opened";
ECLWSEventsEnum["CHANNEL_STATE_CHANGED"] = "channel-state-changed";
ECLWSEventsEnum["CHANNEL_CLOSED"] = "channel-closed";
ECLWSEventsEnum["ONION_MESSAGE_RECEIVED"] = "onion-message-received";
})(ECLWSEventsEnum || (ECLWSEventsEnum = {}));

@ -41,8 +41,8 @@ export class ExpressApplication {
this.app.use(this.common.baseHref + '/api/ecl', eclRoutes);
this.app.use(this.common.baseHref, express.static(join(this.directoryName, '../..', 'frontend')));
this.app.use((req, res, next) => {
res.cookie('XSRF-TOKEN', req.csrfToken ? req.csrfToken() : ''); // RTL Angular Frontend
res.setHeader('XSRF-TOKEN', req.csrfToken ? req.csrfToken() : ''); // RTL Quickpay JQuery
res.cookie('XSRF-TOKEN', req.csrfToken ? req.csrfToken() : (req.cookies && req.cookies._csrf) ? req.cookies._csrf : ''); // RTL Angular Frontend
res.setHeader('XSRF-TOKEN', req.csrfToken ? req.csrfToken() : (req.cookies && req.cookies._csrf) ? req.cookies._csrf : ''); // RTL Quickpay JQuery
res.sendFile(join(this.directoryName, '../..', 'frontend', 'index.html'));
});
this.app.use((err, req, res, next) => {

@ -18,16 +18,14 @@ export const isAuthenticated = (req, res, next) => {
}
};
export const verifyWSUser = (info, next) => {
var _a;
const headers = JSON.parse(JSON.stringify(info.req.headers));
const protocols = !info.req.headers['sec-websocket-protocol'] ? [] : (_a = info.req.headers['sec-websocket-protocol'].split(',')) === null || _a === void 0 ? void 0 : _a.map((s) => s.trim());
const protocols = !info.req.headers['sec-websocket-protocol'] ? [] : info.req.headers['sec-websocket-protocol'].split(',')?.map((s) => s.trim());
const jwToken = (protocols && protocols.length > 0) ? protocols[0] : '';
if (!jwToken || jwToken === '') {
next(false, 401, 'Authentication Failed! Please Login First!');
}
else {
jwt.verify(jwToken, common.secret_key, (verificationErr) => {
var _a, _b, _c;
if (verificationErr) {
next(false, 401, 'Authentication Failed! Please Login First!');
}
@ -42,7 +40,7 @@ export const verifyWSUser = (info, next) => {
}
let cookies = null;
try {
cookies = '{"' + ((_c = (_b = (_a = headers.cookie) === null || _a === void 0 ? void 0 : _a.replace(/ /g, '')) === null || _b === void 0 ? void 0 : _b.replace(/;/g, '","').trim()) === null || _c === void 0 ? void 0 : _c.replace(/[=]/g, '":"')) + '"}';
cookies = '{"' + headers.cookie?.replace(/ /g, '')?.replace(/;/g, '","').trim()?.replace(/[=]/g, '":"') + '"}';
updatedReq['cookies'] = JSON.parse(cookies);
}
catch (err) {

@ -201,18 +201,17 @@ export class CommonService {
};
this.newestOnTop = (array, key, value) => {
const newlyAddedRecord = array.splice(array.findIndex((item) => item[key] === value), 1);
array === null || array === void 0 ? void 0 : array.unshift(newlyAddedRecord[0]);
array?.unshift(newlyAddedRecord[0]);
return array;
};
this.camelCase = (str) => { var _a, _b; return (_b = (_a = str === null || str === void 0 ? void 0 : str.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => (word.toUpperCase()))) === null || _a === void 0 ? void 0 : _a.replace(/\s+/g, '')) === null || _b === void 0 ? void 0 : _b.replace(/-/g, ' '); };
this.camelCase = (str) => str?.replace(/(?:^\w|[A-Z]|\b\w)/g, (word, index) => (word.toUpperCase()))?.replace(/\s+/g, '')?.replace(/-/g, ' ');
this.titleCase = (str) => {
var _a, _b;
if (str.indexOf('!\n') > 0 || str.indexOf('.\n') > 0) {
return (_a = str.split('\n')) === null || _a === void 0 ? void 0 : _a.reduce((accumulator, currentStr) => accumulator + currentStr.charAt(0).toUpperCase() + currentStr.substring(1).toLowerCase() + '\n', '');
return str.split('\n')?.reduce((accumulator, currentStr) => accumulator + currentStr.charAt(0).toUpperCase() + currentStr.substring(1).toLowerCase() + '\n', '');
}
else {
if (str.indexOf(' ') > 0) {
return (_b = str.split(' ')) === null || _b === void 0 ? void 0 : _b.reduce((accumulator, currentStr) => accumulator + currentStr.charAt(0).toUpperCase() + currentStr.substring(1).toLowerCase() + ' ', '');
return str.split(' ')?.reduce((accumulator, currentStr) => accumulator + currentStr.charAt(0).toUpperCase() + currentStr.substring(1).toLowerCase() + ' ', '');
}
else {
return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
@ -276,7 +275,7 @@ export class CommonService {
(err.message && typeof err.message === 'string') ? err.message : (typeof err === 'string') ? err : 'Unknown Error')
};
}
if (selectedNode.ln_implementation === 'ECL' && err.message.indexOf('Authentication Error') < 0 && err.name === 'StatusCodeError') {
if (selectedNode.ln_implementation === 'ECL' && err.message && err.message.indexOf('Authentication Error') < 0 && err.name && err.name === 'StatusCodeError') {
newErrorObj.statusCode = 500;
}
return newErrorObj;
@ -345,9 +344,8 @@ export class CommonService {
}
};
this.createDirectory = (directoryName) => {
var _a;
const initDir = isAbsolute(directoryName) ? sep : '';
(_a = directoryName.split(sep)) === null || _a === void 0 ? void 0 : _a.reduce((parentDir, childDir) => {
directoryName.split(sep)?.reduce((parentDir, childDir) => {
const curDir = resolve(parentDir, childDir);
try {
if (!fs.existsSync(curDir)) {
@ -416,9 +414,8 @@ export class CommonService {
});
};
this.isVersionCompatible = (currentVersion, checkVersion) => {
var _a;
if (currentVersion) {
const versionsArr = ((_a = currentVersion.trim()) === null || _a === void 0 ? void 0 : _a.replace('v', '').split('-')[0].split('.')) || [];
const versionsArr = currentVersion.trim()?.replace('v', '').split('-')[0].split('.') || [];
const checkVersionsArr = checkVersion.split('.');
return (+versionsArr[0] > +checkVersionsArr[0]) ||
(+versionsArr[0] === +checkVersionsArr[0] && +versionsArr[1] > +checkVersionsArr[1]) ||

@ -65,7 +65,7 @@ export class ConfigService {
themeColor: 'PURPLE',
channelBackupPath: channelBackupPath,
logLevel: 'ERROR',
lnServerUrl: 'https://localhost:8080',
lnServerUrl: 'https://127.0.0.1:8080',
fiatConversion: false,
unannouncedChannels: false
}

@ -210,7 +210,7 @@ export class DatabaseAdapter {
try {
this.common.createDirectory(this.dbFilePath);
const oldOffers = JSON.parse(fs.readFileSync(oldFilePath, 'utf-8'));
fs.writeFileSync(oldFilePath, JSON.stringify(oldOffers.Offers, null, 2));
fs.writeFileSync(oldFilePath, JSON.stringify(oldOffers.Offers ? oldOffers.Offers : [], null, 2));
fs.renameSync(oldFilePath, newFilePath);
}
catch (err) {

@ -31,7 +31,6 @@ export class RTLWebSocketServer {
this.logger.log({ selectedNode: this.common.initSelectedNode, level: 'INFO', fileName: 'WebSocketServer', msg: 'Connecting Websocket Server..' });
this.webSocketServer = new WebSocketServer({ noServer: true, path: this.common.baseHref + '/api/ws', verifyClient: (process.env.NODE_ENV === 'development') ? null : verifyWSUser });
httpServer.on('upgrade', (request, socket, head) => {
var _a;
if (request.headers['upgrade'] !== 'websocket') {
socket.end('HTTP/1.1 400 Bad Request');
return;
@ -39,7 +38,7 @@ export class RTLWebSocketServer {
const acceptKey = request.headers['sec-websocket-key'];
const hash = this.generateAcceptValue(acceptKey);
const responseHeaders = ['HTTP/1.1 101 Web Socket Protocol Handshake', 'Upgrade: WebSocket', 'Connection: Upgrade', 'Sec-WebSocket-Accept: ' + hash];
const protocols = !request.headers['sec-websocket-protocol'] ? [] : (_a = request.headers['sec-websocket-protocol'].split(',')) === null || _a === void 0 ? void 0 : _a.map((s) => s.trim());
const protocols = !request.headers['sec-websocket-protocol'] ? [] : request.headers['sec-websocket-protocol'].split(',')?.map((s) => s.trim());
if (protocols.includes('json')) {
responseHeaders.push('Sec-WebSocket-Protocol: json');
}
@ -53,8 +52,7 @@ export class RTLWebSocketServer {
this.webSocketServer.emit('connection', websocket, request);
};
this.mountEventsOnConnection = (websocket, request) => {
var _a;
const protocols = !request.headers['sec-websocket-protocol'] ? [] : (_a = request.headers['sec-websocket-protocol'].split(',')) === null || _a === void 0 ? void 0 : _a.map((s) => s.trim());
const protocols = !request.headers['sec-websocket-protocol'] ? [] : request.headers['sec-websocket-protocol'].split(',')?.map((s) => s.trim());
const cookies = request.headers.cookie ? parse(request.headers.cookie) : null;
websocket.clientId = Date.now();
websocket.isAlive = true;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -601,6 +601,31 @@ 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
@ -1006,31 +1031,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
clone-deep
MIT
The MIT License (MIT)
Copyright (c) 2014-2018, Jon Schlinkert.
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.
convert-hex
convert-string
@ -1774,78 +1774,210 @@ PERFORMANCE OF THIS SOFTWARE.
is-plain-object
MIT
The MIT License (MIT)
Copyright (c) 2014-2017, Jon Schlinkert.
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.
isobject
MIT
The MIT License (MIT)
Copyright (c) 2014-2017, Jon Schlinkert.
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.
kind-of
MIT
The MIT License (MIT)
Copyright (c) 2014-2017, Jon Schlinkert.
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.
material-icons
Apache-2.0
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.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
md5.js
@ -2203,6 +2335,25 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
rfdc
MIT
Copyright 2019 "David Mark Clements <david.mark.clements@gmail.com>"
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.
ripemd160
MIT
The MIT License (MIT)
@ -2228,6 +2379,211 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
roboto-fontface
Apache-2.0
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2013 Christian Hoffmeister
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
rxjs
Apache-2.0
Apache License
@ -2539,31 +2895,6 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
sha256
shallow-clone
MIT
The MIT License (MIT)
Copyright (c) 2015-present, Jon Schlinkert.
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.
stream-browserify
MIT
MIT License

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -10,9 +10,10 @@
<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>@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}html{width:100%;height:99%;line-height:1.5;overflow-x:hidden;font-family:Roboto,sans-serif!important;font-size:62.5%}body{box-sizing:border-box;height:100%;margin:0;overflow:hidden}*{margin:0;padding:0}</style><link rel="stylesheet" href="styles.74a7770ce3bccfdd.css" media="print" onload="this.media='all'"><noscript><link rel="stylesheet" href="styles.74a7770ce3bccfdd.css"></noscript></head>
<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>
<rtl-app></rtl-app>
<script src="runtime.4c81b553a9df7303.js" type="module"></script><script src="polyfills.eddc63f1737a019a.js" type="module"></script><script src="main.41600bb9f8f7e3c5.js" type="module"></script>
<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>
</body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -1 +0,0 @@
(()=>{"use strict";var e,v={},g={};function r(e){var n=g[e];if(void 0!==n)return n.exports;var t=g[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=(n,t,f,o)=>{if(!t){var a=1/0;for(i=0;i<e.length;i++){for(var[t,f,o]=e[i],c=!0,u=0;u<t.length;u++)(!1&o||a>=o)&&Object.keys(r.O).every(b=>r.O[b](t[u]))?t.splice(u--,1):(c=!1,o<a&&(a=o));if(c){e.splice(i--,1);var l=f();void 0!==l&&(n=l)}}return n}o=o||0;for(var i=e.length;i>0&&e[i-1][2]>o;i--)e[i]=e[i-1];e[i]=[t,f,o]},r.n=e=>{var n=e&&e.__esModule?()=>e.default:()=>e;return r.d(n,{a:n}),n},r.d=(e,n)=>{for(var t in n)r.o(n,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:n[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((n,t)=>(r.f[t](e,n),n),[])),r.u=e=>e+"."+{258:"fb8729850462aa0e",267:"4a643eeda98f6031",564:"c60bd98c3a9d472b",636:"2c7ab7c33992b609"}[e]+".js",r.miniCssF=e=>{},r.o=(e,n)=>Object.prototype.hasOwnProperty.call(e,n),(()=>{var e={},n="RTLApp:";r.l=(t,f,o,i)=>{if(e[t])e[t].push(f);else{var a,c;if(void 0!==o)for(var u=document.getElementsByTagName("script"),l=0;l<u.length;l++){var d=u[l];if(d.getAttribute("src")==t||d.getAttribute("data-webpack")==n+o){a=d;break}}a||(c=!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",n+o),a.src=r.tu(t)),e[t]=[f];var s=(m,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(_=>_(b)),m)return m(b)},p=setTimeout(s.bind(null,void 0,{type:"timeout",target:a}),12e4);a.onerror=s.bind(null,a.onerror),a.onload=s.bind(null,a.onload),c&&document.head.appendChild(a)}}})(),r.r=e=>{"undefined"!=typeof Symbol&&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:n=>n},"undefined"!=typeof trustedTypes&&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=(f,o)=>{var i=r.o(e,f)?e[f]:void 0;if(0!==i)if(i)o.push(i[2]);else if(666!=f){var a=new Promise((d,s)=>i=e[f]=[d,s]);o.push(i[2]=a);var c=r.p+r.u(f),u=new Error;r.l(c,d=>{if(r.o(e,f)&&(0!==(i=e[f])&&(e[f]=void 0),i)){var s=d&&("load"===d.type?"missing":d.type),p=d&&d.target&&d.target.src;u.message="Loading chunk "+f+" failed.\n("+s+": "+p+")",u.name="ChunkLoadError",u.type=s,u.request=p,i[1](u)}},"chunk-"+f,f)}else e[f]=0},r.O.j=f=>0===e[f];var n=(f,o)=>{var u,l,[i,a,c]=o,d=0;if(i.some(p=>0!==e[p])){for(u in a)r.o(a,u)&&(r.m[u]=a[u]);if(c)var s=c(r)}for(f&&f(o);d<i.length;d++)r.o(e,l=i[d])&&e[l]&&e[l][0](),e[l]=0;return r.O(s)},t=self.webpackChunkRTLApp=self.webpackChunkRTLApp||[];t.forEach(n.bind(null,0)),t.push=n.bind(null,t.push.bind(t))})()})();

@ -0,0 +1 @@
(()=>{"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

File diff suppressed because one or more lines are too long

19071
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -1,6 +1,6 @@
{
"name": "rtl",
"version": "0.13.2-beta",
"version": "0.13.3-beta",
"license": "MIT",
"type": "module",
"scripts": {
@ -8,7 +8,7 @@
"start": "ng serve --open",
"prebuildfrontendtest": "node src/prebuild.cjs",
"prebuildfrontend": "node src/prebuild.cjs",
"buildfrontendtest": "ng test --watch=false && ng build --configuration production",
"buildfrontendtest": "ng test --watch=false && ng build",
"buildfrontend": "ng build --configuration production",
"buildbackend": "tsc --project tsconfig.json",
"watchbackend": "tsc --project tsconfig.json --watch",
@ -21,75 +21,76 @@
},
"private": true,
"dependencies": {
"@ngrx/effects": "^13.2.0",
"@ngrx/store": "^13.2.0",
"@swimlane/ngx-charts": "^20.1.0",
"@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",
"angular-user-idle": "^3.0.0",
"atob": "^2.1.2",
"cookie-parser": "^1.4.6",
"crypto-browserify": "^3.12.0",
"csurf": "^1.11.0",
"express": "^4.18.1",
"express": "^4.18.2",
"express-session": "^1.17.3",
"hocon-parser": "^1.0.1",
"ini": "^3.0.0",
"ini": "^3.0.1",
"jsonwebtoken": "^8.5.1",
"ng-qrcode": "^6.0.0",
"ng-qrcode": "^8.0.1",
"ngx-perfect-scrollbar-next": "^10.1.1",
"otplib": "^12.0.1",
"pdfmake": "^0.2.5",
"pdfmake": "^0.2.6",
"request": "^2.88.2",
"request-promise": "^4.2.6",
"rxjs": "^7.4.0",
"rxjs": "~7.5.0",
"sha256": "^0.2.0",
"stream-browserify": "^3.0.0",
"tslib": "^2.3.0",
"ws": "^8.8.1",
"zone.js": "~0.11.4"
"ws": "^8.11.0",
"zone.js": "~0.12.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "~13.3.5",
"@angular-eslint/builder": "^12.2.1",
"@angular-eslint/eslint-plugin": "13.0.1",
"@angular-eslint/eslint-plugin-template": "13.0.1",
"@angular-eslint/schematics": "13.0.1",
"@angular-eslint/template-parser": "13.0.1",
"@angular/animations": "~13.3.0",
"@angular/cdk": "^13.3.9",
"@angular/cli": "~13.3.5",
"@angular/common": "~13.3.0",
"@angular/compiler": "~13.3.0",
"@angular/compiler-cli": "~13.3.0",
"@angular/core": "~13.3.0",
"@angular/flex-layout": "^13.0.0-beta.38",
"@angular/forms": "~13.3.0",
"@angular/material": "^13.3.9",
"@angular/platform-browser": "~13.3.0",
"@angular/platform-browser-dynamic": "~13.3.0",
"@angular/router": "~13.3.0",
"@fortawesome/angular-fontawesome": "^0.10.2",
"@fortawesome/fontawesome-svg-core": "^6.1.2",
"@fortawesome/free-regular-svg-icons": "^6.1.2",
"@fortawesome/free-solid-svg-icons": "^6.1.2",
"@ngrx/store-devtools": "^13.0.2",
"@types/jasmine": "~3.10.0",
"@types/node": "^12.11.1",
"@typescript-eslint/eslint-plugin": "^5.33.0",
"@typescript-eslint/parser": "^5.33.0",
"crypto-browserify": "^3.12.0",
"dotenv": "^8.2.0",
"eslint": "^8.21.0",
"eslint-plugin-deprecation": "^1.2.1",
"jasmine-core": "~4.0.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~6.3.0",
"@angular-devkit/build-angular": "^15.0.1",
"@angular-eslint/builder": "^15.1.0",
"@angular-eslint/eslint-plugin": "^15.1.0",
"@angular-eslint/eslint-plugin-template": "^15.1.0",
"@angular-eslint/schematics": "^15.1.0",
"@angular-eslint/template-parser": "^15.1.0",
"@angular/cdk": "^15.0.1",
"@angular/cli": "~15.0.1",
"@angular/compiler-cli": "^15.0.0",
"@angular/flex-layout": "^14.0.0-beta.41",
"@angular/material": "^15.0.1",
"@fortawesome/angular-fontawesome": "^0.12.0",
"@fortawesome/fontawesome-svg-core": "^6.2.1",
"@fortawesome/free-regular-svg-icons": "^6.2.1",
"@fortawesome/free-solid-svg-icons": "^6.2.1",
"@ngrx/store-devtools": "^15.0.0",
"@types/jasmine": "~4.3.0",
"@types/node": "^18.11.13",
"@typescript-eslint/eslint-plugin": "^5.44.0",
"@typescript-eslint/parser": "^5.44.0",
"dotenv": "^16.0.3",
"eslint": "^8.28.0",
"eslint-plugin-deprecation": "^1.3.3",
"jasmine-core": "~4.5.0",
"jasmine-spec-reporter": "^7.0.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.1.0",
"karma-jasmine": "~4.0.0",
"karma-jasmine-html-reporter": "~1.7.0",
"material-design-icons": "^3.0.1",
"nodemon": "~2.0.19",
"protractor": "~7.0.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.0.0",
"material-icons": "^1.13.1",
"nodemon": "^2.0.20",
"protractor": "^7.0.0",
"roboto-fontface": "^0.10.0",
"stream-browserify": "^3.0.0",
"ts-node": "~10.9.1",
"typescript": "~4.6.2"
"ts-node": "^10.9.1",
"typescript": "~4.8.4"
}
}

@ -81,7 +81,13 @@ export const getChannelStats = (req, res, next) => {
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 + '/channelstats';
options.form = {};
const today = new Date(Date.now());
const tillToday = (Math.round(today.getTime() / 1000)).toString();
const fromLastMonth = (Math.round(new Date(today.getFullYear(), today.getMonth() - 1, today.getDate() + 1, 0, 0, 0).getTime() / 1000)).toString();
options.form = {
from: fromLastMonth,
to: tillToday
};
request.post(options).then((body) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Channels', msg: 'Channel States Received', data: body });
res.status(201).json(body);

@ -107,7 +107,8 @@ export const getPayments = (req, res, next) => {
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 + '/audit';
options.form = null;
const tillToday = (Math.round(new Date(Date.now()).getTime() / 1000)).toString();
options.form = { from: 0, to: tillToday };
if (common.read_dummy_data) {
common.getDummyData('Payments', req.session.selectedNode.ln_implementation).then((data) => { res.status(200).json(arrangePayments(req.session.selectedNode, data)); });
} else {

@ -55,13 +55,14 @@ export const listInvoices = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Invoices', msg: 'Getting List Invoices..' });
options = common.getOptions(req);
if (options.error) { return res.status(options.statusCode).json({ message: options.message, error: options.error }); }
options.form = {};
const tillToday = (Math.round(new Date(Date.now()).getTime() / 1000)).toString();
options.form = { from: 0, to: tillToday };
const options1 = JSON.parse(JSON.stringify(options));
options1.url = req.session.selectedNode.ln_server_url + '/listinvoices';
options1.form = {};
options1.form = { from: 0, to: tillToday };
const options2 = JSON.parse(JSON.stringify(options));
options2.url = req.session.selectedNode.ln_server_url + '/listpendinginvoices';
options2.form = {};
options2.form = { from: 0, to: tillToday };
if (common.read_dummy_data) {
return common.getDummyData('Invoices', req.session.selectedNode.ln_implementation).then((body) => {
const invoices = (!body[0] || body[0].length <= 0) ? [] : body[0];

@ -4,6 +4,7 @@ import { Logger, LoggerService } from '../../utils/logger.js';
import { Common, CommonService } from '../../utils/common.js';
import { WSServer } from '../../utils/webSocketServer.js';
import { CommonSelectedNode } from '../../models/config.model.js';
import { ECLWSEventsEnum } from '../../models/ecl.model.js';
export class ECLWebSocketClient {
@ -64,6 +65,7 @@ export class ECLWebSocketClient {
eclWsClt.webSocketClient.onopen = () => {
this.logger.log({ selectedNode: eclWsClt.selectedNode, level: 'INFO', fileName: 'ECLWebSocket', msg: 'Connected to the Eclair\'s Websocket Server..' });
this.waitTime = 0.5;
this.heartbeat(eclWsClt);
};
eclWsClt.webSocketClient.onclose = (e) => {
@ -77,9 +79,11 @@ export class ECLWebSocketClient {
eclWsClt.webSocketClient.onmessage = (msg) => {
this.logger.log({ selectedNode: eclWsClt.selectedNode, level: 'DEBUG', fileName: 'ECLWebSocket', msg: 'Received message from the server..', data: msg.data });
msg = (typeof msg.data === 'string') ? JSON.parse(msg.data) : msg.data;
msg['source'] = 'ECL';
const msgStr = JSON.stringify(msg);
this.wsServer.sendEventsToAllLNClients(msgStr, eclWsClt.selectedNode);
if (msg.type && msg.type !== ECLWSEventsEnum.PAY_RELAYED && msg.type !== ECLWSEventsEnum.PAY_SETTLING_ONCHAIN && msg.type !== ECLWSEventsEnum.ONION_MESSAGE_RECEIVED) {
msg['source'] = 'ECL';
const msgStr = JSON.stringify(msg);
this.wsServer.sendEventsToAllLNClients(msgStr, eclWsClt.selectedNode);
}
};
eclWsClt.webSocketClient.onerror = (err) => {
@ -93,6 +97,7 @@ export class ECLWebSocketClient {
eclWsClt.reConnect = false;
}
};
};
public disconnect = (selectedNode: CommonSelectedNode) => {
@ -114,6 +119,15 @@ export class ECLWebSocketClient {
this.webSocketClients[clientIdx] = newClient;
};
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;
eclWsClt.webSocketClient.ping();
setTimeout(() => {
this.heartbeat(eclWsClt);
}, 59 * 1000);
}
}
export const ECLWSClient = new ECLWebSocketClient();

@ -0,0 +1,12 @@
export enum ECLWSEventsEnum {
PAY_RECEIVED = 'payment-received',
PAY_RELAYED = 'payment-relayed',
PAY_SENT = 'payment-sent',
PAY_SETTLING_ONCHAIN = 'payment-settling-onchain',
PAY_FAILED = 'payment-failed',
CHANNEL_OPENED = 'channel-opened',
CHANNEL_STATE_CHANGED = 'channel-state-changed',
CHANNEL_CLOSED = 'channel-closed',
ONION_MESSAGE_RECEIVED = 'onion-message-received'
}

@ -63,8 +63,8 @@ export class ExpressApplication {
this.app.use(this.common.baseHref + '/api/ecl', eclRoutes);
this.app.use(this.common.baseHref, express.static(join(this.directoryName, '../..', 'frontend')));
this.app.use((req: any, res, next) => {
res.cookie('XSRF-TOKEN', req.csrfToken ? req.csrfToken() : ''); // RTL Angular Frontend
res.setHeader('XSRF-TOKEN', req.csrfToken ? req.csrfToken() : ''); // RTL Quickpay JQuery
res.cookie('XSRF-TOKEN', req.csrfToken ? req.csrfToken() : (req.cookies && req.cookies._csrf) ? req.cookies._csrf : ''); // RTL Angular Frontend
res.setHeader('XSRF-TOKEN', req.csrfToken ? req.csrfToken() : (req.cookies && req.cookies._csrf) ? req.cookies._csrf : ''); // RTL Quickpay JQuery
res.sendFile(join(this.directoryName, '../..', 'frontend', 'index.html'));
});
this.app.use((err, req, res, next) => {

@ -292,7 +292,7 @@ export class CommonService {
)
};
}
if (selectedNode.ln_implementation === 'ECL' && err.message.indexOf('Authentication Error') < 0 && err.name === 'StatusCodeError') { newErrorObj.statusCode = 500; }
if (selectedNode.ln_implementation === 'ECL' && err.message && err.message.indexOf('Authentication Error') < 0 && err.name && err.name === 'StatusCodeError') { newErrorObj.statusCode = 500; }
return newErrorObj;
};

@ -69,7 +69,7 @@ export class ConfigService {
themeColor: 'PURPLE',
channelBackupPath: channelBackupPath,
logLevel: 'ERROR',
lnServerUrl: 'https://localhost:8080',
lnServerUrl: 'https://127.0.0.1:8080',
fiatConversion: false,
unannouncedChannels: false
}

@ -213,7 +213,7 @@ export class DatabaseAdapter {
try {
this.common.createDirectory(this.dbFilePath);
const oldOffers: any = JSON.parse(fs.readFileSync(oldFilePath, 'utf-8'));
fs.writeFileSync(oldFilePath, JSON.stringify(oldOffers.Offers, null, 2));
fs.writeFileSync(oldFilePath, JSON.stringify(oldOffers.Offers ? oldOffers.Offers : [], null, 2));
fs.renameSync(oldFilePath, newFilePath);
} catch (err) {
this.logger.log({ selectedNode: selNode, level: 'ERROR', fileName: 'Database', msg: 'Rename Old Database Error', error: err });

@ -176,7 +176,7 @@ export class RTLWebSocketServer {
}
};
public generateAcceptValue = (acceptKey) => crypto.createHash('sha1').update(acceptKey + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary' as crypto.Utf8AsciiLatin1Encoding).digest('base64');
public generateAcceptValue = (acceptKey) => crypto.createHash('sha1').update(acceptKey + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary').digest('base64');
public getClients = () => this.webSocketServer.clients;

@ -1,35 +1,35 @@
<div fxLayout="column" id="rtl-container" class="rtl-container medium" [ngClass]="[settings.themeColor | lowercase, settings.themeMode | lowercase]">
<mat-toolbar fxLayout="row" fxLayoutAlign="space-between center" class="padding-gap-x bg-primary rtl-top-toolbar">
<mat-toolbar fxLayout="row" fxLayoutAlign="space-between center" class="bg-primary rtl-top-toolbar">
<div>
<button *ngIf="flgLoggedIn" class="top-toolbar-icon mr-1" mat-icon-button (click)="sideNavToggle()" [matTooltip]="flgSideNavOpened ? 'Hide Navigation Menu' : 'Show Navigation Menu'" [matTooltipDisabled]="smallScreen" matTooltipPosition="right">
<mat-icon>menu</mat-icon>
<button *ngIf="flgLoggedIn" mat-icon-button matTooltipPosition="right" [matTooltip]="flgSideNavOpened ? 'Hide Navigation Menu' : 'Show Navigation Menu'" [matTooltipDisabled]="smallScreen" (click)="sideNavToggle()">
<mat-icon class="color-white">menu</mat-icon>
</button>
<button *ngIf="!smallScreen && flgLoggedIn" mat-icon-button (click)="flgSidenavPinned = !flgSidenavPinned" [matTooltip]="flgSidenavPinned ? 'Unpin Navigation Menu' : 'Pin Navigation Menu'" matTooltipPosition="right">
<svg class="top-toolbar-icon icon-pinned" viewBox="0 0 32 32">
<path fill="currentColor" *ngIf="!flgSidenavPinned" d="M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z" />
<path fill="currentColor" *ngIf="flgSidenavPinned" d="M2,5.27L3.28,4L20,20.72L18.73,22L12.8,16.07V22H11.2V16H6V14L8,12V11.27L2,5.27M16,12L18,14V16H17.82L8,6.18V4H7V2H17V4H16V12Z" />
<button *ngIf="!smallScreen && flgLoggedIn" mat-icon-button matTooltipPosition="right" [matTooltip]="flgSidenavPinned ? 'Unpin Navigation Menu' : 'Pin Navigation Menu'" (click)="flgSidenavPinned = !flgSidenavPinned">
<svg class="icon-pinned" width="20" height="20" viewBox="0 0 24 24">
<path *ngIf="!flgSidenavPinned" fill="currentColor" d="M16,12V4H17V2H7V4H8V12L6,14V16H11.2V22H12.8V16H18V14L16,12Z" />
<path *ngIf="flgSidenavPinned" fill="currentColor" d="M2,5.27L3.28,4L20,20.72L18.73,22L12.8,16.07V22H11.2V16H6V14L8,12V11.27L2,5.27M16,12L18,14V16H17.82L8,6.18V4H7V2H17V4H16V12Z" />
</svg>
</button>
</div>
<div>
<span *ngIf="smallScreen">{{information.alias ? 'RTL - ' + information.alias : 'RTL'}}</span>
<span *ngIf="!smallScreen">{{information.alias ? 'Ride The Lightning - ' + information.alias : 'Ride The Lightning'}}</span>
<span *ngIf="smallScreen" class="font-weight-500">{{information.alias ? 'RTL - ' + information.alias : 'RTL'}}</span>
<span *ngIf="!smallScreen" class="font-size-120 font-weight-500">{{information.alias ? 'Ride The Lightning - ' + information.alias : 'Ride The Lightning'}}</span>
</div>
<div>
<rtl-top-menu></rtl-top-menu>
</div>
</mat-toolbar>
<mat-sidenav-container>
<mat-sidenav [perfectScrollbar] [opened]="flgSideNavOpened && flgLoggedIn" [mode]="(flgSidenavPinned && !smallScreen) ? 'side' : 'over'" #sideNavigation class="sidenav mat-elevation-z6">
<rtl-side-navigation (ChildNavClicked)="onNavigationClicked($event)" fxFlex="100"></rtl-side-navigation>
<mat-sidenav #sideNavigation class="sidenav mat-elevation-z6" [perfectScrollbar] [opened]="flgSideNavOpened && flgLoggedIn" [mode]="(flgSidenavPinned && !smallScreen) ? 'side' : 'over'">
<rtl-side-navigation fxFlex="100" (ChildNavClicked)="onNavigationClicked($event)"></rtl-side-navigation>
</mat-sidenav>
<mat-sidenav-content [perfectScrollbar] #sideNavContent>
<mat-sidenav-content #sideNavContent [perfectScrollbar]>
<div class="inner-sidenav-content" fxLayout="column" fxFlex="100" fxLayoutAlign="start stretch">
<router-outlet #outlet="outlet"></router-outlet>
</div>
</mat-sidenav-content>>
</mat-sidenav-container>
<div class="rtl-spinner" *ngIf="!settings.themeColor">
<div *ngIf="!settings.themeColor" class="rtl-spinner">
<mat-spinner color="accent"></mat-spinner>
<h4>Loading RTL...</h4>
</div>

@ -1,5 +1,5 @@
import { HammerModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { NgModule, isDevMode } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { LayoutModule } from '@angular/cdk/layout';
@ -13,7 +13,6 @@ import { routing } from './app.routing';
import { SharedModule } from './shared/shared.module';
import { AppComponent } from './app.component';
import { environment } from '../environments/environment';
import { AuthGuard } from './shared/services/auth.guard';
import { AuthInterceptor } from './shared/services/auth.interceptor';
import { SessionService } from './shared/services/session.service';
@ -32,6 +31,9 @@ import { LNDReducer } from './lnd/store/lnd.reducers';
import { CLNReducer } from './cln/store/cln.reducers';
import { ECLReducer } from './eclair/store/ecl.reducers';
let isDevEnvironemt = false;
if (isDevMode()) { isDevEnvironemt = true; }
@NgModule({
imports: [
BrowserAnimationsModule,
@ -49,7 +51,7 @@ import { ECLReducer } from './eclair/store/ecl.reducers';
}
}),
EffectsModule.forRoot([RTLEffects, LNDEffects, CLNEffects, ECLEffects]),
!environment.production ? StoreDevtoolsModule.instrument() : []
isDevEnvironemt ? StoreDevtoolsModule.instrument() : []
],
declarations: [AppComponent],
providers: [

@ -22,14 +22,16 @@ import { ErrorComponent } from './shared/components/error/error.component';
import { AuthGuard } from './shared/services/auth.guard';
import { ExperimentalSettingsComponent } from './shared/components/node-config/experimental-settings/experimental-settings.component';
type PathMatch = 'full' | 'prefix' | undefined;
export const routes: Routes = [
{ path: '', pathMatch: 'full', redirectTo: 'login' },
{ 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: 'settings', component: SettingsComponent, canActivate: [AuthGuard], children: [
{ path: '', pathMatch: 'full', redirectTo: 'app' },
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'app' },
{ path: 'app', component: AppSettingsComponent, canActivate: [AuthGuard] },
{ path: 'auth', component: AuthSettingsComponent, canActivate: [AuthGuard] },
{ path: 'bconfig', component: BitcoinConfigComponent, canActivate: [AuthGuard] }
@ -37,12 +39,12 @@ export const routes: Routes = [
},
{
path: 'config', component: NodeConfigComponent, canActivate: [AuthGuard], children: [
{ path: '', pathMatch: 'full', redirectTo: 'nodesettings' },
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'nodesettings' },
{ path: 'nodesettings', component: NodeSettingsComponent, canActivate: [AuthGuard] },
{ path: 'pglayout', component: PageSettingsComponent, canActivate: [AuthGuard] },
{
path: 'services', component: ServicesSettingsComponent, canActivate: [AuthGuard], children: [
{ path: '', pathMatch: 'full', redirectTo: 'loop' },
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'loop' },
{ path: 'loop', component: LoopServiceSettingsComponent, canActivate: [AuthGuard] },
{ path: 'boltz', component: BoltzServiceSettingsComponent, canActivate: [AuthGuard] }
]
@ -53,10 +55,10 @@ export const routes: Routes = [
},
{
path: 'services', component: LNServicesComponent, canActivate: [AuthGuard], children: [
{ path: '', pathMatch: 'full', redirectTo: 'loop' },
{ path: 'loop', pathMatch: 'full', redirectTo: 'loop/loopout' },
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'loop' },
{ path: 'loop', pathMatch: <PathMatch>'full', redirectTo: 'loop/loopout' },
{ path: 'loop/:selTab', component: LoopComponent },
{ path: 'boltz', pathMatch: 'full', redirectTo: 'boltz/swapout' },
{ path: 'boltz', pathMatch: <PathMatch>'full', redirectTo: 'boltz/swapout' },
{ path: 'boltz/:selTab', component: BoltzRootComponent }
]
},

@ -36,15 +36,17 @@ import { CLNOfferBookmarksTableComponent } from './transactions/offers/offer-boo
import { CLNLocalFailedTransactionsComponent } from './routing/local-failed-transactions/local-failed-transactions.component';
import { CLNLiquidityAdsListComponent } from './liquidity-ads/liquidity-ads-list/liquidity-ads-list.component';
type PathMatch = 'full' | 'prefix' | undefined;
export const ClnRoutes: Routes = [
{
path: '', component: CLNRootComponent,
children: [
{ path: '', pathMatch: 'full', redirectTo: 'home' },
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'home' },
{ path: 'home', component: CLNHomeComponent, canActivate: [CLNUnlockedGuard] },
{
path: 'onchain', component: CLNOnChainComponent, canActivate: [CLNUnlockedGuard], children: [
{ path: '', pathMatch: 'full', redirectTo: 'receive/utxos' },
{ 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] }
@ -52,10 +54,10 @@ export const ClnRoutes: Routes = [
},
{
path: 'connections', component: CLNConnectionsComponent, canActivate: [CLNUnlockedGuard], children: [
{ path: '', pathMatch: 'full', redirectTo: 'channels' },
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'channels' },
{
path: 'channels', component: CLNChannelsTablesComponent, canActivate: [CLNUnlockedGuard], children: [
{ path: '', pathMatch: 'full', redirectTo: 'open' },
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'open' },
{ path: 'open', component: CLNChannelOpenTableComponent, canActivate: [CLNUnlockedGuard] },
{ path: 'pending', component: CLNChannelPendingTableComponent, canActivate: [CLNUnlockedGuard] }
]
@ -66,7 +68,7 @@ export const ClnRoutes: Routes = [
{ path: 'liquidityads', component: CLNLiquidityAdsListComponent, canActivate: [CLNUnlockedGuard] },
{
path: 'transactions', component: CLNTransactionsComponent, canActivate: [CLNUnlockedGuard], children: [
{ path: '', pathMatch: 'full', redirectTo: 'payments' },
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'payments' },
{ path: 'payments', component: CLNLightningPaymentsComponent, canActivate: [CLNUnlockedGuard] },
{ path: 'invoices', component: CLNLightningInvoicesTableComponent, canActivate: [CLNUnlockedGuard] },
{ path: 'offers', component: CLNOffersTableComponent, canActivate: [CLNUnlockedGuard] },
@ -75,14 +77,14 @@ export const ClnRoutes: Routes = [
},
{
path: 'messages', component: CLNSignVerifyMessageComponent, canActivate: [CLNUnlockedGuard], children: [
{ path: '', pathMatch: 'full', redirectTo: 'sign' },
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'sign' },
{ path: 'sign', component: CLNSignComponent, canActivate: [CLNUnlockedGuard] },
{ path: 'verify', component: CLNVerifyComponent, canActivate: [CLNUnlockedGuard] }
]
},
{
path: 'routing', component: CLNRoutingComponent, canActivate: [CLNUnlockedGuard], children: [
{ path: '', pathMatch: 'full', redirectTo: 'forwardinghistory' },
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'forwardinghistory' },
{ path: 'forwardinghistory', component: CLNForwardingHistoryComponent, canActivate: [CLNUnlockedGuard] },
{ path: 'failedtransactions', component: CLNFailedTransactionsComponent, canActivate: [CLNUnlockedGuard] },
{ path: 'localfail', component: CLNLocalFailedTransactionsComponent, canActivate: [CLNUnlockedGuard] },
@ -91,14 +93,14 @@ export const ClnRoutes: Routes = [
},
{
path: 'reports', component: CLNReportsComponent, canActivate: [CLNUnlockedGuard], children: [
{ path: '', pathMatch: 'full', redirectTo: 'routingreport' },
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'routingreport' },
{ path: 'routingreport', component: CLNRoutingReportComponent, canActivate: [CLNUnlockedGuard] },
{ path: 'transactions', component: CLNTransactionsReportComponent, canActivate: [CLNUnlockedGuard] }
]
},
{
path: 'graph', component: CLNGraphComponent, canActivate: [CLNUnlockedGuard], children: [
{ path: '', pathMatch: 'full', redirectTo: 'lookups' },
{ path: '', pathMatch: <PathMatch>'full', redirectTo: 'lookups' },
{ path: 'lookups', component: CLNLookupsComponent, canActivate: [CLNUnlockedGuard] },
{ path: 'queryroutes', component: CLNQueryRoutesComponent, canActivate: [CLNUnlockedGuard] }
]

@ -1,13 +1,14 @@
<div fxLayout="row wrap" fxLayoutAlign="start center" class="page-title-container">
<fa-icon [icon]="faSearch" class="page-title-img mr-1"></fa-icon>
<fa-icon class="page-title-img mr-1" [icon]="faSearch"></fa-icon>
<span class="page-title">Graph Lookups</span>
</div>
<div fxLayout="column" class="padding-gap-x">
<mat-card>
<mat-card-content fxLayout="column">
<nav mat-tab-nav-bar>
<div role="tab" mat-tab-link *ngFor="let link of links" class="mat-tab-label" [active]="activeLink === link.link" (click)="activeLink = link.link" routerLink="{{link.link}}">{{link.name}}</div>
<nav mat-tab-nav-bar mat-stretch-tabs="false" mat-align-tabs="start" [tabPanel]="tabPanel">
<div *ngFor="let link of links" role="tab" mat-tab-link class="mat-tab-label" [active]="activeLink === link.link" routerLink="{{link.link}}" (click)="activeLink = link.link">{{link.name}}</div>
</nav>
<mat-tab-nav-panel #tabPanel></mat-tab-nav-panel>
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="mat-tab-body-wrapper">
<router-outlet></router-outlet>
</div>

@ -1,164 +1,164 @@
<div fxLayout="column" *ngIf="lookupResult" class="mt-1">
<div *ngIf="lookupResult" fxLayout="column" class="mt-1">
<mat-divider></mat-divider>
<div fxLayout="column" fxLayoutAlign="space-between stretch" fxLayout.gt-sm="row">
<div fxLayout="column" fxFlex="49" fxLayoutAlign="start start" class="mt-1 bordered-box padding-gap-large">
<div fxLayout="column" fxFlex="49" fxLayoutAlign="start stretch" class="mt-1 bordered-box padding-gap-large">
<div fxLayout="column">
<h3 class="page-title font-bold-500" *ngIf="!node1_match">Node 1</h3>
<h3 class="page-title font-bold-500" *ngIf="node1_match">Node 1 (Your Node)</h3>
<h3 *ngIf="!node1_match" class="page-title font-bold-500">Node 1</h3>
<h3 *ngIf="node1_match" class="page-title font-bold-500">Node 1 (Your Node)</h3>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="20" class="my-1">
<mat-divider class="my-1"></mat-divider>
<div fxLayout="column">
<h4 class="font-bold-500">Short Channel ID</h4>
<span class="foreground-secondary-text">{{lookupResult[0]?.short_channel_id}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<mat-divider class="my-1"></mat-divider>
<div fxLayout="column">
<h4 class="font-bold-500">Active</h4>
<span class="foreground-secondary-text">{{lookupResult[0]?.active ? 'True' : 'False'}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<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>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<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>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<mat-divider class="my-1"></mat-divider>
<div fxLayout="column">
<h4 class="font-bold-500">Base Fee (mSats)</h4>
<span class="foreground-secondary-text">{{lookupResult[0]?.base_fee_millisatoshi | number}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<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>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<mat-divider class="my-1"></mat-divider>
<div fxLayout="column">
<h4 class="font-bold-500">Delay</h4>
<span class="foreground-secondary-text">{{lookupResult[0]?.delay | number}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<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 [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<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 [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<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>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<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>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<mat-divider class="my-1"></mat-divider>
<div fxLayout="column">
<h4 class="font-bold-500">Message Flags</h4>
<span class="foreground-secondary-text">{{lookupResult[0]?.message_flags | number}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<mat-divider class="my-1"></mat-divider>
<div fxLayout="column">
<h4 class="font-bold-500">Public</h4>
<span class="foreground-secondary-text">{{lookupResult[0]?.public ? 'Yes' : 'No'}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<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>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<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>
</div>
</div>
<div fxLayout="column" fxFlex="49" fxLayoutAlign="start start" class="mt-1 bordered-box padding-gap-large">
<div fxLayout="column" fxFlex="49" fxLayoutAlign="start stretch" class="mt-1 bordered-box padding-gap-large">
<div>
<h3 class="page-title font-bold-500" *ngIf="!node2_match">Node 2</h3>
<h3 class="page-title font-bold-500" *ngIf="node2_match">Node 2 (Your Node)</h3>
<h3 *ngIf="!node2_match" class="page-title font-bold-500">Node 2</h3>
<h3 *ngIf="node2_match" class="page-title font-bold-500">Node 2 (Your Node)</h3>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="20" class="my-1">
<mat-divider class="my-1"></mat-divider>
<div fxLayout="column">
<h4 class="font-bold-500">Short Channel ID</h4>
<span class="foreground-secondary-text">{{lookupResult[1]?.short_channel_id}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<mat-divider class="my-1"></mat-divider>
<div fxLayout="column">
<h4 class="font-bold-500">Active</h4>
<span class="foreground-secondary-text">{{lookupResult[1]?.active ? 'True' : 'False'}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<mat-divider class="my-1"></mat-divider>
<div fxLayout="column">
<h4 class="font-bold-500">Last Update</h4>
<span class="foreground-secondary-text">{{(lookupResult[1]?.last_update * 1000) | date:'dd/MMM/y HH:mm'}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<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>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<mat-divider class="my-1"></mat-divider>
<div fxLayout="column">
<h4 class="font-bold-500">Base Fee (mSats)</h4>
<span class="foreground-secondary-text">{{lookupResult[1]?.base_fee_millisatoshi | number}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<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>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<mat-divider class="my-1"></mat-divider>
<div fxLayout="column">
<h4 class="font-bold-500">Delay</h4>
<span class="foreground-secondary-text">{{lookupResult[1]?.delay | number}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<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 [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<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 [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<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>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<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>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<mat-divider class="my-1"></mat-divider>
<div fxLayout="column">
<h4 class="font-bold-500">Message Flags</h4>
<span class="foreground-secondary-text">{{lookupResult[1]?.message_flags | number}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<mat-divider class="my-1"></mat-divider>
<div fxLayout="column">
<h4 class="font-bold-500">Public</h4>
<span class="foreground-secondary-text">{{lookupResult[1]?.public ? 'Yes' : 'No'}}</span>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<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>
</div>
<mat-divider [inset]="true"></mat-divider>
<div fxLayout="column" fxFlex="10" class="my-1">
<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>
</div>

@ -1,16 +1,17 @@
<div fxLayout="column">
<div fxLayout="row" fxFlex="100" fxLayoutAlign="start start" class="padding-gap">
<mat-card-content fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch">
<form fxFlex="100" fxLayout="column" fxLayout.gt-sm="row wrap" fxLayoutAlign.gt-sm="space-between center" fxLayoutAlign="start space-between" class="w-100" #form="ngForm">
<form #form="ngForm" fxFlex="100" fxLayout="column" fxLayout.gt-sm="row wrap" fxLayoutAlign.gt-sm="space-between center" fxLayoutAlign="start space-between" class="w-100">
<div fxFlex="100" fxLayoutAlign="start end">
<mat-radio-group color="primary" [(ngModel)]="selectedFieldId" (change)="onSelectChange($event)" tabindex="1" name="lookupField">
<mat-radio-button *ngFor="let lookupField of lookupFields" [value]="lookupField.id" [checked]="selectedFieldId === lookupField.id" class="mr-4">
<mat-radio-group color="primary" tabindex="1" name="lookupField" [(ngModel)]="selectedFieldId" (change)="onSelectChange($event)">
<mat-radio-button *ngFor="let lookupField of lookupFields" class="mr-4" [value]="lookupField.id" [checked]="selectedFieldId === lookupField.id">
{{lookupField.name}}
</mat-radio-button>
</mat-radio-group>
</div>
<mat-form-field fxFlex="100" fxLayoutAlign="start end" [ngClass]="{'mt-1': true, 'mt-2': screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM}">
<input matInput name="lookupKey" [placeholder]="lookupFields[selectedFieldId]?.placeholder || 'Lookup Key'" (change)="clearLookupValue()" [(ngModel)]="lookupKey" tabindex="2" required #key>
<mat-form-field fxLayout="column" fxFlex="100" fxLayoutAlign="start end" [ngClass]="{'mt-1': true, 'mt-2': screenSize === screenSizeEnum.XS || screenSize === screenSizeEnum.SM}">
<mat-label>{{lookupFields[selectedFieldId]?.placeholder || 'Lookup Key'}}</mat-label>
<input #key matInput name="lookupKey" tabindex="2" required [(ngModel)]="lookupKey" (change)="clearLookupValue()">
<mat-error *ngIf="!lookupKey">{{lookupFields[selectedFieldId]?.placeholder}} is required.</mat-error>
</mat-form-field>
<div fxLayout="row" fxFlex="100" class="mt-1">
@ -18,14 +19,14 @@
<button mat-flat-button color="primary" tabindex="4" type="submit" (click)="onLookup()">Lookup</button>
</div>
</form>
<div fxFlex="100" fxLayout="column" fxLayout.gt-sm="row wrap" fxLayoutAlign.gt-sm="space-between center" fxLayoutAlign="start stretch" *ngIf="flgSetLookupValue" class="w-100 mt-2">
<div *ngIf="flgSetLookupValue" fxFlex="100" fxLayout="column" fxLayout.gt-sm="row wrap" fxLayoutAlign.gt-sm="space-between center" fxLayoutAlign="start stretch" class="w-100 mt-2">
<div fxLayout="row" fxFlex="100" fxLayoutAlign="start center">
<span class="page-title font-bold-500">{{lookupFields[selectedFieldId].name}} Details</span>
</div>
<div [ngSwitch]="selectedFieldId" fxLayout="row" fxFlex="100" fxLayoutAlign="start center">
<span fxFlex="100" *ngSwitchCase="0"><div *ngIf="nodeLookupValue.nodeid !== ''; else errorBlock"><rtl-cln-node-lookup [lookupResult]="nodeLookupValue"></rtl-cln-node-lookup></div></span>
<span fxFlex="100" *ngSwitchCase="1"><div *ngIf="channelLookupValue.length>0; else errorBlock"><rtl-cln-channel-lookup [lookupResult]="channelLookupValue"></rtl-cln-channel-lookup></div></span>
<span fxFlex="100" *ngSwitchDefault><h3>Error! Unable to find details!</h3></span>
<div fxLayout="row" fxFlex="100" fxLayoutAlign="start center" [ngSwitch]="selectedFieldId">
<span *ngSwitchCase="0" fxFlex="100"><div *ngIf="nodeLookupValue.nodeid !== ''; else errorBlock"><rtl-cln-node-lookup [lookupResult]="nodeLookupValue"></rtl-cln-node-lookup></div></span>
<span *ngSwitchCase="1" fxFlex="100"><div *ngIf="channelLookupValue.length>0; else errorBlock"><rtl-cln-channel-lookup [lookupResult]="channelLookupValue"></rtl-cln-channel-lookup></div></span>
<span *ngSwitchDefault> fxFlex="100"<h3>Error! Unable to find details!</h3></span>
</div>
</div>
</mat-card-content>

@ -1,5 +1,5 @@
<div fxLayout="column" *ngIf="lookupResult" class="mt-1">
<mat-divider [inset]="true" class="mb-1"></mat-divider>
<div *ngIf="lookupResult" fxLayout="column" class="mt-1">
<mat-divider class="mb-1"></mat-divider>
<div fxLayout="row">
<div fxFlex="30">
<h4 fxLayoutAlign="start" class="font-bold-500">Alias</h4>
@ -10,7 +10,7 @@
<span class="foreground-secondary-text w-100">{{lookupResult?.nodeid}}</span>
</div>
</div>
<mat-divider [inset]="true" class="my-1"></mat-divider>
<mat-divider class="my-1"></mat-divider>
<div fxLayout="row">
<div fxFlex="30">
<h4 fxLayoutAlign="start" class="font-bold-500">Last Update</h4>
@ -18,38 +18,38 @@
</div>
<div fxFlex="70">
<h4 fxLayoutAlign="start" class="font-bold-500">Features</h4>
<span class="foreground-secondary-text" *ngFor="let featureDescription of featureDescriptions">{{featureDescription}}</span>
<span *ngFor="let featureDescription of featureDescriptions" class="foreground-secondary-text">{{featureDescription}}</span>
</div>
</div>
<mat-divider [inset]="true" class="my-1"></mat-divider>
<mat-divider class="my-1"></mat-divider>
<div fxLayout="column">
<h4 fxFlex="100" fxLayoutAlign="start" class="font-bold-500 mb-1">Addresses</h4>
<div [perfectScrollbar] fxLayout="row" fxFlex="100" class="table-container">
<table mat-table #table [dataSource]="addresses" matSort>
<div fxLayout="row" fxFlex="100" class="table-container" [perfectScrollbar]>
<table #table mat-table matSort [dataSource]="addresses">
<ng-container matColumnDef="type">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Type</th>
<td mat-cell *matCellDef="let address">{{address?.type}}</td>
<th *matHeaderCellDef mat-header-cell mat-sort-header>Type</th>
<td *matCellDef="let address" mat-cell>{{address?.type}}</td>
</ng-container>
<ng-container matColumnDef="address">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Address</th>
<td mat-cell *matCellDef="let address">{{address?.address}}</td>
<th *matHeaderCellDef mat-header-cell mat-sort-header>Address</th>
<td *matCellDef="let address" mat-cell>{{address?.address}}</td>
</ng-container>
<ng-container matColumnDef="port">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Port</th>
<td mat-cell *matCellDef="let address">{{address?.port}}</td>
<th *matHeaderCellDef mat-header-cell mat-sort-header>Port</th>
<td *matCellDef="let address" mat-cell>{{address?.port}}</td>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>
<th *matHeaderCellDef mat-header-cell>
<div class="bordered-box table-actions-select btn-action" fxLayoutAlign="center center">Actions</div>
</th>
<td mat-cell *matCellDef="let address">
<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>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns;"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
<tr *matHeaderRowDef="displayedColumns;" mat-header-row></tr>
<tr *matRowDef="let row; columns: displayedColumns;" mat-row></tr>
</table>
</div>
</div>

@ -1,10 +0,0 @@
div.bordered-box.table-actions-select.btn-action {
min-width: 13rem;
width: 13rem;
min-height: 3.6rem;
}
button.mat-stroked-button.btn-action-copy {
min-width: 13rem;
width: 13rem;
}

@ -1,15 +1,17 @@
<div fxLayout="column" fxFlex="100" class="padding-gap">
<form fxLayout="column" fxLayoutAlign="space-between stretch" fxLayout.gt-sm="row wrap" (ngSubmit)="queryRoutesForm.form.valid && onQueryRoutes()" #queryRoutesForm="ngForm">
<form #queryRoutesForm="ngForm" fxLayout="column" fxLayoutAlign="space-between stretch" fxLayout.gt-sm="row wrap" (ngSubmit)="queryRoutesForm.form.valid && onQueryRoutes()">
<div fxFlex="100" class="alert alert-warn">
<fa-icon [icon]="faExclamationTriangle" class="mr-1 alert-icon"></fa-icon>
<fa-icon class="mr-1 alert-icon" [icon]="faExclamationTriangle"></fa-icon>
<span>The actual routing fee on a payment can be different from the fee shown on query routes.</span>
</div>
<mat-form-field fxFlex="69" fxLayoutAlign="start end">
<input matInput placeholder="Destination Pubkey" name="destinationPubkey" [(ngModel)]="destinationPubkey" tabindex="1" required #destPubkey="ngModel">
<mat-form-field fxLayout="column" fxFlex="69" fxLayoutAlign="start end">
<mat-label>Destination Pubkey</mat-label>
<input #destPubkey="ngModel" matInput name="destinationPubkey" tabindex="1" required [(ngModel)]="destinationPubkey">
<mat-error *ngIf="!destinationPubkey">Destination pubkey is required.</mat-error>
</mat-form-field>
<mat-form-field fxFlex="29" fxLayoutAlign="start end">
<input matInput placeholder="Amount (Sats)" [(ngModel)]="amount" name="amount" tabindex="2" type="number" [step]="1000" [min]="0" required>
<mat-form-field fxLayout="column" fxFlex="29" fxLayoutAlign="start end">
<mat-label>Amount (Sats)</mat-label>
<input matInput name="amount" tabindex="2" type="number" required [step]="1000" [min]="0" [(ngModel)]="amount">
<mat-error *ngIf="!amount">Amount is required.</mat-error>
</mat-form-field>
<div fxLayout="row" class="mt-1">
@ -19,55 +21,55 @@
</form>
<div fxLayout="row" fxLayoutAlign="start center" class="page-sub-title-container mt-2 mb-1">
<div fxFlex="70">
<fa-icon [icon]="faRoute" class="page-title-img mr-1"></fa-icon>
<fa-icon class="page-title-img mr-1" [icon]="faRoute"></fa-icon>
<span class="page-title">Transaction Route</span>
</div>
</div>
<div [perfectScrollbar] class="table-container mb-6">
<div class="table-container mb-6" [perfectScrollbar]>
<mat-progress-bar *ngIf="flgLoading[0]===true" mode="indeterminate"></mat-progress-bar>
<table mat-table #table [dataSource]="qrHops" matSort [ngClass]="{'overflow-auto error-border': flgLoading[0]==='error','overflow-auto': true}">
<table #table mat-table matSort [matSortActive]="tableSetting.sortBy" [matSortDirection]="tableSetting.sortOrder" [dataSource]="qrHops" [ngClass]="{'overflow-auto error-border': flgLoading[0]==='error','overflow-auto': true}">
<ng-container matColumnDef="id">
<th mat-header-cell *matHeaderCellDef mat-sort-header>ID</th>
<td mat-cell *matCellDef="let hop">
<div class="ellipsis-parent" [ngStyle]="{'width': (screenSize === screenSizeEnum.XS) ? '10rem' : colWidth}">
<th *matHeaderCellDef mat-header-cell mat-sort-header>ID</th>
<td *matCellDef="let hop" mat-cell>
<div class="ellipsis-parent" [ngStyle]="{'width': (screenSize === screenSizeEnum.XS) ? '6rem' : colWidth}">
<span class="ellipsis-child">{{hop?.id}}</span>
</div>
</td>
</ng-container>
<ng-container matColumnDef="alias">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Alias</th>
<td mat-cell *matCellDef="let hop">
<div class="ellipsis-parent" [ngStyle]="{'width': (screenSize === screenSizeEnum.XS) ? '10rem' : colWidth}">
<th *matHeaderCellDef mat-header-cell mat-sort-header>Alias</th>
<td *matCellDef="let hop" mat-cell>
<div class="ellipsis-parent" [ngStyle]="{'width': (screenSize === screenSizeEnum.XS) ? '6rem' : colWidth}">
<span class="ellipsis-child">{{hop?.alias}}</span>
</div>
</td>
</ng-container>
<ng-container matColumnDef="channel">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Channel</th>
<td mat-cell *matCellDef="let hop">{{hop?.channel}}</td>
<th *matHeaderCellDef mat-header-cell mat-sort-header>Channel</th>
<td *matCellDef="let hop" mat-cell>{{hop?.channel}}</td>
</ng-container>
<ng-container matColumnDef="direction">
<th mat-header-cell *matHeaderCellDef mat-sort-header>Direction</th>
<td mat-cell *matCellDef="let hop">{{hop?.direction}}</td>
<th *matHeaderCellDef mat-header-cell mat-sort-header>Direction</th>
<td *matCellDef="let hop" mat-cell>{{hop?.direction}}</td>
</ng-container>
<ng-container matColumnDef="delay">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Delay</th>
<td mat-cell *matCellDef="let hop"><span fxLayoutAlign="end center">{{hop?.delay | number}} </span></td>
<th *matHeaderCellDef mat-header-cell mat-sort-header arrowPosition="before">Delay</th>
<td *matCellDef="let hop" mat-cell><span fxLayoutAlign="end center">{{hop?.delay | number}} </span></td>
</ng-container>
<ng-container matColumnDef="msatoshi">
<th mat-header-cell *matHeaderCellDef mat-sort-header arrowPosition="before">Amount (Sats)</th>
<td mat-cell *matCellDef="let hop"><span fxLayoutAlign="end center">{{hop?.msatoshi/1000 | number}}</span></td>
<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>
</ng-container>
<ng-container matColumnDef="actions">
<th mat-header-cell *matHeaderCellDef>
<th *matHeaderCellDef mat-header-cell>
<div class="bordered-box table-actions-select" fxLayoutAlign="center center">Actions</div>
</th>
<td mat-cell *matCellDef="let hop" fxLayoutAlign="end center">
<button mat-stroked-button color="primary" type="button" tabindex="4" (click)="onHopClick(hop, $event)" class="table-actions-button">View Info</button>
<td *matCellDef="let hop" mat-cell fxLayoutAlign="end center">
<button mat-stroked-button color="primary" type="button" tabindex="4" class="table-actions-button" (click)="onHopClick(hop, $event)">View Info</button>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
<tr *matHeaderRowDef="displayedColumns" mat-header-row></tr>
<tr *matRowDef="let row; columns: displayedColumns;" mat-row></tr>
</table>
</div>
</div>

@ -66,7 +66,6 @@ export class CLNQueryRoutesComponent implements OnInit, OnDestroy {
}
this.qrHops.sort = this.sort;
this.qrHops.sortingDataAccessor = (data: any, sortHeaderId: string) => ((data[sortHeaderId] && isNaN(data[sortHeaderId])) ? data[sortHeaderId].toLocaleLowerCase() : data[sortHeaderId] ? +data[sortHeaderId] : null);
this.qrHops.sort?.sort({ id: this.tableSetting.sortBy, start: this.tableSetting.sortOrder, disableClear: true });
});
}

@ -1,4 +1,4 @@
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" *ngIf="errorMessage?.trim() === ''; else errorBlock">
<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>

@ -1,10 +1,10 @@
<div fxLayout="column" fxLayoutAlign="space-between stretch" fxFlex="100" *ngIf="errorMessage?.trim() === ''; else errorBlock">
<div fxLayout="column" fxFlex="9" fxLayoutAlign="end start">
<div *ngIf="errorMessage?.trim() === ''; else errorBlock" fxLayout="column" fxLayoutAlign="space-between stretch"fxFlex="100">
<div fxLayout="column" fxFlex="8" fxLayoutAlign="end start">
<span class="dashboard-capacity-header this-channel-capacity">Total Capacity</span>
<div fxLayout="row" fxLayoutAlign="space-between start" class="w-100">
<mat-hint fxFlex="40" fxLayoutAlign="start center" class="font-size-90"><strong class="font-weight-900 mr-5px">Local:</strong>{{channelBalances?.localBalance || 0 | number:'1.0-0'}} Sats</mat-hint>
<mat-hint fxFlex="20" fxLayoutAlign="center center" class="font-size-90">
<fa-icon [icon]="faBalanceScale" class="mr-3px" matTooltip="Balance Score"></fa-icon>
<fa-icon class="mr-3px" matTooltip="Balance Score" [icon]="faBalanceScale"></fa-icon>
({{channelBalances?.balancedness || 0 | number}})
</mat-hint>
<mat-hint fxFlex="40" fxLayoutAlign="end center" class="font-size-90"><strong class="font-weight-900 mr-5px">Remote:</strong>{{channelBalances?.remoteBalance || 0 | number:'1.0-0'}} Sats</mat-hint>
@ -13,15 +13,15 @@
</div>
<div fxLayout="column" fxFlex="3" fxLayoutAlign="end stretch"><mat-divider class="dashboard-divider"></mat-divider></div>
<div class="channels-capacity-scroll" [perfectScrollbar]>
<div fxLayout="column" fxFlex="100" *ngIf="activeChannels && activeChannels.length > 0; else noChannelBlock">
<div *ngIf="activeChannels && activeChannels.length > 0; else noChannelBlock" fxLayout="column"fxFlex="100">
<div *ngFor="let channel of activeChannels" class="mt-2">
<a [routerLink]="['../connections/channels/open']" [state]="{filter: channel.id}" class="dashboard-capacity-header" matTooltip="{{channel.alias || channel.id}}" matTooltipDisabled="{{(channel.alias || channel.id).length < 26}}">
<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>
<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="20" fxLayoutAlign="center center" class="font-size-90 color-primary">
<fa-icon [icon]="faBalanceScale" class="color-primary mr-3px" matTooltip="Balance Score"></fa-icon>
<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>
@ -34,7 +34,7 @@
<ng-template #noChannelBlock>
<div fxLayout="row" fxFlex="100" fxLayoutAlign="space-between start" class="mt-1 w-100">
No channels available.
<button mat-stroked-button color="primary" (click)="goToChannels()" tabindex="1">Open Channel</button>
<button mat-stroked-button color="primary" tabindex="1" (click)="goToChannels()">Open Channel</button>
</div>
</ng-template>
<ng-template #errorBlock>

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

Loading…
Cancel
Save