milestone 18: collapse duplicated infrastructure into shared listener core, crud list helper, resource shells, transport race, name and line helpers, ui modules
CI / test (push) Successful in 1m22s
CI / test-aarch64 (push) Successful in 5m6s
CI / frontend (push) Successful in 45s
CI / cross (push) Successful in 7m53s
CI / docker (push) Failing after 1h10m57s

This commit is contained in:
2026-08-07 18:20:30 +02:00
parent c50c6d285a
commit 6f67940995
82 changed files with 3167 additions and 3114 deletions
+20 -28
View File
@@ -4,11 +4,7 @@ import { formatTime } from "@/lib/format";
import InlineError from "@/lib/InlineError";
import { groupsQuery, ruleCreateMutation, ruleDeleteMutation, rulesQuery } from "@/lib/queries";
import type { Rule, RuleAction, RuleKind } from "@/lib/types";
const TH_CLASS = "border-b border-zinc-300 px-3 py-2 text-left font-medium dark:border-zinc-700";
const TD_CLASS = "border-b border-zinc-200 px-3 py-2 dark:border-zinc-800";
const INPUT_CLASS =
"mt-1 w-full rounded border border-zinc-300 bg-white px-3 py-2 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 dark:border-zinc-700 dark:bg-zinc-900";
import { dangerLinkButtonClass, inputClass, primaryButtonClass, tableWrapClass, tdClass, thClass } from "@/ui/classes";
export default function RulesPage() {
const queryClient = useQueryClient();
@@ -44,16 +40,16 @@ export default function RulesPage() {
{rules.length === 0 ? (
<p className="mt-4 text-zinc-500">No allow or block rules yet. Create one below.</p>
) : (
<div className="mt-4 overflow-x-auto">
<div className={tableWrapClass}>
<table className="w-full min-w-max border-collapse text-sm">
<thead>
<tr>
<th className={TH_CLASS}>Pattern</th>
<th className={TH_CLASS}>Kind</th>
<th className={TH_CLASS}>Action</th>
<th className={TH_CLASS}>Group</th>
<th className={TH_CLASS}>Created</th>
<th className={TH_CLASS}>
<th className={thClass}>Pattern</th>
<th className={thClass}>Kind</th>
<th className={thClass}>Action</th>
<th className={thClass}>Group</th>
<th className={thClass}>Created</th>
<th className={thClass}>
<span className="sr-only">Actions</span>
</th>
</tr>
@@ -61,9 +57,9 @@ export default function RulesPage() {
<tbody>
{rules.map((rule) => (
<tr key={rule.id}>
<td className={`${TD_CLASS} font-medium`}>{rule.pattern}</td>
<td className={TD_CLASS}>{rule.kind}</td>
<td className={TD_CLASS}>
<td className={`${tdClass} font-medium`}>{rule.pattern}</td>
<td className={tdClass}>{rule.kind}</td>
<td className={tdClass}>
<span
className={
rule.action === "allow"
@@ -74,14 +70,14 @@ export default function RulesPage() {
{rule.action}
</span>
</td>
<td className={TD_CLASS}>{rule.group}</td>
<td className={TD_CLASS}>{formatTime(rule.created_at)}</td>
<td className={TD_CLASS}>
<td className={tdClass}>{rule.group}</td>
<td className={tdClass}>{formatTime(rule.created_at)}</td>
<td className={tdClass}>
<button
type="button"
onClick={() => deleteRule(rule)}
disabled={remove.isPending}
className="text-sm font-medium text-red-600 disabled:opacity-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600 dark:text-red-400"
className={dangerLinkButtonClass}
>
Delete
</button>
@@ -107,7 +103,7 @@ export default function RulesPage() {
value={pattern}
onChange={(event) => setPattern(event.target.value)}
placeholder="ads.example.com or *.example.com"
className={INPUT_CLASS}
className={inputClass}
/>
</div>
<div className="grid grid-cols-1 gap-3 sm:grid-cols-3">
@@ -119,7 +115,7 @@ export default function RulesPage() {
id="rule-kind"
value={kind}
onChange={(event) => setKind(event.target.value as RuleKind)}
className={INPUT_CLASS}
className={inputClass}
>
<option value="exact">exact</option>
<option value="wildcard">wildcard</option>
@@ -133,7 +129,7 @@ export default function RulesPage() {
id="rule-action"
value={action}
onChange={(event) => setAction(event.target.value as RuleAction)}
className={INPUT_CLASS}
className={inputClass}
>
<option value="allow">allow</option>
<option value="block">block</option>
@@ -147,7 +143,7 @@ export default function RulesPage() {
id="rule-group"
value={groupId}
onChange={(event) => setGroupId(Number(event.target.value))}
className={INPUT_CLASS}
className={inputClass}
>
{groups.map((group) => (
<option key={group.id} value={group.id}>
@@ -157,11 +153,7 @@ export default function RulesPage() {
</select>
</div>
</div>
<button
type="submit"
disabled={create.isPending}
className="rounded bg-blue-600 px-3 py-1.5 text-sm font-medium text-white disabled:opacity-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
>
<button type="submit" disabled={create.isPending} className={primaryButtonClass}>
{create.isPending ? "Creating…" : "Create rule"}
</button>
<InlineError error={create.error} />