Update hosts form
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import { AppData } from "@/src/data/app-data";
|
||||||
import {
|
import {
|
||||||
ClientRuleProtocols,
|
ClientRuleProtocols,
|
||||||
ClientRuleTypes,
|
ClientRuleTypes,
|
||||||
@@ -231,6 +232,11 @@ const schema: BUN_SQLITE_DatabaseSchemaType = {
|
|||||||
fieldName: "public_ip_address",
|
fieldName: "public_ip_address",
|
||||||
dataType: "TEXT",
|
dataType: "TEXT",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
fieldName: "listen_port",
|
||||||
|
dataType: "INTEGER",
|
||||||
|
defaultValue: AppData["DefaultWGListenPort"],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
fieldName: "interface",
|
fieldName: "interface",
|
||||||
dataType: "TEXT",
|
dataType: "TEXT",
|
||||||
|
|||||||
@@ -134,6 +134,7 @@ export type BUN_SQLITE_WGUI_HOSTS = {
|
|||||||
name?: string;
|
name?: string;
|
||||||
short_description?: string;
|
short_description?: string;
|
||||||
public_ip_address?: string;
|
public_ip_address?: string;
|
||||||
|
listen_port?: number | "";
|
||||||
interface?: string;
|
interface?: string;
|
||||||
wg_ip_address?: string;
|
wg_ip_address?: string;
|
||||||
public_key?: string;
|
public_key?: string;
|
||||||
|
|||||||
@@ -5,17 +5,12 @@ import fetchApi from "../twui/utils/fetch/fetchApi";
|
|||||||
import type { ApiReqParams } from "@/src/types";
|
import type { ApiReqParams } from "@/src/types";
|
||||||
import Row from "../twui/layout/Row";
|
import Row from "../twui/layout/Row";
|
||||||
import Stack from "../twui/layout/Stack";
|
import Stack from "../twui/layout/Stack";
|
||||||
import {
|
import { CircleCheck, TriangleAlert } from "lucide-react";
|
||||||
CircleCheck,
|
|
||||||
TriangleAlert,
|
|
||||||
} from "lucide-react";
|
|
||||||
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";
|
import type { APIResponseObject } from "@moduletrace/bunext/types";
|
||||||
import HostWgIpField, {
|
import HostWgIpField, { type HostWgIpFieldStatus } from "./host-wg-ip-field";
|
||||||
type HostWgIpFieldStatus,
|
|
||||||
} from "./host-wg-ip-field";
|
|
||||||
import GrabNextSubnetButton from "./grab-next-subnet-button";
|
import GrabNextSubnetButton from "./grab-next-subnet-button";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -30,12 +25,12 @@ export default function SetupMainHostButton({ button_props }: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack className="w-full items-stretch gap-3">
|
<Stack className="w-full items-stretch gap-3">
|
||||||
<HostWgIpField
|
{/* <HostWgIpField
|
||||||
value={wgIP}
|
value={wgIP}
|
||||||
onChange={setWgIP}
|
onChange={setWgIP}
|
||||||
onStatus={setIpStatus}
|
onStatus={setIpStatus}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/> */}
|
||||||
|
|
||||||
{status?.error && status.msg ? (
|
{status?.error && status.msg ? (
|
||||||
<Tag
|
<Tag
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
import type { BUN_SQLITE_WGUI_HOSTS, BUN_SQLITE_WGUI_VARIABLES } from "@/db/types/db";
|
import type {
|
||||||
|
BUN_SQLITE_WGUI_HOSTS,
|
||||||
|
BUN_SQLITE_WGUI_VARIABLES,
|
||||||
|
} from "@/db/types/db";
|
||||||
import BunSQLite from "@moduletrace/bun-sqlite";
|
import BunSQLite from "@moduletrace/bun-sqlite";
|
||||||
import type { APIResponseObject } from "@moduletrace/bunext/types";
|
import type { APIResponseObject } from "@moduletrace/bunext/types";
|
||||||
import type { TableType, User } from "@/src/types";
|
import type { TableType, User } from "@/src/types";
|
||||||
@@ -9,7 +12,11 @@ type Params = {
|
|||||||
wg_ip_address?: string | null;
|
wg_ip_address?: string | null;
|
||||||
public_ip_address?: string | null;
|
public_ip_address?: string | null;
|
||||||
interface?: string | null;
|
interface?: string | null;
|
||||||
|
name?: string | null;
|
||||||
|
short_description?: string | null;
|
||||||
|
listen_port?: number | null;
|
||||||
user: User;
|
user: User;
|
||||||
|
is_main_host?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,7 +31,11 @@ export default async function createWireguardHost({
|
|||||||
wg_ip_address,
|
wg_ip_address,
|
||||||
public_ip_address,
|
public_ip_address,
|
||||||
interface: iface,
|
interface: iface,
|
||||||
|
name,
|
||||||
|
short_description,
|
||||||
|
listen_port,
|
||||||
user,
|
user,
|
||||||
|
is_main_host,
|
||||||
}: Params): Promise<APIResponseObject> {
|
}: Params): Promise<APIResponseObject> {
|
||||||
const wg_ip = (wg_ip_address || "").trim();
|
const wg_ip = (wg_ip_address || "").trim();
|
||||||
|
|
||||||
@@ -38,24 +49,34 @@ export default async function createWireguardHost({
|
|||||||
const detected_public_ip = await grabHostPublicIPAddress();
|
const detected_public_ip = await grabHostPublicIPAddress();
|
||||||
const final_public_ip = public_ip_address?.trim() || detected_public_ip;
|
const final_public_ip = public_ip_address?.trim() || detected_public_ip;
|
||||||
const final_interface = iface?.trim() || undefined;
|
const final_interface = iface?.trim() || undefined;
|
||||||
|
const final_name = name?.trim() || undefined;
|
||||||
|
const final_short_description = short_description?.trim() || undefined;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const insert_data: BUN_SQLITE_WGUI_HOSTS = {
|
||||||
|
user_id: user.id,
|
||||||
|
wg_ip_address: wg_ip,
|
||||||
|
public_ip_address: final_public_ip || undefined,
|
||||||
|
interface: final_interface,
|
||||||
|
name: final_name,
|
||||||
|
short_description: final_short_description,
|
||||||
|
listen_port: listen_port != null ? listen_port : undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (is_main_host) {
|
||||||
|
insert_data.id = 0;
|
||||||
|
}
|
||||||
|
|
||||||
const insert_host = await BunSQLite.insert<
|
const insert_host = await BunSQLite.insert<
|
||||||
BUN_SQLITE_WGUI_HOSTS,
|
BUN_SQLITE_WGUI_HOSTS,
|
||||||
TableType
|
TableType
|
||||||
>({
|
>({
|
||||||
table: "hosts",
|
table: "hosts",
|
||||||
data: [
|
data: [insert_data],
|
||||||
{
|
update_on_duplicate: true,
|
||||||
user_id: user.id,
|
|
||||||
wg_ip_address: wg_ip,
|
|
||||||
public_ip_address: final_public_ip || undefined,
|
|
||||||
interface: final_interface,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!insert_host.success) {
|
if (!insert_host.success || !insert_host.postInsertReturn?.insertId) {
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
msg: `Could not create the host record: ${insert_host.msg}`,
|
msg: `Could not create the host record: ${insert_host.msg}`,
|
||||||
@@ -70,13 +91,13 @@ export default async function createWireguardHost({
|
|||||||
wg_ip_address: wg_ip,
|
wg_ip_address: wg_ip,
|
||||||
public_ip_address: final_public_ip || undefined,
|
public_ip_address: final_public_ip || undefined,
|
||||||
interface: final_interface,
|
interface: final_interface,
|
||||||
|
name: final_name,
|
||||||
|
short_description: final_short_description,
|
||||||
|
listen_port: listen_port != null ? listen_port : undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (host_id === 0 && final_interface) {
|
if (host_id === 0 && final_interface) {
|
||||||
await BunSQLite.insert<
|
await BunSQLite.insert<BUN_SQLITE_WGUI_VARIABLES, TableType>({
|
||||||
BUN_SQLITE_WGUI_VARIABLES,
|
|
||||||
TableType
|
|
||||||
>({
|
|
||||||
table: "variables",
|
table: "variables",
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { exec } from "node:child_process";
|
||||||
|
import { promisify } from "node:util";
|
||||||
|
import { AppData } from "@/src/data/app-data";
|
||||||
|
|
||||||
|
const execAsync = promisify(exec);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to get the next available listen port on the system.
|
||||||
|
* Checks both UDP and TCP listening ports.
|
||||||
|
*/
|
||||||
|
export default async function getNextAvailableListenPort(): Promise<number> {
|
||||||
|
const defaultPort = AppData["DefaultWGListenPort"] ?? 51820;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { stdout } = await execAsync("ss -tuln");
|
||||||
|
const usedPorts = new Set<number>();
|
||||||
|
|
||||||
|
for (const line of stdout.split("\n")) {
|
||||||
|
const match = line.match(/[:](\d+)\s/);
|
||||||
|
if (match && match[1]) {
|
||||||
|
const port = Number.parseInt(match[1], 10);
|
||||||
|
if (!Number.isNaN(port)) {
|
||||||
|
usedPorts.add(port);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let port = defaultPort;
|
||||||
|
while (usedPorts.has(port)) {
|
||||||
|
port++;
|
||||||
|
if (port > 65535) {
|
||||||
|
return defaultPort;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return port;
|
||||||
|
} catch (error) {
|
||||||
|
return defaultPort;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,13 +14,8 @@ import BunSQLite from "@moduletrace/bun-sqlite";
|
|||||||
import type { TableType, User } from "@/src/types";
|
import type { TableType, User } from "@/src/types";
|
||||||
import grabClientDirnames from "./grab-client-dir-names";
|
import grabClientDirnames from "./grab-client-dir-names";
|
||||||
|
|
||||||
const {
|
const { WIREGUARD_PUBLIC_KEY_FILE_NAME, WIREGUARD_CLIENT_CONFIG_FILE_NAME } =
|
||||||
WGUI_LIB_HOSTS_CONFIGS_DIR,
|
grabDirNames();
|
||||||
WIREGUARD_PRIVATE_KEY_FILE_NAME,
|
|
||||||
WIREGUARD_PUBLIC_KEY_FILE_NAME,
|
|
||||||
WGUI_LIB_HOST_CLIENTS_DIR_NAME,
|
|
||||||
WIREGUARD_CLIENT_CONFIG_FILE_NAME,
|
|
||||||
} = grabDirNames();
|
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
client: BUN_SQLITE_WGUI_CLIENTS;
|
client: BUN_SQLITE_WGUI_CLIENTS;
|
||||||
@@ -135,7 +130,7 @@ export default async function setupWireguardClient({
|
|||||||
sh += `\n`;
|
sh += `\n`;
|
||||||
sh += `[Peer]\n`;
|
sh += `[Peer]\n`;
|
||||||
sh += `PublicKey = ${HOST_PUBLIC_KEY}\n`;
|
sh += `PublicKey = ${HOST_PUBLIC_KEY}\n`;
|
||||||
sh += `Endpoint = ${PUBLIC_IP_ADDRESS}:51820\n`;
|
sh += `Endpoint = ${PUBLIC_IP_ADDRESS}:${host?.listen_port || AppData["DefaultWGListenPort"]}\n`;
|
||||||
|
|
||||||
if (client?.allow_all_ips == 1) {
|
if (client?.allow_all_ips == 1) {
|
||||||
sh += `AllowedIPs = 0.0.0.0/0, ::/0\n`;
|
sh += `AllowedIPs = 0.0.0.0/0, ::/0\n`;
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import manageWireguardHost from "./manage-wireguard-host";
|
|||||||
import grabHostDirnames from "./grab-host-dir-names";
|
import grabHostDirnames from "./grab-host-dir-names";
|
||||||
import grabClientDirnames from "./grab-client-dir-names";
|
import grabClientDirnames from "./grab-client-dir-names";
|
||||||
import buildHostIptablesScripts from "./build-host-iptables-scripts";
|
import buildHostIptablesScripts from "./build-host-iptables-scripts";
|
||||||
|
import { AppData } from "@/src/data/app-data";
|
||||||
|
|
||||||
const { WIREGUARD_PRIVATE_KEY_FILE_NAME, WIREGUARD_PUBLIC_KEY_FILE_NAME } =
|
const { WIREGUARD_PRIVATE_KEY_FILE_NAME, WIREGUARD_PUBLIC_KEY_FILE_NAME } =
|
||||||
grabDirNames();
|
grabDirNames();
|
||||||
@@ -100,7 +101,8 @@ export default async function setupWireguardHost({
|
|||||||
rules_by_client_id.set(rule.client_id, existing_rules);
|
rules_by_client_id.set(rule.client_id, existing_rules);
|
||||||
}
|
}
|
||||||
|
|
||||||
const TARGET_INTERFACE = host?.interface || await grabHostNetworkInterface();
|
const TARGET_INTERFACE =
|
||||||
|
host?.interface || (await grabHostNetworkInterface());
|
||||||
const HOST_WG_IP =
|
const HOST_WG_IP =
|
||||||
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 ||
|
||||||
@@ -246,7 +248,7 @@ export default async function setupWireguardHost({
|
|||||||
sh += `cat > ${INTERFACE_NAME}.conf << EOF\n`;
|
sh += `cat > ${INTERFACE_NAME}.conf << EOF\n`;
|
||||||
sh += `[Interface]\n`;
|
sh += `[Interface]\n`;
|
||||||
sh += `Address = ${HOST_WG_IP}/24\n`;
|
sh += `Address = ${HOST_WG_IP}/24\n`;
|
||||||
sh += `ListenPort = 51820\n`;
|
sh += `ListenPort = ${host?.listen_port || AppData["DefaultWGListenPort"]}\n`;
|
||||||
sh += `PrivateKey = ${HOST_PRIVATE_KEY}\n`;
|
sh += `PrivateKey = ${HOST_PRIVATE_KEY}\n`;
|
||||||
sh += `PostUp = ${POST_UP_PATH}\n`;
|
sh += `PostUp = ${POST_UP_PATH}\n`;
|
||||||
sh += `PostDown = ${POST_DOWN_PATH}\n`;
|
sh += `PostDown = ${POST_DOWN_PATH}\n`;
|
||||||
@@ -284,6 +286,13 @@ export default async function setupWireguardHost({
|
|||||||
|
|
||||||
const exec = execSync(sh, { encoding: "utf-8" });
|
const exec = execSync(sh, { encoding: "utf-8" });
|
||||||
|
|
||||||
|
if (!is_update_after_client_setup) {
|
||||||
|
execSync(
|
||||||
|
`ip link set ${INTERFACE_NAME} down 2>/dev/null; ip link del ${INTERFACE_NAME} 2>/dev/null; true`,
|
||||||
|
{ encoding: "utf-8" },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const manage_res = manageWireguardHost({
|
const manage_res = manageWireguardHost({
|
||||||
host_id: HOST_ID,
|
host_id: HOST_ID,
|
||||||
action: "restart",
|
action: "restart",
|
||||||
|
|||||||
@@ -86,13 +86,13 @@ export default function EditHostFormSection() {
|
|||||||
</P>
|
</P>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
<HostWgIpField
|
{/* <HostWgIpField
|
||||||
value={wgIP}
|
value={wgIP}
|
||||||
onChange={setWgIP}
|
onChange={setWgIP}
|
||||||
onStatus={setIpStatus}
|
onStatus={setIpStatus}
|
||||||
current_value={host?.wg_ip_address || ""}
|
current_value={host?.wg_ip_address || ""}
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/> */}
|
||||||
|
|
||||||
{status?.error && status.msg ? (
|
{status?.error && status.msg ? (
|
||||||
<Tag
|
<Tag
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ export default async function submitAddHostForm({
|
|||||||
wg_ip_address: form.wg_ip_address || "",
|
wg_ip_address: form.wg_ip_address || "",
|
||||||
public_ip_address: form.public_ip_address || "",
|
public_ip_address: form.public_ip_address || "",
|
||||||
interface: form.interface || "",
|
interface: form.interface || "",
|
||||||
|
name: form.name || "",
|
||||||
|
short_description: form.short_description || "",
|
||||||
|
listen_port: form.listen_port != null ? Number(form.listen_port) : null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import Stack from "@/src/components/twui/layout/Stack";
|
||||||
|
import useFormInit from "@/src/hooks/use-form-init";
|
||||||
|
import type { BUN_SQLITE_WGUI_HOSTS } from "@/db/types/db";
|
||||||
|
import Textarea from "@/src/components/twui/form/Textarea";
|
||||||
|
|
||||||
|
type Props = ReturnType<typeof useFormInit<BUN_SQLITE_WGUI_HOSTS>>;
|
||||||
|
|
||||||
|
export default function AddHostFormDescription({ form, setForm }: Props) {
|
||||||
|
return (
|
||||||
|
<Stack className="w-full gap-2">
|
||||||
|
<Textarea
|
||||||
|
name="short_description"
|
||||||
|
label="Short Description"
|
||||||
|
showLabel
|
||||||
|
placeholder="e.g. Main server at home"
|
||||||
|
defaultValue={form.short_description || ""}
|
||||||
|
changeHandler={(v) => {
|
||||||
|
setForm((prev) => ({
|
||||||
|
...prev,
|
||||||
|
short_description: v,
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { useState } from "react";
|
||||||
|
import Input from "@/src/components/twui/form/Input";
|
||||||
|
import Button from "@/src/components/twui/layout/Button";
|
||||||
|
import Stack from "@/src/components/twui/layout/Stack";
|
||||||
|
import Row from "@/src/components/twui/layout/Row";
|
||||||
|
import useFormInit from "@/src/hooks/use-form-init";
|
||||||
|
import { AppData } from "@/src/data/app-data";
|
||||||
|
import fetchApi from "@/src/components/twui/utils/fetch/fetchApi";
|
||||||
|
import type { ApiReqParams } from "@/src/types";
|
||||||
|
import type { APIResponseObject } from "@moduletrace/bunext/types";
|
||||||
|
import type { BUN_SQLITE_WGUI_HOSTS } from "@/db/types/db";
|
||||||
|
|
||||||
|
type Props = ReturnType<typeof useFormInit<BUN_SQLITE_WGUI_HOSTS>>;
|
||||||
|
|
||||||
|
export default function AddHostFormListenPort({ form, setForm }: Props) {
|
||||||
|
const [busy, setBusy] = useState(false);
|
||||||
|
|
||||||
|
async function handleGrabNext() {
|
||||||
|
setBusy(true);
|
||||||
|
try {
|
||||||
|
const res = await fetchApi<ApiReqParams, APIResponseObject>(
|
||||||
|
"/api/admin/next-listen-port",
|
||||||
|
{ method: "GET" },
|
||||||
|
);
|
||||||
|
if (res.success && res.numberRes) {
|
||||||
|
setForm((prev) => ({
|
||||||
|
...prev,
|
||||||
|
listen_port: Number(res.numberRes),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
} finally {
|
||||||
|
setBusy(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Stack className="w-full gap-2">
|
||||||
|
<Row className="w-full items-stretch gap-3 md:flex-nowrap">
|
||||||
|
<Input
|
||||||
|
name="listen_port"
|
||||||
|
label="Listen Port"
|
||||||
|
showLabel
|
||||||
|
placeholder={String(AppData["DefaultWGListenPort"])}
|
||||||
|
value={
|
||||||
|
form.listen_port != null ? String(form.listen_port) : ""
|
||||||
|
}
|
||||||
|
numberText
|
||||||
|
rawNumber
|
||||||
|
changeHandler={(v) => {
|
||||||
|
setForm((prev) => ({
|
||||||
|
...prev,
|
||||||
|
listen_port: Number(v) || undefined,
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
title="Grab Next Port"
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
disabled={busy}
|
||||||
|
loading={busy}
|
||||||
|
onClick={handleGrabNext}
|
||||||
|
>
|
||||||
|
{busy ? "Fetching…" : "Grab Next"}
|
||||||
|
</Button>
|
||||||
|
</Row>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import Input from "@/src/components/twui/form/Input";
|
||||||
|
import Stack from "@/src/components/twui/layout/Stack";
|
||||||
|
import useFormInit from "@/src/hooks/use-form-init";
|
||||||
|
import type { BUN_SQLITE_WGUI_HOSTS } from "@/db/types/db";
|
||||||
|
|
||||||
|
type Props = ReturnType<typeof useFormInit<BUN_SQLITE_WGUI_HOSTS>>;
|
||||||
|
|
||||||
|
export default function AddHostFormName({ form, setForm }: Props) {
|
||||||
|
return (
|
||||||
|
<Stack className="w-full gap-2">
|
||||||
|
<Input
|
||||||
|
name="name"
|
||||||
|
label="Host Name"
|
||||||
|
showLabel
|
||||||
|
placeholder="e.g. my-server"
|
||||||
|
defaultValue={form.name || ""}
|
||||||
|
changeHandler={(v) => {
|
||||||
|
setForm((prev) => ({
|
||||||
|
...prev,
|
||||||
|
name: v,
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -12,6 +12,9 @@ import AddHostFormSubnet from "./add-host-form-subnet";
|
|||||||
import AddHostFormWgIpAddress from "./add-host-form-wg-ip-address";
|
import AddHostFormWgIpAddress from "./add-host-form-wg-ip-address";
|
||||||
import AddHostFormPublicIP from "./add-host-form-public-ip";
|
import AddHostFormPublicIP from "./add-host-form-public-ip";
|
||||||
import AddHostFormInterface from "./add-host-form-interface";
|
import AddHostFormInterface from "./add-host-form-interface";
|
||||||
|
import AddHostFormName from "./add-host-form-name";
|
||||||
|
import AddHostFormDescription from "./add-host-form-description";
|
||||||
|
import AddHostFormListenPort from "./add-host-form-listen-port";
|
||||||
import LoadingRectangleBlock from "@/src/components/twui/layout/LoadingRectangleBlock";
|
import LoadingRectangleBlock from "@/src/components/twui/layout/LoadingRectangleBlock";
|
||||||
import type { HostWgIpFieldStatus } from "@/src/components/general/host-wg-ip-field";
|
import type { HostWgIpFieldStatus } from "@/src/components/general/host-wg-ip-field";
|
||||||
|
|
||||||
@@ -19,17 +22,21 @@ export default function AddHostForm() {
|
|||||||
const init = useFormInit<BUN_SQLITE_WGUI_HOSTS>({
|
const init = useFormInit<BUN_SQLITE_WGUI_HOSTS>({
|
||||||
default: {
|
default: {
|
||||||
wg_ip_address: AppData["DefaultPrivateIP"],
|
wg_ip_address: AppData["DefaultPrivateIP"],
|
||||||
|
name: "Main",
|
||||||
},
|
},
|
||||||
title: "Add Host",
|
title: "new_host",
|
||||||
async before_ready_function(params) {
|
async before_ready_function(params) {
|
||||||
if (!params.form.interface) {
|
|
||||||
params.setForm((prev) => ({
|
params.setForm((prev) => ({
|
||||||
...prev,
|
...prev,
|
||||||
interface:
|
interface:
|
||||||
|
params.form.interface ||
|
||||||
params.app_context.pageProps?.network_interfaces?.[0] ||
|
params.app_context.pageProps?.network_interfaces?.[0] ||
|
||||||
undefined,
|
undefined,
|
||||||
|
listen_port:
|
||||||
|
params.form.listen_port ||
|
||||||
|
params.app_context.pageProps?.next_listen_port ||
|
||||||
|
undefined,
|
||||||
}));
|
}));
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -49,7 +56,10 @@ export default function AddHostForm() {
|
|||||||
{init.ready ? (
|
{init.ready ? (
|
||||||
<Stack className="w-full gap-6 items-stretch">
|
<Stack className="w-full gap-6 items-stretch">
|
||||||
{/* <AddHostFormSubnet /> */}
|
{/* <AddHostFormSubnet /> */}
|
||||||
|
<AddHostFormName {...init} />
|
||||||
|
<AddHostFormDescription {...init} />
|
||||||
<AddHostFormInterface {...init} />
|
<AddHostFormInterface {...init} />
|
||||||
|
<AddHostFormListenPort {...init} />
|
||||||
<AddHostFormPublicIP {...init} />
|
<AddHostFormPublicIP {...init} />
|
||||||
<AddHostFormWgIpAddress
|
<AddHostFormWgIpAddress
|
||||||
{...init}
|
{...init}
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import AdminCard from "@/src/components/general/admin-card";
|
|
||||||
import AddHostForm from "../(partials)/add-host-form/add-host-form";
|
import AddHostForm from "../(partials)/add-host-form/add-host-form";
|
||||||
|
|
||||||
export default function AddHostFormSection() {
|
export default function AddHostFormSection() {
|
||||||
return (
|
return (
|
||||||
<AdminCard className="w-full p-5 flex flex-col gap-4">
|
<>
|
||||||
<AddHostForm />
|
<AddHostForm />
|
||||||
</AdminCard>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,13 +1,16 @@
|
|||||||
|
import getNextAvailableListenPort from "@/src/functions/backend/setup/get-next-available-listen-port";
|
||||||
import grabHostNetworkInterfaces from "@/src/functions/backend/setup/grab-host-network-interfaces";
|
import grabHostNetworkInterfaces from "@/src/functions/backend/setup/grab-host-network-interfaces";
|
||||||
import type { PagePropsType } from "@/src/types";
|
import type { PagePropsType } from "@/src/types";
|
||||||
import type { BunextPageServerFn } from "@moduletrace/bunext/types";
|
import type { BunextPageServerFn } from "@moduletrace/bunext/types";
|
||||||
|
|
||||||
const server: BunextPageServerFn<PagePropsType> = async ({ req }) => {
|
const server: BunextPageServerFn<PagePropsType> = async ({ req }) => {
|
||||||
const network_interfaces = await grabHostNetworkInterfaces();
|
const network_interfaces = await grabHostNetworkInterfaces();
|
||||||
|
const next_listen_port = await getNextAvailableListenPort();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
network_interfaces,
|
network_interfaces,
|
||||||
|
next_listen_port,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -51,6 +51,14 @@ export default function AdminHostPage() {
|
|||||||
description="WireGuard server configuration and interface details"
|
description="WireGuard server configuration and interface details"
|
||||||
buttons={
|
buttons={
|
||||||
<>
|
<>
|
||||||
|
<Button
|
||||||
|
title="View Hosts"
|
||||||
|
variant="outlined"
|
||||||
|
href="/admin/hosts"
|
||||||
|
color="gray"
|
||||||
|
>
|
||||||
|
View Hosts
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
title="Add New Host"
|
title="Add New Host"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
|
|||||||
@@ -43,6 +43,9 @@ export const handler: BunextAPIRouteHandler<APIResponseObject> = async (
|
|||||||
wg_ip_address: body?.wg_ip_address,
|
wg_ip_address: body?.wg_ip_address,
|
||||||
public_ip_address: body?.public_ip_address,
|
public_ip_address: body?.public_ip_address,
|
||||||
interface: body?.interface,
|
interface: body?.interface,
|
||||||
|
name: body?.name,
|
||||||
|
short_description: body?.short_description,
|
||||||
|
listen_port: body?.listen_port,
|
||||||
user,
|
user,
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import getNextAvailableListenPort from "@/src/functions/backend/setup/get-next-available-listen-port";
|
||||||
|
import type { ApiReqParams } from "@/src/types";
|
||||||
|
import type {
|
||||||
|
APIResponseObject,
|
||||||
|
BunextAPIRouteHandler,
|
||||||
|
} from "@moduletrace/bunext/types";
|
||||||
|
|
||||||
|
export const handler: BunextAPIRouteHandler<APIResponseObject> = async (
|
||||||
|
params,
|
||||||
|
) => {
|
||||||
|
if (params.req.method !== "GET") {
|
||||||
|
return { success: false };
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const port = await getNextAvailableListenPort();
|
||||||
|
return { success: true, numberRes: port };
|
||||||
|
} catch (error: any) {
|
||||||
|
return { success: false, msg: error.message };
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -66,11 +66,12 @@ bring_up() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bring_down() {
|
bring_down() {
|
||||||
if ! interface_is_up; then
|
# If forced or interface exists, run wg-quick down to clean up routes/IPs safely
|
||||||
|
if interface_is_up || [ "${FORCE_DOWN:-0}" -eq 1 ]; then
|
||||||
|
"$WG_QUICK_BIN" down "$CONFIG_PATH" 2>/dev/null || true
|
||||||
|
else
|
||||||
echo "interface $INTERFACE is not up — nothing to do."
|
echo "interface $INTERFACE is not up — nothing to do."
|
||||||
return 0
|
|
||||||
fi
|
fi
|
||||||
"$WG_QUICK_BIN" down "$CONFIG_PATH"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case "$ACTION" in
|
case "$ACTION" in
|
||||||
@@ -81,7 +82,9 @@ case "$ACTION" in
|
|||||||
bring_down
|
bring_down
|
||||||
;;
|
;;
|
||||||
restart)
|
restart)
|
||||||
bring_down
|
# Force wg-quick down to clean up stuck IPs/routes even if link is missing
|
||||||
|
FORCE_DOWN=1 bring_down
|
||||||
bring_up
|
bring_up
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export type PagePropsType = {
|
|||||||
url?: BunextPageModuleServerReturnURLObject;
|
url?: BunextPageModuleServerReturnURLObject;
|
||||||
main_host_dir_names?: ReturnType<typeof grabHostDirnames> | null;
|
main_host_dir_names?: ReturnType<typeof grabHostDirnames> | null;
|
||||||
network_interfaces?: string[] | null;
|
network_interfaces?: string[] | null;
|
||||||
|
next_listen_port?: number | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AppContextObject = {
|
export type AppContextObject = {
|
||||||
@@ -224,6 +225,9 @@ export type ApiReqParams<
|
|||||||
client_id?: string | number | null;
|
client_id?: string | number | null;
|
||||||
public_ip_address?: string | null;
|
public_ip_address?: string | null;
|
||||||
interface?: string | null;
|
interface?: string | null;
|
||||||
|
name?: string | null;
|
||||||
|
short_description?: string | null;
|
||||||
|
listen_port?: number | null;
|
||||||
// client_rules?: BUN_SQLITE_WGUI_CLIENT_RULES[];
|
// client_rules?: BUN_SQLITE_WGUI_CLIENT_RULES[];
|
||||||
|
|
||||||
media_base_64?: string;
|
media_base_64?: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user