287 lines
10 KiB
JavaScript
287 lines
10 KiB
JavaScript
(() => {
|
||
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);
|
||
}
|
||
});
|
||
})();
|