mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-04-10 11:51:22 +00:00
feat(ui): add resourceType field and update resource EntityInfo in drawer
This commit is contained in:
@@ -138,6 +138,7 @@ export function adaptFindingGroupResourcesResponse(
|
||||
providerAlias: item.attributes.provider?.alias || "",
|
||||
providerUid: item.attributes.provider?.uid || "",
|
||||
resourceName: item.attributes.resource?.name || "-",
|
||||
resourceType: item.attributes.resource?.type || "-",
|
||||
resourceGroup: item.attributes.resource?.resource_group || "-",
|
||||
resourceUid: item.attributes.resource?.uid || "-",
|
||||
service: item.attributes.resource?.service || "-",
|
||||
@@ -146,7 +147,6 @@ export function adaptFindingGroupResourcesResponse(
|
||||
status: item.attributes.status,
|
||||
delta: item.attributes.delta || null,
|
||||
isMuted: item.attributes.status === "MUTED",
|
||||
// TODO: remove fallback once the API returns muted_reason in finding-group-resources
|
||||
mutedReason: item.attributes.muted_reason || undefined,
|
||||
firstSeenAt: item.attributes.first_seen_at,
|
||||
lastSeenAt: item.attributes.last_seen_at,
|
||||
|
||||
@@ -204,7 +204,7 @@ export function getColumnFindingResources({
|
||||
<div className="max-w-[240px]">
|
||||
<EntityInfo
|
||||
nameIcon={<Container className="size-4" />}
|
||||
entityAlias={row.original.resourceGroup}
|
||||
entityAlias={row.original.resourceName}
|
||||
entityId={row.original.resourceUid}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
} from "lucide-react";
|
||||
import Image from "next/image";
|
||||
import Link from "next/link";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { useState } from "react";
|
||||
|
||||
import { getCompliancesOverview } from "@/actions/compliances";
|
||||
@@ -84,7 +84,87 @@ function normalizeComplianceFrameworkName(framework: string): string {
|
||||
return framework
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/[\s_]+/g, "-");
|
||||
.replace(/[\s_]+/g, "-")
|
||||
.replace(/-+/g, "-");
|
||||
}
|
||||
|
||||
function stripComplianceVersionSuffix(framework: string): string {
|
||||
return framework.replace(/-\d+(?:\.\d+)*$/g, "");
|
||||
}
|
||||
|
||||
function canonicalComplianceKey(framework: string): string {
|
||||
return stripComplianceVersionSuffix(
|
||||
normalizeComplianceFrameworkName(framework),
|
||||
)
|
||||
.replace(/[^a-z0-9]+/g, "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
function complianceTokens(framework: string): string[] {
|
||||
return stripComplianceVersionSuffix(
|
||||
normalizeComplianceFrameworkName(framework),
|
||||
)
|
||||
.split("-")
|
||||
.map((token) => token.trim())
|
||||
.filter(Boolean)
|
||||
.filter((token) => !/^\d+(?:\.\d+)*$/.test(token));
|
||||
}
|
||||
|
||||
function complianceMatchScore(
|
||||
sourceFramework: string,
|
||||
targetFramework: string,
|
||||
): number {
|
||||
const normalizedSource = normalizeComplianceFrameworkName(sourceFramework);
|
||||
const normalizedTarget = normalizeComplianceFrameworkName(targetFramework);
|
||||
|
||||
if (normalizedSource === normalizedTarget) {
|
||||
return 5;
|
||||
}
|
||||
|
||||
const canonicalSource = canonicalComplianceKey(sourceFramework);
|
||||
const canonicalTarget = canonicalComplianceKey(targetFramework);
|
||||
|
||||
if (canonicalSource === canonicalTarget) {
|
||||
return 4;
|
||||
}
|
||||
|
||||
if (
|
||||
canonicalSource &&
|
||||
canonicalTarget &&
|
||||
(canonicalTarget.startsWith(canonicalSource) ||
|
||||
canonicalSource.startsWith(canonicalTarget))
|
||||
) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
const sourceTokens = complianceTokens(sourceFramework);
|
||||
const targetTokens = complianceTokens(targetFramework);
|
||||
if (!sourceTokens.length || !targetTokens.length) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const sourceMatchesTarget = sourceTokens.every((token) =>
|
||||
targetTokens.includes(token),
|
||||
);
|
||||
const targetMatchesSource = targetTokens.every((token) =>
|
||||
sourceTokens.includes(token),
|
||||
);
|
||||
|
||||
if (sourceMatchesTarget || targetMatchesSource) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (
|
||||
sourceTokens.some((token) => targetTokens.includes(token)) &&
|
||||
canonicalSource &&
|
||||
canonicalTarget &&
|
||||
(canonicalTarget.includes(canonicalSource) ||
|
||||
canonicalSource.includes(canonicalTarget))
|
||||
) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function parseSelectedScanIds(scanFilterValue: string | null): string[] {
|
||||
@@ -110,12 +190,13 @@ function resolveComplianceMatch(
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalizedFramework = normalizeComplianceFrameworkName(framework);
|
||||
const match = compliances.find(
|
||||
(compliance) =>
|
||||
normalizeComplianceFrameworkName(compliance.attributes.framework) ===
|
||||
normalizedFramework,
|
||||
);
|
||||
const match = compliances
|
||||
.map((compliance) => ({
|
||||
compliance,
|
||||
score: complianceMatchScore(framework, compliance.attributes.framework),
|
||||
}))
|
||||
.filter(({ score }) => score > 0)
|
||||
.sort((a, b) => b.score - a.score)[0]?.compliance;
|
||||
|
||||
if (!match) {
|
||||
return null;
|
||||
@@ -202,7 +283,6 @@ export function ResourceDetailDrawerContent({
|
||||
onNavigateNext,
|
||||
onMuteComplete,
|
||||
}: ResourceDetailDrawerContentProps) {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const [isMuteModalOpen, setIsMuteModalOpen] = useState(false);
|
||||
const [isJiraModalOpen, setIsJiraModalOpen] = useState(false);
|
||||
@@ -284,7 +364,7 @@ export function ResourceDetailDrawerContent({
|
||||
return;
|
||||
}
|
||||
|
||||
router.push(
|
||||
window.open(
|
||||
buildComplianceDetailHref({
|
||||
complianceId: complianceMatch.complianceId,
|
||||
framework: complianceMatch.framework,
|
||||
@@ -294,6 +374,8 @@ export function ResourceDetailDrawerContent({
|
||||
currentFinding: f,
|
||||
includeScanData: f?.scan?.id === complianceScanId,
|
||||
}),
|
||||
"_blank",
|
||||
"noopener,noreferrer",
|
||||
);
|
||||
} catch (error) {
|
||||
console.error("Error resolving compliance detail:", error);
|
||||
@@ -477,7 +559,7 @@ export function ResourceDetailDrawerContent({
|
||||
/>
|
||||
<EntityInfo
|
||||
nameIcon={<Container className="size-4" />}
|
||||
entityAlias={f.resourceGroup}
|
||||
entityAlias={f.resourceName}
|
||||
entityId={f.resourceUid}
|
||||
idLabel="UID"
|
||||
/>
|
||||
@@ -505,7 +587,9 @@ export function ResourceDetailDrawerContent({
|
||||
<InfoField label="Failing for" variant="compact">
|
||||
{getFailingForLabel(f.firstSeenAt) || "-"}
|
||||
</InfoField>
|
||||
<div className="hidden md:block" />
|
||||
<InfoField label="Group" variant="compact">
|
||||
{f.resourceGroup || "-"}
|
||||
</InfoField>
|
||||
|
||||
{/* Row 3: IDs */}
|
||||
<InfoField label="Check ID" variant="compact">
|
||||
@@ -529,6 +613,11 @@ export function ResourceDetailDrawerContent({
|
||||
className="max-w-full text-sm"
|
||||
/>
|
||||
</InfoField>
|
||||
|
||||
{/* Row 4: Resource metadata */}
|
||||
<InfoField label="Resource type" variant="compact">
|
||||
{f.resourceType || "-"}
|
||||
</InfoField>
|
||||
</div>
|
||||
|
||||
{/* Actions button — fixed size, aligned with row 1 */}
|
||||
|
||||
@@ -34,6 +34,7 @@ export interface FindingResourceRow {
|
||||
providerAlias: string;
|
||||
providerUid: string;
|
||||
resourceName: string;
|
||||
resourceType: string;
|
||||
resourceGroup: string;
|
||||
resourceUid: string;
|
||||
service: string;
|
||||
|
||||
Reference in New Issue
Block a user