Dashboard Studio 1.7.0: scroll and swipe cards, more themes, per-device dashboards

- Container cards: horizontal scroll (snap, arrows, keeps position) and
  swipe (dots, optional autoplay that survives redraws and respects
  reduced motion), with add/edit/reorder of the cards inside
- Nine new themes, contrast-checked, including High contrast; bright
  accents get dark text
- Choose which dashboard opens per device (optionally in kiosk mode)
  and per Home Assistant user, via /api/whoami and the ingress user
  headers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-25 23:21:24 +01:00
parent fa3e58b747
commit 75ab27db10
10 changed files with 377 additions and 24 deletions
+36
View File
@@ -107,6 +107,40 @@ async def index(request):
return web.Response(text=html, content_type="text/html", headers={"Cache-Control": "no-store"})
USERS_FILE = DATA_DIR / "users.json"
def who(request):
"""The Home Assistant user, from the headers the Supervisor's ingress proxy adds."""
if MOCK:
return {"id": os.environ.get("MOCK_USER_ID", "mock-user-alex"), "name": "alex", "display": "Alex"}
h = request.headers
uid = h.get("X-Remote-User-Id")
return {"id": uid, "name": h.get("X-Remote-User-Name", ""), "display": h.get("X-Remote-User-Display-Name", "")} if uid else None
def load_users():
try:
return json.loads(USERS_FILE.read_text())
except (OSError, json.JSONDecodeError):
return {}
async def whoami(request):
me = who(request)
if me: # remember who has used the app, so dashboards can be assigned to them
users = load_users()
if users.get(me["id"], {}).get("display") != me["display"] or me["id"] not in users:
users[me["id"]] = {"name": me["name"], "display": me["display"]}
DATA_DIR.mkdir(parents=True, exist_ok=True)
USERS_FILE.write_text(json.dumps(users, indent=2))
return web.json_response(me or {}, headers={"Cache-Control": "no-store"})
async def users(request):
return web.json_response([{"id": k, **v} for k, v in load_users().items()], headers={"Cache-Control": "no-store"})
async def version(request):
return web.json_response({"version": VERSION}, headers={"Cache-Control": "no-store"})
@@ -214,6 +248,8 @@ def make_app():
app = web.Application(middlewares=[ingress_only], client_max_size=8 * 1024 * 1024)
app.router.add_get("/", index)
app.router.add_get("/api/version", version)
app.router.add_get("/api/whoami", whoami)
app.router.add_get("/api/users", users)
app.router.add_get("/api/dashboards", get_dashboards)
app.router.add_put("/api/dashboards", put_dashboards)
app.router.add_get("/api/camera/{entity_id}", camera)