feat(attack-paths): merge master for updating boto3 and botocore

This commit is contained in:
Josema Camacho committed 2026-01-15 13:07:03 +01:00
commit bc46003e2e
164 files changed
+5665 -1344

No files matched your search

@@ -14,11 +14,16 @@ export interface ComplianceData {
score: number;
}
// Display 7 items to match the card's min-height (405px) without scrolling
const ITEMS_TO_DISPLAY = 7;
export const ComplianceWatchlist = ({ items }: { items: ComplianceData[] }) => {
const [isAsc, setIsAsc] = useState(true);
// Sort all items and take top 7 based on current sort order
const sortedItems = [...items]
.sort((a, b) => (isAsc ? a.score - b.score : b.score - a.score))
.slice(0, ITEMS_TO_DISPLAY)
.map((item) => ({
key: item.id,
icon: item.icon ? (
@@ -41,7 +46,7 @@ export const ComplianceWatchlist = ({ items }: { items: ComplianceData[] }) => {
<WatchlistCard
title="Compliance Watchlist"
items={sortedItems}
ctaLabel="Compliance Dashboard"
ctaLabel="Explore Compliance for Each Scan"
ctaHref="/compliance"
headerAction={
<SortToggleButton
@@ -51,10 +56,10 @@ export const ComplianceWatchlist = ({ items }: { items: ComplianceData[] }) => {
descendingLabel="Sort by lowest score"
/>
}
// TODO: Enable full emptyState with description once API endpoint is implemented
// Full emptyState: { message: "...", description: "to add compliance frameworks to your watchlist.", linkText: "Compliance Dashboard" }
emptyState={{
message: "This space is looking empty.",
description: "to add compliance frameworks to your watchlist.",
linkText: "Compliance Dashboard",
message: "No compliance data available.",
}}
/>
);
@@ -85,12 +85,15 @@ export const WatchlistCard = ({
const isEmpty = items.length === 0;
return (
<Card variant="base" className="flex min-h-[405px] min-w-[312px] flex-col">
<Card
variant="base"
className="flex min-h-[405px] w-full flex-col overflow-hidden"
>
<div className="flex items-center justify-between">
<CardTitle>{title}</CardTitle>
{headerAction}
</div>
<CardContent className="flex flex-col">
<CardContent className="flex min-w-0 flex-1 flex-col overflow-hidden">
{isEmpty ? (
<div className="flex flex-1 flex-col items-center justify-center gap-12 py-6">
{/* Icon and message */}
@@ -102,19 +105,15 @@ export const WatchlistCard = ({
</div>
{/* Description with link */}
<p className="text-text-neutral-tertiary w-full text-sm leading-6">
{emptyState?.description && ctaHref && (
<>
Visit the{" "}
<Button variant="link" size="link-sm" asChild>
<Link href={ctaHref}>
{emptyState.linkText || ctaLabel}
</Link>
</Button>{" "}
{emptyState.description}
</>
)}
</p>
{emptyState?.description && ctaHref && (
<p className="text-text-neutral-tertiary w-full text-sm leading-6">
Visit the{" "}
<Button variant="link" size="link-sm" asChild>
<Link href={ctaHref}>{emptyState.linkText || ctaLabel}</Link>
</Button>{" "}
{emptyState.description}
</p>
)}
</div>
) : (
<>
@@ -149,7 +148,7 @@ export const WatchlistCard = ({
}
}}
className={cn(
"flex h-[54px] items-center justify-between gap-2 px-3 py-[11px]",
"flex h-[54px] min-w-0 items-center justify-between gap-2 px-3 py-[11px]",
!isLast && "border-border-neutral-tertiary border-b",
isClickable &&
"hover:bg-bg-neutral-tertiary cursor-pointer",
@@ -161,10 +160,10 @@ export const WatchlistCard = ({
</div>
)}
<p className="text-text-neutral-secondary flex-1 truncate text-sm leading-6">
<p className="text-text-neutral-secondary w-0 flex-1 truncate text-sm leading-6">
{item.label}
</p>
<div className="flex items-center gap-1.5">
<div className="flex shrink-0 items-center gap-1.5">
<p
className={cn(
"text-sm leading-6 font-bold",
@@ -1,7 +1,7 @@
import {
adaptComplianceOverviewsResponse,
getCompliancesOverview,
} from "@/actions/compliances";
adaptComplianceWatchlistResponse,
getComplianceWatchlist,
} from "@/actions/overview/compliance-watchlist";
import { pickFilterParams } from "../_lib/filter-params";
import { SSRComponentProps } from "../_types";
@@ -11,20 +11,19 @@ export const ComplianceWatchlistSSR = async ({
searchParams,
}: SSRComponentProps) => {
const filters = pickFilterParams(searchParams);
const response = await getComplianceWatchlist({ filters });
const enrichedData = adaptComplianceWatchlistResponse(response);
const response = await getCompliancesOverview({ filters });
const { data } = adaptComplianceOverviewsResponse(response);
// Filter out ProwlerThreatScore and limit to 5 items
const items = data
.filter((item) => item.framework !== "ProwlerThreatScore")
.slice(0, 5)
.map((compliance) => ({
id: compliance.id,
framework: compliance.framework,
label: compliance.label,
icon: compliance.icon,
score: compliance.score,
// Filter out ProwlerThreatScore and pass all items to client
// Client handles sorting and limiting to display count
const items = enrichedData
.filter((item) => !item.complianceId.toLowerCase().includes("threatscore"))
.map((item) => ({
id: item.id,
framework: item.complianceId,
label: item.label,
icon: item.icon,
score: item.score,
}));
return <ComplianceWatchlist items={items} />;
+14 -14
View File
@@ -1,4 +1,3 @@
import { Spacer } from "@heroui/spacer";
import { Suspense } from "react";
import {
@@ -150,19 +149,20 @@ export default async function Findings({
return (
<ContentLayout title="Findings" icon="lucide:tag">
<FindingsFilters
providers={providersData?.data || []}
providerIds={providerIds}
providerDetails={providerDetails}
completedScans={completedScans || []}
completedScanIds={completedScanIds}
scanDetails={scanDetails}
uniqueRegions={uniqueRegions}
uniqueServices={uniqueServices}
uniqueResourceTypes={uniqueResourceTypes}
uniqueCategories={uniqueCategories}
/>
<Spacer y={8} />
<div className="mb-6">
<FindingsFilters
providers={providersData?.data || []}
providerIds={providerIds}
providerDetails={providerDetails}
completedScans={completedScans || []}
completedScanIds={completedScanIds}
scanDetails={scanDetails}
uniqueRegions={uniqueRegions}
uniqueServices={uniqueServices}
uniqueResourceTypes={uniqueResourceTypes}
uniqueCategories={uniqueCategories}
/>
</div>
<Suspense key={searchParamsKey} fallback={<SkeletonTableFindings />}>
<SSRDataTable searchParams={resolvedSearchParams} />
</Suspense>
@@ -91,11 +91,7 @@ export const createMuteRulesColumns = (
},
{
id: "actions",
header: () => (
<div className="flex items-center justify-center px-2">
<span className="text-sm font-semibold">Actions</span>
</div>
),
header: () => null,
cell: ({ row }) => {
return (
<MuteRuleRowActions
+24 -12
View File
@@ -24,6 +24,7 @@ import {
import { StatusChartSkeleton } from "./_overview/status-chart";
import { ThreatScoreSkeleton, ThreatScoreSSR } from "./_overview/threat-score";
import {
ComplianceWatchlistSSR,
ServiceWatchlistSSR,
WatchlistCardSkeleton,
} from "./_overview/watchlist";
@@ -57,19 +58,30 @@ export default async function Home({
</Suspense>
</div>
<div className="mt-6">
<Suspense fallback={<AttackSurfaceSkeleton />}>
<AttackSurfaceSSR searchParams={resolvedSearchParams} />
</Suspense>
</div>
<div className="mt-6 flex flex-col gap-6 xl:flex-row">
<Suspense fallback={<WatchlistCardSkeleton />}>
<ServiceWatchlistSSR searchParams={resolvedSearchParams} />
</Suspense>
<Suspense fallback={<FindingSeverityOverTimeSkeleton />}>
<FindingSeverityOverTimeSSR searchParams={resolvedSearchParams} />
</Suspense>
{/* Watchlists: stacked on mobile, row on tablet, stacked on desktop */}
<div className="flex min-w-0 flex-col gap-6 overflow-hidden sm:flex-row sm:flex-wrap sm:items-stretch xl:w-[312px] xl:shrink-0 xl:flex-col">
<div className="min-w-0 sm:flex-1 xl:flex-auto [&>*]:h-full">
<Suspense fallback={<WatchlistCardSkeleton />}>
<ComplianceWatchlistSSR searchParams={resolvedSearchParams} />
</Suspense>
</div>
<div className="min-w-0 sm:flex-1 xl:flex-auto [&>*]:h-full">
<Suspense fallback={<WatchlistCardSkeleton />}>
<ServiceWatchlistSSR searchParams={resolvedSearchParams} />
</Suspense>
</div>
</div>
{/* Charts column: Attack Surface on top, Findings Over Time below */}
<div className="flex flex-1 flex-col gap-6">
<Suspense fallback={<AttackSurfaceSkeleton />}>
<AttackSurfaceSSR searchParams={resolvedSearchParams} />
</Suspense>
<Suspense fallback={<FindingSeverityOverTimeSkeleton />}>
<FindingSeverityOverTimeSSR searchParams={resolvedSearchParams} />
</Suspense>
</div>
</div>
<div className="mt-6">
@@ -1,5 +1,7 @@
import { redirect } from "next/navigation";
import React from "react";
import { getProvider } from "@/actions/providers/providers";
import { CredentialsUpdateInfo } from "@/components/providers";
import {
UpdateViaCredentialsForm,
@@ -20,9 +22,24 @@ interface Props {
export default async function UpdateCredentialsPage({ searchParams }: Props) {
const resolvedSearchParams = await searchParams;
const { type: providerType, via } = resolvedSearchParams;
const { type: providerType, via, id: providerId } = resolvedSearchParams;
if (!providerId) {
redirect("/providers");
}
const formType = getProviderFormType(providerType, via);
const formData = new FormData();
formData.append("id", providerId);
const providerResponse = await getProvider(formData);
if (providerResponse?.errors) {
redirect("/providers");
}
const providerUid = providerResponse?.data?.attributes?.uid;
switch (formType) {
case "selector":
return (
@@ -30,14 +47,27 @@ export default async function UpdateCredentialsPage({ searchParams }: Props) {
);
case "credentials":
return <UpdateViaCredentialsForm searchParams={resolvedSearchParams} />;
return (
<UpdateViaCredentialsForm
searchParams={resolvedSearchParams}
providerUid={providerUid}
/>
);
case "role":
return <UpdateViaRoleForm searchParams={resolvedSearchParams} />;
return (
<UpdateViaRoleForm
searchParams={resolvedSearchParams}
providerUid={providerUid}
/>
);
case "service-account":
return (
<UpdateViaServiceAccountForm searchParams={resolvedSearchParams} />
<UpdateViaServiceAccountForm
searchParams={resolvedSearchParams}
providerUid={providerUid}
/>
);
default: