Initial
This commit is contained in:
@@ -304,6 +304,9 @@
|
||||
<p>Search, sort, and attach credentials to the hosts used by RunPlans.</p>
|
||||
</div>
|
||||
<div class="page-actions">
|
||||
<button class="ghost-button compact" type="button" @click="openVCenterImport">
|
||||
<Download :size="16" />Import from vCenter
|
||||
</button>
|
||||
<button class="primary-action compact" type="button" @click="openHostModal()"><Plus :size="16" />Add host</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -414,6 +417,19 @@
|
||||
</ResourceTable>
|
||||
</section>
|
||||
|
||||
<VCenterImportModal
|
||||
:open="vcenterImportOpen"
|
||||
:preview-rows="vcenterPreviewRows"
|
||||
:credentials="credentials"
|
||||
:groups="groups"
|
||||
:loading="vcenterImportLoading"
|
||||
:error="vcenterImportError"
|
||||
:status="vcenterImportStatus"
|
||||
@close="vcenterImportOpen = false"
|
||||
@preview="previewVCenterImport"
|
||||
@import="importVCenterHosts"
|
||||
/>
|
||||
|
||||
<BaseModal
|
||||
:open="hostModalOpen"
|
||||
:title="hostForm.id ? 'Update host' : 'Add host'"
|
||||
@@ -574,6 +590,7 @@
|
||||
:label-for="settingLabel"
|
||||
:description-for="settingDescription"
|
||||
:placeholder-for="settingPlaceholder"
|
||||
:is-secret-setting="isSecretSetting"
|
||||
:source-label="settingSourceLabel"
|
||||
:is-boolean-setting="isBooleanSetting"
|
||||
:normalized-boolean-setting="normalizedBooleanSetting"
|
||||
@@ -917,6 +934,7 @@ import ResourceTable from './components/ui/ResourceTable.vue';
|
||||
import ScriptExplorer from './components/scripts/ScriptExplorer.vue';
|
||||
import ScriptTestRunModal from './components/scripts/ScriptTestRunModal.vue';
|
||||
import VariableEditorModal from './components/scripts/VariableEditorModal.vue';
|
||||
import VCenterImportModal from './components/hosts/VCenterImportModal.vue';
|
||||
import DashboardView from './views/DashboardView.vue';
|
||||
import ErrorView from './views/ErrorView.vue';
|
||||
import LoginView from './views/LoginView.vue';
|
||||
@@ -957,6 +975,11 @@ const scriptTestError = ref('');
|
||||
const userModalOpen = ref(false);
|
||||
const groupModalOpen = ref(false);
|
||||
const hostModalOpen = ref(false);
|
||||
const vcenterImportOpen = ref(false);
|
||||
const vcenterImportLoading = ref(false);
|
||||
const vcenterImportError = ref('');
|
||||
const vcenterPreviewRows = ref([]);
|
||||
const vcenterImportStatus = ref(null);
|
||||
const credentialModalOpen = ref(false);
|
||||
const runPlanModalOpen = ref(false);
|
||||
const variableModalOpen = ref(false);
|
||||
@@ -1075,6 +1098,35 @@ const settingMetadata = {
|
||||
label: 'Script Execution',
|
||||
description: 'Master safety switch that controls whether RunPlans can execute scripts on target hosts.',
|
||||
section: 'Execution'
|
||||
},
|
||||
vcenter_enabled: {
|
||||
label: 'VMware vCenter',
|
||||
description: 'Enable VM discovery and import through the VMware vCenter REST API.',
|
||||
section: 'Integrations'
|
||||
},
|
||||
vcenter_base_url: {
|
||||
label: 'vCenter Base URL',
|
||||
description: 'Root URL of the vCenter API endpoint used for VM inventory discovery.',
|
||||
placeholder: 'https://vcenter.contoso.local',
|
||||
section: 'Integrations'
|
||||
},
|
||||
vcenter_username: {
|
||||
label: 'vCenter Username',
|
||||
description: 'Account used to create vCenter API sessions for host import previews.',
|
||||
placeholder: 'administrator@vsphere.local',
|
||||
section: 'Integrations'
|
||||
},
|
||||
vcenter_password: {
|
||||
label: 'vCenter Password',
|
||||
description: 'Password used only by the backend service when authenticating to vCenter.',
|
||||
placeholder: 'Stored in app settings or pinned by VCENTER_PASSWORD',
|
||||
section: 'Integrations',
|
||||
secret: true
|
||||
},
|
||||
vcenter_allow_untrusted_tls: {
|
||||
label: 'Allow Untrusted vCenter TLS',
|
||||
description: 'Permit self-signed or private CA vCenter certificates for lab and internal deployments.',
|
||||
section: 'Integrations'
|
||||
}
|
||||
};
|
||||
const users = ref([]);
|
||||
@@ -1282,7 +1334,7 @@ const dashboardSettingsPreview = computed(() => ['server_fqdn', 'trusted_origins
|
||||
.filter((key) => settings.value[key])
|
||||
.map((key) => ({ key, ...settings.value[key] })));
|
||||
const configSections = computed(() => {
|
||||
const sections = ['Deployment', 'Authentication', 'Execution', 'Runtime', 'Other'];
|
||||
const sections = ['Deployment', 'Authentication', 'Integrations', 'Execution', 'Runtime', 'Other'];
|
||||
return sections
|
||||
.map((section) => ({
|
||||
section,
|
||||
@@ -2294,6 +2346,50 @@ async function saveHost() {
|
||||
notify('Host saved');
|
||||
}
|
||||
|
||||
async function openVCenterImport() {
|
||||
vcenterImportOpen.value = true;
|
||||
vcenterImportError.value = '';
|
||||
try {
|
||||
vcenterImportStatus.value = await api.get('/api/hosts/import/vcenter/status');
|
||||
} catch (err) {
|
||||
vcenterImportError.value = err.message;
|
||||
}
|
||||
}
|
||||
|
||||
async function previewVCenterImport(payload) {
|
||||
vcenterImportLoading.value = true;
|
||||
vcenterImportError.value = '';
|
||||
try {
|
||||
const result = await api.post('/api/hosts/import/vcenter/preview', payload);
|
||||
vcenterImportStatus.value = result.status;
|
||||
vcenterPreviewRows.value = result.candidates || [];
|
||||
notify(`Found ${result.count || 0} vCenter host candidate(s)`);
|
||||
} catch (err) {
|
||||
vcenterImportError.value = err.message;
|
||||
notify(err.message, 'error');
|
||||
} finally {
|
||||
vcenterImportLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function importVCenterHosts(payload) {
|
||||
vcenterImportLoading.value = true;
|
||||
vcenterImportError.value = '';
|
||||
try {
|
||||
const result = await api.post('/api/hosts/import/vcenter', payload);
|
||||
await refreshAll();
|
||||
vcenterPreviewRows.value = [];
|
||||
vcenterImportOpen.value = false;
|
||||
const skipped = result.skipped?.length || 0;
|
||||
notify(`Imported ${result.imported?.length || 0} host(s)${skipped ? `, skipped ${skipped} duplicate(s)` : ''}`);
|
||||
} catch (err) {
|
||||
vcenterImportError.value = err.message;
|
||||
notify(err.message, 'error');
|
||||
} finally {
|
||||
vcenterImportLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function resetHostForm() {
|
||||
Object.assign(hostForm, { id: null, name: '', address: '', fqdn: '', osFamily: 'windows', transport: 'winrm', credentialId: null, tagsText: '', visibility: 'shared', groupId: null });
|
||||
}
|
||||
@@ -2709,6 +2805,10 @@ function settingPlaceholder(key) {
|
||||
return settingMetadata[key]?.placeholder || '';
|
||||
}
|
||||
|
||||
function isSecretSetting(key) {
|
||||
return Boolean(settingMetadata[key]?.secret || /(?:password|secret|token|key)$/i.test(key));
|
||||
}
|
||||
|
||||
function settingSourceLabel(source) {
|
||||
return source === 'env' ? 'ENV' : 'DB';
|
||||
}
|
||||
@@ -2717,6 +2817,7 @@ function configSectionMeta(section) {
|
||||
const meta = {
|
||||
Deployment: { icon: Server, description: 'Reverse proxy, trusted origins, and public URLs.' },
|
||||
Authentication: { icon: ShieldCheck, description: 'Identity provider and password-flow configuration.' },
|
||||
Integrations: { icon: Download, description: 'External systems used for inventory, publishing, and governance workflows.' },
|
||||
Execution: { icon: PlayCircle, description: 'PowerShell execution guardrails.' },
|
||||
Runtime: { icon: Database, description: 'Container runtime paths and API process settings.' },
|
||||
Other: { icon: Settings, description: 'Additional app settings exposed by the API.' }
|
||||
|
||||
Reference in New Issue
Block a user