From cf0dcb476996e8b86afc8b103bfa6de58ae9275e Mon Sep 17 00:00:00 2001
From: Bastien Wirtz
Date: Thu, 7 Aug 2025 20:11:22 +0200
Subject: [PATCH 01/12] feat(auto-refresh): centralized auto refresh System
---
src/App.vue | 5 +
src/components/services/DockerSocketProxy.vue | 8 +-
src/components/services/FreshRSS.vue | 6 +-
src/components/services/Glances.vue | 8 +-
src/components/services/Immich.vue | 8 +-
src/components/services/Lidarr.vue | 9 +-
src/components/services/PiAlert.vue | 8 +-
src/components/services/PiHole.vue | 33 +----
src/components/services/Ping.vue | 7 +-
src/components/services/Plex.vue | 8 +-
src/components/services/Prowlarr.vue | 9 +-
src/components/services/Radarr.vue | 9 +-
src/components/services/Rtorrent.vue | 22 ++-
src/components/services/SABnzbd.vue | 7 +-
src/components/services/Scrutiny.vue | 8 +-
src/components/services/Sonarr.vue | 9 +-
src/components/services/Tautulli.vue | 7 +-
src/components/services/Tdarr.vue | 7 +-
src/components/services/qBittorrent.vue | 19 ++-
src/mixins/service.js | 96 ++++++++++++-
src/utils/updateScheduler.js | 131 ++++++++++++++++++
21 files changed, 310 insertions(+), 114 deletions(-)
create mode 100644 src/utils/updateScheduler.js
diff --git a/src/App.vue b/src/App.vue
index 7a120973b..2e0e5d019 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -130,6 +130,11 @@ export default {
DarkMode,
DynamicTheme,
},
+ provide() {
+ return {
+ config: () => this.config,
+ };
+ },
data: function () {
return {
loaded: false,
diff --git a/src/components/services/DockerSocketProxy.vue b/src/components/services/DockerSocketProxy.vue
index 444f11aa0..345c7ed01 100644
--- a/src/components/services/DockerSocketProxy.vue
+++ b/src/components/services/DockerSocketProxy.vue
@@ -48,10 +48,10 @@ export default {
};
},
created: function () {
- const checkInterval = parseInt(this.item.checkInterval, 10) || 0;
- if (checkInterval > 0) {
- setInterval(() => this.fetchData(), checkInterval);
- }
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchData;
+
+ // Initial data fetch
this.fetchData();
},
methods: {
diff --git a/src/components/services/FreshRSS.vue b/src/components/services/FreshRSS.vue
index fa49fe646..5947befef 100644
--- a/src/components/services/FreshRSS.vue
+++ b/src/components/services/FreshRSS.vue
@@ -40,10 +40,10 @@ export default {
};
},
created: function () {
- const updateInterval = parseInt(this.item.updateInterval, 10) || 0;
- if (updateInterval > 0)
- setInterval(() => this.fetchConfig(), updateInterval);
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchConfig;
+ // Initial data fetch
this.fetchConfig();
},
methods: {
diff --git a/src/components/services/Glances.vue b/src/components/services/Glances.vue
index 0630d88fc..327ae6c3a 100644
--- a/src/components/services/Glances.vue
+++ b/src/components/services/Glances.vue
@@ -29,10 +29,10 @@ export default {
error: null,
}),
created() {
- const updateInterval = parseInt(this.item.updateInterval, 10) || 0;
- if (updateInterval > 0) {
- setInterval(() => this.fetchStat(), updateInterval);
- }
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchStat;
+
+ // Initial data fetch
this.fetchStat();
},
methods: {
diff --git a/src/components/services/Immich.vue b/src/components/services/Immich.vue
index 19675886d..390dd81f0 100644
--- a/src/components/services/Immich.vue
+++ b/src/components/services/Immich.vue
@@ -62,10 +62,10 @@ export default {
},
},
created: function () {
- const updateInterval = parseInt(this.item.updateInterval, 10) || 0;
- if (updateInterval > 0) {
- setInterval(() => this.fetchConfig(), updateInterval);
- }
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchConfig;
+
+ // Initial data fetch
this.fetchConfig();
},
methods: {
diff --git a/src/components/services/Lidarr.vue b/src/components/services/Lidarr.vue
index 970d2381a..184633886 100644
--- a/src/components/services/Lidarr.vue
+++ b/src/components/services/Lidarr.vue
@@ -43,12 +43,11 @@ export default {
serverError: false,
};
},
- created: function () {
- const checkInterval = parseInt(this.item.checkInterval, 10) || 0;
- if (checkInterval > 0) {
- setInterval(() => this.fetchConfig(), checkInterval);
- }
+ created() {
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchConfig;
+ // Initial data fetch
this.fetchConfig();
},
methods: {
diff --git a/src/components/services/PiAlert.vue b/src/components/services/PiAlert.vue
index 63bfebc87..ba231eefa 100644
--- a/src/components/services/PiAlert.vue
+++ b/src/components/services/PiAlert.vue
@@ -44,10 +44,10 @@ export default {
};
},
created() {
- const updateInterval = parseInt(this.item.updateInterval, 10) || 0;
- if (updateInterval > 0) {
- setInterval(() => this.fetchStatus(), updateInterval);
- }
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchStatus;
+
+ // Initial data fetch
this.fetchStatus();
},
methods: {
diff --git a/src/components/services/PiHole.vue b/src/components/services/PiHole.vue
index 0d9dd782e..7f9a9c740 100644
--- a/src/components/services/PiHole.vue
+++ b/src/components/services/PiHole.vue
@@ -39,8 +39,6 @@ export default {
retryCount: 0,
maxRetries: 3,
retryDelay: 5000,
- localCheckInterval: 1000, // Default value or a fallback
- pollInterval: null,
}),
computed: {
percentage: function () {
@@ -57,18 +55,16 @@ export default {
},
created() {
if (parseInt(this.item.apiVersion, 10) === 6) {
- // Set the interval to the checkInterval or default to 5 minutes
- this.localCheckInterval = parseInt(this.item.checkInterval, 10) || 300000;
this.loadCachedSession();
- this.startStatusPolling();
+
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchStatus;
} else {
- this.fetchStatus_v5();
- }
- },
- beforeUnmount() {
- if (parseInt(this.item.apiVersion, 10) === 6) {
- this.stopStatusPolling();
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchStatus_v5();
}
+ // Initial data fetch
+ this.autoUpdateMethod();
},
methods: {
handleError: function (error, status) {
@@ -76,21 +72,6 @@ export default {
this.subtitle = error;
this.status = status;
},
- startStatusPolling: function () {
- this.fetchStatus();
- if (this.localCheckInterval < 1000) {
- this.localCheckInterval = 1000;
- }
- this.pollInterval = setInterval(
- this.fetchStatus,
- this.localCheckInterval,
- );
- },
- stopStatusPolling: function () {
- if (this.pollInterval) {
- clearInterval(this.pollInterval);
- }
- },
loadCachedSession: function () {
try {
const cachedSession = localStorage.getItem(
diff --git a/src/components/services/Ping.vue b/src/components/services/Ping.vue
index 88b03c7e7..46b536254 100644
--- a/src/components/services/Ping.vue
+++ b/src/components/services/Ping.vue
@@ -41,11 +41,10 @@ export default {
},
},
created() {
- const updateInterval = parseInt(this.item.updateInterval, 10) || 0;
- if (updateInterval > 0) {
- setInterval(this.fetchStatus, updateInterval);
- }
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchStatus;
+ // Initial data fetch
this.fetchStatus();
},
methods: {
diff --git a/src/components/services/Plex.vue b/src/components/services/Plex.vue
index f65ff180d..135b0e0b4 100644
--- a/src/components/services/Plex.vue
+++ b/src/components/services/Plex.vue
@@ -52,10 +52,10 @@ export default {
};
},
created: function () {
- const checkInterval = parseInt(this.item.checkInterval, 10) || 0;
- if (checkInterval > 0) {
- setInterval(() => this.fetchData(), checkInterval);
- }
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchData;
+
+ // Initial data fetch
this.fetchData();
},
methods: {
diff --git a/src/components/services/Prowlarr.vue b/src/components/services/Prowlarr.vue
index 95890da56..3ea9ef543 100644
--- a/src/components/services/Prowlarr.vue
+++ b/src/components/services/Prowlarr.vue
@@ -36,12 +36,11 @@ export default {
serverError: false,
};
},
- created: function () {
- const checkInterval = parseInt(this.item.checkInterval, 10) || 0;
- if (checkInterval > 0) {
- setInterval(() => this.fetchConfig(), checkInterval);
- }
+ created() {
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchConfig;
+ // Initial data fetch
this.fetchConfig();
},
methods: {
diff --git a/src/components/services/Radarr.vue b/src/components/services/Radarr.vue
index 77a3af974..014f731ca 100644
--- a/src/components/services/Radarr.vue
+++ b/src/components/services/Radarr.vue
@@ -51,12 +51,11 @@ export default {
return this.item.legacyApi ? LEGACY_API : V3_API;
},
},
- created: function () {
- const checkInterval = parseInt(this.item.checkInterval, 10) || 0;
- if (checkInterval > 0) {
- setInterval(() => this.fetchConfig(), checkInterval);
- }
+ created() {
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchConfig;
+ // Initial data fetch
this.fetchConfig();
},
methods: {
diff --git a/src/components/services/Rtorrent.vue b/src/components/services/Rtorrent.vue
index 7830a6858..5c0be86be 100644
--- a/src/components/services/Rtorrent.vue
+++ b/src/components/services/Rtorrent.vue
@@ -59,24 +59,18 @@ export default {
},
},
created() {
- // Set intervals if configured so the rates and/or torrent count
- // will be updated.
- const rateInterval = parseInt(this.item.rateInterval, 10) || 0;
- const torrentInterval = parseInt(this.item.torrentInterval, 10) || 0;
-
- if (rateInterval > 0) {
- setInterval(() => this.fetchRates(), rateInterval);
- }
-
- if (torrentInterval > 0) {
- setInterval(() => this.fetchCount(), torrentInterval);
- }
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchAllData;
// Fetch the initial values.
- this.fetchRates();
- this.fetchCount();
+ this.fetchAllData();
},
methods: {
+ // Combined method for scheduler - fetches both rates and count
+ fetchAllData: async function () {
+ this.fetchRates();
+ this.fetchCount();
+ },
// Perform two calls to the XML-RPC service and fetch download
// and upload rates. Values are saved to the `ul` and `dl`
// properties.
diff --git a/src/components/services/SABnzbd.vue b/src/components/services/SABnzbd.vue
index 417968a23..1223fa153 100644
--- a/src/components/services/SABnzbd.vue
+++ b/src/components/services/SABnzbd.vue
@@ -80,11 +80,10 @@ export default {
},
},
created() {
- const downloadInterval = parseInt(this.item.downloadInterval, 10) || 0;
- if (downloadInterval > 0) {
- setInterval(() => this.fetchStatus(), downloadInterval);
- }
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchStatus;
+ // Initial data fetch
this.fetchStatus();
},
methods: {
diff --git a/src/components/services/Scrutiny.vue b/src/components/services/Scrutiny.vue
index e01bad28e..6a64285b1 100644
--- a/src/components/services/Scrutiny.vue
+++ b/src/components/services/Scrutiny.vue
@@ -40,10 +40,10 @@ export default {
};
},
created: function () {
- const updateInterval = parseInt(this.item.updateInterval, 10) || 0;
- if (updateInterval > 0) {
- setInterval(() => this.fetchSummary(), updateInterval);
- }
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchSummary;
+
+ // Initial data fetch
this.fetchSummary();
},
methods: {
diff --git a/src/components/services/Sonarr.vue b/src/components/services/Sonarr.vue
index 3a610d585..69a6671a5 100644
--- a/src/components/services/Sonarr.vue
+++ b/src/components/services/Sonarr.vue
@@ -52,12 +52,11 @@ export default {
return this.item.legacyApi ? LEGACY_API : V3_API;
},
},
- created: function () {
- const checkInterval = parseInt(this.item.checkInterval, 10) || 0;
- if (checkInterval > 0) {
- setInterval(() => this.fetchConfig(), checkInterval);
- }
+ created() {
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchConfig;
+ // Initial data fetch
this.fetchConfig();
},
methods: {
diff --git a/src/components/services/Tautulli.vue b/src/components/services/Tautulli.vue
index 77e781d45..e7bf94702 100644
--- a/src/components/services/Tautulli.vue
+++ b/src/components/services/Tautulli.vue
@@ -41,11 +41,10 @@ export default {
},
},
created() {
- const checkInterval = parseInt(this.item.checkInterval, 10) || 0;
- if (checkInterval > 0) {
- setInterval(() => this.fetchStatus(), checkInterval);
- }
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchStatus;
+ // Initial data fetch
this.fetchStatus();
},
methods: {
diff --git a/src/components/services/Tdarr.vue b/src/components/services/Tdarr.vue
index bc5c892f0..dd821ca8f 100644
--- a/src/components/services/Tdarr.vue
+++ b/src/components/services/Tdarr.vue
@@ -54,11 +54,10 @@ export default {
},
},
created() {
- const checkInterval = parseInt(this.item.checkInterval, 10) || 0;
- if (checkInterval > 0) {
- setInterval(() => this.fetchStatus(), checkInterval);
- }
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchStatus;
+ // Initial data fetch
this.fetchStatus();
},
methods: {
diff --git a/src/components/services/qBittorrent.vue b/src/components/services/qBittorrent.vue
index 829e85051..182958895 100644
--- a/src/components/services/qBittorrent.vue
+++ b/src/components/services/qBittorrent.vue
@@ -61,19 +61,18 @@ export default {
},
},
created() {
- const rateInterval = parseInt(this.item.rateInterval, 10) || 0;
- const torrentInterval = parseInt(this.item.torrentInterval, 10) || 0;
- if (rateInterval > 0) {
- setInterval(() => this.getRate(), rateInterval);
- }
- if (torrentInterval > 0) {
- setInterval(() => this.fetchCount(), torrentInterval);
- }
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchAllData;
- this.getRate();
- this.fetchCount();
+ // Fetch initial values
+ this.fetchAllData();
},
methods: {
+ // Combined method for scheduler - fetches both rates and count
+ fetchAllData: async function () {
+ this.getRate();
+ this.fetchCount();
+ },
fetchCount: async function () {
try {
const body = await this.fetch("/api/v2/torrents/info");
diff --git a/src/mixins/service.js b/src/mixins/service.js
index 6d5c25315..aae6985e2 100644
--- a/src/mixins/service.js
+++ b/src/mixins/service.js
@@ -1,15 +1,35 @@
+import updateScheduler from "@/utils/updateScheduler.js";
+
export default {
props: {
proxy: Object,
},
+ inject: {
+ // Inject global config from parent components
+ config: {
+ default: () => ({}),
+ },
+ },
+ computed: {
+ globalConfig() {
+ return this.config() || {};
+ },
+ },
created: function () {
- // custom service often consume info from an API using the item link (url) as a base url,
+ // Custom service often consume info from an API using the item link (url) as a base url,
// but sometimes the base url is different. An optional alternative URL can be provided with the "endpoint" key.
this.endpoint = this.item.endpoint || this.item.url;
if (this.endpoint && this.endpoint.endsWith("/")) {
this.endpoint = this.endpoint.slice(0, -1);
}
+
+ // Initialize auto-update if configured
+ this.initAutoUpdate();
+ },
+ beforeUnmount() {
+ // Clean up auto-update registration
+ updateScheduler.unregister(this);
},
methods: {
fetch: function (path, init, json = true) {
@@ -62,5 +82,79 @@ export default {
return json ? response.json() : response.text();
});
},
+ initAutoUpdate: function () {
+ // Check if component has defined an auto-update method and interval
+ const interval = this.getUpdateInterval();
+ if (
+ interval > 0 &&
+ this.autoUpdateMethod &&
+ typeof this.autoUpdateMethod === "function"
+ ) {
+ updateScheduler.register(this, interval, this.autoUpdateMethod);
+ }
+ },
+ getUpdateInterval: function () {
+ // Check if auto-update is explicitly disabled for this service
+ if (this.item.autoUpdateInterval === false) {
+ return 0;
+ }
+
+ // Use service-specific interval if defined
+ if (this.item.autoUpdateInterval) {
+ return parseInt(this.item.autoUpdateInterval, 10) || 0;
+ }
+
+ // Check for deprecated keys and warn users
+ const deprecatedKeys = [
+ "updateInterval",
+ "checkInterval",
+ "localCheckInterval",
+ "downloadInterval",
+ "rateInterval",
+ "torrentInterval",
+ ];
+
+ for (const key of deprecatedKeys) {
+ if (this.item[key]) {
+ console.warn(
+ `[DEPRECATED] Service "${this.item.name || "unknown"}" uses deprecated config key "${key}". ` +
+ `Please use "autoUpdateInterval" instead. Support for "${key}" will be removed in a future version.`,
+ );
+ return parseInt(this.item[key], 10) || 0;
+ }
+ }
+
+ // Use global auto-update configuration
+ return this.getGlobalAutoUpdateInterval();
+ },
+
+ getGlobalAutoUpdateInterval: function () {
+ const globalAutoUpdate = this.globalConfig.autoUpdate;
+
+ // If auto-update is not configured globally, disable
+ if (!globalAutoUpdate) {
+ return 0;
+ }
+
+ // If global auto-update is explicitly disabled
+ if (globalAutoUpdate.enabled === false) {
+ return 0;
+ }
+
+ // If autoUpdate is just a number (simplified config)
+ if (typeof globalAutoUpdate === "number") {
+ return globalAutoUpdate;
+ }
+
+ // If autoUpdate is an object, use defaultInterval
+ if (
+ typeof globalAutoUpdate === "object" &&
+ globalAutoUpdate.defaultInterval
+ ) {
+ return parseInt(globalAutoUpdate.defaultInterval, 10) || 0;
+ }
+
+ return 0;
+ },
},
};
diff --git a/src/utils/updateScheduler.js b/src/utils/updateScheduler.js
new file mode 100644
index 000000000..64e126bba
--- /dev/null
+++ b/src/utils/updateScheduler.js
@@ -0,0 +1,131 @@
+/**
+ * This module provides a single-timer solution for managing automatic data updates
+ * across all service components in Homer. Instead of each service component creating
+ * its own setInterval timer, all components register with this centralized scheduler.
+ *
+ */
+class UpdateScheduler {
+ constructor() {
+ this.registeredComponents = new Map();
+ this.globalTimer = null;
+ this.tickCount = 0;
+ this.isRunning = false;
+ }
+
+ register(component, intervalMs, updateMethod) {
+ if (!component || !updateMethod || intervalMs <= 0) {
+ console.warn("UpdateScheduler: Invalid registration parameters");
+ return;
+ }
+
+ const intervalSeconds = Math.floor(intervalMs / 1000);
+ const componentId = this.generateComponentId(component);
+
+ this.registeredComponents.set(componentId, {
+ component,
+ interval: intervalSeconds,
+ method: updateMethod,
+ lastUpdate: 0,
+ });
+
+ this.startGlobalTimer();
+ console.log(
+ `UpdateScheduler: Registered component with ${intervalSeconds}s interval`,
+ );
+ }
+
+ unregister(component) {
+ const componentId = this.generateComponentId(component);
+ const removed = this.registeredComponents.delete(componentId);
+
+ if (removed) {
+ console.log("UpdateScheduler: Unregistered component");
+ }
+
+ if (this.registeredComponents.size === 0) {
+ this.stopGlobalTimer();
+ }
+ }
+
+ generateComponentId(component) {
+ // Use component's unique identifier or Vue instance uid
+ return component._uid || component.$.uid || Symbol("component");
+ }
+
+ startGlobalTimer() {
+ if (!this.globalTimer && !this.isRunning) {
+ this.isRunning = true;
+ this.tickCount = 0;
+
+ this.globalTimer = setInterval(() => {
+ this.tickCount++;
+ this.processUpdates();
+ }, 1000);
+
+ console.log("UpdateScheduler: Global timer started");
+ }
+ }
+
+ stopGlobalTimer() {
+ if (this.globalTimer) {
+ clearInterval(this.globalTimer);
+ this.globalTimer = null;
+ this.isRunning = false;
+ this.tickCount = 0;
+ console.log("UpdateScheduler: Global timer stopped");
+ }
+ }
+
+ processUpdates() {
+ for (const [, config] of this.registeredComponents) {
+ try {
+ if (this.tickCount - config.lastUpdate >= config.interval) {
+ config.method.call(config.component);
+ config.lastUpdate = this.tickCount;
+ }
+ } catch (error) {
+ console.error("UpdateScheduler: Error during component update:", error);
+ }
+ }
+ }
+
+ pause() {
+ if (this.globalTimer) {
+ clearInterval(this.globalTimer);
+ this.globalTimer = null;
+ this.isRunning = false;
+ console.log("UpdateScheduler: Paused");
+ }
+ }
+
+ resume() {
+ if (!this.globalTimer && this.registeredComponents.size > 0) {
+ this.startGlobalTimer();
+ console.log("UpdateScheduler: Resumed");
+ }
+ }
+
+ getStatus() {
+ return {
+ isRunning: this.isRunning,
+ registeredCount: this.registeredComponents.size,
+ tickCount: this.tickCount,
+ };
+ }
+}
+
+// Create and export global singleton instance
+const updateScheduler = new UpdateScheduler();
+
+// Pause updates when tab is hidden (power saving)
+if (typeof document !== "undefined") {
+ document.addEventListener("visibilitychange", () => {
+ if (document.hidden) {
+ updateScheduler.pause();
+ } else {
+ updateScheduler.resume();
+ }
+ });
+}
+
+export default updateScheduler;
From fdddbfd1ec0c53171b86d955b9e9933e166397aa Mon Sep 17 00:00:00 2001
From: "Joris W. van Rijn"
Date: Tue, 7 Oct 2025 14:08:57 +0200
Subject: [PATCH 02/12] feat(auto-refresh): add Transmission and docs
---
docs/customservices.md | 29 ++++++++++++------------
src/components/services/Transmission.vue | 11 +++------
src/mixins/service.js | 1 +
3 files changed, 18 insertions(+), 23 deletions(-)
diff --git a/docs/customservices.md b/docs/customservices.md
index a244a8d45..6a6891558 100644
--- a/docs/customservices.md
+++ b/docs/customservices.md
@@ -83,6 +83,7 @@ Available services are located in `src/components/`:
endpoint: https://my-service-api.url # Optional: alternative base URL used to fetch service data when necessary.
useCredentials: false # Optional: Override global proxy.useCredentials configuration.
headers: # Optional: Override global proxy.headers configuration.
+ autoUpdateInterval: # Optional: Time in ms. Some services can periodically fetch data (see below)
```
If a subtitle is provided, (using the `subtitle` configuration key), **it will override (hide)** any custom information displayed on the subtitle line by the custom integration.
@@ -159,10 +160,10 @@ The `libraryType` configuration let you choose which stats to show.
Displays unread article count and total subscriptions from your FreshRSS server.
```yaml
-- name: "FreshRSS"
+- name: "FreshRSS"
type: "FreshRSS"
url: https://my-service.url
- updateInterval: 5000 # (Optional) Interval (in ms) for updating the stats
+ autoUpdateInterval: 5000 # (Optional) Interval (in ms) for updating the stats
username: "<---your-username--->"
password: "<---your-password--->"
```
@@ -185,7 +186,7 @@ Optionally, the results can be filtered to only include jobs in the defined grou
The status can be checked regularly by defining an update Interval in ms:
```yaml
- updateInterval: 5000
+ autoUpdateInterval: 5000
```
The average times can be hidden (saves their calculation also) by setting the following:
@@ -335,7 +336,7 @@ Two lines are needed in the `config.yml`:
type: "Lidarr" # "Lidarr" "Prowlarr", "Radarr" or "Sonarr"
logo: "assets/tools/sample.png"
url: https://my-service.url
- checkInterval: 5000 # (Optional) Interval (in ms) for updating the status
+ autoUpdateInterval: 5000 # (Optional) Interval (in ms) for updating the status
apikey: "<---insert-api-key-here--->"
```
@@ -551,7 +552,7 @@ Displays stats from your PiAlert server.
type: "PiAlert"
logo: "assets/tools/sample.png"
url: https://my-service.url
- updateInterval: 5000 # (Optional) Interval (in ms) for updating the stats
+ autoUpdateInterval: 5000 # (Optional) Interval (in ms) for updating the stats
```
## PiHole
@@ -566,7 +567,7 @@ Displays info about your local PiHole instance right on your Homer dashboard.
# endpoint: "https://my-service-api.url" # optional, For v6 API, this is the base URL used to fetch Pi-hole data overwriting the url
apikey: "<---insert-api-key-here--->" # optional, needed if web interface is password protected
apiVersion: 5 # optional, defaults to 5. Use 6 if your PiHole instance uses API v6
- checkInterval: 3000 # optional, defaults to 300000. interval in ms to check Pi-hole status
+ autoUpdateInterval: 3000 # optional, defaults to 300000. interval in ms to check Pi-hole status
```
**API Key**: Required only if Pi-hole web interface is password protected. Go to **Settings > API/Web Interface > Show API token**
@@ -682,8 +683,7 @@ for setting up qBittorrent.
type: "qBittorrent"
logo: "assets/tools/sample.png"
url: https://my-service.url # Your rTorrent web UI, f.e. ruTorrent or Flood.
- rateInterval: 2000 # Interval for updating the download and upload rates.
- torrentInterval: 5000 # Interval for updating the torrent count.
+ autoUpdateInterval: 2000 # Interval for updating the download, upload rates & torrent count
```
## rTorrent
@@ -700,8 +700,7 @@ for setting up rTorrent.
logo: "assets/tools/sample.png"
url: "https://my-service.url" # Your rTorrent web UI, f.e. ruTorrent or Flood.
xmlrpc: "https://my-service.url:port" # Reverse proxy for rTorrent's XML-RPC.
- rateInterval: 5000 # Interval for updating the download and upload rates.
- torrentInterval: 60000 # Interval for updating the torrent count.
+ autoUpdateInterval: 5000 # Interval for updating the download, upload rates & torrent count.
username: "username" # Username for logging into rTorrent (if applicable).
password: "password" # Password for logging into rTorrent (if applicable).
```
@@ -716,7 +715,7 @@ Displays the number of currently active downloads on your SABnzbd instance.
logo: "assets/tools/sample.png"
url: https://my-service.url
apikey: "<---insert-api-key-here--->"
- downloadInterval: 5000 # (Optional) Interval (in ms) for updating the download count
+ autoUpdateInterval: 5000 # (Optional) Interval (in ms) for updating the download count
```
**API Key**: An API key is required, and can be obtained from the "Config" > "General" section of the SABnzbd config in the web UI.
@@ -730,7 +729,7 @@ Displays info about the total number of disk passed and failed S.M.A.R.T and scr
type: "Scrutiny"
logo: "assets/tools/sample.png"
url: https://my-service.url
- updateInterval: 5000 # (Optional) Interval (in ms) for updating the status
+ autoUpdateInterval: 5000 # (Optional) Interval (in ms) for updating the status
```
## SpeedtestTracker
@@ -753,7 +752,7 @@ Displays the number of currently active streams on you Plex instance.
type: "Tautulli"
logo: "assets/tools/sample.png"
url: https://my-service.url
- checkInterval: 5000 # (Optional) Interval (in ms) for updating the status
+ autoUpdateInterval: 5000 # (Optional) Interval (in ms) for updating the status
apikey: "<---insert-api-key-here--->"
```
@@ -781,7 +780,7 @@ Displays the number of currently queued items for transcoding on your Tdarr inst
type: "Tdarr"
logo: "assets/tools/sample.png"
url: https://my-service.url
- checkInterval: 5000 # (Optional) Interval (in ms) for updating the queue & error counts
+ autoUpdateInterval: 5000 # (Optional) Interval (in ms) for updating the queue & error counts
```
## Traefik
@@ -809,7 +808,7 @@ The service communicates with the Transmission RPC interface which needs to be a
url: "http://192.168.1.2:9091" # Your Transmission web interface URL
type: "Transmission"
auth: "username:password" # Optional: HTTP Basic Auth
- interval: 5000 # Optional: Interval for refreshing data (ms)
+ autoUpdateInterval: 5000 # Optional: Interval for refreshing data (ms)
target: "_blank" # Optional: HTML a tag target attribute
```
diff --git a/src/components/services/Transmission.vue b/src/components/services/Transmission.vue
index 802c4f38c..f6ce4dd75 100644
--- a/src/components/services/Transmission.vue
+++ b/src/components/services/Transmission.vue
@@ -18,8 +18,7 @@
- {{ count || 0 }}
+ {{ count || 0 }}
torrent
torrents
@@ -69,12 +68,8 @@ export default {
},
},
created() {
- const interval = parseInt(this.item.interval, 10) || 0;
-
- // Set up interval if configured
- if (interval > 0) {
- setInterval(() => this.getStats(), interval);
- }
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.getStats;
// Initial fetch
this.getStats();
diff --git a/src/mixins/service.js b/src/mixins/service.js
index aae6985e2..386847c0b 100644
--- a/src/mixins/service.js
+++ b/src/mixins/service.js
@@ -106,6 +106,7 @@ export default {
// Check for deprecated keys and warn users
const deprecatedKeys = [
+ "interval",
"updateInterval",
"checkInterval",
"localCheckInterval",
From a37a8a3a9dfc688915a5ef0bb19b3168acc91dd9 Mon Sep 17 00:00:00 2001
From: Bastien Wirtz
Date: Sun, 18 Jan 2026 14:38:43 +0100
Subject: [PATCH 03/12] fix: auto update system adjustments
---
docs/configuration.md | 7 ++++--
docs/customservices.md | 39 +++++++++++--------------------
public/assets/config.yml.dist | 4 ++++
src/mixins/service.js | 43 +++++++++++------------------------
src/utils/updateScheduler.js | 13 ++++++++++-
5 files changed, 47 insertions(+), 59 deletions(-)
diff --git a/docs/configuration.md b/docs/configuration.md
index 6db588391..ecc5deb59 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -125,6 +125,9 @@ links:
icon: "fas fa-file-alt"
url: "#page2"
+# Optional: enable periodic refresh for all smart cards that implements it (unless overriden at service configuration level).
+updateIntervalMs: 30000 # interval in milliseconds, minimum is 1000 (1s). Remove or set to 0 to disable. Can be overriden at service level. (default disabled)
+
# Services
# First level array represents a group.
# Leave only a "items" key if not using group (group name, icon & tagstyle are optional, section separation will not be displayed).
@@ -183,9 +186,9 @@ Empty values (either in `config.yml` or the endpoint data) will hide the element
## Connectivity checks
As a webapp (PWA) the dashboard can still be displayed when your homer server is offline.
-The connectivity checker periodically sends a HEAD request bypassing the PWA cache to the dashbord page to make sure it's still reachable.
+The connectivity checker periodically sends a HEAD request bypassing the PWA cache to the dashboard page to make sure it's still reachable.
-It can be useful when you access your dashboard through a VPN or ssh tunnel for example, to know if your conection is up. It also helps when using an authentication proxy, it will reload the page if the authentication expires (when a redirect is send in response to the HEAD request).
+It can be useful when you access your dashboard through a VPN or ssh tunnel for example, to know if your connection is up. It also helps when using an authentication proxy, it will reload the page if the authentication expires (when a redirect is send in response to the HEAD request).
## Style Options
diff --git a/docs/customservices.md b/docs/customservices.md
index 6a6891558..99f7884eb 100644
--- a/docs/customservices.md
+++ b/docs/customservices.md
@@ -1,6 +1,6 @@
# Smart cards
-Smart cards provide specific integrations for external services. They display additional information and extra features beyond basic service card. Smart cards are enabled by adding a `type` key to the service item in your YAML configuration.
+Smart cards provide specific integration for external services. They display additional information and extra features beyond basic service card. Smart cards are enabled by adding a `type` key to the service item in your YAML configuration.
Each service integration has different requirements and may need additional configuration parameters (see card list below).
@@ -8,7 +8,7 @@ Each service integration has different requirements and may need additional conf
> Your `config.yml` file is exposed at `/assets/config.yml` via HTTP. Any sensitive information (like API keys)
> in this file is visible to anyone who can access your Homer instance. Only include API keys if your Homer
> instance is protected by authentication or access controls **or use a proxy like [`CORSair`](https://github.com/bastienwirtz/corsair)
-> to inject your credentials safely**, using environment variable on the server side.
+> to inject your credentials safely**, using environment variable on the server side.
Available services are located in `src/components/`:
@@ -40,7 +40,7 @@ Available services are located in `src/components/`:
- [Olivetin](#olivetin)
- [OpenHAB](#openhab)
- [OpenWeatherMap](#openweathermap)
-- [Paperless-NGX](#paperlessng)
+- [Paperless-NGX](#paperless-ngx)
- [PeaNUT](#peanut)
- [PiAlert](#pialert)
- [PiHole](#pihole)
@@ -69,7 +69,7 @@ Available services are located in `src/components/`:
>
> - All services hosted on the **same domain** as Homer (mydomain.tld/pihole, mydomain.tld/proxmox) to avoid cross-domain request entirely.
> - All services configured to **accept cross-site requests** by sending the necessary CORS headers (either directly in service configuration or via proxy).
-> - **Use a proxy** to add the necessary CORS headers (lot of options, some of them described [here](https://enable-cors.org/server.html). Also check [`CORSair`](https://github.com/bastienwirtz/corsair), a light and simple solution)
+> - **Use a proxy** to add the necessary CORS headers (lot of options, some of them described [here](https://enable-cors.org/server.html). Also check [`CORSair`](https://github.com/bastienwirtz/corsair), a light and simple solution).
>
> If you experience any issues, see the [troubleshooting](troubleshooting.md#my-service-card-doesnt-work-nothing-appears-or-offline-status-is-displayed-pi-hole-sonarr-ping-) page.
@@ -83,7 +83,6 @@ Available services are located in `src/components/`:
endpoint: https://my-service-api.url # Optional: alternative base URL used to fetch service data when necessary.
useCredentials: false # Optional: Override global proxy.useCredentials configuration.
headers: # Optional: Override global proxy.headers configuration.
- autoUpdateInterval: # Optional: Time in ms. Some services can periodically fetch data (see below)
```
If a subtitle is provided, (using the `subtitle` configuration key), **it will override (hide)** any custom information displayed on the subtitle line by the custom integration.
@@ -163,7 +162,6 @@ Displays unread article count and total subscriptions from your FreshRSS server.
- name: "FreshRSS"
type: "FreshRSS"
url: https://my-service.url
- autoUpdateInterval: 5000 # (Optional) Interval (in ms) for updating the stats
username: "<---your-username--->"
password: "<---your-password--->"
```
@@ -180,16 +178,13 @@ Two lines are needed in the config.yml :
```
Optionally, the results can be filtered to only include jobs in the defined groups:
-```yaml
- groups: [Services, External]
-```
-The status can be checked regularly by defining an update Interval in ms:
```yaml
- autoUpdateInterval: 5000
+ groups: [Services, External]
```
The average times can be hidden (saves their calculation also) by setting the following:
+
```yaml
hideaverages: true
```
@@ -313,7 +308,7 @@ Displays user count, photo/video counts, and storage usage from your Immich serv
## Jellystat
-Display the number of concurrent streams on your jellyfin server.
+Display the number of concurrent streams on your Jellyfin server.
```yaml
- name: "Jellystat"
@@ -323,7 +318,7 @@ Display the number of concurrent streams on your jellyfin server.
apikey: "<---insert-api-key-here--->"
```
-**API Key**: You can create an API key in the dashboard of you jellystat server: settings/API Keys -> Add Key
+**API Key**: You can create an API key in the dashboard of you Jellystat server: settings/API Keys -> Add Key
## Lidarr, Prowlarr, Sonarr, Readarr and Radarr
@@ -336,7 +331,6 @@ Two lines are needed in the `config.yml`:
type: "Lidarr" # "Lidarr" "Prowlarr", "Radarr" or "Sonarr"
logo: "assets/tools/sample.png"
url: https://my-service.url
- autoUpdateInterval: 5000 # (Optional) Interval (in ms) for updating the status
apikey: "<---insert-api-key-here--->"
```
@@ -390,7 +384,7 @@ The API page can be found: Click on hamburger menu -> Click on your profile -> C
## Medusa
-Displays News (grey), Warning (orange) or Error (red) notifications bubbles from the Medusa application.
+Displays News (gray), Warning (orange) or Error (red) notifications bubbles from the Medusa application.
```yaml
- name: "Medusa"
@@ -415,7 +409,6 @@ Displays the number of unread articles from your Miniflux RSS reader.
url: https://my-service.url
apikey: "<---insert-api-key-here--->"
style: "status" # Either "status" or "counter"
- checkInterval: 60000 # Optional: Interval (in ms) for updating the unread count
```
**API Key**: Generate an API key in Miniflux web interface under **Settings > API Keys > Create a new API key**
@@ -453,7 +446,7 @@ mode](https://docs.nextcloud.com/server/stable/admin_manual/maintenance/upgrade.
## OctoPrint/Moonraker
The OctoPrint/Moonraker service only needs an `apikey` & `endpoint` and optionally a `display` or `url` option. `url` can be used when you click on the service it will launch the `url`
-Moonraker's API mimmicks a few of OctoPrint's endpoints which makes these services compatible. See for details.
+Moonraker's API mimics a few of OctoPrint's endpoints which makes these services compatible. See for details.
```yaml
- name: "Octoprint"
@@ -552,7 +545,6 @@ Displays stats from your PiAlert server.
type: "PiAlert"
logo: "assets/tools/sample.png"
url: https://my-service.url
- autoUpdateInterval: 5000 # (Optional) Interval (in ms) for updating the stats
```
## PiHole
@@ -567,7 +559,6 @@ Displays info about your local PiHole instance right on your Homer dashboard.
# endpoint: "https://my-service-api.url" # optional, For v6 API, this is the base URL used to fetch Pi-hole data overwriting the url
apikey: "<---insert-api-key-here--->" # optional, needed if web interface is password protected
apiVersion: 5 # optional, defaults to 5. Use 6 if your PiHole instance uses API v6
- autoUpdateInterval: 3000 # optional, defaults to 300000. interval in ms to check Pi-hole status
```
**API Key**: Required only if Pi-hole web interface is password protected. Go to **Settings > API/Web Interface > Show API token**
@@ -592,8 +583,11 @@ Optionally, use `successCodes` to define which HTTP response status codes should
# successCodes: [200, 418] # Optional, default to all 2xx HTTP response status codes
# timeout: 500 # Timeout in ms before ping is aborted. Default 2000
# subtitle: "Bookmark example" # By default, request round trip time is displayed when subtitle is not set
+<<<<<<< HEAD
# updateInterval: 5000 # (Optional) Interval (in ms) for updating ping status
# endpoint: "https://www.wikimediastatus.net" # Optional, will override url for pinging
+=======
+>>>>>>> c25ffaf (fix: auto update system adjustments)
```
## Plex
@@ -683,7 +677,6 @@ for setting up qBittorrent.
type: "qBittorrent"
logo: "assets/tools/sample.png"
url: https://my-service.url # Your rTorrent web UI, f.e. ruTorrent or Flood.
- autoUpdateInterval: 2000 # Interval for updating the download, upload rates & torrent count
```
## rTorrent
@@ -700,7 +693,6 @@ for setting up rTorrent.
logo: "assets/tools/sample.png"
url: "https://my-service.url" # Your rTorrent web UI, f.e. ruTorrent or Flood.
xmlrpc: "https://my-service.url:port" # Reverse proxy for rTorrent's XML-RPC.
- autoUpdateInterval: 5000 # Interval for updating the download, upload rates & torrent count.
username: "username" # Username for logging into rTorrent (if applicable).
password: "password" # Password for logging into rTorrent (if applicable).
```
@@ -715,7 +707,6 @@ Displays the number of currently active downloads on your SABnzbd instance.
logo: "assets/tools/sample.png"
url: https://my-service.url
apikey: "<---insert-api-key-here--->"
- autoUpdateInterval: 5000 # (Optional) Interval (in ms) for updating the download count
```
**API Key**: An API key is required, and can be obtained from the "Config" > "General" section of the SABnzbd config in the web UI.
@@ -729,7 +720,6 @@ Displays info about the total number of disk passed and failed S.M.A.R.T and scr
type: "Scrutiny"
logo: "assets/tools/sample.png"
url: https://my-service.url
- autoUpdateInterval: 5000 # (Optional) Interval (in ms) for updating the status
```
## SpeedtestTracker
@@ -752,7 +742,6 @@ Displays the number of currently active streams on you Plex instance.
type: "Tautulli"
logo: "assets/tools/sample.png"
url: https://my-service.url
- autoUpdateInterval: 5000 # (Optional) Interval (in ms) for updating the status
apikey: "<---insert-api-key-here--->"
```
@@ -780,7 +769,6 @@ Displays the number of currently queued items for transcoding on your Tdarr inst
type: "Tdarr"
logo: "assets/tools/sample.png"
url: https://my-service.url
- autoUpdateInterval: 5000 # (Optional) Interval (in ms) for updating the queue & error counts
```
## Traefik
@@ -808,7 +796,6 @@ The service communicates with the Transmission RPC interface which needs to be a
url: "http://192.168.1.2:9091" # Your Transmission web interface URL
type: "Transmission"
auth: "username:password" # Optional: HTTP Basic Auth
- autoUpdateInterval: 5000 # Optional: Interval for refreshing data (ms)
target: "_blank" # Optional: HTML a tag target attribute
```
diff --git a/public/assets/config.yml.dist b/public/assets/config.yml.dist
index 4b2b8079e..9d7279147 100644
--- a/public/assets/config.yml.dist
+++ b/public/assets/config.yml.dist
@@ -67,6 +67,10 @@ links:
# icon: "fas fa-file-alt"
# url: "#additional-page"
+
+# Optional: enable periodic refresh for all smart cards that implements it (unless overriden at service configuration level).
+updateIntervalMs: 30000 # interval in milliseconds, minimum is 1000 (1s). Remove or set to 0 to disable. Can be overriden at service level. (default disabled)
+
# Services
# First level array represent a group.
# Leave only a "items" key if not using group (group name, icon & tagstyle are optional, section separation will not be displayed).
diff --git a/src/mixins/service.js b/src/mixins/service.js
index 386847c0b..45d31546e 100644
--- a/src/mixins/service.js
+++ b/src/mixins/service.js
@@ -23,7 +23,8 @@ export default {
if (this.endpoint && this.endpoint.endsWith("/")) {
this.endpoint = this.endpoint.slice(0, -1);
}
-
+ },
+ beforeMount: function () {
// Initialize auto-update if configured
this.initAutoUpdate();
},
@@ -95,21 +96,21 @@ export default {
},
getUpdateInterval: function () {
// Check if auto-update is explicitly disabled for this service
- if (this.item.autoUpdateInterval === false) {
+ if (
+ this.item.updateInterval === false ||
+ this.item.updateInterval === 0
+ ) {
return 0;
}
// Use service-specific interval if defined
- if (this.item.autoUpdateInterval) {
- return parseInt(this.item.autoUpdateInterval, 10) || 0;
+ if (this.item.updateInterval) {
+ return parseInt(this.item.updateInterval, 10) || 0;
}
// Check for deprecated keys and warn users
const deprecatedKeys = [
- "interval",
- "updateInterval",
"checkInterval",
- "localCheckInterval",
"downloadInterval",
"rateInterval",
"torrentInterval",
@@ -119,43 +120,25 @@ export default {
if (this.item[key]) {
console.warn(
`[DEPRECATED] Service "${this.item.name || "unknown"}" uses deprecated config key "${key}". ` +
- `Please use "autoUpdateInterval" instead. Support for "${key}" will be removed in a future version.`,
+ `Please use "updateInterval" instead. Support for "${key}" will be removed in a future version.`,
);
return parseInt(this.item[key], 10) || 0;
}
}
// Use global auto-update configuration
- return this.getGlobalAutoUpdateInterval();
+ return this.getGlobalUpdateInterval();
},
- getGlobalAutoUpdateInterval: function () {
- const globalAutoUpdate = this.globalConfig.autoUpdate;
+ getGlobalUpdateInterval: function () {
+ const globalAutoUpdate = this.globalConfig.updateIntervalMs;
// If auto-update is not configured globally, disable
if (!globalAutoUpdate) {
return 0;
}
- // If global auto-update is explicitly disabled
- if (globalAutoUpdate.enabled === false) {
- return 0;
- }
-
- // If autoUpdate is just a number (simplified config)
- if (typeof globalAutoUpdate === "number") {
- return globalAutoUpdate;
- }
-
- // If autoUpdate is an object, use defaultInterval
- if (
- typeof globalAutoUpdate === "object" &&
- globalAutoUpdate.defaultInterval
- ) {
- return parseInt(globalAutoUpdate.defaultInterval, 10) || 0;
- }
-
- return 0;
+ return parseInt(globalAutoUpdate, 10) || 0;
},
},
};
diff --git a/src/utils/updateScheduler.js b/src/utils/updateScheduler.js
index 64e126bba..b4167c8d9 100644
--- a/src/utils/updateScheduler.js
+++ b/src/utils/updateScheduler.js
@@ -4,6 +4,10 @@
* its own setInterval timer, all components register with this centralized scheduler.
*
*/
+
+const TICK_INTERVAL_MS = 1000; // 1 second tick resolution
+const MIN_INTERVAL_MS = TICK_INTERVAL_MS; // Minimum allowed update interval
+
class UpdateScheduler {
constructor() {
this.registeredComponents = new Map();
@@ -18,6 +22,13 @@ class UpdateScheduler {
return;
}
+ if (intervalMs < MIN_INTERVAL_MS) {
+ console.warn(
+ `UpdateScheduler: Interval ${intervalMs}ms is below minimum. Adjusting to ${MIN_INTERVAL_MS}ms`,
+ );
+ intervalMs = MIN_INTERVAL_MS;
+ }
+
const intervalSeconds = Math.floor(intervalMs / 1000);
const componentId = this.generateComponentId(component);
@@ -60,7 +71,7 @@ class UpdateScheduler {
this.globalTimer = setInterval(() => {
this.tickCount++;
this.processUpdates();
- }, 1000);
+ }, TICK_INTERVAL_MS);
console.log("UpdateScheduler: Global timer started");
}
From 6aa3eeb1cc3688a94497ce7e892c16ba41b311e1 Mon Sep 17 00:00:00 2001
From: Bastien Wirtz
Date: Sun, 18 Jan 2026 14:39:09 +0100
Subject: [PATCH 04/12] feat: autoupdate support for adGuardHome
---
src/components/services/AdGuardHome.vue | 28 +++++++++++++++----------
1 file changed, 17 insertions(+), 11 deletions(-)
diff --git a/src/components/services/AdGuardHome.vue b/src/components/services/AdGuardHome.vue
index 91d5b427c..103dba8b8 100644
--- a/src/components/services/AdGuardHome.vue
+++ b/src/components/services/AdGuardHome.vue
@@ -51,21 +51,27 @@ export default {
},
},
created: function () {
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchStatus;
+
+ // Initial data fetch
this.fetchStatus();
- if (!this.item.subtitle) {
- this.fetchStats();
- }
},
methods: {
fetchStatus: async function () {
- this.status = await this.fetch("/control/status").catch((e) =>
- console.log(e),
- );
- },
- fetchStats: async function () {
- this.stats = await this.fetch("/control/stats").catch((e) =>
- console.log(e),
- );
+ this.fetch("/control/status")
+ .then((status) => {
+ this.status = status;
+ })
+ .catch((e) => console.log(e));
+
+ if (!this.item.subtitle) {
+ this.fetch("/control/stats")
+ .then((stats) => {
+ this.stats = stats;
+ })
+ .catch((e) => console.log(e));
+ }
},
},
};
From b2e3af4bd7bf5699154757dac65285aadf1bce0b Mon Sep 17 00:00:00 2001
From: Bastien Wirtz
Date: Sun, 18 Jan 2026 15:26:03 +0100
Subject: [PATCH 05/12] fix: simplify scheduler implementation
---
AGENTS.md | 13 +++++++++++
src/utils/updateScheduler.js | 42 +++++++++---------------------------
2 files changed, 23 insertions(+), 32 deletions(-)
diff --git a/AGENTS.md b/AGENTS.md
index 29d7dfbf0..25d789b9c 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -32,6 +32,19 @@ All service components follow this architecture:
- Use the `service.js` mixin (`src/mixins/service.js`) for common API functionality
- Use a custom `fetch` method provided by the service mixin to seamlessly support proxy configuration, custom headers, and credentials.
+### Auto-Update Configuration
+
+Services support automatic data refreshing using a centralized scheduler system with global and per-service configuration:
+
+#### Global Configuration
+
+`autoUpdate: 30000` - Set default interval for all services (30 seconds)
+
+#### Service Configuration
+- **Service-specific interval**: `updateInterval: 10000` - Override global default
+- **Disable per service**: `autoUpdateInterval: false` - Disable for specific service
+- **Use global default**: Omit `autoUpdateInterval` to use global setting
+
### Configuration & Routing
- **Multi-page Support**: Hash-based routing without Vue Router
diff --git a/src/utils/updateScheduler.js b/src/utils/updateScheduler.js
index b4167c8d9..2261e146a 100644
--- a/src/utils/updateScheduler.js
+++ b/src/utils/updateScheduler.js
@@ -13,7 +13,6 @@ class UpdateScheduler {
this.registeredComponents = new Map();
this.globalTimer = null;
this.tickCount = 0;
- this.isRunning = false;
}
register(component, intervalMs, updateMethod) {
@@ -32,6 +31,11 @@ class UpdateScheduler {
const intervalSeconds = Math.floor(intervalMs / 1000);
const componentId = this.generateComponentId(component);
+ if (!componentId) {
+ console.error(`UpdateScheduler: invalid component id`);
+ return;
+ }
+
this.registeredComponents.set(componentId, {
component,
interval: intervalSeconds,
@@ -60,12 +64,11 @@ class UpdateScheduler {
generateComponentId(component) {
// Use component's unique identifier or Vue instance uid
- return component._uid || component.$.uid || Symbol("component");
+ return component._uid || component.$.uid
}
startGlobalTimer() {
- if (!this.globalTimer && !this.isRunning) {
- this.isRunning = true;
+ if (!this.globalTimer) {
this.tickCount = 0;
this.globalTimer = setInterval(() => {
@@ -81,7 +84,6 @@ class UpdateScheduler {
if (this.globalTimer) {
clearInterval(this.globalTimer);
this.globalTimer = null;
- this.isRunning = false;
this.tickCount = 0;
console.log("UpdateScheduler: Global timer stopped");
}
@@ -99,30 +101,6 @@ class UpdateScheduler {
}
}
}
-
- pause() {
- if (this.globalTimer) {
- clearInterval(this.globalTimer);
- this.globalTimer = null;
- this.isRunning = false;
- console.log("UpdateScheduler: Paused");
- }
- }
-
- resume() {
- if (!this.globalTimer && this.registeredComponents.size > 0) {
- this.startGlobalTimer();
- console.log("UpdateScheduler: Resumed");
- }
- }
-
- getStatus() {
- return {
- isRunning: this.isRunning,
- registeredCount: this.registeredComponents.size,
- tickCount: this.tickCount,
- };
- }
}
// Create and export global singleton instance
@@ -132,9 +110,9 @@ const updateScheduler = new UpdateScheduler();
if (typeof document !== "undefined") {
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
- updateScheduler.pause();
- } else {
- updateScheduler.resume();
+ updateScheduler.stopGlobalTimer();
+ } else if (updateScheduler.registeredComponents.size > 0) {
+ updateScheduler.startGlobalTimer();
}
});
}
From 5a4afc16a132471606727ce1172739bb28213a60 Mon Sep 17 00:00:00 2001
From: Bastien Wirtz
Date: Sun, 18 Jan 2026 15:26:31 +0100
Subject: [PATCH 06/12] feat: add autoupdate support for Emby
---
src/components/services/Emby.vue | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/src/components/services/Emby.vue b/src/components/services/Emby.vue
index 5aa5f0d6d..fb57761da 100644
--- a/src/components/services/Emby.vue
+++ b/src/components/services/Emby.vue
@@ -48,12 +48,20 @@ export default {
},
},
created() {
- this.fetchServerStatus();
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchAll;
- if (!this.item.subtitle && this.status !== "dead")
- this.fetchServerMediaStats();
+ // Initial data fetch
+ this.fetchAll();
},
methods: {
+ fetchAll: async function () {
+ this.fetchServerStatus();
+
+ if (!this.item.subtitle) {
+ this.fetchServerMediaStats();
+ }
+ },
fetchServerStatus: async function () {
this.fetch("/System/info/public")
.then((response) => {
From c0b1929b8b77ededd844950015eeda952f52b95e Mon Sep 17 00:00:00 2001
From: Bastien Wirtz
Date: Sun, 25 Jan 2026 17:05:53 +0100
Subject: [PATCH 07/12] migrate all service to glabal updater
---
src/components/services/Gatus.vue | 8 ++++----
src/components/services/Gotify.vue | 11 +++++++++--
src/components/services/Healthchecks.vue | 4 ++++
src/components/services/Jellystat.vue | 4 ++++
src/components/services/Linkding.vue | 4 ++++
src/components/services/Medusa.vue | 4 ++++
src/components/services/Miniflux.vue | 8 ++++----
src/components/services/Mylar.vue | 4 ++++
src/components/services/OctoPrint.vue | 12 ++++++++++--
src/components/services/PeaNUT.vue | 4 ++++
src/components/services/Portainer.vue | 4 ++++
src/components/services/Prometheus.vue | 4 ++++
src/components/services/Proxmox.vue | 5 +++++
src/components/services/Readarr.vue | 4 ++++
src/components/services/Transmission.vue | 3 ++-
src/components/services/UptimeKuma.vue | 5 +++++
src/components/services/WUD.vue | 4 ++++
17 files changed, 79 insertions(+), 13 deletions(-)
diff --git a/src/components/services/Gatus.vue b/src/components/services/Gatus.vue
index e30933fd6..8954a10ca 100644
--- a/src/components/services/Gatus.vue
+++ b/src/components/services/Gatus.vue
@@ -40,10 +40,10 @@ export default {
statusMessage: false,
}),
created() {
- const updateInterval = parseInt(this.item.updateInterval, 10) || 0;
- if (updateInterval > 0) {
- setInterval(() => this.fetchStatus(), updateInterval);
- }
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchStatus;
+
+ // Initial data fetch
this.fetchStatus();
},
methods: {
diff --git a/src/components/services/Gotify.vue b/src/components/services/Gotify.vue
index 46d9292fe..372e21472 100644
--- a/src/components/services/Gotify.vue
+++ b/src/components/services/Gotify.vue
@@ -44,10 +44,17 @@ export default {
},
},
created() {
- this.fetchStatus();
- this.fetchMessages();
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchAll;
+
+ // Initial data fetch
+ this.fetchAll();
},
methods: {
+ fetchAll: async function () {
+ this.fetchStatus();
+ this.fetchMessages();
+ },
fetchStatus: async function () {
await this.fetch(`/health`)
.catch((e) => console.log(e))
diff --git a/src/components/services/Healthchecks.vue b/src/components/services/Healthchecks.vue
index 8827a4e59..1cfaec8fd 100644
--- a/src/components/services/Healthchecks.vue
+++ b/src/components/services/Healthchecks.vue
@@ -55,6 +55,10 @@ export default {
},
},
created() {
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchStatus;
+
+ // Initial data fetch
this.fetchStatus();
},
methods: {
diff --git a/src/components/services/Jellystat.vue b/src/components/services/Jellystat.vue
index adcd43dfd..202636448 100644
--- a/src/components/services/Jellystat.vue
+++ b/src/components/services/Jellystat.vue
@@ -44,6 +44,10 @@ export default {
},
},
created() {
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchStatus;
+
+ // Initial data fetch
this.fetchStatus();
},
methods: {
diff --git a/src/components/services/Linkding.vue b/src/components/services/Linkding.vue
index 0a9071de9..f5536be18 100644
--- a/src/components/services/Linkding.vue
+++ b/src/components/services/Linkding.vue
@@ -26,6 +26,10 @@ export default {
},
},
created() {
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchBookmarks;
+
+ // Initial data fetch
this.fetchBookmarks();
},
methods: {
diff --git a/src/components/services/Medusa.vue b/src/components/services/Medusa.vue
index 4067eb991..4bc15595e 100644
--- a/src/components/services/Medusa.vue
+++ b/src/components/services/Medusa.vue
@@ -47,6 +47,10 @@ export default {
};
},
created: function () {
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchConfig;
+
+ // Initial data fetch
this.fetchConfig();
},
methods: {
diff --git a/src/components/services/Miniflux.vue b/src/components/services/Miniflux.vue
index 0ae3bb73b..84ce05e33 100644
--- a/src/components/services/Miniflux.vue
+++ b/src/components/services/Miniflux.vue
@@ -63,10 +63,10 @@ export default {
},
},
created() {
- const checkInterval = parseInt(this.item.checkInterval, 10) || 0;
- if (checkInterval > 0) {
- setInterval(() => this.fetchConfig(), checkInterval);
- }
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchStatus;
+
+ // Initial data fetch
this.fetchStatus();
},
methods: {
diff --git a/src/components/services/Mylar.vue b/src/components/services/Mylar.vue
index 82504170e..eed54d3e1 100644
--- a/src/components/services/Mylar.vue
+++ b/src/components/services/Mylar.vue
@@ -39,6 +39,10 @@ export default {
};
},
created: function () {
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchConfig;
+
+ // Initial data fetch
this.fetchConfig();
},
methods: {
diff --git a/src/components/services/OctoPrint.vue b/src/components/services/OctoPrint.vue
index 626288a97..a04214fc0 100644
--- a/src/components/services/OctoPrint.vue
+++ b/src/components/services/OctoPrint.vue
@@ -82,10 +82,18 @@ export default {
},
created() {
this.display = this.item.display == "bar" ? this.item.display : "text";
- this.fetchPrinterStatus();
- this.fetchStatus();
+
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchAll;
+
+ // Initial data fetch
+ this.fetchAll();
},
methods: {
+ fetchAll: async function () {
+ this.fetchPrinterStatus();
+ this.fetchStatus();
+ },
fetchStatus: async function () {
try {
const response = await this.fetch(`api/job?apikey=${this.item.apikey}`);
diff --git a/src/components/services/PeaNUT.vue b/src/components/services/PeaNUT.vue
index 806dbacbb..0e525f8a8 100644
--- a/src/components/services/PeaNUT.vue
+++ b/src/components/services/PeaNUT.vue
@@ -63,6 +63,10 @@ export default {
},
},
created() {
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchStatus;
+
+ // Initial data fetch
this.fetchStatus();
},
methods: {
diff --git a/src/components/services/Portainer.vue b/src/components/services/Portainer.vue
index 8edd0295f..1b2447479 100644
--- a/src/components/services/Portainer.vue
+++ b/src/components/services/Portainer.vue
@@ -82,6 +82,10 @@ export default {
},
},
created() {
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchStatus;
+
+ // Initial data fetch
this.fetchStatus();
this.fetchVersion();
},
diff --git a/src/components/services/Prometheus.vue b/src/components/services/Prometheus.vue
index fa019dd62..59cd331f0 100644
--- a/src/components/services/Prometheus.vue
+++ b/src/components/services/Prometheus.vue
@@ -59,6 +59,10 @@ export default {
},
},
created() {
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchStatus;
+
+ // Initial data fetch
this.fetchStatus();
},
methods: {
diff --git a/src/components/services/Proxmox.vue b/src/components/services/Proxmox.vue
index 975b045a6..e930ce7aa 100644
--- a/src/components/services/Proxmox.vue
+++ b/src/components/services/Proxmox.vue
@@ -101,6 +101,11 @@ export default {
}),
created() {
if (this.item.hide) this.hide = this.item.hide;
+
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchStatus;
+
+ // Initial data fetch
this.fetchStatus();
},
methods: {
diff --git a/src/components/services/Readarr.vue b/src/components/services/Readarr.vue
index 52d38d427..0a02f420c 100644
--- a/src/components/services/Readarr.vue
+++ b/src/components/services/Readarr.vue
@@ -46,6 +46,10 @@ export default {
};
},
created: function () {
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchConfig;
+
+ // Initial data fetch
this.fetchConfig();
},
methods: {
diff --git a/src/components/services/Transmission.vue b/src/components/services/Transmission.vue
index f6ce4dd75..f6e1c75a0 100644
--- a/src/components/services/Transmission.vue
+++ b/src/components/services/Transmission.vue
@@ -18,7 +18,8 @@
- {{ count || 0 }}
+ {{ count || 0 }}
torrent
torrents
diff --git a/src/components/services/UptimeKuma.vue b/src/components/services/UptimeKuma.vue
index 0440a73df..a08543719 100644
--- a/src/components/services/UptimeKuma.vue
+++ b/src/components/services/UptimeKuma.vue
@@ -110,6 +110,11 @@ export default {
created() {
/* eslint-disable */
this.item.url = `${this.item.url}/status/${this.dashboard}`;
+
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchStatus;
+
+ // Initial data fetch
this.fetchStatus();
},
methods: {
diff --git a/src/components/services/WUD.vue b/src/components/services/WUD.vue
index 4cf010b5f..c85467d67 100644
--- a/src/components/services/WUD.vue
+++ b/src/components/services/WUD.vue
@@ -37,6 +37,10 @@ export default {
};
},
created: function () {
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchConfig;
+
+ // Initial data fetch
this.fetchConfig();
},
methods: {
From c42fb20038b33797697be09445218e826eae416f Mon Sep 17 00:00:00 2001
From: Bastien Wirtz
Date: Sun, 25 Jan 2026 17:06:52 +0100
Subject: [PATCH 08/12] update smart cards documentation
---
docs/customservices.md | 66 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 65 insertions(+), 1 deletion(-)
diff --git a/docs/customservices.md b/docs/customservices.md
index 99f7884eb..061892022 100644
--- a/docs/customservices.md
+++ b/docs/customservices.md
@@ -87,6 +87,10 @@ Available services are located in `src/components/`:
If a subtitle is provided, (using the `subtitle` configuration key), **it will override (hide)** any custom information displayed on the subtitle line by the custom integration.
+> [!TIP]
+> **Auto refresh of the card data**: Some cards support periodic update (see indication in detail below). It can be enabled or disabled globally for all service, or individually for each service using the `updateIntervalMs` configuration option.
+
+
## AdGuard Home
Displays AdGuard Home protection status and blocked query statistics.
@@ -105,6 +109,8 @@ Displays AdGuard Home protection status and blocked query statistics.
> Authorization: "Basic "
> ```
+Auto refresh is supported by this integration.
+
## Copy to Clipboard
Displays a service card with a copy button that copies the specified text to your clipboard when clicked.
@@ -129,6 +135,8 @@ Displays counts of running, stopped, and error containers from Docker Socket Pro
endpoint: "https://my-service-api.url:port"
```
+Auto refresh is supported by this integration.
+
## Docuseal
Displays the Docuseal version.
@@ -154,6 +162,8 @@ The `libraryType` configuration let you choose which stats to show.
libraryType: "music" # Choose which stats to show. Can be one of: music, series or movies.
```
+Auto refresh is supported by this integration.
+
## FreshRSS
Displays unread article count and total subscriptions from your FreshRSS server.
@@ -166,6 +176,8 @@ Displays unread article count and total subscriptions from your FreshRSS server.
password: "<---your-password--->"
```
+Auto refresh is supported by this integration.
+
## Gatus
The Gatus service displays information about the configured services from the defined Gatus server.
@@ -189,6 +201,8 @@ The average times can be hidden (saves their calculation also) by setting the fo
hideaverages: true
```
+Auto refresh is supported by this integration.
+
## Gitea / Forgejo
Displays a Gitea / Forgejo version.
@@ -212,6 +226,8 @@ Displays system metrics (CPU, memory, swap, load) from a Glances server.
stats: [cpu, mem] # Options: load, cpu, mem, swap
```
+Auto refresh is supported by this integration.
+
If you don't already have a glances server up and running, here is a sample Docker compose file to get you started:
```yml
@@ -239,6 +255,8 @@ Displays the number of outstanding messages and system health status.
apikey: "<---insert-client-token-here--->"
```
+Auto refresh is supported by this integration.
+
**API Token**: Use a **client token** (not an app token).
## Healthchecks
@@ -252,6 +270,8 @@ Displays status counts (up/down/grace) from your Healthchecks monitoring service
apikey: "<---insert-api-key-here--->"
```
+Auto refresh is supported by this integration.
+
**API Key**: Found in Healthchecks web interface under **Settings > API Access > API key (read-only)**.
## Home Assistant
@@ -303,6 +323,8 @@ Displays user count, photo/video counts, and storage usage from your Immich serv
apikey: "<---insert-api-key-here--->"
```
+Auto refresh is supported by this integration.
+
**Requirements**: Immich server version `1.118.0` or later
**API Key**: Create an API key in Immich web interface under **Administration > API Keys**
@@ -318,6 +340,8 @@ Display the number of concurrent streams on your Jellyfin server.
apikey: "<---insert-api-key-here--->"
```
+Auto refresh is supported by this integration.
+
**API Key**: You can create an API key in the dashboard of you Jellystat server: settings/API Keys -> Add Key
@@ -334,6 +358,8 @@ Two lines are needed in the `config.yml`:
apikey: "<---insert-api-key-here--->"
```
+Auto refresh is supported by this integration.
+
The url must be the root url of Lidarr, Prowlarr, Readarr, Radarr or Sonarr application.
**API Key**: The Lidarr, Prowlarr, Readarr, Radarr or Sonarr API key can be found in `Settings` > `General`. It is needed to access the API.
@@ -356,6 +382,8 @@ This integration supports at max 15 results from Linkding, but you can add it mu
query: "#ToDo #Homer" # query to do on Linkding. Use #tagname to search for tags
```
+Auto refresh is supported by this integration.
+
## Matrix
Displays a Matrix version, and shows if the server is online.
@@ -411,6 +439,8 @@ Displays the number of unread articles from your Miniflux RSS reader.
style: "status" # Either "status" or "counter"
```
+Auto refresh is supported by this integration.
+
**API Key**: Generate an API key in Miniflux web interface under **Settings > API Keys > Create a new API key**
## NetAlertx
@@ -455,9 +485,10 @@ Moonraker's API mimics a few of OctoPrint's endpoints which makes these services
endpoint: "https://my-service-api.url:port"
apikey: "<---insert-api-key-here--->"
display: "text" # 'text' or 'bar'. Default to `text`.
-
```
+Auto refresh is supported by this integration.
+
## Olivetin
Displays a Olivetin version.
@@ -536,6 +567,8 @@ Displays current status and UPS load of the UPS device.
# device: "ups" # The ID of the device
```
+Auto refresh is supported by this integration.
+
## PiAlert
Displays stats from your PiAlert server.
@@ -547,6 +580,8 @@ Displays stats from your PiAlert server.
url: https://my-service.url
```
+Auto refresh is supported by this integration.
+
## PiHole
Displays info about your local PiHole instance right on your Homer dashboard.
@@ -561,6 +596,8 @@ Displays info about your local PiHole instance right on your Homer dashboard.
apiVersion: 5 # optional, defaults to 5. Use 6 if your PiHole instance uses API v6
```
+Auto refresh is supported by this integration.
+
**API Key**: Required only if Pi-hole web interface is password protected. Go to **Settings > API/Web Interface > Show API token**
**API Versions**:
@@ -590,6 +627,8 @@ Optionally, use `successCodes` to define which HTTP response status codes should
>>>>>>> c25ffaf (fix: auto update system adjustments)
```
+Auto refresh is supported by this integration.
+
## Plex
Displays active streams, total movies, and total TV series from your Plex server.
@@ -603,6 +642,8 @@ Displays active streams, total movies, and total TV series from your Plex server
token: "<---insert-plex-token-here--->"
```
+Auto refresh is supported by this integration.
+
**Plex Token**: See [How to find your Plex token](https://www.plexopedia.com/plex-media-server/general/plex-token/)
## Portainer
@@ -620,6 +661,8 @@ Displays container counts (running/dead/misc), version, and online status from y
- "local"
```
+Auto refresh is supported by this integration.
+
**Requirements**: Portainer version 1.11 or later
**API Key**: Generate an access token in Portainer UI. See [Creating an Access Token](https://docs.portainer.io/api/access#creating-an-access-token)
@@ -633,6 +676,8 @@ Displays container counts (running/dead/misc), version, and online status from y
url: https://my-service.url
```
+Auto refresh is supported by this integration.
+
## Proxmox
Displays status information of a Proxmox node (VMs running and disk, memory and cpu used).
@@ -653,6 +698,8 @@ Displays status information of a Proxmox node (VMs running and disk, memory and
small_font_on_desktop: true # uses small font on desktops (just in case you're showing much info)
```
+Auto refresh is supported by this integration.
+
**API Key**: You can set it up in Proxmox under Permissions > API Tokens. You also need to know the realm the user of the API Token is assigned to (by default pam).
The API Token (or the user assigned to that token if not separated permissions is checked) are this:
@@ -679,6 +726,8 @@ for setting up qBittorrent.
url: https://my-service.url # Your rTorrent web UI, f.e. ruTorrent or Flood.
```
+Auto refresh is supported by this integration.
+
## rTorrent
Displays the global upload and download rates, as well as the number of torrents
@@ -697,6 +746,8 @@ for setting up rTorrent.
password: "password" # Password for logging into rTorrent (if applicable).
```
+Auto refresh is supported by this integration.
+
## SABnzbd
Displays the number of currently active downloads on your SABnzbd instance.
@@ -709,6 +760,8 @@ Displays the number of currently active downloads on your SABnzbd instance.
apikey: "<---insert-api-key-here--->"
```
+Auto refresh is supported by this integration.
+
**API Key**: An API key is required, and can be obtained from the "Config" > "General" section of the SABnzbd config in the web UI.
## Scrutiny
@@ -722,6 +775,8 @@ Displays info about the total number of disk passed and failed S.M.A.R.T and scr
url: https://my-service.url
```
+Auto refresh is supported by this integration.
+
## SpeedtestTracker
Displays the download and upload speeds in Mbit/s and the ping in ms.
@@ -745,6 +800,8 @@ Displays the number of currently active streams on you Plex instance.
apikey: "<---insert-api-key-here--->"
```
+Auto refresh is supported by this integration.
+
**API Key**: An API key is required, and can be obtained from the "Web Interface" section of settings on the Tautulli web UI.
Because the service type and link don't necessarily have to match, you could
@@ -771,6 +828,8 @@ Displays the number of currently queued items for transcoding on your Tdarr inst
url: https://my-service.url
```
+Auto refresh is supported by this integration.
+
## Traefik
Displays Traefik.
@@ -799,6 +858,7 @@ The service communicates with the Transmission RPC interface which needs to be a
target: "_blank" # Optional: HTML a tag target attribute
```
+Auto refresh is supported by this integration.
The service automatically handles Transmission's session management and CSRF protection.
## Truenas Scale
@@ -825,6 +885,8 @@ Displays overall status, uptime percentage, and incident information from your U
slug: "default" # status page slug, defaults to "default"
```
+Auto refresh is supported by this integration.
+
**Requirements**: Uptime Kuma version `1.13.1` or later (for [multiple status pages support](https://github.com/louislam/uptime-kuma/releases/tag/1.13.1))
## Vaultwarden
@@ -860,3 +922,5 @@ Display info about the number of container running and the number for which an u
url: https://my-service.url
subtitle: "Docker image update notifier"
```
+
+Auto refresh is supported by this integration.
From 36477ae08d9e5029deaab26bc8001f01124d6029 Mon Sep 17 00:00:00 2001
From: Bastien Wirtz
Date: Sun, 25 Jan 2026 17:07:25 +0100
Subject: [PATCH 09/12] improve updater registration
---
src/mixins/service.js | 63 +++++++++++++++++++++++--------------------
1 file changed, 34 insertions(+), 29 deletions(-)
diff --git a/src/mixins/service.js b/src/mixins/service.js
index 45d31546e..8248c7ee5 100644
--- a/src/mixins/service.js
+++ b/src/mixins/service.js
@@ -85,47 +85,52 @@ export default {
},
initAutoUpdate: function () {
// Check if component has defined an auto-update method and interval
+ if (typeof this.autoUpdateMethod !== "function") {
+ return;
+ }
+
const interval = this.getUpdateInterval();
- if (
- interval > 0 &&
- this.autoUpdateMethod &&
- typeof this.autoUpdateMethod === "function"
- ) {
- updateScheduler.register(this, interval, this.autoUpdateMethod);
+ if (interval > 0) {
+ return;
}
+ updateScheduler.register(this, interval, this.autoUpdateMethod);
},
getUpdateInterval: function () {
+ let intervalKey = "updateIntervalMs";
+
+ if (!Object.hasOwn(this.item, intervalKey)) {
+ const deprecatedKeys = [
+ "checkInterval",
+ "downloadInterval",
+ "rateInterval",
+ "torrentInterval",
+ "updateInterval",
+ ];
+
+ for (const key of deprecatedKeys) {
+ if (Object.hasOwn(this.item, key)) {
+ console.warn(
+ `[DEPRECATED] Service "${this.item.name || "unknown"}" uses deprecated config key "${key}". ` +
+ `Please use "${intervalKey}" instead. Support for "${key}" will be removed in a future version.`,
+ );
+ intervalKey = key;
+ break;
+ }
+ }
+ }
+
+ let interval = this.item[intervalKey];
+
// Check if auto-update is explicitly disabled for this service
- if (
- this.item.updateInterval === false ||
- this.item.updateInterval === 0
- ) {
+ if (interval === false || interval === 0) {
return 0;
}
// Use service-specific interval if defined
- if (this.item.updateInterval) {
+ if (interval) {
return parseInt(this.item.updateInterval, 10) || 0;
}
- // Check for deprecated keys and warn users
- const deprecatedKeys = [
- "checkInterval",
- "downloadInterval",
- "rateInterval",
- "torrentInterval",
- ];
-
- for (const key of deprecatedKeys) {
- if (this.item[key]) {
- console.warn(
- `[DEPRECATED] Service "${this.item.name || "unknown"}" uses deprecated config key "${key}". ` +
- `Please use "updateInterval" instead. Support for "${key}" will be removed in a future version.`,
- );
- return parseInt(this.item[key], 10) || 0;
- }
- }
-
// Use global auto-update configuration
return this.getGlobalUpdateInterval();
},
From 4db4788f17ff224853e6f21b2da599221b4061d6 Mon Sep 17 00:00:00 2001
From: Bastien Wirtz
Date: Sat, 18 Apr 2026 16:04:36 +0200
Subject: [PATCH 10/12] fix: Documentation rebase fuckup -_-
---
docs/customservices.md | 9 +++------
1 file changed, 3 insertions(+), 6 deletions(-)
diff --git a/docs/customservices.md b/docs/customservices.md
index 061892022..1430166a8 100644
--- a/docs/customservices.md
+++ b/docs/customservices.md
@@ -454,9 +454,10 @@ Displays network monitoring stats (connected devices, alerts, network activity)
url: https://my-service.url
apikey: "<---insert-api-key-here--->"
# endpoint: "https://my-service-api.url" # Optional: alternative base URL used to fetch service data when necessary.
- updateInterval: 5000 # (Optional) Interval (in ms) for updating the stats
```
+Auto refresh is supported by this integration.
+
**API Key**: Get your API key in NetAlertx web interface under **Settings > General > API token** or in your installation documentation.
**Note**: NetAlertx is the modern fork/rename of PiAlert. Both integrations are available in Homer for compatibility.
@@ -620,14 +621,10 @@ Optionally, use `successCodes` to define which HTTP response status codes should
# successCodes: [200, 418] # Optional, default to all 2xx HTTP response status codes
# timeout: 500 # Timeout in ms before ping is aborted. Default 2000
# subtitle: "Bookmark example" # By default, request round trip time is displayed when subtitle is not set
-<<<<<<< HEAD
- # updateInterval: 5000 # (Optional) Interval (in ms) for updating ping status
# endpoint: "https://www.wikimediastatus.net" # Optional, will override url for pinging
-=======
->>>>>>> c25ffaf (fix: auto update system adjustments)
```
-Auto refresh is supported by this integration.
+Auto refresh is supported by this integration.
## Plex
From 6346a9468cf8f726aef1255a339ad7454676248a Mon Sep 17 00:00:00 2001
From: Bastien Wirtz
Date: Sat, 18 Apr 2026 16:05:14 +0200
Subject: [PATCH 11/12] fix(hyperHDR): missing return
---
src/components/services/HyperHDR.vue | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/components/services/HyperHDR.vue b/src/components/services/HyperHDR.vue
index c332a8d85..0ed70bc29 100644
--- a/src/components/services/HyperHDR.vue
+++ b/src/components/services/HyperHDR.vue
@@ -69,7 +69,7 @@ export default {
return 0;
}
- this.instances.length - this.running;
+ return this.instances.length - this.running;
},
status: function () {
From f4bdaaccc08f77cafa421357362d627bb6bdccc8 Mon Sep 17 00:00:00 2001
From: Bastien Wirtz
Date: Sat, 18 Apr 2026 16:05:43 +0200
Subject: [PATCH 12/12] feat: Scheduler migration for netalertx
---
src/components/services/NetAlertx.vue | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/components/services/NetAlertx.vue b/src/components/services/NetAlertx.vue
index 655b4d2fb..a9faba9bf 100644
--- a/src/components/services/NetAlertx.vue
+++ b/src/components/services/NetAlertx.vue
@@ -44,10 +44,10 @@ export default {
};
},
created() {
- const updateInterval = parseInt(this.item.updateInterval, 10) || 0;
- if (updateInterval > 0) {
- setInterval(() => this.fetchStatus(), updateInterval);
- }
+ // Set up auto-update method for the scheduler
+ this.autoUpdateMethod = this.fetchStatus;
+
+ // Initial data fetch
this.fetchStatus();
},
methods: {