ux fix
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||
let hiddenColumns = new Set((window.PYTORRENT?.tableColumns?.hidden || []));
|
||||
let knownLabels = [];
|
||||
let jobsPage = 0, jobsLimit = 25, jobsTotal = 0, smartHistoryExpanded = false;
|
||||
let automationSmartQueueStats = null;
|
||||
let peersRefreshTimer = null;
|
||||
let peersRefreshSeconds = Number(window.PYTORRENT?.peersRefreshSeconds || 0);
|
||||
let portCheckEnabled = !!Number(window.PYTORRENT?.portCheckEnabled || 0);
|
||||
@@ -421,6 +422,41 @@
|
||||
|
||||
function smartHistoryDetails(row){ try{ return typeof row.details_json==='string'?JSON.parse(row.details_json||'{}'):(row.details_json||{}); }catch(e){ return {}; } }
|
||||
function smartQueueToastMessage(r){ const noEffect=r.start_no_effect?.length||0; const requested=r.start_requested?.length||0; const stopFailed=r.stop_failed?.length||0; const limit=r.max_active_downloads||r.settings?.max_active_downloads||''; const activeBefore=r.active_before; const activeAfter=r.active_after_stop ?? r.active_after_expected; const activeTail=activeBefore!==undefined?`, active ${esc(activeBefore)}->${esc(activeAfter ?? '?')}${limit?`/${esc(limit)}`:''}`:''; const cap=r.rtorrent_cap?.updated?`, cap ${r.rtorrent_cap.current}->${r.rtorrent_cap.new}`:''; const waiting=r.waiting_labeled||0; const stalled=r.stalled_labeled?.length||0; const tail=noEffect?`, no effect ${noEffect}`:requested?`, requested ${requested}`:''; const waitTail=waiting?`, waiting labeled ${waiting}`:''; const stalledTail=stalled?`, stalled ${stalled}`:''; const failTail=stopFailed?`, stop failed ${stopFailed}`:''; return `Smart Queue: stopped ${r.stopped?.length||r.paused?.length||0}, started ${r.started?.length||r.resumed?.length||0}${activeTail}${tail}${waitTail}${stalledTail}${failTail}${cap}`; }
|
||||
function buildSmartQueueNerdStats(hist=[], totalHistory=0){
|
||||
// Note: Small Smart Queue telemetry for automation nerds; it reads history only and does not affect queue behavior.
|
||||
const stats=hist.reduce((acc,h)=>{
|
||||
const details=smartHistoryDetails(h);
|
||||
const stopped=Number(h.paused_count||0);
|
||||
const started=Number(h.resumed_count||0);
|
||||
const checked=Number(h.checked_count||0);
|
||||
const over=Number(details.over_limit||0);
|
||||
const stopFailed=Array.isArray(details.stop_failed)?details.stop_failed.length:0;
|
||||
acc.checked += checked;
|
||||
acc.stopped += stopped;
|
||||
acc.started += started;
|
||||
acc.overLimit += over;
|
||||
acc.stopFailed += stopFailed;
|
||||
if(over>0) acc.overEvents += 1;
|
||||
return acc;
|
||||
},{checked:0,stopped:0,started:0,overLimit:0,overEvents:0,stopFailed:0});
|
||||
const latest=hist[0]||null;
|
||||
return {...stats,total:Number(totalHistory||hist.length||0),sample:hist.length,latestEvent:latest?.event||'-',latestAt:latest?.created_at||''};
|
||||
}
|
||||
|
||||
function renderSmartQueueNerdStats(stats){
|
||||
// Note: Compact cards keep the extra diagnostics readable above Automation history without changing the history table.
|
||||
if(!stats) return '<div class="automation-smart-stats empty-mini">No Smart Queue stats yet.</div>';
|
||||
const cards=[
|
||||
['Runs',stats.total,`${stats.sample} loaded`],
|
||||
['Checked',stats.checked,'torrent scans'],
|
||||
['Stopped',stats.stopped,'queue trims'],
|
||||
['Started',stats.started,'queue fills'],
|
||||
['Over limit',stats.overEvents,`${stats.overLimit} total over`],
|
||||
['Stop failed',stats.stopFailed,'rTorrent rejects'],
|
||||
['Latest',stats.latestEvent,stats.latestAt?dateCell(stats.latestAt):'no timestamp'],
|
||||
];
|
||||
return `<div class="automation-smart-stats" aria-label="Smart Queue nerd stats">${cards.map(([label,value,hint])=>`<div class="automation-smart-stat"><span>${esc(label)}</span><b>${esc(value)}</b><small>${hint}</small></div>`).join('')}</div>`;
|
||||
}
|
||||
async function loadSmartQueue(){
|
||||
if($('smartManager')) $('smartManager').innerHTML=loadingMarkup('Loading Smart Queue...');
|
||||
if($('smartHistory')) $('smartHistory').innerHTML=loadingMarkup('Loading Smart Queue history...');
|
||||
@@ -702,13 +738,14 @@
|
||||
return `<details class="automation-history-details"><summary>${summary||'No actions'}</summary><pre>${details}</pre></details>`;
|
||||
}
|
||||
|
||||
function renderAutomationHistory(hist=[]){
|
||||
function renderAutomationHistory(hist=[], smartStats=automationSmartQueueStats){
|
||||
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=toolbar+body;
|
||||
$('automationHistory').innerHTML=stats+toolbar+body;
|
||||
}
|
||||
|
||||
async function clearAutomationHistory(){
|
||||
@@ -732,8 +769,13 @@
|
||||
}
|
||||
|
||||
async function loadAutomations(){
|
||||
const j=await (await fetch('/api/automations')).json();
|
||||
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 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;
|
||||
@@ -742,7 +784,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);
|
||||
renderAutomationHistory(hist, automationSmartQueueStats);
|
||||
}
|
||||
|
||||
async function toggleAutomationRule(rule){
|
||||
|
||||
@@ -677,18 +677,32 @@ body {
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
/* Note: Browser title speed preference uses a two-column switch layout, so text aligns with the switch. */
|
||||
.browser-speed-pref {
|
||||
display: grid;
|
||||
gap: 0.25rem;
|
||||
align-content: center;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
align-items: center;
|
||||
column-gap: 0.75rem;
|
||||
row-gap: 0.2rem;
|
||||
min-height: 58px;
|
||||
margin: 0;
|
||||
padding: 0.55rem 0.75rem 0.55rem 2.6rem;
|
||||
padding: 0.55rem 0.75rem;
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: 0.65rem;
|
||||
background: rgba(var(--bs-secondary-bg-rgb), 0.35);
|
||||
}
|
||||
.browser-speed-pref .form-check-input {
|
||||
grid-row: 1 / span 2;
|
||||
grid-column: 1;
|
||||
float: none;
|
||||
margin: 0;
|
||||
}
|
||||
.browser-speed-pref .form-check-label {
|
||||
grid-column: 2;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.browser-speed-pref small {
|
||||
grid-column: 2;
|
||||
color: var(--bs-secondary-color);
|
||||
line-height: 1.2;
|
||||
}
|
||||
@@ -1359,6 +1373,35 @@ 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. */
|
||||
.automation-smart-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||||
gap: 0.5rem;
|
||||
margin: 0.5rem 0 0.75rem;
|
||||
}
|
||||
.automation-smart-stat {
|
||||
min-width: 0;
|
||||
padding: 0.5rem 0.6rem;
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: 0.6rem;
|
||||
background: rgba(var(--bs-secondary-bg-rgb), 0.28);
|
||||
}
|
||||
.automation-smart-stat span,
|
||||
.automation-smart-stat small {
|
||||
display: block;
|
||||
color: var(--bs-secondary-color);
|
||||
font-size: 0.72rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.automation-smart-stat b {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
font-size: 1rem;
|
||||
line-height: 1.3;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.automation-history-toolbar {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
@@ -2087,16 +2130,20 @@ body.mobile-mode .mobile-filter-bar {
|
||||
background: rgba(var(--bs-secondary-bg-rgb), 0.28);
|
||||
}
|
||||
|
||||
/* Note: Smart Queue switch resets Bootstrap's negative switch offset so it cannot overflow narrow frames. */
|
||||
.smart-toggle-row .form-check {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
flex: 0 0 auto;
|
||||
min-height: 0;
|
||||
margin: 0;
|
||||
padding-left: 2.25rem;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.smart-toggle-row .form-check-input {
|
||||
margin-top: 0;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.smart-setting-row .form-check-label,
|
||||
@@ -2171,7 +2218,7 @@ body.mobile-mode .mobile-filter-bar {
|
||||
}
|
||||
|
||||
.smart-toggle-row .form-check {
|
||||
padding-left: 0;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user