feat: improve UI contrast, dark mode, dialog sizing, color coherence, and add table sorting
- Fix dialog scrollability (flex layout + max-h-[90dvh]) and increase L4 dialog to lg width - Add styled enable card to L4 dialog matching proxy host pattern - Unify section colors across proxy host and L4 dialogs (cyan=LB, emerald=DNS, violet=upstream DNS, rose=geo, amber=mTLS) - Improve light mode contrast: muted-foreground oklch 0.552→0.502, remove opacity modifiers on secondary text - Improve dark mode: boost muted-foreground to 0.85, increase border opacity 10%→16%, input 15%→20% - Add bg-card to DataTable wrapper and bg-muted/40 to table headers for surface hierarchy - Add semantic badge variants (success, warning, info, muted) and StatusChip dark mode fix - Add server-side sortable columns to Proxy Hosts and L4 Proxy Hosts (name, upstream, status, protocol, listen) - Add sortKey to DataTable Column type with clickable sort headers (ArrowUp/Down indicators, URL param driven) - Fix E2E test selectors for shadcn UI (label associations, combobox roles, dropdown menus, mobile drawer) - Add htmlFor/id to proxy host form fields and aria-labels to select triggers for accessibility - Add sorting E2E tests for both proxy host pages Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -15,7 +15,7 @@ export function DnsResolverFields({
|
||||
const [enabled, setEnabled] = useState(initial?.enabled ?? false);
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border border-yellow-500/60 bg-yellow-500/5 p-5">
|
||||
<div className="rounded-lg border border-emerald-500/60 bg-emerald-500/5 p-5">
|
||||
<input type="hidden" name="dns_present" value="1" />
|
||||
<input type="hidden" name="dns_enabled_present" value="1" />
|
||||
<div className="flex flex-col gap-4">
|
||||
|
||||
@@ -573,13 +573,13 @@ export function GeoBlockFields({ initialValues, showModeSelector = true }: GeoBl
|
||||
const [mode, setMode] = useState<GeoBlockMode>(initialValues?.geoblock_mode ?? "merge");
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border border-yellow-500/60 bg-yellow-500/5 p-4">
|
||||
<div className="rounded-lg border border-rose-500/60 bg-rose-500/5 p-4">
|
||||
<input type="hidden" name="geoblock_present" value="1" />
|
||||
|
||||
{/* Header */}
|
||||
<div className="flex flex-row items-start justify-between gap-2">
|
||||
<div className="flex flex-row items-start gap-3 flex-1 min-w-0">
|
||||
<div className="mt-0.5 w-8 h-8 rounded-xl bg-yellow-500 flex items-center justify-center shrink-0">
|
||||
<div className="mt-0.5 w-8 h-8 rounded-xl bg-rose-500 flex items-center justify-center shrink-0">
|
||||
<Globe className="h-4 w-4 text-white" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
|
||||
@@ -58,7 +58,7 @@ export function CreateHostDialog({
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
title={initialData ? "Duplicate Proxy Host" : "Create Proxy Host"}
|
||||
maxWidth="md"
|
||||
maxWidth="lg"
|
||||
submitLabel="Create"
|
||||
onSubmit={() => {
|
||||
(document.getElementById("create-host-form") as HTMLFormElement)?.requestSubmit();
|
||||
@@ -76,8 +76,9 @@ export function CreateHostDialog({
|
||||
enabled={true}
|
||||
/>
|
||||
<div>
|
||||
<label className="text-sm font-medium mb-1 block">Name</label>
|
||||
<label htmlFor="name" className="text-sm font-medium mb-1 block">Name</label>
|
||||
<Input
|
||||
id="name"
|
||||
name="name"
|
||||
placeholder="My Service"
|
||||
defaultValue={initialData ? `${initialData.name} (Copy)` : ""}
|
||||
@@ -85,8 +86,9 @@ export function CreateHostDialog({
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium mb-1 block">Domains</label>
|
||||
<label htmlFor="domains" className="text-sm font-medium mb-1 block">Domains</label>
|
||||
<Textarea
|
||||
id="domains"
|
||||
name="domains"
|
||||
placeholder="app.example.com"
|
||||
defaultValue={initialData?.domains.join("\n") ?? ""}
|
||||
@@ -101,7 +103,7 @@ export function CreateHostDialog({
|
||||
<div>
|
||||
<label className="text-sm font-medium mb-1 block">Certificate</label>
|
||||
<Select name="certificate_id" defaultValue={String(initialData?.certificate_id ?? "__none__")}>
|
||||
<SelectTrigger>
|
||||
<SelectTrigger aria-label="Certificate">
|
||||
<SelectValue placeholder="Managed by Caddy (Auto)" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -117,7 +119,7 @@ export function CreateHostDialog({
|
||||
<div>
|
||||
<label className="text-sm font-medium mb-1 block">Access List</label>
|
||||
<Select name="access_list_id" defaultValue={String(initialData?.access_list_id ?? "__none__")}>
|
||||
<SelectTrigger>
|
||||
<SelectTrigger aria-label="Access List">
|
||||
<SelectValue placeholder="None" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -194,7 +196,7 @@ export function EditHostDialog({
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
title="Edit Proxy Host"
|
||||
maxWidth="md"
|
||||
maxWidth="lg"
|
||||
submitLabel="Save Changes"
|
||||
onSubmit={() => {
|
||||
(document.getElementById("edit-host-form") as HTMLFormElement)?.requestSubmit();
|
||||
@@ -212,12 +214,13 @@ export function EditHostDialog({
|
||||
enabled={host.enabled}
|
||||
/>
|
||||
<div>
|
||||
<label className="text-sm font-medium mb-1 block">Name</label>
|
||||
<Input name="name" defaultValue={host.name} required />
|
||||
<label htmlFor="name" className="text-sm font-medium mb-1 block">Name</label>
|
||||
<Input id="name" name="name" defaultValue={host.name} required />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-sm font-medium mb-1 block">Domains</label>
|
||||
<label htmlFor="domains" className="text-sm font-medium mb-1 block">Domains</label>
|
||||
<Textarea
|
||||
id="domains"
|
||||
name="domains"
|
||||
defaultValue={host.domains.join("\n")}
|
||||
rows={2}
|
||||
@@ -230,7 +233,7 @@ export function EditHostDialog({
|
||||
<div>
|
||||
<label className="text-sm font-medium mb-1 block">Certificate</label>
|
||||
<Select name="certificate_id" defaultValue={String(host.certificate_id ?? "__none__")}>
|
||||
<SelectTrigger>
|
||||
<SelectTrigger aria-label="Certificate">
|
||||
<SelectValue placeholder="Managed by Caddy (Auto)" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
@@ -246,7 +249,7 @@ export function EditHostDialog({
|
||||
<div>
|
||||
<label className="text-sm font-medium mb-1 block">Access List</label>
|
||||
<Select name="access_list_id" defaultValue={String(host.access_list_id ?? "__none__")}>
|
||||
<SelectTrigger>
|
||||
<SelectTrigger aria-label="Access List">
|
||||
<SelectValue placeholder="None" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
|
||||
@@ -31,7 +31,7 @@ export function LoadBalancerFields({
|
||||
const showCookieFields = policy === "cookie";
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border border-blue-500/60 bg-blue-500/5 p-5">
|
||||
<div className="rounded-lg border border-cyan-500/60 bg-cyan-500/5 p-5">
|
||||
<input type="hidden" name="lb_present" value="1" />
|
||||
<input type="hidden" name="lb_enabled_present" value="1" />
|
||||
<div className="flex flex-col gap-4">
|
||||
|
||||
@@ -25,7 +25,7 @@ export function MtlsFields({ value, caCertificates }: Props) {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border border-blue-500/60 bg-blue-500/5 p-4">
|
||||
<div className="rounded-lg border border-amber-500/60 bg-amber-500/5 p-4">
|
||||
<input type="hidden" name="mtls_present" value="1" />
|
||||
<input type="hidden" name="mtls_enabled" value={enabled ? "true" : "false"} />
|
||||
{enabled && selectedIds.map(id => (
|
||||
@@ -35,7 +35,7 @@ export function MtlsFields({ value, caCertificates }: Props) {
|
||||
{/* Header */}
|
||||
<div className="flex flex-row items-start justify-between gap-2">
|
||||
<div className="flex flex-row items-start gap-3 flex-1 min-w-0">
|
||||
<div className="mt-0.5 w-8 h-8 rounded-xl bg-blue-500 flex items-center justify-center shrink-0">
|
||||
<div className="mt-0.5 w-8 h-8 rounded-xl bg-amber-500 flex items-center justify-center shrink-0">
|
||||
<LockKeyhole className="h-4 w-4 text-white" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
|
||||
@@ -6,8 +6,9 @@ type Props = { initialData?: RewriteConfig | null };
|
||||
export function RewriteFields({ initialData }: Props) {
|
||||
return (
|
||||
<div>
|
||||
<label className="text-sm font-medium mb-1 block">Path Prefix Rewrite</label>
|
||||
<label htmlFor="rewrite_path_prefix" className="text-sm font-medium mb-1 block">Path Prefix Rewrite</label>
|
||||
<Input
|
||||
id="rewrite_path_prefix"
|
||||
name="rewrite_path_prefix"
|
||||
placeholder="/recipes"
|
||||
defaultValue={initialData?.path_prefix ?? ""}
|
||||
|
||||
@@ -38,7 +38,7 @@ export function UpstreamDnsResolutionFields({
|
||||
: `Override: ${currentMode === "inherit" ? "inherit mode" : currentMode}, ${currentFamily === "inherit" ? "inherit family" : currentFamily}`;
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border border-blue-500/60 bg-blue-500/5 p-5">
|
||||
<div className="rounded-lg border border-violet-500/60 bg-violet-500/5 p-5">
|
||||
<input type="hidden" name="upstream_dns_resolution_present" value="1" />
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-row items-center justify-between">
|
||||
|
||||
Reference in New Issue
Block a user