Add formatting and type checks

This commit is contained in:
StasonJatham committed 2026-05-22 17:05:06 +02:00
1 parent c0d82eaf26
commit 24747866bc
38 files changed
+800 -265

No files matched your search

+18 -3
View File
@@ -67,7 +67,9 @@ def make_router(admin_path: str = "/admin") -> APIRouter:
cleanup_events: int = Query(0, ge=0),
) -> str:
notice = (
_admin_notice("success", f"Cleanup finished. Removed {cleanup_unused} hostnames and {cleanup_events} old events.")
_admin_notice(
"success", f"Cleanup finished. Removed {cleanup_unused} hostnames and {cleanup_events} old events."
)
if cleanup_done
else None
)
@@ -138,7 +140,13 @@ def make_router(admin_path: str = "/admin") -> APIRouter:
"router update URL rotated by admin",
client_ip(request, settings),
)
notice = _admin_secret_panel("Update URL changed", [_copy_row("Update-URL:", service.fritz_update_url(rotated), "Secret")]) if rotated else None
notice = (
_admin_secret_panel(
"Update URL changed", [_copy_row("Update-URL:", service.fritz_update_url(rotated), "Secret")]
)
if rotated
else None
)
elif action == "management":
rotated = await run_in_threadpool(store.rotate_management_slug, hostname)
await run_in_threadpool(
@@ -150,7 +158,14 @@ def make_router(admin_path: str = "/admin") -> APIRouter:
"dashboard link rotated by admin",
client_ip(request, settings),
)
notice = _admin_secret_panel("Dashboard link changed", [_copy_row("Private dashboard:", service.dashboard_url(rotated), "Secret")]) if rotated else None
notice = (
_admin_secret_panel(
"Dashboard link changed",
[_copy_row("Private dashboard:", service.dashboard_url(rotated), "Secret")],
)
if rotated
else None
)
else:
raise HTTPException(status_code=400, detail="invalid rotation action")
page = await run_in_threadpool(render_admin, service, settings, store, None, notice)
+25 -6
View File
@@ -16,8 +16,12 @@ from ..schemas import (
HostnameCreateResponse,
HostnameRotateUpdateResponse,
HostnameStatusResponse,
_admin_cleanup_run,
_admin_hostname,
_admin_metrics,
_admin_record,
_admin_scheduled_task,
_admin_update_event,
_create_response,
_hostname_rotate_update_response,
_hostname_status,
@@ -62,7 +66,11 @@ async def read_hostname(
if not account or account["disabled"]:
raise HTTPException(status_code=404, detail="not found")
account["management_slug"] = dashboard_secret
history = await run_in_threadpool(store.list_update_events_for_hostname, str(account["hostname"]), history_limit) if history_limit else []
history = (
await run_in_threadpool(store.list_update_events_for_hostname, str(account["hostname"]), history_limit)
if history_limit
else []
)
return _hostname_status(service, account, history)
@@ -149,9 +157,9 @@ async def read_admin(
),
metrics=_admin_metrics(metrics),
hostnames=[_admin_hostname(account) for account in accounts],
update_events=events,
cleanup_runs=cleanup_runs,
scheduled_tasks=scheduled_tasks,
update_events=[_admin_update_event(event) for event in events],
cleanup_runs=[_admin_cleanup_run(cleanup_run) for cleanup_run in cleanup_runs],
scheduled_tasks=[_admin_scheduled_task(task) for task in scheduled_tasks],
limit=limit,
)
@@ -204,7 +212,13 @@ async def list_admin_events(
)
next_before = str(items[-1]["created_at"]) if len(items) == limit and items[-1].get("created_at") else None
total = await run_in_threadpool(store.count_update_events)
return AdminEventsResponse(items=items, limit=limit, offset=offset, next_before=next_before, total=total)
return AdminEventsResponse(
items=[_admin_update_event(item) for item in items],
limit=limit,
offset=offset,
next_before=next_before,
total=total,
)
@router.get(
@@ -224,4 +238,9 @@ async def list_admin_records(
) -> AdminRecordsResponse:
items = await run_in_threadpool(store.list_records_page, limit, offset)
total = await run_in_threadpool(store.count_records)
return AdminRecordsResponse(items=items, limit=limit, offset=offset, total=total)
return AdminRecordsResponse(
items=[_admin_record(item) for item in items],
limit=limit,
offset=offset,
total=total,
)
+23 -1
View File
@@ -14,7 +14,16 @@ ASSET_CACHE_HEADERS = {"Cache-Control": "public, max-age=31536000, immutable"}
def _minify_css(css: str) -> str:
return " ".join(css.split()).replace(" {", "{").replace("{ ", "{").replace("; ", ";").replace(": ", ":").replace(", ", ",").replace(" }", "}").replace(";}", "}")
return (
" ".join(css.split())
.replace(" {", "{")
.replace("{ ", "{")
.replace("; ", ";")
.replace(": ", ":")
.replace(", ", ",")
.replace(" }", "}")
.replace(";}", "}")
)
MINIFIED_BASE_CSS = _minify_css(BASE_CSS)
@@ -25,50 +34,62 @@ MINIFIED_ADMIN_CSS = _minify_css(ADMIN_CSS)
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)
@router.get("/favicon-32x32.png")
def favicon_32_png() -> FileResponse:
return FileResponse(ASSET_DIR / "favicon-32x32.png", media_type="image/png", headers=ASSET_CACHE_HEADERS)
@router.get("/apple-touch-icon.png")
def apple_touch_icon_png() -> FileResponse:
return FileResponse(ASSET_DIR / "apple-touch-icon.png", media_type="image/png", headers=ASSET_CACHE_HEADERS)
@router.get("/logo.png")
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)
@router.get("/logo-512.png")
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(
@@ -92,6 +113,7 @@ def site_webmanifest() -> Response:
headers=ASSET_CACHE_HEADERS,
)
@router.get("/theme.js")
def theme_js() -> Response:
return Response(THEME_JS, media_type="application/javascript", headers=ASSET_CACHE_HEADERS)
+27 -14
View File
@@ -50,14 +50,24 @@ def manage_dashboard(
account = get_active_management_account(store, management_slug)
csrf_token = dashboard_csrf_token(management_slug, account)
if not management_account_is_accepted(account):
return _render_pending_management_page(service, settings, management_slug, account, csrf_token, request_ip=client_ip(request, settings))
return _render_pending_management_page(
service, settings, management_slug, account, csrf_token, request_ip=client_ip(request, settings)
)
if not has_management_access(request, settings, management_slug, account):
return _render_management_unlock_page(management_slug, account, csrf_token)
message = "Hostname registered. Add the update URL to your router." if activated else None
return _render_management_page(service, settings, management_slug, account, csrf_token, message=message, request_ip=client_ip(request, settings))
return _render_management_page(
service,
settings,
management_slug,
account,
csrf_token,
message=message,
request_ip=client_ip(request, settings),
)
@router.post("/m/{management_slug}/activate", include_in_schema=False)
@router.post("/m/{management_slug}/activate", include_in_schema=False, response_model=None)
async def activate_dashboard(
management_slug: ManagementSlugPath,
request: Request,
@@ -68,7 +78,7 @@ async def activate_dashboard(
ipaddr: str | None = None,
myipv6: str | None = None,
ip6addr: str | None = None,
) -> RedirectResponse:
) -> RedirectResponse | HTMLResponse:
account = await get_active_management_account_async(store, management_slug)
form = await request_form(request, settings)
require_dashboard_csrf(management_slug, account, first_form_value(form, "csrf"))
@@ -102,8 +112,8 @@ async def activate_dashboard(
),
status_code=500,
)
account = await run_in_threadpool(store.activate_account, hostname)
if not account:
activated_account = await run_in_threadpool(lambda: store.activate_account(hostname))
if not activated_account:
return _not_found()
await run_in_threadpool(
store.log_update_event,
@@ -117,14 +127,14 @@ async def activate_dashboard(
return RedirectResponse(f"/m/{management_slug}?activated=1", status_code=303)
@router.post("/m/{management_slug}/rotate", response_class=HTMLResponse, include_in_schema=False)
@router.post("/m/{management_slug}/rotate", response_class=HTMLResponse, include_in_schema=False, response_model=None)
async def rotate_dashboard_secrets(
management_slug: ManagementSlugPath,
request: Request,
settings: SettingsDep,
store: StoreDep,
service: ServiceDep,
) -> str:
) -> str | HTMLResponse:
account = await get_active_management_account_async(store, management_slug)
if not management_account_is_accepted(account):
return HTMLResponse(
@@ -172,10 +182,15 @@ async def rotate_dashboard_secrets(
@router.post("/m/{management_slug}/delete", include_in_schema=False)
async def delete_dashboard_hostname(management_slug: ManagementSlugPath, request: Request, settings: SettingsDep, store: StoreDep, service: ServiceDep) -> Response:
async def delete_dashboard_hostname(
management_slug: ManagementSlugPath, request: Request, settings: SettingsDep, store: StoreDep, service: ServiceDep
) -> Response:
account = await get_active_management_account_async(store, management_slug)
if not has_management_access(request, settings, management_slug, account):
return HTMLResponse(_render_management_unlock_page(management_slug, account, dashboard_csrf_token(management_slug, account)), status_code=401)
return HTMLResponse(
_render_management_unlock_page(management_slug, account, dashboard_csrf_token(management_slug, account)),
status_code=401,
)
form = await request_form(request, settings)
require_dashboard_csrf(management_slug, account, first_form_value(form, "csrf"))
hostname = str(account["hostname"])
@@ -195,8 +210,7 @@ async def delete_dashboard_hostname(management_slug: ManagementSlugPath, request
return HTMLResponse(
_page(
"Hostname deleted",
trusted(
"""
trusted("""
<main>
<section class="hero-band compact">
<div class="container text-center d-grid justify-items-center gap-3">
@@ -207,8 +221,7 @@ async def delete_dashboard_hostname(management_slug: ManagementSlugPath, request
</div>
</section>
</main>
"""
),
"""),
)
)
+14 -4
View File
@@ -24,7 +24,9 @@ router = APIRouter()
@router.post("/m/{management_slug}/unlock", include_in_schema=False)
async def unlock_dashboard(management_slug: ManagementSlugPath, request: Request, settings: SettingsDep, store: StoreDep) -> Response:
async def unlock_dashboard(
management_slug: ManagementSlugPath, request: Request, settings: SettingsDep, store: StoreDep
) -> Response:
account = await get_active_management_account_async(store, management_slug)
if not account.get("dashboard_password_hash"):
return RedirectResponse(f"/m/{management_slug}", status_code=303)
@@ -35,7 +37,9 @@ async def unlock_dashboard(management_slug: ManagementSlugPath, request: Request
is_valid = await run_in_threadpool(store.verify_dashboard_password, hostname, password)
if not is_valid:
return HTMLResponse(
_render_management_unlock_page(management_slug, account, dashboard_csrf_token(management_slug, account), "Incorrect password."),
_render_management_unlock_page(
management_slug, account, dashboard_csrf_token(management_slug, account), "Incorrect password."
),
status_code=401,
)
response = RedirectResponse(f"/m/{management_slug}", status_code=303)
@@ -59,7 +63,10 @@ async def set_dashboard_password(
) -> Response:
account = await get_active_management_account_async(store, management_slug)
if not has_management_access(request, settings, management_slug, account):
return HTMLResponse(_render_management_unlock_page(management_slug, account, dashboard_csrf_token(management_slug, account)), status_code=401)
return HTMLResponse(
_render_management_unlock_page(management_slug, account, dashboard_csrf_token(management_slug, account)),
status_code=401,
)
form = await request_form(request, settings)
require_dashboard_csrf(management_slug, account, first_form_value(form, "csrf"))
hostname = str(account["hostname"])
@@ -129,7 +136,10 @@ async def remove_dashboard_password(
) -> Response:
account = await get_active_management_account_async(store, management_slug)
if not has_management_access(request, settings, management_slug, account):
return HTMLResponse(_render_management_unlock_page(management_slug, account, dashboard_csrf_token(management_slug, account)), status_code=401)
return HTMLResponse(
_render_management_unlock_page(management_slug, account, dashboard_csrf_token(management_slug, account)),
status_code=401,
)
form = await request_form(request, settings)
require_dashboard_csrf(management_slug, account, first_form_value(form, "csrf"))
hostname = str(account["hostname"])
+10 -3
View File
@@ -35,7 +35,9 @@ def management_account_is_accepted(account: ManagementAccount) -> bool:
return bool(account.get("accepted_at"))
def has_management_access(request: Request, settings: DdnsSettings, management_slug: str, account: ManagementAccount) -> bool:
def has_management_access(
request: Request, settings: DdnsSettings, management_slug: str, account: ManagementAccount
) -> bool:
password_hash = account.get("dashboard_password_hash")
if not password_hash:
return True
@@ -49,7 +51,10 @@ def has_management_access(request: Request, settings: DdnsSettings, management_s
def locked_management_response(management_slug: str, account: ManagementAccount) -> HTMLResponse:
return HTMLResponse(_render_management_unlock_page(management_slug, account, dashboard_csrf_token(management_slug, account)), status_code=401)
return HTMLResponse(
_render_management_unlock_page(management_slug, account, dashboard_csrf_token(management_slug, account)),
status_code=401,
)
def management_error_response(
@@ -82,7 +87,9 @@ async def management_error_response_async(
message: str,
request_ip: str | None = None,
) -> HTMLResponse:
return await run_in_threadpool(management_error_response, service, settings, management_slug, account, message, request_ip)
return await run_in_threadpool(
management_error_response, service, settings, management_slug, account, message, request_ip
)
async def render_management_page_async(
+3 -1
View File
@@ -152,7 +152,9 @@ async def _update_hostname(
raise HTTPException(status_code=404, detail="not found")
if not service.hostname_is_managed(str(account["hostname"])):
raise HTTPException(status_code=404, detail="not found")
return await run_in_threadpool(apply_update_response, service, request, str(account["hostname"]), myip, ipaddr, myipv6, ip6addr)
return await run_in_threadpool(
apply_update_response, service, request, str(account["hostname"]), myip, ipaddr, myipv6, ip6addr
)
async def _ip_params_from_request(