mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-16 01:05:40 +00:00
822 lines
33 KiB
TypeScript
822 lines
33 KiB
TypeScript
import { useEffect, useMemo, useState } from "react";
|
|
import { Helmet } from "react-helmet";
|
|
import { Controller, useForm } from "react-hook-form";
|
|
import {
|
|
faAngleDown,
|
|
faArrowUpRightFromSquare,
|
|
faBookOpen,
|
|
faBugs,
|
|
faCheckCircle,
|
|
faCircleInfo
|
|
} from "@fortawesome/free-solid-svg-icons";
|
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import { useNavigate, useSearch } from "@tanstack/react-router";
|
|
import axios from "axios";
|
|
import { motion } from "framer-motion";
|
|
import { twMerge } from "tailwind-merge";
|
|
import { z, ZodIssueCode } from "zod";
|
|
|
|
import { createNotification } from "@app/components/notifications";
|
|
import {
|
|
Button,
|
|
Card,
|
|
CardTitle,
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuTrigger,
|
|
FormControl,
|
|
Input,
|
|
Select,
|
|
SelectItem,
|
|
Switch,
|
|
Tab,
|
|
TabList,
|
|
TabPanel,
|
|
Tabs
|
|
} from "@app/components/v2";
|
|
import { ROUTE_PATHS } from "@app/const/routes";
|
|
import { useWorkspace } from "@app/context";
|
|
import {
|
|
useCreateIntegration,
|
|
useGetIntegrationAuthApps,
|
|
useGetIntegrationAuthById,
|
|
useGetIntegrationAuthGithubEnvs,
|
|
useGetIntegrationAuthGithubOrgs
|
|
} from "@app/hooks/api";
|
|
import { IntegrationsListPageTabs } from "@app/types/integrations";
|
|
|
|
enum TabSections {
|
|
Connection = "connection",
|
|
Options = "options"
|
|
}
|
|
|
|
const secretsVisibility = [
|
|
{
|
|
value: "selected",
|
|
label: "Select repositories"
|
|
},
|
|
{
|
|
value: "all",
|
|
label: "All public repositories"
|
|
},
|
|
{
|
|
value: "private",
|
|
label: "All private repositories"
|
|
}
|
|
] as const;
|
|
|
|
const targetEnv = ["github-repo", "github-org", "github-env"] as const;
|
|
|
|
const schema = z
|
|
.object({
|
|
selectedSourceEnvironment: z.string().trim(),
|
|
secretPath: z.string().trim(),
|
|
secretSuffix: z.string().trim().optional(),
|
|
shouldEnableDelete: z.boolean().optional(),
|
|
scope: z.enum(targetEnv),
|
|
|
|
// Explanation: If scope is (github-repo) OR (github-org AND visibility is set to selected), then repoIds is required
|
|
repoId: z.string().optional(),
|
|
repoIds: z.string().array().optional(),
|
|
repoName: z.string().optional(),
|
|
repoOwner: z.string().optional(),
|
|
envId: z.string().optional(),
|
|
orgId: z.string().optional(),
|
|
visibility: z.string().optional()
|
|
})
|
|
.superRefine((data, ctx) => {
|
|
if (data.scope === "github-env") {
|
|
if (!data.repoId) {
|
|
ctx.addIssue({
|
|
code: ZodIssueCode.custom,
|
|
path: ["repoId"],
|
|
message: "Repository is required"
|
|
});
|
|
}
|
|
if (!data.repoName) {
|
|
ctx.addIssue({
|
|
code: ZodIssueCode.custom,
|
|
path: ["repoName"],
|
|
message: "Repository is required"
|
|
});
|
|
}
|
|
if (!data.repoOwner) {
|
|
ctx.addIssue({
|
|
code: ZodIssueCode.custom,
|
|
path: ["repoOwner"],
|
|
message: "Repository is required"
|
|
});
|
|
}
|
|
if (!data.envId) {
|
|
ctx.addIssue({
|
|
code: ZodIssueCode.custom,
|
|
path: ["envId"],
|
|
message: "Environment is required"
|
|
});
|
|
}
|
|
}
|
|
|
|
if (data.scope === "github-org") {
|
|
if (!data.orgId) {
|
|
ctx.addIssue({
|
|
code: ZodIssueCode.custom,
|
|
path: ["orgId"],
|
|
message: "Organization is required"
|
|
});
|
|
}
|
|
if (!data.visibility) {
|
|
ctx.addIssue({
|
|
code: ZodIssueCode.custom,
|
|
path: ["visibility"],
|
|
message: "Visibility is required"
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
type FormData = z.infer<typeof schema>;
|
|
|
|
export const GithubConfigurePage = () => {
|
|
const navigate = useNavigate();
|
|
const { mutateAsync } = useCreateIntegration();
|
|
|
|
const { currentWorkspace } = useWorkspace();
|
|
const integrationAuthId = useSearch({
|
|
from: ROUTE_PATHS.SecretManager.Integratons.GithubConfigurePage.id,
|
|
select: (el) => el.integrationAuthId
|
|
});
|
|
const { data: integrationAuth } = useGetIntegrationAuthById(integrationAuthId);
|
|
|
|
const { data: integrationAuthApps, isPending: isIntegrationAuthAppsLoading } =
|
|
useGetIntegrationAuthApps({
|
|
integrationAuthId
|
|
});
|
|
|
|
const { data: integrationAuthOrgs } = useGetIntegrationAuthGithubOrgs(
|
|
integrationAuthId as string
|
|
);
|
|
|
|
const { control, handleSubmit, watch, setValue } = useForm<FormData>({
|
|
resolver: zodResolver(schema),
|
|
defaultValues: {
|
|
secretPath: "/",
|
|
scope: "github-repo",
|
|
repoIds: [],
|
|
visibility: "all",
|
|
shouldEnableDelete: false,
|
|
selectedSourceEnvironment: currentWorkspace.environments[0].slug
|
|
}
|
|
});
|
|
|
|
const scope = watch("scope");
|
|
const repoId = watch("repoId");
|
|
const repoIds = watch("repoIds") || [];
|
|
const repoName = watch("repoName");
|
|
const repoOwner = watch("repoOwner");
|
|
const selectedOrgId = watch("orgId");
|
|
|
|
const { data: integrationAuthGithubEnvs } = useGetIntegrationAuthGithubEnvs(
|
|
integrationAuthId as string,
|
|
repoName || "",
|
|
repoOwner || ""
|
|
);
|
|
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
useEffect(() => {
|
|
if (integrationAuthGithubEnvs && integrationAuthGithubEnvs?.length > 0) {
|
|
setValue("envId", integrationAuthGithubEnvs[0].envId);
|
|
} else {
|
|
setValue("envId", undefined);
|
|
}
|
|
}, [integrationAuthGithubEnvs]);
|
|
|
|
const onFormSubmit = async (data: FormData) => {
|
|
try {
|
|
setIsLoading(true);
|
|
|
|
if (!integrationAuth?.id) return;
|
|
|
|
switch (data.scope) {
|
|
case "github-repo": {
|
|
const targetApps = integrationAuthApps?.filter((integrationAuthApp) =>
|
|
data.repoIds?.includes(String(integrationAuthApp.appId))
|
|
);
|
|
|
|
if (!targetApps) return;
|
|
|
|
await Promise.all(
|
|
targetApps.map(async (targetApp) => {
|
|
await mutateAsync({
|
|
integrationAuthId: integrationAuth?.id,
|
|
isActive: true,
|
|
scope: data.scope,
|
|
secretPath: data.secretPath,
|
|
sourceEnvironment: data.selectedSourceEnvironment,
|
|
app: targetApp.name, // repo name
|
|
owner: targetApp.owner, // repo owner
|
|
metadata: {
|
|
secretSuffix: data.secretSuffix,
|
|
shouldEnableDelete: data.shouldEnableDelete
|
|
}
|
|
});
|
|
})
|
|
);
|
|
|
|
break;
|
|
}
|
|
case "github-org":
|
|
await mutateAsync({
|
|
integrationAuthId: integrationAuth?.id,
|
|
isActive: true,
|
|
secretPath: data.secretPath,
|
|
sourceEnvironment: data.selectedSourceEnvironment,
|
|
scope: data.scope,
|
|
owner: integrationAuthOrgs?.find((e) => e.orgId === data.orgId)?.name,
|
|
metadata: {
|
|
githubVisibility: data.visibility,
|
|
githubVisibilityRepoIds: data.repoIds,
|
|
secretSuffix: data.secretSuffix,
|
|
shouldEnableDelete: data.shouldEnableDelete
|
|
}
|
|
});
|
|
break;
|
|
|
|
case "github-env":
|
|
await mutateAsync({
|
|
integrationAuthId: integrationAuth?.id,
|
|
isActive: true,
|
|
secretPath: data.secretPath,
|
|
sourceEnvironment: data.selectedSourceEnvironment,
|
|
scope: data.scope,
|
|
app: repoName,
|
|
appId: data.repoId,
|
|
owner: repoOwner,
|
|
targetEnvironmentId: data.envId,
|
|
metadata: {
|
|
secretSuffix: data.secretSuffix,
|
|
shouldEnableDelete: data.shouldEnableDelete
|
|
}
|
|
});
|
|
break;
|
|
default:
|
|
throw new Error("Invalid scope");
|
|
}
|
|
|
|
setIsLoading(false);
|
|
navigate({
|
|
to: "/secret-manager/$projectId/integrations",
|
|
params: {
|
|
projectId: currentWorkspace.id
|
|
},
|
|
search: {
|
|
selectedTab: IntegrationsListPageTabs.NativeIntegrations
|
|
}
|
|
});
|
|
} catch (err) {
|
|
console.error(err);
|
|
|
|
let errorMessage: string = "Something went wrong!";
|
|
if (axios.isAxiosError(err)) {
|
|
const { message } = err?.response?.data as { message: string };
|
|
errorMessage = message;
|
|
}
|
|
|
|
createNotification({
|
|
text: errorMessage,
|
|
type: "error"
|
|
});
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const selectedOrganization = useMemo(() => {
|
|
if (!integrationAuthApps) return null;
|
|
|
|
return integrationAuthApps.filter(
|
|
(authApp) =>
|
|
integrationAuthOrgs?.find((e) => e.orgId === selectedOrgId)?.name === authApp.owner
|
|
);
|
|
}, [selectedOrgId, integrationAuthApps]);
|
|
|
|
return integrationAuth && integrationAuthApps ? (
|
|
<div className="flex h-full w-full flex-col items-center justify-center py-4">
|
|
<Helmet>
|
|
<title>Set Up GitHub Integration</title>
|
|
</Helmet>
|
|
<Card className="max-w-lg rounded-md border border-mineshaft-600 p-0">
|
|
<form onSubmit={handleSubmit(onFormSubmit)} className="px-6">
|
|
<CardTitle
|
|
className="px-0 text-left text-xl"
|
|
subTitle="Choose which environment in Infisical you want to sync to environment variables in GitHub."
|
|
>
|
|
<div className="flex flex-row items-center">
|
|
<div className="flex items-center rounded-full bg-mineshaft-200">
|
|
<img
|
|
src="/images/integrations/GitHub.png"
|
|
height={30}
|
|
width={30}
|
|
alt="GitHub logo"
|
|
/>
|
|
</div>
|
|
<span className="ml-2.5">GitHub Integration </span>
|
|
<a
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
href="https://infisical.com/docs/integrations/cicd/githubactions"
|
|
>
|
|
<div className="mb-1 ml-2 inline-block cursor-default rounded-md bg-yellow/20 px-1.5 pb-[0.03rem] pt-[0.04rem] text-sm text-yellow opacity-80 hover:opacity-100">
|
|
<FontAwesomeIcon icon={faBookOpen} className="mr-1.5" />
|
|
Docs
|
|
<FontAwesomeIcon
|
|
icon={faArrowUpRightFromSquare}
|
|
className="mb-[0.07rem] ml-1.5 text-xxs"
|
|
/>
|
|
</div>
|
|
</a>
|
|
</div>
|
|
</CardTitle>
|
|
<Tabs defaultValue={TabSections.Connection}>
|
|
<TabList>
|
|
<div className="flex w-full flex-row border-b border-mineshaft-600">
|
|
<Tab value={TabSections.Connection}>Connection</Tab>
|
|
<Tab value={TabSections.Options}>Options</Tab>
|
|
</div>
|
|
</TabList>
|
|
<TabPanel value={TabSections.Connection}>
|
|
<motion.div
|
|
key="panel-1"
|
|
transition={{ duration: 0.15 }}
|
|
initial={{ opacity: 0, translateX: 30 }}
|
|
animate={{ opacity: 1, translateX: 0 }}
|
|
exit={{ opacity: 0, translateX: 30 }}
|
|
>
|
|
<Controller
|
|
control={control}
|
|
name="selectedSourceEnvironment"
|
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
|
<FormControl
|
|
label="Project Environment"
|
|
errorText={error?.message}
|
|
isError={Boolean(error)}
|
|
>
|
|
<Select
|
|
defaultValue={field.value}
|
|
onValueChange={onChange}
|
|
className="w-full border border-mineshaft-500"
|
|
>
|
|
{currentWorkspace?.environments.map((sourceEnvironment) => (
|
|
<SelectItem
|
|
value={sourceEnvironment.slug}
|
|
key={`source-environment-${sourceEnvironment.slug}`}
|
|
>
|
|
{sourceEnvironment.name}
|
|
</SelectItem>
|
|
))}
|
|
</Select>
|
|
</FormControl>
|
|
)}
|
|
/>
|
|
<Controller
|
|
control={control}
|
|
name="secretPath"
|
|
render={({ field, fieldState: { error } }) => (
|
|
<FormControl
|
|
label="Secrets Path"
|
|
errorText={error?.message}
|
|
isError={Boolean(error)}
|
|
>
|
|
<Input {...field} placeholder="Provide a path, default is /" />
|
|
</FormControl>
|
|
)}
|
|
/>
|
|
<Controller
|
|
control={control}
|
|
name="scope"
|
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
|
<FormControl label="Scope" errorText={error?.message} isError={Boolean(error)}>
|
|
<Select
|
|
defaultValue={field.value}
|
|
onValueChange={(e) => {
|
|
setValue("repoIds", []);
|
|
onChange(e);
|
|
}}
|
|
className="w-full border border-mineshaft-500"
|
|
>
|
|
<SelectItem value="github-org">Organization</SelectItem>
|
|
<SelectItem value="github-repo">Repository</SelectItem>
|
|
<SelectItem value="github-env">Repository Environment</SelectItem>
|
|
</Select>
|
|
</FormControl>
|
|
)}
|
|
/>
|
|
{scope === "github-repo" && (
|
|
<Controller
|
|
control={control}
|
|
name="repoIds"
|
|
render={({ field: { onChange }, fieldState: { error } }) => (
|
|
<FormControl
|
|
label="Repositories"
|
|
isError={Boolean(error?.message)}
|
|
errorText={error?.message}
|
|
>
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
{integrationAuthApps.length > 0 ? (
|
|
<div className="inline-flex w-full cursor-pointer items-center justify-between rounded-md border border-mineshaft-600 bg-mineshaft-900 px-3 py-2 font-inter text-sm font-normal text-bunker-200 outline-none data-[placeholder]:text-mineshaft-200">
|
|
{repoIds?.length === 1
|
|
? integrationAuthApps?.reduce(
|
|
(acc, { appId, name, owner }) =>
|
|
repoIds?.[0] === appId ? `${owner}/${name}` : acc,
|
|
""
|
|
)
|
|
: `${repoIds?.length} repositories selected`}
|
|
<FontAwesomeIcon icon={faAngleDown} className="text-xs" />
|
|
</div>
|
|
) : (
|
|
<div className="inline-flex w-full cursor-default items-center justify-between rounded-md border border-mineshaft-600 bg-mineshaft-900 px-3 py-2 font-inter text-sm font-normal text-bunker-200 outline-none data-[placeholder]:text-mineshaft-200">
|
|
No repositories found
|
|
</div>
|
|
)}
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent
|
|
align="start"
|
|
className="thin-scrollbar z-[100] max-h-80 overflow-y-scroll"
|
|
>
|
|
{integrationAuthApps.length > 0 ? (
|
|
integrationAuthApps.map((integrationAuthApp) => {
|
|
const isSelected = repoIds?.includes(
|
|
String(integrationAuthApp.appId)
|
|
);
|
|
|
|
return (
|
|
<DropdownMenuItem
|
|
onClick={() => {
|
|
if (repoIds?.includes(String(integrationAuthApp.appId))) {
|
|
onChange(
|
|
repoIds.filter(
|
|
(appId: string) =>
|
|
appId !== String(integrationAuthApp.appId)
|
|
)
|
|
);
|
|
} else {
|
|
onChange([
|
|
...(repoIds || []),
|
|
String(integrationAuthApp.appId)
|
|
]);
|
|
}
|
|
}}
|
|
key={`repos-id-${integrationAuthApp.appId}`}
|
|
icon={
|
|
isSelected ? (
|
|
<FontAwesomeIcon
|
|
icon={faCheckCircle}
|
|
className="pr-0.5 text-primary"
|
|
/>
|
|
) : (
|
|
<div className="pl-[1.01rem]" />
|
|
)
|
|
}
|
|
iconPos="left"
|
|
className="w-[28.4rem] text-sm"
|
|
>
|
|
{integrationAuthApp.owner}/{integrationAuthApp.name}
|
|
</DropdownMenuItem>
|
|
);
|
|
})
|
|
) : (
|
|
<div />
|
|
)}
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</FormControl>
|
|
)}
|
|
/>
|
|
)}
|
|
{scope === "github-org" && (
|
|
<>
|
|
<Controller
|
|
control={control}
|
|
name="orgId"
|
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
|
<FormControl
|
|
label="Organization"
|
|
errorText={
|
|
integrationAuthOrgs?.length ? error?.message : "No organizations found"
|
|
}
|
|
isError={Boolean(integrationAuthOrgs?.length || error?.message)}
|
|
>
|
|
<Select
|
|
value={field.value}
|
|
onValueChange={onChange}
|
|
className="w-full border border-mineshaft-500"
|
|
>
|
|
{integrationAuthOrgs &&
|
|
integrationAuthOrgs.map(({ name, orgId }) => (
|
|
<SelectItem key={`github-organization-${orgId}`} value={orgId}>
|
|
{name}
|
|
</SelectItem>
|
|
))}
|
|
</Select>
|
|
</FormControl>
|
|
)}
|
|
/>
|
|
|
|
<Controller
|
|
control={control}
|
|
name="visibility"
|
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
|
<FormControl
|
|
tooltipText="Select which type of repositories that the secrets should be synced to."
|
|
label="Visibility"
|
|
errorText={error?.message}
|
|
isError={Boolean(error?.message)}
|
|
>
|
|
<Select
|
|
value={field.value}
|
|
onValueChange={onChange}
|
|
className="w-full border border-mineshaft-500"
|
|
>
|
|
{secretsVisibility.map(({ label, value }) => (
|
|
<SelectItem key={`github-visibility-${value}`} value={value}>
|
|
{label}
|
|
</SelectItem>
|
|
))}
|
|
</Select>
|
|
</FormControl>
|
|
)}
|
|
/>
|
|
|
|
{watch("visibility") === "selected" && (
|
|
<Controller
|
|
control={control}
|
|
name="repoIds"
|
|
render={({ field: { onChange }, fieldState: { error } }) => (
|
|
<FormControl
|
|
label="Selected Repositories"
|
|
isError={Boolean(error?.message)}
|
|
errorText={error?.message}
|
|
>
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
{integrationAuthApps.length > 0 ? (
|
|
<div className="inline-flex w-full cursor-pointer items-center justify-between rounded-md border border-mineshaft-600 bg-mineshaft-900 px-3 py-2 font-inter text-sm font-normal text-bunker-200 outline-none data-[placeholder]:text-mineshaft-200">
|
|
{repoIds?.length === 1
|
|
? integrationAuthApps?.reduce(
|
|
(acc, { appId, name, owner }) =>
|
|
repoIds?.[0] === appId ? `${owner}/${name}` : acc,
|
|
""
|
|
)
|
|
: `${repoIds?.length} repositories selected`}
|
|
<FontAwesomeIcon icon={faAngleDown} className="text-xs" />
|
|
</div>
|
|
) : (
|
|
<div className="inline-flex w-full cursor-default items-center justify-between rounded-md border border-mineshaft-600 bg-mineshaft-900 px-3 py-2 font-inter text-sm font-normal text-bunker-200 outline-none data-[placeholder]:text-mineshaft-200">
|
|
No repositories found
|
|
</div>
|
|
)}
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent
|
|
align="start"
|
|
className="thin-scrollbar z-[100] max-h-80 overflow-y-scroll"
|
|
>
|
|
{selectedOrganization ? (
|
|
selectedOrganization.map((integrationAuthApp) => {
|
|
const isSelected = repoIds?.includes(
|
|
String(integrationAuthApp.appId)
|
|
);
|
|
|
|
return (
|
|
<DropdownMenuItem
|
|
onClick={() => {
|
|
if (repoIds?.includes(String(integrationAuthApp.appId))) {
|
|
onChange(
|
|
repoIds?.filter(
|
|
(appId: string) =>
|
|
appId !== String(integrationAuthApp.appId)
|
|
)
|
|
);
|
|
} else {
|
|
onChange([
|
|
...repoIds,
|
|
String(integrationAuthApp.appId)
|
|
]);
|
|
}
|
|
}}
|
|
key={`repos-id-${integrationAuthApp.appId}`}
|
|
icon={
|
|
isSelected ? (
|
|
<FontAwesomeIcon
|
|
icon={faCheckCircle}
|
|
className="pr-0.5 text-primary"
|
|
/>
|
|
) : (
|
|
<div className="pl-[1.01rem]" />
|
|
)
|
|
}
|
|
iconPos="left"
|
|
className="w-[28.4rem] text-sm"
|
|
>
|
|
{integrationAuthApp.owner}/{integrationAuthApp.name}
|
|
</DropdownMenuItem>
|
|
);
|
|
})
|
|
) : (
|
|
<div />
|
|
)}
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</FormControl>
|
|
)}
|
|
/>
|
|
)}
|
|
</>
|
|
)}
|
|
{scope === "github-env" && (
|
|
<Controller
|
|
control={control}
|
|
name="repoId"
|
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
|
<FormControl
|
|
label="Repository"
|
|
errorText={error?.message}
|
|
isError={Boolean(error)}
|
|
>
|
|
<Select
|
|
value={field.value}
|
|
onValueChange={(e) => {
|
|
const selectedRepo = integrationAuthApps.find((app) => app.appId === e);
|
|
setValue("repoName", selectedRepo?.name);
|
|
setValue("repoOwner", selectedRepo?.owner);
|
|
onChange(e);
|
|
}}
|
|
className="w-full border border-mineshaft-500"
|
|
>
|
|
{integrationAuthApps?.length ? (
|
|
integrationAuthApps.map((app) => {
|
|
return (
|
|
<SelectItem
|
|
value={app.appId as string}
|
|
key={`repo-id-${app.appId}`}
|
|
className="w-[28.4rem] text-sm"
|
|
>
|
|
{app.owner}/{app.name}
|
|
</SelectItem>
|
|
);
|
|
})
|
|
) : (
|
|
<div />
|
|
)}
|
|
</Select>
|
|
</FormControl>
|
|
)}
|
|
/>
|
|
)}
|
|
{scope === "github-env" && (
|
|
<Controller
|
|
control={control}
|
|
name="envId"
|
|
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
|
<FormControl
|
|
label="Environment"
|
|
errorText={
|
|
integrationAuthGithubEnvs?.length
|
|
? error?.message
|
|
: "No Environment found"
|
|
}
|
|
isError={Boolean(integrationAuthGithubEnvs?.length || error?.message)}
|
|
>
|
|
<Select
|
|
value={field.value}
|
|
onValueChange={onChange}
|
|
isDisabled={!repoId}
|
|
className={twMerge(
|
|
"w-full border border-mineshaft-500",
|
|
!repoId && "h-10 cursor-not-allowed"
|
|
)}
|
|
>
|
|
{integrationAuthGithubEnvs?.length ? (
|
|
integrationAuthGithubEnvs.map((githubEnv) => {
|
|
return (
|
|
<SelectItem
|
|
value={githubEnv.name as string}
|
|
key={`env-id-${githubEnv.envId}`}
|
|
className="w-[28.4rem] text-sm"
|
|
>
|
|
{githubEnv.name}
|
|
</SelectItem>
|
|
);
|
|
})
|
|
) : (
|
|
<div />
|
|
)}
|
|
</Select>
|
|
</FormControl>
|
|
)}
|
|
/>
|
|
)}
|
|
</motion.div>
|
|
</TabPanel>
|
|
<TabPanel value={TabSections.Options}>
|
|
<motion.div
|
|
key="panel-1"
|
|
transition={{ duration: 0.15 }}
|
|
initial={{ opacity: 0, translateX: -30 }}
|
|
animate={{ opacity: 1, translateX: 0 }}
|
|
exit={{ opacity: 0, translateX: 30 }}
|
|
>
|
|
<div className="mb-5 ml-1">
|
|
<Controller
|
|
control={control}
|
|
name="shouldEnableDelete"
|
|
render={({ field: { onChange, value } }) => (
|
|
<Switch
|
|
id="delete-github-option"
|
|
onCheckedChange={(isChecked) => onChange(isChecked)}
|
|
isChecked={value}
|
|
>
|
|
Delete secrets in Github that are not in Infisical
|
|
</Switch>
|
|
)}
|
|
/>
|
|
</div>
|
|
<Controller
|
|
control={control}
|
|
name="secretSuffix"
|
|
render={({ field, fieldState: { error } }) => (
|
|
<FormControl
|
|
label="Append Secret Names with..."
|
|
className="pb-[9.75rem]"
|
|
errorText={error?.message}
|
|
isError={Boolean(error)}
|
|
>
|
|
<Input
|
|
{...field}
|
|
placeholder="Provide a suffix for secret names, default is no suffix"
|
|
/>
|
|
</FormControl>
|
|
)}
|
|
/>
|
|
</motion.div>
|
|
</TabPanel>
|
|
</Tabs>
|
|
<div className="flex w-full justify-end">
|
|
<Button
|
|
type="submit"
|
|
color="mineshaft"
|
|
variant="outline_bg"
|
|
className="mb-6"
|
|
isLoading={isLoading}
|
|
>
|
|
Create Integration
|
|
</Button>
|
|
</div>
|
|
</form>
|
|
</Card>
|
|
<div className="mt-6 w-full max-w-md border-t border-mineshaft-800" />
|
|
<div className="mt-6 flex w-full max-w-lg flex-col rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4">
|
|
<div className="flex flex-row items-center">
|
|
<FontAwesomeIcon icon={faCircleInfo} className="text-xl text-mineshaft-200" />{" "}
|
|
<span className="text-md ml-3 text-mineshaft-100">Pro Tip</span>
|
|
</div>
|
|
<span className="mt-4 text-sm text-mineshaft-300">
|
|
After creating an integration, your secrets will start syncing immediately. This might
|
|
cause an unexpected override of current secrets in GitHub with secrets from Infisical.
|
|
</span>
|
|
</div>
|
|
</div>
|
|
) : (
|
|
<div className="flex h-full w-full items-center justify-center">
|
|
<Helmet>
|
|
<title>Set Up GitHub Integration</title>
|
|
</Helmet>
|
|
{isIntegrationAuthAppsLoading ? (
|
|
<img
|
|
src="/images/loading/loading.gif"
|
|
height={70}
|
|
width={120}
|
|
alt="infisical loading indicator"
|
|
/>
|
|
) : (
|
|
<div className="flex h-max max-w-md flex-col rounded-md border border-mineshaft-600 bg-mineshaft-800 p-6 text-center text-mineshaft-200">
|
|
<FontAwesomeIcon icon={faBugs} className="li my-2 inline text-6xl" />
|
|
<p>
|
|
Something went wrong. Please contact{" "}
|
|
<a
|
|
className="inline cursor-pointer text-mineshaft-100 underline decoration-primary-500 underline-offset-4 opacity-80 duration-200 hover:opacity-100"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
href="mailto:[email protected]"
|
|
>
|
|
support@infisical.com
|
|
</a>{" "}
|
|
if the issue persists.
|
|
</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|