Merge branch 'main' into PRWLR-4375-Services-Page-Card

This commit is contained in:
Pablo Lara
2024-08-15 08:41:05 +02:00
15 changed files with 311 additions and 209 deletions
+24 -20
View File
@@ -49,8 +49,8 @@ const SSRDataTable = async () => {
checkId: "cloudformation_stack_outputs_find_secrets",
checkLink:
"https://app.prowler.pro/app/findings?date=2024-08-05&search=cloudformation_stack_outputs_find_secrets",
type: "Not applicable",
scanTime: "2024-08-05 @ 14:22:00 UTC",
type: ["Not applicable"],
scanTime: "2024-07-17T09:55:14.191475Z",
findingId: "ba123291-03a5-49a1-b962-6fdb1d2b9c9b",
findingLink:
"https://app.prowler.pro/app/findings?date=2024-08-05&search=ba123291-03a5-49a1-b962-6fdb1d2b9c9b",
@@ -87,8 +87,12 @@ const SSRDataTable = async () => {
checkId: "iam_root_mfa_enabled",
checkLink:
"https://app.prowler.pro/app/findings?search=iam_root_mfa_enabled",
type: "Software and Configuration Checks, Industry and Regulatory Standards, CIS AWS Foundations Benchmark",
scanTime: "2024-08-05 @ 14:22:00 UTC",
type: [
"Software and Configuration Checks",
"Industry and Regulatory Standards",
"CIS AWS Foundations Benchmark",
],
scanTime: "2024-07-17T09:55:14.191475Z",
findingId: "bc3a34e0-16f0-4ea1-ac62-f796c8af3448",
findingLink:
"https://app.prowler.pro/app/findings?date=2024-08-05&search=bc3a34e0-16f0-4ea1-ac62-f796c8af3448",
@@ -123,8 +127,8 @@ const SSRDataTable = async () => {
checkId: "s3_bucket_public_access",
checkLink:
"https://app.prowler.pro/app/findings?date=2024-08-06&search=s3_bucket_public_access",
type: "Security",
scanTime: "2024-08-06 @ 10:45:00 UTC",
type: ["Security"],
scanTime: "2024-07-17T09:55:14.191475Z",
findingId: "e7b3d6a2-39a1-4f0e-9b78-29f4e6f292d1",
findingLink:
"https://app.prowler.pro/app/findings?date=2024-08-06&search=e7b3d6a2-39a1-4f0e-9b78-29f4e6f292d1",
@@ -162,8 +166,8 @@ const SSRDataTable = async () => {
checkId: "iam_password_policy_min_length",
checkLink:
"https://app.prowler.pro/app/findings?date=2024-08-06&search=iam_password_policy_min_length",
type: "Security",
scanTime: "2024-08-06 @ 11:10:00 UTC",
type: ["Security"],
scanTime: "2024-07-17T09:55:14.191475Z",
findingId: "c2b3d1a4-7a9f-4d5c-a9ef-765a1d7f421c",
findingLink:
"https://app.prowler.pro/app/findings?date=2024-08-06&search=c2b3d1a4-7a9f-4d5c-a9ef-765a1d7f421c",
@@ -201,8 +205,8 @@ const SSRDataTable = async () => {
checkId: "rds_instance_public_access",
checkLink:
"https://app.prowler.pro/app/findings?date=2024-08-06&search=rds_instance_public_access",
type: "Security",
scanTime: "2024-08-06 @ 13:15:00 UTC",
type: ["Security"],
scanTime: "2024-07-17T09:55:14.191475Z",
findingId: "f3b4c5d6-19a7-45d8-bc3e-8c5f6a7d8e9b",
findingLink:
"https://app.prowler.pro/app/findings?date=2024-08-06&search=f3b4c5d6-19a7-45d8-bc3e-8c5f6a7d8e9b",
@@ -240,8 +244,8 @@ const SSRDataTable = async () => {
checkId: "ebs_volume_encryption",
checkLink:
"https://app.prowler.pro/app/findings?date=2024-08-07&search=ebs_volume_encryption",
type: "Encryption",
scanTime: "2024-08-07 @ 09:30:00 UTC",
type: ["Encryption"],
scanTime: "2024-07-17T09:55:14.191475Z",
findingId: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
findingLink:
"https://app.prowler.pro/app/findings?date=2024-08-07&search=a1b2c3d4-e5f6-7890-abcd-ef1234567890",
@@ -278,8 +282,8 @@ const SSRDataTable = async () => {
checkId: "cloudtrail_all_regions_enabled",
checkLink:
"https://app.prowler.pro/app/findings?date=2024-08-07&search=cloudtrail_all_regions_enabled",
type: "Logging",
scanTime: "2024-08-07 @ 11:00:00 UTC",
type: ["Logging"],
scanTime: "2024-07-17T09:55:14.191475Z",
findingId: "b2c3d4e5-f6a7-8901-bcde-f123456789ab",
findingLink:
"https://app.prowler.pro/app/findings?date=2024-08-07&search=b2c3d4e5-f6a7-8901-bcde-f123456789ab",
@@ -317,8 +321,8 @@ const SSRDataTable = async () => {
checkId: "ec2_instance_outdated_ami",
checkLink:
"https://app.prowler.pro/app/findings?date=2024-08-07&search=ec2_instance_outdated_ami",
type: "Configuration",
scanTime: "2024-08-07 @ 12:45:00 UTC",
type: ["Configuration"],
scanTime: "2024-07-17T09:55:14.191475Z",
findingId: "c3d4e5f6-a789-0123-bcde-f234567890ab",
findingLink:
"https://app.prowler.pro/app/findings?date=2024-08-07&search=c3d4e5f6-a789-0123-bcde-f234567890ab",
@@ -357,8 +361,8 @@ const SSRDataTable = async () => {
checkId: "cloudwatch_alarms_for_critical_events",
checkLink:
"https://app.prowler.pro/app/findings?date=2024-08-08&search=cloudwatch_alarms_for_critical_events",
type: "Monitoring",
scanTime: "2024-08-08 @ 09:50:00 UTC",
type: ["Monitoring"],
scanTime: "2024-07-17T09:55:14.191475Z",
findingId: "d4e5f6a7-8901-bcde-f345678901ab",
findingLink:
"https://app.prowler.pro/app/findings?date=2024-08-08&search=d4e5f6a7-8901-bcde-f345678901ab",
@@ -396,8 +400,8 @@ const SSRDataTable = async () => {
checkId: "iam_user_least_privilege",
checkLink:
"https://app.prowler.pro/app/findings?date=2024-08-08&search=iam_user_least_privilege",
type: "Security",
scanTime: "2024-08-08 @ 10:15:00 UTC",
type: ["Security"],
scanTime: "2024-07-17T09:55:14.191475Z",
findingId: "e5f6a7b8-9012-bcde-f456789012ab",
findingLink:
"https://app.prowler.pro/app/findings?date=2024-08-08&search=e5f6a7b8-9012-bcde-f456789012ab",
+1 -2
View File
@@ -3,8 +3,7 @@ import "@/styles/globals.css";
import { Metadata, Viewport } from "next";
import React from "react";
import { Toaster } from "@/components/ui";
import { SidebarWrap } from "@/components/ui/sidebar";
import { SidebarWrap, Toaster } from "@/components/ui";
import { fontSans } from "@/config/fonts";
import { siteConfig } from "@/config/site";
import { cn } from "@/lib/utils";
+93
View File
@@ -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>
)}
</>
);
};
+25
View File
@@ -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>
</>
);
};
+25
View File
@@ -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>
</>
);
};
+5 -1
View File
@@ -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>
-134
View File
@@ -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";
+28 -27
View File
@@ -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}
+12 -19
View File
@@ -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>
+1 -1
View File
@@ -58,7 +58,7 @@ export interface FindingProps {
resourceARN: string;
checkId: string;
checkLink: string;
type: string;
type: string[];
scanTime: string;
findingId: string;
findingLink: string;