mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 12:31:54 +00:00
feat: generic compliance detailed view (#7990)
This commit is contained in:
@@ -71,13 +71,7 @@ export const ComplianceCard: React.FC<ComplianceCardProps> = ({
|
||||
return "success";
|
||||
};
|
||||
|
||||
const isPressable =
|
||||
id.includes("ens") ||
|
||||
id.includes("iso") ||
|
||||
id.includes("cis_") ||
|
||||
id.includes("kisa") ||
|
||||
id.includes("threat") ||
|
||||
id.includes("pillar");
|
||||
const isPressable = !id.includes("mitre");
|
||||
|
||||
const navigateToDetail = () => {
|
||||
// We will unlock this while developing the rest of complainces.
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import { Requirement } from "@/types/compliance";
|
||||
|
||||
import {
|
||||
ComplianceBadge,
|
||||
ComplianceBadgeContainer,
|
||||
ComplianceDetailContainer,
|
||||
ComplianceDetailSection,
|
||||
ComplianceDetailText,
|
||||
} from "./shared-components";
|
||||
|
||||
export const GenericCustomDetails = ({
|
||||
requirement,
|
||||
}: {
|
||||
requirement: Requirement;
|
||||
}) => {
|
||||
return (
|
||||
<ComplianceDetailContainer>
|
||||
{requirement.description && (
|
||||
<ComplianceDetailSection title="Description">
|
||||
<ComplianceDetailText>{requirement.description}</ComplianceDetailText>
|
||||
</ComplianceDetailSection>
|
||||
)}
|
||||
|
||||
<ComplianceBadgeContainer>
|
||||
{requirement.item_id && (
|
||||
<ComplianceBadge
|
||||
label="Item ID"
|
||||
value={requirement.item_id as string}
|
||||
color="indigo"
|
||||
/>
|
||||
)}
|
||||
|
||||
{requirement.service && (
|
||||
<ComplianceBadge
|
||||
label="Service"
|
||||
value={requirement.service as string}
|
||||
color="blue"
|
||||
/>
|
||||
)}
|
||||
|
||||
{requirement.type && (
|
||||
<ComplianceBadge
|
||||
label="Type"
|
||||
value={requirement.type as string}
|
||||
color="orange"
|
||||
/>
|
||||
)}
|
||||
</ComplianceBadgeContainer>
|
||||
|
||||
{requirement.subsection && (
|
||||
<ComplianceDetailSection title="SubSection">
|
||||
<ComplianceDetailText>
|
||||
{requirement.subsection as string}
|
||||
</ComplianceDetailText>
|
||||
</ComplianceDetailSection>
|
||||
)}
|
||||
|
||||
{requirement.subgroup && (
|
||||
<ComplianceDetailSection title="SubGroup">
|
||||
<ComplianceDetailText>
|
||||
{requirement.subgroup as string}
|
||||
</ComplianceDetailText>
|
||||
</ComplianceDetailSection>
|
||||
)}
|
||||
</ComplianceDetailContainer>
|
||||
);
|
||||
};
|
||||
@@ -3,6 +3,7 @@ import React from "react";
|
||||
import { AWSWellArchitectedCustomDetails } from "@/components/compliance/compliance-custom-details/aws-well-architected-details";
|
||||
import { CISCustomDetails } from "@/components/compliance/compliance-custom-details/cis-details";
|
||||
import { ENSCustomDetails } from "@/components/compliance/compliance-custom-details/ens-details";
|
||||
import { GenericCustomDetails } from "@/components/compliance/compliance-custom-details/generic-details";
|
||||
import { ISOCustomDetails } from "@/components/compliance/compliance-custom-details/iso-details";
|
||||
import { KISACustomDetails } from "@/components/compliance/compliance-custom-details/kisa-details";
|
||||
import { ThreatCustomDetails } from "@/components/compliance/compliance-custom-details/threat-details";
|
||||
@@ -28,6 +29,10 @@ import {
|
||||
mapComplianceData as mapENSComplianceData,
|
||||
toAccordionItems as toENSAccordionItems,
|
||||
} from "./ens";
|
||||
import {
|
||||
mapComplianceData as mapGenericComplianceData,
|
||||
toAccordionItems as toGenericAccordionItems,
|
||||
} from "./generic";
|
||||
import {
|
||||
mapComplianceData as mapISOComplianceData,
|
||||
toAccordionItems as toISOAccordionItems,
|
||||
@@ -145,8 +150,14 @@ const complianceMappers: Record<string, ComplianceMapper> = {
|
||||
},
|
||||
};
|
||||
|
||||
// Default mapper (fallback to ENS for backward compatibility)
|
||||
const defaultMapper: ComplianceMapper = complianceMappers.ENS;
|
||||
// Default mapper (fallback to generic for maximum compatibility)
|
||||
const defaultMapper: ComplianceMapper = {
|
||||
mapComplianceData: mapGenericComplianceData,
|
||||
toAccordionItems: toGenericAccordionItems,
|
||||
getTopFailedSections,
|
||||
getDetailsComponent: (requirement: Requirement) =>
|
||||
React.createElement(GenericCustomDetails, { requirement }),
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the appropriate compliance mapper based on the framework name
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
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,
|
||||
Framework,
|
||||
GenericAttributesMetadata,
|
||||
Requirement,
|
||||
RequirementItemData,
|
||||
RequirementsData,
|
||||
RequirementStatus,
|
||||
} from "@/types/compliance";
|
||||
|
||||
export const mapComplianceData = (
|
||||
attributesData: AttributesData,
|
||||
requirementsData: RequirementsData,
|
||||
): Framework[] => {
|
||||
const attributes = attributesData?.data || [];
|
||||
const requirements = requirementsData?.data || [];
|
||||
|
||||
// Create a map for quick lookup of requirements by id
|
||||
const requirementsMap = new Map<string, RequirementItemData>();
|
||||
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 GenericAttributesMetadata[];
|
||||
const attrs = metadataArray?.[0];
|
||||
if (!attrs) continue;
|
||||
|
||||
// Get corresponding requirement data
|
||||
const requirementData = requirementsMap.get(id);
|
||||
if (!requirementData) continue;
|
||||
|
||||
const frameworkName = attributeItem.attributes.framework;
|
||||
const sectionName = attrs.Section; // Level 1: Section -> Category
|
||||
const requirementName = attributeItem.attributes.name || id; // Level 2: name -> Control
|
||||
const description = attributeItem.attributes.description;
|
||||
const status = requirementData.attributes.status || "";
|
||||
const checks = attributeItem.attributes.attributes.check_ids || [];
|
||||
|
||||
if (!sectionName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Find or create category (Section)
|
||||
let category = framework.categories.find((c) => c.name === sectionName);
|
||||
if (!category) {
|
||||
category = {
|
||||
name: sectionName,
|
||||
pass: 0,
|
||||
fail: 0,
|
||||
manual: 0,
|
||||
controls: [],
|
||||
};
|
||||
framework.categories.push(category);
|
||||
}
|
||||
|
||||
// Create a control for this requirement (each requirement is its own control in this generic approach)
|
||||
const control = {
|
||||
label: requirementName,
|
||||
pass: 0,
|
||||
fail: 0,
|
||||
manual: 0,
|
||||
requirements: [] as Requirement[],
|
||||
};
|
||||
|
||||
// Create requirement
|
||||
const finalStatus: RequirementStatus = status as RequirementStatus;
|
||||
const requirement: Requirement = {
|
||||
name: requirementName,
|
||||
description: description,
|
||||
status: finalStatus,
|
||||
check_ids: checks,
|
||||
pass: finalStatus === "PASS" ? 1 : 0,
|
||||
fail: finalStatus === "FAIL" ? 1 : 0,
|
||||
manual: finalStatus === "MANUAL" ? 1 : 0,
|
||||
item_id: attrs.ItemId,
|
||||
subsection: attrs.SubSection,
|
||||
subgroup: attrs.SubGroup || undefined,
|
||||
service: attrs.Service || undefined,
|
||||
type: attrs.Type || undefined,
|
||||
};
|
||||
|
||||
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: (
|
||||
<ComplianceAccordionTitle
|
||||
label={category.name}
|
||||
pass={category.pass}
|
||||
fail={category.fail}
|
||||
manual={category.manual}
|
||||
isParentLevel={true}
|
||||
/>
|
||||
),
|
||||
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: (
|
||||
<ComplianceAccordionRequirementTitle
|
||||
type=""
|
||||
name={control.label}
|
||||
status={requirement.status as FindingStatus}
|
||||
/>
|
||||
),
|
||||
content: (
|
||||
<ClientAccordionContent
|
||||
requirement={requirement}
|
||||
scanId={scanId || ""}
|
||||
framework={framework.name}
|
||||
disableFindings={
|
||||
requirement.check_ids.length === 0 && requirement.manual === 0
|
||||
}
|
||||
/>
|
||||
),
|
||||
items: [],
|
||||
};
|
||||
}),
|
||||
};
|
||||
}),
|
||||
);
|
||||
};
|
||||
+12
-1
@@ -133,10 +133,20 @@ export interface KISAAttributesMetadata {
|
||||
NonComplianceCases: string[];
|
||||
}
|
||||
|
||||
export interface GenericAttributesMetadata {
|
||||
ItemId: string;
|
||||
Section: string;
|
||||
SubSection: string;
|
||||
SubGroup: string | null;
|
||||
Service: string | null;
|
||||
Type: string | null;
|
||||
}
|
||||
|
||||
export interface AttributesItemData {
|
||||
type: "compliance-requirements-attributes";
|
||||
id: string;
|
||||
attributes: {
|
||||
name?: string;
|
||||
framework: string;
|
||||
version: string;
|
||||
description: string;
|
||||
@@ -147,7 +157,8 @@ export interface AttributesItemData {
|
||||
| CISAttributesMetadata[]
|
||||
| AWSWellArchitectedAttributesMetadata[]
|
||||
| ThreatAttributesMetadata[]
|
||||
| KISAAttributesMetadata[];
|
||||
| KISAAttributesMetadata[]
|
||||
| GenericAttributesMetadata[];
|
||||
check_ids: string[];
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user