diff --git a/messages/en-US.json b/messages/en-US.json index 895ee1332..a9c9c5583 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -148,6 +148,11 @@ "createLink": "Create Link", "resourcesNotFound": "No resources found", "resourceSearch": "Search resources", + "machineSearch": "Search machines", + "machinesSearch": "Search machine clients...", + "machineNotFound": "No machines found", + "userDeviceSearch": "Search user devices", + "userDevicesSearch": "Search user devices...", "openMenu": "Open menu", "resource": "Resource", "title": "Title", diff --git a/server/routers/resource/getResource.ts b/server/routers/resource/getResource.ts index cd870dcbf..7a52c0a85 100644 --- a/server/routers/resource/getResource.ts +++ b/server/routers/resource/getResource.ts @@ -1,15 +1,14 @@ -import { Request, Response, NextFunction } from "express"; -import { z } from "zod"; -import { db } from "@server/db"; -import { Resource, resources, sites } from "@server/db"; -import { eq, and } from "drizzle-orm"; +import { db, resources } from "@server/db"; import response from "@server/lib/response"; -import HttpCode from "@server/types/HttpCode"; -import createHttpError from "http-errors"; -import { fromError } from "zod-validation-error"; -import logger from "@server/logger"; import stoi from "@server/lib/stoi"; +import logger from "@server/logger"; import { OpenAPITags, registry } from "@server/openApi"; +import HttpCode from "@server/types/HttpCode"; +import { and, eq } from "drizzle-orm"; +import { NextFunction, Request, Response } from "express"; +import createHttpError from "http-errors"; +import { z } from "zod"; +import { fromError } from "zod-validation-error"; const getResourceSchema = z.strictObject({ resourceId: z diff --git a/server/routers/target/listTargets.ts b/server/routers/target/listTargets.ts index e4ef45f3b..18e932afa 100644 --- a/server/routers/target/listTargets.ts +++ b/server/routers/target/listTargets.ts @@ -40,6 +40,7 @@ function queryTargets(resourceId: number) { resourceId: targets.resourceId, siteId: targets.siteId, siteType: sites.type, + siteName: sites.name, hcEnabled: targetHealthCheck.hcEnabled, hcPath: targetHealthCheck.hcPath, hcScheme: targetHealthCheck.hcScheme, diff --git a/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx b/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx index 51f11a2c3..aff10dc52 100644 --- a/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx +++ b/src/app/[orgId]/settings/resources/proxy/[niceId]/proxy/page.tsx @@ -124,20 +124,15 @@ export default function ReverseProxyTargetsPage(props: { resourceId: resource.resourceId }) ); - const { data: sites = [], isLoading: isLoadingSites } = useQuery( - orgQueries.sites({ - orgId: params.orgId - }) - ); - if (isLoadingSites || isLoadingTargets) { + if (isLoadingTargets) { return null; } return ( @@ -160,12 +155,12 @@ export default function ReverseProxyTargetsPage(props: { } function ProxyResourceTargetsForm({ - sites, + orgId, initialTargets, resource }: { initialTargets: LocalTarget[]; - sites: ListSitesResponse["sites"]; + orgId: string; resource: GetResourceResponse; }) { const t = useTranslations(); @@ -243,17 +238,21 @@ function ProxyResourceTargetsForm({ }); }, []); + const { data: sites = [] } = useQuery( + orgQueries.sites({ + orgId + }) + ); + const updateTarget = useCallback( (targetId: number, data: Partial) => { setTargets((prevTargets) => { - const site = sites.find((site) => site.siteId === data.siteId); return prevTargets.map((target) => target.targetId === targetId ? { ...target, ...data, - updated: true, - siteType: site ? site.type : target.siteType + updated: true } : target ); @@ -453,7 +452,7 @@ function ProxyResourceTargetsForm({ return ( 0 ? sites[0].siteId : 0, + siteName: sites.length > 0 ? sites[0].name : "", path: isHttp ? null : null, pathMatchType: isHttp ? null : null, rewritePath: isHttp ? null : null, diff --git a/src/app/[orgId]/settings/resources/proxy/create/page.tsx b/src/app/[orgId]/settings/resources/proxy/create/page.tsx index fbc916479..b651a06bb 100644 --- a/src/app/[orgId]/settings/resources/proxy/create/page.tsx +++ b/src/app/[orgId]/settings/resources/proxy/create/page.tsx @@ -216,9 +216,7 @@ export default function Page() { const [remoteExitNodes, setRemoteExitNodes] = useState< ListRemoteExitNodesResponse["remoteExitNodes"] >([]); - const [loadingExitNodes, setLoadingExitNodes] = useState( - build === "saas" - ); + const [loadingExitNodes, setLoadingExitNodes] = useState(build === "saas"); const [createLoading, setCreateLoading] = useState(false); const [showSnippets, setShowSnippets] = useState(false); @@ -282,6 +280,7 @@ export default function Page() { method: isHttp ? "http" : null, port: 0, siteId: sites.length > 0 ? sites[0].siteId : 0, + siteName: sites.length > 0 ? sites[0].name : "", path: isHttp ? null : null, pathMatchType: isHttp ? null : null, rewritePath: isHttp ? null : null, @@ -336,8 +335,7 @@ export default function Page() { // In saas mode with no exit nodes, force HTTP const showTypeSelector = - build !== "saas" || - (!loadingExitNodes && remoteExitNodes.length > 0); + build !== "saas" || (!loadingExitNodes && remoteExitNodes.length > 0); const baseForm = useForm({ resolver: zodResolver(baseResourceFormSchema), @@ -600,7 +598,10 @@ export default function Page() { toast({ variant: "destructive", title: t("resourceErrorCreate"), - description: formatAxiosError(e, t("resourceErrorCreateMessageDescription")) + description: formatAxiosError( + e, + t("resourceErrorCreateMessageDescription") + ) }); } @@ -826,7 +827,8 @@ export default function Page() { cell: ({ row }) => ( - allResources - .filter((r) => r.http) - .map((r) => ({ - resourceId: r.resourceId, - name: r.name, - niceId: r.niceId, - resourceUrl: `${r.ssl ? "https://" : "http://"}${toUnicode(r.fullDomain || "")}/` - })), - [allResources] - ); + const [selectedResource, setSelectedResource] = + useState(null); + + // const resources = useMemo( + // () => + // allResources + // .filter((r) => r.http) + // .map((r) => ({ + // resourceId: r.resourceId, + // name: r.name, + // niceId: r.niceId, + // resourceUrl: `${r.ssl ? "https://" : "http://"}${toUnicode(r.fullDomain || "")}/` + // })), + // [allResources] + // ); const formSchema = z.object({ resourceId: z.number({ message: t("shareErrorSelectResource") }), @@ -199,15 +203,11 @@ export default function CreateShareLinkForm({ setAccessToken(token.accessToken); setAccessTokenId(token.accessTokenId); - const resource = resources.find( - (r) => r.resourceId === values.resourceId - ); - onCreated?.({ accessTokenId: token.accessTokenId, resourceId: token.resourceId, resourceName: values.resourceName, - resourceNiceId: resource ? resource.niceId : "", + resourceNiceId: selectedResource ? selectedResource.niceId : "", title: token.title, createdAt: token.createdAt, expiresAt: token.expiresAt @@ -217,11 +217,6 @@ export default function CreateShareLinkForm({ setLoading(false); } - function getSelectedResourceName(id: number) { - const resource = resources.find((r) => r.resourceId === id); - return `${resource?.name}`; - } - return ( <> -
+
{!link && (
- {field.value - ? getSelectedResourceName( - field.value - ) + {selectedResource?.name + ? selectedResource.name : t( "resourceSelect" )} @@ -281,59 +274,34 @@ export default function CreateShareLinkForm({ - - - - - {t( - "resourcesNotFound" - )} - - - {resources.map( - ( - r - ) => ( - { - form.setValue( - "resourceId", - r.resourceId - ); - form.setValue( - "resourceName", - r.name - ); - form.setValue( - "resourceUrl", - r.resourceUrl - ); - }} - > - - {`${r.name}`} - - ) - )} - - - + { + form.setValue( + "resourceId", + r.resourceId + ); + form.setValue( + "resourceName", + r.name + ); + form.setValue( + "resourceUrl", + `${r.ssl ? "https://" : "http://"}${toUnicode(r.fullDomain || "")}/` + ); + setSelectedResource( + r + ); + }} + /> diff --git a/src/components/InternalResourceForm.tsx b/src/components/InternalResourceForm.tsx index 4486b05fe..2de907079 100644 --- a/src/components/InternalResourceForm.tsx +++ b/src/components/InternalResourceForm.tsx @@ -1,15 +1,10 @@ "use client"; +import { HorizontalTabs } from "@app/components/HorizontalTabs"; +import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert"; +import { StrategySelect } from "@app/components/StrategySelect"; import { Tag, TagInput } from "@app/components/tags/tag-input"; import { Button } from "@app/components/ui/button"; -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, - CommandList -} from "@app/components/ui/command"; import { Form, FormControl, @@ -32,24 +27,24 @@ import { SelectValue } from "@app/components/ui/select"; import { Switch } from "@app/components/ui/switch"; -import { getUserDisplayName } from "@app/lib/getUserDisplayName"; +import { useEnvContext } from "@app/hooks/useEnvContext"; +import { usePaidStatus } from "@app/hooks/usePaidStatus"; import { cn } from "@app/lib/cn"; +import { getUserDisplayName } from "@app/lib/getUserDisplayName"; import { orgQueries, resourceQueries } from "@app/lib/queries"; -import { useQueries, useQuery } from "@tanstack/react-query"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { tierMatrix } from "@server/lib/billing/tierMatrix"; import { ListSitesResponse } from "@server/routers/site"; import { UserType } from "@server/types/UserTypes"; -import { Check, ChevronsUpDown, ExternalLink } from "lucide-react"; +import { useQuery } from "@tanstack/react-query"; +import { ChevronsUpDown, ExternalLink } from "lucide-react"; import { useTranslations } from "next-intl"; import { useEffect, useRef, useState } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { useEnvContext } from "@app/hooks/useEnvContext"; -import { usePaidStatus } from "@app/hooks/usePaidStatus"; -import { tierMatrix } from "@server/lib/billing/tierMatrix"; -import { HorizontalTabs } from "@app/components/HorizontalTabs"; -import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert"; -import { StrategySelect } from "@app/components/StrategySelect"; +import { SitesSelector, type Selectedsite } from "./site-selector"; +import { CaretSortIcon } from "@radix-ui/react-icons"; +import { MachinesSelector } from "./machines-selector"; // --- Helpers (shared) --- @@ -258,7 +253,14 @@ export function InternalResourceForm({ authDaemonPort: z.number().int().positive().optional().nullable(), roles: z.array(tagSchema).optional(), users: z.array(tagSchema).optional(), - clients: z.array(tagSchema).optional() + clients: z + .array( + z.object({ + clientId: z.number(), + name: z.string() + }) + ) + .optional() }); type FormData = z.infer; @@ -267,7 +269,7 @@ export function InternalResourceForm({ const rolesQuery = useQuery(orgQueries.roles({ orgId })); const usersQuery = useQuery(orgQueries.users({ orgId })); - const clientsQuery = useQuery(orgQueries.clients({ orgId })); + const clientsQuery = useQuery(orgQueries.machineClients({ orgId })); const resourceRolesQuery = useQuery({ ...resourceQueries.siteResourceRoles({ siteResourceId: siteResourceId ?? 0 @@ -325,12 +327,9 @@ export function InternalResourceForm({ })); } if (clientsData) { - existingClients = ( - clientsData as { clientId: number; name: string }[] - ).map((c) => ({ - id: c.clientId.toString(), - text: c.name - })); + existingClients = [ + ...(clientsData as { clientId: number; name: string }[]) + ]; } } @@ -416,6 +415,10 @@ export function InternalResourceForm({ clients: [] }; + const [selectedSite, setSelectedSite] = useState( + availableSites[0] + ); + const form = useForm({ resolver: zodResolver(formSchema), defaultValues @@ -537,9 +540,15 @@ export function InternalResourceForm({ return ( - onSubmit(values as InternalResourceFormValues) - )} + onSubmit={form.handleSubmit((values) => { + onSubmit({ + ...values, + clients: (values.clients ?? []).map((c) => ({ + id: c.clientId.toString(), + text: c.name + })) + }); + })} className="space-y-6" id={formId} > @@ -600,46 +609,14 @@ export function InternalResourceForm({ - - - - - {t("noSitesFound")} - - - {availableSites.map( - (site) => ( - - field.onChange( - site.siteId - ) - } - > - - {site.name} - - ) - )} - - - + { + setSelectedSite(site); + field.onChange(site.siteId); + }} + /> @@ -649,8 +626,7 @@ export function InternalResourceForm({
-
+
-
+