diff --git a/public/app.js b/public/app.js
index 6b16970..ea037f6 100644
--- a/public/app.js
+++ b/public/app.js
@@ -213,7 +213,10 @@ async function fetchUserDownloads(isInitialLoad = false) {
hideError();
try {
- const url = showAll ? '/api/dashboard/user-downloads?showAll=true' : '/api/dashboard/user-downloads';
+ const params = new URLSearchParams();
+ if (showAll) params.set('showAll', 'true');
+ params.set('refreshRate', currentRefreshRate);
+ const url = '/api/dashboard/user-downloads?' + params.toString();
const response = await fetch(url);
const data = await response.json();
@@ -624,7 +627,6 @@ function renderStatusPanel(data, panel) {
const uptime = `${hrs}h ${mins}m ${secs}s`;
const totalKB = (data.cache.totalSizeBytes / 1024).toFixed(1);
- const refreshLabel = currentRefreshRate > 0 ? (currentRefreshRate / 1000) + 's' : 'Off';
let html = `
-
Polling
-
Mode${data.polling.enabled ? 'Background' : 'On-demand'}
- ${data.polling.enabled ? `
Interval${data.polling.intervalMs / 1000}s
` : ''}
-
Client refresh${refreshLabel}
-
`;
+ Data Refresh
`;
+
+ const pollIntervalMs = data.polling.intervalMs;
+ const clients = data.clients || [];
+ const activeRefreshers = clients.filter(c => c.refreshRateMs > 0);
+ const fastestClient = activeRefreshers.length > 0
+ ? activeRefreshers.reduce((min, c) => c.refreshRateMs < min.refreshRateMs ? c : min)
+ : null;
+ const hasForegroundClient = fastestClient && data.polling.enabled && fastestClient.refreshRateMs < pollIntervalMs;
+
+ if (data.polling.enabled) {
+ html += `Background poll${pollIntervalMs / 1000}s
`;
+ } else {
+ html += `Background pollDisabled
`;
+ }
+
+ if (hasForegroundClient) {
+ html += `Effective modeForeground ${fastestClient.refreshRateMs / 1000}s
`;
+ } else if (activeRefreshers.length > 0) {
+ html += `Effective mode${data.polling.enabled ? 'Background' : 'On-demand'}
`;
+ } else {
+ html += `Effective modeIdle (no active clients)
`;
+ }
+
+ html += `Active clients${clients.length}
`;
+ for (const c of clients) {
+ const rate = c.refreshRateMs > 0 ? (c.refreshRateMs / 1000) + 's' : 'Off';
+ const age = Math.round((Date.now() - c.lastSeen) / 1000);
+ html += `${escapeHtml(c.user)}${rate} (${age}s ago)
`;
+ }
+
+ html += ``;
// Poll timings card
const lp = data.polling.lastPoll;
diff --git a/public/style.css b/public/style.css
index 74e7bed..b1d0188 100644
--- a/public/style.css
+++ b/public/style.css
@@ -862,6 +862,25 @@ body {
font-size: 0.7rem;
}
+.status-fg-badge {
+ background: #fff3e0;
+ color: #e65100;
+ padding: 1px 8px;
+ border-radius: 8px;
+ font-size: 0.75rem;
+ font-weight: 600;
+}
+
+.status-row-sub {
+ padding-left: 12px;
+ font-size: 0.75rem;
+ opacity: 0.8;
+}
+
+.status-row-sub span:first-child {
+ font-style: italic;
+}
+
.status-timings {
display: flex;
flex-direction: column;
diff --git a/server/routes/dashboard.js b/server/routes/dashboard.js
index 640d57b..f8dd4b9 100644
--- a/server/routes/dashboard.js
+++ b/server/routes/dashboard.js
@@ -90,6 +90,19 @@ function getRadarrLink(movie) {
return `${movie._instanceUrl}/movie/${movie.titleSlug}`;
}
+// Track active dashboard clients: Map
+const activeClients = new Map();
+const CLIENT_STALE_MS = 30000; // consider client gone after 30s of no requests
+
+function getActiveClients() {
+ const now = Date.now();
+ // Prune stale clients
+ for (const [key, client] of activeClients.entries()) {
+ if (now - client.lastSeen > CLIENT_STALE_MS) activeClients.delete(key);
+ }
+ return Array.from(activeClients.values());
+}
+
// Get user downloads for authenticated user
router.get('/user-downloads', async (req, res) => {
try {
@@ -106,6 +119,15 @@ router.get('/user-downloads', async (req, res) => {
const showAll = isAdmin && req.query.showAll === 'true';
console.log(`[Dashboard] Serving downloads for user: ${user.name} (${username}), isAdmin: ${isAdmin}, showAll: ${showAll}`);
+ // Track this client's refresh rate
+ const clientRefreshRate = parseInt(req.query.refreshRate, 10);
+ if (clientRefreshRate > 0) {
+ activeClients.set(username, { user: user.name, refreshRateMs: clientRefreshRate, lastSeen: Date.now() });
+ } else {
+ // Client has refresh off or didn't send — still mark as seen but with no rate
+ activeClients.set(username, { user: user.name, refreshRateMs: 0, lastSeen: Date.now() });
+ }
+
// When polling is disabled, fetch on-demand if cache has expired
// The fetched data is cached (30s TTL) so subsequent requests from any user reuse it
if (!POLLING_ENABLED && !cache.get('poll:sab-queue')) {
@@ -628,7 +650,8 @@ router.get('/status', (req, res) => {
intervalMs: POLLING_ENABLED ? require('../utils/poller').POLL_INTERVAL : 0,
lastPoll: getLastPollTimings()
},
- cache: cacheStats
+ cache: cacheStats,
+ clients: getActiveClients()
});
} catch (err) {
res.status(500).json({ error: 'Failed to get status', details: err.message });