feat: renamed old frontend and fixed some minor bugs

This commit is contained in:
=
2025-01-06 14:56:49 +05:30
parent 8c902d7699
commit 54377a44d3
2577 changed files with 35240 additions and 35116 deletions
@@ -0,0 +1,144 @@
import { useRef, useState } from "react";
import { AnimatePresence, motion } from "framer-motion";
import { createNotification } from "@app/components/notifications";
import { Modal, ModalContent, Step, Stepper } from "@app/components/v2";
import { useCreateSecretRotation } from "@app/hooks/api";
import { TSecretRotationProviderTemplate } from "@app/hooks/api/types";
import { RotationInputForm } from "./steps/RotationInputForm";
import {
RotationOutputForm,
TFormSchema as TRotationOutputSchema
} from "./steps/RotationOutputForm";
const WIZARD_STEPS = [
{
title: "Inputs",
description: "Provider secrets"
},
{
title: "Outputs",
description: "Map rotated secrets to keys"
}
];
type Props = {
isOpen?: boolean;
onToggle: (isOpen: boolean) => void;
customProvider?: string;
workspaceId: string;
provider: TSecretRotationProviderTemplate;
};
export const CreateRotationForm = ({
isOpen,
onToggle,
provider,
workspaceId,
customProvider
}: Props) => {
const [wizardStep, setWizardStep] = useState(0);
const wizardData = useRef<{
input?: Record<string, string>;
output?: TRotationOutputSchema;
}>({});
const { mutateAsync: createSecretRotation } = useCreateSecretRotation();
const handleFormCancel = () => {
onToggle(false);
setWizardStep(0);
wizardData.current = {};
};
const handleFormSubmit = async () => {
if (!wizardData.current.input || !wizardData.current.output) return;
try {
await createSecretRotation({
workspaceId,
provider: provider.name,
customProvider,
secretPath: wizardData.current.output.secretPath,
environment: wizardData.current.output.environment,
interval: wizardData.current.output.interval,
inputs: wizardData.current.input,
outputs: wizardData.current.output.secrets
});
setWizardStep(0);
onToggle(false);
wizardData.current = {};
} catch (error) {
console.log(error);
createNotification({
type: "error",
text: "Failed to create secret rotation"
});
}
};
return (
<Modal
isOpen={isOpen}
onOpenChange={(state) => {
onToggle(state);
setWizardStep(0);
wizardData.current = {};
}}
>
<ModalContent
title={`Secret rotation for ${provider.name}`}
subTitle="Provide the required inputs needed for the rotation"
className="my-4 max-h-screen max-w-2xl overflow-scroll"
>
<Stepper activeStep={wizardStep} direction="horizontal" className="mb-4">
{WIZARD_STEPS.map(({ title, description }, index) => (
<Step
title={title}
description={description}
key={`wizard-stepper-rotation-${index + 1}`}
/>
))}
</Stepper>
<AnimatePresence mode="wait">
{wizardStep === 0 && (
<motion.div
key="input-step"
transition={{ duration: 0.1 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: -30 }}
>
<RotationInputForm
onCancel={handleFormCancel}
onSubmit={(data) => {
wizardData.current.input = data;
setWizardStep((state) => state + 1);
}}
inputSchema={provider.template?.inputs || {}}
/>
</motion.div>
)}
{wizardStep === 1 && (
<motion.div
key="output-step"
transition={{ duration: 0.1 }}
initial={{ opacity: 0, translateX: 30 }}
animate={{ opacity: 1, translateX: 0 }}
exit={{ opacity: 0, translateX: -30 }}
>
<RotationOutputForm
outputSchema={provider.template?.outputs || {}}
onCancel={handleFormCancel}
onSubmit={async (data) => {
wizardData.current.output = data;
await handleFormSubmit();
}}
/>
</motion.div>
)}
</AnimatePresence>
</ModalContent>
</Modal>
);
};
@@ -0,0 +1 @@
export { CreateRotationForm } from "./CreateRotationForm";
@@ -0,0 +1,78 @@
import { Controller, useForm } from "react-hook-form";
import { faQuestionCircle } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { Button, FormControl, FormLabel, SecretInput, Tooltip } from "@app/components/v2";
type Props = {
onSubmit: (data: Record<string, string>) => void;
onCancel: () => void;
inputSchema: {
properties: Record<string, { type: string; desc?: string; default?: string }>;
required: string[];
};
};
const formSchema = z.record(z.string().trim().optional());
export const RotationInputForm = ({ onSubmit, onCancel, inputSchema }: Props) => {
const {
control,
handleSubmit,
formState: { isSubmitting }
} = useForm<Record<string, string>>({
resolver: zodResolver(formSchema)
});
return (
<form onSubmit={handleSubmit(onSubmit)}>
{Object.keys(inputSchema.properties || {}).map((inputName) => (
<Controller
control={control}
name={inputName}
key={`provider-input-${inputName}`}
defaultValue={inputSchema.properties[inputName]?.default}
render={({ field }) => (
<FormControl
key={`provider-input-${inputName}`}
label={
<div className="flex items-center space-x-2">
<FormLabel className="mb-0 uppercase" label={inputName.replaceAll("_", " ")} />
{Boolean(inputSchema.properties[inputName]?.desc) && (
<Tooltip
className="max-w-xs"
content={inputSchema.properties[inputName]?.desc}
position="right"
>
<FontAwesomeIcon
icon={faQuestionCircle}
size="xs"
className="text-bunker-300"
/>
</Tooltip>
)}
</div>
}
>
<SecretInput
{...field}
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)}
/>
</FormControl>
)}
/>
))}
<div className="mt-8 flex items-center space-x-4">
<Button type="submit" isLoading={isSubmitting} isDisabled={isSubmitting}>
Next
</Button>
<Button onClick={onCancel} colorSchema="secondary" variant="plain">
Cancel
</Button>
</div>
</form>
);
};
@@ -0,0 +1,151 @@
import { Controller, useForm } from "react-hook-form";
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 } from "@app/hooks/api";
const formSchema = z.object({
environment: z.string().trim(),
secretPath: z.string().trim().default("/"),
interval: z.number().min(1),
secrets: z.record(z.string())
});
export type TFormSchema = z.infer<typeof formSchema>;
type Props = {
outputSchema: Record<string, unknown>;
onSubmit: (data: TFormSchema) => void;
onCancel: () => void;
};
export const RotationOutputForm = ({ onSubmit, onCancel, outputSchema = {} }: Props) => {
const { currentWorkspace } = useWorkspace();
const environments = currentWorkspace?.environments || [];
const workspaceId = currentWorkspace?.id || "";
const {
control,
handleSubmit,
watch,
formState: { isSubmitting }
} = useForm<TFormSchema>({
resolver: zodResolver(formSchema)
});
const environment = watch("environment", environments?.[0]?.slug);
const secretPath = watch("secretPath");
const selectedSecrets = watch("secrets");
const { data: secrets, isLoading: isSecretsLoading } = useGetProjectSecrets({
workspaceId,
environment,
secretPath
});
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Controller
control={control}
name="environment"
defaultValue={environments?.[0]?.slug}
render={({ field: { value, onChange } }) => (
<FormControl label="Environment">
<Select
value={value}
onValueChange={(val) => onChange(val)}
className="w-full border border-mineshaft-500"
defaultValue={environments?.[0]?.slug}
position="popper"
>
{environments.map((sourceEnvironment) => (
<SelectItem
value={sourceEnvironment.slug}
key={`source-environment-${sourceEnvironment.slug}`}
>
{sourceEnvironment.name}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
<Controller
control={control}
name="secretPath"
defaultValue="/"
render={({ field }) => (
<FormControl className="capitalize" label="Secret path">
<SecretPathInput {...field} environment={environment} />
</FormControl>
)}
/>
<Controller
control={control}
name="interval"
defaultValue={15}
render={({ field }) => (
<FormControl className="capitalize" label="Rotation Interval (Days)">
<Input
{...field}
min={1}
type="number"
onChange={(evt) => field.onChange(parseInt(evt.target.value, 10))}
/>
</FormControl>
)}
/>
<div className="mb-2 mt-4 flex flex-col border-t border-bunker-300/30 pt-4">
<div>Mapping</div>
<div className="text-sm text-bunker-300">Select keys for rotated value to get saved</div>
</div>
{Object.keys(outputSchema).map((outputName) => (
<Controller
key={`provider-output-${outputName}`}
control={control}
name={`secrets.${outputName}`}
render={({ field: { value, onChange } }) => (
<FormControl className="uppercase" label={outputName.replaceAll("_", " ")} isRequired>
<Select
value={value}
onValueChange={(val) => onChange(val)}
className="w-full border border-mineshaft-500"
position="popper"
>
{!isSecretsLoading &&
secrets
?.filter(
({ id }) => value === id || !Object.values(selectedSecrets || {}).includes(id)
)
?.map(({ key, id }) => (
<SelectItem value={id} key={id}>
{key}
</SelectItem>
))}
{isSecretsLoading && (
<SelectItem value="Loading" isDisabled>
<Spinner size="xs" />
</SelectItem>
)}
{!isSecretsLoading && secrets?.length === 0 && (
<SelectItem value="Empty" isDisabled>
No secrets found
</SelectItem>
)}
</Select>
</FormControl>
)}
/>
))}
<div className="mt-8 flex items-center space-x-4">
<Button type="submit" isLoading={isSubmitting} isDisabled={isSubmitting}>
Submit
</Button>
<Button onClick={onCancel} colorSchema="secondary" variant="plain">
Cancel
</Button>
</div>
</form>
);
};