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
@@ -1,12 +1,11 @@
import { RotateCcw, Wrench } from "lucide-react";
import fetchApi from "@/src/components/twui/utils/fetch/fetchApi";
import Row from "@/src/components/twui/layout/Row";
import Span from "@/src/components/twui/layout/Span";
import type useSystemSetupStatus from "../(hooks)/use-system-setup-status";
import SetupActionCard from "../(partials)/setup-action-card";
import SetupUpdateWgUiAction from "../(partials)/setup-update-wg-ui-action";
import Panel from "@/src/components/general/panel";
import type { ApiReqParams } from "@/src/types";
import type { APIResponseObject } from "@moduletrace/bunext/types";
import { RotateCcw, TriangleAlert, Wrench } from "lucide-react";
import type useSystemSetupStatus from "../(hooks)/use-system-setup-status";
import SetupActionRow from "../(partials)/setup-action-row";
import SetupUpdateWgUiAction from "../(partials)/setup-update-wg-ui-action";
type Props = {
setup: ReturnType<typeof useSystemSetupStatus>;
@@ -25,41 +24,21 @@ export default function SetupActionsSection({ setup }: Props) {
.filter((tool) => WG_TOOL_COMMANDS.includes(tool.command))
.every((tool) => tool.installed);
const show_restart =
!setup_status.is_dev && setup_status.init_system !== "unknown";
const show_restart = !setup_status.is_dev && setup_status.init_system !== "unknown";
return (
<>
{!setup_status.is_root ? (
<Row className="gap-2 items-center bg-warning/5 rounded-lg px-3 py-2.5 text-warning">
<TriangleAlert size={16} className="shrink-0" />
<Span className="text-[12.5px]">
The server is not running as root — installs and
tunnel management may fail. Run wg-ui as root for full
functionality.
</Span>
</Row>
) : null}
<SetupActionCard
<Panel flush>
<SetupActionRow
title="WireGuard tools"
description="Installs or updates the WireGuard tools (wg, wg-quick, iproute2) using your distro's package manager. Safe to re-run — the installer is idempotent."
icon={<Wrench size={19} />}
button_title={
have_wg_tools
? "Update WireGuard tools"
: "Install WireGuard tools"
}
description="Installs or updates wg, wg-quick and iproute2 with your distro's package manager. Safe to re-run."
icon={<Wrench size={15} />}
button_title={have_wg_tools ? "Update tools" : "Install tools"}
on_run={async () => {
const res = await fetchApi<ApiReqParams, APIResponseObject>(
`/api/admin/setup-wireguard-tools`,
{
method: "POST",
},
{ method: "POST" },
);
setup.refresh();
return res;
}}
/>
@@ -67,25 +46,27 @@ export default function SetupActionsSection({ setup }: Props) {
<SetupUpdateWgUiAction setup={setup} />
{show_restart ? (
<SetupActionCard
<SetupActionRow
title="Restart service"
description={`Restarts the ${setup_status.service_name} system service. Use this after manual changes — the server goes briefly offline.`}
icon={<RotateCcw size={19} />}
button_title="Restart service"
description={`Restarts the ${setup_status.service_name} system service. Use after manual changes.`}
icon={<RotateCcw size={15} />}
button_title="Restart"
confirm_params={{
title: "Restart wg-ui?",
message: `The ${setup_status.service_name} service restarts and this dashboard goes briefly offline.`,
confirm_label: "Restart service",
tone: "danger",
}}
on_run={async () => {
const res = await fetchApi<
ApiReqParams,
APIResponseObject
>(`/api/admin/restart-wg-ui-service`, {
method: "POST",
});
const res = await fetchApi<ApiReqParams, APIResponseObject>(
`/api/admin/restart-wg-ui-service`,
{ method: "POST" },
);
setup.refresh();
return res;
}}
/>
) : null}
</>
</Panel>
);
}
}