Archived
Add formatting and type checks
This commit is contained in:
1 parent
c0d82eaf26
commit
24747866bc
38 files changed
+800
-265
No files matched your search
@@ -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
@@ -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,
|
||||
)
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
"""
|
||||
),
|
||||
"""),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -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"])
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in new issue
Block a user