@openshift-migration-advisor/shared-components 0.27.0 → 0.28.0-47af17e06309

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 (60) hide show
  1. package/dist/charts/MigrationDonutChart.d.ts.map +1 -1
  2. package/dist/charts/MigrationDonutChart.js +7 -0
  3. package/dist/report/ChartExportProvider.d.ts +4 -3
  4. package/dist/report/ChartExportProvider.d.ts.map +1 -1
  5. package/dist/report/ChartExportProvider.js +7 -4
  6. package/dist/report/ClustersOverview.d.ts +24 -0
  7. package/dist/report/ClustersOverview.d.ts.map +1 -0
  8. package/dist/report/ClustersOverview.js +228 -0
  9. package/dist/report/ClustersOverviewStyles.d.ts +12 -0
  10. package/dist/report/ClustersOverviewStyles.d.ts.map +1 -0
  11. package/dist/report/ClustersOverviewStyles.js +101 -0
  12. package/dist/report/CpuAndMemoryOverview.d.ts +19 -0
  13. package/dist/report/CpuAndMemoryOverview.d.ts.map +1 -0
  14. package/dist/report/CpuAndMemoryOverview.js +112 -0
  15. package/dist/report/FeatureStatusBadge.js +1 -1
  16. package/dist/report/StorageOverview.d.ts +27 -0
  17. package/dist/report/StorageOverview.d.ts.map +1 -0
  18. package/dist/report/StorageOverview.js +319 -0
  19. package/dist/report/VMMigrationStatus.d.ts +33 -0
  20. package/dist/report/VMMigrationStatus.d.ts.map +1 -0
  21. package/dist/report/VMMigrationStatus.js +129 -0
  22. package/dist/report/captureChartElement.d.ts +6 -1
  23. package/dist/report/captureChartElement.d.ts.map +1 -1
  24. package/dist/report/captureChartElement.js +41 -12
  25. package/dist/report/chartExport.d.ts +23 -0
  26. package/dist/report/chartExport.d.ts.map +1 -1
  27. package/dist/report/chartExport.js +21 -1
  28. package/dist/report/chartExportContext.d.ts +6 -2
  29. package/dist/report/chartExportContext.d.ts.map +1 -1
  30. package/dist/report/dashboardStyles.d.ts +6 -0
  31. package/dist/report/dashboardStyles.d.ts.map +1 -1
  32. package/dist/report/dashboardStyles.js +7 -4
  33. package/dist/report/index.d.ts +6 -2
  34. package/dist/report/index.d.ts.map +1 -1
  35. package/dist/report/index.js +4 -0
  36. package/dist/report/pdfExport.d.ts +2 -2
  37. package/dist/report/pdfExport.d.ts.map +1 -1
  38. package/dist/report/pdfExport.js +52 -3
  39. package/package.json +2 -2
  40. package/src/charts/MigrationDonutChart.tsx +9 -0
  41. package/src/report/ChartExportProvider.tsx +18 -6
  42. package/src/report/ClustersOverview.tsx +451 -0
  43. package/src/report/ClustersOverviewStyles.ts +110 -0
  44. package/src/report/CpuAndMemoryOverview.tsx +277 -0
  45. package/src/report/FeatureStatusBadge.tsx +1 -1
  46. package/src/report/StorageOverview.tsx +629 -0
  47. package/src/report/VMMigrationStatus.tsx +358 -0
  48. package/src/report/__tests__/ChartExportProvider.test.tsx +110 -2
  49. package/src/report/__tests__/ClustersOverview.test.tsx +241 -0
  50. package/src/report/__tests__/CpuAndMemoryOverview.test.tsx +200 -0
  51. package/src/report/__tests__/StorageOverview.test.tsx +223 -0
  52. package/src/report/__tests__/VMMigrationStatus.test.tsx +178 -0
  53. package/src/report/__tests__/captureChartElement.test.ts +120 -0
  54. package/src/report/__tests__/chartExportPackagers.test.ts +75 -0
  55. package/src/report/captureChartElement.ts +55 -10
  56. package/src/report/chartExport.ts +41 -1
  57. package/src/report/chartExportContext.tsx +9 -1
  58. package/src/report/dashboardStyles.ts +7 -4
  59. package/src/report/index.ts +22 -0
  60. package/src/report/pdfExport.ts +81 -2
@@ -1 +1 @@
1
- {"version":3,"file":"MigrationDonutChart.d.ts","sourceRoot":"","sources":["../../src/charts/MigrationDonutChart.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,EAAE,EAAgB,MAAM,OAAO,CAAC;AAG9C,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,WAAW,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,MAAM,gCAAgC,GAAG,MAAM,GAAG,OAAO,CAAC;AAEhE,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,wBAAwB,EAAE,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qBAAqB,CAAC,EAAE,CAAC,IAAI,EAAE;QAC7B,KAAK,EAAE;YACL,CAAC,EAAE,MAAM,CAAC;YACV,CAAC,EAAE,MAAM,CAAC;YACV,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;YAC/B,cAAc,EAAE,MAAM,CAAC;SACxB,CAAC;QACF,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;KACf,KAAK,MAAM,CAAC;IACb,iDAAiD;IACjD,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,wBAAwB,KAAK,IAAI,CAAC;IACvD,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,mFAAmF;IACnF,oBAAoB,CAAC,EAAE,CAAC,IAAI,EAAE;QAC5B,CAAC,EAAE,MAAM,CAAC;QACV,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;KAChC,KAAK,MAAM,CAAC;IACb;;;;OAIG;IACH,aAAa,CAAC,EAAE,gCAAgC,CAAC;IACjD,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2CAA2C;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2CAA2C;IAC3C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AA8ED,eAAO,MAAM,mBAAmB,EAAE,EAAE,CAAC,wBAAwB,CAiX5D,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
1
+ {"version":3,"file":"MigrationDonutChart.d.ts","sourceRoot":"","sources":["../../src/charts/MigrationDonutChart.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,EAAE,EAAgB,MAAM,OAAO,CAAC;AAG9C,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC1C,WAAW,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AAED,MAAM,MAAM,gCAAgC,GAAG,MAAM,GAAG,OAAO,CAAC;AAEhE,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,wBAAwB,EAAE,CAAC;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,qBAAqB,CAAC,EAAE,CAAC,IAAI,EAAE;QAC7B,KAAK,EAAE;YACL,CAAC,EAAE,MAAM,CAAC;YACV,CAAC,EAAE,MAAM,CAAC;YACV,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;YAC/B,cAAc,EAAE,MAAM,CAAC;SACxB,CAAC;QACF,OAAO,EAAE,MAAM,CAAC;QAChB,KAAK,EAAE,MAAM,CAAC;KACf,KAAK,MAAM,CAAC;IACb,iDAAiD;IACjD,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,wBAAwB,KAAK,IAAI,CAAC;IACvD,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,mFAAmF;IACnF,oBAAoB,CAAC,EAAE,CAAC,IAAI,EAAE;QAC5B,CAAC,EAAE,MAAM,CAAC;QACV,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;KAChC,KAAK,MAAM,CAAC;IACb;;;;OAIG;IACH,aAAa,CAAC,EAAE,gCAAgC,CAAC;IACjD,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2CAA2C;IAC3C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,2CAA2C;IAC3C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0EAA0E;IAC1E,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAoFD,eAAO,MAAM,mBAAmB,EAAE,EAAE,CAAC,wBAAwB,CAoX5D,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
@@ -35,6 +35,8 @@ const themedChartTooltipFlyoutPadding = {
35
35
  left: 10,
36
36
  right: 10,
37
37
  };
38
+ /** Victory SVG text defaults to a serif face; match PatternFly / html2canvas export. */
39
+ const chartTextFontFamily = "RedHatText, Overpass, Helvetica, Arial, sans-serif";
38
40
  const styles = {
39
41
  legendIcon: css `
40
42
  margin-right: 4px;
@@ -63,6 +65,7 @@ const styles = {
63
65
  background: none;
64
66
  padding: var(--pf-t--global--spacer--xs) var(--pf-t--global--spacer--ml);
65
67
  margin: 0;
68
+ font-family: ${chartTextFontFamily};
66
69
  transition: opacity var(--pf-t--global--motion--duration--short);
67
70
  white-space: nowrap;
68
71
 
@@ -73,6 +76,7 @@ const styles = {
73
76
  legendLabel: css `
74
77
  gap: var(--pf-t--global--spacer--lg);
75
78
  padding: var(--pf-t--global--spacer--xs) var(--pf-t--global--spacer--ml);
79
+ font-family: ${chartTextFontFamily};
76
80
  white-space: nowrap;
77
81
  `,
78
82
  legendInner: css `
@@ -214,6 +218,7 @@ export const MigrationDonutChart = ({ data, legend, customColors, height = 260,
214
218
  labels: {
215
219
  fontSize: labelFontSize,
216
220
  fill: "var(--pf-t--global--text--color--regular)",
221
+ fontFamily: chartTextFontFamily,
217
222
  },
218
223
  } }) }) }) }));
219
224
  return (_jsxs(Flex, { direction: { default: "column" }, alignItems: { default: "alignItemsCenter" }, className: `${onItemClick ? styles.cursorPointer : styles.cursorDefault} ${styles.chartContainer}`, children: [_jsxs("div", { className: styles.donutWrapper, children: [_jsx(ChartDonut, { ariaDesc: "Migration data donut chart", data: chartData, events: chartEvents, labelComponent: donutTooltip, labels: ({ datum, }) => formatLabel(datum), colorScale: colorScale, constrainToVisibleArea: true, innerRadius: innerRadius, padAngle: padAngle, title: title, subTitle: subTitle, height: height, width: width, padding: {
@@ -226,11 +231,13 @@ export const MigrationDonutChart = ({ data, legend, customColors, height = 260,
226
231
  fill: titleColor,
227
232
  fontSize: titleFontSize,
228
233
  fontWeight: "bold",
234
+ fontFamily: chartTextFontFamily,
229
235
  },
230
236
  ] })) : undefined, subTitleComponent: subTitle ? (_jsx(ChartLabel, { dy: showChartLegend ? 7 : undefined, style: [
231
237
  {
232
238
  fill: subTitleColor,
233
239
  fontSize: subTitleFontSize,
240
+ fontFamily: chartTextFontFamily,
234
241
  },
235
242
  ] })) : undefined }), onTitleClick && title && (_jsx("div", { onClick: onTitleClick, className: css `
236
243
  position: absolute;
@@ -1,9 +1,10 @@
1
1
  import { type FC, type ReactNode } from "react";
2
- import { type ChartCaptureSource, type ChartExportFile } from "./chartExport.js";
3
- export type CaptureChartElement = (element: HTMLElement) => Promise<HTMLCanvasElement>;
2
+ import { type ChartCaptureSource, type ChartExportFile, type PdfTextPage } from "./chartExport.js";
3
+ export type CaptureChartElement = (element: HTMLElement, options?: import("./captureChartElement.js").CaptureChartElementOptions) => Promise<HTMLCanvasElement>;
4
4
  export type ZipChartFiles = (files: ChartExportFile[]) => Promise<Blob>;
5
5
  export type EncodeChartPng = (canvas: HTMLCanvasElement) => Promise<Blob>;
6
6
  export type BuildChartDocument = (charts: ChartCaptureSource[], documentTitle: string) => Promise<Blob>;
7
+ export type BuildPdfDocument = (charts: ChartCaptureSource[], documentTitle: string, extraPages?: PdfTextPage[]) => Promise<Blob>;
7
8
  export type { ChartExportApi, ChartExportFormat, } from "./chartExportContext.js";
8
9
  export { useChartExport, useRegisterChart } from "./chartExportContext.js";
9
10
  export interface ChartExportProviderProps {
@@ -11,7 +12,7 @@ export interface ChartExportProviderProps {
11
12
  capture?: CaptureChartElement;
12
13
  zipFiles?: ZipChartFiles;
13
14
  encodePng?: EncodeChartPng;
14
- buildPdf?: BuildChartDocument;
15
+ buildPdf?: BuildPdfDocument;
15
16
  buildHtml?: BuildChartDocument;
16
17
  downloadFile?: (blob: Blob, filename: string) => void;
17
18
  getZipFilename?: () => string;
@@ -1 +1 @@
1
- {"version":3,"file":"ChartExportProvider.d.ts","sourceRoot":"","sources":["../../src/report/ChartExportProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EAAE,EACP,KAAK,SAAS,EAKf,MAAM,OAAO,CAAC;AACf,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,eAAe,EAQrB,MAAM,kBAAkB,CAAC;AAU1B,MAAM,MAAM,mBAAmB,GAAG,CAChC,OAAO,EAAE,WAAW,KACjB,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAChC,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AACxE,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAC1E,MAAM,MAAM,kBAAkB,GAAG,CAC/B,MAAM,EAAE,kBAAkB,EAAE,EAC5B,aAAa,EAAE,MAAM,KAClB,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB,YAAY,EACV,cAAc,EACd,iBAAiB,GAClB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3E,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,SAAS,CAAC;IACpB,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,cAAc,CAAC,EAAE,MAAM,MAAM,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,MAAM,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,MAAM,CAAC;CAChC;AAsED,eAAO,MAAM,mBAAmB,EAAE,EAAE,CAAC,wBAAwB,CAgQ5D,CAAC"}
1
+ {"version":3,"file":"ChartExportProvider.d.ts","sourceRoot":"","sources":["../../src/report/ChartExportProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,EAAE,EACP,KAAK,SAAS,EAKf,MAAM,OAAO,CAAC;AAEf,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,eAAe,EAGpB,KAAK,WAAW,EAMjB,MAAM,kBAAkB,CAAC;AAU1B,MAAM,MAAM,mBAAmB,GAAG,CAChC,OAAO,EAAE,WAAW,EACpB,OAAO,CAAC,EAAE,OAAO,0BAA0B,EAAE,0BAA0B,KACpE,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAChC,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,eAAe,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AACxE,MAAM,MAAM,cAAc,GAAG,CAAC,MAAM,EAAE,iBAAiB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;AAC1E,MAAM,MAAM,kBAAkB,GAAG,CAC/B,MAAM,EAAE,kBAAkB,EAAE,EAC5B,aAAa,EAAE,MAAM,KAClB,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB,MAAM,MAAM,gBAAgB,GAAG,CAC7B,MAAM,EAAE,kBAAkB,EAAE,EAC5B,aAAa,EAAE,MAAM,EACrB,UAAU,CAAC,EAAE,WAAW,EAAE,KACvB,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB,YAAY,EACV,cAAc,EACd,iBAAiB,GAClB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3E,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,SAAS,CAAC;IACpB,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,YAAY,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,cAAc,CAAC,EAAE,MAAM,MAAM,CAAC;IAC9B,cAAc,CAAC,EAAE,MAAM,MAAM,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,MAAM,CAAC;CAChC;AAsED,eAAO,MAAM,mBAAmB,EAAE,EAAE,CAAC,wBAAwB,CAmQ5D,CAAC"}
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useCallback, useMemo, useRef, useState, } from "react";
3
+ import { CHART_CAPTURE_SCALE_SINGLE } from "./captureChartElement.js";
3
4
  import { canvasToBlob, downloadBlob, releaseCanvas, restoreChartExportViews, sortRegisteredChartsByDocumentOrder, toBulkChartCaptureSources, } from "./chartExport.js";
4
5
  import { ChartExportApiContext, ChartExportRegistryContext, } from "./chartExportContext.js";
5
6
  import { getChartExportFilename } from "./chartExportFilenames.js";
@@ -108,7 +109,9 @@ export const ChartExportProvider = ({ children, capture, zipFiles, encodePng = c
108
109
  throw new Error("The chart is not ready to export yet.");
109
110
  }
110
111
  const captureFn = await resolveCapture(capture);
111
- const canvas = await captureFn(chart.element);
112
+ const canvas = await captureFn(chart.element, {
113
+ scale: CHART_CAPTURE_SCALE_SINGLE,
114
+ });
112
115
  try {
113
116
  downloadFile(await encodePng(canvas), chart.filename);
114
117
  }
@@ -156,17 +159,17 @@ export const ChartExportProvider = ({ children, capture, zipFiles, encodePng = c
156
159
  runCapture,
157
160
  zipFiles,
158
161
  ]);
159
- const downloadPdf = useCallback(async (documentTitle) => {
162
+ const downloadPdf = useCallback(async (documentTitle, extraPages) => {
160
163
  await runCapture(async () => {
161
164
  const charts = listCharts();
162
- if (charts.length === 0) {
165
+ if (charts.length === 0 && (extraPages?.length ?? 0) === 0) {
163
166
  throw new Error("The report is not ready to export yet.");
164
167
  }
165
168
  const captureFn = await resolveCapture(capture);
166
169
  const prepareElement = await resolvePrepareElement(capture);
167
170
  const build = await resolvePdf(buildPdf);
168
171
  try {
169
- downloadFile(await build(toCaptureSources(charts, captureFn, prepareElement), documentTitle), getPdfFilename());
172
+ downloadFile(await build(toCaptureSources(charts, captureFn, prepareElement), documentTitle, extraPages), getPdfFilename());
170
173
  }
171
174
  finally {
172
175
  await restoreChartExportViews(charts);
@@ -0,0 +1,24 @@
1
+ import type { FC } from "react";
2
+ import { type MigrationDonutChartLegendVariant } from "../charts/MigrationDonutChart.js";
3
+ /**
4
+ * Cluster inventory fields this card reads.
5
+ * agent-sdk and planner-sdk inventories both satisfy it. `cpuOverCommitment`
6
+ * stays `unknown` because generated models type it as a number while older
7
+ * payloads send a ratio string such as `"1:4"`.
8
+ */
9
+ export interface InventoryDataLike {
10
+ infra?: {
11
+ cpuOverCommitment?: unknown;
12
+ };
13
+ vms?: {
14
+ total?: number;
15
+ };
16
+ }
17
+ export interface ClustersOverviewProps {
18
+ clustersPerDatacenter?: number[];
19
+ clusters?: Record<string, InventoryDataLike>;
20
+ /** `"html"` in agent-ui; ui-app passes `"chart"`. */
21
+ legendVariant?: MigrationDonutChartLegendVariant;
22
+ }
23
+ export declare const ClustersOverview: FC<ClustersOverviewProps>;
24
+ //# sourceMappingURL=ClustersOverview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClustersOverview.d.ts","sourceRoot":"","sources":["../../src/report/ClustersOverview.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,EAAE,EAAO,MAAM,OAAO,CAAC;AAErC,OAAO,EAGL,KAAK,gCAAgC,EACtC,MAAM,kCAAkC,CAAC;AAS1C;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE;QACN,iBAAiB,CAAC,EAAE,OAAO,CAAC;KAC7B,CAAC;IACF,GAAG,CAAC,EAAE;QACJ,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,qBAAqB;IACpC,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;IAC7C,qDAAqD;IACrD,aAAa,CAAC,EAAE,gCAAgC,CAAC;CAClD;AAyOD,eAAO,MAAM,gBAAgB,EAAE,EAAE,CAAC,qBAAqB,CA2JtD,CAAC"}
@@ -0,0 +1,228 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Card, CardBody, CardTitle, Dropdown, DropdownItem, DropdownList, Flex, FlexItem, MenuToggle, } from "@patternfly/react-core";
3
+ import { DatabaseIcon } from "@patternfly/react-icons";
4
+ 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";
5
+ import { useMemo, useState } from "react";
6
+ import { MigrationDonutChart, } from "../charts/MigrationDonutChart.js";
7
+ import { CardEmptyState } from "./CardEmptyState.js";
8
+ import { ChartHeaderActions } from "./ChartDownloadButton.js";
9
+ import { ChartExportSurface } from "./ChartExportSurface.js";
10
+ import { clustersOverviewStyles } from "./ClustersOverviewStyles.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
+ dataCenterDistribution: "Cluster distribution by data center",
16
+ vmByCluster: "VM distribution by cluster",
17
+ cpuOverCommitment: "Cluster CPU over commitment",
18
+ };
19
+ const TOP_CLUSTERS = 4;
20
+ const TOP_CPU_RATIOS = 5;
21
+ const REST_OF_CLUSTERS_LABEL = "Rest of clusters";
22
+ const REST_OF_DATACENTERS_LABEL = "Rest of datacenters";
23
+ const CHART_ID = "clusters-overview";
24
+ const CHART_TITLE = "Clusters";
25
+ /**
26
+ * Same categorical order as the previous cluster hex palette, mapped onto
27
+ * PatternFly `chart_color_*` tokens (nearest token where the old hex was
28
+ * not itself a token).
29
+ */
30
+ const CLUSTER_COLORS = [
31
+ chart_color_blue_300.value,
32
+ chart_color_purple_300.value,
33
+ chart_color_purple_100.value,
34
+ chart_color_teal_200.value,
35
+ chart_color_yellow_400.value,
36
+ chart_color_green_300.value,
37
+ chart_color_orange_100.value,
38
+ chart_color_red_orange_200.value,
39
+ chart_color_teal_400.value,
40
+ chart_color_black_500.value,
41
+ ];
42
+ const EMPTY_CHART = {
43
+ chartData: [],
44
+ legend: {},
45
+ title: "",
46
+ subTitle: "",
47
+ };
48
+ const isViewMode = (value) => value === "dataCenterDistribution" ||
49
+ value === "vmByCluster" ||
50
+ value === "cpuOverCommitment";
51
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
52
+ const legendFor = (categories) => {
53
+ const legend = {};
54
+ categories.forEach((category, index) => {
55
+ const color = CLUSTER_COLORS[index % CLUSTER_COLORS.length];
56
+ if (color) {
57
+ legend[category] = color;
58
+ }
59
+ });
60
+ return legend;
61
+ };
62
+ const readCounts = (values) => {
63
+ if (!Array.isArray(values)) {
64
+ return [];
65
+ }
66
+ return values.filter((value) => typeof value === "number" && Number.isFinite(value) && value >= 0);
67
+ };
68
+ const readClusters = (clusters) => {
69
+ if (!isRecord(clusters)) {
70
+ return [];
71
+ }
72
+ return Object.values(clusters);
73
+ };
74
+ const readVmTotal = (cluster) => {
75
+ if (!isRecord(cluster) || !isRecord(cluster.vms)) {
76
+ return 0;
77
+ }
78
+ const total = cluster.vms.total;
79
+ return typeof total === "number" && Number.isFinite(total) && total >= 0
80
+ ? total
81
+ : 0;
82
+ };
83
+ /**
84
+ * Read `infra.cpuOverCommitment` without assuming either SDK's generated
85
+ * shape. Missing infra, a non-object infra, or an absent property yield
86
+ * `undefined` instead of throwing.
87
+ */
88
+ const readCpuOverCommitment = (cluster) => {
89
+ if (!isRecord(cluster) || !isRecord(cluster.infra)) {
90
+ return undefined;
91
+ }
92
+ if (!("cpuOverCommitment" in cluster.infra)) {
93
+ return undefined;
94
+ }
95
+ return cluster.infra.cpuOverCommitment;
96
+ };
97
+ const parseCpuOverCommitment = (raw) => {
98
+ if (typeof raw === "number") {
99
+ return Number.isFinite(raw) ? raw : Number.NaN;
100
+ }
101
+ if (typeof raw !== "string") {
102
+ return Number.NaN;
103
+ }
104
+ const trimmed = raw.trim();
105
+ if (trimmed === "") {
106
+ return Number.NaN;
107
+ }
108
+ const match = trimmed.match(/^\d+\s*:\s*(\d+(?:\.\d+)?)$/);
109
+ if (match?.[1] != null) {
110
+ return Number(match[1]);
111
+ }
112
+ const asNum = Number(trimmed);
113
+ return Number.isFinite(asNum) ? asNum : Number.NaN;
114
+ };
115
+ const formatRatio = (ratio) => {
116
+ const formatted = ratio % 1 === 0 ? ratio.toFixed(0) : ratio.toFixed(2);
117
+ return formatted.replace(/(?:\.0+|(\.\d*[1-9]))0+$/, "$1");
118
+ };
119
+ const buildRankedDonut = (counts, options) => {
120
+ const total = counts.reduce((sum, count) => sum + count, 0);
121
+ const ranked = counts
122
+ .map((count, index) => ({ count, index }))
123
+ .sort((a, b) => b.count - a.count);
124
+ const top = ranked.slice(0, TOP_CLUSTERS);
125
+ const restSum = ranked
126
+ .slice(TOP_CLUSTERS)
127
+ .reduce((sum, item) => sum + item.count, 0);
128
+ const slices = top.map((item, index) => {
129
+ const name = options.sliceName(index + 1);
130
+ return {
131
+ name,
132
+ count: item.count,
133
+ countDisplay: `${item.count} ${options.countSuffix}`,
134
+ legendCategory: name,
135
+ };
136
+ });
137
+ if (restSum > 0) {
138
+ slices.push({
139
+ name: options.restName,
140
+ count: restSum,
141
+ countDisplay: `${restSum} ${options.countSuffix}`,
142
+ legendCategory: options.restName,
143
+ });
144
+ }
145
+ return {
146
+ chartData: slices,
147
+ legend: legendFor(slices.map((slice) => slice.legendCategory)),
148
+ title: `${total}`,
149
+ subTitle: options.subTitle,
150
+ };
151
+ };
152
+ const buildVmByCluster = (clusters) => {
153
+ const counts = readClusters(clusters).map(readVmTotal);
154
+ if (counts.length === 0) {
155
+ return EMPTY_CHART;
156
+ }
157
+ return buildRankedDonut(counts, {
158
+ sliceName: (rank) => `Cluster ${rank}`,
159
+ countSuffix: "VMs",
160
+ restName: REST_OF_CLUSTERS_LABEL,
161
+ subTitle: "VMs",
162
+ });
163
+ };
164
+ const buildDatacenterDistribution = (clustersPerDatacenter) => {
165
+ const counts = readCounts(clustersPerDatacenter);
166
+ if (counts.length === 0) {
167
+ return EMPTY_CHART;
168
+ }
169
+ return buildRankedDonut(counts, {
170
+ sliceName: (rank) => `Data center ${rank}`,
171
+ countSuffix: "clusters",
172
+ restName: REST_OF_DATACENTERS_LABEL,
173
+ subTitle: "Clusters",
174
+ });
175
+ };
176
+ const buildCpuOverCommitment = (clusters) => {
177
+ const ranked = readClusters(clusters)
178
+ .map((cluster) => parseCpuOverCommitment(readCpuOverCommitment(cluster)))
179
+ .filter((value) => Number.isFinite(value) && value >= 0)
180
+ .sort((a, b) => b - a)
181
+ .slice(0, TOP_CPU_RATIOS);
182
+ const slices = ranked.map((value, index) => {
183
+ const legendCategory = `Cluster ${index + 1}`;
184
+ const display = formatRatio(value);
185
+ return {
186
+ name: display,
187
+ count: value,
188
+ countDisplay: display,
189
+ legendCategory,
190
+ };
191
+ });
192
+ return {
193
+ chartData: slices,
194
+ legend: legendFor(slices.map((slice) => slice.legendCategory)),
195
+ title: "",
196
+ subTitle: "",
197
+ };
198
+ };
199
+ export const ClustersOverview = ({ clustersPerDatacenter, clusters, legendVariant = "html", }) => {
200
+ const [viewMode, setViewMode] = useState("vmByCluster");
201
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
202
+ const chart = useMemo(() => {
203
+ if (viewMode === "cpuOverCommitment") {
204
+ return buildCpuOverCommitment(clusters);
205
+ }
206
+ if (viewMode === "vmByCluster") {
207
+ return buildVmByCluster(clusters);
208
+ }
209
+ return buildDatacenterDistribution(clustersPerDatacenter);
210
+ }, [viewMode, clustersPerDatacenter, clusters]);
211
+ const chartTitle = `${CHART_TITLE} — ${VIEW_MODE_LABELS[viewMode]}`;
212
+ const emptyTitle = viewMode === "cpuOverCommitment"
213
+ ? REPORT_CARD_EMPTY_STATE_TITLES.cpuOvercommitment
214
+ : REPORT_CARD_EMPTY_STATE_TITLES.clusters;
215
+ return (_jsx(ChartExportSurface, { id: CHART_ID, title: chartTitle, exportViews: chartExportViewsFromLabels(CHART_TITLE, VIEW_MODE_LABELS), activeExportViewId: viewMode, onExportViewChange: (viewId) => {
216
+ if (isViewMode(viewId)) {
217
+ setViewMode(viewId);
218
+ }
219
+ }, children: _jsxs(Card, { className: dashboardStyles.card, children: [_jsx(CardTitle, { children: _jsxs(Flex, { className: clustersOverviewStyles.titleRow, justifyContent: { default: "justifyContentSpaceBetween" }, alignItems: { default: "alignItemsCenter" }, children: [_jsx(FlexItem, { children: _jsxs("div", { children: [_jsxs("div", { children: [_jsx(DatabaseIcon, {}), " ", CHART_TITLE] }), _jsx("div", { className: clustersOverviewStyles.cardSubtitle, children: viewMode === "dataCenterDistribution"
220
+ ? "Top 5 datacenters"
221
+ : "Top 5 clusters" })] }) }), _jsx(ChartHeaderActions, { chartId: CHART_ID, title: chartTitle, children: _jsx(Dropdown, { isOpen: isDropdownOpen, onSelect: (_event, value) => {
222
+ if (isViewMode(value)) {
223
+ setViewMode(value);
224
+ }
225
+ setIsDropdownOpen(false);
226
+ }, onOpenChange: setIsDropdownOpen, toggle: (toggleRef) => (_jsx(MenuToggle, { ref: toggleRef, onClick: () => setIsDropdownOpen((open) => !open), isExpanded: isDropdownOpen, className: clustersOverviewStyles.menuToggleMinWidth, children: VIEW_MODE_LABELS[viewMode] })), children: _jsxs(DropdownList, { children: [_jsx(DropdownItem, { value: "vmByCluster", children: VIEW_MODE_LABELS.vmByCluster }, "vmByCluster"), _jsx(DropdownItem, { value: "cpuOverCommitment", children: VIEW_MODE_LABELS.cpuOverCommitment }, "cpuOverCommitment"), _jsx(DropdownItem, { value: "dataCenterDistribution", children: VIEW_MODE_LABELS.dataCenterDistribution }, "dataCenterDistribution")] }) }) })] }) }), _jsx(CardBody, { className: dashboardStyles.cardBodyScrollable, children: chart.chartData.length === 0 ? (_jsx(CardEmptyState, { title: emptyTitle })) : viewMode === "cpuOverCommitment" ? (_jsxs(_Fragment, { children: [_jsx("div", { className: clustersOverviewStyles.cpuOvercommitBoxes, children: chart.chartData.map((item) => (_jsx("div", { className: clustersOverviewStyles.cpuOvercommitBox, style: { background: chart.legend[item.legendCategory] }, children: item.countDisplay }, `cpu-box-${item.legendCategory}`))) }), _jsx("div", { className: clustersOverviewStyles.cpuOvercommitLegend, children: chart.chartData.map((item) => (_jsxs("div", { className: clustersOverviewStyles.cpuOvercommitLegendItem, children: [_jsx("span", { className: clustersOverviewStyles.cpuOvercommitLegendSwatch, style: { background: chart.legend[item.legendCategory] } }), _jsxs("span", { className: clustersOverviewStyles.cpuOvercommitLegendText, children: [item.legendCategory, " (", item.countDisplay, ")"] })] }, `cpu-legend-${item.legendCategory}`))) })] })) : (_jsx(MigrationDonutChart, { legendVariant: legendVariant, data: chart.chartData, height: 300, width: 420, donutThickness: 18, titleFontSize: 34, legend: chart.legend, title: chart.title, subTitle: chart.subTitle, subTitleColor: "var(--pf-t--global--text--color--subtle)", tooltipLabelFormatter: ({ datum, percent }) => `${datum.countDisplay}\n${percent.toFixed(1)}%` })) })] }) }));
227
+ };
228
+ ClustersOverview.displayName = "ClustersOverview";
@@ -0,0 +1,12 @@
1
+ export declare const clustersOverviewStyles: {
2
+ titleRow: string;
3
+ cardSubtitle: string;
4
+ menuToggleMinWidth: string;
5
+ cpuOvercommitBoxes: string;
6
+ cpuOvercommitBox: string;
7
+ cpuOvercommitLegend: string;
8
+ cpuOvercommitLegendItem: string;
9
+ cpuOvercommitLegendSwatch: string;
10
+ cpuOvercommitLegendText: string;
11
+ };
12
+ //# sourceMappingURL=ClustersOverviewStyles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ClustersOverviewStyles.d.ts","sourceRoot":"","sources":["../../src/report/ClustersOverviewStyles.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,sBAAsB;;;;;;;;;;CA2GlC,CAAC"}
@@ -0,0 +1,101 @@
1
+ import { css } from "@emotion/css";
2
+ export const clustersOverviewStyles = {
3
+ titleRow: css `
4
+ width: 100%;
5
+ `,
6
+ cardSubtitle: css `
7
+ color: var(--pf-t--global--text--color--subtle);
8
+ font-size: 0.85rem;
9
+ `,
10
+ menuToggleMinWidth: css `
11
+ min-width: 250px;
12
+ `,
13
+ cpuOvercommitBoxes: css `
14
+ display: flex;
15
+ justify-content: center;
16
+ gap: 12px;
17
+ margin: 124px 0 16px;
18
+ flex-wrap: wrap;
19
+
20
+ @media (max-width: 1200px) {
21
+ margin: 72px 0 12px;
22
+ gap: 20px;
23
+ }
24
+
25
+ @media (max-width: 768px) {
26
+ margin: 40px 0 8px;
27
+ gap: 16px;
28
+ }
29
+ `,
30
+ cpuOvercommitBox: css `
31
+ color: #000;
32
+ min-width: 120px;
33
+ height: 64px;
34
+ border-radius: 12px;
35
+ display: flex;
36
+ align-items: center;
37
+ justify-content: center;
38
+ font-size: 22px;
39
+ padding: 0 16px;
40
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
41
+
42
+ @media (max-width: 768px) {
43
+ min-width: 96px;
44
+ height: 56px;
45
+ font-size: 18px;
46
+ padding: 0 12px;
47
+ border-radius: 10px;
48
+ }
49
+
50
+ @media (max-width: 480px) {
51
+ min-width: 88px;
52
+ height: 48px;
53
+ font-size: 16px;
54
+ border-radius: 8px;
55
+ }
56
+ `,
57
+ cpuOvercommitLegend: css `
58
+ display: flex;
59
+ justify-content: center;
60
+ gap: 6px 24px;
61
+ flex-wrap: wrap;
62
+ margin-top: 20%;
63
+
64
+ @media (max-width: 1200px) {
65
+ margin-top: 20%;
66
+ gap: 6px 20px;
67
+ }
68
+
69
+ @media (max-width: 768px) {
70
+ margin-top: 20%;
71
+ gap: 6px 16px;
72
+ }
73
+ `,
74
+ cpuOvercommitLegendItem: css `
75
+ display: flex;
76
+ align-items: center;
77
+ gap: 12px;
78
+ `,
79
+ cpuOvercommitLegendSwatch: css `
80
+ display: inline-block;
81
+ width: 12px;
82
+ height: 12px;
83
+ border-radius: 2px;
84
+
85
+ @media (max-width: 768px) {
86
+ width: 10px;
87
+ height: 10px;
88
+ }
89
+ `,
90
+ cpuOvercommitLegendText: css `
91
+ font-size: 16px;
92
+
93
+ @media (max-width: 768px) {
94
+ font-size: 16px;
95
+ }
96
+
97
+ @media (max-width: 480px) {
98
+ font-size: 14px;
99
+ }
100
+ `,
101
+ };
@@ -0,0 +1,19 @@
1
+ import type { FC } from "react";
2
+ import { type MigrationDonutChartLegendVariant } from "../charts/MigrationDonutChart.js";
3
+ export interface CpuAndMemoryOverviewProps {
4
+ cpuTierDistribution?: Record<string, number>;
5
+ memoryTierDistribution?: Record<string, number>;
6
+ memoryTotalGB?: number;
7
+ cpuTotalCores?: number;
8
+ /**
9
+ * Memory-tier slice click. The argument is the distribution key.
10
+ * Agent-ui maps it to a VM memory filter; ui-app omits it.
11
+ */
12
+ onTierSelect?: (tierLabel: string) => void;
13
+ /** Center-title click. Agent-ui opens the VM list with no extra filters. */
14
+ onTitleClick?: () => void;
15
+ /** `"html"` in agent-ui; ui-app passes `"chart"`. */
16
+ legendVariant?: MigrationDonutChartLegendVariant;
17
+ }
18
+ export declare const CpuAndMemoryOverview: FC<CpuAndMemoryOverviewProps>;
19
+ //# sourceMappingURL=CpuAndMemoryOverview.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CpuAndMemoryOverview.d.ts","sourceRoot":"","sources":["../../src/report/CpuAndMemoryOverview.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,EAAE,EAAO,MAAM,OAAO,CAAC;AAErC,OAAO,EAGL,KAAK,gCAAgC,EACtC,MAAM,kCAAkC,CAAC;AAQ1C,MAAM,WAAW,yBAAyB;IACxC,mBAAmB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,sBAAsB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,4EAA4E;IAC5E,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,qDAAqD;IACrD,aAAa,CAAC,EAAE,gCAAgC,CAAC;CAClD;AAuFD,eAAO,MAAM,oBAAoB,EAAE,EAAE,CAAC,yBAAyB,CAiJ9D,CAAC"}
@@ -0,0 +1,112 @@
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 { DataProcessorIcon } from "@patternfly/react-icons";
5
+ import { useMemo, useState } from "react";
6
+ import { MigrationDonutChart, } from "../charts/MigrationDonutChart.js";
7
+ import { CardEmptyState } from "./CardEmptyState.js";
8
+ import { ChartHeaderActions } from "./ChartDownloadButton.js";
9
+ import { ChartExportSurface } from "./ChartExportSurface.js";
10
+ import { chartExportViewsFromLabels } from "./chartExport.js";
11
+ import { REPORT_CARD_EMPTY_STATE_TITLES } from "./constants.js";
12
+ import { dashboardStyles } from "./dashboardStyles.js";
13
+ const VIEW_MODE_LABELS = {
14
+ memoryTiers: "VM distribution by memory size tier",
15
+ vcpuTiers: "VM distribution by vCPU count tier",
16
+ };
17
+ const VIEW_SUBTITLES = {
18
+ memoryTiers: "Memory size tiers",
19
+ vcpuTiers: "vCPU count tiers",
20
+ };
21
+ /**
22
+ * Categorical order shared by the agent-ui and ui-app CPU/memory donuts.
23
+ */
24
+ const TIER_COLORS = [
25
+ "#0066cc",
26
+ "#5e40be",
27
+ "#b6a6e9",
28
+ "#73c5c5",
29
+ "#b98412",
30
+ "#28a745",
31
+ "#f0ad4e",
32
+ "#d9534f",
33
+ "#009596",
34
+ "#6a6e73",
35
+ ];
36
+ const CHART_ID = "cpu-memory-overview";
37
+ const CHART_TITLE = "CPU & memory";
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: 290px;
44
+ `;
45
+ const titleRow = css `
46
+ width: 100%;
47
+ `;
48
+ const isViewMode = (value) => value === "memoryTiers" || value === "vcpuTiers";
49
+ const tierSortKey = (label) => {
50
+ const value = Number.parseInt(label.trim(), 10);
51
+ return Number.isFinite(value) ? value : Number.MAX_SAFE_INTEGER;
52
+ };
53
+ const hasUnit = (label, unit) => unit === "GB" ? /gb$/i.test(label) : /cores?$/i.test(label);
54
+ const slicesFromDistribution = (distribution, unit) => Object.entries(distribution ?? {})
55
+ .filter(([, count]) => Number(count) > 0)
56
+ .sort(([left], [right]) => tierSortKey(left) - tierSortKey(right))
57
+ .map(([tier, count]) => {
58
+ const trimmed = tier.trim();
59
+ const name = hasUnit(trimmed, unit) ? trimmed : `${trimmed} ${unit}`;
60
+ const vmCount = Number(count);
61
+ return {
62
+ name,
63
+ count: vmCount,
64
+ countDisplay: `${vmCount} VMs`,
65
+ legendCategory: tier,
66
+ };
67
+ });
68
+ const legendFor = (categories) => {
69
+ const legend = {};
70
+ categories.forEach((category, index) => {
71
+ const color = TIER_COLORS[index % TIER_COLORS.length];
72
+ if (color) {
73
+ legend[category] = color;
74
+ }
75
+ });
76
+ return legend;
77
+ };
78
+ export const CpuAndMemoryOverview = ({ cpuTierDistribution, memoryTierDistribution, memoryTotalGB, cpuTotalCores, onTierSelect, onTitleClick, legendVariant = "html", }) => {
79
+ const [viewMode, setViewMode] = useState("memoryTiers");
80
+ const [isDropdownOpen, setIsDropdownOpen] = useState(false);
81
+ const memorySlices = useMemo(() => slicesFromDistribution(memoryTierDistribution, "GB"), [memoryTierDistribution]);
82
+ const vcpuSlices = useMemo(() => slicesFromDistribution(cpuTierDistribution, "cores"), [cpuTierDistribution]);
83
+ const activeSlices = viewMode === "memoryTiers" ? memorySlices : vcpuSlices;
84
+ const legend = useMemo(() => legendFor(activeSlices.map((slice) => slice.legendCategory)), [activeSlices]);
85
+ const totalVMs = useMemo(() => activeSlices.reduce((sum, slice) => sum + slice.count, 0), [activeSlices]);
86
+ const chartTitle = `${CHART_TITLE} — ${VIEW_MODE_LABELS[viewMode]}`;
87
+ const subTitle = viewMode === "memoryTiers"
88
+ ? typeof memoryTotalGB === "number"
89
+ ? `${memoryTotalGB.toLocaleString()} GB`
90
+ : undefined
91
+ : typeof cpuTotalCores === "number"
92
+ ? `${cpuTotalCores.toLocaleString()} Cores`
93
+ : undefined;
94
+ const emptyTitle = viewMode === "memoryTiers"
95
+ ? REPORT_CARD_EMPTY_STATE_TITLES.memory
96
+ : REPORT_CARD_EMPTY_STATE_TITLES.cpu;
97
+ return (_jsx(ChartExportSurface, { id: CHART_ID, title: chartTitle, exportViews: chartExportViewsFromLabels(CHART_TITLE, VIEW_MODE_LABELS), activeExportViewId: viewMode, onExportViewChange: (viewId) => {
98
+ if (isViewMode(viewId)) {
99
+ setViewMode(viewId);
100
+ }
101
+ }, 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(DataProcessorIcon, {}), " ", CHART_TITLE] }), _jsx("div", { className: cardSubtitle, children: VIEW_SUBTITLES[viewMode] })] }) }), _jsx(ChartHeaderActions, { chartId: CHART_ID, title: chartTitle, children: _jsx(Dropdown, { isOpen: isDropdownOpen, onSelect: (_event, value) => {
102
+ if (isViewMode(value)) {
103
+ setViewMode(value);
104
+ }
105
+ setIsDropdownOpen(false);
106
+ }, 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: "memoryTiers", children: VIEW_MODE_LABELS.memoryTiers }, "memoryTiers"), _jsx(DropdownItem, { value: "vcpuTiers", children: VIEW_MODE_LABELS.vcpuTiers }, "vcpuTiers")] }) }) })] }) }), _jsx(CardBody, { className: dashboardStyles.cardBodyScrollable, children: activeSlices.length === 0 ? (_jsx(CardEmptyState, { title: emptyTitle })) : (_jsx(MigrationDonutChart, { legendVariant: legendVariant, data: activeSlices, legend: legend, height: 300, width: 420, donutThickness: 18, titleFontSize: 34, title: `${totalVMs} VMs`, subTitle: subTitle, subTitleColor: "var(--pf-t--global--text--color--subtle)", legendLabelFormatter: ({ x, countDisplay }) => `${x} (${countDisplay})`, tooltipLabelFormatter: ({ datum, percent }) => `${datum.countDisplay}\n${percent.toFixed(1)}%`, itemsPerRow: legendVariant === "chart"
107
+ ? Math.ceil(activeSlices.length / 2)
108
+ : undefined, labelFontSize: legendVariant === "chart" ? 18 : undefined, marginLeft: legendVariant === "chart" ? "52%" : undefined, onItemClick: viewMode === "memoryTiers" && onTierSelect
109
+ ? (item) => onTierSelect(item.legendCategory)
110
+ : undefined, onTitleClick: onTitleClick })) })] }) }));
111
+ };
112
+ CpuAndMemoryOverview.displayName = "CpuAndMemoryOverview";
@@ -8,6 +8,6 @@ export const FeatureStatusBadge = ({ status }) => {
8
8
  if (status === "enabled") {
9
9
  return (_jsx(Label, { color: "green", isCompact: true, icon: _jsx(CheckCircleIcon, {}), children: "Enabled" }));
10
10
  }
11
- return (_jsx(Label, { color: "grey", isCompact: true, icon: _jsx(TimesCircleIcon, {}), children: "Disabled" }));
11
+ return (_jsx(Label, { variant: "outline", isCompact: true, icon: _jsx(TimesCircleIcon, {}), children: "Disabled" }));
12
12
  };
13
13
  FeatureStatusBadge.displayName = "FeatureStatusBadge";