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
+4
View File
@@ -195,6 +195,10 @@ const schema: BUN_SQLITE_DatabaseSchemaType = {
fieldName: "user_id", fieldName: "user_id",
dataType: "INTEGER", dataType: "INTEGER",
}, },
{
fieldName: "public_ip_address",
dataType: "TEXT",
},
{ {
fieldName: "wg_ip_address", fieldName: "wg_ip_address",
dataType: "TEXT", dataType: "TEXT",
+2 -1
View File
@@ -130,6 +130,7 @@ export type BUN_SQLITE_WGUI_HOSTS = {
*/ */
updated_at?: number | ""; updated_at?: number | "";
user_id?: number | ""; user_id?: number | "";
public_ip_address?: string;
wg_ip_address?: string; wg_ip_address?: string;
public_key?: string; public_key?: string;
} }
@@ -165,7 +166,7 @@ export type BUN_SQLITE_WGUI_VARIABLES = {
* The time when the record was updated. (Unix Timestamp) * The time when the record was updated. (Unix Timestamp)
*/ */
updated_at?: number | ""; updated_at?: number | "";
key?: "main_host_wg_ip_address" | "main_host_wg_public_key" | "main_host_wg_private_key" | ""; key?: "main_host_wg_ip_address" | "main_host_public_ip_address" | "main_host_wg_public_key" | "main_host_wg_private_key" | "";
value?: string; value?: string;
} }
-14
View File
@@ -15,8 +15,6 @@ type Props = {
}; };
export default function AdminHero({ title, description, buttons }: Props) { export default function AdminHero({ title, description, buttons }: Props) {
const { pageProps } = useContext(AppContext);
return ( return (
<Section> <Section>
<Stack className="w-full items-stretch"> <Stack className="w-full items-stretch">
@@ -30,18 +28,6 @@ export default function AdminHero({ title, description, buttons }: Props) {
description description
) )
) : null} ) : null}
<Breadcrumbs
dividerProps={{
className: "-skew-10",
}}
currentLinkProps={{
className:
"text-slate-400! pointer-events-none",
}}
pageUrl={pageProps?.url?.pathname}
backButton
skipHome
/>
</Stack> </Stack>
<Row>{buttons}</Row> <Row>{buttons}</Row>
</Row> </Row>
@@ -16,6 +16,7 @@ import {
import Span from "../twui/layout/Span"; import Span from "../twui/layout/Span";
import Tag from "../twui/elements/Tag"; import Tag from "../twui/elements/Tag";
import { AppData } from "@/src/data/app-data"; import { AppData } from "@/src/data/app-data";
import type { APIResponseObject } from "@moduletrace/bunext/types";
type Props = { type Props = {
button_props?: Omit<ComponentProps<typeof Button>, "title">; button_props?: Omit<ComponentProps<typeof Button>, "title">;
@@ -205,11 +206,18 @@ export default function SetupMainHostButton({ button_props }: Props) {
setLoading(true); setLoading(true);
setStatus(undefined); setStatus(undefined);
fetchApi<ApiReqParams>(`/api/admin/setup-main-host`, { fetchApi<ApiReqParams, APIResponseObject>(
`/api/admin/setup-main-host`,
{
method: "POST", method: "POST",
body: { ip_address: wgIP }, body: { ip_address: wgIP },
}) },
)
.then((res) => { .then((res) => {
if (res.success) {
window.location.reload();
}
setStatus({ setStatus({
success: res.success, success: res.success,
error: !res.success, error: !res.success,
+1
View File
@@ -19,4 +19,5 @@ export const AppData = {
WireguardHostID: 0, WireguardHostID: 0,
DefaultPrivateIP: `10.1.0.1`, DefaultPrivateIP: `10.1.0.1`,
DefaultWGListenPort: 51820,
} as const; } as const;
+5
View File
@@ -4,6 +4,11 @@ export const Variables = [
value: "main_host_wg_ip_address", value: "main_host_wg_ip_address",
description: `Private IP address to use for the main Wireguard host. Eg. 10.1.0.1`, description: `Private IP address to use for the main Wireguard host. Eg. 10.1.0.1`,
}, },
{
title: `Main Host Public IP Address`,
value: "main_host_public_ip_address",
description: `Private IP address to use for the main Public host. Eg. 10.1.0.1`,
},
{ {
title: `Main Host Wireguard Public Key`, title: `Main Host Wireguard Public Key`,
value: "main_host_wg_public_key", value: "main_host_wg_public_key",
@@ -0,0 +1,69 @@
import { AppData } from "@/src/data/app-data";
import deriveWireguardInterfaceName from "@/src/utils/derive-wireguard-interface-name";
import type { BUN_SQLITE_WGUI_HOSTS } from "@/db/types/db";
import path from "path";
import grabDirNames from "@/src/utils/grab-dir-names";
type Params = {
host?: BUN_SQLITE_WGUI_HOSTS;
};
const {
WGUI_LIB_HOSTS_CONFIGS_DIR,
WGUI_LIB_HOST_CLIENTS_DIR_NAME,
WGUI_LIB_HOST_IPTABLES_DIR_NAME,
WIREGUARD_PUBLIC_KEY_FILE_NAME,
WIREGUARD_PRIVATE_KEY_FILE_NAME,
} = grabDirNames();
export default function grabHostDirnames({ host }: Params) {
const HOST_ID = host?.id || AppData["WireguardHostID"];
const INTERFACE_NAME = deriveWireguardInterfaceName({ host_id: 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`,
);
const HOST_PUBLIC_KEY_FILE = path.join(
HOST_CONFIG_DIR,
WIREGUARD_PUBLIC_KEY_FILE_NAME,
);
const HOST_PRIVATE_KEY_FILE = path.join(
HOST_CONFIG_DIR,
WIREGUARD_PRIVATE_KEY_FILE_NAME,
);
const POST_UP_PATH = path.join(HOST_IPTABLES_DIR, `up.sh`);
const POST_DOWN_PATH = path.join(HOST_IPTABLES_DIR, `down.sh`);
return {
HOST_ID,
HOST_CONFIG_DIR,
HOST_CLIENTS_DIR,
HOST_IPTABLES_DIR,
HOST_CONFIG_FILE,
HOST_PUBLIC_KEY_FILE,
HOST_PRIVATE_KEY_FILE,
INTERFACE_NAME,
POST_UP_PATH,
POST_DOWN_PATH,
};
}
@@ -3,25 +3,18 @@ import type {
BUN_SQLITE_WGUI_HOSTS, BUN_SQLITE_WGUI_HOSTS,
BUN_SQLITE_WGUI_VARIABLES, BUN_SQLITE_WGUI_VARIABLES,
} from "@/db/types/db"; } from "@/db/types/db";
import { AppData } from "@/src/data/app-data";
import grabDirNames from "@/src/utils/grab-dir-names"; import grabDirNames from "@/src/utils/grab-dir-names";
import deriveWireguardInterfaceName from "@/src/utils/derive-wireguard-interface-name";
import { execSync } from "node:child_process"; import { execSync } from "node:child_process";
import path from "node:path";
import grabHostNetworkInterface from "./grab-host-network-interface"; import grabHostNetworkInterface from "./grab-host-network-interface";
import type { APIResponseObject } from "@moduletrace/bunext/types"; import type { APIResponseObject } from "@moduletrace/bunext/types";
import BunSQLite from "@moduletrace/bun-sqlite"; import BunSQLite from "@moduletrace/bun-sqlite";
import type { TableType, User } from "@/src/types"; import type { TableType, User } from "@/src/types";
import checkPrivateIPAvailability from "./check-private-ip-availability"; import checkPrivateIPAvailability from "./check-private-ip-availability";
import manageWireguardHost from "./manage-wireguard-host"; import manageWireguardHost from "./manage-wireguard-host";
import grabHostDirnames from "./grab-host-dir-names";
const { const { WIREGUARD_PRIVATE_KEY_FILE_NAME, WIREGUARD_PUBLIC_KEY_FILE_NAME } =
WGUI_LIB_HOSTS_CONFIGS_DIR, grabDirNames();
WIREGUARD_PRIVATE_KEY_FILE_NAME,
WIREGUARD_PUBLIC_KEY_FILE_NAME,
WGUI_LIB_HOST_CLIENTS_DIR_NAME,
WGUI_LIB_HOST_IPTABLES_DIR_NAME,
} = grabDirNames();
type Params = { type Params = {
host?: BUN_SQLITE_WGUI_HOSTS; host?: BUN_SQLITE_WGUI_HOSTS;
@@ -34,29 +27,18 @@ export default async function setupWireguardHost({
wg_subnet_ip, wg_subnet_ip,
user, user,
}: Params): Promise<APIResponseObject> { }: Params): Promise<APIResponseObject> {
const host_id = host?.id || AppData["WireguardHostID"]; const {
HOST_CLIENTS_DIR,
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(
WGUI_LIB_HOSTS_CONFIGS_DIR,
WGUI_LIB_HOST_CLIENTS_DIR_NAME,
);
const HOST_IPTABLES_DIR = path.join(
WGUI_LIB_HOSTS_CONFIGS_DIR,
WGUI_LIB_HOST_IPTABLES_DIR_NAME,
);
const HOST_CONFIG_FILE = path.join(
HOST_CONFIG_DIR, HOST_CONFIG_DIR,
`${INTERFACE_NAME}.conf`, HOST_CONFIG_FILE,
); HOST_ID,
HOST_IPTABLES_DIR,
HOST_PRIVATE_KEY_FILE,
HOST_PUBLIC_KEY_FILE,
INTERFACE_NAME,
POST_DOWN_PATH,
POST_UP_PATH,
} = grabHostDirnames({ host });
const variables_res = await BunSQLite.select< const variables_res = await BunSQLite.select<
BUN_SQLITE_WGUI_VARIABLES, BUN_SQLITE_WGUI_VARIABLES,
@@ -75,7 +57,7 @@ export default async function setupWireguardHost({
query: { query: {
query: { query: {
host_id: { host_id: {
value: host_id, value: HOST_ID,
}, },
}, },
}, },
@@ -111,6 +93,8 @@ export default async function setupWireguardHost({
pre_sh += `set -e\n`; pre_sh += `set -e\n`;
pre_sh += `mkdir -p ${HOST_CONFIG_DIR}\n`; pre_sh += `mkdir -p ${HOST_CONFIG_DIR}\n`;
pre_sh += `mkdir -p ${HOST_CLIENTS_DIR}\n`;
pre_sh += `mkdir -p ${HOST_IPTABLES_DIR}\n`;
pre_sh += `cd ${HOST_CONFIG_DIR}\n`; pre_sh += `cd ${HOST_CONFIG_DIR}\n`;
pre_sh += `if [ ! -f ${WIREGUARD_PRIVATE_KEY_FILE_NAME} ]; then\n`; pre_sh += `if [ ! -f ${WIREGUARD_PRIVATE_KEY_FILE_NAME} ]; then\n`;
pre_sh += ` wg genkey | tee ${WIREGUARD_PRIVATE_KEY_FILE_NAME} | wg pubkey > ${WIREGUARD_PUBLIC_KEY_FILE_NAME}\n`; pre_sh += ` wg genkey | tee ${WIREGUARD_PRIVATE_KEY_FILE_NAME} | wg pubkey > ${WIREGUARD_PUBLIC_KEY_FILE_NAME}\n`;
@@ -126,17 +110,15 @@ export default async function setupWireguardHost({
} }
try { try {
const HOST_PUBLIC_KEY = execSync( const HOST_PUBLIC_KEY = execSync(`cat ${HOST_PUBLIC_KEY_FILE}`, {
`cat ${path.join(HOST_CONFIG_DIR, WIREGUARD_PUBLIC_KEY_FILE_NAME)}`, encoding: "utf-8",
{ encoding: "utf-8" }, }).trim();
).trim();
const HOST_PRIVATE_KEY = execSync( const HOST_PRIVATE_KEY = execSync(`cat ${HOST_PRIVATE_KEY_FILE}`, {
`cat ${path.join(HOST_CONFIG_DIR, WIREGUARD_PRIVATE_KEY_FILE_NAME)}`, encoding: "utf-8",
{ encoding: "utf-8" }, }).trim();
).trim();
if (host_id == 0) { if (HOST_ID == 0) {
const update_variables = await BunSQLite.insert< const update_variables = await BunSQLite.insert<
BUN_SQLITE_WGUI_VARIABLES, BUN_SQLITE_WGUI_VARIABLES,
TableType TableType
@@ -155,12 +137,10 @@ export default async function setupWireguardHost({
update_on_duplicate: true, update_on_duplicate: true,
}); });
console.log("update_variables", update_variables);
if (!update_variables.success) { if (!update_variables.success) {
throw new Error(`Couldn't update host variables`); throw new Error(`Couldn't update host variables`);
} }
} else if (host_id) { } else if (HOST_ID) {
const update_host = await BunSQLite.insert< const update_host = await BunSQLite.insert<
BUN_SQLITE_WGUI_HOSTS, BUN_SQLITE_WGUI_HOSTS,
TableType TableType
@@ -168,7 +148,7 @@ export default async function setupWireguardHost({
table: "hosts", table: "hosts",
data: [ data: [
{ {
id: host_id, id: HOST_ID,
user_id: user.id, user_id: user.id,
wg_ip_address: HOST_WG_IP, wg_ip_address: HOST_WG_IP,
public_key: HOST_PUBLIC_KEY, public_key: HOST_PUBLIC_KEY,
@@ -182,9 +162,6 @@ export default async function setupWireguardHost({
sh += `set -e\n`; sh += `set -e\n`;
const POST_UP_PATH = path.join(HOST_IPTABLES_DIR, `up.sh`);
const POST_DOWN_PATH = path.join(HOST_IPTABLES_DIR, `down.sh`);
sh += `cd ${HOST_CONFIG_DIR}\n`; sh += `cd ${HOST_CONFIG_DIR}\n`;
sh += `cat > ${POST_UP_PATH} << EOF\n`; sh += `cat > ${POST_UP_PATH} << EOF\n`;
@@ -247,7 +224,7 @@ export default async function setupWireguardHost({
const exec = execSync(sh, { encoding: "utf-8" }); const exec = execSync(sh, { encoding: "utf-8" });
const manage_res = manageWireguardHost({ const manage_res = manageWireguardHost({
host_id, host_id: HOST_ID,
action: "restart", action: "restart",
}); });
@@ -9,9 +9,6 @@ export default function checkIfMainHostIsSet({ variables }: Params) {
Boolean( Boolean(
variables?.find((v) => v.key == "main_host_wg_ip_address")?.value, variables?.find((v) => v.key == "main_host_wg_ip_address")?.value,
) && ) &&
Boolean(
variables?.find((v) => v.key == "main_host_wg_private_key")?.value,
) &&
Boolean( Boolean(
variables?.find((v) => v.key == "main_host_wg_public_key")?.value, variables?.find((v) => v.key == "main_host_wg_public_key")?.value,
); );
@@ -1,29 +0,0 @@
import { AppContext } from "@/src/pages/__root";
import { ChevronRight } from "lucide-react";
import { useContext } from "react";
const SECTION_TITLES: Record<string, string> = {
"/admin": "Dashboard",
"/admin/clients": "Clients",
"/admin/host": "Host",
"/admin/settings": "Settings",
};
export default function HeaderPageTitle() {
const { pageProps } = useContext(AppContext);
const pathname = pageProps?.url?.pathname || "/admin";
const title = SECTION_TITLES[pathname] || "Admin";
return (
<span className="hidden xl:flex items-center gap-2 text-[14px] font-semibold text-foreground-light dark:text-foreground-dark">
<span className="font-medium text-foreground-light/50 dark:text-foreground-dark/50">
Admin
</span>
<ChevronRight
size={14}
className="text-foreground-light/30 dark:text-foreground-dark/30"
/>
<span>{title}</span>
</span>
);
}
+16 -5
View File
@@ -2,15 +2,16 @@ import Row from "@/src/components/twui/layout/Row";
import HeaderUser from "../../main/(partials)/header-user"; import HeaderUser from "../../main/(partials)/header-user";
import LogoIcon from "@/src/components/general/logo-icon"; import LogoIcon from "@/src/components/general/logo-icon";
import MobileNav from "../(partials)/MobileNav"; import MobileNav from "../(partials)/MobileNav";
import HeaderPageTitle from "../(partials)/header-page-title";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import Breadcrumbs from "@/src/components/twui/elements/Breadcrumbs";
import { useContext } from "react";
import { AppContext } from "@/src/pages/__root";
type Props = {}; type Props = {};
export default function Header({}: Props) { export default function Header({}: Props) {
/** const { pageProps } = useContext(AppContext);
* Render component
*/
return ( return (
<header className="h-20"> <header className="h-20">
<Row <Row
@@ -25,7 +26,17 @@ export default function Header({}: Props) {
img_props={{ className: "flex xl:hidden" }} img_props={{ className: "flex xl:hidden" }}
/> />
<MobileNav /> <MobileNav />
<HeaderPageTitle /> <Breadcrumbs
dividerProps={{
className: "-skew-10",
}}
currentLinkProps={{
className: "text-slate-400! pointer-events-none",
}}
pageUrl={pageProps?.url?.pathname}
backButton
skipHome
/>
</Row> </Row>
<Row className="flex-nowrap"> <Row className="flex-nowrap">
<HeaderUser /> <HeaderUser />
+4
View File
@@ -1,6 +1,7 @@
import type { BunextPageServerFn } from "@moduletrace/bunext/types"; import type { BunextPageServerFn } from "@moduletrace/bunext/types";
import type { PagePropsType } from "../types"; import type { PagePropsType } from "../types";
import userAuth from "../functions/backend/auth/user-auth"; import userAuth from "../functions/backend/auth/user-auth";
import grabHostDirnames from "../functions/backend/setup/grab-host-dir-names";
const server: BunextPageServerFn<PagePropsType> = async ({ req, url }) => { const server: BunextPageServerFn<PagePropsType> = async ({ req, url }) => {
const { user, user_types } = await userAuth({ req }); const { user, user_types } = await userAuth({ req });
@@ -27,6 +28,8 @@ const server: BunextPageServerFn<PagePropsType> = async ({ req, url }) => {
}; };
} }
const main_host_dir_names = grabHostDirnames({});
return { return {
props: { props: {
environment: process.env.NODE_ENV, environment: process.env.NODE_ENV,
@@ -36,6 +39,7 @@ const server: BunextPageServerFn<PagePropsType> = async ({ req, url }) => {
}, },
user, user,
user_types, user_types,
main_host_dir_names,
}, },
}; };
}; };
@@ -2,16 +2,12 @@ import { AppData } from "@/src/data/app-data";
import deriveWireguardInterfaceName from "@/src/utils/derive-wireguard-interface-name"; import deriveWireguardInterfaceName from "@/src/utils/derive-wireguard-interface-name";
import type { import type {
BUN_SQLITE_WGUI_CLIENTS, BUN_SQLITE_WGUI_CLIENTS,
BUN_SQLITE_WGUI_HOSTS,
BUN_SQLITE_WGUI_VARIABLES, BUN_SQLITE_WGUI_VARIABLES,
} from "@/db/types/db"; } from "@/db/types/db";
import type { BUN_SQLITE_WGUI_HOSTS_JOIN } from "@/src/types/sql-joins";
const LISTEN_PORT = 51820;
const IP_TABLES_DIR = `/var/lib/wgui/iptables`;
const HOSTS_CONFIG_DIR = `/var/lib/wgui/hosts`;
type Params = { type Params = {
host?: BUN_SQLITE_WGUI_HOSTS; host?: BUN_SQLITE_WGUI_HOSTS_JOIN;
variables?: BUN_SQLITE_WGUI_VARIABLES[]; variables?: BUN_SQLITE_WGUI_VARIABLES[];
clients?: BUN_SQLITE_WGUI_CLIENTS[]; clients?: BUN_SQLITE_WGUI_CLIENTS[];
}; };
@@ -23,18 +19,42 @@ export default function deriveHostConfig({ host, variables, clients }: Params) {
host?.wg_ip_address || host?.wg_ip_address ||
variables?.find((v) => v.key == "main_host_wg_ip_address")?.value; 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 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 { return {
host_id, host_id,
interface_name, 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, address: wg_ip_address ? `${wg_ip_address}/24` : undefined,
listen_port: LISTEN_PORT, listen_port: AppData["DefaultWGListenPort"],
post_up: `${IP_TABLES_DIR}/${host_id}-up.sh`, post_up: host?.dir_names?.POST_UP_PATH,
post_down: `${IP_TABLES_DIR}/${host_id}-down.sh`, post_down: host?.dir_names?.POST_DOWN_PATH,
public_key: host?.public_key, public_key: public_key,
client_count: client_count:
clients?.filter((client) => (client.host_id || 0) == host_id) clients?.filter((client) => (client.host_id || 0) == host_id)
.length || 0, .length || 0,
@@ -13,9 +13,14 @@ import type {
BUN_SQLITE_WGUI_HOSTS, BUN_SQLITE_WGUI_HOSTS,
BUN_SQLITE_WGUI_VARIABLES, BUN_SQLITE_WGUI_VARIABLES,
} from "@/db/types/db"; } 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 = { type Props = {
host?: BUN_SQLITE_WGUI_HOSTS; host?: BUN_SQLITE_WGUI_HOSTS_JOIN;
variables?: BUN_SQLITE_WGUI_VARIABLES[]; variables?: BUN_SQLITE_WGUI_VARIABLES[];
clients?: BUN_SQLITE_WGUI_CLIENTS[]; clients?: BUN_SQLITE_WGUI_CLIENTS[];
}; };
@@ -25,12 +30,23 @@ export default function InterfaceConfigSection({
variables, variables,
clients, clients,
}: Props) { }: 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 = [ const rows = [
{ keyName: "Address", value: config.address || "—" }, { keyName: "Address", value: config.address || "—" },
{ keyName: "ListenPort", value: String(config.listen_port) }, { 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: "PostUp", value: config.post_up },
{ keyName: "PostDown", value: config.post_down }, { keyName: "PostDown", value: config.post_down },
]; ];
@@ -51,11 +67,10 @@ export default function InterfaceConfigSection({
return ( return (
<AdminCard className="w-full overflow-hidden"> <AdminCard className="w-full overflow-hidden">
<Row className="justify-between gap-3 px-5 h-12 flex-wrap"> <Stack className="w-full items-stretch py-4">
<Row className="justify-between gap-3 px-5 flex-wrap">
<Stack className="gap-0.5"> <Stack className="gap-0.5">
<H2 className="text-[13.5px] font-medium text-foreground-light/60 dark:text-foreground-dark/60 mb-0!"> <H3>Interface configuration</H3>
Interface configuration
</H2>
<P <P
noMargin noMargin
className="font-mono text-[11.5px] text-foreground-light/40 dark:text-foreground-dark/40" className="font-mono text-[11.5px] text-foreground-light/40 dark:text-foreground-dark/40"
@@ -72,12 +87,12 @@ export default function InterfaceConfigSection({
Copy config Copy config
</AdminButton> </AdminButton>
</Row> </Row>
<div className="border-t border-slate-200 dark:border-white/10"> <Divider />
<Span className="px-5 py-3 font-mono text-[12.5px] font-semibold text-secondary dark:text-secondary block"> <Span className="px-5 py-3 font-mono text-[12.5px] font-semibold text-secondary dark:text-secondary block">
[Interface] [Interface]
</Span> </Span>
{rows.map((row) => { {rows.map((row) => {
if (!row) return; if (!row?.value) return;
return ( return (
<InterfaceConfigRow <InterfaceConfigRow
@@ -95,7 +110,7 @@ export default function InterfaceConfigSection({
: "Not generated"} : "Not generated"}
</Span> </Span>
</Row> </Row>
</div> </Stack>
</AdminCard> </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`,
};
+18 -1
View File
@@ -12,6 +12,8 @@ import EmptyContent from "@/src/components/twui/elements/EmptyContent";
import Span from "@/src/components/twui/layout/Span"; import Span from "@/src/components/twui/layout/Span";
import checkIfMainHostIsSet from "@/src/functions/check-if-main-host-is-set"; import checkIfMainHostIsSet from "@/src/functions/check-if-main-host-is-set";
import SetupMainHostButton from "@/src/components/general/setup-main-host-button"; 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() { export default function AdminHostPage() {
const { hosts, variables, clients } = useHostData(); const { hosts, variables, clients } = useHostData();
@@ -41,7 +43,9 @@ export default function AdminHostPage() {
description="WireGuard server configuration and interface details" description="WireGuard server configuration and interface details"
buttons={ 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" /> <Divider className="mb-6" />
<Stack className="w-full px-6 pb-8 gap-5 items-stretch"> <Stack className="w-full px-6 pb-8 gap-5 items-stretch">
<Row className="w-full justify-between">
<H2>Main Host</H2> <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 <MainHostStatusSection
variables={variables} variables={variables}
clients={clients} clients={clients}
+9
View File
@@ -1,5 +1,7 @@
import type { import type {
BUN_SQLITE_WGUI_ALL_TYPEDEFS, BUN_SQLITE_WGUI_ALL_TYPEDEFS,
BUN_SQLITE_WGUI_CLIENTS,
BUN_SQLITE_WGUI_HOSTS,
BUN_SQLITE_WGUI_MEDIA, BUN_SQLITE_WGUI_MEDIA,
BUN_SQLITE_WGUI_USER_TYPES, BUN_SQLITE_WGUI_USER_TYPES,
BUN_SQLITE_WGUI_USERS, BUN_SQLITE_WGUI_USERS,
@@ -12,6 +14,7 @@ import type { BunextPageModuleServerReturnURLObject } from "@moduletrace/bunext/
import type { BUN_SQLITE_WGUI_USERS_JOIN } from "./sql-joins"; import type { BUN_SQLITE_WGUI_USERS_JOIN } from "./sql-joins";
import type { ImageInputToBase64FunctionReturn } from "../components/twui/utils/form/imageInputToBase64"; import type { ImageInputToBase64FunctionReturn } from "../components/twui/utils/form/imageInputToBase64";
import type { ServerQueryParam } from "@moduletrace/bun-sqlite/dist/types"; import type { ServerQueryParam } from "@moduletrace/bun-sqlite/dist/types";
import type grabHostDirnames from "../functions/backend/setup/grab-host-dir-names";
export type User = { export type User = {
id: number; id: number;
@@ -33,12 +36,17 @@ export type PagePropsType = {
user_types?: BUN_SQLITE_WGUI_USER_TYPES[] | null; user_types?: BUN_SQLITE_WGUI_USER_TYPES[] | null;
person?: BUN_SQLITE_WGUI_USERS_JOIN | null; person?: BUN_SQLITE_WGUI_USERS_JOIN | null;
persons?: BUN_SQLITE_WGUI_USERS_JOIN[] | null; persons?: BUN_SQLITE_WGUI_USERS_JOIN[] | null;
host?: BUN_SQLITE_WGUI_HOSTS | null;
hosts?: BUN_SQLITE_WGUI_HOSTS[] | null;
client?: BUN_SQLITE_WGUI_CLIENTS | null;
clients?: BUN_SQLITE_WGUI_CLIENTS[] | null;
environment?: string; environment?: string;
envs?: { envs?: {
R2_PUBLIC_DOMAIN?: string; R2_PUBLIC_DOMAIN?: string;
PAYSTACK_PUBLIC_KEY?: string; PAYSTACK_PUBLIC_KEY?: string;
}; };
url?: BunextPageModuleServerReturnURLObject; url?: BunextPageModuleServerReturnURLObject;
main_host_dir_names?: ReturnType<typeof grabHostDirnames> | null;
}; };
export type AppContextObject = { export type AppContextObject = {
@@ -283,6 +291,7 @@ export type UserFormObject = BUN_SQLITE_WGUI_USERS & {
export type PageQueryObject = { export type PageQueryObject = {
user_id?: string; user_id?: string;
host_id?: string;
event_id?: string; event_id?: string;
user_type?: UserType; user_type?: UserType;
}; };
+6
View File
@@ -1,8 +1,10 @@
import type { import type {
BUN_SQLITE_WGUI_HOSTS,
BUN_SQLITE_WGUI_MEDIA, BUN_SQLITE_WGUI_MEDIA,
BUN_SQLITE_WGUI_MEDIA_PARADIGMS, BUN_SQLITE_WGUI_MEDIA_PARADIGMS,
BUN_SQLITE_WGUI_USERS, BUN_SQLITE_WGUI_USERS,
} from "@/db/types/db"; } from "@/db/types/db";
import type grabHostDirnames from "../functions/backend/setup/grab-host-dir-names";
export type BUN_SQLITE_WGUI_USERS_JOIN = BUN_SQLITE_WGUI_USERS & { export type BUN_SQLITE_WGUI_USERS_JOIN = BUN_SQLITE_WGUI_USERS & {
user_types?: string; user_types?: string;
@@ -20,3 +22,7 @@ export type BUN_SQLITE_WGUI_MEDIA_JOIN = BUN_SQLITE_WGUI_MEDIA & {
media_paradigms?: string; media_paradigms?: string;
user?: BUN_SQLITE_WGUI_USERS_JOIN; user?: BUN_SQLITE_WGUI_USERS_JOIN;
}; };
export type BUN_SQLITE_WGUI_HOSTS_JOIN = BUN_SQLITE_WGUI_HOSTS & {
dir_names?: ReturnType<typeof grabHostDirnames>;
};