From 9557f755a5b0fca33e7ffe41d452a9e62bec2e37 Mon Sep 17 00:00:00 2001 From: Pallavi Date: Fri, 22 Aug 2025 13:07:03 +0530 Subject: [PATCH] Add Smart Host Parsing --- .../resources/[resourceId]/proxy/page.tsx | 162 +++++++++------- .../settings/resources/create/page.tsx | 182 ++++++++++-------- src/lib/parseHostTarget.ts | 15 ++ 3 files changed, 207 insertions(+), 152 deletions(-) create mode 100644 src/lib/parseHostTarget.ts diff --git a/src/app/[orgId]/settings/resources/[resourceId]/proxy/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/proxy/page.tsx index c6584219..ae831277 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/proxy/page.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/proxy/page.tsx @@ -94,6 +94,7 @@ import { CommandList } from "@app/components/ui/command"; import { Badge } from "@app/components/ui/badge"; +import { parseHostTarget } from "@app/lib/parseHostTarget"; const addTargetSchema = z.object({ ip: z.string().refine(isTargetValid), @@ -417,11 +418,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 ) ); @@ -545,7 +546,7 @@ export default function ReverseProxyTargets(props: { className={cn( "justify-between flex-1", !row.original.siteId && - "text-muted-foreground" + "text-muted-foreground" )} > {row.original.siteId @@ -614,31 +615,31 @@ export default function ReverseProxyTargets(props: { }, ...(resource.http ? [ - { - accessorKey: "method", - header: t("method"), - cell: ({ row }: { row: Row }) => ( - - ) - } - ] + { + accessorKey: "method", + header: t("method"), + cell: ({ row }: { row: Row }) => ( + + ) + } + ] : []), { accessorKey: "ip", @@ -647,13 +648,25 @@ export default function ReverseProxyTargets(props: { - updateTarget(row.original.targetId, { - ...row.original, - ip: e.target.value - }) - } + onBlur={(e) => { + const parsed = parseHostTarget(e.target.value); + if (parsed) { + updateTarget(row.original.targetId, { + ...row.original, + method: parsed.protocol, + ip: parsed.host, + port: parsed.port + }); + } else { + updateTarget(row.original.targetId, { + ...row.original, + ip: e.target.value + }); + } + }} + /> + ) }, { @@ -785,21 +798,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" + )} @@ -865,18 +878,18 @@ export default function ReverseProxyTargets(props: { ); return selectedSite && selectedSite.type === - "newt" ? (() => { - const dockerState = getDockerStateForSite(selectedSite.siteId); - return ( - refreshContainersForSite(selectedSite.siteId)} - /> - ); - })() : null; + "newt" ? (() => { + const dockerState = getDockerStateForSite(selectedSite.siteId); + return ( + refreshContainersForSite(selectedSite.siteId)} + /> + ); + })() : null; })()} @@ -942,11 +955,22 @@ export default function ReverseProxyTargets(props: { name="ip" render={({ field }) => ( - - {t("targetAddr")} - + {t("targetAddr")} - + { + const parsed = parseHostTarget(e.target.value); + if (parsed) { + addTargetForm.setValue("method", parsed.protocol); + addTargetForm.setValue("ip", parsed.host); + addTargetForm.setValue("port", parsed.port); + } else { + field.onBlur(); + } + }} + /> @@ -1048,12 +1072,12 @@ export default function ReverseProxyTargets(props: { {header.isPlaceholder ? null : flexRender( - header - .column - .columnDef - .header, - header.getContext() - )} + header + .column + .columnDef + .header, + header.getContext() + )} ) )} diff --git a/src/app/[orgId]/settings/resources/create/page.tsx b/src/app/[orgId]/settings/resources/create/page.tsx index 438b8917..9caa3655 100644 --- a/src/app/[orgId]/settings/resources/create/page.tsx +++ b/src/app/[orgId]/settings/resources/create/page.tsx @@ -88,6 +88,7 @@ import { ArrayElement } from "@server/types/ArrayElement"; import { isTargetValid } from "@server/lib/validators"; import { ListTargetsResponse } from "@server/routers/target"; import { DockerManager, DockerState } from "@app/lib/docker"; +import { parseHostTarget } from "@app/lib/parseHostTarget"; const baseResourceFormSchema = z.object({ name: z.string().min(1).max(255), @@ -164,12 +165,12 @@ export default function Page() { ...(!env.flags.allowRawResources ? [] : [ - { - id: "raw" as ResourceType, - title: t("resourceRaw"), - description: t("resourceRawDescription") - } - ]) + { + id: "raw" as ResourceType, + title: t("resourceRaw"), + description: t("resourceRawDescription") + } + ]) ]; const baseForm = useForm({ @@ -301,11 +302,11 @@ export default function Page() { targets.map((target) => target.targetId === targetId ? { - ...target, - ...data, - updated: true, - siteType: site?.type || null - } + ...target, + ...data, + updated: true, + siteType: site?.type || null + } : target ) ); @@ -520,7 +521,7 @@ export default function Page() { className={cn( "justify-between flex-1", !row.original.siteId && - "text-muted-foreground" + "text-muted-foreground" )} > {row.original.siteId @@ -589,31 +590,31 @@ export default function Page() { }, ...(baseForm.watch("http") ? [ - { - accessorKey: "method", - header: t("method"), - cell: ({ row }: { row: Row }) => ( - - ) - } - ] + { + accessorKey: "method", + header: t("method"), + cell: ({ row }: { row: Row }) => ( + + ) + } + ] : []), { accessorKey: "ip", @@ -622,12 +623,23 @@ export default function Page() { - updateTarget(row.original.targetId, { - ...row.original, - ip: e.target.value - }) - } + onBlur={(e) => { + const parsed = parseHostTarget(e.target.value); + + if (parsed) { + updateTarget(row.original.targetId, { + ...row.original, + method: parsed.protocol, + ip: parsed.host, + port: parsed.port ? Number(parsed.port) : undefined, + }); + } else { + updateTarget(row.original.targetId, { + ...row.original, + ip: e.target.value, + }); + } + }} /> ) }, @@ -909,10 +921,10 @@ export default function Page() { .target .value ? parseInt( - e - .target - .value - ) + e + .target + .value + ) : undefined ) } @@ -1015,21 +1027,21 @@ export default function Page() { 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" + )} @@ -1097,18 +1109,18 @@ export default function Page() { ); return selectedSite && selectedSite.type === - "newt" ? (() => { - const dockerState = getDockerStateForSite(selectedSite.siteId); - return ( - refreshContainersForSite(selectedSite.siteId)} - /> - ); - })() : null; + "newt" ? (() => { + const dockerState = getDockerStateForSite(selectedSite.siteId); + return ( + refreshContainersForSite(selectedSite.siteId)} + /> + ); + })() : null; })()} @@ -1176,21 +1188,25 @@ export default function Page() { )} ( - - {t( - "targetAddr" - )} - + {t("targetAddr")} { + const parsed = parseHostTarget(e.target.value); + if (parsed) { + addTargetForm.setValue("method", parsed.protocol); + addTargetForm.setValue("ip", parsed.host); + addTargetForm.setValue("port", parsed.port); + } else { + field.onBlur(); + } + }} /> @@ -1270,12 +1286,12 @@ export default function Page() { {header.isPlaceholder ? null : flexRender( - header - .column - .columnDef - .header, - header.getContext() - )} + header + .column + .columnDef + .header, + header.getContext() + )} ) )} diff --git a/src/lib/parseHostTarget.ts b/src/lib/parseHostTarget.ts new file mode 100644 index 00000000..c79c7aa3 --- /dev/null +++ b/src/lib/parseHostTarget.ts @@ -0,0 +1,15 @@ +export function parseHostTarget(input: string) { + try { + const normalized = input.match(/^https?:\/\//) ? input : `http://${input}`; + const url = new URL(normalized); + + const protocol = url.protocol.replace(":", ""); // http | https + const host = url.hostname; + const port = url.port ? parseInt(url.port, 10) : protocol === "https" ? 443 : 80; + + return { protocol, host, port }; + } catch { + return null; + } +} +