Merge branch 'main' of github.com:reginaldbondoc/infisical

This commit is contained in:
Reginald Bondoc
2022-12-20 01:04:33 +01:00
97 changed files with 4294 additions and 1302 deletions
+4 -4
View File
@@ -10,10 +10,10 @@ import { Listbox, Transition } from "@headlessui/react";
interface ListBoxProps {
selected: string;
onChange: () => void;
data: string[];
text: string;
buttonAction: () => void;
onChange: (arg: string) => void;
data: string[] | null;
text?: string;
buttonAction?: () => void;
isFull?: boolean;
}
+1 -1
View File
@@ -9,7 +9,7 @@ import {
const classNames = require("classnames");
type ButtonProps = {
text: string;
text?: string;
onButtonPressed: () => void;
loading?: boolean;
color?: string;
@@ -0,0 +1,92 @@
import { Fragment } from "react";
import { Dialog, Transition } from "@headlessui/react";
import setBotActiveStatus from "../../../pages/api/bot/setBotActiveStatus";
import getLatestFileKey from "../../../pages/api/workspace/getLatestFileKey";
import {
decryptAssymmetric,
encryptAssymmetric
} from "../../utilities/cryptography/crypto";
import Button from "../buttons/Button";
const ActivateBotDialog = ({
isOpen,
closeModal,
selectedIntegrationOption,
handleBotActivate,
handleIntegrationOption
}) => {
const submit = async () => {
try {
// 1. activate bot
await handleBotActivate();
// 2. start integration
await handleIntegrationOption({
integrationOption: selectedIntegrationOption
});
} catch (err) {
console.log(err);
}
closeModal();
}
return (
<div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-70" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-400"
>
Grant Infisical access to your secrets
</Dialog.Title>
<div className="mt-2 mb-2">
<p className="text-sm text-gray-500">
Most cloud integrations require Infisical to be able to decrypt your secrets so they can be forwarded over.
</p>
</div>
<div className="mt-6 max-w-max">
<Button
onButtonPressed={submit}
color="mineshaft"
text="Grant access"
size="md"
/>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
}
export default ActivateBotDialog;
@@ -0,0 +1,101 @@
import { Fragment } from "react";
import { Dialog, Transition } from "@headlessui/react";
import setBotActiveStatus from "../../../pages/api/bot/setBotActiveStatus";
import getLatestFileKey from "../../../pages/api/workspace/getLatestFileKey";
import {
decryptAssymmetric,
encryptAssymmetric
} from "../../utilities/cryptography/crypto";
import Button from "../buttons/Button";
import InputField from "../InputField";
const IntegrationAccessTokenDialog = ({
isOpen,
closeModal,
selectedIntegrationOption,
handleBotActivate,
handleIntegrationOption
}) => {
const submit = async () => {
try {
// 1. activate bot
await handleBotActivate();
// 2. start integration
await handleIntegrationOption({
integrationOption: selectedIntegrationOption
});
} catch (err) {
console.log(err);
}
closeModal();
}
return (
<div>
<Transition appear show={isOpen} as={Fragment}>
<Dialog as="div" className="relative z-10" onClose={closeModal}>
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="ease-in duration-200"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<div className="fixed inset-0 bg-black bg-opacity-70" />
</Transition.Child>
<div className="fixed inset-0 overflow-y-auto">
<div className="flex min-h-full items-center justify-center p-4 text-center">
<Transition.Child
as={Fragment}
enter="ease-out duration-300"
enterFrom="opacity-0 scale-95"
enterTo="opacity-100 scale-100"
leave="ease-in duration-200"
leaveFrom="opacity-100 scale-100"
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="w-full max-w-md transform overflow-hidden rounded-md bg-bunker-800 border border-gray-700 p-6 text-left align-middle shadow-xl transition-all">
<Dialog.Title
as="h3"
className="text-lg font-medium leading-6 text-gray-400"
>
Grant Infisical access to your secrets
</Dialog.Title>
<div className="mt-2 mb-2">
<p className="text-sm text-gray-500">
Most cloud integrations require Infisical to be able to decrypt your secrets so they can be forwarded over.
</p>
</div>
<div className="mt-6 max-w-max">
{/* <Button
onButtonPressed={submit}
color="mineshaft"
text="Grant access"
size="md"
/> */}
<InputField
label="Access token"
onChangeHandler={() => {}}
type="varName"
value={"Hello"}
placeholder=""
isRequired
/>
</div>
</Dialog.Panel>
</Transition.Child>
</div>
</div>
</Dialog>
</Transition>
</div>
);
}
export default IntegrationAccessTokenDialog;
@@ -0,0 +1,121 @@
import React from "react";
import Image from "next/image";
import { useRouter } from "next/router";
import {
faCheck,
faX,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import deleteIntegrationAuth from "../../pages/api/integrations/DeleteIntegrationAuth";
interface CloudIntegrationOption {
isAvailable: boolean;
name: string;
type: string;
clientId: string;
docsLink: string;
slug: string;
}
interface IntegrationAuth {
_id: string;
integration: string;
}
interface Props {
cloudIntegrationOption: CloudIntegrationOption;
setSelectedIntegrationOption: (cloudIntegration: CloudIntegrationOption) => void;
integrationOptionPress: (cloudIntegrationOption: CloudIntegrationOption) => void;
integrationAuths: IntegrationAuth[];
}
const CloudIntegration = ({
cloudIntegrationOption,
setSelectedIntegrationOption,
integrationOptionPress,
integrationAuths
}: Props) => {
const router = useRouter();
return integrationAuths ? (
<div
className={`relative ${
cloudIntegrationOption.isAvailable
? "hover:bg-white/10 duration-200 cursor-pointer"
: "opacity-50"
} flex flex-row bg-white/5 h-32 rounded-md p-4 items-center`}
onClick={() => {
if (!cloudIntegrationOption.isAvailable) return;
setSelectedIntegrationOption(cloudIntegrationOption);
integrationOptionPress(cloudIntegrationOption);
}}
key={cloudIntegrationOption.name}
>
<Image
src={`/images/integrations/${cloudIntegrationOption.name}.png`}
height={70}
width={70}
alt="integration logo"
/>
{cloudIntegrationOption.name.split(" ").length > 2 ? (
<div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-3xl ml-4 max-w-xs">
<div>{cloudIntegrationOption.name.split(" ")[0]}</div>
<div className="text-base">
{cloudIntegrationOption.name.split(" ")[1]}{" "}
{cloudIntegrationOption.name.split(" ")[2]}
</div>
</div>
) : (
<div className="font-semibold text-gray-300 group-hover:text-gray-200 duration-200 text-xl ml-4 max-w-xs">
{cloudIntegrationOption.name}
</div>
)}
{cloudIntegrationOption.isAvailable &&
integrationAuths
.map((authorization) => authorization.integration)
.includes(cloudIntegrationOption.name.toLowerCase()) && (
<div className="absolute group z-50 top-0 right-0 flex flex-row">
<div
onClick={(event) => {
event.stopPropagation();
deleteIntegrationAuth({
integrationAuthId: integrationAuths
.filter(
(authorization) =>
authorization.integration ==
cloudIntegrationOption.name.toLowerCase()
)
.map((authorization) => authorization._id)[0],
});
router.reload();
}}
className="cursor-pointer w-max bg-red py-0.5 px-2 rounded-b-md text-xs flex flex-row items-center opacity-0 group-hover:opacity-100 duration-200"
>
<FontAwesomeIcon
icon={faX}
className="text-xs mr-2 py-px"
/>
Revoke
</div>
<div className="w-max bg-primary py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90 group-hover:opacity-100 duration-200">
<FontAwesomeIcon
icon={faCheck}
className="text-xs mr-2"
/>
Authorized
</div>
</div>
)}
{!cloudIntegrationOption.isAvailable && (
<div className="absolute group z-50 top-0 right-0 flex flex-row">
<div className="w-max bg-yellow py-0.5 px-2 rounded-bl-md rounded-tr-md text-xs flex flex-row items-center text-black opacity-90">
Coming Soon
</div>
</div>
)}
</div>
) : <div></div>
}
export default CloudIntegration;
@@ -0,0 +1,50 @@
import React from "react";
import CloudIntegration from "./CloudIntegration";
interface CloudIntegrationOption {
isAvailable: boolean;
name: string;
type: string;
clientId: string;
docsLink: string;
slug: string;
}
interface Props {
cloudIntegrationOptions: CloudIntegrationOption[];
setSelectedIntegrationOption: () => void;
integrationOptionPress: () => void;
integrationAuths: any;
}
const CloudIntegrationSection = ({
cloudIntegrationOptions,
setSelectedIntegrationOption,
integrationOptionPress,
integrationAuths
}: Props) => {
return (
<>
<div className={`flex flex-col justify-between items-start m-4 mt-7 text-xl max-w-5xl px-2`}>
<h1 className="font-semibold text-3xl">Cloud Integrations</h1>
<p className="text-base text-gray-400">
Click on an integration to begin syncing secrets to it.
</p>
</div>
<div className="grid gap-4 grid-cols-4 grid-rows-2 mx-6 max-w-5xl">
{cloudIntegrationOptions.map((cloudIntegrationOption) => (
<CloudIntegration
cloudIntegrationOption={cloudIntegrationOption}
setSelectedIntegrationOption={setSelectedIntegrationOption}
integrationOptionPress={integrationOptionPress}
integrationAuths={integrationAuths}
key={`cloud-integration-${cloudIntegrationOption.slug}`}
/>
))}
</div>
</>
);
}
export default CloudIntegrationSection;
@@ -0,0 +1,36 @@
import React from "react";
import Image from "next/image";
interface Framework {
name: string;
slug: string;
image: string;
docsLink: string;
}
const FrameworkIntegration = ({
framework
}: {
framework: Framework;
}) => {
return (
<a
href={framework.docsLink}
rel="noopener"
className={`relative flex flex-row items-center justify-center bg-bunker-500 hover:bg-gradient-to-tr duration-200 h-32 rounded-md p-0.5 items-center cursor-pointer`}
>
<div className={`hover:bg-white/10 duration-200 cursor-pointer font-semibold bg-bunker-500 flex flex-col items-center justify-center h-full w-full rounded-md text-gray-300 group-hover:text-gray-200 duration-200 ${framework?.name?.split(" ").length > 1 ? "text-sm px-1" : "text-xl px-2"} text-center w-full max-w-xs`}>
{framework?.image && <Image
src={`/images/integrations/${framework.image}.png`}
height={framework?.name ? 60 : 90}
width={framework?.name ? 60 : 90}
alt="integration logo"
></Image>}
{framework?.name && framework?.image && <div className="h-2"></div>}
{framework?.name && framework.name}
</div>
</a>
);
}
export default FrameworkIntegration;
@@ -0,0 +1,39 @@
import React from "react";
import FrameworkIntegration from "./FrameworkIntegration";
interface Framework {
name: string;
image: string;
link: string;
slug: string;
docsLink: string;
}
interface Props {
frameworks: [Framework]
}
const FrameworkIntegrationSection = ({ frameworks }: Props) => {
return (
<>
<div className="flex flex-col justify-between items-start mx-4 mt-12 mb-4 text-xl max-w-5xl px-2">
<h1 className="font-semibold text-3xl">Framework Integrations</h1>
<p className="text-base text-gray-400">
Click on a framework to get the setup instructions.
</p>
</div>
<div className="grid gap-4 grid-cols-7 grid-rows-2 mx-6 mt-4 max-w-5xl">
{frameworks.map((framework) => (
<FrameworkIntegration
framework={framework}
key={`framework-integration-${framework.slug}`}
/>
))}
</div>
</>
);
}
export default FrameworkIntegrationSection;
@@ -0,0 +1,231 @@
import React, { useEffect, useState } from "react";
import { useRouter } from "next/router";
import {
faArrowRight,
faRotate,
faX,
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Button from "~/components/basic/buttons/Button";
import ListBox from "~/components/basic/Listbox";
import deleteIntegration from "../../pages/api/integrations/DeleteIntegration"
import getIntegrationApps from "../../pages/api/integrations/GetIntegrationApps";
import updateIntegration from "../../pages/api/integrations/updateIntegration"
import {
contextNetlifyMapping,
envMapping,
reverseContextNetlifyMapping,
reverseEnvMapping,
} from "../../public/data/frequentConstants";
interface Integration {
_id: string;
app?: string;
environment: string;
integration: string;
integrationAuth: string;
isActive: boolean;
context: string;
}
interface IntegrationApp {
name: string;
siteId: string;
}
const Integration = ({
integration
}: {
integration: Integration;
}) => {
const [integrationEnvironment, setIntegrationEnvironment] = useState(
reverseEnvMapping[integration.environment]
);
const [fileState, setFileState] = useState([]);
const router = useRouter();
const [apps, setApps] = useState<IntegrationApp[]>([]); // integration app objects
const [integrationApp, setIntegrationApp] = useState(""); // integration app name
const [integrationTarget, setIntegrationTarget] = useState(""); // vercel-specific integration param
const [integrationContext, setIntegrationContext] = useState(""); // netlify-specific integration param
useEffect(() => {
const loadIntegration = async () => {
interface App {
name: string;
siteId?: string;
}
const tempApps: [IntegrationApp] = await getIntegrationApps({
integrationAuthId: integration.integrationAuth,
});
setApps(tempApps);
setIntegrationApp(
integration.app ? integration.app : tempApps[0].name
);
switch (integration.integration) {
case "vercel":
setIntegrationTarget("Development");
break;
case "netlify":
setIntegrationContext(integration?.context ? contextNetlifyMapping[integration.context] : "Local development");
break;
default:
break;
}
}
loadIntegration();
}, []);
const renderIntegrationSpecificParams = (integration: Integration) => {
try {
switch (integration.integration) {
case "vercel":
return (
<div>
<div className="text-gray-400 text-xs font-semibold mb-2">
ENVIRONMENT
</div>
<ListBox
data={!integration.isActive ? [
"Production",
"Preview",
"Development"
] : null}
selected={"Production"}
onChange={setIntegrationTarget}
/>
</div>
);
case "netlify":
return (
<div>
<div className="text-gray-400 text-xs font-semibold mb-2">
CONTEXT
</div>
<ListBox
data={!integration.isActive ? [
"Production",
"Deploy previews",
"Branch deploys",
"Local development"
] : null}
selected={integrationContext}
onChange={setIntegrationContext}
/>
</div>
);
default:
return <div></div>;
}
} catch (err) {
console.error(err);
}
}
if (!integrationApp || apps.length === 0) return <div></div>
return (
<div className="max-w-5xl p-6 mx-6 mb-8 rounded-md bg-white/5 flex justify-between">
<div className="flex">
<div>
<p className="text-gray-400 text-xs font-semibold mb-2">ENVIRONMENT</p>
<ListBox data={!integration.isActive ? [
"Development",
"Staging",
"Testing",
"Production",
] : null}
selected={integrationEnvironment}
onChange={(environment) => {
setIntegrationEnvironment(environment);
}}
isFull={true}
/>
</div>
<div className="pt-2">
<FontAwesomeIcon
icon={faArrowRight}
className="mx-4 text-gray-400 mt-8"
/>
</div>
<div className="mr-2">
<p className="text-gray-400 text-xs font-semibold mb-2">
INTEGRATION
</p>
<div className="py-2.5 bg-white/[.07] rounded-md pl-4 pr-10 text-sm font-semibold text-gray-300">
{integration.integration.charAt(0).toUpperCase() +
integration.integration.slice(1)}
</div>
</div>
<div className="mr-2">
<div className="text-gray-400 text-xs font-semibold mb-2">
APP
</div>
<ListBox
data={!integration.isActive ? apps.map((app) => app.name) : null}
selected={integrationApp}
onChange={(app) => {
setIntegrationApp(app);
}}
/>
</div>
{renderIntegrationSpecificParams(integration)}
</div>
<div className="flex items-end">
{integration.isActive ? (
<div className="max-w-5xl flex flex-row items-center bg-white/5 p-2 rounded-md px-4">
<FontAwesomeIcon
icon={faRotate}
className="text-lg mr-2.5 text-primary animate-spin"
/>
<div className="text-gray-300 font-semibold">In Sync</div>
</div>
) : (
<Button
text="Start Integration"
onButtonPressed={async () => {
const siteApp = apps.find((app) => app.name === integrationApp); // obj or undefined
const siteId = siteApp?.siteId ? siteApp.siteId : null;
const result = await updateIntegration({
integrationId: integration._id,
environment: envMapping[integrationEnvironment],
app: integrationApp,
isActive: true,
target: integrationTarget ? integrationTarget.toLowerCase() : null,
context: integrationContext ? reverseContextNetlifyMapping[integrationContext] : null,
siteId
});
router.reload();
}}
color="mineshaft"
size="md"
/>
)}
<div className="opacity-50 hover:opacity-100 duration-200 ml-2">
<Button
onButtonPressed={async () => {
await deleteIntegration({
integrationId: integration._id,
});
router.reload();
}}
color="red"
size="icon-md"
icon={faX}
/>
</div>
</div>
</div>
);
};
export default Integration;
@@ -0,0 +1,42 @@
import React from "react";
import guidGenerator from "~/utilities/randomId";
import Integration from "./Integration";
interface Props {
integrations: any
}
interface IntegrationType {
_id: string;
app?: string;
environment: string;
integration: string;
integrationAuth: string;
isActive: boolean;
context: string;
}
const ProjectIntegrationSection = ({
integrations
}: Props) => {
return integrations.length > 0 ? (
<div className="mb-12">
<div className="flex flex-col justify-between items-start mx-4 mb-4 mt-6 text-xl max-w-5xl px-2">
<h1 className="font-semibold text-3xl">Current Integrations</h1>
<p className="text-base text-gray-400">
Manage your integrations of Infisical with third-party services.
</p>
</div>
{integrations.map((integration: IntegrationType) => (
<Integration
key={guidGenerator()}
integration={integration}
/>
))}
</div>
) : <div></div>
}
export default ProjectIntegrationSection;
@@ -73,7 +73,7 @@ const attemptLogin = async (
tag,
privateKey
});
const userOrgs = await getOrganizations();
const userOrgsData = userOrgs.map((org) => org._id);
@@ -10,5 +10,5 @@ export {
POSTHOG_API_KEY,
POSTHOG_HOST,
STRIPE_PRODUCT_PRO,
STRIPE_PRODUCT_STARTER,
};
STRIPE_PRODUCT_STARTER
};