329 files
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -392,3 +392,7 @@ NOTIF_TYPE_MANDATORY_PARENT_ALERT: Final = "mandatory_parent_alert"
|
||||
NOTIF_TYPE_MONTHLY_REPORT: Final = "monthly_report"
|
||||
NOTIF_TYPE_SEASON_CHAMPION: Final = "season_champion"
|
||||
NOTIF_TYPE_FAMILY_GOAL_REACHED: Final = "family_goal_reached"
|
||||
|
||||
# Default notification tap target. Must match PANEL_URL_PATH in panel.py —
|
||||
# a bare /taskmate is the static-files prefix and returns 403, not the panel.
|
||||
DEFAULT_NOTIFICATION_NAV_URL: Final = "/taskmate-admin"
|
||||
|
||||
@@ -19,6 +19,7 @@ from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.event import async_track_time_change
|
||||
|
||||
from .const import (
|
||||
DEFAULT_NOTIFICATION_NAV_URL,
|
||||
NOTIF_TYPE_ALL_CHORES_DONE,
|
||||
NOTIF_TYPE_BADGE_EARNED,
|
||||
NOTIF_TYPE_BEDTIME_REMINDER,
|
||||
@@ -88,6 +89,27 @@ NOTIFICATION_TYPES_BY_ID: dict[str, NotificationTypeMeta] = {
|
||||
}
|
||||
|
||||
|
||||
def _validate_nav_url(value: str) -> str:
|
||||
"""Normalise and vet a notification tap target.
|
||||
|
||||
The tap target is opened on the *recipient's* device, so only dashboard
|
||||
paths, web URLs and the companion app's noAction sentinel are allowed —
|
||||
never intent:// / app:// / homeassistant:// style deep links.
|
||||
"""
|
||||
value = (value or "").strip()
|
||||
if value in ("", "noAction"):
|
||||
return value
|
||||
if value.lower().startswith(("http://", "https://")):
|
||||
return value
|
||||
if (
|
||||
value.startswith("/")
|
||||
and not value.startswith("//")
|
||||
and not any(ord(c) <= 32 or ord(c) == 127 for c in value)
|
||||
):
|
||||
return value
|
||||
raise ValueError("nav_url must be a /path, an http(s) URL, or noAction")
|
||||
|
||||
|
||||
def _parse_hhmm(value: str) -> tuple[int, int] | None:
|
||||
"""Parse "HH:MM" into (hour, minute); None if blank/malformed."""
|
||||
if not value:
|
||||
@@ -315,7 +337,11 @@ class NotificationCoordinator:
|
||||
per_type = (getattr(cfg, "nav_url", "") or "").strip()
|
||||
if per_type:
|
||||
return per_type
|
||||
return str(self.storage.get_setting("notification_nav_url", "/taskmate") or "").strip()
|
||||
return str(
|
||||
self.storage.get_setting(
|
||||
"notification_nav_url", DEFAULT_NOTIFICATION_NAV_URL
|
||||
) or ""
|
||||
).strip()
|
||||
|
||||
def _resolve_notify_service(self, recipient_id: str) -> str:
|
||||
if recipient_id.startswith("child:"):
|
||||
@@ -711,8 +737,10 @@ class NotificationCoordinator:
|
||||
|
||||
async def set_nav_url(self, type_id: str | None, nav_url: str) -> None:
|
||||
"""Set the tap target — global (type_id falsy) or per notification type."""
|
||||
nav_url = (nav_url or "").strip()
|
||||
nav_url = _validate_nav_url(nav_url)
|
||||
if type_id:
|
||||
if type_id not in NOTIFICATION_TYPES_BY_ID:
|
||||
raise ValueError(f"Unknown notification type {type_id}")
|
||||
self.storage.set_notification_nav_url(type_id, nav_url)
|
||||
else:
|
||||
self.storage.set_setting("notification_nav_url", nav_url)
|
||||
|
||||
@@ -17,5 +17,5 @@
|
||||
"iot_class": "calculated",
|
||||
"issue_tracker": "https://github.com/tempus2016/taskmate/issues",
|
||||
"requirements": [],
|
||||
"version": "5.0.2"
|
||||
"version": "5.0.3"
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ from typing import Any
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.storage import Store
|
||||
|
||||
from .const import DOMAIN
|
||||
from .const import DEFAULT_NOTIFICATION_NAV_URL, DOMAIN
|
||||
from .models import (
|
||||
AwardedBadge,
|
||||
Badge,
|
||||
@@ -131,6 +131,8 @@ class TaskMateStorage:
|
||||
self._run_notifications_migration()
|
||||
self._data["notifications_migration_done"] = True
|
||||
|
||||
self._migrate_nav_url_default()
|
||||
|
||||
# Badge migration / seeding
|
||||
self._seed_builtin_badges(is_fresh=is_fresh)
|
||||
|
||||
@@ -664,6 +666,20 @@ class TaskMateStorage:
|
||||
if is_fresh:
|
||||
self._data["badges_backfill_pending"] = True
|
||||
|
||||
def _migrate_nav_url_default(self) -> None:
|
||||
"""Rewrite the broken v5.0.2 notification tap target.
|
||||
|
||||
v5.0.2 shipped "/taskmate" as the default, but the panel lives at
|
||||
/taskmate-admin (/taskmate is the static-files prefix and 403s), so a
|
||||
persisted "/taskmate" is broken for every install. Idempotent.
|
||||
"""
|
||||
settings = self._data.get("settings", {}) or {}
|
||||
if settings.get("notification_nav_url") == "/taskmate":
|
||||
settings["notification_nav_url"] = DEFAULT_NOTIFICATION_NAV_URL
|
||||
for cfg in (self._data.get("notification_config", {}) or {}).values():
|
||||
if isinstance(cfg, dict) and cfg.get("nav_url") == "/taskmate":
|
||||
cfg["nav_url"] = DEFAULT_NOTIFICATION_NAV_URL
|
||||
|
||||
def _run_notifications_migration(self) -> None:
|
||||
"""Seed parent_recipients + notification_config from legacy notify_service.
|
||||
|
||||
|
||||
@@ -57,7 +57,13 @@ from homeassistant.components import websocket_api
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import photos
|
||||
from .const import DEFAULT_TIME_PERIODS, DOMAIN, MAX_TIME_PERIODS, TIME_CATEGORY_ICONS
|
||||
from .const import (
|
||||
DEFAULT_NOTIFICATION_NAV_URL,
|
||||
DEFAULT_TIME_PERIODS,
|
||||
DOMAIN,
|
||||
MAX_TIME_PERIODS,
|
||||
TIME_CATEGORY_ICONS,
|
||||
)
|
||||
from .coordinator import TaskMateCoordinator
|
||||
from .models import BonusSubTask, Reward
|
||||
|
||||
@@ -1910,7 +1916,9 @@ async def ws_notif_get_state(hass, connection, msg, coordinator):
|
||||
"streak_at_risk_cutoff_time": c.storage.get_streak_at_risk_cutoff(),
|
||||
"mandatory_escalation_reminder_minutes": c.storage.get_escalation_reminder_minutes(),
|
||||
"mandatory_escalation_parent_minutes": c.storage.get_escalation_parent_minutes(),
|
||||
"notification_nav_url": c.storage.get_setting("notification_nav_url", "/taskmate"),
|
||||
"notification_nav_url": c.storage.get_setting(
|
||||
"notification_nav_url", DEFAULT_NOTIFICATION_NAV_URL
|
||||
),
|
||||
},
|
||||
}
|
||||
connection.send_result(msg["id"], state)
|
||||
|
||||
@@ -867,7 +867,7 @@
|
||||
"panel.notif_section_recipients_desc": "Weise jedem Kind und Elternteil einen Home Assistant Benachrichtigungsdienst zu. Leer lassen, um Benachrichtigungen für diese Person zu überspringen.",
|
||||
"panel.notif_streak_cutoff_label": "Frist",
|
||||
"panel.notif_nav_url_global_label": "Beim Tippen öffnen",
|
||||
"panel.notif_nav_url_global_hint": "Wohin eine angetippte Benachrichtigung führt: ein Pfad wie /taskmate oder /lovelace/parents, eine vollständige URL oder noAction. Gilt für alle Benachrichtigungen, sofern unten nicht pro Typ überschrieben.",
|
||||
"panel.notif_nav_url_global_hint": "Wohin eine angetippte Benachrichtigung führt: ein Pfad wie /taskmate-admin oder /lovelace/parents, eine vollständige URL oder noAction. Gilt für alle Benachrichtigungen, sofern unten nicht pro Typ überschrieben.",
|
||||
"panel.notif_nav_url_row_placeholder": "Nutzt globalen Standard",
|
||||
"panel.notif_tab_title": "Benachrichtigungen",
|
||||
"panel.template_assignment_mode_label": "Zuweisungsmodus",
|
||||
|
||||
@@ -917,7 +917,7 @@
|
||||
"panel.notif_section_recipients_desc": "Map each child and parent to a Home Assistant notify service. Leave blank to skip notifications for that person.",
|
||||
"panel.notif_streak_cutoff_label": "Cutoff",
|
||||
"panel.notif_nav_url_global_label": "When tapped, open",
|
||||
"panel.notif_nav_url_global_hint": "Where a tapped notification opens: a path like /taskmate or /lovelace/parents, a full URL, or noAction. Applies to all notifications unless overridden per type below.",
|
||||
"panel.notif_nav_url_global_hint": "Where a tapped notification opens: a path like /taskmate-admin or /lovelace/parents, a full URL, or noAction. Applies to all notifications unless overridden per type below.",
|
||||
"panel.notif_nav_url_row_placeholder": "Uses global default",
|
||||
"panel.notif_send_test": "Send test",
|
||||
"panel.notif_test_sent": "Test sent to {count} recipient(s) + persistent notification",
|
||||
|
||||
@@ -917,7 +917,7 @@
|
||||
"panel.notif_section_recipients_desc": "Map each child and parent to a Home Assistant notify service. Leave blank to skip notifications for that person.",
|
||||
"panel.notif_streak_cutoff_label": "Cutoff",
|
||||
"panel.notif_nav_url_global_label": "When tapped, open",
|
||||
"panel.notif_nav_url_global_hint": "Where a tapped notification opens: a path like /taskmate or /lovelace/parents, a full URL, or noAction. Applies to all notifications unless overridden per type below.",
|
||||
"panel.notif_nav_url_global_hint": "Where a tapped notification opens: a path like /taskmate-admin or /lovelace/parents, a full URL, or noAction. Applies to all notifications unless overridden per type below.",
|
||||
"panel.notif_nav_url_row_placeholder": "Uses global default",
|
||||
"panel.notif_send_test": "Send test",
|
||||
"panel.notif_test_sent": "Test sent to {count} recipient(s) + persistent notification",
|
||||
|
||||
@@ -867,7 +867,7 @@
|
||||
"panel.notif_section_recipients_desc": "Associez chaque enfant et parent à un service de notification Home Assistant. Laisser vide pour ignorer les notifications pour cette personne.",
|
||||
"panel.notif_streak_cutoff_label": "Limite",
|
||||
"panel.notif_nav_url_global_label": "Au toucher, ouvrir",
|
||||
"panel.notif_nav_url_global_hint": "Où mène une notification touchée : un chemin comme /taskmate ou /lovelace/parents, une URL complète ou noAction. S'applique à toutes les notifications sauf remplacement par type ci-dessous.",
|
||||
"panel.notif_nav_url_global_hint": "Où mène une notification touchée : un chemin comme /taskmate-admin ou /lovelace/parents, une URL complète ou noAction. S'applique à toutes les notifications sauf remplacement par type ci-dessous.",
|
||||
"panel.notif_nav_url_row_placeholder": "Utilise la valeur globale",
|
||||
"panel.notif_tab_title": "Notifications",
|
||||
"panel.template_assignment_mode_label": "Mode d'assignation",
|
||||
|
||||
@@ -867,7 +867,7 @@
|
||||
"panel.notif_section_recipients_desc": "Koble hvert barn og foresatt til en Home Assistant-varslingstjeneste. La stå tomt for å hoppe over varsler for den personen.",
|
||||
"panel.notif_streak_cutoff_label": "Grense",
|
||||
"panel.notif_nav_url_global_label": "Ved trykk, åpne",
|
||||
"panel.notif_nav_url_global_hint": "Hvor et trykk på et varsel åpner: en sti som /taskmate eller /lovelace/parents, en full URL, eller noAction. Gjelder alle varsler med mindre det overstyres per type nedenfor.",
|
||||
"panel.notif_nav_url_global_hint": "Hvor et trykk på et varsel åpner: en sti som /taskmate-admin eller /lovelace/parents, en full URL, eller noAction. Gjelder alle varsler med mindre det overstyres per type nedenfor.",
|
||||
"panel.notif_nav_url_row_placeholder": "Bruker global standard",
|
||||
"panel.notif_tab_title": "Varsler",
|
||||
"panel.template_assignment_mode_label": "Tildelingsmodus",
|
||||
|
||||
@@ -867,7 +867,7 @@
|
||||
"panel.notif_section_recipients_desc": "Kople kvart born og kvar føresett til ein Home Assistant-varslingsteneste. La stå tomt for å hoppa over varsel for den personen.",
|
||||
"panel.notif_streak_cutoff_label": "Grense",
|
||||
"panel.notif_nav_url_global_label": "Ved trykk, opne",
|
||||
"panel.notif_nav_url_global_hint": "Kvar eit trykk på eit varsel opnar: ein sti som /taskmate eller /lovelace/parents, ein full URL, eller noAction. Gjeld alle varsel med mindre det vert overstyrt per type nedanfor.",
|
||||
"panel.notif_nav_url_global_hint": "Kvar eit trykk på eit varsel opnar: ein sti som /taskmate-admin eller /lovelace/parents, ein full URL, eller noAction. Gjeld alle varsel med mindre det vert overstyrt per type nedanfor.",
|
||||
"panel.notif_nav_url_row_placeholder": "Brukar global standard",
|
||||
"panel.notif_tab_title": "Varsel",
|
||||
"panel.template_assignment_mode_label": "Tildelingsmodus",
|
||||
|
||||
@@ -867,7 +867,7 @@
|
||||
"panel.notif_section_recipients_desc": "Associe cada criança e responsável a um serviço de notificação do Home Assistant. Deixe em branco para pular notificações para essa pessoa.",
|
||||
"panel.notif_streak_cutoff_label": "Limite",
|
||||
"panel.notif_nav_url_global_label": "Ao tocar, abrir",
|
||||
"panel.notif_nav_url_global_hint": "Para onde uma notificação tocada abre: um caminho como /taskmate ou /lovelace/parents, uma URL completa ou noAction. Aplica-se a todas as notificações, exceto se substituída por tipo abaixo.",
|
||||
"panel.notif_nav_url_global_hint": "Para onde uma notificação tocada abre: um caminho como /taskmate-admin ou /lovelace/parents, uma URL completa ou noAction. Aplica-se a todas as notificações, exceto se substituída por tipo abaixo.",
|
||||
"panel.notif_nav_url_row_placeholder": "Usa o padrão global",
|
||||
"panel.notif_tab_title": "Notificações",
|
||||
"panel.template_assignment_mode_label": "Modo de atribuição",
|
||||
|
||||
@@ -872,7 +872,7 @@
|
||||
"panel.notif_section_recipients_desc": "Associe cada criança e encarregado a um serviço de notificação do Home Assistant. Deixe em branco para ignorar notificações para essa pessoa.",
|
||||
"panel.notif_streak_cutoff_label": "Limite",
|
||||
"panel.notif_nav_url_global_label": "Ao tocar, abrir",
|
||||
"panel.notif_nav_url_global_hint": "Para onde uma notificação tocada abre: um caminho como /taskmate ou /lovelace/parents, um URL completo ou noAction. Aplica-se a todas as notificações, salvo substituição por tipo abaixo.",
|
||||
"panel.notif_nav_url_global_hint": "Para onde uma notificação tocada abre: um caminho como /taskmate-admin ou /lovelace/parents, um URL completo ou noAction. Aplica-se a todas as notificações, salvo substituição por tipo abaixo.",
|
||||
"panel.notif_nav_url_row_placeholder": "Usa o padrão global",
|
||||
"panel.notif_tab_title": "Notificações",
|
||||
"panel.template_assignment_mode_label": "Modo de atribuição",
|
||||
|
||||
@@ -4599,7 +4599,7 @@ class TaskMatePanel extends HTMLElement {
|
||||
<label style="font-weight:500">${this._t("panel.notif_nav_url_global_label")}</label>
|
||||
<input type="text" class="tm-input" style="flex:1;min-width:180px"
|
||||
value="${this._esc((ns.settings && ns.settings.notification_nav_url) || "")}"
|
||||
data-act="notif-set-nav-url-global" placeholder="/taskmate">
|
||||
data-act="notif-set-nav-url-global" placeholder="/taskmate-admin">
|
||||
<div class="tm-meta" style="flex-basis:100%">${this._t("panel.notif_nav_url_global_hint")}</div>
|
||||
</div>
|
||||
<div class="tm-table-wrap">
|
||||
|
||||
Reference in New Issue
Block a user