v1 feat: Vue Routes (#2872)
This commit is contained in:
parent
5abfbdd07a
commit
6a08d20fe8
28 changed files with 1919 additions and 1597 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -39,6 +39,8 @@ lnbits/static/bundle-components.js
|
||||||
lnbits/static/bundle.css
|
lnbits/static/bundle.css
|
||||||
lnbits/static/bundle.min.js.old
|
lnbits/static/bundle.min.js.old
|
||||||
lnbits/static/bundle.min.css.old
|
lnbits/static/bundle.min.css.old
|
||||||
|
lnbits/static/bundle-components.min.js.old
|
||||||
|
lnbits/upgrades
|
||||||
docker
|
docker
|
||||||
|
|
||||||
# Nix
|
# Nix
|
||||||
|
|
|
||||||
|
|
@ -1,60 +1,75 @@
|
||||||
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
{% if not ajax %} {% extends "base.html" %} {% endif %} {% from "macros.jinja"
|
||||||
%} {% block page %}
|
import window_vars with context %} {% block scripts %} {{ window_vars(user) }}
|
||||||
<div class="row q-col-gutter-md justify-center">
|
{% endblock %} {% block page %}
|
||||||
<div class="col q-mb-md">
|
|
||||||
<q-btn
|
|
||||||
:label="$t('save')"
|
|
||||||
color="primary"
|
|
||||||
@click="updateSettings"
|
|
||||||
:disabled="!checkChanges"
|
|
||||||
>
|
|
||||||
<q-tooltip v-if="checkChanges">
|
|
||||||
<span v-text="$t('save_tooltip')"></span>
|
|
||||||
</q-tooltip>
|
|
||||||
|
|
||||||
<q-badge
|
<div class="row q-col-gutter-md q-mb-md">
|
||||||
v-if="checkChanges"
|
<div class="col-12">
|
||||||
color="red"
|
<q-card>
|
||||||
rounded
|
<div class="q-pa-sm">
|
||||||
floating
|
<div class="row items-center justify-between q-gutter-xs">
|
||||||
style="padding: 6px; border-radius: 6px"
|
<div class="col">
|
||||||
/>
|
<q-btn
|
||||||
</q-btn>
|
:label="$t('save')"
|
||||||
|
color="primary"
|
||||||
|
@click="updateSettings"
|
||||||
|
:disabled="!checkChanges"
|
||||||
|
>
|
||||||
|
<q-tooltip v-if="checkChanges">
|
||||||
|
<span v-text="$t('save_tooltip')"></span>
|
||||||
|
</q-tooltip>
|
||||||
|
|
||||||
<q-btn
|
<q-badge
|
||||||
v-if="isSuperUser"
|
v-if="checkChanges"
|
||||||
:label="$t('restart')"
|
color="red"
|
||||||
color="primary"
|
rounded
|
||||||
@click="restartServer"
|
floating
|
||||||
class="q-ml-md"
|
style="padding: 6px; border-radius: 6px"
|
||||||
>
|
/>
|
||||||
<q-tooltip v-if="needsRestart">
|
</q-btn>
|
||||||
<span v-text="$t('restart_tooltip')"></span>
|
|
||||||
</q-tooltip>
|
|
||||||
|
|
||||||
<q-badge
|
<q-btn
|
||||||
v-if="needsRestart"
|
v-if="isSuperUser"
|
||||||
color="red"
|
:label="$t('restart')"
|
||||||
rounded
|
color="primary"
|
||||||
floating
|
@click="restartServer"
|
||||||
style="padding: 6px; border-radius: 6px"
|
class="q-ml-md"
|
||||||
/>
|
>
|
||||||
</q-btn>
|
<q-tooltip v-if="needsRestart">
|
||||||
|
<span v-text="$t('restart_tooltip')"></span>
|
||||||
|
</q-tooltip>
|
||||||
|
|
||||||
<q-btn :label="$t('download_backup')" flat @click="downloadBackup"></q-btn>
|
<q-badge
|
||||||
|
v-if="needsRestart"
|
||||||
|
color="red"
|
||||||
|
rounded
|
||||||
|
floating
|
||||||
|
style="padding: 6px; border-radius: 6px"
|
||||||
|
/>
|
||||||
|
</q-btn>
|
||||||
|
|
||||||
<q-btn
|
<q-btn
|
||||||
flat
|
:label="$t('download_backup')"
|
||||||
v-if="isSuperUser"
|
flat
|
||||||
:label="$t('reset_defaults')"
|
@click="downloadBackup"
|
||||||
color="primary"
|
></q-btn>
|
||||||
@click="deleteSettings"
|
|
||||||
class="float-right"
|
<q-btn
|
||||||
>
|
flat
|
||||||
<q-tooltip>
|
v-if="isSuperUser"
|
||||||
<span v-text="$t('reset_defaults_tooltip')"></span>
|
:label="$t('reset_defaults')"
|
||||||
</q-tooltip>
|
color="primary"
|
||||||
</q-btn>
|
@click="deleteSettings"
|
||||||
|
class="float-right"
|
||||||
|
>
|
||||||
|
<q-tooltip>
|
||||||
|
<span v-text="$t('reset_defaults_tooltip')"></span>
|
||||||
|
</q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -213,7 +228,4 @@
|
||||||
</div>
|
</div>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
|
|
||||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
|
||||||
<script src="{{ static_url_for('static', 'js/admin.js') }}"></script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,39 @@
|
||||||
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
{% if not ajax %} {% extends "base.html" %} {% endif %}
|
||||||
%} {% block page %}
|
<!---->
|
||||||
|
{% from "macros.jinja" import window_vars with context %}
|
||||||
|
<!---->
|
||||||
|
{% block scripts %} {{ window_vars(user) }} {% endblock %} {% block page %}
|
||||||
|
<div class="row q-col-gutter-md q-mb-md">
|
||||||
|
<div class="col-12">
|
||||||
|
<q-card>
|
||||||
|
<div class="q-pa-sm q-pl-lg">
|
||||||
|
<div class="row items-center justify-between q-gutter-xs">
|
||||||
|
<div class="col">
|
||||||
|
<!-- Optional: Add content here if needed -->
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<q-btn
|
||||||
|
v-if="g.user.admin"
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
icon="settings"
|
||||||
|
to="/admin#audit"
|
||||||
|
>
|
||||||
|
<q-tooltip v-text="$t('admin_settings')"></q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row q-col-gutter-md justify-center q-mb-lg">
|
<div class="row q-col-gutter-md justify-center q-mb-lg">
|
||||||
<div class="col-lg-3 col-md-6 col-sm-12 text-center">
|
<div class="col-lg-3 col-md-6 col-sm-12 text-center">
|
||||||
<q-card class="q-pt-sm">
|
<q-card class="q-pt-sm">
|
||||||
<strong v-text="$t('components')"></strong>
|
<strong v-text="$t('components')"></strong>
|
||||||
<div style="width: 250px" class="q-pa-sm">
|
<div style="width: 250px" class="q-pa-sm">
|
||||||
<canvas ref="componentUseChart"></canvas>
|
<canvas v-if="chartsReady" ref="componentUseChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -14,7 +41,7 @@
|
||||||
<q-card class="q-pt-sm">
|
<q-card class="q-pt-sm">
|
||||||
<strong v-text="$t('long_running_endpoints')"></strong>
|
<strong v-text="$t('long_running_endpoints')"></strong>
|
||||||
<div style="width: 250px; height: 250px" class="q-pa-sm">
|
<div style="width: 250px; height: 250px" class="q-pa-sm">
|
||||||
<canvas ref="longDurationChart"></canvas>
|
<canvas v-if="chartsReady" ref="longDurationChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -22,7 +49,7 @@
|
||||||
<q-card class="q-pt-sm">
|
<q-card class="q-pt-sm">
|
||||||
<strong v-text="$t('http_request_methods')"></strong>
|
<strong v-text="$t('http_request_methods')"></strong>
|
||||||
<div style="width: 250px; height: 250px" class="q-pa-sm">
|
<div style="width: 250px; height: 250px" class="q-pa-sm">
|
||||||
<canvas ref="requestMethodChart"></canvas>
|
<canvas v-if="chartsReady" ref="requestMethodChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -30,7 +57,7 @@
|
||||||
<q-card class="q-pt-sm">
|
<q-card class="q-pt-sm">
|
||||||
<strong v-text="$t('http_response_codes')"></strong>
|
<strong v-text="$t('http_response_codes')"></strong>
|
||||||
<div style="width: 250px; height: 250px" class="q-pa-sm">
|
<div style="width: 250px; height: 250px" class="q-pa-sm">
|
||||||
<canvas ref="responseCodeChart"></canvas>
|
<canvas v-if="chartsReady" ref="responseCodeChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -83,11 +110,6 @@
|
||||||
|
|
||||||
<span v-else v-text="col.label"></span>
|
<span v-else v-text="col.label"></span>
|
||||||
</q-th>
|
</q-th>
|
||||||
<q-th>
|
|
||||||
<q-btn flat round icon="settings" tag="a" href="/admin#audit"
|
|
||||||
><q-tooltip v-text="$t('admin_settings')"></q-tooltip
|
|
||||||
></q-btn>
|
|
||||||
</q-th>
|
|
||||||
</q-tr>
|
</q-tr>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:body="props">
|
<template v-slot:body="props">
|
||||||
|
|
@ -176,6 +198,4 @@
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
|
|
||||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
|
||||||
<script src="{{ static_url_for('static', 'js/audit.js') }}"></script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
{% extends "base.html" %}
|
{% if not ajax %} {% extends "base.html" %} {% endif %}
|
||||||
<!---->
|
<!---->
|
||||||
{% from "macros.jinja" import window_vars with context %}
|
{% from "macros.jinja" import window_vars with context %}
|
||||||
<!---->
|
<!---->
|
||||||
{% block scripts %} {{ window_vars(user) }}
|
{% block scripts %} {{ window_vars(user) }}{% endblock %} {% block page %}
|
||||||
<script src="{{ static_url_for('static', 'js/account.js') }}"></script>
|
|
||||||
{% endblock %} {% block page %}
|
|
||||||
|
|
||||||
<div class="row q-col-gutter-md">
|
<div class="row q-col-gutter-md">
|
||||||
<div v-if="user" class="col-12 col-md-6 q-gutter-y-md">
|
<div v-if="user" class="col-12 col-md-6 q-gutter-y-md">
|
||||||
|
|
@ -292,8 +290,7 @@
|
||||||
flat
|
flat
|
||||||
round
|
round
|
||||||
icon="settings"
|
icon="settings"
|
||||||
tag="a"
|
to="/admin#site_customisation"
|
||||||
href="/admin#site_customisation"
|
|
||||||
><q-tooltip v-text="$t('admin_settings')"></q-tooltip
|
><q-tooltip v-text="$t('admin_settings')"></q-tooltip
|
||||||
></q-btn>
|
></q-btn>
|
||||||
<div class="row q-mb-md">
|
<div class="row q-mb-md">
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
{% if not ajax %} {% extends "base.html" %} {% endif %}
|
||||||
%} {{ window_vars(user, extensions) }}{% block page %}
|
<!---->
|
||||||
|
{% from "macros.jinja" import window_vars with context %}
|
||||||
|
<!---->
|
||||||
|
{% block scripts %} {{ window_vars(user, wallet, extensions, extension_data)
|
||||||
|
}}{% endblock %} {% block page %}
|
||||||
|
|
||||||
<div class="row q-col-gutter-md q-mb-md">
|
<div class="row q-col-gutter-md q-mb-md">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
<q-card>
|
<q-card>
|
||||||
<div class="q-pa-xs">
|
<div>
|
||||||
<div class="q-gutter-y-md">
|
<div class="q-gutter-y-md">
|
||||||
<q-tabs
|
<q-tabs
|
||||||
:model-value="tab"
|
:model-value="tab"
|
||||||
|
|
@ -54,8 +58,7 @@
|
||||||
flat
|
flat
|
||||||
round
|
round
|
||||||
icon="settings"
|
icon="settings"
|
||||||
tag="a"
|
to="/admin#extensions"
|
||||||
href="/admin#extensions"
|
|
||||||
><q-tooltip v-text="$t('admin_settings')"></q-tooltip
|
><q-tooltip v-text="$t('admin_settings')"></q-tooltip
|
||||||
></q-btn>
|
></q-btn>
|
||||||
</q-tabs>
|
</q-tabs>
|
||||||
|
|
@ -66,6 +69,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="row q-col-gutter-md">
|
<div class="row q-col-gutter-md">
|
||||||
<div
|
<div
|
||||||
|
v-if="filteredExtensions"
|
||||||
class="col-12 col-sm-6 col-md-6 col-lg-4"
|
class="col-12 col-sm-6 col-md-6 col-lg-4"
|
||||||
v-for="extension in filteredExtensions"
|
v-for="extension in filteredExtensions"
|
||||||
:key="extension.id + extension.hash"
|
:key="extension.id + extension.hash"
|
||||||
|
|
@ -114,6 +118,7 @@
|
||||||
></q-tooltip>
|
></q-tooltip>
|
||||||
</q-badge>
|
</q-badge>
|
||||||
<div
|
<div
|
||||||
|
v-if="extension.name"
|
||||||
class="text-h5"
|
class="text-h5"
|
||||||
style="cursor: pointer"
|
style="cursor: pointer"
|
||||||
@click="showExtensionDetails(extension.id, extension.details_link)"
|
@click="showExtensionDetails(extension.id, extension.details_link)"
|
||||||
|
|
@ -186,7 +191,7 @@
|
||||||
<div class="col-10">
|
<div class="col-10">
|
||||||
<div v-if="!extension.inProgress">
|
<div v-if="!extension.inProgress">
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="user.extensions.includes(extension.id) && extension.isActive && extension.isInstalled"
|
v-if="g.user.extensions.includes(extension.id) && extension.isActive && extension.isInstalled"
|
||||||
flat
|
flat
|
||||||
color="primary"
|
color="primary"
|
||||||
type="a"
|
type="a"
|
||||||
|
|
@ -194,20 +199,20 @@
|
||||||
:label="$t('open')"
|
:label="$t('open')"
|
||||||
></q-btn>
|
></q-btn>
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="user.extensions.includes(extension.id) && extension.isActive && extension.isInstalled"
|
v-if="g.user.extensions.includes(extension.id) && extension.isActive && extension.isInstalled"
|
||||||
flat
|
flat
|
||||||
color="grey-5"
|
color="grey-5"
|
||||||
@click="disableExtension(extension)"
|
@click="disableExtension(extension)"
|
||||||
:label="$t('disable')"
|
:label="$t('disable')"
|
||||||
></q-btn>
|
></q-btn>
|
||||||
<q-badge
|
<q-badge
|
||||||
v-if="extension.isAdminOnly && !user.admin"
|
v-if="extension.isAdminOnly && !g.user.admin"
|
||||||
v-text="$t('admin_only')"
|
v-text="$t('admin_only')"
|
||||||
>
|
>
|
||||||
</q-badge>
|
</q-badge>
|
||||||
|
|
||||||
<q-btn
|
<q-btn
|
||||||
v-else-if="extension.isInstalled && extension.isActive && !user.extensions.includes(extension.id)"
|
v-else-if="extension.isInstalled && extension.isActive && !g.user.extensions.includes(extension.id)"
|
||||||
flat
|
flat
|
||||||
color="primary"
|
color="primary"
|
||||||
@click="enableExtensionForUser(extension)"
|
@click="enableExtensionForUser(extension)"
|
||||||
|
|
@ -980,674 +985,4 @@
|
||||||
</div>
|
</div>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
|
||||||
<script>
|
|
||||||
window.app = Vue.createApp({
|
|
||||||
el: '#vue',
|
|
||||||
|
|
||||||
data: function () {
|
|
||||||
return {
|
|
||||||
slide: 0,
|
|
||||||
fullscreen: false,
|
|
||||||
autoplay: true,
|
|
||||||
searchTerm: '',
|
|
||||||
tab: 'all',
|
|
||||||
manageExtensionTab: 'releases',
|
|
||||||
filteredExtensions: null,
|
|
||||||
updatableExtensions: [],
|
|
||||||
showUninstallDialog: false,
|
|
||||||
showManageExtensionDialog: false,
|
|
||||||
showExtensionDetailsDialog: false,
|
|
||||||
showDropDbDialog: false,
|
|
||||||
showPayToEnableDialog: false,
|
|
||||||
showUpdateAllDialog: false,
|
|
||||||
dropDbExtensionId: '',
|
|
||||||
selectedExtension: null,
|
|
||||||
selectedImage: null,
|
|
||||||
selectedExtensionDetails: null,
|
|
||||||
selectedExtensionRepos: null,
|
|
||||||
selectedRelease: null,
|
|
||||||
uninstallAndDropDb: false,
|
|
||||||
maxStars: 5,
|
|
||||||
paylinkWebsocket: null,
|
|
||||||
user: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
searchTerm(term) {
|
|
||||||
this.filterExtensions(term, this.tab)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleTabChanged: function (tab) {
|
|
||||||
this.filterExtensions(this.searchTerm, tab)
|
|
||||||
},
|
|
||||||
filterExtensions: function (term, tab) {
|
|
||||||
// Filter the extensions list
|
|
||||||
function extensionNameContains(searchTerm) {
|
|
||||||
return function (extension) {
|
|
||||||
return (
|
|
||||||
extension.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
||||||
extension.shortDescription
|
|
||||||
?.toLowerCase()
|
|
||||||
.includes(searchTerm.toLowerCase())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this.filteredExtensions = this.extensions
|
|
||||||
.filter(e => (tab === 'all' ? !e.isInstalled : true))
|
|
||||||
.filter(e => (tab === 'installed' ? e.isInstalled : true))
|
|
||||||
.filter(e =>
|
|
||||||
tab === 'installed'
|
|
||||||
? e.isActive
|
|
||||||
? true
|
|
||||||
: !!this.g.user.admin
|
|
||||||
: true
|
|
||||||
)
|
|
||||||
.filter(e => (tab === 'featured' ? e.isFeatured : true))
|
|
||||||
.filter(extensionNameContains(term))
|
|
||||||
.map(e => ({
|
|
||||||
...e,
|
|
||||||
details_link:
|
|
||||||
e.installedRelease?.details_link || e.latestRelease?.details_link
|
|
||||||
}))
|
|
||||||
this.tab = tab
|
|
||||||
},
|
|
||||||
|
|
||||||
installExtension: async function (release) {
|
|
||||||
// no longer required to check if the invoice was paid
|
|
||||||
// the install logic has been triggered one way or another
|
|
||||||
this.unsubscribeFromPaylinkWs()
|
|
||||||
|
|
||||||
const extension = this.selectedExtension
|
|
||||||
extension.inProgress = true
|
|
||||||
this.showManageExtensionDialog = false
|
|
||||||
release.payment_hash =
|
|
||||||
release.payment_hash || this.getPaylinkHash(release.pay_link)
|
|
||||||
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'POST',
|
|
||||||
`/api/v1/extension`,
|
|
||||||
this.g.user.wallets[0].adminkey,
|
|
||||||
{
|
|
||||||
ext_id: extension.id,
|
|
||||||
archive: release.archive,
|
|
||||||
source_repo: release.source_repo,
|
|
||||||
payment_hash: release.payment_hash,
|
|
||||||
version: release.version
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(response => {
|
|
||||||
extension.isAvailable = true
|
|
||||||
extension.isInstalled = true
|
|
||||||
extension.installedRelease = release
|
|
||||||
this.toggleExtension(extension)
|
|
||||||
extension.inProgress = false
|
|
||||||
this.filteredExtensions = this.extensions.concat([])
|
|
||||||
this.handleTabChanged('installed')
|
|
||||||
this.tab = 'installed'
|
|
||||||
window.location.reload()
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.warn(err)
|
|
||||||
extension.inProgress = false
|
|
||||||
LNbits.utils.notifyApiError(err)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
uninstallExtension: async function () {
|
|
||||||
const extension = this.selectedExtension
|
|
||||||
this.showManageExtensionDialog = false
|
|
||||||
this.showUninstallDialog = false
|
|
||||||
extension.inProgress = true
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'DELETE',
|
|
||||||
`/api/v1/extension/${extension.id}`,
|
|
||||||
this.g.user.wallets[0].adminkey
|
|
||||||
)
|
|
||||||
.then(response => {
|
|
||||||
extension.isAvailable = false
|
|
||||||
extension.isInstalled = false
|
|
||||||
extension.inProgress = false
|
|
||||||
extension.installedRelease = null
|
|
||||||
this.filteredExtensions = this.extensions.concat([])
|
|
||||||
this.handleTabChanged('installed')
|
|
||||||
this.tab = 'installed'
|
|
||||||
Quasar.Notify.create({
|
|
||||||
type: 'positive',
|
|
||||||
message: 'Extension uninstalled!'
|
|
||||||
})
|
|
||||||
if (this.uninstallAndDropDb) {
|
|
||||||
this.showDropDb()
|
|
||||||
} else {
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.reload()
|
|
||||||
}, 300)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
LNbits.utils.notifyApiError(err)
|
|
||||||
extension.inProgress = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
dropExtensionDb: async function () {
|
|
||||||
const extension = this.selectedExtension
|
|
||||||
this.showManageExtensionDialog = false
|
|
||||||
this.showDropDbDialog = false
|
|
||||||
this.dropDbExtensionId = ''
|
|
||||||
extension.inProgress = true
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'DELETE',
|
|
||||||
`/api/v1/extension/${extension.id}/db`,
|
|
||||||
this.g.user.wallets[0].adminkey
|
|
||||||
)
|
|
||||||
.then(response => {
|
|
||||||
extension.installedRelease = null
|
|
||||||
extension.inProgress = false
|
|
||||||
extension.hasDatabaseTables = false
|
|
||||||
Quasar.Notify.create({
|
|
||||||
type: 'positive',
|
|
||||||
message: 'Extension DB deleted!'
|
|
||||||
})
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.reload()
|
|
||||||
}, 300)
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
LNbits.utils.notifyApiError(err)
|
|
||||||
extension.inProgress = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
toggleExtension(extension) {
|
|
||||||
const action = extension.isActive ? 'activate' : 'deactivate'
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'PUT',
|
|
||||||
`/api/v1/extension/${extension.id}/${action}`,
|
|
||||||
this.g.user.wallets[0].adminkey
|
|
||||||
)
|
|
||||||
.then(response => {
|
|
||||||
Quasar.Notify.create({
|
|
||||||
timeout: 2000,
|
|
||||||
type: 'positive',
|
|
||||||
message: `Extension '${extension.id}' ${action}d!`
|
|
||||||
})
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
LNbits.utils.notifyApiError(err)
|
|
||||||
extension.isActive = false
|
|
||||||
extension.inProgress = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
enableExtensionForUser: function (extension) {
|
|
||||||
if (extension.isPaymentRequired) {
|
|
||||||
this.showPayToEnable(extension)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.enableExtension(extension)
|
|
||||||
},
|
|
||||||
enableExtension: function (extension) {
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'PUT',
|
|
||||||
`/api/v1/extension/${extension.id}/enable`,
|
|
||||||
this.g.user.wallets[0].adminkey
|
|
||||||
)
|
|
||||||
.then(response => {
|
|
||||||
Quasar.Notify.create({
|
|
||||||
type: 'positive',
|
|
||||||
message: 'Extension enabled!'
|
|
||||||
})
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.reload()
|
|
||||||
}, 300)
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.warn(err)
|
|
||||||
LNbits.utils.notifyApiError(err)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
disableExtension: function (extension) {
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'PUT',
|
|
||||||
`/api/v1/extension/${extension.id}/disable`,
|
|
||||||
this.g.user.wallets[0].adminkey
|
|
||||||
)
|
|
||||||
.then(response => {
|
|
||||||
Quasar.Notify.create({
|
|
||||||
type: 'positive',
|
|
||||||
message: 'Extension disabled!'
|
|
||||||
})
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.reload()
|
|
||||||
}, 300)
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
console.warn(error)
|
|
||||||
LNbits.utils.notifyApiError(err)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
showPayToEnable: function (extension) {
|
|
||||||
this.selectedExtension = extension
|
|
||||||
this.selectedExtension.payToEnable.paidAmount =
|
|
||||||
extension.payToEnable.amount
|
|
||||||
this.selectedExtension.payToEnable.showQRCode = false
|
|
||||||
this.showPayToEnableDialog = true
|
|
||||||
},
|
|
||||||
updatePayToInstallData: function (extension) {
|
|
||||||
LNbits.api
|
|
||||||
.request(
|
|
||||||
'PUT',
|
|
||||||
`/api/v1/extension/${extension.id}/sell`,
|
|
||||||
this.g.user.wallets[0].adminkey,
|
|
||||||
{
|
|
||||||
required: extension.payToEnable.required,
|
|
||||||
amount: extension.payToEnable.amount,
|
|
||||||
wallet: extension.payToEnable.wallet
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(response => {
|
|
||||||
Quasar.Notify.create({
|
|
||||||
type: 'positive',
|
|
||||||
message: 'Payment info updated!'
|
|
||||||
})
|
|
||||||
this.showManageExtensionDialog = false
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
LNbits.utils.notifyApiError(err)
|
|
||||||
extension.inProgress = false
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
showUninstall: function () {
|
|
||||||
this.showManageExtensionDialog = false
|
|
||||||
this.showUninstallDialog = true
|
|
||||||
this.uninstallAndDropDb = false
|
|
||||||
},
|
|
||||||
|
|
||||||
showDropDb: function () {
|
|
||||||
this.showDropDbDialog = true
|
|
||||||
},
|
|
||||||
|
|
||||||
showManageExtension: async function (extension) {
|
|
||||||
this.selectedExtension = extension
|
|
||||||
this.selectedRelease = null
|
|
||||||
this.selectedExtensionRepos = null
|
|
||||||
this.manageExtensionTab = 'releases'
|
|
||||||
this.showManageExtensionDialog = true
|
|
||||||
|
|
||||||
try {
|
|
||||||
const {data} = await LNbits.api.request(
|
|
||||||
'GET',
|
|
||||||
`/api/v1/extension/${extension.id}/releases`,
|
|
||||||
this.g.user.wallets[0].adminkey
|
|
||||||
)
|
|
||||||
|
|
||||||
this.selectedExtensionRepos = data.reduce((repos, release) => {
|
|
||||||
repos[release.source_repo] = repos[release.source_repo] || {
|
|
||||||
releases: [],
|
|
||||||
isInstalled: false,
|
|
||||||
repo: release.repo
|
|
||||||
}
|
|
||||||
release.inProgress = false
|
|
||||||
release.error = null
|
|
||||||
release.loaded = false
|
|
||||||
release.isInstalled = this.isInstalledVersion(
|
|
||||||
this.selectedExtension,
|
|
||||||
release
|
|
||||||
)
|
|
||||||
if (release.isInstalled) {
|
|
||||||
repos[release.source_repo].isInstalled = true
|
|
||||||
}
|
|
||||||
if (release.pay_link) {
|
|
||||||
release.requiresPayment = true
|
|
||||||
release.paidAmount = release.cost_sats
|
|
||||||
release.payment_hash = this.getPaylinkHash(release.pay_link)
|
|
||||||
}
|
|
||||||
|
|
||||||
repos[release.source_repo].releases.push(release)
|
|
||||||
return repos
|
|
||||||
}, {})
|
|
||||||
} catch (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
extension.inProgress = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
showExtensionDetails: async function (extId, detailsLink) {
|
|
||||||
if (!detailsLink) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.selectedExtensionDetails = null
|
|
||||||
this.showExtensionDetailsDialog = true
|
|
||||||
this.slide = 0
|
|
||||||
this.fullscreen = false
|
|
||||||
|
|
||||||
try {
|
|
||||||
const {data} = await LNbits.api.request(
|
|
||||||
'GET',
|
|
||||||
`/api/v1/extension/${extId}/details?details_link=${detailsLink}`,
|
|
||||||
this.g.user.wallets[0].inkey
|
|
||||||
)
|
|
||||||
|
|
||||||
this.selectedExtensionDetails = data
|
|
||||||
this.selectedExtensionDetails.description_md =
|
|
||||||
LNbits.utils.convertMarkdown(data.description_md)
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async payAndInstall(release) {
|
|
||||||
try {
|
|
||||||
this.selectedExtension.inProgress = true
|
|
||||||
this.showManageExtensionDialog = false
|
|
||||||
const paymentInfo = await this.requestPaymentForInstall(
|
|
||||||
this.selectedExtension.id,
|
|
||||||
release
|
|
||||||
)
|
|
||||||
this.rememberPaylinkHash(release.pay_link, paymentInfo.payment_hash)
|
|
||||||
const wallet = this.g.user.wallets.find(w => w.id === release.wallet)
|
|
||||||
const {data} = await LNbits.api.payInvoice(
|
|
||||||
wallet,
|
|
||||||
paymentInfo.payment_request
|
|
||||||
)
|
|
||||||
|
|
||||||
release.payment_hash = data.payment_hash
|
|
||||||
|
|
||||||
await this.installExtension(release)
|
|
||||||
} catch (err) {
|
|
||||||
console.warn(err)
|
|
||||||
LNbits.utils.notifyApiError(err)
|
|
||||||
} finally {
|
|
||||||
this.selectedExtension.inProgress = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async payAndEnable(extension) {
|
|
||||||
try {
|
|
||||||
const paymentInfo = await this.requestPaymentForEnable(
|
|
||||||
extension.id,
|
|
||||||
extension.payToEnable.paidAmount
|
|
||||||
)
|
|
||||||
|
|
||||||
const wallet = this.g.user.wallets.find(
|
|
||||||
w => w.id === extension.payToEnable.paymentWallet
|
|
||||||
)
|
|
||||||
const {data} = await LNbits.api.payInvoice(
|
|
||||||
wallet,
|
|
||||||
paymentInfo.payment_request
|
|
||||||
)
|
|
||||||
this.enableExtension(extension)
|
|
||||||
this.showPayToEnableDialog = false
|
|
||||||
} catch (err) {
|
|
||||||
console.warn(err)
|
|
||||||
LNbits.utils.notifyApiError(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async showInstallQRCode(release) {
|
|
||||||
this.selectedRelease = release
|
|
||||||
|
|
||||||
try {
|
|
||||||
const data = await this.requestPaymentForInstall(
|
|
||||||
this.selectedExtension.id,
|
|
||||||
release
|
|
||||||
)
|
|
||||||
|
|
||||||
this.selectedRelease.paymentRequest = data.payment_request
|
|
||||||
this.selectedRelease.payment_hash = data.payment_hash
|
|
||||||
this.selectedRelease = _.clone(this.selectedRelease)
|
|
||||||
this.rememberPaylinkHash(
|
|
||||||
this.selectedRelease.pay_link,
|
|
||||||
this.selectedRelease.payment_hash
|
|
||||||
)
|
|
||||||
|
|
||||||
this.subscribeToPaylinkWs(
|
|
||||||
this.selectedRelease.pay_link,
|
|
||||||
data.payment_hash
|
|
||||||
)
|
|
||||||
} catch (err) {
|
|
||||||
console.warn(err)
|
|
||||||
LNbits.utils.notifyApiError(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async showEnableQRCode(extension) {
|
|
||||||
try {
|
|
||||||
extension.payToEnable.showQRCode = true
|
|
||||||
this.selectedExtension = _.clone(extension)
|
|
||||||
|
|
||||||
const data = await this.requestPaymentForEnable(
|
|
||||||
extension.id,
|
|
||||||
extension.payToEnable.paidAmount
|
|
||||||
)
|
|
||||||
extension.payToEnable.paymentRequest = data.payment_request
|
|
||||||
this.selectedExtension = _.clone(extension)
|
|
||||||
|
|
||||||
const url = new URL(window.location)
|
|
||||||
url.protocol = url.protocol === 'https:' ? 'wss' : 'ws'
|
|
||||||
url.pathname = `/api/v1/ws/${data.payment_hash}`
|
|
||||||
const ws = new WebSocket(url)
|
|
||||||
ws.addEventListener('message', async ({data}) => {
|
|
||||||
const payment = JSON.parse(data)
|
|
||||||
if (payment.pending === false) {
|
|
||||||
Quasar.Notify.create({
|
|
||||||
type: 'positive',
|
|
||||||
message: 'Invoice Paid!'
|
|
||||||
})
|
|
||||||
|
|
||||||
this.enableExtension(extension)
|
|
||||||
ws.close()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (err) {
|
|
||||||
console.warn(err)
|
|
||||||
LNbits.utils.notifyApiError(err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
async requestPaymentForInstall(extId, release) {
|
|
||||||
const {data} = await LNbits.api.request(
|
|
||||||
'PUT',
|
|
||||||
`/api/v1/extension/${extId}/invoice/install`,
|
|
||||||
this.g.user.wallets[0].adminkey,
|
|
||||||
{
|
|
||||||
ext_id: extId,
|
|
||||||
archive: release.archive,
|
|
||||||
source_repo: release.source_repo,
|
|
||||||
cost_sats: release.paidAmount,
|
|
||||||
version: release.version
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return data
|
|
||||||
},
|
|
||||||
|
|
||||||
async requestPaymentForEnable(extId, amount) {
|
|
||||||
const {data} = await LNbits.api.request(
|
|
||||||
'PUT',
|
|
||||||
`/api/v1/extension/${extId}/invoice/enable`,
|
|
||||||
this.g.user.wallets[0].adminkey,
|
|
||||||
{
|
|
||||||
amount
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return data
|
|
||||||
},
|
|
||||||
|
|
||||||
clearHangingInvoice(release) {
|
|
||||||
this.forgetPaylinkHash(release.pay_link)
|
|
||||||
release.payment_hash = null
|
|
||||||
},
|
|
||||||
|
|
||||||
rememberPaylinkHash(pay_link, payment_hash) {
|
|
||||||
this.$q.localStorage.set(
|
|
||||||
`lnbits.extensions.paylink.${pay_link}`,
|
|
||||||
payment_hash
|
|
||||||
)
|
|
||||||
},
|
|
||||||
getPaylinkHash(pay_link) {
|
|
||||||
return this.$q.localStorage.getItem(
|
|
||||||
`lnbits.extensions.paylink.${pay_link}`
|
|
||||||
)
|
|
||||||
},
|
|
||||||
forgetPaylinkHash(pay_link) {
|
|
||||||
this.$q.localStorage.remove(`lnbits.extensions.paylink.${pay_link}`)
|
|
||||||
},
|
|
||||||
subscribeToPaylinkWs(pay_link, payment_hash) {
|
|
||||||
const url = new URL(`${pay_link}/${payment_hash}`)
|
|
||||||
url.protocol = url.protocol === 'https:' ? 'wss' : 'ws'
|
|
||||||
this.paylinkWebsocket = new WebSocket(url)
|
|
||||||
this.paylinkWebsocket.addEventListener('message', async ({data}) => {
|
|
||||||
const resp = JSON.parse(data)
|
|
||||||
if (resp.paid) {
|
|
||||||
Quasar.Notify.create({
|
|
||||||
type: 'positive',
|
|
||||||
message: 'Invoice Paid!'
|
|
||||||
})
|
|
||||||
this.installExtension(this.selectedRelease)
|
|
||||||
} else {
|
|
||||||
Quasar.Notify.create({
|
|
||||||
type: 'warning',
|
|
||||||
message: 'Invoice tracking lost!'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
},
|
|
||||||
unsubscribeFromPaylinkWs() {
|
|
||||||
try {
|
|
||||||
this.paylinkWebsocket && this.paylinkWebsocket.close()
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(error)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
hasNewVersion: function (extension) {
|
|
||||||
if (extension.installedRelease && extension.latestRelease) {
|
|
||||||
return (
|
|
||||||
extension.installedRelease.version !==
|
|
||||||
extension.latestRelease.version
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
isInstalledVersion: function (extension, release) {
|
|
||||||
if (extension.installedRelease) {
|
|
||||||
return (
|
|
||||||
extension.installedRelease.source_repo === release.source_repo &&
|
|
||||||
extension.installedRelease.version === release.version
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getReleaseIcon: function (release) {
|
|
||||||
if (!release.is_version_compatible) return 'block'
|
|
||||||
if (release.isInstalled) return 'download_done'
|
|
||||||
|
|
||||||
return 'download'
|
|
||||||
},
|
|
||||||
getReleaseIconColor: function (release) {
|
|
||||||
if (!release.is_version_compatible) return 'text-red'
|
|
||||||
if (release.isInstalled) return 'text-green'
|
|
||||||
|
|
||||||
return ''
|
|
||||||
},
|
|
||||||
getGitHubReleaseDetails: async function (release) {
|
|
||||||
if (!release.is_github_release || release.loaded) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const [org, repo] = release.source_repo.split('/')
|
|
||||||
release.inProgress = true
|
|
||||||
try {
|
|
||||||
const {data} = await LNbits.api.request(
|
|
||||||
'GET',
|
|
||||||
`/api/v1/extension/release/${org}/${repo}/${release.version}`,
|
|
||||||
this.g.user.wallets[0].adminkey
|
|
||||||
)
|
|
||||||
release.loaded = true
|
|
||||||
release.is_version_compatible = data.is_version_compatible
|
|
||||||
release.min_lnbits_version = data.min_lnbits_version
|
|
||||||
release.warning = data.warning
|
|
||||||
} catch (error) {
|
|
||||||
console.warn(error)
|
|
||||||
release.error = error
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
} finally {
|
|
||||||
release.inProgress = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectAllUpdatableExtensionss: async function () {
|
|
||||||
this.updatableExtensions.forEach(e => (e.selectedForUpdate = true))
|
|
||||||
},
|
|
||||||
updateSelectedExtensions: async function () {
|
|
||||||
let count = 0
|
|
||||||
for (const ext of this.updatableExtensions) {
|
|
||||||
try {
|
|
||||||
if (!ext.selectedForUpdate) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ext.inProgress = true
|
|
||||||
await LNbits.api.request(
|
|
||||||
'POST',
|
|
||||||
`/api/v1/extension`,
|
|
||||||
this.g.user.wallets[0].adminkey,
|
|
||||||
{
|
|
||||||
ext_id: ext.id,
|
|
||||||
archive: ext.latestRelease.archive,
|
|
||||||
source_repo: ext.latestRelease.source_repo,
|
|
||||||
payment_hash: ext.latestRelease.payment_hash,
|
|
||||||
version: ext.latestRelease.version
|
|
||||||
}
|
|
||||||
)
|
|
||||||
count++
|
|
||||||
ext.isAvailable = true
|
|
||||||
ext.isInstalled = true
|
|
||||||
ext.isUpgraded = true
|
|
||||||
ext.inProgress = false
|
|
||||||
ext.installedRelease = ext.latestRelease
|
|
||||||
this.toggleExtension(ext)
|
|
||||||
} catch (err) {
|
|
||||||
console.warn(err)
|
|
||||||
Quasar.Notify.create({
|
|
||||||
type: 'negative',
|
|
||||||
message: `Failed to update ${ext.code}!`
|
|
||||||
})
|
|
||||||
} finally {
|
|
||||||
ext.inProgress = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Quasar.Notify.create({
|
|
||||||
type: 'positive',
|
|
||||||
message: `${count} extensions updated!`
|
|
||||||
})
|
|
||||||
this.showUpdateAllDialog = false
|
|
||||||
setTimeout(() => {
|
|
||||||
window.location.reload()
|
|
||||||
}, 2000)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
created: function () {
|
|
||||||
this.extensions = JSON.parse('{{extensions | tojson | safe}}').map(e => ({
|
|
||||||
...e,
|
|
||||||
inProgress: false,
|
|
||||||
selectedForUpdate: false
|
|
||||||
}))
|
|
||||||
this.filteredExtensions = this.extensions.concat([])
|
|
||||||
for (let i = 0; i < this.filteredExtensions.length; i++) {
|
|
||||||
if (this.filteredExtensions[i].isInstalled != false) {
|
|
||||||
this.handleTabChanged('installed')
|
|
||||||
this.tab = 'installed'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (window.user) {
|
|
||||||
this.user = LNbits.map.user(window.user)
|
|
||||||
}
|
|
||||||
this.updatableExtensions = this.extensions.filter(ext =>
|
|
||||||
this.hasNewVersion(ext)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
mixins: [windowMixin]
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,9 @@
|
||||||
{% extends "base.html" %}
|
{% if not ajax %} {% extends "base.html" %} {% endif %}
|
||||||
<!---->
|
<!---->
|
||||||
{% from "macros.jinja" import window_vars with context %}
|
{% from "macros.jinja" import window_vars with context %}
|
||||||
<!---->
|
<!---->
|
||||||
{% block scripts %} {{ window_vars(user, wallet) }}
|
{% block scripts %} {{ window_vars(user, wallet) }}{% endblock %} {% block page
|
||||||
<script src="{{ static_url_for('static', 'js/wallet.js') }}"></script>
|
%}
|
||||||
{% endblock %}
|
|
||||||
<!---->
|
|
||||||
{% block title %}{{ wallet_name }} - {{ SITE_TITLE }} {% endblock %}
|
|
||||||
<!---->
|
|
||||||
{% block page %}
|
|
||||||
<div class="row q-col-gutter-md">
|
<div class="row q-col-gutter-md">
|
||||||
{% if HIDE_API and AD_SPACE %}
|
{% if HIDE_API and AD_SPACE %}
|
||||||
<div class="col-12 col-md-8 q-gutter-y-md">
|
<div class="col-12 col-md-8 q-gutter-y-md">
|
||||||
|
|
@ -25,68 +20,6 @@
|
||||||
} : ''"
|
} : ''"
|
||||||
>
|
>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<q-scroll-area
|
|
||||||
v-if="!mobileSimple"
|
|
||||||
style="
|
|
||||||
height: 115px;
|
|
||||||
width: 100%;
|
|
||||||
overflow-x: auto;
|
|
||||||
overflow-y: hidden;
|
|
||||||
"
|
|
||||||
>
|
|
||||||
<div class="row no-wrap q-gutter-md q-pr-md">
|
|
||||||
<q-card
|
|
||||||
v-for="wallet in g.user.wallets"
|
|
||||||
:key="wallet.id"
|
|
||||||
class="wallet-list-card"
|
|
||||||
bordered
|
|
||||||
tag="a"
|
|
||||||
:href="wallet.url"
|
|
||||||
:style="
|
|
||||||
g.wallet && g.wallet.id === wallet.id
|
|
||||||
? `border: 1px solid ${primaryColor}; width: 250px; text-decoration: none;`
|
|
||||||
: 'width: 250px; text-decoration: none;'
|
|
||||||
"
|
|
||||||
:class="{
|
|
||||||
'active-wallet-card': g.wallet && g.wallet.id === wallet.id
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<q-card-section>
|
|
||||||
<div class="row items-center">
|
|
||||||
<q-avatar
|
|
||||||
size="lg"
|
|
||||||
:color="
|
|
||||||
g.wallet && g.wallet.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 ? 'black' : 'grey-3'"
|
|
||||||
></q-icon>
|
|
||||||
</q-avatar>
|
|
||||||
<div
|
|
||||||
class="text-h6 q-pl-md"
|
|
||||||
:class="{
|
|
||||||
'text-bold': g.wallet && g.wallet.id === wallet.id
|
|
||||||
}"
|
|
||||||
v-text="wallet.name"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
<div class="row items-center q-pt-sm">
|
|
||||||
<h6 class="q-my-none text-no-wrap">
|
|
||||||
<strong v-text="wallet.fsat"></strong>
|
|
||||||
<small> {{LNBITS_DENOMINATION}}</small>
|
|
||||||
</h6>
|
|
||||||
</div>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</div>
|
|
||||||
</q-scroll-area>
|
|
||||||
<q-card
|
<q-card
|
||||||
:style="$q.screen.lt.md ? {
|
:style="$q.screen.lt.md ? {
|
||||||
background: $q.screen.lt.md ? 'none !important': ''
|
background: $q.screen.lt.md ? 'none !important': ''
|
||||||
|
|
@ -101,7 +34,6 @@
|
||||||
<small> {{LNBITS_DENOMINATION}}</small>
|
<small> {{LNBITS_DENOMINATION}}</small>
|
||||||
<lnbits-update-balance
|
<lnbits-update-balance
|
||||||
:wallet_id="this.g.wallet.id"
|
:wallet_id="this.g.wallet.id"
|
||||||
@credit-value="handleBalanceUpdate"
|
|
||||||
class="q-ml-md"
|
class="q-ml-md"
|
||||||
></lnbits-update-balance>
|
></lnbits-update-balance>
|
||||||
</h3>
|
</h3>
|
||||||
|
|
@ -117,7 +49,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<q-btn
|
<q-btn
|
||||||
@click="mobileSimple = !mobileSimple"
|
@click="simpleMobile()"
|
||||||
color="primary"
|
color="primary"
|
||||||
class="float-right lt-md"
|
class="float-right lt-md"
|
||||||
size="sm"
|
size="sm"
|
||||||
|
|
@ -176,11 +108,15 @@
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<payment-list
|
<payment-list
|
||||||
:update="updatePayments"
|
:update="updatePayments"
|
||||||
:wallet="this.g.wallet"
|
|
||||||
:mobile-simple="mobileSimple"
|
:mobile-simple="mobileSimple"
|
||||||
/>
|
/>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
<div id="hiddenQrCodeContainer" style="display: none">
|
||||||
|
<lnbits-qrcode
|
||||||
|
:value="'lightning:' + this.receive.paymentReq"
|
||||||
|
></lnbits-qrcode>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if HIDE_API %}
|
{% if HIDE_API %}
|
||||||
<div class="col-12 col-md-4 q-gutter-y-md">
|
<div class="col-12 col-md-4 q-gutter-y-md">
|
||||||
|
|
@ -192,8 +128,8 @@
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<h6 class="text-subtitle1 q-mt-none q-mb-sm">
|
<h6 class="text-subtitle1 q-mt-none q-mb-sm">
|
||||||
{{ SITE_TITLE }} <span v-text="$t('wallet')"></span>
|
{{ SITE_TITLE }} Wallet:
|
||||||
<strong><em>{{wallet_name}}</em></strong>
|
<strong><em v-text="g.wallet.name"></em></strong>
|
||||||
</h6>
|
</h6>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-section class="q-pa-none">
|
<q-card-section class="q-pa-none">
|
||||||
|
|
@ -228,7 +164,9 @@
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section class="text-center">
|
<q-card-section class="text-center">
|
||||||
<p v-text="$t('export_to_phone_desc')"></p>
|
<p v-text="$t('export_to_phone_desc')"></p>
|
||||||
<lnbits-qrcode :value="exportUrl"></lnbits-qrcode>
|
<lnbits-qrcode
|
||||||
|
:value="`${baseUrl}/wallet?usr=${g.user.id}&wal=${g.wallet.id}`"
|
||||||
|
></lnbits-qrcode>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<span v-text="exportWalletQR"></span>
|
<span v-text="exportWalletQR"></span>
|
||||||
<q-card-actions class="flex-center q-pb-md">
|
<q-card-actions class="flex-center q-pb-md">
|
||||||
|
|
@ -236,7 +174,7 @@
|
||||||
outline
|
outline
|
||||||
color="grey"
|
color="grey"
|
||||||
:label="$t('copy_wallet_url')"
|
:label="$t('copy_wallet_url')"
|
||||||
@click="copyText(exportUrl)"
|
@click="copyText(`${baseUrl}/wallet?usr=${g.user.id}&wal=${g.wallet.id}`)"
|
||||||
></q-btn>
|
></q-btn>
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
@ -295,6 +233,17 @@
|
||||||
:label="$t('update_currency')"
|
:label="$t('update_currency')"
|
||||||
@click="updateWallet({ currency: update.currency || '' })"
|
@click="updateWallet({ currency: update.currency || '' })"
|
||||||
></q-btn>
|
></q-btn>
|
||||||
|
<q-btn
|
||||||
|
v-if="g.user.admin"
|
||||||
|
class="absolute-top-right"
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
icon="settings"
|
||||||
|
to="/admin#exchange_providers"
|
||||||
|
><q-tooltip
|
||||||
|
v-text="$t('exchange_providers')"
|
||||||
|
></q-tooltip
|
||||||
|
></q-btn>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-expansion-item>
|
</q-expansion-item>
|
||||||
|
|
@ -445,9 +394,7 @@
|
||||||
>
|
>
|
||||||
<div class="text-center q-mb-lg">
|
<div class="text-center q-mb-lg">
|
||||||
<a :href="'lightning:' + receive.paymentReq">
|
<a :href="'lightning:' + receive.paymentReq">
|
||||||
<lnbits-qrcode
|
<div v-html="invoiceQrCode"></div>
|
||||||
:value="'lightning:' + receive.paymentReq.toUpperCase()"
|
|
||||||
></lnbits-qrcode>
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
{% if not ajax %} {% extends "base.html" %} {% endif %}
|
||||||
%} {% block page %}
|
<!---->
|
||||||
|
{% from "macros.jinja" import window_vars with context %}
|
||||||
|
<!---->
|
||||||
|
{% block scripts %} {{ window_vars(user) }}{% endblock %} {% block page %}
|
||||||
|
|
||||||
<q-dialog v-model="nodeInfoDialog.show" position="top">
|
<q-dialog v-model="nodeInfoDialog.show" position="top">
|
||||||
<lnbits-node-qrcode :info="nodeInfoDialog.data"></lnbits-node-qrcode>
|
<lnbits-node-qrcode :info="nodeInfoDialog.data"></lnbits-node-qrcode>
|
||||||
|
|
@ -40,364 +43,4 @@
|
||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
|
||||||
<script>
|
|
||||||
window.app = Vue.createApp({
|
|
||||||
el: '#vue',
|
|
||||||
config: {
|
|
||||||
globalProperties: {
|
|
||||||
LNbits,
|
|
||||||
msg: 'hello'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mixins: [window.windowMixin],
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
isSuperUser: false,
|
|
||||||
wallet: {},
|
|
||||||
tab: 'dashboard',
|
|
||||||
payments: 1000,
|
|
||||||
info: {},
|
|
||||||
channel_stats: {},
|
|
||||||
|
|
||||||
channels: {
|
|
||||||
data: [],
|
|
||||||
filter: ''
|
|
||||||
},
|
|
||||||
|
|
||||||
activeBalance: {},
|
|
||||||
ranks: {},
|
|
||||||
|
|
||||||
peers: {
|
|
||||||
data: [],
|
|
||||||
filter: ''
|
|
||||||
},
|
|
||||||
|
|
||||||
connectPeerDialog: {
|
|
||||||
show: false,
|
|
||||||
data: {}
|
|
||||||
},
|
|
||||||
|
|
||||||
setFeeDialog: {
|
|
||||||
show: false,
|
|
||||||
data: {
|
|
||||||
fee_ppm: 0,
|
|
||||||
fee_base_msat: 0
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
openChannelDialog: {
|
|
||||||
show: false,
|
|
||||||
data: {}
|
|
||||||
},
|
|
||||||
|
|
||||||
closeChannelDialog: {
|
|
||||||
show: false,
|
|
||||||
data: {}
|
|
||||||
},
|
|
||||||
|
|
||||||
nodeInfoDialog: {
|
|
||||||
show: false,
|
|
||||||
data: {}
|
|
||||||
},
|
|
||||||
|
|
||||||
transactionDetailsDialog: {
|
|
||||||
show: false,
|
|
||||||
data: {}
|
|
||||||
},
|
|
||||||
|
|
||||||
states: [
|
|
||||||
{label: 'Active', value: 'active', color: 'green'},
|
|
||||||
{label: 'Pending', value: 'pending', color: 'orange'},
|
|
||||||
{label: 'Inactive', value: 'inactive', color: 'grey'},
|
|
||||||
{label: 'Closed', value: 'closed', color: 'red'}
|
|
||||||
],
|
|
||||||
|
|
||||||
stateFilters: [
|
|
||||||
{label: 'Active', value: 'active'},
|
|
||||||
{label: 'Pending', value: 'pending'}
|
|
||||||
],
|
|
||||||
|
|
||||||
paymentsTable: {
|
|
||||||
data: [],
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
name: 'pending',
|
|
||||||
label: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'date',
|
|
||||||
align: 'left',
|
|
||||||
label: this.$t('date'),
|
|
||||||
field: 'date',
|
|
||||||
sortable: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'sat',
|
|
||||||
align: 'right',
|
|
||||||
label: this.$t('amount') + ' (' + LNBITS_DENOMINATION + ')',
|
|
||||||
field: row => this.formatMsat(row.amount),
|
|
||||||
sortable: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'fee',
|
|
||||||
align: 'right',
|
|
||||||
label: this.$t('fee') + ' (m' + LNBITS_DENOMINATION + ')',
|
|
||||||
field: 'fee'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'destination',
|
|
||||||
align: 'right',
|
|
||||||
label: 'Destination',
|
|
||||||
field: 'destination'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'memo',
|
|
||||||
align: 'left',
|
|
||||||
label: this.$t('memo'),
|
|
||||||
field: 'memo'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
pagination: {
|
|
||||||
rowsPerPage: 10,
|
|
||||||
page: 1,
|
|
||||||
rowsNumber: 10
|
|
||||||
},
|
|
||||||
filter: null
|
|
||||||
},
|
|
||||||
invoiceTable: {
|
|
||||||
data: [],
|
|
||||||
columns: [
|
|
||||||
{
|
|
||||||
name: 'pending',
|
|
||||||
label: ''
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'paid_at',
|
|
||||||
field: 'paid_at',
|
|
||||||
align: 'left',
|
|
||||||
label: 'Paid at',
|
|
||||||
sortable: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'expiry',
|
|
||||||
label: this.$t('expiry'),
|
|
||||||
field: 'expiry',
|
|
||||||
align: 'left',
|
|
||||||
sortable: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'amount',
|
|
||||||
label: this.$t('amount') + ' (' + LNBITS_DENOMINATION + ')',
|
|
||||||
field: row => this.formatMsat(row.amount),
|
|
||||||
sortable: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'memo',
|
|
||||||
align: 'left',
|
|
||||||
label: this.$t('memo'),
|
|
||||||
field: 'memo'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
pagination: {
|
|
||||||
rowsPerPage: 10,
|
|
||||||
page: 1,
|
|
||||||
rowsNumber: 10
|
|
||||||
},
|
|
||||||
filter: null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.getInfo()
|
|
||||||
this.get1MLStats()
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
tab(val) {
|
|
||||||
if (val === 'transactions' && !this.paymentsTable.data.length) {
|
|
||||||
this.getPayments()
|
|
||||||
this.getInvoices()
|
|
||||||
} else if (val === 'channels' && !this.channels.data.length) {
|
|
||||||
this.getChannels()
|
|
||||||
this.getPeers()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
checkChanges() {
|
|
||||||
return !_.isEqual(this.settings, this.formData)
|
|
||||||
},
|
|
||||||
filteredChannels() {
|
|
||||||
return this.stateFilters
|
|
||||||
? this.channels.data.filter(channel => {
|
|
||||||
return this.stateFilters.find(({value}) => value == channel.state)
|
|
||||||
})
|
|
||||||
: this.channels.data
|
|
||||||
},
|
|
||||||
totalBalance() {
|
|
||||||
return this.filteredChannels.reduce(
|
|
||||||
(balance, channel) => {
|
|
||||||
balance.local_msat += channel.balance.local_msat
|
|
||||||
balance.remote_msat += channel.balance.remote_msat
|
|
||||||
balance.total_msat += channel.balance.total_msat
|
|
||||||
return balance
|
|
||||||
},
|
|
||||||
{local_msat: 0, remote_msat: 0, total_msat: 0}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
formatMsat(msat) {
|
|
||||||
return LNbits.utils.formatMsat(msat)
|
|
||||||
},
|
|
||||||
api(method, url, options) {
|
|
||||||
const params = new URLSearchParams(options?.query)
|
|
||||||
return LNbits.api
|
|
||||||
.request(method, `/node/api/v1${url}?${params}`, {}, options?.data)
|
|
||||||
.catch(error => {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getChannel(channel_id) {
|
|
||||||
return this.api('GET', `/channels/${channel_id}`).then(response => {
|
|
||||||
this.setFeeDialog.data.fee_ppm = response.data.fee_ppm
|
|
||||||
this.setFeeDialog.data.fee_base_msat = response.data.fee_base_msat
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getChannels() {
|
|
||||||
return this.api('GET', '/channels').then(response => {
|
|
||||||
this.channels.data = response.data
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getInfo() {
|
|
||||||
return this.api('GET', '/info').then(response => {
|
|
||||||
this.info = response.data
|
|
||||||
this.channel_stats = response.data.channel_stats
|
|
||||||
})
|
|
||||||
},
|
|
||||||
get1MLStats() {
|
|
||||||
return this.api('GET', '/rank').then(response => {
|
|
||||||
this.ranks = response.data
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getPayments(props) {
|
|
||||||
if (props) {
|
|
||||||
this.paymentsTable.pagination = props.pagination
|
|
||||||
}
|
|
||||||
let pagination = this.paymentsTable.pagination
|
|
||||||
const query = {
|
|
||||||
limit: pagination.rowsPerPage,
|
|
||||||
offset: (pagination.page - 1) * pagination.rowsPerPage ?? 0
|
|
||||||
}
|
|
||||||
return this.api('GET', '/payments', {query}).then(response => {
|
|
||||||
this.paymentsTable.data = response.data.data
|
|
||||||
this.paymentsTable.pagination.rowsNumber = response.data.total
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getInvoices(props) {
|
|
||||||
if (props) {
|
|
||||||
this.invoiceTable.pagination = props.pagination
|
|
||||||
}
|
|
||||||
let pagination = this.invoiceTable.pagination
|
|
||||||
const query = {
|
|
||||||
limit: pagination.rowsPerPage,
|
|
||||||
offset: (pagination.page - 1) * pagination.rowsPerPage ?? 0
|
|
||||||
}
|
|
||||||
return this.api('GET', '/invoices', {query}).then(response => {
|
|
||||||
this.invoiceTable.data = response.data.data
|
|
||||||
this.invoiceTable.pagination.rowsNumber = response.data.total
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getPeers() {
|
|
||||||
return this.api('GET', '/peers').then(response => {
|
|
||||||
this.peers.data = response.data
|
|
||||||
console.log('peers', this.peers)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
connectPeer() {
|
|
||||||
this.api('POST', '/peers', {data: this.connectPeerDialog.data}).then(
|
|
||||||
() => {
|
|
||||||
this.connectPeerDialog.show = false
|
|
||||||
this.getPeers()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
},
|
|
||||||
disconnectPeer(id) {
|
|
||||||
LNbits.utils
|
|
||||||
.confirmDialog('Do you really wanna disconnect this peer?')
|
|
||||||
.onOk(() => {
|
|
||||||
this.api('DELETE', `/peers/${id}`).then(response => {
|
|
||||||
Quasar.Notify.create({
|
|
||||||
message: 'Disconnected',
|
|
||||||
icon: null
|
|
||||||
})
|
|
||||||
this.needsRestart = true
|
|
||||||
this.getPeers()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
setChannelFee(channel_id) {
|
|
||||||
this.api('PUT', `/channels/${channel_id}`, {
|
|
||||||
data: this.setFeeDialog.data
|
|
||||||
})
|
|
||||||
.then(response => {
|
|
||||||
this.setFeeDialog.show = false
|
|
||||||
this.getChannels()
|
|
||||||
})
|
|
||||||
.catch(LNbits.utils.notifyApiError)
|
|
||||||
},
|
|
||||||
openChannel() {
|
|
||||||
this.api('POST', '/channels', {data: this.openChannelDialog.data})
|
|
||||||
.then(response => {
|
|
||||||
this.openChannelDialog.show = false
|
|
||||||
this.getChannels()
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.log(error)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
showCloseChannelDialog(channel) {
|
|
||||||
this.closeChannelDialog.show = true
|
|
||||||
this.closeChannelDialog.data = {
|
|
||||||
force: false,
|
|
||||||
short_id: channel.short_id,
|
|
||||||
...channel.point
|
|
||||||
}
|
|
||||||
},
|
|
||||||
closeChannel() {
|
|
||||||
this.api('DELETE', '/channels', {
|
|
||||||
query: this.closeChannelDialog.data
|
|
||||||
}).then(response => {
|
|
||||||
this.closeChannelDialog.show = false
|
|
||||||
this.getChannels()
|
|
||||||
})
|
|
||||||
},
|
|
||||||
showSetFeeDialog(channel_id) {
|
|
||||||
this.setFeeDialog.show = true
|
|
||||||
this.setFeeDialog.channel_id = channel_id
|
|
||||||
this.getChannel(channel_id)
|
|
||||||
},
|
|
||||||
showOpenChannelDialog(peer_id) {
|
|
||||||
this.openChannelDialog.show = true
|
|
||||||
this.openChannelDialog.data = {peer_id, funding_amount: 0}
|
|
||||||
},
|
|
||||||
showNodeInfoDialog(node) {
|
|
||||||
this.nodeInfoDialog.show = true
|
|
||||||
this.nodeInfoDialog.data = node
|
|
||||||
},
|
|
||||||
showTransactionDetailsDialog(details) {
|
|
||||||
this.transactionDetailsDialog.show = true
|
|
||||||
this.transactionDetailsDialog.data = details
|
|
||||||
console.log('details', details)
|
|
||||||
},
|
|
||||||
shortenNodeId(nodeId) {
|
|
||||||
return nodeId
|
|
||||||
? nodeId.substring(0, 5) + '...' + nodeId.substring(nodeId.length - 5)
|
|
||||||
: '...'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
<script src="{{ static_url_for('static', 'js/node.js') }}"></script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
{% extends "base.html" %} {% from "macros.jinja" import window_vars with context
|
{% if not ajax %} {% extends "base.html" %} {% endif %}
|
||||||
%} {% block page %}
|
<!---->
|
||||||
|
{% from "macros.jinja" import window_vars with context %}
|
||||||
|
<!---->
|
||||||
|
{% block scripts %} {{ window_vars(user) }}{% endblock %} {% block page %}
|
||||||
|
|
||||||
<div class="row q-col-gutter-md justify-center">
|
<div class="row q-col-gutter-md justify-center">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
|
|
@ -11,13 +14,35 @@
|
||||||
{%include "users/_createWalletDialog.html" %}
|
{%include "users/_createWalletDialog.html" %}
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<q-btn
|
<div class="row q-col-gutter-md q-mb-md">
|
||||||
@click="showAccountPage()"
|
<div class="col-12">
|
||||||
:label="$t('create_account')"
|
<q-card>
|
||||||
color="primary"
|
<div class="q-pa-sm">
|
||||||
class="q-mb-md"
|
<div class="row items-center justify-between q-gutter-xs">
|
||||||
>
|
<div class="col">
|
||||||
</q-btn>
|
<q-btn
|
||||||
|
@click="showAccountPage()"
|
||||||
|
:label="$t('create_account')"
|
||||||
|
color="primary"
|
||||||
|
>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<q-btn
|
||||||
|
v-if="g.user.admin"
|
||||||
|
flat
|
||||||
|
round
|
||||||
|
icon="settings"
|
||||||
|
to="/admin#users"
|
||||||
|
>
|
||||||
|
<q-tooltip v-text="$t('admin_settings')"></q-tooltip>
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<q-card class="q-pa-md">
|
<q-card class="q-pa-md">
|
||||||
<q-table
|
<q-table
|
||||||
|
|
@ -148,6 +173,4 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% endblock %} {% block scripts %} {{ window_vars(user) }}
|
|
||||||
<script src="{{ static_url_for('static', 'js/users.js') }}"></script>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -107,7 +107,7 @@ async def extensions(request: Request, user: User = Depends(check_user_exists)):
|
||||||
inactive_extensions = [e.id for e in await get_installed_extensions(active=False)]
|
inactive_extensions = [e.id for e in await get_installed_extensions(active=False)]
|
||||||
db_versions = await get_db_versions()
|
db_versions = await get_db_versions()
|
||||||
|
|
||||||
extensions = [
|
extension_data = [
|
||||||
{
|
{
|
||||||
"id": ext.id,
|
"id": ext.id,
|
||||||
"name": ext.name,
|
"name": ext.name,
|
||||||
|
|
@ -152,7 +152,8 @@ async def extensions(request: Request, user: User = Depends(check_user_exists)):
|
||||||
"core/extensions.html",
|
"core/extensions.html",
|
||||||
{
|
{
|
||||||
"user": user.json(),
|
"user": user.json(),
|
||||||
"extensions": extensions,
|
"extension_data": extension_data,
|
||||||
|
"ajax": _is_ajax_request(request),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -182,22 +183,21 @@ async def wallet(
|
||||||
return template_renderer().TemplateResponse(
|
return template_renderer().TemplateResponse(
|
||||||
request, "error.html", {"err": "Wallet not found"}, HTTPStatus.NOT_FOUND
|
request, "error.html", {"err": "Wallet not found"}, HTTPStatus.NOT_FOUND
|
||||||
)
|
)
|
||||||
|
context = {
|
||||||
|
"user": user.json(),
|
||||||
|
"wallet": wallet.json(),
|
||||||
|
"wallet_name": wallet.name,
|
||||||
|
"currencies": allowed_currencies(),
|
||||||
|
"service_fee": settings.lnbits_service_fee,
|
||||||
|
"service_fee_max": settings.lnbits_service_fee_max,
|
||||||
|
"web_manifest": f"/manifest/{user.id}.webmanifest",
|
||||||
|
}
|
||||||
|
|
||||||
resp = template_renderer().TemplateResponse(
|
return template_renderer().TemplateResponse(
|
||||||
request,
|
request,
|
||||||
"core/wallet.html",
|
"core/wallet.html",
|
||||||
{
|
{**context, "ajax": _is_ajax_request(request)},
|
||||||
"user": user.json(),
|
|
||||||
"wallet": wallet.json(),
|
|
||||||
"wallet_name": wallet.name,
|
|
||||||
"currencies": allowed_currencies(),
|
|
||||||
"service_fee": settings.lnbits_service_fee,
|
|
||||||
"service_fee_max": settings.lnbits_service_fee_max,
|
|
||||||
"web_manifest": f"/manifest/{user.id}.webmanifest",
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
resp.set_cookie("lnbits_last_active_wallet", wallet.id)
|
|
||||||
return resp
|
|
||||||
|
|
||||||
|
|
||||||
@generic_router.get(
|
@generic_router.get(
|
||||||
|
|
@ -209,11 +209,13 @@ async def account(
|
||||||
request: Request,
|
request: Request,
|
||||||
user: User = Depends(check_user_exists),
|
user: User = Depends(check_user_exists),
|
||||||
):
|
):
|
||||||
|
|
||||||
return template_renderer().TemplateResponse(
|
return template_renderer().TemplateResponse(
|
||||||
request,
|
request,
|
||||||
"core/account.html",
|
"core/account.html",
|
||||||
{
|
{
|
||||||
"user": user.json(),
|
"user": user.json(),
|
||||||
|
"ajax": _is_ajax_request(request),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -327,6 +329,7 @@ async def node(request: Request, user: User = Depends(check_admin)):
|
||||||
"settings": settings.dict(),
|
"settings": settings.dict(),
|
||||||
"balance": balance,
|
"balance": balance,
|
||||||
"wallets": user.wallets[0].json(),
|
"wallets": user.wallets[0].json(),
|
||||||
|
"ajax": _is_ajax_request(request),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -365,6 +368,7 @@ async def admin_index(request: Request, user: User = Depends(check_admin)):
|
||||||
"settings": settings.dict(),
|
"settings": settings.dict(),
|
||||||
"balance": balance,
|
"balance": balance,
|
||||||
"currencies": list(currencies.keys()),
|
"currencies": list(currencies.keys()),
|
||||||
|
"ajax": _is_ajax_request(request),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -381,6 +385,7 @@ async def users_index(request: Request, user: User = Depends(check_admin)):
|
||||||
"user": user.json(),
|
"user": user.json(),
|
||||||
"settings": settings.dict(),
|
"settings": settings.dict(),
|
||||||
"currencies": list(currencies.keys()),
|
"currencies": list(currencies.keys()),
|
||||||
|
"ajax": _is_ajax_request(request),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -395,6 +400,7 @@ async def audit_index(request: Request, user: User = Depends(check_admin)):
|
||||||
{
|
{
|
||||||
"request": request,
|
"request": request,
|
||||||
"user": user.json(),
|
"user": user.json(),
|
||||||
|
"ajax": _is_ajax_request(request),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -458,3 +464,7 @@ async def lnurlwallet(request: Request):
|
||||||
return RedirectResponse(
|
return RedirectResponse(
|
||||||
f"/wallet?usr={account.id}&wal={wallet.id}",
|
f"/wallet?usr={account.id}&wal={wallet.id}",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _is_ajax_request(request: Request):
|
||||||
|
return request.headers.get("X-Requested-With", None) == "XMLHttpRequest"
|
||||||
|
|
|
||||||
2
lnbits/static/bundle-components.min.js
vendored
2
lnbits/static/bundle-components.min.js
vendored
File diff suppressed because one or more lines are too long
2
lnbits/static/bundle.min.js
vendored
2
lnbits/static/bundle.min.js
vendored
File diff suppressed because one or more lines are too long
|
|
@ -1,5 +1,4 @@
|
||||||
window.app = Vue.createApp({
|
window.AccountPageLogic = {
|
||||||
el: '#vue',
|
|
||||||
mixins: [window.windowMixin],
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -10,7 +9,8 @@ window.app = Vue.createApp({
|
||||||
'None',
|
'None',
|
||||||
'confettiBothSides',
|
'confettiBothSides',
|
||||||
'confettiFireworks',
|
'confettiFireworks',
|
||||||
'confettiStars'
|
'confettiStars',
|
||||||
|
'confettiTop'
|
||||||
],
|
],
|
||||||
borderOptions: ['retro-border', 'hard-border', 'no-border'],
|
borderOptions: ['retro-border', 'hard-border', 'no-border'],
|
||||||
tab: 'user',
|
tab: 'user',
|
||||||
|
|
@ -39,58 +39,12 @@ window.app = Vue.createApp({
|
||||||
this.toggleGradient()
|
this.toggleGradient()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
applyGradient() {
|
|
||||||
darkBgColor = this.$q.localStorage.getItem('lnbits.darkBgColor')
|
|
||||||
primaryColor = this.$q.localStorage.getItem('lnbits.primaryColor')
|
|
||||||
if (this.gradientChoice) {
|
|
||||||
if (!this.$q.dark.isActive) {
|
|
||||||
this.toggleDarkMode()
|
|
||||||
}
|
|
||||||
const gradientStyle = `linear-gradient(to bottom right, ${LNbits.utils.hexDarken(String(primaryColor), -70)}, #0a0a0a)`
|
|
||||||
document.body.style.setProperty(
|
|
||||||
'background-image',
|
|
||||||
gradientStyle,
|
|
||||||
'important'
|
|
||||||
)
|
|
||||||
const gradientStyleCards = `background-color: ${LNbits.utils.hexAlpha(String(darkBgColor), 0.4)} !important`
|
|
||||||
const style = document.createElement('style')
|
|
||||||
style.innerHTML =
|
|
||||||
`body[data-theme="${this.$q.localStorage.getItem('lnbits.theme')}"] .q-card:not(.q-dialog .q-card, .lnbits__dialog-card, .q-dialog-plugin--dark), body.body${this.$q.dark.isActive ? '--dark' : ''} .q-header, body.body${this.$q.dark.isActive ? '--dark' : ''} .q-drawer { ${gradientStyleCards} }` +
|
|
||||||
`body[data-theme="${this.$q.localStorage.getItem('lnbits.theme')}"].body--dark{background: ${LNbits.utils.hexDarken(String(primaryColor), -88)} !important; }` +
|
|
||||||
`[data-theme="${this.$q.localStorage.getItem('lnbits.theme')}"] .q-card--dark{background: ${String(darkBgColor)} !important;} }`
|
|
||||||
document.head.appendChild(style)
|
|
||||||
this.$q.localStorage.set('lnbits.gradientBg', true)
|
|
||||||
} else {
|
|
||||||
this.$q.localStorage.set('lnbits.gradientBg', false)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
applyBorder() {
|
|
||||||
if (this.borderChoice) {
|
|
||||||
this.$q.localStorage.setItem('lnbits.border', this.borderChoice)
|
|
||||||
}
|
|
||||||
let borderStyle = this.$q.localStorage.getItem('lnbits.border')
|
|
||||||
this.borderChoice = borderStyle
|
|
||||||
let borderStyleCSS
|
|
||||||
if (borderStyle == 'hard-border') {
|
|
||||||
borderStyleCSS = `box-shadow: 0 0 0 1px rgba(0,0,0,.12), 0 0 0 1px #ffffff47; border: none;`
|
|
||||||
}
|
|
||||||
if (borderStyle == 'no-border') {
|
|
||||||
borderStyleCSS = `box-shadow: none; border: none;`
|
|
||||||
}
|
|
||||||
if (borderStyle == 'retro-border') {
|
|
||||||
borderStyleCSS = `border: none; border-color: rgba(255, 255, 255, 0.28); box-shadow: 0 1px 5px rgba(255, 255, 255, 0.2), 0 2px 2px rgba(255, 255, 255, 0.14), 0 3px 1px -2px rgba(255, 255, 255, 0.12);`
|
|
||||||
}
|
|
||||||
let style = document.createElement('style')
|
|
||||||
style.innerHTML = `body[data-theme="${this.$q.localStorage.getItem('lnbits.theme')}"] .q-card.q-card--dark, .q-date--dark { ${borderStyleCSS} }`
|
|
||||||
document.head.appendChild(style)
|
|
||||||
},
|
|
||||||
toggleGradient() {
|
toggleGradient() {
|
||||||
this.gradientChoice = !this.gradientChoice
|
this.gradientChoice = !this.gradientChoice
|
||||||
this.applyGradient()
|
this.applyGradient()
|
||||||
if (!this.gradientChoice) {
|
if (!this.gradientChoice) {
|
||||||
window.location.reload()
|
window.location.reload()
|
||||||
}
|
}
|
||||||
this.gradientChoice = this.$q.localStorage.getItem('lnbits.gradientBg')
|
|
||||||
},
|
},
|
||||||
reactionChoiceFunc() {
|
reactionChoiceFunc() {
|
||||||
this.$q.localStorage.set('lnbits.reactions', this.reactionChoice)
|
this.$q.localStorage.set('lnbits.reactions', this.reactionChoice)
|
||||||
|
|
@ -208,15 +162,9 @@ window.app = Vue.createApp({
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
LNbits.utils.notifyApiError(e)
|
LNbits.utils.notifyApiError(e)
|
||||||
}
|
}
|
||||||
if (this.$q.localStorage.getItem('lnbits.gradientBg')) {
|
|
||||||
this.applyGradient()
|
|
||||||
}
|
|
||||||
if (this.$q.localStorage.getItem('lnbits.border')) {
|
|
||||||
this.applyBorder()
|
|
||||||
}
|
|
||||||
const hash = window.location.hash.replace('#', '')
|
const hash = window.location.hash.replace('#', '')
|
||||||
if (hash) {
|
if (hash) {
|
||||||
this.tab = hash
|
this.tab = hash
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
window.app = Vue.createApp({
|
window.AdminPageLogic = {
|
||||||
el: '#vue',
|
|
||||||
mixins: [windowMixin],
|
mixins: [windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -35,7 +34,10 @@ window.app = Vue.createApp({
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
formData: {},
|
formData: {
|
||||||
|
lnbits_exchange_rate_providers: []
|
||||||
|
},
|
||||||
|
chartReady: false,
|
||||||
formAddAdmin: '',
|
formAddAdmin: '',
|
||||||
formAddUser: '',
|
formAddUser: '',
|
||||||
formAddExtensionsManifest: '',
|
formAddExtensionsManifest: '',
|
||||||
|
|
@ -118,11 +120,14 @@ window.app = Vue.createApp({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
async created() {
|
||||||
this.getSettings()
|
await this.getSettings()
|
||||||
this.getAudit()
|
await this.getAudit()
|
||||||
this.balance = +'{{ balance|safe }}'
|
this.balance = +'{{ balance|safe }}'
|
||||||
const hash = window.location.hash.replace('#', '')
|
const hash = window.location.hash.replace('#', '')
|
||||||
|
if (hash === 'exchange_providers') {
|
||||||
|
this.showExchangeProvidersTab(hash)
|
||||||
|
}
|
||||||
if (hash) {
|
if (hash) {
|
||||||
this.tab = hash
|
this.tab = hash
|
||||||
}
|
}
|
||||||
|
|
@ -387,8 +392,8 @@ window.app = Vue.createApp({
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getAudit() {
|
async getAudit() {
|
||||||
LNbits.api
|
await LNbits.api
|
||||||
.request('GET', '/admin/api/v1/audit', this.g.user.wallets[0].adminkey)
|
.request('GET', '/admin/api/v1/audit', this.g.user.wallets[0].adminkey)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
this.auditData = response.data
|
this.auditData = response.data
|
||||||
|
|
@ -405,8 +410,8 @@ window.app = Vue.createApp({
|
||||||
LNbits.utils.notifyApiError(error)
|
LNbits.utils.notifyApiError(error)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
getSettings() {
|
async getSettings() {
|
||||||
LNbits.api
|
await LNbits.api
|
||||||
.request(
|
.request(
|
||||||
'GET',
|
'GET',
|
||||||
'/admin/api/v1/settings',
|
'/admin/api/v1/settings',
|
||||||
|
|
@ -513,4 +518,4 @@ window.app = Vue.createApp({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
window.app = Vue.createApp({
|
window.AuditPageLogic = {
|
||||||
el: '#vue',
|
|
||||||
mixins: [window.windowMixin],
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
chartsReady: false,
|
||||||
auditEntries: [],
|
auditEntries: [],
|
||||||
searchData: {
|
searchData: {
|
||||||
user_id: '',
|
user_id: '',
|
||||||
|
|
@ -96,12 +96,13 @@ window.app = Vue.createApp({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async created() {
|
async created() {},
|
||||||
|
async mounted() {
|
||||||
|
this.chartsReady = true // Allow the DOM to render the canvas elements
|
||||||
|
await this.$nextTick() // Wait for DOM updates before initializing charts
|
||||||
|
this.initCharts() // Initialize charts after DOM is ready
|
||||||
await this.fetchAudit()
|
await this.fetchAudit()
|
||||||
},
|
},
|
||||||
mounted() {
|
|
||||||
this.initCharts()
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
formatDate(dateString) {
|
formatDate(dateString) {
|
||||||
|
|
@ -208,7 +209,11 @@ window.app = Vue.createApp({
|
||||||
}
|
}
|
||||||
return `${value.substring(0, 5)}...${value.substring(valueLength - 5, valueLength)}`
|
return `${value.substring(0, 5)}...${value.substring(valueLength - 5, valueLength)}`
|
||||||
},
|
},
|
||||||
initCharts() {
|
async initCharts() {
|
||||||
|
if (!this.chartsReady) {
|
||||||
|
console.warn('Charts are not ready yet. Initialization delayed.')
|
||||||
|
return
|
||||||
|
}
|
||||||
this.responseCodeChart = new Chart(
|
this.responseCodeChart = new Chart(
|
||||||
this.$refs.responseCodeChart.getContext('2d'),
|
this.$refs.responseCodeChart.getContext('2d'),
|
||||||
{
|
{
|
||||||
|
|
@ -383,4 +388,4 @@ window.app = Vue.createApp({
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,5 @@
|
||||||
window.LOCALE = 'en'
|
|
||||||
window.dateFormat = 'YYYY-MM-DD HH:mm'
|
|
||||||
window.i18n = new VueI18n.createI18n({
|
|
||||||
locale: window.LOCALE,
|
|
||||||
fallbackLocale: window.LOCALE,
|
|
||||||
messages: window.localisation
|
|
||||||
})
|
|
||||||
|
|
||||||
const websocketPrefix =
|
|
||||||
window.location.protocol === 'http:' ? 'ws://' : 'wss://'
|
|
||||||
const websocketUrl = `${websocketPrefix}${window.location.host}/api/v1/ws`
|
|
||||||
|
|
||||||
window.LNbits = {
|
window.LNbits = {
|
||||||
|
g: window.g,
|
||||||
api: {
|
api: {
|
||||||
request(method, url, apiKey, data) {
|
request(method, url, apiKey, data) {
|
||||||
return axios({
|
return axios({
|
||||||
|
|
@ -448,39 +437,115 @@ window.LNbits = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!window.g) {
|
||||||
|
window.g = Vue.reactive({
|
||||||
|
offline: !navigator.onLine,
|
||||||
|
visibleDrawer: false,
|
||||||
|
extensions: [],
|
||||||
|
user: null,
|
||||||
|
wallet: {},
|
||||||
|
wallets: [],
|
||||||
|
payments: [],
|
||||||
|
allowedThemes: null,
|
||||||
|
langs: []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
window.windowMixin = {
|
window.windowMixin = {
|
||||||
|
inject: ['g'],
|
||||||
i18n: window.i18n,
|
i18n: window.i18n,
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
toggleSubs: true,
|
toggleSubs: true,
|
||||||
reactionChoice: 'confettiBothSides',
|
updatePayments: false,
|
||||||
|
updatePaymentsHash: '',
|
||||||
|
mobileSimple: true,
|
||||||
|
walletFlip: true,
|
||||||
|
reactionChoice: 'confettiTop',
|
||||||
borderChoice: '',
|
borderChoice: '',
|
||||||
gradientChoice:
|
gradientChoice:
|
||||||
this.$q.localStorage.getItem('lnbits.gradientBg') || false,
|
this.$q.localStorage.getItem('lnbits.gradientBg') || false,
|
||||||
isUserAuthorized: false,
|
isUserAuthorized: false,
|
||||||
g: {
|
eventListeners: []
|
||||||
offline: !navigator.onLine,
|
|
||||||
visibleDrawer: false,
|
|
||||||
extensions: [],
|
|
||||||
user: null,
|
|
||||||
wallet: null,
|
|
||||||
payments: [],
|
|
||||||
allowedThemes: null,
|
|
||||||
langs: []
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
flipWallets(smallScreen) {
|
||||||
|
this.walletFlip = !this.walletFlip
|
||||||
|
if (this.walletFlip && smallScreen) {
|
||||||
|
this.g.visibleDrawer = false
|
||||||
|
}
|
||||||
|
this.$q.localStorage.set('lnbits.walletFlip', this.walletFlip)
|
||||||
|
},
|
||||||
|
simpleMobile() {
|
||||||
|
this.$q.localStorage.set('lnbits.mobileSimple', !this.mobileSimple)
|
||||||
|
this.refreshRoute()
|
||||||
|
},
|
||||||
|
paymentEvents() {
|
||||||
|
this.g.user.wallets.forEach(wallet => {
|
||||||
|
if (!this.eventListeners.includes(wallet.id)) {
|
||||||
|
this.eventListeners.push(wallet.id)
|
||||||
|
LNbits.events.onInvoicePaid(wallet, data => {
|
||||||
|
const walletIndex = this.g.user.wallets.findIndex(
|
||||||
|
w => w.id === wallet.id
|
||||||
|
)
|
||||||
|
if (walletIndex !== -1) {
|
||||||
|
//needed for balance being deducted
|
||||||
|
let satBalance = data.wallet_balance
|
||||||
|
if (data.payment.amount < 0) {
|
||||||
|
satBalance = data.wallet_balance += data.payment.amount / 1000
|
||||||
|
}
|
||||||
|
//update the wallet
|
||||||
|
Object.assign(this.g.user.wallets[walletIndex], {
|
||||||
|
sat: satBalance,
|
||||||
|
msat: data.wallet_balance * 1000,
|
||||||
|
fsat: data.wallet_balance.toLocaleString()
|
||||||
|
})
|
||||||
|
//update the current wallet
|
||||||
|
if (this.g.wallet.id === data.payment.wallet_id) {
|
||||||
|
Object.assign(this.g.wallet, this.g.user.wallets[walletIndex])
|
||||||
|
this.updatePayments = !this.updatePayments
|
||||||
|
//if on the wallet page and payment is incoming trigger the eventReaction
|
||||||
|
if (
|
||||||
|
data.payment.amount > 0 &&
|
||||||
|
window.location.pathname === '/wallet'
|
||||||
|
) {
|
||||||
|
eventReaction(data.wallet_balance * 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.updatePaymentsHash = data.payment.payment_hash
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
selectWallet(wallet) {
|
||||||
|
Object.assign(this.g.wallet, wallet)
|
||||||
|
// this.wallet = this.g.wallet
|
||||||
|
this.updatePayments = !this.updatePayments
|
||||||
|
this.balance = parseInt(wallet.balance_msat / 1000)
|
||||||
|
const currentPath = this.$route.path
|
||||||
|
if (currentPath !== '/wallet') {
|
||||||
|
this.$router.push({
|
||||||
|
path: '/wallet',
|
||||||
|
query: {wal: this.g.wallet.id}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
const url = new URL(window.location.href)
|
||||||
|
url.searchParams.set('wal', this.g.wallet.id)
|
||||||
|
window.history.replaceState({}, '', url.toString())
|
||||||
|
}
|
||||||
|
},
|
||||||
changeColor(newValue) {
|
changeColor(newValue) {
|
||||||
document.body.setAttribute('data-theme', newValue)
|
document.body.setAttribute('data-theme', newValue)
|
||||||
this.$q.localStorage.set('lnbits.theme', newValue)
|
this.$q.localStorage.set('lnbits.theme', newValue)
|
||||||
},
|
},
|
||||||
applyGradient() {
|
applyGradient() {
|
||||||
if (this.$q.localStorage.getItem('lnbits.gradientBg')) {
|
darkBgColor = this.$q.localStorage.getItem('lnbits.darkBgColor')
|
||||||
this.setColors()
|
primaryColor = this.$q.localStorage.getItem('lnbits.primaryColor')
|
||||||
darkBgColor = this.$q.localStorage.getItem('lnbits.darkBgColor')
|
if (this.gradientChoice) {
|
||||||
primaryColor = this.$q.localStorage.getItem('lnbits.primaryColor')
|
this.$q.localStorage.set('lnbits.gradientBg', true)
|
||||||
const gradientStyle = `linear-gradient(to bottom right, ${LNbits.utils.hexDarken(String(primaryColor), -70)}, #0a0a0a)`
|
const gradientStyle = `linear-gradient(to bottom right, ${LNbits.utils.hexDarken(String(primaryColor), -70)}, #0a0a0a)`
|
||||||
document.body.style.setProperty(
|
document.body.style.setProperty(
|
||||||
'background-image',
|
'background-image',
|
||||||
|
|
@ -494,6 +559,11 @@ window.windowMixin = {
|
||||||
`body[data-theme="${this.$q.localStorage.getItem('lnbits.theme')}"].body--dark{background: ${LNbits.utils.hexDarken(String(primaryColor), -88)} !important; }` +
|
`body[data-theme="${this.$q.localStorage.getItem('lnbits.theme')}"].body--dark{background: ${LNbits.utils.hexDarken(String(primaryColor), -88)} !important; }` +
|
||||||
`[data-theme="${this.$q.localStorage.getItem('lnbits.theme')}"] .q-card--dark{background: ${String(darkBgColor)} !important;} }`
|
`[data-theme="${this.$q.localStorage.getItem('lnbits.theme')}"] .q-card--dark{background: ${String(darkBgColor)} !important;} }`
|
||||||
document.head.appendChild(style)
|
document.head.appendChild(style)
|
||||||
|
} else {
|
||||||
|
this.$q.localStorage.set('lnbits.gradientBg', false)
|
||||||
|
}
|
||||||
|
if (!this.$q.dark.isActive) {
|
||||||
|
this.toggleDarkMode()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
applyBorder() {
|
applyBorder() {
|
||||||
|
|
@ -502,7 +572,7 @@ window.windowMixin = {
|
||||||
}
|
}
|
||||||
let borderStyle = this.$q.localStorage.getItem('lnbits.border')
|
let borderStyle = this.$q.localStorage.getItem('lnbits.border')
|
||||||
if (!borderStyle) {
|
if (!borderStyle) {
|
||||||
this.$q.localStorage.set('lnbits.border', 'retro-border')
|
this.$q.localStorage.set('lnbits.border', 'hard-border')
|
||||||
borderStyle = 'hard-border'
|
borderStyle = 'hard-border'
|
||||||
}
|
}
|
||||||
this.borderChoice = borderStyle
|
this.borderChoice = borderStyle
|
||||||
|
|
@ -533,6 +603,14 @@ window.windowMixin = {
|
||||||
'lnbits.darkBgColor',
|
'lnbits.darkBgColor',
|
||||||
LNbits.utils.getPaletteColor('dark')
|
LNbits.utils.getPaletteColor('dark')
|
||||||
)
|
)
|
||||||
|
document.documentElement.style.setProperty(
|
||||||
|
'--q-primary',
|
||||||
|
LNbits.utils.getPaletteColor('primary')
|
||||||
|
)
|
||||||
|
document.documentElement.style.setProperty(
|
||||||
|
'--q-secondary',
|
||||||
|
LNbits.utils.getPaletteColor('secondary')
|
||||||
|
)
|
||||||
},
|
},
|
||||||
copyText(text, message, position) {
|
copyText(text, message, position) {
|
||||||
Quasar.copyToClipboard(text).then(() => {
|
Quasar.copyToClipboard(text).then(() => {
|
||||||
|
|
@ -576,6 +654,7 @@ window.windowMixin = {
|
||||||
)
|
)
|
||||||
.onOk(async () => {
|
.onOk(async () => {
|
||||||
try {
|
try {
|
||||||
|
this.$q.localStorage.remove('lnbits.disclaimerShown')
|
||||||
await LNbits.api.logout()
|
await LNbits.api.logout()
|
||||||
window.location = '/'
|
window.location = '/'
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -632,6 +711,14 @@ window.windowMixin = {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setColors()
|
this.setColors()
|
||||||
|
},
|
||||||
|
refreshRoute() {
|
||||||
|
const path = window.location.pathname
|
||||||
|
console.log(path)
|
||||||
|
|
||||||
|
this.$router.push('/temp').then(() => {
|
||||||
|
this.$router.replace({path})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async created() {
|
async created() {
|
||||||
|
|
@ -644,7 +731,7 @@ window.windowMixin = {
|
||||||
this.$q.dark.set(true)
|
this.$q.dark.set(true)
|
||||||
}
|
}
|
||||||
this.reactionChoice =
|
this.reactionChoice =
|
||||||
this.$q.localStorage.getItem('lnbits.reactions') || 'confettiBothSides'
|
this.$q.localStorage.getItem('lnbits.reactions') || 'confettiTop'
|
||||||
|
|
||||||
this.g.allowedThemes = window.allowedThemes ?? ['bitcoin']
|
this.g.allowedThemes = window.allowedThemes ?? ['bitcoin']
|
||||||
|
|
||||||
|
|
@ -683,23 +770,24 @@ window.windowMixin = {
|
||||||
this.$q.localStorage.getItem('lnbits.theme')
|
this.$q.localStorage.getItem('lnbits.theme')
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.applyGradient()
|
this.applyGradient()
|
||||||
this.applyBorder()
|
this.applyBorder()
|
||||||
|
|
||||||
if (window.user) {
|
if (window.user) {
|
||||||
this.g.user = Object.freeze(window.LNbits.map.user(window.user))
|
this.g.user = Vue.reactive(window.LNbits.map.user(window.user))
|
||||||
}
|
}
|
||||||
if (window.wallet) {
|
if (window.wallet) {
|
||||||
this.g.wallet = Object.freeze(window.LNbits.map.wallet(window.wallet))
|
this.g.wallet = Vue.reactive(window.LNbits.map.wallet(window.wallet))
|
||||||
}
|
}
|
||||||
if (window.extensions) {
|
if (window.extensions) {
|
||||||
const extensions = Object.freeze(window.extensions)
|
this.g.extensions = Vue.reactive(window.extensions)
|
||||||
|
|
||||||
this.g.extensions = extensions
|
|
||||||
}
|
}
|
||||||
await this.checkUsrInUrl()
|
await this.checkUsrInUrl()
|
||||||
this.themeParams()
|
this.themeParams()
|
||||||
|
this.walletFlip = this.$q.localStorage.getItem('lnbits.walletFlip')
|
||||||
|
this.mobileSimple = this.$q.localStorage.getItem('lnbits.mobileSimple')
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.paymentEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,11 @@ window.app.component('lnbits-fsat', {
|
||||||
})
|
})
|
||||||
|
|
||||||
window.app.component('lnbits-wallet-list', {
|
window.app.component('lnbits-wallet-list', {
|
||||||
|
mixins: [window.windowMixin],
|
||||||
template: '#lnbits-wallet-list',
|
template: '#lnbits-wallet-list',
|
||||||
props: ['balance'],
|
props: ['balance'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
user: null,
|
|
||||||
activeWallet: null,
|
activeWallet: null,
|
||||||
balance: 0,
|
balance: 0,
|
||||||
showForm: false,
|
showForm: false,
|
||||||
|
|
@ -34,67 +34,60 @@ window.app.component('lnbits-wallet-list', {
|
||||||
LNBITS_DENOMINATION: LNBITS_DENOMINATION
|
LNBITS_DENOMINATION: LNBITS_DENOMINATION
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
|
||||||
wallets() {
|
|
||||||
const bal = this.balance
|
|
||||||
return this.user.wallets.map(obj => {
|
|
||||||
obj.live_fsat =
|
|
||||||
bal.length && bal[0] === obj.id
|
|
||||||
? LNbits.utils.formatSat(bal[1])
|
|
||||||
: obj.fsat
|
|
||||||
return obj
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
createWallet() {
|
createWallet() {
|
||||||
LNbits.api.createWallet(this.user.wallets[0], this.walletName)
|
LNbits.api.createWallet(this.g.user.wallets[0], this.walletName)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
if (window.user) {
|
|
||||||
this.user = LNbits.map.user(window.user)
|
|
||||||
}
|
|
||||||
if (window.wallet) {
|
|
||||||
this.activeWallet = LNbits.map.wallet(window.wallet)
|
|
||||||
}
|
|
||||||
document.addEventListener('updateWalletBalance', this.updateWalletBalance)
|
document.addEventListener('updateWalletBalance', this.updateWalletBalance)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
window.app.component('lnbits-extension-list', {
|
window.app.component('lnbits-extension-list', {
|
||||||
|
mixins: [window.windowMixin],
|
||||||
template: '#lnbits-extension-list',
|
template: '#lnbits-extension-list',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
extensions: [],
|
extensions: [],
|
||||||
user: null,
|
|
||||||
userExtensions: [],
|
|
||||||
searchTerm: ''
|
searchTerm: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
searchTerm(term) {
|
'g.user.extensions': {
|
||||||
this.userExtensions = this.updateUserExtensions(term)
|
handler(newExtensions) {
|
||||||
|
this.loadExtensions()
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
userExtensions() {
|
||||||
|
return this.updateUserExtensions(this.searchTerm)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async loadExtensions() {
|
||||||
|
try {
|
||||||
|
const {data} = await LNbits.api.request('GET', '/api/v1/extension')
|
||||||
|
this.extensions = data
|
||||||
|
.map(extension => LNbits.map.extension(extension))
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name))
|
||||||
|
} catch (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
}
|
||||||
|
},
|
||||||
updateUserExtensions(filterBy) {
|
updateUserExtensions(filterBy) {
|
||||||
if (!this.user) return []
|
|
||||||
|
|
||||||
const path = window.location.pathname
|
const path = window.location.pathname
|
||||||
const userExtensions = this.user.extensions
|
const userExtensions = this.g.user.extensions
|
||||||
|
|
||||||
return this.extensions
|
return this.extensions
|
||||||
.filter(o => {
|
.filter(o => userExtensions.includes(o.code))
|
||||||
return userExtensions.indexOf(o.code) !== -1
|
|
||||||
})
|
|
||||||
.filter(o => {
|
.filter(o => {
|
||||||
if (!filterBy) return true
|
if (!filterBy) return true
|
||||||
return (
|
return `${o.code} ${o.name} ${o.short_description} ${o.url}`
|
||||||
`${o.code} ${o.name} ${o.short_description} ${o.url}`
|
.toLocaleLowerCase()
|
||||||
.toLocaleLowerCase()
|
.includes(filterBy.toLocaleLowerCase())
|
||||||
.indexOf(filterBy.toLocaleLowerCase()) !== -1
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.map(obj => {
|
.map(obj => {
|
||||||
obj.isActive = path.startsWith(obj.url)
|
obj.isActive = path.startsWith(obj.url)
|
||||||
|
|
@ -103,27 +96,12 @@ window.app.component('lnbits-extension-list', {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async created() {
|
async created() {
|
||||||
if (window.user) {
|
await this.loadExtensions()
|
||||||
this.user = LNbits.map.user(window.user)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const {data} = await LNbits.api.request('GET', '/api/v1/extension')
|
|
||||||
this.extensions = data
|
|
||||||
.map(data => {
|
|
||||||
return LNbits.map.extension(data)
|
|
||||||
})
|
|
||||||
.sort((a, b) => {
|
|
||||||
return a.name.localeCompare(b.name)
|
|
||||||
})
|
|
||||||
this.userExtensions = this.updateUserExtensions()
|
|
||||||
} catch (error) {
|
|
||||||
LNbits.utils.notifyApiError(error)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
window.app.component('lnbits-manage', {
|
window.app.component('lnbits-manage', {
|
||||||
|
mixins: [window.windowMixin],
|
||||||
template: '#lnbits-manage',
|
template: '#lnbits-manage',
|
||||||
props: ['showAdmin', 'showNode', 'showExtensions', 'showUsers', 'showAudit'],
|
props: ['showAdmin', 'showNode', 'showExtensions', 'showUsers', 'showAudit'],
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -133,18 +111,13 @@ window.app.component('lnbits-manage', {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
extensions: [],
|
extensions: []
|
||||||
user: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
if (window.user) {
|
|
||||||
this.user = LNbits.map.user(window.user)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
window.app.component('lnbits-payment-details', {
|
window.app.component('lnbits-payment-details', {
|
||||||
|
mixins: [window.windowMixin],
|
||||||
template: '#lnbits-payment-details',
|
template: '#lnbits-payment-details',
|
||||||
props: ['payment'],
|
props: ['payment'],
|
||||||
mixins: [window.windowMixin],
|
mixins: [window.windowMixin],
|
||||||
|
|
@ -196,6 +169,7 @@ window.app.component('lnbits-payment-details', {
|
||||||
})
|
})
|
||||||
|
|
||||||
window.app.component('lnbits-lnurlpay-success-action', {
|
window.app.component('lnbits-lnurlpay-success-action', {
|
||||||
|
mixins: [window.windowMixin],
|
||||||
template: '#lnbits-lnurlpay-success-action',
|
template: '#lnbits-lnurlpay-success-action',
|
||||||
props: ['payment', 'success_action'],
|
props: ['payment', 'success_action'],
|
||||||
data() {
|
data() {
|
||||||
|
|
@ -214,8 +188,8 @@ window.app.component('lnbits-lnurlpay-success-action', {
|
||||||
})
|
})
|
||||||
|
|
||||||
window.app.component('lnbits-qrcode', {
|
window.app.component('lnbits-qrcode', {
|
||||||
template: '#lnbits-qrcode',
|
|
||||||
mixins: [window.windowMixin],
|
mixins: [window.windowMixin],
|
||||||
|
template: '#lnbits-qrcode',
|
||||||
components: {
|
components: {
|
||||||
QrcodeVue
|
QrcodeVue
|
||||||
},
|
},
|
||||||
|
|
@ -484,7 +458,7 @@ window.app.component('lnbits-dynamic-chips', {
|
||||||
window.app.component('lnbits-update-balance', {
|
window.app.component('lnbits-update-balance', {
|
||||||
template: '#lnbits-update-balance',
|
template: '#lnbits-update-balance',
|
||||||
mixins: [window.windowMixin],
|
mixins: [window.windowMixin],
|
||||||
props: ['wallet_id', 'credit-value'],
|
props: ['wallet_id'],
|
||||||
computed: {
|
computed: {
|
||||||
denomination() {
|
denomination() {
|
||||||
return LNBITS_DENOMINATION
|
return LNBITS_DENOMINATION
|
||||||
|
|
@ -498,20 +472,15 @@ window.app.component('lnbits-update-balance', {
|
||||||
credit: 0
|
credit: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
credit(val) {
|
|
||||||
this.updateBalance(val)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
updateBalance(credit) {
|
updateBalance(scope) {
|
||||||
LNbits.api
|
LNbits.api
|
||||||
.updateBalance(credit, this.wallet_id)
|
.updateBalance(scope.value, this.wallet_id)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (res.data.success !== true) {
|
if (res.data.success !== true) {
|
||||||
throw new Error(res.data)
|
throw new Error(res.data)
|
||||||
}
|
}
|
||||||
credit = parseInt(credit)
|
credit = parseInt(scope.value)
|
||||||
Quasar.Notify.create({
|
Quasar.Notify.create({
|
||||||
type: 'positive',
|
type: 'positive',
|
||||||
message: this.$t('credit_ok', {
|
message: this.$t('credit_ok', {
|
||||||
|
|
@ -519,8 +488,9 @@ window.app.component('lnbits-update-balance', {
|
||||||
}),
|
}),
|
||||||
icon: null
|
icon: null
|
||||||
})
|
})
|
||||||
this.$emit('credit-value', credit)
|
this.credit = 0
|
||||||
return credit
|
scope.value = 0
|
||||||
|
scope.set()
|
||||||
})
|
})
|
||||||
.catch(LNbits.utils.notifyApiError)
|
.catch(LNbits.utils.notifyApiError)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ window.app.component('payment-chart', {
|
||||||
.request(
|
.request(
|
||||||
'GET',
|
'GET',
|
||||||
'/api/v1/payments/history?group=' + this.paymentsChart.group.value,
|
'/api/v1/payments/history?group=' + this.paymentsChart.group.value,
|
||||||
this.wallet.adminkey
|
this.g.wallet.adminkey
|
||||||
)
|
)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
window.app.component('payment-list', {
|
window.app.component('payment-list', {
|
||||||
name: 'payment-list',
|
name: 'payment-list',
|
||||||
template: '#payment-list',
|
template: '#payment-list',
|
||||||
props: ['update', 'wallet', 'mobileSimple', 'lazy'],
|
props: ['update', 'lazy'],
|
||||||
mixins: [window.windowMixin],
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -32,7 +32,7 @@ window.app.component('payment-list', {
|
||||||
descending: true,
|
descending: true,
|
||||||
rowsNumber: 10
|
rowsNumber: 10
|
||||||
},
|
},
|
||||||
search: null,
|
search: '',
|
||||||
filter: {
|
filter: {
|
||||||
'status[ne]': 'failed'
|
'status[ne]': 'failed'
|
||||||
},
|
},
|
||||||
|
|
@ -116,6 +116,9 @@ window.app.component('payment-list', {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
wallet() {
|
||||||
|
return this.g.wallet
|
||||||
|
},
|
||||||
filteredPayments() {
|
filteredPayments() {
|
||||||
const q = this.paymentsTable.search
|
const q = this.paymentsTable.search
|
||||||
if (!q || q === '') return this.payments
|
if (!q || q === '') return this.payments
|
||||||
|
|
@ -136,7 +139,7 @@ window.app.component('payment-list', {
|
||||||
fetchPayments(props) {
|
fetchPayments(props) {
|
||||||
const params = LNbits.utils.prepareFilterQuery(this.paymentsTable, props)
|
const params = LNbits.utils.prepareFilterQuery(this.paymentsTable, props)
|
||||||
return LNbits.api
|
return LNbits.api
|
||||||
.getPayments(this.wallet, params)
|
.getPayments(this.g.wallet, params)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
this.paymentsTable.loading = false
|
this.paymentsTable.loading = false
|
||||||
this.paymentsTable.pagination.rowsNumber = response.data.total
|
this.paymentsTable.pagination.rowsNumber = response.data.total
|
||||||
|
|
@ -162,7 +165,7 @@ window.app.component('payment-list', {
|
||||||
direction: pagination.descending ? 'desc' : 'asc'
|
direction: pagination.descending ? 'desc' : 'asc'
|
||||||
}
|
}
|
||||||
const params = new URLSearchParams(query)
|
const params = new URLSearchParams(query)
|
||||||
LNbits.api.getPayments(this.wallet, params).then(response => {
|
LNbits.api.getPayments(this.g.wallet, params).then(response => {
|
||||||
let payments = response.data.data.map(LNbits.map.payment)
|
let payments = response.data.data.map(LNbits.map.payment)
|
||||||
let columns = this.paymentsCSV.columns
|
let columns = this.paymentsCSV.columns
|
||||||
|
|
||||||
|
|
@ -190,7 +193,7 @@ window.app.component('payment-list', {
|
||||||
LNbits.utils.exportCSV(
|
LNbits.utils.exportCSV(
|
||||||
columns,
|
columns,
|
||||||
payments,
|
payments,
|
||||||
this.wallet.name + '-payments'
|
this.g.wallet.name + '-payments'
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
@ -233,6 +236,12 @@ window.app.component('payment-list', {
|
||||||
},
|
},
|
||||||
update() {
|
update() {
|
||||||
this.fetchPayments()
|
this.fetchPayments()
|
||||||
|
},
|
||||||
|
'g.wallet': {
|
||||||
|
handler(newWallet) {
|
||||||
|
this.fetchPayments()
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
|
|
||||||
|
|
@ -16,23 +16,53 @@ function eventReaction(amount) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function confettiBothSides() {
|
function confettiTop() {
|
||||||
document.getElementById('vue').disabled = true
|
document.getElementById('vue').disabled = true
|
||||||
var end = Date.now() + 2 * 1000
|
var end = Date.now() + 200
|
||||||
var colors = ['#FFD700', '#ffffff']
|
var colors = [
|
||||||
|
localStorage.getItem('lnbits.primaryColor') || '#FFD700',
|
||||||
|
localStorage.getItem('lnbits.secondaryColor') || 'E89400',
|
||||||
|
'#ffffff'
|
||||||
|
]
|
||||||
function frame() {
|
function frame() {
|
||||||
confetti({
|
confetti({
|
||||||
particleCount: 2,
|
particleCount: 3,
|
||||||
|
angle: 270,
|
||||||
|
spread: 1000,
|
||||||
|
origin: {y: 0},
|
||||||
|
colors: colors,
|
||||||
|
zIndex: 999999
|
||||||
|
})
|
||||||
|
if (Date.now() < end) {
|
||||||
|
requestAnimationFrame(frame)
|
||||||
|
} else {
|
||||||
|
document.getElementById('vue').disabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frame()
|
||||||
|
}
|
||||||
|
|
||||||
|
function confettiBothSides() {
|
||||||
|
document.getElementById('vue').disabled = true
|
||||||
|
var end = Date.now() + 200
|
||||||
|
var colors = [
|
||||||
|
localStorage.getItem('lnbits.primaryColor') || '#FFD700',
|
||||||
|
localStorage.getItem('lnbits.secondaryColor') || 'E89400',
|
||||||
|
'#ffffff'
|
||||||
|
]
|
||||||
|
function frame() {
|
||||||
|
confetti({
|
||||||
|
particleCount: 3,
|
||||||
angle: 60,
|
angle: 60,
|
||||||
spread: 55,
|
spread: 1000,
|
||||||
origin: {x: 0},
|
origin: {x: 0},
|
||||||
colors: colors,
|
colors: colors,
|
||||||
zIndex: 999999
|
zIndex: 999999
|
||||||
})
|
})
|
||||||
confetti({
|
confetti({
|
||||||
particleCount: 2,
|
particleCount: 3,
|
||||||
angle: 120,
|
angle: 120,
|
||||||
spread: 55,
|
spread: 1000,
|
||||||
origin: {x: 1},
|
origin: {x: 1},
|
||||||
colors: colors,
|
colors: colors,
|
||||||
zIndex: 999999
|
zIndex: 999999
|
||||||
|
|
@ -46,9 +76,19 @@ function confettiBothSides() {
|
||||||
frame()
|
frame()
|
||||||
}
|
}
|
||||||
function confettiFireworks() {
|
function confettiFireworks() {
|
||||||
var duration = 3 * 1000
|
var duration = 1000
|
||||||
var animationEnd = Date.now() + duration
|
var animationEnd = Date.now() + duration
|
||||||
var defaults = {startVelocity: 30, spread: 360, ticks: 60, zIndex: 0}
|
var defaults = {
|
||||||
|
startVelocity: 30,
|
||||||
|
spread: 1000,
|
||||||
|
ticks: 60,
|
||||||
|
zIndex: 0,
|
||||||
|
colors: [
|
||||||
|
localStorage.getItem('lnbits.primaryColor') || 'FFE400',
|
||||||
|
localStorage.getItem('lnbits.secondaryColor') || 'E89400',
|
||||||
|
'FFFFFF'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
function randomInRange(min, max) {
|
function randomInRange(min, max) {
|
||||||
return Math.random() * (max - min) + min
|
return Math.random() * (max - min) + min
|
||||||
|
|
@ -80,16 +120,20 @@ function confettiStars() {
|
||||||
var defaults = {
|
var defaults = {
|
||||||
spread: 360,
|
spread: 360,
|
||||||
ticks: 50,
|
ticks: 50,
|
||||||
gravity: 0,
|
gravity: 4,
|
||||||
decay: 0.94,
|
decay: 0.94,
|
||||||
startVelocity: 30,
|
startVelocity: 30,
|
||||||
colors: ['FFE400', 'FFBD00', 'E89400', 'FFCA6C', 'FDFFB8']
|
colors: [
|
||||||
|
localStorage.getItem('lnbits.primaryColor') || 'FFE400',
|
||||||
|
localStorage.getItem('lnbits.secondaryColor') || 'E89400',
|
||||||
|
'FFFFFF'
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
function shoot() {
|
function shoot() {
|
||||||
confetti({
|
confetti({
|
||||||
...defaults,
|
...defaults,
|
||||||
particleCount: 40,
|
particleCount: 20,
|
||||||
scalar: 1.2,
|
scalar: 1.2,
|
||||||
shapes: ['star']
|
shapes: ['star']
|
||||||
})
|
})
|
||||||
|
|
|
||||||
650
lnbits/static/js/extensions.js
Normal file
650
lnbits/static/js/extensions.js
Normal file
|
|
@ -0,0 +1,650 @@
|
||||||
|
window.ExtensionsPageLogic = {
|
||||||
|
data: function () {
|
||||||
|
return {
|
||||||
|
slide: 0,
|
||||||
|
fullscreen: false,
|
||||||
|
autoplay: true,
|
||||||
|
searchTerm: '',
|
||||||
|
tab: 'all',
|
||||||
|
manageExtensionTab: 'releases',
|
||||||
|
filteredExtensions: null,
|
||||||
|
updatableExtensions: [],
|
||||||
|
showUninstallDialog: false,
|
||||||
|
showManageExtensionDialog: false,
|
||||||
|
showExtensionDetailsDialog: false,
|
||||||
|
showDropDbDialog: false,
|
||||||
|
showPayToEnableDialog: false,
|
||||||
|
showUpdateAllDialog: false,
|
||||||
|
dropDbExtensionId: '',
|
||||||
|
selectedExtension: null,
|
||||||
|
selectedImage: null,
|
||||||
|
selectedExtensionDetails: null,
|
||||||
|
selectedExtensionRepos: null,
|
||||||
|
selectedRelease: null,
|
||||||
|
uninstallAndDropDb: false,
|
||||||
|
maxStars: 5,
|
||||||
|
paylinkWebsocket: null,
|
||||||
|
user: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
searchTerm(term) {
|
||||||
|
this.filterExtensions(term, this.tab)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleTabChanged: function (tab) {
|
||||||
|
this.filterExtensions(this.searchTerm, tab)
|
||||||
|
},
|
||||||
|
filterExtensions: function (term, tab) {
|
||||||
|
// Filter the extensions list
|
||||||
|
function extensionNameContains(searchTerm) {
|
||||||
|
return function (extension) {
|
||||||
|
return (
|
||||||
|
extension.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
|
extension.shortDescription
|
||||||
|
?.toLowerCase()
|
||||||
|
.includes(searchTerm.toLowerCase())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.filteredExtensions = this.extensions
|
||||||
|
.filter(e => (tab === 'all' ? !e.isInstalled : true))
|
||||||
|
.filter(e => (tab === 'installed' ? e.isInstalled : true))
|
||||||
|
.filter(e =>
|
||||||
|
tab === 'installed' ? (e.isActive ? true : !!this.g.user.admin) : true
|
||||||
|
)
|
||||||
|
.filter(e => (tab === 'featured' ? e.isFeatured : true))
|
||||||
|
.filter(extensionNameContains(term))
|
||||||
|
.map(e => ({
|
||||||
|
...e,
|
||||||
|
details_link:
|
||||||
|
e.installedRelease?.details_link || e.latestRelease?.details_link
|
||||||
|
}))
|
||||||
|
this.tab = tab
|
||||||
|
},
|
||||||
|
|
||||||
|
installExtension: async function (release) {
|
||||||
|
// no longer required to check if the invoice was paid
|
||||||
|
// the install logic has been triggered one way or another
|
||||||
|
this.unsubscribeFromPaylinkWs()
|
||||||
|
|
||||||
|
const extension = this.selectedExtension
|
||||||
|
extension.inProgress = true
|
||||||
|
this.showManageExtensionDialog = false
|
||||||
|
release.payment_hash =
|
||||||
|
release.payment_hash || this.getPaylinkHash(release.pay_link)
|
||||||
|
|
||||||
|
LNbits.api
|
||||||
|
.request('POST', `/api/v1/extension`, this.g.user.wallets[0].adminkey, {
|
||||||
|
ext_id: extension.id,
|
||||||
|
archive: release.archive,
|
||||||
|
source_repo: release.source_repo,
|
||||||
|
payment_hash: release.payment_hash,
|
||||||
|
version: release.version
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
extension.isAvailable = true
|
||||||
|
extension.isInstalled = true
|
||||||
|
extension.installedRelease = release
|
||||||
|
this.toggleExtension(extension)
|
||||||
|
extension.inProgress = false
|
||||||
|
this.filteredExtensions = this.extensions.concat([])
|
||||||
|
this.handleTabChanged('installed')
|
||||||
|
this.tab = 'installed'
|
||||||
|
this.refreshRoute()
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.warn(err)
|
||||||
|
extension.inProgress = false
|
||||||
|
LNbits.utils.notifyApiError(err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
uninstallExtension: async function () {
|
||||||
|
const extension = this.selectedExtension
|
||||||
|
this.showManageExtensionDialog = false
|
||||||
|
this.showUninstallDialog = false
|
||||||
|
extension.inProgress = true
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'DELETE',
|
||||||
|
`/api/v1/extension/${extension.id}`,
|
||||||
|
this.g.user.wallets[0].adminkey
|
||||||
|
)
|
||||||
|
.then(response => {
|
||||||
|
extension.isAvailable = false
|
||||||
|
extension.isInstalled = false
|
||||||
|
extension.inProgress = false
|
||||||
|
extension.installedRelease = null
|
||||||
|
this.filteredExtensions = this.extensions.concat([])
|
||||||
|
this.handleTabChanged('installed')
|
||||||
|
this.tab = 'installed'
|
||||||
|
Quasar.Notify.create({
|
||||||
|
type: 'positive',
|
||||||
|
message: 'Extension uninstalled!'
|
||||||
|
})
|
||||||
|
if (this.uninstallAndDropDb) {
|
||||||
|
this.showDropDb()
|
||||||
|
} else {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.refreshRoute()
|
||||||
|
}, 300)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
LNbits.utils.notifyApiError(err)
|
||||||
|
extension.inProgress = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
dropExtensionDb: async function () {
|
||||||
|
const extension = this.selectedExtension
|
||||||
|
this.showManageExtensionDialog = false
|
||||||
|
this.showDropDbDialog = false
|
||||||
|
this.dropDbExtensionId = ''
|
||||||
|
extension.inProgress = true
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'DELETE',
|
||||||
|
`/api/v1/extension/${extension.id}/db`,
|
||||||
|
this.g.user.wallets[0].adminkey
|
||||||
|
)
|
||||||
|
.then(response => {
|
||||||
|
extension.installedRelease = null
|
||||||
|
extension.inProgress = false
|
||||||
|
extension.hasDatabaseTables = false
|
||||||
|
Quasar.Notify.create({
|
||||||
|
type: 'positive',
|
||||||
|
message: 'Extension DB deleted!'
|
||||||
|
})
|
||||||
|
setTimeout(() => {
|
||||||
|
this.refreshRoute()
|
||||||
|
}, 300)
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
LNbits.utils.notifyApiError(err)
|
||||||
|
extension.inProgress = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
toggleExtension(extension) {
|
||||||
|
const action = extension.isActive ? 'activate' : 'deactivate'
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'PUT',
|
||||||
|
`/api/v1/extension/${extension.id}/${action}`,
|
||||||
|
this.g.user.wallets[0].adminkey
|
||||||
|
)
|
||||||
|
.then(response => {
|
||||||
|
Quasar.Notify.create({
|
||||||
|
timeout: 2000,
|
||||||
|
type: 'positive',
|
||||||
|
message: `Extension '${extension.id}' ${action}d!`
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
LNbits.utils.notifyApiError(err)
|
||||||
|
extension.isActive = false
|
||||||
|
extension.inProgress = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
enableExtensionForUser: function (extension) {
|
||||||
|
if (extension.isPaymentRequired) {
|
||||||
|
this.showPayToEnable(extension)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.enableExtension(extension)
|
||||||
|
},
|
||||||
|
enableExtension: function (extension) {
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'PUT',
|
||||||
|
`/api/v1/extension/${extension.id}/enable`,
|
||||||
|
this.g.user.wallets[0].adminkey
|
||||||
|
)
|
||||||
|
.then(response => {
|
||||||
|
Quasar.Notify.create({
|
||||||
|
type: 'positive',
|
||||||
|
message: 'Extension enabled!'
|
||||||
|
})
|
||||||
|
setTimeout(() => {
|
||||||
|
this.refreshRoute()
|
||||||
|
}, 300)
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.warn(err)
|
||||||
|
LNbits.utils.notifyApiError(err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
disableExtension: function (extension) {
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'PUT',
|
||||||
|
`/api/v1/extension/${extension.id}/disable`,
|
||||||
|
this.g.user.wallets[0].adminkey
|
||||||
|
)
|
||||||
|
.then(response => {
|
||||||
|
Quasar.Notify.create({
|
||||||
|
type: 'positive',
|
||||||
|
message: 'Extension disabled!'
|
||||||
|
})
|
||||||
|
setTimeout(() => {
|
||||||
|
this.refreshRoute()
|
||||||
|
}, 300)
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.warn(error)
|
||||||
|
LNbits.utils.notifyApiError(err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
showPayToEnable: function (extension) {
|
||||||
|
this.selectedExtension = extension
|
||||||
|
this.selectedExtension.payToEnable.paidAmount =
|
||||||
|
extension.payToEnable.amount
|
||||||
|
this.selectedExtension.payToEnable.showQRCode = false
|
||||||
|
this.showPayToEnableDialog = true
|
||||||
|
},
|
||||||
|
updatePayToInstallData: function (extension) {
|
||||||
|
LNbits.api
|
||||||
|
.request(
|
||||||
|
'PUT',
|
||||||
|
`/api/v1/extension/${extension.id}/sell`,
|
||||||
|
this.g.user.wallets[0].adminkey,
|
||||||
|
{
|
||||||
|
required: extension.payToEnable.required,
|
||||||
|
amount: extension.payToEnable.amount,
|
||||||
|
wallet: extension.payToEnable.wallet
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(response => {
|
||||||
|
Quasar.Notify.create({
|
||||||
|
type: 'positive',
|
||||||
|
message: 'Payment info updated!'
|
||||||
|
})
|
||||||
|
this.showManageExtensionDialog = false
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
LNbits.utils.notifyApiError(err)
|
||||||
|
extension.inProgress = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
showUninstall: function () {
|
||||||
|
this.showManageExtensionDialog = false
|
||||||
|
this.showUninstallDialog = true
|
||||||
|
this.uninstallAndDropDb = false
|
||||||
|
},
|
||||||
|
|
||||||
|
showDropDb: function () {
|
||||||
|
this.showDropDbDialog = true
|
||||||
|
},
|
||||||
|
|
||||||
|
showManageExtension: async function (extension) {
|
||||||
|
this.selectedExtension = extension
|
||||||
|
this.selectedRelease = null
|
||||||
|
this.selectedExtensionRepos = null
|
||||||
|
this.manageExtensionTab = 'releases'
|
||||||
|
this.showManageExtensionDialog = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const {data} = await LNbits.api.request(
|
||||||
|
'GET',
|
||||||
|
`/api/v1/extension/${extension.id}/releases`,
|
||||||
|
this.g.user.wallets[0].adminkey
|
||||||
|
)
|
||||||
|
|
||||||
|
this.selectedExtensionRepos = data.reduce((repos, release) => {
|
||||||
|
repos[release.source_repo] = repos[release.source_repo] || {
|
||||||
|
releases: [],
|
||||||
|
isInstalled: false,
|
||||||
|
repo: release.repo
|
||||||
|
}
|
||||||
|
release.inProgress = false
|
||||||
|
release.error = null
|
||||||
|
release.loaded = false
|
||||||
|
release.isInstalled = this.isInstalledVersion(
|
||||||
|
this.selectedExtension,
|
||||||
|
release
|
||||||
|
)
|
||||||
|
if (release.isInstalled) {
|
||||||
|
repos[release.source_repo].isInstalled = true
|
||||||
|
}
|
||||||
|
if (release.pay_link) {
|
||||||
|
release.requiresPayment = true
|
||||||
|
release.paidAmount = release.cost_sats
|
||||||
|
release.payment_hash = this.getPaylinkHash(release.pay_link)
|
||||||
|
}
|
||||||
|
|
||||||
|
repos[release.source_repo].releases.push(release)
|
||||||
|
return repos
|
||||||
|
}, {})
|
||||||
|
} catch (error) {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
extension.inProgress = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
showExtensionDetails: async function (extId, detailsLink) {
|
||||||
|
if (!detailsLink) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.selectedExtensionDetails = null
|
||||||
|
this.showExtensionDetailsDialog = true
|
||||||
|
this.slide = 0
|
||||||
|
this.fullscreen = false
|
||||||
|
|
||||||
|
try {
|
||||||
|
const {data} = await LNbits.api.request(
|
||||||
|
'GET',
|
||||||
|
`/api/v1/extension/${extId}/details?details_link=${detailsLink}`,
|
||||||
|
this.g.user.wallets[0].inkey
|
||||||
|
)
|
||||||
|
|
||||||
|
this.selectedExtensionDetails = data
|
||||||
|
this.selectedExtensionDetails.description_md =
|
||||||
|
LNbits.utils.convertMarkdown(data.description_md)
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async payAndInstall(release) {
|
||||||
|
try {
|
||||||
|
this.selectedExtension.inProgress = true
|
||||||
|
this.showManageExtensionDialog = false
|
||||||
|
const paymentInfo = await this.requestPaymentForInstall(
|
||||||
|
this.selectedExtension.id,
|
||||||
|
release
|
||||||
|
)
|
||||||
|
this.rememberPaylinkHash(release.pay_link, paymentInfo.payment_hash)
|
||||||
|
const wallet = this.g.user.wallets.find(w => w.id === release.wallet)
|
||||||
|
const {data} = await LNbits.api.payInvoice(
|
||||||
|
wallet,
|
||||||
|
paymentInfo.payment_request
|
||||||
|
)
|
||||||
|
|
||||||
|
release.payment_hash = data.payment_hash
|
||||||
|
|
||||||
|
await this.installExtension(release)
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(err)
|
||||||
|
LNbits.utils.notifyApiError(err)
|
||||||
|
} finally {
|
||||||
|
this.selectedExtension.inProgress = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async payAndEnable(extension) {
|
||||||
|
try {
|
||||||
|
const paymentInfo = await this.requestPaymentForEnable(
|
||||||
|
extension.id,
|
||||||
|
extension.payToEnable.paidAmount
|
||||||
|
)
|
||||||
|
|
||||||
|
const wallet = this.g.user.wallets.find(
|
||||||
|
w => w.id === extension.payToEnable.paymentWallet
|
||||||
|
)
|
||||||
|
const {data} = await LNbits.api.payInvoice(
|
||||||
|
wallet,
|
||||||
|
paymentInfo.payment_request
|
||||||
|
)
|
||||||
|
this.enableExtension(extension)
|
||||||
|
this.showPayToEnableDialog = false
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(err)
|
||||||
|
LNbits.utils.notifyApiError(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async showInstallQRCode(release) {
|
||||||
|
this.selectedRelease = release
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await this.requestPaymentForInstall(
|
||||||
|
this.selectedExtension.id,
|
||||||
|
release
|
||||||
|
)
|
||||||
|
|
||||||
|
this.selectedRelease.paymentRequest = data.payment_request
|
||||||
|
this.selectedRelease.payment_hash = data.payment_hash
|
||||||
|
this.selectedRelease = _.clone(this.selectedRelease)
|
||||||
|
this.rememberPaylinkHash(
|
||||||
|
this.selectedRelease.pay_link,
|
||||||
|
this.selectedRelease.payment_hash
|
||||||
|
)
|
||||||
|
|
||||||
|
this.subscribeToPaylinkWs(
|
||||||
|
this.selectedRelease.pay_link,
|
||||||
|
data.payment_hash
|
||||||
|
)
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(err)
|
||||||
|
LNbits.utils.notifyApiError(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async showEnableQRCode(extension) {
|
||||||
|
try {
|
||||||
|
extension.payToEnable.showQRCode = true
|
||||||
|
this.selectedExtension = _.clone(extension)
|
||||||
|
|
||||||
|
const data = await this.requestPaymentForEnable(
|
||||||
|
extension.id,
|
||||||
|
extension.payToEnable.paidAmount
|
||||||
|
)
|
||||||
|
extension.payToEnable.paymentRequest = data.payment_request
|
||||||
|
this.selectedExtension = _.clone(extension)
|
||||||
|
|
||||||
|
const url = new URL(window.location)
|
||||||
|
url.protocol = url.protocol === 'https:' ? 'wss' : 'ws'
|
||||||
|
url.pathname = `/api/v1/ws/${data.payment_hash}`
|
||||||
|
const ws = new WebSocket(url)
|
||||||
|
ws.addEventListener('message', async ({data}) => {
|
||||||
|
const payment = JSON.parse(data)
|
||||||
|
if (payment.pending === false) {
|
||||||
|
Quasar.Notify.create({
|
||||||
|
type: 'positive',
|
||||||
|
message: 'Invoice Paid!'
|
||||||
|
})
|
||||||
|
|
||||||
|
this.enableExtension(extension)
|
||||||
|
ws.close()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(err)
|
||||||
|
LNbits.utils.notifyApiError(err)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async requestPaymentForInstall(extId, release) {
|
||||||
|
const {data} = await LNbits.api.request(
|
||||||
|
'PUT',
|
||||||
|
`/api/v1/extension/${extId}/invoice/install`,
|
||||||
|
this.g.user.wallets[0].adminkey,
|
||||||
|
{
|
||||||
|
ext_id: extId,
|
||||||
|
archive: release.archive,
|
||||||
|
source_repo: release.source_repo,
|
||||||
|
cost_sats: release.paidAmount,
|
||||||
|
version: release.version
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
|
||||||
|
async requestPaymentForEnable(extId, amount) {
|
||||||
|
const {data} = await LNbits.api.request(
|
||||||
|
'PUT',
|
||||||
|
`/api/v1/extension/${extId}/invoice/enable`,
|
||||||
|
this.g.user.wallets[0].adminkey,
|
||||||
|
{
|
||||||
|
amount
|
||||||
|
}
|
||||||
|
)
|
||||||
|
return data
|
||||||
|
},
|
||||||
|
|
||||||
|
clearHangingInvoice(release) {
|
||||||
|
this.forgetPaylinkHash(release.pay_link)
|
||||||
|
release.payment_hash = null
|
||||||
|
},
|
||||||
|
|
||||||
|
rememberPaylinkHash(pay_link, payment_hash) {
|
||||||
|
this.$q.localStorage.set(
|
||||||
|
`lnbits.extensions.paylink.${pay_link}`,
|
||||||
|
payment_hash
|
||||||
|
)
|
||||||
|
},
|
||||||
|
getPaylinkHash(pay_link) {
|
||||||
|
return this.$q.localStorage.getItem(
|
||||||
|
`lnbits.extensions.paylink.${pay_link}`
|
||||||
|
)
|
||||||
|
},
|
||||||
|
forgetPaylinkHash(pay_link) {
|
||||||
|
this.$q.localStorage.remove(`lnbits.extensions.paylink.${pay_link}`)
|
||||||
|
},
|
||||||
|
subscribeToPaylinkWs(pay_link, payment_hash) {
|
||||||
|
const url = new URL(`${pay_link}/${payment_hash}`)
|
||||||
|
url.protocol = url.protocol === 'https:' ? 'wss' : 'ws'
|
||||||
|
this.paylinkWebsocket = new WebSocket(url)
|
||||||
|
this.paylinkWebsocket.addEventListener('message', async ({data}) => {
|
||||||
|
const resp = JSON.parse(data)
|
||||||
|
if (resp.paid) {
|
||||||
|
Quasar.Notify.create({
|
||||||
|
type: 'positive',
|
||||||
|
message: 'Invoice Paid!'
|
||||||
|
})
|
||||||
|
this.installExtension(this.selectedRelease)
|
||||||
|
} else {
|
||||||
|
Quasar.Notify.create({
|
||||||
|
type: 'warning',
|
||||||
|
message: 'Invoice tracking lost!'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
unsubscribeFromPaylinkWs() {
|
||||||
|
try {
|
||||||
|
this.paylinkWebsocket && this.paylinkWebsocket.close()
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
hasNewVersion: function (extension) {
|
||||||
|
if (extension.installedRelease && extension.latestRelease) {
|
||||||
|
return (
|
||||||
|
extension.installedRelease.version !== extension.latestRelease.version
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
isInstalledVersion: function (extension, release) {
|
||||||
|
if (extension.installedRelease) {
|
||||||
|
return (
|
||||||
|
extension.installedRelease.source_repo === release.source_repo &&
|
||||||
|
extension.installedRelease.version === release.version
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getReleaseIcon: function (release) {
|
||||||
|
if (!release.is_version_compatible) return 'block'
|
||||||
|
if (release.isInstalled) return 'download_done'
|
||||||
|
|
||||||
|
return 'download'
|
||||||
|
},
|
||||||
|
getReleaseIconColor: function (release) {
|
||||||
|
if (!release.is_version_compatible) return 'text-red'
|
||||||
|
if (release.isInstalled) return 'text-green'
|
||||||
|
|
||||||
|
return ''
|
||||||
|
},
|
||||||
|
getGitHubReleaseDetails: async function (release) {
|
||||||
|
if (!release.is_github_release || release.loaded) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const [org, repo] = release.source_repo.split('/')
|
||||||
|
release.inProgress = true
|
||||||
|
try {
|
||||||
|
const {data} = await LNbits.api.request(
|
||||||
|
'GET',
|
||||||
|
`/api/v1/extension/release/${org}/${repo}/${release.version}`,
|
||||||
|
this.g.user.wallets[0].adminkey
|
||||||
|
)
|
||||||
|
release.loaded = true
|
||||||
|
release.is_version_compatible = data.is_version_compatible
|
||||||
|
release.min_lnbits_version = data.min_lnbits_version
|
||||||
|
release.warning = data.warning
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error)
|
||||||
|
release.error = error
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
} finally {
|
||||||
|
release.inProgress = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectAllUpdatableExtensionss: async function () {
|
||||||
|
this.updatableExtensions.forEach(e => (e.selectedForUpdate = true))
|
||||||
|
},
|
||||||
|
updateSelectedExtensions: async function () {
|
||||||
|
let count = 0
|
||||||
|
for (const ext of this.updatableExtensions) {
|
||||||
|
try {
|
||||||
|
if (!ext.selectedForUpdate) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ext.inProgress = true
|
||||||
|
await LNbits.api.request(
|
||||||
|
'POST',
|
||||||
|
`/api/v1/extension`,
|
||||||
|
this.g.user.wallets[0].adminkey,
|
||||||
|
{
|
||||||
|
ext_id: ext.id,
|
||||||
|
archive: ext.latestRelease.archive,
|
||||||
|
source_repo: ext.latestRelease.source_repo,
|
||||||
|
payment_hash: ext.latestRelease.payment_hash,
|
||||||
|
version: ext.latestRelease.version
|
||||||
|
}
|
||||||
|
)
|
||||||
|
count++
|
||||||
|
ext.isAvailable = true
|
||||||
|
ext.isInstalled = true
|
||||||
|
ext.isUpgraded = true
|
||||||
|
ext.inProgress = false
|
||||||
|
ext.installedRelease = ext.latestRelease
|
||||||
|
this.toggleExtension(ext)
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(err)
|
||||||
|
Quasar.Notify.create({
|
||||||
|
type: 'negative',
|
||||||
|
message: `Failed to update ${ext.code}!`
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
ext.inProgress = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Quasar.Notify.create({
|
||||||
|
type: 'positive',
|
||||||
|
message: `${count} extensions updated!`
|
||||||
|
})
|
||||||
|
this.showUpdateAllDialog = false
|
||||||
|
setTimeout(() => {
|
||||||
|
this.refreshRoute()
|
||||||
|
}, 2000)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created: function () {
|
||||||
|
this.extensions = window.extension_data.map(e => ({
|
||||||
|
...e,
|
||||||
|
inProgress: false,
|
||||||
|
selectedForUpdate: false
|
||||||
|
}))
|
||||||
|
this.filteredExtensions = this.extensions.concat([])
|
||||||
|
for (let i = 0; i < this.filteredExtensions.length; i++) {
|
||||||
|
if (this.filteredExtensions[i].isInstalled != false) {
|
||||||
|
this.handleTabChanged('installed')
|
||||||
|
this.tab = 'installed'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.updatableExtensions = this.extensions.filter(ext =>
|
||||||
|
this.hasNewVersion(ext)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
mixins: [windowMixin]
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,206 @@
|
||||||
|
const DynamicComponent = {
|
||||||
|
props: {
|
||||||
|
fetchUrl: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
scripts: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
await this.loadDynamicContent()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async loadScript(src) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const existingScript = document.querySelector(`script[src="${src}"]`)
|
||||||
|
if (existingScript) {
|
||||||
|
existingScript.remove()
|
||||||
|
}
|
||||||
|
const script = document.createElement('script')
|
||||||
|
script.src = src
|
||||||
|
script.async = true
|
||||||
|
script.onload = resolve
|
||||||
|
script.onerror = () =>
|
||||||
|
reject(new Error(`Failed to load script: ${src}`))
|
||||||
|
document.head.appendChild(script)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async loadDynamicContent() {
|
||||||
|
this.$q.loading.show()
|
||||||
|
try {
|
||||||
|
const cleanUrl = this.fetchUrl.split('#')[0]
|
||||||
|
//grab page content, need to be before loading scripts
|
||||||
|
const response = await fetch(cleanUrl, {
|
||||||
|
credentials: 'include',
|
||||||
|
headers: {
|
||||||
|
Accept: 'text/html',
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const html = await response.text()
|
||||||
|
|
||||||
|
// load window variables
|
||||||
|
const parser = new DOMParser()
|
||||||
|
const htmlDocument = parser.parseFromString(html, 'text/html')
|
||||||
|
const inlineScript = htmlDocument.querySelector('#window-vars-script')
|
||||||
|
if (inlineScript) {
|
||||||
|
new Function(inlineScript.innerHTML)() // Execute the script
|
||||||
|
}
|
||||||
|
|
||||||
|
//load scripts defined in the route
|
||||||
|
await this.loadScript('/static/js/base.js')
|
||||||
|
for (const script of this.scripts) {
|
||||||
|
await this.loadScript(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
//housecleaning, remove old component
|
||||||
|
//const previousRouteName =
|
||||||
|
// this.$router.currentRoute.value.meta.previousRouteName
|
||||||
|
//if (
|
||||||
|
// previousRouteName &&
|
||||||
|
// window.app._context.components[previousRouteName]
|
||||||
|
//) {
|
||||||
|
// delete window.app._context.components[previousRouteName]
|
||||||
|
//}
|
||||||
|
|
||||||
|
//load component logic
|
||||||
|
const logicKey = `${this.$route.name}PageLogic`
|
||||||
|
const componentLogic = window[logicKey]
|
||||||
|
|
||||||
|
if (!componentLogic) {
|
||||||
|
throw new Error(
|
||||||
|
`Component logic '${logicKey}' not found. Ensure it is defined in the script.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Add mixins
|
||||||
|
componentLogic.mixins = componentLogic.mixins || []
|
||||||
|
if (window.windowMixin) {
|
||||||
|
componentLogic.mixins.push(window.windowMixin)
|
||||||
|
}
|
||||||
|
|
||||||
|
//Build component
|
||||||
|
window.app.component(this.$route.name, {
|
||||||
|
...componentLogic,
|
||||||
|
template: html // Use the fetched HTML as the template
|
||||||
|
})
|
||||||
|
delete window[logicKey] //dont need this anymore
|
||||||
|
this.$forceUpdate()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading dynamic content:', error)
|
||||||
|
} finally {
|
||||||
|
this.$q.loading.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
$route(to, from) {
|
||||||
|
const validRouteNames = routes.map(route => route.name)
|
||||||
|
if (validRouteNames.includes(to.name)) {
|
||||||
|
this.$router.currentRoute.value.meta.previousRouteName = from.name
|
||||||
|
this.loadDynamicContent()
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
`Route '${to.name}' is not valid. Leave this one to Fastapi.`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<component :is="$route.name"></component>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
path: '/wallet',
|
||||||
|
name: 'Wallet',
|
||||||
|
component: DynamicComponent,
|
||||||
|
props: route => ({
|
||||||
|
fetchUrl: `/wallet${route.query.wal ? `?wal=${route.query.wal}` : ''}`,
|
||||||
|
scripts: ['/static/js/wallet.js']
|
||||||
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/admin',
|
||||||
|
name: 'Admin',
|
||||||
|
component: DynamicComponent,
|
||||||
|
props: {
|
||||||
|
fetchUrl: '/admin',
|
||||||
|
scripts: ['/static/js/admin.js']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/users',
|
||||||
|
name: 'Users',
|
||||||
|
component: DynamicComponent,
|
||||||
|
props: {
|
||||||
|
fetchUrl: '/users',
|
||||||
|
scripts: ['/static/js/users.js']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/audit',
|
||||||
|
name: 'Audit',
|
||||||
|
component: DynamicComponent,
|
||||||
|
props: {
|
||||||
|
fetchUrl: '/audit',
|
||||||
|
scripts: ['/static/js/audit.js']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/extensions',
|
||||||
|
name: 'Extensions',
|
||||||
|
component: DynamicComponent,
|
||||||
|
props: {
|
||||||
|
fetchUrl: '/extensions',
|
||||||
|
scripts: ['/static/js/extensions.js']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/account',
|
||||||
|
name: 'Account',
|
||||||
|
component: DynamicComponent,
|
||||||
|
props: {
|
||||||
|
fetchUrl: '/account',
|
||||||
|
scripts: ['/static/js/account.js']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/node',
|
||||||
|
name: 'Node',
|
||||||
|
component: DynamicComponent,
|
||||||
|
props: {
|
||||||
|
fetchUrl: '/node',
|
||||||
|
scripts: ['/static/js/node.js']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
window.router = VueRouter.createRouter({
|
||||||
|
history: VueRouter.createWebHistory(),
|
||||||
|
routes
|
||||||
|
})
|
||||||
|
|
||||||
|
window.app.mixin({
|
||||||
|
computed: {
|
||||||
|
isVueRoute() {
|
||||||
|
const currentPath = window.location.pathname
|
||||||
|
const matchedRoute = window.router.resolve(currentPath)
|
||||||
|
const isVueRoute = matchedRoute?.matched?.length > 0
|
||||||
|
return isVueRoute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
window.app.use(VueQrcodeReader)
|
window.app.use(VueQrcodeReader)
|
||||||
window.app.use(Quasar)
|
window.app.use(Quasar)
|
||||||
window.app.use(window.i18n)
|
window.app.use(window.i18n)
|
||||||
|
window.app.provide('g', g)
|
||||||
|
window.app.use(window.router)
|
||||||
|
window.app.component('DynamicComponent', DynamicComponent)
|
||||||
window.app.mount('#vue')
|
window.app.mount('#vue')
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,362 @@
|
||||||
|
window.NodePageLogic = {
|
||||||
|
mixins: [window.windowMixin],
|
||||||
|
config: {
|
||||||
|
globalProperties: {
|
||||||
|
LNbits,
|
||||||
|
msg: 'hello'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isSuperUser: false,
|
||||||
|
wallet: {},
|
||||||
|
tab: 'dashboard',
|
||||||
|
payments: 1000,
|
||||||
|
info: {},
|
||||||
|
channel_stats: {},
|
||||||
|
|
||||||
|
channels: {
|
||||||
|
data: [],
|
||||||
|
filter: ''
|
||||||
|
},
|
||||||
|
|
||||||
|
activeBalance: {},
|
||||||
|
ranks: {},
|
||||||
|
|
||||||
|
peers: {
|
||||||
|
data: [],
|
||||||
|
filter: ''
|
||||||
|
},
|
||||||
|
|
||||||
|
connectPeerDialog: {
|
||||||
|
show: false,
|
||||||
|
data: {}
|
||||||
|
},
|
||||||
|
|
||||||
|
setFeeDialog: {
|
||||||
|
show: false,
|
||||||
|
data: {
|
||||||
|
fee_ppm: 0,
|
||||||
|
fee_base_msat: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
openChannelDialog: {
|
||||||
|
show: false,
|
||||||
|
data: {}
|
||||||
|
},
|
||||||
|
|
||||||
|
closeChannelDialog: {
|
||||||
|
show: false,
|
||||||
|
data: {}
|
||||||
|
},
|
||||||
|
|
||||||
|
nodeInfoDialog: {
|
||||||
|
show: false,
|
||||||
|
data: {}
|
||||||
|
},
|
||||||
|
|
||||||
|
transactionDetailsDialog: {
|
||||||
|
show: false,
|
||||||
|
data: {}
|
||||||
|
},
|
||||||
|
|
||||||
|
states: [
|
||||||
|
{label: 'Active', value: 'active', color: 'green'},
|
||||||
|
{label: 'Pending', value: 'pending', color: 'orange'},
|
||||||
|
{label: 'Inactive', value: 'inactive', color: 'grey'},
|
||||||
|
{label: 'Closed', value: 'closed', color: 'red'}
|
||||||
|
],
|
||||||
|
|
||||||
|
stateFilters: [
|
||||||
|
{label: 'Active', value: 'active'},
|
||||||
|
{label: 'Pending', value: 'pending'}
|
||||||
|
],
|
||||||
|
|
||||||
|
paymentsTable: {
|
||||||
|
data: [],
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'pending',
|
||||||
|
label: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'date',
|
||||||
|
align: 'left',
|
||||||
|
label: this.$t('date'),
|
||||||
|
field: 'date',
|
||||||
|
sortable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'sat',
|
||||||
|
align: 'right',
|
||||||
|
label: this.$t('amount') + ' (' + LNBITS_DENOMINATION + ')',
|
||||||
|
field: row => this.formatMsat(row.amount),
|
||||||
|
sortable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fee',
|
||||||
|
align: 'right',
|
||||||
|
label: this.$t('fee') + ' (m' + LNBITS_DENOMINATION + ')',
|
||||||
|
field: 'fee'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'destination',
|
||||||
|
align: 'right',
|
||||||
|
label: 'Destination',
|
||||||
|
field: 'destination'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'memo',
|
||||||
|
align: 'left',
|
||||||
|
label: this.$t('memo'),
|
||||||
|
field: 'memo'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
pagination: {
|
||||||
|
rowsPerPage: 10,
|
||||||
|
page: 1,
|
||||||
|
rowsNumber: 10
|
||||||
|
},
|
||||||
|
filter: null
|
||||||
|
},
|
||||||
|
invoiceTable: {
|
||||||
|
data: [],
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'pending',
|
||||||
|
label: ''
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'paid_at',
|
||||||
|
field: 'paid_at',
|
||||||
|
align: 'left',
|
||||||
|
label: 'Paid at',
|
||||||
|
sortable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'expiry',
|
||||||
|
label: this.$t('expiry'),
|
||||||
|
field: 'expiry',
|
||||||
|
align: 'left',
|
||||||
|
sortable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'amount',
|
||||||
|
label: this.$t('amount') + ' (' + LNBITS_DENOMINATION + ')',
|
||||||
|
field: row => this.formatMsat(row.amount),
|
||||||
|
sortable: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'memo',
|
||||||
|
align: 'left',
|
||||||
|
label: this.$t('memo'),
|
||||||
|
field: 'memo'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
pagination: {
|
||||||
|
rowsPerPage: 10,
|
||||||
|
page: 1,
|
||||||
|
rowsNumber: 10
|
||||||
|
},
|
||||||
|
filter: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.getInfo()
|
||||||
|
this.get1MLStats()
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
tab(val) {
|
||||||
|
if (val === 'transactions' && !this.paymentsTable.data.length) {
|
||||||
|
this.getPayments()
|
||||||
|
this.getInvoices()
|
||||||
|
} else if (val === 'channels' && !this.channels.data.length) {
|
||||||
|
this.getChannels()
|
||||||
|
this.getPeers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
checkChanges() {
|
||||||
|
return !_.isEqual(this.settings, this.formData)
|
||||||
|
},
|
||||||
|
filteredChannels() {
|
||||||
|
return this.stateFilters
|
||||||
|
? this.channels.data.filter(channel => {
|
||||||
|
return this.stateFilters.find(({value}) => value == channel.state)
|
||||||
|
})
|
||||||
|
: this.channels.data
|
||||||
|
},
|
||||||
|
totalBalance() {
|
||||||
|
return this.filteredChannels.reduce(
|
||||||
|
(balance, channel) => {
|
||||||
|
balance.local_msat += channel.balance.local_msat
|
||||||
|
balance.remote_msat += channel.balance.remote_msat
|
||||||
|
balance.total_msat += channel.balance.total_msat
|
||||||
|
return balance
|
||||||
|
},
|
||||||
|
{local_msat: 0, remote_msat: 0, total_msat: 0}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
formatMsat(msat) {
|
||||||
|
return LNbits.utils.formatMsat(msat)
|
||||||
|
},
|
||||||
|
api(method, url, options) {
|
||||||
|
const params = new URLSearchParams(options?.query)
|
||||||
|
return LNbits.api
|
||||||
|
.request(method, `/node/api/v1${url}?${params}`, {}, options?.data)
|
||||||
|
.catch(error => {
|
||||||
|
LNbits.utils.notifyApiError(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getChannel(channel_id) {
|
||||||
|
return this.api('GET', `/channels/${channel_id}`).then(response => {
|
||||||
|
this.setFeeDialog.data.fee_ppm = response.data.fee_ppm
|
||||||
|
this.setFeeDialog.data.fee_base_msat = response.data.fee_base_msat
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getChannels() {
|
||||||
|
return this.api('GET', '/channels').then(response => {
|
||||||
|
this.channels.data = response.data
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getInfo() {
|
||||||
|
return this.api('GET', '/info').then(response => {
|
||||||
|
this.info = response.data
|
||||||
|
this.channel_stats = response.data.channel_stats
|
||||||
|
})
|
||||||
|
},
|
||||||
|
get1MLStats() {
|
||||||
|
return this.api('GET', '/rank').then(response => {
|
||||||
|
this.ranks = response.data
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getPayments(props) {
|
||||||
|
if (props) {
|
||||||
|
this.paymentsTable.pagination = props.pagination
|
||||||
|
}
|
||||||
|
let pagination = this.paymentsTable.pagination
|
||||||
|
const query = {
|
||||||
|
limit: pagination.rowsPerPage,
|
||||||
|
offset: (pagination.page - 1) * pagination.rowsPerPage ?? 0
|
||||||
|
}
|
||||||
|
return this.api('GET', '/payments', {query}).then(response => {
|
||||||
|
this.paymentsTable.data = response.data.data
|
||||||
|
this.paymentsTable.pagination.rowsNumber = response.data.total
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getInvoices(props) {
|
||||||
|
if (props) {
|
||||||
|
this.invoiceTable.pagination = props.pagination
|
||||||
|
}
|
||||||
|
let pagination = this.invoiceTable.pagination
|
||||||
|
const query = {
|
||||||
|
limit: pagination.rowsPerPage,
|
||||||
|
offset: (pagination.page - 1) * pagination.rowsPerPage ?? 0
|
||||||
|
}
|
||||||
|
return this.api('GET', '/invoices', {query}).then(response => {
|
||||||
|
this.invoiceTable.data = response.data.data
|
||||||
|
this.invoiceTable.pagination.rowsNumber = response.data.total
|
||||||
|
})
|
||||||
|
},
|
||||||
|
getPeers() {
|
||||||
|
return this.api('GET', '/peers').then(response => {
|
||||||
|
this.peers.data = response.data
|
||||||
|
console.log('peers', this.peers)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
connectPeer() {
|
||||||
|
this.api('POST', '/peers', {data: this.connectPeerDialog.data}).then(
|
||||||
|
() => {
|
||||||
|
this.connectPeerDialog.show = false
|
||||||
|
this.getPeers()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
disconnectPeer(id) {
|
||||||
|
LNbits.utils
|
||||||
|
.confirmDialog('Do you really wanna disconnect this peer?')
|
||||||
|
.onOk(() => {
|
||||||
|
this.api('DELETE', `/peers/${id}`).then(response => {
|
||||||
|
Quasar.Notify.create({
|
||||||
|
message: 'Disconnected',
|
||||||
|
icon: null
|
||||||
|
})
|
||||||
|
this.needsRestart = true
|
||||||
|
this.getPeers()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setChannelFee(channel_id) {
|
||||||
|
this.api('PUT', `/channels/${channel_id}`, {
|
||||||
|
data: this.setFeeDialog.data
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
this.setFeeDialog.show = false
|
||||||
|
this.getChannels()
|
||||||
|
})
|
||||||
|
.catch(LNbits.utils.notifyApiError)
|
||||||
|
},
|
||||||
|
openChannel() {
|
||||||
|
this.api('POST', '/channels', {data: this.openChannelDialog.data})
|
||||||
|
.then(response => {
|
||||||
|
this.openChannelDialog.show = false
|
||||||
|
this.getChannels()
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
showCloseChannelDialog(channel) {
|
||||||
|
this.closeChannelDialog.show = true
|
||||||
|
this.closeChannelDialog.data = {
|
||||||
|
force: false,
|
||||||
|
short_id: channel.short_id,
|
||||||
|
...channel.point
|
||||||
|
}
|
||||||
|
},
|
||||||
|
closeChannel() {
|
||||||
|
this.api('DELETE', '/channels', {
|
||||||
|
query: this.closeChannelDialog.data
|
||||||
|
}).then(response => {
|
||||||
|
this.closeChannelDialog.show = false
|
||||||
|
this.getChannels()
|
||||||
|
})
|
||||||
|
},
|
||||||
|
showSetFeeDialog(channel_id) {
|
||||||
|
this.setFeeDialog.show = true
|
||||||
|
this.setFeeDialog.channel_id = channel_id
|
||||||
|
this.getChannel(channel_id)
|
||||||
|
},
|
||||||
|
showOpenChannelDialog(peer_id) {
|
||||||
|
this.openChannelDialog.show = true
|
||||||
|
this.openChannelDialog.data = {peer_id, funding_amount: 0}
|
||||||
|
},
|
||||||
|
showNodeInfoDialog(node) {
|
||||||
|
this.nodeInfoDialog.show = true
|
||||||
|
this.nodeInfoDialog.data = node
|
||||||
|
},
|
||||||
|
showTransactionDetailsDialog(details) {
|
||||||
|
this.transactionDetailsDialog.show = true
|
||||||
|
this.transactionDetailsDialog.data = details
|
||||||
|
console.log('details', details)
|
||||||
|
},
|
||||||
|
shortenNodeId(nodeId) {
|
||||||
|
return nodeId
|
||||||
|
? nodeId.substring(0, 5) + '...' + nodeId.substring(nodeId.length - 5)
|
||||||
|
: '...'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
window.app.component('lnbits-node-ranks', {
|
window.app.component('lnbits-node-ranks', {
|
||||||
props: ['ranks'],
|
props: ['ranks'],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
user: {},
|
|
||||||
stats: [
|
stats: [
|
||||||
{label: 'Capacity', key: 'capacity'},
|
{label: 'Capacity', key: 'capacity'},
|
||||||
{label: 'Channels', key: 'channelcount'},
|
{label: 'Channels', key: 'channelcount'},
|
||||||
|
|
@ -57,12 +411,7 @@ window.app.component('lnbits-channel-stats', {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</q-card>
|
</q-card>
|
||||||
`,
|
`
|
||||||
created() {
|
|
||||||
if (window.user) {
|
|
||||||
this.user = LNbits.map.user(window.user)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
window.app.component('lnbits-stat', {
|
window.app.component('lnbits-stat', {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
window.app = Vue.createApp({
|
window.UsersPageLogic = {
|
||||||
el: '#vue',
|
|
||||||
mixins: [window.windowMixin],
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
|
@ -403,4 +402,4 @@ window.app = Vue.createApp({
|
||||||
return `${value.substring(0, 5)}...${value.substring(valueLength - 5, valueLength)}`
|
return `${value.substring(0, 5)}...${value.substring(valueLength - 5, valueLength)}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,9 @@
|
||||||
window.app = Vue.createApp({
|
window.WalletPageLogic = {
|
||||||
el: '#vue',
|
|
||||||
mixins: [window.windowMixin],
|
mixins: [window.windowMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
updatePayments: false,
|
|
||||||
origin: window.location.origin,
|
origin: window.location.origin,
|
||||||
wallet: LNbits.map.wallet(window.wallet),
|
|
||||||
user: LNbits.map.user(window.user),
|
|
||||||
exportUrl: `${window.location.origin}/wallet?usr=${window.user.id}&wal=${window.wallet.id}`,
|
|
||||||
baseUrl: `${window.location.protocol}//${window.location.host}/`,
|
baseUrl: `${window.location.protocol}//${window.location.host}/`,
|
||||||
receive: {
|
|
||||||
show: false,
|
|
||||||
status: 'pending',
|
|
||||||
paymentReq: null,
|
|
||||||
paymentHash: null,
|
|
||||||
amountMsat: null,
|
|
||||||
minMax: [0, 2100000000000000],
|
|
||||||
lnurl: null,
|
|
||||||
units: ['sat'],
|
|
||||||
unit: 'sat',
|
|
||||||
data: {
|
|
||||||
amount: null,
|
|
||||||
memo: ''
|
|
||||||
}
|
|
||||||
},
|
|
||||||
parse: {
|
parse: {
|
||||||
show: false,
|
show: false,
|
||||||
invoice: null,
|
invoice: null,
|
||||||
|
|
@ -44,13 +24,27 @@ window.app = Vue.createApp({
|
||||||
camera: 'auto'
|
camera: 'auto'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
receive: {
|
||||||
|
show: false,
|
||||||
|
status: 'pending',
|
||||||
|
paymentReq: null,
|
||||||
|
paymentHash: null,
|
||||||
|
amountMsat: null,
|
||||||
|
minMax: [0, 2100000000000000],
|
||||||
|
lnurl: null,
|
||||||
|
units: ['sat'],
|
||||||
|
unit: 'sat',
|
||||||
|
data: {
|
||||||
|
amount: null,
|
||||||
|
memo: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
invoiceQrCode: '',
|
||||||
disclaimerDialog: {
|
disclaimerDialog: {
|
||||||
show: false,
|
show: false,
|
||||||
location: window.location
|
location: window.location
|
||||||
},
|
},
|
||||||
balance: parseInt(wallet.balance_msat / 1000),
|
|
||||||
fiatBalance: 0,
|
fiatBalance: 0,
|
||||||
mobileSimple: false,
|
|
||||||
update: {
|
update: {
|
||||||
name: null,
|
name: null,
|
||||||
currency: null
|
currency: null
|
||||||
|
|
@ -65,9 +59,9 @@ window.app = Vue.createApp({
|
||||||
computed: {
|
computed: {
|
||||||
formattedBalance() {
|
formattedBalance() {
|
||||||
if (LNBITS_DENOMINATION != 'sats') {
|
if (LNBITS_DENOMINATION != 'sats') {
|
||||||
return this.balance / 100
|
return this.g.wallet.sat / 100
|
||||||
} else {
|
} else {
|
||||||
return LNbits.utils.formatSat(this.balance || this.g.wallet.sat)
|
return LNbits.utils.formatSat(this.g.wallet.sat)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
formattedFiatBalance() {
|
formattedFiatBalance() {
|
||||||
|
|
@ -80,7 +74,7 @@ window.app = Vue.createApp({
|
||||||
},
|
},
|
||||||
canPay() {
|
canPay() {
|
||||||
if (!this.parse.invoice) return false
|
if (!this.parse.invoice) return false
|
||||||
return this.parse.invoice.sat <= this.balance
|
return this.parse.invoice.sat <= this.g.wallet.sat
|
||||||
},
|
},
|
||||||
formattedAmount() {
|
formattedAmount() {
|
||||||
if (this.receive.unit != 'sat') {
|
if (this.receive.unit != 'sat') {
|
||||||
|
|
@ -94,6 +88,9 @@ window.app = Vue.createApp({
|
||||||
},
|
},
|
||||||
formattedSatAmount() {
|
formattedSatAmount() {
|
||||||
return LNbits.utils.formatMsat(this.receive.amountMsat) + ' sat'
|
return LNbits.utils.formatMsat(this.receive.amountMsat) + ' sat'
|
||||||
|
},
|
||||||
|
wallet() {
|
||||||
|
return this.g.wallet
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -144,21 +141,16 @@ window.app = Vue.createApp({
|
||||||
clearInterval(this.parse.paymentChecker)
|
clearInterval(this.parse.paymentChecker)
|
||||||
}, 10000)
|
}, 10000)
|
||||||
},
|
},
|
||||||
onPaymentReceived(paymentHash) {
|
|
||||||
this.updatePayments = !this.updatePayments
|
|
||||||
if (this.receive.paymentHash === paymentHash) {
|
|
||||||
this.receive.show = false
|
|
||||||
this.receive.paymentHash = null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleBalanceUpdate(value) {
|
handleBalanceUpdate(value) {
|
||||||
this.balance = this.balance + value
|
this.g.wallet.sat = this.g.wallet.sat + value
|
||||||
},
|
},
|
||||||
createInvoice() {
|
createInvoice() {
|
||||||
|
console.log('Creating invoice...')
|
||||||
this.receive.status = 'loading'
|
this.receive.status = 'loading'
|
||||||
if (LNBITS_DENOMINATION != 'sats') {
|
if (LNBITS_DENOMINATION != 'sats') {
|
||||||
this.receive.data.amount = this.receive.data.amount * 100
|
this.receive.data.amount = this.receive.data.amount * 100
|
||||||
}
|
}
|
||||||
|
|
||||||
LNbits.api
|
LNbits.api
|
||||||
.createInvoice(
|
.createInvoice(
|
||||||
this.g.wallet,
|
this.g.wallet,
|
||||||
|
|
@ -172,7 +164,6 @@ window.app = Vue.createApp({
|
||||||
this.receive.paymentReq = response.data.bolt11
|
this.receive.paymentReq = response.data.bolt11
|
||||||
this.receive.amountMsat = response.data.amount
|
this.receive.amountMsat = response.data.amount
|
||||||
this.receive.paymentHash = response.data.payment_hash
|
this.receive.paymentHash = response.data.payment_hash
|
||||||
|
|
||||||
this.readNfcTag()
|
this.readNfcTag()
|
||||||
|
|
||||||
// TODO: lnurl_callback and lnurl_response
|
// TODO: lnurl_callback and lnurl_response
|
||||||
|
|
@ -200,9 +191,12 @@ window.app = Vue.createApp({
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
// Hack as rendering in dialog causes reactivity issues. Does speed up, as only rendering lnbits-qrcode once.
|
||||||
.then(() => {
|
this.$nextTick(() => {
|
||||||
this.updatePayments = !this.updatePayments
|
this.invoiceQrCode = document.getElementById(
|
||||||
|
'hiddenQrCodeContainer'
|
||||||
|
).innerHTML
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
LNbits.utils.notifyApiError(err)
|
LNbits.utils.notifyApiError(err)
|
||||||
|
|
@ -367,22 +361,16 @@ window.app = Vue.createApp({
|
||||||
|
|
||||||
LNbits.api
|
LNbits.api
|
||||||
.payInvoice(this.g.wallet, this.parse.data.request)
|
.payInvoice(this.g.wallet, this.parse.data.request)
|
||||||
.then(response => {
|
.then(_ => {
|
||||||
clearInterval(this.parse.paymentChecker)
|
clearInterval(this.parse.paymentChecker)
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
clearInterval(this.parse.paymentChecker)
|
clearInterval(this.parse.paymentChecker)
|
||||||
}, 40000)
|
}, 40000)
|
||||||
this.parse.paymentChecker = setInterval(() => {
|
this.parse.paymentChecker = setInterval(() => {
|
||||||
LNbits.api
|
if (!this.parse.show) {
|
||||||
.getPayment(this.g.wallet, response.data.payment_hash)
|
dismissPaymentMsg()
|
||||||
.then(res => {
|
clearInterval(this.parse.paymentChecker)
|
||||||
if (res.data.paid) {
|
}
|
||||||
dismissPaymentMsg()
|
|
||||||
clearInterval(this.parse.paymentChecker)
|
|
||||||
this.updatePayments = !this.updatePayments
|
|
||||||
this.parse.show = false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, 2000)
|
}, 2000)
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
|
@ -397,7 +385,6 @@ window.app = Vue.createApp({
|
||||||
timeout: 0,
|
timeout: 0,
|
||||||
message: 'Processing payment...'
|
message: 'Processing payment...'
|
||||||
})
|
})
|
||||||
|
|
||||||
LNbits.api
|
LNbits.api
|
||||||
.payLnurl(
|
.payLnurl(
|
||||||
this.g.wallet,
|
this.g.wallet,
|
||||||
|
|
@ -509,13 +496,13 @@ window.app = Vue.createApp({
|
||||||
updateWallet(data) {
|
updateWallet(data) {
|
||||||
LNbits.api
|
LNbits.api
|
||||||
.request('PATCH', '/api/v1/wallet', this.g.wallet.adminkey, data)
|
.request('PATCH', '/api/v1/wallet', this.g.wallet.adminkey, data)
|
||||||
.then(_ => {
|
.then(response => {
|
||||||
|
this.refreshRoute()
|
||||||
Quasar.Notify.create({
|
Quasar.Notify.create({
|
||||||
message: `Wallet updated.`,
|
message: 'Wallet and user updated.',
|
||||||
type: 'positive',
|
type: 'positive',
|
||||||
timeout: 3500
|
timeout: 3500
|
||||||
})
|
})
|
||||||
window.location.reload()
|
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
LNbits.utils.notifyApiError(err)
|
LNbits.utils.notifyApiError(err)
|
||||||
|
|
@ -543,7 +530,7 @@ window.app = Vue.createApp({
|
||||||
if (!this.g.wallet.currency) return 0
|
if (!this.g.wallet.currency) return 0
|
||||||
LNbits.api
|
LNbits.api
|
||||||
.request('POST', `/api/v1/conversion`, null, {
|
.request('POST', `/api/v1/conversion`, null, {
|
||||||
amount: this.balance || this.g.wallet.sat,
|
amount: this.g.wallet.sat,
|
||||||
to: this.g.wallet.currency
|
to: this.g.wallet.currency
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
|
|
@ -652,6 +639,12 @@ window.app = Vue.createApp({
|
||||||
dismissPaymentMsg()
|
dismissPaymentMsg()
|
||||||
LNbits.utils.notifyApiError(err)
|
LNbits.utils.notifyApiError(err)
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
createdTasks() {
|
||||||
|
this.update.name = this.g.wallet.name
|
||||||
|
this.update.currency = this.g.wallet.currency
|
||||||
|
this.receive.units = ['sat', ...window.currencies]
|
||||||
|
this.updateFiatBalance()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
|
|
@ -665,19 +658,27 @@ window.app = Vue.createApp({
|
||||||
if (this.$q.screen.lt.md) {
|
if (this.$q.screen.lt.md) {
|
||||||
this.mobileSimple = true
|
this.mobileSimple = true
|
||||||
}
|
}
|
||||||
this.update.name = this.g.wallet.name
|
this.createdTasks()
|
||||||
this.update.currency = this.g.wallet.currency
|
|
||||||
this.receive.units = ['sat', ...window.currencies]
|
|
||||||
this.updateFiatBalance()
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
updatePayments() {
|
updatePayments() {
|
||||||
|
this.parse.show = false
|
||||||
|
if (this.receive.paymentHash === this.updatePaymentsHash) {
|
||||||
|
this.receive.show = false
|
||||||
|
this.receive.paymentHash = null
|
||||||
|
}
|
||||||
this.updateFiatBalance()
|
this.updateFiatBalance()
|
||||||
},
|
},
|
||||||
'$q.screen.gt.sm'(value) {
|
'$q.screen.gt.sm'(value) {
|
||||||
if (value == true) {
|
if (value == true) {
|
||||||
this.mobileSimple = false
|
this.mobileSimple = false
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
'g.wallet': {
|
||||||
|
handler(newWallet) {
|
||||||
|
this.createdTasks()
|
||||||
|
},
|
||||||
|
deep: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
@ -685,18 +686,11 @@ window.app = Vue.createApp({
|
||||||
if (!this.$q.localStorage.getItem('lnbits.disclaimerShown')) {
|
if (!this.$q.localStorage.getItem('lnbits.disclaimerShown')) {
|
||||||
this.disclaimerDialog.show = true
|
this.disclaimerDialog.show = true
|
||||||
this.$q.localStorage.set('lnbits.disclaimerShown', true)
|
this.$q.localStorage.set('lnbits.disclaimerShown', true)
|
||||||
|
// Turn on payment reactions by default
|
||||||
|
this.$q.localStorage.set('lnbits.reactions', 'confettiTop')
|
||||||
}
|
}
|
||||||
// listen to incoming payments
|
|
||||||
LNbits.events.onInvoicePaid(this.g.wallet, data => {
|
|
||||||
console.log('Payment received:', data.payment.payment_hash)
|
|
||||||
console.log('Wallet balance:', data.wallet_balance)
|
|
||||||
console.log('Wallet ID:', this.g.wallet)
|
|
||||||
this.onPaymentReceived(data.payment.payment_hash)
|
|
||||||
this.balance = data.wallet_balance
|
|
||||||
eventReaction(data.payment.amount)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
if (navigator.serviceWorker != null) {
|
if (navigator.serviceWorker != null) {
|
||||||
navigator.serviceWorker.register('/service-worker.js').then(registration => {
|
navigator.serviceWorker.register('/service-worker.js').then(registration => {
|
||||||
|
|
|
||||||
8
lnbits/static/vendor/vue.js
vendored
8
lnbits/static/vendor/vue.js
vendored
|
|
@ -4431,12 +4431,12 @@
|
||||||
// options
|
// options
|
||||||
if (options) {
|
if (options) {
|
||||||
this.deep = !!options.deep;
|
this.deep = !!options.deep;
|
||||||
this.user = !!options.user;
|
this.g.user = !!options.user;
|
||||||
this.lazy = !!options.lazy;
|
this.lazy = !!options.lazy;
|
||||||
this.sync = !!options.sync;
|
this.sync = !!options.sync;
|
||||||
this.before = options.before;
|
this.before = options.before;
|
||||||
} else {
|
} else {
|
||||||
this.deep = this.user = this.lazy = this.sync = false;
|
this.deep = this.g.user = this.lazy = this.sync = false;
|
||||||
}
|
}
|
||||||
this.cb = cb;
|
this.cb = cb;
|
||||||
this.id = ++uid$2; // uid for batching
|
this.id = ++uid$2; // uid for batching
|
||||||
|
|
@ -4477,7 +4477,7 @@
|
||||||
try {
|
try {
|
||||||
value = this.getter.call(vm, vm);
|
value = this.getter.call(vm, vm);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (this.user) {
|
if (this.g.user) {
|
||||||
handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
|
handleError(e, vm, ("getter for watcher \"" + (this.expression) + "\""));
|
||||||
} else {
|
} else {
|
||||||
throw e
|
throw e
|
||||||
|
|
@ -4562,7 +4562,7 @@
|
||||||
// set new value
|
// set new value
|
||||||
var oldValue = this.value;
|
var oldValue = this.value;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
if (this.user) {
|
if (this.g.user) {
|
||||||
try {
|
try {
|
||||||
this.cb.call(this.vm, value, oldValue);
|
this.cb.call(this.vm, value, oldValue);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<q-list>
|
<q-list>
|
||||||
<q-item tag="a" href="/account" clickable v-close-popup
|
<q-item to="/account" clickable v-close-popup
|
||||||
><q-item-section>
|
><q-item-section>
|
||||||
<q-icon name="person" />
|
<q-icon name="person" />
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
|
|
@ -128,7 +128,7 @@
|
||||||
<q-item-label> </q-item-label>
|
<q-item-label> </q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item tag="a" href="/account#theme" clickable v-close-popup
|
<q-item to="/account#theme" clickable v-close-popup
|
||||||
><q-item-section>
|
><q-item-section>
|
||||||
<q-icon
|
<q-icon
|
||||||
:name="$q.dark.isActive ? 'dark_mode' : 'light_mode'"
|
:name="$q.dark.isActive ? 'dark_mode' : 'light_mode'"
|
||||||
|
|
@ -170,8 +170,22 @@
|
||||||
show-if-above
|
show-if-above
|
||||||
:elevated="$q.screen.lt.md"
|
:elevated="$q.screen.lt.md"
|
||||||
>
|
>
|
||||||
<lnbits-wallet-list :balance="balance"></lnbits-wallet-list>
|
<q-item-label
|
||||||
|
:style="$q.dark.isActive ? 'color:rgba(255, 255, 255, 0.64)' : ''"
|
||||||
|
class="q-item__label q-item__label--header"
|
||||||
|
header
|
||||||
|
v-text="$t('wallets')"
|
||||||
|
></q-item-label>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
:icon=" walletFlip ? 'keyboard_arrow_right' : 'keyboard_arrow_down'"
|
||||||
|
class="absolute-top-right"
|
||||||
|
@click="flipWallets($q.screen.lt.md)"
|
||||||
|
></q-btn>
|
||||||
|
<lnbits-wallet-list
|
||||||
|
v-if="!walletFlip"
|
||||||
|
:balance="balance"
|
||||||
|
></lnbits-wallet-list>
|
||||||
<lnbits-manage
|
<lnbits-manage
|
||||||
:show-admin="'{{LNBITS_ADMIN_UI}}' == 'True'"
|
:show-admin="'{{LNBITS_ADMIN_UI}}' == 'True'"
|
||||||
:show-users="'{{LNBITS_ADMIN_UI}}' == 'True'"
|
:show-users="'{{LNBITS_ADMIN_UI}}' == 'True'"
|
||||||
|
|
@ -184,7 +198,73 @@
|
||||||
{% endblock %} {% block page_container %}
|
{% endblock %} {% block page_container %}
|
||||||
<q-page-container>
|
<q-page-container>
|
||||||
<q-page class="q-px-md q-py-lg" :class="{'q-px-lg': $q.screen.gt.xs}">
|
<q-page class="q-px-md q-py-lg" :class="{'q-px-lg': $q.screen.gt.xs}">
|
||||||
{% block page %}{% endblock %}
|
<q-scroll-area
|
||||||
|
v-if="walletFlip"
|
||||||
|
style="
|
||||||
|
height: 130px;
|
||||||
|
width: 100%;
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<div class="row no-wrap q-gutter-md q-pr-md">
|
||||||
|
<q-card
|
||||||
|
v-for="wallet in g.user.wallets"
|
||||||
|
:key="wallet.id"
|
||||||
|
clickable
|
||||||
|
@click="selectWallet(wallet)"
|
||||||
|
class="wallet-list-card"
|
||||||
|
style="text-decoration: none"
|
||||||
|
:style="
|
||||||
|
g.wallet && g.wallet.id === wallet.id
|
||||||
|
? `border: 1px solid ${primaryColor}; width: 250px; text-decoration: none; cursor: pointer;`
|
||||||
|
: 'width: 250px; text-decoration: none; border: 0px; cursor: pointer;'
|
||||||
|
"
|
||||||
|
:class="{
|
||||||
|
'active-wallet-card': g.wallet && g.wallet.id === wallet.id
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<q-card-section>
|
||||||
|
<div class="row items-center">
|
||||||
|
<q-avatar
|
||||||
|
size="lg"
|
||||||
|
:color="
|
||||||
|
g.wallet && g.wallet.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 ? 'black' : 'grey-3'"
|
||||||
|
></q-icon>
|
||||||
|
</q-avatar>
|
||||||
|
<div
|
||||||
|
class="text-h6 q-pl-md"
|
||||||
|
:class="{
|
||||||
|
'text-bold': g.wallet && g.wallet.id === wallet.id
|
||||||
|
}"
|
||||||
|
v-text="wallet.name"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
<div class="row items-center q-pt-sm">
|
||||||
|
<h6 class="q-my-none text-no-wrap">
|
||||||
|
<strong v-text="wallet.fsat"></strong>
|
||||||
|
<small> {{LNBITS_DENOMINATION}}</small>
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</div>
|
||||||
|
</q-scroll-area>
|
||||||
|
|
||||||
|
<router-view v-if="isVueRoute"></router-view>
|
||||||
|
|
||||||
|
<!-- FastAPI Content -->
|
||||||
|
<div v-else>{% block page %}{% endblock %}</div>
|
||||||
</q-page>
|
</q-page>
|
||||||
</q-page-container>
|
</q-page-container>
|
||||||
{% endblock %} {% block footer %}
|
{% endblock %} {% block footer %}
|
||||||
|
|
@ -241,33 +321,43 @@
|
||||||
<script src="{{ static_url_for('static', url) }}"></script>
|
<script src="{{ static_url_for('static', url) }}"></script>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
const SITE_DESCRIPTION = {{ SITE_DESCRIPTION | tojson}}
|
const SITE_DESCRIPTION = {{ SITE_DESCRIPTION | tojson}}
|
||||||
const themes = {{ LNBITS_THEME_OPTIONS | tojson }}
|
const themes = {{ LNBITS_THEME_OPTIONS | tojson }}
|
||||||
const LNBITS_DENOMINATION = {{ LNBITS_DENOMINATION | tojson }}
|
const LNBITS_DENOMINATION = {{ LNBITS_DENOMINATION | tojson }}
|
||||||
const LNBITS_VERSION = {{ LNBITS_VERSION | tojson }}
|
const LNBITS_VERSION = {{ LNBITS_VERSION | tojson }}
|
||||||
const LNBITS_QR_LOGO = {{ LNBITS_QR_LOGO | tojson }}
|
const LNBITS_QR_LOGO = {{ LNBITS_QR_LOGO | tojson }}
|
||||||
if (themes && themes.length) {
|
if (themes && themes.length) {
|
||||||
window.allowedThemes = themes.map(str => str.trim())
|
window.allowedThemes = themes.map(str => str.trim())
|
||||||
}
|
}
|
||||||
window.langs = [
|
window.langs = [
|
||||||
{ value: 'en', label: 'English', display: '🇬🇧 EN' },
|
{ value: 'en', label: 'English', display: '🇬🇧 EN' },
|
||||||
{ value: 'de', label: 'Deutsch', display: '🇩🇪 DE' },
|
{ value: 'de', label: 'Deutsch', display: '🇩🇪 DE' },
|
||||||
{ value: 'es', label: 'Español', display: '🇪🇸 ES' },
|
{ value: 'es', label: 'Español', display: '🇪🇸 ES' },
|
||||||
{ value: 'jp', label: '日本語', display: '🇯🇵 JP' },
|
{ value: 'jp', label: '日本語', display: '🇯🇵 JP' },
|
||||||
{ value: 'cn', label: '中文', display: '🇨🇳 CN' },
|
{ value: 'cn', label: '中文', display: '🇨🇳 CN' },
|
||||||
{ value: 'fr', label: 'Français', display: '🇫🇷 FR' },
|
{ value: 'fr', label: 'Français', display: '🇫🇷 FR' },
|
||||||
{ value: 'it', label: 'Italiano', display: '🇮🇹 IT' },
|
{ value: 'it', label: 'Italiano', display: '🇮🇹 IT' },
|
||||||
{ value: 'pi', label: 'Pirate', display: '🏴☠️ PI' },
|
{ value: 'pi', label: 'Pirate', display: '🏴☠️ PI' },
|
||||||
{ value: 'nl', label: 'Nederlands', display: '🇳🇱 NL' },
|
{ value: 'nl', label: 'Nederlands', display: '🇳🇱 NL' },
|
||||||
{ value: 'we', label: 'Cymraeg', display: '🏴 CY' },
|
{ value: 'we', label: 'Cymraeg', display: '🏴 CY' },
|
||||||
{ value: 'pl', label: 'Polski', display: '🇵🇱 PL' },
|
{ value: 'pl', label: 'Polski', display: '🇵🇱 PL' },
|
||||||
{ value: 'pt', label: 'Português', display: '🇵🇹 PT' },
|
{ value: 'pt', label: 'Português', display: '🇵🇹 PT' },
|
||||||
{ value: 'br', label: 'Português do Brasil', display: '🇧🇷 BR' },
|
{ value: 'br', label: 'Português do Brasil', display: '🇧🇷 BR' },
|
||||||
{ value: 'cs', label: 'Česky', display: '🇨🇿 CS' },
|
{ value: 'cs', label: 'Česky', display: '🇨🇿 CS' },
|
||||||
{ value: 'sk', label: 'Slovensky', display: '🇸🇰 SK' },
|
{ value: 'sk', label: 'Slovensky', display: '🇸🇰 SK' },
|
||||||
{ value: 'kr', label: '한국어', display: '🇰🇷 KR' },
|
{ value: 'kr', label: '한국어', display: '🇰🇷 KR' },
|
||||||
{ value: 'fi', label: 'Suomi', display: '🇫🇮 FI' }
|
{ value: 'fi', label: 'Suomi', display: '🇫🇮 FI' }
|
||||||
]
|
]
|
||||||
|
window.LOCALE = 'en'
|
||||||
|
window.dateFormat = 'YYYY-MM-DD HH:mm'
|
||||||
|
window.i18n = new VueI18n.createI18n({
|
||||||
|
locale: window.LOCALE,
|
||||||
|
fallbackLocale: window.LOCALE,
|
||||||
|
messages: window.localisation
|
||||||
|
})
|
||||||
|
const websocketPrefix =
|
||||||
|
window.location.protocol === 'http:' ? 'ws://' : 'wss://'
|
||||||
|
const websocketUrl = `${'http:' ? 'ws://' : 'wss://'}${window.location.host}/api/v1/ws`
|
||||||
</script>
|
</script>
|
||||||
{% block scripts %}{% endblock %} {% for url in INCLUDED_COMPONENTS %}
|
{% block scripts %}{% endblock %} {% for url in INCLUDED_COMPONENTS %}
|
||||||
<script src="{{ static_url_for('static', url) }}"></script>
|
<script src="{{ static_url_for('static', url) }}"></script>
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,21 @@
|
||||||
<template id="lnbits-wallet-list">
|
<template id="lnbits-wallet-list">
|
||||||
<q-list
|
<q-list
|
||||||
v-if="user && user.wallets.length"
|
v-if="g.user && g.user.wallets.length"
|
||||||
dense
|
dense
|
||||||
class="lnbits-drawer__q-list"
|
class="lnbits-drawer__q-list"
|
||||||
>
|
>
|
||||||
<q-item-label header v-text="$t('wallets')"></q-item-label>
|
|
||||||
<q-item
|
<q-item
|
||||||
v-for="wallet in wallets"
|
v-for="walletRec in g.user.wallets"
|
||||||
:key="wallet.id"
|
:key="walletRec.id"
|
||||||
clickable
|
clickable
|
||||||
:active="activeWallet && activeWallet.id === wallet.id"
|
:active="g.wallet && g.wallet.id === walletRec.id"
|
||||||
tag="a"
|
@click="selectWallet(walletRec)"
|
||||||
:href="wallet.url"
|
|
||||||
>
|
>
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-avatar
|
<q-avatar
|
||||||
size="md"
|
size="md"
|
||||||
:color="
|
:color="
|
||||||
activeWallet && activeWallet.id === wallet.id
|
g.wallet && g.wallet.id === walletRec.id
|
||||||
? $q.dark.isActive
|
? $q.dark.isActive
|
||||||
? 'primary'
|
? 'primary'
|
||||||
: 'primary'
|
: 'primary'
|
||||||
|
|
@ -33,27 +31,23 @@
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label lines="1"
|
<q-item-label lines="1"
|
||||||
><span v-text="wallet.name"></span
|
><span v-text="walletRec.name"></span
|
||||||
></q-item-label>
|
></q-item-label>
|
||||||
<q-item-label v-if="LNBITS_DENOMINATION != 'sats'" caption>
|
<q-item-label v-if="LNBITS_DENOMINATION != 'sats'" caption>
|
||||||
<span
|
<span
|
||||||
v-text="
|
v-text="
|
||||||
parseFloat(String(wallet.live_fsat).replaceAll(',', '')) / 100
|
parseFloat(String(walletRec.fsat).replaceAll(',', '')) / 100
|
||||||
"
|
"
|
||||||
></span
|
></span
|
||||||
>
|
>
|
||||||
<span v-text="LNBITS_DENOMINATION"></span>
|
<span v-text="LNBITS_DENOMINATION"></span>
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
<q-item-label v-else caption>
|
<q-item-label v-else caption>
|
||||||
<span v-text="wallet.live_fsat"></span>
|
<span v-text="walletRec.fsat"></span>
|
||||||
<span v-text="LNBITS_DENOMINATION"></span>
|
<span v-text="LNBITS_DENOMINATION"></span>
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section
|
<q-item-section side v-show="g.wallet && g.wallet.id === walletRec.id">
|
||||||
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-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item clickable @click="showForm = !showForm">
|
<q-item clickable @click="showForm = !showForm">
|
||||||
|
|
@ -96,7 +90,9 @@
|
||||||
|
|
||||||
<template id="lnbits-extension-list">
|
<template id="lnbits-extension-list">
|
||||||
<q-list
|
<q-list
|
||||||
v-if="user && (userExtensions.length > 0 || !!searchTerm)"
|
v-if="
|
||||||
|
(g.user && userExtensions && userExtensions.length > 0) || !!searchTerm
|
||||||
|
"
|
||||||
dense
|
dense
|
||||||
class="lnbits-drawer__q-list"
|
class="lnbits-drawer__q-list"
|
||||||
>
|
>
|
||||||
|
|
@ -138,16 +134,10 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template id="lnbits-manage">
|
<template id="lnbits-manage">
|
||||||
<q-list v-if="user" dense class="lnbits-drawer__q-list">
|
<q-list v-if="g.user" dense class="lnbits-drawer__q-list">
|
||||||
<q-item-label header v-text="$t('manage')"></q-item-label>
|
<q-item-label header v-text="$t('manage')"></q-item-label>
|
||||||
<div v-if="user.admin">
|
<div v-if="g.user.admin">
|
||||||
<q-item
|
<q-item v-if="showAdmin" to="/admin">
|
||||||
v-if="showAdmin"
|
|
||||||
clickable
|
|
||||||
tag="a"
|
|
||||||
href="/admin"
|
|
||||||
:active="isActive('/admin')"
|
|
||||||
>
|
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-icon
|
<q-icon
|
||||||
name="admin_panel_settings"
|
name="admin_panel_settings"
|
||||||
|
|
@ -159,13 +149,7 @@
|
||||||
<q-item-label lines="1" v-text="$t('server')"></q-item-label>
|
<q-item-label lines="1" v-text="$t('server')"></q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item
|
<q-item v-if="showNode" to="/node">
|
||||||
v-if="showNode"
|
|
||||||
clickable
|
|
||||||
tag="a"
|
|
||||||
href="/node"
|
|
||||||
:active="isActive('/node')"
|
|
||||||
>
|
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-icon
|
<q-icon
|
||||||
name="developer_board"
|
name="developer_board"
|
||||||
|
|
@ -177,13 +161,7 @@
|
||||||
<q-item-label lines="1" v-text="$t('node')"></q-item-label>
|
<q-item-label lines="1" v-text="$t('node')"></q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item
|
<q-item v-if="showUsers" to="/users">
|
||||||
v-if="showUsers"
|
|
||||||
clickable
|
|
||||||
tag="a"
|
|
||||||
href="/users"
|
|
||||||
:active="isActive('/users')"
|
|
||||||
>
|
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-icon
|
<q-icon
|
||||||
name="groups"
|
name="groups"
|
||||||
|
|
@ -195,13 +173,7 @@
|
||||||
<q-item-label lines="1" v-text="$t('users')"></q-item-label>
|
<q-item-label lines="1" v-text="$t('users')"></q-item-label>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-item
|
<q-item v-if="showAudit" to="/audit">
|
||||||
v-if="showAudit"
|
|
||||||
clickable
|
|
||||||
tag="a"
|
|
||||||
href="/audit"
|
|
||||||
:active="isActive('/audit')"
|
|
||||||
>
|
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-icon
|
<q-icon
|
||||||
name="playlist_add_check_circle"
|
name="playlist_add_check_circle"
|
||||||
|
|
@ -214,13 +186,7 @@
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
</div>
|
</div>
|
||||||
<q-item
|
<q-item v-if="showExtensions" to="/extensions">
|
||||||
v-if="showExtensions"
|
|
||||||
clickable
|
|
||||||
tag="a"
|
|
||||||
href="/extensions"
|
|
||||||
:active="isActive('/extensions')"
|
|
||||||
>
|
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-icon
|
<q-icon
|
||||||
name="extension"
|
name="extension"
|
||||||
|
|
@ -236,7 +202,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template id="lnbits-payment-details">
|
<template id="lnbits-payment-details">
|
||||||
<div class="q-py-md" style="text-align: left">
|
<div v-if="payment" class="q-py-md" style="text-align: left">
|
||||||
<div v-if="payment.tag" class="row justify-center q-mb-md">
|
<div v-if="payment.tag" class="row justify-center q-mb-md">
|
||||||
<q-badge v-if="hasTag" color="yellow" text-color="black">
|
<q-badge v-if="hasTag" color="yellow" text-color="black">
|
||||||
#<span v-text="payment.tag"></span>
|
#<span v-text="payment.tag"></span>
|
||||||
|
|
@ -514,7 +480,7 @@
|
||||||
v-model="scope.value"
|
v-model="scope.value"
|
||||||
dense
|
dense
|
||||||
autofocus
|
autofocus
|
||||||
@keyup.enter="scope.set"
|
@keyup.enter="updateBalance(scope)"
|
||||||
>
|
>
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
<q-icon name="edit" />
|
<q-icon name="edit" />
|
||||||
|
|
@ -568,9 +534,8 @@
|
||||||
|
|
||||||
<template id="payment-list">
|
<template id="payment-list">
|
||||||
<div class="row items-center no-wrap">
|
<div class="row items-center no-wrap">
|
||||||
<div class="col">
|
<div class="col" v-if="!mobileSimple || $q.screen.gt.sm">
|
||||||
<q-input
|
<q-input
|
||||||
v-if="!mobileSimple"
|
|
||||||
:label="$t('search_by_tag_memo_amount')"
|
:label="$t('search_by_tag_memo_amount')"
|
||||||
dense
|
dense
|
||||||
class="q-pr-xl"
|
class="q-pr-xl"
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
{% macro window_vars(user, wallet) -%}
|
{% macro window_vars(user, wallet, extensions, extension_data) -%}
|
||||||
<script>
|
<script id="window-vars-script">
|
||||||
window.extensions = {{ EXTENSIONS | tojson | safe }};
|
window.extensions = JSON.parse('{{ EXTENSIONS | tojson | safe }}');
|
||||||
|
{% if extension_data %}
|
||||||
|
window.extension_data = {{ extension_data | tojson | safe }};
|
||||||
|
{% endif %}
|
||||||
{% if currencies %}
|
{% if currencies %}
|
||||||
window.currencies = {{ currencies | tojson | safe }};
|
window.currencies = {{ currencies | tojson | safe }};
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
@ -11,4 +14,16 @@
|
||||||
window.wallet = JSON.parse({{ wallet | tojson | safe }});
|
window.wallet = JSON.parse({{ wallet | tojson | safe }});
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</script>
|
</script>
|
||||||
|
<script>
|
||||||
|
//Needed for Vue to create the app on first load (although called on every page, its only loaded once)
|
||||||
|
window.app = Vue.createApp({
|
||||||
|
el: '#vue',
|
||||||
|
mixins: [window.windowMixin],
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
updatePayments: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue