From 1fff7ef1d3aa8d5981806b09982d9bcc87c312e5 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 11 Oct 2024 16:18:15 +0200 Subject: [PATCH] feat: add PATCH method for scans --- actions/scans/scans.ts | 41 +++++- app/(prowler)/scans/page.tsx | 2 +- components/providers/forms/edit-form.tsx | 2 +- components/scans/forms/edit-scan-form.tsx | 119 ++++++++++++++++++ components/scans/forms/index.ts | 1 + .../scans/table/column-get-scans-schedule.tsx | 2 +- components/scans/table/index.ts | 2 - .../table/{ => scans}/column-get-scans.tsx | 0 .../{ => scans}/data-table-row-actions.tsx | 44 +++---- components/scans/table/scans/index.ts | 2 + types/formSchemas.ts | 14 +++ 11 files changed, 196 insertions(+), 33 deletions(-) create mode 100644 components/scans/forms/edit-scan-form.tsx rename components/scans/table/{ => scans}/column-get-scans.tsx (100%) rename components/scans/table/{ => scans}/data-table-row-actions.tsx (70%) create mode 100644 components/scans/table/scans/index.ts diff --git a/actions/scans/scans.ts b/actions/scans/scans.ts index 06e274b2c2..ab83c81c27 100644 --- a/actions/scans/scans.ts +++ b/actions/scans/scans.ts @@ -42,7 +42,8 @@ export const getScans = async ({ revalidatePath("/scans"); return parsedData; } catch (error) { - console.error("Error fetching providers:", error); + // eslint-disable-next-line no-console + console.error("Error fetching scans:", error); return undefined; } }; @@ -94,3 +95,41 @@ export const scanOnDemand = async (formData: FormData) => { }; } }; + +export const updateScan = async (formData: FormData) => { + const session = await auth(); + const keyServer = process.env.API_BASE_URL; + + const scanId = formData.get("scanId"); + const scanName = formData.get("scanName"); + + const url = new URL(`${keyServer}/scans/${scanId}`); + + try { + const response = await fetch(url.toString(), { + method: "PATCH", + headers: { + "Content-Type": "application/vnd.api+json", + Accept: "application/vnd.api+json", + Authorization: `Bearer ${session?.accessToken}`, + }, + body: JSON.stringify({ + data: { + type: "Scan", + id: scanId, + attributes: { + name: scanName, + }, + }, + }), + }); + const data = await response.json(); + revalidatePath("/scans"); + return parseStringify(data); + } catch (error) { + console.error(error); + return { + error: getErrorMessage(error), + }; + } +}; diff --git a/app/(prowler)/scans/page.tsx b/app/(prowler)/scans/page.tsx index d31a641a0e..71ed291d70 100644 --- a/app/(prowler)/scans/page.tsx +++ b/app/(prowler)/scans/page.tsx @@ -9,11 +9,11 @@ import { filterScans, } from "@/components/filters"; import { - ColumnGetScans, ColumnGetScansSchedule, SkeletonTableScans, } from "@/components/scans/table"; import { ColumnProviderScans } from "@/components/scans/table/provider-scans"; +import { ColumnGetScans } from "@/components/scans/table/scans"; import { Header } from "@/components/ui"; import { DataTable } from "@/components/ui/table"; import { SearchParamsProps } from "@/types"; diff --git a/components/providers/forms/edit-form.tsx b/components/providers/forms/edit-form.tsx index bae117b7da..7991cd9a5b 100644 --- a/components/providers/forms/edit-form.tsx +++ b/components/providers/forms/edit-form.tsx @@ -77,7 +77,7 @@ export const EditForm = ({ name="alias" type="text" label="Alias" - labelPlacement="inside" + labelPlacement="outside" placeholder={providerAlias} variant="bordered" isRequired={false} diff --git a/components/scans/forms/edit-scan-form.tsx b/components/scans/forms/edit-scan-form.tsx new file mode 100644 index 0000000000..54e3c17701 --- /dev/null +++ b/components/scans/forms/edit-scan-form.tsx @@ -0,0 +1,119 @@ +"use client"; + +import { zodResolver } from "@hookform/resolvers/zod"; +import { Dispatch, SetStateAction } from "react"; +import { useForm } from "react-hook-form"; +import * as z from "zod"; + +import { updateScan } from "@/actions/scans"; +import { SaveIcon } from "@/components/icons"; +import { useToast } from "@/components/ui"; +import { CustomButton, CustomInput } from "@/components/ui/custom"; +import { Form } from "@/components/ui/form"; +import { editScanFormSchema } from "@/types"; + +export const EditScanForm = ({ + scanId, + scanName, + setIsOpen, +}: { + scanId: string; + scanName: string; + setIsOpen: Dispatch>; +}) => { + const formSchema = editScanFormSchema(scanName); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + scanId: scanId, + scanName: scanName, + }, + }); + + const { toast } = useToast(); + + const isLoading = form.formState.isSubmitting; + + const onSubmitClient = async (values: z.infer) => { + const formData = new FormData(); + + Object.entries(values).forEach( + ([key, value]) => value !== undefined && formData.append(key, value), + ); + + const data = await updateScan(formData); + + if (data?.errors && data.errors.length > 0) { + const error = data.errors[0]; + const errorMessage = `${error.detail}`; + // show error + toast({ + variant: "destructive", + title: "Oops! Something went wrong", + description: errorMessage, + }); + } else { + toast({ + title: "Success!", + description: "The scan was updated successfully.", + }); + setIsOpen(false); // Close the modal on success + } + }; + + return ( +
+ +
+ Current name: {scanName} +
+
+ +
+ + +
+ setIsOpen(false)} + disabled={isLoading} + > + Cancel + + + } + > + {isLoading ? <>Loading : Save} + +
+
+ + ); +}; diff --git a/components/scans/forms/index.ts b/components/scans/forms/index.ts index 932d2ca7b4..59deb9251a 100644 --- a/components/scans/forms/index.ts +++ b/components/scans/forms/index.ts @@ -1,2 +1,3 @@ +export * from "./edit-scan-form"; export * from "./scan-on-demand-form"; export * from "./schedule-form"; diff --git a/components/scans/table/column-get-scans-schedule.tsx b/components/scans/table/column-get-scans-schedule.tsx index 71b6267791..2a2ff19996 100644 --- a/components/scans/table/column-get-scans-schedule.tsx +++ b/components/scans/table/column-get-scans-schedule.tsx @@ -6,7 +6,7 @@ import { DateWithTime, EntityInfoShort } from "@/components/ui/entities"; import { DataTableColumnHeader, StatusBadge } from "@/components/ui/table"; import { ScanProps } from "@/types"; -import { DataTableRowActions } from "./data-table-row-actions"; +import { DataTableRowActions } from "./scans/data-table-row-actions"; const getScanData = (row: { original: ScanProps }) => { return row.original; diff --git a/components/scans/table/index.ts b/components/scans/table/index.ts index 45b74533b4..25e8770e31 100644 --- a/components/scans/table/index.ts +++ b/components/scans/table/index.ts @@ -1,4 +1,2 @@ -export * from "./column-get-scans"; export * from "./column-get-scans-schedule"; -export * from "./data-table-row-actions"; export * from "./skeleton-table-scans"; diff --git a/components/scans/table/column-get-scans.tsx b/components/scans/table/scans/column-get-scans.tsx similarity index 100% rename from components/scans/table/column-get-scans.tsx rename to components/scans/table/scans/column-get-scans.tsx diff --git a/components/scans/table/data-table-row-actions.tsx b/components/scans/table/scans/data-table-row-actions.tsx similarity index 70% rename from components/scans/table/data-table-row-actions.tsx rename to components/scans/table/scans/data-table-row-actions.tsx index b37863baf7..06d7ddc833 100644 --- a/components/scans/table/data-table-row-actions.tsx +++ b/components/scans/table/scans/data-table-row-actions.tsx @@ -9,7 +9,6 @@ import { DropdownTrigger, } from "@nextui-org/react"; import { - AddNoteBulkIcon, DeleteDocumentBulkIcon, EditDocumentBulkIcon, } from "@nextui-org/shared-icons"; @@ -20,7 +19,8 @@ import { useState } from "react"; import { VerticalDotsIcon } from "@/components/icons"; import { CustomAlertModal } from "@/components/ui/custom"; -// import { EditForm } from "../forms"; +import { EditScanForm } from "../../forms"; + // import { DeleteForm } from "../forms/delete-form"; interface DataTableRowActionsProps { @@ -34,28 +34,27 @@ export function DataTableRowActions({ }: DataTableRowActionsProps) { const [isEditOpen, setIsEditOpen] = useState(false); const [isDeleteOpen, setIsDeleteOpen] = useState(false); - const providerId = (row.original as { id: string }).id; - // const providerAlias = (row.original as any).attributes?.alias; + const scanId = (row.original as { id: string }).id; + const scanName = (row.original as any).attributes?.name; return ( <> -

Hello

- {/* */} + />

Hello

{/* */} @@ -75,23 +74,14 @@ export function DataTableRowActions({ variant="flat" > - } - > -

action here + {providerId}

- {/* */} -
} onClick={() => setIsEditOpen(true)} > - Edit Provider + Edit Scan
@@ -99,8 +89,8 @@ export function DataTableRowActions({ key="delete" className="text-danger" color="danger" - description="Delete the provider permanently" - textValue="Delete Provider" + description="Delete the scan permanently" + textValue="Delete Scan" startContent={ ({ } onClick={() => setIsDeleteOpen(true)} > - Delete Provider + Delete Scan diff --git a/components/scans/table/scans/index.ts b/components/scans/table/scans/index.ts new file mode 100644 index 0000000000..d38c1879ca --- /dev/null +++ b/components/scans/table/scans/index.ts @@ -0,0 +1,2 @@ +export * from "./column-get-scans"; +export * from "./data-table-row-actions"; diff --git a/types/formSchemas.ts b/types/formSchemas.ts index c03afe2d95..e1df1285b8 100644 --- a/types/formSchemas.ts +++ b/types/formSchemas.ts @@ -1,5 +1,19 @@ import { z } from "zod"; +export const editScanFormSchema = (currentName: string) => + z.object({ + scanName: z + .string() + .refine((val) => val === "" || val.length >= 3, { + message: "The alias must be empty or have at least 3 characters.", + }) + .refine((val) => val !== currentName, { + message: "The new name must be different from the current one.", + }) + .optional(), + scanId: z.string(), + }); + export const onDemandScanFormSchema = () => z.object({ providerId: z.string(),