mirror of
https://github.com/fosrl/pangolin.git
synced 2026-02-04 09:03:48 +00:00
basic setup for rewriting requests to another path
This commit is contained in:
@@ -105,7 +105,9 @@ const addTargetSchema = z.object({
|
||||
port: z.coerce.number().int().positive(),
|
||||
siteId: z.number().int().positive(),
|
||||
path: z.string().optional().nullable(),
|
||||
pathMatchType: z.enum(["exact", "prefix", "regex"]).optional().nullable()
|
||||
pathMatchType: z.enum(["exact", "prefix", "regex"]).optional().nullable(),
|
||||
rewritePath: z.string().optional().nullable(),
|
||||
rewritePathType: z.enum(["exact", "prefix", "regex", "stripPrefix"]).optional().nullable()
|
||||
}).refine(
|
||||
(data) => {
|
||||
// If path is provided, pathMatchType must be provided
|
||||
@@ -138,7 +140,23 @@ const addTargetSchema = z.object({
|
||||
{
|
||||
message: "Invalid path configuration"
|
||||
}
|
||||
);
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
// If rewritePath is provided, rewritePathType must be provided
|
||||
if (data.rewritePath && !data.rewritePathType) {
|
||||
return false;
|
||||
}
|
||||
// If rewritePathType is provided, rewritePath must be provided
|
||||
if (data.rewritePathType && !data.rewritePath) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
{
|
||||
message: "Invalid rewrite path configuration"
|
||||
}
|
||||
);
|
||||
|
||||
const targetsSettingsSchema = z.object({
|
||||
stickySession: z.boolean()
|
||||
@@ -259,8 +277,10 @@ export default function ReverseProxyTargets(props: {
|
||||
method: resource.http ? "http" : null,
|
||||
port: "" as any as number,
|
||||
path: null,
|
||||
pathMatchType: null
|
||||
}
|
||||
pathMatchType: null,
|
||||
rewritePath: null,
|
||||
rewritePathType: null,
|
||||
} as z.infer<typeof addTargetSchema>
|
||||
});
|
||||
|
||||
const watchedIp = addTargetForm.watch("ip");
|
||||
@@ -436,6 +456,8 @@ export default function ReverseProxyTargets(props: {
|
||||
...data,
|
||||
path: data.path || null,
|
||||
pathMatchType: data.pathMatchType || null,
|
||||
rewritePath: data.rewritePath || null,
|
||||
rewritePathType: data.rewritePathType || null,
|
||||
siteType: site?.type || null,
|
||||
enabled: true,
|
||||
targetId: new Date().getTime(),
|
||||
@@ -449,7 +471,9 @@ export default function ReverseProxyTargets(props: {
|
||||
method: resource.http ? "http" : null,
|
||||
port: "" as any as number,
|
||||
path: null,
|
||||
pathMatchType: null
|
||||
pathMatchType: null,
|
||||
rewritePath: null,
|
||||
rewritePathType: null,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -469,11 +493,11 @@ export default function ReverseProxyTargets(props: {
|
||||
targets.map((target) =>
|
||||
target.targetId === targetId
|
||||
? {
|
||||
...target,
|
||||
...data,
|
||||
updated: true,
|
||||
siteType: site?.type || null
|
||||
}
|
||||
...target,
|
||||
...data,
|
||||
updated: true,
|
||||
siteType: site?.type || null
|
||||
}
|
||||
: target
|
||||
)
|
||||
);
|
||||
@@ -494,7 +518,9 @@ export default function ReverseProxyTargets(props: {
|
||||
enabled: target.enabled,
|
||||
siteId: target.siteId,
|
||||
path: target.path,
|
||||
pathMatchType: target.pathMatchType
|
||||
pathMatchType: target.pathMatchType,
|
||||
rewritePath: target.rewritePath,
|
||||
rewritePathType: target.rewritePathType
|
||||
};
|
||||
|
||||
if (target.new) {
|
||||
@@ -590,7 +616,7 @@ export default function ReverseProxyTargets(props: {
|
||||
}
|
||||
}}
|
||||
>
|
||||
+ {t("matchPath")}
|
||||
+ {t("matchPath")}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
@@ -693,7 +719,7 @@ export default function ReverseProxyTargets(props: {
|
||||
className={cn(
|
||||
"justify-between flex-1",
|
||||
!row.original.siteId &&
|
||||
"text-muted-foreground"
|
||||
"text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{row.original.siteId
|
||||
@@ -772,31 +798,31 @@ export default function ReverseProxyTargets(props: {
|
||||
},
|
||||
...(resource.http
|
||||
? [
|
||||
{
|
||||
accessorKey: "method",
|
||||
header: t("method"),
|
||||
cell: ({ row }: { row: Row<LocalTarget> }) => (
|
||||
<Select
|
||||
defaultValue={row.original.method ?? ""}
|
||||
onValueChange={(value) =>
|
||||
updateTarget(row.original.targetId, {
|
||||
...row.original,
|
||||
method: value
|
||||
})
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
{row.original.method}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="http">http</SelectItem>
|
||||
<SelectItem value="https">https</SelectItem>
|
||||
<SelectItem value="h2c">h2c</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)
|
||||
}
|
||||
]
|
||||
{
|
||||
accessorKey: "method",
|
||||
header: t("method"),
|
||||
cell: ({ row }: { row: Row<LocalTarget> }) => (
|
||||
<Select
|
||||
defaultValue={row.original.method ?? ""}
|
||||
onValueChange={(value) =>
|
||||
updateTarget(row.original.targetId, {
|
||||
...row.original,
|
||||
method: value
|
||||
})
|
||||
}
|
||||
>
|
||||
<SelectTrigger>
|
||||
{row.original.method}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="http">http</SelectItem>
|
||||
<SelectItem value="https">https</SelectItem>
|
||||
<SelectItem value="h2c">h2c</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)
|
||||
}
|
||||
]
|
||||
: []),
|
||||
{
|
||||
accessorKey: "ip",
|
||||
@@ -856,6 +882,102 @@ export default function ReverseProxyTargets(props: {
|
||||
/>
|
||||
)
|
||||
},
|
||||
{
|
||||
accessorKey: "rewritePath",
|
||||
header: t("rewritePath"),
|
||||
cell: ({ row }) => {
|
||||
const [showRewritePathInput, setShowRewritePathInput] = useState(
|
||||
!!(row.original.rewritePath || row.original.rewritePathType)
|
||||
);
|
||||
|
||||
if (!showRewritePathInput) {
|
||||
const noPathMatch =
|
||||
!row.original.path && !row.original.pathMatchType;
|
||||
return (
|
||||
<Button
|
||||
variant="outline"
|
||||
disabled={noPathMatch}
|
||||
onClick={() => {
|
||||
setShowRewritePathInput(true);
|
||||
// Set default rewritePathType when first showing path input
|
||||
if (!row.original.rewritePathType) {
|
||||
updateTarget(row.original.targetId, {
|
||||
...row.original,
|
||||
rewritePathType: "prefix"
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
+ {t("rewritePath")}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex gap-2 min-w-[200px] items-center">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setShowRewritePathInput(false);
|
||||
updateTarget(row.original.targetId, {
|
||||
...row.original,
|
||||
rewritePath: null,
|
||||
rewritePathType: null
|
||||
});
|
||||
}}
|
||||
>
|
||||
×
|
||||
</Button>
|
||||
|
||||
<MoveRight className="ml-4 h-4 w-4" />
|
||||
<Select
|
||||
defaultValue={row.original.rewritePathType || "prefix"}
|
||||
onValueChange={(value) =>
|
||||
updateTarget(row.original.targetId, {
|
||||
...row.original,
|
||||
rewritePathType: value as "exact" | "prefix" | "regex"
|
||||
})
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="w-25">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="prefix">Prefix</SelectItem>
|
||||
<SelectItem value="exact">Exact</SelectItem>
|
||||
<SelectItem value="regex">Regex</SelectItem>
|
||||
<SelectItem value="stripPrefix">Strip Prefix</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<Input
|
||||
placeholder={
|
||||
row.original.rewritePathType === "regex"
|
||||
? "^/api/.*"
|
||||
: "/path"
|
||||
}
|
||||
defaultValue={row.original.rewritePath || ""}
|
||||
className="flex-1 min-w-[150px]"
|
||||
onBlur={(e) => {
|
||||
const value = e.target.value.trim();
|
||||
if (!value) {
|
||||
setShowRewritePathInput(false);
|
||||
updateTarget(row.original.targetId, {
|
||||
...row.original,
|
||||
rewritePath: null,
|
||||
rewritePathType: null
|
||||
});
|
||||
} else {
|
||||
updateTarget(row.original.targetId, {
|
||||
...row.original,
|
||||
rewritePath: value
|
||||
});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
// {
|
||||
// accessorKey: "protocol",
|
||||
// header: t('targetProtocol'),
|
||||
@@ -968,21 +1090,21 @@ export default function ReverseProxyTargets(props: {
|
||||
className={cn(
|
||||
"justify-between flex-1",
|
||||
!field.value &&
|
||||
"text-muted-foreground"
|
||||
"text-muted-foreground"
|
||||
)}
|
||||
>
|
||||
{field.value
|
||||
? sites.find(
|
||||
(
|
||||
site
|
||||
) =>
|
||||
site.siteId ===
|
||||
field.value
|
||||
)
|
||||
?.name
|
||||
(
|
||||
site
|
||||
) =>
|
||||
site.siteId ===
|
||||
field.value
|
||||
)
|
||||
?.name
|
||||
: t(
|
||||
"siteSelect"
|
||||
)}
|
||||
"siteSelect"
|
||||
)}
|
||||
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
|
||||
</Button>
|
||||
</FormControl>
|
||||
@@ -1048,34 +1170,34 @@ export default function ReverseProxyTargets(props: {
|
||||
);
|
||||
return selectedSite &&
|
||||
selectedSite.type ===
|
||||
"newt"
|
||||
"newt"
|
||||
? (() => {
|
||||
const dockerState =
|
||||
getDockerStateForSite(
|
||||
selectedSite.siteId
|
||||
);
|
||||
return (
|
||||
<ContainersSelector
|
||||
site={
|
||||
selectedSite
|
||||
}
|
||||
containers={
|
||||
dockerState.containers
|
||||
}
|
||||
isAvailable={
|
||||
dockerState.isAvailable
|
||||
}
|
||||
onContainerSelect={
|
||||
handleContainerSelect
|
||||
}
|
||||
onRefresh={() =>
|
||||
refreshContainersForSite(
|
||||
selectedSite.siteId
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
})()
|
||||
const dockerState =
|
||||
getDockerStateForSite(
|
||||
selectedSite.siteId
|
||||
);
|
||||
return (
|
||||
<ContainersSelector
|
||||
site={
|
||||
selectedSite
|
||||
}
|
||||
containers={
|
||||
dockerState.containers
|
||||
}
|
||||
isAvailable={
|
||||
dockerState.isAvailable
|
||||
}
|
||||
onContainerSelect={
|
||||
handleContainerSelect
|
||||
}
|
||||
onRefresh={() =>
|
||||
refreshContainersForSite(
|
||||
selectedSite.siteId
|
||||
)
|
||||
}
|
||||
/>
|
||||
);
|
||||
})()
|
||||
: null;
|
||||
})()}
|
||||
</div>
|
||||
@@ -1303,12 +1425,12 @@ export default function ReverseProxyTargets(props: {
|
||||
{header.isPlaceholder
|
||||
? null
|
||||
: flexRender(
|
||||
header
|
||||
.column
|
||||
.columnDef
|
||||
.header,
|
||||
header.getContext()
|
||||
)}
|
||||
header
|
||||
.column
|
||||
.columnDef
|
||||
.header,
|
||||
header.getContext()
|
||||
)}
|
||||
</TableHead>
|
||||
)
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user