diff --git a/backend/src/integrations/sync.ts b/backend/src/integrations/sync.ts index c82cc5b4a..fe5885c9e 100644 --- a/backend/src/integrations/sync.ts +++ b/backend/src/integrations/sync.ts @@ -87,13 +87,15 @@ const syncSecrets = async ({ integrationAuth, secrets, accessId, - accessToken + accessToken, + appendices }: { integration: IIntegration; integrationAuth: IIntegrationAuth; secrets: Record; accessId: string | null; accessToken: string; + appendices?: { prefix: string, suffix: string }; }) => { switch (integration.integration) { case INTEGRATION_GCP_SECRET_MANAGER: @@ -153,7 +155,8 @@ const syncSecrets = async ({ await syncSecretsGitHub({ integration, secrets, - accessToken + accessToken, + appendices }); break; case INTEGRATION_GITLAB: @@ -218,7 +221,8 @@ const syncSecrets = async ({ await syncSecretsCheckly({ integration, secrets, - accessToken + accessToken, + appendices }); break; case INTEGRATION_QOVERY: @@ -1342,11 +1346,13 @@ const syncSecretsNetlify = async ({ const syncSecretsGitHub = async ({ integration, secrets, - accessToken + accessToken, + appendices }: { integration: IIntegration; secrets: Record; accessToken: string; + appendices?: { prefix: string, suffix: string }; }) => { interface GitHubRepoKey { key_id: string; @@ -1376,7 +1382,7 @@ const syncSecretsGitHub = async ({ ).data; // Get local copy of decrypted secrets. We cannot decrypt them as we dont have access to GH private key - const encryptedSecrets: GitHubSecretRes = ( + let encryptedSecrets: GitHubSecretRes = ( await octokit.request("GET /repos/{owner}/{repo}/actions/secrets", { owner: integration.owner, repo: integration.app @@ -1389,6 +1395,15 @@ const syncSecretsGitHub = async ({ {} ); + encryptedSecrets = Object.keys(encryptedSecrets).reduce((result: { + [key: string]: GitHubSecret; + }, key) => { + if ((appendices?.prefix !== undefined ? key.startsWith(appendices?.prefix) : true) && (appendices?.suffix !== undefined ? key.endsWith(appendices?.suffix) : true)) { + result[key] = encryptedSecrets[key]; + } + return result; + }, {}); + Object.keys(encryptedSecrets).map(async (key) => { if (!(key in secrets)) { await octokit.request("DELETE /repos/{owner}/{repo}/actions/secrets/{secret_name}", { @@ -2074,13 +2089,15 @@ const syncSecretsSupabase = async ({ const syncSecretsCheckly = async ({ integration, secrets, - accessToken + accessToken, + appendices }: { integration: IIntegration; secrets: Record; accessToken: string; + appendices?: { prefix: string, suffix: string }; }) => { - const getSecretsRes = ( + let getSecretsRes = ( await standardRequest.get(`${INTEGRATION_CHECKLY_API_URL}/v1/variables`, { headers: { Authorization: `Bearer ${accessToken}`, @@ -2096,6 +2113,15 @@ const syncSecretsCheckly = async ({ {} ); + getSecretsRes = Object.keys(getSecretsRes).reduce((result: { + [key: string]: string; + }, key) => { + if ((appendices?.prefix !== undefined ? key.startsWith(appendices?.prefix) : true) && (appendices?.suffix !== undefined ? key.endsWith(appendices?.suffix) : true)) { + result[key] = getSecretsRes[key]; + } + return result; + }, {}); + // add secrets for await (const key of Object.keys(secrets)) { if (!(key in getSecretsRes)) { diff --git a/backend/src/queues/integrations/syncSecretsToThirdPartyServices.ts b/backend/src/queues/integrations/syncSecretsToThirdPartyServices.ts index 7b6819b8c..b18d7bccc 100644 --- a/backend/src/queues/integrations/syncSecretsToThirdPartyServices.ts +++ b/backend/src/queues/integrations/syncSecretsToThirdPartyServices.ts @@ -60,7 +60,8 @@ syncSecretsToThirdPartyServices.process(async (job: Job) => { integrationAuth, secrets: Object.keys(suffixedSecrets).length !== 0 ? suffixedSecrets : secrets, accessId: access.accessId === undefined ? null : access.accessId, - accessToken: access.accessToken + accessToken: access.accessToken, + appendices: { prefix: integration.metadata?.secretPrefix || "", suffix: integration.metadata?.secretSuffix || "" } }); } }) diff --git a/frontend/public/images/infisical-update-september-2023.png b/frontend/public/images/infisical-update-september-2023.png new file mode 100644 index 000000000..c11852762 Binary files /dev/null and b/frontend/public/images/infisical-update-september-2023.png differ diff --git a/frontend/src/components/v2/ContentLoader/ContentLoader.tsx b/frontend/src/components/v2/ContentLoader/ContentLoader.tsx index 343d8adeb..af83ac81c 100644 --- a/frontend/src/components/v2/ContentLoader/ContentLoader.tsx +++ b/frontend/src/components/v2/ContentLoader/ContentLoader.tsx @@ -23,10 +23,8 @@ export const ContentLoader = ({ text, frequency = 2000 }: Props) => { }, []); return ( -
-
- loading animation -
+
+ loading animation {text && isTextArray && ( { )} - {text && !isTextArray &&
{text}
} + {text && !isTextArray &&
{text}
}
); }; diff --git a/frontend/src/components/v2/SecretInput/SecretInput.tsx b/frontend/src/components/v2/SecretInput/SecretInput.tsx index c09061c4f..314dada0c 100644 --- a/frontend/src/components/v2/SecretInput/SecretInput.tsx +++ b/frontend/src/components/v2/SecretInput/SecretInput.tsx @@ -22,7 +22,7 @@ const sanitizeConf = { const syntaxHighlight = (content?: string | null, isVisible?: boolean) => { if (content === "") return "EMPTY"; - if (!content) return "missing"; + if (!content) return "EMPTY"; if (!isVisible) return replaceContentWithDot(content); const sanitizedContent = sanitizeHtml( diff --git a/frontend/src/layouts/AppLayout/AppLayout.tsx b/frontend/src/layouts/AppLayout/AppLayout.tsx index 22c4f1fad..4f0cba998 100644 --- a/frontend/src/layouts/AppLayout/AppLayout.tsx +++ b/frontend/src/layouts/AppLayout/AppLayout.tsx @@ -10,6 +10,7 @@ import crypto from "crypto"; import { useEffect } from "react"; import { Controller, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; +import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/router"; import { faGithub, faSlack } from "@fortawesome/free-brands-svg-icons"; @@ -67,9 +68,10 @@ import { useCreateWorkspace, useGetOrgTrialUrl, useGetSecretApprovalRequestCount, + useGetUserAction, useLogoutUser, - useUploadWsKey -} from "@app/hooks/api"; + useRegisterUserAction, + useUploadWsKey} from "@app/hooks/api"; interface LayoutProps { children: React.ReactNode; @@ -117,7 +119,8 @@ export const AppLayout = ({ children }: LayoutProps) => { const { user } = useUser(); const { subscription } = useSubscription(); const workspaceId = currentWorkspace?._id || ""; - // const [ isLearningNoteOpen, setIsLearningNoteOpen ] = useState(true); + const { data: updateClosed } = useGetUserAction("september_update_closed"); + const { data: secretApprovalReqCount } = useGetSecretApprovalRequestCount({ workspaceId }); const isAddingProjectsAllowed = subscription?.workspaceLimit @@ -144,6 +147,12 @@ export const AppLayout = ({ children }: LayoutProps) => { const { t } = useTranslation(); + const registerUserAction = useRegisterUserAction(); + + const closeUpdate = async () => { + await registerUserAction.mutateAsync("september_update_closed"); + } + const logout = useLogoutUser(); const logOutUser = async () => { try { @@ -479,9 +488,9 @@ export const AppLayout = ({ children }: LayoutProps) => { } icon="system-outline-189-domain-verification" > - Secret approval + Secret approvals {Boolean(secretApprovalReqCount?.open) && ( - + {secretApprovalReqCount?.open} )} @@ -537,19 +546,6 @@ export const AppLayout = ({ children }: LayoutProps) => { - {/* {workspaces.map(project => - - - {project.name} - - -
- {project.name} -
- )} */} { : "mb-4" } flex w-full cursor-default flex-col items-center px-3 text-sm text-mineshaft-400`} > - {/*
+ {/*
-
-
-
Kubernetes Operator
-
Integrate Infisical into your Kubernetes infrastructure
-
- kubernetes image + {router.asPath.includes("org") && (
null} diff --git a/frontend/src/pages/integrations/github/create.tsx b/frontend/src/pages/integrations/github/create.tsx index 5ca725a4b..ce0ec7858 100644 --- a/frontend/src/pages/integrations/github/create.tsx +++ b/frontend/src/pages/integrations/github/create.tsx @@ -3,8 +3,9 @@ import Head from "next/head"; import Image from "next/image"; import Link from "next/link"; import { useRouter } from "next/router"; -import { faArrowUpRightFromSquare, faBookOpen, faBugs, faCircleInfo } from "@fortawesome/free-solid-svg-icons"; +import { faAngleDown, faArrowUpRightFromSquare, faBookOpen, faBugs, faCheckCircle, faCircleInfo } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { motion } from "framer-motion"; import queryString from "query-string"; import { @@ -15,10 +16,18 @@ import { Button, Card, CardTitle, + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, FormControl, Input, Select, - SelectItem + SelectItem, + Tab, + TabList, + TabPanel, + Tabs } from "../../../components/v2"; import { useGetIntegrationAuthApps, @@ -26,6 +35,11 @@ import { } from "../../../hooks/api/integrationAuth"; import { useGetWorkspaceById } from "../../../hooks/api/workspace"; +enum TabSections { + Connection = "connection", + Options = "options" +} + export default function GitHubCreateIntegrationPage() { const router = useRouter(); const { mutateAsync } = useCreateIntegration(); @@ -40,7 +54,8 @@ export default function GitHubCreateIntegrationPage() { const [selectedSourceEnvironment, setSelectedSourceEnvironment] = useState(""); const [secretPath, setSecretPath] = useState("/"); - const [targetAppId, setTargetAppId] = useState(""); + const [targetAppIds, setTargetAppIds] = useState([]); + const [secretSuffix, setSecretSuffix] = useState(""); const [isLoading, setIsLoading] = useState(false); @@ -53,9 +68,9 @@ export default function GitHubCreateIntegrationPage() { useEffect(() => { if (integrationAuthApps) { if (integrationAuthApps.length > 0) { - setTargetAppId(integrationAuthApps[0].appId as string); + setTargetAppIds([String(integrationAuthApps[0].appId)]); } else { - setTargetAppId("none"); + setTargetAppIds(["none"]); } } }, [integrationAuthApps]); @@ -66,20 +81,27 @@ export default function GitHubCreateIntegrationPage() { if (!integrationAuth?._id) return; - const targetApp = integrationAuthApps?.find( - (integrationAuthApp) => integrationAuthApp.appId === targetAppId + const targetApps = integrationAuthApps?.filter( + (integrationAuthApp) => targetAppIds.includes(String(integrationAuthApp.appId)) ); - if (!targetApp || !targetApp.owner) return; + if (!targetApps) return; - await mutateAsync({ - integrationAuthId: integrationAuth?._id, - isActive: true, - app: targetApp.name, - sourceEnvironment: selectedSourceEnvironment, - owner: targetApp.owner, - secretPath - }); + await Promise.all( + targetApps.map(async (targetApp) => { + await mutateAsync({ + integrationAuthId: integrationAuth?._id, + isActive: true, + app: targetApp.name, + sourceEnvironment: selectedSourceEnvironment, + owner: targetApp.owner, + secretPath, + metadata: { + secretSuffix + } + }) + }) + ); setIsLoading(false); router.push(`/integrations/${localStorage.getItem("projectData.id")}`); @@ -92,7 +114,7 @@ export default function GitHubCreateIntegrationPage() { workspace && selectedSourceEnvironment && integrationAuthApps && - targetAppId ? ( + targetAppIds ? (
Set Up GitHub Integration @@ -124,59 +146,109 @@ export default function GitHubCreateIntegrationPage() {
- - - - - setSecretPath(evt.target.value)} - placeholder="Provide a path, default is /" - /> - - - setSelectedSourceEnvironment(val)} + className="w-full border border-mineshaft-500" > - {integrationAuthApp.name} - - )) - ) : ( - - No repositories found - - )} - - + {workspace?.environments.map((sourceEnvironment) => ( + + {sourceEnvironment.name} + + ))} + + + + setSecretPath(evt.target.value)} + placeholder="Provide a path, default is /" + /> + + + + + {(integrationAuthApps.length > 0) ?
+ {targetAppIds.length === 1 ? integrationAuthApps?.find( + (integrationAuthApp) => targetAppIds[0] === String(integrationAuthApp.appId) + )?.name : `${targetAppIds.length} repositories selected`} + +
:
+ No repositories found +
} +
+ + {(integrationAuthApps.length > 0) ? ( + integrationAuthApps.map((integrationAuthApp) => { + const isSelected = targetAppIds.includes(String(integrationAuthApp.appId)); + + return ( + { + if (targetAppIds.includes(String(integrationAuthApp.appId))) { + setTargetAppIds(targetAppIds.filter((appId) => appId !== String(integrationAuthApp.appId))); + } else { + setTargetAppIds([...targetAppIds, String(integrationAuthApp.appId)]); + } + }} + key={integrationAuthApp.appId} + icon={isSelected ? :
} + iconPos="left" + className="w-[28.4rem] text-sm" + > + {integrationAuthApp.name} + + )}) + ) :
} + + + + + + + + + setSecretSuffix(evt.target.value)} + placeholder="Provide a suffix for secret names, default is no suffix" + /> + + + + diff --git a/frontend/src/pages/login/index.tsx b/frontend/src/pages/login/index.tsx index 8fd27c429..fd4011feb 100644 --- a/frontend/src/pages/login/index.tsx +++ b/frontend/src/pages/login/index.tsx @@ -9,7 +9,7 @@ export default function LoginPage() { const { t } = useTranslation(); return ( -
+
{t("common.head-title", { title: t("login.title") })} @@ -23,6 +23,7 @@ export default function LoginPage() {
+
); } diff --git a/frontend/src/pages/signup/index.tsx b/frontend/src/pages/signup/index.tsx index b37c9208b..57621e5d0 100644 --- a/frontend/src/pages/signup/index.tsx +++ b/frontend/src/pages/signup/index.tsx @@ -145,7 +145,7 @@ export default function SignUp() { }; return ( -
+
{t("common.head-title", { title: t("signup.title") })} diff --git a/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx b/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx index 04f7e5bd9..b75952837 100644 --- a/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx +++ b/frontend/src/views/IntegrationsPage/components/IntegrationsSection/IntegrationsSection.tsx @@ -139,7 +139,7 @@ export const IntegrationsSection = ({
)} - {(integration.integration === "checkly") && ( + {((integration.integration === "checkly") || (integration.integration === "github")) && (
diff --git a/frontend/src/views/Login/components/InitialStep/InitialStep.tsx b/frontend/src/views/Login/components/InitialStep/InitialStep.tsx index c8400248b..843e3292b 100644 --- a/frontend/src/views/Login/components/InitialStep/InitialStep.tsx +++ b/frontend/src/views/Login/components/InitialStep/InitialStep.tsx @@ -113,7 +113,7 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }:

Login to Infisical

-
+
-
+
-
+
-
+
@@ -195,10 +195,10 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }: placeholder="Enter your email..." isRequired autoComplete="username" - className="h-11" + className="h-10" />
-
+
setPassword(e.target.value)} @@ -207,15 +207,15 @@ export const InitialStep = ({ setStep, email, setEmail, password, setPassword }: isRequired autoComplete="current-password" id="current-password" - className="select:-webkit-autofill:focus h-11" + className="select:-webkit-autofill:focus h-10" />
-
+
+ diff --git a/frontend/src/views/Settings/BillingSettingsPage/components/BillingCloudTab/PreviewSection.tsx b/frontend/src/views/Settings/BillingSettingsPage/components/BillingCloudTab/PreviewSection.tsx index ee1ffe989..04d878b67 100644 --- a/frontend/src/views/Settings/BillingSettingsPage/components/BillingCloudTab/PreviewSection.tsx +++ b/frontend/src/views/Settings/BillingSettingsPage/components/BillingCloudTab/PreviewSection.tsx @@ -1,3 +1,7 @@ +import Link from "next/link"; +import { faArrowUpRightFromSquare } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + import { OrgPermissionCan } from "@app/components/permissions"; import { Button } from "@app/components/v2"; import { @@ -74,24 +78,36 @@ export const PreviewSection = () => { subscription?.slug !== "enterprise" && subscription?.slug !== "pro" && subscription?.slug !== "pro-annual" && ( -
-
-

Become Infisical

-

- Unlimited members, projects, RBAC, smart alerts, and so much more -

+
+
+
+

Unleash the full power of Infisical

+

+ Get unlimited members, projects, RBAC, smart alerts, and so much more. +

+
+ + {(isAllowed) => ( + + )} + +
+
+
Want to learn more?
+
+ + + Book a demo + + +
- - {(isAllowed) => ( - - )} -
)} {!isLoading && subscription && data && (