Archived
Simplify DDNS flow and polish UI
This commit is contained in:
1 parent
5beee0e5c6
commit
5a90bd9d87
42 files changed
+587
-1418
No files matched your search
@@ -4,7 +4,7 @@ from ..ddns_models import DdnsSettings
|
||||
from ..ddns_service import DdnsService
|
||||
from .html_utils import SafeHtml, attr, escape, trusted
|
||||
from .templates import (
|
||||
_copy_button,
|
||||
_copy_menu,
|
||||
_copy_row,
|
||||
_fritzbox_fields,
|
||||
_manual_update_commands,
|
||||
@@ -24,7 +24,6 @@ def _render_management_page(
|
||||
account: dict[str, str | int | None],
|
||||
csrf_token: str,
|
||||
message: str | None = None,
|
||||
challenge: dict[str, str | None] | None = None,
|
||||
created_account: dict[str, str] | None = None,
|
||||
) -> str:
|
||||
account_for_url = {
|
||||
@@ -48,7 +47,6 @@ def _render_management_page(
|
||||
"""
|
||||
message_html = _management_message(message)
|
||||
rotated_credentials = _credentials_panel(service, created_account)
|
||||
domain_claim = _management_domain_claim_panel(service, management_slug, csrf_token, challenge)
|
||||
dashboard_password_panel = _dashboard_password_panel(management_slug, csrf_token, bool(account.get("dashboard_password_hash")))
|
||||
return _page(
|
||||
"KarlDNS management",
|
||||
@@ -71,12 +69,11 @@ def _render_management_page(
|
||||
<div class="col-lg-6">
|
||||
<p class="eyebrow">Router settings</p>
|
||||
<h2>Router update settings</h2>
|
||||
<p class="section-copy">Use the Update-URL if your router has one URL field. Username and password are only for routers that require them. For your own subdomain, create a CNAME to this hostname.</p>
|
||||
<p class="section-copy">Use the Update-URL if your router has one URL field. Username and password are only for routers that require them. Optional: point another DNS name at this hostname with a CNAME.</p>
|
||||
{_one_time_secret_notice()}
|
||||
{_secret_notice()}
|
||||
<div class="d-flex gap-2 flex-wrap mt-3">
|
||||
{_copy_button("Copy router fields", fritzbox_fields)}
|
||||
{_copy_button("Copy CNAME", cname_record)}
|
||||
<div class="copy-actions mt-3">
|
||||
{_copy_menu("Copy setup values", [("Router fields", fritzbox_fields), ("CNAME", cname_record)])}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-lg-6 d-grid gap-2">
|
||||
@@ -109,7 +106,6 @@ def _render_management_page(
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
{domain_claim}
|
||||
{dashboard_password_panel}
|
||||
<section class="section">
|
||||
<div class="container d-flex align-items-md-center justify-content-between gap-4 flex-column flex-md-row">
|
||||
@@ -275,99 +271,6 @@ def _render_management_unlock_page(
|
||||
)
|
||||
|
||||
|
||||
def _management_domain_claim_panel(
|
||||
service: DdnsService,
|
||||
management_slug: str,
|
||||
csrf_token: str,
|
||||
challenge: dict[str, str | None] | None,
|
||||
) -> SafeHtml:
|
||||
create_claim = trusted(
|
||||
f"""
|
||||
<div class="card card-body gap-3">
|
||||
<div>
|
||||
<p class="eyebrow">Custom domain</p>
|
||||
<h2>Use your domain</h2>
|
||||
<p class="section-copy">Create a TXT record, verify it here, then create a hostname under that domain.</p>
|
||||
</div>
|
||||
<form method="post" action="/m/{attr(management_slug)}/domains/request" class="d-grid gap-3" aria-label="Create dashboard domain claim">
|
||||
<input type="hidden" name="csrf" value="{attr(csrf_token)}">
|
||||
<label>Domain
|
||||
<input class="form-control" name="domain" placeholder="example.com" autocomplete="off" autocapitalize="none" spellcheck="false" required>
|
||||
</label>
|
||||
<button type="submit" class="btn btn-primary rounded-pill">Create claim</button>
|
||||
</form>
|
||||
</div>
|
||||
"""
|
||||
)
|
||||
if not challenge:
|
||||
return trusted(
|
||||
f"""
|
||||
<section class="section">
|
||||
<div class="container narrow">{create_claim}</div>
|
||||
</section>
|
||||
"""
|
||||
)
|
||||
|
||||
domain = str(challenge["domain"])
|
||||
claim_secret = str(challenge.get("claim_secret") or "")
|
||||
verification_name = service.verification_name(domain)
|
||||
token = str(challenge["token"])
|
||||
claim_url = service.domain_claim_url(challenge)
|
||||
status = "Verified" if challenge.get("verified_at") else "Waiting for TXT"
|
||||
txt_bundle = f"TXT name: {verification_name}\nTXT value: {token}"
|
||||
create_hostname = _management_custom_hostname_form(management_slug, claim_secret, csrf_token) if challenge.get("verified_at") else trusted("")
|
||||
return trusted(
|
||||
f"""
|
||||
<section class="section" id="custom-domain">
|
||||
<div class="container narrow d-grid gap-4">
|
||||
{create_claim}
|
||||
<div class="card card-body gap-3">
|
||||
<div>
|
||||
<p class="eyebrow">Domain claim</p>
|
||||
<h2>{escape(domain)}</h2>
|
||||
<p class="section-copy">Status: {escape(status)}</p>
|
||||
<p class="small text-secondary mb-0">This claim belongs to this dashboard. Other dashboards cannot use it.</p>
|
||||
</div>
|
||||
<div class="alert alert-info mb-0 py-2 small">TXT name and value are public. Claim secret and private claim link are secret.</div>
|
||||
<div class="d-flex gap-2 flex-wrap">
|
||||
{_copy_button("Copy TXT", txt_bundle)}
|
||||
</div>
|
||||
<div class="d-grid gap-2">
|
||||
{_copy_row("TXT name", verification_name, "Public")}
|
||||
{_copy_row("TXT value", token, "Public")}
|
||||
{_copy_row("Claim secret", claim_secret, "Secret")}
|
||||
{_copy_row("Private claim link", claim_url, "Secret")}
|
||||
</div>
|
||||
<form method="post" action="/m/{attr(management_slug)}/domains/verify" class="d-flex gap-2 flex-wrap" aria-label="Verify dashboard domain claim">
|
||||
<input type="hidden" name="csrf" value="{attr(csrf_token)}">
|
||||
<input type="hidden" name="domain" value="{attr(domain)}">
|
||||
<input type="hidden" name="claim_secret" value="{attr(claim_secret)}">
|
||||
<button type="submit" class="btn btn-primary rounded-pill">Verify</button>
|
||||
</form>
|
||||
{create_hostname}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def _management_custom_hostname_form(management_slug: str, claim_secret: str, csrf_token: str | None = None) -> SafeHtml:
|
||||
csrf_field = f'<input type="hidden" name="csrf" value="{attr(csrf_token)}">' if csrf_token else ""
|
||||
return trusted(
|
||||
f"""
|
||||
<form method="post" action="/m/{attr(management_slug)}/domains/hostnames" class="d-grid gap-3" aria-label="Create verified custom hostname">
|
||||
{csrf_field}
|
||||
<input type="hidden" name="claim_secret" value="{attr(claim_secret)}">
|
||||
<label>Hostname
|
||||
<input class="form-control" name="hostname" placeholder="home.example.com" autocomplete="off" autocapitalize="none" spellcheck="false" required>
|
||||
</label>
|
||||
<button type="submit" class="btn btn-primary rounded-pill">Create hostname</button>
|
||||
</form>
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
def _user_event_row(event: dict[str, str | int | None]) -> str:
|
||||
created = escape(event.get("created_at") or "-")
|
||||
status = escape(event.get("status") or "-")
|
||||
|
||||
Reference in new issue
Block a user