2 lines
20 KiB
JavaScript
2 lines
20 KiB
JavaScript
export const plannerToolsUiSource = " function ensurePlannerToolsUI(){\n addToolTab('planner','fa-calendar-days','Planner','appstatus');\n addToolTab('poller','fa-satellite-dish','Poller','appstatus');\n const host=$('toolRss')?.parentElement || document.querySelector('#toolsModal .modal-body');\n if(!host) return;\n if(!$('toolPlanner')){\n const panel=document.createElement('div');\n panel.id='toolPlanner'; panel.className='d-none';\n panel.innerHTML=`<div class=\"column-manager-tabs mb-3\">\n <ul class=\"nav nav-pills\" role=\"tablist\">\n <li class=\"nav-item\"><button class=\"nav-link active\" data-bs-toggle=\"pill\" data-bs-target=\"#plannerPane-settings\" type=\"button\" role=\"tab\"><i class=\"fa-solid fa-sliders\"></i> Settings</button></li>\n <li class=\"nav-item\"><button class=\"nav-link\" data-bs-toggle=\"pill\" data-bs-target=\"#plannerPane-history\" type=\"button\" role=\"tab\"><i class=\"fa-solid fa-clock-rotate-left\"></i> Action history</button></li>\n </ul>\n </div>\n <div class=\"tab-content\">\n <div id=\"plannerPane-settings\" class=\"tab-pane fade show active\" role=\"tabpanel\">\n <div class=\"surface-section smart-panel planner-panel\">\n <div class=\"smart-header planner-hero\">\n <div class=\"section-title\"><i class=\"fa-solid fa-calendar-days\"></i> Download planner <span id=\"plannerStatusBadge\" class=\"badge text-bg-secondary\">off</span></div>\n <div class=\"smart-header-actions\">${inlineSwitch('plannerEnabled')}</div>\n </div>\n <div id=\"plannerCurrentSummary\" class=\"smart-setting-row planner-current-summary mb-3\"><div><b><i class=\"fa-solid fa-sliders\"></i> Current settings</b><small class=\"planner-diagnostic-line\">Loading planner settings...</small></div></div>\n <div class=\"planner-layout\">\n <details class=\"planner-card planner-card-primary planner-disclosure\" open>\n <summary class=\"planner-card-title\"><span><i class=\"fa-solid fa-sliders\"></i> Basics</span><i class=\"fa-solid fa-chevron-down planner-card-chevron\"></i></summary>\n <div class=\"smart-input-grid planner-profile-grid\">\n <label class=\"smart-input-field\"><span>Planner profile</span><select id=\"plannerProfileName\" class=\"form-select form-select-sm\"><option value=\"night mode\">night mode</option><option value=\"weekend mode\">weekend mode</option><option value=\"low power mode\">low power mode</option><option value=\"unlimited mode\">unlimited mode</option></select><small>Saved per active rTorrent profile.</small></label>\n <label class=\"smart-input-field\"><span>Auto-resume grace s</span><input id=\"plannerResumeGrace\" class=\"form-control form-control-sm\" type=\"number\" min=\"0\" step=\"30\"><small>Wait before resuming planner-paused torrents.</small></label>\n <label class=\"smart-input-field\"><span>Manual override</span><select id=\"plannerOverrideSeconds\" class=\"form-select form-select-sm\"><option value=\"0\">clear override</option><option value=\"1800\">disable for 30 min</option><option value=\"3600\">disable for 1 hour</option><option value=\"7200\">disable for 2 hours</option></select><small id=\"plannerOverrideStatus\">No active override.</small></label>\n <label class=\"smart-input-field\"><span>Mode</span>${inlineSwitch('plannerDryRun')}<small>Dry-run previews actions without changing rTorrent.</small></label>\n </div>\n </details>\n <details class=\"planner-card planner-disclosure\">\n <summary class=\"planner-card-title\"><span><i class=\"fa-solid fa-clock\"></i> Hourly speed planner</span><i class=\"fa-solid fa-chevron-down planner-card-chevron\"></i></summary>\n ${plannerToggleRow('plannerHourlyEnabled','Use hourly speed limits','When enabled, the current hour overrides weekday and weekend speed limits.')}\n <div class=\"planner-hour-tools\"><button class=\"btn btn-xs btn-outline-secondary planner-hour-fill\" data-mbps=\"0\">Unlimited all day</button><button class=\"btn btn-xs btn-outline-secondary planner-hour-fill\" data-mbps=\"50\">50 Mbit/s all day</button><button class=\"btn btn-xs btn-outline-secondary planner-hour-fill\" data-mbps=\"100\">100 Mbit/s all day</button><button class=\"btn btn-xs btn-outline-primary\" id=\"plannerHourCopyWeekday\" type=\"button\">Copy weekday limit</button></div>\n <div id=\"plannerHourlyGrid\" class=\"planner-hour-grid\"></div>\n </details>\n <details class=\"planner-card planner-disclosure\">\n <summary class=\"planner-card-title\"><span><i class=\"fa-solid fa-calendar-week\"></i> Fallback speed limits</span><i class=\"fa-solid fa-chevron-down planner-card-chevron\"></i></summary>\n <div class=\"smart-input-grid planner-speed-grid\">${plannerSpeedCard('plannerWeekday','Weekday limits','Used when hourly planner is disabled')}${plannerSpeedCard('plannerWeekend','Weekend limits','Saturday and Sunday fallback')}</div>\n </details>\n <details class=\"planner-card planner-card-time planner-disclosure\">\n <summary class=\"planner-card-title\"><span><i class=\"fa-solid fa-moon\"></i> Time windows</span><i class=\"fa-solid fa-chevron-down planner-card-chevron\"></i></summary>\n <div class=\"planner-toggle-stack planner-toggle-stack-compact\">\n ${plannerToggleRow('plannerNightOnly','Download only at night','Pause downloads outside the selected window.')}\n ${plannerToggleRow('plannerQuietEnabled','Quiet hours','Pause active downloads during the selected quiet window.')}\n </div>\n <div class=\"smart-input-grid planner-time-grid\">\n <label class=\"smart-input-field\"><span>Night start</span><input id=\"plannerNightStart\" class=\"form-control form-control-sm\" type=\"time\"></label>\n <label class=\"smart-input-field\"><span>Night end</span><input id=\"plannerNightEnd\" class=\"form-control form-control-sm\" type=\"time\"></label>\n <label class=\"smart-input-field\"><span>Quiet start</span><input id=\"plannerQuietStart\" class=\"form-control form-control-sm\" type=\"time\"><small>Used only when quiet hours are enabled.</small></label>\n <label class=\"smart-input-field\"><span>Quiet end</span><input id=\"plannerQuietEnd\" class=\"form-control form-control-sm\" type=\"time\"><small>Downloads pause and DL limit is set to 0.</small></label>\n </div>\n </details>\n <details class=\"planner-card planner-card-protection planner-disclosure\">\n <summary class=\"planner-card-title\"><span><i class=\"fa-solid fa-shield-halved\"></i> Protection</span><i class=\"fa-solid fa-chevron-down planner-card-chevron\"></i></summary>\n <div class=\"planner-toggle-stack planner-protection-toggles\">\n ${plannerToggleRow('plannerCpuEnabled','CPU protection','Pause downloads when CPU usage stays above the threshold for about 10 seconds.')}\n ${plannerToggleRow('plannerDiskEnabled','Disk protection','Pause downloads and block new download starts when disk usage is high.')}\n ${plannerToggleRow('plannerNetworkEnabled','Network protection','Clamp Planner speed limits to configured network caps.')}\n ${plannerToggleRow('plannerLoadEnabled','Load protection','Pause downloads when system load is above threshold.')}\n ${plannerToggleRow('plannerAutoResume','Auto resume planner-paused torrents','Resume only torrents paused by the planner when all protection rules become clear.')}\n </div>\n <div class=\"smart-input-grid planner-protection-grid\">\n <label class=\"smart-input-field\"><span>CPU threshold %</span><input id=\"plannerCpuPercent\" class=\"form-control form-control-sm\" type=\"number\" min=\"1\" max=\"100\"></label>\n <label class=\"smart-input-field\"><span>Disk threshold %</span><input id=\"plannerDiskPercent\" class=\"form-control form-control-sm\" type=\"number\" min=\"1\" max=\"100\"><small>Uses the disk source configured in Preferences.</small></label>\n <label class=\"smart-input-field\"><span>Network max DL B/s</span><input id=\"plannerNetworkDown\" class=\"form-control form-control-sm\" type=\"number\" min=\"0\"><small>0 keeps scheduled limit.</small></label>\n <label class=\"smart-input-field\"><span>Network max UL B/s</span><input id=\"plannerNetworkUp\" class=\"form-control form-control-sm\" type=\"number\" min=\"0\"><small>Optional load/network cap.</small></label>\n <label class=\"smart-input-field\"><span>Load CPU %</span><input id=\"plannerLoadCpu\" class=\"form-control form-control-sm\" type=\"number\" min=\"1\" max=\"100\"><small>Network/load protection trigger.</small></label>\n </div>\n </details>\n <details class=\"planner-card planner-card-result planner-disclosure\" open><summary class=\"planner-card-title\"><span><i class=\"fa-solid fa-eye\"></i> Preview</span><i class=\"fa-solid fa-chevron-down planner-card-chevron\"></i></summary><small id=\"plannerPreview\">No preview loaded.</small></details>\n </div>\n <div class=\"tool-action-row planner-actions\"><button id=\"plannerSaveBtn\" class=\"btn btn-primary btn-sm\"><i class=\"fa-solid fa-floppy-disk\"></i> Save planner</button><button id=\"plannerCheckBtn\" class=\"btn btn-success btn-sm\"><i class=\"fa-solid fa-play\"></i> Apply now</button><button id=\"plannerDryRunBtn\" class=\"btn btn-outline-secondary btn-sm\"><i class=\"fa-solid fa-vial\"></i> Dry-run now</button><button id=\"plannerOverrideBtn\" class=\"btn btn-outline-warning btn-sm\"><i class=\"fa-solid fa-pause\"></i> Set override</button><button id=\"plannerPreviewBtn\" class=\"btn btn-outline-secondary btn-sm\"><i class=\"fa-solid fa-eye\"></i> Refresh preview</button></div>\n </div>\n </div>\n <div id=\"plannerPane-history\" class=\"tab-pane fade\" role=\"tabpanel\">\n <div class=\"surface-section smart-panel planner-panel\">\n <div class=\"section-title\"><i class=\"fa-solid fa-clock-rotate-left\"></i> Action history</div><div id=\"plannerHistory\" class=\"mt-2\">No actions yet.</div>\n </div>\n </div>\n </div>`\n host.appendChild(panel);\n renderPlannerHourlyGrid();\n // Note: Planner cards are collapsed by default; the summary bar keeps the active state visible.\n panel.addEventListener('change', e=>{ if(e.target.closest('#toolPlanner')) updatePlannerCurrentSummary(); });\n $('plannerSaveBtn')?.addEventListener('click',saveDownloadPlanner);\n $('plannerCheckBtn')?.addEventListener('click',()=>applyDownloadPlannerNow(false));\n $('plannerDryRunBtn')?.addEventListener('click',()=>applyDownloadPlannerNow(true));\n $('plannerOverrideBtn')?.addEventListener('click',setPlannerOverride);\n $('plannerPreviewBtn')?.addEventListener('click',loadPlannerPreview);\n $('plannerHistory')?.addEventListener('click',async e=>{\n const toggle=e.target.closest('#plannerHistoryToggle');\n const clear=e.target.closest('#plannerHistoryClear');\n if(toggle){ plannerHistoryExpanded=!plannerHistoryExpanded; await loadPlannerPreview(); return; }\n if(clear && confirm('Clear Planner action history?')){\n try{ await post('/api/download-planner/history',{},'DELETE'); plannerHistoryExpanded=false; await loadPlannerPreview(); toast('Planner history cleared','success'); }\n catch(err){ toast(err.message,'danger'); }\n }\n });\n $('plannerProfileName')?.addEventListener('change',applyPlannerPreset);\n $('plannerHourCopyWeekday')?.addEventListener('click',()=>copyPlannerSpeedToHours('plannerWeekday'));\n document.querySelectorAll('.planner-hour-fill').forEach(btn=>btn.addEventListener('click',()=>fillPlannerHours(Number(btn.dataset.mbps||0))));\n setupPlannerSpeedControls();\n }\n if(!$('toolPoller')){\n const panel=document.createElement('div');\n panel.id='toolPoller'; panel.className='d-none';\n panel.innerHTML=`<div class=\"surface-section smart-panel poller-panel\">\n <div class=\"smart-header\">\n <div><div class=\"section-title\"><i class=\"fa-solid fa-satellite-dish\"></i> Smart poller <span id=\"pollerStatusBadge\" class=\"badge text-bg-secondary\">normal</span></div><div class=\"tool-note\">Controls separate lightweight live polling and slower full torrent-list polling per active rTorrent profile.</div></div>\n <div class=\"smart-header-actions\">${inlineSwitch('pollerAdaptive')}</div>\n </div>\n <div class=\"smart-settings-list poller-layout\">\n <details class=\"planner-card poller-card poller-disclosure\" open>\n <summary class=\"planner-card-title\"><span><i class=\"fa-solid fa-gauge-high\"></i> Adaptive behavior</span><i class=\"fa-solid fa-chevron-down planner-card-chevron\"></i></summary>\n <div class=\"tool-note mb-2\">These values decide when the poller slows down, recovers after errors, or switches between active and idle refreshes.</div>\n <div class=\"smart-input-grid poller-input-grid\">\n <label class=\"smart-input-field\"><span>Active interval s</span><input id=\"pollerActive\" class=\"form-control form-control-sm\" type=\"number\" min=\"0.5\" step=\"0.5\"><small>Base interval while torrents are active. Safe fallback keeps this at least 3s.</small></label>\n <label class=\"smart-input-field\"><span>Idle interval s</span><input id=\"pollerIdle\" class=\"form-control form-control-sm\" type=\"number\" min=\"1\" step=\"1\"><small>Base interval when there is no active torrent activity. Safe fallback keeps this at least 15s.</small></label>\n <label class=\"smart-input-field\"><span>Error interval s</span><input id=\"pollerError\" class=\"form-control form-control-sm\" type=\"number\" min=\"2\" step=\"1\"><small>Delay after rTorrent errors before trying again.</small></label>\n <label class=\"smart-input-field\"><span>Slow threshold ms</span><input id=\"pollerSlowThreshold\" class=\"form-control form-control-sm\" type=\"number\" min=\"100\" step=\"100\"><small>Response time that triggers adaptive slowdown.</small></label>\n <label class=\"smart-input-field\"><span>Slowdown multiplier</span><input id=\"pollerSlowdown\" class=\"form-control form-control-sm\" type=\"number\" min=\"1\" step=\"0.5\"><small>Backoff factor used when rTorrent is slow.</small></label>\n <label class=\"smart-input-field\"><span>Recovery errors</span><input id=\"pollerRecoveryErrors\" class=\"form-control form-control-sm\" type=\"number\" min=\"1\" step=\"1\"><small>Repeated errors required before recovery mode.</small></label>\n </div>\n </details>\n <details class=\"planner-card poller-card poller-disclosure\" open>\n <summary class=\"planner-card-title\"><span><i class=\"fa-solid fa-bolt\"></i> Live poller</span><i class=\"fa-solid fa-chevron-down planner-card-chevron\"></i></summary>\n <div class=\"tool-note mb-2\">Fast lightweight loop for volatile data: speeds, torrent status changes and UI heartbeat. It should stay responsive, but not below the safe baseline on busy rTorrent instances.</div>\n <div class=\"smart-input-grid poller-input-grid\">\n <label class=\"smart-input-field\"><span>Live stats s</span><input id=\"pollerLiveStats\" class=\"form-control form-control-sm\" type=\"number\" min=\"0.5\" step=\"0.5\"><small>Lightweight speed/status refresh. Safe fallback keeps this at least 3s.</small></label>\n <label class=\"smart-input-field\"><span>System stats s</span><input id=\"pollerSystem\" class=\"form-control form-control-sm\" type=\"number\" min=\"1\" step=\"1\"><small>CPU, RAM and live status telemetry. Safe fallback keeps this at least 5s.</small></label>\n <label class=\"smart-input-field\"><span>Heartbeat s</span><input id=\"pollerHeartbeat\" class=\"form-control form-control-sm\" type=\"number\" min=\"2\" step=\"1\"><small>Minimum heartbeat spacing when no visible data changed.</small></label>\n </div>\n </details>\n <details class=\"planner-card poller-card poller-disclosure\" open>\n <summary class=\"planner-card-title\"><span><i class=\"fa-solid fa-list-check\"></i> Full poller</span><i class=\"fa-solid fa-chevron-down planner-card-chevron\"></i></summary>\n <div class=\"tool-note mb-2\">Slower loop for heavier work: full torrent snapshot/diff, tracker summary, disk state, queue, jobs and planner checks.</div>\n <div class=\"smart-input-grid poller-input-grid\">\n <label class=\"smart-input-field\"><span>Torrent list s</span><input id=\"pollerTorrentList\" class=\"form-control form-control-sm\" type=\"number\" min=\"5\" step=\"5\"><small>Full torrent snapshot and diff loop. Safe fallback keeps this at least 30s.</small></label>\n <label class=\"smart-input-field\"><span>Tracker stats s</span><input id=\"pollerTracker\" class=\"form-control form-control-sm\" type=\"number\" min=\"10\" step=\"5\"><small>Tracker summary refresh. Safe fallback keeps this at least 300s.</small></label>\n <label class=\"smart-input-field\"><span>Disk stats s</span><input id=\"pollerDisk\" class=\"form-control form-control-sm\" type=\"number\" min=\"5\" step=\"5\"><small>Disk usage refresh. Safe fallback keeps this at least 60s.</small></label>\n <label class=\"smart-input-field\"><span>Queue/job stats s</span><input id=\"pollerQueue\" class=\"form-control form-control-sm\" type=\"number\" min=\"5\" step=\"5\"><small>Smart Queue, jobs and planner checks. Safe fallback keeps this at least 15s.</small></label>\n </div>\n </details>\n ${plannerToggleRow('pollerSafeFallback','Safe fallback mode','When enabled, unsafe intervals are raised before saving. It protects rTorrent and the browser from too-aggressive refresh loops while keeping values that are already safe unchanged.')}\n <div id=\"pollerSafeFallbackPreview\" class=\"poller-safe-preview\">Enable Safe fallback mode to protect the app from too-aggressive poller intervals.</div>\n <div class=\"smart-setting-row poller-diagnostics-row\"><div><b><i class=\"fa-solid fa-stethoscope\"></i> Diagnostics</b><small id=\"pollerRuntime\" class=\"poller-diagnostic-line\">Not loaded.</small></div></div>\n </div>\n <div class=\"tool-action-row planner-actions\"><button id=\"pollerSaveBtn\" class=\"btn btn-primary btn-sm\"><i class=\"fa-solid fa-floppy-disk\"></i> Save poller</button><button id=\"pollerReloadBtn\" class=\"btn btn-outline-secondary btn-sm\"><i class=\"fa-solid fa-rotate\"></i> Reload</button></div>\n </div>`;\n host.appendChild(panel);\n $('pollerSaveBtn')?.addEventListener('click',savePollerSettings);\n $('pollerReloadBtn')?.addEventListener('click',loadPollerSettings);\n document.querySelectorAll('#toolPoller input').forEach(input=>input.addEventListener('input',()=>{ if(typeof updatePollerSafeFallbackPreview==='function') updatePollerSafeFallbackPreview(); }));\n document.querySelectorAll('#toolPoller input').forEach(input=>input.addEventListener('change',()=>{ if(typeof updatePollerSafeFallbackPreview==='function') updatePollerSafeFallbackPreview(); }));\n if(typeof updatePollerSafeFallbackPreview==='function') updatePollerSafeFallbackPreview();\n }\n }\n";
|