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 (
-
-
+
+
+
+
}
+ onClick={() => handlePopUpOpen("deployGateway")}
+ >
+ Deploy Gateway
+
@@ -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 (
-
-
+
+
+
+
}
+ onClick={() => handlePopUpOpen("deployRelay")}
+ >
+ Deploy Relay
+
@@ -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", [