This repository has been archived on 2026-10-28. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
karldns.de/karldns/web/templates_admin_tables.py
T

141 lines
5.5 KiB
Python

from __future__ import annotations
from collections.abc import Mapping
from dataclasses import dataclass
from datetime import datetime
from .components import _event_label
from .html_utils import attr, escape
@dataclass(frozen=True, slots=True)
class _AdminEventRow:
created_raw: str
hostname_raw: str
detail_raw: str
created: str
hostname: str
status: str
ipv4: str
ipv6: str
count: str
detail: str
def _admin_event_row_view(event: Mapping[str, object]) -> _AdminEventRow:
created_raw = _event_text(event, "created_at")
hostname_raw = _event_text(event, "hostname")
detail_raw = _event_text(event, "detail")
return _AdminEventRow(
created_raw=created_raw,
hostname_raw=hostname_raw,
detail_raw=detail_raw,
created=escape(_short_time(created_raw)),
hostname=escape(hostname_raw),
status=escape(_event_label(event.get("status"))),
ipv4=escape(_event_text(event, "ipv4")),
ipv6=escape(_event_text(event, "ipv6")),
count=escape(event.get("count") or 1),
detail=escape(detail_raw),
)
def _event_text(event: Mapping[str, object], key: str) -> str:
return str(event.get(key) or "-")
def _account_row(account: Mapping[str, object], csrf: str = "", admin_path: str = "/admin") -> str:
raw_hostname = str(account["hostname"])
hostname = escape(raw_hostname)
hostname_attr = attr(raw_hostname)
status = "Active" if account.get("accepted_at") else "Pending"
ipv4_raw = str(account.get("ipv4") or "-")
ipv6_raw = str(account.get("ipv6") or "-")
updated_raw = str(account.get("updated_at") or "Never")
ipv4 = escape(ipv4_raw)
ipv6 = escape(ipv6_raw)
updated = escape(_short_time(updated_raw, "Never"))
safe_admin_path = attr(admin_path)
return f"""
<tr>
<td class="cell-hostname" title="{hostname_attr}">{hostname}</td>
<td class="cell-status">{escape(status)}</td>
<td class="cell-ip" title="{attr(ipv4_raw)}">{ipv4}</td>
<td class="cell-ip" title="{attr(ipv6_raw)}">{ipv6}</td>
<td class="cell-time" title="{attr(updated_raw)}">{updated}</td>
<td class="cell-actions">
<form method="post" action="{safe_admin_path}/hostnames/rotate" class="admin-action-group">
<input type="hidden" name="csrf" value="{attr(csrf)}">
<input type="hidden" name="hostname" value="{hostname_attr}">
<button name="action" value="update" class="btn btn-sm btn-light rounded-pill"
title="Create a new update URL" aria-label="New update URL for {hostname_attr}">Update URL</button>
<button name="action" value="management" class="btn btn-sm btn-light rounded-pill"
title="Create a new dashboard link" aria-label="New dashboard link for {hostname_attr}">Dashboard</button>
</form>
</td>
<td class="cell-delete">
<form method="post" action="{safe_admin_path}/hostnames/delete">
<input type="hidden" name="csrf" value="{attr(csrf)}">
<input type="hidden" name="hostname" value="{hostname_attr}">
<button class="btn btn-sm btn-outline-danger rounded-pill" title="Delete hostname"
aria-label="Delete {hostname_attr}">Delete</button>
</form>
</td>
</tr>
"""
def _admin_table_toolbar(table_id: str, search_label: str) -> str:
safe_id = attr(table_id)
safe_label = attr(search_label)
return f"""
<div class="admin-table-shell mt-3" data-admin-table>
<div class="d-flex align-items-center justify-content-between gap-3 flex-column flex-md-row mb-3">
<label class="w-100 mb-0" for="{safe_id}-search">
<span class="visually-hidden">{safe_label}</span>
<input id="{safe_id}-search" class="form-control" type="search" placeholder="{safe_label}"
data-table-search autocomplete="off">
</label>
<div class="d-flex align-items-center gap-2 flex-shrink-0">
<span class="small text-secondary text-nowrap" data-table-count>0-0 of 0</span>
<label class="visually-hidden" for="{safe_id}-page-size">Rows per page</label>
<select id="{safe_id}-page-size" class="form-select form-select-sm table-page-size" data-table-page-size>
<option value="10">10</option>
<option value="25" selected>25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</div>
</div>
"""
def _admin_table_pagination() -> str:
return """
<nav class="mt-3" aria-label="Table pagination">
<ul class="pagination pagination-sm justify-content-end mb-0" data-table-pagination></ul>
</nav>
</div>
"""
def _event_row(event: Mapping[str, object]) -> str:
row = _admin_event_row_view(event)
return (
f'<tr><td class="cell-time" title="{attr(row.created_raw)}">{row.created}</td>'
f'<td class="cell-hostname" title="{attr(row.hostname_raw)}">{row.hostname}</td>'
f'<td class="cell-status">{row.status}</td><td class="cell-ip">{row.ipv4}</td>'
f'<td class="cell-ip">{row.ipv6}</td><td class="cell-count">{row.count}</td>'
f'<td class="cell-detail" title="{attr(row.detail_raw)}">{row.detail}</td></tr>'
)
def _short_time(value: str, fallback: str = "-") -> str:
if not value or value == fallback:
return fallback
try:
parsed = datetime.fromisoformat(value)
except ValueError:
return value
return parsed.strftime("%Y-%m-%d %H:%M")