@@ -815,6 +819,10 @@ def _credentials_panel(service: DdnsService, created_account: dict[str, str] | N
Save this now. The password and private status page are only shown on this screen. To use your own DNS name, create a CNAME from that name to this hostname.
+ {_copy_button("Copy FRITZ!Box fields", fritzbox_fields)}
+ {_copy_button("Copy CNAME record", cname_record)}
+
{_copy_row("Update-URL:", update_url, "Secret")}
{_copy_row("Domainnamen:", hostname, "Public")}
@@ -860,6 +868,31 @@ def _copy_row(label: str, value: str, sensitivity: str | None = None) -> str:
"""
+def _copy_button(label: str, value: str) -> str:
+ safe_label = html.escape(label)
+ safe_value = html.escape(value)
+ return f'
{safe_label} '
+
+
+def _fritzbox_fields(
+ update_url: str,
+ hostname: str,
+ username: str,
+ password: str,
+ management_url: str | None = None,
+) -> str:
+ lines = [
+ f"Update-URL: {update_url}",
+ f"Domainnamen: {hostname}",
+ f"CNAME target: {hostname}",
+ f"Benutzername: {username}",
+ f"Kennwort: {password}",
+ ]
+ if management_url:
+ lines.append(f"Private status page: {management_url}")
+ return "\n".join(lines)
+
+
def _secret_notice() -> str:
return """
@@ -893,6 +926,8 @@ def _render_management_page(service: DdnsService, settings: DdnsSettings, manage
ipv4 = html.escape(str(account.get("ipv4") or "-"))
ipv6 = html.escape(str(account.get("ipv6") or "-"))
updated = html.escape(str(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}."
history_rows = "\n".join(_user_event_row(event) for event in service.store.list_update_events_for_hostname(str(account["hostname"]), 20)) or """
No router updates yet.
"""
@@ -917,6 +952,10 @@ def _render_management_page(service: DdnsService, settings: DdnsSettings, manage
FRITZ!Box fields
Use these values in the FRITZ!Box custom DynDNS provider fields. If you use your own DNS name, point it at this hostname with a CNAME.
{_secret_notice()}
+
+ {_copy_button("Copy FRITZ!Box fields", fritzbox_fields)}
+ {_copy_button("Copy CNAME record", cname_record)}
+
{_copy_row("Update-URL:", update_url, "Secret")}
diff --git a/tests/test_ddns.py b/tests/test_ddns.py
index cd76457..7478e1c 100644
--- a/tests/test_ddns.py
+++ b/tests/test_ddns.py
@@ -309,6 +309,8 @@ def test_self_service_managed_hostname_generates_random_account(tmp_path: Path)
assert response.status_code == 200
assert "Update-URL:" in response.text
assert "CNAME target:" in response.text
+ assert "Copy FRITZ!Box fields" in response.text
+ assert "Copy CNAME record" in response.text
assert "Public:" in response.text
assert "Secret:" in response.text
assert ".ddns.example.net" in response.text
@@ -333,6 +335,8 @@ def test_magic_hostname_without_login_generates_update_and_management_links(tmp_
assert "Update-URL:" in response.text
assert "CNAME target:" in response.text
assert "Private status page:" in response.text
+ assert "Copy FRITZ!Box fields" in response.text
+ assert "Copy CNAME record" in response.text
assert response.text.count("Secret") >= 3
assert response.text.count("Public") >= 3
assert ".ddns.example.net" in response.text
@@ -349,6 +353,8 @@ def test_magic_management_link_can_delete_hostname(tmp_path: Path) -> None:
assert management.status_code == 200
assert "home.ddns.example.net" in management.text
assert "CNAME target:" in management.text
+ assert "Copy FRITZ!Box fields" in management.text
+ assert "Copy CNAME record" in management.text
assert "Public:" in management.text
assert "Secret:" in management.text
assert "Router updates" in management.text