Archived
141 lines
5.5 KiB
Python
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")
|