Admin-Konsole & Mandantenverwaltung (Phase 1)
Plattform-Admin-Konsole (Superadmin) + Kunden-Einstellungsbereich: - Datenmodell: TenantSettings (Quelle der ISMS-Template-Variablen), TenantModule (Feature-Toggles je Mandant), Tenant += short/sector, TenantStatus += ARCHIVED, AuditLog += scope (tenant|platform); RLS für die neuen Tenant-Tabellen. - Wiederverwendbare Provisionierung (server/provision.ts): Rollen+Permissions, erster Admin-User, Einstellungen, alle Module, optional Richtlinienpaket-Seed + TISAX-Default; idempotent + Audit (§3.7). syncPolicyVariablesFromSettings mappt Stammdaten → ISMS-Variablen (ORG_NAME, ISMS_SCOPE, ROLE_* …). - Admin-Konsole /admin (Guard isPlatformAdmin): Mandantenliste, „Neuer Kunde" (anlegen + provisionieren), Detailseite mit Modul-Toggles, Lebenszyklus (aktiv/gesperrt/archiviert), Nutzerliste. Aktionen im Plattform-Audit-Log. - Kunden-Einstellungen /settings (Guard tenant:manage): Unternehmensdaten, verantwortliche Rollen, Branding/Regionales, TISAX-Level — Stammdaten speisen die ISMS-Variablen (eine Pflegestelle). Modul-Übersicht. - Modul-Gating der Navigation (deaktivierte Module ausgeblendet); Login bereits für gesperrte/archivierte Mandanten blockiert (auth.authorize). - Seed: Demo-Mandant mit Einstellungen, 12 aktiven Modulen; admin@demo.example = Superadmin. Verifiziert: tsc/lint/build grün; Seed rollt Einstellungen/Module/Superadmin aus. (Browser-Verifikation diese Sitzung nicht möglich — Preview-Tools getrennt.) Offen (Phase 2): separater Superadmin-Store + eigener Login + MFA-Pflicht; Impersonation (zeitlich begrenzt, protokolliert); Plan/Limits; Logo-Upload/ Objektspeicher; SMTP/Benachrichtigungen; per-Route-Modul-Enforcement (serverseitig); Datenexport/Löschung/Retention (DSGVO); SSO. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -17,8 +17,13 @@ import {
|
||||
Truck,
|
||||
LineChart,
|
||||
Search,
|
||||
Settings,
|
||||
ShieldCheck,
|
||||
} from "lucide-react";
|
||||
import { auth, signOut } from "@/server/auth";
|
||||
import { dbForTenant } from "@/server/db";
|
||||
import { hasPermission } from "@/server/rbac";
|
||||
import { HREF_TO_MODULE } from "@/lib/modules";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { NavLink } from "@/components/nav-link";
|
||||
|
||||
@@ -47,6 +52,18 @@ export default async function AppLayout({
|
||||
{ href: "/review", label: t("review"), icon: LineChart, enabled: false },
|
||||
];
|
||||
|
||||
// Modul-Gating: deaktivierte Module werden ausgeblendet (§3.4)
|
||||
const moduleRows = await dbForTenant(session.user.tenantId).tenantModule.findMany();
|
||||
const disabledModules = new Set(moduleRows.filter((m) => !m.enabled).map((m) => m.moduleKey));
|
||||
const moduleEnabled = (href: string) => {
|
||||
const key = HREF_TO_MODULE[href];
|
||||
return !key || !disabledModules.has(key);
|
||||
};
|
||||
const visibleNav = nav.filter((item) => !item.enabled || moduleEnabled(item.href));
|
||||
|
||||
const isPlatformAdmin = session.user.isPlatformAdmin;
|
||||
const canManageTenant = hasPermission(session, "tenant:manage");
|
||||
|
||||
const initials = (session.user.name ?? "?")
|
||||
.split(/\s+/)
|
||||
.map((p) => p[0])
|
||||
@@ -76,7 +93,7 @@ export default async function AppLayout({
|
||||
</Link>
|
||||
</div>
|
||||
<nav className="flex-1 space-y-0.5 overflow-y-auto px-2.5 py-3">
|
||||
{nav.map((item) =>
|
||||
{visibleNav.map((item) =>
|
||||
item.enabled ? (
|
||||
<NavLink key={item.href} href={item.href}>
|
||||
<item.icon className="size-[18px] opacity-85" />
|
||||
@@ -93,6 +110,21 @@ export default async function AppLayout({
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
|
||||
{(canManageTenant || isPlatformAdmin) && (
|
||||
<div className="mt-2 space-y-0.5 border-t border-sidebar-border pt-2">
|
||||
{canManageTenant && (
|
||||
<NavLink href="/settings">
|
||||
<Settings className="size-[18px] opacity-85" /> {t("settings")}
|
||||
</NavLink>
|
||||
)}
|
||||
{isPlatformAdmin && (
|
||||
<NavLink href="/admin">
|
||||
<ShieldCheck className="size-[18px] opacity-85" /> {t("admin")}
|
||||
</NavLink>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user