diff --git a/static/js/admin.js b/static/js/admin.js index ff992eb..2ae615a 100644 --- a/static/js/admin.js +++ b/static/js/admin.js @@ -7,10 +7,10 @@ let slowTimer = null; let fastTimer = null; const activeSids = new Set(); -// --- Small helpers (no optional chaining) --- -function qSel(root, sel){ return root ? root.querySelector(sel) : null; } -function text(el){ return (el && el.textContent) ? el.textContent : ''; } -function val(el){ return el ? el.value : undefined; } +// --- tiny safe helpers --- +function q(root, sel){ return root ? root.querySelector(sel) : null; } +function qq(root, sel){ return root ? Array.from(root.querySelectorAll(sel)) : []; } +function txt(el){ return (el && el.textContent) ? el.textContent : ''; } function low(x){ return String(x||'').toLowerCase(); } function injectOnceCSS() { @@ -29,7 +29,7 @@ function flashDot(cell) { const dot = document.createElement('span'); dot.className = 'pulse-dot'; cell.appendChild(dot); - setTimeout(() => { if (dot && dot.parentNode) dot.parentNode.removeChild(dot); }, 1500); + setTimeout(function(){ if (dot && dot.parentNode) dot.parentNode.removeChild(dot); }, 1500); } function setBadgeCell(cell, textOrState) { @@ -44,21 +44,33 @@ function setBadgeCell(cell, textOrState) { return false; } +function extractNodeNames(ns){ + if (!ns) return []; + if (Array.isArray(ns.nodes)) return Array.from(new Set(ns.nodes.map(n => String(n.name || n.node || n).trim()).filter(Boolean))); + if (Array.isArray(ns)) return Array.from(new Set(ns.map(n => String(n.name || n.node || n).trim()).filter(Boolean))); + return []; +} + function rebuildTargetSelect(selectEl, currentNode, nodes) { if (!selectEl) return []; const current = String(currentNode || '').trim(); - const others = (nodes || []) - .map(n => String(n && (n.name || n.node || n)).trim()) - .filter(Boolean) - .filter(n => n !== current); + const all = (nodes || []).map(n => String(n && (n.name || n.node || n)).trim()).filter(Boolean); + const others = all.filter(n => n !== current); selectEl.innerHTML = others.map(n => ``).join(''); - if (selectEl.options.length > 0) selectEl.selectedIndex = 0; + // default: pick the "second node" if exists, otherwise first available other + var idx = 0; + if (all.length >= 2) { + const preferred = all[1]; + const j = others.indexOf(preferred); + if (j >= 0) idx = j; + } + if (selectEl.options.length > 0) selectEl.selectedIndex = idx; return others; } function updateMigrateButton(tr, isRunning) { - const btn = qSel(tr, '.act-migrate'); - const targetSel = qSel(tr, '.target-node'); + const btn = q(tr, '.act-migrate'); + const targetSel = q(tr, '.target-node'); const hasTarget = !!(targetSel && targetSel.options && targetSel.options.length > 0); const enable = !!(isRunning && hasTarget); if (!btn) return; @@ -67,9 +79,9 @@ function updateMigrateButton(tr, isRunning) { } function updateActionButtons(tr, isRunning) { - const bStart = qSel(tr, '.act-start'); - const bStop = qSel(tr, '.act-stop'); - const bShutdown = qSel(tr, '.act-shutdown'); + const bStart = q(tr, '.act-start'); + const bStop = q(tr, '.act-stop'); + const bShutdown = q(tr, '.act-shutdown'); if (bStart) { if (isRunning) { bStart.setAttribute('disabled',''); bStart.classList.add('disabled'); } else { bStart.removeAttribute('disabled'); bStart.classList.remove('disabled'); } } if (bStop) { if (isRunning) { bStop.removeAttribute('disabled'); bStop.classList.remove('disabled'); } else { bStop.setAttribute('disabled',''); bStop.classList.add('disabled'); } } if (bShutdown) { if (isRunning) { bShutdown.removeAttribute('disabled'); bShutdown.classList.remove('disabled'); } else { bShutdown.setAttribute('disabled',''); bShutdown.classList.add('disabled'); } } @@ -86,8 +98,7 @@ export function stopAllAdminWatches() { function ensureWatchOn() { const tbody = document.querySelector('#vm-admin tbody'); if (!tbody) return; - const rows = Array.from(tbody.querySelectorAll('tr[data-sid]')); - rows.forEach(function(tr){ + qq(tbody, 'tr[data-sid]').forEach(function(tr){ const sid = tr.getAttribute('data-sid'); if (!sid || liveSockets.has(sid)) return; try { @@ -101,19 +112,19 @@ function ensureWatchOn() { try { const msg = JSON.parse(ev.data || '{}'); if (!msg || !msg.type) return; - const tr = tbody.querySelector('tr[data-sid="' + sid + '"]'); - if (!tr) return; - const statusCell = tr.children[4]; - const nameCell = tr.children[2]; - const nodeCell = tr.children[3]; - const targetSel = qSel(tr, '.target-node'); + const tr2 = tbody.querySelector('tr[data-sid="' + sid + '"]'); + if (!tr2) return; + const statusCell = tr2.children[4]; + const nameCell = tr2.children[2]; + const nodeCell = tr2.children[3]; + const targetSel = q(tr2, '.target-node'); if (msg.type === 'status') { const stRaw = low(msg.status); const changed = setBadgeCell(statusCell, stRaw); const isRunning = /running|online|started/.test(stRaw); - updateMigrateButton(tr, isRunning); - updateActionButtons(tr, isRunning); + updateMigrateButton(tr2, isRunning); + updateActionButtons(tr2, isRunning); if (changed) flashDot(nameCell); if (stRaw && /running|stopped|shutdown/.test(stRaw)) { setTimeout(function(){ activeSids.delete(sid); }, 3000); @@ -122,7 +133,7 @@ function ensureWatchOn() { if (msg.type === 'node' && msg.node) { const newNode = String(msg.node).trim(); - if (nodeCell && newNode && text(nodeCell).trim() !== newNode) { + if (nodeCell && newNode && txt(nodeCell).trim() !== newNode) { nodeCell.textContent = newNode; rebuildTargetSelect(targetSel, newNode, window.__nodesCache || []); flashDot(nameCell); @@ -134,14 +145,6 @@ function ensureWatchOn() { }); } -function dedupe(arr){ return Array.from(new Set(arr)); } -function extractNodeNames(ns){ - if (!ns) return []; - if (Array.isArray(ns.nodes)) return dedupe(ns.nodes.map(n => String(n.name || n.node || n).trim()).filter(Boolean)); - if (Array.isArray(ns)) return dedupe(ns.map(n => String(n.name || n.node || n).trim()).filter(Boolean)); - return []; -} - export async function startAdminWatches() { injectOnceCSS(); const tbody = document.querySelector('#vm-admin tbody'); @@ -170,29 +173,37 @@ export async function startAdminWatches() { safe(r.name), safe(r.node), badge(safe(r.status), /running|online|started/i.test(r.status) ? 'ok' : 'dark'), - `