mirror of
https://github.com/fosrl/pangolin.git
synced 2026-01-28 22:00:51 +00:00
UI and backend update to add proxy protocol support
This commit is contained in:
@@ -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", {
|
||||||
|
|||||||
@@ -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, {
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
Reference in New Issue
Block a user