Files
i2pd/contrib/webconsole/torrent_client.html
T
2026-08-30 13:02:00 +03:00

272 lines
9.0 KiB
HTML

<fieldset>
<legend>Torrent Client</legend>
<div id="torrent-ui">
<h3 id="connection_status">_</h3>
<input type="file" id="torrentf" accept=".torrent" style="display: none" />
<button onclick="document.getElementById('torrentf').click()">
Add torrent
</button>
<button onclick="stopAll()">
stopAll
</button>
<button onclick="startAll()">
startAll
</button>
<ul id="torrents"></ul>
</div>
</fieldset>
<div id="torrent_details_modal" style="display: none">
<h3 id="modal_torrent_title"></h3>
<h4>Files:</h4>
<ul id="modal_files_list"></ul>
<h4>Peers (<span id="peer_count">0</span>):</h4>
<ul id="modal_peers_list"></ul>
<h4>Trackers (<span id="trackers_count">0</span>):</h4>
<ul id="modal_trackers_list"></ul>
<h4>trackerStats:</h4>
<pre id="modal_tracker_stats"></pre>
<button onclick="closeDetailsModal()">Close</button>
</div>
<script>
let currentTorrentsData = [];
async function stopAll()
{
const conf = confirm("Are sure to stop all torrents?");
// console.log(conf)
if(conf && window.tjs)
{
const r = await window.tjs.getTorrents()
//console.log(`torrents: ${r}`)
if (!r?.result?.torrents) return;
r.result.torrents.forEach(async (t) => {
console.log(`stop torrent => ${t.id}`)
await window.tjs.stopTorrent([t.id]);
});
}
//if (window.tjs && conf) await window.tjs.stopTorrent();
}
async function startAll()
{
const conf = confirm("Are sure to start all torrents?");
if(conf && window.tjs)
{
const r = await window.tjs.getTorrents()
//console.log(`torrents: ${r}`)
if (!r?.result?.torrents) return;
r.result.torrents.forEach( async (t) =>{
console.log(`start torrent => ${t.id}`)
await window.tjs.startTorrent([t.id]);
});
}
//if (window.tjs && conf) await window.tjs.startTorrent();
}
setInterval(async () => {
const statusIndicator = document.getElementById("connection_status");
if (!window.tjs)
return (statusIndicator.textContent = "STATUS: TJS_NOT_LOADED");
try {
const r = await window.tjs.getTorrents();
if (!r?.result?.torrents)
return (statusIndicator.textContent = "STATUS: INVALID_RESPONSE");
statusIndicator.textContent = "STATUS: Connected";
statusIndicator.style.color = "#00ff66";
const torrents = document.getElementById("torrents");
currentTorrentsData = r.result.torrents;
if (currentTorrentsData.length === 0) {
torrents.innerHTML = "<li>No downloads</li>";
return;
}
torrents.innerHTML = "";
currentTorrentsData.forEach((el) => {
const percent = el.percentDone
? (el.percentDone * 100).toFixed(2)
: "0.00";
const speed = window.tjs.formatBytes
? window.tjs.formatBytes(el.rateDownload)
: `${el.rateDownload} B/s`;
const speedUp = window.tjs.formatBytes
? window.tjs.formatBytes(el.rateUpload)
: `${el.rateUpload} B/s`;
const status = window.tjs.getStatusString
? window.tjs.getStatusString(el.status)
: el.status;
const totalSize = el.totalSize || 0; // bytes
const ETA = el.rateDownload
? (
(totalSize * (1 - el.percentDone)) /
el.rateDownload /
60
).toFixed(1) + " min"
: "-";
const name = el.name || "|unknown name, maybe a bad torrent|";
const li = document.createElement("li");
const span = document.createElement("span");
span.textContent = `${name}${percent}% [${status}] (⬇ ${speed} | ⬆ ${speedUp}) ---- size = ${(totalSize / 1024 / 1024).toFixed(2)}mb; ETA: ${ETA} `;
li.appendChild(span);
const butDetails = document.createElement("button");
butDetails.textContent = "Details";
butDetails.onclick = () => openTorrentDetails(el.id);
li.appendChild(butDetails);
const butStop = document.createElement("button");
butStop.textContent = "Stop";
butStop.onclick = async () => {
if (window.tjs) await window.tjs.stopTorrent([el.id]);
};
li.appendChild(butStop);
const butStart = document.createElement("button");
butStart.textContent = "Start";
butStart.onclick = async () => {
if (window.tjs) await window.tjs.startTorrent([el.id]);
};
li.appendChild(butStart);
const butRemove = document.createElement("button");
butRemove.textContent = "✕";
butRemove.onclick = () => removeTorrent(el.id);
li.appendChild(butRemove);
torrents.appendChild(li);
});
} catch (err) {
statusIndicator.textContent =
"STATUS: ERROR_WITH_DATA (f12, check your console and write issue)";
statusIndicator.style.color = "#ff3333";
}
}, 1000);
document.getElementById("torrentf").onchange = (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = async () => {
const bytes = new Uint8Array(reader.result);
let binary = "";
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
const b64 = btoa(binary);
if (window.tjs) await window.tjs.addTorrent(b64);
e.target.value = "";
};
reader.readAsArrayBuffer(file);
};
window.removeTorrent = async (id) => {
const confirmed = confirm(`Drop the torrent with id ${id}?`);
if (confirmed && window.tjs) await window.tjs.removeTorrent(Number(id));
};
window.openTorrentDetails = (id) => {
const torrent = currentTorrentsData.find(
(t) => String(t.id) === String(id),
);
if (!torrent) return;
document.getElementById("modal_torrent_title").textContent =
`DETAILS: ${torrent.name} (#${torrent.id})`;
const filesList = document.getElementById("modal_files_list");
filesList.innerHTML = "";
if (torrent.files?.length) {
torrent.files.forEach((f) => {
const li = document.createElement("li");
const completedMB = (f.bytesCompleted / 1048576).toFixed(2);
const totalMB = (f.length / 1048576).toFixed(2);
const percent = ((f.bytesCompleted / f.length) * 100 || 0).toFixed(1);
li.textContent = `${f.name}${completedMB} / ${totalMB} MB (${percent}%)`;
filesList.appendChild(li);
});
} else {
const li = document.createElement("li");
li.textContent = "NO_FILES";
filesList.appendChild(li);
}
const peersList = document.getElementById("modal_peers_list");
const trackersList = document.getElementById("modal_trackers_list");
document.getElementById("peer_count").textContent = torrent.peers
? torrent.peers.length
: 0;
peersList.innerHTML = "";
if (torrent.peers?.length) {
peersList.append(
...torrent.peers.map((p) => {
const li = document.createElement("li");
const downSpeed = (p.rateToClient / 1024).toFixed(2);
const upSpeed = (p.rateToPeer / 1024).toFixed(2);
const client = p.clientName || "Unk";
li.textContent = `${p.address} (${client}) — ${downSpeed} KB/s — ${upSpeed} KB/s`;
return li;
}),
);
} else {
const li = document.createElement("li");
li.textContent = "NO_PEERS";
peersList.appendChild(li);
}
trackersList.innerHTML = "";
if (torrent.trackers?.length) {
trackersList.append(
...torrent.trackers.map((p) => {
const li = document.createElement("li");
li.textContent = `[id:${p.id}] announce: ${p.announce} ; tier - ${p.tier}`;
return li;
}),
);
} else {
const li = document.createElement("li");
li.textContent = "NO_TRACKERS";
trackersList.appendChild(li);
}
document.getElementById("trackers_count").textContent = torrent.trackers
? torrent.trackers.length
: 0;
const modal_TrackerStats = document.getElementById("modal_tracker_stats");
const tstats = torrent.trackerStats;
/*
To convert a Unix timestamp to a human-readable date in JavaScript, multiply the timestamp by 1000 and pass it into the new Date() constructor. JavaScript requires milliseconds, while standard Unix timestamps are measured in second
(so, for js we need ms)
*/
modal_TrackerStats.textContent = tstats
? tstats
.map(
(tr) => {
const leachers = tr.leecherCount >= 0 ? tr.leecherCount : '?'; // TODO: why so big value
return `
Host: ${tr.host}
Announce: ${tr.announce}
Status: ${tr.lastAnnounceResult} (${tr.lastAnnounceSucceeded ? "OK" : "Fail"})
Peers: ${tr.lastAnnouncePeerCount} | Seeders: ${tr.seederCount} | Leechers: ${leachers}
Last Announce: ${new Date(tr.lastAnnounceTime * 1000).toLocaleString()}
Next Announce: ${new Date(tr.nextAnnounceTime * 1000).toLocaleString()}
`}).join("\n" + "-".repeat(40) + "<hr/>\n") : "-";
document.getElementById("torrent_details_modal").style.display = "block";
};
window.closeDetailsModal = () => {
document.getElementById("torrent_details_modal").style.display = "none";
};
</script>