You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by an...@apache.org on 2022/09/28 07:12:27 UTC

[syncope] branch master updated: SYNCOPE-1695 audit view improvements (#376)

This is an automated email from the ASF dual-hosted git repository.

andreapatricelli pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/syncope.git


The following commit(s) were added to refs/heads/master by this push:
     new 1c7548c4b1 SYNCOPE-1695 audit view improvements (#376)
1c7548c4b1 is described below

commit 1c7548c4b14a65e33e7418d9a0873089f9ec4bd1
Author: Andrea Patricelli <an...@apache.org>
AuthorDate: Wed Sep 28 09:12:22 2022 +0200

    SYNCOPE-1695 audit view improvements (#376)
    
    * [SYNCOPE-1695] refactored audit view, added possibility to move across versions
---
 .../console/panels/ConnectorDirectoryPanel.java    |   4 +-
 .../console/panels/ResourceDirectoryPanel.java     |   4 +-
 .../console/topology/TopologyTogglePanel.java      |  16 +-
 .../client/console/audit/AuditHistoryDetails.java  | 213 +++++++++++++++++----
 .../console/audit/AuditHistoryDirectoryPanel.java  | 192 -------------------
 .../client/console/audit/AuditHistoryModal.java    |  24 +--
 .../console/panels/AnyObjectDirectoryPanel.java    |   4 +-
 .../client/console/panels/GroupDirectoryPanel.java |   4 +-
 .../client/console/panels/UserDirectoryPanel.java  |   9 +-
 .../wicket/markup/html/form/JsonDiffPanel.java     |  33 ++--
 .../META-INF/resources/css/syncopeConsole.scss     |  20 +-
 .../client/console/audit/AuditHistoryDetails.html  |  18 +-
 .../console/audit/AuditHistoryDetails.properties   |   4 +-
 .../audit/AuditHistoryDetails_fr_CA.properties     |   7 +-
 .../audit/AuditHistoryDetails_it.properties        |   7 +-
 .../audit/AuditHistoryDetails_ja.properties        |   9 +-
 .../audit/AuditHistoryDetails_pt_BR.properties     |   8 +-
 .../audit/AuditHistoryDetails_ru.properties        |   8 +-
 .../syncope/common/lib/types/AuditElements.java    |  26 +--
 .../syncope/common/lib/types/AuditLoggerName.java  |   8 +-
 .../org/apache/syncope/core/logic/AuditLogic.java  |   6 +-
 .../java/pushpull/AbstractPushResultHandler.java   |   6 +-
 .../org/apache/syncope/fit/core/AuditITCase.java   |  90 ++++++++-
 .../apache/syncope/fit/core/PushTaskITCase.java    |   4 +-
 .../reference-guide/concepts/notifications.adoc    |   2 +-
 25 files changed, 370 insertions(+), 356 deletions(-)

diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel.java
index 69035c0e6a..915995d2b2 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel.java
@@ -188,12 +188,10 @@ public class ConnectorDirectoryPanel extends
               ConnInstanceTO modelObject = ConnectorRestClient.read(((ConnInstanceTO) model.getObject()).getKey());
             
               target.add(altDefaultModal.setContent(new AuditHistoryModal<>(
-                      altDefaultModal,
                       AuditElements.EventCategoryType.LOGIC,
                       "ConnectorLogic",
                       modelObject,
-                      IdMEntitlement.CONNECTOR_UPDATE,
-                      pageRef) {
+                      IdMEntitlement.CONNECTOR_UPDATE) {
             
                   private static final long serialVersionUID = -3225348282675513648L;
 
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ResourceDirectoryPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ResourceDirectoryPanel.java
index b5654d7d91..81cd889938 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ResourceDirectoryPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ResourceDirectoryPanel.java
@@ -303,12 +303,10 @@ public class ResourceDirectoryPanel extends
                 ResourceTO modelObject = ResourceRestClient.read(((ResourceTO) model.getObject()).getKey());
 
                 target.add(historyModal.setContent(new AuditHistoryModal<>(
-                        historyModal,
                         AuditElements.EventCategoryType.LOGIC,
                         "ResourceLogic",
                         modelObject,
-                        IdMEntitlement.RESOURCE_UPDATE,
-                        pageRef) {
+                        IdMEntitlement.RESOURCE_UPDATE) {
 
                     private static final long serialVersionUID = -3712506022627033811L;
 
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/TopologyTogglePanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/TopologyTogglePanel.java
index 1ca1ce27aa..a45072f82c 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/TopologyTogglePanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/TopologyTogglePanel.java
@@ -313,8 +313,8 @@ public class TopologyTogglePanel extends TogglePanel<Serializable> {
                         build(BaseModal.CONTENT_ID,
                                 SyncopeConsoleSession.get().
                                         owns(IdMEntitlement.CONNECTOR_UPDATE, connInstance.getAdminRealm())
-                                ? AjaxWizard.Mode.EDIT
-                                : AjaxWizard.Mode.READONLY)));
+                                        ? AjaxWizard.Mode.EDIT
+                                        : AjaxWizard.Mode.READONLY)));
 
                 modal.header(
                         new Model<>(MessageFormat.format(getString("connector.edit"), connInstance.getDisplayName())));
@@ -338,12 +338,10 @@ public class TopologyTogglePanel extends TogglePanel<Serializable> {
                 ConnInstanceTO modelObject = ConnectorRestClient.read(node.getKey());
 
                 target.add(historyModal.setContent(new AuditHistoryModal<>(
-                        historyModal,
                         AuditElements.EventCategoryType.LOGIC,
                         "ConnectorLogic",
                         modelObject,
-                        IdMEntitlement.CONNECTOR_UPDATE,
-                        pageRef) {
+                        IdMEntitlement.CONNECTOR_UPDATE) {
 
                     private static final long serialVersionUID = -3225348282675513648L;
 
@@ -421,8 +419,8 @@ public class TopologyTogglePanel extends TogglePanel<Serializable> {
                         build(BaseModal.CONTENT_ID,
                                 SyncopeConsoleSession.get().
                                         owns(IdMEntitlement.RESOURCE_UPDATE, connInstance.getAdminRealm())
-                                ? AjaxWizard.Mode.EDIT
-                                : AjaxWizard.Mode.READONLY)));
+                                        ? AjaxWizard.Mode.EDIT
+                                        : AjaxWizard.Mode.READONLY)));
 
                 modal.header(new Model<>(MessageFormat.format(getString("resource.edit"), node.getKey())));
                 modal.show(true);
@@ -583,12 +581,10 @@ public class TopologyTogglePanel extends TogglePanel<Serializable> {
                 ResourceTO modelObject = ResourceRestClient.read(node.getKey());
 
                 target.add(historyModal.setContent(new AuditHistoryModal<>(
-                        historyModal,
                         AuditElements.EventCategoryType.LOGIC,
                         "ResourceLogic",
                         modelObject,
-                        IdMEntitlement.RESOURCE_UPDATE,
-                        pageRef) {
+                        IdMEntitlement.RESOURCE_UPDATE) {
 
                     private static final long serialVersionUID = -3712506022627033811L;
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryDetails.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryDetails.java
index c550937d73..713727c8f2 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryDetails.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryDetails.java
@@ -28,35 +28,70 @@ import com.fasterxml.jackson.databind.module.SimpleModule;
 import com.fasterxml.jackson.databind.node.JsonNodeFactory;
 import com.fasterxml.jackson.databind.node.ObjectNode;
 import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
 import java.io.IOException;
 import java.io.Serializable;
-import java.time.OffsetDateTime;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeMap;
 import java.util.TreeSet;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
-import org.apache.syncope.client.console.panels.MultilevelPanel;
+import org.apache.syncope.client.console.rest.AuditRestClient;
+import org.apache.syncope.client.console.wicket.ajax.form.IndicatorAjaxEventBehavior;
 import org.apache.syncope.client.console.wicket.markup.html.form.JsonDiffPanel;
+import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxDropDownChoicePanel;
+import org.apache.syncope.client.ui.commons.panels.ModalPanel;
 import org.apache.syncope.common.lib.audit.AuditEntry;
-import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.to.EntityTO;
 import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.AuditElements;
 import org.apache.wicket.WicketRuntimeException;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.ajax.markup.html.AjaxLink;
 import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
-import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
+import org.apache.wicket.markup.html.form.IChoiceRenderer;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.Model;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-public abstract class AuditHistoryDetails<T extends Serializable> extends MultilevelPanel.SecondLevel {
+public abstract class AuditHistoryDetails<T extends Serializable> extends Panel implements ModalPanel {
 
     private static final long serialVersionUID = -7400543686272100483L;
 
     private static final Logger LOG = LoggerFactory.getLogger(AuditHistoryDetails.class);
 
+    private static final List<String> EVENTS = List.of("create", "update", "matchingrule_update",
+            "unmatchingrule_assign", "unmatchingrule_provision");
+
+    private static final SortParam<String> REST_SORT = new SortParam<>("event_date", false);
+
+    private EntityTO currentEntity;
+    private AuditElements.EventCategoryType type;
+
+    private String category;
+
+    private Class<T> reference;
+
+    private List<AuditEntry> auditEntries = new ArrayList<>();
+
+    private AuditEntry latestAuditEntry;
+
+    private AuditEntry after;
+
+    private AjaxDropDownChoicePanel<AuditEntry> beforeVersionsPanel;
+
+    private AjaxDropDownChoicePanel<AuditEntry> afterVersionsPanel;
+
+    private AjaxLink<Void> restore;
+
     private static class SortingNodeFactory extends JsonNodeFactory {
 
         private static final long serialVersionUID = 1870252010670L;
@@ -118,59 +153,114 @@ public abstract class AuditHistoryDetails<T extends Serializable> extends Multil
 
     private static final ObjectMapper MAPPER = JsonMapper.builder().
             nodeFactory(new SortingNodeFactory()).build().
-            registerModule(new SimpleModule().addSerializer(new SortedSetJsonSerializer(cast(Set.class))));
+            registerModule(new SimpleModule().addSerializer(new SortedSetJsonSerializer(cast(Set.class)))).
+            registerModule(new JavaTimeModule());
 
     public AuditHistoryDetails(
-            final MultilevelPanel mlp,
-            final AuditEntry selected,
+            final String id,
             final EntityTO currentEntity,
+            final AuditElements.EventCategoryType type,
+            final String category,
             final String auditRestoreEntitlement) {
 
-        super();
+        super(id);
 
-        AuditEntry current = new AuditEntry();
-        if (currentEntity instanceof AnyTO) {
-            current.setWho(((AnyTO) currentEntity).getCreator());
-            current.setDate(((AnyTO) currentEntity).getCreationDate());
-        } else {
-            current.setWho(SyncopeConsoleSession.get().getSelfTO().getUsername());
-            current.setDate(OffsetDateTime.now());
-        }
-        try {
-            current.setBefore(MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(currentEntity));
-        } catch (JsonProcessingException e) {
-            LOG.error("While serializing current entity", e);
-            throw new WicketRuntimeException(e);
-        }
+        this.setOutputMarkupId(true);
+        this.reference = (Class<T>) currentEntity.getClass();
+        this.currentEntity = currentEntity;
+        this.type = type;
+        this.category = category;
 
-        add(new Label("current", getString("current")));
-        add(new Label("previous", getString("previous")));
+        IChoiceRenderer<AuditEntry> choiceRenderer = new IChoiceRenderer<>() {
 
-        @SuppressWarnings("unchecked")
-        Class<T> reference = (Class<T>) currentEntity.getClass();
-        add(new JsonDiffPanel(null, toJSON(current, reference), toJSON(selected, reference), null) {
+            private static final long serialVersionUID = -3724971416312135885L;
 
-            private static final long serialVersionUID = 2087989787864619493L;
+            @Override
+            public String getDisplayValue(final AuditEntry value) {
+                return SyncopeConsoleSession.get().getDateFormat().format(value.getDate());
+            }
 
             @Override
-            public void onSubmit(final AjaxRequestTarget target) {
-                modal.close(target);
+            public String getIdValue(final AuditEntry value, final int i) {
+                return Long.toString(value.getDate().toInstant().toEpochMilli());
+            }
+
+            @Override
+            public AuditEntry getObject(final String id, final IModel<? extends List<? extends AuditEntry>> choices) {
+                return choices.getObject().stream()
+                        .filter(c -> StringUtils.isNotBlank(id)
+                                && Long.valueOf(id) == c.getDate().toInstant().toEpochMilli()).findFirst()
+                        .orElse(null);
+            }
+        };
+        // add also select to choose with which version compare
+
+        beforeVersionsPanel =
+                new AjaxDropDownChoicePanel<>("beforeVersions", getString("beforeVersions"), new Model<>(), true);
+        beforeVersionsPanel.setChoiceRenderer(choiceRenderer);
+        beforeVersionsPanel.add(new IndicatorAjaxEventBehavior(Constants.ON_CHANGE) {
+
+            private static final long serialVersionUID = -6383712635009760397L;
+
+            @Override
+            protected void onEvent(final AjaxRequestTarget target) {
+                AuditEntry beforeEntry = beforeVersionsPanel.getModelObject() == null
+                        ? latestAuditEntry
+                        : beforeVersionsPanel.getModelObject();
+                AuditEntry afterEntry = afterVersionsPanel.getModelObject() == null
+                        ? after
+                        : buildAfterAuditEntry(beforeEntry);
+                AuditHistoryDetails.this.addOrReplace(new JsonDiffPanel(toJSON(beforeEntry, reference),
+                        toJSON(afterEntry, reference)));
+                // change after audit entries in order to match only the ones newer than the current after one
+                afterVersionsPanel.setChoices(auditEntries.stream().filter(ae ->
+                                ae.getDate().isAfter(beforeEntry.getDate())
+                                        || ae.getDate().isEqual(beforeEntry.getDate()))
+                        .collect(Collectors.toList()));
+                // set the new after entry
+                afterVersionsPanel.setModelObject(afterEntry);
+                target.add(AuditHistoryDetails.this);
+            }
+        });
+        afterVersionsPanel =
+                new AjaxDropDownChoicePanel<>("afterVersions", getString("afterVersions"), new Model<>(),
+                        true);
+        afterVersionsPanel.setChoiceRenderer(choiceRenderer);
+        afterVersionsPanel.add(new IndicatorAjaxEventBehavior(Constants.ON_CHANGE) {
+
+            private static final long serialVersionUID = -6383712635009760397L;
+
+            @Override
+            protected void onEvent(final AjaxRequestTarget target) {
+                AuditHistoryDetails.this.addOrReplace(
+                        new JsonDiffPanel(toJSON(beforeVersionsPanel.getModelObject() == null
+                                ? latestAuditEntry
+                                : beforeVersionsPanel.getModelObject(), reference),
+                                toJSON(afterVersionsPanel.getModelObject() == null
+                                        ? after
+                                        : buildAfterAuditEntry(afterVersionsPanel.getModelObject()), reference)));
+                target.add(AuditHistoryDetails.this);
             }
         });
+        add(beforeVersionsPanel.setOutputMarkupId(true));
+        add(afterVersionsPanel.setOutputMarkupId(true));
 
-        AjaxLink<Void> restore = new AjaxLink<>("restore") {
+        restore = new AjaxLink<>("restore") {
 
             private static final long serialVersionUID = -817438685948164787L;
 
             @Override
             public void onClick(final AjaxRequestTarget target) {
                 try {
-                    String json = selected.getBefore() == null
-                            ? MAPPER.readTree(selected.getOutput()).get("entity").toPrettyString()
-                            : selected.getBefore();
+                    AuditEntry before = beforeVersionsPanel.getModelObject() == null
+                            ? latestAuditEntry
+                            : beforeVersionsPanel.getModelObject();
+                    String json = before.getBefore() == null
+                            ? MAPPER.readTree(before.getOutput()).get("entity") == null
+                            ? MAPPER.readTree(before.getOutput()).toPrettyString()
+                            : MAPPER.readTree(before.getOutput()).get("entity").toPrettyString()
+                            : before.getBefore();
                     restore(json, target);
-
-                    mlp.prev(target);
                 } catch (JsonProcessingException e) {
                     throw new WicketRuntimeException(e);
                 }
@@ -178,14 +268,61 @@ public abstract class AuditHistoryDetails<T extends Serializable> extends Multil
         };
         MetaDataRoleAuthorizationStrategy.authorize(restore, ENABLE, auditRestoreEntitlement);
         add(restore);
+        
+        initDiff();
     }
 
     protected abstract void restore(String json, AjaxRequestTarget target);
 
+    protected void initDiff() {
+        // audit fetch size is fixed, for the moment... 
+        this.auditEntries.clear();
+        this.auditEntries.addAll(new AuditRestClient().search(
+                currentEntity.getKey(),
+                1,
+                50,
+                type,
+                category,
+                EVENTS,
+                AuditElements.Result.SUCCESS,
+                REST_SORT));
+
+        // the default selected is the newest one, if any
+        this.latestAuditEntry = auditEntries.isEmpty() ? null : auditEntries.get(0);
+        this.after = latestAuditEntry == null ? null : buildAfterAuditEntry(latestAuditEntry);
+        // add default diff panel
+        addOrReplace(new JsonDiffPanel(toJSON(latestAuditEntry, reference), toJSON(after, reference)));
+
+        beforeVersionsPanel.setChoices(auditEntries);
+        afterVersionsPanel.setChoices(auditEntries.stream().filter(ae ->
+                ae.getDate().isAfter(after.getDate()) || ae.getDate().isEqual(after.getDate())).collect(
+                Collectors.toList()));
+
+        beforeVersionsPanel.setModelObject(latestAuditEntry);
+        afterVersionsPanel.setModelObject(after);
+
+        restore.setEnabled(!auditEntries.isEmpty());
+    }
+
+    private AuditEntry buildAfterAuditEntry(final AuditEntry input) {
+        AuditEntry output = new AuditEntry();
+        output.setWho(input.getWho());
+        output.setDate(input.getDate());
+        // current by default is the output of the selected event
+        output.setOutput(input.getOutput());
+        output.setThrowable(input.getThrowable());
+        return output;
+    }
+
     private Model<String> toJSON(final AuditEntry auditEntry, final Class<T> reference) {
         try {
+            if (auditEntry == null) {
+                return Model.of();
+            }
             String content = auditEntry.getBefore() == null
-                    ? MAPPER.readTree(auditEntry.getOutput()).get("entity").toPrettyString()
+                    ? MAPPER.readTree(auditEntry.getOutput()).get("entity") == null
+                    ? MAPPER.readTree(auditEntry.getOutput()).toPrettyString()
+                    : MAPPER.readTree(auditEntry.getOutput()).get("entity").toPrettyString()
                     : auditEntry.getBefore();
 
             T entity = MAPPER.reader().
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryDirectoryPanel.java
deleted file mode 100644
index a12138b5b5..0000000000
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryDirectoryPanel.java
+++ /dev/null
@@ -1,192 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.syncope.client.console.audit;
-
-import java.io.Serializable;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.List;
-import org.apache.syncope.client.console.commons.IdRepoConstants;
-import org.apache.syncope.client.console.panels.AjaxDataTablePanel;
-import org.apache.syncope.client.console.panels.DirectoryPanel;
-import org.apache.syncope.client.console.panels.MultilevelPanel;
-import org.apache.syncope.client.console.rest.AuditRestClient;
-import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.DatePropertyColumn;
-import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
-import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
-import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
-import org.apache.syncope.client.ui.commons.DirectoryDataProvider;
-import org.apache.syncope.client.ui.commons.panels.ModalPanel;
-import org.apache.syncope.common.lib.audit.AuditEntry;
-import org.apache.syncope.common.lib.to.EntityTO;
-import org.apache.syncope.common.lib.types.AuditElements;
-import org.apache.syncope.common.lib.types.IdRepoEntitlement;
-import org.apache.wicket.PageReference;
-import org.apache.wicket.ajax.AjaxRequestTarget;
-import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder;
-import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
-import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
-import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
-import org.apache.wicket.model.CompoundPropertyModel;
-import org.apache.wicket.model.IModel;
-import org.apache.wicket.model.StringResourceModel;
-
-public abstract class AuditHistoryDirectoryPanel<T extends Serializable> extends DirectoryPanel<
-        AuditEntry, AuditEntry, AuditHistoryDirectoryPanel<T>.AuditHistoryProvider, AuditRestClient>
-        implements ModalPanel {
-
-    private static final long serialVersionUID = -8248734710505211261L;
-
-    private static final List<String> EVENTS = List.of("create", "update");
-
-    private static final SortParam<String> REST_SORT = new SortParam<>("event_date", false);
-
-    private final BaseModal<?> baseModal;
-
-    private final MultilevelPanel mlp;
-
-    private final AuditElements.EventCategoryType type;
-
-    private final String category;
-
-    private final EntityTO entity;
-
-    private final String auditRestoreEntitlement;
-
-    public AuditHistoryDirectoryPanel(
-            final BaseModal<?> baseModal,
-            final MultilevelPanel mlp,
-            final AuditElements.EventCategoryType type,
-            final String category,
-            final EntityTO entity,
-            final String auditRestoreEntitlement,
-            final PageReference pageRef) {
-
-        super(MultilevelPanel.FIRST_LEVEL_ID, pageRef);
-        disableCheckBoxes();
-
-        this.baseModal = baseModal;
-        this.mlp = mlp;
-        this.type = type;
-        this.category = category;
-        this.entity = entity;
-        this.auditRestoreEntitlement = auditRestoreEntitlement;
-        this.pageRef = pageRef;
-
-        this.restClient = new AuditRestClient();
-        initResultTable();
-    }
-
-    @Override
-    protected AuditHistoryDirectoryPanel<T>.AuditHistoryProvider dataProvider() {
-        return new AuditHistoryProvider(rows);
-    }
-
-    @Override
-    protected String paginatorRowsKey() {
-        return IdRepoConstants.PREF_AUDIT_HISTORY_PAGINATOR_ROWS;
-    }
-
-    @Override
-    protected List<IColumn<AuditEntry, String>> getColumns() {
-        List<IColumn<AuditEntry, String>> columns = new ArrayList<>();
-        columns.add(new PropertyColumn<>(new StringResourceModel("who", this), "who"));
-        columns.add(new DatePropertyColumn<>(new StringResourceModel("date", this), null, "date"));
-        return columns;
-    }
-
-    @Override
-    protected void resultTableCustomChanges(final AjaxDataTablePanel.Builder<AuditEntry, String> resultTableBuilder) {
-        resultTableBuilder.setMultiLevelPanel(baseModal, mlp);
-    }
-
-    protected abstract void restore(String json, AjaxRequestTarget target);
-
-    @Override
-    protected ActionsPanel<AuditEntry> getActions(final IModel<AuditEntry> model) {
-        final ActionsPanel<AuditEntry> panel = super.getActions(model);
-
-        panel.add(new ActionLink<>() {
-
-            private static final long serialVersionUID = -6745431735457245600L;
-
-            @Override
-            public void onClick(final AjaxRequestTarget target, final AuditEntry modelObject) {
-                AuditHistoryDirectoryPanel.this.getTogglePanel().close(target);
-
-                mlp.next(
-                    new StringResourceModel("audit.diff.view", AuditHistoryDirectoryPanel.this).getObject(),
-                    new AuditHistoryDetails<T>(mlp, modelObject, entity, auditRestoreEntitlement) {
-
-                        private static final long serialVersionUID = -5311898419151367494L;
-
-                        @Override
-                        protected void restore(final String json, final AjaxRequestTarget target) {
-                            AuditHistoryDirectoryPanel.this.restore(json, target);
-                        }
-                    }, target);
-
-                target.add(modal);
-            }
-        }, ActionLink.ActionType.VIEW, IdRepoEntitlement.AUDIT_READ);
-
-        return panel;
-    }
-
-    @Override
-    protected Collection<ActionLink.ActionType> getBatches() {
-        return List.of();
-    }
-
-    protected class AuditHistoryProvider extends DirectoryDataProvider<AuditEntry> {
-
-        private static final long serialVersionUID = 415113175628260864L;
-
-        AuditHistoryProvider(final int paginatorRows) {
-            super(paginatorRows);
-            setSort("date", SortOrder.DESCENDING);
-        }
-
-        @Override
-        public long size() {
-            return restClient.count(entity.getKey(), type, category, EVENTS, AuditElements.Result.SUCCESS);
-        }
-
-        @Override
-        public Iterator<AuditEntry> iterator(final long first, final long count) {
-            int page = ((int) first / paginatorRows);
-            return restClient.search(
-                    entity.getKey(),
-                    (page < 0 ? 0 : page) + 1,
-                    paginatorRows,
-                    type,
-                    category,
-                    EVENTS,
-                    AuditElements.Result.SUCCESS,
-                    REST_SORT).
-                    iterator();
-        }
-
-        @Override
-        public IModel<AuditEntry> model(final AuditEntry auditEntryBean) {
-            return new CompoundPropertyModel<>(auditEntryBean);
-        }
-    }
-}
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryModal.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryModal.java
index 5217b0940d..ccbf968ddf 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryModal.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/audit/AuditHistoryModal.java
@@ -18,12 +18,10 @@
  */
 package org.apache.syncope.client.console.audit;
 
-import org.apache.syncope.client.console.panels.MultilevelPanel;
 import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
 import org.apache.syncope.client.ui.commons.panels.ModalPanel;
 import org.apache.syncope.common.lib.to.EntityTO;
 import org.apache.syncope.common.lib.types.AuditElements;
-import org.apache.wicket.PageReference;
 import org.apache.wicket.ajax.AjaxRequestTarget;
 import org.apache.wicket.markup.html.panel.Panel;
 
@@ -32,33 +30,29 @@ public abstract class AuditHistoryModal<T extends EntityTO> extends Panel implem
     private static final long serialVersionUID = 1066124171682570080L;
 
     public AuditHistoryModal(
-            final BaseModal<?> baseModal,
             final AuditElements.EventCategoryType type,
             final String category,
             final T entity,
-            final String auditRestoreEntitlement,
-            final PageReference pageRef) {
+            final String auditRestoreEntitlement) {
 
         super(BaseModal.CONTENT_ID);
 
-        MultilevelPanel mlp = new MultilevelPanel("history");
-        mlp.setOutputMarkupId(true);
-        add(mlp.setFirstLevel(new AuditHistoryDirectoryPanel<T>(
-                baseModal,
-                mlp,
+        add(new AuditHistoryDetails(
+                "history",
+                entity,
                 type,
                 category,
-                entity,
-                auditRestoreEntitlement,
-                pageRef) {
+                auditRestoreEntitlement) {
 
-            private static final long serialVersionUID = 1952220682903768286L;
+            private static final long serialVersionUID = -5311898419151367494L;
 
             @Override
             protected void restore(final String json, final AjaxRequestTarget target) {
                 AuditHistoryModal.this.restore(json, target);
+                this.initDiff();
+                target.add(this);
             }
-        }));
+        });
     }
 
     protected abstract void restore(String json, AjaxRequestTarget target);
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel.java
index 62146a47e7..f09645b7ab 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/AnyObjectDirectoryPanel.java
@@ -170,12 +170,10 @@ public class AnyObjectDirectoryPanel extends AnyDirectoryPanel<AnyObjectTO, AnyO
             public void onClick(final AjaxRequestTarget target, final AnyObjectTO ignore) {
                 model.setObject(restClient.read(model.getObject().getKey()));
                 target.add(altDefaultModal.setContent(new AuditHistoryModal<>(
-                        altDefaultModal,
                         AuditElements.EventCategoryType.LOGIC,
                         "AnyObjectLogic",
                         model.getObject(),
-                        AnyEntitlement.UPDATE.getFor(type),
-                        pageRef) {
+                        AnyEntitlement.UPDATE.getFor(type)) {
 
                     private static final long serialVersionUID = -7440902560249531201L;
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/GroupDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/GroupDirectoryPanel.java
index a2fe99d098..a9fd3d357f 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/GroupDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/GroupDirectoryPanel.java
@@ -339,12 +339,10 @@ public class GroupDirectoryPanel extends AnyDirectoryPanel<GroupTO, GroupRestCli
             public void onClick(final AjaxRequestTarget target, final GroupTO ignore) {
                 model.setObject(restClient.read(model.getObject().getKey()));
                 target.add(altDefaultModal.setContent(new AuditHistoryModal<>(
-                        altDefaultModal,
                         AuditElements.EventCategoryType.LOGIC,
                         "GroupLogic",
                         model.getObject(),
-                        IdRepoEntitlement.GROUP_UPDATE,
-                        pageRef) {
+                        IdRepoEntitlement.GROUP_UPDATE) {
 
                     private static final long serialVersionUID = -5819724478921691835L;
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/UserDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/UserDirectoryPanel.java
index 7f9d2ecd6f..a9e968436e 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/UserDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/panels/UserDirectoryPanel.java
@@ -52,7 +52,6 @@ import org.apache.syncope.common.lib.to.PlainSchemaTO;
 import org.apache.syncope.common.lib.to.ProvisioningResult;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.syncope.common.lib.types.AnyTypeKind;
-import org.apache.syncope.common.lib.types.AuditElements;
 import org.apache.syncope.common.lib.types.IdRepoEntitlement;
 import org.apache.syncope.common.rest.api.service.UserSelfService;
 import org.apache.wicket.PageReference;
@@ -269,12 +268,10 @@ public class UserDirectoryPanel extends AnyDirectoryPanel<UserTO, UserRestClient
                 public void onClick(final AjaxRequestTarget target, final UserTO ignore) {
                     model.setObject(restClient.read(model.getObject().getKey()));
                     target.add(altDefaultModal.setContent(new AuditHistoryModal<>(
-                            altDefaultModal,
-                            AuditElements.EventCategoryType.LOGIC,
-                            "UserLogic",
+                            null,
+                            null,
                             model.getObject(),
-                            IdRepoEntitlement.USER_UPDATE,
-                            pageRef) {
+                            IdRepoEntitlement.USER_UPDATE) {
 
                         private static final long serialVersionUID = 959378158400669867L;
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/JsonDiffPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/JsonDiffPanel.java
index 2ebc7bc266..9daf2ee279 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/JsonDiffPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/JsonDiffPanel.java
@@ -18,25 +18,19 @@
  */
 package org.apache.syncope.client.console.wicket.markup.html.form;
 
-import org.apache.syncope.client.console.panels.AbstractModalPanel;
-import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
-import org.apache.wicket.PageReference;
 import org.apache.wicket.markup.head.IHeaderResponse;
 import org.apache.wicket.markup.head.OnLoadHeaderItem;
 import org.apache.wicket.markup.html.form.TextArea;
+import org.apache.wicket.markup.html.panel.Panel;
 import org.apache.wicket.model.IModel;
 
-public class JsonDiffPanel extends AbstractModalPanel<String> {
+public class JsonDiffPanel extends Panel {
 
     private static final long serialVersionUID = -5110368813584745668L;
 
-    public JsonDiffPanel(
-            final BaseModal<String> modal,
-            final IModel<String> first,
-            final IModel<String> second,
-            final PageReference pageRef) {
+    public JsonDiffPanel(final IModel<String> first, final IModel<String> second) {
 
-        super(modal, pageRef);
+        super("jsonDiffPanel");
 
         TextArea<String> jsonEditorInfoDefArea1 = new TextArea<>("jsonEditorInfo1", first);
         jsonEditorInfoDefArea1.setMarkupId("jsonEditorInfo1").setOutputMarkupPlaceholderTag(true);
@@ -52,14 +46,15 @@ public class JsonDiffPanel extends AbstractModalPanel<String> {
         super.renderHead(response);
         response.render(OnLoadHeaderItem.forScript(
                 "CodeMirror.MergeView(document.getElementById('jsonDiffEditorInfoDefForm'), {"
-                + "  value: document.getElementById('jsonEditorInfo1').innerHTML, "
-                + "  orig: document.getElementById('jsonEditorInfo2').innerHTML, "
-                + "  lineNumbers: true, "
-                + "  mode: \"application/json\","
-                + "  highlightDifferences: true,"
-                + "  showDifferences: true,"
-                + "  readOnly: true,"
-                + "  revertButtons: false"
-                + "});"));
+                        + "  value: document.getElementById('jsonEditorInfo1').innerHTML, "
+                        + "  orig: document.getElementById('jsonEditorInfo2').innerHTML, "
+                        + "  lineNumbers: true, "
+                        + "  mode: \"application/json\","
+                        + "  highlightDifferences: true,"
+                        + "  showDifferences: true,"
+                        + "  readOnly: true,"
+                        + "  revertButtons: false,"
+                        + "  autoRefresh: true"
+                        + "});"));
     }
 }
diff --git a/client/idrepo/console/src/main/resources/META-INF/resources/css/syncopeConsole.scss b/client/idrepo/console/src/main/resources/META-INF/resources/css/syncopeConsole.scss
index ab5c52d596..6d62576586 100644
--- a/client/idrepo/console/src/main/resources/META-INF/resources/css/syncopeConsole.scss
+++ b/client/idrepo/console/src/main/resources/META-INF/resources/css/syncopeConsole.scss
@@ -22,7 +22,6 @@ $background_console_dark: #00a65a;
 $background_console_white: #eee;
 
 
-
 /* SCSS Methods
 ============================================================================= */
 
@@ -34,8 +33,6 @@ $background_console_white: #eee;
 }
 
 
-
-
 /* General
 ============================================================================= */
 
@@ -73,6 +70,7 @@ body {
       background-color: $background_console_dark;
     }
   }
+
   &.btn-sso {
     &:hover, &:active, &:focus {
       background-color: $background_console_dark;
@@ -85,7 +83,7 @@ body {
 
 #mcp_wrapper {
 
-  .form-control{
+  .form-control {
     margin-bottom: 10px
   }
 
@@ -97,3 +95,17 @@ body {
     padding: 2% 2% 0 2%;
   }
 }
+
+.json-diff-header {
+  text-align: center;
+  
+  display: table;
+  width: 100%;
+  table-layout: fixed;
+  border-spacing: 10px;
+}
+
+.json-diff-item {
+  display: table-cell;
+  vertical-align: middle;
+}
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails.html b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails.html
index 9468f9f3fd..f5b81b16aa 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails.html
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails.html
@@ -17,15 +17,19 @@ specific language governing permissions and limitations
 under the License.
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
-  <wicket:panel>
+<wicket:panel>
 
-    <div style="padding-left: 1%;padding-right: 1%;text-align: center;">
-      <div class="float-left"><strong><span wicket:id="current"/></strong></div>
-      <a href="#" wicket:id="restore" wicket:message="title:restore"><i class="fa fa-history fa-2x"></i></a>
-      <div class="float-right"><strong><span wicket:id="previous"/></strong></div>
+    <div class="json-diff-header">
+        <div class="json-diff-item" wicket:id="beforeVersions"></div>
+        <div class="json-diff-item">
+            <a href="#" wicket:id="restore" wicket:message="title:restore">
+                <i class="fa fa-history fa-2x"></i>
+            </a>
+        </div>
+        <div class="json-diff-item" wicket:id="afterVersions"></div>
     </div>
 
-    <div wicket:id="content"></div>
+    <div wicket:id="jsonDiffPanel"></div>
 
-  </wicket:panel>
+</wicket:panel>
 </html>
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails.properties
index cec89d0704..6db8a35d72 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails.properties
@@ -14,6 +14,8 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-current=Current
+current=After
 previous=Previous
 restore=Restore
+beforeVersions=Versions of the previous
+afterVersions=Versions of the after
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_fr_CA.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_fr_CA.properties
index 6073f49651..6e98ebcb1c 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_fr_CA.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_fr_CA.properties
@@ -14,6 +14,9 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-current=Courant
-previous=Pr\u00e9c\u00e9dent
+current=Apr\uFFFDs
+previous=Pr\u00E9c\u00E9dent
 restore=Restaurer
+beforeVersions=Versions of the previous
+afterVersions=Versions of the after
+
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_it.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_it.properties
index 7af93d885b..7285b7a8c7 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_it.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_it.properties
@@ -14,6 +14,9 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-current=Attuale
-previous=Precedente
+current=Dopo
+previous=Prima
 restore=Ripristina
+beforeVersions=Versioni del precedente
+afterVersions=Versioni del successivo
+
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_ja.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_ja.properties
index cec89d0704..81df9a2f38 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_ja.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_ja.properties
@@ -14,6 +14,9 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-current=Current
-previous=Previous
-restore=Restore
+current=\u73FE\u6642\u70B9\u306E
+previous=\u524D
+restore=\u30EA\u30B9\u30C8\u30A2
+beforeVersions=\u4EE5\u524D\u306E\u30D0\u30FC\u30B8\u30E7\u30F3
+afterVersions=\u4EE5\u964D\u306E\u30D0\u30FC\u30B8\u30E7\u30F3
+
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_pt_BR.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_pt_BR.properties
index cec89d0704..8c4a3510f4 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_pt_BR.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_pt_BR.properties
@@ -14,6 +14,8 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-current=Current
-previous=Previous
-restore=Restore
+current=Ap\uFFFDs
+previous=Anterior
+restore=Restaurar
+beforeVersions=Vers\u00F5es do anterior
+afterVersions=Vers\u00F5es do depois
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_ru.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_ru.properties
index cec89d0704..9eb86bb5f9 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_ru.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/audit/AuditHistoryDetails_ru.properties
@@ -14,6 +14,8 @@
 # KIND, either express or implied.  See the License for the
 # specific language governing permissions and limitations
 # under the License.
-current=Current
-previous=Previous
-restore=Restore
+current=\u0422\u0435\u043A\u0443\u0449\u0438\u0439
+previous=\u041F\u0440\u0435\u0434\u044B\u0434\u0443\u0449\u0438\u0439
+restore=\u0412\u043E\u0441\u0441\u0442\u0430\u043D\u043E\u0432\u0438\u0442\u044C
+beforeVersions=\u0412\u0435\u0440\u0441\u0438\u0438 \u043F\u0440\u0435\u0434\u044B\u0434\u0443\u0449\u0435\u0433\u043E
+afterVersions=\u0412\u0435\u0440\u0441\u0438\u0438 \u043F\u043E\u0441\u043B\u0435
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/AuditElements.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/AuditElements.java
index d978bc7509..b3a336611a 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/AuditElements.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/AuditElements.java
@@ -30,24 +30,14 @@ public final class AuditElements implements BaseBean {
 
     public enum EventCategoryType {
 
-        LOGIC("LOGIC"),
-        WA("WA"),
-        TASK("TASK"),
-        PROPAGATION("PropagationTask"),
-        PULL("PullTask"),
-        PUSH("PushTask"),
-        CUSTOM("CUSTOM");
-
-        private final String value;
-
-        EventCategoryType(final String value) {
-            this.value = value;
-        }
-
-        @Override
-        public String toString() {
-            return value;
-        }
+        LOGIC,
+        WA,
+        TASK,
+        PROPAGATION,
+        PULL,
+        PUSH,
+        CUSTOM;
+
     }
 
     public enum Result {
diff --git a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/AuditLoggerName.java b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/AuditLoggerName.java
index 21e354ff22..5750fcd9dd 100644
--- a/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/AuditLoggerName.java
+++ b/common/idrepo/lib/src/main/java/org/apache/syncope/common/lib/types/AuditLoggerName.java
@@ -165,11 +165,11 @@ public class AuditLoggerName implements BaseBean {
             } else {
                 EventCategoryType type;
 
-                if (EventCategoryType.PROPAGATION.toString().equals(elements[0])) {
+                if (EventCategoryType.PROPAGATION.name().equals(elements[0])) {
                     type = EventCategoryType.PROPAGATION;
-                } else if (EventCategoryType.PULL.toString().equals(elements[0])) {
+                } else if (EventCategoryType.PULL.name().equals(elements[0])) {
                     type = EventCategoryType.PULL;
-                } else if (EventCategoryType.PUSH.toString().equals(elements[0])) {
+                } else if (EventCategoryType.PUSH.name().equals(elements[0])) {
                     type = EventCategoryType.PUSH;
                 } else {
                     try {
@@ -219,7 +219,7 @@ public class AuditLoggerName implements BaseBean {
 
         eventBuilder.append('[');
         if (type != null) {
-            eventBuilder.append(type.toString());
+            eventBuilder.append(type.name());
         }
         eventBuilder.append("]:[");
         if (StringUtils.isNotBlank(category)) {
diff --git a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AuditLogic.java b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AuditLogic.java
index 13ba023389..18d31d2664 100644
--- a/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AuditLogic.java
+++ b/core/idrepo/logic/src/main/java/org/apache/syncope/core/logic/AuditLogic.java
@@ -213,11 +213,11 @@ public class AuditLogic extends AbstractTransactionalLogic<AuditConfTO> {
                     EventCategory pullEventCategory = new EventCategory(EventCategoryType.PULL);
                     EventCategory pushEventCategory = new EventCategory(EventCategoryType.PUSH);
 
-                    propEventCategory.setCategory(anyTypeKind.name().toLowerCase());
+                    propEventCategory.setCategory(anyTypeKind.name());
                     propEventCategory.setSubcategory(resource.getKey());
 
-                    pullEventCategory.setCategory(anyTypeKind.name().toLowerCase());
-                    pushEventCategory.setCategory(anyTypeKind.name().toLowerCase());
+                    pullEventCategory.setCategory(anyTypeKind.name());
+                    pushEventCategory.setCategory(anyTypeKind.name());
                     pullEventCategory.setSubcategory(resource.getKey());
                     pushEventCategory.setSubcategory(resource.getKey());
 
diff --git a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java
index 93a54dfc35..7911d3529e 100644
--- a/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java
+++ b/core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/pushpull/AbstractPushResultHandler.java
@@ -300,13 +300,13 @@ public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHan
 
             boolean notificationsAvailable = notificationManager.notificationsAvailable(
                     AuditElements.EventCategoryType.PUSH,
-                    any.getType().getKind().name().toLowerCase(),
+                    any.getType().getKind().name(),
                     profile.getTask().getResource().getKey(),
                     operation);
             boolean auditRequested = auditManager.auditRequested(
                     AuthContextUtils.getUsername(),
                     AuditElements.EventCategoryType.PUSH,
-                    any.getType().getKind().name().toLowerCase(),
+                    any.getType().getKind().name(),
                     profile.getTask().getResource().getKey(),
                     operation);
 
@@ -488,7 +488,7 @@ public abstract class AbstractPushResultHandler extends AbstractSyncopeResultHan
                     jobMap.put(AfterHandlingEvent.JOBMAP_KEY, new AfterHandlingEvent(
                             AuthContextUtils.getWho(),
                             AuditElements.EventCategoryType.PUSH,
-                            any.getType().getKind().name().toLowerCase(),
+                            any.getType().getKind().name(),
                             profile.getTask().getResource().getKey(),
                             operation,
                             resultStatus,
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuditITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuditITCase.java
index be7bd75d32..d7a990f6aa 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuditITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/AuditITCase.java
@@ -46,15 +46,20 @@ import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
 import org.apache.commons.lang3.SerializationUtils;
 import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.common.lib.Attr;
 import org.apache.syncope.common.lib.SyncopeConstants;
 import org.apache.syncope.common.lib.audit.AuditEntry;
 import org.apache.syncope.common.lib.audit.EventCategory;
+import org.apache.syncope.common.lib.request.AttrPatch;
+import org.apache.syncope.common.lib.request.ResourceDR;
+import org.apache.syncope.common.lib.request.UserUR;
 import org.apache.syncope.common.lib.to.AnyObjectTO;
 import org.apache.syncope.common.lib.to.AuditConfTO;
 import org.apache.syncope.common.lib.to.ConnInstanceTO;
 import org.apache.syncope.common.lib.to.ConnPoolConfTO;
 import org.apache.syncope.common.lib.to.GroupTO;
 import org.apache.syncope.common.lib.to.PagedResult;
+import org.apache.syncope.common.lib.to.PullTaskTO;
 import org.apache.syncope.common.lib.to.PushTaskTO;
 import org.apache.syncope.common.lib.to.ResourceTO;
 import org.apache.syncope.common.lib.to.UserTO;
@@ -64,6 +69,7 @@ import org.apache.syncope.common.lib.types.AuditLoggerName;
 import org.apache.syncope.common.lib.types.ConnConfProperty;
 import org.apache.syncope.common.lib.types.ConnectorCapability;
 import org.apache.syncope.common.lib.types.MatchingRule;
+import org.apache.syncope.common.lib.types.ResourceDeassociationAction;
 import org.apache.syncope.common.lib.types.ResourceOperation;
 import org.apache.syncope.common.lib.types.UnmatchingRule;
 import org.apache.syncope.common.rest.api.beans.AnyQuery;
@@ -320,7 +326,7 @@ public class AuditITCase extends AbstractITCase {
 
         found = false;
         for (EventCategory eventCategoryTO : events) {
-            if (AnyTypeKind.USER.name().toLowerCase().equals(eventCategoryTO.getCategory())) {
+            if (AnyTypeKind.USER.name().equals(eventCategoryTO.getCategory())) {
                 if (RESOURCE_NAME_LDAP.equals(eventCategoryTO.getSubcategory())
                         && AuditElements.EventCategoryType.PULL == eventCategoryTO.getType()) {
 
@@ -333,7 +339,7 @@ public class AuditITCase extends AbstractITCase {
 
         found = false;
         for (EventCategory eventCategoryTO : events) {
-            if (AnyTypeKind.USER.name().toLowerCase().equals(eventCategoryTO.getCategory())) {
+            if (AnyTypeKind.USER.name().equals(eventCategoryTO.getCategory())) {
                 if (RESOURCE_NAME_CSV.equals(eventCategoryTO.getSubcategory())
                         && AuditElements.EventCategoryType.PROPAGATION == eventCategoryTO.getType()) {
 
@@ -455,7 +461,7 @@ public class AuditITCase extends AbstractITCase {
                     auditFilePath,
                     content -> content.contains(
                             "DEBUG Master.syncope.audit.[LOGIC]:[ResourceLogic]:[]:[update]:[SUCCESS]"
-                            + " - This is a static test message"),
+                                    + " - This is a static test message"),
                     10);
 
             // nothing expected in audit_for_Master_norewrite_file.log instead
@@ -463,7 +469,7 @@ public class AuditITCase extends AbstractITCase {
                     auditNoRewriteFilePath,
                     content -> !content.contains(
                             "DEBUG Master.syncope.audit.[LOGIC]:[ResourceLogic]:[]:[update]:[SUCCESS]"
-                            + " - This is a static test message"),
+                                    + " - This is a static test message"),
                     10);
         } catch (IOException e) {
             fail("Unable to read/write log files", e);
@@ -485,25 +491,25 @@ public class AuditITCase extends AbstractITCase {
     public void issueSYNCOPE1446() {
         AuditLoggerName createSuccess = new AuditLoggerName(
                 AuditElements.EventCategoryType.PROPAGATION,
-                AnyTypeKind.ANY_OBJECT.name().toLowerCase(),
+                AnyTypeKind.ANY_OBJECT.name(),
                 RESOURCE_NAME_DBSCRIPTED,
                 "create",
                 AuditElements.Result.SUCCESS);
         AuditLoggerName createFailure = new AuditLoggerName(
                 AuditElements.EventCategoryType.PROPAGATION,
-                AnyTypeKind.ANY_OBJECT.name().toLowerCase(),
+                AnyTypeKind.ANY_OBJECT.name(),
                 RESOURCE_NAME_DBSCRIPTED,
                 "create",
                 AuditElements.Result.FAILURE);
         AuditLoggerName updateSuccess = new AuditLoggerName(
                 AuditElements.EventCategoryType.PROPAGATION,
-                AnyTypeKind.ANY_OBJECT.name().toLowerCase(),
+                AnyTypeKind.ANY_OBJECT.name(),
                 RESOURCE_NAME_DBSCRIPTED,
                 "update",
                 AuditElements.Result.SUCCESS);
         AuditLoggerName updateFailure = new AuditLoggerName(
                 AuditElements.EventCategoryType.PROPAGATION,
-                AnyTypeKind.ANY_OBJECT.name().toLowerCase(),
+                AnyTypeKind.ANY_OBJECT.name(),
                 RESOURCE_NAME_DBSCRIPTED,
                 "update",
                 AuditElements.Result.FAILURE);
@@ -557,4 +563,72 @@ public class AuditITCase extends AbstractITCase {
             }
         }
     }
+
+    @Test
+    public void issueSYNCOPE1695() {
+        // add audit conf for pull
+        AUDIT_SERVICE.set(
+                buildAuditConf("syncope.audit.[PULL]:[USER]:[resource-ldap]:[matchingrule_update]:[SUCCESS]", true));
+        AUDIT_SERVICE.set(
+                buildAuditConf("syncope.audit.[PULL]:[USER]:[resource-ldap]:[unmatchingrule_assign]:[SUCCESS]", true));
+        AUDIT_SERVICE.set(
+                buildAuditConf("syncope.audit.[PULL]:[USER]:[resource-ldap]:[unmatchingrule_provision]:[SUCCESS]",
+                        true));
+        UserTO pullFromLDAP = null;
+        try {
+            // pull from resource-ldap -> generates an audit entry
+            PullTaskTO pullTaskTO = new PullTaskTO();
+            pullTaskTO.setPerformCreate(true);
+            pullTaskTO.setPerformUpdate(true);
+            pullTaskTO.getActions().add("LDAPMembershipPullActions");
+            pullTaskTO.setDestinationRealm(SyncopeConstants.ROOT_REALM);
+            pullTaskTO.setMatchingRule(MatchingRule.UPDATE);
+            pullTaskTO.setUnmatchingRule(UnmatchingRule.ASSIGN);
+            RECONCILIATION_SERVICE.pull(
+                    new ReconQuery.Builder(AnyTypeKind.USER.name(), RESOURCE_NAME_LDAP).fiql("uid==pullFromLDAP")
+                            .build(),
+                    pullTaskTO);
+            // update pullTaskTO -> another audit entry
+            pullFromLDAP = updateUser(new UserUR.Builder(USER_SERVICE.read("pullFromLDAP").getKey())
+                    .plainAttr(new AttrPatch.Builder(new Attr.Builder("ctype").value("abcdef").build()).build())
+                    .build()).getEntity();
+            // search by empty type and category events and get both events on testfromLDAP
+            assertEquals(2,
+                    AUDIT_SERVICE.search(new AuditQuery.Builder()
+                            .entityKey(pullFromLDAP.getKey())
+                            .page(1)
+                            .size(10)
+                            .events(List.of("create", "update", "matchingrule_update", "unmatchingrule_assign",
+                                    "unmatchingrule_provision"))
+                            .result(AuditElements.Result.SUCCESS)
+                            .build()).getTotalCount());
+        } finally {
+            if (pullFromLDAP != null) {
+                USER_SERVICE.deassociate(new ResourceDR.Builder()
+                        .key(pullFromLDAP.getKey())
+                        .resource(RESOURCE_NAME_LDAP)
+                        .action(ResourceDeassociationAction.UNLINK)
+                        .build());
+                USER_SERVICE.delete(pullFromLDAP.getKey());
+
+                // restore previous audit
+                AUDIT_SERVICE.set(
+                        buildAuditConf("syncope.audit.[PULL]:[USER]:[resource-ldap]:[matchingrule_update]:[SUCCESS]",
+                                false));
+                AUDIT_SERVICE.set(
+                        buildAuditConf("syncope.audit.[PULL]:[USER]:[resource-ldap]:[unmatchingrule_assign]:[SUCCESS]",
+                                false));
+                AUDIT_SERVICE.set(buildAuditConf(
+                        "syncope.audit.[PULL]:[USER]:[resource-ldap]:[unmatchingrule_provision]:[SUCCESS]",
+                        false));
+            }
+        }
+    }
+
+    private static AuditConfTO buildAuditConf(final String auditLoggerName, final boolean active) {
+        AuditConfTO auditConfTO = new AuditConfTO();
+        auditConfTO.setActive(active);
+        auditConfTO.setKey(auditLoggerName);
+        return auditConfTO;
+    }
 }
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PushTaskITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PushTaskITCase.java
index 842c3b46df..b650fda655 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PushTaskITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/core/PushTaskITCase.java
@@ -463,8 +463,8 @@ public class PushTaskITCase extends AbstractTaskITCase {
         // 2. Create notification
         NotificationTO notification = new NotificationTO();
         notification.setTraceLevel(TraceLevel.FAILURES);
-        notification.getEvents().add("[PushTask]:[group]:[resource-ldap]:[matchingrule_ignore]:[SUCCESS]");
-        notification.getEvents().add("[PushTask]:[group]:[resource-ldap]:[unmatchingrule_ignore]:[SUCCESS]");
+        notification.getEvents().add("[PUSH]:[GROUP]:[resource-ldap]:[matchingrule_ignore]:[SUCCESS]");
+        notification.getEvents().add("[PUSH]:[GROUP]:[resource-ldap]:[unmatchingrule_ignore]:[SUCCESS]");
 
         notification.getStaticRecipients().add("issueyncope648@syncope.apache.org");
         notification.setSelfAsRecipient(false);
diff --git a/src/main/asciidoc/reference-guide/concepts/notifications.adoc b/src/main/asciidoc/reference-guide/concepts/notifications.adoc
index 97a3af51a5..30723baf68 100644
--- a/src/main/asciidoc/reference-guide/concepts/notifications.adoc
+++ b/src/main/asciidoc/reference-guide/concepts/notifications.adoc
@@ -83,7 +83,7 @@ An event is uniquely identified by a string of the following form:
 
 Some samples:
 
-* `[PushTask]:[group]:[resource-db-scripted]:[matchingrule_deprovision]:[SUCCESS]` +
+* `[PUSH]:[GROUP]:[resource-db-scripted]:[matchingrule_deprovision]:[SUCCESS]` +
 successful Group <<provisioning-push,push>> to the external resource `resource-db-scripted`, when deprovisioning
 matching entities
 * `[LOGIC]:[RealmLogic]:[]:[create]:[FAILURE]` +