Fixing visual issues

This commit is contained in:
Owen
2026-05-31 16:36:13 -07:00
parent c1d933259a
commit cb2ee9c489
3 changed files with 151 additions and 68 deletions

View File

@@ -291,6 +291,12 @@ export async function getTraefikConfig(
domainCertResolver: domains.certResolver, domainCertResolver: domains.certResolver,
preferWildcardCert: domains.preferWildcardCert, preferWildcardCert: domains.preferWildcardCert,
domainNamespaceId: domainNamespaces.domainNamespaceId, domainNamespaceId: domainNamespaces.domainNamespaceId,
// Maintenance fields
maintenanceModeEnabled: resources.maintenanceModeEnabled,
maintenanceModeType: resources.maintenanceModeType,
maintenanceTitle: resources.maintenanceTitle,
maintenanceMessage: resources.maintenanceMessage,
maintenanceEstimatedTime: resources.maintenanceEstimatedTime,
// Browser gateway target fields // Browser gateway target fields
browserGatewayTargetId: browserGatewayTarget.browserGatewayTargetId, browserGatewayTargetId: browserGatewayTarget.browserGatewayTargetId,
bgType: browserGatewayTarget.type, bgType: browserGatewayTarget.type,
@@ -340,6 +346,11 @@ export async function getTraefikConfig(
wildcard: boolean | null; wildcard: boolean | null;
domainCertResolver: string | null; domainCertResolver: string | null;
preferWildcardCert: boolean | null; preferWildcardCert: boolean | null;
maintenanceModeEnabled: boolean | null;
maintenanceModeType: string | null;
maintenanceTitle: string | null;
maintenanceMessage: string | null;
maintenanceEstimatedTime: string | null;
targets: { targets: {
browserGatewayTargetId: number; browserGatewayTargetId: number;
bgType: string; bgType: string;
@@ -371,6 +382,11 @@ export async function getTraefikConfig(
wildcard: row.wildcard, wildcard: row.wildcard,
domainCertResolver: row.domainCertResolver, domainCertResolver: row.domainCertResolver,
preferWildcardCert: row.preferWildcardCert, preferWildcardCert: row.preferWildcardCert,
maintenanceModeEnabled: row.maintenanceModeEnabled,
maintenanceModeType: row.maintenanceModeType,
maintenanceTitle: row.maintenanceTitle,
maintenanceMessage: row.maintenanceMessage,
maintenanceEstimatedTime: row.maintenanceEstimatedTime,
targets: [] targets: []
}); });
} }
@@ -1118,6 +1134,75 @@ export async function getTraefikConfig(
// Collect online sites for this resource (for any type) // Collect online sites for this resource (for any type)
const anySiteOnline = bgResource.targets.some((t) => t.siteOnline); const anySiteOnline = bgResource.targets.some((t) => t.siteOnline);
// Maintenance page logic for browser gateway resources
let showBgMaintenancePage = false;
if (bgResource.maintenanceModeEnabled) {
if (bgResource.maintenanceModeType === "forced") {
showBgMaintenancePage = true;
} else if (bgResource.maintenanceModeType === "automatic") {
showBgMaintenancePage = !anySiteOnline;
}
}
if (showBgMaintenancePage && allowMaintenancePage) {
const bgMaintenanceServiceName = `bg-r${bgResource.resourceId}-maintenance-service`;
const bgMaintenanceRouterName = `bg-r${bgResource.resourceId}-maintenance-router`;
const bgRewriteMiddlewareName = `bg-r${bgResource.resourceId}-maintenance-rewrite`;
const entrypointHttp =
config.getRawConfig().traefik.http_entrypoint;
const entrypointHttps =
config.getRawConfig().traefik.https_entrypoint;
const maintenancePort = config.getRawConfig().server.next_port;
const maintenanceHost =
config.getRawConfig().server.internal_hostname;
if (!config_output.http.services) config_output.http.services = {};
if (!config_output.http.middlewares)
config_output.http.middlewares = {};
if (!config_output.http.routers) config_output.http.routers = {};
config_output.http.services![bgMaintenanceServiceName] = {
loadBalancer: {
servers: [
{ url: `http://${maintenanceHost}:${maintenancePort}` }
],
passHostHeader: true
}
};
config_output.http.middlewares![bgRewriteMiddlewareName] = {
replacePathRegex: {
regex: "^/(.*)",
replacement: "/maintenance-screen"
}
};
config_output.http.routers![bgMaintenanceRouterName] = {
entryPoints: [
bgResource.ssl ? entrypointHttps : entrypointHttp
],
service: bgMaintenanceServiceName,
middlewares: [bgRewriteMiddlewareName],
rule: hostRule,
priority: 2000,
...(bgResource.ssl ? { tls } : {})
};
config_output.http.routers![`${bgMaintenanceRouterName}-assets`] = {
entryPoints: [
bgResource.ssl ? entrypointHttps : entrypointHttp
],
service: bgMaintenanceServiceName,
rule: `${hostRule} && (PathPrefix(\`/_next\`) || PathRegexp(\`^/__nextjs*\`) || Path(\`/favicon.ico\`))`,
priority: 2001,
...(bgResource.ssl ? { tls } : {})
};
continue;
}
// Group targets by type and generate per-type websocket routers and services // Group targets by type and generate per-type websocket routers and services
const typeMap = new Map<string, typeof bgResource.targets>(); const typeMap = new Map<string, typeof bgResource.targets>();
for (const t of bgResource.targets) { for (const t of bgResource.targets) {

View File

@@ -12,6 +12,7 @@ import {
userSites, userSites,
labels, labels,
siteLabels, siteLabels,
browserGatewayTarget,
type Label type Label
} from "@server/db"; } from "@server/db";
import cache from "#dynamic/lib/cache"; import cache from "#dynamic/lib/cache";
@@ -240,6 +241,10 @@ function querySitesBase() {
ON ${siteResources.networkId} = ${siteNetworks.networkId} ON ${siteResources.networkId} = ${siteNetworks.networkId}
WHERE ${siteNetworks.siteId} = ${sites.siteId} WHERE ${siteNetworks.siteId} = ${sites.siteId}
AND ${siteResources.orgId} = ${sites.orgId} AND ${siteResources.orgId} = ${sites.orgId}
) + (
SELECT COUNT(DISTINCT ${browserGatewayTarget.resourceId})
FROM ${browserGatewayTarget}
WHERE ${browserGatewayTarget.siteId} = ${sites.siteId}
)`, )`,
status: sites.status status: sites.status
}) })
@@ -307,7 +312,6 @@ export async function listSites(
) )
); );
} }
const parsedParams = listSitesParamsSchema.safeParse(req.params); const parsedParams = listSitesParamsSchema.safeParse(req.params);
if (!parsedParams.success) { if (!parsedParams.success) {
return next( return next(

View File

@@ -1812,74 +1812,68 @@ export function PrivateResourceForm({
/> />
</div> </div>
{/* Auth Method (standard only) */} <div className="space-y-3">
{!isNative && ( <p className="text-sm font-semibold">
<div className="space-y-3"> {t("sshAuthenticationMethod")}
<p className="text-sm font-semibold"> </p>
{t("sshAuthenticationMethod")} <FormField
</p> control={form.control}
<FormField name="pamMode"
control={form.control} render={({ field }) => (
name="pamMode" <FormItem>
render={({ field }) => ( <FormControl>
<FormItem> <StrategySelect<
<FormControl> "passthrough" | "push"
<StrategySelect< >
"passthrough" | "push" value={
> field.value ??
value={ "passthrough"
field.value ?? }
"passthrough" options={[
} {
options={[ id: "passthrough",
{ title: t(
id: "passthrough", "sshAuthMethodManual"
title: t( ),
"sshAuthMethodManual" description: t(
), "sshAuthMethodManualDescription"
description: t( ),
"sshAuthMethodManualDescription" disabled:
),
disabled:
sshSectionDisabled
},
{
id: "push",
title: t(
"sshAuthMethodAutomated"
),
description: t(
"sshAuthMethodAutomatedDescription"
),
disabled:
sshSectionDisabled
}
]}
onChange={(v) => {
if (
sshSectionDisabled sshSectionDisabled
) },
return; {
field.onChange(v); id: "push",
if ( title: t(
v === "sshAuthMethodAutomated"
"passthrough" ),
) { description: t(
form.setValue( "sshAuthMethodAutomatedDescription"
"authDaemonPort", ),
null disabled:
); sshSectionDisabled
} }
}} ]}
cols={2} onChange={(v) => {
/> if (sshSectionDisabled)
</FormControl> return;
<FormMessage /> field.onChange(v);
</FormItem> if (
)} v === "passthrough"
/> ) {
</div> form.setValue(
)} "authDaemonPort",
null
);
}
}}
cols={2}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
{/* Daemon Location (standard + push) */} {/* Daemon Location (standard + push) */}
{showDaemonLocation && ( {showDaemonLocation && (