(scanners, nucleiTemplates)
| 19398 | const body = document.getElementById('power-modal-body'); |
| 19399 | if (body) body.innerHTML = `<div class="text-red-300">Could not load power details: ${escapeHtml(String(e))}</div>`; |
| 19400 | } |
| 19401 | } |
| 19402 | |
| 19403 | function closePowerModal() { |
| 19404 | const modal = document.getElementById('power-detail-modal'); |
| 19405 | if (modal) modal.remove(); |
| 19406 | document.removeEventListener('keydown', _powerModalEsc); |
| 19407 | } |
| 19408 | function _powerModalEsc(e) { if (e.key === 'Escape') closePowerModal(); } |
| 19409 | |
| 19410 | function _pwMa(ma) { |
| 19411 | if (ma === null || ma === undefined) return '—'; |
| 19412 | return ma >= 1000 ? `${(ma / 1000).toFixed(2)} A` : `${ma} mA`; |
| 19413 | } |
| 19414 | |
| 19415 | function renderPowerModalBody(d) { |
| 19416 | const body = document.getElementById('power-modal-body'); |
| 19417 | if (body) body.innerHTML = buildPowerDetailHtml(d); |
| 19418 | } |
| 19419 | |
| 19420 | // Shared by the dashboard badge modal and the System tab's Power panel, so both |
| 19421 | // show the same measured throttle state + estimated budget from one builder. |
| 19422 | function buildPowerDetailHtml(d) { |
| 19423 | if (!d || d.supported === false) { |
| 19424 | return `<div class="text-gray-400">Power monitoring is not available on this device |
| 19425 | (no <code class="font-mono">vcgencmd</code> — not a Raspberry Pi, or the tool is missing).</div>`; |
| 19426 | } |
| 19427 | const level = d.level; |
| 19428 | const tone = level === 'critical' ? 'text-red-300' |
| 19429 | : level === 'warning' ? 'text-amber-300' : 'text-green-300'; |
| 19430 | const thr = d.throttle || {}; |
| 19431 | const est = d.estimate || {}; |
| 19432 | |
| 19433 | // 1) Live supply state + what it costs, straight from the throttle register. |
| 19434 | let html = `<div class="mb-4"> |
| 19435 | <div class="flex items-center gap-2 mb-1"> |
| 19436 | <span class="text-xs px-2 py-0.5 rounded border ${ |
| 19437 | level === 'critical' ? 'bg-red-600/20 text-red-300 border-red-800' |
| 19438 | : level === 'warning' ? 'bg-amber-600/20 text-amber-300 border-amber-800' |
| 19439 | : 'bg-green-700/20 text-green-300 border-green-800'}">${escapeHtml((level || 'unknown').toUpperCase())}</span> |
| 19440 | <span class="text-gray-400 text-xs font-mono">${escapeHtml(d.model || '')}</span> |
| 19441 | </div>`; |
| 19442 | (d.effects || []).forEach(t => { html += `<p class="${tone} mt-1">${escapeHtml(t)}</p>`; }); |
| 19443 | html += `</div>`; |
| 19444 | |
| 19445 | // 2) Throttle register decode — the measured truth. |
| 19446 | if (thr && thr.raw) { |
| 19447 | const rowsNow = (thr.now || []).map(escapeHtml).join(', ') || 'none'; |
| 19448 | const rowsOcc = (thr.occurred || []).map(escapeHtml).join(', ') || 'none'; |
| 19449 | html += `<div class="bg-slate-900/60 border border-slate-800 rounded-lg p-3 mb-4"> |
| 19450 | <div class="text-xs uppercase tracking-wide text-gray-500 mb-2">Supply health (measured — vcgencmd get_throttled)</div> |
| 19451 | <div class="grid grid-cols-[auto,1fr] gap-x-3 gap-y-1 text-xs"> |
| 19452 | <div class="text-gray-400">Right now</div><div class="${(thr.now||[]).length ? 'text-red-300' : 'text-green-300'}">${rowsNow}</div> |
| 19453 | <div class="text-gray-400">Since boot</div><div class="${(thr.occurred||[]).length ? 'text-amber-300' : 'text-green-300'}">${rowsOcc}</div> |
| 19454 | <div class="text-gray-400">Raw flags</div><div class="font-mono text-gray-400">${escapeHtml(thr.raw)}</div> |
| 19455 | ${d.core_volts != null ? `<div class="text-gray-400">Core voltage</div><div>${escapeHtml(String(d.core_volts))} V</div>` : ''} |
| 19456 | ${d.temp_c != null ? `<div class="text-gray-400">Temperature</div><div>${escapeHtml(String(d.temp_c))} °C</div>` : ''} |
| 19457 | ${d.pmic && d.pmic.total_watts != null ? `<div class="text-gray-400">Board power (PMIC)</div><div>${escapeHtml(String(d.pmic.total_watts))} W</div>` : ''} |
no test coverage detected