diff --git a/CHANGELOG.md b/CHANGELOG.md
index 132c91a..9e45ae7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,18 @@
# Changelog
+## 1.6.0 - 2026-06-18
+- `/v1/actuators/dashboard/system` und `/dashboard/start` lesen fuer
+ Cache-Status nur noch SQLite-Metadaten statt den kompletten Entity-Cache zu
+ materialisieren.
+- Aktor-Summaries lesen benoetigte Entity-Metadaten gezielt aus SQLite anhand
+ der Aktor-IDs.
+- Ingress-Dashboard bereinigt: weniger Erklaertexte, kein Ablauf-Menue, kein
+ Versions-Chip im Einrichtungsbereich.
+- Detailansicht ergaenzt Zurueck-Navigation, Aktualisieren und Auswahl eines
+ anderen beobachteten Geraets.
+- Frontend bleibt Anzeige- und Bedienebene; Backend liefert schlanke
+ View-Daten, Worker aktualisieren HA-/Discovery-Cache im Hintergrund.
+
## 1.5.4 - 2026-06-18
- Add-on-Start vertraut Ingress-Proxy-Headern nicht mehr blind. Uvicorn loggt
damit den direkten Docker-/Ingress-Peer statt LAN-IPs aus `X-Forwarded-For`.
diff --git a/addon/config.yaml b/addon/config.yaml
index fdfbdbd..60a748f 100644
--- a/addon/config.yaml
+++ b/addon/config.yaml
@@ -1,5 +1,5 @@
name: SillyHome Next
-version: "1.5.4"
+version: "1.6.0"
slug: sillyhome_next
description: Lernt automatisch aus deinem Verhalten und steuert freigegebene Aktoren
url: http://192.168.6.31:3000/pino/sillyhome-next
diff --git a/app/actuators/cache_db.py b/app/actuators/cache_db.py
index 23b7b26..6fe6153 100644
--- a/app/actuators/cache_db.py
+++ b/app/actuators/cache_db.py
@@ -33,6 +33,38 @@ class DashboardCache:
"entities": [json.loads(row[1]) for row in rows],
}
+ def load_status(self) -> dict[str, object]:
+ with self._lock, self._connect() as connection:
+ updated_at = self._get_meta(connection, "ha_entities_updated_at")
+ groups_json = self._get_meta(connection, "discovery_groups") or "[]"
+ entity_count = connection.execute("select count(*) from ha_entities").fetchone()[0]
+ try:
+ groups = json.loads(groups_json)
+ except ValueError:
+ groups = []
+ return {
+ "updated_at": updated_at,
+ "discovery_groups": groups if isinstance(groups, list) else [],
+ "entity_count": int(entity_count or 0),
+ }
+
+ def load_entity_map(self, entity_ids: set[str]) -> dict[str, HaEntitySummary]:
+ if not entity_ids:
+ return {}
+ placeholders = ",".join("?" for _ in entity_ids)
+ with self._lock, self._connect() as connection:
+ rows = connection.execute(
+ f"select entity_id, payload from ha_entities where entity_id in ({placeholders})",
+ tuple(sorted(entity_ids)),
+ ).fetchall()
+ result: dict[str, HaEntitySummary] = {}
+ for entity_id, payload in rows:
+ try:
+ result[str(entity_id)] = HaEntitySummary.model_validate(json.loads(payload))
+ except (TypeError, ValueError):
+ continue
+ return result
+
def save_entities_payload(
self,
*,
diff --git a/app/api/v1/actuators.py b/app/api/v1/actuators.py
index ca67329..34eb409 100644
--- a/app/api/v1/actuators.py
+++ b/app/api/v1/actuators.py
@@ -326,13 +326,10 @@ def _dashboard_overview(
include_background: bool,
include_actuators: bool,
) -> DashboardOverview:
- cache_payload = _load_entity_cache_payload(request)
- raw_entities = cache_payload.get("entities", [])
- if not isinstance(raw_entities, list):
- raw_entities = []
- raw_updated_at = cache_payload.get("updated_at")
+ cache_status = _load_entity_cache_status(request)
+ raw_updated_at = cache_status.get("updated_at")
updated_at = raw_updated_at if isinstance(raw_updated_at, str) else None
- raw_groups = cache_payload.get("discovery_groups", [])
+ raw_groups = cache_status.get("discovery_groups", [])
cached_groups = [
DashboardDiscoveryGroup.model_validate(group)
for group in raw_groups
@@ -350,6 +347,9 @@ def _dashboard_overview(
job_p95_duration_ms, slow_job_count, performance_status = _performance_status(jobs)
anomaly_count = sum(record.anomaly_count for record in actuators)
critical_anomaly_count = sum(record.critical_anomaly_count for record in actuators)
+ entity_count = cache_status.get("entity_count")
+ if not isinstance(entity_count, int):
+ entity_count = 0
return DashboardOverview(
system=DashboardSystemStatus(
websocket_status=getattr(ws_status, "status", "unavailable"),
@@ -373,9 +373,9 @@ def _dashboard_overview(
critical_anomaly_count=critical_anomaly_count,
),
cache=EntityCacheStatus(
- available=bool(raw_entities),
+ available=bool(entity_count),
updated_at=updated_at,
- entity_count=len(raw_entities),
+ entity_count=entity_count,
),
actuators=actuators,
discovery_groups=cached_groups,
@@ -846,6 +846,11 @@ def _load_cached_entity_map(
) -> dict[str, HaEntitySummary]:
if not entity_ids:
return {}
+ cache = getattr(request.app.state, "dashboard_cache", None)
+ if isinstance(cache, DashboardCache):
+ cached_result = cache.load_entity_map(entity_ids)
+ if cached_result:
+ return cached_result
payload = _load_entity_cache_payload(request)
raw_entities = payload.get("entities", [])
if not isinstance(raw_entities, list):
@@ -864,6 +869,29 @@ def _load_cached_entity_map(
return result
+def _load_entity_cache_status(request: Request) -> dict[str, object]:
+ cache = getattr(request.app.state, "dashboard_cache", None)
+ if isinstance(cache, DashboardCache):
+ status_payload = cache.load_status()
+ if status_payload.get("entity_count"):
+ return status_payload
+ path = _entity_cache_path(request)
+ if not path.exists():
+ return {}
+ try:
+ payload = json.loads(path.read_text(encoding="utf-8"))
+ except (OSError, TypeError, ValueError):
+ return {}
+ if not isinstance(payload, dict):
+ return {}
+ raw_entities = payload.get("entities", [])
+ return {
+ "updated_at": payload.get("updated_at"),
+ "discovery_groups": payload.get("discovery_groups", []),
+ "entity_count": len(raw_entities) if isinstance(raw_entities, list) else 0,
+ }
+
+
def _load_entity_cache_payload(request: Request) -> dict[str, object]:
cache = getattr(request.app.state, "dashboard_cache", None)
if isinstance(cache, DashboardCache):
diff --git a/app/main.py b/app/main.py
index 57f97ea..5129f62 100644
--- a/app/main.py
+++ b/app/main.py
@@ -117,7 +117,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
app = FastAPI(
title="SillyHome Next API",
description="Lokales Smart-Home-Intelligenzsystem für Home Assistant.",
- version="1.5.4",
+ version="1.6.0",
lifespan=lifespan,
)
app.state.settings = load_settings()
diff --git a/app/static/index.html b/app/static/index.html
index cc847da..820ed19 100644
--- a/app/static/index.html
+++ b/app/static/index.html
@@ -52,6 +52,8 @@
.panel-title { display:flex; align-items:center; justify-content:space-between; gap:10px; margin-bottom:8px; }
.toolbar { display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin:10px 0; }
.toolbar button { margin-top:0; }
+ .detail-tools { display:grid; grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); gap:8px; margin:10px 0; align-items:end; }
+ .detail-tools button { margin-top:0; }
details.collapsible > summary,
.manual-context > summary,
.group-panel > summary { cursor:pointer; font-weight:800; color:#eaf1f8; }
@@ -144,10 +146,9 @@
SH
-
SillyHome Next
+ SillyHome
-
Arbeitsdashboard für gelernte Home-Assistant-Bedienung: Geräte auswählen, Lernstand prüfen, Freigaben steuern.
-
Sicherer Start: Zuerst wird nur beobachtet und vorhergesagt. Ohne deine spätere Freigabe wird nichts geschaltet.
+
Geräte, Lernen, Freigaben und Systemzustand.
@@ -165,11 +165,9 @@
-
Steuerung
- v1
+ Discovery & Einrichtung
- Wähle eine Lampe, einen Rollladen oder einen anderen unterstützten Aktor. Du wählst keine Sensoren und erstellst keine Regeln.
-
+
@@ -199,16 +197,15 @@
-
+
- Noch kein Aktor ausgewählt.
+ Bereit.
Vorschläge anzeigen
- Vorschläge können Home Assistant stark abfragen und werden deshalb nicht beim Start geladen.
@@ -218,7 +215,6 @@
Beobachtete Geräte
-
Öffne „Details“, um Lernfortschritt, aktuelle Vorhersage und den automatisch gefundenen Kontext zu sehen.
@@ -226,8 +222,10 @@
- Lernfortschritt und Freigabe
- Die Freigabe erscheint erst, wenn genug eindeutig zugeordnete Handlungen gelernt wurden. Vorher bleibt das Gerät sicher im Beobachtungsmodus.
+
+
Details
+
+
Öffne bei einem beobachteten Gerät die Details.
@@ -235,7 +233,6 @@
System & Cache
-
Die Startansicht nutzt lokale Summaries und Cache-Daten. Home-Assistant-Discovery lädt erst bei Bedarf.
@@ -259,40 +256,9 @@
- Die API speichert stabile technische Werte. Die Oberfläche übersetzt sie in die gewählte Sprache.
-
-
-
Performance-Standard
-
Startansichten dürfen maximal 3 Sekunden brauchen. Schwere Daten werden nur nach Menüwechsel oder bei Bearbeitung geladen.
-
-
-
- So gehst du vor
-
-
-
1
-
Aktor auswählen
-
Wo? Links im Feld „Gerät auswählen“.
-
Was passiert? SillyHome ordnet Raum, Sensoren, Zustände und vorhandene Historie automatisch zu.
-
-
-
2
-
Wie gewohnt bedienen
-
Wo? Weiterhin in Home Assistant, an Schaltern oder über deine bisherigen Bedienwege.
-
Was passiert? SillyHome lernt deine Handlungen und zeigt Vorhersagen an, schaltet aber noch nicht selbst.
-
-
-
3
-
Später freigeben
-
Wo? In den Details des ausgewählten Geräts, sobald genug Verhalten gelernt wurde.
-
Was passiert? Erst dann darf SillyHome passende Vorhersagen automatisch ausführen. Die Freigabe kann jederzeit gestoppt werden.
-
-
-
-