mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-07-23 12:31:54 +00:00
fix(ui): Improved the findings page scan Id filter
This commit is contained in:
@@ -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]) => {
|
||||
|
||||
@@ -23,8 +23,13 @@ import {
|
||||
extractSortAndKey,
|
||||
hasDateOrScanFilter,
|
||||
} from "@/lib";
|
||||
import { ProviderAccountProps, ProviderProps } from "@/types";
|
||||
import { FindingProps, ScanProps, SearchParamsProps } from "@/types/components";
|
||||
import { ProviderProps } from "@/types";
|
||||
import {
|
||||
FindingProps,
|
||||
IncludeProps,
|
||||
ScanProps,
|
||||
SearchParamsProps,
|
||||
} from "@/types/components";
|
||||
|
||||
export default async function Findings({
|
||||
searchParams,
|
||||
@@ -44,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
|
||||
@@ -62,20 +69,21 @@ export default async function Findings({
|
||||
),
|
||||
);
|
||||
|
||||
const providerDetails: Array<{ [uid: string]: ProviderAccountProps }> =
|
||||
providerUIDs.map((uid) => {
|
||||
const provider = providersData.data.find(
|
||||
(p: { attributes: { uid: string } }) => p.attributes?.uid === uid,
|
||||
);
|
||||
const providerDetails = providerUIDs.map((uid) => {
|
||||
const provider = providersData.data.find(
|
||||
(p: { attributes: { uid: string } }) => p.attributes?.uid === uid,
|
||||
);
|
||||
|
||||
return {
|
||||
[uid]: {
|
||||
return {
|
||||
[uid]: {
|
||||
providerInfo: {
|
||||
provider: provider?.attributes?.provider || "",
|
||||
uid: uid,
|
||||
alias: provider?.attributes?.alias ?? null,
|
||||
},
|
||||
};
|
||||
});
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
// Extract scan UUIDs with "completed" state and more than one resource
|
||||
const completedScans = scansData?.data
|
||||
@@ -87,11 +95,43 @@ export default async function Findings({
|
||||
.map((scan: ScanProps) => ({
|
||||
id: scan.id,
|
||||
name: scan.attributes.name,
|
||||
providerId: scan.relationships.provider.data.id,
|
||||
completed_at: scan.attributes.completed_at,
|
||||
}));
|
||||
|
||||
const completedScanIds =
|
||||
completedScans?.map((scan: ScanProps) => scan.id) || [];
|
||||
|
||||
const providerDetailsAssociatedWithScans = completedScans?.map(
|
||||
(scan: {
|
||||
id: string;
|
||||
name: string;
|
||||
providerId: string;
|
||||
completed_at: string;
|
||||
}) => {
|
||||
const providerId = scan.providerId;
|
||||
|
||||
const providerDetails = scansData.included.find(
|
||||
(provider: IncludeProps) =>
|
||||
provider.type === "providers" && provider.id === providerId,
|
||||
);
|
||||
|
||||
return {
|
||||
[scan.id]: {
|
||||
providerInfo: {
|
||||
provider: providerDetails?.attributes?.provider,
|
||||
alias: providerDetails?.attributes?.alias,
|
||||
uid: providerDetails?.attributes?.uid,
|
||||
},
|
||||
attributes: {
|
||||
name: scan.name,
|
||||
completed_at: scan.completed_at,
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return (
|
||||
<ContentLayout title="Findings" icon="carbon:data-view-alt">
|
||||
<FilterControls search date />
|
||||
@@ -124,6 +164,7 @@ export default async function Findings({
|
||||
key: "scan__in",
|
||||
labelCheckboxGroup: "Scan ID",
|
||||
values: completedScanIds,
|
||||
valueLabelMapping: providerDetailsAssociatedWithScans,
|
||||
},
|
||||
]}
|
||||
defaultOpen={true}
|
||||
|
||||
@@ -10,7 +10,7 @@ interface ComplianceScanInfoProps {
|
||||
alias?: string;
|
||||
uid?: string;
|
||||
};
|
||||
attributes: {
|
||||
attributes?: {
|
||||
name?: string;
|
||||
completed_at: string;
|
||||
};
|
||||
@@ -28,13 +28,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>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,12 +14,11 @@ import { XCircle } from "lucide-react";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import React, { useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import { ComplianceScanInfo } from "@/components/compliance";
|
||||
import { PlusCircleIcon } from "@/components/icons";
|
||||
import { useUrlFilters } from "@/hooks/use-url-filters";
|
||||
import { CustomDropdownFilterProps } from "@/types";
|
||||
|
||||
import { EntityInfoShort } from "../entities";
|
||||
|
||||
const filterSelectedClass =
|
||||
"inline-flex items-center border py-1 text-xs transition-colors border-transparent bg-default-500 text-secondary-foreground hover:bg-default-500/80 rounded-md px-2 font-normal";
|
||||
|
||||
@@ -169,7 +168,7 @@ export const CustomDropdownFilter: React.FC<CustomDropdownFilterProps> = ({
|
||||
)}
|
||||
</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-6 p-2">
|
||||
<CheckboxGroup
|
||||
color="default"
|
||||
@@ -192,14 +191,14 @@ export const CustomDropdownFilter: React.FC<CustomDropdownFilterProps> = ({
|
||||
<Divider orientation="horizontal" className="mt-2" />
|
||||
<ScrollShadow
|
||||
hideScrollBar
|
||||
className="flex max-h-96 max-w-56 flex-col gap-y-2 py-2"
|
||||
className="flex max-h-96 max-w-full flex-col gap-y-2 py-2"
|
||||
>
|
||||
{memoizedFilterValues.map((value) => {
|
||||
// Find the corresponding entity from valueLabelMapping
|
||||
const matchingEntry = filter.valueLabelMapping?.find(
|
||||
(entry) => entry[value],
|
||||
);
|
||||
const entity = matchingEntry?.[value];
|
||||
const scanData = matchingEntry?.[value];
|
||||
|
||||
return (
|
||||
<Checkbox
|
||||
@@ -210,13 +209,8 @@ export const CustomDropdownFilter: React.FC<CustomDropdownFilterProps> = ({
|
||||
key={value}
|
||||
value={value}
|
||||
>
|
||||
{entity ? (
|
||||
<EntityInfoShort
|
||||
cloudProvider={entity.provider}
|
||||
entityAlias={entity.alias}
|
||||
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">
|
||||
|
||||
+13
-1
@@ -1,7 +1,7 @@
|
||||
import { LucideIcon } from "lucide-react";
|
||||
import { SVGProps } from "react";
|
||||
|
||||
import { ProviderType } from "./providers";
|
||||
import { ProviderAccountProps, ProviderType } from "./providers";
|
||||
|
||||
export type IconSvgProps = SVGProps<SVGSVGElement> & {
|
||||
size?: number;
|
||||
@@ -704,3 +704,15 @@ export interface UserProps {
|
||||
dateAdded: string;
|
||||
status: "active" | "inactive";
|
||||
}
|
||||
|
||||
export interface Connection {
|
||||
connected: string;
|
||||
last_checked_at: string;
|
||||
}
|
||||
|
||||
export interface IncludeProps {
|
||||
type: string;
|
||||
id: string;
|
||||
attributes: ProviderAccountProps;
|
||||
connection: Connection;
|
||||
}
|
||||
|
||||
+14
-2
@@ -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;
|
||||
};
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface CustomDropdownFilterProps {
|
||||
|
||||
Reference in New Issue
Block a user