This commit is contained in:
2026-06-25 18:02:16 -05:00
parent 402bf6782a
commit da16e5ff56
17 changed files with 1475 additions and 69 deletions

View File

@@ -1,9 +1,9 @@
<template>
<BaseModal
:open="open"
title="Import hosts from vCenter"
eyebrow="VMWARE VCENTER"
description="Search vCenter virtual machines, preview discovered identity data, and create managed hosts with one shared vault credential."
title="Import VMware hosts"
eyebrow="VMWARE"
description="Import virtual machines from vCenter or standalone ESXi inventory into the Host Library."
wide
@close="$emit('close')"
>
@@ -12,17 +12,17 @@
<div class="section-heading">
<span>1</span>
<div>
<h4>Search filter</h4>
<p>Use vCenter VM names, guest hostnames, FQDNs, or IPs to narrow the import set before writing hosts.</p>
<h4>{{ isStandaloneHost ? 'ESXi VM inventory' : 'Search filter' }}</h4>
<p>{{ isStandaloneHost ? 'Use the vSphere Web Services API on the selected ESXi host to enumerate guest VMs before writing hosts.' : 'Use vCenter VM names, guest hostnames, FQDNs, or IPs to narrow the import set before writing hosts.' }}</p>
</div>
</div>
<div class="vcenter-filter-grid">
<label class="filter-value">
<span>vCenter system</span>
<span>VMware connection</span>
<select v-model="draft.connectionId">
<option :value="null">Configured default</option>
<option v-for="connection in importConnections" :key="connection.id" :value="connection.id">
{{ connection.name }} - {{ connection.baseUrl }}
{{ connection.name }} - {{ connection.targetType === 'host' ? 'Standalone ESXi' : 'vCenter' }} - {{ connection.baseUrl }}
</option>
</select>
</label>
@@ -52,14 +52,19 @@
<span>Preview limit</span>
<input v-model.number="draft.limit" type="number" min="1" max="500" />
</label>
<div v-if="isStandaloneHost" class="standalone-host-callout">
<strong>{{ selectedConnection?.name || 'Selected ESXi host' }}</strong>
<span>{{ selectedConnection?.baseUrl || 'Choose a standalone ESXi connection above.' }}</span>
<small>Standalone ESXi inventory is queried through the /sdk Web Services endpoint.</small>
</div>
</div>
<div class="wizard-actions">
<button class="ghost-button" type="button" :disabled="loading" @click="emitPreview">
<i class="mdi mdi-magnify" aria-hidden="true"></i>Preview matches
<i class="mdi mdi-magnify" aria-hidden="true"></i>{{ isStandaloneHost ? 'Preview VMs' : 'Preview matches' }}
</button>
<span v-if="status" class="vcenter-status">
<span :class="['status-dot', activeStatus.configured ? 'success' : 'warning']"></span>
{{ activeStatus.configured ? `Using ${activeStatus.name || 'vCenter'} at ${activeStatus.baseUrl}` : 'vCenter settings incomplete' }}
{{ activeStatus.configured ? `Using ${activeStatus.name || activeStatus.typeLabel || 'VMware'} at ${activeStatus.baseUrl}` : 'VMware settings incomplete' }}
</span>
<button v-if="traceCount" class="ghost-button compact" type="button" @click="$emit('open-trace')">
<i class="mdi mdi-file-search-outline" aria-hidden="true"></i>View API trace
@@ -123,11 +128,11 @@
<span>3</span>
<div>
<h4>Preview and select</h4>
<p>Guest FQDN/IP data depends on VMware Tools. Missing values are imported with the VM name as the address fallback.</p>
<p>{{ isStandaloneHost ? 'Guest FQDN/IP data comes from VMware Tools through ESXi. Missing values are imported with the VM name as the address fallback.' : 'Guest FQDN/IP data depends on VMware Tools. Missing values are imported with the VM name as the address fallback.' }}</p>
</div>
</div>
<div v-if="error" class="inline-error">{{ error }}</div>
<div v-if="diagnostics" class="vcenter-diagnostics">
<div v-if="diagnostics && !diagnostics.standaloneHost" class="vcenter-diagnostics">
<strong>Preview diagnostics</strong>
<span>{{ diagnostics.totalFromVCenter }} VM(s) returned by vCenter</span>
<span>{{ diagnostics.summaryMatched }} VM(s) matched before guest enrichment</span>
@@ -137,6 +142,15 @@
</button>
<small v-if="diagnostics.sampleNames?.length">Sample inventory names: {{ diagnostics.sampleNames.join(', ') }}</small>
</div>
<div v-else-if="diagnostics?.standaloneHost" class="vcenter-diagnostics">
<strong>Standalone ESXi preview</strong>
<span>{{ diagnostics.totalFromVCenter }} VM(s) returned by ESXi Web Services inventory</span>
<span>{{ diagnostics.summaryMatched }} VM(s) matched the current filter</span>
<span>{{ diagnostics.scanned }} VM(s) scanned for guest FQDN/IP details</span>
<button v-if="traceCount" class="ghost-button compact" type="button" @click="$emit('open-trace')">
<i class="mdi mdi-file-search-outline" aria-hidden="true"></i>Trace
</button>
</div>
<div class="preview-toolbar">
<button class="ghost-button compact" type="button" :disabled="!previewRows.length" @click="toggleAll">
<i class="mdi mdi-checkbox-marked-outline" aria-hidden="true"></i>{{ allSelected ? 'Clear selection' : 'Select all' }}
@@ -219,13 +233,16 @@ const draft = reactive({
});
const selectedIds = ref([]);
const importConnections = computed(() => props.connections.filter((connection) => connection.targetType !== 'host'));
const importConnections = computed(() => props.connections);
const selectedConnection = computed(() => props.connections.find((item) => item.id === draft.connectionId) || null);
const isStandaloneHost = computed(() => selectedConnection.value?.targetType === 'host');
const allSelected = computed(() => props.previewRows.length > 0 && selectedIds.value.length === props.previewRows.length);
const activeStatus = computed(() => {
if (draft.connectionId) {
const connection = importConnections.value.find((item) => item.id === draft.connectionId);
const connection = selectedConnection.value;
return {
name: connection?.name || 'Selected vCenter',
name: connection?.name || 'Selected VMware connection',
typeLabel: connection?.targetType === 'host' ? 'ESXi host' : 'vCenter',
configured: Boolean(connection?.baseUrl && connection?.username),
baseUrl: connection?.baseUrl || ''
};