- Added new "Dashboard-Lab-Widgets" entry to the documentation for better guidance on widget configuration. - Updated the app_dashboard version to 1.8.0 to reflect the introduction of widget catalog features and layout entitlements. - Enhanced widget catalog entries to include optional feature requirements for better visibility and access control. - Improved the DashboardLabPage to manage widget visibility based on feature entitlements, ensuring a more tailored user experience.
80 lines
2.5 KiB
Python
80 lines
2.5 KiB
Python
"""
|
||
Dashboard-Widgets × Feature-System: Sichtbarkeit aus check_feature_access.
|
||
|
||
Katalog-Einträge optional `requires_feature` (features.id). Fehlt der Key → immer erlaubt.
|
||
"""
|
||
from __future__ import annotations
|
||
|
||
import copy
|
||
from typing import Any
|
||
|
||
from widget_catalog import WIDGET_CATALOG
|
||
|
||
|
||
def _check_feature_access(profile_id: str, feature_id: str, conn) -> dict:
|
||
"""Indirection für Tests (monkeypatch) und spätes Laden von auth (bcrypt)."""
|
||
from auth import check_feature_access
|
||
|
||
return check_feature_access(profile_id, feature_id, conn)
|
||
|
||
_WIDGET_ENTRY_BY_ID: dict[str, dict[str, Any]] = {e["id"]: e for e in WIDGET_CATALOG}
|
||
|
||
|
||
def widget_id_allowed(widget_id: str, profile_id: str, conn) -> bool:
|
||
entry = _WIDGET_ENTRY_BY_ID.get(widget_id)
|
||
if entry is None:
|
||
return False
|
||
fid = entry.get("requires_feature")
|
||
if not fid:
|
||
return True
|
||
return bool(_check_feature_access(profile_id, fid, conn)["allowed"])
|
||
|
||
|
||
def _public_row(entry: dict[str, Any], *, allowed: bool) -> dict[str, Any]:
|
||
return {
|
||
"id": entry["id"],
|
||
"title": entry["title"],
|
||
"description": entry["description"],
|
||
"allowed": allowed,
|
||
}
|
||
|
||
|
||
def widgets_catalog_for_profile(profile_id: str, conn) -> list[dict[str, Any]]:
|
||
"""Zeilen für GET /api/app/widgets/catalog (ohne internes requires_feature-Feld)."""
|
||
out: list[dict[str, Any]] = []
|
||
for e in WIDGET_CATALOG:
|
||
fid = e.get("requires_feature")
|
||
allowed = True
|
||
if fid:
|
||
allowed = bool(_check_feature_access(profile_id, fid, conn)["allowed"])
|
||
out.append(_public_row(e, allowed=allowed))
|
||
return out
|
||
|
||
|
||
def widgets_catalog_payload(profile_id: str, conn) -> dict[str, Any]:
|
||
return {
|
||
"catalog_version": 1,
|
||
"widgets": widgets_catalog_for_profile(profile_id, conn),
|
||
}
|
||
|
||
|
||
def apply_entitlements_to_layout_dict(layout: dict[str, Any], profile_id: str, conn) -> dict[str, Any]:
|
||
"""
|
||
Setzt enabled=False für Widgets ohne Berechtigung. Mindestens ein Widget bleibt aktiv (welcome).
|
||
"""
|
||
out = copy.deepcopy(layout)
|
||
widgets = out.get("widgets") or []
|
||
for w in widgets:
|
||
wid = w.get("id")
|
||
if not wid:
|
||
continue
|
||
if w.get("enabled") and not widget_id_allowed(wid, profile_id, conn):
|
||
w["enabled"] = False
|
||
if not any(w.get("enabled") for w in widgets):
|
||
for w in widgets:
|
||
if w.get("id") == "welcome":
|
||
w["enabled"] = True
|
||
break
|
||
return out
|
||
|