Betreiber können den zentralen Werkzeugkatalog jetzt über die UI
pflegen (GET /betreiber/werkzeuge, GET/POST .../neu, GET/POST
.../{id}, POST .../{id}/loeschen) statt nur per SQL. Bearbeitet
ausschließlich zentrale (account_id IS NULL) Einträge — ein
mandantenspezifischer Katalogeintrag bleibt über diese Seiten
unerreichbar (404), das ist weiterhin Sache des jeweiligen Mandanten.
Ohne befüllten Katalog konnte bisher keine Bewertung tatsächlich zu
"genehmigt" mit einem echten Werkzeug führen.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
147 lines
4.5 KiB
Go
147 lines
4.5 KiB
Go
package web
|
|
|
|
import (
|
|
"net/http"
|
|
)
|
|
|
|
type betreiberDashboardData struct {
|
|
Title string
|
|
Nav navData
|
|
AccountCount int
|
|
WerkzeugCount int
|
|
RecentAuditCount int
|
|
}
|
|
|
|
// handleBetreiberDashboard zeigt eine kurze Übersicht als Einstieg in
|
|
// den Plattform-Bereich (Ebene 5 — nur Betreiber, technisch getrennt
|
|
// vom Mandantenbereich).
|
|
func (s *Server) handleBetreiberDashboard(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
accounts, err := s.store.ListAccounts(ctx)
|
|
if err != nil {
|
|
http.Error(w, "Accounts konnten nicht geladen werden: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
werkzeuge, err := s.store.ListZentraleWerkzeuge(ctx)
|
|
if err != nil {
|
|
http.Error(w, "Werkzeugkatalog konnte nicht geladen werden: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
auditLog, err := s.store.ListAuditLog(ctx, 5)
|
|
if err != nil {
|
|
http.Error(w, "Audit-Log konnte nicht geladen werden: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
data := betreiberDashboardData{
|
|
Title: "Plattform", Nav: navFor(r), AccountCount: len(accounts),
|
|
WerkzeugCount: len(werkzeuge), RecentAuditCount: len(auditLog),
|
|
}
|
|
if err := s.templates.ExecuteTemplate(w, "betreiber-dashboard", data); err != nil {
|
|
http.Error(w, "Seite konnte nicht gerendert werden", http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
type betreiberAccountListItem struct {
|
|
ID string
|
|
Name string
|
|
CreatedAt string
|
|
}
|
|
|
|
type betreiberAccountListData struct {
|
|
Title string
|
|
Nav navData
|
|
Accounts []betreiberAccountListItem
|
|
}
|
|
|
|
// handleBetreiberAccountList listet alle Mandanten der Plattform.
|
|
func (s *Server) handleBetreiberAccountList(w http.ResponseWriter, r *http.Request) {
|
|
accounts, err := s.store.ListAccounts(r.Context())
|
|
if err != nil {
|
|
http.Error(w, "Accounts konnten nicht geladen werden: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
data := betreiberAccountListData{Title: "Accounts", Nav: navFor(r)}
|
|
for _, a := range accounts {
|
|
data.Accounts = append(data.Accounts, betreiberAccountListItem{
|
|
ID: a.ID, Name: a.Name, CreatedAt: a.CreatedAt.Format("02.01.2006 15:04"),
|
|
})
|
|
}
|
|
if err := s.templates.ExecuteTemplate(w, "betreiber-accounts", data); err != nil {
|
|
http.Error(w, "Seite konnte nicht gerendert werden", http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
type betreiberUserView struct {
|
|
Email string
|
|
Role string
|
|
}
|
|
|
|
type betreiberAccountDetailData struct {
|
|
Title string
|
|
Nav navData
|
|
AccountID string
|
|
Name string
|
|
Users []betreiberUserView
|
|
CreatedAt string
|
|
}
|
|
|
|
// handleBetreiberAccountDetail zeigt einen Mandanten mit seinen Logins.
|
|
func (s *Server) handleBetreiberAccountDetail(w http.ResponseWriter, r *http.Request) {
|
|
ctx := r.Context()
|
|
acc, err := s.store.GetAccount(ctx, r.PathValue("id"))
|
|
if err != nil {
|
|
http.Error(w, "Account nicht gefunden", http.StatusNotFound)
|
|
return
|
|
}
|
|
users, err := s.store.ListUsersForAccount(ctx, acc.ID)
|
|
if err != nil {
|
|
http.Error(w, "Nutzer konnten nicht geladen werden: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
data := betreiberAccountDetailData{
|
|
Title: "Account", Nav: navFor(r), AccountID: acc.ID, Name: acc.Name,
|
|
CreatedAt: acc.CreatedAt.Format("02.01.2006 15:04"),
|
|
}
|
|
for _, u := range users {
|
|
data.Users = append(data.Users, betreiberUserView{Email: u.Email, Role: u.Role})
|
|
}
|
|
if err := s.templates.ExecuteTemplate(w, "betreiber-account-detail", data); err != nil {
|
|
http.Error(w, "Seite konnte nicht gerendert werden", http.StatusInternalServerError)
|
|
}
|
|
}
|
|
|
|
type betreiberAuditEntryView struct {
|
|
CreatedAt string
|
|
Action string
|
|
TargetType string
|
|
TargetID string
|
|
Details string
|
|
}
|
|
|
|
type betreiberAuditLogData struct {
|
|
Title string
|
|
Nav navData
|
|
Entries []betreiberAuditEntryView
|
|
}
|
|
|
|
// handleBetreiberAuditLog zeigt das plattformweite Audit-Log.
|
|
func (s *Server) handleBetreiberAuditLog(w http.ResponseWriter, r *http.Request) {
|
|
entries, err := s.store.ListAuditLog(r.Context(), 200)
|
|
if err != nil {
|
|
http.Error(w, "Audit-Log konnte nicht geladen werden: "+err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
data := betreiberAuditLogData{Title: "Audit-Log", Nav: navFor(r)}
|
|
for _, e := range entries {
|
|
data.Entries = append(data.Entries, betreiberAuditEntryView{
|
|
CreatedAt: e.CreatedAt.Format("02.01.2006 15:04:05"), Action: e.Action,
|
|
TargetType: e.TargetType, TargetID: e.TargetID, Details: e.Details,
|
|
})
|
|
}
|
|
if err := s.templates.ExecuteTemplate(w, "betreiber-audit-log", data); err != nil {
|
|
http.Error(w, "Seite konnte nicht gerendert werden", http.StatusInternalServerError)
|
|
}
|
|
}
|