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

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 (129) 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 +23 -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/CpuAndMemoryOverview.d.ts +19 -0
  12. package/dist/report/CpuAndMemoryOverview.d.ts.map +1 -0
  13. package/dist/report/CpuAndMemoryOverview.js +112 -0
  14. package/dist/report/ErrorTable.d.ts +18 -0
  15. package/dist/report/ErrorTable.d.ts.map +1 -0
  16. package/dist/report/ErrorTable.js +20 -0
  17. package/dist/report/HostPowerStates.d.ts +0 -1
  18. package/dist/report/HostPowerStates.d.ts.map +1 -1
  19. package/dist/report/HostPowerStates.js +2 -2
  20. package/dist/report/HostsOverview.d.ts +13 -0
  21. package/dist/report/HostsOverview.d.ts.map +1 -0
  22. package/dist/report/HostsOverview.js +84 -0
  23. package/dist/report/InfrastructureSummary.d.ts.map +1 -1
  24. package/dist/report/InfrastructureSummary.js +6 -2
  25. package/dist/report/NetworkOverview.d.ts +31 -0
  26. package/dist/report/NetworkOverview.d.ts.map +1 -0
  27. package/dist/report/NetworkOverview.js +201 -0
  28. package/dist/report/OSDistribution.d.ts +0 -2
  29. package/dist/report/OSDistribution.d.ts.map +1 -1
  30. package/dist/report/OSDistribution.js +8 -5
  31. package/dist/report/OsUpgradeRecommendationPopover.d.ts +0 -1
  32. package/dist/report/OsUpgradeRecommendationPopover.d.ts.map +1 -1
  33. package/dist/report/OsUpgradeRecommendationPopover.js +4 -3
  34. package/dist/report/PowerStateCard.d.ts +0 -1
  35. package/dist/report/PowerStateCard.d.ts.map +1 -1
  36. package/dist/report/PowerStateCard.js +4 -2
  37. package/dist/report/ReportExportMenu.d.ts +19 -0
  38. package/dist/report/ReportExportMenu.d.ts.map +1 -0
  39. package/dist/report/ReportExportMenu.js +31 -0
  40. package/dist/report/ReportTable.d.ts +14 -0
  41. package/dist/report/ReportTable.d.ts.map +1 -0
  42. package/dist/report/ReportTable.js +23 -0
  43. package/dist/report/SupportTierBadge.d.ts +0 -1
  44. package/dist/report/SupportTierBadge.d.ts.map +1 -1
  45. package/dist/report/SupportTierBadge.js +1 -21
  46. package/dist/report/VCenterClusterDetails.d.ts +0 -1
  47. package/dist/report/VCenterClusterDetails.d.ts.map +1 -1
  48. package/dist/report/VCenterClusterDetails.js +9 -4
  49. package/dist/report/VmPowerStates.d.ts +0 -1
  50. package/dist/report/VmPowerStates.d.ts.map +1 -1
  51. package/dist/report/VmPowerStates.js +2 -2
  52. package/dist/report/WarningsTable.d.ts +9 -0
  53. package/dist/report/WarningsTable.d.ts.map +1 -0
  54. package/dist/report/WarningsTable.js +20 -0
  55. package/dist/report/captureChartElement.d.ts +8 -0
  56. package/dist/report/captureChartElement.d.ts.map +1 -0
  57. package/dist/report/captureChartElement.js +304 -0
  58. package/dist/report/chartExport.d.ts +80 -0
  59. package/dist/report/chartExport.d.ts.map +1 -0
  60. package/dist/report/chartExport.js +182 -0
  61. package/dist/report/chartExportContext.d.ts +32 -0
  62. package/dist/report/chartExportContext.d.ts.map +1 -0
  63. package/dist/report/chartExportContext.js +36 -0
  64. package/dist/report/chartExportFilenames.d.ts +4 -0
  65. package/dist/report/chartExportFilenames.d.ts.map +1 -0
  66. package/dist/report/chartExportFilenames.js +16 -0
  67. package/dist/report/constants.d.ts +2 -0
  68. package/dist/report/constants.d.ts.map +1 -1
  69. package/dist/report/constants.js +2 -0
  70. package/dist/report/dashboardStyles.d.ts +0 -4
  71. package/dist/report/dashboardStyles.d.ts.map +1 -1
  72. package/dist/report/dashboardStyles.js +0 -19
  73. package/dist/report/htmlExport.d.ts +9 -0
  74. package/dist/report/htmlExport.d.ts.map +1 -0
  75. package/dist/report/htmlExport.js +65 -0
  76. package/dist/report/index.d.ts +19 -0
  77. package/dist/report/index.d.ts.map +1 -1
  78. package/dist/report/index.js +19 -0
  79. package/dist/report/pdfExport.d.ts +3 -0
  80. package/dist/report/pdfExport.d.ts.map +1 -0
  81. package/dist/report/pdfExport.js +131 -0
  82. package/dist/report/pdfPage.d.ts +15 -0
  83. package/dist/report/pdfPage.d.ts.map +1 -0
  84. package/dist/report/pdfPage.js +44 -0
  85. package/dist/report/reportExportOptions.d.ts +17 -0
  86. package/dist/report/reportExportOptions.d.ts.map +1 -0
  87. package/dist/report/reportExportOptions.js +31 -0
  88. package/dist/report/zipChartPngs.d.ts +3 -0
  89. package/dist/report/zipChartPngs.d.ts.map +1 -0
  90. package/dist/report/zipChartPngs.js +8 -0
  91. package/package.json +7 -2
  92. package/src/report/ChartDownloadButton.tsx +59 -0
  93. package/src/report/ChartExportProvider.tsx +392 -0
  94. package/src/report/ChartExportSurface.tsx +60 -0
  95. package/src/report/CpuAndMemoryOverview.tsx +277 -0
  96. package/src/report/ErrorTable.tsx +85 -0
  97. package/src/report/HostPowerStates.tsx +0 -3
  98. package/src/report/HostsOverview.tsx +185 -0
  99. package/src/report/InfrastructureSummary.tsx +48 -31
  100. package/src/report/NetworkOverview.tsx +405 -0
  101. package/src/report/OSDistribution.tsx +46 -42
  102. package/src/report/OsUpgradeRecommendationPopover.tsx +3 -4
  103. package/src/report/PowerStateCard.tsx +49 -36
  104. package/src/report/ReportExportMenu.tsx +119 -0
  105. package/src/report/ReportTable.tsx +81 -0
  106. package/src/report/SupportTierBadge.tsx +12 -40
  107. package/src/report/VCenterClusterDetails.tsx +38 -16
  108. package/src/report/VmPowerStates.tsx +0 -3
  109. package/src/report/WarningsTable.tsx +75 -0
  110. package/src/report/__tests__/ChartExportProvider.test.tsx +582 -0
  111. package/src/report/__tests__/CpuAndMemoryOverview.test.tsx +200 -0
  112. package/src/report/__tests__/HostsOverview.test.tsx +145 -0
  113. package/src/report/__tests__/InfrastructureSummary.test.tsx +0 -22
  114. package/src/report/__tests__/NetworkOverview.test.tsx +269 -0
  115. package/src/report/__tests__/OSDistribution.test.tsx +10 -11
  116. package/src/report/__tests__/captureChartElement.test.ts +205 -0
  117. package/src/report/__tests__/chartExportPackagers.test.ts +299 -0
  118. package/src/report/captureChartElement.ts +398 -0
  119. package/src/report/chartExport.ts +277 -0
  120. package/src/report/chartExportContext.tsx +92 -0
  121. package/src/report/chartExportFilenames.ts +21 -0
  122. package/src/report/constants.ts +2 -0
  123. package/src/report/dashboardStyles.ts +0 -23
  124. package/src/report/htmlExport.ts +83 -0
  125. package/src/report/index.ts +102 -0
  126. package/src/report/pdfExport.ts +233 -0
  127. package/src/report/pdfPage.ts +83 -0
  128. package/src/report/reportExportOptions.ts +53 -0
  129. package/src/report/zipChartPngs.ts +10 -0
@@ -0,0 +1,84 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { css } from "@emotion/css";
3
+ import { Card, CardBody, CardTitle, Flex, FlexItem, } from "@patternfly/react-core";
4
+ import { ServerIcon } from "@patternfly/react-icons";
5
+ import { chart_color_blue_300, chart_color_green_300, chart_color_purple_100, chart_color_purple_300, chart_color_teal_200, chart_color_yellow_400, } from "@patternfly/react-tokens";
6
+ import { useMemo } 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 { REPORT_CARD_EMPTY_STATE_TITLES } from "./constants.js";
12
+ import { dashboardStyles } from "./dashboardStyles.js";
13
+ const TOP_HOST_MODELS = 5;
14
+ const OTHER_MODELS_LABEL = "Other models";
15
+ const UNKNOWN_MODEL_LABEL = "Unknown model";
16
+ const CHART_ID = "hosts-overview";
17
+ const CHART_TITLE = "Host distribution by model";
18
+ /**
19
+ * Same categorical order as the ui-app host-model donut, mapped onto
20
+ * PatternFly `chart_color_*` tokens (nearest token where the old hex
21
+ * was not itself a token).
22
+ */
23
+ const HOST_MODEL_COLORS = [
24
+ chart_color_blue_300.value,
25
+ chart_color_purple_300.value,
26
+ chart_color_purple_100.value,
27
+ chart_color_teal_200.value,
28
+ chart_color_yellow_400.value,
29
+ chart_color_green_300.value,
30
+ ];
31
+ const cardSubtitle = css `
32
+ color: var(--pf-t--global--text--color--subtle);
33
+ font-size: 0.85rem;
34
+ `;
35
+ const hostModelLabel = (model) => {
36
+ const raw = typeof model === "string"
37
+ ? model
38
+ : typeof model === "number"
39
+ ? String(model)
40
+ : "";
41
+ const trimmed = raw.trim();
42
+ return trimmed !== "" ? trimmed : UNKNOWN_MODEL_LABEL;
43
+ };
44
+ const buildHostModelDonut = (hosts) => {
45
+ const counts = new Map();
46
+ for (const host of hosts) {
47
+ const label = hostModelLabel(host?.model);
48
+ counts.set(label, (counts.get(label) ?? 0) + 1);
49
+ }
50
+ const entries = [...counts.entries()]
51
+ .map(([model, count]) => ({ model, count }))
52
+ .sort((a, b) => b.count - a.count);
53
+ const top = entries.slice(0, TOP_HOST_MODELS);
54
+ const restSum = entries
55
+ .slice(TOP_HOST_MODELS)
56
+ .reduce((sum, entry) => sum + entry.count, 0);
57
+ const slices = top.map((entry) => ({
58
+ name: entry.model,
59
+ count: entry.count,
60
+ countDisplay: `${entry.count} hosts`,
61
+ legendCategory: entry.model,
62
+ }));
63
+ if (restSum > 0) {
64
+ slices.push({
65
+ name: OTHER_MODELS_LABEL,
66
+ count: restSum,
67
+ countDisplay: `${restSum} hosts`,
68
+ legendCategory: OTHER_MODELS_LABEL,
69
+ });
70
+ }
71
+ const legend = {};
72
+ slices.forEach((slice, index) => {
73
+ const color = HOST_MODEL_COLORS[index % HOST_MODEL_COLORS.length];
74
+ if (color) {
75
+ legend[slice.legendCategory] = color;
76
+ }
77
+ });
78
+ return { slices, legend, totalHosts: hosts.length };
79
+ };
80
+ export const HostsOverview = ({ hosts, legendVariant = "html", }) => {
81
+ const { slices, legend, totalHosts } = useMemo(() => buildHostModelDonut(Array.isArray(hosts) ? hosts : []), [hosts]);
82
+ return (_jsx(ChartExportSurface, { id: CHART_ID, title: CHART_TITLE, children: _jsxs(Card, { className: dashboardStyles.card, children: [_jsx(CardTitle, { children: _jsxs(Flex, { justifyContent: { default: "justifyContentSpaceBetween" }, alignItems: { default: "alignItemsCenter" }, children: [_jsx(FlexItem, { children: _jsxs("div", { children: [_jsxs("div", { children: [_jsx(ServerIcon, {}), " ", CHART_TITLE] }), _jsx("div", { className: cardSubtitle, children: "Top 5 models" })] }) }), _jsx(ChartHeaderActions, { chartId: CHART_ID, title: CHART_TITLE })] }) }), _jsx(CardBody, { className: dashboardStyles.cardBodyScrollable, children: slices.length === 0 ? (_jsx(CardEmptyState, { title: REPORT_CARD_EMPTY_STATE_TITLES.hosts })) : (_jsx(MigrationDonutChart, { legendVariant: legendVariant, data: slices, height: 300, width: 420, donutThickness: 18, titleFontSize: 34, legend: legend, legendWidth: 680, title: `${totalHosts}`, subTitle: "Hosts", subTitleColor: "var(--pf-t--global--text--color--subtle)", itemsPerRow: 2, labelFontSize: 16, marginLeft: "0%", tooltipLabelFormatter: ({ datum, percent }) => `${datum.countDisplay}\n${percent.toFixed(1)}%` })) })] }) }));
83
+ };
84
+ HostsOverview.displayName = "HostsOverview";
@@ -1 +1 @@
1
- {"version":3,"file":"InfrastructureSummary.d.ts","sourceRoot":"","sources":["../../src/report/InfrastructureSummary.tsx"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAElF,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,0BAA0B,CAAC;CACrC;AAKD,eAAO,MAAM,qBAAqB,EAAE,EAAE,CAAC,0BAA0B,CAkChE,CAAC"}
1
+ {"version":3,"file":"InfrastructureSummary.d.ts","sourceRoot":"","sources":["../../src/report/InfrastructureSummary.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAGhC,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,iCAAiC,CAAC;AAElF,MAAM,WAAW,0BAA0B;IACzC,OAAO,EAAE,0BAA0B,CAAC;CACrC;AAQD,eAAO,MAAM,qBAAqB,EAAE,EAAE,CAAC,0BAA0B,CA4ChE,CAAC"}
@@ -1,5 +1,9 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { Card, CardBody, CardTitle, DescriptionList, DescriptionListDescription, DescriptionListGroup, DescriptionListTerm, } from "@patternfly/react-core";
2
+ import { Card, CardBody, CardTitle, DescriptionList, DescriptionListDescription, DescriptionListGroup, DescriptionListTerm, Flex, FlexItem, } from "@patternfly/react-core";
3
+ import { ChartHeaderActions } from "./ChartDownloadButton.js";
4
+ import { ChartExportSurface } from "./ChartExportSurface.js";
3
5
  const formatCount = (value) => typeof value === "number" ? String(value) : "—";
4
- export const InfrastructureSummary = ({ summary, }) => (_jsxs(Card, { isFullHeight: true, id: "infrastructure-summary", children: [_jsx(CardTitle, { children: "Infrastructure summary" }), _jsx(CardBody, { children: _jsxs(DescriptionList, { isAutoFit: true, displaySize: "lg", children: [_jsxs(DescriptionListGroup, { children: [_jsx(DescriptionListTerm, { children: "VMware version" }), _jsx(DescriptionListDescription, { children: summary.vmwareVersion })] }), _jsxs(DescriptionListGroup, { children: [_jsx(DescriptionListTerm, { children: "Datacenters" }), _jsx(DescriptionListDescription, { children: formatCount(summary.datacenters) })] }), _jsxs(DescriptionListGroup, { children: [_jsx(DescriptionListTerm, { children: "vCenters" }), _jsx(DescriptionListDescription, { children: formatCount(summary.vCenters) })] }), _jsxs(DescriptionListGroup, { children: [_jsx(DescriptionListTerm, { children: "ESXi hosts" }), _jsx(DescriptionListDescription, { children: formatCount(summary.esxiHosts) })] })] }) })] }));
6
+ const INFRA_CHART_ID = "infrastructure-summary";
7
+ const INFRA_TITLE = "Infrastructure summary";
8
+ export const InfrastructureSummary = ({ summary, }) => (_jsx(ChartExportSurface, { id: INFRA_CHART_ID, title: INFRA_TITLE, children: _jsxs(Card, { isFullHeight: true, children: [_jsx(CardTitle, { children: _jsxs(Flex, { justifyContent: { default: "justifyContentSpaceBetween" }, alignItems: { default: "alignItemsCenter" }, children: [_jsx(FlexItem, { children: INFRA_TITLE }), _jsx(ChartHeaderActions, { chartId: INFRA_CHART_ID, title: INFRA_TITLE })] }) }), _jsx(CardBody, { children: _jsxs(DescriptionList, { isAutoFit: true, displaySize: "lg", children: [_jsxs(DescriptionListGroup, { children: [_jsx(DescriptionListTerm, { children: "VMware version" }), _jsx(DescriptionListDescription, { children: summary.vmwareVersion })] }), _jsxs(DescriptionListGroup, { children: [_jsx(DescriptionListTerm, { children: "Datacenters" }), _jsx(DescriptionListDescription, { children: formatCount(summary.datacenters) })] }), _jsxs(DescriptionListGroup, { children: [_jsx(DescriptionListTerm, { children: "vCenters" }), _jsx(DescriptionListDescription, { children: formatCount(summary.vCenters) })] }), _jsxs(DescriptionListGroup, { children: [_jsx(DescriptionListTerm, { children: "ESXi hosts" }), _jsx(DescriptionListDescription, { children: formatCount(summary.esxiHosts) })] })] }) })] }) }));
5
9
  InfrastructureSummary.displayName = "InfrastructureSummary";
@@ -0,0 +1,31 @@
1
+ import type { FC } from "react";
2
+ import { type MigrationDonutChartLegendVariant } from "../charts/MigrationDonutChart.js";
3
+ /** Network fields this card reads. Apps pass SDK networks structurally. */
4
+ export interface NetworkLike {
5
+ vmsCount?: number;
6
+ vlanId?: string | number;
7
+ }
8
+ /** Infra fields this card reads. */
9
+ export interface NetworkInfraLike {
10
+ networks?: NetworkLike[];
11
+ }
12
+ /** Deprecated NIC histogram used when `distributionByNicCount` is absent. */
13
+ export interface NicCountHistogram {
14
+ data?: number[];
15
+ minValue?: number;
16
+ step?: number;
17
+ }
18
+ /** NIC summary fields this card reads from the VM resource breakdown. */
19
+ export interface NicCountSummary {
20
+ total?: number;
21
+ histogram?: NicCountHistogram;
22
+ }
23
+ export interface NetworkOverviewProps {
24
+ infra?: NetworkInfraLike;
25
+ nicCount?: NicCountSummary;
26
+ distributionByNicCount?: Record<string, number>;
27
+ /** `"html"` in agent-ui; ui-app passes `"chart"`. */
28
+ legendVariant?: MigrationDonutChartLegendVariant;
29
+ }
30
+ export declare const NetworkOverview: FC<NetworkOverviewProps>;
31
+ //# sourceMappingURL=NetworkOverview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NetworkOverview.d.ts","sourceRoot":"","sources":["../../src/report/NetworkOverview.tsx"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,EAAE,EAAO,MAAM,OAAO,CAAC;AAErC,OAAO,EAGL,KAAK,gCAAgC,EACtC,MAAM,kCAAkC,CAAC;AAQ1C,2EAA2E;AAC3E,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAED,oCAAoC;AACpC,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,WAAW,EAAE,CAAC;CAC1B;AAED,6EAA6E;AAC7E,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,yEAAyE;AACzE,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,iBAAiB,CAAC;CAC/B;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,sBAAsB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChD,qDAAqD;IACrD,aAAa,CAAC,EAAE,gCAAgC,CAAC;CAClD;AAiND,eAAO,MAAM,eAAe,EAAE,EAAE,CAAC,oBAAoB,CA2HpD,CAAC"}
@@ -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";