import React, { useState, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { SettingsRolesPage } from './SettingsRoles'; import { SettingsGroupsPage } from './SettingsGroups'; import { useAllEntityPermissions, useEntityRegistry } from '@/api/entityPermissionHooks'; import { type EntityPermission, type EntityRegistryEntry } from '@/api/entityPermissions'; import { useAuditLog, type AuditLogEntry } from '@/api/audit'; import { usePermission } from '@/hooks/usePermission'; import { Table, type TableColumn } from '@/components/ui/Table'; import { Card } from '@/components/ui/Card'; import { Button } from '@/components/ui/Button'; import { Select } from '@/components/ui/Select'; import { Input } from '@/components/ui/Input'; import { Badge } from '@/components/ui/Badge'; import { Skeleton } from '@/components/ui/Skeleton'; import { EmptyState } from '@/components/ui/EmptyState'; import { useToast } from '@/components/ui/Toast'; import { ConfirmDialog } from '@/components/ui/ConfirmDialog'; import { apiDelete } from '@/api/client'; import { Search, Trash2, Shield, Users, Share2, History, LayoutGrid } from 'lucide-react'; import { WorkspaceManager } from '@/components/settings/WorkspaceManager'; import clsx from 'clsx'; // ─── Helpers ──────────────────────────────────────────────────────────────── function getPrincipalName(p: EntityPermission): string | null { return p.principal_type === 'user' ? p.user_name : p.group_name; } function getPrincipalId(p: EntityPermission): string | null { return p.principal_type === 'user' ? p.user_id : p.group_id; } // ─── Permission Level Badge ──────────────────────────────────────────────── function PermissionLevelBadge({ level }: { level: string }) { const colors: Record = { read: 'bg-blue-100 text-blue-800', write: 'bg-yellow-100 text-yellow-800', admin: 'bg-purple-100 text-purple-800', delete: 'bg-red-100 text-red-800', owner: 'bg-green-100 text-green-800', }; const labels: Record = { read: 'Lesen', write: 'Schreiben', admin: 'Admin', delete: 'Löschen', owner: 'Besitzer', }; return ( {labels[level] || level} ); } // ─── Principal Type Badge ──────────────────────────────────────────────────── function PrincipalTypeBadge({ type }: { type: string }) { const colors: Record = { user: 'bg-cyan-100 text-cyan-800', group: 'bg-indigo-100 text-indigo-800', role: 'bg-amber-100 text-amber-800', }; const labels: Record = { user: 'Benutzer', group: 'Gruppe', role: 'Rolle', }; return ( {labels[type] || type} ); } // ─── Freigaben Tab ───────────────────────────────────────────────────────── function FreigabenTab() { const { t } = useTranslation(); const toast = useToast(); const { hasPermission } = usePermission(); const { data: permsData, isLoading: permsLoading, refetch: refetchPerms } = useAllEntityPermissions(); const { data: registryData, isLoading: registryLoading } = useEntityRegistry(); const [filterEntityType, setFilterEntityType] = useState(''); const [filterPrincipal, setFilterPrincipal] = useState(''); const [confirmDelete, setConfirmDelete] = useState(null); const [deleting, setDeleting] = useState(false); const entityTypeMap = useMemo(() => { const map = new Map(); if (registryData) { for (const entry of registryData) { map.set(entry.entity_type, entry.display_name); } } return map; }, [registryData]); const entityTypeOptions = useMemo(() => { const types = new Set(); if (permsData?.items) { for (const p of permsData.items) { types.add(p.entity_type); } } return Array.from(types).sort().map((t) => ({ value: t, label: entityTypeMap.get(t) || t, })); }, [permsData, entityTypeMap]); const filteredPerms = useMemo(() => { if (!permsData?.items) return []; let items = permsData.items; if (filterEntityType) { items = items.filter((p) => p.entity_type === filterEntityType); } if (filterPrincipal) { const q = filterPrincipal.toLowerCase(); items = items.filter( (p) => { const name = getPrincipalName(p); const id = getPrincipalId(p); return (name && name.toLowerCase().includes(q)) || (id && id.toLowerCase().includes(q)); } ); } return items; }, [permsData, filterEntityType, filterPrincipal]); const handleDelete = async () => { if (!confirmDelete) return; setDeleting(true); try { await apiDelete(`/permissions/${confirmDelete.entity_type}/${confirmDelete.entity_id}/${confirmDelete.id}`); toast.success('Berechtigung gelöscht'); setConfirmDelete(null); refetchPerms(); } catch (err: any) { toast.error(err.message || 'Fehler beim Löschen'); } finally { setDeleting(false); } }; const columns: TableColumn[] = [ { key: 'entity_type', header: 'Entity-Typ', render: (row) => entityTypeMap.get(row.entity_type) || row.entity_type, }, { key: 'entity_id', header: 'Entity-ID', render: (row) => row.entity_id.slice(0, 8) + '…' }, { key: 'principal_type', header: 'Principal', render: (row) => { const name = getPrincipalName(row); const id = getPrincipalId(row); return (
{name || (id ? id.slice(0, 8) + '…' : '—')}
); }, }, { key: 'permission_level', header: 'Berechtigungsstufe', render: (row) => , }, { key: 'expires_at', header: 'Läuft ab', render: (row) => { if (!row.expires_at) return '—'; const expires = new Date(row.expires_at); const now = new Date(); const isExpired = expires < now; return ( {expires.toLocaleDateString('de-DE')} {isExpired && ' (abgelaufen)'} ); }, }, { key: 'actions', header: '', render: (row) => hasPermission('settings:write') ? ( ) : null, }, ]; if (permsLoading || registryLoading) { return (
); } return (
setFilterPrincipal(e.target.value)} />
row.id} emptyMessage="Keine Freigaben gefunden" /> {confirmDelete && ( setConfirmDelete(null)} onConfirm={handleDelete} title="Berechtigung löschen" message={`Soll die Berechtigung für ${entityTypeMap.get(confirmDelete.entity_type) || confirmDelete.entity_type} wirklich gelöscht werden?`} confirmLabel={deleting ? 'Wird gelöscht...' : 'Löschen'} variant="danger" /> )} ); } // ─── Audit Tab ───────────────────────────────────────────────────────────── function AuditTab() { const { t } = useTranslation(); const [page, setPage] = useState(1); const pageSize = 25; const { data, isLoading, isError, error } = useAuditLog(page, pageSize, { action: 'permission_grant,permission_revoke,permission_update', }); const columns: TableColumn[] = [ { key: 'timestamp', header: 'Zeitpunkt', render: (row) => new Date(row.timestamp).toLocaleString('de-DE'), }, { key: 'user', header: 'Benutzer' }, { key: 'action', header: 'Aktion', render: (row: AuditLogEntry) => { const labels: Record = { permission_grant: 'Freigabe erteilt', permission_revoke: 'Freigabe entzogen', permission_update: 'Freigabe geändert', }; const colors: Record = { permission_grant: 'bg-green-100 text-green-800', permission_revoke: 'bg-red-100 text-red-800', permission_update: 'bg-yellow-100 text-yellow-800', }; return ( {labels[row.action] || row.action} ); }, }, { key: 'entity', header: 'Entity' }, { key: 'details', header: 'Details', render: (row: AuditLogEntry) => { if (!row.details) return '—'; try { const parsed = JSON.parse(row.details); return ( {Object.entries(parsed).map(([k, v]) => `${k}: ${v}`).join(', ')} ); } catch { return {row.details}; } }, }, ]; const totalPages = data?.total ? Math.ceil(data.total / pageSize) : 1; return (
row.id} loading={isLoading} emptyMessage="Keine Audit-Einträge gefunden" /> {totalPages > 1 && (
Seite {page} von {totalPages}
)} ); } // ─── Main Page ────────────────────────────────────────────────────────────── export function SettingsRechtePage() { const { t } = useTranslation(); const { hasPermission } = usePermission(); const [activeTab, setActiveTab] = useState('rollen'); const tabs = [ { key: 'rollen', label: 'Rollen', icon: Shield, permission: 'roles:read' }, { key: 'gruppen', label: 'Gruppen', icon: Users, permission: 'groups:read' }, { key: 'freigaben', label: 'Freigaben', icon: Share2, permission: 'settings:read' }, { key: 'workspaces', label: 'Workspaces', icon: LayoutGrid, permission: 'workspaces:read' }, { key: 'audit', label: 'Audit', icon: History, permission: 'audit:read' }, ].filter((tab) => hasPermission(tab.permission)); return (

Rechteverwaltung

{activeTab === 'rollen' && } {activeTab === 'gruppen' && } {activeTab === 'freigaben' && } {activeTab === 'workspaces' && } {activeTab === 'audit' && }
); }