mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-11-08 20:33:41 +00:00
feat: reusable graph components (#8873)
Co-authored-by: Alejandro Bailo <59607668+alejandrobailo@users.noreply.github.com>
This commit is contained in:
1 parent
741217ce80
commit
42e816081e
18 files changed
+1764
-9
No files matched your search
@@ -0,0 +1,34 @@
|
||||
import { AlertTriangle } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
interface AlertPillProps {
|
||||
value: number;
|
||||
label?: string;
|
||||
iconSize?: number;
|
||||
textSize?: "xs" | "sm" | "base";
|
||||
}
|
||||
|
||||
export function AlertPill({
|
||||
value,
|
||||
label = "Fail Findings",
|
||||
iconSize = 12,
|
||||
textSize = "xs",
|
||||
}: AlertPillProps) {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="bg-alert-pill-bg flex items-center gap-1 rounded-full px-2 py-1">
|
||||
<AlertTriangle size={iconSize} className="text-alert-pill-text" />
|
||||
<span
|
||||
className={cn(
|
||||
`text-${textSize}`,
|
||||
"text-alert-pill-text font-semibold",
|
||||
)}
|
||||
>
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
<span className={cn(`text-${textSize}`, "text-slate-400")}>{label}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
export interface ChartLegendItem {
|
||||
label: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
interface ChartLegendProps {
|
||||
items: ChartLegendItem[];
|
||||
}
|
||||
|
||||
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]">
|
||||
{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>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
import { Bell, VolumeX } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
import { TooltipData } from "../types";
|
||||
|
||||
interface ChartTooltipProps {
|
||||
active?: boolean;
|
||||
payload?: any[];
|
||||
label?: string;
|
||||
showColorIndicator?: boolean;
|
||||
colorIndicatorShape?: "circle" | "square";
|
||||
}
|
||||
|
||||
export function ChartTooltip({
|
||||
active,
|
||||
payload,
|
||||
label,
|
||||
showColorIndicator = true,
|
||||
colorIndicatorShape = "square",
|
||||
}: ChartTooltipProps) {
|
||||
if (!active || !payload || payload.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const data: TooltipData = payload[0].payload || payload[0];
|
||||
const color = payload[0].color || data.color;
|
||||
|
||||
return (
|
||||
<div className="min-w-[200px] rounded-lg border border-slate-700 bg-slate-800 p-3 shadow-lg">
|
||||
<div className="flex items-center gap-2">
|
||||
{showColorIndicator && color && (
|
||||
<div
|
||||
className={cn(
|
||||
"h-3 w-3",
|
||||
colorIndicatorShape === "circle" ? "rounded-full" : "rounded-sm",
|
||||
)}
|
||||
style={{ backgroundColor: color }}
|
||||
/>
|
||||
)}
|
||||
<p className="text-sm font-semibold text-white">{label || data.name}</p>
|
||||
</div>
|
||||
|
||||
<p className="mt-1 text-xs text-white">
|
||||
{typeof data.value === "number"
|
||||
? data.value.toLocaleString()
|
||||
: data.value}
|
||||
{data.percentage !== undefined && ` (${data.percentage}%)`}
|
||||
</p>
|
||||
|
||||
{data.newFindings !== undefined && data.newFindings > 0 && (
|
||||
<div className="mt-1 flex items-center gap-2">
|
||||
<Bell size={14} className="text-slate-400" />
|
||||
<span className="text-xs text-slate-400">
|
||||
{data.newFindings} New Findings
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data.new !== undefined && data.new > 0 && (
|
||||
<div className="mt-1 flex items-center gap-2">
|
||||
<Bell size={14} className="text-slate-400" />
|
||||
<span className="text-xs text-slate-400">{data.new} New</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data.muted !== undefined && data.muted > 0 && (
|
||||
<div className="mt-1 flex items-center gap-2">
|
||||
<VolumeX size={14} className="text-slate-400" />
|
||||
<span className="text-xs text-slate-400">{data.muted} Muted</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data.change !== undefined && (
|
||||
<p className="mt-1 text-xs text-slate-400">
|
||||
<span className="font-bold">
|
||||
{data.change > 0 ? "+" : ""}
|
||||
{data.change}%
|
||||
</span>{" "}
|
||||
Since Last Scan
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tooltip for charts with multiple data series (like LineChart)
|
||||
*/
|
||||
export function MultiSeriesChartTooltip({
|
||||
active,
|
||||
payload,
|
||||
label,
|
||||
}: ChartTooltipProps) {
|
||||
if (!active || !payload || payload.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-w-[200px] rounded-lg border border-slate-700 bg-slate-800 p-3 shadow-lg">
|
||||
<p className="mb-2 text-sm font-semibold text-white">{label}</p>
|
||||
|
||||
{payload.map((entry: any, index: number) => (
|
||||
<div key={index} className="flex items-center gap-2">
|
||||
<div
|
||||
className="h-2 w-2 rounded-full"
|
||||
style={{ backgroundColor: entry.color }}
|
||||
/>
|
||||
<span className="text-xs text-white">{entry.name}:</span>
|
||||
<span className="text-xs font-semibold text-white">
|
||||
{entry.value}
|
||||
</span>
|
||||
{entry.payload[`${entry.dataKey}_change`] && (
|
||||
<span className="text-xs text-slate-400">
|
||||
({entry.payload[`${entry.dataKey}_change`] > 0 ? "+" : ""}
|
||||
{entry.payload[`${entry.dataKey}_change`]}%)
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
export const SEVERITY_COLORS = {
|
||||
Informational: "var(--color-info)",
|
||||
Low: "var(--color-warning)",
|
||||
Medium: "var(--color-warning-emphasis)",
|
||||
High: "var(--color-danger)",
|
||||
Critical: "var(--color-danger-emphasis)",
|
||||
} as const;
|
||||
|
||||
export const CHART_COLORS = {
|
||||
tooltipBorder: "var(--color-slate-700)",
|
||||
tooltipBackground: "var(--color-slate-800)",
|
||||
textPrimary: "var(--color-white)",
|
||||
textSecondary: "var(--color-slate-400)",
|
||||
gridLine: "var(--color-slate-700)",
|
||||
backgroundTrack: "rgba(51, 65, 85, 0.5)", // slate-700 with 50% opacity
|
||||
alertPillBg: "var(--color-alert-pill-bg)",
|
||||
alertPillText: "var(--color-alert-pill-text)",
|
||||
defaultColor: "var(--color-slate-500)", // Default fallback color for charts
|
||||
} as const;
|
||||
|
||||
export const CHART_DIMENSIONS = {
|
||||
defaultHeight: 400,
|
||||
tooltipMinWidth: "200px",
|
||||
borderRadius: "8px",
|
||||
} as const;
|
||||
|
||||
export const SORT_OPTIONS = {
|
||||
highLow: "high-low",
|
||||
lowHigh: "low-high",
|
||||
alphabetical: "alphabetical",
|
||||
} as const;
|
||||
|
||||
export const DEFAULT_SORT_OPTION = SORT_OPTIONS.highLow;
|
||||
|
||||
export const SEVERITY_ORDER = {
|
||||
Critical: 0,
|
||||
High: 1,
|
||||
Medium: 2,
|
||||
Low: 3,
|
||||
Informational: 4,
|
||||
} as const;
|
||||
|
||||
export const LAYOUT_OPTIONS = {
|
||||
horizontal: "horizontal",
|
||||
vertical: "vertical",
|
||||
} as const;
|
||||
@@ -0,0 +1,13 @@
|
||||
import { SEVERITY_COLORS } from "./constants";
|
||||
|
||||
export function getSeverityColorByRiskScore(riskScore: number): string {
|
||||
if (riskScore >= 7) return SEVERITY_COLORS.Critical;
|
||||
if (riskScore >= 5) return SEVERITY_COLORS.High;
|
||||
if (riskScore >= 3) return SEVERITY_COLORS.Medium;
|
||||
if (riskScore >= 1) return SEVERITY_COLORS.Low;
|
||||
return SEVERITY_COLORS.Informational;
|
||||
}
|
||||
|
||||
export function getSeverityColorByName(name: string): string | undefined {
|
||||
return SEVERITY_COLORS[name as keyof typeof SEVERITY_COLORS];
|
||||
}
|
||||
Reference in new issue
Block a user