diff --git a/pytorrent/static/app.js b/pytorrent/static/app.js index 9fb0b30..2ed443c 100644 --- a/pytorrent/static/app.js +++ b/pytorrent/static/app.js @@ -514,9 +514,9 @@ ['limitDown','limitUp'].forEach(id=>$(id)?.addEventListener('input',updateLimitSliders)); $('saveSpeedBtn')?.addEventListener('click',async()=>{const btn=$('saveSpeedBtn');buttonBusy(btn,true);setBusy(true);try{await post('/api/speed/limits',{down:Math.round(Number($('limitDown').value||0)*1024),up:Math.round(Number($('limitUp').value||0)*1024)});toast('Speed limits queued','success');bootstrap.Modal.getInstance($('speedModal'))?.hide();}catch(e){toast(e.message,'danger');}finally{buttonBusy(btn,false);setBusy(false);}}); $('speedModal')?.addEventListener('show.bs.modal',()=>{setLimitValue('limitDown',lastLimits.down?Math.round(lastLimits.down/1024):0);setLimitValue('limitUp',lastLimits.up?Math.round(lastLimits.up/1024):0);updateLimitSliders();}); async function refreshProfiles(){ $('profileList').innerHTML='Loading profiles...'; const j=await (await fetch('/api/profiles')).json(); const active=j.active?.id; profileCache=new Map((j.profiles||[]).map(p=>[String(p.id),p])); $('profileList').innerHTML=(j.profiles||[]).map(p=>`
${esc(p.name)} ${p.id===active?"active":''} ${p.is_remote?"remote":''}${esc(p.scgi_url)} · jobs ${esc(p.max_parallel_jobs||5)}${p.is_remote?' · remote CPU/RAM/IP':''}
`).join('')||'No profiles.'; } - function resetProfileForm(){ if($('profileId')) $('profileId').value=''; if($('profileName')) $('profileName').value=''; if($('profileUrl')) $('profileUrl').value=''; if($('profileTimeout')) $('profileTimeout').value='5'; if($('profileParallel')) $('profileParallel').value='5'; if($('profileRemote')) $('profileRemote').checked=false; if($('profileFormTitle')) $('profileFormTitle').textContent='Add one rTorrent'; if($('saveProfileBtn')) $('saveProfileBtn').innerHTML=' Add profile'; $('cancelProfileEditBtn')?.classList.add('d-none'); } - function editProfileForm(profile){ if(!profile) return; if($('profileId')) $('profileId').value=profile.id; if($('profileName')) $('profileName').value=profile.name||''; if($('profileUrl')) $('profileUrl').value=profile.scgi_url||''; if($('profileTimeout')) $('profileTimeout').value=profile.timeout_seconds||5; if($('profileParallel')) $('profileParallel').value=profile.max_parallel_jobs||5; if($('profileRemote')) $('profileRemote').checked=!!profile.is_remote; if($('profileFormTitle')) $('profileFormTitle').textContent='Edit rTorrent'; if($('saveProfileBtn')) $('saveProfileBtn').innerHTML=' Save profile'; $('cancelProfileEditBtn')?.classList.remove('d-none'); $('profileName')?.focus(); } - $('profileModal')?.addEventListener('show.bs.modal',refreshProfiles); $('profileList')?.addEventListener('click',async e=>{const btn=e.target.closest('[data-del-profile],[data-use-profile],[data-edit-profile]'); const del=btn?.dataset.delProfile,use=btn?.dataset.useProfile,edit=btn?.dataset.editProfile;if(edit){editProfileForm(profileCache.get(String(edit)));return;} if(del){setBusy(true);await fetch(`/api/profiles/${del}`,{method:'DELETE'});setBusy(false);refreshProfiles();location.reload();} if(use){setBusy(true);await post(`/api/profiles/${use}/activate`,{});setBusy(false);location.reload();}}); $('cancelProfileEditBtn')?.addEventListener('click',resetProfileForm); $('saveProfileBtn')?.addEventListener('click',async()=>{setBusy(true);const id=$('profileId')?.value;const payload={name:$('profileName').value,scgi_url:$('profileUrl').value,timeout_seconds:$('profileTimeout').value,max_parallel_jobs:$('profileParallel').value,is_remote:$('profileRemote')?.checked};const j=await post(id?`/api/profiles/${id}`:'/api/profiles',payload,id?'PUT':'POST').catch(e=>toast(e.message,'danger'));setBusy(false);if(j?.profile)location.reload();}); $('saveBulkProfilesBtn')?.addEventListener('click',async()=>{const lines=($('bulkProfiles').value||'').split(/\n+/).map(x=>x.trim()).filter(Boolean);setBusy(true);try{for(const line of lines){const [name,scgi_url]=line.split('|').map(x=>x.trim());if(name&&scgi_url)await post('/api/profiles',{name,scgi_url,timeout_seconds:$('profileTimeout').value,max_parallel_jobs:$('profileParallel').value,is_remote:$('profileRemote')?.checked});}location.reload();}catch(e){toast(e.message,'danger');}finally{setBusy(false);}}); $('profileSelect')?.addEventListener('change',async e=>{await post(`/api/profiles/${e.target.value}/activate`,{});const opt=e.target.selectedOptions?.[0];if($('activeProfileName') && opt) $('activeProfileName').textContent=opt.textContent || 'rTorrent';bootstrap.Modal.getInstance($('profilePickerModal'))?.hide();defaultDownloadPath=null;applyDefaultDownloadPath(true).catch(()=>{});socket.emit('select_profile',{profile_id:e.target.value});hasTorrentSnapshot=false;torrents.clear();selected.clear();scheduleRender(true);}); $('themeToggle')?.addEventListener('click',async()=>{const cur=document.documentElement.dataset.bsTheme==='dark'?'light':'dark';document.documentElement.dataset.bsTheme=cur;await post('/api/preferences',{theme:cur}).catch(()=>{});}); $('mobileToggle')?.addEventListener('click',()=>{document.body.classList.toggle('mobile-mode-manual');syncMobileMode();}); window.addEventListener('resize',()=>syncMobileMode(),{passive:true}); syncMobileMode(); + function resetProfileForm(){ if($('profileId')) $('profileId').value=''; if($('profileName')) $('profileName').value=''; if($('profileUrl')) $('profileUrl').value=''; if($('profileTimeout')) $('profileTimeout').value='5'; if($('profileParallel')) $('profileParallel').value='5'; if($('profileRemote')) $('profileRemote').checked=false; if($('profileFormTitle')) $('profileFormTitle').textContent='Add rTorrent profile'; if($('saveProfileBtn')) $('saveProfileBtn').innerHTML=' Add profile'; $('cancelProfileEditBtn')?.classList.add('d-none'); } + function editProfileForm(profile){ if(!profile) return; if($('profileId')) $('profileId').value=profile.id; if($('profileName')) $('profileName').value=profile.name||''; if($('profileUrl')) $('profileUrl').value=profile.scgi_url||''; if($('profileTimeout')) $('profileTimeout').value=profile.timeout_seconds||5; if($('profileParallel')) $('profileParallel').value=profile.max_parallel_jobs||5; if($('profileRemote')) $('profileRemote').checked=!!profile.is_remote; if($('profileFormTitle')) $('profileFormTitle').textContent='Edit rTorrent profile'; if($('saveProfileBtn')) $('saveProfileBtn').innerHTML=' Save profile'; $('cancelProfileEditBtn')?.classList.remove('d-none'); $('profileName')?.focus(); } + $('profileModal')?.addEventListener('show.bs.modal',refreshProfiles); $('profileList')?.addEventListener('click',async e=>{const btn=e.target.closest('[data-del-profile],[data-use-profile],[data-edit-profile]'); const del=btn?.dataset.delProfile,use=btn?.dataset.useProfile,edit=btn?.dataset.editProfile;if(edit){editProfileForm(profileCache.get(String(edit)));return;} if(del){setBusy(true);await fetch(`/api/profiles/${del}`,{method:'DELETE'});setBusy(false);refreshProfiles();location.reload();} if(use){setBusy(true);await post(`/api/profiles/${use}/activate`,{});setBusy(false);location.reload();}}); $('cancelProfileEditBtn')?.addEventListener('click',resetProfileForm); $('saveProfileBtn')?.addEventListener('click',async()=>{setBusy(true);const id=$('profileId')?.value;const payload={name:$('profileName').value,scgi_url:$('profileUrl').value,timeout_seconds:$('profileTimeout').value,max_parallel_jobs:$('profileParallel').value,is_remote:$('profileRemote')?.checked};const j=await post(id?`/api/profiles/${id}`:'/api/profiles',payload,id?'PUT':'POST').catch(e=>toast(e.message,'danger'));setBusy(false);if(j?.profile)location.reload();}); $('profileSelect')?.addEventListener('change',async e=>{await post(`/api/profiles/${e.target.value}/activate`,{});const opt=e.target.selectedOptions?.[0];if($('activeProfileName') && opt) $('activeProfileName').textContent=opt.textContent || 'rTorrent';bootstrap.Modal.getInstance($('profilePickerModal'))?.hide();defaultDownloadPath=null;applyDefaultDownloadPath(true).catch(()=>{});socket.emit('select_profile',{profile_id:e.target.value});hasTorrentSnapshot=false;torrents.clear();selected.clear();scheduleRender(true);}); $('themeToggle')?.addEventListener('click',async()=>{const cur=document.documentElement.dataset.bsTheme==='dark'?'light':'dark';document.documentElement.dataset.bsTheme=cur;await post('/api/preferences',{theme:cur}).catch(()=>{});}); $('mobileToggle')?.addEventListener('click',()=>{document.body.classList.toggle('mobile-mode-manual');syncMobileMode();}); window.addEventListener('resize',()=>syncMobileMode(),{passive:true}); syncMobileMode(); function drawTraffic(down,up){ traffic.push({down:Number(down||0),up:Number(up||0)}); if(traffic.length>60)traffic.shift(); const c=$('trafficChart'); if(!c)return; const ctx=c.getContext('2d'),w=c.width,h=c.height; ctx.clearRect(0,0,w,h); const max=Math.max(1,...traffic.map(p=>Math.max(p.down,p.up))); ctx.beginPath(); traffic.forEach((p,i)=>{const x=i*(w/59),y=h-(p.down/max)*h; i?ctx.lineTo(x,y):ctx.moveTo(x,y);}); ctx.strokeStyle='#38bdf8'; ctx.stroke(); ctx.beginPath(); traffic.forEach((p,i)=>{const x=i*(w/59),y=h-(p.up/max)*h; i?ctx.lineTo(x,y):ctx.moveTo(x,y);}); ctx.strokeStyle='#f59e0b'; ctx.stroke(); } function drawSystemUsage(cpu,ram){ const c=$('systemChart'); if(!c) return; diff --git a/pytorrent/static/styles.css b/pytorrent/static/styles.css index f6233b8..e18dfd7 100644 --- a/pytorrent/static/styles.css +++ b/pytorrent/static/styles.css @@ -213,11 +213,60 @@ body { .ctx-menu button:hover { background: var(--bs-secondary-bg); } .ctx-menu .danger { color: var(--bs-danger); } .ctx-menu hr { margin: .25rem 0; border-color: var(--bs-border-color); } -.profile-row { display: grid; grid-template-columns: 1fr auto; gap: .25rem .5rem; align-items: center; padding: .45rem; border: 1px solid var(--bs-border-color); border-radius: .6rem; margin-bottom: .45rem; background: rgba(var(--bs-secondary-bg-rgb), .58); } -.profile-row span { grid-column: 1 / 2; color: var(--bs-secondary-color); overflow-wrap: anywhere; } -.profile-form-actions { display: inline-flex; gap: .35rem; flex-wrap: wrap; } -.profile-actions { display: inline-flex; gap: .35rem; } -.profile-row.active { border-color: var(--bs-primary); background: var(--bs-primary-bg-subtle); } +.profile-row { + display: grid; + grid-template-columns: minmax(0, 1fr) auto; + gap: .25rem .5rem; + align-items: center; + margin-bottom: .45rem; + padding: .45rem; + border: 1px solid var(--bs-border-color); + border-radius: .6rem; + background: rgba(var(--bs-secondary-bg-rgb), .58); +} +.profile-row.active { + border-color: var(--bs-primary); + background: var(--bs-primary-bg-subtle); +} +.profile-row span { + grid-column: 1 / 2; + color: var(--bs-secondary-color); + overflow-wrap: anywhere; +} +.profile-actions, +.profile-form-actions { + display: inline-flex; + gap: .35rem; + flex-wrap: wrap; +} +.profile-form-grid { + display: grid; + grid-template-columns: minmax(150px, 1.1fr) minmax(260px, 2.1fr) minmax(90px, .55fr) minmax(120px, .75fr) minmax(145px, auto) auto; + gap: .65rem; + align-items: start; +} +.profile-form-field { + display: grid; + gap: .25rem; + min-width: 0; +} +.profile-form-field > span:first-child { + color: var(--bs-secondary-color); + font-size: .72rem; + font-weight: 700; + line-height: 1.1; + text-transform: uppercase; +} +.profile-form-field small { + color: var(--bs-secondary-color); + line-height: 1.25; +} +.profile-check-field .form-check { + min-height: 31px; + display: flex; + align-items: center; + gap: .45rem; +} .flag-icon { border-radius: 2px; box-shadow: 0 0 0 1px rgba(255,255,255,.12); } .flag-code { color: var(--bs-secondary-color); margin-left: .25rem; } .peer-actions { display: flex; align-items: center; gap: .25rem; flex-wrap: nowrap; } @@ -349,11 +398,17 @@ body.mobile-mode .torrent-table { display: none; } body.mobile-mode #mobileList{min-height:0;height:100%;overflow:auto;display:block!important} body.mobile-mode .mobile-card{display:block}.mobile-card .mobile-actions button{min-width:34px} #toolSmart .form-label{font-size:.75rem;color:var(--bs-secondary-color);margin-bottom:.2rem} -.profile-form-grid{display:grid;grid-template-columns:1.1fr 2.1fr .55fr .75fr auto auto;gap:.5rem;align-items:center} #toolSmart .btn{padding:.25rem .55rem;border-radius:.5rem;white-space:nowrap} #toolSmart .row .d-flex{align-items:end;justify-content:flex-start} #trafficHistoryChart{width:100%;height:420px;border:1px solid var(--bs-border-color);border-radius:.75rem;background:var(--bs-body-bg)} -@media (max-width: 992px){.profile-form-grid{grid-template-columns:1fr}.profile-form-grid .btn{width:100%}} +@media (max-width: 992px) { + .profile-form-grid { + grid-template-columns: 1fr; + } + .profile-form-grid .btn { + width: 100%; + } +} /* Requested fixes: stable charts, Smart Queue exceptions, label actions, mobile readability */ .history-grid{display:grid;grid-template-columns:1fr;gap:1rem} diff --git a/pytorrent/templates/index.html b/pytorrent/templates/index.html index 743fd44..bcfc074 100644 --- a/pytorrent/templates/index.html +++ b/pytorrent/templates/index.html @@ -145,7 +145,7 @@ - +