diff --git a/app/(dashboard)/proxy-hosts/actions.ts b/app/(dashboard)/proxy-hosts/actions.ts index 8e3f7f46..22432e72 100644 --- a/app/(dashboard)/proxy-hosts/actions.ts +++ b/app/(dashboard)/proxy-hosts/actions.ts @@ -416,6 +416,18 @@ function parseRedirectsConfig(formData: FormData): RedirectRule[] | null { } } +function parseLocationRulesConfig(formData: FormData): import("@/src/lib/models/proxy-hosts").LocationRule[] | null { + const raw = formData.get("location_rules_json"); + if (!raw || typeof raw !== "string") return null; + try { + const parsed = JSON.parse(raw); + if (!Array.isArray(parsed)) return null; + return parsed; + } catch { + return null; + } +} + function parseRewriteConfig(formData: FormData): RewriteConfig | null { const prefix = formData.get("rewrite_path_prefix"); if (!prefix || typeof prefix !== "string" || !prefix.trim()) return null; @@ -494,6 +506,7 @@ export async function createProxyHostAction( mtls: parseMtlsConfig(formData), redirects: parseRedirectsConfig(formData), rewrite: parseRewriteConfig(formData), + location_rules: parseLocationRulesConfig(formData), }, userId ); @@ -570,6 +583,7 @@ export async function updateProxyHostAction( mtls: formData.has("mtls_present") ? parseMtlsConfig(formData) : undefined, redirects: formData.has("redirects_json") ? parseRedirectsConfig(formData) : undefined, rewrite: formData.has("rewrite_path_prefix") ? parseRewriteConfig(formData) : undefined, + location_rules: formData.has("location_rules_json") ? parseLocationRulesConfig(formData) : undefined, }, userId ); diff --git a/src/components/proxy-hosts/HostDialogs.tsx b/src/components/proxy-hosts/HostDialogs.tsx index 584b45b5..a01d8b9d 100644 --- a/src/components/proxy-hosts/HostDialogs.tsx +++ b/src/components/proxy-hosts/HostDialogs.tsx @@ -25,6 +25,7 @@ import { GeoBlockFields } from "./GeoBlockFields"; import { WafFields } from "./WafFields"; import { MtlsFields } from "./MtlsConfig"; import { RedirectsFields } from "./RedirectsFields"; +import { LocationRulesFields } from "./LocationRulesFields"; import { RewriteFields } from "./RewriteFields"; import type { CaCertificate } from "@/lib/models/ca-certificates"; @@ -133,6 +134,7 @@ export function CreateHostDialog({ +
@@ -263,6 +265,7 @@ export function EditHostDialog({
+
diff --git a/src/components/proxy-hosts/LocationRulesFields.tsx b/src/components/proxy-hosts/LocationRulesFields.tsx new file mode 100644 index 00000000..f03ab62a --- /dev/null +++ b/src/components/proxy-hosts/LocationRulesFields.tsx @@ -0,0 +1,91 @@ +"use client"; +import { useState } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { Trash2, Plus } from "lucide-react"; +import type { LocationRule } from "@/lib/models/proxy-hosts"; + +type Props = { initialData?: LocationRule[] }; + +export function LocationRulesFields({ initialData = [] }: Props) { + const [rules, setRules] = useState(initialData); + + const addRule = () => + setRules((r) => [...r, { path: "", upstreams: [] }]); + + const removeRule = (i: number) => + setRules((r) => r.filter((_, idx) => idx !== i)); + + const updatePath = (i: number, value: string) => + setRules((r) => r.map((rule, idx) => (idx === i ? { ...rule, path: value } : rule))); + + const updateUpstreams = (i: number, value: string) => + setRules((r) => + r.map((rule, idx) => + idx === i + ? { + ...rule, + upstreams: value + .split("\n") + .map((u) => u.trim()) + .filter(Boolean), + } + : rule + ) + ); + + return ( +
+

Location Rules

+ + {rules.length > 0 && ( +
+ {rules.map((rule, i) => ( +
+
+ {i === 0 && ( + Path Pattern + )} + updatePath(i, e.target.value)} + className="h-8 text-sm" + /> +
+
+ {i === 0 && ( + Upstreams + )} +