Polish KarlDNS production UI and docs

This commit is contained in:
StasonJatham committed 2026-05-21 10:05:40 +02:00
1 parent ca92dc211a
commit 5beee0e5c6
61 files changed
+1566 -523

No files matched your search

+60 -10
View File
@@ -5,13 +5,13 @@ from pathlib import Path
from fastapi import APIRouter, Response
from fastapi.responses import FileResponse, RedirectResponse
from ..web.styles import APP_CSS
from ..web.styles import ADMIN_CSS, BASE_CSS
ASSET_DIR = Path(__file__).parents[1] / "assets"
router = APIRouter(include_in_schema=False)
ASSET_CACHE_HEADERS = {"Cache-Control": "public, max-age=3600"}
ASSET_CACHE_HEADERS = {"Cache-Control": "public, max-age=31536000, immutable"}
APP_JS = """
THEME_JS = """
(() => {
const media = window.matchMedia("(prefers-color-scheme: dark)");
const storageKey = "karldns-theme";
@@ -30,8 +30,9 @@ APP_JS = """
};
const syncToggle = (theme) => {
document.querySelectorAll("[data-theme-toggle]").forEach((button) => {
button.textContent = theme === "dark" ? "Dark" : "Light";
button.setAttribute("aria-label", `Switch to ${theme === "dark" ? "light" : "dark"} theme`);
const nextTheme = theme === "dark" ? "light" : "dark";
button.dataset.themeNext = nextTheme;
button.setAttribute("aria-label", `Switch to ${nextTheme.toLowerCase()} theme`);
});
};
const applyTheme = (requestedTheme = storedTheme() || systemTheme()) => {
@@ -54,7 +55,10 @@ APP_JS = """
applyTheme(nextTheme);
});
})();
""".strip()
CLIPBOARD_JS = """
document.addEventListener("click", async (event) => {
const toggle = event.target.closest("[data-secret-toggle]");
if (toggle) {
@@ -87,7 +91,10 @@ document.addEventListener("click", async (event) => {
}, 1200);
} catch (_) {}
});
""".strip()
ADMIN_TABLES_JS = """
document.querySelectorAll("[data-admin-table]").forEach((tableShell) => {
const table = tableShell.querySelector("table");
const tbody = tableShell.querySelector("tbody");
@@ -176,10 +183,22 @@ document.querySelectorAll("[data-admin-table]").forEach((tableShell) => {
""".strip()
def _minify_css(css: str) -> str:
return " ".join(css.split()).replace(" {", "{").replace("{ ", "{").replace("; ", ";").replace(": ", ":").replace(", ", ",").replace(" }", "}").replace(";}", "}")
MINIFIED_BASE_CSS = _minify_css(BASE_CSS)
MINIFIED_ADMIN_CSS = _minify_css(ADMIN_CSS)
@router.get("/favicon.ico")
def favicon() -> FileResponse:
return FileResponse(ASSET_DIR / "favicon.ico", media_type="image/x-icon", headers=ASSET_CACHE_HEADERS)
@router.get("/bootstrap-subset.css")
def bootstrap_subset_css() -> FileResponse:
return FileResponse(ASSET_DIR / "bootstrap-subset.css", media_type="text/css", headers=ASSET_CACHE_HEADERS)
@router.get("/favicon-16x16.png")
def favicon_16_png() -> FileResponse:
return FileResponse(ASSET_DIR / "favicon-16x16.png", media_type="image/png", headers=ASSET_CACHE_HEADERS)
@@ -196,6 +215,14 @@ def apple_touch_icon_png() -> FileResponse:
def logo_png() -> FileResponse:
return FileResponse(ASSET_DIR / "logo.png", media_type="image/png", headers=ASSET_CACHE_HEADERS)
@router.get("/logo.webp")
def logo_webp() -> FileResponse:
return FileResponse(ASSET_DIR / "logo.webp", media_type="image/webp", headers=ASSET_CACHE_HEADERS)
@router.get("/logo-nav.webp")
def logo_nav_webp() -> FileResponse:
return FileResponse(ASSET_DIR / "logo-nav.webp", media_type="image/webp", headers=ASSET_CACHE_HEADERS)
@router.get("/logo-192.png")
def logo_192_png() -> FileResponse:
return FileResponse(ASSET_DIR / "logo-192.png", media_type="image/png", headers=ASSET_CACHE_HEADERS)
@@ -204,6 +231,14 @@ def logo_192_png() -> FileResponse:
def logo_512_png() -> FileResponse:
return FileResponse(ASSET_DIR / "logo-512.png", media_type="image/png", headers=ASSET_CACHE_HEADERS)
@router.get("/social-preview.png")
def social_preview_png() -> FileResponse:
return FileResponse(ASSET_DIR / "social-preview.png", media_type="image/png", headers=ASSET_CACHE_HEADERS)
@router.get("/social-preview.webp")
def social_preview_webp() -> FileResponse:
return FileResponse(ASSET_DIR / "social-preview.webp", media_type="image/webp", headers=ASSET_CACHE_HEADERS)
@router.get("/site.webmanifest")
def site_webmanifest() -> Response:
return Response(
@@ -211,7 +246,7 @@ def site_webmanifest() -> Response:
{
"name": "KarlDNS",
"short_name": "KarlDNS",
"description": "Accountless Dynamic DNS for routers and home labs.",
"description": "Private update links for routers.",
"start_url": "/",
"scope": "/",
"display": "standalone",
@@ -227,14 +262,29 @@ def site_webmanifest() -> Response:
headers=ASSET_CACHE_HEADERS,
)
@router.get("/app.js")
def app_js() -> Response:
return Response(APP_JS, media_type="application/javascript", headers=ASSET_CACHE_HEADERS)
@router.get("/theme.js")
def theme_js() -> Response:
return Response(THEME_JS, media_type="application/javascript", headers=ASSET_CACHE_HEADERS)
@router.get("/clipboard.js")
def clipboard_js() -> Response:
return Response(CLIPBOARD_JS, media_type="application/javascript", headers=ASSET_CACHE_HEADERS)
@router.get("/admin-tables.js")
def admin_tables_js() -> Response:
return Response(ADMIN_TABLES_JS, media_type="application/javascript", headers=ASSET_CACHE_HEADERS)
@router.get("/app.css")
def app_css() -> Response:
return Response(APP_CSS, media_type="text/css", headers=ASSET_CACHE_HEADERS)
return Response(MINIFIED_BASE_CSS, media_type="text/css", headers=ASSET_CACHE_HEADERS)
@router.get("/admin.css")
def admin_css() -> Response:
return Response(MINIFIED_ADMIN_CSS, media_type="text/css", headers=ASSET_CACHE_HEADERS)
@router.get("/install.sh")