queue_stopped #3
@@ -862,14 +862,13 @@
|
||||
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;
|
||||
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 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'):'<div class="empty-mini">No automation history yet.</div>';
|
||||
$('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 `<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>';
|
||||
renderAutomationHistory(hist, automationSmartQueueStats);
|
||||
renderAutomationHistory(hist);
|
||||
}
|
||||
|
||||
async function toggleAutomationRule(rule){
|
||||
@@ -1082,10 +1076,14 @@
|
||||
const box=$('appStatusManager'); if(!box) return;
|
||||
box.innerHTML='<span class="spinner-border spinner-border-sm"></span> 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=`<div class="diag-grid">${cards.join('')}</div>${scgi.error?`<div class="alert alert-danger mt-3 mb-0">${esc(scgi.error)}</div>`:''}`;
|
||||
}catch(e){ box.innerHTML=`<div class="text-danger">${esc(e.message)}</div>`; }
|
||||
const smartBlock=`<div class="section-title mt-3"><i class="fa-solid fa-list-check"></i> Smart Queue statistics</div>${renderSmartQueueNerdStats(smartStats)}`;
|
||||
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=''){
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -157,7 +157,7 @@
|
||||
|
||||
|
||||
<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-header">
|
||||
<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>
|
||||
<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 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">
|
||||
<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>Author</dt><dd>linuxiarz.pl</dd></div>
|
||||
<div><dt>Backend</dt><dd>Python, Flask, Flask-SocketIO</dd></div>
|
||||
<div><dt>Frontend</dt><dd>Bootstrap, vanilla JavaScript, Font Awesome</dd></div>
|
||||
<div><dt>Runtime</dt><dd>Gunicorn compatible, rTorrent over SCGI</dd></div>
|
||||
<div><dt>Backend</dt><dd>Python, Flask, Flask-SocketIO, SQLite</dd></div>
|
||||
<div><dt>Frontend</dt><dd>Bootstrap, vanilla JavaScript, Chart.js, Font Awesome</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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user