diff --git a/importarr/static/importarr.css b/importarr/static/importarr.css index 9d6daaf..d9b5917 100644 --- a/importarr/static/importarr.css +++ b/importarr/static/importarr.css @@ -1 +1 @@ -body{font-family:system-ui,sans-serif;margin:0;background:#111827;color:#e5e7eb}header,main{max-width:1100px;margin:auto;padding:1rem}.topbar{display:flex;justify-content:space-between;gap:1rem;align-items:center;background:#0f172a}.build{text-align:right}.build strong{font-size:1.2rem}.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(12rem,1fr));gap:1rem}.cards article,.panel{background:#1f2937;border-radius:.75rem;padding:1rem;margin-top:1rem}strong{display:block;font-size:2rem}span,small,dd{color:#9ca3af}table{width:100%;border-collapse:collapse;background:#1f2937;margin-top:1rem}th,td{padding:.6rem;border-bottom:1px solid #374151;text-align:left;vertical-align:top}input,button{padding:.6rem;border-radius:.4rem;border:1px solid #374151}button{background:#38bdf8;color:#082f49;font-weight:700;cursor:pointer}.danger{background:#f87171;color:#450a0a}.warn{background:#fbbf24;color:#451a03}.controls,.row-actions{display:flex;gap:.5rem;flex-wrap:wrap}.inline-form{display:flex;gap:.5rem;flex-wrap:wrap}.inline-form input[name=path]{min-width:min(100%,28rem);flex:1}.info{display:grid;grid-template-columns:10rem 1fr;gap:.4rem 1rem}.info dt{font-weight:700}.info dd{margin:0;overflow-wrap:anywhere}.state{background:#0f172a;border:1px solid #374151;border-radius:999px;padding:.15rem .5rem;display:inline-block}.section-title{display:flex;align-items:center;justify-content:space-between;gap:1rem}.job-group{margin-top:1.25rem}.job-group h3{display:flex;gap:.5rem;align-items:center}.job-group h3 span{font-size:.9rem;border:1px solid #374151;border-radius:999px;padding:.1rem .45rem}.file-name{font-size:1rem}.row-actions button{padding:.35rem .5rem}td small{display:block;overflow-wrap:anywhere}dialog{background:#1f2937;color:#e5e7eb;border:1px solid #374151;border-radius:.75rem;max-width:min(42rem,90vw)}dialog::backdrop{background:#0009}fieldset{border:1px solid #374151;border-radius:.5rem;margin:1rem 0;padding:1rem}label{display:grid;gap:.35rem;margin:.75rem 0}.hint{color:#9ca3af}output{display:block;margin-top:.5rem;color:#9ca3af}.success{color:#86efac}.error{color:#fca5a5} +body{font-family:system-ui,sans-serif;margin:0;background:#111827;color:#e5e7eb}header,main{max-width:1100px;margin:auto;padding:1rem;box-sizing:border-box}.topbar{display:flex;justify-content:space-between;gap:1rem;align-items:center;background:#0f172a}.build{text-align:right}.build strong{font-size:1.2rem}.build span{display:block;overflow-wrap:anywhere}.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(min(12rem,100%),1fr));gap:1rem}.cards article,.panel{background:#1f2937;border-radius:.75rem;padding:1rem;margin-top:1rem}strong{display:block;font-size:2rem}span,small,dd{color:#9ca3af}table{width:100%;border-collapse:collapse;background:#1f2937;margin-top:1rem}.table-scroll{max-width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch}.table-scroll table{min-width:42rem}th,td{padding:.6rem;border-bottom:1px solid #374151;text-align:left;vertical-align:top}input,button{padding:.6rem;border-radius:.4rem;border:1px solid #374151;box-sizing:border-box}button{background:#38bdf8;color:#082f49;font-weight:700;cursor:pointer;min-height:2.75rem}.danger{background:#f87171;color:#450a0a}.warn{background:#fbbf24;color:#451a03}.controls,.row-actions{display:flex;gap:.5rem;flex-wrap:wrap}.inline-form{display:flex;gap:.5rem;flex-wrap:wrap}.inline-form input[name=path]{min-width:min(100%,28rem);flex:1}.info{display:grid;grid-template-columns:10rem 1fr;gap:.4rem 1rem}.info dt{font-weight:700}.info dd{margin:0;overflow-wrap:anywhere}.state{background:#0f172a;border:1px solid #374151;border-radius:999px;padding:.15rem .5rem;display:inline-block}.section-title{display:flex;align-items:center;justify-content:space-between;gap:1rem}.job-group{margin-top:1.25rem}.job-group h3{display:flex;gap:.5rem;align-items:center}.job-group h3 span{font-size:.9rem;border:1px solid #374151;border-radius:999px;padding:.1rem .45rem}.file-name{font-size:1rem}.row-actions button{padding:.35rem .5rem}td small{display:block;overflow-wrap:anywhere}dialog{background:#1f2937;color:#e5e7eb;border:1px solid #374151;border-radius:.75rem;max-width:min(42rem,90vw);box-sizing:border-box}dialog::backdrop{background:#0009}fieldset{border:1px solid #374151;border-radius:.5rem;margin:1rem 0;padding:1rem}label{display:grid;gap:.35rem;margin:.75rem 0}.hint{color:#9ca3af}output{display:block;margin-top:.5rem;color:#9ca3af}.success{color:#86efac}.error{color:#fca5a5}@media (max-width:640px){header,main{padding:.75rem}.topbar{align-items:flex-start;flex-direction:column}.topbar h1{margin-bottom:.25rem}.build{text-align:left;width:100%}.build button{margin-top:.5rem;width:100%}.cards{gap:.75rem}.cards article,.panel{padding:.85rem;margin-top:.75rem}strong{font-size:1.6rem}.section-title{align-items:stretch;flex-direction:column}.section-title button,.controls button,.inline-form button,.inline-form input{width:100%}.controls,.inline-form,.row-actions{align-items:stretch;flex-direction:column}.info{grid-template-columns:1fr;gap:.1rem}.info dt{margin-top:.5rem}.table-scroll{margin-left:-.85rem;margin-right:-.85rem;padding:0 .85rem}.table-scroll table{min-width:38rem}dialog{width:calc(100vw - 1rem);max-width:calc(100vw - 1rem);padding:.75rem}fieldset{padding:.75rem}input{width:100%}} diff --git a/importarr/templates/index.html b/importarr/templates/index.html index eba2eb5..bfa9c5f 100644 --- a/importarr/templates/index.html +++ b/importarr/templates/index.html @@ -57,9 +57,9 @@ - +
IDStatusPath
{% for batch in batches %}{% endfor %} -
IDStatusPath
{{ batch.id }}{{ batch.status }}{{ batch.path }}
+

Jobs and queue

@@ -96,7 +96,7 @@ 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 actionButtons(j){ const buttons=[]; if(j.can_run_now) buttons.push(``); if(j.can_retry) buttons.push(``); if(j.can_ignore) buttons.push(``); if(j.can_remove) buttons.push(``); return buttons.join(' '); } - function renderJobs(data){ const groups=(data.groups||[]).filter(group=>group.jobs.length); if(!groups.length) return '

No queue items.

'; return groups.map(group=>`

${esc(group.label)} ${group.jobs.length}

${group.jobs.map(j=>``).join('')}
FileRelease / folder contextReadinessSABActions
${esc(j.name)}${esc(j.source_type)}${j.batch_id?' · batch '+esc(j.batch_id):''}${esc(j.relative_path||j.storage||j.source_id)}${esc(j.state)}${esc(j.reason||'')}${esc(j.sab_status||'—')}${j.sab_category?' · '+esc(j.sab_category):''}${actionButtons(j)}
`).join(''); } + function renderJobs(data){ const groups=(data.groups||[]).filter(group=>group.jobs.length); if(!groups.length) return '

No queue items.

'; return groups.map(group=>`

${esc(group.label)} ${group.jobs.length}

${group.jobs.map(j=>``).join('')}
FileRelease / folder contextReadinessSABActions
${esc(j.name)}${esc(j.source_type)}${j.batch_id?' · batch '+esc(j.batch_id):''}${esc(j.relative_path||j.storage||j.source_id)}${esc(j.state)}${esc(j.reason||'')}${esc(j.sab_status||'—')}${j.sab_category?' · '+esc(j.sab_category):''}${actionButtons(j)}
`).join(''); } async function refresh(){ const r=await fetch('/api/jobs'); const d=await r.json(); document.title=d.jobs.length?`📥 ${d.jobs.length} jobs - Importarr`:'📥 - idle · Importarr'; document.getElementById('jobs').innerHTML=renderJobs(d); if(d.control){ document.getElementById('queue-mode').textContent=d.control.queue_mode; document.getElementById('current-job').textContent=d.control.current||'idle'; } } document.getElementById('jobs').addEventListener('click', async e=>{ const button=e.target.closest('button[data-action]'); if(!button)return; const action=button.dataset.action; const destructive=['ignore','remove'].includes(action); if(destructive&&!confirm(`${action} this Importarr queue item?`)) return; await postJson(`/api/queue-items/${button.dataset.id}/action`,{action}); await refresh(); }); document.querySelectorAll('[data-control]').forEach(button=>button.addEventListener('click', async()=>{ if(button.dataset.control==='cancel-current'&&!confirm('Cancel the current import job?')) return; await postJson(`/api/control/${button.dataset.control}`); await refresh(); })); diff --git a/tests/test_status.py b/tests/test_status.py index 4254ec8..c6dd819 100644 --- a/tests/test_status.py +++ b/tests/test_status.py @@ -51,6 +51,30 @@ def test_index_renders_settings_dialog(tmp_path, monkeypatch): assert "Test Sonarr connection" in response.text +def test_index_renders_responsive_table_wrappers(tmp_path, monkeypatch): + monkeypatch.setenv("IMPORTARR_STATE_PATH", str(tmp_path / "state.db")) + import importarr.main as main + + from fastapi.testclient import TestClient + + response = TestClient(main.app).get("/") + + assert response.status_code == 200 + assert 'class="table-scroll"' in response.text + assert '' in response.text + + +def test_stylesheet_includes_mobile_responsive_rules(): + from pathlib import Path + + css = Path("importarr/static/importarr.css").read_text() + + assert "@media (max-width:640px)" in css + assert ".table-scroll" in css + assert "overflow-x:auto" in css + assert "flex-direction:column" in css + + def test_settings_endpoint_persists_arr_connection_values(tmp_path, monkeypatch): monkeypatch.setenv("IMPORTARR_STATE_PATH", str(tmp_path / "state.db")) import importarr.main as main