From 79751c208d8c6566a11a5ef2e2c16c3d66472bee Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 9 Apr 2026 22:24:39 -0400 Subject: [PATCH] basic ui working --- messages/en-US.json | 9 + server/lib/blueprints/clientResources.ts | 27 +- server/lib/ip.ts | 9 +- .../private/lib/traefik/getTraefikConfig.ts | 7 +- .../siteResource/createSiteResource.ts | 15 + .../siteResource/listAllSiteResourcesByOrg.ts | 9 +- .../siteResource/updateSiteResource.ts | 17 + .../settings/resources/client/page.tsx | 14 +- src/components/ClientResourcesTable.tsx | 39 +-- .../CreateInternalResourceDialog.tsx | 55 ++- src/components/EditInternalResourceDialog.tsx | 9 +- src/components/InternalResourceForm.tsx | 322 ++++++++++++------ 12 files changed, 365 insertions(+), 167 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index 40b66fc6e..ba22ea0d1 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1819,6 +1819,9 @@ "editInternalResourceDialogModeCidr": "CIDR", "editInternalResourceDialogModeHttp": "HTTP", "editInternalResourceDialogModeHttps": "HTTPS", + "editInternalResourceDialogScheme": "Scheme", + "editInternalResourceDialogEnableSsl": "Enable SSL", + "editInternalResourceDialogEnableSslDescription": "Enable SSL/TLS encryption for secure HTTPS connections to the destination.", "editInternalResourceDialogDestination": "Destination", "editInternalResourceDialogDestinationHostDescription": "The IP address or hostname of the resource on the site's network.", "editInternalResourceDialogDestinationIPDescription": "The IP or hostname address of the resource on the site's network.", @@ -1864,11 +1867,17 @@ "createInternalResourceDialogModeCidr": "CIDR", "createInternalResourceDialogModeHttp": "HTTP", "createInternalResourceDialogModeHttps": "HTTPS", + "scheme": "Scheme", + "createInternalResourceDialogScheme": "Scheme", + "createInternalResourceDialogEnableSsl": "Enable SSL", + "createInternalResourceDialogEnableSslDescription": "Enable SSL/TLS encryption for secure HTTPS connections to the destination.", "createInternalResourceDialogDestination": "Destination", "createInternalResourceDialogDestinationHostDescription": "The IP address or hostname of the resource on the site's network.", "createInternalResourceDialogDestinationCidrDescription": "The CIDR range of the resource on the site's network.", "createInternalResourceDialogAlias": "Alias", "createInternalResourceDialogAliasDescription": "An optional internal DNS alias for this resource.", + "internalResourceDownstreamSchemeRequired": "Scheme is required for HTTP resources", + "internalResourceHttpPortRequired": "Destination port is required for HTTP resources", "siteConfiguration": "Configuration", "siteAcceptClientConnections": "Accept Client Connections", "siteAcceptClientConnectionsDescription": "Allow user devices and clients to access resources on this site. This can be changed later.", diff --git a/server/lib/blueprints/clientResources.ts b/server/lib/blueprints/clientResources.ts index 4196c67ed..281f4f7dd 100644 --- a/server/lib/blueprints/clientResources.ts +++ b/server/lib/blueprints/clientResources.ts @@ -16,6 +16,20 @@ import { Config } from "./types"; import logger from "@server/logger"; import { getNextAvailableAliasAddress } from "../ip"; +function siteResourceModeForDb(mode: "host" | "cidr" | "http" | "https"): { + mode: "host" | "cidr" | "http"; + ssl: boolean; + scheme: "http" | "https" | null; +} { + if (mode === "https") { + return { mode: "http", ssl: true, scheme: "https" }; + } + if (mode === "http") { + return { mode: "http", ssl: false, scheme: "http" }; + } + return { mode, ssl: false, scheme: null }; +} + export type ClientResourcesResults = { newSiteResource: SiteResource; oldSiteResource?: SiteResource; @@ -76,13 +90,16 @@ export async function updateClientResources( } if (existingResource) { + const mappedMode = siteResourceModeForDb(resourceData.mode); // Update existing resource const [updatedResource] = await trx .update(siteResources) .set({ name: resourceData.name || resourceNiceId, siteId: site.siteId, - mode: resourceData.mode, + mode: mappedMode.mode, + ssl: mappedMode.ssl, + scheme: mappedMode.scheme, destination: resourceData.destination, destinationPort: resourceData["destination-port"], enabled: true, // hardcoded for now @@ -208,9 +225,9 @@ export async function updateClientResources( oldSiteResource: existingResource }); } else { + const mappedMode = siteResourceModeForDb(resourceData.mode); let aliasAddress: string | null = null; - if (resourceData.mode == "host") { - // we can only have an alias on a host + if (mappedMode.mode === "host" || mappedMode.mode === "http") { aliasAddress = await getNextAvailableAliasAddress(orgId); } @@ -222,7 +239,9 @@ export async function updateClientResources( siteId: site.siteId, niceId: resourceNiceId, name: resourceData.name || resourceNiceId, - mode: resourceData.mode, + mode: mappedMode.mode, + ssl: mappedMode.ssl, + scheme: mappedMode.scheme, destination: resourceData.destination, destinationPort: resourceData["destination-port"], enabled: true, // hardcoded for now diff --git a/server/lib/ip.ts b/server/lib/ip.ts index 96ea04873..b4be4285f 100644 --- a/server/lib/ip.ts +++ b/server/lib/ip.ts @@ -652,7 +652,7 @@ export function generateSubnetProxyTargetV2( disableIcmp, resourceId: siteResource.siteResourceId }; - } else if (siteResource.mode == "http" || siteResource.mode == "https") { + } else if (siteResource.mode == "http") { let destination = siteResource.destination; // check if this is a valid ip const ipSchema = z.union([z.ipv4(), z.ipv6()]); @@ -667,10 +667,11 @@ export function generateSubnetProxyTargetV2( !siteResource.scheme ) { logger.debug( - `Site resource ${siteResource.siteResourceId} is in HTTP/HTTPS mode but is missing alias or alias address or destinationPort, skipping alias target generation.` + `Site resource ${siteResource.siteResourceId} is in HTTP mode but is missing alias or alias address or destinationPort or scheme, skipping alias target generation.` ); return; } + const publicProtocol = siteResource.ssl ? "https" : "http"; // also push a match for the alias address target = { sourcePrefixes: [], @@ -679,14 +680,14 @@ export function generateSubnetProxyTargetV2( portRange, disableIcmp, resourceId: siteResource.siteResourceId, - protocol: siteResource.mode, // will be either http or https, + protocol: publicProtocol, httpTargets: [ { destAddr: siteResource.destination, destPort: siteResource.destinationPort, scheme: siteResource.scheme } - ], + ] // tlsCert: "", // tlsKey: "" }; diff --git a/server/private/lib/traefik/getTraefikConfig.ts b/server/private/lib/traefik/getTraefikConfig.ts index 2487574e8..e82f0bdc7 100644 --- a/server/private/lib/traefik/getTraefikConfig.ts +++ b/server/private/lib/traefik/getTraefikConfig.ts @@ -267,7 +267,7 @@ export async function getTraefikConfig( }); }); - // Query siteResources in http/https mode that have aliases - needed for cert generation + // Query siteResources in HTTP mode with SSL enabled and aliases — cert generation / HTTPS edge const siteResourcesWithAliases = await db .select({ siteResourceId: siteResources.siteResourceId, @@ -280,7 +280,8 @@ export async function getTraefikConfig( and( eq(siteResources.enabled, true), isNotNull(siteResources.alias), - inArray(siteResources.mode, ["http", "https"]), + eq(siteResources.mode, "http"), + eq(siteResources.ssl, true), or( eq(sites.exitNodeId, exitNodeId), and( @@ -900,7 +901,7 @@ export async function getTraefikConfig( } } - // Add Traefik routes for siteResource aliases in http/https mode so that + // Add Traefik routes for siteResource aliases (HTTP mode + SSL) so that // Traefik generates TLS certificates for those domains even when no // matching resource exists yet. if (siteResourcesWithAliases.length > 0) { diff --git a/server/routers/siteResource/createSiteResource.ts b/server/routers/siteResource/createSiteResource.ts index 99db6810e..6fbe50d59 100644 --- a/server/routers/siteResource/createSiteResource.ts +++ b/server/routers/siteResource/createSiteResource.ts @@ -111,6 +111,21 @@ const createSiteResourceSchema = z { message: "Destination must be a valid CIDR notation for cidr mode" } + ) + .refine( + (data) => { + if (data.mode !== "http") return true; + return ( + data.scheme !== undefined && + data.destinationPort !== undefined && + data.destinationPort >= 1 && + data.destinationPort <= 65535 + ); + }, + { + message: + "HTTP mode requires scheme (http or https) and a valid destination port" + } ); export type CreateSiteResourceBody = z.infer; diff --git a/server/routers/siteResource/listAllSiteResourcesByOrg.ts b/server/routers/siteResource/listAllSiteResourcesByOrg.ts index 7376fd6ec..896dc77f5 100644 --- a/server/routers/siteResource/listAllSiteResourcesByOrg.ts +++ b/server/routers/siteResource/listAllSiteResourcesByOrg.ts @@ -41,12 +41,12 @@ const listAllSiteResourcesByOrgQuerySchema = z.object({ }), query: z.string().optional(), mode: z - .enum(["host", "cidr", "http", "https"]) + .enum(["host", "cidr", "http"]) .optional() .catch(undefined) .openapi({ type: "string", - enum: ["host", "cidr", "http", "https"], + enum: ["host", "cidr", "http"], description: "Filter site resources by mode" }), sort_by: z @@ -88,6 +88,7 @@ function querySiteResourcesBase() { niceId: siteResources.niceId, name: siteResources.name, mode: siteResources.mode, + ssl: siteResources.ssl, scheme: siteResources.scheme, proxyPort: siteResources.proxyPort, destinationPort: siteResources.destinationPort, @@ -193,7 +194,9 @@ export async function listAllSiteResourcesByOrg( const baseQuery = querySiteResourcesBase().where(and(...conditions)); const countQuery = db.$count( - querySiteResourcesBase().where(and(...conditions)).as("filtered_site_resources") + querySiteResourcesBase() + .where(and(...conditions)) + .as("filtered_site_resources") ); const [siteResourcesList, totalCount] = await Promise.all([ diff --git a/server/routers/siteResource/updateSiteResource.ts b/server/routers/siteResource/updateSiteResource.ts index bb0239478..81283e353 100644 --- a/server/routers/siteResource/updateSiteResource.ts +++ b/server/routers/siteResource/updateSiteResource.ts @@ -125,6 +125,23 @@ const updateSiteResourceSchema = z { message: "Destination must be a valid CIDR notation for cidr mode" } + ) + .refine( + (data) => { + if (data.mode !== "http") return true; + return ( + data.scheme !== undefined && + data.scheme !== null && + data.destinationPort !== undefined && + data.destinationPort !== null && + data.destinationPort >= 1 && + data.destinationPort <= 65535 + ); + }, + { + message: + "HTTP mode requires scheme (http or https) and a valid destination port" + } ); export type UpdateSiteResourceBody = z.infer; diff --git a/src/app/[orgId]/settings/resources/client/page.tsx b/src/app/[orgId]/settings/resources/client/page.tsx index 95477949d..46dfeb9cc 100644 --- a/src/app/[orgId]/settings/resources/client/page.tsx +++ b/src/app/[orgId]/settings/resources/client/page.tsx @@ -56,13 +56,25 @@ export default async function ClientResourcesPage( const internalResourceRows: InternalResourceRow[] = siteResources.map( (siteResource) => { + const rawMode = siteResource.mode as string | undefined; + const normalizedMode = + rawMode === "https" + ? ("http" as const) + : rawMode === "host" || rawMode === "cidr" || rawMode === "http" + ? rawMode + : ("host" as const); return { id: siteResource.siteResourceId, name: siteResource.name, orgId: params.orgId, siteName: siteResource.siteName, siteAddress: siteResource.siteAddress || null, - mode: siteResource.mode || ("port" as any), + mode: normalizedMode, + scheme: + siteResource.scheme ?? + (rawMode === "https" ? ("https" as const) : null), + ssl: + siteResource.ssl === true || rawMode === "https", // protocol: siteResource.protocol, // proxyPort: siteResource.proxyPort, siteId: siteResource.siteId, diff --git a/src/components/ClientResourcesTable.tsx b/src/components/ClientResourcesTable.tsx index 20b968ea3..6adce8fd9 100644 --- a/src/components/ClientResourcesTable.tsx +++ b/src/components/ClientResourcesTable.tsx @@ -46,7 +46,9 @@ export type InternalResourceRow = { siteName: string; siteAddress: string | null; // mode: "host" | "cidr" | "port"; - mode: "host" | "cidr" | "http" | "https"; + mode: "host" | "cidr" | "http"; + scheme: "http" | "https" | null; + ssl: boolean; // protocol: string | null; // proxyPort: number | null; siteId: number; @@ -64,30 +66,27 @@ export type InternalResourceRow = { }; function resolveHttpHttpsDisplayPort( - mode: "http" | "https", + mode: "http", httpHttpsPort: number | null ): number { if (httpHttpsPort != null) { return httpHttpsPort; } - return mode === "https" ? 443 : 80; + return 80; } function formatDestinationDisplay(row: InternalResourceRow): string { - const { mode, destination, httpHttpsPort } = row; - if (mode !== "http" && mode !== "https") { + const { mode, destination, httpHttpsPort, scheme } = row; + if (mode !== "http") { return destination; } const port = resolveHttpHttpsDisplayPort(mode, httpHttpsPort); + const downstreamScheme = scheme ?? "http"; const hostPart = destination.includes(":") && !destination.startsWith("[") ? `[${destination}]` : destination; - return `${hostPart}:${port}`; -} - -function formatHttpHttpsAliasUrl(mode: "http" | "https", alias: string): string { - return `${mode}://${alias}`; + return `${downstreamScheme}://${hostPart}:${port}`; } function isSafeUrlForLink(href: string): boolean { @@ -255,10 +254,6 @@ export default function ClientResourcesTable({ { value: "http", label: t("editInternalResourceDialogModeHttp") - }, - { - value: "https", - label: t("editInternalResourceDialogModeHttps") } ]} selectedValue={searchParams.get("mode") ?? undefined} @@ -272,14 +267,13 @@ export default function ClientResourcesTable({ cell: ({ row }) => { const resourceRow = row.original; const modeLabels: Record< - "host" | "cidr" | "port" | "http" | "https", + "host" | "cidr" | "port" | "http", string > = { host: t("editInternalResourceDialogModeHost"), cidr: t("editInternalResourceDialogModeCidr"), port: t("editInternalResourceDialogModePort"), - http: t("editInternalResourceDialogModeHttp"), - https: t("editInternalResourceDialogModeHttps") + http: t("editInternalResourceDialogModeHttp") }; return {modeLabels[resourceRow.mode]}; } @@ -319,15 +313,8 @@ export default function ClientResourcesTable({ /> ); } - if ( - (resourceRow.mode === "http" || - resourceRow.mode === "https") && - resourceRow.alias - ) { - const url = formatHttpHttpsAliasUrl( - resourceRow.mode, - resourceRow.alias - ); + if (resourceRow.mode === "http" && resourceRow.alias) { + const url = `${resourceRow.ssl ? "https" : "http"}://${resourceRow.alias}`; return ( parseInt(r.id)) : [], + ...(data.authDaemonMode != null && { + authDaemonMode: data.authDaemonMode + }), + ...(data.authDaemonMode === "remote" && + data.authDaemonPort != null && { + authDaemonPort: data.authDaemonPort + }), + roleIds: data.roles + ? data.roles.map((r) => parseInt(r.id)) + : [], userIds: data.users ? data.users.map((u) => u.id) : [], - clientIds: data.clients ? data.clients.map((c) => parseInt(c.id)) : [] + clientIds: data.clients + ? data.clients.map((c) => parseInt(c.id)) + : [] } ); toast({ title: t("createInternalResourceDialogSuccess"), - description: t("createInternalResourceDialogInternalResourceCreatedSuccessfully"), + description: t( + "createInternalResourceDialogInternalResourceCreatedSuccessfully" + ), variant: "default" }); setOpen(false); @@ -98,7 +117,9 @@ export default function CreateInternalResourceDialog({ title: t("createInternalResourceDialogError"), description: formatAxiosError( error, - t("createInternalResourceDialogFailedToCreateInternalResource") + t( + "createInternalResourceDialogFailedToCreateInternalResource" + ) ), variant: "destructive" }); @@ -111,9 +132,13 @@ export default function CreateInternalResourceDialog({ - {t("createInternalResourceDialogCreateClientResource")} + + {t("createInternalResourceDialogCreateClientResource")} + - {t("createInternalResourceDialogCreateClientResourceDescription")} + {t( + "createInternalResourceDialogCreateClientResourceDescription" + )} @@ -128,7 +153,11 @@ export default function CreateInternalResourceDialog({ - diff --git a/src/components/EditInternalResourceDialog.tsx b/src/components/EditInternalResourceDialog.tsx index 2a4cd35fc..5f20dd458 100644 --- a/src/components/EditInternalResourceDialog.tsx +++ b/src/components/EditInternalResourceDialog.tsx @@ -55,9 +55,7 @@ export default function EditInternalResourceDialog({ try { let data = { ...values }; if ( - (data.mode === "host" || - data.mode === "http" || - data.mode === "https") && + (data.mode === "host" || data.mode === "http") && isHostname(data.destination) ) { const currentAlias = data.alias?.trim() || ""; @@ -76,6 +74,11 @@ export default function EditInternalResourceDialog({ mode: data.mode, niceId: data.niceId, destination: data.destination, + ...(data.mode === "http" && { + scheme: data.scheme, + ssl: data.ssl ?? false, + destinationPort: data.httpHttpsPort ?? null + }), alias: data.alias && typeof data.alias === "string" && diff --git a/src/components/InternalResourceForm.tsx b/src/components/InternalResourceForm.tsx index fb31d27b8..9e1390f09 100644 --- a/src/components/InternalResourceForm.tsx +++ b/src/components/InternalResourceForm.tsx @@ -46,6 +46,7 @@ import { SitesSelector, type Selectedsite } from "./site-selector"; import { CaretSortIcon } from "@radix-ui/react-icons"; import { MachinesSelector } from "./machines-selector"; import DomainPicker from "@app/components/DomainPicker"; +import { SwitchInput } from "@app/components/SwitchInput"; // --- Helpers (shared) --- @@ -121,7 +122,7 @@ export const cleanForFQDN = (name: string): string => type Site = ListSitesResponse["sites"][0]; -export type InternalResourceMode = "host" | "cidr" | "http" | "https"; +export type InternalResourceMode = "host" | "cidr" | "http"; export type InternalResourceData = { id: number; @@ -139,6 +140,8 @@ export type InternalResourceData = { authDaemonMode?: "site" | "remote" | null; authDaemonPort?: number | null; httpHttpsPort?: number | null; + scheme?: "http" | "https" | null; + ssl?: boolean; httpConfigSubdomain?: string | null; httpConfigDomainId?: string | null; httpConfigFullDomain?: string | null; @@ -159,6 +162,8 @@ export type InternalResourceFormValues = { authDaemonMode?: "site" | "remote" | null; authDaemonPort?: number | null; httpHttpsPort?: number | null; + scheme?: "http" | "https"; + ssl?: boolean; httpConfigSubdomain?: string | null; httpConfigDomainId?: string | null; httpConfigFullDomain?: string | null; @@ -226,10 +231,18 @@ export function InternalResourceForm({ variant === "create" ? "createInternalResourceDialogModeHttp" : "editInternalResourceDialogModeHttp"; - const modeHttpsKey = + const schemeLabelKey = variant === "create" - ? "createInternalResourceDialogModeHttps" - : "editInternalResourceDialogModeHttps"; + ? "createInternalResourceDialogScheme" + : "editInternalResourceDialogScheme"; + const enableSslLabelKey = + variant === "create" + ? "createInternalResourceDialogEnableSsl" + : "editInternalResourceDialogEnableSsl"; + const enableSslDescriptionKey = + variant === "create" + ? "createInternalResourceDialogEnableSslDescription" + : "editInternalResourceDialogEnableSslDescription"; const destinationLabelKey = variant === "create" ? "createInternalResourceDialogDestination" @@ -255,48 +268,78 @@ export function InternalResourceForm({ ? "createInternalResourceDialogHttpConfigurationDescription" : "editInternalResourceDialogHttpConfigurationDescription"; - const formSchema = z.object({ - name: z.string().min(1, t(nameRequiredKey)).max(255, t(nameMaxKey)), - siteId: z - .number() - .int() - .positive(siteRequiredKey ? t(siteRequiredKey) : undefined), - mode: z.enum(["host", "cidr", "http", "https"]), - destination: z - .string() - .min( - 1, - destinationRequiredKey - ? { message: t(destinationRequiredKey) } - : undefined - ), - alias: z.string().nullish(), - httpHttpsPort: z.number().int().min(1).max(65535).optional().nullable(), - httpConfigSubdomain: z.string().nullish(), - httpConfigDomainId: z.string().nullish(), - httpConfigFullDomain: z.string().nullish(), - niceId: z - .string() - .min(1) - .max(255) - .regex(/^[a-zA-Z0-9-]+$/) - .optional(), - tcpPortRangeString: createPortRangeStringSchema(t), - udpPortRangeString: createPortRangeStringSchema(t), - disableIcmp: z.boolean().optional(), - authDaemonMode: z.enum(["site", "remote"]).optional().nullable(), - authDaemonPort: z.number().int().positive().optional().nullable(), - roles: z.array(tagSchema).optional(), - users: z.array(tagSchema).optional(), - clients: z - .array( - z.object({ - clientId: z.number(), - name: z.string() - }) - ) - .optional() - }); + const formSchema = z + .object({ + name: z.string().min(1, t(nameRequiredKey)).max(255, t(nameMaxKey)), + siteId: z + .number() + .int() + .positive(siteRequiredKey ? t(siteRequiredKey) : undefined), + mode: z.enum(["host", "cidr", "http"]), + destination: z + .string() + .min( + 1, + destinationRequiredKey + ? { message: t(destinationRequiredKey) } + : undefined + ), + alias: z.string().nullish(), + httpHttpsPort: z + .number() + .int() + .min(1) + .max(65535) + .optional() + .nullable(), + scheme: z.enum(["http", "https"]).optional(), + ssl: z.boolean().optional(), + httpConfigSubdomain: z.string().nullish(), + httpConfigDomainId: z.string().nullish(), + httpConfigFullDomain: z.string().nullish(), + niceId: z + .string() + .min(1) + .max(255) + .regex(/^[a-zA-Z0-9-]+$/) + .optional(), + tcpPortRangeString: createPortRangeStringSchema(t), + udpPortRangeString: createPortRangeStringSchema(t), + disableIcmp: z.boolean().optional(), + authDaemonMode: z.enum(["site", "remote"]).optional().nullable(), + authDaemonPort: z.number().int().positive().optional().nullable(), + roles: z.array(tagSchema).optional(), + users: z.array(tagSchema).optional(), + clients: z + .array( + z.object({ + clientId: z.number(), + name: z.string() + }) + ) + .optional() + }) + .superRefine((data, ctx) => { + if (data.mode !== "http") return; + if (!data.scheme) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: t("internalResourceDownstreamSchemeRequired"), + path: ["scheme"] + }); + } + if ( + data.httpHttpsPort == null || + !Number.isFinite(data.httpHttpsPort) || + data.httpHttpsPort < 1 + ) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: t("internalResourceHttpPortRequired"), + path: ["httpHttpsPort"] + }); + } + }); type FormData = z.infer; @@ -430,6 +473,8 @@ export function InternalResourceForm({ authDaemonMode: resource.authDaemonMode ?? "site", authDaemonPort: resource.authDaemonPort ?? null, httpHttpsPort: resource.httpHttpsPort ?? null, + scheme: resource.scheme ?? "http", + ssl: resource.ssl ?? false, httpConfigSubdomain: resource.httpConfigSubdomain ?? null, httpConfigDomainId: resource.httpConfigDomainId ?? null, httpConfigFullDomain: resource.httpConfigFullDomain ?? null, @@ -445,6 +490,8 @@ export function InternalResourceForm({ destination: "", alias: null, httpHttpsPort: null, + scheme: "http", + ssl: false, httpConfigSubdomain: null, httpConfigDomainId: null, httpConfigFullDomain: null, @@ -471,7 +518,7 @@ export function InternalResourceForm({ const httpConfigSubdomain = form.watch("httpConfigSubdomain"); const httpConfigDomainId = form.watch("httpConfigDomainId"); const httpConfigFullDomain = form.watch("httpConfigFullDomain"); - const isHttpOrHttps = mode === "http" || mode === "https"; + const isHttpMode = mode === "http"; const authDaemonMode = form.watch("authDaemonMode") ?? "site"; const hasInitialized = useRef(false); const previousResourceId = useRef(null); @@ -496,6 +543,8 @@ export function InternalResourceForm({ destination: "", alias: null, httpHttpsPort: null, + scheme: "http", + ssl: false, httpConfigSubdomain: null, httpConfigDomainId: null, httpConfigFullDomain: null, @@ -527,6 +576,8 @@ export function InternalResourceForm({ destination: resource.destination ?? "", alias: resource.alias ?? null, httpHttpsPort: resource.httpHttpsPort ?? null, + scheme: resource.scheme ?? "http", + ssl: resource.ssl ?? false, httpConfigSubdomain: resource.httpConfigSubdomain ?? null, httpConfigDomainId: resource.httpConfigDomainId ?? null, httpConfigFullDomain: resource.httpConfigFullDomain ?? null, @@ -681,6 +732,37 @@ export function InternalResourceForm({ )} /> + ( + + {t(modeLabelKey)} + + + + )} + /> + {mode === "http" && ( +
+ ( + + + {t(schemeLabelKey)} + + + + + )} + /> +
+ )}
- ( - - - {t(modeLabelKey)} - - - - - )} - /> -
-
- + @@ -793,7 +868,7 @@ export function InternalResourceForm({ />
{mode === "host" && ( -
+
)} - {(mode === "http" || mode === "https") && ( -
+ {mode === "http" && ( +
- {isHttpOrHttps ? ( + {isHttpMode ? (
+ ( + + + + + + )} + />