from __future__ import annotations from ..services.ddns import DdnsService from ..settings import DdnsSettings from .components import ( _code_window, _copy_menu, _copy_row, _event_label, _fritzbox_fields, _manual_update_commands, _manual_update_panel, _secret_notice, ) from .html_utils import SafeHtml, attr, escape, trusted from .layout import _page, _top_nav def _render_management_page( service: DdnsService, settings: DdnsSettings, management_slug: str, account: dict[str, str | int | None], csrf_token: str, message: str | None = None, request_ip: str | None = None, ) -> str: account_for_url = { "hostname": str(account["hostname"]), "update_slug": str(account["update_slug"]), "management_slug": management_slug, } update_url = service.fritz_update_url(account_for_url) hostname = escape(account["hostname"]) 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) cname_record = f"your-subdomain.example.com. CNAME {hostname}." manual_commands = _manual_update_commands(update_url, request_ip) 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) 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}

Router settings

Router update settings

Use this Update-URL in your router or script. Optional: point another DNS name at this hostname with a CNAME.

{_secret_notice()}
{_copy_menu("Copy setup values", [("Router fields", fritzbox_fields), ("CNAME", cname_record)])}
{_copy_row("Update-URL:", update_url, "Secret")} {_copy_row("Domainnamen:", hostname, "Public")} {_copy_row("CNAME target:", hostname, "Public")}
{_manual_update_panel(manual_commands)}

Actions

Replace router update URL

Create a new update URL. Update your router after changing it.

{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 _render_pending_management_page( service: DdnsService, settings: DdnsSettings, management_slug: str, account: dict[str, str | int | None], csrf_token: str, message: str | None = None, request_ip: str | None = None, ) -> str: raw_hostname = str(account["hostname"]) account_for_url = { "hostname": raw_hostname, "update_slug": str(account["update_slug"]), "management_slug": management_slug, } update_url = service.fritz_update_url(account_for_url) dashboard_url = service.dashboard_url({"management_slug": management_slug}) hostname = escape(raw_hostname) message_html = _management_message(message) cleanup_hours = settings.cleanup_pending_account_hours cleanup_text = f"{cleanup_hours} hours" if cleanup_hours != 24 else "24 hours" manual_commands = _manual_update_commands(update_url, request_ip) return _page( "KarlDNS setup", trusted(f"""
{_top_nav()}

Pending setup

{hostname}

Save these values, then register the hostname. Pending setups are cleaned up after {escape(cleanup_text)}.

{message_html}

Save

Your values.

Save the update URL and dashboard link before you register. After registration, the dashboard hides secrets by default.

Public: Domainnamen. Secret: Update-URL and private dashboard.

{_copy_row("Update-URL:", update_url, "Secret", mask_secret=False)} {_copy_row("Domainnamen:", raw_hostname, "Public")} {_copy_row("Private dashboard:", dashboard_url, "Secret", mask_secret=False)}

FRITZ!Box

Router setup.

Paste this exact URL into the FRITZ!Box Update-URL field. Use {hostname} as Domainnamen. No KarlDNS username or password is needed; if your router requires them, enter any placeholder.

{_code_window(update_url, copy_label="FRITZ!Box update URL", class_name="mt-3")}

Manual updates.

Scripts can send the IP explicitly, or omit it and let KarlDNS use the request IP. Visible examples use your public IP when KarlDNS or your browser can detect it.

{_code_window(manual_commands, copy_label="manual update commands", class_name="manual-code mt-3")}

Use your own DNS name.

Create a CNAME at your DNS provider that points to {hostname}. This is the same value as Domainnamen.

"""), ) 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 _user_event_row(event: dict[str, str | int | None]) -> str: created = escape(event.get("created_at") or "-") status = escape(_event_label(event.get("status"))) 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}"