mirror of
https://github.com/fosrl/pangolin.git
synced 2025-08-29 14:18:26 +02:00
Allow picking ips when creating stuff
This commit is contained in:
parent
d664aa204f
commit
581fdd67b1
9 changed files with 212 additions and 156 deletions
|
@ -44,8 +44,8 @@ export const sites = sqliteTable("sites", {
|
||||||
type: text("type").notNull(), // "newt" or "wireguard"
|
type: text("type").notNull(), // "newt" or "wireguard"
|
||||||
online: integer("online", { mode: "boolean" }).notNull().default(false),
|
online: integer("online", { mode: "boolean" }).notNull().default(false),
|
||||||
|
|
||||||
// exit node stuff that is how to connect to the site when it has a gerbil
|
// exit node stuff that is how to connect to the site when it has a wg server
|
||||||
address: text("address"), // this is the address of the wireguard interface in gerbil
|
address: text("address"), // this is the address of the wireguard interface in newt
|
||||||
endpoint: text("endpoint"), // this is how to reach gerbil externally - gets put into the wireguard config
|
endpoint: text("endpoint"), // this is how to reach gerbil externally - gets put into the wireguard config
|
||||||
publicKey: text("pubicKey"),
|
publicKey: text("pubicKey"),
|
||||||
lastHolePunch: integer("lastHolePunch"),
|
lastHolePunch: integer("lastHolePunch"),
|
||||||
|
|
|
@ -80,7 +80,7 @@ export async function createClient(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subnet && !isValidIP(subnet)) {
|
if (!isValidIP(subnet)) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
|
@ -103,7 +103,7 @@ export async function createClient(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subnet && !isIpInCidr(subnet, org.subnet)) {
|
if (!isIpInCidr(subnet, org.subnet)) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
|
@ -114,6 +114,22 @@ export async function createClient(
|
||||||
|
|
||||||
const updatedSubnet = `${subnet}/${org.subnet.split("/")[1]}`; // we want the block size of the whole org
|
const updatedSubnet = `${subnet}/${org.subnet.split("/")[1]}`; // we want the block size of the whole org
|
||||||
|
|
||||||
|
// make sure the subnet is unique
|
||||||
|
const subnetExists = await db
|
||||||
|
.select()
|
||||||
|
.from(clients)
|
||||||
|
.where(eq(clients.subnet, updatedSubnet))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (subnetExists.length > 0) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.CONFLICT,
|
||||||
|
`Subnet ${subnet} already exists`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await db.transaction(async (trx) => {
|
await db.transaction(async (trx) => {
|
||||||
// TODO: more intelligent way to pick the exit node
|
// TODO: more intelligent way to pick the exit node
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { generateId } from "@server/auth/sessions/app";
|
import { generateId } from "@server/auth/sessions/app";
|
||||||
import { getNextAvailableClientSubnet } from "@server/lib/ip";
|
import { getNextAvailableClientSubnet } from "@server/lib/ip";
|
||||||
import config from "@server/lib/config";
|
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
|
@ -43,6 +42,14 @@ export async function pickClientDefaults(
|
||||||
const secret = generateId(48);
|
const secret = generateId(48);
|
||||||
|
|
||||||
const newSubnet = await getNextAvailableClientSubnet(orgId);
|
const newSubnet = await getNextAvailableClientSubnet(orgId);
|
||||||
|
if (!newSubnet) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
"No available subnet found"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const subnet = newSubnet.split("/")[0];
|
const subnet = newSubnet.split("/")[0];
|
||||||
|
|
||||||
|
|
|
@ -64,45 +64,15 @@ export const handleGetConfigMessage: MessageHandler = async (context) => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let site: Site | undefined;
|
// update the endpoint and the public key
|
||||||
if (!existingSite.address) {
|
const [site] = await db
|
||||||
// This is a new site configuration
|
.update(sites)
|
||||||
let address = await getNextAvailableClientSubnet(existingSite.orgId);
|
.set({
|
||||||
if (!address) {
|
publicKey,
|
||||||
logger.error("handleGetConfigMessage: No available address");
|
listenPort: port
|
||||||
return;
|
})
|
||||||
}
|
.where(eq(sites.siteId, siteId))
|
||||||
|
.returning();
|
||||||
// TODO: WE NEED TO PULL THE CIDR FROM THE DB SUBNET ON THE ORG INSTEAD BECAUSE IT CAN BE DIFFERENT
|
|
||||||
// TODO: SOMEHOW WE NEED TO ALLOW THEM TO PUT IN THEIR OWN ADDRESS
|
|
||||||
address = `${address.split("/")[0]}/${config.getRawConfig().orgs.block_size}`; // we want the block size of the whole org
|
|
||||||
|
|
||||||
// Update the site with new WireGuard info
|
|
||||||
const [updateRes] = await db
|
|
||||||
.update(sites)
|
|
||||||
.set({
|
|
||||||
publicKey,
|
|
||||||
address,
|
|
||||||
listenPort: port
|
|
||||||
})
|
|
||||||
.where(eq(sites.siteId, siteId))
|
|
||||||
.returning();
|
|
||||||
|
|
||||||
site = updateRes;
|
|
||||||
logger.info(`Updated site ${siteId} with new WG Newt info`);
|
|
||||||
} else {
|
|
||||||
// update the endpoint and the public key
|
|
||||||
const [siteRes] = await db
|
|
||||||
.update(sites)
|
|
||||||
.set({
|
|
||||||
publicKey,
|
|
||||||
listenPort: port
|
|
||||||
})
|
|
||||||
.where(eq(sites.siteId, siteId))
|
|
||||||
.returning();
|
|
||||||
|
|
||||||
site = siteRes;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!site) {
|
if (!site) {
|
||||||
logger.error("handleGetConfigMessage: Failed to update site");
|
logger.error("handleGetConfigMessage: Failed to update site");
|
||||||
|
@ -139,7 +109,9 @@ export const handleGetConfigMessage: MessageHandler = async (context) => {
|
||||||
const peerData = {
|
const peerData = {
|
||||||
publicKey: client.clients.pubKey!,
|
publicKey: client.clients.pubKey!,
|
||||||
allowedIps: [client.clients.subnet!],
|
allowedIps: [client.clients.subnet!],
|
||||||
endpoint: client.clientSites.isRelayed ? "" : client.clients.endpoint! // if its relayed it should be localhost
|
endpoint: client.clientSites.isRelayed
|
||||||
|
? ""
|
||||||
|
: client.clients.endpoint! // if its relayed it should be localhost
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add or update this peer on the olm if it is connected
|
// Add or update this peer on the olm if it is connected
|
||||||
|
|
|
@ -71,7 +71,7 @@ export async function createOrg(
|
||||||
|
|
||||||
const { orgId, name, subnet } = parsedBody.data;
|
const { orgId, name, subnet } = parsedBody.data;
|
||||||
|
|
||||||
if (subnet && !isValidCIDR(subnet)) {
|
if (!isValidCIDR(subnet)) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.BAD_REQUEST,
|
HttpCode.BAD_REQUEST,
|
||||||
|
@ -80,6 +80,22 @@ export async function createOrg(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// make sure the subnet is unique
|
||||||
|
const subnetExists = await db
|
||||||
|
.select()
|
||||||
|
.from(orgs)
|
||||||
|
.where(eq(orgs.subnet, subnet))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (subnetExists.length > 0) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.CONFLICT,
|
||||||
|
`Subnet ${subnet} already exists`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// make sure the orgId is unique
|
// make sure the orgId is unique
|
||||||
const orgExists = await db
|
const orgExists = await db
|
||||||
.select()
|
.select()
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { roles, userSites, sites, roleSites, Site } from "@server/db/schema";
|
import {
|
||||||
|
roles,
|
||||||
|
userSites,
|
||||||
|
sites,
|
||||||
|
roleSites,
|
||||||
|
Site,
|
||||||
|
orgs
|
||||||
|
} from "@server/db/schema";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
|
@ -13,6 +20,8 @@ import { fromError } from "zod-validation-error";
|
||||||
import { newts } from "@server/db/schema";
|
import { newts } from "@server/db/schema";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
import { hashPassword } from "@server/auth/password";
|
import { hashPassword } from "@server/auth/password";
|
||||||
|
import { isValidIP } from "@server/lib/validators";
|
||||||
|
import { isIpInCidr } from "@server/lib/ip";
|
||||||
|
|
||||||
const createSiteParamsSchema = z
|
const createSiteParamsSchema = z
|
||||||
.object({
|
.object({
|
||||||
|
@ -34,6 +43,7 @@ const createSiteSchema = z
|
||||||
subnet: z.string().optional(),
|
subnet: z.string().optional(),
|
||||||
newtId: z.string().optional(),
|
newtId: z.string().optional(),
|
||||||
secret: z.string().optional(),
|
secret: z.string().optional(),
|
||||||
|
address: z.string().optional(),
|
||||||
type: z.enum(["newt", "wireguard", "local"])
|
type: z.enum(["newt", "wireguard", "local"])
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
@ -58,8 +68,16 @@ export async function createSite(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { name, type, exitNodeId, pubKey, subnet, newtId, secret } =
|
const {
|
||||||
parsedBody.data;
|
name,
|
||||||
|
type,
|
||||||
|
exitNodeId,
|
||||||
|
pubKey,
|
||||||
|
subnet,
|
||||||
|
newtId,
|
||||||
|
secret,
|
||||||
|
address
|
||||||
|
} = parsedBody.data;
|
||||||
|
|
||||||
const parsedParams = createSiteParamsSchema.safeParse(req.params);
|
const parsedParams = createSiteParamsSchema.safeParse(req.params);
|
||||||
if (!parsedParams.success) {
|
if (!parsedParams.success) {
|
||||||
|
@ -79,6 +97,53 @@ export async function createSite(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const [org] = await db.select().from(orgs).where(eq(orgs.orgId, orgId));
|
||||||
|
|
||||||
|
if (!org) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.NOT_FOUND,
|
||||||
|
`Organization with ID ${orgId} not found`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (address) {
|
||||||
|
if (!isValidIP(address)) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
"Invalid subnet format. Please provide a valid CIDR notation."
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isIpInCidr(address, org.subnet)) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
"IP is not in the CIDR range of the subnet."
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the subnet is unique
|
||||||
|
const addressExists = await db
|
||||||
|
.select()
|
||||||
|
.from(sites)
|
||||||
|
.where(eq(sites.address, address))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (addressExists.length > 0) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.CONFLICT,
|
||||||
|
`Subnet ${subnet} already exists`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const niceId = await getUniqueSiteName(orgId);
|
const niceId = await getUniqueSiteName(orgId);
|
||||||
|
|
||||||
await db.transaction(async (trx) => {
|
await db.transaction(async (trx) => {
|
||||||
|
@ -102,6 +167,7 @@ export async function createSite(
|
||||||
exitNodeId,
|
exitNodeId,
|
||||||
name,
|
name,
|
||||||
niceId,
|
niceId,
|
||||||
|
address: address || null,
|
||||||
subnet,
|
subnet,
|
||||||
type,
|
type,
|
||||||
...(pubKey && type == "wireguard" && { pubKey })
|
...(pubKey && type == "wireguard" && { pubKey })
|
||||||
|
@ -116,6 +182,7 @@ export async function createSite(
|
||||||
orgId,
|
orgId,
|
||||||
name,
|
name,
|
||||||
niceId,
|
niceId,
|
||||||
|
address: address || null,
|
||||||
type,
|
type,
|
||||||
subnet: "0.0.0.0/0"
|
subnet: "0.0.0.0/0"
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,9 +6,11 @@ import response from "@server/lib/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { findNextAvailableCidr } from "@server/lib/ip";
|
import { findNextAvailableCidr, getNextAvailableClientSubnet } from "@server/lib/ip";
|
||||||
import { generateId } from "@server/auth/sessions/app";
|
import { generateId } from "@server/auth/sessions/app";
|
||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { fromError } from "zod-validation-error";
|
||||||
|
|
||||||
export type PickSiteDefaultsResponse = {
|
export type PickSiteDefaultsResponse = {
|
||||||
exitNodeId: number;
|
exitNodeId: number;
|
||||||
|
@ -20,14 +22,32 @@ export type PickSiteDefaultsResponse = {
|
||||||
subnet: string;
|
subnet: string;
|
||||||
newtId: string;
|
newtId: string;
|
||||||
newtSecret: string;
|
newtSecret: string;
|
||||||
|
clientAddress: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const pickSiteDefaultsSchema = z
|
||||||
|
.object({
|
||||||
|
orgId: z.string()
|
||||||
|
})
|
||||||
|
.strict();
|
||||||
|
|
||||||
export async function pickSiteDefaults(
|
export async function pickSiteDefaults(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
next: NextFunction
|
next: NextFunction
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
try {
|
try {
|
||||||
|
const parsedParams = pickSiteDefaultsSchema.safeParse(req.params);
|
||||||
|
if (!parsedParams.success) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
fromError(parsedParams.error).toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { orgId } = parsedParams.data;
|
||||||
// TODO: more intelligent way to pick the exit node
|
// TODO: more intelligent way to pick the exit node
|
||||||
|
|
||||||
// make sure there is an exit node by counting the exit nodes table
|
// make sure there is an exit node by counting the exit nodes table
|
||||||
|
@ -73,6 +93,18 @@ export async function pickSiteDefaults(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newClientAddress = await getNextAvailableClientSubnet(orgId);
|
||||||
|
if (!newClientAddress) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
"No available subnet found"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientAddress = newClientAddress.split("/")[0];
|
||||||
|
|
||||||
const newtId = generateId(15);
|
const newtId = generateId(15);
|
||||||
const secret = generateId(48);
|
const secret = generateId(48);
|
||||||
|
|
||||||
|
@ -86,6 +118,7 @@ export async function pickSiteDefaults(
|
||||||
endpoint: exitNode.endpoint,
|
endpoint: exitNode.endpoint,
|
||||||
// subnet: `${newSubnet.split("/")[0]}/${config.getRawConfig().gerbil.block_size}`, // we want the block size of the whole subnet
|
// subnet: `${newSubnet.split("/")[0]}/${config.getRawConfig().gerbil.block_size}`, // we want the block size of the whole subnet
|
||||||
subnet: newSubnet,
|
subnet: newSubnet,
|
||||||
|
clientAddress: clientAddress,
|
||||||
newtId,
|
newtId,
|
||||||
newtSecret: secret
|
newtSecret: secret
|
||||||
},
|
},
|
||||||
|
|
|
@ -264,7 +264,7 @@ export default function CreateClientForm({
|
||||||
name="subnet"
|
name="subnet"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem>
|
||||||
<FormLabel>Subnet</FormLabel>
|
<FormLabel>Address</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<Input
|
<Input
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
@ -273,7 +273,7 @@ export default function CreateClientForm({
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormDescription>
|
<FormDescription>
|
||||||
The subnet that this client will use for connectivity.
|
The address that this client will use for connectivity.
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|
|
@ -69,7 +69,8 @@ const createSiteFormSchema = z
|
||||||
message: "Name must not be longer than 30 characters."
|
message: "Name must not be longer than 30 characters."
|
||||||
}),
|
}),
|
||||||
method: z.string(),
|
method: z.string(),
|
||||||
copied: z.boolean()
|
copied: z.boolean(),
|
||||||
|
clientAddress: z.string().optional()
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
|
@ -130,7 +131,7 @@ export default function Page() {
|
||||||
const [newtId, setNewtId] = useState("");
|
const [newtId, setNewtId] = useState("");
|
||||||
const [newtSecret, setNewtSecret] = useState("");
|
const [newtSecret, setNewtSecret] = useState("");
|
||||||
const [newtEndpoint, setNewtEndpoint] = useState("");
|
const [newtEndpoint, setNewtEndpoint] = useState("");
|
||||||
|
const [clientAddress, setClientAddress] = useState("");
|
||||||
const [publicKey, setPublicKey] = useState("");
|
const [publicKey, setPublicKey] = useState("");
|
||||||
const [privateKey, setPrivateKey] = useState("");
|
const [privateKey, setPrivateKey] = useState("");
|
||||||
const [wgConfig, setWgConfig] = useState("");
|
const [wgConfig, setWgConfig] = useState("");
|
||||||
|
@ -315,7 +316,8 @@ PersistentKeepalive = 5`;
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: "",
|
name: "",
|
||||||
copied: false,
|
copied: false,
|
||||||
method: "newt"
|
method: "newt",
|
||||||
|
clientAddress: ""
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -324,7 +326,7 @@ PersistentKeepalive = 5`;
|
||||||
|
|
||||||
let payload: CreateSiteBody = {
|
let payload: CreateSiteBody = {
|
||||||
name: data.name,
|
name: data.name,
|
||||||
type: data.method as any,
|
type: data.method as any
|
||||||
};
|
};
|
||||||
|
|
||||||
if (data.method == "wireguard") {
|
if (data.method == "wireguard") {
|
||||||
|
@ -361,7 +363,8 @@ PersistentKeepalive = 5`;
|
||||||
subnet: siteDefaults.subnet,
|
subnet: siteDefaults.subnet,
|
||||||
exitNodeId: siteDefaults.exitNodeId,
|
exitNodeId: siteDefaults.exitNodeId,
|
||||||
secret: siteDefaults.newtSecret,
|
secret: siteDefaults.newtSecret,
|
||||||
newtId: siteDefaults.newtId
|
newtId: siteDefaults.newtId,
|
||||||
|
address: clientAddress
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -431,10 +434,12 @@ PersistentKeepalive = 5`;
|
||||||
const newtId = data.newtId;
|
const newtId = data.newtId;
|
||||||
const newtSecret = data.newtSecret;
|
const newtSecret = data.newtSecret;
|
||||||
const newtEndpoint = data.endpoint;
|
const newtEndpoint = data.endpoint;
|
||||||
|
const clientAddress = data.clientAddress;
|
||||||
|
|
||||||
setNewtId(newtId);
|
setNewtId(newtId);
|
||||||
setNewtSecret(newtSecret);
|
setNewtSecret(newtSecret);
|
||||||
setNewtEndpoint(newtEndpoint);
|
setNewtEndpoint(newtEndpoint);
|
||||||
|
setClientAddress(clientAddress);
|
||||||
|
|
||||||
hydrateCommands(
|
hydrateCommands(
|
||||||
newtId,
|
newtId,
|
||||||
|
@ -617,18 +622,6 @@ PersistentKeepalive = 5`;
|
||||||
</InfoSection>
|
</InfoSection>
|
||||||
</InfoSections>
|
</InfoSections>
|
||||||
|
|
||||||
<Alert variant="default" className="">
|
|
||||||
<InfoIcon className="h-4 w-4" />
|
|
||||||
<AlertTitle className="font-semibold">
|
|
||||||
Save Your Credentials
|
|
||||||
</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
You will only be able to see
|
|
||||||
this once. Make sure to copy it
|
|
||||||
to a secure place.
|
|
||||||
</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
|
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
className="space-y-4"
|
className="space-y-4"
|
||||||
|
@ -669,97 +662,49 @@ PersistentKeepalive = 5`;
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="clientAddress"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
Client Address
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
autoComplete="off"
|
||||||
|
value={
|
||||||
|
clientAddress
|
||||||
|
}
|
||||||
|
onChange={(
|
||||||
|
e
|
||||||
|
) => {
|
||||||
|
setClientAddress(
|
||||||
|
e
|
||||||
|
.target
|
||||||
|
.value
|
||||||
|
);
|
||||||
|
field.onChange(
|
||||||
|
e
|
||||||
|
.target
|
||||||
|
.value
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
<FormDescription>
|
||||||
|
Specify the IP
|
||||||
|
address of the
|
||||||
|
host.
|
||||||
|
</FormDescription>
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</SettingsSectionBody>
|
</SettingsSectionBody>
|
||||||
</SettingsSection>
|
</SettingsSection>
|
||||||
|
|
||||||
<SettingsSection>
|
|
||||||
<SettingsSectionHeader>
|
|
||||||
<SettingsSectionTitle>
|
|
||||||
Install Newt
|
|
||||||
</SettingsSectionTitle>
|
|
||||||
<SettingsSectionDescription>
|
|
||||||
Get Newt running on your system
|
|
||||||
</SettingsSectionDescription>
|
|
||||||
</SettingsSectionHeader>
|
|
||||||
<SettingsSectionBody>
|
|
||||||
<div>
|
|
||||||
<p className="font-bold mb-3">
|
|
||||||
Operating System
|
|
||||||
</p>
|
|
||||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-2">
|
|
||||||
{[
|
|
||||||
"linux",
|
|
||||||
"docker",
|
|
||||||
"mac",
|
|
||||||
"windows",
|
|
||||||
"freebsd"
|
|
||||||
].map((os) => (
|
|
||||||
<Button
|
|
||||||
key={os}
|
|
||||||
variant={
|
|
||||||
platform === os
|
|
||||||
? "squareOutlinePrimary"
|
|
||||||
: "squareOutline"
|
|
||||||
}
|
|
||||||
className={`flex-1 min-w-[120px] ${platform === os ? "bg-primary/10" : ""}`}
|
|
||||||
onClick={() => {
|
|
||||||
setPlatform(os);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{getPlatformIcon(os)}
|
|
||||||
{getPlatformName(os)}
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p className="font-bold mb-3">
|
|
||||||
{platform === "docker"
|
|
||||||
? "Method"
|
|
||||||
: "Architecture"}
|
|
||||||
</p>
|
|
||||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-2">
|
|
||||||
{getArchitectures().map(
|
|
||||||
(arch) => (
|
|
||||||
<Button
|
|
||||||
key={arch}
|
|
||||||
variant={
|
|
||||||
architecture ===
|
|
||||||
arch
|
|
||||||
? "squareOutlinePrimary"
|
|
||||||
: "squareOutline"
|
|
||||||
}
|
|
||||||
className={`flex-1 min-w-[120px] ${architecture === arch ? "bg-primary/10" : ""}`}
|
|
||||||
onClick={() =>
|
|
||||||
setArchitecture(
|
|
||||||
arch
|
|
||||||
)
|
|
||||||
}
|
|
||||||
>
|
|
||||||
{arch}
|
|
||||||
</Button>
|
|
||||||
)
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="pt-4">
|
|
||||||
<p className="font-bold mb-3">
|
|
||||||
Commands
|
|
||||||
</p>
|
|
||||||
<div className="mt-2">
|
|
||||||
<CopyTextBox
|
|
||||||
text={getCommand().join(
|
|
||||||
"\n"
|
|
||||||
)}
|
|
||||||
outline={true}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</SettingsSectionBody>
|
|
||||||
</SettingsSection>
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue