feat: adding root path functionality
- add --root-path to lnbits application start to use in uvicorn - utils.urlFor is aware of root-path - api.request is aware of root-path TODO: dynamic component loading kinda works, but older extension loading is curently broken because of path issues on `normalize_path` unhardcode api fixup nocache unhardcode feat: adding root path functionality - add --root-path to lnbits application start to use in uvicorn - utils.urlFor is aware of root-path - api.request is aware of root-path TODO: dynamic component loading kinda works, but older extension loading is curently broken because of path issues on `normalize_path`
This commit is contained in:
@@ -32,7 +32,7 @@ generic_router = APIRouter(
|
|||||||
|
|
||||||
@generic_router.get("/favicon.ico", response_class=FileResponse)
|
@generic_router.get("/favicon.ico", response_class=FileResponse)
|
||||||
async def favicon():
|
async def favicon():
|
||||||
return RedirectResponse(settings.lnbits_qr_logo)
|
return RedirectResponse(settings.root_path + settings.lnbits_qr_logo)
|
||||||
|
|
||||||
|
|
||||||
@generic_router.get("/robots.txt", response_class=HTMLResponse)
|
@generic_router.get("/robots.txt", response_class=HTMLResponse)
|
||||||
|
|||||||
@@ -517,6 +517,7 @@ async def _check_account_api_access(
|
|||||||
raise HTTPException(HTTPStatus.FORBIDDEN, "Method not allowed.")
|
raise HTTPException(HTTPStatus.FORBIDDEN, "Method not allowed.")
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: this messes up my extension urls
|
||||||
def url_for_interceptor(original_method):
|
def url_for_interceptor(original_method):
|
||||||
def normalize_url(self, *args, **kwargs):
|
def normalize_url(self, *args, **kwargs):
|
||||||
url = original_method(self, *args, **kwargs)
|
url = original_method(self, *args, **kwargs)
|
||||||
@@ -527,6 +528,7 @@ def url_for_interceptor(original_method):
|
|||||||
|
|
||||||
# Upgraded extensions modify the path.
|
# Upgraded extensions modify the path.
|
||||||
# This interceptor ensures that the path is normalized.
|
# This interceptor ensures that the path is normalized.
|
||||||
|
# TODO: this messes up my extension urls
|
||||||
Request.url_for = url_for_interceptor(Request.url_for) # type: ignore[method-assign]
|
Request.url_for = url_for_interceptor(Request.url_for) # type: ignore[method-assign]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+17
-4
@@ -48,8 +48,11 @@ def url_for(endpoint: str, external: bool | None = False, **params: Any) -> str:
|
|||||||
return url
|
return url
|
||||||
|
|
||||||
|
|
||||||
def static_url_for(static: str, path: str) -> str:
|
def static_url_for(static: str, path: str, no_cache: bool = False) -> str:
|
||||||
return f"/{static}/{path}?v={settings.server_startup_time}"
|
url = f"{settings.root_path}{static}/{path}"
|
||||||
|
if no_cache:
|
||||||
|
url += f"?v={settings.server_startup_time}"
|
||||||
|
return url
|
||||||
|
|
||||||
|
|
||||||
def template_renderer(additional_folders: list | None = None) -> Jinja2Templates:
|
def template_renderer(additional_folders: list | None = None) -> Jinja2Templates:
|
||||||
@@ -57,7 +60,6 @@ def template_renderer(additional_folders: list | None = None) -> Jinja2Templates
|
|||||||
"lnbits/templates",
|
"lnbits/templates",
|
||||||
settings.extension_builder_working_dir_path.as_posix(),
|
settings.extension_builder_working_dir_path.as_posix(),
|
||||||
]
|
]
|
||||||
|
|
||||||
if additional_folders:
|
if additional_folders:
|
||||||
additional_folders += [
|
additional_folders += [
|
||||||
Path(settings.lnbits_extensions_path, "extensions", f)
|
Path(settings.lnbits_extensions_path, "extensions", f)
|
||||||
@@ -69,6 +71,7 @@ def template_renderer(additional_folders: list | None = None) -> Jinja2Templates
|
|||||||
t.env.globals["normalize_path"] = normalize_path
|
t.env.globals["normalize_path"] = normalize_path
|
||||||
|
|
||||||
# used in base.html
|
# used in base.html
|
||||||
|
t.env.globals["ROOT_PATH"] = settings.root_path
|
||||||
t.env.globals["SITE_TITLE"] = settings.lnbits_site_title
|
t.env.globals["SITE_TITLE"] = settings.lnbits_site_title
|
||||||
t.env.globals["LNBITS_APPLE_TOUCH_ICON"] = settings.lnbits_apple_touch_icon
|
t.env.globals["LNBITS_APPLE_TOUCH_ICON"] = settings.lnbits_apple_touch_icon
|
||||||
t.env.globals["SETTINGS"] = settings.to_public().dict(by_alias=True)
|
t.env.globals["SETTINGS"] = settings.to_public().dict(by_alias=True)
|
||||||
@@ -310,6 +313,8 @@ def get_api_routes(routes: list) -> dict[str, str]:
|
|||||||
|
|
||||||
def path_segments(path: str) -> list[str]:
|
def path_segments(path: str) -> list[str]:
|
||||||
path = path.strip("/")
|
path = path.strip("/")
|
||||||
|
# Remove empty segments caused by '//' in the path
|
||||||
|
# segments = [s for s in path.split("/") if s]
|
||||||
segments = path.split("/")
|
segments = path.split("/")
|
||||||
if len(segments) < 2:
|
if len(segments) < 2:
|
||||||
return segments
|
return segments
|
||||||
@@ -319,8 +324,16 @@ def path_segments(path: str) -> list[str]:
|
|||||||
|
|
||||||
|
|
||||||
def normalize_path(path: str | None) -> str:
|
def normalize_path(path: str | None) -> str:
|
||||||
|
print(path)
|
||||||
path = path or ""
|
path = path or ""
|
||||||
return "/" + "/".join(path_segments(path))
|
segments = path_segments(path)
|
||||||
|
print(segments)
|
||||||
|
joined = "/".join(segments)
|
||||||
|
print("!!!!!!!!!!")
|
||||||
|
print(joined)
|
||||||
|
return joined
|
||||||
|
|
||||||
|
# return "/" + "/".join(path_segments(path))
|
||||||
|
|
||||||
|
|
||||||
def normalize_endpoint(endpoint: str, add_proto=True) -> str:
|
def normalize_endpoint(endpoint: str, add_proto=True) -> str:
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ from lnbits.helpers import normalize_path, template_renderer
|
|||||||
from lnbits.settings import settings
|
from lnbits.settings import settings
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: root path should be considered here?
|
||||||
class InstalledExtensionMiddleware:
|
class InstalledExtensionMiddleware:
|
||||||
# This middleware class intercepts calls made to the extensions API and:
|
# This middleware class intercepts calls made to the extensions API and:
|
||||||
# - it blocks the calls if the extension has been disabled or uninstalled.
|
# - it blocks the calls if the extension has been disabled or uninstalled.
|
||||||
@@ -51,7 +52,7 @@ class InstalledExtensionMiddleware:
|
|||||||
await self.app(scope, receive, send)
|
await self.app(scope, receive, send)
|
||||||
return
|
return
|
||||||
|
|
||||||
# re-route all trafic if the extension has been upgraded
|
# re-route all traffic if the extension has been upgraded
|
||||||
if top_path in settings.lnbits_upgraded_extensions:
|
if top_path in settings.lnbits_upgraded_extensions:
|
||||||
upgrade_path = (
|
upgrade_path = (
|
||||||
f"""{settings.lnbits_upgraded_extensions[top_path]}/{top_path}"""
|
f"""{settings.lnbits_upgraded_extensions[top_path]}/{top_path}"""
|
||||||
@@ -241,6 +242,7 @@ def add_first_install_middleware(app: FastAPI):
|
|||||||
async def first_install_middleware(request: Request, call_next):
|
async def first_install_middleware(request: Request, call_next):
|
||||||
if (
|
if (
|
||||||
settings.first_install
|
settings.first_install
|
||||||
|
# TODO: root path should be considered here?
|
||||||
and request.url.path != "/api/v1/auth/first_install"
|
and request.url.path != "/api/v1/auth/first_install"
|
||||||
and request.url.path != "/first_install"
|
and request.url.path != "/first_install"
|
||||||
and not request.url.path.startswith("/static")
|
and not request.url.path.startswith("/static")
|
||||||
|
|||||||
+13
-1
@@ -17,6 +17,11 @@ from lnbits.settings import set_cli_settings, settings
|
|||||||
)
|
)
|
||||||
@click.option("--port", default=settings.port, help="Port to listen on")
|
@click.option("--port", default=settings.port, help="Port to listen on")
|
||||||
@click.option("--host", default=settings.host, help="Host to run LNbits on")
|
@click.option("--host", default=settings.host, help="Host to run LNbits on")
|
||||||
|
@click.option(
|
||||||
|
"--root-path",
|
||||||
|
default=settings.root_path,
|
||||||
|
help="Root path of proxy, my.lnbits.com/rootpath ",
|
||||||
|
)
|
||||||
@click.option(
|
@click.option(
|
||||||
"--forwarded-allow-ips",
|
"--forwarded-allow-ips",
|
||||||
default=settings.forwarded_allow_ips,
|
default=settings.forwarded_allow_ips,
|
||||||
@@ -30,6 +35,7 @@ from lnbits.settings import set_cli_settings, settings
|
|||||||
def main(
|
def main(
|
||||||
port: int,
|
port: int,
|
||||||
host: str,
|
host: str,
|
||||||
|
root_path: str,
|
||||||
forwarded_allow_ips: str,
|
forwarded_allow_ips: str,
|
||||||
ssl_keyfile: str,
|
ssl_keyfile: str,
|
||||||
ssl_certfile: str,
|
ssl_certfile: str,
|
||||||
@@ -46,7 +52,12 @@ def main(
|
|||||||
parents=True, exist_ok=True
|
parents=True, exist_ok=True
|
||||||
)
|
)
|
||||||
|
|
||||||
set_cli_settings(host=host, port=port, forwarded_allow_ips=forwarded_allow_ips)
|
set_cli_settings(
|
||||||
|
host=host,
|
||||||
|
port=port,
|
||||||
|
forwarded_allow_ips=forwarded_allow_ips,
|
||||||
|
root_path=root_path,
|
||||||
|
)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
config = uvicorn.Config(
|
config = uvicorn.Config(
|
||||||
@@ -54,6 +65,7 @@ def main(
|
|||||||
loop="uvloop",
|
loop="uvloop",
|
||||||
port=port,
|
port=port,
|
||||||
host=host,
|
host=host,
|
||||||
|
root_path=root_path,
|
||||||
forwarded_allow_ips=forwarded_allow_ips,
|
forwarded_allow_ips=forwarded_allow_ips,
|
||||||
ssl_keyfile=ssl_keyfile,
|
ssl_keyfile=ssl_keyfile,
|
||||||
ssl_certfile=ssl_certfile,
|
ssl_certfile=ssl_certfile,
|
||||||
|
|||||||
@@ -1060,6 +1060,7 @@ class EnvSettings(LNbitsSettings):
|
|||||||
auth_https_only: bool = Field(default=True)
|
auth_https_only: bool = Field(default=True)
|
||||||
host: str = Field(default="127.0.0.1")
|
host: str = Field(default="127.0.0.1")
|
||||||
port: int = Field(default=5000, gt=0)
|
port: int = Field(default=5000, gt=0)
|
||||||
|
root_path: str = Field(default="/")
|
||||||
forwarded_allow_ips: str = Field(default="*")
|
forwarded_allow_ips: str = Field(default="*")
|
||||||
lnbits_title: str = Field(default="LNbits API")
|
lnbits_title: str = Field(default="LNbits API")
|
||||||
lnbits_path: str = Field(default=".")
|
lnbits_path: str = Field(default=".")
|
||||||
|
|||||||
+19
-51
@@ -1,5 +1,7 @@
|
|||||||
window._lnbitsApi = {
|
window._lnbitsApi = {
|
||||||
request(method, url, apiKey, data, options = {}) {
|
request(method, url, apiKey, data, options = {}) {
|
||||||
|
url = ROOT_PATH + url.replace(/^\/+/, '') // Ensure single slash after rootPath
|
||||||
|
console.log(`API Request: ${method.toUpperCase()} ${url}`)
|
||||||
return axios({
|
return axios({
|
||||||
method: method,
|
method: method,
|
||||||
url: url,
|
url: url,
|
||||||
@@ -67,75 +69,41 @@ window._lnbitsApi = {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
register(username, email, password, password_repeat, invitation_code) {
|
register(username, email, password, password_repeat, invitation_code) {
|
||||||
return axios({
|
return this.request('post', '/api/v1/auth/register', null, {
|
||||||
method: 'POST',
|
username,
|
||||||
url: '/api/v1/auth/register',
|
email,
|
||||||
data: {
|
password,
|
||||||
username,
|
password_repeat,
|
||||||
email,
|
invitation_code
|
||||||
password,
|
|
||||||
password_repeat,
|
|
||||||
invitation_code
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
reset(reset_key, password, password_repeat) {
|
reset(reset_key, password, password_repeat) {
|
||||||
return axios({
|
return this.request('put', '/api/v1/auth/reset', null, {
|
||||||
method: 'PUT',
|
reset_key,
|
||||||
url: '/api/v1/auth/reset',
|
password,
|
||||||
data: {
|
password_repeat
|
||||||
reset_key,
|
|
||||||
password,
|
|
||||||
password_repeat
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
getAuthUser() {
|
getAuthUser() {
|
||||||
return axios({
|
return this.request('get', '/api/v1/auth')
|
||||||
method: 'GET',
|
|
||||||
url: '/api/v1/auth'
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
login(username, password) {
|
login(username, password) {
|
||||||
return axios({
|
return this.request('post', '/api/v1/auth', null, {username, password})
|
||||||
method: 'POST',
|
|
||||||
url: '/api/v1/auth',
|
|
||||||
data: {username, password}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
loginByProvider(provider, headers, data) {
|
loginByProvider(provider, headers, data) {
|
||||||
return axios({
|
return this.request('post', `/api/v1/auth/${provider}`, null, data)
|
||||||
method: 'POST',
|
|
||||||
url: `/api/v1/auth/${provider}`,
|
|
||||||
headers: headers,
|
|
||||||
data
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
loginUsr(usr) {
|
loginUsr(usr) {
|
||||||
return axios({
|
return this.request('post', '/api/v1/auth/usr', null, {usr})
|
||||||
method: 'POST',
|
|
||||||
url: '/api/v1/auth/usr',
|
|
||||||
data: {usr}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
logout() {
|
logout() {
|
||||||
return axios({
|
return this.request('post', '/api/v1/auth/logout')
|
||||||
method: 'POST',
|
|
||||||
url: '/api/v1/auth/logout'
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
impersonateUser(usr) {
|
impersonateUser(usr) {
|
||||||
return axios({
|
return this.request('POST', '/api/v1/auth/impersonate', null, {usr})
|
||||||
method: 'POST',
|
|
||||||
url: '/api/v1/auth/impersonate',
|
|
||||||
data: {usr}
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
stopImpersonation() {
|
stopImpersonation() {
|
||||||
return axios({
|
return this.request('DELETE', '/api/v1/auth/impersonate')
|
||||||
method: 'DELETE',
|
|
||||||
url: '/api/v1/auth/impersonate'
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
getAuthenticatedUser() {
|
getAuthenticatedUser() {
|
||||||
return this.request('get', '/api/v1/auth')
|
return this.request('get', '/api/v1/auth')
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ window.dateFormat = 'YYYY-MM-DD HH:mm'
|
|||||||
|
|
||||||
const websocketPrefix =
|
const websocketPrefix =
|
||||||
window.location.protocol === 'http:' ? 'ws://' : 'wss://'
|
window.location.protocol === 'http:' ? 'ws://' : 'wss://'
|
||||||
const websocketUrl = `${websocketPrefix}${window.location.host}/api/v1/ws`
|
const websocketUrl = `${websocketPrefix}${window.location.host}${ROOT_PATH}api/v1/ws`
|
||||||
|
|
||||||
const _access_cookies_for_safari_refresh_do_not_delete = document.cookie
|
const _access_cookies_for_safari_refresh_do_not_delete = document.cookie
|
||||||
|
|
||||||
@@ -87,9 +87,11 @@ addEventListener('online', event => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (navigator.serviceWorker != null) {
|
if (navigator.serviceWorker != null) {
|
||||||
navigator.serviceWorker.register('/service-worker.js').then(registration => {
|
navigator.serviceWorker
|
||||||
console.log('Registered events at scope: ', registration.scope)
|
.register(ROOT_PATH + 'service-worker.js')
|
||||||
})
|
.then(registration => {
|
||||||
|
console.log('Registered events at scope: ', registration.scope)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) {
|
if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) {
|
||||||
|
|||||||
@@ -11,11 +11,16 @@ const quasarConfig = {
|
|||||||
|
|
||||||
const DynamicComponent = {
|
const DynamicComponent = {
|
||||||
async created() {
|
async created() {
|
||||||
|
// no trailing /
|
||||||
|
const rootPath = ROOT_PATH.replace(/\/+$/, '')
|
||||||
const name = this.$route.path.split('/')[1]
|
const name = this.$route.path.split('/')[1]
|
||||||
const path = `/${name}/`
|
const path = `${rootPath}/${name}/`
|
||||||
const routesPath = `/${name}/static/routes.json`
|
const routesPath = `${rootPath}/${name}/static/routes.json`
|
||||||
if (this.$router.getRoutes().some(r => r.path === path)) return
|
if (this.$router.getRoutes().some(r => r.path === path)) return
|
||||||
if (this.$route.fullPath.startsWith('/extensions/builder/preview')) return
|
if (
|
||||||
|
this.$route.fullPath.startsWith(rootPath + '/extensions/builder/preview')
|
||||||
|
)
|
||||||
|
return
|
||||||
fetch(routesPath)
|
fetch(routesPath)
|
||||||
.then(async res => {
|
.then(async res => {
|
||||||
if (!res.ok) throw new Error('No dynamic routes found')
|
if (!res.ok) throw new Error('No dynamic routes found')
|
||||||
@@ -38,9 +43,17 @@ const DynamicComponent = {
|
|||||||
let route = RENDERED_ROUTE
|
let route = RENDERED_ROUTE
|
||||||
// append trailing slash only on the root path `/path` -> `/path/`
|
// append trailing slash only on the root path `/path` -> `/path/`
|
||||||
if (route.split('/').length === 2) route += '/'
|
if (route.split('/').length === 2) route += '/'
|
||||||
|
console.log('ROUTE', route)
|
||||||
|
|
||||||
|
console.log('path / fullpath', this.$route.path, this.$route.fullPath)
|
||||||
|
|
||||||
if (route !== this.$route.path) {
|
if (route !== this.$route.path) {
|
||||||
console.log('Redirecting to non-vue route:', this.$route.fullPath)
|
const rootPath = ROOT_PATH.replace(/\/+$/, '')
|
||||||
window.location = this.$route.fullPath
|
console.log(
|
||||||
|
'Redirecting to non-vue route:',
|
||||||
|
rootPath + this.$route.fullPath
|
||||||
|
)
|
||||||
|
// window.location = rootPath + this.$route.fullPath
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -139,7 +152,7 @@ const routes = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
window.router = VueRouter.createRouter({
|
window.router = VueRouter.createRouter({
|
||||||
history: VueRouter.createWebHistory(),
|
history: VueRouter.createWebHistory(ROOT_PATH),
|
||||||
routes
|
routes
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,17 @@
|
|||||||
window._lnbitsUtils = {
|
window._lnbitsUtils = {
|
||||||
url_for(url) {
|
urlFor(url, noCache = false) {
|
||||||
const _url = new URL(url, window.location.origin)
|
if (url.startsWith('http://') || url.startsWith('https://')) {
|
||||||
_url.searchParams.set('v', window.g.settings.cacheKey)
|
return url
|
||||||
|
}
|
||||||
|
const rootPath = ROOT_PATH.replace(/\/+$/, '')
|
||||||
|
const _url = new URL(rootPath + url, window.location.origin)
|
||||||
|
if (!noCache) _url.searchParams.set('v', window.g.settings.cacheKey)
|
||||||
return _url.toString()
|
return _url.toString()
|
||||||
},
|
},
|
||||||
loadScript(src) {
|
loadScript(src) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const script = document.createElement('script')
|
const script = document.createElement('script')
|
||||||
script.src = this.url_for(src)
|
script.src = this.urlFor(src)
|
||||||
script.onload = () => {
|
script.onload = () => {
|
||||||
resolve()
|
resolve()
|
||||||
}
|
}
|
||||||
@@ -18,7 +22,7 @@ window._lnbitsUtils = {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
async loadTemplate(url) {
|
async loadTemplate(url) {
|
||||||
return fetch(this.url_for(url))
|
return fetch(this.urlFor(url))
|
||||||
.then(response => {
|
.then(response => {
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`Failed to load template from ${url}`)
|
throw new Error(`Failed to load template from ${url}`)
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
name="viewport"
|
name="viewport"
|
||||||
content="width=device-width, initial-scale=1, maximum-scale=1, shrink-to-fit=no"
|
content="width=device-width, initial-scale=1, maximum-scale=1, shrink-to-fit=no"
|
||||||
/>
|
/>
|
||||||
|
<link rel="icon" type="image/x-icon" href="{{ ROOT_PATH }}favicon.ico" />
|
||||||
<meta name="mobile-web-app-capable" content="yes" />
|
<meta name="mobile-web-app-capable" content="yes" />
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
<link
|
<link
|
||||||
@@ -32,6 +33,13 @@
|
|||||||
{% if web_manifest %}
|
{% if web_manifest %}
|
||||||
<link async="async" rel="manifest" href="{{ web_manifest }}" />
|
<link async="async" rel="manifest" href="{{ web_manifest }}" />
|
||||||
{% endif %} {% block head_scripts %}{% endblock %}
|
{% endif %} {% block head_scripts %}{% endblock %}
|
||||||
|
<script type="text/javascript">
|
||||||
|
const ROOT_PATH = '{{ ROOT_PATH }}'
|
||||||
|
const RENDERED_ROUTE = '{{ normalize_path(request.path) }}'.replace(
|
||||||
|
ROOT_PATH.replace(/\/+$/, '') || '/',
|
||||||
|
''
|
||||||
|
)
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body data-theme="bitcoin">
|
<body data-theme="bitcoin">
|
||||||
@@ -52,9 +60,7 @@
|
|||||||
v-if="g.user && !g.isPublicPage"
|
v-if="g.user && !g.isPublicPage"
|
||||||
></lnbits-header-wallets>
|
></lnbits-header-wallets>
|
||||||
<!-- block page content from static extensions -->
|
<!-- block page content from static extensions -->
|
||||||
<div
|
<div v-if="$route.path.startsWith(RENDERED_ROUTE)">
|
||||||
v-if="$route.path.startsWith('{{ normalize_path(request.path) }}')"
|
|
||||||
>
|
|
||||||
{% block page %}{% endblock %}
|
{% block page %}{% endblock %}
|
||||||
</div>
|
</div>
|
||||||
<!-- vue router-view -->
|
<!-- vue router-view -->
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
@click="g.visibleDrawer = !g.visibleDrawer"
|
@click="g.visibleDrawer = !g.visibleDrawer"
|
||||||
></q-btn>
|
></q-btn>
|
||||||
<q-toolbar-title>
|
<q-toolbar-title>
|
||||||
<q-btn flat no-caps dense class="q-mr-sm" size="lg" type="a" href="/">
|
<q-btn flat no-caps dense class="q-mr-sm" size="lg" type="a" :href="utils.urlFor('/', true)">
|
||||||
<q-avatar v-if="g.settings.customLogo" height="30px">
|
<q-avatar v-if="g.settings.customLogo" height="30px">
|
||||||
<img alt="Logo" :src="g.settings.customLogo" />
|
<img alt="Logo" :src="g.settings.customLogo" />
|
||||||
</q-avatar>
|
</q-avatar>
|
||||||
|
|||||||
@@ -7,7 +7,9 @@
|
|||||||
<a :href="logo.url" target="_blank" rel="noopener noreferrer">
|
<a :href="logo.url" target="_blank" rel="noopener noreferrer">
|
||||||
<q-img
|
<q-img
|
||||||
contain
|
contain
|
||||||
:src="$q.dark.isActive ? logo.darkSrc : logo.lightSrc"
|
:src="
|
||||||
|
utils.urlFor($q.dark.isActive ? logo.darkSrc : logo.lightSrc)
|
||||||
|
"
|
||||||
></q-img>
|
></q-img>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -18,7 +20,9 @@
|
|||||||
<a :href="logo.url" target="_blank" rel="noopener noreferrer">
|
<a :href="logo.url" target="_blank" rel="noopener noreferrer">
|
||||||
<q-img
|
<q-img
|
||||||
contain
|
contain
|
||||||
:src="$q.dark.isActive ? logo.darkSrc : logo.lightSrc"
|
:src="
|
||||||
|
utils.urlFor($q.dark.isActive ? logo.darkSrc : logo.lightSrc)
|
||||||
|
"
|
||||||
></q-img>
|
></q-img>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,11 +20,14 @@
|
|||||||
clickable
|
clickable
|
||||||
:active="$route.path.startsWith('/' + extension.code)"
|
:active="$route.path.startsWith('/' + extension.code)"
|
||||||
tag="a"
|
tag="a"
|
||||||
:to="'/' + extension.code + '/'"
|
:to="`/${extension.code}/`"
|
||||||
>
|
>
|
||||||
<q-item-section side>
|
<q-item-section side>
|
||||||
<q-avatar size="md">
|
<q-avatar size="md">
|
||||||
<q-img :src="extension.tile" style="max-width: 20px"></q-img>
|
<q-img
|
||||||
|
:src="utils.urlFor(extension.tile)"
|
||||||
|
style="max-width: 20px"
|
||||||
|
></q-img>
|
||||||
</q-avatar>
|
</q-avatar>
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
|
|||||||
@@ -285,7 +285,9 @@
|
|||||||
>
|
>
|
||||||
<q-avatar size="32px" class="q-mr-md">
|
<q-avatar size="32px" class="q-mr-md">
|
||||||
<q-img
|
<q-img
|
||||||
:src="'{{ static_url_for('static', 'images/google-logo.png') }}'"
|
:src="
|
||||||
|
utils.urlFor('/static/images/google-logo.png')
|
||||||
|
"
|
||||||
></q-img>
|
></q-img>
|
||||||
</q-avatar>
|
</q-avatar>
|
||||||
<div>Google</div>
|
<div>Google</div>
|
||||||
@@ -306,7 +308,9 @@
|
|||||||
>
|
>
|
||||||
<q-avatar size="32px" class="q-mr-md">
|
<q-avatar size="32px" class="q-mr-md">
|
||||||
<q-img
|
<q-img
|
||||||
:src="'{{ static_url_for('static', 'images/github-logo.png') }}'"
|
:src="
|
||||||
|
utils.urlFor('/static/images/github-logo.png')
|
||||||
|
"
|
||||||
></q-img>
|
></q-img>
|
||||||
</q-avatar>
|
</q-avatar>
|
||||||
<div>GitHub</div>
|
<div>GitHub</div>
|
||||||
|
|||||||
@@ -216,8 +216,12 @@
|
|||||||
<q-card-section class="text-subtitle1">
|
<q-card-section class="text-subtitle1">
|
||||||
<span v-text="g.settings.adSpaceTitle"></span>
|
<span v-text="g.settings.adSpaceTitle"></span>
|
||||||
<a :href="ad[0]" class="lnbits-ad" v-for="ad in g.settings.adSpace">
|
<a :href="ad[0]" class="lnbits-ad" v-for="ad in g.settings.adSpace">
|
||||||
<q-img class="q-mb-xs" v-if="$q.dark.isActive" :src="ad[1]"></q-img>
|
<q-img
|
||||||
<q-img class="q-mb-xs" v-else :src="ad[2]"></q-img>
|
class="q-mb-xs"
|
||||||
|
v-if="$q.dark.isActive"
|
||||||
|
:src="utils.urlFor(ad[1])"
|
||||||
|
></q-img>
|
||||||
|
<q-img class="q-mb-xs" v-else :src="utils.urlFor(ad[2])"></q-img>
|
||||||
</a>
|
</a>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|||||||
Reference in New Issue
Block a user