mirror of
https://github.com/lnbits/lnbits-legend.git
synced 2025-02-25 15:10:41 +01:00
* Update admin_api.py Common misconception is that the top up related to the funds on the funding source. Success msg extended with info that correlated funds on funding source are needed and the amount is virtual until fitting. * chore: code format * feat: customise top-up message * refactor: move the `Quasar.Notify` to `components.js` * refactor: use `this.$q.notify` instead of `Quasar.Notify.create` --------- Co-authored-by: Vlad Stan <stan.v.vlad@gmail.com>
745 lines
23 KiB
JavaScript
745 lines
23 KiB
JavaScript
/* global _, Vue, moment, LNbits, EventHub, decryptLnurlPayAES */
|
|
|
|
Vue.component('lnbits-fsat', {
|
|
props: {
|
|
amount: {
|
|
type: Number,
|
|
default: 0
|
|
}
|
|
},
|
|
template: '<span>{{ fsat }}</span>',
|
|
computed: {
|
|
fsat: function () {
|
|
return LNbits.utils.formatSat(this.amount)
|
|
}
|
|
}
|
|
})
|
|
|
|
Vue.component('lnbits-wallet-list', {
|
|
data: function () {
|
|
return {
|
|
user: null,
|
|
activeWallet: null,
|
|
activeBalance: [],
|
|
showForm: false,
|
|
walletName: '',
|
|
LNBITS_DENOMINATION: LNBITS_DENOMINATION
|
|
}
|
|
},
|
|
template: `
|
|
<q-list v-if="user && user.wallets.length" dense class="lnbits-drawer__q-list">
|
|
<q-item-label header v-text="$t('wallets')"></q-item-label>
|
|
<q-item v-for="wallet in wallets" :key="wallet.id"
|
|
clickable
|
|
:active="activeWallet && activeWallet.id === wallet.id"
|
|
tag="a" :href="wallet.url">
|
|
<q-item-section side>
|
|
<q-avatar size="md"
|
|
:color="(activeWallet && activeWallet.id === wallet.id)
|
|
? (($q.dark.isActive) ? 'primary' : 'primary')
|
|
: 'grey-5'">
|
|
<q-icon name="flash_on" :size="($q.dark.isActive) ? '21px' : '20px'"
|
|
:color="($q.dark.isActive) ? 'blue-grey-10' : 'grey-3'"></q-icon>
|
|
</q-avatar>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label lines="1">{{ wallet.name }}</q-item-label>
|
|
<q-item-label v-if="LNBITS_DENOMINATION != 'sats'" caption>{{ parseFloat(String(wallet.live_fsat).replaceAll(",", "")) / 100 }} {{ LNBITS_DENOMINATION }}</q-item-label>
|
|
<q-item-label v-else caption>{{ wallet.live_fsat }} {{ LNBITS_DENOMINATION }}</q-item-label>
|
|
</q-item-section>
|
|
<q-item-section side v-show="activeWallet && activeWallet.id === wallet.id">
|
|
<q-icon name="chevron_right" color="grey-5" size="md"></q-icon>
|
|
</q-item-section>
|
|
</q-item>
|
|
<q-item clickable @click="showForm = !showForm">
|
|
<q-item-section side>
|
|
<q-icon :name="(showForm) ? 'remove' : 'add'" color="grey-5" size="md"></q-icon>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label lines="1" class="text-caption" v-text="$t('add_wallet')"></q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
<q-item v-if="showForm">
|
|
<q-item-section>
|
|
<q-form @submit="createWallet">
|
|
<q-input filled dense v-model="walletName" label="Name wallet *">
|
|
<template v-slot:append>
|
|
<q-btn round dense flat icon="send" size="sm" @click="createWallet" :disable="walletName === ''"></q-btn>
|
|
</template>
|
|
</q-input>
|
|
</q-form>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
`,
|
|
computed: {
|
|
wallets: function () {
|
|
var bal = this.activeBalance
|
|
return this.user.wallets.map(function (obj) {
|
|
obj.live_fsat =
|
|
bal.length && bal[0] === obj.id
|
|
? LNbits.utils.formatSat(bal[1])
|
|
: obj.fsat
|
|
return obj
|
|
})
|
|
}
|
|
},
|
|
methods: {
|
|
createWallet: function () {
|
|
LNbits.api.createWallet(this.user.wallets[0], this.walletName)
|
|
},
|
|
updateWalletBalance: function (payload) {
|
|
this.activeBalance = payload
|
|
}
|
|
},
|
|
created: function () {
|
|
if (window.user) {
|
|
this.user = LNbits.map.user(window.user)
|
|
}
|
|
if (window.wallet) {
|
|
this.activeWallet = LNbits.map.wallet(window.wallet)
|
|
}
|
|
EventHub.$on('update-wallet-balance', this.updateWalletBalance)
|
|
}
|
|
})
|
|
|
|
Vue.component('lnbits-extension-list', {
|
|
data: function () {
|
|
return {
|
|
extensions: [],
|
|
user: null
|
|
}
|
|
},
|
|
template: `
|
|
<q-list v-if="user && userExtensions.length > 0" dense class="lnbits-drawer__q-list">
|
|
<q-item-label header v-text="$t('extensions')"></q-item-label>
|
|
<q-item v-for="extension in userExtensions" :key="extension.code"
|
|
clickable
|
|
:active="extension.isActive"
|
|
tag="a" :href="extension.url">
|
|
<q-item-section side>
|
|
<q-avatar size="md">
|
|
<q-img
|
|
:src="extension.tile"
|
|
style="max-width:20px"
|
|
></q-img>
|
|
</q-avatar>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label lines="1">{{ extension.name }} </q-item-label>
|
|
</q-item-section>
|
|
<q-item-section side v-show="extension.isActive">
|
|
<q-icon name="chevron_right" color="grey-5" size="md"></q-icon>
|
|
</q-item-section>
|
|
</q-item>
|
|
<div class="lt-md q-mt-xl q-mb-xl"></div>
|
|
</q-list>
|
|
`,
|
|
computed: {
|
|
userExtensions: function () {
|
|
if (!this.user) return []
|
|
|
|
var path = window.location.pathname
|
|
var userExtensions = this.user.extensions
|
|
|
|
return this.extensions
|
|
.filter(function (obj) {
|
|
return userExtensions.indexOf(obj.code) !== -1
|
|
})
|
|
.map(function (obj) {
|
|
obj.isActive = path.startsWith(obj.url)
|
|
return obj
|
|
})
|
|
}
|
|
},
|
|
created: function () {
|
|
if (window.extensions) {
|
|
this.extensions = window.extensions
|
|
.map(function (data) {
|
|
return LNbits.map.extension(data)
|
|
})
|
|
.sort(function (a, b) {
|
|
return a.name.localeCompare(b.name)
|
|
})
|
|
}
|
|
|
|
if (window.user) {
|
|
this.user = LNbits.map.user(window.user)
|
|
}
|
|
}
|
|
})
|
|
|
|
Vue.component('lnbits-manage', {
|
|
props: ['showAdmin', 'showNode', 'showExtensions'],
|
|
methods: {
|
|
isActive: function (path) {
|
|
return window.location.pathname === path
|
|
}
|
|
},
|
|
data: function () {
|
|
return {
|
|
extensions: [],
|
|
user: null
|
|
}
|
|
},
|
|
template: `
|
|
<q-list v-if="user" dense class="lnbits-drawer__q-list">
|
|
<q-item-label header v-text="$t('manage')"></q-item-label>
|
|
<div v-if="user.admin">
|
|
<q-item v-if='showAdmin' clickable tag="a" href="/admin" :active="isActive('/admin')">
|
|
<q-item-section side>
|
|
<q-icon name="admin_panel_settings" :color="isActive('/admin') ? 'primary' : 'grey-5'" size="md"></q-icon>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label lines="1" v-text="$t('server')"></q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
<q-item v-if='showNode' clickable tag="a" href="/node" :active="isActive('/node')">
|
|
<q-item-section side>
|
|
<q-icon name="developer_board" :color="isActive('/node') ? 'primary' : 'grey-5'" size="md"></q-icon>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label lines="1" v-text="$t('node')"></q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
</div>
|
|
<q-item v-if="showExtensions" clickable tag="a" href="/extensions" :active="isActive('/extensions')">
|
|
<q-item-section side>
|
|
<q-icon name="extension" :color="isActive('/extensions') ? 'primary' : 'grey-5'" size="md"></q-icon>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label lines="1" v-text="$t('extensions')"></q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
`,
|
|
|
|
created: function () {
|
|
if (window.user) {
|
|
this.user = LNbits.map.user(window.user)
|
|
}
|
|
}
|
|
})
|
|
|
|
Vue.component('lnbits-payment-details', {
|
|
props: ['payment'],
|
|
mixins: [windowMixin],
|
|
data: function () {
|
|
return {
|
|
LNBITS_DENOMINATION: LNBITS_DENOMINATION
|
|
}
|
|
},
|
|
template: `
|
|
<div class="q-py-md" style="text-align: left">
|
|
|
|
<div v-if="payment.tag" class="row justify-center q-mb-md">
|
|
<q-badge v-if="hasTag" color="yellow" text-color="black">
|
|
#{{ payment.tag }}
|
|
</q-badge>
|
|
</div>
|
|
|
|
<div class="row">
|
|
<b v-text="$t('created')"></b>:
|
|
{{ payment.date }} ({{ payment.dateFrom }})
|
|
</div>
|
|
|
|
<div class="row" v-if="hasExpiry">
|
|
<b v-text="$t('expiry')"></b>:
|
|
{{ payment.expirydate }} ({{ payment.expirydateFrom }})
|
|
</div>
|
|
|
|
<div class="row">
|
|
<b v-text="$t('amount')"></b>:
|
|
{{ (payment.amount / 1000).toFixed(3) }} {{LNBITS_DENOMINATION}}
|
|
</div>
|
|
|
|
<div class="row">
|
|
<b v-text="$t('fee')"></b>:
|
|
{{ (payment.fee / 1000).toFixed(3) }} {{LNBITS_DENOMINATION}}
|
|
</div>
|
|
|
|
<div class="text-wrap">
|
|
<b style="white-space: nowrap;" v-text="$t('payment_hash')"></b>: {{ payment.payment_hash }}
|
|
<q-icon name="content_copy" @click="copyText(payment.payment_hash)" size="1em" color="grey" class="q-mb-xs cursor-pointer" />
|
|
</div>
|
|
|
|
<div class="text-wrap">
|
|
<b style="white-space: nowrap;" v-text="$t('memo')"></b>: {{ payment.memo }}
|
|
</div>
|
|
|
|
<div class="text-wrap" v-if="payment.webhook">
|
|
<b style="white-space: nowrap;" v-text="$t('webhook')"></b>: {{ payment.webhook }}: <q-badge :color="webhookStatusColor" text-color="white">
|
|
{{ webhookStatusText }}
|
|
</q-badge>
|
|
</div>
|
|
|
|
<div class="text-wrap" v-if="hasPreimage">
|
|
<b style="white-space: nowrap;" v-text="$t('payment_proof')"></b>: {{ payment.preimage }}
|
|
</div>
|
|
|
|
<div class="row" v-for="entry in extras">
|
|
<q-badge v-if="hasTag" color="secondary" text-color="white">
|
|
extra
|
|
</q-badge>
|
|
<b>{{ entry.key }}</b>:
|
|
{{ entry.value }}
|
|
</div>
|
|
|
|
<div class="row" v-if="hasSuccessAction">
|
|
<b>Success action</b>:
|
|
<lnbits-lnurlpay-success-action
|
|
:payment="payment"
|
|
:success_action="payment.extra.success_action"
|
|
></lnbits-lnurlpay-success-action>
|
|
</div>
|
|
|
|
</div>
|
|
`,
|
|
computed: {
|
|
hasPreimage() {
|
|
return (
|
|
this.payment.preimage &&
|
|
this.payment.preimage !==
|
|
'0000000000000000000000000000000000000000000000000000000000000000'
|
|
)
|
|
},
|
|
hasExpiry() {
|
|
return !!this.payment.expiry
|
|
},
|
|
hasSuccessAction() {
|
|
return (
|
|
this.hasPreimage &&
|
|
this.payment.extra &&
|
|
this.payment.extra.success_action
|
|
)
|
|
},
|
|
webhookStatusColor() {
|
|
return this.payment.webhook_status >= 300 ||
|
|
this.payment.webhook_status < 0
|
|
? 'red-10'
|
|
: !this.payment.webhook_status
|
|
? 'cyan-7'
|
|
: 'green-10'
|
|
},
|
|
webhookStatusText() {
|
|
return this.payment.webhook_status
|
|
? this.payment.webhook_status
|
|
: 'not sent yet'
|
|
},
|
|
hasTag() {
|
|
return this.payment.extra && !!this.payment.extra.tag
|
|
},
|
|
extras() {
|
|
if (!this.payment.extra) return []
|
|
let extras = _.omit(this.payment.extra, ['tag', 'success_action'])
|
|
return Object.keys(extras).map(key => ({key, value: extras[key]}))
|
|
}
|
|
}
|
|
})
|
|
|
|
Vue.component('lnbits-lnurlpay-success-action', {
|
|
props: ['payment', 'success_action'],
|
|
data() {
|
|
return {
|
|
decryptedValue: this.success_action.ciphertext
|
|
}
|
|
},
|
|
template: `
|
|
<div>
|
|
<p class="q-mb-sm">{{ success_action.message || success_action.description }}</p>
|
|
<code v-if="decryptedValue" class="text-h6 q-mt-sm q-mb-none">
|
|
{{ decryptedValue }}
|
|
</code>
|
|
<p v-else-if="success_action.url" class="text-h6 q-mt-sm q-mb-none">
|
|
<a target="_blank" style="color: inherit;" :href="success_action.url">{{ success_action.url }}</a>
|
|
</p>
|
|
</div>
|
|
`,
|
|
mounted: function () {
|
|
if (this.success_action.tag !== 'aes') return null
|
|
|
|
decryptLnurlPayAES(this.success_action, this.payment.preimage).then(
|
|
value => {
|
|
this.decryptedValue = value
|
|
}
|
|
)
|
|
}
|
|
})
|
|
|
|
Vue.component('lnbits-qrcode', {
|
|
mixins: [windowMixin],
|
|
props: ['value'],
|
|
components: {[VueQrcode.name]: VueQrcode},
|
|
data() {
|
|
return {
|
|
logo: LNBITS_QR_LOGO
|
|
}
|
|
},
|
|
template: `
|
|
<div class="qrcode__wrapper">
|
|
<qrcode :value="value"
|
|
:options="{errorCorrectionLevel: 'Q', width: 800}" class="rounded-borders"></qrcode>
|
|
<img class="qrcode__image" :src="logo" alt="..." />
|
|
</div>
|
|
`
|
|
})
|
|
|
|
Vue.component('lnbits-notifications-btn', {
|
|
mixins: [windowMixin],
|
|
props: ['pubkey'],
|
|
data() {
|
|
return {
|
|
isSupported: false,
|
|
isSubscribed: false,
|
|
isPermissionGranted: false,
|
|
isPermissionDenied: false
|
|
}
|
|
},
|
|
template: `
|
|
<q-btn
|
|
v-if="g.user.wallets"
|
|
:disabled="!this.isSupported"
|
|
dense
|
|
flat
|
|
round
|
|
@click="toggleNotifications()"
|
|
:icon="this.isSubscribed ? 'notifications_active' : 'notifications_off'"
|
|
size="sm"
|
|
type="a"
|
|
>
|
|
<q-tooltip v-if="this.isSupported && !this.isSubscribed">Subscribe to notifications</q-tooltip>
|
|
<q-tooltip v-if="this.isSupported && this.isSubscribed">Unsubscribe from notifications</q-tooltip>
|
|
<q-tooltip v-if="this.isSupported && this.isPermissionDenied">
|
|
Notifications are disabled,<br/>please enable or reset permissions
|
|
</q-tooltip>
|
|
<q-tooltip v-if="!this.isSupported">Notifications are not supported</q-tooltip>
|
|
</q-btn>
|
|
`,
|
|
methods: {
|
|
// converts base64 to Array buffer
|
|
urlB64ToUint8Array(base64String) {
|
|
const padding = '='.repeat((4 - (base64String.length % 4)) % 4)
|
|
const base64 = (base64String + padding)
|
|
.replace(/\-/g, '+')
|
|
.replace(/_/g, '/')
|
|
const rawData = atob(base64)
|
|
const outputArray = new Uint8Array(rawData.length)
|
|
|
|
for (let i = 0; i < rawData.length; ++i) {
|
|
outputArray[i] = rawData.charCodeAt(i)
|
|
}
|
|
|
|
return outputArray
|
|
},
|
|
toggleNotifications() {
|
|
this.isSubscribed ? this.unsubscribe() : this.subscribe()
|
|
},
|
|
saveUserSubscribed(user) {
|
|
let subscribedUsers =
|
|
JSON.parse(
|
|
this.$q.localStorage.getItem('lnbits.webpush.subscribedUsers')
|
|
) || []
|
|
if (!subscribedUsers.includes(user)) subscribedUsers.push(user)
|
|
this.$q.localStorage.set(
|
|
'lnbits.webpush.subscribedUsers',
|
|
JSON.stringify(subscribedUsers)
|
|
)
|
|
},
|
|
removeUserSubscribed(user) {
|
|
let subscribedUsers =
|
|
JSON.parse(
|
|
this.$q.localStorage.getItem('lnbits.webpush.subscribedUsers')
|
|
) || []
|
|
subscribedUsers = subscribedUsers.filter(arr => arr !== user)
|
|
this.$q.localStorage.set(
|
|
'lnbits.webpush.subscribedUsers',
|
|
JSON.stringify(subscribedUsers)
|
|
)
|
|
},
|
|
isUserSubscribed(user) {
|
|
let subscribedUsers =
|
|
JSON.parse(
|
|
this.$q.localStorage.getItem('lnbits.webpush.subscribedUsers')
|
|
) || []
|
|
return subscribedUsers.includes(user)
|
|
},
|
|
subscribe() {
|
|
var self = this
|
|
|
|
// catch clicks from disabled type='a' button (https://github.com/quasarframework/quasar/issues/9258)
|
|
if (!this.isSupported || this.isPermissionDenied) {
|
|
return
|
|
}
|
|
|
|
// ask for notification permission
|
|
Notification.requestPermission()
|
|
.then(permission => {
|
|
this.isPermissionGranted = permission === 'granted'
|
|
this.isPermissionDenied = permission === 'denied'
|
|
})
|
|
.catch(function (e) {
|
|
console.log(e)
|
|
})
|
|
|
|
// create push subscription
|
|
navigator.serviceWorker.ready.then(registration => {
|
|
navigator.serviceWorker.getRegistration().then(registration => {
|
|
registration.pushManager
|
|
.getSubscription()
|
|
.then(function (subscription) {
|
|
if (
|
|
subscription === null ||
|
|
!self.isUserSubscribed(self.g.user.id)
|
|
) {
|
|
const applicationServerKey = self.urlB64ToUint8Array(
|
|
self.pubkey
|
|
)
|
|
const options = {applicationServerKey, userVisibleOnly: true}
|
|
|
|
registration.pushManager
|
|
.subscribe(options)
|
|
.then(function (subscription) {
|
|
LNbits.api
|
|
.request(
|
|
'POST',
|
|
'/api/v1/webpush',
|
|
self.g.user.wallets[0].adminkey,
|
|
{
|
|
subscription: JSON.stringify(subscription)
|
|
}
|
|
)
|
|
.then(function (response) {
|
|
self.saveUserSubscribed(response.data.user)
|
|
self.isSubscribed = true
|
|
})
|
|
.catch(function (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
})
|
|
}
|
|
})
|
|
.catch(function (e) {
|
|
console.log(e)
|
|
})
|
|
})
|
|
})
|
|
},
|
|
unsubscribe() {
|
|
var self = this
|
|
|
|
navigator.serviceWorker.ready
|
|
.then(registration => {
|
|
registration.pushManager.getSubscription().then(subscription => {
|
|
if (subscription) {
|
|
LNbits.api
|
|
.request(
|
|
'DELETE',
|
|
'/api/v1/webpush?endpoint=' + btoa(subscription.endpoint),
|
|
self.g.user.wallets[0].adminkey
|
|
)
|
|
.then(function () {
|
|
self.removeUserSubscribed(self.g.user.id)
|
|
self.isSubscribed = false
|
|
})
|
|
.catch(function (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
}
|
|
})
|
|
})
|
|
.catch(function (e) {
|
|
console.log(e)
|
|
})
|
|
},
|
|
checkSupported: function () {
|
|
let https = window.location.protocol === 'https:'
|
|
let serviceWorkerApi = 'serviceWorker' in navigator
|
|
let notificationApi = 'Notification' in window
|
|
let pushApi = 'PushManager' in window
|
|
|
|
this.isSupported = https && serviceWorkerApi && notificationApi && pushApi
|
|
|
|
if (!this.isSupported) {
|
|
console.log(
|
|
'Notifications disabled because requirements are not met:',
|
|
{
|
|
HTTPS: https,
|
|
'Service Worker API': serviceWorkerApi,
|
|
'Notification API': notificationApi,
|
|
'Push API': pushApi
|
|
}
|
|
)
|
|
}
|
|
|
|
return this.isSupported
|
|
},
|
|
updateSubscriptionStatus: async function () {
|
|
var self = this
|
|
|
|
await navigator.serviceWorker.ready
|
|
.then(registration => {
|
|
registration.pushManager.getSubscription().then(subscription => {
|
|
self.isSubscribed =
|
|
!!subscription && self.isUserSubscribed(self.g.user.id)
|
|
})
|
|
})
|
|
.catch(function (e) {
|
|
console.log(e)
|
|
})
|
|
}
|
|
},
|
|
created: function () {
|
|
this.isPermissionDenied = Notification.permission === 'denied'
|
|
|
|
if (this.checkSupported()) {
|
|
this.updateSubscriptionStatus()
|
|
}
|
|
}
|
|
})
|
|
|
|
Vue.component('lnbits-dynamic-fields', {
|
|
mixins: [windowMixin],
|
|
props: ['options', 'value'],
|
|
data() {
|
|
return {
|
|
formData: null
|
|
}
|
|
},
|
|
|
|
template: `
|
|
<div v-if="formData">
|
|
<div class="row q-mb-lg" v-for="o in options">
|
|
<div class="col auto-width">
|
|
<p v-if=o.options?.length class="q-ml-xl">
|
|
<span v-text="o.name"></span> <small v-if="o.description"> (<span v-text="o.description"></span>)</small>
|
|
</p>
|
|
<lnbits-dynamic-fields v-if="o.options?.length" :options="o.options" v-model="formData[o.name]"
|
|
@input="handleValueChanged" class="q-ml-xl">
|
|
</lnbits-dynamic-fields>
|
|
<div v-else>
|
|
<q-input v-if="o.type === 'number'" v-model="formData[o.name]" @input="handleValueChanged" type="number"
|
|
:label="o.name" :hint="o.description" filled dense>
|
|
</q-input>
|
|
<q-input v-else-if="o.type === 'text'" v-model="formData[o.name]" @input="handleValueChanged" type="textarea"
|
|
rows="5" :label="o.name" :hint="o.description" filled dense>
|
|
</q-input>
|
|
<q-input v-else-if="o.type === 'password'" v-model="formData[o.name]" @input="handleValueChanged" type="password"
|
|
:label="o.name" :hint="o.description" filled dense>
|
|
</q-input>
|
|
<div v-else-if="o.type === 'bool'">
|
|
<q-item tag="label" v-ripple>
|
|
<q-item-section avatar top>
|
|
<q-checkbox v-model="formData[o.name]" @input="handleValueChanged" />
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label><span v-text="o.name"></span></q-item-label>
|
|
<q-item-label caption> <span v-text="o.description"></span> </q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
</div>
|
|
<q-select v-else-if="o.type === 'select'" v-model="formData[o.name]" @input="handleValueChanged" :label="o.name"
|
|
:hint="o.description" :options="o.values"></q-select>
|
|
|
|
<q-select v-else-if="o.isList" filled multiple dense v-model.trim="formData[o.name]" use-input use-chips
|
|
@input="handleValueChanged" multiple hide-dropdown-icon input-debounce="0" new-value-mode="add-unique"
|
|
:label="o.name" :hint="o.description">
|
|
</q-select>
|
|
<q-input v-else v-model="formData[o.name]" @input="handleValueChanged" :label="o.name" :hint="o.description"
|
|
filled dense>
|
|
</q-input>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`,
|
|
methods: {
|
|
buildData(options, data = {}) {
|
|
return options.reduce((d, option) => {
|
|
if (option.options?.length) {
|
|
d[option.name] = this.buildData(option.options, data[option.name])
|
|
} else {
|
|
d[option.name] = data[option.name] ?? option.default
|
|
}
|
|
return d
|
|
}, {})
|
|
},
|
|
handleValueChanged() {
|
|
this.$emit('input', this.formData)
|
|
}
|
|
},
|
|
created: function () {
|
|
this.formData = this.buildData(this.options, this.value)
|
|
}
|
|
})
|
|
|
|
Vue.component('lnbits-update-balance', {
|
|
mixins: [windowMixin],
|
|
props: ['wallet_id', 'callback'],
|
|
computed: {
|
|
denomination() {
|
|
return LNBITS_DENOMINATION
|
|
},
|
|
admin() {
|
|
return this.g.user.admin
|
|
}
|
|
},
|
|
data: function () {
|
|
return {
|
|
credit: 0
|
|
}
|
|
},
|
|
methods: {
|
|
updateBalance: function (credit) {
|
|
LNbits.api
|
|
.updateBalance(credit, this.wallet_id)
|
|
.then(res => {
|
|
this.callback({value: res, wallet_id: this.wallet_id})
|
|
})
|
|
.then(_ => {
|
|
credit = parseInt(credit)
|
|
this.$q.notify({
|
|
type: 'positive',
|
|
message: this.$t('wallet_topup_ok', {
|
|
amount: credit
|
|
}),
|
|
icon: null
|
|
})
|
|
return credit
|
|
})
|
|
.catch(function (error) {
|
|
LNbits.utils.notifyApiError(error)
|
|
})
|
|
}
|
|
},
|
|
template: `
|
|
<q-btn
|
|
v-if="admin"
|
|
round
|
|
color="primary"
|
|
icon="add"
|
|
size="sm"
|
|
>
|
|
<q-popup-edit
|
|
class="bg-accent text-white"
|
|
v-slot="scope"
|
|
v-model="credit"
|
|
>
|
|
<q-input
|
|
filled
|
|
:label='$t("credit_label", { denomination: denomination })'
|
|
:hint="$t('credit_hint')"
|
|
v-model="scope.value"
|
|
dense
|
|
autofocus
|
|
@keyup.enter="updateBalance(scope.value)"
|
|
>
|
|
<template v-slot:append>
|
|
<q-icon name="edit" />
|
|
</template>
|
|
</q-input>
|
|
</q-popup-edit>
|
|
<q-tooltip>Topup Wallet</q-tooltip>
|
|
</q-btn>
|
|
`
|
|
})
|