diff --git a/apps/web/src/app/(portal)/admin/ldap/page.tsx b/apps/web/src/app/(portal)/admin/ldap/page.tsx index 3c30021..20688a1 100644 --- a/apps/web/src/app/(portal)/admin/ldap/page.tsx +++ b/apps/web/src/app/(portal)/admin/ldap/page.tsx @@ -63,6 +63,15 @@ interface SyncResult { created: number; updated: number; deactivated: number; + // D-21: group-membership reconciliation counters — existed on the backend + // since Plan 15/D-21 but were never wired into this interface until now. + groupMembershipsAdded: number; + groupMembershipsRemoved: number; + // Plan 16-03/16-05: group reconciliation counters (SC-3/SC-4/D-05/D-06). + groupsAdopted: number; + groupsRenamed: number; + groupsDeleted: number; + defaultMarkerMoved: number; errors: string[]; } @@ -84,6 +93,7 @@ export default function AdminLdapPage() { } | null>(null); const [syncResult, setSyncResult] = useState(null); const [syncing, setSyncing] = useState(false); + const [syncRequestError, setSyncRequestError] = useState(null); // Form state for connection settings. const [formData, setFormData] = useState({ @@ -265,6 +275,7 @@ export default function AdminLdapPage() { const handleSync = async () => { setSyncing(true); setSyncResult(null); + setSyncRequestError(null); try { const res = await fetch(`${API_URL}/ldap/sync`, { method: 'POST', @@ -274,9 +285,13 @@ export default function AdminLdapPage() { const data = await res.json(); setSyncResult(data); await fetchConfig(); + } else { + setSyncResult(null); + setSyncRequestError(t('sync.requestError')); } } catch { - setSyncResult({ created: 0, updated: 0, deactivated: 0, errors: ['Network error'] }); + setSyncResult(null); + setSyncRequestError(t('sync.requestError')); } finally { setSyncing(false); } @@ -1287,6 +1302,12 @@ export default function AdminLdapPage() { {syncing ? t('sync.syncing') : t('sync.trigger')} + {/* Sync request failure (Owner-Entscheidung 2026-08-06): a failed + sync request must never render as a report full of zeros. */} + {syncRequestError && ( +

{syncRequestError}

+ )} + {/* Sync result display */} {syncResult && (
@@ -1297,6 +1318,26 @@ export default function AdminLdapPage() { deactivated: syncResult.deactivated, })}

+

+ {t('sync.resultGroupMemberships', { + groupMembershipsAdded: syncResult.groupMembershipsAdded, + groupMembershipsRemoved: syncResult.groupMembershipsRemoved, + })} +

+

+ {t('sync.resultGroups', { + groupsAdopted: syncResult.groupsAdopted, + groupsRenamed: syncResult.groupsRenamed, + groupsDeleted: syncResult.groupsDeleted, + })} +

+ {syncResult.defaultMarkerMoved > 0 && ( +

+ {t('sync.defaultMarkerMoved', { + defaultMarkerMoved: syncResult.defaultMarkerMoved, + })} +

+ )} {syncResult.errors.length > 0 && (
{syncResult.errors.map((err, i) => ( diff --git a/apps/web/src/messages/de.json b/apps/web/src/messages/de.json index e2a33c0..cb3d77b 100644 --- a/apps/web/src/messages/de.json +++ b/apps/web/src/messages/de.json @@ -329,7 +329,11 @@ "neverSynced": "Noch nie synchronisiert", "trigger": "Jetzt synchronisieren", "syncing": "Synchronisiere...", - "result": "Erstellt: {created}, aktualisiert: {updated}, deaktiviert: {deactivated}" + "result": "Erstellt: {created}, aktualisiert: {updated}, deaktiviert: {deactivated}", + "resultGroupMemberships": "Gruppenmitgliedschaften: {groupMembershipsAdded} hinzugefügt, {groupMembershipsRemoved} entfernt", + "resultGroups": "AD-Gruppen: {groupsAdopted} neu übernommen, {groupsRenamed} umbenannt, {groupsDeleted} gelöscht", + "defaultMarkerMoved": "Standardgruppen-Markierung musste neu vergeben werden ({defaultMarkerMoved}×).", + "requestError": "Die Synchronisation konnte nicht ausgeführt werden." }, "groupFilter": { "title": "Import-Filter (Gruppen/OUs)", diff --git a/apps/web/src/messages/en.json b/apps/web/src/messages/en.json index 9bed80f..4c723ee 100644 --- a/apps/web/src/messages/en.json +++ b/apps/web/src/messages/en.json @@ -329,7 +329,11 @@ "neverSynced": "Never synced", "trigger": "Sync now", "syncing": "Syncing...", - "result": "Created: {created}, updated: {updated}, deactivated: {deactivated}" + "result": "Created: {created}, updated: {updated}, deactivated: {deactivated}", + "resultGroupMemberships": "Group memberships: {groupMembershipsAdded} added, {groupMembershipsRemoved} removed", + "resultGroups": "AD groups: {groupsAdopted} newly adopted, {groupsRenamed} renamed, {groupsDeleted} deleted", + "defaultMarkerMoved": "Default-group marker had to be reassigned ({defaultMarkerMoved}×).", + "requestError": "The synchronization could not be run." }, "groupFilter": { "title": "Import filter (groups/OUs)",