mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-10-31 07:44:21 +00:00
feat(attack-paths): merge master for updating boto3 and botocore
This commit is contained in:
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} />;
|
||||
|
||||
@@ -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
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user