This commit is contained in:
2026-06-25 13:58:04 -05:00
parent 411e8325b0
commit acc94dc6a1
6 changed files with 91 additions and 19 deletions

View File

@@ -39,12 +39,14 @@ export class VCenterTraceError extends Error {
export function getVCenterSettings() {
const apiMode = getStringSetting('vcenter_api_mode', config.vcenter.apiMode || 'auto').toLowerCase();
const requestTimeoutMs = Number(getStringSetting('vcenter_request_timeout_ms', String(config.vcenter.requestTimeoutMs || 20000)));
return {
enabled: getBooleanSetting('vcenter_enabled', config.vcenter.enabled),
baseUrl: normalizeBaseUrl(getStringSetting('vcenter_base_url', config.vcenter.baseUrl)),
username: getStringSetting('vcenter_username', config.vcenter.username),
password: getStringSetting('vcenter_password', config.vcenter.password),
apiMode: ['auto', 'api', 'rest'].includes(apiMode) ? apiMode : 'auto',
requestTimeoutMs: Number.isFinite(requestTimeoutMs) && requestTimeoutMs > 0 ? requestTimeoutMs : 20000,
allowUntrustedTls: getBooleanSetting('vcenter_allow_untrusted_tls', config.vcenter.allowUntrustedTls)
};
}
@@ -61,6 +63,7 @@ export function vcenterStatus() {
baseUrl: settings.baseUrl,
username: settings.username,
apiMode: settings.apiMode,
requestTimeoutMs: settings.requestTimeoutMs,
allowUntrustedTls: settings.allowUntrustedTls
};
}
@@ -137,7 +140,8 @@ async function vcenterFetch(settings, path, { method = 'GET', sessionId, allow40
response = await fetch(`${settings.baseUrl}${path}`, {
method,
headers,
dispatcher: dispatcherFor(settings)
dispatcher: dispatcherFor(settings),
signal: AbortSignal.timeout(settings.requestTimeoutMs)
});
payload = await parseVCenterResponse(response);
} catch (err) {
@@ -150,9 +154,15 @@ async function vcenterFetch(settings, path, { method = 'GET', sessionId, allow40
'vmware-api-session-id': headers['vmware-api-session-id'] ? '[redacted session id]' : undefined
},
durationMs: Date.now() - startedAt,
networkError: err.message
timeoutMs: settings.requestTimeoutMs,
networkError: err.name === 'TimeoutError'
? `Timed out after ${settings.requestTimeoutMs} ms waiting for vCenter.`
: err.message
});
throw new VCenterTraceError(`Unable to reach vCenter ${method} ${path}: ${err.message}`, trace);
const message = err.name === 'TimeoutError'
? `Timed out after ${settings.requestTimeoutMs} ms waiting for vCenter ${method} ${path}.`
: `Unable to reach vCenter ${method} ${path}: ${err.message}`;
throw new VCenterTraceError(message, trace);
}
pushTrace(trace, {
@@ -225,12 +235,19 @@ function allIpsFromInterfaces(interfaces = []) {
}
function inferOsFamily(identity = {}) {
const haystack = [identity.family, identity.name, identity.full_name, identity.guest_OS].filter(Boolean).join(' ').toLowerCase();
const haystack = [identity.family, identity.name, localizedText(identity.full_name), identity.guest_OS].filter(Boolean).join(' ').toLowerCase();
if (WINDOWS_HINTS.some((hint) => haystack.includes(hint))) return 'windows';
if (LINUX_HINTS.some((hint) => haystack.includes(hint))) return 'linux';
return 'windows';
}
function localizedText(value) {
if (!value) return '';
if (typeof value === 'string') return value;
if (typeof value === 'object') return value.default_message || value.id || '';
return String(value);
}
export function normalizeVCenterVm(vm, identity = null, interfacesPayload = null) {
const interfaces = unwrapList(interfacesPayload);
const interfaceIp = firstIpFromInterfaces(interfaces);
@@ -249,7 +266,7 @@ export function normalizeVCenterVm(vm, identity = null, interfacesPayload = null
ipAddresses,
powerState: vm.power_state || vm.powerState || '',
osFamily: inferOsFamily(identity || {}),
guestFullName: identity?.full_name || identity?.name || '',
guestFullName: localizedText(identity?.full_name) || identity?.name || '',
toolsAvailable: Boolean(identity || interfaces.length),
source: 'vcenter'
};
@@ -285,16 +302,16 @@ export function vmMatchesFilter(candidate, filter = {}) {
if (operator === 'endsWith') return values.some((value) => value.endsWith(needle));
return values.some((value) => value.includes(needle));
}
const haystack = {
name: candidate.name,
hostname: candidate.name || candidate.fqdn,
fqdn: candidate.fqdn || candidate.address,
}[field] || candidate.name;
const value = String(haystack || '').toLowerCase();
if (operator === 'equals') return value === needle;
if (operator === 'startsWith') return value.startsWith(needle);
if (operator === 'endsWith') return value.endsWith(needle);
return value.includes(needle);
const haystacks = {
name: [candidate.name],
hostname: [candidate.name, candidate.fqdn, candidate.address].filter(Boolean),
fqdn: [candidate.fqdn, candidate.address].filter(Boolean),
}[field] || [candidate.name];
const values = haystacks.map((value) => String(value || '').toLowerCase());
if (operator === 'equals') return values.some((value) => value === needle);
if (operator === 'startsWith') return values.some((value) => value.startsWith(needle));
if (operator === 'endsWith') return values.some((value) => value.endsWith(needle));
return values.some((value) => value.includes(needle));
}
export function filterSummaryVms(vms = [], filter = {}) {