adding velocity control
This commit is contained in:
99
web/app.js
99
web/app.js
@@ -20,6 +20,7 @@ const TRANSMISSION_STATUSES = {
|
||||
const state = {
|
||||
torrents: [],
|
||||
stats: {},
|
||||
session: null,
|
||||
filter: "",
|
||||
sortKey: "status",
|
||||
refreshMs: DEFAULT_REFRESH_MS,
|
||||
@@ -31,6 +32,8 @@ const actionStates = new Map();
|
||||
let syncInFlight = false;
|
||||
let syncQueued = false;
|
||||
let refreshTimer = null;
|
||||
let speedControlPending = false;
|
||||
let speedControlError = null;
|
||||
|
||||
const $ = (sel) => document.querySelector(sel);
|
||||
|
||||
@@ -63,6 +66,11 @@ async function loadStats() {
|
||||
state.stats = data || {};
|
||||
}
|
||||
|
||||
async function loadSession() {
|
||||
const data = await fetchJSON("/api/session");
|
||||
state.session = data && typeof data === "object" ? data : null;
|
||||
}
|
||||
|
||||
async function sync() {
|
||||
if (syncInFlight) {
|
||||
syncQueued = true;
|
||||
@@ -72,8 +80,9 @@ async function sync() {
|
||||
syncInFlight = true;
|
||||
setSyncing(true);
|
||||
try {
|
||||
await Promise.all([loadTorrents(), loadStats()]);
|
||||
await Promise.all([loadTorrents(), loadStats(), loadSession()]);
|
||||
state.lastError = null;
|
||||
speedControlError = null;
|
||||
} catch (err) {
|
||||
state.lastError = err.message || String(err);
|
||||
} finally {
|
||||
@@ -143,6 +152,28 @@ async function runTorrentAction(id, action) {
|
||||
}
|
||||
}
|
||||
|
||||
async function setAlternativeSpeed(enabled) {
|
||||
if (speedControlPending || !state.session) return;
|
||||
speedControlPending = true;
|
||||
speedControlError = null;
|
||||
renderHeader();
|
||||
|
||||
try {
|
||||
await fetchJSON("/api/session/alternative-speed", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ enabled }),
|
||||
});
|
||||
await sync();
|
||||
speedControlPending = false;
|
||||
renderHeader();
|
||||
} catch (err) {
|
||||
speedControlError = err.message || String(err);
|
||||
speedControlPending = false;
|
||||
renderHeader();
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------- Formatting helpers ---------- */
|
||||
function humanSize(bytes) {
|
||||
if (bytes == null || isNaN(bytes) || bytes < 0) return "—";
|
||||
@@ -337,6 +368,64 @@ function renderHeader() {
|
||||
$("#stat-torrents").textContent = String(torrents.length);
|
||||
$("#stat-dl").textContent = humanSpeed(dl);
|
||||
$("#stat-ul").textContent = humanSpeed(ul);
|
||||
renderSpeedControls();
|
||||
}
|
||||
|
||||
function sessionValue(session, kebab, snake) {
|
||||
if (!session) return null;
|
||||
if (session[kebab] != null) return session[kebab];
|
||||
return snake ? session[snake] : null;
|
||||
}
|
||||
|
||||
function sessionSpeedBytes(session) {
|
||||
const units = session && session.units;
|
||||
const value = units && (units["speed-bytes"] ?? units.speed_bytes ?? units.speedBytes);
|
||||
return Number.isFinite(Number(value)) && Number(value) > 0 ? Number(value) : 1000;
|
||||
}
|
||||
|
||||
function activeSpeedCap(session, direction) {
|
||||
const enabled = Boolean(sessionValue(session, "alt-speed-enabled", "alt_speed_enabled"));
|
||||
const alt = direction === "down"
|
||||
? sessionValue(session, "alt-speed-down", "alt_speed_down")
|
||||
: sessionValue(session, "alt-speed-up", "alt_speed_up");
|
||||
const normalEnabled = Boolean(sessionValue(
|
||||
session,
|
||||
direction === "down" ? "speed-limit-down-enabled" : "speed-limit-up-enabled",
|
||||
direction === "down" ? "speed_limit_down_enabled" : "speed_limit_up_enabled",
|
||||
));
|
||||
const normal = direction === "down"
|
||||
? sessionValue(session, "speed-limit-down", "speed_limit_down")
|
||||
: sessionValue(session, "speed-limit-up", "speed_limit_up");
|
||||
const value = enabled ? alt : (normalEnabled ? normal : null);
|
||||
const number = Number(value);
|
||||
return Number.isFinite(number) && number > 0 ? number * sessionSpeedBytes(session) : null;
|
||||
}
|
||||
|
||||
function renderSpeedControls() {
|
||||
const toggle = $("#alt-speed-toggle");
|
||||
const toggleState = $("#alt-speed-state");
|
||||
const status = $("#speed-control-status");
|
||||
if (!toggle || !toggleState || !status) return;
|
||||
|
||||
const session = state.session;
|
||||
const enabled = Boolean(sessionValue(session, "alt-speed-enabled", "alt_speed_enabled"));
|
||||
toggle.disabled = !session || speedControlPending;
|
||||
toggle.setAttribute("aria-pressed", String(enabled));
|
||||
toggle.classList.toggle("is-saving", speedControlPending);
|
||||
setText(toggleState, !session ? (state.lastError ? "Unavailable" : "Loading...") : (speedControlPending ? "Saving..." : (enabled ? "On" : "Off")));
|
||||
|
||||
const caps = [
|
||||
{ id: "speed-cap-down", value: activeSpeedCap(session, "down") },
|
||||
{ id: "speed-cap-up", value: activeSpeedCap(session, "up") },
|
||||
];
|
||||
for (const cap of caps) {
|
||||
const el = $("#" + cap.id);
|
||||
if (!el) continue;
|
||||
el.classList.toggle("hidden", cap.value == null);
|
||||
const valueEl = el.querySelector("strong");
|
||||
if (valueEl && cap.value != null) setText(valueEl, humanSpeed(cap.value));
|
||||
}
|
||||
setText(status, speedControlError || "");
|
||||
}
|
||||
|
||||
function initial(name) {
|
||||
@@ -611,6 +700,14 @@ function wireEvents() {
|
||||
});
|
||||
}
|
||||
|
||||
const speedToggle = $("#alt-speed-toggle");
|
||||
if (speedToggle) {
|
||||
speedToggle.addEventListener("click", () => {
|
||||
const enabled = !Boolean(sessionValue(state.session, "alt-speed-enabled", "alt_speed_enabled"));
|
||||
setAlternativeSpeed(enabled);
|
||||
});
|
||||
}
|
||||
|
||||
$("#torrents").addEventListener("click", (e) => {
|
||||
const button = e.target.closest("[data-action-button]");
|
||||
if (!button || button.disabled) return;
|
||||
|
||||
Reference in New Issue
Block a user