From 335b7bee5a6684e50bb57ed5edd8a2ea319e7146 Mon Sep 17 00:00:00 2001 From: Striker72rus Date: Wed, 25 Mar 2026 15:35:16 +0300 Subject: [PATCH] - --- custom_components/wall_panel/__init__.py | 6 +- .../wall_panel/frontend/panel.js | 52 ++++++++------ custom_components/wall_panel/views.py | 68 +++++++++++++++++++ lib/config.php | 55 +++++++++++---- run.sh | 16 ++++- storage/popup_state.json | 4 +- wall_panel/config.yaml | 2 +- wall_panel/lib/config.php | 55 +++++++++++---- wall_panel/run.sh | 11 ++- 9 files changed, 211 insertions(+), 58 deletions(-) diff --git a/custom_components/wall_panel/__init__.py b/custom_components/wall_panel/__init__.py index 929304c..0726c88 100755 --- a/custom_components/wall_panel/__init__.py +++ b/custom_components/wall_panel/__init__.py @@ -7,7 +7,7 @@ from homeassistant.core import HomeAssistant from .const import CONF_CONFIG, DOMAIN from .frontend import async_setup_frontend from .helpers import current_entry_config -from .views import WallPanelConfigView +from .views import WallPanelConfigView, WallPanelPanelView async def async_setup_entry(hass: HomeAssistant, entry) -> bool: @@ -26,6 +26,10 @@ async def async_setup_entry(hass: HomeAssistant, entry) -> bool: hass.http.register_view(WallPanelConfigView) state["_config_view_registered"] = True + if not state.get("_panel_view_registered"): + hass.http.register_view(WallPanelPanelView) + state["_panel_view_registered"] = True + entry.async_on_unload(entry.add_update_listener(_async_options_updated)) return True diff --git a/custom_components/wall_panel/frontend/panel.js b/custom_components/wall_panel/frontend/panel.js index 42b38f8..0dced3e 100755 --- a/custom_components/wall_panel/frontend/panel.js +++ b/custom_components/wall_panel/frontend/panel.js @@ -62,6 +62,10 @@ class WallPanelPanel extends HTMLElement { } _configUrl() { + return '/api/wall_panel/panel'; + } + + _legacyConfigUrl() { const payload = this._panelConfig(); const entryId = String(payload.entry_id || '').trim(); if (!entryId) { @@ -72,33 +76,39 @@ class WallPanelPanel extends HTMLElement { async _fetchPanelUrl() { const payload = this._panelConfig(); - const configUrl = this._configUrl(); const syncToken = String(payload.sync_token || '').trim(); - if (!configUrl || !syncToken) { + if (!syncToken) { return ''; } - try { - const response = await fetch(configUrl, { - method: 'GET', - headers: { - 'X-Wall-Panel-Token': syncToken, - Accept: 'application/json', - }, - credentials: 'same-origin', - cache: 'no-store', - }); + const configUrls = [this._configUrl(), this._legacyConfigUrl()].filter(Boolean); + for (const configUrl of configUrls) { + try { + const response = await fetch(configUrl, { + method: 'GET', + headers: { + 'X-Wall-Panel-Token': syncToken, + Accept: 'application/json', + }, + credentials: 'same-origin', + cache: 'no-store', + }); - if (!response.ok) { - return ''; + if (!response.ok) { + continue; + } + + const data = await response.json(); + const panelUrl = String(data?.panel?.panel_url || data?.panel_url || '').trim(); + if (panelUrl) { + return panelUrl; + } + } catch (error) { + continue; } - - const data = await response.json(); - const panelUrl = String(data?.panel?.panel_url || data?.panel_url || '').trim(); - return panelUrl; - } catch (error) { - return ''; } + + return ''; } _renderMessage(title, body, extra = '') { @@ -200,7 +210,7 @@ class WallPanelPanel extends HTMLElement { return; } - const panelUrl = this._resolveUrl(await this._fetchPanelUrl()); + const panelUrl = this._resolveUrl(await this._fetchPanelUrl()); if (panelUrl && panelUrl === this._activePanelUrl) { return; } diff --git a/custom_components/wall_panel/views.py b/custom_components/wall_panel/views.py index 794db1d..2844286 100755 --- a/custom_components/wall_panel/views.py +++ b/custom_components/wall_panel/views.py @@ -30,6 +30,23 @@ def _entry_from_hass(hass: HomeAssistant, entry_id: str): return hass.data.get(DOMAIN, {}).get(entry_id, {}).get("entry") +def _default_entry_from_hass(hass: HomeAssistant): + entries = hass.data.get(DOMAIN, {}) + for value in entries.values(): + entry = value.get("entry") if isinstance(value, dict) else None + if entry is not None: + return entry + return None + + +def _current_entry(hass: HomeAssistant, entry_id: str | None = None): + if entry_id: + entry = _entry_from_hass(hass, entry_id) + if entry is not None: + return entry + return _default_entry_from_hass(hass) + + def _request_token(request: web.Request) -> str: header = request.headers.get("X-Wall-Panel-Token", "").strip() if header: @@ -180,3 +197,54 @@ class WallPanelConfigView(HomeAssistantView): "config": config, "panel": current_entry_panel(entry), }) + + +class WallPanelPanelView(HomeAssistantView): + """Serve the active Wall Panel config without an entry id.""" + + url = "/api/wall_panel/panel" + name = "api:wall_panel:panel" + requires_auth = False + + async def async_get(self, request: web.Request) -> web.Response: + hass = request.app["hass"] + entry = _default_entry_from_hass(hass) + if entry is None: + return _response({"ok": False, "error": "Unknown entry"}, 404) + if not _authorized(entry, request): + return _response({"ok": False, "error": "Unauthorized"}, 401) + + config = current_entry_config(entry) + return _response({ + "ok": True, + "config": config, + "panel": current_entry_panel(entry), + }) + + async def async_post(self, request: web.Request) -> web.Response: + hass = request.app["hass"] + entry = _default_entry_from_hass(hass) + if entry is None: + return _response({"ok": False, "error": "Unknown entry"}, 404) + + payload = await request.json() + if not isinstance(payload, dict): + return _response({"ok": False, "error": "Invalid payload"}, 400) + + action = str(payload.get("action", "") or "").strip().lower() + if action != "register-panel": + return _response({"ok": False, "error": "Unknown action"}, 404) + + action_payload = payload.get("payload") + if not isinstance(action_payload, dict): + action_payload = payload + + panel_url = str(action_payload.get("panel_url", "") or "").strip() + if not panel_url: + return _response({"ok": False, "error": "panel_url is required"}, 400) + + _save_entry_panel_url(hass, entry, panel_url) + return _response({ + "ok": True, + "panel": current_entry_panel(entry), + }) diff --git a/lib/config.php b/lib/config.php index f7cb050..27faf41 100755 --- a/lib/config.php +++ b/lib/config.php @@ -270,7 +270,17 @@ function app_supervisor_addon_slug(): string return $override; } - return 'wall_panel'; + return 'self'; +} + +function app_addon_register_panel_url(): string +{ + $override = trim((string)getenv('WALL_PANEL_REGISTER_URL')); + if ($override !== '') { + return $override; + } + + return 'http://supervisor/core/api/wall_panel/register-panel'; } function app_supervisor_ingress_url(): string @@ -292,35 +302,47 @@ function app_supervisor_ingress_url(): string return trim((string)($response['ingress_url'] ?? '')); } -function app_register_ingress_url(array $config): void +function app_register_ingress_url(array $config): bool { - if (!app_remote_sync_enabled($config)) { - return; + if (app_runtime_mode() !== 'addon') { + return false; } $ingressUrl = app_supervisor_ingress_url(); if ($ingressUrl === '') { - return; + return false; } - $syncUrl = trim((string)($config['home_assistant']['sync_url'] ?? '')); - $syncToken = trim((string)($config['home_assistant']['sync_token'] ?? '')); - $cacheKey = hash('sha256', $ingressUrl . '|' . $syncUrl . '|' . $syncToken); + $registerUrl = app_addon_register_panel_url(); + if ($registerUrl === '') { + return false; + } + + $cacheKey = hash('sha256', $ingressUrl . '|' . $registerUrl); $cachePath = app_panel_registration_cache_path(); $cache = app_load_json_file($cachePath, []); if (trim((string)($cache['cache_key'] ?? '')) === $cacheKey) { - return; + return true; } - $response = app_http_json_request('POST', $syncUrl, [ - 'X-Wall-Panel-Token: ' . $syncToken, - ], [ - 'action' => 'register-panel', - 'payload' => [ + $headers = []; + $supervisorToken = trim((string)getenv('SUPERVISOR_TOKEN')); + if ($supervisorToken !== '') { + $headers[] = 'Authorization: Bearer ' . $supervisorToken; + } + + $response = app_http_json_request( + 'POST', + $registerUrl, + $headers, + [ 'panel_url' => $ingressUrl, ], - ], max(1, (int)($config['home_assistant']['sync_timeout'] ?? 10)), (bool)($config['home_assistant']['sync_verify_ssl'] ?? true), false); + max(1, (int)($config['home_assistant']['sync_timeout'] ?? 10)), + false, + false + ); if (is_array($response) && (bool)($response['ok'] ?? false)) { app_save_json_file($cachePath, [ @@ -328,7 +350,10 @@ function app_register_ingress_url(array $config): void 'ingress_url' => $ingressUrl, 'registered_at' => time(), ]); + return true; } + + return false; } function app_load_remote_config(array $config, bool $refresh = false): array|null diff --git a/run.sh b/run.sh index d6da18f..71c7b28 100755 --- a/run.sh +++ b/run.sh @@ -5,6 +5,7 @@ DOCROOT="${WALL_PANEL_DOCROOT:-/app}" PORT="${WALL_PANEL_PORT:-8099}" CONFIG_PATH="${WALL_PANEL_CONFIG_PATH:-/config/config.json}" STORAGE_DIR="${WALL_PANEL_STORAGE_DIR:-/config/storage}" +RUNTIME_MODE="${WALL_PANEL_RUNTIME_MODE:-standalone}" mkdir -p "$(dirname "$CONFIG_PATH")" "$STORAGE_DIR" @@ -14,8 +15,19 @@ fi export WALL_PANEL_CONFIG_PATH="$CONFIG_PATH" export WALL_PANEL_STORAGE_DIR="$STORAGE_DIR" -export WALL_PANEL_RUNTIME_MODE="addon" +export WALL_PANEL_RUNTIME_MODE="$RUNTIME_MODE" -php -r 'require "/app/lib/bootstrap.php"; $config = app_load_config(); app_register_ingress_url($config);' >/dev/null 2>&1 || true & +if [ "$RUNTIME_MODE" = "addon" ]; then + ( + i=0 + while [ "$i" -lt 120 ]; do + if php -r 'require "/app/lib/bootstrap.php"; $config = app_load_config(); exit(app_register_ingress_url($config) ? 0 : 1);' >/dev/null 2>&1; then + exit 0 + fi + i=$((i + 1)) + sleep 2 + done + ) >/dev/null 2>&1 || true & +fi exec php -S "0.0.0.0:${PORT}" -t "$DOCROOT" diff --git a/storage/popup_state.json b/storage/popup_state.json index b9e994f..64c51a5 100755 --- a/storage/popup_state.json +++ b/storage/popup_state.json @@ -1,6 +1,6 @@ { "active": false, - "sensor_entity_id": "binary_sensor.barn_all_occupancy", - "opened_at": 1774439958, + "sensor_entity_id": "binary_sensor.doorbell_all_occupancy", + "opened_at": 1774441493, "expires_at": null } diff --git a/wall_panel/config.yaml b/wall_panel/config.yaml index 46be760..c00a38f 100755 --- a/wall_panel/config.yaml +++ b/wall_panel/config.yaml @@ -1,6 +1,6 @@ name: Wall Panel description: Wall Panel PHP interface as a Home Assistant add-on -version: "1.0.10" +version: "1.0.12" slug: wall_panel url: https://git.striker72rus.ru/PHP/wallpanell.git init: false diff --git a/wall_panel/lib/config.php b/wall_panel/lib/config.php index f7cb050..acccdc6 100755 --- a/wall_panel/lib/config.php +++ b/wall_panel/lib/config.php @@ -270,7 +270,7 @@ function app_supervisor_addon_slug(): string return $override; } - return 'wall_panel'; + return 'self'; } function app_supervisor_ingress_url(): string @@ -292,35 +292,57 @@ function app_supervisor_ingress_url(): string return trim((string)($response['ingress_url'] ?? '')); } -function app_register_ingress_url(array $config): void +function app_addon_register_panel_url(): string { - if (!app_remote_sync_enabled($config)) { - return; + $override = trim((string)getenv('WALL_PANEL_REGISTER_URL')); + if ($override !== '') { + return $override; + } + + return 'http://supervisor/core/api/wall_panel/register-panel'; +} + +function app_register_ingress_url(array $config): bool +{ + if (app_runtime_mode() !== 'addon') { + return false; } $ingressUrl = app_supervisor_ingress_url(); if ($ingressUrl === '') { - return; + return false; } - $syncUrl = trim((string)($config['home_assistant']['sync_url'] ?? '')); - $syncToken = trim((string)($config['home_assistant']['sync_token'] ?? '')); - $cacheKey = hash('sha256', $ingressUrl . '|' . $syncUrl . '|' . $syncToken); + $registerUrl = app_addon_register_panel_url(); + if ($registerUrl === '') { + return false; + } + + $cacheKey = hash('sha256', $ingressUrl . '|' . $registerUrl); $cachePath = app_panel_registration_cache_path(); $cache = app_load_json_file($cachePath, []); if (trim((string)($cache['cache_key'] ?? '')) === $cacheKey) { - return; + return true; } - $response = app_http_json_request('POST', $syncUrl, [ - 'X-Wall-Panel-Token: ' . $syncToken, - ], [ - 'action' => 'register-panel', - 'payload' => [ + $headers = []; + $supervisorToken = trim((string)getenv('SUPERVISOR_TOKEN')); + if ($supervisorToken !== '') { + $headers[] = 'Authorization: Bearer ' . $supervisorToken; + } + + $response = app_http_json_request( + 'POST', + $registerUrl, + $headers, + [ 'panel_url' => $ingressUrl, ], - ], max(1, (int)($config['home_assistant']['sync_timeout'] ?? 10)), (bool)($config['home_assistant']['sync_verify_ssl'] ?? true), false); + max(1, (int)($config['home_assistant']['sync_timeout'] ?? 10)), + false, + false + ); if (is_array($response) && (bool)($response['ok'] ?? false)) { app_save_json_file($cachePath, [ @@ -328,7 +350,10 @@ function app_register_ingress_url(array $config): void 'ingress_url' => $ingressUrl, 'registered_at' => time(), ]); + return true; } + + return false; } function app_load_remote_config(array $config, bool $refresh = false): array|null diff --git a/wall_panel/run.sh b/wall_panel/run.sh index 928aa3f..d2752fc 100755 --- a/wall_panel/run.sh +++ b/wall_panel/run.sh @@ -16,6 +16,15 @@ export WALL_PANEL_CONFIG_PATH="$CONFIG_PATH" export WALL_PANEL_STORAGE_DIR="$STORAGE_DIR" export WALL_PANEL_RUNTIME_MODE="addon" -php -r 'require "/app/lib/bootstrap.php"; $config = app_load_config(); app_register_ingress_url($config);' >/dev/null 2>&1 || true & +( + i=0 + while [ "$i" -lt 120 ]; do + if php -r 'require "/app/lib/bootstrap.php"; $config = app_load_config(); exit(app_register_ingress_url($config) ? 0 : 1);' >/dev/null 2>&1; then + exit 0 + fi + i=$((i + 1)) + sleep 2 + done +) >/dev/null 2>&1 || true & exec php -S "0.0.0.0:${PORT}" -t "$DOCROOT"