diff --git a/ui/components/compliance/compliance-custom-details/kisa-details.tsx b/ui/components/compliance/compliance-custom-details/kisa-details.tsx
new file mode 100644
index 0000000000..8f0041be7f
--- /dev/null
+++ b/ui/components/compliance/compliance-custom-details/kisa-details.tsx
@@ -0,0 +1,93 @@
+import { Requirement } from "@/types/compliance";
+
+export const KISACustomDetails = ({
+ requirement,
+}: {
+ requirement: Requirement;
+}) => {
+ const auditChecklist = requirement.audit_checklist as string[] | undefined;
+ const relatedRegulations = requirement.related_regulations as
+ | string[]
+ | undefined;
+ const auditEvidence = requirement.audit_evidence as string[] | undefined;
+ const nonComplianceCases = requirement.non_compliance_cases as
+ | string[]
+ | undefined;
+
+ return (
+
+ {requirement.description && (
+
+
+ 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/skeletons/bar-chart-skeleton.tsx b/ui/components/compliance/skeletons/bar-chart-skeleton.tsx
index 05f26ae938..2338818068 100644
--- a/ui/components/compliance/skeletons/bar-chart-skeleton.tsx
+++ b/ui/components/compliance/skeletons/bar-chart-skeleton.tsx
@@ -4,7 +4,7 @@ import { Skeleton } from "@nextui-org/react";
export const BarChartSkeleton = () => {
return (
-
+
{/* Title skeleton */}
diff --git a/ui/lib/compliance/commons.ts b/ui/lib/compliance/commons.ts
index c62933b635..49fb6bf932 100644
--- a/ui/lib/compliance/commons.ts
+++ b/ui/lib/compliance/commons.ts
@@ -4,6 +4,7 @@ import { AWSWellArchitectedCustomDetails } from "@/components/compliance/complia
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 { KISACustomDetails } from "@/components/compliance/compliance-custom-details/kisa-details";
import { AccordionItemProps } from "@/components/ui/accordion/Accordion";
import {
AttributesData,
@@ -30,6 +31,10 @@ import {
mapComplianceData as mapISOComplianceData,
toAccordionItems as toISOAccordionItems,
} from "./iso";
+import {
+ mapComplianceData as mapKISAComplianceData,
+ toAccordionItems as toKISAAccordionItems,
+} from "./kisa";
export interface ComplianceMapper {
mapComplianceData: (
@@ -119,6 +124,13 @@ const complianceMappers: Record = {
getDetailsComponent: (requirement: Requirement) =>
React.createElement(AWSWellArchitectedCustomDetails, { requirement }),
},
+ "KISA-ISMS-P": {
+ mapComplianceData: mapKISAComplianceData,
+ toAccordionItems: toKISAAccordionItems,
+ getTopFailedSections,
+ getDetailsComponent: (requirement: Requirement) =>
+ React.createElement(KISACustomDetails, { requirement }),
+ },
};
// Default mapper (fallback to ENS for backward compatibility)
diff --git a/ui/lib/compliance/kisa.tsx b/ui/lib/compliance/kisa.tsx
new file mode 100644
index 0000000000..70545ead92
--- /dev/null
+++ b/ui/lib/compliance/kisa.tsx
@@ -0,0 +1,214 @@
+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,
+ KISAAttributesMetadata,
+ 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();
+ 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 KISAAttributesMetadata[];
+ 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 categoryName = attrs.Domain; // Level 1: Domain
+ const controlLabel = attrs.Subdomain; // Level 2: Subdomain
+ const sectionName = attrs.Section; // Level 3: 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);
+ }
+
+ // Find or create category (Domain)
+ let category = framework.categories.find((c) => c.name === categoryName);
+ if (!category) {
+ category = {
+ name: categoryName,
+ pass: 0,
+ fail: 0,
+ manual: 0,
+ controls: [],
+ };
+ framework.categories.push(category);
+ }
+
+ // Find or create control (Subdomain)
+ let control = category.controls.find((c) => c.label === controlLabel);
+ if (!control) {
+ control = {
+ label: controlLabel,
+ pass: 0,
+ fail: 0,
+ manual: 0,
+ requirements: [],
+ };
+ category.controls.push(control);
+ }
+
+ // Create requirement (Section)
+ 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,
+ section: sectionName,
+ audit_checklist: attrs.AuditChecklist,
+ related_regulations: attrs.RelatedRegulations,
+ audit_evidence: attrs.AuditEvidence,
+ non_compliance_cases: attrs.NonComplianceCases,
+ };
+
+ control.requirements.push(requirement);
+ }
+
+ // Calculate counters
+ 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) => {
+ 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++;
+ }
+ });
+
+ 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) => {
+ 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 994c90d200..f6551ba07e 100644
--- a/ui/types/compliance.ts
+++ b/ui/types/compliance.ts
@@ -113,6 +113,16 @@ export interface AWSWellArchitectedAttributesMetadata {
ImplementationGuidanceUrl: string;
}
+export interface KISAAttributesMetadata {
+ Domain: string;
+ Subdomain: string;
+ Section: string;
+ AuditChecklist: string[];
+ RelatedRegulations: string[];
+ AuditEvidence: string[];
+ NonComplianceCases: string[];
+}
+
export interface AttributesItemData {
type: "compliance-requirements-attributes";
id: string;
@@ -125,7 +135,8 @@ export interface AttributesItemData {
| ENSAttributesMetadata[]
| ISO27001AttributesMetadata[]
| CISAttributesMetadata[]
- | AWSWellArchitectedAttributesMetadata[];
+ | AWSWellArchitectedAttributesMetadata[]
+ | KISAAttributesMetadata[];
check_ids: string[];
};
};