feat: Bereitstellungsvorlagen im Kunden-Admin-Bereich anlegen

Neue Sektion auf der /admin/kunden/{id}-Detailseite: Liste bestehender
Bereitstellungsvorlagen der Organisation + Formular zum Anlegen einer
neuen (Workspace, Backend, Root-Dateisystem, Default/Diskverschluesselung/
Secure-Boot), ruft den neuen provisioning-server-Endpoint auf. Schliesst
die in Phase 5 dokumentierte Luecke: bisher hatte eine frische
Kundenorganisation keine Moeglichkeit, ueberhaupt eine Vorlage zu
bekommen, und damit eine leere Auswahl auf der
Installationsmedium-Selfservice-Seite.

Lokal end-to-end verifiziert (siehe provisioning-server-Commit).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Thomas Stallinger 2026-08-07 01:59:34 +02:00
parent 19b39af0ce
commit 43c75df1ed
2 changed files with 102 additions and 0 deletions

View File

@ -51,6 +51,15 @@ def detail(request: Request, organization_id: str, user: dict = Depends(require_
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 []
return templates.TemplateResponse(
request,
"admin/kunden_detail.html",
@ -60,10 +69,42 @@ def detail(request: Request, organization_id: str, user: dict = Depends(require_
"organization_id": organization_id,
"activation_codes": activation_codes,
"benutzer": benutzer,
"bereitstellungsvorlagen": bereitstellungsvorlagen,
"workspaces": workspaces,
"backends": backends,
},
)
@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")

View File

@ -25,6 +25,67 @@
</form>
</section>
<section>
<h3>Bereitstellungsvorlagen</h3>
<figure>
<table>
<thead><tr><th>Bezeichnung</th><th>Workspace</th><th>Backend</th><th>Standard</th></tr></thead>
<tbody>
{% for v in bereitstellungsvorlagen %}
<tr>
<td>{{ v.label }}</td>
<td>{{ v.workspace.name }}</td>
<td>{{ v.backend.name }} {{ v.backend.version }}</td>
<td>{{ "ja" if v.is_default else "nein" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</figure>
<details>
<summary role="button" class="secondary outline">Neue Bereitstellungsvorlage anlegen</summary>
<form method="post" action="/admin/kunden/{{ organization_id }}/bereitstellungsvorlagen">
<label for="label">Bezeichnung</label>
<input type="text" id="label" name="label" required>
<label for="workspace_id">Workspace</label>
<select id="workspace_id" name="workspace_id" required>
{% for w in workspaces %}
<option value="{{ w.id }}">{{ w.name }}{{ " (" + w.organization_name + ")" if w.organization_name else " (global)" }}</option>
{% endfor %}
</select>
<label for="backend_id">Backend</label>
<select id="backend_id" name="backend_id" required>
{% for b in backends %}
<option value="{{ b.id }}">{{ b.name }} {{ b.version }}</option>
{% endfor %}
</select>
<label for="root_filesystem">Root-Dateisystem</label>
<select id="root_filesystem" name="root_filesystem">
<option value="ext4">ext4</option>
<option value="btrfs">btrfs</option>
</select>
<label>
<input type="checkbox" name="is_default">
Als Standard für diese Organisation festlegen
</label>
<label>
<input type="checkbox" name="disk_encryption">
Festplattenverschlüsselung
</label>
<label>
<input type="checkbox" name="secure_boot_required">
Secure Boot erforderlich
</label>
<button type="submit">Anlegen</button>
</form>
</details>
</section>
<section>
<h3>Kundenkonten</h3>
<figure>