fix(dashboard): eliminate @apply CSS, fix status column, fix click propagation

Playwright testing revealed 3 critical bugs:

1. Tailwind CDN @apply with custom brand-* colors produces empty CSS
   rules, breaking ALL design components (kpi-card, data-table, badges,
   filter-btn, section-card, nav-item). Fix: replace all @apply
   directives with equivalent raw CSS values.

2. Traffic API and IP detail API reference non-existent 'status' column
   in http_logs table → HTTP 500 on /traffic and /ip/{ip}. Fix: remove
   status from SELECT, sort whitelist, filters, and templates.

3. Nested <a> links (fmtJA4, fmtASN, fmtCountry, fmtBotName) inside
   clickable <tr onclick> capture clicks, preventing row navigation to
   /ip/ detail. Fix: add event.stopPropagation() to all formatter links.

Verified with Playwright: 10 pages × 0 JS errors, all tooltips hidden
by default, sidebar toggle works, keyboard shortcuts (Alt+1-9, Alt+B),
classification form saves to DB, campaign detail panel opens on click.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
toto
2026-04-09 13:54:38 +02:00
parent 6babc55e3e
commit 70188b508c
4 changed files with 39 additions and 45 deletions

View File

@ -35,7 +35,7 @@ _SCORE_SORT_COLS = {
"asn_org", "country_code", "browser_family",
}
_TRAFFIC_SORT_COLS = {
"time", "src_ip", "method", "status", "host", "path", "http_version",
"time", "src_ip", "method", "host", "path", "http_version",
"header_user_agent", "ja4", "src_country_code",
}
_ORDER_VALUES = {"ASC", "DESC"}
@ -315,7 +315,7 @@ async def traffic(
method: str | None = Query(None),
host: str | None = Query(None),
http_version: str | None = Query(None),
status: int | None = Query(None),
status: int | None = Query(None, description="Not implemented — reserved"),
search: str | None = Query(None),
) -> dict[str, Any]:
sort = _validate_sort(sort, _TRAFFIC_SORT_COLS, "time")
@ -337,10 +337,6 @@ async def traffic(
where_clauses.append("http_version = {http_version:String}")
params["http_version"] = http_version
if status is not None:
where_clauses.append("status = {status:UInt16}")
params["status"] = status
if search:
where_clauses.append(
"(toString(src_ip) LIKE {search:String} "
@ -358,7 +354,7 @@ async def traffic(
)
rows = query(
f"SELECT time, toString(src_ip) AS src_ip, method, status, host, path, "
f"SELECT time, toString(src_ip) AS src_ip, method, host, path, "
f"http_version, header_user_agent, ja4, src_country_code "
f"FROM {_DB_LOGS}.http_logs "
f"WHERE {where} ORDER BY {sort} {order} "
@ -406,7 +402,7 @@ async def ip_detail(ip: str) -> dict[str, Any]:
)
http_logs = query(
f"SELECT time, method, status, host, path, http_version, header_user_agent, ja4 "
f"SELECT time, method, host, path, http_version, header_user_agent, ja4 "
f"FROM {_DB_LOGS}.http_logs "
"WHERE src_ip = toIPv4OrZero({ip:String}) "
"AND time >= now() - INTERVAL 1 DAY "