feat: status page shows effective refresh mode across all active clients

- Server tracks each client's refresh rate via query param on /user-downloads
- Active clients expire after 30s of no requests
- Status panel 'Data Refresh' card shows:
  - Background poll interval (or Disabled)
  - Effective mode: Background if all clients >= poll rate,
    Foreground (with rate) if any client is faster, Idle if no clients
  - Active client list with per-user refresh rate and last-seen age
- Foreground mode shown with orange badge for visibility
- Client refresh rate sent on every dashboard request
This commit is contained in:
2026-05-16 00:00:08 +01:00
parent 57e1db18e2
commit 6e3a98ae75
3 changed files with 79 additions and 8 deletions

View File

@@ -90,6 +90,19 @@ function getRadarrLink(movie) {
return `${movie._instanceUrl}/movie/${movie.titleSlug}`;
}
// Track active dashboard clients: Map<username, { refreshRateMs, lastSeen }>
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 });