diff --git a/frontend/src/const/routes.ts b/frontend/src/const/routes.ts index 410df7440..c9a6b326d 100644 --- a/frontend/src/const/routes.ts +++ b/frontend/src/const/routes.ts @@ -62,7 +62,11 @@ export const ROUTE_PATHS = Object.freeze({ "/organization/app-connections/$appConnection/oauth/callback", "/_authenticate/_inject-org-details/_org-layout/organization/app-connections/$appConnection/oauth/callback" ) - } + }, + NetworkingPage: setRoute( + "/organization/networking", + "/_authenticate/_inject-org-details/_org-layout/organization/networking" + ) }, SecretManager: { ApprovalPage: setRoute( diff --git a/frontend/src/hooks/api/index.tsx b/frontend/src/hooks/api/index.tsx index 6167f6e07..e9e80feec 100644 --- a/frontend/src/hooks/api/index.tsx +++ b/frontend/src/hooks/api/index.tsx @@ -31,6 +31,7 @@ export * from "./pkiSubscriber"; export * from "./projects"; export * from "./projectUserAdditionalPrivilege"; export * from "./rateLimit"; +export * from "./relays"; export * from "./roles"; export * from "./scim"; export * from "./secretApproval"; diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/GatewayTab.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/GatewayTab.tsx index 27ebb6820..a2a24015f 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/GatewayTab.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/GatewayTab.tsx @@ -8,6 +8,7 @@ import { faEllipsisV, faInfoCircle, faMagnifyingGlass, + faPlus, faSearch, faTrash } from "@fortawesome/free-solid-svg-icons"; @@ -17,6 +18,7 @@ import { useQuery } from "@tanstack/react-query"; import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { + Button, DeleteActionModal, DropdownMenu, DropdownMenuContent, @@ -47,6 +49,7 @@ import { gatewaysQueryKeys, useDeleteGatewayById } from "@app/hooks/api/gateways import { useDeleteGatewayV2ById } from "@app/hooks/api/gateways-v2"; import { EditGatewayDetailsModal } from "./components/EditGatewayDetailsModal"; +import { GatewayDeployModal } from "./components/GatewayDeployModal"; const GatewayHealthStatus = ({ heartbeat }: { heartbeat?: string }) => { const heartbeatDate = heartbeat ? new Date(heartbeat) : null; @@ -73,6 +76,7 @@ export const GatewayTab = withPermission( const { data: gateways, isPending: isGatewaysLoading } = useQuery(gatewaysQueryKeys.list()); const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([ + "deployGateway", "deleteGateway", "editDetails" ] as const); @@ -101,8 +105,8 @@ export const GatewayTab = withPermission( return (
-
-
+
+

Gateways

+
+

@@ -257,6 +269,10 @@ export const GatewayTab = withPermission( deleteKey="confirm" onDeleteApproved={() => handleDeleteGateway()} /> + handlePopUpToggle("deployGateway", isOpen)} + />

diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx new file mode 100644 index 000000000..7537d95a3 --- /dev/null +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx @@ -0,0 +1,387 @@ +import { useMemo, useState } from "react"; +import { SingleValue } from "react-select"; +import { faCopy, faQuestionCircle, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useNavigate } from "@tanstack/react-router"; +import { z } from "zod"; + +import { createNotification } from "@app/components/notifications"; +import { + Button, + Checkbox, + FilterableSelect, + FormLabel, + IconButton, + Input, + ModalClose, + Tooltip +} from "@app/components/v2"; +import { ROUTE_PATHS } from "@app/const/routes"; +import { + OrgPermissionIdentityActions, + OrgPermissionSubjects, + useOrganization, + useOrgPermission +} from "@app/context"; +import { + useAddIdentityTokenAuth, + useCreateTokenIdentityTokenAuth, + useGetIdentityMembershipOrgs, + useGetIdentityTokenAuth, + useGetRelays +} from "@app/hooks/api"; +import { slugSchema } from "@app/lib/schemas"; + +import { RelayOption } from "./RelayOption"; + +const baseFormSchema = z.object({ + name: slugSchema({ field: "name" }), + instanceDomain: z.string().url("Must be a valid URL").or(z.literal("")), + relay: z + .object( + { + id: z.string(), + name: z.string() + }, + { required_error: "Relay is required" } + ) + .nullable() + .refine((val) => val !== null, { message: "Relay is required" }) +}); + +const formSchemaWithIdentity = baseFormSchema.extend({ + identity: z + .object( + { + id: z.string(), + name: z.string() + }, + { required_error: "Identity is required" } + ) + .nullable() + .refine((val) => val !== null, { message: "Identity is required" }) +}); + +const formSchemaWithToken = baseFormSchema.extend({ + identityToken: z.string().min(1, "Token is required") +}); + +export const GatewayCliDeploymentMethod = () => { + const { protocol, hostname, port } = window.location; + const portSuffix = port && port !== "80" ? `:${port}` : ""; + const siteURL = `${protocol}//${hostname}${portSuffix}`; + + const navigate = useNavigate({ + from: ROUTE_PATHS.Organization.NetworkingPage.path + }); + + const [autogenerateToken, setAutogenerateToken] = useState(true); + const [step, setStep] = useState<"form" | "command">("form"); + const [name, setName] = useState(""); + const [instanceDomain, setInstanceDomain] = useState(siteURL); + const [relay, setRelay] = useState(null); + const [identity, setIdentity] = useState(null); + const [identityToken, setIdentityToken] = useState(""); + const [formErrors, setFormErrors] = useState([]); + + const errors = useMemo(() => { + const errorMap: Record = {}; + formErrors.forEach((issue) => { + if (issue.path.length > 0) { + errorMap[String(issue.path[0])] = issue.message; + } + }); + return errorMap; + }, [formErrors]); + + const { data: relays, isPending: isRelaysLoading } = useGetRelays(); + + const { currentOrg } = useOrganization(); + const organizationId = currentOrg?.id || ""; + + const { permission } = useOrgPermission(); + const canCreateToken = permission.can( + OrgPermissionIdentityActions.CreateToken, + OrgPermissionSubjects.Identity + ); + + const { data: identityMembershipOrgsData, isPending: isIdentitiesLoading } = + useGetIdentityMembershipOrgs({ + organizationId, + limit: 20000 + }); + const identityMembershipOrgs = identityMembershipOrgsData?.identityMemberships || []; + + const { mutateAsync: createToken, isPending: isCreatingToken } = + useCreateTokenIdentityTokenAuth(); + const { mutateAsync: addIdentityTokenAuth, isPending: isAddingTokenAuth } = + useAddIdentityTokenAuth(); + const { refetch } = useGetIdentityTokenAuth(identity?.id ?? ""); + + const handleGenerateCommand = async () => { + setFormErrors([]); + + if (canCreateToken && autogenerateToken) { + const validation = formSchemaWithIdentity.safeParse({ + name, + relay, + identity, + instanceDomain + }); + if (!validation.success) { + setFormErrors(validation.error.issues); + return; + } + + const validatedIdentity = validation.data.identity; + + try { + const { data: identityTokenAuth } = await refetch(); + if (!identityTokenAuth) { + await addIdentityTokenAuth({ + identityId: validatedIdentity.id, + organizationId, + accessTokenTTL: 2592000, + accessTokenMaxTTL: 2592000, + accessTokenNumUsesLimit: 0, + accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] + }); + createNotification({ + text: "Token authentication has been automatically enabled for the selected identity. By default, it is configured to allow all IP addresses with a default token TTL of 30 days. You can manage these settings in Access Control.", + type: "warning" + }); + } + + const token = await createToken({ + identityId: validatedIdentity.id, + name: `gateway token for ${name} (autogenerated)` + }); + setIdentityToken(token.accessToken); + createNotification({ + text: "Automatically generated a token for the selected identity.", + type: "info" + }); + setStep("command"); + } catch (err) { + console.error(err); + createNotification({ + text: "Failed to generate token for the selected identity", + type: "error" + }); + setIdentityToken(""); + } + } else { + const validation = formSchemaWithToken.safeParse({ + name, + relay, + identityToken, + instanceDomain + }); + if (!validation.success) { + setFormErrors(validation.error.issues); + return; + } + setStep("command"); + } + }; + + const command = useMemo(() => { + const domainFlag = instanceDomain ? ` --domain=${instanceDomain}` : ""; + return `infisical gateway start --name=${name} --relay=${ + relay?.name || "" + }${domainFlag} --token=${identityToken}`; + }, [name, relay, identityToken, instanceDomain]); + + if (step === "command") { + return ( + <> + +
+ + { + navigator.clipboard.writeText(command); + createNotification({ + text: "Command copied to clipboard", + type: "info" + }); + }} + className="w-10" + > + + +
+ + Install the Infisical CLI + + +
+ + + +
+ + ); + } + + return ( + <> + + setName(e.target.value)} + placeholder="Enter gateway name..." + isError={Boolean(errors.name)} + /> + {errors.name &&

{errors.name}

} + + + { + if ((newValue as SingleValue<{ id: string }>)?.id === "_create") { + navigate({ + search: (prev) => ({ ...prev, selectedTab: "relays", action: "deploy-relay" }) + }); + return; + } + + setRelay(newValue as SingleValue<{ id: string; name: string }>); + }} + isLoading={isRelaysLoading} + options={[ + { + id: "_create", + name: "Deploy New Relay" + }, + ...(relays || []) + ]} + placeholder="Select relay..." + getOptionLabel={(option) => option.name} + getOptionValue={(option) => option.id} + components={{ Option: RelayOption }} + /> + {errors.relay &&

{errors.relay}

} + + + setInstanceDomain(e.target.value)} + placeholder="https://app.infisical.com" + isError={Boolean(errors.instanceDomain)} + /> + {errors.instanceDomain &&

{errors.instanceDomain}

} + + {canCreateToken && autogenerateToken ? ( + <> + + + setIdentity( + e as SingleValue<{ + id: string; + name: string; + }> + ) + } + isLoading={isIdentitiesLoading} + placeholder="Select identity..." + options={identityMembershipOrgs.map((membership) => membership.identity)} + getOptionValue={(option) => option.id} + getOptionLabel={(option) => option.name} + /> + {errors.identity &&

{errors.identity}

} + + ) : ( + <> + + setIdentityToken(e.target.value)} + placeholder="Enter identity token..." + isError={Boolean(errors.identityToken)} + /> + {errors.identityToken &&

{errors.identityToken}

} + + )} + + {canCreateToken && ( +
+ { + setAutogenerateToken(Boolean(e)); + }} + id="autogenerate-token" + className="mr-2" + > +
+ Automatically enable token auth and generate a token for identity + + Token authentication will be automatically enabled for the selected identity if + it isn't already configured. By default, it will be configured to allow all + IP addresses with a token TTL of 30 days. You can manage these settings in + Access Control. +
+
A token will automatically be generated to be used with the CLI command. + + } + > + +
+
+
+
+ )} + +
+ + + + +
+ + ); +}; diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeployModal.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeployModal.tsx new file mode 100644 index 000000000..170ea2eab --- /dev/null +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeployModal.tsx @@ -0,0 +1,45 @@ +import { useState } from "react"; + +import { Modal, ModalContent } from "@app/components/v2"; +import { GatewayDeploymentMethodSelect } from "@app/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeploymentMethodSelect"; + +import { GatewayCliDeploymentMethod } from "./GatewayCliDeploymentMethod"; + +type Props = { + isOpen: boolean; + onOpenChange: (isOpen: boolean) => void; +}; + +export const GatewayDeploymentInfoMap = { + cli: { name: "CLI", image: "SSH.png", component: GatewayCliDeploymentMethod } +} as const; + +export type GatewayDeploymentMethod = keyof typeof GatewayDeploymentInfoMap; + +const Content = () => { + const [selectedMethod, setSelectedMethod] = useState(null); + + if (selectedMethod) { + const ComponentToRender = GatewayDeploymentInfoMap[selectedMethod]?.component; + if (ComponentToRender) { + return ; + } + } + + return ; +}; + +export const GatewayDeployModal = ({ isOpen, onOpenChange }: Props) => { + return ( + + + + + + ); +}; diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeploymentMethodSelect.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeploymentMethodSelect.tsx new file mode 100644 index 000000000..20a169c24 --- /dev/null +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeploymentMethodSelect.tsx @@ -0,0 +1,54 @@ +import { useMemo } from "react"; + +import { + GatewayDeploymentInfoMap, + GatewayDeploymentMethod +} from "@app/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeployModal"; + +type Props = { + onSelect: (method: GatewayDeploymentMethod) => void; +}; + +export const GatewayDeploymentMethodSelect = ({ onSelect }: Props) => { + const deploymentOptions = useMemo( + () => + (Object.keys(GatewayDeploymentInfoMap) as GatewayDeploymentMethod[]).map((method) => ({ + method, + name: GatewayDeploymentInfoMap[method].name, + image: GatewayDeploymentInfoMap[method].image + })), + [] + ); + + const handleResourceSelect = (method: GatewayDeploymentMethod) => { + onSelect(method); + }; + + return ( +
+ {deploymentOptions.map((option) => { + const { image, name, method } = option; + + return ( + + ); + })} +
+ ); +}; diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/RelayOption.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/RelayOption.tsx new file mode 100644 index 000000000..90c9a7ebd --- /dev/null +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/RelayOption.tsx @@ -0,0 +1,32 @@ +import { components, OptionProps } from "react-select"; +import { faCheckCircle } from "@fortawesome/free-regular-svg-icons"; +import { faPlus } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +export const RelayOption = ({ + isSelected, + children, + ...props +}: OptionProps<{ id: string; name: string }>) => { + const isCreateOption = props.data.id === "_create"; + + return ( + +
+ {isCreateOption ? ( +
+ + Deploy New Relay +
+ ) : ( + <> +

{children}

+ {isSelected && ( + + )} + + )} +
+
+ ); +}; diff --git a/frontend/src/pages/organization/NetworkingPage/components/NetworkingTabGroup/NetworkingTabGroup.tsx b/frontend/src/pages/organization/NetworkingPage/components/NetworkingTabGroup/NetworkingTabGroup.tsx index 5aca54a00..b48b8b113 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/NetworkingTabGroup/NetworkingTabGroup.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/NetworkingTabGroup/NetworkingTabGroup.tsx @@ -1,15 +1,20 @@ -import { useState } from "react"; -import { useSearch } from "@tanstack/react-router"; +import { useNavigate, useSearch } from "@tanstack/react-router"; import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; +import { ROUTE_PATHS } from "@app/const/routes"; import { useOrganization } from "@app/context"; import { GatewayTab } from "../GatewayTab/GatewayTab"; import { RelayTab } from "../RelayTab/RelayTab"; export const NetworkingTabGroup = () => { - const search = useSearch({ - from: "/_authenticate/_inject-org-details/_org-layout/organization/networking/" + const navigate = useNavigate({ + from: ROUTE_PATHS.Organization.NetworkingPage.path + }); + const selectedTab = useSearch({ + from: ROUTE_PATHS.Organization.NetworkingPage.id, + select: (el) => el.selectedTab, + structuralSharing: true }); const tabs = [ @@ -17,12 +22,16 @@ export const NetworkingTabGroup = () => { { name: "Relays", key: "relays", component: RelayTab } ]; - const [selectedTab, setSelectedTab] = useState(search.selectedTab || tabs[0].key); + const handleTabChange = (tab: string) => { + navigate({ + search: { selectedTab: tab } + }); + }; const { isSubOrganization } = useOrganization(); return ( - + {tabs.map((tab) => ( diff --git a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx index e525b765d..594c9a27c 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useEffect, useState } from "react"; import { faArrowUpRightFromSquare, faBookOpen, @@ -7,15 +7,18 @@ import { faEllipsisV, faInfoCircle, faMagnifyingGlass, + faPlus, faSearch, faTrash } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useNavigate, useSearch } from "@tanstack/react-router"; import { formatRelative } from "date-fns"; import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { + Button, DeleteActionModal, DropdownMenu, DropdownMenuContent, @@ -34,6 +37,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { ROUTE_PATHS } from "@app/const/routes"; import { OrgPermissionSubjects, OrgRelayPermissionActions @@ -42,6 +46,8 @@ import { withPermission } from "@app/hoc"; import { usePopUp } from "@app/hooks"; import { useDeleteRelayById, useGetRelays } from "@app/hooks/api/relays"; +import { RelayDeployModal } from "./components/RelayDeployModal"; + const RelayHealthStatus = ({ heartbeat }: { heartbeat?: string }) => { const heartbeatDate = heartbeat ? new Date(heartbeat) : null; const now = new Date(); @@ -66,7 +72,29 @@ export const RelayTab = withPermission( const [search, setSearch] = useState(""); const { data: relays, isPending: isRelaysLoading } = useGetRelays(); - const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["deleteRelay"] as const); + const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([ + "deleteRelay", + "deployRelay" + ] as const); + + const action = useSearch({ + from: ROUTE_PATHS.Organization.NetworkingPage.id, + select: (s) => s.action + }); + + const navigate = useNavigate({ + from: ROUTE_PATHS.Organization.NetworkingPage.path + }); + + useEffect(() => { + if (action === "deploy-relay") { + handlePopUpOpen("deployRelay"); + navigate({ + search: (prev) => ({ ...prev, action: undefined }), + replace: true + }); + } + }, [action, handlePopUpOpen, navigate]); const deleteRelayById = useDeleteRelayById(); @@ -87,8 +115,8 @@ export const RelayTab = withPermission( return (
-
-
+
+

Relays

+
+

@@ -222,6 +258,10 @@ export const RelayTab = withPermission( deleteKey="confirm" onDeleteApproved={() => handleDeleteRelay()} /> + handlePopUpToggle("deployRelay", isOpen)} + />

diff --git a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayCliDeploymentMethod.tsx b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayCliDeploymentMethod.tsx new file mode 100644 index 000000000..1d89e9877 --- /dev/null +++ b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayCliDeploymentMethod.tsx @@ -0,0 +1,352 @@ +import { useMemo, useState } from "react"; +import { SingleValue } from "react-select"; +import { faCopy, faQuestionCircle, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { z } from "zod"; + +import { createNotification } from "@app/components/notifications"; +import { + Button, + Checkbox, + FilterableSelect, + FormLabel, + IconButton, + Input, + ModalClose, + Tooltip +} from "@app/components/v2"; +import { + OrgPermissionIdentityActions, + OrgPermissionSubjects, + useOrganization, + useOrgPermission +} from "@app/context"; +import { + useAddIdentityTokenAuth, + useCreateTokenIdentityTokenAuth, + useGetIdentityMembershipOrgs, + useGetIdentityTokenAuth +} from "@app/hooks/api"; +import { slugSchema } from "@app/lib/schemas"; + +const baseFormSchema = z.object({ + name: slugSchema({ field: "name" }), + host: z.string().min(1, "Host is required"), + instanceDomain: z.string().url("Must be a valid URL").or(z.literal("")) +}); + +const formSchemaWithIdentity = baseFormSchema.extend({ + identity: z + .object( + { + id: z.string(), + name: z.string() + }, + { required_error: "Identity is required" } + ) + .nullable() + .refine((val) => val !== null, { message: "Identity is required" }) +}); + +const formSchemaWithToken = baseFormSchema.extend({ + identityToken: z.string().min(1, "Token is required") +}); + +export const RelayCliDeploymentMethod = () => { + const { protocol, hostname, port } = window.location; + const portSuffix = port && port !== "80" ? `:${port}` : ""; + const siteURL = `${protocol}//${hostname}${portSuffix}`; + + const [autogenerateToken, setAutogenerateToken] = useState(true); + const [step, setStep] = useState<"form" | "command">("form"); + const [name, setName] = useState(""); + const [host, setHost] = useState(""); + + const [instanceDomain, setInstanceDomain] = useState(siteURL); + const [identity, setIdentity] = useState(null); + const [identityToken, setIdentityToken] = useState(""); + const [formErrors, setFormErrors] = useState([]); + + const errors = useMemo(() => { + const errorMap: Record = {}; + formErrors.forEach((issue) => { + if (issue.path.length > 0) { + errorMap[String(issue.path[0])] = issue.message; + } + }); + return errorMap; + }, [formErrors]); + + const { currentOrg } = useOrganization(); + const organizationId = currentOrg?.id || ""; + + const { permission } = useOrgPermission(); + const canCreateToken = permission.can( + OrgPermissionIdentityActions.CreateToken, + OrgPermissionSubjects.Identity + ); + + const { data: identityMembershipOrgsData, isPending: isIdentitiesLoading } = + useGetIdentityMembershipOrgs({ + organizationId, + limit: 20000 + }); + const identityMembershipOrgs = identityMembershipOrgsData?.identityMemberships || []; + + const { mutateAsync: createToken, isPending: isCreatingToken } = + useCreateTokenIdentityTokenAuth(); + const { mutateAsync: addIdentityTokenAuth, isPending: isAddingTokenAuth } = + useAddIdentityTokenAuth(); + const { refetch } = useGetIdentityTokenAuth(identity?.id ?? ""); + + const handleGenerateCommand = async () => { + setFormErrors([]); + + if (canCreateToken && autogenerateToken) { + const validation = formSchemaWithIdentity.safeParse({ name, host, instanceDomain, identity }); + if (!validation.success) { + setFormErrors(validation.error.issues); + return; + } + + const validatedIdentity = validation.data.identity; + + try { + const { data: identityTokenAuth } = await refetch(); + if (!identityTokenAuth) { + await addIdentityTokenAuth({ + identityId: validatedIdentity.id, + organizationId, + accessTokenTTL: 2592000, + accessTokenMaxTTL: 2592000, + accessTokenNumUsesLimit: 0, + accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] + }); + createNotification({ + text: "Token authentication has been automatically enabled for the selected identity. By default, it is configured to allow all IP addresses with a default token TTL of 30 days. You can manage these settings in Access Control.", + type: "warning" + }); + } + + const token = await createToken({ + identityId: validatedIdentity.id, + name: `relay token for ${name} (autogenerated)` + }); + setIdentityToken(token.accessToken); + createNotification({ + text: "Automatically generated a token for the selected identity.", + type: "info" + }); + setStep("command"); + } catch (err) { + console.error(err); + createNotification({ + text: "Failed to generate token for the selected identity", + type: "error" + }); + setIdentityToken(""); + } + } else { + const validation = formSchemaWithToken.safeParse({ + name, + host, + instanceDomain, + identityToken + }); + if (!validation.success) { + setFormErrors(validation.error.issues); + return; + } + setStep("command"); + } + }; + + const handleIdentityChange = ( + selectedIdentity: SingleValue<{ + id: string; + name: string; + }> + ) => { + setIdentity(selectedIdentity); + }; + + const command = useMemo(() => { + const domainFlag = instanceDomain ? ` --domain=${instanceDomain}` : ""; + return `infisical relay start --name=${name}${domainFlag} --host=${host} --token=${identityToken}`; + }, [name, instanceDomain, host, identityToken]); + + if (step === "command") { + return ( + <> + +
+ + { + navigator.clipboard.writeText(command); + createNotification({ + text: "Command copied to clipboard", + type: "info" + }); + }} + className="w-10" + > + + +
+ + Install the Infisical CLI + + +
+ + + +
+ + ); + } + + return ( + <> + + setName(e.target.value)} + placeholder="Enter relay name..." + isError={Boolean(errors.name)} + /> + {errors.name &&

{errors.name}

} + + + setHost(e.target.value)} + placeholder="0.0.0.0" + isError={Boolean(errors.host)} + /> + {errors.host &&

{errors.host}

} + + + setInstanceDomain(e.target.value)} + placeholder="https://app.infisical.com" + isError={Boolean(errors.instanceDomain)} + /> + {errors.instanceDomain &&

{errors.instanceDomain}

} + + {canCreateToken && autogenerateToken ? ( + <> + + + handleIdentityChange( + e as SingleValue<{ + id: string; + name: string; + }> + ) + } + isLoading={isIdentitiesLoading} + placeholder="Select identity..." + options={identityMembershipOrgs.map((membership) => membership.identity)} + getOptionValue={(option) => option.id} + getOptionLabel={(option) => option.name} + /> + {errors.identity &&

{errors.identity}

} + + ) : ( + <> + + setIdentityToken(e.target.value)} + placeholder="Enter identity token..." + isError={Boolean(errors.identityToken)} + /> + {errors.identityToken &&

{errors.identityToken}

} + + )} + + {canCreateToken && ( +
+ { + setAutogenerateToken(Boolean(e)); + }} + id="autogenerate-token" + className="mr-2" + > +
+ Automatically enable token auth and generate a token for identity + + Token authentication will be automatically enabled for the selected identity if + it isn't already configured. By default, it will be configured to allow all + IP addresses with a token TTL of 30 days. You can manage these settings in + Access Control. +
+
A token will automatically be generated to be used with the CLI command. + + } + > + +
+
+
+
+ )} + +
+ + + + +
+ + ); +}; diff --git a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayDeployModal.tsx b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayDeployModal.tsx new file mode 100644 index 000000000..496ecb762 --- /dev/null +++ b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayDeployModal.tsx @@ -0,0 +1,45 @@ +import { useState } from "react"; + +import { Modal, ModalContent } from "@app/components/v2"; +import { RelayDeploymentMethodSelect } from "@app/pages/organization/NetworkingPage/components/RelayTab/components/RelayDeploymentMethodSelect"; + +import { RelayCliDeploymentMethod } from "./RelayCliDeploymentMethod"; + +type Props = { + isOpen: boolean; + onOpenChange: (isOpen: boolean) => void; +}; + +export const RelayDeploymentInfoMap = { + cli: { name: "CLI", image: "SSH.png", component: RelayCliDeploymentMethod } +} as const; + +export type RelayDeploymentMethod = keyof typeof RelayDeploymentInfoMap; + +const Content = () => { + const [selectedMethod, setSelectedMethod] = useState(null); + + if (selectedMethod) { + const ComponentToRender = RelayDeploymentInfoMap[selectedMethod]?.component; + if (ComponentToRender) { + return ; + } + } + + return ; +}; + +export const RelayDeployModal = ({ isOpen, onOpenChange }: Props) => { + return ( + + + + + + ); +}; diff --git a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayDeploymentMethodSelect.tsx b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayDeploymentMethodSelect.tsx new file mode 100644 index 000000000..cf7861a8b --- /dev/null +++ b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayDeploymentMethodSelect.tsx @@ -0,0 +1,54 @@ +import { useMemo } from "react"; + +import { + RelayDeploymentInfoMap, + RelayDeploymentMethod +} from "@app/pages/organization/NetworkingPage/components/RelayTab/components/RelayDeployModal"; + +type Props = { + onSelect: (method: RelayDeploymentMethod) => void; +}; + +export const RelayDeploymentMethodSelect = ({ onSelect }: Props) => { + const deploymentOptions = useMemo( + () => + (Object.keys(RelayDeploymentInfoMap) as RelayDeploymentMethod[]).map((method) => ({ + method, + name: RelayDeploymentInfoMap[method].name, + image: RelayDeploymentInfoMap[method].image + })), + [] + ); + + const handleResourceSelect = (method: RelayDeploymentMethod) => { + onSelect(method); + }; + + return ( +
+ {deploymentOptions.map((option) => { + const { image, name, method } = option; + + return ( + + ); + })} +
+ ); +}; diff --git a/frontend/src/pages/organization/NetworkingPage/route.tsx b/frontend/src/pages/organization/NetworkingPage/route.tsx index 906b12806..fb81e3725 100644 --- a/frontend/src/pages/organization/NetworkingPage/route.tsx +++ b/frontend/src/pages/organization/NetworkingPage/route.tsx @@ -5,16 +5,17 @@ import { z } from "zod"; import { NetworkingPage } from "./NetworkingPage"; const NetworkingPageQueryParams = z.object({ - selectedTab: z.string().catch("") + selectedTab: z.string().catch("gateways"), + action: z.string().optional() }); export const Route = createFileRoute( - "/_authenticate/_inject-org-details/_org-layout/organization/networking/" + "/_authenticate/_inject-org-details/_org-layout/organization/networking" )({ component: NetworkingPage, validateSearch: zodValidator(NetworkingPageQueryParams), search: { - middlewares: [stripSearchParams({ selectedTab: "" })] + middlewares: [stripSearchParams({ selectedTab: "gateways" })] }, context: () => ({ breadcrumbs: [ diff --git a/frontend/src/routeTree.gen.ts b/frontend/src/routeTree.gen.ts index 853864c9b..34c4153d6 100644 --- a/frontend/src/routeTree.gen.ts +++ b/frontend/src/routeTree.gen.ts @@ -50,6 +50,7 @@ import { Route as adminCachingPageRouteImport } from './pages/admin/CachingPage/ import { Route as adminAuthenticationPageRouteImport } from './pages/admin/AuthenticationPage/route' import { Route as adminAccessManagementPageRouteImport } from './pages/admin/AccessManagementPage/route' import { Route as organizationProjectsPageRouteImport } from './pages/organization/ProjectsPage/route' +import { Route as organizationNetworkingPageRouteImport } from './pages/organization/NetworkingPage/route' import { Route as organizationBillingPageRouteImport } from './pages/organization/BillingPage/route' import { Route as organizationAuditLogsPageRouteImport } from './pages/organization/AuditLogsPage/route' import { Route as organizationAccessManagementPageRouteImport } from './pages/organization/AccessManagementPage/route' @@ -62,7 +63,6 @@ import { Route as organizationIdentityDetailsByIDPageRouteImport } from './pages import { Route as organizationGroupDetailsByIDPageRouteImport } from './pages/organization/GroupDetailsByIDPage/route' import { Route as organizationSettingsPageRouteImport } from './pages/organization/SettingsPage/route' import { Route as organizationSecretSharingPageRouteImport } from './pages/organization/SecretSharingPage/route' -import { Route as organizationNetworkingPageRouteImport } from './pages/organization/NetworkingPage/route' import { Route as organizationAppConnectionsAppConnectionsPageRouteImport } from './pages/organization/AppConnections/AppConnectionsPage/route' import { Route as sshLayoutImport } from './pages/ssh/layout' import { Route as secretScanningLayoutImport } from './pages/secret-scanning/layout' @@ -270,10 +270,6 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingImport = createFileRoute( '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing', )() -const AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingImport = - createFileRoute( - '/_authenticate/_inject-org-details/_org-layout/organization/networking', - )() const AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsImport = createFileRoute( '/_authenticate/_inject-org-details/_org-layout/organization/app-connections', @@ -594,14 +590,6 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute = AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute, } as any) -const AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRoute = - AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingImport.update({ - id: '/networking', - path: '/networking', - getParentRoute: () => - AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute, - } as any) - const AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRoute = AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsImport.update({ id: '/app-connections', @@ -658,6 +646,14 @@ const organizationProjectsPageRouteRoute = AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute, } as any) +const organizationNetworkingPageRouteRoute = + organizationNetworkingPageRouteImport.update({ + id: '/networking', + path: '/networking', + getParentRoute: () => + AuthenticateInjectOrgDetailsOrgLayoutOrganizationRoute, + } as any) + const organizationBillingPageRouteRoute = organizationBillingPageRouteImport.update({ id: '/billing', @@ -799,14 +795,6 @@ const organizationSecretSharingPageRouteRoute = AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute, } as any) -const organizationNetworkingPageRouteRoute = - organizationNetworkingPageRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => - AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRoute, - } as any) - const organizationAppConnectionsAppConnectionsPageRouteRoute = organizationAppConnectionsAppConnectionsPageRouteImport.update({ id: '/', @@ -2476,6 +2464,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof organizationBillingPageRouteImport parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport } + '/_authenticate/_inject-org-details/_org-layout/organization/networking': { + id: '/_authenticate/_inject-org-details/_org-layout/organization/networking' + path: '/networking' + fullPath: '/organization/networking' + preLoaderRoute: typeof organizationNetworkingPageRouteImport + parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport + } '/_authenticate/_inject-org-details/_org-layout/organization/projects': { id: '/_authenticate/_inject-org-details/_org-layout/organization/projects' path: '/projects' @@ -2532,13 +2527,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsImport parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport } - '/_authenticate/_inject-org-details/_org-layout/organization/networking': { - id: '/_authenticate/_inject-org-details/_org-layout/organization/networking' - path: '/networking' - fullPath: '/organization/networking' - preLoaderRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingImport - parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationImport - } '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing': { id: '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing' path: '/secret-sharing' @@ -2567,13 +2555,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof organizationAppConnectionsAppConnectionsPageRouteImport parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsImport } - '/_authenticate/_inject-org-details/_org-layout/organization/networking/': { - id: '/_authenticate/_inject-org-details/_org-layout/organization/networking/' - path: '/' - fullPath: '/organization/networking/' - preLoaderRoute: typeof organizationNetworkingPageRouteImport - parentRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingImport - } '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/': { id: '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/' path: '/' @@ -4001,20 +3982,6 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithCh AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteChildren, ) -interface AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRouteChildren { - organizationNetworkingPageRouteRoute: typeof organizationNetworkingPageRouteRoute -} - -const AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRouteChildren: AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRouteChildren = - { - organizationNetworkingPageRouteRoute: organizationNetworkingPageRouteRoute, - } - -const AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRouteWithChildren = - AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRoute._addFileChildren( - AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRouteChildren, - ) - interface AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteChildren { organizationSecretSharingPageRouteRoute: typeof organizationSecretSharingPageRouteRoute } @@ -4051,9 +4018,9 @@ interface AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren { organizationAccessManagementPageRouteRoute: typeof organizationAccessManagementPageRouteRoute organizationAuditLogsPageRouteRoute: typeof organizationAuditLogsPageRouteRoute organizationBillingPageRouteRoute: typeof organizationBillingPageRouteRoute + organizationNetworkingPageRouteRoute: typeof organizationNetworkingPageRouteRoute organizationProjectsPageRouteRoute: typeof organizationProjectsPageRouteRoute AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren - AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRouteWithChildren AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRoute: typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRouteWithChildren organizationGroupDetailsByIDPageRouteRoute: typeof organizationGroupDetailsByIDPageRouteRoute @@ -4068,11 +4035,10 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren: Authentica organizationAccessManagementPageRouteRoute, organizationAuditLogsPageRouteRoute: organizationAuditLogsPageRouteRoute, organizationBillingPageRouteRoute: organizationBillingPageRouteRoute, + organizationNetworkingPageRouteRoute: organizationNetworkingPageRouteRoute, organizationProjectsPageRouteRoute: organizationProjectsPageRouteRoute, AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRoute: AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren, - AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRoute: - AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRouteWithChildren, AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute: AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren, AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRoute: @@ -5055,6 +5021,7 @@ export interface FileRoutesByFullPath { '/organization/access-management': typeof organizationAccessManagementPageRouteRoute '/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute '/organization/billing': typeof organizationBillingPageRouteRoute + '/organization/networking': typeof organizationNetworkingPageRouteRoute '/organization/projects': typeof organizationProjectsPageRouteRoute '/admin/access-management': typeof adminAccessManagementPageRouteRoute '/admin/authentication': typeof adminAuthenticationPageRouteRoute @@ -5063,12 +5030,10 @@ export interface FileRoutesByFullPath { '/admin/environment': typeof adminEnvironmentPageRouteRoute '/admin/integrations': typeof adminIntegrationsPageRouteRoute '/organization/app-connections': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren - '/organization/networking': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRouteWithChildren '/organization/secret-sharing': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren '/organization/settings': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRouteWithChildren '/secret-manager/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdRouteWithChildren '/organization/app-connections/': typeof organizationAppConnectionsAppConnectionsPageRouteRoute - '/organization/networking/': typeof organizationNetworkingPageRouteRoute '/organization/secret-sharing/': typeof organizationSecretSharingPageRouteRoute '/organization/settings/': typeof organizationSettingsPageRouteRoute '/organization/groups/$groupId': typeof organizationGroupDetailsByIDPageRouteRoute @@ -5292,6 +5257,7 @@ export interface FileRoutesByTo { '/organization/access-management': typeof organizationAccessManagementPageRouteRoute '/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute '/organization/billing': typeof organizationBillingPageRouteRoute + '/organization/networking': typeof organizationNetworkingPageRouteRoute '/organization/projects': typeof organizationProjectsPageRouteRoute '/admin/access-management': typeof adminAccessManagementPageRouteRoute '/admin/authentication': typeof adminAuthenticationPageRouteRoute @@ -5301,7 +5267,6 @@ export interface FileRoutesByTo { '/admin/integrations': typeof adminIntegrationsPageRouteRoute '/secret-manager/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdRouteWithChildren '/organization/app-connections': typeof organizationAppConnectionsAppConnectionsPageRouteRoute - '/organization/networking': typeof organizationNetworkingPageRouteRoute '/organization/secret-sharing': typeof organizationSecretSharingPageRouteRoute '/organization/settings': typeof organizationSettingsPageRouteRoute '/organization/groups/$groupId': typeof organizationGroupDetailsByIDPageRouteRoute @@ -5527,6 +5492,7 @@ export interface FileRoutesById { '/_authenticate/_inject-org-details/_org-layout/organization/access-management': typeof organizationAccessManagementPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/audit-logs': typeof organizationAuditLogsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/billing': typeof organizationBillingPageRouteRoute + '/_authenticate/_inject-org-details/_org-layout/organization/networking': typeof organizationNetworkingPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/projects': typeof organizationProjectsPageRouteRoute '/_authenticate/_inject-org-details/admin/_admin-layout/access-management': typeof adminAccessManagementPageRouteRoute '/_authenticate/_inject-org-details/admin/_admin-layout/authentication': typeof adminAuthenticationPageRouteRoute @@ -5535,12 +5501,10 @@ export interface FileRoutesById { '/_authenticate/_inject-org-details/admin/_admin-layout/environment': typeof adminEnvironmentPageRouteRoute '/_authenticate/_inject-org-details/admin/_admin-layout/integrations': typeof adminIntegrationsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/app-connections': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren - '/_authenticate/_inject-org-details/_org-layout/organization/networking': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/organization/settings': typeof AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId': typeof AuthenticateInjectOrgDetailsOrgLayoutSecretManagerProjectIdRouteWithChildren '/_authenticate/_inject-org-details/_org-layout/organization/app-connections/': typeof organizationAppConnectionsAppConnectionsPageRouteRoute - '/_authenticate/_inject-org-details/_org-layout/organization/networking/': typeof organizationNetworkingPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/': typeof organizationSecretSharingPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/settings/': typeof organizationSettingsPageRouteRoute '/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId': typeof organizationGroupDetailsByIDPageRouteRoute @@ -5776,6 +5740,7 @@ export interface FileRouteTypes { | '/organization/access-management' | '/organization/audit-logs' | '/organization/billing' + | '/organization/networking' | '/organization/projects' | '/admin/access-management' | '/admin/authentication' @@ -5784,12 +5749,10 @@ export interface FileRouteTypes { | '/admin/environment' | '/admin/integrations' | '/organization/app-connections' - | '/organization/networking' | '/organization/secret-sharing' | '/organization/settings' | '/secret-manager/$projectId' | '/organization/app-connections/' - | '/organization/networking/' | '/organization/secret-sharing/' | '/organization/settings/' | '/organization/groups/$groupId' @@ -6012,6 +5975,7 @@ export interface FileRouteTypes { | '/organization/access-management' | '/organization/audit-logs' | '/organization/billing' + | '/organization/networking' | '/organization/projects' | '/admin/access-management' | '/admin/authentication' @@ -6021,7 +5985,6 @@ export interface FileRouteTypes { | '/admin/integrations' | '/secret-manager/$projectId' | '/organization/app-connections' - | '/organization/networking' | '/organization/secret-sharing' | '/organization/settings' | '/organization/groups/$groupId' @@ -6245,6 +6208,7 @@ export interface FileRouteTypes { | '/_authenticate/_inject-org-details/_org-layout/organization/access-management' | '/_authenticate/_inject-org-details/_org-layout/organization/audit-logs' | '/_authenticate/_inject-org-details/_org-layout/organization/billing' + | '/_authenticate/_inject-org-details/_org-layout/organization/networking' | '/_authenticate/_inject-org-details/_org-layout/organization/projects' | '/_authenticate/_inject-org-details/admin/_admin-layout/access-management' | '/_authenticate/_inject-org-details/admin/_admin-layout/authentication' @@ -6253,12 +6217,10 @@ export interface FileRouteTypes { | '/_authenticate/_inject-org-details/admin/_admin-layout/environment' | '/_authenticate/_inject-org-details/admin/_admin-layout/integrations' | '/_authenticate/_inject-org-details/_org-layout/organization/app-connections' - | '/_authenticate/_inject-org-details/_org-layout/organization/networking' | '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing' | '/_authenticate/_inject-org-details/_org-layout/organization/settings' | '/_authenticate/_inject-org-details/_org-layout/secret-manager/$projectId' | '/_authenticate/_inject-org-details/_org-layout/organization/app-connections/' - | '/_authenticate/_inject-org-details/_org-layout/organization/networking/' | '/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/' | '/_authenticate/_inject-org-details/_org-layout/organization/settings/' | '/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId' @@ -6701,9 +6663,9 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/organization/access-management", "/_authenticate/_inject-org-details/_org-layout/organization/audit-logs", "/_authenticate/_inject-org-details/_org-layout/organization/billing", + "/_authenticate/_inject-org-details/_org-layout/organization/networking", "/_authenticate/_inject-org-details/_org-layout/organization/projects", "/_authenticate/_inject-org-details/_org-layout/organization/app-connections", - "/_authenticate/_inject-org-details/_org-layout/organization/networking", "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing", "/_authenticate/_inject-org-details/_org-layout/organization/settings", "/_authenticate/_inject-org-details/_org-layout/organization/groups/$groupId", @@ -6742,6 +6704,10 @@ export const routeTree = rootRoute "filePath": "organization/BillingPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/organization" }, + "/_authenticate/_inject-org-details/_org-layout/organization/networking": { + "filePath": "organization/NetworkingPage/route.tsx", + "parent": "/_authenticate/_inject-org-details/_org-layout/organization" + }, "/_authenticate/_inject-org-details/_org-layout/organization/projects": { "filePath": "organization/ProjectsPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/organization" @@ -6778,13 +6744,6 @@ export const routeTree = rootRoute "/_authenticate/_inject-org-details/_org-layout/organization/app-connections/$appConnection/oauth/callback" ] }, - "/_authenticate/_inject-org-details/_org-layout/organization/networking": { - "filePath": "", - "parent": "/_authenticate/_inject-org-details/_org-layout/organization", - "children": [ - "/_authenticate/_inject-org-details/_org-layout/organization/networking/" - ] - }, "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing": { "filePath": "", "parent": "/_authenticate/_inject-org-details/_org-layout/organization", @@ -6811,10 +6770,6 @@ export const routeTree = rootRoute "filePath": "organization/AppConnections/AppConnectionsPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/organization/app-connections" }, - "/_authenticate/_inject-org-details/_org-layout/organization/networking/": { - "filePath": "organization/NetworkingPage/route.tsx", - "parent": "/_authenticate/_inject-org-details/_org-layout/organization/networking" - }, "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing/": { "filePath": "organization/SecretSharingPage/route.tsx", "parent": "/_authenticate/_inject-org-details/_org-layout/organization/secret-sharing" diff --git a/frontend/src/routes.ts b/frontend/src/routes.ts index 8f48f6fe5..16bdaf10f 100644 --- a/frontend/src/routes.ts +++ b/frontend/src/routes.ts @@ -37,7 +37,7 @@ const organizationRoutes = route("/organization", [ "organization/AppConnections/OauthCallbackPage/route.tsx" ) ]), - route("/networking", [index("organization/NetworkingPage/route.tsx")]) + route("/networking", "organization/NetworkingPage/route.tsx") ]); const secretManagerRoutes = route("/projects/secret-management/$projectId", [