diff --git a/src/components/risk-handling/RiskHandlingRow.tsx b/src/components/risk-handling/RiskHandlingRow.tsx new file mode 100644 index 0000000..5adeef0 --- /dev/null +++ b/src/components/risk-handling/RiskHandlingRow.tsx @@ -0,0 +1,118 @@ +// Copyright (C) 2024 Tim Bastin, l3montree UG (haftungsbeschränkt) +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import { FlawByPackage } from "@/types/api/api"; +import { classNames } from "@/utils/common"; +import { ChevronDownIcon, ChevronRightIcon } from "@heroicons/react/24/outline"; +import { flexRender, Row } from "@tanstack/react-table"; +import React, { FunctionComponent } from "react"; +import FlawState from "../common/FlawState"; +import { useRouter } from "next/router"; + +interface Props { + row: Row; + index: number; + arrLength: number; +} +const RiskHandlingRow: FunctionComponent = ({ + row, + index, + arrLength, +}) => { + const [isOpen, setIsOpen] = React.useState(false); + const router = useRouter(); + return ( + <> + setIsOpen((prev) => !prev)} + className={classNames( + "relative cursor-pointer align-top transition-all", + index === arrLength - 1 || isOpen ? "" : "border-b", + index % 2 != 0 && "bg-card", + "hover:bg-gray-50 dark:hover:bg-secondary", + )} + key={row.id} + > + + {isOpen ? ( + + ) : ( + + )} + + {row.getVisibleCells().map((cell, i) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + {isOpen && ( + + +
+ + + + + + + + + + + {row.original.flaws?.map((flaw) => ( + + router.push(router.asPath + "/../flaws/" + flaw.id) + } + className="border-b align-baseline hover:bg-gray-50 dark:hover:bg-secondary" + key={flaw.id} + > + + + + + + ))} + +
StateCVERiskDescription
+
+ +
+
{flaw.cveId}{flaw.rawRiskAssessment} +
+ {flaw.cve?.description} +
+
+
+ + + )} + + ); +}; + +export default RiskHandlingRow; diff --git a/src/pages/[organizationSlug]/projects/[projectSlug]/assets/[assetSlug]/risk-handling.tsx b/src/pages/[organizationSlug]/projects/[projectSlug]/assets/[assetSlug]/risk-handling.tsx index 482a3e3..eeb97c6 100644 --- a/src/pages/[organizationSlug]/projects/[projectSlug]/assets/[assetSlug]/risk-handling.tsx +++ b/src/pages/[organizationSlug]/projects/[projectSlug]/assets/[assetSlug]/risk-handling.tsx @@ -1,6 +1,3 @@ -import DateString from "@/components/common/DateString"; -import Filter from "@/components/common/Filter"; -import FlawState from "@/components/common/FlawState"; import SortingCaret from "@/components/common/SortingCaret"; import { middleware } from "@/decorators/middleware"; import { withAsset } from "@/decorators/withAsset"; @@ -9,16 +6,8 @@ import { useActiveAsset } from "@/hooks/useActiveAsset"; import { useActiveOrg } from "@/hooks/useActiveOrg"; import { useActiveProject } from "@/hooks/useActiveProject"; import { useAssetMenu } from "@/hooks/useAssetMenu"; -import { useColumnVisibility } from "@/hooks/useColumnVisibility"; import useFilter from "@/hooks/useFilter"; -import { - FilterableColumnDef, - dateOperators, - numberOperators, - stringOperators, -} from "@/services/filter"; -import { AssetDTO, FlawWithCVE, Paged } from "@/types/api/api"; -import { ViewColumnsIcon } from "@heroicons/react/24/outline"; +import { AssetDTO, FlawByPackage, FlawWithCVE, Paged } from "@/types/api/api"; import { createColumnHelper, flexRender, @@ -34,109 +23,29 @@ import Page from "../../../../../../components/Page"; import { withOrgs } from "../../../../../../decorators/withOrgs"; import { withSession } from "../../../../../../decorators/withSession"; import { getApiClientFromContext } from "../../../../../../services/devGuardApi"; -import { - beautifyPurl, - classNames, - extractVersion, -} from "../../../../../../utils/common"; +import { beautifyPurl, classNames } from "../../../../../../utils/common"; import CustomPagination from "@/components/common/CustomPagination"; import EcosystemImage from "@/components/common/EcosystemImage"; import EmptyList from "@/components/common/EmptyList"; import Section from "@/components/common/Section"; import { Badge } from "@/components/ui/badge"; -import { buttonVariants } from "@/components/ui/button"; -import { - DropdownMenu, - DropdownMenuCheckboxItem, - DropdownMenuContent, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu"; import { Input } from "@/components/ui/input"; import { withOrganization } from "@/decorators/withOrganization"; import { debounce } from "lodash"; import { Loader2 } from "lucide-react"; +import RiskHandlingRow from "@/components/risk-handling/RiskHandlingRow"; interface Props { asset: AssetDTO; - flaws: Paged; + flaws: Paged; } -const columnHelper = createColumnHelper(); +const columnHelper = createColumnHelper(); const columnsDef = [ { - ...columnHelper.accessor("state", { - header: "State", - enableSorting: true, - id: "state", - cell: (row) => ( -
- -
- ), - }), - operators: stringOperators, - filterValues: ["open", "fixed", "ignored"], - }, - { - ...columnHelper.accessor("cve.cve", { - header: "CVE", - enableSorting: true, - id: "cve.cve", - cell: (row) => ( -
- {row.getValue()} -
- ), - }), - }, - { - ...columnHelper.accessor("rawRiskAssessment", { - header: "Risk", - id: "rawRiskAssessment", - enableSorting: true, - cell: (row) => row.getValue(), - }), - }, - { - ...columnHelper.accessor("cve.cisaExploitAdd", { - header: "CISA Exploit Add", - id: "cve.cisaExploitAdd", - enableSorting: true, - cell: (row) => { - const value = row.getValue(); - if (!value) { - return null; - } - return ; - }, - }), - }, - { - ...columnHelper.accessor("cve.cisaActionDue", { - header: "CISA Action Due", - id: "cve.cisaActionDue", - enableSorting: true, - cell: (row) => { - const value = row.getValue(); - if (!value) { - return null; - } - return ; - }, - }), - }, - { - ...columnHelper.accessor("cve.cisaRequiredAction", { - header: "CISA Required Action", - id: "cve.cisaRequiredAction", - enableSorting: true, - cell: (row) => row.getValue(), - }), - }, - { - ...columnHelper.accessor("arbitraryJsonData.packageName", { + ...columnHelper.accessor("packageName", { header: "Package", id: "packageName", cell: (row) => ( @@ -149,110 +58,57 @@ const columnsDef = [ ), }), }, + { - ...columnHelper.accessor("component.purlOrCpe", { - header: "Installed Version", - id: "installedVersion", - cell: (row) => ( - - {extractVersion(row.getValue())} - - ), - }), - }, - { - ...columnHelper.accessor("arbitraryJsonData.fixedVersion", { - header: "Fixed in Version", - id: "fixedVersion", - cell: (row) => ( - - {beautifyPurl(row.getValue() as string)} - - ), - }), - }, - { - ...columnHelper.accessor("cve.description", { - header: "Message", - id: "message", + ...columnHelper.accessor("maxRisk", { + header: "Max Risk", enableSorting: true, + id: "max_risk", cell: (row) => ( -
-

{row.getValue()}

+
+ {row.getValue()}
), }), }, { - ...columnHelper.accessor("cve.cvss", { - header: "Base CVSS", + ...columnHelper.accessor("avgRisk", { + header: "Average Risk", + id: "avg_risk", enableSorting: true, - id: "cve.cvss", - cell: (row) => row.getValue(), + cell: (row) => row.getValue().toFixed(2), }), - operators: numberOperators, }, { - ...columnHelper.accessor("cve.severity", { - header: "Severity", + ...columnHelper.accessor("totalRisk", { + header: "Total Risk", + id: "total_risk", enableSorting: true, - id: "cve.severity", - cell: (row) => ( - {row.getValue()} - ), + cell: (row) => row.getValue().toFixed(2), }), }, { - ...columnHelper.accessor("cve.epss", { - header: "Exploit Prediction Scoring System", - id: "cve.epss", + ...columnHelper.accessor("flawCount", { + header: "Flaw Count", + id: "flaw_count", enableSorting: true, cell: (row) => row.getValue(), }), - operators: numberOperators, - }, - - { - ...columnHelper.accessor("createdAt", { - header: "First reported", - id: "createdAt", - enableSorting: true, - cell: (row) => ( - - - - ), - }), - operators: dateOperators, }, ]; const Index: FunctionComponent = (props) => { const { sortingState, handleSort } = useFilter(); - const { visibleColumns, setVisibleColumns } = useColumnVisibility( - "scaTable", - { - "cve.cisaActionDue": false, - "cve.cisaExploitAdd": false, - "cve.cisaRequiredAction": false, - "cve.severity": false, - "cve.epss": false, - "cve.cvss": false, - "cve.createdAt": false, - }, - ); + const table = useReactTable({ columns: columnsDef, data: props.flaws.data, getCoreRowModel: getCoreRowModel(), onSortingChange: handleSort, - onColumnVisibilityChange: (v) => { - setVisibleColumns(v); - }, + manualSorting: true, state: { sorting: sortingState, - columnVisibility: visibleColumns, }, }); @@ -360,43 +216,6 @@ const Index: FunctionComponent = (props) => { forceVertical title="Identified Risks" description="This table shows all the identified risks for this asset." - Button={ -
- - - - Columns - - - {table.getAllLeafColumns().map((column) => { - return ( - - setVisibleColumns((prev) => ({ - ...prev, - [column.id]: !prev[column.id], - })) - } - key={column.id} - > - {(column.columnDef.header as string) ?? ""} - - ); - })} - - - "operators" in c, - )} - /> -
- } >
= (props) => { {table.getHeaderGroups().map((headerGroup) => ( + {headerGroup.headers.map((header) => ( = (props) => { {table.getRowModel().rows.map((row, i, arr) => ( - { - router.push(r + "/flaws/" + row.original.id); - }} - className={classNames( - "relative cursor-pointer align-top transition-all", - i === arr.length - 1 ? "" : "border-b", - i % 2 != 0 && "bg-card", - "hover:bg-gray-50 dark:hover:bg-secondary", - )} - key={row.id} - > - {row.getVisibleCells().map((cell, i) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext(), - )} - - ))} - + ))} diff --git a/src/types/api/api.ts b/src/types/api/api.ts index 51c2ec6..95bc60e 100644 --- a/src/types/api/api.ts +++ b/src/types/api/api.ts @@ -299,3 +299,12 @@ export interface AffectedPackage { PackageName: string; PurlWithVersion: string; } + +export interface FlawByPackage { + packageName: string; + maxRisk: number; + totalRisk: number; + flawCount: number; + avgRisk: number; + flaws: FlawWithCVE[]; +} diff --git a/src/utils/common.ts b/src/utils/common.ts index e91813c..e16de61 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -143,6 +143,9 @@ export const getEcosystem = (packageName: string) => { }; export const beautifyPurl = (purl: string) => { + if (!purl) { + return ""; + } const parts = purl.split("@"); let first = parts[0]; // remove everything before the first slash