mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 09:26:47 +00:00
feat: fetch folder and secrets
This commit is contained in:
@@ -1,12 +1,16 @@
|
|||||||
/* eslint-disable react/no-danger */
|
/* eslint-disable react/no-danger */
|
||||||
import { forwardRef, TextareaHTMLAttributes } from "react";
|
import React, { forwardRef, TextareaHTMLAttributes, useState } from "react";
|
||||||
import { faChevronRight, faFolder, faKey, faRecycle } from "@fortawesome/free-solid-svg-icons";
|
import { faChevronRight, faFolder, faKey, faRecycle } from "@fortawesome/free-solid-svg-icons";
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from "tailwind-merge";
|
||||||
|
|
||||||
|
import { useWorkspace } from "@app/context";
|
||||||
import { useToggle } from "@app/hooks";
|
import { useToggle } from "@app/hooks";
|
||||||
|
import { useGetUserWsKey } from "@app/hooks/api";
|
||||||
|
import { fetchProjectFolders } from "@app/hooks/api/secretFolders/queries";
|
||||||
|
import { decryptSecrets, fetchProjectEncryptedSecrets } from "@app/hooks/api/secrets/queries";
|
||||||
|
|
||||||
const REGEX = /(\${([^}]+)})/g;
|
const REGEX_REFERENCE = /(\${([^}]*)})/g;
|
||||||
const replaceContentWithDot = (str: string) => {
|
const replaceContentWithDot = (str: string) => {
|
||||||
let finalStr = "";
|
let finalStr = "";
|
||||||
for (let i = 0; i < str.length; i += 1) {
|
for (let i = 0; i < str.length; i += 1) {
|
||||||
@@ -21,12 +25,8 @@ const syntaxHighlight = (content?: string | null, isVisible?: boolean) => {
|
|||||||
if (!content) return "EMPTY";
|
if (!content) return "EMPTY";
|
||||||
if (!isVisible) return replaceContentWithDot(content);
|
if (!isVisible) return replaceContentWithDot(content);
|
||||||
|
|
||||||
// List all the all the variable and the enviroments
|
|
||||||
// On Environment select list all the secret name and folder
|
|
||||||
//
|
|
||||||
|
|
||||||
let skipNext = false;
|
let skipNext = false;
|
||||||
const formatedContent = content.split(REGEX).flatMap((el, i) => {
|
const formatedContent = content.split(REGEX_REFERENCE).flatMap((el, i) => {
|
||||||
const isInterpolationSyntax = el.startsWith("${") && el.endsWith("}");
|
const isInterpolationSyntax = el.startsWith("${") && el.endsWith("}");
|
||||||
if (isInterpolationSyntax) {
|
if (isInterpolationSyntax) {
|
||||||
skipNext = true;
|
skipNext = true;
|
||||||
@@ -55,6 +55,14 @@ type Props = TextareaHTMLAttributes<HTMLTextAreaElement> & {
|
|||||||
isReadOnly?: boolean;
|
isReadOnly?: boolean;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
containerClassName?: string;
|
containerClassName?: string;
|
||||||
|
environment?: string;
|
||||||
|
secretPath?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type VariableType = {
|
||||||
|
name: string;
|
||||||
|
type: "folder" | "secret";
|
||||||
|
slug?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const commonClassName = "font-mono text-sm caret-white border-none outline-none w-full break-all";
|
const commonClassName = "font-mono text-sm caret-white border-none outline-none w-full break-all";
|
||||||
@@ -65,6 +73,113 @@ export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
|||||||
ref
|
ref
|
||||||
) => {
|
) => {
|
||||||
const [isSecretFocused, setIsSecretFocused] = useToggle();
|
const [isSecretFocused, setIsSecretFocused] = useToggle();
|
||||||
|
const [showReferencePopup, setShowReferencePopup] = useState<boolean>(false);
|
||||||
|
const { currentWorkspace } = useWorkspace();
|
||||||
|
const [listVariables, setListVariables] = useState<VariableType[]>([]);
|
||||||
|
|
||||||
|
const workspaceId = currentWorkspace?.id || "";
|
||||||
|
const { data: decryptFileKey } = useGetUserWsKey(workspaceId);
|
||||||
|
|
||||||
|
const { environment, secretPath } = props;
|
||||||
|
|
||||||
|
async function extractReference(refValue: string, refIndex: number) {
|
||||||
|
console.log({ refIndex });
|
||||||
|
const isNested = refValue.includes(".");
|
||||||
|
const currentListVariable: VariableType[] = [];
|
||||||
|
|
||||||
|
let currentEnvironment = environment;
|
||||||
|
let currentSecretPath = secretPath || "/";
|
||||||
|
|
||||||
|
if (isNested) {
|
||||||
|
const [envSlug, ...folderPaths] = refValue.split(".");
|
||||||
|
currentEnvironment = envSlug;
|
||||||
|
currentSecretPath = `/${folderPaths?.join("/")}` || "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!currentEnvironment || !decryptFileKey || !currentSecretPath || !currentWorkspace) {
|
||||||
|
setListVariables(currentListVariable);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log({ currentEnvironment, currentSecretPath });
|
||||||
|
const [encryptSecrets, folders] = await Promise.all([
|
||||||
|
fetchProjectEncryptedSecrets({
|
||||||
|
workspaceId,
|
||||||
|
environment: currentEnvironment,
|
||||||
|
secretPath: currentSecretPath
|
||||||
|
}),
|
||||||
|
// secret reference based on folder only support for nested reference that start with envs
|
||||||
|
isNested ? fetchProjectFolders(workspaceId, currentEnvironment, currentSecretPath) : []
|
||||||
|
]);
|
||||||
|
|
||||||
|
folders?.forEach((folder) => {
|
||||||
|
currentListVariable.unshift({ name: folder.name, type: "folder" });
|
||||||
|
});
|
||||||
|
|
||||||
|
const secrets = decryptSecrets(encryptSecrets, decryptFileKey);
|
||||||
|
|
||||||
|
secrets?.forEach((secret) => {
|
||||||
|
currentListVariable.unshift({ name: secret.key, type: "secret" });
|
||||||
|
});
|
||||||
|
|
||||||
|
// get list of secrets, folder name and envs
|
||||||
|
// On env select get list of secrets
|
||||||
|
// on env select show list of secrets and folder
|
||||||
|
// on env or folder select replace the text and update the caret?
|
||||||
|
// fetch secrets based on current base environment and the path
|
||||||
|
|
||||||
|
setListVariables(currentListVariable);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleVariablePopup(element: HTMLTextAreaElement) {
|
||||||
|
const { selectionStart, selectionEnd, value: elValue } = element;
|
||||||
|
if (selectionStart !== selectionEnd || selectionStart === 0) {
|
||||||
|
setShowReferencePopup(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let match = null;
|
||||||
|
for (
|
||||||
|
let matches = REGEX_REFERENCE.exec(elValue);
|
||||||
|
matches !== null;
|
||||||
|
matches = REGEX_REFERENCE.exec(elValue)
|
||||||
|
) {
|
||||||
|
if (matches.index <= selectionStart && REGEX_REFERENCE.lastIndex >= selectionStart) {
|
||||||
|
match = matches?.[2];
|
||||||
|
extractReference(match, matches.index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setShowReferencePopup(Boolean(match));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeyDown(event: React.KeyboardEvent<HTMLTextAreaElement>) {
|
||||||
|
// On Key up or down if the popup is open ignore it
|
||||||
|
if ((showReferencePopup && event.key === "ArrowUp") || event.key === "ArrowDown") {
|
||||||
|
event.preventDefault();
|
||||||
|
// todo: point up or down in the variable popup
|
||||||
|
// return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKeyUp(event: React.KeyboardEvent<HTMLTextAreaElement>) {
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
setShowReferencePopup(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// On Key up or down if the popup is open ignore it
|
||||||
|
if ((showReferencePopup && event.key === "ArrowUp") || event.key === "ArrowDown") {
|
||||||
|
event.preventDefault();
|
||||||
|
// todo: point up or down in the variable popup
|
||||||
|
// return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleVariablePopup(event.currentTarget);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMouseClick(event: React.MouseEvent<HTMLTextAreaElement, MouseEvent>) {
|
||||||
|
handleVariablePopup(event.currentTarget);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -87,11 +202,14 @@ export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
className={`absolute inset-0 block h-full resize-none overflow-hidden bg-transparent text-transparent no-scrollbar focus:border-0 ${commonClassName}`}
|
className={`absolute inset-0 block h-full resize-none overflow-hidden bg-transparent text-transparent no-scrollbar focus:border-0 ${commonClassName}`}
|
||||||
onFocus={() => setIsSecretFocused.on()}
|
onFocus={() => setIsSecretFocused.on()}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
onKeyUp={handleKeyUp}
|
||||||
|
onClick={handleMouseClick}
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
onBlur={(evt) => {
|
onBlur={(evt) => {
|
||||||
onBlur?.(evt);
|
onBlur?.(evt);
|
||||||
setIsSecretFocused.off();
|
if (!showReferencePopup) setIsSecretFocused.off();
|
||||||
}}
|
}}
|
||||||
value={value || ""}
|
value={value || ""}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -99,61 +217,62 @@ export const SecretInput = forwardRef<HTMLTextAreaElement, Props>(
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{isSecretFocused && (
|
{showReferencePopup && isSecretFocused && (
|
||||||
<div className="absolute z-10 mt-2 w-60 rounded-md border border-mineshaft-600 bg-mineshaft-700 text-sm text-bunker-200">
|
<div className="absolute z-10 mt-2 w-60 rounded-md border border-mineshaft-600 bg-mineshaft-700 text-sm text-bunker-200">
|
||||||
<div className="h-full w-full flex-col items-center justify-center rounded-md py-4 text-white">
|
<div className="max-w-60 h-full w-full flex-col items-center justify-center rounded-md py-4 text-white">
|
||||||
{[
|
{listVariables.map((e, i) => {
|
||||||
{ name: "SECRET NAME", type: "secret" },
|
return (
|
||||||
{ name: "Folder", type: "folder" },
|
<div
|
||||||
{ name: "Development", type: "environment" }
|
className="flex items-center justify-between border-b border-mineshaft-600 px-2 py-1 last:border-b-0"
|
||||||
].map((e, i) => {
|
key={`key-${i + 1}`}
|
||||||
return (
|
>
|
||||||
|
{e.type === "folder" && (
|
||||||
|
<>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="flex items-center text-yellow-700">
|
||||||
|
<FontAwesomeIcon icon={faFolder} />
|
||||||
|
</div>
|
||||||
|
<div className="w-48 truncate">{e.name}</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center text-bunker-200">
|
||||||
|
<FontAwesomeIcon icon={faChevronRight} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{e.type === "secret" && (
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<div className="flex items-center text-yellow-700">
|
||||||
|
<FontAwesomeIcon icon={faKey} />
|
||||||
|
</div>
|
||||||
|
<div className="w-48 truncate">{e.name}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
|
||||||
|
<div className="flex w-full justify-center gap-2">All Secrets</div>
|
||||||
|
|
||||||
|
{currentWorkspace?.environments.map((env, i) => (
|
||||||
<div
|
<div
|
||||||
className="flex items-center justify-between border-b border-mineshaft-600 px-2 py-1 last:border-b-0"
|
className="flex items-center justify-between border-b border-mineshaft-600 px-2 py-1 last:border-b-0"
|
||||||
key={`key-${i + 1}`}
|
key={`key-${i + 1}`}
|
||||||
>
|
>
|
||||||
{e.type === "folder" && (
|
<div className="flex gap-2">
|
||||||
<>
|
<div className="flex items-center text-yellow-700">
|
||||||
<div className="flex gap-2">
|
<FontAwesomeIcon icon={faRecycle} />
|
||||||
<div className="flex items-center text-yellow-700">
|
|
||||||
<FontAwesomeIcon icon={faFolder} />
|
|
||||||
</div>
|
|
||||||
<div>{e.name}</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center text-bunker-200">
|
|
||||||
<FontAwesomeIcon icon={faChevronRight} />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{e.type === "environment" && (
|
|
||||||
<>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<div className="flex items-center text-yellow-700">
|
|
||||||
<FontAwesomeIcon icon={faRecycle} />
|
|
||||||
</div>
|
|
||||||
<div>{e.name}</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center text-bunker-200">
|
|
||||||
<FontAwesomeIcon icon={faChevronRight} />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{e.type === "secret" && (
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<div className="flex items-center text-yellow-700">
|
|
||||||
<FontAwesomeIcon icon={faKey} />
|
|
||||||
</div>
|
|
||||||
<div>{e.name}</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
<div className="w-48 truncate">{env.name}</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center text-bunker-200">
|
||||||
|
<FontAwesomeIcon icon={faChevronRight} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
))}
|
||||||
})}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export const folderQueryKeys = {
|
|||||||
["secret-folders", { projectId, environment, path }] as const
|
["secret-folders", { projectId, environment, path }] as const
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchProjectFolders = async (workspaceId: string, environment: string, path = "/") => {
|
export const fetchProjectFolders = async (workspaceId: string, environment: string, path = "/") => {
|
||||||
const { data } = await apiRequest.get<{ folders: TSecretFolder[] }>("/api/v1/folders", {
|
const { data } = await apiRequest.get<{ folders: TSecretFolder[] }>("/api/v1/folders", {
|
||||||
params: {
|
params: {
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ export const decryptSecrets = (
|
|||||||
return secrets;
|
return secrets;
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchProjectEncryptedSecrets = async ({
|
export const fetchProjectEncryptedSecrets = async ({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
environment,
|
environment,
|
||||||
secretPath
|
secretPath
|
||||||
|
|||||||
@@ -105,6 +105,8 @@ export const CreateSecretForm = ({
|
|||||||
>
|
>
|
||||||
<SecretInput
|
<SecretInput
|
||||||
{...field}
|
{...field}
|
||||||
|
environment={environment}
|
||||||
|
secretPath={secretPath}
|
||||||
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-mineshaft-900 px-2 py-1.5"
|
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-mineshaft-900 px-2 py-1.5"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|||||||
+6
-1
@@ -172,7 +172,12 @@ export const SecretImportItem = ({
|
|||||||
{key}
|
{key}
|
||||||
</td>
|
</td>
|
||||||
<td className="h-10" style={{ padding: "0.25rem 1rem" }}>
|
<td className="h-10" style={{ padding: "0.25rem 1rem" }}>
|
||||||
<SecretInput value={value} isReadOnly />
|
<SecretInput
|
||||||
|
value={value}
|
||||||
|
isReadOnly
|
||||||
|
environment={overriden?.env}
|
||||||
|
secretPath={overriden?.secretPath}
|
||||||
|
/>
|
||||||
</td>
|
</td>
|
||||||
<td className="h-10" style={{ padding: "0.25rem 1rem" }}>
|
<td className="h-10" style={{ padding: "0.25rem 1rem" }}>
|
||||||
<EnvFolderIcon env={overriden?.env} secretPath={overriden?.secretPath} />
|
<EnvFolderIcon env={overriden?.env} secretPath={overriden?.secretPath} />
|
||||||
|
|||||||
@@ -206,6 +206,8 @@ export const SecretDetailSidebar = ({
|
|||||||
<FormControl label="Value">
|
<FormControl label="Value">
|
||||||
<SecretInput
|
<SecretInput
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
|
environment={environment}
|
||||||
|
secretPath={secretPath}
|
||||||
key="secret-value"
|
key="secret-value"
|
||||||
isDisabled={isOverridden || !isAllowed}
|
isDisabled={isOverridden || !isAllowed}
|
||||||
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-bunker-800 px-2 py-1.5"
|
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-bunker-800 px-2 py-1.5"
|
||||||
@@ -242,6 +244,8 @@ export const SecretDetailSidebar = ({
|
|||||||
<FormControl label="Value Override">
|
<FormControl label="Value Override">
|
||||||
<SecretInput
|
<SecretInput
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
|
environment={environment}
|
||||||
|
secretPath={secretPath}
|
||||||
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-bunker-800 px-2 py-1.5"
|
containerClassName="text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-bunker-800 px-2 py-1.5"
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -267,6 +267,8 @@ export const SecretItem = memo(
|
|||||||
key="value-overriden"
|
key="value-overriden"
|
||||||
isVisible={isVisible}
|
isVisible={isVisible}
|
||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
|
environment={environment}
|
||||||
|
secretPath={secretPath}
|
||||||
{...field}
|
{...field}
|
||||||
containerClassName="py-1.5 rounded-md transition-all group-hover:mr-2"
|
containerClassName="py-1.5 rounded-md transition-all group-hover:mr-2"
|
||||||
/>
|
/>
|
||||||
@@ -282,6 +284,8 @@ export const SecretItem = memo(
|
|||||||
isReadOnly={isReadOnly}
|
isReadOnly={isReadOnly}
|
||||||
key="secret-value"
|
key="secret-value"
|
||||||
isVisible={isVisible}
|
isVisible={isVisible}
|
||||||
|
environment={environment}
|
||||||
|
secretPath={secretPath}
|
||||||
{...field}
|
{...field}
|
||||||
containerClassName="py-1.5 rounded-md transition-all group-hover:mr-2"
|
containerClassName="py-1.5 rounded-md transition-all group-hover:mr-2"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -120,7 +120,9 @@ export const SecretItem = ({ mode, preSecret, postSecret }: Props) => {
|
|||||||
<Td className="border-r border-mineshaft-600">Value</Td>
|
<Td className="border-r border-mineshaft-600">Value</Td>
|
||||||
{isModified && (
|
{isModified && (
|
||||||
<Td className="border-r border-mineshaft-600">
|
<Td className="border-r border-mineshaft-600">
|
||||||
<SecretInput value={preSecret?.value} />
|
<SecretInput
|
||||||
|
value={preSecret?.value}
|
||||||
|
/>
|
||||||
</Td>
|
</Td>
|
||||||
)}
|
)}
|
||||||
<Td>
|
<Td>
|
||||||
|
|||||||
+1
-1
@@ -93,7 +93,7 @@ export const SecretEditRow = ({
|
|||||||
control={control}
|
control={control}
|
||||||
name="value"
|
name="value"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<SecretInput {...field} value={field.value as string} isVisible={isVisible} />
|
<SecretInput {...field} value={field.value as string} isVisible={isVisible} secretPath={secretPath} environment={environment} />
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+4
-1
@@ -102,7 +102,8 @@ export const CreateRotationForm = ({
|
|||||||
))}
|
))}
|
||||||
</Stepper>
|
</Stepper>
|
||||||
<AnimatePresence exitBeforeEnter>
|
<AnimatePresence exitBeforeEnter>
|
||||||
{wizardStep === 0 && (
|
{/* TODO: Check this before merge */}
|
||||||
|
{wizardStep === 0 && wizardData.current.output && (
|
||||||
<motion.div
|
<motion.div
|
||||||
key="input-step"
|
key="input-step"
|
||||||
transition={{ duration: 0.1 }}
|
transition={{ duration: 0.1 }}
|
||||||
@@ -117,6 +118,8 @@ export const CreateRotationForm = ({
|
|||||||
setWizardStep((state) => state + 1);
|
setWizardStep((state) => state + 1);
|
||||||
}}
|
}}
|
||||||
inputSchema={provider.template?.inputs || {}}
|
inputSchema={provider.template?.inputs || {}}
|
||||||
|
secretPath={wizardData.current.output.secretPath}
|
||||||
|
environment={wizardData.current.output.environment}
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
+4
-1
@@ -13,11 +13,13 @@ type Props = {
|
|||||||
properties: Record<string, { type: string; desc?: string; default?: string }>;
|
properties: Record<string, { type: string; desc?: string; default?: string }>;
|
||||||
required: string[];
|
required: string[];
|
||||||
};
|
};
|
||||||
|
secretPath: string;
|
||||||
|
environment: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const formSchema = z.record(z.string().trim().optional());
|
const formSchema = z.record(z.string().trim().optional());
|
||||||
|
|
||||||
export const RotationInputForm = ({ onSubmit, onCancel, inputSchema }: Props) => {
|
export const RotationInputForm = ({ onSubmit, onCancel, inputSchema, secretPath, environment }: Props) => {
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
@@ -60,6 +62,7 @@ export const RotationInputForm = ({ onSubmit, onCancel, inputSchema }: Props) =>
|
|||||||
{...field}
|
{...field}
|
||||||
containerClassName="normal-case text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-bunker-800 px-2 py-1.5"
|
containerClassName="normal-case text-bunker-300 hover:border-primary-400/50 border border-mineshaft-600 bg-bunker-800 px-2 py-1.5"
|
||||||
required={inputSchema.required.includes(inputName)}
|
required={inputSchema.required.includes(inputName)}
|
||||||
|
secretPath={secretPath} environment={environment}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user