mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 13:01:56 +00:00
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
This commit is contained in:
+29
-15
@@ -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({
|
||||
<div className="col-span-12 lg:col-span-6">
|
||||
<Spacer y={16} />
|
||||
<h3 className="mb-4 text-sm font-bold uppercase">
|
||||
Provider Distribution
|
||||
Severity Distribution
|
||||
</h3>
|
||||
<DonutChart
|
||||
data={sampleDonutChartData}
|
||||
height={350}
|
||||
showLegend
|
||||
centerLabel={{
|
||||
value: "4,040",
|
||||
value: "3,240",
|
||||
label: "Total Findings",
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -24,18 +24,27 @@ const CustomTooltip = ({ active, payload }: any) => {
|
||||
if (active && payload && payload.length) {
|
||||
const data = payload[0].payload;
|
||||
return (
|
||||
<div className="rounded-lg border border-slate-700 bg-slate-800 p-3 shadow-lg">
|
||||
<div
|
||||
className="rounded-lg border p-3 shadow-lg"
|
||||
style={{
|
||||
backgroundColor: "var(--chart-background)",
|
||||
borderColor: "var(--chart-border-emphasis)",
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className="h-3 w-3 rounded-sm"
|
||||
style={{ backgroundColor: data.color }}
|
||||
/>
|
||||
<span className="text-sm font-semibold text-white">
|
||||
<span
|
||||
className="text-sm font-semibold"
|
||||
style={{ color: "var(--chart-text-primary)" }}
|
||||
>
|
||||
{data.percentage}% {data.name}
|
||||
</span>
|
||||
</div>
|
||||
{data.change !== undefined && (
|
||||
<p className="mt-2 text-xs text-slate-400">
|
||||
<p className="mt-2 text-xs" style={{ color: "var(--chart-text-secondary)" }}>
|
||||
<span className="font-bold">
|
||||
{data.change > 0 ? "+" : ""}
|
||||
{data.change}%
|
||||
@@ -145,14 +154,19 @@ export function DonutChart({
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={viewBox.cy}
|
||||
className="fill-white text-3xl font-bold"
|
||||
className="text-3xl font-bold"
|
||||
style={{
|
||||
fill: "var(--chart-text-primary)",
|
||||
}}
|
||||
>
|
||||
{formattedValue}
|
||||
</tspan>
|
||||
<tspan
|
||||
x={viewBox.cx}
|
||||
y={(viewBox.cy || 0) + 24}
|
||||
className="fill-slate-400"
|
||||
style={{
|
||||
fill: "var(--chart-text-secondary)",
|
||||
}}
|
||||
>
|
||||
{centerLabel.label}
|
||||
</tspan>
|
||||
|
||||
@@ -26,7 +26,12 @@ export function HorizontalBarChart({ data, title }: HorizontalBarChartProps) {
|
||||
<div className="w-full">
|
||||
{title && (
|
||||
<div className="mb-4">
|
||||
<h3 className="text-lg font-semibold text-white">{title}</h3>
|
||||
<h3
|
||||
className="text-lg font-semibold"
|
||||
style={{ color: "var(--chart-text-primary)" }}
|
||||
>
|
||||
{title}
|
||||
</h3>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -48,8 +53,9 @@ export function HorizontalBarChart({ data, title }: HorizontalBarChartProps) {
|
||||
>
|
||||
<div className="w-24 text-right">
|
||||
<span
|
||||
className="text-sm text-white"
|
||||
className="text-sm"
|
||||
style={{
|
||||
color: "var(--chart-text-primary)",
|
||||
opacity: isFaded ? 0.5 : 1,
|
||||
transition: "opacity 0.2s",
|
||||
}}
|
||||
@@ -70,26 +76,44 @@ export function HorizontalBarChart({ data, title }: HorizontalBarChartProps) {
|
||||
/>
|
||||
|
||||
{isHovered && (
|
||||
<div className="absolute top-10 left-0 z-10 min-w-[200px] rounded-lg border border-slate-700 bg-slate-800 p-3 shadow-lg">
|
||||
<div
|
||||
className="absolute top-10 left-0 z-10 min-w-[200px] rounded-lg border p-3 shadow-lg"
|
||||
style={{
|
||||
backgroundColor: "var(--chart-background)",
|
||||
borderColor: "var(--chart-border-emphasis)",
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<div
|
||||
className="h-3 w-3 rounded-sm"
|
||||
style={{ backgroundColor: barColor }}
|
||||
/>
|
||||
<span className="font-semibold text-white">
|
||||
<span
|
||||
className="font-semibold"
|
||||
style={{ color: "var(--chart-text-primary)" }}
|
||||
>
|
||||
{item.value.toLocaleString()} {item.name} Risk
|
||||
</span>
|
||||
</div>
|
||||
{item.newFindings !== undefined && (
|
||||
<div className="mt-2 flex items-center gap-2">
|
||||
<Bell size={14} className="text-slate-400" />
|
||||
<span className="text-sm text-slate-400">
|
||||
<Bell
|
||||
size={14}
|
||||
style={{ color: "var(--chart-text-secondary)" }}
|
||||
/>
|
||||
<span
|
||||
className="text-sm"
|
||||
style={{ color: "var(--chart-text-secondary)" }}
|
||||
>
|
||||
{item.newFindings} New Findings
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{item.change !== undefined && (
|
||||
<p className="mt-1 text-sm text-slate-400">
|
||||
<p
|
||||
className="mt-1 text-sm"
|
||||
style={{ color: "var(--chart-text-secondary)" }}
|
||||
>
|
||||
<span className="font-bold">
|
||||
{item.change > 0 ? "+" : ""}
|
||||
{item.change}%
|
||||
@@ -102,14 +126,15 @@ export function HorizontalBarChart({ data, title }: HorizontalBarChartProps) {
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="flex w-40 items-center gap-2 text-sm text-white"
|
||||
className="flex w-40 items-center gap-2 text-sm"
|
||||
style={{
|
||||
color: "var(--chart-text-primary)",
|
||||
opacity: isFaded ? 0.5 : 1,
|
||||
transition: "opacity 0.2s",
|
||||
}}
|
||||
>
|
||||
<span className="font-semibold">{item.percentage}%</span>
|
||||
<span className="text-slate-400">•</span>
|
||||
<span style={{ color: "var(--chart-text-secondary)" }}>•</span>
|
||||
<span className="font-bold">{item.value.toLocaleString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -48,8 +48,16 @@ const CustomLineTooltip = ({
|
||||
const totalValue = typedPayload.reduce((sum, item) => sum + item.value, 0);
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border border-slate-700 bg-slate-800 p-3 shadow-lg">
|
||||
<p className="mb-3 text-xs text-slate-400">{label}</p>
|
||||
<div
|
||||
className="rounded-lg border p-3 shadow-lg"
|
||||
style={{
|
||||
backgroundColor: "var(--chart-background)",
|
||||
borderColor: "var(--chart-border-emphasis)",
|
||||
}}
|
||||
>
|
||||
<p className="mb-3 text-xs" style={{ color: "var(--chart-text-secondary)" }}>
|
||||
{label}
|
||||
</p>
|
||||
|
||||
<div className="mb-3">
|
||||
<AlertPill value={totalValue} textSize="sm" />
|
||||
@@ -67,18 +75,23 @@ const CustomLineTooltip = ({
|
||||
className="h-2 w-2 rounded-full"
|
||||
style={{ backgroundColor: item.stroke }}
|
||||
/>
|
||||
<span className="text-sm text-white">{item.value}</span>
|
||||
<span className="text-sm" style={{ color: "var(--chart-text-primary)" }}>
|
||||
{item.value}
|
||||
</span>
|
||||
</div>
|
||||
{newFindings !== undefined && (
|
||||
<div className="flex items-center gap-2">
|
||||
<Bell size={14} className="text-slate-400" />
|
||||
<span className="text-xs text-slate-400">
|
||||
<Bell size={14} style={{ color: "var(--chart-text-secondary)" }} />
|
||||
<span
|
||||
className="text-xs"
|
||||
style={{ color: "var(--chart-text-secondary)" }}
|
||||
>
|
||||
{newFindings} New Findings
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{change !== undefined && typeof change === "number" && (
|
||||
<p className="text-xs text-slate-400">
|
||||
<p className="text-xs" style={{ color: "var(--chart-text-secondary)" }}>
|
||||
<span className="font-bold">
|
||||
{change > 0 ? "+" : ""}
|
||||
{change}%
|
||||
|
||||
@@ -36,15 +36,27 @@ const CustomTooltip = ({ active, payload }: any) => {
|
||||
if (active && payload && payload.length) {
|
||||
const data = payload[0];
|
||||
return (
|
||||
<div className="rounded-lg border border-slate-700 bg-slate-800 p-3 shadow-lg">
|
||||
<p className="text-sm font-semibold text-white">
|
||||
<div
|
||||
className="rounded-lg border p-3 shadow-lg"
|
||||
style={{
|
||||
borderColor: CHART_COLORS.tooltipBorder,
|
||||
backgroundColor: CHART_COLORS.tooltipBackground,
|
||||
}}
|
||||
>
|
||||
<p
|
||||
className="text-sm font-semibold"
|
||||
style={{ color: CHART_COLORS.textPrimary }}
|
||||
>
|
||||
{data.payload.category}
|
||||
</p>
|
||||
<div className="mt-1">
|
||||
<AlertPill value={data.value} />
|
||||
</div>
|
||||
{data.payload.change !== undefined && (
|
||||
<p className="mt-1 text-xs text-slate-400">
|
||||
<p
|
||||
className="mt-1 text-xs"
|
||||
style={{ color: CHART_COLORS.textSecondary }}
|
||||
>
|
||||
<span className="font-bold">
|
||||
{data.payload.change > 0 ? "+" : ""}
|
||||
{data.payload.change}%
|
||||
|
||||
@@ -66,10 +66,23 @@ const CustomTooltip = ({ active, payload }: any) => {
|
||||
if (active && payload && payload.length) {
|
||||
const data = payload[0].payload;
|
||||
return (
|
||||
<div className="rounded-lg border border-slate-700 bg-slate-800 p-3 shadow-lg">
|
||||
<p className="text-sm font-semibold text-white">{data.name}</p>
|
||||
<div
|
||||
className="rounded-lg border p-3 shadow-lg"
|
||||
style={{
|
||||
borderColor: CHART_COLORS.tooltipBorder,
|
||||
backgroundColor: CHART_COLORS.tooltipBackground,
|
||||
}}
|
||||
>
|
||||
<p
|
||||
className="text-sm font-semibold"
|
||||
style={{ color: CHART_COLORS.textPrimary }}
|
||||
>
|
||||
{data.name}
|
||||
</p>
|
||||
{data.value && (
|
||||
<p className="text-xs text-slate-400">Value: {data.value}</p>
|
||||
<p className="text-xs" style={{ color: CHART_COLORS.textSecondary }}>
|
||||
Value: {data.value}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@@ -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}
|
||||
</text>
|
||||
@@ -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}
|
||||
</text>
|
||||
|
||||
@@ -45,9 +45,23 @@ const CustomTooltip = ({ active, payload }: any) => {
|
||||
const severityColor = getSeverityColorByRiskScore(data.x);
|
||||
|
||||
return (
|
||||
<div className="rounded-lg border border-slate-700 bg-slate-800 p-3 shadow-lg">
|
||||
<p className="text-sm font-semibold text-white">{data.name}</p>
|
||||
<p className="mt-1 text-xs text-slate-400">
|
||||
<div
|
||||
className="rounded-lg border p-3 shadow-lg"
|
||||
style={{
|
||||
borderColor: CHART_COLORS.tooltipBorder,
|
||||
backgroundColor: CHART_COLORS.tooltipBackground,
|
||||
}}
|
||||
>
|
||||
<p
|
||||
className="text-sm font-semibold"
|
||||
style={{ color: CHART_COLORS.textPrimary }}
|
||||
>
|
||||
{data.name}
|
||||
</p>
|
||||
<p
|
||||
className="mt-1 text-xs"
|
||||
style={{ color: CHART_COLORS.textSecondary }}
|
||||
>
|
||||
<span style={{ color: severityColor }}>{data.x}</span> Risk Score
|
||||
</p>
|
||||
<div className="mt-2">
|
||||
|
||||
@@ -9,14 +9,22 @@ interface ChartLegendProps {
|
||||
|
||||
export function ChartLegend({ items }: ChartLegendProps) {
|
||||
return (
|
||||
<div className="bg-card-border mt-4 inline-flex gap-[46px] rounded-full border-2 px-[19px] py-[9px]">
|
||||
<div
|
||||
className="mt-4 inline-flex gap-[46px] rounded-full border-2 px-[19px] py-[9px]"
|
||||
style={{ borderColor: "var(--chart-border)" }}
|
||||
>
|
||||
{items.map((item, index) => (
|
||||
<div key={`legend-${index}`} className="flex items-center gap-1">
|
||||
<div
|
||||
className="h-3 w-3 rounded"
|
||||
style={{ backgroundColor: item.color }}
|
||||
/>
|
||||
<span className="text-xs text-gray-300">{item.label}</span>
|
||||
<span
|
||||
className="text-xs"
|
||||
style={{ color: "var(--chart-text-secondary)" }}
|
||||
>
|
||||
{item.label}
|
||||
</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
<div
|
||||
className="pointer-events-none absolute z-50 min-w-[200px] rounded-lg border border-slate-700 bg-slate-800 p-3 shadow-lg"
|
||||
className="pointer-events-none absolute z-50 min-w-[200px] rounded-lg border p-3 shadow-lg"
|
||||
style={{
|
||||
left: `${position.x + 15}px`,
|
||||
top: `${position.y + 15}px`,
|
||||
transform: "translate(0, -50%)",
|
||||
borderColor: CHART_COLORS.tooltipBorder,
|
||||
backgroundColor: CHART_COLORS.tooltipBackground,
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<MapPin size={14} className="text-slate-400" />
|
||||
<span className="text-sm font-semibold text-white">
|
||||
<MapPin size={14} style={{ color: CHART_COLORS.textSecondary }} />
|
||||
<span
|
||||
className="text-sm font-semibold"
|
||||
style={{ color: CHART_COLORS.textPrimary }}
|
||||
>
|
||||
{location.name}
|
||||
</span>
|
||||
</div>
|
||||
<div className="mt-1 flex items-center gap-2">
|
||||
<AlertTriangle size={14} className="text-[#DB2B49]" />
|
||||
<span className="text-sm text-white">
|
||||
<span className="text-sm" style={{ color: CHART_COLORS.textPrimary }}>
|
||||
{location.totalFindings.toLocaleString()} Fail Findings
|
||||
</span>
|
||||
</div>
|
||||
{location.change !== undefined && (
|
||||
<p className="mt-1 text-xs text-slate-400">
|
||||
<p className="mt-1 text-xs" style={{ color: CHART_COLORS.textSecondary }}>
|
||||
<span className="font-bold">
|
||||
{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 (
|
||||
<div className="flex h-full min-h-[400px] items-center justify-center rounded-lg border border-slate-700 bg-slate-800 p-6">
|
||||
<div
|
||||
className="flex h-full min-h-[400px] items-center justify-center rounded-lg border p-6"
|
||||
style={{
|
||||
borderColor: CHART_COLORS.tooltipBorder,
|
||||
backgroundColor: CHART_COLORS.tooltipBackground,
|
||||
}}
|
||||
>
|
||||
<div className="text-center">
|
||||
<Info size={48} className="mx-auto mb-2 text-slate-500" />
|
||||
<p className="text-sm text-slate-400">
|
||||
<Info size={48} className="mx-auto mb-2" style={{ color: CHART_COLORS.textSecondary }} />
|
||||
<p className="text-sm" style={{ color: CHART_COLORS.textSecondary }}>
|
||||
Select a location on the map to view details
|
||||
</p>
|
||||
</div>
|
||||
@@ -166,10 +190,16 @@ function EmptyState() {
|
||||
}
|
||||
|
||||
function LoadingState({ height }: { height: number }) {
|
||||
const CHART_COLORS = {
|
||||
textSecondary: "var(--chart-text-secondary)",
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-center" style={{ height }}>
|
||||
<div className="text-center">
|
||||
<div className="mb-2 text-slate-400">Loading map...</div>
|
||||
<div className="mb-2" style={{ color: CHART_COLORS.textSecondary }}>
|
||||
Loading map...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -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 (
|
||||
<div className="flex w-full flex-col gap-6 lg:flex-row lg:items-start">
|
||||
{/* Map Section */}
|
||||
<div className="flex-1">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<h3 className="text-lg font-semibold text-white">Threat Map</h3>
|
||||
<h3
|
||||
className="text-lg font-semibold"
|
||||
style={{ color: CHART_COLORS.textPrimary }}
|
||||
>
|
||||
Threat Map
|
||||
</h3>
|
||||
<div className="relative">
|
||||
<select
|
||||
value={selectedRegion}
|
||||
onChange={(e) => setSelectedRegion(e.target.value)}
|
||||
className="appearance-none rounded-lg border border-slate-700 bg-slate-800 px-4 py-2 pr-10 text-sm text-white focus:border-slate-600 focus:outline-none"
|
||||
className="appearance-none rounded-lg border px-4 py-2 pr-10 text-sm focus:outline-none"
|
||||
style={{
|
||||
borderColor: CHART_COLORS.tooltipBorder,
|
||||
backgroundColor: CHART_COLORS.tooltipBackground,
|
||||
color: CHART_COLORS.textPrimary,
|
||||
}}
|
||||
>
|
||||
<option value="All Regions">All Regions</option>
|
||||
{data.regions.map((region) => (
|
||||
@@ -371,14 +418,19 @@ export function ThreatMap({
|
||||
</select>
|
||||
<ChevronDown
|
||||
size={16}
|
||||
className="pointer-events-none absolute top-1/2 right-3 -translate-y-1/2 text-slate-400"
|
||||
className="pointer-events-none absolute top-1/2 right-3 -translate-y-1/2"
|
||||
style={{ color: CHART_COLORS.textSecondary }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="rounded-lg border border-slate-700 bg-slate-800/50 p-4"
|
||||
className="rounded-lg border p-4"
|
||||
style={{
|
||||
borderColor: CHART_COLORS.tooltipBorder,
|
||||
backgroundColor: CHART_COLORS.tooltipBackground,
|
||||
}}
|
||||
>
|
||||
{isLoadingMap ? (
|
||||
<LoadingState height={dimensions.height} />
|
||||
@@ -401,7 +453,10 @@ export function ThreatMap({
|
||||
</div>
|
||||
<div className="mt-4 flex items-center gap-2">
|
||||
<div className="h-3 w-3 rounded-full bg-[#DB2B49]" />
|
||||
<span className="text-sm text-slate-400">
|
||||
<span
|
||||
className="text-sm"
|
||||
style={{ color: CHART_COLORS.textSecondary }}
|
||||
>
|
||||
{filteredLocations.length} Locations
|
||||
</span>
|
||||
</div>
|
||||
@@ -414,15 +469,27 @@ export function ThreatMap({
|
||||
<div className="w-full lg:w-[400px]">
|
||||
<div className="mb-4 h-10" />
|
||||
{selectedLocation ? (
|
||||
<div className="rounded-lg border border-slate-700 bg-slate-800 p-6">
|
||||
<div
|
||||
className="rounded-lg border p-6"
|
||||
style={{
|
||||
borderColor: CHART_COLORS.tooltipBorder,
|
||||
backgroundColor: CHART_COLORS.tooltipBackground,
|
||||
}}
|
||||
>
|
||||
<div className="mb-6">
|
||||
<div className="mb-1 flex items-center gap-2">
|
||||
<div className="h-2 w-2 rounded-full bg-[#86DA26]" />
|
||||
<h4 className="text-base font-semibold text-white">
|
||||
<h4
|
||||
className="text-base font-semibold"
|
||||
style={{ color: CHART_COLORS.textPrimary }}
|
||||
>
|
||||
{selectedLocation.name}
|
||||
</h4>
|
||||
</div>
|
||||
<p className="text-sm text-slate-400">
|
||||
<p
|
||||
className="text-sm"
|
||||
style={{ color: CHART_COLORS.textSecondary }}
|
||||
>
|
||||
{selectedLocation.totalFindings.toLocaleString()} Total Findings
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -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<DataPoint | null>(null);
|
||||
|
||||
return (
|
||||
<ScatterPlot
|
||||
data={data}
|
||||
height={height}
|
||||
xLabel={xLabel}
|
||||
yLabel={yLabel}
|
||||
selectedPoint={selectedPoint}
|
||||
onSelectPoint={setSelectedPoint}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
+39
-2
@@ -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%;
|
||||
|
||||
Reference in New Issue
Block a user