This commit is contained in:
Striker72rus 2026-03-25 15:35:16 +03:00
parent ed73223ee1
commit 335b7bee5a
9 changed files with 211 additions and 58 deletions

View File

@ -7,7 +7,7 @@ from homeassistant.core import HomeAssistant
from .const import CONF_CONFIG, DOMAIN from .const import CONF_CONFIG, DOMAIN
from .frontend import async_setup_frontend from .frontend import async_setup_frontend
from .helpers import current_entry_config from .helpers import current_entry_config
from .views import WallPanelConfigView from .views import WallPanelConfigView, WallPanelPanelView
async def async_setup_entry(hass: HomeAssistant, entry) -> bool: 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) hass.http.register_view(WallPanelConfigView)
state["_config_view_registered"] = True 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)) entry.async_on_unload(entry.add_update_listener(_async_options_updated))
return True return True

View File

@ -62,6 +62,10 @@ class WallPanelPanel extends HTMLElement {
} }
_configUrl() { _configUrl() {
return '/api/wall_panel/panel';
}
_legacyConfigUrl() {
const payload = this._panelConfig(); const payload = this._panelConfig();
const entryId = String(payload.entry_id || '').trim(); const entryId = String(payload.entry_id || '').trim();
if (!entryId) { if (!entryId) {
@ -72,33 +76,39 @@ class WallPanelPanel extends HTMLElement {
async _fetchPanelUrl() { async _fetchPanelUrl() {
const payload = this._panelConfig(); const payload = this._panelConfig();
const configUrl = this._configUrl();
const syncToken = String(payload.sync_token || '').trim(); const syncToken = String(payload.sync_token || '').trim();
if (!configUrl || !syncToken) { if (!syncToken) {
return ''; return '';
} }
try { const configUrls = [this._configUrl(), this._legacyConfigUrl()].filter(Boolean);
const response = await fetch(configUrl, { for (const configUrl of configUrls) {
method: 'GET', try {
headers: { const response = await fetch(configUrl, {
'X-Wall-Panel-Token': syncToken, method: 'GET',
Accept: 'application/json', headers: {
}, 'X-Wall-Panel-Token': syncToken,
credentials: 'same-origin', Accept: 'application/json',
cache: 'no-store', },
}); credentials: 'same-origin',
cache: 'no-store',
});
if (!response.ok) { if (!response.ok) {
return ''; 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 = '') { _renderMessage(title, body, extra = '') {
@ -200,7 +210,7 @@ class WallPanelPanel extends HTMLElement {
return; return;
} }
const panelUrl = this._resolveUrl(await this._fetchPanelUrl()); const panelUrl = this._resolveUrl(await this._fetchPanelUrl());
if (panelUrl && panelUrl === this._activePanelUrl) { if (panelUrl && panelUrl === this._activePanelUrl) {
return; return;
} }

View File

@ -30,6 +30,23 @@ def _entry_from_hass(hass: HomeAssistant, entry_id: str):
return hass.data.get(DOMAIN, {}).get(entry_id, {}).get("entry") 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: def _request_token(request: web.Request) -> str:
header = request.headers.get("X-Wall-Panel-Token", "").strip() header = request.headers.get("X-Wall-Panel-Token", "").strip()
if header: if header:
@ -180,3 +197,54 @@ class WallPanelConfigView(HomeAssistantView):
"config": config, "config": config,
"panel": current_entry_panel(entry), "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),
})

View File

@ -270,7 +270,17 @@ function app_supervisor_addon_slug(): string
return $override; 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 function app_supervisor_ingress_url(): string
@ -292,35 +302,47 @@ function app_supervisor_ingress_url(): string
return trim((string)($response['ingress_url'] ?? '')); 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)) { if (app_runtime_mode() !== 'addon') {
return; return false;
} }
$ingressUrl = app_supervisor_ingress_url(); $ingressUrl = app_supervisor_ingress_url();
if ($ingressUrl === '') { if ($ingressUrl === '') {
return; return false;
} }
$syncUrl = trim((string)($config['home_assistant']['sync_url'] ?? '')); $registerUrl = app_addon_register_panel_url();
$syncToken = trim((string)($config['home_assistant']['sync_token'] ?? '')); if ($registerUrl === '') {
$cacheKey = hash('sha256', $ingressUrl . '|' . $syncUrl . '|' . $syncToken); return false;
}
$cacheKey = hash('sha256', $ingressUrl . '|' . $registerUrl);
$cachePath = app_panel_registration_cache_path(); $cachePath = app_panel_registration_cache_path();
$cache = app_load_json_file($cachePath, []); $cache = app_load_json_file($cachePath, []);
if (trim((string)($cache['cache_key'] ?? '')) === $cacheKey) { if (trim((string)($cache['cache_key'] ?? '')) === $cacheKey) {
return; return true;
} }
$response = app_http_json_request('POST', $syncUrl, [ $headers = [];
'X-Wall-Panel-Token: ' . $syncToken, $supervisorToken = trim((string)getenv('SUPERVISOR_TOKEN'));
], [ if ($supervisorToken !== '') {
'action' => 'register-panel', $headers[] = 'Authorization: Bearer ' . $supervisorToken;
'payload' => [ }
$response = app_http_json_request(
'POST',
$registerUrl,
$headers,
[
'panel_url' => $ingressUrl, '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)) { if (is_array($response) && (bool)($response['ok'] ?? false)) {
app_save_json_file($cachePath, [ app_save_json_file($cachePath, [
@ -328,7 +350,10 @@ function app_register_ingress_url(array $config): void
'ingress_url' => $ingressUrl, 'ingress_url' => $ingressUrl,
'registered_at' => time(), 'registered_at' => time(),
]); ]);
return true;
} }
return false;
} }
function app_load_remote_config(array $config, bool $refresh = false): array|null function app_load_remote_config(array $config, bool $refresh = false): array|null

16
run.sh
View File

@ -5,6 +5,7 @@ DOCROOT="${WALL_PANEL_DOCROOT:-/app}"
PORT="${WALL_PANEL_PORT:-8099}" PORT="${WALL_PANEL_PORT:-8099}"
CONFIG_PATH="${WALL_PANEL_CONFIG_PATH:-/config/config.json}" CONFIG_PATH="${WALL_PANEL_CONFIG_PATH:-/config/config.json}"
STORAGE_DIR="${WALL_PANEL_STORAGE_DIR:-/config/storage}" STORAGE_DIR="${WALL_PANEL_STORAGE_DIR:-/config/storage}"
RUNTIME_MODE="${WALL_PANEL_RUNTIME_MODE:-standalone}"
mkdir -p "$(dirname "$CONFIG_PATH")" "$STORAGE_DIR" mkdir -p "$(dirname "$CONFIG_PATH")" "$STORAGE_DIR"
@ -14,8 +15,19 @@ fi
export WALL_PANEL_CONFIG_PATH="$CONFIG_PATH" export WALL_PANEL_CONFIG_PATH="$CONFIG_PATH"
export WALL_PANEL_STORAGE_DIR="$STORAGE_DIR" 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" exec php -S "0.0.0.0:${PORT}" -t "$DOCROOT"

View File

@ -1,6 +1,6 @@
{ {
"active": false, "active": false,
"sensor_entity_id": "binary_sensor.barn_all_occupancy", "sensor_entity_id": "binary_sensor.doorbell_all_occupancy",
"opened_at": 1774439958, "opened_at": 1774441493,
"expires_at": null "expires_at": null
} }

View File

@ -1,6 +1,6 @@
name: Wall Panel name: Wall Panel
description: Wall Panel PHP interface as a Home Assistant add-on description: Wall Panel PHP interface as a Home Assistant add-on
version: "1.0.10" version: "1.0.12"
slug: wall_panel slug: wall_panel
url: https://git.striker72rus.ru/PHP/wallpanell.git url: https://git.striker72rus.ru/PHP/wallpanell.git
init: false init: false

View File

@ -270,7 +270,7 @@ function app_supervisor_addon_slug(): string
return $override; return $override;
} }
return 'wall_panel'; return 'self';
} }
function app_supervisor_ingress_url(): string function app_supervisor_ingress_url(): string
@ -292,35 +292,57 @@ function app_supervisor_ingress_url(): string
return trim((string)($response['ingress_url'] ?? '')); 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)) { $override = trim((string)getenv('WALL_PANEL_REGISTER_URL'));
return; 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(); $ingressUrl = app_supervisor_ingress_url();
if ($ingressUrl === '') { if ($ingressUrl === '') {
return; return false;
} }
$syncUrl = trim((string)($config['home_assistant']['sync_url'] ?? '')); $registerUrl = app_addon_register_panel_url();
$syncToken = trim((string)($config['home_assistant']['sync_token'] ?? '')); if ($registerUrl === '') {
$cacheKey = hash('sha256', $ingressUrl . '|' . $syncUrl . '|' . $syncToken); return false;
}
$cacheKey = hash('sha256', $ingressUrl . '|' . $registerUrl);
$cachePath = app_panel_registration_cache_path(); $cachePath = app_panel_registration_cache_path();
$cache = app_load_json_file($cachePath, []); $cache = app_load_json_file($cachePath, []);
if (trim((string)($cache['cache_key'] ?? '')) === $cacheKey) { if (trim((string)($cache['cache_key'] ?? '')) === $cacheKey) {
return; return true;
} }
$response = app_http_json_request('POST', $syncUrl, [ $headers = [];
'X-Wall-Panel-Token: ' . $syncToken, $supervisorToken = trim((string)getenv('SUPERVISOR_TOKEN'));
], [ if ($supervisorToken !== '') {
'action' => 'register-panel', $headers[] = 'Authorization: Bearer ' . $supervisorToken;
'payload' => [ }
$response = app_http_json_request(
'POST',
$registerUrl,
$headers,
[
'panel_url' => $ingressUrl, '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)) { if (is_array($response) && (bool)($response['ok'] ?? false)) {
app_save_json_file($cachePath, [ app_save_json_file($cachePath, [
@ -328,7 +350,10 @@ function app_register_ingress_url(array $config): void
'ingress_url' => $ingressUrl, 'ingress_url' => $ingressUrl,
'registered_at' => time(), 'registered_at' => time(),
]); ]);
return true;
} }
return false;
} }
function app_load_remote_config(array $config, bool $refresh = false): array|null function app_load_remote_config(array $config, bool $refresh = false): array|null

View File

@ -16,6 +16,15 @@ export WALL_PANEL_CONFIG_PATH="$CONFIG_PATH"
export WALL_PANEL_STORAGE_DIR="$STORAGE_DIR" export WALL_PANEL_STORAGE_DIR="$STORAGE_DIR"
export WALL_PANEL_RUNTIME_MODE="addon" 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" exec php -S "0.0.0.0:${PORT}" -t "$DOCROOT"