- {requirement.profile && (
-
-
- Profile Level
-
-
{requirement.profile}
-
+
+ {requirement.description && (
+
+ {requirement.description}
+
)}
+
+ {requirement.profile && (
+
+ )}
+
+ {requirement.assessment_status && (
+
+ )}
+
+
{requirement.subsection && (
-
-
- SubSection
-
-
{requirement.subsection}
-
- )}
-
- {requirement.assessment_status && (
-
-
- Assessment Status
-
-
{requirement.assessment_status}
-
- )}
-
- {requirement.description && (
-
-
- Description
-
-
{requirement.description}
-
+
+
+ {requirement.subsection as string}
+
+
)}
{requirement.rationale_statement && (
-
-
- Rationale Statement
-
-
{requirement.rationale_statement}
-
+
+
+ {requirement.rationale_statement as string}
+
+
)}
{requirement.impact_statement && (
-
-
- Impact Statement
-
-
{requirement.impact_statement}
-
+
+
+ {requirement.impact_statement as string}
+
+
)}
{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.additional_information as string}
+
+
)}
{requirement.default_value && (
-
-
- Default Value
-
-
{requirement.default_value}
-
+
+
+ {requirement.default_value as string}
+
+
)}
{requirement.references && (
-
-
- References
-
-
+
+
{processReferences(requirement.references).map(
(url: string, index: number) => (
-
- {url}
-
+ {url}
),
)}
-
+
)}
-
+
);
};
diff --git a/ui/components/compliance/compliance-custom-details/ens-details.tsx b/ui/components/compliance/compliance-custom-details/ens-details.tsx
index 2c133f7e11..9e947cea14 100644
--- a/ui/components/compliance/compliance-custom-details/ens-details.tsx
+++ b/ui/components/compliance/compliance-custom-details/ens-details.tsx
@@ -1,47 +1,50 @@
import { translateType } from "@/lib/compliance/ens";
import { Requirement } from "@/types/compliance";
+import {
+ ComplianceBadge,
+ ComplianceBadgeContainer,
+ ComplianceChipContainer,
+ ComplianceDetailContainer,
+ ComplianceDetailSection,
+ ComplianceDetailText,
+} from "./shared-components";
+
export const ENSCustomDetails = ({
requirement,
}: {
requirement: Requirement;
}) => {
return (
-
-
- {requirement.description}
-
-
-
- Type:
-
- {translateType(requirement.type as string)}
-
-
-
- Level:
- {requirement.nivel}
-
- {requirement.dimensiones &&
- Array.isArray(requirement.dimensiones) &&
- requirement.dimensiones.length > 0 && (
-
-
Dimensions:
-
- {requirement.dimensiones.map(
- (dimension: string, index: number) => (
-
- {dimension}
-
- ),
- )}
-
-
- )}
-
-
+
+ {requirement.description && (
+
+ {requirement.description}
+
+ )}
+
+
+ {requirement.type && (
+
+ )}
+
+ {requirement.nivel && (
+
+ )}
+
+
+
+
);
};
diff --git a/ui/components/compliance/compliance-custom-details/iso-details.tsx b/ui/components/compliance/compliance-custom-details/iso-details.tsx
index 14699abe9a..425a6aa49f 100644
--- a/ui/components/compliance/compliance-custom-details/iso-details.tsx
+++ b/ui/components/compliance/compliance-custom-details/iso-details.tsx
@@ -1,23 +1,31 @@
import { Requirement } from "@/types/compliance";
+import {
+ ComplianceDetailContainer,
+ ComplianceDetailSection,
+ ComplianceDetailText,
+} from "./shared-components";
+
export const ISOCustomDetails = ({
requirement,
}: {
requirement: Requirement;
}) => {
return (
-
-
- {requirement.description}
-
-
- {requirement.objetive_name && (
-
- Objective:
- {requirement.objetive_name}
-
- )}
-
-
+
+ {requirement.description && (
+
+ {requirement.description}
+
+ )}
+
+ {requirement.objetive_name && (
+
+
+ {requirement.objetive_name as string}
+
+
+ )}
+
);
};
diff --git a/ui/components/compliance/compliance-custom-details/kisa-details.tsx b/ui/components/compliance/compliance-custom-details/kisa-details.tsx
index 8f0041be7f..c29a39b22d 100644
--- a/ui/components/compliance/compliance-custom-details/kisa-details.tsx
+++ b/ui/components/compliance/compliance-custom-details/kisa-details.tsx
@@ -1,5 +1,12 @@
import { Requirement } from "@/types/compliance";
+import {
+ ComplianceBulletList,
+ ComplianceDetailContainer,
+ ComplianceDetailSection,
+ ComplianceDetailText,
+} from "./shared-components";
+
export const KISACustomDetails = ({
requirement,
}: {
@@ -15,79 +22,32 @@ export const KISACustomDetails = ({
| undefined;
return (
-
+
{requirement.description && (
-
-
- Description
-
-
{requirement.description}
-
+
+ {requirement.description}
+
)}
- {auditChecklist && auditChecklist.length > 0 && (
-
-
- Audit Checklist
-
-
- {auditChecklist.map((item: string, index: number) => (
-
- ))}
-
-
- )}
+
- {relatedRegulations && relatedRegulations.length > 0 && (
-
-
- Related Regulations
-
-
- {relatedRegulations.map((regulation: string, index: number) => (
-
- ))}
-
-
- )}
+
- {auditEvidence && auditEvidence.length > 0 && (
-
-
- Audit Evidence
-
-
- {auditEvidence.map((evidence: string, index: number) => (
-
- ))}
-
-
- )}
+
- {nonComplianceCases && nonComplianceCases.length > 0 && (
-
-
- Non-Compliance Cases
-
-
- {nonComplianceCases.map((caseItem: string, index: number) => (
-
- ))}
-
-
- )}
-
+
+
);
};
diff --git a/ui/components/compliance/compliance-custom-details/shared-components.tsx b/ui/components/compliance/compliance-custom-details/shared-components.tsx
new file mode 100644
index 0000000000..2d1344b19b
--- /dev/null
+++ b/ui/components/compliance/compliance-custom-details/shared-components.tsx
@@ -0,0 +1,166 @@
+import Link from "next/link";
+
+import { cn } from "@/lib/utils";
+
+export const ComplianceLink = ({
+ href,
+ children,
+}: {
+ href: string;
+ children: React.ReactNode;
+}) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export const ComplianceDetailContainer = ({
+ children,
+}: {
+ children: React.ReactNode;
+}) => {
+ return
{children}
;
+};
+
+export const ComplianceDetailSection = ({
+ title,
+ children,
+}: {
+ title: string;
+ children: React.ReactNode;
+}) => {
+ return (
+
+
+ {title}
+
+ {children}
+
+ );
+};
+
+export const ComplianceDetailText = ({
+ children,
+ className = "",
+}: {
+ children: React.ReactNode;
+ className?: string;
+}) => {
+ return
{children}
;
+};
+
+export const ComplianceBadgeContainer = ({
+ children,
+}: {
+ children: React.ReactNode;
+}) => {
+ return
{children}
;
+};
+
+type BadgeColor =
+ | "red" // Risk/Level/Severity
+ | "blue" // Assessment/Method
+ | "orange" // Type/Category
+ | "green" // Weight/Score (positive)
+ | "purple" // Profile
+ | "indigo" // IDs/References
+ | "gray"; // Additional Info/Neutral
+
+export const ComplianceBadge = ({
+ label,
+ value,
+ color,
+ conditional = false,
+}: {
+ label: string;
+ value: string | number;
+ color: BadgeColor;
+ conditional?: boolean;
+}) => {
+ const actualColor = conditional && Number(value) === 0 ? "gray" : color;
+
+ const colorClasses = {
+ red: "bg-red-50 text-red-700 ring-red-600/10 dark:bg-red-400/10 dark:text-red-400 dark:ring-red-400/20",
+ blue: "bg-blue-50 text-blue-700 ring-blue-600/10 dark:bg-blue-400/10 dark:text-blue-400 dark:ring-blue-400/20",
+ orange:
+ "bg-orange-50 text-orange-700 ring-orange-600/10 dark:bg-orange-400/10 dark:text-orange-400 dark:ring-orange-400/20",
+ green:
+ "bg-green-50 text-green-700 ring-green-600/10 dark:bg-green-400/10 dark:text-green-400 dark:ring-green-400/20",
+ purple:
+ "bg-purple-50 text-purple-700 ring-purple-600/10 dark:bg-purple-400/10 dark:text-purple-400 dark:ring-purple-400/20",
+ indigo:
+ "bg-indigo-50 text-indigo-700 ring-indigo-600/10 dark:bg-indigo-400/10 dark:text-indigo-400 dark:ring-indigo-400/20",
+ gray: "bg-gray-50 text-gray-600 ring-gray-500/10 dark:bg-gray-400/10 dark:text-gray-400 dark:ring-gray-400/20",
+ };
+
+ return (
+
+
+ {label}:
+
+
+ {value}
+
+
+ );
+};
+
+export const ComplianceBulletList = ({
+ title,
+ items,
+}: {
+ title: string;
+ items: string[];
+}) => {
+ if (!items || items.length === 0) return null;
+
+ return (
+
+
+ {items.map((item: string, index: number) => (
+
+ •
+ {item}
+
+ ))}
+
+
+ );
+};
+
+export const ComplianceChipContainer = ({
+ title,
+ items,
+}: {
+ title: string;
+ items: string[];
+}) => {
+ if (!items || items.length === 0) return null;
+
+ return (
+
+
+ {items.map((item: string, index: number) => (
+
+ {item}
+
+ ))}
+
+
+ );
+};
diff --git a/ui/components/compliance/compliance-custom-details/threat-details.tsx b/ui/components/compliance/compliance-custom-details/threat-details.tsx
new file mode 100644
index 0000000000..4f2f5e87da
--- /dev/null
+++ b/ui/components/compliance/compliance-custom-details/threat-details.tsx
@@ -0,0 +1,68 @@
+import { Requirement } from "@/types/compliance";
+
+import {
+ ComplianceBadge,
+ ComplianceBadgeContainer,
+ ComplianceDetailContainer,
+ ComplianceDetailSection,
+ ComplianceDetailText,
+} from "./shared-components";
+
+export const ThreatCustomDetails = ({
+ requirement,
+}: {
+ requirement: Requirement;
+}) => {
+ return (
+
+ {requirement.description && (
+
+ {requirement.description}
+
+ )}
+
+ {requirement.attributeDescription && (
+
+
+ {requirement.attributeDescription}
+
+
+ )}
+
+
+ {typeof requirement.levelOfRisk === "number" && (
+
+ )}
+
+ {typeof requirement.weight === "number" && (
+
+ )}
+
+ {typeof requirement.score === "number" && (
+
+ )}
+
+
+ {requirement.additionalInformation && (
+
+
+ {requirement.additionalInformation}
+
+
+ )}
+
+ );
+};
diff --git a/ui/lib/compliance/commons.ts b/ui/lib/compliance/commons.ts
index 49fb6bf932..0ede59f1c4 100644
--- a/ui/lib/compliance/commons.ts
+++ b/ui/lib/compliance/commons.ts
@@ -5,6 +5,7 @@ import { CISCustomDetails } from "@/components/compliance/compliance-custom-deta
import { ENSCustomDetails } from "@/components/compliance/compliance-custom-details/ens-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";
import { AccordionItemProps } from "@/components/ui/accordion/Accordion";
import {
AttributesData,
@@ -35,6 +36,10 @@ import {
mapComplianceData as mapKISAComplianceData,
toAccordionItems as toKISAAccordionItems,
} from "./kisa";
+import {
+ mapComplianceData as mapThetaComplianceData,
+ toAccordionItems as toThetaAccordionItems,
+} from "./threat";
export interface ComplianceMapper {
mapComplianceData: (
@@ -131,6 +136,13 @@ const complianceMappers: Record
= {
getDetailsComponent: (requirement: Requirement) =>
React.createElement(KISACustomDetails, { requirement }),
},
+ ProwlerThreatScore: {
+ mapComplianceData: mapThetaComplianceData,
+ toAccordionItems: toThetaAccordionItems,
+ getTopFailedSections,
+ getDetailsComponent: (requirement: Requirement) =>
+ React.createElement(ThreatCustomDetails, { requirement }),
+ },
};
// Default mapper (fallback to ENS for backward compatibility)
diff --git a/ui/lib/compliance/threat.tsx b/ui/lib/compliance/threat.tsx
new file mode 100644
index 0000000000..df59f11685
--- /dev/null
+++ b/ui/lib/compliance/threat.tsx
@@ -0,0 +1,246 @@
+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,
+ Requirement,
+ RequirementItemData,
+ RequirementsData,
+ RequirementStatus,
+ ThreatAttributesMetadata,
+} 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();
+ 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 ThreatAttributesMetadata[];
+ 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;
+ const subSectionName = attrs.SubSection;
+ const title = attrs.Title;
+ const description = attributeItem.attributes.description;
+ const status = requirementData.attributes.status || "";
+ const checks = attributeItem.attributes.attributes.check_ids || [];
+ const requirementName = id;
+ const levelOfRisk = attrs.LevelOfRisk;
+ const weight = attrs.Weight;
+ const attributeDescription = attrs.AttributeDescription;
+ const additionalInformation = attrs.AdditionalInformation;
+
+ // Calculate score: if PASS = levelOfRisk * weight, if FAIL = 0
+ const score = status === "PASS" ? levelOfRisk * weight : 0;
+
+ // 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);
+ }
+
+ // Find or create control (SubSection)
+ let control = category.controls.find((c) => c.label === subSectionName);
+ if (!control) {
+ control = {
+ label: subSectionName,
+ pass: 0,
+ fail: 0,
+ manual: 0,
+ requirements: [],
+ };
+ category.controls.push(control);
+ }
+
+ // 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,
+ title: title,
+ levelOfRisk: levelOfRisk,
+ weight: weight,
+ score: score,
+ attributeDescription: attributeDescription,
+ additionalInformation: additionalInformation,
+ };
+
+ control.requirements.push(requirement);
+ }
+
+ // Calculate counters and percentualScore
+ 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;
+
+ // Calculate total score for this section and maximum possible score
+ let totalSectionScore = 0;
+ let maxPossibleSectionScore = 0;
+
+ category.controls.forEach((control) => {
+ control.pass = 0;
+ control.fail = 0;
+ control.manual = 0;
+
+ control.requirements.forEach((requirement) => {
+ if (requirement.status === "MANUAL") {
+ control.manual++;
+ } else if (requirement.status === "PASS") {
+ control.pass++;
+ } else if (requirement.status === "FAIL") {
+ control.fail++;
+ }
+
+ // Add to total section score (actual score obtained)
+ totalSectionScore += (requirement.score as number) || 0;
+
+ // Add to maximum possible score (weight * levelOfRisk for each requirement)
+ const levelOfRisk = (requirement.levelOfRisk as number) || 0;
+ const weight = (requirement.weight as number) || 0;
+ maxPossibleSectionScore += levelOfRisk * weight;
+ });
+
+ category.pass += control.pass;
+ category.fail += control.fail;
+ category.manual += control.manual;
+ });
+
+ // Calculate percentualScore for this section: (suma de scores obtenidos / suma de weight * levelOfRisk) * 100
+ const percentualScore =
+ maxPossibleSectionScore > 0
+ ? (totalSectionScore / maxPossibleSectionScore) * 100
+ : 0;
+
+ // Add percentualScore to category (we can extend the type or use a custom property)
+ (category as any).percentualScore =
+ Math.round(percentualScore * 100) / 100; // Round to 2 decimal places
+
+ 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) => {
+ const percentualScore = (category as any).percentualScore || 0;
+
+ return {
+ key: `${framework.name}-${category.name}`,
+ title: (
+
+ ),
+ content: "",
+ items: category.controls.map((control, i: number) => {
+ return {
+ key: `${framework.name}-${category.name}-control-${i}`,
+ title: (
+
+ ),
+ content: "",
+ items: control.requirements.map((requirement, j: number) => {
+ const itemKey = `${framework.name}-${category.name}-control-${i}-req-${j}`;
+
+ return {
+ key: itemKey,
+ title: (
+
+ ),
+ content: (
+
+ ),
+ items: [],
+ };
+ }),
+ isDisabled:
+ control.pass === 0 && control.fail === 0 && control.manual === 0,
+ };
+ }),
+ };
+ }),
+ );
+};
diff --git a/ui/types/compliance.ts b/ui/types/compliance.ts
index f6551ba07e..c4f028331c 100644
--- a/ui/types/compliance.ts
+++ b/ui/types/compliance.ts
@@ -113,6 +113,16 @@ export interface AWSWellArchitectedAttributesMetadata {
ImplementationGuidanceUrl: string;
}
+export interface ThreatAttributesMetadata {
+ Title: string;
+ Section: string;
+ SubSection: string;
+ AttributeDescription: string;
+ AdditionalInformation: string;
+ LevelOfRisk: number;
+ Weight: number;
+}
+
export interface KISAAttributesMetadata {
Domain: string;
Subdomain: string;
@@ -136,6 +146,7 @@ export interface AttributesItemData {
| ISO27001AttributesMetadata[]
| CISAttributesMetadata[]
| AWSWellArchitectedAttributesMetadata[]
+ | ThreatAttributesMetadata[]
| KISAAttributesMetadata[];
check_ids: string[];
};