Comma-separated host list logs into every host, merges VM lists tagged by origin host, and routes SPICE connections to the right one. Also adds a username format hint (user@realm) on the login form.
183 lines
5.4 KiB
JavaScript
183 lines
5.4 KiB
JavaScript
/* global api */
|
|
|
|
const loadingEl = document.getElementById('loadingState');
|
|
const listEl = document.getElementById('vmList');
|
|
const emptyEl = document.getElementById('emptyState');
|
|
const errorEl = document.getElementById('error');
|
|
const noticeEl = document.getElementById('notice');
|
|
const refreshBtn = document.getElementById('refreshBtn');
|
|
const settingsBtn = document.getElementById('settingsBtn');
|
|
const settingsOverlay = document.getElementById('settingsOverlay');
|
|
const viewerPathDisplay = document.getElementById('viewerPathDisplay');
|
|
|
|
document.getElementById('logoutBtn').addEventListener('click', async () => {
|
|
await api.proxmox.logout();
|
|
});
|
|
|
|
refreshBtn.addEventListener('click', () => loadVMs());
|
|
|
|
// ── Settings (Windows only — VirtViewer's install folder name carries the
|
|
// version number, so auto-detection can miss non-standard installs) ──
|
|
|
|
initSettings();
|
|
|
|
async function initSettings() {
|
|
const platform = await api.settings.platform();
|
|
if (platform !== 'win32') return;
|
|
|
|
settingsBtn.classList.remove('hidden');
|
|
settingsBtn.addEventListener('click', openSettings);
|
|
document.getElementById('closeSettingsBtn').addEventListener('click', closeSettings);
|
|
document.getElementById('browseViewerBtn').addEventListener('click', async () => {
|
|
const chosen = await api.settings.browseViewerPath();
|
|
if (chosen) await refreshViewerPathDisplay();
|
|
});
|
|
document.getElementById('resetViewerBtn').addEventListener('click', async () => {
|
|
await api.settings.clearViewerPath();
|
|
await refreshViewerPathDisplay();
|
|
});
|
|
}
|
|
|
|
async function openSettings() {
|
|
await refreshViewerPathDisplay();
|
|
settingsOverlay.classList.remove('hidden');
|
|
}
|
|
|
|
function closeSettings() {
|
|
settingsOverlay.classList.add('hidden');
|
|
}
|
|
|
|
async function refreshViewerPathDisplay() {
|
|
const viewerPath = await api.settings.getViewerPath();
|
|
viewerPathDisplay.textContent = viewerPath || 'automatisch erkannt';
|
|
}
|
|
|
|
// ── Load on mount ─────────────────────────────────────────
|
|
|
|
async function loadVMs() {
|
|
clearMessages();
|
|
listEl.classList.add('hidden');
|
|
emptyEl.classList.add('hidden');
|
|
loadingEl.classList.remove('hidden');
|
|
|
|
const result = await api.proxmox.getVMs();
|
|
|
|
loadingEl.classList.add('hidden');
|
|
|
|
if (!result.success) {
|
|
showError(result.error || 'Fehler beim Laden der VMs.');
|
|
return;
|
|
}
|
|
|
|
const vms = result.vms;
|
|
|
|
if (vms.length === 0) {
|
|
emptyEl.classList.remove('hidden');
|
|
return;
|
|
}
|
|
|
|
// Auto-connect when there is exactly one SPICE VM
|
|
if (vms.length === 1) {
|
|
showNotice(`Nur eine VM verfügbar — verbinde mit „${vms[0].name}" …`);
|
|
await connectVM(vms[0]);
|
|
return;
|
|
}
|
|
|
|
renderList(vms);
|
|
}
|
|
|
|
loadVMs();
|
|
|
|
// ── Render ────────────────────────────────────────────────
|
|
|
|
function renderList(vms) {
|
|
// Only show the host when there's more than one — single-host setups don't need the noise.
|
|
const multiHost = new Set(vms.map((vm) => vm.host)).size > 1;
|
|
|
|
listEl.innerHTML = vms
|
|
.map(
|
|
(vm) => `
|
|
<div class="vm-card" data-vmid="${vm.vmid}">
|
|
<div class="vm-info">
|
|
<div class="vm-name">${esc(vm.name)}</div>
|
|
<div class="vm-meta">
|
|
<span class="status-dot"></span>
|
|
Läuft · ${multiHost ? `Host: ${esc(vm.host)} · ` : ''}Node: ${esc(vm.node)} · ID: ${vm.vmid}
|
|
</div>
|
|
</div>
|
|
<button
|
|
class="btn-connect"
|
|
data-vmid="${vm.vmid}"
|
|
data-host="${esc(vm.host)}"
|
|
data-node="${esc(vm.node)}"
|
|
data-name="${esc(vm.name)}"
|
|
>Verbinden</button>
|
|
</div>
|
|
`
|
|
)
|
|
.join('');
|
|
|
|
listEl.querySelectorAll('.btn-connect').forEach((btn) => {
|
|
btn.addEventListener('click', () =>
|
|
connectVM({
|
|
vmid: btn.dataset.vmid,
|
|
host: btn.dataset.host,
|
|
node: btn.dataset.node,
|
|
name: btn.dataset.name,
|
|
})
|
|
);
|
|
});
|
|
|
|
listEl.classList.remove('hidden');
|
|
}
|
|
|
|
// ── Connect ───────────────────────────────────────────────
|
|
|
|
async function connectVM(vm) {
|
|
clearMessages();
|
|
setAllButtons(true, 'Verbinde…');
|
|
|
|
const result = await api.proxmox.connect(vm);
|
|
|
|
setAllButtons(false, 'Verbinden');
|
|
|
|
if (!result.success) {
|
|
showError(result.error || 'Verbindung fehlgeschlagen.');
|
|
} else {
|
|
showNotice(`„${vm.name}" — SPICE-Sitzung gestartet.`);
|
|
}
|
|
}
|
|
|
|
// ── Helpers ───────────────────────────────────────────────
|
|
|
|
function setAllButtons(disabled, label) {
|
|
listEl.querySelectorAll('.btn-connect').forEach((b) => {
|
|
b.disabled = disabled;
|
|
b.textContent = label;
|
|
});
|
|
}
|
|
|
|
function showError(msg) {
|
|
errorEl.textContent = msg;
|
|
errorEl.classList.remove('hidden');
|
|
}
|
|
|
|
function showNotice(msg) {
|
|
noticeEl.textContent = msg;
|
|
noticeEl.classList.remove('hidden');
|
|
setTimeout(() => noticeEl.classList.add('hidden'), 5000);
|
|
}
|
|
|
|
function clearMessages() {
|
|
errorEl.classList.add('hidden');
|
|
noticeEl.classList.add('hidden');
|
|
}
|
|
|
|
function esc(str) {
|
|
return String(str)
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"');
|
|
}
|