feat: add dynamic errorpage (#3602)

This commit is contained in:
dni ⚡ 2025-12-02 13:33:25 +01:00 committed by GitHub
parent 89cabda123
commit eefaf3c50c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 143 additions and 117 deletions

View file

@ -221,6 +221,7 @@ async def index(
@generic_router.get("/") @generic_router.get("/")
@generic_router.get("/error")
@generic_router.get("/node/public") @generic_router.get("/node/public")
@generic_router.get("/first_install", dependencies=[Depends(check_first_install)]) @generic_router.get("/first_install", dependencies=[Depends(check_first_install)])
async def index_public(request: Request) -> HTMLResponse: async def index_public(request: Request) -> HTMLResponse:

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -454,3 +454,12 @@ video {
width: 90% !important; width: 90% !important;
} }
} }
.error-code {
font-size: clamp(15vh, 20vw, 30vh);
}
.error-message {
font-size: clamp(1.5rem, 3vw, 3.75rem);
font-weight: 300;
opacity: 0.4;
}

View file

@ -0,0 +1,53 @@
window.app.component('lnbits-error', {
template: '#lnbits-error',
mixins: [window.windowMixin],
props: ['dynamic', 'code', 'message'],
computed: {
isExtension() {
if (this.code != 403) return false
if (this.message.startsWith('Extension ')) return true
}
},
methods: {
goBack() {
window.history.back()
},
goHome() {
window.location = '/'
},
goToWallet() {
if (this.dynamic) {
this.$router.push('/wallet')
return
}
window.location = '/wallet'
},
goToExtension() {
const extension = this.message.match(/'([^']+)'/)[1]
const url = `/extensions#${extension}`
if (this.dynamic) {
this.$router.push(url)
return
}
window.location = url
},
async logOut() {
try {
await LNbits.api.logout()
window.location = '/'
} catch (e) {
LNbits.utils.notifyApiError(e)
}
}
},
async created() {
// check if we have error from error.html
if (!this.dynamic) {
if (this.code == 401) {
console.warn(`Unauthorized: ${this.errorMessage}`)
this.logOut()
return
}
}
}
})

View file

@ -9,6 +9,8 @@ const localStore = (key, defaultValue) => {
} }
window.g = Vue.reactive({ window.g = Vue.reactive({
errorCode: null,
errorMessage: null,
isUserAuthorized: !!Quasar.Cookies.get('is_lnbits_user_authorized'), isUserAuthorized: !!Quasar.Cookies.get('is_lnbits_user_authorized'),
offline: !navigator.onLine, offline: !navigator.onLine,
hasCamera: false, hasCamera: false,

View file

@ -78,6 +78,11 @@ const routes = [
path: '/', path: '/',
name: 'PageHome', name: 'PageHome',
component: PageHome component: PageHome
},
{
path: '/error',
name: 'PageError',
component: PageError
} }
] ]

View file

@ -0,0 +1,4 @@
window.PageError = {
template: '#page-error',
mixins: [window.windowMixin]
}

View file

@ -604,16 +604,17 @@ window.PageWallet = {
this.decodeRequest() this.decodeRequest()
this.parse.show = true this.parse.show = true
} }
let wallet = g.user.wallets.find(w => w.id === this.$route.params.id) const wallet = g.user.wallets.find(w => w.id === this.$route.params.id)
if (!wallet) { if (wallet) {
const walletId = g.lastActiveWallet || g.user.wallets[0].id
wallet = g.user.wallets.find(w => w.id === walletId)
// TODO: should show PageError(404) if wallet not found
}
this.g.wallet = wallet this.g.wallet = wallet
this.g.lastActiveWallet = wallet.id this.g.lastActiveWallet = wallet.id
this.$q.localStorage.setItem('lnbits.lastActiveWallet', wallet.id) this.$q.localStorage.setItem('lnbits.lastActiveWallet', wallet.id)
this.$router.replace(`/wallet/${wallet.id}`) this.$router.replace(`/wallet/${wallet.id}`)
} else {
this.g.errorCode = 404
this.g.errorMessage = 'Wallet not found.'
this.$router.push('/error')
}
}, },
watch: { watch: {
'g.updatePaymentsHash'() { 'g.updatePaymentsHash'() {

View file

@ -197,3 +197,12 @@ video {
} }
} }
} }
.error-code {
font-size: clamp(15vh, 20vw, 30vh);
}
.error-message {
font-size: clamp(1.5rem, calc(1.5 / 10 * 20vw), 3.75rem);
font-weight: 300;
opacity: 0.4;
}

View file

@ -42,6 +42,7 @@
"js/bolt11-decoder.js" "js/bolt11-decoder.js"
], ],
"components": [ "components": [
"js/pages/error.js",
"js/pages/home.js", "js/pages/home.js",
"js/pages/extensions_builder.js", "js/pages/extensions_builder.js",
"js/pages/extensions.js", "js/pages/extensions.js",
@ -75,6 +76,7 @@
"js/components/lnbits-wallet-paylinks.js", "js/components/lnbits-wallet-paylinks.js",
"js/components/lnbits-wallet-extra.js", "js/components/lnbits-wallet-extra.js",
"js/components/lnbits-home-logos.js", "js/components/lnbits-home-logos.js",
"js/components/lnbits-error.js",
"js/components/lnbits-qrcode.js", "js/components/lnbits-qrcode.js",
"js/components/lnbits-qrcode-lnurl.js", "js/components/lnbits-qrcode-lnurl.js",
"js/components/lnbits-disclaimer.js", "js/components/lnbits-disclaimer.js",

View file

@ -28,7 +28,8 @@ include('components/lnbits-wallet-api-docs.vue') %} {%
include('components/lnbits-wallet-share.vue') %} {% include('components/lnbits-wallet-share.vue') %} {%
include('components/lnbits-wallet-charts.vue') %} {% include('components/lnbits-wallet-charts.vue') %} {%
include('components/lnbits-wallet-paylinks.vue') %} {% include('components/lnbits-wallet-paylinks.vue') %} {%
include('components/lnbits-wallet-extra.vue') %} include('components/lnbits-wallet-extra.vue') %} {%
include('components/lnbits-error.vue') %}
<template id="lnbits-manage"> <template id="lnbits-manage">
<q-list v-if="g.user" dense class="lnbits-drawer__q-list"> <q-list v-if="g.user" dense class="lnbits-drawer__q-list">

View file

@ -24,7 +24,6 @@
flat flat
:icon="g.walletFlip ? 'view_list' : 'view_column'" :icon="g.walletFlip ? 'view_list' : 'view_column'"
color="grey" color="grey"
class=""
@click="g.walletFlip = !g.walletFlip" @click="g.walletFlip = !g.walletFlip"
> >
<q-tooltip <q-tooltip

View file

@ -0,0 +1,25 @@
<template id="lnbits-error">
<div class="text-center q-pa-md flex flex-center">
<div>
<div class="error-code" v-text="code"></div>
<div class="error-message" v-text="message"></div>
<div class="q-mx-auto q-mt-lg justify-center" style="width: max-content">
<q-btn
v-if="isExtension"
color="primary"
@click="goToExtension()"
label="Go To Extension"
></q-btn>
<q-btn
v-else-if="g.isUserAuthorized"
color="primary"
@click="goToWallet()"
label="Go to Wallet"
></q-btn>
<q-btn v-else color="primary" @click="goBack()" label="Go Back"></q-btn>
<span class="q-mx-md">OR</span>
<q-btn color="secondary" @click="goHome()" label="Go Home"></q-btn>
</div>
</div>
</div>
</template>

View file

@ -1,102 +1,5 @@
{% extends "public.html" %} {% block page_container %} {% extends "public.html" %} {% from "macros.jinja" import window_vars with
<q-page-container> context %} {% block scripts %} {{ window_vars() }} {% endblock %} {% block
<q-page page_container %}
class="q-px-md q-py-lg content-center" <lnbits-error code="{{ status_code }}" message="{{ message }}"></lnbits-error>
:class="{'q-px-lg': $q.screen.gt.xs}"
>
{% block page %}
<div class="text-center q-pa-md flex flex-center">
<div v-if="statusCode">
<div class="error-code" v-text="statusCode"></div>
<div class="error-message" v-text="message"></div>
<div
class="q-mx-auto q-mt-lg justify-center"
style="width: max-content"
>
<q-btn
v-if="isExtension"
color="primary"
@click="goToExtension"
label="Go To Extension"
class="q-mb-lg full-width"
></q-btn>
<br />
<q-btn color="primary" href="/" label="Go Home"></q-btn>
<span class="q-mx-md">OR</span>
<q-btn color="primary" @click="goBack" label="Go Back"></q-btn>
</div>
</div>
</div>
{% endblock %}
</q-page>
</q-page-container>
{% endblock %} {% block scripts %}
<style>
.error-code {
font-size: clamp(15vh, 20vw, 30vh);
}
.error-message {
font-size: clamp(1.5rem, calc(1.5 / 10 * 20vw), 3.75rem);
font-weight: 300;
opacity: 0.4;
}
</style>
<script>
window.app = Vue.createApp({
el: '#vue',
mixins: [window.windowMixin],
data() {
return {
err: null,
statusCode: null,
message: null
}
},
methods: {
goBack: function () {
window.history.back()
},
goHome: function () {
window.location.href = '/'
},
goToExtension() {
window.location.href = `/extensions#${this.extension}`
},
async logOut() {
try {
await LNbits.api.logout()
window.location = '/'
} catch (e) {
LNbits.utils.notifyApiError(e)
}
},
},
computed: {
isExtension() {
if (this.statusCode != 403) return false
if (this.message.startsWith('Extension ')) return true
}
},
async created() {
this.err = '{{ err }}'
const statusCode = '{{ status_code }}' || 404
this.message = String({{ message | tojson }}) || 'Page not found'
if (statusCode == 401) {
console.warn(`Unauthorized: ${this.message}`)
this.logOut()
return
}
this.statusCode = statusCode
if (this.isExtension) {
this.extension = this.message.match(/'([^']+)'/)[1]
}
}
})
</script>
{% endblock %} {% endblock %}

View file

@ -3,4 +3,5 @@ include('pages/audit.vue') %} {% include('pages/wallets.vue') %} {%
include('pages/users.vue') %} {% include('pages/admin.vue') %} {% include('pages/users.vue') %} {% include('pages/admin.vue') %} {%
include('pages/account.vue') %} {% include('pages/extensions_builder.vue') %} {% include('pages/account.vue') %} {% include('pages/extensions_builder.vue') %} {%
include('pages/extensions.vue') %} {% include('pages/first-install.vue') %} {% include('pages/extensions.vue') %} {% include('pages/first-install.vue') %} {%
include('pages/home.vue') %} {% include('pages/wallet.vue') %} include('pages/home.vue') %} {% include('pages/wallet.vue') %} {%
include('pages/error.vue') %}

View file

@ -0,0 +1,9 @@
<template id="page-error">
<div id="page-error">
<lnbits-error
:dynamic="true"
:code="g.errorCode"
:message="g.errorMessage"
></lnbits-error>
</div>
</template>

View file

@ -1,5 +1,5 @@
<template id="page-wallet"> <template id="page-wallet">
<div class="row q-col-gutter-md" style="margin-bottom: 6rem"> <div v-if="g.wallet" class="row q-col-gutter-md" style="margin-bottom: 6rem">
<div class="col-12 col-md-7 q-gutter-y-md wallet-wrapper"> <div class="col-12 col-md-7 q-gutter-y-md wallet-wrapper">
<q-card class="wallet-card"> <q-card class="wallet-card">
<q-card-section> <q-card-section>

View file

@ -94,6 +94,7 @@
"js/bolt11-decoder.js" "js/bolt11-decoder.js"
], ],
"components": [ "components": [
"js/pages/error.js",
"js/pages/home.js", "js/pages/home.js",
"js/pages/extensions_builder.js", "js/pages/extensions_builder.js",
"js/pages/extensions.js", "js/pages/extensions.js",
@ -127,6 +128,7 @@
"js/components/lnbits-wallet-paylinks.js", "js/components/lnbits-wallet-paylinks.js",
"js/components/lnbits-wallet-extra.js", "js/components/lnbits-wallet-extra.js",
"js/components/lnbits-home-logos.js", "js/components/lnbits-home-logos.js",
"js/components/lnbits-error.js",
"js/components/lnbits-qrcode.js", "js/components/lnbits-qrcode.js",
"js/components/lnbits-qrcode-lnurl.js", "js/components/lnbits-qrcode-lnurl.js",
"js/components/lnbits-disclaimer.js", "js/components/lnbits-disclaimer.js",