mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-04 01:36:39 +00:00
match and rewrite path ui improve for create resource
This commit is contained in:
@@ -34,7 +34,7 @@ const createTargetSchema = z
|
|||||||
path: z.string().optional().nullable(),
|
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(),
|
rewritePath: z.string().optional().nullable(),
|
||||||
rewritePathType: z.enum(["exact", "prefix", "regex", "stripPrefix"]).optional().nullable() // NEW: rewrite path type
|
rewritePathType: z.enum(["exact", "prefix", "regex", "stripPrefix"]).optional().nullable()
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ import {
|
|||||||
} from "@app/components/ui/popover";
|
} from "@app/components/ui/popover";
|
||||||
import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
|
import { CaretSortIcon, CheckIcon } from "@radix-ui/react-icons";
|
||||||
import { cn } from "@app/lib/cn";
|
import { cn } from "@app/lib/cn";
|
||||||
import { ArrowRight, MoveRight, SquareArrowOutUpRight } from "lucide-react";
|
import { ArrowRight, MoveRight, Plus, SquareArrowOutUpRight } from "lucide-react";
|
||||||
import CopyTextBox from "@app/components/CopyTextBox";
|
import CopyTextBox from "@app/components/CopyTextBox";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
@@ -92,6 +92,7 @@ import { parseHostTarget } from "@app/lib/parseHostTarget";
|
|||||||
import { toASCII, toUnicode } from 'punycode';
|
import { toASCII, toUnicode } from 'punycode';
|
||||||
import { DomainRow } from "../../../../../components/DomainsTable";
|
import { DomainRow } from "../../../../../components/DomainsTable";
|
||||||
import { finalizeSubdomainSanitize } from "@app/lib/subdomain-utils";
|
import { finalizeSubdomainSanitize } from "@app/lib/subdomain-utils";
|
||||||
|
import { PathMatchDisplay, PathMatchModal, PathRewriteDisplay, PathRewriteModal } from "@app/components/PathMatchRenameModal";
|
||||||
|
|
||||||
|
|
||||||
const baseResourceFormSchema = z.object({
|
const baseResourceFormSchema = z.object({
|
||||||
@@ -152,7 +153,7 @@ const addTargetSchema = z.object({
|
|||||||
message: "Invalid path configuration"
|
message: "Invalid path configuration"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.refine(
|
.refine(
|
||||||
(data) => {
|
(data) => {
|
||||||
// If rewritePath is provided, rewritePathType must be provided
|
// If rewritePath is provided, rewritePathType must be provided
|
||||||
if (data.rewritePath && !data.rewritePathType) {
|
if (data.rewritePath && !data.rewritePathType) {
|
||||||
@@ -575,93 +576,64 @@ export default function Page() {
|
|||||||
accessorKey: "path",
|
accessorKey: "path",
|
||||||
header: t("matchPath"),
|
header: t("matchPath"),
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const [showPathInput, setShowPathInput] = useState(
|
const hasPathMatch = !!(row.original.path || row.original.pathMatchType);
|
||||||
!!(row.original.path || row.original.pathMatchType)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!showPathInput) {
|
return hasPathMatch ? (
|
||||||
return (
|
<div className="flex items-center gap-1">
|
||||||
<Button
|
<PathMatchModal
|
||||||
variant="outline"
|
value={{
|
||||||
onClick={() => {
|
path: row.original.path,
|
||||||
setShowPathInput(true);
|
pathMatchType: row.original.pathMatchType,
|
||||||
// Set default pathMatchType when first showing path input
|
|
||||||
if (!row.original.pathMatchType) {
|
|
||||||
updateTarget(row.original.targetId, {
|
|
||||||
...row.original,
|
|
||||||
pathMatchType: "prefix"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
onChange={(config) => updateTarget(row.original.targetId, config)}
|
||||||
+ {t("matchPath")}
|
trigger={
|
||||||
</Button>
|
<Button
|
||||||
);
|
variant="outline"
|
||||||
}
|
className="flex items-center gap-2 p-2 max-w-md w-full text-left cursor-pointer"
|
||||||
|
>
|
||||||
return (
|
<PathMatchDisplay
|
||||||
<div className="flex gap-2 min-w-[200px] items-center">
|
value={{
|
||||||
<Select
|
path: row.original.path,
|
||||||
defaultValue={row.original.pathMatchType || "prefix"}
|
pathMatchType: row.original.pathMatchType,
|
||||||
onValueChange={(value) =>
|
}}
|
||||||
updateTarget(row.original.targetId, {
|
/>
|
||||||
...row.original,
|
</Button>
|
||||||
pathMatchType: 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>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
<Input
|
|
||||||
placeholder={
|
|
||||||
row.original.pathMatchType === "regex"
|
|
||||||
? "^/api/.*"
|
|
||||||
: "/path"
|
|
||||||
}
|
|
||||||
defaultValue={row.original.path || ""}
|
|
||||||
className="flex-1 min-w-[150px]"
|
|
||||||
onBlur={(e) => {
|
|
||||||
const value = e.target.value.trim();
|
|
||||||
if (!value) {
|
|
||||||
setShowPathInput(false);
|
|
||||||
updateTarget(row.original.targetId, {
|
|
||||||
...row.original,
|
|
||||||
path: null,
|
|
||||||
pathMatchType: null
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
updateTarget(row.original.targetId, {
|
|
||||||
...row.original,
|
|
||||||
path: value
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="text"
|
||||||
onClick={() => {
|
size="sm"
|
||||||
setShowPathInput(false);
|
className="px-1"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
updateTarget(row.original.targetId, {
|
updateTarget(row.original.targetId, {
|
||||||
...row.original,
|
...row.original,
|
||||||
path: null,
|
path: null,
|
||||||
pathMatchType: null
|
pathMatchType: null,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
×
|
×
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<MoveRight className="ml-4 h-4 w-4" />
|
{/* <MoveRight className="ml-1 h-4 w-4" /> */}
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<PathMatchModal
|
||||||
|
value={{
|
||||||
|
path: row.original.path,
|
||||||
|
pathMatchType: row.original.pathMatchType,
|
||||||
|
}}
|
||||||
|
onChange={(config) => updateTarget(row.original.targetId, config)}
|
||||||
|
trigger={
|
||||||
|
<Button variant="outline">
|
||||||
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
|
{t("matchPath")}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "siteId",
|
accessorKey: "siteId",
|
||||||
@@ -850,97 +822,66 @@ export default function Page() {
|
|||||||
accessorKey: "rewritePath",
|
accessorKey: "rewritePath",
|
||||||
header: t("rewritePath"),
|
header: t("rewritePath"),
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const [showRewritePathInput, setShowRewritePathInput] = useState(
|
const hasRewritePath = !!(row.original.rewritePath || row.original.rewritePathType);
|
||||||
!!(row.original.rewritePath || row.original.rewritePathType)
|
const noPathMatch = !row.original.path && !row.original.pathMatchType;
|
||||||
);
|
|
||||||
|
|
||||||
if (!showRewritePathInput) {
|
return hasRewritePath && !noPathMatch ? (
|
||||||
const noPathMatch =
|
<div className="flex items-center gap-1">
|
||||||
!row.original.path && !row.original.pathMatchType;
|
{/* <MoveRight className="mr-2 h-4 w-4" /> */}
|
||||||
return (
|
<PathRewriteModal
|
||||||
<Button
|
value={{
|
||||||
variant="outline"
|
rewritePath: row.original.rewritePath,
|
||||||
disabled={noPathMatch}
|
rewritePathType: row.original.rewritePathType,
|
||||||
onClick={() => {
|
|
||||||
setShowRewritePathInput(true);
|
|
||||||
// Set default rewritePathType when first showing path input
|
|
||||||
if (!row.original.rewritePathType) {
|
|
||||||
updateTarget(row.original.targetId, {
|
|
||||||
...row.original,
|
|
||||||
rewritePathType: "prefix"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
onChange={(config) => updateTarget(row.original.targetId, config)}
|
||||||
+ {t("rewritePath")}
|
trigger={
|
||||||
</Button>
|
<Button
|
||||||
);
|
variant="outline"
|
||||||
}
|
className="flex items-center gap-2 p-2 max-w-md w-full text-left cursor-pointer"
|
||||||
|
disabled={noPathMatch}
|
||||||
return (
|
>
|
||||||
<div className="flex gap-2 min-w-[200px] items-center">
|
<PathRewriteDisplay
|
||||||
|
value={{
|
||||||
|
rewritePath: row.original.rewritePath,
|
||||||
|
rewritePathType: row.original.rewritePathType,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
size="sm"
|
||||||
onClick={() => {
|
variant="text"
|
||||||
setShowRewritePathInput(false);
|
className="px-1"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
updateTarget(row.original.targetId, {
|
updateTarget(row.original.targetId, {
|
||||||
...row.original,
|
...row.original,
|
||||||
rewritePath: null,
|
rewritePath: null,
|
||||||
rewritePathType: null
|
rewritePathType: null,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
×
|
×
|
||||||
</Button>
|
</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>
|
</div>
|
||||||
|
) : (
|
||||||
|
<PathRewriteModal
|
||||||
|
value={{
|
||||||
|
rewritePath: row.original.rewritePath,
|
||||||
|
rewritePathType: row.original.rewritePathType,
|
||||||
|
}}
|
||||||
|
onChange={(config) => updateTarget(row.original.targetId, config)}
|
||||||
|
trigger={
|
||||||
|
<Button variant="outline" disabled={noPathMatch}>
|
||||||
|
<Plus className="h-4 w-4 mr-2" />
|
||||||
|
{t("rewritePath")}
|
||||||
|
</Button>
|
||||||
|
}
|
||||||
|
disabled={noPathMatch}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "enabled",
|
accessorKey: "enabled",
|
||||||
|
|||||||
Reference in New Issue
Block a user