@openshift-migration-advisor/shared-components 0.27.0-6438cd962b5f → 0.27.0-ad43dbdf05ab
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/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/captureChartElement.d.ts.map +1 -1
- package/dist/report/captureChartElement.js +28 -8
- package/dist/report/chartExport.d.ts +7 -0
- package/dist/report/chartExport.d.ts.map +1 -1
- package/dist/report/chartExport.js +20 -0
- package/dist/report/index.d.ts +2 -0
- package/dist/report/index.d.ts.map +1 -1
- package/dist/report/index.js +2 -0
- package/package.json +2 -2
- package/src/report/ClustersOverview.tsx +451 -0
- package/src/report/ClustersOverviewStyles.ts +110 -0
- package/src/report/CpuAndMemoryOverview.tsx +277 -0
- package/src/report/__tests__/ChartExportProvider.test.tsx +40 -0
- package/src/report/__tests__/ClustersOverview.test.tsx +241 -0
- package/src/report/__tests__/CpuAndMemoryOverview.test.tsx +200 -0
- package/src/report/__tests__/captureChartElement.test.ts +73 -0
- package/src/report/__tests__/chartExportPackagers.test.ts +17 -0
- package/src/report/captureChartElement.ts +37 -7
- package/src/report/chartExport.ts +22 -0
- package/src/report/index.ts +9 -0
|
@@ -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";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"captureChartElement.d.ts","sourceRoot":"","sources":["../../src/report/captureChartElement.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"captureChartElement.d.ts","sourceRoot":"","sources":["../../src/report/captureChartElement.ts"],"names":[],"mappings":"AAsCA;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,IAAI,CAqD3E;AAuQD,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,iBAAiB,CAAC,CAoC5B"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { t_color_gray_20, t_color_white, t_global_text_color_regular, } from "@patternfly/react-tokens";
|
|
1
2
|
import html2canvas from "html2canvas-pro";
|
|
2
|
-
import { CHART_EXPORT_CAPTURING_ATTR, CHART_EXPORT_SCROLL_ATTR, shouldIgnoreChartExportElement, } from "./chartExport.js";
|
|
3
|
+
import { applyLightThemeToDocument, CHART_EXPORT_CAPTURING_ATTR, CHART_EXPORT_SCROLL_ATTR, shouldIgnoreChartExportElement, } from "./chartExport.js";
|
|
3
4
|
/** CSS-pixel scale; 2x made zip-all of ~15 live cards too slow. */
|
|
4
5
|
const CHART_CAPTURE_SCALE = 1;
|
|
5
6
|
const HIDDEN_LAYOUT_STYLE_KEYS = [
|
|
@@ -71,6 +72,10 @@ export function revealHiddenChartAncestors(element) {
|
|
|
71
72
|
const PADDED_CAPTURE_SELECTOR = ".pf-v6-c-card__title, .pf-v6-c-card__header, .pf-v6-c-card__body";
|
|
72
73
|
const CARD_CAPTURE_SELECTOR = ".pf-v6-c-card";
|
|
73
74
|
const TRANSPARENT_FILLS = new Set(["", "transparent", "rgba(0, 0, 0, 0)"]);
|
|
75
|
+
/** PatternFly light-theme chrome. html2canvas inlines live computed SVG styles. */
|
|
76
|
+
const LIGHT_EXPORT_BACKGROUND = t_color_white.value;
|
|
77
|
+
const LIGHT_EXPORT_BORDER = t_color_gray_20.value;
|
|
78
|
+
const LIGHT_EXPORT_TEXT = t_global_text_color_regular.value;
|
|
74
79
|
function queryCards(root) {
|
|
75
80
|
const cards = Array.from(root.querySelectorAll(CARD_CAPTURE_SELECTOR));
|
|
76
81
|
if (root.matches(CARD_CAPTURE_SELECTOR)) {
|
|
@@ -112,16 +117,29 @@ function readCardBorder(source) {
|
|
|
112
117
|
}
|
|
113
118
|
return { width, style, color };
|
|
114
119
|
}
|
|
115
|
-
function applyCardChrome(source, dest) {
|
|
120
|
+
function applyCardChrome(source, dest, colors) {
|
|
116
121
|
const computed = window.getComputedStyle(source);
|
|
117
122
|
dest.style.borderRadius = computed.borderRadius;
|
|
118
|
-
dest.style.backgroundColor = resolvedFill(computed.backgroundColor);
|
|
119
123
|
dest.style.overflow = "hidden";
|
|
124
|
+
dest.style.backgroundColor =
|
|
125
|
+
colors === "light"
|
|
126
|
+
? LIGHT_EXPORT_BACKGROUND
|
|
127
|
+
: resolvedFill(computed.backgroundColor);
|
|
120
128
|
const border = readCardBorder(source);
|
|
121
129
|
if (border) {
|
|
122
130
|
dest.style.borderWidth = border.width;
|
|
123
131
|
dest.style.borderStyle = border.style;
|
|
124
|
-
dest.style.borderColor =
|
|
132
|
+
dest.style.borderColor =
|
|
133
|
+
colors === "light" ? LIGHT_EXPORT_BORDER : border.color;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function applyLightThemeSvgText(root) {
|
|
137
|
+
for (const node of root.querySelectorAll("text, tspan")) {
|
|
138
|
+
if (!(node instanceof SVGElement)) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
node.style.fill = LIGHT_EXPORT_TEXT;
|
|
142
|
+
node.style.color = LIGHT_EXPORT_TEXT;
|
|
125
143
|
}
|
|
126
144
|
}
|
|
127
145
|
function hideCardBeforePseudo() {
|
|
@@ -162,7 +180,7 @@ function copyCardChrome(node) {
|
|
|
162
180
|
height: node.style.height,
|
|
163
181
|
maxHeight: node.style.maxHeight,
|
|
164
182
|
};
|
|
165
|
-
applyCardChrome(node, node);
|
|
183
|
+
applyCardChrome(node, node, "computed");
|
|
166
184
|
node.style.minHeight = "0px";
|
|
167
185
|
node.style.height = "auto";
|
|
168
186
|
node.style.maxHeight = "none";
|
|
@@ -174,7 +192,7 @@ function applyComputedStylesToClone(sourceRoot, clonedRoot) {
|
|
|
174
192
|
sourceCards.forEach((source, index) => {
|
|
175
193
|
const dest = cloneCards[index];
|
|
176
194
|
if (dest) {
|
|
177
|
-
applyCardChrome(source, dest);
|
|
195
|
+
applyCardChrome(source, dest, "light");
|
|
178
196
|
}
|
|
179
197
|
});
|
|
180
198
|
const sources = sourceRoot.querySelectorAll(PADDED_CAPTURE_SELECTOR);
|
|
@@ -264,14 +282,16 @@ export async function captureChartElement(element) {
|
|
|
264
282
|
}
|
|
265
283
|
return html2canvas(element, {
|
|
266
284
|
useCORS: true,
|
|
267
|
-
backgroundColor:
|
|
285
|
+
backgroundColor: LIGHT_EXPORT_BACKGROUND,
|
|
268
286
|
logging: false,
|
|
269
287
|
scale: CHART_CAPTURE_SCALE,
|
|
270
288
|
imageTimeout: 0,
|
|
271
289
|
ignoreElements: (node) => node instanceof Element &&
|
|
272
290
|
shouldIgnoreChartExportElement(node, element),
|
|
273
|
-
onclone: (
|
|
291
|
+
onclone: (clonedDoc, clonedElement) => {
|
|
292
|
+
applyLightThemeToDocument(clonedDoc);
|
|
274
293
|
applyComputedStylesToClone(element, clonedElement);
|
|
294
|
+
applyLightThemeSvgText(clonedElement);
|
|
275
295
|
},
|
|
276
296
|
});
|
|
277
297
|
}
|
|
@@ -56,6 +56,13 @@ export type PdfTextPage = {
|
|
|
56
56
|
};
|
|
57
57
|
export declare function releaseCanvas(canvas: HTMLCanvasElement): void;
|
|
58
58
|
export declare function waitForChartExportPaint(): Promise<void>;
|
|
59
|
+
/** PatternFly applies dark tokens when this class is on `:root`. */
|
|
60
|
+
export declare const PATTERNFLY_DARK_THEME_CLASSES: readonly ["pf-v6-theme-dark", "pf-v5-theme-dark"];
|
|
61
|
+
/**
|
|
62
|
+
* Force PatternFly light tokens on a document (the html2canvas clone).
|
|
63
|
+
* Does not touch the live page.
|
|
64
|
+
*/
|
|
65
|
+
export declare function applyLightThemeToDocument(root: Document): void;
|
|
59
66
|
export declare function chartExportViewsFromLabels(titlePrefix: string, labels: Record<string, string>): ChartExportView[];
|
|
60
67
|
export declare function toBulkChartCaptureSources(charts: RegisteredChart[], capture: (element: HTMLElement) => Promise<HTMLCanvasElement>, prepareElement?: (element: HTMLElement) => () => void): ChartCaptureSource[];
|
|
61
68
|
export declare function restoreChartExportViews(charts: RegisteredChart[]): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chartExport.d.ts","sourceRoot":"","sources":["../../src/report/chartExport.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chartExport.d.ts","sourceRoot":"","sources":["../../src/report/chartExport.ts"],"names":[],"mappings":"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"}
|
|
@@ -25,6 +25,26 @@ export function waitForChartExportPaint() {
|
|
|
25
25
|
});
|
|
26
26
|
});
|
|
27
27
|
}
|
|
28
|
+
/** PatternFly applies dark tokens when this class is on `:root`. */
|
|
29
|
+
export const PATTERNFLY_DARK_THEME_CLASSES = [
|
|
30
|
+
"pf-v6-theme-dark",
|
|
31
|
+
"pf-v5-theme-dark",
|
|
32
|
+
];
|
|
33
|
+
/**
|
|
34
|
+
* Force PatternFly light tokens on a document (the html2canvas clone).
|
|
35
|
+
* Does not touch the live page.
|
|
36
|
+
*/
|
|
37
|
+
export function applyLightThemeToDocument(root) {
|
|
38
|
+
for (const node of [root.documentElement, root.body]) {
|
|
39
|
+
if (!node) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
for (const className of PATTERNFLY_DARK_THEME_CLASSES) {
|
|
43
|
+
node.classList.remove(className);
|
|
44
|
+
}
|
|
45
|
+
node.style.colorScheme = "light";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
28
48
|
export function chartExportViewsFromLabels(titlePrefix, labels) {
|
|
29
49
|
return Object.entries(labels).map(([id, label]) => ({
|
|
30
50
|
id,
|