From f218b5a9449302160c7e9779ad232920033bcc78 Mon Sep 17 00:00:00 2001 From: x032205 Date: Fri, 17 Oct 2025 06:26:24 -0400 Subject: [PATCH 01/60] feat(gateways): gateway and relay deployment CLI command interactive helper --- frontend/src/const/routes.ts | 6 +- .../components/GatewayTab/GatewayTab.tsx | 20 +- .../components/DeployGatewayModal.tsx | 243 ++++++++++++++++++ .../components/DeployRelayModal.tsx | 204 +++++++++++++++ .../GatewayTab/components/RelayOption.tsx | 32 +++ .../NetworkingTabGroup/NetworkingTabGroup.tsx | 21 +- .../components/RelayTab/RelayTab.tsx | 24 +- .../organization/NetworkingPage/route.tsx | 6 +- frontend/src/routeTree.gen.ts | 103 +++----- frontend/src/routes.ts | 2 +- 10 files changed, 571 insertions(+), 90 deletions(-) create mode 100644 frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx create mode 100644 frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployRelayModal.tsx create mode 100644 frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/RelayOption.tsx diff --git a/frontend/src/const/routes.ts b/frontend/src/const/routes.ts index 2e835a1db..a31306818 100644 --- a/frontend/src/const/routes.ts +++ b/frontend/src/const/routes.ts @@ -66,7 +66,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/pages/organization/NetworkingPage/components/GatewayTab/GatewayTab.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/GatewayTab.tsx index 27ebb6820..d5e79d326 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 { DeployGatewayModal } from "./components/DeployGatewayModal"; 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/DeployGatewayModal.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx new file mode 100644 index 000000000..2b8f17fe7 --- /dev/null +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx @@ -0,0 +1,243 @@ +import { createNotification } from "@app/components/notifications"; +import { + FilterableSelect, + FormLabel, + IconButton, + Input, + Modal, + ModalContent +} from "@app/components/v2"; +import { useOrganization } from "@app/context"; +import { + useAddIdentityTokenAuth, + useCreateTokenIdentityTokenAuth, + useGetIdentityMembershipOrgs, + useGetIdentityTokenAuth +} from "@app/hooks/api"; +import { useGetRelays } from "@app/hooks/api/relays"; +import { slugSchema } from "@app/lib/schemas"; +import { faCopy, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useEffect, useMemo, useState } from "react"; +import { SingleValue } from "react-select"; +import { RelayOption } from "./RelayOption"; +import { useNavigate } from "@tanstack/react-router"; +import { ROUTE_PATHS } from "@app/const/routes"; +import { twMerge } from "tailwind-merge"; + +type Props = { + isOpen: boolean; + onOpenChange: (isOpen: boolean) => void; +}; + +export const DeployGatewayModal = ({ isOpen, onOpenChange }: Props) => { + 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 [name, setName] = useState(""); + const [relay, setRelay] = useState(null); + const [identity, setIdentity] = useState(null); + const [identityToken, setIdentityToken] = useState(""); + + useEffect(() => { + if (!isOpen) { + setName(""); + setRelay(null); + setIdentity(null); + setIdentityToken(""); + } + }, [isOpen]); + + const { data: relays, isPending: isRelaysLoading } = useGetRelays(); + + const { currentOrg } = useOrganization(); + const organizationId = currentOrg?.id || ""; + + const { data: identityMembershipOrgsData, isPending: isIdentitiesLoading } = + useGetIdentityMembershipOrgs({ + organizationId, + limit: 20000 + }); + const identityMembershipOrgs = identityMembershipOrgsData?.identityMemberships || []; + + const { mutateAsync: createToken } = useCreateTokenIdentityTokenAuth(); + const { mutateAsync: addIdentityTokenAuth } = useAddIdentityTokenAuth(); + const { refetch } = useGetIdentityTokenAuth(identity?.id ?? ""); + + useEffect(() => { + const generateToken = async () => { + if (!identity) return; + + try { + const { data: identityTokenAuth } = await refetch(); + if (!identityTokenAuth) { + await addIdentityTokenAuth({ + identityId: identity.id, + organizationId, + accessTokenTTL: 2592000, + accessTokenMaxTTL: 2592000, + accessTokenNumUsesLimit: 0, + accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] + }); + createNotification({ + text: "Automatically enabled token authentication for this identity.", + type: "info" + }); + } + + const token = await createToken({ + identityId: identity.id, + name: "gateway token (autogenerated)" + }); + setIdentityToken(token.accessToken); + createNotification({ + text: "Automatically generated a token for this identity.", + type: "info" + }); + } catch (err) { + console.error(err); + createNotification({ + text: "Failed to generate token for identity", + type: "error" + }); + setIdentityToken(""); + } + }; + + generateToken(); + }, [identity, organizationId, refetch, addIdentityTokenAuth, createToken]); + + const handleIdentityChange = (selectedIdentity: SingleValue<{ id: string; name: string }>) => { + setIdentity(selectedIdentity); + setIdentityToken(""); + }; + + const isNameValid = useMemo(() => !name || slugSchema().safeParse(name).success, [name]); + + const isCommandReady = useMemo( + () => !!name && isNameValid && !!relay && !!identityToken, + [name, isNameValid, relay, identityToken] + ); + + const command = useMemo( + () => + `infisical gateway start --name=${name} --relay=${ + relay?.name || "" + } --domain=${siteURL} --token=${identityToken}`, + [name, relay, identityToken, siteURL] + ); + + return ( + + + + setName(e.target.value)} + placeholder="Enter gateway name..." + isError={!isNameValid} + /> + + + { + if ((newValue as SingleValue<{ id: string }>)?.id === "_create") { + navigate({ + search: { selectedTab: "relays" } + }); + 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 }} + /> + + + + 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} + /> + + +
+ + { + navigator.clipboard.writeText(command); + + createNotification({ + text: "Command copied to clipboard", + type: "info" + }); + }} + className={twMerge("w-10", !isCommandReady && "pointer-events-none opacity-50")} + isDisabled={!isCommandReady} + > + + +
+ + Install the Infisical CLI + + +
+
+ ); +}; diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployRelayModal.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployRelayModal.tsx new file mode 100644 index 000000000..2347c0472 --- /dev/null +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployRelayModal.tsx @@ -0,0 +1,204 @@ +import { createNotification } from "@app/components/notifications"; +import { + FilterableSelect, + FormLabel, + IconButton, + Input, + Modal, + ModalContent +} from "@app/components/v2"; +import { useOrganization } from "@app/context"; +import { + useAddIdentityTokenAuth, + useCreateTokenIdentityTokenAuth, + useGetIdentityMembershipOrgs, + useGetIdentityTokenAuth +} from "@app/hooks/api"; +import { slugSchema } from "@app/lib/schemas"; +import { faCopy, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useEffect, useMemo, useState } from "react"; +import { SingleValue } from "react-select"; +import { twMerge } from "tailwind-merge"; + +type Props = { + isOpen: boolean; + onOpenChange: (isOpen: boolean) => void; +}; + +export const DeployRelayModal = ({ isOpen, onOpenChange }: Props) => { + const { protocol, hostname, port } = window.location; + const portSuffix = port && port !== "80" ? `:${port}` : ""; + const siteURL = `${protocol}//${hostname}${portSuffix}`; + + const [name, setName] = useState(""); + const [host, setHost] = useState(""); + const [identity, setIdentity] = useState(null); + const [identityToken, setIdentityToken] = useState(""); + + useEffect(() => { + if (!isOpen) { + setName(""); + setHost(""); + setIdentity(null); + setIdentityToken(""); + } + }, [isOpen]); + + const { currentOrg } = useOrganization(); + const organizationId = currentOrg?.id || ""; + + const { data: identityMembershipOrgsData, isPending: isIdentitiesLoading } = + useGetIdentityMembershipOrgs({ + organizationId, + limit: 20000 + }); + const identityMembershipOrgs = identityMembershipOrgsData?.identityMemberships || []; + + const { mutateAsync: createToken } = useCreateTokenIdentityTokenAuth(); + const { mutateAsync: addIdentityTokenAuth } = useAddIdentityTokenAuth(); + const { refetch } = useGetIdentityTokenAuth(identity?.id ?? ""); + + useEffect(() => { + const generateToken = async () => { + if (!identity) return; + + try { + const { data: identityTokenAuth } = await refetch(); + if (!identityTokenAuth) { + await addIdentityTokenAuth({ + identityId: identity.id, + organizationId, + accessTokenTTL: 2592000, + accessTokenMaxTTL: 2592000, + accessTokenNumUsesLimit: 0, + accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] + }); + createNotification({ + text: "Automatically enabled token authentication for this identity.", + type: "info" + }); + } + + const token = await createToken({ + identityId: identity.id, + name: "relay token (autogenerated)" + }); + setIdentityToken(token.accessToken); + createNotification({ + text: "Automatically generated a token for this identity.", + type: "info" + }); + } catch (err) { + console.error(err); + createNotification({ + text: "Failed to generate token for identity", + type: "error" + }); + setIdentityToken(""); + } + }; + + generateToken(); + }, [identity, organizationId, refetch, addIdentityTokenAuth, createToken]); + + const handleIdentityChange = (selectedIdentity: SingleValue<{ id: string; name: string }>) => { + setIdentity(selectedIdentity); + setIdentityToken(""); + }; + + const isNameValid = useMemo(() => !name || slugSchema().safeParse(name).success, [name]); + + const isCommandReady = useMemo( + () => !!name && !!host && isNameValid && !!identityToken, + [name, isNameValid, host, identityToken] + ); + + const command = useMemo( + () => + `infisical relay start --name=${name} --domain=${siteURL} --host=${host} --token=${identityToken}`, + [name, siteURL, host, identityToken] + ); + + return ( + + + + setName(e.target.value)} + placeholder="Enter relay name..." + isError={!isNameValid} + /> + + + setHost(e.target.value)} placeholder="0.0.0.0" /> + + + + 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} + /> + + +
+ + { + navigator.clipboard.writeText(command); + + createNotification({ + text: "Command copied to clipboard", + type: "info" + }); + }} + className={twMerge("w-10", !isCommandReady && "pointer-events-none opacity-50")} + isDisabled={!isCommandReady} + > + + +
+ + Install the Infisical CLI + + +
+
+ ); +}; 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 956a4d1ed..dc90b7f21 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/NetworkingTabGroup/NetworkingTabGroup.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/NetworkingTabGroup/NetworkingTabGroup.tsx @@ -1,14 +1,19 @@ -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 { GatewayTab } from "../GatewayTab/GatewayTab"; import { RelayTab } from "../RelayTab/RelayTab"; +import { ROUTE_PATHS } from "@app/const/routes"; 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 = [ @@ -16,10 +21,14 @@ 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 } + }); + }; 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..2bfa8b250 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx @@ -7,6 +7,7 @@ import { faEllipsisV, faInfoCircle, faMagnifyingGlass, + faPlus, faSearch, faTrash } from "@fortawesome/free-solid-svg-icons"; @@ -16,6 +17,7 @@ import { formatRelative } from "date-fns"; import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import { + Button, DeleteActionModal, DropdownMenu, DropdownMenuContent, @@ -41,6 +43,7 @@ import { import { withPermission } from "@app/hoc"; import { usePopUp } from "@app/hooks"; import { useDeleteRelayById, useGetRelays } from "@app/hooks/api/relays"; +import { DeployRelayModal } from "../GatewayTab/components/DeployRelayModal"; const RelayHealthStatus = ({ heartbeat }: { heartbeat?: string }) => { const heartbeatDate = heartbeat ? new Date(heartbeat) : null; @@ -66,7 +69,10 @@ 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 deleteRelayById = useDeleteRelayById(); @@ -87,8 +93,8 @@ export const RelayTab = withPermission( return (
-
-
+
+

Relays

+
+

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

diff --git a/frontend/src/pages/organization/NetworkingPage/route.tsx b/frontend/src/pages/organization/NetworkingPage/route.tsx index 906b12806..433647476 100644 --- a/frontend/src/pages/organization/NetworkingPage/route.tsx +++ b/frontend/src/pages/organization/NetworkingPage/route.tsx @@ -5,16 +5,16 @@ import { z } from "zod"; import { NetworkingPage } from "./NetworkingPage"; const NetworkingPageQueryParams = z.object({ - selectedTab: z.string().catch("") + selectedTab: z.string().catch("gateways") }); 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 1562f302d..bc92b6ef0 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' @@ -63,7 +64,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', @@ -807,14 +803,6 @@ const organizationSecretSharingPageRouteRoute = AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute, } as any) -const organizationNetworkingPageRouteRoute = - organizationNetworkingPageRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => - AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRoute, - } as any) - const organizationAppConnectionsAppConnectionsPageRouteRoute = organizationAppConnectionsAppConnectionsPageRouteImport.update({ id: '/', @@ -2477,6 +2465,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' @@ -2533,13 +2528,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' @@ -2568,13 +2556,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: '/' @@ -4002,20 +3983,6 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithCh AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteChildren, ) -interface AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRouteChildren { - organizationNetworkingPageRouteRoute: typeof organizationNetworkingPageRouteRoute -} - -const AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRouteChildren: AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRouteChildren = - { - organizationNetworkingPageRouteRoute: organizationNetworkingPageRouteRoute, - } - -const AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRouteWithChildren = - AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRoute._addFileChildren( - AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRouteChildren, - ) - interface AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteChildren { organizationSecretSharingPageRouteRoute: typeof organizationSecretSharingPageRouteRoute organizationSecretSharingSettingsPageRouteRoute: typeof organizationSecretSharingSettingsPageRouteRoute @@ -4055,9 +4022,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 @@ -4072,11 +4039,10 @@ const AuthenticateInjectOrgDetailsOrgLayoutOrganizationRouteChildren: Authentica organizationAccessManagementPageRouteRoute, organizationAuditLogsPageRouteRoute: organizationAuditLogsPageRouteRoute, organizationBillingPageRouteRoute: organizationBillingPageRouteRoute, + organizationNetworkingPageRouteRoute: organizationNetworkingPageRouteRoute, organizationProjectsPageRouteRoute: organizationProjectsPageRouteRoute, AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRoute: AuthenticateInjectOrgDetailsOrgLayoutOrganizationAppConnectionsRouteWithChildren, - AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRoute: - AuthenticateInjectOrgDetailsOrgLayoutOrganizationNetworkingRouteWithChildren, AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRoute: AuthenticateInjectOrgDetailsOrgLayoutOrganizationSecretSharingRouteWithChildren, AuthenticateInjectOrgDetailsOrgLayoutOrganizationSettingsRoute: @@ -5057,6 +5023,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 @@ -5065,12 +5032,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 @@ -5294,6 +5259,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 @@ -5303,7 +5269,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 @@ -5529,6 +5494,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 @@ -5537,12 +5503,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 @@ -5778,6 +5742,7 @@ export interface FileRouteTypes { | '/organization/access-management' | '/organization/audit-logs' | '/organization/billing' + | '/organization/networking' | '/organization/projects' | '/admin/access-management' | '/admin/authentication' @@ -5786,12 +5751,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' @@ -6014,6 +5977,7 @@ export interface FileRouteTypes { | '/organization/access-management' | '/organization/audit-logs' | '/organization/billing' + | '/organization/networking' | '/organization/projects' | '/admin/access-management' | '/admin/authentication' @@ -6023,7 +5987,6 @@ export interface FileRouteTypes { | '/admin/integrations' | '/secret-manager/$projectId' | '/organization/app-connections' - | '/organization/networking' | '/organization/secret-sharing' | '/organization/settings' | '/organization/groups/$groupId' @@ -6247,6 +6210,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' @@ -6255,12 +6219,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' @@ -6703,9 +6665,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", @@ -6744,6 +6706,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" @@ -6780,13 +6746,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", @@ -6814,10 +6773,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 bebc50339..e84e5873d 100644 --- a/frontend/src/routes.ts +++ b/frontend/src/routes.ts @@ -40,7 +40,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", [ From 1e6beb08c945b7176a830039d5244466b25a4a52 Mon Sep 17 00:00:00 2001 From: x032205 Date: Fri, 17 Oct 2025 06:29:48 -0400 Subject: [PATCH 02/60] lint --- .../components/GatewayTab/GatewayTab.tsx | 2 +- .../GatewayTab/components/DeployGatewayModal.tsx | 16 +++++++++------- .../GatewayTab/components/DeployRelayModal.tsx | 11 ++++++----- .../NetworkingTabGroup/NetworkingTabGroup.tsx | 2 +- .../components/RelayTab/RelayTab.tsx | 1 + 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/GatewayTab.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/GatewayTab.tsx index d5e79d326..a93694a3d 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/GatewayTab.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/GatewayTab.tsx @@ -48,8 +48,8 @@ import { usePopUp } from "@app/hooks"; import { gatewaysQueryKeys, useDeleteGatewayById } from "@app/hooks/api/gateways"; import { useDeleteGatewayV2ById } from "@app/hooks/api/gateways-v2"; -import { EditGatewayDetailsModal } from "./components/EditGatewayDetailsModal"; import { DeployGatewayModal } from "./components/DeployGatewayModal"; +import { EditGatewayDetailsModal } from "./components/EditGatewayDetailsModal"; const GatewayHealthStatus = ({ heartbeat }: { heartbeat?: string }) => { const heartbeatDate = heartbeat ? new Date(heartbeat) : null; diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx index 2b8f17fe7..e5bc048c5 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx @@ -1,3 +1,10 @@ +import { useEffect, useMemo, useState } from "react"; +import { SingleValue } from "react-select"; +import { faCopy, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useNavigate } from "@tanstack/react-router"; +import { twMerge } from "tailwind-merge"; + import { createNotification } from "@app/components/notifications"; import { FilterableSelect, @@ -7,6 +14,7 @@ import { Modal, ModalContent } from "@app/components/v2"; +import { ROUTE_PATHS } from "@app/const/routes"; import { useOrganization } from "@app/context"; import { useAddIdentityTokenAuth, @@ -16,14 +24,8 @@ import { } from "@app/hooks/api"; import { useGetRelays } from "@app/hooks/api/relays"; import { slugSchema } from "@app/lib/schemas"; -import { faCopy, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { useEffect, useMemo, useState } from "react"; -import { SingleValue } from "react-select"; + import { RelayOption } from "./RelayOption"; -import { useNavigate } from "@tanstack/react-router"; -import { ROUTE_PATHS } from "@app/const/routes"; -import { twMerge } from "tailwind-merge"; type Props = { isOpen: boolean; diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployRelayModal.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployRelayModal.tsx index 2347c0472..399d9851c 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployRelayModal.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployRelayModal.tsx @@ -1,3 +1,9 @@ +import { useEffect, useMemo, useState } from "react"; +import { SingleValue } from "react-select"; +import { faCopy, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { twMerge } from "tailwind-merge"; + import { createNotification } from "@app/components/notifications"; import { FilterableSelect, @@ -15,11 +21,6 @@ import { useGetIdentityTokenAuth } from "@app/hooks/api"; import { slugSchema } from "@app/lib/schemas"; -import { faCopy, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { useEffect, useMemo, useState } from "react"; -import { SingleValue } from "react-select"; -import { twMerge } from "tailwind-merge"; type Props = { isOpen: boolean; diff --git a/frontend/src/pages/organization/NetworkingPage/components/NetworkingTabGroup/NetworkingTabGroup.tsx b/frontend/src/pages/organization/NetworkingPage/components/NetworkingTabGroup/NetworkingTabGroup.tsx index dc90b7f21..07da157ab 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/NetworkingTabGroup/NetworkingTabGroup.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/NetworkingTabGroup/NetworkingTabGroup.tsx @@ -1,10 +1,10 @@ import { useNavigate, useSearch } from "@tanstack/react-router"; import { Tab, TabList, TabPanel, Tabs } from "@app/components/v2"; +import { ROUTE_PATHS } from "@app/const/routes"; import { GatewayTab } from "../GatewayTab/GatewayTab"; import { RelayTab } from "../RelayTab/RelayTab"; -import { ROUTE_PATHS } from "@app/const/routes"; export const NetworkingTabGroup = () => { const navigate = useNavigate({ diff --git a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx index 2bfa8b250..e731aab42 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx @@ -43,6 +43,7 @@ import { import { withPermission } from "@app/hoc"; import { usePopUp } from "@app/hooks"; import { useDeleteRelayById, useGetRelays } from "@app/hooks/api/relays"; + import { DeployRelayModal } from "../GatewayTab/components/DeployRelayModal"; const RelayHealthStatus = ({ heartbeat }: { heartbeat?: string }) => { From ab48e098d2c2df5ee65eee9a22615a4064d6ff2d Mon Sep 17 00:00:00 2001 From: x032205 Date: Fri, 17 Oct 2025 06:43:36 -0400 Subject: [PATCH 03/60] remove low opacity from input --- .../components/GatewayTab/components/DeployGatewayModal.tsx | 2 +- .../components/GatewayTab/components/DeployRelayModal.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx index e5bc048c5..9a98ce4b1 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx @@ -211,7 +211,7 @@ export const DeployGatewayModal = ({ isOpen, onOpenChange }: Props) => {
- + {
- + Date: Fri, 17 Oct 2025 23:25:48 -0400 Subject: [PATCH 04/60] make "deploy new relay" open the modal --- .../components/DeployGatewayModal.tsx | 2 +- .../components/RelayTab/RelayTab.tsx | 23 ++++++++++++++++++- .../organization/NetworkingPage/route.tsx | 3 ++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx index 9a98ce4b1..010126a41 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx @@ -166,7 +166,7 @@ export const DeployGatewayModal = ({ isOpen, onOpenChange }: Props) => { onChange={(newValue) => { if ((newValue as SingleValue<{ id: string }>)?.id === "_create") { navigate({ - search: { selectedTab: "relays" } + search: (prev) => ({ ...prev, selectedTab: "relays", action: "deploy-relay" }) }); return; } diff --git a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx index e731aab42..8c62bae1c 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, @@ -12,6 +12,7 @@ import { 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"; @@ -36,6 +37,7 @@ import { Tooltip, Tr } from "@app/components/v2"; +import { ROUTE_PATHS } from "@app/const/routes"; import { OrgPermissionSubjects, OrgRelayPermissionActions @@ -75,6 +77,25 @@ export const RelayTab = withPermission( "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]); + const deleteRelayById = useDeleteRelayById(); const handleDeleteRelay = async () => { diff --git a/frontend/src/pages/organization/NetworkingPage/route.tsx b/frontend/src/pages/organization/NetworkingPage/route.tsx index 433647476..fb81e3725 100644 --- a/frontend/src/pages/organization/NetworkingPage/route.tsx +++ b/frontend/src/pages/organization/NetworkingPage/route.tsx @@ -5,7 +5,8 @@ import { z } from "zod"; import { NetworkingPage } from "./NetworkingPage"; const NetworkingPageQueryParams = z.object({ - selectedTab: z.string().catch("gateways") + selectedTab: z.string().catch("gateways"), + action: z.string().optional() }); export const Route = createFileRoute( From 91bcc22d1b8774e570cda580cb6a356e9ef61ae8 Mon Sep 17 00:00:00 2001 From: x032205 Date: Fri, 17 Oct 2025 23:29:52 -0400 Subject: [PATCH 05/60] lower opacity for command input while its not ready --- .../components/GatewayTab/components/DeployGatewayModal.tsx | 2 +- .../components/GatewayTab/components/DeployRelayModal.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx index 010126a41..53e061eb5 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx @@ -211,7 +211,7 @@ export const DeployGatewayModal = ({ isOpen, onOpenChange }: Props) => {
- + {
- + Date: Sat, 18 Oct 2025 05:08:19 -0400 Subject: [PATCH 06/60] feat(pam): ui improvements --- .../components/PamAccessAccountModal.tsx | 38 +++++++++++++++++-- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccessAccountModal.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccessAccountModal.tsx index 9049001e6..c298064ef 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccessAccountModal.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccessAccountModal.tsx @@ -19,12 +19,44 @@ export const PamAccessAccountModal = ({ isOpen, onOpenChange, account }: Props) const isDurationValid = useMemo(() => duration && ms(duration || "1s") > 0, [duration]); + const cliDuration = useMemo(() => { + if (!duration) return duration; + + const unit = duration.replace(/[\d\s.-]/g, ""); + + const dayOrLargerUnits = [ + "d", + "day", + "days", + "w", + "week", + "weeks", + "y", + "yr", + "yrs", + "year", + "years" + ]; + + if (unit === "M" || dayOrLargerUnits.includes(unit.toLowerCase())) { + const valueInMs = ms(duration); + const oneHourInMs = 1000 * 60 * 60; + + if (typeof valueInMs === "number" && valueInMs > 0) { + const hours = valueInMs / oneHourInMs; + return `${hours}h`; + } + } + + return duration; + }, [duration]); + const command = useMemo( () => account && account.resource.resourceType === PamResourceType.Postgres - ? `infisical pam db access-account ${account.id} --duration ${duration}` + ? `infisical pam db access-account ${account.id} --duration ${cliDuration}` : "", - [account, duration] + [account, cliDuration] ); if (!account) return null; @@ -48,7 +80,7 @@ export const PamAccessAccountModal = ({ isOpen, onOpenChange, account }: Props) />
- + Date: Sat, 18 Oct 2025 05:21:54 -0400 Subject: [PATCH 07/60] floor hour value --- .../pam/PamAccountsPage/components/PamAccessAccountModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccessAccountModal.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccessAccountModal.tsx index c298064ef..a4a52d9ba 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccessAccountModal.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccessAccountModal.tsx @@ -43,7 +43,7 @@ export const PamAccessAccountModal = ({ isOpen, onOpenChange, account }: Props) const oneHourInMs = 1000 * 60 * 60; if (typeof valueInMs === "number" && valueInMs > 0) { - const hours = valueInMs / oneHourInMs; + const hours = Math.floor(valueInMs / oneHourInMs); return `${hours}h`; } } From 00b2a6bafc8dd8ff8994f031eb5edcb8dc92c316 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Mon, 20 Oct 2025 15:51:26 +0400 Subject: [PATCH 08/60] docs: better postgres SSL docs --- docs/self-hosting/configuration/envars.mdx | 21 +++++++++++++++++++ .../deployment-options/kubernetes-helm.mdx | 11 ++++++++-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/docs/self-hosting/configuration/envars.mdx b/docs/self-hosting/configuration/envars.mdx index 551c79184..edebf1670 100644 --- a/docs/self-hosting/configuration/envars.mdx +++ b/docs/self-hosting/configuration/envars.mdx @@ -116,6 +116,27 @@ The platform utilizes Postgres to persist all of its data and Redis for caching Configure the SSL certificate for securing a Postgres connection by first encoding it in base64. Use the following command to encode your certificate: `echo "" | base64` + + Many cloud providers provide a CA certificate for their data regions that you can use to secure your connection with SSL. + + + + If you're hosting your database on AWS RDS, you can use their publicly available CA certificate as the database root certificate. + + You can find all the available CA certificates for AWS RDS on the official [AWS RDS documentation](https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL.html). + + As an example, if your RDS cluster is hosted in `us-east-1` _(US East, N. Virginia)_, you can use the following root certificate: https://truststore.pki.rds.amazonaws.com/us-east-1/us-east-1-bundle.pem. + + All the available CA certificates can be found in the AWS RDS documentation linked above. + + Remember to base64 encode the certificate before setting it as the `DB_ROOT_CERT` environment variable. `cat /path/to/certificate.pem | base64`. + + ```bash + DB_ROOT_CERT=LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1 # .... (base64 encoded certificate) + DB_CONNECTION_URI=?sslmode=verify-ca # or verify-full depending on your security policies + ``` + + diff --git a/docs/self-hosting/deployment-options/kubernetes-helm.mdx b/docs/self-hosting/deployment-options/kubernetes-helm.mdx index f8a2a6864..f4ef67e1e 100644 --- a/docs/self-hosting/deployment-options/kubernetes-helm.mdx +++ b/docs/self-hosting/deployment-options/kubernetes-helm.mdx @@ -1,8 +1,10 @@ --- title: "Kubernetes via Helm Chart" description: "Learn how to use Helm chart to install Infisical on your Kubernetes cluster." ---- +--- + **Prerequisites** + - You have extensive understanding of [Kubernetes](https://kubernetes.io/) - Installed [Helm package manager](https://helm.sh/) version v3.11.3 or greater - You have [kubectl](https://kubernetes.io/docs/reference/kubectl/kubectl/) installed and connected to your kubernetes cluster @@ -12,7 +14,7 @@ description: "Learn how to use Helm chart to install Infisical on your Kubernete ```bash helm repo add infisical-helm-charts 'https://dl.cloudsmith.io/public/infisical/helm-charts/helm/charts/' ``` - ``` + ```bash helm repo update ``` @@ -61,6 +63,7 @@ description: "Learn how to use Helm chart to install Infisical on your Kubernete For production environments, we recommend using Cloud-based Platform as a Service (PaaS) solutions for PostgreSQL and Redis to ensure high availability. In on-premise setups, it's recommended to configure Redis and Postgres for high availability, either by using Bitnami charts or a custom configuration. + ```yaml simple-values-example.yaml apiVersion: v1 kind: Secret @@ -74,6 +77,10 @@ description: "Learn how to use Helm chart to install Infisical on your Kubernete DB_CONNECTION_URI: <> SITE_URL: <> ``` + + + If you need to configure the SSL certificate for your production Postgres instance, you can use the `DB_ROOT_CERT` environment variable. [Learn more about configuring the SSL certificate](/self-hosting/configuration/envars#aws-rds). + From bf37ad958a51dd593523c6c811012c665a3f3285 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Mon, 20 Oct 2025 15:54:51 +0400 Subject: [PATCH 09/60] feat(helm/infisical-core): auto-reload --- helm-charts/infisical-standalone-postgres/CHANGELOG.md | 5 +++++ helm-charts/infisical-standalone-postgres/Chart.lock | 7 +++++-- helm-charts/infisical-standalone-postgres/Chart.yaml | 6 +++++- .../infisical-standalone-postgres/templates/infisical.yaml | 3 +++ .../templates/schema-migration-job.yaml | 4 ++++ helm-charts/infisical-standalone-postgres/values.yaml | 7 +++++-- 6 files changed, 27 insertions(+), 5 deletions(-) diff --git a/helm-charts/infisical-standalone-postgres/CHANGELOG.md b/helm-charts/infisical-standalone-postgres/CHANGELOG.md index f94f80073..ac6906de3 100644 --- a/helm-charts/infisical-standalone-postgres/CHANGELOG.md +++ b/helm-charts/infisical-standalone-postgres/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.7.2 (October 20, 2025) +Changes: +* Added automatic reloading support for the Infisical deployment when the `infisical.kubeSecretRef` kubernetes secret changes. + * Configurable by `reloader.enabled: true|false`. Defaults to `true`. + ## 1.7.1 (October 10, 2025) Changes: diff --git a/helm-charts/infisical-standalone-postgres/Chart.lock b/helm-charts/infisical-standalone-postgres/Chart.lock index 993acf0a9..ef1f9371b 100644 --- a/helm-charts/infisical-standalone-postgres/Chart.lock +++ b/helm-charts/infisical-standalone-postgres/Chart.lock @@ -8,5 +8,8 @@ dependencies: - name: redis repository: oci://registry-1.docker.io/bitnamicharts version: 18.14.1 -digest: sha256:57a18fb5258fc153d27b633f6570104c7628af651f08f3ae7e1cf8920c2c31fa -generated: "2025-09-30T18:44:50.303037+04:00" +- name: reloader + repository: https://stakater.github.io/stakater-charts + version: 2.2.3 +digest: sha256:cdaf2a4056a24633b7bbcafb72c8c6fd1c0e8d75a9ceb016917906f83db1e6b0 +generated: "2025-10-20T13:56:11.25867+04:00" diff --git a/helm-charts/infisical-standalone-postgres/Chart.yaml b/helm-charts/infisical-standalone-postgres/Chart.yaml index e7069532f..543043843 100644 --- a/helm-charts/infisical-standalone-postgres/Chart.yaml +++ b/helm-charts/infisical-standalone-postgres/Chart.yaml @@ -7,7 +7,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.7.1 +version: 1.7.2 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to @@ -28,3 +28,7 @@ dependencies: version: 18.14.1 repository: oci://registry-1.docker.io/bitnamicharts condition: redis.enabled + - name: reloader + version: 2.2.3 + repository: https://stakater.github.io/stakater-charts + condition: reloader.enabled diff --git a/helm-charts/infisical-standalone-postgres/templates/infisical.yaml b/helm-charts/infisical-standalone-postgres/templates/infisical.yaml index 11ebb8f3d..49e7626dd 100644 --- a/helm-charts/infisical-standalone-postgres/templates/infisical.yaml +++ b/helm-charts/infisical-standalone-postgres/templates/infisical.yaml @@ -4,6 +4,9 @@ kind: Deployment metadata: name: {{ include "infisical.fullname" . }} annotations: + {{- if .Values.reloader.enabled }} + secret.reloader.stakater.com/reload: {{ $infisicalValues.kubeSecretRef }} + {{- end }} updatedAt: {{ now | date "2006-01-01 MST 15:04:05" | quote }} {{- with $infisicalValues.deploymentAnnotations }} {{- toYaml . | nindent 4 }} diff --git a/helm-charts/infisical-standalone-postgres/templates/schema-migration-job.yaml b/helm-charts/infisical-standalone-postgres/templates/schema-migration-job.yaml index 8280b98a3..a887ae37b 100644 --- a/helm-charts/infisical-standalone-postgres/templates/schema-migration-job.yaml +++ b/helm-charts/infisical-standalone-postgres/templates/schema-migration-job.yaml @@ -15,6 +15,10 @@ spec: app.kubernetes.io/managed-by: {{ .Release.Service | quote }} app.kubernetes.io/instance: {{ .Release.Name | quote }} helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" + {{- if .Values.reloader.enabled }} + annotations: + secret.reloader.stakater.com/reload: {{ $infisicalValues.kubeSecretRef }} + {{- end }} spec: serviceAccountName: {{ include "infisical.serviceAccountName" . }} {{- if $infisicalValues.image.imagePullSecrets }} diff --git a/helm-charts/infisical-standalone-postgres/values.yaml b/helm-charts/infisical-standalone-postgres/values.yaml index 078c84e78..770b4f5b8 100644 --- a/helm-charts/infisical-standalone-postgres/values.yaml +++ b/helm-charts/infisical-standalone-postgres/values.yaml @@ -118,8 +118,7 @@ ingress: # -- Custom annotations for ingress resource annotations: {} # -- TLS settings for HTTPS access - tls: - [] + tls: [] # -- TLS secret name for HTTPS # - secretName: letsencrypt-prod # -- Domain name to associate with the TLS certificate @@ -184,3 +183,7 @@ redis: # -- Redis deployment type (e.g., standalone or cluster) architecture: standalone + +# -- Reloader is used to reload the Infisical instance when the Kubernetes secret referenced by `infisical.kubeSecretRef` is updated +reloader: + enabled: true From ccaf4d78d1a22d06da3a05614aee39d0fd7b19b7 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Mon, 20 Oct 2025 16:00:01 +0400 Subject: [PATCH 10/60] fix(doc): hsm versioning mismatch --- docs/documentation/platform/kms/hsm-integration.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/documentation/platform/kms/hsm-integration.mdx b/docs/documentation/platform/kms/hsm-integration.mdx index 45d883977..7a8d15fe5 100644 --- a/docs/documentation/platform/kms/hsm-integration.mdx +++ b/docs/documentation/platform/kms/hsm-integration.mdx @@ -1428,7 +1428,7 @@ Enabling HSM encryption has a set of key benefits: infisical: image: repository: infisical/infisical - tag: "v0.151.0-nightly-20251013.1" + tag: "v0.151.0" pullPolicy: IfNotPresent extraVolumeMounts: From 2b8e2d67ee897181694f926ef221276c553b1892 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Mon, 20 Oct 2025 17:45:27 +0400 Subject: [PATCH 11/60] requested changes --- helm-charts/infisical-standalone-postgres/CHANGELOG.md | 2 +- helm-charts/infisical-standalone-postgres/Chart.yaml | 2 +- .../infisical-standalone-postgres/templates/infisical.yaml | 2 +- .../templates/schema-migration-job.yaml | 2 +- helm-charts/infisical-standalone-postgres/values.yaml | 7 +++---- 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/helm-charts/infisical-standalone-postgres/CHANGELOG.md b/helm-charts/infisical-standalone-postgres/CHANGELOG.md index ac6906de3..a0e7216e0 100644 --- a/helm-charts/infisical-standalone-postgres/CHANGELOG.md +++ b/helm-charts/infisical-standalone-postgres/CHANGELOG.md @@ -1,7 +1,7 @@ ## 1.7.2 (October 20, 2025) Changes: * Added automatic reloading support for the Infisical deployment when the `infisical.kubeSecretRef` kubernetes secret changes. - * Configurable by `reloader.enabled: true|false`. Defaults to `true`. + * Configurable by `infisical.redeployOnSecretChange: true|false`. Defaults to `true`. ## 1.7.1 (October 10, 2025) diff --git a/helm-charts/infisical-standalone-postgres/Chart.yaml b/helm-charts/infisical-standalone-postgres/Chart.yaml index 543043843..0d4381e72 100644 --- a/helm-charts/infisical-standalone-postgres/Chart.yaml +++ b/helm-charts/infisical-standalone-postgres/Chart.yaml @@ -31,4 +31,4 @@ dependencies: - name: reloader version: 2.2.3 repository: https://stakater.github.io/stakater-charts - condition: reloader.enabled + condition: infisical.redeployOnSecretChange diff --git a/helm-charts/infisical-standalone-postgres/templates/infisical.yaml b/helm-charts/infisical-standalone-postgres/templates/infisical.yaml index 49e7626dd..d4637866b 100644 --- a/helm-charts/infisical-standalone-postgres/templates/infisical.yaml +++ b/helm-charts/infisical-standalone-postgres/templates/infisical.yaml @@ -4,7 +4,7 @@ kind: Deployment metadata: name: {{ include "infisical.fullname" . }} annotations: - {{- if .Values.reloader.enabled }} + {{- if $infisicalValues.redeployOnSecretChange }} secret.reloader.stakater.com/reload: {{ $infisicalValues.kubeSecretRef }} {{- end }} updatedAt: {{ now | date "2006-01-01 MST 15:04:05" | quote }} diff --git a/helm-charts/infisical-standalone-postgres/templates/schema-migration-job.yaml b/helm-charts/infisical-standalone-postgres/templates/schema-migration-job.yaml index a887ae37b..c53c6e3d1 100644 --- a/helm-charts/infisical-standalone-postgres/templates/schema-migration-job.yaml +++ b/helm-charts/infisical-standalone-postgres/templates/schema-migration-job.yaml @@ -15,7 +15,7 @@ spec: app.kubernetes.io/managed-by: {{ .Release.Service | quote }} app.kubernetes.io/instance: {{ .Release.Name | quote }} helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - {{- if .Values.reloader.enabled }} + {{- if $infisicalValues.redeployOnSecretChange }} annotations: secret.reloader.stakater.com/reload: {{ $infisicalValues.kubeSecretRef }} {{- end }} diff --git a/helm-charts/infisical-standalone-postgres/values.yaml b/helm-charts/infisical-standalone-postgres/values.yaml index 770b4f5b8..202a9008a 100644 --- a/helm-charts/infisical-standalone-postgres/values.yaml +++ b/helm-charts/infisical-standalone-postgres/values.yaml @@ -13,6 +13,9 @@ infisical: # -- Automatically migrates new database schema when deploying autoDatabaseSchemaMigration: true + # -- redeployOnSecretChange is used to reload the Infisical instance when the Kubernetes secret referenced by `infisical.kubeSecretRef` is updated + redeployOnSecretChange: true + autoBootstrap: # -- Enable auto-bootstrap of the Infisical instance enabled: false @@ -183,7 +186,3 @@ redis: # -- Redis deployment type (e.g., standalone or cluster) architecture: standalone - -# -- Reloader is used to reload the Infisical instance when the Kubernetes secret referenced by `infisical.kubeSecretRef` is updated -reloader: - enabled: true From 64df9872b301ae324abfff4b74ac8db46a7e79f1 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Mon, 20 Oct 2025 21:04:49 +0400 Subject: [PATCH 12/60] Update run-helm-chart-tests-infisical-standalone-postgres.yml --- .../run-helm-chart-tests-infisical-standalone-postgres.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml b/.github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml index d48562fc6..a7bb2c619 100644 --- a/.github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml +++ b/.github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml @@ -29,6 +29,7 @@ jobs: run: | helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx helm repo add bitnami https://charts.bitnami.com/bitnami + helm repo add stakater https://stakater.github.io/stakater-charts helm repo update - name: Set up chart-testing From 53563a5c3a836fa280073a21f3fded5e99b1b8e1 Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Mon, 20 Oct 2025 21:07:32 +0400 Subject: [PATCH 13/60] Update Chart.lock --- helm-charts/infisical-standalone-postgres/Chart.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/helm-charts/infisical-standalone-postgres/Chart.lock b/helm-charts/infisical-standalone-postgres/Chart.lock index ef1f9371b..8e7a3004e 100644 --- a/helm-charts/infisical-standalone-postgres/Chart.lock +++ b/helm-charts/infisical-standalone-postgres/Chart.lock @@ -11,5 +11,5 @@ dependencies: - name: reloader repository: https://stakater.github.io/stakater-charts version: 2.2.3 -digest: sha256:cdaf2a4056a24633b7bbcafb72c8c6fd1c0e8d75a9ceb016917906f83db1e6b0 -generated: "2025-10-20T13:56:11.25867+04:00" +digest: sha256:ae7d9ff526de87e972fed0f9c8f32ca40af8cf8b24b59d814cf72beb66ee4198 +generated: "2025-10-20T21:07:19.162271+04:00" From 4dd343af11206c9b32ad2b9da84704072fa559cf Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Tue, 21 Oct 2025 22:27:20 +0400 Subject: [PATCH 14/60] removed reloader requested changes --- .github/values.yaml | 57 ------------------- .../workflows/helm-release-infisical-core.yml | 2 +- ...rt-tests-infisical-standalone-postgres.yml | 2 +- .../CHANGELOG.md | 4 +- .../infisical-standalone-postgres/Chart.lock | 7 +-- .../infisical-standalone-postgres/Chart.yaml | 4 -- .../infisical-standalone-postgres/README.md | 1 - .../templates/infisical.yaml | 13 ----- .../templates/schema-migration-job.yaml | 56 ------------------ .../infisical-standalone-postgres/values.yaml | 8 +-- 10 files changed, 8 insertions(+), 146 deletions(-) delete mode 100644 .github/values.yaml delete mode 100644 helm-charts/infisical-standalone-postgres/templates/schema-migration-job.yaml diff --git a/.github/values.yaml b/.github/values.yaml deleted file mode 100644 index 1b3ffd87a..000000000 --- a/.github/values.yaml +++ /dev/null @@ -1,57 +0,0 @@ -## @section Common parameters -## - -## @param nameOverride Override release name -## -nameOverride: "" -## @param fullnameOverride Override release fullname -## -fullnameOverride: "" - -## @section Infisical backend parameters -## Documentation : https://infisical.com/docs/self-hosting/deployments/kubernetes -## - -infisical: - autoDatabaseSchemaMigration: false - - enabled: false - - name: infisical - replicaCount: 3 - image: - repository: infisical/staging_infisical - tag: "latest" - pullPolicy: Always - - deploymentAnnotations: - secrets.infisical.com/auto-reload: "true" - - kubeSecretRef: "managed-secret" - -ingress: - ## @param ingress.enabled Enable ingress - ## - enabled: true - ## @param ingress.ingressClassName Ingress class name - ## - ingressClassName: nginx - ## @param ingress.nginx.enabled Ingress controller - ## - # nginx: - # enabled: true - ## @param ingress.annotations Ingress annotations - ## - annotations: - cert-manager.io/cluster-issuer: "letsencrypt-prod" - hostName: "gamma.infisical.com" - tls: - - secretName: letsencrypt-prod - hosts: - - gamma.infisical.com - -postgresql: - enabled: false - -redis: - enabled: false diff --git a/.github/workflows/helm-release-infisical-core.yml b/.github/workflows/helm-release-infisical-core.yml index 6c317cc27..49118a5ae 100644 --- a/.github/workflows/helm-release-infisical-core.yml +++ b/.github/workflows/helm-release-infisical-core.yml @@ -56,7 +56,7 @@ jobs: --config ct.yaml \ --charts helm-charts/infisical-standalone-postgres \ --helm-extra-args="--timeout=300s" \ - --helm-extra-set-args="--set ingress.nginx.enabled=false --set infisical.autoDatabaseSchemaMigration=false --set infisical.replicaCount=1 --set infisical.image.tag=v0.132.2-postgres" \ + --helm-extra-set-args="--set ingress.nginx.enabled=false --set infisical.replicaCount=1 --set infisical.image.tag=v0.151.0" \ --namespace infisical-standalone-postgres release: diff --git a/.github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml b/.github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml index a7bb2c619..2023de187 100644 --- a/.github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml +++ b/.github/workflows/run-helm-chart-tests-infisical-standalone-postgres.yml @@ -67,5 +67,5 @@ jobs: --config ct.yaml \ --charts helm-charts/infisical-standalone-postgres \ --helm-extra-args="--timeout=300s" \ - --helm-extra-set-args="--set ingress.nginx.enabled=false --set infisical.autoDatabaseSchemaMigration=false --set infisical.replicaCount=1 --set infisical.image.tag=v0.132.2-postgres --set infisical.autoBootstrap.enabled=true" \ + --helm-extra-set-args="--set ingress.nginx.enabled=false --set infisical.replicaCount=1 --set infisical.image.tag=v0.151.0 --set infisical.autoBootstrap.enabled=true" \ --namespace infisical-standalone-postgres diff --git a/helm-charts/infisical-standalone-postgres/CHANGELOG.md b/helm-charts/infisical-standalone-postgres/CHANGELOG.md index a0e7216e0..5298432d7 100644 --- a/helm-charts/infisical-standalone-postgres/CHANGELOG.md +++ b/helm-charts/infisical-standalone-postgres/CHANGELOG.md @@ -1,7 +1,9 @@ ## 1.7.2 (October 20, 2025) Changes: +* Updated the default `infisical.image.tag` value to `v0.151.0`. +* `autoDatabaseSchemaMigration` has been fully removed as all newer versions of Infisical automatically run migrations as apart of the startup process. * Added automatic reloading support for the Infisical deployment when the `infisical.kubeSecretRef` kubernetes secret changes. - * Configurable by `infisical.redeployOnSecretChange: true|false`. Defaults to `true`. + * Configurable by `infisical.redeployOnSecretChange: true|false`. Defaults to `false`. ## 1.7.1 (October 10, 2025) diff --git a/helm-charts/infisical-standalone-postgres/Chart.lock b/helm-charts/infisical-standalone-postgres/Chart.lock index 8e7a3004e..b8ae1fd3f 100644 --- a/helm-charts/infisical-standalone-postgres/Chart.lock +++ b/helm-charts/infisical-standalone-postgres/Chart.lock @@ -8,8 +8,5 @@ dependencies: - name: redis repository: oci://registry-1.docker.io/bitnamicharts version: 18.14.1 -- name: reloader - repository: https://stakater.github.io/stakater-charts - version: 2.2.3 -digest: sha256:ae7d9ff526de87e972fed0f9c8f32ca40af8cf8b24b59d814cf72beb66ee4198 -generated: "2025-10-20T21:07:19.162271+04:00" +digest: sha256:57a18fb5258fc153d27b633f6570104c7628af651f08f3ae7e1cf8920c2c31fa +generated: "2025-10-21T22:30:21.313884+04:00" diff --git a/helm-charts/infisical-standalone-postgres/Chart.yaml b/helm-charts/infisical-standalone-postgres/Chart.yaml index 0d4381e72..21f358626 100644 --- a/helm-charts/infisical-standalone-postgres/Chart.yaml +++ b/helm-charts/infisical-standalone-postgres/Chart.yaml @@ -28,7 +28,3 @@ dependencies: version: 18.14.1 repository: oci://registry-1.docker.io/bitnamicharts condition: redis.enabled - - name: reloader - version: 2.2.3 - repository: https://stakater.github.io/stakater-charts - condition: infisical.redeployOnSecretChange diff --git a/helm-charts/infisical-standalone-postgres/README.md b/helm-charts/infisical-standalone-postgres/README.md index cd0e18848..b266563e8 100644 --- a/helm-charts/infisical-standalone-postgres/README.md +++ b/helm-charts/infisical-standalone-postgres/README.md @@ -18,7 +18,6 @@ A helm chart to deploy Infisical |-----|------|---------|-------------| | fullnameOverride | string | `""` | Overrides the full name of the release, affecting resource names | | infisical.affinity | object | `{}` | Node affinity settings for pod placement | -| infisical.autoDatabaseSchemaMigration | bool | `true` | Automatically migrates new database schema when deploying | | infisical.databaseSchemaMigrationJob.image.pullPolicy | string | `"IfNotPresent"` | Pulls image only if not present on the node | | infisical.databaseSchemaMigrationJob.image.repository | string | `"ghcr.io/groundnuty/k8s-wait-for"` | Image repository for migration wait job | | infisical.databaseSchemaMigrationJob.image.tag | string | `"no-root-v2.0"` | Image tag version | diff --git a/helm-charts/infisical-standalone-postgres/templates/infisical.yaml b/helm-charts/infisical-standalone-postgres/templates/infisical.yaml index d4637866b..d13c51295 100644 --- a/helm-charts/infisical-standalone-postgres/templates/infisical.yaml +++ b/helm-charts/infisical-standalone-postgres/templates/infisical.yaml @@ -4,9 +4,6 @@ kind: Deployment metadata: name: {{ include "infisical.fullname" . }} annotations: - {{- if $infisicalValues.redeployOnSecretChange }} - secret.reloader.stakater.com/reload: {{ $infisicalValues.kubeSecretRef }} - {{- end }} updatedAt: {{ now | date "2006-01-01 MST 15:04:05" | quote }} {{- with $infisicalValues.deploymentAnnotations }} {{- toYaml . | nindent 4 }} @@ -47,16 +44,6 @@ spec: {{- if $infisicalValues.image.imagePullSecrets }} imagePullSecrets: {{- toYaml $infisicalValues.image.imagePullSecrets | nindent 6 }} - {{- end }} - {{- if $infisicalValues.autoDatabaseSchemaMigration }} - serviceAccountName: {{ include "infisical.serviceAccountName" . }} - initContainers: - - name: "migration-init" - image: "{{ $infisicalValues.databaseSchemaMigrationJob.image.repository }}:{{ $infisicalValues.databaseSchemaMigrationJob.image.tag }}" - imagePullPolicy: {{ $infisicalValues.databaseSchemaMigrationJob.image.pullPolicy }} - args: - - "job" - - "{{ .Release.Name }}-schema-migration-{{ .Release.Revision }}" {{- end }} containers: - name: {{ template "infisical.name" . }}-{{ $infisicalValues.name }} diff --git a/helm-charts/infisical-standalone-postgres/templates/schema-migration-job.yaml b/helm-charts/infisical-standalone-postgres/templates/schema-migration-job.yaml deleted file mode 100644 index c53c6e3d1..000000000 --- a/helm-charts/infisical-standalone-postgres/templates/schema-migration-job.yaml +++ /dev/null @@ -1,56 +0,0 @@ -{{- $infisicalValues := .Values.infisical }} -{{- if $infisicalValues.autoDatabaseSchemaMigration }} -apiVersion: batch/v1 -kind: Job -metadata: - name: "{{ .Release.Name }}-schema-migration-{{ .Release.Revision }}" - labels: - helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" -spec: - backoffLimit: 10 - template: - metadata: - name: "{{ .Release.Name }}-create-tables" - labels: - app.kubernetes.io/managed-by: {{ .Release.Service | quote }} - app.kubernetes.io/instance: {{ .Release.Name | quote }} - helm.sh/chart: "{{ .Chart.Name }}-{{ .Chart.Version }}" - {{- if $infisicalValues.redeployOnSecretChange }} - annotations: - secret.reloader.stakater.com/reload: {{ $infisicalValues.kubeSecretRef }} - {{- end }} - spec: - serviceAccountName: {{ include "infisical.serviceAccountName" . }} - {{- if $infisicalValues.image.imagePullSecrets }} - imagePullSecrets: - {{- toYaml $infisicalValues.image.imagePullSecrets | nindent 6 }} - {{- end }} - restartPolicy: OnFailure - containers: - - name: infisical-schema-migration - image: "{{ $infisicalValues.image.repository }}:{{ $infisicalValues.image.tag }}" - command: ["npm", "run", "migration:latest"] - env: - {{- if .Values.postgresql.useExistingPostgresSecret.enabled }} - - name: DB_CONNECTION_URI - valueFrom: - secretKeyRef: - name: {{ .Values.postgresql.useExistingPostgresSecret.existingConnectionStringSecret.name }} - key: {{ .Values.postgresql.useExistingPostgresSecret.existingConnectionStringSecret.key }} - {{- end }} - {{- if .Values.postgresql.enabled }} - - name: DB_CONNECTION_URI - value: {{ include "infisical.postgresDBConnectionString" . }} - {{- end }} - envFrom: - - secretRef: - name: {{ $infisicalValues.kubeSecretRef }} - {{- with $infisicalValues.extraVolumeMounts }} - volumeMounts: - {{- toYaml . | nindent 10 }} - {{- end }} - {{- with $infisicalValues.extraVolumes }} - volumes: - {{- toYaml . | nindent 8 }} - {{- end }} -{{- end }} \ No newline at end of file diff --git a/helm-charts/infisical-standalone-postgres/values.yaml b/helm-charts/infisical-standalone-postgres/values.yaml index 202a9008a..e0a81d9e3 100644 --- a/helm-charts/infisical-standalone-postgres/values.yaml +++ b/helm-charts/infisical-standalone-postgres/values.yaml @@ -10,12 +10,6 @@ infisical: # -- Sets the name of the deployment within this chart name: infisical - # -- Automatically migrates new database schema when deploying - autoDatabaseSchemaMigration: true - - # -- redeployOnSecretChange is used to reload the Infisical instance when the Kubernetes secret referenced by `infisical.kubeSecretRef` is updated - redeployOnSecretChange: true - autoBootstrap: # -- Enable auto-bootstrap of the Infisical instance enabled: false @@ -71,7 +65,7 @@ infisical: # -- Image repository for the Infisical service repository: infisical/infisical # -- Specific version tag of the Infisical image. View the latest version here https://hub.docker.com/r/infisical/infisical - tag: "v0.93.1-postgres" + tag: "v0.151.0" # -- Pulls image only if not already present on the node pullPolicy: IfNotPresent # -- Secret references for pulling the image, if needed From 52352fc00ae193b630c861bd2ecdffa83ac9cab6 Mon Sep 17 00:00:00 2001 From: x032205 Date: Wed, 22 Oct 2025 02:51:58 -0400 Subject: [PATCH 15/60] rewrite to use a multi-step modal flow --- frontend/src/hooks/api/index.tsx | 1 + .../components/GatewayTab/GatewayTab.tsx | 4 +- .../components/DeployGatewayModal.tsx | 245 --------------- .../components/DeployRelayModal.tsx | 205 ------------ .../components/GatewayCliDeploymentMethod.tsx | 297 ++++++++++++++++++ .../components/GatewayDeployModal.tsx | 45 +++ .../GatewayDeploymentMethodSelect.tsx | 56 ++++ .../components/RelayTab/RelayTab.tsx | 4 +- .../components/RelayCliDeploymentMethod.tsx | 268 ++++++++++++++++ .../RelayTab/components/RelayDeployModal.tsx | 45 +++ .../RelayDeploymentMethodSelect.tsx | 56 ++++ 11 files changed, 772 insertions(+), 454 deletions(-) delete mode 100644 frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx delete mode 100644 frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployRelayModal.tsx create mode 100644 frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx create mode 100644 frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeployModal.tsx create mode 100644 frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeploymentMethodSelect.tsx create mode 100644 frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayCliDeploymentMethod.tsx create mode 100644 frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayDeployModal.tsx create mode 100644 frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayDeploymentMethodSelect.tsx 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 a93694a3d..a2a24015f 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/GatewayTab.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/GatewayTab.tsx @@ -48,8 +48,8 @@ import { usePopUp } from "@app/hooks"; import { gatewaysQueryKeys, useDeleteGatewayById } from "@app/hooks/api/gateways"; import { useDeleteGatewayV2ById } from "@app/hooks/api/gateways-v2"; -import { DeployGatewayModal } from "./components/DeployGatewayModal"; import { EditGatewayDetailsModal } from "./components/EditGatewayDetailsModal"; +import { GatewayDeployModal } from "./components/GatewayDeployModal"; const GatewayHealthStatus = ({ heartbeat }: { heartbeat?: string }) => { const heartbeatDate = heartbeat ? new Date(heartbeat) : null; @@ -269,7 +269,7 @@ export const GatewayTab = withPermission( deleteKey="confirm" onDeleteApproved={() => handleDeleteGateway()} /> - handlePopUpToggle("deployGateway", isOpen)} /> diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx deleted file mode 100644 index 53e061eb5..000000000 --- a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployGatewayModal.tsx +++ /dev/null @@ -1,245 +0,0 @@ -import { useEffect, useMemo, useState } from "react"; -import { SingleValue } from "react-select"; -import { faCopy, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { useNavigate } from "@tanstack/react-router"; -import { twMerge } from "tailwind-merge"; - -import { createNotification } from "@app/components/notifications"; -import { - FilterableSelect, - FormLabel, - IconButton, - Input, - Modal, - ModalContent -} from "@app/components/v2"; -import { ROUTE_PATHS } from "@app/const/routes"; -import { useOrganization } from "@app/context"; -import { - useAddIdentityTokenAuth, - useCreateTokenIdentityTokenAuth, - useGetIdentityMembershipOrgs, - useGetIdentityTokenAuth -} from "@app/hooks/api"; -import { useGetRelays } from "@app/hooks/api/relays"; -import { slugSchema } from "@app/lib/schemas"; - -import { RelayOption } from "./RelayOption"; - -type Props = { - isOpen: boolean; - onOpenChange: (isOpen: boolean) => void; -}; - -export const DeployGatewayModal = ({ isOpen, onOpenChange }: Props) => { - 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 [name, setName] = useState(""); - const [relay, setRelay] = useState(null); - const [identity, setIdentity] = useState(null); - const [identityToken, setIdentityToken] = useState(""); - - useEffect(() => { - if (!isOpen) { - setName(""); - setRelay(null); - setIdentity(null); - setIdentityToken(""); - } - }, [isOpen]); - - const { data: relays, isPending: isRelaysLoading } = useGetRelays(); - - const { currentOrg } = useOrganization(); - const organizationId = currentOrg?.id || ""; - - const { data: identityMembershipOrgsData, isPending: isIdentitiesLoading } = - useGetIdentityMembershipOrgs({ - organizationId, - limit: 20000 - }); - const identityMembershipOrgs = identityMembershipOrgsData?.identityMemberships || []; - - const { mutateAsync: createToken } = useCreateTokenIdentityTokenAuth(); - const { mutateAsync: addIdentityTokenAuth } = useAddIdentityTokenAuth(); - const { refetch } = useGetIdentityTokenAuth(identity?.id ?? ""); - - useEffect(() => { - const generateToken = async () => { - if (!identity) return; - - try { - const { data: identityTokenAuth } = await refetch(); - if (!identityTokenAuth) { - await addIdentityTokenAuth({ - identityId: identity.id, - organizationId, - accessTokenTTL: 2592000, - accessTokenMaxTTL: 2592000, - accessTokenNumUsesLimit: 0, - accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] - }); - createNotification({ - text: "Automatically enabled token authentication for this identity.", - type: "info" - }); - } - - const token = await createToken({ - identityId: identity.id, - name: "gateway token (autogenerated)" - }); - setIdentityToken(token.accessToken); - createNotification({ - text: "Automatically generated a token for this identity.", - type: "info" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to generate token for identity", - type: "error" - }); - setIdentityToken(""); - } - }; - - generateToken(); - }, [identity, organizationId, refetch, addIdentityTokenAuth, createToken]); - - const handleIdentityChange = (selectedIdentity: SingleValue<{ id: string; name: string }>) => { - setIdentity(selectedIdentity); - setIdentityToken(""); - }; - - const isNameValid = useMemo(() => !name || slugSchema().safeParse(name).success, [name]); - - const isCommandReady = useMemo( - () => !!name && isNameValid && !!relay && !!identityToken, - [name, isNameValid, relay, identityToken] - ); - - const command = useMemo( - () => - `infisical gateway start --name=${name} --relay=${ - relay?.name || "" - } --domain=${siteURL} --token=${identityToken}`, - [name, relay, identityToken, siteURL] - ); - - return ( - - - - setName(e.target.value)} - placeholder="Enter gateway name..." - isError={!isNameValid} - /> - - - { - 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 }} - /> - - - - 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} - /> - - -
- - { - navigator.clipboard.writeText(command); - - createNotification({ - text: "Command copied to clipboard", - type: "info" - }); - }} - className={twMerge("w-10", !isCommandReady && "pointer-events-none opacity-50")} - isDisabled={!isCommandReady} - > - - -
- - Install the Infisical CLI - - -
-
- ); -}; diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployRelayModal.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployRelayModal.tsx deleted file mode 100644 index 61501c40f..000000000 --- a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/DeployRelayModal.tsx +++ /dev/null @@ -1,205 +0,0 @@ -import { useEffect, useMemo, useState } from "react"; -import { SingleValue } from "react-select"; -import { faCopy, faUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { twMerge } from "tailwind-merge"; - -import { createNotification } from "@app/components/notifications"; -import { - FilterableSelect, - FormLabel, - IconButton, - Input, - Modal, - ModalContent -} from "@app/components/v2"; -import { useOrganization } from "@app/context"; -import { - useAddIdentityTokenAuth, - useCreateTokenIdentityTokenAuth, - useGetIdentityMembershipOrgs, - useGetIdentityTokenAuth -} from "@app/hooks/api"; -import { slugSchema } from "@app/lib/schemas"; - -type Props = { - isOpen: boolean; - onOpenChange: (isOpen: boolean) => void; -}; - -export const DeployRelayModal = ({ isOpen, onOpenChange }: Props) => { - const { protocol, hostname, port } = window.location; - const portSuffix = port && port !== "80" ? `:${port}` : ""; - const siteURL = `${protocol}//${hostname}${portSuffix}`; - - const [name, setName] = useState(""); - const [host, setHost] = useState(""); - const [identity, setIdentity] = useState(null); - const [identityToken, setIdentityToken] = useState(""); - - useEffect(() => { - if (!isOpen) { - setName(""); - setHost(""); - setIdentity(null); - setIdentityToken(""); - } - }, [isOpen]); - - const { currentOrg } = useOrganization(); - const organizationId = currentOrg?.id || ""; - - const { data: identityMembershipOrgsData, isPending: isIdentitiesLoading } = - useGetIdentityMembershipOrgs({ - organizationId, - limit: 20000 - }); - const identityMembershipOrgs = identityMembershipOrgsData?.identityMemberships || []; - - const { mutateAsync: createToken } = useCreateTokenIdentityTokenAuth(); - const { mutateAsync: addIdentityTokenAuth } = useAddIdentityTokenAuth(); - const { refetch } = useGetIdentityTokenAuth(identity?.id ?? ""); - - useEffect(() => { - const generateToken = async () => { - if (!identity) return; - - try { - const { data: identityTokenAuth } = await refetch(); - if (!identityTokenAuth) { - await addIdentityTokenAuth({ - identityId: identity.id, - organizationId, - accessTokenTTL: 2592000, - accessTokenMaxTTL: 2592000, - accessTokenNumUsesLimit: 0, - accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] - }); - createNotification({ - text: "Automatically enabled token authentication for this identity.", - type: "info" - }); - } - - const token = await createToken({ - identityId: identity.id, - name: "relay token (autogenerated)" - }); - setIdentityToken(token.accessToken); - createNotification({ - text: "Automatically generated a token for this identity.", - type: "info" - }); - } catch (err) { - console.error(err); - createNotification({ - text: "Failed to generate token for identity", - type: "error" - }); - setIdentityToken(""); - } - }; - - generateToken(); - }, [identity, organizationId, refetch, addIdentityTokenAuth, createToken]); - - const handleIdentityChange = (selectedIdentity: SingleValue<{ id: string; name: string }>) => { - setIdentity(selectedIdentity); - setIdentityToken(""); - }; - - const isNameValid = useMemo(() => !name || slugSchema().safeParse(name).success, [name]); - - const isCommandReady = useMemo( - () => !!name && !!host && isNameValid && !!identityToken, - [name, isNameValid, host, identityToken] - ); - - const command = useMemo( - () => - `infisical relay start --name=${name} --domain=${siteURL} --host=${host} --token=${identityToken}`, - [name, siteURL, host, identityToken] - ); - - return ( - - - - setName(e.target.value)} - placeholder="Enter relay name..." - isError={!isNameValid} - /> - - - setHost(e.target.value)} placeholder="0.0.0.0" /> - - - - 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} - /> - - -
- - { - navigator.clipboard.writeText(command); - - createNotification({ - text: "Command copied to clipboard", - type: "info" - }); - }} - className={twMerge("w-10", !isCommandReady && "pointer-events-none opacity-50")} - isDisabled={!isCommandReady} - > - - -
- - Install the Infisical CLI - - -
-
- ); -}; 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..279dc61c9 --- /dev/null +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx @@ -0,0 +1,297 @@ +import { useMemo, useState } from "react"; +import { SingleValue } from "react-select"; +import { faCopy, 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, + FilterableSelect, + FormLabel, + IconButton, + Input, + ModalClose +} from "@app/components/v2"; +import { ROUTE_PATHS } from "@app/const/routes"; +import { useOrganization } from "@app/context"; +import { + useAddIdentityTokenAuth, + useCreateTokenIdentityTokenAuth, + useGetIdentityMembershipOrgs, + useGetIdentityTokenAuth, + useGetRelays +} from "@app/hooks/api"; +import { slugSchema } from "@app/lib/schemas"; + +import { RelayOption } from "./RelayOption"; + +const formSchema = 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" }), + identity: z + .object( + { + id: z.string(), + name: z.string() + }, + { required_error: "Identity is required" } + ) + .nullable() + .refine((val) => val !== null, { message: "Identity 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 [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 { 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([]); + const validation = formSchema.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: "Automatically enabled token authentication for this identity.", + type: "info" + }); + } + + const token = await createToken({ + identityId: validatedIdentity.id, + name: "gateway token (autogenerated)" + }); + setIdentityToken(token.accessToken); + createNotification({ + text: "Automatically generated a token for this identity.", + type: "info" + }); + setStep("command"); + } catch (err) { + console.error(err); + createNotification({ + text: "Failed to generate token for identity", + type: "error" + }); + setIdentityToken(""); + } + }; + + 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}

+ )} + + + + 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}

} + +
+ + + + +
+ + ); +}; 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..0a1fd5e12 --- /dev/null +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeploymentMethodSelect.tsx @@ -0,0 +1,56 @@ +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 } = option; + + return ( + + ); + })} +
+ ); +}; diff --git a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx index 8c62bae1c..9ca3e1318 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/RelayTab.tsx @@ -46,7 +46,7 @@ import { withPermission } from "@app/hoc"; import { usePopUp } from "@app/hooks"; import { useDeleteRelayById, useGetRelays } from "@app/hooks/api/relays"; -import { DeployRelayModal } from "../GatewayTab/components/DeployRelayModal"; +import { RelayDeployModal } from "./components/RelayDeployModal"; const RelayHealthStatus = ({ heartbeat }: { heartbeat?: string }) => { const heartbeatDate = heartbeat ? new Date(heartbeat) : null; @@ -258,7 +258,7 @@ 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..543d31bae --- /dev/null +++ b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayCliDeploymentMethod.tsx @@ -0,0 +1,268 @@ +import { useMemo, useState } from "react"; +import { SingleValue } from "react-select"; +import { faCopy, 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, + FilterableSelect, + FormLabel, + IconButton, + Input, + ModalClose +} from "@app/components/v2"; +import { useOrganization } from "@app/context"; +import { + useAddIdentityTokenAuth, + useCreateTokenIdentityTokenAuth, + useGetIdentityMembershipOrgs, + useGetIdentityTokenAuth +} from "@app/hooks/api"; +import { slugSchema } from "@app/lib/schemas"; + +const formSchema = 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("")), + identity: z + .object( + { + id: z.string(), + name: z.string() + }, + { required_error: "Identity is required" } + ) + .nullable() + .refine((val) => val !== null, { message: "Identity is required" }) +}); + +export const RelayCliDeploymentMethod = () => { + const { protocol, hostname, port } = window.location; + const portSuffix = port && port !== "80" ? `:${port}` : ""; + const siteURL = `${protocol}//${hostname}${portSuffix}`; + + 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 { 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([]); + const validation = formSchema.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: "Automatically enabled token authentication for this identity.", + type: "info" + }); + } + + const token = await createToken({ + identityId: validatedIdentity.id, + name: "relay token (autogenerated)" + }); + setIdentityToken(token.accessToken); + createNotification({ + text: "Automatically generated a token for this identity.", + type: "info" + }); + setStep("command"); + } catch (err) { + console.error(err); + createNotification({ + text: "Failed to generate token for identity", + type: "error" + }); + setIdentityToken(""); + } + }; + + 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}

+ )} + + + + 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}

} + +
+ + + + +
+ + ); +}; 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..cb21499e4 --- /dev/null +++ b/frontend/src/pages/organization/NetworkingPage/components/RelayTab/components/RelayDeploymentMethodSelect.tsx @@ -0,0 +1,56 @@ +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 } = option; + + return ( + + ); + })} +
+ ); +}; From e6a76187a28216825d535f6eaf74845c84e5fc80 Mon Sep 17 00:00:00 2001 From: x032205 Date: Wed, 22 Oct 2025 03:35:33 -0400 Subject: [PATCH 16/60] improve UI & greptile review fixes --- .../components/GatewayCliDeploymentMethod.tsx | 10 +++++----- .../components/GatewayDeploymentMethodSelect.tsx | 10 ++++------ .../NetworkingPage/components/RelayTab/RelayTab.tsx | 2 +- .../RelayTab/components/RelayCliDeploymentMethod.tsx | 10 +++++----- .../components/RelayDeploymentMethodSelect.tsx | 10 ++++------ 5 files changed, 19 insertions(+), 23 deletions(-) diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx index 279dc61c9..a98853bbb 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx @@ -125,25 +125,25 @@ export const GatewayCliDeploymentMethod = () => { accessTokenTrustedIps: [{ ipAddress: "0.0.0.0/0" }, { ipAddress: "::/0" }] }); createNotification({ - text: "Automatically enabled token authentication for this identity.", - type: "info" + 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 (autogenerated)" + name: `gateway token for ${name} (autogenerated)` }); setIdentityToken(token.accessToken); createNotification({ - text: "Automatically generated a token for this identity.", + 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 identity", + text: "Failed to generate token for the selected identity", type: "error" }); setIdentityToken(""); diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeploymentMethodSelect.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeploymentMethodSelect.tsx index 0a1fd5e12..099c7d791 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeploymentMethodSelect.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayDeploymentMethodSelect.tsx @@ -27,21 +27,19 @@ export const GatewayDeploymentMethodSelect = ({ onSelect }: Props) => { return (
{deploymentOptions.map((option) => { - const { image, name } = option; + const { image, name, method } = option; return (
-
-

Price

-

- {subscription.status === "trialing" - ? "$0.00 / month" - : `${formatAmount(data.amount)} / ${data.interval}`} -

-
+ {subscription.slug !== "enterprise" ? ( +
+

Price

+

+ {subscription.status === "trialing" ? ( + "$0.00 / month" + ) : ( + <> + {formatAmount(totalAmount)} / {data.interval} + {(subscription.slug === "pro" || subscription.slug === "pro-annual") && ( + 1 ? "users" : "user"} and ${data.identities} ${data.identities > 1 ? "machine identities" : "machine identity"}.`} + className="max-w-lg" + > + + + )} + + )} +

+
+ ) : null}

Subscription renews on

From 4a7f4e101b46116c42f72c922bf4c3a2d5f449ec Mon Sep 17 00:00:00 2001 From: x032205 Date: Wed, 22 Oct 2025 19:22:49 -0400 Subject: [PATCH 19/60] some review changes --- .../components/GatewayCliDeploymentMethod.tsx | 10 +- .../GatewayDeploymentMethodSelect.tsx | 2 +- .../components/RelayCliDeploymentMethod.tsx | 202 ++++++++++++------ .../RelayDeploymentMethodSelect.tsx | 2 +- 4 files changed, 141 insertions(+), 75 deletions(-) diff --git a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx index a98853bbb..87ca2deee 100644 --- a/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx +++ b/frontend/src/pages/organization/NetworkingPage/components/GatewayTab/components/GatewayCliDeploymentMethod.tsx @@ -208,7 +208,7 @@ export const GatewayCliDeploymentMethod = () => { placeholder="Enter gateway name..." isError={Boolean(errors.name)} /> - {errors.name &&

{errors.name}

} + {errors.name &&

{errors.name}

} { getOptionValue={(option) => option.id} components={{ Option: RelayOption }} /> - {errors.relay &&

{errors.relay}

} + {errors.relay &&

{errors.relay}

} { placeholder="https://app.infisical.com" isError={Boolean(errors.instanceDomain)} /> - {errors.instanceDomain && ( -

{errors.instanceDomain}

- )} + {errors.instanceDomain &&

{errors.instanceDomain}

} { getOptionValue={(option) => option.id} getOptionLabel={(option) => option.name} /> - {errors.identity &&

{errors.identity}

} + {errors.identity &&

{errors.identity}

}
+

+ {errors.identity &&

{errors.identity}

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

{errors.identityToken}

} + + )}
-

{errors.identity &&

{errors.identity}

} ) : ( @@ -303,18 +295,42 @@ export const RelayCliDeploymentMethod = () => { placeholder="Enter identity token..." isError={Boolean(errors.identityToken)} /> - {canCreateToken && ( - - )} {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. + + } + > + +
+
+
+
+ )} +
+ ) + }, + parameters: { + docs: { + description: { + story: + "Use the `asChild` prop with a `button` tag to use a badge as a button. Do not use a styled `Button` component." + } + } + } +}; + +export const IsTruncatable: Story = { + name: "Example: isTruncatable", + args: { + isTruncatable: true, + children: ( + <> + + Infisical Infrastructure + + ) + }, + parameters: { + docs: { + description: { + story: + "Use the `isTruncatable` prop with a `span` tag wrapping the text content to support truncation." + } + } + }, + decorators: (Story) => ( +
+ +
+ ) +}; diff --git a/frontend/src/components/v3/generic/Badge/Badge.tsx b/frontend/src/components/v3/generic/Badge/Badge.tsx new file mode 100644 index 000000000..ada354e7d --- /dev/null +++ b/frontend/src/components/v3/generic/Badge/Badge.tsx @@ -0,0 +1,82 @@ +import { forwardRef } from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "cva"; + +import { cn } from "@app/components/v3/utils"; + +const badgeVariants = cva( + [ + "select-none items-center rounded-sm px-1.5 py-0.5 text-xs", + "gap-x-1 [a&,button&]:cursor-pointer inline-flex", + "[&>svg]:pointer-events-none [&>svg]:shrink-0 [&>svg]:stroke-[2.25] [&>svg]:size-3", + "transition duration-200 ease-in-out" + ], + { + variants: { + isTruncatable: { + true: "[&>span,&>p]:truncate min-w-0", + false: "w-fit shrink-0 whitespace-nowrap overflow-hidden" + }, + variant: { + neutral: [ + "border-neutral/75 bg-neutral/30 text-neutral", + "[a&,button&]:hover:bg-neutral/40 [a&,button&]:hover:border-neutral" + ], + success: [ + "border-success/75 bg-success/30 text-success", + "[a&,button&]:hover:bg-success/40 [a&,button&]:hover:border-success" + ], + info: [ + "border-info/75 bg-info/30 text-info", + "[a&,button&]:hover:bg-info/40 [a&,button&]:hover:border-info" + ], + warning: [ + "border-warning/75 bg-warning/30 text-warning", + "[a&,button&]:hover:bg-warning/40 [a&,button&]:hover:border-warning" + ], + danger: [ + "border-danger/75 bg-danger/30 text-danger", + "[a&,button&]:hover:bg-danger/40 [a&,button&]:hover:border-danger" + ], + project: [ + "border-project/75 bg-project/30 text-project", + "[a&,button&]:hover:bg-project/40 [a&,button&]:hover:border-project" + ], + org: [ + "border-org/75 bg-org/30 text-org", + "[a&,button&]:hover:bg-org/40 [a&,button&]:hover:border-org" + ], + "sub-org": [ + "border-sub-org/75 bg-sub-org/30 text-sub-org", + "[a&,button&]:hover:bg-sub-org/40 [a&,button&]:hover:border-sub-org" + ] + } + }, + defaultVariants: { + variant: "success" + } + } +); + +type TBadgeProps = VariantProps & + React.ComponentProps<"span"> & { + asChild?: boolean; + }; + +const Badge = forwardRef( + ({ className, variant, asChild = false, isTruncatable = false, ...props }, ref): JSX.Element => { + const Comp = asChild ? Slot : "span"; + return ( + + ); + } +); + +Badge.displayName = "Badge"; + +export { Badge, badgeVariants, type TBadgeProps }; diff --git a/frontend/src/components/v3/generic/Badge/index.ts b/frontend/src/components/v3/generic/Badge/index.ts new file mode 100644 index 000000000..ae21190ba --- /dev/null +++ b/frontend/src/components/v3/generic/Badge/index.ts @@ -0,0 +1 @@ +export * from "./Badge"; diff --git a/frontend/src/components/v3/generic/index.ts b/frontend/src/components/v3/generic/index.ts new file mode 100644 index 000000000..ae21190ba --- /dev/null +++ b/frontend/src/components/v3/generic/index.ts @@ -0,0 +1 @@ +export * from "./Badge"; diff --git a/frontend/src/components/v3/index.ts b/frontend/src/components/v3/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/frontend/src/components/v3/utils/index.ts b/frontend/src/components/v3/utils/index.ts new file mode 100644 index 000000000..365058ceb --- /dev/null +++ b/frontend/src/components/v3/utils/index.ts @@ -0,0 +1,6 @@ +import { type ClassValue, clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/frontend/src/index.css b/frontend/src/index.css index 7c9b7db9b..2a1da3fa2 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -37,12 +37,26 @@ } @theme { - /*legacy color schema */ /* Fonts */ --font-inter: "Inter", sans-serif; + --max-width-8xl: 88rem; /* 1408px */ + + /* Colors v2 */ + --color-background: #19191c; + --color-foreground: white; + --color-success: #2ecc71; + --color-info: #34c2db; + --color-warning: #f1c40f; + --color-danger: #e74c3c; + --color-org: #30B3FF; + --color-sub-org: #96ff59; + --color-project: #e0ed34; + --color-neutral: #adaeb0; + + /*legacy color schema */ --color-org-v1: #30B3FF; --color-namespace-v1: #96ff59; - --max-width-8xl: 88rem; /* 1408px */ + /* Primary */ --color-primary-50: #fffff5; --color-primary-100: #fcfce8; diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json index 49ac1742b..690a1155d 100644 --- a/frontend/tsconfig.app.json +++ b/frontend/tsconfig.app.json @@ -31,5 +31,7 @@ "include": [ "./src/**/*.ts", "./src/**/*.tsx", + "./.storybook/**/*.ts", + "./.storybook/**/*.tsx", ], } From 7034cac3677e34bf6fd7dbd6e11710a8efe4a734 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Thu, 23 Oct 2025 09:35:43 -0700 Subject: [PATCH 25/60] chore: remove unused border styling --- .../src/components/v3/generic/Badge/Badge.tsx | 40 ++++--------------- 1 file changed, 8 insertions(+), 32 deletions(-) diff --git a/frontend/src/components/v3/generic/Badge/Badge.tsx b/frontend/src/components/v3/generic/Badge/Badge.tsx index ada354e7d..a38cc856a 100644 --- a/frontend/src/components/v3/generic/Badge/Badge.tsx +++ b/frontend/src/components/v3/generic/Badge/Badge.tsx @@ -18,38 +18,14 @@ const badgeVariants = cva( false: "w-fit shrink-0 whitespace-nowrap overflow-hidden" }, variant: { - neutral: [ - "border-neutral/75 bg-neutral/30 text-neutral", - "[a&,button&]:hover:bg-neutral/40 [a&,button&]:hover:border-neutral" - ], - success: [ - "border-success/75 bg-success/30 text-success", - "[a&,button&]:hover:bg-success/40 [a&,button&]:hover:border-success" - ], - info: [ - "border-info/75 bg-info/30 text-info", - "[a&,button&]:hover:bg-info/40 [a&,button&]:hover:border-info" - ], - warning: [ - "border-warning/75 bg-warning/30 text-warning", - "[a&,button&]:hover:bg-warning/40 [a&,button&]:hover:border-warning" - ], - danger: [ - "border-danger/75 bg-danger/30 text-danger", - "[a&,button&]:hover:bg-danger/40 [a&,button&]:hover:border-danger" - ], - project: [ - "border-project/75 bg-project/30 text-project", - "[a&,button&]:hover:bg-project/40 [a&,button&]:hover:border-project" - ], - org: [ - "border-org/75 bg-org/30 text-org", - "[a&,button&]:hover:bg-org/40 [a&,button&]:hover:border-org" - ], - "sub-org": [ - "border-sub-org/75 bg-sub-org/30 text-sub-org", - "[a&,button&]:hover:bg-sub-org/40 [a&,button&]:hover:border-sub-org" - ] + neutral: "bg-neutral/30 text-neutral [a&,button&]:hover:bg-neutral/40", + success: "bg-success/30 text-success [a&,button&]:hover:bg-success/40", + info: "bg-info/30 text-info [a&,button&]:hover:bg-info/40", + warning: "bg-warning/30 text-warning [a&,button&]:hover:bg-warning/40", + danger: "bg-danger/30 text-danger [a&,button&]:hover:bg-danger/40", + project: "bg-project/30 text-project [a&,button&]:hover:bg-project/40", + org: "bg-org/30 text-org [a&,button&]:hover:bg-org/40", + "sub-org": "bg-sub-org/30 text-sub-org [a&,button&]:hover:bg-sub-org/40" } }, defaultVariants: { From e9c97c9ed36804a893cc7f95e3a5beec3931743e Mon Sep 17 00:00:00 2001 From: Daniel Hougaard Date: Thu, 23 Oct 2025 20:36:53 +0400 Subject: [PATCH 26/60] fix: backwards compatibility for assumed roles as role formatting --- .../identity-aws-auth/identity-aws-auth-fns.ts | 10 +++++----- .../identity-aws-auth/identity-aws-auth-service.ts | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/src/services/identity-aws-auth/identity-aws-auth-fns.ts b/backend/src/services/identity-aws-auth/identity-aws-auth-fns.ts index c8b494e7b..38944917e 100644 --- a/backend/src/services/identity-aws-auth/identity-aws-auth-fns.ts +++ b/backend/src/services/identity-aws-auth/identity-aws-auth-fns.ts @@ -8,7 +8,7 @@ interface PrincipalArnEntity { SessionInfo: string; // Only populated for assumed-role } -export const extractPrincipalArnEntity = (arn: string): PrincipalArnEntity => { +export const extractPrincipalArnEntity = (arn: string, formatAsIamRole: boolean = false): PrincipalArnEntity => { // split the ARN into parts using ":" as the delimiter const fullParts = arn.split(":"); if (fullParts.length !== 6) { @@ -49,7 +49,7 @@ export const extractPrincipalArnEntity = (arn: string): PrincipalArnEntity => { } // assumed roles use a special format where the friendly name is the role name const [roleName, sessionId] = rest; - finalType = "assumed-role"; + finalType = formatAsIamRole ? "role" : "assumed-role"; friendlyName = roleName; sessionInfo = sessionId; break; @@ -84,8 +84,8 @@ export const extractPrincipalArnEntity = (arn: string): PrincipalArnEntity => { * - arn:aws:iam::123456789012:user/MyUserName * - arn:aws:iam::123456789012:role/MyRoleName */ -export const extractPrincipalArn = (arn: string) => { - const entity = extractPrincipalArnEntity(arn); +export const extractPrincipalArn = (arn: string, formatAsIamRole: boolean = false) => { + const entity = extractPrincipalArnEntity(arn, formatAsIamRole); - return `arn:aws:${entity.Service}::${entity.AccountNumber}:${entity.Type}/${entity.FriendlyName}`; + return `arn:aws:${formatAsIamRole ? "iam" : entity.Service}::${entity.AccountNumber}:${entity.Type}/${entity.FriendlyName}`; }; diff --git a/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts b/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts index 3bbe88824..e61e8296b 100644 --- a/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts +++ b/backend/src/services/identity-aws-auth/identity-aws-auth-service.ts @@ -158,7 +158,7 @@ export const identityAwsAuthServiceFactory = ({ // considers exact matches + wildcard matches // heavily validated in router const regex = new RE2(`^${principalArn.replaceAll("*", ".*")}$`); - return regex.test(formattedArn); + return regex.test(formattedArn) || regex.test(extractPrincipalArn(Arn, true)); }); if (!isArnAllowed) { From cee47af0f8ded48f0b1688457e93c1e8127f64ae Mon Sep 17 00:00:00 2001 From: Piyush Gupta Date: Thu, 23 Oct 2025 22:11:55 +0530 Subject: [PATCH 27/60] fix: update paywall to show correct plan: team -> pro/enterprise --- .../UpgradePlanModal/UpgradePlanModal.tsx | 43 ++++++++++++++++--- .../OverviewPage/OverviewPage.tsx | 7 ++- .../components/ActionBar/ActionBar.tsx | 9 +++- .../EnvironmentTabs/EnvironmentTabs.tsx | 2 +- .../SecretRotationPage/SecretRotationPage.tsx | 2 +- .../EnvironmentSection/EnvironmentSection.tsx | 2 +- 6 files changed, 51 insertions(+), 14 deletions(-) diff --git a/frontend/src/components/license/UpgradePlanModal/UpgradePlanModal.tsx b/frontend/src/components/license/UpgradePlanModal/UpgradePlanModal.tsx index 17c0777f1..2279d6f8d 100644 --- a/frontend/src/components/license/UpgradePlanModal/UpgradePlanModal.tsx +++ b/frontend/src/components/license/UpgradePlanModal/UpgradePlanModal.tsx @@ -8,22 +8,40 @@ type Props = { isOpen?: boolean; onOpenChange?: (isOpen: boolean) => void; text: string; + isEnterpriseFeature?: boolean; }; -export const UpgradePlanModal = ({ text, isOpen, onOpenChange }: Props): JSX.Element => { +export const UpgradePlanModal = ({ + text, + isOpen, + onOpenChange, + isEnterpriseFeature = false +}: Props): JSX.Element => { const { subscription } = useSubscription(); const { currentOrg } = useOrganization(); const { mutateAsync, isPending } = useGetOrgTrialUrl(); - const link = - subscription && subscription.slug !== null - ? ("/organization/billing" as const) - : "https://infisical.com/scheduledemo"; + + const getLink = () => { + // self-hosting + if (!subscription || subscription.slug === null) { + return "https://infisical.com/scheduledemo"; + } + + // Infisical cloud + if (isEnterpriseFeature) { + return "https://infisical.com/talk-to-us"; + } + + return "/organization/billing" as const; + }; + + const link = getLink(); const handleUpgradeBtnClick = async () => { try { if (!subscription || !currentOrg) return; - if (!subscription.has_used_trial) { + if (!subscription.has_used_trial && !isEnterpriseFeature) { // direct user to start pro trial const url = await mutateAsync({ @@ -40,6 +58,17 @@ export const UpgradePlanModal = ({ text, isOpen, onOpenChange }: Props): JSX.Ele console.error(err); } }; + const getUpgradePlanLabel = () => { + if (subscription) { + if (isEnterpriseFeature) { + return "Talk to Us"; + } + if (!subscription.has_used_trial) { + return "Start Pro Free Trial"; + } + } + return "Upgrade Plan"; + }; return ( @@ -55,7 +84,7 @@ export const UpgradePlanModal = ({ text, isOpen, onOpenChange }: Props): JSX.Ele onClick={handleUpgradeBtnClick} className="mr-4" > - {subscription && !subscription.has_used_trial ? "Start Pro Free Trial" : "Upgrade Plan"} + {getUpgradePlanLabel()}
); From 5e316d9d6ec19c8265fc59ea70f7cb23df994a1b Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Thu, 23 Oct 2025 15:38:37 -0300 Subject: [PATCH 28/60] Add missing membership role to SAML mapped group --- ...023121055_fix-missing-group-memberships.ts | 19 +++++++++++++------ .../saml-config/saml-config-service.ts | 10 +++++++++- 2 files changed, 22 insertions(+), 7 deletions(-) diff --git a/backend/src/db/migrations/20251023121055_fix-missing-group-memberships.ts b/backend/src/db/migrations/20251023121055_fix-missing-group-memberships.ts index e4af23c46..117ac12e9 100644 --- a/backend/src/db/migrations/20251023121055_fix-missing-group-memberships.ts +++ b/backend/src/db/migrations/20251023121055_fix-missing-group-memberships.ts @@ -1,21 +1,20 @@ import { Knex } from "knex"; -import { AccessScope, TableName } from "../schemas"; +import { AccessScope, OrgMembershipRole, TableName } from "../schemas"; export async function up(knex: Knex): Promise { const hasGroupsTable = await knex.schema.hasTable(TableName.Groups); const hasMembershipTable = await knex.schema.hasTable(TableName.Membership); + const hasMembershipRoleTable = await knex.schema.hasTable(TableName.MembershipRole); - if (!hasGroupsTable || !hasMembershipTable) { + if (!hasGroupsTable || !hasMembershipTable || !hasMembershipRoleTable) { return; } const groupsWithoutMembership = await knex .select(`${TableName.Groups}.id`, `${TableName.Groups}.orgId`) .from(TableName.Groups) - .leftJoin(TableName.Membership, function joinGroupMembership() { - this.on(`${TableName.Groups}.id`, "=", `${TableName.Membership}.actorGroupId`); - }) + .leftJoin(TableName.Membership, `${TableName.Groups}.id`, `${TableName.Membership}.actorGroupId`) .whereNull(`${TableName.Membership}.actorGroupId`); if (groupsWithoutMembership.length > 0) { @@ -26,7 +25,15 @@ export async function up(knex: Knex): Promise { isActive: true })); - await knex(TableName.Membership).insert(membershipInserts); + const insertedMemberships = await knex(TableName.Membership).insert(membershipInserts).returning("*"); + + const membershipRoleInserts = insertedMemberships.map((membership) => ({ + membershipId: membership.id, + role: OrgMembershipRole.NoAccess, + customRoleId: null + })); + + await knex(TableName.MembershipRole).insert(membershipRoleInserts); } await knex.schema.alterTable(TableName.Membership, (t) => { diff --git a/backend/src/ee/services/saml-config/saml-config-service.ts b/backend/src/ee/services/saml-config/saml-config-service.ts index f9c97fc6f..c99ae8b28 100644 --- a/backend/src/ee/services/saml-config/saml-config-service.ts +++ b/backend/src/ee/services/saml-config/saml-config-service.ts @@ -183,7 +183,7 @@ export const samlConfigServiceFactory = ({ transaction ); orgGroupsMap.set(groupName, newGroup); - await membershipGroupDAL.create( + const orgMembership = await membershipGroupDAL.create( { actorGroupId: newGroup.id, scope: AccessScope.Organization, @@ -191,6 +191,14 @@ export const samlConfigServiceFactory = ({ }, transaction ); + await membershipRoleDAL.create( + { + membershipId: orgMembership.id, + role: OrgMembershipRole.NoAccess, + customRoleId: null + }, + transaction + ); } } From 3196e24c275ae0e347ef86ba5fd6e622b844315e Mon Sep 17 00:00:00 2001 From: Carlos Monastyrski Date: Thu, 23 Oct 2025 15:55:15 -0300 Subject: [PATCH 29/60] Small improvement on custom roles for groups memerships migration --- ...023121055_fix-missing-group-memberships.ts | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/backend/src/db/migrations/20251023121055_fix-missing-group-memberships.ts b/backend/src/db/migrations/20251023121055_fix-missing-group-memberships.ts index 117ac12e9..56fe82b2c 100644 --- a/backend/src/db/migrations/20251023121055_fix-missing-group-memberships.ts +++ b/backend/src/db/migrations/20251023121055_fix-missing-group-memberships.ts @@ -1,6 +1,6 @@ import { Knex } from "knex"; -import { AccessScope, OrgMembershipRole, TableName } from "../schemas"; +import { AccessScope, TableName } from "../schemas"; export async function up(knex: Knex): Promise { const hasGroupsTable = await knex.schema.hasTable(TableName.Groups); @@ -12,7 +12,12 @@ export async function up(knex: Knex): Promise { } const groupsWithoutMembership = await knex - .select(`${TableName.Groups}.id`, `${TableName.Groups}.orgId`) + .select( + `${TableName.Groups}.id`, + `${TableName.Groups}.orgId`, + `${TableName.Groups}.role`, + `${TableName.Groups}.roleId` + ) .from(TableName.Groups) .leftJoin(TableName.Membership, `${TableName.Groups}.id`, `${TableName.Membership}.actorGroupId`) .whereNull(`${TableName.Membership}.actorGroupId`); @@ -27,11 +32,14 @@ export async function up(knex: Knex): Promise { const insertedMemberships = await knex(TableName.Membership).insert(membershipInserts).returning("*"); - const membershipRoleInserts = insertedMemberships.map((membership) => ({ - membershipId: membership.id, - role: OrgMembershipRole.NoAccess, - customRoleId: null - })); + const membershipRoleInserts = insertedMemberships.map((membership, index) => { + const group = groupsWithoutMembership[index]; + return { + membershipId: membership.id, + role: group.role, + customRoleId: group.roleId + }; + }); await knex(TableName.MembershipRole).insert(membershipRoleInserts); } From 761a8984846eb0104727cd4bc17b320ce45114a4 Mon Sep 17 00:00:00 2001 From: Victor Santos Date: Thu, 23 Oct 2025 16:40:54 -0300 Subject: [PATCH 30/60] feat(secret-sync): validate secret names using SecretNameSchema before processing --- backend/src/services/secret-sync/secret-sync-queue.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/backend/src/services/secret-sync/secret-sync-queue.ts b/backend/src/services/secret-sync/secret-sync-queue.ts index 63faf3b03..4b28564c0 100644 --- a/backend/src/services/secret-sync/secret-sync-queue.ts +++ b/backend/src/services/secret-sync/secret-sync-queue.ts @@ -63,6 +63,7 @@ import { TAppConnectionDALFactory } from "../app-connection/app-connection-dal"; import { TFolderCommitServiceFactory } from "../folder-commit/folder-commit-service"; import { TNotificationServiceFactory } from "../notification/notification-service"; import { NotificationType } from "../notification/notification-types"; +import { SecretNameSchema } from "@app/server/lib/schemas"; export type TSecretSyncQueueFactory = ReturnType; @@ -408,6 +409,14 @@ export const secretSyncQueueFactory = ({ if (!Object.keys(importedSecrets).length) return {}; + for (const [key] of Object.entries(importedSecrets)) { + const result = SecretNameSchema.safeParse(key); + if (!result.success) { + const errorMessage = result.error.issues[0]?.message || "Invalid secret name"; + throw new Error(`Invalid secret name "${key}": ${errorMessage}`); + } + } + const importedSecretMap: TSecretMap = {}; const secretMap = await $getInfisicalSecrets(secretSync, false); From 2081cd958553404a73533d9357e2dac6c410c4a5 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Thu, 23 Oct 2025 13:02:26 -0700 Subject: [PATCH 31/60] improvements: address feedback --- .../decorators/DocumentDecorator.tsx | 12 +++++++++++ .../.storybook/decorators/RouterDecorator.tsx | 20 +++++++++---------- frontend/.storybook/decorators/index.ts | 1 + frontend/.storybook/preview.tsx | 13 ++---------- .../v3/generic/Badge/Badge.stories.tsx | 4 ++-- .../src/components/v3/generic/Badge/Badge.tsx | 2 +- frontend/tsconfig.app.json | 2 +- 7 files changed, 29 insertions(+), 25 deletions(-) create mode 100644 frontend/.storybook/decorators/DocumentDecorator.tsx diff --git a/frontend/.storybook/decorators/DocumentDecorator.tsx b/frontend/.storybook/decorators/DocumentDecorator.tsx new file mode 100644 index 000000000..13f4dc8c7 --- /dev/null +++ b/frontend/.storybook/decorators/DocumentDecorator.tsx @@ -0,0 +1,12 @@ +import { useEffect } from "react"; +import type { Decorator } from "@storybook/react-vite"; + +export const DocumentDecorator: Decorator = (Story) => { + useEffect(() => { + const root = document.getElementsByTagName("html")[0]; + + root.setAttribute("class", "overflow-visible"); + }, []); + + return ; +}; diff --git a/frontend/.storybook/decorators/RouterDecorator.tsx b/frontend/.storybook/decorators/RouterDecorator.tsx index 06f8d7575..a559c5cd1 100644 --- a/frontend/.storybook/decorators/RouterDecorator.tsx +++ b/frontend/.storybook/decorators/RouterDecorator.tsx @@ -1,17 +1,17 @@ +import { useMemo } from "react"; import type { Decorator } from "@storybook/react-vite"; import { createRootRoute, createRouter, RouterProvider } from "@tanstack/react-router"; export const RouterDecorator: Decorator = (Story) => { - const rootRoute = createRootRoute({ - component: Story - }); + const router = useMemo(() => { + const routeTree = createRootRoute({ + component: Story + }); - const routeTree = rootRoute; + return createRouter({ + routeTree + }); + }, [Story]); - const router = createRouter({ - routeTree - }); - - // @ts-expect-error just to make Links happy :) - return ; + return ; }; diff --git a/frontend/.storybook/decorators/index.ts b/frontend/.storybook/decorators/index.ts index b613f5c6a..7bbb10f15 100644 --- a/frontend/.storybook/decorators/index.ts +++ b/frontend/.storybook/decorators/index.ts @@ -1 +1,2 @@ +export * from "./DocumentDecorator"; export * from "./RouterDecorator"; diff --git a/frontend/.storybook/preview.tsx b/frontend/.storybook/preview.tsx index 67157339d..d2aaca582 100644 --- a/frontend/.storybook/preview.tsx +++ b/frontend/.storybook/preview.tsx @@ -1,20 +1,11 @@ import type { Preview } from "@storybook/react-vite"; -import { RouterDecorator } from "./decorators"; +import { DocumentDecorator, RouterDecorator } from "./decorators"; import "../src/index.css"; const preview: Preview = { - decorators: [ - (Story) => { - const root = document.getElementsByTagName("html")[0]; - - root.setAttribute("class", "overflow-visible"); - - return ; - }, - RouterDecorator - ], + decorators: [DocumentDecorator, RouterDecorator], parameters: { controls: { matchers: { diff --git a/frontend/src/components/v3/generic/Badge/Badge.stories.tsx b/frontend/src/components/v3/generic/Badge/Badge.stories.tsx index 86905b1ea..da0743a66 100644 --- a/frontend/src/components/v3/generic/Badge/Badge.stories.tsx +++ b/frontend/src/components/v3/generic/Badge/Badge.stories.tsx @@ -222,7 +222,7 @@ export const AsExternalLink: Story = { variant: "info", asChild: true, children: ( - + Link ) @@ -263,7 +263,7 @@ export const AsButton: Story = { variant: "org", asChild: true, children: ( - + + + +
+ + + ); +}; diff --git a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx index 8b553e656..7a1c1658e 100644 --- a/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx +++ b/frontend/src/pages/pam/PamAccountsPage/components/PamAccountForm/PamAccountForm.tsx @@ -8,6 +8,7 @@ import { import { DiscriminativePick } from "@app/types"; import { PamAccountHeader } from "../PamAccountHeader"; +import { MySQLAccountForm } from "./MySQLAccountForm"; import { PostgresAccountForm } from "./PostgresAccountForm"; type FormProps = { @@ -72,6 +73,13 @@ const CreateForm = ({ resourceType={resourceType} /> ); + case PamResourceType.MySQL: + return ( + ); default: throw new Error(`Unhandled resource: ${resourceType}`); } @@ -110,6 +118,8 @@ const UpdateForm = ({ account, onComplete }: UpdateFormProps) => { switch (account.resource.resourceType) { case PamResourceType.Postgres: return ; + case PamResourceType.MySQL: + return ; default: throw new Error(`Unhandled resource: ${account.resource.resourceType}`); } diff --git a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/MySQLResourceForm.tsx b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/MySQLResourceForm.tsx new file mode 100644 index 000000000..b7c2996d3 --- /dev/null +++ b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/MySQLResourceForm.tsx @@ -0,0 +1,84 @@ +import { useState } from "react"; +import { FormProvider, useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; + +import { Button, ModalClose } from "@app/components/v2"; +import { PamResourceType, TMySQLResource } from "@app/hooks/api/pam"; + +import { BaseSqlResourceSchema } from "./shared/sql-resource-schemas"; +import { SqlResourceFields } from "./shared/SqlResourceFields"; +import { GenericResourceFields, genericResourceFieldsSchema } from "./GenericResourceFields"; + +type Props = { + resource?: TMySQLResource; + onSubmit: (formData: FormData) => Promise; +}; + +const formSchema = genericResourceFieldsSchema.extend({ + resourceType: z.literal(PamResourceType.MySQL), + connectionDetails: BaseSqlResourceSchema.extend({ + database: z.string().trim().optional().default("") + }) +}); + +type FormData = z.infer; + +export const MySQLResourceForm = ({ resource, onSubmit }: Props) => { + const isUpdate = Boolean(resource); + const [selectedTabIndex, setSelectedTabIndex] = useState(0); + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: resource ?? { + resourceType: PamResourceType.MySQL, + connectionDetails: { + host: "", + port: 3306, + database: "", + sslEnabled: true, + sslRejectUnauthorized: true, + sslCertificate: undefined + } + } + }); + + const { + handleSubmit, + formState: { isSubmitting, isDirty } + } = form; + + return ( + +
{ + setSelectedTabIndex(0); + handleSubmit(onSubmit)(e); + }} + > + + +
+ + + + +
+ +
+ ); +}; diff --git a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PamResourceForm.tsx b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PamResourceForm.tsx index 8cfdc8582..2bc54e7cd 100644 --- a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PamResourceForm.tsx +++ b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/PamResourceForm.tsx @@ -9,6 +9,7 @@ import { import { DiscriminativePick } from "@app/types"; import { PamResourceHeader } from "../PamResourceHeader"; +import { MySQLResourceForm } from "./MySQLResourceForm"; import { PostgresResourceForm } from "./PostgresResourceForm"; type FormProps = { @@ -57,6 +58,8 @@ const CreateForm = ({ resourceType, onComplete, projectId }: CreateFormProps) => switch (resourceType) { case PamResourceType.Postgres: return ; + case PamResourceType.MySQL: + return ; default: throw new Error(`Unhandled resource: ${resourceType}`); } @@ -92,8 +95,10 @@ const UpdateForm = ({ resource, onComplete }: UpdateFormProps) => { switch (resource.resourceType) { case PamResourceType.Postgres: return ; + case PamResourceType.MySQL: + return ; default: - throw new Error(`Unhandled resource: ${resource.resourceType}`); + throw new Error(`Unhandled resource: ${(resource as any).resourceType}`); } }; diff --git a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/shared/SqlResourceFields.tsx b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/shared/SqlResourceFields.tsx index 888b62984..c3f3982ec 100644 --- a/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/shared/SqlResourceFields.tsx +++ b/frontend/src/pages/pam/PamResourcesPage/components/PamResourceForm/shared/SqlResourceFields.tsx @@ -110,7 +110,7 @@ export const SqlResourceFields = ({ setSelectedTabIndex, selectedTabIndex }: Pro errorText={error?.message} isError={Boolean(error?.message)} className={sslEnabled ? "" : "opacity-50"} - label="SSL Certificate" + label="Trusted CA SSL Certificate" isOptional >