mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-01-25 02:08:11 +00:00
270 lines
8.4 KiB
TypeScript
270 lines
8.4 KiB
TypeScript
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,
|
|
REQUIREMENT_STATUS,
|
|
RequirementsData,
|
|
RequirementStatus,
|
|
} from "@/types/compliance";
|
|
|
|
import {
|
|
calculateFrameworkCounters,
|
|
createRequirementsMap,
|
|
findOrCreateCategory,
|
|
findOrCreateControl,
|
|
findOrCreateFramework,
|
|
updateCounters,
|
|
} from "./commons";
|
|
|
|
interface ProcessedItem {
|
|
id: string;
|
|
attrs: GenericAttributesMetadata;
|
|
attributeItem: any;
|
|
requirementData: any;
|
|
}
|
|
|
|
const createRequirement = (itemData: ProcessedItem): Requirement => {
|
|
const { id, attrs, attributeItem, requirementData } = itemData;
|
|
const name = attributeItem.attributes.name || id;
|
|
const description = attributeItem.attributes.description;
|
|
const status = requirementData.attributes.status || "";
|
|
const checks = attributeItem.attributes.attributes.check_ids || [];
|
|
const finalStatus: RequirementStatus = status as RequirementStatus;
|
|
|
|
return {
|
|
name: attributeItem.attributes.framework === "PCI" ? id : name,
|
|
description: description,
|
|
status: finalStatus,
|
|
check_ids: checks,
|
|
pass: finalStatus === REQUIREMENT_STATUS.PASS ? 1 : 0,
|
|
fail: finalStatus === REQUIREMENT_STATUS.FAIL ? 1 : 0,
|
|
manual: finalStatus === REQUIREMENT_STATUS.MANUAL ? 1 : 0,
|
|
item_id: attrs.ItemId,
|
|
subsection: attrs.SubSection,
|
|
subgroup: attrs.SubGroup || undefined,
|
|
service: attrs.Service || undefined,
|
|
type: attrs.Type || undefined,
|
|
};
|
|
};
|
|
|
|
const shouldUseThreeLevelHierarchy = (items: ProcessedItem[]): boolean => {
|
|
const itemsWithSection = items.filter(
|
|
(item) =>
|
|
item.attrs.Section &&
|
|
item.attrs.Section !== (item.attributeItem.attributes.name || item.id),
|
|
);
|
|
return (
|
|
itemsWithSection.length > 0 &&
|
|
itemsWithSection.every((item) => item.attrs.SubSection)
|
|
);
|
|
};
|
|
|
|
export const mapComplianceData = (
|
|
attributesData: AttributesData,
|
|
requirementsData: RequirementsData,
|
|
): Framework[] => {
|
|
const attributes = attributesData?.data || [];
|
|
const requirementsMap = createRequirementsMap(requirementsData);
|
|
const frameworks: Framework[] = [];
|
|
const itemsByFramework = new Map<string, ProcessedItem[]>();
|
|
|
|
// First pass: collect all 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;
|
|
|
|
const requirementData = requirementsMap.get(id);
|
|
if (!requirementData) continue;
|
|
|
|
const frameworkName = attributeItem.attributes.framework;
|
|
|
|
if (!itemsByFramework.has(frameworkName)) {
|
|
itemsByFramework.set(frameworkName, []);
|
|
}
|
|
|
|
itemsByFramework.get(frameworkName)!.push({
|
|
id,
|
|
attrs,
|
|
attributeItem,
|
|
requirementData,
|
|
});
|
|
}
|
|
|
|
// Process each framework
|
|
for (const [frameworkName, items] of Array.from(itemsByFramework.entries())) {
|
|
const framework = findOrCreateFramework(frameworks, frameworkName);
|
|
const allHaveSubsection = shouldUseThreeLevelHierarchy(items);
|
|
|
|
// Process each item in the framework
|
|
for (const itemData of items) {
|
|
const requirement = createRequirement(itemData);
|
|
const sectionName = itemData.attrs.Section;
|
|
const subSectionName = itemData.attrs.SubSection;
|
|
|
|
// Determine structure: flat, 2-level, or 3-level hierarchy
|
|
if (!sectionName || sectionName === requirement.name) {
|
|
// Flat structure: store requirements directly in framework
|
|
(framework as any).requirements = (framework as any).requirements || [];
|
|
(framework as any).requirements.push(requirement);
|
|
updateCounters(framework, requirement.status);
|
|
} else if (allHaveSubsection && subSectionName) {
|
|
// 3-level hierarchy: Section -> SubSection -> Requirements
|
|
const category = findOrCreateCategory(
|
|
framework.categories,
|
|
sectionName,
|
|
);
|
|
const control = findOrCreateControl(category.controls, subSectionName);
|
|
control.requirements.push(requirement);
|
|
updateCounters(control, requirement.status);
|
|
} else {
|
|
// 2-level hierarchy: Section -> Requirements
|
|
const category = findOrCreateCategory(
|
|
framework.categories,
|
|
sectionName,
|
|
);
|
|
const control = {
|
|
label: requirement.name,
|
|
pass: 0,
|
|
fail: 0,
|
|
manual: 0,
|
|
requirements: [requirement],
|
|
};
|
|
updateCounters(control, requirement.status);
|
|
category.controls.push(control);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Calculate counters using common helper
|
|
calculateFrameworkCounters(frameworks);
|
|
|
|
return frameworks;
|
|
};
|
|
|
|
// Helper function to create accordion item for requirement
|
|
const createRequirementAccordionItem = (
|
|
requirement: Requirement,
|
|
itemKey: string,
|
|
scanId: string,
|
|
frameworkName: string,
|
|
): AccordionItemProps => ({
|
|
key: itemKey,
|
|
title: (
|
|
<ComplianceAccordionRequirementTitle
|
|
type=""
|
|
name={requirement.name}
|
|
status={requirement.status as FindingStatus}
|
|
/>
|
|
),
|
|
content: (
|
|
<ClientAccordionContent
|
|
key={`content-${itemKey}`}
|
|
requirement={requirement}
|
|
scanId={scanId}
|
|
framework={frameworkName}
|
|
disableFindings={
|
|
requirement.check_ids.length === 0 && requirement.manual === 0
|
|
}
|
|
/>
|
|
),
|
|
items: [],
|
|
});
|
|
|
|
export const toAccordionItems = (
|
|
data: Framework[],
|
|
scanId: string | undefined,
|
|
): AccordionItemProps[] => {
|
|
return data.flatMap((framework) => {
|
|
const directRequirements = (framework as any).requirements || [];
|
|
|
|
// Flat structure - requirements directly
|
|
if (directRequirements.length > 0) {
|
|
return directRequirements.map((requirement: Requirement, i: number) =>
|
|
createRequirementAccordionItem(
|
|
requirement,
|
|
`${framework.name}-req-${i}`,
|
|
scanId || "",
|
|
framework.name,
|
|
),
|
|
);
|
|
}
|
|
|
|
// Hierarchical structure - categories with controls
|
|
return framework.categories.map((category) => ({
|
|
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 baseKey = `${framework.name}-${category.name}-control-${i}`;
|
|
|
|
// 3-level hierarchy: control has multiple requirements
|
|
if (control.requirements.length > 1) {
|
|
return {
|
|
key: baseKey,
|
|
title: (
|
|
<ComplianceAccordionTitle
|
|
label={control.label}
|
|
pass={control.pass}
|
|
fail={control.fail}
|
|
manual={control.manual}
|
|
/>
|
|
),
|
|
content: "",
|
|
items: control.requirements.map((requirement, j: number) =>
|
|
createRequirementAccordionItem(
|
|
requirement,
|
|
`${baseKey}-req-${j}`,
|
|
scanId || "",
|
|
framework.name,
|
|
),
|
|
),
|
|
isDisabled:
|
|
control.pass === 0 && control.fail === 0 && control.manual === 0,
|
|
};
|
|
}
|
|
|
|
// 2-level hierarchy: direct requirement
|
|
const requirement = control.requirements[0];
|
|
return {
|
|
key: baseKey,
|
|
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: [],
|
|
};
|
|
}),
|
|
}));
|
|
});
|
|
};
|