Complete removal of http and protocol from public

This commit is contained in:
Owen
2026-05-27 17:19:04 -07:00
parent 6d491b7bb9
commit 0ff0e83c9f
24 changed files with 169 additions and 131 deletions
+1 -1
View File
@@ -106,7 +106,7 @@ export async function applyBlueprint({
site.newt.newtId, site.newt.newtId,
[target], [target],
matchingHealthcheck ? [matchingHealthcheck] : [], matchingHealthcheck ? [matchingHealthcheck] : [],
result.proxyResource.protocol, result.proxyResource.mode === "udp" ? "udp" : "tcp",
site.newt.version site.newt.version
); );
} }
+33 -14
View File
@@ -36,6 +36,7 @@ import { isValidRegionId } from "@server/db/regions";
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed"; import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
import { fireHealthCheckUnknownAlert } from "@server/lib/alerts"; import { fireHealthCheckUnknownAlert } from "@server/lib/alerts";
import { tierMatrix } from "../billing/tierMatrix"; import { tierMatrix } from "../billing/tierMatrix";
import { http } from "winston";
export type ProxyResourcesResults = { export type ProxyResourcesResults = {
proxyResource: Resource; proxyResource: Resource;
@@ -198,9 +199,6 @@ export async function updateProxyResources(
) )
.limit(1); .limit(1);
const http = resourceData.protocol == "http";
const protocol =
resourceData.protocol == "http" ? "tcp" : resourceData.protocol;
const resourceEnabled = const resourceEnabled =
resourceData.enabled == undefined || resourceData.enabled == null resourceData.enabled == undefined || resourceData.enabled == null
? true ? true
@@ -216,7 +214,9 @@ export async function updateProxyResources(
if (existingResource) { if (existingResource) {
let domain; let domain;
if (http) { if (
["http", "ssh", "rdp", "vnc"].includes(resourceData.mode || "")
) {
domain = await getDomain( domain = await getDomain(
existingResource.resourceId, existingResource.resourceId,
resourceData["full-domain"]!, resourceData["full-domain"]!,
@@ -246,10 +246,17 @@ export async function updateProxyResources(
.update(resources) .update(resources)
.set({ .set({
name: resourceData.name || "Unnamed Resource", name: resourceData.name || "Unnamed Resource",
protocol: protocol || "tcp", mode: resourceData.mode,
http: http, proxyPort: ["http", "ssh", "rdp", "vnc"].includes(
proxyPort: http ? null : resourceData["proxy-port"], resourceData.mode || ""
fullDomain: http ? resourceData["full-domain"] : null, )
? null
: resourceData["proxy-port"],
fullDomain: ["http", "ssh", "rdp", "vnc"].includes(
resourceData.mode || ""
)
? resourceData["full-domain"]
: null,
subdomain: domain ? domain.subdomain : null, subdomain: domain ? domain.subdomain : null,
domainId: domain ? domain.domainId : null, domainId: domain ? domain.domainId : null,
wildcard: domain ? domain.wildcard : false, wildcard: domain ? domain.wildcard : false,
@@ -466,7 +473,10 @@ export async function updateProxyResources(
.set({ .set({
siteId: site.siteId, siteId: site.siteId,
ip: targetData.hostname, ip: targetData.hostname,
method: http ? targetData.method : null, method:
resourceData.mode == "http" // the other types of ssh, rdp, and vnc use the browser gateway targets and not this one so this is okay
? targetData.method
: null,
port: targetData.port, port: targetData.port,
enabled: targetData.enabled, enabled: targetData.enabled,
path: targetData.path, path: targetData.path,
@@ -687,7 +697,9 @@ export async function updateProxyResources(
} else { } else {
// create a brand new resource // create a brand new resource
let domain; let domain;
if (http) { if (
["http", "ssh", "rdp", "vnc"].includes(resourceData.mode || "")
) {
domain = await getDomain( domain = await getDomain(
undefined, undefined,
resourceData["full-domain"]!, resourceData["full-domain"]!,
@@ -711,10 +723,17 @@ export async function updateProxyResources(
orgId, orgId,
niceId: resourceNiceId, niceId: resourceNiceId,
name: resourceData.name || "Unnamed Resource", name: resourceData.name || "Unnamed Resource",
protocol: protocol || "tcp", mode: resourceData.mode,
http: http, proxyPort: ["http", "ssh", "rdp", "vnc"].includes(
proxyPort: http ? null : resourceData["proxy-port"], resourceData.mode || ""
fullDomain: http ? resourceData["full-domain"] : null, )
? null
: resourceData["proxy-port"],
fullDomain: ["http", "ssh", "rdp", "vnc"].includes(
resourceData.mode || ""
)
? resourceData["full-domain"]
: null,
subdomain: domain ? domain.subdomain : null, subdomain: domain ? domain.subdomain : null,
domainId: domain ? domain.domainId : null, domainId: domain ? domain.domainId : null,
wildcard: domain ? domain.wildcard : false, wildcard: domain ? domain.wildcard : false,
+43 -25
View File
@@ -162,10 +162,13 @@ export const HeaderSchema = z.object({
}); });
// Schema for individual resource // Schema for individual resource
export const ResourceSchema = z export const PublicResourceSchema = z
.object({ .object({
name: z.string().optional(), name: z.string().optional(),
protocol: z.enum(["http", "tcp", "udp"]).optional(), protocol: z
.enum(["http", "tcp", "udp", "ssh", "rdp", "vnc"])
.optional(), // this was the old one and is now DEPRECATED in favor of the mode
mode: z.enum(["http", "tcp", "udp", "ssh", "rdp", "vnc"]).optional(),
ssl: z.boolean().optional(), ssl: z.boolean().optional(),
scheme: z.enum(["http", "https"]).optional(), scheme: z.enum(["http", "https"]).optional(),
"full-domain": z.string().optional(), "full-domain": z.string().optional(),
@@ -185,9 +188,10 @@ export const ResourceSchema = z
return true; return true;
} }
// Otherwise, require name and protocol for full resource definition // Otherwise, require name and protocol/mode for full resource definition
return ( return (
resource.name !== undefined && resource.protocol !== undefined resource.name !== undefined &&
(resource.mode !== undefined || resource.protocol !== undefined)
); );
}, },
{ {
@@ -201,8 +205,8 @@ export const ResourceSchema = z
return true; return true;
} }
// If protocol is http, all targets must have method field // If protocol/mode is http, all targets must have method field
if (resource.protocol === "http") { if ((resource.mode ?? resource.protocol) === "http") {
return resource.targets.every( return resource.targets.every(
(target) => target == null || target.method !== undefined (target) => target == null || target.method !== undefined
); );
@@ -220,8 +224,9 @@ export const ResourceSchema = z
return true; return true;
} }
// If protocol is tcp or udp, no target should have method field // If protocol/mode is tcp or udp, no target should have method field
if (resource.protocol === "tcp" || resource.protocol === "udp") { const effectiveProtocol1 = resource.mode ?? resource.protocol;
if (effectiveProtocol1 === "tcp" || effectiveProtocol1 === "udp") {
return resource.targets.every( return resource.targets.every(
(target) => target == null || target.method === undefined (target) => target == null || target.method === undefined
); );
@@ -239,8 +244,8 @@ export const ResourceSchema = z
return true; return true;
} }
// If protocol is http, it must have a full-domain // If protocol/mode is http, it must have a full-domain
if (resource.protocol === "http") { if ((resource.mode ?? resource.protocol) === "http") {
return ( return (
resource["full-domain"] !== undefined && resource["full-domain"] !== undefined &&
resource["full-domain"].length > 0 resource["full-domain"].length > 0
@@ -259,8 +264,9 @@ export const ResourceSchema = z
return true; return true;
} }
// If protocol is tcp or udp, it must have both proxy-port // If protocol/mode is tcp or udp, it must have both proxy-port
if (resource.protocol === "tcp" || resource.protocol === "udp") { const effectiveProtocol2 = resource.mode ?? resource.protocol;
if (effectiveProtocol2 === "tcp" || effectiveProtocol2 === "udp") {
return resource["proxy-port"] !== undefined; return resource["proxy-port"] !== undefined;
} }
return true; return true;
@@ -277,8 +283,9 @@ export const ResourceSchema = z
return true; return true;
} }
// If protocol is tcp or udp, it must not have auth // If protocol/mode is tcp or udp, it must not have auth
if (resource.protocol === "tcp" || resource.protocol === "udp") { const effectiveProtocol3 = resource.mode ?? resource.protocol;
if (effectiveProtocol3 === "tcp" || effectiveProtocol3 === "udp") {
return resource.auth === undefined; return resource.auth === undefined;
} }
return true; return true;
@@ -340,7 +347,8 @@ export const ResourceSchema = z
if (parts.includes("*", 1)) return false; // no further wildcards if (parts.includes("*", 1)) return false; // no further wildcards
if (parts.length < 3) return false; // need at least *.label.tld if (parts.length < 3) return false; // need at least *.label.tld
const labelRegex = /^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$|^[a-zA-Z0-9]$/; const labelRegex =
/^[a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?$|^[a-zA-Z0-9]$/;
return parts.slice(1).every((label) => labelRegex.test(label)); return parts.slice(1).every((label) => labelRegex.test(label));
}, },
{ {
@@ -348,16 +356,23 @@ export const ResourceSchema = z
message: message:
'Wildcard full-domain must have "*" as the leftmost label only, followed by at least two valid hostname labels (e.g. "*.example.com" or "*.level1.example.com"). Patterns like "*example.com" or "level2.*.example.com" are not supported.' 'Wildcard full-domain must have "*" as the leftmost label only, followed by at least two valid hostname labels (e.g. "*.example.com" or "*.level1.example.com"). Patterns like "*example.com" or "level2.*.example.com" are not supported.'
} }
); )
.transform((resource) => {
// Normalize: prefer mode, fall back to protocol for backwards compatibility
if (resource.mode === undefined && resource.protocol !== undefined) {
resource.mode = resource.protocol;
}
return resource;
});
export function isTargetsOnlyResource(resource: any): boolean { export function isTargetsOnlyResource(resource: any): boolean {
return Object.keys(resource).length === 1 && resource.targets; return Object.keys(resource).length === 1 && resource.targets;
} }
export const ClientResourceSchema = z export const PrivateResourceSchema = z
.object({ .object({
name: z.string().min(1).max(255), name: z.string().min(1).max(255),
mode: z.enum(["host", "cidr", "http"]), mode: z.enum(["host", "cidr", "http", "ssh"]),
site: z.string().optional(), // DEPRECATED IN FAVOR OF sites site: z.string().optional(), // DEPRECATED IN FAVOR OF sites
sites: z.array(z.string()).optional().default([]), sites: z.array(z.string()).optional().default([]),
// protocol: z.enum(["tcp", "udp"]).optional(), // protocol: z.enum(["tcp", "udp"]).optional(),
@@ -435,19 +450,19 @@ export const ClientResourceSchema = z
export const ConfigSchema = z export const ConfigSchema = z
.object({ .object({
"proxy-resources": z "proxy-resources": z
.record(z.string(), ResourceSchema) .record(z.string(), PublicResourceSchema)
.optional() .optional()
.prefault({}), .prefault({}),
"public-resources": z "public-resources": z
.record(z.string(), ResourceSchema) .record(z.string(), PublicResourceSchema)
.optional() .optional()
.prefault({}), .prefault({}),
"client-resources": z "client-resources": z
.record(z.string(), ClientResourceSchema) .record(z.string(), PrivateResourceSchema)
.optional() .optional()
.prefault({}), .prefault({}),
"private-resources": z "private-resources": z
.record(z.string(), ClientResourceSchema) .record(z.string(), PrivateResourceSchema)
.optional() .optional()
.prefault({}), .prefault({}),
sites: z.record(z.string(), SiteSchema).optional().prefault({}) sites: z.record(z.string(), SiteSchema).optional().prefault({})
@@ -472,10 +487,13 @@ export const ConfigSchema = z
} }
return data as { return data as {
"proxy-resources": Record<string, z.infer<typeof ResourceSchema>>; "proxy-resources": Record<
string,
z.infer<typeof PublicResourceSchema>
>;
"client-resources": Record< "client-resources": Record<
string, string,
z.infer<typeof ClientResourceSchema> z.infer<typeof PrivateResourceSchema>
>; >;
sites: Record<string, z.infer<typeof SiteSchema>>; sites: Record<string, z.infer<typeof SiteSchema>>;
}; };
@@ -614,5 +632,5 @@ export const ConfigSchema = z
// Type inference from the schema // Type inference from the schema
export type Site = z.infer<typeof SiteSchema>; export type Site = z.infer<typeof SiteSchema>;
export type Target = z.infer<typeof TargetSchema>; export type Target = z.infer<typeof TargetSchema>;
export type Resource = z.infer<typeof ResourceSchema>; export type Resource = z.infer<typeof PublicResourceSchema>;
export type Config = z.infer<typeof ConfigSchema>; export type Config = z.infer<typeof ConfigSchema>;
+10 -4
View File
@@ -2,7 +2,14 @@ import { PostHog } from "posthog-node";
import config from "./config"; import config from "./config";
import { getHostMeta } from "./hostMeta"; import { getHostMeta } from "./hostMeta";
import logger from "@server/logger"; import logger from "@server/logger";
import { alertRules, apiKeys, blueprints, db, roles, siteResources } from "@server/db"; import {
alertRules,
apiKeys,
blueprints,
db,
roles,
siteResources
} from "@server/db";
import { sites, users, orgs, resources, clients, idp } from "@server/db"; import { sites, users, orgs, resources, clients, idp } from "@server/db";
import { eq, count, notInArray, and, isNotNull, isNull } from "drizzle-orm"; import { eq, count, notInArray, and, isNotNull, isNull } from "drizzle-orm";
import { APP_VERSION } from "./consts"; import { APP_VERSION } from "./consts";
@@ -143,8 +150,7 @@ class TelemetryClient {
.select({ .select({
name: resources.name, name: resources.name,
sso: resources.sso, sso: resources.sso,
protocol: resources.protocol, mode: resources.mode
http: resources.http
}) })
.from(resources); .from(resources);
@@ -311,7 +317,7 @@ class TelemetryClient {
(r) => r.sso (r) => r.sso
).length, ).length,
num_resources_non_http: stats.resources.filter( num_resources_non_http: stats.resources.filter(
(r) => !r.http (r) => r.mode !== "http"
).length, ).length,
num_newt_sites: stats.sites.filter((s) => s.type === "newt") num_newt_sites: stats.sites.filter((s) => s.type === "newt")
.length, .length,
+5 -7
View File
@@ -55,9 +55,7 @@ export async function getTraefikConfig(
resourceName: resources.name, resourceName: resources.name,
fullDomain: resources.fullDomain, fullDomain: resources.fullDomain,
ssl: resources.ssl, ssl: resources.ssl,
http: resources.http,
proxyPort: resources.proxyPort, proxyPort: resources.proxyPort,
protocol: resources.protocol,
subdomain: resources.subdomain, subdomain: resources.subdomain,
domainId: resources.domainId, domainId: resources.domainId,
enabled: resources.enabled, enabled: resources.enabled,
@@ -68,6 +66,7 @@ export async function getTraefikConfig(
headers: resources.headers, headers: resources.headers,
proxyProtocol: resources.proxyProtocol, proxyProtocol: resources.proxyProtocol,
proxyProtocolVersion: resources.proxyProtocolVersion, proxyProtocolVersion: resources.proxyProtocolVersion,
mode: resources.mode,
// Target fields // Target fields
targetId: targets.targetId, targetId: targets.targetId,
@@ -115,8 +114,8 @@ export async function getTraefikConfig(
), ),
inArray(sites.type, siteTypes), inArray(sites.type, siteTypes),
allowRawResources allowRawResources
? isNotNull(resources.http) // ignore the http check if allow_raw_resources is true ? inArray(resources.mode, ["http", "udp", "tcp"]) // allow all three
: eq(resources.http, true) : eq(resources.mode, "http")
) )
) )
.orderBy(desc(targets.priority), targets.targetId); // stable ordering .orderBy(desc(targets.priority), targets.targetId); // stable ordering
@@ -166,9 +165,8 @@ export async function getTraefikConfig(
key: key, key: key,
fullDomain: row.fullDomain, fullDomain: row.fullDomain,
ssl: row.ssl, ssl: row.ssl,
http: row.http, mode: row.mode,
proxyPort: row.proxyPort, proxyPort: row.proxyPort,
protocol: row.protocol,
subdomain: row.subdomain, subdomain: row.subdomain,
domainId: row.domainId, domainId: row.domainId,
enabled: row.enabled, enabled: row.enabled,
@@ -580,7 +578,7 @@ export async function getTraefikConfig(
continue; continue;
} }
const protocol = resource.protocol.toLowerCase(); const protocol = resource.mode === "udp" ? "udp" : "tcp"; // all of the other ones are tcp
const port = resource.proxyPort; const port = resource.proxyPort;
if (!port) { if (!port) {
@@ -944,7 +944,7 @@ export async function getTraefikConfig(
continue; continue;
} }
const protocol = resource.protocol.toLowerCase(); const protocol = resource.mode == "udp" ? "udp" : "tcp";
const port = resource.proxyPort; const port = resource.proxyPort;
if (!port) { if (!port) {
+6 -5
View File
@@ -197,7 +197,7 @@ export async function buildTargetConfigurationForNewtClient(
siteId: number, siteId: number,
version?: string | null version?: string | null
) { ) {
// Get all enabled targets with their resource protocol information // Get all enabled targets with their resource mode information
const allTargets = await db const allTargets = await db
.select({ .select({
resourceId: targets.resourceId, resourceId: targets.resourceId,
@@ -207,7 +207,7 @@ export async function buildTargetConfigurationForNewtClient(
port: targets.port, port: targets.port,
internalPort: targets.internalPort, internalPort: targets.internalPort,
enabled: targets.enabled, enabled: targets.enabled,
protocol: resources.protocol mode: resources.mode
}) })
.from(targets) .from(targets)
.innerJoin(resources, eq(targets.resourceId, resources.resourceId)) .innerJoin(resources, eq(targets.resourceId, resources.resourceId))
@@ -252,10 +252,11 @@ export async function buildTargetConfigurationForNewtClient(
const formattedTarget = `${target.internalPort}:${formatEndpoint(target.ip, target.port)}`; const formattedTarget = `${target.internalPort}:${formatEndpoint(target.ip, target.port)}`;
// Add to the appropriate protocol array // Add to the appropriate protocol array
if (target.protocol === "tcp") { if (target.mode === "udp") {
acc.tcpTargets.push(formattedTarget);
} else {
acc.udpTargets.push(formattedTarget); acc.udpTargets.push(formattedTarget);
} else {
// all other modes are tcp
acc.tcpTargets.push(formattedTarget);
} }
return acc; return acc;
@@ -94,7 +94,7 @@ export async function createResourceRule(
); );
} }
if (!resource.http) { if (!["http", "ssh", "rdp", "vnc"].includes(resource.mode)) {
return next( return next(
createHttpError( createHttpError(
HttpCode.BAD_REQUEST, HttpCode.BAD_REQUEST,
+1 -1
View File
@@ -106,7 +106,7 @@ export async function deleteResource(
// [target], // [target],
[], // deleting the target from newt causes issues because we cant unbind the port. this needs to be fixed in newt before we can do this [], // deleting the target from newt causes issues because we cant unbind the port. this needs to be fixed in newt before we can do this
healthChecksToBeRemoved, healthChecksToBeRemoved,
deletedResource.protocol, deletedResource.mode === "udp" ? "udp" : "tcp",
newt.version newt.version
); );
} }
+4 -6
View File
@@ -120,7 +120,7 @@ export async function getUserResources(
ssl: boolean; ssl: boolean;
enabled: boolean; enabled: boolean;
sso: boolean; sso: boolean;
protocol: string; mode: string;
emailWhitelistEnabled: boolean; emailWhitelistEnabled: boolean;
}> = []; }> = [];
if (accessibleResourceIds.length > 0) { if (accessibleResourceIds.length > 0) {
@@ -132,7 +132,7 @@ export async function getUserResources(
ssl: resources.ssl, ssl: resources.ssl,
enabled: resources.enabled, enabled: resources.enabled,
sso: resources.sso, sso: resources.sso,
protocol: resources.protocol, mode: resources.mode,
emailWhitelistEnabled: resources.emailWhitelistEnabled emailWhitelistEnabled: resources.emailWhitelistEnabled
}) })
.from(resources) .from(resources)
@@ -316,7 +316,7 @@ export async function getUserResources(
hasPincode || hasPincode ||
hasWhitelist hasWhitelist
), ),
protocol: resource.protocol, mode: resource.mode,
sso: resource.sso, sso: resource.sso,
password: hasPassword, password: hasPassword,
pincode: hasPincode, pincode: hasPincode,
@@ -332,7 +332,6 @@ export async function getUserResources(
name: siteResource.name, name: siteResource.name,
destination: siteResource.destination, destination: siteResource.destination,
mode: siteResource.mode, mode: siteResource.mode,
protocol: siteResource.scheme,
ssl: siteResource.ssl, ssl: siteResource.ssl,
fullDomain: siteResource.fullDomain, fullDomain: siteResource.fullDomain,
enabled: siteResource.enabled, enabled: siteResource.enabled,
@@ -380,14 +379,13 @@ export type GetUserResourcesResponse = {
domain: string; domain: string;
enabled: boolean; enabled: boolean;
protected: boolean; protected: boolean;
protocol: string; mode: string;
}>; }>;
siteResources: Array<{ siteResources: Array<{
siteResourceId: number; siteResourceId: number;
name: string; name: string;
destination: string; destination: string;
mode: string; mode: string;
protocol: string | null;
tcpPortRangeString: string | null; tcpPortRangeString: string | null;
udpPortRangeString: string | null; udpPortRangeString: string | null;
disableIcmp: boolean | null; disableIcmp: boolean | null;
+1 -1
View File
@@ -247,7 +247,7 @@ export async function updateResource(
); );
} }
if (resource.http) { if (["http", "ssh", "rdp", "vnc"].includes(resource.mode)) {
// HANDLE UPDATING HTTP RESOURCES // HANDLE UPDATING HTTP RESOURCES
return await updateHttpResource( return await updateHttpResource(
{ {
@@ -26,7 +26,9 @@ const updateResourceRuleParamsSchema = z.strictObject({
const updateResourceRuleSchema = z const updateResourceRuleSchema = z
.strictObject({ .strictObject({
action: z.enum(["ACCEPT", "DROP", "PASS"]).optional(), action: z.enum(["ACCEPT", "DROP", "PASS"]).optional(),
match: z.enum(["CIDR", "IP", "PATH", "COUNTRY", "ASN", "REGION"]).optional(), match: z
.enum(["CIDR", "IP", "PATH", "COUNTRY", "ASN", "REGION"])
.optional(),
value: z.string().min(1).optional(), value: z.string().min(1).optional(),
priority: z.int(), priority: z.int(),
enabled: z.boolean().optional() enabled: z.boolean().optional()
@@ -102,7 +104,7 @@ export async function updateResourceRule(
); );
} }
if (!resource.http) { if (!["http", "ssh", "rdp", "vnc"].includes(resource.mode)) {
return next( return next(
createHttpError( createHttpError(
HttpCode.BAD_REQUEST, HttpCode.BAD_REQUEST,
+1 -1
View File
@@ -314,7 +314,7 @@ export async function createTarget(
newt.newtId, newt.newtId,
newTarget, newTarget,
healthCheck, healthCheck,
resource.protocol, resource.mode === "udp" ? "udp" : "tcp",
newt.version newt.version
); );
} }
+1 -1
View File
@@ -126,7 +126,7 @@ export async function deleteTarget(
// [deletedTarget], // [deletedTarget],
[], // deleting the target from newt causes issues because we cant unbind the port. this needs to be fixed in newt before we can do this [], // deleting the target from newt causes issues because we cant unbind the port. this needs to be fixed in newt before we can do this
[deletedHealthCheck], [deletedHealthCheck],
resource.protocol, resource.mode === "udp" ? "udp" : "tcp",
newt.version newt.version
); );
} }
+1 -1
View File
@@ -332,7 +332,7 @@ export async function updateTarget(
newt.newtId, newt.newtId,
[updatedTarget], [updatedTarget],
[updatedHc], [updatedHc],
resource.protocol, resource.mode === "udp" ? "udp" : "tcp",
newt.version newt.version
); );
} }
@@ -146,7 +146,7 @@ function MaintenanceSectionForm({
} }
} }
if (!resource.http) { if (!["http", "ssh", "rdp", "vnc"].includes(resource.mode)) {
return null; return null;
} }
@@ -176,7 +176,9 @@ function MaintenanceSectionForm({
render={({ field }) => { render={({ field }) => {
const isDisabled = const isDisabled =
!isPaidUser(tierMatrix.maintencePage) || !isPaidUser(tierMatrix.maintencePage) ||
resource.http === false; !["http", "ssh", "rdp", "vnc"].includes(
resource.mode
);
return ( return (
<FormItem> <FormItem>
@@ -462,14 +464,14 @@ export default function GeneralForm() {
.refine( .refine(
(data) => { (data) => {
// For non-HTTP resources, proxyPort should be defined // For non-HTTP resources, proxyPort should be defined
if (!resource.http) { if (!["http", "ssh", "rdp", "vnc"].includes(resource.mode)) {
return data.proxyPort !== undefined; return data.proxyPort !== undefined;
} }
// For HTTP resources, proxyPort should be undefined // For HTTP resources, proxyPort should be undefined
return data.proxyPort === undefined; return data.proxyPort === undefined;
}, },
{ {
message: !resource.http message: !["http", "ssh", "rdp", "vnc"].includes(resource.mode)
? "Port number is required for non-HTTP resources" ? "Port number is required for non-HTTP resources"
: "Port number should not be set for HTTP resources", : "Port number should not be set for HTTP resources",
path: ["proxyPort"] path: ["proxyPort"]
@@ -623,7 +625,9 @@ export default function GeneralForm() {
/> />
</div> </div>
{!resource.http && ( {!["http", "ssh", "rdp", "vnc"].includes(
resource.mode
) && (
<> <>
<FormField <FormField
control={form.control} control={form.control}
@@ -672,7 +676,9 @@ export default function GeneralForm() {
</> </>
)} )}
{resource.http && ( {["http", "ssh", "rdp", "vnc"].includes(
resource.mode
) && (
<div className="space-y-4"> <div className="space-y-4">
<div id="resource-domain-picker"> <div id="resource-domain-picker">
<DomainPicker <DomainPicker
@@ -130,20 +130,20 @@ export default function ReverseProxyTargetsPage(props: {
<SettingsContainer> <SettingsContainer>
<ProxyResourceTargetsForm <ProxyResourceTargetsForm
orgId={params.orgId} orgId={params.orgId}
isHttp={resource.http} isHttp={["http", "ssh", "rdp", "vnc"].includes(resource.mode)}
initialTargets={remoteTargets} initialTargets={remoteTargets}
resource={resource} resource={resource}
updateResource={updateResource} updateResource={updateResource}
/> />
{resource.http && ( {["http", "ssh", "rdp", "vnc"].includes(resource.mode) && (
<ProxyResourceHttpForm <ProxyResourceHttpForm
resource={resource} resource={resource}
updateResource={updateResource} updateResource={updateResource}
/> />
)} )}
{!resource.http && resource.protocol == "tcp" && ( {resource.mode == "tcp" && (
<ProxyResourceProtocolForm <ProxyResourceProtocolForm
resource={resource} resource={resource}
updateResource={updateResource} updateResource={updateResource}
@@ -91,7 +91,7 @@ export default async function ResourceLayout(props: ResourceLayoutProps) {
} }
]; ];
if (resource.http) { if (["http", "ssh", "rdp", "vnc"].includes(resource.mode)) {
navItems.push({ navItems.push({
title: t("authentication"), title: t("authentication"),
href: `/{orgId}/settings/resources/proxy/{niceId}/authentication` href: `/{orgId}/settings/resources/proxy/{niceId}/authentication`
@@ -149,7 +149,7 @@ export default function ResourceRules(props: {
resolver: zodResolver(addRuleSchema), resolver: zodResolver(addRuleSchema),
defaultValues: { defaultValues: {
action: "ACCEPT", action: "ACCEPT",
match: resource.http && resource.mode == "http" ? "PATH" : "IP", match: resource.mode == "http" ? "PATH" : "IP",
value: "" value: ""
} }
}); });
@@ -577,7 +577,7 @@ export default function ResourceRules(props: {
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{resource.http && resource.mode == "http" && ( {resource.mode == "http" && (
<SelectItem value="PATH"> <SelectItem value="PATH">
{RuleMatch.PATH} {RuleMatch.PATH}
</SelectItem> </SelectItem>
@@ -1037,15 +1037,14 @@ export default function ResourceRules(props: {
<SelectValue /> <SelectValue />
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
{resource.http && {resource.mode ==
resource.mode == "http" && (
"http" && ( <SelectItem value="PATH">
<SelectItem value="PATH"> {
{ RuleMatch.PATH
RuleMatch.PATH }
} </SelectItem>
</SelectItem> )}
)}
<SelectItem value="IP"> <SelectItem value="IP">
{RuleMatch.IP} {RuleMatch.IP}
</SelectItem> </SelectItem>
@@ -108,11 +108,11 @@ export default async function ProxyResourcesPage(
orgId: params.orgId, orgId: params.orgId,
nice: resource.niceId, nice: resource.niceId,
domain: `${resource.ssl ? "https://" : "http://"}${toUnicode(resource.fullDomain || "")}`, domain: `${resource.ssl ? "https://" : "http://"}${toUnicode(resource.fullDomain || "")}`,
protocol: resource.protocol,
proxyPort: resource.proxyPort, proxyPort: resource.proxyPort,
http: resource.http,
labels: resource.labels, labels: resource.labels,
authState: !resource.http authState: !["http", "ssh", "rdp", "vnc"].includes(
resource.mode || ""
)
? "none" ? "none"
: resource.sso || : resource.sso ||
resource.pincodeId !== null || resource.pincodeId !== null ||
+3 -19
View File
@@ -49,7 +49,7 @@ type Resource = {
domain: string; domain: string;
enabled: boolean; enabled: boolean;
protected: boolean; protected: boolean;
protocol: string; mode: string; // "http", "tcp", "udp", "rdp", "vnc", "ssh"
// Auth method fields // Auth method fields
sso?: boolean; sso?: boolean;
password?: boolean; password?: boolean;
@@ -64,7 +64,6 @@ type SiteResource = {
name: string; name: string;
destination: string; destination: string;
mode: string; mode: string;
protocol: string | null;
ssl: boolean; ssl: boolean;
fullDomain: string | null; fullDomain: string | null;
enabled: boolean; enabled: boolean;
@@ -882,21 +881,6 @@ export default function MemberResourcesPortal({
} }
</span> </span>
</div> </div>
{siteResource.protocol && (
<div>
<span className="font-medium">
{t(
"protocol"
)}
:
</span>
<span className="ml-2 text-muted-foreground uppercase">
{
siteResource.protocol
}
</span>
</div>
)}
<div> <div>
<span className="font-medium"> <span className="font-medium">
{t( {t(
@@ -954,7 +938,7 @@ export default function MemberResourcesPortal({
siteResource.fullDomain ? ( siteResource.fullDomain ? (
/* HTTP mode - show as clickable link */ /* HTTP mode - show as clickable link */
<CopyToClipboard <CopyToClipboard
text={`${siteResource.ssl ? "https" : (siteResource.protocol ?? "http")}://${siteResource.fullDomain}`} text={`${siteResource.ssl ? "https" : (siteResource.mode ?? "http")}://${siteResource.fullDomain}`}
isLink={true} isLink={true}
/> />
) : siteResource.alias ? ( ) : siteResource.alias ? (
@@ -1037,7 +1021,7 @@ export default function MemberResourcesPortal({
<Button <Button
onClick={() => onClick={() =>
window.open( window.open(
`${siteResource.ssl ? "https" : (siteResource.protocol ?? "http")}://${siteResource.fullDomain}`, `${siteResource.ssl ? "https" : (siteResource.mode ?? "http")}://${siteResource.fullDomain}`,
"_blank" "_blank"
) )
} }
+14 -9
View File
@@ -90,8 +90,6 @@ export type ResourceRow = {
domain: string; domain: string;
mode: string | null; mode: string | null;
authState: string; authState: string;
http: boolean;
protocol: string;
proxyPort: number | null; proxyPort: number | null;
enabled: boolean; enabled: boolean;
domainId?: string; domainId?: string;
@@ -365,11 +363,11 @@ export default function ProxyResourcesTable({
const resourceRow = row.original; const resourceRow = row.original;
return ( return (
<span> <span>
{resourceRow.http {resourceRow.mode == "http"
? resourceRow.ssl ? resourceRow.ssl
? "HTTPS" ? "HTTPS"
: "HTTP" : "HTTP"
: resourceRow.protocol.toUpperCase()} : resourceRow.mode?.toUpperCase()}
</span> </span>
); );
} }
@@ -412,7 +410,7 @@ export default function ProxyResourcesTable({
), ),
cell: ({ row }) => { cell: ({ row }) => {
const resourceRow = row.original; const resourceRow = row.original;
if (!resourceRow.http || resourceRow.mode !== "http") { if (resourceRow.mode !== "http") {
return <span>-</span>; return <span>-</span>;
} }
return ( return (
@@ -443,7 +441,7 @@ export default function ProxyResourcesTable({
header: () => <span className="p-3">{t("uptime30d")}</span>, header: () => <span className="p-3">{t("uptime30d")}</span>,
cell: ({ row }) => { cell: ({ row }) => {
const resourceRow = row.original; const resourceRow = row.original;
if (!resourceRow.http || resourceRow.mode !== "http") { if (resourceRow.mode !== "http") {
return <span>-</span>; return <span>-</span>;
} }
return ( return (
@@ -458,7 +456,11 @@ export default function ProxyResourcesTable({
cell: ({ row }) => { cell: ({ row }) => {
const resourceRow = row.original; const resourceRow = row.original;
if (!resourceRow.http) { if (
!["http", "ssh", "rdp", "vnc"].includes(
resourceRow.mode || ""
)
) {
return ( return (
<div className="flex items-center gap-2 min-w-0"> <div className="flex items-center gap-2 min-w-0">
<CopyToClipboard <CopyToClipboard
@@ -975,7 +977,7 @@ function ResourceEnabledForm({
resource, resource,
onToggleResourceEnabled onToggleResourceEnabled
}: ResourceEnabledFormProps) { }: ResourceEnabledFormProps) {
const enabled = resource.http const enabled = ["http", "ssh", "rdp", "vnc"].includes(resource.mode || "")
? !!resource.domainId && resource.enabled ? !!resource.domainId && resource.enabled
: resource.enabled; : resource.enabled;
const [optimisticEnabled, setOptimisticEnabled] = useOptimistic(enabled); const [optimisticEnabled, setOptimisticEnabled] = useOptimistic(enabled);
@@ -993,7 +995,10 @@ function ResourceEnabledForm({
<Switch <Switch
checked={optimisticEnabled} checked={optimisticEnabled}
disabled={ disabled={
(resource.http && !resource.domainId) || (["http", "ssh", "rdp", "vnc"].includes(
resource.mode || ""
) &&
!resource.domainId) ||
optimisticEnabled !== enabled optimisticEnabled !== enabled
} }
name="enabled" name="enabled"
+6 -4
View File
@@ -31,12 +31,14 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
const fullUrl = `${resource.ssl ? "https" : "http"}://${toUnicode(resource.fullDomain || "")}`; const fullUrl = `${resource.ssl ? "https" : "http"}://${toUnicode(resource.fullDomain || "")}`;
const showCertificate = !!( const showCertificate = !!(
resource.http && ["http", "ssh", "rdp", "vnc"].includes(resource.mode) &&
resource.domainId && resource.domainId &&
resource.fullDomain && resource.fullDomain &&
build != "oss" build != "oss"
); );
const showType = !!(resource.http && resource.mode); const showType = !!(
["http", "ssh", "rdp", "vnc"].includes(resource.mode) && resource.mode
);
const showHealth = const showHealth =
!["ssh", "rdp", "vnc"].includes(resource.mode || "") && !["ssh", "rdp", "vnc"].includes(resource.mode || "") &&
!!resource.health && !!resource.health &&
@@ -64,7 +66,7 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
</span> </span>
</InfoSectionContent> </InfoSectionContent>
</InfoSection> */} </InfoSection> */}
{resource.http ? ( {["http", "ssh", "rdp", "vnc"].includes(resource.mode) ? (
<> <>
<InfoSection> <InfoSection>
<InfoSectionTitle>URL</InfoSectionTitle> <InfoSectionTitle>URL</InfoSectionTitle>
@@ -124,7 +126,7 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) {
</InfoSectionTitle> </InfoSectionTitle>
<InfoSectionContent> <InfoSectionContent>
<span className="inline-flex items-center"> <span className="inline-flex items-center">
{resource.protocol.toUpperCase()} {resource.mode?.toUpperCase()}
</span> </span>
</InfoSectionContent> </InfoSectionContent>
</InfoSection> </InfoSection>
+4 -4
View File
@@ -44,13 +44,13 @@ function isSafeUrlForLink(href: string): boolean {
const OVERVIEW_META_CLASS = "w-full min-w-0 text-muted-foreground text-sm"; const OVERVIEW_META_CLASS = "w-full min-w-0 text-muted-foreground text-sm";
function publicProtocolLabel(r: PublicResourceRow): string { function publicProtocolLabel(r: PublicResourceRow): string {
if (r.http) { if (r.mode == "http") {
return r.ssl ? "HTTPS" : "HTTP"; return r.ssl ? "HTTPS" : "HTTP";
} }
const p = (r.protocol || "").toLowerCase(); const p = (r.mode || "").toLowerCase();
if (p === "tcp") return "TCP"; if (p === "tcp") return "TCP";
if (p === "udp") return "UDP"; if (p === "udp") return "UDP";
return (r.protocol || "—").toUpperCase(); return (r.mode || "—").toUpperCase();
} }
function PublicResourceMeta({ resource: r }: { resource: PublicResourceRow }) { function PublicResourceMeta({ resource: r }: { resource: PublicResourceRow }) {
@@ -91,7 +91,7 @@ function PrivateResourceMeta({ row }: { row: SiteResourceRow }) {
function PublicAccessMethod({ resource: r }: { resource: PublicResourceRow }) { function PublicAccessMethod({ resource: r }: { resource: PublicResourceRow }) {
const t = useTranslations(); const t = useTranslations();
if (!r.http) { if (!["http", "ssh", "rdp", "vnc"].includes(r.mode || "")) {
return ( return (
<CopyToClipboard <CopyToClipboard
text={r.proxyPort?.toString() ?? ""} text={r.proxyPort?.toString() ?? ""}