diff --git a/data/config/mosquitto/public/default-dynamic-security.json b/data/config/mosquitto/public/default-dynamic-security.json index b6d2bac6ec..36c798d93e 100644 --- a/data/config/mosquitto/public/default-dynamic-security.json +++ b/data/config/mosquitto/public/default-dynamic-security.json @@ -398,6 +398,12 @@ "priority": 0, "allow": true }, + { + "acltype": "publishClientReceive", + "topic": "openWB/optional/int_display/display_url", + "priority": 0, + "allow": true + }, { "acltype": "publishClientReceive", "topic": "openWB/optional/int_display/theme", @@ -1252,6 +1258,12 @@ "priority": 0, "allow": true }, + { + "acltype": "publishClientSend", + "topic": "openWB/set/optional/int_display/display_url", + "priority": 0, + "allow": true + }, { "acltype": "publishClientSend", "topic": "openWB/set/optional/int_display/rotation", @@ -1294,6 +1306,12 @@ "priority": 0, "allow": true }, + { + "acltype": "publishClientReceive", + "topic": "openWB/optional/int_display/display_url", + "priority": 0, + "allow": true + }, { "acltype": "publishClientReceive", "topic": "openWB/optional/int_display/rotation", diff --git a/packages/control/optional_data.py b/packages/control/optional_data.py index 66260f6804..caa67b2a3b 100644 --- a/packages/control/optional_data.py +++ b/packages/control/optional_data.py @@ -137,6 +137,7 @@ class InternalDisplay: detected: bool = field(default=False, metadata={"topic": "int_display/detected"}) on_if_plugged_in: bool = field(default=True, metadata={"topic": "int_display/on_if_plugged_in"}) only_local_charge_points: bool = field(default=False, metadata={"topic": "int_display/only_local_charge_points"}) + display_url: Optional[str] = field(default=None, metadata={"topic": "int_display/display_url"}) pin_active: bool = field(default=False, metadata={"topic": "int_display/pin_active"}) pin_code: str = field(default="0000", metadata={"topic": "int_display/pin_code"}) rotation: int = field(default=0, metadata={"topic": "int_display/rotation"}) diff --git a/packages/helpermodules/setdata.py b/packages/helpermodules/setdata.py index 256e34d37c..a094e328ad 100644 --- a/packages/helpermodules/setdata.py +++ b/packages/helpermodules/setdata.py @@ -6,6 +6,7 @@ from threading import Event from typing import List, Optional, Tuple import re +from urllib.parse import urlsplit import paho.mqtt.client as mqtt import logging @@ -23,6 +24,16 @@ TIMESTAMP_2100 = 4102441200 # 01.01.2100 00:00:00 +def _is_valid_display_url(value: str) -> bool: + if not value: + return True + try: + parsed_url = urlsplit(value) + except ValueError: + return False + return parsed_url.scheme.lower() in ("http", "https") and bool(parsed_url.hostname) + + class SetData: def __init__(self, event_ev_template: Event, @@ -956,6 +967,13 @@ def process_optional_topic(self, msg: mqtt.MQTTMessage): self._validate_value(msg, bool) elif "openWB/set/optional/int_display/pin_code" in msg.topic: self._validate_value(msg, str) + elif "openWB/set/optional/int_display/display_url" in msg.topic: + display_url = decode_payload(msg.payload) + if isinstance(display_url, str) and not _is_valid_display_url(display_url): + log.error("Payload ungültig: Topic %s, display URL must use HTTP or HTTPS.", msg.topic) + Pub().pub(msg.topic, "") + else: + self._validate_value(msg, str) elif "openWB/set/optional/int_display/standby" in msg.topic: self._validate_value(msg, int, [(0, 600)]) elif "openWB/set/optional/int_display/theme" in msg.topic: diff --git a/packages/helpermodules/update_config.py b/packages/helpermodules/update_config.py index aa2e69aa40..3e320bb638 100644 --- a/packages/helpermodules/update_config.py +++ b/packages/helpermodules/update_config.py @@ -446,6 +446,7 @@ class UpdateConfig: "^openWB/optional/int_display/rotation$", "^openWB/optional/int_display/theme$", "^openWB/optional/int_display/only_local_charge_points$", + "^openWB/optional/int_display/display_url$", "^openWB/optional/monitoring/config$", "^openWB/optional/rfid/active$", "^openWB/optional/ocpp/config$", @@ -752,6 +753,7 @@ class UpdateConfig: ("openWB/optional/int_display/rotation", 0), ("openWB/optional/int_display/theme", asdict(CardsDisplayTheme())), ("openWB/optional/int_display/only_local_charge_points", False), + ("openWB/optional/int_display/display_url", None), ("openWB/optional/monitoring/config", NO_MODULE), ("openWB/optional/ocpp/config", asdict(OcppConfig())), ("openWB/optional/rfid/active", False), diff --git a/web/display/processAllMqttMsg.js b/web/display/processAllMqttMsg.js index 0a4fca7395..e59f5820bf 100644 --- a/web/display/processAllMqttMsg.js +++ b/web/display/processAllMqttMsg.js @@ -1,4 +1,21 @@ var credentialsFetched = false; +var pendingDisplayNavigationTimeout = null; +var pendingDisplayNavigationDestination = null; + +function scheduleDisplayNavigation(destination) { + if (destination === pendingDisplayNavigationDestination) { + return; + } + if (pendingDisplayNavigationTimeout !== null) { + clearTimeout(pendingDisplayNavigationTimeout); + } + pendingDisplayNavigationDestination = destination; + pendingDisplayNavigationTimeout = setTimeout(() => { + pendingDisplayNavigationTimeout = null; + pendingDisplayNavigationDestination = null; + location.href = destination; + }, 2000); +} function setIframeSource() { if (allTopicsReceived()) { @@ -47,12 +64,16 @@ function setIframeSource() { break; } // load display from primary or local - destination = `${location.protocol}//${host}/openWB/web/display/?${query.toString()}`; - addLog(`all done, loading theme from primary`); + const configuredDisplayUrl = data["openWB/optional/int_display/display_url"]; + if (configuredDisplayUrl && configuredDisplayUrl.trim()) { + destination = configuredDisplayUrl.trim(); + addLog(`all done, loading manually configured display URL`); + } else { + destination = `${location.protocol}//${host}/openWB/web/display/?${query.toString()}`; + addLog(`all done, loading theme from primary`); + } // no iframe here as this would result in another nesting with the wrapper on primary - setTimeout(() => { - location.href = destination; - }, 2000); + scheduleDisplayNavigation(destination); } else { // load primary display (from primary or secondary openWB) host = location.host; @@ -103,12 +124,12 @@ function setIframeSource() { function addLog(message, forceDisplay = false) { const logElement = document.getElementById('log'); - let displayedMessages = logElement.innerHTML.split("\n"); + let displayedMessages = logElement.textContent.split("\n"); if (displayedMessages.length > 25) { displayedMessages.shift(); } displayedMessages.push(message); - logElement.innerHTML = displayedMessages.join("\n"); + logElement.textContent = displayedMessages.join("\n"); if (forceDisplay) { logElement.classList.remove("hide"); } diff --git a/web/display/setupMqttServices.js b/web/display/setupMqttServices.js index 8021930cd7..fb254e8bc0 100644 --- a/web/display/setupMqttServices.js +++ b/web/display/setupMqttServices.js @@ -20,6 +20,7 @@ var secondaryTopicsToSubscribe = { "openWB/system/current_branch": false, "openWB/system/current_commit": false, "openWB/general/extern_display_mode": false, + "openWB/optional/int_display/display_url": false, "openWB/internal_chargepoint/global_data": false, "openWB/internal_chargepoint/0/data/parent_cp": false, "openWB/internal_chargepoint/1/data/parent_cp": false,