mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-02-09 15:10:36 +00:00
Compare commits
12 Commits
PRWLR-7784
...
PRWLR-7160
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cb9bb53e4 | ||
|
|
8a8586f298 | ||
|
|
e9a22c1513 | ||
|
|
5b22dcb7ac | ||
|
|
d8683630a9 | ||
|
|
d2d68ded2b | ||
|
|
9c19f9bc0c | ||
|
|
c17001bb4f | ||
|
|
1dc8636276 | ||
|
|
97d216c49f | ||
|
|
f87eaeac6b | ||
|
|
ff995e7750 |
@@ -23,6 +23,7 @@ All notable changes to the **Prowler UI** are documented in this file.
|
||||
### 🔄 Changed
|
||||
|
||||
- Add `Provider UID` filter to scans page. [(#7820)](https://github.com/prowler-cloud/prowler/pull/7820)
|
||||
- Improve `Scan ID` filter by adding more context and enhancing the UI/UX. [(#7827)](https://github.com/prowler-cloud/prowler/pull/7827/)
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ export const getScans = async ({
|
||||
sort = "",
|
||||
filters = {},
|
||||
pageSize = 10,
|
||||
include = "",
|
||||
}) => {
|
||||
const headers = await getAuthHeaders({ contentType: false });
|
||||
|
||||
@@ -27,6 +28,7 @@ export const getScans = async ({
|
||||
if (pageSize) url.searchParams.append("page[size]", pageSize.toString());
|
||||
if (query) url.searchParams.append("filter[search]", query);
|
||||
if (sort) url.searchParams.append("sort", sort);
|
||||
if (include) url.searchParams.append("include", include);
|
||||
|
||||
// Handle multiple filters
|
||||
Object.entries(filters).forEach(([key, value]) => {
|
||||
|
||||
@@ -3,7 +3,6 @@ import { Suspense } from "react";
|
||||
|
||||
import { getCompliancesOverview } from "@/actions/compliances";
|
||||
import { getComplianceOverviewMetadataInfo } from "@/actions/compliances";
|
||||
import { getProvider } from "@/actions/providers";
|
||||
import { getScans } from "@/actions/scans";
|
||||
import {
|
||||
ComplianceCard,
|
||||
@@ -12,6 +11,7 @@ import {
|
||||
} from "@/components/compliance";
|
||||
import { ComplianceHeader } from "@/components/compliance/compliance-header/compliance-header";
|
||||
import { ContentLayout } from "@/components/ui";
|
||||
import { getProviderDetailsByScan } from "@/lib/provider-helpers";
|
||||
import { ScanProps, SearchParamsProps } from "@/types";
|
||||
import { ComplianceOverviewData } from "@/types/compliance";
|
||||
|
||||
@@ -31,6 +31,7 @@ export default async function Compliance({
|
||||
"filter[state]": "completed",
|
||||
},
|
||||
pageSize: 50,
|
||||
include: "provider",
|
||||
});
|
||||
|
||||
if (!scansData?.data) {
|
||||
@@ -38,31 +39,10 @@ export default async function Compliance({
|
||||
}
|
||||
|
||||
// Expand scans with provider information
|
||||
const expandedScansData = await Promise.all(
|
||||
scansData.data.map(async (scan: ScanProps) => {
|
||||
const providerId = scan.relationships?.provider?.data?.id;
|
||||
|
||||
if (!providerId) {
|
||||
return { ...scan, providerInfo: null };
|
||||
}
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append("id", providerId);
|
||||
|
||||
const providerData = await getProvider(formData);
|
||||
|
||||
return {
|
||||
...scan,
|
||||
providerInfo: providerData?.data
|
||||
? {
|
||||
provider: providerData.data.attributes.provider,
|
||||
uid: providerData.data.attributes.uid,
|
||||
alias: providerData.data.attributes.alias,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
}),
|
||||
);
|
||||
const expandedScansData = scansData.data.map((scan: ScanProps) => ({
|
||||
id: scan.id,
|
||||
...getProviderDetailsByScan(scan, scansData.included, "flat"),
|
||||
}));
|
||||
|
||||
const selectedScanId =
|
||||
searchParams.scanId || expandedScansData[0]?.id || null;
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
import {
|
||||
createProviderDetailsMapping,
|
||||
extractProviderUIDs,
|
||||
getProviderDetailsByScan,
|
||||
} from "@/lib/provider-helpers";
|
||||
import { ScanProps } from "@/types";
|
||||
import { FindingProps, SearchParamsProps } from "@/types/components";
|
||||
@@ -48,7 +49,9 @@ export default async function Findings({
|
||||
filters,
|
||||
}),
|
||||
getProviders({ pageSize: 50 }),
|
||||
getScans({}),
|
||||
getScans({
|
||||
include: "provider",
|
||||
}),
|
||||
]);
|
||||
|
||||
// Extract unique regions and services from the new endpoint
|
||||
@@ -83,13 +86,17 @@ export default async function Findings({
|
||||
scan.attributes.unique_resource_count > 1,
|
||||
)
|
||||
.map((scan: ScanProps) => ({
|
||||
...scan,
|
||||
id: scan.id,
|
||||
name: scan.attributes.name,
|
||||
}));
|
||||
|
||||
const completedScanIds =
|
||||
completedScans?.map((scan: ScanProps) => scan.id) || [];
|
||||
|
||||
const providerDetailsAssociatedWithScans = scansData.data.map(
|
||||
(scan: ScanProps) => getProviderDetailsByScan(scan, scansData.included),
|
||||
);
|
||||
|
||||
return (
|
||||
<ContentLayout title="Findings" icon="carbon:data-view-alt">
|
||||
<FilterControls search date />
|
||||
@@ -119,6 +126,7 @@ export default async function Findings({
|
||||
key: "scan__in",
|
||||
labelCheckboxGroup: "Scan ID",
|
||||
values: completedScanIds,
|
||||
valueLabelMapping: providerDetailsAssociatedWithScans,
|
||||
index: 9,
|
||||
},
|
||||
]}
|
||||
|
||||
@@ -11,7 +11,7 @@ interface ComplianceScanInfoProps {
|
||||
alias?: string;
|
||||
uid?: string;
|
||||
};
|
||||
attributes: {
|
||||
attributes?: {
|
||||
name?: string;
|
||||
completed_at: string;
|
||||
};
|
||||
@@ -29,13 +29,17 @@ export const ComplianceScanInfo: React.FC<ComplianceScanInfoProps> = ({
|
||||
entityId={scan.providerInfo.uid}
|
||||
hideCopyButton
|
||||
/>
|
||||
<Divider orientation="vertical" className="mx-2 h-6" />
|
||||
<div className="flex flex-col items-start">
|
||||
<p className="text-xs text-default-500">
|
||||
{scan.attributes.name || "- -"}
|
||||
</p>
|
||||
<DateWithTime inline dateTime={scan.attributes.completed_at} />
|
||||
</div>
|
||||
{scan.attributes && (
|
||||
<>
|
||||
<Divider orientation="vertical" className="mx-2 h-6" />
|
||||
<div className="flex flex-col items-start">
|
||||
<p className="text-xs text-default-500">
|
||||
{scan.attributes.name || "- -"}
|
||||
</p>
|
||||
<DateWithTime inline dateTime={scan.attributes.completed_at} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -20,7 +20,7 @@ import React, {
|
||||
useState,
|
||||
} from "react";
|
||||
|
||||
import { EntityInfoShort } from "@/components/ui/entities";
|
||||
import { ComplianceScanInfo } from "@/components/compliance";
|
||||
import { CustomDropdownFilterProps } from "@/types";
|
||||
|
||||
export const CustomDropdownFilter = ({
|
||||
@@ -180,7 +180,7 @@ export const CustomDropdownFilter = ({
|
||||
const entity = filter.valueLabelMapping?.find((entry) => entry[value])?.[
|
||||
value
|
||||
];
|
||||
return entity?.alias || entity?.uid || value;
|
||||
return entity?.providerInfo?.alias || entity?.providerInfo?.uid || value;
|
||||
},
|
||||
[filter.valueLabelMapping],
|
||||
);
|
||||
@@ -251,7 +251,7 @@ export const CustomDropdownFilter = ({
|
||||
</div>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80 dark:bg-prowler-blue-800">
|
||||
<PopoverContent className="min-w-[20rem] dark:bg-prowler-blue-800">
|
||||
<div className="flex w-full flex-col gap-4 p-2">
|
||||
<CheckboxGroup
|
||||
color="default"
|
||||
@@ -281,7 +281,8 @@ export const CustomDropdownFilter = ({
|
||||
{filterValues.map((value) => {
|
||||
const entity = filter.valueLabelMapping?.find(
|
||||
(entry) => entry[value],
|
||||
)?.[value];
|
||||
);
|
||||
const scanData = entity?.[value];
|
||||
|
||||
return (
|
||||
<Checkbox
|
||||
@@ -292,13 +293,8 @@ export const CustomDropdownFilter = ({
|
||||
key={value}
|
||||
value={value}
|
||||
>
|
||||
{entity ? (
|
||||
<EntityInfoShort
|
||||
cloudProvider={entity.provider}
|
||||
entityAlias={entity.alias ?? undefined}
|
||||
entityId={entity.uid}
|
||||
hideCopyButton
|
||||
/>
|
||||
{scanData ? (
|
||||
<ComplianceScanInfo scan={scanData} />
|
||||
) : (
|
||||
value
|
||||
)}
|
||||
|
||||
@@ -20,7 +20,7 @@ export const EntityInfoShort: React.FC<EntityInfoProps> = ({
|
||||
hideCopyButton = false,
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex w-full items-center justify-between space-x-2">
|
||||
<div className="flex w-fit items-center justify-between">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<div className="flex-shrink-0">{getProviderLogo(cloudProvider)}</div>
|
||||
<div className="flex flex-col">
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { ScanProps } from "@/types";
|
||||
import {
|
||||
ProviderAccountProps,
|
||||
ProviderProps,
|
||||
ProvidersApiResponse,
|
||||
ProviderType,
|
||||
} from "@/types/providers";
|
||||
|
||||
export const extractProviderUIDs = (
|
||||
@@ -21,7 +22,15 @@ export const extractProviderUIDs = (
|
||||
export const createProviderDetailsMapping = (
|
||||
providerUIDs: string[],
|
||||
providersData: ProvidersApiResponse,
|
||||
): Array<{ [uid: string]: ProviderAccountProps }> => {
|
||||
): Array<{
|
||||
[uid: string]: {
|
||||
providerInfo: {
|
||||
provider: ProviderType;
|
||||
alias?: string;
|
||||
uid?: string;
|
||||
};
|
||||
};
|
||||
}> => {
|
||||
if (!providersData?.data) return [];
|
||||
|
||||
return providerUIDs.map((uid) => {
|
||||
@@ -31,10 +40,84 @@ export const createProviderDetailsMapping = (
|
||||
|
||||
return {
|
||||
[uid]: {
|
||||
provider: provider?.attributes?.provider || "aws",
|
||||
uid: uid,
|
||||
alias: provider?.attributes?.alias ?? null,
|
||||
providerInfo: {
|
||||
provider: provider?.attributes?.provider || "aws",
|
||||
uid: uid,
|
||||
alias: provider?.attributes?.alias,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Extracts and formats provider details associated with a given scan.
|
||||
*
|
||||
* @param scan - The scan object containing scan attributes and provider relationship data.
|
||||
* @param included - An array of included related resources (e.g., providers).
|
||||
* @param format - Optional. Specifies the structure of the returned object.
|
||||
* - "keyed" (default): Returns an object keyed by scan ID with providerInfo and attributes.
|
||||
* - "flat": Returns a flat object containing providerInfo and attributes.
|
||||
* - "merged": Returns the full scan object with providerInfo merged in.
|
||||
*
|
||||
* @returns An object containing provider information and scan attributes in the specified format.
|
||||
*
|
||||
* @example
|
||||
* const result = getProviderDetailsByScan(scan, included, "flat");
|
||||
* // {
|
||||
* // providerInfo: { provider: 'Example Provider', alias: 'EP', uid: '123' },
|
||||
* // attributes: { name: 'Scan 1', started_at: '2024-01-01', completed_at: '2024-01-02' }
|
||||
* // }
|
||||
*/
|
||||
export const getProviderDetailsByScan = (
|
||||
scan: ScanProps,
|
||||
included: ProviderProps[],
|
||||
format: "keyed" | "flat" | "merged" = "keyed",
|
||||
) => {
|
||||
const providerId = scan.relationships?.provider?.data?.id;
|
||||
|
||||
const providerDetails = providerId
|
||||
? included.find(
|
||||
(provider) =>
|
||||
provider.type === "providers" && provider.id === providerId,
|
||||
)
|
||||
: null;
|
||||
|
||||
const providerInfo = {
|
||||
provider: providerDetails?.attributes?.provider,
|
||||
alias: providerDetails?.attributes?.alias ?? null,
|
||||
uid: providerDetails?.attributes?.uid ?? null,
|
||||
};
|
||||
|
||||
const attributes = {
|
||||
name: scan.attributes?.name,
|
||||
started_at: scan.attributes?.started_at,
|
||||
completed_at: scan.attributes?.completed_at,
|
||||
};
|
||||
|
||||
// --- FORMAT 1: keyed (default)
|
||||
if (format === "keyed") {
|
||||
return {
|
||||
[scan.id]: {
|
||||
providerInfo,
|
||||
attributes,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// --- FORMAT 2: flat object
|
||||
if (format === "flat") {
|
||||
return {
|
||||
providerInfo,
|
||||
attributes,
|
||||
};
|
||||
}
|
||||
|
||||
// --- FORMAT 3: merged into full scan object
|
||||
if (format === "merged") {
|
||||
return {
|
||||
...scan,
|
||||
providerInfo,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,10 +1,22 @@
|
||||
import { ProviderAccountProps } from "./providers";
|
||||
import { ProviderType } from "./providers";
|
||||
|
||||
export interface FilterOption {
|
||||
key: string;
|
||||
labelCheckboxGroup: string;
|
||||
values: string[];
|
||||
valueLabelMapping?: Array<{ [uid: string]: ProviderAccountProps }>;
|
||||
valueLabelMapping?: Array<{
|
||||
[uid: string]: {
|
||||
providerInfo: {
|
||||
provider: ProviderType;
|
||||
alias?: string;
|
||||
uid?: string;
|
||||
};
|
||||
attributes?: {
|
||||
name?: string;
|
||||
completed_at: string;
|
||||
};
|
||||
};
|
||||
}>;
|
||||
index?: number;
|
||||
showSelectAll?: boolean;
|
||||
defaultToSelectAll?: boolean;
|
||||
|
||||
Reference in New Issue
Block a user