feat(PROJ-69): Admin-Dashboard Tab-Gruppierung (2-Ebenen-Navigation)

17 flache Tabs unter /admin durch Gruppen-Leiste (Übersicht, Benutzer &
Zugriff, Compliance, Mail-Eingang, System) ersetzt. Rollenbasierte
Sichtbarkeit pro Tab unverändert, nur Navigation strukturiert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-07-06 12:08:53 +02:00
co-authored by Claude Sonnet 5
parent 8bd02facf5
commit 3b2a8388c1
3 changed files with 202 additions and 25 deletions
+98
View File
@@ -8992,3 +8992,101 @@ Keine Commits in dieser Session.
- features/PROJ-66-backup-strategie.md | 75 ++++++++++++++++++++++++++++++++++++++++++++-
---
## 2026-07-06 11:47 11:47 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** archivmail
### Commits
- 8bd02fa docs(PROJ-67,PROJ-68): Deployment-Status auf Deployed setzen
### Geänderte Dateien
- DEVLOG.md | 829 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- features/INDEX.md | 2 +-
- features/PROJ-67-manticore-upgrade-25-zu-27.md | 157 +++++++++++++++-
- features/PROJ-68-sudo-provisionierung-dienststeuerung.md | 111 ++++++++++-
---
## 2026-07-06 11:47 11:47 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** archivmail
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- DEVLOG.md | 829 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- features/INDEX.md | 2 +-
- features/PROJ-67-manticore-upgrade-25-zu-27.md | 157 +++++++++++++++-
- features/PROJ-68-sudo-provisionierung-dienststeuerung.md | 111 ++++++++++-
---
## 2026-07-06 11:48 11:48 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** archivmail
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- DEVLOG.md | 829 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- features/INDEX.md | 2 +-
- features/PROJ-67-manticore-upgrade-25-zu-27.md | 157 +++++++++++++++-
- features/PROJ-68-sudo-provisionierung-dienststeuerung.md | 111 ++++++++++-
---
## 2026-07-06 11:49 11:49 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** archivmail
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- DEVLOG.md | 829 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- features/INDEX.md | 2 +-
- features/PROJ-67-manticore-upgrade-25-zu-27.md | 157 +++++++++++++++-
- features/PROJ-68-sudo-provisionierung-dienststeuerung.md | 111 ++++++++++-
---
## 2026-07-06 11:50 11:51 (0m)
**Beschreibung:** Claude Code Session
**Projekt:** archivmail
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- DEVLOG.md | 829 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- features/INDEX.md | 2 +-
- features/PROJ-67-manticore-upgrade-25-zu-27.md | 157 +++++++++++++++-
- features/PROJ-68-sudo-provisionierung-dienststeuerung.md | 111 ++++++++++-
---
## 2026-07-06 11:52 12:03 (10m)
**Beschreibung:** Claude Code Session
**Projekt:** archivmail
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- DEVLOG.md | 829 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- features/INDEX.md | 2 +-
- features/PROJ-67-manticore-upgrade-25-zu-27.md | 157 +++++++++++++++-
- features/PROJ-68-sudo-provisionierung-dienststeuerung.md | 111 ++++++++++-
---
## 2026-07-06 12:04 12:08 (4m)
**Beschreibung:** Claude Code Session
**Projekt:** archivmail
### Commits
Keine Commits in dieser Session.
### Geänderte Dateien
- DEVLOG.md | 829 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
- features/INDEX.md | 2 +-
- features/PROJ-67-manticore-upgrade-25-zu-27.md | 157 +++++++++++++++-
- features/PROJ-68-sudo-provisionierung-dienststeuerung.md | 111 ++++++++++-
---
+2 -1
View File
@@ -84,7 +84,8 @@
| PROJ-66 | Backup-Strategie für Store, Keyfile, PostgreSQL (Produktiv + Teilproduktiv) | Deployed | [PROJ-66](PROJ-66-backup-strategie.md) | 2026-07-04 |
| PROJ-67 | Manticore Search Upgrade 25.0.0 → 27.1.5 + Auto-Upgrade-Pfad | Deployed | [PROJ-67](PROJ-67-manticore-upgrade-25-zu-27.md) | 2026-07-05 |
| PROJ-68 | sudo-Provisionierung für Admin-Dienststeuerung fehlte komplett | Deployed | [PROJ-68](PROJ-68-sudo-provisionierung-dienststeuerung.md) | 2026-07-05 |
| PROJ-69 | Admin-Dashboard Tab-Gruppierung (2-Ebenen-Navigation) | Planned | [PROJ-69](PROJ-69-admin-tabs-gruppierung.md) | 2026-07-06 |
<!-- Add features above this line -->
## Next Available ID: PROJ-69
## Next Available ID: PROJ-70
+102 -24
View File
@@ -1,6 +1,6 @@
"use client";
import { useEffect } from "react";
import { useEffect, useMemo, useState } from "react";
import { useAuth } from "@/hooks/useAuth";
import { useLDAPConfig } from "@/hooks/useLDAPConfig";
import { useTenantLDAPConfig } from "@/hooks/useTenantLDAPConfig";
@@ -135,6 +135,84 @@ export default function AdminPage() {
loadServices();
}, [user, loadUsers, loadAudit, loadServices]);
const tabGroups = useMemo(
() => [
{
id: "overview",
label: "Übersicht",
tabs: [
{ value: "dashboard", label: "Dashboard", visible: true },
{ value: "services", label: "Dienste", visible: isSuperAdmin },
],
},
{
id: "users",
label: "Benutzer & Zugriff",
tabs: [
{ value: "users", label: "Benutzer", visible: true },
{ value: "ldap", label: "LDAP (Global)", visible: isSuperAdmin, onClick: loadLDAP },
{
value: "tenant-ldap",
label: "LDAP",
visible: !isSuperAdmin && user?.role === "domain_admin",
onClick: loadTenantLDAP,
},
{ value: "tenants", label: "Mandanten", visible: isSuperAdmin, onClick: tenantsState.loadTenants },
],
},
{
id: "compliance",
label: "Compliance",
tabs: [
{ value: "audit", label: "Audit-Log", visible: true },
{ value: "dsgvo", label: "DSGVO-Anfragen", visible: canViewDSGVO },
{ value: "retention", label: "Retention", visible: isSuperAdmin },
{ value: "archiving-rules", label: "Regeln", visible: isSuperAdmin },
],
},
{
id: "mail",
label: "Mail-Eingang",
tabs: [
{ value: "import", label: "Import", visible: true },
{ value: "imap-settings", label: "IMAP", visible: !isSuperAdmin },
{ value: "routing-rules", label: "Routing-Regeln", visible: true },
{ value: "smtp-out", label: "SMTP-Out", visible: isSuperAdmin },
{ value: "quotas", label: "Quotas", visible: isSuperAdmin },
],
},
{
id: "system",
label: "System",
tabs: [
{ value: "security", label: "Security", visible: isSuperAdmin },
{ value: "cert", label: "Zertifikat", visible: isSuperAdmin, onClick: cert.loadCert },
{ value: "modules", label: "Module", visible: isSuperAdmin },
],
},
],
[isSuperAdmin, canViewDSGVO, user?.role, loadLDAP, loadTenantLDAP, tenantsState.loadTenants, cert.loadCert]
);
const visibleGroups = tabGroups
.map((g) => ({ ...g, tabs: g.tabs.filter((t) => t.visible) }))
.filter((g) => g.tabs.length > 0);
const [activeGroupId, setActiveGroupId] = useState(visibleGroups[0]?.id ?? "overview");
const [activeTab, setActiveTab] = useState(visibleGroups[0]?.tabs[0]?.value ?? "dashboard");
const activeGroup = visibleGroups.find((g) => g.id === activeGroupId) ?? visibleGroups[0];
function handleGroupChange(groupId: string) {
setActiveGroupId(groupId);
const group = visibleGroups.find((g) => g.id === groupId);
const firstTab = group?.tabs[0];
if (firstTab) {
setActiveTab(firstTab.value);
firstTab.onClick?.();
}
}
return (
<div className="min-h-screen">
<Navbar username={user?.username ?? ""} role={user?.role ?? ""} />
@@ -148,30 +226,30 @@ export default function AdminPage() {
) : (<>
<h1 className="mb-6 text-2xl font-bold">Administration</h1>
<Tabs defaultValue="dashboard">
<div className="mb-4 flex flex-wrap gap-1 border-b pb-2">
{visibleGroups.map((g) => (
<button
key={g.id}
type="button"
onClick={() => handleGroupChange(g.id)}
className={`rounded-md px-3 py-1.5 text-sm font-medium transition-colors ${
g.id === activeGroup?.id
? "bg-primary text-primary-foreground"
: "text-muted-foreground hover:bg-muted hover:text-foreground"
}`}
>
{g.label}
</button>
))}
</div>
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList>
<TabsTrigger value="dashboard">Dashboard</TabsTrigger>
{isSuperAdmin && <TabsTrigger value="services">Dienste</TabsTrigger>}
<TabsTrigger value="users">Benutzer</TabsTrigger>
<TabsTrigger value="audit">Audit-Log</TabsTrigger>
{canViewDSGVO && <TabsTrigger value="dsgvo">DSGVO-Anfragen</TabsTrigger>}
<TabsTrigger value="import">Import</TabsTrigger>
{isSuperAdmin && <TabsTrigger value="ldap" onClick={loadLDAP}>LDAP (Global)</TabsTrigger>}
{!isSuperAdmin && user?.role === "domain_admin" && (
<TabsTrigger value="tenant-ldap" onClick={loadTenantLDAP}>LDAP</TabsTrigger>
)}
{!isSuperAdmin && user?.role === "domain_admin" && (
<TabsTrigger value="imap-settings">IMAP</TabsTrigger>
)}
{isSuperAdmin && <TabsTrigger value="security">Security</TabsTrigger>}
{isSuperAdmin && <TabsTrigger value="cert" onClick={cert.loadCert}>Zertifikat</TabsTrigger>}
{isSuperAdmin && <TabsTrigger value="tenants" onClick={tenantsState.loadTenants}>Mandanten</TabsTrigger>}
{isSuperAdmin && <TabsTrigger value="retention">Retention</TabsTrigger>}
{isSuperAdmin && <TabsTrigger value="archiving-rules">Regeln</TabsTrigger>}
<TabsTrigger value="routing-rules">Routing-Regeln</TabsTrigger>
{isSuperAdmin && <TabsTrigger value="quotas">Quotas</TabsTrigger>}
{isSuperAdmin && <TabsTrigger value="smtp-out">SMTP-Out</TabsTrigger>}
{isSuperAdmin && <TabsTrigger value="modules">Module</TabsTrigger>}
{activeGroup?.tabs.map((t) => (
<TabsTrigger key={t.value} value={t.value} onClick={t.onClick}>
{t.label}
</TabsTrigger>
))}
</TabsList>
<TabsContent value="dashboard" className="mt-4 space-y-6">