new ui changes
All checks were successful
Build and Deploy (internal) / Build Transmission Manager Image (push) Successful in 1m27s
Build and Deploy (internal) / Deploy Transmission Manager (internal) (push) Successful in 9s

This commit is contained in:
2026-07-08 19:39:58 -03:00
parent eaade84817
commit 44d2f1a5e9
4 changed files with 139 additions and 24 deletions

View File

@@ -2,10 +2,11 @@
/* ============================================================
Transmission Manager - frontend
Vanilla SPA. Auto-refreshes every 3s with in-place updates.
Vanilla SPA. Auto-refreshes on a selectable interval with in-place updates.
============================================================ */
const REFRESH_MS = 3000;
const DEFAULT_REFRESH_MS = 3000;
const REFRESH_INTERVALS = new Set([1000, 3000, 5000, 10000]);
const TRANSMISSION_STATUSES = {
0: "stopped",
1: "check-wait",
@@ -21,6 +22,7 @@ const state = {
stats: {},
filter: "",
sortKey: "status",
refreshMs: DEFAULT_REFRESH_MS,
lastError: null,
};
@@ -28,6 +30,7 @@ const torrentCards = new Map();
const actionStates = new Map();
let syncInFlight = false;
let syncQueued = false;
let refreshTimer = null;
const $ = (sel) => document.querySelector(sel);
@@ -91,8 +94,34 @@ function setSyncing(on) {
const el = $("#refresh-indicator");
if (!el) return;
el.classList.toggle("syncing", on);
const t = el.querySelector(".refresh-text");
if (t) t.textContent = on ? "syncing" : "live";
updateRefreshMetadata(on);
}
function refreshLabel() {
return `${state.refreshMs / 1000}s`;
}
function updateRefreshMetadata(syncing = false) {
const label = refreshLabel();
const indicator = $("#refresh-indicator");
if (indicator) {
indicator.title = `Auto-refreshes every ${label}`;
indicator.setAttribute(
"aria-label",
syncing ? `Syncing; auto-refresh every ${label}` : `Auto-refresh active every ${label}`,
);
}
const hint = $("#error-hint");
if (hint) hint.textContent = `Retrying in ${label}...`;
}
function setRefreshInterval(ms) {
state.refreshMs = REFRESH_INTERVALS.has(ms) ? ms : DEFAULT_REFRESH_MS;
updateRefreshMetadata(syncInFlight);
if (refreshTimer) clearInterval(refreshTimer);
refreshTimer = setInterval(sync, state.refreshMs);
}
async function runTorrentAction(id, action) {
@@ -217,6 +246,10 @@ function compareName(a, b) {
return (a.name || "").localeCompare(b.name || "");
}
function activitySpeed(t) {
return (t.rateDownload || 0) + (t.rateUpload || 0);
}
function sortTorrents(list) {
const key = state.sortKey;
const sorted = list.slice();
@@ -230,6 +263,11 @@ function sortTorrents(list) {
return (b.uploadRatio || 0) - (a.uploadRatio || 0) || compareName(a, b);
case "size":
return (b.totalSize || 0) - (a.totalSize || 0) || compareName(a, b);
case "speed":
return (activitySpeed(b) - activitySpeed(a))
|| ((b.rateDownload || 0) - (a.rateDownload || 0))
|| ((b.rateUpload || 0) - (a.rateUpload || 0))
|| compareName(a, b);
case "peer100": {
const pa = peerStatus(a);
const pb = peerStatus(b);
@@ -513,6 +551,19 @@ function wireEvents() {
});
});
const refreshSelect = $("#refresh-interval");
if (refreshSelect) {
refreshSelect.value = String(state.refreshMs);
refreshSelect.addEventListener("change", (e) => {
const next = Number(e.target.value);
if (!REFRESH_INTERVALS.has(next)) {
e.target.value = String(state.refreshMs);
return;
}
setRefreshInterval(next);
});
}
$("#torrents").addEventListener("click", (e) => {
const button = e.target.closest("[data-action-button]");
if (!button || button.disabled) return;
@@ -535,8 +586,8 @@ function wireEvents() {
/* ---------- Init ---------- */
function boot() {
wireEvents();
setRefreshInterval(state.refreshMs);
sync();
setInterval(sync, REFRESH_MS);
}
document.addEventListener("DOMContentLoaded", boot);