Files
prowler/ui/app/(prowler)/_overview/exploitability/_components/top-issues.tsx
T

99 lines
3.8 KiB
TypeScript

import Link from "next/link";
import { IssuesLoadError } from "@/components/issues/issues-load-error";
import { Card, CardContent, CardTitle } from "@/components/shadcn";
import { Badge } from "@/components/shadcn/badge/badge";
import { Button } from "@/components/shadcn/button/button";
import { riskFactors } from "@/lib/issues/risk-factors";
import { issuesHref } from "@/lib/issues/scope";
import { VERDICTS } from "@/lib/issues/verdicts";
import { IssueResource } from "@/types/issues";
/** Enough to show the combination; the rest is on the issue. */
const TOP_FACTORS = 3;
interface TopIssuesProps {
issues: IssueResource[] | null;
scope: Record<string, string>;
}
/**
* The worst issues in scope, each on its own tile. Two tiles per row once the
* charts column is wide enough; the list is short, so a single column would
* leave most of the card empty on desktop.
*/
export const TopIssues = ({ issues, scope }: TopIssuesProps) => (
<Card variant="base" className="w-full min-w-0">
<CardTitle>Top issues</CardTitle>
<CardContent className="flex flex-col gap-4">
{issues === null ? (
<IssuesLoadError />
) : issues.length > 0 ? (
<ul className="grid grid-cols-1 gap-3 md:grid-cols-2">
{issues.map(({ id, attributes, relationships }) => {
const verdict = VERDICTS.find(
(item) => item.id === attributes.verdict,
);
const providerId = relationships?.provider?.data?.id;
const factors = riskFactors(attributes)
.slice(0, TOP_FACTORS)
.map((factor) => factor.label)
.join(" · ");
return (
<li key={id} className="min-w-0">
<Card variant="inner" padding="md" className="h-full gap-2">
<div className="flex items-center justify-between gap-3">
<Badge variant={verdict?.variant ?? "tag"}>
{verdict?.label ?? attributes.verdict}
</Badge>
<span className="text-text-neutral-tertiary shrink-0 text-xs">
{attributes.findings_count}{" "}
{attributes.findings_count === 1 ? "finding" : "findings"}
</span>
</div>
<div className="min-w-0">
<Button
asChild
variant="link"
size="link-sm"
className="max-w-full"
>
<Link
href={issuesHref(scope, {
"filter[pattern]": attributes.pattern,
...(providerId
? { "filter[provider_id__in]": providerId }
: {}),
})}
title={attributes.title}
>
<span className="truncate">{attributes.title}</span>
</Link>
</Button>
<p className="text-text-neutral-tertiary truncate text-xs">
{attributes.account}
</p>
{/* The reason to look is the combination, not the rank. */}
{factors && (
<p className="text-text-neutral-secondary truncate text-xs">
{factors}
</p>
)}
</div>
</Card>
</li>
);
})}
</ul>
) : (
<p className="text-text-neutral-tertiary py-8 text-center text-sm">
No issues in this scope.
</p>
)}
<Button asChild variant="link" size="link-sm" className="self-end">
<Link href={issuesHref(scope)}>View all issues</Link>
</Button>
</CardContent>
</Card>
);