mitai-jinkendo/backend/routers/app_dashboard.py
Lars 365ce49c6a
All checks were successful
Deploy Development / deploy (push) Successful in 57s
Build Test / lint-backend (push) Successful in 0s
Build Test / build-frontend (push) Successful in 17s
feat: Introduce admin dashboard product standard management
- Added new API endpoints for managing the product dashboard standard, including retrieval, update, and deletion functionalities.
- Enhanced the DashboardConfigurePage to support admin mode for configuring the product dashboard standard.
- Updated the admin navigation to include a link for the product dashboard standard configuration.
- Refactored the dashboard layout logic to utilize the new product standard management features.
- Bumped app_dashboard version to 1.10.0 to reflect these enhancements and changes.
2026-04-08 10:32:18 +02:00

116 lines
3.9 KiB
Python

"""
Geschützter App-Bereich: Dashboard-Lab Layout (kein Produktiv-Dashboard).
/api/app/dashboard-layout — nur mit Session + aktivem Profil (X-Profile-Id).
"""
from typing import Any, Optional
from fastapi import APIRouter, Depends, Header, HTTPException
from psycopg2.extras import Json
from auth import require_auth
from dashboard_layout_schema import (
DashboardLayoutPayload,
coalesce_effective_layout,
lab_default_layout_dict,
)
from dashboard_widget_entitlements import apply_entitlements_to_layout_dict, widgets_catalog_payload
from db import get_cursor, get_db
from routers.profiles import get_pid
from system_dashboard_product_default import get_product_default_base_dict
router = APIRouter(prefix="/api/app", tags=["app-dashboard-lab"])
@router.get("/widgets/catalog")
def get_widgets_catalog(
x_profile_id: Optional[str] = Header(default=None),
session: dict = Depends(require_auth),
) -> dict[str, Any]:
"""Katalog inkl. allowed pro Widget (Feature / Subscription, effektiver Tier)."""
_ = session
pid = get_pid(x_profile_id)
with get_db() as conn:
return widgets_catalog_payload(pid, conn)
@router.get("/dashboard-layout")
def get_dashboard_layout(
x_profile_id: Optional[str] = Header(default=None),
session: dict = Depends(require_auth),
) -> dict[str, Any]:
_ = session
pid = get_pid(x_profile_id)
with get_db() as conn:
cur = get_cursor(conn)
cur.execute(
"SELECT dashboard_layout FROM profiles WHERE id = %s",
(pid,),
)
row = cur.fetchone()
raw = row["dashboard_layout"] if row else None
custom, effective = coalesce_effective_layout(raw)
with get_db() as conn:
base_product = get_product_default_base_dict(conn)
if not custom:
effective = base_product
effective = apply_entitlements_to_layout_dict(effective, pid, conn)
product_adj = apply_entitlements_to_layout_dict(base_product, pid, conn)
lab_adj = apply_entitlements_to_layout_dict(lab_default_layout_dict(), pid, conn)
return {
"custom": custom,
"layout": effective,
"product_default_layout": product_adj,
"lab_default_layout": lab_adj,
}
@router.put("/dashboard-layout")
def put_dashboard_layout(
body: dict[str, Any],
x_profile_id: Optional[str] = Header(default=None),
session: dict = Depends(require_auth),
) -> dict[str, Any]:
_ = session
pid = get_pid(x_profile_id)
try:
payload = DashboardLayoutPayload.model_validate(body)
except Exception as e:
raise HTTPException(422, str(e)) from e
with get_db() as conn:
adjusted = apply_entitlements_to_layout_dict(payload.to_stored_dict(), pid, conn)
try:
payload = DashboardLayoutPayload.model_validate(adjusted)
except Exception as e:
raise HTTPException(422, str(e)) from e
stored = payload.to_stored_dict()
with get_db() as conn:
cur = get_cursor(conn)
cur.execute(
"UPDATE profiles SET dashboard_layout = %s WHERE id = %s",
(Json(stored), pid),
)
if cur.rowcount == 0:
raise HTTPException(404, "Profil nicht gefunden")
return {"ok": True, "layout": stored}
@router.post("/dashboard-layout/reset")
def reset_dashboard_layout(
x_profile_id: Optional[str] = Header(default=None),
session: dict = Depends(require_auth),
) -> dict[str, Any]:
_ = session
pid = get_pid(x_profile_id)
with get_db() as conn:
cur = get_cursor(conn)
cur.execute(
"UPDATE profiles SET dashboard_layout = NULL WHERE id = %s",
(pid,),
)
if cur.rowcount == 0:
raise HTTPException(404, "Profil nicht gefunden")
base = get_product_default_base_dict(conn)
cleared = apply_entitlements_to_layout_dict(base, pid, conn)
return {"ok": True, "layout": cleared}