diff --git a/ui/app/(prowler)/providers/(set-up-provider)/layout.tsx b/ui/app/(prowler)/providers/(set-up-provider)/layout.tsx index afaf3ed092..78110077c3 100644 --- a/ui/app/(prowler)/providers/(set-up-provider)/layout.tsx +++ b/ui/app/(prowler)/providers/(set-up-provider)/layout.tsx @@ -1,6 +1,5 @@ import "@/styles/globals.css"; -import { Spacer } from "@heroui/spacer"; import React from "react"; import { WorkflowAddProvider } from "@/components/providers/workflow"; @@ -18,7 +17,7 @@ export default function ProviderLayout({ children }: ProviderLayoutProps) { icon="icon-park-outline:close-small" href="/providers" /> - +
diff --git a/ui/components/providers/workflow/forms/connect-account-form.tsx b/ui/components/providers/workflow/forms/connect-account-form.tsx index 90e3cba642..4a0b73ccdf 100644 --- a/ui/components/providers/workflow/forms/connect-account-form.tsx +++ b/ui/components/providers/workflow/forms/connect-account-form.tsx @@ -8,6 +8,8 @@ import { useForm } from "react-hook-form"; import { z } from "zod"; import { addProvider } from "@/actions/providers/providers"; +import { AwsMethodSelector } from "@/components/providers/organizations/aws-method-selector"; +import { OrgWizardModal } from "@/components/providers/organizations/org-wizard-modal"; import { ProviderTitleDocs } from "@/components/providers/workflow/provider-title-docs"; import { Button } from "@/components/shadcn"; import { useToast } from "@/components/ui"; @@ -83,6 +85,8 @@ const getProviderFieldDetails = (providerType?: ProviderType) => { export const ConnectAccountForm = () => { const { toast } = useToast(); const [prevStep, setPrevStep] = useState(1); + const [awsMethod, setAwsMethod] = useState<"single" | null>(null); + const [isOrgModalOpen, setIsOrgModalOpen] = useState(false); const router = useRouter(); const formSchema = addProviderFormSchema; @@ -170,10 +174,19 @@ export const ConnectAccountForm = () => { }; const handleBackStep = () => { + // If in UID form after choosing single, go back to method selector + if (prevStep === 2 && awsMethod === "single") { + setAwsMethod(null); + form.setValue("providerUid", ""); + form.setValue("providerAlias", ""); + return; + } + setPrevStep((prev) => prev - 1); - //Deselect the providerType if the user is going back to the first step + // Deselect the providerType if the user is going back to the first step if (prevStep === 2) { form.setValue("providerType", undefined as unknown as ProviderType); + setAwsMethod(null); } // Reset the providerUid and providerAlias fields when going back form.setValue("providerUid", ""); @@ -200,35 +213,50 @@ export const ConnectAccountForm = () => { errorMessage={form.formState.errors.providerType?.message} /> )} - {/* Step 2: UID, alias, and credentials (if AWS) */} - {prevStep === 2 && ( + {/* Step 2: AWS method selector (only for AWS, before choosing method) */} + {prevStep === 2 && providerType === "aws" && awsMethod === null && ( <> - setAwsMethod("single")} + onSelectOrganizations={() => setIsOrgModalOpen(true)} /> - )} + {/* Step 2: UID, alias form (non-AWS or AWS single account) */} + {prevStep === 2 && + (providerType !== "aws" || awsMethod === "single") && ( + <> + + + + + )} {/* Navigation buttons */}
- {/* Show "Back" button only in Step 2 */} + {/* Show "Back" button in Step 2 (skip when on AWS method selector) */} {prevStep === 2 && ( )} - {/* Show "Next" button in Step 2 */} - {prevStep === 2 && ( - - )} + {/* Show "Next" button in Step 2 (only when UID form is visible) */} + {prevStep === 2 && + (providerType !== "aws" || awsMethod === "single") && ( + + )}
diff --git a/ui/components/providers/workflow/index.ts b/ui/components/providers/workflow/index.ts index 65cabc53ce..fa64a01884 100644 --- a/ui/components/providers/workflow/index.ts +++ b/ui/components/providers/workflow/index.ts @@ -1,5 +1,4 @@ export * from "./credentials-role-helper"; export * from "./provider-title-docs"; export * from "./skeleton-provider-workflow"; -export * from "./vertical-steps"; export * from "./workflow-add-provider"; diff --git a/ui/components/providers/workflow/workflow-add-provider.tsx b/ui/components/providers/workflow/workflow-add-provider.tsx index fbb5a3e393..46ca13e9bb 100644 --- a/ui/components/providers/workflow/workflow-add-provider.tsx +++ b/ui/components/providers/workflow/workflow-add-provider.tsx @@ -1,97 +1,149 @@ "use client"; -import { Progress } from "@heroui/progress"; -import { Spacer } from "@heroui/spacer"; +import { FolderGit2, KeyRound, Shield } from "lucide-react"; import { usePathname } from "next/navigation"; -import React from "react"; -import { VerticalSteps } from "./vertical-steps"; +import { ProwlerShort } from "@/components/icons/prowler/ProwlerIcons"; +import { cn } from "@/lib/utils"; +import { IconComponent, IconSvgProps } from "@/types/components"; -const steps = [ +interface StepConfig { + label: string; + description: string; + icon: IconComponent; +} + +const STEPS: StepConfig[] = [ { - title: "Choose your Cloud Provider", + label: "Link a Cloud Provider", description: - "Select the cloud provider you wish to connect and specify your preferred authentication method from the supported options.", - href: "/providers/connect-account", + "Enter the provider details you would like to add in Prowler.", + icon: FolderGit2, }, { - title: "Enter Authentication Details", + label: "Authenticate Credentials", description: - "Provide the necessary credentials to establish a secure connection to your selected cloud provider.", - href: "/providers/add-credentials", + "Authorize a secure connection between Prowler and your provider.", + icon: KeyRound, }, { - title: "Verify Connection & Start Scan", + label: "Validate Connection", description: - "Ensure your credentials are correct and start scanning your cloud environment.", - href: "/providers/test-connection", + "Review provider resources and test the connection to Prowler.", + icon: Shield, + }, + { + label: "Launch Scan", + description: "Scan newly connected resources.", + icon: ProwlerShort, }, ]; -const ROUTE_CONFIG: Record< - string, - { - stepIndex: number; - stepOverride?: { index: number; title: string; description: string }; - } -> = { - "/providers/connect-account": { stepIndex: 0 }, - "/providers/add-credentials": { stepIndex: 1 }, - "/providers/test-connection": { stepIndex: 2 }, - "/providers/update-credentials": { - stepIndex: 1, - stepOverride: { - index: 2, - title: "Make sure the new credentials are valid", - description: "Valid credentials will take you back to the providers page", - }, - }, +const ROUTE_TO_STEP: Record = { + "/providers/connect-account": 0, + "/providers/add-credentials": 1, + "/providers/test-connection": 2, + "/providers/update-credentials": 1, }; export const WorkflowAddProvider = () => { const pathname = usePathname(); - - const config = ROUTE_CONFIG[pathname] || { stepIndex: 0 }; - const currentStep = config.stepIndex; - - const updatedSteps = steps.map((step, index) => { - if (config.stepOverride && index === config.stepOverride.index) { - return { ...step, ...config.stepOverride }; - } - return step; - }); + const currentStep = ROUTE_TO_STEP[pathname] ?? 0; return ( -
-

- Add a Cloud Provider -

-

- Complete these steps to configure your cloud provider and initiate your - first scan. -

- - - -
+ ); }; + +interface StepCircleProps { + isComplete: boolean; + isActive: boolean; + icon: IconComponent; +} + +function StepCircle({ isComplete, isActive, icon: Icon }: StepCircleProps) { + const borderColor = + isComplete || isActive ? "border-primary" : "border-[#202020]"; + const iconColor = isComplete || isActive ? "text-primary" : "text-[#525252]"; + + return ( +
+ +
+ ); +} + +/** + * Renders either a Lucide icon (className-based) or a custom SVG icon + * (size-prop-based like ProwlerShort) with consistent sizing. + */ +function StepIcon({ + icon: Icon, + className, +}: { + icon: IconComponent; + className: string; +}) { + if (isCustomSvgIcon(Icon)) { + return ; + } + return ; +} + +/** + * Type guard: Lucide icons have a `displayName` property set by lucide-react. + * Custom SVG icons (React.FC) accept a `size` number prop. + */ +function isCustomSvgIcon(icon: IconComponent): icon is React.FC { + return !("displayName" in icon && typeof icon.displayName === "string"); +}