Admin-Reiter je Organisation: OE-Baum als Karten mit Geräten/Distros,
eigenem bzw. geerbtem Workspace, Workspace zuweisen (automatisch oder
Version pinnen) und Unter-OEs anlegen; Ablehnungen der Distro-Regel
werden mit Begründung angezeigt. Reiterleiste bricht jetzt um.
Kundenseite: Struktur zeigt den Workspace je OE, Gerät verschieben zeigt
die Ablehnung statt sie zu verschlucken, Installationsmedium mit
Ziel-OE (gespeichert, Konflikt mit dem Profil verständlich gemeldet).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Veröffentlichte Version nur lesend, Bearbeiten legt einen Entwurf an.
Im Entwurf: Inhalt wie im Assistenten (auf der Distro nicht umgesetzte
Einträge gesperrt), Webapps aus Vorlage oder frei, Wirkungsvorschau
(+/− je Bereich, wie viele Geräte automatisch umsteigen, gepinnte,
Profile), Veröffentlichen mit Notiz, Verwerfen. Dazu Nutzung,
Versionsliste, Name/Zweck, Vorlage und Sichtbarkeit (alle, Kundengruppen,
Organisationen), Kopieren mit Vorab-Prüfung für andere Distro, Löschen
nur wenn ungenutzt mit Bestätigung.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sechs Schritte: Name/Zweck/Eigentümer, Apps, Webapps, Richtlinien
(aufklappbar wie im Katalog), Distro, Prüfen. Mitlaufende Distro-Leiste
zeigt, welche Distro noch möglich ist; bei Konflikten nennt Schritt 5 die
fehlenden Einträge mit Alternativen aus derselben Kategorie und
Entfernen-Knopf. Ohne JavaScript ein langes Formular, der Server prüft
ohnehin. Workspace-Liste verweist auf den Assistenten statt auf das alte
Schnellformular.
Nebenbei gefunden: das Inline-Skript in base.html, das ein manuell
gewähltes Hell-/Dunkel-Design vor dem CSS setzt, wurde von der eigenen
CSP blockiert - jetzt static/theme-init.js.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Workspace-Zuweisung an Gerätegruppen entfällt (Kunden- und Admin-Seite),
Gruppen ergänzen nur noch. Workspace-Liste zeigt Distro, Version,
Eigentümer und Vorlagen-Status, beim Anlegen wählbare Distro.
Workspace-Creator neu, Schritt C.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Workspace-Liste unter /admin/workspaces/liste, Merkmale, Kategorien,
Webapp-Vorlagen und Distro-Abdeckung (frühere Flottenmatrix) unter
/admin/workspaces/vorlagen. Gemeinsame Unternavigation, Menüpunkt
Datenbank entfällt, alte /admin/technik-Adressen leiten weiter
(POST mit 307). Workspace-Creator neu, Schritt B.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Katalog ersetzt die Flottenmatrix als Übersicht: Apps, Webapps und
Konfigurationsrichtlinien aufklappbar nach Kategorien, je Eintrag Icon
(Fallback Zahnrad) und Distro-Verfügbarkeit aus den Blueprints.
Kategorien bekommen eine Art (App/Richtlinie), Webapp-Vorlagen eine
Kategorie. Workspace-Creator neu, Schritt A.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Menüpunkt ausgegraut, /workspace zeigt nur einen Hinweis plus die
bereitstehenden Workspaces (lesend), alle schreibenden Endpunkte liefern
403. Zuweisung an Gerätegruppen bleibt offen. Freischalten über
WORKSPACE_CREATOR_FREIGEGEBEN in routers/workspace.py.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Angehakte /home-/var-Partitionen wurden ohne Hinweis verworfen, wenn das
separate Auswahlfeld auf 'Einfach' stand (Kunden-ISO-Test 02.10.2026).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Neues Schema-Dropdown (single/custom) + feste Auswahl fuer /home, /var,
/usr (Aktivieren-Checkbox + Prozent + Dateisystem je Mountpoint) - bewusst
keine freie Mountpoint-Liste, einfacher serverseitig zu validieren und
deckt die konkret gewuenschten Faelle ab. Admin-Tabelle zeigt das
gewaehlte Schema jetzt mit an.
Teil der E2E-Testrunde, siehe platform-docs/roadmap/
e2e-testrunde-lokale-adressierung.md Phase 3. Gegenstueck in
provisioning-server (Validierung, DB-Persistenz) ist dort bereits
committet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DWKevK2QUK5tsGmqfSD6Yy
Neuer Proxy /admin/technik/merkmal-icons/{filename} (gleiches Muster wie
der bestehende Webapp-Icon-Proxy, anode kennt seine öffentliche Adresse
nicht) - anders als dort aber mit tatsächlich durchgereichtem Content-
Type statt fest angenommenem "image/x-icon", weil der Bildtyp hier
wirklich variiert (Simple-Icons-SVG vs. Favicon-ICO/PNG/JPG). Icons sind
klein genug (Obergrenze 2 MB) für einmaliges Einlesen statt echtem
Streaming.
Merkmale-Verwaltung zeigt jetzt Icon + Beschreibung sichtbar (vorher nur
im Bearbeiten-Formular versteckt), Flottenmatrix zeigt das Icon neben
Key/Name der Zeile.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Beide Platzhalter-Dashboards aus Phase 1 bekommen jetzt echten Inhalt -
Nächste-Schritte-Checkliste, Flotten-Gesundheit (Balken für Platten-/
RAM-Auslastung, Liste überfälliger Check-ins), letzte Aktionen. Holt die
Auswertung von den neuen anode-Endpunkten (provisioning-server), ergänzt
kundenseitig nur die Installationsmedium-Prüfung (iso_build_id lebt in
installationsmedium_konfiguration, kundenplattforms eigener DB).
Grafiken bewusst als einfache CSS-Balken (.balken/.balken-fuellung in
base.html) - kein Chart-Framework/CDN-Import, passt zum bestehenden
Vanilla-JS/CSS-Ansatz (table-tools.js, theme-toggle.js).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Formular-Ergänzung zum serverseitigen Gegenstück (provisioning-server):
optionale Kategorie-Auswahl (dieselben App-Kategorien wie bei Merkmalen)
und ein Kiosk-Modus-Kontrollkästchen. Das Kontrollkästchen ist deaktiviert
mit Hinweistext, solange der Workspace weder Chrome noch Chromium als
Merkmal hat (hat_kiosk_browser in workspace.py) - vermeidet einen
Schalter, der sichtbar existiert, aber wirkungslos wäre. Webapp-Liste
zeigt Kategorie/Kiosk-Status als kleine Badges an.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Nutzer-Feedback (11.09.2026): der Excel-artige Spalten-Filter (Runde von
heute Vormittag) fehlte weiterhin auf einigen Spalten - Hostname/Kennung/
Gruppen/Letzter Check-in in der Flotte, Key/Name in den Merkmalen, sowie
allen Spalten der Flottenmatrix. Jetzt bekommt konsequent jede
data-sort-Spalte auch data-filter, wie beim Vorbild Excel (auch dort hat
jede Spalte einen Autofilter, unabhängig von der Sinnhaftigkeit im
Einzelfall).
Zweiter Wunsch: solange ein Filter tatsächlich einschränkt (nicht mehr
alle Werte angehakt), färbt sich die Titelzeile sanft in der Akzentfarbe
(neue CSS-Klasse .spalten-gefiltert, von wendeSpaltenFilterAn() gesetzt) -
das Trichter-Icon nutzt fill="currentColor" und färbt sich automatisch
mit.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Schliesst die Lücke aus dem heutigen Nutzer-Feedback: Organisationen ohne
einen einzigen eigenen Workspace hatten keinen Weg mehr zu Webapps, weil
Webapps strikt an Besitz gebunden sind (fetch_sichtbaren_workspace()).
Neue Route POST /workspace/{id}/uebernehmen prüft Sichtbarkeit (global
oder eigene Organisation), ruft den neuen anode-Klon-Endpunkt auf und
leitet auf die Detailseite des neuen, voll editierbaren Workspace um.
Button dazu in workspace_detail.html, nur bei schreibgeschützten
(globalen) Standards sichtbar.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Gleicher Rueckfall wie der Bugfix vom Vortag (workspace_namen|length),
diesmal in der neuen clientseitigen baueSpaltenFilter() - Nutzer wollte
das Icon konsistent immer sehen, unabhaengig von der aktuellen
Werteanzahl in der Spalte.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Live vom Nutzer gefunden: eine Aktualisierung von table-tools.js kam
nicht an, weil der Browser die alte Version unter derselben URL
gecacht hatte - kein Grund fuer den Browser, neu zu laden. Neuer
static_version-Jinja-Global (Hash ueber den tatsaechlichen Inhalt aller
static/-Dateien, bei Prozessstart berechnet), als ?v=... an pico.min.css/
table-tools.js/theme-toggle.js angehaengt - aendert sich automatisch bei
jedem Deploy, ohne manuelles Hochzaehlen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Nutzer-Wunsch: Filter-Icon direkt in der Titelzeile statt eines eigenen
Blocks unter der Suche. table-tools.js komplett umgebaut - ein
th[data-filter] bekommt automatisch ein ▾-Symbol, Klick oeffnet ein
Dropdown mit Checkboxen fuer jeden in dieser Spalte tatsaechlich
vorkommenden Wert (rein clientseitig aus den gerenderten Zellen
ermittelt, kein Server-Code mehr noetig). Ersetzt das aeltere,
serverseitig befuellte data-table-category-filter-Muster vollstaendig -
Merkmale/Kategorie und beide Geraete-Listen/Workspace nutzen jetzt nur
noch das Attribut, kein <details>-Block + kein workspace_namen/
Wertelisten-Handling im Router mehr noetig. Bei Gelegenheit gleich auf
OE, Status (Kunde) und Organisation (Admin) ausgeweitet, da jetzt keine
Server-Aenderung mehr pro Spalte noetig ist.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Nutzer-Feedback: als Org-Admin (Liebherr, aktuell alle 6 Geräte auf
'Entwickler') war der Filter unsichtbar, weil er absichtlich erst ab 2
unterschiedlichen Workspace-Namen erschien - schlechte Entscheidung,
Filter jetzt immer sichtbar sobald mindestens ein Workspace vorkommt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Organisations-Admins bekommen erstmals Zugriff auf die Workspace-
Komposition (bisher exklusiv /admin/technik/workspaces vorbehalten,
ADR-0028 hatte das als "zunaechst admin-only" offen gelassen).
- routers/workspace.py (Neubau, ersetzt den Phase-1-Platzhalter):
/workspace (eigene Workspaces + bereitgestellte globale Standards,
ueber den bestehenden ADR-0028-Endpunkt), /workspace (POST, anlegen -
organization_id kommt nie aus dem Formular), /workspace/{id} (Detail:
eigener Workspace -> volle Kategorie-Checkbox-Komposition, Wiederver-
wendung von admin_technik.gruppiere_merkmale_nach_kategorie(); globaler
Standard -> reine Leseansicht, kein Formular).
- Webapps-Verwaltung zieht von der bisherigen eigenen Seite (/webapps)
hierher um (workspace_detail.html, nur fuer eigene Workspaces sichtbar)
- alte Route bleibt nur noch als 301-Redirect auf /workspace bestehen.
- Neue Besitz-/Sichtbarkeitspruefung fetch_sichtbaren_workspace() (eigene
Organisation ODER global sichtbar, sonst 404) analog zum bestehenden
find_oe_in_organization()-Muster.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Nach dem in Runde 2 gebauten data-table-category-filter-Muster (gleicher
JS-Mechanismus, kein neuer Code) - Kunden- und Admin-Geräteliste bekommen
einen Checkbox-Filter nach Workspace-Name. Admin-Geräteliste hatte bisher
gar keine Workspace-Spalte, jetzt ergänzt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
- Neue Primaer-Nav-Reihenfolge Kunde: Dashboard -> Organisation ->
Workspace (neu) -> Installationsmedium -> Flotte (ans Ende verschoben).
- Neue Primaer-Nav-Reihenfolge Admin: Dashboard -> Organisationen ->
Datenbank -> Flotte -> Admin-Accounts (Flows als eigener Punkt entfernt).
- Dashboard (Kunde+Admin) und Workspace (Kunde) als Platzhalter-Routen
angelegt - Navigationspunkte von Anfang an klickbar, Inhalt folgt in
spaeteren Phasen (Dashboard: Phase 5: Workspace: Phase 3).
- "Webapps" aus der Organisation-Unternavigation entfernt (zieht in
Phase 3 in den neuen Workspace-Bereich um, vorlaeufig weiter unter
/webapps erreichbar ueber einen Link auf der Workspace-Platzhalterseite).
- Flows: frueherer eigenstaendiger Einstiegspunkt (/admin/flows,
Organisationswahl) entfernt (admin/flows_index.html geloescht) - neuer
7. Reiter "Flows" auf der Organisations-Detailseite
(admin/kunden_detail.html + admin/flows_organisation.html mit
derselben Reiter-Nav), routers/admin_flows.py-Logik unveraendert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Pico setzt "nav ul { align-items: center }" fuer den ueblichen
horizontalen Navbar-Fall - durch die erzwungene flex-direction:column
in .primary-nav wurde daraus horizontale Zentrierung jedes Eintrags
statt linksbuendig ueber die volle Spaltenbreite. align-items:stretch
behoben, macht nebenbei die ganze Zeile klickbar statt nur den Text.
Mobile Zeilen-Variante behaelt bewusst align-items:center (dort richtig).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Der GUI-Umbau vom Vortag entfernte den "Organisation"-Nav-Punkt fuer
is_superuser-Konten - "Mein Konto" lag aber ausschliesslich auf
/organisation, wodurch root sein eigenes Passwort nicht mehr erreichen
konnte (Route funktionierte weiter, war nur aus der Navigation
verschwunden - live vom Nutzer bemerkt). passwort_aendern() antwortet
jetzt rollenabhaengig (admin/accounts.html statt organisation.html fuer
is_superuser-Konten), neue "Mein Konto"-Sektion auf der Admin-Accounts-
Seite, beide Formulare posten an denselben bestehenden Endpunkt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
- Status-Spalte (Kunden-Geraeteliste) jetzt auch sortierbar.
- Admin-Flotte: Kennung jetzt aufklappbar (analog Kunden-Geraeteliste).
- Merkmale: Kategorie-Checkbox-Filter (kombiniert mit Suche, neues
filtereNachKategorie() in table-tools.js).
- Backends/Blueprints -> "Flottenmatrix" umbenannt (Label, ueberall).
- Organisation (Kunde): "Stammdaten/Konten" in zwei eigene Seiten
aufgeteilt (/organisation, /organisation/konten) - Kolleg:innen-
Verwaltung jetzt eigenstaendig statt Teil der Organisationsdaten-Seite.
- Admin "Kunden" -> "Organisationen" umbenannt (Nav-Label + Seiten),
Detail-Seite von einer langen Seite auf sechs Reiter aufgeteilt
(Organisationsdaten/Hostnamenkonvention/Aktivierungscodes/
Bereitstellungsvorlagen/Kundenkonten/Gruppen) - jeder Reiter holt nur
die Daten, die er braucht (_render_detail_tab()-Helper).
- Manueller Darkmode-Umschalter (static/theme-toggle.js) - Pico folgt
bereits automatisch der Systemeinstellung, dieser Knopf erlaubt
zusaetzlich eine manuelle, persistierte Wahl unabhaengig vom System.
- Emoji-Icons fuer die Hauptnavigation (Flotte/Organisation/
Installationsmedium/Organisationen/Datenbank/Flows/Admin-Accounts).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Backends als Spalten, Merkmale als Zeilen (_blueprint_matrix() in
admin_technik.py), Haken mit Rollenname als Tooltip statt Rollenname in
eigener Spalte. Merkmale ohne jeden Blueprint bleiben als komplett leere
Zeile sichtbar (Lücken-Anzeige). Installer-Typ (kickstart/autoinstall)
nicht mehr angezeigt (Nutzer-Wunsch, Daten unveraendert). Sortierung/
Suche via table-tools.js.
Icons je Zeile bewusst nicht Teil dieses Schritts (Nutzer-Entscheidung
10.09.2026: erst Struktur, Icon-Quelle ist ein eigener, spaeterer
Beschaffungsschritt - haben aktuell keine App-Icon-Quelle im System).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Medien / Kontingent & Gültigkeit / Erstellen als eigene Unterseiten
(analog zum Organisation-Muster), Kontext-Ermittlung in
_installationsmedium_kontext() geteilt statt dreifach dupliziert. Ohne
konfiguriertes Medium verweisen "Medien"/"Kontingent" auf den
"Erstellen"-Reiter statt leer zu wirken.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
- base.html: linke Hauptnavigation branch nach user.is_superuser - root
sieht Kunden/Datenbank/Flotte/Flows/Admin-Accounts statt der
Kunden-Self-Service-Bereiche (Flotte/Organisation/Installationsmedium,
die braucht root fuer die eigene Test-Org "Liebherr" nicht extra).
- admin/layout.html: die alte Zweitnavigation (Technik/Flotte/Flows/
Kunden) entfernt - liegt jetzt direkt in der Hauptnavigation.
- Neue Admin-Accounts-Verwaltung (routers/admin_accounts.py,
templates/admin/accounts.html): Plattform-Admin-Konten (is_superuser)
getrennt von Kundenkonten. organization_id bleibt aus Schema-Gruenden
gesetzt (Pflichtfeld), aber ohne sichtbare Bedeutung - Admin-Konten
werden nur noch herausgefiltert+separat gezeigt (Nutzer-Entscheidung),
keine Schema-Aenderung.
- fetch_benutzer_fuer_organisation(): liefert is_superuser-Konten nicht
mehr mit, dafuer neue fetch_admin_konten() (organisationsuebergreifend).
- Kunden-Detail (Admin): "Tuxflotte-Admin"-Spalte + is_superuser-Checkbox
aus dem Kundenkonto-Formular entfernt (waere seither ohnehin nie mehr
in der eigenen Liste sichtbar gewesen).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
- Container-Breite auf breiten Bildschirmen deutlich vergrößert (Pico-
Default liess ab 1536px beidseitig viel Platz ungenutzt).
- "Kolleg:innen"-Sektion auf /organisation jetzt komplett vor
Nicht-Org-Admins verborgen (vorher nur die Bearbeiten-Knöpfe).
- Admin-Technik-Nav: "Kategorien" -> "App-Kategorien", "Workspaces" ->
"Workspaces-Vorlagen" (reine Label-Umbenennung, URLs/Daten unveraendert).
- Debug-Modus-Hinweistext auf der Auftragskatalog-Seite ergaenzt.
- Struktur/Gruppen: erklaerende Hinweistexte mit Beispiel ergaenzt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Die alte Zusammensetzungs-Ansicht war ein einziges Dropdown mit ALLEN
~50 Merkmalen unsortiert durcheinander, ein Merkmal pro Formular-Absenden -
bei der inzwischen erreichten Katalog-Größe praktisch unbenutzbar. Ersetzt
durch nach Kategorie gruppierte Checkboxen (gleiches Muster wie
gruppe_detail.html/auftragskatalog.html) mit einem einzigen
Speichern-Knopf - Differenz zur bisherigen Zusammensetzung wird berechnet
und über die bestehenden Einzel-Endpunkte nachgezogen (Diff-Muster wie
geraete.py:gruppen_aktualisieren(), kein neuer anode-Endpunkt nötig).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Live beim Deploy gefunden: admin_geraete.py importierte noch
OPTIONEN_FELDER aus routers.geraete (dort entfernt) und hatte dieselben
select/deselect-Endpunkte wie geraete.py, die im vorigen Commit dort schon
entfernt wurden - Service crashte beim Start (ImportError). Gleiches
Muster wie geraete.py jetzt auch hier angewendet: Endpunkte entfernt,
eigene-gruppe-Route als admin-seitiges Gegenstück ergänzt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
- gruppen.py/admin_kunden.py: Workspace-Zuweisung/-Entfernung an
Gerätegruppen (Kundenportal + Admin-Parallelansicht), Konflikt-
Fehlermeldung bei Exklusivitätsverletzung (workspace_fehler-Query-Param,
URL-encodiert).
- gruppe_detail.html (Kundenportal + Admin): neue Workspace-Sektion mit
Auswahl-Dropdown (nur global + eigene Org, siehe neuer anode-Endpunkt)
bzw. Entfernen-Knopf.
- gruppen_liste.html: Workspace-Spalte ergänzt.
- geraete.py: per-Geräte-Auftragskatalog-Select/Deselect entfernt
(device_merkmale ist weg) - auftragskatalog.html jetzt reine
Ist-Zustand-Anzeige, neue Route "eigene-gruppe" legt bei Bedarf direkt
eine Ein-Geräte-Gruppe an und leitet dorthin weiter (ersetzt den alten
Direkt-Toggle als GUI-Einstieg).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
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
Admin: /admin/technik/webapp-templates (Anlegen/Bearbeiten/Loeschen
globaler Vorlagen).
Kunde (NEU, erster echter Workspace-Self-Service-Zugang - bisher gab es
nur OEs/Gruppen unter /struktur): /webapps zeigt je eigenem Workspace
(abgeleitet aus den eigenen Bereitstellungsvorlagen, dedupliziert -
es gibt noch keine direkte Workspace-Verwaltung) die aktiven Webapps,
noch nicht aktivierte Vorlagen zum Aktivieren, und ein Formular fuer
eigene freie Eintraege. Besitz-Validierung analog
find_oe_in_organization() (struktur.py, ADR-0011).
Beide Bereiche nutzen fuer Icon-Vorschauen denselben Streaming-Proxy
wie der bestehende ISO-Download (kundenplattform kennt anodes
oeffentliche Adresse nicht).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Liste+Anlegen+Entfernen von Webapps (Name+URL) je Workspace,
Favicon-Vorschau per Proxy-Route (/admin/technik/webapp-icons/...) -
gleiches Streaming-Proxy-Muster wie der bestehende
ISO-Download (installationsmedium_download()), da kundenplattform die
oeffentliche anode-Adresse nicht kennt (ANODE_URL hier ist die interne
127.0.0.1-Adresse).
Bewusst vorerst im Admin-Bereich angesiedelt (Tuxflotte-Personal),
nicht im Kunden-Self-Service (struktur.py) - es gibt dort noch keine
Workspace-Verwaltung, nur OEs/Gruppen. Offene Scope-Frage, dem Nutzer
separat vorgeschlagen statt unilateral entschieden: Workspaces generell
dem Kunden-Self-Service oeffnen, oder Webapps auf OE-/Gruppen-Ebene
umziehen (analog zu oe_merkmale) statt Workspace-Ebene, damit der Kunde
sie wirklich selbst pflegen kann ('der Kunde hat diverse URLs').
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VLk93dhDMrkPXefVdknMV9
Neue Route /admin/geraete/{device_id}/log fasst device_merkmal_events
(Auftragskatalog, bereits bestehend) und die neuen
device_installation_events (Migration 0025 in provisioning-server,
Boot-Medium-Meilensteine/Fehlschläge) chronologisch zusammen - eigenes
Template (templates/admin/geraete_log.html), bewusst getrennt von der
Kunden-Seite (templates/geraete_log.html bleibt unverändert), da noch
offen ist, ob/wie umfangreich Installationsereignisse dem Kunden selbst
gezeigt werden sollen.
Nutzer-Feedback (01.09.2026, echter Hardware-Test):
- "Wenn man ein Installationsmedium erstellt... wird relativ weit oben die
Erstellungsinfo sichtbar. Aber etwas unscheinbar. Vielleicht kann man
das leicht farbig hinterlegen" - neue .highlight-box-Klasse (farbiger
linker Rand + Picos eigene Sectioning-Hintergrundfarbe, Dark/Light-Mode-
sicher da nur Pico-CSS-Variablen) auf der "Aktuelles Installationsmedium"
Sektion.
- "Bitte nach ISO-Build auch die Größe anzeigen lassen" - zeigt
iso_build.size_bytes (neu von provisioning-server geliefert, siehe
dortiger Commit fb87a23) über Jinjas eingebauten filesizeformat-Filter an.
Ergaenzt die neue iso_builds.sha256-Spalte (provisioning-server) als
copy-paste-fertige sha256sum -c-Zeile in der Anleitung, plus als reiner
Wert oben beim Download-Button.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Direkte Folge des vorherigen Commits (sprechender Download-Dateiname) -
die Anleitung auf /installationsmedium zeigte weiterhin
'tuxflotte-installationsmedium.iso'. Namensbildung in _iso_dateiname()
extrahiert, von installationsmedium_ansicht() (fuer die Anzeige) UND
installationsmedium_download() (fuer den echten Header) gemeinsam
genutzt, damit beide nie auseinanderlaufen koennen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Live-Fund beim Self-Service-Flow-Test: bei mehreren Bau-Durchlaeufen/
Workspaces war der Download-Dateiname nicht unterscheidbar. Jetzt
zusammengesetzt aus Backend+Version+Workspace (aus der verwendeten
Bereitstellungsvorlage) + Bau-Datum, z.B.
'tuxflotte-Linux-Mint-22-Entwickler-2026-08-28.iso'.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Live gefunden beim echten Self-Service-Flow-Test: fetch_konfiguration()
gab wifi_ssid roh weiter, ohne den bekannten bytes-Decode-Guard, den
andere Stellen im Projekt schon haben (siehe auth.py:_row_to_user()).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Neue Sektion 'Debug-Modus' auf der Auftragskatalog-Seite (Kunden- und
Admin-Bereich, gleiches zurueck_url-Praefix-Muster wie die bestehenden
select/deselect-Formulare) mit Aktivieren/Beenden-Toggle. Geräteliste
zeigt jetzt ein kleines Debug-Badge. Hardware-Seite bekommt eine neue
Sektion 'Aktueller Zustand (letzter Checkin)' mit den Health-Metriken,
nur sichtbar wenn device.health vorhanden ist. Log-Seite zeigt jetzt
'Zuletzt gemeldet' oberhalb der Ereignistabelle - vorher nur in der
Geräteliste sichtbar, nicht in der Detail-Historie.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ergänzt um 'Zum Download verfügbar bis: ...' beim Download-Button, sobald
provisioning-server (siehe ADR-0014-Nachtrag) ein expires_at für den
Build liefert. Kein Router-Code nötig - das Feld kommt automatisch mit
der bestehenden iso_build-Antwort durch.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Neue Sektion 'Mein Konto' auf der bestehenden /organisation-Seite. Jeder
eingeloggte Nutzer darf sein eigenes Passwort ändern (kein
require_org_admin nötig, anders als bei /organisation/konten für fremde
Konten). Prüft aktuelles Passwort per user_manager.authenticate() (gleiches
Muster wie der Login), validiert das neue Passwort explizit gegen die
bestehende Policy (>=12 Zeichen, nicht gleich der E-Mail) aus der
Sicherheitshärtung vom 25.08.2026, zeigt Fehler inline auf derselben Seite
statt als nackte HTTPException.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Siehe ADR-0020 (platform-docs) für die vollständige Begründung.
- base.html: Nav-Marke 'Kundenplattform' -> 'Flotten-Management'.
Navigation von flacher horizontaler Liste auf vertikale Primär-Spalte
(Flotte/Organisation/Installationsmedium/Admin) + horizontale
Sekundär-Nav umgestellt. Struktur und Gruppen sind jetzt Unterpunkte
von Organisation. Aktive Kategorie wird aus dem URL-Pfad abgeleitet,
keine Router-Änderungen nötig, URLs unverändert.
- app.py: FastAPI-Titel ebenfalls angepasst (aktuell dormant, da /docs
deaktiviert).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sicherheits-Review 25.08.2026 vor geplanter öffentlicher Erreichbarkeit
über Pangolin (bisher nur Pangolin-interne Auth vorgeschaltet):
- auth.py: In-Memory-Rate-Limiting auf /login (je Email UND je Quell-IP,
8 Fehlversuche / 10 Minuten), UserManager.validate_password() setzt
jetzt eine Mindestlänge von 12 Zeichen durch (fastapi_users' Default
war ein No-Op, ließ auch leere Passwörter zu).
- routers/admin_kunden.py, routers/organisation.py: fangen
InvalidPasswordException/UserAlreadyExists jetzt sauber ab statt 500.
- app.py: /docs, /redoc, /openapi.json deaktiviert (legten zuvor die
komplette Routenstruktur inkl. /admin/* offen), Security-Header-
Middleware (X-Content-Type-Options, X-Frame-Options, Referrer-Policy,
CSP).
- scripts/reset_password.py: neues Skript, Gegenstück zu create_user.py,
für die im selben Review gefundenen aktiven Testaccounts mit dem
Passwort 'test123' (einer davon Superuser) gebraucht.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Live bei der Verifikation gefunden: konto_loeschen() lud das Zielkonto
über get_by_email(ziel['email']) nach - der email-Wert kam aus
fetch_benutzer_fuer_organisation()'s sync-Verbindungspfad, wo psycopg
TEXT-Spalten teils als bytes statt str liefert (dasselbe bereits bekannte,
nicht deterministische Verhalten wie in _row_to_user(), siehe dortiger
Kommentar) - die async get_by_email()-Abfrage bekam dadurch einen
bytes-Parameter und scheiterte mit 'operator does not exist: text =
bytea'.
Zwei Fixes: konto_loeschen() nutzt jetzt user_manager.get(id) statt des
E-Mail-Umwegs (nur async-Pfad, umgeht das Problem strukturell).
fetch_benutzer_fuer_organisation() bekommt zusätzlich denselben
defensiven bytes-Decode wie _row_to_user(), damit das nicht an anderer
Stelle erneut zuschlägt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Neue Rolle ist_organisationsadmin (auth.py), bewusst getrennt von
is_superuser: is_superuser bleibt exklusiv internes, organisationsuebergreifendes
Tuxflotte-Personal mit Zugriff auf /admin/* - ist_organisationsadmin ist
auf die eigene Organisation beschränkt und kann nie über Self-Service
gesetzt werden (organization_id/is_superuser kommen bei
POST /organisation/konten nie aus dem Formular, sondern immer aus der
Session).
/organisation zeigt jetzt Kontakt-/Stammdaten (bearbeitbar) und die
Kolleg:innen-Liste der eigenen Organisation (für alle sichtbar,
Verwaltung nur für Org-Admins/internes Personal via require_org_admin).
Selbstlöschung des eigenen Kontos wird verweigert.
fetch_benutzer_fuer_organisation() von admin_kunden.py nach auth.py
verschoben (jetzt von beiden Routern importiert, kein Duplikat) -
liefert zusätzlich id + ist_organisationsadmin. admin_kunden.py bekommt
eine neue Organisationsdaten-Sektion (nutzt denselben PATCH-Endpoint)
und die neue Checkbox beim Konto-Anlegen, um den ersten Org-Admin einer
neuen Organisation zu bootstrappen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
pics/tuxflotte-beschriftet-schatten.svg (das laut platform-docs bereits
als 'das Logo' referenzierte Markenbild, siehe
13-live-provisioning-boot.md) als static/tuxflotte-logo.svg eingebunden,
im Header neben dem Kundenplattform-Link (wirkt dadurch auch auf der
Login-Seite, die base.html erbt). Passend dazu buchstabiert das Logo
selbst bereits 'Tux'/'Flotte' als Wortmarke.
Nav-Link '/geraete' zeigt jetzt 'Flotte' statt 'Geräte' - sowohl im
Kundenportal-Header (base.html) als auch im Admin-Sidebar
(admin/layout.html), für konsistente Beschriftung über die ganze
Weboberfläche. Reine Anzeigetext-Änderung, URLs/Routen unverändert.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Gegenstück zur bestehenden Verwaltung von der Gruppen-Seite aus
(/gruppen/{id}) - jetzt auch umgekehrt vom Gerät aus möglich, ohne
anode-Änderung (fetch_gruppen() aus routers/gruppen.py wiederverwendet,
Diff-Berechnung gegen bestehende add/remove-Endpunkte).
geraete_liste(): neue Spalte 'Gruppen' (aktuelle Mitgliedschaft),
Aktionen-Dropdown bekommt eine Checkbox-Liste aller Organisations-
Gruppen; POST /geraete/{id}/gruppen berechnet die Differenz zur
gewünschten Auswahl und ruft die bestehenden Endpunkte entsprechend auf.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Bugfix: auftragskatalog.html hatte Select/Deselect-Formular-Ziele fest
auf /geraete/... verdrahtet statt zurueck_url zu nutzen - Admin-Ansicht
eines fremden-Org-Geraets scheiterte dadurch mit 404. Jetzt dynamisch.
- Herkunfts-Anzeige ('via OE'/'via Gruppe') ist jetzt anklickbar, sofern
ein Sprungziel existiert (neue anreichere_mit_quelle_url()-Hilfsfunktion
in routers/geraete.py, von geraete.py und admin_geraete.py genutzt).
- Admin-Geräteliste (/admin/geraete) bekommt OE-Spalte + 'In OE
verschieben', analog zur Kundenportal-Liste.
- Neue, bewusst schlanke Admin-OE-Ansicht
(/admin/kunden/{org}/organisationseinheiten/{oe}/merkmale) - nur der
Merkmale-Editor als Sprungziel, kein OE-CRUD (bleibt laut ADR-0012
Kunden-Self-Service unter /struktur).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Kund:innen können jetzt eigene Gruppen selbst verwalten (/gruppen) statt
nur intern über /admin/kunden - reiner Kundenplattform-Zusatz, keine
Backend-Änderung nötig (anode-API war bereits organisationsgebunden und
generisch aufgebaut, siehe ADR-0012). Admin-Zugang bleibt zusätzlich
bestehen (Support/Onboarding), beide teilen sich dieselbe anode-API.
Neuer Router routers/gruppen.py (eng an struktur.py angelehnt, gleiches
Besitz-Validierungsmuster), Templates gruppen_liste/gruppe_detail
(Merkmale-Tristate-Editor + Geräte-Mitgliedschaft, wie
admin/gruppe_detail.html)/gruppe_umbenennen/gruppe_loeschen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Kundenportal:
- Neue Seite /struktur - OE-Baum (rekursives Jinja-Makro), Anlegen/
Umbenennen/Löschen (Standard-OE 'Neue Geräte' geschützt), Merkmale-
Editor pro OE mit Tristate aktiv/inaktiv/nicht festgelegt.
- geraete_liste.html: neue Spalte 'OE', Aktionen-Dropdown um 'In OE
verschieben' erweitert.
- auftragskatalog.html: zeigt jetzt die Herkunft einer Einstellung an
(OE/Gruppe/manuell), sofern sie nicht vom Workspace-Default kommt.
Admin (/admin/kunden/{id}):
- Neue Sektion 'Gruppen' - Anlegen/Löschen, verlinkt auf neue
Detailseite (admin/gruppe_detail.html) mit Merkmale-Editor (wie OE)
und Geräte-Mitgliedschaft-Checkliste. Bewusst nur intern verwaltbar
in v1 (siehe Konfigurationsgruppen-Diskussion).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
_row_to_user() dekodierte bislang nur hashed_password von bytes zu str,
obwohl psycopg dieselbe Eigenart (TEXT-Spalte kommt teils als bytes
zurück) auch bei email zeigt. Sichtbar z.B. im Header als
b'name@domain.tld' statt name@domain.tld.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Hardware-Informationen, Installations-/Aktions-Log, De-/Reprovisionieren,
Gerät löschen (Soft Delete, mit Bestätigungsseite) - via Pico-Dropdown,
kein zusätzliches JS. Spricht die neuen anode-Endpunkte aus
provisioning-server an.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Audit ergab: der Dienst laeuft korrekt als unprivilegierter User (nicht
root, kein sudo, nologin-Shell, bestaetigt per ps/id auf anode), aber
systemd-analyze security kam trotz der vier bestehenden Basis-Direktiven
(NoNewPrivileges/PrivateTmp/ProtectSystem=strict/ProtectHome) nur auf
8.3/10 "EXPOSED" - der volle Capability-Bounding-Set, kein
Syscall-Filter, keine Namespace-/Kernel-/Proc-Beschraenkungen.
Ergaenzt: CapabilityBoundingSet= (leer - der Dienst braucht keine
einzige Linux-Capability), SystemCallFilter=@system-service,
RestrictAddressFamilies (nur INET/INET6/UNIX), RestrictNamespaces,
RestrictSUIDSGID, LockPersonality, MemoryDenyWriteExecute,
ProtectKernel{Modules,Tunables,Logs}, ProtectControlGroups,
ProtectClock, ProtectHostname, ProtectProc=invisible, ProcSubset=pid,
PrivateDevices, RemoveIPC, RestrictRealtime, UMask=0077.
Vor dem Scharfschalten per systemd-run mit exakt dieser
Direktivenkombination gegen den echten Code getestet (nicht nur gegen
eine leere Testanwendung): Login/JWT-Signierung, Postgres-Lese-/
Schreibzugriff via psycopg, ausgehender HTTP-Aufruf via httpx an
provisioning-server, Fernet-Verschluesselung des WLAN-PSK - alles ohne
Fehler unter der vollen Sandbox.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
Zwei neue Menuepunkte fuer eingeloggte Kunden (nicht der
organisationsuebergreifende /admin/-Bereich):
- "Organisation": Name bearbeiten (ruft Phase-3-PATCH auf
provisioning-server auf).
- "Installationsmedium": WLAN-SSID/-Passwort, Vorlagenwahl, Kontingent
und Gueltigkeitsdauer speichern, personalisierte Kunden-ISO per
Klick erstellen (Phase-4-Endpoints), Fortschritt/Status-Anzeige
(Meta-Refresh waehrend pending/running), Download + dd/Rufus-
Anleitung. Getrennte "aufladen"-Aktion erhoeht Kontingent/
Gueltigkeit der bestehenden Enrollment Session (neuer PATCH-
Endpoint), ohne eine neue ISO zu erzeugen - die bereits ausgeteilte
bleibt gueltig.
Neue Tabelle installationsmedium_konfiguration (Migration 0004, eigene
DB, kein Foreign Key auf provisioning-server-Ressourcen, siehe
ADR-0011) haelt SSID/verschluesselten PSK/Vorlage/Kontingent-Defaults
sowie die IDs der aktuell gueltigen Enrollment Session und des
zugehoerigen ISO-Baus.
WLAN-PSK wird Fernet-verschluesselt gespeichert (encryption.py, neuer
KUNDENPLATTFORM_ENCRYPTION_KEY) - echte Zugangsdaten, kein
Passwort-Hash-Fall. Download laeuft ueber einen Streaming-Proxy
(anode_stream() in anode_client.py) statt eines direkten Links, weil
der Browser kein KUNDENPLATTFORM_TOKEN besitzt.
Lokal end-to-end verifiziert (Wegwerf-Postgres fuer beide Services,
echter build_customer_iso.sh-Lauf ueber die Kundenplattform-UI):
Formular -> Enrollment Session + ISO-Bau -> Status-Polling ->
Download-Proxy liefert byte-identische, korrekt personalisierte Datei
(SSID/PSK/Aktivierungscode per xorriso-Extraktion gegengeprueft) ->
Aufladen erhoeht Kontingent/Gueltigkeit der bestehenden Session ohne
neuen Bau -> "neues Installationsmedium erstellen" erzeugt neue
Session + ISO, alte Session bleibt unangetastet nutzbar (alter
ISO-Bau wird laut Phase-4-Cleanup automatisch entfernt), leeres
PSK-Feld uebernimmt das zuvor gespeicherte PSK korrekt.
Organisation-Name-Aenderung ueber PATCH verifiziert. Unauthentifizierter
Zugriff auf beide neuen Routen leitet korrekt zu /login um.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Ersetzt die handgestrickte bcrypt+Session-Auth durch fastapi-users
(CookieTransport+JWTStrategy statt Starlette-Session, custom
psycopg3-async-Adapter statt SQLAlchemy). Bestehende bcrypt-Hashes
bleiben gültig und werden beim nächsten Login automatisch auf Argon2
angehoben (pwdlib-Default-PasswordHelper). ist_admin wird zu
is_superuser, damit fastapi-users' eingebaute Semantik direkt nutzbar
ist - require_admin/get_current_user bleiben als Wrapper unter
gleichem Namen, alle Router-Dateien bis auf geraete.py (dict- zu
attribut-Zugriff auf user.organization_id) unverändert.
Lokal end-to-end gegen eine Wegwerf-Postgres-Instanz (podman) verifiziert:
Migration, Bestandskonto-Login mit altem Passwort samt Argon2-Upgrade,
Admin-Kontoanlage über beide Aufrufstellen (create_user.py und
admin_kunden.py), 403 für Nicht-Admins, Logout.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
routers/admin_flows.py: pro Organisation unbestätigte Geräte anzeigen
und bestätigen (Bereitstellungsvorlage zuweisen), Enrollment Sessions
anlegen/widerrufen. Confirm-Aktion ruft direkt anodes bestehenden
resolve-Endpoint auf.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
routers/admin_technik.py: Merkmale (anlegen/bearbeiten inkl. Kategorie
und Katalogfähigkeit), Kategorien (anlegen), Workspaces (anlegen +
Zusammensetzung aus Merkmalen - Grundbaustein des Workspace-Creators),
Backends/Blueprints nur zur Anzeige.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
routers/admin_geraete.py mit echter Funktionalität: alle Geräte aller
Organisationen auflisten, pro Gerät der Auftragskatalog (Wiederverwendung
von auftragskatalog.html und der Logik aus routers/geraete.py, nur ohne
Org-Besitz-Check - Admins dürfen jedes Gerät direkt per ID öffnen).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
routers/admin_kunden.py mit echter Funktionalität: Organisationen
auflisten/anlegen, pro Organisation Aktivierungscodes erzeugen und
Kundenkonten anlegen (direkt in der Kundenplattform-eigenen
benutzer-Tabelle, kein anode-Aufruf nötig). Löst das bisherige manuelle
create_user.py-Skript für den Alltagsgebrauch ab.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
app.py auf APIRouter-Module umgestellt (db.py, templating.py,
anode_client.py, auth.py, routers/geraete.py + vier Admin-Router als
Platzhalter). benutzer.ist_admin (Migration 0002) + require_admin-
Dependency (403 ohne Admin-Flag). Admin-Nav-Link im Header nur für
Admin-Konten sichtbar. create_user.py um --admin-Flag erweitert.
Die vier Admin-Bereiche (Technik/Datenbank, Geräte, Flows, Kunden) sind
als Platzhalterseiten unter /admin/... verdrahtet und bekommen in den
nächsten Schritten echte Funktionalität.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Klassenloses CSS-Framework (self-hosted unter static/, kein CDN), passt
zum bestehenden Ansatz ohne JS-Build-Schritt. Bringt automatisches
Dark/Light-Mode (prefers-color-scheme) und Responsivität ohne
Zusatzaufwand. Templates auf Picos semantische Konventionen umgestellt
(label/input-Paare, article für die Login-Karte, figure um Tabellen für
horizontales Scrollen). Eigene CSS-Regeln auf die zwei Farb-Utility-
Klassen (state-present/state-absent) reduziert, Rest kommt von Pico.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
psycopg liefert die TEXT-Spalte password_hash beim SELECT als bytes
zurück, nicht als str wie beim create_user.py-Insert - bcrypt.checkpw()
schlug dadurch bei jedem Login-Versuch mit AttributeError fehl. Gefunden
beim ersten E2E-Login-Test auf anode.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Neuer, von provisioning-server (anode) getrennter Dienst, siehe ADR-0011:
FastAPI + Jinja2, eigene Postgres-DB (benutzer-Tabelle), eigenes
Service-Token (TUXFLOTTE_KUNDENPLATTFORM_TOKEN) für die Kommunikation mit
anode. Org-Zugehörigkeits-Prüfung passiert hier in der Anwendung vor jedem
Aufruf gegen anode.
Umfang v1: Login, Geräteliste, Auftragskatalog geräteweise aus-/abwählen
(inkl. der standardbrowser-Option für Brave). systemd-Unit läuft unter
eigenem unprivilegiertem System-User statt root.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>