Redesign the frontend UI on twui
Rebuild every page on a shared set of twui-based primitives with a dense ops-console look, WireGuard red accent and light/dark/system theming. - Add design tokens (WireGuard red accent, zinc neutrals, theme-flipping semantic colours) and a no-flash theme cookie + toggle - Add shared components: page header, panel, sub-nav, data table with mobile card list, stat tile, status dot, key-value list, copy button, inline alert, empty state, form section/actions, confirm dialog and toast providers - New admin shell (sidebar, sticky header, mobile drawer) and a centered shell for auth and 404; strip the public template pages - Unify adding clients at /admin/clients/add with a host picker; tabbed client detail (Overview / Rules / Config & QR) with config copy/download - Re-run client setup after rule changes on the detail page so the firewall matches the DB - Split Setup into status/maintenance/firewall and Settings into profile/password subpages - Replace window.confirm/alert and page reloads with in-app dialogs, toasts and in-place list updates - Fix nested rule form submitting the client form, prefilled signup passwords, hidden submit buttons, fields not updating after "next free" lookups, and TinyMCE white-on-white in dark mode Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -1,53 +1,18 @@
|
||||
import CodeBlock from "@/src/components/twui/elements/CodeBlock";
|
||||
import Tag from "@/src/components/twui/elements/Tag";
|
||||
import Row from "@/src/components/twui/layout/Row";
|
||||
import Span from "@/src/components/twui/layout/Span";
|
||||
import Stack from "@/src/components/twui/layout/Stack";
|
||||
import StatusDot, { type StatusTone } from "@/src/components/general/status-dot";
|
||||
import type {
|
||||
FirewallRuleKind,
|
||||
FirewallRuleInfo,
|
||||
} from "@/src/functions/backend/firewall/grab-firewall-status";
|
||||
import Row from "@/src/components/twui/layout/Row";
|
||||
import Span from "@/src/components/twui/layout/Span";
|
||||
import Stack from "@/src/components/twui/layout/Stack";
|
||||
import type { TWUIButtonColorType } from "@/src/components/twui/layout/Button";
|
||||
import {
|
||||
ShieldAlert,
|
||||
ShieldCheck,
|
||||
ShieldX,
|
||||
type LucideIcon,
|
||||
} from "lucide-react";
|
||||
|
||||
const KIND_META: Record<
|
||||
FirewallRuleKind,
|
||||
{
|
||||
label: string;
|
||||
color: TWUIButtonColorType;
|
||||
icon: LucideIcon;
|
||||
text_class: string;
|
||||
}
|
||||
> = {
|
||||
required: {
|
||||
label: `Active`,
|
||||
color: `success`,
|
||||
icon: ShieldCheck,
|
||||
text_class: `text-success`,
|
||||
},
|
||||
temporary: {
|
||||
label: `Temporary`,
|
||||
color: `warning`,
|
||||
icon: ShieldAlert,
|
||||
text_class: `text-warning`,
|
||||
},
|
||||
orphaned: {
|
||||
label: `Orphaned`,
|
||||
color: `error`,
|
||||
icon: ShieldX,
|
||||
text_class: `text-error`,
|
||||
},
|
||||
other: {
|
||||
label: `Unmanaged`,
|
||||
color: `gray`,
|
||||
icon: ShieldCheck,
|
||||
text_class: `text-foreground-light/45 dark:text-foreground-dark/45`,
|
||||
},
|
||||
const KIND_META: Record<FirewallRuleKind, { label: string; tone: StatusTone }> = {
|
||||
required: { label: "Active", tone: "success" },
|
||||
temporary: { label: "Temporary", tone: "warning" },
|
||||
orphaned: { label: "Orphaned", tone: "error" },
|
||||
other: { label: "Unmanaged", tone: "neutral" },
|
||||
};
|
||||
|
||||
type Props = {
|
||||
@@ -56,40 +21,23 @@ type Props = {
|
||||
|
||||
export default function FirewallRuleRow({ rule }: Props) {
|
||||
const meta = KIND_META[rule.kind];
|
||||
const Icon = meta.icon;
|
||||
|
||||
return (
|
||||
<Stack className="w-full items-stretch gap-2.5 rounded-lg bg-background-light dark:bg-[#0e0e10] border border-slate-200 dark:border-white/10 p-3">
|
||||
<Row className="w-full items-start justify-between gap-3 flex-wrap">
|
||||
<Row className="gap-2.5 items-start min-w-0">
|
||||
<Icon
|
||||
size={15}
|
||||
className={`mt-0.5 shrink-0 ${meta.text_class}`}
|
||||
/>
|
||||
<Span className="text-[12.5px] leading-5 min-w-0">
|
||||
{rule.description}
|
||||
</Span>
|
||||
</Row>
|
||||
<Tag
|
||||
variant="outlined"
|
||||
color={meta.color}
|
||||
className="shrink-0"
|
||||
>
|
||||
{meta.label}
|
||||
</Tag>
|
||||
<Stack className="w-full items-stretch gap-2.5 px-4 py-3 border-t border-line first:border-t-0">
|
||||
<Row className="w-full items-start justify-between gap-3 flex-nowrap">
|
||||
<Span className="text-[15px] leading-5 min-w-0">{rule.description}</Span>
|
||||
<StatusDot tone={meta.tone} label={meta.label} className="shrink-0 mt-0.5" />
|
||||
</Row>
|
||||
|
||||
{rule.delete_command ? (
|
||||
<CodeBlock
|
||||
singleBlock
|
||||
language="shell"
|
||||
wrapperProps={{
|
||||
className: `max-w-none! xl:max-w-none!`,
|
||||
}}
|
||||
wrapperProps={{ className: "max-w-full!" }}
|
||||
>
|
||||
{`# remove rule\n${rule.delete_command}`}
|
||||
</CodeBlock>
|
||||
) : null}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user