-
- {totalEntries} entries in Total.
+
+ {totalEntries} entries in total
-
- {/* Rows per page selector */}
-
-
Rows per page
-
+ )}
);
}
diff --git a/ui/components/ui/table/data-table.tsx b/ui/components/ui/table/data-table.tsx
index b759e4316d..49ad556897 100644
--- a/ui/components/ui/table/data-table.tsx
+++ b/ui/components/ui/table/data-table.tsx
@@ -29,12 +29,14 @@ interface DataTableProviderProps
{
data: TData[];
metadata?: MetaDataProps;
customFilters?: FilterOption[];
+ disableScroll?: boolean;
}
export function DataTable({
columns,
data,
metadata,
+ disableScroll = false,
}: DataTableProviderProps) {
const [sorting, setSorting] = useState([]);
const [columnFilters, setColumnFilters] = useState([]);
@@ -109,7 +111,10 @@ export function DataTable({
{metadata && (
-
+
)}
>
diff --git a/ui/components/ui/table/status-finding-badge.tsx b/ui/components/ui/table/status-finding-badge.tsx
index b9532d3b5d..8177d116d7 100644
--- a/ui/components/ui/table/status-finding-badge.tsx
+++ b/ui/components/ui/table/status-finding-badge.tsx
@@ -16,10 +16,12 @@ const statusColorMap: Record<
export const StatusFindingBadge = ({
status,
size = "sm",
+ value,
...props
}: {
status: FindingStatus;
size?: "sm" | "md" | "lg";
+ value?: string | number;
}) => {
const color = statusColorMap[status];
@@ -33,6 +35,7 @@ export const StatusFindingBadge = ({
>
{status.charAt(0).toUpperCase() + status.slice(1).toLowerCase()}
+ {value !== undefined && `: ${value}`}
);
diff --git a/ui/components/users/profile/membership-item.tsx b/ui/components/users/profile/membership-item.tsx
index 5db1e5cc6b..a22f33dda0 100644
--- a/ui/components/users/profile/membership-item.tsx
+++ b/ui/components/users/profile/membership-item.tsx
@@ -5,7 +5,7 @@ import { useState } from "react";
import { CustomAlertModal, CustomButton } from "@/components/ui/custom";
import { DateWithTime, InfoField } from "@/components/ui/entities";
-import { MembershipDetailData } from "@/types/users/users";
+import { MembershipDetailData } from "@/types/users";
import { EditTenantForm } from "../forms";
diff --git a/ui/components/users/profile/memberships-card.tsx b/ui/components/users/profile/memberships-card.tsx
index 43138c74c5..0aba1b4834 100644
--- a/ui/components/users/profile/memberships-card.tsx
+++ b/ui/components/users/profile/memberships-card.tsx
@@ -1,6 +1,6 @@
import { Card, CardBody, CardHeader } from "@nextui-org/react";
-import { MembershipDetailData, TenantDetailData } from "@/types/users/users";
+import { MembershipDetailData, TenantDetailData } from "@/types/users";
import { MembershipItem } from "./membership-item";
diff --git a/ui/components/users/profile/role-item.tsx b/ui/components/users/profile/role-item.tsx
index 460d9d5398..4d1ad7861e 100644
--- a/ui/components/users/profile/role-item.tsx
+++ b/ui/components/users/profile/role-item.tsx
@@ -6,7 +6,7 @@ import { useState } from "react";
import { CustomButton } from "@/components/ui/custom/custom-button";
import { getRolePermissions } from "@/lib/permissions";
-import { RoleData, RoleDetail } from "@/types/users/users";
+import { RoleData, RoleDetail } from "@/types/users";
interface PermissionItemProps {
enabled: boolean;
diff --git a/ui/components/users/profile/roles-card.tsx b/ui/components/users/profile/roles-card.tsx
index 5135c41528..126984be08 100644
--- a/ui/components/users/profile/roles-card.tsx
+++ b/ui/components/users/profile/roles-card.tsx
@@ -1,6 +1,6 @@
import { Card, CardBody, CardHeader } from "@nextui-org/react";
-import { RoleData, RoleDetail } from "@/types/users/users";
+import { RoleData, RoleDetail } from "@/types/users";
import { RoleItem } from "./role-item";
diff --git a/ui/components/users/profile/user-basic-info-card.tsx b/ui/components/users/profile/user-basic-info-card.tsx
index 37805b2c55..7085953062 100644
--- a/ui/components/users/profile/user-basic-info-card.tsx
+++ b/ui/components/users/profile/user-basic-info-card.tsx
@@ -3,7 +3,7 @@
import { Card, CardBody, Divider } from "@nextui-org/react";
import { DateWithTime, InfoField, SnippetChip } from "@/components/ui/entities";
-import { UserDataWithRoles } from "@/types/users/users";
+import { UserDataWithRoles } from "@/types/users";
import { ProwlerShort } from "../../icons";
diff --git a/ui/lib/compliance/ens.tsx b/ui/lib/compliance/ens.tsx
new file mode 100644
index 0000000000..bab025dd06
--- /dev/null
+++ b/ui/lib/compliance/ens.tsx
@@ -0,0 +1,242 @@
+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,
+ MappedComplianceData,
+ Requirement,
+ RequirementItemData,
+ RequirementsData,
+ RequirementStatus,
+} from "@/types/compliance";
+
+export const translateType = (type: string) => {
+ switch (type.toLowerCase()) {
+ case "requisito":
+ return "Requirement";
+ case "recomendacion":
+ return "Recommendation";
+ case "refuerzo":
+ return "Reinforcement";
+ case "medida":
+ return "Measure";
+ default:
+ return type;
+ }
+};
+
+export const mapComplianceData = (
+ attributesData: AttributesData,
+ requirementsData: RequirementsData,
+): MappedComplianceData => {
+ 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 attrs = attributeItem.attributes?.attributes?.metadata?.[0];
+ if (!attrs) continue;
+
+ // Get corresponding requirement data
+ const requirementData = requirementsMap.get(id);
+ if (!requirementData) continue;
+
+ const frameworkName = attrs.Marco;
+ const categoryName = attrs.Categoria;
+ const groupControl = attrs.IdGrupoControl;
+ const type = attrs.Tipo;
+ const description = attributeItem.attributes.description;
+ const status = requirementData.attributes.status || "";
+ const controlDescription = attrs.DescripcionControl || "";
+ const checks = attributeItem.attributes.attributes.check_ids || [];
+ const isManual = attrs.ModoEjecucion === "manual";
+ const requirementName = id;
+ const groupControlLabel = `${groupControl} - ${description}`;
+
+ // 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
+ 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
+ let control = category.controls.find((c) => c.label === groupControlLabel);
+ if (!control) {
+ control = {
+ label: groupControlLabel,
+ type,
+ pass: 0,
+ fail: 0,
+ manual: 0,
+ requirements: [],
+ };
+ category.controls.push(control);
+ }
+
+ // Create requirement
+ const finalStatus: RequirementStatus = isManual
+ ? "MANUAL"
+ : (status as RequirementStatus);
+ const requirement: Requirement = {
+ name: requirementName,
+ description: controlDescription,
+ status: finalStatus,
+ type,
+ check_ids: checks,
+ pass: finalStatus === "PASS" ? 1 : 0,
+ fail: finalStatus === "FAIL" ? 1 : 0,
+ manual: finalStatus === "MANUAL" ? 1 : 0,
+ nivel: attrs.Nivel || "",
+ dimensiones: attrs.Dimensiones || [],
+ };
+
+ 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: MappedComplianceData,
+ scanId: string | undefined,
+): AccordionItemProps[] => {
+ return data.map((framework) => {
+ return {
+ key: framework.name,
+ title: (
+
+ ),
+ content: "",
+ items: 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:
+ requirement.check_ids.length === 0 &&
+ requirement.manual === 0,
+ };
+ }),
+ isDisabled:
+ control.pass === 0 &&
+ control.fail === 0 &&
+ control.manual === 0,
+ };
+ }),
+ };
+ }),
+ };
+ });
+};
diff --git a/ui/lib/permissions.ts b/ui/lib/permissions.ts
index 7315258a9d..7d1d4448e8 100644
--- a/ui/lib/permissions.ts
+++ b/ui/lib/permissions.ts
@@ -1,4 +1,4 @@
-import { RolePermissionAttributes } from "@/types/users/users";
+import { RolePermissionAttributes } from "@/types/users";
export const isUserOwnerAndHasManageAccount = (
roles: any[],
diff --git a/ui/public/ens.png b/ui/public/ens.png
new file mode 100644
index 0000000000..c3e6433f31
Binary files /dev/null and b/ui/public/ens.png differ
diff --git a/ui/types/compliance.ts b/ui/types/compliance.ts
new file mode 100644
index 0000000000..beeafcd836
--- /dev/null
+++ b/ui/types/compliance.ts
@@ -0,0 +1,119 @@
+export type RequirementStatus = "PASS" | "FAIL" | "MANUAL" | "No findings";
+
+export type ComplianceId = "ens_rd2022_aws";
+
+export interface CompliancesOverview {
+ data: ComplianceOverviewData[];
+}
+
+export interface ComplianceOverviewData {
+ type: "compliance-requirements-status";
+ id: string;
+ attributes: {
+ framework: string;
+ version: string;
+ requirements_passed: number;
+ requirements_failed: number;
+ requirements_manual: number;
+ total_requirements: number;
+ };
+}
+
+export interface Requirement {
+ name: string;
+ description: string;
+ status: RequirementStatus;
+ type: string;
+ pass: number;
+ fail: number;
+ manual: number;
+ check_ids: string[];
+ // ENS
+ nivel?: string;
+ dimensiones?: string[];
+}
+
+export interface Control {
+ label: string;
+ type: string;
+ pass: number;
+ fail: number;
+ manual: number;
+ requirements: Requirement[];
+}
+
+export interface Category {
+ name: string;
+ pass: number;
+ fail: number;
+ manual: number;
+ controls: Control[];
+}
+
+export interface Framework {
+ name: string;
+ pass: number;
+ fail: number;
+ manual: number;
+ categories: Category[];
+}
+
+export type MappedComplianceData = Framework[];
+
+export interface FailedSection {
+ name: string;
+ total: number;
+ types: { [key: string]: number };
+}
+
+export interface RequirementsTotals {
+ pass: number;
+ fail: number;
+ manual: number;
+}
+
+// API Responses types:
+export interface AttributesMetadata {
+ IdGrupoControl: string;
+ Marco: string;
+ Categoria: string;
+ DescripcionControl: string;
+ Tipo: string;
+ Nivel: string;
+ Dimensiones: string[];
+ ModoEjecucion: string;
+ Dependencias: any[];
+}
+
+export interface AttributesItemData {
+ type: "compliance-requirements-attributes";
+ id: string;
+ attributes: {
+ framework: string;
+ version: string;
+ description: string;
+ attributes: {
+ metadata: AttributesMetadata[];
+ check_ids: string[];
+ };
+ };
+}
+
+export interface RequirementItemData {
+ type: "compliance-requirements-details";
+ id: string;
+ attributes: {
+ framework: string;
+ version: string;
+ description: string;
+ status: RequirementStatus;
+ };
+}
+
+export interface AttributesData {
+ data: AttributesItemData[];
+}
+
+export interface RequirementsData {
+ data: RequirementItemData[];
+}
diff --git a/ui/types/components.ts b/ui/types/components.ts
index f5928386d0..c8a8054f80 100644
--- a/ui/types/components.ts
+++ b/ui/types/components.ts
@@ -1,8 +1,6 @@
import { LucideIcon } from "lucide-react";
import { SVGProps } from "react";
-import { ProviderType } from "./providers";
-
export type IconSvgProps = SVGProps & {
size?: number;
};
@@ -44,18 +42,6 @@ export interface CollapseMenuButtonProps {
isOpen: boolean | undefined;
}
-export interface SelectScanComplianceDataProps {
- scans: (ScanProps & {
- providerInfo: {
- provider: ProviderType;
- uid: string;
- alias: string;
- };
- })[];
- selectedScanId: string;
- onSelectionChange: (selectedKey: string) => void;
-}
-
export type NextUIVariants =
| "solid"
| "faded"
@@ -269,53 +255,6 @@ export interface ApiError {
};
code: string;
}
-export interface CompliancesOverview {
- links: {
- first: string;
- last: string;
- next: string | null;
- prev: string | null;
- };
- data: ComplianceOverviewData[];
- meta: {
- pagination: {
- page: number;
- pages: number;
- count: number;
- };
- version: string;
- };
-}
-
-export interface ComplianceOverviewData {
- type: "compliance-overviews";
- id: string;
- attributes: {
- inserted_at: string;
- compliance_id: string;
- framework: string;
- version: string;
- requirements_status: {
- passed: number;
- failed: number;
- manual: number;
- total: number;
- };
- region: string;
- provider_type: string;
- };
- relationships: {
- scan: {
- data: {
- type: "scans";
- id: string;
- };
- };
- };
- links: {
- self: string;
- };
-}
export interface InvitationProps {
type: "invitations";
@@ -497,52 +436,9 @@ export interface UserProps {
}[];
}
-export interface ScanProps {
- type: "scans";
- id: string;
- attributes: {
- name: string;
- trigger: "scheduled" | "manual";
- state:
- | "available"
- | "scheduled"
- | "executing"
- | "completed"
- | "failed"
- | "cancelled";
- unique_resource_count: number;
- progress: number;
- scanner_args: {
- only_logs?: boolean;
- excluded_checks?: string[];
- aws_retries_max_attempts?: number;
- } | null;
- duration: number;
- started_at: string;
- inserted_at: string;
- completed_at: string;
- scheduled_at: string;
- next_scan_at: string;
- };
- relationships: {
- provider: {
- data: {
- id: string;
- type: "providers";
- };
- };
- task: {
- data: {
- id: string;
- type: "tasks";
- };
- };
- };
- providerInfo?: {
- provider: ProviderType;
- uid: string;
- alias: string;
- };
+export interface FindingsResponse {
+ data: FindingProps[];
+ meta: MetaDataProps;
}
export interface FindingProps {
diff --git a/ui/types/index.ts b/ui/types/index.ts
index e35a2815da..1483946f3e 100644
--- a/ui/types/index.ts
+++ b/ui/types/index.ts
@@ -3,3 +3,4 @@ export * from "./components";
export * from "./filters";
export * from "./formSchemas";
export * from "./providers";
+export * from "./scans";
diff --git a/ui/types/scans.ts b/ui/types/scans.ts
new file mode 100644
index 0000000000..ac8bfe64e6
--- /dev/null
+++ b/ui/types/scans.ts
@@ -0,0 +1,49 @@
+import { ProviderType } from "./providers";
+
+export interface ScanProps {
+ type: "scans";
+ id: string;
+ attributes: {
+ name: string;
+ trigger: "scheduled" | "manual";
+ state:
+ | "available"
+ | "scheduled"
+ | "executing"
+ | "completed"
+ | "failed"
+ | "cancelled";
+ unique_resource_count: number;
+ progress: number;
+ scanner_args: {
+ only_logs?: boolean;
+ excluded_checks?: string[];
+ aws_retries_max_attempts?: number;
+ } | null;
+ duration: number;
+ started_at: string;
+ inserted_at: string;
+ completed_at: string;
+ scheduled_at: string;
+ next_scan_at: string;
+ };
+ relationships: {
+ provider: {
+ data: {
+ id: string;
+ type: "providers";
+ };
+ };
+ task: {
+ data: {
+ id: string;
+ type: "tasks";
+ };
+ };
+ };
+ providerInfo?: {
+ provider: ProviderType;
+ uid: string;
+ alias: string;
+ };
+}
diff --git a/ui/types/users/users.ts b/ui/types/users.ts
similarity index 100%
rename from ui/types/users/users.ts
rename to ui/types/users.ts
diff --git a/ui/types/users/index.ts b/ui/types/users/index.ts
deleted file mode 100644
index ddf77b4624..0000000000
--- a/ui/types/users/index.ts
+++ /dev/null
@@ -1 +0,0 @@
-export * from "./users";