Files
proxmox-spice-client/src/renderer/vms.js
T
aria.hacker ebb405ab9b Support multiple standalone Proxmox hosts with shared credentials
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.
2026-07-06 10:29:44 +02:00

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 &nbsp;·&nbsp; ${multiHost ? `Host: ${esc(vm.host)} &nbsp;·&nbsp; ` : ''}Node: ${esc(vm.node)} &nbsp;·&nbsp; 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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;');
}