diff --git a/client/package.json b/client/package.json index 5dbb09de7..8dc4695ca 100644 --- a/client/package.json +++ b/client/package.json @@ -30,6 +30,8 @@ "@tanstack/react-query-devtools": "^5.61.0", "axios": "^1.16.0", "dayjs": "^1.11.18", + "echarts": "^5.6.0", + "echarts-for-react": "^3.0.2", "file-saver": "^2.0.5", "oidc-client-ts": "^3.3.0", "packageurl-js": "^2.0.1", diff --git a/client/src/app/pages/advisory-details/advisory-details.tsx b/client/src/app/pages/advisory-details/advisory-details.tsx index 484f4781e..277a474ed 100644 --- a/client/src/app/pages/advisory-details/advisory-details.tsx +++ b/client/src/app/pages/advisory-details/advisory-details.tsx @@ -43,9 +43,11 @@ import { useFetchAdvisoryById, } from "@app/queries/advisories"; +import { DocumentMetadata } from "@app/components/DocumentMetadata"; + +import { CsafAdvisoryDetails } from "./csaf-advisory-details"; import { Overview } from "./overview"; import { VulnerabilitiesByAdvisory } from "./vulnerabilities-by-advisory"; -import { DocumentMetadata } from "@app/components/DocumentMetadata"; export const AdvisoryDetails: React.FC = () => { const navigate = useNavigate(); @@ -87,11 +89,13 @@ export const AdvisoryDetails: React.FC = () => { const { mutate: deleteAdvisory, isPending: isDeleting } = useDeleteAdvisoryMutation(onDeleteAdvisorySuccess, onDeleteAdvisoryError); - // Tabs + const isCsaf = advisory?.labels.type === "csaf"; + + // Tabs (default non-CSAF layout) const { propHelpers: { getTabsProps, getTabProps, getTabContentProps }, } = useTabControls({ - persistenceKeyPrefix: "ad", // ad="advisory details" + persistenceKeyPrefix: "ad", persistTo: "urlParams", tabKeys: ["info", "vulnerabilities"], }); @@ -177,47 +181,54 @@ export const AdvisoryDetails: React.FC = () => { - - - Info} - tabContentRef={infoTabRef} - /> - Vulnerabilities} - tabContentRef={vulnerabilitiesTabRef} - /> - - - - - - {advisory && } - - - - - - + + {isCsaf ? ( + + ) : ( + <> + + + Info} + tabContentRef={infoTabRef} + /> + Vulnerabilities} + tabContentRef={vulnerabilitiesTabRef} + /> + + + + + + {advisory && } + + + + + + + + )} = ({ cvss }) => { + const [isExpanded, setIsExpanded] = React.useState(false); + + const metrics: { label: string; value?: string }[] = [ + { label: "Attack Vector", value: cvss.attackVector }, + { label: "Attack Complexity", value: cvss.attackComplexity }, + { label: "Privileges Required", value: cvss.privilegesRequired }, + { label: "User Interaction", value: cvss.userInteraction }, + { label: "Scope", value: cvss.scope }, + { label: "Confidentiality Impact", value: cvss.confidentialityImpact }, + { label: "Integrity Impact", value: cvss.integrityImpact }, + { label: "Availability Impact", value: cvss.availabilityImpact }, + { label: "Vector String", value: cvss.vectorString }, + ]; + + return ( + setIsExpanded(expanded)} + isExpanded={isExpanded} + > + + {metrics + .filter((m) => m.value) + .map((m) => ( + + {m.label} + {m.value} + + ))} + + + ); +}; diff --git a/client/src/app/pages/advisory-details/components/csaf-remediations.tsx b/client/src/app/pages/advisory-details/components/csaf-remediations.tsx new file mode 100644 index 000000000..06214850f --- /dev/null +++ b/client/src/app/pages/advisory-details/components/csaf-remediations.tsx @@ -0,0 +1,157 @@ +/** Remediations section grouped by category with expandable product lists. */ +import React from "react"; + +import { + Card, + CardBody, + CardTitle, + ExpandableSection, + Flex, + FlexItem, + Label, + List, + ListItem, +} from "@patternfly/react-core"; +import ExternalLinkAltIcon from "@patternfly/react-icons/dist/esm/icons/external-link-alt-icon"; + +import type { CsafRemediation } from "@app/types/csaf"; + +interface CsafRemediationsProps { + remediations: CsafRemediation[]; + productNameMap: Map; +} + +/** Groups remediations by category. */ +function groupByCategory( + remediations: CsafRemediation[], +): Map { + const groups = new Map(); + for (const rem of remediations) { + const existing = groups.get(rem.category); + if (existing) { + existing.push(rem); + } else { + groups.set(rem.category, [rem]); + } + } + return groups; +} + +/** Renders text with URLs converted to links. */ +const LinkifiedText: React.FC<{ text: string }> = ({ text }) => { + const urlRegex = /(https?:\/\/[^\s)]+)/g; + const parts = text.split(urlRegex); + return ( + <> + {parts.map((part, i) => + urlRegex.test(part) ? ( + + {part} + + ) : ( + {part} + ), + )} + + ); +}; + +/** Single remediation card within a category group. */ +const RemediationCard: React.FC<{ + remediation: CsafRemediation; + productNameMap: Map; +}> = ({ remediation, productNameMap }) => { + const [showProducts, setShowProducts] = React.useState(false); + const productIds = remediation.product_ids || []; + + return ( + + + + + + + {remediation.url && ( + + + {remediation.url} + + + )} + {productIds.length > 0 && ( + + setShowProducts(expanded)} + isExpanded={showProducts} + > + + {productIds.map((pid) => ( + + {productNameMap.get(pid) || pid} + + ))} + + + + )} + + + + ); +}; + +export const CsafRemediations: React.FC = ({ + remediations, + productNameMap, +}) => { + const grouped = React.useMemo( + () => groupByCategory(remediations), + [remediations], + ); + + return ( + + + {Array.from(grouped.entries()).map(([category, rems]) => ( + + + + ( + {rems.length}) + + + + {rems.map((rem, i) => ( + + + + ))} + + + + + ))} + + + ); +}; diff --git a/client/src/app/pages/advisory-details/components/csaf-vulnerability-card.tsx b/client/src/app/pages/advisory-details/components/csaf-vulnerability-card.tsx new file mode 100644 index 000000000..3730d1be3 --- /dev/null +++ b/client/src/app/pages/advisory-details/components/csaf-vulnerability-card.tsx @@ -0,0 +1,191 @@ +/** Individual CVE card with severity, CVSS breakdown, products, remediations, and references. */ +import React from "react"; +import { Link } from "react-router-dom"; + +import dayjs from "dayjs"; + +import { + Button, + Card, + CardBody, + CardHeader, + Content, + DescriptionList, + DescriptionListDescription, + DescriptionListGroup, + DescriptionListTerm, + ExpandableSection, + Flex, + FlexItem, + List, + ListItem, +} from "@patternfly/react-core"; +import ExternalLinkAltIcon from "@patternfly/react-icons/dist/esm/icons/external-link-alt-icon"; + +import { Paths } from "@app/Routes"; +import { SeverityShieldAndText } from "@app/components/SeverityShieldAndText"; +import type { CsafVulnerability } from "@app/types/csaf"; + +import { CsafCvssDetails } from "./csaf-cvss-details"; +import { CsafRemediations } from "./csaf-remediations"; + +interface CsafVulnerabilityCardProps { + vulnerability: CsafVulnerability; + productNameMap: Map; +} + +const INITIAL_PRODUCTS_SHOWN = 5; + +export const CsafVulnerabilityCard: React.FC = ({ + vulnerability, + productNameMap, +}) => { + const [showAllProducts, setShowAllProducts] = React.useState(false); + const cvss = vulnerability.scores?.[0]?.cvss_v3; + const affectedProducts = vulnerability.product_status?.known_affected || []; + const visibleProducts = showAllProducts + ? affectedProducts + : affectedProducts.slice(0, INITIAL_PRODUCTS_SHOWN); + const hiddenCount = affectedProducts.length - INITIAL_PRODUCTS_SHOWN; + + return ( + + + + {vulnerability.cve && ( + + + {vulnerability.cve} + + + )} + {cvss && ( + + + + )} + {vulnerability.title && ( + + {vulnerability.title} + + )} + + + + + {/* Metadata */} + + + {vulnerability.cwe && ( + + CWE + + {vulnerability.cwe.id} — {vulnerability.cwe.name} + + + )} + {vulnerability.discovery_date && ( + + Discovered + + {dayjs(vulnerability.discovery_date).format("YYYY-MM-DD")} + + + )} + {vulnerability.release_date && ( + + Released + + {dayjs(vulnerability.release_date).format("YYYY-MM-DD")} + + + )} + + + + {/* CVSS Details */} + {cvss && ( + + + + )} + + {/* Affected Products */} + {affectedProducts.length > 0 && ( + + + + {visibleProducts.map((pid) => ( + + {productNameMap.get(pid) || pid} + + ))} + + {hiddenCount > 0 && !showAllProducts && ( + + )} + + + )} + + {/* Remediations */} + {vulnerability.remediations && + vulnerability.remediations.length > 0 && ( + + + + )} + + {/* References */} + {vulnerability.references && vulnerability.references.length > 0 && ( + + + + {vulnerability.references.map((ref, i) => ( + + + {ref.summary || ref.url} + + + ))} + + + + )} + + + + ); +}; diff --git a/client/src/app/pages/advisory-details/csaf-advisory-details.tsx b/client/src/app/pages/advisory-details/csaf-advisory-details.tsx new file mode 100644 index 000000000..6d8588971 --- /dev/null +++ b/client/src/app/pages/advisory-details/csaf-advisory-details.tsx @@ -0,0 +1,143 @@ +/** CSAF-specific advisory details with 5-tab layout for VEX document visualization. */ +import React from "react"; + +import { + EmptyState, + EmptyStateBody, + EmptyStateVariant, + PageSection, + Tab, + TabContent, + Tabs, + TabTitleText, +} from "@patternfly/react-core"; +import CubesIcon from "@patternfly/react-icons/dist/esm/icons/cubes-icon"; + +import { LoadingWrapper } from "@app/components/LoadingWrapper"; +import { useTabControls } from "@app/hooks/tab-controls"; +import { useFetchAdvisoryCsafById } from "@app/queries/advisories"; + +import { CsafVulnerabilities } from "./csaf-vulnerabilities"; + +interface CsafAdvisoryDetailsProps { + advisoryId: string; +} + +/** Placeholder for tabs not yet implemented. */ +const ComingSoon: React.FC<{ label: string }> = ({ label }) => ( + + This tab is under construction. + +); + +export const CsafAdvisoryDetails: React.FC = ({ + advisoryId, +}) => { + const { csafDocument, isFetching, fetchError } = + useFetchAdvisoryCsafById(advisoryId); + + const { + propHelpers: { getTabsProps, getTabProps, getTabContentProps }, + } = useTabControls({ + persistenceKeyPrefix: "cad", + persistTo: "urlParams", + tabKeys: [ + "overview", + "vulnerabilities", + "product-tree", + "relationship-tree", + "source", + ], + }); + + const overviewTabRef = React.createRef(); + const vulnerabilitiesTabRef = React.createRef(); + const productTreeTabRef = React.createRef(); + const relationshipTreeTabRef = React.createRef(); + const sourceTabRef = React.createRef(); + + return ( + <> + + + Overview} + tabContentRef={overviewTabRef} + /> + Vulnerabilities} + tabContentRef={vulnerabilitiesTabRef} + /> + Product Tree} + tabContentRef={productTreeTabRef} + /> + Relationship Tree} + tabContentRef={relationshipTreeTabRef} + /> + Source} + tabContentRef={sourceTabRef} + /> + + + + + + + + + {csafDocument && ( + + )} + + + + + + + + + + + + + + ); +}; diff --git a/client/src/app/pages/advisory-details/csaf-vulnerabilities.tsx b/client/src/app/pages/advisory-details/csaf-vulnerabilities.tsx new file mode 100644 index 000000000..c9aaa4834 --- /dev/null +++ b/client/src/app/pages/advisory-details/csaf-vulnerabilities.tsx @@ -0,0 +1,94 @@ +/** CSAF Vulnerabilities tab rendering per-CVE cards sorted by severity. */ +import React from "react"; + +import { + EmptyState, + EmptyStateBody, + EmptyStateVariant, + Flex, + FlexItem, +} from "@patternfly/react-core"; +import CubesIcon from "@patternfly/react-icons/dist/esm/icons/cubes-icon"; + +import type { CsafDocument, CsafVulnerability } from "@app/types/csaf"; + +import { CsafVulnerabilityCard } from "./components/csaf-vulnerability-card"; + +interface CsafVulnerabilitiesProps { + csafDocument: CsafDocument; +} + +const SEVERITY_ORDER: Record = { + CRITICAL: 0, + HIGH: 1, + MEDIUM: 2, + LOW: 3, + NONE: 4, +}; + +/** Sorts vulnerabilities by CVSS severity, critical first. */ +function sortBySeverity( + vulnerabilities: CsafVulnerability[], +): CsafVulnerability[] { + return [...vulnerabilities].sort((a, b) => { + const aSev = a.scores?.[0]?.cvss_v3?.baseSeverity?.toUpperCase() || "NONE"; + const bSev = b.scores?.[0]?.cvss_v3?.baseSeverity?.toUpperCase() || "NONE"; + return (SEVERITY_ORDER[aSev] ?? 5) - (SEVERITY_ORDER[bSev] ?? 5); + }); +} + +/** Builds product ID to display name map from full_product_names. */ +function buildProductNameMap(csafDocument: CsafDocument): Map { + const map = new Map(); + const products = csafDocument.product_tree?.full_product_names; + if (products) { + for (const p of products) { + map.set(p.product_id, p.name); + } + } + return map; +} + +export const CsafVulnerabilities: React.FC = ({ + csafDocument, +}) => { + const vulnerabilities = csafDocument.vulnerabilities; + + const sorted = React.useMemo( + () => (vulnerabilities ? sortBySeverity(vulnerabilities) : []), + [vulnerabilities], + ); + + const productNameMap = React.useMemo( + () => buildProductNameMap(csafDocument), + [csafDocument], + ); + + if (sorted.length === 0) { + return ( + + + This advisory does not contain vulnerability data. + + + ); + } + + return ( + + {sorted.map((vuln, i) => ( + + + + ))} + + ); +}; diff --git a/client/src/app/queries/advisories.ts b/client/src/app/queries/advisories.ts index a2bebe849..709487d89 100644 --- a/client/src/app/queries/advisories.ts +++ b/client/src/app/queries/advisories.ts @@ -18,6 +18,7 @@ import { listAdvisoryLabels, updateAdvisoryLabels, } from "@app/client"; +import type { CsafDocument } from "@app/types/csaf"; import { uploadAdvisory } from "@app/api/rest"; import { @@ -141,6 +142,26 @@ export const useFetchAdvisorySourceById = (id: string) => { }; }; +/** Fetches the raw CSAF JSON document and parses it into a typed CsafDocument. */ +export const useFetchAdvisoryCsafById = (id: string) => { + const { data, isLoading, error } = useQuery({ + queryKey: [AdvisoriesQueryKey, id, "csaf"], + queryFn: async () => { + const response = await downloadAdvisory({ client, path: { key: id } }); + const blob = response.data as Blob; + const text = await blob.text(); + return JSON.parse(text) as CsafDocument; + }, + enabled: !!id, + }); + + return { + csafDocument: data, + isFetching: isLoading, + fetchError: error as AxiosError | null, + }; +}; + export const useUploadAdvisory = () => { const queryClient = useQueryClient(); return useUpload({ diff --git a/client/src/app/types/csaf.ts b/client/src/app/types/csaf.ts new file mode 100644 index 000000000..d633ee1e0 --- /dev/null +++ b/client/src/app/types/csaf.ts @@ -0,0 +1,290 @@ +/** CSAF 2.0 VEX document type definitions per the OASIS CSAF JSON schema. */ + +/** Top-level CSAF document container. */ +export interface CsafDocument { + document: CsafDocumentMetadata; + product_tree?: CsafProductTree; + vulnerabilities?: CsafVulnerability[]; +} + +/** Document-level metadata section. */ +export interface CsafDocumentMetadata { + category: string; + csaf_version: string; + title: string; + publisher: CsafPublisher; + tracking: CsafTracking; + notes?: CsafNote[]; + references?: CsafReference[]; + distribution?: CsafDistribution; + aggregate_severity?: CsafAggregateSeverity; + lang?: string; + source_lang?: string; +} + +/** Document publisher information. */ +export interface CsafPublisher { + category: string; + name: string; + namespace: string; + contact_details?: string; + issuing_authority?: string; +} + +/** Document tracking metadata including revision history. */ +export interface CsafTracking { + current_release_date: string; + id: string; + initial_release_date: string; + revision_history: CsafRevision[]; + status: string; + version: string; + generator?: CsafGenerator; + aliases?: string[]; +} + +/** Document version revision entry. */ +export interface CsafRevision { + date: string; + number: string; + summary: string; +} + +/** Tool that generated the CSAF document. */ +export interface CsafGenerator { + engine: CsafGeneratorEngine; + date?: string; +} + +/** Generator engine identification. */ +export interface CsafGeneratorEngine { + name: string; + version?: string; +} + +/** Document-level note. */ +export interface CsafNote { + category: string; + text: string; + audience?: string; + title?: string; +} + +/** External reference link. */ +export interface CsafReference { + url: string; + summary?: string; + category?: string; +} + +/** TLP distribution information. */ +export interface CsafDistribution { + text?: string; + tlp?: CsafTlp; +} + +/** Traffic Light Protocol classification. */ +export interface CsafTlp { + label: string; + url?: string; +} + +/** Aggregate severity for the entire document. */ +export interface CsafAggregateSeverity { + text: string; + namespace?: string; +} + +/** Product tree describing the vendor/product/version hierarchy. */ +export interface CsafProductTree { + branches?: CsafBranch[]; + full_product_names?: CsafFullProductName[]; + relationships?: CsafRelationship[]; + product_groups?: CsafProductGroup[]; +} + +/** Recursive branch in the product tree hierarchy. */ +export interface CsafBranch { + category: string; + name: string; + branches?: CsafBranch[]; + product?: CsafFullProductName; +} + +/** A uniquely identifiable product with an ID and display name. */ +export interface CsafFullProductName { + name: string; + product_id: string; + product_identification_helper?: CsafProductIdentificationHelper; +} + +/** Helper data for identifying a product (CPE, PURL, etc.). */ +export interface CsafProductIdentificationHelper { + cpe?: string; + purl?: string; + hashes?: CsafHash[]; + model_numbers?: string[]; + serial_numbers?: string[]; + skus?: string[]; + x_generic_uris?: CsafGenericUri[]; +} + +/** Cryptographic hash for product identification. */ +export interface CsafHash { + file_hashes: CsafFileHash[]; + filename: string; +} + +/** Individual file hash entry. */ +export interface CsafFileHash { + algorithm: string; + value: string; +} + +/** Generic URI reference. */ +export interface CsafGenericUri { + namespace: string; + uri: string; +} + +/** Relationship between two products. */ +export interface CsafRelationship { + category: string; + full_product_name: CsafFullProductName; + product_reference: string; + relates_to_product_reference: string; +} + +/** Named group of product IDs. */ +export interface CsafProductGroup { + group_id: string; + product_ids: string[]; + summary?: string; +} + +/** Vulnerability entry in a CSAF document. */ +export interface CsafVulnerability { + cve?: string; + cwe?: CsafCwe; + title?: string; + notes?: CsafNote[]; + references?: CsafReference[]; + discovery_date?: string; + release_date?: string; + scores?: CsafScore[]; + product_status?: CsafProductStatus; + remediations?: CsafRemediation[]; + threats?: CsafThreat[]; + acknowledgments?: CsafAcknowledgment[]; + involvements?: CsafInvolvement[]; + ids?: CsafVulnerabilityId[]; +} + +/** CWE weakness classification. */ +export interface CsafCwe { + id: string; + name: string; +} + +/** CVSS scoring data for a set of products. */ +export interface CsafScore { + products: string[]; + cvss_v3?: CsafCvssV3; + cvss_v2?: CsafCvssV2; +} + +/** CVSS v3.x score details. */ +export interface CsafCvssV3 { + version: string; + vectorString: string; + baseScore: number; + baseSeverity: string; + attackVector?: string; + attackComplexity?: string; + privilegesRequired?: string; + userInteraction?: string; + scope?: string; + confidentialityImpact?: string; + integrityImpact?: string; + availabilityImpact?: string; + exploitCodeMaturity?: string; + remediationLevel?: string; + reportConfidence?: string; + temporalScore?: number; + temporalSeverity?: string; + environmentalScore?: number; + environmentalSeverity?: string; +} + +/** CVSS v2 score details. */ +export interface CsafCvssV2 { + version: string; + vectorString: string; + baseScore: number; + accessVector?: string; + accessComplexity?: string; + authentication?: string; + confidentialityImpact?: string; + integrityImpact?: string; + availabilityImpact?: string; +} + +/** Product status categories per vulnerability. */ +export interface CsafProductStatus { + fixed?: string[]; + known_affected?: string[]; + known_not_affected?: string[]; + first_affected?: string[]; + first_fixed?: string[]; + last_affected?: string[]; + recommended?: string[]; + under_investigation?: string[]; +} + +/** Remediation action for affected products. */ +export interface CsafRemediation { + category: string; + details: string; + product_ids?: string[]; + group_ids?: string[]; + url?: string; + date?: string; + entitlements?: string[]; + restart_required?: CsafRestartRequired; +} + +/** Restart requirement for a remediation. */ +export interface CsafRestartRequired { + category: string; + details?: string; +} + +/** Threat information for affected products. */ +export interface CsafThreat { + category: string; + details: string; + product_ids?: string[]; + group_ids?: string[]; + date?: string; +} + +/** Acknowledgment of contributors or reporters. */ +export interface CsafAcknowledgment { + names?: string[]; + organization?: string; + summary?: string; + urls?: string[]; +} + +/** Involvement of a party in vulnerability handling. */ +export interface CsafInvolvement { + party: string; + status: string; + summary?: string; +} + +/** Alternative vulnerability identifier. */ +export interface CsafVulnerabilityId { + system_name: string; + text: string; +} diff --git a/package-lock.json b/package-lock.json index f1d69cf59..d272e179a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -70,6 +70,8 @@ "@tanstack/react-query-devtools": "^5.61.0", "axios": "^1.16.0", "dayjs": "^1.11.18", + "echarts": "^5.6.0", + "echarts-for-react": "^3.0.2", "file-saver": "^2.0.5", "oidc-client-ts": "^3.3.0", "packageurl-js": "^2.0.1", @@ -6200,6 +6202,36 @@ "node": ">= 0.4" } }, + "node_modules/echarts": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/echarts/-/echarts-5.6.0.tgz", + "integrity": "sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "2.3.0", + "zrender": "5.6.1" + } + }, + "node_modules/echarts-for-react": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/echarts-for-react/-/echarts-for-react-3.0.6.tgz", + "integrity": "sha512-4zqLgTGWS3JvkQDXjzkR1k1CHRdpd6by0988TWMJgnvDytegWLbeP/VNZmMa+0VJx2eD7Y632bi2JquXDgiGJg==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "size-sensor": "^1.0.1" + }, + "peerDependencies": { + "echarts": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", + "react": "^15.0.0 || >=16.0.0" + } + }, + "node_modules/echarts/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -6888,7 +6920,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, "license": "MIT" }, "node_modules/fast-diff": { @@ -11458,6 +11489,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/size-sensor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/size-sensor/-/size-sensor-1.0.3.tgz", + "integrity": "sha512-+k9mJ2/rQMiRmQUcjn+qznch260leIXY8r4FyYKKyRBO/s5UoeMAHGkCJyE1R/4wrIhTJONfyloY55SkE7ve3A==", + "license": "ISC" + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -13606,6 +13643,21 @@ "url": "https://github.com/sponsors/colinhacks" } }, + "node_modules/zrender": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/zrender/-/zrender-5.6.1.tgz", + "integrity": "sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==", + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "2.3.0" + } + }, + "node_modules/zrender/node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + }, "server": { "name": "@trustify-ui/server", "version": "0.1.0",