mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-24 04:51:51 +00:00
feat: Attack Surface component is ready
This commit is contained in:
@@ -2,6 +2,7 @@ import { Spacer } from "@nextui-org/react";
|
||||
|
||||
import { StatusChart } from "@/components/charts";
|
||||
import { FilterControls } from "@/components/filters";
|
||||
import { AttackSurface } from "@/components/overview";
|
||||
import { Header } from "@/components/ui";
|
||||
import { CustomBox } from "@/components/ui/custom";
|
||||
|
||||
@@ -29,7 +30,7 @@ export default function Home() {
|
||||
preTitle={"Attack Surface"}
|
||||
className="col-span-12 sm:col-span-12 xl:col-span-4 3xl:col-span-5"
|
||||
>
|
||||
<p>hi hi</p>
|
||||
<AttackSurface />
|
||||
</CustomBox>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -39,22 +39,10 @@ const chartConfig = {
|
||||
number: {
|
||||
label: "Findings",
|
||||
},
|
||||
chrome: {
|
||||
label: "Chrome",
|
||||
color: "hsl(var(--chart-1))",
|
||||
},
|
||||
success: {
|
||||
label: "Success",
|
||||
color: "hsl(var(--chart-success))",
|
||||
},
|
||||
firefox: {
|
||||
label: "Firefox",
|
||||
color: "hsl(var(--chart-3))",
|
||||
},
|
||||
edge: {
|
||||
label: "Edge",
|
||||
color: "hsl(var(--chart-4))",
|
||||
},
|
||||
fail: {
|
||||
label: "Fail",
|
||||
color: "hsl(var(--chart-fail))",
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import React from "react";
|
||||
|
||||
import ActionCard from "../ui/action-card/ActionCard";
|
||||
|
||||
const cardData = [
|
||||
{
|
||||
findings: 3,
|
||||
icon: "solar:danger-triangle-bold",
|
||||
title: "Internet Exposed Resources",
|
||||
},
|
||||
{
|
||||
findings: 15,
|
||||
icon: "solar:danger-triangle-bold",
|
||||
title: "Exposed Secrets",
|
||||
},
|
||||
{
|
||||
findings: 0,
|
||||
icon: "heroicons:shield-check-solid",
|
||||
title: "IAM Policies Leading to Privilege Escalation",
|
||||
},
|
||||
{
|
||||
findings: 0,
|
||||
icon: "heroicons:shield-check-solid",
|
||||
title: "EC2 with Metadata Service V1 (IMDSv1)",
|
||||
},
|
||||
];
|
||||
|
||||
export const AttackSurface = () => {
|
||||
return (
|
||||
<div className="flex flex-col gap-3">
|
||||
{cardData.map((card, index) => (
|
||||
<ActionCard
|
||||
key={index}
|
||||
color={card.findings > 0 ? "fail" : "success"}
|
||||
icon={card.findings > 0 ? "solar:danger-triangle-bold" : card.icon}
|
||||
title={card.title}
|
||||
description={
|
||||
card.findings > 0 ? "Review Required" : "No Issues Found"
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./AttackSurface";
|
||||
@@ -0,0 +1,86 @@
|
||||
"use client";
|
||||
|
||||
import { Icon } from "@iconify/react";
|
||||
import type { CardProps } from "@nextui-org/react";
|
||||
import { Card, CardBody } from "@nextui-org/react";
|
||||
import React from "react";
|
||||
|
||||
import { cn } from "@/lib";
|
||||
|
||||
export type ActionCardProps = CardProps & {
|
||||
icon: string;
|
||||
title: string;
|
||||
color?: "success" | "secondary" | "warning" | "fail";
|
||||
description: string;
|
||||
};
|
||||
|
||||
const ActionCard = React.forwardRef<HTMLDivElement, ActionCardProps>(
|
||||
({ color, title, icon, description, children, className, ...props }, ref) => {
|
||||
const colors = React.useMemo(() => {
|
||||
switch (color) {
|
||||
case "success":
|
||||
return {
|
||||
card: "border-system-success-medium",
|
||||
iconWrapper: "bg-system-success-lighter border-system-success",
|
||||
icon: "text-system-success",
|
||||
};
|
||||
case "secondary":
|
||||
return {
|
||||
card: "border-secondary-100",
|
||||
iconWrapper: "bg-secondary-50 border-secondary-100",
|
||||
icon: "text-secondary",
|
||||
};
|
||||
case "warning":
|
||||
return {
|
||||
card: "border-warning-500",
|
||||
iconWrapper: "bg-warning-50 border-warning-100",
|
||||
icon: "text-warning-600",
|
||||
};
|
||||
case "fail":
|
||||
return {
|
||||
card: "border-danger-300",
|
||||
iconWrapper: "bg-danger-50 border-danger-100",
|
||||
icon: "text-danger",
|
||||
};
|
||||
|
||||
default:
|
||||
return {
|
||||
card: "border-default-200",
|
||||
iconWrapper: "bg-default-50 border-default-100",
|
||||
icon: "text-default-500",
|
||||
};
|
||||
}
|
||||
}, [color]);
|
||||
|
||||
return (
|
||||
<Card
|
||||
ref={ref}
|
||||
isPressable
|
||||
className={cn("border-small", colors?.card, className)}
|
||||
shadow="sm"
|
||||
{...props}
|
||||
>
|
||||
<CardBody className="flex h-full flex-row items-start gap-3 p-4">
|
||||
<div
|
||||
className={cn(
|
||||
"item-center flex rounded-medium border p-2",
|
||||
colors?.iconWrapper,
|
||||
)}
|
||||
>
|
||||
<Icon className={colors?.icon} icon={icon} width={24} />
|
||||
</div>
|
||||
<div className="flex flex-col">
|
||||
<p className="text-medium">{title}</p>
|
||||
<p className="text-small text-default-400">
|
||||
{description || children}
|
||||
</p>
|
||||
</div>
|
||||
</CardBody>
|
||||
</Card>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
ActionCard.displayName = "ActionCard";
|
||||
|
||||
export default ActionCard;
|
||||
@@ -1,3 +1,4 @@
|
||||
export * from "./action-card/ActionCard";
|
||||
export * from "./alert/Alert";
|
||||
export * from "./chart/Chart";
|
||||
export * from "./dialog/Dialog";
|
||||
|
||||
+17
-2
@@ -1,12 +1,27 @@
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { extendTailwindMerge } from "tailwind-merge";
|
||||
|
||||
const COMMON_UNITS = ["small", "medium", "large"];
|
||||
import { MetaDataProps } from "@/types";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
const twMerge = extendTailwindMerge({
|
||||
extend: {
|
||||
theme: {
|
||||
opacity: ["disabled"],
|
||||
spacing: ["divider"],
|
||||
borderWidth: COMMON_UNITS,
|
||||
borderRadius: COMMON_UNITS,
|
||||
},
|
||||
classGroups: {
|
||||
shadow: [{ shadow: COMMON_UNITS }],
|
||||
"font-size": [{ text: ["tiny", ...COMMON_UNITS] }],
|
||||
"bg-image": ["bg-stripe-gradient"],
|
||||
},
|
||||
},
|
||||
});
|
||||
export const parseStringify = (value: any) => JSON.parse(JSON.stringify(value));
|
||||
|
||||
export const convertFileToUrl = (file: File) => URL.createObjectURL(file);
|
||||
|
||||
@@ -18,6 +18,34 @@ module.exports = {
|
||||
},
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
system: {
|
||||
success: {
|
||||
DEFAULT: "#09BF3D",
|
||||
medium: "#3CEC6D",
|
||||
light: "#B5FDC8",
|
||||
lighter: "#D9FFE3",
|
||||
},
|
||||
error: {
|
||||
DEFAULT: "#E11D48",
|
||||
medium: "#FB718F",
|
||||
light: "#FECDD8",
|
||||
lighter: "#FFE4EA",
|
||||
},
|
||||
info: {
|
||||
DEFAULT: "#7C3AED",
|
||||
medium: "#B48BFA",
|
||||
light: "#E5D6FE",
|
||||
lighter: "#F1E9FE",
|
||||
},
|
||||
warning: {
|
||||
DEFAULT: "#FBBF24",
|
||||
medium: "#FDDD8A",
|
||||
light: "#feefc7",
|
||||
lighter: "#FFF9EB",
|
||||
},
|
||||
},
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ["var(--font-sans)"],
|
||||
mono: ["var(--font-geist-mono)"],
|
||||
@@ -31,6 +59,27 @@ module.exports = {
|
||||
from: { height: "var(--radix-accordion-content-height)" },
|
||||
to: { height: "0" },
|
||||
},
|
||||
advance: { from: { width: 0 }, to: { width: "100%" } },
|
||||
"fade-in": { from: { opacity: 0 }, to: { opacity: 1 } },
|
||||
"fade-out": { from: { opacity: 1 }, to: { opacity: 0 } },
|
||||
"slide-in": {
|
||||
from: { transform: "translateX(100%)" },
|
||||
to: { transform: "translateX(0)" },
|
||||
},
|
||||
"slide-out": {
|
||||
from: { transform: "translateX(0)" },
|
||||
to: { transform: "translateX(100%)" },
|
||||
},
|
||||
woosh: {
|
||||
"0, 10%": { left: 0, right: "100%" },
|
||||
"40%, 60%": { left: 0, right: 0 },
|
||||
"90%, 100%": { left: "100%", right: 0 },
|
||||
},
|
||||
lineAnim: {
|
||||
"0%": { left: "-40%" },
|
||||
"50%": { left: "20%", width: "80%" },
|
||||
"100%": { left: "100%", width: "100%" },
|
||||
},
|
||||
},
|
||||
animation: {
|
||||
"accordion-down": "accordion-down 0.2s ease-out",
|
||||
|
||||
Reference in New Issue
Block a user