Dashboard Studio 2.0.0: Admin, Builder and Dashboard screens, roles, Haven

- Home Assistant administrators are admins: Dashboard, Builder and Admin screens
- Everyone else only sees the dashboards an admin chose, always in kiosk mode;
  the server enforces it (saving, people, devices, history are admin-only, and
  the websocket only allows reading states and controlling devices)
- Admin screen: dashboards, people and what each sees (with preview), devices,
  messages to the screens, the Haven app, activity, about
- Haven: new Android app (haven/) that only shows dashboards; replaces the
  Dashboard Studio Android app and signs in through Home Assistant's page
- Screens pick up dashboard and access changes within a minute
- Version history records who made each change; panel visible to all users

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-26 23:20:55 +01:00
parent 297fb2a7a0
commit 3d958d0411
37 changed files with 1096 additions and 248 deletions
+16 -2
View File
@@ -469,7 +469,19 @@ class MockHA:
# -- websocket ----------------------------------------------------------
async def serve(self, ws):
# Home Assistant's logins, as config/auth/list returns them. Alex is the owner; the others aren't administrators.
USERS = {
"mock-user-alex": {"id": "mock-user-alex", "name": "Alex", "username": "alex", "is_owner": True, "is_active": True, "system_generated": False, "group_ids": ["system-admin"]},
"mock-user-sam": {"id": "mock-user-sam", "name": "Sam", "username": "sam", "is_owner": False, "is_active": True, "system_generated": False, "group_ids": ["system-users"]},
"mock-user-noor": {"id": "mock-user-noor", "name": "Noor", "username": "noor", "is_owner": False, "is_active": True, "system_generated": False, "group_ids": ["system-users"]},
"mock-user-tablet": {"id": "mock-user-tablet", "name": "Kitchen tablet", "username": "tablet", "is_owner": False, "is_active": True, "system_generated": False, "group_ids": ["system-users"]},
}
@staticmethod
def USERS_LIST(): # noqa: N802 - mirrors USERS
return [*MockHA.USERS.values(), {"id": "mock-supervisor", "name": "Supervisor", "is_owner": False, "is_active": True, "system_generated": True, "group_ids": ["system-admin"]}]
async def serve(self, ws, allow=None):
subs = set()
entry = (ws, frozenset())
await ws.send_json({"type": "ready", "ha_version": "mock"})
@@ -479,7 +491,9 @@ class MockHA:
continue
req = json.loads(msg.data)
mid, typ = req.get("id"), req.get("type")
if typ == "get_states":
if allow and not allow(req):
await ws.send_json({"id": mid, "type": "result", "success": False, "error": {"code": "unauthorized", "message": "Only Home Assistant administrators can do this"}})
elif typ == "get_states":
await ws.send_json({"id": mid, "type": "result", "success": True, "result": list(self.states.values())})
elif typ == "subscribe_events":
subs.add(mid)