Merge pull request #511 from x86txt/sticky_targets

Add ability for sticky sessions to backend resource.
This commit is contained in:
Milo Schwartz 2025-04-21 22:28:45 -04:00 committed by GitHub
commit 9ea7c43212
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 80 additions and 7 deletions

View file

@ -78,6 +78,9 @@ export const resources = sqliteTable("resources", {
.notNull()
.default(false),
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
stickySession: integer("stickySession", { mode: "boolean" })
.notNull()
.default(false),
tlsServerName: text("tlsServerName"),
setHostHeader: text("setHostHeader")
});

View file

@ -44,6 +44,7 @@ const updateHttpResourceBodySchema = z
applyRules: z.boolean().optional(),
domainId: z.string().optional(),
enabled: z.boolean().optional(),
stickySession: z.boolean().optional(),
tlsServerName: z.string().optional(),
setHostHeader: z.string().optional()
})

View file

@ -41,6 +41,7 @@ export async function traefikConfigProvider(
orgId: orgs.orgId
},
enabled: resources.enabled,
stickySession: resources.stickySessionk,
tlsServerName: resources.tlsServerName,
setHostHeader: resources.setHostHeader
})
@ -104,7 +105,10 @@ export async function traefikConfigProvider(
[badgerMiddlewareName]: {
apiBaseUrl: new URL(
"/api/v1",
`http://${config.getRawConfig().server.internal_hostname}:${
`http://${
config.getRawConfig().server
.internal_hostname
}:${
config.getRawConfig().server
.internal_port
}`
@ -279,7 +283,18 @@ export async function traefikConfigProvider(
url: `${target.method}://${ip}:${target.internalPort}`
};
}
})
}),
...(resource.stickySession
? {
sticky: {
cookie: {
name: "pangolin_sticky",
secure: resource.ssl,
httpOnly: true
}
}
}
: {})
}
};
@ -376,7 +391,17 @@ export async function traefikConfigProvider(
address: `${ip}:${target.internalPort}`
};
}
})
}),
...(resource.stickySession
? {
sticky: {
ipStrategy: {
depth: 0,
sourcePort: true
}
}
}
: {})
}
};
}

View file

@ -9,10 +9,13 @@ export default async function migration() {
try {
db.transaction((trx) => {
trx.run(
sql`ALTER TABLE 'resources' ADD 'tlsServerName' text DEFAULT '' NOT NULL;`
sql`ALTER TABLE resources ADD stickySession integer DEFAULT false NOT NULL;`
);
trx.run(
sql`ALTER TABLE 'resources' ADD 'setHostHeader' text DEFAULT '' NOT NULL;`
sql`ALTER TABLE 'resources' ADD 'tlsServerName' text;`
);
trx.run(
sql`ALTER TABLE 'resources' ADD 'setHostHeader' text;`
);
});

View file

@ -94,6 +94,7 @@ export default function ReverseProxyTargets(props: {
const [site, setSite] = useState<GetSiteResponse>();
const [targetsToRemove, setTargetsToRemove] = useState<number[]>([]);
const [sslEnabled, setSslEnabled] = useState(resource.ssl);
const [stickySession, setStickySession] = useState(resource.stickySession);
const [loading, setLoading] = useState(false);
@ -317,6 +318,35 @@ export default function ReverseProxyTargets(props: {
}
}
async function saveStickySession(val: boolean) {
const res = await api
.post(`/resource/${params.resourceId}`, {
stickySession: val
})
.catch((err) => {
console.error(err);
toast({
variant: "destructive",
title: "Failed to update sticky session configuration",
description: formatAxiosError(
err,
"An error occurred while updating the sticky session configuration"
)
});
});
if (res && res.status === 200) {
setStickySession(val);
updateResource({ stickySession: val });
toast({
title: "Sticky Session Configuration",
description: "Sticky session configuration updated successfully"
});
router.refresh();
}
}
const columns: ColumnDef<LocalTarget>[] = [
{
accessorKey: "ip",
@ -456,13 +486,24 @@ export default function ReverseProxyTargets(props: {
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
SSL Configuration
Advanced Configuration
</SettingsSectionTitle>
<SettingsSectionDescription>
Set up SSL to secure your connections with certificates
Configure advanced settings for your resource
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
{targets.length >= 2 && (
<SwitchInput
id="sticky-toggle"
label="Enable Sticky Sessions"
description="Keep users on the same backend target for their entire session. Useful for applications like VNC that require persistent connections."
defaultChecked={resource.stickySession}
onCheckedChange={async (val) => {
await saveStickySession(val);
}}
/>
)}
<SwitchInput
id="ssl-toggle"
label="Enable SSL (https)"