@@ -264,57 +217,31 @@ export const VulnerabilitiesByPackage: React.FC<
rowIndex={rowIndex}
>
- showDrawer("showVulnerability", item)}
- >
+
{item.vulnerabilityId}
-
+
- {item.vulnerability?.title}
+ {item.vulnerability?.title ||
+ item.vulnerability?.description}
-
+
{item.vulnerability?.average_severity && (
)}
-
- showDrawer("showAdvisory", item)}
- >
- {item.advisory.identifier}
-
-
-
-
-
-
- {item.status.charAt(0).toUpperCase() +
- item.status.slice(1).replace("_", " ")}
-
+ {formatDate(item.vulnerability?.published)}
@@ -329,48 +256,6 @@ export const VulnerabilitiesByPackage: React.FC<
isCompact
paginationProps={paginationProps}
/>
-
- setSelectedRowAction(null)}
- pageKey="drawer"
- drawerPanelContentProps={{ defaultSize: "600px" }}
- header={
- <>
- {selectedRowAction === "showVulnerability" && (
-
-
- Vulnerability
-
-
- )}
- {selectedRowAction === "showAdvisory" && (
-
-
- Advisory
-
-
- )}
- >
- }
- >
- {selectedRowAction === "showVulnerability" && (
- <>
- {selectedRow?.advisory && (
-
- )}
- >
- )}{" "}
- {selectedRowAction === "showAdvisory" && (
- <>
- {selectedRow?.advisory && (
-
- )}
- >
- )}
-
>
);
};
diff --git a/client/src/app/pages/package-list/package-context.tsx b/client/src/app/pages/package-list/package-context.tsx
index fb2fae85..e10c4f9f 100644
--- a/client/src/app/pages/package-list/package-context.tsx
+++ b/client/src/app/pages/package-list/package-context.tsx
@@ -23,9 +23,9 @@ interface PackageTableData extends PurlSummary {
interface IPackageSearchContext {
tableControls: ITableControls<
PackageTableData,
- "name" | "version" | "type" | "vulnerabilities" | "sboms",
+ "name" | "namespace" | "version" | "type" | "path" | "qualifiers",
never,
- "" | "type",
+ "" | "type" | "arch",
string
>;
@@ -51,10 +51,11 @@ export const PackageSearchProvider: React.FunctionComponent<
persistenceKeyPrefix: TablePersistenceKeyPrefixes.packages,
columnNames: {
name: "Name",
+ namespace: "Namespace",
version: "Version",
type: "Type",
- vulnerabilities: "Vulnerabilities",
- sboms: "SBOMs",
+ path: "Path",
+ qualifiers: "Qualifiers",
},
isPaginationEnabled: true,
isSortEnabled: true,
@@ -76,6 +77,20 @@ export const PackageSearchProvider: React.FunctionComponent<
{ value: "maven", label: "Maven" },
{ value: "rpm", label: "RPM" },
{ value: "npm", label: "NPM" },
+ { value: "oci", label: "OCI" },
+ ],
+ },
+ {
+ categoryKey: "arch",
+ title: "Architecture",
+ placeholderText: "Architecture",
+ type: FilterType.multiselect,
+ selectOptions: [
+ { value: "x86_64", label: "AMD 64bit" },
+ { value: "aarch64", label: "ARM 64bit" },
+ { value: "ppc64le", label: "PowerPC" },
+ { value: "s390x", label: "S390" },
+ { value: "noarch", label: "No Arch" },
],
},
],
diff --git a/client/src/app/pages/package-list/package-table.tsx b/client/src/app/pages/package-list/package-table.tsx
index fd12acec..b50aa85e 100644
--- a/client/src/app/pages/package-list/package-table.tsx
+++ b/client/src/app/pages/package-list/package-table.tsx
@@ -3,7 +3,6 @@ import { NavLink } from "react-router-dom";
import { Table, Tbody, Td, Th, Thead, Tr } from "@patternfly/react-table";
-import { SbomsByPackageCount } from "@app/components/SbomsByPackageCount";
import { SimplePagination } from "@app/components/SimplePagination";
import {
ConditionalTableBody,
@@ -11,6 +10,7 @@ import {
TableRowContentWithControls,
} from "@app/components/TableControls";
+import { PackageQualifiers } from "@app/components/PackageQualifiers";
import { PackageSearchContext } from "./package-context";
export const PackageTable: React.FC = () => {
@@ -37,10 +37,11 @@ export const PackageTable: React.FC = () => {
+
-
-
+
+
@@ -59,17 +60,22 @@ export const PackageTable: React.FC = () => {
item={item}
rowIndex={rowIndex}
>
-
-
+
+
{item.decomposedPurl
? item.decomposedPurl?.name
: item.purl}
+ {item.decomposedPurl?.namespace}
+
+
@@ -83,11 +89,18 @@ export const PackageTable: React.FC = () => {
{item.decomposedPurl?.type}
-
-
+ width={10}
+ modifier="truncate"
+ {...getTdProps({ columnKey: "path" })}
+ >
+ {item.decomposedPurl?.path}
+
+
+ {item.decomposedPurl?.qualifiers && (
+
+ )}
From 474020a638bba55dc6e85c36e8cce4f2a5d59ff8 Mon Sep 17 00:00:00 2001
From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com>
Date: Tue, 29 Oct 2024 12:23:08 +0100
Subject: [PATCH 03/11] Fix Pamel filter alignment and checkboxes ids
---
.../FilterPanel/CheckboxFilterControl.tsx | 2 +-
.../components/FilterPanel/FilterPanel.tsx | 32 +++++++++----------
2 files changed, 17 insertions(+), 17 deletions(-)
diff --git a/client/src/app/components/FilterPanel/CheckboxFilterControl.tsx b/client/src/app/components/FilterPanel/CheckboxFilterControl.tsx
index 4570c3c5..e9fcdead 100644
--- a/client/src/app/components/FilterPanel/CheckboxFilterControl.tsx
+++ b/client/src/app/components/FilterPanel/CheckboxFilterControl.tsx
@@ -59,7 +59,7 @@ export const CheckboxFilterControl = ({
return (
{
filterCategories: FilterCategory[];
@@ -60,18 +60,18 @@ export const FilterPanel = ({
Clear all filters
-
- {filterCategories
- .filter((filterCategory) => {
- return (
- omitFilterCategoryKeys.find(
- (categoryKey) => categoryKey === filterCategory.categoryKey
- ) === undefined
- );
- })
- .map((category) => {
- return (
-
+ {filterCategories
+ .filter((filterCategory) => {
+ return (
+ omitFilterCategoryKeys.find(
+ (categoryKey) => categoryKey === filterCategory.categoryKey
+ ) === undefined
+ );
+ })
+ .map((category) => {
+ return (
+
+
{category.title}
@@ -89,9 +89,9 @@ export const FilterPanel = ({
/>
- );
- })}
-
+
+ );
+ })}
>
);
From c5d222cf37d80b0876ed98db56ae6653d11b6b6c Mon Sep 17 00:00:00 2001
From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com>
Date: Wed, 30 Oct 2024 08:35:50 +0100
Subject: [PATCH 04/11] Save dashboard
---
client/src/app/pages/home/home.tsx | 284 +++++++++++++++++++++-
client/src/app/queries/vulnerabilities.ts | 37 ++-
2 files changed, 318 insertions(+), 3 deletions(-)
diff --git a/client/src/app/pages/home/home.tsx b/client/src/app/pages/home/home.tsx
index 285af6a7..7a0e4d25 100644
--- a/client/src/app/pages/home/home.tsx
+++ b/client/src/app/pages/home/home.tsx
@@ -1,5 +1,287 @@
import React from "react";
+import { Link } from "react-router-dom";
+
+import {
+ Chart,
+ ChartAxis,
+ ChartBar,
+ ChartLabel,
+ ChartLegend,
+ ChartStack,
+ ChartThemeColor,
+ ChartTooltip,
+} from "@patternfly/react-charts";
+import {
+ Bullseye,
+ Card,
+ CardBody,
+ CardTitle,
+ DescriptionList,
+ DescriptionListDescription,
+ DescriptionListGroup,
+ DescriptionListTerm,
+ Grid,
+ GridItem,
+ PageSection,
+ Stack,
+ StackItem,
+ TextContent,
+} from "@patternfly/react-core";
+
+import { Severity } from "@app/client";
+import { useFetchSBOMs } from "@app/queries/sboms";
+import { useFetchVulnerabilities } from "@app/queries/vulnerabilities";
+import { formatDate } from "@app/utils/utils";
+import { severityList } from "@app/api/model-utils";
+
+interface Legend {
+ severity: Severity;
+}
+
+const LEGENDS: Legend[] = [
+ { severity: "critical" },
+ { severity: "high" },
+ { severity: "medium" },
+ { severity: "low" },
+ { severity: "none" },
+];
export const Home: React.FC = () => {
- return <>Dashboard here>;
+ const {
+ result: { data: sboms, total: totalSboms },
+ isFetching: isFetchingSboms,
+ fetchError: fetchErrorSboms,
+ } = useFetchSBOMs(
+ {
+ page: { pageNumber: 1, itemsPerPage: 10 },
+ sort: { field: "published", direction: "desc" },
+ },
+ true
+ );
+
+ const {
+ result: { data: vulnerabilities, total: totalVulnerabilities },
+ isFetching: isFetchingVulnerabilities,
+ fetchError: fetchErrorVulnerabilities,
+ } = useFetchVulnerabilities(
+ {
+ page: { pageNumber: 1, itemsPerPage: 10 },
+ sort: { field: "published", direction: "desc" },
+ },
+ true
+ );
+
+ return (
+ <>
+
+
+
+
+ Your dashboard
+
+
+
+
+
+
+ Below is a summary of CVE status for your last 10
+ ingested SBOMs. You can click on the SBOM name or CVE
+ severity number below to be taken to their respective
+ details page.
+
+
+
+
+ {
+ const severity = severityList[legend.severity];
+ return { name: severity.name };
+ })}
+ legendPosition="bottom-left"
+ height={375}
+ name="sbom-summary-status"
+ padding={{
+ bottom: 75,
+ left: 330,
+ right: 50,
+ top: 50,
+ }}
+ themeColor={ChartThemeColor.multiOrdered}
+ width={700}
+ legendComponent={
+ {
+ const severity =
+ severityList[legend.severity];
+ return severity.color.value;
+ })}
+ />
+ }
+ >
+
+ }
+ tickLabelComponent={
+ {
+ // const sbom_name = (event.target as any)
+ // .innerHTML as string | null;
+ // const sbom = props.find(
+ // (item) => item.sbom_name === sbom_name
+ // );
+ // if (sbom) {
+ // const sbomDetailsPage = `/sbom/content/${sbom.sbom_id}`;
+
+ // const wasmBindings = (window as any)
+ // .wasmBindings;
+ // if (wasmBindings) {
+ // wasmBindings.spogNavigateTo(
+ // sbomDetailsPage
+ // );
+ // } else {
+ // window.open(sbomDetailsPage);
+ // }
+ // }
+ // },
+ // }}
+ />
+ }
+ />
+
+ {
+ const severity = severityList[legend.severity];
+ return severity.color.value;
+ })}
+ >
+ {LEGENDS.map((legend) => {
+ const severity = severityList[legend.severity];
+ return (
+
+ }
+ // data={props.map((sbom) => {
+ // const severityKey = legend.severity;
+ // const count = sbom.vulnerabilities[
+ // severityKey
+ // ] as number;
+ // return {
+ // name: legend.name,
+ // x: sbom.sbom_name,
+ // y: count,
+ // label: `${legend.name}: ${count}`,
+ // };
+ // })}
+ />
+ );
+ })}
+
+
+
+
+
+
+
+
+
+
+
+
+ Last SBOM ingested
+
+
+
+
+ {formatDate(sboms?.[0]?.published)}
+
+
+
+ {sboms?.[0]?.name}
+
+
+
+
+
+
+
+
+
+
+
+ Total SBOMs
+
+
+ {totalSboms}
+
+
+
+
+
+
+
+
+ Last Vulnerability ingested
+
+
+
+
+ {formatDate(vulnerabilities?.[0]?.published)}
+
+
+ {vulnerabilities?.[0]?.identifier}
+
+
+
+
+
+
+
+
+
+
+ Total Vulnerabilities
+
+
+ {totalVulnerabilities}
+
+
+
+
+
+
+
+
+
+
+
+
+ sdf
+
+
+
+
+ >
+ );
};
diff --git a/client/src/app/queries/vulnerabilities.ts b/client/src/app/queries/vulnerabilities.ts
index f0cfc49b..16656ef9 100644
--- a/client/src/app/queries/vulnerabilities.ts
+++ b/client/src/app/queries/vulnerabilities.ts
@@ -1,7 +1,7 @@
-import { useMutation, useQuery } from "@tanstack/react-query";
+import { useMutation, useQueries, useQuery } from "@tanstack/react-query";
import { AxiosError } from "axios";
-import { HubRequestParams } from "@app/api/models";
+import { HubRequestParams, VulnerabilityStatus } from "@app/api/models";
import { client } from "@app/axios-config/apiInit";
import {
deleteVulnerability,
@@ -10,6 +10,7 @@ import {
VulnerabilityDetails,
} from "@app/client";
import { requestParamsQuery } from "@app/hooks/table-controls";
+import { useFetchSbomsAdvisory } from "./sboms";
export const VulnerabilitiesQueryKey = "vulnerabilities";
@@ -65,3 +66,35 @@ export const useDeleteVulnerabilityMutation = (
onError,
});
};
+
+export const useFetchVulnerabilitiesFromSbom = (sbomId: string) => {
+ const {
+ advisories,
+ isFetching: isFetchingAdvisories,
+ fetchError: fetchErrorAdvisories,
+ } = useFetchSbomsAdvisory(sbomId);
+
+ const usersMessages = useQueries({
+ queries: (advisories ?? [])
+ .flatMap((advisory) => {
+ return (advisory.status ?? []).map((status) => {
+ const result = {
+ vulnerabilityId: status.vulnerability_id,
+ status: status.status as VulnerabilityStatus,
+ };
+ return result;
+ });
+ })
+ .map((item) => {
+ return {
+ queryKey: ["messages", "id"],
+ queryFn: () => {
+ return getVulnerability({
+ client,
+ path: { id: item.vulnerabilityId },
+ });
+ },
+ };
+ }),
+ });
+};
From b709b10d733fe493703d5e43d94937add2e5a6fe Mon Sep 17 00:00:00 2001
From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com>
Date: Mon, 11 Nov 2024 16:17:10 +0100
Subject: [PATCH 05/11] Save 4 donut charts
---
client/src/app/api/models.ts | 9 +
.../SbomVulnerabilitiesDonutChart.tsx | 59 ++++++
.../domain-controls/useSbomVulnerabilities.ts | 2 +-
.../app/pages/home/components/WatchedSbom.tsx | 199 ++++++++++++++++++
.../home/components/WatchedSbomDonutChart.tsx | 26 +++
.../pages/home/components/WatchedSboms.tsx | 52 +++++
client/src/app/pages/home/home.tsx | 23 +-
.../app/pages/home/watched-sboms-context.tsx | 61 ++++++
.../sbom-details/vulnerabilities-by-sbom.tsx | 50 +----
client/src/app/queries/dashboard.ts | 65 ++++++
client/src/app/queries/sboms.ts | 8 +-
client/src/app/queries/vulnerabilities.ts | 37 +---
12 files changed, 496 insertions(+), 95 deletions(-)
create mode 100644 client/src/app/components/SbomVulnerabilitiesDonutChart.tsx
create mode 100644 client/src/app/pages/home/components/WatchedSbom.tsx
create mode 100644 client/src/app/pages/home/components/WatchedSbomDonutChart.tsx
create mode 100644 client/src/app/pages/home/components/WatchedSboms.tsx
create mode 100644 client/src/app/pages/home/watched-sboms-context.tsx
create mode 100644 client/src/app/queries/dashboard.ts
diff --git a/client/src/app/api/models.ts b/client/src/app/api/models.ts
index 4e3e8232..84f96a66 100644
--- a/client/src/app/api/models.ts
+++ b/client/src/app/api/models.ts
@@ -51,3 +51,12 @@ export interface DecomposedPurl {
qualifiers?: Labels;
path?: string;
}
+
+// User preferences
+
+export interface WatchedSboms {
+ sbom1Id: string | null;
+ sbom2Id: string | null;
+ sbom3Id: string | null;
+ sbom4Id: string | null;
+}
diff --git a/client/src/app/components/SbomVulnerabilitiesDonutChart.tsx b/client/src/app/components/SbomVulnerabilitiesDonutChart.tsx
new file mode 100644
index 00000000..1f317e64
--- /dev/null
+++ b/client/src/app/components/SbomVulnerabilitiesDonutChart.tsx
@@ -0,0 +1,59 @@
+import React from "react";
+
+import { ChartDonut } from "@patternfly/react-charts";
+
+import { compareBySeverityFn, severityList } from "@app/api/model-utils";
+import { Severity } from "@app/client";
+import { SbomVulnerabilitySummary } from "@app/hooks/domain-controls/useSbomVulnerabilities";
+
+export interface SbomVulnerabilitiesDonutChartProps {
+ vulnerabilitiesSummary: SbomVulnerabilitySummary;
+}
+
+export const SbomVulnerabilitiesDonutChart: React.FC<
+ SbomVulnerabilitiesDonutChartProps
+> = ({ vulnerabilitiesSummary }) => {
+ const donutChart = React.useMemo(() => {
+ return Object.keys(vulnerabilitiesSummary.severities)
+ .map((item) => {
+ const severity = item as Severity;
+ const count = vulnerabilitiesSummary.severities[severity];
+ const severityProps = severityList[severity];
+ return {
+ severity,
+ count,
+ label: severityProps.name,
+ color: severityProps.color.value,
+ };
+ })
+ .sort(compareBySeverityFn((item) => item.severity));
+ }, [vulnerabilitiesSummary]);
+
+ return (
+
+ ({
+ name: `${label}: ${count}`,
+ }))}
+ data={donutChart.map(({ label, count }) => ({
+ x: label,
+ y: count,
+ }))}
+ labels={({ datum }) => `${datum.x}: ${datum.y}`}
+ colorScale={donutChart.map(({ color }) => color)}
+ />
+
+ );
+};
diff --git a/client/src/app/hooks/domain-controls/useSbomVulnerabilities.ts b/client/src/app/hooks/domain-controls/useSbomVulnerabilities.ts
index d4c8ff0b..2ce26083 100644
--- a/client/src/app/hooks/domain-controls/useSbomVulnerabilities.ts
+++ b/client/src/app/hooks/domain-controls/useSbomVulnerabilities.ts
@@ -19,7 +19,7 @@ interface SbomVulnerability {
vulnerability?: VulnerabilityDetails;
}
-interface SbomVulnerabilitySummary {
+export interface SbomVulnerabilitySummary {
total: number;
severities: { [key in Severity]: number };
}
diff --git a/client/src/app/pages/home/components/WatchedSbom.tsx b/client/src/app/pages/home/components/WatchedSbom.tsx
new file mode 100644
index 00000000..1b1dcf6d
--- /dev/null
+++ b/client/src/app/pages/home/components/WatchedSbom.tsx
@@ -0,0 +1,199 @@
+import React from "react";
+import { Link } from "react-router-dom";
+
+import {
+ Button,
+ Card,
+ CardBody,
+ CardFooter,
+ CardTitle,
+ EmptyState,
+ EmptyStateBody,
+ EmptyStateHeader,
+ EmptyStateVariant,
+ MenuToggle,
+ MenuToggleElement,
+ Select,
+ SelectList,
+ SelectOption,
+ Stack,
+ StackItem,
+ TextInputGroup,
+ TextInputGroupMain,
+ TextInputGroupUtilities,
+} from "@patternfly/react-core";
+import TimesIcon from "@patternfly/react-icons/dist/esm/icons/times-icon";
+
+import { LoadingWrapper } from "@app/components/LoadingWrapper";
+import { useFetchSBOMById, useFetchSBOMs } from "@app/queries/sboms";
+
+import { WatchedSbomsContext } from "../watched-sboms-context";
+import { WatchedSbomDonutChart } from "./WatchedSbomDonutChart";
+
+interface WatchedSbomProps {
+ fieldName: string;
+ sbomId: string | null;
+}
+
+export const WatchedSbom: React.FC = ({
+ fieldName,
+ sbomId,
+}) => {
+ const { patch } = React.useContext(WatchedSbomsContext);
+
+ const textInputRef = React.useRef();
+ const [inputValue, setInputValue] = React.useState("");
+ const [debouncedInputValue, setDebouncedInputValue] = React.useState("");
+
+ React.useEffect(() => {
+ const delayInputTimeoutId = setTimeout(() => {
+ setDebouncedInputValue(inputValue);
+ }, 500);
+ return () => clearTimeout(delayInputTimeoutId);
+ }, [inputValue, 500]);
+
+ const [isSelectOpen, setIsSelectOpen] = React.useState(false);
+
+ const {
+ sbom: currentSbom,
+ isFetching: isFetchingCurrentSbom,
+ fetchError: fetchErrorCurrentSbom,
+ } = useFetchSBOMById(sbomId ?? undefined);
+
+ const {
+ result: { data: sbomOptions },
+ isFetching: isFetchingSbomOptions,
+ fetchError: fetchErrorSbomOptions,
+ } = useFetchSBOMs(
+ {
+ filters: [{ field: "", operator: "~", value: debouncedInputValue }],
+ page: { pageNumber: 1, itemsPerPage: 10 },
+ },
+ true
+ );
+
+ const onSelectItem = (
+ _event: React.MouseEvent | undefined,
+ value: string | number | undefined
+ ) => {
+ if (value) {
+ patch(fieldName, value as string);
+ }
+
+ closeMenu();
+ };
+
+ const closeMenu = () => {
+ setIsSelectOpen(false);
+ };
+
+ const onToggleClick = () => {
+ setIsSelectOpen(!isSelectOpen);
+ };
+
+ const onInputClick = () => {
+ if (!isSelectOpen) {
+ setIsSelectOpen(true);
+ } else if (!inputValue) {
+ closeMenu();
+ }
+ };
+
+ const onTextInputChange = (
+ _event: React.FormEvent,
+ value: string
+ ) => {
+ setInputValue(value);
+ };
+
+ const onClearButtonClick = () => {
+ setInputValue("");
+ textInputRef?.current?.focus();
+ };
+
+ return (
+
+
+ {currentSbom && {currentSbom?.name} }
+
+ {sbomId ? (
+
+
+
+
+
+ View Details
+
+
+ ) : (
+
+
+
+ You can get started by uploading an SBOM. Once your SBOMs are
+ uploaded come back to this page to change the SBOMs you would
+ like to track.
+
+
+ )}
+
+
+
+ {
+ !isOpen && closeMenu();
+ }}
+ toggle={(toggleRef: React.Ref) => (
+
+
+
+
+
+
+
+
+
+
+
+ )}
+ >
+
+ {sbomOptions.map((option) => (
+
+ {option.name}
+
+ ))}
+
+
+
+
+ );
+};
diff --git a/client/src/app/pages/home/components/WatchedSbomDonutChart.tsx b/client/src/app/pages/home/components/WatchedSbomDonutChart.tsx
new file mode 100644
index 00000000..99a74053
--- /dev/null
+++ b/client/src/app/pages/home/components/WatchedSbomDonutChart.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+
+
+import { SbomVulnerabilitiesDonutChart } from "@app/components/SbomVulnerabilitiesDonutChart";
+import { useSbomVulnerabilities } from "@app/hooks/domain-controls/useSbomVulnerabilities";
+
+interface WatchedSbomDonutChartProps {
+ sbomId: string;
+}
+
+export const WatchedSbomDonutChart: React.FC = ({
+ sbomId,
+}) => {
+ const {
+ vulnerabilities: vulnerabilities,
+ summary: vulnerabilitiesSummary,
+ isFetching: isFetchingVulnerabilities,
+ fetchError: fetchErrorVulnerabilities,
+ } = useSbomVulnerabilities(sbomId);
+
+ return (
+
+ );
+};
diff --git a/client/src/app/pages/home/components/WatchedSboms.tsx b/client/src/app/pages/home/components/WatchedSboms.tsx
new file mode 100644
index 00000000..f5c2daa3
--- /dev/null
+++ b/client/src/app/pages/home/components/WatchedSboms.tsx
@@ -0,0 +1,52 @@
+import React from "react";
+
+import { LoadingWrapper } from "@app/components/LoadingWrapper";
+import {
+ Bullseye,
+ Card,
+ CardBody,
+ Grid,
+ GridItem,
+ Spinner,
+} from "@patternfly/react-core";
+
+import { WatchedSbomsContext } from "../watched-sboms-context";
+import { WatchedSbom } from "./WatchedSbom";
+
+export const WatchedSboms: React.FC = () => {
+ const { sboms, isFetching, fetchError } =
+ React.useContext(WatchedSbomsContext);
+
+ return (
+
+ {Array.from(Array(4).keys()).map((_, index) => (
+
+
+
+
+
+
+
+
+
+ ))}
+
+ }
+ >
+
+ {sboms &&
+ Object.entries(sboms).map(([fieldName, fieldValue]) => {
+ return (
+
+
+
+ );
+ })}
+
+
+ );
+};
diff --git a/client/src/app/pages/home/home.tsx b/client/src/app/pages/home/home.tsx
index 7a0e4d25..996d53c0 100644
--- a/client/src/app/pages/home/home.tsx
+++ b/client/src/app/pages/home/home.tsx
@@ -12,7 +12,6 @@ import {
ChartTooltip,
} from "@patternfly/react-charts";
import {
- Bullseye,
Card,
CardBody,
CardTitle,
@@ -28,11 +27,13 @@ import {
TextContent,
} from "@patternfly/react-core";
+import { severityList } from "@app/api/model-utils";
import { Severity } from "@app/client";
import { useFetchSBOMs } from "@app/queries/sboms";
import { useFetchVulnerabilities } from "@app/queries/vulnerabilities";
import { formatDate } from "@app/utils/utils";
-import { severityList } from "@app/api/model-utils";
+import { WatchedSboms } from "./components/WatchedSboms";
+import { WatchedSbomsProvider } from "./watched-sboms-context";
interface Legend {
severity: Severity;
@@ -48,9 +49,9 @@ const LEGENDS: Legend[] = [
export const Home: React.FC = () => {
const {
- result: { data: sboms, total: totalSboms },
- isFetching: isFetchingSboms,
- fetchError: fetchErrorSboms,
+ result: { data: barchartSboms, total: totalSboms },
+ isFetching: isFetchingBarchartSboms,
+ fetchError: fetchErrorBarchartSboms,
} = useFetchSBOMs(
{
page: { pageNumber: 1, itemsPerPage: 10 },
@@ -214,11 +215,11 @@ export const Home: React.FC = () => {
- {formatDate(sboms?.[0]?.published)}
+ {formatDate(barchartSboms?.[0]?.published)}
-
- {sboms?.[0]?.name}
+
+ {barchartSboms?.[0]?.name}
@@ -276,9 +277,9 @@ export const Home: React.FC = () => {
-
- sdf
-
+
+
+
diff --git a/client/src/app/pages/home/watched-sboms-context.tsx b/client/src/app/pages/home/watched-sboms-context.tsx
new file mode 100644
index 00000000..1cb890ec
--- /dev/null
+++ b/client/src/app/pages/home/watched-sboms-context.tsx
@@ -0,0 +1,61 @@
+import React from "react";
+
+import { AxiosError } from "axios";
+
+import { WatchedSboms } from "@app/api/models";
+import { NotificationsContext } from "@app/components/NotificationsContext";
+import {
+ useFetchWatchedSboms,
+ useUpdateWatchedSbomsMutation,
+} from "@app/queries/dashboard";
+
+interface IWatchedSbomsContext {
+ sboms?: WatchedSboms;
+ isFetching: boolean;
+ fetchError: AxiosError | null;
+
+ patch: (key: string, value: string) => void;
+}
+
+const contextDefaultValue = {} as IWatchedSbomsContext;
+
+export const WatchedSbomsContext =
+ React.createContext(contextDefaultValue);
+
+interface IWatchedSbomsProvider {
+ children: React.ReactNode;
+}
+
+export const WatchedSbomsProvider: React.FunctionComponent<
+ IWatchedSbomsProvider
+> = ({ children }) => {
+ const { sboms, isFetching, fetchError } = useFetchWatchedSboms();
+
+ const { pushNotification } = React.useContext(NotificationsContext);
+
+ const onUpdateSuccess = () => {};
+ const onUpdateError = (_error: AxiosError) => {
+ pushNotification({
+ title: "Error while updating the user preferences",
+ variant: "danger",
+ });
+ };
+
+ const { mutate: updateSboms } = useUpdateWatchedSbomsMutation(
+ onUpdateSuccess,
+ onUpdateError
+ );
+
+ const patch = (key: string, value: string) => {
+ const newSboms = { ...sboms, [key]: value };
+ updateSboms(newSboms as WatchedSboms);
+ };
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/client/src/app/pages/sbom-details/vulnerabilities-by-sbom.tsx b/client/src/app/pages/sbom-details/vulnerabilities-by-sbom.tsx
index dd7c5424..0c497d28 100644
--- a/client/src/app/pages/sbom-details/vulnerabilities-by-sbom.tsx
+++ b/client/src/app/pages/sbom-details/vulnerabilities-by-sbom.tsx
@@ -1,7 +1,6 @@
import React from "react";
import { Link } from "react-router-dom";
-import { ChartDonut } from "@patternfly/react-charts";
import {
Card,
CardBody,
@@ -27,10 +26,9 @@ import {
Tr,
} from "@patternfly/react-table";
-import { compareBySeverityFn, severityList } from "@app/api/model-utils";
-import { Severity } from "@app/client";
import { LoadingWrapper } from "@app/components/LoadingWrapper";
import { PackageQualifiers } from "@app/components/PackageQualifiers";
+import { SbomVulnerabilitiesDonutChart } from "@app/components/SbomVulnerabilitiesDonutChart";
import { SeverityShieldAndText } from "@app/components/SeverityShieldAndText";
import { SimplePagination } from "@app/components/SimplePagination";
import {
@@ -107,24 +105,6 @@ export const VulnerabilitiesBySbom: React.FC = ({
expansionDerivedState: { isCellExpanded },
} = tableControls;
- //
-
- const donutChart = React.useMemo(() => {
- return Object.keys(vulnerabilitiesSummary.severities)
- .map((item) => {
- const severity = item as Severity;
- const count = vulnerabilitiesSummary.severities[severity];
- const severityProps = severityList[severity];
- return {
- severity,
- count,
- label: severityProps.name,
- color: severityProps.color.value,
- };
- })
- .sort(compareBySeverityFn((item) => item.severity));
- }, [vulnerabilitiesSummary]);
-
return (
<>
@@ -136,31 +116,9 @@ export const VulnerabilitiesBySbom: React.FC = ({
>
-
- ({
- name: `${label}: ${count}`,
- }))}
- data={donutChart.map(({ label, count }) => ({
- x: label,
- y: count,
- }))}
- labels={({ datum }) => `${datum.x}: ${datum.y}`}
- colorScale={donutChart.map(({ color }) => color)}
- />
-
+
diff --git a/client/src/app/queries/dashboard.ts b/client/src/app/queries/dashboard.ts
new file mode 100644
index 00000000..e62329db
--- /dev/null
+++ b/client/src/app/queries/dashboard.ts
@@ -0,0 +1,65 @@
+import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+import { AxiosError } from "axios";
+
+import { WatchedSboms } from "@app/api/models";
+import { client } from "@app/axios-config/apiInit";
+import { getUserPreferences, setUserPreferences } from "@app/client";
+
+const WATCHED_SBOMS_KEY = "watched-sboms";
+
+export const DashboardQueryKey = "dashboard";
+
+export const useFetchWatchedSboms = () => {
+ const { data, isLoading, error } = useQuery({
+ queryKey: [DashboardQueryKey, WATCHED_SBOMS_KEY],
+ queryFn: async () => {
+ try {
+ const response = await getUserPreferences({
+ client,
+ path: { key: WATCHED_SBOMS_KEY },
+ });
+ return response.data as WatchedSboms;
+ } catch (error) {
+ const axiosError = error as AxiosError;
+ if (axiosError.status === 404) {
+ const defaultValue: WatchedSboms = {
+ sbom1Id: null,
+ sbom2Id: null,
+ sbom3Id: null,
+ sbom4Id: null,
+ };
+ return defaultValue;
+ }
+ }
+ },
+ });
+
+ return {
+ sboms: data,
+ isFetching: isLoading,
+ fetchError: error as AxiosError | null,
+ };
+};
+
+export const useUpdateWatchedSbomsMutation = (
+ onSuccess: () => void,
+ onError: (err: AxiosError, payload: WatchedSboms) => void
+) => {
+ const queryClient = useQueryClient();
+ return useMutation({
+ mutationFn: (obj) => {
+ return setUserPreferences({
+ client,
+ path: { key: WATCHED_SBOMS_KEY },
+ body: obj,
+ });
+ },
+ onSuccess: (_res, _payload) => {
+ onSuccess();
+ queryClient.invalidateQueries({
+ queryKey: [DashboardQueryKey, WATCHED_SBOMS_KEY],
+ });
+ },
+ onError: onError,
+ });
+};
diff --git a/client/src/app/queries/sboms.ts b/client/src/app/queries/sboms.ts
index 7b8d14bc..4028d7e2 100644
--- a/client/src/app/queries/sboms.ts
+++ b/client/src/app/queries/sboms.ts
@@ -46,10 +46,14 @@ export const useFetchSBOMs = (
};
};
-export const useFetchSBOMById = (id: string) => {
+export const useFetchSBOMById = (id?: string) => {
const { data, isLoading, error } = useQuery({
queryKey: [SBOMsQueryKey, id],
- queryFn: () => getSbom({ client, path: { id } }),
+ queryFn: () => {
+ return id === undefined
+ ? Promise.resolve(undefined)
+ : getSbom({ client, path: { id: id! } });
+ },
enabled: id !== undefined,
});
diff --git a/client/src/app/queries/vulnerabilities.ts b/client/src/app/queries/vulnerabilities.ts
index 16656ef9..f0cfc49b 100644
--- a/client/src/app/queries/vulnerabilities.ts
+++ b/client/src/app/queries/vulnerabilities.ts
@@ -1,7 +1,7 @@
-import { useMutation, useQueries, useQuery } from "@tanstack/react-query";
+import { useMutation, useQuery } from "@tanstack/react-query";
import { AxiosError } from "axios";
-import { HubRequestParams, VulnerabilityStatus } from "@app/api/models";
+import { HubRequestParams } from "@app/api/models";
import { client } from "@app/axios-config/apiInit";
import {
deleteVulnerability,
@@ -10,7 +10,6 @@ import {
VulnerabilityDetails,
} from "@app/client";
import { requestParamsQuery } from "@app/hooks/table-controls";
-import { useFetchSbomsAdvisory } from "./sboms";
export const VulnerabilitiesQueryKey = "vulnerabilities";
@@ -66,35 +65,3 @@ export const useDeleteVulnerabilityMutation = (
onError,
});
};
-
-export const useFetchVulnerabilitiesFromSbom = (sbomId: string) => {
- const {
- advisories,
- isFetching: isFetchingAdvisories,
- fetchError: fetchErrorAdvisories,
- } = useFetchSbomsAdvisory(sbomId);
-
- const usersMessages = useQueries({
- queries: (advisories ?? [])
- .flatMap((advisory) => {
- return (advisory.status ?? []).map((status) => {
- const result = {
- vulnerabilityId: status.vulnerability_id,
- status: status.status as VulnerabilityStatus,
- };
- return result;
- });
- })
- .map((item) => {
- return {
- queryKey: ["messages", "id"],
- queryFn: () => {
- return getVulnerability({
- client,
- path: { id: item.vulnerabilityId },
- });
- },
- };
- }),
- });
-};
From 9a439db94b6e95e571ba0ae26ee653008a45f015 Mon Sep 17 00:00:00 2001
From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com>
Date: Mon, 11 Nov 2024 16:22:03 +0100
Subject: [PATCH 06/11] Move code to sub components
---
.../home/components/MonitoringSection.tsx | 266 +++++++++++++++++
.../home/components/WatchedSbomDonutChart.tsx | 1 -
...tchedSboms.tsx => WatchedSbomsSection.tsx} | 2 +-
client/src/app/pages/home/home.tsx | 272 +-----------------
4 files changed, 272 insertions(+), 269 deletions(-)
create mode 100644 client/src/app/pages/home/components/MonitoringSection.tsx
rename client/src/app/pages/home/components/{WatchedSboms.tsx => WatchedSbomsSection.tsx} (95%)
diff --git a/client/src/app/pages/home/components/MonitoringSection.tsx b/client/src/app/pages/home/components/MonitoringSection.tsx
new file mode 100644
index 00000000..fe564d13
--- /dev/null
+++ b/client/src/app/pages/home/components/MonitoringSection.tsx
@@ -0,0 +1,266 @@
+import React from "react";
+import { Link } from "react-router-dom";
+
+import {
+ Chart,
+ ChartAxis,
+ ChartBar,
+ ChartLabel,
+ ChartLegend,
+ ChartStack,
+ ChartThemeColor,
+ ChartTooltip,
+} from "@patternfly/react-charts";
+import {
+ Card,
+ CardBody,
+ CardTitle,
+ DescriptionList,
+ DescriptionListDescription,
+ DescriptionListGroup,
+ DescriptionListTerm,
+ Grid,
+ GridItem,
+ Stack,
+ StackItem,
+ TextContent,
+} from "@patternfly/react-core";
+
+import { severityList } from "@app/api/model-utils";
+import { Severity } from "@app/client";
+import { useFetchSBOMs } from "@app/queries/sboms";
+import { useFetchVulnerabilities } from "@app/queries/vulnerabilities";
+import { formatDate } from "@app/utils/utils";
+
+interface Legend {
+ severity: Severity;
+}
+
+const LEGENDS: Legend[] = [
+ { severity: "critical" },
+ { severity: "high" },
+ { severity: "medium" },
+ { severity: "low" },
+ { severity: "none" },
+];
+
+export const MonitoringSection: React.FC = () => {
+ const {
+ result: { data: barchartSboms, total: totalSboms },
+ isFetching: isFetchingBarchartSboms,
+ fetchError: fetchErrorBarchartSboms,
+ } = useFetchSBOMs(
+ {
+ page: { pageNumber: 1, itemsPerPage: 10 },
+ sort: { field: "published", direction: "desc" },
+ },
+ true
+ );
+
+ const {
+ result: { data: vulnerabilities, total: totalVulnerabilities },
+ isFetching: isFetchingVulnerabilities,
+ fetchError: fetchErrorVulnerabilities,
+ } = useFetchVulnerabilities(
+ {
+ page: { pageNumber: 1, itemsPerPage: 10 },
+ sort: { field: "published", direction: "desc" },
+ },
+ true
+ );
+
+ return (
+
+ Your dashboard
+
+
+
+
+
+
+ Below is a summary of CVE status for your last 10 ingested
+ SBOMs. You can click on the SBOM name or CVE severity number
+ below to be taken to their respective details page.
+
+
+
+
+ {
+ const severity = severityList[legend.severity];
+ return { name: severity.name };
+ })}
+ legendPosition="bottom-left"
+ height={375}
+ name="sbom-summary-status"
+ padding={{
+ bottom: 75,
+ left: 330,
+ right: 50,
+ top: 50,
+ }}
+ themeColor={ChartThemeColor.multiOrdered}
+ width={700}
+ legendComponent={
+ {
+ const severity = severityList[legend.severity];
+ return severity.color.value;
+ })}
+ />
+ }
+ >
+ }
+ tickLabelComponent={
+ {
+ // const sbom_name = (event.target as any)
+ // .innerHTML as string | null;
+ // const sbom = props.find(
+ // (item) => item.sbom_name === sbom_name
+ // );
+ // if (sbom) {
+ // const sbomDetailsPage = `/sbom/content/${sbom.sbom_id}`;
+
+ // const wasmBindings = (window as any)
+ // .wasmBindings;
+ // if (wasmBindings) {
+ // wasmBindings.spogNavigateTo(
+ // sbomDetailsPage
+ // );
+ // } else {
+ // window.open(sbomDetailsPage);
+ // }
+ // }
+ // },
+ // }}
+ />
+ }
+ />
+
+ {
+ const severity = severityList[legend.severity];
+ return severity.color.value;
+ })}
+ >
+ {LEGENDS.map((legend) => {
+ const severity = severityList[legend.severity];
+ return (
+
+ }
+ // data={props.map((sbom) => {
+ // const severityKey = legend.severity;
+ // const count = sbom.vulnerabilities[
+ // severityKey
+ // ] as number;
+ // return {
+ // name: legend.name,
+ // x: sbom.sbom_name,
+ // y: count,
+ // label: `${legend.name}: ${count}`,
+ // };
+ // })}
+ />
+ );
+ })}
+
+
+
+
+
+
+
+
+
+
+
+
+ Last SBOM ingested
+
+
+
+
+ {formatDate(barchartSboms?.[0]?.published)}
+
+
+
+ {barchartSboms?.[0]?.name}
+
+
+
+
+
+
+
+
+
+
+ Total SBOMs
+
+ {totalSboms}
+
+
+
+
+
+
+
+
+ Last Vulnerability ingested
+
+
+
+
+ {formatDate(vulnerabilities?.[0]?.published)}
+
+
+ {vulnerabilities?.[0]?.identifier}
+
+
+
+
+
+
+
+
+
+
+ Total Vulnerabilities
+
+
+ {totalVulnerabilities}
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/client/src/app/pages/home/components/WatchedSbomDonutChart.tsx b/client/src/app/pages/home/components/WatchedSbomDonutChart.tsx
index 99a74053..497bd813 100644
--- a/client/src/app/pages/home/components/WatchedSbomDonutChart.tsx
+++ b/client/src/app/pages/home/components/WatchedSbomDonutChart.tsx
@@ -1,6 +1,5 @@
import React from "react";
-
import { SbomVulnerabilitiesDonutChart } from "@app/components/SbomVulnerabilitiesDonutChart";
import { useSbomVulnerabilities } from "@app/hooks/domain-controls/useSbomVulnerabilities";
diff --git a/client/src/app/pages/home/components/WatchedSboms.tsx b/client/src/app/pages/home/components/WatchedSbomsSection.tsx
similarity index 95%
rename from client/src/app/pages/home/components/WatchedSboms.tsx
rename to client/src/app/pages/home/components/WatchedSbomsSection.tsx
index f5c2daa3..bc88c661 100644
--- a/client/src/app/pages/home/components/WatchedSboms.tsx
+++ b/client/src/app/pages/home/components/WatchedSbomsSection.tsx
@@ -13,7 +13,7 @@ import {
import { WatchedSbomsContext } from "../watched-sboms-context";
import { WatchedSbom } from "./WatchedSbom";
-export const WatchedSboms: React.FC = () => {
+export const WatchedSbomsSection: React.FC = () => {
const { sboms, isFetching, fetchError } =
React.useContext(WatchedSbomsContext);
diff --git a/client/src/app/pages/home/home.tsx b/client/src/app/pages/home/home.tsx
index 996d53c0..1cc5f979 100644
--- a/client/src/app/pages/home/home.tsx
+++ b/client/src/app/pages/home/home.tsx
@@ -1,284 +1,22 @@
import React from "react";
-import { Link } from "react-router-dom";
-import {
- Chart,
- ChartAxis,
- ChartBar,
- ChartLabel,
- ChartLegend,
- ChartStack,
- ChartThemeColor,
- ChartTooltip,
-} from "@patternfly/react-charts";
-import {
- Card,
- CardBody,
- CardTitle,
- DescriptionList,
- DescriptionListDescription,
- DescriptionListGroup,
- DescriptionListTerm,
- Grid,
- GridItem,
- PageSection,
- Stack,
- StackItem,
- TextContent,
-} from "@patternfly/react-core";
+import { PageSection, Stack, StackItem } from "@patternfly/react-core";
-import { severityList } from "@app/api/model-utils";
-import { Severity } from "@app/client";
-import { useFetchSBOMs } from "@app/queries/sboms";
-import { useFetchVulnerabilities } from "@app/queries/vulnerabilities";
-import { formatDate } from "@app/utils/utils";
-import { WatchedSboms } from "./components/WatchedSboms";
+import { MonitoringSection } from "./components/MonitoringSection";
+import { WatchedSbomsSection } from "./components/WatchedSbomsSection";
import { WatchedSbomsProvider } from "./watched-sboms-context";
-interface Legend {
- severity: Severity;
-}
-
-const LEGENDS: Legend[] = [
- { severity: "critical" },
- { severity: "high" },
- { severity: "medium" },
- { severity: "low" },
- { severity: "none" },
-];
-
export const Home: React.FC = () => {
- const {
- result: { data: barchartSboms, total: totalSboms },
- isFetching: isFetchingBarchartSboms,
- fetchError: fetchErrorBarchartSboms,
- } = useFetchSBOMs(
- {
- page: { pageNumber: 1, itemsPerPage: 10 },
- sort: { field: "published", direction: "desc" },
- },
- true
- );
-
- const {
- result: { data: vulnerabilities, total: totalVulnerabilities },
- isFetching: isFetchingVulnerabilities,
- fetchError: fetchErrorVulnerabilities,
- } = useFetchVulnerabilities(
- {
- page: { pageNumber: 1, itemsPerPage: 10 },
- sort: { field: "published", direction: "desc" },
- },
- true
- );
-
return (
<>
-
- Your dashboard
-
-
-
-
-
-
- Below is a summary of CVE status for your last 10
- ingested SBOMs. You can click on the SBOM name or CVE
- severity number below to be taken to their respective
- details page.
-
-
-
-
- {
- const severity = severityList[legend.severity];
- return { name: severity.name };
- })}
- legendPosition="bottom-left"
- height={375}
- name="sbom-summary-status"
- padding={{
- bottom: 75,
- left: 330,
- right: 50,
- top: 50,
- }}
- themeColor={ChartThemeColor.multiOrdered}
- width={700}
- legendComponent={
- {
- const severity =
- severityList[legend.severity];
- return severity.color.value;
- })}
- />
- }
- >
-
- }
- tickLabelComponent={
- {
- // const sbom_name = (event.target as any)
- // .innerHTML as string | null;
- // const sbom = props.find(
- // (item) => item.sbom_name === sbom_name
- // );
- // if (sbom) {
- // const sbomDetailsPage = `/sbom/content/${sbom.sbom_id}`;
-
- // const wasmBindings = (window as any)
- // .wasmBindings;
- // if (wasmBindings) {
- // wasmBindings.spogNavigateTo(
- // sbomDetailsPage
- // );
- // } else {
- // window.open(sbomDetailsPage);
- // }
- // }
- // },
- // }}
- />
- }
- />
-
- {
- const severity = severityList[legend.severity];
- return severity.color.value;
- })}
- >
- {LEGENDS.map((legend) => {
- const severity = severityList[legend.severity];
- return (
-
- }
- // data={props.map((sbom) => {
- // const severityKey = legend.severity;
- // const count = sbom.vulnerabilities[
- // severityKey
- // ] as number;
- // return {
- // name: legend.name,
- // x: sbom.sbom_name,
- // y: count,
- // label: `${legend.name}: ${count}`,
- // };
- // })}
- />
- );
- })}
-
-
-
-
-
-
-
-
-
-
-
-
- Last SBOM ingested
-
-
-
-
- {formatDate(barchartSboms?.[0]?.published)}
-
-
-
- {barchartSboms?.[0]?.name}
-
-
-
-
-
-
-
-
-
-
-
- Total SBOMs
-
-
- {totalSboms}
-
-
-
-
-
-
-
-
- Last Vulnerability ingested
-
-
-
-
- {formatDate(vulnerabilities?.[0]?.published)}
-
-
- {vulnerabilities?.[0]?.identifier}
-
-
-
-
-
-
-
-
-
-
- Total Vulnerabilities
-
-
- {totalVulnerabilities}
-
-
-
-
-
-
-
-
-
+
-
+
From 39815241c0d769452d25437760364e3c807d680d Mon Sep 17 00:00:00 2001
From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com>
Date: Thu, 14 Nov 2024 16:00:55 +0100
Subject: [PATCH 07/11] fix imports
---
client/src/app/components/SbomVulnerabilitiesDonutChart.tsx | 4 ++--
.../src/app/pages/home/components/WatchedSbomDonutChart.tsx | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/client/src/app/components/SbomVulnerabilitiesDonutChart.tsx b/client/src/app/components/SbomVulnerabilitiesDonutChart.tsx
index 1f317e64..72322d9a 100644
--- a/client/src/app/components/SbomVulnerabilitiesDonutChart.tsx
+++ b/client/src/app/components/SbomVulnerabilitiesDonutChart.tsx
@@ -4,10 +4,10 @@ import { ChartDonut } from "@patternfly/react-charts";
import { compareBySeverityFn, severityList } from "@app/api/model-utils";
import { Severity } from "@app/client";
-import { SbomVulnerabilitySummary } from "@app/hooks/domain-controls/useSbomVulnerabilities";
+import { VulnerabilityOfSbomSummary } from "@app/hooks/domain-controls/useVulnerabilitiesOfSbom";
export interface SbomVulnerabilitiesDonutChartProps {
- vulnerabilitiesSummary: SbomVulnerabilitySummary;
+ vulnerabilitiesSummary: VulnerabilityOfSbomSummary;
}
export const SbomVulnerabilitiesDonutChart: React.FC<
diff --git a/client/src/app/pages/home/components/WatchedSbomDonutChart.tsx b/client/src/app/pages/home/components/WatchedSbomDonutChart.tsx
index 497bd813..3d591ce5 100644
--- a/client/src/app/pages/home/components/WatchedSbomDonutChart.tsx
+++ b/client/src/app/pages/home/components/WatchedSbomDonutChart.tsx
@@ -1,7 +1,7 @@
import React from "react";
import { SbomVulnerabilitiesDonutChart } from "@app/components/SbomVulnerabilitiesDonutChart";
-import { useSbomVulnerabilities } from "@app/hooks/domain-controls/useSbomVulnerabilities";
+import { useVulnerabilitiesOfSbom } from "@app/hooks/domain-controls/useVulnerabilitiesOfSbom";
interface WatchedSbomDonutChartProps {
sbomId: string;
@@ -15,7 +15,7 @@ export const WatchedSbomDonutChart: React.FC = ({
summary: vulnerabilitiesSummary,
isFetching: isFetchingVulnerabilities,
fetchError: fetchErrorVulnerabilities,
- } = useSbomVulnerabilities(sbomId);
+ } = useVulnerabilitiesOfSbom(sbomId);
return (
Date: Thu, 21 Nov 2024 11:07:00 +0100
Subject: [PATCH 08/11] Based on
https://github.com/trustification/trustify/pull/1005
---
client/openapi/trustd.yaml | 27 +--
client/package.json | 4 +-
client/src/app/client/schemas.gen.ts | 45 ++---
client/src/app/client/types.gen.ts | 8 +-
.../useVulnerabilitiesOfSbom.ts | 164 ++++++++++--------
.../home/components/MonitoringSection.tsx | 28 +--
.../src/app/pages/sbom-details/overview.tsx | 4 +-
.../sbom-details/vulnerabilities-by-sbom.tsx | 6 +-
client/src/app/queries/organizations.ts | 48 -----
client/src/app/queries/products.ts | 68 --------
client/src/app/queries/sboms.ts | 33 +++-
package-lock.json | 26 +--
12 files changed, 184 insertions(+), 277 deletions(-)
delete mode 100644 client/src/app/queries/organizations.ts
delete mode 100644 client/src/app/queries/products.ts
diff --git a/client/openapi/trustd.yaml b/client/openapi/trustd.yaml
index 027059c1..b14f2edd 100644
--- a/client/openapi/trustd.yaml
+++ b/client/openapi/trustd.yaml
@@ -2245,7 +2245,6 @@ components:
- node_id
- purl
- name
- - version
properties:
name:
type: string
@@ -2255,8 +2254,6 @@ components:
type: string
sbom_id:
type: string
- version:
- type: string
AncestorSummary:
type: object
required:
@@ -2264,7 +2261,6 @@ components:
- node_id
- purl
- name
- - version
- published
- document_id
- product_name
@@ -2291,8 +2287,6 @@ components:
type: string
sbom_id:
type: string
- version:
- type: string
BasePurlDetails:
allOf:
- $ref: '#/components/schemas/BasePurlHead'
@@ -2457,10 +2451,8 @@ components:
required:
- sbom_id
- node_id
- - relationship
- purl
- name
- - version
- deps
properties:
deps:
@@ -2473,12 +2465,8 @@ components:
type: string
purl:
type: string
- relationship:
- type: string
sbom_id:
type: string
- version:
- type: string
DepSummary:
type: object
required:
@@ -2486,7 +2474,6 @@ components:
- node_id
- purl
- name
- - version
- published
- document_id
- product_name
@@ -2513,8 +2500,6 @@ components:
type: string
sbom_id:
type: string
- version:
- type: string
Id:
type: string
description: A hash/digest prefixed with its type.
@@ -3517,7 +3502,7 @@ components:
SbomStatus:
type: object
required:
- - vulnerability_id
+ - vulnerability
- status
- packages
properties:
@@ -3529,10 +3514,14 @@ components:
type: array
items:
$ref: '#/components/schemas/SbomPackage'
+ severity:
+ oneOf:
+ - type: 'null'
+ - $ref: '#/components/schemas/Severity'
status:
type: string
- vulnerability_id:
- type: string
+ vulnerability:
+ $ref: '#/components/schemas/VulnerabilityHead'
SbomSummary:
allOf:
- $ref: '#/components/schemas/SbomHead'
@@ -3931,4 +3920,4 @@ components:
oneOf:
- type: 'null'
- $ref: '#/components/schemas/Severity'
- description: Average (arithmetic mean) severity of the vulnerability aggregated from *all* related advisories.
+ description: Average (arithmetic mean) severity of the vulnerability aggregated from *all* related advisories.
\ No newline at end of file
diff --git a/client/package.json b/client/package.json
index bf8af438..e160ca7f 100644
--- a/client/package.json
+++ b/client/package.json
@@ -30,8 +30,8 @@
"@patternfly/react-table": "^5.4.0",
"@patternfly/react-tokens": "^5.4.0",
"@segment/analytics-next": "^1.64.0",
- "@tanstack/react-query": "^5.50.1",
- "@tanstack/react-query-devtools": "^5.50.1",
+ "@tanstack/react-query": "^5.61.0",
+ "@tanstack/react-query-devtools": "^5.61.0",
"axios": "^1.7.2",
"dayjs": "^1.11.7",
"ejs": "^3.1.10",
diff --git a/client/src/app/client/schemas.gen.ts b/client/src/app/client/schemas.gen.ts
index 3f53d01f..1813f0a7 100644
--- a/client/src/app/client/schemas.gen.ts
+++ b/client/src/app/client/schemas.gen.ts
@@ -276,7 +276,7 @@ export const AnalysisStatusSchema = {
export const AncNodeSchema = {
type: "object",
- required: ["sbom_id", "node_id", "purl", "name", "version"],
+ required: ["sbom_id", "node_id", "purl", "name"],
properties: {
name: {
type: "string",
@@ -290,9 +290,6 @@ export const AncNodeSchema = {
sbom_id: {
type: "string",
},
- version: {
- type: "string",
- },
},
} as const;
@@ -303,7 +300,6 @@ export const AncestorSummarySchema = {
"node_id",
"purl",
"name",
- "version",
"published",
"document_id",
"product_name",
@@ -341,9 +337,6 @@ export const AncestorSummarySchema = {
sbom_id: {
type: "string",
},
- version: {
- type: "string",
- },
},
} as const;
@@ -594,15 +587,7 @@ export const CweImporterSchema = {
export const DepNodeSchema = {
type: "object",
- required: [
- "sbom_id",
- "node_id",
- "relationship",
- "purl",
- "name",
- "version",
- "deps",
- ],
+ required: ["sbom_id", "node_id", "purl", "name", "deps"],
properties: {
deps: {
type: "array",
@@ -619,15 +604,9 @@ export const DepNodeSchema = {
purl: {
type: "string",
},
- relationship: {
- type: "string",
- },
sbom_id: {
type: "string",
},
- version: {
- type: "string",
- },
},
} as const;
@@ -638,7 +617,6 @@ export const DepSummarySchema = {
"node_id",
"purl",
"name",
- "version",
"published",
"document_id",
"product_name",
@@ -676,9 +654,6 @@ export const DepSummarySchema = {
sbom_id: {
type: "string",
},
- version: {
- type: "string",
- },
},
} as const;
@@ -2078,7 +2053,7 @@ export const SbomPackageRelationSchema = {
export const SbomStatusSchema = {
type: "object",
- required: ["vulnerability_id", "status", "packages"],
+ required: ["vulnerability", "status", "packages"],
properties: {
context: {
oneOf: [
@@ -2096,11 +2071,21 @@ export const SbomStatusSchema = {
$ref: "#/components/schemas/SbomPackage",
},
},
+ severity: {
+ oneOf: [
+ {
+ type: "null",
+ },
+ {
+ $ref: "#/components/schemas/Severity",
+ },
+ ],
+ },
status: {
type: "string",
},
- vulnerability_id: {
- type: "string",
+ vulnerability: {
+ $ref: "#/components/schemas/VulnerabilityHead",
},
},
} as const;
diff --git a/client/src/app/client/types.gen.ts b/client/src/app/client/types.gen.ts
index 0eaa7805..1f48ea13 100644
--- a/client/src/app/client/types.gen.ts
+++ b/client/src/app/client/types.gen.ts
@@ -120,7 +120,6 @@ export type AncNode = {
node_id: string;
purl: string;
sbom_id: string;
- version: string;
};
export type AncestorSummary = {
@@ -133,7 +132,6 @@ export type AncestorSummary = {
published: string;
purl: string;
sbom_id: string;
- version: string;
};
export type BasePurlDetails = BasePurlHead & {
@@ -232,9 +230,7 @@ export type DepNode = {
name: string;
node_id: string;
purl: string;
- relationship: string;
sbom_id: string;
- version: string;
};
export type DepSummary = {
@@ -247,7 +243,6 @@ export type DepSummary = {
published: string;
purl: string;
sbom_id: string;
- version: string;
};
/**
@@ -770,8 +765,9 @@ export type SbomPackageRelation = {
export type SbomStatus = {
context?: null | StatusContext;
packages: Array;
+ severity?: null | Severity;
status: string;
- vulnerability_id: string;
+ vulnerability: VulnerabilityHead;
};
export type SbomSummary = SbomHead &
diff --git a/client/src/app/hooks/domain-controls/useVulnerabilitiesOfSbom.ts b/client/src/app/hooks/domain-controls/useVulnerabilitiesOfSbom.ts
index 51b1f5d6..ce151b37 100644
--- a/client/src/app/hooks/domain-controls/useVulnerabilitiesOfSbom.ts
+++ b/client/src/app/hooks/domain-controls/useVulnerabilitiesOfSbom.ts
@@ -1,22 +1,24 @@
import React from "react";
import { VulnerabilityStatus } from "@app/api/models";
-import { client } from "@app/axios-config/apiInit";
import {
- getVulnerability,
SbomAdvisory,
SbomPackage,
Severity,
- VulnerabilityDetails,
+ VulnerabilityHead,
} from "@app/client";
-import { useFetchSbomsAdvisory } from "@app/queries/sboms";
+import {
+ useFetchSbomsAdvisory,
+ useFetchSbomsAdvisory2,
+} from "@app/queries/sboms";
interface VulnerabilityOfSbom {
- vulnerabilityId: string;
advisory: SbomAdvisory;
+ vulnerabilityId: string;
+ vulnerability: VulnerabilityHead;
+ severity?: Severity | null;
status: VulnerabilityStatus;
packages: SbomPackage[];
- vulnerability?: VulnerabilityDetails;
}
export interface VulnerabilityOfSbomSummary {
@@ -36,28 +38,17 @@ export const useVulnerabilitiesOfSbom = (sbomId: string) => {
fetchError: fetchErrorAdvisories,
} = useFetchSbomsAdvisory(sbomId);
- const [allVulnerabilities, setAllVulnerabilities] = React.useState<
- VulnerabilityOfSbom[]
- >([]);
- const [vulnerabilitiesById, setVulnerabilitiesById] = React.useState<
- Map
- >(new Map());
- const [isFetchingVulnerabilities, setIsFetchingVulnerabilities] =
- React.useState(false);
-
- React.useEffect(() => {
- if (advisories.length === 0) {
- return;
- }
-
+ const allVulnerabilities = React.useMemo(() => {
const vulnerabilities = (advisories ?? [])
.flatMap((advisory) => {
return (advisory.status ?? []).map((status) => {
const result: VulnerabilityOfSbom = {
- vulnerabilityId: status.vulnerability_id,
+ advisory: advisory,
+ vulnerabilityId: status.vulnerability.identifier,
+ vulnerability: status.vulnerability,
+ severity: status.severity,
status: status.status as VulnerabilityStatus,
packages: status.packages || [],
- advisory: { ...advisory },
};
return result;
});
@@ -68,7 +59,8 @@ export const useVulnerabilitiesOfSbom = (sbomId: string) => {
.reduce((prev, current) => {
const exists = prev.find(
(item) =>
- item.vulnerabilityId === current.vulnerabilityId &&
+ item.vulnerability.identifier ===
+ current.vulnerability.identifier &&
item.advisory.uuid === current.advisory.uuid
);
if (!exists) {
@@ -78,55 +70,15 @@ export const useVulnerabilitiesOfSbom = (sbomId: string) => {
}
}, [] as VulnerabilityOfSbom[]);
- setAllVulnerabilities(vulnerabilities);
- setIsFetchingVulnerabilities(true);
-
- Promise.all(
- vulnerabilities
- .map(async (item) => {
- const response = await getVulnerability({
- client,
- path: { id: item.vulnerabilityId },
- });
- return response.data;
- })
- .map((vulnerability) => vulnerability.catch(() => null))
- ).then((vulnerabilities) => {
- const validVulnerabilities = vulnerabilities.reduce((prev, current) => {
- if (current) {
- return [...prev, current];
- } else {
- // Filter out error responses
- return prev;
- }
- }, [] as VulnerabilityDetails[]);
-
- const vulnerabilitiesById = new Map();
- validVulnerabilities.forEach((vulnerability) => {
- vulnerabilitiesById.set(vulnerability.identifier, vulnerability);
- });
-
- setVulnerabilitiesById(vulnerabilitiesById);
- setIsFetchingVulnerabilities(false);
- });
+ return vulnerabilities;
}, [advisories]);
- const allVulnerabilitiesWithMappedData = React.useMemo(() => {
- return allVulnerabilities.map((item) => {
- const result: VulnerabilityOfSbom = {
- ...item,
- vulnerability: vulnerabilitiesById.get(item.vulnerabilityId),
- };
- return result;
- });
- }, [allVulnerabilities, vulnerabilitiesById]);
-
// Summary
const vulnerabilitiesSummary = React.useMemo(() => {
- return allVulnerabilitiesWithMappedData.reduce((prev, current) => {
- if (current.vulnerability?.average_severity) {
- const severity = current.vulnerability?.average_severity;
+ return allVulnerabilities.reduce((prev, current) => {
+ if (current.severity) {
+ const severity = current.severity;
return {
...prev,
total: prev.total + 1,
@@ -139,12 +91,84 @@ export const useVulnerabilitiesOfSbom = (sbomId: string) => {
return prev;
}
}, DEFAULT_SUMMARY);
- }, [allVulnerabilitiesWithMappedData]);
+ }, [allVulnerabilities]);
return {
- isFetching: isFetchingAdvisories || isFetchingVulnerabilities,
+ isFetching: isFetchingAdvisories,
fetchError: fetchErrorAdvisories,
- vulnerabilities: allVulnerabilitiesWithMappedData,
+ vulnerabilities: allVulnerabilities,
+ summary: vulnerabilitiesSummary,
+ };
+};
+
+export const useVulnerabilitiesOfSboms = (sbomIds: string[]) => {
+ const { advisories, isFetching, fetchError } =
+ useFetchSbomsAdvisory2(sbomIds);
+
+ const allVulnerabilities = React.useMemo(() => {
+ const vulnerabilities = (advisories ?? []).map((advisories) => {
+ return (
+ advisories
+ .flatMap((advisory) => {
+ return (advisory.status ?? []).map((status) => {
+ const result: VulnerabilityOfSbom = {
+ advisory: advisory,
+ vulnerabilityId: status.vulnerability.identifier,
+ vulnerability: status.vulnerability,
+ severity: status.severity,
+ status: status.status as VulnerabilityStatus,
+ packages: status.packages || [],
+ };
+ return result;
+ });
+ })
+ // Take only "affected"
+ .filter((item) => item.status === "affected")
+ // Remove duplicates if exists
+ .reduce((prev, current) => {
+ const exists = prev.find(
+ (item) =>
+ item.vulnerability.identifier ===
+ current.vulnerability.identifier &&
+ item.advisory.uuid === current.advisory.uuid
+ );
+ if (!exists) {
+ return [...prev, current];
+ } else {
+ return prev;
+ }
+ }, [] as VulnerabilityOfSbom[])
+ );
+ });
+ return vulnerabilities;
+ }, [advisories]);
+
+ // Summary
+
+ const vulnerabilitiesSummary = React.useMemo(() => {
+ return allVulnerabilities.map((allVulnerabilities) => {
+ return allVulnerabilities.reduce((prev, current) => {
+ if (current.severity) {
+ const severity = current.severity;
+ return {
+ ...prev,
+ total: prev.total + 1,
+ severities: {
+ ...prev.severities,
+ [severity]: prev.severities[severity] + 1,
+ },
+ };
+ } else {
+ return prev;
+ }
+ }, DEFAULT_SUMMARY);
+ });
+ }, [allVulnerabilities]);
+
+ return {
+ isFetching: isFetching,
+ fetchError: fetchError,
+ vulnerabilities: allVulnerabilities,
summary: vulnerabilitiesSummary,
};
};
diff --git a/client/src/app/pages/home/components/MonitoringSection.tsx b/client/src/app/pages/home/components/MonitoringSection.tsx
index fe564d13..1a323909 100644
--- a/client/src/app/pages/home/components/MonitoringSection.tsx
+++ b/client/src/app/pages/home/components/MonitoringSection.tsx
@@ -28,6 +28,7 @@ import {
import { severityList } from "@app/api/model-utils";
import { Severity } from "@app/client";
+import { useVulnerabilitiesOfSboms } from "@app/hooks/domain-controls/useVulnerabilitiesOfSbom";
import { useFetchSBOMs } from "@app/queries/sboms";
import { useFetchVulnerabilities } from "@app/queries/vulnerabilities";
import { formatDate } from "@app/utils/utils";
@@ -69,6 +70,10 @@ export const MonitoringSection: React.FC = () => {
true
);
+ const { summary, isFetching, fetchError } = useVulnerabilitiesOfSboms(
+ barchartSboms.map((e) => e.id)
+ );
+
return (
Your dashboard
@@ -172,18 +177,17 @@ export const MonitoringSection: React.FC = () => {
labelComponent={
}
- // data={props.map((sbom) => {
- // const severityKey = legend.severity;
- // const count = sbom.vulnerabilities[
- // severityKey
- // ] as number;
- // return {
- // name: legend.name,
- // x: sbom.sbom_name,
- // y: count,
- // label: `${legend.name}: ${count}`,
- // };
- // })}
+ data={summary.map((item, index) => {
+ const sbom = barchartSboms[index];
+ const severityKey = legend.severity;
+ const count = item.severities[severityKey];
+ return {
+ name: severityKey,
+ x: sbom.name,
+ y: count,
+ label: `${severity.name}: ${count}`,
+ };
+ })}
/>
);
})}
diff --git a/client/src/app/pages/sbom-details/overview.tsx b/client/src/app/pages/sbom-details/overview.tsx
index 5f93b278..b073faf3 100644
--- a/client/src/app/pages/sbom-details/overview.tsx
+++ b/client/src/app/pages/sbom-details/overview.tsx
@@ -118,8 +118,8 @@ export const Overview: React.FC = ({ sbom }) => {
{sbom.described_by
.flatMap((e) => e.cpe)
- .map((e) => (
- {e}
+ .map((e, index) => (
+ {e}
))}
{sbom.described_by
diff --git a/client/src/app/pages/sbom-details/vulnerabilities-by-sbom.tsx b/client/src/app/pages/sbom-details/vulnerabilities-by-sbom.tsx
index ca4720f7..a13de9ab 100644
--- a/client/src/app/pages/sbom-details/vulnerabilities-by-sbom.tsx
+++ b/client/src/app/pages/sbom-details/vulnerabilities-by-sbom.tsx
@@ -211,10 +211,8 @@ export const VulnerabilitiesBySbom: React.FC = ({
)}
- {item.vulnerability?.average_severity && (
-
+ {item.severity && (
+
)}
{
- const { data, isLoading, error, refetch } = useQuery({
- queryKey: [OrganizationsQueryKey, params],
- queryFn: () => {
- return listOrganizations({
- client,
- query: { ...requestParamsQuery(params) },
- });
- },
- refetchInterval: !refetchDisabled ? 5000 : false,
- });
- return {
- result: {
- data: data?.data?.items || [],
- total: data?.data?.total ?? 0,
- params: params,
- },
- isFetching: isLoading,
- fetchError: error,
- refetch,
- };
-};
-
-export const useFetchOrganizationById = (id: string) => {
- const { data, isLoading, error } = useQuery({
- queryKey: [OrganizationsQueryKey, id],
- queryFn: () => getOrganization({ client, path: { id } }),
- });
- return {
- organization: data?.data,
- isFetching: isLoading,
- fetchError: error as AxiosError,
- };
-};
diff --git a/client/src/app/queries/products.ts b/client/src/app/queries/products.ts
deleted file mode 100644
index 66e8cc99..00000000
--- a/client/src/app/queries/products.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import { useMutation, useQuery } from "@tanstack/react-query";
-import { AxiosError } from "axios";
-
-import { HubRequestParams } from "@app/api/models";
-import { client } from "@app/axios-config/apiInit";
-import {
- deleteProduct,
- getProduct,
- listProducts,
- ProductDetails,
-} from "@app/client";
-
-import { requestParamsQuery } from "../hooks/table-controls";
-
-export const ProductsQueryKey = "products";
-
-export const useFetchProducts = (
- params: HubRequestParams = {},
- refetchDisabled: boolean = false
-) => {
- const { data, isLoading, error, refetch } = useQuery({
- queryKey: [ProductsQueryKey, params],
- queryFn: () => {
- return listProducts({
- client,
- query: { ...requestParamsQuery(params) },
- });
- },
- refetchInterval: !refetchDisabled ? 5000 : false,
- });
- return {
- result: {
- data: data?.data?.items || [],
- total: data?.data?.total ?? 0,
- params: params,
- },
- isFetching: isLoading,
- fetchError: error,
- refetch,
- };
-};
-
-export const useFetchProductById = (id: string) => {
- const { data, isLoading, error } = useQuery({
- queryKey: [ProductsQueryKey, id],
- queryFn: () => getProduct({ client, path: { id } }),
- });
- return {
- product: data?.data,
- isFetching: isLoading,
- fetchError: error as AxiosError,
- };
-};
-
-export const useDeleteProductMutation = (
- onError?: (err: AxiosError, id: string) => void,
- onSuccess?: (payload: ProductDetails, id: string) => void
-) => {
- return useMutation({
- mutationFn: async (id: string) => {
- const response = await deleteProduct({ client, path: { id } });
- return response.data as ProductDetails;
- },
- mutationKey: [ProductsQueryKey],
- onSuccess,
- onError,
- });
-};
diff --git a/client/src/app/queries/sboms.ts b/client/src/app/queries/sboms.ts
index 4028d7e2..326a731e 100644
--- a/client/src/app/queries/sboms.ts
+++ b/client/src/app/queries/sboms.ts
@@ -1,4 +1,9 @@
-import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
+import {
+ useMutation,
+ useQueries,
+ useQuery,
+ useQueryClient,
+} from "@tanstack/react-query";
import { AxiosError } from "axios";
import { HubRequestParams } from "@app/api/models";
@@ -137,7 +142,7 @@ export const useFetchSbomsByPackageId = (
params: HubRequestParams = {}
) => {
const { data, isLoading, error, refetch } = useQuery({
- queryKey: [SBOMsQueryKey, "by-package", packageId, params],
+ queryKey: ["SBOMsQueryKeysss", "by-package", packageId, params],
queryFn: () => {
return listRelatedSboms({
client,
@@ -171,6 +176,28 @@ export const useFetchSbomsAdvisory = (sbomId: string) => {
return {
advisories: data?.data || [],
isFetching: isLoading,
- fetchError: error,
+ fetchError: error as AxiosError | null,
+ };
+};
+
+export const useFetchSbomsAdvisory2 = (sbomIds: string[]) => {
+ const userQueries = useQueries({
+ queries: sbomIds.map((sbomId) => {
+ return {
+ queryKey: [SBOMsQueryKey, sbomId, "advisory"],
+ queryFn: () => {
+ return getSbomAdvisories({
+ client,
+ path: { id: sbomId },
+ });
+ },
+ };
+ }),
+ });
+
+ return {
+ advisories: userQueries.map(({ data }) => data?.data || []),
+ isFetching: userQueries.some(({ isLoading }) => isLoading),
+ fetchError: userQueries.map(({ error }) => error as AxiosError | null),
};
};
diff --git a/package-lock.json b/package-lock.json
index b9cb8d8a..6ff9aa26 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -82,8 +82,8 @@
"@patternfly/react-table": "^5.4.0",
"@patternfly/react-tokens": "^5.4.0",
"@segment/analytics-next": "^1.64.0",
- "@tanstack/react-query": "^5.50.1",
- "@tanstack/react-query-devtools": "^5.50.1",
+ "@tanstack/react-query": "^5.61.0",
+ "@tanstack/react-query-devtools": "^5.61.0",
"axios": "^1.7.2",
"dayjs": "^1.11.7",
"ejs": "^3.1.10",
@@ -3986,9 +3986,9 @@
}
},
"node_modules/@tanstack/query-core": {
- "version": "5.59.20",
- "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.59.20.tgz",
- "integrity": "sha512-e8vw0lf7KwfGe1if4uPFhvZRWULqHjFcz3K8AebtieXvnMOz5FSzlZe3mTLlPuUBcydCnBRqYs2YJ5ys68wwLg==",
+ "version": "5.60.6",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.60.6.tgz",
+ "integrity": "sha512-tI+k0KyCo1EBJ54vxK1kY24LWj673ujTydCZmzEZKAew4NqZzTaVQJEuaG1qKj2M03kUHN46rchLRd+TxVq/zQ==",
"license": "MIT",
"funding": {
"type": "github",
@@ -4006,12 +4006,12 @@
}
},
"node_modules/@tanstack/react-query": {
- "version": "5.59.20",
- "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.59.20.tgz",
- "integrity": "sha512-Zly0egsK0tFdfSbh5/mapSa+Zfc3Et0Zkar7Wo5sQkFzWyB3p3uZWOHR2wrlAEEV2L953eLuDBtbgFvMYiLvUw==",
+ "version": "5.61.0",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.61.0.tgz",
+ "integrity": "sha512-SBzV27XAeCRBOQ8QcC94w2H1Md0+LI0gTWwc3qRJoaGuewKn5FNW4LSqwPFJZVEItfhMfGT7RpZuSFXjTi12pQ==",
"license": "MIT",
"dependencies": {
- "@tanstack/query-core": "5.59.20"
+ "@tanstack/query-core": "5.60.6"
},
"funding": {
"type": "github",
@@ -4022,9 +4022,9 @@
}
},
"node_modules/@tanstack/react-query-devtools": {
- "version": "5.59.20",
- "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.59.20.tgz",
- "integrity": "sha512-AL/eQS1NFZhwwzq2Bq9Gd8wTTH+XhPNOJlDFpzPMu9NC5CQVgA0J8lWrte/sXpdWNo5KA4hgHnEdImZsF4h6Lw==",
+ "version": "5.61.0",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.61.0.tgz",
+ "integrity": "sha512-hd3yXl+KV+OGQmAw946qHAFp6DygcXcYN+1ai9idYddx6uEQyCwYk3jyIBOQEUw9uzN5DOGJLBsgd/QcimDQsA==",
"license": "MIT",
"dependencies": {
"@tanstack/query-devtools": "5.59.20"
@@ -4034,7 +4034,7 @@
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
- "@tanstack/react-query": "^5.59.20",
+ "@tanstack/react-query": "^5.61.0",
"react": "^18 || ^19"
}
},
From 35b94996a51f83a6acfeba1501e802c4fe0e686e Mon Sep 17 00:00:00 2001
From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com>
Date: Fri, 22 Nov 2024 13:15:04 +0100
Subject: [PATCH 09/11] Add dashboard code and match latest backend pr
---
client/openapi/trustd.yaml | 81 ++--
client/src/app/client/schemas.gen.ts | 115 +++--
client/src/app/client/types.gen.ts | 26 +-
.../SbomVulnerabilitiesDonutChart.tsx | 4 +-
.../useVulnerabilitiesOfSbom.ts | 200 ++++-----
.../home/components/MonitoringSection.tsx | 401 ++++++++++--------
.../app/pages/home/components/WatchedSbom.tsx | 6 +-
.../home/components/WatchedSbomDonutChart.tsx | 16 +-
.../app/pages/home/watched-sboms-context.tsx | 11 +-
.../sbom-details/vulnerabilities-by-sbom.tsx | 22 +-
.../components/SbomVulnerabilities.tsx | 6 +-
.../advisories-by-vulnerability.stories.tsx | 35 +-
client/src/app/queries/sboms.ts | 2 +-
13 files changed, 459 insertions(+), 466 deletions(-)
diff --git a/client/openapi/trustd.yaml b/client/openapi/trustd.yaml
index b14f2edd..f4c3ede1 100644
--- a/client/openapi/trustd.yaml
+++ b/client/openapi/trustd.yaml
@@ -5,7 +5,7 @@ info:
license:
name: Apache License, Version 2.0
identifier: Apache-2.0
- version: 0.1.0-alpha.23
+ version: 0.1.0-alpha.24
paths:
/.well-known/trustify:
get:
@@ -2243,8 +2243,10 @@ components:
required:
- sbom_id
- node_id
+ - relationship
- purl
- name
+ - version
properties:
name:
type: string
@@ -2252,8 +2254,12 @@ components:
type: string
purl:
type: string
+ relationship:
+ type: string
sbom_id:
type: string
+ version:
+ type: string
AncestorSummary:
type: object
required:
@@ -2261,6 +2267,7 @@ components:
- node_id
- purl
- name
+ - version
- published
- document_id
- product_name
@@ -2287,6 +2294,8 @@ components:
type: string
sbom_id:
type: string
+ version:
+ type: string
BasePurlDetails:
allOf:
- $ref: '#/components/schemas/BasePurlHead'
@@ -2451,8 +2460,10 @@ components:
required:
- sbom_id
- node_id
+ - relationship
- purl
- name
+ - version
- deps
properties:
deps:
@@ -2465,8 +2476,12 @@ components:
type: string
purl:
type: string
+ relationship:
+ type: string
sbom_id:
type: string
+ version:
+ type: string
DepSummary:
type: object
required:
@@ -2474,6 +2489,7 @@ components:
- node_id
- purl
- name
+ - version
- published
- document_id
- product_name
@@ -2500,6 +2516,8 @@ components:
type: string
sbom_id:
type: string
+ version:
+ type: string
Id:
type: string
description: A hash/digest prefixed with its type.
@@ -3019,17 +3037,11 @@ components:
- type: object
required:
- described_by
- - number_of_packages
properties:
described_by:
type: array
items:
$ref: '#/components/schemas/SbomPackage'
- number_of_packages:
- type: integer
- format: int64
- description: The number of packages this SBOM has
- minimum: 0
total:
type: integer
format: int64
@@ -3329,6 +3341,7 @@ components:
- dev_tool_of
- described_by
- package_of
+ - undefined
Report:
type: object
required:
@@ -3412,6 +3425,7 @@ components:
- published
- authors
- name
+ - number_of_packages
properties:
authors:
type: array
@@ -3429,6 +3443,11 @@ components:
$ref: '#/components/schemas/Labels'
name:
type: string
+ number_of_packages:
+ type: integer
+ format: int64
+ description: The number of packages this SBOM has
+ minimum: 0
published:
type:
- string
@@ -3500,28 +3519,26 @@ components:
relationship:
$ref: '#/components/schemas/Relationship'
SbomStatus:
- type: object
- required:
- - vulnerability
- - status
- - packages
- properties:
- context:
- oneOf:
- - type: 'null'
- - $ref: '#/components/schemas/StatusContext'
- packages:
- type: array
- items:
- $ref: '#/components/schemas/SbomPackage'
- severity:
- oneOf:
- - type: 'null'
- - $ref: '#/components/schemas/Severity'
- status:
- type: string
- vulnerability:
- $ref: '#/components/schemas/VulnerabilityHead'
+ allOf:
+ - $ref: '#/components/schemas/VulnerabilityHead'
+ - type: object
+ required:
+ - average_severity
+ - status
+ - packages
+ properties:
+ average_severity:
+ $ref: '#/components/schemas/Severity'
+ context:
+ oneOf:
+ - type: 'null'
+ - $ref: '#/components/schemas/StatusContext'
+ packages:
+ type: array
+ items:
+ $ref: '#/components/schemas/SbomPackage'
+ status:
+ type: string
SbomSummary:
allOf:
- $ref: '#/components/schemas/SbomHead'
@@ -3531,17 +3548,11 @@ components:
- type: object
required:
- described_by
- - number_of_packages
properties:
described_by:
type: array
items:
$ref: '#/components/schemas/SbomPackage'
- number_of_packages:
- type: integer
- format: int64
- description: The number of packages this SBOM has
- minimum: 0
Severity:
type: string
description: |-
diff --git a/client/src/app/client/schemas.gen.ts b/client/src/app/client/schemas.gen.ts
index 1813f0a7..c34110de 100644
--- a/client/src/app/client/schemas.gen.ts
+++ b/client/src/app/client/schemas.gen.ts
@@ -276,7 +276,7 @@ export const AnalysisStatusSchema = {
export const AncNodeSchema = {
type: "object",
- required: ["sbom_id", "node_id", "purl", "name"],
+ required: ["sbom_id", "node_id", "relationship", "purl", "name", "version"],
properties: {
name: {
type: "string",
@@ -287,9 +287,15 @@ export const AncNodeSchema = {
purl: {
type: "string",
},
+ relationship: {
+ type: "string",
+ },
sbom_id: {
type: "string",
},
+ version: {
+ type: "string",
+ },
},
} as const;
@@ -300,6 +306,7 @@ export const AncestorSummarySchema = {
"node_id",
"purl",
"name",
+ "version",
"published",
"document_id",
"product_name",
@@ -337,6 +344,9 @@ export const AncestorSummarySchema = {
sbom_id: {
type: "string",
},
+ version: {
+ type: "string",
+ },
},
} as const;
@@ -587,7 +597,15 @@ export const CweImporterSchema = {
export const DepNodeSchema = {
type: "object",
- required: ["sbom_id", "node_id", "purl", "name", "deps"],
+ required: [
+ "sbom_id",
+ "node_id",
+ "relationship",
+ "purl",
+ "name",
+ "version",
+ "deps",
+ ],
properties: {
deps: {
type: "array",
@@ -604,9 +622,15 @@ export const DepNodeSchema = {
purl: {
type: "string",
},
+ relationship: {
+ type: "string",
+ },
sbom_id: {
type: "string",
},
+ version: {
+ type: "string",
+ },
},
} as const;
@@ -617,6 +641,7 @@ export const DepSummarySchema = {
"node_id",
"purl",
"name",
+ "version",
"published",
"document_id",
"product_name",
@@ -654,6 +679,9 @@ export const DepSummarySchema = {
sbom_id: {
type: "string",
},
+ version: {
+ type: "string",
+ },
},
} as const;
@@ -1357,7 +1385,7 @@ export const PaginatedResults_SbomSummarySchema = {
},
{
type: "object",
- required: ["described_by", "number_of_packages"],
+ required: ["described_by"],
properties: {
described_by: {
type: "array",
@@ -1365,12 +1393,6 @@ export const PaginatedResults_SbomSummarySchema = {
$ref: "#/components/schemas/SbomPackage",
},
},
- number_of_packages: {
- type: "integer",
- format: "int64",
- description: "The number of packages this SBOM has",
- minimum: 0,
- },
},
},
],
@@ -1818,6 +1840,7 @@ export const RelationshipSchema = {
"dev_tool_of",
"described_by",
"package_of",
+ "undefined",
],
} as const;
@@ -1927,6 +1950,7 @@ export const SbomHeadSchema = {
"published",
"authors",
"name",
+ "number_of_packages",
],
properties: {
authors: {
@@ -1953,6 +1977,12 @@ export const SbomHeadSchema = {
name: {
type: "string",
},
+ number_of_packages: {
+ type: "integer",
+ format: "int64",
+ description: "The number of packages this SBOM has",
+ minimum: 0,
+ },
published: {
type: ["string", "null"],
format: "date-time",
@@ -2052,42 +2082,39 @@ export const SbomPackageRelationSchema = {
} as const;
export const SbomStatusSchema = {
- type: "object",
- required: ["vulnerability", "status", "packages"],
- properties: {
- context: {
- oneOf: [
- {
- type: "null",
+ allOf: [
+ {
+ $ref: "#/components/schemas/VulnerabilityHead",
+ },
+ {
+ type: "object",
+ required: ["average_severity", "status", "packages"],
+ properties: {
+ average_severity: {
+ $ref: "#/components/schemas/Severity",
},
- {
- $ref: "#/components/schemas/StatusContext",
+ context: {
+ oneOf: [
+ {
+ type: "null",
+ },
+ {
+ $ref: "#/components/schemas/StatusContext",
+ },
+ ],
},
- ],
- },
- packages: {
- type: "array",
- items: {
- $ref: "#/components/schemas/SbomPackage",
- },
- },
- severity: {
- oneOf: [
- {
- type: "null",
+ packages: {
+ type: "array",
+ items: {
+ $ref: "#/components/schemas/SbomPackage",
+ },
},
- {
- $ref: "#/components/schemas/Severity",
+ status: {
+ type: "string",
},
- ],
- },
- status: {
- type: "string",
- },
- vulnerability: {
- $ref: "#/components/schemas/VulnerabilityHead",
+ },
},
- },
+ ],
} as const;
export const SbomSummarySchema = {
@@ -2107,7 +2134,7 @@ export const SbomSummarySchema = {
},
{
type: "object",
- required: ["described_by", "number_of_packages"],
+ required: ["described_by"],
properties: {
described_by: {
type: "array",
@@ -2115,12 +2142,6 @@ export const SbomSummarySchema = {
$ref: "#/components/schemas/SbomPackage",
},
},
- number_of_packages: {
- type: "integer",
- format: "int64",
- description: "The number of packages this SBOM has",
- minimum: 0,
- },
},
},
],
diff --git a/client/src/app/client/types.gen.ts b/client/src/app/client/types.gen.ts
index 1f48ea13..3b320af7 100644
--- a/client/src/app/client/types.gen.ts
+++ b/client/src/app/client/types.gen.ts
@@ -119,7 +119,9 @@ export type AncNode = {
name: string;
node_id: string;
purl: string;
+ relationship: string;
sbom_id: string;
+ version: string;
};
export type AncestorSummary = {
@@ -132,6 +134,7 @@ export type AncestorSummary = {
published: string;
purl: string;
sbom_id: string;
+ version: string;
};
export type BasePurlDetails = BasePurlHead & {
@@ -230,7 +233,9 @@ export type DepNode = {
name: string;
node_id: string;
purl: string;
+ relationship: string;
sbom_id: string;
+ version: string;
};
export type DepSummary = {
@@ -243,6 +248,7 @@ export type DepSummary = {
published: string;
purl: string;
sbom_id: string;
+ version: string;
};
/**
@@ -532,10 +538,6 @@ export type PaginatedResults_SbomSummary = {
SbomHead &
(null | SourceDocument) & {
described_by: Array;
- /**
- * The number of packages this SBOM has
- */
- number_of_packages: number;
}
>;
total: number;
@@ -682,7 +684,8 @@ export type Relationship =
| "build_tool_of"
| "dev_tool_of"
| "described_by"
- | "package_of";
+ | "package_of"
+ | "undefined";
export type Report = {
/**
@@ -736,6 +739,10 @@ export type SbomHead = {
id: string;
labels: Labels;
name: string;
+ /**
+ * The number of packages this SBOM has
+ */
+ number_of_packages: number;
published: string | null;
};
@@ -762,21 +769,16 @@ export type SbomPackageRelation = {
relationship: Relationship;
};
-export type SbomStatus = {
+export type SbomStatus = VulnerabilityHead & {
+ average_severity: Severity;
context?: null | StatusContext;
packages: Array;
- severity?: null | Severity;
status: string;
- vulnerability: VulnerabilityHead;
};
export type SbomSummary = SbomHead &
(null | SourceDocument) & {
described_by: Array;
- /**
- * The number of packages this SBOM has
- */
- number_of_packages: number;
};
/**
diff --git a/client/src/app/components/SbomVulnerabilitiesDonutChart.tsx b/client/src/app/components/SbomVulnerabilitiesDonutChart.tsx
index 72322d9a..6e1d43f5 100644
--- a/client/src/app/components/SbomVulnerabilitiesDonutChart.tsx
+++ b/client/src/app/components/SbomVulnerabilitiesDonutChart.tsx
@@ -4,10 +4,10 @@ import { ChartDonut } from "@patternfly/react-charts";
import { compareBySeverityFn, severityList } from "@app/api/model-utils";
import { Severity } from "@app/client";
-import { VulnerabilityOfSbomSummary } from "@app/hooks/domain-controls/useVulnerabilitiesOfSbom";
+import { SeveritySummary } from "@app/hooks/domain-controls/useVulnerabilitiesOfSbom";
export interface SbomVulnerabilitiesDonutChartProps {
- vulnerabilitiesSummary: VulnerabilityOfSbomSummary;
+ vulnerabilitiesSummary: SeveritySummary;
}
export const SbomVulnerabilitiesDonutChart: React.FC<
diff --git a/client/src/app/hooks/domain-controls/useVulnerabilitiesOfSbom.ts b/client/src/app/hooks/domain-controls/useVulnerabilitiesOfSbom.ts
index ce151b37..cbfba409 100644
--- a/client/src/app/hooks/domain-controls/useVulnerabilitiesOfSbom.ts
+++ b/client/src/app/hooks/domain-controls/useVulnerabilitiesOfSbom.ts
@@ -1,36 +1,85 @@
import React from "react";
import { VulnerabilityStatus } from "@app/api/models";
-import {
- SbomAdvisory,
- SbomPackage,
- Severity,
- VulnerabilityHead,
-} from "@app/client";
+import { SbomAdvisory, SbomPackage, SbomStatus, Severity } from "@app/client";
import {
useFetchSbomsAdvisory,
- useFetchSbomsAdvisory2,
+ useFetchSbomsAdvisoryBatch,
} from "@app/queries/sboms";
interface VulnerabilityOfSbom {
+ vulnerability: SbomStatus;
advisory: SbomAdvisory;
- vulnerabilityId: string;
- vulnerability: VulnerabilityHead;
- severity?: Severity | null;
- status: VulnerabilityStatus;
+ vulnerabilityStatus: VulnerabilityStatus;
packages: SbomPackage[];
}
-export interface VulnerabilityOfSbomSummary {
+export type SeveritySummary = {
total: number;
severities: { [key in Severity]: number };
+};
+
+export interface VulnerabilityOfSbomSummary {
+ vulnerabilityStatus: {
+ [key in VulnerabilityStatus]: SeveritySummary;
+ };
}
-const DEFAULT_SUMMARY: VulnerabilityOfSbomSummary = {
+const DEFAULT_SEVERITY: SeveritySummary = {
total: 0,
severities: { none: 0, low: 0, medium: 0, high: 0, critical: 0 },
};
+const DEFAULT_SUMMARY: VulnerabilityOfSbomSummary = {
+ vulnerabilityStatus: {
+ affected: { ...DEFAULT_SEVERITY },
+ fixed: { ...DEFAULT_SEVERITY },
+ not_affected: { ...DEFAULT_SEVERITY },
+ known_not_affected: { ...DEFAULT_SEVERITY },
+ },
+};
+
+const advisoryStatusToModels = (advisories: SbomAdvisory[]) => {
+ const vulnerabilities = advisories.flatMap((advisory) => {
+ return (advisory.status ?? []).map((sbomStatus) => {
+ const result: VulnerabilityOfSbom = {
+ vulnerability: sbomStatus,
+ advisory: advisory,
+ vulnerabilityStatus: sbomStatus.status as VulnerabilityStatus,
+ packages: sbomStatus.packages || [],
+ };
+ return result;
+ });
+ });
+
+ const summary = vulnerabilities.reduce((prev, current) => {
+ const vulnStatus = current.vulnerabilityStatus;
+ const severity = current.vulnerability.average_severity;
+
+ const prevVulnStatusValue = prev.vulnerabilityStatus[vulnStatus];
+
+ const result: VulnerabilityOfSbomSummary = {
+ ...prev,
+ vulnerabilityStatus: {
+ ...prev.vulnerabilityStatus,
+ [vulnStatus]: {
+ total: prevVulnStatusValue.total + 1,
+ severities: {
+ ...prevVulnStatusValue.severities,
+ [severity]: prevVulnStatusValue.severities[severity] + 1,
+ },
+ },
+ },
+ };
+ return result;
+ }, DEFAULT_SUMMARY);
+
+ return {
+ vulnerabilities,
+ summary,
+ };
+};
+
export const useVulnerabilitiesOfSbom = (sbomId: string) => {
const {
advisories,
@@ -38,137 +87,30 @@ export const useVulnerabilitiesOfSbom = (sbomId: string) => {
fetchError: fetchErrorAdvisories,
} = useFetchSbomsAdvisory(sbomId);
- const allVulnerabilities = React.useMemo(() => {
- const vulnerabilities = (advisories ?? [])
- .flatMap((advisory) => {
- return (advisory.status ?? []).map((status) => {
- const result: VulnerabilityOfSbom = {
- advisory: advisory,
- vulnerabilityId: status.vulnerability.identifier,
- vulnerability: status.vulnerability,
- severity: status.severity,
- status: status.status as VulnerabilityStatus,
- packages: status.packages || [],
- };
- return result;
- });
- })
- // Take only "affected"
- .filter((item) => item.status === "affected")
- // Remove duplicates if exists
- .reduce((prev, current) => {
- const exists = prev.find(
- (item) =>
- item.vulnerability.identifier ===
- current.vulnerability.identifier &&
- item.advisory.uuid === current.advisory.uuid
- );
- if (!exists) {
- return [...prev, current];
- } else {
- return prev;
- }
- }, [] as VulnerabilityOfSbom[]);
-
- return vulnerabilities;
+ const result = React.useMemo(() => {
+ return advisoryStatusToModels(advisories || []);
}, [advisories]);
- // Summary
-
- const vulnerabilitiesSummary = React.useMemo(() => {
- return allVulnerabilities.reduce((prev, current) => {
- if (current.severity) {
- const severity = current.severity;
- return {
- ...prev,
- total: prev.total + 1,
- severities: {
- ...prev.severities,
- [severity]: prev.severities[severity] + 1,
- },
- };
- } else {
- return prev;
- }
- }, DEFAULT_SUMMARY);
- }, [allVulnerabilities]);
-
return {
+ data: result,
isFetching: isFetchingAdvisories,
fetchError: fetchErrorAdvisories,
- vulnerabilities: allVulnerabilities,
- summary: vulnerabilitiesSummary,
};
};
export const useVulnerabilitiesOfSboms = (sbomIds: string[]) => {
const { advisories, isFetching, fetchError } =
- useFetchSbomsAdvisory2(sbomIds);
-
- const allVulnerabilities = React.useMemo(() => {
- const vulnerabilities = (advisories ?? []).map((advisories) => {
- return (
- advisories
- .flatMap((advisory) => {
- return (advisory.status ?? []).map((status) => {
- const result: VulnerabilityOfSbom = {
- advisory: advisory,
- vulnerabilityId: status.vulnerability.identifier,
- vulnerability: status.vulnerability,
- severity: status.severity,
- status: status.status as VulnerabilityStatus,
- packages: status.packages || [],
- };
- return result;
- });
- })
- // Take only "affected"
- .filter((item) => item.status === "affected")
- // Remove duplicates if exists
- .reduce((prev, current) => {
- const exists = prev.find(
- (item) =>
- item.vulnerability.identifier ===
- current.vulnerability.identifier &&
- item.advisory.uuid === current.advisory.uuid
- );
- if (!exists) {
- return [...prev, current];
- } else {
- return prev;
- }
- }, [] as VulnerabilityOfSbom[])
- );
- });
- return vulnerabilities;
- }, [advisories]);
+ useFetchSbomsAdvisoryBatch(sbomIds);
- // Summary
-
- const vulnerabilitiesSummary = React.useMemo(() => {
- return allVulnerabilities.map((allVulnerabilities) => {
- return allVulnerabilities.reduce((prev, current) => {
- if (current.severity) {
- const severity = current.severity;
- return {
- ...prev,
- total: prev.total + 1,
- severities: {
- ...prev.severities,
- [severity]: prev.severities[severity] + 1,
- },
- };
- } else {
- return prev;
- }
- }, DEFAULT_SUMMARY);
+ const result = React.useMemo(() => {
+ return (advisories ?? []).map((item) => {
+ return advisoryStatusToModels(item || []);
});
- }, [allVulnerabilities]);
+ }, [advisories]);
return {
+ data: result,
isFetching: isFetching,
fetchError: fetchError,
- vulnerabilities: allVulnerabilities,
- summary: vulnerabilitiesSummary,
};
};
diff --git a/client/src/app/pages/home/components/MonitoringSection.tsx b/client/src/app/pages/home/components/MonitoringSection.tsx
index 1a323909..56996ac4 100644
--- a/client/src/app/pages/home/components/MonitoringSection.tsx
+++ b/client/src/app/pages/home/components/MonitoringSection.tsx
@@ -1,5 +1,5 @@
import React from "react";
-import { Link } from "react-router-dom";
+import { Link, useNavigate } from "react-router-dom";
import {
Chart,
@@ -27,7 +27,8 @@ import {
} from "@patternfly/react-core";
import { severityList } from "@app/api/model-utils";
-import { Severity } from "@app/client";
+import { SbomHead, Severity } from "@app/client";
+import { LoadingWrapper } from "@app/components/LoadingWrapper";
import { useVulnerabilitiesOfSboms } from "@app/hooks/domain-controls/useVulnerabilitiesOfSbom";
import { useFetchSBOMs } from "@app/queries/sboms";
import { useFetchVulnerabilities } from "@app/queries/vulnerabilities";
@@ -46,6 +47,10 @@ const LEGENDS: Legend[] = [
];
export const MonitoringSection: React.FC = () => {
+ const navigate = useNavigate();
+
+ //
+
const {
result: { data: barchartSboms, total: totalSboms },
isFetching: isFetchingBarchartSboms,
@@ -58,6 +63,22 @@ export const MonitoringSection: React.FC = () => {
true
);
+ const {
+ data: barchartSbomsVulnerabilities,
+ isFetching: isFetchingBarchartSbomsVulnerabilities,
+ fetchError: fetchErrorBarchartSbomsVulnerabilities,
+ } = useVulnerabilitiesOfSboms(barchartSboms.map((e) => e.id));
+
+ const showTickValues = barchartSbomsVulnerabilities
+ .map((e) => e.summary.vulnerabilityStatus.affected.total)
+ .every((item) => item === 0);
+
+ const generateSbomBarName = (sbom: SbomHead, index: number) => {
+ return `${" ".repeat(index + 1)}${sbom.name}`;
+ };
+
+ //
+
const {
result: { data: vulnerabilities, total: totalVulnerabilities },
isFetching: isFetchingVulnerabilities,
@@ -70,198 +91,216 @@ export const MonitoringSection: React.FC = () => {
true
);
- const { summary, isFetching, fetchError } = useVulnerabilitiesOfSboms(
- barchartSboms.map((e) => e.id)
- );
-
return (
Your dashboard
-
-
-
- Below is a summary of CVE status for your last 10 ingested
- SBOMs. You can click on the SBOM name or CVE severity number
- below to be taken to their respective details page.
-
-
-
-
-
{
- const severity = severityList[legend.severity];
- return { name: severity.name };
- })}
- legendPosition="bottom-left"
- height={375}
- name="sbom-summary-status"
- padding={{
- bottom: 75,
- left: 330,
- right: 50,
- top: 50,
- }}
- themeColor={ChartThemeColor.multiOrdered}
- width={700}
- legendComponent={
- !!e)
+ }
+ >
+
+
+
+ Below is a summary of CVE status for your last 10 ingested
+ SBOMs. You can click on the SBOM name or CVE severity number
+ below to be taken to their respective details page.
+
+
+
+
+ {
+ const severity = severityList[legend.severity];
+ return { name: severity.name };
+ })}
+ legendPosition="bottom-left"
+ height={375}
+ name="sbom-summary-status"
+ padding={{
+ bottom: 75,
+ left: 330,
+ right: 50,
+ top: 50,
+ }}
+ themeColor={ChartThemeColor.multiOrdered}
+ width={700}
+ legendComponent={
+ {
+ const severity = severityList[legend.severity];
+ return severity.color.value;
+ })}
+ />
+ }
+ >
+
+ }
+ tickLabelComponent={
+ {
+ const sbomName = (event.target as any)
+ .innerHTML as string | null;
+ const sbom = barchartSboms.find(
+ (item, index) => {
+ return (
+ generateSbomBarName(item, index) ===
+ sbomName
+ );
+ }
+ );
+ if (sbom) {
+ navigate(`/sboms/${sbom.id}`);
+ }
+ },
+ }}
+ />
+ }
+ />
+
+ {
const severity = severityList[legend.severity];
return severity.color.value;
})}
- />
- }
- >
- }
- tickLabelComponent={
- {
- // const sbom_name = (event.target as any)
- // .innerHTML as string | null;
- // const sbom = props.find(
- // (item) => item.sbom_name === sbom_name
- // );
- // if (sbom) {
- // const sbomDetailsPage = `/sbom/content/${sbom.sbom_id}`;
+ >
+ {LEGENDS.map((legend) => {
+ const severityData = severityList[legend.severity];
+ return (
+
+ }
+ data={barchartSbomsVulnerabilities.map(
+ (
+ {
+ summary: {
+ vulnerabilityStatus: { affected },
+ },
+ },
+ index
+ ) => {
+ const sbom = barchartSboms[index];
- // const wasmBindings = (window as any)
- // .wasmBindings;
- // if (wasmBindings) {
- // wasmBindings.spogNavigateTo(
- // sbomDetailsPage
- // );
- // } else {
- // window.open(sbomDetailsPage);
- // }
- // }
- // },
- // }}
- />
- }
- />
-
- {
- const severity = severityList[legend.severity];
- return severity.color.value;
- })}
- >
- {LEGENDS.map((legend) => {
- const severity = severityList[legend.severity];
- return (
-
- }
- data={summary.map((item, index) => {
- const sbom = barchartSboms[index];
- const severityKey = legend.severity;
- const count = item.severities[severityKey];
- return {
- name: severityKey,
- x: sbom.name,
- y: count,
- label: `${severity.name}: ${count}`,
- };
- })}
- />
- );
- })}
-
-
-
-
-
+ const severity = legend.severity;
+ const count = affected.severities[severity];
+ return {
+ x: generateSbomBarName(sbom, index),
+ y: count,
+ label: `${severityData.name}: ${count}`,
+ };
+ }
+ )}
+ />
+ );
+ })}
+
+
+
+
+
+
-
-
-
-
-
- Last SBOM ingested
-
-
-
-
- {formatDate(barchartSboms?.[0]?.published)}
-
-
-
- {barchartSboms?.[0]?.name}
-
-
-
-
-
-
-
-
-
-
- Total SBOMs
-
- {totalSboms}
-
-
-
-
-
-
-
-
- Last Vulnerability ingested
-
-
-
-
- {formatDate(vulnerabilities?.[0]?.published)}
-
-
- {vulnerabilities?.[0]?.identifier}
-
-
-
-
-
-
-
-
-
-
- Total Vulnerabilities
-
-
- {totalVulnerabilities}
-
-
-
-
-
+
+
+
+
+
+
+ Last SBOM ingested
+
+
+
+
+ {formatDate(barchartSboms?.[0]?.published)}
+
+
+
+ {barchartSboms?.[0]?.name}
+
+
+
+
+
+
+
+
+
+
+ Total SBOMs
+
+ {totalSboms}
+
+
+
+
+
+
+
+
+ Last Vulnerability ingested
+
+
+
+
+ {formatDate(vulnerabilities?.[0]?.published)}
+
+
+ {vulnerabilities?.[0]?.identifier}
+
+
+
+
+
+
+
+
+
+
+ Total Vulnerabilities
+
+
+ {totalVulnerabilities}
+
+
+
+
+
+
diff --git a/client/src/app/pages/home/components/WatchedSbom.tsx b/client/src/app/pages/home/components/WatchedSbom.tsx
index 1b1dcf6d..46ced431 100644
--- a/client/src/app/pages/home/components/WatchedSbom.tsx
+++ b/client/src/app/pages/home/components/WatchedSbom.tsx
@@ -20,7 +20,7 @@ import {
StackItem,
TextInputGroup,
TextInputGroupMain,
- TextInputGroupUtilities,
+ TextInputGroupUtilities
} from "@patternfly/react-core";
import TimesIcon from "@patternfly/react-icons/dist/esm/icons/times-icon";
@@ -121,11 +121,11 @@ export const WatchedSbom: React.FC = ({
{sbomId ? (
-
+
- View Details
+ View Details
) : (
diff --git a/client/src/app/pages/home/components/WatchedSbomDonutChart.tsx b/client/src/app/pages/home/components/WatchedSbomDonutChart.tsx
index 3d591ce5..f768f3bf 100644
--- a/client/src/app/pages/home/components/WatchedSbomDonutChart.tsx
+++ b/client/src/app/pages/home/components/WatchedSbomDonutChart.tsx
@@ -1,5 +1,6 @@
import React from "react";
+import { LoadingWrapper } from "@app/components/LoadingWrapper";
import { SbomVulnerabilitiesDonutChart } from "@app/components/SbomVulnerabilitiesDonutChart";
import { useVulnerabilitiesOfSbom } from "@app/hooks/domain-controls/useVulnerabilitiesOfSbom";
@@ -10,16 +11,13 @@ interface WatchedSbomDonutChartProps {
export const WatchedSbomDonutChart: React.FC = ({
sbomId,
}) => {
- const {
- vulnerabilities: vulnerabilities,
- summary: vulnerabilitiesSummary,
- isFetching: isFetchingVulnerabilities,
- fetchError: fetchErrorVulnerabilities,
- } = useVulnerabilitiesOfSbom(sbomId);
+ const { data, isFetching, fetchError } = useVulnerabilitiesOfSbom(sbomId);
return (
-
+
+
+
);
};
diff --git a/client/src/app/pages/home/watched-sboms-context.tsx b/client/src/app/pages/home/watched-sboms-context.tsx
index 1cb890ec..9c46009f 100644
--- a/client/src/app/pages/home/watched-sboms-context.tsx
+++ b/client/src/app/pages/home/watched-sboms-context.tsx
@@ -29,10 +29,10 @@ interface IWatchedSbomsProvider {
export const WatchedSbomsProvider: React.FunctionComponent<
IWatchedSbomsProvider
> = ({ children }) => {
- const { sboms, isFetching, fetchError } = useFetchWatchedSboms();
-
const { pushNotification } = React.useContext(NotificationsContext);
+ const { sboms, isFetching, fetchError } = useFetchWatchedSboms();
+
const onUpdateSuccess = () => {};
const onUpdateError = (_error: AxiosError) => {
pushNotification({
@@ -53,7 +53,12 @@ export const WatchedSbomsProvider: React.FunctionComponent<
return (
{children}
diff --git a/client/src/app/pages/sbom-details/vulnerabilities-by-sbom.tsx b/client/src/app/pages/sbom-details/vulnerabilities-by-sbom.tsx
index a13de9ab..4e808da5 100644
--- a/client/src/app/pages/sbom-details/vulnerabilities-by-sbom.tsx
+++ b/client/src/app/pages/sbom-details/vulnerabilities-by-sbom.tsx
@@ -56,15 +56,15 @@ export const VulnerabilitiesBySbom: React.FC = ({
fetchError: fetchErrorSbom,
} = useFetchSBOMById(sbomId);
const {
- vulnerabilities: vulnerabilities,
- summary: vulnerabilitiesSummary,
+ data: { vulnerabilities, summary: vulnerabilitiesSummary },
isFetching: isFetchingVulnerabilities,
fetchError: fetchErrorVulnerabilities,
} = useVulnerabilitiesOfSbom(sbomId);
const tableDataWithUiId = useWithUiId(
vulnerabilities,
- (d) => `${d.vulnerabilityId}-${d.advisory.identifier}-${d.advisory.uuid}`
+ (d) =>
+ `${d.vulnerability.identifier}-${d.advisory.identifier}-${d.advisory.uuid}`
);
const tableControls = useLocalTableControls({
@@ -118,7 +118,9 @@ export const VulnerabilitiesBySbom: React.FC = ({
@@ -195,8 +197,10 @@ export const VulnerabilitiesBySbom: React.FC = ({
rowIndex={rowIndex}
>
-
- {item.vulnerabilityId}
+
+ {item.vulnerability.identifier}
= ({
)}
- {item.severity && (
-
+ {item.vulnerability.average_severity && (
+
)}
= ({
sbomId,
}) => {
- const { summary, isFetching, fetchError } = useVulnerabilitiesOfSbom(sbomId);
+ const { data, isFetching, fetchError } = useVulnerabilitiesOfSbom(sbomId);
return (
= ({
isFetchingState={ }
fetchErrorState={Error }
>
-
+
);
};
diff --git a/client/src/app/pages/vulnerability-details/advisories-by-vulnerability.stories.tsx b/client/src/app/pages/vulnerability-details/advisories-by-vulnerability.stories.tsx
index ee492922..2976af31 100644
--- a/client/src/app/pages/vulnerability-details/advisories-by-vulnerability.stories.tsx
+++ b/client/src/app/pages/vulnerability-details/advisories-by-vulnerability.stories.tsx
@@ -69,40 +69,7 @@ export const PrimaryState: Story = {
],
},
number_of_vulnerabilities: 1,
- sboms: [
- {
- id: "urn:uuid:0193013f-1b8a-7152-8857-8b8f4238b8ba",
- document_id:
- "https://access.redhat.com/security/data/sbom/spdx/quarkus-bom-3.2.12.Final-redhat-00002",
- labels: {
- type: "spdx",
- },
- data_licenses: ["CC0-1.0"],
- published: "2024-07-05T09:40:48Z",
- authors: [
- "Organization: Red Hat Product Security (secalert@redhat.com)",
- ],
- name: "quarkus-bom",
- version: "3.2.12.Final-redhat-00002",
- status: ["affected"],
- },
- {
- id: "urn:uuid:0193013f-0f00-77e1-afe4-b7d7f8585b7a",
- document_id:
- "https://access.redhat.com/security/data/sbom/spdx/quarkus-bom-3.2.11.Final-redhat-00001",
- labels: {
- type: "spdx",
- },
- data_licenses: ["CC0-1.0"],
- published: "2024-05-28T09:26:01Z",
- authors: [
- "Organization: Red Hat Product Security (secalert@redhat.com)",
- ],
- name: "quarkus-bom",
- version: "3.2.11.Final-redhat-00001",
- status: ["affected"],
- },
- ],
+ sboms: [],
},
],
},
diff --git a/client/src/app/queries/sboms.ts b/client/src/app/queries/sboms.ts
index 326a731e..4de7393e 100644
--- a/client/src/app/queries/sboms.ts
+++ b/client/src/app/queries/sboms.ts
@@ -180,7 +180,7 @@ export const useFetchSbomsAdvisory = (sbomId: string) => {
};
};
-export const useFetchSbomsAdvisory2 = (sbomIds: string[]) => {
+export const useFetchSbomsAdvisoryBatch = (sbomIds: string[]) => {
const userQueries = useQueries({
queries: sbomIds.map((sbomId) => {
return {
From 83b9f1b762d20c1dc84c33498945af2951ee953d Mon Sep 17 00:00:00 2001
From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com>
Date: Fri, 22 Nov 2024 13:21:30 +0100
Subject: [PATCH 10/11] Fix format
---
client/src/app/pages/home/components/WatchedSbom.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/client/src/app/pages/home/components/WatchedSbom.tsx b/client/src/app/pages/home/components/WatchedSbom.tsx
index 46ced431..c331700f 100644
--- a/client/src/app/pages/home/components/WatchedSbom.tsx
+++ b/client/src/app/pages/home/components/WatchedSbom.tsx
@@ -20,7 +20,7 @@ import {
StackItem,
TextInputGroup,
TextInputGroupMain,
- TextInputGroupUtilities
+ TextInputGroupUtilities,
} from "@patternfly/react-core";
import TimesIcon from "@patternfly/react-icons/dist/esm/icons/times-icon";
From 47cb3d09030c988cee11114e8c2d322ab1d135cc Mon Sep 17 00:00:00 2001
From: Carlos Feria <2582866+carlosthe19916@users.noreply.github.com>
Date: Fri, 22 Nov 2024 14:01:19 +0100
Subject: [PATCH 11/11] fix: restore story book data deleted while developing
---
.../advisories-by-vulnerability.stories.tsx | 37 ++++++++++++++++++-
1 file changed, 36 insertions(+), 1 deletion(-)
diff --git a/client/src/app/pages/vulnerability-details/advisories-by-vulnerability.stories.tsx b/client/src/app/pages/vulnerability-details/advisories-by-vulnerability.stories.tsx
index 2976af31..db4182cb 100644
--- a/client/src/app/pages/vulnerability-details/advisories-by-vulnerability.stories.tsx
+++ b/client/src/app/pages/vulnerability-details/advisories-by-vulnerability.stories.tsx
@@ -69,7 +69,42 @@ export const PrimaryState: Story = {
],
},
number_of_vulnerabilities: 1,
- sboms: [],
+ sboms: [
+ {
+ id: "urn:uuid:0193013f-1b8a-7152-8857-8b8f4238b8ba",
+ document_id:
+ "https://access.redhat.com/security/data/sbom/spdx/quarkus-bom-3.2.12.Final-redhat-00002",
+ labels: {
+ type: "spdx",
+ },
+ data_licenses: ["CC0-1.0"],
+ published: "2024-07-05T09:40:48Z",
+ authors: [
+ "Organization: Red Hat Product Security (secalert@redhat.com)",
+ ],
+ name: "quarkus-bom",
+ version: "3.2.12.Final-redhat-00002",
+ status: ["affected"],
+ number_of_packages: 1,
+ },
+ {
+ id: "urn:uuid:0193013f-0f00-77e1-afe4-b7d7f8585b7a",
+ document_id:
+ "https://access.redhat.com/security/data/sbom/spdx/quarkus-bom-3.2.11.Final-redhat-00001",
+ labels: {
+ type: "spdx",
+ },
+ data_licenses: ["CC0-1.0"],
+ published: "2024-05-28T09:26:01Z",
+ authors: [
+ "Organization: Red Hat Product Security (secalert@redhat.com)",
+ ],
+ name: "quarkus-bom",
+ version: "3.2.11.Final-redhat-00001",
+ status: ["affected"],
+ number_of_packages: 1,
+ },
+ ],
},
],
},