from __future__ import annotations from ..ddns_models import DdnsSettings from ..ddns_service import DdnsService from .html_utils import SafeHtml, attr, escape, trusted from .templates import ( _copy_button, _copy_row, _fritzbox_fields, _manual_update_commands, _manual_update_panel, _one_time_secret_notice, _page, _secret_notice, _top_nav, ) from .templates_public_sections import _credentials_panel def _render_management_page( service: DdnsService, settings: DdnsSettings, management_slug: str, account: dict[str, str | int | None], csrf_token: str, message: str | None = None, challenge: dict[str, str | None] | None = None, created_account: dict[str, str] | None = None, ) -> str: account_for_url = { "hostname": str(account["hostname"]), "username": str(account["username"]), "password": "", "update_slug": str(account["update_slug"]), "management_slug": management_slug, } update_url = service.fritz_update_url(account_for_url) hostname = escape(account["hostname"]) username = escape(account["username"]) ipv4 = escape(account.get("ipv4") or "-") ipv6 = escape(account.get("ipv6") or "-") updated = escape(account.get("updated_at") or "Never") fritzbox_fields = _fritzbox_fields(update_url, hostname, username, "unchanged; only shown when generated") cname_record = f"your-subdomain.example.com. CNAME {hostname}." manual_commands = _manual_update_commands(update_url) history_rows = "\n".join(_user_event_row(event) for event in service.store.list_update_events_for_hostname(str(account["hostname"]), 10)) or """ No router updates yet. """ message_html = _management_message(message) rotated_credentials = _credentials_panel(service, created_account) domain_claim = _management_domain_claim_panel(service, management_slug, csrf_token, challenge) dashboard_password_panel = _dashboard_password_panel(management_slug, csrf_token, bool(account.get("dashboard_password_hash"))) return _page( "KarlDNS management", trusted(f"""
{_top_nav()}

Magic management

{hostname}

Anyone with this link can manage or delete this hostname. Keep it private.

Back home
{message_html} {rotated_credentials}

Router settings

Router update settings

Use the Update-URL directly if your router only has one URL field. Separate Benutzername and Kennwort values are optional compatibility fields. If you use your own DNS name, point it at this hostname with a CNAME.

{_one_time_secret_notice()} {_secret_notice()}
{_copy_button("Copy complete FRITZ!Box fields", fritzbox_fields)} {_copy_button("Copy CNAME record", cname_record)}
{_copy_row("Update-URL:", update_url, "Secret")} {_copy_row("Domainnamen:", hostname, "Public")} {_copy_row("CNAME target:", hostname, "Public")} {_copy_row("Benutzername (optional):", username, "Public")} {_copy_row("Kennwort (optional):", "unchanged; only shown when generated", "Secret")}
{_manual_update_panel(manual_commands)}

Actions

Rotate dashboard secrets

Rotate the URL used by routers, or issue a new router password for compatibility clients. Update your router after rotating.

{domain_claim} {dashboard_password_panel}

Current address

Status

IPv4{ipv4}
IPv6{ipv6}
Updated{updated}

History

Router updates

Recent IP changes and failed DNS publishes for this hostname.

{history_rows}
TimeStatusIPv4IPv6CountDetail

Danger zone

Delete hostname

This removes the hostname and DNS records. The router update URL will stop working.

"""), ) def _management_message(message: str | None) -> SafeHtml: if not message: return trusted("") return trusted( f"""
{escape(message)}
""" ) def _dashboard_password_panel(management_slug: str, csrf_token: str, password_enabled: bool) -> SafeHtml: password_status = "Enabled" if password_enabled else "Not enabled" password_action = "Change dashboard password" if password_enabled else "Set dashboard password" remove_form = "" if password_enabled: remove_form = f"""
""" current_password_field = "" if password_enabled: current_password_field = """
""" return trusted( f"""

Dashboard password

Password protection

Status: {escape(password_status)}. This protects this management dashboard only. Router update URLs continue to work normally.

{current_password_field}
{remove_form}
""" ) def _render_management_unlock_page( management_slug: str, account: dict[str, str | int | None], csrf_token: str, message: str | None = None, ) -> str: hostname = escape(account["hostname"]) message_html = "" if message: message_html = f'' return _page( "Unlock KarlDNS management", trusted(f"""
{_top_nav()}

Dashboard locked

{hostname}

Enter the dashboard password to manage this hostname.

{message_html}
"""), ) def _management_domain_claim_panel( service: DdnsService, management_slug: str, csrf_token: str, challenge: dict[str, str | None] | None, ) -> SafeHtml: create_claim = trusted( f"""

Custom domain

Claim a domain

Create a TXT challenge, verify it from this dashboard, then create router credentials for a hostname under that verified domain.

""" ) if not challenge: return trusted( f"""
{create_claim}
""" ) domain = str(challenge["domain"]) claim_secret = str(challenge.get("claim_secret") or "") verification_name = service.verification_name(domain) token = str(challenge["token"]) claim_url = service.domain_claim_url(challenge) status = "Verified" if challenge.get("verified_at") else "Waiting for TXT verification" txt_bundle = f"TXT name: {verification_name}\nTXT value: {token}" create_hostname = _management_custom_hostname_form(management_slug, claim_secret, csrf_token) if challenge.get("verified_at") else trusted("") return trusted( f"""
{create_claim}

Domain claim

{escape(domain)}

Status: {escape(status)}

This claim belongs to this dashboard. Another dashboard cannot verify or use it.

TXT name and value are public DNS records. The claim secret and private claim link are secrets.
{_copy_button("Copy TXT record", txt_bundle)}
{_copy_row("TXT name", verification_name, "Public")} {_copy_row("TXT value", token, "Public")} {_copy_row("Claim secret", claim_secret, "Secret")} {_copy_row("Private claim link", claim_url, "Secret")}
{create_hostname}
""" ) def _management_custom_hostname_form(management_slug: str, claim_secret: str, csrf_token: str | None = None) -> SafeHtml: csrf_field = f'' if csrf_token else "" return trusted( f"""
{csrf_field}
""" ) def _user_event_row(event: dict[str, str | int | None]) -> str: created = escape(event.get("created_at") or "-") status = escape(event.get("status") or "-") ipv4 = escape(event.get("ipv4") or "-") ipv6 = escape(event.get("ipv6") or "-") count = escape(event.get("count") or 1) detail = escape(event.get("detail") or "-") return f"{created}{status}{ipv4}{ipv6}{count}{detail}"