From ec4eb70539c49ce7350b856409371ac3cd61b4bf Mon Sep 17 00:00:00 2001 From: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com> Date: Fri, 16 Jan 2026 12:00:01 +0100 Subject: [PATCH] refactor(ui): improve layouts and styles (#9807) --- .../findings-view/findings-view.ssr.tsx | 15 +-- ui/app/(prowler)/invitations/page.tsx | 30 ++--- ui/app/(prowler)/providers/page.tsx | 17 ++- ui/app/(prowler)/resources/page.tsx | 61 +++++---- ui/app/(prowler)/roles/page.tsx | 30 +++-- ui/app/(prowler)/scans/page.tsx | 50 ++++---- ui/app/(prowler)/users/page.tsx | 29 ++--- .../compliance-header/compliance-header.tsx | 2 + .../compliance-scan-info.tsx | 1 + ui/components/filters/active-filter-badge.tsx | 116 ------------------ ui/components/filters/filter-controls.tsx | 4 +- ui/components/filters/index.ts | 1 - ui/components/graphs/shared/chart-legend.tsx | 60 +++++---- .../ui/table/data-table-filter-custom.tsx | 4 +- 14 files changed, 151 insertions(+), 269 deletions(-) delete mode 100644 ui/components/filters/active-filter-badge.tsx diff --git a/ui/app/(prowler)/_overview/graphs-tabs/findings-view/findings-view.ssr.tsx b/ui/app/(prowler)/_overview/graphs-tabs/findings-view/findings-view.ssr.tsx index c71e74c623..be2aec7770 100644 --- a/ui/app/(prowler)/_overview/graphs-tabs/findings-view/findings-view.ssr.tsx +++ b/ui/app/(prowler)/_overview/graphs-tabs/findings-view/findings-view.ssr.tsx @@ -1,7 +1,5 @@ "use server"; -import { Spacer } from "@heroui/spacer"; - import { getLatestFindings } from "@/actions/findings/findings"; import { LighthouseBanner } from "@/components/lighthouse/banner"; import { LinkToFindings } from "@/components/overview"; @@ -59,22 +57,19 @@ export async function FindingsViewSSR({ searchParams }: FindingsViewSSRProps) { }; return ( -
+
-
-
-

+
+
+

Latest new failing findings

-

+

Showing the latest 10 new failing findings by severity.

-
-
- -
- +
+
+ - + +
+ + }> + +
- - - }> - - ); } diff --git a/ui/app/(prowler)/providers/page.tsx b/ui/app/(prowler)/providers/page.tsx index e6726c2fd5..6e52449c04 100644 --- a/ui/app/(prowler)/providers/page.tsx +++ b/ui/app/(prowler)/providers/page.tsx @@ -1,4 +1,3 @@ -import { Spacer } from "@heroui/spacer"; import { Suspense } from "react"; import { getProviders } from "@/actions/providers"; @@ -26,20 +25,20 @@ export default async function Providers({ return ( - - - - - }> - - +
+ + + }> + + +
); } const ProvidersActions = () => { return ( -
+
diff --git a/ui/app/(prowler)/resources/page.tsx b/ui/app/(prowler)/resources/page.tsx index 1064da4721..d5b8da9b69 100644 --- a/ui/app/(prowler)/resources/page.tsx +++ b/ui/app/(prowler)/resources/page.tsx @@ -1,4 +1,3 @@ -import { Spacer } from "@heroui/spacer"; import { Suspense } from "react"; import { getProviders } from "@/actions/providers"; @@ -63,36 +62,36 @@ export default async function Resources({ return ( - - - - }> - - +
+ + }> + + +
); } diff --git a/ui/app/(prowler)/roles/page.tsx b/ui/app/(prowler)/roles/page.tsx index e10912576e..259776c5a3 100644 --- a/ui/app/(prowler)/roles/page.tsx +++ b/ui/app/(prowler)/roles/page.tsx @@ -1,4 +1,3 @@ -import { Spacer } from "@heroui/spacer"; import Link from "next/link"; import { Suspense } from "react"; @@ -6,8 +5,7 @@ import { getRoles } from "@/actions/roles"; import { FilterControls } from "@/components/filters"; import { filterRoles } from "@/components/filters/data-filters"; import { AddIcon } from "@/components/icons"; -import { ColumnsRoles } from "@/components/roles/table"; -import { SkeletonTableRoles } from "@/components/roles/table"; +import { ColumnsRoles, SkeletonTableRoles } from "@/components/roles/table"; import { Button } from "@/components/shadcn"; import { ContentLayout } from "@/components/ui"; import { DataTable, DataTableFilterCustom } from "@/components/ui/table"; @@ -25,21 +23,21 @@ export default async function Roles({ -
- +
+
+ + +
- + }> + +
- - - }> - - ); } diff --git a/ui/app/(prowler)/scans/page.tsx b/ui/app/(prowler)/scans/page.tsx index 3ba35d2a5d..ba7a78d146 100644 --- a/ui/app/(prowler)/scans/page.tsx +++ b/ui/app/(prowler)/scans/page.tsx @@ -1,4 +1,3 @@ -import { Spacer } from "@heroui/spacer"; import { Suspense } from "react"; import { getProviders } from "@/actions/providers"; @@ -87,33 +86,32 @@ export default async function Scans({ <> - {!hasManageScansPermission ? ( - + {!hasManageScansPermission ? ( + + ) : thereIsNoProvidersConnected ? ( + <> + + + ) : ( + + )} + +
+ - ) : thereIsNoProvidersConnected ? ( - <> - - - - - ) : ( - - )} - - - -
- +
+ +
+ }> + +
- - }> - - ); diff --git a/ui/app/(prowler)/users/page.tsx b/ui/app/(prowler)/users/page.tsx index 397e8ae94c..8691137ae9 100644 --- a/ui/app/(prowler)/users/page.tsx +++ b/ui/app/(prowler)/users/page.tsx @@ -1,15 +1,13 @@ -import { Spacer } from "@heroui/spacer"; import Link from "next/link"; import { Suspense } from "react"; import { getRoles } from "@/actions/roles/roles"; import { getUsers } from "@/actions/users/users"; import { FilterControls } from "@/components/filters"; -import { filterUsers } from "@/components/filters/data-filters"; import { AddIcon } from "@/components/icons"; import { Button } from "@/components/shadcn"; import { ContentLayout } from "@/components/ui"; -import { DataTable, DataTableFilterCustom } from "@/components/ui/table"; +import { DataTable } from "@/components/ui/table"; import { ColumnsUser, SkeletonTableUser } from "@/components/users/table"; import { Role, SearchParamsProps, UserProps } from "@/types"; @@ -25,21 +23,20 @@ export default async function Users({ -
- +
+
+ +
- + }> + +
- - - }> - - ); } diff --git a/ui/components/compliance/compliance-header/compliance-header.tsx b/ui/components/compliance/compliance-header/compliance-header.tsx index 5f259f9e9b..13511d78cb 100644 --- a/ui/components/compliance/compliance-header/compliance-header.tsx +++ b/ui/components/compliance/compliance-header/compliance-header.tsx @@ -73,8 +73,10 @@ export const ComplianceHeader = ({ {hasContent && (
+ {/* Showed in the details page */} {selectedScan && } + {/* Showed in the compliance page */} {showProviders && } {!hideFilters && allFilters.length > 0 && ( diff --git a/ui/components/compliance/compliance-header/compliance-scan-info.tsx b/ui/components/compliance/compliance-header/compliance-scan-info.tsx index 1f00222ef3..086a9183ed 100644 --- a/ui/components/compliance/compliance-header/compliance-scan-info.tsx +++ b/ui/components/compliance/compliance-header/compliance-scan-info.tsx @@ -27,6 +27,7 @@ export const ComplianceScanInfo = ({ scan }: ComplianceScanInfoProps) => { entityAlias={scan.providerInfo.alias} entityId={scan.providerInfo.uid} showCopyAction={false} + maxWidth="w-[80px]" />
diff --git a/ui/components/filters/active-filter-badge.tsx b/ui/components/filters/active-filter-badge.tsx deleted file mode 100644 index bc8f9244f4..0000000000 --- a/ui/components/filters/active-filter-badge.tsx +++ /dev/null @@ -1,116 +0,0 @@ -"use client"; - -import { X } from "lucide-react"; -import { useSearchParams } from "next/navigation"; - -import { Badge } from "@/components/shadcn"; -import { useUrlFilters } from "@/hooks/use-url-filters"; - -export interface FilterBadgeConfig { - /** - * The filter key without the "filter[]" wrapper. - * Example: "scan__in", "check_id__in", "provider__in" - */ - filterKey: string; - - /** - * Label to display before the value. - * Example: "Scan", "Check ID", "Provider" - */ - label: string; - - /** - * Optional function to format a single value for display. - * Useful for truncating UUIDs, etc. - * Default: shows value as-is - */ - formatValue?: (value: string) => string; - - /** - * Optional function to format the display when multiple values are selected. - * Default: "{count} {label}s filtered" - */ - formatMultiple?: (count: number, label: string) => string; -} - -/** - * Default filter badge configurations for common use cases. - * Add new filters here to automatically show them as badges. - */ -export const DEFAULT_FILTER_BADGES: FilterBadgeConfig[] = [ - { - filterKey: "check_id__in", - label: "Check ID", - formatMultiple: (count) => `${count} Check IDs filtered`, - }, -]; - -interface ActiveFilterBadgeProps { - config: FilterBadgeConfig; -} - -/** - * Single filter badge component that reads from URL and displays if active. - */ -const ActiveFilterBadge = ({ config }: ActiveFilterBadgeProps) => { - const searchParams = useSearchParams(); - const { clearFilter } = useUrlFilters(); - - const { - filterKey, - label, - formatValue = (v) => v, - formatMultiple = (count, lbl) => `${count} ${lbl}s filtered`, - } = config; - - const fullKey = filterKey.startsWith("filter[") - ? filterKey - : `filter[${filterKey}]`; - - const filterValue = searchParams.get(fullKey); - - if (!filterValue) { - return null; - } - - const values = filterValue.split(","); - const displayText = - values.length > 1 - ? formatMultiple(values.length, label) - : `${label}: ${formatValue(values[0])}`; - - return ( - clearFilter(filterKey)} - > - {displayText} - - - ); -}; - -interface ActiveFilterBadgesProps { - /** - * Filter configurations to render as badges. - * Defaults to DEFAULT_FILTER_BADGES if not provided. - */ - filters?: FilterBadgeConfig[]; -} - -/** - * Renders filter badges for all configured filters that are active in the URL. - * Only shows badges for filters that have values in the URL params. - */ -export const ActiveFilterBadges = ({ - filters = DEFAULT_FILTER_BADGES, -}: ActiveFilterBadgesProps) => { - return ( - <> - {filters.map((config) => ( - - ))} - - ); -}; diff --git a/ui/components/filters/filter-controls.tsx b/ui/components/filters/filter-controls.tsx index d00e5da8a1..e8df5c30be 100644 --- a/ui/components/filters/filter-controls.tsx +++ b/ui/components/filters/filter-controls.tsx @@ -30,8 +30,8 @@ export const FilterControls = ({ customFilters, }: FilterControlsProps) => { return ( -
-
+
+
{search && } {providers && } diff --git a/ui/components/filters/index.ts b/ui/components/filters/index.ts index 4d6fa55ebe..5d9f577590 100644 --- a/ui/components/filters/index.ts +++ b/ui/components/filters/index.ts @@ -1,4 +1,3 @@ -export * from "./active-filter-badge"; export * from "./clear-filters-button"; export * from "./custom-account-selection"; export * from "./custom-checkbox-muted-findings"; diff --git a/ui/components/graphs/shared/chart-legend.tsx b/ui/components/graphs/shared/chart-legend.tsx index f05bfbad57..78966c9828 100644 --- a/ui/components/graphs/shared/chart-legend.tsx +++ b/ui/components/graphs/shared/chart-legend.tsx @@ -1,3 +1,9 @@ +import { + Tooltip, + TooltipContent, + TooltipTrigger, +} from "@/components/shadcn/tooltip"; + export interface ChartLegendItem { label: string; color: string; @@ -18,36 +24,42 @@ export function ChartLegend({ const isInteractive = !!onItemClick; return ( -
+
{items.map((item, index) => { const dataKey = item.dataKey ?? item.label.toLowerCase(); const isSelected = selectedItem === dataKey; const isFaded = selectedItem !== null && !isSelected; return ( - + + + + + +

{item.label}

+
+
); })}
diff --git a/ui/components/ui/table/data-table-filter-custom.tsx b/ui/components/ui/table/data-table-filter-custom.tsx index 6b3dc9a71b..3b5a670193 100644 --- a/ui/components/ui/table/data-table-filter-custom.tsx +++ b/ui/components/ui/table/data-table-filter-custom.tsx @@ -3,7 +3,6 @@ import { useSearchParams } from "next/navigation"; import { ComplianceScanInfo } from "@/components/compliance/compliance-header/compliance-scan-info"; -import { ActiveFilterBadges } from "@/components/filters/active-filter-badge"; import { ClearFiltersButton } from "@/components/filters/clear-filters-button"; import { MultiSelect, @@ -176,8 +175,7 @@ export const DataTableFilterCustom = ({ ); })} {!hideClearButton && ( -
- +
)}