diff --git a/server/db/schemas/schema.ts b/server/db/schemas/schema.ts index a8627553..a72df539 100644 --- a/server/db/schemas/schema.ts +++ b/server/db/schemas/schema.ts @@ -77,7 +77,10 @@ export const resources = sqliteTable("resources", { applyRules: integer("applyRules", { mode: "boolean" }) .notNull() .default(false), - enabled: integer("enabled", { mode: "boolean" }).notNull().default(true) + enabled: integer("enabled", { mode: "boolean" }).notNull().default(true), + stickySession: integer("stickySession", { mode: "boolean" }) + .notNull() + .default(false) }); export const targets = sqliteTable("targets", { diff --git a/server/routers/resource/updateResource.ts b/server/routers/resource/updateResource.ts index df4a41e7..df04adea 100644 --- a/server/routers/resource/updateResource.ts +++ b/server/routers/resource/updateResource.ts @@ -42,7 +42,8 @@ const updateHttpResourceBodySchema = z isBaseDomain: z.boolean().optional(), applyRules: z.boolean().optional(), domainId: z.string().optional(), - enabled: z.boolean().optional() + enabled: z.boolean().optional(), + stickySession: z.boolean().optional() }) .strict() .refine((data) => Object.keys(data).length > 0, { diff --git a/server/routers/traefik/getTraefikConfig.ts b/server/routers/traefik/getTraefikConfig.ts index 17e385ed..aee5e2a9 100644 --- a/server/routers/traefik/getTraefikConfig.ts +++ b/server/routers/traefik/getTraefikConfig.ts @@ -40,7 +40,8 @@ export async function traefikConfigProvider( org: { orgId: orgs.orgId }, - enabled: resources.enabled + enabled: resources.enabled, + stickySession: resources.stickySession }) .from(resources) .innerJoin(sites, eq(sites.siteId, resources.siteId)) @@ -275,7 +276,18 @@ export async function traefikConfigProvider( url: `${target.method}://${ip}:${target.internalPort}` }; } - }) + }), + ...(resource.stickySession + ? { + sticky: { + cookie: { + name: "pangolin_sticky", + secure: resource.ssl, + httpOnly: true + } + } + } + : {}) } }; } else { @@ -335,7 +347,18 @@ export async function traefikConfigProvider( address: `${ip}:${target.internalPort}` }; } - }) + }), + ...(resource.stickySession + ? { + sticky: { + cookie: { + name: "pangolin_sticky", + secure: resource.ssl, + httpOnly: true + } + } + } + : {}) } }; } diff --git a/server/setup/migrations.ts b/server/setup/migrations.ts index 77248f62..1a2c1d22 100644 --- a/server/setup/migrations.ts +++ b/server/setup/migrations.ts @@ -19,7 +19,7 @@ import m15 from "./scripts/1.0.0-beta15"; import m16 from "./scripts/1.0.0"; import m17 from "./scripts/1.1.0"; import m18 from "./scripts/1.2.0"; - +import m19 from "./scripts/1.3.0"; // THIS CANNOT IMPORT ANYTHING FROM THE SERVER // EXCEPT FOR THE DATABASE AND THE SCHEMA @@ -37,7 +37,8 @@ const migrations = [ { version: "1.0.0-beta.15", run: m15 }, { version: "1.0.0", run: m16 }, { version: "1.1.0", run: m17 }, - { version: "1.2.0", run: m18 } + { version: "1.2.0", run: m18 }, + { version: "1.3.0", run: m19 } // Add new migrations here as they are created ] as const; diff --git a/server/setup/scripts/1.3.0.ts b/server/setup/scripts/1.3.0.ts new file mode 100644 index 00000000..a3a16e1c --- /dev/null +++ b/server/setup/scripts/1.3.0.ts @@ -0,0 +1,23 @@ +import db from "@server/db"; +import { sql } from "drizzle-orm"; + +const version = "1.3.0"; + +export default async function migration() { + console.log(`Running setup script ${version}...`); + + try { + db.transaction((trx) => { + trx.run( + sql`ALTER TABLE resources ADD stickySession integer DEFAULT false NOT NULL;` + ); + }); + + console.log(`Added new column: stickySession`); + } catch (e) { + console.log("Unable to add new column: stickySession"); + throw e; + } + + console.log(`${version} migration complete`); +} diff --git a/src/app/[orgId]/settings/resources/[resourceId]/connectivity/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/connectivity/page.tsx index f0dd8978..bb238b68 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/connectivity/page.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/connectivity/page.tsx @@ -94,6 +94,7 @@ export default function ReverseProxyTargets(props: { const [site, setSite] = useState(); const [targetsToRemove, setTargetsToRemove] = useState([]); 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[] = [ { accessorKey: "ip", @@ -456,13 +486,22 @@ export default function ReverseProxyTargets(props: { - SSL Configuration + Advanced Configuration - Set up SSL to secure your connections with certificates + Configure advanced settings for your resource + { + await saveStickySession(val); + }} + />