From a1585142b7beece14127aee5ba0f0cf0fd71c8a1 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Wed, 9 Oct 2024 13:56:56 +0200 Subject: [PATCH 01/20] chore: refactor custom filters to be able to have two in the same page --- app/(prowler)/providers/page.tsx | 3 +- app/(prowler)/scans/page.tsx | 114 ++++++++++++------ components/filters/data-filters.ts | 14 ++- components/filters/filter-controls.tsx | 6 +- components/providers/table/index.ts | 1 - .../scans/table/data-table-filter-custom.tsx | 75 ------------ components/scans/table/index.ts | 1 - .../table/data-table-filter-custom.tsx | 0 components/ui/table/data-table.tsx | 8 +- components/ui/table/index.ts | 1 + 10 files changed, 106 insertions(+), 117 deletions(-) delete mode 100644 components/scans/table/data-table-filter-custom.tsx rename components/{providers => ui}/table/data-table-filter-custom.tsx (100%) diff --git a/app/(prowler)/providers/page.tsx b/app/(prowler)/providers/page.tsx index abc0ccfa59..02860e2a0b 100644 --- a/app/(prowler)/providers/page.tsx +++ b/app/(prowler)/providers/page.tsx @@ -24,7 +24,7 @@ export default async function Providers({
- + @@ -60,6 +60,7 @@ const SSRDataTable = async ({ columns={ColumnProviders} data={providersData?.data || []} metadata={providersData?.meta} + customFilters={filterProviders} /> ); }; diff --git a/app/(prowler)/scans/page.tsx b/app/(prowler)/scans/page.tsx index f9cbb862cd..c0cc62a0af 100644 --- a/app/(prowler)/scans/page.tsx +++ b/app/(prowler)/scans/page.tsx @@ -1,54 +1,100 @@ import { Spacer } from "@nextui-org/react"; +import { Suspense } from "react"; -// import { Suspense } from "react"; -// import { getProviders } from "@/actions/providers"; -import { FilterControls, filterScans } from "@/components/filters"; -// import { ColumnScans, SkeletonTableScans } from "@/components/scans/table"; +import { getProviders } from "@/actions/providers"; +import { + FilterControls, + filterProviders, + filterScans, +} from "@/components/filters"; +import { ColumnScans, SkeletonTableScans } from "@/components/scans/table"; import { Header } from "@/components/ui"; -// import { DataTable } from "@/components/ui/table"; -// import { SearchParamsProps } from "@/types"; +import { DataTable } from "@/components/ui/table"; +import { SearchParamsProps } from "@/types"; -export default async function Scans() { - // const searchParamsKey = JSON.stringify(searchParams || {}); +export default async function Scans({ + searchParams, +}: { + searchParams: SearchParamsProps; +}) { + const searchParamsKey = JSON.stringify(searchParams || {}); return ( <>
- + - {/* }> - - */} +
+
+ }> + + +
+
+ }> + + +
+
); } -// const SSRDataTable = async ({ -// searchParams, -// }: { -// searchParams: SearchParamsProps; -// }) => { -// const page = parseInt(searchParams.page?.toString() || "1", 10); -// const sort = searchParams.sort?.toString(); +const SSRDataTableProviders = async ({ + searchParams, +}: { + searchParams: SearchParamsProps; +}) => { + const page = parseInt(searchParams.page?.toString() || "1", 10); + const sort = searchParams.sort?.toString(); -// // Extract all filter parameters -// const filters = Object.fromEntries( -// Object.entries(searchParams).filter(([key]) => key.startsWith("filter[")), -// ); + // Extract all filter parameters + const filters = Object.fromEntries( + Object.entries(searchParams).filter(([key]) => key.startsWith("filter[")), + ); -// // Extract query from filters -// const query = (filters["filter[search]"] as string) || ""; + // Extract query from filters + const query = (filters["filter[search]"] as string) || ""; -// const providersData = await getProviders({ query, page, sort, filters }); + const providersData = await getProviders({ query, page, sort, filters }); -// return ( -// -// ); -// }; + return ( + + ); +}; + +const SSRDataTableScans = async ({ + searchParams, +}: { + searchParams: SearchParamsProps; +}) => { + const page = parseInt(searchParams.page?.toString() || "1", 10); + const sort = searchParams.sort?.toString(); + + // Extract all filter parameters + const filters = Object.fromEntries( + Object.entries(searchParams).filter(([key]) => key.startsWith("filter[")), + ); + + // Extract query from filters + const query = (filters["filter[search]"] as string) || ""; + + const providersData = await getProviders({ query, page, sort, filters }); + + return ( + + ); +}; diff --git a/components/filters/data-filters.ts b/components/filters/data-filters.ts index 81bbf0d852..d6d876b522 100644 --- a/components/filters/data-filters.ts +++ b/components/filters/data-filters.ts @@ -8,9 +8,21 @@ export const filterProviders = [ ]; export const filterScans = [ + { + key: "state", + labelCheckboxGroup: "State", + values: [ + "available", + "scheduled", + "executing", + "completed", + "failed", + "cancelled", + ], + }, { key: "trigger", - labelCheckboxGroup: "Scan Schedule", + labelCheckboxGroup: "Schedule", values: ["scheduled", "manual"], }, // Add more filter categories as needed diff --git a/components/filters/filter-controls.tsx b/components/filters/filter-controls.tsx index 19053cb62b..28c455ceea 100644 --- a/components/filters/filter-controls.tsx +++ b/components/filters/filter-controls.tsx @@ -6,8 +6,8 @@ import React, { useCallback, useEffect, useState } from "react"; import { FilterControlsProps } from "@/types"; import { CrossIcon } from "../icons"; -import { DataTableFilterCustom } from "../providers/table"; import { CustomButton } from "../ui/custom"; +import { DataTableFilterCustom } from "../ui/table"; import { CustomCheckboxMutedFindings } from "./custo-checkbox-muted-findings"; import { CustomAccountSelection } from "./custom-account-selection"; import { CustomDatePicker } from "./custom-date-picker"; @@ -22,7 +22,7 @@ export const FilterControls: React.FC = ({ regions = false, accounts = false, mutedFindings = false, - customFilters = [], + customFilters, }) => { const router = useRouter(); const searchParams = useSearchParams(); @@ -69,7 +69,7 @@ export const FilterControls: React.FC = ({ )} - + {customFilters && } ); }; diff --git a/components/providers/table/index.ts b/components/providers/table/index.ts index 7cee6919ed..eb6e772b49 100644 --- a/components/providers/table/index.ts +++ b/components/providers/table/index.ts @@ -1,4 +1,3 @@ export * from "./column-providers"; -export * from "./data-table-filter-custom"; export * from "./data-table-row-actions"; export * from "./skeleton-table-provider"; diff --git a/components/scans/table/data-table-filter-custom.tsx b/components/scans/table/data-table-filter-custom.tsx deleted file mode 100644 index 5dc2f94dc2..0000000000 --- a/components/scans/table/data-table-filter-custom.tsx +++ /dev/null @@ -1,75 +0,0 @@ -"use client"; - -import { useRouter, useSearchParams } from "next/navigation"; -import React, { useState } from "react"; -import { useCallback } from "react"; - -import { CustomFilterIcon } from "@/components/icons"; -import { CustomButton, CustomDropdownFilter } from "@/components/ui/custom"; -import { FilterOption } from "@/types"; - -export interface DataTableFilterCustomProps { - filters: FilterOption[]; -} - -export const DataTableFilterCustom = ({ - filters, -}: DataTableFilterCustomProps) => { - const router = useRouter(); - const searchParams = useSearchParams(); - const [showFilters, setShowFilters] = useState(false); - - const pushDropdownFilter = useCallback( - (key: string, values: string[]) => { - const params = new URLSearchParams(searchParams); - const filterKey = `filter[${key}]`; - - if (values.length === 0) { - params.delete(filterKey); - } else { - params.set(filterKey, values.join(",")); - } - - router.push(`?${params.toString()}`); - }, - [router, searchParams], - ); - - return ( -
- } - onPress={() => setShowFilters(!showFilters)} - > -

- {showFilters ? "Hide Filters" : "Show Filters"} -

-
- -
-
- {filters.map((filter) => ( - - ))} -
-
-
- ); -}; diff --git a/components/scans/table/index.ts b/components/scans/table/index.ts index 1d24d2e598..62300a2d96 100644 --- a/components/scans/table/index.ts +++ b/components/scans/table/index.ts @@ -1,4 +1,3 @@ export * from "./column-scans"; -export * from "./data-table-filter-custom"; export * from "./data-table-row-actions"; export * from "./skeleton-table-scans"; diff --git a/components/providers/table/data-table-filter-custom.tsx b/components/ui/table/data-table-filter-custom.tsx similarity index 100% rename from components/providers/table/data-table-filter-custom.tsx rename to components/ui/table/data-table-filter-custom.tsx diff --git a/components/ui/table/data-table.tsx b/components/ui/table/data-table.tsx index 3fc9b65dcc..30593a7cf9 100644 --- a/components/ui/table/data-table.tsx +++ b/components/ui/table/data-table.tsx @@ -14,6 +14,7 @@ import { import { useState } from "react"; import { + DataTableFilterCustom, Table, TableBody, TableCell, @@ -22,18 +23,20 @@ import { TableRow, } from "@/components/ui/table"; import { DataTablePagination } from "@/components/ui/table/data-table-pagination"; -import { MetaDataProps } from "@/types"; +import { FilterOption, MetaDataProps } from "@/types"; interface DataTableProviderProps { columns: ColumnDef[]; data: TData[]; metadata?: MetaDataProps; + customFilters?: FilterOption[]; } export function DataTable({ columns, data, metadata, + customFilters, }: DataTableProviderProps) { const [sorting, setSorting] = useState([]); const [columnFilters, setColumnFilters] = useState([]); @@ -55,6 +58,9 @@ export function DataTable({ return ( <> +
+ +
diff --git a/components/ui/table/index.ts b/components/ui/table/index.ts index 67cf0fab8b..a344b959de 100644 --- a/components/ui/table/index.ts +++ b/components/ui/table/index.ts @@ -1,5 +1,6 @@ export * from "./data-table"; export * from "./data-table-column-header"; +export * from "./data-table-filter-custom"; export * from "./data-table-pagination"; export * from "./severity-badge"; export * from "./status-badge"; From 970cb97f7393766ee2c20bce1d11518ca4ed02c5 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 10 Oct 2024 08:52:36 +0200 Subject: [PATCH 02/20] chore: table for launch scan is added to scan page --- app/(prowler)/scans/page.tsx | 14 +-- components/providers/index.ts | 1 + components/providers/provider-info-short.tsx | 51 ++++++++ components/providers/snippet-id-provider.tsx | 3 + components/scans/table/column-scans.tsx | 116 ++++--------------- components/ui/table/data-table.tsx | 8 +- 6 files changed, 90 insertions(+), 103 deletions(-) create mode 100644 components/providers/provider-info-short.tsx diff --git a/app/(prowler)/scans/page.tsx b/app/(prowler)/scans/page.tsx index c0cc62a0af..3ce355a465 100644 --- a/app/(prowler)/scans/page.tsx +++ b/app/(prowler)/scans/page.tsx @@ -2,11 +2,7 @@ import { Spacer } from "@nextui-org/react"; import { Suspense } from "react"; import { getProviders } from "@/actions/providers"; -import { - FilterControls, - filterProviders, - filterScans, -} from "@/components/filters"; +import { FilterControls, filterScans } from "@/components/filters"; import { ColumnScans, SkeletonTableScans } from "@/components/scans/table"; import { Header } from "@/components/ui"; import { DataTable } from "@/components/ui/table"; @@ -27,13 +23,13 @@ export default async function Scans({ -
-
+
+
}>
-
+
}> @@ -66,7 +62,7 @@ const SSRDataTableProviders = async ({ columns={ColumnScans} data={providersData?.data || []} metadata={providersData?.meta} - customFilters={filterProviders} + // customFilters={filterProviders} /> ); }; diff --git a/components/providers/index.ts b/components/providers/index.ts index 9f2aed9e3a..0751b3587c 100644 --- a/components/providers/index.ts +++ b/components/providers/index.ts @@ -3,6 +3,7 @@ export * from "./CheckConnectionProvider"; export * from "./date-with-time"; export * from "./forms/delete-form"; export * from "./provider-info"; +export * from "./provider-info-short"; export * from "./radio-group-provider"; export * from "./scan-status"; export * from "./snippet-id-provider"; diff --git a/components/providers/provider-info-short.tsx b/components/providers/provider-info-short.tsx new file mode 100644 index 0000000000..5f32cbeefc --- /dev/null +++ b/components/providers/provider-info-short.tsx @@ -0,0 +1,51 @@ +import React from "react"; + +import { + AWSProviderBadge, + AzureProviderBadge, + GCPProviderBadge, + KS8ProviderBadge, +} from "../icons/providers-badge"; +import { SnippetIdProvider } from "./snippet-id-provider"; + +interface ProviderInfoProps { + connected: boolean | null; + provider: "aws" | "azure" | "gcp" | "kubernetes"; + providerAlias: string; + providerId: string; +} + +export const ProviderInfoShort: React.FC = ({ + provider, + providerAlias, + providerId, +}) => { + const getProviderLogo = () => { + switch (provider) { + case "aws": + return ; + case "azure": + return ; + case "gcp": + return ; + case "kubernetes": + return ; + default: + return null; + } + }; + + return ( +
+
+
+
{getProviderLogo()}
+
+ {providerAlias} + +
+
+
+
+ ); +}; diff --git a/components/providers/snippet-id-provider.tsx b/components/providers/snippet-id-provider.tsx index 820b08bd6d..0bcc0951c7 100644 --- a/components/providers/snippet-id-provider.tsx +++ b/components/providers/snippet-id-provider.tsx @@ -5,9 +5,11 @@ import { CopyIcon, DoneIcon, IdIcon } from "../icons"; interface SnippetIdProviderProps { providerId: string; + [key: string]: any; } export const SnippetIdProvider: React.FC = ({ providerId, + ...props }) => { return ( = ({ hideSymbol copyIcon={} checkIcon={} + {...props} >

diff --git a/components/scans/table/column-scans.tsx b/components/scans/table/column-scans.tsx index 401f02aa14..624d2bdc40 100644 --- a/components/scans/table/column-scans.tsx +++ b/components/scans/table/column-scans.tsx @@ -1,122 +1,56 @@ "use client"; import { ColumnDef } from "@tanstack/react-table"; -import { add } from "date-fns"; -import { - DateWithTime, - ProviderInfo, - SnippetIdProvider, -} from "@/components/providers"; -import { DataTableColumnHeader, StatusBadge } from "@/components/ui/table"; +import { AddIcon } from "@/components/icons/Icons"; +import { ProviderInfoShort } from "@/components/providers"; +import { CustomButton } from "@/components/ui/custom"; import { ProviderProps } from "@/types"; -import { DataTableRowActions } from "./data-table-row-actions"; - const getProviderData = (row: { original: ProviderProps }) => { return row.original; }; export const ColumnScans: ColumnDef[] = [ { - header: " ", - cell: ({ row }) =>

{row.index + 1}

, - }, - { - accessorKey: "account", - header: ({ column }) => ( - - ), + accessorKey: "provider", + header: "Provider", cell: ({ row }) => { const { - attributes: { connection, provider, alias }, + attributes: { connection, provider, alias, uid }, } = getProviderData(row); return ( - ); }, }, { - accessorKey: "uid", - header: ({ column }) => ( - - ), + accessorKey: "launchScan", + header: "Launch Scan", cell: ({ row }) => { const { attributes: { uid }, } = getProviderData(row); - return ; - }, - }, - { - accessorKey: "status", - header: "Scan Status", - cell: () => { - // Temporarily overwriting the value until the API is functional. - return ; - }, - }, - { - accessorKey: "lastScan", - header: ({ column }) => ( - - ), - cell: ({ row }) => { - const { - attributes: { updated_at }, - } = getProviderData(row); - return ; - }, - }, - { - accessorKey: "nextScan", - header: "Next Scan", - cell: ({ row }) => { - const { - attributes: { updated_at }, - } = getProviderData(row); - const nextDay = add(new Date(updated_at), { - hours: 24, - }); - return ; - }, - }, - { - accessorKey: "resources", - header: "Resources", - cell: () => { - // Temporarily overwriting the value until the API is functional. - return

{288}

; - }, - }, - { - accessorKey: "added", - header: ({ column }) => ( - - ), - cell: ({ row }) => { - const { - attributes: { inserted_at }, - } = getProviderData(row); - return ; - }, - }, - { - id: "actions", - cell: ({ row }) => { - return ; + return ( + } + onPress={() => { + console.log(uid); + }} + > + Start + + ); }, }, ]; diff --git a/components/ui/table/data-table.tsx b/components/ui/table/data-table.tsx index 30593a7cf9..ecec4422a3 100644 --- a/components/ui/table/data-table.tsx +++ b/components/ui/table/data-table.tsx @@ -58,9 +58,11 @@ export function DataTable({ return ( <> -
- -
+ {customFilters && ( +
+ +
+ )}
From 9409ea75e50834381c19616f96d62b2d311bca17 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 10 Oct 2024 09:17:31 +0200 Subject: [PATCH 03/20] chore: table for launch scan is added to scan page --- app/(prowler)/scans/page.tsx | 15 ++- components/providers/provider-info-short.tsx | 9 +- components/providers/provider-info.tsx | 6 +- components/providers/snippet-id-provider.tsx | 2 +- components/scans/table/column-get-scans.tsx | 122 ++++++++++++++++++ ...mn-scans.tsx => column-provider-scans.tsx} | 2 +- components/scans/table/index.ts | 3 +- 7 files changed, 146 insertions(+), 13 deletions(-) create mode 100644 components/scans/table/column-get-scans.tsx rename components/scans/table/{column-scans.tsx => column-provider-scans.tsx} (95%) diff --git a/app/(prowler)/scans/page.tsx b/app/(prowler)/scans/page.tsx index 3ce355a465..56043a18fe 100644 --- a/app/(prowler)/scans/page.tsx +++ b/app/(prowler)/scans/page.tsx @@ -3,7 +3,11 @@ import { Suspense } from "react"; import { getProviders } from "@/actions/providers"; import { FilterControls, filterScans } from "@/components/filters"; -import { ColumnScans, SkeletonTableScans } from "@/components/scans/table"; +import { + ColumnGetScans, + ColumnProviderScans, + SkeletonTableScans, +} from "@/components/scans/table"; import { Header } from "@/components/ui"; import { DataTable } from "@/components/ui/table"; import { SearchParamsProps } from "@/types"; @@ -23,8 +27,8 @@ export default async function Scans({ -
-
+
+
}> @@ -59,10 +63,9 @@ const SSRDataTableProviders = async ({ return ( ); }; @@ -87,7 +90,7 @@ const SSRDataTableScans = async ({ return ( = ({
{getProviderLogo()}
- {providerAlias} - + + {providerAlias} + +
diff --git a/components/providers/provider-info.tsx b/components/providers/provider-info.tsx index 7ff3a52821..47fb6c293c 100644 --- a/components/providers/provider-info.tsx +++ b/components/providers/provider-info.tsx @@ -60,13 +60,15 @@ export const ProviderInfo: React.FC = ({ }; return ( -
+
{getProviderLogo()}
{getIcon()}
- {providerAlias} + + {providerAlias} + {/* */}
diff --git a/components/providers/snippet-id-provider.tsx b/components/providers/snippet-id-provider.tsx index 0bcc0951c7..0d3c76d566 100644 --- a/components/providers/snippet-id-provider.tsx +++ b/components/providers/snippet-id-provider.tsx @@ -25,7 +25,7 @@ export const SnippetIdProvider: React.FC = ({ >

- + {providerId}

diff --git a/components/scans/table/column-get-scans.tsx b/components/scans/table/column-get-scans.tsx new file mode 100644 index 0000000000..caf45268e5 --- /dev/null +++ b/components/scans/table/column-get-scans.tsx @@ -0,0 +1,122 @@ +"use client"; + +import { ColumnDef } from "@tanstack/react-table"; +import { add } from "date-fns"; + +import { + DateWithTime, + ProviderInfo, + SnippetIdProvider, +} from "@/components/providers"; +import { DataTableColumnHeader, StatusBadge } from "@/components/ui/table"; +import { ProviderProps } from "@/types"; + +import { DataTableRowActions } from "./data-table-row-actions"; + +const getProviderData = (row: { original: ProviderProps }) => { + return row.original; +}; + +export const ColumnGetScans: ColumnDef[] = [ + { + header: " ", + cell: ({ row }) =>

{row.index + 1}

, + }, + { + accessorKey: "account", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const { + attributes: { connection, provider, alias }, + } = getProviderData(row); + return ( + + ); + }, + }, + { + accessorKey: "uid", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const { + attributes: { uid }, + } = getProviderData(row); + return ; + }, + }, + { + accessorKey: "status", + header: "Scan Status", + cell: () => { + // Temporarily overwriting the value until the API is functional. + return ; + }, + }, + { + accessorKey: "lastScan", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const { + attributes: { updated_at }, + } = getProviderData(row); + return ; + }, + }, + { + accessorKey: "nextScan", + header: "Next Scan", + cell: ({ row }) => { + const { + attributes: { updated_at }, + } = getProviderData(row); + const nextDay = add(new Date(updated_at), { + hours: 24, + }); + return ; + }, + }, + { + accessorKey: "resources", + header: "Resources", + cell: () => { + // Temporarily overwriting the value until the API is functional. + return

{288}

; + }, + }, + { + accessorKey: "added", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const { + attributes: { inserted_at }, + } = getProviderData(row); + return ; + }, + }, + { + id: "actions", + cell: ({ row }) => { + return ; + }, + }, +]; diff --git a/components/scans/table/column-scans.tsx b/components/scans/table/column-provider-scans.tsx similarity index 95% rename from components/scans/table/column-scans.tsx rename to components/scans/table/column-provider-scans.tsx index 624d2bdc40..9311f50086 100644 --- a/components/scans/table/column-scans.tsx +++ b/components/scans/table/column-provider-scans.tsx @@ -11,7 +11,7 @@ const getProviderData = (row: { original: ProviderProps }) => { return row.original; }; -export const ColumnScans: ColumnDef[] = [ +export const ColumnProviderScans: ColumnDef[] = [ { accessorKey: "provider", header: "Provider", diff --git a/components/scans/table/index.ts b/components/scans/table/index.ts index 62300a2d96..471e150f9f 100644 --- a/components/scans/table/index.ts +++ b/components/scans/table/index.ts @@ -1,3 +1,4 @@ -export * from "./column-scans"; +export * from "./column-get-scans"; +export * from "./column-provider-scans"; export * from "./data-table-row-actions"; export * from "./skeleton-table-scans"; From 5246d84599e1b9d3c457eb90eb5c93b589cda477 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 10 Oct 2024 14:02:21 +0200 Subject: [PATCH 04/20] chore: retrieve values for all scans in getScans --- actions/scans/actions.ts | 48 +++++++++ actions/scans/index.ts | 1 + app/(prowler)/scans/page.tsx | 7 +- components/providers/index.ts | 4 - .../providers/table/column-providers.tsx | 5 +- components/scans/table/column-get-scans.tsx | 97 ++++++++----------- .../scans/table/column-provider-scans.tsx | 15 ++- .../entities}/date-with-time.tsx | 3 +- .../entities/entity-info-short.tsx} | 31 +++--- components/ui/entities/index.ts | 4 + .../entities}/scan-status.tsx | 0 .../entities/snippet-id.tsx} | 13 +-- components/ui/table/status-badge.tsx | 22 ++--- components/users/table/ColumnsUser.tsx | 12 +-- types/components.ts | 41 ++++++++ 15 files changed, 178 insertions(+), 125 deletions(-) create mode 100644 actions/scans/actions.ts create mode 100644 actions/scans/index.ts rename components/{providers => ui/entities}/date-with-time.tsx (86%) rename components/{providers/provider-info-short.tsx => ui/entities/entity-info-short.tsx} (65%) create mode 100644 components/ui/entities/index.ts rename components/{providers => ui/entities}/scan-status.tsx (100%) rename components/{providers/snippet-id-provider.tsx => ui/entities/snippet-id.tsx} (70%) diff --git a/actions/scans/actions.ts b/actions/scans/actions.ts new file mode 100644 index 0000000000..81669dba15 --- /dev/null +++ b/actions/scans/actions.ts @@ -0,0 +1,48 @@ +"use server"; + +import { revalidatePath } from "next/cache"; +import { redirect } from "next/navigation"; + +import { auth } from "@/auth.config"; +import { parseStringify } from "@/lib"; + +export const getScans = async ({ + page = 1, + query = "", + sort = "", + filters = {}, +}) => { + const session = await auth(); + + if (isNaN(Number(page)) || page < 1) redirect("/scans"); + + const keyServer = process.env.API_BASE_URL; + const url = new URL(`${keyServer}/scans`); + + if (page) url.searchParams.append("page[number]", page.toString()); + if (query) url.searchParams.append("filter[search]", query); + if (sort) url.searchParams.append("sort", sort); + + // Handle multiple filters + Object.entries(filters).forEach(([key, value]) => { + if (key !== "filter[search]") { + url.searchParams.append(key, String(value)); + } + }); + + try { + const scans = await fetch(url.toString(), { + headers: { + Accept: "application/vnd.api+json", + Authorization: `Bearer ${session?.accessToken}`, + }, + }); + const data = await scans.json(); + const parsedData = parseStringify(data); + revalidatePath("/scans"); + return parsedData; + } catch (error) { + console.error("Error fetching providers:", error); + return undefined; + } +}; diff --git a/actions/scans/index.ts b/actions/scans/index.ts new file mode 100644 index 0000000000..8ff5a89aff --- /dev/null +++ b/actions/scans/index.ts @@ -0,0 +1 @@ +export * from "./actions"; diff --git a/app/(prowler)/scans/page.tsx b/app/(prowler)/scans/page.tsx index 56043a18fe..bf2c594d8a 100644 --- a/app/(prowler)/scans/page.tsx +++ b/app/(prowler)/scans/page.tsx @@ -2,6 +2,7 @@ import { Spacer } from "@nextui-org/react"; import { Suspense } from "react"; import { getProviders } from "@/actions/providers"; +import { getScans } from "@/actions/scans"; import { FilterControls, filterScans } from "@/components/filters"; import { ColumnGetScans, @@ -86,13 +87,13 @@ const SSRDataTableScans = async ({ // Extract query from filters const query = (filters["filter[search]"] as string) || ""; - const providersData = await getProviders({ query, page, sort, filters }); + const scansData = await getScans({ query, page, sort, filters }); return ( ); diff --git a/components/providers/index.ts b/components/providers/index.ts index 0751b3587c..5093cbf0ac 100644 --- a/components/providers/index.ts +++ b/components/providers/index.ts @@ -1,9 +1,5 @@ export * from "./add-provider"; export * from "./CheckConnectionProvider"; -export * from "./date-with-time"; export * from "./forms/delete-form"; export * from "./provider-info"; -export * from "./provider-info-short"; export * from "./radio-group-provider"; -export * from "./scan-status"; -export * from "./snippet-id-provider"; diff --git a/components/providers/table/column-providers.tsx b/components/providers/table/column-providers.tsx index 4ba3e7d0ff..bc6e8cf69b 100644 --- a/components/providers/table/column-providers.tsx +++ b/components/providers/table/column-providers.tsx @@ -3,12 +3,11 @@ import { ColumnDef } from "@tanstack/react-table"; import { add } from "date-fns"; +import { DateWithTime, SnippetId } from "@/components/ui/entities"; import { DataTableColumnHeader, StatusBadge } from "@/components/ui/table"; import { ProviderProps } from "@/types"; -import { DateWithTime } from "../date-with-time"; import { ProviderInfo } from "../provider-info"; -import { SnippetIdProvider } from "../snippet-id-provider"; import { DataTableRowActions } from "./data-table-row-actions"; const getProviderData = (row: { original: ProviderProps }) => { @@ -47,7 +46,7 @@ export const ColumnProviders: ColumnDef[] = [ const { attributes: { uid }, } = getProviderData(row); - return ; + return ; }, }, { diff --git a/components/scans/table/column-get-scans.tsx b/components/scans/table/column-get-scans.tsx index caf45268e5..670d165c6f 100644 --- a/components/scans/table/column-get-scans.tsx +++ b/components/scans/table/column-get-scans.tsx @@ -3,61 +3,40 @@ import { ColumnDef } from "@tanstack/react-table"; import { add } from "date-fns"; -import { - DateWithTime, - ProviderInfo, - SnippetIdProvider, -} from "@/components/providers"; +import { DateWithTime, EntityInfoShort } from "@/components/ui/entities"; import { DataTableColumnHeader, StatusBadge } from "@/components/ui/table"; -import { ProviderProps } from "@/types"; +import { ScanProps } from "@/types"; import { DataTableRowActions } from "./data-table-row-actions"; -const getProviderData = (row: { original: ProviderProps }) => { +const getScanData = (row: { original: ScanProps }) => { return row.original; }; -export const ColumnGetScans: ColumnDef[] = [ +export const ColumnGetScans: ColumnDef[] = [ { - header: " ", - cell: ({ row }) =>

{row.index + 1}

, - }, - { - accessorKey: "account", + accessorKey: "name", header: ({ column }) => ( - + ), cell: ({ row }) => { const { - attributes: { connection, provider, alias }, - } = getProviderData(row); - return ( - - ); - }, - }, - { - accessorKey: "uid", - header: ({ column }) => ( - - ), - cell: ({ row }) => { - const { - attributes: { uid }, - } = getProviderData(row); - return ; + attributes: { name }, + } = getScanData(row); + return ; }, }, + { accessorKey: "status", - header: "Scan Status", - cell: () => { - // Temporarily overwriting the value until the API is functional. - return ; + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const { + attributes: { state }, + } = getScanData(row); + return ; }, }, { @@ -65,15 +44,15 @@ export const ColumnGetScans: ColumnDef[] = [ header: ({ column }) => ( ), cell: ({ row }) => { const { - attributes: { updated_at }, - } = getProviderData(row); - return ; + attributes: { completed_at }, + } = getScanData(row); + return ; }, }, { @@ -81,36 +60,40 @@ export const ColumnGetScans: ColumnDef[] = [ header: "Next Scan", cell: ({ row }) => { const { - attributes: { updated_at }, - } = getProviderData(row); - const nextDay = add(new Date(updated_at), { + attributes: { scheduled_at, completed_at }, + } = getScanData(row); + const nextDay = add(new Date(completed_at), { hours: 24, }); - return ; + if (scheduled_at === null) + return ; + return ; }, }, { accessorKey: "resources", header: "Resources", - cell: () => { - // Temporarily overwriting the value until the API is functional. - return

{288}

; + cell: ({ row }) => { + const { + attributes: { unique_resource_count }, + } = getScanData(row); + return

{unique_resource_count}

; }, }, { - accessorKey: "added", + accessorKey: "started_at", header: ({ column }) => ( ), cell: ({ row }) => { const { - attributes: { inserted_at }, - } = getProviderData(row); - return ; + attributes: { started_at }, + } = getScanData(row); + return ; }, }, { diff --git a/components/scans/table/column-provider-scans.tsx b/components/scans/table/column-provider-scans.tsx index 9311f50086..c86fd0f434 100644 --- a/components/scans/table/column-provider-scans.tsx +++ b/components/scans/table/column-provider-scans.tsx @@ -3,8 +3,8 @@ import { ColumnDef } from "@tanstack/react-table"; import { AddIcon } from "@/components/icons/Icons"; -import { ProviderInfoShort } from "@/components/providers"; import { CustomButton } from "@/components/ui/custom"; +import { EntityInfoShort } from "@/components/ui/entities"; import { ProviderProps } from "@/types"; const getProviderData = (row: { original: ProviderProps }) => { @@ -20,11 +20,11 @@ export const ColumnProviderScans: ColumnDef[] = [ attributes: { connection, provider, alias, uid }, } = getProviderData(row); return ( - ); }, @@ -33,9 +33,6 @@ export const ColumnProviderScans: ColumnDef[] = [ accessorKey: "launchScan", header: "Launch Scan", cell: ({ row }) => { - const { - attributes: { uid }, - } = getProviderData(row); return ( [] = [ size="md" endContent={} onPress={() => { - console.log(uid); + console.log(row.original.id); }} > Start diff --git a/components/providers/date-with-time.tsx b/components/ui/entities/date-with-time.tsx similarity index 86% rename from components/providers/date-with-time.tsx rename to components/ui/entities/date-with-time.tsx index 1e968fae3f..ae89c61985 100644 --- a/components/providers/date-with-time.tsx +++ b/components/ui/entities/date-with-time.tsx @@ -2,7 +2,7 @@ import { format, parseISO } from "date-fns"; import React from "react"; interface DateWithTimeProps { - dateTime: string; // e.g., "2024-07-17T09:55:14.191475Z" + dateTime: string | null; // e.g., "2024-07-17T09:55:14.191475Z" showTime?: boolean; } @@ -10,6 +10,7 @@ export const DateWithTime: React.FC = ({ dateTime, showTime = true, }) => { + if (!dateTime) return --; const date = parseISO(dateTime); const formattedDate = format(date, "MMM dd, yyyy"); const formattedTime = format(date, "p 'UTC'"); diff --git a/components/providers/provider-info-short.tsx b/components/ui/entities/entity-info-short.tsx similarity index 65% rename from components/providers/provider-info-short.tsx rename to components/ui/entities/entity-info-short.tsx index dfd8d46ad8..3e71d7afbc 100644 --- a/components/providers/provider-info-short.tsx +++ b/components/ui/entities/entity-info-short.tsx @@ -5,23 +5,23 @@ import { AzureProviderBadge, GCPProviderBadge, KS8ProviderBadge, -} from "../icons/providers-badge"; -import { SnippetIdProvider } from "./snippet-id-provider"; +} from "../../icons/providers-badge"; +import { SnippetId } from "./snippet-id"; -interface ProviderInfoProps { - connected: boolean | null; - provider: "aws" | "azure" | "gcp" | "kubernetes"; - providerAlias: string; - providerId: string; +interface EntityInfoProps { + connected?: boolean | null; + cloudProvider?: "aws" | "azure" | "gcp" | "kubernetes"; + entityAlias?: string; + entityId?: string; } -export const ProviderInfoShort: React.FC = ({ - provider, - providerAlias, - providerId, +export const EntityInfoShort: React.FC = ({ + cloudProvider, + entityAlias, + entityId, }) => { const getProviderLogo = () => { - switch (provider) { + switch (cloudProvider) { case "aws": return ; case "azure": @@ -42,12 +42,9 @@ export const ProviderInfoShort: React.FC = ({
{getProviderLogo()}
- {providerAlias} + {entityAlias} - +
diff --git a/components/ui/entities/index.ts b/components/ui/entities/index.ts new file mode 100644 index 0000000000..70a72f858c --- /dev/null +++ b/components/ui/entities/index.ts @@ -0,0 +1,4 @@ +export * from "./date-with-time"; +export * from "./entity-info-short"; +export * from "./scan-status"; +export * from "./snippet-id"; diff --git a/components/providers/scan-status.tsx b/components/ui/entities/scan-status.tsx similarity index 100% rename from components/providers/scan-status.tsx rename to components/ui/entities/scan-status.tsx diff --git a/components/providers/snippet-id-provider.tsx b/components/ui/entities/snippet-id.tsx similarity index 70% rename from components/providers/snippet-id-provider.tsx rename to components/ui/entities/snippet-id.tsx index 0d3c76d566..54b3a4dfa9 100644 --- a/components/providers/snippet-id-provider.tsx +++ b/components/ui/entities/snippet-id.tsx @@ -1,16 +1,13 @@ import { Snippet } from "@nextui-org/react"; import React from "react"; -import { CopyIcon, DoneIcon, IdIcon } from "../icons"; +import { CopyIcon, DoneIcon, IdIcon } from "@/components/icons"; -interface SnippetIdProviderProps { - providerId: string; +interface SnippetIdProps { + entityId: string; [key: string]: any; } -export const SnippetIdProvider: React.FC = ({ - providerId, - ...props -}) => { +export const SnippetId: React.FC = ({ entityId, ...props }) => { return ( = ({

- {providerId} + {entityId}

diff --git a/components/ui/table/status-badge.tsx b/components/ui/table/status-badge.tsx index e688021cd4..ca8b3e2a49 100644 --- a/components/ui/table/status-badge.tsx +++ b/components/ui/table/status-badge.tsx @@ -2,27 +2,23 @@ import { Chip } from "@nextui-org/react"; import React from "react"; type Status = + | "available" + | "scheduled" + | "executing" | "completed" - | "pending" - | "cancelled" - | "fail" - | "success" - | "muted" - | "active" - | "inactive"; + | "failed" + | "cancelled"; const statusColorMap: Record< Status, "danger" | "warning" | "success" | "default" > = { + available: "default", + scheduled: "warning", + executing: "default", completed: "success", - pending: "warning", + failed: "danger", cancelled: "danger", - fail: "danger", - success: "success", - muted: "default", - active: "success", - inactive: "default", }; export const StatusBadge = ({ status }: { status: Status }) => { diff --git a/components/users/table/ColumnsUser.tsx b/components/users/table/ColumnsUser.tsx index 605120c707..20ac2dba9d 100644 --- a/components/users/table/ColumnsUser.tsx +++ b/components/users/table/ColumnsUser.tsx @@ -2,8 +2,7 @@ import { ColumnDef } from "@tanstack/react-table"; -import { DateWithTime } from "@/components/providers"; -import { StatusBadge } from "@/components/ui/table"; +import { DateWithTime } from "@/components/ui/entities"; import { UserActions } from "@/components/users"; import { UserProps } from "@/types"; @@ -44,14 +43,7 @@ export const ColumnsUser: ColumnDef[] = [ return ; }, }, - { - accessorKey: "status", - header: "Status", - cell: ({ row }) => { - const { status } = getUserData(row); - return ; - }, - }, + { accessorKey: "actions", header: () =>
Actions
, diff --git a/types/components.ts b/types/components.ts index 02018469dc..f88967e85e 100644 --- a/types/components.ts +++ b/types/components.ts @@ -66,6 +66,47 @@ export interface ProviderProps { }; } +export interface ScanProps { + type: "Scan"; + id: string; + attributes: { + name: string; + trigger: "scheduled" | "manual"; + state: + | "available" + | "scheduled" + | "executing" + | "completed" + | "failed" + | "cancelled"; + unique_resource_count: number; + progress: number; + scanner_args: { + only_logs?: boolean; + excluded_checks?: string[]; + aws_retries_max_attempts?: number; + } | null; + duration: number; + started_at: string; + completed_at: string; + scheduled_at: string; + }; + relationships: { + provider: { + data: { + id: string; + type: "Provider"; + }; + }; + task: { + data: { + id: string; + type: "Task"; + }; + }; + }; +} + export interface FindingProps { id: string; attributes: { From 5b51653d78e4fdd78cd6db48531b6026cbd1c30d Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 10 Oct 2024 14:02:36 +0200 Subject: [PATCH 05/20] chore: retrieve values for all scans in getScans --- components/findings/table/ColumnsFindings.tsx | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/components/findings/table/ColumnsFindings.tsx b/components/findings/table/ColumnsFindings.tsx index c8fef32133..b27f4a6dbb 100644 --- a/components/findings/table/ColumnsFindings.tsx +++ b/components/findings/table/ColumnsFindings.tsx @@ -10,7 +10,7 @@ import { import { ColumnDef } from "@tanstack/react-table"; import { VerticalDotsIcon } from "@/components/icons"; -import { SeverityBadge, StatusBadge } from "@/components/ui/table"; +import { SeverityBadge } from "@/components/ui/table"; import { FindingProps } from "@/types"; const getFindingsAttributes = (row: { original: FindingProps }) => { @@ -34,14 +34,7 @@ export const ColumnsFindings: ColumnDef[] = [ return ; }, }, - { - accessorKey: "status", - header: "Status", - cell: ({ row }) => { - const { status } = getFindingsAttributes(row); - return ; - }, - }, + { accessorKey: "region", header: "Region", From d79e1d6c9443a12cf2466586d2b160e10185d0d7 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 10 Oct 2024 15:24:50 +0200 Subject: [PATCH 06/20] chore: add table for schedule scans --- app/(prowler)/scans/page.tsx | 47 +++++++++- .../scans/table/column-get-scans-schedule.tsx | 90 +++++++++++++++++++ components/scans/table/column-get-scans.tsx | 79 ++++++++++------ components/scans/table/index.ts | 1 + 4 files changed, 185 insertions(+), 32 deletions(-) create mode 100644 components/scans/table/column-get-scans-schedule.tsx diff --git a/app/(prowler)/scans/page.tsx b/app/(prowler)/scans/page.tsx index bf2c594d8a..811f547412 100644 --- a/app/(prowler)/scans/page.tsx +++ b/app/(prowler)/scans/page.tsx @@ -3,9 +3,14 @@ import { Suspense } from "react"; import { getProviders } from "@/actions/providers"; import { getScans } from "@/actions/scans"; -import { FilterControls, filterScans } from "@/components/filters"; +import { + FilterControls, + filterProviders, + filterScans, +} from "@/components/filters"; import { ColumnGetScans, + ColumnGetScansSchedule, ColumnProviderScans, SkeletonTableScans, } from "@/components/scans/table"; @@ -26,15 +31,20 @@ export default async function Scans({ - + -
-
+
+
}>
+ }> + + +
+
}> @@ -67,6 +77,35 @@ const SSRDataTableProviders = async ({ columns={ColumnProviderScans} data={providersData?.data || []} metadata={providersData?.meta} + customFilters={filterProviders} + /> + ); +}; + +const SSRDataTableScansSchedule = async ({ + searchParams, +}: { + searchParams: SearchParamsProps; +}) => { + const page = parseInt(searchParams.page?.toString() || "1", 10); + const sort = searchParams.sort?.toString(); + + // Extract all filter parameters + const filters = Object.fromEntries( + Object.entries(searchParams).filter(([key]) => key.startsWith("filter[")), + ); + + // Extract query from filters + const query = (filters["filter[search]"] as string) || ""; + + const scansData = await getScans({ query, page, sort, filters }); + + return ( + ); }; diff --git a/components/scans/table/column-get-scans-schedule.tsx b/components/scans/table/column-get-scans-schedule.tsx new file mode 100644 index 0000000000..71b6267791 --- /dev/null +++ b/components/scans/table/column-get-scans-schedule.tsx @@ -0,0 +1,90 @@ +"use client"; + +import { ColumnDef } from "@tanstack/react-table"; + +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"; + +const getScanData = (row: { original: ScanProps }) => { + return row.original; +}; + +export const ColumnGetScansSchedule: ColumnDef[] = [ + { + accessorKey: "name", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const { + attributes: { name }, + } = getScanData(row); + return ; + }, + }, + + { + accessorKey: "status", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const { + attributes: { state }, + } = getScanData(row); + return ; + }, + }, + { + accessorKey: "started_at", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const { + attributes: { started_at }, + } = getScanData(row); + return ; + }, + }, + { + accessorKey: "lastScan", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const { + attributes: { completed_at }, + } = getScanData(row); + return ; + }, + }, + { + accessorKey: "resources", + header: "Resources", + cell: ({ row }) => { + const { + attributes: { unique_resource_count }, + } = getScanData(row); + return

{unique_resource_count}

; + }, + }, + + { + id: "actions", + cell: ({ row }) => { + return ; + }, + }, +]; diff --git a/components/scans/table/column-get-scans.tsx b/components/scans/table/column-get-scans.tsx index 670d165c6f..e8acabb434 100644 --- a/components/scans/table/column-get-scans.tsx +++ b/components/scans/table/column-get-scans.tsx @@ -1,7 +1,6 @@ "use client"; import { ColumnDef } from "@tanstack/react-table"; -import { add } from "date-fns"; import { DateWithTime, EntityInfoShort } from "@/components/ui/entities"; import { DataTableColumnHeader, StatusBadge } from "@/components/ui/table"; @@ -26,6 +25,18 @@ export const ColumnGetScans: ColumnDef[] = [ return ; }, }, + { + accessorKey: "trigger", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const { + attributes: { trigger }, + } = getScanData(row); + return

{trigger}

; + }, + }, { accessorKey: "status", @@ -40,11 +51,43 @@ export const ColumnGetScans: ColumnDef[] = [ }, }, { - accessorKey: "lastScan", + accessorKey: "scheduled_at", header: ({ column }) => ( + ), + cell: ({ row }) => { + const { + attributes: { scheduled_at }, + } = getScanData(row); + return ; + }, + }, + { + accessorKey: "started_at", + header: ({ column }) => ( + + ), + cell: ({ row }) => { + const { + attributes: { started_at }, + } = getScanData(row); + return ; + }, + }, + { + accessorKey: "completed_at", + header: ({ column }) => ( + ), @@ -56,18 +99,13 @@ export const ColumnGetScans: ColumnDef[] = [ }, }, { - accessorKey: "nextScan", - header: "Next Scan", + accessorKey: "scanner_args", + header: "Scanner Args", cell: ({ row }) => { const { - attributes: { scheduled_at, completed_at }, + attributes: { scanner_args }, } = getScanData(row); - const nextDay = add(new Date(completed_at), { - hours: 24, - }); - if (scheduled_at === null) - return ; - return ; + return

{scanner_args?.only_logs}

; }, }, { @@ -80,22 +118,7 @@ export const ColumnGetScans: ColumnDef[] = [ return

{unique_resource_count}

; }, }, - { - accessorKey: "started_at", - header: ({ column }) => ( - - ), - cell: ({ row }) => { - const { - attributes: { started_at }, - } = getScanData(row); - return ; - }, - }, + { id: "actions", cell: ({ row }) => { diff --git a/components/scans/table/index.ts b/components/scans/table/index.ts index 471e150f9f..9015ed3651 100644 --- a/components/scans/table/index.ts +++ b/components/scans/table/index.ts @@ -1,4 +1,5 @@ export * from "./column-get-scans"; +export * from "./column-get-scans-schedule"; export * from "./column-provider-scans"; export * from "./data-table-row-actions"; export * from "./skeleton-table-scans"; From bd46196fd08f3308cf9efd3b086a2eba41b62983 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 10 Oct 2024 15:34:10 +0200 Subject: [PATCH 07/20] chore: replace icon for spnippet id --- components/icons/Icons.tsx | 9 +++------ components/ui/entities/snippet-id.tsx | 2 +- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/components/icons/Icons.tsx b/components/icons/Icons.tsx index 2816f0dd74..2b9d9d533c 100644 --- a/components/icons/Icons.tsx +++ b/components/icons/Icons.tsx @@ -475,16 +475,13 @@ export const IdIcon: React.FC = ({ }) => ( - + ); diff --git a/components/ui/entities/snippet-id.tsx b/components/ui/entities/snippet-id.tsx index 54b3a4dfa9..8ac946d4ca 100644 --- a/components/ui/entities/snippet-id.tsx +++ b/components/ui/entities/snippet-id.tsx @@ -21,7 +21,7 @@ export const SnippetId: React.FC = ({ entityId, ...props }) => { {...props} >

- + {entityId} From 4cf7a3244f6b4de32dec6cffb8a85a46f2104764 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 10 Oct 2024 15:43:41 +0200 Subject: [PATCH 08/20] chore: replace icon for check connection component --- components/icons/Icons.tsx | 83 ++++++++++++++++++++++++++ components/providers/provider-info.tsx | 14 ++--- 2 files changed, 90 insertions(+), 7 deletions(-) diff --git a/components/icons/Icons.tsx b/components/icons/Icons.tsx index 2b9d9d533c..6d886cf788 100644 --- a/components/icons/Icons.tsx +++ b/components/icons/Icons.tsx @@ -575,6 +575,89 @@ export const ConnectionIcon: React.FC = ({ ); }; +export const ConnectionTrue: React.FC = ({ + size = 24, + width, + height, + ...props +}) => ( + + + +); + +export const ConnectionFalse: React.FC = ({ + size = 24, + width, + height, + ...props +}) => ( + + + +); + +export const ConnectionPending: React.FC = ({ + size = 24, + width, + height, + ...props +}) => ( + + + + + + + + +); + export const SuccessIcon: React.FC = ({ size = 24, width, diff --git a/components/providers/provider-info.tsx b/components/providers/provider-info.tsx index 47fb6c293c..9d6a11c371 100644 --- a/components/providers/provider-info.tsx +++ b/components/providers/provider-info.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { ConnectionIcon } from "../icons"; +import { ConnectionFalse, ConnectionPending, ConnectionTrue } from "../icons"; import { AWSProviderBadge, AzureProviderBadge, @@ -23,24 +23,24 @@ export const ProviderInfo: React.FC = ({ switch (connected) { case true: return ( -

- +
+
); case false: return ( -
- +
+
); case null: return (
- +
); default: - return ; + return ; } }; From b304f11b188386b4f80ccd9d69dcd289377c33a9 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 10 Oct 2024 16:37:18 +0200 Subject: [PATCH 09/20] chore: tweak styles for entity info short component --- components/ui/entities/entity-info-short.tsx | 17 +++++------ components/ui/entities/index.ts | 1 + components/ui/entities/snippet-id.tsx | 4 +-- components/ui/entities/snippet-label.tsx | 30 ++++++++++++++++++++ 4 files changed, 40 insertions(+), 12 deletions(-) create mode 100644 components/ui/entities/snippet-label.tsx diff --git a/components/ui/entities/entity-info-short.tsx b/components/ui/entities/entity-info-short.tsx index 3e71d7afbc..80a7084223 100644 --- a/components/ui/entities/entity-info-short.tsx +++ b/components/ui/entities/entity-info-short.tsx @@ -7,6 +7,7 @@ import { KS8ProviderBadge, } from "../../icons/providers-badge"; import { SnippetId } from "./snippet-id"; +import { SnippetLabel } from "./snippet-label"; interface EntityInfoProps { connected?: boolean | null; @@ -36,16 +37,12 @@ export const EntityInfoShort: React.FC = ({ }; return ( -
-
-
-
{getProviderLogo()}
-
- - {entityAlias} - - -
+
+
+
{getProviderLogo()}
+
+ +
diff --git a/components/ui/entities/index.ts b/components/ui/entities/index.ts index 70a72f858c..811b90956c 100644 --- a/components/ui/entities/index.ts +++ b/components/ui/entities/index.ts @@ -2,3 +2,4 @@ export * from "./date-with-time"; export * from "./entity-info-short"; export * from "./scan-status"; export * from "./snippet-id"; +export * from "./snippet-label"; diff --git a/components/ui/entities/snippet-id.tsx b/components/ui/entities/snippet-id.tsx index 8ac946d4ca..9ec4b90375 100644 --- a/components/ui/entities/snippet-id.tsx +++ b/components/ui/entities/snippet-id.tsx @@ -10,7 +10,7 @@ interface SnippetIdProps { export const SnippetId: React.FC = ({ entityId, ...props }) => { return ( = ({ entityId, ...props }) => { >

- + {entityId}

diff --git a/components/ui/entities/snippet-label.tsx b/components/ui/entities/snippet-label.tsx new file mode 100644 index 0000000000..8875976616 --- /dev/null +++ b/components/ui/entities/snippet-label.tsx @@ -0,0 +1,30 @@ +import { Snippet } from "@nextui-org/react"; +import React from "react"; + +import { CopyIcon, DoneIcon } from "@/components/icons"; + +interface SnippetLabelProps { + label: string; + [key: string]: any; +} +export const SnippetLabel: React.FC = ({ + label, + ...props +}) => { + return ( + } + checkIcon={} + {...props} + > +

+ {label} +

+
+ ); +}; From ab293735373078f1e33de2072b15180093c27dad Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 10 Oct 2024 16:39:30 +0200 Subject: [PATCH 10/20] chore: tweak styles snippet id component --- components/providers/table/column-providers.tsx | 2 +- components/ui/entities/snippet-id.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/providers/table/column-providers.tsx b/components/providers/table/column-providers.tsx index bc6e8cf69b..eb67f731d2 100644 --- a/components/providers/table/column-providers.tsx +++ b/components/providers/table/column-providers.tsx @@ -46,7 +46,7 @@ export const ColumnProviders: ColumnDef[] = [ const { attributes: { uid }, } = getProviderData(row); - return ; + return ; }, }, { diff --git a/components/ui/entities/snippet-id.tsx b/components/ui/entities/snippet-id.tsx index 9ec4b90375..5d08be7486 100644 --- a/components/ui/entities/snippet-id.tsx +++ b/components/ui/entities/snippet-id.tsx @@ -22,7 +22,7 @@ export const SnippetId: React.FC = ({ entityId, ...props }) => { >

- + {entityId}

From 0a8c352194a022bfb6e485fab4954f3ac11cbf13 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 10 Oct 2024 16:47:05 +0200 Subject: [PATCH 11/20] chore: rename file for scan actions --- actions/scans/index.ts | 2 +- actions/scans/{actions.ts => scans.ts} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename actions/scans/{actions.ts => scans.ts} (100%) diff --git a/actions/scans/index.ts b/actions/scans/index.ts index 8ff5a89aff..7e4b1aceeb 100644 --- a/actions/scans/index.ts +++ b/actions/scans/index.ts @@ -1 +1 @@ -export * from "./actions"; +export * from "./scans"; diff --git a/actions/scans/actions.ts b/actions/scans/scans.ts similarity index 100% rename from actions/scans/actions.ts rename to actions/scans/scans.ts From 69a1468c1897dafd5417486d18375837d8ab63f2 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 11 Oct 2024 06:49:14 +0200 Subject: [PATCH 12/20] Wrap CustomButton component with React.forwardRef --- app/(prowler)/scans/page.tsx | 4 +- components/scans/table/index.ts | 1 - .../column-provider-scans.tsx | 20 +-- .../provider-scans/data-table-row-actions.tsx | 128 +++++++++++++++++ .../scans/table/provider-scans/index.ts | 2 + components/ui/custom/custom-button.tsx | 129 ++++++++++-------- 6 files changed, 206 insertions(+), 78 deletions(-) rename components/scans/table/{ => provider-scans}/column-provider-scans.tsx (63%) create mode 100644 components/scans/table/provider-scans/data-table-row-actions.tsx create mode 100644 components/scans/table/provider-scans/index.ts diff --git a/app/(prowler)/scans/page.tsx b/app/(prowler)/scans/page.tsx index 811f547412..d31a641a0e 100644 --- a/app/(prowler)/scans/page.tsx +++ b/app/(prowler)/scans/page.tsx @@ -11,9 +11,9 @@ import { import { ColumnGetScans, ColumnGetScansSchedule, - ColumnProviderScans, SkeletonTableScans, } from "@/components/scans/table"; +import { ColumnProviderScans } from "@/components/scans/table/provider-scans"; import { Header } from "@/components/ui"; import { DataTable } from "@/components/ui/table"; import { SearchParamsProps } from "@/types"; @@ -33,7 +33,7 @@ export default async function Scans({ -
+
}> diff --git a/components/scans/table/index.ts b/components/scans/table/index.ts index 9015ed3651..45b74533b4 100644 --- a/components/scans/table/index.ts +++ b/components/scans/table/index.ts @@ -1,5 +1,4 @@ export * from "./column-get-scans"; export * from "./column-get-scans-schedule"; -export * from "./column-provider-scans"; export * from "./data-table-row-actions"; export * from "./skeleton-table-scans"; diff --git a/components/scans/table/column-provider-scans.tsx b/components/scans/table/provider-scans/column-provider-scans.tsx similarity index 63% rename from components/scans/table/column-provider-scans.tsx rename to components/scans/table/provider-scans/column-provider-scans.tsx index c86fd0f434..938c3c1681 100644 --- a/components/scans/table/column-provider-scans.tsx +++ b/components/scans/table/provider-scans/column-provider-scans.tsx @@ -2,11 +2,11 @@ import { ColumnDef } from "@tanstack/react-table"; -import { AddIcon } from "@/components/icons/Icons"; -import { CustomButton } from "@/components/ui/custom"; import { EntityInfoShort } from "@/components/ui/entities"; import { ProviderProps } from "@/types"; +import { DataTableRowActions } from "./data-table-row-actions"; + const getProviderData = (row: { original: ProviderProps }) => { return row.original; }; @@ -33,21 +33,7 @@ export const ColumnProviderScans: ColumnDef[] = [ accessorKey: "launchScan", header: "Launch Scan", cell: ({ row }) => { - return ( - } - onPress={() => { - console.log(row.original.id); - }} - > - Start - - ); + return ; }, }, ]; diff --git a/components/scans/table/provider-scans/data-table-row-actions.tsx b/components/scans/table/provider-scans/data-table-row-actions.tsx new file mode 100644 index 0000000000..954a197e7d --- /dev/null +++ b/components/scans/table/provider-scans/data-table-row-actions.tsx @@ -0,0 +1,128 @@ +"use client"; + +import { + Button, + Dropdown, + DropdownItem, + DropdownMenu, + DropdownSection, + DropdownTrigger, +} from "@nextui-org/react"; +import { + AddNoteBulkIcon, + DeleteDocumentBulkIcon, + EditDocumentBulkIcon, +} from "@nextui-org/shared-icons"; +import { Row } from "@tanstack/react-table"; +import clsx from "clsx"; +import { useState } from "react"; + +import { AddIcon } from "@/components/icons"; +import { CustomAlertModal, CustomButton } from "@/components/ui/custom"; + +// import { EditForm } from "../forms"; +// import { DeleteForm } from "../forms/delete-form"; + +interface DataTableRowActionsProps { + row: Row; +} +const iconClasses = + "text-2xl text-default-500 pointer-events-none flex-shrink-0"; + +export function DataTableRowActions({ + row, +}: DataTableRowActionsProps) { + const [isEditOpen, setIsEditOpen] = useState(false); + const [isDeleteOpen, setIsDeleteOpen] = useState(false); + const providerId = (row.original as { id: string }).id; + return ( + <> + {/* + + */} + {/* + + */} + +
+ + + } + > + Start + + {/* */} + + + + } + > + {/* */} + + } + onClick={() => setIsEditOpen(true)} + > + Edit Provider + + + + + } + onClick={() => setIsDeleteOpen(true)} + > + Delete Provider + + + + +
+ + ); +} diff --git a/components/scans/table/provider-scans/index.ts b/components/scans/table/provider-scans/index.ts new file mode 100644 index 0000000000..415bafad0d --- /dev/null +++ b/components/scans/table/provider-scans/index.ts @@ -0,0 +1,2 @@ +export * from "./column-provider-scans"; +export * from "./data-table-row-actions"; diff --git a/components/ui/custom/custom-button.tsx b/components/ui/custom/custom-button.tsx index a74751801a..f5e408173c 100644 --- a/components/ui/custom/custom-button.tsx +++ b/components/ui/custom/custom-button.tsx @@ -1,6 +1,7 @@ import { Button, CircularProgress } from "@nextui-org/react"; import type { PressEvent } from "@react-types/shared"; import clsx from "clsx"; +import React from "react"; import { NextUIColors, NextUIVariants } from "@/types"; @@ -16,7 +17,7 @@ export const buttonClasses = { hover: "hover:shadow-md", }; -interface ButtonProps { +interface CustomButtonProps { type?: "button" | "submit" | "reset"; ariaLabel: string; ariaDisabled?: boolean; @@ -48,64 +49,76 @@ interface ButtonProps { disabled?: boolean; isLoading?: boolean; isIconOnly?: boolean; + ref?: React.RefObject; } -export const CustomButton = ({ - type = "button", - ariaLabel, - ariaDisabled, - className, - variant = "solid", - color = "primary", - onPress, - children, - startContent, - endContent, - size = "md", - radius = "sm", - disabled = false, - isLoading = false, - isIconOnly, - ...props -}: ButtonProps) => ( - + variant = "solid", + color = "primary", + onPress, + children, + startContent, + endContent, + size = "md", + radius = "sm", + disabled = false, + isLoading = false, + isIconOnly, + ...props + }, + ref, + ) => ( + + ), ); + +CustomButton.displayName = "CustomButton"; From f29e87f45bb18fa318c4804a5a2edb514d180c67 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 11 Oct 2024 15:33:03 +0200 Subject: [PATCH 13/20] feat: Scan on demand can be executed now from the UI --- actions/providers/providers.ts | 17 +-- actions/scans/scans.ts | 50 ++++++- .../table/data-table-row-actions.tsx | 3 - components/scans/forms/index.ts | 2 + .../scans/forms/scan-on-demand-form.tsx | 126 ++++++++++++++++++ components/scans/forms/schedule-form.tsx | 112 ++++++++++++++++ .../scans/table/data-table-row-actions.tsx | 3 - .../provider-scans/data-table-row-actions.tsx | 108 +++++++-------- components/ui/entities/snippet-label.tsx | 30 +++-- lib/custom.ts | 15 +++ types/formSchemas.ts | 17 +++ 11 files changed, 383 insertions(+), 100 deletions(-) create mode 100644 components/scans/forms/index.ts create mode 100644 components/scans/forms/scan-on-demand-form.tsx create mode 100644 components/scans/forms/schedule-form.tsx diff --git a/actions/providers/providers.ts b/actions/providers/providers.ts index 200bc79bd6..4578786534 100644 --- a/actions/providers/providers.ts +++ b/actions/providers/providers.ts @@ -4,7 +4,7 @@ import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; import { auth } from "@/auth.config"; -import { parseStringify } from "@/lib"; +import { getErrorMessage, parseStringify } from "@/lib"; export const getProviders = async ({ page = 1, @@ -198,18 +198,3 @@ export const deleteProvider = async (formData: FormData) => { }; } }; - -export const getErrorMessage = async (error: unknown): Promise => { - let message: string; - - if (error instanceof Error) { - message = error.message; - } else if (error && typeof error === "object" && "message" in error) { - message = String(error.message); - } else if (typeof error === "string") { - message = error; - } else { - message = "Oops! Something went wrong."; - } - return message; -}; diff --git a/actions/scans/scans.ts b/actions/scans/scans.ts index 81669dba15..06e274b2c2 100644 --- a/actions/scans/scans.ts +++ b/actions/scans/scans.ts @@ -4,7 +4,7 @@ import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; import { auth } from "@/auth.config"; -import { parseStringify } from "@/lib"; +import { getErrorMessage, parseStringify } from "@/lib"; export const getScans = async ({ page = 1, @@ -46,3 +46,51 @@ export const getScans = async ({ return undefined; } }; + +export const scanOnDemand = async (formData: FormData) => { + const session = await auth(); + const keyServer = process.env.API_BASE_URL; + + const providerId = formData.get("providerId"); + const scanName = formData.get("scanName"); + + const url = new URL(`${keyServer}/scans`); + + try { + const response = await fetch(url.toString(), { + method: "POST", + headers: { + "Content-Type": "application/vnd.api+json", + Accept: "application/vnd.api+json", + Authorization: `Bearer ${session?.accessToken}`, + }, + body: JSON.stringify({ + data: { + type: "Scan", + attributes: { + name: scanName, + scanner_args: { + checks_to_execute: ["accessanalyzer_enabled"], + }, + }, + relationships: { + provider: { + data: { + type: "Provider", + id: providerId, + }, + }, + }, + }, + }), + }); + const data = await response.json(); + revalidatePath("/scans"); + return parseStringify(data); + } catch (error) { + console.error(error); + return { + error: getErrorMessage(error), + }; + } +}; diff --git a/components/providers/table/data-table-row-actions.tsx b/components/providers/table/data-table-row-actions.tsx index 661b2e86c8..35f14d395c 100644 --- a/components/providers/table/data-table-row-actions.tsx +++ b/components/providers/table/data-table-row-actions.tsx @@ -77,7 +77,6 @@ export function DataTableRowActions({ } > @@ -86,7 +85,6 @@ export function DataTableRowActions({ } onClick={() => setIsEditOpen(true)} @@ -101,7 +99,6 @@ export function DataTableRowActions({ color="danger" description="Delete the provider permanently" textValue="Delete Provider" - shortcut="⌘⇧D" startContent={ >; +}) => { + const formSchema = onDemandScanFormSchema(); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + providerId: providerId, + scanName: scanName, + scannerArgs: scannerArgs, + }, + }); + + const { toast } = useToast(); + + const isLoading = form.formState.isSubmitting; + + const onSubmitClient = async (values: z.infer) => { + const formData = new FormData(); + + // Loop through form values and add to formData, converting objects to JSON strings + Object.entries(values).forEach( + ([key, value]) => + value !== undefined && + formData.append( + key, + typeof value === "object" ? JSON.stringify(value) : value, + ), + ); + + const data = await scanOnDemand(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 launched successfully.", + }); + setIsOpen(false); + } + }; + + return ( +
+ + + +
+ +
+ +
+ setIsOpen(false)} + disabled={isLoading} + > + Cancel + + + } + > + {isLoading ? <>Loading : Start now} + +
+ + + ); +}; diff --git a/components/scans/forms/schedule-form.tsx b/components/scans/forms/schedule-form.tsx new file mode 100644 index 0000000000..d990380891 --- /dev/null +++ b/components/scans/forms/schedule-form.tsx @@ -0,0 +1,112 @@ +"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 { updateProvider } from "@/actions/providers"; +import { SaveIcon } from "@/components/icons"; +import { useToast } from "@/components/ui"; +import { CustomButton, CustomInput } from "@/components/ui/custom"; +import { Form } from "@/components/ui/form"; +import { scheduleScanFormSchema } from "@/types"; + +export const ScheduleForm = ({ + providerId, + scheduleDate, + setIsOpen, +}: { + providerId: string; + scheduleDate: string; + setIsOpen: Dispatch>; +}) => { + const formSchema = scheduleScanFormSchema(); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + providerId: providerId, + scheduleDate: scheduleDate, + }, + }); + + 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 updateProvider(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 scheduled successfully.", + }); + setIsOpen(false); // Close the modal on success + } + }; + + return ( +
+ + + + +
+ setIsOpen(false)} + disabled={isLoading} + > + Cancel + + + } + > + {isLoading ? <>Loading : Schedule} + +
+ + + ); +}; diff --git a/components/scans/table/data-table-row-actions.tsx b/components/scans/table/data-table-row-actions.tsx index fdb4f2d3b0..b37863baf7 100644 --- a/components/scans/table/data-table-row-actions.tsx +++ b/components/scans/table/data-table-row-actions.tsx @@ -78,7 +78,6 @@ export function DataTableRowActions({ } > @@ -88,7 +87,6 @@ export function DataTableRowActions({ } onClick={() => setIsEditOpen(true)} @@ -103,7 +101,6 @@ export function DataTableRowActions({ color="danger" description="Delete the provider permanently" textValue="Delete Provider" - shortcut="⌘⇧D" startContent={ { row: Row; @@ -32,34 +25,41 @@ const iconClasses = export function DataTableRowActions({ row, }: DataTableRowActionsProps) { - const [isEditOpen, setIsEditOpen] = useState(false); - const [isDeleteOpen, setIsDeleteOpen] = useState(false); + const [isScanOnDemandOpen, setIsScanOnDemandOpen] = useState(false); + const [isScanScheduleOpen, setIsScanScheduleOpen] = useState(false); + const providerId = (row.original as { id: string }).id; + const scanName = (row.original as any).attributes?.name; return ( <> - {/* - - */} - {/* + + - - */} + +
- + ({ > Start - {/* */} - + } + key="scanNow" + color="primary" + description="Allows you to start a scan on demand" + textValue="Start now" + startContent={} + onClick={() => setIsScanOnDemandOpen(true)} > - {/* */} - - } - onClick={() => setIsEditOpen(true)} - > - Edit Provider + Start now - + - } - onClick={() => setIsDeleteOpen(true)} + key="schedule" + color="primary" + description="Schedule a scan for this provider" + textValue="Schedule Scan" + startContent={} + onClick={() => setIsScanScheduleOpen(true)} > - Delete Provider + Schedule Scan diff --git a/components/ui/entities/snippet-label.tsx b/components/ui/entities/snippet-label.tsx index 8875976616..18525d6fe5 100644 --- a/components/ui/entities/snippet-label.tsx +++ b/components/ui/entities/snippet-label.tsx @@ -12,19 +12,21 @@ export const SnippetLabel: React.FC = ({ ...props }) => { return ( - } - checkIcon={} - {...props} - > -

- {label} -

-
+ label !== "" && ( + } + checkIcon={} + {...props} + > +

+ {label} +

+
+ ) ); }; diff --git a/lib/custom.ts b/lib/custom.ts index 075f6b95c8..a33f07d6fe 100644 --- a/lib/custom.ts +++ b/lib/custom.ts @@ -19,3 +19,18 @@ export function encryptKey(passkey: string) { export function decryptKey(passkey: string) { return atob(passkey); } + +export const getErrorMessage = async (error: unknown): Promise => { + let message: string; + + if (error instanceof Error) { + message = error.message; + } else if (error && typeof error === "object" && "message" in error) { + message = String(error.message); + } else if (typeof error === "string") { + message = error; + } else { + message = "Oops! Something went wrong."; + } + return message; +}; diff --git a/types/formSchemas.ts b/types/formSchemas.ts index 3726245f68..c03afe2d95 100644 --- a/types/formSchemas.ts +++ b/types/formSchemas.ts @@ -1,5 +1,22 @@ import { z } from "zod"; +export const onDemandScanFormSchema = () => + z.object({ + providerId: z.string(), + scanName: z.string().optional(), + scannerArgs: z + .object({ + checksToExecute: z.array(z.string()), + }) + .optional(), + }); + +export const scheduleScanFormSchema = () => + z.object({ + providerId: z.string(), + scheduleDate: z.string(), + }); + export const addProviderFormSchema = z.object({ providerType: z.string(), providerAlias: z.string(), From 1fff7ef1d3aa8d5981806b09982d9bcc87c312e5 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Fri, 11 Oct 2024 16:18:15 +0200 Subject: [PATCH 14/20] 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(), From bfb591977e22246a3ccfadac8dae23e12930adc3 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Sat, 12 Oct 2024 07:43:27 +0200 Subject: [PATCH 15/20] feat: there is no DELETE method for scans for now --- .../table/scans/data-table-row-actions.tsx | 31 +++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/components/scans/table/scans/data-table-row-actions.tsx b/components/scans/table/scans/data-table-row-actions.tsx index 06d7ddc833..336ff37389 100644 --- a/components/scans/table/scans/data-table-row-actions.tsx +++ b/components/scans/table/scans/data-table-row-actions.tsx @@ -9,11 +9,11 @@ import { DropdownTrigger, } from "@nextui-org/react"; import { - DeleteDocumentBulkIcon, + // DeleteDocumentBulkIcon, EditDocumentBulkIcon, } from "@nextui-org/shared-icons"; import { Row } from "@tanstack/react-table"; -import clsx from "clsx"; +// import clsx from "clsx"; import { useState } from "react"; import { VerticalDotsIcon } from "@/components/icons"; @@ -21,19 +21,17 @@ import { CustomAlertModal } from "@/components/ui/custom"; import { EditScanForm } from "../../forms"; -// import { DeleteForm } from "../forms/delete-form"; - -interface DataTableRowActionsProps { - row: Row; +interface DataTableRowActionsProps { + row: Row; } const iconClasses = "text-2xl text-default-500 pointer-events-none flex-shrink-0"; -export function DataTableRowActions({ +export function DataTableRowActions({ row, -}: DataTableRowActionsProps) { +}: DataTableRowActionsProps) { const [isEditOpen, setIsEditOpen] = useState(false); - const [isDeleteOpen, setIsDeleteOpen] = useState(false); + // const [isDeleteOpen, setIsDeleteOpen] = useState(false); const scanId = (row.original as { id: string }).id; const scanName = (row.original as any).attributes?.name; return ( @@ -50,15 +48,16 @@ export function DataTableRowActions({ setIsOpen={setIsEditOpen} />
- -

Hello

- {/* */} -
+ > */} + {/*

Hello

*/} + {/* */} + {/* */}
@@ -84,7 +83,7 @@ export function DataTableRowActions({ Edit Scan - + {/* ({ > Delete Scan - + */}
From 05854280294b121428159d60dbfc63f4dd230a50 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Sat, 12 Oct 2024 14:19:18 +0200 Subject: [PATCH 16/20] feat: getScan detail view first iteration --- actions/scans/scans.ts | 24 +++ .../scans/table/column-get-scans-schedule.tsx | 25 ++- components/scans/table/index.ts | 1 + components/scans/table/scan-detail.tsx | 67 ++++++ components/ui/sheet/index.ts | 2 + components/ui/sheet/sheet.tsx | 143 ++++++++++++ components/ui/sheet/trigger-sheet.tsx | 35 +++ .../ui/table/data-table-row-details.tsx | 39 ++++ components/ui/table/index.ts | 1 + package-lock.json | 204 ++++++++++++++++-- package.json | 4 +- 11 files changed, 518 insertions(+), 27 deletions(-) create mode 100644 components/scans/table/scan-detail.tsx create mode 100644 components/ui/sheet/index.ts create mode 100644 components/ui/sheet/sheet.tsx create mode 100644 components/ui/sheet/trigger-sheet.tsx create mode 100644 components/ui/table/data-table-row-details.tsx diff --git a/actions/scans/scans.ts b/actions/scans/scans.ts index ab83c81c27..be481fc2ea 100644 --- a/actions/scans/scans.ts +++ b/actions/scans/scans.ts @@ -48,6 +48,30 @@ export const getScans = async ({ } }; +export const getScan = async (scanId: string) => { + const session = await auth(); + + const keyServer = process.env.API_BASE_URL; + const url = new URL(`${keyServer}/scans/${scanId}`); + + try { + const scan = await fetch(url.toString(), { + headers: { + Accept: "application/vnd.api+json", + Authorization: `Bearer ${session?.accessToken}`, + }, + }); + const data = await scan.json(); + const parsedData = parseStringify(data); + + return parsedData; + } catch (error) { + return { + error: getErrorMessage(error), + }; + } +}; + export const scanOnDemand = async (formData: FormData) => { const session = await auth(); const keyServer = process.env.API_BASE_URL; diff --git a/components/scans/table/column-get-scans-schedule.tsx b/components/scans/table/column-get-scans-schedule.tsx index 2a2ff19996..768fc05341 100644 --- a/components/scans/table/column-get-scans-schedule.tsx +++ b/components/scans/table/column-get-scans-schedule.tsx @@ -2,8 +2,14 @@ import { ColumnDef } from "@tanstack/react-table"; +import { PlusIcon } from "@/components/icons"; import { DateWithTime, EntityInfoShort } from "@/components/ui/entities"; -import { DataTableColumnHeader, StatusBadge } from "@/components/ui/table"; +import { TriggerSheet } from "@/components/ui/sheet"; +import { + DataTableColumnHeader, + DataTableRowDetails, + StatusBadge, +} from "@/components/ui/table"; import { ScanProps } from "@/types"; import { DataTableRowActions } from "./scans/data-table-row-actions"; @@ -71,13 +77,18 @@ export const ColumnGetScansSchedule: ColumnDef[] = [ }, }, { - accessorKey: "resources", - header: "Resources", + id: "moreInfo", + header: "Details", cell: ({ row }) => { - const { - attributes: { unique_resource_count }, - } = getScanData(row); - return

{unique_resource_count}

; + return ( + } + title="Scan Details" + description="View the scan details" + > + + + ); }, }, diff --git a/components/scans/table/index.ts b/components/scans/table/index.ts index 25e8770e31..c11dfa5d4e 100644 --- a/components/scans/table/index.ts +++ b/components/scans/table/index.ts @@ -1,2 +1,3 @@ export * from "./column-get-scans-schedule"; +export * from "./scan-detail"; export * from "./skeleton-table-scans"; diff --git a/components/scans/table/scan-detail.tsx b/components/scans/table/scan-detail.tsx new file mode 100644 index 0000000000..d765a063f8 --- /dev/null +++ b/components/scans/table/scan-detail.tsx @@ -0,0 +1,67 @@ +"use client"; + +import { Card, CardBody, CardHeader, Chip, Divider } from "@nextui-org/react"; + +import { ScanProps } from "@/types"; + +export const ScanDetail = ({ scanDetails }: { scanDetails: ScanProps }) => { + return ( + + +

Scan Details

+ + {scanDetails.attributes.state} + +
+ + +
+ + + + + + + + + +
+
+
+ ); +}; + +const DetailItem = ({ label, value }: { label: string; value: string }) => ( +
+ {label}: + {value} +
+); diff --git a/components/ui/sheet/index.ts b/components/ui/sheet/index.ts new file mode 100644 index 0000000000..2865f1c5a7 --- /dev/null +++ b/components/ui/sheet/index.ts @@ -0,0 +1,2 @@ +export * from "./sheet"; +export * from "./trigger-sheet"; diff --git a/components/ui/sheet/sheet.tsx b/components/ui/sheet/sheet.tsx new file mode 100644 index 0000000000..01e91f0e95 --- /dev/null +++ b/components/ui/sheet/sheet.tsx @@ -0,0 +1,143 @@ +"use client"; + +import * as SheetPrimitive from "@radix-ui/react-dialog"; +import { Cross2Icon } from "@radix-ui/react-icons"; +import { cva, type VariantProps } from "class-variance-authority"; +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Sheet = SheetPrimitive.Root; + +const SheetTrigger = SheetPrimitive.Trigger; + +const SheetClose = SheetPrimitive.Close; + +const SheetPortal = SheetPrimitive.Portal; + +const SheetOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; + +const sheetVariants = cva( + "fixed z-50 gap-4 bg-white p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out dark:bg-neutral-950", + { + variants: { + side: { + top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", + bottom: + "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", + left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", + right: + "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", + }, + }, + defaultVariants: { + side: "right", + }, + }, +); + +interface SheetContentProps + extends React.ComponentPropsWithoutRef, + VariantProps {} + +const SheetContent = React.forwardRef< + React.ElementRef, + SheetContentProps +>(({ side = "right", className, children, ...props }, ref) => ( + + + + + + Close + + {children} + + +)); +SheetContent.displayName = SheetPrimitive.Content.displayName; + +const SheetHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +SheetHeader.displayName = "SheetHeader"; + +const SheetFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +SheetFooter.displayName = "SheetFooter"; + +const SheetTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SheetTitle.displayName = SheetPrimitive.Title.displayName; + +const SheetDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SheetDescription.displayName = SheetPrimitive.Description.displayName; + +export { + Sheet, + SheetClose, + SheetContent, + SheetDescription, + SheetFooter, + SheetHeader, + SheetOverlay, + SheetPortal, + SheetTitle, + SheetTrigger, +}; diff --git a/components/ui/sheet/trigger-sheet.tsx b/components/ui/sheet/trigger-sheet.tsx new file mode 100644 index 0000000000..dd0df60062 --- /dev/null +++ b/components/ui/sheet/trigger-sheet.tsx @@ -0,0 +1,35 @@ +import { + Sheet, + SheetContent, + SheetDescription, + SheetHeader, + SheetTitle, + SheetTrigger, +} from "./sheet"; + +interface TriggerSheetProps { + triggerComponent: React.ReactNode; + title: string; + description: string; + children: React.ReactNode; +} + +export function TriggerSheet({ + triggerComponent, + title, + description, + children, +}: TriggerSheetProps) { + return ( + + {triggerComponent} + + + {title} + {description} + + {children} + + + ); +} diff --git a/components/ui/table/data-table-row-details.tsx b/components/ui/table/data-table-row-details.tsx new file mode 100644 index 0000000000..73407250f7 --- /dev/null +++ b/components/ui/table/data-table-row-details.tsx @@ -0,0 +1,39 @@ +"use client"; + +import { useEffect, useState } from "react"; + +import { getScan } from "@/actions/scans"; +import { ScanDetail, SkeletonTableScans } from "@/components/scans/table"; +import { ScanProps } from "@/types"; + +export const DataTableRowDetails = ({ entityId }: { entityId: string }) => { + const [scanDetails, setScanDetails] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const fetchScanDetails = async () => { + try { + const result = await getScan(entityId); + setScanDetails(result?.data); + } catch (error) { + console.error("Error fetching scan details:", error); + } finally { + setIsLoading(false); + } + }; + + fetchScanDetails(); + }, [entityId]); + + if (isLoading) { + return ; + } + + if (!scanDetails) { + return
No scan details available
; + } + + return ; +}; + +// Remove the SSRDataRowDetails component as it's no longer needed diff --git a/components/ui/table/index.ts b/components/ui/table/index.ts index a344b959de..3f18621c91 100644 --- a/components/ui/table/index.ts +++ b/components/ui/table/index.ts @@ -2,6 +2,7 @@ export * from "./data-table"; export * from "./data-table-column-header"; export * from "./data-table-filter-custom"; export * from "./data-table-pagination"; +export * from "./data-table-row-details"; export * from "./severity-badge"; export * from "./status-badge"; export * from "./table"; diff --git a/package-lock.json b/package-lock.json index 1f8510886b..f060d1495a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@nextui-org/system": "2.2.1", "@nextui-org/theme": "2.2.5", "@radix-ui/react-alert-dialog": "^1.1.1", - "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.1.0", @@ -44,7 +44,7 @@ "recharts": "^2.13.0-alpha.4", "server-only": "^0.0.1", "shadcn-ui": "^0.2.3", - "tailwind-merge": "^2.5.2", + "tailwind-merge": "^2.5.3", "tailwindcss-animate": "^1.0.7", "uuid": "^10.0.0", "zod": "^3.23.8", @@ -2748,6 +2748,67 @@ } } }, + "node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-dialog": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz", + "integrity": "sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.0", + "@radix-ui/react-dismissable-layer": "1.1.0", + "@radix-ui/react-focus-guards": "1.1.0", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.1", + "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.7" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-alert-dialog/node_modules/react-remove-scroll": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz", + "integrity": "sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==", + "license": "MIT", + "dependencies": { + "react-remove-scroll-bar": "^2.3.4", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-arrow": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.0.tgz", @@ -2824,24 +2885,130 @@ } }, "node_modules/@radix-ui/react-dialog": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.1.tgz", - "integrity": "sha512-zysS+iU4YP3STKNS6USvFVqI4qqx8EpiwmT5TuCApVEBca+eRCbONi4EgzfNSuVnOXvC5UPHHMjs8RXO6DH9Bg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.2.tgz", + "integrity": "sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==", + "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.0", "@radix-ui/react-compose-refs": "1.1.0", - "@radix-ui/react-context": "1.1.0", - "@radix-ui/react-dismissable-layer": "1.1.0", - "@radix-ui/react-focus-guards": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-focus-guards": "1.1.1", "@radix-ui/react-focus-scope": "1.1.0", "@radix-ui/react-id": "1.1.0", - "@radix-ui/react-portal": "1.1.1", - "@radix-ui/react-presence": "1.1.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", "@radix-ui/react-primitive": "2.0.0", "@radix-ui/react-slot": "1.1.0", "@radix-ui/react-use-controllable-state": "1.1.0", "aria-hidden": "^1.1.1", - "react-remove-scroll": "2.5.7" + "react-remove-scroll": "2.6.0" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz", + "integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "1.1.0" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", + "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz", + "integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "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 + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-presence": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz", + "integrity": "sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-use-layout-effect": "1.1.0" }, "peerDependencies": { "@types/react": "*", @@ -2859,11 +3026,12 @@ } }, "node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.7.tgz", - "integrity": "sha512-FnrTWO4L7/Bhhf3CYBNArEG/yROV0tKmTv7/3h9QCFvH6sndeFf1wPqOcbFVu5VAulS5dV1wGT3GZZ/1GawqiA==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz", + "integrity": "sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==", + "license": "MIT", "dependencies": { - "react-remove-scroll-bar": "^2.3.4", + "react-remove-scroll-bar": "^2.3.6", "react-style-singleton": "^2.2.1", "tslib": "^2.1.0", "use-callback-ref": "^1.3.0", @@ -12211,9 +12379,9 @@ } }, "node_modules/tailwind-merge": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.2.tgz", - "integrity": "sha512-kjEBm+pvD+6eAwzJL2Bi+02/9LFLal1Gs61+QB7HvTfQQ0aXwC5LGT8PEt1gS0CWKktKe6ysPTAy3cBC5MeiIg==", + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.5.3.tgz", + "integrity": "sha512-d9ZolCAIzom1nf/5p4LdD5zvjmgSxY0BGgdSvmXIoMYAiPdAW/dSpP7joCDYFY7r/HkEa2qmPtkgsu0xjQeQtw==", "license": "MIT", "funding": { "type": "github", diff --git a/package.json b/package.json index 67ac210f66..2d9f0f0ba4 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "@nextui-org/system": "2.2.1", "@nextui-org/theme": "2.2.5", "@radix-ui/react-alert-dialog": "^1.1.1", - "@radix-ui/react-dialog": "^1.1.1", + "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-label": "^2.1.0", @@ -36,7 +36,7 @@ "recharts": "^2.13.0-alpha.4", "server-only": "^0.0.1", "shadcn-ui": "^0.2.3", - "tailwind-merge": "^2.5.2", + "tailwind-merge": "^2.5.3", "tailwindcss-animate": "^1.0.7", "uuid": "^10.0.0", "zod": "^3.23.8", From c0a9bd14aa7cb8b38ff3d88b85d621af4f22ae41 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Sun, 13 Oct 2024 08:56:03 +0200 Subject: [PATCH 17/20] feat: integrate Sheet component with ScanDetail view via getScan --- app/(prowler)/scans/page.tsx | 6 +- components/scans/table/index.ts | 1 - components/scans/table/scan-detail.tsx | 139 ++++++++++++------ .../column-get-scans-schedule.tsx | 9 +- .../data-table-row-details.tsx | 2 - .../scans/table/schedule-scans/index.ts | 2 + components/ui/sheet/sheet.tsx | 4 +- components/ui/sheet/trigger-sheet.tsx | 6 +- components/ui/table/index.ts | 1 - 9 files changed, 103 insertions(+), 67 deletions(-) rename components/scans/table/{ => schedule-scans}/column-get-scans-schedule.tsx (92%) rename components/{ui/table => scans/table/schedule-scans}/data-table-row-details.tsx (93%) create mode 100644 components/scans/table/schedule-scans/index.ts diff --git a/app/(prowler)/scans/page.tsx b/app/(prowler)/scans/page.tsx index 71ed291d70..fc914afd65 100644 --- a/app/(prowler)/scans/page.tsx +++ b/app/(prowler)/scans/page.tsx @@ -8,12 +8,10 @@ import { filterProviders, filterScans, } from "@/components/filters"; -import { - ColumnGetScansSchedule, - SkeletonTableScans, -} from "@/components/scans/table"; +import { SkeletonTableScans } from "@/components/scans/table"; import { ColumnProviderScans } from "@/components/scans/table/provider-scans"; import { ColumnGetScans } from "@/components/scans/table/scans"; +import { ColumnGetScansSchedule } from "@/components/scans/table/schedule-scans"; import { Header } from "@/components/ui"; import { DataTable } from "@/components/ui/table"; import { SearchParamsProps } from "@/types"; diff --git a/components/scans/table/index.ts b/components/scans/table/index.ts index c11dfa5d4e..0a8aee6de4 100644 --- a/components/scans/table/index.ts +++ b/components/scans/table/index.ts @@ -1,3 +1,2 @@ -export * from "./column-get-scans-schedule"; export * from "./scan-detail"; export * from "./skeleton-table-scans"; diff --git a/components/scans/table/scan-detail.tsx b/components/scans/table/scan-detail.tsx index d765a063f8..7124a11943 100644 --- a/components/scans/table/scan-detail.tsx +++ b/components/scans/table/scan-detail.tsx @@ -6,62 +6,105 @@ import { ScanProps } from "@/types"; export const ScanDetail = ({ scanDetails }: { scanDetails: ScanProps }) => { return ( - - -

Scan Details

- - {scanDetails.attributes.state} - -
- - -
- - - - - - - - +
+ + +

Scan Details

+ + {scanDetails.attributes.state} + +
+ + + + +
+
+ + + + + + +
+
+ + + + + +
+
+
+
+ + + +

Scan Arguments

+
+ + + + -
- - + + +
); }; const DetailItem = ({ label, value }: { label: string; value: string }) => ( -
- {label}: - {value} +
+ {label}: + {value}
); diff --git a/components/scans/table/column-get-scans-schedule.tsx b/components/scans/table/schedule-scans/column-get-scans-schedule.tsx similarity index 92% rename from components/scans/table/column-get-scans-schedule.tsx rename to components/scans/table/schedule-scans/column-get-scans-schedule.tsx index 768fc05341..583a9cdd16 100644 --- a/components/scans/table/column-get-scans-schedule.tsx +++ b/components/scans/table/schedule-scans/column-get-scans-schedule.tsx @@ -5,14 +5,11 @@ import { ColumnDef } from "@tanstack/react-table"; import { PlusIcon } from "@/components/icons"; import { DateWithTime, EntityInfoShort } from "@/components/ui/entities"; import { TriggerSheet } from "@/components/ui/sheet"; -import { - DataTableColumnHeader, - DataTableRowDetails, - StatusBadge, -} from "@/components/ui/table"; +import { DataTableColumnHeader, StatusBadge } from "@/components/ui/table"; import { ScanProps } from "@/types"; -import { DataTableRowActions } from "./scans/data-table-row-actions"; +import { DataTableRowActions } from "../scans/data-table-row-actions"; +import { DataTableRowDetails } from "."; const getScanData = (row: { original: ScanProps }) => { return row.original; diff --git a/components/ui/table/data-table-row-details.tsx b/components/scans/table/schedule-scans/data-table-row-details.tsx similarity index 93% rename from components/ui/table/data-table-row-details.tsx rename to components/scans/table/schedule-scans/data-table-row-details.tsx index 73407250f7..997fa0e5d9 100644 --- a/components/ui/table/data-table-row-details.tsx +++ b/components/scans/table/schedule-scans/data-table-row-details.tsx @@ -35,5 +35,3 @@ export const DataTableRowDetails = ({ entityId }: { entityId: string }) => { return ; }; - -// Remove the SSRDataRowDetails component as it's no longer needed diff --git a/components/scans/table/schedule-scans/index.ts b/components/scans/table/schedule-scans/index.ts new file mode 100644 index 0000000000..e93f00f4c5 --- /dev/null +++ b/components/scans/table/schedule-scans/index.ts @@ -0,0 +1,2 @@ +export * from "./column-get-scans-schedule"; +export * from "./data-table-row-details"; diff --git a/components/ui/sheet/sheet.tsx b/components/ui/sheet/sheet.tsx index 01e91f0e95..cf945c0f9d 100644 --- a/components/ui/sheet/sheet.tsx +++ b/components/ui/sheet/sheet.tsx @@ -38,9 +38,9 @@ const sheetVariants = cva( top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", bottom: "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", - left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", + left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left", right: - "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", + "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right", }, }, defaultVariants: { diff --git a/components/ui/sheet/trigger-sheet.tsx b/components/ui/sheet/trigger-sheet.tsx index dd0df60062..af8a2f988d 100644 --- a/components/ui/sheet/trigger-sheet.tsx +++ b/components/ui/sheet/trigger-sheet.tsx @@ -23,10 +23,10 @@ export function TriggerSheet({ return ( {triggerComponent} - + - {title} - {description} + {title} + {description} {children} diff --git a/components/ui/table/index.ts b/components/ui/table/index.ts index 3f18621c91..a344b959de 100644 --- a/components/ui/table/index.ts +++ b/components/ui/table/index.ts @@ -2,7 +2,6 @@ export * from "./data-table"; export * from "./data-table-column-header"; export * from "./data-table-filter-custom"; export * from "./data-table-pagination"; -export * from "./data-table-row-details"; export * from "./severity-badge"; export * from "./status-badge"; export * from "./table"; From da9429351f2b31bf7b4b212a22e4bfebd6d720d8 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 14 Oct 2024 17:28:22 +0200 Subject: [PATCH 18/20] chore: replace disable for isDisable prop --- app/(prowler)/scans/page.tsx | 24 ++++++++----------- components/auth/oss/auth-form.tsx | 2 +- components/providers/forms/add-form.tsx | 2 +- components/providers/forms/delete-form.tsx | 2 +- components/providers/forms/edit-form.tsx | 2 +- components/scans/forms/edit-scan-form.tsx | 2 +- .../scans/forms/scan-on-demand-form.tsx | 4 ++-- components/scans/forms/schedule-form.tsx | 3 ++- components/ui/custom/custom-button.tsx | 9 +++---- 9 files changed, 24 insertions(+), 26 deletions(-) diff --git a/app/(prowler)/scans/page.tsx b/app/(prowler)/scans/page.tsx index fc914afd65..d696bdc9a0 100644 --- a/app/(prowler)/scans/page.tsx +++ b/app/(prowler)/scans/page.tsx @@ -34,10 +34,10 @@ export default async function Scans({
}> - +
-
+
}> @@ -52,23 +52,19 @@ export default async function Scans({ ); } -const SSRDataTableProviders = async ({ - searchParams, -}: { - searchParams: SearchParamsProps; -}) => { - const page = parseInt(searchParams.page?.toString() || "1", 10); - const sort = searchParams.sort?.toString(); +const SSRDataTableProviders = async () => { + // const page = parseInt(searchParams.page?.toString() || "1", 10); + // const sort = searchParams.sort?.toString(); // Extract all filter parameters - const filters = Object.fromEntries( - Object.entries(searchParams).filter(([key]) => key.startsWith("filter[")), - ); + // const filters = Object.fromEntries( + // Object.entries(searchParams).filter(([key]) => key.startsWith("filter[")), + // ); // Extract query from filters - const query = (filters["filter[search]"] as string) || ""; + // const query = (filters["filter[search]"] as string) || ""; - const providersData = await getProviders({ query, page, sort, filters }); + const providersData = await getProviders({ page: 1 }); return ( { size="md" radius="md" isLoading={isLoading} - disabled={isLoading} + isDisabled={isLoading} > {isLoading ? ( Loading diff --git a/components/providers/forms/add-form.tsx b/components/providers/forms/add-form.tsx index c721a709c0..3529995cfa 100644 --- a/components/providers/forms/add-form.tsx +++ b/components/providers/forms/add-form.tsx @@ -103,7 +103,7 @@ export const AddForm = ({ size="lg" radius="lg" onPress={() => setIsOpen(false)} - disabled={isLoading} + isDisabled={isLoading} > Cancel diff --git a/components/providers/forms/delete-form.tsx b/components/providers/forms/delete-form.tsx index e08ac4ada5..2f80b3294b 100644 --- a/components/providers/forms/delete-form.tsx +++ b/components/providers/forms/delete-form.tsx @@ -63,7 +63,7 @@ export const DeleteForm = ({ size="lg" radius="lg" onPress={() => setIsOpen(false)} - disabled={isLoading} + isDisabled={isLoading} > Cancel diff --git a/components/providers/forms/edit-form.tsx b/components/providers/forms/edit-form.tsx index 7991cd9a5b..fba187a22f 100644 --- a/components/providers/forms/edit-form.tsx +++ b/components/providers/forms/edit-form.tsx @@ -95,7 +95,7 @@ export const EditForm = ({ size="lg" radius="lg" onPress={() => setIsOpen(false)} - disabled={isLoading} + isDisabled={isLoading} > Cancel diff --git a/components/scans/forms/edit-scan-form.tsx b/components/scans/forms/edit-scan-form.tsx index 54e3c17701..62c6e338b6 100644 --- a/components/scans/forms/edit-scan-form.tsx +++ b/components/scans/forms/edit-scan-form.tsx @@ -95,7 +95,7 @@ export const EditScanForm = ({ size="lg" radius="lg" onPress={() => setIsOpen(false)} - disabled={isLoading} + isDisabled={isLoading} > Cancel diff --git a/components/scans/forms/scan-on-demand-form.tsx b/components/scans/forms/scan-on-demand-form.tsx index 49ce39f235..a805e55ac5 100644 --- a/components/scans/forms/scan-on-demand-form.tsx +++ b/components/scans/forms/scan-on-demand-form.tsx @@ -102,14 +102,14 @@ export const ScanOnDemandForm = ({ size="lg" radius="lg" onPress={() => setIsOpen(false)} - disabled={isLoading} + isDisabled={isLoading} > Cancel setIsOpen(false)} - disabled={isLoading} + isDisabled={isLoading} > Cancel @@ -102,6 +102,7 @@ export const ScheduleForm = ({ size="lg" isLoading={isLoading} startContent={!isLoading && } + isDisabled={true} > {isLoading ? <>Loading : Schedule} diff --git a/components/ui/custom/custom-button.tsx b/components/ui/custom/custom-button.tsx index f5e408173c..b2a6cf355a 100644 --- a/components/ui/custom/custom-button.tsx +++ b/components/ui/custom/custom-button.tsx @@ -46,7 +46,7 @@ interface CustomButtonProps { size?: "sm" | "md" | "lg"; radius?: "none" | "sm" | "md" | "lg" | "full"; dashed?: boolean; - disabled?: boolean; + isDisabled?: boolean; isLoading?: boolean; isIconOnly?: boolean; ref?: React.RefObject; @@ -70,7 +70,7 @@ export const CustomButton = React.forwardRef< endContent, size = "md", radius = "sm", - disabled = false, + isDisabled = false, isLoading = false, isIconOnly, ...props @@ -92,8 +92,8 @@ export const CustomButton = React.forwardRef< [buttonClasses.action]: color === "action", [buttonClasses.dashed]: variant === "dashed", [buttonClasses.transparent]: color === "transparent", - [buttonClasses.disabled]: disabled, - [buttonClasses.hover]: color !== "transparent" && !disabled, + [buttonClasses.disabled]: isDisabled, + [buttonClasses.hover]: color !== "transparent" && !isDisabled, }, className, )} @@ -112,6 +112,7 @@ export const CustomButton = React.forwardRef< /> } ref={ref} + isDisabled={isDisabled} isLoading={isLoading} isIconOnly={isIconOnly} {...props} From d0b54d1950d43e2d4eb3c2d2978901ed86f7815d Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 14 Oct 2024 17:30:32 +0200 Subject: [PATCH 19/20] feat: detail view for scans has been implemented --- .../providers/table/column-providers.tsx | 30 +-- components/scans/table/scan-detail.tsx | 184 ++++++++++-------- .../table/scans/data-table-row-actions.tsx | 28 --- .../column-get-scans-schedule.tsx | 27 +-- components/ui/sheet/trigger-sheet.tsx | 2 +- components/ui/table/status-badge.tsx | 12 +- 6 files changed, 129 insertions(+), 154 deletions(-) diff --git a/components/providers/table/column-providers.tsx b/components/providers/table/column-providers.tsx index eb67f731d2..1680fd8000 100644 --- a/components/providers/table/column-providers.tsx +++ b/components/providers/table/column-providers.tsx @@ -1,7 +1,6 @@ "use client"; import { ColumnDef } from "@tanstack/react-table"; -import { add } from "date-fns"; import { DateWithTime, SnippetId } from "@/components/ui/entities"; import { DataTableColumnHeader, StatusBadge } from "@/components/ui/table"; @@ -15,10 +14,10 @@ const getProviderData = (row: { original: ProviderProps }) => { }; export const ColumnProviders: ColumnDef[] = [ - { - header: " ", - cell: ({ row }) =>

{row.index + 1}

, - }, + // { + // header: " ", + // cell: ({ row }) =>

{row.index + 1}

, + // }, { accessorKey: "account", header: ({ column }) => ( @@ -73,27 +72,6 @@ export const ColumnProviders: ColumnDef[] = [ return ; }, }, - { - accessorKey: "nextScan", - header: "Next Scan", - cell: ({ row }) => { - const { - attributes: { updated_at }, - } = getProviderData(row); - const nextDay = add(new Date(updated_at), { - hours: 24, - }); - return ; - }, - }, - { - accessorKey: "resources", - header: "Resources", - cell: () => { - // Temporarily overwriting the value until the API is functional. - return

{288}

; - }, - }, { accessorKey: "added", header: ({ column }) => ( diff --git a/components/scans/table/scan-detail.tsx b/components/scans/table/scan-detail.tsx index 7124a11943..f9c14384ee 100644 --- a/components/scans/table/scan-detail.tsx +++ b/components/scans/table/scan-detail.tsx @@ -1,85 +1,98 @@ "use client"; -import { Card, CardBody, CardHeader, Chip, Divider } from "@nextui-org/react"; +import { Card, CardBody, CardHeader, Divider } from "@nextui-org/react"; +import { DateWithTime, SnippetId } from "@/components/ui/entities"; +import { StatusBadge } from "@/components/ui/table/status-badge"; import { ScanProps } from "@/types"; export const ScanDetail = ({ scanDetails }: { scanDetails: ScanProps }) => { + const scanOnDemand = scanDetails.attributes; return (
- - -

Scan Details

- - {scanDetails.attributes.state} - -
+
+
+

Scan Details -

+

{scanOnDemand.name}

+
- - - -
-
- - - - - - -
-
- - - - - -
+ +
+ +
+
+
+ } + /> + + + +
- - - +
+ + ) : ( + "Not Started" + ) + } + /> + + ) : ( + "Not Started" + ) + } + /> + + ) : ( + "Not Scheduled" + ) + } + /> + + } + /> + + } + /> +
+
+

Scan Arguments

@@ -91,9 +104,9 @@ export const ScanDetail = ({ scanDetails }: { scanDetails: ScanProps }) => {
@@ -102,7 +115,26 @@ export const ScanDetail = ({ scanDetails }: { scanDetails: ScanProps }) => { ); }; -const DetailItem = ({ label, value }: { label: string; value: string }) => ( +const DateItem = ({ + label, + value, +}: { + label: string; + value: React.ReactNode; +}) => ( +
+ {label}: + {value} +
+); + +const DetailItem = ({ + label, + value, +}: { + label: string; + value: React.ReactNode; +}) => (
{label}: {value} diff --git a/components/scans/table/scans/data-table-row-actions.tsx b/components/scans/table/scans/data-table-row-actions.tsx index 336ff37389..195851b68c 100644 --- a/components/scans/table/scans/data-table-row-actions.tsx +++ b/components/scans/table/scans/data-table-row-actions.tsx @@ -31,7 +31,6 @@ export function DataTableRowActions({ row, }: DataTableRowActionsProps) { const [isEditOpen, setIsEditOpen] = useState(false); - // const [isDeleteOpen, setIsDeleteOpen] = useState(false); const scanId = (row.original as { id: string }).id; const scanName = (row.original as any).attributes?.name; return ( @@ -49,16 +48,6 @@ export function DataTableRowActions({ /> - {/* */} - {/*

Hello

*/} - {/* */} - {/*
*/} -
@@ -83,23 +72,6 @@ export function DataTableRowActions({ Edit Scan - {/* - - } - onClick={() => setIsDeleteOpen(true)} - > - Delete Scan - - */}
diff --git a/components/scans/table/schedule-scans/column-get-scans-schedule.tsx b/components/scans/table/schedule-scans/column-get-scans-schedule.tsx index 583a9cdd16..69bb478ecc 100644 --- a/components/scans/table/schedule-scans/column-get-scans-schedule.tsx +++ b/components/scans/table/schedule-scans/column-get-scans-schedule.tsx @@ -42,37 +42,22 @@ export const ColumnGetScansSchedule: ColumnDef[] = [ }, }, { - accessorKey: "started_at", + accessorKey: "scheduled_at", header: ({ column }) => ( ), cell: ({ row }) => { const { - attributes: { started_at }, + attributes: { scheduled_at }, } = getScanData(row); - return ; - }, - }, - { - accessorKey: "lastScan", - header: ({ column }) => ( - - ), - cell: ({ row }) => { - const { - attributes: { completed_at }, - } = getScanData(row); - return ; + return ; }, }, + { id: "moreInfo", header: "Details", diff --git a/components/ui/sheet/trigger-sheet.tsx b/components/ui/sheet/trigger-sheet.tsx index af8a2f988d..e97e2a45a5 100644 --- a/components/ui/sheet/trigger-sheet.tsx +++ b/components/ui/sheet/trigger-sheet.tsx @@ -23,7 +23,7 @@ export function TriggerSheet({ return ( {triggerComponent} - + {title} {description} diff --git a/components/ui/table/status-badge.tsx b/components/ui/table/status-badge.tsx index ca8b3e2a49..c0f0356407 100644 --- a/components/ui/table/status-badge.tsx +++ b/components/ui/table/status-badge.tsx @@ -21,15 +21,23 @@ const statusColorMap: Record< cancelled: "danger", }; -export const StatusBadge = ({ status }: { status: Status }) => { +export const StatusBadge = ({ + status, + size = "sm", + ...props +}: { + status: Status; + size?: "sm" | "md" | "lg"; +}) => { const color = statusColorMap[status as keyof typeof statusColorMap]; return ( {status} From 7f73e2601612ad2c700adacda28c3d917b209485 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Mon, 14 Oct 2024 17:42:30 +0200 Subject: [PATCH 20/20] fix: showing the error for company name when try to sign up --- components/auth/oss/auth-form.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/components/auth/oss/auth-form.tsx b/components/auth/oss/auth-form.tsx index 8c66f108a5..e8c997f6a9 100644 --- a/components/auth/oss/auth-form.tsx +++ b/components/auth/oss/auth-form.tsx @@ -71,7 +71,6 @@ export const AuthForm = ({ type }: { type: string }) => { if (newUser?.errors && newUser.errors.length > 0) { newUser.errors.forEach((error: ApiError) => { const errorMessage = error.detail; - switch (error.source.pointer) { case "/data/attributes/name": form.setError("name", { type: "server", message: errorMessage }); @@ -79,6 +78,12 @@ export const AuthForm = ({ type }: { type: string }) => { case "/data/attributes/email": form.setError("email", { type: "server", message: errorMessage }); break; + case "/data/attributes/company_name": + form.setError("company", { + type: "server", + message: errorMessage, + }); + break; case "/data/attributes/password": form.setError("password", { type: "server",