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

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 (138) 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/ClustersOverview.d.ts +24 -0
  12. package/dist/report/ClustersOverview.d.ts.map +1 -0
  13. package/dist/report/ClustersOverview.js +228 -0
  14. package/dist/report/ClustersOverviewStyles.d.ts +12 -0
  15. package/dist/report/ClustersOverviewStyles.d.ts.map +1 -0
  16. package/dist/report/ClustersOverviewStyles.js +101 -0
  17. package/dist/report/CpuAndMemoryOverview.d.ts +19 -0
  18. package/dist/report/CpuAndMemoryOverview.d.ts.map +1 -0
  19. package/dist/report/CpuAndMemoryOverview.js +112 -0
  20. package/dist/report/ErrorTable.d.ts +18 -0
  21. package/dist/report/ErrorTable.d.ts.map +1 -0
  22. package/dist/report/ErrorTable.js +20 -0
  23. package/dist/report/HostPowerStates.d.ts +0 -1
  24. package/dist/report/HostPowerStates.d.ts.map +1 -1
  25. package/dist/report/HostPowerStates.js +2 -2
  26. package/dist/report/HostsOverview.d.ts +13 -0
  27. package/dist/report/HostsOverview.d.ts.map +1 -0
  28. package/dist/report/HostsOverview.js +84 -0
  29. package/dist/report/InfrastructureSummary.d.ts.map +1 -1
  30. package/dist/report/InfrastructureSummary.js +6 -2
  31. package/dist/report/NetworkOverview.d.ts +31 -0
  32. package/dist/report/NetworkOverview.d.ts.map +1 -0
  33. package/dist/report/NetworkOverview.js +201 -0
  34. package/dist/report/OSDistribution.d.ts +0 -2
  35. package/dist/report/OSDistribution.d.ts.map +1 -1
  36. package/dist/report/OSDistribution.js +8 -5
  37. package/dist/report/OsUpgradeRecommendationPopover.d.ts +0 -1
  38. package/dist/report/OsUpgradeRecommendationPopover.d.ts.map +1 -1
  39. package/dist/report/OsUpgradeRecommendationPopover.js +4 -3
  40. package/dist/report/PowerStateCard.d.ts +0 -1
  41. package/dist/report/PowerStateCard.d.ts.map +1 -1
  42. package/dist/report/PowerStateCard.js +4 -2
  43. package/dist/report/ReportExportMenu.d.ts +19 -0
  44. package/dist/report/ReportExportMenu.d.ts.map +1 -0
  45. package/dist/report/ReportExportMenu.js +31 -0
  46. package/dist/report/ReportTable.d.ts +14 -0
  47. package/dist/report/ReportTable.d.ts.map +1 -0
  48. package/dist/report/ReportTable.js +23 -0
  49. package/dist/report/SupportTierBadge.d.ts +0 -1
  50. package/dist/report/SupportTierBadge.d.ts.map +1 -1
  51. package/dist/report/SupportTierBadge.js +1 -21
  52. package/dist/report/VCenterClusterDetails.d.ts +0 -1
  53. package/dist/report/VCenterClusterDetails.d.ts.map +1 -1
  54. package/dist/report/VCenterClusterDetails.js +9 -4
  55. package/dist/report/VmPowerStates.d.ts +0 -1
  56. package/dist/report/VmPowerStates.d.ts.map +1 -1
  57. package/dist/report/VmPowerStates.js +2 -2
  58. package/dist/report/WarningsTable.d.ts +9 -0
  59. package/dist/report/WarningsTable.d.ts.map +1 -0
  60. package/dist/report/WarningsTable.js +20 -0
  61. package/dist/report/captureChartElement.d.ts +8 -0
  62. package/dist/report/captureChartElement.d.ts.map +1 -0
  63. package/dist/report/captureChartElement.js +304 -0
  64. package/dist/report/chartExport.d.ts +80 -0
  65. package/dist/report/chartExport.d.ts.map +1 -0
  66. package/dist/report/chartExport.js +182 -0
  67. package/dist/report/chartExportContext.d.ts +32 -0
  68. package/dist/report/chartExportContext.d.ts.map +1 -0
  69. package/dist/report/chartExportContext.js +36 -0
  70. package/dist/report/chartExportFilenames.d.ts +4 -0
  71. package/dist/report/chartExportFilenames.d.ts.map +1 -0
  72. package/dist/report/chartExportFilenames.js +16 -0
  73. package/dist/report/constants.d.ts +2 -0
  74. package/dist/report/constants.d.ts.map +1 -1
  75. package/dist/report/constants.js +2 -0
  76. package/dist/report/dashboardStyles.d.ts +0 -4
  77. package/dist/report/dashboardStyles.d.ts.map +1 -1
  78. package/dist/report/dashboardStyles.js +0 -19
  79. package/dist/report/htmlExport.d.ts +9 -0
  80. package/dist/report/htmlExport.d.ts.map +1 -0
  81. package/dist/report/htmlExport.js +65 -0
  82. package/dist/report/index.d.ts +20 -0
  83. package/dist/report/index.d.ts.map +1 -1
  84. package/dist/report/index.js +20 -0
  85. package/dist/report/pdfExport.d.ts +3 -0
  86. package/dist/report/pdfExport.d.ts.map +1 -0
  87. package/dist/report/pdfExport.js +131 -0
  88. package/dist/report/pdfPage.d.ts +15 -0
  89. package/dist/report/pdfPage.d.ts.map +1 -0
  90. package/dist/report/pdfPage.js +44 -0
  91. package/dist/report/reportExportOptions.d.ts +17 -0
  92. package/dist/report/reportExportOptions.d.ts.map +1 -0
  93. package/dist/report/reportExportOptions.js +31 -0
  94. package/dist/report/zipChartPngs.d.ts +3 -0
  95. package/dist/report/zipChartPngs.d.ts.map +1 -0
  96. package/dist/report/zipChartPngs.js +8 -0
  97. package/package.json +7 -2
  98. package/src/report/ChartDownloadButton.tsx +59 -0
  99. package/src/report/ChartExportProvider.tsx +392 -0
  100. package/src/report/ChartExportSurface.tsx +60 -0
  101. package/src/report/ClustersOverview.tsx +451 -0
  102. package/src/report/ClustersOverviewStyles.ts +110 -0
  103. package/src/report/CpuAndMemoryOverview.tsx +277 -0
  104. package/src/report/ErrorTable.tsx +85 -0
  105. package/src/report/HostPowerStates.tsx +0 -3
  106. package/src/report/HostsOverview.tsx +185 -0
  107. package/src/report/InfrastructureSummary.tsx +48 -31
  108. package/src/report/NetworkOverview.tsx +405 -0
  109. package/src/report/OSDistribution.tsx +46 -42
  110. package/src/report/OsUpgradeRecommendationPopover.tsx +3 -4
  111. package/src/report/PowerStateCard.tsx +49 -36
  112. package/src/report/ReportExportMenu.tsx +119 -0
  113. package/src/report/ReportTable.tsx +81 -0
  114. package/src/report/SupportTierBadge.tsx +12 -40
  115. package/src/report/VCenterClusterDetails.tsx +38 -16
  116. package/src/report/VmPowerStates.tsx +0 -3
  117. package/src/report/WarningsTable.tsx +75 -0
  118. package/src/report/__tests__/ChartExportProvider.test.tsx +582 -0
  119. package/src/report/__tests__/ClustersOverview.test.tsx +241 -0
  120. package/src/report/__tests__/CpuAndMemoryOverview.test.tsx +200 -0
  121. package/src/report/__tests__/HostsOverview.test.tsx +145 -0
  122. package/src/report/__tests__/InfrastructureSummary.test.tsx +0 -22
  123. package/src/report/__tests__/NetworkOverview.test.tsx +269 -0
  124. package/src/report/__tests__/OSDistribution.test.tsx +10 -11
  125. package/src/report/__tests__/captureChartElement.test.ts +205 -0
  126. package/src/report/__tests__/chartExportPackagers.test.ts +299 -0
  127. package/src/report/captureChartElement.ts +398 -0
  128. package/src/report/chartExport.ts +277 -0
  129. package/src/report/chartExportContext.tsx +92 -0
  130. package/src/report/chartExportFilenames.ts +21 -0
  131. package/src/report/constants.ts +2 -0
  132. package/src/report/dashboardStyles.ts +0 -23
  133. package/src/report/htmlExport.ts +83 -0
  134. package/src/report/index.ts +107 -0
  135. package/src/report/pdfExport.ts +233 -0
  136. package/src/report/pdfPage.ts +83 -0
  137. package/src/report/reportExportOptions.ts +53 -0
  138. package/src/report/zipChartPngs.ts +10 -0
@@ -0,0 +1,451 @@
1
+ import {
2
+ Card,
3
+ CardBody,
4
+ CardTitle,
5
+ Dropdown,
6
+ DropdownItem,
7
+ DropdownList,
8
+ Flex,
9
+ FlexItem,
10
+ MenuToggle,
11
+ type MenuToggleElement,
12
+ } from "@patternfly/react-core";
13
+ import { DatabaseIcon } from "@patternfly/react-icons";
14
+ import {
15
+ chart_color_black_500,
16
+ chart_color_blue_300,
17
+ chart_color_green_300,
18
+ chart_color_orange_100,
19
+ chart_color_purple_100,
20
+ chart_color_purple_300,
21
+ chart_color_red_orange_200,
22
+ chart_color_teal_200,
23
+ chart_color_teal_400,
24
+ chart_color_yellow_400,
25
+ } from "@patternfly/react-tokens";
26
+ import type { FC, Ref } from "react";
27
+ import { useMemo, useState } from "react";
28
+ import {
29
+ MigrationDonutChart,
30
+ type MigrationDonutChartDatum,
31
+ type MigrationDonutChartLegendVariant,
32
+ } from "../charts/MigrationDonutChart.js";
33
+ import { CardEmptyState } from "./CardEmptyState.js";
34
+ import { ChartHeaderActions } from "./ChartDownloadButton.js";
35
+ import { ChartExportSurface } from "./ChartExportSurface.js";
36
+ import { clustersOverviewStyles } from "./ClustersOverviewStyles.js";
37
+ import { chartExportViewsFromLabels } from "./chartExport.js";
38
+ import { REPORT_CARD_EMPTY_STATE_TITLES } from "./constants.js";
39
+ import { dashboardStyles } from "./dashboardStyles.js";
40
+
41
+ /**
42
+ * Cluster inventory fields this card reads.
43
+ * agent-sdk and planner-sdk inventories both satisfy it. `cpuOverCommitment`
44
+ * stays `unknown` because generated models type it as a number while older
45
+ * payloads send a ratio string such as `"1:4"`.
46
+ */
47
+ export interface InventoryDataLike {
48
+ infra?: {
49
+ cpuOverCommitment?: unknown;
50
+ };
51
+ vms?: {
52
+ total?: number;
53
+ };
54
+ }
55
+
56
+ export interface ClustersOverviewProps {
57
+ clustersPerDatacenter?: number[];
58
+ clusters?: Record<string, InventoryDataLike>;
59
+ /** `"html"` in agent-ui; ui-app passes `"chart"`. */
60
+ legendVariant?: MigrationDonutChartLegendVariant;
61
+ }
62
+
63
+ type ViewMode = "dataCenterDistribution" | "vmByCluster" | "cpuOverCommitment";
64
+
65
+ interface ClusterChart {
66
+ chartData: MigrationDonutChartDatum[];
67
+ legend: Record<string, string>;
68
+ title: string;
69
+ subTitle: string;
70
+ }
71
+
72
+ const VIEW_MODE_LABELS: Record<ViewMode, string> = {
73
+ dataCenterDistribution: "Cluster distribution by data center",
74
+ vmByCluster: "VM distribution by cluster",
75
+ cpuOverCommitment: "Cluster CPU over commitment",
76
+ };
77
+
78
+ const TOP_CLUSTERS = 4;
79
+ const TOP_CPU_RATIOS = 5;
80
+ const REST_OF_CLUSTERS_LABEL = "Rest of clusters";
81
+ const REST_OF_DATACENTERS_LABEL = "Rest of datacenters";
82
+ const CHART_ID = "clusters-overview";
83
+ const CHART_TITLE = "Clusters";
84
+
85
+ /**
86
+ * Same categorical order as the previous cluster hex palette, mapped onto
87
+ * PatternFly `chart_color_*` tokens (nearest token where the old hex was
88
+ * not itself a token).
89
+ */
90
+ const CLUSTER_COLORS = [
91
+ chart_color_blue_300.value,
92
+ chart_color_purple_300.value,
93
+ chart_color_purple_100.value,
94
+ chart_color_teal_200.value,
95
+ chart_color_yellow_400.value,
96
+ chart_color_green_300.value,
97
+ chart_color_orange_100.value,
98
+ chart_color_red_orange_200.value,
99
+ chart_color_teal_400.value,
100
+ chart_color_black_500.value,
101
+ ];
102
+
103
+ const EMPTY_CHART: ClusterChart = {
104
+ chartData: [],
105
+ legend: {},
106
+ title: "",
107
+ subTitle: "",
108
+ };
109
+
110
+ const isViewMode = (value: string | number | undefined): value is ViewMode =>
111
+ value === "dataCenterDistribution" ||
112
+ value === "vmByCluster" ||
113
+ value === "cpuOverCommitment";
114
+
115
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
116
+ typeof value === "object" && value !== null && !Array.isArray(value);
117
+
118
+ const legendFor = (categories: string[]): Record<string, string> => {
119
+ const legend: Record<string, string> = {};
120
+ categories.forEach((category, index) => {
121
+ const color = CLUSTER_COLORS[index % CLUSTER_COLORS.length];
122
+ if (color) {
123
+ legend[category] = color;
124
+ }
125
+ });
126
+ return legend;
127
+ };
128
+
129
+ const readCounts = (values: unknown): number[] => {
130
+ if (!Array.isArray(values)) {
131
+ return [];
132
+ }
133
+ return values.filter(
134
+ (value): value is number =>
135
+ typeof value === "number" && Number.isFinite(value) && value >= 0,
136
+ );
137
+ };
138
+
139
+ const readClusters = (clusters: unknown): unknown[] => {
140
+ if (!isRecord(clusters)) {
141
+ return [];
142
+ }
143
+ return Object.values(clusters);
144
+ };
145
+
146
+ const readVmTotal = (cluster: unknown): number => {
147
+ if (!isRecord(cluster) || !isRecord(cluster.vms)) {
148
+ return 0;
149
+ }
150
+ const total = cluster.vms.total;
151
+ return typeof total === "number" && Number.isFinite(total) && total >= 0
152
+ ? total
153
+ : 0;
154
+ };
155
+
156
+ /**
157
+ * Read `infra.cpuOverCommitment` without assuming either SDK's generated
158
+ * shape. Missing infra, a non-object infra, or an absent property yield
159
+ * `undefined` instead of throwing.
160
+ */
161
+ const readCpuOverCommitment = (cluster: unknown): unknown => {
162
+ if (!isRecord(cluster) || !isRecord(cluster.infra)) {
163
+ return undefined;
164
+ }
165
+ if (!("cpuOverCommitment" in cluster.infra)) {
166
+ return undefined;
167
+ }
168
+ return cluster.infra.cpuOverCommitment;
169
+ };
170
+
171
+ const parseCpuOverCommitment = (raw: unknown): number => {
172
+ if (typeof raw === "number") {
173
+ return Number.isFinite(raw) ? raw : Number.NaN;
174
+ }
175
+ if (typeof raw !== "string") {
176
+ return Number.NaN;
177
+ }
178
+ const trimmed = raw.trim();
179
+ if (trimmed === "") {
180
+ return Number.NaN;
181
+ }
182
+ const match = trimmed.match(/^\d+\s*:\s*(\d+(?:\.\d+)?)$/);
183
+ if (match?.[1] != null) {
184
+ return Number(match[1]);
185
+ }
186
+ const asNum = Number(trimmed);
187
+ return Number.isFinite(asNum) ? asNum : Number.NaN;
188
+ };
189
+
190
+ const formatRatio = (ratio: number): string => {
191
+ const formatted = ratio % 1 === 0 ? ratio.toFixed(0) : ratio.toFixed(2);
192
+ return formatted.replace(/(?:\.0+|(\.\d*[1-9]))0+$/, "$1");
193
+ };
194
+
195
+ const buildRankedDonut = (
196
+ counts: number[],
197
+ options: {
198
+ sliceName: (rank: number) => string;
199
+ countSuffix: string;
200
+ restName: string;
201
+ subTitle: string;
202
+ },
203
+ ): ClusterChart => {
204
+ const total = counts.reduce((sum, count) => sum + count, 0);
205
+ const ranked = counts
206
+ .map((count, index) => ({ count, index }))
207
+ .sort((a, b) => b.count - a.count);
208
+ const top = ranked.slice(0, TOP_CLUSTERS);
209
+ const restSum = ranked
210
+ .slice(TOP_CLUSTERS)
211
+ .reduce((sum, item) => sum + item.count, 0);
212
+
213
+ const slices: MigrationDonutChartDatum[] = top.map((item, index) => {
214
+ const name = options.sliceName(index + 1);
215
+ return {
216
+ name,
217
+ count: item.count,
218
+ countDisplay: `${item.count} ${options.countSuffix}`,
219
+ legendCategory: name,
220
+ };
221
+ });
222
+
223
+ if (restSum > 0) {
224
+ slices.push({
225
+ name: options.restName,
226
+ count: restSum,
227
+ countDisplay: `${restSum} ${options.countSuffix}`,
228
+ legendCategory: options.restName,
229
+ });
230
+ }
231
+
232
+ return {
233
+ chartData: slices,
234
+ legend: legendFor(slices.map((slice) => slice.legendCategory)),
235
+ title: `${total}`,
236
+ subTitle: options.subTitle,
237
+ };
238
+ };
239
+
240
+ const buildVmByCluster = (clusters: unknown): ClusterChart => {
241
+ const counts = readClusters(clusters).map(readVmTotal);
242
+ if (counts.length === 0) {
243
+ return EMPTY_CHART;
244
+ }
245
+ return buildRankedDonut(counts, {
246
+ sliceName: (rank) => `Cluster ${rank}`,
247
+ countSuffix: "VMs",
248
+ restName: REST_OF_CLUSTERS_LABEL,
249
+ subTitle: "VMs",
250
+ });
251
+ };
252
+
253
+ const buildDatacenterDistribution = (
254
+ clustersPerDatacenter: unknown,
255
+ ): ClusterChart => {
256
+ const counts = readCounts(clustersPerDatacenter);
257
+ if (counts.length === 0) {
258
+ return EMPTY_CHART;
259
+ }
260
+ return buildRankedDonut(counts, {
261
+ sliceName: (rank) => `Data center ${rank}`,
262
+ countSuffix: "clusters",
263
+ restName: REST_OF_DATACENTERS_LABEL,
264
+ subTitle: "Clusters",
265
+ });
266
+ };
267
+
268
+ const buildCpuOverCommitment = (clusters: unknown): ClusterChart => {
269
+ const ranked = readClusters(clusters)
270
+ .map((cluster) => parseCpuOverCommitment(readCpuOverCommitment(cluster)))
271
+ .filter((value) => Number.isFinite(value) && value >= 0)
272
+ .sort((a, b) => b - a)
273
+ .slice(0, TOP_CPU_RATIOS);
274
+
275
+ const slices: MigrationDonutChartDatum[] = ranked.map((value, index) => {
276
+ const legendCategory = `Cluster ${index + 1}`;
277
+ const display = formatRatio(value);
278
+ return {
279
+ name: display,
280
+ count: value,
281
+ countDisplay: display,
282
+ legendCategory,
283
+ };
284
+ });
285
+
286
+ return {
287
+ chartData: slices,
288
+ legend: legendFor(slices.map((slice) => slice.legendCategory)),
289
+ title: "",
290
+ subTitle: "",
291
+ };
292
+ };
293
+
294
+ export const ClustersOverview: FC<ClustersOverviewProps> = ({
295
+ clustersPerDatacenter,
296
+ clusters,
297
+ legendVariant = "html",
298
+ }) => {
299
+ const [viewMode, setViewMode] = useState<ViewMode>("vmByCluster");
300
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
301
+
302
+ const chart = useMemo(() => {
303
+ if (viewMode === "cpuOverCommitment") {
304
+ return buildCpuOverCommitment(clusters);
305
+ }
306
+ if (viewMode === "vmByCluster") {
307
+ return buildVmByCluster(clusters);
308
+ }
309
+ return buildDatacenterDistribution(clustersPerDatacenter);
310
+ }, [viewMode, clustersPerDatacenter, clusters]);
311
+
312
+ const chartTitle = `${CHART_TITLE} — ${VIEW_MODE_LABELS[viewMode]}`;
313
+ const emptyTitle =
314
+ viewMode === "cpuOverCommitment"
315
+ ? REPORT_CARD_EMPTY_STATE_TITLES.cpuOvercommitment
316
+ : REPORT_CARD_EMPTY_STATE_TITLES.clusters;
317
+
318
+ return (
319
+ <ChartExportSurface
320
+ id={CHART_ID}
321
+ title={chartTitle}
322
+ exportViews={chartExportViewsFromLabels(CHART_TITLE, VIEW_MODE_LABELS)}
323
+ activeExportViewId={viewMode}
324
+ onExportViewChange={(viewId) => {
325
+ if (isViewMode(viewId)) {
326
+ setViewMode(viewId);
327
+ }
328
+ }}
329
+ >
330
+ <Card className={dashboardStyles.card}>
331
+ <CardTitle>
332
+ <Flex
333
+ className={clustersOverviewStyles.titleRow}
334
+ justifyContent={{ default: "justifyContentSpaceBetween" }}
335
+ alignItems={{ default: "alignItemsCenter" }}
336
+ >
337
+ <FlexItem>
338
+ <div>
339
+ <div>
340
+ <DatabaseIcon /> {CHART_TITLE}
341
+ </div>
342
+ <div className={clustersOverviewStyles.cardSubtitle}>
343
+ {viewMode === "dataCenterDistribution"
344
+ ? "Top 5 datacenters"
345
+ : "Top 5 clusters"}
346
+ </div>
347
+ </div>
348
+ </FlexItem>
349
+ <ChartHeaderActions chartId={CHART_ID} title={chartTitle}>
350
+ <Dropdown
351
+ isOpen={isDropdownOpen}
352
+ onSelect={(_event, value) => {
353
+ if (isViewMode(value)) {
354
+ setViewMode(value);
355
+ }
356
+ setIsDropdownOpen(false);
357
+ }}
358
+ onOpenChange={setIsDropdownOpen}
359
+ toggle={(toggleRef: Ref<MenuToggleElement>) => (
360
+ <MenuToggle
361
+ ref={toggleRef}
362
+ onClick={() => setIsDropdownOpen((open) => !open)}
363
+ isExpanded={isDropdownOpen}
364
+ className={clustersOverviewStyles.menuToggleMinWidth}
365
+ >
366
+ {VIEW_MODE_LABELS[viewMode]}
367
+ </MenuToggle>
368
+ )}
369
+ >
370
+ <DropdownList>
371
+ <DropdownItem value="vmByCluster" key="vmByCluster">
372
+ {VIEW_MODE_LABELS.vmByCluster}
373
+ </DropdownItem>
374
+ <DropdownItem
375
+ value="cpuOverCommitment"
376
+ key="cpuOverCommitment"
377
+ >
378
+ {VIEW_MODE_LABELS.cpuOverCommitment}
379
+ </DropdownItem>
380
+ <DropdownItem
381
+ value="dataCenterDistribution"
382
+ key="dataCenterDistribution"
383
+ >
384
+ {VIEW_MODE_LABELS.dataCenterDistribution}
385
+ </DropdownItem>
386
+ </DropdownList>
387
+ </Dropdown>
388
+ </ChartHeaderActions>
389
+ </Flex>
390
+ </CardTitle>
391
+ <CardBody className={dashboardStyles.cardBodyScrollable}>
392
+ {chart.chartData.length === 0 ? (
393
+ <CardEmptyState title={emptyTitle} />
394
+ ) : viewMode === "cpuOverCommitment" ? (
395
+ <>
396
+ <div className={clustersOverviewStyles.cpuOvercommitBoxes}>
397
+ {chart.chartData.map((item) => (
398
+ <div
399
+ key={`cpu-box-${item.legendCategory}`}
400
+ className={clustersOverviewStyles.cpuOvercommitBox}
401
+ style={{ background: chart.legend[item.legendCategory] }}
402
+ >
403
+ {item.countDisplay}
404
+ </div>
405
+ ))}
406
+ </div>
407
+ <div className={clustersOverviewStyles.cpuOvercommitLegend}>
408
+ {chart.chartData.map((item) => (
409
+ <div
410
+ key={`cpu-legend-${item.legendCategory}`}
411
+ className={clustersOverviewStyles.cpuOvercommitLegendItem}
412
+ >
413
+ <span
414
+ className={
415
+ clustersOverviewStyles.cpuOvercommitLegendSwatch
416
+ }
417
+ style={{ background: chart.legend[item.legendCategory] }}
418
+ />
419
+ <span
420
+ className={clustersOverviewStyles.cpuOvercommitLegendText}
421
+ >
422
+ {item.legendCategory} ({item.countDisplay})
423
+ </span>
424
+ </div>
425
+ ))}
426
+ </div>
427
+ </>
428
+ ) : (
429
+ <MigrationDonutChart
430
+ legendVariant={legendVariant}
431
+ data={chart.chartData}
432
+ height={300}
433
+ width={420}
434
+ donutThickness={18}
435
+ titleFontSize={34}
436
+ legend={chart.legend}
437
+ title={chart.title}
438
+ subTitle={chart.subTitle}
439
+ subTitleColor="var(--pf-t--global--text--color--subtle)"
440
+ tooltipLabelFormatter={({ datum, percent }) =>
441
+ `${datum.countDisplay}\n${percent.toFixed(1)}%`
442
+ }
443
+ />
444
+ )}
445
+ </CardBody>
446
+ </Card>
447
+ </ChartExportSurface>
448
+ );
449
+ };
450
+
451
+ ClustersOverview.displayName = "ClustersOverview";
@@ -0,0 +1,110 @@
1
+ import { css } from "@emotion/css";
2
+
3
+ export const clustersOverviewStyles = {
4
+ titleRow: css`
5
+ width: 100%;
6
+ `,
7
+
8
+ cardSubtitle: css`
9
+ color: var(--pf-t--global--text--color--subtle);
10
+ font-size: 0.85rem;
11
+ `,
12
+
13
+ menuToggleMinWidth: css`
14
+ min-width: 250px;
15
+ `,
16
+
17
+ cpuOvercommitBoxes: css`
18
+ display: flex;
19
+ justify-content: center;
20
+ gap: 12px;
21
+ margin: 124px 0 16px;
22
+ flex-wrap: wrap;
23
+
24
+ @media (max-width: 1200px) {
25
+ margin: 72px 0 12px;
26
+ gap: 20px;
27
+ }
28
+
29
+ @media (max-width: 768px) {
30
+ margin: 40px 0 8px;
31
+ gap: 16px;
32
+ }
33
+ `,
34
+
35
+ cpuOvercommitBox: css`
36
+ color: #000;
37
+ min-width: 120px;
38
+ height: 64px;
39
+ border-radius: 12px;
40
+ display: flex;
41
+ align-items: center;
42
+ justify-content: center;
43
+ font-size: 22px;
44
+ padding: 0 16px;
45
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
46
+
47
+ @media (max-width: 768px) {
48
+ min-width: 96px;
49
+ height: 56px;
50
+ font-size: 18px;
51
+ padding: 0 12px;
52
+ border-radius: 10px;
53
+ }
54
+
55
+ @media (max-width: 480px) {
56
+ min-width: 88px;
57
+ height: 48px;
58
+ font-size: 16px;
59
+ border-radius: 8px;
60
+ }
61
+ `,
62
+
63
+ cpuOvercommitLegend: css`
64
+ display: flex;
65
+ justify-content: center;
66
+ gap: 6px 24px;
67
+ flex-wrap: wrap;
68
+ margin-top: 20%;
69
+
70
+ @media (max-width: 1200px) {
71
+ margin-top: 20%;
72
+ gap: 6px 20px;
73
+ }
74
+
75
+ @media (max-width: 768px) {
76
+ margin-top: 20%;
77
+ gap: 6px 16px;
78
+ }
79
+ `,
80
+
81
+ cpuOvercommitLegendItem: css`
82
+ display: flex;
83
+ align-items: center;
84
+ gap: 12px;
85
+ `,
86
+
87
+ cpuOvercommitLegendSwatch: css`
88
+ display: inline-block;
89
+ width: 12px;
90
+ height: 12px;
91
+ border-radius: 2px;
92
+
93
+ @media (max-width: 768px) {
94
+ width: 10px;
95
+ height: 10px;
96
+ }
97
+ `,
98
+
99
+ cpuOvercommitLegendText: css`
100
+ font-size: 16px;
101
+
102
+ @media (max-width: 768px) {
103
+ font-size: 16px;
104
+ }
105
+
106
+ @media (max-width: 480px) {
107
+ font-size: 14px;
108
+ }
109
+ `,
110
+ };