from __future__ import annotations from urllib.parse import urlencode from ..ddns_models import DdnsSettings from ..ddns_security import admin_csrf_token from ..ddns_service import DdnsService from .html_utils import SafeHtml, attr, escape, trusted from .templates import _page, _top_nav from .templates_admin_tables import ( _account_row, _admin_table_pagination, _admin_table_toolbar, _cleanup_row, _domain_claim_row, _event_row, _scheduled_task_row, ) def _admin_secret_panel(title: str, rows: list[SafeHtml]) -> SafeHtml: return trusted(f"""

Admin action

{escape(title)}

Copy this now. Secret values are only shown in this response.

{"".join(rows)}
""") def _render_admin_page( service: DdnsService, settings: DdnsSettings, accounts: list[dict[str, str | int | None]], domain_challenges: list[dict[str, str | None]], cleanup_runs: list[dict[str, str | int]], scheduled_tasks: list[dict[str, str | int | None]], update_events: list[dict[str, str | int | None]], table_counts: dict[str, int], table_offsets: dict[str, int], table_limit: int, notice_html: SafeHtml | None, ) -> str: csrf = attr(admin_csrf_token(settings)) admin_path = settings.admin_path safe_admin_path = attr(admin_path) safe_records_path = attr(f"{admin_path}/records") safe_events_path = attr(f"{admin_path}/events") rows = "\n".join(_account_row(account, csrf=csrf, admin_path=admin_path) for account in accounts) or """ No hostnames yet. """ domain_rows = "\n".join(_domain_claim_row(challenge) for challenge in domain_challenges) or """ No domain claims yet. """ cleanup_rows = "\n".join(_cleanup_row(run) for run in cleanup_runs) or """ No cleanup runs yet. """ scheduled_task_rows = "\n".join(_scheduled_task_row(task) for task in scheduled_tasks) or """ No scheduled tasks yet. """ event_rows = "\n".join(_event_row(event) for event in update_events) or """ No update events yet. """ accounts_count = escape(table_counts.get("accounts", len(accounts))) domain_count = escape(table_counts.get("domain_challenges", len(domain_challenges))) cleanup_count = escape(table_counts.get("cleanup_runs", len(cleanup_runs))) event_count = escape(table_counts.get("update_events", len(update_events))) account_pager = _server_page_links(admin_path, "accounts_offset", table_offsets.get("accounts", 0), table_limit, table_counts.get("accounts", 0)) domain_pager = _server_page_links( admin_path, "domain_claims_offset", table_offsets.get("domain_challenges", 0), table_limit, table_counts.get("domain_challenges", 0), ) cleanup_pager = _server_page_links(admin_path, "cleanup_runs_offset", table_offsets.get("cleanup_runs", 0), table_limit, table_counts.get("cleanup_runs", 0)) event_pager = _server_page_links(admin_path, "update_events_offset", table_offsets.get("update_events", 0), table_limit, table_counts.get("update_events", 0)) return _page( "KarlDNS Admin", trusted(f"""
{_top_nav("Operator console", trusted(f'RecordsEvents'))}

KarlDNS aktiv

Operator console

Inspect hostnames, verify domain claims, rotate links, run cleanup, and review router update events.

{notice_html or ""}

Service health

Scheduled cleanup

Cleanup runs every {escape(settings.cleanup_interval_seconds)} seconds. It removes unverified domain claims after {escape(settings.cleanup_challenge_hours)} hours and never-used generated hostnames after {escape(settings.cleanup_unused_account_hours)} hours.

The scheduler state is stored in SQLite. Multiple app workers coordinate with a database lease, so only one worker runs a due cleanup task.

Hostnames

Active credentials

Showing up to {escape(len(accounts))} of {accounts_count} hostnames.

{_admin_table_toolbar("hostnames", "Search hostnames")}
{rows}
DomainUserIPv4IPv6UpdatedLinks
{_admin_table_pagination()} {account_pager}

Domain claims

Verification lifecycle

Showing up to {escape(len(domain_challenges))} of {domain_count} claims.

{_admin_table_toolbar("domain-claims", "Search domain claims")}
{domain_rows}
DomainOwner dashboardStatusCreatedVerified
{_admin_table_pagination()} {domain_pager}

Cleanup

Scheduler state

{scheduled_task_rows}
TaskStatusNext runLease expiresLast finishedLast error

Cleanup

Recent runs

Showing up to {escape(len(cleanup_runs))} of {cleanup_count} runs.

{_admin_table_toolbar("cleanup-runs", "Search cleanup runs")}
{cleanup_rows}
TimeDomain claims removedUnused hostnames removed
{_admin_table_pagination()} {cleanup_pager}

Updates

Recent events

Showing up to {escape(len(update_events))} of {event_count} event rows. Repeated unchanged polls are aggregated.

{_admin_table_toolbar("update-events", "Search update events")}
{event_rows}
TimeDomainStatusIPv4IPv6CountDetail
{_admin_table_pagination()} {event_pager}
"""), ) def _server_page_links(admin_path: str, offset_name: str, offset: int, limit: int, total: int) -> SafeHtml: if total <= limit: return trusted("") previous_offset = max(0, offset - limit) next_offset = offset + limit previous_disabled = " disabled" if offset <= 0 else "" next_disabled = " disabled" if next_offset >= total else "" previous_href = attr(f"{admin_path}?{urlencode({offset_name: previous_offset})}") next_href = attr(f"{admin_path}?{urlencode({offset_name: next_offset})}") start = min(offset + 1, total) end = min(offset + limit, total) return trusted(f""" """)