revert split js
This commit is contained in:
gru
2026-05-31 13:07:06 +02:00
parent b6a5003f2c
commit 6b8321e6e6
31 changed files with 253 additions and 148 deletions
-30
View File
@@ -9,21 +9,6 @@ import { torrentDetailsSource } from './torrentDetails.js';
import { modalsSource } from './modals.js';
import { rssSource } from './rss.js';
import { smartQueueSource } from './smartQueue.js';
import { rtorrentConfigSource } from './rtorrentConfig.js';
import { appearancePreferencesSource } from './appearancePreferences.js';
import { automationRulesSource } from './automationRules.js';
import { cleanupToolsSource } from './cleanupTools.js';
import { diagnosticCardsSource } from './diagnosticCards.js';
import { footerPreferencesSource } from './footerPreferences.js';
import { liveSpeedStatsSource } from './liveSpeedStats.js';
import { statusBarSource } from './statusBar.js';
import { portCheckUiSource } from './portCheckUi.js';
import { preferencesLoaderSource } from './preferencesLoader.js';
import { diskMonitorSource } from './diskMonitor.js';
import { portCheckActionsSource } from './portCheckActions.js';
import { appStatusSource } from './appStatus.js';
import { torrentStatsSource } from './torrentStats.js';
import { toolHelpersSource } from './toolHelpers.js';
import { authUsersSource } from './authUsers.js';
import { plannerSource } from './planner.js';
import { pollerSource } from './poller.js';
@@ -45,21 +30,6 @@ export const moduleSources = [
modalsSource,
rssSource,
smartQueueSource,
rtorrentConfigSource,
appearancePreferencesSource,
automationRulesSource,
cleanupToolsSource,
diagnosticCardsSource,
footerPreferencesSource,
liveSpeedStatsSource,
statusBarSource,
portCheckUiSource,
preferencesLoaderSource,
diskMonitorSource,
portCheckActionsSource,
appStatusSource,
torrentStatsSource,
toolHelpersSource,
authUsersSource,
plannerSource,
dashboardSource,
-2
View File
@@ -1,2 +0,0 @@
// Note: This chunk keeps one Tools/status feature separate from the former smartQueue.js bundle.
export const appStatusSource = "async function loadAppStatus(){\n const box=$('appStatusManager'); if(!box) return;\n box.innerHTML='<span class=\"spinner-border spinner-border-sm\"></span> Loading diagnostics...';\n try{\n const [status,poller]=await Promise.all([\n fetch('/api/app/status').then(r=>r.json()),\n fetch('/api/poller/settings').then(r=>r.json()).catch(()=>({}))\n ]);\n if(!status.ok) throw new Error(status.error||'Failed to load diagnostics');\n const st=status.status||{}, py=st.pytorrent||{}, scgi=st.scgi||{}, profile=st.profile||{};\n const rt=poller.runtime||{}, ps=poller.settings||{};\n // Note: App status now keeps only unique operational diagnostics; storage, jobs, planner and queue details stay in their dedicated tools.\n const processCards=[\n diagCard('PID', py.pid),\n diagCard('Uptime', `${py.uptime_seconds||0}s`),\n diagCard('Memory RSS', py.memory_rss_h||py.memory_rss),\n diagCard('Threads', py.threads),\n diagCard('CPU', `${py.cpu_percent ?? '-'}%`),\n diagCard('Python', py.python||'-'),\n diagCard('Worker threads', py.worker_threads ?? '-'),\n diagCard('Jobs total', py.jobs_total ?? '-')\n ];\n const pollerCards=[\n diagCard('Adaptive', ps.adaptive_enabled===false?'off':'on'),\n diagCard('Mode', rt.adaptive_mode||'-'),\n diagCard('Live interval', `${rt.live_stats_interval_seconds ?? ps.live_stats_interval_seconds ?? '-'}s`),\n diagCard('List interval', `${rt.torrent_list_interval_seconds ?? ps.torrent_list_interval_seconds ?? '-'}s`),\n diagCard('Last tick', `${rt.duration_ms||rt.last_tick_ms||0} ms`),\n diagCard('Tick gap', `${rt.last_tick_gap_ms||0} ms`),\n diagCard('Payload', fmtBytes(rt.emitted_payload_size||0)),\n diagCard('rTorrent calls', rt.rtorrent_call_count||0)\n ];\n const connectionCards=[\n diagCard('Active profile', profile.name||profile.id||'-'),\n diagCard('API response time', `${st.api_ms ?? '-'} ms`),\n diagCard('SCGI status', scgi.ok?'OK':'ERROR', scgi.ok?'':'diag-error'),\n diagCard('SCGI URL', scgi.url||'-'),\n diagCard('SCGI connect', scgi.connect_ms!=null?`${scgi.connect_ms} ms`:'-'),\n diagCard('SCGI first byte', scgi.first_byte_ms!=null?`${scgi.first_byte_ms} ms`:'-'),\n diagCard('SCGI total', scgi.total_ms!=null?`${scgi.total_ms} ms`:'-'),\n diagCard('Request bytes', scgi.request_bytes),\n diagCard('Response bytes', scgi.response_bytes),\n diagCard('XML bytes', scgi.xml_bytes),\n diagCard('rTorrent version', scgi.client_version||'-')\n ];\n const panes=[\n ['process','Process', `${diagnosticsSection('pyTorrent process', processCards)}${diagnosticsSection('Runtime poller', pollerCards)}`],\n ['connection','Connection', diagnosticsSection('Profile and rTorrent', connectionCards)]\n ];\n const tabs=`<div class=\"column-manager-tabs appstatus-tabs\"><ul class=\"nav nav-pills\">${panes.map((p,i)=>`<li class=\"nav-item\"><button class=\"nav-link ${i?'':'active'}\" type=\"button\" data-appstatus-pane=\"${p[0]}\">${p[1]}</button></li>`).join('')}</ul></div>`;\n if($('appStatusTabs')) $('appStatusTabs').innerHTML=tabs;\n box.innerHTML=`${panes.map((p,i)=>`<div class=\"appstatus-pane ${i?'d-none':''}\" data-appstatus-panel=\"${p[0]}\">${p[2]}</div>`).join('')}${scgi.error?`<div class=\"alert alert-danger mt-3 mb-0\">${esc(scgi.error)}</div>`:''}`;\n }catch(e){ box.innerHTML=`<div class=\"alert alert-danger mb-0\">${esc(e.message)}</div>`; }\n }\n";
@@ -1,2 +0,0 @@
// Note: This chunk was split from smartQueue.js so each Tools feature can evolve independently.
export const appearancePreferencesSource = "function bootstrapThemeUrl(theme){ /* Note: Themes use the URL map generated by the backend, so they also work offline. */ const key=theme||\"default\"; return window.PYTORRENT?.bootstrapThemeUrls?.[key] || window.PYTORRENT?.bootstrapThemeUrls?.default || \"\"; }\n function applyBootstrapTheme(theme){\n // Note: Custom Bootstrap 2-inspired themes are normal selectable themes and keep light/dark compatibility through data-bs-theme.\n bootstrapTheme = theme || \"default\";\n document.documentElement.dataset.bootstrapSkin = bootstrapTheme;\n const link=$(\"bootstrapThemeStylesheet\");\n if(link) link.href = bootstrapThemeUrl(bootstrapTheme);\n if($(\"bootstrapThemeSelect\")) $(\"bootstrapThemeSelect\").value = bootstrapTheme;\n }\n function applyFontFamily(font){ fontFamily = font || \"default\"; document.documentElement.dataset.appFont = fontFamily; if($(\"fontFamilySelect\")) $(\"fontFamilySelect\").value = fontFamily; }\n function clampInterfaceScale(value){ value = Number(value || 100); if(!Number.isFinite(value)) value = 100; return Math.max(80, Math.min(140, Math.round(value / 5) * 5)); }\n function applyInterfaceScale(value){ interfaceScale = clampInterfaceScale(value); document.documentElement.style.setProperty(\"--ui-scale\", String(interfaceScale / 100)); if($(\"interfaceScaleRange\")) $(\"interfaceScaleRange\").value = interfaceScale; if($(\"interfaceScaleValue\")) $(\"interfaceScaleValue\").textContent = `${interfaceScale}%`; scheduleRender(false); }\n function torrentRowHeight(){ return compactTorrentListEnabled ? COMPACT_ROW_HEIGHT : ROW_HEIGHT; }\n function applyCompactTorrentList(value){\n // Note: The compact switch changes density only; filtering, sorting and existing row actions stay unchanged.\n compactTorrentListEnabled = !!value;\n document.body.classList.toggle(\"compact-torrent-list\", compactTorrentListEnabled);\n if($(\"compactTorrentListEnabled\")) $(\"compactTorrentListEnabled\").checked = compactTorrentListEnabled;\n scheduleRender(true);\n }\n async function saveAppearancePreferences(){ applyBootstrapTheme($(\"bootstrapThemeSelect\")?.value || \"default\"); applyFontFamily($(\"fontFamilySelect\")?.value || \"default\"); applyInterfaceScale($(\"interfaceScaleRange\")?.value || interfaceScale); applyCompactTorrentList($(\"compactTorrentListEnabled\")?.checked); try{ await post(\"/api/preferences\",{bootstrap_theme:bootstrapTheme,font_family:fontFamily,interface_scale:interfaceScale,compact_torrent_list_enabled:compactTorrentListEnabled}); toast(\"Appearance preferences saved\",\"success\"); }catch(e){ toast(e.message,\"danger\"); } }\n if($(\"titleSpeedEnabled\")) $(\"titleSpeedEnabled\").checked=titleSpeedEnabled;\n applyBootstrapTheme(bootstrapTheme);\n applyCompactTorrentList(compactTorrentListEnabled);\n\n function setupPeersRefresh(tab=activeTab()){ clearInterval(peersRefreshTimer); peersRefreshTimer=null; if($('peersRefreshSelect')) $('peersRefreshSelect').value=String(peersRefreshSeconds||0); if(tab==='peers' && peersRefreshSeconds>0){ peersRefreshTimer=setInterval(()=>{ if(activeTab()==='peers' && selectedHash) loadDetails('peers'); }, peersRefreshSeconds*1000); } }\n function refreshPeersOnceForReverseDns(){\n // Note: Reverse DNS can resolve after the first peers fetch, so trigger one silent follow-up even when auto-refresh is disabled.\n if(activeTab()==='peers' && selectedHash){\n loadDetails('peers');\n setTimeout(()=>{ if(activeTab()==='peers' && selectedHash) loadDetails('peers',{silent:true}); }, 1200);\n }\n }\n function syncMobileMode(){ const auto=window.matchMedia&&window.matchMedia(\"(max-width: 900px)\").matches; document.body.classList.toggle(\"mobile-mode\", auto || document.body.classList.contains(\"mobile-mode-manual\")); scheduleRender(true); }\n\n\n let automationRulesCache=[];\n let automationConditions=[];\n let automationEffects=[];\n";
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-2
View File
@@ -1,2 +0,0 @@
// Note: This chunk keeps one Tools/status feature separate from the former smartQueue.js bundle.
export const diagnosticCardsSource = "function diagCard(label,value,extra=''){ return `<div class=\"diag-card ${extra}\"><b>${esc(label)}</b><span>${esc(value ?? '-')}</span></div>`; }\n\n // Note: Centralizes footer visibility so Preferences can hide items without removing existing status logic.\n";
File diff suppressed because one or more lines are too long
-2
View File
@@ -1,2 +0,0 @@
// Note: This chunk keeps one Tools/status feature separate from the former smartQueue.js bundle.
export const footerPreferencesSource = "function applyFooterPreferences(){\n document.querySelectorAll('[data-footer-item]').forEach(el=>{\n const key=el.dataset.footerItem;\n el.classList.toggle('footer-pref-hidden', footerItems[key] === false);\n });\n }\n function renderFooterPreferences(){\n const box=$('footerPreferences');\n if(!box) return;\n box.innerHTML=FOOTER_ITEM_DEFS.map(([key,label])=>`<label class=\"footer-pref-card form-check form-switch ${footerItems[key]===false?'':'active'}\"><input class=\"form-check-input footer-pref-toggle\" type=\"checkbox\" data-footer-key=\"${esc(key)}\" ${footerItems[key]===false?'':'checked'}><span class=\"form-check-label\">${esc(label)}</span></label>`).join('');\n }\n async function saveFooterPreferences(){\n document.querySelectorAll('.footer-pref-toggle').forEach(cb=>{ footerItems[cb.dataset.footerKey] = !!cb.checked; });\n applyFooterPreferences();\n renderFooterPreferences();\n try{ await post('/api/preferences',{footer_items_json:footerItems}); toast('Footer preferences saved','success'); }\n catch(e){ toast(e.message,'danger'); }\n }\n";
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-2
View File
@@ -1,2 +0,0 @@
// Note: This chunk keeps one Tools/status feature separate from the former smartQueue.js bundle.
export const portCheckActionsSource = "async function savePortCheckPref(){ portCheckEnabled=!!$('portCheckEnabled')?.checked; try{ await post('/api/preferences',{port_check_enabled:portCheckEnabled}); toast('Preferences saved','success'); await loadPortCheck(false); }catch(e){ toast(e.message,'danger'); } }\n async function loadPortCheck(force=false){ try{ const res=force?await post('/api/port-check',{}):await (await fetch('/api/port-check')).json(); if(!res.ok) throw new Error(res.error||'Port check failed'); renderPortCheck(res.port_check||{}); }catch(e){ renderPortCheck({status:'error',enabled:portCheckEnabled,error:e.message}); } }\n";
-2
View File
@@ -1,2 +0,0 @@
// Note: This chunk keeps one Tools/status feature separate from the former smartQueue.js bundle.
export const portCheckUiSource = "function portStatusLabel(st){ return st==='open'?'open':st==='closed'?'closed':st==='disabled'?'disabled':st==='error'?'error':'unknown'; }\n function portStatusClass(st){ return st==='open'?'port-ok':st==='closed'?'port-bad':'port-secondary'; }\n function portStatusIcon(st){ return st==='open'?'fa-circle-check':st==='closed'?'fa-circle-xmark':'fa-circle-question'; }\n function portStatusBadge(data={},attrs='',withPort=false){ const st=portStatusLabel(data.status); const active=data.open_port||data.port; const port=active?String(active):'-'; const label=withPort?`Port ${port} ${st}`:st; return `<span ${attrs}class=\"port-status ${portStatusClass(st)}\"><i class=\"fa-solid ${portStatusIcon(st)}\"></i> ${esc(label)}</span>`; }\n function portCheckedAt(data={}){ if(data.checked_at) return String(data.checked_at).replace('T',' ').replace(/\\+00:00$/,' UTC'); if(data.checked_at_epoch) return new Date(Number(data.checked_at_epoch)*1000).toLocaleString(); return ''; }\n function portCheckDetails(data={}){ const bits=[]; if(data.open_port) bits.push(`Open port: ${data.open_port}`); else if(data.port) bits.push(`First port: ${data.port}`); if(Array.isArray(data.ports)&&data.ports.length>1) bits.push(`Candidates: ${data.ports.join(', ')}`); if(Array.isArray(data.checked_ports)&&data.checked_ports.length) bits.push(`Checked: ${data.checked_ports.join(', ')}`); if(data.ports_truncated) bits.push('Port list truncated to safety limit'); if(data.public_ip) bits.push(`Public IP: ${data.public_ip}`); if(data.remote) bits.push('Remote profile'); if(data.source) bits.push(`Source: ${data.source}`); const checked=portCheckedAt(data); if(checked) bits.push(`Last check: ${checked}`); if(data.cached) bits.push('Cached result'); if(data.error) bits.push(data.error); if(data.fallback_error) bits.push(data.fallback_error); return bits; }\n function renderPortCheck(data={}){\n if($('portCheckEnabled')) $('portCheckEnabled').checked=!!data.enabled;\n const details=portCheckDetails(data);\n const title=details.join(' · ') || 'Port check disabled';\n if($('portCheckBadge')) $('portCheckBadge').outerHTML=portStatusBadge(data,'id=\"portCheckBadge\" ');\n if($('portCheckInfo')) $('portCheckInfo').textContent=details.join(' · ') || 'Uses YouGetSignal first. Manual check bypasses the 6h cache.';\n if($('statusPortCheck')){\n $('statusPortCheck').classList.toggle('d-none', !data.enabled);\n $('statusPortCheck').title=title;\n }\n if($('statusPortCheckBadge')) $('statusPortCheckBadge').outerHTML=portStatusBadge(data,'id=\"statusPortCheckBadge\" ',true);\n }\n";
-2
View File
@@ -1,2 +0,0 @@
// Note: This chunk keeps one Tools/status feature separate from the former smartQueue.js bundle.
export const preferencesLoaderSource = "async function loadPreferences(){\n try{\n const j=await (await fetch(`/api/preferences?_=${Date.now()}`, {cache:'no-store'})).json();\n const prefs=j.preferences||{};\n portCheckEnabled=!!Number(prefs.port_check_enabled ?? portCheckEnabled);\n reverseDnsEnabled=!!Number(prefs.reverse_dns_enabled ?? (reverseDnsEnabled?1:0));\n if($('reverseDnsEnabled')) $('reverseDnsEnabled').checked=reverseDnsEnabled;\n automationToastsEnabled=Number(prefs.automation_toasts_enabled ?? (automationToastsEnabled?1:0))!==0;\n smartQueueToastsEnabled=Number(prefs.smart_queue_toasts_enabled ?? (smartQueueToastsEnabled?1:0))!==0;\n easterEggEnabled=Number(prefs.easter_egg_enabled ?? (easterEggEnabled?1:0))!==0;\n easterEggLoadingImageUrl=String(prefs.easter_egg_loading_image_url ?? easterEggLoadingImageUrl ?? '').trim();\n easterEggClickImageUrl=String(prefs.easter_egg_click_image_url ?? easterEggClickImageUrl ?? '').trim();\n diskMonitorMode=prefs.disk_monitor_mode||diskMonitorMode;\n diskMonitorSelectedPath=prefs.disk_monitor_selected_path||'';\n try{ diskMonitorPaths=JSON.parse(prefs.disk_monitor_paths_json||'[]'); }catch(_){ diskMonitorPaths=[]; }\n bootstrapTheme=prefs.bootstrap_theme||bootstrapTheme;\n fontFamily=prefs.font_family||fontFamily;\n interfaceScale=Number(prefs.interface_scale||interfaceScale||100);\n compactTorrentListEnabled=Number(prefs.compact_torrent_list_enabled ?? (compactTorrentListEnabled?1:0))!==0;\n try{ footerItems={...DEFAULT_FOOTER_ITEMS,...JSON.parse(prefs.footer_items_json||'{}')}; }catch(_){ footerItems={...DEFAULT_FOOTER_ITEMS}; }\n }catch(e){ console.warn('Preference load failed', e); }\n if($('portCheckEnabled')) $('portCheckEnabled').checked=portCheckEnabled; if($('automationToastsEnabled')) $('automationToastsEnabled').checked=automationToastsEnabled; if($('smartQueueToastsEnabled')) $('smartQueueToastsEnabled').checked=smartQueueToastsEnabled; if($('easterEggEnabled')) $('easterEggEnabled').checked=easterEggEnabled; if($('easterEggLoadingImageUrl')) $('easterEggLoadingImageUrl').value=easterEggLoadingImageUrl; if($('easterEggClickImageUrl')) $('easterEggClickImageUrl').value=easterEggClickImageUrl; if($('diskMonitorMode')) $('diskMonitorMode').value=diskMonitorMode; if($('diskMonitorSelectedPath')) $('diskMonitorSelectedPath').value=diskMonitorSelectedPath; renderDiskMonitorPaths(); applyInitialLoaderEasterEgg(); scheduleRender(true); applyBootstrapTheme(bootstrapTheme); applyFontFamily(fontFamily); applyInterfaceScale(interfaceScale); applyCompactTorrentList(compactTorrentListEnabled); renderFooterPreferences(); applyFooterPreferences(); await loadPortCheck(false); }\n";
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
-2
View File
@@ -1,2 +0,0 @@
// Note: This chunk keeps one Tools/status feature separate from the former smartQueue.js bundle.
export const statusBarSource = "function updateFooterClock(){\n const el=$('statClock');\n if(el) el.textContent=new Date().toLocaleTimeString([], {hour:'2-digit', minute:'2-digit', second:'2-digit'});\n }\n function updateSocketStatus(s={}){\n const el=$('statSockets');\n if(!el) return;\n const open=s.open_sockets;\n const max=s.max_open_sockets;\n el.textContent=open == null ? '-' : (max == null ? String(open) : `${open}/${max}`);\n const box=$('statusSockets');\n if(box) box.title=open == null ? 'Open sockets unavailable from this rTorrent build' : `Open rTorrent sockets${max == null ? '' : ' / max'}: ${el.textContent}`;\n }\n";
-2
View File
@@ -1,2 +0,0 @@
// Note: This chunk was split from smartQueue.js so each Tools feature can evolve independently.
export const toolHelpersSource = "function addToolTab(tool, icon, label, beforeTool='appstatus'){\n if(document.querySelector(`.tool-tab[data-tool=\"${tool}\"]`)) return;\n const nav=document.querySelector('#toolsModal .nav.nav-pills');\n if(!nav) return;\n const li=document.createElement('li');\n li.className='nav-item';\n li.innerHTML=`<button class=\"nav-link tool-tab\" data-tool=\"${tool}\" type=\"button\"><i class=\"fa-solid ${icon}\"></i> ${label}</button>`;\n const before=document.querySelector(`#toolsModal .tool-tab[data-tool=\"${beforeTool}\"]`)?.closest('.nav-item');\n nav.insertBefore(li,before||null);\n li.querySelector('.tool-tab')?.addEventListener('click',()=>activateToolTab(tool));\n }\n function inlineSwitch(id,label='Enable',extraClass=''){\n return `<label class=\"form-check form-switch inline-switch ${extraClass}\"><input id=\"${id}\" class=\"form-check-input\" type=\"checkbox\"><span class=\"form-check-label\">${label}</span></label>`;\n }\n function plannerToggleRow(id,title,description){\n return `<div class=\"smart-setting-row smart-toggle-row\"><div><b>${title}</b><small>${description}</small></div>${inlineSwitch(id)}</div>`;\n }\n function plannerSpeedCard(prefix,title,sub){\n return `<div class=\"smart-input-field planner-speed-card\" data-planner-speed=\"${prefix}\">\n <span>${title}</span>\n <small>${sub}</small>\n <div class=\"planner-limit-summary\" id=\"${prefix}Summary\">Unlimited</div>\n <div class=\"planner-presets\" aria-label=\"${title} presets\">\n <button class=\"btn btn-sm btn-outline-secondary planner-speed-preset\" data-prefix=\"${prefix}\" data-mbps=\"0\" type=\"button\">Unlimited</button>\n <button class=\"btn btn-sm btn-outline-secondary planner-speed-preset\" data-prefix=\"${prefix}\" data-mbps=\"50\" type=\"button\">50</button>\n <button class=\"btn btn-sm btn-outline-secondary planner-speed-preset\" data-prefix=\"${prefix}\" data-mbps=\"100\" type=\"button\">100</button>\n <button class=\"btn btn-sm btn-outline-secondary planner-speed-preset\" data-prefix=\"${prefix}\" data-mbps=\"250\" type=\"button\">250</button>\n <button class=\"btn btn-sm btn-outline-secondary planner-speed-preset\" data-prefix=\"${prefix}\" data-mbps=\"500\" type=\"button\">500</button>\n <button class=\"btn btn-sm btn-outline-secondary planner-speed-preset\" data-prefix=\"${prefix}\" data-mbps=\"1000\" type=\"button\">1G</button>\n </div>\n <div class=\"planner-speed-sliders\">\n <label>Download <b id=\"${prefix}DownMbps\">Unlimited</b><input id=\"${prefix}DownSlider\" class=\"form-range planner-mbps-slider\" type=\"range\" min=\"0\" max=\"1000\" step=\"5\" value=\"0\" data-target=\"${prefix}Down\"></label>\n <input id=\"${prefix}Down\" class=\"form-control form-control-sm planner-byte-input\" type=\"number\" min=\"0\" placeholder=\"B/s\" aria-label=\"${title} download bytes per second\">\n <label>Upload <b id=\"${prefix}UpMbps\">Unlimited</b><input id=\"${prefix}UpSlider\" class=\"form-range planner-mbps-slider\" type=\"range\" min=\"0\" max=\"1000\" step=\"5\" value=\"0\" data-target=\"${prefix}Up\"></label>\n <input id=\"${prefix}Up\" class=\"form-control form-control-sm planner-byte-input\" type=\"number\" min=\"0\" placeholder=\"B/s\" aria-label=\"${title} upload bytes per second\">\n </div>\n <small>Slider uses Mbit/s. Numeric fields store B/s for rTorrent.</small>\n </div>`;\n }\n";
File diff suppressed because one or more lines are too long
-2
View File
@@ -1,2 +0,0 @@
// Note: This chunk was split from smartQueue.js so each Tools feature can evolve independently.
export const torrentStatsSource = "const TORRENT_STATS_PANE_STORAGE_KEY = 'pytorrent.torrentStatsPane.v1';\n function torrentStatsCard(label, value, note=''){\n return `<div class=\"torrent-stats-card\"><b>${esc(label)}</b><span>${esc(value ?? '-')}</span>${note?`<small>${esc(note)}</small>`:''}</div>`;\n }\n function activeTorrentStatsPane(){\n const value=localStorage.getItem(TORRENT_STATS_PANE_STORAGE_KEY)||'overview';\n return ['overview','storage','sources','speed','cache'].includes(value) ? value : 'overview';\n }\n function setTorrentStatsPane(pane){\n const box=$('torrentStatsManager');\n if(!box) return;\n localStorage.setItem(TORRENT_STATS_PANE_STORAGE_KEY, pane);\n box.querySelectorAll('[data-torrentstats-pane]').forEach(x=>x.classList.toggle('active',x.dataset.torrentstatsPane===pane));\n box.querySelectorAll('[data-torrentstats-panel]').forEach(x=>x.classList.toggle('d-none',x.dataset.torrentstatsPanel!==pane));\n }\n function renderTorrentStats(stats={}){\n const box=$('torrentStatsManager');\n if(!box) return;\n const age=Number(stats.age_seconds||0);\n const updated=stats.updated_at ? String(stats.updated_at).replace('T',' ').replace(/\\+00:00$/,' UTC') : '-';\n const active=activeTorrentStatsPane();\n const panes=[\n ['overview','Overview', [\n torrentStatsCard('Torrents', stats.torrent_count, `${stats.complete_count||0} complete / ${stats.incomplete_count||0} incomplete`),\n torrentStatsCard('Sampled', stats.sampled_torrents ?? 0, stats.stale?'cache is stale':'cache is fresh')\n ]],\n ['storage','Storage', [\n torrentStatsCard('Torrent size', stats.total_torrent_size_h || fmtBytes(stats.total_torrent_size)),\n torrentStatsCard('Files size', stats.total_file_size_h || fmtBytes(stats.total_file_size), `${stats.file_count||0} files`)\n ]],\n ['sources','Seeds / peers', [\n torrentStatsCard('Seeds / peers', `${stats.seeds_total||0} / ${stats.peers_total||0}`, 'current sum from last sample')\n ]],\n ['speed','Speed', [\n torrentStatsCard('Speed DL / UL', `${stats.down_rate_total_h||'0 B/s'} / ${stats.up_rate_total_h||'0 B/s'}`)\n ]],\n ['cache','Cache', [\n torrentStatsCard('Updated', updated),\n torrentStatsCard('Age', `${age}s`)\n ]]\n ];\n if($('torrentStatsMeta')) $('torrentStatsMeta').textContent=`Updated: ${updated}, age: ${age}s`;\n const errors=Array.isArray(stats.errors)&&stats.errors.length ? `<div class=\"alert alert-warning py-2 mt-3 mb-0\">File metadata warnings: ${esc(stats.errors.length)} torrent(s). ${esc(stats.error||'')}</div>` : '';\n box.innerHTML=`<div class=\"column-manager-tabs\"><ul class=\"nav nav-pills\">${panes.map(p=>`<li class=\"nav-item\"><button class=\"nav-link ${p[0]===active?'active':''}\" type=\"button\" data-torrentstats-pane=\"${p[0]}\">${p[1]}</button></li>`).join('')}</ul></div>${panes.map(p=>`<div class=\"torrentstats-pane ${p[0]===active?'':'d-none'}\" data-torrentstats-panel=\"${p[0]}\"><div class=\"torrent-stats-grid\">${p[2].join('')}</div></div>`).join('')}${errors}`;\n }\n async function loadTorrentStats(force=false){\n const box=$('torrentStatsManager');\n if(!box) return;\n box.innerHTML='<span class=\"spinner-border spinner-border-sm\"></span> Loading torrent statistics...';\n try{\n const j=await (await fetch(`/api/torrent-stats${force?'?force=1':''}`)).json();\n if(!j.ok) throw new Error(j.error||'Torrent statistics failed');\n renderTorrentStats(j.stats||{});\n if(force) toast('Torrent statistics refreshed','success');\n }catch(e){ box.innerHTML=`<div class=\"text-danger\">${esc(e.message)}</div>`; }\n }\n";
File diff suppressed because one or more lines are too long
+91 -42
View File
@@ -2046,6 +2046,7 @@ body.mobile-mode .mobile-filter-bar {
}
.rt-config-card-head small,
.rt-config-runtime-note,
.rt-config-value-note {
display: block;
overflow-wrap: anywhere;
@@ -2119,10 +2120,15 @@ body.mobile-mode .mobile-filter-bar {
box-shadow: 0 0 0 0.12rem rgba(var(--bs-danger-rgb), 0.18);
}
.rt-config-runtime-note,
.rt-config-value-note {
margin-top: -0.2rem;
}
.rt-config-runtime-note i {
color: var(--bs-warning-text-emphasis);
}
.rt-config-output {
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 0.82rem;
@@ -4641,7 +4647,7 @@ body,
}
}
/* Note: Peers tables keep hostnames readable without letting the Host column dominate the layout. */
/* Note: Peers tables keep hostnames readable and keep progress columns stable. */
.peers-table {
min-width: 960px;
table-layout: fixed;
@@ -4656,56 +4662,76 @@ body,
}
.peers-table .peer-progress-wide {
min-width: 108px;
min-width: 0;
width: 100%;
}
.peers-table-hosts th:nth-child(1),
.peers-table-hosts td:nth-child(1) {
.peers-table:not(.mobile-details-peers-table):not(.peers-table-hosts) th:nth-child(1),
.peers-table:not(.mobile-details-peers-table):not(.peers-table-hosts) td:nth-child(1),
.peers-table:not(.mobile-details-peers-table).peers-table-hosts th:nth-child(1),
.peers-table:not(.mobile-details-peers-table).peers-table-hosts td:nth-child(1) {
width: 4%;
}
.peers-table-hosts th:nth-child(2),
.peers-table-hosts td:nth-child(2) {
.peers-table:not(.mobile-details-peers-table):not(.peers-table-hosts) th:nth-child(2),
.peers-table:not(.mobile-details-peers-table):not(.peers-table-hosts) td:nth-child(2),
.peers-table:not(.mobile-details-peers-table).peers-table-hosts th:nth-child(2),
.peers-table:not(.mobile-details-peers-table).peers-table-hosts td:nth-child(2) {
width: 13%;
}
.peers-table-hosts th:nth-child(3),
.peers-table-hosts td:nth-child(3) {
.peers-table:not(.mobile-details-peers-table).peers-table-hosts th:nth-child(3),
.peers-table:not(.mobile-details-peers-table).peers-table-hosts td:nth-child(3) {
width: 15%;
}
.peers-table-hosts th:nth-child(4),
.peers-table-hosts td:nth-child(4),
.peers-table-hosts th:nth-child(5),
.peers-table-hosts td:nth-child(5) {
.peers-table:not(.mobile-details-peers-table):not(.peers-table-hosts) th:nth-child(3),
.peers-table:not(.mobile-details-peers-table):not(.peers-table-hosts) td:nth-child(3),
.peers-table:not(.mobile-details-peers-table):not(.peers-table-hosts) th:nth-child(4),
.peers-table:not(.mobile-details-peers-table):not(.peers-table-hosts) td:nth-child(4),
.peers-table:not(.mobile-details-peers-table).peers-table-hosts th:nth-child(4),
.peers-table:not(.mobile-details-peers-table).peers-table-hosts td:nth-child(4),
.peers-table:not(.mobile-details-peers-table).peers-table-hosts th:nth-child(5),
.peers-table:not(.mobile-details-peers-table).peers-table-hosts td:nth-child(5) {
width: 8%;
}
.peers-table-hosts th:nth-child(6),
.peers-table-hosts td:nth-child(6) {
.peers-table:not(.mobile-details-peers-table):not(.peers-table-hosts) th:nth-child(5),
.peers-table:not(.mobile-details-peers-table):not(.peers-table-hosts) td:nth-child(5),
.peers-table:not(.mobile-details-peers-table).peers-table-hosts th:nth-child(6),
.peers-table:not(.mobile-details-peers-table).peers-table-hosts td:nth-child(6) {
width: 15%;
}
.peers-table-hosts th:nth-child(7),
.peers-table-hosts td:nth-child(7) {
width: 10%;
.peers-table:not(.mobile-details-peers-table):not(.peers-table-hosts) th:nth-child(6),
.peers-table:not(.mobile-details-peers-table):not(.peers-table-hosts) td:nth-child(6),
.peers-table:not(.mobile-details-peers-table).peers-table-hosts th:nth-child(7),
.peers-table:not(.mobile-details-peers-table).peers-table-hosts td:nth-child(7) {
width: 8rem;
}
.peers-table-hosts th:nth-child(8),
.peers-table-hosts td:nth-child(8),
.peers-table-hosts th:nth-child(9),
.peers-table-hosts td:nth-child(9) {
.peers-table:not(.mobile-details-peers-table):not(.peers-table-hosts) th:nth-child(7),
.peers-table:not(.mobile-details-peers-table):not(.peers-table-hosts) td:nth-child(7),
.peers-table:not(.mobile-details-peers-table):not(.peers-table-hosts) th:nth-child(8),
.peers-table:not(.mobile-details-peers-table):not(.peers-table-hosts) td:nth-child(8),
.peers-table:not(.mobile-details-peers-table).peers-table-hosts th:nth-child(8),
.peers-table:not(.mobile-details-peers-table).peers-table-hosts td:nth-child(8),
.peers-table:not(.mobile-details-peers-table).peers-table-hosts th:nth-child(9),
.peers-table:not(.mobile-details-peers-table).peers-table-hosts td:nth-child(9) {
width: 6%;
}
.peers-table-hosts th:nth-child(10),
.peers-table-hosts td:nth-child(10) {
.peers-table:not(.mobile-details-peers-table):not(.peers-table-hosts) th:nth-child(9),
.peers-table:not(.mobile-details-peers-table):not(.peers-table-hosts) td:nth-child(9),
.peers-table:not(.mobile-details-peers-table).peers-table-hosts th:nth-child(10),
.peers-table:not(.mobile-details-peers-table).peers-table-hosts td:nth-child(10) {
width: 5%;
}
.peers-table-hosts th:nth-child(11),
.peers-table-hosts td:nth-child(11) {
.peers-table:not(.mobile-details-peers-table):not(.peers-table-hosts) th:nth-child(10),
.peers-table:not(.mobile-details-peers-table):not(.peers-table-hosts) td:nth-child(10),
.peers-table:not(.mobile-details-peers-table).peers-table-hosts th:nth-child(11),
.peers-table:not(.mobile-details-peers-table).peers-table-hosts td:nth-child(11) {
width: 10%;
}
@@ -4718,7 +4744,7 @@ body,
white-space: nowrap;
}
/* Note: Mobile torrent details use a narrower fixed table so long reverse-DNS names cannot stretch the modal. */
/* Note: Mobile torrent details use a stable table so progress bars always render on a 0-100% track. */
.mobile-details-modal .modal-body {
overflow-x: hidden;
}
@@ -4728,36 +4754,54 @@ body,
}
.mobile-details-peers-table {
min-width: 720px;
margin-bottom: 0;
min-width: 780px;
}
.mobile-details-peers-table:not(.peers-table-hosts) th:nth-child(1),
.mobile-details-peers-table:not(.peers-table-hosts) td:nth-child(1),
.mobile-details-peers-table.peers-table-hosts th:nth-child(1),
.mobile-details-peers-table.peers-table-hosts td:nth-child(1) {
width: 5%;
}
.mobile-details-peers-table:not(.peers-table-hosts) th:nth-child(2),
.mobile-details-peers-table:not(.peers-table-hosts) td:nth-child(2),
.mobile-details-peers-table.peers-table-hosts th:nth-child(2),
.mobile-details-peers-table.peers-table-hosts td:nth-child(2) {
width: 14%;
}
.mobile-details-peers-table.peers-table-hosts th:nth-child(3),
.mobile-details-peers-table.peers-table-hosts td:nth-child(3),
.mobile-details-peers-table.peers-table-hosts th:nth-child(4),
.mobile-details-peers-table.peers-table-hosts td:nth-child(4) {
width: 15%;
}
.mobile-details-peers-table.peers-table-hosts th:nth-child(5),
.mobile-details-peers-table.peers-table-hosts td:nth-child(5) {
.mobile-details-peers-table.peers-table-hosts td:nth-child(3) {
width: 16%;
}
.mobile-details-peers-table.peers-table-hosts th:nth-child(6),
.mobile-details-peers-table.peers-table-hosts td:nth-child(6) {
width: 10%;
.mobile-details-peers-table:not(.peers-table-hosts) th:nth-child(3),
.mobile-details-peers-table:not(.peers-table-hosts) td:nth-child(3),
.mobile-details-peers-table.peers-table-hosts th:nth-child(4),
.mobile-details-peers-table.peers-table-hosts td:nth-child(4) {
width: 16%;
}
.mobile-details-peers-table:not(.peers-table-hosts) th:nth-child(4),
.mobile-details-peers-table:not(.peers-table-hosts) td:nth-child(4),
.mobile-details-peers-table.peers-table-hosts th:nth-child(5),
.mobile-details-peers-table.peers-table-hosts td:nth-child(5) {
width: 15%;
}
.mobile-details-peers-table:not(.peers-table-hosts) th:nth-child(5),
.mobile-details-peers-table:not(.peers-table-hosts) td:nth-child(5),
.mobile-details-peers-table.peers-table-hosts th:nth-child(6),
.mobile-details-peers-table.peers-table-hosts td:nth-child(6) {
width: 8rem;
}
.mobile-details-peers-table:not(.peers-table-hosts) th:nth-child(6),
.mobile-details-peers-table:not(.peers-table-hosts) td:nth-child(6),
.mobile-details-peers-table:not(.peers-table-hosts) th:nth-child(7),
.mobile-details-peers-table:not(.peers-table-hosts) td:nth-child(7),
.mobile-details-peers-table.peers-table-hosts th:nth-child(7),
.mobile-details-peers-table.peers-table-hosts td:nth-child(7),
.mobile-details-peers-table.peers-table-hosts th:nth-child(8),
@@ -4765,13 +4809,19 @@ body,
width: 7%;
}
.mobile-details-peers-table:not(.peers-table-hosts) th:nth-child(8),
.mobile-details-peers-table:not(.peers-table-hosts) td:nth-child(8),
.mobile-details-peers-table.peers-table-hosts th:nth-child(9),
.mobile-details-peers-table.peers-table-hosts td:nth-child(9),
.mobile-details-peers-table.peers-table-hosts th:nth-child(10),
.mobile-details-peers-table.peers-table-hosts td:nth-child(10) {
.mobile-details-peers-table.peers-table-hosts td:nth-child(9) {
width: 6%;
}
.mobile-details-peers-table:not(.peers-table-hosts) th:nth-child(9),
.mobile-details-peers-table:not(.peers-table-hosts) td:nth-child(9),
.mobile-details-peers-table.peers-table-hosts th:nth-child(10),
.mobile-details-peers-table.peers-table-hosts td:nth-child(10) {
width: 8%;
}
/* App modal widths stay consistent while Bootstrap still handles full-screen mobile breakpoints. */
.app-modal-dialog,
@@ -5237,7 +5287,6 @@ body,
overflow-wrap: anywhere;
}
.mobile-details-peers-table,
.mobile-details-files-table {
margin-bottom: 0;
}