Improve configuration form for readability and UX

This commit is contained in:
blackcoffeexbt 2025-09-10 12:00:45 +01:00
commit abdd7a781b
12 changed files with 552 additions and 512 deletions

View file

@ -1,7 +1,7 @@
window.app = Vue.createApp({
el: "#vue",
el: '#vue',
mixins: [windowMixin],
delimiters: ["${", "}"],
delimiters: ['${', '}'],
data: function () {
return {
selectedWallet: null,
@ -10,244 +10,244 @@ window.app = Vue.createApp({
nwcsTable: {
columns: [
{
name: "description",
align: "left",
label: "Description",
field: "description",
name: 'description',
align: 'left',
label: 'Description',
field: 'description'
},
{ name: "status", align: "left", label: "Status", field: "status" },
{name: 'status', align: 'left', label: 'Status', field: 'status'},
{
name: "last_used",
align: "left",
label: "Last used",
field: "last_used",
name: 'last_used',
align: 'left',
label: 'Last used',
field: 'last_used'
},
{
name: "created_at",
align: "left",
label: "Created",
field: "created_at",
name: 'created_at',
align: 'left',
label: 'Created',
field: 'created_at'
},
{
name: "expires_at",
align: "left",
label: "Expires",
field: "expires_at",
},
name: 'expires_at',
align: 'left',
label: 'Expires',
field: 'expires_at'
}
],
pagination: {
rowsPerPage: 10,
},
rowsPerPage: 10
}
},
connectDialog: {
show: false,
data: {},
data: {}
},
pairingDialog: {
show: false,
data: {
pairingUrl: "",
},
pairingUrl: ''
}
},
pairingQrDialog: {
show: false,
data: {
pairingUrl: "",
},
pairingUrl: ''
}
},
connectionInfoDialog: {
show: false,
data: {},
},
};
data: {}
}
}
},
methods: {
showConnectDialog() {
const wallet = this.getWallet();
const wallet = this.getWallet()
if (!wallet) {
Quasar.Notify.create({
type: "negative",
message: "Please select a wallet first",
});
return;
type: 'negative',
message: 'Please select a wallet first'
})
return
} else {
this.connectDialog.show = true;
this.connectDialog.show = true
}
},
openConnectionInfoDialog(data) {
this.connectionInfoDialog.data = data;
this.connectionInfoDialog.show = true;
this.connectionInfoDialog.data = data
this.connectionInfoDialog.show = true
},
closeConnectionInfoDialog() {
this.connectionInfoDialog.show = false;
this.connectionInfoDialog.show = false
},
openPairingUrl() {
const url = this.pairingDialog.data.pairingUrl;
if (url) window.open(url, "_blank");
const url = this.pairingDialog.data.pairingUrl
if (url) window.open(url, '_blank')
},
go(url) {
window.open(url, "_blank");
window.open(url, '_blank')
},
async copyPairingUrl() {
const url = this.pairingDialog.data.pairingUrl;
const url = this.pairingDialog.data.pairingUrl
if (url) {
try {
await navigator.clipboard.writeText(url);
await navigator.clipboard.writeText(url)
Quasar.Notify.create({
type: "positive",
message: "URL copied to clipboard",
});
type: 'positive',
message: 'URL copied to clipboard'
})
} catch (err) {
Quasar.Notify.create({
type: "negative",
message: "Failed to copy URL.",
});
type: 'negative',
message: 'Failed to copy URL.'
})
}
}
},
showPairingQR() {
this.pairingQrDialog.data.pairingUrl = this.pairingDialog.data.pairingUrl;
this.pairingQrDialog.show = true;
this.pairingQrDialog.data.pairingUrl = this.pairingDialog.data.pairingUrl
this.pairingQrDialog.show = true
},
closePairingQrDialog() {
this.pairingQrDialog.show = false;
this.pairingQrDialog.show = false
},
loadConnectDialogData() {
this.connectDialog.data = {
description: "",
description: '',
expires_at: Date.now() + 1000 * 60 * 60 * 24 * 7,
neverExpires: true,
permissions: [],
budgets: [],
};
budgets: []
}
for (const permission of this.nodePermissions) {
this.connectDialog.data.permissions.push({
key: permission.key,
name: permission.name,
value: permission.value,
});
value: permission.value
})
}
},
deleteBudget(index) {
this.connectDialog.data.budgets.splice(index, 1);
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",
});
expiration: 'never'
})
},
closeConnectDialog() {
this.connectDialog.show = false;
this.loadConnectDialogData();
this.connectDialog.show = false
this.loadConnectDialogData()
},
getWallet: function () {
let wallet = undefined;
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;
wallet = this.g.user.wallets[i]
break
}
}
return wallet;
return wallet
},
async generateKeyPair() {
while (!window.NobleSecp256k1) {
await new Promise((resolve) => setTimeout(resolve, 1));
await new Promise(resolve => setTimeout(resolve, 1))
}
const privKeyBytes = window.NobleSecp256k1.utils.randomPrivateKey();
const pubKeyBytes = window.NobleSecp256k1.getPublicKey(privKeyBytes);
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;
pubKey: window.NobleSecp256k1.etc.bytesToHex(pubKeyBytes.slice(1))
}
return out
},
deleteNWC: async function (pubkey) {
Quasar.Dialog.create({
title: "Confirm Deletion",
message: "Are you sure you want to delete this connection?",
title: 'Confirm Deletion',
message: 'Are you sure you want to delete this connection?',
cancel: true,
persistent: true,
persistent: true
})
.onOk(async () => {
try {
const wallet = this.getWallet();
const wallet = this.getWallet()
const response = await LNbits.api.request(
"DELETE",
'DELETE',
`/nwcprovider/api/v1/nwc/${pubkey}`,
wallet.adminkey,
);
this.loadNwcs();
wallet.adminkey
)
this.loadNwcs()
Quasar.Notify.create({
type: "positive",
message: "Deleted successfully",
});
type: 'positive',
message: 'Deleted successfully'
})
} catch (error) {
LNbits.utils.notifyApiError(error);
LNbits.utils.notifyApiError(error)
}
})
.onCancel(() => {
// User canceled the operation
});
})
},
loadNwcs: async function () {
const wallet = this.getWallet();
const wallet = this.getWallet()
if (!wallet) {
this.nwcs = [];
return;
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;
'GET',
'/nwcprovider/api/v1/nwc?include_expired=true&calculate_spent_budget=true',
wallet.adminkey
)
this.nwcs = response.data
} catch (error) {
this.nwcs = [];
this.nwcs = []
}
try {
const response = await LNbits.api.request(
"GET",
"/nwcprovider/api/v1/permissions",
wallet.adminkey,
);
const permissions = [];
'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,
});
value: value.default
})
}
this.nodePermissions = permissions;
this.nodePermissions = permissions
} catch (error) {
Lnbits.utils.notifyApiError(error);
Lnbits.utils.notifyApiError(error)
}
this.loadConnectDialogData();
const newTableEntries = [];
this.loadConnectDialogData()
const newTableEntries = []
for (const nwc of this.nwcs) {
const t = Quasar.date.formatDate(
new Date(nwc.data.created_at * 1000),
"YYYY-MM-DD HH:mm",
);
'YYYY-MM-DD HH:mm'
)
const e =
nwc.data.expires_at > 0
? Quasar.date.formatDate(
new Date(nwc.data.expires_at * 1000),
"YYYY-MM-DD HH:mm",
'YYYY-MM-DD HH:mm'
)
: "Never";
: 'Never'
const l = Quasar.date.formatDate(
new Date(nwc.data.last_used * 1000),
"YYYY-MM-DD HH:mm",
);
'YYYY-MM-DD HH:mm'
)
const nwcTableEntry = {
description: nwc.data.description,
created_at: t,
@ -256,131 +256,130 @@ window.app = Vue.createApp({
pubkey: nwc.data.pubkey,
permissions: nwc.data.permissions,
budgets: [],
status: "Active",
};
status: 'Active'
}
if (
nwc.data.expires_at > 0 &&
nwc.data.expires_at < new Date().getTime() / 1000
) {
nwcTableEntry.status = "Expired";
nwcTableEntry.status = 'Expired'
}
for (const budget of nwc.budgets) {
const createdAt = Quasar.date.formatDate(
new Date(budget.created_at * 1000),
"YYYY-MM-DD HH:mm",
);
let refreshWindow = budget.refresh_window;
'YYYY-MM-DD HH:mm'
)
let refreshWindow = budget.refresh_window
if (refreshWindow <= 0) {
refreshWindow = "Never";
refreshWindow = 'Never'
} else if (refreshWindow == 60 * 60 * 24) {
refreshWindow = "Daily";
refreshWindow = 'Daily'
} else if (refreshWindow == 60 * 60 * 24 * 7) {
refreshWindow = "Weekly";
refreshWindow = 'Weekly'
} else if (refreshWindow == 60 * 60 * 24 * 30) {
refreshWindow = "Monthly";
refreshWindow = 'Monthly'
} else if (refreshWindow == 60 * 60 * 24 * 365) {
refreshWindow = "Yearly";
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,
});
refresh_window: refreshWindow
})
}
newTableEntries.push(nwcTableEntry);
newTableEntries.push(nwcTableEntry)
}
this.nwcEntries = newTableEntries;
this.nwcEntries = newTableEntries
},
closePairingDialog() {
this.pairingDialog.show = false;
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;
'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();
const keyPair = await this.generateKeyPair()
// timestamp
let expires_at = 0;
let expires_at = 0
if (!this.connectDialog.data.neverExpires) {
expires_at =
new Date(this.connectDialog.data.expires_at).getTime() / 1000;
new Date(this.connectDialog.data.expires_at).getTime() / 1000
}
const data = {
permissions: [],
description: this.connectDialog.data.description,
expires_at: expires_at,
budgets: [],
};
budgets: []
}
for (const permission of this.connectDialog.data.permissions) {
if (permission.value) data.permissions.push(permission.key);
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;
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;
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,
});
created_at: new Date(new Date().setHours(0, 0, 0, 0)).getTime() / 1000
})
}
const wallet = this.getWallet();
const wallet = this.getWallet()
try {
const response = await LNbits.api.request(
"PUT",
"/nwcprovider/api/v1/nwc/" + keyPair.pubKey,
'PUT',
'/nwcprovider/api/v1/nwc/' + keyPair.pubKey,
wallet.adminkey,
data,
);
this.closeConnectDialog();
data
)
this.closeConnectDialog()
if (
!response.data ||
!response.data.data ||
!response.data.data.pubkey
) {
LNbits.utils.notifyApiError("Error creating nwc pairing");
return;
LNbits.utils.notifyApiError('Error creating nwc pairing')
return
}
this.showPairingDialog(keyPair.privKey);
this.showPairingDialog(keyPair.privKey)
} catch (error) {
LNbits.utils.notifyApiError(error);
LNbits.utils.notifyApiError(error)
}
this.loadNwcs();
},
this.loadNwcs()
}
},
created: function () {
this.loadNwcs();
this.loadNwcs()
},
watch: {
selectedWallet(newValue, oldValue) {
this.loadNwcs();
},
},
});
this.loadNwcs()
}
}
})