diff --git a/backend/routes/api.js b/backend/routes/api.js
index 7cbe0f5..a3e7ebb 100644
--- a/backend/routes/api.js
+++ b/backend/routes/api.js
@@ -909,6 +909,83 @@ router.post("/setTaskSettings", async (req, res) => {
}
});
+// Get Activity Monitor Polling Settings
+router.get("/getActivityMonitorSettings", async (req, res) => {
+ try {
+ const settingsjson = await db.query('SELECT settings FROM app_config where "ID"=1').then((res) => res.rows);
+
+ if (settingsjson.length > 0) {
+ const settings = settingsjson[0].settings || {};
+ console.log(settings);
+ const pollingSettings = settings.ActivityMonitorPolling || {
+ activeSessionsInterval: 1000,
+ idleInterval: 5000
+ };
+ res.send(pollingSettings);
+ } else {
+ res.status(404);
+ res.send({ error: "Settings Not Found" });
+ }
+ } catch (error) {
+ res.status(503);
+ res.send({ error: "Error: " + error });
+ }
+});
+
+// Set Activity Monitor Polling Settings
+router.post("/setActivityMonitorSettings", async (req, res) => {
+ const { activeSessionsInterval, idleInterval } = req.body;
+
+ if (activeSessionsInterval === undefined || idleInterval === undefined) {
+ res.status(400);
+ res.send("activeSessionsInterval and idleInterval are required");
+ return;
+ }
+
+ if (!Number.isInteger(activeSessionsInterval) || activeSessionsInterval <= 0) {
+ res.status(400);
+ res.send("A valid activeSessionsInterval(int) which is > 0 milliseconds is required");
+ return;
+ }
+
+ if (!Number.isInteger(idleInterval) || idleInterval <= 0) {
+ res.status(400);
+ res.send("A valid idleInterval(int) which is > 0 milliseconds is required");
+ return;
+ }
+
+ if (activeSessionsInterval > idleInterval) {
+ res.status(400);
+ res.send("activeSessionsInterval should be <= idleInterval for optimal performance");
+ return;
+ }
+
+ try {
+ const settingsjson = await db.query('SELECT settings FROM app_config where "ID"=1').then((res) => res.rows);
+
+ if (settingsjson.length > 0) {
+ const settings = settingsjson[0].settings || {};
+
+ settings.ActivityMonitorPolling = {
+ activeSessionsInterval: activeSessionsInterval,
+ idleInterval: idleInterval
+ };
+
+ let query = 'UPDATE app_config SET settings=$1 where "ID"=1';
+ await db.query(query, [settings]);
+
+ res.status(200);
+ res.send(settings.ActivityMonitorPolling);
+ } else {
+ res.status(404);
+ res.send({ error: "Settings Not Found" });
+ }
+ } catch (error) {
+ res.status(503);
+ res.send({ error: "Error: " + error });
+ }
+});
+
//Jellystat functions
router.get("/CheckForUpdates", async (req, res) => {
try {
diff --git a/backend/tasks/ActivityMonitor.js b/backend/tasks/ActivityMonitor.js
index 43f1969..62c903b 100644
--- a/backend/tasks/ActivityMonitor.js
+++ b/backend/tasks/ActivityMonitor.js
@@ -114,20 +114,70 @@ function getWatchDogNotInSessions(SessionData, WatchdogData) {
return removedData;
}
-async function ActivityMonitor(interval) {
- // console.log("Activity Interval: " + interval);
+let currentIntervalId = null;
+let lastHadActiveSessions = false;
+let cachedPollingSettings = {
+ activeSessionsInterval: 1000,
+ idleInterval: 5000
+};
- setInterval(async () => {
+async function ActivityMonitor(defaultInterval) {
+ // console.log("Activity Monitor started with default interval: " + defaultInterval);
+
+ const runMonitoring = async () => {
try {
const config = await new configClass().getConfig();
if (config.error || config.state !== 2) {
return;
}
+
+ // Get adaptive polling settings from config
+ const pollingSettings = config.settings?.ActivityMonitorPolling || {
+ activeSessionsInterval: 1000,
+ idleInterval: 5000
+ };
+
+ // Check if polling settings have changed
+ const settingsChanged =
+ cachedPollingSettings.activeSessionsInterval !== pollingSettings.activeSessionsInterval ||
+ cachedPollingSettings.idleInterval !== pollingSettings.idleInterval;
+
+ if (settingsChanged) {
+ console.log('[ActivityMonitor] Polling settings changed, updating intervals');
+ console.log('Old settings:', cachedPollingSettings);
+ console.log('New settings:', pollingSettings);
+ cachedPollingSettings = { ...pollingSettings };
+ }
+
const ExcludedUsers = config.settings?.ExcludedUsers || [];
const apiSessionData = await API.getSessions();
const SessionData = apiSessionData.filter((row) => row.NowPlayingItem !== undefined && !ExcludedUsers.includes(row.UserId));
sendUpdate("sessions", apiSessionData);
+
+ const hasActiveSessions = SessionData.length > 0;
+
+ // Determine current appropriate interval
+ const currentInterval = hasActiveSessions ? pollingSettings.activeSessionsInterval : pollingSettings.idleInterval;
+
+ // Check if we need to change the interval (either due to session state change OR settings change)
+ if (hasActiveSessions !== lastHadActiveSessions || settingsChanged) {
+ if (hasActiveSessions !== lastHadActiveSessions) {
+ console.log(`[ActivityMonitor] Switching to ${hasActiveSessions ? 'active' : 'idle'} polling mode (${currentInterval}ms)`);
+ lastHadActiveSessions = hasActiveSessions;
+ }
+ if (settingsChanged) {
+ console.log(`[ActivityMonitor] Applying new ${hasActiveSessions ? 'active' : 'idle'} interval: ${currentInterval}ms`);
+ }
+
+ // Clear current interval and restart with new timing
+ if (currentIntervalId) {
+ clearInterval(currentIntervalId);
+ }
+ currentIntervalId = setInterval(runMonitoring, currentInterval);
+ return; // Let the new interval handle the next execution
+ }
+
/////get data from jf_activity_monitor
const WatchdogData = await db.query("SELECT * FROM jf_activity_watchdog").then((res) => res.rows);
@@ -258,7 +308,50 @@ async function ActivityMonitor(interval) {
}
return [];
}
- }, interval);
+ };
+
+ // Get initial configuration to start with the correct interval
+ const initConfig = async () => {
+ try {
+ const config = await new configClass().getConfig();
+
+ if (config.error || config.state !== 2) {
+ console.log("[ActivityMonitor] Config not ready, starting with default interval:", defaultInterval + "ms");
+ currentIntervalId = setInterval(runMonitoring, defaultInterval);
+ return;
+ }
+
+ // Get adaptive polling settings from config
+ const pollingSettings = config.settings?.ActivityMonitorPolling || {
+ activeSessionsInterval: 1000,
+ idleInterval: 5000
+ };
+
+ // Initialize cached settings
+ cachedPollingSettings = { ...pollingSettings };
+
+ // Start with idle interval since there are likely no active sessions at startup
+ const initialInterval = pollingSettings.idleInterval;
+ console.log("[ActivityMonitor] Starting adaptive polling with idle interval:", initialInterval + "ms");
+ console.log("[ActivityMonitor] Loaded settings:", pollingSettings);
+ currentIntervalId = setInterval(runMonitoring, initialInterval);
+
+ } catch (error) {
+ console.log("[ActivityMonitor] Error loading config, using default interval:", defaultInterval + "ms");
+ currentIntervalId = setInterval(runMonitoring, defaultInterval);
+ }
+ };
+
+ // Initialize with proper configuration
+ await initConfig();
+
+ // Return a cleanup function
+ return () => {
+ if (currentIntervalId) {
+ clearInterval(currentIntervalId);
+ currentIntervalId = null;
+ }
+ };
}
module.exports = {
diff --git a/public/locales/en-UK/translation.json b/public/locales/en-UK/translation.json
index 4b9357b..e524200 100644
--- a/public/locales/en-UK/translation.json
+++ b/public/locales/en-UK/translation.json
@@ -211,6 +211,15 @@
"1_DAY": "1 Day",
"1_WEEK": "1 Week"
},
+ "ACTIVITY_MONITOR": "Activity Monitor",
+ "ACTIVE_SESSIONS_INTERVAL": "Active Sessions Interval (ms)",
+ "ACTIVE_SESSIONS_HELP": "How often to check when users are watching content (recommended: 1000ms)",
+ "IDLE_INTERVAL": "Idle Interval (ms)",
+ "IDLE_HELP": "How often to check when no active sessions (recommended: 5000ms)",
+ "POLLING_INFO_TITLE": "Smart Polling",
+ "POLLING_INFO": "The system automatically adapts monitoring frequency: fast when users are watching content, slower when the server is idle. This reduces CPU load on your Jellyfin server.",
+ "INTERVAL_WARNING": "Active sessions interval should not be greater than idle interval",
+ "REALTIME_UPDATE_INFO": "Changes are applied in real-time without server restart.",
"SELECT_LIBRARIES_TO_IMPORT": "Select Libraries to Import",
"SELECT_LIBRARIES_TO_IMPORT_TOOLTIP": "Activity for Items within these libraries are still Tracked - Even when not imported.",
"DATE_ADDED": "Date Added"
diff --git a/public/locales/fr-FR/translation.json b/public/locales/fr-FR/translation.json
index 78929ef..6047ca7 100644
--- a/public/locales/fr-FR/translation.json
+++ b/public/locales/fr-FR/translation.json
@@ -211,6 +211,15 @@
"1_DAY": "1 Jour",
"1_WEEK": "1 Semaine"
},
+ "ACTIVITY_MONITOR": "Surveillance d'activité",
+ "ACTIVE_SESSIONS_INTERVAL": "Intervalle avec sessions actives (ms)",
+ "ACTIVE_SESSIONS_HELP": "Fréquence de vérification quand des utilisateurs regardent du contenu (recommandé: 1000ms)",
+ "IDLE_INTERVAL": "Intervalle en veille (ms)",
+ "IDLE_HELP": "Fréquence de vérification quand aucune session active (recommandé: 5000ms)",
+ "POLLING_INFO_TITLE": "Polling intelligent",
+ "POLLING_INFO": "Le système adapte automatiquement la fréquence de surveillance : rapide quand des utilisateurs regardent du contenu, plus lent quand le serveur est inactif. Cela réduit la charge CPU sur votre serveur Jellyfin.",
+ "INTERVAL_WARNING": "L'intervalle actif ne devrait pas être supérieur à l'intervalle de veille",
+ "REALTIME_UPDATE_INFO": "Les modifications sont appliquées en temps réel sans redémarrage du serveur.",
"SELECT_LIBRARIES_TO_IMPORT": "Sélectionner les médiathèques à importer",
"SELECT_LIBRARIES_TO_IMPORT_TOOLTIP": "L'activité du contenu de ces médiathèques est toujours suivie, même s'ils ne sont pas importés.",
"DATE_ADDED": "Date d'ajout"
diff --git a/src/pages/components/settings/ActivityMonitorSettings.jsx b/src/pages/components/settings/ActivityMonitorSettings.jsx
new file mode 100644
index 0000000..7ad3992
--- /dev/null
+++ b/src/pages/components/settings/ActivityMonitorSettings.jsx
@@ -0,0 +1,175 @@
+import { useState, useEffect } from "react";
+import axios from "../../../lib/axios_instance";
+import { Row, Col, Form, Button, Alert } from "react-bootstrap";
+import { Trans } from "react-i18next";
+import Loading from "../general/loading";
+
+export default function ActivityMonitorSettings() {
+ const [settings, setSettings] = useState({
+ activeSessionsInterval: 1000,
+ idleInterval: 5000
+ });
+ const [isSubmitted, setIsSubmitted] = useState("");
+ const [submissionMessage, setSubmissionMessage] = useState("");
+ const [loading, setLoading] = useState(true);
+ const token = localStorage.getItem("token");
+
+ useEffect(() => {
+ fetchSettings();
+ }, []);
+
+ const fetchSettings = async () => {
+ try {
+ const response = await axios.get("/api/getActivityMonitorSettings", {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ "Content-Type": "application/json",
+ },
+ });
+ setSettings(response.data);
+ setLoading(false);
+ } catch (error) {
+ console.error("Error fetching Activity Monitor settings:", error);
+ setLoading(false);
+ }
+ };
+
+ const handleInputChange = (e) => {
+ const { name, value } = e.target;
+ setSettings(prev => ({
+ ...prev,
+ [name]: parseInt(value, 10)
+ }));
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ setIsSubmitted("");
+ setSubmissionMessage("");
+
+ try {
+ await axios.post("/api/setActivityMonitorSettings", settings, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ "Content-Type": "application/json",
+ },
+ });
+ setIsSubmitted("Success");
+ setSubmissionMessage("Paramètres de surveillance mis à jour avec succès - Appliqués en temps réel");
+ } catch (error) {
+ console.error("Error updating Activity Monitor settings:", error);
+ setIsSubmitted("Failed");
+ setSubmissionMessage(
+ error.response?.data || "Erreur lors de la mise à jour des paramètres"
+ );
+ }
+ };
+
+ if (loading) {
+ return