@openshift-migration-advisor/shared-components 0.25.0 → 0.27.0-3f8ae10b086b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -0
- package/dist/report/ChartDownloadButton.d.ts +11 -0
- package/dist/report/ChartDownloadButton.d.ts.map +1 -0
- package/dist/report/ChartDownloadButton.js +24 -0
- package/dist/report/ChartExportProvider.d.ts +23 -0
- package/dist/report/ChartExportProvider.d.ts.map +1 -0
- package/dist/report/ChartExportProvider.js +222 -0
- package/dist/report/ChartExportSurface.d.ts +12 -0
- package/dist/report/ChartExportSurface.d.ts.map +1 -0
- package/dist/report/ChartExportSurface.js +32 -0
- 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/ErrorTable.d.ts +18 -0
- package/dist/report/ErrorTable.d.ts.map +1 -0
- package/dist/report/ErrorTable.js +20 -0
- package/dist/report/HostPowerStates.d.ts +0 -1
- package/dist/report/HostPowerStates.d.ts.map +1 -1
- package/dist/report/HostPowerStates.js +2 -2
- package/dist/report/HostsOverview.d.ts +13 -0
- package/dist/report/HostsOverview.d.ts.map +1 -0
- package/dist/report/HostsOverview.js +84 -0
- package/dist/report/InfrastructureSummary.d.ts.map +1 -1
- package/dist/report/InfrastructureSummary.js +6 -2
- package/dist/report/NetworkOverview.d.ts +31 -0
- package/dist/report/NetworkOverview.d.ts.map +1 -0
- package/dist/report/NetworkOverview.js +201 -0
- package/dist/report/OSDistribution.d.ts +0 -2
- package/dist/report/OSDistribution.d.ts.map +1 -1
- package/dist/report/OSDistribution.js +8 -5
- package/dist/report/OsUpgradeRecommendationPopover.d.ts +0 -1
- package/dist/report/OsUpgradeRecommendationPopover.d.ts.map +1 -1
- package/dist/report/OsUpgradeRecommendationPopover.js +4 -3
- package/dist/report/PowerStateCard.d.ts +0 -1
- package/dist/report/PowerStateCard.d.ts.map +1 -1
- package/dist/report/PowerStateCard.js +4 -2
- package/dist/report/ReportExportMenu.d.ts +19 -0
- package/dist/report/ReportExportMenu.d.ts.map +1 -0
- package/dist/report/ReportExportMenu.js +31 -0
- package/dist/report/ReportTable.d.ts +14 -0
- package/dist/report/ReportTable.d.ts.map +1 -0
- package/dist/report/ReportTable.js +23 -0
- package/dist/report/SupportTierBadge.d.ts +0 -1
- package/dist/report/SupportTierBadge.d.ts.map +1 -1
- package/dist/report/SupportTierBadge.js +1 -21
- package/dist/report/VCenterClusterDetails.d.ts +0 -1
- package/dist/report/VCenterClusterDetails.d.ts.map +1 -1
- package/dist/report/VCenterClusterDetails.js +9 -4
- package/dist/report/VmPowerStates.d.ts +0 -1
- package/dist/report/VmPowerStates.d.ts.map +1 -1
- package/dist/report/VmPowerStates.js +2 -2
- package/dist/report/WarningsTable.d.ts +9 -0
- package/dist/report/WarningsTable.d.ts.map +1 -0
- package/dist/report/WarningsTable.js +20 -0
- package/dist/report/captureChartElement.d.ts +8 -0
- package/dist/report/captureChartElement.d.ts.map +1 -0
- package/dist/report/captureChartElement.js +304 -0
- package/dist/report/chartExport.d.ts +80 -0
- package/dist/report/chartExport.d.ts.map +1 -0
- package/dist/report/chartExport.js +182 -0
- package/dist/report/chartExportContext.d.ts +32 -0
- package/dist/report/chartExportContext.d.ts.map +1 -0
- package/dist/report/chartExportContext.js +36 -0
- package/dist/report/chartExportFilenames.d.ts +4 -0
- package/dist/report/chartExportFilenames.d.ts.map +1 -0
- package/dist/report/chartExportFilenames.js +16 -0
- package/dist/report/constants.d.ts +2 -0
- package/dist/report/constants.d.ts.map +1 -1
- package/dist/report/constants.js +2 -0
- package/dist/report/dashboardStyles.d.ts +0 -4
- package/dist/report/dashboardStyles.d.ts.map +1 -1
- package/dist/report/dashboardStyles.js +0 -19
- package/dist/report/htmlExport.d.ts +9 -0
- package/dist/report/htmlExport.d.ts.map +1 -0
- package/dist/report/htmlExport.js +65 -0
- package/dist/report/index.d.ts +20 -0
- package/dist/report/index.d.ts.map +1 -1
- package/dist/report/index.js +20 -0
- package/dist/report/pdfExport.d.ts +3 -0
- package/dist/report/pdfExport.d.ts.map +1 -0
- package/dist/report/pdfExport.js +131 -0
- package/dist/report/pdfPage.d.ts +15 -0
- package/dist/report/pdfPage.d.ts.map +1 -0
- package/dist/report/pdfPage.js +44 -0
- package/dist/report/reportExportOptions.d.ts +17 -0
- package/dist/report/reportExportOptions.d.ts.map +1 -0
- package/dist/report/reportExportOptions.js +31 -0
- package/dist/report/zipChartPngs.d.ts +3 -0
- package/dist/report/zipChartPngs.d.ts.map +1 -0
- package/dist/report/zipChartPngs.js +8 -0
- package/package.json +7 -2
- package/src/report/ChartDownloadButton.tsx +59 -0
- package/src/report/ChartExportProvider.tsx +392 -0
- package/src/report/ChartExportSurface.tsx +60 -0
- package/src/report/ClustersOverview.tsx +451 -0
- package/src/report/ClustersOverviewStyles.ts +110 -0
- package/src/report/CpuAndMemoryOverview.tsx +277 -0
- package/src/report/ErrorTable.tsx +85 -0
- package/src/report/HostPowerStates.tsx +0 -3
- package/src/report/HostsOverview.tsx +185 -0
- package/src/report/InfrastructureSummary.tsx +48 -31
- package/src/report/NetworkOverview.tsx +405 -0
- package/src/report/OSDistribution.tsx +46 -42
- package/src/report/OsUpgradeRecommendationPopover.tsx +3 -4
- package/src/report/PowerStateCard.tsx +49 -36
- package/src/report/ReportExportMenu.tsx +119 -0
- package/src/report/ReportTable.tsx +81 -0
- package/src/report/SupportTierBadge.tsx +12 -40
- package/src/report/VCenterClusterDetails.tsx +38 -16
- package/src/report/VmPowerStates.tsx +0 -3
- package/src/report/WarningsTable.tsx +75 -0
- package/src/report/__tests__/ChartExportProvider.test.tsx +582 -0
- package/src/report/__tests__/ClustersOverview.test.tsx +241 -0
- package/src/report/__tests__/CpuAndMemoryOverview.test.tsx +200 -0
- package/src/report/__tests__/HostsOverview.test.tsx +145 -0
- package/src/report/__tests__/InfrastructureSummary.test.tsx +0 -22
- package/src/report/__tests__/NetworkOverview.test.tsx +269 -0
- package/src/report/__tests__/OSDistribution.test.tsx +10 -11
- package/src/report/__tests__/captureChartElement.test.ts +205 -0
- package/src/report/__tests__/chartExportPackagers.test.ts +299 -0
- package/src/report/captureChartElement.ts +398 -0
- package/src/report/chartExport.ts +277 -0
- package/src/report/chartExportContext.tsx +92 -0
- package/src/report/chartExportFilenames.ts +21 -0
- package/src/report/constants.ts +2 -0
- package/src/report/dashboardStyles.ts +0 -23
- package/src/report/htmlExport.ts +83 -0
- package/src/report/index.ts +107 -0
- package/src/report/pdfExport.ts +233 -0
- package/src/report/pdfPage.ts +83 -0
- package/src/report/reportExportOptions.ts +53 -0
- package/src/report/zipChartPngs.ts +10 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { css } from "@emotion/css";
|
|
2
|
+
import {
|
|
3
|
+
Card,
|
|
4
|
+
CardBody,
|
|
5
|
+
CardTitle,
|
|
6
|
+
Dropdown,
|
|
7
|
+
DropdownItem,
|
|
8
|
+
DropdownList,
|
|
9
|
+
Flex,
|
|
10
|
+
FlexItem,
|
|
11
|
+
MenuToggle,
|
|
12
|
+
type MenuToggleElement,
|
|
13
|
+
} from "@patternfly/react-core";
|
|
14
|
+
import { DataProcessorIcon } from "@patternfly/react-icons";
|
|
15
|
+
import type { FC, Ref } from "react";
|
|
16
|
+
import { useMemo, useState } from "react";
|
|
17
|
+
import {
|
|
18
|
+
MigrationDonutChart,
|
|
19
|
+
type MigrationDonutChartDatum,
|
|
20
|
+
type MigrationDonutChartLegendVariant,
|
|
21
|
+
} from "../charts/MigrationDonutChart.js";
|
|
22
|
+
import { CardEmptyState } from "./CardEmptyState.js";
|
|
23
|
+
import { ChartHeaderActions } from "./ChartDownloadButton.js";
|
|
24
|
+
import { ChartExportSurface } from "./ChartExportSurface.js";
|
|
25
|
+
import { chartExportViewsFromLabels } from "./chartExport.js";
|
|
26
|
+
import { REPORT_CARD_EMPTY_STATE_TITLES } from "./constants.js";
|
|
27
|
+
import { dashboardStyles } from "./dashboardStyles.js";
|
|
28
|
+
|
|
29
|
+
export interface CpuAndMemoryOverviewProps {
|
|
30
|
+
cpuTierDistribution?: Record<string, number>;
|
|
31
|
+
memoryTierDistribution?: Record<string, number>;
|
|
32
|
+
memoryTotalGB?: number;
|
|
33
|
+
cpuTotalCores?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Memory-tier slice click. The argument is the distribution key.
|
|
36
|
+
* Agent-ui maps it to a VM memory filter; ui-app omits it.
|
|
37
|
+
*/
|
|
38
|
+
onTierSelect?: (tierLabel: string) => void;
|
|
39
|
+
/** Center-title click. Agent-ui opens the VM list with no extra filters. */
|
|
40
|
+
onTitleClick?: () => void;
|
|
41
|
+
/** `"html"` in agent-ui; ui-app passes `"chart"`. */
|
|
42
|
+
legendVariant?: MigrationDonutChartLegendVariant;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
type ViewMode = "memoryTiers" | "vcpuTiers";
|
|
46
|
+
|
|
47
|
+
const VIEW_MODE_LABELS: Record<ViewMode, string> = {
|
|
48
|
+
memoryTiers: "VM distribution by memory size tier",
|
|
49
|
+
vcpuTiers: "VM distribution by vCPU count tier",
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const VIEW_SUBTITLES: Record<ViewMode, string> = {
|
|
53
|
+
memoryTiers: "Memory size tiers",
|
|
54
|
+
vcpuTiers: "vCPU count tiers",
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Categorical order shared by the agent-ui and ui-app CPU/memory donuts.
|
|
59
|
+
*/
|
|
60
|
+
const TIER_COLORS = [
|
|
61
|
+
"#0066cc",
|
|
62
|
+
"#5e40be",
|
|
63
|
+
"#b6a6e9",
|
|
64
|
+
"#73c5c5",
|
|
65
|
+
"#b98412",
|
|
66
|
+
"#28a745",
|
|
67
|
+
"#f0ad4e",
|
|
68
|
+
"#d9534f",
|
|
69
|
+
"#009596",
|
|
70
|
+
"#6a6e73",
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const CHART_ID = "cpu-memory-overview";
|
|
74
|
+
const CHART_TITLE = "CPU & memory";
|
|
75
|
+
|
|
76
|
+
const cardSubtitle = css`
|
|
77
|
+
color: var(--pf-t--global--text--color--subtle);
|
|
78
|
+
font-size: 0.85rem;
|
|
79
|
+
`;
|
|
80
|
+
|
|
81
|
+
const menuToggleMinWidth = css`
|
|
82
|
+
min-width: 290px;
|
|
83
|
+
`;
|
|
84
|
+
|
|
85
|
+
const titleRow = css`
|
|
86
|
+
width: 100%;
|
|
87
|
+
`;
|
|
88
|
+
|
|
89
|
+
const isViewMode = (value: string | number | undefined): value is ViewMode =>
|
|
90
|
+
value === "memoryTiers" || value === "vcpuTiers";
|
|
91
|
+
|
|
92
|
+
const tierSortKey = (label: string): number => {
|
|
93
|
+
const value = Number.parseInt(label.trim(), 10);
|
|
94
|
+
return Number.isFinite(value) ? value : Number.MAX_SAFE_INTEGER;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const hasUnit = (label: string, unit: "GB" | "cores"): boolean =>
|
|
98
|
+
unit === "GB" ? /gb$/i.test(label) : /cores?$/i.test(label);
|
|
99
|
+
|
|
100
|
+
const slicesFromDistribution = (
|
|
101
|
+
distribution: Record<string, number> | undefined,
|
|
102
|
+
unit: "GB" | "cores",
|
|
103
|
+
): MigrationDonutChartDatum[] =>
|
|
104
|
+
Object.entries(distribution ?? {})
|
|
105
|
+
.filter(([, count]) => Number(count) > 0)
|
|
106
|
+
.sort(([left], [right]) => tierSortKey(left) - tierSortKey(right))
|
|
107
|
+
.map(([tier, count]) => {
|
|
108
|
+
const trimmed = tier.trim();
|
|
109
|
+
const name = hasUnit(trimmed, unit) ? trimmed : `${trimmed} ${unit}`;
|
|
110
|
+
const vmCount = Number(count);
|
|
111
|
+
return {
|
|
112
|
+
name,
|
|
113
|
+
count: vmCount,
|
|
114
|
+
countDisplay: `${vmCount} VMs`,
|
|
115
|
+
legendCategory: tier,
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
const legendFor = (categories: string[]): Record<string, string> => {
|
|
120
|
+
const legend: Record<string, string> = {};
|
|
121
|
+
categories.forEach((category, index) => {
|
|
122
|
+
const color = TIER_COLORS[index % TIER_COLORS.length];
|
|
123
|
+
if (color) {
|
|
124
|
+
legend[category] = color;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
return legend;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export const CpuAndMemoryOverview: FC<CpuAndMemoryOverviewProps> = ({
|
|
131
|
+
cpuTierDistribution,
|
|
132
|
+
memoryTierDistribution,
|
|
133
|
+
memoryTotalGB,
|
|
134
|
+
cpuTotalCores,
|
|
135
|
+
onTierSelect,
|
|
136
|
+
onTitleClick,
|
|
137
|
+
legendVariant = "html",
|
|
138
|
+
}) => {
|
|
139
|
+
const [viewMode, setViewMode] = useState<ViewMode>("memoryTiers");
|
|
140
|
+
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
|
141
|
+
|
|
142
|
+
const memorySlices = useMemo(
|
|
143
|
+
() => slicesFromDistribution(memoryTierDistribution, "GB"),
|
|
144
|
+
[memoryTierDistribution],
|
|
145
|
+
);
|
|
146
|
+
const vcpuSlices = useMemo(
|
|
147
|
+
() => slicesFromDistribution(cpuTierDistribution, "cores"),
|
|
148
|
+
[cpuTierDistribution],
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
const activeSlices = viewMode === "memoryTiers" ? memorySlices : vcpuSlices;
|
|
152
|
+
const legend = useMemo(
|
|
153
|
+
() => legendFor(activeSlices.map((slice) => slice.legendCategory)),
|
|
154
|
+
[activeSlices],
|
|
155
|
+
);
|
|
156
|
+
const totalVMs = useMemo(
|
|
157
|
+
() => activeSlices.reduce((sum, slice) => sum + slice.count, 0),
|
|
158
|
+
[activeSlices],
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const chartTitle = `${CHART_TITLE} — ${VIEW_MODE_LABELS[viewMode]}`;
|
|
162
|
+
const subTitle =
|
|
163
|
+
viewMode === "memoryTiers"
|
|
164
|
+
? typeof memoryTotalGB === "number"
|
|
165
|
+
? `${memoryTotalGB.toLocaleString()} GB`
|
|
166
|
+
: undefined
|
|
167
|
+
: typeof cpuTotalCores === "number"
|
|
168
|
+
? `${cpuTotalCores.toLocaleString()} Cores`
|
|
169
|
+
: undefined;
|
|
170
|
+
const emptyTitle =
|
|
171
|
+
viewMode === "memoryTiers"
|
|
172
|
+
? REPORT_CARD_EMPTY_STATE_TITLES.memory
|
|
173
|
+
: REPORT_CARD_EMPTY_STATE_TITLES.cpu;
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<ChartExportSurface
|
|
177
|
+
id={CHART_ID}
|
|
178
|
+
title={chartTitle}
|
|
179
|
+
exportViews={chartExportViewsFromLabels(CHART_TITLE, VIEW_MODE_LABELS)}
|
|
180
|
+
activeExportViewId={viewMode}
|
|
181
|
+
onExportViewChange={(viewId) => {
|
|
182
|
+
if (isViewMode(viewId)) {
|
|
183
|
+
setViewMode(viewId);
|
|
184
|
+
}
|
|
185
|
+
}}
|
|
186
|
+
>
|
|
187
|
+
<Card className={dashboardStyles.card}>
|
|
188
|
+
<CardTitle>
|
|
189
|
+
<Flex
|
|
190
|
+
className={titleRow}
|
|
191
|
+
justifyContent={{ default: "justifyContentSpaceBetween" }}
|
|
192
|
+
alignItems={{ default: "alignItemsCenter" }}
|
|
193
|
+
>
|
|
194
|
+
<FlexItem>
|
|
195
|
+
<div>
|
|
196
|
+
<div>
|
|
197
|
+
<DataProcessorIcon /> {CHART_TITLE}
|
|
198
|
+
</div>
|
|
199
|
+
<div className={cardSubtitle}>{VIEW_SUBTITLES[viewMode]}</div>
|
|
200
|
+
</div>
|
|
201
|
+
</FlexItem>
|
|
202
|
+
<ChartHeaderActions chartId={CHART_ID} title={chartTitle}>
|
|
203
|
+
<Dropdown
|
|
204
|
+
isOpen={isDropdownOpen}
|
|
205
|
+
onSelect={(_event, value) => {
|
|
206
|
+
if (isViewMode(value)) {
|
|
207
|
+
setViewMode(value);
|
|
208
|
+
}
|
|
209
|
+
setIsDropdownOpen(false);
|
|
210
|
+
}}
|
|
211
|
+
onOpenChange={setIsDropdownOpen}
|
|
212
|
+
toggle={(toggleRef: Ref<MenuToggleElement>) => (
|
|
213
|
+
<MenuToggle
|
|
214
|
+
ref={toggleRef}
|
|
215
|
+
onClick={() => setIsDropdownOpen((open) => !open)}
|
|
216
|
+
isExpanded={isDropdownOpen}
|
|
217
|
+
className={menuToggleMinWidth}
|
|
218
|
+
>
|
|
219
|
+
{VIEW_MODE_LABELS[viewMode]}
|
|
220
|
+
</MenuToggle>
|
|
221
|
+
)}
|
|
222
|
+
>
|
|
223
|
+
<DropdownList>
|
|
224
|
+
<DropdownItem value="memoryTiers" key="memoryTiers">
|
|
225
|
+
{VIEW_MODE_LABELS.memoryTiers}
|
|
226
|
+
</DropdownItem>
|
|
227
|
+
<DropdownItem value="vcpuTiers" key="vcpuTiers">
|
|
228
|
+
{VIEW_MODE_LABELS.vcpuTiers}
|
|
229
|
+
</DropdownItem>
|
|
230
|
+
</DropdownList>
|
|
231
|
+
</Dropdown>
|
|
232
|
+
</ChartHeaderActions>
|
|
233
|
+
</Flex>
|
|
234
|
+
</CardTitle>
|
|
235
|
+
<CardBody className={dashboardStyles.cardBodyScrollable}>
|
|
236
|
+
{activeSlices.length === 0 ? (
|
|
237
|
+
<CardEmptyState title={emptyTitle} />
|
|
238
|
+
) : (
|
|
239
|
+
<MigrationDonutChart
|
|
240
|
+
legendVariant={legendVariant}
|
|
241
|
+
data={activeSlices}
|
|
242
|
+
legend={legend}
|
|
243
|
+
height={300}
|
|
244
|
+
width={420}
|
|
245
|
+
donutThickness={18}
|
|
246
|
+
titleFontSize={34}
|
|
247
|
+
title={`${totalVMs} VMs`}
|
|
248
|
+
subTitle={subTitle}
|
|
249
|
+
subTitleColor="var(--pf-t--global--text--color--subtle)"
|
|
250
|
+
legendLabelFormatter={({ x, countDisplay }) =>
|
|
251
|
+
`${x} (${countDisplay})`
|
|
252
|
+
}
|
|
253
|
+
tooltipLabelFormatter={({ datum, percent }) =>
|
|
254
|
+
`${datum.countDisplay}\n${percent.toFixed(1)}%`
|
|
255
|
+
}
|
|
256
|
+
itemsPerRow={
|
|
257
|
+
legendVariant === "chart"
|
|
258
|
+
? Math.ceil(activeSlices.length / 2)
|
|
259
|
+
: undefined
|
|
260
|
+
}
|
|
261
|
+
labelFontSize={legendVariant === "chart" ? 18 : undefined}
|
|
262
|
+
marginLeft={legendVariant === "chart" ? "52%" : undefined}
|
|
263
|
+
onItemClick={
|
|
264
|
+
viewMode === "memoryTiers" && onTierSelect
|
|
265
|
+
? (item) => onTierSelect(item.legendCategory)
|
|
266
|
+
: undefined
|
|
267
|
+
}
|
|
268
|
+
onTitleClick={onTitleClick}
|
|
269
|
+
/>
|
|
270
|
+
)}
|
|
271
|
+
</CardBody>
|
|
272
|
+
</Card>
|
|
273
|
+
</ChartExportSurface>
|
|
274
|
+
);
|
|
275
|
+
};
|
|
276
|
+
|
|
277
|
+
CpuAndMemoryOverview.displayName = "CpuAndMemoryOverview";
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Card,
|
|
3
|
+
CardBody,
|
|
4
|
+
CardTitle,
|
|
5
|
+
Flex,
|
|
6
|
+
FlexItem,
|
|
7
|
+
Icon,
|
|
8
|
+
} from "@patternfly/react-core";
|
|
9
|
+
import { ExclamationCircleIcon } from "@patternfly/react-icons";
|
|
10
|
+
import type React from "react";
|
|
11
|
+
import { CardEmptyState } from "./CardEmptyState.js";
|
|
12
|
+
import { ChartHeaderActions } from "./ChartDownloadButton.js";
|
|
13
|
+
import { ChartExportSurface } from "./ChartExportSurface.js";
|
|
14
|
+
import { REPORT_CARD_EMPTY_STATE_TITLES } from "./constants.js";
|
|
15
|
+
import { dashboardStyles } from "./dashboardStyles.js";
|
|
16
|
+
import { ReportTable } from "./ReportTable.js";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Minimal shape of a migration issue consumed by {@link ErrorTable}. Declared
|
|
20
|
+
* locally so this package does not depend on a specific SDK (`agent-sdk` or
|
|
21
|
+
* `planner-sdk`); callers pass the subset of `MigrationIssue` fields read here.
|
|
22
|
+
*/
|
|
23
|
+
export interface MigrationIssueLike {
|
|
24
|
+
label: string;
|
|
25
|
+
assessment: string;
|
|
26
|
+
count: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface ErrorTableProps {
|
|
30
|
+
errors: MigrationIssueLike[];
|
|
31
|
+
/** Optional drill-down callback invoked with an issue's `label`. */
|
|
32
|
+
onConcernClick?: (concernLabel: string) => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const ErrorTable: React.FC<ErrorTableProps> = ({
|
|
36
|
+
errors,
|
|
37
|
+
onConcernClick,
|
|
38
|
+
}) => {
|
|
39
|
+
const handleRowClick = (issue: MigrationIssueLike) => {
|
|
40
|
+
if (issue.label && onConcernClick) {
|
|
41
|
+
onConcernClick(issue.label);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const chartId = "errors-table";
|
|
46
|
+
const chartTitle = "Errors";
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<ChartExportSurface id={chartId} title={chartTitle}>
|
|
50
|
+
<Card className={dashboardStyles.card}>
|
|
51
|
+
<CardTitle>
|
|
52
|
+
<Flex
|
|
53
|
+
justifyContent={{ default: "justifyContentSpaceBetween" }}
|
|
54
|
+
alignItems={{ default: "alignItemsCenter" }}
|
|
55
|
+
>
|
|
56
|
+
<FlexItem>
|
|
57
|
+
<Icon status="danger">
|
|
58
|
+
<ExclamationCircleIcon />
|
|
59
|
+
</Icon>{" "}
|
|
60
|
+
Errors
|
|
61
|
+
</FlexItem>
|
|
62
|
+
<ChartHeaderActions chartId={chartId} title={chartTitle} />
|
|
63
|
+
</Flex>
|
|
64
|
+
</CardTitle>
|
|
65
|
+
<CardBody className={dashboardStyles.cardBodyScrollable}>
|
|
66
|
+
{errors.length === 0 ? (
|
|
67
|
+
<CardEmptyState title={REPORT_CARD_EMPTY_STATE_TITLES.errors} />
|
|
68
|
+
) : (
|
|
69
|
+
<div>
|
|
70
|
+
<ReportTable<MigrationIssueLike>
|
|
71
|
+
data={errors}
|
|
72
|
+
columns={["Description", "Total VMs"]}
|
|
73
|
+
fields={["assessment", "count"]}
|
|
74
|
+
onRowClick={onConcernClick ? handleRowClick : undefined}
|
|
75
|
+
clickableFields={onConcernClick ? ["assessment"] : []}
|
|
76
|
+
/>
|
|
77
|
+
</div>
|
|
78
|
+
)}
|
|
79
|
+
</CardBody>
|
|
80
|
+
</Card>
|
|
81
|
+
</ChartExportSurface>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
ErrorTable.displayName = "ErrorTable";
|
|
@@ -8,13 +8,11 @@ import { buildHostPowerStateChart } from "./powerStates.js";
|
|
|
8
8
|
|
|
9
9
|
export interface HostPowerStatesProps {
|
|
10
10
|
hostPowerStates?: Record<string, number>;
|
|
11
|
-
isExportMode?: boolean;
|
|
12
11
|
legendVariant?: MigrationDonutChartLegendVariant;
|
|
13
12
|
}
|
|
14
13
|
|
|
15
14
|
export const HostPowerStates: FC<HostPowerStatesProps> = ({
|
|
16
15
|
hostPowerStates,
|
|
17
|
-
isExportMode = false,
|
|
18
16
|
legendVariant,
|
|
19
17
|
}) => {
|
|
20
18
|
const chart = useMemo(
|
|
@@ -32,7 +30,6 @@ export const HostPowerStates: FC<HostPowerStatesProps> = ({
|
|
|
32
30
|
legend={chart.legend}
|
|
33
31
|
total={chart.total}
|
|
34
32
|
subTitle="Hosts"
|
|
35
|
-
isExportMode={isExportMode}
|
|
36
33
|
legendVariant={legendVariant}
|
|
37
34
|
/>
|
|
38
35
|
);
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { css } from "@emotion/css";
|
|
2
|
+
import {
|
|
3
|
+
Card,
|
|
4
|
+
CardBody,
|
|
5
|
+
CardTitle,
|
|
6
|
+
Flex,
|
|
7
|
+
FlexItem,
|
|
8
|
+
} from "@patternfly/react-core";
|
|
9
|
+
import { ServerIcon } from "@patternfly/react-icons";
|
|
10
|
+
import {
|
|
11
|
+
chart_color_blue_300,
|
|
12
|
+
chart_color_green_300,
|
|
13
|
+
chart_color_purple_100,
|
|
14
|
+
chart_color_purple_300,
|
|
15
|
+
chart_color_teal_200,
|
|
16
|
+
chart_color_yellow_400,
|
|
17
|
+
} from "@patternfly/react-tokens";
|
|
18
|
+
import type { FC } from "react";
|
|
19
|
+
import { useMemo } from "react";
|
|
20
|
+
import {
|
|
21
|
+
MigrationDonutChart,
|
|
22
|
+
type MigrationDonutChartDatum,
|
|
23
|
+
type MigrationDonutChartLegendVariant,
|
|
24
|
+
} from "../charts/MigrationDonutChart.js";
|
|
25
|
+
import { CardEmptyState } from "./CardEmptyState.js";
|
|
26
|
+
import { ChartHeaderActions } from "./ChartDownloadButton.js";
|
|
27
|
+
import { ChartExportSurface } from "./ChartExportSurface.js";
|
|
28
|
+
import { REPORT_CARD_EMPTY_STATE_TITLES } from "./constants.js";
|
|
29
|
+
import { dashboardStyles } from "./dashboardStyles.js";
|
|
30
|
+
|
|
31
|
+
/** Smallest host shape this card reads. Apps pass SDK hosts structurally. */
|
|
32
|
+
export interface HostLike {
|
|
33
|
+
model?: string | number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface HostsOverviewProps {
|
|
37
|
+
hosts?: HostLike[];
|
|
38
|
+
/** `"html"` in agent-ui; ui-app passes `"chart"`. */
|
|
39
|
+
legendVariant?: MigrationDonutChartLegendVariant;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const TOP_HOST_MODELS = 5;
|
|
43
|
+
const OTHER_MODELS_LABEL = "Other models";
|
|
44
|
+
const UNKNOWN_MODEL_LABEL = "Unknown model";
|
|
45
|
+
const CHART_ID = "hosts-overview";
|
|
46
|
+
const CHART_TITLE = "Host distribution by model";
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Same categorical order as the ui-app host-model donut, mapped onto
|
|
50
|
+
* PatternFly `chart_color_*` tokens (nearest token where the old hex
|
|
51
|
+
* was not itself a token).
|
|
52
|
+
*/
|
|
53
|
+
const HOST_MODEL_COLORS = [
|
|
54
|
+
chart_color_blue_300.value,
|
|
55
|
+
chart_color_purple_300.value,
|
|
56
|
+
chart_color_purple_100.value,
|
|
57
|
+
chart_color_teal_200.value,
|
|
58
|
+
chart_color_yellow_400.value,
|
|
59
|
+
chart_color_green_300.value,
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
const cardSubtitle = css`
|
|
63
|
+
color: var(--pf-t--global--text--color--subtle);
|
|
64
|
+
font-size: 0.85rem;
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
const hostModelLabel = (model: HostLike["model"]): string => {
|
|
68
|
+
const raw =
|
|
69
|
+
typeof model === "string"
|
|
70
|
+
? model
|
|
71
|
+
: typeof model === "number"
|
|
72
|
+
? String(model)
|
|
73
|
+
: "";
|
|
74
|
+
const trimmed = raw.trim();
|
|
75
|
+
return trimmed !== "" ? trimmed : UNKNOWN_MODEL_LABEL;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const buildHostModelDonut = (
|
|
79
|
+
hosts: HostLike[],
|
|
80
|
+
): {
|
|
81
|
+
slices: MigrationDonutChartDatum[];
|
|
82
|
+
legend: Record<string, string>;
|
|
83
|
+
totalHosts: number;
|
|
84
|
+
} => {
|
|
85
|
+
const counts = new Map<string, number>();
|
|
86
|
+
for (const host of hosts) {
|
|
87
|
+
const label = hostModelLabel(host?.model);
|
|
88
|
+
counts.set(label, (counts.get(label) ?? 0) + 1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const entries = [...counts.entries()]
|
|
92
|
+
.map(([model, count]) => ({ model, count }))
|
|
93
|
+
.sort((a, b) => b.count - a.count);
|
|
94
|
+
|
|
95
|
+
const top = entries.slice(0, TOP_HOST_MODELS);
|
|
96
|
+
const restSum = entries
|
|
97
|
+
.slice(TOP_HOST_MODELS)
|
|
98
|
+
.reduce((sum, entry) => sum + entry.count, 0);
|
|
99
|
+
|
|
100
|
+
const slices: MigrationDonutChartDatum[] = top.map((entry) => ({
|
|
101
|
+
name: entry.model,
|
|
102
|
+
count: entry.count,
|
|
103
|
+
countDisplay: `${entry.count} hosts`,
|
|
104
|
+
legendCategory: entry.model,
|
|
105
|
+
}));
|
|
106
|
+
|
|
107
|
+
if (restSum > 0) {
|
|
108
|
+
slices.push({
|
|
109
|
+
name: OTHER_MODELS_LABEL,
|
|
110
|
+
count: restSum,
|
|
111
|
+
countDisplay: `${restSum} hosts`,
|
|
112
|
+
legendCategory: OTHER_MODELS_LABEL,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const legend: Record<string, string> = {};
|
|
117
|
+
slices.forEach((slice, index) => {
|
|
118
|
+
const color = HOST_MODEL_COLORS[index % HOST_MODEL_COLORS.length];
|
|
119
|
+
if (color) {
|
|
120
|
+
legend[slice.legendCategory] = color;
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return { slices, legend, totalHosts: hosts.length };
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export const HostsOverview: FC<HostsOverviewProps> = ({
|
|
128
|
+
hosts,
|
|
129
|
+
legendVariant = "html",
|
|
130
|
+
}) => {
|
|
131
|
+
const { slices, legend, totalHosts } = useMemo(
|
|
132
|
+
() => buildHostModelDonut(Array.isArray(hosts) ? hosts : []),
|
|
133
|
+
[hosts],
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
return (
|
|
137
|
+
<ChartExportSurface id={CHART_ID} title={CHART_TITLE}>
|
|
138
|
+
<Card className={dashboardStyles.card}>
|
|
139
|
+
<CardTitle>
|
|
140
|
+
<Flex
|
|
141
|
+
justifyContent={{ default: "justifyContentSpaceBetween" }}
|
|
142
|
+
alignItems={{ default: "alignItemsCenter" }}
|
|
143
|
+
>
|
|
144
|
+
<FlexItem>
|
|
145
|
+
<div>
|
|
146
|
+
<div>
|
|
147
|
+
<ServerIcon /> {CHART_TITLE}
|
|
148
|
+
</div>
|
|
149
|
+
<div className={cardSubtitle}>Top 5 models</div>
|
|
150
|
+
</div>
|
|
151
|
+
</FlexItem>
|
|
152
|
+
<ChartHeaderActions chartId={CHART_ID} title={CHART_TITLE} />
|
|
153
|
+
</Flex>
|
|
154
|
+
</CardTitle>
|
|
155
|
+
<CardBody className={dashboardStyles.cardBodyScrollable}>
|
|
156
|
+
{slices.length === 0 ? (
|
|
157
|
+
<CardEmptyState title={REPORT_CARD_EMPTY_STATE_TITLES.hosts} />
|
|
158
|
+
) : (
|
|
159
|
+
<MigrationDonutChart
|
|
160
|
+
legendVariant={legendVariant}
|
|
161
|
+
data={slices}
|
|
162
|
+
height={300}
|
|
163
|
+
width={420}
|
|
164
|
+
donutThickness={18}
|
|
165
|
+
titleFontSize={34}
|
|
166
|
+
legend={legend}
|
|
167
|
+
legendWidth={680}
|
|
168
|
+
title={`${totalHosts}`}
|
|
169
|
+
subTitle="Hosts"
|
|
170
|
+
subTitleColor="var(--pf-t--global--text--color--subtle)"
|
|
171
|
+
itemsPerRow={2}
|
|
172
|
+
labelFontSize={16}
|
|
173
|
+
marginLeft="0%"
|
|
174
|
+
tooltipLabelFormatter={({ datum, percent }) =>
|
|
175
|
+
`${datum.countDisplay}\n${percent.toFixed(1)}%`
|
|
176
|
+
}
|
|
177
|
+
/>
|
|
178
|
+
)}
|
|
179
|
+
</CardBody>
|
|
180
|
+
</Card>
|
|
181
|
+
</ChartExportSurface>
|
|
182
|
+
);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
HostsOverview.displayName = "HostsOverview";
|
|
@@ -6,8 +6,12 @@ import {
|
|
|
6
6
|
DescriptionListDescription,
|
|
7
7
|
DescriptionListGroup,
|
|
8
8
|
DescriptionListTerm,
|
|
9
|
+
Flex,
|
|
10
|
+
FlexItem,
|
|
9
11
|
} from "@patternfly/react-core";
|
|
10
12
|
import type { FC } from "react";
|
|
13
|
+
import { ChartHeaderActions } from "./ChartDownloadButton.js";
|
|
14
|
+
import { ChartExportSurface } from "./ChartExportSurface.js";
|
|
11
15
|
import type { InfrastructureSummaryModel } from "./infrastructureSummaryModel.js";
|
|
12
16
|
|
|
13
17
|
export interface InfrastructureSummaryProps {
|
|
@@ -17,40 +21,53 @@ export interface InfrastructureSummaryProps {
|
|
|
17
21
|
const formatCount = (value: number | undefined): string =>
|
|
18
22
|
typeof value === "number" ? String(value) : "—";
|
|
19
23
|
|
|
24
|
+
const INFRA_CHART_ID = "infrastructure-summary";
|
|
25
|
+
const INFRA_TITLE = "Infrastructure summary";
|
|
26
|
+
|
|
20
27
|
export const InfrastructureSummary: FC<InfrastructureSummaryProps> = ({
|
|
21
28
|
summary,
|
|
22
29
|
}) => (
|
|
23
|
-
<
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
</
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
30
|
+
<ChartExportSurface id={INFRA_CHART_ID} title={INFRA_TITLE}>
|
|
31
|
+
<Card isFullHeight>
|
|
32
|
+
<CardTitle>
|
|
33
|
+
<Flex
|
|
34
|
+
justifyContent={{ default: "justifyContentSpaceBetween" }}
|
|
35
|
+
alignItems={{ default: "alignItemsCenter" }}
|
|
36
|
+
>
|
|
37
|
+
<FlexItem>{INFRA_TITLE}</FlexItem>
|
|
38
|
+
<ChartHeaderActions chartId={INFRA_CHART_ID} title={INFRA_TITLE} />
|
|
39
|
+
</Flex>
|
|
40
|
+
</CardTitle>
|
|
41
|
+
<CardBody>
|
|
42
|
+
<DescriptionList isAutoFit displaySize="lg">
|
|
43
|
+
<DescriptionListGroup>
|
|
44
|
+
<DescriptionListTerm>VMware version</DescriptionListTerm>
|
|
45
|
+
<DescriptionListDescription>
|
|
46
|
+
{summary.vmwareVersion}
|
|
47
|
+
</DescriptionListDescription>
|
|
48
|
+
</DescriptionListGroup>
|
|
49
|
+
<DescriptionListGroup>
|
|
50
|
+
<DescriptionListTerm>Datacenters</DescriptionListTerm>
|
|
51
|
+
<DescriptionListDescription>
|
|
52
|
+
{formatCount(summary.datacenters)}
|
|
53
|
+
</DescriptionListDescription>
|
|
54
|
+
</DescriptionListGroup>
|
|
55
|
+
<DescriptionListGroup>
|
|
56
|
+
<DescriptionListTerm>vCenters</DescriptionListTerm>
|
|
57
|
+
<DescriptionListDescription>
|
|
58
|
+
{formatCount(summary.vCenters)}
|
|
59
|
+
</DescriptionListDescription>
|
|
60
|
+
</DescriptionListGroup>
|
|
61
|
+
<DescriptionListGroup>
|
|
62
|
+
<DescriptionListTerm>ESXi hosts</DescriptionListTerm>
|
|
63
|
+
<DescriptionListDescription>
|
|
64
|
+
{formatCount(summary.esxiHosts)}
|
|
65
|
+
</DescriptionListDescription>
|
|
66
|
+
</DescriptionListGroup>
|
|
67
|
+
</DescriptionList>
|
|
68
|
+
</CardBody>
|
|
69
|
+
</Card>
|
|
70
|
+
</ChartExportSurface>
|
|
54
71
|
);
|
|
55
72
|
|
|
56
73
|
InfrastructureSummary.displayName = "InfrastructureSummary";
|