Files
prowler/ui/components/shared/resource-metadata-panel.tsx
T
2026-05-22 12:03:03 +02:00

79 lines
2.4 KiB
TypeScript

"use client";
import { Card } from "@/components/shadcn/card/card";
import {
QUERY_EDITOR_LANGUAGE,
QueryCodeEditor,
} from "@/components/shared/query-code-editor";
import { parseMetadata } from "@/lib/resource-metadata";
interface ResourceMetadataPanelProps {
metadata: Record<string, unknown> | string | null | undefined;
details: string | null | undefined;
}
/**
* Shared "Metadata" panel for a resource.
*
* Renders the resource `details` text and its `metadata` as formatted JSON
* with a copy-to-clipboard action, falling back to an empty state when
* neither is available. Reused by the resource detail view and the finding
* detail drawer (compliance requirement findings view) to keep the UX
* consistent across surfaces.
*
* Layout contract: the parent must be a bounded flex column (e.g.
* `flex min-h-0 flex-1 flex-col overflow-hidden`). The embedded editor
* fills that height and scrolls internally, so JSON-heavy resources do
* not push the surrounding chrome (drawer, page) into a double scroll.
*/
export function ResourceMetadataPanel({
metadata,
details,
}: ResourceMetadataPanelProps) {
const parsedMetadata = parseMetadata(metadata);
const hasMetadata =
parsedMetadata !== null && Object.keys(parsedMetadata).length > 0;
const hasDetails = Boolean(details?.trim());
const formattedMetadata =
hasMetadata && parsedMetadata
? JSON.stringify(parsedMetadata, null, 2)
: null;
return (
<>
{hasDetails && (
<Card variant="inner">
<div className="flex flex-col gap-1">
<span className="text-text-neutral-secondary text-sm font-semibold">
Details:
</span>
<p className="text-text-neutral-primary text-sm break-words whitespace-pre-wrap">
{details}
</p>
</div>
</Card>
)}
{formattedMetadata && (
<QueryCodeEditor
ariaLabel="Resource metadata"
visibleLabel={null}
language={QUERY_EDITOR_LANGUAGE.JSON}
value={formattedMetadata}
copyValue={formattedMetadata}
editable={false}
fill
showCopyButton
onChange={() => {}}
/>
)}
{!hasDetails && !hasMetadata && (
<p className="text-text-neutral-tertiary py-8 text-center text-sm">
No metadata available for this resource.
</p>
)}
</>
);
}