first
This commit is contained in:
commit
299007e990
29 changed files with 2887 additions and 0 deletions
103
templates/nwcprovider/admin.html
Normal file
103
templates/nwcprovider/admin.html
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
||||
%} {% block page %}
|
||||
<div class="row q-col-gutter-md" id="configTable">
|
||||
<div class="col-12 q-gutter-y-md">
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<div class="row items-center no-wrap q-mb-md">
|
||||
<div class="col">
|
||||
<h5 class="text-subtitle1 q-my-none">NWC Service Provider - Config</h5>
|
||||
</div>
|
||||
</div>
|
||||
<template>
|
||||
<q-markup-table flat>
|
||||
<tbody>
|
||||
<q-tr v-for="entry in entries" :key="entry.key">
|
||||
<q-td>
|
||||
<span>
|
||||
${entry.key}
|
||||
</span>
|
||||
</q-td>
|
||||
<q-td>
|
||||
<q-input v-model="entry.value" />
|
||||
</q-td>
|
||||
</q-tr>
|
||||
</tbody>
|
||||
</q-markup-table>
|
||||
</template>
|
||||
<q-btn @click="saveConfig" unelevated label="Save" color="primary" class="q-mt-md full-width" />
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
||||
<script>
|
||||
|
||||
|
||||
new Vue({
|
||||
el: '#vue',
|
||||
mixins: [windowMixin],
|
||||
delimiters: ['${', '}'],
|
||||
data: function () {
|
||||
return {
|
||||
entries: [],
|
||||
columns: [
|
||||
{ name: 'key', required: true, label: 'Key', align: 'left', field: row => row.key, sortable: true },
|
||||
{ name: 'value', required: true, label: 'Value', align: 'left', field: row => row.value, sortable: true },
|
||||
]
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
fetchConfig() {
|
||||
this.entries = [];
|
||||
LNbits.api
|
||||
.request('GET', '/nwcprovider/api/v1/config', this.g.user.wallets[0].adminkey)
|
||||
.then((response) => {
|
||||
const newEntries = [];
|
||||
for (const [key, value] of Object.entries(response.data)) {
|
||||
newEntries.push({
|
||||
key: key,
|
||||
value: value,
|
||||
});
|
||||
}
|
||||
|
||||
this.entries = newEntries;
|
||||
|
||||
})
|
||||
.catch(function (error) {
|
||||
console.error("Error fetching config:", error);
|
||||
});
|
||||
},
|
||||
async saveConfig() {
|
||||
const data = {};
|
||||
for (const entry of this.entries) {
|
||||
data[entry.key] = entry.value;
|
||||
}
|
||||
try {
|
||||
const response = await LNbits.api
|
||||
.request('POST', '/nwcprovider/api/v1/config', this.g.user.wallets[0].adminkey, data)
|
||||
|
||||
|
||||
this.$q.notify({
|
||||
message: 'Config saved, please restart the server',
|
||||
color: 'positive',
|
||||
position: 'top',
|
||||
timeout: 2000,
|
||||
actions: [{ icon: 'close', color: 'white' }]
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Error saving config:", error);
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
created: function () {
|
||||
this.fetchConfig()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
659
templates/nwcprovider/index.html
Normal file
659
templates/nwcprovider/index.html
Normal file
|
|
@ -0,0 +1,659 @@
|
|||
<!--/////////////////////////////////////////////////-->
|
||||
<!--//PAGE FOR THE EXTENSIONS BACKEND IN LNBITS//////-->
|
||||
<!--/////////////////////////////////////////////////-->
|
||||
|
||||
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
||||
%} {% block page %}
|
||||
<div class="row q-col-gutter-md">
|
||||
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<!-- <div class="q-gutter-x-md row items-center"> -->
|
||||
<!-- <div class="col"> -->
|
||||
<q-select v-model="selectedWallet" filled dense emit-value :options="g.user.walletOptions"
|
||||
label="Wallet *"></q-select>
|
||||
<!-- </div>
|
||||
<div class="col">
|
||||
<q-btn unelevated color="primary" icon="add" @click="formDialog.show = true">Connect app</q-btn>
|
||||
</div> -->
|
||||
<!-- </div> -->
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<div class="row items-center no-wrap q-mb-md">
|
||||
<div class="col items-center">
|
||||
<h5 class="text-subtitle1">Connected Apps</h5>
|
||||
</div>
|
||||
<q-btn @click="showConnectDialog()" aria-label="Connect" icon="add" color="primary" size="sm" round />
|
||||
|
||||
|
||||
</div>
|
||||
<q-table dense flat :data="nwcEntries" row-key="id" :columns="nwcsTable.columns"
|
||||
:pagination.sync="nwcsTable.pagination">
|
||||
<nwcprovider v-slot:header="props">
|
||||
<q-tr :props="props">
|
||||
<q-th v-for="col in props.cols" :key="col.name" :props="props">
|
||||
${ col.label }
|
||||
</q-th>
|
||||
|
||||
</q-tr>
|
||||
</nwcprovider>
|
||||
|
||||
<template v-slot:body="props">
|
||||
<q-tr :props="props">
|
||||
<q-td v-for="col in props.cols" :key="col.name" :props="props">
|
||||
<div>${ col.value }</div>
|
||||
</q-td>
|
||||
|
||||
|
||||
|
||||
<q-td>
|
||||
<q-btn flat dense size="xs" @click="openConnectionInfoDialog(props.row)" icon="info" color="primary">
|
||||
<q-tooltip> Info </q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn flat dense size="xs" @click="deleteNWC(props.row.pubkey)" icon="cancel" color="pink">
|
||||
<q-tooltip> Delete </q-tooltip>
|
||||
</q-btn>
|
||||
</q-td>
|
||||
|
||||
</q-tr>
|
||||
</template>
|
||||
|
||||
</q-table>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-4 col-lg-5 q-gutter-y-md">
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<h6 class="text-subtitle1 q-my-none">{{SITE_TITLE}} NWC Service provider extension</h6>
|
||||
<p>
|
||||
An extension that turns LNBits into a Nostr Wallet Connect service provider.
|
||||
</p>
|
||||
</q-card-section>
|
||||
<q-card-section class="q-pa-none">
|
||||
<q-separator></q-separator>
|
||||
<q-list>
|
||||
|
||||
<q-item clickable @click="go('../docs#/NWC%20Service%20Provider')"
|
||||
class="q-flex items-center justify-between">
|
||||
<q-icon name="swap_vertical_circle" class="q-mr-md"></q-icon>
|
||||
<q-item-section>Swagger API</q-item-section>
|
||||
</q-item>
|
||||
<q-separator></q-separator>
|
||||
<q-item v-if="this.g.user.admin" clickable @click="go('/nwcprovider/admin')"
|
||||
class="q-flex items-center justify-between">
|
||||
<q-icon name="settings" class="q-mr-md"></q-icon>
|
||||
<q-item-section>Settings</q-item-section>
|
||||
</q-item>
|
||||
<!-- <q-expansion-item v-if="this.g.user.admin" group="extras" icon="admin" label="Admin">
|
||||
<q-card>
|
||||
<q-card-section>
|
||||
<p>
|
||||
Manage wallets using NWC
|
||||
</p>
|
||||
<small>Created by
|
||||
<a class="text-secondary" href="https://github.com/riccardobl" target="_blank">Riccardo Balbo</a>.</small>
|
||||
<small>Repo
|
||||
<a class="text-secondary" href="https://github.com/riccardobl/nwcprovider" target="_blank">NWC Service
|
||||
Providers</a>.</small>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</q-expansion-item> -->
|
||||
</q-list>
|
||||
</q-card-section>
|
||||
</q-card>
|
||||
</div>
|
||||
|
||||
<!--/////////////////////////////////////////////////-->
|
||||
<!--//////////////FORM DIALOG////////////////////////-->
|
||||
<!--/////////////////////////////////////////////////-->
|
||||
|
||||
|
||||
<q-dialog v-model="connectionInfoDialog.show" @hide="closeConnectionInfoDialog">
|
||||
<q-card class="q-pa-lg q-pt-xl" style="width: 500px">
|
||||
<q-card-section class="q-ma-none">
|
||||
<h3 class="q-ma-none q-mb-md text-center">Info</h3>
|
||||
<q-list>
|
||||
|
||||
<q-item>
|
||||
<q-item-section>Description</q-item-section>
|
||||
<q-item-section side>${connectionInfoDialog.data.description}</q-item-section>
|
||||
</q-item>
|
||||
<q-item>
|
||||
<q-item-section>Last used</q-item-section>
|
||||
<q-item-section side>${connectionInfoDialog.data.last_used}</q-item-section>
|
||||
</q-item>
|
||||
<q-item>
|
||||
<q-item-section>Expires</q-item-section>
|
||||
<q-item-section side>${connectionInfoDialog.data.expires_at}</q-item-section>
|
||||
</q-item>
|
||||
<q-item>
|
||||
<q-item-section>Created</q-item-section>
|
||||
<q-item-section side>${connectionInfoDialog.data.created_at}</q-item-section>
|
||||
</q-item>
|
||||
|
||||
<q-item>
|
||||
<q-item-section>Permissions</q-item-section>
|
||||
<q-item-section side>
|
||||
|
||||
${connectionInfoDialog.data.permissions}
|
||||
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
<q-item>
|
||||
<q-item-section>Limits
|
||||
<q-markup-table class="q-mt-md">
|
||||
<thead>
|
||||
<q-tr>
|
||||
<q-th>
|
||||
Budget (sats)
|
||||
<div style="font-size: smaller;">used/max</div>
|
||||
|
||||
|
||||
</q-th>
|
||||
<q-th>Refresh</q-th>
|
||||
</q-tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<q-tr v-if="!connectionInfoDialog.data.budgets || connectionInfoDialog.data.budgets.length === 0">
|
||||
<q-td colspan="2" class="text-center">No limits</q-td>
|
||||
</q-tr>
|
||||
<q-tr v-else v-for="budget in connectionInfoDialog.data.budgets">
|
||||
<q-td class="text-center">${budget.used_budget_sats} / ${budget.budget_sats}</q-td>
|
||||
<q-td class="text-center">${budget.refresh_window}</q-td>
|
||||
</q-tr>
|
||||
</tbody>
|
||||
</q-markup-table>
|
||||
|
||||
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-list>
|
||||
|
||||
<q-card-actions class="q-mt-md">
|
||||
|
||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto full-width " @click="closePairingDialog">Close</q-btn>
|
||||
</q-card-actions>
|
||||
|
||||
</q-card>
|
||||
|
||||
</q-dialog>
|
||||
|
||||
|
||||
<q-dialog v-model="pairingQrDialog.show" @hide="closePairingQrDialog">
|
||||
<q-card class="q-pa-lg q-pt-xl" style="width: 500px">
|
||||
|
||||
<q-card-section class="q-ma-none">
|
||||
<q-responsive :ratio="1" class="q-mx-xl q-mb-md">
|
||||
<lnbits-qrcode :value="pairingQrDialog.data.pairingUrl"></lnbits-qrcode>
|
||||
</q-responsive>
|
||||
</q-card-section>
|
||||
<q-card-section class="q-ma-none">
|
||||
<p class=" q-ma-none text-center">Please scan this QR code with a supported app</p>
|
||||
<p class=" q-ma-none text-center text-caption ">Connect only with app you trust!</p>
|
||||
|
||||
</q-card-section>
|
||||
|
||||
<q-card-actions>
|
||||
<q-btn label="Close" flat color="grey" class="full-width
|
||||
" @click="closePairingQrDialog"></q-btn>
|
||||
</q-card-actions>
|
||||
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
|
||||
<q-dialog v-model="pairingDialog.show" @hide="closePairingDialog">
|
||||
|
||||
<q-card class="q-pa-lg q-pt-xl" style="width: 500px">
|
||||
|
||||
<q-card-section class="q-ma-none">
|
||||
<h3 class="q-ma-none q-mb-md text-center">Pairing</h3>
|
||||
<p class=" q-ma-none text-center">Complete the last step of the setup by pasting or scanning your connection's
|
||||
pairing secret in the desired
|
||||
app to
|
||||
finalise the connection.</p>
|
||||
<p class=" q-ma-none text-center text-caption ">Connect only with app you trust!</p>
|
||||
|
||||
</q-card-section>
|
||||
<q-card-actions>
|
||||
<q-btn label="Open in Supported App" color="primary" class="full-width" icon="launch"
|
||||
@click="openPairingUrl()"></q-btn>
|
||||
</q-card-actions>
|
||||
<q-card-section class="q-ma-none">
|
||||
<h5 class="q-ma-none text-center">Advanced</h5>
|
||||
|
||||
</q-card-section>
|
||||
|
||||
<q-card-actions>
|
||||
<q-btn label="Copy Pairing URL" flat color="grey" class="full-width" icon="content_copy"
|
||||
@click="copyPairingUrl()"></q-btn>
|
||||
</q-card-actions>
|
||||
<q-card-actions>
|
||||
|
||||
<q-btn label="Show QR Code" flat color="grey" class="full-width" icon="qr_code_scanner"
|
||||
@click="showPairingQR()"></q-btn>
|
||||
</q-card-actions>
|
||||
|
||||
<q-card-actions>
|
||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto full-width" @click="closePairingDialog">Close</q-btn>
|
||||
</q-card-actions>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
|
||||
<q-dialog v-model="connectDialog.show" @hide="closeConnectDialog">
|
||||
<q-card class="q-pa-lg q-pt-xl" style="width: 500px">
|
||||
<h3 class="q-ma-none q-mb-xl text-center">Add connection</h3>
|
||||
|
||||
<q-form @submit="confirmConnectDialog" class="q-gutter-md">
|
||||
<q-input v-model="connectDialog.data.description" filled dense label="Description"></q-input>
|
||||
<div class="row">
|
||||
<q-input :disable="connectDialog.data.neverExpires" v-model="connectDialog.data.expires_at" class="col"
|
||||
v-model="connectDialog.data.expires_at" filled dense label="Expires at" type="datetime-local"></q-input>
|
||||
<q-checkbox v-model="connectDialog.data.neverExpires" class="col-auto"
|
||||
v-model="connectDialog.data.neverExpires" label="Never"></q-checkbox>
|
||||
</div>
|
||||
<q-list dense bordered padding>
|
||||
<q-item>
|
||||
<q-item-label header>Authorize the app to</q-item-label>
|
||||
</q-item>
|
||||
<q-item v-for="permission in connectDialog.data.permissions">
|
||||
<q-checkbox v-model="permission.value" :label="permission.name"></q-checkbox>
|
||||
</q-item>
|
||||
</q-list>
|
||||
<q-list dense bordered padding>
|
||||
<q-item>
|
||||
<q-item-label header>Limit the spendable amount</q-item-label>
|
||||
</q-item>
|
||||
<q-item v-if="!connectDialog.data.budgets || connectDialog.data.budgets.length === 0">
|
||||
<q-item-section class="full-width text-center">No limit</q-item-section>
|
||||
</q-item>
|
||||
<template v-else>
|
||||
|
||||
<q-item v-for="(budget,index) in connectDialog.data.budgets">
|
||||
<q-item-section>
|
||||
<q-input filled dense type="number" v-model="budget.budget_sats" label="Limit (sats)"></q-input>
|
||||
</q-item-section>
|
||||
<q-item-section>
|
||||
<q-select filled dense emit-value :options="[
|
||||
{label: 'Daily', value: 'Daily'},
|
||||
{label: 'Weekly', value: 'Weekly'},
|
||||
{label: 'Monthly', value: 'Monthly'},
|
||||
{label: 'Yearly', value: 'Yearly'},
|
||||
{label: 'Never', value: 'Never'}
|
||||
]" label="Expires" v-model="budget.expiry"></q-select>
|
||||
</q-item-section>
|
||||
<q-item-section side>
|
||||
<q-btn flat icon="delete" @click="deleteBudget(index)"></q-btn>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</template>
|
||||
<q-item>
|
||||
<q-btn flat icon="add" class="full-width" label="Add limit" @click="addBudget"></q-btn>
|
||||
</q-item>
|
||||
|
||||
</q-list>
|
||||
|
||||
<q-card-actions class="q-gutter-sm">
|
||||
<div class="col q-mr-md">
|
||||
<q-btn unelevated color="primary" type="submit" class="full-width">Connect</q-btn>
|
||||
</div>
|
||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
|
||||
</q-card-actions>
|
||||
</q-form>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</div>
|
||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
||||
<script type="module">
|
||||
import * as NobleSecp256k1 from '/nwcprovider/static/js/noble-secp256k1.min.js';
|
||||
window.NobleSecp256k1 = NobleSecp256k1;
|
||||
</script>
|
||||
<script>
|
||||
|
||||
|
||||
new Vue({
|
||||
el: '#vue',
|
||||
mixins: [windowMixin],
|
||||
delimiters: ['${', '}'],
|
||||
data: function () {
|
||||
return {
|
||||
selectedWallet: null,
|
||||
nodePermissions: [],
|
||||
nwcEntries: [],
|
||||
nwcsTable: {
|
||||
columns: [
|
||||
{ name: 'description', align: 'left', label: 'Description', field: 'description' },
|
||||
{ name: 'status', align: 'left', label: 'Status', field: 'status' },
|
||||
{ name: 'last_used', align: 'left', label: 'Last used', field: 'last_used' },
|
||||
{ name: 'created_at', align: 'left', label: 'Created', field: 'created_at' },
|
||||
{ name: 'expires_at', align: 'left', label: 'Expires', field: 'expires_at' }
|
||||
],
|
||||
pagination: {
|
||||
rowsPerPage: 10
|
||||
}
|
||||
},
|
||||
connectDialog: {
|
||||
show: false,
|
||||
data: {},
|
||||
},
|
||||
pairingDialog: {
|
||||
show: false,
|
||||
data: {
|
||||
pairingUrl: ''
|
||||
}
|
||||
},
|
||||
pairingQrDialog: {
|
||||
show: false,
|
||||
data: {
|
||||
pairingUrl: ''
|
||||
}
|
||||
},
|
||||
connectionInfoDialog: {
|
||||
show: false,
|
||||
data: {}
|
||||
},
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
showConnectDialog() {
|
||||
const wallet = this.getWallet();
|
||||
if (!wallet) {
|
||||
this.$q.notify({ color: 'red', message: 'Please select a wallet first' });
|
||||
return;
|
||||
} else {
|
||||
this.connectDialog.show = true;
|
||||
}
|
||||
},
|
||||
openConnectionInfoDialog(data) {
|
||||
this.connectionInfoDialog.data = data;
|
||||
this.connectionInfoDialog.show = true;
|
||||
},
|
||||
closeConnectionInfoDialog() {
|
||||
this.connectionInfoDialog.show = false;
|
||||
},
|
||||
openPairingUrl() {
|
||||
const url = this.pairingDialog.data.pairingUrl;
|
||||
if (url) window.open(url, '_blank');
|
||||
},
|
||||
go(url) {
|
||||
window.open(url, '_blank');
|
||||
},
|
||||
async copyPairingUrl() {
|
||||
const url = this.pairingDialog.data.pairingUrl;
|
||||
if (url) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(url);
|
||||
this.$q.notify({ color: 'primary', message: 'URL copied to clipboard!' });
|
||||
} catch (err) {
|
||||
this.$q.notify({ color: 'red', message: 'Failed to copy URL.' });
|
||||
}
|
||||
}
|
||||
},
|
||||
showPairingQR() {
|
||||
this.pairingQrDialog.data.pairingUrl = this.pairingDialog.data.pairingUrl;
|
||||
this.pairingQrDialog.show = true;
|
||||
},
|
||||
closePairingQrDialog() {
|
||||
this.pairingQrDialog.show = false;
|
||||
},
|
||||
loadConnectDialogData() {
|
||||
this.connectDialog.data = {
|
||||
description: '',
|
||||
expires_at: Date.now() + 1000 * 60 * 60 * 24 * 7,
|
||||
neverExpires: true,
|
||||
permissions: [],
|
||||
budgets: []
|
||||
};
|
||||
for (const permission of this.nodePermissions) {
|
||||
this.connectDialog.data.permissions.push({
|
||||
key: permission.key,
|
||||
name: permission.name,
|
||||
value: permission.value
|
||||
});
|
||||
}
|
||||
},
|
||||
deleteBudget(index) {
|
||||
this.connectDialog.data.budgets.splice(index, 1)
|
||||
},
|
||||
addBudget() {
|
||||
this.connectDialog.data.budgets.push({
|
||||
budget_sats: 1000,
|
||||
used_budget_sats: 0,
|
||||
created_at: new Date(new Date().setHours(0, 0, 0, 0)).getTime() / 1000,
|
||||
expiration: 'never'
|
||||
})
|
||||
},
|
||||
closeConnectDialog() {
|
||||
this.connectDialog.show = false
|
||||
this.loadConnectDialogData();
|
||||
},
|
||||
getWallet: function () {
|
||||
let wallet = undefined;
|
||||
for (let i = 0; i < this.g.user.wallets.length; i++) {
|
||||
if (this.g.user.wallets[i].id == this.selectedWallet) {
|
||||
wallet = this.g.user.wallets[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
return wallet;
|
||||
},
|
||||
async generateKeyPair() {
|
||||
while (!window.NobleSecp256k1) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1));
|
||||
}
|
||||
const privKeyBytes = window.NobleSecp256k1.utils.randomPrivateKey();
|
||||
const pubKeyBytes = window.NobleSecp256k1.getPublicKey(privKeyBytes);
|
||||
const out = {
|
||||
privKeyBytes: privKeyBytes,
|
||||
pubKeyBytes: pubKeyBytes,
|
||||
privKey: window.NobleSecp256k1.etc.bytesToHex(privKeyBytes),
|
||||
pubKey: window.NobleSecp256k1.etc.bytesToHex(pubKeyBytes.slice(1))
|
||||
|
||||
};
|
||||
return out;
|
||||
},
|
||||
deleteNWC: async function (pubkey) {
|
||||
this.$q.dialog({
|
||||
title: 'Confirm Deletion',
|
||||
message: 'Are you sure you want to delete this connection?',
|
||||
cancel: true,
|
||||
persistent: true
|
||||
}).onOk(async () => {
|
||||
try {
|
||||
const wallet = this.getWallet();
|
||||
const response = await LNbits.api.request('DELETE', `/nwcprovider/api/v1/nwc/${pubkey}`, wallet.adminkey);
|
||||
this.loadNwcs();
|
||||
this.$q.notify({ type: 'positive', message: 'Deleted successfully' });
|
||||
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error);
|
||||
}
|
||||
}).onCancel(() => {
|
||||
// User canceled the operation
|
||||
});
|
||||
},
|
||||
loadNwcs: async function () {
|
||||
|
||||
const wallet = this.getWallet();
|
||||
if (!wallet) {
|
||||
this.nwcs = [];
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await LNbits.api
|
||||
.request(
|
||||
'GET',
|
||||
'/nwcprovider/api/v1/nwc?includeExpired=true&calculateSpendBudget=true',
|
||||
wallet.adminkey
|
||||
)
|
||||
this.nwcs = response.data;
|
||||
|
||||
} catch (error) {
|
||||
this.nwcs = [];
|
||||
}
|
||||
try {
|
||||
const response = await LNbits.api.request(
|
||||
'GET',
|
||||
'/nwcprovider/api/v1/permissions',
|
||||
wallet.adminkey
|
||||
)
|
||||
const permissions = [];
|
||||
for (const [key, value] of Object.entries(response.data)) {
|
||||
permissions.push({
|
||||
key: key,
|
||||
name: value.name,
|
||||
value: value.default
|
||||
});
|
||||
}
|
||||
this.nodePermissions = permissions;
|
||||
} catch (error) {
|
||||
Lnbits.utils.notifyApiError(error);
|
||||
}
|
||||
this.loadConnectDialogData();
|
||||
const newTableEntries = [];
|
||||
for (const nwc of this.nwcs) {
|
||||
const t = Quasar.utils.date.formatDate(
|
||||
new Date(nwc.data.created_at * 1000),
|
||||
'YYYY-MM-DD HH:mm'
|
||||
);
|
||||
const e = nwc.data.expires_at > 0 ? Quasar.utils.date.formatDate(
|
||||
new Date(nwc.data.expires_at * 1000),
|
||||
'YYYY-MM-DD HH:mm'
|
||||
) : "Never";
|
||||
const l = Quasar.utils.date.formatDate(
|
||||
new Date(nwc.data.last_used * 1000),
|
||||
'YYYY-MM-DD HH:mm'
|
||||
);
|
||||
const nwcTableEntry = {
|
||||
description: nwc.data.description,
|
||||
created_at: t,
|
||||
expires_at: e,
|
||||
last_used: l,
|
||||
pubkey: nwc.data.pubkey,
|
||||
permissions: nwc.data.permissions,
|
||||
budgets: [],
|
||||
status: "Active"
|
||||
}
|
||||
if(nwc.data.expires_at > 0 && nwc.data.expires_at < new Date().getTime() / 1000){
|
||||
nwcTableEntry.status = "Expired";
|
||||
}
|
||||
for (const budget of nwc.budgets) {
|
||||
const createdAt = Quasar.utils.date.formatDate(
|
||||
new Date(budget.created_at * 1000),
|
||||
'YYYY-MM-DD HH:mm'
|
||||
);
|
||||
let refreshWindow = budget.refresh_window;
|
||||
if (refreshWindow <= 0) {
|
||||
refreshWindow = 'Never';
|
||||
} else if (refreshWindow == 60 * 60 * 24) {
|
||||
refreshWindow = 'Daily';
|
||||
} else if (refreshWindow == 60 * 60 * 24 * 7) {
|
||||
refreshWindow = 'Weekly';
|
||||
} else if (refreshWindow == 60 * 60 * 24 * 30) {
|
||||
refreshWindow = 'Monthly';
|
||||
} else if (refreshWindow == 60 * 60 * 24 * 365) {
|
||||
refreshWindow = 'Yearly';
|
||||
}
|
||||
nwcTableEntry.budgets.push({
|
||||
budget_sats: budget.budget_msats / 1000,
|
||||
used_budget_sats: budget.used_budget_msats / 1000,
|
||||
created_at: createdAt,
|
||||
refresh_window: refreshWindow
|
||||
});
|
||||
}
|
||||
newTableEntries.push(nwcTableEntry);
|
||||
}
|
||||
this.nwcEntries = newTableEntries;
|
||||
|
||||
},
|
||||
closePairingDialog() {
|
||||
this.pairingDialog.show = false;
|
||||
},
|
||||
async showPairingDialog(secret) {
|
||||
let response = await LNbits.api.request(
|
||||
'GET',
|
||||
'/nwcprovider/api/v1/pairing/{SECRET}'
|
||||
);
|
||||
response = response.data;
|
||||
response = response.replace("{SECRET}", secret);
|
||||
this.pairingDialog.data.pairingUrl = response;
|
||||
this.pairingDialog.show = true;
|
||||
},
|
||||
async confirmConnectDialog() {
|
||||
const keyPair = await this.generateKeyPair();
|
||||
// timestamp
|
||||
let expires_at = 0;
|
||||
if (!this.connectDialog.data.neverExpires) {
|
||||
expires_at = new Date(this.connectDialog.data.expires_at).getTime() / 1000;
|
||||
}
|
||||
const data = {
|
||||
permissions: [],
|
||||
description: this.connectDialog.data.description,
|
||||
expires_at: expires_at,
|
||||
budgets: []
|
||||
};
|
||||
for (const permission of this.connectDialog.data.permissions) {
|
||||
if (permission.value) data.permissions.push(permission.key);
|
||||
}
|
||||
for (const budget of this.connectDialog.data.budgets) {
|
||||
const budget_msats = budget.budget_sats * 1000;
|
||||
let refresh_window = 0
|
||||
switch (budget.expiry) {
|
||||
case 'Daily':
|
||||
refresh_window = 60 * 60 * 24;
|
||||
break;
|
||||
case 'Weekly':
|
||||
refresh_window = 60 * 60 * 24 * 7;
|
||||
break;
|
||||
case 'Monthly':
|
||||
refresh_window = 60 * 60 * 24 * 30;
|
||||
break;
|
||||
case 'Yearly':
|
||||
refresh_window = 60 * 60 * 24 * 365;
|
||||
break;
|
||||
case 'Never':
|
||||
refresh_window = 0;
|
||||
break;
|
||||
}
|
||||
data.budgets.push({
|
||||
budget_msats: budget_msats,
|
||||
refresh_window: refresh_window,
|
||||
created_at: new Date(new Date().setHours(0, 0, 0, 0)).getTime() / 1000,
|
||||
});
|
||||
}
|
||||
const wallet = this.getWallet();
|
||||
|
||||
try {
|
||||
const response = await LNbits.api
|
||||
.request('PUT', '/nwcprovider/api/v1/nwc/' + keyPair.pubKey, wallet.adminkey, data);
|
||||
this.closeConnectDialog()
|
||||
if (!response.data || !response.data.data || !response.data.data.pubkey) {
|
||||
LNbits.utils.notifyApiError("Error creating nwc pairing");
|
||||
return;
|
||||
|
||||
}
|
||||
this.showPairingDialog(keyPair.privKey);
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
this.loadNwcs();
|
||||
}
|
||||
},
|
||||
|
||||
created: function () {
|
||||
this.loadNwcs()
|
||||
},
|
||||
watch: {
|
||||
selectedWallet(newValue, oldValue) {
|
||||
this.loadNwcs();
|
||||
},
|
||||
},
|
||||
})
|
||||
</script>
|
||||
{% endblock %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue