@openshift-migration-advisor/shared-components 0.27.0 → 0.28.0-09381b4d22e5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/charts/MigrationDonutChart.d.ts.map +1 -1
  2. package/dist/charts/MigrationDonutChart.js +7 -0
  3. package/dist/report/ChartExportProvider.d.ts +4 -3
  4. package/dist/report/ChartExportProvider.d.ts.map +1 -1
  5. package/dist/report/ChartExportProvider.js +7 -4
  6. package/dist/report/ClustersOverview.d.ts +24 -0
  7. package/dist/report/ClustersOverview.d.ts.map +1 -0
  8. package/dist/report/ClustersOverview.js +228 -0
  9. package/dist/report/ClustersOverviewStyles.d.ts +12 -0
  10. package/dist/report/ClustersOverviewStyles.d.ts.map +1 -0
  11. package/dist/report/ClustersOverviewStyles.js +101 -0
  12. package/dist/report/CpuAndMemoryOverview.d.ts +19 -0
  13. package/dist/report/CpuAndMemoryOverview.d.ts.map +1 -0
  14. package/dist/report/CpuAndMemoryOverview.js +112 -0
  15. package/dist/report/FeatureStatusBadge.js +1 -1
  16. package/dist/report/ReportExportMenu.d.ts +1 -0
  17. package/dist/report/ReportExportMenu.d.ts.map +1 -1
  18. package/dist/report/ReportExportMenu.js +38 -4
  19. package/dist/report/StorageOverview.d.ts +27 -0
  20. package/dist/report/StorageOverview.d.ts.map +1 -0
  21. package/dist/report/StorageOverview.js +319 -0
  22. package/dist/report/VMMigrationStatus.d.ts +33 -0
  23. package/dist/report/VMMigrationStatus.d.ts.map +1 -0
  24. package/dist/report/VMMigrationStatus.js +129 -0
  25. package/dist/report/captureChartElement.d.ts +6 -1
  26. package/dist/report/captureChartElement.d.ts.map +1 -1
  27. package/dist/report/captureChartElement.js +41 -12
  28. package/dist/report/chartExport.d.ts +23 -0
  29. package/dist/report/chartExport.d.ts.map +1 -1
  30. package/dist/report/chartExport.js +21 -1
  31. package/dist/report/chartExportContext.d.ts +6 -2
  32. package/dist/report/chartExportContext.d.ts.map +1 -1
  33. package/dist/report/dashboardStyles.d.ts +6 -0
  34. package/dist/report/dashboardStyles.d.ts.map +1 -1
  35. package/dist/report/dashboardStyles.js +7 -4
  36. package/dist/report/index.d.ts +7 -3
  37. package/dist/report/index.d.ts.map +1 -1
  38. package/dist/report/index.js +4 -0
  39. package/dist/report/pdfExport.d.ts +2 -2
  40. package/dist/report/pdfExport.d.ts.map +1 -1
  41. package/dist/report/pdfExport.js +52 -3
  42. package/dist/report/reportExportOptions.d.ts +13 -2
  43. package/dist/report/reportExportOptions.d.ts.map +1 -1
  44. package/dist/report/reportExportOptions.js +13 -5
  45. package/package.json +2 -2
  46. package/src/charts/MigrationDonutChart.tsx +9 -0
  47. package/src/report/ChartExportProvider.tsx +18 -6
  48. package/src/report/ClustersOverview.tsx +451 -0
  49. package/src/report/ClustersOverviewStyles.ts +110 -0
  50. package/src/report/CpuAndMemoryOverview.tsx +277 -0
  51. package/src/report/FeatureStatusBadge.tsx +1 -1
  52. package/src/report/ReportExportMenu.tsx +69 -13
  53. package/src/report/StorageOverview.tsx +629 -0
  54. package/src/report/VMMigrationStatus.tsx +358 -0
  55. package/src/report/__tests__/ChartExportProvider.test.tsx +110 -2
  56. package/src/report/__tests__/ClustersOverview.test.tsx +241 -0
  57. package/src/report/__tests__/CpuAndMemoryOverview.test.tsx +200 -0
  58. package/src/report/__tests__/StorageOverview.test.tsx +223 -0
  59. package/src/report/__tests__/VMMigrationStatus.test.tsx +178 -0
  60. package/src/report/__tests__/captureChartElement.test.ts +120 -0
  61. package/src/report/__tests__/chartExportPackagers.test.ts +75 -0
  62. package/src/report/captureChartElement.ts +55 -10
  63. package/src/report/chartExport.ts +41 -1
  64. package/src/report/chartExportContext.tsx +9 -1
  65. package/src/report/dashboardStyles.ts +7 -4
  66. package/src/report/index.ts +24 -0
  67. package/src/report/pdfExport.ts +81 -2
  68. package/src/report/reportExportOptions.ts +28 -5
@@ -14,6 +14,7 @@ export interface ExportReportButtonProps extends StandardReportExportHandlers {
14
14
  loadingLabel?: string | null;
15
15
  isDisabled?: boolean;
16
16
  toggleLabel?: string;
17
+ assessmentSectionLabel?: string;
17
18
  }
18
19
  export declare const ExportReportButton: FC<ExportReportButtonProps>;
19
20
  //# sourceMappingURL=ReportExportMenu.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ReportExportMenu.d.ts","sourceRoot":"","sources":["../../src/report/ReportExportMenu.tsx"],"names":[],"mappings":"AASA,OAAO,EAAE,KAAK,EAAE,EAAsB,MAAM,OAAO,CAAC;AACpD,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,4BAA4B,EAElC,MAAM,0BAA0B,CAAC;AAElC,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,gBAAgB,EAAE,EAAE,CAAC,qBAAqB,CA8DtD,CAAC;AAIF,MAAM,WAAW,uBAAwB,SAAQ,4BAA4B;IAC3E,YAAY,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACpC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,kBAAkB,EAAE,EAAE,CAAC,uBAAuB,CAkB1D,CAAC"}
1
+ {"version":3,"file":"ReportExportMenu.d.ts","sourceRoot":"","sources":["../../src/report/ReportExportMenu.tsx"],"names":[],"mappings":"AAWA,OAAO,EAAE,KAAK,EAAE,EAA+C,MAAM,OAAO,CAAC;AAC7E,OAAO,EACL,KAAK,kBAAkB,EAEvB,KAAK,4BAA4B,EAElC,MAAM,0BAA0B,CAAC;AA6BlC,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,gBAAgB,EAAE,EAAE,CAAC,qBAAqB,CAqFtD,CAAC;AAIF,MAAM,WAAW,uBAAwB,SAAQ,4BAA4B;IAC3E,YAAY,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACpC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,eAAO,MAAM,kBAAkB,EAAE,EAAE,CAAC,uBAAuB,CAoB1D,CAAC"}
@@ -1,13 +1,46 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { Dropdown, DropdownItem, DropdownList, MenuToggle, Spinner, } from "@patternfly/react-core";
2
+ import { Divider, Dropdown, DropdownGroup, DropdownItem, DropdownList, MenuToggle, Spinner, } from "@patternfly/react-core";
3
3
  import { DownloadIcon } from "@patternfly/react-icons";
4
- import { useState } from "react";
4
+ import { useMemo, useState } from "react";
5
5
  import { standardReportExportOptions, } from "./reportExportOptions.js";
6
+ function sectionGroupKey(section) {
7
+ return section?.id;
8
+ }
9
+ function groupExportOptions(options) {
10
+ const groups = [];
11
+ for (const option of options) {
12
+ const sectionKey = sectionGroupKey(option.section);
13
+ const last = groups.length > 0 ? groups[groups.length - 1] : undefined;
14
+ const lastKey = last ? sectionGroupKey(last.section) : undefined;
15
+ if (last && lastKey === sectionKey) {
16
+ last.options.push(option);
17
+ }
18
+ else {
19
+ groups.push({ section: option.section, options: [option] });
20
+ }
21
+ }
22
+ return groups;
23
+ }
6
24
  export const ReportExportMenu = ({ options, isLoading = false, loadingLabel = null, isDisabled = false, toggleLabel = "Export", }) => {
7
25
  const [isDropdownOpen, setIsDropdownOpen] = useState(false);
26
+ const optionGroups = useMemo(() => groupExportOptions(options), [options]);
8
27
  if (options.length === 0) {
9
28
  return null;
10
29
  }
30
+ const menuItems = [];
31
+ optionGroups.forEach((group, index) => {
32
+ if (index > 0) {
33
+ const dividerKey = group.section?.id ?? group.options[0]?.key ?? "ungrouped";
34
+ menuItems.push(_jsx(Divider, { component: "li" }, `divider-before-${dividerKey}`));
35
+ }
36
+ const items = group.options.map((option) => (_jsx(DropdownItem, { isDisabled: option.isDisabled, description: option.description, onClick: option.onSelect, children: option.label }, option.key)));
37
+ if (group.section) {
38
+ menuItems.push(_jsx(DropdownGroup, { label: group.section.label, children: items }, group.section.id));
39
+ }
40
+ else {
41
+ menuItems.push(...items);
42
+ }
43
+ });
11
44
  return (_jsx(Dropdown, { isOpen: isDropdownOpen, popperProps: {
12
45
  placement: "bottom-end",
13
46
  preventOverflow: true,
@@ -17,14 +50,15 @@ export const ReportExportMenu = ({ options, isLoading = false, loadingLabel = nu
17
50
  setIsDropdownOpen(isOpen);
18
51
  }, toggle: (toggleRef) => (_jsx(MenuToggle, { ref: toggleRef, onClick: () => {
19
52
  setIsDropdownOpen((open) => !open);
20
- }, isExpanded: isDropdownOpen, variant: "secondary", isDisabled: isLoading || isDisabled, "aria-label": "Export report options", icon: isLoading ? (_jsx(Spinner, { size: "sm", "aria-hidden": "true" })) : (_jsx(DownloadIcon, { "aria-hidden": "true" })), children: isLoading ? (loadingLabel ?? "Generating...") : toggleLabel })), children: _jsx(DropdownList, { children: options.map((option) => (_jsx(DropdownItem, { isDisabled: option.isDisabled, description: option.description, onClick: option.onSelect, children: option.label }, option.key))) }) }));
53
+ }, isExpanded: isDropdownOpen, variant: "secondary", isDisabled: isLoading || isDisabled, "aria-label": "Export report options", icon: isLoading ? (_jsx(Spinner, { size: "sm", "aria-hidden": "true" })) : (_jsx(DownloadIcon, { "aria-hidden": "true" })), children: isLoading ? (loadingLabel ?? "Generating...") : toggleLabel })), children: _jsx(DropdownList, { children: menuItems }) }));
21
54
  };
22
55
  ReportExportMenu.displayName = "ReportExportMenu";
23
- export const ExportReportButton = ({ onExportPdf, onExportPng, onExportHtml, extraOptions = [], ...menuProps }) => (_jsx(ReportExportMenu, { options: [
56
+ export const ExportReportButton = ({ onExportPdf, onExportPng, onExportHtml, extraOptions = [], assessmentSectionLabel, ...menuProps }) => (_jsx(ReportExportMenu, { options: [
24
57
  ...standardReportExportOptions({
25
58
  onExportPdf,
26
59
  onExportPng,
27
60
  onExportHtml,
61
+ assessmentSectionLabel,
28
62
  }),
29
63
  ...extraOptions,
30
64
  ], ...menuProps }));
@@ -0,0 +1,27 @@
1
+ import type { FC } from "react";
2
+ import { type MigrationDonutChartLegendVariant } from "../charts/MigrationDonutChart.js";
3
+ export interface StorageDiskTierData {
4
+ vmCount?: number;
5
+ totalSizeTB?: number;
6
+ }
7
+ export interface StorageOverviewProps {
8
+ diskSizeTier?: Record<string, StorageDiskTierData>;
9
+ diskTypes?: Record<string, StorageDiskTierData>;
10
+ totalVMs?: number;
11
+ totalWithSharedDisks?: number;
12
+ /**
13
+ * Disk-tier slice / center-title click. The argument is the tier's parsed
14
+ * disk-size range (in MB). Agent-ui maps it to a VM disk filter; ui-app omits
15
+ * it. Called with `undefined` for the center-title click (no tier filter).
16
+ */
17
+ onDiskTierSelect?: (diskRange: {
18
+ min: number;
19
+ max?: number;
20
+ }) => void;
21
+ /** Center-title click. Agent-ui opens the VM list with no extra filters. */
22
+ onTitleClick?: () => void;
23
+ /** `"html"` in agent-ui; ui-app passes `"chart"`. */
24
+ legendVariant?: MigrationDonutChartLegendVariant;
25
+ }
26
+ export declare const StorageOverview: FC<StorageOverviewProps>;
27
+ //# sourceMappingURL=StorageOverview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StorageOverview.d.ts","sourceRoot":"","sources":["../../src/report/StorageOverview.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,EAAE,EAAO,MAAM,OAAO,CAAC;AAErC,OAAO,EAGL,KAAK,gCAAgC,EACtC,MAAM,kCAAkC,CAAC;AAQ1C,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAChD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,CAAC,SAAS,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACtE,4EAA4E;IAC5E,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,qDAAqD;IACrD,aAAa,CAAC,EAAE,gCAAgC,CAAC;CAClD;AA0TD,eAAO,MAAM,eAAe,EAAE,EAAE,CAAC,oBAAoB,CA6PpD,CAAC"}
@@ -0,0 +1,319 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Card, CardBody, CardTitle, Content, Dropdown, DropdownItem, DropdownList, Flex, FlexItem, MenuToggle, } from "@patternfly/react-core";
3
+ import { DatabaseIcon } from "@patternfly/react-icons";
4
+ import { chart_color_blue_300, chart_color_green_300, chart_color_orange_200, chart_color_purple_100, chart_color_purple_300, chart_color_red_orange_300, chart_color_teal_200, chart_color_teal_300, chart_color_yellow_400, } from "@patternfly/react-tokens";
5
+ import { useMemo, useState } from "react";
6
+ import { MigrationDonutChart, } from "../charts/MigrationDonutChart.js";
7
+ import { CardEmptyState } from "./CardEmptyState.js";
8
+ import { ChartHeaderActions } from "./ChartDownloadButton.js";
9
+ import { ChartExportSurface } from "./ChartExportSurface.js";
10
+ import { chartExportViewsFromLabels } from "./chartExport.js";
11
+ import { REPORT_CARD_EMPTY_STATE_TITLES } from "./constants.js";
12
+ import { dashboardStyles } from "./dashboardStyles.js";
13
+ const VIEW_MODE_LABELS = {
14
+ totalSize: "Total disk size by tier",
15
+ vmCount: "VM count by disk size tier",
16
+ vmCountByDiskType: "VM count by disk type",
17
+ sharedDisks: "Shared disks VS. No shared disks",
18
+ };
19
+ const CHART_ID = "storage-overview";
20
+ const CHART_TITLE = "Storage";
21
+ const TIER_CONFIG = {
22
+ Easy: { order: 0, label: "0-10 TB", legendCategory: "Easy" },
23
+ Medium: { order: 1, label: "11-20 TB", legendCategory: "Medium" },
24
+ Hard: { order: 2, label: "21-50 TB", legendCategory: "Hard" },
25
+ White: { order: 3, label: "> 50 TB", legendCategory: "White glove" },
26
+ };
27
+ /**
28
+ * Categorical order shared by the agent-ui and ui-app storage donuts, mapped
29
+ * onto PatternFly `chart_color_*` tokens.
30
+ */
31
+ const COLOR_PALETTE = [
32
+ chart_color_blue_300.value,
33
+ chart_color_purple_300.value,
34
+ chart_color_purple_100.value,
35
+ chart_color_teal_300.value,
36
+ chart_color_yellow_400.value,
37
+ chart_color_green_300.value,
38
+ chart_color_orange_200.value,
39
+ chart_color_red_orange_300.value,
40
+ ];
41
+ const DISK_TYPE_BAR_COLORS = [
42
+ chart_color_blue_300.value,
43
+ chart_color_purple_300.value,
44
+ chart_color_purple_100.value,
45
+ chart_color_yellow_400.value,
46
+ chart_color_teal_200.value,
47
+ ];
48
+ const SHARED_DISKS_COLORS = {
49
+ "With shared disks": chart_color_yellow_400.value,
50
+ "No shared disks": chart_color_blue_300.value,
51
+ };
52
+ const DISK_TYPE_PREFERRED_ORDER = ["VMFS", "NFS", "vSAN", "RDM", "vVols"];
53
+ // --- Disk-tier label parsing (self-contained copy so the shared card does not
54
+ // depend on agent-ui internals). Used for both tier ordering and drill-down. ---
55
+ const MB_IN_GIB = 1024;
56
+ const MB_IN_TIB = 1024 * 1024;
57
+ const diskSizeRanges = [
58
+ { label: "0-100GiB", min: 0, max: 100 * MB_IN_GIB },
59
+ { label: "100-500GiB", min: 100 * MB_IN_GIB, max: 500 * MB_IN_GIB },
60
+ { label: "500GiB-1TiB", min: 500 * MB_IN_GIB, max: MB_IN_TIB },
61
+ { label: "1-2TiB", min: MB_IN_TIB, max: 2 * MB_IN_TIB },
62
+ { label: "2-5TiB", min: 2 * MB_IN_TIB, max: 5 * MB_IN_TIB },
63
+ ];
64
+ const legacyDiskSizeRanges = [
65
+ { label: "0-10 TB", min: 0, max: 10 * MB_IN_TIB },
66
+ { label: "11-20 TB", min: 10 * MB_IN_TIB + 1, max: 20 * MB_IN_TIB },
67
+ { label: "21-50 TB", min: 20 * MB_IN_TIB + 1, max: 50 * MB_IN_TIB },
68
+ { label: "50+ TB", min: 50 * MB_IN_TIB + 1, max: undefined },
69
+ ];
70
+ const sizeValueToMB = (value, unit) => {
71
+ const amount = Number.parseFloat(value);
72
+ if (!Number.isFinite(amount)) {
73
+ return Number.NaN;
74
+ }
75
+ switch (unit.toLowerCase()) {
76
+ case "gib":
77
+ case "gb":
78
+ return amount * MB_IN_GIB;
79
+ case "tib":
80
+ case "tb":
81
+ return amount * MB_IN_TIB;
82
+ case "mib":
83
+ case "mb":
84
+ return amount;
85
+ default:
86
+ return Number.NaN;
87
+ }
88
+ };
89
+ /** Parses inventory disk tier labels (GiB/TiB or legacy TB buckets) into MB ranges. */
90
+ const parseDiskTierLabelToRange = (label) => {
91
+ const normalized = label.trim();
92
+ if (!normalized) {
93
+ return undefined;
94
+ }
95
+ const predefined = diskSizeRanges.find((range) => range.label === normalized) ??
96
+ legacyDiskSizeRanges.find((range) => range.label === normalized ||
97
+ (normalized === "> 50 TB" && range.label === "50+ TB"));
98
+ if (predefined) {
99
+ return { min: predefined.min, max: predefined.max };
100
+ }
101
+ const mixedUnitRange = /^(\d+(?:\.\d+)?)\s*(GiB|TiB|GB|TB|MIB|MB)\s*-\s*(\d+(?:\.\d+)?)\s*(GiB|TiB|GB|TB|MIB|MB)$/i.exec(normalized);
102
+ if (mixedUnitRange) {
103
+ const min = sizeValueToMB(mixedUnitRange[1], mixedUnitRange[2]);
104
+ const max = sizeValueToMB(mixedUnitRange[3], mixedUnitRange[4]);
105
+ if (Number.isFinite(min) && Number.isFinite(max)) {
106
+ return { min: Math.floor(min), max: Math.floor(max) };
107
+ }
108
+ }
109
+ const singleUnitRange = /^(\d+(?:\.\d+)?)\s*-\s*(\d+(?:\.\d+)?)\s*(GiB|TiB|GB|TB|MIB|MB)$/i.exec(normalized);
110
+ if (singleUnitRange) {
111
+ const min = sizeValueToMB(singleUnitRange[1], singleUnitRange[3]);
112
+ const max = sizeValueToMB(singleUnitRange[2], singleUnitRange[3]);
113
+ if (Number.isFinite(min) && Number.isFinite(max)) {
114
+ return { min: Math.floor(min), max: Math.floor(max) };
115
+ }
116
+ }
117
+ const openEndedRange = /^(\d+(?:\.\d+)?)\s*(GiB|TiB|GB|TB|MIB|MB)?\s*\+$/i.exec(normalized);
118
+ if (openEndedRange) {
119
+ const unit = openEndedRange[2] || "TB";
120
+ const min = sizeValueToMB(openEndedRange[1], unit);
121
+ if (Number.isFinite(min)) {
122
+ const minMB = Math.floor(min);
123
+ return { min: minMB > 0 ? minMB + 1 : minMB, max: undefined };
124
+ }
125
+ }
126
+ return undefined;
127
+ };
128
+ const buildTierChartData = (summary, selector) => {
129
+ if (!summary) {
130
+ return [];
131
+ }
132
+ const getTierPrefix = (key) => {
133
+ const normalizedKey = key.trim().toLowerCase();
134
+ for (const prefix of Object.keys(TIER_CONFIG)) {
135
+ if (normalizedKey.startsWith(prefix.toLowerCase())) {
136
+ return prefix;
137
+ }
138
+ }
139
+ return null;
140
+ };
141
+ return Object.entries(summary)
142
+ .map(([key, tier]) => {
143
+ const prefix = getTierPrefix(key);
144
+ const display = prefix
145
+ ? TIER_CONFIG[prefix]
146
+ : { label: key, legendCategory: key };
147
+ const { count, countDisplay } = selector(tier);
148
+ const label = display.label;
149
+ return {
150
+ name: label,
151
+ count,
152
+ countDisplay,
153
+ legendCategory: display.legendCategory,
154
+ diskRange: parseDiskTierLabelToRange(key) ?? parseDiskTierLabelToRange(label),
155
+ sortOrder: parseDiskTierLabelToRange(key)?.min ??
156
+ parseDiskTierLabelToRange(label)?.min ??
157
+ (prefix ? TIER_CONFIG[prefix].order : 999),
158
+ };
159
+ })
160
+ .sort((a, b) => a.sortOrder - b.sortOrder)
161
+ .map(({ sortOrder: _sortOrder, ...datum }) => datum);
162
+ };
163
+ const getContrastColor = (hexColor) => {
164
+ const hex = hexColor.replace("#", "");
165
+ const r = Number.parseInt(hex.substring(0, 2), 16);
166
+ const g = Number.parseInt(hex.substring(2, 4), 16);
167
+ const b = Number.parseInt(hex.substring(4, 6), 16);
168
+ const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
169
+ return luminance > 0.5 ? "#000" : "#fff";
170
+ };
171
+ const DiskTypeBarChart = ({ data, colors }) => {
172
+ const maxCount = useMemo(() => (data.length > 0 ? Math.max(...data.map((d) => d.count)) : 0), [data]);
173
+ if (data.length === 0) {
174
+ return _jsx(CardEmptyState, { title: REPORT_CARD_EMPTY_STATE_TITLES.diskTypes });
175
+ }
176
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: dashboardStyles.storageChartWrapper, children: _jsx(Flex, { direction: { default: "row" }, alignItems: { default: "alignItemsFlexEnd" }, justifyContent: { default: "justifyContentCenter" }, spaceItems: { default: "spaceItemsMd" }, style: { height: "250px", width: "100%" }, children: data.map((item, index) => {
177
+ const heightPercentage = maxCount > 0 ? (item.count / maxCount) * 100 : 0;
178
+ const minHeight = item.count > 0 ? 10 : 0;
179
+ const barColor = colors[index % colors.length];
180
+ const textColor = getContrastColor(barColor);
181
+ return (_jsxs(Flex, { direction: { default: "column" }, alignItems: { default: "alignItemsCenter" }, spaceItems: { default: "spaceItemsSm" }, style: { flex: "1", maxWidth: "120px" }, children: [_jsx(FlexItem, { style: {
182
+ height: "200px",
183
+ display: "flex",
184
+ alignItems: "flex-end",
185
+ width: "100%",
186
+ justifyContent: "center",
187
+ }, children: _jsx("div", { style: {
188
+ width: "40px",
189
+ height: `${Math.max(heightPercentage, minHeight)}%`,
190
+ backgroundColor: barColor,
191
+ transition: "height 0.3s ease",
192
+ borderRadius: "4px 4px 0 0",
193
+ display: "flex",
194
+ alignItems: "flex-start",
195
+ justifyContent: "center",
196
+ paddingTop: "4px",
197
+ }, title: `${item.name}: ${item.count} VMs`, children: _jsx(Content, { component: "small", style: {
198
+ color: textColor,
199
+ fontWeight: "bold",
200
+ fontSize: "10px",
201
+ }, children: item.count }) }) }), _jsx(FlexItem, { children: _jsx(Content, { component: "small", style: {
202
+ fontSize: "12px",
203
+ textAlign: "center",
204
+ wordBreak: "break-word",
205
+ }, children: item.name }) })] }, item.name));
206
+ }) }) }), _jsx(Content, { component: "small", className: dashboardStyles.storageTotalsNote, children: "Totals may exceed the unique VM count because individual VMs can have multiple disk types" })] }));
207
+ };
208
+ const isViewMode = (value) => value === "totalSize" ||
209
+ value === "vmCount" ||
210
+ value === "vmCountByDiskType" ||
211
+ value === "sharedDisks";
212
+ export const StorageOverview = ({ diskSizeTier = {}, diskTypes = {}, totalVMs, totalWithSharedDisks, onDiskTierSelect, onTitleClick, legendVariant = "html", }) => {
213
+ const [viewMode, setViewMode] = useState("vmCount");
214
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
215
+ const totals = useMemo(() => Object.values(diskSizeTier).reduce((acc, tier) => ({
216
+ totalSize: acc.totalSize + (tier?.totalSizeTB || 0),
217
+ totalVMs: acc.totalVMs + (tier?.vmCount || 0),
218
+ }), { totalSize: 0, totalVMs: 0 }), [diskSizeTier]);
219
+ const chartData = useMemo(() => buildTierChartData(diskSizeTier, (tier) => {
220
+ const count = viewMode === "totalSize"
221
+ ? tier?.totalSizeTB || 0
222
+ : tier?.vmCount || 0;
223
+ return {
224
+ count,
225
+ countDisplay: viewMode === "totalSize" ? `${count.toFixed(2)}` : `${count}`,
226
+ };
227
+ }), [diskSizeTier, viewMode]);
228
+ const diskTypeChartData = useMemo(() => {
229
+ if (Object.keys(diskTypes).length === 0) {
230
+ return [];
231
+ }
232
+ const orderIndex = (name) => {
233
+ const idx = DISK_TYPE_PREFERRED_ORDER.indexOf(name);
234
+ return idx === -1 ? Number.MAX_SAFE_INTEGER : idx;
235
+ };
236
+ return Object.entries(diskTypes)
237
+ .map(([diskTypeName, summary]) => ({
238
+ name: diskTypeName,
239
+ count: summary?.vmCount || 0,
240
+ }))
241
+ .sort((a, b) => orderIndex(a.name) - orderIndex(b.name));
242
+ }, [diskTypes]);
243
+ const sharedDisksChartData = useMemo(() => {
244
+ if (totalWithSharedDisks === undefined ||
245
+ totalVMs === undefined ||
246
+ totalWithSharedDisks === 0) {
247
+ return null;
248
+ }
249
+ const withSharedDisks = Math.min(totalWithSharedDisks, totalVMs);
250
+ const withoutSharedDisks = Math.max(0, totalVMs - withSharedDisks);
251
+ return [
252
+ {
253
+ name: "With shared disks",
254
+ count: withSharedDisks,
255
+ countDisplay: `${withSharedDisks}`,
256
+ legendCategory: "With shared disks",
257
+ },
258
+ {
259
+ name: "No shared disks",
260
+ count: withoutSharedDisks,
261
+ countDisplay: `${withoutSharedDisks}`,
262
+ legendCategory: "No shared disks",
263
+ },
264
+ ];
265
+ }, [totalVMs, totalWithSharedDisks]);
266
+ const tierLegend = useMemo(() => {
267
+ const legendMap = {};
268
+ chartData.forEach((slice, idx) => {
269
+ const color = COLOR_PALETTE[idx % COLOR_PALETTE.length];
270
+ if (color) {
271
+ legendMap[slice.legendCategory] = color;
272
+ }
273
+ });
274
+ return legendMap;
275
+ }, [chartData]);
276
+ const commonDonutProps = {
277
+ height: 300,
278
+ width: 420,
279
+ donutThickness: 18,
280
+ titleFontSize: 34,
281
+ subTitleColor: "var(--pf-t--global--text--color--subtle)",
282
+ legendVariant,
283
+ };
284
+ const handleDiskTierClick = (item) => {
285
+ const diskRange = item.diskRange ??
286
+ parseDiskTierLabelToRange(item.name) ??
287
+ parseDiskTierLabelToRange(item.legendCategory);
288
+ if (diskRange) {
289
+ onDiskTierSelect?.(diskRange);
290
+ }
291
+ };
292
+ const chartTitle = `${CHART_TITLE} — ${VIEW_MODE_LABELS[viewMode]}`;
293
+ return (_jsx(ChartExportSurface, { id: CHART_ID, title: chartTitle, exportViews: chartExportViewsFromLabels(CHART_TITLE, VIEW_MODE_LABELS), activeExportViewId: viewMode, onExportViewChange: (viewId) => {
294
+ if (isViewMode(viewId)) {
295
+ setViewMode(viewId);
296
+ }
297
+ }, children: _jsxs(Card, { className: `${dashboardStyles.card} ${dashboardStyles.storageCardOverflowHidden}`, children: [_jsx(CardTitle, { children: _jsxs(Flex, { justifyContent: { default: "justifyContentSpaceBetween" }, alignItems: { default: "alignItemsCenter" }, className: dashboardStyles.storageFlexFullWidth, children: [_jsxs(FlexItem, { children: [_jsx(DatabaseIcon, {}), " Disks"] }), _jsx(ChartHeaderActions, { chartId: CHART_ID, title: chartTitle, children: _jsx(Dropdown, { isOpen: isDropdownOpen, onSelect: (_event, value) => {
298
+ if (isViewMode(value)) {
299
+ setViewMode(value);
300
+ }
301
+ setIsDropdownOpen(false);
302
+ }, onOpenChange: setIsDropdownOpen, toggle: (toggleRef) => (_jsx(MenuToggle, { ref: toggleRef, onClick: () => setIsDropdownOpen((open) => !open), isExpanded: isDropdownOpen, className: dashboardStyles.storageMenuToggleMinWidth, children: VIEW_MODE_LABELS[viewMode] })), children: _jsxs(DropdownList, { children: [_jsx(DropdownItem, { value: "vmCount", children: VIEW_MODE_LABELS.vmCount }, "vmCount"), _jsx(DropdownItem, { value: "vmCountByDiskType", children: VIEW_MODE_LABELS.vmCountByDiskType }, "vmCountByDiskType"), _jsx(DropdownItem, { value: "totalSize", children: VIEW_MODE_LABELS.totalSize }, "totalSize"), _jsx(DropdownItem, { value: "sharedDisks", isDisabled: totalWithSharedDisks !== undefined &&
303
+ totalWithSharedDisks === 0, children: VIEW_MODE_LABELS.sharedDisks }, "sharedDisks")] }) }) })] }) }), _jsx(CardBody, { className: dashboardStyles.cardBodyScrollable, children: viewMode === "vmCountByDiskType" ? (_jsx(DiskTypeBarChart, { data: diskTypeChartData, colors: DISK_TYPE_BAR_COLORS })) : viewMode === "sharedDisks" ? (sharedDisksChartData && totalVMs !== undefined ? (_jsx(MigrationDonutChart, { ...commonDonutProps, data: sharedDisksChartData.map((item) => ({
304
+ ...item,
305
+ countDisplay: `${item.countDisplay} VMs`,
306
+ })), customColors: SHARED_DISKS_COLORS, title: `${totalVMs} VMs`, subTitle: `${totalWithSharedDisks ?? 0} with shared disks`, marginLeft: "25%", tooltipLabelFormatter: ({ datum, percent }) => `${datum.x}: ${datum.countDisplay}\n${percent.toFixed(1)}%` })) : (_jsx(CardEmptyState, { title: REPORT_CARD_EMPTY_STATE_TITLES.storage }))) : chartData.length === 0 ? (_jsx(CardEmptyState, { title: REPORT_CARD_EMPTY_STATE_TITLES.storage })) : (_jsx(MigrationDonutChart, { ...commonDonutProps, data: chartData.map((item) => ({
307
+ ...item,
308
+ countDisplay: viewMode === "totalSize"
309
+ ? `${item.countDisplay} TB`
310
+ : `${item.countDisplay} VMs`,
311
+ })), legend: tierLegend, title: viewMode === "totalSize"
312
+ ? `${totals.totalSize.toFixed(2)} TB`
313
+ : `${totals.totalVMs} VMs`, subTitle: viewMode === "totalSize"
314
+ ? `${totals.totalVMs} VMs`
315
+ : `${totals.totalSize.toFixed(2)} TB`, legendLabelFormatter: ({ x, countDisplay }) => `${x} (${countDisplay})`, tooltipLabelFormatter: ({ datum, percent }) => `${datum.x}: ${datum.countDisplay}\n${percent.toFixed(1)}%`, onItemClick: viewMode === "vmCount" || viewMode === "totalSize"
316
+ ? handleDiskTierClick
317
+ : undefined, onTitleClick: onTitleClick })) })] }) }));
318
+ };
319
+ StorageOverview.displayName = "StorageOverview";
@@ -0,0 +1,33 @@
1
+ import type { FC } from "react";
2
+ import { type MigrationDonutChartLegendVariant } from "../charts/MigrationDonutChart.js";
3
+ /**
4
+ * VM-issue counts this card reads. agent-sdk's `IssuesBreakdown` satisfies it;
5
+ * each field stays optional so older or partial payloads do not throw.
6
+ */
7
+ export interface IssuesBreakdownLike {
8
+ critical?: number;
9
+ error?: number;
10
+ warning?: number;
11
+ information?: number;
12
+ advisory?: number;
13
+ }
14
+ export interface VMMigrationStatusProps {
15
+ data: {
16
+ migratable: number;
17
+ nonMigratable: number;
18
+ };
19
+ issuesBreakdown?: IssuesBreakdownLike;
20
+ /**
21
+ * Donut-slice click. `true` for the migratable slice, `false` otherwise.
22
+ * Agent-ui maps it to a VM migration-readiness filter; ui-app omits it.
23
+ */
24
+ onReadinessSelect?: (isMigratable: boolean) => void;
25
+ /** Breakdown-bar click. The argument is the concern category label. */
26
+ onCategorySelect?: (category: string) => void;
27
+ /** Center-title click. Agent-ui opens the VM list with no extra filters. */
28
+ onTitleClick?: () => void;
29
+ /** `"html"` in agent-ui; ui-app passes `"chart"`. */
30
+ legendVariant?: MigrationDonutChartLegendVariant;
31
+ }
32
+ export declare const VMMigrationStatus: FC<VMMigrationStatusProps>;
33
+ //# sourceMappingURL=VMMigrationStatus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VMMigrationStatus.d.ts","sourceRoot":"","sources":["../../src/report/VMMigrationStatus.tsx"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,EAAE,EAAO,MAAM,OAAO,CAAC;AAErC,OAAO,EAEL,KAAK,gCAAgC,EACtC,MAAM,kCAAkC,CAAC;AAY1C;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE;QACJ,UAAU,EAAE,MAAM,CAAC;QACnB,aAAa,EAAE,MAAM,CAAC;KACvB,CAAC;IACF,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC;;;OAGG;IACH,iBAAiB,CAAC,EAAE,CAAC,YAAY,EAAE,OAAO,KAAK,IAAI,CAAC;IACpD,uEAAuE;IACvE,gBAAgB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,4EAA4E;IAC5E,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,qDAAqD;IACrD,aAAa,CAAC,EAAE,gCAAgC,CAAC;CAClD;AA+CD,eAAO,MAAM,iBAAiB,EAAE,EAAE,CAAC,sBAAsB,CAgPxD,CAAC"}
@@ -0,0 +1,129 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { css } from "@emotion/css";
3
+ import { Card, CardBody, CardTitle, Content, Dropdown, DropdownItem, DropdownList, Flex, FlexItem, MenuToggle, } from "@patternfly/react-core";
4
+ import { VirtualMachineIcon } from "@patternfly/react-icons";
5
+ import { chart_color_blue_300, chart_color_purple_100, chart_color_purple_300, chart_color_teal_200, chart_color_yellow_400, } from "@patternfly/react-tokens";
6
+ import { useMemo, useState } from "react";
7
+ import { MigrationDonutChart, } from "../charts/MigrationDonutChart.js";
8
+ import { CardEmptyState } from "./CardEmptyState.js";
9
+ import { ChartHeaderActions } from "./ChartDownloadButton.js";
10
+ import { ChartExportSurface } from "./ChartExportSurface.js";
11
+ import { chartExportViewsFromLabels } from "./chartExport.js";
12
+ import { chartColorFailure, chartColorSuccess, REPORT_CARD_EMPTY_STATE_TITLES, } from "./constants.js";
13
+ import { dashboardStyles } from "./dashboardStyles.js";
14
+ const VIEW_MODE_LABELS = {
15
+ issuesVsNoIssues: "No issues vs with issues",
16
+ issuesBreakdown: "With issues breakdown",
17
+ };
18
+ const CHART_ID = "vm-migration-status";
19
+ const CHART_TITLE = "VM migration status";
20
+ const MIGRATABLE_LABEL = "Migratable";
21
+ const NOT_MIGRATABLE_LABEL = "Not ready for migration";
22
+ const CATEGORY_ORDER = [
23
+ { label: "Critical", key: "critical" },
24
+ { label: "Error", key: "error" },
25
+ { label: "Warning", key: "warning" },
26
+ { label: "Information", key: "information" },
27
+ { label: "Advisory", key: "advisory" },
28
+ ];
29
+ /**
30
+ * Same categorical order as the previous breakdown hex palette, mapped onto
31
+ * PatternFly `chart_color_*` tokens (nearest token where the old hex was not
32
+ * itself a token).
33
+ */
34
+ const CATEGORY_COLORS = {
35
+ Critical: chart_color_blue_300.value,
36
+ Error: chart_color_purple_300.value,
37
+ Warning: chart_color_purple_100.value,
38
+ Information: chart_color_teal_200.value,
39
+ Advisory: chart_color_yellow_400.value,
40
+ };
41
+ const menuToggleMinWidth = css `
42
+ min-width: 250px;
43
+ `;
44
+ const isViewMode = (value) => value === "issuesVsNoIssues" || value === "issuesBreakdown";
45
+ const toCount = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : 0;
46
+ export const VMMigrationStatus = ({ data, issuesBreakdown, onReadinessSelect, onCategorySelect, onTitleClick, legendVariant = "html", }) => {
47
+ const [viewMode, setViewMode] = useState("issuesVsNoIssues");
48
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
49
+ const totalVMs = data.migratable + data.nonMigratable;
50
+ const donutData = [
51
+ {
52
+ name: MIGRATABLE_LABEL,
53
+ count: data.migratable,
54
+ countDisplay: `${data.migratable} VMs`,
55
+ legendCategory: MIGRATABLE_LABEL,
56
+ },
57
+ {
58
+ name: NOT_MIGRATABLE_LABEL,
59
+ count: data.nonMigratable,
60
+ countDisplay: `${data.nonMigratable} VMs`,
61
+ legendCategory: NOT_MIGRATABLE_LABEL,
62
+ },
63
+ ];
64
+ const legend = {
65
+ [MIGRATABLE_LABEL]: chartColorSuccess,
66
+ [NOT_MIGRATABLE_LABEL]: chartColorFailure,
67
+ };
68
+ const breakdownData = useMemo(() => {
69
+ if (!issuesBreakdown) {
70
+ return [];
71
+ }
72
+ return CATEGORY_ORDER.map(({ label, key }) => ({
73
+ name: label,
74
+ count: toCount(issuesBreakdown[key]),
75
+ }));
76
+ }, [issuesBreakdown]);
77
+ const maxCount = useMemo(() => breakdownData.length > 0
78
+ ? Math.max(...breakdownData.map((d) => d.count))
79
+ : 0, [breakdownData]);
80
+ const chartTitle = `${CHART_TITLE} — ${VIEW_MODE_LABELS[viewMode]}`;
81
+ return (_jsx(ChartExportSurface, { id: CHART_ID, title: chartTitle, exportViews: chartExportViewsFromLabels(CHART_TITLE, VIEW_MODE_LABELS), activeExportViewId: viewMode, onExportViewChange: (viewId) => {
82
+ if (isViewMode(viewId)) {
83
+ setViewMode(viewId);
84
+ }
85
+ }, children: _jsxs(Card, { className: dashboardStyles.card, children: [_jsx(CardTitle, { children: _jsxs(Flex, { alignItems: { default: "alignItemsCenter" }, justifyContent: { default: "justifyContentSpaceBetween" }, children: [_jsxs(FlexItem, { children: [_jsx(VirtualMachineIcon, {}), " VM Migration Status"] }), _jsx(ChartHeaderActions, { chartId: CHART_ID, title: chartTitle, children: _jsx(Dropdown, { isOpen: isDropdownOpen, onSelect: (_event, value) => {
86
+ if (isViewMode(value)) {
87
+ setViewMode(value);
88
+ }
89
+ setIsDropdownOpen(false);
90
+ }, onOpenChange: setIsDropdownOpen, toggle: (toggleRef) => (_jsx(MenuToggle, { ref: toggleRef, onClick: () => setIsDropdownOpen((open) => !open), isExpanded: isDropdownOpen, className: menuToggleMinWidth, children: VIEW_MODE_LABELS[viewMode] })), children: _jsxs(DropdownList, { children: [_jsx(DropdownItem, { value: "issuesVsNoIssues", children: VIEW_MODE_LABELS.issuesVsNoIssues }, "issuesVsNoIssues"), _jsx(DropdownItem, { value: "issuesBreakdown", children: VIEW_MODE_LABELS.issuesBreakdown }, "issuesBreakdown")] }) }) })] }) }), _jsx(CardBody, { className: dashboardStyles.cardBodyScrollable, children: viewMode === "issuesVsNoIssues" ? (totalVMs === 0 ? (_jsx(CardEmptyState, { title: REPORT_CARD_EMPTY_STATE_TITLES.migrationStatus })) : (_jsx(MigrationDonutChart, { legendVariant: legendVariant, data: donutData, legend: legend, height: 300, width: 420, donutThickness: 18, padAngle: 1, title: `${totalVMs}`, subTitle: "VMs", subTitleColor: "var(--pf-t--global--text--color--subtle)", titleFontSize: 34, legendLabelFormatter: ({ x, countDisplay }) => `${x} (${countDisplay})`, onItemClick: onReadinessSelect
91
+ ? (item) => onReadinessSelect(item.name === MIGRATABLE_LABEL)
92
+ : undefined, onTitleClick: onTitleClick }))) : breakdownData.length === 0 ? (_jsx(CardEmptyState, { title: REPORT_CARD_EMPTY_STATE_TITLES.issuesBreakdown })) : (_jsxs("div", { children: [_jsx("div", { className: dashboardStyles.storageChartWrapper, children: _jsx(Flex, { direction: { default: "row" }, alignItems: { default: "alignItemsFlexEnd" }, justifyContent: { default: "justifyContentCenter" }, spaceItems: { default: "spaceItemsMd" }, style: {
93
+ height: "250px",
94
+ width: "100%",
95
+ }, children: breakdownData.map((item) => {
96
+ const heightPercentage = maxCount > 0 ? (item.count / maxCount) * 100 : 0;
97
+ const minHeightPercentage = item.count > 0 ? 20 : 0;
98
+ const finalHeightPercentage = Math.max(heightPercentage, minHeightPercentage);
99
+ const barColor = CATEGORY_COLORS[item.name] || CATEGORY_COLORS.Critical;
100
+ return (_jsxs(Flex, { direction: { default: "column" }, alignItems: { default: "alignItemsCenter" }, spaceItems: { default: "spaceItemsSm" }, style: { flex: "1", maxWidth: "120px" }, children: [_jsx(FlexItem, { style: {
101
+ height: "200px",
102
+ display: "flex",
103
+ alignItems: "flex-end",
104
+ width: "100%",
105
+ justifyContent: "center",
106
+ }, children: _jsx("button", { type: "button", onClick: () => onCategorySelect?.(item.name), title: `${item.name}: ${item.count} VMs`, style: {
107
+ width: "60px",
108
+ height: `${finalHeightPercentage}%`,
109
+ backgroundColor: barColor,
110
+ transition: "height 0.3s ease",
111
+ borderRadius: "4px 4px 0 0",
112
+ cursor: "pointer",
113
+ border: "none",
114
+ padding: 0,
115
+ } }) }), _jsx(FlexItem, { children: _jsxs(Content, { component: "small", style: {
116
+ fontSize: "12px",
117
+ textAlign: "center",
118
+ wordBreak: "break-word",
119
+ color: "var(--pf-t--global--text--color--regular)",
120
+ }, children: [item.name, _jsx("br", {}), "(", item.count, " VMs)"] }) })] }, item.name));
121
+ }) }) }), _jsx("div", { children: _jsx(Content, { component: "small", style: {
122
+ fontSize: "12px",
123
+ color: "var(--pf-t--global--text--color--subtle)",
124
+ marginTop: "16px",
125
+ textAlign: "center",
126
+ display: "block",
127
+ }, children: "Totals may exceed the unique VM count because a VM can appear in multiple categories" }) })] })) })] }) }));
128
+ };
129
+ VMMigrationStatus.displayName = "VMMigrationStatus";
@@ -1,8 +1,13 @@
1
+ export declare const CHART_CAPTURE_SCALE_SINGLE = 2;
2
+ export type CaptureChartElementOptions = {
3
+ /** Higher scale sharpens text in PNG exports; keep at 1 for bulk PDF/zip. */
4
+ scale?: number;
5
+ };
1
6
  /**
2
7
  * Tab panels stay mounted with `hidden` / `display:none` when another tab is
3
8
  * active. html2canvas then measures a 0×0 box. Reveal those ancestors off-screen
4
9
  * for the duration of the capture, then restore.
5
10
  */
6
11
  export declare function revealHiddenChartAncestors(element: HTMLElement): () => void;
7
- export declare function captureChartElement(element: HTMLElement): Promise<HTMLCanvasElement>;
12
+ export declare function captureChartElement(element: HTMLElement, options?: CaptureChartElementOptions): Promise<HTMLCanvasElement>;
8
13
  //# sourceMappingURL=captureChartElement.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"captureChartElement.d.ts","sourceRoot":"","sources":["../../src/report/captureChartElement.ts"],"names":[],"mappings":"AAgCA;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,IAAI,CAqD3E;AAiPD,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,iBAAiB,CAAC,CAkC5B"}
1
+ {"version":3,"file":"captureChartElement.d.ts","sourceRoot":"","sources":["../../src/report/captureChartElement.ts"],"names":[],"mappings":"AAeA,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAG5C,MAAM,MAAM,0BAA0B,GAAG;IACvC,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAwBF;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,IAAI,CAqD3E;AAwQD,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,iBAAiB,CAAC,CA0C5B"}