@openshift-migration-advisor/shared-components 0.22.0-dd51551836b7 → 0.23.0-e2d66ca45d64
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 +6 -0
- package/dist/charts/MigrationDonutChart.d.ts +18 -1
- package/dist/charts/MigrationDonutChart.d.ts.map +1 -1
- package/dist/charts/MigrationDonutChart.js +103 -43
- package/dist/charts/index.d.ts +1 -1
- package/dist/charts/index.d.ts.map +1 -1
- package/dist/forms/RadioButtonFormGroup.d.ts.map +1 -1
- package/dist/forms/RadioButtonFormGroup.js +2 -2
- package/dist/report/FeatureStatusBadge.d.ts +7 -0
- package/dist/report/FeatureStatusBadge.d.ts.map +1 -0
- package/dist/report/FeatureStatusBadge.js +13 -0
- package/dist/report/HostPowerStates.d.ts +9 -0
- package/dist/report/HostPowerStates.d.ts.map +1 -0
- package/dist/report/HostPowerStates.js +11 -0
- package/dist/report/InfrastructureSummary.d.ts +7 -0
- package/dist/report/InfrastructureSummary.d.ts.map +1 -0
- package/dist/report/InfrastructureSummary.js +5 -0
- package/dist/report/OSDistribution.d.ts.map +1 -1
- package/dist/report/OSDistribution.js +5 -13
- package/dist/report/OsSupportTiersHelpPopover.d.ts.map +1 -1
- package/dist/report/OsSupportTiersHelpPopover.js +2 -31
- package/dist/report/OsUpgradeNotice.d.ts.map +1 -1
- package/dist/report/OsUpgradeNotice.js +1 -5
- package/dist/report/PowerStateCard.d.ts +17 -0
- package/dist/report/PowerStateCard.d.ts.map +1 -0
- package/dist/report/PowerStateCard.js +7 -0
- package/dist/report/SupportTierBadge.d.ts.map +1 -1
- package/dist/report/SupportTierBadge.js +4 -2
- package/dist/report/VCenterClusterDetails.d.ts +10 -0
- package/dist/report/VCenterClusterDetails.d.ts.map +1 -0
- package/dist/report/VCenterClusterDetails.js +26 -0
- package/dist/report/VmPowerStates.d.ts +9 -0
- package/dist/report/VmPowerStates.d.ts.map +1 -0
- package/dist/report/VmPowerStates.js +11 -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/index.d.ts +10 -0
- package/dist/report/index.d.ts.map +1 -1
- package/dist/report/index.js +8 -0
- package/dist/report/infrastructureSummaryModel.d.ts +82 -0
- package/dist/report/infrastructureSummaryModel.d.ts.map +1 -0
- package/dist/report/infrastructureSummaryModel.js +139 -0
- package/dist/report/powerStates.d.ts +11 -0
- package/dist/report/powerStates.d.ts.map +1 -0
- package/dist/report/powerStates.js +113 -0
- package/package.json +1 -1
- package/src/charts/MigrationDonutChart.tsx +232 -111
- package/src/charts/index.ts +1 -0
- package/src/forms/RadioButtonFormGroup.tsx +10 -3
- package/src/report/FeatureStatusBadge.tsx +30 -0
- package/src/report/HostPowerStates.tsx +41 -0
- package/src/report/InfrastructureSummary.tsx +56 -0
- package/src/report/OSDistribution.tsx +60 -67
- package/src/report/OsSupportTiersHelpPopover.tsx +32 -61
- package/src/report/OsUpgradeNotice.tsx +1 -7
- package/src/report/PowerStateCard.tsx +73 -0
- package/src/report/SupportTierBadge.tsx +4 -2
- package/src/report/VCenterClusterDetails.tsx +211 -0
- package/src/report/VmPowerStates.tsx +41 -0
- package/src/report/__tests__/HostPowerStates.test.tsx +47 -0
- package/src/report/__tests__/InfrastructureSummary.test.tsx +148 -0
- package/src/report/__tests__/infrastructureSummaryModel.test.ts +224 -0
- package/src/report/__tests__/powerStates.test.ts +70 -0
- package/src/report/constants.ts +2 -0
- package/src/report/index.ts +56 -0
- package/src/report/infrastructureSummaryModel.ts +281 -0
- package/src/report/powerStates.ts +169 -0
|
@@ -8,15 +8,11 @@ import { css } from "@emotion/css";
|
|
|
8
8
|
import {
|
|
9
9
|
ChartDonut,
|
|
10
10
|
ChartLabel,
|
|
11
|
+
ChartLegend,
|
|
12
|
+
ChartTooltip,
|
|
11
13
|
} from "@patternfly/react-charts/dist/esm/victory/index.js";
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
EmptyStateVariant,
|
|
15
|
-
Flex,
|
|
16
|
-
FlexItem,
|
|
17
|
-
} from "@patternfly/react-core";
|
|
18
|
-
import { InboxIcon } from "@patternfly/react-icons";
|
|
19
|
-
import type { FC } from "react";
|
|
14
|
+
import { Flex, FlexItem } from "@patternfly/react-core";
|
|
15
|
+
import type { FC, ReactElement } from "react";
|
|
20
16
|
import { useCallback, useMemo } from "react";
|
|
21
17
|
|
|
22
18
|
export interface MigrationDonutChartDatum {
|
|
@@ -30,6 +26,8 @@ export interface MigrationDonutChartDatum {
|
|
|
30
26
|
networkNames?: string[];
|
|
31
27
|
}
|
|
32
28
|
|
|
29
|
+
export type MigrationDonutChartLegendVariant = "html" | "chart";
|
|
30
|
+
|
|
33
31
|
export interface MigrationDonutChartProps {
|
|
34
32
|
data: MigrationDonutChartDatum[];
|
|
35
33
|
legend?: Record<string, string>;
|
|
@@ -55,16 +53,60 @@ export interface MigrationDonutChartProps {
|
|
|
55
53
|
percent: number;
|
|
56
54
|
total: number;
|
|
57
55
|
}) => string;
|
|
56
|
+
/** Agent-ui: slice / legend click drill-down. */
|
|
58
57
|
onItemClick?: (item: MigrationDonutChartDatum) => void;
|
|
58
|
+
/** Agent-ui: clickable center title overlay. */
|
|
59
59
|
onTitleClick?: () => void;
|
|
60
|
-
/**
|
|
60
|
+
/** Agent-ui: custom HTML legend labels (ignored when legendVariant is "chart"). */
|
|
61
61
|
legendLabelFormatter?: (item: {
|
|
62
62
|
x: string;
|
|
63
63
|
countDisplay?: string | number;
|
|
64
64
|
}) => string;
|
|
65
|
+
/**
|
|
66
|
+
* Legend rendering mode.
|
|
67
|
+
* - `"html"` (default): custom clickable legend used by agent-ui
|
|
68
|
+
* - `"chart"`: PatternFly ChartLegend used by ui-app
|
|
69
|
+
*/
|
|
70
|
+
legendVariant?: MigrationDonutChartLegendVariant;
|
|
71
|
+
/** ui-app ChartLegend: total legend width. */
|
|
72
|
+
legendWidth?: number;
|
|
73
|
+
/** ui-app ChartLegend: columns per row. */
|
|
74
|
+
itemsPerRow?: number;
|
|
75
|
+
/** ui-app ChartLegend: label font size. */
|
|
76
|
+
labelFontSize?: number;
|
|
77
|
+
/** Prefer themed Victory ChartTooltip (default true for chart legend). */
|
|
78
|
+
useThemedTooltip?: boolean;
|
|
65
79
|
}
|
|
66
80
|
|
|
67
|
-
const legendColors = [
|
|
81
|
+
const legendColors = [
|
|
82
|
+
"#0066cc",
|
|
83
|
+
"#5e40be",
|
|
84
|
+
"#009596",
|
|
85
|
+
"#4cb140",
|
|
86
|
+
"#f0ab00",
|
|
87
|
+
"#b98412",
|
|
88
|
+
"#a30000",
|
|
89
|
+
"#b6a6e9",
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
/** Victory chart tooltips that respect PatternFly light/dark themes. */
|
|
93
|
+
const themedChartTooltipStyle = {
|
|
94
|
+
fontSize: 9,
|
|
95
|
+
fill: "var(--pf-t--global--text--color--regular)",
|
|
96
|
+
} as const;
|
|
97
|
+
|
|
98
|
+
const themedChartTooltipFlyoutStyle = {
|
|
99
|
+
stroke: "var(--pf-t--global--border--color--default)",
|
|
100
|
+
strokeWidth: 1,
|
|
101
|
+
fill: "var(--pf-t--global--background--color--floating--default)",
|
|
102
|
+
} as const;
|
|
103
|
+
|
|
104
|
+
const themedChartTooltipFlyoutPadding = {
|
|
105
|
+
top: 6,
|
|
106
|
+
bottom: 6,
|
|
107
|
+
left: 10,
|
|
108
|
+
right: 10,
|
|
109
|
+
} as const;
|
|
68
110
|
|
|
69
111
|
const styles = {
|
|
70
112
|
legendIcon: css`
|
|
@@ -120,8 +162,8 @@ export const MigrationDonutChart: FC<MigrationDonutChartProps> = ({
|
|
|
120
162
|
width = 420,
|
|
121
163
|
title,
|
|
122
164
|
subTitle,
|
|
123
|
-
titleColor = "
|
|
124
|
-
subTitleColor = "
|
|
165
|
+
titleColor = "var(--pf-t--global--text--color--regular)",
|
|
166
|
+
subTitleColor = "var(--pf-t--global--text--color--subtle)",
|
|
125
167
|
marginLeft = "0%",
|
|
126
168
|
titleFontSize = 28,
|
|
127
169
|
subTitleFontSize = 14,
|
|
@@ -131,30 +173,35 @@ export const MigrationDonutChart: FC<MigrationDonutChartProps> = ({
|
|
|
131
173
|
onItemClick,
|
|
132
174
|
onTitleClick,
|
|
133
175
|
legendLabelFormatter,
|
|
176
|
+
legendVariant = "html",
|
|
177
|
+
legendWidth,
|
|
178
|
+
itemsPerRow = 1,
|
|
179
|
+
labelFontSize = 25,
|
|
180
|
+
useThemedTooltip,
|
|
134
181
|
}) => {
|
|
182
|
+
const showChartLegend = legendVariant === "chart";
|
|
183
|
+
const themedTooltipEnabled = useThemedTooltip ?? showChartLegend;
|
|
184
|
+
|
|
135
185
|
const dynamicLegend = useMemo(() => {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
result: Record<string, string>[];
|
|
151
|
-
},
|
|
152
|
-
).result;
|
|
186
|
+
const legendMap: Record<string, string> = {};
|
|
187
|
+
const seen = new Set<string>();
|
|
188
|
+
|
|
189
|
+
for (const item of data) {
|
|
190
|
+
const key = item.legendCategory;
|
|
191
|
+
if (!seen.has(key)) {
|
|
192
|
+
seen.add(key);
|
|
193
|
+
legendMap[key] =
|
|
194
|
+
customColors?.[key] ??
|
|
195
|
+
legendColors[(seen.size - 1) % legendColors.length];
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return legendMap;
|
|
153
200
|
}, [data, customColors]);
|
|
154
201
|
|
|
155
|
-
const chartLegend = legend
|
|
202
|
+
const chartLegend = legend ?? dynamicLegend;
|
|
156
203
|
const getColor = useCallback(
|
|
157
|
-
(name: string): string => chartLegend[name],
|
|
204
|
+
(name: string): string => chartLegend[name] ?? legendColors[0],
|
|
158
205
|
[chartLegend],
|
|
159
206
|
);
|
|
160
207
|
|
|
@@ -171,6 +218,33 @@ export const MigrationDonutChart: FC<MigrationDonutChartProps> = ({
|
|
|
171
218
|
return chartData.map((item) => getColor(item.legendCategory));
|
|
172
219
|
}, [chartData, getColor]);
|
|
173
220
|
|
|
221
|
+
const legendData = useMemo(() => {
|
|
222
|
+
return chartData.map((item) => ({
|
|
223
|
+
name: `${item.x} (${item.countDisplay})`,
|
|
224
|
+
symbol: { fill: getColor(item.legendCategory) },
|
|
225
|
+
}));
|
|
226
|
+
}, [chartData, getColor]);
|
|
227
|
+
|
|
228
|
+
const legendWidthValue = legendWidth ?? 800;
|
|
229
|
+
const legendX = useMemo(() => {
|
|
230
|
+
const symbolAndGap = 34;
|
|
231
|
+
const charWidth = labelFontSize * 0.55;
|
|
232
|
+
const itemWidths = legendData.map(
|
|
233
|
+
(d) => symbolAndGap + d.name.length * charWidth,
|
|
234
|
+
);
|
|
235
|
+
const numCols = Math.min(legendData.length, itemsPerRow);
|
|
236
|
+
const gutter = 16;
|
|
237
|
+
let contentWidth = (numCols - 1) * gutter;
|
|
238
|
+
for (let c = 0; c < numCols; c++) {
|
|
239
|
+
let maxW = 0;
|
|
240
|
+
for (let i = c; i < itemWidths.length; i += itemsPerRow) {
|
|
241
|
+
maxW = Math.max(maxW, itemWidths[i] ?? 0);
|
|
242
|
+
}
|
|
243
|
+
contentWidth += maxW;
|
|
244
|
+
}
|
|
245
|
+
return Math.max(0, (legendWidthValue - contentWidth) / 2);
|
|
246
|
+
}, [legendData, itemsPerRow, legendWidthValue, labelFontSize]);
|
|
247
|
+
|
|
174
248
|
const innerRadius = useMemo(() => {
|
|
175
249
|
const outerApprox = Math.min(width, height) / 2;
|
|
176
250
|
const computed = outerApprox - donutThickness;
|
|
@@ -230,17 +304,132 @@ export const MigrationDonutChart: FC<MigrationDonutChartProps> = ({
|
|
|
230
304
|
];
|
|
231
305
|
}, [onItemClick, handleClick]);
|
|
232
306
|
|
|
307
|
+
const donutTooltip = useMemo(
|
|
308
|
+
() =>
|
|
309
|
+
themedTooltipEnabled ? (
|
|
310
|
+
<ChartTooltip
|
|
311
|
+
style={themedChartTooltipStyle}
|
|
312
|
+
flyoutStyle={themedChartTooltipFlyoutStyle}
|
|
313
|
+
flyoutPadding={themedChartTooltipFlyoutPadding}
|
|
314
|
+
/>
|
|
315
|
+
) : undefined,
|
|
316
|
+
[themedTooltipEnabled],
|
|
317
|
+
);
|
|
318
|
+
|
|
319
|
+
const formatLabel = useCallback(
|
|
320
|
+
(datum: {
|
|
321
|
+
x: string;
|
|
322
|
+
y: number;
|
|
323
|
+
legendCategory: string;
|
|
324
|
+
countDisplay?: string | number;
|
|
325
|
+
}) => {
|
|
326
|
+
const percent = totalY > 0 ? (Number(datum.y) / totalY) * 100 : 0;
|
|
327
|
+
return tooltipLabelFormatter
|
|
328
|
+
? tooltipLabelFormatter({
|
|
329
|
+
datum: {
|
|
330
|
+
x: datum.x,
|
|
331
|
+
y: Number(datum.y),
|
|
332
|
+
countDisplay: datum.countDisplay,
|
|
333
|
+
legendCategory: datum.legendCategory,
|
|
334
|
+
},
|
|
335
|
+
percent,
|
|
336
|
+
total: totalY,
|
|
337
|
+
})
|
|
338
|
+
: `${datum.x}: ${datum.countDisplay ?? datum.y}`;
|
|
339
|
+
},
|
|
340
|
+
[tooltipLabelFormatter, totalY],
|
|
341
|
+
);
|
|
342
|
+
|
|
233
343
|
if (!data || data.length === 0) {
|
|
234
|
-
return
|
|
235
|
-
<EmptyState
|
|
236
|
-
headingLevel="h3"
|
|
237
|
-
titleText="No data available"
|
|
238
|
-
icon={InboxIcon}
|
|
239
|
-
variant={EmptyStateVariant.xs}
|
|
240
|
-
/>
|
|
241
|
-
);
|
|
344
|
+
return null;
|
|
242
345
|
}
|
|
243
346
|
|
|
347
|
+
const htmlLegend: ReactElement = (
|
|
348
|
+
<Flex
|
|
349
|
+
className={`${styles.legendContainer} ${css`margin-left: ${marginLeft};`}`}
|
|
350
|
+
justifyContent={{ default: "justifyContentCenter" }}
|
|
351
|
+
alignItems={{ default: "alignItemsFlexStart" }}
|
|
352
|
+
>
|
|
353
|
+
<Flex
|
|
354
|
+
className={styles.legendInner}
|
|
355
|
+
spaceItems={{ default: "spaceItemsMd" }}
|
|
356
|
+
justifyContent={{ default: "justifyContentCenter" }}
|
|
357
|
+
alignItems={{ default: "alignItemsCenter" }}
|
|
358
|
+
flexWrap={{ default: "wrap" }}
|
|
359
|
+
>
|
|
360
|
+
{data.map((item) => {
|
|
361
|
+
const label = legendLabelFormatter
|
|
362
|
+
? legendLabelFormatter({
|
|
363
|
+
x: item.name,
|
|
364
|
+
countDisplay: item.countDisplay,
|
|
365
|
+
})
|
|
366
|
+
: item.name;
|
|
367
|
+
|
|
368
|
+
const content = (
|
|
369
|
+
<>
|
|
370
|
+
<svg
|
|
371
|
+
width="10"
|
|
372
|
+
height="10"
|
|
373
|
+
aria-hidden="true"
|
|
374
|
+
className={styles.legendIcon}
|
|
375
|
+
>
|
|
376
|
+
<title>Legend color indicator</title>
|
|
377
|
+
<rect
|
|
378
|
+
width="10"
|
|
379
|
+
height="10"
|
|
380
|
+
fill={getColor(item.legendCategory)}
|
|
381
|
+
/>
|
|
382
|
+
</svg>
|
|
383
|
+
<span>{label}</span>
|
|
384
|
+
</>
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
return (
|
|
388
|
+
<FlexItem key={`${item.legendCategory}-${item.name}`}>
|
|
389
|
+
{onItemClick ? (
|
|
390
|
+
<button
|
|
391
|
+
type="button"
|
|
392
|
+
onClick={() => onItemClick(item)}
|
|
393
|
+
className={styles.legendButton}
|
|
394
|
+
>
|
|
395
|
+
{content}
|
|
396
|
+
</button>
|
|
397
|
+
) : (
|
|
398
|
+
<span className={styles.legendLabel}>{content}</span>
|
|
399
|
+
)}
|
|
400
|
+
</FlexItem>
|
|
401
|
+
);
|
|
402
|
+
})}
|
|
403
|
+
</Flex>
|
|
404
|
+
</Flex>
|
|
405
|
+
);
|
|
406
|
+
|
|
407
|
+
const chartLegendElement: ReactElement = (
|
|
408
|
+
<FlexItem flex={{ default: "flex_1" }}>
|
|
409
|
+
<Flex
|
|
410
|
+
justifyContent={{ default: "justifyContentCenter" }}
|
|
411
|
+
alignItems={{ default: "alignItemsCenter" }}
|
|
412
|
+
>
|
|
413
|
+
<FlexItem>
|
|
414
|
+
<ChartLegend
|
|
415
|
+
data={legendData}
|
|
416
|
+
orientation="horizontal"
|
|
417
|
+
height={200}
|
|
418
|
+
width={legendWidthValue}
|
|
419
|
+
x={legendX}
|
|
420
|
+
itemsPerRow={itemsPerRow}
|
|
421
|
+
style={{
|
|
422
|
+
labels: {
|
|
423
|
+
fontSize: labelFontSize,
|
|
424
|
+
fill: "var(--pf-t--global--text--color--regular)",
|
|
425
|
+
},
|
|
426
|
+
}}
|
|
427
|
+
/>
|
|
428
|
+
</FlexItem>
|
|
429
|
+
</Flex>
|
|
430
|
+
</FlexItem>
|
|
431
|
+
);
|
|
432
|
+
|
|
244
433
|
return (
|
|
245
434
|
<Flex
|
|
246
435
|
direction={{ default: "column" }}
|
|
@@ -252,6 +441,7 @@ export const MigrationDonutChart: FC<MigrationDonutChartProps> = ({
|
|
|
252
441
|
ariaDesc="Migration data donut chart"
|
|
253
442
|
data={chartData}
|
|
254
443
|
events={chartEvents}
|
|
444
|
+
labelComponent={donutTooltip}
|
|
255
445
|
labels={({
|
|
256
446
|
datum,
|
|
257
447
|
}: {
|
|
@@ -261,21 +451,7 @@ export const MigrationDonutChart: FC<MigrationDonutChartProps> = ({
|
|
|
261
451
|
legendCategory: string;
|
|
262
452
|
countDisplay?: string | number;
|
|
263
453
|
};
|
|
264
|
-
}) =>
|
|
265
|
-
const percent = totalY > 0 ? (Number(datum.y) / totalY) * 100 : 0;
|
|
266
|
-
return tooltipLabelFormatter
|
|
267
|
-
? tooltipLabelFormatter({
|
|
268
|
-
datum: {
|
|
269
|
-
x: datum.x,
|
|
270
|
-
y: Number(datum.y),
|
|
271
|
-
countDisplay: datum.countDisplay,
|
|
272
|
-
legendCategory: datum.legendCategory,
|
|
273
|
-
},
|
|
274
|
-
percent,
|
|
275
|
-
total: totalY,
|
|
276
|
-
})
|
|
277
|
-
: `${datum.x}: ${datum.countDisplay ?? datum.y}`;
|
|
278
|
-
}}
|
|
454
|
+
}) => formatLabel(datum)}
|
|
279
455
|
colorScale={colorScale}
|
|
280
456
|
constrainToVisibleArea
|
|
281
457
|
innerRadius={innerRadius}
|
|
@@ -306,6 +482,7 @@ export const MigrationDonutChart: FC<MigrationDonutChartProps> = ({
|
|
|
306
482
|
subTitleComponent={
|
|
307
483
|
subTitle ? (
|
|
308
484
|
<ChartLabel
|
|
485
|
+
dy={showChartLegend ? 7 : undefined}
|
|
309
486
|
style={[
|
|
310
487
|
{
|
|
311
488
|
fill: subTitleColor,
|
|
@@ -343,63 +520,7 @@ export const MigrationDonutChart: FC<MigrationDonutChartProps> = ({
|
|
|
343
520
|
/>
|
|
344
521
|
)}
|
|
345
522
|
</div>
|
|
346
|
-
|
|
347
|
-
className={`${styles.legendContainer} ${css`margin-left: ${marginLeft};`}`}
|
|
348
|
-
justifyContent={{ default: "justifyContentCenter" }}
|
|
349
|
-
alignItems={{ default: "alignItemsFlexStart" }}
|
|
350
|
-
>
|
|
351
|
-
<Flex
|
|
352
|
-
className={styles.legendInner}
|
|
353
|
-
spaceItems={{ default: "spaceItemsMd" }}
|
|
354
|
-
justifyContent={{ default: "justifyContentCenter" }}
|
|
355
|
-
alignItems={{ default: "alignItemsCenter" }}
|
|
356
|
-
flexWrap={{ default: "wrap" }}
|
|
357
|
-
>
|
|
358
|
-
{data.map((item) => {
|
|
359
|
-
const label = legendLabelFormatter
|
|
360
|
-
? legendLabelFormatter({
|
|
361
|
-
x: item.name,
|
|
362
|
-
countDisplay: item.countDisplay,
|
|
363
|
-
})
|
|
364
|
-
: item.name;
|
|
365
|
-
|
|
366
|
-
const content = (
|
|
367
|
-
<>
|
|
368
|
-
<svg
|
|
369
|
-
width="10"
|
|
370
|
-
height="10"
|
|
371
|
-
aria-hidden="true"
|
|
372
|
-
className={styles.legendIcon}
|
|
373
|
-
>
|
|
374
|
-
<title>Legend color indicator</title>
|
|
375
|
-
<rect
|
|
376
|
-
width="10"
|
|
377
|
-
height="10"
|
|
378
|
-
fill={getColor(item.legendCategory)}
|
|
379
|
-
/>
|
|
380
|
-
</svg>
|
|
381
|
-
<span>{label}</span>
|
|
382
|
-
</>
|
|
383
|
-
);
|
|
384
|
-
|
|
385
|
-
return (
|
|
386
|
-
<FlexItem key={`${item.legendCategory}-${item.name}`}>
|
|
387
|
-
{onItemClick ? (
|
|
388
|
-
<button
|
|
389
|
-
type="button"
|
|
390
|
-
onClick={() => onItemClick(item)}
|
|
391
|
-
className={styles.legendButton}
|
|
392
|
-
>
|
|
393
|
-
{content}
|
|
394
|
-
</button>
|
|
395
|
-
) : (
|
|
396
|
-
<span className={styles.legendLabel}>{content}</span>
|
|
397
|
-
)}
|
|
398
|
-
</FlexItem>
|
|
399
|
-
);
|
|
400
|
-
})}
|
|
401
|
-
</Flex>
|
|
402
|
-
</Flex>
|
|
523
|
+
{showChartLegend ? chartLegendElement : htmlLegend}
|
|
403
524
|
</Flex>
|
|
404
525
|
);
|
|
405
526
|
};
|
package/src/charts/index.ts
CHANGED
|
@@ -33,12 +33,19 @@ export default function RadioButtonFormGroup({
|
|
|
33
33
|
const showError = error && (isTouched || isSubmitted);
|
|
34
34
|
|
|
35
35
|
return (
|
|
36
|
-
<FormGroup
|
|
36
|
+
<FormGroup
|
|
37
|
+
label={label}
|
|
38
|
+
isRequired={isRequired}
|
|
39
|
+
fieldId={id}
|
|
40
|
+
role="radiogroup"
|
|
41
|
+
isInline
|
|
42
|
+
{...props}
|
|
43
|
+
>
|
|
37
44
|
<Controller
|
|
38
45
|
name={name}
|
|
39
46
|
control={methods.control}
|
|
40
47
|
render={({ field }) => (
|
|
41
|
-
|
|
48
|
+
<>
|
|
42
49
|
{options.map((option) => (
|
|
43
50
|
<Radio
|
|
44
51
|
key={option.value}
|
|
@@ -50,7 +57,7 @@ export default function RadioButtonFormGroup({
|
|
|
50
57
|
onBlur={field.onBlur}
|
|
51
58
|
/>
|
|
52
59
|
))}
|
|
53
|
-
|
|
60
|
+
</>
|
|
54
61
|
)}
|
|
55
62
|
/>
|
|
56
63
|
<FormFieldHelperText
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Label } from "@patternfly/react-core";
|
|
2
|
+
import { CheckCircleIcon, TimesCircleIcon } from "@patternfly/react-icons";
|
|
3
|
+
import type { FC } from "react";
|
|
4
|
+
import type { FeatureStatus } from "./infrastructureSummaryModel.js";
|
|
5
|
+
|
|
6
|
+
export interface FeatureStatusBadgeProps {
|
|
7
|
+
status: FeatureStatus;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const FeatureStatusBadge: FC<FeatureStatusBadgeProps> = ({ status }) => {
|
|
11
|
+
if (status === "unknown") {
|
|
12
|
+
return <span>—</span>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (status === "enabled") {
|
|
16
|
+
return (
|
|
17
|
+
<Label color="green" isCompact icon={<CheckCircleIcon />}>
|
|
18
|
+
Enabled
|
|
19
|
+
</Label>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return (
|
|
24
|
+
<Label color="grey" isCompact icon={<TimesCircleIcon />}>
|
|
25
|
+
Disabled
|
|
26
|
+
</Label>
|
|
27
|
+
);
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
FeatureStatusBadge.displayName = "FeatureStatusBadge";
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ServerIcon } from "@patternfly/react-icons";
|
|
2
|
+
import type { FC } from "react";
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
import type { MigrationDonutChartLegendVariant } from "../charts/MigrationDonutChart.js";
|
|
5
|
+
import { REPORT_CARD_EMPTY_STATE_TITLES } from "./constants.js";
|
|
6
|
+
import { PowerStateCard } from "./PowerStateCard.js";
|
|
7
|
+
import { buildHostPowerStateChart } from "./powerStates.js";
|
|
8
|
+
|
|
9
|
+
export interface HostPowerStatesProps {
|
|
10
|
+
hostPowerStates?: Record<string, number>;
|
|
11
|
+
isExportMode?: boolean;
|
|
12
|
+
legendVariant?: MigrationDonutChartLegendVariant;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const HostPowerStates: FC<HostPowerStatesProps> = ({
|
|
16
|
+
hostPowerStates,
|
|
17
|
+
isExportMode = false,
|
|
18
|
+
legendVariant,
|
|
19
|
+
}) => {
|
|
20
|
+
const chart = useMemo(
|
|
21
|
+
() => buildHostPowerStateChart(hostPowerStates),
|
|
22
|
+
[hostPowerStates],
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<PowerStateCard
|
|
27
|
+
id="esxi-host-power-states"
|
|
28
|
+
title="ESXi host power states"
|
|
29
|
+
icon={<ServerIcon />}
|
|
30
|
+
emptyTitle={REPORT_CARD_EMPTY_STATE_TITLES.hostPowerStates}
|
|
31
|
+
slices={chart.slices}
|
|
32
|
+
legend={chart.legend}
|
|
33
|
+
total={chart.total}
|
|
34
|
+
subTitle="Hosts"
|
|
35
|
+
isExportMode={isExportMode}
|
|
36
|
+
legendVariant={legendVariant}
|
|
37
|
+
/>
|
|
38
|
+
);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
HostPowerStates.displayName = "HostPowerStates";
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Card,
|
|
3
|
+
CardBody,
|
|
4
|
+
CardTitle,
|
|
5
|
+
DescriptionList,
|
|
6
|
+
DescriptionListDescription,
|
|
7
|
+
DescriptionListGroup,
|
|
8
|
+
DescriptionListTerm,
|
|
9
|
+
} from "@patternfly/react-core";
|
|
10
|
+
import type { FC } from "react";
|
|
11
|
+
import type { InfrastructureSummaryModel } from "./infrastructureSummaryModel.js";
|
|
12
|
+
|
|
13
|
+
export interface InfrastructureSummaryProps {
|
|
14
|
+
summary: InfrastructureSummaryModel;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const formatCount = (value: number | undefined): string =>
|
|
18
|
+
typeof value === "number" ? String(value) : "—";
|
|
19
|
+
|
|
20
|
+
export const InfrastructureSummary: FC<InfrastructureSummaryProps> = ({
|
|
21
|
+
summary,
|
|
22
|
+
}) => (
|
|
23
|
+
<Card isFullHeight id="infrastructure-summary">
|
|
24
|
+
<CardTitle>Infrastructure summary</CardTitle>
|
|
25
|
+
<CardBody>
|
|
26
|
+
<DescriptionList isAutoFit displaySize="lg">
|
|
27
|
+
<DescriptionListGroup>
|
|
28
|
+
<DescriptionListTerm>VMware version</DescriptionListTerm>
|
|
29
|
+
<DescriptionListDescription>
|
|
30
|
+
{summary.vmwareVersion}
|
|
31
|
+
</DescriptionListDescription>
|
|
32
|
+
</DescriptionListGroup>
|
|
33
|
+
<DescriptionListGroup>
|
|
34
|
+
<DescriptionListTerm>Datacenters</DescriptionListTerm>
|
|
35
|
+
<DescriptionListDescription>
|
|
36
|
+
{formatCount(summary.datacenters)}
|
|
37
|
+
</DescriptionListDescription>
|
|
38
|
+
</DescriptionListGroup>
|
|
39
|
+
<DescriptionListGroup>
|
|
40
|
+
<DescriptionListTerm>vCenters</DescriptionListTerm>
|
|
41
|
+
<DescriptionListDescription>
|
|
42
|
+
{formatCount(summary.vCenters)}
|
|
43
|
+
</DescriptionListDescription>
|
|
44
|
+
</DescriptionListGroup>
|
|
45
|
+
<DescriptionListGroup>
|
|
46
|
+
<DescriptionListTerm>ESXi hosts</DescriptionListTerm>
|
|
47
|
+
<DescriptionListDescription>
|
|
48
|
+
{formatCount(summary.esxiHosts)}
|
|
49
|
+
</DescriptionListDescription>
|
|
50
|
+
</DescriptionListGroup>
|
|
51
|
+
</DescriptionList>
|
|
52
|
+
</CardBody>
|
|
53
|
+
</Card>
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
InfrastructureSummary.displayName = "InfrastructureSummary";
|