From 3c2b0a58a1320083e2a10ddfee20018c03b0bbc5 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Sun, 1 Dec 2024 15:28:11 +0100 Subject: [PATCH] feat(v5): tweaks UI for v5 release - 2 (#5979) --- ui/actions/findings/findings.ts | 1 + ui/app/(prowler)/findings/page.tsx | 29 ++-- ui/app/(prowler)/page.tsx | 1 + ui/app/(prowler)/scans/page.tsx | 140 ++++++++++++------ ui/components/filters/data-filters.ts | 12 +- .../findings/table/column-findings.tsx | 18 ++- .../findings-by-severity-chart.tsx | 2 +- .../findings-by-status-chart.tsx | 2 +- .../table/column-new-findings-to-date.tsx | 18 ++- .../provider-overview/provider-overview.tsx | 10 +- .../table/data-table-row-actions.tsx | 5 +- ui/components/scans/index.ts | 2 + .../launch-scan-workflow-form.tsx | 128 +++++++--------- .../launch-workflow/select-scan-provider.tsx | 6 +- ui/components/scans/no-providers-added.tsx | 30 ++++ .../scans/no-providers-connected.tsx | 31 ++++ .../scans/table/scans/column-get-scans.tsx | 88 ++++++----- ui/components/ui/entities/date-with-time.tsx | 6 +- .../ui/entities/entity-info-short.tsx | 11 +- ui/components/ui/entities/snippet-id.tsx | 4 +- ui/components/ui/sidebar/sidebar-wrap.tsx | 4 +- .../ui/table/data-table-column-header.tsx | 14 +- ui/components/ui/table/status-badge.tsx | 2 +- ui/components/ui/table/table.tsx | 4 +- ui/types/components.ts | 6 + 25 files changed, 354 insertions(+), 220 deletions(-) create mode 100644 ui/components/scans/no-providers-added.tsx create mode 100644 ui/components/scans/no-providers-connected.tsx diff --git a/ui/actions/findings/findings.ts b/ui/actions/findings/findings.ts index 98aee3456b..3b035f84ab 100644 --- a/ui/actions/findings/findings.ts +++ b/ui/actions/findings/findings.ts @@ -45,6 +45,7 @@ export const getFindings = async ({ revalidatePath("/findings"); return parsedData; } catch (error) { + // eslint-disable-next-line no-console console.error("Error fetching findings:", error); return undefined; } diff --git a/ui/app/(prowler)/findings/page.tsx b/ui/app/(prowler)/findings/page.tsx index dc1161f531..52801e7259 100644 --- a/ui/app/(prowler)/findings/page.tsx +++ b/ui/app/(prowler)/findings/page.tsx @@ -13,7 +13,12 @@ import { import { Header } from "@/components/ui"; import { DataTable, DataTableFilterCustom } from "@/components/ui/table"; import { createDict } from "@/lib"; -import { FindingProps, SearchParamsProps } from "@/types/components"; +import { + FindingProps, + ProviderProps, + ScanProps, + SearchParamsProps, +} from "@/types/components"; export default async function Findings({ searchParams, @@ -56,24 +61,28 @@ export default async function Findings({ const scansData = await getScans({}); // Extract provider UIDs - const providerUIDs = providersData?.data - ?.map((provider: any) => provider.attributes.uid) - .filter(Boolean); + const providerUIDs = Array.from( + new Set( + providersData?.data + ?.map((provider: ProviderProps) => provider.attributes.uid) + .filter(Boolean), + ), + ); // Extract scan UUIDs with "completed" state and more than one resource const completedScans = scansData?.data ?.filter( (scan: any) => scan.attributes.state === "completed" && - scan.attributes.unique_resource_count > 1 && - scan.attributes.name, // Ensure it has a name + scan.attributes.unique_resource_count > 1, ) - .map((scan: any) => ({ + .map((scan: ScanProps) => ({ id: scan.id, name: scan.attributes.name, })); - const completedScanIds = completedScans?.map((scan: any) => scan.id) || []; + const completedScanIds = + completedScans?.map((scan: ScanProps) => scan.id) || []; return ( <> @@ -97,12 +106,12 @@ export default async function Findings({ }, { key: "provider_uid__in", - labelCheckboxGroup: "Account", + labelCheckboxGroup: "Provider UID", values: providerUIDs, }, { key: "scan__in", - labelCheckboxGroup: "Scans", + labelCheckboxGroup: "Scan ID", values: completedScanIds, }, ]} diff --git a/ui/app/(prowler)/page.tsx b/ui/app/(prowler)/page.tsx index 730b4e3499..ff2ab3d28c 100644 --- a/ui/app/(prowler)/page.tsx +++ b/ui/app/(prowler)/page.tsx @@ -56,6 +56,7 @@ export default function Home({
+ } diff --git a/ui/app/(prowler)/scans/page.tsx b/ui/app/(prowler)/scans/page.tsx index f8e7b8e067..8af2caf594 100644 --- a/ui/app/(prowler)/scans/page.tsx +++ b/ui/app/(prowler)/scans/page.tsx @@ -1,10 +1,14 @@ import { Spacer } from "@nextui-org/react"; import { Suspense } from "react"; -import { getProviders } from "@/actions/providers"; +import { getProvider, getProviders } from "@/actions/providers"; import { getScans } from "@/actions/scans"; -import { FilterControls, filterScans } from "@/components/filters"; -import { ButtonRefreshData } from "@/components/scans"; +import { filterScans } from "@/components/filters"; +import { + ButtonRefreshData, + NoProvidersAdded, + NoProvidersConnected, +} from "@/components/scans"; import { LaunchScanWorkflow } from "@/components/scans/launch-workflow"; import { SkeletonTableScans } from "@/components/scans/table"; import { ColumnGetScans } from "@/components/scans/table/scans"; @@ -21,48 +25,72 @@ export default async function Scans({ delete filteredParams.scanId; const searchParamsKey = JSON.stringify(filteredParams); - const providersData = await getProviders({}); + const providersData = await getProviders({ + filters: { + "filter[connected]": true, + }, + }); - const providerInfo = providersData?.data?.length - ? providersData.data.map((provider: ProviderProps) => ({ - providerId: provider.id, - alias: provider.attributes.alias, - providerType: provider.attributes.provider, - uid: provider.attributes.uid, - connected: provider.attributes.connection.connected, - })) - : []; + const providerInfo = providersData.data.map((provider: ProviderProps) => ({ + providerId: provider.id, + alias: provider.attributes.alias, + providerType: provider.attributes.provider, + uid: provider.attributes.uid, + connected: provider.attributes.connection.connected, + })); - // const executingScans = await getExecutingScans(); + const providersCountConnected = await getProviders({}); + const thereIsNoProviders = + !providersCountConnected?.data || providersCountConnected.data.length === 0; + + const thereIsNoProvidersConnected = providersCountConnected?.data?.every( + (provider: ProviderProps) => !provider.attributes.connection.connected, + ); return ( <>
- - - - - -
- - { - "use server"; - await getScans({}); - }} - /> -
+ {thereIsNoProviders && ( + <> + + + + )} - - -
-
- }> - - -
-
+ {!thereIsNoProviders && ( + <> + {thereIsNoProvidersConnected ? ( + <> + + + + ) : ( + <> + + + + )} + +
+
+
+ + { + "use server"; + await getScans({}); + }} + /> +
+ + }> + + +
+
+ + )} ); } @@ -85,22 +113,40 @@ const SSRDataTableScans = async ({ // Extract query from filters const query = (filters["filter[search]"] as string) || ""; + // Fetch scans data const scansData = await getScans({ query, page, sort, filters }); + // Handle expanded scans data + const expandedScansData = await Promise.all( + scansData?.data?.map(async (scan: any) => { + const providerId = scan.relationships?.provider?.data?.id; + + if (!providerId) { + return { ...scan, providerInfo: null }; + } + + const formData = new FormData(); + formData.append("id", providerId); + + const providerData = await getProvider(formData); + + if (providerData?.data) { + const { provider, uid, alias } = providerData.data.attributes; + return { + ...scan, + providerInfo: { provider, uid, alias }, + }; + } + + return { ...scan, providerInfo: null }; + }) || [], + ); + return ( ); }; - -// const getExecutingScans = async () => { -// const scansData = await getScans({}); - -// return scansData?.data?.some( -// (scan: ScanProps) => -// scan.attributes.state === "executing" && scan.attributes.progress < 100, -// ); -// }; diff --git a/ui/components/filters/data-filters.ts b/ui/components/filters/data-filters.ts index 938972fc38..9400dc427b 100644 --- a/ui/components/filters/data-filters.ts +++ b/ui/components/filters/data-filters.ts @@ -8,11 +8,11 @@ export const filterProviders = [ ]; export const filterScans = [ - // { - // key: "provider_type__in", - // labelCheckboxGroup: "Provider", - // values: ["aws", "azure", "gcp", "kubernetes"], - // }, + { + key: "provider_type__in", + labelCheckboxGroup: "Cloud Provider", + values: ["aws", "azure", "gcp", "kubernetes"], + }, { key: "state", labelCheckboxGroup: "State", @@ -51,7 +51,7 @@ export const filterFindings = [ }, { key: "provider_type__in", - labelCheckboxGroup: "Provider", + labelCheckboxGroup: "Cloud Provider", values: ["aws", "azure", "gcp", "kubernetes"], }, // Add more filter categories as needed diff --git a/ui/components/findings/table/column-findings.tsx b/ui/components/findings/table/column-findings.tsx index 5ee758027e..e43cb6ae3e 100644 --- a/ui/components/findings/table/column-findings.tsx +++ b/ui/components/findings/table/column-findings.tsx @@ -5,7 +5,7 @@ import { useSearchParams } from "next/navigation"; import { DataTableRowDetails } from "@/components/findings/table"; import { InfoIcon } from "@/components/icons"; -import { DateWithTime } from "@/components/ui/entities"; +import { DateWithTime, EntityInfoShort } from "@/components/ui/entities"; import { TriggerSheet } from "@/components/ui/sheet"; import { DataTableColumnHeader, @@ -181,16 +181,20 @@ export const ColumnFindings: ColumnDef[] = [ }, }, { - accessorKey: "account", - header: "Account", + accessorKey: "cloudProvider", + header: "Cloud provider", cell: ({ row }) => { - const account = getProviderData(row, "uid"); + const provider = getProviderData(row, "provider"); + const alias = getProviderData(row, "alias"); + const uid = getProviderData(row, "uid"); return ( <> -

- {typeof account === "string" ? account : "Invalid account"} -

+ ); }, diff --git a/ui/components/overview/findings-by-severity-chart/findings-by-severity-chart.tsx b/ui/components/overview/findings-by-severity-chart/findings-by-severity-chart.tsx index 7e590fb0d1..66c4b6d081 100644 --- a/ui/components/overview/findings-by-severity-chart/findings-by-severity-chart.tsx +++ b/ui/components/overview/findings-by-severity-chart/findings-by-severity-chart.tsx @@ -56,7 +56,7 @@ export const FindingsBySeverityChart = ({ })); return ( - +
diff --git a/ui/components/overview/findings-by-status-chart/findings-by-status-chart.tsx b/ui/components/overview/findings-by-status-chart/findings-by-status-chart.tsx index 3bf7d162c1..571cb37420 100644 --- a/ui/components/overview/findings-by-status-chart/findings-by-status-chart.tsx +++ b/ui/components/overview/findings-by-status-chart/findings-by-status-chart.tsx @@ -91,7 +91,7 @@ export const FindingsByStatusChart: React.FC = ({ ]; return ( - +
[] = [ }, }, { - accessorKey: "account", - header: "Account", + accessorKey: "cloudProvider", + header: "Cloud provider", cell: ({ row }) => { - const account = getProviderData(row, "uid"); + const provider = getProviderData(row, "provider"); + const alias = getProviderData(row, "alias"); + const uid = getProviderData(row, "uid"); return ( <> -

- {typeof account === "string" ? account : "Invalid account"} -

+ ); }, diff --git a/ui/components/overview/provider-overview/provider-overview.tsx b/ui/components/overview/provider-overview/provider-overview.tsx index a64d42c48b..48333c5fbc 100644 --- a/ui/components/overview/provider-overview/provider-overview.tsx +++ b/ui/components/overview/provider-overview/provider-overview.tsx @@ -44,9 +44,9 @@ export const ProvidersOverview = ({ if (!providersOverview || !Array.isArray(providersOverview.data)) { return ( - + -
+
Provider @@ -92,9 +92,9 @@ export const ProvidersOverview = ({ } return ( - + -
+
Provider @@ -175,7 +175,7 @@ export const ProvidersOverview = ({
({ ({ startContent={} onClick={() => setIsEditOpen(true)} > - Edit Provider + Edit Provider Alias diff --git a/ui/components/scans/index.ts b/ui/components/scans/index.ts index ede26bb35e..aa42edfe3c 100644 --- a/ui/components/scans/index.ts +++ b/ui/components/scans/index.ts @@ -1,2 +1,4 @@ export * from "./button-refresh-data"; export * from "./link-to-findings-from-scan"; +export * from "./no-providers-added"; +export * from "./no-providers-connected"; diff --git a/ui/components/scans/launch-workflow/launch-scan-workflow-form.tsx b/ui/components/scans/launch-workflow/launch-scan-workflow-form.tsx index a19acfea42..20088cad93 100644 --- a/ui/components/scans/launch-workflow/launch-scan-workflow-form.tsx +++ b/ui/components/scans/launch-workflow/launch-scan-workflow-form.tsx @@ -5,7 +5,7 @@ import { useForm } from "react-hook-form"; import * as z from "zod"; import { scanOnDemand } from "@/actions/scans"; -import { RocketIcon, ScheduleIcon } from "@/components/icons"; +import { RocketIcon } from "@/components/icons"; import { CustomButton, CustomInput } from "@/components/ui/custom"; import { Form } from "@/components/ui/form"; import { toast } from "@/components/ui/toast"; @@ -79,26 +79,26 @@ export const LaunchScanWorkflow = ({ onSubmit={form.handleSubmit(onSubmitClient)} className="flex flex-col space-y-4" > -
-
-
-

- Launch Scan -

- -
- - - {form.watch("providerId") && ( +
+
+

+ Launch Scan +

+ +
+ + {form.watch("providerId") && ( + <> - )} - -
- + +
+ } + > + {isLoading ? <>Loading : Start now} + + form.reset()} + className="w-fit border-gray-200 bg-transparent" + ariaLabel="Clear form" + variant="bordered" + size="md" + radius="lg" + > + Cancel + +
+
+ + )} +
+ {/*
{form.watch("providerId") && ( @@ -139,54 +170,7 @@ export const LaunchScanWorkflow = ({ )} -
- - - {form.watch("providerId") && ( - - form.reset()} - className="w-fit border-gray-200 bg-transparent" - ariaLabel="Clear form" - variant="bordered" - size="lg" - radius="lg" - > - Cancel - - } - isDisabled={true} - > - {isLoading ? <>Loading : Schedule} - - - } - > - {isLoading ? <>Loading : Start now} - - - )} - +
*/}
diff --git a/ui/components/scans/launch-workflow/select-scan-provider.tsx b/ui/components/scans/launch-workflow/select-scan-provider.tsx index c1805f488b..781656b59b 100644 --- a/ui/components/scans/launch-workflow/select-scan-provider.tsx +++ b/ui/components/scans/launch-workflow/select-scan-provider.tsx @@ -55,8 +55,8 @@ export const SelectScanProvider = < <>