Initial commit — Proxmox SPICE Client (Electron)

This commit is contained in:
aria.hacker
2026-07-01 09:23:50 +02:00
commit efb8c7f446
15 changed files with 1223 additions and 0 deletions
+134
View File
@@ -0,0 +1,134 @@
/* 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');
document.getElementById('logoutBtn').addEventListener('click', async () => {
await api.proxmox.logout();
});
refreshBtn.addEventListener('click', () => loadVMs());
// ── 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) {
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; Node: ${esc(vm.node)} &nbsp;·&nbsp; ID: ${vm.vmid}
</div>
</div>
<button
class="btn-connect"
data-vmid="${vm.vmid}"
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, 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;');
}