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,
preferWildcardCert: domains.preferWildcardCert,
domainNamespaceId: domainNamespaces.domainNamespaceId,
// Maintenance fields
maintenanceModeEnabled: resources.maintenanceModeEnabled,
maintenanceModeType: resources.maintenanceModeType,
maintenanceTitle: resources.maintenanceTitle,
maintenanceMessage: resources.maintenanceMessage,
maintenanceEstimatedTime: resources.maintenanceEstimatedTime,
// Browser gateway target fields
browserGatewayTargetId: browserGatewayTarget.browserGatewayTargetId,
bgType: browserGatewayTarget.type,
@@ -340,6 +346,11 @@ export async function getTraefikConfig(
wildcard: boolean | null;
domainCertResolver: string | null;
preferWildcardCert: boolean | null;
maintenanceModeEnabled: boolean | null;
maintenanceModeType: string | null;
maintenanceTitle: string | null;
maintenanceMessage: string | null;
maintenanceEstimatedTime: string | null;
targets: {
browserGatewayTargetId: number;
bgType: string;
@@ -371,6 +382,11 @@ export async function getTraefikConfig(
wildcard: row.wildcard,
domainCertResolver: row.domainCertResolver,
preferWildcardCert: row.preferWildcardCert,
maintenanceModeEnabled: row.maintenanceModeEnabled,
maintenanceModeType: row.maintenanceModeType,
maintenanceTitle: row.maintenanceTitle,
maintenanceMessage: row.maintenanceMessage,
maintenanceEstimatedTime: row.maintenanceEstimatedTime,
targets: []
});
}
@@ -1118,6 +1134,75 @@ export async function getTraefikConfig(
// Collect online sites for this resource (for any type)
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
const typeMap = new Map<string, typeof bgResource.targets>();
for (const t of bgResource.targets) {

View File

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

View File

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