@openshift-migration-advisor/shared-components 0.22.0-aaabd4569e38 → 0.23.0-54185b7f1be8
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/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/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- 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/infrastructureSummary.d.ts +82 -0
- package/dist/report/infrastructureSummary.d.ts.map +1 -0
- package/dist/report/infrastructureSummary.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/index.ts +1 -0
- package/src/forms/RadioButtonFormGroup.tsx +10 -3
- package/src/index.ts +3 -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__/infrastructureSummary.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/infrastructureSummary.ts +281 -0
- package/src/report/powerStates.ts +169 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
booleanToFeatureStatus,
|
|
4
|
+
buildClusterDetailRows,
|
|
5
|
+
buildClusterDetails,
|
|
6
|
+
buildInfrastructureSummary,
|
|
7
|
+
countVCenters,
|
|
8
|
+
formatVSphereVersion,
|
|
9
|
+
hostCapabilityStatus,
|
|
10
|
+
type InventoryCluster,
|
|
11
|
+
type InventoryHost,
|
|
12
|
+
type InventoryInfra,
|
|
13
|
+
visibleNetworks,
|
|
14
|
+
vsanStatus,
|
|
15
|
+
} from "../infrastructureSummary.js";
|
|
16
|
+
|
|
17
|
+
const createInfra = (
|
|
18
|
+
overrides: Partial<InventoryInfra> = {},
|
|
19
|
+
): InventoryInfra => ({
|
|
20
|
+
totalHosts: 0,
|
|
21
|
+
networks: [],
|
|
22
|
+
datastores: [],
|
|
23
|
+
...overrides,
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("formatVSphereVersion", () => {
|
|
27
|
+
it("formats API versions as vSphere labels and drops trailing build zeros", () => {
|
|
28
|
+
expect(formatVSphereVersion("7.0.3.0")).toBe("vSphere 7.0.3");
|
|
29
|
+
expect(formatVSphereVersion("8.0.3.0")).toBe("vSphere 8.0.3");
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("keeps an existing vSphere prefix and missing values", () => {
|
|
33
|
+
expect(formatVSphereVersion("vSphere 7.0.3")).toBe("vSphere 7.0.3");
|
|
34
|
+
expect(formatVSphereVersion(undefined)).toBe("—");
|
|
35
|
+
expect(formatVSphereVersion("")).toBe("—");
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
describe("feature status helpers", () => {
|
|
40
|
+
it("maps booleans to enabled, disabled, or unknown", () => {
|
|
41
|
+
expect(booleanToFeatureStatus(true)).toBe("enabled");
|
|
42
|
+
expect(booleanToFeatureStatus(false)).toBe("disabled");
|
|
43
|
+
expect(booleanToFeatureStatus(undefined)).toBe("unknown");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("treats vMotion as enabled when any host supports it", () => {
|
|
47
|
+
const hosts: InventoryHost[] = [
|
|
48
|
+
{ vmotionSupported: false },
|
|
49
|
+
{ vmotionSupported: true },
|
|
50
|
+
];
|
|
51
|
+
expect(hostCapabilityStatus(hosts, "vmotionSupported")).toBe("enabled");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("treats vMotion as disabled when every known host reports false", () => {
|
|
55
|
+
const hosts: InventoryHost[] = [
|
|
56
|
+
{ vmotionSupported: false },
|
|
57
|
+
{ vmotionSupported: false },
|
|
58
|
+
];
|
|
59
|
+
expect(hostCapabilityStatus(hosts, "vmotionSupported")).toBe("disabled");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("treats missing host capability flags as unknown", () => {
|
|
63
|
+
expect(hostCapabilityStatus([{}], "vmotionSupported")).toBe("unknown");
|
|
64
|
+
expect(hostCapabilityStatus([], "vmotionSupported")).toBe("unknown");
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("detects vSAN from datastore type or disk type", () => {
|
|
68
|
+
expect(
|
|
69
|
+
vsanStatus(
|
|
70
|
+
createInfra({
|
|
71
|
+
datastores: [{ type: "vSAN" }],
|
|
72
|
+
}),
|
|
73
|
+
),
|
|
74
|
+
).toBe("enabled");
|
|
75
|
+
expect(
|
|
76
|
+
vsanStatus(createInfra(), { total: 10, diskTypes: { vSAN: {} } }),
|
|
77
|
+
).toBe("enabled");
|
|
78
|
+
expect(vsanStatus(createInfra({ datastores: [] }))).toBe("disabled");
|
|
79
|
+
expect(vsanStatus()).toBe("unknown");
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("buildInfrastructureSummary", () => {
|
|
84
|
+
it("maps inventory metadata into the summary card model", () => {
|
|
85
|
+
const summary = buildInfrastructureSummary({
|
|
86
|
+
infra: createInfra({
|
|
87
|
+
totalHosts: 12,
|
|
88
|
+
totalDatacenters: 1,
|
|
89
|
+
}),
|
|
90
|
+
vcenterVersion: "7.0.3.0",
|
|
91
|
+
vcenterId: "vc-1",
|
|
92
|
+
clusters: {},
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
expect(summary).toEqual({
|
|
96
|
+
vmwareVersion: "vSphere 7.0.3",
|
|
97
|
+
datacenters: 1,
|
|
98
|
+
vCenters: 1,
|
|
99
|
+
esxiHosts: 12,
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("falls back to clustersPerDatacenter length and unique vCenter ids", () => {
|
|
104
|
+
const summary = buildInfrastructureSummary({
|
|
105
|
+
infra: createInfra({
|
|
106
|
+
totalHosts: 7,
|
|
107
|
+
clustersPerDatacenter: [1, 1],
|
|
108
|
+
}),
|
|
109
|
+
clusters: {
|
|
110
|
+
A: {
|
|
111
|
+
infra: createInfra(),
|
|
112
|
+
vms: { total: 1 },
|
|
113
|
+
vcenter: { id: "vc-a" },
|
|
114
|
+
},
|
|
115
|
+
B: {
|
|
116
|
+
infra: createInfra(),
|
|
117
|
+
vms: { total: 1 },
|
|
118
|
+
vcenter: { id: "vc-b" },
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
expect(summary.datacenters).toBe(2);
|
|
124
|
+
expect(summary.vCenters).toBe(2);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe("countVCenters", () => {
|
|
129
|
+
it("returns undefined when no vCenter identity is present", () => {
|
|
130
|
+
expect(countVCenters(undefined, undefined)).toBeUndefined();
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe("cluster details", () => {
|
|
135
|
+
it("builds sorted aggregate rows from cluster features", () => {
|
|
136
|
+
const clusters: Record<string, InventoryCluster> = {
|
|
137
|
+
"Cluster-Dev-02": {
|
|
138
|
+
infra: createInfra({
|
|
139
|
+
totalHosts: 7,
|
|
140
|
+
hosts: [{ vmotionSupported: true }],
|
|
141
|
+
datastores: [],
|
|
142
|
+
}),
|
|
143
|
+
vms: { total: 350 },
|
|
144
|
+
clusterFeatures: { drsEnabled: false, haEnabled: true },
|
|
145
|
+
},
|
|
146
|
+
"Cluster-Prod-01": {
|
|
147
|
+
infra: createInfra({
|
|
148
|
+
totalHosts: 5,
|
|
149
|
+
hosts: [{ vmotionSupported: true }],
|
|
150
|
+
datastores: [{ type: "vSAN" }],
|
|
151
|
+
}),
|
|
152
|
+
vms: { total: 280 },
|
|
153
|
+
clusterFeatures: { drsEnabled: true, haEnabled: true },
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const rows = buildClusterDetailRows(clusters);
|
|
158
|
+
expect(rows.map((row) => row.name)).toEqual([
|
|
159
|
+
"Cluster-Dev-02",
|
|
160
|
+
"Cluster-Prod-01",
|
|
161
|
+
]);
|
|
162
|
+
expect(rows[0]).toMatchObject({
|
|
163
|
+
hosts: 7,
|
|
164
|
+
vms: 350,
|
|
165
|
+
vmotion: "enabled",
|
|
166
|
+
drs: "disabled",
|
|
167
|
+
vsan: "disabled",
|
|
168
|
+
});
|
|
169
|
+
expect(rows[1]).toMatchObject({
|
|
170
|
+
hosts: 5,
|
|
171
|
+
vms: 280,
|
|
172
|
+
drs: "enabled",
|
|
173
|
+
vsan: "enabled",
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("builds the detailed cluster model including HA and network labels", () => {
|
|
178
|
+
const details = buildClusterDetails({
|
|
179
|
+
infra: createInfra({
|
|
180
|
+
totalHosts: 7,
|
|
181
|
+
hosts: [{ vmotionSupported: true }],
|
|
182
|
+
networks: [
|
|
183
|
+
{ type: "distributed", name: "VDS-Dev", vlanId: "10" },
|
|
184
|
+
{ type: "distributed", name: "VDS-Dev", vlanId: "11" },
|
|
185
|
+
{ type: "dvswitch", name: "vDSwitch0" },
|
|
186
|
+
],
|
|
187
|
+
}),
|
|
188
|
+
vms: { total: 350 },
|
|
189
|
+
clusterFeatures: { drsEnabled: false, haEnabled: true },
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
expect(details).toMatchObject({
|
|
193
|
+
hosts: 7,
|
|
194
|
+
vms: 350,
|
|
195
|
+
networksDetected: 2,
|
|
196
|
+
vmotion: "enabled",
|
|
197
|
+
drs: "disabled",
|
|
198
|
+
ha: "enabled",
|
|
199
|
+
vsan: "disabled",
|
|
200
|
+
});
|
|
201
|
+
expect(details?.networks.map((network) => network.displayName)).toEqual([
|
|
202
|
+
"VDS-Dev (VLAN 10)",
|
|
203
|
+
"VDS-Dev (VLAN 11)",
|
|
204
|
+
]);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
describe("visibleNetworks", () => {
|
|
209
|
+
it("omits unnamed networks and dvswitches", () => {
|
|
210
|
+
expect(
|
|
211
|
+
visibleNetworks(
|
|
212
|
+
createInfra({
|
|
213
|
+
networks: [
|
|
214
|
+
{ type: "distributed", name: " " },
|
|
215
|
+
{ type: "dvswitch", name: "switch" },
|
|
216
|
+
{ type: "standard", name: "VM Network" },
|
|
217
|
+
],
|
|
218
|
+
}),
|
|
219
|
+
),
|
|
220
|
+
).toEqual([
|
|
221
|
+
{ name: "VM Network", vlanId: undefined, displayName: "VM Network" },
|
|
222
|
+
]);
|
|
223
|
+
});
|
|
224
|
+
});
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
buildHostPowerStateChart,
|
|
4
|
+
buildVmPowerStateChart,
|
|
5
|
+
} from "../powerStates.js";
|
|
6
|
+
|
|
7
|
+
describe("buildHostPowerStateChart", () => {
|
|
8
|
+
it("maps overallStatus green to powered on and keeps zero categories", () => {
|
|
9
|
+
const chart = buildHostPowerStateChart({ green: 7 });
|
|
10
|
+
|
|
11
|
+
expect(chart.total).toBe(7);
|
|
12
|
+
expect(chart.slices.map((slice) => [slice.name, slice.count])).toEqual([
|
|
13
|
+
["Powered on", 7],
|
|
14
|
+
["Powered off", 0],
|
|
15
|
+
["Standby", 0],
|
|
16
|
+
["Not responding", 0],
|
|
17
|
+
["Maintenance", 0],
|
|
18
|
+
]);
|
|
19
|
+
expect(chart.slices[0].countDisplay).toBe("7 hosts");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("maps explicit host power and maintenance keys", () => {
|
|
23
|
+
const chart = buildHostPowerStateChart({
|
|
24
|
+
poweredOn: 5,
|
|
25
|
+
poweredOff: 1,
|
|
26
|
+
standBy: 1,
|
|
27
|
+
notResponding: 1,
|
|
28
|
+
inMaintenanceMode: 2,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
expect(chart.total).toBe(10);
|
|
32
|
+
expect(
|
|
33
|
+
chart.slices.find((slice) => slice.name === "Maintenance")?.count,
|
|
34
|
+
).toBe(2);
|
|
35
|
+
expect(
|
|
36
|
+
chart.slices.find((slice) => slice.name === "Not responding")?.count,
|
|
37
|
+
).toBe(1);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("buildVmPowerStateChart", () => {
|
|
42
|
+
it("maps VM power states and omits unused suspended", () => {
|
|
43
|
+
const chart = buildVmPowerStateChart({
|
|
44
|
+
poweredOff: 297,
|
|
45
|
+
poweredOn: 53,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
expect(chart.total).toBe(350);
|
|
49
|
+
expect(chart.slices.map((slice) => [slice.name, slice.count])).toEqual([
|
|
50
|
+
["Powered off", 297],
|
|
51
|
+
["Powered on", 53],
|
|
52
|
+
]);
|
|
53
|
+
expect(chart.slices[0].countDisplay).toBe("297 VMs");
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("includes suspended when present", () => {
|
|
57
|
+
const chart = buildVmPowerStateChart({
|
|
58
|
+
poweredOn: 10,
|
|
59
|
+
poweredOff: 2,
|
|
60
|
+
suspended: 1,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
expect(chart.slices.map((slice) => slice.name)).toEqual([
|
|
64
|
+
"Powered off",
|
|
65
|
+
"Powered on",
|
|
66
|
+
"Suspended",
|
|
67
|
+
]);
|
|
68
|
+
expect(chart.slices[2].countDisplay).toBe("1 VM");
|
|
69
|
+
});
|
|
70
|
+
});
|
package/src/report/constants.ts
CHANGED
|
@@ -25,4 +25,6 @@ export const REPORT_CARD_EMPTY_STATE_TITLES = {
|
|
|
25
25
|
operatingSystems: "Operating system data not collected",
|
|
26
26
|
migrationStatus: "Migration status data not collected",
|
|
27
27
|
issuesBreakdown: "Issues breakdown data not collected",
|
|
28
|
+
hostPowerStates: "Host power state data not collected",
|
|
29
|
+
vmPowerStates: "VM power state data not collected",
|
|
28
30
|
} as const;
|
package/src/report/index.ts
CHANGED
|
@@ -17,6 +17,43 @@ export {
|
|
|
17
17
|
EmptySearchResults,
|
|
18
18
|
type EmptySearchResultsProps,
|
|
19
19
|
} from "./EmptySearchResults.js";
|
|
20
|
+
export {
|
|
21
|
+
FeatureStatusBadge,
|
|
22
|
+
type FeatureStatusBadgeProps,
|
|
23
|
+
} from "./FeatureStatusBadge.js";
|
|
24
|
+
export {
|
|
25
|
+
HostPowerStates,
|
|
26
|
+
type HostPowerStatesProps,
|
|
27
|
+
} from "./HostPowerStates.js";
|
|
28
|
+
export {
|
|
29
|
+
InfrastructureSummary,
|
|
30
|
+
type InfrastructureSummaryProps,
|
|
31
|
+
} from "./InfrastructureSummary.js";
|
|
32
|
+
export type {
|
|
33
|
+
ClusterDetailRow,
|
|
34
|
+
ClusterDetailsModel,
|
|
35
|
+
FeatureStatus,
|
|
36
|
+
HostCapability,
|
|
37
|
+
InfrastructureSummaryModel,
|
|
38
|
+
InventoryCluster,
|
|
39
|
+
InventoryDatastore,
|
|
40
|
+
InventoryHost,
|
|
41
|
+
InventoryInfra,
|
|
42
|
+
InventoryNetwork,
|
|
43
|
+
InventoryVms,
|
|
44
|
+
NetworkLabel,
|
|
45
|
+
} from "./infrastructureSummary.js";
|
|
46
|
+
export {
|
|
47
|
+
booleanToFeatureStatus,
|
|
48
|
+
buildClusterDetailRows,
|
|
49
|
+
buildClusterDetails,
|
|
50
|
+
buildInfrastructureSummary,
|
|
51
|
+
countVCenters,
|
|
52
|
+
formatVSphereVersion,
|
|
53
|
+
hostCapabilityStatus,
|
|
54
|
+
visibleNetworks,
|
|
55
|
+
vsanStatus,
|
|
56
|
+
} from "./infrastructureSummary.js";
|
|
20
57
|
export { OSBarChart, OSDistribution } from "./OSDistribution.js";
|
|
21
58
|
export { OsSupportTiersHelpPopover } from "./OsSupportTiersHelpPopover.js";
|
|
22
59
|
export { OsUpgradeNotice } from "./OsUpgradeNotice.js";
|
|
@@ -45,6 +82,17 @@ export {
|
|
|
45
82
|
SupportTier,
|
|
46
83
|
} from "./osSupportTier.js";
|
|
47
84
|
export { default as PopoverIcon } from "./PopoverIcon.js";
|
|
85
|
+
export {
|
|
86
|
+
PowerStateCard,
|
|
87
|
+
type PowerStateCardProps,
|
|
88
|
+
} from "./PowerStateCard.js";
|
|
89
|
+
export type { PowerStateChartModel } from "./powerStates.js";
|
|
90
|
+
export {
|
|
91
|
+
buildHostPowerStateChart,
|
|
92
|
+
buildVmPowerStateChart,
|
|
93
|
+
HOST_POWER_COLORS,
|
|
94
|
+
VM_POWER_COLORS,
|
|
95
|
+
} from "./powerStates.js";
|
|
48
96
|
export { SupportTierBadge } from "./SupportTierBadge.js";
|
|
49
97
|
export {
|
|
50
98
|
ALL_TIERS_FILTER,
|
|
@@ -52,3 +100,11 @@ export {
|
|
|
52
100
|
type OsTableRow,
|
|
53
101
|
useOsBarChartViewModel,
|
|
54
102
|
} from "./useOsBarChartViewModel.js";
|
|
103
|
+
export {
|
|
104
|
+
VCenterClusterDetails,
|
|
105
|
+
type VCenterClusterDetailsProps,
|
|
106
|
+
} from "./VCenterClusterDetails.js";
|
|
107
|
+
export {
|
|
108
|
+
VmPowerStates,
|
|
109
|
+
type VmPowerStatesProps,
|
|
110
|
+
} from "./VmPowerStates.js";
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
export type FeatureStatus = "enabled" | "disabled" | "unknown";
|
|
2
|
+
|
|
3
|
+
export type HostCapability = "vmotionSupported" | "storageVmotionSupported";
|
|
4
|
+
|
|
5
|
+
export type InventoryHost = {
|
|
6
|
+
vmotionSupported?: boolean;
|
|
7
|
+
storageVmotionSupported?: boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type InventoryDatastore = {
|
|
11
|
+
type?: string;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type InventoryNetwork = {
|
|
15
|
+
name: string;
|
|
16
|
+
type?: string;
|
|
17
|
+
vlanId?: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type InventoryInfra = {
|
|
21
|
+
totalHosts?: number;
|
|
22
|
+
totalDatacenters?: number;
|
|
23
|
+
hosts?: InventoryHost[];
|
|
24
|
+
clustersPerDatacenter?: number[];
|
|
25
|
+
networks?: InventoryNetwork[];
|
|
26
|
+
datastores?: InventoryDatastore[];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type InventoryVms = {
|
|
30
|
+
total?: number;
|
|
31
|
+
diskTypes?: Record<string, unknown>;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type InventoryCluster = {
|
|
35
|
+
infra?: InventoryInfra;
|
|
36
|
+
vms?: InventoryVms;
|
|
37
|
+
vcenter?: { id?: string };
|
|
38
|
+
clusterFeatures?: {
|
|
39
|
+
drsEnabled?: boolean;
|
|
40
|
+
haEnabled?: boolean;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type InfrastructureSummaryModel = {
|
|
45
|
+
vmwareVersion: string;
|
|
46
|
+
datacenters: number | undefined;
|
|
47
|
+
vCenters: number | undefined;
|
|
48
|
+
esxiHosts: number | undefined;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export type NetworkLabel = {
|
|
52
|
+
name: string;
|
|
53
|
+
vlanId?: string;
|
|
54
|
+
displayName: string;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export type ClusterDetailRow = {
|
|
58
|
+
id: string;
|
|
59
|
+
name: string;
|
|
60
|
+
hosts: number;
|
|
61
|
+
vms: number;
|
|
62
|
+
vmotion: FeatureStatus;
|
|
63
|
+
drs: FeatureStatus;
|
|
64
|
+
vsan: FeatureStatus;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export type ClusterDetailsModel = {
|
|
68
|
+
hosts: number;
|
|
69
|
+
vms: number;
|
|
70
|
+
networksDetected: number;
|
|
71
|
+
vmotion: FeatureStatus;
|
|
72
|
+
drs: FeatureStatus;
|
|
73
|
+
ha: FeatureStatus;
|
|
74
|
+
vsan: FeatureStatus;
|
|
75
|
+
networks: NetworkLabel[];
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
const VSAN_TYPE_PATTERN = /vsan/i;
|
|
79
|
+
|
|
80
|
+
export const formatVSphereVersion = (version?: string): string => {
|
|
81
|
+
if (!version?.trim()) {
|
|
82
|
+
return "—";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const withoutPrefix = version.trim().replace(/^vSphere\s+/i, "");
|
|
86
|
+
const parts = withoutPrefix.split(".");
|
|
87
|
+
while (parts.length > 3 && parts[parts.length - 1] === "0") {
|
|
88
|
+
parts.pop();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return `vSphere ${parts.join(".")}`;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export const booleanToFeatureStatus = (
|
|
95
|
+
value: boolean | undefined,
|
|
96
|
+
): FeatureStatus => {
|
|
97
|
+
if (value === true) {
|
|
98
|
+
return "enabled";
|
|
99
|
+
}
|
|
100
|
+
if (value === false) {
|
|
101
|
+
return "disabled";
|
|
102
|
+
}
|
|
103
|
+
return "unknown";
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
export const hostCapabilityStatus = (
|
|
107
|
+
hosts: InventoryHost[] | undefined,
|
|
108
|
+
capability: HostCapability,
|
|
109
|
+
): FeatureStatus => {
|
|
110
|
+
if (!hosts || hosts.length === 0) {
|
|
111
|
+
return "unknown";
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const known = hosts
|
|
115
|
+
.map((host) => host[capability])
|
|
116
|
+
.filter((value): value is boolean => typeof value === "boolean");
|
|
117
|
+
|
|
118
|
+
if (known.length === 0) {
|
|
119
|
+
return "unknown";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return known.some(Boolean) ? "enabled" : "disabled";
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export const vsanStatus = (
|
|
126
|
+
infra?: InventoryInfra,
|
|
127
|
+
vms?: InventoryVms,
|
|
128
|
+
): FeatureStatus => {
|
|
129
|
+
const datastoreMatch = infra?.datastores?.some((datastore) =>
|
|
130
|
+
VSAN_TYPE_PATTERN.test(datastore.type ?? ""),
|
|
131
|
+
);
|
|
132
|
+
const diskTypeMatch = Object.keys(vms?.diskTypes ?? {}).some((type) =>
|
|
133
|
+
VSAN_TYPE_PATTERN.test(type),
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
if (datastoreMatch || diskTypeMatch) {
|
|
137
|
+
return "enabled";
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (infra?.datastores || vms?.diskTypes) {
|
|
141
|
+
return "disabled";
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return "unknown";
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const hostCount = (infra?: InventoryInfra): number | undefined => {
|
|
148
|
+
if (typeof infra?.totalHosts === "number") {
|
|
149
|
+
return infra.totalHosts;
|
|
150
|
+
}
|
|
151
|
+
if (infra?.hosts) {
|
|
152
|
+
return infra.hosts.length;
|
|
153
|
+
}
|
|
154
|
+
return undefined;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const datacenterCount = (infra?: InventoryInfra): number | undefined => {
|
|
158
|
+
if (typeof infra?.totalDatacenters === "number") {
|
|
159
|
+
return infra.totalDatacenters;
|
|
160
|
+
}
|
|
161
|
+
if (infra?.clustersPerDatacenter && infra.clustersPerDatacenter.length > 0) {
|
|
162
|
+
return infra.clustersPerDatacenter.length;
|
|
163
|
+
}
|
|
164
|
+
return undefined;
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
export const countVCenters = (
|
|
168
|
+
clusters?: Record<string, InventoryCluster>,
|
|
169
|
+
vcenterId?: string,
|
|
170
|
+
): number | undefined => {
|
|
171
|
+
const ids = new Set<string>();
|
|
172
|
+
if (vcenterId) {
|
|
173
|
+
ids.add(vcenterId);
|
|
174
|
+
}
|
|
175
|
+
for (const cluster of Object.values(clusters ?? {})) {
|
|
176
|
+
if (cluster.vcenter?.id) {
|
|
177
|
+
ids.add(cluster.vcenter.id);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return ids.size > 0 ? ids.size : undefined;
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const toNetworkLabel = (network: InventoryNetwork): NetworkLabel => {
|
|
184
|
+
const vlanId = network.vlanId?.trim();
|
|
185
|
+
return {
|
|
186
|
+
name: network.name,
|
|
187
|
+
vlanId: vlanId || undefined,
|
|
188
|
+
displayName: vlanId ? `${network.name} (VLAN ${vlanId})` : network.name,
|
|
189
|
+
};
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
export const visibleNetworks = (infra?: InventoryInfra): NetworkLabel[] =>
|
|
193
|
+
(infra?.networks ?? [])
|
|
194
|
+
.filter(
|
|
195
|
+
(network) => Boolean(network.name?.trim()) && network.type !== "dvswitch",
|
|
196
|
+
)
|
|
197
|
+
.map(toNetworkLabel);
|
|
198
|
+
|
|
199
|
+
export const buildInfrastructureSummary = ({
|
|
200
|
+
infra,
|
|
201
|
+
vcenterVersion,
|
|
202
|
+
vcenterId,
|
|
203
|
+
clusters,
|
|
204
|
+
}: {
|
|
205
|
+
infra?: InventoryInfra;
|
|
206
|
+
vcenterVersion?: string;
|
|
207
|
+
vcenterId?: string;
|
|
208
|
+
clusters?: Record<string, InventoryCluster>;
|
|
209
|
+
}): InfrastructureSummaryModel => ({
|
|
210
|
+
vmwareVersion: formatVSphereVersion(vcenterVersion),
|
|
211
|
+
datacenters: datacenterCount(infra),
|
|
212
|
+
vCenters: countVCenters(clusters, vcenterId),
|
|
213
|
+
esxiHosts: hostCount(infra),
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const clusterFeatureStatus = (
|
|
217
|
+
cluster: InventoryCluster,
|
|
218
|
+
): Pick<ClusterDetailRow, "vmotion" | "drs" | "vsan"> & {
|
|
219
|
+
ha: FeatureStatus;
|
|
220
|
+
} => ({
|
|
221
|
+
vmotion: hostCapabilityStatus(cluster.infra?.hosts, "vmotionSupported"),
|
|
222
|
+
drs: booleanToFeatureStatus(cluster.clusterFeatures?.drsEnabled),
|
|
223
|
+
ha: booleanToFeatureStatus(cluster.clusterFeatures?.haEnabled),
|
|
224
|
+
vsan: vsanStatus(cluster.infra, cluster.vms),
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const compareClustersByVmCount = (
|
|
228
|
+
a: string,
|
|
229
|
+
b: string,
|
|
230
|
+
clusters: Record<string, InventoryCluster>,
|
|
231
|
+
): number => {
|
|
232
|
+
const vmsA = clusters[a]?.vms?.total ?? 0;
|
|
233
|
+
const vmsB = clusters[b]?.vms?.total ?? 0;
|
|
234
|
+
return vmsB - vmsA || a.localeCompare(b);
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
export const buildClusterDetailRows = (
|
|
238
|
+
clusters?: Record<string, InventoryCluster>,
|
|
239
|
+
): ClusterDetailRow[] => {
|
|
240
|
+
if (!clusters) {
|
|
241
|
+
return [];
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return Object.keys(clusters)
|
|
245
|
+
.sort((a, b) => compareClustersByVmCount(a, b, clusters))
|
|
246
|
+
.map((id) => {
|
|
247
|
+
const cluster = clusters[id];
|
|
248
|
+
const features = clusterFeatureStatus(cluster);
|
|
249
|
+
return {
|
|
250
|
+
id,
|
|
251
|
+
name: id,
|
|
252
|
+
hosts: hostCount(cluster.infra) ?? 0,
|
|
253
|
+
vms: cluster.vms?.total ?? 0,
|
|
254
|
+
vmotion: features.vmotion,
|
|
255
|
+
drs: features.drs,
|
|
256
|
+
vsan: features.vsan,
|
|
257
|
+
};
|
|
258
|
+
});
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
export const buildClusterDetails = (
|
|
262
|
+
cluster?: InventoryCluster,
|
|
263
|
+
): ClusterDetailsModel | undefined => {
|
|
264
|
+
if (!cluster) {
|
|
265
|
+
return undefined;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const networks = visibleNetworks(cluster.infra);
|
|
269
|
+
const features = clusterFeatureStatus(cluster);
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
hosts: hostCount(cluster.infra) ?? 0,
|
|
273
|
+
vms: cluster.vms?.total ?? 0,
|
|
274
|
+
networksDetected: networks.length,
|
|
275
|
+
vmotion: features.vmotion,
|
|
276
|
+
drs: features.drs,
|
|
277
|
+
ha: features.ha,
|
|
278
|
+
vsan: features.vsan,
|
|
279
|
+
networks,
|
|
280
|
+
};
|
|
281
|
+
};
|