From 0e9433d53b2adb79238fb75145ec6ca103887c3c Mon Sep 17 00:00:00 2001 From: m5r Date: Wed, 12 Aug 2026 22:45:53 +0200 Subject: [PATCH] milestone 23 s3: convert features/queries/QueryLogPage.tsx to stylex --- web/src/features/queries/QueryLogPage.tsx | 256 ++++++++++++++++------ 1 file changed, 193 insertions(+), 63 deletions(-) diff --git a/web/src/features/queries/QueryLogPage.tsx b/web/src/features/queries/QueryLogPage.tsx index 37d3fc6..426d9bb 100644 --- a/web/src/features/queries/QueryLogPage.tsx +++ b/web/src/features/queries/QueryLogPage.tsx @@ -1,14 +1,146 @@ import { useState, type FormEvent } from "react"; import { useInfiniteQuery } from "@tanstack/react-query"; +import * as stylex from "@stylexjs/stylex"; import * as api from "@/lib/api"; import { formatMicros, formatTime } from "@/lib/format"; import { queriesInfiniteQuery } from "@/lib/queries"; import type { QueriesFilter, QueryRow } from "@/lib/types"; import { qtypeName } from "./qtype"; -import { buttonClass, smallInputClass, tableWrapClass } from "@/ui/classes"; +import Select from "@/ui/Select"; +import { styles as shared } from "@/ui/styles"; +import { colors } from "@/ui/tokens.stylex"; -const filterInputClass = `mt-1 w-full ${smallInputClass}`; -const toolbarButtonClass = `${buttonClass} font-medium`; +const DARK = "@media (prefers-color-scheme: dark)"; + +const STATUS_OPTIONS = [ + { value: "any", label: "All" }, + { value: "blocked", label: "Blocked only" }, + { value: "allowed", label: "Allowed only" }, +]; + +const styles = stylex.create({ + heading: { + fontSize: "1.5rem", + lineHeight: "2rem", + fontWeight: 600, + }, + /** One column on a phone, two from `sm`, five from `lg`, as before. */ + filterGrid: { + marginTop: "1rem", + display: "grid", + gap: "0.75rem", + gridTemplateColumns: { + default: "repeat(1, minmax(0, 1fr))", + "@media (min-width: 640px)": "repeat(2, minmax(0, 1fr))", + "@media (min-width: 1024px)": "repeat(5, minmax(0, 1fr))", + }, + }, + filterLabel: { + display: "block", + fontSize: "0.875rem", + lineHeight: "1.25rem", + }, + filterInput: { + marginTop: "0.25rem", + width: "100%", + }, + buttonRow: { + display: "flex", + alignItems: "flex-end", + gap: "0.5rem", + gridColumn: { + default: null, + "@media (min-width: 640px)": "span 2 / span 2", + "@media (min-width: 1024px)": "span 5 / span 5", + }, + }, + toolbarButton: { + fontWeight: 500, + }, + note: { + fontSize: "0.875rem", + lineHeight: "1.25rem", + color: colors.textMuted, + }, + empty: { + marginTop: "1.5rem", + color: colors.textMuted, + }, + tableWrap: { + marginTop: "1rem", + overflowX: "auto", + borderRadius: "0.25rem", + borderWidth: 1, + borderStyle: "solid", + borderColor: colors.border, + }, + table: { + width: "100%", + fontSize: "0.875rem", + lineHeight: "1.25rem", + }, + /** The header tint is a shade off the ground in each scheme, not a token role. */ + head: { + backgroundColor: { default: "oklch(98.5% 0 none)", [DARK]: "oklch(21% 0.006 285.885)" }, + textAlign: "left", + }, + th: { + paddingInline: "0.75rem", + paddingBlock: "0.5rem", + fontWeight: 500, + color: colors.textMuted, + }, + /** `divide-y`: a hairline between rows, so the first row carries none. */ + row: { + borderTopWidth: { default: 1, ":first-child": 0 }, + borderTopStyle: "solid", + borderTopColor: colors.border, + }, + cell: { + paddingInline: "0.75rem", + paddingBlock: "0.5rem", + }, + nowrap: { + whiteSpace: "nowrap", + }, + breakAll: { + wordBreak: "break-all", + }, + small: { + fontSize: "0.75rem", + lineHeight: "1rem", + }, + muted: { + color: colors.textMuted, + }, + blockedWrap: { + display: "inline-flex", + alignItems: "center", + gap: "0.375rem", + }, + blockedBadge: { + borderRadius: "0.25rem", + paddingInline: "0.375rem", + paddingBlock: "0.125rem", + fontSize: "0.75rem", + lineHeight: "1rem", + fontWeight: 500, + backgroundColor: { default: "oklch(93.6% 0.032 17.717)", [DARK]: "oklch(39.6% 0.141 25.723)" }, + color: { default: "oklch(44.4% 0.177 26.899)", [DARK]: "oklch(88.5% 0.062 18.334)" }, + }, + footer: { + marginTop: "0.75rem", + display: "flex", + alignItems: "center", + gap: "0.75rem", + }, + moreError: { + marginTop: "0.5rem", + fontSize: "0.875rem", + lineHeight: "1.25rem", + color: colors.dangerText, + }, +}); function errorMessage(error: unknown): string { return error instanceof Error ? error.message : String(error); @@ -21,13 +153,11 @@ function datetimeLocalToUnix(value: string): number | undefined { } export function BlockedCell({ row }: { row: Pick }) { - if (!row.blocked) return ; + if (!row.blocked) return ; return ( - - - Blocked - - {row.block_reason !== "" && {row.block_reason}} + + Blocked + {row.block_reason !== "" && {row.block_reason}} ); } @@ -35,37 +165,38 @@ export function BlockedCell({ row }: { row: Pick }) { return ( <> - {formatTime(row.ts)} - {row.domain} - {row.client_ip} - {qtypeName(row.qtype)} - + {formatTime(row.ts)} + {row.domain} + {row.client_ip} + {qtypeName(row.qtype)} + - + {row.response_time_us === null ? "—" : formatMicros(row.response_time_us)} - + {row.cache_hit === null ? "—" : row.cache_hit ? "hit" : "miss"} - {row.upstream === "" ? "—" : row.upstream} + + {row.upstream === "" ? "—" : row.upstream} + ); } export function QueryTableHead() { - const th = "px-3 py-2 font-medium text-zinc-600 dark:text-zinc-400"; return ( - + - Time - Domain - Client - Type - Status - Response - Cache - Upstream + Time + Domain + Client + Type + Status + Response + Cache + Upstream ); @@ -124,66 +255,65 @@ export default function QueryLogPage() { return (
-

Query Log

+

Query Log

-
-