mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-11-02 01:15:31 +00:00
chore(ui): Merge UI repository
This commit is contained in:
commit
725fcf80aa
274 files changed
+33204
No files matched your search
@@ -0,0 +1,226 @@
|
||||
"use client";
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { ChevronLeftIcon, ChevronRightIcon, SaveIcon } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import * as z from "zod";
|
||||
|
||||
import { useToast } from "@/components/ui";
|
||||
import { CustomButton, CustomInput } from "@/components/ui/custom";
|
||||
import { Form } from "@/components/ui/form";
|
||||
|
||||
import { addProvider } from "../../../../actions/providers/providers";
|
||||
import { addProviderFormSchema, ApiError } from "../../../../types";
|
||||
import { RadioGroupProvider } from "../../radio-group-provider";
|
||||
import { RadioGroupAWSViaCredentialsForm } from "./radio-group-aws-via-credentials-form";
|
||||
|
||||
export type FormValues = z.infer<typeof addProviderFormSchema>;
|
||||
|
||||
export const ConnectAccountForm = () => {
|
||||
const { toast } = useToast();
|
||||
const [prevStep, setPrevStep] = useState(1);
|
||||
const router = useRouter();
|
||||
|
||||
const formSchema = addProviderFormSchema;
|
||||
|
||||
const form = useForm<FormValues>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
providerType: undefined,
|
||||
providerUid: "",
|
||||
providerAlias: "",
|
||||
awsCredentialsType: "",
|
||||
},
|
||||
});
|
||||
const providerType = form.watch("providerType");
|
||||
const isLoading = form.formState.isSubmitting;
|
||||
|
||||
const onSubmitClient = async (values: FormValues) => {
|
||||
const formValues = { ...values };
|
||||
|
||||
// If providerAlias is empty, set default value
|
||||
if (!formValues.providerAlias.trim()) {
|
||||
const date = new Date();
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
||||
const day = date.getDate().toString().padStart(2, "0");
|
||||
const year = date.getFullYear();
|
||||
formValues.providerAlias = `${formValues.providerType}:${month}/${day}/${year}`;
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
|
||||
Object.entries(formValues).forEach(
|
||||
([key, value]) => value !== undefined && formData.append(key, value),
|
||||
);
|
||||
|
||||
const data = await addProvider(formData);
|
||||
|
||||
if (data?.errors && data.errors.length > 0) {
|
||||
data.errors.forEach((error: ApiError) => {
|
||||
const errorMessage = error.detail;
|
||||
const pointer = error.source?.pointer;
|
||||
|
||||
switch (pointer) {
|
||||
case "/data/attributes/provider":
|
||||
form.setError("providerType", {
|
||||
type: "server",
|
||||
message: errorMessage,
|
||||
});
|
||||
break;
|
||||
case "/data/attributes/uid":
|
||||
case "/data/attributes/__all__":
|
||||
form.setError("providerUid", {
|
||||
type: "server",
|
||||
message: errorMessage,
|
||||
});
|
||||
break;
|
||||
case "/data/attributes/alias":
|
||||
form.setError("providerAlias", {
|
||||
type: "server",
|
||||
message: errorMessage,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Oops! Something went wrong",
|
||||
description: errorMessage,
|
||||
});
|
||||
}
|
||||
});
|
||||
setPrevStep(1);
|
||||
} else {
|
||||
const {
|
||||
id,
|
||||
attributes: { provider: providerType },
|
||||
} = data.data;
|
||||
const credentialsParam = values.awsCredentialsType
|
||||
? `&via=${values.awsCredentialsType}`
|
||||
: "";
|
||||
router.push(
|
||||
`/providers/add-credentials?type=${providerType}&id=${id}${credentialsParam}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleNextStep = () => {
|
||||
setPrevStep((prev) => prev + 1);
|
||||
};
|
||||
|
||||
const handleBackStep = () => {
|
||||
setPrevStep((prev) => prev - 1);
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmitClient)}
|
||||
className="flex flex-col space-y-4"
|
||||
>
|
||||
{prevStep === 1 && (
|
||||
<>
|
||||
{/* Select a provider */}
|
||||
<RadioGroupProvider
|
||||
control={form.control}
|
||||
isInvalid={!!form.formState.errors.providerType}
|
||||
errorMessage={form.formState.errors.providerType?.message}
|
||||
/>
|
||||
{/* Provider UID */}
|
||||
<CustomInput
|
||||
control={form.control}
|
||||
name="providerUid"
|
||||
type="text"
|
||||
label="Provider UID"
|
||||
labelPlacement="inside"
|
||||
placeholder={"Enter the provider UID"}
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={!!form.formState.errors.providerUid}
|
||||
/>
|
||||
{/* Provider alias */}
|
||||
<CustomInput
|
||||
control={form.control}
|
||||
name="providerAlias"
|
||||
type="text"
|
||||
label="Provider alias (optional)"
|
||||
labelPlacement="inside"
|
||||
placeholder={"Enter the provider alias"}
|
||||
variant="bordered"
|
||||
isRequired={false}
|
||||
isInvalid={!!form.formState.errors.providerAlias}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
{prevStep === 2 && (
|
||||
<>
|
||||
{/* Select AWS credentials type */}
|
||||
<RadioGroupAWSViaCredentialsForm
|
||||
control={form.control}
|
||||
isInvalid={!!form.formState.errors.awsCredentialsType}
|
||||
errorMessage={form.formState.errors.awsCredentialsType?.message}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="flex w-full justify-end sm:space-x-6">
|
||||
{prevStep === 2 && (
|
||||
<CustomButton
|
||||
type="button"
|
||||
ariaLabel="Back"
|
||||
className="w-1/2 bg-transparent"
|
||||
variant="faded"
|
||||
size="lg"
|
||||
radius="lg"
|
||||
onPress={handleBackStep}
|
||||
startContent={!isLoading && <ChevronLeftIcon size={24} />}
|
||||
isDisabled={isLoading}
|
||||
>
|
||||
<span>Back</span>
|
||||
</CustomButton>
|
||||
)}
|
||||
|
||||
<CustomButton
|
||||
type="button"
|
||||
ariaLabel={
|
||||
prevStep === 1 && providerType === "aws" ? "Next" : "Save"
|
||||
}
|
||||
className="w-1/2"
|
||||
variant="solid"
|
||||
color="action"
|
||||
size="lg"
|
||||
isLoading={isLoading}
|
||||
startContent={
|
||||
!isLoading &&
|
||||
!(prevStep === 1 && providerType === "aws") && (
|
||||
<SaveIcon size={24} />
|
||||
)
|
||||
}
|
||||
endContent={
|
||||
!isLoading &&
|
||||
prevStep === 1 &&
|
||||
providerType === "aws" && <ChevronRightIcon size={24} />
|
||||
}
|
||||
onPress={() => {
|
||||
if (prevStep === 1 && providerType === "aws") {
|
||||
handleNextStep();
|
||||
} else {
|
||||
form.handleSubmit(onSubmitClient)();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{isLoading ? (
|
||||
<>Loading</>
|
||||
) : (
|
||||
<span>
|
||||
{prevStep === 1 && providerType === "aws" ? "Next" : "Save"}
|
||||
</span>
|
||||
)}
|
||||
</CustomButton>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
export * from "./connect-account-form";
|
||||
export * from "./launch-scan-form";
|
||||
export * from "./radio-group-aws-via-credentials-form";
|
||||
export * from "./test-connection-form";
|
||||
export * from "./via-credentials-form";
|
||||
export * from "./via-role-form";
|
||||
@@ -0,0 +1,162 @@
|
||||
"use client";
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import { scanOnDemand } from "@/actions/scans/scans";
|
||||
import { AddIcon } from "@/components/icons";
|
||||
import { CustomButton } from "@/components/ui/custom";
|
||||
import { Form } from "@/components/ui/form";
|
||||
import { ProviderProps } from "@/types"; // Asegúrate de importar la interfaz correcta
|
||||
import { launchScanFormSchema } from "@/types/formSchemas";
|
||||
|
||||
import { ProviderInfo } from "../../provider-info";
|
||||
|
||||
type FormValues = z.infer<ReturnType<typeof launchScanFormSchema>>;
|
||||
|
||||
interface LaunchScanFormProps {
|
||||
searchParams: { type: string; id: string };
|
||||
providerData: {
|
||||
data: {
|
||||
type: string;
|
||||
id: string;
|
||||
attributes: ProviderProps["attributes"];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export const LaunchScanForm = ({
|
||||
searchParams,
|
||||
providerData,
|
||||
}: LaunchScanFormProps) => {
|
||||
const providerType = searchParams.type;
|
||||
const providerId = searchParams.id;
|
||||
|
||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
||||
const router = useRouter();
|
||||
|
||||
const formSchema = launchScanFormSchema();
|
||||
|
||||
const form = useForm<FormValues>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
providerId,
|
||||
providerType,
|
||||
scannerArgs: {
|
||||
checksToExecute: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// const isLoading = form.formState.isSubmitting;
|
||||
|
||||
const onSubmitClient = async (values: FormValues) => {
|
||||
const formData = new FormData();
|
||||
formData.append("providerId", values.providerId);
|
||||
|
||||
// Generate default scan name using provider type and current date
|
||||
const date = new Date();
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, "0");
|
||||
const day = date.getDate().toString().padStart(2, "0");
|
||||
const year = date.getFullYear();
|
||||
const defaultScanName = `${providerType}:${month}/${day}/${year}`;
|
||||
|
||||
formData.append("scanName", defaultScanName);
|
||||
|
||||
try {
|
||||
const data = await scanOnDemand(formData);
|
||||
|
||||
if (data.error) {
|
||||
setApiErrorMessage(data.error);
|
||||
form.setError("providerId", {
|
||||
type: "server",
|
||||
message: data.error,
|
||||
});
|
||||
} else {
|
||||
router.push("/scans");
|
||||
}
|
||||
} catch (error) {
|
||||
form.setError("providerId", {
|
||||
type: "server",
|
||||
message: "An unexpected error occurred. Please try again.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmitClient)}
|
||||
className="flex flex-col space-y-4"
|
||||
>
|
||||
<div className="text-left">
|
||||
<div className="text-2xl font-bold leading-9 text-default-foreground">
|
||||
Scan started
|
||||
</div>
|
||||
<div className="py-2 text-default-500">
|
||||
The scan has just started. From now on, a new scan will be launched
|
||||
every 24 hours, starting from this moment.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{apiErrorMessage && (
|
||||
<div className="mt-4 rounded-md bg-red-100 p-3 text-red-700">
|
||||
<p>{apiErrorMessage.toLowerCase()}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<ProviderInfo
|
||||
connected={providerData.data.attributes.connection.connected}
|
||||
provider={providerData.data.attributes.provider}
|
||||
providerAlias={providerData.data.attributes.alias}
|
||||
/>
|
||||
|
||||
<input type="hidden" name="providerId" value={providerId} />
|
||||
<input type="hidden" name="providerType" value={providerType} />
|
||||
|
||||
{/* <div className="flex w-full justify-end sm:space-x-6">
|
||||
<CustomButton
|
||||
type="submit"
|
||||
ariaLabel={"Save"}
|
||||
className="w-1/2"
|
||||
variant="solid"
|
||||
color="action"
|
||||
size="lg"
|
||||
isLoading={isLoading}
|
||||
startContent={!isLoading && <ScheduleIcon size={24} />}
|
||||
isDisabled={true}
|
||||
>
|
||||
<span>Schedule</span>
|
||||
</CustomButton>
|
||||
<CustomButton
|
||||
type="submit"
|
||||
ariaLabel={"Save"}
|
||||
className="w-1/2"
|
||||
variant="solid"
|
||||
color="action"
|
||||
size="lg"
|
||||
isLoading={isLoading}
|
||||
endContent={!isLoading && <RocketIcon size={24} />}
|
||||
>
|
||||
{isLoading ? <>Loading</> : <span>Start now</span>}
|
||||
</CustomButton>
|
||||
</div> */}
|
||||
<div className="flex w-full items-center justify-end">
|
||||
<CustomButton
|
||||
asLink="/scans"
|
||||
ariaLabel="Go to Scans page"
|
||||
variant="solid"
|
||||
color="action"
|
||||
size="md"
|
||||
endContent={<AddIcon size={20} />}
|
||||
>
|
||||
Go to Scans
|
||||
</CustomButton>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
"use client";
|
||||
|
||||
import { RadioGroup } from "@nextui-org/react";
|
||||
import React from "react";
|
||||
import { Control, Controller } from "react-hook-form";
|
||||
|
||||
import { CustomRadio } from "@/components/ui/custom";
|
||||
import { FormMessage } from "@/components/ui/form";
|
||||
|
||||
import { FormValues } from "./connect-account-form";
|
||||
|
||||
type RadioGroupAWSViaCredentialsFormProps = {
|
||||
control: Control<FormValues>;
|
||||
isInvalid: boolean;
|
||||
errorMessage?: string;
|
||||
};
|
||||
|
||||
export const RadioGroupAWSViaCredentialsForm = ({
|
||||
control,
|
||||
isInvalid,
|
||||
errorMessage,
|
||||
}: RadioGroupAWSViaCredentialsFormProps) => {
|
||||
return (
|
||||
<Controller
|
||||
name="awsCredentialsType"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<RadioGroup
|
||||
className="flex flex-wrap"
|
||||
isInvalid={isInvalid}
|
||||
{...field}
|
||||
value={field.value || ""}
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<span className="text-sm text-default-500">Using IAM Role</span>
|
||||
<CustomRadio description="Connect assuming IAM Role" value="role">
|
||||
<div className="flex items-center">
|
||||
<span className="ml-2">Connect assuming IAM Role</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
<span className="text-sm text-default-500">
|
||||
Using Credentials
|
||||
</span>
|
||||
<CustomRadio
|
||||
description="Connect via Credentials"
|
||||
value="credentials"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span className="ml-2">Connect via Credentials</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
{errorMessage && (
|
||||
<FormMessage className="text-system-error dark:text-system-error">
|
||||
{errorMessage}
|
||||
</FormMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,286 @@
|
||||
"use client";
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { Icon } from "@iconify/react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
|
||||
import {
|
||||
checkConnectionProvider,
|
||||
deleteCredentials,
|
||||
} from "@/actions/providers";
|
||||
import { scanOnDemand } from "@/actions/scans";
|
||||
import { getTask } from "@/actions/task/tasks";
|
||||
import { CheckIcon, SaveIcon } from "@/components/icons";
|
||||
import { useToast } from "@/components/ui";
|
||||
import { CustomButton } from "@/components/ui/custom";
|
||||
import { Form } from "@/components/ui/form";
|
||||
import { checkTaskStatus } from "@/lib/helper";
|
||||
import { ApiError, testConnectionFormSchema } from "@/types";
|
||||
|
||||
import { ProviderInfo } from "../..";
|
||||
|
||||
type FormValues = z.infer<typeof testConnectionFormSchema>;
|
||||
|
||||
export const TestConnectionForm = ({
|
||||
searchParams,
|
||||
providerData,
|
||||
}: {
|
||||
searchParams: { type: string; id: string };
|
||||
providerData: {
|
||||
data: {
|
||||
id: string;
|
||||
type: string;
|
||||
attributes: {
|
||||
connection: {
|
||||
connected: boolean | null;
|
||||
last_checked_at: string | null;
|
||||
};
|
||||
provider: "aws" | "azure" | "gcp" | "kubernetes";
|
||||
alias: string;
|
||||
scanner_args: Record<string, any>;
|
||||
};
|
||||
relationships: {
|
||||
secret: {
|
||||
data: {
|
||||
type: string;
|
||||
id: string;
|
||||
} | null;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}) => {
|
||||
const { toast } = useToast();
|
||||
const router = useRouter();
|
||||
const providerType = searchParams.type;
|
||||
const providerId = searchParams.id;
|
||||
console.log({ providerData }, "providerData from test connection form");
|
||||
const formSchema = testConnectionFormSchema;
|
||||
const [apiErrorMessage, setApiErrorMessage] = useState<string | null>(null);
|
||||
const [connectionStatus, setConnectionStatus] = useState<{
|
||||
connected: boolean;
|
||||
error: string | null;
|
||||
} | null>(null);
|
||||
const [isResettingCredentials, setIsResettingCredentials] = useState(false);
|
||||
|
||||
const form = useForm<FormValues>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
providerId,
|
||||
},
|
||||
});
|
||||
|
||||
const isLoading = form.formState.isSubmitting;
|
||||
|
||||
const onSubmitClient = async (values: FormValues) => {
|
||||
console.log({ values }, "values from test connection form");
|
||||
const formData = new FormData();
|
||||
formData.append("providerId", values.providerId);
|
||||
|
||||
const data = await checkConnectionProvider(formData);
|
||||
|
||||
if (data?.errors && data.errors.length > 0) {
|
||||
data.errors.forEach((error: ApiError) => {
|
||||
const errorMessage = error.detail;
|
||||
|
||||
switch (errorMessage) {
|
||||
case "Not found.":
|
||||
setApiErrorMessage(errorMessage);
|
||||
break;
|
||||
default:
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: `Error ${error.status}`,
|
||||
description: errorMessage,
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
const taskId = data.data.id;
|
||||
setApiErrorMessage(null);
|
||||
|
||||
// Use the helper function to check the task status
|
||||
const taskResult = await checkTaskStatus(taskId);
|
||||
|
||||
if (taskResult.completed) {
|
||||
const task = await getTask(taskId);
|
||||
const { connected, error } = task.data.attributes.result;
|
||||
|
||||
setConnectionStatus({
|
||||
connected,
|
||||
error: connected ? null : error || "Unknown error",
|
||||
});
|
||||
|
||||
if (connected) {
|
||||
try {
|
||||
const data = await scanOnDemand(formData);
|
||||
|
||||
if (data.error) {
|
||||
setApiErrorMessage(data.error);
|
||||
form.setError("providerId", {
|
||||
type: "server",
|
||||
message: data.error,
|
||||
});
|
||||
} else {
|
||||
router.push(
|
||||
`/providers/launch-scan?type=${providerType}&id=${providerId}`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
form.setError("providerId", {
|
||||
type: "server",
|
||||
message: "An unexpected error occurred. Please try again.",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setConnectionStatus({
|
||||
connected: false,
|
||||
error: error || "Connection failed, please review credentials.",
|
||||
});
|
||||
}
|
||||
} else {
|
||||
setConnectionStatus({
|
||||
connected: false,
|
||||
error: taskResult.error || "Unknown error",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const onResetCredentials = async () => {
|
||||
setIsResettingCredentials(true);
|
||||
|
||||
// Check if provider the provider has no credentials
|
||||
const providerSecretId =
|
||||
providerData?.data?.relationships?.secret?.data?.id;
|
||||
const hasNoCredentials = !providerSecretId;
|
||||
|
||||
if (hasNoCredentials) {
|
||||
// If no credentials, redirect to add credentials page
|
||||
router.push(
|
||||
`/providers/add-credentials?type=${providerType}&id=${providerId}`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// If provider has credentials, delete them first
|
||||
try {
|
||||
await deleteCredentials(providerSecretId);
|
||||
// After successful deletion, redirect to add credentials page
|
||||
router.push(
|
||||
`/providers/add-credentials?type=${providerType}&id=${providerId}`,
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Failed to delete credentials:", error);
|
||||
} finally {
|
||||
setIsResettingCredentials(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmitClient)}
|
||||
className="flex flex-col space-y-4"
|
||||
>
|
||||
<div className="text-left">
|
||||
<div className="text-2xl font-bold leading-9 text-default-foreground">
|
||||
Test connection
|
||||
</div>
|
||||
<p className="py-2 text-default-500">
|
||||
Ensure all required credentials and configurations are completed
|
||||
accurately. A successful connection will enable the option to
|
||||
initiate a scan in the following step.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{apiErrorMessage && (
|
||||
<div className="mt-4 rounded-md bg-red-100 p-3 text-danger">
|
||||
<p>{`Provider ID ${apiErrorMessage.toLowerCase()}. Please check and try again.`}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{connectionStatus && !connectionStatus.connected && (
|
||||
<>
|
||||
<div className="flex items-center gap-4 rounded-lg border border-red-200 bg-red-50 p-4">
|
||||
<div className="flex items-center">
|
||||
<Icon
|
||||
icon="heroicons:exclamation-circle"
|
||||
className="h-5 w-5 text-danger"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center">
|
||||
<p className="text-danger">
|
||||
{connectionStatus.error || "Unknown error"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-md text-danger">
|
||||
It seems there was an issue with your credentials. Please review
|
||||
your credentials and try again.
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
|
||||
<ProviderInfo
|
||||
connected={providerData.data.attributes.connection.connected}
|
||||
provider={providerData.data.attributes.provider}
|
||||
providerAlias={providerData.data.attributes.alias}
|
||||
/>
|
||||
|
||||
<input type="hidden" name="providerId" value={providerId} />
|
||||
|
||||
<div className="flex w-full justify-end sm:space-x-6">
|
||||
{apiErrorMessage ? (
|
||||
<Link
|
||||
href="/providers"
|
||||
className="mr-3 flex w-fit items-center justify-center space-x-2 rounded-lg border border-solid border-gray-200 px-4 py-2 hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||
>
|
||||
<Icon
|
||||
icon="icon-park-outline:close-small"
|
||||
className="h-5 w-5 text-gray-600 dark:text-gray-400"
|
||||
/>
|
||||
<span>Back to providers</span>
|
||||
</Link>
|
||||
) : connectionStatus?.error ? (
|
||||
<CustomButton
|
||||
onPress={onResetCredentials}
|
||||
type="button"
|
||||
ariaLabel={"Save"}
|
||||
className="w-1/2"
|
||||
variant="solid"
|
||||
color="warning"
|
||||
size="lg"
|
||||
isLoading={isResettingCredentials}
|
||||
startContent={!isResettingCredentials && <CheckIcon size={24} />}
|
||||
isDisabled={isResettingCredentials}
|
||||
>
|
||||
{isResettingCredentials ? (
|
||||
<>Loading</>
|
||||
) : (
|
||||
<span>Reset credentials</span>
|
||||
)}
|
||||
</CustomButton>
|
||||
) : (
|
||||
<CustomButton
|
||||
type="submit"
|
||||
ariaLabel={"Save"}
|
||||
className="w-1/2"
|
||||
variant="solid"
|
||||
color="action"
|
||||
size="lg"
|
||||
isLoading={isLoading}
|
||||
startContent={!isLoading && <SaveIcon size={24} />}
|
||||
>
|
||||
{isLoading ? <>Loading</> : <span>Test connection</span>}
|
||||
</CustomButton>
|
||||
)}
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,207 @@
|
||||
"use client";
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { SaveIcon } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Control, useForm } from "react-hook-form";
|
||||
import * as z from "zod";
|
||||
|
||||
import { addCredentialsProvider } from "@/actions/providers/providers";
|
||||
import { useToast } from "@/components/ui";
|
||||
import { CustomButton } from "@/components/ui/custom";
|
||||
import { Form } from "@/components/ui/form";
|
||||
import {
|
||||
addCredentialsFormSchema,
|
||||
ApiError,
|
||||
AWSCredentials,
|
||||
AzureCredentials,
|
||||
GCPCredentials,
|
||||
KubernetesCredentials,
|
||||
} from "@/types";
|
||||
|
||||
import { AWScredentialsForm } from "./via-credentials/aws-credentials-form";
|
||||
import { AzureCredentialsForm } from "./via-credentials/azure-credentials-form";
|
||||
import { GCPcredentialsForm } from "./via-credentials/gcp-credentials-form";
|
||||
import { KubernetesCredentialsForm } from "./via-credentials/k8s-credentials-form";
|
||||
|
||||
type CredentialsFormSchema = z.infer<
|
||||
ReturnType<typeof addCredentialsFormSchema>
|
||||
>;
|
||||
|
||||
// Add this type intersection to include all fields
|
||||
type FormType = CredentialsFormSchema &
|
||||
AWSCredentials &
|
||||
AzureCredentials &
|
||||
GCPCredentials &
|
||||
KubernetesCredentials;
|
||||
|
||||
export const ViaCredentialsForm = ({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: { type: string; id: string };
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const { toast } = useToast();
|
||||
|
||||
const providerType = searchParams.type;
|
||||
const providerId = searchParams.id;
|
||||
const formSchema = addCredentialsFormSchema(providerType);
|
||||
|
||||
const form = useForm<FormType>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
providerId,
|
||||
providerType,
|
||||
...(providerType === "aws"
|
||||
? {
|
||||
aws_access_key_id: "",
|
||||
aws_secret_access_key: "",
|
||||
aws_session_token: "",
|
||||
}
|
||||
: providerType === "azure"
|
||||
? {
|
||||
client_id: "",
|
||||
client_secret: "",
|
||||
tenant_id: "",
|
||||
}
|
||||
: providerType === "gcp"
|
||||
? {
|
||||
client_id: "",
|
||||
client_secret: "",
|
||||
refresh_token: "",
|
||||
}
|
||||
: providerType === "kubernetes"
|
||||
? {
|
||||
kubeconfig_content: "",
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
|
||||
const isLoading = form.formState.isSubmitting;
|
||||
|
||||
const onSubmitClient = async (values: FormType) => {
|
||||
console.log("via credentials form", values);
|
||||
const formData = new FormData();
|
||||
|
||||
Object.entries(values).forEach(
|
||||
([key, value]) => value !== undefined && formData.append(key, value),
|
||||
);
|
||||
|
||||
const data = await addCredentialsProvider(formData);
|
||||
|
||||
if (data?.errors && data.errors.length > 0) {
|
||||
data.errors.forEach((error: ApiError) => {
|
||||
const errorMessage = error.detail;
|
||||
switch (error.source.pointer) {
|
||||
case "/data/attributes/secret/aws_access_key_id":
|
||||
form.setError("aws_access_key_id", {
|
||||
type: "server",
|
||||
message: errorMessage,
|
||||
});
|
||||
break;
|
||||
case "/data/attributes/secret/aws_secret_access_key":
|
||||
form.setError("aws_secret_access_key", {
|
||||
type: "server",
|
||||
message: errorMessage,
|
||||
});
|
||||
break;
|
||||
case "/data/attributes/secret/aws_session_token":
|
||||
form.setError("aws_session_token", {
|
||||
type: "server",
|
||||
message: errorMessage,
|
||||
});
|
||||
break;
|
||||
case "/data/attributes/secret/client_id":
|
||||
form.setError("client_id", {
|
||||
type: "server",
|
||||
message: errorMessage,
|
||||
});
|
||||
break;
|
||||
case "/data/attributes/secret/client_secret":
|
||||
form.setError("client_secret", {
|
||||
type: "server",
|
||||
message: errorMessage,
|
||||
});
|
||||
break;
|
||||
case "/data/attributes/secret/tenant_id":
|
||||
form.setError("tenant_id", {
|
||||
type: "server",
|
||||
message: errorMessage,
|
||||
});
|
||||
break;
|
||||
case "/data/attributes/secret/kubeconfig_content":
|
||||
form.setError("kubeconfig_content", {
|
||||
type: "server",
|
||||
message: errorMessage,
|
||||
});
|
||||
break;
|
||||
case "/data/attributes/name":
|
||||
form.setError("secretName", {
|
||||
type: "server",
|
||||
message: errorMessage,
|
||||
});
|
||||
break;
|
||||
default:
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Oops! Something went wrong",
|
||||
description: errorMessage,
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
router.push(
|
||||
`/providers/test-connection?type=${providerType}&id=${providerId}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmitClient)}
|
||||
className="flex flex-col space-y-4"
|
||||
>
|
||||
<input type="hidden" name="providerId" value={providerId} />
|
||||
<input type="hidden" name="providerType" value={providerType} />
|
||||
|
||||
{providerType === "aws" && (
|
||||
<AWScredentialsForm
|
||||
control={form.control as unknown as Control<AWSCredentials>}
|
||||
/>
|
||||
)}
|
||||
{providerType === "azure" && (
|
||||
<AzureCredentialsForm
|
||||
control={form.control as unknown as Control<AzureCredentials>}
|
||||
/>
|
||||
)}
|
||||
{providerType === "gcp" && (
|
||||
<GCPcredentialsForm
|
||||
control={form.control as unknown as Control<GCPCredentials>}
|
||||
/>
|
||||
)}
|
||||
{providerType === "kubernetes" && (
|
||||
<KubernetesCredentialsForm
|
||||
control={form.control as unknown as Control<KubernetesCredentials>}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="flex w-full justify-end sm:space-x-6">
|
||||
<CustomButton
|
||||
type="submit"
|
||||
ariaLabel={"Save"}
|
||||
className="w-1/2"
|
||||
variant="solid"
|
||||
color="action"
|
||||
size="lg"
|
||||
isLoading={isLoading}
|
||||
startContent={!isLoading && <SaveIcon size={24} />}
|
||||
>
|
||||
{isLoading ? <>Loading</> : <span>Save</span>}
|
||||
</CustomButton>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { AWSCredentials } from "@/types";
|
||||
|
||||
export const AWScredentialsForm = ({
|
||||
control,
|
||||
}: {
|
||||
control: Control<AWSCredentials>;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div className="text-left">
|
||||
<div className="text-2xl font-bold leading-9 text-default-foreground">
|
||||
Connect via Credentials
|
||||
</div>
|
||||
<div className="py-2 text-default-500">
|
||||
Please provide the information for your AWS credentials.
|
||||
</div>
|
||||
</div>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name="aws_access_key_id"
|
||||
type="password"
|
||||
label="AWS Access Key ID"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the AWS Access Key ID"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={!!control._formState.errors.aws_access_key_id}
|
||||
/>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name="aws_secret_access_key"
|
||||
type="password"
|
||||
label="AWS Secret Access Key"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the AWS Secret Access Key"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={!!control._formState.errors.aws_secret_access_key}
|
||||
/>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name="aws_session_token"
|
||||
type="password"
|
||||
label="AWS Session Token"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the AWS Session Token"
|
||||
variant="bordered"
|
||||
isRequired={false}
|
||||
isInvalid={!!control._formState.errors.aws_session_token}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { AzureCredentials } from "@/types";
|
||||
|
||||
export const AzureCredentialsForm = ({
|
||||
control,
|
||||
}: {
|
||||
control: Control<AzureCredentials>;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div className="text-left">
|
||||
<div className="text-2xl font-bold leading-9 text-default-foreground">
|
||||
Connect via Credentials
|
||||
</div>
|
||||
<div className="py-2 text-default-500">
|
||||
Please provide the information for your Azure credentials.
|
||||
</div>
|
||||
</div>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name="client_id"
|
||||
type="text"
|
||||
label="Client ID"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Client ID"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={!!control._formState.errors.client_id}
|
||||
/>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name="client_secret"
|
||||
type="password"
|
||||
label="Client Secret"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Client Secret"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={!!control._formState.errors.client_secret}
|
||||
/>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name="tenant_id"
|
||||
type="text"
|
||||
label="Tenant ID"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Tenant ID"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={!!control._formState.errors.tenant_id}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { GCPCredentials } from "@/types";
|
||||
|
||||
export const GCPcredentialsForm = ({
|
||||
control,
|
||||
}: {
|
||||
control: Control<GCPCredentials>;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div className="text-left">
|
||||
<div className="text-2xl font-bold leading-9 text-default-foreground">
|
||||
Connect via Credentials
|
||||
</div>
|
||||
<div className="py-2 text-default-500">
|
||||
Please provide the information for your GCP credentials.
|
||||
</div>
|
||||
</div>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name="client_id"
|
||||
type="text"
|
||||
label="Client ID"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Client ID"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={!!control._formState.errors.client_id}
|
||||
/>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name="client_secret"
|
||||
type="password"
|
||||
label="Client Secret"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Client Secret"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={!!control._formState.errors.client_secret}
|
||||
/>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name="refresh_token"
|
||||
type="password"
|
||||
label="Refresh Token"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Refresh Token"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={!!control._formState.errors.refresh_token}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from "./aws-credentials-form";
|
||||
export * from "./azure-credentials-form";
|
||||
export * from "./gcp-credentials-form";
|
||||
export * from "./k8s-credentials-form";
|
||||
@@ -0,0 +1,34 @@
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { KubernetesCredentials } from "@/types";
|
||||
|
||||
export const KubernetesCredentialsForm = ({
|
||||
control,
|
||||
}: {
|
||||
control: Control<KubernetesCredentials>;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div className="text-left">
|
||||
<div className="text-2xl font-bold leading-9 text-default-foreground">
|
||||
Connect via Credentials
|
||||
</div>
|
||||
<div className="py-2 text-default-500">
|
||||
Please provide the information for your Kubernetes credentials.
|
||||
</div>
|
||||
</div>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name="kubeconfig_content"
|
||||
type="text"
|
||||
label="Kubeconfig Content"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Kubeconfig Content"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={!!control._formState.errors.kubeconfig_content}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,125 @@
|
||||
"use client";
|
||||
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { SaveIcon } from "lucide-react";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Control, useForm } from "react-hook-form";
|
||||
import * as z from "zod";
|
||||
|
||||
import { addCredentialsProvider } from "@/actions/providers/providers";
|
||||
import { useToast } from "@/components/ui";
|
||||
import { CustomButton } from "@/components/ui/custom";
|
||||
import { Form } from "@/components/ui/form";
|
||||
import {
|
||||
addCredentialsRoleFormSchema,
|
||||
ApiError,
|
||||
AWSCredentialsRole,
|
||||
} from "@/types";
|
||||
|
||||
import { AWSCredentialsRoleForm } from "./via-role/aws-role-form";
|
||||
|
||||
export const ViaRoleForm = ({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: { type: string; id: string };
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const { toast } = useToast();
|
||||
|
||||
const providerType = searchParams.type;
|
||||
const providerId = searchParams.id;
|
||||
|
||||
const formSchema = addCredentialsRoleFormSchema(providerType);
|
||||
type FormSchemaType = z.infer<typeof formSchema>;
|
||||
|
||||
const form = useForm<FormSchemaType>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
providerId,
|
||||
providerType,
|
||||
...(providerType === "aws"
|
||||
? {
|
||||
role_arn: "",
|
||||
aws_access_key_id: "",
|
||||
aws_secret_access_key: "",
|
||||
aws_session_token: "",
|
||||
session_duration: 3600,
|
||||
external_id: "",
|
||||
role_session_name: "",
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
});
|
||||
|
||||
const isLoading = form.formState.isSubmitting;
|
||||
|
||||
const onSubmitClient = async (values: FormSchemaType) => {
|
||||
console.log("via ROLE form", values);
|
||||
const formData = new FormData();
|
||||
|
||||
Object.entries(values).forEach(
|
||||
([key, value]) =>
|
||||
value !== undefined && formData.append(key, String(value)),
|
||||
);
|
||||
|
||||
const data = await addCredentialsProvider(formData);
|
||||
|
||||
if (data?.errors && data.errors.length > 0) {
|
||||
data.errors.forEach((error: ApiError) => {
|
||||
const errorMessage = error.detail;
|
||||
switch (error.source.pointer) {
|
||||
case "/data/attributes/secret/role_arn":
|
||||
form.setError("role_arn" as keyof FormSchemaType, {
|
||||
type: "server",
|
||||
message: errorMessage,
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: "Oops! Something went wrong",
|
||||
description: errorMessage,
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
router.push(
|
||||
`/providers/test-connection?type=${providerType}&id=${providerId}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmitClient)}
|
||||
className="flex flex-col space-y-4"
|
||||
>
|
||||
<input type="hidden" name="providerId" value={providerId} />
|
||||
<input type="hidden" name="providerType" value={providerType} />
|
||||
|
||||
{providerType === "aws" && (
|
||||
<AWSCredentialsRoleForm
|
||||
control={form.control as unknown as Control<AWSCredentialsRole>}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="flex w-full justify-end sm:space-x-6">
|
||||
<CustomButton
|
||||
type="submit"
|
||||
ariaLabel={"Save"}
|
||||
className="w-1/2"
|
||||
variant="solid"
|
||||
color="action"
|
||||
size="lg"
|
||||
isLoading={isLoading}
|
||||
startContent={!isLoading && <SaveIcon size={24} />}
|
||||
>
|
||||
{isLoading ? <>Loading</> : <span>Save</span>}
|
||||
</CustomButton>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,105 @@
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { AWSCredentialsRole } from "@/types";
|
||||
|
||||
export const AWSCredentialsRoleForm = ({
|
||||
control,
|
||||
}: {
|
||||
control: Control<AWSCredentialsRole>;
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<div className="mb-4 text-left">
|
||||
<div className="text-2xl font-bold leading-9 text-default-foreground">
|
||||
Connect assuming IAM Role
|
||||
</div>
|
||||
<div className="py-2 text-default-500">
|
||||
Please provide the information for your AWS credentials.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CustomInput
|
||||
control={control}
|
||||
name="role_arn"
|
||||
type="text"
|
||||
label="Role ARN"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Role ARN"
|
||||
variant="bordered"
|
||||
isRequired
|
||||
isInvalid={!!control._formState.errors.role_arn}
|
||||
/>
|
||||
<span className="text-sm text-default-500">Optional fields</span>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name="aws_access_key_id"
|
||||
type="password"
|
||||
label="AWS Access Key ID"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the AWS Access Key ID"
|
||||
variant="bordered"
|
||||
isRequired={false}
|
||||
isInvalid={!!control._formState.errors.aws_access_key_id}
|
||||
/>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name="aws_secret_access_key"
|
||||
type="password"
|
||||
label="AWS Secret Access Key"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the AWS Secret Access Key"
|
||||
variant="bordered"
|
||||
isRequired={false}
|
||||
isInvalid={!!control._formState.errors.aws_secret_access_key}
|
||||
/>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name="aws_session_token"
|
||||
type="password"
|
||||
label="AWS Session Token"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the AWS Session Token"
|
||||
variant="bordered"
|
||||
isRequired={false}
|
||||
isInvalid={!!control._formState.errors.aws_session_token}
|
||||
/>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name="external_id"
|
||||
type="text"
|
||||
label="External ID"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the External ID"
|
||||
variant="bordered"
|
||||
isRequired={false}
|
||||
isInvalid={!!control._formState.errors.external_id}
|
||||
/>
|
||||
|
||||
<div className="mt-4 grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<CustomInput
|
||||
control={control}
|
||||
name="role_session_name"
|
||||
type="text"
|
||||
label="Role Session Name"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the Role Session Name"
|
||||
variant="bordered"
|
||||
isRequired={false}
|
||||
isInvalid={!!control._formState.errors.role_session_name}
|
||||
/>
|
||||
<CustomInput
|
||||
control={control}
|
||||
name="session_duration"
|
||||
type="number"
|
||||
label="Session Duration (seconds)"
|
||||
labelPlacement="inside"
|
||||
placeholder="Enter the session duration (default: 3600)"
|
||||
variant="bordered"
|
||||
isRequired={false}
|
||||
isInvalid={!!control._formState.errors.session_duration}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./aws-role-form";
|
||||
Reference in new issue
Block a user