fix json bug, move js to own file
This commit is contained in:
parent
dd0f4cad7f
commit
be3d4b9e5c
4 changed files with 397 additions and 404 deletions
|
|
@ -32,7 +32,6 @@ def nwcprovider_stop():
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
logger.warning(ex)
|
logger.warning(ex)
|
||||||
|
|
||||||
logger.debug("NWC Service Provider extension loaded")
|
|
||||||
def nwcprovider_start():
|
def nwcprovider_start():
|
||||||
task = create_permanent_unique_task("ext_nwcprovider", handle_nwc)
|
task = create_permanent_unique_task("ext_nwcprovider", handle_nwc)
|
||||||
scheduled_tasks.append(task)
|
scheduled_tasks.append(task)
|
||||||
|
|
|
||||||
386
static/js/index.js
Normal file
386
static/js/index.js
Normal file
|
|
@ -0,0 +1,386 @@
|
||||||
|
window.app = Vue.createApp({
|
||||||
|
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?include_expired=true&calculate_spent_budget=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();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
<!--/////////////////////////////////////////////////-->
|
|
||||||
<!--//PAGE FOR THE EXTENSIONS BACKEND IN LNBITS//////-->
|
|
||||||
<!--/////////////////////////////////////////////////-->
|
|
||||||
|
|
||||||
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
||||||
%} {% block page %}
|
%} {% block scripts %} {{ window_vars(user) }}
|
||||||
|
<script type="module">
|
||||||
|
import * as NobleSecp256k1 from "/nwcprovider/static/js/noble-secp256k1.min.js";
|
||||||
|
window.NobleSecp256k1 = NobleSecp256k1;
|
||||||
|
</script>
|
||||||
|
<script src="{{ static_url_for('nwcprovider/static', path='js/index.js') }}"></script>
|
||||||
|
{% endblock %} {% block page %}
|
||||||
<div class="row q-col-gutter-md">
|
<div class="row q-col-gutter-md">
|
||||||
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
|
<div class="col-12 col-md-8 col-lg-7 q-gutter-y-md">
|
||||||
<q-card>
|
<q-card>
|
||||||
|
|
@ -402,397 +404,4 @@
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
</div>
|
</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>
|
|
||||||
window.app = Vue.createApp({
|
|
||||||
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?include_expired=true&calculate_spent_budget=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 %}
|
{% endblock %}
|
||||||
|
|
|
||||||
9
views.py
9
views.py
|
|
@ -4,23 +4,22 @@ from lnbits.decorators import check_admin, check_user_exists
|
||||||
from lnbits.helpers import template_renderer
|
from lnbits.helpers import template_renderer
|
||||||
from starlette.responses import HTMLResponse
|
from starlette.responses import HTMLResponse
|
||||||
|
|
||||||
|
nwcprovider_router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
def nwcprovider_renderer():
|
def nwcprovider_renderer():
|
||||||
return template_renderer(["nwcprovider/templates"])
|
return template_renderer(["nwcprovider/templates"])
|
||||||
|
|
||||||
|
|
||||||
nwcprovider_router = APIRouter()
|
|
||||||
|
|
||||||
|
|
||||||
@nwcprovider_router.get("/", response_class=HTMLResponse)
|
@nwcprovider_router.get("/", response_class=HTMLResponse)
|
||||||
async def index(request: Request, user: User = Depends(check_user_exists)):
|
async def index(request: Request, user: User = Depends(check_user_exists)):
|
||||||
return nwcprovider_renderer().TemplateResponse(
|
return nwcprovider_renderer().TemplateResponse(
|
||||||
"nwcprovider/index.html", {"request": request, "user": user.dict()}
|
"nwcprovider/index.html", {"request": request, "user": user.json()}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@nwcprovider_router.get("/admin", response_class=HTMLResponse)
|
@nwcprovider_router.get("/admin", response_class=HTMLResponse)
|
||||||
async def admin(request: Request, user: User = Depends(check_admin)):
|
async def admin(request: Request, user: User = Depends(check_admin)):
|
||||||
return nwcprovider_renderer().TemplateResponse(
|
return nwcprovider_renderer().TemplateResponse(
|
||||||
"nwcprovider/admin.html", {"request": request, "user": user.dict()}
|
"nwcprovider/admin.html", {"request": request, "user": user.json()}
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue