diff --git a/routers/admin_geraete.py b/routers/admin_geraete.py index 51a6a68..381355c 100644 --- a/routers/admin_geraete.py +++ b/routers/admin_geraete.py @@ -1,9 +1,9 @@ -from fastapi import APIRouter, Depends, HTTPException, Request +from fastapi import APIRouter, Depends, Form, HTTPException, Request from fastapi.responses import RedirectResponse from anode_client import anode_request from auth import require_admin -from routers.geraete import OPTIONEN_FELDER, gruppiere_nach_kategorie +from routers.geraete import OPTIONEN_FELDER, anreichere_mit_quelle_url, gruppiere_nach_kategorie from templating import templates @@ -26,11 +26,31 @@ def index(request: Request, user: dict = Depends(require_admin)): result = anode_request("GET", "/api/v1/devices") devices = result.get("devices", []) if result.get("success") else [] + # Admin-Liste ist organisationsübergreifend - jede Zeile braucht die + # OE-Auswahl ihrer eigenen Organisation für "In OE verschieben", daher + # je vorkommender Organisation einmal (nicht pro Gerät) abfragen. + oe_by_org: dict[str, list[dict]] = {} + for organization_id in {device["organization_id"] for device in devices}: + oe_result = anode_request("GET", f"/api/v1/organizations/{organization_id}/organisationseinheiten") + oe_by_org[organization_id] = oe_result.get("organisationseinheiten", []) if oe_result.get("success") else [] + return templates.TemplateResponse( - request, "admin/geraete_liste.html", {"user": user, "devices": devices} + request, + "admin/geraete_liste.html", + {"user": user, "devices": devices, "oe_by_org": oe_by_org}, ) +@router.post("/{device_id}/oe") +def oe_verschieben(device_id: str, oe_id: str = Form(...), user: dict = Depends(require_admin)): + if find_device(device_id) is None: + raise HTTPException(status_code=404, detail="Gerät wurde nicht gefunden.") + + anode_request("POST", f"/api/v1/devices/{device_id}/oe", json={"oe_id": oe_id}) + + return RedirectResponse("/admin/geraete", status_code=303) + + @router.get("/{device_id}") def auftragskatalog_ansicht(request: Request, device_id: str, user: dict = Depends(require_admin)): device = find_device(device_id) @@ -42,6 +62,12 @@ def auftragskatalog_ansicht(request: Request, device_id: str, user: dict = Depen katalog = result.get("auftragskatalog", []) if result.get("success") else [] fehler = None if result.get("success") else result.get("message") + katalog = anreichere_mit_quelle_url( + katalog, + f"/admin/kunden/{device['organization_id']}/organisationseinheiten", + f"/admin/kunden/{device['organization_id']}/gruppen", + ) + return templates.TemplateResponse( request, "auftragskatalog.html", diff --git a/routers/admin_kunden.py b/routers/admin_kunden.py index 7a38377..fba182d 100644 --- a/routers/admin_kunden.py +++ b/routers/admin_kunden.py @@ -228,3 +228,48 @@ async def konto_anlegen( ) 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) diff --git a/routers/geraete.py b/routers/geraete.py index 798df7c..0163371 100644 --- a/routers/geraete.py +++ b/routers/geraete.py @@ -48,6 +48,28 @@ def gruppiere_nach_kategorie(katalog: list[dict]) -> dict[str, list[dict]]: return kategorien +def anreichere_mit_quelle_url(katalog: list[dict], struktur_praefix: str, gruppen_praefix: str) -> list[dict]: + """ + Ergänzt jeden Auftragskatalog-Eintrag um quelle_url - ein Sprungziel zur + verantwortlichen OE/Gruppe (siehe ADR-0012), passend zum jeweiligen + Kontext (Kundenportal-Self-Service vs. Admin-Bereich, siehe Aufrufer). + None für die Fälle "workspace"/"geraet", die kein sinnvolles Linkziel + haben. + """ + + for eintrag in katalog: + quelle, quelle_id = eintrag.get("quelle"), eintrag.get("quelle_id") + + if quelle == "oe" and quelle_id: + eintrag["quelle_url"] = f"{struktur_praefix}/{quelle_id}/merkmale" + elif quelle == "gruppe" and quelle_id: + eintrag["quelle_url"] = f"{gruppen_praefix}/{quelle_id}" + else: + eintrag["quelle_url"] = None + + return katalog + + @router.get("/") def index(user: dict = Depends(get_current_user)): return RedirectResponse("/geraete", status_code=303) @@ -79,6 +101,8 @@ def auftragskatalog_ansicht(request: Request, device_id: str, user: dict = Depen katalog = result.get("auftragskatalog", []) if result.get("success") else [] fehler = None if result.get("success") else result.get("message") + katalog = anreichere_mit_quelle_url(katalog, "/struktur", "/gruppen") + return templates.TemplateResponse( request, "auftragskatalog.html", diff --git a/templates/admin/geraete_liste.html b/templates/admin/geraete_liste.html index ba7661d..b337dc7 100644 --- a/templates/admin/geraete_liste.html +++ b/templates/admin/geraete_liste.html @@ -8,7 +8,7 @@
- + {% for device in devices %} @@ -16,8 +16,19 @@ + + {% endfor %} diff --git a/templates/admin/oe_merkmale.html b/templates/admin/oe_merkmale.html new file mode 100644 index 0000000..cd6c8dd --- /dev/null +++ b/templates/admin/oe_merkmale.html @@ -0,0 +1,55 @@ +{% extends "admin/layout.html" %} +{% block title %}OE-Merkmale — Tuxflotte Admin{% endblock %} +{% block admin_content %} +

← Organisation

+

Merkmale dieser OE

+

"Nicht festgelegt" überlässt die Entscheidung einer übergeordneten OE bzw. dem Workspace-Standard des Geräts. Die OE-Struktur selbst wird im Kundenportal verwaltet (Self-Service), diese Seite dient nur der Merkmal-Konfiguration.

+ +{% if not kategorien %} +

Keine katalogfähigen Merkmale vorhanden.

+{% else %} + {% for kategorie_name, eintraege in kategorien.items() %} +

{{ kategorie_name }}

+
+
OrganisationHostnameKennungLetzter Check-in
OrganisationHostnameKennungOELetzter Check-in
{{ device.organization_name }} {{ device.hostname or "—" }} {{ device.device_fingerprint }}{{ device.oe_name or "—" }} {{ device.agent_last_checkin or "noch nie" }} Auftragskatalog → +
+ + +
+
+ + {% for eintrag in eintraege %} + + + + + + {% endfor %} + +
+ {{ eintrag.name }} + {% if eintrag.description %}
{{ eintrag.description }}{% endif %} +
+ {% if eintrag.aktiv is none %} + nicht festgelegt + {% elif eintrag.aktiv %} + aktiv + {% else %} + inaktiv + {% endif %} + + {% if eintrag.aktiv != true %} +
+ +
+ {% endif %} + {% if eintrag.aktiv != false %} +
+ +
+ {% endif %} + {% if eintrag.aktiv is not none %} +
+ +
+ {% endif %} +
+
+ {% endfor %} +{% endif %} +{% endblock %} diff --git a/templates/auftragskatalog.html b/templates/auftragskatalog.html index d2459d7..79e6932 100644 --- a/templates/auftragskatalog.html +++ b/templates/auftragskatalog.html @@ -22,16 +22,22 @@ {{ "ausgewählt" if eintrag.state == "present" else "nicht ausgewählt" }} {% if eintrag.quelle and eintrag.quelle != "workspace" %} -
via {{ {"oe": "OE", "gruppe": "Gruppe", "geraet": "manuell"}[eintrag.quelle] }} +
+ {% if eintrag.quelle_url %} + via {{ {"oe": "OE", "gruppe": "Gruppe"}[eintrag.quelle] }} + {% else %} + via {{ {"oe": "OE", "gruppe": "Gruppe", "geraet": "manuell"}[eintrag.quelle] }} + {% endif %} + {% endif %} {% if eintrag.state == "present" %} -
+
{% else %} -
+ {% if optionen_felder.get(eintrag.merkmal) %}
{% for feld in optionen_felder[eintrag.merkmal] %}