from __future__ import annotations
from ipaddress import ip_address
from .html_utils import SafeHtml, attr, escape, trusted
from .icons import _svg_icon
_EVENT_LABELS = {
"admin_hostname_deleted": "hostname deleted by admin",
"admin_management_url_rotated": "dashboard link rotated by admin",
"admin_update_url_rotated": "update URL rotated by admin",
"dashboard_hostname_activated": "hostname registered",
"dashboard_hostname_deleted": "hostname deleted",
"dashboard_password_changed": "dashboard password changed",
"dashboard_password_enabled": "dashboard password enabled",
"dashboard_password_removed": "dashboard password removed",
"dashboard_update_url_rotated": "update URL rotated",
"dns_delete_failed": "DNS delete failed",
"dns_failed": "DNS update failed",
"dns_reconcile_failed": "DNS reconciliation failed",
"unchanged": "unchanged",
"updated": "updated",
}
def _event_label(status: object) -> str:
value = str(status or "-")
return _EVENT_LABELS.get(value, value.replace("_", " "))
def _copy_row(label: str, value: str, sensitivity: str | None = None, *, mask_secret: bool = True) -> SafeHtml:
safe_label = escape(label)
safe_value = escape(value)
badge = ""
is_secret = bool(sensitivity and sensitivity.lower() == "secret")
if sensitivity:
safe_sensitivity = escape(sensitivity)
badge_class = "text-bg-warning" if is_secret else "text-bg-secondary"
badge = f'{safe_sensitivity} '
should_mask = is_secret and mask_secret
value_field = (
f' '
if should_mask
else f'{safe_value}'
)
reveal_button = (
f'View '
if should_mask
else ""
)
return trusted(
f"""
{safe_label} {badge}
{value_field}
{reveal_button}
{_svg_icon("clipboard")}Copy {_svg_icon("check", "check-icon")}
"""
)
def _copy_button(label: str, value: str, *, style: str = "outline-primary") -> SafeHtml:
safe_label = escape(label)
safe_value = attr(value)
safe_style = attr(style)
return trusted(
f'{_svg_icon("clipboard")}{safe_label} {_svg_icon("check", "check-icon")} '
)
def _copy_icon_button(label: str, value: str) -> SafeHtml:
safe_label = escape(label)
safe_value = attr(value)
return trusted(
f''
f'{_svg_icon("clipboard")}Copy {safe_label} {_svg_icon("check", "check-icon")} '
)
def _copy_menu(label: str, items: list[tuple[str, str]]) -> SafeHtml:
safe_label = escape(label)
buttons = []
for item_label, item_value in items:
buttons.append(
f''
)
return trusted(
f"""
"""
)
def _code_window(commands: str, *, copy_label: str = "code", class_name: str = "", copy_value: str | None = None) -> SafeHtml:
safe_class = f" {attr(class_name)}" if class_name else ""
code_lines = "\n".join(f"{escape(line)}" for line in commands.splitlines())
copy_value = copy_value if copy_value is not None else _code_copy_value(commands)
return trusted(
f"""
{_copy_icon_button(copy_label, copy_value)}
{code_lines}
"""
)
def _code_copy_value(commands: str) -> str:
lines = commands.splitlines()
prompt_lines = [line[2:] for line in lines if line.startswith("$ ")]
if prompt_lines:
return "\n".join(prompt_lines)
return "\n".join(line[2:] if line.startswith("$ ") else line for line in lines)
def _example_update_ips(request_ip: str | None = None) -> tuple[str, str]:
ipv4 = "203.0.113.10"
ipv6 = "2001:db8::10"
if not request_ip:
return ipv4, ipv6
try:
parsed = ip_address(request_ip.strip())
except ValueError:
return ipv4, ipv6
if not parsed.is_global:
return ipv4, ipv6
if parsed.version == 4:
return str(parsed), ipv6
return ipv4, str(parsed)
def _manual_update_commands(update_url: str, request_ip: str | None = None) -> str:
update_base = update_url.split("?", 1)[0]
ipv4, ipv6 = _example_update_ips(request_ip)
return "\n".join(
[
f"curl -sS '{update_base}?myip={ipv4}'",
f"curl -sS '{update_base}?myipv6={ipv6}'",
f"curl -sS '{update_base}' # use request IP",
]
)
def _manual_update_panel(commands: str) -> SafeHtml:
return trusted(
f"""
Manual updates
FRITZ!Box fills in the IP placeholders automatically. Scripts can use curl. If no IP is sent, KarlDNS uses the request IP.
{_code_window(commands, copy_label="manual update commands", class_name="manual-code")}
"""
)
def _fritzbox_fields(
update_url: str,
hostname: str,
management_url: str | None = None,
) -> str:
lines = [
f"Update-URL: {update_url}",
f"Domainnamen: {hostname}",
f"CNAME target: {hostname}",
]
if management_url:
lines.append(f"Private dashboard: {management_url}")
return "\n".join(lines)
def _secret_notice(*, include_registration: bool = False) -> SafeHtml:
secret_items = "Update URL, register URL, and dashboard link" if include_registration else "Update URL and dashboard link"
return trusted(
f"""
Public: Hostname and CNAME target can be shared.
Secret: {escape(secret_items)} can update or manage this hostname.
Secrets are hidden on screen. Copy still copies the real value.
"""
)
def _message_band(message: str | None) -> SafeHtml:
if not message:
return trusted("")
return trusted(
f"""
"""
)