fix: HA-Event-Update URL-Encoding und Popup-Überlappung bei Lösch-Dialog

- HA Update/Delete: UID wird URL-encoded (@ → %40), Delete mit
  Fallback auf Service-Call API für ältere HA-Versionen
- Lösch-Dialog: Event-Popup wird geschlossen BEVOR der Bestätigungsdialog
  erscheint, kein Überlappen mehr
This commit is contained in:
Scarriffle
2026-04-29 18:38:43 +02:00
parent 20e98e660a
commit 3351263c85
2 changed files with 27 additions and 14 deletions

View File

@@ -110,7 +110,11 @@ def _ha_get_events(url: str, token: str, entity_id: str, start_dt: datetime, end
def _ha_update_event(url: str, token: str, entity_id: str, uid: str, data: dict): def _ha_update_event(url: str, token: str, entity_id: str, uid: str, data: dict):
"""Update an event via HA REST API.""" """Update an event via HA REST API (HA 2023.11+)."""
from urllib.parse import quote
base = url.rstrip("/")
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
encoded_uid = quote(uid, safe="")
body = {} body = {}
if "title" in data: if "title" in data:
body["summary"] = data["title"] body["summary"] = data["title"]
@@ -126,26 +130,35 @@ def _ha_update_event(url: str, token: str, entity_id: str, uid: str, data: dict)
body["start"] = {"dateTime": data["start"]} body["start"] = {"dateTime": data["start"]}
body["end"] = {"dateTime": data["end"]} body["end"] = {"dateTime": data["end"]}
resp = http_requests.put( resp = http_requests.put(
f"{url.rstrip('/')}/api/calendars/{entity_id}/{uid}", f"{base}/api/calendars/{entity_id}/{encoded_uid}",
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"}, headers=headers, json=body, timeout=15, verify=False,
json=body,
timeout=15,
verify=False,
) )
resp.raise_for_status() resp.raise_for_status()
return resp return resp
def _ha_delete_event(url: str, token: str, entity_id: str, uid: str): def _ha_delete_event(url: str, token: str, entity_id: str, uid: str):
"""Delete an event via HA REST API.""" """Delete an event via HA REST API with fallback to service call."""
base = url.rstrip("/")
headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"}
# Try REST API first (HA 2023.11+)
from urllib.parse import quote
encoded_uid = quote(uid, safe="")
resp = http_requests.delete( resp = http_requests.delete(
f"{url.rstrip('/')}/api/calendars/{entity_id}/{uid}", f"{base}/api/calendars/{entity_id}/{encoded_uid}",
headers={"Authorization": f"Bearer {token}"}, headers=headers, timeout=15, verify=False,
timeout=15,
verify=False,
) )
resp.raise_for_status() if resp.status_code < 400:
return resp return resp
# Fallback: service call
resp2 = http_requests.post(
f"{base}/api/services/calendar/delete_event",
headers=headers,
json={"entity_id": entity_id, "uid": uid},
timeout=15, verify=False,
)
resp2.raise_for_status()
return resp2
def _parse_ha_event(ev: dict, cal_db_id: int, cal_name: str, cal_color: str) -> dict: def _parse_ha_event(ev: dict, cal_db_id: int, cal_name: str, cal_color: str) -> dict:

View File

@@ -1034,9 +1034,9 @@ function showEventPopup(ev, anchor) {
}; };
document.getElementById('popup-delete').onclick = async () => { document.getElementById('popup-delete').onclick = async () => {
popup.classList.add('hidden');
const scope = await showDeleteConfirm(ev); const scope = await showDeleteConfirm(ev);
if (!scope) return; if (!scope) return;
popup.classList.add('hidden');
try { try {
await deleteEventByScope(ev, scope); await deleteEventByScope(ev, scope);
showToast(t('event_deleted')); showToast(t('event_deleted'));