Advanced torrent client web UI aimed at power users, with a zero-dependency Node stub server (built-in http + SSE) serving live mock data. - Dense sortable/multi-select torrent grid with live updates - Detail panel: General/Trackers/Peers/Content/Pieces (resizable) - Sidebar filters: status, categories, tags, trackers - Create/delete categories and tags (sidebar + right-click) - Add via magnet or client-side-parsed .torrent upload - RSS, integrated search, and read-only engine views - Hand-drawn SVG icon set, dark theme, keyboard shortcuts Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
48 lines
1.7 KiB
JavaScript
48 lines
1.7 KiB
JavaScript
// Thin API client + SSE live stream.
|
|
|
|
async function jget(url) {
|
|
const r = await fetch(url);
|
|
if (!r.ok) throw new Error(`${url} → ${r.status}`);
|
|
return r.json();
|
|
}
|
|
async function jpost(url, body) {
|
|
const r = await fetch(url, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(body || {}),
|
|
});
|
|
return r.json();
|
|
}
|
|
|
|
export const api = {
|
|
meta: () => jget('/api/meta'),
|
|
snapshot: () => jget('/api/snapshot'),
|
|
torrent: (hash) => jget(`/api/torrents/${hash}`),
|
|
trackers: (hash) => jget(`/api/torrents/${hash}/trackers`),
|
|
peers: (hash) => jget(`/api/torrents/${hash}/peers`),
|
|
files: (hash) => jget(`/api/torrents/${hash}/files`),
|
|
pieces: (hash) => jget(`/api/torrents/${hash}/pieces`),
|
|
|
|
action: (action, hashes, params) => jpost('/api/action', { action, hashes, params }),
|
|
remove: (hashes, deleteFiles) => jpost('/api/delete', { hashes, deleteFiles }),
|
|
add: (opts) => jpost('/api/add', opts),
|
|
toggleAltSpeed: () => jpost('/api/altspeed', {}),
|
|
|
|
createCategory: (name, savePath) => jpost('/api/categories', { name, savePath }),
|
|
deleteCategory: (name) => jpost('/api/categories/delete', { name }),
|
|
createTag: (name) => jpost('/api/tags', { name }),
|
|
deleteTag: (name) => jpost('/api/tags/delete', { name }),
|
|
|
|
rss: () => jget('/api/rss'),
|
|
rssRules: () => jget('/api/rss/rules'),
|
|
search: (q) => jget(`/api/search?q=${encodeURIComponent(q)}`),
|
|
|
|
// Live snapshot stream. onSnapshot(snapshot) called ~1/s.
|
|
stream(onSnapshot, onStatus) {
|
|
const es = new EventSource('/api/stream');
|
|
es.addEventListener('snapshot', (e) => onSnapshot(JSON.parse(e.data)));
|
|
es.onopen = () => onStatus && onStatus('connected');
|
|
es.onerror = () => onStatus && onStatus('reconnecting');
|
|
return es;
|
|
},
|
|
};
|