{html.escape(group['name'])}
" f"{camera_stream_link(group, lang)}" "{html.escape(action.display_name(lang))}
" f'{html.escape(action.id)}'
"import html import urllib.parse from pathlib import Path from .i18n import available_languages, normalize_language, text_for PACKAGE_DIR = Path(__file__).resolve().parent TEMPLATE_PATH = PACKAGE_DIR / "templates" / "index.html" STATIC_DIR = PACKAGE_DIR / "static" def read_static_file(name): # The HTTP layer serves only explicitly named static files from this package # directory. This avoids exposing arbitrary local paths. path = STATIC_DIR / name if not path.is_file() or path.parent != STATIC_DIR: return None return path.read_bytes() def action_visual(action, lang): # Keep GUI visuals tied to action semantics, not to local camera names. New # action families can add another name_key/kind branch without changing MQTT # topics or camera command handling. name_key = action.cfg.get("name_key") if name_key == "action.siren": return { "kind": "siren", "label": text_for(lang, "visual.siren"), "svg": ( '" ), } if name_key == "action.red_blue": return { "kind": "red-blue", "label": text_for(lang, "visual.red_blue"), "svg": ( '" ), } if name_key == "action.manual_white": return { "kind": "white-light", "label": text_for(lang, "visual.white_light"), "svg": ( '" ), } if action.kind == "all_off": return { "kind": "all-off", "label": text_for(lang, "visual.all_off"), "svg": ( '" ), } return { "kind": "generic", "label": text_for(lang, "visual.generic"), "svg": ( '" ), } def visual_markup(action, lang): visual = action_visual(action, lang) return ( f'
{html.escape(action.id)}'
"{html.escape(action.id)}'
"