/* * admin_status.js — Server status panel + backup/restore (Admin tab). */ window.AdminStatus = (function() { function _fmtUptime(s) { const d = Math.floor(s / 86400); const h = Math.floor((s % 86400) / 3600); const m = Math.floor((s % 3600) / 60); const parts = []; if (d) parts.push(`${d}d`); if (h || d) parts.push(`${h}h`); parts.push(`${m}m`); return parts.join(' '); } function _dot(ok, labelOk, labelBad) { const color = ok ? 'var(--green)' : 'var(--red)'; const label = ok ? labelOk : labelBad; return `● ${label}`; } async function refresh() { const body = document.getElementById('admin-status-body'); if (!body) return; try { const r = await fetch('/api/status', { credentials: 'include' }); if (!r.ok) { body.innerHTML = `
${I18n.t('log_error_prefix')}HTTP ${r.status}
`; return; } const d = await r.json(); if (!d.ok) return; const rig = d.rig || {}; const audio = d.audio || {}; const sys = d.system || {}; // Build the status grid const rows = [ [I18n.t('adm_stat_version'), `${d.version} (Python ${d.python}, ${d.platform})`], [I18n.t('adm_stat_uptime'), _fmtUptime(d.uptime_s)], [I18n.t('adm_stat_online'), I18n.t('adm_stat_users_count').replace('{n}', d.online_count)], [I18n.t('adm_stat_radio'), rig.sim ? _dot(false, '', I18n.t('adm_stat_sim')) : _dot(rig.connected, `${I18n.t('adm_stat_civ_connected')} (${rig.backend})`, I18n.t('adm_stat_disconnected'))], [I18n.t('adm_stat_model_port'), `${rig.model || '?'} @ ${rig.port || '?'}` + (rig.speed && rig.speed !== '?' ? ` (${rig.speed} bd)` : '')], [I18n.t('adm_stat_freq'), rig.freq ? `${(rig.freq/1e6).toFixed(3)} MHz` : '—'], [I18n.t('adm_stat_audio'), _dot(true, `${audio.backend}${audio.rust ? ' (Rust)' : ''}`, '')], [I18n.t('adm_stat_dxcluster'), _dot(d.dxcluster?.available, I18n.t('adm_stat_available'), I18n.t('adm_stat_unavailable'))], [I18n.t('adm_stat_relays'), d.relay?.available ? _dot(d.relay.connected, I18n.t('adm_stat_relays_connected'), I18n.t('adm_stat_relays_configured')) : `${I18n.t('adm_stat_no_module')}`], ]; // CPU/RAM if available if (sys.cpu_pct !== null && sys.cpu_pct !== undefined) { const cpuColor = sys.cpu_pct > 80 ? 'var(--red)' : sys.cpu_pct > 50 ? 'var(--amber)' : 'var(--green)'; rows.push(['CPU', `${sys.cpu_pct.toFixed(0)}%`]); } if (sys.ram_pct !== null && sys.ram_pct !== undefined) { const ramColor = sys.ram_pct > 85 ? 'var(--red)' : sys.ram_pct > 65 ? 'var(--amber)' : 'var(--green)'; rows.push(['RAM', `${sys.ram_pct.toFixed(0)}% (${sys.ram_used_mb} MB)`]); } body.innerHTML = `
${rows.map(([k,v]) => `
${k}:
${v}
`).join('')}
${I18n.t('adm_stat_perf_link')}
`; } catch(e) { body.innerHTML = `
${I18n.t('log_error_prefix')}${e.message}
`; } } async function testCiv() { const res = document.getElementById('admin-civ-test-result'); if (res) { res.textContent = I18n.t('adm_testing'); res.style.color = 'var(--dim)'; } try { const r = await fetch('/api/status/test_civ', { method: 'POST', credentials: 'include' }); const d = await r.json(); if (res) { res.textContent = (d.ok ? '✓ ' : '✕ ') + (d.message || ''); res.style.color = d.ok ? 'var(--green)' : 'var(--red)'; } } catch(e) { if (res) { res.textContent = '✕ ' + e.message; res.style.color = 'var(--red)'; } } } async function downloadBackup() { const status = document.getElementById('admin-backup-status'); if (status) { status.textContent = I18n.t('adm_generating'); status.style.color = 'var(--dim)'; } try { const r = await fetch('/api/backup', { credentials: 'include' }); if (!r.ok) throw new Error('HTTP ' + r.status); const data = await r.json(); // Download as a file const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); const ts = new Date().toISOString().slice(0,19).replace(/[:T]/g, '-'); a.href = url; a.download = `hamctrl-backup-${ts}.json`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); if (status) { status.textContent = I18n.t('adm_downloaded'); status.style.color = 'var(--green)'; } } catch(e) { if (status) { status.textContent = '✕ ' + e.message; status.style.color = 'var(--red)'; } } } async function uploadBackup(input) { const status = document.getElementById('admin-backup-status'); const file = input.files?.[0]; if (!file) return; if (!await window.UI?.confirmModal(I18n.t('adm_confirm_restore').replace('{name}', file.name), { danger: true, okLabel: I18n.t('adm_restore_btn') })) { input.value = ''; return; } if (status) { status.textContent = I18n.t('adm_loading_ellipsis'); status.style.color = 'var(--dim)'; } try { const text = await file.text(); const backup = JSON.parse(text); const r = await fetch('/api/restore', { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ backup }), }); const d = await r.json(); if (r.ok && d.ok) { if (status) { status.textContent = I18n.t('adm_restored').replace('{list}', d.restored.join(', ')); status.style.color = 'var(--green)'; } window.UI?.showToast?.(I18n.t('adm_toast_restored'), 'info'); } else { if (status) { status.textContent = '✕ ' + (d.error || 'Błąd'); status.style.color = 'var(--red)'; } } } catch(e) { if (status) { status.textContent = '✕ ' + e.message; status.style.color = 'var(--red)'; } } finally { input.value = ''; } } return { refresh, testCiv, downloadBackup, uploadBackup }; })();