mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-29 06:44:21 +00:00
Merge branch 'main' into PRWLR-4375-Services-Page-Card
This commit is contained in:
15 files changed
+311
-209
No files matched your search
@@ -0,0 +1,93 @@
|
||||
import { Divider } from "@nextui-org/react";
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
FindingsCardContent,
|
||||
FindingsCardDetail,
|
||||
FindingsCardScan,
|
||||
FindingsCardType,
|
||||
} from "@/components/findings";
|
||||
import { FindingProps } from "@/types";
|
||||
interface FindingsCardProps {
|
||||
selectedRow: FindingProps;
|
||||
}
|
||||
|
||||
export const FindingsCard: React.FC<FindingsCardProps> = ({ selectedRow }) => {
|
||||
const { attributes, card } = selectedRow || {};
|
||||
const { CheckTitle } = attributes || {};
|
||||
const {
|
||||
resourceLink,
|
||||
resourceId,
|
||||
resourceARN,
|
||||
checkLink,
|
||||
checkId,
|
||||
type,
|
||||
scanTime,
|
||||
findingLink,
|
||||
findingId,
|
||||
details,
|
||||
riskDetails,
|
||||
riskLink,
|
||||
recommendationDetails,
|
||||
recommendationLink,
|
||||
referenceInformation,
|
||||
referenceLink,
|
||||
} = card || {};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={clsx(
|
||||
"max-h-[calc(100vh-146px)] overflow-y-auto rounded-md border bg-background transition-all duration-300",
|
||||
{
|
||||
"w-1/3 translate-x-0 ml-2 p-3 opacity-100": selectedRow,
|
||||
"translate-x-full w-0 opacity-0": !selectedRow,
|
||||
},
|
||||
)}
|
||||
>
|
||||
<p className="font-bold mb-3">{CheckTitle}</p>
|
||||
<Divider />
|
||||
|
||||
<FindingsCardContent
|
||||
title="Resource ID:"
|
||||
url={resourceLink}
|
||||
description={resourceId}
|
||||
/>
|
||||
<FindingsCardContent title="Resource ARN:" description={resourceARN} />
|
||||
<FindingsCardContent
|
||||
title="Check ID:"
|
||||
url={checkLink}
|
||||
description={checkId}
|
||||
/>
|
||||
<FindingsCardType type={type} />
|
||||
<FindingsCardScan title="Scan Time:" dateTime={scanTime} />
|
||||
<FindingsCardContent
|
||||
title="Prowler Finding ID:"
|
||||
url={findingLink}
|
||||
description={findingId}
|
||||
/>
|
||||
|
||||
<FindingsCardDetail title="Details:" description={details} />
|
||||
<FindingsCardDetail
|
||||
title="Risk:"
|
||||
url={riskLink}
|
||||
description={riskDetails}
|
||||
type="risk"
|
||||
/>
|
||||
<FindingsCardDetail
|
||||
title="Recommendation:"
|
||||
url={recommendationLink}
|
||||
description={recommendationDetails}
|
||||
type="recommendation"
|
||||
/>
|
||||
<FindingsCardDetail
|
||||
title="Reference Information:"
|
||||
url={referenceLink}
|
||||
description={referenceInformation}
|
||||
type="reference"
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Link } from "@nextui-org/react";
|
||||
import React from "react";
|
||||
|
||||
interface FindingsCardContentProps {
|
||||
title: string;
|
||||
url?: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export const FindingsCardContent: React.FC<FindingsCardContentProps> = ({
|
||||
title,
|
||||
url,
|
||||
description,
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
<p className="text-sm font-bold mt-3">{title}</p>
|
||||
{url ? (
|
||||
<Link className="text-sm" href={url}>
|
||||
{description}
|
||||
</Link>
|
||||
) : (
|
||||
<p className="text-sm">{description}</p>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,59 @@
|
||||
import { Button, Link } from "@nextui-org/react";
|
||||
import React from "react";
|
||||
|
||||
interface FindingsCardDetailProps {
|
||||
title: string;
|
||||
url?: string;
|
||||
description: string;
|
||||
type?: DetailType;
|
||||
}
|
||||
|
||||
type DetailType = "default" | "risk" | "recommendation" | "reference";
|
||||
|
||||
const getDetailColorClass = (type: DetailType): string => {
|
||||
switch (type) {
|
||||
case "risk":
|
||||
return "border-red-200";
|
||||
case "recommendation":
|
||||
return "border-green-200";
|
||||
case "reference":
|
||||
return "border-gray-200";
|
||||
case "default":
|
||||
default:
|
||||
return "border-yellow-200";
|
||||
}
|
||||
};
|
||||
|
||||
export const FindingsCardDetail: React.FC<FindingsCardDetailProps> = ({
|
||||
title,
|
||||
url,
|
||||
description,
|
||||
type = "default",
|
||||
}) => {
|
||||
return (
|
||||
<>
|
||||
{description && (
|
||||
<div
|
||||
className={`text-sm mt-3 rounded-md border-2 ${getDetailColorClass(type)} p-2`}
|
||||
>
|
||||
<p className="font-bold flex justify-between items-center mb-2">
|
||||
<span>{title}</span>
|
||||
{url && (
|
||||
<Button
|
||||
href={url}
|
||||
as={Link}
|
||||
color="primary"
|
||||
variant="flat"
|
||||
isExternal
|
||||
size="sm"
|
||||
>
|
||||
View Source
|
||||
</Button>
|
||||
)}
|
||||
</p>
|
||||
<p>{description}</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import { format, parseISO } from "date-fns";
|
||||
import React from "react";
|
||||
|
||||
interface FindingsCardScanProps {
|
||||
title: string;
|
||||
dateTime: string;
|
||||
}
|
||||
|
||||
export const FindingsCardScan: React.FC<FindingsCardScanProps> = ({
|
||||
title,
|
||||
dateTime = "",
|
||||
}) => {
|
||||
const date = dateTime && parseISO(dateTime);
|
||||
const formattedDate = date && format(date, "MMM dd, yyyy");
|
||||
const formattedTime = date && format(date, "p 'UTC'");
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className="text-sm font-bold mt-3">{title}</p>
|
||||
<p className="text-sm">
|
||||
{formattedDate} at {formattedTime}
|
||||
</p>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from "react";
|
||||
|
||||
interface FindingsCardTypeProps {
|
||||
type: string[];
|
||||
}
|
||||
|
||||
export const FindingsCardType: React.FC<FindingsCardTypeProps> = ({
|
||||
type = [],
|
||||
}) => {
|
||||
const typeContent = () => {
|
||||
if (type.length > 0) {
|
||||
return type.join(", ");
|
||||
}
|
||||
return type[0];
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className="text-sm font-bold mt-3">
|
||||
{type.length > 1 ? "Types:" : "Type:"}
|
||||
</p>
|
||||
<p className="text-sm">{typeContent()}</p>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -1,4 +1,8 @@
|
||||
export * from "./FindingsCard";
|
||||
export * from "./FindingsCardContent";
|
||||
export * from "./FindingsCardDetail";
|
||||
export * from "./FindingsCardScan";
|
||||
export * from "./FindingsCardType";
|
||||
export * from "./table/ColumnsFindings";
|
||||
export * from "./table/DataTableFindings";
|
||||
export * from "./table/FindingsCard";
|
||||
export * from "./table/SkeletonTableFindings";
|
||||
@@ -9,8 +9,9 @@ import {
|
||||
Row,
|
||||
useReactTable,
|
||||
} from "@tanstack/react-table";
|
||||
import clsx from "clsx";
|
||||
|
||||
import { FindingsCard } from "@/components/findings/table/FindingsCard";
|
||||
import { FindingsCard } from "@/components/findings";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
@@ -18,7 +19,7 @@ import {
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table/Table";
|
||||
} from "@/components/ui";
|
||||
import { FindingProps } from "@/types";
|
||||
|
||||
interface DataTableFindingsProps<TData extends FindingProps, TValue> {
|
||||
@@ -48,7 +49,12 @@ export function DataTableFindings<TData extends FindingProps, TValue>({
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-row w-full">
|
||||
<div className="w-full">
|
||||
<div
|
||||
className={clsx("transition-all duration-300", {
|
||||
"w-2/3": selectedRow,
|
||||
"w-full": !selectedRow,
|
||||
})}
|
||||
>
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
|
||||
@@ -1,134 +0,0 @@
|
||||
import { Button, Divider, Link } from "@nextui-org/react";
|
||||
import React from "react";
|
||||
|
||||
import { FindingProps } from "@/types";
|
||||
interface FindingsCardProps {
|
||||
selectedRow: FindingProps;
|
||||
}
|
||||
|
||||
export const FindingsCard: React.FC<FindingsCardProps> = ({ selectedRow }) => {
|
||||
const { attributes, card } = selectedRow || {};
|
||||
const { CheckTitle } = attributes || {};
|
||||
const {
|
||||
resourceLink,
|
||||
resourceId,
|
||||
resourceARN,
|
||||
checkLink,
|
||||
checkId,
|
||||
type,
|
||||
scanTime,
|
||||
findingLink,
|
||||
findingId,
|
||||
details,
|
||||
riskDetails,
|
||||
riskLink,
|
||||
recommendationDetails,
|
||||
recommendationLink,
|
||||
referenceInformation,
|
||||
referenceLink,
|
||||
} = card || {};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`rounded-md border bg-background transition-transform duration-300 ${selectedRow ? "w-2/5 translate-x-0 ml-2 p-3" : "translate-x-full w-0"}`}
|
||||
>
|
||||
<p className="font-bold mb-3">{CheckTitle}</p>
|
||||
<Divider />
|
||||
<div className="text-sm mt-3">
|
||||
<p className="font-bold">Resource ID:</p>
|
||||
<Link className="text-sm" href={resourceLink}>
|
||||
{resourceId}
|
||||
</Link>
|
||||
<p className="font-bold mt-3">Resource ARN:</p>
|
||||
<p>{resourceARN}</p>
|
||||
<p className="font-bold mt-3">Check ID:</p>
|
||||
<Link className="text-sm" href={checkLink}>
|
||||
{checkId}
|
||||
</Link>
|
||||
<p className="font-bold mt-3">Type:</p>
|
||||
<p>{type}</p>
|
||||
<p className="font-bold mt-3">Scan Time:</p>
|
||||
<p>{scanTime}</p>
|
||||
<p className="font-bold mt-3">Prowler Finding ID:</p>
|
||||
<Link className="text-sm" href={findingLink}>
|
||||
{findingId}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
{details && (
|
||||
<div className="text-sm mt-3 rounded-md border-2 border-yellow-500 p-2">
|
||||
<p className="font-bold">Details:</p>
|
||||
<p>{details}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{riskDetails && (
|
||||
<div className="text-sm mt-3 rounded-md border-2 border-red-500 p-2">
|
||||
<p className="font-bold flex justify-between items-center mb-2">
|
||||
<span>Risk:</span>
|
||||
{riskLink && (
|
||||
<Button
|
||||
href={riskLink}
|
||||
as={Link}
|
||||
color="primary"
|
||||
variant="flat"
|
||||
isExternal
|
||||
size="sm"
|
||||
className="h-[28px] p-2"
|
||||
>
|
||||
View Source
|
||||
</Button>
|
||||
)}
|
||||
</p>
|
||||
<p>{riskDetails}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{recommendationDetails && (
|
||||
<div className="text-sm mt-3 rounded-md border-2 border-green-500 p-2">
|
||||
<div className="font-bold flex justify-between items-center mb-2">
|
||||
<span>Recommendation:</span>
|
||||
{recommendationLink && (
|
||||
<Button
|
||||
href={recommendationLink}
|
||||
as={Link}
|
||||
color="primary"
|
||||
variant="flat"
|
||||
isExternal
|
||||
size="sm"
|
||||
className="h-[28px] p-2"
|
||||
>
|
||||
View Source
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<p>{recommendationDetails}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{referenceInformation && (
|
||||
<div className="text-sm mt-3 rounded-md border-2 border-gray-500 p-2">
|
||||
<div className="font-bold flex justify-between items-center mb-2">
|
||||
<span>Reference Information:</span>
|
||||
{referenceLink && (
|
||||
<Button
|
||||
href={referenceLink}
|
||||
as={Link}
|
||||
color="primary"
|
||||
variant="flat"
|
||||
isExternal
|
||||
size="sm"
|
||||
className="h-[28px] p-2"
|
||||
>
|
||||
View Source
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<p>{referenceInformation}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -11,7 +11,7 @@ import { ColumnDef } from "@tanstack/react-table";
|
||||
import { add } from "date-fns";
|
||||
|
||||
import { VerticalDotsIcon } from "@/components/icons";
|
||||
import { StatusBadge } from "@/components/ui/table/StatusBadge";
|
||||
import { StatusBadge } from "@/components/ui";
|
||||
import { ProviderProps } from "@/types";
|
||||
|
||||
import { CheckConnectionProvider } from "../CheckConnectionProvider";
|
||||
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table/Table";
|
||||
} from "@/components/ui";
|
||||
import { MetaDataProps } from "@/types";
|
||||
|
||||
import { DataTablePagination } from "./DataTablePagination";
|
||||
|
||||
@@ -1,46 +1,47 @@
|
||||
import { Chip } from "@nextui-org/react";
|
||||
import clsx from "clsx";
|
||||
import React from "react";
|
||||
|
||||
import { AlertIcon } from "@/components/icons";
|
||||
|
||||
type Severity = "critical" | "high" | "medium" | "low";
|
||||
|
||||
const severityColorMap: Record<
|
||||
Severity,
|
||||
| "text-white bg-red-800"
|
||||
| "text-white bg-red-600"
|
||||
| "text-white bg-orange-500"
|
||||
| "bg-yellow-200"
|
||||
> = {
|
||||
critical: "text-white bg-red-800",
|
||||
high: "text-white bg-red-600",
|
||||
medium: "text-white bg-orange-500",
|
||||
low: "bg-yellow-200",
|
||||
};
|
||||
|
||||
const severityIconMap: Partial<Record<Severity, React.ReactNode>> = {
|
||||
const severityIconMap = {
|
||||
critical: <AlertIcon size={14} className="mr-1" />,
|
||||
} as const;
|
||||
|
||||
const getSeverityColor = (
|
||||
severity: Severity,
|
||||
): "danger" | "warning" | "default" => {
|
||||
switch (severity) {
|
||||
case "critical":
|
||||
return "danger";
|
||||
case "high":
|
||||
return "danger";
|
||||
case "medium":
|
||||
return "warning";
|
||||
case "low":
|
||||
return "default";
|
||||
default:
|
||||
return "default"; // this is a fallback, though unnecessary due to typing
|
||||
}
|
||||
};
|
||||
|
||||
const getSeverityColor: (severity: Severity) => string = (severity) =>
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
severityColorMap[severity];
|
||||
|
||||
const getSeverityIcon: (severity: Severity) => React.ReactNode | null = (
|
||||
severity,
|
||||
) =>
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
severityIconMap[severity] || null;
|
||||
const getSeverityIcon = (severity: Severity): React.ReactNode | null => {
|
||||
return severity === "critical" ? severityIconMap.critical : null;
|
||||
};
|
||||
|
||||
export const SeverityBadge = ({ severity }: { severity: Severity }) => {
|
||||
const color = getSeverityColor(severity);
|
||||
|
||||
return (
|
||||
<Chip
|
||||
classNames={{
|
||||
base: `capitalize border-none gap-1 text-gray-600 ${getSeverityColor(severity)}`,
|
||||
content: "font-semibold",
|
||||
}}
|
||||
className={clsx("capitalize border-none gap-1 text-default-600", {
|
||||
"bg-rose-700/20": severity === "critical",
|
||||
})}
|
||||
size="sm"
|
||||
variant="flat"
|
||||
color={color}
|
||||
endContent={getSeverityIcon(severity)}
|
||||
>
|
||||
{severity}
|
||||
|
||||
@@ -11,32 +11,25 @@ type Status =
|
||||
|
||||
const statusColorMap: Record<
|
||||
Status,
|
||||
| "text-white bg-green-600"
|
||||
| "bg-yellow-200"
|
||||
| "text-white bg-red-600"
|
||||
| "bg-gray-300"
|
||||
"danger" | "warning" | "success" | "default"
|
||||
> = {
|
||||
completed: "text-white bg-green-600",
|
||||
pending: "bg-yellow-200",
|
||||
cancelled: "text-white bg-red-600",
|
||||
fail: "text-white bg-red-600",
|
||||
success: "text-white bg-green-600",
|
||||
muted: "bg-gray-300",
|
||||
completed: "success",
|
||||
pending: "warning",
|
||||
cancelled: "danger",
|
||||
fail: "danger",
|
||||
success: "success",
|
||||
muted: "default",
|
||||
};
|
||||
|
||||
const getStatusColor: (status: Status) => string = (status) =>
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
statusColorMap[status];
|
||||
|
||||
export const StatusBadge = ({ status }: { status: Status }) => {
|
||||
const color = statusColorMap[status as keyof typeof statusColorMap];
|
||||
|
||||
return (
|
||||
<Chip
|
||||
classNames={{
|
||||
base: `capitalize border-none gap-1 text-gray-600 ${getStatusColor(status)}`,
|
||||
content: "font-semibold",
|
||||
}}
|
||||
className="capitalize border-none gap-1 text-default-600"
|
||||
size="sm"
|
||||
variant="solid"
|
||||
variant="flat"
|
||||
color={color}
|
||||
>
|
||||
{status}
|
||||
</Chip>
|
||||
|
||||
Reference in new issue
Block a user