Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d6b8346
feat: add new findings table cell components
alejandrobailo Dec 29, 2025
0ca9964
feat: redesign shadcn checkbox and select components
alejandrobailo Dec 29, 2025
018ad9f
feat: update table components with new design system
alejandrobailo Dec 29, 2025
300666e
feat: refactor findings table columns and details
alejandrobailo Dec 29, 2025
34d9360
feat: add toggle functionality to filter controls
alejandrobailo Dec 29, 2025
130d1a9
feat: add shadcn calendar component with react-day-picker
alejandrobailo Dec 29, 2025
033f726
feat: add data-table-search component with toolbar
alejandrobailo Dec 29, 2025
e045c99
feat: redesign date picker and clear filters button
alejandrobailo Dec 29, 2025
efe4d4b
feat: refactor findings filters with provider selectors
alejandrobailo Dec 29, 2025
880ec5c
feat: enable search in findings table
alejandrobailo Dec 29, 2025
ef8fccc
feat: add expandable animation to data-table-search
alejandrobailo Dec 30, 2025
3bb4a5e
chore: CHANGELOG.md updated
alejandrobailo Dec 30, 2025
58c9eb9
feat: filters expand with animation and delta tooltip
alejandrobailo Dec 30, 2025
13bf004
fix: filters spacing
alejandrobailo Dec 30, 2025
b553ff6
fix: lint issues
alejandrobailo Dec 30, 2025
0e8001f
Merge branch 'master' into feat/PROWLER-379-new-table
alejandrobailo Dec 30, 2025
051471e
refactor: improve code quality
alejandrobailo Dec 30, 2025
e3f076e
fix: PR comments
alejandrobailo Dec 30, 2025
74fd3f2
fix: different account filter param for overview and findings
alejandrobailo Dec 30, 2025
771b45e
feat: skeleton for findings table
alejandrobailo Dec 30, 2025
32a5e93
style: search bar border color
alejandrobailo Dec 30, 2025
ede82e8
build: format issues
alejandrobailo Jan 5, 2026
892cbe4
fix: account filter param diff (#9701)
alejandrobailo Jan 12, 2026
5c85ee6
feat: new finding detail view (#9727)
alejandrobailo Jan 12, 2026
af68429
fix: lint issues
alejandrobailo Jan 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ui/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ All notable changes to the **Prowler UI** are documented in this file.
### 🚀 Added

- Add search bar when adding a provider [(#9634)](https://github.com/prowler-cloud/prowler/pull/9634)
- New findings table UI with new design system components, improved filtering UX, and enhanced table interactions [(#9699)](https://github.com/prowler-cloud/prowler/pull/9699)
- Add gradient background to Risk Plot for visual risk context [(#9664)](https://github.com/prowler-cloud/prowler/pull/9664)

### 🔄 Changed
Expand Down
1 change: 1 addition & 0 deletions ui/app/(prowler)/findings/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export default async function Findings({
return (
<ContentLayout title="Findings" icon="lucide:tag">
<FindingsFilters
providers={providersData?.data || []}
providerIds={providerIds}
providerDetails={providerDetails}
completedScans={completedScans || []}
Expand Down
19 changes: 17 additions & 2 deletions ui/components/filters/clear-filters-button.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use client";

import { XCircle } from "lucide-react";
import { useSearchParams } from "next/navigation";

import { useUrlFilters } from "@/hooks/use-url-filters";

Expand All @@ -10,22 +11,36 @@ export interface ClearFiltersButtonProps {
className?: string;
text?: string;
ariaLabel?: string;
/** Show the count of active filters */
showCount?: boolean;
/** Use link style (text only, no button background) */
variant?: "link" | "default";
}

export const ClearFiltersButton = ({
text = "Clear all filters",
ariaLabel = "Reset",
showCount = false,
variant = "link",
}: ClearFiltersButtonProps) => {
const searchParams = useSearchParams();
const { clearAllFilters, hasFilters } = useUrlFilters();

// Count active filters (excluding search)
const filterCount = Array.from(searchParams.keys()).filter(
(key) => key.startsWith("filter[") && key !== "filter[search]",
).length;

if (!hasFilters()) {
return null;
}

const displayText = showCount ? `Clear Filters (${filterCount})` : text;

return (
<Button aria-label={ariaLabel} onClick={clearAllFilters} variant="link">
<Button aria-label={ariaLabel} onClick={clearAllFilters} variant={variant}>
<XCircle className="mr-0.5 size-4" />
{text}
{displayText}
</Button>
);
};
153 changes: 63 additions & 90 deletions ui/components/filters/custom-date-picker.tsx
Original file line number Diff line number Diff line change
@@ -1,120 +1,93 @@
"use client";

import { Button, ButtonGroup } from "@heroui/button";
import { DatePicker } from "@heroui/date-picker";
import {
getLocalTimeZone,
parseDate,
startOfMonth,
startOfWeek,
today,
} from "@internationalized/date";
import { useLocale } from "@react-aria/i18n";
import type { DateValue } from "@react-types/datepicker";
import { format } from "date-fns";
import { CalendarIcon, ChevronDown } from "lucide-react";
import { useSearchParams } from "next/navigation";
import { useEffect, useRef, useState } from "react";
import { useEffect, useState } from "react";

import { Calendar } from "@/components/shadcn/calendar";
import {
Popover,
PopoverContent,
PopoverTrigger,
} from "@/components/shadcn/popover";
import { useUrlFilters } from "@/hooks/use-url-filters";
import { cn } from "@/lib/utils";

export const CustomDatePicker = () => {
const searchParams = useSearchParams();
const { updateFilter } = useUrlFilters();
const [open, setOpen] = useState(false);

const [value, setValue] = useState<DateValue | null>(() => {
const [date, setDate] = useState<Date | undefined>(() => {
const dateParam = searchParams.get("filter[inserted_at]");
if (!dateParam) return null;
if (!dateParam) return undefined;
try {
return parseDate(dateParam);
return new Date(dateParam);
} catch {
return null;
return undefined;
}
});

const { locale } = useLocale();

const now = today(getLocalTimeZone());
const nextWeek = startOfWeek(now.add({ weeks: 1 }), locale);
const nextMonth = startOfMonth(now.add({ months: 1 }));

const applyDateFilter = (date: DateValue | null) => {
if (date) {
updateFilter("inserted_at", date.toString());
const applyDateFilter = (selectedDate: Date | undefined) => {
if (selectedDate) {
// Format as YYYY-MM-DD for the API
updateFilter("inserted_at", format(selectedDate, "yyyy-MM-dd"));
} else {
updateFilter("inserted_at", null);
}
};

const initialRender = useRef(true);

// Sync local state with URL params (e.g., when Clear Filters is clicked)
useEffect(() => {
if (initialRender.current) {
initialRender.current = false;
return;
}
const params = new URLSearchParams(searchParams.toString());
if (params.size === 0) {
setValue(null);
const dateParam = searchParams.get("filter[inserted_at]");
if (!dateParam) {
setDate(undefined);
} else {
try {
setDate(new Date(dateParam));
} catch {
setDate(undefined);
}
}
}, [searchParams]);

const handleDateChange = (newValue: DateValue | null) => {
setValue(newValue);
applyDateFilter(newValue);
const handleDateSelect = (newDate: Date | undefined) => {
setDate(newDate);
applyDateFilter(newDate);
setOpen(false);
};

return (
<div className="flex w-full flex-col md:gap-2">
<DatePicker
style={{
borderRadius: "0.5rem",
}}
aria-label="Select a Date"
classNames={{
base: "w-full [&]:!rounded-lg [&>*]:!rounded-lg",
selectorButton: "text-bg-button-secondary shrink-0",
input:
"text-bg-button-secondary placeholder:text-bg-button-secondary text-sm",
innerWrapper: "[&]:!rounded-lg",
inputWrapper:
"!border-border-input-primary !bg-bg-input-primary dark:!bg-input/30 dark:hover:!bg-input/50 hover:!bg-bg-neutral-secondary !border [&]:!rounded-lg !shadow-xs !transition-[color,box-shadow] focus-within:!border-border-input-primary-press focus-within:!ring-1 focus-within:!ring-border-input-primary-press focus-within:!ring-offset-1 !h-10 !px-4 !py-3 !outline-none",
segment: "text-bg-button-secondary",
}}
popoverProps={{
classNames: {
content:
"border-border-input-primary bg-bg-input-primary border rounded-lg",
},
}}
CalendarTopContent={
<ButtonGroup
fullWidth
className="bg-bg-neutral-secondary [&>button]:border-border-neutral-secondary [&>button]:text-bg-button-secondary px-3 pt-3 pb-2"
radius="full"
size="sm"
variant="flat"
>
<Button onPress={() => handleDateChange(now)}>Today</Button>
<Button onPress={() => handleDateChange(nextWeek)}>
Next week
</Button>
<Button onPress={() => handleDateChange(nextMonth)}>
Next month
</Button>
</ButtonGroup>
}
calendarProps={{
focusedValue: value || undefined,
onFocusChange: setValue,
nextButtonProps: {
variant: "bordered",
},
prevButtonProps: {
variant: "bordered",
},
}}
value={value}
onChange={handleDateChange}
/>
</div>
<Popover open={open} onOpenChange={setOpen}>
<PopoverTrigger asChild>
<button
type="button"
aria-haspopup="dialog"
aria-expanded={open}
className={cn(
"border-border-input-primary bg-bg-input-primary text-bg-button-secondary dark:bg-input/30 dark:hover:bg-input/50 focus-visible:border-border-input-primary-press focus-visible:ring-border-input-primary-press flex h-[52px] w-full items-center justify-between gap-2 rounded-lg border px-4 py-3 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-1 focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
!date && "text-bg-button-secondary",
)}
>
<span className="flex items-center gap-2">
<CalendarIcon className="text-bg-button-secondary size-5 opacity-70" />
{date ? format(date, "PPP") : "Pick a date"}
</span>
<ChevronDown
className={cn(
"text-bg-button-secondary size-6 shrink-0 opacity-70 transition-transform duration-200",
open && "rotate-180",
)}
/>
</button>
</PopoverTrigger>
<PopoverContent
className="border-border-input-primary bg-bg-input-primary w-auto p-0"
align="start"
>
<Calendar mode="single" selected={date} onSelect={handleDateSelect} />
</PopoverContent>
</Popover>
);
};
9 changes: 1 addition & 8 deletions ui/components/filters/data-filters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export const filterScans = [
// Add more filter categories as needed
];

//Static filters for findings
//Static filters for findings (Cloud Provider removed - now uses ProviderTypeSelector)
export const filterFindings = [
{
key: FilterType.SEVERITY,
Expand All @@ -75,13 +75,6 @@ export const filterFindings = [
values: ["PASS", "FAIL", "MANUAL"],
index: 1,
},
{
key: FilterType.PROVIDER_TYPE,
labelCheckboxGroup: "Cloud Provider",
values: [...PROVIDER_TYPES],
valueLabelMapping: PROVIDER_TYPE_MAPPING,
index: 5,
},
{
key: FilterType.DELTA,
labelCheckboxGroup: "Delta",
Expand Down
13 changes: 8 additions & 5 deletions ui/components/filters/filter-controls.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
"use client";

import { Spacer } from "@heroui/spacer";
import React from "react";

import { FilterOption } from "@/types";

Expand All @@ -23,15 +22,15 @@ export interface FilterControlsProps {
customFilters?: FilterOption[];
}

export const FilterControls: React.FC<FilterControlsProps> = ({
export const FilterControls = ({
search = false,
providers = false,
date = false,
regions = false,
accounts = false,
mutedFindings = false,
customFilters,
}) => {
}: FilterControlsProps) => {
return (
<div className="flex flex-col">
<div className="flex flex-col items-start gap-4 md:flex-row md:items-center">
Expand All @@ -44,8 +43,12 @@ export const FilterControls: React.FC<FilterControlsProps> = ({
{mutedFindings && <CustomCheckboxMutedFindings />}
</div>
</div>
<Spacer y={8} />
{customFilters && <DataTableFilterCustom filters={customFilters} />}
{customFilters && customFilters.length > 0 && (
<>
<Spacer y={8} />
<DataTableFilterCustom filters={customFilters} />
</>
)}
</div>
);
};
Loading