diff --git a/ui/CHANGELOG.md b/ui/CHANGELOG.md index 1cb3bc30de..e770712ddd 100644 --- a/ui/CHANGELOG.md +++ b/ui/CHANGELOG.md @@ -12,6 +12,7 @@ All notable changes to the **Prowler UI** are documented in this file. - Add GCP credential method (Account Service Key). [(#7872)](https://github.com/prowler-cloud/prowler/pull/7872) - Add compliance detail view: ENS [(#7853)](https://github.com/prowler-cloud/prowler/pull/7853) - Add compliance detail view: ISO [(#7897)](https://github.com/prowler-cloud/prowler/pull/7897) +- Add compliance detail view: CIS [(#7913)](https://github.com/prowler-cloud/prowler/pull/7913) ### 🔄 Changed diff --git a/ui/app/(prowler)/compliance/[compliancetitle]/page.tsx b/ui/app/(prowler)/compliance/[compliancetitle]/page.tsx index b32781e482..189a33531a 100644 --- a/ui/app/(prowler)/compliance/[compliancetitle]/page.tsx +++ b/ui/app/(prowler)/compliance/[compliancetitle]/page.tsx @@ -9,16 +9,24 @@ import { } from "@/actions/compliances"; import { getProvider } from "@/actions/providers"; import { getScans } from "@/actions/scans"; -import { ClientAccordionWrapper } from "@/components/compliance/compliance-accordion/client-accordion-wrapper"; -import { ComplianceHeader } from "@/components/compliance/compliance-header/compliance-header"; -import { SkeletonAccordion } from "@/components/compliance/compliance-skeleton-accordion"; -import { FailedSectionsChart } from "@/components/compliance/failed-sections-chart"; -import { FailedSectionsChartSkeleton } from "@/components/compliance/failed-sections-chart-skeleton"; -import { RequirementsChart } from "@/components/compliance/requirements-chart"; -import { RequirementsChartSkeleton } from "@/components/compliance/requirements-chart-skeleton"; +import { + BarChart, + BarChartSkeleton, + ClientAccordionWrapper, + ComplianceHeader, + HeatmapChart, + HeatmapChartSkeleton, + PieChart, + PieChartSkeleton, + SkeletonAccordion, +} from "@/components/compliance"; import { getComplianceIcon } from "@/components/icons/compliance/IconCompliance"; import { ContentLayout } from "@/components/ui"; -import { getComplianceMapper } from "@/lib/compliance/commons"; +import { + calculateCategoryHeatmapData, + calculateRegionHeatmapData, + getComplianceMapper, +} from "@/lib/compliance/commons"; import { ScanProps } from "@/types"; import { Framework, RequirementsTotals } from "@/types/compliance"; @@ -27,17 +35,23 @@ interface ComplianceDetailSearchParams { version?: string; scanId?: string; "filter[region__in]"?: string; + "filter[cis_profile_level]"?: string; } -const ComplianceLogo = ({ logoPath }: { logoPath: string }) => { +const ComplianceIconSmall = ({ + logoPath, + title, +}: { + logoPath: string; + title: string; +}) => { return ( -
+
Compliance Logo
); @@ -45,17 +59,13 @@ const ComplianceLogo = ({ logoPath }: { logoPath: string }) => { const ChartsWrapper = ({ children, - logoPath, }: { children: React.ReactNode; logoPath?: string; }) => { return ( -
-
- {children} -
- {logoPath && } +
+ {children}
); }; @@ -70,6 +80,7 @@ export default async function ComplianceDetail({ const { compliancetitle } = params; const { complianceId, version, scanId } = searchParams; const regionFilter = searchParams["filter[region__in]"]; + const cisProfileFilter = searchParams["filter[cis_profile_level]"]; const logoPath = getComplianceIcon(compliancetitle); // Create a key that includes region filter for Suspense @@ -88,31 +99,33 @@ export default async function ComplianceDetail({ }); // Expand scans with provider information - const expandedScansData = await Promise.all( - scansData.data.map(async (scan: ScanProps) => { - const providerId = scan.relationships?.provider?.data?.id; + const expandedScansData = scansData?.data?.length + ? await Promise.all( + scansData.data.map(async (scan: ScanProps) => { + const providerId = scan.relationships?.provider?.data?.id; - if (!providerId) { - return { ...scan, providerInfo: null }; - } + if (!providerId) { + return { ...scan, providerInfo: null }; + } - const formData = new FormData(); - formData.append("id", providerId); + const formData = new FormData(); + formData.append("id", providerId); - const providerData = await getProvider(formData); + const providerData = await getProvider(formData); - return { - ...scan, - providerInfo: providerData?.data - ? { - provider: providerData.data.attributes.provider, - uid: providerData.data.attributes.uid, - alias: providerData.data.attributes.alias, - } - : null, - }; - }), - ); + return { + ...scan, + providerInfo: providerData?.data + ? { + provider: providerData.data.attributes.provider, + uid: providerData.data.attributes.uid, + alias: providerData.data.attributes.alias, + } + : null, + }; + }), + ) + : []; const selectedScanId = scanId || expandedScansData[0]?.id || null; @@ -126,11 +139,21 @@ export default async function ComplianceDetail({ const uniqueRegions = metadataInfoData?.data?.attributes?.regions || []; return ( - + + ) : ( + "fluent-mdl2:compliance-audit" + ) + } + > - - + + +
@@ -149,18 +173,57 @@ export default async function ComplianceDetail({ complianceId={complianceId} scanId={selectedScanId} region={regionFilter} + filter={cisProfileFilter} logoPath={logoPath} + uniqueRegions={uniqueRegions} + isRegionFiltered={ + !!regionFilter && + regionFilter.split(",").length < uniqueRegions.length + } /> ); } -const getComplianceData = async ( - complianceId: string, - scanId: string, - region?: string, -): Promise => { +const SSRComplianceContent = async ({ + complianceId, + scanId, + region, + filter, + logoPath, + uniqueRegions, + isRegionFiltered, +}: { + complianceId: string; + scanId: string; + region?: string; + filter?: string; + logoPath?: string; + uniqueRegions: string[]; + isRegionFiltered: boolean; +}) => { + if (!scanId) { + return ( +
+ + + + + + +
+ ); + } + + // Get compliance data and attributes once const [attributesData, requirementsData] = await Promise.all([ getComplianceAttributes(complianceId), getComplianceRequirements({ @@ -172,40 +235,25 @@ const getComplianceData = async ( // Determine framework from the first attribute item const framework = attributesData?.data?.[0]?.attributes?.framework; - - // Get the appropriate mapper for this framework const mapper = getComplianceMapper(framework); - const mappedData = mapper.mapComplianceData(attributesData, requirementsData); + const data = mapper.mapComplianceData( + attributesData, + requirementsData, + filter, + ); - return mappedData; -}; + // Calculate region heatmap data using already obtained data + const regionHeatmapData = await calculateRegionHeatmapData( + complianceId, + scanId, + uniqueRegions, + attributesData, + mapper, + ); + const categoryHeatmapData = calculateCategoryHeatmapData(data); -const SSRComplianceContent = async ({ - complianceId, - scanId, - region, - logoPath, -}: { - complianceId: string; - scanId: string; - region?: string; - logoPath?: string; -}) => { - if (!scanId) { - return ( -
- - - - - -
- ); - } - - const data = await getComplianceData(complianceId, scanId, region); const totalRequirements: RequirementsTotals = data.reduce( - (acc, framework) => ({ + (acc: RequirementsTotals, framework: Framework) => ({ pass: acc.pass + framework.pass, fail: acc.fail + framework.fail, manual: acc.manual + framework.manual, @@ -213,12 +261,6 @@ const SSRComplianceContent = async ({ { pass: 0, fail: 0, manual: 0 }, ); - // Get the framework to determine which mapper to use - const attributesData = await getComplianceAttributes(complianceId); - const framework = attributesData?.data?.[0]?.attributes?.framework; - - // Get the appropriate mapper for this framework - const mapper = getComplianceMapper(framework); const accordionItems = mapper.toAccordionItems(data, scanId); const topFailedSections = mapper.getTopFailedSections(data); @@ -229,12 +271,18 @@ const SSRComplianceContent = async ({ return (
- - + + diff --git a/ui/components/compliance/compliance-accordion/client-accordion-content.tsx b/ui/components/compliance/compliance-accordion/client-accordion-content.tsx index b79a1c1e8e..d22c4b2fe4 100644 --- a/ui/components/compliance/compliance-accordion/client-accordion-content.tsx +++ b/ui/components/compliance/compliance-accordion/client-accordion-content.tsx @@ -11,20 +11,22 @@ import { import { Accordion } from "@/components/ui/accordion/Accordion"; import { DataTable } from "@/components/ui/table"; import { createDict } from "@/lib"; +import { getComplianceMapper } from "@/lib/compliance/commons"; import { ComplianceId, Requirement } from "@/types/compliance"; import { FindingProps, FindingsResponse } from "@/types/components"; -import { ENSCustomDetails } from "../compliance-custom-details/ens-details"; -import { ISOCustomDetails } from "../compliance-custom-details/iso-details"; - interface ClientAccordionContentProps { requirement: Requirement; scanId: string; + framework: string; + disableFindings?: boolean; } export const ClientAccordionContent = ({ requirement, + framework, scanId, + disableFindings = false, }: ClientAccordionContentProps) => { const [findings, setFindings] = useState(null); const [expandedFindings, setExpandedFindings] = useState([]); @@ -41,6 +43,7 @@ export const ClientAccordionContent = ({ useEffect(() => { async function loadFindings() { if ( + !disableFindings && requirement.check_ids?.length > 0 && requirement.status !== "No findings" && (loadedPageRef.current !== pageNumber || @@ -96,7 +99,29 @@ export const ClientAccordionContent = ({ } loadFindings(); - }, [requirement, scanId, pageNumber, sort, region]); + }, [requirement, scanId, pageNumber, sort, region, disableFindings]); + + const renderDetails = () => { + if (!complianceId) { + return null; + } + + const mapper = getComplianceMapper(framework); + const detailsComponent = mapper.getDetailsComponent(requirement); + + return
{detailsComponent}
; + }; + + if (disableFindings) { + return ( +
+ {renderDetails()} +

+ This requirement has no checks; therefore, there are no findings. +

+
+ ); + } const checks = requirement.check_ids || []; const checksList = ( @@ -142,30 +167,6 @@ export const ClientAccordionContent = ({ return
There are no findings for this regions
; }; - const renderDetails = () => { - if (!complianceId) { - return null; - } - - switch (complianceId) { - case "ens_rd2022_aws": - return ( -
- -
- ); - case "iso27001_2013_aws": - case "iso27001_2022_aws": - return ( -
- -
- ); - default: - return null; - } - }; - return (
{renderDetails()} diff --git a/ui/components/compliance/compliance-accordion/compliance-accordion-requeriment-title.tsx b/ui/components/compliance/compliance-accordion/compliance-accordion-requeriment-title.tsx index 1c62e87d29..8f14cddf36 100644 --- a/ui/components/compliance/compliance-accordion/compliance-accordion-requeriment-title.tsx +++ b/ui/components/compliance/compliance-accordion/compliance-accordion-requeriment-title.tsx @@ -13,7 +13,7 @@ export const ComplianceAccordionRequirementTitle = ({ return (
- {name} + {name}
diff --git a/ui/components/compliance/compliance-card.tsx b/ui/components/compliance/compliance-card.tsx index c7a745348e..10a473236f 100644 --- a/ui/components/compliance/compliance-card.tsx +++ b/ui/components/compliance/compliance-card.tsx @@ -73,7 +73,7 @@ export const ComplianceCard: React.FC = ({ const navigateToDetail = () => { // We will unlock this while developing the rest of complainces. - if (!id.includes("ens") && !id.includes("iso")) { + if (!id.includes("ens") && !id.includes("iso") && !id.includes("cis_")) { return; } diff --git a/ui/components/compliance/failed-sections-chart.tsx b/ui/components/compliance/compliance-charts/bar-chart.tsx similarity index 81% rename from ui/components/compliance/failed-sections-chart.tsx rename to ui/components/compliance/compliance-charts/bar-chart.tsx index d27e961737..5701075247 100644 --- a/ui/components/compliance/failed-sections-chart.tsx +++ b/ui/components/compliance/compliance-charts/bar-chart.tsx @@ -3,7 +3,7 @@ import { useTheme } from "next-themes"; import { Bar, - BarChart, + BarChart as RechartsBarChart, Legend, ResponsiveContainer, Tooltip, @@ -24,7 +24,7 @@ const title = ( ); -export const FailedSectionsChart = ({ sections }: FailedSectionsListProps) => { +export const BarChart = ({ sections }: FailedSectionsListProps) => { const { theme } = useTheme(); const getTypeColor = (type: string) => { @@ -84,16 +84,16 @@ export const FailedSectionsChart = ({ sections }: FailedSectionsListProps) => { } return ( -
-
{title}
+
+
{title}
-
+
- { { }} cursor={false} /> - {allTypes.length > 1 && ( - translateType(value)} - wrapperStyle={{ - fontSize: "10px", - display: "flex", - justifyContent: "center", - width: "100%", - paddingTop: "16px", - marginBottom: "4px", - }} - iconType="circle" - layout="horizontal" - verticalAlign="bottom" - /> - )} {allTypes.map((type, i) => ( { radius={i === allTypes.length - 1 ? [0, 4, 4, 0] : [0, 0, 0, 0]} /> ))} - + translateType(value)} + wrapperStyle={{ + fontSize: "10px", + display: "flex", + justifyContent: "center", + width: "100%", + paddingTop: "16px", + marginBottom: "16px", + }} + iconType="circle" + layout="horizontal" + verticalAlign="bottom" + /> +
diff --git a/ui/components/compliance/compliance-charts/heatmap-chart.tsx b/ui/components/compliance/compliance-charts/heatmap-chart.tsx new file mode 100644 index 0000000000..d4cb3b3a0a --- /dev/null +++ b/ui/components/compliance/compliance-charts/heatmap-chart.tsx @@ -0,0 +1,165 @@ +"use client"; + +import { useTheme } from "next-themes"; +import { useState } from "react"; + +import { CategoryData, RegionData } from "@/types/compliance"; + +interface HeatmapChartProps { + regions: RegionData[]; + categories?: CategoryData[]; + isRegionFiltered?: boolean; // Indicates if a region filter is active + filteredRegionName?: string; // Name of the filtered region +} + +const getHeatmapColor = (percentage: number): string => { + if (percentage === 0) return "#10b981"; // Green for 0% failures + if (percentage <= 25) return "#eab308"; // Yellow + if (percentage <= 50) return "#f97316"; // Orange + if (percentage <= 100) return "#ef4444"; // Red + return "#ef4444"; +}; + +const capitalizeFirstLetter = (text: string): string => { + const lowerText = text.toLowerCase(); + const firstLetterIndex = lowerText.search(/[a-zA-Z]/); + if (firstLetterIndex === -1) return text; // No letters found + + return ( + lowerText.slice(0, firstLetterIndex) + + lowerText.charAt(firstLetterIndex).toUpperCase() + + lowerText.slice(firstLetterIndex + 1) + ); +}; + +export const HeatmapChart = ({ + regions, + categories = [], + isRegionFiltered = false, +}: HeatmapChartProps) => { + const { theme } = useTheme(); + const [hoveredItem, setHoveredItem] = useState< + RegionData | CategoryData | null + >(null); + const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); + + // Determine what data to show and prepare it + const dataToShow = isRegionFiltered ? categories : regions; + const heatmapData = dataToShow + .filter((item) => item.totalRequirements > 0) + .sort((a, b) => b.failurePercentage - a.failurePercentage) + .slice(0, 9); // Exactly 9 items for 3x3 grid + + // Check if there are no items with data + if (!dataToShow || dataToShow.length === 0 || heatmapData.length === 0) { + const noDataMessage = isRegionFiltered + ? "No category data available" + : "No regional data available"; + + return ( +
+

+ {isRegionFiltered + ? "Categories Failure Rate" + : "Failure Rate by Region"} +

+
+

{noDataMessage}

+
+
+ ); + } + + const handleMouseEnter = ( + item: RegionData | CategoryData, + event: React.MouseEvent, + ) => { + setHoveredItem(item); + setMousePosition({ x: event.clientX, y: event.clientY }); + }; + + const handleMouseMove = (event: React.MouseEvent) => { + setMousePosition({ x: event.clientX, y: event.clientY }); + }; + + const handleMouseLeave = () => { + setHoveredItem(null); + }; + + return ( +
+
+

+ {isRegionFiltered + ? "Categories Failure Rate" + : "Failure Rate by Region"} +

+
+ +
+ {/* 3x3 Grid */} +
+ {heatmapData.map((item) => ( +
handleMouseEnter(item, e)} + onMouseMove={handleMouseMove} + onMouseLeave={handleMouseLeave} + > +
+
+ {isRegionFiltered + ? capitalizeFirstLetter(item.name) + : item.name} +
+
+ {item.failurePercentage}% +
+
+
+ ))} +
+ + {/* Custom Tooltip */} + {hoveredItem && ( +
+
+ {isRegionFiltered + ? capitalizeFirstLetter(hoveredItem.name) + : hoveredItem.name} +
+
Failure Rate: {hoveredItem.failurePercentage}%
+
+ Failed: {hoveredItem.failedRequirements}/ + {hoveredItem.totalRequirements} +
+
+ )} +
+
+ ); +}; diff --git a/ui/components/compliance/requirements-chart.tsx b/ui/components/compliance/compliance-charts/pie-chart.tsx similarity index 95% rename from ui/components/compliance/requirements-chart.tsx rename to ui/components/compliance/compliance-charts/pie-chart.tsx index eb40619a8b..3e5cc01fb8 100644 --- a/ui/components/compliance/requirements-chart.tsx +++ b/ui/components/compliance/compliance-charts/pie-chart.tsx @@ -1,11 +1,17 @@ "use client"; import { useTheme } from "next-themes"; -import { Cell, Label, Pie, PieChart, Tooltip } from "recharts"; +import { + Cell, + Label, + Pie, + PieChart as RechartsPieChart, + Tooltip, +} from "recharts"; import { ChartConfig, ChartContainer } from "@/components/ui/chart/Chart"; -interface RequirementsChartProps { +interface PieChartProps { pass: number; fail: number; manual: number; @@ -29,11 +35,7 @@ const chartConfig = { }, } satisfies ChartConfig; -export const RequirementsChart = ({ - pass, - fail, - manual, -}: RequirementsChartProps) => { +export const PieChart = ({ pass, fail, manual }: PieChartProps) => { const { theme } = useTheme(); const chartData = [ @@ -119,7 +121,7 @@ export const RequirementsChart = ({ config={chartConfig} className="aspect-square w-[200px] min-w-[200px]" > - + } @@ -168,7 +170,7 @@ export const RequirementsChart = ({ }} /> - +
diff --git a/ui/components/compliance/compliance-custom-details/cis-details.tsx b/ui/components/compliance/compliance-custom-details/cis-details.tsx new file mode 100644 index 0000000000..e4d99db68f --- /dev/null +++ b/ui/components/compliance/compliance-custom-details/cis-details.tsx @@ -0,0 +1,150 @@ +import ReactMarkdown from "react-markdown"; + +import { Requirement } from "@/types/compliance"; + +interface CISDetailsProps { + requirement: Requirement; +} + +export const CISCustomDetails = ({ requirement }: CISDetailsProps) => { + const processReferences = ( + references: string | number | string[] | undefined, + ): string[] => { + if (typeof references !== "string") return []; + + // Use regex to extract all URLs that start with https:// + const urlRegex = /https:\/\/[^:]+/g; + const urls = references.match(urlRegex); + + return urls || []; + }; + + return ( +
+ {requirement.profile && ( +
+

+ Profile Level +

+

{requirement.profile}

+
+ )} + + {requirement.subsection && ( +
+

+ SubSection +

+

{requirement.subsection}

+
+ )} + + {requirement.assessment_status && ( +
+

+ Assessment Status +

+

{requirement.assessment_status}

+
+ )} + + {requirement.description && ( +
+

+ Description +

+

{requirement.description}

+
+ )} + + {requirement.rationale_statement && ( +
+

+ Rationale Statement +

+

{requirement.rationale_statement}

+
+ )} + + {requirement.impact_statement && ( +
+

+ Impact Statement +

+

{requirement.impact_statement}

+
+ )} + + {requirement.remediation_procedure && + typeof requirement.remediation_procedure === "string" && ( +
+

+ Remediation Procedure +

+ {/* Prettier -> "plugins": ["prettier-plugin-tailwindcss"] is not ready yet to "prose": */} + {/* eslint-disable-next-line */} +
+ {requirement.remediation_procedure} +
+
+ )} + + {requirement.audit_procedure && + typeof requirement.audit_procedure === "string" && ( +
+

+ Audit Procedure +

+ {/* eslint-disable-next-line */} +
+ {requirement.audit_procedure} +
+
+ )} + + {requirement.additional_information && ( +
+

+ Additional Information +

+

+ {requirement.additional_information} +

+
+ )} + + {requirement.default_value && ( +
+

+ Default Value +

+

{requirement.default_value}

+
+ )} + + {requirement.references && ( +
+

+ References +

+
+ {processReferences(requirement.references).map( + (url: string, index: number) => ( + + ), + )} +
+
+ )} +
+ ); +}; diff --git a/ui/components/compliance/compliance-header/compliance-header.tsx b/ui/components/compliance/compliance-header/compliance-header.tsx index 9e31f4de5d..e6bfe0dd2d 100644 --- a/ui/components/compliance/compliance-header/compliance-header.tsx +++ b/ui/components/compliance/compliance-header/compliance-header.tsx @@ -13,6 +13,7 @@ interface ComplianceHeaderProps { uniqueRegions: string[]; showSearch?: boolean; showRegionFilter?: boolean; + framework?: string; // Framework name to show specific filters } export const ComplianceHeader = ({ @@ -20,25 +21,46 @@ export const ComplianceHeader = ({ uniqueRegions, showSearch = true, showRegionFilter = true, + framework, }: ComplianceHeaderProps) => { + const frameworkFilters = []; + + // Add CIS Profile Level filter if framework is CIS + if (framework === "CIS") { + frameworkFilters.push({ + key: "cis_profile_level", + labelCheckboxGroup: "Level", + values: ["Level 1", "Level 2"], + index: 0, // Show first + showSelectAll: false, // No "Select All" option since Level 2 includes Level 1 + defaultValues: ["Level 2"], // Default to Level 2 selected (which includes Level 1) + }); + } + + // Prepare region filters + const regionFilters = showRegionFilter + ? [ + { + key: "region__in", + labelCheckboxGroup: "Regions", + values: uniqueRegions, + index: 1, // Show after framework filters + defaultToSelectAll: true, // Default to all regions selected + }, + ] + : []; + + const allFilters = [...frameworkFilters, ...regionFilters]; + return ( <> {showSearch && } - {showRegionFilter && ( + {allFilters.length > 0 && ( <> - + )} diff --git a/ui/components/compliance/index.ts b/ui/components/compliance/index.ts index fc730a267d..5beaaf4c5b 100644 --- a/ui/components/compliance/index.ts +++ b/ui/components/compliance/index.ts @@ -1,4 +1,21 @@ +export * from "./compliance-accordion/client-accordion-content"; +export * from "./compliance-accordion/client-accordion-wrapper"; +export * from "./compliance-accordion/compliance-accordion-requeriment-title"; +export * from "./compliance-accordion/compliance-accordion-title"; export * from "./compliance-card"; +export * from "./compliance-charts/bar-chart"; +export * from "./compliance-charts/heatmap-chart"; +export * from "./compliance-charts/pie-chart"; +export * from "./compliance-custom-details/cis-details"; +export * from "./compliance-custom-details/ens-details"; +export * from "./compliance-custom-details/iso-details"; +export * from "./compliance-header/compliance-header"; export * from "./compliance-header/compliance-scan-info"; -export * from "./compliance-skeleton-grid"; +export * from "./compliance-header/data-compliance"; +export * from "./compliance-header/select-scan-compliance-data"; export * from "./no-scans-available"; +export * from "./skeletons/bar-chart-skeleton"; +export * from "./skeletons/compliance-accordion-skeleton"; +export * from "./skeletons/compliance-grid-skeleton"; +export * from "./skeletons/heatmap-chart-skeleton"; +export * from "./skeletons/pie-chart-skeleton"; diff --git a/ui/components/compliance/failed-sections-chart-skeleton.tsx b/ui/components/compliance/skeletons/bar-chart-skeleton.tsx similarity index 97% rename from ui/components/compliance/failed-sections-chart-skeleton.tsx rename to ui/components/compliance/skeletons/bar-chart-skeleton.tsx index 24164ae55c..05f26ae938 100644 --- a/ui/components/compliance/failed-sections-chart-skeleton.tsx +++ b/ui/components/compliance/skeletons/bar-chart-skeleton.tsx @@ -2,7 +2,7 @@ import { Skeleton } from "@nextui-org/react"; -export const FailedSectionsChartSkeleton = () => { +export const BarChartSkeleton = () => { return (
{/* Title skeleton */} diff --git a/ui/components/compliance/compliance-skeleton-accordion.tsx b/ui/components/compliance/skeletons/compliance-accordion-skeleton.tsx similarity index 100% rename from ui/components/compliance/compliance-skeleton-accordion.tsx rename to ui/components/compliance/skeletons/compliance-accordion-skeleton.tsx diff --git a/ui/components/compliance/compliance-skeleton-grid.tsx b/ui/components/compliance/skeletons/compliance-grid-skeleton.tsx similarity index 100% rename from ui/components/compliance/compliance-skeleton-grid.tsx rename to ui/components/compliance/skeletons/compliance-grid-skeleton.tsx diff --git a/ui/components/compliance/skeletons/heatmap-chart-skeleton.tsx b/ui/components/compliance/skeletons/heatmap-chart-skeleton.tsx new file mode 100644 index 0000000000..ae247900f2 --- /dev/null +++ b/ui/components/compliance/skeletons/heatmap-chart-skeleton.tsx @@ -0,0 +1,28 @@ +"use client"; + +import { Skeleton } from "@nextui-org/react"; + +export const HeatmapChartSkeleton = () => { + return ( +
+ {/* Title skeleton */} + +
+ + + {/* Heatmap area skeleton - 3x3 grid like the real component */} +
+
+ {Array.from({ length: 9 }).map((_, index) => ( + +
+ + ))} +
+
+
+ ); +}; diff --git a/ui/components/compliance/requirements-chart-skeleton.tsx b/ui/components/compliance/skeletons/pie-chart-skeleton.tsx similarity index 97% rename from ui/components/compliance/requirements-chart-skeleton.tsx rename to ui/components/compliance/skeletons/pie-chart-skeleton.tsx index eae6ff6e4e..f21c653b24 100644 --- a/ui/components/compliance/requirements-chart-skeleton.tsx +++ b/ui/components/compliance/skeletons/pie-chart-skeleton.tsx @@ -2,7 +2,7 @@ import { Skeleton } from "@nextui-org/react"; -export const RequirementsChartSkeleton = () => { +export const PieChartSkeleton = () => { return (
{/* Title skeleton */} diff --git a/ui/components/ui/content-layout/content-layout.tsx b/ui/components/ui/content-layout/content-layout.tsx index f1fdb13a57..518fb58efa 100644 --- a/ui/components/ui/content-layout/content-layout.tsx +++ b/ui/components/ui/content-layout/content-layout.tsx @@ -1,12 +1,13 @@ -import { Suspense, use } from "react"; +import { ReactNode, Suspense, use } from "react"; import { getUserInfo } from "@/actions/users/users"; import { Navbar } from "../nav-bar/navbar"; import { SkeletonContentLayout } from "./skeleton-content-layout"; + interface ContentLayoutProps { title: string; - icon: string; + icon: string | ReactNode; children: React.ReactNode; } diff --git a/ui/components/ui/custom/custom-dropdown-filter.tsx b/ui/components/ui/custom/custom-dropdown-filter.tsx index 254efa5024..b17828083a 100644 --- a/ui/components/ui/custom/custom-dropdown-filter.tsx +++ b/ui/components/ui/custom/custom-dropdown-filter.tsx @@ -12,7 +12,13 @@ import { } from "@nextui-org/react"; import { ChevronDown, X } from "lucide-react"; import { useSearchParams } from "next/navigation"; -import React, { useCallback, useEffect, useMemo, useState } from "react"; +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; import { CustomDropdownFilterProps } from "@/types"; @@ -25,6 +31,7 @@ export const CustomDropdownFilter = ({ const searchParams = useSearchParams(); const [groupSelected, setGroupSelected] = useState(new Set()); const [isOpen, setIsOpen] = useState(false); + const hasUserInteracted = useRef(false); const filterValues = useMemo(() => filter?.values || [], [filter?.values]); const selectedValues = Array.from(groupSelected).filter( @@ -42,24 +49,66 @@ export const CustomDropdownFilter = ({ useEffect(() => { if (activeFilterValue.length > 0) { const newSelection = new Set(activeFilterValue); - if (newSelection.size === filterValues.length) { + if ( + newSelection.size === filterValues.length && + filter?.showSelectAll !== false + ) { newSelection.add("all"); } setGroupSelected(newSelection); - } else { - setGroupSelected(new Set()); + } else if (!hasUserInteracted.current) { + // Handle default behavior when no URL params exist + // Only apply defaults if user hasn't interacted yet + // Only set visual state, don't trigger URL changes automatically + if (filter?.defaultToSelectAll && filterValues.length > 0) { + const newSelection = new Set(filterValues); + if (filter?.showSelectAll !== false) { + newSelection.add("all"); + } + setGroupSelected(newSelection); + // DON'T notify parent automatically - wait for user interaction + } else if (filter?.defaultValues && filter.defaultValues.length > 0) { + // Handle specific default values + const validDefaultValues = filter.defaultValues.filter((value) => + filterValues.includes(value), + ); + const newSelection = new Set(validDefaultValues); + + // Add "all" if all items are selected and showSelectAll is not false + if ( + validDefaultValues.length === filterValues.length && + filter?.showSelectAll !== false + ) { + newSelection.add("all"); + } + + setGroupSelected(newSelection); + // DON'T notify parent automatically - wait for user interaction + } else { + setGroupSelected(new Set()); + } } - }, [activeFilterValue, filterValues.length]); + }, [ + activeFilterValue, + filterValues, + filter?.defaultToSelectAll, + filter?.defaultValues, + filter?.showSelectAll, + ]); const updateSelection = useCallback( (newValues: string[]) => { + // Mark that user has interacted with the filter + hasUserInteracted.current = true; + const actualValues = newValues.filter((key) => key !== "all"); const newSelection = new Set(actualValues); - // Auto-add "all" if all items are selected + // Auto-add "all" if all items are selected and showSelectAll is not false if ( actualValues.length === filterValues.length && - filterValues.length > 0 + filterValues.length > 0 && + filter?.showSelectAll !== false ) { newSelection.add("all"); } @@ -69,7 +118,7 @@ export const CustomDropdownFilter = ({ // Notify parent with actual values (excluding "all") onFilterChange?.(filter.key, actualValues); }, - [filterValues.length, onFilterChange, filter.key], + [filterValues.length, onFilterChange, filter.key, filter?.showSelectAll], ); const onSelectionChange = useCallback( @@ -194,16 +243,20 @@ export const CustomDropdownFilter = ({ onValueChange={onSelectionChange} className="font-bold" > - - Select All - - + {filter?.showSelectAll !== false && ( + <> + + Select All + + + + )}
- + {typeof icon === "string" ? ( + + ) : ( +
+ {icon} +
+ )}

{title}

diff --git a/ui/lib/compliance/cis.tsx b/ui/lib/compliance/cis.tsx new file mode 100644 index 0000000000..df59c6dd95 --- /dev/null +++ b/ui/lib/compliance/cis.tsx @@ -0,0 +1,204 @@ +import { ClientAccordionContent } from "@/components/compliance/compliance-accordion/client-accordion-content"; +import { ComplianceAccordionRequirementTitle } from "@/components/compliance/compliance-accordion/compliance-accordion-requeriment-title"; +import { ComplianceAccordionTitle } from "@/components/compliance/compliance-accordion/compliance-accordion-title"; +import { AccordionItemProps } from "@/components/ui/accordion/Accordion"; +import { FindingStatus } from "@/components/ui/table/status-finding-badge"; +import { + AttributesData, + CISAttributesMetadata, + Framework, + Requirement, + RequirementItemData, + RequirementsData, + RequirementStatus, +} from "@/types/compliance"; + +export const mapComplianceData = ( + attributesData: AttributesData, + requirementsData: RequirementsData, + filter?: string, // "Level 1" or "Level 2" or undefined (show all) +): Framework[] => { + const attributes = attributesData?.data || []; + const requirements = requirementsData?.data || []; + + // Create a map for quick lookup of requirements by id + const requirementsMap = new Map(); + requirements.forEach((req: RequirementItemData) => { + requirementsMap.set(req.id, req); + }); + + const frameworks: Framework[] = []; + + // Process attributes and merge with requirements data + for (const attributeItem of attributes) { + const id = attributeItem.id; + const metadataArray = attributeItem.attributes?.attributes + ?.metadata as unknown as CISAttributesMetadata[]; + const attrs = metadataArray?.[0]; + if (!attrs) continue; + + // Apply profile filter + if (filter === "Level 1" && attrs.Profile !== "Level 1") { + continue; // Skip Level 2 requirements when Level 1 is selected + } + + // Get corresponding requirement data + const requirementData = requirementsMap.get(id); + if (!requirementData) continue; + + const frameworkName = attributeItem.attributes.framework; + const sectionName = attrs.Section; + const description = attributeItem.attributes.description; + const status = requirementData.attributes.status || ""; + const checks = attributeItem.attributes.attributes.check_ids || []; + const requirementName = id; + + // Find or create framework + let framework = frameworks.find((f) => f.name === frameworkName); + if (!framework) { + framework = { + name: frameworkName, + pass: 0, + fail: 0, + manual: 0, + categories: [], + }; + frameworks.push(framework); + } + + const normalizedSectionName = sectionName.replace(/^(\d+)\s/, "$1. "); + let category = framework.categories.find( + (c) => c.name === normalizedSectionName, + ); + + if (!category) { + category = { + name: normalizedSectionName, + pass: 0, + fail: 0, + manual: 0, + controls: [], + }; + framework.categories.push(category); + } + + // Create a control for this requirement (each requirement is its own control) + const controlLabel = `${id} - ${description}`; + const control = { + label: controlLabel, + pass: 0, + fail: 0, + manual: 0, + requirements: [] as Requirement[], + }; + + // Create requirement + const finalStatus: RequirementStatus = status as RequirementStatus; + const requirement: Requirement = { + name: requirementName, + description: attrs.Description, + status: finalStatus, + check_ids: checks, + pass: finalStatus === "PASS" ? 1 : 0, + fail: finalStatus === "FAIL" ? 1 : 0, + manual: finalStatus === "MANUAL" ? 1 : 0, + profile: attrs.Profile, + subsection: attrs.SubSection || "", + assessment_status: attrs.AssessmentStatus, + rationale_statement: attrs.RationaleStatement, + impact_statement: attrs.ImpactStatement, + remediation_procedure: attrs.RemediationProcedure, + audit_procedure: attrs.AuditProcedure, + additional_information: attrs.AdditionalInformation, + default_value: attrs.DefaultValue || "", + references: attrs.References, + }; + + control.requirements.push(requirement); + + // Update control counters + if (requirement.status === "MANUAL") { + control.manual++; + } else if (requirement.status === "PASS") { + control.pass++; + } else if (requirement.status === "FAIL") { + control.fail++; + } + + category.controls.push(control); + } + + // Calculate counters for categories and frameworks + frameworks.forEach((framework) => { + framework.pass = 0; + framework.fail = 0; + framework.manual = 0; + + framework.categories.forEach((category) => { + category.pass = 0; + category.fail = 0; + category.manual = 0; + + category.controls.forEach((control) => { + category.pass += control.pass; + category.fail += control.fail; + category.manual += control.manual; + }); + + framework.pass += category.pass; + framework.fail += category.fail; + framework.manual += category.manual; + }); + }); + + return frameworks; +}; + +export const toAccordionItems = ( + data: Framework[], + scanId: string | undefined, +): AccordionItemProps[] => { + return data.flatMap((framework) => + framework.categories.map((category) => { + return { + key: `${framework.name}-${category.name}`, + title: ( + + ), + content: "", + items: category.controls.map((control, i: number) => { + const requirement = control.requirements[0]; // Each control has one requirement + const itemKey = `${framework.name}-${category.name}-control-${i}`; + + return { + key: itemKey, + title: ( + + ), + content: ( + + ), + items: [], + }; + }), + }; + }), + ); +}; diff --git a/ui/lib/compliance/commons.ts b/ui/lib/compliance/commons.ts index 747cd59c06..6d0c3c7779 100644 --- a/ui/lib/compliance/commons.ts +++ b/ui/lib/compliance/commons.ts @@ -1,11 +1,23 @@ +import React from "react"; + +import { CISCustomDetails } from "@/components/compliance/compliance-custom-details/cis-details"; +import { ENSCustomDetails } from "@/components/compliance/compliance-custom-details/ens-details"; +import { ISOCustomDetails } from "@/components/compliance/compliance-custom-details/iso-details"; import { AccordionItemProps } from "@/components/ui/accordion/Accordion"; import { AttributesData, + CategoryData, FailedSection, Framework, + RegionData, + Requirement, RequirementsData, } from "@/types/compliance"; +import { + mapComplianceData as mapCISComplianceData, + toAccordionItems as toCISAccordionItems, +} from "./cis"; import { mapComplianceData as mapENSComplianceData, toAccordionItems as toENSAccordionItems, @@ -19,12 +31,14 @@ export interface ComplianceMapper { mapComplianceData: ( attributesData: AttributesData, requirementsData: RequirementsData, + filter?: string, ) => Framework[]; toAccordionItems: ( data: Framework[], scanId: string | undefined, ) => AccordionItemProps[]; getTopFailedSections: (mappedData: Framework[]) => FailedSection[]; + getDetailsComponent: (requirement: Requirement) => React.ReactNode; } // Common function for getting top failed sections @@ -70,11 +84,22 @@ const complianceMappers: Record = { mapComplianceData: mapENSComplianceData, toAccordionItems: toENSAccordionItems, getTopFailedSections, + getDetailsComponent: (requirement: Requirement) => + React.createElement(ENSCustomDetails, { requirement }), }, ISO27001: { mapComplianceData: mapISOComplianceData, toAccordionItems: toISOAccordionItems, getTopFailedSections, + getDetailsComponent: (requirement: Requirement) => + React.createElement(ISOCustomDetails, { requirement }), + }, + CIS: { + mapComplianceData: mapCISComplianceData, + toAccordionItems: toCISAccordionItems, + getTopFailedSections, + getDetailsComponent: (requirement: Requirement) => + React.createElement(CISCustomDetails, { requirement }), }, }; @@ -83,7 +108,7 @@ const defaultMapper: ComplianceMapper = complianceMappers.ENS; /** * Get the appropriate compliance mapper based on the framework name - * @param framework - The framework name (e.g., "ENS", "ISO27001") + * @param framework - The framework name (e.g., "ENS", "ISO27001", "CIS") * @returns ComplianceMapper object with specific functions for the framework */ export const getComplianceMapper = (framework?: string): ComplianceMapper => { @@ -93,3 +118,140 @@ export const getComplianceMapper = (framework?: string): ComplianceMapper => { return complianceMappers[framework] || defaultMapper; }; + +export const calculateRegionHeatmapData = async ( + complianceId: string, + scanId: string, + uniqueRegions: string[], + attributesData: AttributesData, + mapper: ComplianceMapper, +): Promise => { + if (!complianceId || !scanId || !uniqueRegions?.length) { + return []; + } + + try { + const { getComplianceRequirements } = await import("@/actions/compliances"); + + // Get data for each region in parallel + const regionPromises = uniqueRegions.map(async (region) => { + try { + // Only need to fetch requirements data per region + const regionRequirementsData = await getComplianceRequirements({ + complianceId, + scanId, + region, // Filter by specific region + }); + + // Map the data using the provided mapper + const mappedData = mapper.mapComplianceData( + attributesData, + regionRequirementsData, + ); + + // Calculate totals for this region + const regionTotals = mappedData.reduce( + (acc, framework) => ({ + pass: acc.pass + framework.pass, + fail: acc.fail + framework.fail, + manual: acc.manual + framework.manual, + }), + { pass: 0, fail: 0, manual: 0 }, + ); + + const totalRequirements = + regionTotals.pass + regionTotals.fail + regionTotals.manual; + const failurePercentage = + totalRequirements > 0 + ? Math.round((regionTotals.fail / totalRequirements) * 100) + : 0; + + return { + name: region, + failurePercentage, + totalRequirements, + failedRequirements: regionTotals.fail, + }; + } catch (error) { + console.error(`Error fetching data for region ${region}:`, error); + return { + name: region, + failurePercentage: 0, + totalRequirements: 0, + failedRequirements: 0, + }; + } + }); + + const regionData = await Promise.all(regionPromises); + + // Filter, sort and limit to top 9 regions for 3x3 grid + const filteredData = regionData + .filter((region) => region.totalRequirements > 0) + .sort((a, b) => b.failurePercentage - a.failurePercentage) + .slice(0, 9); + + return filteredData; + } catch (error) { + console.error("Error calculating region heatmap data:", error); + return []; + } +}; + +export const calculateCategoryHeatmapData = ( + complianceData: Framework[], +): CategoryData[] => { + if (!complianceData?.length) { + return []; + } + + try { + const categoryMap = new Map< + string, + { pass: number; fail: number; manual: number } + >(); + + // Aggregate data by category + complianceData.forEach((framework) => { + framework.categories.forEach((category) => { + const existing = categoryMap.get(category.name) || { + pass: 0, + fail: 0, + manual: 0, + }; + categoryMap.set(category.name, { + pass: existing.pass + category.pass, + fail: existing.fail + category.fail, + manual: existing.manual + category.manual, + }); + }); + }); + + const categoryData: CategoryData[] = Array.from(categoryMap.entries()).map( + ([name, stats]) => { + const totalRequirements = stats.pass + stats.fail + stats.manual; + const failurePercentage = + totalRequirements > 0 + ? Math.round((stats.fail / totalRequirements) * 100) + : 0; + + return { + name, + failurePercentage, + totalRequirements, + failedRequirements: stats.fail, + }; + }, + ); + + const filteredData = categoryData + .filter((category) => category.totalRequirements > 0) + .sort((a, b) => b.failurePercentage - a.failurePercentage) + .slice(0, 9); // Show top 9 categories + + return filteredData; + } catch (error) { + console.error("Error calculating category heatmap data:", error); + return []; + } +}; diff --git a/ui/lib/compliance/ens.tsx b/ui/lib/compliance/ens.tsx index c46c659ecd..a3103035a3 100644 --- a/ui/lib/compliance/ens.tsx +++ b/ui/lib/compliance/ens.tsx @@ -14,6 +14,10 @@ import { } from "@/types/compliance"; export const translateType = (type: string) => { + if (!type) { + return ""; + } + switch (type.toLowerCase()) { case "requisito": return "Requirement"; @@ -223,12 +227,13 @@ export const toAccordionItems = ( ), - items: [], - isDisabled: - requirement.check_ids.length === 0 && - requirement.manual === 0, }; }), isDisabled: diff --git a/ui/lib/compliance/iso.tsx b/ui/lib/compliance/iso.tsx index 92e5cb999d..8fd2e7ae7e 100644 --- a/ui/lib/compliance/iso.tsx +++ b/ui/lib/compliance/iso.tsx @@ -192,12 +192,14 @@ export const toAccordionItems = ( ), items: [], - isDisabled: - requirement.check_ids.length === 0 && - requirement.manual === 0, }; }), isDisabled: diff --git a/ui/package-lock.json b/ui/package-lock.json index b00fb5b597..14523f6c8f 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -22,6 +22,7 @@ "@radix-ui/react-toast": "^1.2.4", "@react-aria/ssr": "3.9.4", "@react-aria/visually-hidden": "3.8.12", + "@tailwindcss/typography": "^0.5.16", "@tanstack/react-table": "^8.19.3", "add": "^2.0.6", "alert": "^6.0.2", @@ -43,6 +44,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-hook-form": "^7.52.2", + "react-markdown": "^10.1.0", "recharts": "^2.15.2", "server-only": "^0.0.1", "shadcn-ui": "^0.2.3", @@ -7418,6 +7420,34 @@ "tslib": "^2.4.0" } }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.16.tgz", + "integrity": "sha512-0wDLwCVF5V3x3b1SGXPCDcdsbDHMBe+lkFzBRaHeLvNi+nrrnZ1lA18u+OTWO8iSWU2GxUOCvlXtDuqftc1oiA==", + "license": "MIT", + "dependencies": { + "lodash.castarray": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" + } + }, + "node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@tanstack/react-table": { "version": "8.19.3", "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.19.3.tgz", @@ -7539,6 +7569,39 @@ "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==" }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "license": "MIT" + }, + "node_modules/@types/estree-jsx": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree-jsx/-/estree-jsx-1.0.5.tgz", + "integrity": "sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==", + "license": "MIT", + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -7560,6 +7623,21 @@ "@types/lodash": "*" } }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, "node_modules/@types/node": { "version": "20.5.7", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.5.7.tgz", @@ -7569,14 +7647,12 @@ "node_modules/@types/prop-types": { "version": "15.7.12", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.12.tgz", - "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==", - "devOptional": true + "integrity": "sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q==" }, "node_modules/@types/react": { "version": "18.3.3", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", - "devOptional": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.0.2" @@ -7591,6 +7667,12 @@ "@types/react": "*" } }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "license": "MIT" + }, "node_modules/@types/uuid": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", @@ -7785,8 +7867,7 @@ "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, "node_modules/acorn": { "version": "8.12.1", @@ -8178,6 +8259,16 @@ "deep-equal": "^2.0.5" } }, + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -8368,6 +8459,16 @@ ], "license": "CC-BY-4.0" }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -8384,6 +8485,46 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-reference-invalid": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz", + "integrity": "sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -8982,6 +9123,16 @@ "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -9269,6 +9420,19 @@ "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" }, + "node_modules/decode-named-character-reference": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.1.0.tgz", + "integrity": "sha512-Wy+JTSbFThEOXQIR2L6mxJvEs+veIzpmqD7ynWxMXGpnk3smkHQOp6forLdHsKpAMW9iJpaBBIxz285t1n1C3w==", + "license": "MIT", + "dependencies": { + "character-entities": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/deep-equal": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", @@ -9360,6 +9524,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", @@ -9374,6 +9547,19 @@ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -10352,6 +10538,16 @@ "node": ">=4.0" } }, + "node_modules/estree-util-is-identifier-name": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz", + "integrity": "sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -10390,6 +10586,12 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -10941,6 +11143,56 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-jsx-runtime": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz", + "integrity": "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "comma-separated-tokens": "^2.0.0", + "devlop": "^1.0.0", + "estree-util-is-identifier-name": "^3.0.0", + "hast-util-whitespace": "^3.0.0", + "mdast-util-mdx-expression": "^2.0.0", + "mdast-util-mdx-jsx": "^3.0.0", + "mdast-util-mdxjs-esm": "^2.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "style-to-js": "^1.0.0", + "unist-util-position": "^5.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/html-url-attributes": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-url-attributes/-/html-url-attributes-3.0.1.tgz", + "integrity": "sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/https-proxy-agent": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-6.2.1.tgz", @@ -11055,6 +11307,12 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/inline-style-parser": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.4.tgz", + "integrity": "sha512-0aO8FkhNZlj/ZIbNi7Lxxr12obT7cL1moPfE4tg1LkX7LlLfC6DeX4l2ZEud1ukP9jNQyNnfzQVqwbwmAATY4Q==", + "license": "MIT" + }, "node_modules/internal-slot": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", @@ -11088,6 +11346,30 @@ "tslib": "^2.4.0" } }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "license": "MIT", + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-arguments": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", @@ -11235,6 +11517,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -11289,6 +11581,16 @@ "node": ">=0.10.0" } }, + "node_modules/is-hexadecimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz", + "integrity": "sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/is-interactive": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", @@ -11356,6 +11658,18 @@ "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -11887,6 +12201,12 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.castarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", + "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==", + "license": "MIT" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -11902,6 +12222,12 @@ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, "node_modules/lodash.kebabcase": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", @@ -11915,8 +12241,7 @@ "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, "node_modules/lodash.omit": { "version": "4.5.0", @@ -12078,6 +12403,16 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/longest-streak": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", + "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -12105,6 +12440,159 @@ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/mdast-util-from-markdown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", + "integrity": "sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark": "^4.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-expression": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz", + "integrity": "sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdx-jsx": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz", + "integrity": "sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "devlop": "^1.1.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0", + "parse-entities": "^4.0.0", + "stringify-entities": "^4.0.0", + "unist-util-stringify-position": "^4.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-mdxjs-esm": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz", + "integrity": "sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==", + "license": "MIT", + "dependencies": { + "@types/estree-jsx": "^1.0.0", + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-phrasing": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", + "integrity": "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz", + "integrity": "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-markdown": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/mdast-util-to-markdown/-/mdast-util-to-markdown-2.1.2.tgz", + "integrity": "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "@types/unist": "^3.0.0", + "longest-streak": "^3.0.0", + "mdast-util-phrasing": "^4.0.0", + "mdast-util-to-string": "^4.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-decode-string": "^2.0.0", + "unist-util-visit": "^5.0.0", + "zwitch": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/mdast-util-to-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz", + "integrity": "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -12118,6 +12606,448 @@ "node": ">= 8" } }, + "node_modules/micromark": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.2.tgz", + "integrity": "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "@types/debug": "^4.0.0", + "debug": "^4.0.0", + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-core-commonmark": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-combine-extensions": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-core-commonmark": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/micromark-core-commonmark/-/micromark-core-commonmark-2.0.3.tgz", + "integrity": "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "devlop": "^1.0.0", + "micromark-factory-destination": "^2.0.0", + "micromark-factory-label": "^2.0.0", + "micromark-factory-space": "^2.0.0", + "micromark-factory-title": "^2.0.0", + "micromark-factory-whitespace": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-classify-character": "^2.0.0", + "micromark-util-html-tag-name": "^2.0.0", + "micromark-util-normalize-identifier": "^2.0.0", + "micromark-util-resolve-all": "^2.0.0", + "micromark-util-subtokenize": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-destination": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz", + "integrity": "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-label": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz", + "integrity": "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-space": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-space/-/micromark-factory-space-2.0.1.tgz", + "integrity": "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-title": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz", + "integrity": "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-factory-whitespace": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz", + "integrity": "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-chunked": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz", + "integrity": "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-classify-character": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz", + "integrity": "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-combine-extensions": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz", + "integrity": "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-chunked": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-numeric-character-reference": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz", + "integrity": "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-decode-string": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz", + "integrity": "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "decode-named-character-reference": "^1.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-decode-numeric-character-reference": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-html-tag-name": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz", + "integrity": "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-normalize-identifier": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz", + "integrity": "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-resolve-all": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz", + "integrity": "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-subtokenize": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz", + "integrity": "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "devlop": "^1.0.0", + "micromark-util-chunked": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -12741,6 +13671,31 @@ "node": ">=6" } }, + "node_modules/parse-entities": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/parse-entities/-/parse-entities-4.0.2.tgz", + "integrity": "sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^2.0.0", + "character-entities-legacy": "^3.0.0", + "character-reference-invalid": "^2.0.0", + "decode-named-character-reference": "^1.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0", + "is-hexadecimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/parse-entities/node_modules/@types/unist": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", + "integrity": "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==", + "license": "MIT" + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -13174,6 +14129,16 @@ "react-is": "^16.13.1" } }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -13317,6 +14282,33 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-markdown": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", + "integrity": "sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "hast-util-to-jsx-runtime": "^2.0.0", + "html-url-attributes": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "remark-parse": "^11.0.0", + "remark-rehype": "^11.0.0", + "unified": "^11.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + }, + "peerDependencies": { + "@types/react": ">=18", + "react": ">=18" + } + }, "node_modules/react-remove-scroll": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.3.tgz", @@ -13572,6 +14564,39 @@ "url": "https://github.com/sponsors/mysticatea" } }, + "node_modules/remark-parse": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", + "integrity": "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-from-markdown": "^2.0.0", + "micromark-util-types": "^2.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/remark-rehype": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/remark-rehype/-/remark-rehype-11.1.2.tgz", + "integrity": "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "mdast-util-to-hast": "^13.0.0", + "unified": "^11.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/resolve": { "version": "1.22.8", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", @@ -14142,6 +15167,16 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/stdin-discarder": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", @@ -14338,6 +15373,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -14392,6 +15441,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-to-js": { + "version": "1.1.16", + "resolved": "https://registry.npmjs.org/style-to-js/-/style-to-js-1.1.16.tgz", + "integrity": "sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==", + "license": "MIT", + "dependencies": { + "style-to-object": "1.0.8" + } + }, + "node_modules/style-to-object": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.8.tgz", + "integrity": "sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==", + "license": "MIT", + "dependencies": { + "inline-style-parser": "0.2.4" + } + }, "node_modules/styled-jsx": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz", @@ -14603,6 +15670,26 @@ "node": ">=8.0" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/trough": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", + "integrity": "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/ts-api-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz", @@ -14771,6 +15858,93 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/unified": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", + "integrity": "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "bail": "^2.0.0", + "devlop": "^1.0.0", + "extend": "^3.0.0", + "is-plain-obj": "^4.0.0", + "trough": "^2.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -14946,6 +16120,34 @@ "uuid": "dist/esm/bin/uuid" } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.2.tgz", + "integrity": "sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==", + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/victory-vendor": { "version": "36.9.2", "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", @@ -15235,6 +16437,16 @@ "optional": true } } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } } } } diff --git a/ui/package.json b/ui/package.json index 5d16567d19..10432cb767 100644 --- a/ui/package.json +++ b/ui/package.json @@ -14,6 +14,7 @@ "@radix-ui/react-toast": "^1.2.4", "@react-aria/ssr": "3.9.4", "@react-aria/visually-hidden": "3.8.12", + "@tailwindcss/typography": "^0.5.16", "@tanstack/react-table": "^8.19.3", "add": "^2.0.6", "alert": "^6.0.2", @@ -35,6 +36,7 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-hook-form": "^7.52.2", + "react-markdown": "^10.1.0", "recharts": "^2.15.2", "server-only": "^0.0.1", "shadcn-ui": "^0.2.3", diff --git a/ui/tailwind.config.js b/ui/tailwind.config.js index 664e360167..503418fa2c 100644 --- a/ui/tailwind.config.js +++ b/ui/tailwind.config.js @@ -171,9 +171,9 @@ module.exports = { "100%": { left: "100%", width: "100%" }, }, dropArrow: { - '0%': { transform: 'translateY(-8px)', opacity: '0' }, - '50%': { opacity: '1' }, - '100%': { transform: 'translateY(0)', opacity: '1' }, + "0%": { transform: "translateY(-8px)", opacity: "0" }, + "50%": { opacity: "1" }, + "100%": { transform: "translateY(0)", opacity: "1" }, }, }, animation: { @@ -188,6 +188,7 @@ module.exports = { }, plugins: [ require("tailwindcss-animate"), + require("@tailwindcss/typography"), nextui({ themes: { dark: { diff --git a/ui/types/compliance.ts b/ui/types/compliance.ts index ce563f4214..4fa4cf7e1a 100644 --- a/ui/types/compliance.ts +++ b/ui/types/compliance.ts @@ -3,7 +3,13 @@ export type RequirementStatus = "PASS" | "FAIL" | "MANUAL" | "No findings"; export type ComplianceId = | "ens_rd2022_aws" | "iso27001_2013_aws" - | "iso27001_2022_aws"; + | "iso27001_2022_aws" + | "cis_1.4_aws" + | "cis_1.5_aws" + | "cis_2.0_aws" + | "cis_3.0_aws" + | "cis_4.0_aws" + | "cis_5.0_aws"; export interface CompliancesOverview { data: ComplianceOverviewData[]; @@ -91,6 +97,21 @@ export interface ISO27001AttributesMetadata { Check_Summary: string; } +export interface CISAttributesMetadata { + Section: string; + SubSection: string | null; + Profile: string; // "Level 1" or "Level 2" + AssessmentStatus: string; // "Manual" or "Automated" + Description: string; + RationaleStatement: string; + ImpactStatement: string; + RemediationProcedure: string; + AuditProcedure: string; + AdditionalInformation: string; + DefaultValue: string | null; + References: string; +} + export interface AttributesItemData { type: "compliance-requirements-attributes"; id: string; @@ -123,3 +144,17 @@ export interface AttributesData { export interface RequirementsData { data: RequirementItemData[]; } + +export interface RegionData { + name: string; + failurePercentage: number; + totalRequirements: number; + failedRequirements: number; +} + +export interface CategoryData { + name: string; + failurePercentage: number; + totalRequirements: number; + failedRequirements: number; +} diff --git a/ui/types/filters.ts b/ui/types/filters.ts index e6f364794e..3d0684e1d0 100644 --- a/ui/types/filters.ts +++ b/ui/types/filters.ts @@ -6,6 +6,9 @@ export interface FilterOption { values: string[]; valueLabelMapping?: Array<{ [uid: string]: ProviderAccountProps }>; index?: number; + showSelectAll?: boolean; + defaultToSelectAll?: boolean; + defaultValues?: string[]; } export interface CustomDropdownFilterProps {