Moved enableOffers to Experimental Tab

Moved enableOffers to Experimental Tab
pull/897/head
Shahana Farooqui 3 years ago
parent e6ce4014d0
commit 078107bda4

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,9 @@
<link i18n-rel="" rel="mask-icon" href="assets/images/favicon-light/safari-pinned-tab.svg" color="#5bbad5">
<meta i18n-content="" name="msapplication-TileColor" content="#da532c">
<meta i18n-content="" name="theme-color" content="#ffffff">
<style>@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.e4959b9dcf14c7c7.css" media="print" onload="this.media='all'"><noscript><link rel="stylesheet" href="styles.e4959b9dcf14c7c7.css"></noscript></head>
<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.15e3ffcb5aa94cda.css" media="print" onload="this.media='all'"><noscript><link rel="stylesheet" href="styles.15e3ffcb5aa94cda.css"></noscript></head>
<body>
<rtl-app></rtl-app>
<script src="runtime.1d059d41518f96f4.js" type="module"></script><script src="polyfills.c41bc361ba340cf0.js" type="module"></script><script src="main.3931073821850715.js" type="module"></script>
<script src="runtime.16eced0351f891d0.js" type="module"></script><script src="polyfills.c41bc361ba340cf0.js" type="module"></script><script src="main.8ccd6d396e9af350.js" type="module"></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

@ -1 +1 @@
(()=>{"use strict";var e,v={},g={};function r(e){var i=g[e];if(void 0!==i)return i.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=(i,t,f,o)=>{if(!t){var a=1/0;for(n=0;n<e.length;n++){for(var[t,f,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=f();void 0!==u&&(i=u)}}return i}o=o||0;for(var n=e.length;n>0&&e[n-1][2]>o;n--)e[n]=e[n-1];e[n]=[t,f,o]},r.n=e=>{var i=e&&e.__esModule?()=>e.default:()=>e;return r.d(i,{a:i}),i},r.d=(e,i)=>{for(var t in i)r.o(i,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:i[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((i,t)=>(r.f[t](e,i),i),[])),r.u=e=>e+"."+{119:"250471d071591bd5",314:"11e27c4b0ea1c1d1",549:"3996f98c51c99563",871:"a24ad7a32d9f3b33"}[e]+".js",r.miniCssF=e=>"styles.e4959b9dcf14c7c7.css",r.o=(e,i)=>Object.prototype.hasOwnProperty.call(e,i),(()=>{var e={},i="RTLApp:";r.l=(t,f,o,n)=>{if(e[t])e[t].push(f);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")==i+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",i+o),a.src=r.tu(t)),e[t]=[f];var c=(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(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=>{"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.tu=i=>(void 0===e&&(e={createScriptURL:t=>t},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("angular#bundler",e))),e.createScriptURL(i))})(),r.p="",(()=>{var e={666:0};r.f.j=(f,o)=>{var n=r.o(e,f)?e[f]:void 0;if(0!==n)if(n)o.push(n[2]);else if(666!=f){var a=new Promise((l,c)=>n=e[f]=[l,c]);o.push(n[2]=a);var s=r.p+r.u(f),d=new Error;r.l(s,l=>{if(r.o(e,f)&&(0!==(n=e[f])&&(e[f]=void 0),n)){var c=l&&("load"===l.type?"missing":l.type),p=l&&l.target&&l.target.src;d.message="Loading chunk "+f+" failed.\n("+c+": "+p+")",d.name="ChunkLoadError",d.type=c,d.request=p,n[1](d)}},"chunk-"+f,f)}else e[f]=0},r.O.j=f=>0===e[f];var i=(f,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(f&&f(o);l<n.length;l++)r.o(e,u=n[l])&&e[u]&&e[u][0](),e[n[l]]=0;return r.O(c)},t=self.webpackChunkRTLApp=self.webpackChunkRTLApp||[];t.forEach(i.bind(null,0)),t.push=i.bind(null,t.push.bind(t))})()})();
(()=>{"use strict";var e,v={},g={};function r(e){var i=g[e];if(void 0!==i)return i.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=(i,t,f,o)=>{if(!t){var a=1/0;for(n=0;n<e.length;n++){for(var[t,f,o]=e[n],s=!0,l=0;l<t.length;l++)(!1&o||a>=o)&&Object.keys(r.O).every(b=>r.O[b](t[l]))?t.splice(l--,1):(s=!1,o<a&&(a=o));if(s){e.splice(n--,1);var d=f();void 0!==d&&(i=d)}}return i}o=o||0;for(var n=e.length;n>0&&e[n-1][2]>o;n--)e[n]=e[n-1];e[n]=[t,f,o]},r.n=e=>{var i=e&&e.__esModule?()=>e.default:()=>e;return r.d(i,{a:i}),i},r.d=(e,i)=>{for(var t in i)r.o(i,t)&&!r.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:i[t]})},r.f={},r.e=e=>Promise.all(Object.keys(r.f).reduce((i,t)=>(r.f[t](e,i),i),[])),r.u=e=>e+"."+{119:"57225c6e33716289",314:"6f64b8d2d7662c1d",549:"44a1dc2cbe276272",871:"18b1ab553a6b7402"}[e]+".js",r.miniCssF=e=>"styles.15e3ffcb5aa94cda.css",r.o=(e,i)=>Object.prototype.hasOwnProperty.call(e,i),(()=>{var e={},i="RTLApp:";r.l=(t,f,o,n)=>{if(e[t])e[t].push(f);else{var a,s;if(void 0!==o)for(var l=document.getElementsByTagName("script"),d=0;d<l.length;d++){var u=l[d];if(u.getAttribute("src")==t||u.getAttribute("data-webpack")==i+o){a=u;break}}a||(s=!0,(a=document.createElement("script")).type="module",a.charset="utf-8",a.timeout=120,r.nc&&a.setAttribute("nonce",r.nc),a.setAttribute("data-webpack",i+o),a.src=r.tu(t)),e[t]=[f];var c=(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(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=>{"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.tu=i=>(void 0===e&&(e={createScriptURL:t=>t},"undefined"!=typeof trustedTypes&&trustedTypes.createPolicy&&(e=trustedTypes.createPolicy("angular#bundler",e))),e.createScriptURL(i))})(),r.p="",(()=>{var e={666:0};r.f.j=(f,o)=>{var n=r.o(e,f)?e[f]:void 0;if(0!==n)if(n)o.push(n[2]);else if(666!=f){var a=new Promise((u,c)=>n=e[f]=[u,c]);o.push(n[2]=a);var s=r.p+r.u(f),l=new Error;r.l(s,u=>{if(r.o(e,f)&&(0!==(n=e[f])&&(e[f]=void 0),n)){var c=u&&("load"===u.type?"missing":u.type),p=u&&u.target&&u.target.src;l.message="Loading chunk "+f+" failed.\n("+c+": "+p+")",l.name="ChunkLoadError",l.type=c,l.request=p,n[1](l)}},"chunk-"+f,f)}else e[f]=0},r.O.j=f=>0===e[f];var i=(f,o)=>{var l,d,[n,a,s]=o,u=0;if(n.some(p=>0!==e[p])){for(l in a)r.o(a,l)&&(r.m[l]=a[l]);if(s)var c=s(r)}for(f&&f(o);u<n.length;u++)r.o(e,d=n[u])&&e[d]&&e[d][0](),e[n[u]]=0;return r.O(c)},t=self.webpackChunkRTLApp=self.webpackChunkRTLApp||[];t.forEach(i.bind(null,0)),t.push=i.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

@ -111,7 +111,7 @@ export const postPayment = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Sent' });
if (req.body.paymentType === 'OFFER') {
if (req.body.saveToDB) {
const offer = { id: v4(), invoice: req.body.invoice, amountmSat: req.body.amount, description: req.body.description };
const offer = { id: v4(), offer: req.body.offer, amountmSat: req.body.amount, label: req.body.label, issuer: req.body.issuer, description: req.body.description };
return Database.offer.create(offer).then((savedOffer) => {
logger.log({ level: 'DEBUG', fileName: 'Offer', msg: 'Offer Saved', data: savedOffer });
return res.status(201).json({ paymentResponse: body, saveToDBResponse: savedOffer.dataValues });

@ -5,17 +5,25 @@ export const Offer = (sequelize, Sequelize) => {
primaryKey: true,
allowNull: false
},
invoice: {
offer: {
type: Sequelize.STRING,
allowNull: false
},
amount: {
amountmSat: {
type: Sequelize.NUMBER,
allowNull: false
},
description: {
label: {
type: Sequelize.STRING,
allowNull: false
},
issuer: {
type: Sequelize.STRING,
allowNull: true
},
description: {
type: Sequelize.STRING,
allowNull: true
}
});
return offerInstance;

@ -106,7 +106,7 @@ export const postPayment = (req, res, next) => {
logger.log({ selectedNode: req.session.selectedNode, level: 'INFO', fileName: 'Payments', msg: 'Payment Sent' });
if (req.body.paymentType === 'OFFER') {
if (req.body.saveToDB) {
const offer = { id: v4(), invoice: req.body.invoice, amountmSat: req.body.amount, description: req.body.description };
const offer = { id: v4(), offer: req.body.offer, amountmSat: req.body.amount, label: req.body.label, issuer: req.body.issuer, description: req.body.description };
return Database.offer.create(offer).then((savedOffer) => {
logger.log({ level: 'DEBUG', fileName: 'Offer', msg: 'Offer Saved', data: savedOffer });
return res.status(201).json({ paymentResponse: body, saveToDBResponse: savedOffer.dataValues });

@ -5,17 +5,25 @@ export const Offer = (sequelize, Sequelize) => {
primaryKey: true,
allowNull: false
},
invoice: {
offer: {
type: Sequelize.STRING,
allowNull: false
},
amount: {
amountmSat: {
type: Sequelize.NUMBER,
allowNull: false
},
description: {
label: {
type: Sequelize.STRING,
allowNull: false
},
issuer: {
type: Sequelize.STRING,
allowNull: true
},
description: {
type: Sequelize.STRING,
allowNull: true
}
});
return offerInstance;

@ -19,7 +19,7 @@ import { LoginComponent } from './shared/components/login/login.component';
import { NotFoundComponent } from './shared/components/not-found/not-found.component';
import { ErrorComponent } from './shared/components/error/error.component';
import { AuthGuard } from './shared/services/auth.guard';
import { OffersServiceSettingsComponent } from './shared/components/node-config/services-settings/offers-service-settings/offers-service-settings.component';
import { ExperimentalSettingsComponent } from './shared/components/node-config/experimental-settings/experimental-settings.component';
export const routes: Routes = [
{ path: '', pathMatch: 'full', redirectTo: 'login' },
@ -36,16 +36,16 @@ export const routes: Routes = [
},
{
path: 'config', component: NodeConfigComponent, canActivate: [AuthGuard], children: [
{ path: '', pathMatch: 'full', redirectTo: 'node' },
{ path: 'node', component: NodeSettingsComponent, canActivate: [AuthGuard] },
{ path: '', pathMatch: 'full', redirectTo: 'layout' },
{ path: 'layout', component: NodeSettingsComponent, canActivate: [AuthGuard] },
{
path: 'services', component: ServicesSettingsComponent, canActivate: [AuthGuard], children: [
{ path: '', pathMatch: 'full', redirectTo: 'loop' },
{ path: 'loop', component: LoopServiceSettingsComponent, canActivate: [AuthGuard] },
{ path: 'boltz', component: BoltzServiceSettingsComponent, canActivate: [AuthGuard] },
{ path: 'offers', component: OffersServiceSettingsComponent, canActivate: [AuthGuard] }
]
},
{ path: 'experimental', component: ExperimentalSettingsComponent, canActivate: [AuthGuard] },
{ path: 'lnconfig', component: LNPConfigComponent, canActivate: [AuthGuard] }
]
},

@ -4,7 +4,7 @@
<div fxFlex="95" fxLayoutAlign="start start">
<span class="page-title">Send Payment</span>
</div>
<button tabindex="12" fxFlex="9" fxLayoutAlign="center" class="btn-close-x p-0" [mat-dialog-close]="false" default mat-button>X</button>
<button tabindex="12" fxFlex="10" fxLayoutAlign="center" class="btn-close-x p-0" [mat-dialog-close]="false" default mat-button>X</button>
</mat-card-header>
<mat-card-content class="padding-gap-x-large" fxLayout="column" fxLayoutAlign="start stretch">
<mat-radio-group *ngIf="isCompatibleVersion" class="my-1" color="primary" name="paymentType" [(ngModel)]="paymentType" (change)="onPaymentTypeChange()" fxFlex="100" fxLayoutAlign="start start">
@ -12,15 +12,15 @@
<mat-radio-button fxFlex="20" tabindex="2" value="{{paymentTypes.KEYSEND}}">Keysend</mat-radio-button>
<mat-radio-button *ngIf="selNode.enableOffers" fxFlex="20" tabindex="3" value="{{paymentTypes.OFFER}}">Offer</mat-radio-button>
</mat-radio-group>
<form fxLayoutAlign="space-between stretch" fxLayout="column" #sendPaymentForm="ngForm">
<form fxLayoutAlign="space-between stretch" fxLayout="column" #sendPaymentForm="ngForm" (submit)="onSendPayment()" (reset)="resetData()">
<ng-container *ngTemplateOutlet="paymentType === paymentTypes.KEYSEND ? keysendBlock : paymentType === paymentTypes.OFFER ? offerBlock : invoiceBlock"></ng-container>
<div fxFlex="100" class="alert alert-danger mt-1" *ngIf="paymentError !== ''">
<fa-icon [icon]="faExclamationTriangle" class="mr-1 alert-icon"></fa-icon>
<span *ngIf="paymentError !== ''">{{paymentError}}</span>
</div>
<div class="mt-2" fxLayout="row" fxLayoutAlign="end center">
<button class="mr-1" mat-button color="primary" tabindex="8" type="reset" (click)="resetData()">Clear Fields</button>
<button mat-button color="primary" (click)="onSendPayment()" tabindex="7">Send Payment</button>
<button class="mr-1" mat-button color="primary" tabindex="9" type="reset">Clear Fields</button>
<button mat-button color="primary" type="submit" tabindex="8">Send Payment</button>
</div>
</form>
</mat-card-content>
@ -62,6 +62,9 @@
<mat-error *ngIf="!offerAmount">Offer amount is required.</mat-error>
</mat-form-field>
<div fxFlex="100">
<mat-checkbox fxFlex="30" tabindex="6" class="mt-1" color="primary" [(ngModel)]="flgSaveToDB" matTooltip="Save offer in database for future payments" [matTooltipPosition]="'above'">Save Offer</mat-checkbox>
<mat-checkbox fxFlex="30" tabindex="6" class="my-1" color="primary" [(ngModel)]="flgSaveToDB" matTooltip="Save offer in database for future payments" [matTooltipPosition]="'above'">Save Offer</mat-checkbox>
</div>
<mat-form-field fxFlex="100" *ngIf="flgSaveToDB">
<input matInput placeholder="Label to Save" [(ngModel)]="offerLabel" tabindex="7">
</mat-form-field>
</ng-template>

@ -1,5 +1,5 @@
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
import { NgModel } from '@angular/forms';
import { NgForm, NgModel } from '@angular/forms';
import { DecimalPipe } from '@angular/common';
import { Subject } from 'rxjs';
import { takeUntil, filter } from 'rxjs/operators';
@ -27,6 +27,10 @@ import { ApiCallStatusPayload } from '../../../shared/models/apiCallsPayload';
})
export class CLLightningSendPaymentsComponent implements OnInit, OnDestroy {
@ViewChild('sendPaymentForm', { static: false }) form: NgForm;
@ViewChild('paymentAmt', { static: false }) paymentAmt: NgModel;
@ViewChild('offerAmt', { static: false }) offerAmt: NgModel;
private paymentReq: NgModel;
@ViewChild('paymentReq') set payReq(paymReq: NgModel) {
if (paymReq) {
@ -47,7 +51,9 @@ export class CLLightningSendPaymentsComponent implements OnInit, OnDestroy {
public offerDecoded: OfferRequest = {};
public offerRequest = '';
public offerDecodedHint = '';
public offerMemo = '';
public offerDescription = '';
public offerIssuer = '';
public offerLabel = '';
public zeroAmtOffer = false;
public offerInvoice: OfferInvoice = null;
public offerAmount = null;
@ -146,7 +152,11 @@ export class CLLightningSendPaymentsComponent implements OnInit, OnDestroy {
break;
case PaymentTypes.INVOICE:
if (!this.paymentRequest) { return true; }
if (!this.paymentRequest || (this.zeroAmtInvoice && (this.paymentAmount === 0 || !this.paymentAmount))) {
this.paymentReq.control.markAsTouched();
this.paymentAmt.control.markAsTouched();
return true;
}
if (this.paymentDecoded.created_at) {
this.sendPayment();
} else {
@ -156,7 +166,11 @@ export class CLLightningSendPaymentsComponent implements OnInit, OnDestroy {
break;
case PaymentTypes.OFFER:
if (!this.offerRequest) { return true; }
if (!this.offerRequest || (this.zeroAmtOffer && (this.offerAmount === 0 || !this.offerAmount))) {
this.offerReq.control.markAsTouched();
this.offerAmt.control.markAsTouched();
return true;
}
if (this.offerDecoded.offer_id) {
this.sendPayment();
} else {
@ -189,7 +203,7 @@ export class CLLightningSendPaymentsComponent implements OnInit, OnDestroy {
this.store.dispatch(fetchOfferInvoice({ payload: { offer: this.offerRequest } }));
}
} else {
this.store.dispatch(sendPayment({ payload: { uiMessage: UI_MESSAGES.SEND_OFFER, paymentType: PaymentTypes.OFFER, invoice: this.offerInvoice.invoice, saveToDB: this.flgSaveToDB, amount: this.offerAmount * 1000, description: this.offerMemo, fromDialog: true } }));
this.store.dispatch(sendPayment({ payload: { uiMessage: UI_MESSAGES.SEND_OFFER, paymentType: PaymentTypes.OFFER, invoice: this.offerInvoice.invoice, saveToDB: this.flgSaveToDB, offer: this.offerRequest, amount: this.offerAmount * 1000, label: this.offerLabel, issuer: this.offerIssuer, description: this.offerDescription, fromDialog: true } }));
}
}
}
@ -216,6 +230,7 @@ export class CLLightningSendPaymentsComponent implements OnInit, OnDestroy {
this.offerDecodedHint = '';
this.offerReq.control.setErrors(null);
this.zeroAmtOffer = false;
this.flgSaveToDB = false;
this.paymentError = '';
}
@ -257,7 +272,8 @@ export class CLLightningSendPaymentsComponent implements OnInit, OnDestroy {
this.zeroAmtOffer = false;
this.offerDecoded.amount = +(this.offerDecoded.amount || this.offerDecoded.amount_msat.slice(0, -4));
this.offerAmount = this.offerDecoded.amount ? this.offerDecoded.amount / 1000 : 0;
this.offerMemo = this.offerDecoded.description;
this.offerDescription = this.offerDecoded.description;
this.offerIssuer = this.offerDecoded.issuer ? this.offerDecoded.issuer : this.offerDecoded.vendor ? this.offerDecoded.vendor : '';
if (this.selNode.fiatConversion) {
this.commonService.convertCurrency(this.offerAmount, CurrencyUnitEnum.SATS, CurrencyUnitEnum.OTHER, this.selNode.currencyUnits[2], this.selNode.fiatConversion).
pipe(takeUntil(this.unSubs[5])).

@ -26,14 +26,14 @@ export class CLTransactionsComponent implements OnInit, OnDestroy {
faChartPie = faChartPie;
currencyUnits = [];
balances = [{ title: 'Local Capacity', dataValue: 0, tooltip: 'Amount you can send' }, { title: 'Remote Capacity', dataValue: 0, tooltip: 'Amount you can receive' }];
public links = [{ link: 'payments', name: 'Payments' }, { link: 'invoices', name: 'Invoices' }, { link: 'offers', name: 'Offers' }];
public selNode: SelNodeChild = {};
public links = [{ link: 'payments', name: 'Payments' }, { link: 'invoices', name: 'Invoices' }];
public activeLink = this.links[0].link;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject(), new Subject()];
constructor(private logger: LoggerService, private store: Store<RTLState>, private router: Router) { }
ngOnInit() {
this.store.dispatch(fetchOffers());
const linkFound = this.links.find((link) => this.router.url.includes(link.link));
this.activeLink = linkFound ? linkFound.link : this.links[0].link;
this.router.events.pipe(takeUntil(this.unSubs[0]), filter((e) => e instanceof ResolveEnd)).
@ -41,7 +41,14 @@ export class CLTransactionsComponent implements OnInit, OnDestroy {
const linkFound = this.links.find((link) => value.urlAfterRedirects.includes(link.link));
this.activeLink = linkFound ? linkFound.link : this.links[0].link;
});
this.store.select(localRemoteBalance).pipe(takeUntil(this.unSubs[1]),
this.store.select(clNodeSettings).pipe(takeUntil(this.unSubs[1])).subscribe((nodeSettings: SelNodeChild) => {
this.selNode = nodeSettings;
if (this.selNode.enableOffers) {
this.store.dispatch(fetchOffers());
this.links.push({ link: 'offers', name: 'Offers' });
}
});
this.store.select(localRemoteBalance).pipe(takeUntil(this.unSubs[2]),
withLatestFrom(this.store.select(clNodeSettings))).
subscribe(([lrBalSeletor, nodeSettings]: [{ localRemoteBalance: LocalRemoteBalance, apiCallStatus: ApiCallStatusPayload }, SelNodeChild]) => {
this.currencyUnits = nodeSettings.currencyUnits;

@ -135,15 +135,28 @@ export class ECLChannelOpenTableComponent implements OnInit, AfterViewInit, OnDe
const base_fee = confirmRes[0].inputValue;
const fee_rate = confirmRes[1].inputValue;
let updateRequestPayload = null;
let node_ids = '';
if (channelToUpdate === 'all') {
this.activeChannels.forEach((channel) => {
node_ids = node_ids + ',' + channel.nodeId;
});
node_ids = node_ids.substring(1);
updateRequestPayload = { baseFeeMsat: base_fee, feeRate: fee_rate, nodeIds: node_ids };
if (this.commonService.isVersionCompatible(this.information.version, '0.6.2')) {
let node_ids = '';
if (channelToUpdate === 'all') {
this.activeChannels.forEach((channel) => {
node_ids = node_ids + ',' + channel.nodeId;
});
node_ids = node_ids.substring(1);
updateRequestPayload = { baseFeeMsat: base_fee, feeRate: fee_rate, nodeIds: node_ids };
} else {
updateRequestPayload = { baseFeeMsat: base_fee, feeRate: fee_rate, nodeId: channelToUpdate.nodeId };
}
} else {
updateRequestPayload = { baseFeeMsat: base_fee, feeRate: fee_rate, nodeId: channelToUpdate.nodeId };
let channel_ids = '';
if (channelToUpdate === 'all') {
this.activeChannels.forEach((channel) => {
channel_ids = channel_ids + ',' + channel.channelId;
});
channel_ids = channel_ids.substring(1);
updateRequestPayload = { baseFeeMsat: base_fee, feeRate: fee_rate, channelIds: channel_ids };
} else {
updateRequestPayload = { baseFeeMsat: base_fee, feeRate: fee_rate, channelId: channelToUpdate.channelId };
}
}
this.store.dispatch(updateChannel({ payload: updateRequestPayload }));
}

@ -364,15 +364,19 @@ export class ECLEffects implements OnDestroy {
let queryParam = '?feeBaseMsat=' + action.payload.baseFeeMsat + '&feeProportionalMillionths=' + action.payload.feeRate;
if (action.payload.nodeIds) {
queryParam = queryParam + '&nodeIds=' + action.payload.nodeIds;
} else {
} else if (action.payload.nodeId) {
queryParam = queryParam + '&nodeId=' + action.payload.nodeId;
} else if (action.payload.channelIds) {
queryParam = queryParam + '&channelIds=' + action.payload.channelIds;
} else {
queryParam = queryParam + '&channelId=' + action.payload.channelId;
}
return this.httpClient.post(this.CHILD_API_URL + environment.CHANNELS_API + '/updateRelayFee' + queryParam, {}).
pipe(
map((postRes: any) => {
this.logger.info(postRes);
this.store.dispatch(closeSpinner({ payload: UI_MESSAGES.UPDATE_CHAN_POLICY }));
if (action.payload.nodeIds) {
if (action.payload.nodeIds || action.payload.channelIds) {
this.store.dispatch(openSnackBar({ payload: 'Channels Updated Successfully.' }));
} else {
this.store.dispatch(openSnackBar({ payload: 'Channel Updated Successfully!' }));

@ -1,12 +1,12 @@
<div [perfectScrollbar] fxLayout="column" fxFlex="100" fxLayoutAlign="space-between start">
<div fxLayout="column" fxFlex="90" fxLayoutAlign="start stretch" class="w-100">
<mat-select *ngIf="appConfig.nodes.length > 1" [value]="selNode" (selectionChange)="onNodeSelectionChange($event.value)" class="m-2 multi-node-select">
<mat-option *ngFor="let node of appConfig.nodes" [value]="node" tabindex="1">
<mat-select *ngIf="appConfig.nodes.length > 1" [value]="selConfigNodeIndex" (selectionChange)="onNodeSelectionChange($event.value)" class="m-2 multi-node-select">
<mat-option *ngFor="let node of appConfig.nodes" [value]="node.index" tabindex="1">
{{node.lnNode}} ({{node.lnImplementation}})
</mat-option>
</mat-select>
<mat-divider class="w-100"></mat-divider>
<mat-tree #tree [dataSource]="navMenus" [treeControl]="treeControlNested" *ngIf="settings.lnServerUrl">
<mat-tree #tree [dataSource]="navMenus" [treeControl]="treeControlNested" *ngIf="settings?.lnServerUrl">
<mat-tree-node *matTreeNodeDef="let node" matTreeNodeToggle routerLinkActive="active-link" routerLink="{{node.link}}">
<div (click)="onChildNavClicked(node)">
<div fxLayout="row" fxFlex="100" fxLayoutAlign="start center">

@ -34,6 +34,7 @@ export class SideNavigationComponent implements OnInit, OnDestroy {
faEject = faEject;
faEye = faEye;
public appConfig: RTLConfiguration;
public selConfigNodeIndex: Number;
public selNode: ConfigSettingsNode;
public settings: Settings;
public version = '';
@ -87,9 +88,6 @@ export class SideNavigationComponent implements OnInit, OnDestroy {
this.informationChain.chain = '';
this.informationChain.network = '';
}
if (this.selNode && this.selNode.lnImplementation === 'CLT' && this.information.api_version) {
this.loadCLTMenu();
}
this.flgLoading = !(this.information.identity_pubkey);
if (window.innerWidth <= 414) {
this.smallScreen = true;
@ -98,9 +96,13 @@ export class SideNavigationComponent implements OnInit, OnDestroy {
this.store.select(rootSelectedNode).
pipe(takeUntil(this.unSubs[2])).
subscribe((selNode) => {
let previousSelNode: ConfigSettingsNode = this.selNode ? JSON.parse(JSON.stringify(this.selNode)) : null;
this.selNode = selNode;
this.settings = this.selNode.settings;
if (this.selNode && this.selNode.lnImplementation) {
if (previousSelNode.index !== this.selNode.index) {
this.selConfigNodeIndex = +selNode.index;
}
if (this.selNode && this.selNode.lnImplementation && this.selNode.lnImplementation !== previousSelNode.lnImplementation) {
this.filterSideMenuNodes();
}
this.logger.info(selNode);
@ -197,10 +199,11 @@ export class SideNavigationComponent implements OnInit, OnDestroy {
this.ChildNavClicked.emit('showData');
}
onNodeSelectionChange(selNodeValue: ConfigSettingsNode) {
const prevIndex = this.selNode.index;
this.selNode = selNodeValue;
this.store.dispatch(setSelectedNode({ payload: { uiMessage: UI_MESSAGES.UPDATE_SELECTED_NODE, prevLnNodeIndex: +prevIndex, currentLnNode: selNodeValue, isInitialSetup: false } }));
onNodeSelectionChange(selNodeValue: Number) {
const prevIndex = this.selConfigNodeIndex;
this.selConfigNodeIndex = selNodeValue;
const foundNode = this.appConfig.nodes.find((node) => +node.index === selNodeValue);
this.store.dispatch(setSelectedNode({ payload: { uiMessage: UI_MESSAGES.UPDATE_SELECTED_NODE, prevLnNodeIndex: +prevIndex, currentLnNode: foundNode, isInitialSetup: false } }));
this.ChildNavClicked.emit('selectNode');
}

@ -0,0 +1,44 @@
<div [perfectScrollbar] fxLayout="column" fxFlex="100">
<div fxFlex="100" class="alert alert-info mt-1">
<fa-icon [icon]="faInfoCircle" class="mr-1 alert-icon"></fa-icon>
<span>Exprimental features ahead, so proceed with caution. By enabling these features, you could lose funds.</span>
</div>
<form fxLayout="column" fxLayoutAlign="start stretch" class="page-sub-title-container mt-1" #form="ngForm">
<div fxLayout="row">
<fa-icon [icon]="faCode" class="page-title-img mr-1"></fa-icon>
<span class="page-title">Features</span>
</div>
<mat-accordion>
<mat-expansion-panel [expanded]="false" class="flat-expansion-panel my-1" *ngFor="let feature of features; index as i">
<mat-expansion-panel-header>
<mat-panel-title fxFlex="100" fxLayoutAlign="space-between center">
<h4 class="font-bold-500">{{feature.name}}</h4>
<h4 class="font-bold-500">
<span *ngIf="feature.enabled" class="dot green"></span>
<span *ngIf="!feature.enabled" class="dot yellow"></span>
{{feature.enabled ? 'Enabled' : 'Disabled'}}
</h4>
</mat-panel-title>
</mat-expansion-panel-header>
<div fxLayout="column" fxFlex="100" fxLayoutAlign="start stretch">
<form *ngIf="i === 0" fxLayout="column" fxFlex="100" fxLayoutAlign="start stretch" class="page-sub-title-container" #form="ngForm">
<div fxFlex="100" class="alert alert-info">
<fa-icon [icon]="faInfoCircle" class="mr-1 alert-icon"></fa-icon>
<span>Please ensure that <strong>experimental-offers</strong> flag is set to true in the CLightning config before enabling it in the RTL. Click <strong><a href="http://bolt12.org" target="_blank">here</a></strong> to learn more about CLightning offers.</span>
</div>
<h4 class="mt-2">Description</h4>
<span>Offers is a draft specification (called a "BOLT") for Lightning wallets and nodes, with experimental support already in c-lightning.</span>
<h4 class="mt-2">Links</h4>
<span><a href="https://github.com/lightningnetwork/lightning-rfc/pull/798 " target="blank">Clightning Bolt12</a></span>
<mat-divider [inset]="true" class="my-2"></mat-divider>
<div class="alert alert-warn">
<fa-icon [icon]="faExclamationTriangle" class="mr-1 alert-icon"></fa-icon>
<span>Do not get an Offer tattoo until spec is fully ratified!</span>
</div>
<mat-slide-toggle autoFocus class="my-1" tabindex="1" color="primary" name="enableOfr" [(ngModel)]="enableOffers" (change)="onUpdateFeature()">Enable Offers {{enableOffers ? '(You can find Offers under Lightning -> Transactions -> Offers)' : ''}}</mat-slide-toggle>
</form>
</div>
</mat-expansion-panel>
</mat-accordion>
</form>
</div>

@ -0,0 +1,41 @@
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { StoreModule } from '@ngrx/store';
import { RootReducer } from '../../../../store/rtl.reducers';
import { LNDReducer } from '../../../../lnd/store/lnd.reducers';
import { CLReducer } from '../../../../clightning/store/cl.reducers';
import { ECLReducer } from '../../../../eclair/store/ecl.reducers';
import { SharedModule } from '../../../shared.module';
import { ExperimentalSettingsComponent } from './experimental-settings.component';
describe('ExperimentalSettingsComponent', () => {
let component: ExperimentalSettingsComponent;
let fixture: ComponentFixture<ExperimentalSettingsComponent>;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ExperimentalSettingsComponent],
imports: [
SharedModule,
RouterTestingModule,
StoreModule.forRoot({ root: RootReducer, lnd: LNDReducer, cl: CLReducer, ecl: ECLReducer })
]
}).
compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ExperimentalSettingsComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
afterEach(() => {
TestBed.resetTestingModule();
});
});

@ -1,31 +1,33 @@
import { Component, ViewChild, OnInit, OnDestroy } from '@angular/core';
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { faInfoCircle, faCode, faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
import { ServicesEnum, UI_MESSAGES } from '../../../../services/consts-enums-functions';
import { ConfigSettingsNode } from '../../../../models/RTLconfig';
import { LoggerService } from '../../../../services/logger.service';
import { faInfoCircle } from '@fortawesome/free-solid-svg-icons';
import { updateServiceSettings } from '../../../../../store/rtl.actions';
import { RTLState } from '../../../../../store/rtl.state';
import { setChildNodeSettingsLND } from '../../../../../lnd/store/lnd.actions';
import { setChildNodeSettingsCL } from '../../../../../clightning/store/cl.actions';
import { setChildNodeSettingsECL } from '../../../../../eclair/store/ecl.actions';
import { rootSelectedNode } from '../../../../../store/rtl.selector';
import { LoggerService } from '../../../services/logger.service';
import { RTLState } from '../../../../store/rtl.state';
import { rootSelectedNode } from '../../../../store/rtl.selector';
import { ConfigSettingsNode } from '../../../models/RTLconfig';
import { updateServiceSettings } from '../../../../store/rtl.actions';
import { setChildNodeSettingsLND } from '../../../../lnd/store/lnd.actions';
import { setChildNodeSettingsCL } from '../../../../clightning/store/cl.actions';
import { setChildNodeSettingsECL } from '../../../../eclair/store/ecl.actions';
import { ServicesEnum, UI_MESSAGES } from '../../../services/consts-enums-functions';
@Component({
selector: 'rtl-offers-service-settings',
templateUrl: './offers-service-settings.component.html',
styleUrls: ['./offers-service-settings.component.scss']
selector: 'rtl-experimental-settings',
templateUrl: './experimental-settings.component.html',
styleUrls: ['./experimental-settings.component.scss']
})
export class OffersServiceSettingsComponent implements OnInit, OnDestroy {
export class ExperimentalSettingsComponent implements OnInit, OnDestroy {
@ViewChild('form', { static: true }) form: any;
public faInfoCircle = faInfoCircle;
public selNode: ConfigSettingsNode;
public faExclamationTriangle = faExclamationTriangle;
public faCode = faCode;
public features = [{ name: 'Offers', enabled: false }];
public enableOffers = false;
unSubs: Array<Subject<void>> = [new Subject(), new Subject()];
public selNode: ConfigSettingsNode;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject()];
constructor(private logger: LoggerService, private store: Store<RTLState>) { }
@ -33,18 +35,16 @@ export class OffersServiceSettingsComponent implements OnInit, OnDestroy {
this.store.select(rootSelectedNode).pipe(takeUntil(this.unSubs[0])).
subscribe((selNode) => {
this.selNode = selNode;
this.enableOffers = selNode.settings.enableOffers || false;
this.logger.info(selNode);
this.enableOffers = this.selNode.settings.enableOffers;
this.features[0].enabled = this.enableOffers;
this.logger.info(this.selNode);
});
}
onEnableServiceChanged(event) {
this.enableOffers = event.checked;
}
onUpdateService(): boolean | void {
onUpdateFeature(): boolean | void {
this.logger.info(this.selNode);
this.selNode.settings.enableOffers = this.enableOffers;
this.features[0].enabled = this.enableOffers;
this.store.dispatch(updateServiceSettings({ payload: { uiMessage: UI_MESSAGES.UPDATE_SETTING, service: ServicesEnum.OFFERS, settings: { enableOffers: this.enableOffers } } }));
this.store.dispatch(setChildNodeSettingsLND({
payload: {
@ -64,9 +64,9 @@ export class OffersServiceSettingsComponent implements OnInit, OnDestroy {
}
ngOnDestroy() {
this.unSubs.forEach((unsub) => {
unsub.next();
unsub.complete();
this.unSubs.forEach((completeSub) => {
completeSub.next(null);
completeSub.complete();
});
}

@ -7,8 +7,9 @@
<mat-card-content fxLayout="column">
<nav mat-tab-nav-bar>
<div role="tab" mat-tab-link class="mat-tab-label" [active]="activeLink === links[0].link" (click)="activeLink = links[0].link" routerLink="{{links[0].link}}">{{links[0].name}}</div>
<div role="tab" mat-tab-link *ngIf="selNode?.lnImplementation?.toUpperCase() !== 'ECL'" class="mat-tab-label" [active]="activeLink === links[1].link" (click)="activeLink = links[1].link" routerLink="{{links[1].link}}" [state]="{ initial: false }">{{links[1].name}}</div>
<div role="tab" mat-tab-link *ngIf="showLnConfig" class="mat-tab-label" [active]="activeLink === links[2].link" (click)="activeLink = links[2].link" routerLink="{{links[2].link}}">{{links[2].name}}</div>
<div role="tab" mat-tab-link *ngIf="selNode?.lnImplementation?.toUpperCase() === 'LND'" class="mat-tab-label" [active]="activeLink === links[1].link" (click)="activeLink = links[1].link" routerLink="{{links[1].link}}" [state]="{ initial: false }">{{links[1].name}}</div>
<div role="tab" mat-tab-link *ngIf="selNode?.lnImplementation?.toUpperCase() === 'CLT'" class="mat-tab-label" [active]="activeLink === links[2].link" (click)="activeLink = links[2].link" routerLink="{{links[2].link}}">{{links[2].name}}</div>
<div role="tab" mat-tab-link *ngIf="showLnConfig" class="mat-tab-label" [active]="activeLink === links[3].link" (click)="activeLink = links[3].link" routerLink="{{links[3].link}}">{{links[3].name}}</div>
</nav>
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="mat-tab-body-wrapper">
<router-outlet></router-outlet>

@ -20,7 +20,7 @@ export class NodeConfigComponent implements OnInit, OnDestroy {
public showLnConfig = false;
public selNode: ConfigSettingsNode;
public lnImplementationStr = '';
public links = [{ link: 'node', name: 'Node' }, { link: 'services', name: 'Services' }, { link: 'lnconfig', name: this.lnImplementationStr }];
public links = [{ link: 'layout', name: 'Layout' }, { link: 'services', name: 'Services' }, { link: 'experimental', name: 'Experimental' }, { link: 'lnconfig', name: this.lnImplementationStr }];
public activeLink = '';
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject()];
@ -51,7 +51,7 @@ export class NodeConfigComponent implements OnInit, OnDestroy {
break;
}
if (this.selNode.authentication && this.selNode.authentication.configPath && this.selNode.authentication.configPath.trim() !== '') {
this.links[2].name = this.lnImplementationStr;
this.links[3].name = this.lnImplementationStr;
this.showLnConfig = true;
}
});

@ -1,14 +0,0 @@
<div [perfectScrollbar] fxLayout="column" fxFlex="100">
<div fxFlex="100" class="alert alert-info mt-1">
<fa-icon [icon]="faInfoCircle" class="mr-1 alert-icon"></fa-icon>
<span>Please ensure that <strong>experimental-offers</strong> flag is set to true in the CLightning config before enabling it in the RTL. Click <strong><a href="http://bolt12.org" target="_blank">here</a></strong> to learn more about CLightning offers.</span>
</div>
<form fxLayout="column" fxFlex="100" fxLayoutAlign="start stretch" class="settings-container page-sub-title-container mt-1" #form="ngForm">
<div fxLayout="column" fxFlex="50" fxLayoutAlign="start stretch">
<mat-slide-toggle autoFocus class="mb-1" tabindex="1" color="primary" [(ngModel)]="enableOffers" (change)="onEnableServiceChanged($event)" name="offers">Enable Offers</mat-slide-toggle>
</div>
</form>
<div fxLayout="row" class="mt-2">
<button mat-flat-button color="primary" type="submit" (click)="onUpdateService()" tabindex="2">Update</button>
</div>
</div>

@ -6,9 +6,8 @@
<mat-card>
<mat-card-content fxLayout="column">
<nav mat-tab-nav-bar>
<div role="tab" *ngIf="selNode?.lnImplementation === links[0].implementation" mat-tab-link class="mat-tab-label" [active]="activeLink === links[0].link" (click)="activeLink = links[0].link" routerLink="{{links[0].link}}">{{links[0].name}}</div>
<div role="tab" *ngIf="selNode?.lnImplementation === links[1].implementation" mat-tab-link class="mat-tab-label" [active]="activeLink === links[1].link" (click)="activeLink = links[1].link" routerLink="{{links[1].link}}" [state]="{ initial: false }">{{links[1].name}}</div>
<div role="tab" *ngIf="selNode?.lnImplementation === links[2].implementation" mat-tab-link class="mat-tab-label" [active]="activeLink === links[2].link" (click)="activeLink = links[2].link" routerLink="{{links[2].link}}">{{links[2].name}}</div>
<div role="tab" mat-tab-link class="mat-tab-label" [active]="activeLink === links[0].link" (click)="activeLink = links[0].link" routerLink="{{links[0].link}}">{{links[0].name}}</div>
<div role="tab" mat-tab-link class="mat-tab-label" [active]="activeLink === links[1].link" (click)="activeLink = links[1].link" routerLink="{{links[1].link}}" [state]="{ initial: false }">{{links[1].name}}</div>
</nav>
<div fxLayout="column" fxFlex="100" fxLayoutAlign="space-between stretch" class="mat-tab-body-wrapper">
<router-outlet></router-outlet>

@ -1,14 +1,9 @@
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Router, ResolveEnd, ActivatedRoute } from '@angular/router';
import { Router, ResolveEnd } from '@angular/router';
import { Subject } from 'rxjs';
import { takeUntil, filter } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { faLayerGroup } from '@fortawesome/free-solid-svg-icons';
import { RTLState } from '../../../../store/rtl.state';
import { rootSelectedNode } from '../../../../store/rtl.selector';
import { ConfigSettingsNode } from '../../../models/RTLconfig';
@Component({
selector: 'rtl-services-settings',
templateUrl: './services-settings.component.html',
@ -17,12 +12,11 @@ import { ConfigSettingsNode } from '../../../models/RTLconfig';
export class ServicesSettingsComponent implements OnInit, OnDestroy {
public faLayerGroup = faLayerGroup;
public links = [{ link: 'loop', name: 'Loop', implementation: 'LND' }, { link: 'boltz', name: 'Boltz', implementation: 'LND' }, { link: 'offers', name: 'Offers', implementation: 'CLT' }];
public links = [{ link: 'loop', name: 'Loop' }, { link: 'boltz', name: 'Boltz' }];
public activeLink = '';
public selNode: ConfigSettingsNode;
private unSubs: Array<Subject<void>> = [new Subject(), new Subject(), new Subject()];
constructor(private store: Store<RTLState>, private router: Router, private activatedRoute: ActivatedRoute) { }
constructor(private router: Router) { }
ngOnInit() {
const linkFound = this.links.find((link) => this.router.url.includes(link.link));
@ -32,16 +26,6 @@ export class ServicesSettingsComponent implements OnInit, OnDestroy {
const linkFound = this.links.find((link) => value.urlAfterRedirects.includes(link.link));
this.activeLink = linkFound ? linkFound.link : this.links[0].link;
});
this.store.select(rootSelectedNode).pipe(takeUntil(this.unSubs[1])).subscribe((selNode: ConfigSettingsNode) => {
this.selNode = selNode;
if (this.selNode.lnImplementation === 'CLT') {
let prevActiveLink = this.activeLink;
this.activeLink = this.links[2].link;
if (prevActiveLink !== this.activeLink) {
this.router.navigate(['./', this.activeLink], { relativeTo: this.activatedRoute });
}
}
});
}
ngOnDestroy() {

@ -201,6 +201,7 @@ export interface OfferRequest {
description?: string;
signature?: string;
chains?: string[];
issuer?: string;
currency?: string;
minor_unit?: number;
amount?: number;
@ -400,9 +401,11 @@ export interface SendPayment {
fromDialog: boolean;
paymentType: PaymentTypes;
label?: string;
issuer?: string;
invoice?: string;
description?: string;
saveToDB?: boolean;
offer?: string;
amount?: number;
pubkey?: string;
}

@ -230,6 +230,8 @@ export interface SaveChannel {
export interface UpdateChannel {
baseFeeMsat: number;
feeRate: number;
channelId?: string;
channelIds?: string;
nodeId?: string;
nodeIds?: string;
}

@ -321,6 +321,7 @@ export enum RTLActions {
SAVE_SETTINGS = 'SAVE_SETTINGS',
TWO_FA_SAVE_SETTINGS = 'TWO_FA_SAVE_SETTINGS',
SET_SELECTED_NODE = 'SET_SELECTED_NODE',
UPDATE_ROOT_NODE_SETTINGS = 'UPDATE_ROOT_NODE_SETTINGS',
UPDATE_SERVICE_SETTINGS = 'UPDATE_SERVICE_SETTINGS',
SET_NODE_DATA = 'SET_NODE_DATA',
IS_AUTHORIZED = 'IS_AUTHORIZED',

@ -64,7 +64,7 @@ import { NodeSettingsComponent } from './components/node-config/node-settings/no
import { ServicesSettingsComponent } from './components/node-config/services-settings/services-settings.component';
import { LoopServiceSettingsComponent } from './components/node-config/services-settings/loop-service-settings/loop-service-settings.component';
import { BoltzServiceSettingsComponent } from './components/node-config/services-settings/boltz-service-settings/boltz-service-settings.component';
import { OffersServiceSettingsComponent } from './components/node-config/services-settings/offers-service-settings/offers-service-settings.component';
import { ExperimentalSettingsComponent } from './components/node-config/experimental-settings/experimental-settings.component';
import { ErrorComponent } from './components/error/error.component';
import { CurrencyUnitConverterComponent } from './components/currency-unit-converter/currency-unit-converter.component';
import { HorizontalScrollerComponent } from './components/horizontal-scroller/horizontal-scroller.component';
@ -241,7 +241,7 @@ export const DEFAULT_DATE_FORMAT: MatDateFormats = {
ServicesSettingsComponent,
LoopServiceSettingsComponent,
BoltzServiceSettingsComponent,
OffersServiceSettingsComponent,
ExperimentalSettingsComponent,
CurrencyUnitConverterComponent,
HorizontalScrollerComponent,
TransactionsReportTableComponent,
@ -279,7 +279,7 @@ export const DEFAULT_DATE_FORMAT: MatDateFormats = {
ServicesSettingsComponent,
LoopServiceSettingsComponent,
BoltzServiceSettingsComponent,
OffersServiceSettingsComponent,
ExperimentalSettingsComponent,
CurrencyUnitConverterComponent,
HorizontalScrollerComponent,
ErrorComponent,

@ -1540,6 +1540,9 @@ th.mat-header-cell:last-of-type, td.mat-cell:last-of-type, td.mat-footer-cell:la
@include for_screensize(phone) {
padding: 0 math.div($gap, 2) 0 math.div($gap, 2);
}
& .mat-expansion-indicator {
margin-top: -5px;
}
}
& .mat-expansion-panel-body {
padding: 0 $gap*3 $gap*2 $gap*3;

@ -378,6 +378,7 @@
border: 1px solid $red-color;
background-color: $red-background-color;
color: $red-color;
overflow-wrap: break-word;
& .alert-icon.ng-fa-icon {
color: $red-color;
}

@ -48,6 +48,8 @@ export const twoFASaveSettings = createAction(RTLActions.TWO_FA_SAVE_SETTINGS, p
export const setSelectedNode = createAction(RTLActions.SET_SELECTED_NODE, props<{ payload: SetSelectedNode }>());
export const updateRootNodeSettings = createAction(RTLActions.UPDATE_ROOT_NODE_SETTINGS, props<{ payload: UpdateServiceSetting }>());
export const updateServiceSettings = createAction(RTLActions.UPDATE_SERVICE_SETTINGS, props<{ payload: UpdateServiceSetting }>());
export const setNodeData = createAction(RTLActions.SET_NODE_DATA, props<{ payload: GetInfoRoot }>());

@ -27,7 +27,7 @@ import { ErrorMessageComponent } from '../shared/components/data-modal/error-mes
import { ShowPubkeyComponent } from '../shared/components/data-modal/show-pubkey/show-pubkey.component';
import { RTLState } from './rtl.state';
import { resetRootStore, setNodeData, setSelectedNode, updateRootAPICallStatus, closeSpinner, openAlert, openSpinner, openSnackBar, fetchRTLConfig, closeAllDialogs, logout } from './rtl.actions';
import { resetRootStore, setNodeData, setSelectedNode, updateRootAPICallStatus, closeSpinner, openAlert, openSpinner, openSnackBar, fetchRTLConfig, closeAllDialogs, logout, updateRootNodeSettings } from './rtl.actions';
import { fetchInfoLND, resetLNDStore } from '../lnd/store/lnd.actions';
import { fetchInfoCL, resetCLStore } from '../clightning/store/cl.actions';
import { fetchInfoECL, resetECLStore } from '../eclair/store/ecl.actions';
@ -289,6 +289,7 @@ export class RTLEffects implements OnDestroy {
this.logger.info(updateStatus);
this.store.dispatch(updateRootAPICallStatus({ payload: { action: 'UpdateServiceSettings', status: APICallStatusEnum.COMPLETED } }));
this.store.dispatch(closeSpinner({ payload: action.payload.uiMessage }));
this.store.dispatch(updateRootNodeSettings({ payload: action.payload }));
return {
type: RTLActions.OPEN_SNACK_BAR,
payload: updateStatus.message + '.'

@ -1,7 +1,9 @@
import { createReducer, on } from '@ngrx/store';
import { initRootState } from './rtl.state';
import { resetRootStore, setNodeData, setRTLConfig, setSelectedNode, updateRootAPICallStatus } from './rtl.actions';
import { resetRootStore, setNodeData, setRTLConfig, setSelectedNode, updateRootAPICallStatus, updateRootNodeSettings } from './rtl.actions';
import { ServicesEnum } from '../shared/services/consts-enums-functions';
import { ConfigSettingsNode } from '../shared/models/RTLconfig';
export const RootReducer = createReducer(initRootState,
on(updateRootAPICallStatus, (state, { payload }) => {
@ -28,6 +30,27 @@ export const RootReducer = createReducer(initRootState,
...state,
selNode: payload.currentLnNode
})),
on(updateRootNodeSettings, (state, { payload }) => {
let updatedSelNode: ConfigSettingsNode = JSON.parse(JSON.stringify(state.selNode));
switch (payload.service) {
case ServicesEnum.BOLTZ:
updatedSelNode.settings.boltzServerUrl = payload.settings.boltzServerUrl;
break;
case ServicesEnum.LOOP:
updatedSelNode.settings.swapServerUrl = payload.settings.swapServerUrl;
break;
case ServicesEnum.OFFERS:
updatedSelNode.settings.enableOffers = payload.settings.enableOffers;
break;
default:
break;
}
return {
...state,
selNode: updatedSelNode
};
}),
on(setNodeData, (state, { payload }) => ({
...state,
nodeData: payload

Loading…
Cancel
Save