From 34b5f483d7606207c3b21144e39b21af7cbfcb13 Mon Sep 17 00:00:00 2001 From: Pablo Lara Date: Thu, 23 Jan 2025 13:20:41 +0100 Subject: [PATCH] chore(scans): improve scan details (#6665) --- .../providers/(set-up-provider)/layout.tsx | 2 +- ui/app/(prowler)/providers/page.tsx | 4 +- ui/app/(prowler)/scans/page.tsx | 9 +- .../providers/add-provider-button.tsx | 4 +- .../workflow/forms/test-connection-form.tsx | 52 ++- .../workflow/workflow-add-provider.tsx | 12 +- ui/components/scans/no-providers-added.tsx | 51 ++- .../scans/no-providers-connected.tsx | 38 +- ui/components/scans/table/scan-detail.tsx | 327 ++++++++---------- .../table/scans/data-table-row-details.tsx | 26 +- .../ui/custom/custom-alert-modal.tsx | 4 +- ui/components/ui/entities/info-field.tsx | 8 +- 12 files changed, 268 insertions(+), 269 deletions(-) diff --git a/ui/app/(prowler)/providers/(set-up-provider)/layout.tsx b/ui/app/(prowler)/providers/(set-up-provider)/layout.tsx index 0367a86090..0790b996a5 100644 --- a/ui/app/(prowler)/providers/(set-up-provider)/layout.tsx +++ b/ui/app/(prowler)/providers/(set-up-provider)/layout.tsx @@ -14,7 +14,7 @@ export default function ProviderLayout({ children }: ProviderLayoutProps) { return ( <> diff --git a/ui/app/(prowler)/providers/page.tsx b/ui/app/(prowler)/providers/page.tsx index 3851c75f0d..54dd5214c2 100644 --- a/ui/app/(prowler)/providers/page.tsx +++ b/ui/app/(prowler)/providers/page.tsx @@ -22,10 +22,10 @@ export default async function Providers({ return ( <> -
+
- +
diff --git a/ui/app/(prowler)/scans/page.tsx b/ui/app/(prowler)/scans/page.tsx index 6ec8419c5b..7bd1317c12 100644 --- a/ui/app/(prowler)/scans/page.tsx +++ b/ui/app/(prowler)/scans/page.tsx @@ -51,8 +51,6 @@ export default async function Scans({ return ( <> -
- {thereIsNoProviders && ( <> @@ -64,18 +62,23 @@ export default async function Scans({ <> {thereIsNoProvidersConnected ? ( <> +
+ - + ) : ( <> +
+ )} +
diff --git a/ui/components/providers/add-provider-button.tsx b/ui/components/providers/add-provider-button.tsx index 87dfc3d932..24b1b54c86 100644 --- a/ui/components/providers/add-provider-button.tsx +++ b/ui/components/providers/add-provider-button.tsx @@ -7,13 +7,13 @@ export const AddProviderButton = () => { return ( } > - Add Account + Add Cloud Provider ); }; diff --git a/ui/components/providers/workflow/forms/test-connection-form.tsx b/ui/components/providers/workflow/forms/test-connection-form.tsx index 778df1e478..726ab3be30 100644 --- a/ui/components/providers/workflow/forms/test-connection-form.tsx +++ b/ui/components/providers/workflow/forms/test-connection-form.tsx @@ -116,9 +116,13 @@ export const TestConnectionForm = ({ error: connected ? null : error || "Unknown error", }); - if (connected) { + if (connected && isUpdated) return router.push("/providers"); + + if (connected && !isUpdated) { try { + // Schedule daily scan by default const data = await scheduleDaily(formData); + if (data.error) { setApiErrorMessage(data.error); form.setError("providerId", { @@ -126,19 +130,8 @@ export const TestConnectionForm = ({ message: data.error, }); } else { - const urlParams = new URLSearchParams(window.location.search); - const isUpdated = urlParams.get("updated") === "true"; - - if (!isUpdated) { - setIsRedirecting(true); - router.push("/scans"); - return; - } else { - setConnectionStatus({ - connected: true, - error: null, - }); - } + setIsRedirecting(true); + router.push("/scans"); } } catch (error) { form.setError("providerId", { @@ -203,8 +196,8 @@ export const TestConnectionForm = ({

Scan initiated successfully

-

- Redirecting to scans dashboard... +

+ Redirecting to scans job details...

@@ -219,10 +212,14 @@ export const TestConnectionForm = ({ >
- Check connection and launch scan + {!isUpdated + ? "Check connection and launch scan" + : "Check connection"}

- A successful connection will launch a daily scheduled scan. + {!isUpdated + ? "A successful connection will launch a daily scheduled scan." + : "A successful connection will redirect you to the providers page."}

@@ -267,6 +264,12 @@ export const TestConnectionForm = ({

)} */} + {isUpdated && !connectionStatus?.error && ( +

+ Check the new credentials and test the connection. +

+ )} +
@@ -307,27 +310,18 @@ export const TestConnectionForm = ({ type={ isUpdated && connectionStatus?.connected ? "button" : "submit" } - onPress={ - isUpdated && connectionStatus?.connected - ? () => router.push("/providers") - : undefined - } ariaLabel={"Save"} className="w-1/2" variant="solid" color="action" size="lg" isLoading={isLoading} - endContent={!isLoading && } + endContent={!isLoading && !isUpdated && } > {isLoading ? ( <>Loading ) : ( - - {isUpdated && connectionStatus?.connected - ? "Go to providers" - : "Launch scan"} - + {isUpdated ? "Check connection" : "Launch scan"} )} )} diff --git a/ui/components/providers/workflow/workflow-add-provider.tsx b/ui/components/providers/workflow/workflow-add-provider.tsx index 9d135e0ce6..abc5c0b78f 100644 --- a/ui/components/providers/workflow/workflow-add-provider.tsx +++ b/ui/components/providers/workflow/workflow-add-provider.tsx @@ -8,21 +8,21 @@ import { VerticalSteps } from "./vertical-steps"; const steps = [ { - title: "Add your cloud account", + title: "Add your cloud provider", description: "Select the cloud provider for the account to connect and specify whether to use an IAM role or credentials for access.", href: "/providers/connect-account", }, { - title: "Add credentials to your cloud account", + title: "Add credentials to your cloud provider", description: - "Provide the credentials required to connect to the cloud account.", + "Provide the credentials required to connect to the cloud provider.", href: "/providers/add-credentials", }, { title: "Check connection and launch scan", description: - "Verify the connection to ensure that the provided credentials are valid for accessing the cloud account and initiating a scan.", + "Verify the connection to ensure that the provided credentials are valid for accessing the cloud provider and initiating a scan.", href: "/providers/test-connection", }, ]; @@ -39,10 +39,10 @@ export const WorkflowAddProvider = () => { return (

- Add a cloud account + Add a cloud provider

- Complete the steps to configure the cloud account, enabling the launch + Complete the steps to configure the cloud provider, enabling the launch of the first scan once completed.

{ return ( -
- - -

No Cloud Accounts Configured

-

- You don't have any cloud accounts configured yet. This is the - first step to get started. -

- - Add Cloud Account - -
-
+
+
+ + +
+ +

+ No Cloud Providers Configured +

+
+
+

+ No cloud providers have been configured. Start by setting up a + cloud provider. +

+
+ + + Get Started + +
+
+
); }; diff --git a/ui/components/scans/no-providers-connected.tsx b/ui/components/scans/no-providers-connected.tsx index 36f145a06e..96acb15e15 100644 --- a/ui/components/scans/no-providers-connected.tsx +++ b/ui/components/scans/no-providers-connected.tsx @@ -1,31 +1,43 @@ "use client"; -import { Card, CardBody } from "@nextui-org/react"; import React from "react"; +import { InfoIcon } from "../icons/Icons"; import { CustomButton } from "../ui/custom"; export const NoProvidersConnected = () => { return ( -
- - -

No Cloud Accounts Connected

-

- All your cloud accounts are currently disconnected. Please review - their configuration to proceed. +

+
+
+
+ +

+ No Connected Cloud Providers +

+
+

+ No cloud providers are currently connected. Connecting a cloud + provider is required to launch on-demand scans.

+

+ Once the cloud providers are correctly configured, this message will + disappear, and on-demand scans can be launched. +

+
+
- Review Cloud Accounts + Review Cloud Providers - - +
+
); }; diff --git a/ui/components/scans/table/scan-detail.tsx b/ui/components/scans/table/scan-detail.tsx index 6430dc8c3e..6e9c3e288c 100644 --- a/ui/components/scans/table/scan-detail.tsx +++ b/ui/components/scans/table/scan-detail.tsx @@ -1,214 +1,165 @@ "use client"; -import { Card, CardBody, CardHeader, Divider } from "@nextui-org/react"; +import { Snippet } from "@nextui-org/react"; -import { DateWithTime, SnippetId } from "@/components/ui/entities"; +import { ConnectionTrue } from "@/components/icons"; +import { ConnectionFalse } from "@/components/icons/Icons"; +import { + DateWithTime, + EntityInfoShort, + InfoField, +} from "@/components/ui/entities"; import { StatusBadge } from "@/components/ui/table/status-badge"; -import { ScanProps, TaskDetails } from "@/types"; +import { ProviderProps, ScanProps, TaskDetails } from "@/types"; -interface ScanDetailsProps { +const renderValue = (value: string | null | undefined) => { + return value && value.trim() !== "" ? value : "-"; +}; + +const formatDuration = (seconds: number) => { + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + const remainingSeconds = seconds % 60; + + const parts = []; + if (hours > 0) parts.push(`${hours}h`); + if (minutes > 0) parts.push(`${minutes}m`); + if (remainingSeconds > 0 || parts.length === 0) + parts.push(`${remainingSeconds}s`); + + return parts.join(" "); +}; + +const Section = ({ + title, + children, +}: { + title: string; + children: React.ReactNode; +}) => ( +
+

+ {title} +

+ {children} +
+); + +export const ScanDetail = ({ + scanDetails, +}: { scanDetails: ScanProps & { taskDetails?: TaskDetails; + providerDetails?: ProviderProps; }; -} - -export const ScanDetail = ({ scanDetails }: ScanDetailsProps) => { - const scanOnDemand = scanDetails.attributes; +}) => { + const scan = scanDetails.attributes; const taskDetails = scanDetails.taskDetails; + const providerDetails = scanDetails.providerDetails?.attributes; return (
{/* Header */}
-

- Scan Details -

- - - {/* Details Section */} -
-
-
- - } - /> - - - - -
-
- - ) : ( - "Not Started" - ) - } - /> - - ) : ( - "Not Completed" - ) - } - /> - - ) : ( - "Not Scheduled" - ) - } - /> - - } - /> - - ) : ( - "N/A" - ) - } - /> -
+ {/* Scan Details */} +
+
+ {renderValue(scan.name)} + + {scan.unique_resource_count} + + {scan.progress}%
-
- {/* Scan Arguments Section */} - {/* - -

- Scan Arguments -

-
- - -
- - Checks - - - {(scanOnDemand.scanner_args as any)?.checks_to_execute?.join( - ", ", - ) || "N/A"} - -
-
-
*/} +
+ {renderValue(scan.trigger)} + {renderValue(scan.state)} + + {formatDuration(scan.duration)} + +
- {/* Task Details Section */} - {taskDetails && ( - - -

- State Details -

-
- - -
- - - {taskDetails.attributes.result && ( - <> - - {taskDetails.attributes.result.exc_message && ( - - )} - - )} - + {scan.state === "failed" && taskDetails?.attributes.result && ( + <> + {taskDetails.attributes.result.exc_message && ( + + + + {taskDetails.attributes.result.exc_message.join("\n")} + + + + )} +
+ + {renderValue(taskDetails.attributes.result.exc_type)} + + + + {renderValue(taskDetails?.attributes.task_args.scan_id)} + +
- - - )} + + )} + +
+ + + + + + + {scan.next_scan_at && ( + + + + )} +
+
+ + {/* Provider Details */} +
+ {providerDetails ? ( +
+ + + {providerDetails.connection.connected ? ( + + ) : ( + + )} + + + + +
+ ) : ( + + No provider details available + + )} +
); }; - -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/ui/components/scans/table/scans/data-table-row-details.tsx b/ui/components/scans/table/scans/data-table-row-details.tsx index 7af4936569..7e2e86e240 100644 --- a/ui/components/scans/table/scans/data-table-row-details.tsx +++ b/ui/components/scans/table/scans/data-table-row-details.tsx @@ -3,9 +3,11 @@ import { useRouter, useSearchParams } from "next/navigation"; import { useEffect, useState } from "react"; +import { getProvider } from "@/actions/providers"; import { getScan } from "@/actions/scans"; import { getTask } from "@/actions/task"; -import { ScanDetail, SkeletonTableScans } from "@/components/scans/table"; +import { ScanDetail } from "@/components/scans/table"; +import { Alert } from "@/components/ui/alert/Alert"; import { checkTaskStatus } from "@/lib"; import { ScanProps } from "@/types"; @@ -35,6 +37,15 @@ export const DataTableRowDetails = ({ entityId }: { entityId: string }) => { const result = await getScan(entityId); const taskId = result.data.relationships.task?.data?.id; + const providerId = result.data.relationships.provider?.data?.id; + + let providerDetails = null; + if (providerId) { + const formData = new FormData(); + formData.append("id", providerId); + const providerResult = await getProvider(formData); + providerDetails = providerResult.data; + } if (taskId) { const taskResult = await checkTaskStatus(taskId); @@ -44,10 +55,14 @@ export const DataTableRowDetails = ({ entityId }: { entityId: string }) => { setScanDetails({ ...result.data, taskDetails: task.data, + providerDetails: providerDetails, }); } } else { - setScanDetails(result.data); + setScanDetails({ + ...result.data, + providerDetails: providerDetails, + }); } } catch (error) { // eslint-disable-next-line no-console @@ -61,7 +76,12 @@ export const DataTableRowDetails = ({ entityId }: { entityId: string }) => { }, [entityId]); if (isLoading) { - return ; + return ( + + Scan details are loading and will be available once the scan is + completed. + + ); } if (!scanDetails) { diff --git a/ui/components/ui/custom/custom-alert-modal.tsx b/ui/components/ui/custom/custom-alert-modal.tsx index 87fecfb0f0..2df428104e 100644 --- a/ui/components/ui/custom/custom-alert-modal.tsx +++ b/ui/components/ui/custom/custom-alert-modal.tsx @@ -32,7 +32,9 @@ export const CustomAlertModal: React.FC = ({ <> {title} - {description} +

+ {description} +

{children}
diff --git a/ui/components/ui/entities/info-field.tsx b/ui/components/ui/entities/info-field.tsx index cd4af6182e..6be2b9e486 100644 --- a/ui/components/ui/entities/info-field.tsx +++ b/ui/components/ui/entities/info-field.tsx @@ -1,13 +1,17 @@ +import clsx from "clsx"; + interface InfoFieldProps { label: string; children: React.ReactNode; variant?: "default" | "simple"; + className?: string; } export const InfoField = ({ label, children, variant = "default", + className, }: InfoFieldProps) => { if (variant === "simple") { return ( @@ -23,11 +27,11 @@ export const InfoField = ({ } return ( -
+
{label} -
+
{children}