Merge pull request #3241 from Infisical/feat/addDynamicSecretsToOverview

Add dynamic secrets modal form on secrets overview page
This commit is contained in:
carlosmonastyrski
2025-03-17 13:14:22 -03:00
committed by GitHub
18 changed files with 753 additions and 132 deletions
@@ -19,6 +19,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Link, useNavigate, useRouter, useSearch } from "@tanstack/react-router"; import { Link, useNavigate, useRouter, useSearch } from "@tanstack/react-router";
import { twMerge } from "tailwind-merge"; import { twMerge } from "tailwind-merge";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { ProjectPermissionCan } from "@app/components/permissions"; import { ProjectPermissionCan } from "@app/components/permissions";
import { import {
@@ -49,8 +50,10 @@ import {
import { ROUTE_PATHS } from "@app/const/routes"; import { ROUTE_PATHS } from "@app/const/routes";
import { import {
ProjectPermissionActions, ProjectPermissionActions,
ProjectPermissionDynamicSecretActions,
ProjectPermissionSub, ProjectPermissionSub,
useProjectPermission, useProjectPermission,
useSubscription,
useWorkspace useWorkspace
} from "@app/context"; } from "@app/context";
import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks"; import { useDebounce, usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
@@ -71,6 +74,7 @@ import { SecretType, SecretV3RawSanitized, TSecretFolder } from "@app/hooks/api/
import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types"; import { ProjectType, ProjectVersion } from "@app/hooks/api/workspace/types";
import { useDynamicSecretOverview, useFolderOverview, useSecretOverview } from "@app/hooks/utils"; import { useDynamicSecretOverview, useFolderOverview, useSecretOverview } from "@app/hooks/utils";
import { CreateDynamicSecretForm } from "../SecretDashboardPage/components/ActionBar/CreateDynamicSecretForm";
import { FolderForm } from "../SecretDashboardPage/components/ActionBar/FolderForm"; import { FolderForm } from "../SecretDashboardPage/components/ActionBar/FolderForm";
import { CreateSecretForm } from "./components/CreateSecretForm"; import { CreateSecretForm } from "./components/CreateSecretForm";
import { FolderBreadCrumbs } from "./components/FolderBreadCrumbs"; import { FolderBreadCrumbs } from "./components/FolderBreadCrumbs";
@@ -131,6 +135,7 @@ export const OverviewPage = () => {
const [searchFilter, setSearchFilter] = useState(""); const [searchFilter, setSearchFilter] = useState("");
const [debouncedSearchFilter, setDebouncedSearchFilter] = useDebounce(searchFilter); const [debouncedSearchFilter, setDebouncedSearchFilter] = useDebounce(searchFilter);
const secretPath = (routerSearch?.secretPath as string) || "/"; const secretPath = (routerSearch?.secretPath as string) || "/";
const { subscription } = useSubscription();
const [filter, setFilter] = useState<Filter>(DEFAULT_FILTER_STATE); const [filter, setFilter] = useState<Filter>(DEFAULT_FILTER_STATE);
const [filterHistory, setFilterHistory] = useState< const [filterHistory, setFilterHistory] = useState<
@@ -178,6 +183,15 @@ export const OverviewPage = () => {
}, []); }, []);
const userAvailableEnvs = currentWorkspace?.environments || []; const userAvailableEnvs = currentWorkspace?.environments || [];
const userAvailableDynamicSecretEnvs = userAvailableEnvs.filter((env) =>
permission.can(
ProjectPermissionDynamicSecretActions.CreateRootCredential,
subject(ProjectPermissionSub.DynamicSecrets, {
environment: env.slug,
secretPath
})
)
);
const [visibleEnvs, setVisibleEnvs] = useState(userAvailableEnvs); const [visibleEnvs, setVisibleEnvs] = useState(userAvailableEnvs);
@@ -249,7 +263,9 @@ export const OverviewPage = () => {
"addSecretsInAllEnvs", "addSecretsInAllEnvs",
"addFolder", "addFolder",
"misc", "misc",
"updateFolder" "updateFolder",
"addDynamicSecret",
"upgradePlan"
] as const); ] as const);
const handleFolderCreate = async (folderName: string, description: string | null) => { const handleFolderCreate = async (folderName: string, description: string | null) => {
@@ -851,20 +867,43 @@ export const OverviewPage = () => {
> >
{(isAllowed) => ( {(isAllowed) => (
<Button <Button
leftIcon={<FontAwesomeIcon icon={faFolderPlus} />} leftIcon={<FontAwesomeIcon icon={faFolderPlus} className="pr-2" />}
onClick={() => { onClick={() => {
handlePopUpOpen("addFolder"); handlePopUpOpen("addFolder");
handlePopUpClose("misc"); handlePopUpClose("misc");
}} }}
isDisabled={!isAllowed} isDisabled={!isAllowed}
variant="outline_bg" variant="outline_bg"
className="h-10" className="h-10 text-left"
isFullWidth isFullWidth
> >
Add Folder Add Folder
</Button> </Button>
)} )}
</ProjectPermissionCan> </ProjectPermissionCan>
<Tooltip
content={
userAvailableDynamicSecretEnvs.length === 0 ? "Access restricted" : ""
}
>
<Button
leftIcon={<FontAwesomeIcon icon={faFingerprint} className="pr-2" />}
onClick={() => {
if (subscription?.dynamicSecret) {
handlePopUpOpen("addDynamicSecret");
handlePopUpClose("misc");
return;
}
handlePopUpOpen("upgradePlan");
}}
isDisabled={userAvailableDynamicSecretEnvs.length === 0}
variant="outline_bg"
className="h-10 text-left"
isFullWidth
>
Add Dynamic Secret
</Button>
</Tooltip>
</div> </div>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
@@ -1170,6 +1209,24 @@ export const OverviewPage = () => {
/> />
</ModalContent> </ModalContent>
</Modal> </Modal>
<CreateDynamicSecretForm
isOpen={popUp.addDynamicSecret.isOpen}
onToggle={(isOpen) => handlePopUpToggle("addDynamicSecret", isOpen)}
projectSlug={projectSlug}
environments={userAvailableDynamicSecretEnvs}
secretPath={secretPath}
/>
{subscription && (
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text={
subscription.slug === null
? "You can perform this action under an Enterprise license"
: "You can perform this action if you switch to Infisical's Team plan"
}
/>
)}
</div> </div>
); );
}; };
@@ -655,8 +655,9 @@ export const ActionBar = ({
isOpen={popUp.addDynamicSecret.isOpen} isOpen={popUp.addDynamicSecret.isOpen}
onToggle={(isOpen) => handlePopUpToggle("addDynamicSecret", isOpen)} onToggle={(isOpen) => handlePopUpToggle("addDynamicSecret", isOpen)}
projectSlug={projectSlug} projectSlug={projectSlug}
environment={environment} environments={[{ slug: environment, name: environment, id: "not-used" }]}
secretPath={secretPath} secretPath={secretPath}
isSingleEnvironmentMode
/> />
<Modal <Modal
isOpen={popUp.addFolder.isOpen} isOpen={popUp.addFolder.isOpen}
@@ -11,6 +11,7 @@ import {
AccordionItem, AccordionItem,
AccordionTrigger, AccordionTrigger,
Button, Button,
FilterableSelect,
FormControl, FormControl,
Input, Input,
SecretInput, SecretInput,
@@ -18,6 +19,7 @@ import {
} from "@app/components/v2"; } from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api"; import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({ const formSchema = z.object({
provider: z.object({ provider: z.object({
@@ -50,7 +52,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000) if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}), }),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase") name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
environment: z.object({ name: z.string(), slug: z.string() })
}); });
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -59,15 +62,17 @@ type Props = {
onCancel: () => void; onCancel: () => void;
secretPath: string; secretPath: string;
projectSlug: string; projectSlug: string;
environment: string; environments: WorkspaceEnv[];
isSingleEnvironmentMode?: boolean;
}; };
export const AwsElastiCacheInputForm = ({ export const AwsElastiCacheInputForm = ({
onCompleted, onCompleted,
onCancel, onCancel,
environment, environments,
secretPath, secretPath,
projectSlug projectSlug,
isSingleEnvironmentMode
}: Props) => { }: Props) => {
const { const {
control, control,
@@ -87,13 +92,20 @@ export const AwsElastiCacheInputForm = ({
revocationStatement: `{ revocationStatement: `{
"UserId": "{{username}}" "UserId": "{{username}}"
}` }`
} },
environment: isSingleEnvironmentMode ? environments[0] : undefined
} }
}); });
const createDynamicSecret = useCreateDynamicSecret(); const createDynamicSecret = useCreateDynamicSecret();
const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { const handleCreateDynamicSecret = async ({
name,
maxTTL,
provider,
defaultTTL,
environment
}: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
try { try {
@@ -104,7 +116,7 @@ export const AwsElastiCacheInputForm = ({
path: secretPath, path: secretPath,
defaultTTL, defaultTTL,
projectSlug, projectSlug,
environmentSlug: environment environmentSlug: environment.slug
}); });
onCompleted(); onCompleted();
} catch { } catch {
@@ -299,6 +311,28 @@ export const AwsElastiCacheInputForm = ({
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
</Accordion> </Accordion>
{!isSingleEnvironmentMode && (
<Controller
control={control}
name="environment"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
label="Environment"
isError={Boolean(error)}
errorText={error?.message}
>
<FilterableSelect
options={environments}
value={value}
onChange={onChange}
placeholder="Select the environment to create secret in..."
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.slug}
/>
</FormControl>
)}
/>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -5,9 +5,10 @@ import { z } from "zod";
import { TtlFormLabel } from "@app/components/features"; import { TtlFormLabel } from "@app/components/features";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { Button, FormControl, Input, TextArea } from "@app/components/v2"; import { Button, FilterableSelect, FormControl, Input, TextArea } from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api"; import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({ const formSchema = z.object({
provider: z.object({ provider: z.object({
@@ -40,7 +41,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000) if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}), }),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase") name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
environment: z.object({ name: z.string(), slug: z.string() })
}); });
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -49,29 +51,41 @@ type Props = {
onCancel: () => void; onCancel: () => void;
secretPath: string; secretPath: string;
projectSlug: string; projectSlug: string;
environment: string; environments: WorkspaceEnv[];
isSingleEnvironmentMode?: boolean;
}; };
export const AwsIamInputForm = ({ export const AwsIamInputForm = ({
onCompleted, onCompleted,
onCancel, onCancel,
environment, environments,
secretPath, secretPath,
projectSlug projectSlug,
isSingleEnvironmentMode
}: Props) => { }: Props) => {
const { const {
control, control,
formState: { isSubmitting }, formState: { isSubmitting },
handleSubmit handleSubmit
} = useForm<TForm>({ } = useForm<TForm>({
resolver: zodResolver(formSchema) resolver: zodResolver(formSchema),
defaultValues: {
environment: isSingleEnvironmentMode ? environments[0] : undefined
}
}); });
const createDynamicSecret = useCreateDynamicSecret(); const createDynamicSecret = useCreateDynamicSecret();
const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { const handleCreateDynamicSecret = async ({
name,
maxTTL,
provider,
defaultTTL,
environment
}: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
try { try {
await createDynamicSecret.mutateAsync({ await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.AwsIam, inputs: provider }, provider: { type: DynamicSecretProviders.AwsIam, inputs: provider },
@@ -80,7 +94,7 @@ export const AwsIamInputForm = ({
path: secretPath, path: secretPath,
defaultTTL, defaultTTL,
projectSlug, projectSlug,
environmentSlug: environment environmentSlug: environment.slug
}); });
onCompleted(); onCompleted();
} catch { } catch {
@@ -286,6 +300,29 @@ export const AwsIamInputForm = ({
</FormControl> </FormControl>
)} )}
/> />
{!isSingleEnvironmentMode && (
<Controller
control={control}
name="environment"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
label="Environment"
isError={Boolean(error)}
errorText={error?.message}
>
<FilterableSelect
options={environments}
value={value}
onChange={onChange}
placeholder="Select the environment to create secret in..."
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
/>
</FormControl>
)}
/>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -12,7 +12,7 @@ import { z } from "zod";
import { TtlFormLabel } from "@app/components/features"; import { TtlFormLabel } from "@app/components/features";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { Button, FormControl, Input } from "@app/components/v2"; import { Button, FilterableSelect, FormControl, Input } from "@app/components/v2";
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@@ -23,6 +23,7 @@ import { Tooltip } from "@app/components/v2/Tooltip";
import { useCreateDynamicSecret } from "@app/hooks/api"; import { useCreateDynamicSecret } from "@app/hooks/api";
import { useGetDynamicSecretProviderData } from "@app/hooks/api/dynamicSecret/queries"; import { useGetDynamicSecretProviderData } from "@app/hooks/api/dynamicSecret/queries";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({ const formSchema = z.object({
selectedUsers: z.array( selectedUsers: z.array(
@@ -60,7 +61,8 @@ const formSchema = z.object({
name: z name: z
.string() .string()
.min(1) .min(1)
.refine((val) => val.toLowerCase() === val, "Must be lowercase") .refine((val) => val.toLowerCase() === val, "Must be lowercase"),
environment: z.object({ name: z.string(), slug: z.string() })
}); });
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -69,15 +71,17 @@ type Props = {
onCancel: () => void; onCancel: () => void;
secretPath: string; secretPath: string;
projectSlug: string; projectSlug: string;
environment: string; environments: WorkspaceEnv[];
isSingleEnvironmentMode?: boolean;
}; };
export const AzureEntraIdInputForm = ({ export const AzureEntraIdInputForm = ({
onCompleted, onCompleted,
onCancel, onCancel,
environment, environments,
secretPath, secretPath,
projectSlug projectSlug,
isSingleEnvironmentMode
}: Props) => { }: Props) => {
const { const {
control, control,
@@ -85,7 +89,10 @@ export const AzureEntraIdInputForm = ({
watch, watch,
handleSubmit handleSubmit
} = useForm<TForm>({ } = useForm<TForm>({
resolver: zodResolver(formSchema) resolver: zodResolver(formSchema),
defaultValues: {
environment: isSingleEnvironmentMode ? environments[0] : undefined
}
}); });
const tenantId = watch("provider.tenantId"); const tenantId = watch("provider.tenantId");
const applicationId = watch("provider.applicationId"); const applicationId = watch("provider.applicationId");
@@ -107,7 +114,8 @@ export const AzureEntraIdInputForm = ({
selectedUsers, selectedUsers,
provider, provider,
maxTTL, maxTTL,
defaultTTL defaultTTL,
environment
}: TForm) => { }: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
@@ -129,7 +137,7 @@ export const AzureEntraIdInputForm = ({
path: secretPath, path: secretPath,
defaultTTL, defaultTTL,
projectSlug, projectSlug,
environmentSlug: environment environmentSlug: environment.slug
}); });
}); });
onCompleted(); onCompleted();
@@ -373,6 +381,29 @@ export const AzureEntraIdInputForm = ({
</div> </div>
</div> </div>
</div> </div>
{!isSingleEnvironmentMode && (
<Controller
control={control}
name="environment"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
label="Environment"
isError={Boolean(error)}
errorText={error?.message}
>
<FilterableSelect
options={environments}
value={value}
onChange={onChange}
placeholder="Select the environment to create secret in..."
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
/>
</FormControl>
)}
/>
)}
</div> </div>
<div className="mt-4 flex items-center space-x-4"> <div className="mt-4 flex items-center space-x-4">
<Button type="submit" isLoading={isSubmitting} isDisabled={isLoading || isError}> <Button type="submit" isLoading={isSubmitting} isDisabled={isLoading || isError}>
@@ -11,6 +11,7 @@ import {
AccordionItem, AccordionItem,
AccordionTrigger, AccordionTrigger,
Button, Button,
FilterableSelect,
FormControl, FormControl,
Input, Input,
SecretInput, SecretInput,
@@ -18,6 +19,7 @@ import {
} from "@app/components/v2"; } from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api"; import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({ const formSchema = z.object({
provider: z.object({ provider: z.object({
@@ -52,7 +54,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000) if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}), }),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase") name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
environment: z.object({ name: z.string(), slug: z.string() })
}); });
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -61,7 +64,8 @@ type Props = {
onCancel: () => void; onCancel: () => void;
secretPath: string; secretPath: string;
projectSlug: string; projectSlug: string;
environment: string; environments: WorkspaceEnv[];
isSingleEnvironmentMode?: boolean;
}; };
const getSqlStatements = () => { const getSqlStatements = () => {
@@ -76,9 +80,10 @@ const getSqlStatements = () => {
export const CassandraInputForm = ({ export const CassandraInputForm = ({
onCompleted, onCompleted,
onCancel, onCancel,
environment, environments,
secretPath, secretPath,
projectSlug projectSlug,
isSingleEnvironmentMode
}: Props) => { }: Props) => {
const { const {
control, control,
@@ -87,15 +92,23 @@ export const CassandraInputForm = ({
} = useForm<TForm>({ } = useForm<TForm>({
resolver: zodResolver(formSchema), resolver: zodResolver(formSchema),
defaultValues: { defaultValues: {
provider: getSqlStatements() provider: getSqlStatements(),
environment: isSingleEnvironmentMode ? environments[0] : undefined
} }
}); });
const createDynamicSecret = useCreateDynamicSecret(); const createDynamicSecret = useCreateDynamicSecret();
const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { const handleCreateDynamicSecret = async ({
name,
maxTTL,
provider,
defaultTTL,
environment
}: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
try { try {
await createDynamicSecret.mutateAsync({ await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.Cassandra, inputs: provider }, provider: { type: DynamicSecretProviders.Cassandra, inputs: provider },
@@ -104,7 +117,7 @@ export const CassandraInputForm = ({
path: secretPath, path: secretPath,
defaultTTL, defaultTTL,
projectSlug, projectSlug,
environmentSlug: environment environmentSlug: environment.slug
}); });
onCompleted(); onCompleted();
} catch { } catch {
@@ -345,6 +358,29 @@ export const CassandraInputForm = ({
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
</Accordion> </Accordion>
{!isSingleEnvironmentMode && (
<Controller
control={control}
name="environment"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
label="Environment"
isError={Boolean(error)}
errorText={error?.message}
>
<FilterableSelect
options={environments}
value={value}
onChange={onChange}
placeholder="Select the environment to create secret in..."
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
/>
</FormControl>
)}
/>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -17,6 +17,7 @@ import { AnimatePresence, motion } from "framer-motion";
import { Modal, ModalContent } from "@app/components/v2"; import { Modal, ModalContent } from "@app/components/v2";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
import { WorkspaceEnv } from "@app/hooks/api/types";
import { AwsElastiCacheInputForm } from "./AwsElastiCacheInputForm"; import { AwsElastiCacheInputForm } from "./AwsElastiCacheInputForm";
import { AwsIamInputForm } from "./AwsIamInputForm"; import { AwsIamInputForm } from "./AwsIamInputForm";
@@ -38,8 +39,9 @@ type Props = {
isOpen?: boolean; isOpen?: boolean;
onToggle: (isOpen: boolean) => void; onToggle: (isOpen: boolean) => void;
projectSlug: string; projectSlug: string;
environment: string; environments: WorkspaceEnv[];
secretPath: string; secretPath: string;
isSingleEnvironmentMode?: boolean;
}; };
enum WizardSteps { enum WizardSteps {
@@ -129,8 +131,9 @@ export const CreateDynamicSecretForm = ({
isOpen, isOpen,
onToggle, onToggle,
projectSlug, projectSlug,
environment, environments,
secretPath secretPath,
isSingleEnvironmentMode
}: Props) => { }: Props) => {
const [wizardStep, setWizardStep] = useState(WizardSteps.SelectProvider); const [wizardStep, setWizardStep] = useState(WizardSteps.SelectProvider);
const [selectedProvider, setSelectedProvider] = useState<DynamicSecretProviders | null>(null); const [selectedProvider, setSelectedProvider] = useState<DynamicSecretProviders | null>(null);
@@ -197,7 +200,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset} onCancel={handleFormReset}
projectSlug={projectSlug} projectSlug={projectSlug}
secretPath={secretPath} secretPath={secretPath}
environment={environment} environments={environments}
isSingleEnvironmentMode={isSingleEnvironmentMode}
/> />
</motion.div> </motion.div>
)} )}
@@ -215,7 +219,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset} onCancel={handleFormReset}
projectSlug={projectSlug} projectSlug={projectSlug}
secretPath={secretPath} secretPath={secretPath}
environment={environment} environments={environments}
isSingleEnvironmentMode={isSingleEnvironmentMode}
/> />
</motion.div> </motion.div>
)} )}
@@ -233,7 +238,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset} onCancel={handleFormReset}
projectSlug={projectSlug} projectSlug={projectSlug}
secretPath={secretPath} secretPath={secretPath}
environment={environment} environments={environments}
isSingleEnvironmentMode={isSingleEnvironmentMode}
/> />
</motion.div> </motion.div>
)} )}
@@ -251,7 +257,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset} onCancel={handleFormReset}
projectSlug={projectSlug} projectSlug={projectSlug}
secretPath={secretPath} secretPath={secretPath}
environment={environment} environments={environments}
isSingleEnvironmentMode={isSingleEnvironmentMode}
/> />
</motion.div> </motion.div>
)} )}
@@ -269,7 +276,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset} onCancel={handleFormReset}
projectSlug={projectSlug} projectSlug={projectSlug}
secretPath={secretPath} secretPath={secretPath}
environment={environment} environments={environments}
isSingleEnvironmentMode={isSingleEnvironmentMode}
/> />
</motion.div> </motion.div>
)} )}
@@ -287,7 +295,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset} onCancel={handleFormReset}
projectSlug={projectSlug} projectSlug={projectSlug}
secretPath={secretPath} secretPath={secretPath}
environment={environment} environments={environments}
isSingleEnvironmentMode={isSingleEnvironmentMode}
/> />
</motion.div> </motion.div>
)} )}
@@ -305,7 +314,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset} onCancel={handleFormReset}
projectSlug={projectSlug} projectSlug={projectSlug}
secretPath={secretPath} secretPath={secretPath}
environment={environment} environments={environments}
isSingleEnvironmentMode={isSingleEnvironmentMode}
/> />
</motion.div> </motion.div>
)} )}
@@ -323,7 +333,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset} onCancel={handleFormReset}
projectSlug={projectSlug} projectSlug={projectSlug}
secretPath={secretPath} secretPath={secretPath}
environment={environment} environments={environments}
isSingleEnvironmentMode={isSingleEnvironmentMode}
/> />
</motion.div> </motion.div>
)} )}
@@ -341,7 +352,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset} onCancel={handleFormReset}
projectSlug={projectSlug} projectSlug={projectSlug}
secretPath={secretPath} secretPath={secretPath}
environment={environment} environments={environments}
isSingleEnvironmentMode={isSingleEnvironmentMode}
/> />
</motion.div> </motion.div>
)} )}
@@ -359,7 +371,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset} onCancel={handleFormReset}
projectSlug={projectSlug} projectSlug={projectSlug}
secretPath={secretPath} secretPath={secretPath}
environment={environment} environments={environments}
isSingleEnvironmentMode={isSingleEnvironmentMode}
/> />
</motion.div> </motion.div>
)} )}
@@ -377,7 +390,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset} onCancel={handleFormReset}
projectSlug={projectSlug} projectSlug={projectSlug}
secretPath={secretPath} secretPath={secretPath}
environment={environment} environments={environments}
isSingleEnvironmentMode={isSingleEnvironmentMode}
/> />
</motion.div> </motion.div>
)} )}
@@ -395,7 +409,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset} onCancel={handleFormReset}
projectSlug={projectSlug} projectSlug={projectSlug}
secretPath={secretPath} secretPath={secretPath}
environment={environment} environments={environments}
isSingleEnvironmentMode={isSingleEnvironmentMode}
/> />
</motion.div> </motion.div>
)} )}
@@ -413,7 +428,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset} onCancel={handleFormReset}
projectSlug={projectSlug} projectSlug={projectSlug}
secretPath={secretPath} secretPath={secretPath}
environment={environment} environments={environments}
isSingleEnvironmentMode={isSingleEnvironmentMode}
/> />
</motion.div> </motion.div>
)} )}
@@ -432,7 +448,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset} onCancel={handleFormReset}
projectSlug={projectSlug} projectSlug={projectSlug}
secretPath={secretPath} secretPath={secretPath}
environment={environment} environments={environments}
isSingleEnvironmentMode={isSingleEnvironmentMode}
/> />
</motion.div> </motion.div>
)} )}
@@ -450,7 +467,8 @@ export const CreateDynamicSecretForm = ({
onCancel={handleFormReset} onCancel={handleFormReset}
projectSlug={projectSlug} projectSlug={projectSlug}
secretPath={secretPath} secretPath={secretPath}
environment={environment} environments={environments}
isSingleEnvironmentMode={isSingleEnvironmentMode}
/> />
</motion.div> </motion.div>
)} )}
@@ -9,6 +9,7 @@ import { TtlFormLabel } from "@app/components/features";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { import {
Button, Button,
FilterableSelect,
FormControl, FormControl,
FormLabel, FormLabel,
IconButton, IconButton,
@@ -19,6 +20,7 @@ import {
} from "@app/components/v2"; } from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api"; import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
import { WorkspaceEnv } from "@app/hooks/api/types";
const authMethods = [ const authMethods = [
{ {
@@ -73,7 +75,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000) if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}), }),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase") name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
environment: z.object({ name: z.string(), slug: z.string() })
}); });
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -82,15 +85,17 @@ type Props = {
onCancel: () => void; onCancel: () => void;
secretPath: string; secretPath: string;
projectSlug: string; projectSlug: string;
environment: string; environments: WorkspaceEnv[];
isSingleEnvironmentMode?: boolean;
}; };
export const ElasticSearchInputForm = ({ export const ElasticSearchInputForm = ({
onCompleted, onCompleted,
onCancel, onCancel,
environment, environments,
secretPath, secretPath,
projectSlug projectSlug,
isSingleEnvironmentMode
}: Props) => { }: Props) => {
const { const {
control, control,
@@ -107,13 +112,20 @@ export const ElasticSearchInputForm = ({
}, },
roles: ["superuser"], roles: ["superuser"],
port: 443 port: 443
} },
environment: isSingleEnvironmentMode ? environments[0] : undefined
} }
}); });
const createDynamicSecret = useCreateDynamicSecret(); const createDynamicSecret = useCreateDynamicSecret();
const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { const handleCreateDynamicSecret = async ({
name,
maxTTL,
provider,
defaultTTL,
environment
}: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
try { try {
@@ -124,7 +136,7 @@ export const ElasticSearchInputForm = ({
path: secretPath, path: secretPath,
defaultTTL, defaultTTL,
projectSlug, projectSlug,
environmentSlug: environment environmentSlug: environment.slug
}); });
onCompleted(); onCompleted();
} catch { } catch {
@@ -406,6 +418,29 @@ export const ElasticSearchInputForm = ({
)} )}
/> />
</div> </div>
{!isSingleEnvironmentMode && (
<Controller
control={control}
name="environment"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
label="Environment"
isError={Boolean(error)}
errorText={error?.message}
>
<FilterableSelect
options={environments}
value={value}
onChange={onChange}
placeholder="Select the environment to create secret in..."
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
/>
</FormControl>
)}
/>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -7,9 +7,18 @@ import { z } from "zod";
import { TtlFormLabel } from "@app/components/features"; import { TtlFormLabel } from "@app/components/features";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { Button, FormControl, Input, Select, SelectItem, TextArea } from "@app/components/v2"; import {
Button,
FilterableSelect,
FormControl,
Input,
Select,
SelectItem,
TextArea
} from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api"; import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
import { WorkspaceEnv } from "@app/hooks/api/types";
enum CredentialType { enum CredentialType {
Dynamic = "dynamic", Dynamic = "dynamic",
@@ -69,7 +78,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000) if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}), }),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase") name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
environment: z.object({ name: z.string(), slug: z.string() })
}); });
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -79,7 +89,8 @@ type Props = {
onCancel: () => void; onCancel: () => void;
secretPath: string; secretPath: string;
projectSlug: string; projectSlug: string;
environment: string; environments: WorkspaceEnv[];
isSingleEnvironmentMode?: boolean;
}; };
export const LdapInputForm = ({ export const LdapInputForm = ({
@@ -87,7 +98,8 @@ export const LdapInputForm = ({
onCancel, onCancel,
secretPath, secretPath,
projectSlug, projectSlug,
environment environments,
isSingleEnvironmentMode
}: Props) => { }: Props) => {
const { const {
control, control,
@@ -107,7 +119,8 @@ export const LdapInputForm = ({
revocationLdif: "", revocationLdif: "",
rollbackLdif: "", rollbackLdif: "",
credentialType: CredentialType.Dynamic credentialType: CredentialType.Dynamic
} },
environment: isSingleEnvironmentMode ? environments[0] : undefined
} }
}); });
@@ -115,7 +128,13 @@ export const LdapInputForm = ({
const createDynamicSecret = useCreateDynamicSecret(); const createDynamicSecret = useCreateDynamicSecret();
const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { const handleCreateDynamicSecret = async ({
name,
maxTTL,
provider,
defaultTTL,
environment
}: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
try { try {
@@ -126,7 +145,7 @@ export const LdapInputForm = ({
path: secretPath, path: secretPath,
defaultTTL, defaultTTL,
projectSlug, projectSlug,
environmentSlug: environment environmentSlug: environment.slug
}); });
onCompleted(); onCompleted();
} catch { } catch {
@@ -369,6 +388,29 @@ export const LdapInputForm = ({
)} )}
/> />
)} )}
{!isSingleEnvironmentMode && (
<Controller
control={control}
name="environment"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
label="Environment"
isError={Boolean(error)}
errorText={error?.message}
>
<FilterableSelect
options={environments}
value={value}
onChange={onChange}
placeholder="Select the environment to create secret in..."
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
/>
</FormControl>
)}
/>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -13,6 +13,7 @@ import {
AccordionItem, AccordionItem,
AccordionTrigger, AccordionTrigger,
Button, Button,
FilterableSelect,
FormControl, FormControl,
FormLabel, FormLabel,
IconButton, IconButton,
@@ -22,6 +23,7 @@ import {
} from "@app/components/v2"; } from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api"; import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({ const formSchema = z.object({
provider: z.object({ provider: z.object({
@@ -63,7 +65,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000) if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}), }),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase") name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
environment: z.object({ name: z.string(), slug: z.string() })
}); });
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -72,7 +75,8 @@ type Props = {
onCancel: () => void; onCancel: () => void;
secretPath: string; secretPath: string;
projectSlug: string; projectSlug: string;
environment: string; environments: WorkspaceEnv[];
isSingleEnvironmentMode?: boolean;
}; };
const ATLAS_SCOPE_TYPES = [ const ATLAS_SCOPE_TYPES = [
@@ -93,9 +97,10 @@ const ATLAS_SCOPE_TYPES = [
export const MongoAtlasInputForm = ({ export const MongoAtlasInputForm = ({
onCompleted, onCompleted,
onCancel, onCancel,
environment, environments,
secretPath, secretPath,
projectSlug projectSlug,
isSingleEnvironmentMode
}: Props) => { }: Props) => {
const { const {
control, control,
@@ -108,7 +113,8 @@ export const MongoAtlasInputForm = ({
defaultValues: { defaultValues: {
provider: { provider: {
roles: [{ databaseName: "", roleName: "" }] roles: [{ databaseName: "", roleName: "" }]
} },
environment: isSingleEnvironmentMode ? environments[0] : undefined
} }
}); });
@@ -124,7 +130,13 @@ export const MongoAtlasInputForm = ({
const createDynamicSecret = useCreateDynamicSecret(); const createDynamicSecret = useCreateDynamicSecret();
const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { const handleCreateDynamicSecret = async ({
name,
maxTTL,
provider,
defaultTTL,
environment
}: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
try { try {
@@ -135,7 +147,7 @@ export const MongoAtlasInputForm = ({
path: secretPath, path: secretPath,
defaultTTL, defaultTTL,
projectSlug, projectSlug,
environmentSlug: environment environmentSlug: environment.slug
}); });
onCompleted(); onCompleted();
} catch { } catch {
@@ -438,6 +450,29 @@ export const MongoAtlasInputForm = ({
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
</Accordion> </Accordion>
{!isSingleEnvironmentMode && (
<Controller
control={control}
name="environment"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
label="Environment"
isError={Boolean(error)}
errorText={error?.message}
>
<FilterableSelect
options={environments}
value={value}
onChange={onChange}
placeholder="Select the environment to create secret in..."
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
/>
</FormControl>
)}
/>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -7,9 +7,18 @@ import { z } from "zod";
import { TtlFormLabel } from "@app/components/features"; import { TtlFormLabel } from "@app/components/features";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { Button, FormControl, FormLabel, IconButton, Input, SecretInput } from "@app/components/v2"; import {
Button,
FilterableSelect,
FormControl,
FormLabel,
IconButton,
Input,
SecretInput
} from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api"; import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({ const formSchema = z.object({
provider: z.object({ provider: z.object({
@@ -46,7 +55,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000) if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}), }),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase") name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
environment: z.object({ name: z.string(), slug: z.string() })
}); });
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -55,15 +65,17 @@ type Props = {
onCancel: () => void; onCancel: () => void;
secretPath: string; secretPath: string;
projectSlug: string; projectSlug: string;
environment: string; environments: WorkspaceEnv[];
isSingleEnvironmentMode?: boolean;
}; };
export const MongoDBDatabaseInputForm = ({ export const MongoDBDatabaseInputForm = ({
onCompleted, onCompleted,
onCancel, onCancel,
environment, environments,
secretPath, secretPath,
projectSlug projectSlug,
isSingleEnvironmentMode
}: Props) => { }: Props) => {
const { const {
control, control,
@@ -76,7 +88,8 @@ export const MongoDBDatabaseInputForm = ({
defaultValues: { defaultValues: {
provider: { provider: {
roles: [{ roleName: "readWrite" }] roles: [{ roleName: "readWrite" }]
} },
environment: isSingleEnvironmentMode ? environments[0] : undefined
} }
}); });
@@ -87,7 +100,13 @@ export const MongoDBDatabaseInputForm = ({
const createDynamicSecret = useCreateDynamicSecret(); const createDynamicSecret = useCreateDynamicSecret();
const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { const handleCreateDynamicSecret = async ({
name,
maxTTL,
provider,
defaultTTL,
environment
}: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
try { try {
@@ -105,7 +124,7 @@ export const MongoDBDatabaseInputForm = ({
path: secretPath, path: secretPath,
defaultTTL, defaultTTL,
projectSlug, projectSlug,
environmentSlug: environment environmentSlug: environment.slug
}); });
onCompleted(); onCompleted();
} catch { } catch {
@@ -321,6 +340,29 @@ export const MongoDBDatabaseInputForm = ({
)} )}
/> />
</div> </div>
{!isSingleEnvironmentMode && (
<Controller
control={control}
name="environment"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
label="Environment"
isError={Boolean(error)}
errorText={error?.message}
>
<FilterableSelect
options={environments}
value={value}
onChange={onChange}
placeholder="Select the environment to create secret in..."
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
/>
</FormControl>
)}
/>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -7,9 +7,18 @@ import { z } from "zod";
import { TtlFormLabel } from "@app/components/features"; import { TtlFormLabel } from "@app/components/features";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { Button, FormControl, FormLabel, IconButton, Input, SecretInput } from "@app/components/v2"; import {
Button,
FilterableSelect,
FormControl,
FormLabel,
IconButton,
Input,
SecretInput
} from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api"; import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({ const formSchema = z.object({
provider: z.object({ provider: z.object({
@@ -50,7 +59,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000) if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}), }),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase") name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
environment: z.object({ name: z.string(), slug: z.string() })
}); });
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -59,15 +69,17 @@ type Props = {
onCancel: () => void; onCancel: () => void;
secretPath: string; secretPath: string;
projectSlug: string; projectSlug: string;
environment: string; environments: WorkspaceEnv[];
isSingleEnvironmentMode?: boolean;
}; };
export const RabbitMqInputForm = ({ export const RabbitMqInputForm = ({
onCompleted, onCompleted,
onCancel, onCancel,
environment, environments,
secretPath, secretPath,
projectSlug projectSlug,
isSingleEnvironmentMode
}: Props) => { }: Props) => {
const { const {
control, control,
@@ -89,13 +101,20 @@ export const RabbitMqInputForm = ({
} }
}, },
tags: [] tags: []
} },
environment: isSingleEnvironmentMode ? environments[0] : undefined
} }
}); });
const createDynamicSecret = useCreateDynamicSecret(); const createDynamicSecret = useCreateDynamicSecret();
const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { const handleCreateDynamicSecret = async ({
name,
maxTTL,
provider,
defaultTTL,
environment
}: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
try { try {
@@ -106,7 +125,7 @@ export const RabbitMqInputForm = ({
path: secretPath, path: secretPath,
defaultTTL, defaultTTL,
projectSlug, projectSlug,
environmentSlug: environment environmentSlug: environment.slug
}); });
onCompleted(); onCompleted();
} catch { } catch {
@@ -405,6 +424,29 @@ export const RabbitMqInputForm = ({
)} )}
/> />
</div> </div>
{!isSingleEnvironmentMode && (
<Controller
control={control}
name="environment"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
label="Environment"
isError={Boolean(error)}
errorText={error?.message}
>
<FilterableSelect
options={environments}
value={value}
onChange={onChange}
placeholder="Select the environment to create secret in..."
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
/>
</FormControl>
)}
/>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -11,6 +11,7 @@ import {
AccordionItem, AccordionItem,
AccordionTrigger, AccordionTrigger,
Button, Button,
FilterableSelect,
FormControl, FormControl,
Input, Input,
SecretInput, SecretInput,
@@ -18,6 +19,7 @@ import {
} from "@app/components/v2"; } from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api"; import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({ const formSchema = z.object({
provider: z.object({ provider: z.object({
@@ -50,7 +52,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000) if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}), }),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase") name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
environment: z.object({ name: z.string(), slug: z.string() })
}); });
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -59,15 +62,17 @@ type Props = {
onCancel: () => void; onCancel: () => void;
secretPath: string; secretPath: string;
projectSlug: string; projectSlug: string;
environment: string; environments: WorkspaceEnv[];
isSingleEnvironmentMode?: boolean;
}; };
export const RedisInputForm = ({ export const RedisInputForm = ({
onCompleted, onCompleted,
onCancel, onCancel,
environment, environments,
secretPath, secretPath,
projectSlug projectSlug,
isSingleEnvironmentMode
}: Props) => { }: Props) => {
const { const {
control, control,
@@ -81,13 +86,20 @@ export const RedisInputForm = ({
port: 6379, port: 6379,
creationStatement: "ACL SETUSER {{username}} on >{{password}} ~* &* +@all", creationStatement: "ACL SETUSER {{username}} on >{{password}} ~* &* +@all",
revocationStatement: "ACL DELUSER {{username}}" revocationStatement: "ACL DELUSER {{username}}"
} },
environment: isSingleEnvironmentMode ? environments[0] : undefined
} }
}); });
const createDynamicSecret = useCreateDynamicSecret(); const createDynamicSecret = useCreateDynamicSecret();
const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { const handleCreateDynamicSecret = async ({
name,
maxTTL,
provider,
defaultTTL,
environment
}: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
try { try {
@@ -98,7 +110,7 @@ export const RedisInputForm = ({
path: secretPath, path: secretPath,
defaultTTL, defaultTTL,
projectSlug, projectSlug,
environmentSlug: environment environmentSlug: environment.slug
}); });
onCompleted(); onCompleted();
} catch { } catch {
@@ -313,6 +325,29 @@ export const RedisInputForm = ({
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
</Accordion> </Accordion>
{!isSingleEnvironmentMode && (
<Controller
control={control}
name="environment"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
label="Environment"
isError={Boolean(error)}
errorText={error?.message}
>
<FilterableSelect
options={environments}
value={value}
onChange={onChange}
placeholder="Select the environment to create secret in..."
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
/>
</FormControl>
)}
/>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -11,12 +11,14 @@ import {
AccordionItem, AccordionItem,
AccordionTrigger, AccordionTrigger,
Button, Button,
FilterableSelect,
FormControl, FormControl,
Input, Input,
TextArea TextArea
} from "@app/components/v2"; } from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api"; import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({ const formSchema = z.object({
provider: z.object({ provider: z.object({
@@ -48,7 +50,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000) if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}), }),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase") name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
environment: z.object({ name: z.string(), slug: z.string() })
}); });
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -57,15 +60,17 @@ type Props = {
onCancel: () => void; onCancel: () => void;
secretPath: string; secretPath: string;
projectSlug: string; projectSlug: string;
environment: string; environments: WorkspaceEnv[];
isSingleEnvironmentMode?: boolean;
}; };
export const SapAseInputForm = ({ export const SapAseInputForm = ({
onCompleted, onCompleted,
onCancel, onCancel,
environment, environments,
secretPath, secretPath,
projectSlug projectSlug,
isSingleEnvironmentMode
}: Props) => { }: Props) => {
const { const {
control, control,
@@ -82,13 +87,20 @@ sp_adduser '{{username}}', '{{username}}', null;
sp_role 'grant', 'mon_role', '{{username}}';`, sp_role 'grant', 'mon_role', '{{username}}';`,
revocationStatement: `sp_dropuser '{{username}}'; revocationStatement: `sp_dropuser '{{username}}';
sp_droplogin '{{username}}';` sp_droplogin '{{username}}';`
} },
environment: isSingleEnvironmentMode ? environments[0] : undefined
} }
}); });
const createDynamicSecret = useCreateDynamicSecret(); const createDynamicSecret = useCreateDynamicSecret();
const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { const handleCreateDynamicSecret = async ({
name,
maxTTL,
provider,
defaultTTL,
environment
}: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
try { try {
@@ -99,7 +111,7 @@ sp_droplogin '{{username}}';`
path: secretPath, path: secretPath,
defaultTTL, defaultTTL,
projectSlug, projectSlug,
environmentSlug: environment environmentSlug: environment.slug
}); });
onCompleted(); onCompleted();
} catch { } catch {
@@ -291,6 +303,29 @@ sp_droplogin '{{username}}';`
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
</Accordion> </Accordion>
{!isSingleEnvironmentMode && (
<Controller
control={control}
name="environment"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
label="Environment"
isError={Boolean(error)}
errorText={error?.message}
>
<FilterableSelect
options={environments}
value={value}
onChange={onChange}
placeholder="Select the environment to create secret in..."
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
/>
</FormControl>
)}
/>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -11,6 +11,7 @@ import {
AccordionItem, AccordionItem,
AccordionTrigger, AccordionTrigger,
Button, Button,
FilterableSelect,
FormControl, FormControl,
Input, Input,
SecretInput, SecretInput,
@@ -18,6 +19,7 @@ import {
} from "@app/components/v2"; } from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api"; import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({ const formSchema = z.object({
provider: z.object({ provider: z.object({
@@ -50,7 +52,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000) if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}), }),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase") name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
environment: z.object({ name: z.string(), slug: z.string() })
}); });
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -59,15 +62,17 @@ type Props = {
onCancel: () => void; onCancel: () => void;
secretPath: string; secretPath: string;
projectSlug: string; projectSlug: string;
environment: string; environments: WorkspaceEnv[];
isSingleEnvironmentMode?: boolean;
}; };
export const SapHanaInputForm = ({ export const SapHanaInputForm = ({
onCompleted, onCompleted,
onCancel, onCancel,
environment, environments,
secretPath, secretPath,
projectSlug projectSlug,
isSingleEnvironmentMode
}: Props) => { }: Props) => {
const { const {
control, control,
@@ -82,13 +87,20 @@ GRANT "MONITORING" TO {{username}};`,
revocationStatement: `REVOKE "MONITORING" FROM {{username}}; revocationStatement: `REVOKE "MONITORING" FROM {{username}};
DROP USER {{username}};`, DROP USER {{username}};`,
renewStatement: "ALTER USER {{username}} VALID UNTIL '{{expiration}}';" renewStatement: "ALTER USER {{username}} VALID UNTIL '{{expiration}}';"
} },
environment: isSingleEnvironmentMode ? environments[0] : undefined
} }
}); });
const createDynamicSecret = useCreateDynamicSecret(); const createDynamicSecret = useCreateDynamicSecret();
const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { const handleCreateDynamicSecret = async ({
name,
maxTTL,
provider,
defaultTTL,
environment
}: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
try { try {
@@ -99,7 +111,7 @@ DROP USER {{username}};`,
path: secretPath, path: secretPath,
defaultTTL, defaultTTL,
projectSlug, projectSlug,
environmentSlug: environment environmentSlug: environment.slug
}); });
onCompleted(); onCompleted();
} catch { } catch {
@@ -311,6 +323,29 @@ DROP USER {{username}};`,
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
</Accordion> </Accordion>
{!isSingleEnvironmentMode && (
<Controller
control={control}
name="environment"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
label="Environment"
isError={Boolean(error)}
errorText={error?.message}
>
<FilterableSelect
options={environments}
value={value}
onChange={onChange}
placeholder="Select the environment to create secret in..."
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
/>
</FormControl>
)}
/>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -13,12 +13,14 @@ import {
AccordionItem, AccordionItem,
AccordionTrigger, AccordionTrigger,
Button, Button,
FilterableSelect,
FormControl, FormControl,
Input, Input,
TextArea TextArea
} from "@app/components/v2"; } from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api"; import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
import { WorkspaceEnv } from "@app/hooks/api/types";
const formSchema = z.object({ const formSchema = z.object({
provider: z.object({ provider: z.object({
@@ -54,7 +56,8 @@ const formSchema = z.object({
.string() .string()
.trim() .trim()
.min(1) .min(1)
.refine((val) => val.toLowerCase() === val, "Must be lowercase") .refine((val) => val.toLowerCase() === val, "Must be lowercase"),
environment: z.object({ name: z.string(), slug: z.string() })
}); });
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -63,15 +66,17 @@ type Props = {
onCancel: () => void; onCancel: () => void;
secretPath: string; secretPath: string;
projectSlug: string; projectSlug: string;
environment: string; environments: WorkspaceEnv[];
isSingleEnvironmentMode?: boolean;
}; };
export const SnowflakeInputForm = ({ export const SnowflakeInputForm = ({
onCompleted, onCompleted,
onCancel, onCancel,
environment, environments,
secretPath, secretPath,
projectSlug projectSlug,
isSingleEnvironmentMode
}: Props) => { }: Props) => {
const { const {
control, control,
@@ -85,13 +90,20 @@ export const SnowflakeInputForm = ({
"CREATE USER {{username}} PASSWORD = '{{password}}' DEFAULT_ROLE = public DEFAULT_SECONDARY_ROLES = ('ALL') MUST_CHANGE_PASSWORD = FALSE DAYS_TO_EXPIRY = {{expiration}};", "CREATE USER {{username}} PASSWORD = '{{password}}' DEFAULT_ROLE = public DEFAULT_SECONDARY_ROLES = ('ALL') MUST_CHANGE_PASSWORD = FALSE DAYS_TO_EXPIRY = {{expiration}};",
revocationStatement: "DROP USER {{username}};", revocationStatement: "DROP USER {{username}};",
renewStatement: "ALTER USER {{username}} SET DAYS_TO_EXPIRY = {{expiration}};" renewStatement: "ALTER USER {{username}} SET DAYS_TO_EXPIRY = {{expiration}};"
} },
environment: isSingleEnvironmentMode ? environments[0] : undefined
} }
}); });
const createDynamicSecret = useCreateDynamicSecret(); const createDynamicSecret = useCreateDynamicSecret();
const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { const handleCreateDynamicSecret = async ({
name,
maxTTL,
provider,
defaultTTL,
environment
}: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
try { try {
@@ -102,7 +114,7 @@ export const SnowflakeInputForm = ({
path: secretPath, path: secretPath,
defaultTTL, defaultTTL,
projectSlug, projectSlug,
environmentSlug: environment environmentSlug: environment.slug
}); });
onCompleted(); onCompleted();
} catch (err) { } catch (err) {
@@ -314,6 +326,29 @@ export const SnowflakeInputForm = ({
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
</Accordion> </Accordion>
{!isSingleEnvironmentMode && (
<Controller
control={control}
name="environment"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
label="Environment"
isError={Boolean(error)}
errorText={error?.message}
>
<FilterableSelect
options={environments}
value={value}
onChange={onChange}
placeholder="Select the environment to create secret in..."
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
/>
</FormControl>
)}
/>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -12,6 +12,7 @@ import {
AccordionItem, AccordionItem,
AccordionTrigger, AccordionTrigger,
Button, Button,
FilterableSelect,
FormControl, FormControl,
Input, Input,
SecretInput, SecretInput,
@@ -22,6 +23,7 @@ import {
import { useWorkspace } from "@app/context"; import { useWorkspace } from "@app/context";
import { gatewaysQueryKeys, useCreateDynamicSecret } from "@app/hooks/api"; import { gatewaysQueryKeys, useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders, SqlProviders } from "@app/hooks/api/dynamicSecret/types"; import { DynamicSecretProviders, SqlProviders } from "@app/hooks/api/dynamicSecret/types";
import { WorkspaceEnv } from "@app/hooks/api/types";
const passwordRequirementsSchema = z const passwordRequirementsSchema = z
.object({ .object({
@@ -79,7 +81,8 @@ const formSchema = z.object({
if (valMs > 24 * 60 * 60 * 1000) if (valMs > 24 * 60 * 60 * 1000)
ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" }); ctx.addIssue({ code: z.ZodIssueCode.custom, message: "TTL must be less than a day" });
}), }),
name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase") name: z.string().refine((val) => val.toLowerCase() === val, "Must be lowercase"),
environment: z.object({ name: z.string(), slug: z.string() })
}); });
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -88,7 +91,8 @@ type Props = {
onCancel: () => void; onCancel: () => void;
secretPath: string; secretPath: string;
projectSlug: string; projectSlug: string;
environment: string; environments: WorkspaceEnv[];
isSingleEnvironmentMode?: boolean;
}; };
const getSqlStatements = (provider: SqlProviders) => { const getSqlStatements = (provider: SqlProviders) => {
@@ -145,9 +149,10 @@ const getDefaultPort = (provider: SqlProviders) => {
export const SqlDatabaseInputForm = ({ export const SqlDatabaseInputForm = ({
onCompleted, onCompleted,
onCancel, onCancel,
environment, environments,
secretPath, secretPath,
projectSlug projectSlug,
isSingleEnvironmentMode
}: Props) => { }: Props) => {
const { currentWorkspace } = useWorkspace(); const { currentWorkspace } = useWorkspace();
@@ -172,7 +177,8 @@ export const SqlDatabaseInputForm = ({
}, },
allowedSymbols: "-_.~!*" allowedSymbols: "-_.~!*"
} }
} },
environment: isSingleEnvironmentMode ? environments[0] : undefined
} }
}); });
@@ -181,9 +187,16 @@ export const SqlDatabaseInputForm = ({
gatewaysQueryKeys.listProjectGateways({ projectId: currentWorkspace.id }) gatewaysQueryKeys.listProjectGateways({ projectId: currentWorkspace.id })
); );
const handleCreateDynamicSecret = async ({ name, maxTTL, provider, defaultTTL }: TForm) => { const handleCreateDynamicSecret = async ({
name,
maxTTL,
provider,
defaultTTL,
environment
}: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
try { try {
await createDynamicSecret.mutateAsync({ await createDynamicSecret.mutateAsync({
provider: { type: DynamicSecretProviders.SqlDatabase, inputs: provider }, provider: { type: DynamicSecretProviders.SqlDatabase, inputs: provider },
@@ -192,7 +205,7 @@ export const SqlDatabaseInputForm = ({
path: secretPath, path: secretPath,
defaultTTL, defaultTTL,
projectSlug, projectSlug,
environmentSlug: environment environmentSlug: environment.slug
}); });
onCompleted(); onCompleted();
} catch { } catch {
@@ -649,6 +662,29 @@ export const SqlDatabaseInputForm = ({
</AccordionContent> </AccordionContent>
</AccordionItem> </AccordionItem>
</Accordion> </Accordion>
{!isSingleEnvironmentMode && (
<Controller
control={control}
name="environment"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
label="Environment"
isError={Boolean(error)}
errorText={error?.message}
>
<FilterableSelect
options={environments}
value={value}
onChange={onChange}
placeholder="Select the environment to create secret in..."
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
/>
</FormControl>
)}
/>
)}
</div> </div>
</div> </div>
</div> </div>
@@ -5,9 +5,17 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod"; import { z } from "zod";
import { createNotification } from "@app/components/notifications"; import { createNotification } from "@app/components/notifications";
import { Button, FormControl, Input, Select, SelectItem } from "@app/components/v2"; import {
Button,
FilterableSelect,
FormControl,
Input,
Select,
SelectItem
} from "@app/components/v2";
import { useCreateDynamicSecret } from "@app/hooks/api"; import { useCreateDynamicSecret } from "@app/hooks/api";
import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types"; import { DynamicSecretProviders } from "@app/hooks/api/dynamicSecret/types";
import { WorkspaceEnv } from "@app/hooks/api/types";
enum ConfigType { enum ConfigType {
URL = "url", URL = "url",
@@ -52,7 +60,8 @@ const formSchema = z.object({
.string() .string()
.trim() .trim()
.min(1) .min(1)
.refine((val) => val.toLowerCase() === val, "Must be lowercase") .refine((val) => val.toLowerCase() === val, "Must be lowercase"),
environment: z.object({ name: z.string(), slug: z.string() })
}); });
type TForm = z.infer<typeof formSchema>; type TForm = z.infer<typeof formSchema>;
@@ -62,15 +71,17 @@ type Props = {
onCancel: () => void; onCancel: () => void;
secretPath: string; secretPath: string;
projectSlug: string; projectSlug: string;
environment: string; environments: WorkspaceEnv[];
isSingleEnvironmentMode?: boolean;
}; };
export const TotpInputForm = ({ export const TotpInputForm = ({
onCompleted, onCompleted,
onCancel, onCancel,
environment, environments,
secretPath, secretPath,
projectSlug projectSlug,
isSingleEnvironmentMode
}: Props) => { }: Props) => {
const { const {
control, control,
@@ -82,7 +93,8 @@ export const TotpInputForm = ({
defaultValues: { defaultValues: {
provider: { provider: {
configType: ConfigType.URL configType: ConfigType.URL
} },
environment: isSingleEnvironmentMode ? environments[0] : undefined
} }
}); });
@@ -90,7 +102,7 @@ export const TotpInputForm = ({
const createDynamicSecret = useCreateDynamicSecret(); const createDynamicSecret = useCreateDynamicSecret();
const handleCreateDynamicSecret = async ({ name, provider }: TForm) => { const handleCreateDynamicSecret = async ({ name, provider, environment }: TForm) => {
// wait till previous request is finished // wait till previous request is finished
if (createDynamicSecret.isPending) return; if (createDynamicSecret.isPending) return;
try { try {
@@ -101,7 +113,7 @@ export const TotpInputForm = ({
path: secretPath, path: secretPath,
defaultTTL: "1m", defaultTTL: "1m",
projectSlug, projectSlug,
environmentSlug: environment environmentSlug: environment.slug
}); });
onCompleted(); onCompleted();
} catch (err) { } catch (err) {
@@ -295,6 +307,29 @@ export const TotpInputForm = ({
</p> </p>
</> </>
)} )}
{!isSingleEnvironmentMode && (
<Controller
control={control}
name="environment"
render={({ field: { value, onChange }, fieldState: { error } }) => (
<FormControl
label="Environment"
isError={Boolean(error)}
errorText={error?.message}
>
<FilterableSelect
options={environments}
value={value}
onChange={onChange}
placeholder="Select the environment to create secret in..."
getOptionLabel={(option) => option.name}
getOptionValue={(option) => option.slug}
menuPlacement="top"
/>
</FormControl>
)}
/>
)}
</div> </div>
</div> </div>
</div> </div>