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.' }
|
||||
|
||||
240
client/src/components/hosts/VCenterImportModal.vue
Normal file
240
client/src/components/hosts/VCenterImportModal.vue
Normal file
@@ -0,0 +1,240 @@
|
||||
<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."
|
||||
wide
|
||||
@close="$emit('close')"
|
||||
>
|
||||
<form class="vcenter-import-form" @submit.prevent="submitImport">
|
||||
<section class="wizard-section">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vcenter-filter-grid">
|
||||
<label>
|
||||
<span>Field</span>
|
||||
<select v-model="draft.filter.field">
|
||||
<option value="hostname">Hostname / VM name</option>
|
||||
<option value="name">VM name only</option>
|
||||
<option value="fqdn">Guest FQDN</option>
|
||||
<option value="ip">IP address</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>Operator</span>
|
||||
<select v-model="draft.filter.operator">
|
||||
<option value="contains">Contains</option>
|
||||
<option value="equals">Equals</option>
|
||||
<option value="startsWith">Starts with</option>
|
||||
<option value="endsWith">Ends with</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="filter-value">
|
||||
<span>Value</span>
|
||||
<input v-model="draft.filter.value" placeholder="ENG-ENT" />
|
||||
</label>
|
||||
<label>
|
||||
<span>Preview limit</span>
|
||||
<input v-model.number="draft.limit" type="number" min="1" max="500" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="wizard-actions">
|
||||
<button class="ghost-button" type="button" :disabled="loading" @click="emitPreview">
|
||||
<Search :size="16" />Preview matches
|
||||
</button>
|
||||
<span v-if="status" class="vcenter-status">
|
||||
<span :class="['status-dot', status.configured ? 'success' : 'warning']"></span>
|
||||
{{ status.configured ? `Connected settings for ${status.baseUrl}` : 'vCenter settings incomplete' }}
|
||||
</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="wizard-section">
|
||||
<div class="section-heading">
|
||||
<span>2</span>
|
||||
<div>
|
||||
<h4>Import options</h4>
|
||||
<p>Attach authentication and host defaults once so every created host is ready for RunPlans.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vcenter-options-grid">
|
||||
<label>
|
||||
<span>Vault credential</span>
|
||||
<select v-model="draft.credentialId">
|
||||
<option :value="null">No credential</option>
|
||||
<option v-for="credential in credentials" :key="credential.id" :value="credential.id">{{ credential.name }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>Transport</span>
|
||||
<select v-model="draft.transport">
|
||||
<option value="winrm">WinRM</option>
|
||||
<option value="ssh">SSH</option>
|
||||
<option value="local">Local</option>
|
||||
<option value="api">API</option>
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
<span>Port</span>
|
||||
<input v-model.number="draft.port" type="number" min="1" max="65535" placeholder="Default" />
|
||||
</label>
|
||||
<label>
|
||||
<span>Visibility</span>
|
||||
<select v-model="draft.visibility">
|
||||
<option value="shared">Shared</option>
|
||||
<option value="personal">Personal</option>
|
||||
<option value="group">Group</option>
|
||||
</select>
|
||||
</label>
|
||||
<label v-if="draft.visibility === 'group'">
|
||||
<span>Group</span>
|
||||
<select v-model="draft.groupId">
|
||||
<option :value="null">Choose group</option>
|
||||
<option v-for="group in groups" :key="group.id" :value="group.id">{{ group.name }}</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="option-tags">
|
||||
<span>Additional tags</span>
|
||||
<input v-model="draft.tagsText" placeholder="engineering, intune-ready" />
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="wizard-section preview-section">
|
||||
<div class="section-heading">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="error" class="inline-error">{{ error }}</div>
|
||||
<div class="preview-toolbar">
|
||||
<button class="ghost-button compact" type="button" :disabled="!previewRows.length" @click="toggleAll">
|
||||
<CheckSquare :size="15" />{{ allSelected ? 'Clear selection' : 'Select all' }}
|
||||
</button>
|
||||
<span>{{ selectedIds.length }} of {{ previewRows.length }} selected</span>
|
||||
</div>
|
||||
<div class="vcenter-preview-table">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Select</th>
|
||||
<th>VM / Hostname</th>
|
||||
<th>FQDN</th>
|
||||
<th>Address</th>
|
||||
<th>OS</th>
|
||||
<th>Power</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="row in previewRows" :key="row.id">
|
||||
<td>
|
||||
<input type="checkbox" :checked="selectedIds.includes(row.id)" @change="toggleRow(row.id)" />
|
||||
</td>
|
||||
<td>
|
||||
<strong>{{ row.name }}</strong>
|
||||
<small>{{ row.id }}</small>
|
||||
</td>
|
||||
<td><code>{{ row.fqdn || 'Unavailable' }}</code></td>
|
||||
<td><code>{{ row.address || row.ipAddress || 'Unavailable' }}</code></td>
|
||||
<td><span class="status-pill">{{ row.osFamily }}</span></td>
|
||||
<td>{{ row.powerState || '-' }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div v-if="!previewRows.length" class="wizard-empty">
|
||||
Run a preview to see matching virtual machines before importing hosts.
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div class="form-actions modal-actions">
|
||||
<button class="ghost-button" type="button" @click="$emit('close')">Cancel</button>
|
||||
<button class="primary-action" type="submit" :disabled="loading || !selectedIds.length">
|
||||
<DownloadCloud :size="17" />Import {{ selectedIds.length || '' }} host{{ selectedIds.length === 1 ? '' : 's' }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</BaseModal>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, reactive, ref, watch } from 'vue';
|
||||
import { CheckSquare, DownloadCloud, Search } from '@lucide/vue';
|
||||
import BaseModal from '../ui/BaseModal.vue';
|
||||
|
||||
const props = defineProps({
|
||||
open: Boolean,
|
||||
previewRows: { type: Array, default: () => [] },
|
||||
credentials: { type: Array, default: () => [] },
|
||||
groups: { type: Array, default: () => [] },
|
||||
loading: Boolean,
|
||||
error: { type: String, default: '' },
|
||||
status: { type: Object, default: null }
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close', 'preview', 'import']);
|
||||
|
||||
const draft = reactive({
|
||||
filter: { field: 'hostname', operator: 'contains', value: 'ENG-ENT' },
|
||||
limit: 100,
|
||||
credentialId: null,
|
||||
transport: 'winrm',
|
||||
port: null,
|
||||
visibility: 'shared',
|
||||
groupId: null,
|
||||
tagsText: ''
|
||||
});
|
||||
const selectedIds = ref([]);
|
||||
|
||||
const allSelected = computed(() => props.previewRows.length > 0 && selectedIds.value.length === props.previewRows.length);
|
||||
|
||||
watch(() => props.previewRows, (rows) => {
|
||||
selectedIds.value = rows.map((row) => row.id);
|
||||
});
|
||||
|
||||
watch(() => props.open, (open) => {
|
||||
if (open && props.previewRows.length) selectedIds.value = props.previewRows.map((row) => row.id);
|
||||
});
|
||||
|
||||
function payloadBase() {
|
||||
return {
|
||||
filter: { ...draft.filter, value: draft.filter.value.trim() },
|
||||
limit: Number(draft.limit) || 100
|
||||
};
|
||||
}
|
||||
|
||||
function emitPreview() {
|
||||
emit('preview', payloadBase());
|
||||
}
|
||||
|
||||
function toggleRow(id) {
|
||||
selectedIds.value = selectedIds.value.includes(id)
|
||||
? selectedIds.value.filter((selectedId) => selectedId !== id)
|
||||
: [...selectedIds.value, id];
|
||||
}
|
||||
|
||||
function toggleAll() {
|
||||
selectedIds.value = allSelected.value ? [] : props.previewRows.map((row) => row.id);
|
||||
}
|
||||
|
||||
function submitImport() {
|
||||
emit('import', {
|
||||
...payloadBase(),
|
||||
vmIds: selectedIds.value,
|
||||
credentialId: draft.credentialId || null,
|
||||
transport: draft.transport,
|
||||
port: draft.port ? Number(draft.port) : null,
|
||||
visibility: draft.visibility,
|
||||
groupId: draft.visibility === 'group' ? draft.groupId : null,
|
||||
tags: draft.tagsText.split(',').map((tag) => tag.trim()).filter(Boolean)
|
||||
});
|
||||
}
|
||||
</script>
|
||||
@@ -29,7 +29,7 @@
|
||||
<strong>{{ normalizedBooleanSetting(row.value) ? 'Enabled' : 'Disabled' }}</strong>
|
||||
</button>
|
||||
<textarea v-else-if="key === 'trusted_origins'" v-model="row.value" class="settings-input settings-textarea" :placeholder="placeholderFor(key)"></textarea>
|
||||
<input v-else v-model="row.value" class="settings-input" :placeholder="placeholderFor(key)" />
|
||||
<input v-else v-model="row.value" :type="isSecretSetting(key) ? 'password' : 'text'" class="settings-input" :placeholder="placeholderFor(key)" />
|
||||
</div>
|
||||
<span :class="['setting-source', row.source]">{{ sourceLabel(row.source) }}</span>
|
||||
</div>
|
||||
@@ -48,6 +48,7 @@ defineProps({
|
||||
labelFor: { type: Function, required: true },
|
||||
descriptionFor: { type: Function, required: true },
|
||||
placeholderFor: { type: Function, required: true },
|
||||
isSecretSetting: { type: Function, default: () => false },
|
||||
sourceLabel: { type: Function, required: true },
|
||||
isBooleanSetting: { type: Function, required: true },
|
||||
normalizedBooleanSetting: { type: Function, required: true }
|
||||
|
||||
@@ -6867,13 +6867,188 @@ input:read-only {
|
||||
box-shadow: 0 -18px 34px rgba(0, 0, 0, .24);
|
||||
}
|
||||
|
||||
.vcenter-import-form {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
max-height: min(74vh, 820px);
|
||||
overflow: auto;
|
||||
padding: 2px 2px 0;
|
||||
}
|
||||
|
||||
.wizard-section {
|
||||
border: 1px solid rgba(139, 224, 255, .14);
|
||||
border-radius: 18px;
|
||||
background:
|
||||
linear-gradient(135deg, rgba(88, 221, 255, .075), rgba(255, 255, 255, .025)),
|
||||
rgba(7, 13, 28, .52);
|
||||
padding: 16px;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .045);
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
display: grid;
|
||||
grid-template-columns: 34px minmax(0, 1fr);
|
||||
gap: 12px;
|
||||
align-items: start;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.section-heading > span {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
border-radius: 12px;
|
||||
color: #06101d;
|
||||
font-weight: 900;
|
||||
background: linear-gradient(135deg, #75d8ff, #a579ff 55%, #ffe4f1);
|
||||
box-shadow: 0 12px 30px rgba(108, 122, 255, .28);
|
||||
}
|
||||
|
||||
.section-heading h4 {
|
||||
margin: 0;
|
||||
color: rgba(255, 255, 255, .9);
|
||||
font-size: .98rem;
|
||||
}
|
||||
|
||||
.section-heading p {
|
||||
margin: 4px 0 0;
|
||||
color: rgba(219, 227, 255, .62);
|
||||
font-size: .82rem;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.vcenter-filter-grid,
|
||||
.vcenter-options-grid {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(150px, .85fr) minmax(150px, .85fr) minmax(220px, 1.35fr) minmax(120px, .55fr);
|
||||
gap: 12px;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.vcenter-options-grid {
|
||||
grid-template-columns: repeat(3, minmax(170px, 1fr));
|
||||
}
|
||||
|
||||
.vcenter-filter-grid label,
|
||||
.vcenter-options-grid label {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.vcenter-filter-grid .filter-value,
|
||||
.vcenter-options-grid .option-tags {
|
||||
grid-column: span 1;
|
||||
}
|
||||
|
||||
.wizard-actions,
|
||||
.preview-toolbar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-top: 14px;
|
||||
}
|
||||
|
||||
.vcenter-status,
|
||||
.preview-toolbar span {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: rgba(219, 227, 255, .68);
|
||||
font-size: .82rem;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border-radius: 999px;
|
||||
background: #f8c66a;
|
||||
box-shadow: 0 0 14px rgba(248, 198, 106, .62);
|
||||
}
|
||||
|
||||
.status-dot.success {
|
||||
background: #66f2b5;
|
||||
box-shadow: 0 0 14px rgba(102, 242, 181, .62);
|
||||
}
|
||||
|
||||
.inline-error {
|
||||
border: 1px solid rgba(255, 124, 152, .28);
|
||||
border-radius: 14px;
|
||||
padding: 10px 12px;
|
||||
color: #ffb7c7;
|
||||
background: rgba(255, 65, 112, .08);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.vcenter-preview-table {
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
border: 1px solid rgba(139, 224, 255, .12);
|
||||
border-radius: 16px;
|
||||
background: rgba(3, 8, 18, .36);
|
||||
}
|
||||
|
||||
.vcenter-preview-table table {
|
||||
width: 100%;
|
||||
min-width: 820px;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.vcenter-preview-table th,
|
||||
.vcenter-preview-table td {
|
||||
padding: 12px 14px;
|
||||
border-bottom: 1px solid rgba(139, 224, 255, .08);
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.vcenter-preview-table th {
|
||||
color: rgba(151, 221, 255, .78);
|
||||
font-size: .68rem;
|
||||
letter-spacing: .14em;
|
||||
text-transform: uppercase;
|
||||
background: rgba(7, 13, 28, .72);
|
||||
}
|
||||
|
||||
.vcenter-preview-table td {
|
||||
color: rgba(238, 242, 255, .84);
|
||||
font-size: .84rem;
|
||||
}
|
||||
|
||||
.vcenter-preview-table strong,
|
||||
.vcenter-preview-table small {
|
||||
display: block;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.vcenter-preview-table small {
|
||||
margin-top: 4px;
|
||||
color: rgba(219, 227, 255, .42);
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.vcenter-preview-table input[type="checkbox"] {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
accent-color: #75d8ff;
|
||||
}
|
||||
|
||||
.wizard-empty {
|
||||
padding: 28px 18px;
|
||||
color: rgba(219, 227, 255, .56);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 1180px) {
|
||||
.deployment-section-body.three,
|
||||
.deployment-section-body.two,
|
||||
.return-code-row,
|
||||
.assignment-row,
|
||||
.relationship-row,
|
||||
.intune-deployment-form .installer-analyze-row {
|
||||
.intune-deployment-form .installer-analyze-row,
|
||||
.vcenter-filter-grid,
|
||||
.vcenter-options-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user