@openshift-migration-advisor/shared-components 0.25.0 → 0.27.0

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 (124) hide show
  1. package/README.md +25 -0
  2. package/dist/report/ChartDownloadButton.d.ts +11 -0
  3. package/dist/report/ChartDownloadButton.d.ts.map +1 -0
  4. package/dist/report/ChartDownloadButton.js +24 -0
  5. package/dist/report/ChartExportProvider.d.ts +22 -0
  6. package/dist/report/ChartExportProvider.d.ts.map +1 -0
  7. package/dist/report/ChartExportProvider.js +222 -0
  8. package/dist/report/ChartExportSurface.d.ts +12 -0
  9. package/dist/report/ChartExportSurface.d.ts.map +1 -0
  10. package/dist/report/ChartExportSurface.js +32 -0
  11. package/dist/report/ErrorTable.d.ts +18 -0
  12. package/dist/report/ErrorTable.d.ts.map +1 -0
  13. package/dist/report/ErrorTable.js +20 -0
  14. package/dist/report/HostPowerStates.d.ts +0 -1
  15. package/dist/report/HostPowerStates.d.ts.map +1 -1
  16. package/dist/report/HostPowerStates.js +2 -2
  17. package/dist/report/HostsOverview.d.ts +13 -0
  18. package/dist/report/HostsOverview.d.ts.map +1 -0
  19. package/dist/report/HostsOverview.js +84 -0
  20. package/dist/report/InfrastructureSummary.d.ts.map +1 -1
  21. package/dist/report/InfrastructureSummary.js +6 -2
  22. package/dist/report/NetworkOverview.d.ts +31 -0
  23. package/dist/report/NetworkOverview.d.ts.map +1 -0
  24. package/dist/report/NetworkOverview.js +201 -0
  25. package/dist/report/OSDistribution.d.ts +0 -2
  26. package/dist/report/OSDistribution.d.ts.map +1 -1
  27. package/dist/report/OSDistribution.js +8 -5
  28. package/dist/report/OsUpgradeRecommendationPopover.d.ts +0 -1
  29. package/dist/report/OsUpgradeRecommendationPopover.d.ts.map +1 -1
  30. package/dist/report/OsUpgradeRecommendationPopover.js +4 -3
  31. package/dist/report/PowerStateCard.d.ts +0 -1
  32. package/dist/report/PowerStateCard.d.ts.map +1 -1
  33. package/dist/report/PowerStateCard.js +4 -2
  34. package/dist/report/ReportExportMenu.d.ts +19 -0
  35. package/dist/report/ReportExportMenu.d.ts.map +1 -0
  36. package/dist/report/ReportExportMenu.js +31 -0
  37. package/dist/report/ReportTable.d.ts +14 -0
  38. package/dist/report/ReportTable.d.ts.map +1 -0
  39. package/dist/report/ReportTable.js +23 -0
  40. package/dist/report/SupportTierBadge.d.ts +0 -1
  41. package/dist/report/SupportTierBadge.d.ts.map +1 -1
  42. package/dist/report/SupportTierBadge.js +1 -21
  43. package/dist/report/VCenterClusterDetails.d.ts +0 -1
  44. package/dist/report/VCenterClusterDetails.d.ts.map +1 -1
  45. package/dist/report/VCenterClusterDetails.js +9 -4
  46. package/dist/report/VmPowerStates.d.ts +0 -1
  47. package/dist/report/VmPowerStates.d.ts.map +1 -1
  48. package/dist/report/VmPowerStates.js +2 -2
  49. package/dist/report/WarningsTable.d.ts +9 -0
  50. package/dist/report/WarningsTable.d.ts.map +1 -0
  51. package/dist/report/WarningsTable.js +20 -0
  52. package/dist/report/captureChartElement.d.ts +8 -0
  53. package/dist/report/captureChartElement.d.ts.map +1 -0
  54. package/dist/report/captureChartElement.js +284 -0
  55. package/dist/report/chartExport.d.ts +57 -0
  56. package/dist/report/chartExport.d.ts.map +1 -0
  57. package/dist/report/chartExport.js +162 -0
  58. package/dist/report/chartExportContext.d.ts +28 -0
  59. package/dist/report/chartExportContext.d.ts.map +1 -0
  60. package/dist/report/chartExportContext.js +36 -0
  61. package/dist/report/chartExportFilenames.d.ts +4 -0
  62. package/dist/report/chartExportFilenames.d.ts.map +1 -0
  63. package/dist/report/chartExportFilenames.js +16 -0
  64. package/dist/report/constants.d.ts +2 -0
  65. package/dist/report/constants.d.ts.map +1 -1
  66. package/dist/report/constants.js +2 -0
  67. package/dist/report/dashboardStyles.d.ts +0 -4
  68. package/dist/report/dashboardStyles.d.ts.map +1 -1
  69. package/dist/report/dashboardStyles.js +0 -19
  70. package/dist/report/htmlExport.d.ts +9 -0
  71. package/dist/report/htmlExport.d.ts.map +1 -0
  72. package/dist/report/htmlExport.js +65 -0
  73. package/dist/report/index.d.ts +18 -0
  74. package/dist/report/index.d.ts.map +1 -1
  75. package/dist/report/index.js +18 -0
  76. package/dist/report/pdfExport.d.ts +3 -0
  77. package/dist/report/pdfExport.d.ts.map +1 -0
  78. package/dist/report/pdfExport.js +82 -0
  79. package/dist/report/pdfPage.d.ts +15 -0
  80. package/dist/report/pdfPage.d.ts.map +1 -0
  81. package/dist/report/pdfPage.js +44 -0
  82. package/dist/report/reportExportOptions.d.ts +17 -0
  83. package/dist/report/reportExportOptions.d.ts.map +1 -0
  84. package/dist/report/reportExportOptions.js +31 -0
  85. package/dist/report/zipChartPngs.d.ts +3 -0
  86. package/dist/report/zipChartPngs.d.ts.map +1 -0
  87. package/dist/report/zipChartPngs.js +8 -0
  88. package/package.json +7 -2
  89. package/src/report/ChartDownloadButton.tsx +59 -0
  90. package/src/report/ChartExportProvider.tsx +384 -0
  91. package/src/report/ChartExportSurface.tsx +60 -0
  92. package/src/report/ErrorTable.tsx +85 -0
  93. package/src/report/HostPowerStates.tsx +0 -3
  94. package/src/report/HostsOverview.tsx +185 -0
  95. package/src/report/InfrastructureSummary.tsx +48 -31
  96. package/src/report/NetworkOverview.tsx +405 -0
  97. package/src/report/OSDistribution.tsx +46 -42
  98. package/src/report/OsUpgradeRecommendationPopover.tsx +3 -4
  99. package/src/report/PowerStateCard.tsx +49 -36
  100. package/src/report/ReportExportMenu.tsx +119 -0
  101. package/src/report/ReportTable.tsx +81 -0
  102. package/src/report/SupportTierBadge.tsx +12 -40
  103. package/src/report/VCenterClusterDetails.tsx +38 -16
  104. package/src/report/VmPowerStates.tsx +0 -3
  105. package/src/report/WarningsTable.tsx +75 -0
  106. package/src/report/__tests__/ChartExportProvider.test.tsx +474 -0
  107. package/src/report/__tests__/HostsOverview.test.tsx +145 -0
  108. package/src/report/__tests__/InfrastructureSummary.test.tsx +0 -22
  109. package/src/report/__tests__/NetworkOverview.test.tsx +269 -0
  110. package/src/report/__tests__/OSDistribution.test.tsx +10 -11
  111. package/src/report/__tests__/captureChartElement.test.ts +132 -0
  112. package/src/report/__tests__/chartExportPackagers.test.ts +224 -0
  113. package/src/report/captureChartElement.ts +368 -0
  114. package/src/report/chartExport.ts +237 -0
  115. package/src/report/chartExportContext.tsx +84 -0
  116. package/src/report/chartExportFilenames.ts +21 -0
  117. package/src/report/constants.ts +2 -0
  118. package/src/report/dashboardStyles.ts +0 -23
  119. package/src/report/htmlExport.ts +83 -0
  120. package/src/report/index.ts +95 -0
  121. package/src/report/pdfExport.ts +154 -0
  122. package/src/report/pdfPage.ts +83 -0
  123. package/src/report/reportExportOptions.ts +53 -0
  124. package/src/report/zipChartPngs.ts +10 -0
@@ -0,0 +1,201 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { css } from "@emotion/css";
3
+ import { Card, CardBody, CardTitle, Dropdown, DropdownItem, DropdownList, Flex, FlexItem, MenuToggle, } from "@patternfly/react-core";
4
+ import { TopologyIcon } from "@patternfly/react-icons";
5
+ import { chart_color_black_500, chart_color_blue_300, chart_color_green_300, chart_color_orange_100, chart_color_purple_100, chart_color_purple_300, chart_color_red_orange_200, chart_color_teal_200, chart_color_teal_400, 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 { REPORT_CARD_EMPTY_STATE_TITLES } from "./constants.js";
13
+ import { dashboardStyles } from "./dashboardStyles.js";
14
+ const VIEW_MODE_LABELS = {
15
+ networkDistribution: "VM distribution by network",
16
+ nicCount: "VM distribution by NIC count",
17
+ };
18
+ const TOP_NETWORKS = 4;
19
+ const REST_OF_NETWORKS_LABEL = "Rest of networks";
20
+ const CHART_ID = "network-overview";
21
+ const CHART_TITLE = "Networks";
22
+ /**
23
+ * Categorical order from the ui-app network donut, mapped onto PatternFly
24
+ * `chart_color_*` tokens (nearest token where the old hex was not a token).
25
+ */
26
+ const NETWORK_COLORS = [
27
+ chart_color_blue_300.value,
28
+ chart_color_purple_300.value,
29
+ chart_color_purple_100.value,
30
+ chart_color_teal_200.value,
31
+ chart_color_yellow_400.value,
32
+ chart_color_green_300.value,
33
+ chart_color_orange_100.value,
34
+ chart_color_red_orange_200.value,
35
+ chart_color_teal_400.value,
36
+ chart_color_black_500.value,
37
+ ];
38
+ const cardSubtitle = css `
39
+ color: var(--pf-t--global--text--color--subtle);
40
+ font-size: 0.85rem;
41
+ `;
42
+ const menuToggleMinWidth = css `
43
+ min-width: 250px;
44
+ `;
45
+ const titleRow = css `
46
+ width: 100%;
47
+ `;
48
+ const isViewMode = (value) => value === "networkDistribution" || value === "nicCount";
49
+ const legendFor = (categories) => {
50
+ const legend = {};
51
+ categories.forEach((category, index) => {
52
+ const color = NETWORK_COLORS[index % NETWORK_COLORS.length];
53
+ if (color) {
54
+ legend[category] = color;
55
+ }
56
+ });
57
+ return legend;
58
+ };
59
+ const vlanLabel = (vlanId) => {
60
+ if (typeof vlanId !== "string" && typeof vlanId !== "number") {
61
+ return "-";
62
+ }
63
+ const value = String(vlanId).trim();
64
+ return value !== "" ? value : "-";
65
+ };
66
+ const buildNetworkDonut = (networks) => {
67
+ const items = (Array.isArray(networks) ? networks : [])
68
+ .map((network) => ({
69
+ vmsCount: typeof network?.vmsCount === "number" ? network.vmsCount : Number.NaN,
70
+ vlanId: vlanLabel(network?.vlanId),
71
+ }))
72
+ .filter((network) => Number.isFinite(network.vmsCount) && network.vmsCount >= 0)
73
+ .sort((a, b) => b.vmsCount - a.vmsCount);
74
+ const totalVMs = items.reduce((sum, network) => sum + network.vmsCount, 0);
75
+ const top = items.slice(0, TOP_NETWORKS);
76
+ const restSum = items
77
+ .slice(TOP_NETWORKS)
78
+ .reduce((sum, network) => sum + network.vmsCount, 0);
79
+ const vlanBySlice = {};
80
+ const slices = top.map((network, index) => {
81
+ const name = `Network ${index + 1}`;
82
+ vlanBySlice[name] = network.vlanId;
83
+ return {
84
+ name,
85
+ count: network.vmsCount,
86
+ countDisplay: `${network.vmsCount} VMs`,
87
+ legendCategory: name,
88
+ };
89
+ });
90
+ if (restSum > 0) {
91
+ slices.push({
92
+ name: REST_OF_NETWORKS_LABEL,
93
+ count: restSum,
94
+ countDisplay: `${restSum} VMs`,
95
+ legendCategory: REST_OF_NETWORKS_LABEL,
96
+ });
97
+ vlanBySlice[REST_OF_NETWORKS_LABEL] = "-";
98
+ }
99
+ return {
100
+ slices,
101
+ legend: legendFor(slices.map((slice) => slice.legendCategory)),
102
+ title: `${totalVMs}`,
103
+ vlanBySlice,
104
+ };
105
+ };
106
+ const nicBucketLabel = (bucket) => {
107
+ const count = Number.parseInt(bucket, 10);
108
+ if (bucket.endsWith("+")) {
109
+ return `${count}+ NIC`;
110
+ }
111
+ return count === 1 ? "1 NIC" : `${count} NIC`;
112
+ };
113
+ const compareNicBuckets = (a, b) => {
114
+ const aPlus = a.bucket.endsWith("+");
115
+ const bPlus = b.bucket.endsWith("+");
116
+ if (aPlus !== bPlus) {
117
+ return aPlus ? 1 : -1;
118
+ }
119
+ const aNum = Number.parseInt(a.bucket, 10);
120
+ const bNum = Number.parseInt(b.bucket, 10);
121
+ if (Number.isFinite(aNum) && Number.isFinite(bNum)) {
122
+ return aNum - bNum;
123
+ }
124
+ return a.bucket.localeCompare(b.bucket);
125
+ };
126
+ const slicesFromBuckets = (entries) => entries.map((entry) => {
127
+ const label = nicBucketLabel(entry.bucket);
128
+ return {
129
+ name: label,
130
+ count: entry.count,
131
+ countDisplay: `${entry.count} VMs`,
132
+ legendCategory: label,
133
+ };
134
+ });
135
+ const buildNicDonut = (distributionByNicCount, nicCount) => {
136
+ if (distributionByNicCount != null) {
137
+ const entries = Object.entries(distributionByNicCount)
138
+ .map(([bucket, value]) => ({
139
+ bucket,
140
+ count: Number.isFinite(Number(value)) ? Number(value) : 0,
141
+ }))
142
+ .filter((entry) => entry.count > 0)
143
+ .sort(compareNicBuckets);
144
+ const slices = slicesFromBuckets(entries);
145
+ const total = entries.reduce((sum, entry) => sum + entry.count, 0);
146
+ return {
147
+ slices,
148
+ legend: legendFor(slices.map((slice) => slice.legendCategory)),
149
+ title: `${total}`,
150
+ };
151
+ }
152
+ const histogram = nicCount?.histogram;
153
+ const data = Array.isArray(histogram?.data) ? histogram.data : [];
154
+ const minValue = typeof histogram?.minValue === "number" ? histogram.minValue : 0;
155
+ const step = typeof histogram?.step === "number" ? histogram.step : 1;
156
+ const buckets = data
157
+ .map((count, index) => ({
158
+ bucket: String(minValue + index * step),
159
+ count: Number(count) || 0,
160
+ }))
161
+ .filter((entry) => entry.count > 0)
162
+ .sort((a, b) => Number(a.bucket) - Number(b.bucket));
163
+ const slices = slicesFromBuckets(buckets);
164
+ const total = typeof nicCount?.total === "number"
165
+ ? nicCount.total
166
+ : buckets.reduce((sum, entry) => sum + entry.count, 0);
167
+ return {
168
+ slices,
169
+ legend: legendFor(slices.map((slice) => slice.legendCategory)),
170
+ title: `${total}`,
171
+ };
172
+ };
173
+ export const NetworkOverview = ({ infra, nicCount, distributionByNicCount, legendVariant = "html", }) => {
174
+ const [viewMode, setViewMode] = useState("networkDistribution");
175
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
176
+ const networkChart = useMemo(() => buildNetworkDonut(infra?.networks), [infra?.networks]);
177
+ const nicChart = useMemo(() => buildNicDonut(distributionByNicCount, nicCount), [distributionByNicCount, nicCount]);
178
+ const chartTitle = `${CHART_TITLE} — ${VIEW_MODE_LABELS[viewMode]}`;
179
+ const activeChart = viewMode === "networkDistribution" ? networkChart : nicChart;
180
+ const emptyTitle = viewMode === "networkDistribution"
181
+ ? REPORT_CARD_EMPTY_STATE_TITLES.networks
182
+ : REPORT_CARD_EMPTY_STATE_TITLES.nicCount;
183
+ return (_jsx(ChartExportSurface, { id: CHART_ID, title: chartTitle, exportViews: chartExportViewsFromLabels(CHART_TITLE, VIEW_MODE_LABELS), activeExportViewId: viewMode, onExportViewChange: (viewId) => {
184
+ if (isViewMode(viewId)) {
185
+ setViewMode(viewId);
186
+ }
187
+ }, children: _jsxs(Card, { className: dashboardStyles.card, children: [_jsx(CardTitle, { children: _jsxs(Flex, { className: titleRow, justifyContent: { default: "justifyContentSpaceBetween" }, alignItems: { default: "alignItemsCenter" }, children: [_jsx(FlexItem, { children: _jsxs("div", { children: [_jsxs("div", { children: [_jsx(TopologyIcon, {}), " ", CHART_TITLE] }), viewMode === "networkDistribution" && (_jsx("div", { className: cardSubtitle, children: "Top 5 networks" }))] }) }), _jsx(ChartHeaderActions, { chartId: CHART_ID, title: chartTitle, children: _jsx(Dropdown, { isOpen: isDropdownOpen, onSelect: (_event, value) => {
188
+ if (isViewMode(value)) {
189
+ setViewMode(value);
190
+ }
191
+ setIsDropdownOpen(false);
192
+ }, 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: "networkDistribution", children: VIEW_MODE_LABELS.networkDistribution }, "networkDistribution"), _jsx(DropdownItem, { value: "nicCount", children: VIEW_MODE_LABELS.nicCount }, "nicCount")] }) }) })] }) }), _jsx(CardBody, { className: dashboardStyles.cardBodyScrollable, children: activeChart.slices.length === 0 ? (_jsx(CardEmptyState, { title: emptyTitle })) : (_jsx(MigrationDonutChart, { legendVariant: legendVariant, data: activeChart.slices, legend: activeChart.legend, height: 300, width: 420, donutThickness: 18, titleFontSize: 34, title: activeChart.title, subTitle: "VMs", subTitleColor: "var(--pf-t--global--text--color--subtle)", itemsPerRow: Math.ceil(activeChart.slices.length / 2), labelFontSize: 18, marginLeft: "0%", tooltipLabelFormatter: ({ datum, percent }) => {
193
+ const summary = `${datum.countDisplay}\n${percent.toFixed(1)}%`;
194
+ if (viewMode !== "networkDistribution") {
195
+ return summary;
196
+ }
197
+ const vlan = networkChart.vlanBySlice[datum.legendCategory];
198
+ return `${summary}\nVLAN: ${vlan ?? "-"}`;
199
+ } })) })] }) }));
200
+ };
201
+ NetworkOverview.displayName = "NetworkOverview";
@@ -2,12 +2,10 @@ import type { FC } from "react";
2
2
  import { type OSDistributionEntry } from "./osSupportTier.js";
3
3
  interface OSDistributionProps {
4
4
  osData: Record<string, OSDistributionEntry>;
5
- isExportMode?: boolean;
6
5
  }
7
6
  export declare const OSDistribution: FC<OSDistributionProps>;
8
7
  interface OSBarChartProps {
9
8
  osData: Record<string, OSDistributionEntry>;
10
- isExportMode?: boolean;
11
9
  }
12
10
  export declare const OSBarChart: FC<OSBarChartProps>;
13
11
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"OSDistribution.d.ts","sourceRoot":"","sources":["../../src/report/OSDistribution.tsx"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,EAAE,EAAO,MAAM,OAAO,CAAC;AAQrC,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,oBAAoB,CAAC;AAY5B,UAAU,mBAAmB;IAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAC5C,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,eAAO,MAAM,cAAc,EAAE,EAAE,CAAC,mBAAmB,CAyBlD,CAAC;AAEF,UAAU,eAAe;IACvB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAC5C,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,eAAO,MAAM,UAAU,EAAE,EAAE,CAAC,eAAe,CAoH1C,CAAC"}
1
+ {"version":3,"file":"OSDistribution.d.ts","sourceRoot":"","sources":["../../src/report/OSDistribution.tsx"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,EAAE,EAAO,MAAM,OAAO,CAAC;AAWrC,OAAO,EAGL,KAAK,mBAAmB,EACzB,MAAM,oBAAoB,CAAC;AAe5B,UAAU,mBAAmB;IAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;CAC7C;AAED,eAAO,MAAM,cAAc,EAAE,EAAE,CAAC,mBAAmB,CAmClD,CAAC;AAEF,UAAU,eAAe;IACvB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;CAC7C;AAED,eAAO,MAAM,UAAU,EAAE,EAAE,CAAC,eAAe,CA0G1C,CAAC"}
@@ -4,6 +4,9 @@ import { Card, CardBody, CardTitle, Flex, FlexItem, MenuToggle, SearchInput, Sel
4
4
  import { DesktopIcon } from "@patternfly/react-icons";
5
5
  import { Table, Tbody, Td, Th, Thead, Tr } from "@patternfly/react-table";
6
6
  import { CardEmptyState } from "./CardEmptyState.js";
7
+ import { ChartHeaderActions } from "./ChartDownloadButton.js";
8
+ import { ChartExportSurface } from "./ChartExportSurface.js";
9
+ import { chartExportHideProps, chartExportScrollProps } from "./chartExport.js";
7
10
  import { REPORT_CARD_EMPTY_STATE_TITLES } from "./constants.js";
8
11
  import { dashboardStyles, tableFullWidthStyle } from "./dashboardStyles.js";
9
12
  import { EmptySearchResults } from "./EmptySearchResults.js";
@@ -17,13 +20,13 @@ const tableScrollStyle = css `
17
20
  overflow: auto;
18
21
  max-height: 350px;
19
22
  `;
20
- export const OSDistribution = ({ osData, isExportMode = false, }) => (_jsxs(Card, { className: isExportMode ? dashboardStyles.cardPrint : dashboardStyles.card, id: "os-distribution", children: [_jsx(CardTitle, { children: _jsxs(Flex, { alignItems: { default: "alignItemsCenter" }, spaceItems: { default: "spaceItemsSm" }, children: [_jsxs(FlexItem, { children: [_jsx(DesktopIcon, {}), " Operating Systems"] }), _jsx(FlexItem, { children: _jsx(OsSupportTiersHelpPopover, {}) })] }) }), _jsx(CardBody, { children: _jsx(OSBarChart, { osData: osData, isExportMode: isExportMode }) })] }));
21
- export const OSBarChart = ({ osData, isExportMode = false, }) => {
23
+ const OS_DISTRIBUTION_CHART_ID = "os-distribution";
24
+ const OS_DISTRIBUTION_TITLE = "Operating system distribution";
25
+ export const OSDistribution = ({ osData }) => (_jsx(ChartExportSurface, { id: OS_DISTRIBUTION_CHART_ID, title: OS_DISTRIBUTION_TITLE, children: _jsxs(Card, { className: dashboardStyles.card, children: [_jsx(CardTitle, { children: _jsxs(Flex, { justifyContent: { default: "justifyContentSpaceBetween" }, alignItems: { default: "alignItemsCenter" }, children: [_jsx(FlexItem, { children: _jsxs(Flex, { alignItems: { default: "alignItemsCenter" }, spaceItems: { default: "spaceItemsSm" }, children: [_jsxs(FlexItem, { children: [_jsx(DesktopIcon, {}), " Operating Systems"] }), _jsx(FlexItem, { ...chartExportHideProps, children: _jsx(OsSupportTiersHelpPopover, {}) })] }) }), _jsx(ChartHeaderActions, { chartId: OS_DISTRIBUTION_CHART_ID, title: OS_DISTRIBUTION_TITLE })] }) }), _jsx(CardBody, { children: _jsx(OSBarChart, { osData: osData }) })] }) }));
26
+ export const OSBarChart = ({ osData }) => {
22
27
  const vm = useOsBarChartViewModel(osData);
23
28
  if (vm.tableRows.length === 0) {
24
29
  return (_jsx(CardEmptyState, { title: REPORT_CARD_EMPTY_STATE_TITLES.operatingSystems }));
25
30
  }
26
- return (_jsxs(_Fragment, { children: [!isExportMode ? (_jsx(Toolbar, { hasNoPadding: true, children: _jsxs(ToolbarContent, { children: [_jsx(ToolbarItem, { children: _jsx(SearchInput, { placeholder: "Filter by OS", value: vm.osFilter, onChange: (_event, value) => vm.setOsFilter(value), onClear: vm.clearOsFilter, "aria-label": "Filter operating systems by name" }) }), _jsx(ToolbarItem, { children: _jsx(Select, { isOpen: vm.isTierSelectOpen, selected: vm.tierFilter, onSelect: vm.handleTierSelect, onOpenChange: vm.setIsTierSelectOpen, toggle: (toggleRef) => (_jsx(MenuToggle, { ref: toggleRef, isExpanded: vm.isTierSelectOpen, onClick: vm.toggleTierSelectOpen, isFullWidth: true, children: vm.tierFilterLabel })), "aria-label": "Filter operating systems by support tier", children: _jsxs(SelectList, { children: [_jsx(SelectOption, { value: ALL_TIERS_FILTER, children: "All tiers" }), ORDERED_SUPPORT_TIERS.map((tier) => (_jsx(SelectOption, { value: tier, children: getSupportTierLegendLabel(tier) }, tier)))] }) }) })] }) })) : null, _jsxs(Stack, { hasGutter: true, children: [vm.showUpgradeNotice ? (_jsx(StackItem, { children: _jsx(OsUpgradeNotice, {}) })) : null, _jsx(StackItem, { children: _jsx("div", { className: isExportMode
27
- ? tableFullWidthStyle
28
- : `${tableScrollStyle} ${tableFullWidthStyle}`, children: _jsxs(Table, { "aria-label": "Operating systems", variant: "compact", children: [_jsx(Thead, { children: _jsxs(Tr, { children: [_jsx(Th, { children: "OS" }), _jsx(Th, { children: "Tier" }), _jsx(Th, { children: "VMs" })] }) }), _jsx(Tbody, { children: vm.showNoResults ? (_jsx(Tr, { children: _jsx(Td, { colSpan: 3, children: _jsx(EmptySearchResults, { title: "No matching operating system found" }) }) })) : (vm.filteredRows.map((row) => (_jsxs(Tr, { children: [_jsx(Td, { dataLabel: "OS", children: _jsx(OsNameCell, { osName: row.osName, upgradeRecommendation: row.upgradeRecommendation, isExportMode: isExportMode }) }), _jsx(Td, { dataLabel: "Tier", children: _jsx(SupportTierBadge, { tier: row.tier, isExportMode: isExportMode }) }), _jsx(Td, { dataLabel: "VMs", children: row.count })] }, row.osName)))) })] }) }) })] })] }));
31
+ return (_jsxs(_Fragment, { children: [_jsx("div", { ...chartExportHideProps, children: _jsx(Toolbar, { hasNoPadding: true, children: _jsxs(ToolbarContent, { children: [_jsx(ToolbarItem, { children: _jsx(SearchInput, { placeholder: "Filter by OS", value: vm.osFilter, onChange: (_event, value) => vm.setOsFilter(value), onClear: vm.clearOsFilter, "aria-label": "Filter operating systems by name" }) }), _jsx(ToolbarItem, { children: _jsx(Select, { isOpen: vm.isTierSelectOpen, selected: vm.tierFilter, onSelect: vm.handleTierSelect, onOpenChange: vm.setIsTierSelectOpen, toggle: (toggleRef) => (_jsx(MenuToggle, { ref: toggleRef, isExpanded: vm.isTierSelectOpen, onClick: vm.toggleTierSelectOpen, isFullWidth: true, children: vm.tierFilterLabel })), "aria-label": "Filter operating systems by support tier", children: _jsxs(SelectList, { children: [_jsx(SelectOption, { value: ALL_TIERS_FILTER, children: "All tiers" }), ORDERED_SUPPORT_TIERS.map((tier) => (_jsx(SelectOption, { value: tier, children: getSupportTierLegendLabel(tier) }, tier)))] }) }) })] }) }) }), _jsxs(Stack, { hasGutter: true, children: [vm.showUpgradeNotice ? (_jsx(StackItem, { children: _jsx(OsUpgradeNotice, {}) })) : null, _jsx(StackItem, { children: _jsx("div", { className: `${tableScrollStyle} ${tableFullWidthStyle}`, ...chartExportScrollProps, children: _jsxs(Table, { "aria-label": "Operating systems", variant: "compact", children: [_jsx(Thead, { children: _jsxs(Tr, { children: [_jsx(Th, { children: "OS" }), _jsx(Th, { children: "Tier" }), _jsx(Th, { children: "VMs" })] }) }), _jsx(Tbody, { children: vm.showNoResults ? (_jsx(Tr, { children: _jsx(Td, { colSpan: 3, children: _jsx(EmptySearchResults, { title: "No matching operating system found" }) }) })) : (vm.filteredRows.map((row) => (_jsxs(Tr, { children: [_jsx(Td, { dataLabel: "OS", children: _jsx(OsNameCell, { osName: row.osName, upgradeRecommendation: row.upgradeRecommendation }) }), _jsx(Td, { dataLabel: "Tier", children: _jsx(SupportTierBadge, { tier: row.tier }) }), _jsx(Td, { dataLabel: "VMs", children: row.count })] }, row.osName)))) })] }) }) })] })] }));
29
32
  };
@@ -6,7 +6,6 @@ export declare const OsUpgradeRecommendationPopover: FC<OsUpgradeRecommendationP
6
6
  interface OsNameCellProps {
7
7
  osName: string;
8
8
  upgradeRecommendation?: string;
9
- isExportMode?: boolean;
10
9
  }
11
10
  export declare const OsNameCell: FC<OsNameCellProps>;
12
11
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"OsUpgradeRecommendationPopover.d.ts","sourceRoot":"","sources":["../../src/report/OsUpgradeRecommendationPopover.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAchC,UAAU,mCAAmC;IAC3C,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED,eAAO,MAAM,8BAA8B,EAAE,EAAE,CAC7C,mCAAmC,CAiBpC,CAAC;AAIF,UAAU,eAAe;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,eAAO,MAAM,UAAU,EAAE,EAAE,CAAC,eAAe,CA0B1C,CAAC"}
1
+ {"version":3,"file":"OsUpgradeRecommendationPopover.d.ts","sourceRoot":"","sources":["../../src/report/OsUpgradeRecommendationPopover.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAehC,UAAU,mCAAmC;IAC3C,qBAAqB,EAAE,MAAM,CAAC;CAC/B;AAED,eAAO,MAAM,8BAA8B,EAAE,EAAE,CAC7C,mCAAmC,CAiBpC,CAAC;AAIF,UAAU,eAAe;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAED,eAAO,MAAM,UAAU,EAAE,EAAE,CAAC,eAAe,CAyB1C,CAAC"}
@@ -2,6 +2,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
2
2
  import { css } from "@emotion/css";
3
3
  import { Button, Flex, FlexItem, Popover } from "@patternfly/react-core";
4
4
  import { InfoCircleIcon } from "@patternfly/react-icons";
5
+ import { chartExportHideProps } from "./chartExport.js";
5
6
  const upgradeRecommendationPopoverCloseButton = css `
6
7
  .pf-v6-c-popover__close .pf-v6-c-button.pf-m-plain,
7
8
  .pf-v6-c-popover__close .pf-v6-c-button.pf-m-plain:hover {
@@ -14,11 +15,11 @@ const infoButtonStyle = css `
14
15
  `;
15
16
  export const OsUpgradeRecommendationPopover = ({ upgradeRecommendation }) => (_jsx(Popover, { className: upgradeRecommendationPopoverCloseButton, position: "bottom", headerContent: "Upgrade to get support", bodyContent: _jsx("div", { children: upgradeRecommendation }), children: _jsx(Button, { type: "button", "aria-label": "Open operating system upgrade information", variant: "plain", className: infoButtonStyle, children: _jsx(InfoCircleIcon, { color: "var(--pf-t--global--icon--color--status--info--default)" }) }) }));
16
17
  OsUpgradeRecommendationPopover.displayName = "OsUpgradeRecommendationPopover";
17
- export const OsNameCell = ({ osName, upgradeRecommendation, isExportMode = false, }) => {
18
+ export const OsNameCell = ({ osName, upgradeRecommendation, }) => {
18
19
  const hasUpgradeRecommendation = upgradeRecommendation && upgradeRecommendation.trim() !== "";
19
- if (!hasUpgradeRecommendation || isExportMode) {
20
+ if (!hasUpgradeRecommendation) {
20
21
  return _jsx(_Fragment, { children: osName });
21
22
  }
22
- return (_jsxs(Flex, { alignItems: { default: "alignItemsCenter" }, spaceItems: { default: "spaceItemsXs" }, flexWrap: { default: "nowrap" }, children: [_jsx(FlexItem, { children: osName }), _jsx(FlexItem, { shrink: { default: "shrink" }, children: _jsx(OsUpgradeRecommendationPopover, { upgradeRecommendation: upgradeRecommendation }) })] }));
23
+ return (_jsxs(Flex, { alignItems: { default: "alignItemsCenter" }, spaceItems: { default: "spaceItemsXs" }, flexWrap: { default: "nowrap" }, children: [_jsx(FlexItem, { children: osName }), _jsx(FlexItem, { shrink: { default: "shrink" }, ...chartExportHideProps, children: _jsx(OsUpgradeRecommendationPopover, { upgradeRecommendation: upgradeRecommendation }) })] }));
23
24
  };
24
25
  OsNameCell.displayName = "OsNameCell";
@@ -9,7 +9,6 @@ export interface PowerStateCardProps {
9
9
  legend: Record<string, string>;
10
10
  total: number;
11
11
  subTitle: string;
12
- isExportMode?: boolean;
13
12
  itemsPerRow?: number;
14
13
  legendVariant?: MigrationDonutChartLegendVariant;
15
14
  }
@@ -1 +1 @@
1
- {"version":3,"file":"PowerStateCard.d.ts","sourceRoot":"","sources":["../../src/report/PowerStateCard.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAC3C,OAAO,EAEL,KAAK,wBAAwB,EAC7B,KAAK,gCAAgC,EACtC,MAAM,kCAAkC,CAAC;AAI1C,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,wBAAwB,EAAE,CAAC;IACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,gCAAgC,CAAC;CAClD;AAED,eAAO,MAAM,cAAc,EAAE,EAAE,CAAC,mBAAmB,CA8ClD,CAAC"}
1
+ {"version":3,"file":"PowerStateCard.d.ts","sourceRoot":"","sources":["../../src/report/PowerStateCard.tsx"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAC3C,OAAO,EAEL,KAAK,wBAAwB,EAC7B,KAAK,gCAAgC,EACtC,MAAM,kCAAkC,CAAC;AAM1C,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,wBAAwB,EAAE,CAAC;IACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,aAAa,CAAC,EAAE,gCAAgC,CAAC;CAClD;AAED,eAAO,MAAM,cAAc,EAAE,EAAE,CAAC,mBAAmB,CAoDlD,CAAC"}
@@ -1,7 +1,9 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- import { Card, CardBody, CardTitle } from "@patternfly/react-core";
2
+ import { Card, CardBody, CardTitle, Flex, FlexItem, } from "@patternfly/react-core";
3
3
  import { MigrationDonutChart, } from "../charts/MigrationDonutChart.js";
4
4
  import { CardEmptyState } from "./CardEmptyState.js";
5
+ import { ChartHeaderActions } from "./ChartDownloadButton.js";
6
+ import { ChartExportSurface } from "./ChartExportSurface.js";
5
7
  import { dashboardStyles } from "./dashboardStyles.js";
6
- export const PowerStateCard = ({ id, title, icon, emptyTitle, slices, legend, total, subTitle, isExportMode = false, itemsPerRow = 2, legendVariant = "html", }) => (_jsxs(Card, { className: isExportMode ? dashboardStyles.cardPrint : dashboardStyles.card, id: id, children: [_jsxs(CardTitle, { children: [icon, " ", title] }), _jsx(CardBody, { children: total === 0 ? (_jsx(CardEmptyState, { title: emptyTitle })) : (_jsx(MigrationDonutChart, { legendVariant: legendVariant, data: slices, legend: legend, height: 300, width: 420, donutThickness: 18, padAngle: 1, title: `${total}`, subTitle: subTitle, subTitleColor: "var(--pf-t--global--text--color--subtle)", titleFontSize: 34, labelFontSize: 16, itemsPerRow: itemsPerRow, marginLeft: "0%", tooltipLabelFormatter: ({ datum, percent }) => `${datum.countDisplay}\n${percent.toFixed(1)}%` })) })] }));
8
+ export const PowerStateCard = ({ id, title, icon, emptyTitle, slices, legend, total, subTitle, itemsPerRow = 2, legendVariant = "html", }) => (_jsx(ChartExportSurface, { id: id, title: title, children: _jsxs(Card, { className: dashboardStyles.card, children: [_jsx(CardTitle, { children: _jsxs(Flex, { justifyContent: { default: "justifyContentSpaceBetween" }, alignItems: { default: "alignItemsCenter" }, children: [_jsxs(FlexItem, { children: [icon, " ", title] }), _jsx(ChartHeaderActions, { chartId: id, title: title })] }) }), _jsx(CardBody, { children: total === 0 ? (_jsx(CardEmptyState, { title: emptyTitle })) : (_jsx(MigrationDonutChart, { legendVariant: legendVariant, data: slices, legend: legend, height: 300, width: 420, donutThickness: 18, padAngle: 1, title: `${total}`, subTitle: subTitle, subTitleColor: "var(--pf-t--global--text--color--subtle)", titleFontSize: 34, labelFontSize: 16, itemsPerRow: itemsPerRow, marginLeft: "0%", tooltipLabelFormatter: ({ datum, percent }) => `${datum.countDisplay}\n${percent.toFixed(1)}%` })) })] }) }));
7
9
  PowerStateCard.displayName = "PowerStateCard";
@@ -0,0 +1,19 @@
1
+ import { type FC } from "react";
2
+ import { type ReportExportOption, type StandardReportExportHandlers } from "./reportExportOptions.js";
3
+ export interface ReportExportMenuProps {
4
+ options: ReportExportOption[];
5
+ isLoading?: boolean;
6
+ loadingLabel?: string | null;
7
+ isDisabled?: boolean;
8
+ toggleLabel?: string;
9
+ }
10
+ export declare const ReportExportMenu: FC<ReportExportMenuProps>;
11
+ export interface ExportReportButtonProps extends StandardReportExportHandlers {
12
+ extraOptions?: ReportExportOption[];
13
+ isLoading?: boolean;
14
+ loadingLabel?: string | null;
15
+ isDisabled?: boolean;
16
+ toggleLabel?: string;
17
+ }
18
+ export declare const ExportReportButton: FC<ExportReportButtonProps>;
19
+ //# sourceMappingURL=ReportExportMenu.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { Dropdown, DropdownItem, DropdownList, MenuToggle, Spinner, } from "@patternfly/react-core";
3
+ import { DownloadIcon } from "@patternfly/react-icons";
4
+ import { useState } from "react";
5
+ import { standardReportExportOptions, } from "./reportExportOptions.js";
6
+ export const ReportExportMenu = ({ options, isLoading = false, loadingLabel = null, isDisabled = false, toggleLabel = "Export", }) => {
7
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
8
+ if (options.length === 0) {
9
+ return null;
10
+ }
11
+ return (_jsx(Dropdown, { isOpen: isDropdownOpen, popperProps: {
12
+ placement: "bottom-end",
13
+ preventOverflow: true,
14
+ }, onSelect: () => {
15
+ setIsDropdownOpen(false);
16
+ }, onOpenChange: (isOpen) => {
17
+ setIsDropdownOpen(isOpen);
18
+ }, toggle: (toggleRef) => (_jsx(MenuToggle, { ref: toggleRef, onClick: () => {
19
+ 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))) }) }));
21
+ };
22
+ ReportExportMenu.displayName = "ReportExportMenu";
23
+ export const ExportReportButton = ({ onExportPdf, onExportPng, onExportHtml, extraOptions = [], ...menuProps }) => (_jsx(ReportExportMenu, { options: [
24
+ ...standardReportExportOptions({
25
+ onExportPdf,
26
+ onExportPng,
27
+ onExportHtml,
28
+ }),
29
+ ...extraOptions,
30
+ ], ...menuProps }));
31
+ ExportReportButton.displayName = "ExportReportButton";
@@ -0,0 +1,14 @@
1
+ import type React from "react";
2
+ export interface ReportTableProps<DataItem> {
3
+ columns: string[];
4
+ data: DataItem[];
5
+ fields: Array<keyof DataItem>;
6
+ caption?: string;
7
+ onRowClick?: (item: DataItem) => void;
8
+ clickableFields?: Array<keyof DataItem>;
9
+ }
10
+ export declare function ReportTable<DataItem>(props: ReportTableProps<DataItem>): React.ReactNode;
11
+ export declare namespace ReportTable {
12
+ var displayName: string;
13
+ }
14
+ //# sourceMappingURL=ReportTable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReportTable.d.ts","sourceRoot":"","sources":["../../src/report/ReportTable.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,MAAM,WAAW,gBAAgB,CAAC,QAAQ;IACxC,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,MAAM,EAAE,KAAK,CAAC,MAAM,QAAQ,CAAC,CAAC;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC;IACtC,eAAe,CAAC,EAAE,KAAK,CAAC,MAAM,QAAQ,CAAC,CAAC;CACzC;AAED,wBAAgB,WAAW,CAAC,QAAQ,EAClC,KAAK,EAAE,gBAAgB,CAAC,QAAQ,CAAC,GAChC,KAAK,CAAC,SAAS,CA8DjB;yBAhEe,WAAW"}
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Button } from "@patternfly/react-core";
3
+ import { Table, Tbody, Td, Th, Thead, Tr } from "@patternfly/react-table";
4
+ import { tableFullWidthStyle } from "./dashboardStyles.js";
5
+ export function ReportTable(props) {
6
+ const { columns, data, fields, caption, onRowClick, clickableFields = [], } = props;
7
+ const renderCell = (item, field) => {
8
+ const value = item[field];
9
+ if (value === "" || value == null) {
10
+ return "-";
11
+ }
12
+ if (typeof value === "boolean") {
13
+ return value ? "True" : "False";
14
+ }
15
+ // Make field clickable if specified
16
+ if (clickableFields.includes(field) && onRowClick) {
17
+ return (_jsx(Button, { variant: "link", isInline: true, onClick: () => onRowClick(item), style: { padding: 0, fontSize: "inherit" }, children: value }));
18
+ }
19
+ return value;
20
+ };
21
+ return (_jsxs(Table, { variant: "compact", borders: false, className: tableFullWidthStyle, children: [caption && _jsx("caption", { children: caption }), _jsx(Thead, { children: _jsx(Tr, { children: columns.map((name) => (_jsx(Th, { children: name }, name))) }) }), _jsx(Tbody, { children: data.map((item) => (_jsx(Tr, { children: fields.map((f) => (_jsx(Td, { children: renderCell(item, f) }, String(f)))) }, `row-${String(item[fields[0]])}`))) })] }));
22
+ }
23
+ ReportTable.displayName = "ReportTable";
@@ -2,7 +2,6 @@ import type { FC } from "react";
2
2
  import { type SupportTier } from "./osSupportTier.js";
3
3
  interface SupportTierBadgeProps {
4
4
  tier: SupportTier;
5
- isExportMode?: boolean;
6
5
  }
7
6
  export declare const SupportTierBadge: FC<SupportTierBadgeProps>;
8
7
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"SupportTierBadge.d.ts","sourceRoot":"","sources":["../../src/report/SupportTierBadge.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAKL,KAAK,WAAW,EACjB,MAAM,oBAAoB,CAAC;AAa5B,UAAU,qBAAqB;IAC7B,IAAI,EAAE,WAAW,CAAC;IAClB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAYD,eAAO,MAAM,gBAAgB,EAAE,EAAE,CAAC,qBAAqB,CAiBtD,CAAC"}
1
+ {"version":3,"file":"SupportTierBadge.d.ts","sourceRoot":"","sources":["../../src/report/SupportTierBadge.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAKL,KAAK,WAAW,EACjB,MAAM,oBAAoB,CAAC;AAE5B,UAAU,qBAAqB;IAC7B,IAAI,EAAE,WAAW,CAAC;CACnB;AAED,eAAO,MAAM,gBAAgB,EAAE,EAAE,CAAC,qBAAqB,CAYtD,CAAC"}
@@ -1,25 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { css } from "@emotion/css";
3
2
  import { Label, Tooltip } from "@patternfly/react-core";
4
3
  import { getSupportTierBadgeColor, getSupportTierBadgeInlineStyle, getSupportTierDefinition, getSupportTierLegendLabel, } from "./osSupportTier.js";
5
- const exportBadgeStyle = (backgroundColor, color) => css `
6
- display: inline-block;
7
- padding: var(--pf-t--global--spacer--xs) var(--pf-t--global--spacer--sm);
8
- border-radius: var(--pf-t--global--border--radius--pill);
9
- font-size: var(--pf-t--global--font--size--body--sm);
10
- line-height: var(--pf-t--global--font--line-height--body);
11
- white-space: nowrap;
12
- background-color: ${backgroundColor};
13
- color: ${color};
14
- `;
15
- const ExportSupportTierBadge = ({ tier }) => {
16
- const { backgroundColor, color } = getSupportTierBadgeInlineStyle(tier);
17
- return (_jsx("span", { className: exportBadgeStyle(backgroundColor, color), children: getSupportTierLegendLabel(tier) }));
18
- };
19
- export const SupportTierBadge = ({ tier, isExportMode = false, }) => {
20
- if (isExportMode) {
21
- return _jsx(ExportSupportTierBadge, { tier: tier });
22
- }
23
- return (_jsx(Tooltip, { content: getSupportTierDefinition(tier), children: _jsx("span", { children: _jsx(Label, { color: getSupportTierBadgeColor(tier), isCompact: true, children: getSupportTierLegendLabel(tier) }) }) }));
24
- };
4
+ export const SupportTierBadge = ({ tier }) => (_jsx(Tooltip, { content: getSupportTierDefinition(tier), children: _jsx("span", { children: _jsx(Label, { color: getSupportTierBadgeColor(tier), isCompact: true, style: getSupportTierBadgeInlineStyle(tier), children: getSupportTierLegendLabel(tier) }) }) }));
25
5
  SupportTierBadge.displayName = "SupportTierBadge";
@@ -4,7 +4,6 @@ export interface VCenterClusterDetailsProps {
4
4
  isAggregateView: boolean;
5
5
  rows: ClusterDetailRow[];
6
6
  details?: ClusterDetailsModel;
7
- isExportMode?: boolean;
8
7
  }
9
8
  export declare const VCenterClusterDetails: FC<VCenterClusterDetailsProps>;
10
9
  //# sourceMappingURL=VCenterClusterDetails.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"VCenterClusterDetails.d.ts","sourceRoot":"","sources":["../../src/report/VCenterClusterDetails.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAEhC,OAAO,KAAK,EACV,gBAAgB,EAChB,mBAAmB,EAEpB,MAAM,iCAAiC,CAAC;AAEzC,MAAM,WAAW,0BAA0B;IACzC,eAAe,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,gBAAgB,EAAE,CAAC;IACzB,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB;AAmKD,eAAO,MAAM,qBAAqB,EAAE,EAAE,CAAC,0BAA0B,CAgBhE,CAAC"}
1
+ {"version":3,"file":"VCenterClusterDetails.d.ts","sourceRoot":"","sources":["../../src/report/VCenterClusterDetails.tsx"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAKhC,OAAO,KAAK,EACV,gBAAgB,EAChB,mBAAmB,EAEpB,MAAM,iCAAiC,CAAC;AAEzC,MAAM,WAAW,0BAA0B;IACzC,eAAe,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,gBAAgB,EAAE,CAAC;IACzB,OAAO,CAAC,EAAE,mBAAmB,CAAC;CAC/B;AAsKD,eAAO,MAAM,qBAAqB,EAAE,EAAE,CAAC,0BAA0B,CA+BhE,CAAC"}
@@ -1,7 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { css } from "@emotion/css";
3
- import { Card, CardBody, CardTitle, Content, DescriptionList, DescriptionListDescription, DescriptionListGroup, DescriptionListTerm, Grid, GridItem, Label, LabelGroup, } from "@patternfly/react-core";
3
+ import { Card, CardBody, CardTitle, Content, DescriptionList, DescriptionListDescription, DescriptionListGroup, DescriptionListTerm, Flex, FlexItem, Grid, GridItem, Label, LabelGroup, } from "@patternfly/react-core";
4
4
  import { Table, Tbody, Td, Th, Thead, Tr } from "@patternfly/react-table";
5
+ import { ChartHeaderActions } from "./ChartDownloadButton.js";
6
+ import { ChartExportSurface } from "./ChartExportSurface.js";
7
+ import { chartExportScrollProps } from "./chartExport.js";
5
8
  import { FeatureStatusBadge } from "./FeatureStatusBadge.js";
6
9
  const clusterDetailsTableScrollStyle = css `
7
10
  max-height: 320px;
@@ -9,11 +12,11 @@ const clusterDetailsTableScrollStyle = css `
9
12
  `;
10
13
  const FeatureList = ({ items }) => (_jsx(DescriptionList, { isHorizontal: true, isCompact: true, children: items.map((item) => (_jsxs(DescriptionListGroup, { children: [_jsx(DescriptionListTerm, { children: item.label }), _jsx(DescriptionListDescription, { children: _jsx(FeatureStatusBadge, { status: item.status }) })] }, item.label))) }));
11
14
  const AggregateClusterTable = ({ rows }) => (_jsxs(Table, { variant: "compact", borders: false, isStickyHeader: true, "data-testid": "vcenter-cluster-details-table", children: [_jsx(Thead, { children: _jsxs(Tr, { children: [_jsx(Th, { children: "Cluster name" }), _jsx(Th, { children: "Hosts" }), _jsx(Th, { children: "VMs" }), _jsx(Th, { children: "vMotion" }), _jsx(Th, { children: "DRS" }), _jsx(Th, { children: "vSAN" })] }) }), _jsx(Tbody, { children: rows.map((row) => (_jsxs(Tr, { children: [_jsx(Td, { dataLabel: "Cluster name", children: row.name }), _jsx(Td, { dataLabel: "Hosts", children: row.hosts }), _jsx(Td, { dataLabel: "VMs", children: row.vms }), _jsx(Td, { dataLabel: "vMotion", children: _jsx(FeatureStatusBadge, { status: row.vmotion }) }), _jsx(Td, { dataLabel: "DRS", children: _jsx(FeatureStatusBadge, { status: row.drs }) }), _jsx(Td, { dataLabel: "vSAN", children: _jsx(FeatureStatusBadge, { status: row.vsan }) })] }, row.id))) })] }));
12
- const AggregateCluster = ({ rows, isExportMode }) => {
15
+ const AggregateCluster = ({ rows }) => {
13
16
  if (rows.length === 0) {
14
17
  return _jsx(Content, { component: "p", children: "No vSphere clusters detected" });
15
18
  }
16
- return (_jsx("div", { className: isExportMode ? undefined : clusterDetailsTableScrollStyle, "data-scroll-constrained": isExportMode ? undefined : "true", children: _jsx(AggregateClusterTable, { rows: rows }) }));
19
+ return (_jsx("div", { className: clusterDetailsTableScrollStyle, "data-scroll-constrained": "true", ...chartExportScrollProps, children: _jsx(AggregateClusterTable, { rows: rows }) }));
17
20
  };
18
21
  const DetailedClusterView = ({ details, }) => (_jsxs(Grid, { hasGutter: true, children: [_jsx(GridItem, { children: _jsxs(DescriptionList, { isAutoFit: true, displaySize: "lg", children: [_jsxs(DescriptionListGroup, { children: [_jsx(DescriptionListTerm, { children: "Hosts" }), _jsx(DescriptionListDescription, { children: details.hosts })] }), _jsxs(DescriptionListGroup, { children: [_jsx(DescriptionListTerm, { children: "VMs" }), _jsx(DescriptionListDescription, { children: details.vms })] }), _jsxs(DescriptionListGroup, { children: [_jsx(DescriptionListTerm, { children: "Networks detected" }), _jsx(DescriptionListDescription, { children: details.networksDetected })] })] }) }), _jsx(GridItem, { md: 4, children: _jsxs(Card, { isFullHeight: true, children: [_jsx(CardTitle, { children: "Core infrastructure" }), _jsx(CardBody, { children: _jsx(FeatureList, { items: [
19
22
  { label: "vMotion", status: details.vmotion },
@@ -22,5 +25,7 @@ const DetailedClusterView = ({ details, }) => (_jsxs(Grid, { hasGutter: true, ch
22
25
  ] }) })] }) }), _jsx(GridItem, { md: 4, children: _jsxs(Card, { isFullHeight: true, children: [_jsx(CardTitle, { children: "vSAN capabilities" }), _jsx(CardBody, { children: _jsx(FeatureList, { items: [{ label: "vSAN", status: details.vsan }] }) })] }) }), _jsx(GridItem, { md: 4, children: _jsxs(Card, { isFullHeight: true, children: [_jsx(CardTitle, { children: "Network topology" }), _jsx(CardBody, { children: details.networks.length === 0 ? (_jsx(Content, { component: "p", children: "No networks detected" })) : (_jsxs(_Fragment, { children: [_jsxs(Content, { component: "p", children: [details.networksDetected, " ", details.networksDetected === 1
23
26
  ? "network detected"
24
27
  : "networks detected"] }), _jsx(LabelGroup, { isCompact: true, numLabels: details.networks.length, "aria-label": "Detected networks", children: details.networks.map((network, index) => (_jsx(Label, { color: "blue", isCompact: true, children: network.displayName }, `${network.name}-${network.vlanId ?? index}`))) })] })) })] }) })] }));
25
- export const VCenterClusterDetails = ({ isAggregateView, rows, details, isExportMode = false, }) => (_jsxs(Card, { isFullHeight: true, id: "vcenter-cluster-details", children: [_jsx(CardTitle, { children: "vCenter cluster details" }), _jsx(CardBody, { children: isAggregateView || !details ? (_jsx(AggregateCluster, { rows: rows, isExportMode: isExportMode })) : (_jsx(DetailedClusterView, { details: details })) })] }));
28
+ const CLUSTER_DETAILS_CHART_ID = "vcenter-cluster-details";
29
+ const CLUSTER_DETAILS_TITLE = "vCenter cluster details";
30
+ export const VCenterClusterDetails = ({ isAggregateView, rows, details, }) => (_jsx(ChartExportSurface, { id: CLUSTER_DETAILS_CHART_ID, title: CLUSTER_DETAILS_TITLE, children: _jsxs(Card, { isFullHeight: true, children: [_jsx(CardTitle, { children: _jsxs(Flex, { justifyContent: { default: "justifyContentSpaceBetween" }, alignItems: { default: "alignItemsCenter" }, children: [_jsx(FlexItem, { children: CLUSTER_DETAILS_TITLE }), _jsx(ChartHeaderActions, { chartId: CLUSTER_DETAILS_CHART_ID, title: CLUSTER_DETAILS_TITLE })] }) }), _jsx(CardBody, { children: isAggregateView || !details ? (_jsx(AggregateCluster, { rows: rows })) : (_jsx(DetailedClusterView, { details: details })) })] }) }));
26
31
  VCenterClusterDetails.displayName = "VCenterClusterDetails";
@@ -2,7 +2,6 @@ import type { FC } from "react";
2
2
  import type { MigrationDonutChartLegendVariant } from "../charts/MigrationDonutChart.js";
3
3
  export interface VmPowerStatesProps {
4
4
  powerStates?: Record<string, number>;
5
- isExportMode?: boolean;
6
5
  legendVariant?: MigrationDonutChartLegendVariant;
7
6
  }
8
7
  export declare const VmPowerStates: FC<VmPowerStatesProps>;
@@ -1 +1 @@
1
- {"version":3,"file":"VmPowerStates.d.ts","sourceRoot":"","sources":["../../src/report/VmPowerStates.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAEhC,OAAO,KAAK,EAAE,gCAAgC,EAAE,MAAM,kCAAkC,CAAC;AAKzF,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,aAAa,CAAC,EAAE,gCAAgC,CAAC;CAClD;AAED,eAAO,MAAM,aAAa,EAAE,EAAE,CAAC,kBAAkB,CAwBhD,CAAC"}
1
+ {"version":3,"file":"VmPowerStates.d.ts","sourceRoot":"","sources":["../../src/report/VmPowerStates.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAEhC,OAAO,KAAK,EAAE,gCAAgC,EAAE,MAAM,kCAAkC,CAAC;AAKzF,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,aAAa,CAAC,EAAE,gCAAgC,CAAC;CAClD;AAED,eAAO,MAAM,aAAa,EAAE,EAAE,CAAC,kBAAkB,CAsBhD,CAAC"}
@@ -4,8 +4,8 @@ import { useMemo } from "react";
4
4
  import { REPORT_CARD_EMPTY_STATE_TITLES } from "./constants.js";
5
5
  import { PowerStateCard } from "./PowerStateCard.js";
6
6
  import { buildVmPowerStateChart } from "./powerStates.js";
7
- export const VmPowerStates = ({ powerStates, isExportMode = false, legendVariant, }) => {
7
+ export const VmPowerStates = ({ powerStates, legendVariant, }) => {
8
8
  const chart = useMemo(() => buildVmPowerStateChart(powerStates), [powerStates]);
9
- return (_jsx(PowerStateCard, { id: "vm-power-states", title: "VM power states", icon: _jsx(VirtualMachineIcon, {}), emptyTitle: REPORT_CARD_EMPTY_STATE_TITLES.vmPowerStates, slices: chart.slices, legend: chart.legend, total: chart.total, subTitle: "VMs", isExportMode: isExportMode, legendVariant: legendVariant }));
9
+ return (_jsx(PowerStateCard, { id: "vm-power-states", title: "VM power states", icon: _jsx(VirtualMachineIcon, {}), emptyTitle: REPORT_CARD_EMPTY_STATE_TITLES.vmPowerStates, slices: chart.slices, legend: chart.legend, total: chart.total, subTitle: "VMs", legendVariant: legendVariant }));
10
10
  };
11
11
  VmPowerStates.displayName = "VmPowerStates";
@@ -0,0 +1,9 @@
1
+ import type React from "react";
2
+ import type { MigrationIssueLike } from "./ErrorTable.js";
3
+ export interface WarningsTableProps {
4
+ warnings: MigrationIssueLike[];
5
+ /** Optional drill-down callback invoked with an issue's `label`. */
6
+ onConcernClick?: (concernLabel: string) => void;
7
+ }
8
+ export declare const WarningsTable: React.FC<WarningsTableProps>;
9
+ //# sourceMappingURL=WarningsTable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WarningsTable.d.ts","sourceRoot":"","sources":["../../src/report/WarningsTable.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAM/B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAG1D,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,oEAAoE;IACpE,cAAc,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;CACjD;AAED,eAAO,MAAM,aAAa,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAgDtD,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Card, CardBody, CardTitle, Flex, FlexItem, Icon, } from "@patternfly/react-core";
3
+ import { ExclamationTriangleIcon } from "@patternfly/react-icons";
4
+ import { CardEmptyState } from "./CardEmptyState.js";
5
+ import { ChartHeaderActions } from "./ChartDownloadButton.js";
6
+ import { ChartExportSurface } from "./ChartExportSurface.js";
7
+ import { REPORT_CARD_EMPTY_STATE_TITLES } from "./constants.js";
8
+ import { dashboardStyles } from "./dashboardStyles.js";
9
+ import { ReportTable } from "./ReportTable.js";
10
+ export const WarningsTable = ({ warnings, onConcernClick, }) => {
11
+ const handleRowClick = (issue) => {
12
+ if (issue.label && onConcernClick) {
13
+ onConcernClick(issue.label);
14
+ }
15
+ };
16
+ const chartId = "warnings-table";
17
+ const chartTitle = "Warnings";
18
+ return (_jsx(ChartExportSurface, { id: chartId, title: chartTitle, children: _jsxs(Card, { className: dashboardStyles.card, children: [_jsx(CardTitle, { children: _jsxs(Flex, { justifyContent: { default: "justifyContentSpaceBetween" }, alignItems: { default: "alignItemsCenter" }, children: [_jsxs(FlexItem, { children: [_jsx(Icon, { status: "warning", children: _jsx(ExclamationTriangleIcon, {}) }), " ", "Warnings"] }), _jsx(ChartHeaderActions, { chartId: chartId, title: chartTitle })] }) }), _jsx(CardBody, { className: dashboardStyles.cardBodyScrollable, children: warnings.length === 0 ? (_jsx(CardEmptyState, { title: REPORT_CARD_EMPTY_STATE_TITLES.warnings })) : (_jsx("div", { children: _jsx(ReportTable, { data: warnings, columns: ["Description", "Total VMs"], fields: ["assessment", "count"], onRowClick: onConcernClick ? handleRowClick : undefined, clickableFields: onConcernClick ? ["assessment"] : [] }) })) })] }) }));
19
+ };
20
+ WarningsTable.displayName = "WarningsTable";
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Tab panels stay mounted with `hidden` / `display:none` when another tab is
3
+ * active. html2canvas then measures a 0×0 box. Reveal those ancestors off-screen
4
+ * for the duration of the capture, then restore.
5
+ */
6
+ export declare function revealHiddenChartAncestors(element: HTMLElement): () => void;
7
+ export declare function captureChartElement(element: HTMLElement): Promise<HTMLCanvasElement>;
8
+ //# sourceMappingURL=captureChartElement.d.ts.map
@@ -0,0 +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"}