From 17a841a86d5a9faf60811fd1ed01440d102da7a4 Mon Sep 17 00:00:00 2001 From: Alan Buscaglia Date: Tue, 21 Oct 2025 17:07:23 +0200 Subject: [PATCH] feat: add comprehensive light/dark theme support for all graph components - Add complete CSS variables for both light and dark themes in globals.css - Light theme: dark text on light backgrounds (primary: #1f2937, bg: #f9fafb) - Dark theme: white text on dark backgrounds (primary: #ffffff, bg: #1e293b) - Update all chart components to use CSS variables instead of hardcoded colors - Tooltips, legends, titles, and text automatically adapt to theme changes - Fix SankeyChart text to properly display in light mode (was hardcoded white) - Update DonutChart center label to use theme-aware text colors - All components now dynamically respond to theme toggle via next-themes --- ui/app/(prowler)/page.tsx | 44 +++++--- ui/components/graphs/donut-chart.tsx | 24 ++++- ui/components/graphs/horizontal-bar-chart.tsx | 43 ++++++-- ui/components/graphs/line-chart.tsx | 25 +++-- ui/components/graphs/radar-chart.tsx | 18 +++- ui/components/graphs/sankey-chart.tsx | 24 +++-- ui/components/graphs/scatter-plot.tsx | 20 +++- ui/components/graphs/shared/chart-legend.tsx | 12 ++- ui/components/graphs/shared/constants.ts | 2 +- ui/components/graphs/threat-map.tsx | 101 +++++++++++++++--- .../overview/scatter-plot-with-selection.tsx | 6 ++ ui/styles/globals.css | 41 ++++++- 12 files changed, 291 insertions(+), 69 deletions(-) diff --git a/ui/app/(prowler)/page.tsx b/ui/app/(prowler)/page.tsx index f6850914e4..857251c08f 100644 --- a/ui/app/(prowler)/page.tsx +++ b/ui/app/(prowler)/page.tsx @@ -545,28 +545,42 @@ const sampleThreatMapData = { ], }; -// Sample data for DonutChart +// Sample data for DonutChart (Findings by Severity) const sampleDonutChartData = [ { - name: "AWS", - value: 1840, - percentage: 45, - color: "var(--chart-provider-aws)", + name: "Critical", + value: 432, + percentage: 22, + color: "var(--chart-danger-emphasis)", change: 5, }, { - name: "Azure", - value: 1280, + name: "High", + value: 1232, percentage: 32, - color: "var(--chart-provider-azure)", + color: "var(--chart-danger)", + change: 8, + }, + { + name: "Medium", + value: 925, + percentage: 28, + color: "var(--chart-warning-emphasis)", + change: -3, + }, + { + name: "Low", + value: 543, + percentage: 15, + color: "var(--chart-warning)", change: -2, }, { - name: "Google", - value: 920, - percentage: 23, - color: "var(--chart-provider-google)", - change: 8, + name: "Info", + value: 108, + percentage: 3, + color: "var(--chart-info)", + change: 0, }, ]; @@ -753,14 +767,14 @@ export default async function Home({

- Provider Distribution + Severity Distribution

diff --git a/ui/components/graphs/donut-chart.tsx b/ui/components/graphs/donut-chart.tsx index e92cc30962..9c212fe84e 100644 --- a/ui/components/graphs/donut-chart.tsx +++ b/ui/components/graphs/donut-chart.tsx @@ -24,18 +24,27 @@ const CustomTooltip = ({ active, payload }: any) => { if (active && payload && payload.length) { const data = payload[0].payload; return ( -
+
- + {data.percentage}% {data.name}
{data.change !== undefined && ( -

+

{data.change > 0 ? "+" : ""} {data.change}% @@ -145,14 +154,19 @@ export function DonutChart({ {formattedValue} {centerLabel.label} diff --git a/ui/components/graphs/horizontal-bar-chart.tsx b/ui/components/graphs/horizontal-bar-chart.tsx index b0ea53e747..7af8556b65 100644 --- a/ui/components/graphs/horizontal-bar-chart.tsx +++ b/ui/components/graphs/horizontal-bar-chart.tsx @@ -26,7 +26,12 @@ export function HorizontalBarChart({ data, title }: HorizontalBarChartProps) {

{title && (
-

{title}

+

+ {title} +

)} @@ -48,8 +53,9 @@ export function HorizontalBarChart({ data, title }: HorizontalBarChartProps) { >
{isHovered && ( -
+
- + {item.value.toLocaleString()} {item.name} Risk
{item.newFindings !== undefined && (
- - + + {item.newFindings} New Findings
)} {item.change !== undefined && ( -

+

{item.change > 0 ? "+" : ""} {item.change}% @@ -102,14 +126,15 @@ export function HorizontalBarChart({ data, title }: HorizontalBarChartProps) {

{item.percentage}% - + {item.value.toLocaleString()}
diff --git a/ui/components/graphs/line-chart.tsx b/ui/components/graphs/line-chart.tsx index ce4327010d..51b0a31b48 100644 --- a/ui/components/graphs/line-chart.tsx +++ b/ui/components/graphs/line-chart.tsx @@ -48,8 +48,16 @@ const CustomLineTooltip = ({ const totalValue = typedPayload.reduce((sum, item) => sum + item.value, 0); return ( -
-

{label}

+
+

+ {label} +

@@ -67,18 +75,23 @@ const CustomLineTooltip = ({ className="h-2 w-2 rounded-full" style={{ backgroundColor: item.stroke }} /> - {item.value} + + {item.value} +
{newFindings !== undefined && (
- - + + {newFindings} New Findings
)} {change !== undefined && typeof change === "number" && ( -

+

{change > 0 ? "+" : ""} {change}% diff --git a/ui/components/graphs/radar-chart.tsx b/ui/components/graphs/radar-chart.tsx index 5855f624b0..b0fb6936c2 100644 --- a/ui/components/graphs/radar-chart.tsx +++ b/ui/components/graphs/radar-chart.tsx @@ -36,15 +36,27 @@ const CustomTooltip = ({ active, payload }: any) => { if (active && payload && payload.length) { const data = payload[0]; return ( -

-

+

+

{data.payload.category}

{data.payload.change !== undefined && ( -

+

{data.payload.change > 0 ? "+" : ""} {data.payload.change}% diff --git a/ui/components/graphs/sankey-chart.tsx b/ui/components/graphs/sankey-chart.tsx index c93deb8353..2208b69576 100644 --- a/ui/components/graphs/sankey-chart.tsx +++ b/ui/components/graphs/sankey-chart.tsx @@ -66,10 +66,23 @@ const CustomTooltip = ({ active, payload }: any) => { if (active && payload && payload.length) { const data = payload[0].payload; return ( -

-

{data.name}

+
+

+ {data.name} +

{data.value && ( -

Value: {data.value}

+

+ Value: {data.value} +

)}
); @@ -143,7 +156,7 @@ const CustomNode = (props: any) => { x={isOut ? x - 6 : x + width + 6} y={y + height / 2} fontSize="14" - className="fill-white stroke-white" + fill={CHART_COLORS.textPrimary} > {nodeName} @@ -152,8 +165,7 @@ const CustomNode = (props: any) => { x={isOut ? x - 6 : x + width + 6} y={y + height / 2 + 13} fontSize="12" - className="fill-slate-400 stroke-slate-400" - strokeOpacity="0.5" + fill={CHART_COLORS.textSecondary} > {payload.value} diff --git a/ui/components/graphs/scatter-plot.tsx b/ui/components/graphs/scatter-plot.tsx index d137a6ccdf..0b8cf7b8fd 100644 --- a/ui/components/graphs/scatter-plot.tsx +++ b/ui/components/graphs/scatter-plot.tsx @@ -45,9 +45,23 @@ const CustomTooltip = ({ active, payload }: any) => { const severityColor = getSeverityColorByRiskScore(data.x); return ( -
-

{data.name}

-

+

+

+ {data.name} +

+

{data.x} Risk Score

diff --git a/ui/components/graphs/shared/chart-legend.tsx b/ui/components/graphs/shared/chart-legend.tsx index 11066aa54a..2efea3ea85 100644 --- a/ui/components/graphs/shared/chart-legend.tsx +++ b/ui/components/graphs/shared/chart-legend.tsx @@ -9,14 +9,22 @@ interface ChartLegendProps { export function ChartLegend({ items }: ChartLegendProps) { return ( -
+
{items.map((item, index) => (
- {item.label} + + {item.label} +
))}
diff --git a/ui/components/graphs/shared/constants.ts b/ui/components/graphs/shared/constants.ts index 44e2d50414..4aadd4aac0 100644 --- a/ui/components/graphs/shared/constants.ts +++ b/ui/components/graphs/shared/constants.ts @@ -21,7 +21,7 @@ export const STATUS_COLORS = { export const CHART_COLORS = { tooltipBorder: "var(--chart-border-emphasis)", tooltipBackground: "var(--chart-background)", - textPrimary: "#ffffff", + textPrimary: "var(--chart-text-primary)", textSecondary: "var(--chart-text-secondary)", gridLine: "var(--chart-border-emphasis)", backgroundTrack: "rgba(51, 65, 85, 0.5)", // slate-700 with 50% opacity diff --git a/ui/components/graphs/threat-map.tsx b/ui/components/graphs/threat-map.tsx index ee74f7426f..571bba221c 100644 --- a/ui/components/graphs/threat-map.tsx +++ b/ui/components/graphs/threat-map.tsx @@ -118,29 +118,41 @@ function MapTooltip({ location: LocationPoint; position: { x: number; y: number }; }) { + const CHART_COLORS = { + tooltipBorder: "var(--chart-border-emphasis)", + tooltipBackground: "var(--chart-background)", + textPrimary: "var(--chart-text-primary)", + textSecondary: "var(--chart-text-secondary)", + }; + return (
- - + + {location.name}
- + {location.totalFindings.toLocaleString()} Fail Findings
{location.change !== undefined && ( -

+

{location.change > 0 ? "+" : ""} {location.change}% @@ -153,11 +165,23 @@ function MapTooltip({ } function EmptyState() { + const CHART_COLORS = { + tooltipBorder: "var(--chart-border-emphasis)", + tooltipBackground: "var(--chart-background)", + textSecondary: "var(--chart-text-secondary)", + }; + return ( -

+
- -

+ +

Select a location on the map to view details

@@ -166,10 +190,16 @@ function EmptyState() { } function LoadingState({ height }: { height: number }) { + const CHART_COLORS = { + textSecondary: "var(--chart-text-secondary)", + }; + return (
-
Loading map...
+
+ Loading map... +
); @@ -350,17 +380,34 @@ export function ThreatMap({ isLoadingMap, ]); + const CHART_COLORS = { + tooltipBorder: "var(--chart-border-emphasis)", + tooltipBackground: "var(--chart-background)", + textPrimary: "var(--chart-text-primary)", + textSecondary: "var(--chart-text-secondary)", + }; + return (
{/* Map Section */}
-

Threat Map

+

+ Threat Map +

{isLoadingMap ? ( @@ -401,7 +453,10 @@ export function ThreatMap({
- + {filteredLocations.length} Locations
@@ -414,15 +469,27 @@ export function ThreatMap({
{selectedLocation ? ( -
+
-

+

{selectedLocation.name}

-

+

{selectedLocation.totalFindings.toLocaleString()} Total Findings

diff --git a/ui/components/overview/scatter-plot-with-selection.tsx b/ui/components/overview/scatter-plot-with-selection.tsx index 558eaac37e..ad9a0b6b17 100644 --- a/ui/components/overview/scatter-plot-with-selection.tsx +++ b/ui/components/overview/scatter-plot-with-selection.tsx @@ -1,5 +1,7 @@ "use client"; +import { useState } from "react"; + import { ScatterPlot } from "@/components/graphs"; interface DataPoint { @@ -23,12 +25,16 @@ export function ScatterPlotWithSelection({ xLabel = "Risk Score", yLabel = "Compliance %", }: ScatterPlotWithSelectionProps) { + const [selectedPoint, setSelectedPoint] = useState(null); + return ( ); } diff --git a/ui/styles/globals.css b/ui/styles/globals.css index f87555e889..3265d97c4f 100644 --- a/ui/styles/globals.css +++ b/ui/styles/globals.css @@ -2,7 +2,7 @@ @config "../tailwind.config.js"; @theme { - /* Chart Severity Colors */ + /* Chart Severity Colors - Dark Theme */ --chart-info: #2e51b2; --chart-warning: #fdd34f; --chart-warning-emphasis: #ff7d19; @@ -22,7 +22,8 @@ --chart-provider-azure: #00bcd4; --chart-provider-google: #EA4335; - /* Chart UI Colors */ + /* Chart UI Colors - Dark Theme (defaults) */ + --chart-text-primary: #ffffff; --chart-text-secondary: #94a3b8; --chart-border: #475569; --chart-border-emphasis: #334155; @@ -35,6 +36,24 @@ @layer base { :root { + /* Light Theme Chart Colors */ + --chart-info: #1e40af; + --chart-warning: #d97706; + --chart-warning-emphasis: #dc2626; + --chart-danger: #dc2626; + --chart-danger-emphasis: #991b1b; + --chart-success-color: #16a34a; + --chart-fail: #dc2626; + --chart-radar-primary: #9d174d; + --chart-text-primary: #1f2937; + --chart-text-secondary: #6b7280; + --chart-border: #d1d5db; + --chart-border-emphasis: #9ca3af; + --chart-background: #f9fafb; + --chart-alert-bg: #fecdd3; + --chart-alert-text: #be123c; + + /* Chart HSL values */ --chart-success: 146 80% 35%; --chart-fail: 339 90% 51%; --chart-muted: 45 93% 47%; @@ -49,6 +68,24 @@ } .dark { + /* Dark Theme Chart Colors */ + --chart-info: #2e51b2; + --chart-warning: #fdd34f; + --chart-warning-emphasis: #ff7d19; + --chart-danger: #ff3077; + --chart-danger-emphasis: #971348; + --chart-success-color: #86da26; + --chart-fail: #db2b49; + --chart-radar-primary: #b51c80; + --chart-text-primary: #ffffff; + --chart-text-secondary: #94a3b8; + --chart-border: #475569; + --chart-border-emphasis: #334155; + --chart-background: #1e293b; + --chart-alert-bg: #432232; + --chart-alert-text: #f54280; + + /* Chart HSL values */ --chart-success: 146 80% 35%; --chart-fail: 339 90% 51%; --chart-muted: 45 93% 47%;