From 8f29bdb270771fdb03bb19ff15ec09d354fe01dc Mon Sep 17 00:00:00 2001 From: ValueOn AG Date: Sun, 8 Feb 2026 14:26:06 +0100 Subject: [PATCH] fiixed feature instance role access --- .../admin/AdminMandateRolePermissionsPage.tsx | 221 +++++++++++++++++- 1 file changed, 220 insertions(+), 1 deletion(-) diff --git a/src/pages/admin/AdminMandateRolePermissionsPage.tsx b/src/pages/admin/AdminMandateRolePermissionsPage.tsx index ec94651..619e39c 100644 --- a/src/pages/admin/AdminMandateRolePermissionsPage.tsx +++ b/src/pages/admin/AdminMandateRolePermissionsPage.tsx @@ -10,12 +10,15 @@ * - Mandate-specific roles (mandateId=xyz) - editable permissions * * Each role can be expanded to show/edit its AccessRules via AccessRulesEditor. + * + * Includes a "Cleanup Duplicates" tool to find and remove duplicate AccessRules. */ import React, { useState, useEffect, useMemo, useCallback } from 'react'; import { useMandateRoles, type Role } from '../../hooks/useMandateRoles'; import { useUserMandates, type Mandate } from '../../hooks/useUserMandates'; import { AccessRulesEditor } from '../../components/AccessRules'; +import api from '../../api'; import { FaUserShield, FaShieldAlt, @@ -24,10 +27,35 @@ import { FaChevronRight, FaGlobe, FaBuilding, - FaFilter + FaFilter, + FaBroom, + FaTimes, + FaExclamationTriangle, + FaCheckCircle } from 'react-icons/fa'; import styles from './Admin.module.css'; +// Types for cleanup result +interface DuplicateGroup { + roleId: string; + context: string; + item: string; + totalCount: number; + keepId: string; + deleteCount: number; + deleteIds: string[]; +} + +interface CleanupResult { + dryRun: boolean; + totalRules: number; + uniqueSignatures: number; + duplicateGroups: number; + duplicateRulesToDelete: number; + deletedCount: number; + details: DuplicateGroup[]; +} + export const AdminMandateRolePermissionsPage: React.FC = () => { const { roles, @@ -44,6 +72,13 @@ export const AdminMandateRolePermissionsPage: React.FC = () => { const [scopeFilter, setScopeFilter] = useState<'all' | 'mandate' | 'global'>('all'); const [expandedRoleId, setExpandedRoleId] = useState(null); + // Cleanup state + const [showCleanupModal, setShowCleanupModal] = useState(false); + const [cleanupLoading, setCleanupLoading] = useState(false); + const [cleanupResult, setCleanupResult] = useState(null); + const [cleanupError, setCleanupError] = useState(null); + const [cleanupPhase, setCleanupPhase] = useState<'idle' | 'preview' | 'done'>('idle'); + // Load mandates on mount useEffect(() => { const loadMandates = async () => { @@ -105,6 +140,49 @@ export const AdminMandateRolePermissionsPage: React.FC = () => { ); }; + // --- Cleanup functions --- + const _openCleanupModal = useCallback(async () => { + setShowCleanupModal(true); + setCleanupError(null); + setCleanupResult(null); + setCleanupPhase('idle'); + setCleanupLoading(true); + try { + const response = await api.post('/api/rbac/cleanup/duplicate-rules?dryRun=true'); + setCleanupResult(response.data); + setCleanupPhase('preview'); + } catch (err: any) { + setCleanupError(err?.response?.data?.detail || err?.message || 'Fehler beim Laden der Duplikate'); + } finally { + setCleanupLoading(false); + } + }, []); + + const _executeCleanup = useCallback(async () => { + setCleanupLoading(true); + setCleanupError(null); + try { + const response = await api.post('/api/rbac/cleanup/duplicate-rules?dryRun=false'); + setCleanupResult(response.data); + setCleanupPhase('done'); + // Refresh roles after cleanup + if (selectedMandateId) { + fetchRoles(selectedMandateId, { scopeFilter }); + } + } catch (err: any) { + setCleanupError(err?.response?.data?.detail || err?.message || 'Fehler beim Bereinigen'); + } finally { + setCleanupLoading(false); + } + }, [selectedMandateId, scopeFilter, fetchRoles]); + + const _closeCleanupModal = useCallback(() => { + setShowCleanupModal(false); + setCleanupResult(null); + setCleanupError(null); + setCleanupPhase('idle'); + }, []); + // Filter options for scope const scopeOptions = useMemo(() => [ { value: 'all', label: 'Alle Rollen' }, @@ -140,6 +218,14 @@ export const AdminMandateRolePermissionsPage: React.FC = () => {

+
)} + + {/* Cleanup Duplicates Modal */} + {showCleanupModal && ( +
+
e.stopPropagation()}> +
+

+ + Doppelte Regeln bereinigen +

+ +
+ +
+ {/* Loading */} + {cleanupLoading && ( +
+
+ {cleanupPhase === 'idle' ? 'Analysiere Duplikate...' : 'Bereinige Duplikate...'} +
+ )} + + {/* Error */} + {cleanupError && ( +
+ + {cleanupError} +
+ )} + + {/* Results */} + {cleanupResult && !cleanupLoading && ( + <> + {/* Summary Cards */} +
+
+
{cleanupResult.totalRules}
+
Regeln total
+
+
+
{cleanupResult.uniqueSignatures}
+
Eindeutige Regeln
+
+
0 ? '#fff5f5' : '#f0fff4', borderRadius: '8px', textAlign: 'center', border: `1px solid ${cleanupResult.duplicateGroups > 0 ? '#fc8181' : '#9ae6b4'}` }}> +
0 ? '#c53030' : '#2f855a' }}>{cleanupResult.duplicateGroups}
+
Duplikat-Gruppen
+
+
0 ? '#fff5f5' : '#f0fff4', borderRadius: '8px', textAlign: 'center', border: `1px solid ${cleanupResult.duplicateRulesToDelete > 0 ? '#fc8181' : '#9ae6b4'}` }}> +
0 ? '#c53030' : '#2f855a' }}> + {cleanupPhase === 'done' ? cleanupResult.deletedCount : cleanupResult.duplicateRulesToDelete} +
+
+ {cleanupPhase === 'done' ? 'Geloescht' : 'Zu loeschen'} +
+
+
+ + {/* Status Message */} + {cleanupPhase === 'done' && ( +
+ + {cleanupResult.deletedCount} doppelte Regeln wurden erfolgreich entfernt. +
+ )} + + {cleanupPhase === 'preview' && cleanupResult.duplicateGroups === 0 && ( +
+ + Keine Duplikate gefunden. Alles sauber! +
+ )} + + {/* Details Table */} + {cleanupResult.details.length > 0 && ( +
+

+ Duplikat-Details {cleanupResult.details.length < cleanupResult.duplicateGroups && `(${cleanupResult.details.length} von ${cleanupResult.duplicateGroups})`} +

+
+ + + + + + + + + + + {cleanupResult.details.map((group, idx) => ( + + + + + + + ))} + +
KontextItemTotalDuplikate
+ + {group.context} + + + + {group.item} + + {group.totalCount}{group.deleteCount}
+
+
+ )} + + )} +
+ +
+ + {cleanupPhase === 'preview' && cleanupResult && cleanupResult.duplicateRulesToDelete > 0 && ( + + )} +
+
+
+ )}
); };