feat: session-cookie login/logout + three-tier roles (superadmin/admin/viewer) with read-only UI + remove pink theme

- cookie-based auth (/login /logout) replacing Basic Auth for UI, enabling logout
- roles: superadmin (account management only), admin (full except accounts), viewer/audit (read-only status+cluster, export logs)
- readonly accounts hide edit buttons (added remote node, group ops, canvas layout/save/import, cluster manage, install) instead of greying them
- auditors see status+cluster only; ordinary admins lose the accounts nav; last-admin guard covers superadmin
- remove pink theme entirely (switcher, [data-theme=pink], leftover localStorage), keep white/blue
This commit is contained in:
2026-08-20 09:08:07 +08:00
parent b39bd427fa
commit f29ec81e4a
27 changed files with 1195 additions and 724 deletions

View File

@ -93,8 +93,8 @@ func (h *Handler) handleUserByName(w http.ResponseWriter, r *http.Request) {
if req.Enabled != nil {
enabled = *req.Enabled
}
// Guard: never disable/demote the last admin.
if u.Role == "admin" && (role != "admin" || !enabled) {
// Guard: never disable/demote the last admin or superadmin.
if (u.Role == "admin" || u.Role == "superadmin") && (role != u.Role || !enabled) {
n, _ := h.Store.CountAdmins()
if n <= 1 {
http.Error(w, "cannot demote or disable the last admin", http.StatusConflict)
@ -112,7 +112,7 @@ func (h *Handler) handleUserByName(w http.ResponseWriter, r *http.Request) {
http.Error(w, "system user is managed by -user/-password flags", http.StatusConflict)
return
}
if u.Role == "admin" {
if u.Role == "admin" || u.Role == "superadmin" {
n, _ := h.Store.CountAdmins()
if n <= 1 {
http.Error(w, "cannot delete the last admin", http.StatusConflict)