change target config ui for create resource

This commit is contained in:
Pallavi Kumari
2025-10-09 01:33:42 +05:30
parent a6086d3724
commit 94137e587c
2 changed files with 198 additions and 230 deletions

View File

@@ -969,7 +969,7 @@ export default function ReverseProxyTargets(props: {
</SelectContent> </SelectContent>
</Select> </Select>
<div className="flex items-center justify-center bg-gray-400 text-black px-1 h-9"> <div className="flex items-center justify-center bg-gray-200 text-black px-2 h-9">
{"://"} {"://"}
</div> </div>
@@ -1009,11 +1009,10 @@ export default function ReverseProxyTargets(props: {
} }
}} }}
/> />
<div className="flex items-center justify-center bg-gray-400 text-black px-1 h-9"> <div className="flex items-center justify-center bg-gray-200 text-black px-2 h-9">
{":"} {":"}
</div> </div>
<Input <Input
type="number"
placeholder="Port" placeholder="Port"
defaultValue={row.original.port} defaultValue={row.original.port}
className="min-w-[60px] pl-0 border-none placeholder-gray-400" className="min-w-[60px] pl-0 border-none placeholder-gray-400"

View File

@@ -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, Info, MoveRight, Plus, SquareArrowOutUpRight } from "lucide-react"; import { ArrowRight, CircleCheck, CircleX, Info, 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";
@@ -96,6 +96,7 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@app/c
import { PathMatchDisplay, PathMatchModal, PathRewriteDisplay, PathRewriteModal } from "@app/components/PathMatchRenameModal"; import { PathMatchDisplay, PathMatchModal, PathRewriteDisplay, PathRewriteModal } from "@app/components/PathMatchRenameModal";
import { TargetModal } from "@app/components/TargetModal"; import { TargetModal } from "@app/components/TargetModal";
import { TargetDisplay } from "@app/components/TargetDisplay"; import { TargetDisplay } from "@app/components/TargetDisplay";
import { Badge } from "@app/components/ui/badge";
const baseResourceFormSchema = z.object({ const baseResourceFormSchema = z.object({
@@ -606,6 +607,21 @@ export default function Page() {
}, []); }, []);
const columns: ColumnDef<LocalTarget>[] = [ const columns: ColumnDef<LocalTarget>[] = [
{
accessorKey: "enabled",
header: t("enabled"),
cell: ({ row }) => (
<Switch
defaultChecked={row.original.enabled}
onCheckedChange={(val) =>
updateTarget(row.original.targetId, {
...row.original,
enabled: val
})
}
/>
)
},
{ {
id: "priority", id: "priority",
header: () => ( header: () => (
@@ -674,55 +690,37 @@ export default function Page() {
</Button> </Button>
} }
/> />
<Button <MoveRight className="ml-1 h-4 w-4" />
variant="text"
size="sm"
className="px-1"
onClick={(e) => {
e.stopPropagation();
updateTarget(row.original.targetId, {
...row.original,
path: null,
pathMatchType: null,
rewritePath: null,
rewritePathType: null
});
}}
>
×
</Button>
{/* <MoveRight className="ml-1 h-4 w-4" /> */}
</div> </div>
) : ( ) : (
<PathMatchModal <div className="flex items-center gap-1">
value={{ <PathMatchModal
path: row.original.path, value={{
path: row.original.path,
pathMatchType: row.original.pathMatchType, pathMatchType: row.original.pathMatchType,
}} }}
onChange={(config) => updateTarget(row.original.targetId, config)} onChange={(config) => updateTarget(row.original.targetId, config)}
trigger={ trigger={
<Button variant="outline"> <Button variant="outline">
<Plus className="h-4 w-4 mr-2" /> <Plus className="h-4 w-4 mr-2" />
{t("matchPath")} {t("matchPath")}
</Button> </Button>
} }
/> />
<MoveRight className="ml-1 h-4 w-4" />
</div>
); );
}, }
}, },
{ {
accessorKey: "siteId", accessorKey: "address",
header: t("site"), header: t("address"),
cell: ({ row }) => { cell: ({ row }) => {
const selectedSite = sites.find( const selectedSite = sites.find(
(site) => site.siteId === row.original.siteId (site) => site.siteId === row.original.siteId
); );
const handleContainerSelectForTarget = ( const handleContainerSelectForTarget = (hostname: string, port?: number) => {
hostname: string,
port?: number
) => {
updateTarget(row.original.targetId, { updateTarget(row.original.targetId, {
...row.original, ...row.original,
ip: hostname ip: hostname
@@ -736,158 +734,151 @@ export default function Page() {
}; };
return ( return (
<div className="flex gap-2 items-center">
<Popover>
<PopoverTrigger asChild>
<Button
variant="outline"
role="combobox"
className={cn(
"justify-between flex-1",
!row.original.siteId &&
"text-muted-foreground"
)}
>
{row.original.siteId
? selectedSite?.name
: t("siteSelect")}
<CaretSortIcon className="ml-2 h-4 w-4 shrink-0 opacity-50" />
</Button>
</PopoverTrigger>
<PopoverContent className="p-0">
<Command>
<CommandInput
placeholder={t("siteSearch")}
/>
<CommandList>
<CommandEmpty>
{t("siteNotFound")}
</CommandEmpty>
<CommandGroup>
{sites.map((site) => (
<CommandItem
value={`${site.siteId}:${site.name}:${site.niceId}`}
key={site.siteId}
onSelect={() => {
updateTarget(
row.original
.targetId,
{
siteId: site.siteId
}
);
}}
>
<CheckIcon
className={cn(
"mr-2 h-4 w-4",
site.siteId ===
row.original
.siteId
? "opacity-100"
: "opacity-0"
)}
/>
{site.name}
</CommandItem>
))}
</CommandGroup>
</CommandList>
</Command>
</PopoverContent>
</Popover>
{selectedSite && selectedSite.type === "newt" && (() => {
const dockerState = getDockerStateForSite(selectedSite.siteId);
return (
<ContainersSelector
site={selectedSite}
containers={dockerState.containers}
isAvailable={dockerState.isAvailable}
onContainerSelect={handleContainerSelectForTarget}
onRefresh={() => refreshContainersForSite(selectedSite.siteId)}
/>
);
})()}
</div>
);
}
},
{
accessorKey: "target",
header: t("target"),
cell: ({ row }) => {
const hasTarget = !!(row.original.ip || row.original.port || row.original.method);
return hasTarget ? (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<TargetModal <Button variant={"outline"} className="w-full justify-start py-0 space-x-2 px-0 hover:bg-card cursor-default">
value={{ <Popover>
method: row.original.method, <PopoverTrigger asChild>
ip: row.original.ip, <Button
port: row.original.port variant="ghost"
}} role="combobox"
onChange={(config) => className={cn(
updateTarget(row.original.targetId, { "min-w-[90px] justify-between text-sm font-medium border-r pr-4 rounded-none h-8 hover:bg-transparent",
...row.original, !row.original.siteId && "text-muted-foreground"
...config )}
}) >
} {row.original.siteId ? selectedSite?.name : t("siteSelect")}
showMethod={baseForm.watch("http")} <CaretSortIcon className="ml-2h-4 w-4 shrink-0 opacity-50" />
trigger={ </Button>
<Button </PopoverTrigger>
variant="outline" <PopoverContent className="p-0 w-[180px]">
className="flex items-center gap-2 max-w-md text-left cursor-pointer" <Command>
> <CommandInput placeholder={t("siteSearch")} />
<TargetDisplay <CommandList>
value={{ <CommandEmpty>{t("siteNotFound")}</CommandEmpty>
method: row.original.method, <CommandGroup>
ip: row.original.ip, {sites.map((site) => (
port: row.original.port <CommandItem
}} key={site.siteId}
showMethod={baseForm.watch("http")} value={`${site.siteId}:${site.name}`}
/> onSelect={() =>
</Button> updateTarget(row.original.targetId, { siteId: site.siteId })
} }
/> >
<Button <CheckIcon
variant="ghost" className={cn(
size="sm" "mr-2 h-4 w-4",
className="h-8 w-8 p-0" site.siteId === row.original.siteId
onClick={(e) => { ? "opacity-100"
e.stopPropagation(); : "opacity-0"
updateTarget(row.original.targetId, { )}
...row.original, />
method: null, {site.name}
ip: "", </CommandItem>
port: undefined ))}
}); </CommandGroup>
}} </CommandList>
> </Command>
× </PopoverContent>
</Popover>
{selectedSite &&
selectedSite.type === "newt" &&
(() => {
const dockerState = getDockerStateForSite(
selectedSite.siteId
);
return (
<ContainersSelector
site={selectedSite}
containers={dockerState.containers}
isAvailable={dockerState.isAvailable}
onContainerSelect={
handleContainerSelectForTarget
}
onRefresh={() =>
refreshContainersForSite(
selectedSite.siteId
)
}
/>
);
})()}
<Select
defaultValue={row.original.method ?? "http"}
onValueChange={(value) =>
updateTarget(row.original.targetId, {
...row.original,
method: value,
})
}
>
<SelectTrigger className="h-8 px-2 w-[70px] text-sm font-normal border-none bg-transparent shadow-none focus:ring-0 focus:outline-none focus-visible:ring-0 data-[state=open]:bg-transparent">
{row.original.method || "http"}
</SelectTrigger>
<SelectContent>
<SelectItem value="http">http</SelectItem>
<SelectItem value="https">https</SelectItem>
<SelectItem value="h2c">h2c</SelectItem>
</SelectContent>
</Select>
<div className="flex items-center justify-center bg-gray-200 text-black px-2 h-9">
{"://"}
</div>
<Input
defaultValue={row.original.ip}
placeholder="IP / Hostname"
className="min-w-[130px] border-none placeholder-gray-400"
onBlur={(e) => {
const input = e.target.value.trim();
const hasProtocol = /^(https?|h2c):\/\//.test(input);
const hasPort = /:\d+(?:\/|$)/.test(input);
if (hasProtocol || hasPort) {
const parsed = parseHostTarget(input);
if (parsed) {
updateTarget(row.original.targetId, {
...row.original,
method: hasProtocol
? parsed.protocol
: row.original.method,
ip: parsed.host,
port: hasPort
? parsed.port
: row.original.port
});
} else {
updateTarget(row.original.targetId, {
...row.original,
ip: input
});
}
} else {
updateTarget(row.original.targetId, {
...row.original,
ip: input
});
}
}}
/>
<div className="flex items-center justify-center bg-gray-200 text-black px-2 h-9">
{":"}
</div>
<Input
placeholder="Port"
defaultValue={row.original.port}
className="min-w-[60px] pl-0 border-none placeholder-gray-400"
onBlur={(e) =>
updateTarget(row.original.targetId, {
...row.original,
port: parseInt(e.target.value, 10)
})
}
/>
</Button> </Button>
<MoveRight className="mr-2 h-4 w-4" /> <MoveRight className="ml-1 h-4 w-4" />
</div> </div>
) : (
<TargetModal
value={{
method: row.original.method,
ip: row.original.ip,
port: row.original.port
}}
onChange={(config) =>
updateTarget(row.original.targetId, {
...row.original,
...config
})
}
showMethod={baseForm.watch("http")}
trigger={
<Button variant="outline">
<Plus className="h-4 w-4 mr-2" />
{t("configureTarget")}
</Button>
}
/>
); );
} }
}, },
@@ -895,18 +886,22 @@ export default function Page() {
accessorKey: "rewritePath", accessorKey: "rewritePath",
header: t("rewritePath"), header: t("rewritePath"),
cell: ({ row }) => { cell: ({ row }) => {
const hasRewritePath = !!(row.original.rewritePath || row.original.rewritePathType); const hasRewritePath = !!(
const noPathMatch = !row.original.path && !row.original.pathMatchType; row.original.rewritePath || row.original.rewritePathType
);
const noPathMatch =
!row.original.path && !row.original.pathMatchType;
return hasRewritePath && !noPathMatch ? ( return hasRewritePath && !noPathMatch ? (
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
{/* <MoveRight className="mr-2 h-4 w-4" /> */}
<PathRewriteModal <PathRewriteModal
value={{ value={{
rewritePath: row.original.rewritePath, rewritePath: row.original.rewritePath,
rewritePathType: row.original.rewritePathType, rewritePathType: row.original.rewritePathType
}} }}
onChange={(config) => updateTarget(row.original.targetId, config)} onChange={(config) =>
updateTarget(row.original.targetId, config)
}
trigger={ trigger={
<Button <Button
variant="outline" variant="outline"
@@ -915,36 +910,25 @@ export default function Page() {
> >
<PathRewriteDisplay <PathRewriteDisplay
value={{ value={{
rewritePath: row.original.rewritePath, rewritePath:
rewritePathType: row.original.rewritePathType, row.original.rewritePath,
rewritePathType:
row.original.rewritePathType
}} }}
/> />
</Button> </Button>
} }
/> />
<Button
size="sm"
variant="text"
className="px-1"
onClick={(e) => {
e.stopPropagation();
updateTarget(row.original.targetId, {
...row.original,
rewritePath: null,
rewritePathType: null,
});
}}
>
×
</Button>
</div> </div>
) : ( ) : (
<PathRewriteModal <PathRewriteModal
value={{ value={{
rewritePath: row.original.rewritePath, rewritePath: row.original.rewritePath,
rewritePathType: row.original.rewritePathType, rewritePathType: row.original.rewritePathType
}} }}
onChange={(config) => updateTarget(row.original.targetId, config)} onChange={(config) =>
updateTarget(row.original.targetId, config)
}
trigger={ trigger={
<Button variant="outline" disabled={noPathMatch}> <Button variant="outline" disabled={noPathMatch}>
<Plus className="h-4 w-4 mr-2" /> <Plus className="h-4 w-4 mr-2" />
@@ -954,22 +938,7 @@ export default function Page() {
disabled={noPathMatch} disabled={noPathMatch}
/> />
); );
}, }
},
{
accessorKey: "enabled",
header: t("enabled"),
cell: ({ row }) => (
<Switch
defaultChecked={row.original.enabled}
onCheckedChange={(val) =>
updateTarget(row.original.targetId, {
...row.original,
enabled: val
})
}
/>
)
}, },
{ {
id: "actions", id: "actions",