queue_stopped #3

Merged
gru merged 33 commits from queue_stopped into master 2026-05-08 23:45:33 +02:00
3 changed files with 53 additions and 19 deletions
Showing only changes of commit da17facf39 - Show all commits

View File

@@ -862,14 +862,13 @@
return `<details class="automation-history-details"><summary>${summary||'No actions'}</summary><pre>${details}</pre></details>`; return `<details class="automation-history-details"><summary>${summary||'No actions'}</summary><pre>${details}</pre></details>`;
} }
function renderAutomationHistory(hist=[], smartStats=automationSmartQueueStats){ function renderAutomationHistory(hist=[]){
if(!$('automationHistory')) return; if(!$('automationHistory')) return;
const stats=renderSmartQueueNerdStats(smartStats);
const toolbar='<div class="automation-history-toolbar"><button id="automationClearHistoryBtn" class="btn btn-xs btn-outline-danger" type="button"><i class="fa-solid fa-trash"></i> Clear history</button></div>'; const toolbar='<div class="automation-history-toolbar"><button id="automationClearHistoryBtn" class="btn btn-xs btn-outline-danger" type="button"><i class="fa-solid fa-trash"></i> Clear history</button></div>';
const rows=hist.map(h=>[humanDateCell(h.created_at),esc(h.rule_name||''),esc(h.torrent_name||h.torrent_hash||''),automationHistoryActions(h.actions_json||'')]); 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. // 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'):'<div class="empty-mini">No automation history yet.</div>'; const body=hist.length?responsiveTable(['Time','Rule','Torrent / batch','Actions'],rows,'automation-history-table'):'<div class="empty-mini">No automation history yet.</div>';
$('automationHistory').innerHTML=stats+toolbar+body; $('automationHistory').innerHTML=toolbar+body;
} }
async function clearAutomationHistory(){ async function clearAutomationHistory(){
@@ -893,13 +892,8 @@
} }
async function loadAutomations(){ async function loadAutomations(){
const [j,smart]=await Promise.all([ const j=await fetch('/api/automations').then(r=>r.json());
fetch('/api/automations').then(r=>r.json()),
fetch('/api/smart-queue?history_limit=100').then(r=>r.json()).catch(()=>({}))
]);
const rules=j.rules||[], hist=j.history||[]; 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; automationRulesCache=rules;
if($('automationManager')) $('automationManager').innerHTML=rules.length?rules.map(r=>{ if($('automationManager')) $('automationManager').innerHTML=rules.length?rules.map(r=>{
const enabled=!!r.enabled; const enabled=!!r.enabled;
@@ -908,7 +902,7 @@
const toggleClass=enabled?'btn-outline-warning':'btn-outline-success'; const toggleClass=enabled?'btn-outline-warning':'btn-outline-success';
return `<div class="automation-row"><div class="automation-row-main"><div><b>${esc(r.name)}</b> ${enabled?'<span class="badge text-bg-success">on</span>':'<span class="badge text-bg-secondary">off</span>'}</div><div class="small text-muted automation-rule-summary">${esc(ruleSummary(r))} · cooldown ${esc(r.cooldown_minutes||0)} min</div></div><div class="automation-row-actions"><button class="btn btn-xs ${toggleClass} automation-toggle" data-id="${esc(r.id)}" type="button" title="${toggleTitle}"><i class="fa-solid ${toggleIcon}"></i></button><button class="btn btn-xs btn-outline-secondary automation-edit" data-id="${esc(r.id)}" type="button" title="Edit automation"><i class="fa-solid fa-pen"></i></button><button class="btn btn-xs btn-outline-danger automation-delete" data-id="${esc(r.id)}" type="button" title="Delete automation"><i class="fa-solid fa-trash"></i></button></div></div>`; return `<div class="automation-row"><div class="automation-row-main"><div><b>${esc(r.name)}</b> ${enabled?'<span class="badge text-bg-success">on</span>':'<span class="badge text-bg-secondary">off</span>'}</div><div class="small text-muted automation-rule-summary">${esc(ruleSummary(r))} · cooldown ${esc(r.cooldown_minutes||0)} min</div></div><div class="automation-row-actions"><button class="btn btn-xs ${toggleClass} automation-toggle" data-id="${esc(r.id)}" type="button" title="${toggleTitle}"><i class="fa-solid ${toggleIcon}"></i></button><button class="btn btn-xs btn-outline-secondary automation-edit" data-id="${esc(r.id)}" type="button" title="Edit automation"><i class="fa-solid fa-pen"></i></button><button class="btn btn-xs btn-outline-danger automation-delete" data-id="${esc(r.id)}" type="button" title="Delete automation"><i class="fa-solid fa-trash"></i></button></div></div>`;
}).join(''):'<div class="empty-mini">No automation rules.</div>'; }).join(''):'<div class="empty-mini">No automation rules.</div>';
renderAutomationHistory(hist, automationSmartQueueStats); renderAutomationHistory(hist);
} }
async function toggleAutomationRule(rule){ async function toggleAutomationRule(rule){
@@ -1082,10 +1076,14 @@
const box=$('appStatusManager'); if(!box) return; const box=$('appStatusManager'); if(!box) return;
box.innerHTML='<span class="spinner-border spinner-border-sm"></span> Loading diagnostics...'; box.innerHTML='<span class="spinner-border spinner-border-sm"></span> Loading diagnostics...';
try{ 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'); 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 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 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=[ 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('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), 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('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||'-') 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=`<div class="diag-grid">${cards.join('')}</div>${scgi.error?`<div class="alert alert-danger mt-3 mb-0">${esc(scgi.error)}</div>`:''}`; const smartBlock=`<div class="section-title mt-3"><i class="fa-solid fa-list-check"></i> Smart Queue statistics</div>${renderSmartQueueNerdStats(smartStats)}`;
}catch(e){ box.innerHTML=`<div class="text-danger">${esc(e.message)}</div>`; } box.innerHTML=`<div class="diag-grid">${cards.join('')}</div>${smartBlock}${scgi.error?`<div class="alert alert-danger mt-3 mb-0">${esc(scgi.error)}</div>`:''}`;
}catch(e){ box.innerHTML=`<div class="alert alert-danger mb-0">${esc(e.message)}</div>`; }
} }
function torrentStatsCard(label, value, note=''){ function torrentStatsCard(label, value, note=''){

View File

@@ -1425,7 +1425,7 @@ body.mobile-mode .mobile-card {
white-space: normal; white-space: normal;
word-break: break-word; 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 { .automation-smart-stats {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
@@ -2326,6 +2326,34 @@ body.mobile-mode .mobile-filter-bar {
color: var(--bs-secondary-color); 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 { .about-list {
display: grid; display: grid;
gap: 0.55rem; gap: 0.55rem;

View File

@@ -157,7 +157,7 @@
<div class="modal fade" id="aboutModal" tabindex="-1" aria-labelledby="aboutModalLabel" aria-hidden="true"> <div class="modal fade" id="aboutModal" tabindex="-1" aria-labelledby="aboutModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered"> <div class="modal-dialog modal-dialog-centered modal-lg">
<div class="modal-content about-modal-content"> <div class="modal-content about-modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 id="aboutModalLabel" class="modal-title"><i class="fa-solid fa-robot"></i> About pyTorrent</h5> <h5 id="aboutModalLabel" class="modal-title"><i class="fa-solid fa-robot"></i> About pyTorrent</h5>
@@ -168,15 +168,22 @@
<div class="about-logo"><i class="fa-solid fa-robot"></i></div> <div class="about-logo"><i class="fa-solid fa-robot"></i></div>
<div> <div>
<h6>pyTorrent</h6> <h6>pyTorrent</h6>
<p>Lightweight web panel for rTorrent management.</p> <p>Lightweight web panel for rTorrent management, queue control and live torrent diagnostics.</p>
</div> </div>
</div> </div>
<div class="about-summary-grid mb-3">
<div><b>rTorrent panel</b><span>SCGI connection, live table, labels, ratios and tracker tools.</span></div>
<div><b>Automation</b><span>Rules, Smart Queue, RSS checks, cleanup and job queue support.</span></div>
<div><b>Operations</b><span>App status, port checks, traffic history and configurable UI preferences.</span></div>
</div>
<dl class="about-list"> <dl class="about-list">
<div><dt>Repository</dt><dd><a href="https://git.linuxiarz.pl/gru/pyTorrent" target="_blank" rel="noopener noreferrer"><i class="fa-brands fa-git-alt"></i> git.linuxiarz.pl/gru/pyTorrent</a></dd></div>
<div><dt>License</dt><dd>Open source</dd></div> <div><dt>License</dt><dd>Open source</dd></div>
<div><dt>Author</dt><dd>linuxiarz.pl</dd></div> <div><dt>Author</dt><dd>linuxiarz.pl</dd></div>
<div><dt>Backend</dt><dd>Python, Flask, Flask-SocketIO</dd></div> <div><dt>Backend</dt><dd>Python, Flask, Flask-SocketIO, SQLite</dd></div>
<div><dt>Frontend</dt><dd>Bootstrap, vanilla JavaScript, Font Awesome</dd></div> <div><dt>Frontend</dt><dd>Bootstrap, vanilla JavaScript, Chart.js, Font Awesome</dd></div>
<div><dt>Runtime</dt><dd>Gunicorn compatible, rTorrent over SCGI</dd></div> <div><dt>Runtime</dt><dd>Gunicorn compatible, systemd-ready, rTorrent over SCGI</dd></div>
<div><dt>Features</dt><dd>Magnet and torrent upload, file priorities, labels, ratio groups, Smart Queue, automation rules, RSS, traffic charts and diagnostics.</dd></div>
</dl> </dl>
</div> </div>
</div> </div>