Files
pyTorrent/pytorrent/templates/index.html
Mateusz Gruszczyński 9caa155324 post-check
2026-05-24 11:04:42 +02:00

375 lines
88 KiB
HTML

<!doctype html>
<html lang="en" data-bs-theme="{{ prefs.theme if prefs else 'dark' }}" data-app-font="{{ prefs.font_family if prefs and prefs.font_family else 'default' }}" style="--ui-scale: {{ ((prefs.interface_scale if prefs and prefs.interface_scale else 100) / 100) }};">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>pyTorrent</title>
<link rel="icon" href="{{ static_url('favicon.svg') }}" type="image/svg+xml">
<link rel="shortcut icon" href="{{ static_url('favicon.svg') }}" type="image/svg+xml">
<link id="bootstrapBaseStylesheet" href="{{ bootstrap_theme_url('default') }}" rel="stylesheet">
<link id="bootstrapThemeStylesheet" href="{{ bootstrap_theme_url(prefs.bootstrap_theme if prefs else 'default') }}" rel="stylesheet">
<link href="{{ frontend_asset_url('fontawesome_css') }}" rel="stylesheet">
<link href="{{ frontend_asset_url('font_css') }}" rel="stylesheet">
<link href="{{ frontend_asset_url('flag_icons_css') }}" rel="stylesheet">
<link href="{{ static_url('styles.css') }}" rel="stylesheet">
</head>
<body>
<div id="initialLoader" class="initial-loader" role="status" aria-live="polite">
<div class="initial-loader-card">
<div class="initial-loader-brand"><i class="fa-solid fa-robot"></i> pyTorrent</div>
<div class="initial-loader-spinner"><span class="spinner-border" aria-hidden="true"></span></div>
<div id="initialLoaderTitle" class="initial-loader-title">Loading torrents...</div>
<div id="initialLoaderText" class="initial-loader-text">Connecting to rTorrent and preparing data.</div>
</div>
</div>
<div id="globalLoader" class="global-loader d-none"><span class="spinner-border spinner-border-sm"></span><span>Working...</span></div>
<div class="app-shell">
<header class="topbar border-bottom">
<div class="toolbar-left">
<div class="brand"><i class="fa-solid fa-robot"></i> pyTorrent</div>
<button id="profilePickerBtn" class="btn btn-xs btn-outline-primary nav-btn profile-picker-btn" data-bs-toggle="modal" data-bs-target="#profilePickerModal" title="Change rTorrent">
<i class="fa-solid fa-server"></i><span id="activeProfileName">{{ active_profile.name if active_profile else 'Add rTorrent' }}</span>
</button>
<button class="btn btn-xs btn-outline-primary nav-btn" data-bs-toggle="modal" data-bs-target="#trafficModal"><i class="fa-solid fa-chart-column"></i><span> History</span></button>
<button class="btn btn-xs btn-success nav-btn" data-bs-toggle="modal" data-bs-target="#addModal"><i class="fa-solid fa-plus"></i><span> Add</span></button>
<button class="btn btn-xs btn-outline-secondary nav-btn" data-bs-toggle="modal" data-bs-target="#jobsModal"><i class="fa-solid fa-list-check"></i><span> Jobs</span></button>
<button class="btn btn-xs btn-outline-secondary nav-btn" data-bs-toggle="modal" data-bs-target="#logsModal"><i class="fa-solid fa-book"></i><span> Logs</span></button>
<button class="btn btn-xs btn-outline-secondary nav-btn" data-bs-toggle="modal" data-bs-target="#toolsModal"><i class="fa-solid fa-sliders"></i><span> Tools</span></button>
<button class="btn btn-xs btn-outline-success torrent-action nav-btn" data-action="start" title="Start"><i class="fa-solid fa-play"></i></button>
<button class="btn btn-xs btn-outline-warning torrent-action nav-btn" data-action="pause" title="Pause"><i class="fa-solid fa-pause"></i></button>
<button class="btn btn-xs btn-outline-secondary torrent-action nav-btn" data-action="stop" title="Stop"><i class="fa-solid fa-stop"></i></button>
<button class="btn btn-xs btn-outline-info torrent-action nav-btn" data-action="recheck" title="Force recheck"><i class="fa-solid fa-rotate"></i></button><button class="btn btn-xs btn-outline-primary torrent-action nav-btn" data-action="reannounce" title="Reannounce"><i class="fa-solid fa-bullhorn"></i></button>
<button class="btn btn-xs btn-outline-danger nav-btn" data-bs-toggle="modal" data-bs-target="#removeModal" title="Remove"><i class="fa-solid fa-trash"></i></button>
</div>
<div class="toolbar-right">
<input id="searchBox" class="form-control form-control-sm search" placeholder="Search torrents..."><span id="mobileSpeedStats" class="mobile-speed-stats" aria-label="Current transfer speed"><span><i class="fa-solid fa-down-long"></i> <b id="mobileSpeedDl">0 B/s</b></span><span><i class="fa-solid fa-up-long"></i> <b id="mobileSpeedUl">0 B/s</b></span></span>
<span id="busyBadge" class="badge text-bg-dark d-none"><span class="spinner-border spinner-border-xs"></span> busy</span>
<span id="connBadge" class="badge text-bg-secondary">offline</span>
<button class="btn btn-xs btn-outline-info nav-btn" id="mobileToggle" title="Mobile/simple mode"><i class="fa-solid fa-mobile-screen"></i></button>
<button id="themeToggle" class="btn btn-xs btn-outline-secondary nav-btn" title="Change theme"><i class="fa-solid fa-moon"></i></button>
<button class="btn btn-xs btn-outline-secondary nav-btn about-nav-btn" data-bs-toggle="modal" data-bs-target="#aboutModal" title="About pyTorrent"><i class="fa-solid fa-circle-info"></i></button>
{% if auth_enabled %}<a class="btn btn-xs btn-outline-danger nav-btn" href="/logout" title="Log out"><i class="fa-solid fa-right-from-bracket"></i><span> {{ current_user.username if current_user else 'logout' }}</span></a>{% endif %}
</div>
</header>
<main class="main-grid">
<aside class="sidebar border-end">
<button class="filter active" data-filter="all"><span><i class="fa-solid fa-list me-1"></i>All</span> <span id="countAll">0</span></button>
<button class="filter" data-filter="downloading"><span><i class="fa-solid fa-download me-1"></i>Downloading</span> <span id="countDownloading">0</span></button>
<button class="filter" data-filter="seeding"><span><i class="fa-solid fa-seedling me-1"></i>Seeding</span> <span id="countSeeding">0</span></button>
<button class="filter" data-filter="paused"><span><i class="fa-solid fa-pause me-1"></i>Paused</span> <span id="countPaused">0</span></button>
<button class="filter" data-filter="checking"><span><i class="fa-solid fa-rotate me-1"></i>Checking</span> <span id="countChecking">0</span></button>
<button class="filter" data-filter="error"><span><i class="fa-solid fa-triangle-exclamation me-1"></i>With error</span> <span id="countError">0</span></button>
<button class="filter" data-filter="post_check"><span><i class="fa-solid fa-clipboard-check me-1"></i>Post-check</span> <span id="countPostCheck">0</span></button>
<button class="filter" data-filter="stopped"><span><i class="fa-solid fa-stop me-1"></i>Stopped</span> <span id="countStopped">0</span></button>
<button class="filter d-none" data-filter="moving"><span><i class="fa-solid fa-folder-open me-1"></i>Moving</span> <span id="countMoving">0</span></button>
<div id="labelFilters" class="label-filters mt-2"></div>
<div id="trackerFilters" class="tracker-filters mt-2"></div>
<hr>
<div class="small text-muted px-2">Shortcuts</div>
<div class="shortcut">Ctrl+A — select visible</div>
<div class="shortcut">Ctrl+I — invert visible</div>
<div class="shortcut">Space — start</div>
<div class="shortcut">P — pause</div>
<div class="shortcut">S — stop</div>
<div class="shortcut">R — resume</div>
<div class="shortcut">M — move</div>
<div class="shortcut">Esc — clear selection</div>
<div class="shortcut">Delete — remove</div>
<div class="shortcut">Ctrl+O — add</div><div class="shortcut">Ctrl+S — download .torrent</div>
</aside>
<section class="content">
<div id="bulkBar" class="bulk-bar d-none"><span><b id="bulkSelectedCount">0</b> selected</span><button class="btn btn-xs btn-outline-primary" data-bs-toggle="modal" data-bs-target="#labelModal"><i class="fa-solid fa-tag"></i> Label</button><button class="btn btn-xs btn-outline-primary" data-bs-toggle="modal" data-bs-target="#ratioAssignModal"><i class="fa-solid fa-scale-balanced"></i> Ratio</button><button class="btn btn-xs btn-outline-primary" data-action="move"><i class="fa-solid fa-folder-open"></i> Move</button><button class="btn btn-xs btn-outline-secondary" data-download-torrent="1"><i class="fa-solid fa-file-arrow-down"></i> Download .torrent</button><button id="bulkClearBtn" class="btn btn-xs btn-outline-secondary ms-auto"><i class="fa-solid fa-xmark"></i> Clear</button></div><div id="tableWrap" class="table-wrap">
<table class="table table-hover table-sm align-middle torrent-table">
<thead><tr>
<th class="sel" data-col="select"><input id="selectAll" type="checkbox"></th>
<th data-sort="name" data-col="name">Name</th><th data-sort="status" data-col="status">Status</th><th data-sort="size" data-col="size">Size</th><th data-sort="progress" data-col="progress">Progress</th>
<th data-sort="down_rate" data-col="down_rate">DL</th><th data-sort="up_rate" data-col="up_rate">UL</th><th data-sort="eta" data-col="eta">ETA</th><th data-sort="seeds" data-col="seeds">Seeds</th><th data-sort="peers" data-col="peers">Peers</th>
<th data-sort="ratio" data-col="ratio">Ratio</th><th data-sort="path" data-col="path">Path</th><th data-sort="label" data-col="label">Label</th><th data-sort="ratio_group" data-col="ratio_group">Ratio group</th><th data-sort="down_total" data-col="down_total">Downloaded</th><th data-sort="to_download" data-col="to_download">To download</th><th data-sort="up_total" data-col="up_total">Uploaded</th><th data-sort="created" data-col="created">Added</th><th data-sort="priority" data-col="priority">Priority</th><th data-sort="state" data-col="state">State</th><th data-sort="active" data-col="active">Active</th><th data-sort="complete" data-col="complete">Complete</th><th data-sort="hashing" data-col="hashing">Hashing</th><th data-sort="message" data-col="message">Message</th><th data-sort="hash" data-col="hash">Hash</th>
</tr></thead>
<tbody id="torrentBody"><tr><td colspan="25" class="empty"><span class="spinner-border spinner-border-sm me-2"></span>Waiting for data.</td></tr></tbody>
</table>
</div>
<div id="mobileFilterBar" class="mobile-filter-bar d-none" aria-label="Torrent filters"></div>
<div id="mobileList" class="mobile-list d-none"></div>
<div id="detailResizeHandle" class="detail-resize-handle" role="separator" aria-orientation="horizontal" aria-label="Resize torrent details panel" title="Drag to resize details panel"></div>
<div class="details border-top">
<ul class="nav nav-tabs" id="detailTabs">
<li class="nav-item"><button class="nav-link active" data-tab="general"><i class="fa-solid fa-circle-info"></i> General</button></li>
<li class="nav-item"><button class="nav-link" data-tab="files"><i class="fa-solid fa-file-lines"></i> Files</button></li>
<li class="nav-item"><button class="nav-link" data-tab="chunks"><i class="fa-solid fa-grip"></i> Chunks</button></li>
<li class="nav-item"><button class="nav-link" data-tab="peers"><i class="fa-solid fa-users"></i> Peers</button></li>
<li class="nav-item"><button class="nav-link" data-tab="trackers"><i class="fa-solid fa-bullseye"></i> Trackers</button></li>
<li class="nav-item"><button class="nav-link" data-tab="log"><i class="fa-solid fa-terminal"></i> Log</button></li>
</ul>
<div id="peersRefreshBox" class="peers-refresh d-none"><label class="form-label mb-0 small">Peers auto refresh</label><select id="peersRefreshSelect" class="form-select form-select-sm"><option value="0">Off</option><option value="10">10s</option><option value="15">15s</option><option value="30">30s</option><option value="60">60s</option></select></div>
<div id="detailPane" class="detail-pane muted-pane">Select a torrent.</div>
</div>
</section>
</main>
<footer class="statusbar border-top">
<span id="statCpuBox" data-footer-item="cpu">CPU <b id="statCpu">-</b>%</span><span id="statRamBox" data-footer-item="ram">RAM <b id="statRam">-</b>%</span><canvas id="systemChart" class="system-chart" data-footer-item="usage_chart" width="96" height="24" title="CPU / RAM usage"></canvas>
<span id="diskStatus" class="disk-status" data-footer-item="disk" title="Disk usage unavailable"><i class="fa-solid fa-hard-drive"></i><canvas id="diskChart" width="90" height="24"></canvas><b id="statDisk">-</b></span>
<span data-footer-item="version">rTorrent <b id="statVersion">-</b></span><span data-footer-item="speed_down">DL <b id="statDl">0 B/s</b></span><span data-footer-item="speed_up">UL <b id="statUl">0 B/s</b></span><span id="statusSpeedPeaks" class="speed-peaks" data-footer-item="speed_peaks" title="Peak speed unavailable">Peak S <b id="statPeakSession">0B/s / 0B/s</b> · All <b id="statPeakAllTime">0B/s / 0B/s</b></span>
<button class="status-limit" data-footer-item="limits" data-bs-toggle="modal" data-bs-target="#speedModal">Limit DL <b id="statDlLimit"></b> / UL <b id="statUlLimit"></b> <i class="fa-solid fa-pen-to-square"></i></button>
<span data-footer-item="totals">Total DL/UP <b id="statTotalDl">0B</b>/<b id="statTotalUl">0B</b></span><span id="statusPortCheck" class="d-none" data-footer-item="port_check" title="Port check disabled"><span id="statusPortCheckBadge" class="port-status port-secondary"><i class="fa-solid fa-circle-question"></i> Port - unknown</span></span><span id="statusClock" data-footer-item="clock" title="Local browser time"><i class="fa-solid fa-clock"></i> <b id="statClock">--:--:--</b></span><span id="statusSockets" data-footer-item="sockets" title="Open rTorrent sockets"><i class="fa-solid fa-network-wired"></i> Sockets <b id="statSockets">-</b></span><span id="statusRtDownloads" data-footer-item="rt_downloads" title="Active rTorrent downloads / max global downloads"><i class="fa-solid fa-download"></i> Downloads <b id="statRtDownloads">-</b></span><span id="statusRtUploads" data-footer-item="rt_uploads" title="Active rTorrent uploads / max global uploads"><i class="fa-solid fa-upload"></i> Uploads <b id="statRtUploads">-</b></span><span id="statusRtHttp" data-footer-item="rt_http" title="Open rTorrent HTTP connections / max HTTP connections"><i class="fa-solid fa-globe"></i> HTTP <b id="statRtHttp">-</b></span><span id="statusRtFiles" data-footer-item="rt_files" title="Open rTorrent files / max open files"><i class="fa-solid fa-file"></i> Files <b id="statRtFiles">-</b></span><span id="statusRtPort" data-footer-item="rt_port" title="rTorrent incoming port"><i class="fa-solid fa-plug"></i> Port <b id="statRtPort">-</b></span><span data-footer-item="shown">Shown <b id="statShown">0</b></span><span data-footer-item="selected">Selected <b id="statSelected">0</b></span><button id="statusPlannerOpen" class="status-planner d-none" data-footer-item="planner" type="button" title="Open Download planner"><i class="fa-solid fa-calendar-days"></i><span>Planner</span></button><a class="status-docs" data-footer-item="docs" href="/docs" target="_blank" rel="noopener"><i class="fa-solid fa-book"></i> Docs API</a>
</footer>
</div>
<div id="ctxMenu" class="ctx-menu shadow">
<button data-action="start"><i class="fa-solid fa-play"></i> Start</button><button data-action="pause"><i class="fa-solid fa-pause"></i> Pause</button><button data-action="stop"><i class="fa-solid fa-stop"></i> Stop</button><button data-action="recheck"><i class="fa-solid fa-rotate"></i> Force recheck</button><button data-action="move"><i class="fa-solid fa-folder-open"></i> Move...</button><hr>
<button data-bs-toggle="modal" data-bs-target="#labelModal"><i class="fa-solid fa-tag"></i> Set label...</button><button data-bs-toggle="modal" data-bs-target="#ratioAssignModal"><i class="fa-solid fa-scale-balanced"></i> Set ratio group...</button><button id="smartExcludeCtx"><i class="fa-solid fa-ban"></i> Exclude from Smart Queue</button><button data-download-torrent="1"><i class="fa-solid fa-file-arrow-down"></i> Download .torrent</button><hr>
<button data-copy="hash"><i class="fa-solid fa-hashtag"></i> Copy hash</button><button data-copy="name"><i class="fa-solid fa-font"></i> Copy name</button><button data-copy="path"><i class="fa-solid fa-folder-tree"></i> Copy path</button><hr><button data-bs-toggle="modal" data-bs-target="#removeModal" class="danger"><i class="fa-solid fa-trash"></i> Remove...</button>
</div>
<div class="modal fade" id="profilePickerModal" tabindex="-1">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fa-solid fa-server"></i> Choose rTorrent</h5>
<button class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<label class="form-label" for="profileSelect">Configured rTorrents</label>
<select id="profileSelect" class="form-select profile-select">
{% for p in profiles %}<option value="{{ p.id }}" {% if active_profile and active_profile.id == p.id %}selected{% endif %}>{{ p.name }}</option>{% endfor %}
</select>
<div class="form-text">Changing rTorrent reloads the live torrent snapshot.</div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="removeModal" tabindex="-1"><div class="modal-dialog"><div class="modal-content"><div class="modal-header"><h5 class="modal-title">Remove selected torrents</h5><button class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><p><b id="removeCount">0</b> selected torrents will be removed.</p><div class="form-check form-switch"><input id="removeData" class="form-check-input" type="checkbox" checked><label class="form-check-label" for="removeData">Remove with data</label></div></div><div class="modal-footer"><button class="btn btn-secondary" data-bs-dismiss="modal"><i class="fa-solid fa-xmark"></i> Cancel</button><button id="confirmRemoveBtn" class="btn btn-danger"><i class="fa-solid fa-trash"></i> Remove</button></div></div></div></div>
<!-- Note: Restores the standalone Jobs modal used by the topbar Jobs button; existing job APIs and JS handlers stay unchanged. -->
<div class="modal fade" id="jobsModal" tabindex="-1" aria-labelledby="jobsModalLabel" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 id="jobsModalLabel" class="modal-title"><i class="fa-solid fa-list-check"></i> Job queue</h5>
<button class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="jobs-toolbar d-flex gap-2 mb-2 flex-wrap align-items-center">
<button id="refreshJobsBtn" class="btn btn-sm btn-outline-primary" type="button"><i class="fa-solid fa-rotate"></i> Refresh</button>
<button id="clearJobsBtn" class="btn btn-sm btn-outline-danger" type="button"><i class="fa-solid fa-trash"></i> Clear finished</button>
<button id="emergencyClearJobsBtn" class="btn btn-sm btn-danger" type="button"><i class="fa-solid fa-triangle-exclamation"></i> Emergency clean all</button>
<span class="text-muted small">Pending, running, done, failed, retry and cancel history.</span>
</div>
<div id="jobsTable" class="table-responsive"><span class="spinner-border spinner-border-sm"></span> Loading jobs...</div>
<div id="jobsPager" class="pager-row mt-2"></div>
</div>
</div>
</div>
</div>
<!-- Note: Shared path picker is required by Move and by every browse-path button; styles are reused unchanged. -->
<div class="modal fade" id="pathModal" tabindex="-1" aria-labelledby="pathModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 id="pathModalLabel" class="modal-title"><i class="fa-solid fa-folder-open"></i> Select path</h5>
<button class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="input-group input-group-sm mb-3">
<button id="pathUpBtn" class="btn btn-outline-secondary" type="button" title="Parent directory"><i class="fa-solid fa-arrow-up"></i></button>
<input id="pathCurrent" class="form-control" autocomplete="off" placeholder="/path/to/downloads">
<button id="pathGoBtn" class="btn btn-outline-primary" type="button"><i class="fa-solid fa-arrow-right"></i> Go</button>
<button id="pathReloadBtn" class="btn btn-outline-secondary" type="button" title="Reload"><i class="fa-solid fa-rotate"></i></button>
</div>
<div id="moveOptions" class="move-options mb-3 d-none">
<div class="form-check form-switch">
<input id="moveDataPhysical" class="form-check-input" type="checkbox" checked>
<label class="form-check-label" for="moveDataPhysical">Move data files</label>
</div>
<div class="form-check form-switch">
<input id="moveRecheck" class="form-check-input" type="checkbox" checked>
<label class="form-check-label" for="moveRecheck">Recheck after move</label>
</div>
</div>
<div id="pathList" class="path-list"><div class="p-3 text-muted">No path loaded.</div></div>
</div>
<div class="modal-footer">
<button class="btn btn-secondary" data-bs-dismiss="modal"><i class="fa-solid fa-xmark"></i> Cancel</button>
<button id="pathSelectBtn" class="btn btn-primary"><i class="fa-solid fa-check"></i> Select</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="addModal" tabindex="-1">
<div class="modal-dialog modal-xl modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header"><h5 class="modal-title">Add / create torrent</h5><button class="btn-close" data-bs-dismiss="modal"></button></div>
<div class="modal-body add-create-modal-body">
<div class="column-manager-tabs mb-3"><ul class="nav nav-pills" role="tablist"><li class="nav-item" role="presentation"><button class="nav-link active" id="addTorrentTab" data-bs-toggle="pill" data-bs-target="#addTorrentPane" type="button" role="tab"><i class="fa-solid fa-plus"></i> Add</button></li><li class="nav-item" role="presentation"><button class="nav-link" id="createTorrentTab" data-bs-toggle="pill" data-bs-target="#createTorrentPane" type="button" role="tab"><i class="fa-solid fa-file-circle-plus"></i> Create torrent</button></li></ul></div>
<div class="tab-content preference-tab-content add-create-tab-content">
<div class="tab-pane fade show active" id="addTorrentPane" role="tabpanel" aria-labelledby="addTorrentTab">
<div class="add-torrent-layout">
<section class="add-torrent-panel"><div class="add-torrent-panel-heading"><div><div class="section-title mb-1"><i class="fa-solid fa-magnet"></i> Magnet links</div><div class="small text-muted">Paste one magnet URI per line.</div></div></div><textarea id="magnetInput" class="form-control magnet-box add-magnet-input" rows="5" placeholder="magnet:?xt=urn:btih:..."></textarea></section>
<section class="add-torrent-panel"><div class="add-torrent-panel-heading"><div><div class="section-title mb-1"><i class="fa-solid fa-file-arrow-up"></i> Torrent files</div><div class="small text-muted">Select one or more .torrent files.</div></div><label class="btn btn-sm btn-outline-primary add-file-picker"><i class="fa-solid fa-folder-open"></i> Choose files<input id="torrentFiles" type="file" accept=".torrent,application/x-bittorrent" multiple></label></div><div id="torrentFilesInfo" class="add-file-summary">No files selected.</div><div id="torrentPreview" class="torrent-preview add-file-preview"></div></section>
<section class="add-torrent-panel"><div class="add-target-grid"><label class="form-field add-path-field"><span>Save location</span><div class="input-group"><input id="addPath" class="form-control" placeholder="/data/torrents"><button class="btn btn-outline-secondary browse-path" data-target="addPath" type="button"><i class="fa-solid fa-folder-open"></i></button></div></label><label class="form-field add-label-field"><span>Label</span><input id="addLabel" class="form-control" placeholder="movies"></label><label class="form-check form-switch add-start-card"><input id="addStart" class="form-check-input" type="checkbox" checked><span class="form-check-label">Start after add</span></label></div></section>
</div>
</div>
<div class="tab-pane fade" id="createTorrentPane" role="tabpanel" aria-labelledby="createTorrentTab">
<div class="create-torrent-form add-torrent-layout">
<section class="add-torrent-panel"><div class="section-title mb-2"><i class="fa-solid fa-folder-tree"></i> Source</div><label class="form-field"><span>File or directory path</span><div class="input-group"><input id="createSourcePath" class="form-control" placeholder="/data/folder-or-file"><button class="btn btn-outline-secondary browse-path" data-target="createSourcePath" type="button"><i class="fa-solid fa-folder-open"></i></button></div></label></section>
<section class="add-torrent-panel"><div class="section-title mb-2"><i class="fa-solid fa-sliders"></i> Torrent properties</div><div class="create-properties-grid"><label class="form-field create-trackers-field"><span>Trackers</span><textarea id="createTrackers" class="form-control" rows="5" placeholder="One tracker URL per line"></textarea></label><div class="create-side-fields"><label class="form-field"><span>Piece size</span><select id="createPieceSize" class="form-select"><option value="64">64 KiB</option><option value="128">128 KiB</option><option value="256" selected>256 KiB</option><option value="512">512 KiB</option><option value="1024">1 MiB</option><option value="2048">2 MiB</option><option value="4096">4 MiB</option><option value="8192">8 MiB</option><option value="16384">16 MiB</option></select></label><label class="form-field"><span>Label after share</span><input id="createLabel" class="form-control" placeholder="optional"></label></div></div><div class="create-meta-grid mt-2"><label class="form-field"><span>Comment</span><input id="createComment" class="form-control"></label><label class="form-field"><span>Source</span><input id="createSourceName" class="form-control" placeholder="optional private source tag"></label></div></section>
<section class="add-torrent-panel create-options-panel"><label class="form-check form-switch add-start-card mb-0"><input id="createShare" class="form-check-input" type="checkbox"><span class="form-check-label">Share after creating</span></label><label class="form-check form-switch add-start-card mb-0"><input id="createPrivate" class="form-check-input" type="checkbox"><span class="form-check-label">Private torrent</span></label></section><div id="createTorrentInfo" class="small text-muted"></div>
</div>
</div>
</div>
</div>
<div class="modal-footer"><button class="btn btn-secondary" data-bs-dismiss="modal"><i class="fa-solid fa-xmark"></i> Close</button><button id="addBtn" class="btn btn-success"><span class="btn-label"><i class="fa-solid fa-plus"></i> Add</span></button><button id="createTorrentBtn" class="btn btn-primary d-none"><span class="btn-label"><i class="fa-solid fa-file-circle-plus"></i> Create</span></button></div>
</div>
</div>
</div>
<!-- Note: Restores the speed limit modal targeted by the footer limit button; existing speed limit API and slider handlers stay unchanged. -->
<div class="modal fade" id="speedModal" tabindex="-1"><div class="modal-dialog"><div class="modal-content"><div class="modal-header"><h5 class="modal-title">Speed limits</h5><button class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><div class="row g-2"><div class="col"><label class="form-label">Download KiB/s</label><input id="limitDown" class="form-control" type="number" min="0"></div><div class="col"><label class="form-label">Upload KiB/s</label><input id="limitUp" class="form-control" type="number" min="0"></div></div><div class="limit-slider-panel mt-3"><div class="limit-slider-row"><label for="limitDownSlider" class="form-label">Custom download <b id="limitDownMbps">0 Mbit/s</b></label><input id="limitDownSlider" class="form-range limit-slider" type="range" min="0" max="2000" step="1" value="0" data-target="limitDown" data-output="limitDownMbps"></div><div class="limit-slider-row"><label for="limitUpSlider" class="form-label">Custom upload <b id="limitUpMbps">0 Mbit/s</b></label><input id="limitUpSlider" class="form-range limit-slider" type="range" min="0" max="2000" step="1" value="0" data-target="limitUp" data-output="limitUpMbps"></div><div class="small text-muted">0 means unlimited. Sliders use Mbit/s and save through the existing speed limits API.</div></div><div class="preset-grid mt-3"><button class="btn btn-sm btn-outline-secondary limit-preset" data-mbps="0"><i class="fa-solid fa-infinity"></i> Unlimited</button><button class="btn btn-sm btn-outline-secondary limit-preset" data-mbps="100"><i class="fa-solid fa-gauge"></i> 100 Mbit/s</button><button class="btn btn-sm btn-outline-secondary limit-preset" data-mbps="200"><i class="fa-solid fa-gauge"></i> 200 Mbit/s</button><button class="btn btn-sm btn-outline-secondary limit-preset" data-mbps="500"><i class="fa-solid fa-gauge-high"></i> 500 Mbit/s</button><button class="btn btn-sm btn-outline-secondary limit-preset" data-mbps="1000"><i class="fa-solid fa-gauge-high"></i> 1 Gbit/s</button><button class="btn btn-sm btn-outline-secondary limit-preset" data-mbps="2000"><i class="fa-solid fa-gauge-high"></i> 2 Gbit/s</button></div></div><div class="modal-footer"><button class="btn btn-secondary" data-bs-dismiss="modal"><i class="fa-solid fa-xmark"></i> Close</button><button id="saveSpeedBtn" class="btn btn-primary"><span class="btn-label"><i class="fa-solid fa-floppy-disk"></i> Save limits</span></button></div></div></div></div>
<div class="modal fade" id="labelModal" tabindex="-1"><div class="modal-dialog"><div class="modal-content"><div class="modal-header"><h5 class="modal-title"><i class="fa-solid fa-tags"></i> Set labels</h5><button class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><label class="form-label small text-muted">Add new label</label><div class="input-group mb-3"><input id="labelInput" class="form-control" placeholder="Label name, or several separated by comma"><button id="addLabelToSelectionBtn" class="btn btn-outline-primary" type="button"><i class="fa-solid fa-plus"></i> Add</button></div><div class="section-title">Selected labels</div><div id="selectedLabelList" class="chips mb-3"></div><div class="section-title">Saved labels</div><div id="labelList" class="chips mt-2"></div></div><div class="modal-footer"><button id="clearLabelsBtn" class="btn btn-outline-danger me-auto"><i class="fa-solid fa-eraser"></i> Clear labels</button><button class="btn btn-secondary" data-bs-dismiss="modal"><i class="fa-solid fa-xmark"></i> Close</button><button id="saveLabelBtn" class="btn btn-primary"><i class="fa-solid fa-check"></i> Apply</button></div></div></div></div>
<div class="modal fade" id="ratioAssignModal" tabindex="-1"><div class="modal-dialog"><div class="modal-content"><div class="modal-header"><h5 class="modal-title">Set ratio group</h5><button class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><select id="ratioAssignSelect" class="form-select"></select></div><div class="modal-footer"><button class="btn btn-secondary" data-bs-dismiss="modal"><i class="fa-solid fa-xmark"></i> Close</button><button id="applyRatioBtn" class="btn btn-primary"><i class="fa-solid fa-check"></i> Apply</button></div></div></div></div>
<div class="modal fade" id="toolsModal" tabindex="-1">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Tools & rTorrents</h5>
<button class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<ul class="nav nav-pills tools-nav mb-3" aria-label="Tools sections">
<li class="nav-item"><button class="nav-link active tool-tab" data-tool="rtorrents" type="button"><i class="fa-solid fa-server"></i> rTorrents</button></li>
<li class="nav-item"><button class="nav-link tool-tab" data-tool="torrentstats" type="button"><i class="fa-solid fa-chart-pie"></i> Torrent stats</button></li>
<li class="nav-item"><button class="nav-link tool-tab" data-tool="preferences" type="button"><i class="fa-solid fa-sliders"></i> Preferences</button></li>
<li class="nav-item"><button class="nav-link tool-tab" data-tool="jobs" type="button"><i class="fa-solid fa-gauge-high"></i> Jobs</button></li>
<li class="nav-item"><button class="nav-link tool-tab" data-tool="logs" type="button"><i class="fa-solid fa-book"></i> Logs</button></li>
{% if auth_enabled and current_user and current_user.role == 'admin' %}<li class="nav-item"><button class="nav-link tool-tab" data-tool="users" type="button"><i class="fa-solid fa-users-gear"></i> Users</button></li>{% endif %}
<li class="nav-item"><button class="nav-link tool-tab" data-tool="labels" type="button"><i class="fa-solid fa-tags"></i> Labels</button></li>
<li class="nav-item"><button class="nav-link tool-tab" data-tool="ratio" type="button"><i class="fa-solid fa-scale-balanced"></i> Ratio groups</button></li>
<li class="nav-item"><button class="nav-link tool-tab" data-tool="rss" type="button"><i class="fa-solid fa-rss"></i> RSS downloader</button></li>
<li class="nav-item"><button class="nav-link tool-tab" data-tool="columns" type="button"><i class="fa-solid fa-table-columns"></i> Columns</button></li>
<li class="nav-item"><button class="nav-link tool-tab" data-tool="smart" type="button"><i class="fa-solid fa-shuffle"></i> Smart Queue</button></li>
<li class="nav-item"><button class="nav-link tool-tab" data-tool="automations" type="button"><i class="fa-solid fa-wand-magic-sparkles"></i> Automations</button></li>
<li class="nav-item"><button class="nav-link tool-tab" data-tool="rtconfig" type="button"><i class="fa-solid fa-gears"></i> rTorrent config</button></li>
<li class="nav-item"><button class="nav-link tool-tab" data-tool="cleanup" type="button"><i class="fa-solid fa-broom"></i> Cleanup</button></li>
<li class="nav-item"><button class="nav-link tool-tab" data-tool="backup" type="button"><i class="fa-solid fa-box-archive"></i> Backup</button></li>
<li class="nav-item"><button class="nav-link tool-tab" data-tool="appstatus" type="button"><i class="fa-solid fa-heart-pulse"></i> App status</button></li>
</ul>
<div id="toolRtorrents"><div class="surface-section mb-3"><div class="section-title">rTorrents</div><div id="profileList" class="mb-3 small"><span class="spinner-border spinner-border-sm me-2"></span>Loading profiles...</div></div><div class="surface-section mb-3"><div class="section-title"><span id="profileFormTitle">Add rTorrent profile</span></div><input id="profileId" type="hidden"><div class="profile-form-grid"><label class="profile-form-field"><span>Profile name</span><input id="profileName" class="form-control" placeholder="rtorrent1"><small>Visible name used in the profile selector.</small></label><label class="profile-form-field profile-form-field-wide"><span>SCGI URL</span><input id="profileUrl" class="form-control" placeholder="scgi://127.0.0.1:5000/RPC2"><small>Connection address in scgi://host:port/RPC2 format.</small></label><label class="profile-form-field"><span>Timeout</span><input id="profileTimeout" class="form-control" type="number" value="5" min="1"><small>Seconds to wait for rTorrent response.</small></label><label class="profile-form-field"><span>Parallel jobs</span><input id="profileParallel" class="form-control" type="number" value="5" min="1"><small>Maximum queued actions running at once.</small></label><label class="profile-form-field profile-check-field"><span>Location</span><span class="form-check form-switch mb-0"><input id="profileRemote" class="form-check-input" type="checkbox"><span class="form-check-label">Remote location</span></span><small>Check this if you want to connect to a remote rTorrent instance instead of localhost.</small></label><div class="profile-form-actions"><button id="saveProfileBtn" class="btn btn-primary btn-sm"><i class="fa-solid fa-plus"></i> Add profile</button><button id="testProfileBtn" class="btn btn-outline-info btn-sm" type="button"><i class="fa-solid fa-plug-circle-check"></i> Test SCGI</button><button id="profileExportBtn" class="btn btn-outline-secondary btn-sm" type="button"><i class="fa-solid fa-file-export"></i> Export</button><button id="profileImportBtn" class="btn btn-outline-secondary btn-sm" type="button"><i class="fa-solid fa-file-import"></i> Import</button><input id="profileImportFile" class="d-none" type="file" accept="application/json,.json"><button id="cancelProfileEditBtn" class="btn btn-outline-secondary btn-sm d-none" type="button"><i class="fa-solid fa-xmark"></i> Cancel</button></div></div><div id="profileDiagnosticsResult" class="mt-3"></div><div class="form-text">Create one rTorrent profile at a time. Move/remove queues keep their order for each profile.</div></div></div>
<div id="toolTorrentStats" class="d-none"><div class="surface-section"><div class="section-title"><i class="fa-solid fa-chart-pie"></i> Torrent statistics</div><div class="tool-note mb-3">Cached metadata summary. File metadata is refreshed every 15 minutes, a few minutes after startup, or manually.</div><div class="torrent-stats-toolbar"><button id="torrentStatsRefreshBtn" class="btn btn-sm btn-outline-primary"><i class="fa-solid fa-rotate"></i> Refresh now</button><span id="torrentStatsMeta" class="small text-muted">Not loaded.</span></div><div id="torrentStatsManager" class="mt-3">Open this tab to load statistics.</div></div></div>
<div id="toolPreferences" class="d-none"><div class="preferences-sections"><div class="column-manager-tabs"><ul class="nav nav-pills" role="tablist"><li class="nav-item"><button class="nav-link active" data-bs-toggle="pill" data-bs-target="#prefPane-appearance" type="button" role="tab"><i class="fa-solid fa-palette"></i> Appearance</button></li><li class="nav-item"><button class="nav-link " data-bs-toggle="pill" data-bs-target="#prefPane-browser" type="button" role="tab"><i class="fa-solid fa-window-maximize"></i> Browser</button></li><li class="nav-item"><button class="nav-link " data-bs-toggle="pill" data-bs-target="#prefPane-notifications" type="button" role="tab"><i class="fa-solid fa-bell"></i> Notifications</button></li><li class="nav-item"><button class="nav-link " data-bs-toggle="pill" data-bs-target="#prefPane-disk" type="button" role="tab"><i class="fa-solid fa-hard-drive"></i> Disk monitor</button></li><li class="nav-item"><button class="nav-link " data-bs-toggle="pill" data-bs-target="#prefPane-port" type="button" role="tab"><i class="fa-solid fa-network-wired"></i> Port checker</button></li><li class="nav-item"><button class="nav-link " data-bs-toggle="pill" data-bs-target="#prefPane-peers" type="button" role="tab"><i class="fa-solid fa-users-viewfinder"></i> Peers</button></li><li class="nav-item"><button class="nav-link " data-bs-toggle="pill" data-bs-target="#prefPane-footer" type="button" role="tab"><i class="fa-solid fa-table-list"></i> Footer</button></li></ul></div><div class="tab-content preference-tab-content"><div id="prefPane-appearance" class="tab-pane fade show active" role="tabpanel"><div class="surface-section"><div class="section-title"><i class="fa-solid fa-palette"></i> Appearance</div><div class="small text-muted mb-2">Theme, typography and interface scale. Torrent view preferences also remember the selected filter, sorting and the height of the General / Files / Trackers panel.</div><div class="view-preferences-note"><i class="fa-solid fa-circle-info"></i><span>View state is saved automatically in the database: current torrent filter, last sort column and direction, visible columns, and details panel height.</span><button id="resetViewPreferencesBtn" class="btn btn-sm btn-outline-secondary" type="button"><i class="fa-solid fa-arrow-rotate-left"></i> Reset view defaults</button></div><div class="preferences-grid"><label class="form-field"><span>Bootstrap theme</span><select id="bootstrapThemeSelect" class="form-select form-select-sm">{% for value, label in bootstrap_themes.items() %}<option value="{{ value }}" {% if prefs and prefs.bootstrap_theme == value %}selected{% endif %}>{{ label }}</option>{% endfor %}</select></label><label class="form-field"><span>Font</span><select id="fontFamilySelect" class="form-select form-select-sm">{% for value, label in font_families.items() %}<option value="{{ value }}" {% if prefs and prefs.font_family == value %}selected{% endif %}>{{ label }}</option>{% endfor %}</select></label><label class="form-field interface-scale-field"><span>Interface scale <b id="interfaceScaleValue">{{ prefs.interface_scale if prefs and prefs.interface_scale else 100 }}%</b></span><input id="interfaceScaleRange" class="form-range" type="range" min="80" max="140" step="5" value="{{ prefs.interface_scale if prefs and prefs.interface_scale else 100 }}"><small>Decrease or increase the whole interface size.</small></label></div></div></div><div id="prefPane-browser" class="tab-pane fade " role="tabpanel"><div class="preferences-browser-layout"><div class="surface-section preference-block"><div class="section-title"><i class="fa-solid fa-heading"></i> Browser title</div><div class="small text-muted mb-2">Controls what is shown in the browser tab.</div><label class="browser-speed-pref form-check form-switch"><input id="titleSpeedEnabled" class="form-check-input" type="checkbox" {% if prefs and prefs.title_speed_enabled %}checked{% endif %}><span class="form-check-label">Show DL/UP in browser title</span><small>Displays current speeds next to pyTorrent in the tab title.</small></label></div><div class="surface-section preference-block"><div class="section-title"><i class="fa-solid fa-icons"></i> Tracker icons</div><div class="small text-muted mb-2">Visual helper for tracker filters in the sidebar.</div><label class="browser-speed-pref form-check form-switch"><input id="trackerFaviconsEnabled" class="form-check-input" type="checkbox" {% if prefs and prefs.tracker_favicons_enabled %}checked{% endif %}><span class="form-check-label">Download tracker favicons</span><small>Shows tracker icons in the sidebar tracker filter when available.</small></label></div></div></div><div id="prefPane-notifications" class="tab-pane fade " role="tabpanel"><div class="surface-section"><div class="section-title"><i class="fa-solid fa-bell"></i> Notifications</div><div class="small text-muted mb-2">Toast notifications from automatic systems.</div><div class="preferences-grid"><label class="browser-speed-pref form-check form-switch"><input id="automationToastsEnabled" class="form-check-input" type="checkbox" {% if not prefs or prefs.automation_toasts_enabled %}checked{% endif %}><span class="form-check-label">Automation toasts</span><small>Show toasts created by automation runs.</small></label><label class="browser-speed-pref form-check form-switch"><input id="smartQueueToastsEnabled" class="form-check-input" type="checkbox" {% if not prefs or prefs.smart_queue_toasts_enabled %}checked{% endif %}><span class="form-check-label">Smart Queue toasts</span><small>Show Smart Queue automatic run messages.</small></label></div></div></div><div id="prefPane-disk" class="tab-pane fade " role="tabpanel"><div class="surface-section"><div class="section-title"><i class="fa-solid fa-hard-drive"></i> Disk monitor</div><div class="small text-muted mb-2">Choose what the footer disk bar should represent and add extra storage paths.</div><div class="disk-monitor-shell disk-monitor-shell-flat"><div class="disk-monitor-mode-card"><div class="disk-monitor-card-title">Progress source</div><label class="form-check form-switch disk-monitor-switch"><input id="diskModeDefault" class="form-check-input disk-monitor-mode" type="radio" name="diskMonitorModeChoice" value="default"><span class="form-check-label">Default rTorrent path</span><small>Use the main directory from the active rTorrent profile.</small></label><label class="form-check form-switch disk-monitor-switch"><input id="diskModeSelected" class="form-check-input disk-monitor-mode" type="radio" name="diskMonitorModeChoice" value="selected"><span class="form-check-label">Selected monitored path</span><small>Use one custom path below as the footer progress value.</small></label><label class="form-check form-switch disk-monitor-switch"><input id="diskModeAggregate" class="form-check-input disk-monitor-mode" type="radio" name="diskMonitorModeChoice" value="aggregate"><span class="form-check-label">Aggregate all paths</span><small>Show combined usage. Single path selection is disabled in this mode.</small></label></div><div class="disk-monitor-path-card"><div class="disk-monitor-card-title">Monitored paths</div><div class="input-group input-group-sm"><input id="diskMonitorPathInput" class="form-control" placeholder="/data/torrents"><button id="addDiskPathBtn" class="btn btn-outline-primary" type="button"><i class="fa-solid fa-plus"></i> Add path</button></div><label class="form-field mt-2"><span>Path used for selected mode</span><select id="diskMonitorSelectedPath" class="form-select form-select-sm"></select><small id="diskMonitorSelectedHint">Select a monitored path first.</small></label><div id="diskMonitorPaths" class="disk-monitor-path-list"></div></div></div><input id="diskMonitorMode" type="hidden" value="default"><div class="form-text">The footer tooltip always shows details for available paths; this setting only decides which value drives the visible progress bar.</div></div></div><div id="prefPane-port" class="tab-pane fade " role="tabpanel"><div class="surface-section"><div class="section-title"><i class="fa-solid fa-network-wired"></i> Port checker</div><div class="small text-muted mb-2">Incoming connection test, separate from visual preferences.</div><div class="form-check form-switch mb-3"><input id="portCheckEnabled" class="form-check-input" type="checkbox"><label class="form-check-label" for="portCheckEnabled">Enable incoming port check</label></div><div class="d-flex gap-2 align-items-center flex-wrap"><span id="portCheckBadge" class="port-status port-secondary">disabled</span><button id="portCheckNowBtn" class="btn btn-sm btn-outline-primary"><i class="fa-solid fa-plug-circle-check"></i> Check port now</button></div><div id="portCheckInfo" class="form-text mt-2">Uses YouGetSignal first. Manual check bypasses the 6h cache.</div></div></div><div id="prefPane-peers" class="tab-pane fade " role="tabpanel"><div class="surface-section"><div class="section-title"><i class="fa-solid fa-users-viewfinder"></i> Peers</div><div class="small text-muted mb-2">Optional peer table helpers.</div><label class="browser-speed-pref form-check form-switch"><input id="reverseDnsEnabled" class="form-check-input" type="checkbox" {% if prefs and prefs.reverse_dns_enabled %}checked{% endif %}><span class="form-check-label">Resolve peer IP to reverse DNS host</span><small>Uses a lightweight built-in resolver with cache. Hostnames appear only in the Peers tab.</small></label></div></div><div id="prefPane-footer" class="tab-pane fade " role="tabpanel"><div class="surface-section"><div class="section-title"><i class="fa-solid fa-table-list"></i> Footer</div><div class="small text-muted mb-2">Choose which status items are visible in the bottom bar.</div><div id="footerPreferences" class="footer-preferences"></div><button id="saveFooterPrefsBtn" class="btn btn-sm btn-primary mt-2" type="button"><i class="fa-solid fa-floppy-disk"></i> Save footer</button></div></div></div></div></div>
<div id="toolJobs" class="d-none"><div class="surface-section"><div class="section-title"><i class="fa-solid fa-gauge-high"></i> Job scheduling</div><div class="tool-note mb-3">These settings are stored per active rTorrent profile. Light jobs are control actions such as start, stop, pause, resume, labels, ratio assignment, reannounce and speed limits. Heavy jobs are long or destructive actions such as move, remove and adding torrents.</div><div class="job-settings-grid"><label class="form-field"><span>Heavy parallel jobs</span><input id="jobHeavyParallel" class="form-control form-control-sm" type="number" min="1" max="64" value="5"><small>Maximum heavy jobs running at once for this profile. Default: 5.</small></label><label class="form-field"><span>Light parallel jobs</span><input id="jobLightParallel" class="form-control form-control-sm" type="number" min="1" max="64" value="4"><small>Separate slot pool for lightweight control jobs so they do not wait behind heavy IO work. Default: 4.</small></label><label class="form-field"><span>Light timeout seconds</span><input id="jobLightTimeout" class="form-control form-control-sm" type="number" min="30" max="86400" value="300"><small>Watchdog marks a light job as failed after this time. Default: 300 seconds.</small></label><label class="form-field"><span>Heavy timeout seconds</span><input id="jobHeavyTimeout" class="form-control form-control-sm" type="number" min="300" max="172800" value="7200"><small>Watchdog timeout for move/remove/add jobs. Default: 7200 seconds.</small></label><label class="form-field"><span>Pending timeout seconds</span><input id="jobPendingTimeout" class="form-control form-control-sm" type="number" min="60" max="86400" value="900"><small>Pending jobs older than this are resubmitted if no worker is currently handling them. Default: 900 seconds.</small></label></div><div class="job-settings-actions mt-3"><button id="saveJobSettingsBtn" class="btn btn-sm btn-primary"><i class="fa-solid fa-floppy-disk"></i> Save job settings</button><button id="reloadJobSettingsBtn" class="btn btn-sm btn-outline-secondary"><i class="fa-solid fa-rotate"></i> Reload</button><span id="jobSettingsProfileName" class="small text-muted"></span></div></div></div>
<div id="toolLogs" class="d-none"><div class="surface-section"><div class="section-title"><i class="fa-solid fa-book"></i> Operation log retention</div><div class="tool-note mb-3">Manage operation log retention and review profile-scoped statistics without changing torrent data.</div><div class="operation-log-settings-grid"><label class="form-field"><span>Retention mode</span><select id="operationLogRetentionMode" class="form-select form-select-sm"><option value="days">By days</option><option value="lines">By line count</option><option value="both">Days and line count</option><option value="manual">Manual cleanup only</option></select></label><label class="form-field"><span>Retention days</span><input id="operationLogRetentionDays" class="form-control form-control-sm" type="number" min="1" max="3650" value="30"></label><label class="form-field"><span>Keep lines</span><input id="operationLogRetentionLines" class="form-control form-control-sm" type="number" min="100" max="1000000" value="5000"></label><div class="operation-log-settings-actions"><button id="saveOperationLogRetentionBtn" class="btn btn-sm btn-primary" type="button"><i class="fa-solid fa-floppy-disk"></i> Save retention</button><button id="applyOperationLogRetentionBtn" class="btn btn-sm btn-outline-warning" type="button"><i class="fa-solid fa-filter-circle-xmark"></i> Apply retention now</button><button id="clearOperationLogsBtn" class="btn btn-sm btn-outline-danger" type="button"><i class="fa-solid fa-trash"></i> Clear current filter</button></div></div><div class="operation-log-view-settings"><div><b>Default log view</b><small>Controls the default category and job log visibility used by the Logs modal.</small></div><label class="form-field"><span>Default log category</span><select id="operationLogDefaultType" class="form-select form-select-sm"><option value="">All non-job types</option><option value="torrent_added">Torrent added</option><option value="torrent_removed">Torrent removed</option><option value="torrent_completed">Torrent completed</option><option value="job_started">Job started</option><option value="job_done">Job done</option><option value="job_failed">Job failed</option></select></label><label class="form-check form-switch operation-log-hide-jobs"><input id="operationLogHideJobsDefault" class="form-check-input" type="checkbox" checked><span class="form-check-label">Hide job logs by default</span></label><button id="saveOperationLogViewBtn" class="btn btn-sm btn-outline-primary" type="button"><i class="fa-solid fa-eye-slash"></i> Save log view</button></div><div id="operationLogStats" class="mt-3"><span class="spinner-border spinner-border-sm"></span> Loading statistics...</div></div></div>
{% if auth_enabled and current_user and current_user.role == 'admin' %}
<div id="toolUsers" class="d-none"><div class="surface-section"><div class="section-title"><i class="fa-solid fa-users-gear"></i> Users</div><div class="tool-note mb-3">Manage optional pyTorrent users. Empty profile means all profiles. R/O blocks rTorrent-changing actions; Full allows them.</div><div class="user-form-grid"><input id="authUserId" type="hidden"><input id="authUsername" class="form-control" placeholder="User"><input id="authPassword" class="form-control" type="password" placeholder="Password / new password"><select id="authRole" class="form-select"><option value="user">user</option><option value="admin">admin</option></select><select id="authProfile" class="form-select"><option value="0">All profiles</option></select><select id="authAccess" class="form-select"><option value="ro">R/O</option><option value="full">Full</option></select><label class="form-check form-switch mb-0"><input id="authActive" class="form-check-input" type="checkbox" checked><span class="form-check-label">Active</span></label><button id="authUserSaveBtn" class="btn btn-sm btn-primary" type="button"><i class="fa-solid fa-floppy-disk"></i> Save user</button><button id="authUserCancelBtn" class="btn btn-sm btn-outline-secondary d-none" type="button"><i class="fa-solid fa-xmark"></i> Cancel</button></div><div id="authTokenInline" class="api-token-inline d-none mt-3"></div><div id="authUsersManager" class="mt-3"></div></div></div>
{% endif %}
<div id="toolLabels" class="d-none"><div class="surface-section"><div class="section-title"><i class="fa-solid fa-tags"></i> Labels</div><div class="tool-note mb-3">Create reusable labels and remove labels that are no longer needed.</div><div class="input-group input-group-sm mb-3"><span class="input-group-text"><i class="fa-solid fa-tag"></i></span><input id="newLabelName" class="form-control" placeholder="New label"><button id="newLabelBtn" class="btn btn-primary" type="button"><i class="fa-solid fa-plus"></i> Add label</button></div><div id="labelsManager" class="labels-manager"></div></div></div>
<div id="toolRatio" class="d-none">
<div class="surface-section tool-split-section">
<div class="section-title"><i class="fa-solid fa-scale-balanced"></i> Ratio rules</div>
<div class="tool-note mb-3">Rules are checked automatically every 5 minutes. A torrent uses the group stored in its rTorrent custom ratio field.</div>
<!-- Note: These fields match the ratio save payload used by the frontend module, including action, move target and safety switches. -->
<div class="management-card">
<div class="management-card-title"><i class="fa-solid fa-plus"></i> Add or edit group</div>
<div class="ratio-rule-grid management-form-grid">
<label class="form-field"><span>Group name</span><input id="ratioName" class="form-control" placeholder="Group name"></label>
<label class="form-field"><span>Min ratio</span><input id="ratioMin" class="form-control" type="number" step="0.1" value="1" placeholder="Min ratio"></label>
<label class="form-field"><span>Max ratio</span><input id="ratioMax" class="form-control" type="number" step="0.1" value="2" placeholder="Max ratio"></label>
<label class="form-field"><span>Seed minutes</span><input id="ratioSeed" class="form-control" type="number" value="0" placeholder="Min seed minutes"></label>
<label class="form-field"><span>Action</span><select id="ratioAction" class="form-select"><option value="stop">stop</option><option value="remove">remove</option><option value="remove_data">remove data</option><option value="move">move</option><option value="set_label">set label</option><option value="pause">pause</option></select></label>
<label class="form-field"><span>Move path</span><input id="ratioMovePath" class="form-control" placeholder="Move path"></label>
<label class="form-field"><span>Set label</span><input id="ratioSetLabel" class="form-control" placeholder="Set label"></label>
<label class="form-check form-switch management-switch"><input id="ratioIgnorePrivate" class="form-check-input" type="checkbox" checked><span class="form-check-label">Skip private torrents</span></label>
<label class="form-check form-switch management-switch"><input id="ratioIgnoreUpload" class="form-check-input" type="checkbox" checked><span class="form-check-label">Skip active upload</span></label>
</div>
<div class="management-actions"><button id="ratioSaveBtn" class="btn btn-sm btn-primary"><i class="fa-solid fa-floppy-disk"></i> Save group</button><button id="ratioCheckBtn" class="btn btn-sm btn-outline-success"><i class="fa-solid fa-play"></i> Check now</button></div>
</div>
<div class="management-card mt-3"><div class="management-card-title"><i class="fa-solid fa-list-check"></i> Existing groups</div><div id="ratioManager" class="mt-2"></div></div>
</div>
</div><div id="toolRss" class="d-none"><div class="surface-section tool-split-section"><div class="section-title"><i class="fa-solid fa-rss"></i> RSS downloader</div><div class="tool-note mb-3">Feeds are checked by schedule and every match is logged per feed/rule.</div><div class="management-card"><div class="management-card-title"><i class="fa-solid fa-satellite-dish"></i> Feed</div><div class="rss-form-grid management-form-grid"><input id="rssFeedId" type="hidden"><label class="form-field"><span>Feed name</span><input id="rssName" class="form-control" placeholder="Feed name"></label><label class="form-field form-field-wide"><span>Feed URL</span><input id="rssUrl" class="form-control" placeholder="https://.../feed.rss"></label><label class="form-field"><span>Interval minutes</span><input id="rssInterval" class="form-control" type="number" min="5" value="30" placeholder="Interval minutes"></label></div><div class="management-actions"><button id="rssFeedBtn" class="btn btn-sm btn-primary"><i class="fa-solid fa-floppy-disk"></i> Save feed</button></div></div><div class="management-card mt-3"><div class="management-card-title"><i class="fa-solid fa-filter-circle-dollar"></i> Rule</div><div class="rss-form-grid management-form-grid"><input id="rssRuleId" type="hidden"><label class="form-field"><span>Rule name</span><input id="rssRuleName" class="form-control" placeholder="Rule name"></label><label class="form-field"><span>Include pattern</span><input id="rssPattern" class="form-control" placeholder="Regex / text"></label><label class="form-field"><span>Exclude pattern</span><input id="rssExclude" class="form-control" placeholder="Exclude"></label><label class="form-field"><span>Min MB</span><input id="rssMinSize" class="form-control" type="number" min="0" value="0" placeholder="Min MB"></label><label class="form-field"><span>Max MB</span><input id="rssMaxSize" class="form-control" type="number" min="0" value="0" placeholder="Max MB"></label><label class="form-field"><span>Category</span><input id="rssCategory" class="form-control" placeholder="Category"></label><label class="form-field"><span>Quality</span><input id="rssQuality" class="form-control" placeholder="Quality"></label><label class="form-field"><span>Season</span><input id="rssSeason" class="form-control" type="number" min="1" placeholder="Season"></label><label class="form-field"><span>Episode</span><input id="rssEpisode" class="form-control" type="number" min="1" placeholder="Episode"></label><label class="form-field form-field-wide"><span>Save path</span><div class="input-group"><input id="rssPath" class="form-control" placeholder="Save path"><button class="btn btn-outline-secondary browse-path" data-target="rssPath" type="button"><i class="fa-solid fa-folder-open"></i></button></div></label><label class="form-field"><span>Label</span><input id="rssLabel" class="form-control" placeholder="Label"></label></div><div class="management-actions"><button id="rssRuleBtn" class="btn btn-sm btn-primary"><i class="fa-solid fa-floppy-disk"></i> Save rule</button><button id="rssTestBtn" class="btn btn-sm btn-outline-success"><i class="fa-solid fa-vial"></i> Test rule</button><button id="rssCheckBtn" class="btn btn-sm btn-success"><i class="fa-solid fa-play"></i> Check now</button></div></div><!-- Note: RSS test output is required by the existing Test rule handler. -->
<div id="rssTestResult" class="mt-3"></div><div class="management-card mt-3"><div class="management-card-title"><i class="fa-solid fa-list"></i> Feeds, rules and matches</div><div id="rssManager" class="mt-2"></div></div></div></div>
<div id="toolColumns" class="d-none"><div class="small text-muted mb-2">Choose columns visible in the torrent list.</div><div id="columnManager" class="column-manager"></div><div class="mt-3"><button id="saveColumnsBtn" class="btn btn-primary btn-sm"><i class="fa-solid fa-floppy-disk"></i> Save columns</button><button id="recommendedColumnsBtn" class="btn btn-outline-primary btn-sm ms-2" type="button"><i class="fa-solid fa-table-columns"></i> Recommended columns</button><button id="resetColumnsBtn" class="btn btn-outline-secondary btn-sm ms-2"><i class="fa-solid fa-rotate-left"></i> Reset</button></div></div>
<div id="toolSmart" class="d-none"><div class="column-manager-tabs mb-3"><ul class="nav nav-pills" role="tablist"><li class="nav-item"><button class="nav-link active" data-bs-toggle="pill" data-bs-target="#smartPane-settings" type="button" role="tab"><i class="fa-solid fa-sliders"></i> Settings</button></li><li class="nav-item"><button class="nav-link" data-bs-toggle="pill" data-bs-target="#smartPane-logs" type="button" role="tab"><i class="fa-solid fa-clock-rotate-left"></i> Logs</button></li></ul></div><div class="tab-content"><div id="smartPane-settings" class="tab-pane fade show active" role="tabpanel"><div class="surface-section smart-panel"><div class="smart-header"><div><div class="section-title mb-1"><i class="fa-solid fa-shuffle"></i> Smart Queue</div><div class="small text-muted">Automatic queue balancing for slow or stalled downloads.</div></div><div class="smart-header-actions"><button id="smartSaveBtn" class="btn btn-sm btn-primary"><i class="fa-solid fa-floppy-disk"></i> Save</button><button id="smartCheckBtn" class="btn btn-sm btn-success"><i class="fa-solid fa-play"></i> Check now</button></div></div><!-- UI note: Smart Queue switch is named plainly and kept inside the row on mobile. --><div class="smart-settings-list"><div class="smart-setting-row smart-toggle-row"><div><label class="form-check-label" for="smartEnabled">Enabled</label><div class="form-text">Run Smart Queue during polling. Stopped torrents are managed; Paused torrents stay user-controlled.</div></div><div class="form-check form-switch"><input id="smartEnabled" class="form-check-input" type="checkbox"></div></div><!-- UI note: Auto-stop keeps saved thresholds but disables Smart Queue when the active profile has no active or waiting downloads. --><div class="smart-setting-row smart-auto-stop-idle-row"><div><label class="form-check-label" for="smartAutoStopIdle">Auto-stop when idle</label><div class="form-text">When enabled, Smart Queue disables itself after a check finds no active downloads and no waiting stopped candidates. Enable it again manually when you add more work.</div></div><div class="form-check form-switch"><input id="smartAutoStopIdle" class="form-check-input" type="checkbox"></div></div><div class="smart-cooldown-card"><div><span class="smart-cooldown-label">Next Smart Queue run</span><strong id="smartCooldownBadge" class="cooldown-live smart-cooldown-live" data-seconds="0">next: ready</strong><small id="smartCooldownHint">Automatic runs use the cooldown below. Manual Check now still runs immediately.</small></div><label class="smart-cooldown-field"><span>Cooldown minutes</span><input id="smartCooldown" class="form-control form-control-sm" type="number" min="1" value="10"></label></div><div class="smart-refill-card"><div><span class="smart-refill-title">Queue refill during cooldown</span><small id="smartRefillHint">Automatic keeps the current poller cadence. Custom runs only after the selected number of minutes. Off disables refill completely.</small></div><div class="smart-refill-controls"><label class="smart-refill-field"><span>Mode</span><select id="smartRefillMode" class="form-select form-select-sm"><option value="auto">Automatic</option><option value="custom">Every N minutes</option><option value="off">Off</option></select></label><label class="smart-refill-field"><span>Minutes</span><input id="smartRefillInterval" class="form-control form-control-sm" type="number" min="1" value="5"></label></div></div><div class="smart-input-grid"><label class="smart-input-field"><span>Target active downloads</span><input id="smartMaxActive" class="form-control form-control-sm" type="number" min="1" value="5"><small>Smart Queue keeps only this many active downloads; overflow is stopped.</small></label><label class="smart-input-field"><span>Stalled after seconds</span><input id="smartStalled" class="form-control form-control-sm" type="number" min="30" value="300"><small>How long a matching active torrent must stay stalled before it can be replaced.</small></label><label class="smart-input-field"><span>Max stops per check</span><input id="smartStopBatch" class="form-control form-control-sm" type="number" min="1" value="50"><small>Maximum stalled/overflow downloads Smart Queue may stop in one pass.</small></label><label class="smart-input-field"><span>Start grace seconds</span><input id="smartStartGrace" class="form-control form-control-sm" type="number" min="0" value="900"><small>Newly queue-started torrents are protected from stalled cleanup during this warm-up.</small></label><label class="smart-input-field"><span>Min speed KiB/s</span><input id="smartMinSpeed" class="form-control form-control-sm" type="number" min="0" value="1"></label><label class="smart-input-field"><span>Min seeds</span><input id="smartMinSeeds" class="form-control form-control-sm" type="number" min="0" value="1"></label><label class="smart-input-field"><span>Min peers</span><input id="smartMinPeers" class="form-control form-control-sm" type="number" min="0" value="0"><small>0 means only seed threshold is required.</small></label></div><div class="smart-setting-row smart-protect-active-row"><div><label class="form-check-label" for="smartProtectActiveBelowCap">Protect active count below cap</label><div class="form-text">Recommended for best efficiency. When enabled, Smart Queue refills empty slots first and does not stop stalled downloads while active downloads are below the cap. Stalled cleanup resumes once the cap is reached or exceeded. Disable only if you prefer aggressive cleanup over keeping the active count near the cap.</div></div><div class="form-check form-switch"><input id="smartProtectActiveBelowCap" class="form-check-input" type="checkbox" checked></div></div><!-- UI note: This option keeps rare-source torrents waiting by the stalled timer instead of stopping them only because seeds/peers are missing. --><div class="smart-setting-row smart-ignore-source-row"><div><label class="form-check-label" for="smartIgnoreSeedPeer">Ignore missing seeds/peers for stalled timer</label><div class="form-text">When enabled, Smart Queue does not use seed/peer count as a stalled criterion.</div></div><div class="form-check form-switch"><input id="smartIgnoreSeedPeer" class="form-check-input" type="checkbox"></div></div><!-- UI note: This option removes transfer speed from stalled detection; together with seed/peer ignore only the timer remains. --><div class="smart-setting-row smart-ignore-speed-row"><div><label class="form-check-label" for="smartIgnoreSpeed">Ignore speed for stalled timer</label><div class="form-text">When enabled, low speed is not required. With source and speed ignores enabled, only Stalled after seconds decides.</div></div><div class="form-check form-switch"><input id="smartIgnoreSpeed" class="form-check-input" type="checkbox"></div></div></div><div class="smart-actions mt-3"><button id="smartExcludeSelectedBtn" class="btn btn-sm btn-outline-warning"><i class="fa-solid fa-ban"></i> Manage exceptions</button><span class="small text-muted">Choose torrents ignored by Smart Queue. Existing behavior stays unchanged for all non-excluded torrents.</span></div><div id="smartManager" class="mt-3"></div></div></div><div id="smartPane-logs" class="tab-pane fade" role="tabpanel"><div class="surface-section smart-panel"><div class="section-title"><i class="fa-solid fa-clock-rotate-left"></i> Last operations</div><div id="smartHistory" class="mt-2"></div></div></div></div></div>
<div id="toolAutomations" class="d-none"><div class="column-manager-tabs mb-3"><ul class="nav nav-pills" role="tablist"><li class="nav-item"><button class="nav-link active" data-bs-toggle="pill" data-bs-target="#automationPane-settings" type="button" role="tab"><i class="fa-solid fa-sliders"></i> Settings</button></li><li class="nav-item"><button class="nav-link" data-bs-toggle="pill" data-bs-target="#automationPane-logs" type="button" role="tab"><i class="fa-solid fa-clock-rotate-left"></i> Logs</button></li></ul></div><div class="tab-content"><div id="automationPane-settings" class="tab-pane fade show active" role="tabpanel"><div class="surface-section"><div class="section-title"><i class="fa-solid fa-wand-magic-sparkles"></i> Automations / rules</div><div class="small text-muted mb-3">Build a rule as: conditions first, then ordered actions. Matching torrents are handled as one batch and the cooldown is applied to the whole rule.</div><input id="autoEditId" type="hidden"><div class="automation-shell"><div class="automation-main-card"><div class="automation-card-title">1. Rule</div><div class="automation-rule-grid"><label class="form-field"><span>Name</span><input id="autoName" class="form-control" placeholder="e.g. Punkty katalogowanie"></label><label class="form-field"><span>Cooldown minutes</span><input id="autoCooldown" class="form-control" type="number" min="0" value="60"></label><label class="form-check form-switch automation-enabled"><input id="autoEnabled" class="form-check-input" type="checkbox" checked><span class="form-check-label">Enabled</span></label></div></div><div class="automation-main-card"><div class="automation-card-title">2. Conditions</div><div class="automation-builder-grid"><select id="autoConditionType" class="form-select"><option value="completed">Completed</option><option value="no_seeds">Seeds are low for time</option><option value="ratio_gte">Ratio is at least</option><option value="progress_gte">Progress is at least %</option><option value="progress_lte">Progress is at most %</option><option value="label_missing">Label is missing</option><option value="label_has">Label exists</option><option value="status">Status equals</option><option value="path_contains">Path contains</option></select><label class="form-check form-switch automation-negate"><input id="autoCondNegate" class="form-check-input" type="checkbox"><span class="form-check-label">Negate</span></label><input id="autoCondSeeds" data-auto-cond="no_seeds" class="form-control" type="number" min="0" value="0" placeholder="Seeds <="><input id="autoCondMinutes" data-auto-cond="no_seeds" class="form-control" type="number" min="0" value="30" placeholder="Minutes"><input id="autoCondRatio" data-auto-cond="ratio_gte" class="form-control" type="number" step="0.1" value="1" placeholder="Ratio"><input id="autoCondProgress" data-auto-cond="progress_gte,progress_lte" class="form-control" type="number" min="0" max="100" step="0.1" value="100" placeholder="Progress %"><input id="autoCondLabel" data-auto-cond="label_missing,label_has" class="form-control" placeholder="Label"><select id="autoCondStatus" data-auto-cond="status" class="form-select"><option>Downloading</option><option>Seeding</option><option>Paused</option><option>Post-check</option><option>Stopped</option><option>Checking</option></select><input id="autoCondText" data-auto-cond="path_contains" class="form-control" placeholder="Path text"><button id="automationAddConditionBtn" class="btn btn-sm btn-outline-primary" type="button"><i class="fa-solid fa-plus"></i> Add condition</button></div><div id="automationConditionList" class="automation-chip-list mt-2"></div></div><div class="automation-main-card"><div class="automation-card-title">3. Actions, in order</div><div class="automation-builder-grid"><select id="autoEffectType" class="form-select"><option value="add_label">Add label</option><option value="remove_label">Remove label</option><option value="set_labels">Set labels</option><option value="move">Move to path</option><option value="pause">Pause</option><option value="stop">Stop</option><option value="start">Start</option><option value="resume">Resume</option><option value="recheck">Recheck</option></select><input id="autoEffectLabel" data-auto-effect="add_label,remove_label" class="form-control" placeholder="Label"><input id="autoEffectLabels" data-auto-effect="set_labels" class="form-control" placeholder="Labels separated by comma"><div data-auto-effect="move" class="input-group automation-path-input"><input id="autoEffectPath" class="form-control" placeholder="Target path"><button class="btn btn-outline-secondary browse-path" data-target="autoEffectPath" type="button"><i class="fa-solid fa-folder-open"></i></button></div><label data-auto-effect="move" class="form-check form-switch"><input id="autoMoveData" class="form-check-input" type="checkbox" checked><span class="form-check-label">Move data</span></label><label data-auto-effect="move" class="form-check form-switch"><input id="autoMoveRecheck" class="form-check-input" type="checkbox" checked><span class="form-check-label">Recheck</span></label><label data-auto-effect="move" class="form-check form-switch"><input id="autoMoveKeepSeeding" class="form-check-input" type="checkbox" checked><span class="form-check-label">Keep seeding</span></label><button id="automationAddEffectBtn" class="btn btn-sm btn-outline-primary" type="button"><i class="fa-solid fa-plus"></i> Add action</button></div><div id="automationEffectList" class="automation-chip-list mt-2"></div></div><div class="automation-actions"><button id="automationSaveBtn" class="btn btn-sm btn-primary"><i class="fa-solid fa-floppy-disk"></i> Save rule</button><button id="automationCancelEditBtn" class="btn btn-sm btn-outline-secondary d-none" type="button"><i class="fa-solid fa-xmark"></i> Cancel edit</button><button id="automationCheckBtn" class="btn btn-sm btn-success"><i class="fa-solid fa-play"></i> Check now</button><button id="automationExportBtn" class="btn btn-sm btn-outline-secondary" type="button"><i class="fa-solid fa-file-export"></i> Export JSON</button><button id="automationImportBtn" class="btn btn-sm btn-outline-secondary" type="button"><i class="fa-solid fa-file-import"></i> Import JSON</button><input id="automationImportFile" class="d-none" type="file" accept="application/json,.json"></div></div><div class="section-title mt-3">Rules</div><div id="automationManager"></div></div></div><div id="automationPane-logs" class="tab-pane fade" role="tabpanel"><div class="surface-section"><div class="section-title"><i class="fa-solid fa-clock-rotate-left"></i> History</div><div id="automationHistory"></div></div></div></div></div>
<div id="toolRtconfig" class="d-none"><div class="surface-section"><div class="section-title"><i class="fa-solid fa-gears"></i> rTorrent config</div><div class="small text-muted mb-2">Typical rTorrent options, like in ruTorrent. Unsupported methods are shown as unavailable.</div><div class="alert alert-secondary py-2 small rt-config-note">Reference value is kept from the first override save. Later saves add or clear differences without replacing the original reference.</div><div class="rt-config-toolbar"><span id="rtConfigChangedCount" class="badge text-bg-secondary">No changes</span><label class="form-check form-switch mb-0"><input id="rtConfigApplyOnStart" class="form-check-input" type="checkbox"><span class="form-check-label">Apply saved changes 60s after pyTorrent start</span></label></div><div id="rtConfigManager"><span class="spinner-border spinner-border-sm"></span> Loading config...</div><div class="mt-3"><button id="rtConfigReloadBtn" class="btn btn-sm btn-outline-primary"><i class="fa-solid fa-rotate"></i> Reload</button><button id="rtConfigResetBtn" class="btn btn-sm btn-outline-danger ms-2"><i class="fa-solid fa-rotate-left"></i> Reset UI settings</button><button id="rtConfigGenerateBtn" class="btn btn-sm btn-outline-secondary ms-2" disabled><i class="fa-solid fa-file-code"></i> Generate config</button><button id="rtConfigSaveBtn" class="btn btn-sm btn-primary ms-2"><i class="fa-solid fa-floppy-disk"></i> Save config</button></div><textarea id="rtConfigOutput" class="form-control form-control-sm mt-3 rt-config-output" rows="7" readonly placeholder="Generated rTorrent config changes will appear here."></textarea></div></div>
<div id="toolCleanup" class="d-none"><div class="surface-section"><div class="section-title"><i class="fa-solid fa-broom"></i> Cleanup / retention</div><div class="tool-note mb-3">One place to clear logs and active profile caches. Pending/running jobs, rules, settings and torrents are preserved.</div><div id="cleanupManager"><span class="spinner-border spinner-border-sm"></span> Loading cleanup data...</div></div></div>
<div id="toolBackup" class="d-none"><div class="surface-section"><div class="section-title"><i class="fa-solid fa-box-archive"></i> Backup / restore</div><div class="tool-note mb-3">Backup contains persistent settings: users, permissions, preferences, profiles, labels, ratio groups, RSS, Smart Queue, Automations, Planner and rTorrent config overrides.</div><div class="backup-settings-grid mb-3"><label class="form-check form-switch backup-auto-switch"><input id="backupAutoEnabled" class="form-check-input" type="checkbox"><span class="form-check-label">Enable automatic backups</span></label><label class="form-field"><span>Every X hours</span><input id="backupAutoInterval" class="form-control form-control-sm" type="number" min="1" value="24"></label><label class="form-field"><span>Retention days</span><input id="backupRetentionDays" class="form-control form-control-sm" type="number" min="1" value="30"></label><button id="backupSettingsSaveBtn" class="btn btn-sm btn-outline-primary"><i class="fa-solid fa-floppy-disk"></i> Save schedule</button></div><div class="input-group input-group-sm mb-3 backup-create-row"><input id="backupName" class="form-control" placeholder="Backup name"><button id="backupCreateBtn" class="btn btn-primary"><i class="fa-solid fa-floppy-disk"></i> Create backup</button></div><div id="backupPreview" class="backup-preview d-none"></div><div id="backupManager"></div></div></div>
<div id="toolAppstatus" class="d-none"><div id="appStatusTabs"></div><div class="surface-section"><div class="section-title"><i class="fa-solid fa-heart-pulse"></i> pyTorrent status</div><div class="small text-muted mb-2">Diagnostics for pyTorrent process and active SCGI/XMLRPC connection.</div><div class="mb-2"><button id="appStatusRefreshBtn" class="btn btn-sm btn-outline-primary"><i class="fa-solid fa-rotate"></i> Refresh</button></div><div id="appStatusManager">Open this tab to load diagnostics.</div></div></div>
</div>
</div>
</div>
</div>
<div class="modal fade" id="trafficModal" tabindex="-1"><div class="modal-dialog modal-xl"><div class="modal-content"><div class="modal-header"><h5 class="modal-title"><i class="fa-solid fa-chart-column"></i> Transfer history</h5><button class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><div class="column-manager-tabs"><ul class="nav nav-pills" role="tablist"><li class="nav-item" role="presentation"><button class="nav-link active traffic-history-tab" data-bs-toggle="pill" data-bs-target="#trafficSpeedPane" type="button" role="tab" data-history-pane="speed"><i class="fa-solid fa-gauge-high"></i> Speed</button></li><li class="nav-item" role="presentation"><button class="nav-link traffic-history-tab" data-bs-toggle="pill" data-bs-target="#trafficTransferPane" type="button" role="tab" data-history-pane="transfer"><i class="fa-solid fa-right-left"></i> Transfer</button></li></ul></div><div class="d-flex gap-2 mb-3 flex-wrap"><button class="btn btn-sm btn-outline-secondary traffic-range" data-range="15m"><i class="fa-solid fa-clock"></i> 15m</button><button class="btn btn-sm btn-outline-secondary traffic-range" data-range="1h"><i class="fa-solid fa-clock"></i> 1h</button><button class="btn btn-sm btn-outline-secondary traffic-range" data-range="3h"><i class="fa-solid fa-clock"></i> 3h</button><button class="btn btn-sm btn-outline-secondary traffic-range" data-range="6h"><i class="fa-solid fa-clock"></i> 6h</button><button class="btn btn-sm btn-outline-secondary traffic-range" data-range="24h"><i class="fa-solid fa-calendar-day"></i> 24h</button><button class="btn btn-sm btn-primary traffic-range" data-range="7d"><i class="fa-solid fa-calendar-week"></i> 7d</button><button class="btn btn-sm btn-outline-secondary traffic-range" data-range="30d"><i class="fa-solid fa-calendar"></i> 30d</button><button class="btn btn-sm btn-outline-secondary traffic-range" data-range="90d"><i class="fa-solid fa-calendar"></i> 90d</button></div><div class="tab-content preference-tab-content"><div class="tab-pane fade show active" id="trafficSpeedPane" role="tabpanel"><div class="history-card"><div class="history-title">Speed trend</div><canvas id="trafficSpeedChart" class="traffic-chart traffic-chart-line"></canvas></div></div><div class="tab-pane fade" id="trafficTransferPane" role="tabpanel"><div class="history-card"><div class="history-title">Transferred data</div><canvas id="trafficHistoryChart" class="traffic-chart traffic-chart-line"></canvas></div></div></div><div id="trafficHistoryInfo" class="small text-muted mt-2"></div></div></div></div></div>
<div class="modal fade" id="logsModal" tabindex="-1"><div class="modal-dialog modal-xl modal-dialog-scrollable"><div class="modal-content"><div class="modal-header"><h5 class="modal-title"><i class="fa-solid fa-book"></i> Logs</h5><button class="btn-close" data-bs-dismiss="modal"></button></div><div class="modal-body"><div class="operation-log-toolbar"><div class="operation-log-toolbar-main"><button id="refreshOperationLogsBtn" class="btn btn-sm btn-outline-primary" type="button"><i class="fa-solid fa-rotate"></i> Refresh</button><select id="operationLogTypeFilter" class="form-select form-select-sm operation-log-type-filter"><option value="">All types</option></select><input id="operationLogSearch" class="form-control form-control-sm operation-log-search" type="search" placeholder="Search logs..."><span class="small text-muted">Adds, removals, completions and queued operation results.</span></div><label class="form-check form-switch operation-log-hide-jobs operation-log-toolbar-toggle"><input id="operationLogHideJobs" class="form-check-input" type="checkbox" checked><span class="form-check-label">Hide job logs</span></label></div><div id="operationLogsTable" class="mt-3"><span class="spinner-border spinner-border-sm"></span> Loading logs...</div><div id="operationLogsPager" class="pager-row mt-2"></div></div></div></div></div>
<div class="modal fade" id="aboutModal" tabindex="-1" aria-labelledby="aboutModalLabel" aria-hidden="true">
<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>
<button class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="about-hero">
<div class="about-logo"><i class="fa-solid fa-robot"></i></div>
<div>
<h6>pyTorrent</h6>
<p>Lightweight web panel for rTorrent management, queue control and live torrent diagnostics.</p>
</div>
</div>
<dl class="about-list">
<div><dt>Repository</dt><dd><i class="fa-brands fa-git-alt"></i> <a href="https://git.linuxiarz.pl/gru/pyTorrent" target="_blank" rel="noopener noreferrer">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, SQLite</dd></div>
<div><dt>Frontend</dt><dd>Bootstrap, vanilla JavaScript, Chart.js, Font Awesome</dd></div>
<div><dt>Runtime</dt><dd>Gunicorn, systemd, 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, port checker, system status.</dd></div>
</dl>
</div>
</div>
</div>
</div>
<div class="modal fade" id="smartExclusionModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-scrollable modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fa-solid fa-ban"></i> Smart Queue exceptions</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<div class="tool-note mb-3">Select torrents that Smart Queue should ignore. Use search to filter by name, label, status or hash.</div>
<input id="smartExclusionSearch" class="form-control mb-3" type="search" placeholder="Search torrents to exclude...">
<div id="smartExclusionChoiceList" class="smart-exclusion-choice-list"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancel</button>
<button id="smartExclusionSaveBtn" type="button" class="btn btn-primary"><i class="fa-solid fa-floppy-disk"></i> Save exceptions</button>
</div>
</div>
</div>
</div>
<div id="toastHost" class="toast-host"></div>
<script src="{{ frontend_asset_url('socket_io_js') }}"></script>
<script src="{{ frontend_asset_url('bootstrap_js') }}"></script>
<script>window.PYTORRENT = {authEnabled: {{ 1 if auth_enabled else 0 }}, currentUser: {% if current_user %}{{ current_user | tojson }}{% else %}null{% endif %}, activeProfile: {{ active_profile.id if active_profile else 'null' }}, tableColumns: {{ (prefs.table_columns_json or '{}') | safe }}, torrentSort: {{ (prefs.torrent_sort_json or '{}') | safe }}, activeFilter: {{ (prefs.active_filter if prefs and prefs.active_filter else 'all') | tojson }}, detailPanelHeight: {{ prefs.detail_panel_height if prefs and prefs.detail_panel_height else 255 }}, peersRefreshSeconds: {{ prefs.peers_refresh_seconds if prefs else 0 }}, portCheckEnabled: {{ 1 if prefs and prefs.port_check_enabled else 0 }}, interfaceScale: {{ prefs.interface_scale if prefs and prefs.interface_scale else 100 }}, titleSpeedEnabled: {{ 1 if prefs and prefs.title_speed_enabled else 0 }}, trackerFaviconsEnabled: {{ 1 if prefs and prefs.tracker_favicons_enabled else 0 }}, reverseDnsEnabled: {{ 1 if prefs and prefs.reverse_dns_enabled else 0 }}, automationToastsEnabled: {{ 1 if not prefs or prefs.automation_toasts_enabled else 0 }}, smartQueueToastsEnabled: {{ 1 if not prefs or prefs.smart_queue_toasts_enabled else 0 }}, diskMonitorPaths: {{ (prefs.disk_monitor_paths_json or "[]") | safe }}, diskMonitorMode: {{ (prefs.disk_monitor_mode if prefs and prefs.disk_monitor_mode else "default") | tojson }}, diskMonitorSelectedPath: {{ (prefs.disk_monitor_selected_path if prefs and prefs.disk_monitor_selected_path else "") | tojson }}, bootstrapTheme: {{ (prefs.bootstrap_theme if prefs and prefs.bootstrap_theme else 'default') | tojson }}, fontFamily: {{ (prefs.font_family if prefs and prefs.font_family else 'default') | tojson }}, footerItems: {{ (prefs.footer_items_json or '{}') | safe }}, bootstrapThemes: {{ bootstrap_themes | tojson }}, bootstrapThemeUrls: { {% for key in bootstrap_themes.keys() %}{{ key | tojson }}: {{ bootstrap_theme_url(key) | tojson }}{% if not loop.last %}, {% endif %}{% endfor %} }, fontFamilies: {{ font_families | tojson }}};</script>
<!-- Rollback: uncomment the legacy include below and comment the module include. -->
<!-- <script src="{{ static_url('app.js') }}"></script> -->
<script type="module" src="{{ static_url('js/app.js') }}"></script>
</body></html>