from fastapi import APIRouter, Depends, Form, HTTPException, Request from fastapi.responses import RedirectResponse from anode_client import anode_request from auth import UserCreate, UserManager, get_user_manager, require_admin from db import get_database_connection from templating import templates router = APIRouter(prefix="/admin/kunden") def fetch_benutzer_fuer_organisation(organization_id: str) -> list[dict]: with get_database_connection() as conn: with conn.cursor() as cur: cur.execute( "SELECT email, is_superuser, created_at FROM benutzer WHERE organization_id = %s ORDER BY email", (organization_id,), ) return [ {"email": email, "is_superuser": is_superuser, "created_at": created_at} for email, is_superuser, created_at in cur.fetchall() ] @router.get("") def index(request: Request, user: dict = Depends(require_admin)): result = anode_request("GET", "/api/v1/organizations") organizations = result.get("organizations", []) if result.get("success") else [] return templates.TemplateResponse( request, "admin/kunden_index.html", {"user": user, "organizations": organizations} ) @router.post("") def organisation_anlegen(name: str = Form(...), user: dict = Depends(require_admin)): anode_request("POST", "/api/v1/organizations", json={"name": name}) return RedirectResponse("/admin/kunden", status_code=303) @router.get("/{organization_id}") def detail(request: Request, organization_id: str, user: dict = Depends(require_admin)): orgs_result = anode_request("GET", "/api/v1/organizations") organizations = orgs_result.get("organizations", []) if orgs_result.get("success") else [] organization = next((o for o in organizations if o["id"] == organization_id), None) codes_result = anode_request("GET", f"/api/v1/organizations/{organization_id}/activation-codes") activation_codes = codes_result.get("activation_codes", []) if codes_result.get("success") else [] benutzer = fetch_benutzer_fuer_organisation(organization_id) vorlagen_result = anode_request("GET", f"/api/v1/organizations/{organization_id}/bereitstellungsvorlagen") bereitstellungsvorlagen = vorlagen_result.get("bereitstellungsvorlagen", []) if vorlagen_result.get("success") else [] workspaces_result = anode_request("GET", "/api/v1/workspaces") workspaces = workspaces_result.get("workspaces", []) if workspaces_result.get("success") else [] backends_result = anode_request("GET", "/api/v1/backends") backends = backends_result.get("backends", []) if backends_result.get("success") else [] gruppen_result = anode_request("GET", f"/api/v1/organizations/{organization_id}/gruppen") gruppen = gruppen_result.get("gruppen", []) if gruppen_result.get("success") else [] return templates.TemplateResponse( request, "admin/kunden_detail.html", { "user": user, "organization": organization, "organization_id": organization_id, "activation_codes": activation_codes, "benutzer": benutzer, "bereitstellungsvorlagen": bereitstellungsvorlagen, "workspaces": workspaces, "backends": backends, "gruppen": gruppen, }, ) @router.post("/{organization_id}/gruppen") def gruppe_anlegen(organization_id: str, name: str = Form(...), user: dict = Depends(require_admin)): anode_request("POST", f"/api/v1/organizations/{organization_id}/gruppen", json={"name": name}) return RedirectResponse(f"/admin/kunden/{organization_id}", status_code=303) @router.post("/{organization_id}/gruppen/{gruppe_id}/loeschen") def gruppe_loeschen(organization_id: str, gruppe_id: str, user: dict = Depends(require_admin)): anode_request("DELETE", f"/api/v1/gruppen/{gruppe_id}") return RedirectResponse(f"/admin/kunden/{organization_id}", status_code=303) @router.get("/{organization_id}/gruppen/{gruppe_id}") def gruppe_detail(request: Request, organization_id: str, gruppe_id: str, user: dict = Depends(require_admin)): gruppen_result = anode_request("GET", f"/api/v1/organizations/{organization_id}/gruppen") gruppen = gruppen_result.get("gruppen", []) if gruppen_result.get("success") else [] gruppe = next((g for g in gruppen if g["id"] == gruppe_id), None) if gruppe is None: raise HTTPException(status_code=404, detail="Gruppe wurde nicht gefunden.") merkmale_result = anode_request("GET", f"/api/v1/gruppen/{gruppe_id}/merkmale") merkmale = merkmale_result.get("merkmale", []) if merkmale_result.get("success") else [] kategorien: dict[str, list[dict]] = {} for eintrag in merkmale: kategorie_name = eintrag["kategorie"]["name"] if eintrag["kategorie"] else "Ohne Kategorie" kategorien.setdefault(kategorie_name, []).append(eintrag) devices_result = anode_request("GET", f"/api/v1/organizations/{organization_id}/devices") devices = devices_result.get("devices", []) if devices_result.get("success") else [] # anode bietet keinen direkten "Geräte einer Gruppe"-Endpunkt an, nur # "Gruppen eines Geräts" (GET /devices/{id}/gruppen) - daher hier für # jedes Gerät der Organisation dessen Gruppen abfragen und filtern. mitglied_ids = set() for device in devices: dg_result = anode_request("GET", f"/api/v1/devices/{device['id']}/gruppen") device_gruppen = dg_result.get("gruppen", []) if dg_result.get("success") else [] if any(g["id"] == gruppe_id for g in device_gruppen): mitglied_ids.add(device["id"]) return templates.TemplateResponse( request, "admin/gruppe_detail.html", { "user": user, "organization_id": organization_id, "gruppe": gruppe, "kategorien": kategorien, "devices": devices, "mitglied_ids": mitglied_ids, }, ) @router.post("/{organization_id}/gruppen/{gruppe_id}/merkmale/{merkmal_key}/select") def gruppe_merkmal_select(organization_id: str, gruppe_id: str, merkmal_key: str, user: dict = Depends(require_admin)): anode_request("POST", f"/api/v1/gruppen/{gruppe_id}/merkmale/{merkmal_key}/select") return RedirectResponse(f"/admin/kunden/{organization_id}/gruppen/{gruppe_id}", status_code=303) @router.post("/{organization_id}/gruppen/{gruppe_id}/merkmale/{merkmal_key}/deselect") def gruppe_merkmal_deselect(organization_id: str, gruppe_id: str, merkmal_key: str, user: dict = Depends(require_admin)): anode_request("POST", f"/api/v1/gruppen/{gruppe_id}/merkmale/{merkmal_key}/deselect") return RedirectResponse(f"/admin/kunden/{organization_id}/gruppen/{gruppe_id}", status_code=303) @router.post("/{organization_id}/gruppen/{gruppe_id}/merkmale/{merkmal_key}/unset") def gruppe_merkmal_unset(organization_id: str, gruppe_id: str, merkmal_key: str, user: dict = Depends(require_admin)): anode_request("POST", f"/api/v1/gruppen/{gruppe_id}/merkmale/{merkmal_key}/unset") return RedirectResponse(f"/admin/kunden/{organization_id}/gruppen/{gruppe_id}", status_code=303) @router.post("/{organization_id}/gruppen/{gruppe_id}/geraete/{device_id}/hinzufuegen") def gruppe_geraet_hinzufuegen(organization_id: str, gruppe_id: str, device_id: str, user: dict = Depends(require_admin)): anode_request("POST", f"/api/v1/devices/{device_id}/gruppen/{gruppe_id}") return RedirectResponse(f"/admin/kunden/{organization_id}/gruppen/{gruppe_id}", status_code=303) @router.post("/{organization_id}/gruppen/{gruppe_id}/geraete/{device_id}/entfernen") def gruppe_geraet_entfernen(organization_id: str, gruppe_id: str, device_id: str, user: dict = Depends(require_admin)): anode_request("DELETE", f"/api/v1/devices/{device_id}/gruppen/{gruppe_id}") return RedirectResponse(f"/admin/kunden/{organization_id}/gruppen/{gruppe_id}", status_code=303) @router.post("/{organization_id}/bereitstellungsvorlagen") def bereitstellungsvorlage_anlegen( organization_id: str, workspace_id: str = Form(...), backend_id: str = Form(...), label: str = Form(...), is_default: str = Form(default=""), disk_encryption: str = Form(default=""), root_filesystem: str = Form(default="ext4"), secure_boot_required: str = Form(default=""), user: dict = Depends(require_admin), ): anode_request( "POST", f"/api/v1/organizations/{organization_id}/bereitstellungsvorlagen", json={ "workspace_id": workspace_id, "backend_id": backend_id, "label": label, "is_default": is_default == "on", "disk_encryption": disk_encryption == "on", "root_filesystem": root_filesystem, "secure_boot_required": secure_boot_required == "on", }, ) return RedirectResponse(f"/admin/kunden/{organization_id}", status_code=303) @router.post("/{organization_id}/activation-codes") def aktivierungscode_anlegen(organization_id: str, user: dict = Depends(require_admin)): anode_request("POST", f"/api/v1/organizations/{organization_id}/activation-codes") return RedirectResponse(f"/admin/kunden/{organization_id}", status_code=303) @router.post("/{organization_id}/konten") async def konto_anlegen( organization_id: str, email: str = Form(...), password: str = Form(...), is_superuser: str = Form(default=""), user: dict = Depends(require_admin), user_manager: UserManager = Depends(get_user_manager), ): await user_manager.create( UserCreate( email=email, password=password, organization_id=organization_id, is_superuser=is_superuser == "on", ) ) return RedirectResponse(f"/admin/kunden/{organization_id}", status_code=303) @router.get("/{organization_id}/organisationseinheiten/{oe_id}/merkmale") def oe_merkmale_ansicht(request: Request, organization_id: str, oe_id: str, user: dict = Depends(require_admin)): """ Minimale Admin-Ansicht einer OE - anders als bei Gruppen kein volles CRUD hier (OE-Struktur bleibt laut ADR-0012 Kunden-Self-Service unter /struktur), nur der Merkmale-Editor als Sprungziel von der Auftragskatalog-Herkunftsanzeige aus. """ merkmale_result = anode_request("GET", f"/api/v1/organisationseinheiten/{oe_id}/merkmale") merkmale = merkmale_result.get("merkmale", []) if merkmale_result.get("success") else [] kategorien: dict[str, list[dict]] = {} for eintrag in merkmale: kategorie_name = eintrag["kategorie"]["name"] if eintrag["kategorie"] else "Ohne Kategorie" kategorien.setdefault(kategorie_name, []).append(eintrag) return templates.TemplateResponse( request, "admin/oe_merkmale.html", {"user": user, "organization_id": organization_id, "oe_id": oe_id, "kategorien": kategorien}, ) @router.post("/{organization_id}/organisationseinheiten/{oe_id}/merkmale/{merkmal_key}/select") def admin_oe_merkmal_select(organization_id: str, oe_id: str, merkmal_key: str, user: dict = Depends(require_admin)): anode_request("POST", f"/api/v1/organisationseinheiten/{oe_id}/merkmale/{merkmal_key}/select") return RedirectResponse(f"/admin/kunden/{organization_id}/organisationseinheiten/{oe_id}/merkmale", status_code=303) @router.post("/{organization_id}/organisationseinheiten/{oe_id}/merkmale/{merkmal_key}/deselect") def admin_oe_merkmal_deselect(organization_id: str, oe_id: str, merkmal_key: str, user: dict = Depends(require_admin)): anode_request("POST", f"/api/v1/organisationseinheiten/{oe_id}/merkmale/{merkmal_key}/deselect") return RedirectResponse(f"/admin/kunden/{organization_id}/organisationseinheiten/{oe_id}/merkmale", status_code=303) @router.post("/{organization_id}/organisationseinheiten/{oe_id}/merkmale/{merkmal_key}/unset") def admin_oe_merkmal_unset(organization_id: str, oe_id: str, merkmal_key: str, user: dict = Depends(require_admin)): anode_request("POST", f"/api/v1/organisationseinheiten/{oe_id}/merkmale/{merkmal_key}/unset") return RedirectResponse(f"/admin/kunden/{organization_id}/organisationseinheiten/{oe_id}/merkmale", status_code=303)