from __future__ import annotations from ..services.ddns import DdnsService from ..settings import DdnsSettings from .components import ( _code_window, _copy_menu, _copy_row, _event_label, _example_update_ips, _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) registration_url = service.registration_url({"management_slug": management_slug}) 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) example_ipv4, example_ipv6 = _example_update_ips(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.

This page is private. You can revisit it while the setup is pending. After registration, the dashboard hides secrets by default.

Public: hostname and CNAME target. Secret: update URL, register URL, and private dashboard.

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

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. Your current public examples use {escape(example_ipv4)} and {escape(example_ipv6)} where available.

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

Use your own DNS name.

Create a CNAME at your DNS provider that points to {hostname}. KarlDNS only updates this generated hostname.

"""), ) 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}"