(() => { const form = document.querySelector('[data-host-form]'); if (!form) return; const idInput = form.querySelector('input[name="id"]'); const nameInput = form.querySelector('input[name="name"]'); const hostInput = form.querySelector('input[name="host"]'); const portInput = form.querySelector('input[name="port"]'); const userInput = form.querySelector('input[name="username"]'); const authSelect = form.querySelector('select[name="auth_type"]'); const keyInput = form.querySelector('input[name="key_path"]'); const passInput = form.querySelector('input[name="password"]'); const imageInput = form.querySelector('input[name="image_url"]'); const submitBtn = form.querySelector('[data-host-submit]'); const modal = document.querySelector('[data-host-modal]'); const modalTitle = document.querySelector('[data-host-modal-title]'); const closeBtn = document.querySelector('[data-host-close]'); const newBtn = document.querySelector('[data-host-new]'); const checkAllBtn = document.querySelector('[data-host-check-all]'); const cancelBtn = form.querySelector('[data-host-cancel]'); let initialSnapshot = ''; const resetForm = () => { if (idInput) idInput.value = ''; if (nameInput) nameInput.value = ''; if (hostInput) hostInput.value = ''; if (portInput) portInput.value = '22'; if (userInput) userInput.value = ''; if (authSelect) authSelect.value = 'key'; if (keyInput) keyInput.value = ''; if (passInput) passInput.value = ''; if (imageInput) imageInput.value = ''; if (submitBtn) submitBtn.textContent = 'Speichern'; if (modal && modal.classList.contains('is-open')) { initialSnapshot = snapshot(); } }; const snapshot = () => { return JSON.stringify({ id: idInput ? idInput.value : '', name: nameInput ? nameInput.value : '', host: hostInput ? hostInput.value : '', port: portInput ? portInput.value : '', user: userInput ? userInput.value : '', auth: authSelect ? authSelect.value : '', key: keyInput ? keyInput.value : '', pass: passInput ? passInput.value : '', image: imageInput ? imageInput.value : '', }); }; const isDirty = () => snapshot() !== initialSnapshot; const openModal = () => { if (!modal) return; modal.classList.add('is-open'); modal.setAttribute('aria-hidden', 'false'); initialSnapshot = snapshot(); }; const closeModal = (force = false) => { if (!modal) return; if (!force && isDirty()) { const ok = window.confirm('Änderungen nicht gespeichert. Ohne Speichern schließen?'); if (!ok) return; } modal.classList.remove('is-open'); modal.setAttribute('aria-hidden', 'true'); }; document.querySelectorAll('[data-host-edit]').forEach((btn) => { btn.addEventListener('click', () => { const card = btn.closest('.host-card'); if (!card) return; if (idInput) idInput.value = card.dataset.hostId || ''; if (nameInput) nameInput.value = card.dataset.name || ''; if (hostInput) hostInput.value = card.dataset.host || ''; if (portInput) portInput.value = card.dataset.port || '22'; if (userInput) userInput.value = card.dataset.username || ''; if (authSelect) authSelect.value = card.dataset.auth || 'key'; if (keyInput) keyInput.value = card.dataset.keyPath || ''; if (passInput) passInput.value = ''; if (imageInput) imageInput.value = card.dataset.imageUrl || ''; if (submitBtn) submitBtn.textContent = 'Aktualisieren'; if (modalTitle) modalTitle.textContent = 'Host bearbeiten'; const details = btn.closest('details'); if (details) details.removeAttribute('open'); openModal(); }); }); if (newBtn) { newBtn.addEventListener('click', () => { resetForm(); if (modalTitle) modalTitle.textContent = 'Neuer Host'; openModal(); }); } if (closeBtn) { closeBtn.addEventListener('click', () => closeModal(false)); } if (cancelBtn) { cancelBtn.addEventListener('click', (e) => { e.preventDefault(); resetForm(); }); } const updateStatus = (card, status) => { const dot = card.querySelector('[data-host-status]'); if (!dot) return; dot.classList.remove('status-ok', 'status-auth', 'status-down'); if (status === 'ok') dot.classList.add('status-ok'); else if (status === 'down') dot.classList.add('status-down'); else dot.classList.add('status-auth'); }; const fetchStatus = (card) => { const id = card.dataset.hostId; if (!id) return; fetch(`${window.location.pathname}?status_json=1&id=${encodeURIComponent(id)}`, { cache: 'no-store' }) .then((res) => res.json()) .then((data) => { if (data && data.ok) { updateStatus(card, data.status); } }) .catch(() => {}); }; document.querySelectorAll('.host-card').forEach((card) => { fetchStatus(card); }); const setUpdateUi = (card, data) => { const upd = card.querySelector('[data-update-badge]'); const upg = card.querySelector('[data-upgrade-badge]'); const time = card.querySelector('[data-update-time]'); const updDebug = card.querySelector('[data-update-debug]'); const upgDebug = card.querySelector('[data-upgrade-debug]'); if (upd) { upd.classList.remove('badge-warn', 'badge-ok', 'badge-error'); if (data.updates && data.updates.error) { upd.textContent = 'Updates: Fehler'; upd.classList.add('badge-error'); upd.setAttribute('title', data.updates.error); } else if (data.updates && typeof data.updates.count === 'number') { upd.textContent = `Updates: ${data.updates.count}`; upd.classList.toggle('badge-warn', data.updates.count > 0); upd.classList.toggle('badge-ok', data.updates.count === 0); if (data.updates.preview || data.updates.raw) { upd.setAttribute('title', data.updates.preview || data.updates.raw); } } else { upd.textContent = 'Updates: –'; if (data.updates && (data.updates.preview || data.updates.raw)) { upd.setAttribute('title', data.updates.preview || data.updates.raw); } } } if (upg) { upg.classList.remove('badge-warn', 'badge-ok', 'badge-error'); if (data.os && data.os.error) { upg.textContent = 'OS: Fehler'; upg.classList.add('badge-error'); upg.setAttribute('title', data.os.error); } else if (data.os && typeof data.os.available === 'boolean') { upg.textContent = data.os.available ? 'OS: Upgrade verfügbar' : 'OS: OK'; upg.classList.toggle('badge-warn', data.os.available); upg.classList.toggle('badge-ok', !data.os.available); if (data.os.raw) upg.setAttribute('title', data.os.raw); } else { upg.textContent = 'OS: –'; if (data.os && data.os.raw) { upg.setAttribute('title', data.os.raw); } } } if (updDebug) { const raw = (data.updates && (data.updates.raw || data.updates.preview)) || ''; updDebug.textContent = raw ? `Update Debug: ${raw}` : 'Update Debug: –'; } if (upgDebug) { const raw = (data.os && data.os.raw) || ''; upgDebug.textContent = raw ? `Upgrade Debug: ${raw}` : 'Upgrade Debug: –'; } if (time && data.checked_at) { const dt = new Date(data.checked_at); time.textContent = isNaN(dt.getTime()) ? data.checked_at : dt.toLocaleString(); } }; const checkHostUpdates = (card) => { const id = card.dataset.hostId; if (!id) return Promise.resolve(); const btn = card.querySelector('[data-host-check]'); if (btn) { btn.disabled = true; btn.textContent = 'Prüfe...'; } return fetch(`${window.location.pathname}?update_json=1&id=${encodeURIComponent(id)}`, { cache: 'no-store' }) .then((res) => res.json()) .then((data) => { if (data && data.ok) { setUpdateUi(card, data); } }) .finally(() => { if (btn) { btn.disabled = false; btn.textContent = 'Updates prüfen'; } }); }; document.querySelectorAll('[data-host-check]').forEach((btn) => { btn.addEventListener('click', () => { const card = btn.closest('.host-card'); if (card) checkHostUpdates(card); }); }); if (checkAllBtn) { checkAllBtn.addEventListener('click', async () => { const cards = Array.from(document.querySelectorAll('.host-card')); for (const card of cards) { await checkHostUpdates(card); } }); } const applyStoredUpdate = (card) => { const checkedAt = card.dataset.updateChecked || ''; const updateCount = card.dataset.updateCount; const updateError = card.dataset.updateError || ''; const upgradeAvailable = card.dataset.upgradeAvailable; const upgradeRaw = card.dataset.upgradeRaw || ''; const upgradeError = card.dataset.upgradeError || ''; const payload = { updates: {}, os: {}, checked_at: checkedAt || '', }; if (updateError) { payload.updates.error = updateError; } else if (updateCount !== undefined && updateCount !== '') { payload.updates.count = Number(updateCount); payload.updates.preview = card.dataset.updatePreview || ''; payload.updates.raw = card.dataset.updatePreview || ''; } else { payload.updates.preview = card.dataset.updatePreview || ''; payload.updates.raw = card.dataset.updatePreview || ''; } if (upgradeError) { payload.os.error = upgradeError; } else if (upgradeAvailable !== undefined && upgradeAvailable !== '') { payload.os.available = upgradeAvailable === '1' || upgradeAvailable === 'true'; payload.os.raw = upgradeRaw; } else { payload.os.raw = upgradeRaw; } setUpdateUi(card, payload); }; const isStale = (checkedAt) => { if (!checkedAt) return true; const dt = new Date(checkedAt); if (isNaN(dt.getTime())) return true; const ageMs = Date.now() - dt.getTime(); return ageMs > 24 * 60 * 60 * 1000; }; document.querySelectorAll('.host-card').forEach((card) => { applyStoredUpdate(card); if (isStale(card.dataset.updateChecked || '')) { checkHostUpdates(card); } }); })();