feat: Attack Surface component is ready

This commit is contained in:
Pablo Lara
2024-08-19 12:11:50 +02:00
parent 441945e075
commit f236d2087a
8 changed files with 200 additions and 15 deletions
+2 -1
View File
@@ -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>
</>
-12
View File
@@ -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))",
+44
View File
@@ -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>
);
};
+1
View File
@@ -0,0 +1 @@
export * from "./AttackSurface";
+86
View File
@@ -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
View File
@@ -1,3 +1,4 @@
export * from "./action-card/ActionCard";
export * from "./alert/Alert";
export * from "./chart/Chart";
export * from "./dialog/Dialog";
+17 -2
View File
@@ -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);
+49
View 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",