mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-03-21 18:58:04 +00:00
refactor(ui): migrate provider wizard forms from HeroUI to shadcn (#10259)
This commit is contained in:
@@ -12,11 +12,12 @@ import {
|
||||
WIZARD_FOOTER_ACTION_TYPE,
|
||||
WizardFooterConfig,
|
||||
} from "@/components/providers/wizard/steps/footer-controls";
|
||||
import { WizardInputField } from "@/components/providers/workflow/forms/fields";
|
||||
import { Alert, AlertDescription } from "@/components/shadcn/alert";
|
||||
import { Button } from "@/components/shadcn/button/button";
|
||||
import { Checkbox } from "@/components/shadcn/checkbox/checkbox";
|
||||
import { Input } from "@/components/shadcn/input/input";
|
||||
import { TreeSpinner } from "@/components/shadcn/tree-view/tree-spinner";
|
||||
import { Form } from "@/components/ui/form";
|
||||
import { getAWSCredentialsTemplateLinks } from "@/lib";
|
||||
import { ORG_SETUP_PHASE, OrgSetupPhase } from "@/types/organizations";
|
||||
|
||||
@@ -68,14 +69,7 @@ export function OrgSetupForm({
|
||||
const [setupPhase, setSetupPhase] = useState<OrgSetupPhase>(initialPhase);
|
||||
const formId = "org-wizard-setup-form";
|
||||
|
||||
const {
|
||||
control,
|
||||
register,
|
||||
handleSubmit,
|
||||
formState: { errors, isSubmitting, isValid },
|
||||
setError,
|
||||
watch,
|
||||
} = useForm<OrgSetupFormData>({
|
||||
const form = useForm<OrgSetupFormData>({
|
||||
resolver: zodResolver(orgSetupSchema),
|
||||
mode: "onChange",
|
||||
reValidateMode: "onChange",
|
||||
@@ -86,10 +80,13 @@ export function OrgSetupForm({
|
||||
stackSetDeployed: false,
|
||||
},
|
||||
});
|
||||
const awsOrgIdField = register("awsOrgId", {
|
||||
setValueAs: (value: unknown) =>
|
||||
typeof value === "string" ? value.toLowerCase() : value,
|
||||
});
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
formState: { errors, isSubmitting, isValid },
|
||||
setError,
|
||||
watch,
|
||||
} = form;
|
||||
|
||||
const awsOrgId = watch("awsOrgId") || "";
|
||||
const isOrgIdValid = /^o-[a-z0-9]{10,32}$/.test(awsOrgId.trim());
|
||||
@@ -180,228 +177,209 @@ export function OrgSetupForm({
|
||||
}, [apiError, formId]);
|
||||
|
||||
return (
|
||||
<form
|
||||
id={formId}
|
||||
onSubmit={handleFormSubmit}
|
||||
className="flex flex-col gap-5"
|
||||
>
|
||||
{setupPhase === ORG_SETUP_PHASE.DETAILS && (
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<AWSProviderBadge size={32} />
|
||||
<h3 className="text-base font-semibold">
|
||||
Amazon Web Services (AWS) / Organization Details
|
||||
</h3>
|
||||
<Form {...form}>
|
||||
<form
|
||||
id={formId}
|
||||
onSubmit={handleFormSubmit}
|
||||
className="flex flex-col gap-5"
|
||||
>
|
||||
{setupPhase === ORG_SETUP_PHASE.DETAILS && (
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex items-center gap-4">
|
||||
<AWSProviderBadge size={32} />
|
||||
<h3 className="text-base font-semibold">
|
||||
Amazon Web Services (AWS) / Organization Details
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Enter the Organization ID for the accounts you want to add to
|
||||
Prowler.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Enter the Organization ID for the accounts you want to add to
|
||||
Prowler.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{setupPhase === ORG_SETUP_PHASE.ACCESS && (
|
||||
<div className="flex flex-col gap-8">
|
||||
<div className="flex items-center gap-4">
|
||||
<AWSProviderBadge size={32} />
|
||||
<h3 className="text-base font-semibold">
|
||||
Amazon Web Services (AWS) / Authentication Details
|
||||
</h3>
|
||||
{setupPhase === ORG_SETUP_PHASE.ACCESS && (
|
||||
<div className="flex flex-col gap-8">
|
||||
<div className="flex items-center gap-4">
|
||||
<AWSProviderBadge size={32} />
|
||||
<h3 className="text-base font-semibold">
|
||||
Amazon Web Services (AWS) / Authentication Details
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{setupPhase === ORG_SETUP_PHASE.ACCESS && isSubmitting && (
|
||||
<div className="flex min-h-[220px] items-center justify-center">
|
||||
<div className="flex items-center gap-3 py-2">
|
||||
<TreeSpinner className="size-6" />
|
||||
<p className="text-sm font-medium">Gathering AWS Accounts...</p>
|
||||
{setupPhase === ORG_SETUP_PHASE.ACCESS && isSubmitting && (
|
||||
<div className="flex min-h-[220px] items-center justify-center">
|
||||
<div className="flex items-center gap-3 py-2">
|
||||
<TreeSpinner className="size-6" />
|
||||
<p className="text-sm font-medium">Gathering AWS Accounts...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{apiError && (
|
||||
<Alert variant="error">
|
||||
<AlertDescription className="text-text-error-primary">
|
||||
{apiError}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
{apiError && (
|
||||
<Alert variant="error">
|
||||
<AlertDescription className="text-text-error-primary">
|
||||
{apiError}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{setupPhase === ORG_SETUP_PHASE.DETAILS && (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label htmlFor="awsOrgId" className="text-sm font-medium">
|
||||
Organization ID
|
||||
</label>
|
||||
<Input
|
||||
id="awsOrgId"
|
||||
{setupPhase === ORG_SETUP_PHASE.DETAILS && (
|
||||
<div className="flex flex-col gap-4">
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name="awsOrgId"
|
||||
label="Organization ID"
|
||||
labelPlacement="outside"
|
||||
placeholder="e.g. o-123456789-abcdefg"
|
||||
required
|
||||
aria-required="true"
|
||||
isRequired
|
||||
normalizeValue={(value) => value.toLowerCase()}
|
||||
autoCapitalize="none"
|
||||
autoCorrect="off"
|
||||
spellCheck={false}
|
||||
{...awsOrgIdField}
|
||||
onInput={(event) => {
|
||||
const loweredValue = event.currentTarget.value.toLowerCase();
|
||||
if (event.currentTarget.value !== loweredValue) {
|
||||
event.currentTarget.value = loweredValue;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{errors.awsOrgId && (
|
||||
<span className="text-text-error-primary text-xs">
|
||||
{errors.awsOrgId.message}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label htmlFor="organizationName" className="text-sm font-medium">
|
||||
Name (optional)
|
||||
</label>
|
||||
<Input
|
||||
id="organizationName"
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name="organizationName"
|
||||
label="Name (optional)"
|
||||
labelPlacement="outside"
|
||||
placeholder=""
|
||||
{...register("organizationName")}
|
||||
isRequired={false}
|
||||
/>
|
||||
{errors.organizationName && (
|
||||
<span className="text-text-error-primary text-xs">
|
||||
{errors.organizationName.message}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="text-muted-foreground text-sm">
|
||||
If left blank, Prowler will use the Organization name stored in AWS.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{setupPhase === ORG_SETUP_PHASE.ACCESS && !isSubmitting && (
|
||||
<div className="flex flex-col gap-8">
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="text-text-neutral-primary text-sm leading-7 font-normal">
|
||||
1) Launch the Prowler CloudFormation StackSet in your AWS Console.
|
||||
<p className="text-muted-foreground text-sm">
|
||||
If left blank, Prowler will use the Organization name stored in
|
||||
AWS.
|
||||
</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
className="border-border-input-primary bg-bg-input-primary text-button-tertiary hover:bg-bg-input-primary active:bg-bg-input-primary h-12 w-full justify-start"
|
||||
disabled={!stackSetQuickLink}
|
||||
asChild
|
||||
>
|
||||
<a
|
||||
href={stackSetQuickLink || "#"}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
</div>
|
||||
)}
|
||||
|
||||
{setupPhase === ORG_SETUP_PHASE.ACCESS && !isSubmitting && (
|
||||
<div className="flex flex-col gap-8">
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="text-text-neutral-primary text-sm leading-7 font-normal">
|
||||
1) Launch the Prowler CloudFormation StackSet in your AWS
|
||||
Console.
|
||||
</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="lg"
|
||||
className="border-border-input-primary bg-bg-input-primary text-button-tertiary hover:bg-bg-input-primary active:bg-bg-input-primary h-12 w-full justify-start"
|
||||
disabled={!stackSetQuickLink}
|
||||
asChild
|
||||
>
|
||||
<ExternalLink className="size-5" />
|
||||
<span>
|
||||
Prowler CloudFormation StackSet for AWS Organizations
|
||||
</span>
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="text-text-neutral-primary text-sm leading-7 font-normal">
|
||||
2) Use the following Prowler External ID parameter in the
|
||||
StackSet.
|
||||
</p>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-text-neutral-tertiary text-xs">
|
||||
External ID:
|
||||
</span>
|
||||
<div className="bg-bg-neutral-tertiary border-border-input-primary flex h-10 max-w-full items-center gap-3 rounded-full border px-4">
|
||||
<span className="truncate text-xs font-medium">
|
||||
{stackSetExternalId || "Loading organization external ID..."}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
disabled={!stackSetExternalId}
|
||||
onClick={async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(stackSetExternalId);
|
||||
setIsExternalIdCopied(true);
|
||||
setTimeout(() => setIsExternalIdCopied(false), 1500);
|
||||
} catch {
|
||||
// Ignore clipboard errors (e.g., unsupported browser context).
|
||||
}
|
||||
}}
|
||||
className="text-text-neutral-secondary hover:text-text-neutral-primary shrink-0 transition-colors"
|
||||
aria-label="Copy external ID"
|
||||
<a
|
||||
href={stackSetQuickLink || "#"}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{isExternalIdCopied ? (
|
||||
<Check className="size-4" />
|
||||
) : (
|
||||
<Copy className="size-4" />
|
||||
)}
|
||||
</button>
|
||||
<ExternalLink className="size-5" />
|
||||
<span>
|
||||
Prowler CloudFormation StackSet for AWS Organizations
|
||||
</span>
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="text-text-neutral-primary text-sm leading-7 font-normal">
|
||||
2) Use the following Prowler External ID parameter in the
|
||||
StackSet.
|
||||
</p>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-text-neutral-tertiary text-xs">
|
||||
External ID:
|
||||
</span>
|
||||
<div className="bg-bg-neutral-tertiary border-border-input-primary flex h-10 max-w-full items-center gap-3 rounded-full border px-4">
|
||||
<span className="truncate text-xs font-medium">
|
||||
{stackSetExternalId ||
|
||||
"Loading organization external ID..."}
|
||||
</span>
|
||||
<button
|
||||
type="button"
|
||||
disabled={!stackSetExternalId}
|
||||
onClick={async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(stackSetExternalId);
|
||||
setIsExternalIdCopied(true);
|
||||
setTimeout(() => setIsExternalIdCopied(false), 1500);
|
||||
} catch {
|
||||
// Ignore clipboard errors (e.g., unsupported browser context).
|
||||
}
|
||||
}}
|
||||
className="text-text-neutral-secondary hover:text-text-neutral-primary shrink-0 transition-colors"
|
||||
aria-label="Copy external ID"
|
||||
>
|
||||
{isExternalIdCopied ? (
|
||||
<Check className="size-4" />
|
||||
) : (
|
||||
<Copy className="size-4" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="text-text-neutral-primary text-sm leading-7 font-normal">
|
||||
3) Copy the Prowler IAM Role ARN from AWS and confirm the StackSet
|
||||
is successfully deployed by clicking the checkbox below.
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
<p className="text-text-neutral-primary text-sm leading-7 font-normal">
|
||||
3) Copy the Prowler IAM Role ARN from AWS and confirm the
|
||||
StackSet is successfully deployed by clicking the checkbox
|
||||
below.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label htmlFor="roleArn" className="text-sm font-medium">
|
||||
Role ARN
|
||||
</label>
|
||||
<Input
|
||||
id="roleArn"
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name="roleArn"
|
||||
label="Role ARN"
|
||||
labelPlacement="outside"
|
||||
placeholder="e.g. arn:aws:iam::123456789012:role/ProwlerOrgRole"
|
||||
{...register("roleArn")}
|
||||
isRequired={false}
|
||||
requiredIndicator
|
||||
/>
|
||||
{errors.roleArn && (
|
||||
|
||||
<p className="text-text-neutral-tertiary text-sm">
|
||||
* It may take up to 60 seconds for AWS to generate the IAM Role
|
||||
ARN
|
||||
</p>
|
||||
|
||||
<div className="flex items-start gap-4">
|
||||
<Controller
|
||||
name="stackSetDeployed"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<Checkbox
|
||||
id="stackSetDeployed"
|
||||
className="mt-0.5"
|
||||
checked={field.value}
|
||||
onCheckedChange={(checked) =>
|
||||
field.onChange(Boolean(checked))
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor="stackSetDeployed"
|
||||
className="text-text-neutral-tertiary text-xs leading-5 font-normal"
|
||||
>
|
||||
The StackSet has been successfully deployed in AWS
|
||||
<span className="text-text-error-primary">*</span>
|
||||
</label>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{errors.stackSetDeployed && (
|
||||
<span className="text-text-error-primary text-xs">
|
||||
{errors.roleArn.message}
|
||||
{errors.stackSetDeployed.message}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="text-text-neutral-tertiary text-sm">
|
||||
* It may take up to 60 seconds for AWS to generate the IAM Role ARN
|
||||
</p>
|
||||
|
||||
<div className="flex items-start gap-4">
|
||||
<Controller
|
||||
name="stackSetDeployed"
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<Checkbox
|
||||
id="stackSetDeployed"
|
||||
className="mt-0.5"
|
||||
checked={field.value}
|
||||
onCheckedChange={(checked) =>
|
||||
field.onChange(Boolean(checked))
|
||||
}
|
||||
/>
|
||||
<label
|
||||
htmlFor="stackSetDeployed"
|
||||
className="text-text-neutral-primary text-sm leading-7 font-normal"
|
||||
>
|
||||
The StackSet has been successfully deployed in AWS
|
||||
</label>
|
||||
</>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{errors.stackSetDeployed && (
|
||||
<span className="text-text-error-primary text-xs">
|
||||
{errors.stackSetDeployed.message}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
)}
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { IdIcon } from "@/components/icons";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import { SnippetChip } from "@/components/ui/entities";
|
||||
import { CodeSnippet } from "@/components/ui/code-snippet/code-snippet";
|
||||
import { IntegrationType } from "@/types/integrations";
|
||||
|
||||
interface CredentialsRoleHelperProps {
|
||||
@@ -95,7 +95,7 @@ export const CredentialsRoleHelper = ({
|
||||
<span className="text-default-500 block text-xs font-medium">
|
||||
External ID:
|
||||
</span>
|
||||
<SnippetChip value={externalId} icon={<IdIcon size={16} />} />
|
||||
<CodeSnippet value={externalId} icon={<IdIcon size={16} />} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
"use client";
|
||||
|
||||
import { Divider } from "@heroui/divider";
|
||||
import { ChevronLeftIcon, ChevronRightIcon, Loader2 } from "lucide-react";
|
||||
import { useEffect } from "react";
|
||||
import { Control, UseFormSetValue } from "react-hook-form";
|
||||
|
||||
import { Button } from "@/components/shadcn";
|
||||
import { Separator } from "@/components/shadcn/separator/separator";
|
||||
import { Form } from "@/components/ui/form";
|
||||
import { useCredentialsForm } from "@/hooks/use-credentials-form";
|
||||
import { getAWSCredentialsTemplateLinks } from "@/lib";
|
||||
@@ -149,7 +149,7 @@ export const BaseCredentialsForm = ({
|
||||
|
||||
<ProviderTitleDocs providerType={providerType} />
|
||||
|
||||
<Divider />
|
||||
<Separator />
|
||||
|
||||
{providerType === "aws" && effectiveVia === "role" && (
|
||||
<AWSRoleCredentialsForm
|
||||
|
||||
@@ -9,10 +9,10 @@ import { z } from "zod";
|
||||
|
||||
import { addProvider } from "@/actions/providers/providers";
|
||||
import { AwsMethodSelector } from "@/components/providers/organizations/aws-method-selector";
|
||||
import { WizardInputField } from "@/components/providers/workflow/forms/fields";
|
||||
import { ProviderTitleDocs } from "@/components/providers/workflow/provider-title-docs";
|
||||
import { Button } from "@/components/shadcn";
|
||||
import { useToast } from "@/components/ui";
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { Form } from "@/components/ui/form";
|
||||
import { addProviderFormSchema, ApiError, ProviderType } from "@/types";
|
||||
|
||||
@@ -346,7 +346,7 @@ export const ConnectAccountForm = ({
|
||||
(providerType !== "aws" || awsMethod === "single") && (
|
||||
<>
|
||||
<ProviderTitleDocs providerType={providerType} />
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={form.control}
|
||||
name="providerUid"
|
||||
type="text"
|
||||
@@ -356,7 +356,7 @@ export const ConnectAccountForm = ({
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={form.control}
|
||||
name="providerAlias"
|
||||
type="text"
|
||||
|
||||
3
ui/components/providers/workflow/forms/fields/index.ts
Normal file
3
ui/components/providers/workflow/forms/fields/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { WizardInputField } from "./wizard-input-field";
|
||||
export { WizardRadioCard } from "./wizard-radio-card";
|
||||
export { WizardTextareaField } from "./wizard-textarea-field";
|
||||
@@ -0,0 +1,172 @@
|
||||
"use client";
|
||||
|
||||
import { Icon } from "@iconify/react";
|
||||
import { InputHTMLAttributes, useState } from "react";
|
||||
import { Control, FieldPath, FieldValues } from "react-hook-form";
|
||||
|
||||
import { Input } from "@/components/shadcn/input/input";
|
||||
import { FormControl, FormField, FormMessage } from "@/components/ui/form";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface WizardInputFieldProps<T extends FieldValues> {
|
||||
control: Control<T>;
|
||||
name: FieldPath<T>;
|
||||
label?: string;
|
||||
labelPlacement?: "inside" | "outside";
|
||||
variant?: "flat" | "bordered" | "underlined" | "faded";
|
||||
size?: "sm" | "md" | "lg";
|
||||
type?: string;
|
||||
placeholder?: string;
|
||||
password?: boolean;
|
||||
confirmPassword?: boolean;
|
||||
defaultValue?: string;
|
||||
isReadOnly?: boolean;
|
||||
isRequired?: boolean;
|
||||
isDisabled?: boolean;
|
||||
normalizeValue?: (value: string) => string;
|
||||
autoCapitalize?: InputHTMLAttributes<HTMLInputElement>["autoCapitalize"];
|
||||
autoCorrect?: InputHTMLAttributes<HTMLInputElement>["autoCorrect"];
|
||||
spellCheck?: boolean;
|
||||
requiredIndicator?: boolean;
|
||||
}
|
||||
|
||||
export const WizardInputField = <T extends FieldValues>({
|
||||
control,
|
||||
name,
|
||||
type = "text",
|
||||
label = name,
|
||||
labelPlacement = "inside",
|
||||
variant,
|
||||
size,
|
||||
placeholder,
|
||||
confirmPassword = false,
|
||||
password = false,
|
||||
defaultValue,
|
||||
isReadOnly = false,
|
||||
isRequired = true,
|
||||
isDisabled = false,
|
||||
normalizeValue,
|
||||
autoCapitalize,
|
||||
autoCorrect,
|
||||
spellCheck,
|
||||
requiredIndicator,
|
||||
}: WizardInputFieldProps<T>) => {
|
||||
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
|
||||
const [isConfirmPasswordVisible, setIsConfirmPasswordVisible] =
|
||||
useState(false);
|
||||
void variant;
|
||||
void size;
|
||||
|
||||
const inputLabel = confirmPassword
|
||||
? "Confirm Password"
|
||||
: password
|
||||
? "Password"
|
||||
: label;
|
||||
|
||||
const inputPlaceholder = confirmPassword
|
||||
? "Confirm Password"
|
||||
: password
|
||||
? "Password"
|
||||
: placeholder;
|
||||
|
||||
const isMaskedInput = type === "password" || password || confirmPassword;
|
||||
const inputType = isMaskedInput
|
||||
? isPasswordVisible || isConfirmPasswordVisible
|
||||
? "text"
|
||||
: "password"
|
||||
: type;
|
||||
const inputIsRequired = password || confirmPassword ? true : isRequired;
|
||||
const showRequiredIndicator = requiredIndicator ?? inputIsRequired;
|
||||
|
||||
const toggleVisibility = () => {
|
||||
if (password || type === "password") {
|
||||
setIsPasswordVisible((current) => !current);
|
||||
return;
|
||||
}
|
||||
if (confirmPassword) {
|
||||
setIsConfirmPasswordVisible((current) => !current);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FormField
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ field }) => {
|
||||
const value = field.value ?? defaultValue ?? "";
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label
|
||||
htmlFor={name}
|
||||
className={cn(
|
||||
"text-text-neutral-tertiary text-xs",
|
||||
labelPlacement === "outside"
|
||||
? "font-medium"
|
||||
: "font-light tracking-tight",
|
||||
)}
|
||||
>
|
||||
{inputLabel}
|
||||
{showRequiredIndicator && (
|
||||
<span className="text-text-error-primary">*</span>
|
||||
)}
|
||||
</label>
|
||||
<FormControl>
|
||||
<div className="relative">
|
||||
<Input
|
||||
id={name}
|
||||
aria-label={inputLabel}
|
||||
placeholder={inputPlaceholder}
|
||||
type={inputType}
|
||||
required={inputIsRequired}
|
||||
disabled={isDisabled}
|
||||
readOnly={isReadOnly}
|
||||
autoCapitalize={autoCapitalize}
|
||||
autoCorrect={autoCorrect}
|
||||
spellCheck={spellCheck}
|
||||
className={cn(isMaskedInput && "pr-10")}
|
||||
name={field.name}
|
||||
onBlur={field.onBlur}
|
||||
ref={field.ref}
|
||||
onChange={(event) => {
|
||||
if (!normalizeValue) {
|
||||
field.onChange(event);
|
||||
return;
|
||||
}
|
||||
const normalizedValue = normalizeValue(event.target.value);
|
||||
field.onChange(normalizedValue);
|
||||
}}
|
||||
value={value}
|
||||
/>
|
||||
{isMaskedInput && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={toggleVisibility}
|
||||
className="text-default-400 hover:text-default-500 absolute top-1/2 right-3 -translate-y-1/2"
|
||||
aria-label={
|
||||
inputType === "password"
|
||||
? "Show password"
|
||||
: "Hide password"
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
className="pointer-events-none text-xl"
|
||||
icon={
|
||||
(password && isPasswordVisible) ||
|
||||
(confirmPassword && isConfirmPasswordVisible) ||
|
||||
(type === "password" && isPasswordVisible)
|
||||
? "solar:eye-closed-linear"
|
||||
: "solar:eye-bold"
|
||||
}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</FormControl>
|
||||
<FormMessage className="text-text-error max-w-full text-xs" />
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,35 @@
|
||||
"use client";
|
||||
|
||||
import { RadioGroupItem } from "@/components/shadcn/radio-group/radio-group";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface WizardRadioCardProps {
|
||||
value: string;
|
||||
children: React.ReactNode;
|
||||
isInvalid?: boolean;
|
||||
}
|
||||
|
||||
export const WizardRadioCard = ({
|
||||
value,
|
||||
children,
|
||||
isInvalid = false,
|
||||
}: WizardRadioCardProps) => {
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"group inline-flex w-full cursor-pointer items-center justify-between gap-4 rounded-lg border-2 p-4",
|
||||
"border-default hover:border-button-primary",
|
||||
"has-[[data-state=checked]]:border-button-primary",
|
||||
isInvalid && "border-bg-fail",
|
||||
)}
|
||||
>
|
||||
<label
|
||||
htmlFor={value}
|
||||
className="flex flex-1 cursor-pointer items-center"
|
||||
>
|
||||
<span className="ml-2">{children}</span>
|
||||
</label>
|
||||
<RadioGroupItem value={value} id={value} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,96 @@
|
||||
"use client";
|
||||
|
||||
import { Control, FieldPath, FieldValues } from "react-hook-form";
|
||||
|
||||
import { Textarea } from "@/components/shadcn/textarea/textarea";
|
||||
import { FormControl, FormField, FormMessage } from "@/components/ui/form";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface WizardTextareaFieldProps<T extends FieldValues> {
|
||||
control: Control<T>;
|
||||
name: FieldPath<T>;
|
||||
label?: string;
|
||||
labelPlacement?: "inside" | "outside" | "outside-left";
|
||||
variant?: "flat" | "bordered" | "underlined" | "faded";
|
||||
size?: "sm" | "md" | "lg";
|
||||
placeholder?: string;
|
||||
defaultValue?: string;
|
||||
isRequired?: boolean;
|
||||
minRows?: number;
|
||||
maxRows?: number;
|
||||
fullWidth?: boolean;
|
||||
disableAutosize?: boolean;
|
||||
description?: React.ReactNode;
|
||||
requiredIndicator?: boolean;
|
||||
}
|
||||
|
||||
export const WizardTextareaField = <T extends FieldValues>({
|
||||
control,
|
||||
name,
|
||||
label = name,
|
||||
labelPlacement = "inside",
|
||||
variant,
|
||||
size,
|
||||
placeholder,
|
||||
defaultValue,
|
||||
isRequired = false,
|
||||
minRows = 3,
|
||||
maxRows,
|
||||
fullWidth = true,
|
||||
disableAutosize = false,
|
||||
description,
|
||||
requiredIndicator,
|
||||
}: WizardTextareaFieldProps<T>) => {
|
||||
void variant;
|
||||
void size;
|
||||
void fullWidth;
|
||||
void disableAutosize;
|
||||
const showRequiredIndicator = requiredIndicator ?? isRequired;
|
||||
|
||||
return (
|
||||
<FormField
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ field }) => {
|
||||
const value = field.value ?? defaultValue ?? "";
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<label
|
||||
htmlFor={name}
|
||||
className={cn(
|
||||
"text-text-neutral-tertiary text-xs",
|
||||
labelPlacement === "outside"
|
||||
? "font-medium"
|
||||
: "font-light tracking-tight",
|
||||
)}
|
||||
>
|
||||
{label}
|
||||
{showRequiredIndicator && (
|
||||
<span className="text-text-error-primary">*</span>
|
||||
)}
|
||||
</label>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
id={name}
|
||||
aria-label={label}
|
||||
placeholder={placeholder}
|
||||
required={isRequired}
|
||||
rows={maxRows ? Math.min(minRows, maxRows) : minRows}
|
||||
className={cn(description && "mb-1")}
|
||||
{...field}
|
||||
value={value}
|
||||
/>
|
||||
</FormControl>
|
||||
{description && (
|
||||
<p className="text-text-neutral-tertiary max-w-full text-xs">
|
||||
{description}
|
||||
</p>
|
||||
)}
|
||||
<FormMessage className="text-text-error max-w-full text-xs" />
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
import { ProviderType } from "@/types";
|
||||
|
||||
import {
|
||||
ConnectionFalse,
|
||||
ConnectionPending,
|
||||
ConnectionTrue,
|
||||
} from "../../../icons";
|
||||
import { getProviderLogo } from "../../../ui/entities/get-provider-logo";
|
||||
|
||||
interface ProviderConnectionInfoProps {
|
||||
connected: boolean | null;
|
||||
provider: ProviderType;
|
||||
providerAlias: string;
|
||||
providerUID?: string;
|
||||
}
|
||||
|
||||
export const ProviderConnectionInfo = ({
|
||||
connected,
|
||||
provider,
|
||||
providerAlias,
|
||||
providerUID,
|
||||
}: ProviderConnectionInfoProps) => {
|
||||
const getIcon = () => {
|
||||
switch (connected) {
|
||||
case true:
|
||||
return (
|
||||
<div className="rounded-medium border-system-success bg-system-success-lighter flex items-center justify-center border-2 p-1">
|
||||
<ConnectionTrue className="text-system-success" size={24} />
|
||||
</div>
|
||||
);
|
||||
case false:
|
||||
return (
|
||||
<div className="rounded-medium border-border-error flex items-center justify-center border-2 p-1">
|
||||
<ConnectionFalse className="text-text-error-primary" size={24} />
|
||||
</div>
|
||||
);
|
||||
case null:
|
||||
return (
|
||||
<div className="bg-info-lighter border-info-lighter rounded-medium flex items-center justify-center border p-1">
|
||||
<ConnectionPending className="text-info" size={24} />
|
||||
</div>
|
||||
);
|
||||
default:
|
||||
return <ConnectionPending size={24} />;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center text-sm">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="shrink-0">{getProviderLogo(provider)}</div>
|
||||
{getIcon()}
|
||||
<span className="font-medium">{providerAlias || providerUID}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Divider } from "@heroui/divider";
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { WizardInputField } from "@/components/providers/workflow/forms/fields";
|
||||
import { Separator } from "@/components/shadcn/separator/separator";
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
import { AlibabaCloudCredentialsRole } from "@/types";
|
||||
|
||||
@@ -26,7 +26,7 @@ export const AlibabaCloudRoleCredentialsForm = ({
|
||||
RAM Role to Assume
|
||||
</span>
|
||||
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.ALIBABACLOUD_ROLE_ARN}
|
||||
type="text"
|
||||
@@ -37,13 +37,13 @@ export const AlibabaCloudRoleCredentialsForm = ({
|
||||
isRequired
|
||||
/>
|
||||
|
||||
<Divider />
|
||||
<Separator />
|
||||
|
||||
<span className="text-default-500 text-xs font-bold">
|
||||
Credentials for Role Assumption
|
||||
</span>
|
||||
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_ID}
|
||||
type="text"
|
||||
@@ -53,7 +53,7 @@ export const AlibabaCloudRoleCredentialsForm = ({
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_SECRET}
|
||||
type="password"
|
||||
@@ -66,7 +66,7 @@ export const AlibabaCloudRoleCredentialsForm = ({
|
||||
|
||||
<span className="text-default-500 text-xs">Optional fields</span>
|
||||
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.ALIBABACLOUD_ROLE_SESSION_NAME}
|
||||
type="text"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { WizardInputField } from "@/components/providers/workflow/forms/fields";
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
import { AlibabaCloudCredentials } from "@/types";
|
||||
|
||||
@@ -20,7 +20,7 @@ export const AlibabaCloudStaticCredentialsForm = ({
|
||||
access to the resources you want Prowler to assess.
|
||||
</div>
|
||||
</div>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_ID}
|
||||
type="text"
|
||||
@@ -30,7 +30,7 @@ export const AlibabaCloudStaticCredentialsForm = ({
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.ALIBABACLOUD_ACCESS_KEY_SECRET}
|
||||
type="password"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { RadioGroup } from "@heroui/radio";
|
||||
import { Control, Controller, FieldValues, Path } from "react-hook-form";
|
||||
|
||||
import { CustomRadio } from "@/components/ui/custom";
|
||||
import { WizardRadioCard } from "@/components/providers/workflow/forms/fields";
|
||||
import { RadioGroup } from "@/components/shadcn/radio-group/radio-group";
|
||||
import { FormMessage } from "@/components/ui/form";
|
||||
|
||||
type RadioGroupAlibabaCloudViaCredentialsFormProps<T extends FieldValues> = {
|
||||
@@ -25,47 +25,38 @@ export const RadioGroupAlibabaCloudViaCredentialsTypeForm = <
|
||||
<Controller
|
||||
name={"alibabacloudCredentialsType" as Path<T>}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<RadioGroup
|
||||
className="flex flex-wrap"
|
||||
isInvalid={isInvalid}
|
||||
{...field}
|
||||
value={field.value || ""}
|
||||
onValueChange={(value) => {
|
||||
field.onChange(value);
|
||||
if (onChange) {
|
||||
onChange(value);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
render={({ field }) => {
|
||||
const currentValue = String(field.value ?? "");
|
||||
|
||||
return (
|
||||
<>
|
||||
<RadioGroup
|
||||
name={field.name}
|
||||
value={currentValue}
|
||||
onValueChange={(value: string) => {
|
||||
field.onChange(value);
|
||||
onChange?.(value);
|
||||
}}
|
||||
>
|
||||
<span className="text-default-500 text-sm">Using RAM Role</span>
|
||||
<CustomRadio description="Connect assuming RAM Role" value="role">
|
||||
<div className="flex items-center">
|
||||
<span className="ml-2">Connect assuming RAM Role</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
<WizardRadioCard value="role" isInvalid={isInvalid}>
|
||||
Connect assuming RAM Role
|
||||
</WizardRadioCard>
|
||||
<span className="text-default-500 text-sm">
|
||||
Using Credentials
|
||||
</span>
|
||||
<CustomRadio
|
||||
description="Connect via Access Keys"
|
||||
value="credentials"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span className="ml-2">Connect via Access Keys</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
</div>
|
||||
</RadioGroup>
|
||||
{errorMessage && (
|
||||
<FormMessage className="text-text-error">
|
||||
{errorMessage}
|
||||
</FormMessage>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<WizardRadioCard value="credentials" isInvalid={isInvalid}>
|
||||
Connect via Access Keys
|
||||
</WizardRadioCard>
|
||||
</RadioGroup>
|
||||
{errorMessage && (
|
||||
<FormMessage className="text-text-error">
|
||||
{errorMessage}
|
||||
</FormMessage>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Chip } from "@heroui/chip";
|
||||
import { Divider } from "@heroui/divider";
|
||||
import { Switch } from "@heroui/switch";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Control, UseFormSetValue, useWatch } from "react-hook-form";
|
||||
|
||||
import { CredentialsRoleHelper } from "@/components/providers/workflow";
|
||||
import { WizardInputField } from "@/components/providers/workflow/forms/fields";
|
||||
import { Badge } from "@/components/shadcn/badge/badge";
|
||||
import { Checkbox } from "@/components/shadcn/checkbox/checkbox";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/shadcn/select/select";
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { Separator } from "@/components/shadcn/separator/separator";
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
import { AWSCredentialsRole } from "@/types";
|
||||
import { IntegrationType } from "@/types/integrations";
|
||||
@@ -105,14 +105,9 @@ export const AWSRoleCredentialsForm = ({
|
||||
: "AWS SDK Default"}
|
||||
</span>
|
||||
{isCloudEnv && (
|
||||
<Chip
|
||||
size="sm"
|
||||
variant="flat"
|
||||
color="success"
|
||||
className="ml-2"
|
||||
>
|
||||
<Badge variant="tag" className="ml-2">
|
||||
Recommended
|
||||
</Chip>
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</SelectItem>
|
||||
@@ -127,7 +122,7 @@ export const AWSRoleCredentialsForm = ({
|
||||
|
||||
{credentialsType === "access-secret-key" && (
|
||||
<>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.AWS_ACCESS_KEY_ID}
|
||||
type="password"
|
||||
@@ -137,7 +132,7 @@ export const AWSRoleCredentialsForm = ({
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.AWS_SECRET_ACCESS_KEY}
|
||||
type="password"
|
||||
@@ -147,7 +142,7 @@ export const AWSRoleCredentialsForm = ({
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.AWS_SESSION_TOKEN}
|
||||
type="password"
|
||||
@@ -159,7 +154,7 @@ export const AWSRoleCredentialsForm = ({
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Divider className="" />
|
||||
<Separator />
|
||||
|
||||
{type === "providers" ? (
|
||||
<span className="text-default-500 text-xs font-bold">Assume Role</span>
|
||||
@@ -170,11 +165,11 @@ export const AWSRoleCredentialsForm = ({
|
||||
? "Adding a role is required"
|
||||
: "Optionally add a role"}
|
||||
</span>
|
||||
<Switch
|
||||
size="sm"
|
||||
isSelected={showRoleSection}
|
||||
onValueChange={setShowOptionalRole}
|
||||
isDisabled={isCloudEnv && credentialsType === "aws-sdk-default"}
|
||||
<Checkbox
|
||||
checked={showRoleSection}
|
||||
onCheckedChange={(checked) => setShowOptionalRole(Boolean(checked))}
|
||||
disabled={isCloudEnv && credentialsType === "aws-sdk-default"}
|
||||
aria-label="Optionally add a role"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -187,9 +182,9 @@ export const AWSRoleCredentialsForm = ({
|
||||
integrationType={integrationType}
|
||||
/>
|
||||
|
||||
<Divider />
|
||||
<Separator />
|
||||
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.ROLE_ARN}
|
||||
type="text"
|
||||
@@ -199,7 +194,7 @@ export const AWSRoleCredentialsForm = ({
|
||||
variant="bordered"
|
||||
isRequired={showRoleSection}
|
||||
/>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.EXTERNAL_ID}
|
||||
type="text"
|
||||
@@ -214,7 +209,7 @@ export const AWSRoleCredentialsForm = ({
|
||||
|
||||
<span className="text-default-500 text-xs">Optional fields</span>
|
||||
<div className="mt-4 grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.ROLE_SESSION_NAME}
|
||||
type="text"
|
||||
@@ -224,7 +219,7 @@ export const AWSRoleCredentialsForm = ({
|
||||
variant="bordered"
|
||||
isRequired={false}
|
||||
/>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.SESSION_DURATION}
|
||||
type="number"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { WizardInputField } from "@/components/providers/workflow/forms/fields";
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
import { AWSCredentials } from "@/types";
|
||||
|
||||
@@ -19,7 +19,7 @@ export const AWSStaticCredentialsForm = ({
|
||||
Please provide the information for your AWS credentials.
|
||||
</div>
|
||||
</div>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.AWS_ACCESS_KEY_ID}
|
||||
type="password"
|
||||
@@ -29,7 +29,7 @@ export const AWSStaticCredentialsForm = ({
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.AWS_SECRET_ACCESS_KEY}
|
||||
type="password"
|
||||
@@ -39,7 +39,7 @@ export const AWSStaticCredentialsForm = ({
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.AWS_SESSION_TOKEN}
|
||||
type="password"
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { RadioGroup } from "@heroui/radio";
|
||||
import React from "react";
|
||||
import { Control, Controller } from "react-hook-form";
|
||||
|
||||
import { CustomRadio } from "@/components/ui/custom";
|
||||
import { WizardRadioCard } from "@/components/providers/workflow/forms/fields";
|
||||
import { RadioGroup } from "@/components/shadcn/radio-group/radio-group";
|
||||
import { FormMessage } from "@/components/ui/form";
|
||||
|
||||
type RadioGroupAWSViaCredentialsFormProps = {
|
||||
@@ -27,36 +26,21 @@ export const RadioGroupAWSViaCredentialsTypeForm = ({
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<RadioGroup
|
||||
className="flex flex-wrap"
|
||||
isInvalid={isInvalid}
|
||||
{...field}
|
||||
name={field.name}
|
||||
value={field.value || ""}
|
||||
onValueChange={(value) => {
|
||||
onValueChange={(value: string) => {
|
||||
field.onChange(value);
|
||||
if (onChange) {
|
||||
onChange(value);
|
||||
}
|
||||
onChange?.(value);
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<span className="text-default-500 text-sm">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-default-500 text-sm">
|
||||
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>
|
||||
<span className="text-default-500 text-sm">Using IAM Role</span>
|
||||
<WizardRadioCard value="role" isInvalid={isInvalid}>
|
||||
Connect assuming IAM Role
|
||||
</WizardRadioCard>
|
||||
<span className="text-default-500 text-sm">Using Credentials</span>
|
||||
<WizardRadioCard value="credentials" isInvalid={isInvalid}>
|
||||
Connect via Credentials
|
||||
</WizardRadioCard>
|
||||
</RadioGroup>
|
||||
{errorMessage && (
|
||||
<FormMessage className="text-text-error">
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { WizardInputField } from "@/components/providers/workflow/forms/fields";
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
import { CloudflareApiKeyCredentials } from "@/types";
|
||||
|
||||
@@ -22,7 +22,7 @@ export const CloudflareApiKeyCredentialsForm = ({
|
||||
associated with your Cloudflare account.
|
||||
</div>
|
||||
</div>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.CLOUDFLARE_API_EMAIL}
|
||||
type="text"
|
||||
@@ -32,7 +32,7 @@ export const CloudflareApiKeyCredentialsForm = ({
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.CLOUDFLARE_API_KEY}
|
||||
type="password"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { WizardInputField } from "@/components/providers/workflow/forms/fields";
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
import { CloudflareTokenCredentials } from "@/types";
|
||||
|
||||
@@ -23,7 +23,7 @@ export const CloudflareApiTokenCredentialsForm = ({
|
||||
method.
|
||||
</div>
|
||||
</div>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.CLOUDFLARE_API_TOKEN}
|
||||
type="password"
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { RadioGroup } from "@heroui/radio";
|
||||
import { Control, Controller } from "react-hook-form";
|
||||
|
||||
import { CustomRadio } from "@/components/ui/custom";
|
||||
import { WizardRadioCard } from "@/components/providers/workflow/forms/fields";
|
||||
import { RadioGroup } from "@/components/shadcn/radio-group/radio-group";
|
||||
import { FormMessage } from "@/components/ui/form";
|
||||
|
||||
type RadioGroupCloudflareViaCredentialsFormProps = {
|
||||
@@ -26,38 +26,22 @@ export const RadioGroupCloudflareViaCredentialsTypeForm = ({
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<RadioGroup
|
||||
className="flex flex-wrap"
|
||||
isInvalid={isInvalid}
|
||||
{...field}
|
||||
name={field.name}
|
||||
value={field.value || ""}
|
||||
onValueChange={(value) => {
|
||||
onValueChange={(value: string) => {
|
||||
field.onChange(value);
|
||||
if (onChange) {
|
||||
onChange(value);
|
||||
}
|
||||
onChange?.(value);
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<span className="text-default-500 text-sm">
|
||||
Select Authentication Method
|
||||
</span>
|
||||
<CustomRadio
|
||||
description="Connect using a Cloudflare API Token (recommended)"
|
||||
value="api_token"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span className="ml-2">API Token</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
<CustomRadio
|
||||
description="Connect using Global API Key and Email"
|
||||
value="api_key"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span className="ml-2">API Key + Email</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
</div>
|
||||
<span className="text-default-500 text-sm">
|
||||
Select Authentication Method
|
||||
</span>
|
||||
<WizardRadioCard value="api_token" isInvalid={isInvalid}>
|
||||
API Token
|
||||
</WizardRadioCard>
|
||||
<WizardRadioCard value="api_key" isInvalid={isInvalid}>
|
||||
API Key + Email
|
||||
</WizardRadioCard>
|
||||
</RadioGroup>
|
||||
{errorMessage && (
|
||||
<FormMessage className="text-text-error">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { WizardInputField } from "@/components/providers/workflow/forms/fields";
|
||||
import { GCPDefaultCredentials } from "@/types";
|
||||
|
||||
export const GCPDefaultCredentialsForm = ({
|
||||
@@ -18,7 +18,7 @@ export const GCPDefaultCredentialsForm = ({
|
||||
Please provide the information for your GCP credentials.
|
||||
</div>
|
||||
</div>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name="client_id"
|
||||
type="text"
|
||||
@@ -28,7 +28,7 @@ export const GCPDefaultCredentialsForm = ({
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name="client_secret"
|
||||
type="password"
|
||||
@@ -38,7 +38,7 @@ export const GCPDefaultCredentialsForm = ({
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name="refresh_token"
|
||||
type="password"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomTextarea } from "@/components/ui/custom";
|
||||
import { WizardTextareaField } from "@/components/providers/workflow/forms/fields";
|
||||
import { GCPServiceAccountKey } from "@/types";
|
||||
|
||||
export const GCPServiceAccountKeyForm = ({
|
||||
@@ -18,7 +18,7 @@ export const GCPServiceAccountKeyForm = ({
|
||||
Please provide the service account key for your GCP credentials.
|
||||
</div>
|
||||
</div>
|
||||
<CustomTextarea
|
||||
<WizardTextareaField
|
||||
control={control}
|
||||
name="service_account_key"
|
||||
label="Service Account Key"
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import { RadioGroup } from "@heroui/radio";
|
||||
import React from "react";
|
||||
import { Control, Controller } from "react-hook-form";
|
||||
|
||||
import { CustomRadio } from "@/components/ui/custom";
|
||||
import { WizardRadioCard } from "@/components/providers/workflow/forms/fields";
|
||||
import { RadioGroup } from "@/components/shadcn/radio-group/radio-group";
|
||||
import { FormMessage } from "@/components/ui/form";
|
||||
|
||||
type RadioGroupAWSViaCredentialsFormProps = {
|
||||
type RadioGroupGCPViaCredentialsFormProps = {
|
||||
control: Control<any>;
|
||||
isInvalid: boolean;
|
||||
errorMessage?: string;
|
||||
@@ -19,7 +18,7 @@ export const RadioGroupGCPViaCredentialsTypeForm = ({
|
||||
isInvalid,
|
||||
errorMessage,
|
||||
onChange,
|
||||
}: RadioGroupAWSViaCredentialsFormProps) => {
|
||||
}: RadioGroupGCPViaCredentialsFormProps) => {
|
||||
return (
|
||||
<Controller
|
||||
name="gcpCredentialsType"
|
||||
@@ -27,43 +26,25 @@ export const RadioGroupGCPViaCredentialsTypeForm = ({
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<RadioGroup
|
||||
className="flex flex-wrap"
|
||||
isInvalid={isInvalid}
|
||||
{...field}
|
||||
name={field.name}
|
||||
value={field.value || ""}
|
||||
onValueChange={(value) => {
|
||||
onValueChange={(value: string) => {
|
||||
field.onChange(value);
|
||||
if (onChange) {
|
||||
onChange(value);
|
||||
}
|
||||
onChange?.(value);
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<span className="text-default-500 text-sm">
|
||||
Using Service Account
|
||||
</span>
|
||||
<CustomRadio
|
||||
description="Connect using Service Account"
|
||||
value="service-account"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span className="ml-2">Connect via Service Account Key</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
<span className="text-default-500 text-sm">
|
||||
Using Application Default Credentials
|
||||
</span>
|
||||
<CustomRadio
|
||||
description="Connect via Credentials"
|
||||
value="credentials"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span className="ml-2">
|
||||
Connect via Application Default Credentials
|
||||
</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
</div>
|
||||
<span className="text-default-500 text-sm">
|
||||
Using Service Account
|
||||
</span>
|
||||
<WizardRadioCard value="service-account" isInvalid={isInvalid}>
|
||||
Connect via Service Account Key
|
||||
</WizardRadioCard>
|
||||
<span className="text-default-500 text-sm">
|
||||
Using Application Default Credentials
|
||||
</span>
|
||||
<WizardRadioCard value="credentials" isInvalid={isInvalid}>
|
||||
Connect via Application Default Credentials
|
||||
</WizardRadioCard>
|
||||
</RadioGroup>
|
||||
{errorMessage && (
|
||||
<FormMessage className="text-text-error">
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput, CustomTextarea } from "@/components/ui/custom";
|
||||
import {
|
||||
WizardInputField,
|
||||
WizardTextareaField,
|
||||
} from "@/components/providers/workflow/forms/fields";
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
|
||||
export const GitHubAppForm = ({ control }: { control: Control<any> }) => {
|
||||
@@ -16,7 +19,7 @@ export const GitHubAppForm = ({ control }: { control: Control<any> }) => {
|
||||
Please provide your GitHub App ID and private key.
|
||||
</div>
|
||||
</div>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.GITHUB_APP_ID}
|
||||
type="text"
|
||||
@@ -26,7 +29,7 @@ export const GitHubAppForm = ({ control }: { control: Control<any> }) => {
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
<CustomTextarea
|
||||
<WizardTextareaField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.GITHUB_APP_KEY}
|
||||
label="GitHub App Private Key"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { WizardInputField } from "@/components/providers/workflow/forms/fields";
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
|
||||
export const GitHubOAuthAppForm = ({ control }: { control: Control<any> }) => {
|
||||
@@ -16,7 +16,7 @@ export const GitHubOAuthAppForm = ({ control }: { control: Control<any> }) => {
|
||||
Please provide your GitHub OAuth App token.
|
||||
</div>
|
||||
</div>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.OAUTH_APP_TOKEN}
|
||||
type="password"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { WizardInputField } from "@/components/providers/workflow/forms/fields";
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
|
||||
export const GitHubPersonalAccessTokenForm = ({
|
||||
@@ -20,7 +20,7 @@ export const GitHubPersonalAccessTokenForm = ({
|
||||
Please provide your GitHub personal access token.
|
||||
</div>
|
||||
</div>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.PERSONAL_ACCESS_TOKEN}
|
||||
type="password"
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { RadioGroup } from "@heroui/radio";
|
||||
import React from "react";
|
||||
import { Control, Controller } from "react-hook-form";
|
||||
|
||||
import { CustomRadio } from "@/components/ui/custom";
|
||||
import { WizardRadioCard } from "@/components/providers/workflow/forms/fields";
|
||||
import { RadioGroup } from "@/components/shadcn/radio-group/radio-group";
|
||||
import { FormMessage } from "@/components/ui/form";
|
||||
|
||||
type RadioGroupGitHubViaCredentialsFormProps = {
|
||||
@@ -27,50 +26,32 @@ export const RadioGroupGitHubViaCredentialsTypeForm = ({
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<RadioGroup
|
||||
className="flex flex-wrap"
|
||||
isInvalid={isInvalid}
|
||||
{...field}
|
||||
name={field.name}
|
||||
value={field.value || ""}
|
||||
onValueChange={(value) => {
|
||||
onValueChange={(value: string) => {
|
||||
field.onChange(value);
|
||||
if (onChange) {
|
||||
onChange(value);
|
||||
}
|
||||
onChange?.(value);
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<span className="text-default-500 text-sm">
|
||||
Personal Access Token
|
||||
</span>
|
||||
<CustomRadio
|
||||
description="Use a personal access token for authentication"
|
||||
value="personal_access_token"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span className="ml-2">Personal Access Token</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
<span className="text-default-500 text-sm">
|
||||
Personal Access Token
|
||||
</span>
|
||||
<WizardRadioCard
|
||||
value="personal_access_token"
|
||||
isInvalid={isInvalid}
|
||||
>
|
||||
Personal Access Token
|
||||
</WizardRadioCard>
|
||||
|
||||
<span className="text-default-500 text-sm">OAuth App</span>
|
||||
<CustomRadio
|
||||
description="Use OAuth App token for authentication"
|
||||
value="oauth_app"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span className="ml-2">OAuth App Token</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
<span className="text-default-500 text-sm">OAuth App</span>
|
||||
<WizardRadioCard value="oauth_app" isInvalid={isInvalid}>
|
||||
OAuth App Token
|
||||
</WizardRadioCard>
|
||||
|
||||
<span className="text-default-500 text-sm">GitHub App</span>
|
||||
<CustomRadio
|
||||
description="Use GitHub App ID and private key for authentication"
|
||||
value="github_app"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span className="ml-2">GitHub App</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
</div>
|
||||
<span className="text-default-500 text-sm">GitHub App</span>
|
||||
<WizardRadioCard value="github_app" isInvalid={isInvalid}>
|
||||
GitHub App
|
||||
</WizardRadioCard>
|
||||
</RadioGroup>
|
||||
{errorMessage && (
|
||||
<FormMessage className="text-text-error">
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput, CustomTextarea } from "@/components/ui/custom";
|
||||
import { CustomLink } from "@/components/ui/custom/custom-link";
|
||||
import {
|
||||
WizardInputField,
|
||||
WizardTextareaField,
|
||||
} from "@/components/providers/workflow/forms/fields";
|
||||
import { M365CertificateCredentials } from "@/types";
|
||||
|
||||
export const M365CertificateCredentialsForm = ({
|
||||
@@ -22,7 +25,7 @@ export const M365CertificateCredentialsForm = ({
|
||||
certificate authentication.
|
||||
</div>
|
||||
</div>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name="tenant_id"
|
||||
type="text"
|
||||
@@ -32,7 +35,7 @@ export const M365CertificateCredentialsForm = ({
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name="client_id"
|
||||
type="text"
|
||||
@@ -42,7 +45,7 @@ export const M365CertificateCredentialsForm = ({
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
<CustomTextarea
|
||||
<WizardTextareaField
|
||||
control={control}
|
||||
name="certificate_content"
|
||||
label="Certificate Content"
|
||||
@@ -56,12 +59,14 @@ export const M365CertificateCredentialsForm = ({
|
||||
The certificate content must be base64 encoded from an unsigned
|
||||
certificate. For detailed instructions on how to generate and encode
|
||||
your certificate, please refer to the{" "}
|
||||
<CustomLink
|
||||
<Link
|
||||
href="https://docs.prowler.com/user-guide/providers/microsoft365/authentication#generate-the-certificate"
|
||||
size="sm"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-button-tertiary p-0 text-sm"
|
||||
>
|
||||
certificate generation guide
|
||||
</CustomLink>
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { WizardInputField } from "@/components/providers/workflow/forms/fields";
|
||||
import { M365ClientSecretCredentials } from "@/types";
|
||||
|
||||
export const M365ClientSecretCredentialsForm = ({
|
||||
@@ -20,7 +20,7 @@ export const M365ClientSecretCredentialsForm = ({
|
||||
Please provide your Microsoft 365 application credentials.
|
||||
</div>
|
||||
</div>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name="tenant_id"
|
||||
type="text"
|
||||
@@ -30,7 +30,7 @@ export const M365ClientSecretCredentialsForm = ({
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name="client_id"
|
||||
type="text"
|
||||
@@ -40,7 +40,7 @@ export const M365ClientSecretCredentialsForm = ({
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name="client_secret"
|
||||
type="password"
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
"use client";
|
||||
|
||||
import { RadioGroup } from "@heroui/radio";
|
||||
import React from "react";
|
||||
import { Control, Controller } from "react-hook-form";
|
||||
|
||||
import { CustomRadio } from "@/components/ui/custom";
|
||||
import { WizardRadioCard } from "@/components/providers/workflow/forms/fields";
|
||||
import { RadioGroup } from "@/components/shadcn/radio-group/radio-group";
|
||||
import { FormMessage } from "@/components/ui/form";
|
||||
|
||||
type RadioGroupM365ViaCredentialsFormProps = {
|
||||
@@ -27,38 +26,22 @@ export const RadioGroupM365ViaCredentialsTypeForm = ({
|
||||
render={({ field }) => (
|
||||
<>
|
||||
<RadioGroup
|
||||
className="flex flex-wrap"
|
||||
isInvalid={isInvalid}
|
||||
{...field}
|
||||
name={field.name}
|
||||
value={field.value || ""}
|
||||
onValueChange={(value) => {
|
||||
onValueChange={(value: string) => {
|
||||
field.onChange(value);
|
||||
if (onChange) {
|
||||
onChange(value);
|
||||
}
|
||||
onChange?.(value);
|
||||
}}
|
||||
>
|
||||
<div className="flex flex-col gap-4">
|
||||
<span className="text-default-500 text-sm">
|
||||
Select Authentication Method
|
||||
</span>
|
||||
<CustomRadio
|
||||
description="Connect using Application Client Secret"
|
||||
value="app_client_secret"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span className="ml-2">App Client Secret Credentials</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
<CustomRadio
|
||||
description="Connect using Application Certificate"
|
||||
value="app_certificate"
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<span className="ml-2">App Certificate Credentials</span>
|
||||
</div>
|
||||
</CustomRadio>
|
||||
</div>
|
||||
<span className="text-default-500 text-sm">
|
||||
Select Authentication Method
|
||||
</span>
|
||||
<WizardRadioCard value="app_client_secret" isInvalid={isInvalid}>
|
||||
App Client Secret Credentials
|
||||
</WizardRadioCard>
|
||||
<WizardRadioCard value="app_certificate" isInvalid={isInvalid}>
|
||||
App Certificate Credentials
|
||||
</WizardRadioCard>
|
||||
</RadioGroup>
|
||||
{errorMessage && (
|
||||
<FormMessage className="text-text-error">
|
||||
|
||||
@@ -22,7 +22,7 @@ import { checkTaskStatus } from "@/lib/helper";
|
||||
import { ProviderType } from "@/types";
|
||||
import { ApiError, testConnectionFormSchema } from "@/types";
|
||||
|
||||
import { ProviderInfo } from "../..";
|
||||
import { ProviderConnectionInfo } from "./provider-connection-info";
|
||||
|
||||
type FormValues = z.input<typeof testConnectionFormSchema>;
|
||||
|
||||
@@ -243,7 +243,7 @@ export const TestConnectionForm = ({
|
||||
</>
|
||||
)}
|
||||
|
||||
<ProviderInfo
|
||||
<ProviderConnectionInfo
|
||||
connected={providerData.data.attributes.connection.connected}
|
||||
provider={providerData.data.attributes.provider}
|
||||
providerAlias={providerData.data.attributes.alias}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { WizardInputField } from "@/components/providers/workflow/forms/fields";
|
||||
import { AzureCredentials } from "@/types";
|
||||
|
||||
export const AzureCredentialsForm = ({
|
||||
@@ -18,7 +18,7 @@ export const AzureCredentialsForm = ({
|
||||
Please provide the information for your Azure credentials.
|
||||
</div>
|
||||
</div>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name="client_id"
|
||||
type="text"
|
||||
@@ -28,7 +28,7 @@ export const AzureCredentialsForm = ({
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name="client_secret"
|
||||
type="password"
|
||||
@@ -38,7 +38,7 @@ export const AzureCredentialsForm = ({
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name="tenant_id"
|
||||
type="text"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { WizardInputField } from "@/components/providers/workflow/forms/fields";
|
||||
import { IacCredentials } from "@/types";
|
||||
|
||||
export const IacCredentialsForm = ({
|
||||
@@ -18,7 +18,7 @@ export const IacCredentialsForm = ({
|
||||
Provide an access token if the repository is private (optional).
|
||||
</div>
|
||||
</div>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name="access_token"
|
||||
label="Access Token (Optional)"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomTextarea } from "@/components/ui/custom";
|
||||
import { WizardTextareaField } from "@/components/providers/workflow/forms/fields";
|
||||
import { KubernetesCredentials } from "@/types";
|
||||
|
||||
export const KubernetesCredentialsForm = ({
|
||||
@@ -18,7 +18,7 @@ export const KubernetesCredentialsForm = ({
|
||||
Please provide the kubeconfig content for your Kubernetes credentials.
|
||||
</div>
|
||||
</div>
|
||||
<CustomTextarea
|
||||
<WizardTextareaField
|
||||
control={control}
|
||||
name="kubeconfig_content"
|
||||
label="Kubeconfig Content"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput } from "@/components/ui/custom";
|
||||
import { WizardInputField } from "@/components/providers/workflow/forms/fields";
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
import { MongoDBAtlasCredentials } from "@/types";
|
||||
|
||||
@@ -20,7 +20,7 @@ export const MongoDBAtlasCredentialsForm = ({
|
||||
with read access to the resources you want Prowler to assess.
|
||||
</div>
|
||||
</div>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.ATLAS_PUBLIC_KEY}
|
||||
type="text"
|
||||
@@ -30,7 +30,7 @@ export const MongoDBAtlasCredentialsForm = ({
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.ATLAS_PRIVATE_KEY}
|
||||
type="password"
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Control } from "react-hook-form";
|
||||
|
||||
import { CustomInput, CustomTextarea } from "@/components/ui/custom";
|
||||
import {
|
||||
WizardInputField,
|
||||
WizardTextareaField,
|
||||
} from "@/components/providers/workflow/forms/fields";
|
||||
import { OpenStackCredentials } from "@/types";
|
||||
|
||||
export const OpenStackCredentialsForm = ({
|
||||
@@ -18,7 +21,7 @@ export const OpenStackCredentialsForm = ({
|
||||
Please provide your OpenStack clouds.yaml content and the cloud name.
|
||||
</div>
|
||||
</div>
|
||||
<CustomTextarea
|
||||
<WizardTextareaField
|
||||
control={control}
|
||||
name="clouds_yaml_content"
|
||||
label="Clouds YAML Content"
|
||||
@@ -28,7 +31,7 @@ export const OpenStackCredentialsForm = ({
|
||||
minRows={10}
|
||||
isRequired
|
||||
/>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name="clouds_yaml_cloud"
|
||||
type="text"
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Control, Controller } from "react-hook-form";
|
||||
|
||||
import { CustomInput, CustomTextarea } from "@/components/ui/custom";
|
||||
import {
|
||||
WizardInputField,
|
||||
WizardTextareaField,
|
||||
} from "@/components/providers/workflow/forms/fields";
|
||||
import { ProviderCredentialFields } from "@/lib/provider-credentials/provider-credential-fields";
|
||||
import { OCICredentials } from "@/types";
|
||||
|
||||
@@ -25,7 +28,7 @@ export const OracleCloudCredentialsForm = ({
|
||||
name={ProviderCredentialFields.OCI_TENANCY}
|
||||
render={({ field }) => <input type="hidden" {...field} />}
|
||||
/>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.OCI_USER}
|
||||
type="text"
|
||||
@@ -35,7 +38,7 @@ export const OracleCloudCredentialsForm = ({
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.OCI_FINGERPRINT}
|
||||
type="text"
|
||||
@@ -45,7 +48,7 @@ export const OracleCloudCredentialsForm = ({
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.OCI_REGION}
|
||||
type="text"
|
||||
@@ -55,7 +58,7 @@ export const OracleCloudCredentialsForm = ({
|
||||
variant="bordered"
|
||||
isRequired
|
||||
/>
|
||||
<CustomTextarea
|
||||
<WizardTextareaField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.OCI_KEY_CONTENT}
|
||||
label="Private Key Content"
|
||||
@@ -65,7 +68,7 @@ export const OracleCloudCredentialsForm = ({
|
||||
minRows={6}
|
||||
isRequired
|
||||
/>
|
||||
<CustomInput
|
||||
<WizardInputField
|
||||
control={control}
|
||||
name={ProviderCredentialFields.OCI_PASS_PHRASE}
|
||||
type="password"
|
||||
|
||||
@@ -1,33 +1,21 @@
|
||||
import { Card, CardBody, CardHeader } from "@heroui/card";
|
||||
import { Skeleton } from "@heroui/skeleton";
|
||||
import { Card, CardContent, CardHeader } from "@/components/shadcn/card/card";
|
||||
import { Skeleton } from "@/components/shadcn/skeleton/skeleton";
|
||||
|
||||
export const SkeletonProviderWorkflow = () => {
|
||||
return (
|
||||
<Card>
|
||||
<Card variant="inner">
|
||||
<CardHeader className="flex flex-col items-start gap-2">
|
||||
<Skeleton className="h-6 w-2/3 rounded-lg">
|
||||
<div className="bg-default-200 h-6"></div>
|
||||
</Skeleton>
|
||||
<Skeleton className="h-4 w-1/2 rounded-lg">
|
||||
<div className="bg-default-200 h-4"></div>
|
||||
</Skeleton>
|
||||
<Skeleton className="h-6 w-2/3 rounded-lg" />
|
||||
<Skeleton className="h-4 w-1/2 rounded-lg" />
|
||||
</CardHeader>
|
||||
<CardBody className="flex flex-col items-start gap-6">
|
||||
<CardContent className="flex flex-col items-start gap-6">
|
||||
<div className="flex gap-4">
|
||||
<Skeleton className="h-12 w-12 rounded-lg">
|
||||
<div className="bg-default-200 h-12 w-12"></div>
|
||||
</Skeleton>
|
||||
<Skeleton className="h-12 w-12 rounded-lg">
|
||||
<div className="bg-default-200 h-12 w-12"></div>
|
||||
</Skeleton>
|
||||
<Skeleton className="h-12 w-12 rounded-lg" />
|
||||
<Skeleton className="h-12 w-12 rounded-lg" />
|
||||
</div>
|
||||
<Skeleton className="h-5 w-3/4 rounded-lg">
|
||||
<div className="bg-default-200 h-5"></div>
|
||||
</Skeleton>
|
||||
<Skeleton className="h-12 w-40 self-end rounded-lg">
|
||||
<div className="bg-default-200 h-12"></div>
|
||||
</Skeleton>
|
||||
</CardBody>
|
||||
<Skeleton className="h-5 w-3/4 rounded-lg" />
|
||||
<Skeleton className="h-12 w-40 self-end rounded-lg" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
46
ui/components/shadcn/radio-group/radio-group.tsx
Normal file
46
ui/components/shadcn/radio-group/radio-group.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
"use client";
|
||||
|
||||
import * as RadioGroupPrimitive from "@radix-ui/react-radio-group";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
function RadioGroup({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof RadioGroupPrimitive.Root>) {
|
||||
return (
|
||||
<RadioGroupPrimitive.Root
|
||||
data-slot="radio-group"
|
||||
className={cn("flex flex-col gap-4", className)}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
function RadioGroupItem({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof RadioGroupPrimitive.Item>) {
|
||||
return (
|
||||
<RadioGroupPrimitive.Item
|
||||
data-slot="radio-group-item"
|
||||
className={cn(
|
||||
"border-border-input-primary aspect-square size-4 shrink-0 rounded-full border shadow-[0_1px_2px_0_rgba(0,0,0,0.1)] transition-all outline-none",
|
||||
"focus-visible:border-border-input-primary-press focus-visible:ring-border-input-primary-press/50 focus-visible:ring-2",
|
||||
"data-[state=checked]:border-button-primary",
|
||||
"disabled:cursor-not-allowed disabled:opacity-40",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<RadioGroupPrimitive.Indicator
|
||||
data-slot="radio-group-indicator"
|
||||
className="grid place-content-center"
|
||||
>
|
||||
<span className="bg-button-primary size-2 rounded-full" />
|
||||
</RadioGroupPrimitive.Indicator>
|
||||
</RadioGroupPrimitive.Item>
|
||||
);
|
||||
}
|
||||
|
||||
export { RadioGroup, RadioGroupItem };
|
||||
50
ui/components/shadcn/textarea/textarea.tsx
Normal file
50
ui/components/shadcn/textarea/textarea.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
"use client";
|
||||
|
||||
import { cva, type VariantProps } from "class-variance-authority";
|
||||
import { ComponentProps, forwardRef } from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const textareaVariants = cva(
|
||||
"flex w-full rounded-lg border text-sm transition-all outline-none resize-none disabled:cursor-not-allowed disabled:opacity-50",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-border-input-primary bg-bg-input-primary dark:bg-input/30 hover:bg-bg-neutral-secondary dark:hover:bg-input/50 focus:border-border-input-primary-press focus:ring-1 focus:ring-inset focus:ring-border-input-primary-press placeholder:text-text-neutral-tertiary",
|
||||
ghost:
|
||||
"border-transparent bg-transparent hover:bg-bg-neutral-tertiary focus:bg-bg-neutral-tertiary placeholder:text-text-neutral-tertiary",
|
||||
},
|
||||
textareaSize: {
|
||||
default: "min-h-16 px-4 py-3",
|
||||
sm: "min-h-12 px-3 py-2 text-xs",
|
||||
lg: "min-h-24 px-5 py-4",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
textareaSize: "default",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
export interface TextareaProps
|
||||
extends Omit<ComponentProps<"textarea">, "size">,
|
||||
VariantProps<typeof textareaVariants> {}
|
||||
|
||||
const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
({ className, variant, textareaSize, ...props }, ref) => {
|
||||
return (
|
||||
<textarea
|
||||
ref={ref}
|
||||
data-slot="textarea"
|
||||
className={cn(textareaVariants({ variant, textareaSize, className }))}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Textarea.displayName = "Textarea";
|
||||
|
||||
export { Textarea, textareaVariants };
|
||||
@@ -1,23 +0,0 @@
|
||||
import { type ReactNode } from "react";
|
||||
|
||||
interface CustomSectionProps {
|
||||
title: string | ReactNode;
|
||||
children: ReactNode;
|
||||
action?: ReactNode;
|
||||
}
|
||||
|
||||
export const CustomSection = ({
|
||||
title,
|
||||
children,
|
||||
action,
|
||||
}: CustomSectionProps) => (
|
||||
<div className="dark:bg-prowler-blue-400 flex flex-col gap-4 rounded-lg p-4 shadow">
|
||||
<div className="flex items-center justify-between">
|
||||
<h3 className="text-md dark:text-prowler-theme-pale/90 font-medium text-gray-800">
|
||||
{title}
|
||||
</h3>
|
||||
{action && <div>{action}</div>}
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
@@ -3,7 +3,6 @@ export * from "./custom-input";
|
||||
export * from "./custom-link";
|
||||
export * from "./custom-modal-buttons";
|
||||
export * from "./custom-radio";
|
||||
export * from "./custom-section";
|
||||
export * from "./custom-server-input";
|
||||
export * from "./custom-table-link";
|
||||
export * from "./custom-textarea";
|
||||
|
||||
@@ -159,6 +159,14 @@
|
||||
"strategy": "installed",
|
||||
"generatedAt": "2025-11-19T12:28:39.510Z"
|
||||
},
|
||||
{
|
||||
"section": "dependencies",
|
||||
"name": "@radix-ui/react-radio-group",
|
||||
"from": "1.3.8",
|
||||
"to": "1.3.8",
|
||||
"strategy": "installed",
|
||||
"generatedAt": "2026-03-05T14:12:59.626Z"
|
||||
},
|
||||
{
|
||||
"section": "dependencies",
|
||||
"name": "@radix-ui/react-scroll-area",
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
"@radix-ui/react-icons": "1.3.2",
|
||||
"@radix-ui/react-label": "2.1.7",
|
||||
"@radix-ui/react-popover": "1.1.15",
|
||||
"@radix-ui/react-radio-group": "1.3.8",
|
||||
"@radix-ui/react-scroll-area": "1.2.10",
|
||||
"@radix-ui/react-select": "2.2.5",
|
||||
"@radix-ui/react-separator": "1.1.7",
|
||||
|
||||
34
ui/pnpm-lock.yaml
generated
34
ui/pnpm-lock.yaml
generated
@@ -92,6 +92,9 @@ importers:
|
||||
'@radix-ui/react-popover':
|
||||
specifier: 1.1.15
|
||||
version: 1.1.15(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-radio-group':
|
||||
specifier: 1.3.8
|
||||
version: 1.3.8(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-scroll-area':
|
||||
specifier: 1.2.10
|
||||
version: 1.2.10(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
@@ -3047,6 +3050,19 @@ packages:
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-radio-group@1.3.8':
|
||||
resolution: {integrity: sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==}
|
||||
peerDependencies:
|
||||
'@types/react': '*'
|
||||
'@types/react-dom': '*'
|
||||
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
peerDependenciesMeta:
|
||||
'@types/react':
|
||||
optional: true
|
||||
'@types/react-dom':
|
||||
optional: true
|
||||
|
||||
'@radix-ui/react-roving-focus@1.1.10':
|
||||
resolution: {integrity: sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q==}
|
||||
peerDependencies:
|
||||
@@ -13078,6 +13094,24 @@ snapshots:
|
||||
'@types/react': 19.2.8
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.8)
|
||||
|
||||
'@radix-ui/react-radio-group@1.3.8(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.3
|
||||
'@radix-ui/react-compose-refs': 1.1.2(@types/react@19.2.8)(react@19.2.4)
|
||||
'@radix-ui/react-context': 1.1.2(@types/react@19.2.8)(react@19.2.4)
|
||||
'@radix-ui/react-direction': 1.1.1(@types/react@19.2.8)(react@19.2.4)
|
||||
'@radix-ui/react-presence': 1.1.5(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
|
||||
'@radix-ui/react-use-controllable-state': 1.2.2(@types/react@19.2.8)(react@19.2.4)
|
||||
'@radix-ui/react-use-previous': 1.1.1(@types/react@19.2.8)(react@19.2.4)
|
||||
'@radix-ui/react-use-size': 1.1.1(@types/react@19.2.8)(react@19.2.4)
|
||||
react: 19.2.4
|
||||
react-dom: 19.2.4(react@19.2.4)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.8
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.8)
|
||||
|
||||
'@radix-ui/react-roving-focus@1.1.10(@types/react-dom@19.2.3(@types/react@19.2.8))(@types/react@19.2.8)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
|
||||
dependencies:
|
||||
'@radix-ui/primitive': 1.1.2
|
||||
|
||||
Reference in New Issue
Block a user