mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 20:42:02 +00:00
feat: KISA detailed view (#7965)
This commit is contained in:
@@ -19,6 +19,7 @@ All notable changes to the **Prowler UI** are documented in this file.
|
||||
- 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)
|
||||
- Add compliance detail view: AWS Well-Architected Framework [(#7925)](https://github.com/prowler-cloud/prowler/pull/7925)
|
||||
- Add compliance detail view: KISA [(#7965)](https://github.com/prowler-cloud/prowler/pull/7965)
|
||||
- Improve `Scan ID` filter by adding more context and enhancing the UI/UX. [(#7949)](https://github.com/prowler-cloud/prowler/pull/7949)
|
||||
|
||||
### 🔄 Changed
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Spacer } from "@nextui-org/react";
|
||||
import Image from "next/image";
|
||||
import { Suspense } from "react";
|
||||
import React, { Suspense } from "react";
|
||||
|
||||
import {
|
||||
getComplianceAttributes,
|
||||
@@ -63,8 +63,18 @@ const ChartsWrapper = ({
|
||||
logoPath?: string;
|
||||
}) => {
|
||||
return (
|
||||
<div className="mb-8 flex w-full flex-col items-center justify-between lg:flex-row">
|
||||
{children}
|
||||
<div className="mb-8 flex w-full flex-wrap items-center justify-center gap-12 lg:justify-start">
|
||||
{children &&
|
||||
React.Children.toArray(children).map(
|
||||
(child: React.ReactNode, index: number) => (
|
||||
<div
|
||||
key={index}
|
||||
className="rounded-lg bg-gray-50 p-6 dark:bg-gray-900"
|
||||
>
|
||||
{child}
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -116,7 +116,7 @@ export const ClientAccordionContent = ({
|
||||
return (
|
||||
<div className="w-full">
|
||||
{renderDetails()}
|
||||
<p className="mt-2 text-sm font-medium text-gray-800">
|
||||
<p className="mb-1 mt-3 text-sm font-medium text-gray-800 dark:text-gray-200">
|
||||
⚠️ This requirement has no checks; therefore, there are no findings.
|
||||
</p>
|
||||
</div>
|
||||
@@ -165,7 +165,7 @@ export const ClientAccordionContent = ({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="text-sm font-medium text-gray-800">
|
||||
<div className="mb-1 mt-3 text-sm font-medium text-gray-800 dark:text-gray-200">
|
||||
⚠️ There are no findings for this regions
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -75,6 +75,7 @@ export const ComplianceCard: React.FC<ComplianceCardProps> = ({
|
||||
id.includes("ens") ||
|
||||
id.includes("iso") ||
|
||||
id.includes("cis_") ||
|
||||
id.includes("kisa") ||
|
||||
id.includes("pillar");
|
||||
|
||||
const navigateToDetail = () => {
|
||||
|
||||
@@ -14,12 +14,36 @@ import {
|
||||
import { translateType } from "@/lib/compliance/ens";
|
||||
import { FailedSection } from "@/types/compliance";
|
||||
|
||||
const CustomYAxisTick = (props: any) => {
|
||||
const { x, y, payload, theme } = props;
|
||||
const text = payload.value;
|
||||
const maxLength = 50;
|
||||
|
||||
const truncatedText =
|
||||
text.length > maxLength ? `${text.slice(0, maxLength)}...` : text;
|
||||
|
||||
return (
|
||||
<g transform={`translate(${x},${y})`}>
|
||||
<text
|
||||
x={10}
|
||||
y={-24}
|
||||
fill={theme === "dark" ? "#94a3b8" : "#374151"}
|
||||
fontSize={12}
|
||||
textAnchor="start"
|
||||
dominantBaseline="middle"
|
||||
>
|
||||
{truncatedText}
|
||||
</text>
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
interface FailedSectionsListProps {
|
||||
sections: FailedSection[];
|
||||
}
|
||||
|
||||
const title = (
|
||||
<h3 className="whitespace-nowrap text-xs font-semibold uppercase tracking-wide">
|
||||
<h3 className="mb-2 whitespace-nowrap text-xs font-semibold uppercase tracking-wide">
|
||||
Failed Sections (Top 5)
|
||||
</h3>
|
||||
);
|
||||
@@ -74,26 +98,28 @@ export const BarChart = ({ sections }: FailedSectionsListProps) => {
|
||||
// Check if there are no failed sections
|
||||
if (!sections || sections.length === 0) {
|
||||
return (
|
||||
<div className="flex w-[400px] flex-col items-center justify-between lg:w-[600px]">
|
||||
<div className="flex w-[400px] flex-col items-center justify-between">
|
||||
{title}
|
||||
<div className="flex h-[320px] w-full items-center justify-center">
|
||||
<p className="text-sm text-gray-500">There are no failed sections</p>
|
||||
<p className="text-sm text-gray-800 dark:text-gray-200">
|
||||
There are no failed sections
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex h-[320px] w-[400px] flex-col items-center justify-between lg:w-[400px]">
|
||||
<div>{title}</div>
|
||||
<div className="flex h-[320px] w-[400px] flex-col items-center justify-between">
|
||||
{title}
|
||||
|
||||
<div className="h-full w-full">
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<RechartsBarChart
|
||||
data={chartData}
|
||||
layout="vertical"
|
||||
margin={{ top: 12, bottom: 0 }}
|
||||
maxBarSize={32}
|
||||
margin={{ top: 12, bottom: 0, right: 0, left: -56 }}
|
||||
maxBarSize={30}
|
||||
>
|
||||
<XAxis
|
||||
type="number"
|
||||
@@ -111,16 +137,7 @@ export const BarChart = ({ sections }: FailedSectionsListProps) => {
|
||||
<YAxis
|
||||
type="category"
|
||||
dataKey="name"
|
||||
width={1}
|
||||
tick={{
|
||||
fontSize: 12,
|
||||
fill: theme === "dark" ? "#94a3b8" : "#374151",
|
||||
textAnchor: "start",
|
||||
style: {
|
||||
transform: "translateX(10px) translateY(-26px)",
|
||||
},
|
||||
width: 400,
|
||||
}}
|
||||
tick={(props) => <CustomYAxisTick {...props} theme={theme} />}
|
||||
axisLine={false}
|
||||
tickLine={false}
|
||||
/>
|
||||
@@ -153,8 +170,13 @@ export const BarChart = ({ sections }: FailedSectionsListProps) => {
|
||||
}}
|
||||
>
|
||||
{props.payload.map((entry: any, index: number) => (
|
||||
<div key={index} style={{ color: entry.color }}>
|
||||
{translateType(entry.dataKey)}: {entry.value}
|
||||
<div key={index} className="max-w-[200px]">
|
||||
<p>{data.name}</p>
|
||||
<p>
|
||||
<span style={{ color: entry.color }}>
|
||||
{translateType(entry.dataKey)}: {entry.value}
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -180,6 +202,7 @@ export const BarChart = ({ sections }: FailedSectionsListProps) => {
|
||||
width: "100%",
|
||||
paddingTop: "16px",
|
||||
marginBottom: "16px",
|
||||
marginLeft: "56px",
|
||||
}}
|
||||
iconType="circle"
|
||||
layout="horizontal"
|
||||
|
||||
@@ -30,6 +30,12 @@ const capitalizeFirstLetter = (text: string): string => {
|
||||
);
|
||||
};
|
||||
|
||||
const title = (
|
||||
<h3 className="mb-2 whitespace-nowrap text-xs font-semibold uppercase tracking-wide">
|
||||
Sections Failure Rate
|
||||
</h3>
|
||||
);
|
||||
|
||||
export const HeatmapChart = ({ categories = [] }: HeatmapChartProps) => {
|
||||
const { theme } = useTheme();
|
||||
const [hoveredItem, setHoveredItem] = useState<CategoryData | null>(null);
|
||||
@@ -45,9 +51,7 @@ export const HeatmapChart = ({ categories = [] }: HeatmapChartProps) => {
|
||||
if (!categories.length || heatmapData.length === 0) {
|
||||
return (
|
||||
<div className="flex w-[400px] flex-col items-center justify-between lg:w-[400px]">
|
||||
<h3 className="whitespace-nowrap text-xs font-semibold uppercase tracking-wide">
|
||||
Sections Failure Rate
|
||||
</h3>
|
||||
{title}
|
||||
<div className="flex h-[320px] w-full items-center justify-center">
|
||||
<p className="text-sm text-gray-500">No category data available</p>
|
||||
</div>
|
||||
@@ -70,11 +74,7 @@ export const HeatmapChart = ({ categories = [] }: HeatmapChartProps) => {
|
||||
|
||||
return (
|
||||
<div className="flex h-[320px] w-[400px] flex-col items-center justify-between lg:w-[400px]">
|
||||
<div>
|
||||
<h3 className="whitespace-nowrap text-xs font-semibold uppercase tracking-wide">
|
||||
Sections Failure Rate
|
||||
</h3>
|
||||
</div>
|
||||
{title}
|
||||
|
||||
<div className="h-full w-full p-2">
|
||||
<div
|
||||
|
||||
@@ -7,7 +7,7 @@ export const ISOCustomDetails = ({
|
||||
}) => {
|
||||
return (
|
||||
<div className="mb-4">
|
||||
<div className="mb-2 text-sm text-gray-600">
|
||||
<div className="mb-2 text-sm text-gray-800 dark:text-gray-200">
|
||||
{requirement.description}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 text-sm">
|
||||
|
||||
@@ -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 (
|
||||
<div className="space-y-4">
|
||||
{requirement.description && (
|
||||
<div>
|
||||
<h4 className="text-muted-foreground mb-1 text-sm font-medium">
|
||||
Description
|
||||
</h4>
|
||||
<p className="text-sm">{requirement.description}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{auditChecklist && auditChecklist.length > 0 && (
|
||||
<div>
|
||||
<h4 className="text-muted-foreground mb-1 text-sm font-medium">
|
||||
Audit Checklist
|
||||
</h4>
|
||||
<div className="space-y-2">
|
||||
{auditChecklist.map((item: string, index: number) => (
|
||||
<div key={index} className="flex items-start gap-2">
|
||||
<span className="text-muted-foreground mt-1 text-xs">•</span>
|
||||
<p className="text-sm">{item}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{relatedRegulations && relatedRegulations.length > 0 && (
|
||||
<div>
|
||||
<h4 className="text-muted-foreground mb-1 text-sm font-medium">
|
||||
Related Regulations
|
||||
</h4>
|
||||
<div className="space-y-2">
|
||||
{relatedRegulations.map((regulation: string, index: number) => (
|
||||
<div key={index} className="flex items-start gap-2">
|
||||
<span className="text-muted-foreground mt-1 text-xs">•</span>
|
||||
<p className="text-sm">{regulation}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{auditEvidence && auditEvidence.length > 0 && (
|
||||
<div>
|
||||
<h4 className="text-muted-foreground mb-1 text-sm font-medium">
|
||||
Audit Evidence
|
||||
</h4>
|
||||
<div className="space-y-2">
|
||||
{auditEvidence.map((evidence: string, index: number) => (
|
||||
<div key={index} className="flex items-start gap-2">
|
||||
<span className="text-muted-foreground mt-1 text-xs">•</span>
|
||||
<p className="text-sm">{evidence}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{nonComplianceCases && nonComplianceCases.length > 0 && (
|
||||
<div>
|
||||
<h4 className="text-muted-foreground mb-1 text-sm font-medium">
|
||||
Non-Compliance Cases
|
||||
</h4>
|
||||
<div className="space-y-2">
|
||||
{nonComplianceCases.map((caseItem: string, index: number) => (
|
||||
<div key={index} className="flex items-start gap-2">
|
||||
<span className="text-muted-foreground mt-1 text-xs">•</span>
|
||||
<p className="text-sm">{caseItem}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -4,7 +4,7 @@ import { Skeleton } from "@nextui-org/react";
|
||||
|
||||
export const BarChartSkeleton = () => {
|
||||
return (
|
||||
<div className="flex w-[400px] flex-col items-center justify-between lg:w-[600px]">
|
||||
<div className="flex w-[400px] flex-col items-center justify-between">
|
||||
{/* Title skeleton */}
|
||||
<Skeleton className="h-4 w-40 rounded-lg">
|
||||
<div className="h-4 bg-default-200" />
|
||||
|
||||
@@ -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<string, ComplianceMapper> = {
|
||||
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)
|
||||
|
||||
@@ -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<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 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: (
|
||||
<ComplianceAccordionTitle
|
||||
label={category.name}
|
||||
pass={category.pass}
|
||||
fail={category.fail}
|
||||
manual={category.manual}
|
||||
isParentLevel={true}
|
||||
/>
|
||||
),
|
||||
content: "",
|
||||
items: category.controls.map((control, i: number) => {
|
||||
return {
|
||||
key: `${framework.name}-${category.name}-control-${i}`,
|
||||
title: (
|
||||
<ComplianceAccordionTitle
|
||||
label={control.label}
|
||||
pass={control.pass}
|
||||
fail={control.fail}
|
||||
manual={control.manual}
|
||||
/>
|
||||
),
|
||||
content: "",
|
||||
items: control.requirements.map((requirement, j: number) => {
|
||||
const itemKey = `${framework.name}-${category.name}-control-${i}-req-${j}`;
|
||||
|
||||
return {
|
||||
key: itemKey,
|
||||
title: (
|
||||
<ComplianceAccordionRequirementTitle
|
||||
type=""
|
||||
name={requirement.section as string}
|
||||
status={requirement.status as FindingStatus}
|
||||
/>
|
||||
),
|
||||
content: (
|
||||
<ClientAccordionContent
|
||||
requirement={requirement}
|
||||
scanId={scanId || ""}
|
||||
framework={framework.name}
|
||||
disableFindings={
|
||||
requirement.check_ids.length === 0 &&
|
||||
requirement.manual === 0
|
||||
}
|
||||
/>
|
||||
),
|
||||
items: [],
|
||||
};
|
||||
}),
|
||||
isDisabled:
|
||||
control.pass === 0 && control.fail === 0 && control.manual === 0,
|
||||
};
|
||||
}),
|
||||
};
|
||||
}),
|
||||
);
|
||||
};
|
||||
+12
-1
@@ -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[];
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user