feat(ui): integrate threat map with regions API endpoint (#9324)

Co-authored-by: alejandrobailo <alejandrobailo94@gmail.com>
This commit is contained in:
Alan Buscaglia
2025-11-26 16:12:31 +01:00
committed by GitHub
parent c8d9f37e70
commit 28d5b2bb6c
26 changed files with 721 additions and 475 deletions

View File

@@ -1,3 +1,4 @@
export * from "./overview";
export * from "./overview.adapter";
export * from "./sankey.adapter";
export * from "./threat-map.adapter";
export * from "./types";

View File

@@ -7,6 +7,7 @@ import { handleApiResponse } from "@/lib/server-actions-helper";
import {
FindingsSeverityOverviewResponse,
ProvidersOverviewResponse,
RegionsOverviewResponse,
ServicesOverviewResponse,
} from "./types";
@@ -178,3 +179,31 @@ export const getThreatScore = async ({
return undefined;
}
};
export const getRegionsOverview = async ({
filters = {},
}: {
filters?: Record<string, string | string[] | undefined>;
} = {}): Promise<RegionsOverviewResponse | undefined> => {
const headers = await getAuthHeaders({ contentType: false });
const url = new URL(`${apiBaseUrl}/overviews/regions`);
// Handle multiple filters
Object.entries(filters).forEach(([key, value]) => {
if (key !== "filter[search]" && value !== undefined) {
url.searchParams.append(key, String(value));
}
});
try {
const response = await fetch(url.toString(), {
headers,
});
return handleApiResponse(response);
} catch (error) {
console.error("Error fetching regions overview:", error);
return undefined;
}
};

View File

@@ -1,52 +1,26 @@
import { getProviderDisplayName } from "@/types/providers";
import {
FindingsSeverityOverviewResponse,
ProviderOverview,
ProvidersOverviewResponse,
} from "./types";
/**
* Sankey chart node structure
*/
export interface SankeyNode {
name: string;
}
/**
* Sankey chart link structure
*/
export interface SankeyLink {
source: number;
target: number;
value: number;
}
/**
* Sankey chart data structure
*/
export interface SankeyData {
nodes: SankeyNode[];
links: SankeyLink[];
}
/**
* Provider display name mapping
* Maps provider IDs to user-friendly display names
* These names must match the COLOR_MAP keys in sankey-chart.tsx
*/
const PROVIDER_DISPLAY_NAMES: Record<string, string> = {
aws: "AWS",
azure: "Azure",
gcp: "Google Cloud",
kubernetes: "Kubernetes",
github: "GitHub",
m365: "Microsoft 365",
iac: "Infrastructure as Code",
oraclecloud: "Oracle Cloud Infrastructure",
};
/**
* Aggregated provider data after grouping by provider type
*/
interface AggregatedProvider {
id: string;
displayName: string;
@@ -54,19 +28,9 @@ interface AggregatedProvider {
fail: number;
}
/**
* Provider types to exclude from the Sankey chart
*/
const EXCLUDED_PROVIDERS = new Set(["mongo", "mongodb", "mongodbatlas"]);
/**
* Aggregates multiple provider entries by provider type (id)
* Since the API can return multiple entries for the same provider type,
* we need to sum up their findings
*
* @param providers - Raw provider overview data from API
* @returns Aggregated providers with summed findings
*/
// API can return multiple entries for the same provider type, so we sum their findings
function aggregateProvidersByType(
providers: ProviderOverview[],
): AggregatedProvider[] {
@@ -75,10 +39,7 @@ function aggregateProvidersByType(
for (const provider of providers) {
const { id, attributes } = provider;
// Skip excluded providers
if (EXCLUDED_PROVIDERS.has(id)) {
continue;
}
if (EXCLUDED_PROVIDERS.has(id)) continue;
const existing = aggregated.get(id);
@@ -88,7 +49,7 @@ function aggregateProvidersByType(
} else {
aggregated.set(id, {
id,
displayName: PROVIDER_DISPLAY_NAMES[id] || id,
displayName: getProviderDisplayName(id),
pass: attributes.findings.pass,
fail: attributes.findings.fail,
});
@@ -98,9 +59,6 @@ function aggregateProvidersByType(
return Array.from(aggregated.values());
}
/**
* Severity display names in order
*/
const SEVERITY_ORDER = [
"Critical",
"High",
@@ -110,18 +68,8 @@ const SEVERITY_ORDER = [
] as const;
/**
* Adapts providers overview and findings severity API responses to Sankey chart format
*
* Creates a 2-level flow visualization:
* - Level 1: Cloud providers (AWS, Azure, GCP, etc.)
* - Level 2: Severity breakdown (Critical, High, Medium, Low, Informational)
*
* The severity distribution is calculated proportionally based on each provider's
* fail count relative to the total fails across all providers.
*
* @param providersResponse - Raw API response from /overviews/providers
* @param severityResponse - Raw API response from /overviews/findings_severity
* @returns Sankey chart data with nodes and links
* Adapts providers overview and findings severity API responses to Sankey chart format.
* Severity distribution is calculated proportionally based on each provider's fail count.
*/
export function adaptProvidersOverviewToSankey(
providersResponse: ProvidersOverviewResponse | undefined,
@@ -131,34 +79,23 @@ export function adaptProvidersOverviewToSankey(
return { nodes: [], links: [] };
}
// Aggregate providers by type
const aggregatedProviders = aggregateProvidersByType(providersResponse.data);
// Filter out providers with no findings (only need fail > 0 for severity view)
const providersWithFailures = aggregatedProviders.filter((p) => p.fail > 0);
if (providersWithFailures.length === 0) {
return { nodes: [], links: [] };
}
// Build nodes array: providers first, then severities
const providerNodes: SankeyNode[] = providersWithFailures.map((p) => ({
name: p.displayName,
}));
const severityNodes: SankeyNode[] = SEVERITY_ORDER.map((severity) => ({
name: severity,
}));
const nodes = [...providerNodes, ...severityNodes];
// Calculate severity start index (after provider nodes)
const severityStartIndex = providerNodes.length;
// Build links from each provider to severities
const links: SankeyLink[] = [];
// If we have severity data, distribute proportionally
if (severityResponse?.data?.attributes) {
const { critical, high, medium, low, informational } =
severityResponse.data.attributes;
@@ -167,18 +104,15 @@ export function adaptProvidersOverviewToSankey(
const totalSeverity = severityValues.reduce((sum, v) => sum + v, 0);
if (totalSeverity > 0) {
// Calculate total fails across all providers
const totalFails = providersWithFailures.reduce(
(sum, p) => sum + p.fail,
0,
);
providersWithFailures.forEach((provider, sourceIndex) => {
// Calculate this provider's proportion of total fails
const providerRatio = provider.fail / totalFails;
severityValues.forEach((severityValue, severityIndex) => {
// Distribute severity proportionally to this provider
const value = Math.round(severityValue * providerRatio);
if (value > 0) {
@@ -192,7 +126,7 @@ export function adaptProvidersOverviewToSankey(
});
}
} else {
// Fallback: if no severity data, just show fail counts to a generic "Fail" node
// Fallback when no severity data available
const failNode: SankeyNode = { name: "Fail" };
nodes.push(failNode);
const failIndex = nodes.length - 1;

View File

@@ -0,0 +1,272 @@
import { getProviderDisplayName } from "@/types/providers";
import { RegionsOverviewResponse } from "./types";
export interface ThreatMapLocation {
id: string;
name: string;
region: string;
coordinates: [number, number];
totalFindings: number;
riskLevel: "low-high" | "high" | "critical";
severityData: Array<{
name: string;
value: number;
percentage?: number;
color?: string;
}>;
change?: number;
}
export interface ThreatMapData {
locations: ThreatMapLocation[];
regions: string[];
}
const AWS_REGION_COORDINATES: Record<string, { lat: number; lng: number }> = {
"us-east-1": { lat: 37.5, lng: -77.5 }, // N. Virginia
"us-east-2": { lat: 40.0, lng: -83.0 }, // Ohio
"us-west-1": { lat: 37.8, lng: -122.4 }, // N. California
"us-west-2": { lat: 45.5, lng: -122.7 }, // Oregon
"af-south-1": { lat: -33.9, lng: 18.4 }, // Cape Town
"ap-east-1": { lat: 22.3, lng: 114.2 }, // Hong Kong
"ap-south-1": { lat: 19.1, lng: 72.9 }, // Mumbai
"ap-south-2": { lat: 17.4, lng: 78.5 }, // Hyderabad
"ap-northeast-1": { lat: 35.7, lng: 139.7 }, // Tokyo
"ap-northeast-2": { lat: 37.6, lng: 127.0 }, // Seoul
"ap-northeast-3": { lat: 34.7, lng: 135.5 }, // Osaka
"ap-southeast-1": { lat: 1.4, lng: 103.8 }, // Singapore
"ap-southeast-2": { lat: -33.9, lng: 151.2 }, // Sydney
"ap-southeast-3": { lat: -6.2, lng: 106.8 }, // Jakarta
"ap-southeast-4": { lat: -37.8, lng: 144.96 }, // Melbourne
"ca-central-1": { lat: 45.5, lng: -73.6 }, // Montreal
"ca-west-1": { lat: 51.0, lng: -114.1 }, // Calgary
"eu-central-1": { lat: 50.1, lng: 8.7 }, // Frankfurt
"eu-central-2": { lat: 47.4, lng: 8.5 }, // Zurich
"eu-west-1": { lat: 53.3, lng: -6.3 }, // Ireland
"eu-west-2": { lat: 51.5, lng: -0.1 }, // London
"eu-west-3": { lat: 48.9, lng: 2.3 }, // Paris
"eu-north-1": { lat: 59.3, lng: 18.1 }, // Stockholm
"eu-south-1": { lat: 45.5, lng: 9.2 }, // Milan
"eu-south-2": { lat: 40.4, lng: -3.7 }, // Spain
"il-central-1": { lat: 32.1, lng: 34.8 }, // Tel Aviv
"me-central-1": { lat: 25.3, lng: 55.3 }, // UAE
"me-south-1": { lat: 26.1, lng: 50.6 }, // Bahrain
"sa-east-1": { lat: -23.5, lng: -46.6 }, // São Paulo
};
const AZURE_REGION_COORDINATES: Record<string, { lat: number; lng: number }> = {
eastus: { lat: 37.5, lng: -79.0 },
eastus2: { lat: 36.7, lng: -78.9 },
westus: { lat: 37.8, lng: -122.4 },
westus2: { lat: 47.6, lng: -122.3 },
westus3: { lat: 33.4, lng: -112.1 },
centralus: { lat: 41.6, lng: -93.6 },
northcentralus: { lat: 41.9, lng: -87.6 },
southcentralus: { lat: 29.4, lng: -98.5 },
westcentralus: { lat: 40.9, lng: -110.0 },
canadacentral: { lat: 43.7, lng: -79.4 },
canadaeast: { lat: 46.8, lng: -71.2 },
brazilsouth: { lat: -23.5, lng: -46.6 },
northeurope: { lat: 53.3, lng: -6.3 },
westeurope: { lat: 52.4, lng: 4.9 },
uksouth: { lat: 51.5, lng: -0.1 },
ukwest: { lat: 53.4, lng: -3.0 },
francecentral: { lat: 46.3, lng: 2.4 },
francesouth: { lat: 43.8, lng: 2.1 },
switzerlandnorth: { lat: 47.5, lng: 8.5 },
switzerlandwest: { lat: 46.2, lng: 6.1 },
germanywestcentral: { lat: 50.1, lng: 8.7 },
germanynorth: { lat: 53.1, lng: 8.8 },
norwayeast: { lat: 59.9, lng: 10.7 },
norwaywest: { lat: 58.97, lng: 5.73 },
swedencentral: { lat: 60.67, lng: 17.14 },
polandcentral: { lat: 52.23, lng: 21.01 },
italynorth: { lat: 45.5, lng: 9.2 },
spaincentral: { lat: 40.4, lng: -3.7 },
australiaeast: { lat: -33.9, lng: 151.2 },
australiasoutheast: { lat: -37.8, lng: 145.0 },
australiacentral: { lat: -35.3, lng: 149.1 },
eastasia: { lat: 22.3, lng: 114.2 },
southeastasia: { lat: 1.3, lng: 103.8 },
japaneast: { lat: 35.7, lng: 139.7 },
japanwest: { lat: 34.7, lng: 135.5 },
koreacentral: { lat: 37.6, lng: 127.0 },
koreasouth: { lat: 35.2, lng: 129.0 },
centralindia: { lat: 18.6, lng: 73.9 },
southindia: { lat: 12.9, lng: 80.2 },
westindia: { lat: 19.1, lng: 72.9 },
uaenorth: { lat: 25.3, lng: 55.3 },
uaecentral: { lat: 24.5, lng: 54.4 },
southafricanorth: { lat: -26.2, lng: 28.0 },
southafricawest: { lat: -34.0, lng: 18.5 },
israelcentral: { lat: 32.1, lng: 34.8 },
qatarcentral: { lat: 25.3, lng: 51.5 },
};
const GCP_REGION_COORDINATES: Record<string, { lat: number; lng: number }> = {
"us-central1": { lat: 41.3, lng: -95.9 }, // Iowa
"us-east1": { lat: 33.2, lng: -80.0 }, // South Carolina
"us-east4": { lat: 39.0, lng: -77.5 }, // Northern Virginia
"us-east5": { lat: 39.96, lng: -82.99 }, // Columbus
"us-south1": { lat: 32.8, lng: -96.8 }, // Dallas
"us-west1": { lat: 45.6, lng: -122.8 }, // Oregon
"us-west2": { lat: 34.1, lng: -118.2 }, // Los Angeles
"us-west3": { lat: 40.8, lng: -111.9 }, // Salt Lake City
"us-west4": { lat: 36.2, lng: -115.1 }, // Las Vegas
"northamerica-northeast1": { lat: 45.5, lng: -73.6 }, // Montreal
"northamerica-northeast2": { lat: 43.7, lng: -79.4 }, // Toronto
"southamerica-east1": { lat: -23.5, lng: -46.6 }, // São Paulo
"southamerica-west1": { lat: -33.4, lng: -70.6 }, // Santiago
"europe-north1": { lat: 60.6, lng: 27.0 }, // Finland
"europe-west1": { lat: 50.4, lng: 3.8 }, // Belgium
"europe-west2": { lat: 51.5, lng: -0.1 }, // London
"europe-west3": { lat: 50.1, lng: 8.7 }, // Frankfurt
"europe-west4": { lat: 53.4, lng: 6.8 }, // Netherlands
"europe-west6": { lat: 47.4, lng: 8.5 }, // Zurich
"europe-west8": { lat: 45.5, lng: 9.2 }, // Milan
"europe-west9": { lat: 48.9, lng: 2.3 }, // Paris
"europe-west10": { lat: 52.5, lng: 13.4 }, // Berlin
"europe-west12": { lat: 45.0, lng: 7.7 }, // Turin
"europe-central2": { lat: 52.2, lng: 21.0 }, // Warsaw
"europe-southwest1": { lat: 40.4, lng: -3.7 }, // Madrid
"asia-east1": { lat: 24.0, lng: 121.0 }, // Taiwan
"asia-east2": { lat: 22.3, lng: 114.2 }, // Hong Kong
"asia-northeast1": { lat: 35.7, lng: 139.7 }, // Tokyo
"asia-northeast2": { lat: 34.7, lng: 135.5 }, // Osaka
"asia-northeast3": { lat: 37.6, lng: 127.0 }, // Seoul
"asia-south1": { lat: 19.1, lng: 72.9 }, // Mumbai
"asia-south2": { lat: 28.6, lng: 77.2 }, // Delhi
"asia-southeast1": { lat: 1.4, lng: 103.8 }, // Singapore
"asia-southeast2": { lat: -6.2, lng: 106.8 }, // Jakarta
"australia-southeast1": { lat: -33.9, lng: 151.2 }, // Sydney
"australia-southeast2": { lat: -37.8, lng: 145.0 }, // Melbourne
"me-central1": { lat: 25.3, lng: 51.5 }, // Doha
"me-central2": { lat: 24.5, lng: 54.4 }, // Dammam
"me-west1": { lat: 32.1, lng: 34.8 }, // Tel Aviv
"africa-south1": { lat: -26.2, lng: 28.0 }, // Johannesburg
};
const PROVIDER_COORDINATES: Record<
string,
Record<string, { lat: number; lng: number }>
> = {
aws: AWS_REGION_COORDINATES,
azure: AZURE_REGION_COORDINATES,
gcp: GCP_REGION_COORDINATES,
};
// Returns [lng, lat] format for D3/GeoJSON compatibility
function getRegionCoordinates(
providerType: string,
region: string,
): [number, number] | null {
const coords =
PROVIDER_COORDINATES[providerType.toLowerCase()]?.[region.toLowerCase()];
return coords ? [coords.lng, coords.lat] : null;
}
function getRiskLevel(failRate: number): "low-high" | "high" | "critical" {
if (failRate >= 0.5) return "critical";
if (failRate >= 0.25) return "high";
return "low-high";
}
// CSS variables are used for Recharts inline styles, not className
function buildSeverityData(fail: number, pass: number, muted: number) {
const total = fail + pass + muted;
const pct = (value: number) =>
total > 0 ? Math.round((value / total) * 100) : 0;
return [
{
name: "Fail",
value: fail,
percentage: pct(fail),
color: "var(--color-bg-fail)",
},
{
name: "Pass",
value: pass,
percentage: pct(pass),
color: "var(--color-bg-pass)",
},
{
name: "Muted",
value: muted,
percentage: pct(muted),
color: "var(--color-bg-data-muted)",
},
];
}
// Formats "europe-west10" → "Europe West 10"
function formatRegionCode(region: string): string {
return region
.split(/[-_]/)
.map((part) => {
const match = part.match(/^([a-zA-Z]+)(\d+)$/);
if (match) {
const [, text, number] = match;
return `${text.charAt(0).toUpperCase()}${text.slice(1).toLowerCase()} ${number}`;
}
return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
})
.join(" ");
}
function formatRegionName(providerType: string, region: string): string {
return `${getProviderDisplayName(providerType)} - ${formatRegionCode(region)}`;
}
/**
* Adapts regions overview API response to threat map format.
*/
export function adaptRegionsOverviewToThreatMap(
regionsResponse: RegionsOverviewResponse | undefined,
): ThreatMapData {
if (!regionsResponse?.data || regionsResponse.data.length === 0) {
return {
locations: [],
regions: [],
};
}
const locations: ThreatMapLocation[] = [];
const regionSet = new Set<string>();
for (const regionData of regionsResponse.data) {
const { id, attributes } = regionData;
const coordinates = getRegionCoordinates(
attributes.provider_type,
attributes.region,
);
if (!coordinates) continue;
const providerRegion = getProviderDisplayName(attributes.provider_type);
regionSet.add(providerRegion);
const failRate =
attributes.total > 0 ? attributes.fail / attributes.total : 0;
locations.push({
id,
name: formatRegionName(attributes.provider_type, attributes.region),
region: providerRegion,
coordinates,
totalFindings: attributes.fail,
riskLevel: getRiskLevel(failRate),
severityData: buildSeverityData(
attributes.fail,
attributes.pass,
attributes.muted,
),
});
}
return {
locations,
regions: Array.from(regionSet).sort(),
};
}

View File

@@ -1,137 +0,0 @@
// Providers Overview Types
// Corresponds to the /overviews/providers endpoint
export interface ProviderOverviewFindings {
pass: number;
fail: number;
muted: number;
total: number;
}
export interface ProviderOverviewResources {
total: number;
}
export interface ProviderOverviewAttributes {
findings: ProviderOverviewFindings;
resources: ProviderOverviewResources;
}
export interface ProviderOverview {
type: "providers-overview";
id: string;
attributes: ProviderOverviewAttributes;
}
export interface ProvidersOverviewResponse {
data: ProviderOverview[];
meta: {
version: string;
};
}
// Services Overview Types
// Corresponds to the /overviews/services endpoint
export interface ServiceOverviewAttributes {
total: number;
fail: number;
muted: number;
pass: number;
}
export interface ServiceOverview {
type: "services-overview";
id: string;
attributes: ServiceOverviewAttributes;
}
export interface ServicesOverviewResponse {
data: ServiceOverview[];
meta: {
version: string;
};
}
// ThreatScore Snapshot Types
// Corresponds to the ThreatScoreSnapshot model from the API
export interface CriticalRequirement {
requirement_id: string;
risk_level: number;
weight: number;
title: string;
}
export type SectionScores = Record<string, number>;
export interface ThreatScoreSnapshotAttributes {
id: string;
inserted_at: string;
scan: string | null;
provider: string | null;
compliance_id: string;
overall_score: string;
score_delta: string | null;
section_scores: SectionScores;
critical_requirements: CriticalRequirement[];
total_requirements: number;
passed_requirements: number;
failed_requirements: number;
manual_requirements: number;
total_findings: number;
passed_findings: number;
failed_findings: number;
}
export interface ThreatScoreSnapshot {
id: string;
type: "threatscore-snapshots";
attributes: ThreatScoreSnapshotAttributes;
}
export interface ThreatScoreResponse {
data: ThreatScoreSnapshot[];
}
// Findings Severity Overview Types
// Corresponds to the /overviews/findings_severity endpoint
export interface FindingsSeverityAttributes {
critical: number;
high: number;
medium: number;
low: number;
informational: number;
}
export interface FindingsSeverityOverview {
type: "findings-severity-overview";
id: string;
attributes: FindingsSeverityAttributes;
}
export interface FindingsSeverityOverviewResponse {
data: FindingsSeverityOverview;
meta: {
version: string;
};
}
// Filters for ThreatScore endpoint
export interface ThreatScoreFilters {
snapshot_id?: string;
provider_id?: string;
provider_id__in?: string;
provider_type?: string;
provider_type__in?: string;
scan_id?: string;
scan_id__in?: string;
compliance_id?: string;
compliance_id__in?: string;
inserted_at?: string;
inserted_at__gte?: string;
inserted_at__lte?: string;
overall_score__gte?: string;
overall_score__lte?: string;
}

View File

@@ -0,0 +1,5 @@
// Common types shared across overview endpoints
export interface OverviewResponseMeta {
version: string;
}

View File

@@ -0,0 +1,23 @@
// Findings Severity Overview Types
// Corresponds to the /overviews/findings_severity endpoint
import { OverviewResponseMeta } from "./common";
export interface FindingsSeverityAttributes {
critical: number;
high: number;
medium: number;
low: number;
informational: number;
}
export interface FindingsSeverityOverview {
type: "findings-severity-overview";
id: string;
attributes: FindingsSeverityAttributes;
}
export interface FindingsSeverityOverviewResponse {
data: FindingsSeverityOverview;
meta: OverviewResponseMeta;
}

View File

@@ -0,0 +1,6 @@
export * from "./common";
export * from "./findings-severity";
export * from "./providers";
export * from "./regions";
export * from "./services";
export * from "./threat-score";

View File

@@ -0,0 +1,31 @@
// Providers Overview Types
// Corresponds to the /overviews/providers endpoint
import { OverviewResponseMeta } from "./common";
export interface ProviderOverviewFindings {
pass: number;
fail: number;
muted: number;
total: number;
}
export interface ProviderOverviewResources {
total: number;
}
export interface ProviderOverviewAttributes {
findings: ProviderOverviewFindings;
resources: ProviderOverviewResources;
}
export interface ProviderOverview {
type: "providers-overview";
id: string;
attributes: ProviderOverviewAttributes;
}
export interface ProvidersOverviewResponse {
data: ProviderOverview[];
meta: OverviewResponseMeta;
}

View File

@@ -0,0 +1,24 @@
// Regions Overview Types
// Corresponds to the /overviews/regions endpoint
import { OverviewResponseMeta } from "./common";
export interface RegionOverviewAttributes {
provider_type: string;
region: string;
total: number;
fail: number;
muted: number;
pass: number;
}
export interface RegionOverview {
type: "regions-overview";
id: string;
attributes: RegionOverviewAttributes;
}
export interface RegionsOverviewResponse {
data: RegionOverview[];
meta: OverviewResponseMeta;
}

View File

@@ -0,0 +1,22 @@
// Services Overview Types
// Corresponds to the /overviews/services endpoint
import { OverviewResponseMeta } from "./common";
export interface ServiceOverviewAttributes {
total: number;
fail: number;
muted: number;
pass: number;
}
export interface ServiceOverview {
type: "services-overview";
id: string;
attributes: ServiceOverviewAttributes;
}
export interface ServicesOverviewResponse {
data: ServiceOverview[];
meta: OverviewResponseMeta;
}

View File

@@ -0,0 +1,58 @@
// ThreatScore Snapshot Types
// Corresponds to the ThreatScoreSnapshot model from the API
export interface CriticalRequirement {
requirement_id: string;
risk_level: number;
weight: number;
title: string;
}
export type SectionScores = Record<string, number>;
export interface ThreatScoreSnapshotAttributes {
id: string;
inserted_at: string;
scan: string | null;
provider: string | null;
compliance_id: string;
overall_score: string;
score_delta: string | null;
section_scores: SectionScores;
critical_requirements: CriticalRequirement[];
total_requirements: number;
passed_requirements: number;
failed_requirements: number;
manual_requirements: number;
total_findings: number;
passed_findings: number;
failed_findings: number;
}
export interface ThreatScoreSnapshot {
id: string;
type: "threatscore-snapshots";
attributes: ThreatScoreSnapshotAttributes;
}
export interface ThreatScoreResponse {
data: ThreatScoreSnapshot[];
}
// Filters for ThreatScore endpoint
export interface ThreatScoreFilters {
snapshot_id?: string;
provider_id?: string;
provider_id__in?: string;
provider_type?: string;
provider_type__in?: string;
scan_id?: string;
scan_id__in?: string;
compliance_id?: string;
compliance_id__in?: string;
inserted_at?: string;
inserted_at__gte?: string;
inserted_at__lte?: string;
overall_score__gte?: string;
overall_score__lte?: string;
}