mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 12:31:54 +00:00
Merge pull request #95 from prowler-cloud/PRWLR-5363-Compliance-Overview
Compliance overview - first iteration -
This commit is contained in:
@@ -62,7 +62,7 @@ export const createNewUser = async (
|
||||
|
||||
const bodyData = {
|
||||
data: {
|
||||
type: "User",
|
||||
type: "users",
|
||||
attributes: {
|
||||
name: formData.name,
|
||||
email: formData.email,
|
||||
@@ -99,7 +99,7 @@ export const getToken = async (formData: z.infer<typeof formSchemaSignIn>) => {
|
||||
|
||||
const bodyData = {
|
||||
data: {
|
||||
type: "Token",
|
||||
type: "tokens",
|
||||
attributes: {
|
||||
email: formData.email,
|
||||
password: formData.password,
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
import { parseStringify } from "@/lib";
|
||||
|
||||
export const getCompliance = async ({ page = 1 }) => {
|
||||
if (isNaN(Number(page)) || page < 1) redirect("/compliance");
|
||||
const keyServer = process.env.SITE_URL;
|
||||
|
||||
try {
|
||||
const compliance = await fetch(
|
||||
`${keyServer}/api/compliance?page%5Bnumber%5D=${page}`,
|
||||
);
|
||||
const data = await compliance.json();
|
||||
const parsedData = parseStringify(data);
|
||||
revalidatePath("/compliance");
|
||||
return parsedData;
|
||||
} catch (error) {
|
||||
console.error("Error fetching Compliance:", error);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
export const getErrorMessage = (error: unknown): string => {
|
||||
let message: string;
|
||||
|
||||
if (error instanceof Error) {
|
||||
message = error.message;
|
||||
} else if (error && typeof error === "object" && "message" in error) {
|
||||
message = String(error.message);
|
||||
} else if (typeof error === "string") {
|
||||
message = error;
|
||||
} else {
|
||||
message = "Oops! Something went wrong.";
|
||||
}
|
||||
return message;
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
"use server";
|
||||
import { revalidatePath } from "next/cache";
|
||||
|
||||
import { auth } from "@/auth.config";
|
||||
import { parseStringify } from "@/lib";
|
||||
|
||||
export const getCompliancesOverview = async ({
|
||||
scanId,
|
||||
region,
|
||||
}: {
|
||||
scanId: string;
|
||||
region?: string | string[];
|
||||
}) => {
|
||||
const session = await auth();
|
||||
|
||||
const keyServer = process.env.API_BASE_URL;
|
||||
const url = new URL(`${keyServer}/compliance-overviews`);
|
||||
|
||||
if (scanId) url.searchParams.append("filter[scan_id]", scanId);
|
||||
|
||||
if (region) {
|
||||
const regionValue = Array.isArray(region) ? region.join(",") : region;
|
||||
url.searchParams.append("filter[region__in]", regionValue);
|
||||
}
|
||||
|
||||
try {
|
||||
const compliances = await fetch(url.toString(), {
|
||||
headers: {
|
||||
Accept: "application/vnd.api+json",
|
||||
Authorization: `Bearer ${session?.accessToken}`,
|
||||
},
|
||||
});
|
||||
const data = await compliances.json();
|
||||
const parsedData = parseStringify(data);
|
||||
|
||||
revalidatePath("/compliance");
|
||||
return parsedData;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error fetching providers:", error);
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./compliances";
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./invitation";
|
||||
@@ -42,6 +42,7 @@ export const getInvitations = async ({
|
||||
revalidatePath("/invitations");
|
||||
return parsedData;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error fetching invitations:", error);
|
||||
return undefined;
|
||||
}
|
||||
@@ -56,7 +57,7 @@ export const sendInvite = async (formData: FormData) => {
|
||||
|
||||
const body = JSON.stringify({
|
||||
data: {
|
||||
type: "Invitation",
|
||||
type: "invitations",
|
||||
attributes: {
|
||||
email,
|
||||
},
|
||||
@@ -104,7 +105,7 @@ export const updateInvite = async (formData: FormData) => {
|
||||
},
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
type: "Invitation",
|
||||
type: "invitations",
|
||||
id: invitationId,
|
||||
attributes: {
|
||||
email: invitationEmail,
|
||||
@@ -117,6 +118,7 @@ export const updateInvite = async (formData: FormData) => {
|
||||
revalidatePath("/invitations");
|
||||
return parseStringify(data);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error("Error updating invitation:", error);
|
||||
return {
|
||||
error: getErrorMessage(error),
|
||||
|
||||
@@ -90,7 +90,7 @@ export const updateProvider = async (formData: FormData) => {
|
||||
},
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
type: "Provider",
|
||||
type: "providers",
|
||||
id: providerId,
|
||||
attributes: {
|
||||
alias: providerAlias,
|
||||
@@ -129,7 +129,7 @@ export const addProvider = async (formData: FormData) => {
|
||||
},
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
type: "Provider",
|
||||
type: "providers",
|
||||
attributes: {
|
||||
provider: providerType,
|
||||
uid: providerUid,
|
||||
@@ -209,7 +209,7 @@ export const addCredentialsProvider = async (formData: FormData) => {
|
||||
|
||||
const bodyData = {
|
||||
data: {
|
||||
type: "ProviderSecret",
|
||||
type: "provider-secrets",
|
||||
attributes: {
|
||||
secret_type: secretType,
|
||||
secret,
|
||||
@@ -219,7 +219,7 @@ export const addCredentialsProvider = async (formData: FormData) => {
|
||||
provider: {
|
||||
data: {
|
||||
id: providerId,
|
||||
type: "Provider",
|
||||
type: "providers",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -91,14 +91,14 @@ export const scanOnDemand = async (formData: FormData) => {
|
||||
},
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
type: "Scan",
|
||||
type: "scans",
|
||||
attributes: {
|
||||
name: scanName,
|
||||
},
|
||||
relationships: {
|
||||
provider: {
|
||||
data: {
|
||||
type: "Provider",
|
||||
type: "providers",
|
||||
id: providerId,
|
||||
},
|
||||
},
|
||||
@@ -136,7 +136,7 @@ export const updateScan = async (formData: FormData) => {
|
||||
},
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
type: "Scan",
|
||||
type: "scans",
|
||||
id: scanId,
|
||||
attributes: {
|
||||
name: scanName,
|
||||
|
||||
@@ -69,7 +69,7 @@ export const updateUser = async (formData: FormData) => {
|
||||
},
|
||||
body: JSON.stringify({
|
||||
data: {
|
||||
type: "User",
|
||||
type: "users",
|
||||
id: userId,
|
||||
attributes: {
|
||||
name: userName,
|
||||
|
||||
@@ -1,30 +1,55 @@
|
||||
import { Spacer } from "@nextui-org/react";
|
||||
import { redirect } from "next/navigation";
|
||||
import { Suspense } from "react";
|
||||
|
||||
import { getCompliance } from "@/actions/compliance";
|
||||
import { getCompliancesOverview } from "@/actions/compliances";
|
||||
import { getScans } from "@/actions/scans";
|
||||
import {
|
||||
ComplianceCard,
|
||||
ComplianceSkeletonGrid,
|
||||
} from "@/components/compliance";
|
||||
import { FilterControls } from "@/components/filters";
|
||||
import { DataCompliance } from "@/components/compliance/data-compliance";
|
||||
import { Header } from "@/components/ui";
|
||||
import { SearchParamsProps } from "@/types";
|
||||
import { ComplianceOverviewData, SearchParamsProps } from "@/types";
|
||||
|
||||
export default async function Compliance({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: SearchParamsProps;
|
||||
}) {
|
||||
const searchParamsKey = JSON.stringify(searchParams || {});
|
||||
const scansData = await getScans({});
|
||||
const scanList = scansData?.data.map((scan: any) => ({
|
||||
id: scan.id,
|
||||
name: scan.attributes.name || "Unnamed Scan",
|
||||
state: scan.attributes.state,
|
||||
progress: scan.attributes.progress,
|
||||
}));
|
||||
|
||||
const selectedScanId = searchParams.scanId || scanList[0]?.id;
|
||||
|
||||
// Fetch compliance data for regions
|
||||
const compliancesData = await getCompliancesOverview({
|
||||
scanId: selectedScanId,
|
||||
});
|
||||
|
||||
// Extract unique regions
|
||||
const regions = compliancesData?.data
|
||||
? Array.from(
|
||||
new Set(
|
||||
compliancesData.data.map(
|
||||
(compliance: ComplianceOverviewData) =>
|
||||
compliance.attributes.region as string,
|
||||
),
|
||||
),
|
||||
)
|
||||
: [];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header title="Compliance" icon="fluent-mdl2:compliance-audit" />
|
||||
<Spacer y={4} />
|
||||
<FilterControls mutedFindings={false} />
|
||||
<Spacer y={4} />
|
||||
<Suspense key={searchParamsKey} fallback={<ComplianceSkeletonGrid />}>
|
||||
<DataCompliance scans={scanList} regions={regions as string[]} />
|
||||
<Spacer y={12} />
|
||||
<Suspense fallback={<ComplianceSkeletonGrid />}>
|
||||
<SSRComplianceGrid searchParams={searchParams} />
|
||||
</Suspense>
|
||||
</>
|
||||
@@ -36,28 +61,56 @@ const SSRComplianceGrid = async ({
|
||||
}: {
|
||||
searchParams: SearchParamsProps;
|
||||
}) => {
|
||||
const page = parseInt(searchParams.page?.toString() || "1", 10);
|
||||
const compliancesData = await getCompliance({ page });
|
||||
const [compliances] = await Promise.all([compliancesData]);
|
||||
const scanId = searchParams.scanId?.toString() || "";
|
||||
|
||||
if (compliances?.errors) redirect("/compliance");
|
||||
const regionFilter = searchParams["filter[region__in]"]?.toString() || "";
|
||||
|
||||
// Fetch compliance data
|
||||
const compliancesData = await getCompliancesOverview({
|
||||
scanId,
|
||||
region: regionFilter,
|
||||
});
|
||||
|
||||
// Check if the response contains no data
|
||||
if (!compliancesData || compliancesData?.data?.length === 0) {
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<div className="text-default-500">
|
||||
No compliance data available for the selected scan.
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Handle errors returned by the API
|
||||
if (compliancesData?.errors?.length > 0) {
|
||||
return (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<div className="text-default-500">Provide a valid scan ID.</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3 3xl:grid-cols-4">
|
||||
{compliances.compliance?.data.map((compliance: any) => (
|
||||
<ComplianceCard
|
||||
key={compliance.id}
|
||||
title={compliance.attributes.title}
|
||||
passingRequirements={compliance.attributes.passingRequirements}
|
||||
totalRequirements={compliance.attributes.totalRequirements}
|
||||
prevPassingRequirements={
|
||||
compliance.lastScan.attributes.passingRequirements
|
||||
}
|
||||
prevTotalRequirements={
|
||||
compliance.lastScan.attributes.totalRequirements
|
||||
}
|
||||
/>
|
||||
))}
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{compliancesData.data.map((compliance: ComplianceOverviewData) => {
|
||||
const { attributes } = compliance;
|
||||
const {
|
||||
framework,
|
||||
requirements_status: { passed, total },
|
||||
} = attributes;
|
||||
|
||||
return (
|
||||
<ComplianceCard
|
||||
key={compliance.id}
|
||||
title={framework}
|
||||
passingRequirements={passed}
|
||||
totalRequirements={total}
|
||||
prevPassingRequirements={passed}
|
||||
prevTotalRequirements={total}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
import data from "../../../dataCompliance.json";
|
||||
|
||||
export async function GET() {
|
||||
// Simulate fetching data with a delay
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
|
||||
return NextResponse.json({ compliance: data });
|
||||
}
|
||||
+1
-1
@@ -16,7 +16,7 @@ const refreshAccessToken = async (token: JwtPayload) => {
|
||||
|
||||
const bodyData = {
|
||||
data: {
|
||||
type: "TokenRefresh",
|
||||
type: "tokens-refresh",
|
||||
attributes: {
|
||||
refresh: (token as any).refreshToken,
|
||||
},
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
|
||||
import { SelectScanComplianceData } from "@/components/compliance/data-compliance";
|
||||
import { CrossIcon } from "@/components/icons";
|
||||
import { CustomButton, CustomDropdownFilter } from "@/components/ui/custom";
|
||||
|
||||
interface DataComplianceProps {
|
||||
scans: { id: string; name: string; state: string; progress: number }[];
|
||||
regions: string[];
|
||||
}
|
||||
|
||||
export const DataCompliance = ({ scans, regions }: DataComplianceProps) => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const [showClearButton, setShowClearButton] = useState(false);
|
||||
const scanIdParam = searchParams.get("scanId");
|
||||
const selectedScanId = scanIdParam || scans[0]?.id;
|
||||
|
||||
useEffect(() => {
|
||||
const hasFilters = Array.from(searchParams.keys()).some(
|
||||
(key) => key.startsWith("filter[") || key === "sort",
|
||||
);
|
||||
setShowClearButton(hasFilters);
|
||||
}, [searchParams]);
|
||||
const handleScanChange = (selectedKey: string) => {
|
||||
const params = new URLSearchParams(searchParams);
|
||||
params.set("scanId", selectedKey);
|
||||
router.push(`?${params.toString()}`);
|
||||
};
|
||||
|
||||
const pushDropdownFilter = useCallback(
|
||||
(key: string, values: string[]) => {
|
||||
const params = new URLSearchParams(searchParams);
|
||||
const filterKey = `filter[${key}]`;
|
||||
|
||||
if (values.length === 0) {
|
||||
params.delete(filterKey);
|
||||
} else {
|
||||
params.set(filterKey, values.join(","));
|
||||
}
|
||||
|
||||
router.push(`?${params.toString()}`);
|
||||
},
|
||||
[router, searchParams],
|
||||
);
|
||||
const clearAllFilters = useCallback(() => {
|
||||
const params = new URLSearchParams(searchParams.toString());
|
||||
Array.from(params.keys()).forEach((key) => {
|
||||
if (key.startsWith("filter[") || key === "sort") {
|
||||
params.delete(key);
|
||||
}
|
||||
});
|
||||
router.push(`?${params.toString()}`, { scroll: false });
|
||||
}, [router, searchParams]);
|
||||
return (
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="grid grid-cols-1 items-center gap-x-4 gap-y-4 md:grid-cols-2 xl:grid-cols-4">
|
||||
<SelectScanComplianceData
|
||||
scans={scans}
|
||||
selectedScanId={selectedScanId}
|
||||
onSelectionChange={handleScanChange}
|
||||
/>
|
||||
<CustomDropdownFilter
|
||||
filter={{
|
||||
key: "region__in",
|
||||
values: regions,
|
||||
labelCheckboxGroup: "Regions",
|
||||
}}
|
||||
onFilterChange={pushDropdownFilter}
|
||||
/>
|
||||
{showClearButton && (
|
||||
<CustomButton
|
||||
ariaLabel="Reset"
|
||||
className="w-full md:w-fit"
|
||||
onPress={clearAllFilters}
|
||||
variant="dashed"
|
||||
size="sm"
|
||||
endContent={<CrossIcon size={24} />}
|
||||
radius="sm"
|
||||
>
|
||||
Reset
|
||||
</CustomButton>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./data-compliance";
|
||||
export * from "./select-scan-compliance-data";
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Select, SelectItem } from "@nextui-org/react";
|
||||
|
||||
interface SelectScanComplianceDataProps {
|
||||
scans: { id: string; name: string; state: string; progress: number }[];
|
||||
selectedScanId: string;
|
||||
onSelectionChange: (selectedKey: string) => void;
|
||||
}
|
||||
|
||||
export const SelectScanComplianceData = ({
|
||||
scans,
|
||||
selectedScanId,
|
||||
onSelectionChange,
|
||||
}: SelectScanComplianceDataProps) => {
|
||||
return (
|
||||
<Select
|
||||
aria-label="Select a Scan"
|
||||
placeholder="Select a scan"
|
||||
labelPlacement="outside"
|
||||
size="md"
|
||||
selectedKeys={new Set([selectedScanId])}
|
||||
onSelectionChange={(keys) =>
|
||||
onSelectionChange(Array.from(keys)[0] as string)
|
||||
}
|
||||
renderValue={() => {
|
||||
const selectedItem = scans.find((item) => item.id === selectedScanId);
|
||||
return selectedItem ? (
|
||||
<div className="flex flex-col">
|
||||
<span className="font-bold">{selectedItem.name}</span>
|
||||
<span className="text-sm text-gray-500">
|
||||
State: {selectedItem.state}, Progress: {selectedItem.progress}%
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
"Select a scan"
|
||||
);
|
||||
}}
|
||||
>
|
||||
{scans.map((scan) => (
|
||||
<SelectItem key={scan.id} textValue={scan.name}>
|
||||
<div className="flex flex-col">
|
||||
<span className="font-bold">{scan.name}</span>
|
||||
<span className="text-sm text-gray-500">
|
||||
State: {scan.state}, Progress: {scan.progress}%
|
||||
</span>
|
||||
</div>
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
@@ -1,2 +1,2 @@
|
||||
export * from "./ComplianceCard";
|
||||
export * from "./ComplianceSkeletonGrid";
|
||||
export * from "./compliance-card";
|
||||
export * from "./compliance-skeleton-grid";
|
||||
|
||||
@@ -1,438 +0,0 @@
|
||||
{
|
||||
"links": {
|
||||
"first": "http://localhost:8080/api/v1/compliance?page%5Bnumber%5D=1",
|
||||
"last": "http://localhost:8080/api/v1/compliance?page%5Bnumber%5D=1",
|
||||
"next": null,
|
||||
"prev": null
|
||||
},
|
||||
"data": [
|
||||
{
|
||||
"id": "001",
|
||||
"attributes": {
|
||||
"title": "AWS Account Security Onboarding",
|
||||
"passingRequirements": 28,
|
||||
"totalRequirements": 83,
|
||||
"regions": ["us-east-1", "us-east-2"]
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 28,
|
||||
"totalRequirements": 83
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "002",
|
||||
"attributes": {
|
||||
"title": "AWS Audit Manager Control Tower Guardrails",
|
||||
"passingRequirements": 10,
|
||||
"totalRequirements": 14,
|
||||
"regions": ["us-west-1", "us-west-2"]
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 14,
|
||||
"totalRequirements": 14
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "003",
|
||||
"attributes": {
|
||||
"title": "AWS Foundational Security Best Practices",
|
||||
"passingRequirements": 22,
|
||||
"totalRequirements": 37,
|
||||
"regions": ["eu-west-1", "eu-west-2"]
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 22,
|
||||
"totalRequirements": 37
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "004",
|
||||
"attributes": {
|
||||
"title": "AWS Foundational Technical Review",
|
||||
"passingRequirements": 3,
|
||||
"totalRequirements": 45,
|
||||
"regions": ["ap-southeast-1", "ap-southeast-2"]
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 25,
|
||||
"totalRequirements": 45
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "005",
|
||||
"attributes": {
|
||||
"title": "AWS Well Architected Framework Reliability Pillar",
|
||||
"passingRequirements": 2,
|
||||
"totalRequirements": 3,
|
||||
"regions": []
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 0,
|
||||
"totalRequirements": 3
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "006",
|
||||
"attributes": {
|
||||
"title": "AWS Well Architected Framework Security Pillar",
|
||||
"passingRequirements": 19,
|
||||
"totalRequirements": 57,
|
||||
"regions": []
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 24,
|
||||
"totalRequirements": 57
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "007",
|
||||
"attributes": {
|
||||
"title": "CIS 1.4",
|
||||
"passingRequirements": 43,
|
||||
"totalRequirements": 58,
|
||||
"regions": []
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 40,
|
||||
"totalRequirements": 58
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "008",
|
||||
"attributes": {
|
||||
"title": "CIS 1.5",
|
||||
"passingRequirements": 48,
|
||||
"totalRequirements": 63,
|
||||
"regions": []
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 50,
|
||||
"totalRequirements": 63
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "009",
|
||||
"attributes": {
|
||||
"title": "CIS 2.0",
|
||||
"passingRequirements": 47,
|
||||
"totalRequirements": 64,
|
||||
"regions": []
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 64,
|
||||
"totalRequirements": 64
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "010",
|
||||
"attributes": {
|
||||
"title": "CIS 3.0",
|
||||
"passingRequirements": 45,
|
||||
"totalRequirements": 62,
|
||||
"regions": []
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 48,
|
||||
"totalRequirements": 62
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "011",
|
||||
"attributes": {
|
||||
"title": "CISA",
|
||||
"passingRequirements": 4,
|
||||
"totalRequirements": 16,
|
||||
"regions": []
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 4,
|
||||
"totalRequirements": 16
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "012",
|
||||
"attributes": {
|
||||
"title": "ENS RD2022 Categoría ALTA",
|
||||
"passingRequirements": 89,
|
||||
"totalRequirements": 189,
|
||||
"regions": []
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 82,
|
||||
"totalRequirements": 189
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "013",
|
||||
"attributes": {
|
||||
"title": "FFIEC",
|
||||
"passingRequirements": 8,
|
||||
"totalRequirements": 44,
|
||||
"regions": []
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 40,
|
||||
"totalRequirements": 44
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "014",
|
||||
"attributes": {
|
||||
"title": "FedRAMP Low Revision 4",
|
||||
"passingRequirements": 4,
|
||||
"totalRequirements": 18,
|
||||
"regions": []
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 4,
|
||||
"totalRequirements": 18
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "015",
|
||||
"attributes": {
|
||||
"title": "FedRamp Moderate Revision 4",
|
||||
"passingRequirements": 11,
|
||||
"totalRequirements": 64,
|
||||
"regions": []
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 28,
|
||||
"totalRequirements": 64
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "016",
|
||||
"attributes": {
|
||||
"title": "GDPR",
|
||||
"passingRequirements": 0,
|
||||
"totalRequirements": 3,
|
||||
"regions": []
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 0,
|
||||
"totalRequirements": 3
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "017",
|
||||
"attributes": {
|
||||
"title": "GxP 21 CFR Part 11",
|
||||
"passingRequirements": 1,
|
||||
"totalRequirements": 11,
|
||||
"regions": []
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 11,
|
||||
"totalRequirements": 11
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "018",
|
||||
"attributes": {
|
||||
"title": "GxP EU Annex 11",
|
||||
"passingRequirements": 5,
|
||||
"totalRequirements": 14,
|
||||
"regions": []
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 5,
|
||||
"totalRequirements": 14
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "019",
|
||||
"attributes": {
|
||||
"title": "HIPAA",
|
||||
"passingRequirements": 2,
|
||||
"totalRequirements": 32,
|
||||
"regions": []
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 9,
|
||||
"totalRequirements": 32
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "020",
|
||||
"attributes": {
|
||||
"title": "ISO27001 2013",
|
||||
"passingRequirements": 55,
|
||||
"totalRequirements": 79,
|
||||
"regions": []
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 66,
|
||||
"totalRequirements": 79
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "021",
|
||||
"attributes": {
|
||||
"title": "MITRE ATTACK",
|
||||
"passingRequirements": 7,
|
||||
"totalRequirements": 46,
|
||||
"regions": []
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 19,
|
||||
"totalRequirements": 46
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "022",
|
||||
"attributes": {
|
||||
"title": "NIST 800 171 Revision 2",
|
||||
"passingRequirements": 3,
|
||||
"totalRequirements": 50,
|
||||
"regions": []
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 3,
|
||||
"totalRequirements": 50
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "023",
|
||||
"attributes": {
|
||||
"title": "NIST 800 53 Revision 4",
|
||||
"passingRequirements": 21,
|
||||
"totalRequirements": 64,
|
||||
"regions": []
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 21,
|
||||
"totalRequirements": 64
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "024",
|
||||
"attributes": {
|
||||
"title": "NIST 800 53 Revision 5",
|
||||
"passingRequirements": 75,
|
||||
"totalRequirements": 288,
|
||||
"regions": []
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 75,
|
||||
"totalRequirements": 288
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "025",
|
||||
"attributes": {
|
||||
"title": "NIST CSF 1.1",
|
||||
"passingRequirements": 15,
|
||||
"totalRequirements": 56,
|
||||
"regions": []
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 46,
|
||||
"totalRequirements": 56
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "026",
|
||||
"attributes": {
|
||||
"title": "PCI 3.2.1",
|
||||
"passingRequirements": 11,
|
||||
"totalRequirements": 19,
|
||||
"regions": []
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 19,
|
||||
"totalRequirements": 19
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "027",
|
||||
"attributes": {
|
||||
"title": "RBI Cyber Security Framework",
|
||||
"passingRequirements": 3,
|
||||
"totalRequirements": 9,
|
||||
"regions": []
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 3,
|
||||
"totalRequirements": 9
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "028",
|
||||
"attributes": {
|
||||
"title": "SOC2",
|
||||
"passingRequirements": 7,
|
||||
"totalRequirements": 56,
|
||||
"regions": []
|
||||
},
|
||||
"lastScan": {
|
||||
"attributes": {
|
||||
"passingRequirements": 7,
|
||||
"totalRequirements": 56
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"meta": {
|
||||
"pagination": {
|
||||
"page": 1,
|
||||
"pages": 1,
|
||||
"count": 28
|
||||
},
|
||||
"version": "v1"
|
||||
}
|
||||
}
|
||||
+62
-15
@@ -100,9 +100,56 @@ 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: "Invitation";
|
||||
type: "invitations";
|
||||
id: string;
|
||||
attributes: {
|
||||
inserted_at: string;
|
||||
@@ -115,7 +162,7 @@ export interface InvitationProps {
|
||||
relationships: {
|
||||
inviter: {
|
||||
data: {
|
||||
type: "User";
|
||||
type: "users";
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
@@ -126,7 +173,7 @@ export interface InvitationProps {
|
||||
}
|
||||
|
||||
export interface UserProps {
|
||||
type: "User";
|
||||
type: "users";
|
||||
id: string;
|
||||
attributes: {
|
||||
name: string;
|
||||
@@ -140,7 +187,7 @@ export interface UserProps {
|
||||
count: number;
|
||||
};
|
||||
data: Array<{
|
||||
type: "Membership";
|
||||
type: "memberships";
|
||||
id: string;
|
||||
}>;
|
||||
};
|
||||
@@ -175,7 +222,7 @@ export interface ProviderProps {
|
||||
}
|
||||
|
||||
export interface ScanProps {
|
||||
type: "Scan";
|
||||
type: "scans";
|
||||
id: string;
|
||||
attributes: {
|
||||
name: string;
|
||||
@@ -203,20 +250,20 @@ export interface ScanProps {
|
||||
provider: {
|
||||
data: {
|
||||
id: string;
|
||||
type: "Provider";
|
||||
type: "providers";
|
||||
};
|
||||
};
|
||||
task: {
|
||||
data: {
|
||||
id: string;
|
||||
type: "Task";
|
||||
type: "tasks";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface FindingProps {
|
||||
type: "Finding";
|
||||
type: "findings";
|
||||
id: string;
|
||||
attributes: {
|
||||
uid: string;
|
||||
@@ -264,13 +311,13 @@ export interface FindingProps {
|
||||
relationships: {
|
||||
resources: {
|
||||
data: {
|
||||
type: "Resource";
|
||||
type: "resources";
|
||||
id: string;
|
||||
}[];
|
||||
};
|
||||
scan: {
|
||||
data: {
|
||||
type: "Scan";
|
||||
type: "scans";
|
||||
id: string;
|
||||
};
|
||||
attributes: {
|
||||
@@ -290,7 +337,7 @@ export interface FindingProps {
|
||||
};
|
||||
resource: {
|
||||
data: {
|
||||
type: "Resource";
|
||||
type: "resources";
|
||||
id: string;
|
||||
}[];
|
||||
id: string;
|
||||
@@ -307,7 +354,7 @@ export interface FindingProps {
|
||||
relationships: {
|
||||
provider: {
|
||||
data: {
|
||||
type: "Provider";
|
||||
type: "providers";
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
@@ -316,7 +363,7 @@ export interface FindingProps {
|
||||
count: number;
|
||||
};
|
||||
data: {
|
||||
type: "Finding";
|
||||
type: "findings";
|
||||
id: string;
|
||||
}[];
|
||||
};
|
||||
@@ -327,7 +374,7 @@ export interface FindingProps {
|
||||
};
|
||||
provider: {
|
||||
data: {
|
||||
type: "Provider";
|
||||
type: "providers";
|
||||
id: string;
|
||||
};
|
||||
attributes: {
|
||||
@@ -344,7 +391,7 @@ export interface FindingProps {
|
||||
relationships: {
|
||||
secret: {
|
||||
data: {
|
||||
type: "ProviderSecret";
|
||||
type: "provider-secrets";
|
||||
id: string;
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user