spill 0.5.2

Modern torrent client
Documentation
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<meta name="theme-color" content="#172b46">
<title>Spill — Transfers</title>
<link rel="icon" href="/logo.svg" type="image/svg+xml">
<script>
document.documentElement.dataset.theme=localStorage.getItem('spill-theme')||(matchMedia('(prefers-color-scheme: dark)').matches?'dark':'light');
</script>
<link rel="stylesheet" href="/style.css">
</head>
<body>
<svg style="display:none" aria-hidden="true"><defs>
<symbol id="grid" viewBox="0 0 24 24"><rect x="3" y="3" width="7" height="7" rx="1.5"/><rect x="14" y="3" width="7" height="7" rx="1.5"/><rect x="3" y="14" width="7" height="7" rx="1.5"/><rect x="14" y="14" width="7" height="7" rx="1.5"/></symbol>
<symbol id="down" viewBox="0 0 24 24"><path d="M12 4v15m-5-5 5 5 5-5M4 21h16"/></symbol><symbol id="up" viewBox="0 0 24 24"><path d="M12 20V5m-5 5 5-5 5 5M4 3h16"/></symbol>
<symbol id="pause" viewBox="0 0 24 24"><path d="M9 5v14M15 5v14"/></symbol><symbol id="play" viewBox="0 0 24 24"><path d="m8 5 11 7-11 7Z"/></symbol>
<symbol id="clock" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8"/><path d="M12 7v5l3 2"/></symbol><symbol id="alert" viewBox="0 0 24 24"><circle cx="12" cy="12" r="8"/><path d="M12 7v6m0 3v.2"/></symbol>
<symbol id="server" viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="6" rx="2"/><rect x="3" y="14" width="18" height="6" rx="2"/><path d="M7 7h.01M7 17h.01m4-10h6m-6 10h6"/></symbol>
<symbol id="plus" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></symbol><symbol id="search" viewBox="0 0 24 24"><circle cx="10.5" cy="10.5" r="6.5"/><path d="m16 16 4 4"/></symbol>
<symbol id="refresh" viewBox="0 0 24 24"><path d="M20 10a8 8 0 1 0-2 8M20 4v6h-6"/></symbol><symbol id="trash" viewBox="0 0 24 24"><path d="M4 6h16M9 6V3h6v3M6 6l1 15h10l1-15M10 10v7m4-7v7"/></symbol>
<symbol id="file" viewBox="0 0 24 24"><path d="M14 3H6v18h12V7Zm0 0v5h4M9 12h6m-6 4h6"/></symbol><symbol id="close" viewBox="0 0 24 24"><path d="m6 6 12 12M6 18 18 6"/></symbol>
<symbol id="peers" viewBox="0 0 24 24"><circle cx="9" cy="8" r="3"/><path d="M3 20v-2a6 6 0 0 1 12 0v2m1-15a3 3 0 0 1 0 6m3 9v-2a6 6 0 0 0-3-5"/></symbol><symbol id="link" viewBox="0 0 24 24"><path d="m10 14 4-4m-5-1 3-3a4 4 0 0 1 6 6l-3 3m-6-6-3 3a4 4 0 0 0 6 6l3-3"/></symbol>
<symbol id="dots" viewBox="0 0 24 24"><circle cx="5" cy="12" r=".6"/><circle cx="12" cy="12" r=".6"/><circle cx="19" cy="12" r=".6"/></symbol>
</defs></svg>
<div class="app">
<aside class="sidebar">
<div class="brand"><img src="/logo.svg" alt="" width="28" height="28"><span>spill</span></div>
<div class="workspace"><div class="server-icon"><svg><use href="#server"/></svg></div><div><strong>Spill daemon</strong><div class="tiny"><span id="sidebar-status">Connecting…</span></div></div></div>
<div class="section-label">Library</div><nav class="nav" aria-label="Transfer filters" id="navigation"></nav>
<div class="sidebar-bottom"><div class="tiny">Local web interface · 1s refresh</div></div>
</aside>
<main class="main">
<header class="topbar"><div class="breadcrumb">Spill daemon <span>/</span><span>Transfers</span></div><div class="top-actions"><button class="secondary" id="theme-button" type="button" aria-pressed="false">Dark mode</button><span class="status-badge">CONNECTING</span><button class="primary" id="add-button"><svg><use href="#plus"/></svg>Add torrent</button></div></header>
<section class="overview" aria-label="Session transfer rates"><div class="stats">
<div class="stat"><div class="stat-label"><svg class="download-color"><use href="#down"/></svg>Download</div><div class="stat-value" id="download-stat">—</div></div>
<div class="stat"><div class="stat-label"><svg class="upload-color"><use href="#up"/></svg>Upload</div><div class="stat-value" id="upload-stat">—</div></div>
<div class="stat"><div class="stat-label"><svg><use href="#peers"/></svg>Connected peers</div><div class="stat-value" id="peers-stat">—</div></div>
</div></section>
<section class="content" aria-label="Torrent library">
<div class="toolbar"><div class="toolbar-left"><span class="toolbar-title" id="list-title">All transfers</span><div class="divider"></div><div class="tool-buttons">
<button class="icon-button" id="resume-button" title="Resume selected" aria-label="Resume selected"><svg><use href="#play"/></svg></button>
<button class="icon-button" id="pause-button" title="Pause selected" aria-label="Pause selected"><svg><use href="#pause"/></svg></button>
<button class="icon-button" id="recheck-button" title="Recheck selected" aria-label="Recheck selected"><svg><use href="#refresh"/></svg></button>
<button class="icon-button" id="reannounce-button" title="Reannounce selected" aria-label="Reannounce selected"><svg><use href="#link"/></svg></button>
<button class="icon-button" id="remove-button" title="Remove selected" aria-label="Remove selected"><svg><use href="#trash"/></svg></button>
</div></div><label class="search"><svg><use href="#search"/></svg><input id="search" placeholder="Search torrents…" aria-label="Search torrents"><kbd>/</kbd></label></div>
<div class="table-scroll"><table><thead><tr>
<th><input type="checkbox" id="select-all" aria-label="Select all visible torrents"></th>
<th><button data-sort="name">Name <span id="sort-name"></span></button></th>
<th><button data-sort="size">Size <span id="sort-size"></span></button></th>
<th><button data-sort="progress">Progress <span id="sort-progress"></span></button></th>
<th><button data-sort="state">Status <span id="sort-state"></span></button></th>
<th><button data-sort="downloaded_bytes">Downloaded <span id="sort-downloaded_bytes"></span></button></th>
<th><button data-sort="uploaded_bytes">Uploaded <span id="sort-uploaded_bytes"></span></button></th>
<th><button data-sort="down">↓ /s <span id="sort-down"></span></button></th>
<th><button data-sort="up">↑ /s <span id="sort-up"></span></button></th>
<th>Peers</th><th>ETA</th><th>Discovery</th><th></th>
</tr></thead><tbody id="torrents"></tbody></table><div id="empty" class="empty" hidden>No transfers match your search.</div></div>
<div class="table-footer"><span id="visible-count">0 transfers</span><span>Waiting for daemon</span></div>
</section>
<section hidden class="inspector" id="inspector" aria-label="Selected torrent details">
<div class="detail-resizer" id="detail-resizer" role="separator" aria-label="Resize torrent details" aria-orientation="horizontal" aria-controls="detail-body" aria-valuemin="60" aria-valuemax="245" aria-valuenow="245" tabindex="0" title="Drag to resize · Arrow keys to adjust · Double-click to reset"></div>
<div class="detail-header"><div class="tabs" role="tablist" aria-label="Torrent details">
<button class="tab active" role="tab" data-tab="overview" aria-selected="true">Overview</button><button class="tab" role="tab" data-tab="files" aria-selected="false">Files</button><button class="tab" role="tab" data-tab="peers" aria-selected="false">Peers</button><button class="tab" role="tab" data-tab="sources" aria-selected="false">Sources</button>
</div><button class="secondary" id="limits-button">Rate limits</button><button class="secondary" id="seeding-button">Seeding rules</button><button class="icon-button" id="close-detail" aria-label="Close details"><svg><use href="#close"/></svg></button></div>
<div class="detail-body" id="detail-body"></div>
</section>
<footer class="bottom-bar"><div class="left"><span>Local interface</span><span class="optional"><svg><use href="#link"/></svg><span id="server-address"></span></span></div><span id="connection-status">Connecting…</span></footer>
</main></div>
<dialog id="add-dialog"><form id="add-form"><h2>Add torrent</h2><p>Add a magnet link or a .torrent file. Downloads are managed by the local daemon.</p>
<label class="field" for="magnet">Magnet link</label><input type="text" id="magnet" placeholder="magnet:?xt=urn:btih:…">
<label class="upload-area"><svg><use href="#file"/></svg>Or choose a .torrent file<input type="file" id="torrent-file" accept=".torrent"></label>
<label class="field" for="output">Download location</label><input type="text" id="output" placeholder="Daemon default (or absolute path)">
<div class="options"><label><input type="checkbox" id="start-now" checked> Start immediately</label></div>
<div class="options"><label><input type="checkbox" id="allow-trackers" checked> Trackers</label><label><input type="checkbox" id="allow-dht" checked> DHT</label><label><input type="checkbox" id="allow-web" checked> Web seeds</label></div>
<p class="telemetry-note">Discovery follows daemon policy. Unresolved magnets may disclose their hash before privacy is known.</p>
<div class="actions"><button type="button" class="secondary" id="cancel-add">Cancel</button><button class="primary" type="submit">Add torrent</button></div></form></dialog>
<dialog id="remove-dialog" aria-labelledby="remove-title" aria-describedby="remove-description"><h2 id="remove-title">Remove from Spill?</h2><p id="remove-description"></p><ul class="remove-transfers" id="remove-transfers"></ul><label><input type="checkbox" id="delete-data"> Also delete downloaded files</label><div class="actions"><button class="secondary" id="cancel-remove">Keep transfers</button><button class="primary danger" id="confirm-remove">Remove</button></div></dialog>
<dialog id="limits-dialog"><form id="limits-form"><h2>Payload rate limits</h2><p>Bytes per second. 0 means unlimited. Limits apply to this torrent.</p>
<label class="field" for="limit-down">Download bytes/s</label><input id="limit-down" type="number" min="0" max="9007199254740991" step="1" required>
<label class="field" for="limit-up">Upload bytes/s</label><input id="limit-up" type="number" min="0" max="9007199254740991" step="1" required>
<div class="actions"><button type="button" class="secondary" id="cancel-limits">Cancel</button><button class="primary" type="submit">Save limits</button></div></form></dialog>
<dialog id="seeding-dialog"><form id="seeding-form"><h2>Seeding stop rules</h2><p>Pause when either goal is reached. 0 disables a rule. Ratio is uploaded payload divided by torrent size. Active seeding time excludes pause and downtime; totals survive restart.</p>
<label class="field" for="seed-ratio">Seed ratio</label><input id="seed-ratio" type="number" min="0" step="any" required>
<label class="field" for="seed-time">Active seeding seconds</label><input id="seed-time" type="number" min="0" max="9007199254740991" step="1" required>
<div class="actions"><button type="button" class="secondary" id="cancel-seeding">Cancel</button><button class="primary" type="submit">Save rules</button></div></form></dialog>
<div class="toast" id="toast" role="status" hidden></div>
<script>const token='__TOKEN__';</script>
<script src="/details.js" defer></script>
<script src="/app.js" defer></script>
</body></html>