Archived
Refactor UI assets and harden quality checks
This commit is contained in:
1 parent
23ab466c52
commit
4ecc8eb874
62 files changed
+3314
-3206
No files matched your search
+110
-39
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from ipaddress import ip_address
|
||||
|
||||
from .html_utils import SafeHtml, attr, escape, trusted
|
||||
@@ -28,32 +29,77 @@ def _event_label(status: object) -> str:
|
||||
return _EVENT_LABELS.get(value, value.replace("_", " "))
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class _CopyRowParts:
|
||||
label: str
|
||||
value: str
|
||||
safe_label: str
|
||||
safe_value: str
|
||||
attr_label: str
|
||||
attr_value: str
|
||||
is_secret: bool
|
||||
should_mask: bool
|
||||
sensitivity_badge: str
|
||||
|
||||
@property
|
||||
def row_class(self) -> str:
|
||||
return "copy-field copy-field-secret" if self.is_secret else "copy-field copy-field-public"
|
||||
|
||||
@property
|
||||
def value_field(self) -> str:
|
||||
if self.should_mask:
|
||||
return (
|
||||
f'<input class="copy-pill-value copy-value" type="password" value="{self.attr_value}" '
|
||||
"readonly data-secret-input>"
|
||||
)
|
||||
return f'<code class="copy-pill-value copy-value">{self.safe_value}</code>'
|
||||
|
||||
@property
|
||||
def reveal_button(self) -> str:
|
||||
if not self.should_mask:
|
||||
return ""
|
||||
return (
|
||||
f'<button type="button" class="btn btn-outline-secondary rounded-pill copy-reveal-button" '
|
||||
f'data-secret-toggle aria-label="Show {self.attr_label}">View</button>'
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_values(
|
||||
cls, label: str, value: str, sensitivity: str | None = None, *, mask_secret: bool = True
|
||||
) -> _CopyRowParts:
|
||||
is_secret = bool(sensitivity and sensitivity.lower() == "secret")
|
||||
return cls(
|
||||
label=label,
|
||||
value=value,
|
||||
safe_label=escape(label),
|
||||
safe_value=escape(value),
|
||||
attr_label=attr(label),
|
||||
attr_value=attr(value),
|
||||
is_secret=is_secret,
|
||||
should_mask=is_secret and mask_secret,
|
||||
sensitivity_badge=_copy_row_sensitivity_badge(sensitivity, is_secret),
|
||||
)
|
||||
|
||||
|
||||
def _copy_row_sensitivity_badge(sensitivity: str | None, is_secret: bool) -> str:
|
||||
if not sensitivity:
|
||||
return ""
|
||||
badge_kind = "secret" if is_secret else "public"
|
||||
return f'<span class="ui-badge ui-badge-{badge_kind}">{escape(sensitivity)}</span>'
|
||||
|
||||
|
||||
def _copy_row(label: str, value: str, sensitivity: str | None = None, *, mask_secret: bool = True) -> SafeHtml:
|
||||
safe_label = escape(label)
|
||||
safe_value = escape(value)
|
||||
attr_label = attr(label)
|
||||
attr_value = attr(value)
|
||||
is_secret = bool(sensitivity and sensitivity.lower() == "secret")
|
||||
should_mask = is_secret and mask_secret
|
||||
value_field = (
|
||||
f'<input class="copy-pill-value copy-value" type="password" value="{attr_value}" readonly data-secret-input>'
|
||||
if should_mask
|
||||
else f'<code class="copy-pill-value copy-value">{safe_value}</code>'
|
||||
)
|
||||
reveal_button = (
|
||||
f'<button type="button" class="btn btn-outline-secondary rounded-pill copy-reveal-button" data-secret-toggle aria-label="Show {attr_label}">View</button>'
|
||||
if should_mask
|
||||
else ""
|
||||
)
|
||||
row_class = "copy-field copy-field-secret" if is_secret else "copy-field copy-field-public"
|
||||
parts = _CopyRowParts.from_values(label, value, sensitivity, mask_secret=mask_secret)
|
||||
return trusted(f"""
|
||||
<div class="{row_class}">
|
||||
<div class="{parts.row_class}">
|
||||
<div class="copy-pill">
|
||||
<span class="copy-pill-label">{safe_label}</span>
|
||||
{value_field}
|
||||
{reveal_button}
|
||||
<span class="copy-pill-label">{parts.safe_label}</span>
|
||||
{parts.sensitivity_badge}
|
||||
{parts.value_field}
|
||||
{parts.reveal_button}
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline-secondary rounded-pill copy-button" data-copy="{attr_value}" aria-label="Copy {attr_label}">
|
||||
<button type="button" class="btn btn-outline-secondary rounded-pill copy-button"
|
||||
data-copy="{parts.attr_value}" aria-label="Copy {parts.attr_label}">
|
||||
{_svg_icon("clipboard")}<span data-copy-label>Copy</span>{_svg_icon("check", "check-icon")}
|
||||
</button>
|
||||
</div>
|
||||
@@ -66,16 +112,22 @@ def _copy_button(label: str, value: str, *, style: str = "outline-primary") -> S
|
||||
safe_style = attr(style)
|
||||
return trusted(
|
||||
f'<button type="button" class="btn btn-{safe_style} rounded-pill copy-button" data-copy="{safe_value}" '
|
||||
f'aria-label="Copy {safe_label}">{_svg_icon("clipboard")}<span data-copy-label>{safe_label}</span>{_svg_icon("check", "check-icon")}</button>'
|
||||
f'aria-label="Copy {safe_label}">{_svg_icon("clipboard")}'
|
||||
f'<span data-copy-label>{safe_label}</span>{_svg_icon("check", "check-icon")}</button>'
|
||||
)
|
||||
|
||||
|
||||
def _copy_icon_button(label: str, value: str) -> SafeHtml:
|
||||
safe_label = escape(label)
|
||||
safe_value = attr(value)
|
||||
button_attrs = (
|
||||
f'type="button" class="copy-icon-button" data-copy="{safe_value}" '
|
||||
f'aria-label="Copy {safe_label}" title="Copy"'
|
||||
)
|
||||
return trusted(
|
||||
f'<button type="button" class="copy-icon-button" data-copy="{safe_value}" aria-label="Copy {safe_label}" title="Copy">'
|
||||
f'{_svg_icon("clipboard")}<span class="visually-hidden" data-copy-label>Copy {safe_label}</span>{_svg_icon("check", "check-icon")}</button>'
|
||||
f"<button {button_attrs}>"
|
||||
f'{_svg_icon("clipboard")}<span class="visually-hidden" data-copy-label>Copy {safe_label}</span>'
|
||||
f'{_svg_icon("check", "check-icon")}</button>'
|
||||
)
|
||||
|
||||
|
||||
@@ -83,9 +135,14 @@ def _copy_menu(label: str, items: list[tuple[str, str]]) -> SafeHtml:
|
||||
safe_label = escape(label)
|
||||
buttons = []
|
||||
for item_label, item_value in items:
|
||||
button_attrs = (
|
||||
f'type="button" class="copy-menu-item" data-copy="{attr(item_value)}" '
|
||||
f'aria-label="Copy {attr(item_label)}"'
|
||||
)
|
||||
buttons.append(
|
||||
f'<button type="button" class="copy-menu-item" data-copy="{attr(item_value)}" aria-label="Copy {attr(item_label)}">'
|
||||
f'{_svg_icon("clipboard")}<span data-copy-label>{escape(item_label)}</span>{_svg_icon("check", "check-icon")}</button>'
|
||||
f"<button {button_attrs}>"
|
||||
f'{_svg_icon("clipboard")}<span data-copy-label>{escape(item_label)}</span>'
|
||||
f'{_svg_icon("check", "check-icon")}</button>'
|
||||
)
|
||||
return trusted(f"""
|
||||
<details class="copy-menu">
|
||||
@@ -106,7 +163,7 @@ def _code_window(
|
||||
code_lines = "\n".join(f"<code>{escape(line)}</code>" for line in commands.splitlines())
|
||||
copy_value = copy_value if copy_value is not None else _code_copy_value(commands)
|
||||
return trusted(f"""
|
||||
<div class="terminal-card code-window{safe_class}">
|
||||
<div class="terminal-card code-window{safe_class}" data-slot="code-window">
|
||||
{_copy_icon_button(copy_label, copy_value)}
|
||||
{code_lines}
|
||||
</div>
|
||||
@@ -115,10 +172,14 @@ def _code_window(
|
||||
|
||||
def _code_copy_value(commands: str) -> str:
|
||||
lines = commands.splitlines()
|
||||
prompt_lines = [line[2:] for line in lines if line.startswith("$ ")]
|
||||
prompt_lines = [_code_copy_line(line) 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)
|
||||
return "\n".join(_code_copy_line(line) for line in lines)
|
||||
|
||||
|
||||
def _code_copy_line(line: str) -> str:
|
||||
return line[2:] if line.startswith("$ ") else line
|
||||
|
||||
|
||||
def _example_update_ips(request_ip: str | None = None) -> tuple[str, str]:
|
||||
@@ -164,12 +225,17 @@ def _known_request_ip(request_ip: str | None = None) -> tuple[int | None, str]:
|
||||
|
||||
def _manual_update_panel(commands: str) -> SafeHtml:
|
||||
return trusted(f"""
|
||||
<div class="card card-body gap-3 mt-4">
|
||||
<div>
|
||||
<div class="card card-body gap-3 mt-4" data-slot="card">
|
||||
<div data-slot="card-header">
|
||||
<h3>Manual updates</h3>
|
||||
<p class="section-copy">FRITZ!Box fills in the IP placeholders automatically. Scripts can use curl. If no IP is sent, KarlDNS uses the request IP.</p>
|
||||
<p class="section-copy">
|
||||
FRITZ!Box fills in the IP placeholders automatically. Scripts can use curl.
|
||||
If no IP is sent, KarlDNS uses the request IP.
|
||||
</p>
|
||||
</div>
|
||||
<div data-slot="card-content">
|
||||
{_code_window(commands, copy_label="manual update commands", class_name="manual-code")}
|
||||
</div>
|
||||
{_code_window(commands, copy_label="manual update commands", class_name="manual-code")}
|
||||
</div>
|
||||
""")
|
||||
|
||||
@@ -194,10 +260,14 @@ def _secret_notice(*, include_registration: bool = False) -> SafeHtml:
|
||||
"Update URL, register URL, and dashboard link" if include_registration else "Update URL and dashboard link"
|
||||
)
|
||||
return trusted(f"""
|
||||
<div class="alert alert-info my-3 py-2 small" role="note">
|
||||
<strong>Public:</strong> Hostname and CNAME target can be shared.
|
||||
<strong>Secret:</strong> {escape(secret_items)} can update or manage this hostname.
|
||||
Secrets are hidden on screen. Copy still copies the real value.
|
||||
<div class="alert alert-info ui-alert my-3 py-2 small" role="note" data-slot="alert">
|
||||
<span class="ui-badge ui-badge-public">Public:</span>
|
||||
<span>Hostname and CNAME target can be shared.</span>
|
||||
<span class="ui-badge ui-badge-secret">Secret:</span>
|
||||
<span>
|
||||
{escape(secret_items)} can update or manage this hostname. Secrets are hidden on screen.
|
||||
Copy still copies the real value.
|
||||
</span>
|
||||
</div>
|
||||
""")
|
||||
|
||||
@@ -206,7 +276,8 @@ def _message_band(message: str | None) -> SafeHtml:
|
||||
if not message:
|
||||
return trusted("")
|
||||
return trusted(f"""
|
||||
<div class="alert alert-warning mb-0 rounded-0 border-start-0 border-end-0" role="status">
|
||||
<div class="alert alert-warning ui-alert mb-0 rounded-0 border-start-0 border-end-0"
|
||||
role="status" data-slot="alert">
|
||||
<div class="container">{escape(message)}</div>
|
||||
</div>
|
||||
""")
|
||||
Reference in new issue
Block a user