diff --git a/pytorrent/static/app.js b/pytorrent/static/app.js index 36e3f2b..05bc0de 100644 --- a/pytorrent/static/app.js +++ b/pytorrent/static/app.js @@ -862,14 +862,13 @@ return `
${summary||'No actions'}
${details}
`; } - function renderAutomationHistory(hist=[], smartStats=automationSmartQueueStats){ + function renderAutomationHistory(hist=[]){ if(!$('automationHistory')) return; - const stats=renderSmartQueueNerdStats(smartStats); const toolbar='
'; const rows=hist.map(h=>[humanDateCell(h.created_at),esc(h.rule_name||''),esc(h.torrent_name||h.torrent_hash||''),automationHistoryActions(h.actions_json||'')]); // Note: Automation history uses the shared responsive table wrapper so it stays inside narrow mobile modals. const body=hist.length?responsiveTable(['Time','Rule','Torrent / batch','Actions'],rows,'automation-history-table'):'
No automation history yet.
'; - $('automationHistory').innerHTML=stats+toolbar+body; + $('automationHistory').innerHTML=toolbar+body; } async function clearAutomationHistory(){ @@ -893,13 +892,8 @@ } async function loadAutomations(){ - const [j,smart]=await Promise.all([ - fetch('/api/automations').then(r=>r.json()), - fetch('/api/smart-queue?history_limit=100').then(r=>r.json()).catch(()=>({})) - ]); + const j=await fetch('/api/automations').then(r=>r.json()); const rules=j.rules||[], hist=j.history||[]; - // Note: Automations only display Smart Queue diagnostics here; saving/checking rules remains unchanged. - automationSmartQueueStats=smart?.ok?buildSmartQueueNerdStats(smart.history||[], Number(smart.history_total||0)):null; automationRulesCache=rules; if($('automationManager')) $('automationManager').innerHTML=rules.length?rules.map(r=>{ const enabled=!!r.enabled; @@ -908,7 +902,7 @@ const toggleClass=enabled?'btn-outline-warning':'btn-outline-success'; return `
${esc(r.name)} ${enabled?'on':'off'}
${esc(ruleSummary(r))} ยท cooldown ${esc(r.cooldown_minutes||0)} min
`; }).join(''):'
No automation rules.
'; - renderAutomationHistory(hist, automationSmartQueueStats); + renderAutomationHistory(hist); } async function toggleAutomationRule(rule){ @@ -1082,10 +1076,14 @@ const box=$('appStatusManager'); if(!box) return; box.innerHTML=' Loading diagnostics...'; try{ - const j=await (await fetch('/api/app/status')).json(); + const [j,smart]=await Promise.all([ + fetch('/api/app/status').then(r=>r.json()), + fetch('/api/smart-queue?history_limit=100').then(r=>r.json()).catch(()=>({ok:false})) + ]); if(!j.ok) throw new Error(j.error||'Failed to load diagnostics'); const st=j.status||{}, py=st.pytorrent||{}, scgi=st.scgi||{}, profile=st.profile||{}, pc=st.port_check||{}, cleanup=st.cleanup||{}, db=cleanup.database||{}; const peaks=st.speed_peaks||{}, peakSession=peaks.session||{}, peakAllTime=peaks.all_time||{}; + const smartStats=smart?.ok?buildSmartQueueNerdStats(smart.history||[], Number(smart.history_total||0)):null; const cards=[ diagCard('pyTorrent PID', py.pid), diagCard('pyTorrent uptime', `${py.uptime_seconds||0}s`), diagCard('Memory RSS', py.memory_rss_h||py.memory_rss), diagCard('Threads', py.threads), diagCard('CPU', `${py.cpu_percent ?? '-'}%`), diagCard('Jobs total', py.jobs_total), @@ -1098,8 +1096,9 @@ diagCard('SCGI first byte', scgi.first_byte_ms!=null?`${scgi.first_byte_ms} ms`:'-'), diagCard('SCGI total', scgi.total_ms!=null?`${scgi.total_ms} ms`:'-'), diagCard('Request bytes', scgi.request_bytes), diagCard('Response bytes', scgi.response_bytes), diagCard('XML bytes', scgi.xml_bytes), diagCard('rTorrent version', scgi.client_version||'-') ]; - box.innerHTML=`
${cards.join('')}
${scgi.error?`
${esc(scgi.error)}
`:''}`; - }catch(e){ box.innerHTML=`
${esc(e.message)}
`; } + const smartBlock=`
Smart Queue statistics
${renderSmartQueueNerdStats(smartStats)}`; + box.innerHTML=`
${cards.join('')}
${smartBlock}${scgi.error?`
${esc(scgi.error)}
`:''}`; + }catch(e){ box.innerHTML=`
${esc(e.message)}
`; } } function torrentStatsCard(label, value, note=''){ diff --git a/pytorrent/static/styles.css b/pytorrent/static/styles.css index 6f39ba5..934d835 100644 --- a/pytorrent/static/styles.css +++ b/pytorrent/static/styles.css @@ -1425,7 +1425,7 @@ body.mobile-mode .mobile-card { white-space: normal; word-break: break-word; } -/* Note: Smart Queue nerd stats are scoped to Automations to avoid reusing or overriding generic cards. */ +/* Note: Smart Queue stats are reusable because they are shown in App status. */ .automation-smart-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); @@ -2326,6 +2326,34 @@ body.mobile-mode .mobile-filter-bar { color: var(--bs-secondary-color); } + +.about-summary-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); + gap: 0.6rem; +} + +.about-summary-grid div { + padding: 0.7rem; + border: 1px solid var(--bs-border-color); + border-radius: 0.75rem; + background: rgba(var(--bs-secondary-bg-rgb), 0.28); +} + +.about-summary-grid b, +.about-summary-grid span { + display: block; +} + +.about-summary-grid b { + margin-bottom: 0.2rem; +} + +.about-summary-grid span { + color: var(--bs-secondary-color); + font-size: 0.82rem; +} + .about-list { display: grid; gap: 0.55rem; diff --git a/pytorrent/templates/index.html b/pytorrent/templates/index.html index c4a1d7c..c7204e4 100644 --- a/pytorrent/templates/index.html +++ b/pytorrent/templates/index.html @@ -157,7 +157,7 @@