mirror of
https://github.com/fosrl/pangolin.git
synced 2026-09-19 17:19:58 +02:00
♻️ Add ssl field to redirects and add traefik config to public file
This commit is contained in:
+3
-1
@@ -4452,5 +4452,7 @@
|
|||||||
"redirectDomainRequired": "Select a domain to attach this redirect to",
|
"redirectDomainRequired": "Select a domain to attach this redirect to",
|
||||||
"redirectResourceRequired": "Select a resource to attach this redirect to",
|
"redirectResourceRequired": "Select a resource to attach this redirect to",
|
||||||
"redirectPermanent": "Permanent Redirect",
|
"redirectPermanent": "Permanent Redirect",
|
||||||
"redirectPermanentDescription": "Respond with 308 instead of 307. Permanent redirects are cached by browsers."
|
"redirectPermanentDescription": "Respond with 308 instead of 307. Permanent redirects are cached by browsers.",
|
||||||
|
"redirectSslDescription": "Serve this redirect over HTTPS. Turn off to match requests on plain HTTP.",
|
||||||
|
"redirectSslInheritedDescription": "Inherited from the attached resource's TLS setting."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -256,6 +256,9 @@ export const redirects = pgTable("redirects", {
|
|||||||
>(), // exact, prefix, regex, stripPrefix
|
>(), // exact, prefix, regex, stripPrefix
|
||||||
priority: integer("priority").default(100),
|
priority: integer("priority").default(100),
|
||||||
permanent: boolean("permanent").notNull().default(false),
|
permanent: boolean("permanent").notNull().default(false),
|
||||||
|
// Only consulted for domain-attached redirects; resource-attached ones
|
||||||
|
// inherit the resource's ssl setting.
|
||||||
|
ssl: boolean("ssl").notNull().default(true),
|
||||||
enabled: boolean("enabled").notNull().default(true)
|
enabled: boolean("enabled").notNull().default(true)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -274,6 +274,9 @@ export const redirects = sqliteTable("redirects", {
|
|||||||
permanent: integer("permanent", { mode: "boolean" })
|
permanent: integer("permanent", { mode: "boolean" })
|
||||||
.notNull()
|
.notNull()
|
||||||
.default(false),
|
.default(false),
|
||||||
|
// Only consulted for domain-attached redirects; resource-attached ones
|
||||||
|
// inherit the resource's ssl setting.
|
||||||
|
ssl: integer("ssl", { mode: "boolean" }).notNull().default(true),
|
||||||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true)
|
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
db,
|
db,
|
||||||
domains,
|
domains,
|
||||||
exitNodes,
|
exitNodes,
|
||||||
|
redirects,
|
||||||
resources,
|
resources,
|
||||||
siteNetworks,
|
siteNetworks,
|
||||||
siteResources,
|
siteResources,
|
||||||
@@ -45,6 +46,7 @@ import {
|
|||||||
buildTcpUdpLoadBalancerServers
|
buildTcpUdpLoadBalancerServers
|
||||||
} from "./loadBalancer";
|
} from "./loadBalancer";
|
||||||
import { applyPathRewriteMiddleware } from "./middleware";
|
import { applyPathRewriteMiddleware } from "./middleware";
|
||||||
|
import { buildRedirectConfig, RedirectRouteRow } from "./redirect";
|
||||||
import { appendPathMatch, buildHostRule, computeRoutePriority } from "./rule";
|
import { appendPathMatch, buildHostRule, computeRoutePriority } from "./rule";
|
||||||
import { buildSiteResourceAliasCertPlaceholders } from "./siteResourceAlias";
|
import { buildSiteResourceAliasCertPlaceholders } from "./siteResourceAlias";
|
||||||
import { TargetWithSite } from "./types";
|
import { TargetWithSite } from "./types";
|
||||||
@@ -326,12 +328,87 @@ export async function getTraefikConfig(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Redirects have no targets/sites, so like inference resources they are
|
||||||
|
// queried separately and emitted on every exit node. A redirect listens
|
||||||
|
// either on a resource's fullDomain or on subdomain.baseDomain of a
|
||||||
|
// domain; the domain join resolves to whichever one applies.
|
||||||
|
const redirectRows = await db
|
||||||
|
.select({
|
||||||
|
name: redirects.name,
|
||||||
|
enabled: redirects.enabled,
|
||||||
|
redirectId: redirects.redirectId,
|
||||||
|
subdomain: redirects.subdomain,
|
||||||
|
matchPath: redirects.matchPath,
|
||||||
|
pathMatchType: redirects.pathMatchType,
|
||||||
|
priority: redirects.priority,
|
||||||
|
ssl: redirects.ssl,
|
||||||
|
// Resource (when attached to one)
|
||||||
|
resourceId: resources.resourceId,
|
||||||
|
resourceFullDomain: resources.fullDomain,
|
||||||
|
resourceSubdomain: resources.subdomain,
|
||||||
|
resourceSsl: resources.ssl,
|
||||||
|
resourceWildcard: resources.wildcard,
|
||||||
|
// Domain (the redirect's own, or the resource's)
|
||||||
|
baseDomain: domains.baseDomain,
|
||||||
|
domainCertResolver: domains.certResolver,
|
||||||
|
preferWildcardCert: domains.preferWildcardCert
|
||||||
|
})
|
||||||
|
.from(redirects)
|
||||||
|
.leftJoin(resources, eq(resources.resourceId, redirects.resourceId))
|
||||||
|
.leftJoin(
|
||||||
|
domains,
|
||||||
|
eq(
|
||||||
|
domains.domainId,
|
||||||
|
sql`coalesce(${redirects.domainId}, ${resources.domainId})`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(redirects.enabled, true),
|
||||||
|
or(isNull(redirects.resourceId), eq(resources.enabled, true))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.orderBy(desc(redirects.priority), redirects.redirectId); // stable ordering
|
||||||
|
|
||||||
|
const redirectRoutes: RedirectRouteRow[] = [];
|
||||||
|
for (const row of redirectRows) {
|
||||||
|
const attachedToResource = row.resourceId !== null;
|
||||||
|
const fullDomain = attachedToResource
|
||||||
|
? row.resourceFullDomain
|
||||||
|
: [row.subdomain, row.baseDomain].filter(Boolean).join(".");
|
||||||
|
if (!fullDomain) {
|
||||||
|
logger.debug(
|
||||||
|
`Redirect ${row.redirectId} has no host to listen on, skipping Traefik config`
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
redirectRoutes.push({
|
||||||
|
enabled: row.enabled,
|
||||||
|
name: sanitize(row.name) || "",
|
||||||
|
redirectId: row.redirectId,
|
||||||
|
fullDomain,
|
||||||
|
hasSubdomain: attachedToResource
|
||||||
|
? !!row.resourceSubdomain
|
||||||
|
: !!row.subdomain,
|
||||||
|
wildcard: row.resourceWildcard,
|
||||||
|
ssl: attachedToResource ? !!row.resourceSsl : row.ssl,
|
||||||
|
attachedTo: attachedToResource ? "resource" : "domain",
|
||||||
|
matchPath: row.matchPath,
|
||||||
|
pathMatchType: row.pathMatchType,
|
||||||
|
priority: row.priority,
|
||||||
|
domainCertResolver: row.domainCertResolver,
|
||||||
|
preferWildcardCert: row.preferWildcardCert
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// make sure we have at least one resource
|
// make sure we have at least one resource
|
||||||
if (
|
if (
|
||||||
resourcesMap.size === 0 &&
|
resourcesMap.size === 0 &&
|
||||||
inferenceResources.length === 0 &&
|
inferenceResources.length === 0 &&
|
||||||
browserGatewayResourcesMap.size === 0 &&
|
browserGatewayResourcesMap.size === 0 &&
|
||||||
siteResourcesWithFullDomain.length === 0
|
siteResourcesWithFullDomain.length === 0 &&
|
||||||
|
redirectRoutes.length === 0
|
||||||
) {
|
) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
@@ -518,6 +595,21 @@ export async function getTraefikConfig(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildRedirectConfig({
|
||||||
|
config_output,
|
||||||
|
redirects: redirectRoutes,
|
||||||
|
badgerMiddlewareName,
|
||||||
|
redirectHttpsMiddlewareName,
|
||||||
|
resolveTls: (redirect) =>
|
||||||
|
buildWildcardTls({
|
||||||
|
fullDomain: redirect.fullDomain,
|
||||||
|
hasSubdomain: redirect.hasSubdomain,
|
||||||
|
domainCertResolver: redirect.domainCertResolver,
|
||||||
|
preferWildcardCert:
|
||||||
|
redirect.preferWildcardCert || redirect.wildcard
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
if (browserGatewayUiUrl) {
|
if (browserGatewayUiUrl) {
|
||||||
buildBrowserGatewayConfig({
|
buildBrowserGatewayConfig({
|
||||||
config_output,
|
config_output,
|
||||||
|
|||||||
@@ -414,6 +414,7 @@ export async function getTraefikConfig(
|
|||||||
matchPath: redirects.matchPath,
|
matchPath: redirects.matchPath,
|
||||||
pathMatchType: redirects.pathMatchType,
|
pathMatchType: redirects.pathMatchType,
|
||||||
priority: redirects.priority,
|
priority: redirects.priority,
|
||||||
|
ssl: redirects.ssl,
|
||||||
// Resource (when attached to one)
|
// Resource (when attached to one)
|
||||||
resourceId: resources.resourceId,
|
resourceId: resources.resourceId,
|
||||||
resourceFullDomain: resources.fullDomain,
|
resourceFullDomain: resources.fullDomain,
|
||||||
@@ -473,8 +474,7 @@ export async function getTraefikConfig(
|
|||||||
? !!row.resourceSubdomain
|
? !!row.resourceSubdomain
|
||||||
: !!row.subdomain,
|
: !!row.subdomain,
|
||||||
wildcard: row.resourceWildcard,
|
wildcard: row.resourceWildcard,
|
||||||
// Domain-attached redirects always get a certificate on creation
|
ssl: attachedToResource ? !!row.resourceSsl : row.ssl,
|
||||||
ssl: attachedToResource ? !!row.resourceSsl : true,
|
|
||||||
attachedTo: attachedToResource ? "resource" : "domain",
|
attachedTo: attachedToResource ? "resource" : "domain",
|
||||||
matchPath: row.matchPath,
|
matchPath: row.matchPath,
|
||||||
pathMatchType: row.pathMatchType,
|
pathMatchType: row.pathMatchType,
|
||||||
@@ -484,14 +484,6 @@ export async function getTraefikConfig(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
console.dir(
|
|
||||||
{
|
|
||||||
redirectRoutes,
|
|
||||||
redirectRows
|
|
||||||
},
|
|
||||||
{ depth: null }
|
|
||||||
);
|
|
||||||
|
|
||||||
// Pangolin-managed DNS-01/ACME cert mode requires either a tier1
|
// Pangolin-managed DNS-01/ACME cert mode requires either a tier1
|
||||||
// license (self-hosted) or a saas build - otherwise fall back to
|
// license (self-hosted) or a saas build - otherwise fall back to
|
||||||
// Traefik's own cert resolvers (buildWildcardTls) throughout.
|
// Traefik's own cert resolvers (buildWildcardTls) throughout.
|
||||||
|
|||||||
@@ -1091,7 +1091,7 @@ function buildRedirectUrl(
|
|||||||
function redirected(res: Response, redirectUrl: string, permanent: boolean) {
|
function redirected(res: Response, redirectUrl: string, permanent: boolean) {
|
||||||
const data = {
|
const data = {
|
||||||
data: {
|
data: {
|
||||||
valid: false,
|
valid: true,
|
||||||
redirectUrl,
|
redirectUrl,
|
||||||
redirectPermanent: permanent,
|
redirectPermanent: permanent,
|
||||||
pangolinVersion: APP_VERSION
|
pangolinVersion: APP_VERSION
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import {
|
|||||||
redirectPathMatchTypeSchema,
|
redirectPathMatchTypeSchema,
|
||||||
redirectRewritePathSchema,
|
redirectRewritePathSchema,
|
||||||
isValidMatchPath,
|
isValidMatchPath,
|
||||||
|
isAllowedSsl,
|
||||||
redirectPrioritySchema,
|
redirectPrioritySchema,
|
||||||
redirectRewritePathTypeSchema
|
redirectRewritePathTypeSchema
|
||||||
} from "@server/routers/redirect/validation";
|
} from "@server/routers/redirect/validation";
|
||||||
@@ -42,6 +43,7 @@ const bodySchema = z
|
|||||||
rewritePathType: redirectRewritePathTypeSchema.optional().nullable(),
|
rewritePathType: redirectRewritePathTypeSchema.optional().nullable(),
|
||||||
priority: redirectPrioritySchema.optional().nullable(),
|
priority: redirectPrioritySchema.optional().nullable(),
|
||||||
permanent: z.boolean().optional(),
|
permanent: z.boolean().optional(),
|
||||||
|
ssl: z.boolean().optional(),
|
||||||
enabled: z.boolean().optional()
|
enabled: z.boolean().optional()
|
||||||
})
|
})
|
||||||
.refine(
|
.refine(
|
||||||
@@ -64,6 +66,10 @@ const bodySchema = z
|
|||||||
.refine((data) => isValidMatchPath(data.matchPath, data.pathMatchType), {
|
.refine((data) => isValidMatchPath(data.matchPath, data.pathMatchType), {
|
||||||
message: "matchPath must be a valid regular expression",
|
message: "matchPath must be a valid regular expression",
|
||||||
path: ["matchPath"]
|
path: ["matchPath"]
|
||||||
|
})
|
||||||
|
.refine((data) => isAllowedSsl(data.ssl), {
|
||||||
|
message: "TLS cannot be disabled on this build",
|
||||||
|
path: ["ssl"]
|
||||||
});
|
});
|
||||||
|
|
||||||
registry.registerPath({
|
registry.registerPath({
|
||||||
@@ -127,6 +133,7 @@ export async function createRedirect(
|
|||||||
rewritePathType,
|
rewritePathType,
|
||||||
priority,
|
priority,
|
||||||
permanent,
|
permanent,
|
||||||
|
ssl,
|
||||||
enabled
|
enabled
|
||||||
} = parsedBody.data;
|
} = parsedBody.data;
|
||||||
|
|
||||||
@@ -200,6 +207,9 @@ export async function createRedirect(
|
|||||||
rewritePathType: rewritePathType ?? null,
|
rewritePathType: rewritePathType ?? null,
|
||||||
priority: priority ?? 100,
|
priority: priority ?? 100,
|
||||||
permanent: permanent ?? false,
|
permanent: permanent ?? false,
|
||||||
|
// Resource-attached redirects follow the resource's ssl, so
|
||||||
|
// the column is only meaningful for domain-attached ones.
|
||||||
|
ssl: resource ? true : (ssl ?? true),
|
||||||
enabled: enabled ?? true
|
enabled: enabled ?? true
|
||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export type GetRedirectResponse = {
|
|||||||
rewritePathType: "exact" | "prefix" | "regex" | "stripPrefix" | null;
|
rewritePathType: "exact" | "prefix" | "regex" | "stripPrefix" | null;
|
||||||
priority: number | null;
|
priority: number | null;
|
||||||
permanent: boolean;
|
permanent: boolean;
|
||||||
|
ssl: boolean;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
resourceId: number | null;
|
resourceId: number | null;
|
||||||
resourceName: string | null;
|
resourceName: string | null;
|
||||||
@@ -49,6 +50,7 @@ const redirectColumns = {
|
|||||||
rewritePathType: redirects.rewritePathType,
|
rewritePathType: redirects.rewritePathType,
|
||||||
priority: redirects.priority,
|
priority: redirects.priority,
|
||||||
permanent: redirects.permanent,
|
permanent: redirects.permanent,
|
||||||
|
ssl: redirects.ssl,
|
||||||
enabled: redirects.enabled,
|
enabled: redirects.enabled,
|
||||||
resourceId: redirects.resourceId,
|
resourceId: redirects.resourceId,
|
||||||
resourceName: resources.name,
|
resourceName: resources.name,
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export type ListRedirectsResponse = PaginatedResponse<{
|
|||||||
rewritePathType: "exact" | "prefix" | "regex" | "stripPrefix" | null;
|
rewritePathType: "exact" | "prefix" | "regex" | "stripPrefix" | null;
|
||||||
priority: number | null;
|
priority: number | null;
|
||||||
permanent: boolean;
|
permanent: boolean;
|
||||||
|
ssl: boolean;
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
resourceId: number | null;
|
resourceId: number | null;
|
||||||
resourceName: string | null;
|
resourceName: string | null;
|
||||||
@@ -148,6 +149,7 @@ export async function listRedirects(
|
|||||||
rewritePathType: redirects.rewritePathType,
|
rewritePathType: redirects.rewritePathType,
|
||||||
priority: redirects.priority,
|
priority: redirects.priority,
|
||||||
permanent: redirects.permanent,
|
permanent: redirects.permanent,
|
||||||
|
ssl: redirects.ssl,
|
||||||
enabled: redirects.enabled,
|
enabled: redirects.enabled,
|
||||||
resourceId: redirects.resourceId,
|
resourceId: redirects.resourceId,
|
||||||
resourceName: resources.name,
|
resourceName: resources.name,
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ import {
|
|||||||
redirectRewritePathSchema,
|
redirectRewritePathSchema,
|
||||||
redirectRewritePathTypeSchema,
|
redirectRewritePathTypeSchema,
|
||||||
redirectPrioritySchema,
|
redirectPrioritySchema,
|
||||||
isValidMatchPath
|
isValidMatchPath,
|
||||||
|
isAllowedSsl
|
||||||
} from "@server/routers/redirect/validation";
|
} from "@server/routers/redirect/validation";
|
||||||
import { createCertificate } from "../certificates";
|
import { createCertificate } from "../certificates";
|
||||||
|
|
||||||
@@ -43,6 +44,7 @@ const bodySchema = z.strictObject({
|
|||||||
rewritePathType: redirectRewritePathTypeSchema.optional().nullable(),
|
rewritePathType: redirectRewritePathTypeSchema.optional().nullable(),
|
||||||
priority: redirectPrioritySchema.optional(),
|
priority: redirectPrioritySchema.optional(),
|
||||||
permanent: z.boolean().optional(),
|
permanent: z.boolean().optional(),
|
||||||
|
ssl: z.boolean().optional(),
|
||||||
enabled: z.boolean().optional()
|
enabled: z.boolean().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -225,6 +227,15 @@ export async function updateRedirect(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isAllowedSsl(body.ssl)) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
"TLS cannot be disabled on this build"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const updateData: Partial<typeof redirects.$inferInsert> = {};
|
const updateData: Partial<typeof redirects.$inferInsert> = {};
|
||||||
|
|
||||||
if (body.name !== undefined) {
|
if (body.name !== undefined) {
|
||||||
@@ -263,6 +274,13 @@ export async function updateRedirect(
|
|||||||
if (body.permanent !== undefined) {
|
if (body.permanent !== undefined) {
|
||||||
updateData.permanent = body.permanent;
|
updateData.permanent = body.permanent;
|
||||||
}
|
}
|
||||||
|
if (effectiveResourceId) {
|
||||||
|
// Resource-attached redirects follow the resource's ssl; reset
|
||||||
|
// the column so a later move back to a domain starts from TLS on.
|
||||||
|
updateData.ssl = true;
|
||||||
|
} else if (body.ssl !== undefined) {
|
||||||
|
updateData.ssl = body.ssl;
|
||||||
|
}
|
||||||
if (body.enabled !== undefined) {
|
if (body.enabled !== undefined) {
|
||||||
updateData.enabled = body.enabled;
|
updateData.enabled = body.enabled;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { isValidDomain } from "@server/lib/validators";
|
import { isValidDomain } from "@server/lib/validators";
|
||||||
|
import { build } from "@server/build";
|
||||||
|
|
||||||
export const redirectNiceIdSchema = z
|
export const redirectNiceIdSchema = z
|
||||||
.string()
|
.string()
|
||||||
@@ -54,3 +55,12 @@ export const redirectDestinationDomainSchema = z
|
|||||||
.refine(isValidDomain, {
|
.refine(isValidDomain, {
|
||||||
message: "Invalid domain"
|
message: "Invalid domain"
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The cloud only serves HTTPS, so a domain-attached redirect may not opt out
|
||||||
|
* of TLS there. Resource-attached redirects inherit the resource's ssl
|
||||||
|
* setting and never carry their own.
|
||||||
|
*/
|
||||||
|
export function isAllowedSsl(ssl: boolean | undefined): boolean {
|
||||||
|
return build !== "saas" || ssl !== false;
|
||||||
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import { toast } from "@app/hooks/useToast";
|
|||||||
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
||||||
import { isValidDomain } from "@server/lib/validators";
|
import { isValidDomain } from "@server/lib/validators";
|
||||||
import { isValidRegex } from "@server/routers/redirect/validation";
|
import { isValidRegex } from "@server/routers/redirect/validation";
|
||||||
|
import { build } from "@server/build";
|
||||||
import { cn } from "@app/lib/cn";
|
import { cn } from "@app/lib/cn";
|
||||||
import { CaretSortIcon } from "@radix-ui/react-icons";
|
import { CaretSortIcon } from "@radix-ui/react-icons";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
@@ -139,6 +140,7 @@ export default function RedirectForm({
|
|||||||
.min(1, { message: t("redirectPriorityInvalid") })
|
.min(1, { message: t("redirectPriorityInvalid") })
|
||||||
.max(1000, { message: t("redirectPriorityInvalid") }),
|
.max(1000, { message: t("redirectPriorityInvalid") }),
|
||||||
permanent: z.boolean(),
|
permanent: z.boolean(),
|
||||||
|
ssl: z.boolean(),
|
||||||
enabled: z.boolean()
|
enabled: z.boolean()
|
||||||
})
|
})
|
||||||
.superRefine((data, ctx) => {
|
.superRefine((data, ctx) => {
|
||||||
@@ -201,6 +203,7 @@ export default function RedirectForm({
|
|||||||
rewritePathType: redirect?.rewritePathType ?? null,
|
rewritePathType: redirect?.rewritePathType ?? null,
|
||||||
priority: redirect?.priority ?? DEFAULT_PRIORITY,
|
priority: redirect?.priority ?? DEFAULT_PRIORITY,
|
||||||
permanent: redirect?.permanent ?? false,
|
permanent: redirect?.permanent ?? false,
|
||||||
|
ssl: redirect?.ssl ?? true,
|
||||||
enabled: redirect?.enabled ?? true
|
enabled: redirect?.enabled ?? true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -257,6 +260,8 @@ export default function RedirectForm({
|
|||||||
rewritePathType: values.rewritePathType,
|
rewritePathType: values.rewritePathType,
|
||||||
priority: values.priority,
|
priority: values.priority,
|
||||||
permanent: values.permanent,
|
permanent: values.permanent,
|
||||||
|
// Resource-attached redirects inherit the resource's ssl setting
|
||||||
|
ssl: values.attachTo === "domain" ? values.ssl : true,
|
||||||
enabled: values.enabled
|
enabled: values.enabled
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -581,6 +586,53 @@ export default function RedirectForm({
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
</SettingsFormCell>
|
</SettingsFormCell>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* The cloud only serves HTTPS, so there is nothing to toggle there. */}
|
||||||
|
{build !== "saas" && (
|
||||||
|
<SettingsFormCell span="full">
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="ssl"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormControl>
|
||||||
|
<SwitchInput
|
||||||
|
id="redirect-ssl"
|
||||||
|
label={t(
|
||||||
|
"proxyEnableSSL"
|
||||||
|
)}
|
||||||
|
description={
|
||||||
|
attachTo ===
|
||||||
|
"resource"
|
||||||
|
? t(
|
||||||
|
"redirectSslInheritedDescription"
|
||||||
|
)
|
||||||
|
: t(
|
||||||
|
"redirectSslDescription"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
disabled={
|
||||||
|
attachTo ===
|
||||||
|
"resource"
|
||||||
|
}
|
||||||
|
checked={
|
||||||
|
attachTo ===
|
||||||
|
"resource"
|
||||||
|
? (selectedResource?.ssl ??
|
||||||
|
true)
|
||||||
|
: field.value
|
||||||
|
}
|
||||||
|
onCheckedChange={
|
||||||
|
field.onChange
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SettingsFormCell>
|
||||||
|
)}
|
||||||
</SettingsFormGrid>
|
</SettingsFormGrid>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
Reference in New Issue
Block a user