Initialize client forms

This commit is contained in:
2026-09-17 07:22:20 +01:00
parent e0d105beb9
commit 90ee3debf0
28 changed files with 584 additions and 145 deletions
@@ -2,16 +2,12 @@ import { AppData } from "@/src/data/app-data";
import deriveWireguardInterfaceName from "@/src/utils/derive-wireguard-interface-name";
import type {
BUN_SQLITE_WGUI_CLIENTS,
BUN_SQLITE_WGUI_HOSTS,
BUN_SQLITE_WGUI_VARIABLES,
} from "@/db/types/db";
const LISTEN_PORT = 51820;
const IP_TABLES_DIR = `/var/lib/wgui/iptables`;
const HOSTS_CONFIG_DIR = `/var/lib/wgui/hosts`;
import type { BUN_SQLITE_WGUI_HOSTS_JOIN } from "@/src/types/sql-joins";
type Params = {
host?: BUN_SQLITE_WGUI_HOSTS;
host?: BUN_SQLITE_WGUI_HOSTS_JOIN;
variables?: BUN_SQLITE_WGUI_VARIABLES[];
clients?: BUN_SQLITE_WGUI_CLIENTS[];
};
@@ -23,18 +19,42 @@ export default function deriveHostConfig({ host, variables, clients }: Params) {
host?.wg_ip_address ||
variables?.find((v) => v.key == "main_host_wg_ip_address")?.value;
const public_key =
host?.public_key ||
variables?.find((v) => v.key == "main_host_wg_public_key")?.value;
const interface_name = deriveWireguardInterfaceName({ host_id });
// const HOST_CONFIG_DIR = path.join(
// WGUI_LIB_HOSTS_CONFIGS_DIR,
// String(host_id),
// );
// const HOST_CLIENTS_DIR = path.join(
// HOST_CONFIG_DIR,
// WGUI_LIB_HOST_CLIENTS_DIR_NAME,
// );
// const HOST_IPTABLES_DIR = path.join(
// HOST_CONFIG_DIR,
// WGUI_LIB_HOST_IPTABLES_DIR_NAME,
// );
// const HOST_CONFIG_FILE = path.join(
// HOST_CONFIG_DIR,
// `${interface_name}.conf`,
// );
return {
host_id,
interface_name,
config_path: `${HOSTS_CONFIG_DIR}/${interface_name}.conf`,
config_path: host?.dir_names?.HOST_CONFIG_FILE,
address: wg_ip_address ? `${wg_ip_address}/24` : undefined,
listen_port: LISTEN_PORT,
post_up: `${IP_TABLES_DIR}/${host_id}-up.sh`,
post_down: `${IP_TABLES_DIR}/${host_id}-down.sh`,
public_key: host?.public_key,
listen_port: AppData["DefaultWGListenPort"],
post_up: host?.dir_names?.POST_UP_PATH,
post_down: host?.dir_names?.POST_DOWN_PATH,
public_key: public_key,
client_count:
clients?.filter((client) => (client.host_id || 0) == host_id)
.length || 0,
@@ -13,9 +13,14 @@ import type {
BUN_SQLITE_WGUI_HOSTS,
BUN_SQLITE_WGUI_VARIABLES,
} from "@/db/types/db";
import Divider from "@/src/components/twui/layout/Divider";
import H3 from "@/src/components/twui/layout/H3";
import type { BUN_SQLITE_WGUI_HOSTS_JOIN } from "@/src/types/sql-joins";
import { useContext } from "react";
import { AppContext } from "@/src/pages/__root";
type Props = {
host?: BUN_SQLITE_WGUI_HOSTS;
host?: BUN_SQLITE_WGUI_HOSTS_JOIN;
variables?: BUN_SQLITE_WGUI_VARIABLES[];
clients?: BUN_SQLITE_WGUI_CLIENTS[];
};
@@ -25,12 +30,23 @@ export default function InterfaceConfigSection({
variables,
clients,
}: Props) {
const config = deriveHostConfig({ host, variables, clients });
const { pageProps } = useContext(AppContext);
const final_host: BUN_SQLITE_WGUI_HOSTS_JOIN = host
? host
: {
dir_names: pageProps?.main_host_dir_names || undefined,
};
const config = deriveHostConfig({ host: final_host, variables, clients });
const rows = [
{ keyName: "Address", value: config.address || "—" },
{ keyName: "ListenPort", value: String(config.listen_port) },
,
{
keyName: "PublicKey",
value: config.public_key ? config.public_key : "Not set",
},
{ keyName: "PostUp", value: config.post_up },
{ keyName: "PostDown", value: config.post_down },
];
@@ -51,33 +67,32 @@ export default function InterfaceConfigSection({
return (
<AdminCard className="w-full overflow-hidden">
<Row className="justify-between gap-3 px-5 h-12 flex-wrap">
<Stack className="gap-0.5">
<H2 className="text-[13.5px] font-medium text-foreground-light/60 dark:text-foreground-dark/60 mb-0!">
Interface configuration
</H2>
<P
noMargin
className="font-mono text-[11.5px] text-foreground-light/40 dark:text-foreground-dark/40"
<Stack className="w-full items-stretch py-4">
<Row className="justify-between gap-3 px-5 flex-wrap">
<Stack className="gap-0.5">
<H3>Interface configuration</H3>
<P
noMargin
className="font-mono text-[11.5px] text-foreground-light/40 dark:text-foreground-dark/40"
>
{config.config_path}
</P>
</Stack>
<AdminButton
Icon={Copy}
onClick={() =>
navigator.clipboard?.writeText(buildConfigText())
}
>
{config.config_path}
</P>
</Stack>
<AdminButton
Icon={Copy}
onClick={() =>
navigator.clipboard?.writeText(buildConfigText())
}
>
Copy config
</AdminButton>
</Row>
<div className="border-t border-slate-200 dark:border-white/10">
Copy config
</AdminButton>
</Row>
<Divider />
<Span className="px-5 py-3 font-mono text-[12.5px] font-semibold text-secondary dark:text-secondary block">
[Interface]
</Span>
{rows.map((row) => {
if (!row) return;
if (!row?.value) return;
return (
<InterfaceConfigRow
@@ -95,7 +110,7 @@ export default function InterfaceConfigSection({
: "Not generated"}
</Span>
</Row>
</div>
</Stack>
</AdminCard>
);
}
@@ -0,0 +1,48 @@
import Loading from "@/src/components/twui/elements/Loading";
import Paper from "@/src/components/twui/elements/Paper";
import Center from "@/src/components/twui/layout/Center";
import Span from "@/src/components/twui/layout/Span";
import Stack from "@/src/components/twui/layout/Stack";
import { useAdminCrudGet } from "@/src/hooks/use-admin-crud-get";
import { AppContext } from "@/src/pages/__root";
import { useContext } from "react";
type Props = {
host_id: string | number;
};
export default function ClientsList({ host_id }: Props) {
const { pageProps } = useContext(AppContext);
const { res: clients } = useAdminCrudGet({
table: "clients",
sql_query: {
query: {
host_id: { value: host_id },
},
},
initial_res: pageProps?.clients || undefined,
});
if (!clients) {
return (
<Paper>
<Center>
<Loading />
</Center>
</Paper>
);
}
if (!clients[0]) {
return (
<Paper>
<Center>
<Span variant="faded">No Clients added yet</Span>
</Center>
</Paper>
);
}
return <Stack>{}</Stack>;
}
@@ -0,0 +1,18 @@
import H2 from "@/src/components/twui/layout/H2";
import Section from "@/src/components/twui/layout/Section";
import { AppContext } from "@/src/pages/__root";
import { useContext } from "react";
import ClientsList from "../(partials)/clients-list";
export default function ClientsListSection() {
const { pageProps } = useContext(AppContext);
const clients = pageProps?.clients || [];
return (
<Section>
<H2>{clients.length} Clients</H2>
<ClientsList host_id={pageProps?.host?.id || 0} />
</Section>
);
}
@@ -0,0 +1,16 @@
import type { BUN_SQLITE_WGUI_CLIENTS } from "@/db/types/db";
import Input from "@/src/components/twui/form/Input";
import useFormInit from "@/src/hooks/use-form-init";
export default function AddClientFormTitle({
form,
setForm,
}: ReturnType<typeof useFormInit<BUN_SQLITE_WGUI_CLIENTS>>) {
return (
<Input
title="Client Display Name"
placeholder="Eg. Pixel 2XL Phone"
showLabel
/>
);
}
@@ -0,0 +1,31 @@
import type { BUN_SQLITE_WGUI_CLIENTS } from "@/db/types/db";
import Form from "@/src/components/twui/form/Form";
import Stack from "@/src/components/twui/layout/Stack";
import useFormInit from "@/src/hooks/use-form-init";
import AddClientFormTitle from "./add-client-form-title";
type Props = {
existing_client?: BUN_SQLITE_WGUI_CLIENTS;
existing_client_full?: BUN_SQLITE_WGUI_CLIENTS;
host_id?: string | number;
};
export default function AddClientForm({
existing_client,
existing_client_full,
host_id,
}: Props) {
const init = useFormInit<BUN_SQLITE_WGUI_CLIENTS>({
existing: existing_client,
existing_full: existing_client_full,
title: "Add Client",
});
return (
<Form>
<Stack>
<AddClientFormTitle {...init} />
</Stack>
</Form>
);
}
@@ -0,0 +1,16 @@
import Section from "@/src/components/twui/layout/Section";
import AddClientForm from "../(partials)/client-form/add-client-form";
import { useContext } from "react";
import { AppContext } from "@/src/pages/__root";
export default function AddClientFormSection() {
const { pageProps, query } = useContext(AppContext);
const host_id = query?.host_id || 0;
return (
<Section>
<AddClientForm host_id={host_id} />
</Section>
);
}
@@ -0,0 +1,21 @@
import type { BUN_SQLITE_WGUI_HOSTS } from "@/db/types/db";
import type { PagePropsType, PageQueryObject, TableType } from "@/src/types";
import BunSQLite from "@moduletrace/bun-sqlite";
import type { BunextPageServerFn } from "@moduletrace/bunext/types";
const server: BunextPageServerFn<PagePropsType> = async ({ query }) => {
const page_query = query as PageQueryObject;
const host_record_res = await BunSQLite.select<
BUN_SQLITE_WGUI_HOSTS,
TableType
>({ table: "hosts", targetId: page_query.host_id });
return {
props: {
host: host_record_res.singleRes || null,
},
};
};
export default server;
@@ -0,0 +1,29 @@
import type { BunextPageModuleMeta } from "@moduletrace/bunext/types";
import AdminHero from "@/src/components/general/admin-hero";
import { SiteData } from "@/src/data/site-data";
import Divider from "@/src/components/twui/layout/Divider";
import { useContext } from "react";
import { AppContext } from "@/src/pages/__root";
import AddClientFormSection from "./(sections)/add-client-form-section";
export default function AdminSingleHostPage() {
const { pageProps } = useContext(AppContext);
return (
<>
<AdminHero
title="Add New Client"
description={`Add a new Client to host ${pageProps?.host?.id || 0}`}
/>
<Divider className="mb-6" />
<AddClientFormSection />
</>
);
}
export const meta: BunextPageModuleMeta = {
title: `Add new Client | ${SiteData["SiteName"]}`,
description: `WireGuard add new host client page`,
};
@@ -0,0 +1,43 @@
import type {
BUN_SQLITE_WGUI_CLIENTS,
BUN_SQLITE_WGUI_HOSTS,
} from "@/db/types/db";
import type { PagePropsType, PageQueryObject, TableType } from "@/src/types";
import BunSQLite from "@moduletrace/bun-sqlite";
import type { BunextPageServerFn } from "@moduletrace/bunext/types";
const server: BunextPageServerFn<PagePropsType> = async ({
req,
url,
query,
}) => {
const page_query = query as PageQueryObject;
const host_record_res = await BunSQLite.select<
BUN_SQLITE_WGUI_HOSTS,
TableType
>({ table: "hosts", targetId: page_query.host_id });
const host_clients = await BunSQLite.select<
BUN_SQLITE_WGUI_CLIENTS,
TableType
>({
table: "clients",
query: {
query: {
host_id: {
value: page_query.host_id,
},
},
},
});
return {
props: {
host: host_record_res.singleRes || null,
clients: host_clients.payload || null,
},
};
};
export default server;
@@ -0,0 +1,43 @@
import type { BunextPageModuleMeta } from "@moduletrace/bunext/types";
import AdminHero from "@/src/components/general/admin-hero";
import { SiteData } from "@/src/data/site-data";
import Button from "@/src/components/twui/layout/Button";
import Divider from "@/src/components/twui/layout/Divider";
import { useContext } from "react";
import { AppContext } from "@/src/pages/__root";
import ClientsListSection from "./(sections)/clients-list-section";
export default function AdminSingleHostPage() {
const { pageProps } = useContext(AppContext);
const host_id = pageProps?.host?.id || 0;
return (
<>
<AdminHero
title="Clients"
description={`Clients for host ${pageProps?.host?.id || 0}`}
buttons={
<>
<Button
title="Add New Client"
variant="outlined"
href={`/admin/hosts/${host_id}/clients/add-client`}
>
Add New Client
</Button>
</>
}
/>
<Divider className="mb-6" />
<ClientsListSection />
</>
);
}
export const meta: BunextPageModuleMeta = {
title: `Host Clients | ${SiteData["SiteName"]}`,
description: `WireGuard host clients page`,
};
@@ -0,0 +1,43 @@
import type {
BUN_SQLITE_WGUI_CLIENTS,
BUN_SQLITE_WGUI_HOSTS,
} from "@/db/types/db";
import type { PagePropsType, PageQueryObject, TableType } from "@/src/types";
import BunSQLite from "@moduletrace/bun-sqlite";
import type { BunextPageServerFn } from "@moduletrace/bunext/types";
const server: BunextPageServerFn<PagePropsType> = async ({
req,
url,
query,
}) => {
const page_query = query as PageQueryObject;
const host_record_res = await BunSQLite.select<
BUN_SQLITE_WGUI_HOSTS,
TableType
>({ table: "hosts", targetId: page_query.host_id });
const host_clients = await BunSQLite.select<
BUN_SQLITE_WGUI_CLIENTS,
TableType
>({
table: "clients",
query: {
query: {
host_id: {
value: page_query.host_id,
},
},
},
});
return {
props: {
host: host_record_res.singleRes || null,
clients: host_clients.payload || null,
},
};
};
export default server;
+30
View File
@@ -0,0 +1,30 @@
import type { BunextPageModuleMeta } from "@moduletrace/bunext/types";
import AdminHero from "@/src/components/general/admin-hero";
import { SiteData } from "@/src/data/site-data";
import Button from "@/src/components/twui/layout/Button";
import Divider from "@/src/components/twui/layout/Divider";
export default function AdminSingleHostPage() {
return (
<>
<AdminHero
title="Host"
description="WireGuard server configuration and interface details"
buttons={
<>
<Button title="Add New Host" variant="outlined">
Add New Host
</Button>
</>
}
/>
<Divider className="mb-6" />
</>
);
}
export const meta: BunextPageModuleMeta = {
title: `Host | ${SiteData["SiteName"]}`,
description: `WireGuard host page`,
};
+19 -2
View File
@@ -12,6 +12,8 @@ import EmptyContent from "@/src/components/twui/elements/EmptyContent";
import Span from "@/src/components/twui/layout/Span";
import checkIfMainHostIsSet from "@/src/functions/check-if-main-host-is-set";
import SetupMainHostButton from "@/src/components/general/setup-main-host-button";
import Row from "@/src/components/twui/layout/Row";
import { Plus } from "lucide-react";
export default function AdminHostPage() {
const { hosts, variables, clients } = useHostData();
@@ -41,7 +43,9 @@ export default function AdminHostPage() {
description="WireGuard server configuration and interface details"
buttons={
<>
<Button title="Add New Host">Add New Host</Button>
<Button title="Add New Host" variant="outlined">
Add New Host
</Button>
</>
}
/>
@@ -49,7 +53,20 @@ export default function AdminHostPage() {
<Divider className="mb-6" />
<Stack className="w-full px-6 pb-8 gap-5 items-stretch">
<H2>Main Host</H2>
<Row className="w-full justify-between">
<H2>Main Host</H2>
<Row>
<Button
title="Add Client"
beforeIcon={<Plus />}
size="small"
href={`/admin/hosts/0/clients/add`}
>
Add Client
</Button>
</Row>
</Row>
<MainHostStatusSection
variables={variables}
clients={clients}