@openshift-migration-advisor/shared-components 0.27.0 → 0.28.0-9da6a7613f16
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/StorageOverview.d.ts +27 -0
- package/dist/report/StorageOverview.d.ts.map +1 -0
- package/dist/report/StorageOverview.js +319 -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 +5 -2
- package/dist/report/index.d.ts.map +1 -1
- package/dist/report/index.js +3 -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/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/StorageOverview.tsx +629 -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__/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 +17 -0
- package/src/report/pdfExport.ts +81 -2
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { FC } from "react";
|
|
2
|
+
import { type MigrationDonutChartLegendVariant } from "../charts/MigrationDonutChart.js";
|
|
3
|
+
export interface StorageDiskTierData {
|
|
4
|
+
vmCount?: number;
|
|
5
|
+
totalSizeTB?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface StorageOverviewProps {
|
|
8
|
+
diskSizeTier?: Record<string, StorageDiskTierData>;
|
|
9
|
+
diskTypes?: Record<string, StorageDiskTierData>;
|
|
10
|
+
totalVMs?: number;
|
|
11
|
+
totalWithSharedDisks?: number;
|
|
12
|
+
/**
|
|
13
|
+
* Disk-tier slice / center-title click. The argument is the tier's parsed
|
|
14
|
+
* disk-size range (in MB). Agent-ui maps it to a VM disk filter; ui-app omits
|
|
15
|
+
* it. Called with `undefined` for the center-title click (no tier filter).
|
|
16
|
+
*/
|
|
17
|
+
onDiskTierSelect?: (diskRange: {
|
|
18
|
+
min: number;
|
|
19
|
+
max?: number;
|
|
20
|
+
}) => void;
|
|
21
|
+
/** Center-title click. Agent-ui opens the VM list with no extra filters. */
|
|
22
|
+
onTitleClick?: () => void;
|
|
23
|
+
/** `"html"` in agent-ui; ui-app passes `"chart"`. */
|
|
24
|
+
legendVariant?: MigrationDonutChartLegendVariant;
|
|
25
|
+
}
|
|
26
|
+
export declare const StorageOverview: FC<StorageOverviewProps>;
|
|
27
|
+
//# sourceMappingURL=StorageOverview.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StorageOverview.d.ts","sourceRoot":"","sources":["../../src/report/StorageOverview.tsx"],"names":[],"mappings":"AAyBA,OAAO,KAAK,EAAE,EAAE,EAAO,MAAM,OAAO,CAAC;AAErC,OAAO,EAGL,KAAK,gCAAgC,EACtC,MAAM,kCAAkC,CAAC;AAQ1C,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,oBAAoB;IACnC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IACnD,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;IAChD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,CAAC,SAAS,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACtE,4EAA4E;IAC5E,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,qDAAqD;IACrD,aAAa,CAAC,EAAE,gCAAgC,CAAC;CAClD;AA0TD,eAAO,MAAM,eAAe,EAAE,EAAE,CAAC,oBAAoB,CA6PpD,CAAC"}
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Card, CardBody, CardTitle, Content, Dropdown, DropdownItem, DropdownList, Flex, FlexItem, MenuToggle, } from "@patternfly/react-core";
|
|
3
|
+
import { DatabaseIcon } from "@patternfly/react-icons";
|
|
4
|
+
import { chart_color_blue_300, chart_color_green_300, chart_color_orange_200, chart_color_purple_100, chart_color_purple_300, chart_color_red_orange_300, chart_color_teal_200, chart_color_teal_300, 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 { 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
|
+
totalSize: "Total disk size by tier",
|
|
15
|
+
vmCount: "VM count by disk size tier",
|
|
16
|
+
vmCountByDiskType: "VM count by disk type",
|
|
17
|
+
sharedDisks: "Shared disks VS. No shared disks",
|
|
18
|
+
};
|
|
19
|
+
const CHART_ID = "storage-overview";
|
|
20
|
+
const CHART_TITLE = "Storage";
|
|
21
|
+
const TIER_CONFIG = {
|
|
22
|
+
Easy: { order: 0, label: "0-10 TB", legendCategory: "Easy" },
|
|
23
|
+
Medium: { order: 1, label: "11-20 TB", legendCategory: "Medium" },
|
|
24
|
+
Hard: { order: 2, label: "21-50 TB", legendCategory: "Hard" },
|
|
25
|
+
White: { order: 3, label: "> 50 TB", legendCategory: "White glove" },
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Categorical order shared by the agent-ui and ui-app storage donuts, mapped
|
|
29
|
+
* onto PatternFly `chart_color_*` tokens.
|
|
30
|
+
*/
|
|
31
|
+
const COLOR_PALETTE = [
|
|
32
|
+
chart_color_blue_300.value,
|
|
33
|
+
chart_color_purple_300.value,
|
|
34
|
+
chart_color_purple_100.value,
|
|
35
|
+
chart_color_teal_300.value,
|
|
36
|
+
chart_color_yellow_400.value,
|
|
37
|
+
chart_color_green_300.value,
|
|
38
|
+
chart_color_orange_200.value,
|
|
39
|
+
chart_color_red_orange_300.value,
|
|
40
|
+
];
|
|
41
|
+
const DISK_TYPE_BAR_COLORS = [
|
|
42
|
+
chart_color_blue_300.value,
|
|
43
|
+
chart_color_purple_300.value,
|
|
44
|
+
chart_color_purple_100.value,
|
|
45
|
+
chart_color_yellow_400.value,
|
|
46
|
+
chart_color_teal_200.value,
|
|
47
|
+
];
|
|
48
|
+
const SHARED_DISKS_COLORS = {
|
|
49
|
+
"With shared disks": chart_color_yellow_400.value,
|
|
50
|
+
"No shared disks": chart_color_blue_300.value,
|
|
51
|
+
};
|
|
52
|
+
const DISK_TYPE_PREFERRED_ORDER = ["VMFS", "NFS", "vSAN", "RDM", "vVols"];
|
|
53
|
+
// --- Disk-tier label parsing (self-contained copy so the shared card does not
|
|
54
|
+
// depend on agent-ui internals). Used for both tier ordering and drill-down. ---
|
|
55
|
+
const MB_IN_GIB = 1024;
|
|
56
|
+
const MB_IN_TIB = 1024 * 1024;
|
|
57
|
+
const diskSizeRanges = [
|
|
58
|
+
{ label: "0-100GiB", min: 0, max: 100 * MB_IN_GIB },
|
|
59
|
+
{ label: "100-500GiB", min: 100 * MB_IN_GIB, max: 500 * MB_IN_GIB },
|
|
60
|
+
{ label: "500GiB-1TiB", min: 500 * MB_IN_GIB, max: MB_IN_TIB },
|
|
61
|
+
{ label: "1-2TiB", min: MB_IN_TIB, max: 2 * MB_IN_TIB },
|
|
62
|
+
{ label: "2-5TiB", min: 2 * MB_IN_TIB, max: 5 * MB_IN_TIB },
|
|
63
|
+
];
|
|
64
|
+
const legacyDiskSizeRanges = [
|
|
65
|
+
{ label: "0-10 TB", min: 0, max: 10 * MB_IN_TIB },
|
|
66
|
+
{ label: "11-20 TB", min: 10 * MB_IN_TIB + 1, max: 20 * MB_IN_TIB },
|
|
67
|
+
{ label: "21-50 TB", min: 20 * MB_IN_TIB + 1, max: 50 * MB_IN_TIB },
|
|
68
|
+
{ label: "50+ TB", min: 50 * MB_IN_TIB + 1, max: undefined },
|
|
69
|
+
];
|
|
70
|
+
const sizeValueToMB = (value, unit) => {
|
|
71
|
+
const amount = Number.parseFloat(value);
|
|
72
|
+
if (!Number.isFinite(amount)) {
|
|
73
|
+
return Number.NaN;
|
|
74
|
+
}
|
|
75
|
+
switch (unit.toLowerCase()) {
|
|
76
|
+
case "gib":
|
|
77
|
+
case "gb":
|
|
78
|
+
return amount * MB_IN_GIB;
|
|
79
|
+
case "tib":
|
|
80
|
+
case "tb":
|
|
81
|
+
return amount * MB_IN_TIB;
|
|
82
|
+
case "mib":
|
|
83
|
+
case "mb":
|
|
84
|
+
return amount;
|
|
85
|
+
default:
|
|
86
|
+
return Number.NaN;
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
/** Parses inventory disk tier labels (GiB/TiB or legacy TB buckets) into MB ranges. */
|
|
90
|
+
const parseDiskTierLabelToRange = (label) => {
|
|
91
|
+
const normalized = label.trim();
|
|
92
|
+
if (!normalized) {
|
|
93
|
+
return undefined;
|
|
94
|
+
}
|
|
95
|
+
const predefined = diskSizeRanges.find((range) => range.label === normalized) ??
|
|
96
|
+
legacyDiskSizeRanges.find((range) => range.label === normalized ||
|
|
97
|
+
(normalized === "> 50 TB" && range.label === "50+ TB"));
|
|
98
|
+
if (predefined) {
|
|
99
|
+
return { min: predefined.min, max: predefined.max };
|
|
100
|
+
}
|
|
101
|
+
const mixedUnitRange = /^(\d+(?:\.\d+)?)\s*(GiB|TiB|GB|TB|MIB|MB)\s*-\s*(\d+(?:\.\d+)?)\s*(GiB|TiB|GB|TB|MIB|MB)$/i.exec(normalized);
|
|
102
|
+
if (mixedUnitRange) {
|
|
103
|
+
const min = sizeValueToMB(mixedUnitRange[1], mixedUnitRange[2]);
|
|
104
|
+
const max = sizeValueToMB(mixedUnitRange[3], mixedUnitRange[4]);
|
|
105
|
+
if (Number.isFinite(min) && Number.isFinite(max)) {
|
|
106
|
+
return { min: Math.floor(min), max: Math.floor(max) };
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const singleUnitRange = /^(\d+(?:\.\d+)?)\s*-\s*(\d+(?:\.\d+)?)\s*(GiB|TiB|GB|TB|MIB|MB)$/i.exec(normalized);
|
|
110
|
+
if (singleUnitRange) {
|
|
111
|
+
const min = sizeValueToMB(singleUnitRange[1], singleUnitRange[3]);
|
|
112
|
+
const max = sizeValueToMB(singleUnitRange[2], singleUnitRange[3]);
|
|
113
|
+
if (Number.isFinite(min) && Number.isFinite(max)) {
|
|
114
|
+
return { min: Math.floor(min), max: Math.floor(max) };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const openEndedRange = /^(\d+(?:\.\d+)?)\s*(GiB|TiB|GB|TB|MIB|MB)?\s*\+$/i.exec(normalized);
|
|
118
|
+
if (openEndedRange) {
|
|
119
|
+
const unit = openEndedRange[2] || "TB";
|
|
120
|
+
const min = sizeValueToMB(openEndedRange[1], unit);
|
|
121
|
+
if (Number.isFinite(min)) {
|
|
122
|
+
const minMB = Math.floor(min);
|
|
123
|
+
return { min: minMB > 0 ? minMB + 1 : minMB, max: undefined };
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return undefined;
|
|
127
|
+
};
|
|
128
|
+
const buildTierChartData = (summary, selector) => {
|
|
129
|
+
if (!summary) {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
const getTierPrefix = (key) => {
|
|
133
|
+
const normalizedKey = key.trim().toLowerCase();
|
|
134
|
+
for (const prefix of Object.keys(TIER_CONFIG)) {
|
|
135
|
+
if (normalizedKey.startsWith(prefix.toLowerCase())) {
|
|
136
|
+
return prefix;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
};
|
|
141
|
+
return Object.entries(summary)
|
|
142
|
+
.map(([key, tier]) => {
|
|
143
|
+
const prefix = getTierPrefix(key);
|
|
144
|
+
const display = prefix
|
|
145
|
+
? TIER_CONFIG[prefix]
|
|
146
|
+
: { label: key, legendCategory: key };
|
|
147
|
+
const { count, countDisplay } = selector(tier);
|
|
148
|
+
const label = display.label;
|
|
149
|
+
return {
|
|
150
|
+
name: label,
|
|
151
|
+
count,
|
|
152
|
+
countDisplay,
|
|
153
|
+
legendCategory: display.legendCategory,
|
|
154
|
+
diskRange: parseDiskTierLabelToRange(key) ?? parseDiskTierLabelToRange(label),
|
|
155
|
+
sortOrder: parseDiskTierLabelToRange(key)?.min ??
|
|
156
|
+
parseDiskTierLabelToRange(label)?.min ??
|
|
157
|
+
(prefix ? TIER_CONFIG[prefix].order : 999),
|
|
158
|
+
};
|
|
159
|
+
})
|
|
160
|
+
.sort((a, b) => a.sortOrder - b.sortOrder)
|
|
161
|
+
.map(({ sortOrder: _sortOrder, ...datum }) => datum);
|
|
162
|
+
};
|
|
163
|
+
const getContrastColor = (hexColor) => {
|
|
164
|
+
const hex = hexColor.replace("#", "");
|
|
165
|
+
const r = Number.parseInt(hex.substring(0, 2), 16);
|
|
166
|
+
const g = Number.parseInt(hex.substring(2, 4), 16);
|
|
167
|
+
const b = Number.parseInt(hex.substring(4, 6), 16);
|
|
168
|
+
const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
169
|
+
return luminance > 0.5 ? "#000" : "#fff";
|
|
170
|
+
};
|
|
171
|
+
const DiskTypeBarChart = ({ data, colors }) => {
|
|
172
|
+
const maxCount = useMemo(() => (data.length > 0 ? Math.max(...data.map((d) => d.count)) : 0), [data]);
|
|
173
|
+
if (data.length === 0) {
|
|
174
|
+
return _jsx(CardEmptyState, { title: REPORT_CARD_EMPTY_STATE_TITLES.diskTypes });
|
|
175
|
+
}
|
|
176
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: dashboardStyles.storageChartWrapper, children: _jsx(Flex, { direction: { default: "row" }, alignItems: { default: "alignItemsFlexEnd" }, justifyContent: { default: "justifyContentCenter" }, spaceItems: { default: "spaceItemsMd" }, style: { height: "250px", width: "100%" }, children: data.map((item, index) => {
|
|
177
|
+
const heightPercentage = maxCount > 0 ? (item.count / maxCount) * 100 : 0;
|
|
178
|
+
const minHeight = item.count > 0 ? 10 : 0;
|
|
179
|
+
const barColor = colors[index % colors.length];
|
|
180
|
+
const textColor = getContrastColor(barColor);
|
|
181
|
+
return (_jsxs(Flex, { direction: { default: "column" }, alignItems: { default: "alignItemsCenter" }, spaceItems: { default: "spaceItemsSm" }, style: { flex: "1", maxWidth: "120px" }, children: [_jsx(FlexItem, { style: {
|
|
182
|
+
height: "200px",
|
|
183
|
+
display: "flex",
|
|
184
|
+
alignItems: "flex-end",
|
|
185
|
+
width: "100%",
|
|
186
|
+
justifyContent: "center",
|
|
187
|
+
}, children: _jsx("div", { style: {
|
|
188
|
+
width: "40px",
|
|
189
|
+
height: `${Math.max(heightPercentage, minHeight)}%`,
|
|
190
|
+
backgroundColor: barColor,
|
|
191
|
+
transition: "height 0.3s ease",
|
|
192
|
+
borderRadius: "4px 4px 0 0",
|
|
193
|
+
display: "flex",
|
|
194
|
+
alignItems: "flex-start",
|
|
195
|
+
justifyContent: "center",
|
|
196
|
+
paddingTop: "4px",
|
|
197
|
+
}, title: `${item.name}: ${item.count} VMs`, children: _jsx(Content, { component: "small", style: {
|
|
198
|
+
color: textColor,
|
|
199
|
+
fontWeight: "bold",
|
|
200
|
+
fontSize: "10px",
|
|
201
|
+
}, children: item.count }) }) }), _jsx(FlexItem, { children: _jsx(Content, { component: "small", style: {
|
|
202
|
+
fontSize: "12px",
|
|
203
|
+
textAlign: "center",
|
|
204
|
+
wordBreak: "break-word",
|
|
205
|
+
}, children: item.name }) })] }, item.name));
|
|
206
|
+
}) }) }), _jsx(Content, { component: "small", className: dashboardStyles.storageTotalsNote, children: "Totals may exceed the unique VM count because individual VMs can have multiple disk types" })] }));
|
|
207
|
+
};
|
|
208
|
+
const isViewMode = (value) => value === "totalSize" ||
|
|
209
|
+
value === "vmCount" ||
|
|
210
|
+
value === "vmCountByDiskType" ||
|
|
211
|
+
value === "sharedDisks";
|
|
212
|
+
export const StorageOverview = ({ diskSizeTier = {}, diskTypes = {}, totalVMs, totalWithSharedDisks, onDiskTierSelect, onTitleClick, legendVariant = "html", }) => {
|
|
213
|
+
const [viewMode, setViewMode] = useState("vmCount");
|
|
214
|
+
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
|
215
|
+
const totals = useMemo(() => Object.values(diskSizeTier).reduce((acc, tier) => ({
|
|
216
|
+
totalSize: acc.totalSize + (tier?.totalSizeTB || 0),
|
|
217
|
+
totalVMs: acc.totalVMs + (tier?.vmCount || 0),
|
|
218
|
+
}), { totalSize: 0, totalVMs: 0 }), [diskSizeTier]);
|
|
219
|
+
const chartData = useMemo(() => buildTierChartData(diskSizeTier, (tier) => {
|
|
220
|
+
const count = viewMode === "totalSize"
|
|
221
|
+
? tier?.totalSizeTB || 0
|
|
222
|
+
: tier?.vmCount || 0;
|
|
223
|
+
return {
|
|
224
|
+
count,
|
|
225
|
+
countDisplay: viewMode === "totalSize" ? `${count.toFixed(2)}` : `${count}`,
|
|
226
|
+
};
|
|
227
|
+
}), [diskSizeTier, viewMode]);
|
|
228
|
+
const diskTypeChartData = useMemo(() => {
|
|
229
|
+
if (Object.keys(diskTypes).length === 0) {
|
|
230
|
+
return [];
|
|
231
|
+
}
|
|
232
|
+
const orderIndex = (name) => {
|
|
233
|
+
const idx = DISK_TYPE_PREFERRED_ORDER.indexOf(name);
|
|
234
|
+
return idx === -1 ? Number.MAX_SAFE_INTEGER : idx;
|
|
235
|
+
};
|
|
236
|
+
return Object.entries(diskTypes)
|
|
237
|
+
.map(([diskTypeName, summary]) => ({
|
|
238
|
+
name: diskTypeName,
|
|
239
|
+
count: summary?.vmCount || 0,
|
|
240
|
+
}))
|
|
241
|
+
.sort((a, b) => orderIndex(a.name) - orderIndex(b.name));
|
|
242
|
+
}, [diskTypes]);
|
|
243
|
+
const sharedDisksChartData = useMemo(() => {
|
|
244
|
+
if (totalWithSharedDisks === undefined ||
|
|
245
|
+
totalVMs === undefined ||
|
|
246
|
+
totalWithSharedDisks === 0) {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
const withSharedDisks = Math.min(totalWithSharedDisks, totalVMs);
|
|
250
|
+
const withoutSharedDisks = Math.max(0, totalVMs - withSharedDisks);
|
|
251
|
+
return [
|
|
252
|
+
{
|
|
253
|
+
name: "With shared disks",
|
|
254
|
+
count: withSharedDisks,
|
|
255
|
+
countDisplay: `${withSharedDisks}`,
|
|
256
|
+
legendCategory: "With shared disks",
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
name: "No shared disks",
|
|
260
|
+
count: withoutSharedDisks,
|
|
261
|
+
countDisplay: `${withoutSharedDisks}`,
|
|
262
|
+
legendCategory: "No shared disks",
|
|
263
|
+
},
|
|
264
|
+
];
|
|
265
|
+
}, [totalVMs, totalWithSharedDisks]);
|
|
266
|
+
const tierLegend = useMemo(() => {
|
|
267
|
+
const legendMap = {};
|
|
268
|
+
chartData.forEach((slice, idx) => {
|
|
269
|
+
const color = COLOR_PALETTE[idx % COLOR_PALETTE.length];
|
|
270
|
+
if (color) {
|
|
271
|
+
legendMap[slice.legendCategory] = color;
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
return legendMap;
|
|
275
|
+
}, [chartData]);
|
|
276
|
+
const commonDonutProps = {
|
|
277
|
+
height: 300,
|
|
278
|
+
width: 420,
|
|
279
|
+
donutThickness: 18,
|
|
280
|
+
titleFontSize: 34,
|
|
281
|
+
subTitleColor: "var(--pf-t--global--text--color--subtle)",
|
|
282
|
+
legendVariant,
|
|
283
|
+
};
|
|
284
|
+
const handleDiskTierClick = (item) => {
|
|
285
|
+
const diskRange = item.diskRange ??
|
|
286
|
+
parseDiskTierLabelToRange(item.name) ??
|
|
287
|
+
parseDiskTierLabelToRange(item.legendCategory);
|
|
288
|
+
if (diskRange) {
|
|
289
|
+
onDiskTierSelect?.(diskRange);
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
const chartTitle = `${CHART_TITLE} — ${VIEW_MODE_LABELS[viewMode]}`;
|
|
293
|
+
return (_jsx(ChartExportSurface, { id: CHART_ID, title: chartTitle, exportViews: chartExportViewsFromLabels(CHART_TITLE, VIEW_MODE_LABELS), activeExportViewId: viewMode, onExportViewChange: (viewId) => {
|
|
294
|
+
if (isViewMode(viewId)) {
|
|
295
|
+
setViewMode(viewId);
|
|
296
|
+
}
|
|
297
|
+
}, children: _jsxs(Card, { className: `${dashboardStyles.card} ${dashboardStyles.storageCardOverflowHidden}`, children: [_jsx(CardTitle, { children: _jsxs(Flex, { justifyContent: { default: "justifyContentSpaceBetween" }, alignItems: { default: "alignItemsCenter" }, className: dashboardStyles.storageFlexFullWidth, children: [_jsxs(FlexItem, { children: [_jsx(DatabaseIcon, {}), " Disks"] }), _jsx(ChartHeaderActions, { chartId: CHART_ID, title: chartTitle, children: _jsx(Dropdown, { isOpen: isDropdownOpen, onSelect: (_event, value) => {
|
|
298
|
+
if (isViewMode(value)) {
|
|
299
|
+
setViewMode(value);
|
|
300
|
+
}
|
|
301
|
+
setIsDropdownOpen(false);
|
|
302
|
+
}, onOpenChange: setIsDropdownOpen, toggle: (toggleRef) => (_jsx(MenuToggle, { ref: toggleRef, onClick: () => setIsDropdownOpen((open) => !open), isExpanded: isDropdownOpen, className: dashboardStyles.storageMenuToggleMinWidth, children: VIEW_MODE_LABELS[viewMode] })), children: _jsxs(DropdownList, { children: [_jsx(DropdownItem, { value: "vmCount", children: VIEW_MODE_LABELS.vmCount }, "vmCount"), _jsx(DropdownItem, { value: "vmCountByDiskType", children: VIEW_MODE_LABELS.vmCountByDiskType }, "vmCountByDiskType"), _jsx(DropdownItem, { value: "totalSize", children: VIEW_MODE_LABELS.totalSize }, "totalSize"), _jsx(DropdownItem, { value: "sharedDisks", isDisabled: totalWithSharedDisks !== undefined &&
|
|
303
|
+
totalWithSharedDisks === 0, children: VIEW_MODE_LABELS.sharedDisks }, "sharedDisks")] }) }) })] }) }), _jsx(CardBody, { className: dashboardStyles.cardBodyScrollable, children: viewMode === "vmCountByDiskType" ? (_jsx(DiskTypeBarChart, { data: diskTypeChartData, colors: DISK_TYPE_BAR_COLORS })) : viewMode === "sharedDisks" ? (sharedDisksChartData && totalVMs !== undefined ? (_jsx(MigrationDonutChart, { ...commonDonutProps, data: sharedDisksChartData.map((item) => ({
|
|
304
|
+
...item,
|
|
305
|
+
countDisplay: `${item.countDisplay} VMs`,
|
|
306
|
+
})), customColors: SHARED_DISKS_COLORS, title: `${totalVMs} VMs`, subTitle: `${totalWithSharedDisks ?? 0} with shared disks`, marginLeft: "25%", tooltipLabelFormatter: ({ datum, percent }) => `${datum.x}: ${datum.countDisplay}\n${percent.toFixed(1)}%` })) : (_jsx(CardEmptyState, { title: REPORT_CARD_EMPTY_STATE_TITLES.storage }))) : chartData.length === 0 ? (_jsx(CardEmptyState, { title: REPORT_CARD_EMPTY_STATE_TITLES.storage })) : (_jsx(MigrationDonutChart, { ...commonDonutProps, data: chartData.map((item) => ({
|
|
307
|
+
...item,
|
|
308
|
+
countDisplay: viewMode === "totalSize"
|
|
309
|
+
? `${item.countDisplay} TB`
|
|
310
|
+
: `${item.countDisplay} VMs`,
|
|
311
|
+
})), legend: tierLegend, title: viewMode === "totalSize"
|
|
312
|
+
? `${totals.totalSize.toFixed(2)} TB`
|
|
313
|
+
: `${totals.totalVMs} VMs`, subTitle: viewMode === "totalSize"
|
|
314
|
+
? `${totals.totalVMs} VMs`
|
|
315
|
+
: `${totals.totalSize.toFixed(2)} TB`, legendLabelFormatter: ({ x, countDisplay }) => `${x} (${countDisplay})`, tooltipLabelFormatter: ({ datum, percent }) => `${datum.x}: ${datum.countDisplay}\n${percent.toFixed(1)}%`, onItemClick: viewMode === "vmCount" || viewMode === "totalSize"
|
|
316
|
+
? handleDiskTierClick
|
|
317
|
+
: undefined, onTitleClick: onTitleClick })) })] }) }));
|
|
318
|
+
};
|
|
319
|
+
StorageOverview.displayName = "StorageOverview";
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
export declare const CHART_CAPTURE_SCALE_SINGLE = 2;
|
|
2
|
+
export type CaptureChartElementOptions = {
|
|
3
|
+
/** Higher scale sharpens text in PNG exports; keep at 1 for bulk PDF/zip. */
|
|
4
|
+
scale?: number;
|
|
5
|
+
};
|
|
1
6
|
/**
|
|
2
7
|
* Tab panels stay mounted with `hidden` / `display:none` when another tab is
|
|
3
8
|
* active. html2canvas then measures a 0×0 box. Reveal those ancestors off-screen
|
|
4
9
|
* for the duration of the capture, then restore.
|
|
5
10
|
*/
|
|
6
11
|
export declare function revealHiddenChartAncestors(element: HTMLElement): () => void;
|
|
7
|
-
export declare function captureChartElement(element: HTMLElement): Promise<HTMLCanvasElement>;
|
|
12
|
+
export declare function captureChartElement(element: HTMLElement, options?: CaptureChartElementOptions): Promise<HTMLCanvasElement>;
|
|
8
13
|
//# sourceMappingURL=captureChartElement.d.ts.map
|
|
@@ -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":"AAeA,eAAO,MAAM,0BAA0B,IAAI,CAAC;AAG5C,MAAM,MAAM,0BAA0B,GAAG;IACvC,6EAA6E;IAC7E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAwBF;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,WAAW,GAAG,MAAM,IAAI,CAqD3E;AAwQD,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,WAAW,EACpB,OAAO,GAAE,0BAA+B,GACvC,OAAO,CAAC,iBAAiB,CAAC,CA0C5B"}
|
|
@@ -1,7 +1,10 @@
|
|
|
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
|
-
/**
|
|
4
|
-
const
|
|
3
|
+
import { applyLightThemeToDocument, CHART_EXPORT_CAPTURING_ATTR, CHART_EXPORT_SCROLL_ATTR, shouldIgnoreChartExportElement, } from "./chartExport.js";
|
|
4
|
+
/** Default scale for bulk exports; single-chart PNG uses a higher scale. */
|
|
5
|
+
const CHART_CAPTURE_SCALE_DEFAULT = 1;
|
|
6
|
+
export const CHART_CAPTURE_SCALE_SINGLE = 2;
|
|
7
|
+
const EXPORT_FONT_FAMILY = "RedHatText, Overpass, Helvetica, Arial, sans-serif";
|
|
5
8
|
const HIDDEN_LAYOUT_STYLE_KEYS = [
|
|
6
9
|
"display",
|
|
7
10
|
"position",
|
|
@@ -71,6 +74,10 @@ export function revealHiddenChartAncestors(element) {
|
|
|
71
74
|
const PADDED_CAPTURE_SELECTOR = ".pf-v6-c-card__title, .pf-v6-c-card__header, .pf-v6-c-card__body";
|
|
72
75
|
const CARD_CAPTURE_SELECTOR = ".pf-v6-c-card";
|
|
73
76
|
const TRANSPARENT_FILLS = new Set(["", "transparent", "rgba(0, 0, 0, 0)"]);
|
|
77
|
+
/** PatternFly light-theme chrome. html2canvas inlines live computed SVG styles. */
|
|
78
|
+
const LIGHT_EXPORT_BACKGROUND = t_color_white.value;
|
|
79
|
+
const LIGHT_EXPORT_BORDER = t_color_gray_20.value;
|
|
80
|
+
const LIGHT_EXPORT_TEXT = t_global_text_color_regular.value;
|
|
74
81
|
function queryCards(root) {
|
|
75
82
|
const cards = Array.from(root.querySelectorAll(CARD_CAPTURE_SELECTOR));
|
|
76
83
|
if (root.matches(CARD_CAPTURE_SELECTOR)) {
|
|
@@ -112,16 +119,30 @@ function readCardBorder(source) {
|
|
|
112
119
|
}
|
|
113
120
|
return { width, style, color };
|
|
114
121
|
}
|
|
115
|
-
function applyCardChrome(source, dest) {
|
|
122
|
+
function applyCardChrome(source, dest, colors) {
|
|
116
123
|
const computed = window.getComputedStyle(source);
|
|
117
124
|
dest.style.borderRadius = computed.borderRadius;
|
|
118
|
-
dest.style.backgroundColor = resolvedFill(computed.backgroundColor);
|
|
119
125
|
dest.style.overflow = "hidden";
|
|
126
|
+
dest.style.backgroundColor =
|
|
127
|
+
colors === "light"
|
|
128
|
+
? LIGHT_EXPORT_BACKGROUND
|
|
129
|
+
: resolvedFill(computed.backgroundColor);
|
|
120
130
|
const border = readCardBorder(source);
|
|
121
131
|
if (border) {
|
|
122
132
|
dest.style.borderWidth = border.width;
|
|
123
133
|
dest.style.borderStyle = border.style;
|
|
124
|
-
dest.style.borderColor =
|
|
134
|
+
dest.style.borderColor =
|
|
135
|
+
colors === "light" ? LIGHT_EXPORT_BORDER : border.color;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
function applyLightThemeSvgText(root) {
|
|
139
|
+
for (const node of root.querySelectorAll("text, tspan")) {
|
|
140
|
+
if (!(node instanceof SVGElement)) {
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
node.style.fill = LIGHT_EXPORT_TEXT;
|
|
144
|
+
node.style.color = LIGHT_EXPORT_TEXT;
|
|
145
|
+
node.style.fontFamily = EXPORT_FONT_FAMILY;
|
|
125
146
|
}
|
|
126
147
|
}
|
|
127
148
|
function hideCardBeforePseudo() {
|
|
@@ -162,7 +183,7 @@ function copyCardChrome(node) {
|
|
|
162
183
|
height: node.style.height,
|
|
163
184
|
maxHeight: node.style.maxHeight,
|
|
164
185
|
};
|
|
165
|
-
applyCardChrome(node, node);
|
|
186
|
+
applyCardChrome(node, node, "computed");
|
|
166
187
|
node.style.minHeight = "0px";
|
|
167
188
|
node.style.height = "auto";
|
|
168
189
|
node.style.maxHeight = "none";
|
|
@@ -174,7 +195,7 @@ function applyComputedStylesToClone(sourceRoot, clonedRoot) {
|
|
|
174
195
|
sourceCards.forEach((source, index) => {
|
|
175
196
|
const dest = cloneCards[index];
|
|
176
197
|
if (dest) {
|
|
177
|
-
applyCardChrome(source, dest);
|
|
198
|
+
applyCardChrome(source, dest, "light");
|
|
178
199
|
}
|
|
179
200
|
});
|
|
180
201
|
const sources = sourceRoot.querySelectorAll(PADDED_CAPTURE_SELECTOR);
|
|
@@ -248,7 +269,8 @@ function prepareCaptureLayout(element) {
|
|
|
248
269
|
}
|
|
249
270
|
};
|
|
250
271
|
}
|
|
251
|
-
export async function captureChartElement(element) {
|
|
272
|
+
export async function captureChartElement(element, options = {}) {
|
|
273
|
+
const scale = options.scale ?? CHART_CAPTURE_SCALE_DEFAULT;
|
|
252
274
|
const restoreHidden = revealHiddenChartAncestors(element);
|
|
253
275
|
const restoreLayout = prepareCaptureLayout(element);
|
|
254
276
|
element.setAttribute(CHART_EXPORT_CAPTURING_ATTR, "");
|
|
@@ -264,14 +286,21 @@ export async function captureChartElement(element) {
|
|
|
264
286
|
}
|
|
265
287
|
return html2canvas(element, {
|
|
266
288
|
useCORS: true,
|
|
267
|
-
backgroundColor:
|
|
289
|
+
backgroundColor: LIGHT_EXPORT_BACKGROUND,
|
|
268
290
|
logging: false,
|
|
269
|
-
scale
|
|
291
|
+
scale,
|
|
270
292
|
imageTimeout: 0,
|
|
271
293
|
ignoreElements: (node) => node instanceof Element &&
|
|
272
294
|
shouldIgnoreChartExportElement(node, element),
|
|
273
|
-
onclone: (
|
|
295
|
+
onclone: (clonedDoc, clonedElement) => {
|
|
296
|
+
// Dropping the dark-theme class off the clone root makes every
|
|
297
|
+
// PatternFly CSS-variable surface (cards, tables, labels) resolve to
|
|
298
|
+
// light automatically — html2canvas reads computed styles from this
|
|
299
|
+
// clone. Only Victory's inlined SVG text needs explicit recoloring.
|
|
300
|
+
applyLightThemeToDocument(clonedDoc);
|
|
274
301
|
applyComputedStylesToClone(element, clonedElement);
|
|
302
|
+
applyLightThemeSvgText(clonedElement);
|
|
303
|
+
clonedElement.style.fontFamily = EXPORT_FONT_FAMILY;
|
|
275
304
|
},
|
|
276
305
|
});
|
|
277
306
|
}
|
|
@@ -38,8 +38,31 @@ export type ChartCaptureSource = {
|
|
|
38
38
|
filename: string;
|
|
39
39
|
capture: () => Promise<HTMLCanvasElement>;
|
|
40
40
|
};
|
|
41
|
+
/** One label/value row rendered on a {@link PdfTextPage}. */
|
|
42
|
+
export type PdfTextPageItem = {
|
|
43
|
+
label: string;
|
|
44
|
+
value: string;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* A native-text PDF page appended after the captured chart images.
|
|
48
|
+
*
|
|
49
|
+
* Used for content that is not a chart/card (e.g. calculated recommendations)
|
|
50
|
+
* but still needs to be part of the exported PDF.
|
|
51
|
+
*/
|
|
52
|
+
export type PdfTextPage = {
|
|
53
|
+
title: string;
|
|
54
|
+
items: PdfTextPageItem[];
|
|
55
|
+
footer?: string;
|
|
56
|
+
};
|
|
41
57
|
export declare function releaseCanvas(canvas: HTMLCanvasElement): void;
|
|
42
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;
|
|
43
66
|
export declare function chartExportViewsFromLabels(titlePrefix: string, labels: Record<string, string>): ChartExportView[];
|
|
44
67
|
export declare function toBulkChartCaptureSources(charts: RegisteredChart[], capture: (element: HTMLElement) => Promise<HTMLCanvasElement>, prepareElement?: (element: HTMLElement) => () => void): ChartCaptureSource[];
|
|
45
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":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,OAAO,CAAC;AAE3C,eAAO,MAAM,sBAAsB,2BAA2B,CAAC;AAC/D,eAAO,MAAM,wBAAwB,6BAA6B,CAAC;AACnE,eAAO,MAAM,2BAA2B,yBAAyB,CAAC;AAElE,eAAO,MAAM,oBAAoB;;CAEvB,CAAC;AAEX,eAAO,MAAM,sBAAsB;;CAEzB,CAAC;AAEX,eAAO,MAAM,oBAAoB,EAAE,aAGlC,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,WAAW,CAAC;IACrB,WAAW,CAAC,EAAE,eAAe,EAAE,CAAC;IAChC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD,CAAC;AAEF,MAAM,MAAM,eAAe,GAAG;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,IAAI,CAAC;CACZ,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,OAAO,CAAC,iBAAiB,CAAC,CAAC;CAC3C,CAAC;AAEF,wBAAgB,aAAa,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI,CAG7D;AAED,wBAAgB,uBAAuB,IAAI,OAAO,CAAC,IAAI,CAAC,CASvD;AAED,wBAAgB,0BAA0B,CACxC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC7B,eAAe,EAAE,CAKnB;AAqBD,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,eAAe,EAAE,EACzB,OAAO,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAC,iBAAiB,CAAC,EAC7D,cAAc,CAAC,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,MAAM,IAAI,GACpD,kBAAkB,EAAE,CAyBtB;AAED,wBAAsB,uBAAuB,CAC3C,MAAM,EAAE,eAAe,EAAE,GACxB,OAAO,CAAC,IAAI,CAAC,CAcf;AAED,wBAAgB,mCAAmC,CACjD,MAAM,EAAE,eAAe,EAAE,GACxB,eAAe,EAAE,CAcnB;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAcrE;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAMvD;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,SAAU,GAAG,MAAM,CAEpE;AAWD;;;;GAIG;AACH,wBAAgB,8BAA8B,CAC5C,IAAI,EAAE,OAAO,EACb,WAAW,CAAC,EAAE,OAAO,GAAG,IAAI,GAC3B,OAAO,CAeT;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAS/D"}
|
|
1
|
+
{"version":3,"file":"chartExport.d.ts","sourceRoot":"","sources":["../../src/report/chartExport.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,7 +9,7 @@ export const chartExportScrollProps = {
|
|
|
9
9
|
};
|
|
10
10
|
export const chartExportRootStyle = {
|
|
11
11
|
width: "100%",
|
|
12
|
-
height: "
|
|
12
|
+
height: "auto",
|
|
13
13
|
};
|
|
14
14
|
export function releaseCanvas(canvas) {
|
|
15
15
|
canvas.width = 0;
|
|
@@ -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,
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import { type RefCallback } from "react";
|
|
2
|
-
import { type ChartExportMeta, type ChartExportView, type RegisteredChart } from "./chartExport.js";
|
|
2
|
+
import { type ChartExportMeta, type ChartExportView, type PdfTextPage, type RegisteredChart } from "./chartExport.js";
|
|
3
3
|
export type ChartExportFormat = "pdf" | "png" | "html";
|
|
4
4
|
export type ChartExportApi = {
|
|
5
5
|
downloadChart: (id: string) => Promise<void>;
|
|
6
6
|
downloadAll: () => Promise<void>;
|
|
7
|
-
|
|
7
|
+
/**
|
|
8
|
+
* @param extraPages - Native-text pages appended after the captured chart
|
|
9
|
+
* images (e.g. calculated recommendations that aren't rendered as charts).
|
|
10
|
+
*/
|
|
11
|
+
downloadPdf: (documentTitle: string, extraPages?: PdfTextPage[]) => Promise<void>;
|
|
8
12
|
downloadHtml: (documentTitle: string) => Promise<void>;
|
|
9
13
|
downloadingChartId: string | null;
|
|
10
14
|
exportingFormat: ChartExportFormat | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chartExportContext.d.ts","sourceRoot":"","sources":["../../src/report/chartExportContext.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,WAAW,EAIjB,MAAM,OAAO,CAAC;AACf,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,eAAe,EAEpB,KAAK,eAAe,EACrB,MAAM,kBAAkB,CAAC;AAE1B,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;AAEvD,MAAM,MAAM,cAAc,GAAG;IAC3B,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"chartExportContext.d.ts","sourceRoot":"","sources":["../../src/report/chartExportContext.tsx"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,WAAW,EAIjB,MAAM,OAAO,CAAC;AACf,OAAO,EACL,KAAK,eAAe,EACpB,KAAK,eAAe,EAEpB,KAAK,WAAW,EAChB,KAAK,eAAe,EACrB,MAAM,kBAAkB,CAAC;AAE1B,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;AAEvD,MAAM,MAAM,cAAc,GAAG;IAC3B,aAAa,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,WAAW,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACjC;;;OAGG;IACH,WAAW,EAAE,CACX,aAAa,EAAE,MAAM,EACrB,UAAU,CAAC,EAAE,WAAW,EAAE,KACvB,OAAO,CAAC,IAAI,CAAC,CAAC;IACnB,YAAY,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,eAAe,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAC1C,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;IAClC,cAAc,EAAE,OAAO,CAAC;IACxB,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,gBAAgB,EAAE,MAAM,IAAI,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,MAAM,IAAI,CAAC;CAClD,CAAC;AAEF,eAAO,MAAM,0BAA0B,qDACU,CAAC;AAClD,eAAO,MAAM,qBAAqB,gDAA6C,CAAC;AAEhF,wBAAgB,cAAc,IAAI,cAAc,GAAG,IAAI,CAEtD;AAED,wBAAgB,gBAAgB,CAAC,EAC/B,EAAE,EACF,KAAK,EACL,QAAQ,EACR,WAAW,EACX,kBAAkB,EAClB,aAAa,GACd,EAAE,eAAe,GAAG;IACnB,WAAW,CAAC,EAAE,eAAe,EAAE,CAAC;IAChC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACnD,GAAG,WAAW,CAAC,WAAW,CAAC,CA8B3B"}
|
|
@@ -3,6 +3,12 @@ export declare const dashboardStyles: {
|
|
|
3
3
|
cardScroll: string;
|
|
4
4
|
equalHeightRow: string;
|
|
5
5
|
equalHeight: string;
|
|
6
|
+
/**
|
|
7
|
+
* Flex child of {@link dashboardStyles.card}. `min-height: 0` lets the body
|
|
8
|
+
* shrink inside the card max-height; `overflow: auto` scrolls tables and tall
|
|
9
|
+
* charts when needed (phantom scrollbars on donut cards are avoided by the
|
|
10
|
+
* card layout and ChartExportSurface sizing, not a second body class).
|
|
11
|
+
*/
|
|
6
12
|
cardBodyScrollable: string;
|
|
7
13
|
card: string;
|
|
8
14
|
storageCardOverflowHidden: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dashboardStyles.d.ts","sourceRoot":"","sources":["../../src/report/dashboardStyles.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,eAAe
|
|
1
|
+
{"version":3,"file":"dashboardStyles.d.ts","sourceRoot":"","sources":["../../src/report/dashboardStyles.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,eAAe;;;;;IA8B1B;;;;;OAKG;;;;;;;;;CAkDJ,CAAC;AAEF,eAAO,MAAM,mBAAmB,QAM/B,CAAC"}
|
|
@@ -25,6 +25,12 @@ export const dashboardStyles = {
|
|
|
25
25
|
flex: 1 1 0;
|
|
26
26
|
height: 100%;
|
|
27
27
|
`,
|
|
28
|
+
/**
|
|
29
|
+
* Flex child of {@link dashboardStyles.card}. `min-height: 0` lets the body
|
|
30
|
+
* shrink inside the card max-height; `overflow: auto` scrolls tables and tall
|
|
31
|
+
* charts when needed (phantom scrollbars on donut cards are avoided by the
|
|
32
|
+
* card layout and ChartExportSurface sizing, not a second body class).
|
|
33
|
+
*/
|
|
28
34
|
cardBodyScrollable: css `
|
|
29
35
|
overflow: auto;
|
|
30
36
|
`,
|
|
@@ -32,12 +38,9 @@ export const dashboardStyles = {
|
|
|
32
38
|
// at the same specificity, which wins in the production build due to stylesheet
|
|
33
39
|
// injection order. `!important` forces `hidden` regardless of that order.
|
|
34
40
|
card: css `
|
|
35
|
-
|
|
36
|
-
max-height: 520px;
|
|
37
|
-
height: 100%;
|
|
41
|
+
height: 520px;
|
|
38
42
|
display: flex;
|
|
39
43
|
flex-direction: column;
|
|
40
|
-
justify-content: space-between;
|
|
41
44
|
overflow: hidden !important;
|
|
42
45
|
`,
|
|
43
46
|
storageCardOverflowHidden: css `
|