diff --git a/messages/en-US.json b/messages/en-US.json index 410d1414..a01d8b61 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1618,9 +1618,8 @@ "createInternalResourceDialogResourceProperties": "Resource Properties", "createInternalResourceDialogName": "Name", "createInternalResourceDialogSite": "Site", - "createInternalResourceDialogSelectSite": "Select site...", - "createInternalResourceDialogSearchSites": "Search sites...", - "createInternalResourceDialogNoSitesFound": "No sites found.", + "selectSite": "Select site...", + "noSitesFound": "No sites found.", "createInternalResourceDialogProtocol": "Protocol", "createInternalResourceDialogTcp": "TCP", "createInternalResourceDialogUdp": "UDP", diff --git a/server/routers/external.ts b/server/routers/external.ts index 54b48c6e..f85cc4be 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -239,9 +239,8 @@ authenticated.get( // Site Resource endpoints authenticated.put( - "/org/:orgId/site/:siteId/resource", + "/org/:orgId/site-resource", verifyOrgAccess, - verifySiteAccess, verifyUserHasAction(ActionsEnum.createSiteResource), logActionAudit(ActionsEnum.createSiteResource), siteResource.createSiteResource @@ -263,18 +262,14 @@ authenticated.get( ); authenticated.get( - "/org/:orgId/site/:siteId/resource/:siteResourceId", - verifyOrgAccess, - verifySiteAccess, + "/site-resource/:siteResourceId", verifySiteResourceAccess, verifyUserHasAction(ActionsEnum.getSiteResource), siteResource.getSiteResource ); authenticated.post( - "/org/:orgId/site/:siteId/resource/:siteResourceId", - verifyOrgAccess, - verifySiteAccess, + "/site-resource/:siteResourceId", verifySiteResourceAccess, verifyUserHasAction(ActionsEnum.updateSiteResource), logActionAudit(ActionsEnum.updateSiteResource), @@ -282,9 +277,7 @@ authenticated.post( ); authenticated.delete( - "/org/:orgId/site/:siteId/resource/:siteResourceId", - verifyOrgAccess, - verifySiteAccess, + "/site-resource/:siteResourceId", verifySiteResourceAccess, verifyUserHasAction(ActionsEnum.deleteSiteResource), logActionAudit(ActionsEnum.deleteSiteResource), diff --git a/server/routers/integration.ts b/server/routers/integration.ts index 6301bb6d..59276aae 100644 --- a/server/routers/integration.ts +++ b/server/routers/integration.ts @@ -146,9 +146,8 @@ authenticated.get( ); // Site Resource endpoints authenticated.put( - "/org/:orgId/site/:siteId/resource", + "/org/:orgId/private-resource", verifyApiKeyOrgAccess, - verifyApiKeySiteAccess, verifyApiKeyHasAction(ActionsEnum.createSiteResource), logActionAudit(ActionsEnum.createSiteResource), siteResource.createSiteResource @@ -170,18 +169,14 @@ authenticated.get( ); authenticated.get( - "/org/:orgId/site/:siteId/resource/:siteResourceId", - verifyApiKeyOrgAccess, - verifyApiKeySiteAccess, + "/site-resource/:siteResourceId", verifyApiKeySiteResourceAccess, verifyApiKeyHasAction(ActionsEnum.getSiteResource), siteResource.getSiteResource ); authenticated.post( - "/org/:orgId/site/:siteId/resource/:siteResourceId", - verifyApiKeyOrgAccess, - verifyApiKeySiteAccess, + "/site-resource/:siteResourceId", verifyApiKeySiteResourceAccess, verifyApiKeyHasAction(ActionsEnum.updateSiteResource), logActionAudit(ActionsEnum.updateSiteResource), @@ -189,9 +184,7 @@ authenticated.post( ); authenticated.delete( - "/org/:orgId/site/:siteId/resource/:siteResourceId", - verifyApiKeyOrgAccess, - verifyApiKeySiteAccess, + "/site-resource/:siteResourceId", verifyApiKeySiteResourceAccess, verifyApiKeyHasAction(ActionsEnum.deleteSiteResource), logActionAudit(ActionsEnum.deleteSiteResource), diff --git a/server/routers/siteResource/createSiteResource.ts b/server/routers/siteResource/createSiteResource.ts index c103b09e..c50a800b 100644 --- a/server/routers/siteResource/createSiteResource.ts +++ b/server/routers/siteResource/createSiteResource.ts @@ -23,7 +23,6 @@ import { z } from "zod"; import { fromError } from "zod-validation-error"; const createSiteResourceParamsSchema = z.strictObject({ - siteId: z.string().transform(Number).pipe(z.int().positive()), orgId: z.string() }); @@ -31,6 +30,7 @@ const createSiteResourceSchema = z .strictObject({ name: z.string().min(1).max(255), mode: z.enum(["host", "cidr", "port"]), + siteId: z.int(), // protocol: z.enum(["tcp", "udp"]).optional(), // proxyPort: z.int().positive().optional(), // destinationPort: z.int().positive().optional(), @@ -101,7 +101,7 @@ export type CreateSiteResourceResponse = SiteResource; registry.registerPath({ method: "put", - path: "/org/{orgId}/site/{siteId}/resource", + path: "/org/{orgId}/site-resource", description: "Create a new site resource.", tags: [OpenAPITags.Client, OpenAPITags.Org], request: { @@ -145,9 +145,10 @@ export async function createSiteResource( ); } - const { siteId, orgId } = parsedParams.data; + const { orgId } = parsedParams.data; const { name, + siteId, mode, // protocol, // proxyPort, diff --git a/server/routers/siteResource/getSiteResource.ts b/server/routers/siteResource/getSiteResource.ts index 7cb9e620..f97a5e22 100644 --- a/server/routers/siteResource/getSiteResource.ts +++ b/server/routers/siteResource/getSiteResource.ts @@ -63,7 +63,7 @@ export type GetSiteResourceResponse = NonNullable< registry.registerPath({ method: "get", - path: "/org/{orgId}/site/{siteId}/resource/{siteResourceId}", + path: "/site-resource/{siteResourceId}", description: "Get a specific site resource by siteResourceId.", tags: [OpenAPITags.Client, OpenAPITags.Org], request: { diff --git a/server/routers/siteResource/updateSiteResource.ts b/server/routers/siteResource/updateSiteResource.ts index c3360e6f..44b2bc33 100644 --- a/server/routers/siteResource/updateSiteResource.ts +++ b/server/routers/siteResource/updateSiteResource.ts @@ -32,14 +32,13 @@ import { } from "@server/lib/rebuildClientAssociations"; const updateSiteResourceParamsSchema = z.strictObject({ - siteResourceId: z.string().transform(Number).pipe(z.int().positive()), - siteId: z.string().transform(Number).pipe(z.int().positive()), - orgId: z.string() + siteResourceId: z.string().transform(Number).pipe(z.int().positive()) }); const updateSiteResourceSchema = z .strictObject({ name: z.string().min(1).max(255).optional(), + siteId: z.int(), // mode: z.enum(["host", "cidr", "port"]).optional(), mode: z.enum(["host", "cidr"]).optional(), // protocol: z.enum(["tcp", "udp"]).nullish(), @@ -78,7 +77,10 @@ const updateSiteResourceSchema = z const domainRegex = /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)*[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/; const isValidDomain = domainRegex.test(data.destination); - const isValidAlias = data.alias !== undefined && data.alias !== null && data.alias.trim() !== ""; + const isValidAlias = + data.alias !== undefined && + data.alias !== null && + data.alias.trim() !== ""; return isValidDomain && isValidAlias; // require the alias to be set in the case of domain } @@ -111,7 +113,7 @@ export type UpdateSiteResourceResponse = SiteResource; registry.registerPath({ method: "post", - path: "/org/{orgId}/site/{siteId}/resource/{siteResourceId}", + path: "/site-resource/{siteResourceId}", description: "Update a site resource.", tags: [OpenAPITags.Client, OpenAPITags.Org], request: { @@ -155,9 +157,10 @@ export async function updateSiteResource( ); } - const { siteResourceId, siteId, orgId } = parsedParams.data; + const { siteResourceId } = parsedParams.data; const { name, + siteId, // because it can change mode, destination, alias, @@ -173,7 +176,7 @@ export async function updateSiteResource( const [site] = await db .select() .from(sites) - .where(and(eq(sites.siteId, siteId), eq(sites.orgId, orgId))) + .where(eq(sites.siteId, siteId)) .limit(1); if (!site) { @@ -184,13 +187,7 @@ export async function updateSiteResource( const [existingSiteResource] = await db .select() .from(siteResources) - .where( - and( - eq(siteResources.siteResourceId, siteResourceId), - eq(siteResources.siteId, siteId), - eq(siteResources.orgId, orgId) - ) - ) + .where(and(eq(siteResources.siteResourceId, siteResourceId))) .limit(1); if (!existingSiteResource) { @@ -206,7 +203,7 @@ export async function updateSiteResource( .from(siteResources) .where( and( - eq(siteResources.orgId, orgId), + eq(siteResources.orgId, existingSiteResource.orgId), eq(siteResources.alias, alias.trim()), ne(siteResources.siteResourceId, siteResourceId) // exclude self ) @@ -230,6 +227,7 @@ export async function updateSiteResource( .update(siteResources) .set({ name: name, + siteId: siteId, mode: mode, destination: destination, enabled: enabled, @@ -238,13 +236,7 @@ export async function updateSiteResource( udpPortRangeString: udpPortRangeString, disableIcmp: disableIcmp }) - .where( - and( - eq(siteResources.siteResourceId, siteResourceId), - eq(siteResources.siteId, siteId), - eq(siteResources.orgId, orgId) - ) - ) + .where(and(eq(siteResources.siteResourceId, siteResourceId))) .returning(); //////////////////// update the associations //////////////////// diff --git a/src/components/ClientResourcesTable.tsx b/src/components/ClientResourcesTable.tsx index 758b6e12..023ef00c 100644 --- a/src/components/ClientResourcesTable.tsx +++ b/src/components/ClientResourcesTable.tsx @@ -100,7 +100,7 @@ export default function ClientResourcesTable({ ) => { try { await api - .delete(`/org/${orgId}/site/${siteId}/resource/${resourceId}`) + .delete(`/site-resource/${resourceId}`) .then(() => { startTransition(() => { router.refresh(); @@ -327,6 +327,7 @@ export default function ClientResourcesTable({ setOpen={setIsEditDialogOpen} resource={editingResource} orgId={orgId} + sites={sites} onSuccess={() => { router.refresh(); setEditingResource(null); diff --git a/src/components/CreateInternalResourceDialog.tsx b/src/components/CreateInternalResourceDialog.tsx index 894315b8..00e8ce96 100644 --- a/src/components/CreateInternalResourceDialog.tsx +++ b/src/components/CreateInternalResourceDialog.tsx @@ -395,9 +395,10 @@ export default function CreateInternalResourceDialog({ } const response = await api.put>( - `/org/${orgId}/site/${data.siteId}/resource`, + `/org/${orgId}/site-resource`, { name: data.name, + siteId: data.siteId, mode: data.mode, // protocol: data.protocol, // proxyPort: data.mode === "port" ? data.proxyPort : undefined, @@ -548,7 +549,7 @@ export default function CreateInternalResourceDialog({ {t( - "createInternalResourceDialogSite" + "site" )} @@ -572,7 +573,7 @@ export default function CreateInternalResourceDialog({ field.value )?.name : t( - "createInternalResourceDialogSelectSite" + "selectSite" )} @@ -582,13 +583,13 @@ export default function CreateInternalResourceDialog({ {t( - "createInternalResourceDialogNoSitesFound" + "noSitesFound" )} diff --git a/src/components/EditInternalResourceDialog.tsx b/src/components/EditInternalResourceDialog.tsx index cfd4fbc1..5d5745c7 100644 --- a/src/components/EditInternalResourceDialog.tsx +++ b/src/components/EditInternalResourceDialog.tsx @@ -41,6 +41,22 @@ import { Tag, TagInput } from "@app/components/tags/tag-input"; import { UserType } from "@server/types/UserTypes"; import { useQueries, useQuery, useQueryClient } from "@tanstack/react-query"; import { orgQueries, resourceQueries } from "@app/lib/queries"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList +} from "@app/components/ui/command"; +import { + Popover, + PopoverContent, + PopoverTrigger +} from "@app/components/ui/popover"; +import { cn } from "@app/lib/cn"; +import { ListSitesResponse } from "@server/routers/site"; +import { Check, ChevronsUpDown } from "lucide-react"; // import { InfoPopup } from "@app/components/ui/info-popup"; // Helper to validate port range string format @@ -118,6 +134,8 @@ const getPortStringFromMode = (mode: PortMode, customValue: string): string | un return customValue; }; +type Site = ListSitesResponse["sites"][0]; + type InternalResourceData = { id: number; name: string; @@ -141,6 +159,7 @@ type EditInternalResourceDialogProps = { setOpen: (val: boolean) => void; resource: InternalResourceData; orgId: string; + sites: Site[]; onSuccess?: () => void; }; @@ -149,6 +168,7 @@ export default function EditInternalResourceDialog({ setOpen, resource, orgId, + sites, onSuccess }: EditInternalResourceDialogProps) { const t = useTranslations(); @@ -161,6 +181,7 @@ export default function EditInternalResourceDialog({ .string() .min(1, t("editInternalResourceDialogNameRequired")) .max(255, t("editInternalResourceDialogNameMaxLength")), + siteId: z.number().int().positive(), mode: z.enum(["host", "cidr", "port"]), // protocol: z.enum(["tcp", "udp"]).nullish(), // proxyPort: z.int().positive().min(1, t("editInternalResourceDialogProxyPortMin")).max(65535, t("editInternalResourceDialogProxyPortMax")).nullish(), @@ -349,10 +370,15 @@ export default function EditInternalResourceDialog({ : "" ); + const availableSites = sites.filter( + (site) => site.type === "newt" && site.subnet + ); + const form = useForm({ resolver: zodResolver(formSchema), defaultValues: { name: resource.name, + siteId: resource.siteId, mode: resource.mode || "host", // protocol: (resource.protocol as "tcp" | "udp" | null | undefined) ?? undefined, // proxyPort: resource.proxyPort ?? undefined, @@ -421,9 +447,10 @@ export default function EditInternalResourceDialog({ // Update the site resource await api.post( - `/org/${orgId}/site/${resource.siteId}/resource/${resource.id}`, + `/site-resource/${resource.id}`, { name: data.name, + siteId: data.siteId, mode: data.mode, // protocol: data.mode === "port" ? data.protocol : null, // proxyPort: data.mode === "port" ? data.proxyPort : null, @@ -504,6 +531,7 @@ export default function EditInternalResourceDialog({ if (resourceChanged) { form.reset({ name: resource.name, + siteId: resource.siteId, mode: resource.mode || "host", destination: resource.destination || "", alias: resource.alias ?? null, @@ -559,6 +587,7 @@ export default function EditInternalResourceDialog({ // reset only on close form.reset({ name: resource.name, + siteId: resource.siteId, mode: resource.mode || "host", // protocol: (resource.protocol as "tcp" | "udp" | null | undefined) ?? undefined, // proxyPort: resource.proxyPort ?? undefined, @@ -636,6 +665,99 @@ export default function EditInternalResourceDialog({ )} /> + ( + + + {t( + "site" + )} + + + + + + + + + + + + + {t( + "noSitesFound" + )} + + + {availableSites.map( + ( + site + ) => ( + { + field.onChange( + site.siteId + ); + }} + > + + { + site.name + } + + ) + )} + + + + + + + + )} + /> +