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:
2026-10-16 06:29:21 +01:00
co-authored by Claude Opus 5.5
parent e50b0405b0
commit cad1d32ae4
291 changed files with 6436 additions and 6612 deletions
@@ -0,0 +1,49 @@
import StatusDot from "@/src/components/general/status-dot";
import type { KeyValueItem } from "@/src/components/general/key-value-row";
import type { SystemSetupStatus } from "../(hooks)/use-system-setup-status";
type Params = {
setup_status: SystemSetupStatus;
};
export default function buildSetupOverview({ setup_status }: Params): KeyValueItem[] {
const service_value =
setup_status.service_active === null ? (
<StatusDot tone="neutral" label="Not managed" />
) : (
<StatusDot
tone={setup_status.service_active ? "success" : "error"}
label={setup_status.service_active ? "Active" : "Inactive"}
/>
);
return [
{
key: "env",
label: "Environment",
value: (
<StatusDot
tone={setup_status.is_dev ? "warning" : "success"}
label={setup_status.environment}
/>
),
},
{
key: "root",
label: "Running as root",
value: (
<StatusDot
tone={setup_status.is_root ? "success" : "error"}
label={setup_status.is_root ? "Yes" : "No"}
/>
),
},
{ key: "distro", label: "Distro", value: setup_status.distro_pretty_name || setup_status.distro },
{ key: "init", label: "Init system", value: setup_status.init_system, mono: true },
{ key: "service", label: "Service", value: setup_status.service_name, mono: true },
{ key: "service-status", label: "Service status", value: service_value },
{ key: "version", label: "App version", value: setup_status.app_version || "—", mono: true },
{ key: "dir", label: "Install directory", value: setup_status.install_dir, mono: true },
{ key: "repo", label: "Repository", value: setup_status.repo_url || "—", mono: true },
];
}
@@ -0,0 +1,32 @@
import type { SetupCheckItem } from "../(partials)/setup-check-status-item";
import type { SystemSetupStatus } from "../(hooks)/use-system-setup-status";
type Params = {
setup_status: SystemSetupStatus;
};
export default function buildSetupRuntimeChecks({ setup_status }: Params): SetupCheckItem[] {
return [
{
key: "kernel-module",
title: "WireGuard kernel module",
subtitle: "/sys/module/wireguard",
ok: setup_status.wg_module_loaded,
ok_label: "Loaded",
},
{
key: "wg-quick-helper",
title: "wg-quick manage helper",
subtitle: `/var/lib/wgui/scripts/wg-quick-manage.sh`,
ok: setup_status.wg_quick_helper_installed,
ok_label: "Installed",
},
...setup_status.lib_dirs.map((dir) => ({
key: dir.path,
title: `Runtime directory · ${dir.label}`,
subtitle: dir.path,
ok: dir.exists,
ok_label: "Exists",
})),
];
}
@@ -0,0 +1,24 @@
import checkUserAccess from "@/src/functions/backend/auth/check-user-access";
import userAuth from "@/src/functions/backend/auth/user-auth";
type Params = {
req: Request;
};
/**
* Setup pages are super-admin only. Returns a redirect when the request
* isn't allowed, `null` otherwise.
*/
export default async function guardSetupPage({ req }: Params) {
const { user, user_types } = await userAuth({ req });
if (!user?.logged_in_status || !user.id) {
return { destination: "/auth/login", permanent: false };
}
if (!checkUserAccess({ user_types }).success) {
return { destination: "/admin", permanent: false };
}
return null;
}