UI and backend update to add proxy protocol support

This commit is contained in:
Pallavi Kumari
2025-10-23 23:07:26 +05:30
parent c1bb029a1c
commit b5a931c96e
3 changed files with 147 additions and 27 deletions

View File

@@ -114,7 +114,10 @@ export const resources = sqliteTable("resources", {
skipToIdpId: integer("skipToIdpId").references(() => idp.idpId, { skipToIdpId: integer("skipToIdpId").references(() => idp.idpId, {
onDelete: "cascade" onDelete: "cascade"
}), }),
headers: text("headers") // comma-separated list of headers to add to the request headers: text("headers"), // comma-separated list of headers to add to the request
proxyProtocol: integer("proxyProtocol", { mode: "boolean" }).notNull().default(false),
proxyProtocolVersion: integer("proxyProtocolVersion").default(1)
}); });
export const targets = sqliteTable("targets", { export const targets = sqliteTable("targets", {

View File

@@ -99,8 +99,9 @@ const updateRawResourceBodySchema = z
name: z.string().min(1).max(255).optional(), name: z.string().min(1).max(255).optional(),
proxyPort: z.number().int().min(1).max(65535).optional(), proxyPort: z.number().int().min(1).max(65535).optional(),
stickySession: z.boolean().optional(), stickySession: z.boolean().optional(),
enabled: z.boolean().optional() enabled: z.boolean().optional(),
// enableProxy: z.boolean().optional() // always true now proxyProtocol: z.boolean().optional(),
proxyProtocolVersion: z.number().int().min(1).optional()
}) })
.strict() .strict()
.refine((data) => Object.keys(data).length > 0, { .refine((data) => Object.keys(data).length > 0, {

View File

@@ -77,7 +77,8 @@ import {
MoveRight, MoveRight,
ArrowUp, ArrowUp,
Info, Info,
ArrowDown ArrowDown,
AlertTriangle
} from "lucide-react"; } from "lucide-react";
import { ContainersSelector } from "@app/components/ContainersSelector"; import { ContainersSelector } from "@app/components/ContainersSelector";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
@@ -115,6 +116,7 @@ import {
TooltipProvider, TooltipProvider,
TooltipTrigger TooltipTrigger
} from "@app/components/ui/tooltip"; } from "@app/components/ui/tooltip";
import { Alert, AlertDescription } from "@app/components/ui/alert";
const addTargetSchema = z const addTargetSchema = z
.object({ .object({
@@ -288,7 +290,9 @@ export default function ReverseProxyTargets(props: {
), ),
headers: z headers: z
.array(z.object({ name: z.string(), value: z.string() })) .array(z.object({ name: z.string(), value: z.string() }))
.nullable() .nullable(),
proxyProtocol: z.boolean().optional(),
proxyProtocolVersion: z.number().int().min(1).max(2).optional()
}); });
const tlsSettingsSchema = z.object({ const tlsSettingsSchema = z.object({
@@ -325,7 +329,9 @@ export default function ReverseProxyTargets(props: {
resolver: zodResolver(proxySettingsSchema), resolver: zodResolver(proxySettingsSchema),
defaultValues: { defaultValues: {
setHostHeader: resource.setHostHeader || "", setHostHeader: resource.setHostHeader || "",
headers: resource.headers headers: resource.headers,
proxyProtocol: resource.proxyProtocol || false,
proxyProtocolVersion: resource.proxyProtocolVersion || 1
} }
}); });
@@ -549,11 +555,11 @@ export default function ReverseProxyTargets(props: {
prev.map((t) => prev.map((t) =>
t.targetId === target.targetId t.targetId === target.targetId
? { ? {
...t, ...t,
targetId: response.data.data.targetId, targetId: response.data.data.targetId,
new: false, new: false,
updated: false updated: false
} }
: t : t
) )
); );
@@ -673,11 +679,11 @@ export default function ReverseProxyTargets(props: {
targets.map((target) => targets.map((target) =>
target.targetId === targetId target.targetId === targetId
? { ? {
...target, ...target,
...data, ...data,
updated: true, updated: true,
siteType: site ? site.type : target.siteType siteType: site ? site.type : target.siteType
} }
: target : target
) )
); );
@@ -688,10 +694,10 @@ export default function ReverseProxyTargets(props: {
targets.map((target) => targets.map((target) =>
target.targetId === targetId target.targetId === targetId
? { ? {
...target, ...target,
...config, ...config,
updated: true updated: true
} }
: target : target
) )
); );
@@ -800,6 +806,22 @@ export default function ReverseProxyTargets(props: {
setHostHeader: proxyData.setHostHeader || null, setHostHeader: proxyData.setHostHeader || null,
headers: proxyData.headers || null headers: proxyData.headers || null
}); });
} else {
// For TCP/UDP resources, save proxy protocol settings
const proxyData = proxySettingsForm.getValues();
const payload = {
proxyProtocol: proxyData.proxyProtocol || false,
proxyProtocolVersion: proxyData.proxyProtocolVersion || 1
};
await api.post(`/resource/${resource.resourceId}`, payload);
updateResource({
...resource,
proxyProtocol: proxyData.proxyProtocol || false,
proxyProtocolVersion: proxyData.proxyProtocolVersion || 1
});
} }
toast({ toast({
@@ -1064,7 +1086,7 @@ export default function ReverseProxyTargets(props: {
className={cn( className={cn(
"w-[180px] justify-between text-sm border-r pr-4 rounded-none h-8 hover:bg-transparent", "w-[180px] justify-between text-sm border-r pr-4 rounded-none h-8 hover:bg-transparent",
!row.original.siteId && !row.original.siteId &&
"text-muted-foreground" "text-muted-foreground"
)} )}
> >
<span className="truncate max-w-[150px]"> <span className="truncate max-w-[150px]">
@@ -1404,12 +1426,12 @@ export default function ReverseProxyTargets(props: {
{header.isPlaceholder {header.isPlaceholder
? null ? null
: flexRender( : flexRender(
header header
.column .column
.columnDef .columnDef
.header, .header,
header.getContext() header.getContext()
)} )}
</TableHead> </TableHead>
) )
)} )}
@@ -1675,6 +1697,100 @@ export default function ReverseProxyTargets(props: {
</SettingsSection> </SettingsSection>
)} )}
{!resource.http && resource.protocol && (
<SettingsSection>
<SettingsSectionHeader>
<SettingsSectionTitle>
Proxy Protocol Settings
</SettingsSectionTitle>
<SettingsSectionDescription>
Configure Proxy Protocol to preserve client IP addresses for TCP/UDP services.
</SettingsSectionDescription>
</SettingsSectionHeader>
<SettingsSectionBody>
<SettingsSectionForm>
<Form {...proxySettingsForm}>
<form
onSubmit={proxySettingsForm.handleSubmit(
saveAllSettings
)}
className="space-y-4"
id="proxy-protocol-settings-form"
>
<FormField
control={proxySettingsForm.control}
name="proxyProtocol"
render={({ field }) => (
<FormItem>
<FormControl>
<SwitchInput
id="proxy-protocol-toggle"
label="Enable Proxy Protocol"
description="Preserve client IP addresses for TCP/UDP backends"
defaultChecked={
field.value || false
}
onCheckedChange={(val) => {
field.onChange(val);
}}
/>
</FormControl>
</FormItem>
)}
/>
{proxySettingsForm.watch("proxyProtocol") && (
<>
<FormField
control={proxySettingsForm.control}
name="proxyProtocolVersion"
render={({ field }) => (
<FormItem>
<FormLabel>Proxy Protocol Version</FormLabel>
<FormControl>
<Select
value={String(field.value || 1)}
onValueChange={(value) =>
field.onChange(parseInt(value, 10))
}
>
<SelectTrigger>
<SelectValue placeholder="Select version" />
</SelectTrigger>
<SelectContent>
<SelectItem value="1">
Version 1 (Recommended)
</SelectItem>
<SelectItem value="2">
Version 2
</SelectItem>
</SelectContent>
</Select>
</FormControl>
<FormDescription>
Version 1 is text-based and widely supported. Version 2 is binary and more efficient but less compatible.
</FormDescription>
</FormItem>
)}
/>
<Alert>
<AlertTriangle className="h-4 w-4" />
<AlertDescription>
<strong>Warning:</strong> Your backend application must be configured to accept Proxy Protocol connections.
If your backend doesn't support Proxy Protocol, enabling this will break all connections.
Make sure to configure your backend to trust Proxy Protocol headers from Traefik.
</AlertDescription>
</Alert>
</>
)}
</form>
</Form>
</SettingsSectionForm>
</SettingsSectionBody>
</SettingsSection>
)}
<div className="flex justify-end mt-6"> <div className="flex justify-end mt-6">
<Button <Button
onClick={saveAllSettings} onClick={saveAllSettings}