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}
IPv4 {ipv4}
IPv6 {ipv6}
Updated {updated}
History
Router updates
Recent IP changes and failed updates for this hostname.
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)
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.
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. 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} . This is the same value as Domainnamen.
"""),
)
def _management_message(message: str | None) -> SafeHtml:
if not message:
return trusted("")
return trusted(
f"""
"""
)
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 = """
Current password
"""
return trusted(
f"""
Dashboard password
Password protection
Status: {escape(password_status)}. This password protects only this dashboard. Router updates still work.
"""
)
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'{escape(message)}
'
return _page(
"Unlock KarlDNS management",
trusted(f"""
{_top_nav()}
Dashboard locked
{hostname}
Enter the dashboard password for this hostname.
"""),
)
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} "