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()}

Dashboard

{hostname}

This private link can manage this hostname. Keep it safe.

Home
{message_html} {rotated_credentials}

Router settings

Router update settings

Use the Update-URL if your router has one URL field. Username and password are only for routers that require them. For your own subdomain, create a CNAME to this hostname.

{_one_time_secret_notice()} {_secret_notice()}
{_copy_button("Copy router fields", fritzbox_fields)} {_copy_button("Copy CNAME", 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

Replace router secrets

Create a new update URL or router password. Update your router after changing either one.

{domain_claim} {dashboard_password_panel}

Current address

Status

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

History

Router updates

Recent IP changes and failed updates for this hostname.

{history_rows}
TimeStatusIPv4IPv6CountDetail

Delete

Delete hostname

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

"""), extra_styles=trusted(''), ) 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 password" if password_enabled else "Set 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 password protects only this dashboard. Router updates still work.

{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 for 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

Use your domain

Create a TXT record, verify it here, then create a hostname under that 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" 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. Other dashboards cannot use it.

TXT name and value are public. Claim secret and private claim link are secret.
{_copy_button("Copy TXT", 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}"