Optionen-Eingabe auf OE-/Gruppen-Ebene + Text-/Passwort-Feldtypen
Bisher konnten Merkmal-Optionen nur pro Gerät gesetzt werden (nur Checkboxen, siehe alte OPTIONEN_FELDER in geraete.py) - für die neuen AD/Fileserver/Update-Proxy-Merkmale (admin/ansible-content) muss das aber auf OE-/Gruppen-Ebene passieren (Entscheidung vom 09.09.2026: Geräte- Einzel-Konfiguration ist nicht der vorgesehene Weg). Neues gemeinsames Modul optionen_felder.py (ersetzt die bisher lokale, duplizierte Map in geraete.py): Feld-Definitionen mit type (checkbox/text/password), genutzt von geraete.py, struktur.py (neu) und gruppen.py (neu). parse_optionen_form() ersetzt die bisherige reine Checkbox-Parsing-Logik - Text-/Passwort-Felder werden nur übernommen, wenn tatsächlich etwas eingegeben wurde (leer = "nicht ändern", siehe merge_optionen() in admin/provisioning-server). Templates (auftragskatalog.html, struktur_merkmale.html neu, gruppe_detail.html neu) rendern je nach type ein Checkbox- oder Text-/ Passwort-Eingabefeld, mit Hinweis-Platzhalter, wenn für ein Passwort-Feld bereits ein Wert hinterlegt ist (aus der maskierten optionen-Anzeige des Servers, nie der Klartext). Gehört zusammen mit admin/provisioning-server (encrypt/decrypt/merge- Logik, erweiterte OE-/Gruppen-Select-Endpunkte) und admin/ansible-content (die drei neuen Rollen, die diese Optionen konsumieren). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
This commit is contained in:
1 parent
3b98d682d0
commit
2618e5e468
7 files changed
+109
-18
No files matched your search
@@ -0,0 +1,48 @@
|
||||
# Gemeinsame Feld-Definitionen für Merkmal-Optionen (ADR-0010), verwendet
|
||||
# von geraete.py (Geräte-Ebene), struktur.py (OE-Ebene) und gruppen.py
|
||||
# (Gruppen-Ebene) - bisher lag das nur lokal+dupliziert in geraete.py und
|
||||
# kannte nur Checkboxen (siehe browser-brave/standardbrowser). Seit den
|
||||
# AD/OIDC/Fileserver-Merkmalen (10.09.2026) gibt es auch Text- und
|
||||
# Passwort-Felder - "type" steuert den HTML-Input-Typ und die
|
||||
# Parsing-Logik in parse_optionen_form().
|
||||
#
|
||||
# Passwort-Felder werden nie mit ihrem aktuellen Wert vorausgefüllt (siehe
|
||||
# Templates) und nur übernommen, wenn tatsächlich etwas eingegeben wurde -
|
||||
# ein leer gelassenes Passwort-Feld beim Bearbeiten überschreibt also
|
||||
# nicht versehentlich ein bereits gesetztes Passwort mit nichts.
|
||||
|
||||
OPTIONEN_FELDER = {
|
||||
"browser-brave": [
|
||||
{"key": "standardbrowser", "label": "Als Standardbrowser festlegen", "type": "checkbox"},
|
||||
],
|
||||
"ad-anbindung": [
|
||||
{"key": "ad_domain", "label": "AD-Domäne (z.B. schule.local)", "type": "text"},
|
||||
{"key": "ad_join_user", "label": "Beitritts-Benutzer (z.B. Administrator)", "type": "text"},
|
||||
{"key": "ad_join_passwort", "label": "Beitritts-Passwort", "type": "password"},
|
||||
],
|
||||
"fileserver": [
|
||||
{"key": "fileserver_adresse", "label": "Server-Adresse (Host oder IP)", "type": "text"},
|
||||
{"key": "fileserver_protokoll", "label": "Protokoll (smb oder webdav)", "type": "text"},
|
||||
{"key": "fileserver_home_freigabe", "label": "Freigabename für $HOME (leer = kein Home-Mount)", "type": "text"},
|
||||
{"key": "fileserver_user", "label": "Zugangs-Benutzer (leer = Kerberos/AD-Anmeldung verwenden)", "type": "text"},
|
||||
{"key": "fileserver_passwort", "label": "Zugangs-Passwort (leer = Kerberos/AD-Anmeldung verwenden)", "type": "password"},
|
||||
],
|
||||
"update-proxy": [
|
||||
{"key": "proxy_adresse", "label": "Proxy-Gerät (Host oder IP, Port 3142)", "type": "text"},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def parse_optionen_form(form, merkmal_key: str) -> dict:
|
||||
"""Liest die zu einem Merkmal gehörenden optionen-Felder aus einem
|
||||
FastAPI-Formular gemäß OPTIONEN_FELDER - Checkboxen werden zu bool,
|
||||
Text-/Passwort-Felder nur übernommen, wenn tatsächlich etwas eingegeben
|
||||
wurde (leer = "nicht ändern", siehe Modul-Kommentar oben)."""
|
||||
ergebnis = {}
|
||||
for feld in OPTIONEN_FELDER.get(merkmal_key, []):
|
||||
roh = form.get(f"optionen.{feld['key']}")
|
||||
if feld["type"] == "checkbox":
|
||||
ergebnis[feld["key"]] = roh == "on"
|
||||
elif roh:
|
||||
ergebnis[feld["key"]] = roh
|
||||
return ergebnis
|
||||
+2
-13
@@ -3,21 +3,13 @@ from fastapi.responses import RedirectResponse
|
||||
|
||||
from anode_client import anode_request
|
||||
from auth import get_current_user
|
||||
from optionen_felder import OPTIONEN_FELDER, parse_optionen_form
|
||||
from routers.gruppen import fetch_gruppen
|
||||
from templating import templates
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
# Merkmale, für die die UI eine zusätzliche Auswahl-Option anbietet (siehe
|
||||
# ADR-0010 optionen-Mechanismus). Bewusst hier als kleine, explizite Map
|
||||
# statt eines generischen Options-Schemas - nur ein Fall existiert aktuell.
|
||||
OPTIONEN_FELDER = {
|
||||
"browser-brave": [
|
||||
{"key": "standardbrowser", "label": "Als Standardbrowser festlegen"},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def find_device_in_organization(device_id: str, organization_id: str) -> dict | None:
|
||||
"""
|
||||
@@ -177,10 +169,7 @@ async def auftrag_select(
|
||||
raise HTTPException(status_code=404, detail="Gerät wurde nicht gefunden.")
|
||||
|
||||
form = await request.form()
|
||||
optionen = {
|
||||
feld["key"]: (form.get(f"optionen.{feld['key']}") == "on")
|
||||
for feld in OPTIONEN_FELDER.get(merkmal_key, [])
|
||||
}
|
||||
optionen = parse_optionen_form(form, merkmal_key)
|
||||
|
||||
anode_request(
|
||||
"POST",
|
||||
|
||||
+10
-2
@@ -3,6 +3,7 @@ from fastapi.responses import RedirectResponse
|
||||
|
||||
from anode_client import anode_request
|
||||
from auth import get_current_user
|
||||
from optionen_felder import OPTIONEN_FELDER, parse_optionen_form
|
||||
from templating import templates
|
||||
|
||||
|
||||
@@ -121,16 +122,23 @@ def gruppe_detail(request: Request, gruppe_id: str, user=Depends(get_current_use
|
||||
"kategorien": kategorien,
|
||||
"devices": devices,
|
||||
"mitglied_ids": mitglied_ids,
|
||||
"optionen_felder": OPTIONEN_FELDER,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.post("/gruppen/{gruppe_id}/merkmale/{merkmal_key}/select")
|
||||
def gruppe_merkmal_select(gruppe_id: str, merkmal_key: str, user=Depends(get_current_user)):
|
||||
async def gruppe_merkmal_select(request: Request, gruppe_id: str, merkmal_key: str, user=Depends(get_current_user)):
|
||||
if find_gruppe_in_organization(gruppe_id, user.organization_id) is None:
|
||||
raise HTTPException(status_code=404, detail="Gruppe wurde nicht gefunden.")
|
||||
|
||||
anode_request("POST", f"/api/v1/gruppen/{gruppe_id}/merkmale/{merkmal_key}/select")
|
||||
form = await request.form()
|
||||
optionen = parse_optionen_form(form, merkmal_key)
|
||||
|
||||
anode_request(
|
||||
"POST", f"/api/v1/gruppen/{gruppe_id}/merkmale/{merkmal_key}/select",
|
||||
json={"optionen": optionen},
|
||||
)
|
||||
|
||||
return RedirectResponse(f"/gruppen/{gruppe_id}", status_code=303)
|
||||
|
||||
|
||||
+12
-3
@@ -3,6 +3,7 @@ from fastapi.responses import RedirectResponse
|
||||
|
||||
from anode_client import anode_request
|
||||
from auth import get_current_user
|
||||
from optionen_felder import OPTIONEN_FELDER, parse_optionen_form
|
||||
from templating import templates
|
||||
|
||||
|
||||
@@ -122,16 +123,24 @@ def oe_merkmale_ansicht(request: Request, oe_id: str, user=Depends(get_current_u
|
||||
kategorien.setdefault(kategorie_name, []).append(eintrag)
|
||||
|
||||
return templates.TemplateResponse(
|
||||
request, "struktur_merkmale.html", {"user": user, "oe": oe, "kategorien": kategorien}
|
||||
request,
|
||||
"struktur_merkmale.html",
|
||||
{"user": user, "oe": oe, "kategorien": kategorien, "optionen_felder": OPTIONEN_FELDER},
|
||||
)
|
||||
|
||||
|
||||
@router.post("/struktur/{oe_id}/merkmale/{merkmal_key}/select")
|
||||
def oe_merkmal_select(oe_id: str, merkmal_key: str, user=Depends(get_current_user)):
|
||||
async def oe_merkmal_select(request: Request, oe_id: str, merkmal_key: str, user=Depends(get_current_user)):
|
||||
if find_oe_in_organization(oe_id, user.organization_id) is None:
|
||||
raise HTTPException(status_code=404, detail="OE wurde nicht gefunden.")
|
||||
|
||||
anode_request("POST", f"/api/v1/organisationseinheiten/{oe_id}/merkmale/{merkmal_key}/select")
|
||||
form = await request.form()
|
||||
optionen = parse_optionen_form(form, merkmal_key)
|
||||
|
||||
anode_request(
|
||||
"POST", f"/api/v1/organisationseinheiten/{oe_id}/merkmale/{merkmal_key}/select",
|
||||
json={"optionen": optionen},
|
||||
)
|
||||
|
||||
return RedirectResponse(f"/struktur/{oe_id}/merkmale", status_code=303)
|
||||
|
||||
|
||||
@@ -56,8 +56,13 @@
|
||||
<div class="optionen">
|
||||
{% for feld in optionen_felder[eintrag.merkmal] %}
|
||||
<label>
|
||||
{% if feld.type == "checkbox" %}
|
||||
<input type="checkbox" name="optionen.{{ feld.key }}">
|
||||
{{ feld.label }}
|
||||
{% else %}
|
||||
{{ feld.label }}
|
||||
<input type="{{ feld.type }}" name="optionen.{{ feld.key }}" autocomplete="off">
|
||||
{% endif %}
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
@@ -33,6 +33,22 @@
|
||||
<td>
|
||||
{% if eintrag.aktiv != true %}
|
||||
<form method="post" action="/gruppen/{{ gruppe.id }}/merkmale/{{ eintrag.merkmal }}/select">
|
||||
{% if optionen_felder.get(eintrag.merkmal) %}
|
||||
<div class="optionen">
|
||||
{% for feld in optionen_felder[eintrag.merkmal] %}
|
||||
<label>
|
||||
{% if feld.type == "checkbox" %}
|
||||
<input type="checkbox" name="optionen.{{ feld.key }}">
|
||||
{{ feld.label }}
|
||||
{% else %}
|
||||
{{ feld.label }}
|
||||
<input type="{{ feld.type }}" name="optionen.{{ feld.key }}" autocomplete="off"
|
||||
{% if eintrag.optionen.get(feld.key) %}placeholder="(bereits gesetzt - leer lassen zum Beibehalten)"{% endif %}>
|
||||
{% endif %}
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<button type="submit">Aktiv setzen</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
@@ -32,6 +32,22 @@
|
||||
<td>
|
||||
{% if eintrag.aktiv != true %}
|
||||
<form method="post" action="/struktur/{{ oe.id }}/merkmale/{{ eintrag.merkmal }}/select">
|
||||
{% if optionen_felder.get(eintrag.merkmal) %}
|
||||
<div class="optionen">
|
||||
{% for feld in optionen_felder[eintrag.merkmal] %}
|
||||
<label>
|
||||
{% if feld.type == "checkbox" %}
|
||||
<input type="checkbox" name="optionen.{{ feld.key }}">
|
||||
{{ feld.label }}
|
||||
{% else %}
|
||||
{{ feld.label }}
|
||||
<input type="{{ feld.type }}" name="optionen.{{ feld.key }}" autocomplete="off"
|
||||
{% if eintrag.optionen.get(feld.key) %}placeholder="(bereits gesetzt - leer lassen zum Beibehalten)"{% endif %}>
|
||||
{% endif %}
|
||||
</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<button type="submit">Aktiv setzen</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
Reference in new issue
Block a user