mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-21 10:54:20 +00:00
164 lines
5.0 KiB
TypeScript
164 lines
5.0 KiB
TypeScript
"use client";
|
|
|
|
import { zodResolver } from "@hookform/resolvers/zod";
|
|
import { CloudCog, Rocket } from "lucide-react";
|
|
import Link from "next/link";
|
|
import { useRouter, useSearchParams } from "next/navigation";
|
|
import { useForm } from "react-hook-form";
|
|
import { z } from "zod";
|
|
|
|
import { scanOnDemand } from "@/actions/scans";
|
|
import { AccountsSelector } from "@/app/(prowler)/_overview/_components/accounts-selector";
|
|
import { Field, FieldError, FieldLabel, Input } from "@/components/shadcn";
|
|
import { Modal } from "@/components/shadcn/modal";
|
|
import { FormButtons } from "@/components/ui/form";
|
|
import { toast, ToastAction } from "@/components/ui/toast";
|
|
import { SCAN_JOBS_TAB } from "@/types";
|
|
import type { ProviderProps } from "@/types/providers";
|
|
|
|
import { scanAliasSchema } from "./scan-alias-validation";
|
|
import { getScanJobsTab } from "./scans.utils";
|
|
|
|
const launchScanSchema = z.object({
|
|
providerId: z.string().min(1, "Select a provider to launch a scan."),
|
|
scanAlias: scanAliasSchema.optional(),
|
|
});
|
|
|
|
type LaunchScanFormValues = z.infer<typeof launchScanSchema>;
|
|
|
|
interface LaunchScanModalProps {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
providers: ProviderProps[];
|
|
}
|
|
|
|
interface LaunchScanFormProps {
|
|
providers: ProviderProps[];
|
|
onClose: () => void;
|
|
}
|
|
|
|
function LaunchScanForm({ providers, onClose }: LaunchScanFormProps) {
|
|
const router = useRouter();
|
|
const searchParams = useSearchParams();
|
|
const form = useForm<LaunchScanFormValues>({
|
|
resolver: zodResolver(launchScanSchema),
|
|
defaultValues: { providerId: "", scanAlias: "" },
|
|
});
|
|
|
|
const providerId = form.watch("providerId");
|
|
const activeTab = getScanJobsTab(searchParams.get("tab") ?? undefined);
|
|
const shouldShowActiveTabAction = activeTab !== SCAN_JOBS_TAB.ACTIVE;
|
|
const disconnectedProviderIds = providers
|
|
.filter((provider) => provider.attributes.connection.connected !== true)
|
|
.map((provider) => provider.id);
|
|
|
|
const onSubmit = form.handleSubmit(async ({ providerId, scanAlias }) => {
|
|
const formData = new FormData();
|
|
formData.set("providerId", providerId);
|
|
const trimmedAlias = scanAlias?.trim();
|
|
if (trimmedAlias) {
|
|
formData.set("scanName", trimmedAlias);
|
|
}
|
|
|
|
const result = await scanOnDemand(formData);
|
|
|
|
if (result?.error) {
|
|
form.setError("root", { message: String(result.error) });
|
|
return;
|
|
}
|
|
|
|
if (result?.errors && result.errors.length > 0) {
|
|
form.setError("root", {
|
|
message: String(result.errors[0]?.detail ?? "Failed to launch scan."),
|
|
});
|
|
return;
|
|
}
|
|
|
|
toast({
|
|
title: "Scan launched",
|
|
description: "The scan was launched successfully.",
|
|
action: shouldShowActiveTabAction ? (
|
|
<ToastAction altText="View scan in progress" asChild>
|
|
<Link href="/scans?tab=active">View scan</Link>
|
|
</ToastAction>
|
|
) : undefined,
|
|
});
|
|
onClose();
|
|
router.refresh();
|
|
});
|
|
|
|
const providerError = form.formState.errors.providerId?.message;
|
|
const aliasError = form.formState.errors.scanAlias?.message;
|
|
const rootError = form.formState.errors.root?.message;
|
|
const isSubmitting = form.formState.isSubmitting;
|
|
|
|
return (
|
|
<form onSubmit={onSubmit} className="flex flex-col gap-8">
|
|
<div className="flex items-center gap-2">
|
|
<CloudCog className="text-text-neutral-secondary size-4" />
|
|
<span className="text-text-neutral-secondary text-sm">
|
|
Select the provider you would like to scan
|
|
</span>
|
|
</div>
|
|
|
|
<Field>
|
|
<FieldLabel htmlFor="launch-scan-account">Providers</FieldLabel>
|
|
<AccountsSelector
|
|
id="launch-scan-account"
|
|
providers={providers}
|
|
disabledValues={disconnectedProviderIds}
|
|
onBatchChange={(_, values) =>
|
|
form.setValue("providerId", values.at(-1) ?? "", {
|
|
shouldValidate: true,
|
|
})
|
|
}
|
|
selectedValues={providerId ? [providerId] : []}
|
|
closeOnSelect
|
|
/>
|
|
{providerError && <FieldError>{providerError}</FieldError>}
|
|
</Field>
|
|
|
|
<Field>
|
|
<FieldLabel htmlFor="launch-scan-alias">Alias (optional)</FieldLabel>
|
|
<Input
|
|
id="launch-scan-alias"
|
|
aria-label="Alias"
|
|
{...form.register("scanAlias")}
|
|
/>
|
|
{aliasError && <FieldError>{aliasError}</FieldError>}
|
|
</Field>
|
|
|
|
{rootError && <FieldError>{rootError}</FieldError>}
|
|
|
|
<FormButtons
|
|
onCancel={onClose}
|
|
submitText={isSubmitting ? "Launching..." : "Launch Scan"}
|
|
loadingText="Launching..."
|
|
isDisabled={isSubmitting || !providers.length}
|
|
rightIcon={<Rocket className="size-4" />}
|
|
/>
|
|
</form>
|
|
);
|
|
}
|
|
|
|
export function LaunchScanModal({
|
|
open,
|
|
onOpenChange,
|
|
providers,
|
|
}: LaunchScanModalProps) {
|
|
return (
|
|
<Modal
|
|
open={open}
|
|
onOpenChange={onOpenChange}
|
|
title="Launch A Scan"
|
|
size="xl"
|
|
className="gap-8"
|
|
>
|
|
<LaunchScanForm
|
|
providers={providers}
|
|
onClose={() => onOpenChange(false)}
|
|
/>
|
|
</Modal>
|
|
);
|
|
}
|