From 6426558b189ed8d6a58fb393c62f8d9badf48ab3 Mon Sep 17 00:00:00 2001 From: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com> Date: Thu, 20 Nov 2025 16:18:25 +0100 Subject: [PATCH] fix(ui): pre-release fixes and improvements (#9278) --- ui/app/(prowler)/lighthouse/page.tsx | 2 + .../components/accounts-selector.tsx | 2 +- .../components/provider-type-selector.tsx | 2 +- .../threat-score/threat-score.ssr.tsx | 6 +- .../components/threat-score/threat-score.tsx | 22 +- ui/components/auth/oss/auth-layout.tsx | 22 +- ui/components/auth/oss/password-validator.tsx | 18 +- ui/components/auth/oss/sign-up-form.tsx | 12 +- .../client-accordion-wrapper.tsx | 8 +- .../compliance-scan-info.tsx | 21 +- .../compliance/threatscore-badge.tsx | 2 +- ui/components/findings/send-to-jira-modal.tsx | 157 +++++------ .../findings/table/column-findings.tsx | 38 ++- .../findings/table/finding-detail.tsx | 6 +- .../providers-badge/azure-provider-badge.tsx | 149 ++++++----- .../providers-badge/m365-provider-badge.tsx | 252 ++++++++++-------- .../invitations/table/column-invitations.tsx | 15 +- .../lighthouse/ai-elements/prompt-input.tsx | 4 +- ui/components/lighthouse/chat.tsx | 167 +++++++----- .../manage-groups/table/column-groups.tsx | 2 + .../table/column-new-findings-to-date.tsx | 55 +++- .../providers/table/column-providers.tsx | 12 +- .../resources/table/column-resources.tsx | 38 +-- ui/components/roles/table/column-roles.tsx | 5 +- .../launch-workflow/select-scan-provider.tsx | 10 +- ui/components/scans/table/scan-detail.tsx | 130 ++++----- .../scans/table/scans/column-get-scans.tsx | 73 ++--- .../table/scans/skeleton-scan-detail.tsx | 86 +++--- .../resource-stats-card.tsx | 2 +- ui/components/shadcn/combobox/combobox.tsx | 16 +- ui/components/shadcn/index.ts | 1 + ui/components/shadcn/select/select.tsx | 6 +- ui/components/ui/accordion/Accordion.tsx | 7 +- .../ui/entities/entity-info-short.tsx | 63 ----- ui/components/ui/entities/entity-info.tsx | 110 ++++++++ ui/components/ui/entities/index.ts | 2 +- .../ui/table/data-table-column-header.tsx | 8 +- .../ui/table/data-table-filter-custom.tsx | 6 +- ui/components/ui/table/table.tsx | 5 +- .../profile/api-keys/column-api-keys.tsx | 11 +- ui/components/users/table/column-users.tsx | 10 +- 41 files changed, 889 insertions(+), 674 deletions(-) delete mode 100644 ui/components/ui/entities/entity-info-short.tsx create mode 100644 ui/components/ui/entities/entity-info.tsx diff --git a/ui/app/(prowler)/lighthouse/page.tsx b/ui/app/(prowler)/lighthouse/page.tsx index 533fa33a94..e301122c8b 100644 --- a/ui/app/(prowler)/lighthouse/page.tsx +++ b/ui/app/(prowler)/lighthouse/page.tsx @@ -8,6 +8,8 @@ import { LighthouseIcon } from "@/components/icons/Icons"; import { Chat } from "@/components/lighthouse"; import { ContentLayout } from "@/components/ui"; +export const dynamic = "force-dynamic"; + export default async function AIChatbot() { const hasConfig = await isLighthouseConfigured(); diff --git a/ui/app/(prowler)/new-overview/components/accounts-selector.tsx b/ui/app/(prowler)/new-overview/components/accounts-selector.tsx index d878712d60..254b213b4f 100644 --- a/ui/app/(prowler)/new-overview/components/accounts-selector.tsx +++ b/ui/app/(prowler)/new-overview/components/accounts-selector.tsx @@ -48,7 +48,7 @@ export function AccountsSelector({ providers }: AccountsSelectorProps) { : []; const selectedIds = current ? current.split(",").filter(Boolean) : []; const visibleProviders = providers - .filter((p) => p.attributes.connection?.connected) + // .filter((p) => p.attributes.connection?.connected) .filter((p) => selectedTypesList.length > 0 ? selectedTypesList.includes(p.attributes.provider) diff --git a/ui/app/(prowler)/new-overview/components/provider-type-selector.tsx b/ui/app/(prowler)/new-overview/components/provider-type-selector.tsx index 7dd0504deb..d02458ab43 100644 --- a/ui/app/(prowler)/new-overview/components/provider-type-selector.tsx +++ b/ui/app/(prowler)/new-overview/components/provider-type-selector.tsx @@ -132,7 +132,7 @@ export const ProviderTypeSelector = ({ const availableTypes = Array.from( new Set( providers - .filter((p) => p.attributes.connection?.connected) + // .filter((p) => p.attributes.connection?.connected) .map((p) => p.attributes.provider), ), ) as ProviderType[]; diff --git a/ui/app/(prowler)/new-overview/components/threat-score/threat-score.ssr.tsx b/ui/app/(prowler)/new-overview/components/threat-score/threat-score.ssr.tsx index a7a29d3e52..562f4f6f16 100644 --- a/ui/app/(prowler)/new-overview/components/threat-score/threat-score.ssr.tsx +++ b/ui/app/(prowler)/new-overview/components/threat-score/threat-score.ssr.tsx @@ -21,10 +21,10 @@ export const ThreatScoreSSR = async ({ const snapshot = threatScoreData.data[0]; const attributes = snapshot.attributes; - // Parse score from decimal string to number and round to integer - const score = Math.round(parseFloat(attributes.overall_score)); + // Parse score from decimal string to number + const score = parseFloat(attributes.overall_score); const scoreDelta = attributes.score_delta - ? Math.round(parseFloat(attributes.score_delta)) + ? parseFloat(attributes.score_delta) : null; return ( diff --git a/ui/app/(prowler)/new-overview/components/threat-score/threat-score.tsx b/ui/app/(prowler)/new-overview/components/threat-score/threat-score.tsx index db07e951b8..84dcf9d837 100644 --- a/ui/app/(prowler)/new-overview/components/threat-score/threat-score.tsx +++ b/ui/app/(prowler)/new-overview/components/threat-score/threat-score.tsx @@ -66,14 +66,11 @@ function convertSectionScoresToTooltipData( if (!sectionScores) return []; return Object.entries(sectionScores).map(([name, value]) => { - // Round to nearest integer - const roundedValue = Math.round(value); - // Determine color based on the same ranges as THREAT_LEVEL_CONFIG - const threatLevel = getThreatLevel(roundedValue); + const threatLevel = getThreatLevel(value); const color = THREAT_LEVEL_CONFIG[threatLevel].chartColor; - return { name, value: roundedValue, color }; + return { name, value, color }; }); } @@ -162,8 +159,12 @@ export function ThreatScore({ {scoreDelta !== undefined && scoreDelta !== null && scoreDelta !== 0 && ( -
- +
+

Threat score has{" "} {scoreDelta > 0 ? "improved" : "decreased"} by{" "} @@ -174,10 +175,11 @@ export function ThreatScore({ {/* Gaps Message */} {gaps.length > 0 && ( -

+

Major gaps include {gaps.slice(0, 2).join(", ")} diff --git a/ui/components/auth/oss/auth-layout.tsx b/ui/components/auth/oss/auth-layout.tsx index 3fe26234ab..84d47c218c 100644 --- a/ui/components/auth/oss/auth-layout.tsx +++ b/ui/components/auth/oss/auth-layout.tsx @@ -10,18 +10,24 @@ interface AuthLayoutProps { export const AuthLayout = ({ title, children }: AuthLayoutProps) => { return ( -

-
+
+
{/* Background Pattern */} -
+
+ + {/* Prowler Logo */} +
+ +
{/* Auth Form Container */}
- {/* Prowler Logo */} -
- -
- {/* Header with Title and Theme Toggle */}

{title}

diff --git a/ui/components/auth/oss/password-validator.tsx b/ui/components/auth/oss/password-validator.tsx index 020a65bedc..e5703aee6a 100644 --- a/ui/components/auth/oss/password-validator.tsx +++ b/ui/components/auth/oss/password-validator.tsx @@ -58,21 +58,17 @@ export const PasswordRequirementsMessage = ({ return (
{allRequirementsMet ? (
@@ -80,10 +76,10 @@ export const PasswordRequirementsMessage = ({
@@ -99,12 +95,12 @@ export const PasswordRequirementsMessage = ({
); }, + enableSorting: false, }, { accessorKey: "status", - header: "Status", + header: ({ column }) => ( + + ), cell: ({ row }) => { const { attributes: { state }, @@ -123,10 +133,13 @@ export const ColumnGetScans: ColumnDef[] = [
); }, + enableSorting: false, }, { accessorKey: "findings", - header: "Findings", + header: ({ column }) => ( + + ), cell: ({ row }) => { const { id } = getScanData(row); const scanState = row.original.attributes?.state; @@ -138,10 +151,13 @@ export const ColumnGetScans: ColumnDef[] = [ /> ); }, + enableSorting: false, }, { accessorKey: "compliance", - header: "Compliance", + header: ({ column }) => ( + + ), cell: ({ row }) => { const { id } = getScanData(row); const scanState = row.original.attributes?.state; @@ -153,21 +169,12 @@ export const ColumnGetScans: ColumnDef[] = [ /> ); }, + enableSorting: false, }, { id: "download", - header: () => ( -
-

Download

- -
- -
-
-
+ header: ({ column }) => ( + ), cell: ({ row }) => { return ( @@ -176,21 +183,13 @@ export const ColumnGetScans: ColumnDef[] = [
); }, + enableSorting: false, }, - - // { - // accessorKey: "scanner_args", - // header: "Scanner Args", - // cell: ({ row }) => { - // const { - // attributes: { scanner_args }, - // } = getScanData(row); - // return

{scanner_args?.only_logs}

; - // }, - // }, { accessorKey: "resources", - header: "Resources", + header: ({ column }) => ( + + ), cell: ({ row }) => { const { attributes: { unique_resource_count }, @@ -201,16 +200,20 @@ export const ColumnGetScans: ColumnDef[] = [
); }, + enableSorting: false, }, { accessorKey: "scheduled_at", - header: "Scheduled at", + header: ({ column }) => ( + + ), cell: ({ row }) => { const { attributes: { scheduled_at }, } = getScanData(row); return ; }, + enableSorting: false, }, { accessorKey: "completed_at", @@ -272,8 +275,10 @@ export const ColumnGetScans: ColumnDef[] = [ }, { id: "actions", + header: ({ column }) => , cell: ({ row }) => { return ; }, + enableSorting: false, }, ]; diff --git a/ui/components/scans/table/scans/skeleton-scan-detail.tsx b/ui/components/scans/table/scans/skeleton-scan-detail.tsx index 5e69becb48..82645b8204 100644 --- a/ui/components/scans/table/scans/skeleton-scan-detail.tsx +++ b/ui/components/scans/table/scans/skeleton-scan-detail.tsx @@ -1,58 +1,64 @@ +import { Card, CardContent, CardHeader } from "@/components/shadcn"; +import { Skeleton } from "@/components/shadcn/skeleton/skeleton"; + export const SkeletonScanDetail = () => { return (
{/* Header Skeleton */}
-
+
-
+
-
-
+ +
{/* Scan Details Section Skeleton */} -
-
+ + + + + + {/* First grid row */} +
+ {Array.from({ length: 3 }).map((_, index) => ( +
+ + +
+ ))} +
- {/* First grid row */} -
- {Array.from({ length: 3 }).map((_, index) => ( -
-
-
-
- ))} -
+ {/* Second grid row */} +
+ {Array.from({ length: 3 }).map((_, index) => ( +
+ + +
+ ))} +
- {/* Second grid row */} -
- {Array.from({ length: 3 }).map((_, index) => ( -
-
-
-
- ))} -
+ {/* Scan ID field */} +
+ + +
- {/* Scan ID field */} -
-
-
-
- - {/* Third grid row */} -
- {Array.from({ length: 3 }).map((_, index) => ( -
-
-
-
- ))} -
-
+ {/* Third grid row */} +
+ {Array.from({ length: 3 }).map((_, index) => ( +
+ + +
+ ))} +
+ +
); }; diff --git a/ui/components/shadcn/card/resource-stats-card/resource-stats-card.tsx b/ui/components/shadcn/card/resource-stats-card/resource-stats-card.tsx index bb42f4bfff..769c412b8e 100644 --- a/ui/components/shadcn/card/resource-stats-card/resource-stats-card.tsx +++ b/ui/components/shadcn/card/resource-stats-card/resource-stats-card.tsx @@ -103,7 +103,7 @@ export const ResourceStatsCard = ({ > {header && } {emptyState ? ( -
+

{emptyState.message}

diff --git a/ui/components/shadcn/combobox/combobox.tsx b/ui/components/shadcn/combobox/combobox.tsx index 115c830d31..b10bcf8973 100644 --- a/ui/components/shadcn/combobox/combobox.tsx +++ b/ui/components/shadcn/combobox/combobox.tsx @@ -1,7 +1,7 @@ "use client"; import { cva, type VariantProps } from "class-variance-authority"; -import { Check, ChevronsUpDown } from "lucide-react"; +import { Check, ChevronsUpDown, Loader2 } from "lucide-react"; import { useState } from "react"; import { Button } from "@/components/shadcn/button/button"; @@ -72,6 +72,8 @@ export interface ComboboxProps contentClassName?: string; disabled?: boolean; showSelectedFirst?: boolean; + loading?: boolean; + loadingMessage?: string; } export function Combobox({ @@ -88,6 +90,8 @@ export function Combobox({ variant = "default", disabled = false, showSelectedFirst = true, + loading = false, + loadingMessage = "Loading...", }: ComboboxProps) { const [open, setOpen] = useState(false); @@ -127,8 +131,16 @@ export function Combobox({ align="start" > - + {!loading && ( + + )} + {loading && ( +
+ + {loadingMessage} +
+ )} {emptyMessage} {/* Show selected option first if enabled */} diff --git a/ui/components/shadcn/index.ts b/ui/components/shadcn/index.ts index b47617530b..2186db85c2 100644 --- a/ui/components/shadcn/index.ts +++ b/ui/components/shadcn/index.ts @@ -12,3 +12,4 @@ export * from "./separator/separator"; export * from "./skeleton/skeleton"; export * from "./tabs/generic-tabs"; export * from "./tabs/tabs"; +export * from "./tooltip"; diff --git a/ui/components/shadcn/select/select.tsx b/ui/components/shadcn/select/select.tsx index b56e3f186c..8c2b12d283 100644 --- a/ui/components/shadcn/select/select.tsx +++ b/ui/components/shadcn/select/select.tsx @@ -131,13 +131,15 @@ function SelectItem({ - {children} + + {children} + diff --git a/ui/components/ui/accordion/Accordion.tsx b/ui/components/ui/accordion/Accordion.tsx index efb390bda7..1907c940c4 100644 --- a/ui/components/ui/accordion/Accordion.tsx +++ b/ui/components/ui/accordion/Accordion.tsx @@ -120,7 +120,10 @@ export const Accordion = ({ return ( } classNames={{ - base: index === 0 || index === 1 ? "my-1" : "my-1", + base: index === 0 || index === 1 ? "my-2" : "my-2", title: "text-sm", subtitle: "text-xs text-gray-500", trigger: diff --git a/ui/components/ui/entities/entity-info-short.tsx b/ui/components/ui/entities/entity-info-short.tsx deleted file mode 100644 index e5e0621878..0000000000 --- a/ui/components/ui/entities/entity-info-short.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { Tooltip } from "@heroui/tooltip"; -import React from "react"; - -import { IdIcon } from "@/components/icons"; -import { ProviderType } from "@/types"; - -import { getProviderLogo } from "./get-provider-logo"; -import { SnippetChip } from "./snippet-chip"; - -interface EntityInfoProps { - cloudProvider: ProviderType; - entityAlias?: string; - entityId?: string; - hideCopyButton?: boolean; - snippetWidth?: string; - showConnectionStatus?: boolean; - maxWidth?: string; -} - -export const EntityInfoShort: React.FC = ({ - cloudProvider, - entityAlias, - entityId, - hideCopyButton = false, - showConnectionStatus = false, - maxWidth = "max-w-[120px]", -}) => { - return ( -
-
-
- {getProviderLogo(cloudProvider)} - {showConnectionStatus && ( - - - - )} -
-
- {entityAlias && ( - - - {entityAlias} - - - )} - } - /> -
-
-
- ); -}; diff --git a/ui/components/ui/entities/entity-info.tsx b/ui/components/ui/entities/entity-info.tsx new file mode 100644 index 0000000000..6d36eed011 --- /dev/null +++ b/ui/components/ui/entities/entity-info.tsx @@ -0,0 +1,110 @@ +"use client"; + +import { Tooltip } from "@heroui/tooltip"; +import { useEffect, useState } from "react"; + +import { CopyIcon, DoneIcon } from "@/components/icons"; +import type { ProviderType } from "@/types"; + +import { getProviderLogo } from "./get-provider-logo"; + +interface EntityInfoProps { + cloudProvider: ProviderType; + entityAlias?: string; + entityId?: string; + snippetWidth?: string; + showConnectionStatus?: boolean; + maxWidth?: string; + showCopyAction?: boolean; +} + +export const EntityInfo = ({ + cloudProvider, + entityAlias, + entityId, + showConnectionStatus = false, + maxWidth = "w-[120px]", + showCopyAction = true, +}: EntityInfoProps) => { + const [copied, setCopied] = useState(false); + + useEffect(() => { + if (!copied) return undefined; + + const timer = setTimeout(() => setCopied(false), 1400); + return () => clearTimeout(timer); + }, [copied]); + + const handleCopyEntityId = async () => { + if (!entityId) return; + + try { + await navigator.clipboard.writeText(entityId); + setCopied(true); + } catch (_error) { + setCopied(false); + } + }; + + const canCopy = Boolean(entityId && showCopyAction); + + return ( +
+
+ {getProviderLogo(cloudProvider)} + {showConnectionStatus && ( + + + + )} +
+
+ {entityAlias ? ( + +

+ {entityAlias} +

+
+ ) : ( + +

+ - +

+
+ )} + {entityId && ( +
+ +

+ {entityId} +

+
+ {canCopy && ( + + + + )} +
+ )} +
+
+ ); +}; diff --git a/ui/components/ui/entities/index.ts b/ui/components/ui/entities/index.ts index fb13c99282..88cf9504f3 100644 --- a/ui/components/ui/entities/index.ts +++ b/ui/components/ui/entities/index.ts @@ -1,5 +1,5 @@ export * from "./date-with-time"; -export * from "./entity-info-short"; +export * from "./entity-info"; export * from "./get-provider-logo"; export * from "./info-field"; export * from "./scan-status"; diff --git a/ui/components/ui/table/data-table-column-header.tsx b/ui/components/ui/table/data-table-column-header.tsx index 73947b5cbf..b5e6e71e93 100644 --- a/ui/components/ui/table/data-table-column-header.tsx +++ b/ui/components/ui/table/data-table-column-header.tsx @@ -75,14 +75,18 @@ export const DataTableColumnHeader = ({ }; if (!column.getCanSort()) { - return
{title}
; + return ( +
+ {title} +
+ ); } return (