mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-14 11:29:42 +00:00
Merge remote-tracking branch 'origin' into groups-phase-2c
This commit is contained in:
@@ -353,7 +353,7 @@ export const InfisicalSecretInput = ({
|
||||
highlightedIndex === i ? "bg-gray-600" : ""
|
||||
} text-md relative mb-0.5 flex w-full cursor-pointer select-none items-center justify-between rounded-md px-2 py-2 outline-none transition-all hover:bg-mineshaft-500 data-[highlighted]:bg-mineshaft-500`}
|
||||
>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex w-full gap-2">
|
||||
<div className="flex items-center text-yellow-700">
|
||||
<FontAwesomeIcon
|
||||
icon={entryIcon}
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
import { InputHTMLAttributes, useEffect, useState } from "react";
|
||||
import { faFolder } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import * as Popover from "@radix-ui/react-popover";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { useDebounce } from "@app/hooks";
|
||||
import { useGetFoldersByEnv } from "@app/hooks/api";
|
||||
|
||||
import { Input } from "../Input";
|
||||
|
||||
type Props = Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "onChange"> & {
|
||||
value?: string | null;
|
||||
isImport?: boolean;
|
||||
isVisible?: boolean;
|
||||
isReadOnly?: boolean;
|
||||
isDisabled?: boolean;
|
||||
environment?: string;
|
||||
containerClassName?: string;
|
||||
onChange?: (arg: string) => void;
|
||||
};
|
||||
|
||||
export const SecretPathInput = ({
|
||||
containerClassName,
|
||||
onChange,
|
||||
environment,
|
||||
value: propValue,
|
||||
...props
|
||||
}: Props) => {
|
||||
const [inputValue, setInputValue] = useState(propValue ?? "");
|
||||
const [secretPath, setSecretPath] = useState("/");
|
||||
const [suggestions, setSuggestions] = useState<string[]>([]);
|
||||
const [highlightedIndex, setHighlightedIndex] = useState(-1);
|
||||
const debouncedInputValue = useDebounce(inputValue, 200);
|
||||
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const workspaceId = currentWorkspace?.id || "";
|
||||
const { folderNames: folders } = useGetFoldersByEnv({
|
||||
path: secretPath,
|
||||
environments: [environment || currentWorkspace?.environments?.[0].slug!],
|
||||
projectId: workspaceId
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
setInputValue(propValue ?? "/");
|
||||
}, [propValue]);
|
||||
|
||||
useEffect(() => {
|
||||
if (environment) {
|
||||
setInputValue("/");
|
||||
setSecretPath("/");
|
||||
onChange?.("/");
|
||||
}
|
||||
}, [environment]);
|
||||
|
||||
useEffect(() => {
|
||||
// update secret path if input is valid
|
||||
if (
|
||||
(debouncedInputValue.length > 0 &&
|
||||
debouncedInputValue[debouncedInputValue.length - 1] === "/") ||
|
||||
debouncedInputValue.length === 0
|
||||
) {
|
||||
setSecretPath(debouncedInputValue);
|
||||
}
|
||||
|
||||
// filter suggestions based on matching
|
||||
const searchFragment = debouncedInputValue.split("/").pop() || "";
|
||||
const filteredSuggestions = folders
|
||||
.filter((suggestionEntry) =>
|
||||
suggestionEntry.toUpperCase().startsWith(searchFragment.toUpperCase())
|
||||
)
|
||||
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
||||
|
||||
setSuggestions(filteredSuggestions);
|
||||
}, [debouncedInputValue]);
|
||||
|
||||
const handleSuggestionSelect = (selectedIndex: number) => {
|
||||
if (!suggestions[selectedIndex]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const validPaths = inputValue.split("/");
|
||||
validPaths.pop();
|
||||
|
||||
const newValue = `${validPaths.join("/")}/${suggestions[selectedIndex]}`;
|
||||
onChange?.(newValue);
|
||||
setInputValue(newValue);
|
||||
setSecretPath(newValue);
|
||||
setHighlightedIndex(-1);
|
||||
setSuggestions([]);
|
||||
};
|
||||
|
||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
const mod = (n: number, m: number) => ((n % m) + m) % m;
|
||||
if (e.key === "ArrowDown") {
|
||||
setHighlightedIndex((prevIndex) => mod(prevIndex + 1, suggestions.length));
|
||||
} else if (e.key === "ArrowUp") {
|
||||
setHighlightedIndex((prevIndex) => mod(prevIndex - 1, suggestions.length));
|
||||
} else if (e.key === "Enter" && highlightedIndex >= 0) {
|
||||
handleSuggestionSelect(highlightedIndex);
|
||||
}
|
||||
if (["ArrowDown", "ArrowUp", "Enter"].includes(e.key)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
const handleInputChange = (e: any) => {
|
||||
// propagate event to react-hook-form onChange
|
||||
if (onChange) {
|
||||
onChange(e.target.value);
|
||||
}
|
||||
|
||||
setInputValue(e.target.value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover.Root
|
||||
open={suggestions.length > 0 && inputValue.length > 1}
|
||||
onOpenChange={() => {
|
||||
setHighlightedIndex(-1);
|
||||
}}
|
||||
>
|
||||
<Popover.Trigger asChild>
|
||||
<Input
|
||||
{...props}
|
||||
type="text"
|
||||
autoComplete="off"
|
||||
onKeyDown={handleKeyDown}
|
||||
value={inputValue}
|
||||
onChange={handleInputChange}
|
||||
className={containerClassName}
|
||||
/>
|
||||
</Popover.Trigger>
|
||||
<Popover.Content
|
||||
align="start"
|
||||
onOpenAutoFocus={(e) => e.preventDefault()}
|
||||
className={twMerge(
|
||||
"relative top-2 z-[100] overflow-hidden rounded-md border border-mineshaft-600 bg-mineshaft-900 font-inter text-bunker-100 shadow-md"
|
||||
)}
|
||||
style={{
|
||||
width: "var(--radix-popover-trigger-width)",
|
||||
maxHeight: "var(--radix-select-content-available-height)"
|
||||
}}
|
||||
>
|
||||
<div className="max-w-60 h-full w-full flex-col items-center justify-center rounded-md text-white">
|
||||
{suggestions.map((suggestion, i) => (
|
||||
<div
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
setHighlightedIndex(i);
|
||||
handleSuggestionSelect(i);
|
||||
}}
|
||||
style={{ pointerEvents: "auto" }}
|
||||
className="flex items-center justify-between border-mineshaft-600 text-left"
|
||||
key={`secret-reference-secret-${i + 1}`}
|
||||
>
|
||||
<div
|
||||
className={`${
|
||||
highlightedIndex === i ? "bg-gray-600" : ""
|
||||
} text-md relative mb-0.5 flex w-full cursor-pointer select-none items-center justify-between rounded-md px-2 py-1 outline-none transition-all hover:bg-mineshaft-500 data-[highlighted]:bg-mineshaft-500`}
|
||||
>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex items-center text-yellow-700">
|
||||
<FontAwesomeIcon icon={faFolder} />
|
||||
</div>
|
||||
<div className="text-md w-10/12 truncate text-left">{suggestion}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Popover.Content>
|
||||
</Popover.Root>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { SecretPathInput } from "./SecretPathInput";
|
||||
@@ -1,14 +1,16 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Head from "next/head";
|
||||
import frameworkIntegrationOptions from "public/json/frameworkIntegrations.json";
|
||||
import infrastructureIntegrationOptions from "public/json/infrastructureIntegrations.json";
|
||||
|
||||
import { IntegrationsPage } from "@app/views/IntegrationsPage";
|
||||
|
||||
type Props = {
|
||||
frameworkIntegrations: typeof frameworkIntegrationOptions;
|
||||
infrastructureIntegrations: typeof infrastructureIntegrationOptions;
|
||||
};
|
||||
|
||||
const Integration = ({ frameworkIntegrations }: Props) => {
|
||||
const Integration = ({ frameworkIntegrations, infrastructureIntegrations }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
@@ -20,7 +22,7 @@ const Integration = ({ frameworkIntegrations }: Props) => {
|
||||
<meta property="og:title" content="Manage your .env files in seconds" />
|
||||
<meta name="og:description" content={t("integrations.description") as string} />
|
||||
</Head>
|
||||
<IntegrationsPage frameworkIntegrations={frameworkIntegrations} />
|
||||
<IntegrationsPage frameworkIntegrations={frameworkIntegrations} infrastructureIntegrations={infrastructureIntegrations} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -28,7 +30,8 @@ const Integration = ({ frameworkIntegrations }: Props) => {
|
||||
export const getStaticProps = () => {
|
||||
return {
|
||||
props: {
|
||||
frameworkIntegrations: frameworkIntegrationOptions
|
||||
frameworkIntegrations: frameworkIntegrationOptions,
|
||||
infrastructureIntegrations: infrastructureIntegrationOptions
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@ import { motion } from "framer-motion";
|
||||
import queryString from "query-string";
|
||||
|
||||
import { useCreateIntegration } from "@app/hooks/api";
|
||||
import { useGetIntegrationAuthAwsKmsKeys } from "@app/hooks/api/integrationAuth/queries";
|
||||
|
||||
import {
|
||||
Button,
|
||||
@@ -90,6 +91,7 @@ export default function AWSParameterStoreCreateIntegrationPage() {
|
||||
const [shouldTag, setShouldTag] = useState(false);
|
||||
const [tagKey, setTagKey] = useState("");
|
||||
const [tagValue, setTagValue] = useState("");
|
||||
const [kmsKeyId, setKmsKeyId] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if (workspace) {
|
||||
@@ -98,6 +100,19 @@ export default function AWSParameterStoreCreateIntegrationPage() {
|
||||
}
|
||||
}, [workspace]);
|
||||
|
||||
|
||||
const { data: integrationAuthAwsKmsKeys, isLoading: isIntegrationAuthAwsKmsKeysLoading } =
|
||||
useGetIntegrationAuthAwsKmsKeys({
|
||||
integrationAuthId: String(integrationAuthId),
|
||||
region: selectedAWSRegion
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (integrationAuthAwsKmsKeys) {
|
||||
setKmsKeyId(String(integrationAuthAwsKmsKeys?.filter(key => key.alias === "default")[0]?.id))
|
||||
}
|
||||
}, [integrationAuthAwsKmsKeys])
|
||||
|
||||
const isValidAWSParameterStorePath = (awsStorePath: string) => {
|
||||
const pattern = /^\/([\w-]+\/)*[\w-]+\/$/;
|
||||
return pattern.test(awsStorePath) && awsStorePath.length <= 2048;
|
||||
@@ -133,7 +148,11 @@ export default function AWSParameterStoreCreateIntegrationPage() {
|
||||
value: tagValue
|
||||
}]
|
||||
}
|
||||
: {})
|
||||
: {}),
|
||||
...((kmsKeyId && integrationAuthAwsKmsKeys?.filter(key => key.id === kmsKeyId)[0]?.alias !== "default") ?
|
||||
{
|
||||
kmsKeyId
|
||||
}: {})
|
||||
}
|
||||
});
|
||||
|
||||
@@ -146,7 +165,7 @@ export default function AWSParameterStoreCreateIntegrationPage() {
|
||||
}
|
||||
};
|
||||
|
||||
return integrationAuth && workspace && selectedSourceEnvironment ? (
|
||||
return (integrationAuth && workspace && selectedSourceEnvironment && !isIntegrationAuthAwsKmsKeysLoading) ? (
|
||||
<div className="flex h-full w-full flex-col items-center justify-center">
|
||||
<Head>
|
||||
<title>Set Up AWS Parameter Integration</title>
|
||||
@@ -286,6 +305,31 @@ export default function AWSParameterStoreCreateIntegrationPage() {
|
||||
</FormControl>
|
||||
</div>
|
||||
)}
|
||||
<FormControl label="Encryption Key" className="mt-4">
|
||||
<Select
|
||||
value={kmsKeyId}
|
||||
onValueChange={(e) => {
|
||||
setKmsKeyId(e)
|
||||
}}
|
||||
className="w-full border border-mineshaft-500"
|
||||
>
|
||||
{integrationAuthAwsKmsKeys?.length ? (
|
||||
integrationAuthAwsKmsKeys.map((key) => {
|
||||
return (
|
||||
<SelectItem
|
||||
value={key.id as string}
|
||||
key={`repo-id-${key.id}`}
|
||||
className="w-[28.4rem] text-sm"
|
||||
>
|
||||
{key.alias}
|
||||
</SelectItem>
|
||||
);
|
||||
})
|
||||
) : (
|
||||
<div />
|
||||
)}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</motion.div>
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
@@ -318,7 +362,7 @@ export default function AWSParameterStoreCreateIntegrationPage() {
|
||||
<title>Set Up AWS Parameter Store Integration</title>
|
||||
<link rel="icon" href="/infisical.ico" />
|
||||
</Head>
|
||||
{isintegrationAuthLoading ? (
|
||||
{(isintegrationAuthLoading || isIntegrationAuthAwsKmsKeysLoading) ? (
|
||||
<img
|
||||
src="/images/loading/loading.gif"
|
||||
height={70}
|
||||
|
||||
@@ -148,7 +148,7 @@ export default function AWSSecretManagerCreateIntegrationPage() {
|
||||
}]
|
||||
}
|
||||
: {}),
|
||||
...((kmsKeyId && integrationAuthAwsKmsKeys?.filter(key => key.id === kmsKeyId)[0]?.alias !== "alias/aws/secretsmanager") ?
|
||||
...((kmsKeyId && integrationAuthAwsKmsKeys?.filter(key => key.id === kmsKeyId)[0]?.alias !== "default") ?
|
||||
{
|
||||
kmsKeyId
|
||||
}: {})
|
||||
|
||||
@@ -4,16 +4,10 @@ import axios from "axios";
|
||||
import queryString from "query-string";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { SecretPathInput } from "@app/components/v2/SecretPathInput";
|
||||
import { useCreateIntegration, useGetWorkspaceById } from "@app/hooks/api";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardTitle,
|
||||
FormControl,
|
||||
Input,
|
||||
Select,
|
||||
SelectItem} from "../../../components/v2";
|
||||
import { Button, Card, CardTitle, FormControl, Select, SelectItem } from "../../../components/v2";
|
||||
import {
|
||||
useGetIntegrationAuthApps,
|
||||
useGetIntegrationAuthById
|
||||
@@ -27,7 +21,6 @@ const cloudflareEnvironments = [
|
||||
export default function CloudflarePagesIntegrationPage() {
|
||||
const router = useRouter();
|
||||
const { mutateAsync } = useCreateIntegration();
|
||||
|
||||
|
||||
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||
const [secretPath, setSecretPath] = useState("/");
|
||||
@@ -130,9 +123,10 @@ export default function CloudflarePagesIntegrationPage() {
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl label="Infisical Secret Path" className="mt-2 px-6">
|
||||
<Input
|
||||
<SecretPathInput
|
||||
value={secretPath}
|
||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||
onChange={(value) => setSecretPath(value)}
|
||||
environment={selectedSourceEnvironment}
|
||||
placeholder="Provide a path, default is /"
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
@@ -4,17 +4,10 @@ import axios from "axios";
|
||||
import queryString from "query-string";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { SecretPathInput } from "@app/components/v2/SecretPathInput";
|
||||
import { useCreateIntegration, useGetWorkspaceById } from "@app/hooks/api";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardTitle,
|
||||
FormControl,
|
||||
Input,
|
||||
Select,
|
||||
SelectItem
|
||||
} from "../../../components/v2";
|
||||
import { Button, Card, CardTitle, FormControl, Select, SelectItem } from "../../../components/v2";
|
||||
import {
|
||||
useGetIntegrationAuthApps,
|
||||
useGetIntegrationAuthById
|
||||
@@ -23,7 +16,6 @@ import {
|
||||
export default function CloudflareWorkersIntegrationPage() {
|
||||
const router = useRouter();
|
||||
const { mutateAsync } = useCreateIntegration();
|
||||
|
||||
|
||||
const { integrationAuthId } = queryString.parse(router.asPath.split("?")[1]);
|
||||
const { data: workspace } = useGetWorkspaceById(localStorage.getItem("projectData.id") ?? "");
|
||||
@@ -122,9 +114,10 @@ export default function CloudflareWorkersIntegrationPage() {
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl label="Infisical Secret Path" className="mt-2 px-6">
|
||||
<Input
|
||||
<SecretPathInput
|
||||
value={secretPath}
|
||||
onChange={(evt) => setSecretPath(evt.target.value)}
|
||||
onChange={(value) => setSecretPath(value)}
|
||||
environment={selectedSourceEnvironment}
|
||||
placeholder="Provide a path, default is /"
|
||||
/>
|
||||
</FormControl>
|
||||
|
||||
@@ -11,6 +11,7 @@ import { motion } from "framer-motion";
|
||||
import queryString from "query-string";
|
||||
import * as yup from "yup";
|
||||
|
||||
import { SecretPathInput } from "@app/components/v2/SecretPathInput";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useCreateIntegration } from "@app/hooks/api";
|
||||
|
||||
@@ -258,7 +259,11 @@ export default function GCPSecretManagerCreateIntegrationPage() {
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} placeholder="/" />
|
||||
<SecretPathInput
|
||||
{...field}
|
||||
environment={selectedSourceEnvironment}
|
||||
placeholder="/"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -11,6 +11,7 @@ import { motion } from "framer-motion";
|
||||
import queryString from "query-string";
|
||||
import * as yup from "yup";
|
||||
|
||||
import { SecretPathInput } from "@app/components/v2/SecretPathInput";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import { useCreateIntegration } from "@app/hooks/api";
|
||||
|
||||
@@ -268,7 +269,11 @@ export default function GitLabCreateIntegrationPage() {
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} placeholder="/" />
|
||||
<SecretPathInput
|
||||
{...field}
|
||||
placeholder="/"
|
||||
environment={selectedSourceEnvironment}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -9,15 +9,8 @@ import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import queryString from "query-string";
|
||||
import * as yup from "yup";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardTitle,
|
||||
FormControl,
|
||||
Input,
|
||||
Select,
|
||||
SelectItem
|
||||
} from "@app/components/v2";
|
||||
import { Button, Card, CardTitle, FormControl, Select, SelectItem } from "@app/components/v2";
|
||||
import { SecretPathInput } from "@app/components/v2/SecretPathInput";
|
||||
import { useCreateIntegration } from "@app/hooks/api";
|
||||
import {
|
||||
useGetIntegrationAuthApps,
|
||||
@@ -38,6 +31,7 @@ export default function HasuraCloudCreateIntegrationPage() {
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
watch,
|
||||
formState: { isSubmitting }
|
||||
} = useForm<FormData>({
|
||||
resolver: yupResolver(schema)
|
||||
@@ -51,6 +45,8 @@ export default function HasuraCloudCreateIntegrationPage() {
|
||||
(integrationAuthId as string) ?? ""
|
||||
);
|
||||
|
||||
const selectedSourceEnvironment = watch("sourceEnvironment");
|
||||
|
||||
const { data: integrationAuthApps, isLoading: isIntegrationAuthAppsLoading } =
|
||||
useGetIntegrationAuthApps({
|
||||
integrationAuthId: (integrationAuthId as string) ?? ""
|
||||
@@ -147,7 +143,7 @@ export default function HasuraCloudCreateIntegrationPage() {
|
||||
name="secretPath"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl label="Secrets Path" errorText={error?.message} isError={Boolean(error)}>
|
||||
<Input {...field} />
|
||||
<SecretPathInput {...field} environment={selectedSourceEnvironment} />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -17,19 +17,12 @@ import queryString from "query-string";
|
||||
// import { App, Pipeline } from "@app/hooks/api/integrationAuth/types";
|
||||
import * as yup from "yup";
|
||||
|
||||
import { SecretPathInput } from "@app/components/v2/SecretPathInput";
|
||||
// import { RadioGroup } from "@app/components/v2/RadioGroup";
|
||||
import { useCreateIntegration } from "@app/hooks/api";
|
||||
import { IntegrationSyncBehavior } from "@app/hooks/api/integrations/types";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
CardTitle,
|
||||
FormControl,
|
||||
Input,
|
||||
Select,
|
||||
SelectItem
|
||||
} from "../../../components/v2";
|
||||
import { Button, Card, CardTitle, FormControl, Select, SelectItem } from "../../../components/v2";
|
||||
import {
|
||||
useGetIntegrationAuthApps,
|
||||
useGetIntegrationAuthById
|
||||
@@ -280,7 +273,11 @@ export default function HerokuCreateIntegrationPage() {
|
||||
name="secretPath"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl label="Secrets Path" isError={Boolean(error)} errorText={error?.message}>
|
||||
<Input {...field} placeholder="/" />
|
||||
<SecretPathInput
|
||||
{...field}
|
||||
placeholder="/"
|
||||
environment={selectedSourceEnvironment}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -15,6 +15,7 @@ import { yupResolver } from "@hookform/resolvers/yup";
|
||||
import queryString from "query-string";
|
||||
import * as yup from "yup";
|
||||
|
||||
import { SecretPathInput } from "@app/components/v2/SecretPathInput";
|
||||
import { useCreateIntegration } from "@app/hooks/api";
|
||||
|
||||
import {
|
||||
@@ -22,7 +23,6 @@ import {
|
||||
Card,
|
||||
CardTitle,
|
||||
FormControl,
|
||||
Input,
|
||||
Select,
|
||||
SelectItem,
|
||||
Switch
|
||||
@@ -185,7 +185,11 @@ export default function RenderCreateIntegrationPage() {
|
||||
name="secretPath"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl label="Secrets Path" isError={Boolean(error)} errorText={error?.message}>
|
||||
<Input {...field} placeholder="/" />
|
||||
<SecretPathInput
|
||||
{...field}
|
||||
placeholder="/"
|
||||
environment={selectedSourceEnvironment}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -21,15 +21,17 @@ import { ProjectVersion } from "@app/hooks/api/workspace/types";
|
||||
|
||||
import { CloudIntegrationSection } from "./components/CloudIntegrationSection";
|
||||
import { FrameworkIntegrationSection } from "./components/FrameworkIntegrationSection";
|
||||
import { InfrastructureIntegrationSection } from "./components/InfrastructureIntegrationSection/InfrastructureIntegrationSection";
|
||||
import { IntegrationsSection } from "./components/IntegrationsSection";
|
||||
import { generateBotKey, redirectForProviderAuth } from "./IntegrationPage.utils";
|
||||
|
||||
type Props = {
|
||||
frameworkIntegrations: Array<{ name: string; slug: string; image: string; docsLink: string }>;
|
||||
infrastructureIntegrations: Array<{ name: string; slug: string; image: string; docsLink: string }>;
|
||||
};
|
||||
|
||||
export const IntegrationsPage = withProjectPermission(
|
||||
({ frameworkIntegrations }: Props) => {
|
||||
({ frameworkIntegrations, infrastructureIntegrations }: Props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
|
||||
@@ -228,6 +230,7 @@ export const IntegrationsPage = withProjectPermission(
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<FrameworkIntegrationSection frameworks={frameworkIntegrations} />
|
||||
<InfrastructureIntegrationSection integrations={infrastructureIntegrations} />
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
||||
+1
-1
@@ -109,7 +109,7 @@ export const CloudIntegrationSection = ({
|
||||
</div>
|
||||
{cloudIntegration.isAvailable &&
|
||||
Boolean(integrationAuths?.[cloudIntegration.slug]) && (
|
||||
<div className="absolute top-0 right-0 z-40 h-full">
|
||||
<div className="absolute top-0 right-0 z-30 h-full">
|
||||
<div className="relative h-full">
|
||||
<div className="absolute top-0 right-0 w-24 flex-row items-center overflow-hidden whitespace-nowrap rounded-tr-md rounded-bl-md bg-primary py-0.5 px-2 text-xs text-black opacity-80 transition-all duration-300 group-hover:w-0 group-hover:p-0">
|
||||
<FontAwesomeIcon icon={faCheck} className="mr-2 text-xs" />
|
||||
|
||||
+33
@@ -1,4 +1,7 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { faKeyboard } from "@fortawesome/free-regular-svg-icons";
|
||||
import { faComputer } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
|
||||
type Props = {
|
||||
frameworks: Array<{
|
||||
@@ -50,6 +53,36 @@ export const FrameworkIntegrationSection = ({ frameworks }: Props) => {
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
<a
|
||||
key="framework-integration-more"
|
||||
href="https://infisical.com/docs/cli/commands/run"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
className="relative flex h-32 cursor-pointer flex-row items-center justify-center rounded-md p-0.5 duration-200"
|
||||
>
|
||||
<div
|
||||
className="flex h-full w-full cursor-pointer flex-col items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-800 font-semibold text-gray-300 duration-200 hover:bg-mineshaft-700 group-hover:text-gray-200 px-1 text-xl w-full max-w-xs text-center"
|
||||
>
|
||||
<FontAwesomeIcon className="text-5xl mb-2 text-white/90" icon={faKeyboard} />
|
||||
<div className="h-2" />
|
||||
CLI
|
||||
</div>
|
||||
</a>
|
||||
<a
|
||||
key="framework-integration-more"
|
||||
href="https://infisical.com/docs/sdks/overview"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
className="relative flex h-32 cursor-pointer flex-row items-center justify-center rounded-md p-0.5 duration-200"
|
||||
>
|
||||
<div
|
||||
className="flex h-full w-full cursor-pointer flex-col items-center justify-center rounded-md border border-mineshaft-600 bg-mineshaft-800 font-semibold text-gray-300 duration-200 hover:bg-mineshaft-700 group-hover:text-gray-200 px-1 text-xl w-full max-w-xs text-center"
|
||||
>
|
||||
<FontAwesomeIcon className="text-5xl mb-1 text-white/90" icon={faComputer} />
|
||||
<div className="h-2" />
|
||||
SDKs
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
type Props = {
|
||||
integrations: Array<{
|
||||
name: string;
|
||||
image: string;
|
||||
slug: string;
|
||||
docsLink: string;
|
||||
}>;
|
||||
};
|
||||
|
||||
export const InfrastructureIntegrationSection = ({ integrations }: Props) => {
|
||||
const sortedIntegrations = integrations.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="mx-4 mt-12 mb-4 flex flex-col items-start justify-between px-2 text-xl">
|
||||
<h1 className="text-3xl font-semibold">Infrastructure Integrations</h1>
|
||||
<p className="text-base text-gray-400">Click on of the integration to read the documentation.</p>
|
||||
</div>
|
||||
<div className="mx-6 grid grid-cols-2 gap-4 lg:grid-cols-3 2xl:grid-cols-4">
|
||||
{sortedIntegrations.map((integration) => (
|
||||
<a
|
||||
key={`framework-integration-${integration.slug}`}
|
||||
href={integration.docsLink}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
className="relative w-full flex h-32 cursor-pointer flex-row items-center justify-center rounded-md p-0.5 duration-200"
|
||||
>
|
||||
<div
|
||||
onKeyDown={() => null}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
className="group relative w-full cursor-pointer duration-200 hover:bg-mineshaft-700 flex h-32 flex-row items-center rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4"
|
||||
key={integration?.name}
|
||||
>
|
||||
<img
|
||||
src={`/images/integrations/${integration.image}.png`}
|
||||
height={integration?.name ? 60 : 90}
|
||||
width={integration?.name ? 60 : 90}
|
||||
alt="integration logo"
|
||||
/>
|
||||
<div className="ml-4 max-w-xs text-xl font-semibold text-gray-300 duration-200 group-hover:text-gray-200">
|
||||
{integration?.name && integration.name}
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
+1
@@ -0,0 +1 @@
|
||||
export { InfrastructureIntegrationSection } from "./InfrastructureIntegrationSection";
|
||||
+3
-2
@@ -137,7 +137,7 @@ export const IntegrationsSection = ({
|
||||
"App"
|
||||
}
|
||||
/>
|
||||
<div className="min-w-[8rem] max-w-[12rem] overflow-clip text-ellipsis whitespace-nowrap rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
|
||||
<div className="min-w-[8rem] max-w-[12rem] overflow-scroll no-scrollbar no-scrollbar::-webkit-scrollbar whitespace-nowrap rounded-md border border-mineshaft-700 bg-mineshaft-900 px-3 py-2 font-inter text-sm text-bunker-200">
|
||||
{(integration.integration === "hashicorp-vault" &&
|
||||
`${integration.app} - path: ${integration.path}`) ||
|
||||
(integration.scope === "github-org" && `${integration.owner}`) ||
|
||||
@@ -217,11 +217,12 @@ export const IntegrationsSection = ({
|
||||
isOpen={popUp.deleteConfirmation.isOpen}
|
||||
title={`Are you sure want to remove ${
|
||||
(popUp?.deleteConfirmation.data as TIntegration)?.integration || " "
|
||||
} integration for ${(popUp?.deleteConfirmation.data as TIntegration)?.app || " "}?`}
|
||||
} integration for ${(popUp?.deleteConfirmation.data as TIntegration)?.app || "this project"}?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteConfirmation", isOpen)}
|
||||
deleteKey={
|
||||
(popUp?.deleteConfirmation?.data as TIntegration)?.app ||
|
||||
(popUp?.deleteConfirmation?.data as TIntegration)?.owner ||
|
||||
(popUp?.deleteConfirmation?.data as TIntegration)?.path ||
|
||||
""
|
||||
}
|
||||
onDeleteApproved={async () =>
|
||||
|
||||
+8
-1
@@ -34,6 +34,7 @@ import {
|
||||
Tag,
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
import { SecretPathInput } from "@app/components/v2/SecretPathInput";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
@@ -115,6 +116,7 @@ const SpecificPrivilegeSecretForm = ({
|
||||
});
|
||||
|
||||
const temporaryAccessField = privilegeForm.watch("temporaryAccess");
|
||||
const selectedEnvironmentSlug = privilegeForm.watch("environmentSlug");
|
||||
const isTemporary = temporaryAccessField?.isTemporary;
|
||||
const isExpired =
|
||||
temporaryAccessField.isTemporary &&
|
||||
@@ -220,7 +222,12 @@ const SpecificPrivilegeSecretForm = ({
|
||||
name="secretPath"
|
||||
render={({ field }) => (
|
||||
<FormControl label="Secret Path">
|
||||
<Input {...field} isDisabled={isMemberEditDisabled} className="w-48" />
|
||||
<SecretPathInput
|
||||
{...field}
|
||||
isDisabled={isMemberEditDisabled}
|
||||
environment={selectedEnvironmentSlug}
|
||||
containerClassName="w-48"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
+8
-1
@@ -34,6 +34,7 @@ import {
|
||||
Tag,
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
import { SecretPathInput } from "@app/components/v2/SecretPathInput";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
@@ -107,6 +108,7 @@ const SpecificPrivilegeSecretForm = ({ privilege }: { privilege: TProjectUserPri
|
||||
});
|
||||
|
||||
const temporaryAccessField = privilegeForm.watch("temporaryAccess");
|
||||
const selectedEnvironmentSlug = privilegeForm.watch("environmentSlug");
|
||||
const isTemporary = temporaryAccessField?.isTemporary;
|
||||
const isExpired =
|
||||
temporaryAccessField.isTemporary &&
|
||||
@@ -208,7 +210,12 @@ const SpecificPrivilegeSecretForm = ({ privilege }: { privilege: TProjectUserPri
|
||||
name="secretPath"
|
||||
render={({ field }) => (
|
||||
<FormControl label="Secret Path">
|
||||
<Input {...field} isDisabled={isMemberEditDisabled} className="w-48" />
|
||||
<SecretPathInput
|
||||
{...field}
|
||||
isDisabled={isMemberEditDisabled}
|
||||
containerClassName="w-48"
|
||||
environment={selectedEnvironmentSlug}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
+8
-2
@@ -20,6 +20,7 @@ import {
|
||||
Select,
|
||||
SelectItem
|
||||
} from "@app/components/v2";
|
||||
import { SecretPathInput } from "@app/components/v2/SecretPathInput";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { useCreateSecretApprovalPolicy, useUpdateSecretApprovalPolicy } from "@app/hooks/api";
|
||||
import { TSecretApprovalPolicy } from "@app/hooks/api/types";
|
||||
@@ -59,13 +60,14 @@ export const SecretPolicyForm = ({
|
||||
control,
|
||||
handleSubmit,
|
||||
reset,
|
||||
watch,
|
||||
formState: { isSubmitting }
|
||||
} = useForm<TFormSchema>({
|
||||
resolver: zodResolver(formSchema),
|
||||
values: editValues ? { ...editValues, environment: editValues.environment.slug } : undefined
|
||||
});
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
|
||||
const selectedEnvironment = watch("environment");
|
||||
|
||||
const environments = currentWorkspace?.environments || [];
|
||||
useEffect(() => {
|
||||
@@ -174,7 +176,11 @@ export const SecretPolicyForm = ({
|
||||
name="secretPath"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl label="Secret Path" isError={Boolean(error)} errorText={error?.message}>
|
||||
<Input {...field} value={field.value || ""} />
|
||||
<SecretPathInput
|
||||
{...field}
|
||||
value={field.value || ""}
|
||||
environment={selectedEnvironment}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
@@ -4,15 +4,8 @@ import { AxiosError } from "axios";
|
||||
import { z } from "zod";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
Input,
|
||||
Modal,
|
||||
ModalContent,
|
||||
Select,
|
||||
SelectItem
|
||||
} from "@app/components/v2";
|
||||
import { Button, FormControl, Modal, ModalContent, Select, SelectItem } from "@app/components/v2";
|
||||
import { SecretPathInput } from "@app/components/v2/SecretPathInput";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { useCreateSecretImport } from "@app/hooks/api";
|
||||
|
||||
@@ -50,12 +43,12 @@ export const CreateSecretImportForm = ({
|
||||
handleSubmit,
|
||||
control,
|
||||
reset,
|
||||
watch,
|
||||
formState: { isSubmitting }
|
||||
} = useForm<TFormSchema>({ resolver: zodResolver(typeSchema) });
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const environments = currentWorkspace?.environments || [];
|
||||
|
||||
|
||||
const selectedEnvironment = watch("environment");
|
||||
|
||||
const { mutateAsync: createSecretImport } = useCreateSecretImport();
|
||||
|
||||
@@ -130,7 +123,7 @@ export const CreateSecretImportForm = ({
|
||||
defaultValue="/"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl label="Secret Path" isError={Boolean(error)} errorText={error?.message}>
|
||||
<Input {...field} />
|
||||
<SecretPathInput {...field} environment={selectedEnvironment} />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
+14
-4
@@ -28,6 +28,7 @@ import {
|
||||
Skeleton,
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
import { SecretPathInput } from "@app/components/v2/SecretPathInput";
|
||||
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/context";
|
||||
import { useDebounce } from "@app/hooks";
|
||||
import { useGetProjectSecrets } from "@app/hooks/api";
|
||||
@@ -76,7 +77,6 @@ export const CopySecretsFromBoard = ({
|
||||
handleSubmit,
|
||||
control,
|
||||
watch,
|
||||
register,
|
||||
reset,
|
||||
setValue,
|
||||
formState: { isDirty }
|
||||
@@ -192,9 +192,19 @@ export const CopySecretsFromBoard = ({
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<FormControl label="Secret Path" className="flex-grow" isRequired>
|
||||
<Input {...register("secretPath")} placeholder="Provide a path, default is /" />
|
||||
</FormControl>
|
||||
<Controller
|
||||
control={control}
|
||||
name="secretPath"
|
||||
render={({ field }) => (
|
||||
<FormControl label="Secret Path" className="flex-grow" isRequired>
|
||||
<SecretPathInput
|
||||
{...field}
|
||||
placeholder="Provide a path, default is /"
|
||||
environment={selectedEnvSlug}
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div className="border-t border-mineshaft-600 pt-4">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
|
||||
+2
-1
@@ -3,6 +3,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { Button, FormControl, Input, Select, SelectItem, Spinner } from "@app/components/v2";
|
||||
import { SecretPathInput } from "@app/components/v2/SecretPathInput";
|
||||
import { useWorkspace } from "@app/context";
|
||||
import { useGetProjectSecrets, useGetUserWsKey } from "@app/hooks/api";
|
||||
|
||||
@@ -78,7 +79,7 @@ export const RotationOutputForm = ({ onSubmit, onCancel, outputSchema = {} }: Pr
|
||||
defaultValue="/"
|
||||
render={({ field }) => (
|
||||
<FormControl className="capitalize" label="Secret path">
|
||||
<Input {...field} />
|
||||
<SecretPathInput {...field} environment={environment} />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user