diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index fb981271dc..eb95918429 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to the **Prowler UI** are documented in this file. ### 🚀 Added +- Risk Radar component with category-based severity breakdown to Overview page [(#9532)](https://github.com/prowler-cloud/prowler/pull/9532) - More extensive resource details (partition, details and metadata) within Findings detail and Resources detail view [(#9515)](https://github.com/prowler-cloud/prowler/pull/9515) ### 🔄 Changed @@ -21,6 +22,8 @@ All notable changes to the **Prowler UI** are documented in this file. - Bump Next.js to version 15.5.9 [(#9522)](https://github.com/prowler-cloud/prowler/pull/9522), [(#9513)](https://github.com/prowler-cloud/prowler/pull/9513) - Bump React to version 19.2.2 [(#9534)](https://github.com/prowler-cloud/prowler/pull/9534) +--- + ## [1.15.0] (Prowler v5.15.0) ### 🚀 Added diff --git a/ui/actions/overview/attack-surface/attack-surface.adapter.ts b/ui/actions/overview/attack-surface/attack-surface.adapter.ts index 99a7d6a537..bea25ce11b 100644 --- a/ui/actions/overview/attack-surface/attack-surface.adapter.ts +++ b/ui/actions/overview/attack-surface/attack-surface.adapter.ts @@ -15,7 +15,6 @@ export interface AttackSurfaceItem { label: string; failedFindings: number; totalFindings: number; - checkIds: string[]; } const ATTACK_SURFACE_LABELS: Record = { @@ -39,7 +38,6 @@ function mapAttackSurfaceItem(item: AttackSurfaceOverview): AttackSurfaceItem { label: ATTACK_SURFACE_LABELS[id] || item.id, failedFindings: item.attributes.failed_findings, totalFindings: item.attributes.total_findings, - checkIds: item.attributes.check_ids ?? [], }; } diff --git a/ui/actions/overview/index.ts b/ui/actions/overview/index.ts index 7aa0859127..fa3b434389 100644 --- a/ui/actions/overview/index.ts +++ b/ui/actions/overview/index.ts @@ -4,6 +4,7 @@ export * from "./findings"; export * from "./providers"; export * from "./regions"; export * from "./risk-plot"; +export * from "./risk-radar"; export * from "./services"; export * from "./severity-trends"; export * from "./threat-score"; diff --git a/ui/actions/overview/risk-radar/index.ts b/ui/actions/overview/risk-radar/index.ts new file mode 100644 index 0000000000..bbe9f780ca --- /dev/null +++ b/ui/actions/overview/risk-radar/index.ts @@ -0,0 +1,3 @@ +export * from "./risk-radar"; +export * from "./risk-radar.adapter"; +export * from "./types"; diff --git a/ui/actions/overview/risk-radar/risk-radar.adapter.ts b/ui/actions/overview/risk-radar/risk-radar.adapter.ts new file mode 100644 index 0000000000..4de4e259f5 --- /dev/null +++ b/ui/actions/overview/risk-radar/risk-radar.adapter.ts @@ -0,0 +1,110 @@ +import type { RadarDataPoint } from "@/components/graphs/types"; + +import { CategoryOverview, CategoryOverviewResponse } from "./types"; + +// Category IDs from the API +const CATEGORY_IDS = { + E3: "e3", + E5: "e5", + ENCRYPTION: "encryption", + FORENSICS_READY: "forensics-ready", + IAM: "iam", + INTERNET_EXPOSED: "internet-exposed", + LOGGING: "logging", + NETWORK: "network", + PUBLICLY_ACCESSIBLE: "publicly-accessible", + SECRETS: "secrets", + STORAGE: "storage", + THREAT_DETECTION: "threat-detection", + TRUSTBOUNDARIES: "trustboundaries", + UNUSED: "unused", +} as const; + +export type CategoryId = (typeof CATEGORY_IDS)[keyof typeof CATEGORY_IDS]; + +// Human-readable labels for category IDs +const CATEGORY_LABELS: Record = { + [CATEGORY_IDS.E3]: "E3", + [CATEGORY_IDS.E5]: "E5", + [CATEGORY_IDS.ENCRYPTION]: "Encryption", + [CATEGORY_IDS.FORENSICS_READY]: "Forensics Ready", + [CATEGORY_IDS.IAM]: "IAM", + [CATEGORY_IDS.INTERNET_EXPOSED]: "Internet Exposed", + [CATEGORY_IDS.LOGGING]: "Logging", + [CATEGORY_IDS.NETWORK]: "Network", + [CATEGORY_IDS.PUBLICLY_ACCESSIBLE]: "Publicly Accessible", + [CATEGORY_IDS.SECRETS]: "Secrets", + [CATEGORY_IDS.STORAGE]: "Storage", + [CATEGORY_IDS.THREAT_DETECTION]: "Threat Detection", + [CATEGORY_IDS.TRUSTBOUNDARIES]: "Trust Boundaries", + [CATEGORY_IDS.UNUSED]: "Unused", +}; + +/** + * Converts a category ID to a human-readable label. + * Falls back to capitalizing the ID if not found in the mapping. + */ +function getCategoryLabel(id: string): string { + if (CATEGORY_LABELS[id]) { + return CATEGORY_LABELS[id]; + } + // Fallback: capitalize and replace hyphens with spaces + return id + .split("-") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); +} + +/** + * Calculates the percentage of new failed findings relative to total failed findings. + */ +function calculateChangePercentage( + newFailedFindings: number, + failedFindings: number, +): number { + if (failedFindings === 0) return 0; + return Math.round((newFailedFindings / failedFindings) * 100); +} + +/** + * Maps a single category overview item to a RadarDataPoint. + */ +function mapCategoryToRadarPoint(item: CategoryOverview): RadarDataPoint { + const { id, attributes } = item; + const { failed_findings, new_failed_findings, severity } = attributes; + + return { + category: getCategoryLabel(id), + categoryId: id, + value: failed_findings, + change: calculateChangePercentage(new_failed_findings, failed_findings), + severityData: [ + { name: "Critical", value: severity.critical }, + { name: "High", value: severity.high }, + { name: "Medium", value: severity.medium }, + { name: "Low", value: severity.low }, + { name: "Info", value: severity.informational }, + ], + }; +} + +/** + * Adapts the category overview API response to RadarDataPoint[] format. + * Filters out categories with no failed findings. + * + * @param response - The category overview API response + * @returns An array of RadarDataPoint objects for the radar chart + */ +export function adaptCategoryOverviewToRadarData( + response: CategoryOverviewResponse | undefined, +): RadarDataPoint[] { + if (!response?.data || response.data.length === 0) { + return []; + } + + // Map all categories to radar points, filtering out those with no failed findings + return response.data + .filter((item) => item.attributes.failed_findings > 0) + .map(mapCategoryToRadarPoint) + .sort((a, b) => b.value - a.value); // Sort by failed findings descending +} diff --git a/ui/actions/overview/risk-radar/risk-radar.ts b/ui/actions/overview/risk-radar/risk-radar.ts new file mode 100644 index 0000000000..44c04ad87e --- /dev/null +++ b/ui/actions/overview/risk-radar/risk-radar.ts @@ -0,0 +1,34 @@ +"use server"; + +import { apiBaseUrl, getAuthHeaders } from "@/lib"; +import { handleApiResponse } from "@/lib/server-actions-helper"; + +import { CategoryOverviewResponse } from "./types"; + +export const getCategoryOverview = async ({ + filters = {}, +}: { + filters?: Record; +} = {}): Promise => { + const headers = await getAuthHeaders({ contentType: false }); + + const url = new URL(`${apiBaseUrl}/overviews/categories`); + + // Handle multiple filters + Object.entries(filters).forEach(([key, value]) => { + if (key !== "filter[search]" && value !== undefined) { + url.searchParams.append(key, String(value)); + } + }); + + try { + const response = await fetch(url.toString(), { + headers, + }); + + return handleApiResponse(response); + } catch (error) { + console.error("Error fetching category overview:", error); + return undefined; + } +}; diff --git a/ui/actions/overview/risk-radar/types/index.ts b/ui/actions/overview/risk-radar/types/index.ts new file mode 100644 index 0000000000..45edc86c3d --- /dev/null +++ b/ui/actions/overview/risk-radar/types/index.ts @@ -0,0 +1 @@ +export * from "./risk-radar.types"; diff --git a/ui/actions/overview/risk-radar/types/risk-radar.types.ts b/ui/actions/overview/risk-radar/types/risk-radar.types.ts new file mode 100644 index 0000000000..6be313de1b --- /dev/null +++ b/ui/actions/overview/risk-radar/types/risk-radar.types.ts @@ -0,0 +1,32 @@ +// Category Overview Types +// Corresponds to the /overviews/categories endpoint + +interface OverviewResponseMeta { + version: string; +} + +export interface CategorySeverity { + informational: number; + low: number; + medium: number; + high: number; + critical: number; +} + +export interface CategoryOverviewAttributes { + total_findings: number; + failed_findings: number; + new_failed_findings: number; + severity: CategorySeverity; +} + +export interface CategoryOverview { + type: "category-overviews"; + id: string; + attributes: CategoryOverviewAttributes; +} + +export interface CategoryOverviewResponse { + data: CategoryOverview[]; + meta: OverviewResponseMeta; +} diff --git a/ui/actions/overview/severity-trends/severity-trends.ts b/ui/actions/overview/severity-trends/severity-trends.ts index 7e48ec513c..70701a41a8 100644 --- a/ui/actions/overview/severity-trends/severity-trends.ts +++ b/ui/actions/overview/severity-trends/severity-trends.ts @@ -3,7 +3,7 @@ import { getDateFromForTimeRange, type TimeRange, -} from "@/app/(prowler)/_new-overview/severity-over-time/_constants/time-range.constants"; +} from "@/app/(prowler)/_overview/severity-over-time/_constants/time-range.constants"; import { apiBaseUrl, getAuthHeaders } from "@/lib"; import { handleApiResponse } from "@/lib/server-actions-helper"; diff --git a/ui/app/(prowler)/_new-overview/graphs-tabs/risk-radar-view/risk-radar-view.ssr.tsx b/ui/app/(prowler)/_new-overview/graphs-tabs/risk-radar-view/risk-radar-view.ssr.tsx deleted file mode 100644 index ca4a23695c..0000000000 --- a/ui/app/(prowler)/_new-overview/graphs-tabs/risk-radar-view/risk-radar-view.ssr.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import type { RadarDataPoint } from "@/components/graphs/types"; - -import { RiskRadarViewClient } from "./risk-radar-view-client"; - -// Mock data - replace with actual API call -const mockRadarData: RadarDataPoint[] = [ - { - category: "Amazon Kinesis", - value: 45, - change: 2, - severityData: [ - { name: "Critical", value: 32 }, - { name: "High", value: 65 }, - { name: "Medium", value: 18 }, - { name: "Low", value: 54 }, - { name: "Info", value: 1 }, - ], - }, - { - category: "Amazon MQ", - value: 38, - change: -1, - severityData: [ - { name: "Critical", value: 28 }, - { name: "High", value: 58 }, - { name: "Medium", value: 16 }, - { name: "Low", value: 48 }, - { name: "Info", value: 2 }, - ], - }, - { - category: "AWS Lambda", - value: 52, - change: 5, - severityData: [ - { name: "Critical", value: 40 }, - { name: "High", value: 72 }, - { name: "Medium", value: 20 }, - { name: "Low", value: 60 }, - { name: "Info", value: 3 }, - ], - }, - { - category: "Amazon RDS", - value: 41, - change: 3, - severityData: [ - { name: "Critical", value: 30 }, - { name: "High", value: 60 }, - { name: "Medium", value: 17 }, - { name: "Low", value: 50 }, - { name: "Info", value: 1 }, - ], - }, - { - category: "Amazon S3", - value: 48, - change: -2, - severityData: [ - { name: "Critical", value: 36 }, - { name: "High", value: 68 }, - { name: "Medium", value: 19 }, - { name: "Low", value: 56 }, - { name: "Info", value: 2 }, - ], - }, - { - category: "Amazon VPC", - value: 55, - change: 4, - severityData: [ - { name: "Critical", value: 42 }, - { name: "High", value: 75 }, - { name: "Medium", value: 21 }, - { name: "Low", value: 62 }, - { name: "Info", value: 3 }, - ], - }, -]; - -// Helper to simulate loading delay -const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); - -export async function RiskRadarViewSSR() { - // TODO: Call server action to fetch radar chart data - await delay(3000); // Simulating server action fetch time - - return ; -} diff --git a/ui/app/(prowler)/_new-overview/page.tsx b/ui/app/(prowler)/_new-overview/page.tsx deleted file mode 100644 index a9886782c0..0000000000 --- a/ui/app/(prowler)/_new-overview/page.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import { Suspense } from "react"; - -import { getProviders } from "@/actions/providers"; -import { ContentLayout } from "@/components/ui"; -import { SearchParamsProps } from "@/types"; - -import { AccountsSelector } from "./_components/accounts-selector"; -import { ProviderTypeSelector } from "./_components/provider-type-selector"; -import { CheckFindingsSSR } from "./check-findings"; -import { GraphsTabsWrapper } from "./graphs-tabs/graphs-tabs-wrapper"; -import { RiskSeverityChartSkeleton } from "./risk-severity"; -import { RiskSeverityChartSSR } from "./risk-severity/risk-severity-chart.ssr"; -import { - FindingSeverityOverTimeSkeleton, - FindingSeverityOverTimeSSR, -} from "./severity-over-time/finding-severity-over-time.ssr"; -import { StatusChartSkeleton } from "./status-chart"; -import { ThreatScoreSkeleton, ThreatScoreSSR } from "./threat-score"; -import { - ComplianceWatchlistSSR, - ServiceWatchlistSSR, - WatchlistCardSkeleton, -} from "./watchlist"; - -export default async function NewOverviewPage({ - searchParams, -}: { - searchParams: Promise; -}) { - //if cloud env throw a 500 err - if (process.env.NEXT_PUBLIC_IS_CLOUD_ENV === "true") { - throw new Error("500"); - } - - const resolvedSearchParams = await searchParams; - const providersData = await getProviders({ page: 1, pageSize: 200 }); - - return ( - -
- - -
- -
- }> - - - - }> - - - - }> - - -
-
- }> - - - }> - - -
-
- }> - - - -
-
- ); -} diff --git a/ui/app/(prowler)/_new-overview/risk-severity/risk-severity-chart.ssr.tsx b/ui/app/(prowler)/_new-overview/risk-severity/risk-severity-chart.ssr.tsx deleted file mode 100644 index 16c4b75cbb..0000000000 --- a/ui/app/(prowler)/_new-overview/risk-severity/risk-severity-chart.ssr.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { pickFilterParams } from "../_lib/filter-params"; -import { SSRComponentProps } from "../_types"; -import { RiskSeverityChartDetailSSR } from "./risk-severity-chart-detail.ssr"; - -export const RiskSeverityChartSSR = async ({ - searchParams, -}: SSRComponentProps) => { - const filters = pickFilterParams(searchParams); - - return ; -}; diff --git a/ui/app/(prowler)/_new-overview/severity-over-time/finding-severity-over-time-detail.ssr.tsx b/ui/app/(prowler)/_new-overview/severity-over-time/finding-severity-over-time-detail.ssr.tsx deleted file mode 100644 index a6d99a0109..0000000000 --- a/ui/app/(prowler)/_new-overview/severity-over-time/finding-severity-over-time-detail.ssr.tsx +++ /dev/null @@ -1,35 +0,0 @@ -import { getFindingsSeverityTrends } from "@/actions/overview/severity-trends"; - -import { pickFilterParams } from "../_lib/filter-params"; -import { SSRComponentProps } from "../_types"; -import { FindingSeverityOverTime } from "./_components/finding-severity-over-time"; - -const EmptyState = ({ message }: { message: string }) => ( -
-

{message}

-
-); - -export const FindingSeverityOverTimeDetailSSR = async ({ - searchParams, -}: SSRComponentProps) => { - const filters = pickFilterParams(searchParams); - const result = await getFindingsSeverityTrends({ filters }); - - if (result.status === "error") { - return ; - } - - if (result.status === "empty") { - return ; - } - - return ( -
-

- Finding Severity Over Time -

- -
- ); -}; diff --git a/ui/app/(prowler)/_new-overview/_components/accounts-selector.tsx b/ui/app/(prowler)/_overview/_components/accounts-selector.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/_components/accounts-selector.tsx rename to ui/app/(prowler)/_overview/_components/accounts-selector.tsx diff --git a/ui/app/(prowler)/_new-overview/_components/provider-type-selector.tsx b/ui/app/(prowler)/_overview/_components/provider-type-selector.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/_components/provider-type-selector.tsx rename to ui/app/(prowler)/_overview/_components/provider-type-selector.tsx diff --git a/ui/app/(prowler)/_new-overview/_lib/filter-params.ts b/ui/app/(prowler)/_overview/_lib/filter-params.ts similarity index 100% rename from ui/app/(prowler)/_new-overview/_lib/filter-params.ts rename to ui/app/(prowler)/_overview/_lib/filter-params.ts diff --git a/ui/app/(prowler)/_new-overview/_types.ts b/ui/app/(prowler)/_overview/_types.ts similarity index 100% rename from ui/app/(prowler)/_new-overview/_types.ts rename to ui/app/(prowler)/_overview/_types.ts diff --git a/ui/app/(prowler)/_new-overview/attack-surface/_components/attack-surface-card-item.tsx b/ui/app/(prowler)/_overview/attack-surface/_components/attack-surface-card-item.tsx similarity index 75% rename from ui/app/(prowler)/_new-overview/attack-surface/_components/attack-surface-card-item.tsx rename to ui/app/(prowler)/_overview/attack-surface/_components/attack-surface-card-item.tsx index fd9b0d9ea5..9a26763c3e 100644 --- a/ui/app/(prowler)/_new-overview/attack-surface/_components/attack-surface-card-item.tsx +++ b/ui/app/(prowler)/_overview/attack-surface/_components/attack-surface-card-item.tsx @@ -13,16 +13,12 @@ export function AttackSurfaceCardItem({ item, filters = {}, }: AttackSurfaceCardItemProps) { - const hasCheckIds = item.checkIds.length > 0; - // Build URL with current filters + attack surface specific filters const buildFindingsUrl = () => { - if (!hasCheckIds) return null; - const params = new URLSearchParams(); - // Add attack surface specific filters - params.set("filter[check_id__in]", item.checkIds.join(",")); + // Add attack surface category filter + params.set("filter[category__in]", item.id); params.set("filter[status__in]", "FAIL"); params.set("filter[muted]", "false"); @@ -44,11 +40,8 @@ export function AttackSurfaceCardItem({ const hasFindings = item.failedFindings > 0; const getCardStyles = () => { - if (!hasCheckIds) { - return "opacity-50 cursor-not-allowed"; - } if (hasFindings) { - return "cursor-pointer border-rose-500/40 shadow-[0_0_12px_rgba(244,63,94,0.2)] transition-all hover:border-rose-500/60 hover:shadow-[0_0_16px_rgba(244,63,94,0.3)]"; + return "cursor-pointer border-rose-500/40 shadow-rose-500/20 shadow-lg transition-all hover:border-rose-500/60 hover:shadow-rose-500/30"; } return "cursor-pointer transition-colors hover:bg-accent"; }; @@ -74,13 +67,9 @@ export function AttackSurfaceCardItem({ ); - if (findingsUrl) { - return ( - - {cardContent} - - ); - } - - return cardContent; + return ( + + {cardContent} + + ); } diff --git a/ui/app/(prowler)/_new-overview/attack-surface/_components/attack-surface.tsx b/ui/app/(prowler)/_overview/attack-surface/_components/attack-surface.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/attack-surface/_components/attack-surface.tsx rename to ui/app/(prowler)/_overview/attack-surface/_components/attack-surface.tsx diff --git a/ui/app/(prowler)/_new-overview/attack-surface/attack-surface-skeleton.tsx b/ui/app/(prowler)/_overview/attack-surface/attack-surface-skeleton.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/attack-surface/attack-surface-skeleton.tsx rename to ui/app/(prowler)/_overview/attack-surface/attack-surface-skeleton.tsx diff --git a/ui/app/(prowler)/_new-overview/attack-surface/attack-surface.ssr.tsx b/ui/app/(prowler)/_overview/attack-surface/attack-surface.ssr.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/attack-surface/attack-surface.ssr.tsx rename to ui/app/(prowler)/_overview/attack-surface/attack-surface.ssr.tsx diff --git a/ui/app/(prowler)/_new-overview/attack-surface/index.ts b/ui/app/(prowler)/_overview/attack-surface/index.ts similarity index 100% rename from ui/app/(prowler)/_new-overview/attack-surface/index.ts rename to ui/app/(prowler)/_overview/attack-surface/index.ts diff --git a/ui/app/(prowler)/_new-overview/check-findings/check-findings.ssr.tsx b/ui/app/(prowler)/_overview/check-findings/check-findings.ssr.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/check-findings/check-findings.ssr.tsx rename to ui/app/(prowler)/_overview/check-findings/check-findings.ssr.tsx diff --git a/ui/app/(prowler)/_new-overview/check-findings/index.ts b/ui/app/(prowler)/_overview/check-findings/index.ts similarity index 100% rename from ui/app/(prowler)/_new-overview/check-findings/index.ts rename to ui/app/(prowler)/_overview/check-findings/index.ts diff --git a/ui/app/(prowler)/_new-overview/graphs-tabs/_components/graphs-tabs-client.tsx b/ui/app/(prowler)/_overview/graphs-tabs/_components/graphs-tabs-client.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/graphs-tabs/_components/graphs-tabs-client.tsx rename to ui/app/(prowler)/_overview/graphs-tabs/_components/graphs-tabs-client.tsx diff --git a/ui/app/(prowler)/_new-overview/graphs-tabs/_config/graphs-tabs-config.ts b/ui/app/(prowler)/_overview/graphs-tabs/_config/graphs-tabs-config.ts similarity index 72% rename from ui/app/(prowler)/_new-overview/graphs-tabs/_config/graphs-tabs-config.ts rename to ui/app/(prowler)/_overview/graphs-tabs/_config/graphs-tabs-config.ts index 56594efb4c..826a93fea7 100644 --- a/ui/app/(prowler)/_new-overview/graphs-tabs/_config/graphs-tabs-config.ts +++ b/ui/app/(prowler)/_overview/graphs-tabs/_config/graphs-tabs-config.ts @@ -3,23 +3,22 @@ export const GRAPH_TABS = [ id: "findings", label: "New Findings", }, - { - id: "risk-pipeline", - label: "Risk Pipeline", - }, { id: "threat-map", label: "Threat Map", }, + { + id: "risk-radar", + label: "Risk Radar", + }, + { + id: "risk-pipeline", + label: "Risk Pipeline", + }, { id: "risk-plot", label: "Risk Plot", }, - // TODO: Uncomment when ready to enable other tabs - // { - // id: "risk-radar", - // label: "Risk Radar", - // }, ] as const; export type TabId = (typeof GRAPH_TABS)[number]["id"]; diff --git a/ui/app/(prowler)/_new-overview/graphs-tabs/findings-view/findings-view.ssr.tsx b/ui/app/(prowler)/_overview/graphs-tabs/findings-view/findings-view.ssr.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/graphs-tabs/findings-view/findings-view.ssr.tsx rename to ui/app/(prowler)/_overview/graphs-tabs/findings-view/findings-view.ssr.tsx diff --git a/ui/app/(prowler)/_new-overview/graphs-tabs/findings-view/index.ts b/ui/app/(prowler)/_overview/graphs-tabs/findings-view/index.ts similarity index 100% rename from ui/app/(prowler)/_new-overview/graphs-tabs/findings-view/index.ts rename to ui/app/(prowler)/_overview/graphs-tabs/findings-view/index.ts diff --git a/ui/app/(prowler)/_new-overview/graphs-tabs/graphs-tabs-wrapper.tsx b/ui/app/(prowler)/_overview/graphs-tabs/graphs-tabs-wrapper.tsx similarity index 88% rename from ui/app/(prowler)/_new-overview/graphs-tabs/graphs-tabs-wrapper.tsx rename to ui/app/(prowler)/_overview/graphs-tabs/graphs-tabs-wrapper.tsx index 4aafa4ce40..c21491e37f 100644 --- a/ui/app/(prowler)/_new-overview/graphs-tabs/graphs-tabs-wrapper.tsx +++ b/ui/app/(prowler)/_overview/graphs-tabs/graphs-tabs-wrapper.tsx @@ -8,9 +8,8 @@ import { GRAPH_TABS, type TabId } from "./_config/graphs-tabs-config"; import { FindingsViewSSR } from "./findings-view"; import { RiskPipelineViewSSR } from "./risk-pipeline-view/risk-pipeline-view.ssr"; import { RiskPlotSSR } from "./risk-plot/risk-plot.ssr"; +import { RiskRadarViewSSR } from "./risk-radar-view/risk-radar-view.ssr"; import { ThreatMapViewSSR } from "./threat-map-view/threat-map-view.ssr"; -// TODO: Uncomment when ready to enable other tabs -// import { RiskRadarViewSSR } from "./risk-radar-view/risk-radar-view.ssr"; const LoadingFallback = () => (
@@ -26,8 +25,7 @@ const GRAPH_COMPONENTS: Record = { "risk-pipeline": RiskPipelineViewSSR as GraphComponent, "threat-map": ThreatMapViewSSR as GraphComponent, "risk-plot": RiskPlotSSR as GraphComponent, - // TODO: Uncomment when ready to enable other tabs - // "risk-radar": RiskRadarViewSSR as GraphComponent, + "risk-radar": RiskRadarViewSSR as GraphComponent, }; interface GraphsTabsWrapperProps { diff --git a/ui/app/(prowler)/_new-overview/graphs-tabs/risk-pipeline-view/index.ts b/ui/app/(prowler)/_overview/graphs-tabs/risk-pipeline-view/index.ts similarity index 100% rename from ui/app/(prowler)/_new-overview/graphs-tabs/risk-pipeline-view/index.ts rename to ui/app/(prowler)/_overview/graphs-tabs/risk-pipeline-view/index.ts diff --git a/ui/app/(prowler)/_new-overview/graphs-tabs/risk-pipeline-view/risk-pipeline-view-skeleton.tsx b/ui/app/(prowler)/_overview/graphs-tabs/risk-pipeline-view/risk-pipeline-view-skeleton.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/graphs-tabs/risk-pipeline-view/risk-pipeline-view-skeleton.tsx rename to ui/app/(prowler)/_overview/graphs-tabs/risk-pipeline-view/risk-pipeline-view-skeleton.tsx diff --git a/ui/app/(prowler)/_new-overview/graphs-tabs/risk-pipeline-view/risk-pipeline-view.ssr.tsx b/ui/app/(prowler)/_overview/graphs-tabs/risk-pipeline-view/risk-pipeline-view.ssr.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/graphs-tabs/risk-pipeline-view/risk-pipeline-view.ssr.tsx rename to ui/app/(prowler)/_overview/graphs-tabs/risk-pipeline-view/risk-pipeline-view.ssr.tsx diff --git a/ui/app/(prowler)/_new-overview/graphs-tabs/risk-plot/risk-plot-client.tsx b/ui/app/(prowler)/_overview/graphs-tabs/risk-plot/risk-plot-client.tsx similarity index 96% rename from ui/app/(prowler)/_new-overview/graphs-tabs/risk-plot/risk-plot-client.tsx rename to ui/app/(prowler)/_overview/graphs-tabs/risk-plot/risk-plot-client.tsx index 3b332157af..80baed4edf 100644 --- a/ui/app/(prowler)/_new-overview/graphs-tabs/risk-plot/risk-plot-client.tsx +++ b/ui/app/(prowler)/_overview/graphs-tabs/risk-plot/risk-plot-client.tsx @@ -34,7 +34,7 @@ import type { BarDataPoint } from "@/components/graphs/types"; import { mapProviderFiltersForFindings } from "@/lib/provider-helpers"; import { SEVERITY_FILTER_MAP } from "@/types/severities"; -// ThreatScore colors (0-100 scale, higher = better) +// Threat Score colors (0-100 scale, higher = better) const THREAT_COLORS = { DANGER: "var(--bg-fail-primary)", // 0-30 WARNING: "var(--bg-warning-primary)", // 31-60 @@ -100,7 +100,7 @@ const CustomTooltip = ({ active, payload }: TooltipProps) => {

{x}%{" "} - Prowler ThreatScore + Threat Score

@@ -268,8 +268,8 @@ export function RiskPlotClient({ data }: RiskPlotClientProps) { Risk Plot

- Prowler ThreatScore is severity-weighted, not quantity-based. - Higher severity findings have greater impact on the score. + Threat Score is severity-weighted, not quantity-based. Higher + severity findings have greater impact on the score.

@@ -287,9 +287,9 @@ export function RiskPlotClient({ data }: RiskPlotClientProps) {

- Prowler ThreatScore: {selectedPoint.x}% | Fail Findings:{" "} + Threat Score: {selectedPoint.x}% | Fail Findings:{" "} {selectedPoint.y}

diff --git a/ui/app/(prowler)/_new-overview/graphs-tabs/risk-plot/risk-plot.ssr.tsx b/ui/app/(prowler)/_overview/graphs-tabs/risk-plot/risk-plot.ssr.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/graphs-tabs/risk-plot/risk-plot.ssr.tsx rename to ui/app/(prowler)/_overview/graphs-tabs/risk-plot/risk-plot.ssr.tsx diff --git a/ui/app/(prowler)/_new-overview/graphs-tabs/risk-radar-view/risk-radar-view-client.tsx b/ui/app/(prowler)/_overview/graphs-tabs/risk-radar-view/risk-radar-view-client.tsx similarity index 66% rename from ui/app/(prowler)/_new-overview/graphs-tabs/risk-radar-view/risk-radar-view-client.tsx rename to ui/app/(prowler)/_overview/graphs-tabs/risk-radar-view/risk-radar-view-client.tsx index d330f46551..2c8cb8c989 100644 --- a/ui/app/(prowler)/_new-overview/graphs-tabs/risk-radar-view/risk-radar-view-client.tsx +++ b/ui/app/(prowler)/_overview/graphs-tabs/risk-radar-view/risk-radar-view-client.tsx @@ -1,17 +1,21 @@ "use client"; +import { useRouter, useSearchParams } from "next/navigation"; import { useState } from "react"; import { HorizontalBarChart } from "@/components/graphs/horizontal-bar-chart"; import { RadarChart } from "@/components/graphs/radar-chart"; -import type { RadarDataPoint } from "@/components/graphs/types"; +import type { BarDataPoint, RadarDataPoint } from "@/components/graphs/types"; import { Card } from "@/components/shadcn/card/card"; +import { SEVERITY_FILTER_MAP } from "@/types/severities"; interface RiskRadarViewClientProps { data: RadarDataPoint[]; } export function RiskRadarViewClient({ data }: RiskRadarViewClientProps) { + const router = useRouter(); + const searchParams = useSearchParams(); const [selectedPoint, setSelectedPoint] = useState( null, ); @@ -20,6 +24,31 @@ export function RiskRadarViewClient({ data }: RiskRadarViewClientProps) { setSelectedPoint(point); }; + const handleBarClick = (dataPoint: BarDataPoint) => { + if (!selectedPoint) return; + + // Build the URL with current filters + const params = new URLSearchParams(searchParams.toString()); + + // Add severity filter + const severity = SEVERITY_FILTER_MAP[dataPoint.name]; + if (severity) { + params.set("filter[severity__in]", severity); + } + + // Add category filter for the selected point + params.set("filter[category__in]", selectedPoint.categoryId); + + // Add exclude muted findings filter + params.set("filter[muted]", "false"); + + // Filter by FAIL findings + params.set("filter[status__in]", "FAIL"); + + // Navigate to findings page + router.push(`/findings?${params.toString()}`); + }; + return (
@@ -55,7 +84,10 @@ export function RiskRadarViewClient({ data }: RiskRadarViewClientProps) { {selectedPoint.value} Total Findings

- +
) : (
diff --git a/ui/app/(prowler)/_overview/graphs-tabs/risk-radar-view/risk-radar-view.ssr.tsx b/ui/app/(prowler)/_overview/graphs-tabs/risk-radar-view/risk-radar-view.ssr.tsx new file mode 100644 index 0000000000..932251e08a --- /dev/null +++ b/ui/app/(prowler)/_overview/graphs-tabs/risk-radar-view/risk-radar-view.ssr.tsx @@ -0,0 +1,40 @@ +import { Info } from "lucide-react"; + +import { + adaptCategoryOverviewToRadarData, + getCategoryOverview, +} from "@/actions/overview/risk-radar"; +import { SearchParamsProps } from "@/types"; + +import { pickFilterParams } from "../../_lib/filter-params"; +import { RiskRadarViewClient } from "./risk-radar-view-client"; + +export async function RiskRadarViewSSR({ + searchParams, +}: { + searchParams: SearchParamsProps; +}) { + const filters = pickFilterParams(searchParams); + + // Fetch category overview data + const categoryResponse = await getCategoryOverview({ filters }); + + // Transform to radar chart format + const radarData = adaptCategoryOverviewToRadarData(categoryResponse); + + // No data available + if (radarData.length === 0) { + return ( +
+
+ +

+ No category data available for the selected filters +

+
+
+ ); + } + + return ; +} diff --git a/ui/app/(prowler)/_new-overview/graphs-tabs/threat-map-view/threat-map-view.ssr.tsx b/ui/app/(prowler)/_overview/graphs-tabs/threat-map-view/threat-map-view.ssr.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/graphs-tabs/threat-map-view/threat-map-view.ssr.tsx rename to ui/app/(prowler)/_overview/graphs-tabs/threat-map-view/threat-map-view.ssr.tsx diff --git a/ui/app/(prowler)/_new-overview/risk-severity/_components/risk-severity-chart.skeleton.tsx b/ui/app/(prowler)/_overview/risk-severity/_components/risk-severity-chart.skeleton.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/risk-severity/_components/risk-severity-chart.skeleton.tsx rename to ui/app/(prowler)/_overview/risk-severity/_components/risk-severity-chart.skeleton.tsx diff --git a/ui/app/(prowler)/_new-overview/risk-severity/_components/risk-severity-chart.tsx b/ui/app/(prowler)/_overview/risk-severity/_components/risk-severity-chart.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/risk-severity/_components/risk-severity-chart.tsx rename to ui/app/(prowler)/_overview/risk-severity/_components/risk-severity-chart.tsx diff --git a/ui/app/(prowler)/_new-overview/risk-severity/index.ts b/ui/app/(prowler)/_overview/risk-severity/index.ts similarity index 100% rename from ui/app/(prowler)/_new-overview/risk-severity/index.ts rename to ui/app/(prowler)/_overview/risk-severity/index.ts diff --git a/ui/app/(prowler)/_new-overview/risk-severity/risk-severity-chart-detail.ssr.tsx b/ui/app/(prowler)/_overview/risk-severity/risk-severity-chart.ssr.tsx similarity index 95% rename from ui/app/(prowler)/_new-overview/risk-severity/risk-severity-chart-detail.ssr.tsx rename to ui/app/(prowler)/_overview/risk-severity/risk-severity-chart.ssr.tsx index 7091232a80..c825748169 100644 --- a/ui/app/(prowler)/_new-overview/risk-severity/risk-severity-chart-detail.ssr.tsx +++ b/ui/app/(prowler)/_overview/risk-severity/risk-severity-chart.ssr.tsx @@ -4,7 +4,7 @@ import { pickFilterParams } from "../_lib/filter-params"; import { SSRComponentProps } from "../_types"; import { RiskSeverityChart } from "./_components/risk-severity-chart"; -export const RiskSeverityChartDetailSSR = async ({ +export const RiskSeverityChartSSR = async ({ searchParams, }: SSRComponentProps) => { const filters = pickFilterParams(searchParams); diff --git a/ui/app/(prowler)/_new-overview/severity-over-time/_components/finding-severity-over-time.skeleton.tsx b/ui/app/(prowler)/_overview/severity-over-time/_components/finding-severity-over-time.skeleton.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/severity-over-time/_components/finding-severity-over-time.skeleton.tsx rename to ui/app/(prowler)/_overview/severity-over-time/_components/finding-severity-over-time.skeleton.tsx diff --git a/ui/app/(prowler)/_new-overview/severity-over-time/_components/finding-severity-over-time.tsx b/ui/app/(prowler)/_overview/severity-over-time/_components/finding-severity-over-time.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/severity-over-time/_components/finding-severity-over-time.tsx rename to ui/app/(prowler)/_overview/severity-over-time/_components/finding-severity-over-time.tsx diff --git a/ui/app/(prowler)/_new-overview/severity-over-time/_components/time-range-selector.tsx b/ui/app/(prowler)/_overview/severity-over-time/_components/time-range-selector.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/severity-over-time/_components/time-range-selector.tsx rename to ui/app/(prowler)/_overview/severity-over-time/_components/time-range-selector.tsx diff --git a/ui/app/(prowler)/_new-overview/severity-over-time/_constants/index.ts b/ui/app/(prowler)/_overview/severity-over-time/_constants/index.ts similarity index 100% rename from ui/app/(prowler)/_new-overview/severity-over-time/_constants/index.ts rename to ui/app/(prowler)/_overview/severity-over-time/_constants/index.ts diff --git a/ui/app/(prowler)/_new-overview/severity-over-time/_constants/time-range.constants.ts b/ui/app/(prowler)/_overview/severity-over-time/_constants/time-range.constants.ts similarity index 100% rename from ui/app/(prowler)/_new-overview/severity-over-time/_constants/time-range.constants.ts rename to ui/app/(prowler)/_overview/severity-over-time/_constants/time-range.constants.ts diff --git a/ui/app/(prowler)/_new-overview/severity-over-time/finding-severity-over-time.ssr.tsx b/ui/app/(prowler)/_overview/severity-over-time/finding-severity-over-time.ssr.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/severity-over-time/finding-severity-over-time.ssr.tsx rename to ui/app/(prowler)/_overview/severity-over-time/finding-severity-over-time.ssr.tsx diff --git a/ui/app/(prowler)/_new-overview/severity-over-time/index.ts b/ui/app/(prowler)/_overview/severity-over-time/index.ts similarity index 100% rename from ui/app/(prowler)/_new-overview/severity-over-time/index.ts rename to ui/app/(prowler)/_overview/severity-over-time/index.ts diff --git a/ui/app/(prowler)/_new-overview/status-chart/_components/status-chart.skeleton.tsx b/ui/app/(prowler)/_overview/status-chart/_components/status-chart.skeleton.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/status-chart/_components/status-chart.skeleton.tsx rename to ui/app/(prowler)/_overview/status-chart/_components/status-chart.skeleton.tsx diff --git a/ui/app/(prowler)/_new-overview/status-chart/_components/status-chart.tsx b/ui/app/(prowler)/_overview/status-chart/_components/status-chart.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/status-chart/_components/status-chart.tsx rename to ui/app/(prowler)/_overview/status-chart/_components/status-chart.tsx diff --git a/ui/app/(prowler)/_new-overview/status-chart/index.ts b/ui/app/(prowler)/_overview/status-chart/index.ts similarity index 100% rename from ui/app/(prowler)/_new-overview/status-chart/index.ts rename to ui/app/(prowler)/_overview/status-chart/index.ts diff --git a/ui/app/(prowler)/_new-overview/threat-score/_components/threat-score.skeleton.tsx b/ui/app/(prowler)/_overview/threat-score/_components/threat-score.skeleton.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/threat-score/_components/threat-score.skeleton.tsx rename to ui/app/(prowler)/_overview/threat-score/_components/threat-score.skeleton.tsx diff --git a/ui/app/(prowler)/_new-overview/threat-score/_components/threat-score.tsx b/ui/app/(prowler)/_overview/threat-score/_components/threat-score.tsx similarity index 97% rename from ui/app/(prowler)/_new-overview/threat-score/_components/threat-score.tsx rename to ui/app/(prowler)/_overview/threat-score/_components/threat-score.tsx index 1487c04ca8..fe152d88f0 100644 --- a/ui/app/(prowler)/_new-overview/threat-score/_components/threat-score.tsx +++ b/ui/app/(prowler)/_overview/threat-score/_components/threat-score.tsx @@ -116,7 +116,7 @@ export function ThreatScore({ className="flex min-h-[372px] w-full flex-col justify-between lg:max-w-[312px]" > - Prowler ThreatScore + Prowler Threat Score @@ -165,7 +165,7 @@ export function ThreatScore({ className="mt-0.5 min-h-4 min-w-4 shrink-0" />

- Prowler ThreatScore has{" "} + Threat score has{" "} {scoreDelta > 0 ? "improved" : "decreased"} by{" "} {Math.abs(scoreDelta)}%

@@ -194,7 +194,7 @@ export function ThreatScore({ className="items-center justify-center" >

- Prowler ThreatScore Data Unavailable + Threat Score Data Unavailable

)} diff --git a/ui/app/(prowler)/_new-overview/threat-score/index.ts b/ui/app/(prowler)/_overview/threat-score/index.ts similarity index 100% rename from ui/app/(prowler)/_new-overview/threat-score/index.ts rename to ui/app/(prowler)/_overview/threat-score/index.ts diff --git a/ui/app/(prowler)/_new-overview/threat-score/threat-score.ssr.tsx b/ui/app/(prowler)/_overview/threat-score/threat-score.ssr.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/threat-score/threat-score.ssr.tsx rename to ui/app/(prowler)/_overview/threat-score/threat-score.ssr.tsx diff --git a/ui/app/(prowler)/_new-overview/watchlist/_components/compliance-watchlist.tsx b/ui/app/(prowler)/_overview/watchlist/_components/compliance-watchlist.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/watchlist/_components/compliance-watchlist.tsx rename to ui/app/(prowler)/_overview/watchlist/_components/compliance-watchlist.tsx diff --git a/ui/app/(prowler)/_new-overview/watchlist/_components/service-watchlist.tsx b/ui/app/(prowler)/_overview/watchlist/_components/service-watchlist.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/watchlist/_components/service-watchlist.tsx rename to ui/app/(prowler)/_overview/watchlist/_components/service-watchlist.tsx diff --git a/ui/app/(prowler)/_new-overview/watchlist/_components/sort-toggle-button.tsx b/ui/app/(prowler)/_overview/watchlist/_components/sort-toggle-button.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/watchlist/_components/sort-toggle-button.tsx rename to ui/app/(prowler)/_overview/watchlist/_components/sort-toggle-button.tsx diff --git a/ui/app/(prowler)/_new-overview/watchlist/_components/watchlist-card.tsx b/ui/app/(prowler)/_overview/watchlist/_components/watchlist-card.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/watchlist/_components/watchlist-card.tsx rename to ui/app/(prowler)/_overview/watchlist/_components/watchlist-card.tsx diff --git a/ui/app/(prowler)/_new-overview/watchlist/_components/watchlist.skeleton.tsx b/ui/app/(prowler)/_overview/watchlist/_components/watchlist.skeleton.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/watchlist/_components/watchlist.skeleton.tsx rename to ui/app/(prowler)/_overview/watchlist/_components/watchlist.skeleton.tsx diff --git a/ui/app/(prowler)/_new-overview/watchlist/compliance-watchlist.ssr.tsx b/ui/app/(prowler)/_overview/watchlist/compliance-watchlist.ssr.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/watchlist/compliance-watchlist.ssr.tsx rename to ui/app/(prowler)/_overview/watchlist/compliance-watchlist.ssr.tsx diff --git a/ui/app/(prowler)/_new-overview/watchlist/index.ts b/ui/app/(prowler)/_overview/watchlist/index.ts similarity index 100% rename from ui/app/(prowler)/_new-overview/watchlist/index.ts rename to ui/app/(prowler)/_overview/watchlist/index.ts diff --git a/ui/app/(prowler)/_new-overview/watchlist/service-watchlist.ssr.tsx b/ui/app/(prowler)/_overview/watchlist/service-watchlist.ssr.tsx similarity index 100% rename from ui/app/(prowler)/_new-overview/watchlist/service-watchlist.ssr.tsx rename to ui/app/(prowler)/_overview/watchlist/service-watchlist.ssr.tsx diff --git a/ui/app/(prowler)/page.tsx b/ui/app/(prowler)/page.tsx index eb653a34ae..245fae9ef6 100644 --- a/ui/app/(prowler)/page.tsx +++ b/ui/app/(prowler)/page.tsx @@ -4,32 +4,29 @@ import { getProviders } from "@/actions/providers"; import { ContentLayout } from "@/components/ui"; import { SearchParamsProps } from "@/types"; -import { AccountsSelector } from "./_new-overview/_components/accounts-selector"; -import { ProviderTypeSelector } from "./_new-overview/_components/provider-type-selector"; +import { AccountsSelector } from "./_overview/_components/accounts-selector"; +import { ProviderTypeSelector } from "./_overview/_components/provider-type-selector"; import { AttackSurfaceSkeleton, AttackSurfaceSSR, -} from "./_new-overview/attack-surface"; -import { CheckFindingsSSR } from "./_new-overview/check-findings"; -import { GraphsTabsWrapper } from "./_new-overview/graphs-tabs/graphs-tabs-wrapper"; -import { RiskPipelineViewSkeleton } from "./_new-overview/graphs-tabs/risk-pipeline-view"; +} from "./_overview/attack-surface"; +import { CheckFindingsSSR } from "./_overview/check-findings"; +import { GraphsTabsWrapper } from "./_overview/graphs-tabs/graphs-tabs-wrapper"; +import { RiskPipelineViewSkeleton } from "./_overview/graphs-tabs/risk-pipeline-view"; import { RiskSeverityChartSkeleton, RiskSeverityChartSSR, -} from "./_new-overview/risk-severity"; +} from "./_overview/risk-severity"; import { FindingSeverityOverTimeSkeleton, FindingSeverityOverTimeSSR, -} from "./_new-overview/severity-over-time/finding-severity-over-time.ssr"; -import { StatusChartSkeleton } from "./_new-overview/status-chart"; -import { - ThreatScoreSkeleton, - ThreatScoreSSR, -} from "./_new-overview/threat-score"; +} from "./_overview/severity-over-time/finding-severity-over-time.ssr"; +import { StatusChartSkeleton } from "./_overview/status-chart"; +import { ThreatScoreSkeleton, ThreatScoreSSR } from "./_overview/threat-score"; import { ServiceWatchlistSSR, WatchlistCardSkeleton, -} from "./_new-overview/watchlist"; +} from "./_overview/watchlist"; export default async function Home({ searchParams, diff --git a/ui/components/filters/active-check-id-filter.tsx b/ui/components/filters/active-check-id-filter.tsx deleted file mode 100644 index 63690221db..0000000000 --- a/ui/components/filters/active-check-id-filter.tsx +++ /dev/null @@ -1,35 +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 const ActiveCheckIdFilter = () => { - const searchParams = useSearchParams(); - const { clearFilter } = useUrlFilters(); - - const checkIdFilter = searchParams.get("filter[check_id__in]"); - - if (!checkIdFilter) { - return null; - } - - const checkIds = checkIdFilter.split(","); - const displayText = - checkIds.length > 1 - ? `${checkIds.length} Check IDs filtered` - : `Check ID: ${checkIds[0]}`; - - return ( - clearFilter("check_id__in")} - > - {displayText} - - - ); -}; diff --git a/ui/components/filters/active-filter-badge.tsx b/ui/components/filters/active-filter-badge.tsx new file mode 100644 index 0000000000..9e0327d598 --- /dev/null +++ b/ui/components/filters/active-filter-badge.tsx @@ -0,0 +1,126 @@ +"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`, + }, + { + filterKey: "category__in", + label: "Category", + formatMultiple: (count) => `${count} Categories filtered`, + }, + { + filterKey: "scan__in", + label: "Scan", + formatValue: (id) => `${id.slice(0, 8)}...`, + }, +]; + +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/index.ts b/ui/components/filters/index.ts index a7f8b28f8b..4d6fa55ebe 100644 --- a/ui/components/filters/index.ts +++ b/ui/components/filters/index.ts @@ -1,4 +1,4 @@ -export * from "./active-check-id-filter"; +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/radar-chart.tsx b/ui/components/graphs/radar-chart.tsx index 30aac64728..f5beb535c1 100644 --- a/ui/components/graphs/radar-chart.tsx +++ b/ui/components/graphs/radar-chart.tsx @@ -112,6 +112,7 @@ const CustomDot = ({ ); const point: RadarDataPoint = { category: currentCategory, + categoryId: fullDataItem?.categoryId || payload.categoryId || "", value: payload.value, change: payload.change, severityData: fullDataItem?.severityData || payload.severityData, diff --git a/ui/components/graphs/types.ts b/ui/components/graphs/types.ts index 9ec1c1d4a4..181e8b5b7e 100644 --- a/ui/components/graphs/types.ts +++ b/ui/components/graphs/types.ts @@ -32,6 +32,7 @@ export interface LineDataPoint { export interface RadarDataPoint { category: string; + categoryId: string; value: number; change?: number; severityData?: BarDataPoint[]; diff --git a/ui/components/ui/table/data-table-filter-custom.tsx b/ui/components/ui/table/data-table-filter-custom.tsx index b2735b55f5..890fb62c23 100644 --- a/ui/components/ui/table/data-table-filter-custom.tsx +++ b/ui/components/ui/table/data-table-filter-custom.tsx @@ -3,7 +3,7 @@ import { useSearchParams } from "next/navigation"; import { ComplianceScanInfo } from "@/components/compliance/compliance-header/compliance-scan-info"; -import { ActiveCheckIdFilter } from "@/components/filters/active-check-id-filter"; +import { ActiveFilterBadges } from "@/components/filters/active-filter-badge"; import { ClearFiltersButton } from "@/components/filters/clear-filters-button"; import { MultiSelect, @@ -166,7 +166,7 @@ export const DataTableFilterCustom = ({ ); })}
- +
diff --git a/ui/types/filters.ts b/ui/types/filters.ts index ddd1845074..6585f379a2 100644 --- a/ui/types/filters.ts +++ b/ui/types/filters.ts @@ -33,4 +33,5 @@ export enum FilterType { SEVERITY = "severity__in", STATUS = "status__in", DELTA = "delta__in", + CATEGORY = "category__in", }