add option to configure base url

This commit is contained in:
Markbeep
2025-04-12 20:01:05 +02:00
parent 004e1ccda6
commit a449a70724
29 changed files with 133 additions and 85 deletions

View File

@@ -2,3 +2,4 @@ ABR_APP__CONFIG_DIR=config # Path to the config directory. Default: /config
ABR_APP__DEBUG=true # Default: false
ABR_APP__OPENAPI_ENABLED=true # Default: false
ABR_APP__LOG_LEVEL=DEBUG
ABR_APP__BASE_URL=

View File

@@ -153,6 +153,7 @@ spec:
| `ABR_APP__OPENAPI_ENABLED` | If set to `true`, enables an OpenAPI specs page on `/docs`. | false |
| `ABR_APP__CONFIG_DIR` | The directory path where persistant data and configuration is stored. If ran using Docker or Kubernetes, this is the location a volume should be mounted to. | /config |
| `ABR_APP__LOG_LEVEL` | One of `DEBUG`, `INFO`, `WARN`, `ERROR`. | INFO |
| `ABR_APP__BASE_URL` | Defines the base url the website is hosted at. If the website is accessed at `example.org/abr/`, set the base URL to `/abr/` | |
| `ABR_DB__SQLITE_PATH` | If relative, path and name of the sqlite database in relation to `ABR_APP__CONFIG_DIR`. If absolute (path starts with `/`), the config dir is ignored and only the absolute path is used. | db.sqlite |
---

View File

@@ -15,6 +15,7 @@ class ApplicationSettings(BaseModel):
port: int = 8000
version: str = "local"
log_level: str = "INFO"
base_url: str = ""
class Settings(BaseSettings):

View File

@@ -5,7 +5,6 @@ from urllib.parse import quote_plus, urlencode
from fastapi import FastAPI, HTTPException, Request, status
from fastapi.middleware import Middleware
from fastapi.middleware.gzip import GZipMiddleware
from fastapi.responses import RedirectResponse
from sqlalchemy import func
from sqlmodel import select
@@ -19,6 +18,7 @@ from app.internal.env_settings import Settings
from app.internal.models import User
from app.routers import auth, root, search, settings, wishlist
from app.util.db import open_session
from app.util.redirect import BaseUrlRedirectResponse
from app.util.templates import templates
from app.util.toast import ToastException
from app.util.fetch_js import fetch_scripts
@@ -46,6 +46,7 @@ app = FastAPI(
Middleware(DynamicSessionMiddleware, auth_secret, middleware_linker),
Middleware(GZipMiddleware),
],
root_path=Settings().app.base_url.rstrip("/"),
)
app.include_router(auth.router)
@@ -66,7 +67,7 @@ async def redirect_to_login(request: Request, exc: RequiresLoginException):
path = request.url.path
if path != "/" and not path.startswith("/login"):
params["redirect_uri"] = path
return RedirectResponse("/login?" + urlencode(params))
return BaseUrlRedirectResponse("/login?" + urlencode(params))
else:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
@@ -80,7 +81,7 @@ async def redirect_to_invalid_oidc(request: Request, exc: InvalidOIDCConfigurati
path = "/auth/invalid-oidc"
if exc.detail:
path += f"?error={quote_plus(exc.detail)}"
return RedirectResponse(path)
return BaseUrlRedirectResponse(path)
@app.exception_handler(ToastException)
@@ -116,10 +117,10 @@ async def redirect_to_init(request: Request, call_next: Any):
with open_session() as session:
user_count = session.exec(select(func.count()).select_from(User)).one()
if user_count == 0:
return RedirectResponse("/init")
return BaseUrlRedirectResponse("/init")
else:
user_exists = True
elif user_exists and request.url.path.startswith("/init"):
return RedirectResponse("/")
return BaseUrlRedirectResponse("/")
response = await call_next(request)
return response

View File

@@ -8,7 +8,6 @@ from urllib.parse import urlencode, urljoin
import jwt
from aiohttp import ClientSession
from fastapi import APIRouter, Depends, Form, HTTPException, Request, Response, status
from fastapi.responses import RedirectResponse
from fastapi.security import OAuth2PasswordRequestForm
from sqlmodel import Session, select
@@ -24,6 +23,7 @@ from app.internal.auth.oidc_config import InvalidOIDCConfiguration, oidc_config
from app.internal.models import GroupEnum, User
from app.util.connection import get_connection
from app.util.db import get_session
from app.util.redirect import BaseUrlRedirectResponse
from app.util.templates import templates
from app.util.toast import ToastException
@@ -41,14 +41,14 @@ async def login(
):
login_type = auth_config.get(session, "login_type")
if login_type in [LoginTypeEnum.basic, LoginTypeEnum.none]:
return RedirectResponse(redirect_uri)
return BaseUrlRedirectResponse(redirect_uri)
if login_type != LoginTypeEnum.oidc and backup:
backup = False
try:
await get_authenticated_user()(request, session)
# already logged in
return RedirectResponse(redirect_uri)
return BaseUrlRedirectResponse(redirect_uri)
except (HTTPException, RequiresLoginException):
pass
@@ -91,7 +91,7 @@ async def login(
"scope": scope,
"state": state,
}
return RedirectResponse(f"{authorize_endpoint}?" + urlencode(params))
return BaseUrlRedirectResponse(f"{authorize_endpoint}?" + urlencode(params))
@router.post("/logout")

View File

@@ -5,7 +5,7 @@ from typing import Annotated, Callable
from urllib.parse import urlencode
from fastapi import APIRouter, Depends, Form, HTTPException, Request, Response
from fastapi.responses import FileResponse, RedirectResponse
from fastapi.responses import FileResponse
from sqlmodel import Session
from app.internal.auth.authentication import (
@@ -18,6 +18,7 @@ from app.internal.auth.config import LoginTypeEnum, auth_config
from app.internal.env_settings import Settings
from app.internal.models import GroupEnum
from app.util.db import get_session
from app.util.redirect import BaseUrlRedirectResponse
from app.util.templates import templates
router = APIRouter()
@@ -129,7 +130,7 @@ def read_root(
request: Request,
user: Annotated[DetailedUser, Depends(get_authenticated_user())],
):
return RedirectResponse("/search")
return BaseUrlRedirectResponse("/search")
# TODO: create a root page
# return templates.TemplateResponse(
# "root.html",
@@ -179,4 +180,4 @@ def create_init(
@router.get("/login")
def redirect_login(request: Request):
return RedirectResponse("/auth/login?" + urlencode(request.query_params))
return BaseUrlRedirectResponse("/auth/login?" + urlencode(request.query_params))

View File

@@ -12,7 +12,6 @@ from fastapi import (
Request,
Response,
)
from fastapi.responses import RedirectResponse
from sqlmodel import Session, asc, col, select
from app.internal.models import (
@@ -30,6 +29,7 @@ from app.internal.query import query_sources
from app.internal.auth.authentication import DetailedUser, get_authenticated_user
from app.util.connection import get_connection
from app.util.db import get_session, open_session
from app.util.redirect import BaseUrlRedirectResponse
from app.util.templates import template_response
router = APIRouter(prefix="/wishlist")
@@ -239,7 +239,7 @@ async def list_sources(
try:
prowlarr_config.raise_if_invalid(session)
except ProwlarrMisconfigured:
return RedirectResponse(
return BaseUrlRedirectResponse(
"/settings/prowlarr?prowlarr_misconfigured=1", status_code=302
)

23
app/util/redirect.py Normal file
View File

@@ -0,0 +1,23 @@
from fastapi.responses import RedirectResponse
from starlette.datastructures import URL
from app.internal.env_settings import Settings
class BaseUrlRedirectResponse(RedirectResponse):
"""
Redirects while preserving the base URL
"""
def __init__(self, url: str | URL, status_code: int = 302) -> None:
if (
isinstance(url, str)
and url.startswith("/")
or isinstance(url, URL)
and url.path.startswith("/")
):
url = f"{Settings().app.base_url.rstrip('/')}{url}"
super().__init__(
url=url,
status_code=status_code,
)

View File

@@ -15,6 +15,7 @@ templates.env.globals["version"] = Settings().app.version # pyright: ignore[rep
templates.env.globals["json_regexp"] = ( # pyright: ignore[reportUnknownMemberType]
r'^\{\s*(?:"[^"\\]*(?:\\.[^"\\]*)*"\s*:\s*"[^"\\]*(?:\\.[^"\\]*)*"\s*(?:,\s*"[^"\\]*(?:\\.[^"\\]*)*"\s*:\s*"[^"\\]*(?:\\.[^"\\]*)*"\s*)*)?\}$'
)
templates.env.globals["base_url"] = Settings().app.base_url.rstrip("/") # pyright: ignore[reportUnknownMemberType]
@overload

View File

@@ -5,9 +5,15 @@
{% block head %}
<title>AudioBookRequest</title>
{% endblock %}
<link rel="stylesheet" href="/static/globals.css?v={{ version }}" />
<script src="/static/htmx.js?v={{ version }}"></script>
<script defer src="/static/htmx-preload.js?v={{ version }}"></script>
<link
rel="stylesheet"
href="{{base_url}}/static/globals.css?v={{ version }}"
/>
<script src="{{base_url}}/static/htmx.js?v={{ version }}"></script>
<script
defer
src="{{base_url}}/static/htmx-preload.js?v={{ version }}"
></script>
<script>
const setTheme = theme => {
if (!theme) {
@@ -42,27 +48,30 @@
<link
rel="apple-touch-icon"
sizes="180x180"
href="/static/apple-touch-icon.png?v={{ version }}"
href="{{base_url}}/static/apple-touch-icon.png?v={{ version }}"
/>
<link
rel="icon"
sizes="any"
type="image/svg+xml"
href="/static/favicon.svg?v={{ version }}"
href="{{base_url}}/static/favicon.svg?v={{ version }}"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/static/favicon-32x32.png?v={{ version }}"
href="{{base_url}}/static/favicon-32x32.png?v={{ version }}"
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/static/favicon-16x16.png?v={{ version }}"
href="{{base_url}}/static/favicon-16x16.png?v={{ version }}"
/>
<link
rel="manifest"
href="{{base_url}}/static/site.webmanifest?v={{ version }}"
/>
<link rel="manifest" href="/static/site.webmanifest?v={{ version }}" />
{% include 'scripts/toast.html' %}
</head>
@@ -89,15 +98,20 @@
<div class="flex-1">
<a
preload
href="/"
href="{{base_url}}/"
class="btn btn-ghost text-lg hidden sm:inline-flex"
>AudioBookRequest</a
>
<a preload href="/" class="btn btn-ghost text-lg sm:hidden">ABR</a>
<a
preload
href="{{base_url}}/"
class="btn btn-ghost text-lg sm:hidden"
>ABR</a
>
<a
preload
href="/search"
href="{{base_url}}/search"
class="btn btn-ghost btn-square"
title="Search"
>
@@ -106,7 +120,7 @@
<a
preload
href="/wishlist"
href="{{base_url}}/wishlist"
class="btn btn-ghost btn-square group relative"
title="Wishlist"
>
@@ -139,7 +153,7 @@
</button>
{% if user.can_logout() %}
<btn
hx-post="/auth/logout"
hx-post="{{base_url}}/auth/logout"
class="btn btn-ghost btn-square"
title="Logout"
>
@@ -148,7 +162,7 @@
{% endif %}
<a
preload
href="/settings/account"
href="{{base_url}}/settings/account"
class="btn btn-ghost btn-square group"
title="Settings"
>

View File

@@ -5,7 +5,7 @@
<div class="h-screen w-full flex items-center justify-center">
<form
class="flex flex-col gap-2 max-w-[30rem]"
hx-post="/init"
hx-post="{{base_url}}/init"
id="form"
hx-target="#message"
>

View File

@@ -9,7 +9,7 @@
<p>
Click the button below to log in with a root admin account as a backup:
</p>
<a class="btn" href="/login?backup=1">Backup Login</a>
<a class="btn" href="{{base_url}}/login?backup=1">Backup Login</a>
</div>
</div>

View File

@@ -6,7 +6,7 @@
<div class="h-screen w-full flex items-center justify-center">
<form
class="flex flex-col gap-2 max-w-[30rem]"
hx-post="/auth/token"
hx-post="{{base_url}}/auth/token"
id="form"
hx-target="this"
>

View File

@@ -10,7 +10,7 @@
{% block form %}
<form
class="flex flex-col gap-2 w-full max-w-[40rem]"
hx-post="/search/manual"
hx-post="{{base_url}}/search/manual"
hx-target="this"
hx-swap="outerHTML"
hx-disabled-elt="#submit-button"

View File

@@ -1 +1 @@
<script defer src="/static/alpine.js?v={{ version }}"></script>
<script defer src="{{base_url}}/static/alpine.js?v={{ version }}"></script>

View File

@@ -1,11 +1,11 @@
<link
rel="stylesheet"
type="text/css"
href="/static/toastify.css?v={{ version }}"
href="{{base_url}}/static/toastify.css?v={{ version }}"
/>
<script
type="text/javascript"
src="/static/toastify.js?v={{ version }}"
src="{{base_url}}/static/toastify.js?v={{ version }}"
></script>
<script>
const toast = (message, type = "success") => {

View File

@@ -22,7 +22,7 @@
<h1 class="text-3xl font-bold text-left">Search</h1>
<a
preload
href="/search/manual"
href="{{base_url}}/search/manual"
title="Manually add request"
class="btn btn- flex items-center justify-center"
>
@@ -42,7 +42,7 @@
spellcheck="false"
autocomplete="off"
list="search-suggestions"
hx-get="/search/suggestions?region={{ selected_region }}"
hx-get="{{base_url}}/search/suggestions?region={{ selected_region }}"
hx-target="#search-suggestions"
hx-swap="outerHTML"
hx-trigger="keyup changed delay:250ms"
@@ -95,7 +95,7 @@
<!-- prettier-ignore -->
<button
class="absolute top-0 right-0 rounded-none rounded-bl-md btn-sm btn btn-square items-center justify-center flex {% if book.downloaded or book.already_requested %}btn-ghost bg-success text-neutral/20{% else %}btn-info{% endif %}"
hx-post="/search/request/{{ book.asin }}"
hx-post="{{base_url}}/search/request/{{ book.asin }}"
hx-disabled-elt="this"
hx-target="#book-results"
hx-include="this"
@@ -161,7 +161,8 @@
<span class="text-sm"
>No audiobooks were found. Do note, that internally Audible is used for
searching. If the book doesn't exist on Audible it'll have to be added
<a class="link" preload href="/search/manual">manually</a>.</span
<a class="link" preload href="{{base_url}}/search/manual">manually</a
>.</span
>
{% else %}
<span class="text-xl font-semibold">Perform a search</span>

View File

@@ -3,7 +3,7 @@
<form
id="change-password-form"
class="flex flex-col gap-2"
hx-post="/settings/account/password"
hx-post="{{base_url}}/settings/account/password"
hx-target="this"
>
{% if success %}

View File

@@ -15,49 +15,49 @@
<div role="tablist" class="tabs tabs-box">
<a
preload
href="/settings/account"
href="{{base_url}}/settings/account"
role="tab"
class="tab {% if page=='account' %}tab-active{% endif %}"
>Account</a
>
<a
preload
href="/settings/users"
href="{{base_url}}/settings/users"
role="tab"
class="tab {% if page=='users' %}tab-active{% endif %}"
>Users</a
>
<a
preload
href="/settings/prowlarr"
href="{{base_url}}/settings/prowlarr"
role="tab"
class="tab {% if page=='prowlarr' %}tab-active{% endif %}"
>Prowlarr</a
>
<a
preload
href="/settings/download"
href="{{base_url}}/settings/download"
role="tab"
class="tab {% if page=='download' %}tab-active{% endif %}"
>Download</a
>
<a
preload
href="/settings/notifications"
href="{{base_url}}/settings/notifications"
role="tab"
class="tab {% if page=='notifications' %}tab-active{% endif %}"
>Notifications</a
>
<a
preload
href="/settings/security"
href="{{base_url}}/settings/security"
role="tab"
class="tab {% if page=='security' %}tab-active{% endif %}"
>Security</a
>
<a
preload
href="/settings/indexers"
href="{{base_url}}/settings/indexers"
role="tab"
class="tab {% if page=='indexers' %}tab-active{% endif %}"
>Indexers</a

View File

@@ -1,7 +1,10 @@
{% extends "settings_page/base.html" %} {% block head %}
<title>Settings - Download</title>
<link href="/static/nouislider.css?v={{ version }}" rel="stylesheet" />
<script src="/static/nouislider.js?v={{ version }}"></script>
<link
href="{{base_url}}/static/nouislider.css?v={{ version }}"
rel="stylesheet"
/>
<script src="{{base_url}}/static/nouislider.js?v={{ version }}"></script>
<script>
const createSlider = (sliderId, fromId, toId, start, stop) => {
const slider = document.getElementById(sliderId);
@@ -37,7 +40,7 @@
{% block form %}
<form
class="flex flex-col gap-2"
hx-post="/settings/download"
hx-post="{{base_url}}/settings/download"
hx-disabled-elt="#save-button"
hx-target="this"
>
@@ -279,7 +282,7 @@
{% block flags %}
<form
id="flags-form"
hx-post="/settings/download/indexer-flag"
hx-post="{{base_url}}/settings/download/indexer-flag"
hx-target="this"
hx-disabled-elt="#add-button"
>
@@ -329,7 +332,7 @@
title="Delete flag"
type="button"
class="btn btn-square delete-button"
hx-delete="/settings/download/indexer-flag/{{ flag.flag }}"
hx-delete="{{base_url}}/settings/download/indexer-flag/{{ flag.flag }}"
hx-disabled-elt=".delete-button"
hx-target="#flags-form"
>
@@ -352,7 +355,7 @@
<button
type="button"
class="btn btn-error"
hx-delete="/settings/download"
hx-delete="{{base_url}}/settings/download"
hx-disabled-elt="this"
>
Reset download settings

View File

@@ -5,7 +5,7 @@
<form
id="change-password-form"
class="flex flex-col gap-2"
hx-post="/settings/indexers"
hx-post="{{base_url}}/settings/indexers"
hx-target="this"
>
<h2 class="text-lg">Indexer Settings</h2>

View File

@@ -8,7 +8,7 @@
<form
id="add-notification-form"
class="flex flex-col gap-2"
hx-post="/settings/notification"
hx-post="{{base_url}}/settings/notification"
hx-target="#notification-list"
hx-swap="outerHTML"
hx-on::after-request="if (event.detail.successful && event.detail.target?.id === 'notification-list') this.reset()"
@@ -128,7 +128,7 @@
<button
title="Test"
class="btn btn-square"
hx-post="/settings/notification/{{n.id}}"
hx-post="{{base_url}}/settings/notification/{{n.id}}"
hx-disabled-elt="this"
>
{% include 'icons/test-pipe.html' %}
@@ -143,7 +143,7 @@
<button
title="{{ 'Enabled' if n.enabled else 'Disabled' }}"
class="btn btn-square {{ 'btn-success' if n.enabled else 'btn-error' }}"
hx-patch="/settings/notification/{{n.id}}/enable"
hx-patch="{{base_url}}/settings/notification/{{n.id}}/enable"
hx-disabled-elt="this"
hx-target="#notification-list"
hx-swap="outerHTML"
@@ -155,7 +155,7 @@
<button
title="Delete"
class="btn btn-error btn-square"
hx-delete="/settings/notification/{{n.id}}"
hx-delete="{{base_url}}/settings/notification/{{n.id}}"
hx-target="#notification-list"
hx-swap="outerHTML"
hx-confirm="Are you sure you want to delete this notification? ({{n.name}})"
@@ -174,7 +174,7 @@
<form
x-data="{ name: '{{n.name}}', event: '{{n.event.value}}', apprise_url: '{{n.apprise_url}}', headers: '{{n.headers}}', title_template: '{{n.title_template}}', body_template: '{{n.body_template}}' }"
class="flex flex-col gap-2"
hx-put="/settings/notification/{{n.id}}"
hx-put="{{base_url}}/settings/notification/{{n.id}}"
hx-target="#notification-list"
hx-swap="outerHTML"
id="edit-notification-form"

View File

@@ -12,7 +12,7 @@
<label for="prowlarr-api-key">API Key</label>
<form
class="join w-full"
hx-put="/settings/prowlarr/api-key"
hx-put="{{base_url}}/settings/prowlarr/api-key"
hx-disabled-elt="#api-button"
>
<!-- prettier-ignore -->
@@ -33,7 +33,7 @@
<label for="prowlarr-base-url" class="pt-2">Base URL</label>
<form
class="join w-full"
hx-put="/settings/prowlarr/base-url"
hx-put="{{base_url}}/settings/prowlarr/base-url"
hx-disabled-elt="#base-url-button"
>
<input
@@ -55,7 +55,7 @@
id="category-select"
class="flex flex-col gap-1"
x-data="{ selected: [{{ selected_categories|join(',') }}].sort(), categories: {{ indexer_categories }}, dirty: false }"
hx-put="/settings/prowlarr/category"
hx-put="{{base_url}}/settings/prowlarr/category"
hx-disabled-elt="#category-submit-button"
hx-target="this"
hx-swap="outerHTML"

View File

@@ -5,7 +5,7 @@
{% block form %}
<form
class="flex flex-col gap-2"
hx-post="/settings/security"
hx-post="{{base_url}}/settings/security"
hx-disabled-elt="#save-button"
hx-target="this"
x-data="{ loginType: '{{ login_type.value }}' }"
@@ -218,7 +218,7 @@
Make sure all the settings are correct. In the case of a
miconfiguration, you can log in at
<a
href="/login?backup=1"
href="{{base_url}}/login?backup=1"
class="font-mono link whitespace-nowrap inline-block"
>/login?backup=1</a
>
@@ -248,7 +248,7 @@
<button
type="button"
class="btn btn-error"
hx-post="/settings/security/reset-auth"
hx-post="{{base_url}}/settings/security/reset-auth"
hx-confirm="Are you sure you want to reset the authentication secret? This will invalidate everyone's login session forcing them to log in again."
hx-target="this"
>

View File

@@ -11,7 +11,7 @@
<form
id="create-user-form"
class="flex flex-col gap-2"
hx-post="/settings/user"
hx-post="{{base_url}}/settings/user"
hx-target="#user-list"
hx-on::after-request="if (event.detail.successful && event.detail.target?.id === 'user-list') this.reset()"
hx-swap="outerHTML"
@@ -84,7 +84,7 @@
name="group"
class="select w-full"
required {% if u.root %}disabled{% endif %}
hx-patch="/settings/user/{{ u.username }}"
hx-patch="{{base_url}}/settings/user/{{ u.username }}"
hx-trigger="change"
hx-disabled-elt="this"
hx-target="#user-list"
@@ -122,7 +122,7 @@
<button class="btn">Cancel</button>
<button
class="btn bg-primary"
hx-delete="/settings/user/{{ u.username }}"
hx-delete="{{base_url}}/settings/user/{{ u.username }}"
hx-disabled-elt="this"
hx-target="#user-list"
hx-swap="outerHTML"

View File

@@ -5,21 +5,21 @@
<div role="tablist" class="tabs tabs-box">
<a
preload
href="/wishlist"
href="{{base_url}}/wishlist"
role="tab"
class="tab {% if page=='wishlist' %}tab-active{% endif %}"
>Requests</a
>
<a
preload
href="/wishlist/downloaded"
href="{{base_url}}/wishlist/downloaded"
role="tab"
class="tab {% if page=='downloaded' %}tab-active{% endif %}"
>Downloaded</a
>
<a
preload
href="/wishlist/manual"
href="{{base_url}}/wishlist/manual"
role="tab"
class="tab {% if page=='manual' %}tab-active{% endif %}"
>Manual</a

View File

@@ -26,7 +26,8 @@
<span>
No manual book requests on your wishlist. Add some books by heading to
the
<a preload class="link" href="/search/manual">search</a> tab.
<a preload class="link" href="{{base_url}}/search/manual">search</a>
tab.
</span>
</div>
@@ -55,7 +56,7 @@
title="Remove"
class="btn btn-square"
{% if not user.is_admin() %}disabled{% endif %}
hx-delete="/wishlist/manual/{{ book.id }}"
hx-delete="{{base_url}}/wishlist/manual/{{ book.id }}"
hx-swap="outerHTML"
hx-target="#book-table-body"
hx-disabled-elt="this"
@@ -76,7 +77,7 @@
class="btn btn-square"
title="Set as downloaded"
{% if not user.is_admin() %}disabled{% endif %}
hx-patch="/wishlist/manual/{{ book.id }}"
hx-patch="{{base_url}}/wishlist/manual/{{ book.id }}"
hx-swap="outerHTML"
hx-target="#book-table-body"
hx-disabled-elt="this"

View File

@@ -5,7 +5,7 @@
<div class="w-screen p-2 md:p-4 lg:p-8 flex flex-col gap-2" id="sources">
<a
preload
href="/wishlist#{{ result.book.asin }}"
href="{{base_url}}/wishlist#{{ result.book.asin }}"
class="w-fit btn btn-ghost"
>
&lt; Back to wishlist
@@ -26,7 +26,7 @@
<div
role="alert"
class="alert"
hx-get="/wishlist/sources/{{ result.book.asin }}?only_body=true"
hx-get="{{base_url}}/wishlist/sources/{{ result.book.asin }}?only_body=true"
hx-trigger="load"
hx-target="#sources"
hx-swap="outerHTML"
@@ -93,7 +93,7 @@
type="checkbox"
hx-trigger="click"
hx-target="this"
hx-post="/wishlist/sources/{{ result.book.asin }}"
hx-post="{{base_url}}/wishlist/sources/{{ result.book.asin }}"
hx-include="#form-{{ loop.index }}"
hx-on::after-request="if (event.detail.successful) this.disabled = true"
/>

View File

@@ -26,9 +26,9 @@
<span>
{% if page.__eq__("wishlist") %} No books on your wishlist. Add some
books by heading to the
<a preload class="link" href="/search">search</a> tab {% elif
page.__eq__("downloaded") %} No books have been downloaded yet. {% endif
%}
<a preload class="link" href="{{base_url}}/search">search</a> tab {%
elif page.__eq__("downloaded") %} No books have been downloaded yet. {%
endif %}
</span>
</div>
{% endif %}
@@ -59,7 +59,7 @@
<div class="flex flex-col">
<a
preload
href="/search?q={{ book.title+' ' +(book.authors|join(',')) }}"
href="{{base_url}}/search?q={{ book.title+' ' +(book.authors|join(',')) }}"
class="font-bold text-primary line-clamp-4"
title="{{ book.title }}"
>{{ book.title }}</a
@@ -88,7 +88,7 @@
<a
preload
title="Torrent Sources"
href="/wishlist/sources/{{ book.asin }}"
href="{{base_url}}/wishlist/sources/{{ book.asin }}"
{% if not user.is_admin() %}disabled{% endif %}
class="btn btn-square"
>{% include 'icons/list.html' %}</a
@@ -112,7 +112,7 @@
class="btn btn-square"
{% endif %}
{% if not user.can_download() or book.downloaded %}disabled{% endif %}
hx-post="/wishlist/auto-download/{{ book.asin }}"
hx-post="{{base_url}}/wishlist/auto-download/{{ book.asin }}"
hx-swap="outerHTML"
hx-target="#book-table-body"
hx-disabled-elt="this"
@@ -125,7 +125,7 @@
title="Remove"
class="btn btn-square"
{% if not user.is_admin() %}disabled{% endif %}
hx-delete="/search/request/{{ book.asin }}{% if page.__eq__('downloaded') %}?downloaded=true{% endif %}"
hx-delete="{{base_url}}/search/request/{{ book.asin }}{% if page.__eq__('downloaded') %}?downloaded=true{% endif %}"
hx-swap="outerHTML"
hx-target="#book-table-body"
hx-disabled-elt="this"
@@ -144,7 +144,7 @@
<button
class="btn btn-square"
title="Set as downloaded"
hx-patch="/wishlist/downloaded/{{ book.asin }}"
hx-patch="{{base_url}}/wishlist/downloaded/{{ book.asin }}"
hx-swap="outerHTML"
hx-target="#book-table-body"
hx-disabled-elt="this"