mirror of
https://github.com/markbeep/AudioBookRequest.git
synced 2026-01-08 22:49:45 -06:00
add option to configure base url
This commit is contained in:
@@ -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=
|
||||
|
||||
@@ -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 |
|
||||
|
||||
---
|
||||
|
||||
@@ -15,6 +15,7 @@ class ApplicationSettings(BaseModel):
|
||||
port: int = 8000
|
||||
version: str = "local"
|
||||
log_level: str = "INFO"
|
||||
base_url: str = ""
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
|
||||
11
app/main.py
11
app/main.py
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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
23
app/util/redirect.py
Normal 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,
|
||||
)
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -1 +1 @@
|
||||
<script defer src="/static/alpine.js?v={{ version }}"></script>
|
||||
<script defer src="{{base_url}}/static/alpine.js?v={{ version }}"></script>
|
||||
|
||||
@@ -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") => {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
< 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"
|
||||
/>
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user