@openshift-migration-advisor/shared-components 0.27.0 → 0.28.0-09381b4d22e5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/charts/MigrationDonutChart.d.ts.map +1 -1
- package/dist/charts/MigrationDonutChart.js +7 -0
- package/dist/report/ChartExportProvider.d.ts +4 -3
- package/dist/report/ChartExportProvider.d.ts.map +1 -1
- package/dist/report/ChartExportProvider.js +7 -4
- package/dist/report/ClustersOverview.d.ts +24 -0
- package/dist/report/ClustersOverview.d.ts.map +1 -0
- package/dist/report/ClustersOverview.js +228 -0
- package/dist/report/ClustersOverviewStyles.d.ts +12 -0
- package/dist/report/ClustersOverviewStyles.d.ts.map +1 -0
- package/dist/report/ClustersOverviewStyles.js +101 -0
- package/dist/report/CpuAndMemoryOverview.d.ts +19 -0
- package/dist/report/CpuAndMemoryOverview.d.ts.map +1 -0
- package/dist/report/CpuAndMemoryOverview.js +112 -0
- package/dist/report/FeatureStatusBadge.js +1 -1
- package/dist/report/ReportExportMenu.d.ts +1 -0
- package/dist/report/ReportExportMenu.d.ts.map +1 -1
- package/dist/report/ReportExportMenu.js +38 -4
- package/dist/report/StorageOverview.d.ts +27 -0
- package/dist/report/StorageOverview.d.ts.map +1 -0
- package/dist/report/StorageOverview.js +319 -0
- package/dist/report/VMMigrationStatus.d.ts +33 -0
- package/dist/report/VMMigrationStatus.d.ts.map +1 -0
- package/dist/report/VMMigrationStatus.js +129 -0
- package/dist/report/captureChartElement.d.ts +6 -1
- package/dist/report/captureChartElement.d.ts.map +1 -1
- package/dist/report/captureChartElement.js +41 -12
- package/dist/report/chartExport.d.ts +23 -0
- package/dist/report/chartExport.d.ts.map +1 -1
- package/dist/report/chartExport.js +21 -1
- package/dist/report/chartExportContext.d.ts +6 -2
- package/dist/report/chartExportContext.d.ts.map +1 -1
- package/dist/report/dashboardStyles.d.ts +6 -0
- package/dist/report/dashboardStyles.d.ts.map +1 -1
- package/dist/report/dashboardStyles.js +7 -4
- package/dist/report/index.d.ts +7 -3
- package/dist/report/index.d.ts.map +1 -1
- package/dist/report/index.js +4 -0
- package/dist/report/pdfExport.d.ts +2 -2
- package/dist/report/pdfExport.d.ts.map +1 -1
- package/dist/report/pdfExport.js +52 -3
- package/dist/report/reportExportOptions.d.ts +13 -2
- package/dist/report/reportExportOptions.d.ts.map +1 -1
- package/dist/report/reportExportOptions.js +13 -5
- package/package.json +2 -2
- package/src/charts/MigrationDonutChart.tsx +9 -0
- package/src/report/ChartExportProvider.tsx +18 -6
- package/src/report/ClustersOverview.tsx +451 -0
- package/src/report/ClustersOverviewStyles.ts +110 -0
- package/src/report/CpuAndMemoryOverview.tsx +277 -0
- package/src/report/FeatureStatusBadge.tsx +1 -1
- package/src/report/ReportExportMenu.tsx +69 -13
- package/src/report/StorageOverview.tsx +629 -0
- package/src/report/VMMigrationStatus.tsx +358 -0
- package/src/report/__tests__/ChartExportProvider.test.tsx +110 -2
- package/src/report/__tests__/ClustersOverview.test.tsx +241 -0
- package/src/report/__tests__/CpuAndMemoryOverview.test.tsx +200 -0
- package/src/report/__tests__/StorageOverview.test.tsx +223 -0
- package/src/report/__tests__/VMMigrationStatus.test.tsx +178 -0
- package/src/report/__tests__/captureChartElement.test.ts +120 -0
- package/src/report/__tests__/chartExportPackagers.test.ts +75 -0
- package/src/report/captureChartElement.ts +55 -10
- package/src/report/chartExport.ts +41 -1
- package/src/report/chartExportContext.tsx +9 -1
- package/src/report/dashboardStyles.ts +7 -4
- package/src/report/index.ts +24 -0
- package/src/report/pdfExport.ts +81 -2
- package/src/report/reportExportOptions.ts +28 -5
|
@@ -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;
|
|
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?:
|
|
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;
|
|
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, {
|
|
11
|
+
return (_jsx(Label, { variant: "outline", isCompact: true, icon: _jsx(TimesCircleIcon, {}), children: "Disabled" }));
|
|
12
12
|
};
|
|
13
13
|
FeatureStatusBadge.displayName = "FeatureStatusBadge";
|