feat(PROJ-32): Message-ID-basierte Duplikatserkennung
- message_id Spalte + UNIQUE-Index in emails-Tabelle - Save() prüft Message-ID vor SHA-256-Flow (kein Disk-I/O bei Duplikat) - lookupByMessageID() als private Hilfsfunktion - insertMeta() schreibt message_id, gibt error zurück (Race-safe) - SaveMeta() schreibt message_id idempotent (Backfill) feat(PROJ-34): Retention-Policy + Löschsperre (GoBD) - retain_until TIMESTAMPTZ Spalte in emails-Tabelle - ErrRetentionLock typed error - Delete() prüft Retention-Frist vor Löschung - Purge() löscht alle Mails mit abgelaufener Retention - POST /api/admin/purge Endpunkt (superadmin only) - config: storage.retention_days fix: Superadmin-Benutzerübersicht zeigt Mandant-Spalte - UsersTab: Mandant-Spalte wenn isSuperAdmin - domain_auditor Rolle im Create-Dialog ergänzt - storage Modulversion → 1.6 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -840,6 +840,7 @@ export default function AdminPage() {
|
||||
<UsersTab
|
||||
isSuperAdmin={isSuperAdmin}
|
||||
users={users}
|
||||
tenants={tenants}
|
||||
usersLoading={usersLoading}
|
||||
usersError={usersError}
|
||||
dialogOpen={dialogOpen}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { type User } from "@/lib/api";
|
||||
import { type Tenant } from "@/lib/api/tenants";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
@@ -35,6 +36,7 @@ import {
|
||||
interface UsersTabProps {
|
||||
isSuperAdmin: boolean;
|
||||
users: User[];
|
||||
tenants?: Tenant[];
|
||||
usersLoading: boolean;
|
||||
usersError: string;
|
||||
// Create dialog
|
||||
@@ -61,6 +63,7 @@ interface UsersTabProps {
|
||||
export function UsersTab({
|
||||
isSuperAdmin,
|
||||
users,
|
||||
tenants = [],
|
||||
usersLoading,
|
||||
usersError,
|
||||
dialogOpen,
|
||||
@@ -138,6 +141,7 @@ export function UsersTab({
|
||||
<SelectContent>
|
||||
<SelectItem value="user">User</SelectItem>
|
||||
<SelectItem value="auditor">Auditor</SelectItem>
|
||||
<SelectItem value="domain_auditor">Domain Auditor</SelectItem>
|
||||
<SelectItem value="domain_admin">Domain Admin</SelectItem>
|
||||
{isSuperAdmin && <SelectItem value="superadmin">Superadmin</SelectItem>}
|
||||
</SelectContent>
|
||||
@@ -186,6 +190,7 @@ export function UsersTab({
|
||||
<TableHead>Benutzername</TableHead>
|
||||
<TableHead>E-Mail</TableHead>
|
||||
<TableHead>Rolle</TableHead>
|
||||
{isSuperAdmin && <TableHead>Mandant</TableHead>}
|
||||
<TableHead>Status</TableHead>
|
||||
<TableHead className="text-right">Aktionen</TableHead>
|
||||
</TableRow>
|
||||
@@ -198,6 +203,13 @@ export function UsersTab({
|
||||
<TableCell>
|
||||
<Badge variant="secondary">{u.role}</Badge>
|
||||
</TableCell>
|
||||
{isSuperAdmin && (
|
||||
<TableCell className="text-muted-foreground text-sm">
|
||||
{u.tenant_id
|
||||
? (tenants.find((t) => t.id === u.tenant_id)?.name ?? `#${u.tenant_id}`)
|
||||
: <span className="italic">–</span>}
|
||||
</TableCell>
|
||||
)}
|
||||
<TableCell>
|
||||
<Badge variant={u.active ? "default" : "destructive"}>
|
||||
{u.active ? "Aktiv" : "Inaktiv"}
|
||||
|
||||
Reference in New Issue
Block a user