From c546fc160579990d540b5d942962227c0bef968b Mon Sep 17 00:00:00 2001 From: Cees Bos Date: Fri, 28 Feb 2025 16:13:28 +0100 Subject: [PATCH 001/121] Add YAML config schema for auto complete support --- .schema/config-schema.json | 353 +++++++++++++++++++++++++++++++++++++ docs/tips-and-tricks.md | 15 ++ 2 files changed, 368 insertions(+) create mode 100644 .schema/config-schema.json diff --git a/.schema/config-schema.json b/.schema/config-schema.json new file mode 100644 index 0000000..ddf0bec --- /dev/null +++ b/.schema/config-schema.json @@ -0,0 +1,353 @@ +{ + "$id": "https://raw.githubusercontent.com/bastienwirtz/homer/main/.schema/config-schema.json", + "$schema": "http://json-schema.org/draft-07/schema", + "description": "https://github.com/bastienwirtz/homer/blob/main/docs/configuration.md", + "examples": [], + "title": "Homer Dashboard configuration", + "type": "object", + "definitions": { + "Colors": { + "type": "object", + "additionalProperties": false, + "properties": { + "light": { + "$ref": "#/definitions/ColorSet" + }, + "dark": { + "$ref": "#/definitions/ColorSet" + } + }, + "title": "Colors" + }, + "ColorSet": { + "type": "object", + "additionalProperties": false, + "properties": { + "highlight-primary": { + "type": "string" + }, + "highlight-secondary": { + "type": "string" + }, + "highlight-hover": { + "type": "string" + }, + "background": { + "type": "string" + }, + "card-background": { + "type": "string" + }, + "text": { + "type": "string" + }, + "text-header": { + "type": "string" + }, + "text-title": { + "type": "string" + }, + "text-subtitle": { + "type": "string" + }, + "card-shadow": { + "type": "string" + }, + "link": { + "type": "string" + }, + "link-hover": { + "type": "string" + }, + "background-image": { + "type": "string" + } + } + }, + "Defaults": { + "type": "object", + "additionalProperties": false, + "properties": { + "layout": { + "enum": [ + "colums", + "list" + ], + "description": "Layout of the dashboard, either 'column' or 'list'" + }, + "colorTheme": { + "enum": [ + "auto", + "light", + "dark" + ], + "description": "One of 'auto', 'light', or 'dark'" + } + }, + "title": "Defaults" + }, + "Hotkey": { + "type": "object", + "additionalProperties": false, + "properties": { + "search": { + "type": "string", + "description": "hotkey for search, e.g. Shift" + } + }, + "required": [ + "search" + ] + }, + "Link": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "Name as soon in the navbar" + }, + "icon": { + "type": "string", + "description": "Fontawesome icon" + }, + "url": { + "type": "string", + "description": "Url of the link. When #filename is used, it is a link to another homer page, while 'filename' is the name of the config file" + }, + "target": { + "type": "string", + "description": "html tag target attribute like _blank for a new page" + } + }, + "required": [ + "name", + "url" + ], + "title": "Link" + }, + "Message": { + "type": "object", + "additionalProperties": false, + "properties": { + "url": { + "type": "string", + "format": "uri" + }, + "mapping": { + "$ref": "#/definitions/Mapping", + "description": "Mapping for the content loaded from the URL" + }, + "refreshInterval": { + "type": "integer", + "description": "The refresh interval in milliseconds for reloading the message url" + }, + "style": { + "type": "string", + "description": "See https://bulma.io/documentation/components/message/#colors for styling options" + }, + "title": { + "type": "string", + "description": "Title of the message box" + }, + "icon": { + "type": "string", + "description": "Fontawesome icon for the message box" + }, + "content": { + "type": "string", + "description": "HTML content for the message box" + } + }, + "title": "Messagebox" + }, + "Mapping": { + "type": "object", + "additionalProperties": true, + "title": "Mapping" + }, + "Proxy": { + "type": "object", + "additionalProperties": false, + "properties": { + "useCredentials": { + "type": "boolean", + "description": "# send cookies & authorization headers when fetching service specific data. Set to `true` if you use an authentication proxy. Can be overrided on service level. " + }, + "headers": { + "$ref": "#/definitions/Headers", + "description": "send custom headers when fetching service specific data. Can also be set on a service level." + } + }, + "title": "Proxy" + }, + "Headers": { + "type": "object", + "additionalProperties": true, + "title": "Headers" + }, + "Service": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string", + "description": "Service name" + }, + "icon": { + "type": "string", + "description": "Fontawesome icon for the service" + }, + "logo": { + "type": "string", + "description": "A path to an image can also be provided. Note that icon take precedence if both icon and logo are set." + }, + "class": { + "type": "string", + "description": "Optional css class to add on the service group. Example 'highlight-purple'" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/Item" + } + } + }, + "required": [ + "items" + ], + "title": "Service" + }, + "Item": { + "type": "object", + "additionalProperties": true, + "properties": { + "name": { + "type": "string" + }, + "logo": { + "type": "string", + "description": "Path to a logo. Alternatively a fa icon can be provided" + }, + "icon": { + "type": "string", + "description": "Fontawesome icon for the item, alternative for logo" + }, + "subtitle": { + "type": "string" + }, + "tag": { + "type": "string", + "description": "Show tag" + }, + "keywords": { + "type": "string", + "description": "Optional keyword used for searching purpose" + }, + "url": { + "type": "string", + "description": "Url of this item" + }, + "target": { + "type": "string", + "description": "html tag target attribute like _blank for a new page" + }, + "tagstyle": { + "type": "string", + "description": "Styleclass for the tag" + }, + "type": { + "type": "string", + "description": "Optional, loads a specific component that provides extra features. MUST MATCH a file name (without file extension) available in `src/components/services`" + } + }, + "title": "Item" + } + }, + "properties": { + "externalConfig": { + "type": "string", + "description": "Use external configuration file. Using this will ignore remaining config in this file externalConfig: https://example.com/server-luci/config.yaml" + }, + "title": { + "type": "string", + "description": "Title of the dashboard" + }, + "subtitle": { + "type": "string", + "description": "Subtitle of the dashboard" + }, + "documentTitle": { + "type": "string", + "description": "Title of the document. When not filled, title (and subtitle will be used)" + }, + "logo": { + "type": "string", + "description": "Path to logo image" + }, + "icon": { + "type": "string", + "description": "Dashboard icon" + }, + "header": { + "type": "boolean", + "description": "Show header, default is true" + }, + "hotkey": { + "$ref": "#/definitions/Hotkey", + "description": "Define hotkeys, for example for search" + }, + "footer": { + "type": "string", + "description": "HTML shown as footer" + }, + "columns": { + "type": "string", + "description": "'auto' or number (must be a factor of 12: 1, 2, 3, 4, 6, 12)", + "format": "integer" + }, + "connectivityCheck": { + "type": "boolean", + "description": "# whether you want to display a message when the apps are not accessible anymore (VPN disconnected for example). You should set it to true when using an authentication proxy, it also reloads the page when a redirection is detected when checking connectivity." + }, + "proxy": { + "$ref": "#/definitions/Proxy", + "description": "Optional: Proxy / hosting option" + }, + "defaults": { + "$ref": "#/definitions/Defaults" + }, + "theme": { + "type": "string", + "description": "'default' or one of the themes available in 'src/assets/themes'" + }, + "stylesheet": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Will load custom CSS files. Especially useful for custom icon sets. Entries are paths to the stylesheets" + }, + "colors": { + "$ref": "#/definitions/Colors" + }, + "message": { + "$ref": "#/definitions/Message", + "description": "Messagebox" + }, + "links": { + "description": "Links in the navigation bar", + "type": "array", + "items": { + "$ref": "#/definitions/Link" + } + }, + "services": { + "description": "Services", + "type": "array", + "items": { + "$ref": "#/definitions/Service" + } + } + } +} diff --git a/docs/tips-and-tricks.md b/docs/tips-and-tricks.md index 8317ecd..437b586 100644 --- a/docs/tips-and-tricks.md +++ b/docs/tips-and-tricks.md @@ -78,6 +78,21 @@ Then when Homer reads your config, it will substitute your anchors automatically The end result is that if you want to update the name or style of any particular tag, just update it once, in the tags section! Great if you have a lot of services or a lot of tags! +## YAML auto complete with a YAML schema + +A lot of editor support auto completion, see +The homer schema is available here: + +For example with IntelliJ you can define: + +```yaml +# $schema: https://raw.githubusercontent.com/bastienwirtz/homer/main/.schema/config-schema.json +``` +With VSCode you can define it like this: +```yaml +# yaml-language-server: $schema=https://raw.githubusercontent.com/bastienwirtz/homer/main/.schema/config-schema.json +``` + ## Remotely edit your config with Code Server #### `by @JamiePhonic` From 1b607b63571a5ca3e43e212185229c45cfd7c5c8 Mon Sep 17 00:00:00 2001 From: Cees Bos Date: Fri, 7 Mar 2025 16:34:58 +0100 Subject: [PATCH 002/121] Update schema based on review --- .schema/config-schema.json | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.schema/config-schema.json b/.schema/config-schema.json index ddf0bec..eaea0b7 100644 --- a/.schema/config-schema.json +++ b/.schema/config-schema.json @@ -105,7 +105,7 @@ "properties": { "name": { "type": "string", - "description": "Name as soon in the navbar" + "description": "Name as seen in the navbar" }, "icon": { "type": "string", @@ -121,7 +121,6 @@ } }, "required": [ - "name", "url" ], "title": "Link" @@ -298,8 +297,15 @@ "description": "Define hotkeys, for example for search" }, "footer": { - "type": "string", - "description": "HTML shown as footer" + "anyOf": [ + { + "type": "boolean" + }, + { + "type": "string" + } + ], + "description": "footer Line content. HTML is supported. Set false if you want to hide it." }, "columns": { "type": "string", From c812bda08f4e90499587867fcf698559f51e7707 Mon Sep 17 00:00:00 2001 From: Bastien Wirtz Date: Mon, 17 Mar 2025 21:47:21 +0100 Subject: [PATCH 003/121] fix(docker): disable log for healthcheck requests --- lighttpd.conf | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lighttpd.conf b/lighttpd.conf index 2965450..e892bb9 100644 --- a/lighttpd.conf +++ b/lighttpd.conf @@ -12,3 +12,7 @@ server.follow-symlink = "enable" server.feature-flags += ( "server.clock-jump-restart" => 0 ) server.max-request-field-size = 65535 accesslog.filename = "/dev/fd/3" + +# Avoid logging docker healthcheck request +$HTTP["remote-ip"] == "127.0.0.1" { accesslog.filename = "" } +$HTTP["remote-ip"] == "[::1]" { accesslog.filename = "" } From 76e6c7069605542b63987a3bc4fa434a74809969 Mon Sep 17 00:00:00 2001 From: Bastien Wirtz Date: Mon, 17 Mar 2025 21:54:56 +0100 Subject: [PATCH 004/121] release 25.03.3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a06b6f9..50bb9f5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homer", - "version": "25.03.2", + "version": "25.03.3", "type": "module", "scripts": { "dev": "vite", From 50acb9957e81f75bb28747ad1434132c3c5cb752 Mon Sep 17 00:00:00 2001 From: Cees Bos Date: Wed, 19 Mar 2025 22:25:34 +0100 Subject: [PATCH 005/121] Add Linkding support --- docs/customservices.md | 22 +++++++++ src/components/services/Linkding.vue | 68 ++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 src/components/services/Linkding.vue diff --git a/docs/customservices.md b/docs/customservices.md index 7ddaaaa..8274546 100644 --- a/docs/customservices.md +++ b/docs/customservices.md @@ -25,6 +25,7 @@ within Homer: - [Immich](#immich) - [Jellystat](#jellystat) - [Lidarr, Prowlarr, Sonarr, Readarr and Radarr](#lidarr-prowlarr-sonarr-readarr-and-radarr) +- [Linkding](#linkding) - [Mealie](#mealie) - [Medusa](#medusa) - [Nextcloud](#nextcloud) @@ -276,6 +277,27 @@ If you are using an older version of Radarr or Sonarr which don't support the ne legacyApi: true ``` +## Linkding + +This integration makes it possible to query Linkding and list multiple results from Linkding. +Linkding has to be configured with CORS enabled. Linkding does not support that, but a reverse proxy in front can fix that. +For example if you use Traefik, documentation about that is here: https://doc.traefik.io/traefik/middlewares/http/headers/#cors-headers +Examples for various servers can be found at https://enable-cors.org/server.html. + +This integration supports at max 15 results from Linkding. But you can add it multiple times to you dashboard with different queries to retrieve what you need. + +```yaml + - name: "Linkding" + # Url to Linkding instance + url: https://ld.ceesbos.nl + token: "" + type: "Linkding" + # Maximum number of items returned by Linkding, minimal 1 and max 15 + limit: 10 + # query to do on Linkding. User #tagname to search for tags + query: "#ToDo #Homer" +``` + ## Mealie First off make sure to remove an existing `subtitle` as it will take precedence if set. diff --git a/src/components/services/Linkding.vue b/src/components/services/Linkding.vue new file mode 100644 index 0000000..840650a --- /dev/null +++ b/src/components/services/Linkding.vue @@ -0,0 +1,68 @@ + + + \ No newline at end of file From 37716c8d423aa8351423c5ab5f1d677c01f99ef3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mathieu=20B=C3=A9langer?= <56379077+mbelangergit@users.noreply.github.com> Date: Wed, 19 Mar 2025 19:40:21 -0400 Subject: [PATCH 006/121] Fix typos in config-schema.json --- .schema/config-schema.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.schema/config-schema.json b/.schema/config-schema.json index eaea0b7..f751668 100644 --- a/.schema/config-schema.json +++ b/.schema/config-schema.json @@ -70,10 +70,10 @@ "properties": { "layout": { "enum": [ - "colums", + "columns", "list" ], - "description": "Layout of the dashboard, either 'column' or 'list'" + "description": "Layout of the dashboard, either 'columns' or 'list'" }, "colorTheme": { "enum": [ From e1fdb0069b67490553f99dfddb06dfb7bbc0f1d9 Mon Sep 17 00:00:00 2001 From: Cees Bos Date: Thu, 20 Mar 2025 18:27:59 +0100 Subject: [PATCH 007/121] Fix issues in Linkding integration revealed by code check in the pipeline --- src/components/services/Linkding.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/services/Linkding.vue b/src/components/services/Linkding.vue index 840650a..97aab9a 100644 --- a/src/components/services/Linkding.vue +++ b/src/components/services/Linkding.vue @@ -1,5 +1,5 @@ From 28ad80369ff4733d448cdf8bcb08010f3979b0f8 Mon Sep 17 00:00:00 2001 From: Molham Date: Sat, 19 Apr 2025 14:19:28 +0200 Subject: [PATCH 042/121] add support to dynamic interval time for polling the status --- docs/customservices.md | 3 ++- src/components/services/PiHole.vue | 8 ++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/docs/customservices.md b/docs/customservices.md index fc6d318..ac77ba3 100644 --- a/docs/customservices.md +++ b/docs/customservices.md @@ -468,10 +468,11 @@ The following configuration is available for the PiHole service. - name: "Pi-hole" logo: "assets/tools/sample.png" # subtitle: "Network-wide Ad Blocking" # optional, if no subtitle is defined, PiHole statistics will be shown - url: "http://192.168.0.151/admin" + url: "http://192.168.0.151/admin" # For v6 API, do not include /admin in the URL apikey: "<---insert-api-key-here--->" # optional, needed if web interface is password protected type: "PiHole" 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 ``` **Remarks:** diff --git a/src/components/services/PiHole.vue b/src/components/services/PiHole.vue index 004f382..9a20d42 100644 --- a/src/components/services/PiHole.vue +++ b/src/components/services/PiHole.vue @@ -68,8 +68,12 @@ export default { methods: { startStatusPolling: function () { this.fetchStatus(); - // Poll every 5 minutes - this.pollInterval = setInterval(this.fetchStatus, 300000); + // Set the interval to the checkInterval or default to 5 minutes + const interval = parseInt(this.item.checkInterval, 10) || 300000; + if (this.item.checkInterval < 1000) { + this.item.checkInterval = 1000; + } + this.pollInterval = setInterval(this.fetchStatus, interval); }, stopStatusPolling: function () { if (this.pollInterval) { From 4684b23a8c46a49c27960c00ee3b1f3583d1b63f Mon Sep 17 00:00:00 2001 From: Molham Date: Sat, 19 Apr 2025 14:46:18 +0200 Subject: [PATCH 043/121] better handle errors and set the subtitle as a message holder for errors --- src/components/services/PiHole.vue | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/components/services/PiHole.vue b/src/components/services/PiHole.vue index 9a20d42..a2b1ccf 100644 --- a/src/components/services/PiHole.vue +++ b/src/components/services/PiHole.vue @@ -42,7 +42,7 @@ export default { }), computed: { percentage: function () { - if (this.percent_blocked) { + if (typeof this.percent_blocked === 'number' && this.percent_blocked >= 0) { return this.percent_blocked.toFixed(1); } return ""; @@ -66,6 +66,11 @@ export default { } }, methods: { + handleError: function (error, status) { + console.error(error); + this.subtitle = error; + this.status = status; + }, startStatusPolling: function () { this.fetchStatus(); // Set the interval to the checkInterval or default to 5 minutes @@ -93,7 +98,7 @@ export default { } } } catch (e) { - console.error("Failed to load cached session:", e); + this.handleError(`Failed to load cached session: ${e}`, "error"); this.removeCacheSession(); } }, @@ -110,15 +115,14 @@ export default { method: 'DELETE' }); } catch (e) { - console.error("Failed to delete session:", e); + this.handleError(`Failed to delete session: ${e}`, "error"); } finally { this.removeCacheSession(); } }, authenticate: async function () { if (!this.item.apikey) { - console.error("API key is required for PiHole authentication"); - this.status = "disabled"; + this.handleError("API key is required for PiHole authentication", "disabled"); return false; } @@ -145,8 +149,7 @@ export default { } throw new Error("Invalid authentication response"); } catch (e) { - console.error("Authentication failed:", e); - this.status = "disabled"; + this.handleError(`Authentication failed: ${e}`, "disabled"); return false; } }, @@ -184,12 +187,11 @@ export default { throw new Error(`HTTP error: ${response.status}`); } } catch (e) { - console.error("Failed to fetch status:", e); - if (e.message.includes("HTTP error: 401") || e.message.includes("HTTP error: 403")) { + if (e.includes("HTTP error: 401") || e.includes("HTTP error: 403")) { this.removeCacheSession(); return this.retryWithDelay(); } - this.status = "disabled"; + this.handleError(`Failed to fetch status: ${e}`, "disabled"); this.removeCacheSession(); } }, @@ -214,8 +216,7 @@ export default { throw new Error(`HTTP error: ${response.status}`); } } catch (e) { - console.error("Failed to fetch v5 status:", e); - this.status = "error"; + this.handleError(`Failed to fetch v5 status: ${e}`, "error"); } }, } From 59b0ed76885fc07733e9cdb0814b9d3b25c0f69f Mon Sep 17 00:00:00 2001 From: Molham Date: Sat, 19 Apr 2025 14:48:10 +0200 Subject: [PATCH 044/121] set this.status after checking the response is OK --- src/components/services/PiHole.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/services/PiHole.vue b/src/components/services/PiHole.vue index a2b1ccf..353ccd0 100644 --- a/src/components/services/PiHole.vue +++ b/src/components/services/PiHole.vue @@ -170,11 +170,11 @@ export default { const url = `${this.endpoint}/${`api/stats/summary?sid=${encodeURIComponent(this.sessionId)}`.replace(/^\/+/, '')}`; const response = await fetch(url); - this.status = "enabled"; if (response.ok) { const result = await response.json(); if (result?.queries?.percent_blocked !== undefined) { + this.status = "enabled"; this.percent_blocked = result.queries.percent_blocked; this.retryCount = 0; } else { From 07207dca5521c7ff51982cfb3db22409b5439890 Mon Sep 17 00:00:00 2001 From: Molham Date: Sat, 19 Apr 2025 15:19:08 +0200 Subject: [PATCH 045/121] reset the api_v5 function to fetch the remote api with this.fetch --- src/components/services/PiHole.vue | 37 ++++++++++-------------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/src/components/services/PiHole.vue b/src/components/services/PiHole.vue index 353ccd0..bb9afc0 100644 --- a/src/components/services/PiHole.vue +++ b/src/components/services/PiHole.vue @@ -42,7 +42,7 @@ export default { }), computed: { percentage: function () { - if (typeof this.percent_blocked === 'number' && this.percent_blocked >= 0) { + if (this.percent_blocked) { return this.percent_blocked.toFixed(1); } return ""; @@ -187,37 +187,24 @@ export default { throw new Error(`HTTP error: ${response.status}`); } } catch (e) { - if (e.includes("HTTP error: 401") || e.includes("HTTP error: 403")) { + if (e.message.includes("HTTP error: 401") || e.message.includes("HTTP error: 403")) { this.removeCacheSession(); return this.retryWithDelay(); } - this.handleError(`Failed to fetch status: ${e}`, "disabled"); + this.handleError(`Failed to fetch status: ${e.message || e}`, "disabled"); this.removeCacheSession(); } }, async fetchStatus_v5() { - try { - const params = {}; - if (this.item.apikey) { - params.auth = this.item.apikey; - } - const url = new URL(`${this.endpoint}/api.php`); - Object.keys(params).forEach(key => url.searchParams.append(key, params[key])); - - const response = await fetch(url); - this.status = response.status.toString(); - - if (response.ok) { - const result = await response.json(); - if (result?.ads_percentage_today !== undefined) { - this.percent_blocked = result.ads_percentage_today; - } - } else { - throw new Error(`HTTP error: ${response.status}`); - } - } catch (e) { - this.handleError(`Failed to fetch v5 status: ${e}`, "error"); - } + const authQueryParams = this.item.apikey + ? `?summaryRaw&auth=${this.item.apikey}` + : ""; + const result = await this.fetch(`/api.php${authQueryParams}`).catch((e) => + this.handleError(`Failed to fetch status: ${e}`, "disabled"), + ); + + this.status = result.status; + this.percent_blocked = result.ads_percentage_today; }, } }; From 9e314c960b2612163dd3320c347deebec4513e51 Mon Sep 17 00:00:00 2001 From: Molham Date: Sat, 19 Apr 2025 15:31:19 +0200 Subject: [PATCH 046/121] fix integration test --- src/components/services/PiHole.vue | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/components/services/PiHole.vue b/src/components/services/PiHole.vue index bb9afc0..afbc5c6 100644 --- a/src/components/services/PiHole.vue +++ b/src/components/services/PiHole.vue @@ -39,6 +39,8 @@ export default { retryCount: 0, maxRetries: 3, retryDelay: 5000, + localCheckInterval: 1000, // Default value or a fallback + pollInterval: null, }), computed: { percentage: function () { @@ -53,6 +55,8 @@ 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(); } else { @@ -73,12 +77,10 @@ export default { }, startStatusPolling: function () { this.fetchStatus(); - // Set the interval to the checkInterval or default to 5 minutes - const interval = parseInt(this.item.checkInterval, 10) || 300000; - if (this.item.checkInterval < 1000) { - this.item.checkInterval = 1000; + if (this.localCheckInterval < 1000) { + this.localCheckInterval = 1000; } - this.pollInterval = setInterval(this.fetchStatus, interval); + this.pollInterval = setInterval(this.fetchStatus, this.localCheckInterval); }, stopStatusPolling: function () { if (this.pollInterval) { From ad76093a389f799f029acba3b1d94afb21c55193 Mon Sep 17 00:00:00 2001 From: Molham Date: Sat, 19 Apr 2025 21:10:44 +0200 Subject: [PATCH 047/121] modify service.js mixin to accept one more parameter and return full response and use this.fetch in PiHole.vue --- src/components/services/PiHole.vue | 10 +++++++--- src/mixins/service.js | 6 +++++- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/components/services/PiHole.vue b/src/components/services/PiHole.vue index afbc5c6..1703bf3 100644 --- a/src/components/services/PiHole.vue +++ b/src/components/services/PiHole.vue @@ -169,10 +169,14 @@ export default { const authenticated = await this.authenticate(); if (!authenticated) return; } + const options = { + method: "GET", + headers: { + 'Content-Type': 'application/json' + } + }; + const response = await this.fetch(`api/stats/summary?sid=${encodeURIComponent(this.sessionId)}`, options, false, true); - const url = `${this.endpoint}/${`api/stats/summary?sid=${encodeURIComponent(this.sessionId)}`.replace(/^\/+/, '')}`; - const response = await fetch(url); - if (response.ok) { const result = await response.json(); if (result?.queries?.percent_blocked !== undefined) { diff --git a/src/mixins/service.js b/src/mixins/service.js index d52fc29..f1e77bf 100644 --- a/src/mixins/service.js +++ b/src/mixins/service.js @@ -12,7 +12,7 @@ export default { } }, methods: { - fetch: function (path, init, json = true) { + fetch: function (path, init, json = true, returnFullResponse = false) { let options = {}; if (this.proxy?.useCredentials) { @@ -58,6 +58,10 @@ export default { ); } + if (returnFullResponse) { + return response; + } + return json ? response.json() : response.text(); }); }, From 42f3a3ee71e236f6a3871febbafd8a11a83fae85 Mon Sep 17 00:00:00 2001 From: Molham Date: Sun, 20 Apr 2025 00:58:37 +0200 Subject: [PATCH 048/121] reset the mixin service.js and make use of the actual error message from the current service.js --- src/components/services/PiHole.vue | 27 ++++++++++----------------- src/mixins/service.js | 6 +----- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/src/components/services/PiHole.vue b/src/components/services/PiHole.vue index 1703bf3..654cd86 100644 --- a/src/components/services/PiHole.vue +++ b/src/components/services/PiHole.vue @@ -44,7 +44,7 @@ export default { }), computed: { percentage: function () { - if (this.percent_blocked) { + if (this.percent_blocked >= 0) { return this.percent_blocked.toFixed(1); } return ""; @@ -156,6 +156,7 @@ export default { } }, retryWithDelay: async function () { + console.log("Retrying authentication..."); if (this.retryCount < this.maxRetries) { this.retryCount++; await new Promise(resolve => setTimeout(resolve, this.retryDelay)); @@ -175,25 +176,17 @@ export default { 'Content-Type': 'application/json' } }; - const response = await this.fetch(`api/stats/summary?sid=${encodeURIComponent(this.sessionId)}`, options, false, true); + const response = await this.fetch(`api/stats/summary?sid=${encodeURIComponent(this.sessionId)}`); - if (response.ok) { - const result = await response.json(); - if (result?.queries?.percent_blocked !== undefined) { - this.status = "enabled"; - this.percent_blocked = result.queries.percent_blocked; - this.retryCount = 0; - } else { - throw new Error("Invalid response format"); - } - } else if (response.status === 401) { - this.removeCacheSession(); - return this.retryWithDelay(); - } else { - throw new Error(`HTTP error: ${response.status}`); + if (response?.queries?.percent_blocked === undefined) { + throw new Error("Invalid response format"); } + + this.status = "enabled"; + this.percent_blocked = response.queries.percent_blocked; + this.retryCount = 0; } catch (e) { - if (e.message.includes("HTTP error: 401") || e.message.includes("HTTP error: 403")) { + if (e.message.includes("401 error") || e.message.includes("403 error")) { this.removeCacheSession(); return this.retryWithDelay(); } diff --git a/src/mixins/service.js b/src/mixins/service.js index f1e77bf..d52fc29 100644 --- a/src/mixins/service.js +++ b/src/mixins/service.js @@ -12,7 +12,7 @@ export default { } }, methods: { - fetch: function (path, init, json = true, returnFullResponse = false) { + fetch: function (path, init, json = true) { let options = {}; if (this.proxy?.useCredentials) { @@ -58,10 +58,6 @@ export default { ); } - if (returnFullResponse) { - return response; - } - return json ? response.json() : response.text(); }); }, From 15f59b9e36aae7abf72ba1bd075a7e7617795c66 Mon Sep 17 00:00:00 2001 From: Molham Date: Sun, 20 Apr 2025 01:02:27 +0200 Subject: [PATCH 049/121] delete unused object --- src/components/services/PiHole.vue | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/components/services/PiHole.vue b/src/components/services/PiHole.vue index 654cd86..55656e9 100644 --- a/src/components/services/PiHole.vue +++ b/src/components/services/PiHole.vue @@ -170,12 +170,6 @@ export default { const authenticated = await this.authenticate(); if (!authenticated) return; } - const options = { - method: "GET", - headers: { - 'Content-Type': 'application/json' - } - }; const response = await this.fetch(`api/stats/summary?sid=${encodeURIComponent(this.sessionId)}`); if (response?.queries?.percent_blocked === undefined) { From 347a3d062b08fcfd54fd9aefe75ab435375efdf5 Mon Sep 17 00:00:00 2001 From: Molham Date: Wed, 30 Apr 2025 13:59:01 +0200 Subject: [PATCH 050/121] remove deleteing the session over the API call when leaving the page --- src/components/services/PiHole.vue | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/components/services/PiHole.vue b/src/components/services/PiHole.vue index 55656e9..8ca67b8 100644 --- a/src/components/services/PiHole.vue +++ b/src/components/services/PiHole.vue @@ -66,7 +66,6 @@ export default { beforeDestroy() { if (parseInt(this.item.apiVersion, 10) === 6) { this.stopStatusPolling(); - this.deleteSession(); } }, methods: { @@ -109,19 +108,6 @@ export default { this.sessionId = null; this.sessionExpiry = null; }, - deleteSession: async function () { - if (!this.sessionId) return; - - try { - await this.fetch(`/api/auth/session/${encodeURIComponent(this.sessionId)}`, { - method: 'DELETE' - }); - } catch (e) { - this.handleError(`Failed to delete session: ${e}`, "error"); - } finally { - this.removeCacheSession(); - } - }, authenticate: async function () { if (!this.item.apikey) { this.handleError("API key is required for PiHole authentication", "disabled"); From acb304adeca43500c04be6c1e5e8465ea0ed3e88 Mon Sep 17 00:00:00 2001 From: Bastien Wirtz Date: Sun, 4 May 2025 14:15:57 +0200 Subject: [PATCH 051/121] deps update --- package.json | 8 ++++---- pnpm-lock.yaml | 38 +++++++++++++++++++------------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/package.json b/package.json index cec7286..54584ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homer", - "version": "25.04.1", + "version": "25.05.1", "type": "module", "scripts": { "dev": "vite", @@ -11,10 +11,10 @@ }, "dependencies": { "@fortawesome/fontawesome-free": "^6.7.2", - "bulma": "^1.0.3", + "bulma": "^1.0.4", "lodash.merge": "^4.6.2", "vue": "^3.5.13", - "yaml": "^2.7.0" + "yaml": "^2.7.1" }, "devDependencies": { "@eslint/js": "^9.21.0", @@ -30,7 +30,7 @@ "vite-plugin-pwa": "^0.21.1" }, "license": "Apache-2.0", - "packageManager": "pnpm@10.4.1+sha512.c753b6c3ad7afa13af388fa6d808035a008e30ea9993f58c6663e2bc5ff21679aa834db094987129aa4d488b86df57f7b634981b2f827cdcacc698cc0cfb88af", + "packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39", "pnpm": { "neverBuiltDependencies": [] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 24694e8..467bd5f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ importers: specifier: ^6.7.2 version: 6.7.2 bulma: - specifier: ^1.0.3 - version: 1.0.3 + specifier: ^1.0.4 + version: 1.0.4 lodash.merge: specifier: ^4.6.2 version: 4.6.2 @@ -21,15 +21,15 @@ importers: specifier: ^3.5.13 version: 3.5.13 yaml: - specifier: ^2.7.0 - version: 2.7.0 + specifier: ^2.7.1 + version: 2.7.1 devDependencies: '@eslint/js': specifier: ^9.21.0 version: 9.21.0 '@vitejs/plugin-vue': specifier: ^5.2.1 - version: 5.2.1(vite@6.1.6(sass-embedded@1.85.0)(terser@5.39.0)(yaml@2.7.0))(vue@3.5.13) + version: 5.2.1(vite@6.1.6(sass-embedded@1.85.0)(terser@5.39.0)(yaml@2.7.1))(vue@3.5.13) '@vue/eslint-config-prettier': specifier: ^10.2.0 version: 10.2.0(eslint@9.21.0)(prettier@3.5.2) @@ -53,10 +53,10 @@ importers: version: 1.85.0 vite: specifier: ^6.1.6 - version: 6.1.6(sass-embedded@1.85.0)(terser@5.39.0)(yaml@2.7.0) + version: 6.1.6(sass-embedded@1.85.0)(terser@5.39.0)(yaml@2.7.1) vite-plugin-pwa: specifier: ^0.21.1 - version: 0.21.1(vite@6.1.6(sass-embedded@1.85.0)(terser@5.39.0)(yaml@2.7.0))(workbox-build@7.3.0)(workbox-window@7.3.0) + version: 0.21.1(vite@6.1.6(sass-embedded@1.85.0)(terser@5.39.0)(yaml@2.7.1))(workbox-build@7.3.0)(workbox-window@7.3.0) packages: @@ -1116,8 +1116,8 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - bulma@1.0.3: - resolution: {integrity: sha512-9eVXBrXwlU337XUXBjIIq7i88A+tRbJYAjXQjT/21lwam+5tpvKF0R7dCesre9N+HV9c6pzCNEPKrtgvBBes2g==} + bulma@1.0.4: + resolution: {integrity: sha512-Ffb6YGXDiZYX3cqvSbHWqQ8+LkX6tVoTcZuVB3lm93sbAVXlO0D6QlOTMnV6g18gILpAXqkG2z9hf9z4hCjz2g==} call-bind-apply-helpers@1.0.2: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} @@ -2523,8 +2523,8 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - yaml@2.7.0: - resolution: {integrity: sha512-+hSoy/QHluxmC9kCIJyL/uyFmLmc+e5CFR5Wa+bpIhIj85LVb9ZH2nVnqrHoSvKogwODv0ClqZkmiSSaIH5LTA==} + yaml@2.7.1: + resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==} engines: {node: '>= 14'} hasBin: true @@ -3480,9 +3480,9 @@ snapshots: '@types/trusted-types@2.0.7': {} - '@vitejs/plugin-vue@5.2.1(vite@6.1.6(sass-embedded@1.85.0)(terser@5.39.0)(yaml@2.7.0))(vue@3.5.13)': + '@vitejs/plugin-vue@5.2.1(vite@6.1.6(sass-embedded@1.85.0)(terser@5.39.0)(yaml@2.7.1))(vue@3.5.13)': dependencies: - vite: 6.1.6(sass-embedded@1.85.0)(terser@5.39.0)(yaml@2.7.0) + vite: 6.1.6(sass-embedded@1.85.0)(terser@5.39.0)(yaml@2.7.1) vue: 3.5.13 '@vue/compiler-core@3.5.13': @@ -3657,7 +3657,7 @@ snapshots: buffer-from@1.1.2: {} - bulma@1.0.3: {} + bulma@1.0.4: {} call-bind-apply-helpers@1.0.2: dependencies: @@ -4998,18 +4998,18 @@ snapshots: varint@6.0.0: {} - vite-plugin-pwa@0.21.1(vite@6.1.6(sass-embedded@1.85.0)(terser@5.39.0)(yaml@2.7.0))(workbox-build@7.3.0)(workbox-window@7.3.0): + vite-plugin-pwa@0.21.1(vite@6.1.6(sass-embedded@1.85.0)(terser@5.39.0)(yaml@2.7.1))(workbox-build@7.3.0)(workbox-window@7.3.0): dependencies: debug: 4.4.0 pretty-bytes: 6.1.1 tinyglobby: 0.2.12 - vite: 6.1.6(sass-embedded@1.85.0)(terser@5.39.0)(yaml@2.7.0) + vite: 6.1.6(sass-embedded@1.85.0)(terser@5.39.0)(yaml@2.7.1) workbox-build: 7.3.0 workbox-window: 7.3.0 transitivePeerDependencies: - supports-color - vite@6.1.6(sass-embedded@1.85.0)(terser@5.39.0)(yaml@2.7.0): + vite@6.1.6(sass-embedded@1.85.0)(terser@5.39.0)(yaml@2.7.1): dependencies: esbuild: 0.24.2 postcss: 8.5.3 @@ -5018,7 +5018,7 @@ snapshots: fsevents: 2.3.3 sass-embedded: 1.85.0 terser: 5.39.0 - yaml: 2.7.0 + yaml: 2.7.1 vue-eslint-parser@9.4.3(eslint@9.21.0): dependencies: @@ -5219,6 +5219,6 @@ snapshots: yallist@3.1.1: {} - yaml@2.7.0: {} + yaml@2.7.1: {} yocto-queue@0.1.0: {} From a5eeb1e44e5bc550f81ef95b8dd8f44075e1b326 Mon Sep 17 00:00:00 2001 From: Bastien Wirtz Date: Sun, 4 May 2025 15:12:06 +0200 Subject: [PATCH 052/121] feat(smart-cards): handle dynamic loading error --- src/assets/components/base.scss | 4 ++++ src/components/Service.vue | 10 ++++++---- src/components/services/OpenWeather.vue | 12 ++++++++---- src/components/services/_error.vue | 22 ++++++++++++++++++++++ 4 files changed, 40 insertions(+), 8 deletions(-) create mode 100644 src/components/services/_error.vue diff --git a/src/assets/components/base.scss b/src/assets/components/base.scss index 87c7455..b88211e 100644 --- a/src/assets/components/base.scss +++ b/src/assets/components/base.scss @@ -55,6 +55,10 @@ background-color: var(--card-background); } } + .component-error .card { + border: 1px solid rgba(255, 33, 33, 0.664); + background-color: rgba(255, 58, 58, 0.24); + } .message { .message-body { diff --git a/src/components/Service.vue b/src/components/Service.vue index 405bf10..f250aae 100644 --- a/src/components/Service.vue +++ b/src/components/Service.vue @@ -5,7 +5,7 @@ From 1afa0afd00428f320fa8dd5108d5caee51e4f245 Mon Sep 17 00:00:00 2001 From: Bastien Wirtz Date: Sun, 4 May 2025 15:15:12 +0200 Subject: [PATCH 053/121] chore(lint): Apply lint --- src/components/services/DockerSocketProxy.vue | 28 ++++++-- src/components/services/PiHole.vue | 68 ++++++++++++------ src/components/services/Plex.vue | 70 +++++++++++-------- src/components/services/SABnzbd.vue | 3 +- 4 files changed, 109 insertions(+), 60 deletions(-) diff --git a/src/components/services/DockerSocketProxy.vue b/src/components/services/DockerSocketProxy.vue index ab33709..444f11a 100644 --- a/src/components/services/DockerSocketProxy.vue +++ b/src/components/services/DockerSocketProxy.vue @@ -2,16 +2,28 @@