diff --git a/shared/models/severity/IColor.ts b/shared/models/severity/IColor.ts index 7d45802..55d040f 100644 --- a/shared/models/severity/IColor.ts +++ b/shared/models/severity/IColor.ts @@ -3,7 +3,3 @@ export interface IColor { green: number; blue: number; } - -export function getHexStringFromColor(color: IColor): string { - return `#${color.red.toString(16).padStart(2, '0')}${color.green.toString(16).padStart(2, '0')}${color.blue.toString(16).padStart(2, '0')}`; -} diff --git a/shared/models/severity/Severities.ts b/shared/models/severity/Severities.ts index 521d6b3..5390436 100644 --- a/shared/models/severity/Severities.ts +++ b/shared/models/severity/Severities.ts @@ -9,6 +9,10 @@ export const SEVERITIES: ISeverity[] = [ ]; export const DEFAULT_SEVERITY: ISeverity = SEVERITIES[0]; +export const LOW_SEVERITY: ISeverity = SEVERITIES[1]; +export const MODERATE_SEVERITY: ISeverity = SEVERITIES[2]; +export const HIGH_SEVERITY: ISeverity = SEVERITIES[3]; +export const CRITICAL_SEVERITY: ISeverity = SEVERITIES[4]; export function getSeverityByName(name: string): ISeverity { const normalizedName = name?.toUpperCase(); diff --git a/shared/spdx/parseSpdxSecurityAdvisoriesLegacy.ts b/shared/spdx/parseSpdxSecurityAdvisoriesLegacy.ts index 321e291..2592fdb 100644 --- a/shared/spdx/parseSpdxSecurityAdvisoriesLegacy.ts +++ b/shared/spdx/parseSpdxSecurityAdvisoriesLegacy.ts @@ -16,7 +16,6 @@ import { IPackage } from '../models/spdx/2.3/IPackage'; * @returns */ export function parseSpdxSecurityAdvisoriesLegacy(pkg: IPackage): ISecurityVulnerability[] | undefined { - console.info('Parsing security advisories from legacy SPDX document...'); return parseExternalRefsAs( pkg.externalRefs || [], ExternalRefCategory.Security, diff --git a/ui/components/SpdxSummaryCard.tsx b/ui/components/SpdxSummaryCard.tsx deleted file mode 100644 index ea58a99..0000000 --- a/ui/components/SpdxSummaryCard.tsx +++ /dev/null @@ -1,264 +0,0 @@ -import * as React from 'react'; - -import { Card } from 'azure-devops-ui/Card'; -import { ZeroData } from 'azure-devops-ui/ZeroData'; - -import { SecurityAdvisoryIdentifierType } from '../../shared/ghsa/ISecurityAdvisory'; -import { ISecurityVulnerability } from '../../shared/ghsa/ISecurityVulnerability'; -import { getHexStringFromColor } from '../../shared/models/severity/IColor'; -import { ISeverity } from '../../shared/models/severity/ISeverity'; -import { SEVERITIES } from '../../shared/models/severity/Severities'; -import { spdxConstantsAreEqual } from '../../shared/models/spdx/2.3/Constants'; -import { IDocument, isPackageTopLevel } from '../../shared/models/spdx/2.3/IDocument'; -import { ExternalRefCategory, getExternalRefPackageManagerName } from '../../shared/models/spdx/2.3/IExternalRef'; -import { IFile } from '../../shared/models/spdx/2.3/IFile'; -import { getLicensesFromExpression, ILicense } from '../../shared/models/spdx/2.3/ILicense'; -import { - getPackageLicenseExpression, - getPackageSupplierOrganization, - IPackage, -} from '../../shared/models/spdx/2.3/IPackage'; - -import { VulnerabilitiesSummaryBadge } from './VulnerabilitiesSummaryBadge'; - -interface ChartData {} - -interface Props { - document: IDocument; - files: IFile[]; - packages: IPackage[]; - securityAdvisories: ISecurityVulnerability[]; - licenses: ILicense[]; - suppliers: string[]; -} - -interface State { - files?: { - total: number; - }; - packages?: { - total: number; - groupedByPackageManager: Record; - groupedByType: Record; - groupedByLicense: Record; - groupedBySupplier: Record; - groupedByVulnerable: Record; - }; - securityAdvisories?: { - total: number; - chartBySeverity: ChartData; - byAgeInDays: Record; - groupedBySeverity: Record; - groupedByPackageName: Record; - groupedByFixable: Record; - groupedByCvssScore: Record; - groupedByWeakness: Record; - }; - licenses?: { - total: number; - }; - suppliers?: { - total: number; - }; -} - -export class SpdxSummaryCard extends React.Component { - constructor(props: Props) { - super(props); - this.state = {}; - } - - static getDerivedStateFromProps(props: Props): State { - return { - files: !props.files - ? undefined - : { - total: props.files.length, - }, - packages: !props.packages - ? undefined - : { - total: props.packages.length, - groupedByPackageManager: props.packages.reduce( - (acc, p) => { - const name = getExternalRefPackageManagerName(p.externalRefs) || 'Other'; - acc[name] = (acc[name] || 0) + 1; - return acc; - }, - {} as { [name: string]: number }, - ), - groupedByType: props.packages.reduce( - (acc, p) => { - const type = isPackageTopLevel(props.document, p.SPDXID) ? 'Top Level' : 'Transitive'; - acc[type] = (acc[type] || 0) + 1; - return acc; - }, - {} as { [name: string]: number }, - ), - groupedByLicense: props.packages.reduce( - (acc, p) => { - getLicensesFromExpression(getPackageLicenseExpression(p) || '')?.forEach((license) => { - acc[license.licenseId] = (acc[license.licenseId] || 0) + 1; - }); - return acc; - }, - {} as { [name: string]: number }, - ), - groupedBySupplier: props.packages.reduce( - (acc, p) => { - const supplier = getPackageSupplierOrganization(p); - if (supplier) { - acc[supplier] = (acc[supplier] || 0) + 1; - } - return acc; - }, - {} as { [name: string]: number }, - ), - groupedByVulnerable: props.packages.reduce( - (acc, p) => { - const hasVulnerabilities = p.externalRefs?.some((ref) => - spdxConstantsAreEqual(ref.referenceCategory, ExternalRefCategory.Security), - ); - const key = hasVulnerabilities ? 'Vulnerable' : 'Not Vulnerable'; - acc[key] = (acc[key] || 0) + 1; - return acc; - }, - {} as { [name: string]: number }, - ), - }, - securityAdvisories: !props.securityAdvisories - ? undefined - : { - total: props.securityAdvisories.length, - byAgeInDays: props.securityAdvisories.reduce( - (acc, vuln) => { - const id = vuln.advisory.identifiers?.find((id) => id.type == SecurityAdvisoryIdentifierType.Ghsa); - if (id) { - acc[id.value] = Math.floor( - (Date.now() - new Date(vuln.advisory.publishedAt).getTime()) / (1000 * 60 * 60 * 24), - ); - } - return acc; - }, - {} as { [name: string]: number }, - ), - chartBySeverity: SpdxSummaryCard.getChartBySeverity(props.securityAdvisories), - groupedBySeverity: props.securityAdvisories.reduce( - (acc, vuln) => { - const severity = vuln.advisory.severity?.toPascalCase(); - if (severity) { - acc[severity] = (acc[severity] || 0) + 1; - } - return acc; - }, - {} as { [name: string]: number }, - ), - groupedByPackageName: props.securityAdvisories.reduce( - (acc, vuln) => { - acc[vuln.package.name] = (acc[vuln.package.name] || 0) + 1; - return acc; - }, - {} as { [name: string]: number }, - ), - groupedByFixable: props.securityAdvisories.reduce( - (acc, vuln) => { - const key = vuln.firstPatchedVersion ? 'Fixable' : 'Not Fixable'; - acc[key] = (acc[key] || 0) + 1; - return acc; - }, - {} as { [name: string]: number }, - ), - groupedByCvssScore: props.securityAdvisories.reduce( - (acc, vuln) => { - const key = Math.floor(vuln.advisory.cvss?.score || 0); - if (key > 0) { - acc[key] = (acc[key] || 0) + 1; - } - return acc; - }, - {} as { [name: number]: number }, - ), - groupedByWeakness: props.securityAdvisories.reduce( - (acc, vuln) => { - vuln.advisory.cwes.forEach((weakness) => { - acc[weakness.id] = (acc[weakness.id] || 0) + 1; - }); - return acc; - }, - {} as { [name: string]: number }, - ), - }, - licenses: !props.licenses - ? undefined - : { - total: props.licenses.length, - }, - suppliers: !props.suppliers - ? undefined - : { - total: props.suppliers.length, - }, - }; - } - - static getChartBySeverity(securityAdvisories: ISecurityVulnerability[]): ChartData { - const severities = SEVERITIES.filter((s: ISeverity) => s.id > 0) - .orderBy((s: ISeverity) => s.weight, false) - .map((s: ISeverity) => { - return { - name: s.name, - color: getHexStringFromColor(s.color), - count: securityAdvisories.filter((v) => v.advisory.severity.toUpperCase() === s.name.toUpperCase()).length, - }; - }); - - // TODO: Implement chart data - return {}; - } - - public componentDidUpdate(prevProps: Readonly): void { - if ( - prevProps.document !== this.props.document || - prevProps.files !== this.props.files || - prevProps.packages !== this.props.packages || - prevProps.securityAdvisories !== this.props.securityAdvisories || - prevProps.licenses !== this.props.licenses || - prevProps.suppliers !== this.props.suppliers - ) { - this.setState(SpdxSummaryCard.getDerivedStateFromProps(this.props)); - } - } - - public render(): JSX.Element { - if (!this.props?.document) { - return ( - - ); - } - return ( - -
-
-
- {this.props.securityAdvisories.length > 0 && ( - - )} -
-
-
-
- TODO: Add pretty dashboard for: -
{JSON.stringify(this.state, null, 2)}
-
-
-
-
- ); - } -} diff --git a/ui/components/charts/BarChart.scss b/ui/components/charts/BarChart.scss new file mode 100644 index 0000000..b1a2dec --- /dev/null +++ b/ui/components/charts/BarChart.scss @@ -0,0 +1,3 @@ +.bar-chart .title { + margin-bottom: 0px; +} diff --git a/ui/components/charts/BarChart.tsx b/ui/components/charts/BarChart.tsx new file mode 100644 index 0000000..b96c8e2 --- /dev/null +++ b/ui/components/charts/BarChart.tsx @@ -0,0 +1,85 @@ +import * as React from 'react'; + +import { BarSeriesType, cheerfulFiestaPalette, BarChart as MuiBarChart } from '@mui/x-charts'; +import { MakeOptional } from '@mui/x-charts/internals'; + +import './BarChart.scss'; + +export interface BarChartSeries { + color?: string; + label: string; + data: number[]; + stack?: string; +} + +interface Props { + className?: string; + colors?: string[]; + bands?: string[]; + data: BarChartSeries[]; + layout: 'horizontal' | 'vertical'; + title?: string; + width?: number; + height?: number; +} + +interface State { + series: MakeOptional[]; +} + +export class BarChart extends React.Component { + constructor(props: Props) { + super(props); + this.state = BarChart.getDerivedStateFromProps(props); + } + + static getDerivedStateFromProps(props: Props): State { + return { + series: props.data.map((d) => ({ + type: 'bar', + layout: props.layout, + label: d.label, + data: d.data, + stack: d.stack, + ...(d.color ? { color: d.color } : {}), + })), + }; + } + + public componentDidUpdate(prevProps: Readonly): void { + if (prevProps.data !== this.props.data) { + this.setState(BarChart.getDerivedStateFromProps(this.props)); + } + } + + public render(): JSX.Element { + return ( +
+ {this.props.title &&

{this.props.title}

} + +
+ ); + } +} diff --git a/ui/components/charts/PieChart.scss b/ui/components/charts/PieChart.scss new file mode 100644 index 0000000..3e4ef8b --- /dev/null +++ b/ui/components/charts/PieChart.scss @@ -0,0 +1,3 @@ +.pie-chart .title { + margin-bottom: 4px; +} diff --git a/ui/components/charts/PieChart.tsx b/ui/components/charts/PieChart.tsx new file mode 100644 index 0000000..9204372 --- /dev/null +++ b/ui/components/charts/PieChart.tsx @@ -0,0 +1,83 @@ +import * as React from 'react'; + +import { cheerfulFiestaPalette, PieChart as MuiPieChart, PieValueType as MuiPieChartValue } from '@mui/x-charts'; +import { MakeOptional } from '@mui/x-charts/internals'; + +import './PieChart.scss'; + +export interface PieChartValue { + label: string; + value: number; +} + +interface Props { + className?: string; + colors?: string[]; + data: PieChartValue[]; + title?: string; + width?: number; + height?: number; +} + +interface State { + data: MakeOptional[]; + total: number; +} + +export class PieChart extends React.Component { + constructor(props: Props) { + super(props); + this.state = PieChart.getDerivedStateFromProps(props); + } + + static getDerivedStateFromProps(props: Props): State { + return { + data: props.data, + total: props.data.reduce((acc, item) => acc + item.value, 0), + }; + } + + public componentDidUpdate(prevProps: Readonly): void { + if (prevProps.data !== this.props.data) { + this.setState(PieChart.getDerivedStateFromProps(this.props)); + } + } + + public render(): JSX.Element { + return ( +
+ {this.props.title &&

{this.props.title}

} + `${item.label?.substring(0, 20)}`, + arcLabelMinAngle: 30, + innerRadius: '50%', + highlightScope: { fade: 'global', highlight: 'item' }, + faded: { color: 'gray', additionalRadius: -10, innerRadius: 60 }, + data: this.state.data || [], + }, + ]} + slotProps={{ + legend: { + hidden: true, + direction: 'column', + position: { + horizontal: 'middle', + vertical: 'bottom', + }, + labelStyle: { + fill: 'var(--text-primary-color)', + fontSize: '0.8em', + }, + }, + }} + width={this.props.width || 250} + height={this.props.height || 250} + /> +
+ ); + } +} diff --git a/ui/components/charts/Tile.scss b/ui/components/charts/Tile.scss new file mode 100644 index 0000000..2c4e00c --- /dev/null +++ b/ui/components/charts/Tile.scss @@ -0,0 +1,8 @@ +.tile { + border: solid 1px var(--border-subtle-color); +} + +.tile .title { + margin-top: 4px; + margin-bottom: 4px; +} diff --git a/ui/components/charts/Tile.tsx b/ui/components/charts/Tile.tsx new file mode 100644 index 0000000..d796bef --- /dev/null +++ b/ui/components/charts/Tile.tsx @@ -0,0 +1,58 @@ +import * as React from 'react'; + +import { IColor } from 'azure-devops-extension-api'; +import { rgbToHex } from 'azure-devops-ui/Utilities/Color'; + +import './Tile.scss'; + +interface Props { + className?: string; + color?: IColor; + value: string; + title?: string; + header?: string; + footer?: string; + size?: number; +} + +interface State {} + +export class Tile extends React.Component { + constructor(props: Props) { + super(props); + this.state = Tile.getDerivedStateFromProps(props); + } + + static getDerivedStateFromProps(props: Props): State { + return {}; + } + + public componentDidUpdate(prevProps: Readonly): void { + if (prevProps.value !== this.props.value) { + this.setState(Tile.getDerivedStateFromProps(this.props)); + } + } + + public render(): JSX.Element { + return ( +
+ {this.props.title &&

{this.props.title}

} +
+ {this.props.value} +
+ {this.props.header &&
{this.props.header}
} + {this.props.footer &&
{this.props.footer}
} +
+ ); + } +} diff --git a/ui/components/SbomDocumentHeader.tsx b/ui/components/sbom/SbomDocumentHeader.tsx similarity index 94% rename from ui/components/SbomDocumentHeader.tsx rename to ui/components/sbom/SbomDocumentHeader.tsx index 522ce5e..b43c4f0 100644 --- a/ui/components/SbomDocumentHeader.tsx +++ b/ui/components/sbom/SbomDocumentHeader.tsx @@ -12,10 +12,10 @@ import { import { HeaderCommandBar, IHeaderCommandBarItem } from 'azure-devops-ui/HeaderCommandBar'; import { MenuItemType } from 'azure-devops-ui/Menu'; -import { ISbomBuildArtifact } from '../../shared/models/ISbomBuildArtifact'; -import { getCreatorOrganization, getCreatorTool } from '../../shared/models/spdx/2.3/ICreationInfo'; -import { convertSpdxToSvgAsync } from '../../shared/spdx/convertSpdxToSvg'; -import { convertSpdxToXlsxAsync } from '../../shared/spdx/convertSpdxToXlsx'; +import { ISbomBuildArtifact } from '../../../shared/models/ISbomBuildArtifact'; +import { getCreatorOrganization, getCreatorTool } from '../../../shared/models/spdx/2.3/ICreationInfo'; +import { convertSpdxToSvgAsync } from '../../../shared/spdx/convertSpdxToSvg'; +import { convertSpdxToXlsxAsync } from '../../../shared/spdx/convertSpdxToXlsx'; interface Props { artifact: ISbomBuildArtifact; diff --git a/ui/components/SbomDocumentPage.tsx b/ui/components/sbom/SbomDocumentPage.tsx similarity index 92% rename from ui/components/SbomDocumentPage.tsx rename to ui/components/sbom/SbomDocumentPage.tsx index 3cd668e..06b4b26 100644 --- a/ui/components/SbomDocumentPage.tsx +++ b/ui/components/sbom/SbomDocumentPage.tsx @@ -7,21 +7,21 @@ import { Tab, TabBar, TabContent, TabSize } from 'azure-devops-ui/Tabs'; import { InlineKeywordFilterBarItem } from 'azure-devops-ui/TextFilterBarItem'; import { Filter, IFilter } from 'azure-devops-ui/Utilities/Filter'; -import { ISecurityVulnerability } from '../../shared/ghsa/ISecurityVulnerability'; -import { ISbomBuildArtifact } from '../../shared/models/ISbomBuildArtifact'; +import { ISecurityVulnerability } from '../../../shared/ghsa/ISecurityVulnerability'; +import { ISbomBuildArtifact } from '../../../shared/models/ISbomBuildArtifact'; import { ExternalRefCategory, ExternalRefSecurityType, parseExternalRefsAs, -} from '../../shared/models/spdx/2.3/IExternalRef'; -import { IFile } from '../../shared/models/spdx/2.3/IFile'; -import { getLicensesFromExpression, ILicense } from '../../shared/models/spdx/2.3/ILicense'; +} from '../../../shared/models/spdx/2.3/IExternalRef'; +import { IFile } from '../../../shared/models/spdx/2.3/IFile'; +import { getLicensesFromExpression, ILicense } from '../../../shared/models/spdx/2.3/ILicense'; import { getPackageLicenseExpression, getPackageSupplierOrganization, IPackage, -} from '../../shared/models/spdx/2.3/IPackage'; -import { parseSpdxSecurityAdvisoriesLegacy } from '../../shared/spdx/parseSpdxSecurityAdvisoriesLegacy'; +} from '../../../shared/models/spdx/2.3/IPackage'; +import { parseSpdxSecurityAdvisoriesLegacy } from '../../../shared/spdx/parseSpdxSecurityAdvisoriesLegacy'; import { SbomDocumentHeader } from './SbomDocumentHeader'; import { SpdxFileTableCard } from './SpdxFileTableCard'; @@ -52,7 +52,7 @@ export class SbomDocumentPage extends React.Component { constructor(props: Props) { super(props); this.state = SbomDocumentPage.getDerivedStateFromProps(props); - this.selectedTabId = new ObservableValue('files'); + this.selectedTabId = new ObservableValue('summary'); this.filter = new Filter(); } @@ -118,7 +118,7 @@ export class SbomDocumentPage extends React.Component { renderAdditionalContent={this.onRenderFilterBar} className="margin-vertical-16" > - {window.location.hostname === 'localhost' ? : null} + {this.state.packages.length ? ( diff --git a/ui/components/SpdxFileTableCard.tsx b/ui/components/sbom/SpdxFileTableCard.tsx similarity index 96% rename from ui/components/SpdxFileTableCard.tsx rename to ui/components/sbom/SpdxFileTableCard.tsx index bc1b32c..9d14133 100644 --- a/ui/components/SpdxFileTableCard.tsx +++ b/ui/components/sbom/SpdxFileTableCard.tsx @@ -15,9 +15,9 @@ import { import { FILTER_CHANGE_EVENT, IFilter } from 'azure-devops-ui/Utilities/Filter'; import { ZeroData } from 'azure-devops-ui/ZeroData'; -import { ChecksumAlgorithm, getChecksum } from '../../shared/models/spdx/2.3/IChecksum'; -import { IDocument } from '../../shared/models/spdx/2.3/IDocument'; -import { IFile } from '../../shared/models/spdx/2.3/IFile'; +import { ChecksumAlgorithm, getChecksum } from '../../../shared/models/spdx/2.3/IChecksum'; +import { IDocument } from '../../../shared/models/spdx/2.3/IDocument'; +import { IFile } from '../../../shared/models/spdx/2.3/IFile'; interface IFileTableItem extends ISimpleTableCell { id: string; diff --git a/ui/components/SpdxLicenseTableCard.tsx b/ui/components/sbom/SpdxLicenseTableCard.tsx similarity index 95% rename from ui/components/SpdxLicenseTableCard.tsx rename to ui/components/sbom/SpdxLicenseTableCard.tsx index 065b80e..592e696 100644 --- a/ui/components/SpdxLicenseTableCard.tsx +++ b/ui/components/sbom/SpdxLicenseTableCard.tsx @@ -17,13 +17,13 @@ import { Tooltip } from 'azure-devops-ui/TooltipEx'; import { FILTER_CHANGE_EVENT, IFilter } from 'azure-devops-ui/Utilities/Filter'; import { ZeroData } from 'azure-devops-ui/ZeroData'; -import { getLicenseRiskAssessment, LicenseRiskSeverity } from '../../shared/ghsa/ILicense'; -import { ISeverity } from '../../shared/models/severity/ISeverity'; -import { getSeverityByName } from '../../shared/models/severity/Severities'; -import { IDocument } from '../../shared/models/spdx/2.3/IDocument'; -import { getExternalRefPackageManagerUrl } from '../../shared/models/spdx/2.3/IExternalRef'; -import { ILicense } from '../../shared/models/spdx/2.3/ILicense'; -import { getPackageLicenseExpression } from '../../shared/models/spdx/2.3/IPackage'; +import { getLicenseRiskAssessment, LicenseRiskSeverity } from '../../../shared/ghsa/ILicense'; +import { ISeverity } from '../../../shared/models/severity/ISeverity'; +import { getSeverityByName } from '../../../shared/models/severity/Severities'; +import { IDocument } from '../../../shared/models/spdx/2.3/IDocument'; +import { getExternalRefPackageManagerUrl } from '../../../shared/models/spdx/2.3/IExternalRef'; +import { ILicense } from '../../../shared/models/spdx/2.3/ILicense'; +import { getPackageLicenseExpression } from '../../../shared/models/spdx/2.3/IPackage'; interface ILicenseTableItem { id: string; diff --git a/ui/components/SpdxPackageTableCard.tsx b/ui/components/sbom/SpdxPackageTableCard.tsx similarity index 96% rename from ui/components/SpdxPackageTableCard.tsx rename to ui/components/sbom/SpdxPackageTableCard.tsx index 31c346f..f63a324 100644 --- a/ui/components/SpdxPackageTableCard.tsx +++ b/ui/components/sbom/SpdxPackageTableCard.tsx @@ -15,22 +15,22 @@ import { import { FILTER_CHANGE_EVENT, IFilter } from 'azure-devops-ui/Utilities/Filter'; import { ZeroData } from 'azure-devops-ui/ZeroData'; -import { ISecurityVulnerability } from '../../shared/ghsa/ISecurityVulnerability'; -import { getSeverityByName } from '../../shared/models/severity/Severities'; -import { getPackageDependsOnChain, IDocument, isPackageTopLevel } from '../../shared/models/spdx/2.3/IDocument'; +import { ISecurityVulnerability } from '../../../shared/ghsa/ISecurityVulnerability'; +import { getSeverityByName } from '../../../shared/models/severity/Severities'; +import { getPackageDependsOnChain, IDocument, isPackageTopLevel } from '../../../shared/models/spdx/2.3/IDocument'; import { ExternalRefCategory, ExternalRefSecurityType, getExternalRefPackageManagerName, getExternalRefPackageManagerUrl, parseExternalRefsAs, -} from '../../shared/models/spdx/2.3/IExternalRef'; +} from '../../../shared/models/spdx/2.3/IExternalRef'; import { getPackageLicenseExpression, getPackageSupplierOrganization, IPackage, -} from '../../shared/models/spdx/2.3/IPackage'; -import { parseSpdxSecurityAdvisoriesLegacy } from '../../shared/spdx/parseSpdxSecurityAdvisoriesLegacy'; +} from '../../../shared/models/spdx/2.3/IPackage'; +import { parseSpdxSecurityAdvisoriesLegacy } from '../../../shared/spdx/parseSpdxSecurityAdvisoriesLegacy'; import { VulnerabilitiesSummaryBadge } from './VulnerabilitiesSummaryBadge'; diff --git a/ui/components/SpdxRelationshipCard.tsx b/ui/components/sbom/SpdxRelationshipCard.tsx similarity index 97% rename from ui/components/SpdxRelationshipCard.tsx rename to ui/components/sbom/SpdxRelationshipCard.tsx index 5312aee..6b29e8e 100644 --- a/ui/components/SpdxRelationshipCard.tsx +++ b/ui/components/sbom/SpdxRelationshipCard.tsx @@ -4,7 +4,7 @@ import { TransformComponent, TransformWrapper } from 'react-zoom-pan-pinch'; import { ZeroData } from 'azure-devops-ui/ZeroData'; -import { IDocument } from '../../shared/models/spdx/2.3/IDocument'; +import { IDocument } from '../../../shared/models/spdx/2.3/IDocument'; interface Props { document: IDocument; diff --git a/ui/components/SpdxSecurityTableCard.tsx b/ui/components/sbom/SpdxSecurityTableCard.tsx similarity index 97% rename from ui/components/SpdxSecurityTableCard.tsx rename to ui/components/sbom/SpdxSecurityTableCard.tsx index a2cd13f..baa47e1 100644 --- a/ui/components/SpdxSecurityTableCard.tsx +++ b/ui/components/sbom/SpdxSecurityTableCard.tsx @@ -18,12 +18,12 @@ import { import { FILTER_CHANGE_EVENT, IFilter } from 'azure-devops-ui/Utilities/Filter'; import { ZeroData } from 'azure-devops-ui/ZeroData'; -import { IPackage } from '../../shared/ghsa/IPackage'; -import { SecurityAdvisoryIdentifierType } from '../../shared/ghsa/ISecurityAdvisory'; -import { ISecurityVulnerability } from '../../shared/ghsa/ISecurityVulnerability'; -import { ISeverity } from '../../shared/models/severity/ISeverity'; -import { getSeverityByName } from '../../shared/models/severity/Severities'; -import { getPackageDependsOnChain, IDocument } from '../../shared/models/spdx/2.3/IDocument'; +import { IPackage } from '../../../shared/ghsa/IPackage'; +import { SecurityAdvisoryIdentifierType } from '../../../shared/ghsa/ISecurityAdvisory'; +import { ISecurityVulnerability } from '../../../shared/ghsa/ISecurityVulnerability'; +import { ISeverity } from '../../../shared/models/severity/ISeverity'; +import { getSeverityByName } from '../../../shared/models/severity/Severities'; +import { getPackageDependsOnChain, IDocument } from '../../../shared/models/spdx/2.3/IDocument'; interface ISecurityAdvisoryTableItem { ghsaId: string; diff --git a/ui/components/sbom/SpdxSummaryCard.tsx b/ui/components/sbom/SpdxSummaryCard.tsx new file mode 100644 index 0000000..a9ed73e --- /dev/null +++ b/ui/components/sbom/SpdxSummaryCard.tsx @@ -0,0 +1,309 @@ +import * as React from 'react'; + +import { Card } from 'azure-devops-ui/Card'; +import { rgbToHex } from 'azure-devops-ui/Utilities/Color'; +import { ZeroData } from 'azure-devops-ui/ZeroData'; + +import { createTheme, Theme, ThemeProvider } from '@mui/material'; + +import { SecurityAdvisoryIdentifierType } from '../../../shared/ghsa/ISecurityAdvisory'; +import { ISecurityVulnerability } from '../../../shared/ghsa/ISecurityVulnerability'; +import { ISeverity } from '../../../shared/models/severity/ISeverity'; +import { DEFAULT_SEVERITY, getSeverityByName, SEVERITIES } from '../../../shared/models/severity/Severities'; +import { spdxConstantsAreEqual } from '../../../shared/models/spdx/2.3/Constants'; +import { IDocument, isPackageTopLevel } from '../../../shared/models/spdx/2.3/IDocument'; +import { ExternalRefCategory, getExternalRefPackageManagerName } from '../../../shared/models/spdx/2.3/IExternalRef'; +import { IFile } from '../../../shared/models/spdx/2.3/IFile'; +import { ILicense } from '../../../shared/models/spdx/2.3/ILicense'; +import { IPackage } from '../../../shared/models/spdx/2.3/IPackage'; + +import { BarChart, BarChartSeries } from '../charts/BarChart'; +import { PieChart, PieChartValue } from '../charts/PieChart'; +import { Tile } from '../charts/Tile'; + +interface Props { + document: IDocument; + files: IFile[]; + packages: IPackage[]; + securityAdvisories: ISecurityVulnerability[]; + licenses: ILicense[]; + suppliers: string[]; +} + +interface State { + theme: Theme; + files?: { + total: number; + }; + packageManagers: string[]; + packages?: { + total: number; + totalVulnerable: number; + packageManagersChartData: PieChartValue[]; + packageTypesChartData: PieChartValue[]; + }; + securityAdvisories?: { + total: number; + weaknesses: string[]; + vulnHighestSeverity: ISeverity; + vulnByPackageManagerChartData: BarChartSeries[]; + vulnByWeaknessChartData: BarChartSeries[]; + vulnAgeInDaysChartData?: PieChartValue[]; + vulnPackagesChartData?: PieChartValue[]; + vulnFixableChartData?: PieChartValue[]; + vulnCvssScoresChartData?: PieChartValue[]; + }; + licenses?: { + total: number; + }; + suppliers?: { + total: number; + }; +} + +export class SpdxSummaryCard extends React.Component { + constructor(props: Props) { + super(props); + this.state = { theme: SpdxSummaryCard.getChartTheme(), packageManagers: [] }; + } + + static getDerivedStateFromProps(props: Props): State { + const packageManagers = reduceAsMap( + props.packages, + (p) => getExternalRefPackageManagerName(p.externalRefs) || 'Other', + (k, v) => k, + ) + .orderBy((pm: string) => pm, false) + .distinct(); + const weaknesses = props.securityAdvisories + .flatMap((v) => v.advisory.cwes || []) + .map((w) => w.id) + .orderBy((w: string) => new Number(w.match(/\d+/)?.[0] || '0'), false) + .distinct(); + + return { + theme: SpdxSummaryCard.getChartTheme(), + files: props.files ? { total: props.files.length } : undefined, + packageManagers: packageManagers, + packages: props.packages + ? { + total: props.packages.length, + totalVulnerable: props.packages.filter((p) => + p.externalRefs?.some((ref) => spdxConstantsAreEqual(ref.referenceCategory, ExternalRefCategory.Security)), + ).length, + packageManagersChartData: reduceAsMap( + props.packages, + (p) => getExternalRefPackageManagerName(p.externalRefs) || 'Other', + (k, v) => ({ label: k, value: v }), + ), + packageTypesChartData: reduceAsMap( + props.packages, + (p) => (isPackageTopLevel(props.document, p.SPDXID) ? 'Top Level' : 'Transitive'), + (k, v) => ({ label: k, value: v }), + ), + } + : undefined, + securityAdvisories: props.securityAdvisories + ? { + total: props.securityAdvisories.length, + weaknesses: weaknesses, + vulnHighestSeverity: props.securityAdvisories + .map((s) => getSeverityByName(s.advisory.severity)) + .reduce((max, s) => (s.weight > max.weight ? s : max), DEFAULT_SEVERITY), + vulnByPackageManagerChartData: SpdxSummaryCard.getVulnerabilitiesByPackageManagerChartData( + props.packages, + packageManagers, + props.securityAdvisories, + ), + vulnByWeaknessChartData: SpdxSummaryCard.getVulnerabiilityWeaknessesChartData( + weaknesses, + props.securityAdvisories, + ), + vulnAgeInDaysChartData: summariseAsMap( + props.securityAdvisories, + (v) => + v.advisory.identifiers?.find((id) => id.type == SecurityAdvisoryIdentifierType.Ghsa)?.value || + 'Unknown', + (v) => Math.floor((Date.now() - new Date(v.advisory.publishedAt).getTime()) / (1000 * 60 * 60 * 24)), + ), + vulnPackagesChartData: reduceAsMap( + props.securityAdvisories, + (v) => v.package.name, + (k, v) => ({ label: k, value: v }), + ), + vulnFixableChartData: reduceAsMap( + props.securityAdvisories, + (v) => (v.firstPatchedVersion ? 'Fixable' : 'Not Fixable'), + (k, v) => ({ label: k, value: v }), + ), + vulnCvssScoresChartData: reduceAsMap( + props.securityAdvisories, + (v) => Math.floor(v.advisory.cvss?.score || 0).toString(), + (k, v) => ({ label: k, value: v }), + ), + } + : undefined, + licenses: props.licenses ? { total: props.licenses.length } : undefined, + suppliers: props.suppliers ? { total: props.suppliers.length } : undefined, + }; + } + + static getChartTheme(): Theme { + return createTheme({ palette: { mode: 'dark' } }); + } + + static getVulnerabilitiesByPackageManagerChartData( + packages: IPackage[], + packageManagers: string[], + securityAdvisories: ISecurityVulnerability[], + ): BarChartSeries[] { + return SEVERITIES.filter((s: ISeverity) => s.id > 0) + .orderBy((s: ISeverity) => s.weight, false) + .map((s: ISeverity) => { + return { + color: rgbToHex(s.color), + label: s.name, + data: packageManagers.map( + (pm) => + securityAdvisories.filter((v) => { + const pkg = packages.find((p) => p.name == v.package.name); + return ( + pkg && + pkg.externalRefs && + getExternalRefPackageManagerName(pkg.externalRefs) == pm && + v.advisory.severity.toUpperCase() === s.name.toUpperCase() + ); + }).length, + ), + stack: 'severity', + }; + }); + } + + static getVulnerabiilityWeaknessesChartData( + weaknesses: string[], + securityAdvisories: ISecurityVulnerability[], + ): BarChartSeries[] { + return [ + { + label: 'Vulnerabilities', + data: weaknesses.map((w) => securityAdvisories.filter((v) => v.advisory.cwes?.some((c) => c.id == w)).length), + }, + ]; + } + + public componentDidUpdate(prevProps: Readonly): void { + if ( + prevProps.document !== this.props.document || + prevProps.files !== this.props.files || + prevProps.packages !== this.props.packages || + prevProps.securityAdvisories !== this.props.securityAdvisories || + prevProps.licenses !== this.props.licenses || + prevProps.suppliers !== this.props.suppliers + ) { + this.setState(SpdxSummaryCard.getDerivedStateFromProps(this.props)); + } + } + + public render(): JSX.Element { + if (!this.props?.document) { + return ( + + ); + } + return ( + + +
+
+ 0 && this.state.packages!.total > 0 + ? ((this.state.packages!.totalVulnerable / this.state.packages!.total) * 100).toFixed(2) + : 0) + '%' + } + title="Vulnerable Packages" + header={`${this.state.packages?.totalVulnerable || 0} of ${this.state.packages?.total || 0} packages are vulnerable`} + size={200} + /> + + +
+
+ + + +
+
+
+
+ ); + } +} + +function reduceAsMap( + array: T[], + keySelector: (item: T) => K | K[], + mapper: (key: K, value: number) => M, +) { + const reduced = Object.entries( + array.reduce( + (acc, item) => { + const key = keySelector(item); + const keys = Array.isArray(key) ? key : [key]; + keys.forEach((k) => { + acc[k] = (acc[k] || 0) + 1; + }); + return acc; + }, + {} as Record, + ), + ) as [K, number][]; + return reduced.sort().map(([k, v]) => mapper(k, v)); +} + +function summariseAsMap( + array: T[], + keySelector: (item: T) => K | K[], + valueSelector: (item: T) => V, + mapper?: (key: K, value: V) => M, +) { + const summarised = Object.entries( + array.reduce( + (acc, item) => { + const key = keySelector(item); + const keys = Array.isArray(key) ? key : [key]; + keys.forEach((k) => { + acc[k] = valueSelector(item); + }); + return acc; + }, + {} as Record, + ), + ) as [K, V][]; + return summarised.sort().map(([k, v]) => (mapper ? mapper(k, v) : { label: k, value: v })); +} diff --git a/ui/components/SpdxSupplierTableCard.tsx b/ui/components/sbom/SpdxSupplierTableCard.tsx similarity index 96% rename from ui/components/SpdxSupplierTableCard.tsx rename to ui/components/sbom/SpdxSupplierTableCard.tsx index fce17d5..2636593 100644 --- a/ui/components/SpdxSupplierTableCard.tsx +++ b/ui/components/sbom/SpdxSupplierTableCard.tsx @@ -15,9 +15,9 @@ import { import { FILTER_CHANGE_EVENT, IFilter } from 'azure-devops-ui/Utilities/Filter'; import { ZeroData } from 'azure-devops-ui/ZeroData'; -import { IDocument } from '../../shared/models/spdx/2.3/IDocument'; -import { getExternalRefPackageManagerUrl } from '../../shared/models/spdx/2.3/IExternalRef'; -import { getPackageSupplierOrganization } from '../../shared/models/spdx/2.3/IPackage'; +import { IDocument } from '../../../shared/models/spdx/2.3/IDocument'; +import { getExternalRefPackageManagerUrl } from '../../../shared/models/spdx/2.3/IExternalRef'; +import { getPackageSupplierOrganization } from '../../../shared/models/spdx/2.3/IPackage'; interface ISupplierTableItem { id: string; diff --git a/ui/components/VulnerabilitiesSummaryBadge.scss b/ui/components/sbom/VulnerabilitiesSummaryBadge.scss similarity index 100% rename from ui/components/VulnerabilitiesSummaryBadge.scss rename to ui/components/sbom/VulnerabilitiesSummaryBadge.scss diff --git a/ui/components/VulnerabilitiesSummaryBadge.tsx b/ui/components/sbom/VulnerabilitiesSummaryBadge.tsx similarity index 86% rename from ui/components/VulnerabilitiesSummaryBadge.tsx rename to ui/components/sbom/VulnerabilitiesSummaryBadge.tsx index d1dbf4a..55fd178 100644 --- a/ui/components/VulnerabilitiesSummaryBadge.tsx +++ b/ui/components/sbom/VulnerabilitiesSummaryBadge.tsx @@ -3,13 +3,14 @@ import * as React from 'react'; import { Pill, PillSize, PillVariant } from 'azure-devops-ui/Pill'; import { Tooltip } from 'azure-devops-ui/TooltipEx'; -import { ISecurityVulnerability } from '../../shared/ghsa/ISecurityVulnerability'; -import { ISeverity } from '../../shared/models/severity/ISeverity'; -import { DEFAULT_SEVERITY, SEVERITIES } from '../../shared/models/severity/Severities'; +import { ISecurityVulnerability } from '../../../shared/ghsa/ISecurityVulnerability'; +import { ISeverity } from '../../../shared/models/severity/ISeverity'; +import { DEFAULT_SEVERITY, SEVERITIES } from '../../../shared/models/severity/Severities'; import './VulnerabilitiesSummaryBadge.scss'; interface Props { + className?: string; vulnerabilities: ISecurityVulnerability[]; } @@ -48,7 +49,7 @@ export class VulnerabilitiesSummaryBadge extends React.Component { public render(): JSX.Element { return ( -
+
{this.state.severitySummary .sort((a, b) => b.severity.id - a.severity.id) .map((severitySummary, index) => ( diff --git a/ui/package-lock.json b/ui/package-lock.json index ace1c0a..ea7fed7 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -5,6 +5,10 @@ "packages": { "": { "dependencies": { + "@emotion/react": "^11.13.5", + "@emotion/styled": "^11.13.5", + "@mui/material": "^6.1.10", + "@mui/x-charts": "^7.23.0", "azure-devops-extension-api": "^4.245.1", "azure-devops-extension-sdk": "^4.0.2", "azure-devops-ui": "^2.246.0", @@ -12,8 +16,8 @@ "react-zoom-pan-pinch": "^3.6.1" }, "devDependencies": { - "@types/react": "~16.8.2", - "@types/react-dom": "~16.8.0", + "@types/react": "^17.0.0", + "@types/react-dom": "^17.0.0", "copy-webpack-plugin": "^12.0.2", "css-loader": "^7.1.2", "sass": "^1.80.6", @@ -26,6 +30,150 @@ "webpack-dev-server": "^5.1.0" } }, + "node_modules/@babel/code-frame": { + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", + "integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==", + "dependencies": { + "@babel/parser": "^7.26.3", + "@babel/types": "^7.26.3", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", + "dependencies": { + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", + "dependencies": { + "@babel/types": "^7.26.3" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz", + "integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==", + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", + "dependencies": { + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.3.tgz", + "integrity": "sha512-yTmc8J+Sj8yLzwr4PD5Xb/WF3bOYu2C2OoSZPzbuqRm4n98XirsbzaX+GloeO376UnSYIYJ4NCanwV5/ugZkwA==", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.3", + "@babel/parser": "^7.26.3", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.3", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/@babel/types": { + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", "dev": true, @@ -34,9 +182,149 @@ "node": ">=10.0.0" } }, + "node_modules/@emotion/babel-plugin": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", + "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==", + "dependencies": { + "@babel/helper-module-imports": "^7.16.7", + "@babel/runtime": "^7.18.3", + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/serialize": "^1.3.3", + "babel-plugin-macros": "^3.1.0", + "convert-source-map": "^1.5.0", + "escape-string-regexp": "^4.0.0", + "find-root": "^1.1.0", + "source-map": "^0.5.7", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/babel-plugin/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@emotion/cache": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.13.5.tgz", + "integrity": "sha512-Z3xbtJ+UcK76eWkagZ1onvn/wAVb1GOMuR15s30Fm2wrMgC7jzpnO2JZXr4eujTTqoQFUrZIw/rT0c6Zzjca1g==", + "dependencies": { + "@emotion/memoize": "^0.9.0", + "@emotion/sheet": "^1.4.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "stylis": "4.2.0" + } + }, + "node_modules/@emotion/hash": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz", + "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==" + }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.3.1.tgz", + "integrity": "sha512-/ACwoqx7XQi9knQs/G0qKvv5teDMhD7bXYns9N/wM8ah8iNb8jZ2uNO0YOgiq2o2poIvVtJS2YALasQuMSQ7Kw==", + "dependencies": { + "@emotion/memoize": "^0.9.0" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz", + "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==" + }, + "node_modules/@emotion/react": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.13.5.tgz", + "integrity": "sha512-6zeCUxUH+EPF1s+YF/2hPVODeV/7V07YU5x+2tfuRL8MdW6rv5vb2+CBEGTGwBdux0OIERcOS+RzxeK80k2DsQ==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.2", + "@emotion/weak-memoize": "^0.4.0", + "hoist-non-react-statics": "^3.3.1" + }, + "peerDependencies": { + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/serialize": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz", + "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==", + "dependencies": { + "@emotion/hash": "^0.9.2", + "@emotion/memoize": "^0.9.0", + "@emotion/unitless": "^0.10.0", + "@emotion/utils": "^1.4.2", + "csstype": "^3.0.2" + } + }, + "node_modules/@emotion/sheet": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz", + "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==" + }, + "node_modules/@emotion/styled": { + "version": "11.13.5", + "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.13.5.tgz", + "integrity": "sha512-gnOQ+nGLPvDXgIx119JqGalys64lhMdnNQA9TMxhDA4K0Hq5+++OE20Zs5GxiCV9r814xQ2K5WmtofSpHVW6BQ==", + "dependencies": { + "@babel/runtime": "^7.18.3", + "@emotion/babel-plugin": "^11.13.5", + "@emotion/is-prop-valid": "^1.3.0", + "@emotion/serialize": "^1.3.3", + "@emotion/use-insertion-effect-with-fallbacks": "^1.1.0", + "@emotion/utils": "^1.4.2" + }, + "peerDependencies": { + "@emotion/react": "^11.0.0-rc.0", + "react": ">=16.8.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@emotion/unitless": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz", + "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==" + }, + "node_modules/@emotion/use-insertion-effect-with-fallbacks": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.1.0.tgz", + "integrity": "sha512-+wBOcIV5snwGgI2ya3u99D7/FJquOIniQT1IKyDsBmEgwvpxMNeS65Oib7OnE2d2aY+3BU4OiH+0Wchf8yk3Hw==", + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@emotion/utils": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz", + "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==" + }, + "node_modules/@emotion/weak-memoize": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz", + "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.5", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/set-array": "^1.2.1", @@ -49,7 +337,6 @@ }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -57,7 +344,6 @@ }, "node_modules/@jridgewell/set-array": { "version": "1.2.1", - "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -74,12 +360,10 @@ }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.0", - "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", - "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -142,6 +426,278 @@ "dev": true, "license": "MIT" }, + "node_modules/@mui/core-downloads-tracker": { + "version": "6.1.10", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.10.tgz", + "integrity": "sha512-LY5wdiLCBDY7u+Od8UmFINZFGN/5ZU90fhAslf/ZtfP+5RhuY45f679pqYIxe0y54l6Gkv9PFOc8Cs10LDTBYg==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + } + }, + "node_modules/@mui/material": { + "version": "6.1.10", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.10.tgz", + "integrity": "sha512-txnwYObY4N9ugv5T2n5h1KcbISegZ6l65w1/7tpSU5OB6MQCU94YkP8n/3slDw2KcEfRk4+4D8EUGfhSPMODEQ==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/core-downloads-tracker": "^6.1.10", + "@mui/system": "^6.1.10", + "@mui/types": "^7.2.19", + "@mui/utils": "^6.1.10", + "@popperjs/core": "^2.11.8", + "@types/react-transition-group": "^4.4.11", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1", + "react-is": "^18.3.1", + "react-transition-group": "^4.4.5" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@mui/material-pigment-css": "^6.1.10", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/private-theming": { + "version": "6.1.10", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.10.tgz", + "integrity": "sha512-DqgsH0XFEweeG3rQfVkqTkeXcj/E76PGYWag8flbPdV8IYdMo+DfVdFlZK8JEjsaIVD2Eu1kJg972XnH5pfnBQ==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/utils": "^6.1.10", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/styled-engine": { + "version": "6.1.10", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.10.tgz", + "integrity": "sha512-+NV9adKZYhslJ270iPjf2yzdVJwav7CIaXcMlPSi1Xy1S/zRe5xFgZ6BEoMdmGRpr34lIahE8H1acXP2myrvRw==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@emotion/cache": "^11.13.5", + "@emotion/serialize": "^1.3.3", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/system": { + "version": "6.1.10", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.10.tgz", + "integrity": "sha512-5YNIqxETR23SIkyP7MY2fFnXmplX/M4wNi2R+10AVRd3Ub+NLctWY/Vs5vq1oAMF0eSDLhRTGUjaUe+IGSfWqg==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/private-theming": "^6.1.10", + "@mui/styled-engine": "^6.1.10", + "@mui/types": "^7.2.19", + "@mui/utils": "^6.1.10", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/types": { + "version": "7.2.19", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.2.19.tgz", + "integrity": "sha512-6XpZEM/Q3epK9RN8ENoXuygnqUQxE+siN/6rGRi2iwJPgBUR25mphYQ9ZI87plGh58YoZ5pp40bFvKYOCDJ3tA==", + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/utils": { + "version": "6.1.10", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.10.tgz", + "integrity": "sha512-1ETuwswGjUiAf2dP9TkBy8p49qrw2wXa+RuAjNTRE5+91vtXJ1HKrs7H9s8CZd1zDlQVzUcUAPm9lpQwF5ogTw==", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/types": "^7.2.19", + "@types/prop-types": "^15.7.13", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^18.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/x-charts": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-7.23.0.tgz", + "integrity": "sha512-lXG+vRMFvisFaj93LlglZij7e5NEHptXQG0x9EshSI8uIm8HN9Xp77qIdE95xLyltImakuFCeJaEk9oaRMBTpA==", + "dependencies": { + "@babel/runtime": "^7.25.7", + "@mui/utils": "^5.16.6 || ^6.0.0", + "@mui/x-charts-vendor": "7.20.0", + "@mui/x-internals": "7.23.0", + "@react-spring/rafz": "^9.7.5", + "@react-spring/web": "^9.7.5", + "clsx": "^2.1.1", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14 || ^6.0.0", + "@mui/system": "^5.15.14 || ^6.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/x-charts-vendor": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@mui/x-charts-vendor/-/x-charts-vendor-7.20.0.tgz", + "integrity": "sha512-pzlh7z/7KKs5o0Kk0oPcB+sY0+Dg7Q7RzqQowDQjpy5Slz6qqGsgOB5YUzn0L+2yRmvASc4Pe0914Ao3tMBogg==", + "dependencies": { + "@babel/runtime": "^7.25.7", + "@types/d3-color": "^3.1.3", + "@types/d3-delaunay": "^6.0.4", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-scale": "^4.0.8", + "@types/d3-shape": "^3.1.6", + "@types/d3-time": "^3.0.3", + "d3-color": "^3.1.0", + "d3-delaunay": "^6.0.4", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.2.0", + "d3-time": "^3.1.0", + "delaunator": "^5.0.1", + "robust-predicates": "^3.0.2" + } + }, + "node_modules/@mui/x-internals": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-7.23.0.tgz", + "integrity": "sha512-bPclKpqUiJYIHqmTxSzMVZi6MH51cQsn5U+8jskaTlo3J4QiMeCYJn/gn7YbeR9GOZFp8hetyHjoQoVHKRXCig==", + "dependencies": { + "@babel/runtime": "^7.25.7", + "@mui/utils": "^5.16.6 || ^6.0.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "dev": true, @@ -247,6 +803,81 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@react-spring/animated": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.5.tgz", + "integrity": "sha512-Tqrwz7pIlsSDITzxoLS3n/v/YCUHQdOIKtOJf4yL6kYVSDTSmVK1LI1Q3M/uu2Sx4X3pIWF3xLUhlsA6SPNTNg==", + "dependencies": { + "@react-spring/shared": "~9.7.5", + "@react-spring/types": "~9.7.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/core": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.5.tgz", + "integrity": "sha512-rmEqcxRcu7dWh7MnCcMXLvrf6/SDlSokLaLTxiPlAYi11nN3B5oiCUAblO72o+9z/87j2uzxa2Inm8UbLjXA+w==", + "dependencies": { + "@react-spring/animated": "~9.7.5", + "@react-spring/shared": "~9.7.5", + "@react-spring/types": "~9.7.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-spring/donate" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/rafz": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.7.5.tgz", + "integrity": "sha512-5ZenDQMC48wjUzPAm1EtwQ5Ot3bLIAwwqP2w2owG5KoNdNHpEJV263nGhCeKKmuA3vG2zLLOdu3or6kuDjA6Aw==" + }, + "node_modules/@react-spring/shared": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.5.tgz", + "integrity": "sha512-wdtoJrhUeeyD/PP/zo+np2s1Z820Ohr/BbuVYv+3dVLW7WctoiN7std8rISoYoHpUXtbkpesSKuPIw/6U1w1Pw==", + "dependencies": { + "@react-spring/rafz": "~9.7.5", + "@react-spring/types": "~9.7.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@react-spring/types": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.5.tgz", + "integrity": "sha512-HVj7LrZ4ReHWBimBvu2SKND3cDVUPWKLqRTmWe/fNY6o1owGOX0cAHbdPDTMelgBlVbrTKrre6lFkhqGZErK/g==" + }, + "node_modules/@react-spring/web": { + "version": "9.7.5", + "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.7.5.tgz", + "integrity": "sha512-lmvqGwpe+CSttsWNZVr+Dg62adtKhauGwLyGE/RRyZ8AAMLgb9x3NDMA5RMElXo+IMyTkPp7nxTB8ZQlmhb6JQ==", + "dependencies": { + "@react-spring/animated": "~9.7.5", + "@react-spring/core": "~9.7.5", + "@react-spring/shared": "~9.7.5", + "@react-spring/types": "~9.7.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/@sindresorhus/merge-streams": { "version": "2.3.0", "dev": true, @@ -292,6 +923,50 @@ "@types/node": "*" } }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==" + }, + "node_modules/@types/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", + "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==" + }, "node_modules/@types/eslint": { "version": "9.6.1", "dev": true, @@ -387,9 +1062,13 @@ "@types/node": "*" } }, + "node_modules/@types/parse-json": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", + "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==" + }, "node_modules/@types/prop-types": { "version": "15.7.13", - "dev": true, "license": "MIT" }, "node_modules/@types/qs": { @@ -403,18 +1082,28 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "16.8.25", - "dev": true, - "license": "MIT", + "version": "17.0.83", + "resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.83.tgz", + "integrity": "sha512-l0m4ArKJvmFtR4e8UmKrj1pB4tUgOhJITf+mADyF/p69Ts1YAR/E+G9XEM0mHXKVRa1dQNHseyyDNzeuAXfXQw==", "dependencies": { "@types/prop-types": "*", - "csstype": "^2.2.0" + "@types/scheduler": "^0.16", + "csstype": "^3.0.2" } }, "node_modules/@types/react-dom": { - "version": "16.8.5", + "version": "17.0.25", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.25.tgz", + "integrity": "sha512-urx7A7UxkZQmThYA4So0NelOVjx3V4rNFVJwp0WZlbIK5eM4rNJDiN3R/E9ix0MBh6kAEojk/9YL+Te6D9zHNA==", "dev": true, - "license": "MIT", + "dependencies": { + "@types/react": "^17" + } + }, + "node_modules/@types/react-transition-group": { + "version": "4.4.11", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.11.tgz", + "integrity": "sha512-RM05tAniPZ5DZPzzNFP+DmrcOdD0efDUxMy3145oljWSl3x9ZV5vhme98gTxFrj2lhXvmGNnUiuDyJgY9IKkNA==", "dependencies": { "@types/react": "*" } @@ -424,6 +1113,11 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/scheduler": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", + "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==" + }, "node_modules/@types/send": { "version": "0.17.4", "dev": true, @@ -852,6 +1546,20 @@ "react-dom": "^16.8.1" } }, + "node_modules/babel-plugin-macros": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", + "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==", + "dependencies": { + "@babel/runtime": "^7.12.5", + "cosmiconfig": "^7.0.0", + "resolve": "^1.19.0" + }, + "engines": { + "node": ">=10", + "npm": ">=6" + } + }, "node_modules/batch": { "version": "0.6.1", "dev": true, @@ -986,6 +1694,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001684", "dev": true, @@ -1066,6 +1782,14 @@ "node": ">=6" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "engines": { + "node": ">=6" + } + }, "node_modules/color-convert": { "version": "2.0.1", "dev": true, @@ -1147,6 +1871,11 @@ "node": ">= 0.6" } }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==" + }, "node_modules/cookie": { "version": "0.7.1", "dev": true, @@ -1188,6 +1917,29 @@ "dev": true, "license": "MIT" }, + "node_modules/cosmiconfig": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz", + "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==", + "dependencies": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.2.1", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.10.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cosmiconfig/node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "engines": { + "node": ">=8" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "dev": true, @@ -1247,9 +1999,114 @@ } }, "node_modules/csstype": { - "version": "2.6.21", - "dev": true, - "license": "MIT" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } }, "node_modules/data-view-buffer": { "version": "1.0.1", @@ -1371,6 +2228,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/depd": { "version": "2.0.0", "dev": true, @@ -1416,6 +2281,15 @@ "node": ">=6" } }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/ee-first": { "version": "1.1.1", "dev": true, @@ -1457,6 +2331,14 @@ "node": ">=4" } }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-abstract": { "version": "1.23.5", "license": "MIT", @@ -1607,6 +2489,17 @@ "dev": true, "license": "MIT" }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint-scope": { "version": "5.1.1", "dev": true, @@ -1804,6 +2697,11 @@ "node": ">= 0.8" } }, + "node_modules/find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==" + }, "node_modules/find-up": { "version": "4.1.0", "dev": true, @@ -1944,6 +2842,14 @@ "dev": true, "license": "BSD-2-Clause" }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, "node_modules/globalthis": { "version": "1.0.4", "license": "MIT", @@ -2065,6 +2971,19 @@ "node": ">= 0.4" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/hpack.js": { "version": "2.1.6", "dev": true, @@ -2227,6 +3146,29 @@ "dev": true, "license": "MIT" }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, "node_modules/import-local": { "version": "3.2.0", "dev": true, @@ -2262,6 +3204,14 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, "node_modules/interpret": { "version": "3.1.1", "dev": true, @@ -2296,6 +3246,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, "node_modules/is-async-function": { "version": "2.0.0", "license": "MIT", @@ -2356,7 +3311,6 @@ }, "node_modules/is-core-module": { "version": "2.15.1", - "dev": true, "license": "MIT", "dependencies": { "hasown": "^2.0.2" @@ -2700,12 +3654,21 @@ }, "node_modules/js-tokens": { "version": "4.0.0", - "license": "MIT", - "peer": true + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", - "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { @@ -2730,6 +3693,11 @@ "shell-quote": "^1.8.1" } }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, "node_modules/loader-runner": { "version": "4.3.0", "dev": true, @@ -2752,7 +3720,6 @@ "node_modules/loose-envify": { "version": "1.4.0", "license": "MIT", - "peer": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -2939,7 +3906,6 @@ "node_modules/object-assign": { "version": "4.1.1", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -3067,6 +4033,34 @@ "node": ">=6" } }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parseurl": { "version": "1.3.3", "dev": true, @@ -3097,7 +4091,6 @@ }, "node_modules/path-parse": { "version": "1.0.7", - "dev": true, "license": "MIT" }, "node_modules/path-to-regexp": { @@ -3118,7 +4111,6 @@ }, "node_modules/picocolors": { "version": "1.1.1", - "dev": true, "license": "ISC" }, "node_modules/picomatch": { @@ -3256,14 +4248,19 @@ }, "node_modules/prop-types": { "version": "15.8.1", - "license": "MIT", - "peer": true, + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/proxy-addr": { "version": "2.0.7", "dev": true, @@ -3356,36 +4353,51 @@ } }, "node_modules/react": { - "version": "16.14.0", - "license": "MIT", + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", + "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", "peer": true, "dependencies": { "loose-envify": "^1.1.0", - "object-assign": "^4.1.1", - "prop-types": "^15.6.2" + "object-assign": "^4.1.1" }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "16.14.0", - "license": "MIT", + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", + "integrity": "sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==", "peer": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1", - "prop-types": "^15.6.2", - "scheduler": "^0.19.1" + "scheduler": "^0.20.2" }, "peerDependencies": { - "react": "^16.14.0" + "react": "17.0.2" } }, "node_modules/react-is": { - "version": "16.13.1", - "license": "MIT", - "peer": true + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==" + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } }, "node_modules/react-zoom-pan-pinch": { "version": "3.6.1", @@ -3454,6 +4466,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, "node_modules/regexp.prototype.flags": { "version": "1.5.3", "license": "MIT", @@ -3485,7 +4502,6 @@ }, "node_modules/resolve": { "version": "1.22.8", - "dev": true, "license": "MIT", "dependencies": { "is-core-module": "^2.13.0", @@ -3535,6 +4551,11 @@ "node": ">=0.10.0" } }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" + }, "node_modules/run-applescript": { "version": "7.0.0", "dev": true, @@ -3682,8 +4703,9 @@ } }, "node_modules/scheduler": { - "version": "0.19.1", - "license": "MIT", + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.20.2.tgz", + "integrity": "sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==", "peer": true, "dependencies": { "loose-envify": "^1.1.0", @@ -4148,6 +5170,11 @@ "webpack": "^5.27.0" } }, + "node_modules/stylis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz", + "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==" + }, "node_modules/supports-color": { "version": "8.1.1", "dev": true, @@ -4164,7 +5191,6 @@ }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -4998,6 +6024,14 @@ "optional": true } } + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "engines": { + "node": ">= 6" + } } } } diff --git a/ui/package.json b/ui/package.json index 9ab28b8..83caa95 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,6 +1,10 @@ { "private": true, "dependencies": { + "@emotion/react": "^11.13.5", + "@emotion/styled": "^11.13.5", + "@mui/material": "^6.1.10", + "@mui/x-charts": "^7.23.0", "azure-devops-extension-api": "^4.245.1", "azure-devops-extension-sdk": "^4.0.2", "azure-devops-ui": "^2.246.0", @@ -8,8 +12,8 @@ "react-zoom-pan-pinch": "^3.6.1" }, "devDependencies": { - "@types/react": "~16.8.2", - "@types/react-dom": "~16.8.0", + "@types/react": "^17.0.0", + "@types/react-dom": "^17.0.0", "copy-webpack-plugin": "^12.0.2", "css-loader": "^7.1.2", "sass": "^1.80.6", @@ -20,5 +24,11 @@ "webpack": "^5.96.1", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.1.0" + }, + "overrides": { + "azure-devops-ui": { + "react": "^17.0.0", + "react-dom": "^17.0.0" + } } } diff --git a/ui/sbom-report-tab.scss b/ui/sbom-report-tab.scss index 02f6bc7..aa66126 100644 --- a/ui/sbom-report-tab.scss +++ b/ui/sbom-report-tab.scss @@ -32,3 +32,7 @@ $severity-none-background: var(--status-neutral-background); .flex-gap-16 { gap: 16px; } + +.flex-gap-24 { + gap: 24px; +} diff --git a/ui/sbom-report-tab.tsx b/ui/sbom-report-tab.tsx index e01727e..9ce9ffc 100644 --- a/ui/sbom-report-tab.tsx +++ b/ui/sbom-report-tab.tsx @@ -15,7 +15,7 @@ import '../shared/extensions/StringExtensions'; import { ISbomBuildArtifact } from '../shared/models/ISbomBuildArtifact'; import { IDocument } from '../shared/models/spdx/2.3/IDocument'; import { BuildRestClient } from './clients/BuildRestClient'; -import { SbomDocumentPage } from './components/SbomDocumentPage'; +import { SbomDocumentPage } from './components/sbom/SbomDocumentPage'; import './sbom-report-tab.scss';