Show self-update availability in UI #25
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -10,6 +10,11 @@
|
||||
<header class="topbar">
|
||||
<div class="brand"><h1>Importarr</h1><p id="ready-state">{{ 'Running' if status.control.queue_mode == 'start' else status.control.queue_mode|capitalize }}</p></div>
|
||||
<div class="top-status"><span>Current</span><strong id="top-current-job">{{ status.current or 'idle' }}</strong></div>
|
||||
<section id="update-banner" class="update-banner" hidden aria-live="polite">
|
||||
<strong id="update-message">New version available</strong>
|
||||
<button type="button" id="update-now">Update now</button>
|
||||
<code id="update-command">{{ status.build.version }}</code>
|
||||
</section>
|
||||
<div class="top-controls">
|
||||
<button type="button" data-control="start" aria-label="Start imports">▶</button>
|
||||
<button type="button" data-control="pause" aria-label="Pause imports">⏸</button>
|
||||
@@ -123,6 +128,8 @@
|
||||
<script>
|
||||
const esc=value=>String(value??'').replace(/[&<>"']/g,ch=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[ch]));
|
||||
async function postJson(url, body){ const response=await fetch(url,{method:'POST',headers:{'content-type':'application/json'},body:body?JSON.stringify(body):undefined}); if(!response.ok){ const error=await response.json().catch(()=>({detail:response.statusText})); alert(error.detail||'Request failed'); } return response; }
|
||||
function renderUpdateCheck(update){ const banner=document.getElementById('update-banner'); if(!update?.update_available){ banner.hidden=true; return; } document.getElementById('update-message').textContent=`↑ New version available: ${update.latest_version}`; document.getElementById('update-command').textContent=`current ${update.current_version}`; banner.hidden=false; }
|
||||
async function checkForUpdates(){ const response=await fetch('/api/control/update-check'); if(!response.ok) return; renderUpdateCheck(await response.json()); }
|
||||
function actionButtons(j){ const buttons=[]; if(j.can_run_now) buttons.push(`<button type="button" data-action="run-now" data-id="${j.id}" title="Run now">▶</button>`); if(j.can_retry) buttons.push(`<button type="button" data-action="retry" data-id="${j.id}" title="Retry">↻</button>`); if(j.can_ignore) buttons.push(`<button type="button" data-action="ignore" data-id="${j.id}" class="warn" title="Ignore">!</button>`); if(j.can_remove) buttons.push(`<button type="button" data-action="remove" data-id="${j.id}" class="danger" title="Remove">🗑</button>`); return buttons.join(' '); }
|
||||
function jobSubtext(j){ return `${esc(j.source_type)}${j.batch_id?' · batch '+esc(j.batch_id):''} · ${esc(j.relative_path||j.storage||j.source_id)}`; }
|
||||
function readiness(j){ return `<span class="state" title="${esc(j.reason||j.state)}">${esc(j.state)}</span>`; }
|
||||
@@ -145,7 +152,8 @@
|
||||
document.querySelectorAll('[data-test-connection]').forEach(button=>button.addEventListener('click', async()=>{ const form=document.getElementById('settings-form'); const service=button.dataset.testConnection; const prefix=service==='sabnzbd'?'sab':service; const output=document.getElementById(`${service}-test-result`); output.textContent='Testing…'; output.className=''; const response=await postJson('/api/settings/test-connection',{service,url:form.elements[`${prefix}_url`].value,api_key:form.elements[`${prefix}_api_key`].value}); if(response.ok){ const data=await response.json(); output.textContent=data.message; output.className=data.ok?'success':'error'; } }));
|
||||
const themes=['auto','light','dark']; function applyTheme(theme){ document.documentElement.dataset.theme=theme; localStorage.setItem('importarr-theme',theme); document.getElementById('theme-toggle').textContent=`Theme: ${theme}`; } document.getElementById('theme-toggle').addEventListener('click',()=>applyTheme(themes[(themes.indexOf(document.documentElement.dataset.theme)+1)%themes.length])); applyTheme(localStorage.getItem('importarr-theme')||'auto');
|
||||
document.getElementById('force-run').addEventListener('click', async()=>{ await postJson('/api/import/run-now',{force:true}); await refresh(); });
|
||||
refresh(); setInterval(refresh, 2000);
|
||||
document.getElementById('update-now').addEventListener('click', async()=>{ if(!confirm('Update Importarr now?')) return; const response=await postJson('/api/control/update'); if(response.ok){ const data=await response.json(); renderUpdateCheck(data); alert(data.command_result?.stdout||data.status||'Update command started.'); } });
|
||||
refresh(); checkForUpdates(); setInterval(refresh, 2000); setInterval(checkForUpdates, 3600000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+12
-6
@@ -69,6 +69,10 @@ def test_index_packs_secondary_controls_into_menu(tmp_path, monkeypatch):
|
||||
assert "e.target===e.currentTarget" in response.text
|
||||
assert "function readiness" in response.text
|
||||
assert 'title="${esc(j.reason||j.state)}"' in response.text
|
||||
assert "update-banner" in response.text
|
||||
assert "checkForUpdates()" in response.text
|
||||
assert "/api/control/update-check" in response.text
|
||||
assert "update-now" in response.text
|
||||
|
||||
|
||||
def test_stylesheet_includes_theme_and_progress_rules():
|
||||
@@ -79,7 +83,9 @@ def test_stylesheet_includes_theme_and_progress_rules():
|
||||
assert 'html[data-theme="light"]' in css
|
||||
assert 'html[data-theme="dark"]' in css
|
||||
assert ".current-panel progress" in css
|
||||
assert ".top-controls>button,.menu summary" in css
|
||||
assert ".top-controls>button" in css
|
||||
assert ".update-banner" in css
|
||||
assert "#ff9f0a" in css
|
||||
|
||||
|
||||
def test_index_renders_settings_dialog(tmp_path, monkeypatch):
|
||||
@@ -122,14 +128,14 @@ def test_stylesheet_includes_mobile_responsive_rules():
|
||||
|
||||
assert "@media (max-width:640px)" in css
|
||||
assert ".table-scroll" in css
|
||||
assert "overflow-x:auto" in css
|
||||
assert "overflow: hidden" in css
|
||||
assert "flex-direction: column" in css
|
||||
assert ".jobs-table{display:none}" in css
|
||||
assert ".job-cards{display:block}" in css
|
||||
assert ".jobs-table { display: none" in css
|
||||
assert ".job-cards { display: block" in css
|
||||
assert "word-break: break-word" in css
|
||||
assert "max-width: 1280px" in css
|
||||
assert ".jobs-table table{table-layout:fixed;min-width:0}" in css
|
||||
assert ".jobs-table th:nth-child(1){width:66%}" in css
|
||||
assert "table-layout: fixed" in css
|
||||
assert ".jobs-table th:nth-child(1) { width: 66%" in css
|
||||
assert ".row-actions button { width: 1.85rem" in css
|
||||
assert ".summary-strip" in css
|
||||
assert ".menu-panel" in css
|
||||
|
||||
Reference in New Issue
Block a user