You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by fm...@apache.org on 2016/04/08 18:30:15 UTC

[4/4] syncope git commit: [SYNCOPE-745] Provides notification and email template management. Still missing tests and tasks per notification.

[SYNCOPE-745] Provides notification and email template management. Still missing tests and tasks per notification.


Project: http://git-wip-us.apache.org/repos/asf/syncope/repo
Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/8455cb96
Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/8455cb96
Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/8455cb96

Branch: refs/heads/master
Commit: 8455cb96be28ce8298dbfe41c073356710a06350
Parents: 91befa2
Author: fmartelli <fa...@gmail.com>
Authored: Fri Apr 8 18:29:45 2016 +0200
Committer: fmartelli <fa...@gmail.com>
Committed: Fri Apr 8 18:29:45 2016 +0200

----------------------------------------------------------------------
 .../console/approvals/ApprovalDetails.java      |   4 +-
 .../client/console/commons/Constants.java       |   2 +
 .../notifications/EventSelectionPanel.java      | 240 +++++++++
 .../notifications/LoggerCategoryPanel.java      | 509 +++++++++++++++++++
 .../notifications/MailTemplateContentModal.java | 117 +++++
 .../MailTemplateDirectoryPanel.java             | 222 ++++++++
 .../notifications/MailTemplateModal.java        |  72 +++
 .../NotificationDirectoryPanel.java             | 208 ++++++++
 .../notifications/NotificationHandler.java      | 131 +++++
 .../notifications/NotificationTasks.java        |  71 +++
 .../NotificationWizardBuilder.java              | 435 ++++++++++++++++
 .../notifications/SelectedEventsPanel.java      | 172 +++++++
 .../client/console/pages/Notifications.java     |  41 ++
 .../syncope/client/console/pages/Roles.java     |   4 +-
 .../syncope/client/console/panels/AnyPanel.java |   7 +-
 .../console/panels/AnyTypeClassesPanel.java     |   6 +-
 .../client/console/panels/AnyTypesPanel.java    |   6 +-
 .../panels/ParametersCreateModalPanel.java      |   4 +-
 .../panels/ParametersCreateWizardPanel.java     |   4 +-
 .../client/console/panels/ParametersPanel.java  |   5 +-
 .../console/panels/RelationshipTypesPanel.java  |   7 +-
 .../client/console/panels/ResourceModal.java    |   6 +-
 .../client/console/panels/SchemaTypePanel.java  |   8 +-
 .../console/panels/SecurityQuestionsPanel.java  |   7 +-
 .../console/rest/NotificationRestClient.java    |  39 ++
 .../tasks/NotificationTaskDirectoryPanel.java   | 208 ++++++++
 .../console/tasks/SchedTaskWizardBuilder.java   |   2 +-
 .../repeater/data/table/CollectionPanel.java    |  47 ++
 .../data/table/CollectionPropertyColumn.java    |  54 ++
 .../markup/html/form/AbstractMultiPanel.java    | 217 ++++++++
 .../wicket/markup/html/form/ActionLink.java     |   2 +
 .../markup/html/form/ActionLinksPanel.java      |  48 ++
 .../markup/html/form/MultiFieldPanel.java       | 197 +------
 .../wicket/markup/html/form/MultiPanel.java     |  37 ++
 .../wizards/AbstractModalPanelBuilder.java      |  16 +-
 .../console/wizards/AjaxWizardBuilder.java      |  29 +-
 .../client/console/wizards/WizardMgtPanel.java  |   6 +
 .../wizards/any/AnyObjectWizardBuilder.java     |   4 +-
 .../console/wizards/any/AnyWizardBuilder.java   |  12 +-
 .../console/wizards/any/GroupHandler.java       |  20 +-
 .../console/wizards/any/GroupWizardBuilder.java |   6 +-
 .../console/wizards/any/UserWizardBuilder.java  |   4 +-
 .../provision/ProvisionWizardBuilder.java       |   5 +-
 .../console/wizards/role/RoleHandler.java       |   5 +-
 .../console/wizards/role/RoleWizardBuilder.java |   5 +-
 .../META-INF/resources/css/syncopeConsole.css   |  38 +-
 .../notifications/EventSelectionPanel.html      | 125 +++++
 .../notifications/LoggerCategoryPanel.html      |  91 ++++
 .../notifications/MailTemplateContentModal.html |  54 ++
 .../MailTemplateDirectoryPanel.properties       |  20 +
 .../MailTemplateDirectoryPanel_it.properties    |  20 +
 .../MailTemplateDirectoryPanel_pt_BR.properties |  20 +
 .../notifications/MailTemplateModal.html        |  28 +
 .../NotificationDirectoryPanel.properties       |  20 +
 .../NotificationDirectoryPanel_it.properties    |  20 +
 .../NotificationDirectoryPanel_pt_BR.properties |  20 +
 .../notifications/NotificationTasks.html        |  24 +
 .../NotificationWizardBuilder$About.html        |  31 ++
 .../NotificationWizardBuilder$Abouts.html       |  28 +
 .../NotificationWizardBuilder$Abouts.properties |  24 +
 ...tificationWizardBuilder$Abouts_it.properties |  24 +
 ...icationWizardBuilder$Abouts_pt_BR.properties |  24 +
 .../NotificationWizardBuilder$Details.html      |  46 ++
 ...NotificationWizardBuilder$Details.properties |  23 +
 ...ificationWizardBuilder$Details_it.properties |  23 +
 ...cationWizardBuilder$Details_pt_BR.properties |  23 +
 .../NotificationWizardBuilder$Events.html       |  27 +
 .../NotificationWizardBuilder$Events.properties |  42 ++
 ...tificationWizardBuilder$Events_it.properties |  42 ++
 ...icationWizardBuilder$Events_pt_BR.properties |  42 ++
 .../NotificationWizardBuilder$Recipients.html   |  43 ++
 ...ificationWizardBuilder$Recipients.properties |  22 +
 ...cationWizardBuilder$Recipients_it.properties |  22 +
 ...ionWizardBuilder$Recipients_pt_BR.properties |  22 +
 .../notifications/SelectedEventsPanel.html      |  33 ++
 .../client/console/pages/Notifications.html     |  22 +-
 .../console/pages/Notifications.properties      |  46 ++
 .../console/pages/Notifications_it.properties   |  46 ++
 .../pages/Notifications_pt_BR.properties        |  46 ++
 .../PropagationTaskDirectoryPanel.properties    |   2 -
 .../PropagationTaskDirectoryPanel_it.properties |   2 -
 ...opagationTaskDirectoryPanel_pt_BR.properties |   2 -
 .../console/tasks/TaskDirectoryPanel.properties |   2 +
 .../tasks/TaskDirectoryPanel_it.properties      |   2 +
 .../tasks/TaskDirectoryPanel_pt_BR.properties   |   2 +
 .../repeater/data/table/CollectionPanel.html    |  25 +
 .../markup/html/form/ActionLinksPanel.html      |  10 +
 .../markup/html/form/MultiFieldPanel.properties |  22 -
 .../html/form/MultiFieldPanel_it.properties     |  22 -
 .../html/form/MultiFieldPanel_pt_BR.properties  |  22 -
 .../wicket/markup/html/form/MultiPanel.html     |  71 +++
 .../panels/CamelRoutesDirectoryPanel.java       |   6 +-
 92 files changed, 4267 insertions(+), 355 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/approvals/ApprovalDetails.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/approvals/ApprovalDetails.java b/client/console/src/main/java/org/apache/syncope/client/console/approvals/ApprovalDetails.java
index 7c954fd..c5787ed 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/approvals/ApprovalDetails.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/approvals/ApprovalDetails.java
@@ -40,8 +40,8 @@ public class ApprovalDetails extends MultilevelPanel.SecondLevel {
         final UserTO userTO = new UserRestClient().read(formTO.getUserKey());
         final List<String> anyTypeClasses = new AnyTypeRestClient().read(AnyTypeKind.USER.name()).getClasses();
 
-        final AjaxWizard<AnyHandler<UserTO>> wizard
-                = new UserWizardBuilder("wizard", userTO, anyTypeClasses, pageRef).build(AjaxWizard.Mode.READONLY);
+        final AjaxWizard<AnyHandler<UserTO>> wizard = new UserWizardBuilder(userTO, anyTypeClasses, pageRef).
+                build(AjaxWizard.Mode.READONLY);
 
         add(wizard);
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/commons/Constants.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/Constants.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/Constants.java
index def4c51..96d4ba6 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/commons/Constants.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/Constants.java
@@ -105,6 +105,8 @@ public final class Constants {
 
     public static final String PREF_NOTIFICATION_PAGINATOR_ROWS = "notification.paginator.rows";
 
+    public static final String PREF_MAIL_TEMPLATE_PAGINATOR_ROWS = "mail.template.paginator.rows";
+
     public static final String PREF_PROPAGATION_TASKS_PAGINATOR_ROWS = "proagationtasks.paginator.rows";
 
     public static final String PREF_TASK_EXECS_PAGINATOR_ROWS = "task.execs.paginator.rows";

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/notifications/EventSelectionPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/notifications/EventSelectionPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/notifications/EventSelectionPanel.java
new file mode 100644
index 0000000..38d2dd9
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/notifications/EventSelectionPanel.java
@@ -0,0 +1,240 @@
+/*
+ * 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.notifications;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.common.lib.to.EventCategoryTO;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.AuditLoggerName;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.form.AjaxFormChoiceComponentUpdatingBehavior;
+import org.apache.wicket.event.Broadcast;
+import org.apache.wicket.event.IEvent;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.Check;
+import org.apache.wicket.markup.html.form.CheckGroup;
+import org.apache.wicket.markup.html.form.CheckGroupSelector;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.ResourceModel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class EventSelectionPanel extends Panel {
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(EventSelectionPanel.class);
+
+    private static final long serialVersionUID = 752233163798301002L;
+
+    private final Set<String> selected = new HashSet<String>();
+
+    public EventSelectionPanel(
+            final String id, final EventCategoryTO eventCategoryTO, final IModel<List<String>> model) {
+        super(id);
+        setOutputMarkupId(true);
+
+        final List<String> events = getEvents(eventCategoryTO);
+
+        // needed to avoid model reset: model have to be managed into SelectedEventsPanel
+        selected.addAll(model.getObject());
+
+        final CheckGroup<String> successGroup = new CheckGroup<String>(
+                "successGroup",
+                selected);
+
+        successGroup.add(new AjaxFormChoiceComponentUpdatingBehavior() {
+
+            private static final long serialVersionUID = -151291731388673682L;
+
+            @Override
+            protected void onUpdate(final AjaxRequestTarget target) {
+
+                final Set<String> toBeRemoved = new HashSet<String>();
+                final Set<String> toBeAdded = new HashSet<String>();
+
+                for (String event : getEvents(eventCategoryTO)) {
+                    final String eventString = AuditLoggerName.buildEvent(
+                            eventCategoryTO.getType(),
+                            eventCategoryTO.getCategory(),
+                            eventCategoryTO.getSubcategory(),
+                            event,
+                            AuditElements.Result.SUCCESS);
+
+                    if (successGroup.getModelObject().contains(eventString)) {
+                        toBeAdded.add(eventString);
+                    } else {
+                        toBeRemoved.add(eventString);
+                    }
+                }
+
+                send(EventSelectionPanel.this.getPage(), Broadcast.BREADTH,
+                        new SelectedEventsPanel.EventSelectionChanged(target, toBeAdded, toBeRemoved));
+            }
+        });
+
+        successGroup.setVisible(!events.isEmpty());
+        add(successGroup);
+
+        add(new Label("successLabel", new ResourceModel("Success", "Success"))).setVisible(!events.isEmpty());
+
+        final CheckGroupSelector successSelector = new CheckGroupSelector("successSelector", successGroup);
+        successSelector.setVisible(!events.isEmpty());
+        add(successSelector);
+
+        final ListView<String> categoryView = new ListView<String>("categoryView", events) {
+
+            private static final long serialVersionUID = 4949588177564901031L;
+
+            @Override
+            protected void populateItem(final ListItem<String> item) {
+                final String subcategory = item.getModelObject();
+
+                item.add(new Label("subcategory", new ResourceModel(subcategory, subcategory)));
+            }
+        };
+        add(categoryView);
+
+        final ListView<String> successView = new ListView<String>("successView", events) {
+
+            private static final long serialVersionUID = 4949588177564901031L;
+
+            @Override
+            protected void populateItem(final ListItem<String> item) {
+                final String event = item.getModelObject();
+
+                final Check<String> successCheck = new Check<String>("successCheck",
+                        new Model<String>(AuditLoggerName.buildEvent(
+                                eventCategoryTO.getType(),
+                                eventCategoryTO.getCategory(),
+                                eventCategoryTO.getSubcategory(),
+                                event,
+                                AuditElements.Result.SUCCESS)),
+                        successGroup);
+                item.add(successCheck);
+            }
+        };
+        successGroup.add(successView);
+
+        final CheckGroup<String> failureGroup = new CheckGroup<String>("failureGroup", selected);
+
+        failureGroup.add(new AjaxFormChoiceComponentUpdatingBehavior() {
+
+            private static final long serialVersionUID = -151291731388673682L;
+
+            @Override
+            protected void onUpdate(final AjaxRequestTarget target) {
+
+                final Set<String> toBeRemoved = new HashSet<String>();
+                final Set<String> toBeAdded = new HashSet<String>();
+
+                for (String event : getEvents(eventCategoryTO)) {
+                    final String eventString = AuditLoggerName.buildEvent(
+                            eventCategoryTO.getType(),
+                            eventCategoryTO.getCategory(),
+                            eventCategoryTO.getSubcategory(),
+                            event,
+                            AuditElements.Result.FAILURE);
+
+                    if (failureGroup.getModelObject().contains(eventString)) {
+                        toBeAdded.add(eventString);
+                    } else {
+                        toBeRemoved.add(eventString);
+                    }
+                }
+
+                send(EventSelectionPanel.this.getPage(), Broadcast.BREADTH,
+                        new SelectedEventsPanel.EventSelectionChanged(target, toBeAdded, toBeRemoved));
+            }
+        });
+
+        failureGroup.setVisible(!events.isEmpty());
+        add(failureGroup);
+
+        add(new Label("failureLabel", new ResourceModel("Failure", "Failure"))).setVisible(!events.isEmpty());
+
+        final CheckGroupSelector failureSelector = new CheckGroupSelector("failureSelector", failureGroup);
+        failureSelector.setVisible(!events.isEmpty());
+        add(failureSelector);
+
+        final ListView<String> failureView = new ListView<String>("failureView", events) {
+
+            private static final long serialVersionUID = 4949588177564901031L;
+
+            @Override
+            protected void populateItem(final ListItem<String> item) {
+                final String event = item.getModelObject();
+
+                final Check<String> failureCheck = new Check<String>("failureCheck",
+                        new Model<String>(AuditLoggerName.buildEvent(
+                                eventCategoryTO.getType(),
+                                eventCategoryTO.getCategory(),
+                                eventCategoryTO.getSubcategory(),
+                                event,
+                                AuditElements.Result.FAILURE)),
+                        failureGroup);
+                item.add(failureCheck);
+            }
+        };
+        failureGroup.add(failureView);
+    }
+
+    private List<String> getEvents(final EventCategoryTO eventCategoryTO) {
+        final List<String> res;
+
+        res = eventCategoryTO.getEvents();
+
+        if (res.isEmpty()) {
+            if ((AuditElements.EventCategoryType.PROPAGATION == eventCategoryTO.getType()
+                    || AuditElements.EventCategoryType.PULL == eventCategoryTO.getType()
+                    || AuditElements.EventCategoryType.PUSH == eventCategoryTO.getType())
+                    && StringUtils.isEmpty(eventCategoryTO.getCategory())) {
+                res.add(eventCategoryTO.getType().toString());
+            } else if (AuditElements.EventCategoryType.TASK == eventCategoryTO.getType()
+                    && StringUtils.isNotEmpty(eventCategoryTO.getCategory())) {
+                res.add(eventCategoryTO.getCategory());
+            }
+        } else {
+            Collections.sort(res);
+        }
+
+        return res;
+    }
+
+    /**
+     * To be extended in order to add actions on events.
+     *
+     * @param event event.
+     */
+    protected abstract void onEventAction(final IEvent<?> event);
+
+    @Override
+    public void onEvent(final IEvent<?> event) {
+        onEventAction(event);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/notifications/LoggerCategoryPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/notifications/LoggerCategoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/notifications/LoggerCategoryPanel.java
new file mode 100644
index 0000000..d1715f1
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/notifications/LoggerCategoryPanel.java
@@ -0,0 +1,509 @@
+/*
+ * 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.notifications;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.commons.collections4.IterableUtils;
+import org.apache.commons.collections4.Predicate;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.notifications.SelectedEventsPanel.EventSelectionChanged;
+import org.apache.syncope.client.console.notifications.SelectedEventsPanel.InspectSelectedEvent;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionLinksPanel;
+import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel;
+import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel;
+import org.apache.syncope.common.lib.to.EventCategoryTO;
+import org.apache.syncope.common.lib.types.AuditElements;
+import org.apache.syncope.common.lib.types.AuditElements.EventCategoryType;
+import org.apache.syncope.common.lib.types.AuditLoggerName;
+import org.apache.syncope.common.lib.types.StandardEntitlement;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
+import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
+import org.apache.wicket.event.Broadcast;
+import org.apache.wicket.event.IEvent;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+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.apache.wicket.model.PropertyModel;
+import org.apache.wicket.model.ResourceModel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public abstract class LoggerCategoryPanel extends Panel {
+
+    /**
+     * Logger.
+     */
+    private static final Logger LOG = LoggerFactory.getLogger(LoggerCategoryPanel.class);
+
+    private static final long serialVersionUID = 6429053774964787734L;
+
+    private final List<EventCategoryTO> eventCategoryTOs;
+
+    private final EventCategoryTO eventCategoryTO = new EventCategoryTO();
+
+    private final WebMarkupContainer categoryContainer;
+
+    private final WebMarkupContainer eventsContainer;
+
+    private final SelectedEventsPanel selectedEventsPanel;
+
+    private final AjaxDropDownChoicePanel<EventCategoryType> type;
+
+    private final AjaxDropDownChoicePanel<String> category;
+
+    private final AjaxDropDownChoicePanel<String> subcategory;
+
+    private final AjaxTextFieldPanel custom;
+
+    private final ActionLinksPanel actionPanel;
+
+    private final IModel<List<String>> model;
+
+    public LoggerCategoryPanel(
+            final String id,
+            final List<EventCategoryTO> eventCategoryTOs,
+            final IModel<List<String>> model,
+            final PageReference pageReference,
+            final String pageId) {
+        super(id);
+
+        this.model = model;
+        selectedEventsPanel = new SelectedEventsPanel("selectedEventsPanel", model);
+        add(selectedEventsPanel);
+
+        this.eventCategoryTOs = eventCategoryTOs;
+
+        categoryContainer = new WebMarkupContainer("categoryContainer");
+        categoryContainer.setOutputMarkupId(true);
+        add(categoryContainer);
+
+        eventsContainer = new WebMarkupContainer("eventsContainer");
+        eventsContainer.setOutputMarkupId(true);
+        add(eventsContainer);
+
+        authorizeList();
+        authorizeChanges();
+
+        type = new AjaxDropDownChoicePanel<EventCategoryType>(
+                "type",
+                "type",
+                new PropertyModel<EventCategoryType>(eventCategoryTO, "type"),
+                false);
+        type.setChoices(Arrays.asList(EventCategoryType.values()));
+        type.setStyleSheet("ui-widget-content ui-corner-all");
+        type.setChoiceRenderer(new IChoiceRenderer<EventCategoryType>() {
+
+            private static final long serialVersionUID = 2317134950949778735L;
+
+            @Override
+            public String getDisplayValue(final EventCategoryType eventCategoryType) {
+                return eventCategoryType.name();
+            }
+
+            @Override
+            public String getIdValue(final EventCategoryType eventCategoryType, final int i) {
+                return eventCategoryType.name();
+            }
+
+            @Override
+            public EventCategoryType getObject(
+                    final String id, final IModel<? extends List<? extends EventCategoryType>> choices) {
+                return IterableUtils.find(choices.getObject(), new Predicate<EventCategoryType>() {
+
+                    @Override
+                    public boolean evaluate(final EventCategoryType object) {
+                        return object.name().equals(id);
+                    }
+                });
+            }
+        });
+        categoryContainer.add(type);
+
+        type.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+            private static final long serialVersionUID = -1107858522700306810L;
+
+            @Override
+            protected void onUpdate(final AjaxRequestTarget target) {
+                send(LoggerCategoryPanel.this, Broadcast.EXACT, new ChangeCategoryEvent(target, type));
+            }
+        });
+
+        category = new AjaxDropDownChoicePanel<String>(
+                "category",
+                "category",
+                new PropertyModel<String>(eventCategoryTO, "category"),
+                false);
+        category.setChoices(filter(eventCategoryTOs, type.getModelObject()));
+//        category.setStyleSheet("ui-widget-content ui-corner-all");
+        categoryContainer.add(category);
+
+        category.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+            private static final long serialVersionUID = -1107858522700306811L;
+
+            @Override
+            protected void onUpdate(final AjaxRequestTarget target) {
+                send(LoggerCategoryPanel.this, Broadcast.EXACT, new ChangeCategoryEvent(target, category));
+            }
+        });
+
+        subcategory = new AjaxDropDownChoicePanel<String>(
+                "subcategory",
+                "subcategory",
+                new PropertyModel<String>(eventCategoryTO, "subcategory"),
+                false);
+        subcategory.setChoices(filter(eventCategoryTOs, type.getModelObject(), category.getModelObject()));
+//        subcategory.setStyleSheet("ui-widget-content ui-corner-all");
+        categoryContainer.add(subcategory);
+
+        subcategory.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+            private static final long serialVersionUID = -1107858522700306812L;
+
+            @Override
+            protected void onUpdate(final AjaxRequestTarget target) {
+                send(LoggerCategoryPanel.this, Broadcast.EXACT, new ChangeCategoryEvent(target, subcategory));
+            }
+        });
+
+        categoryContainer.add(new Label("customLabel", new ResourceModel("custom", "custom")).setVisible(false));
+
+        custom = new AjaxTextFieldPanel("custom", "custom", new Model<String>(null));
+//        custom.setStyleSheet("ui-widget-content ui-corner-all short_fixedsize");
+        custom.setVisible(false);
+        custom.setEnabled(false);
+
+        categoryContainer.add(custom.hideLabel());
+
+        actionPanel = ActionLinksPanel.<EventCategoryTO>builder().
+                add(new ActionLink<EventCategoryTO>() {
+
+                    private static final long serialVersionUID = -3722207913631435501L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target, final EventCategoryTO modelObject) {
+
+                        if (StringUtils.isNotBlank(custom.getModelObject())) {
+                            final Map.Entry<EventCategoryTO, AuditElements.Result> parsed = AuditLoggerName.
+                                    parseEventCategory(
+                                            custom.getModelObject());
+
+                            final String eventString = AuditLoggerName.buildEvent(
+                                    parsed.getKey().getType(),
+                                    null,
+                                    null,
+                                    parsed.getKey().getEvents().isEmpty()
+                                            ? StringUtils.EMPTY : parsed.getKey().getEvents().iterator().next(),
+                                    parsed.getValue());
+
+                            custom.setModelObject(StringUtils.EMPTY);
+                            send(LoggerCategoryPanel.this.getPage(), Broadcast.BREADTH, new EventSelectionChanged(
+                                    target,
+                                    Collections.<String>singleton(eventString),
+                                    Collections.<String>emptySet()));
+                            target.add(categoryContainer);
+                        }
+                    }
+                }, ActionLink.ActionType.CREATE, StandardEntitlement.NOTIFICATION_UPDATE, true).
+                add(new ActionLink<EventCategoryTO>() {
+
+                    private static final long serialVersionUID = -3722207913631435501L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target, final EventCategoryTO modelObject) {
+                        if (StringUtils.isNotBlank(custom.getModelObject())) {
+                            final Map.Entry<EventCategoryTO, AuditElements.Result> parsed = AuditLoggerName.
+                                    parseEventCategory(
+                                            custom.getModelObject());
+
+                            final String eventString = AuditLoggerName.buildEvent(
+                                    parsed.getKey().getType(),
+                                    null,
+                                    null,
+                                    parsed.getKey().getEvents().isEmpty()
+                                            ? StringUtils.EMPTY : parsed.getKey().getEvents().iterator().next(),
+                                    parsed.getValue());
+
+                            custom.setModelObject(StringUtils.EMPTY);
+                            send(LoggerCategoryPanel.this.getPage(), Broadcast.BREADTH, new EventSelectionChanged(
+                                    target,
+                                    Collections.<String>singleton(eventString),
+                                    Collections.<String>emptySet()));
+                            target.add(categoryContainer);
+                        }
+                    }
+                }, ActionLink.ActionType.CREATE, pageId, true).
+                add(new ActionLink<EventCategoryTO>() {
+
+                    private static final long serialVersionUID = -3722207913631435521L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target, final EventCategoryTO modelObject) {
+                        if (StringUtils.isNotBlank(custom.getModelObject())) {
+                            final Map.Entry<EventCategoryTO, AuditElements.Result> parsed = AuditLoggerName.
+                                    parseEventCategory(
+                                            custom.getModelObject());
+
+                            final String eventString = AuditLoggerName.buildEvent(
+                                    parsed.getKey().getType(),
+                                    null,
+                                    null,
+                                    parsed.getKey().getEvents().isEmpty()
+                                            ? StringUtils.EMPTY : parsed.getKey().getEvents().iterator().next(),
+                                    parsed.getValue());
+
+                            custom.setModelObject(StringUtils.EMPTY);
+                            send(LoggerCategoryPanel.this.getPage(), Broadcast.BREADTH, new EventSelectionChanged(
+                                    target,
+                                    Collections.<String>emptySet(),
+                                    Collections.<String>singleton(eventString)));
+                            target.add(categoryContainer);
+                        }
+                    }
+                }, ActionLink.ActionType.DELETE, pageId, true).build("customActions");
+
+        //, new Model(), pageReference);
+        categoryContainer.add(actionPanel);
+
+        actionPanel.setVisible(false);
+        actionPanel.setEnabled(false);
+
+        eventsContainer.add(new EventSelectionPanel("eventsPanel", eventCategoryTO, model) {
+
+            private static final long serialVersionUID = 3513194801190026082L;
+
+            @Override
+            protected void onEventAction(final IEvent<?> event) {
+                LoggerCategoryPanel.this.onEventAction(event);
+            }
+        });
+    }
+
+    private List<String> filter(
+            final List<EventCategoryTO> eventCategoryTOs, final EventCategoryType type) {
+        final Set<String> res = new HashSet<String>();
+
+        for (EventCategoryTO eventCategory : eventCategoryTOs) {
+            if (type == eventCategory.getType() && StringUtils.isNotEmpty(eventCategory.getCategory())) {
+                res.add(eventCategory.getCategory());
+            }
+        }
+
+        final List<String> filtered = new ArrayList<String>(res);
+        Collections.sort(filtered);
+        return filtered;
+    }
+
+    private List<String> filter(
+            final List<EventCategoryTO> eventCategoryTOs, final EventCategoryType type, final String category) {
+        final Set<String> res = new HashSet<String>();
+
+        for (EventCategoryTO eventCategory : eventCategoryTOs) {
+            if (type == eventCategory.getType() && StringUtils.equals(category, eventCategory.getCategory())
+                    && StringUtils.isNotEmpty(eventCategory.getSubcategory())) {
+                res.add(eventCategory.getSubcategory());
+            }
+        }
+
+        final List<String> filtered = new ArrayList<String>(res);
+        Collections.sort(filtered);
+        return filtered;
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void onEvent(final IEvent<?> event) {
+        if (event.getPayload() instanceof ChangeCategoryEvent) {
+            // update objects ....
+            eventCategoryTO.getEvents().clear();
+
+            final ChangeCategoryEvent change = (ChangeCategoryEvent) event.getPayload();
+
+            final Panel changedPanel = change.getChangedPanel();
+            if ("type".equals(changedPanel.getId())) {
+                eventCategoryTO.setType(type.getModelObject());
+                eventCategoryTO.setCategory(null);
+                eventCategoryTO.setSubcategory(null);
+
+                if (type.getModelObject() == EventCategoryType.CUSTOM) {
+                    category.setChoices(Collections.<String>emptyList());
+                    subcategory.setChoices(Collections.<String>emptyList());
+                    category.setEnabled(false);
+                    subcategory.setEnabled(false);
+                    custom.setVisible(true);
+                    custom.setEnabled(true);
+                    actionPanel.setVisible(true);
+                    actionPanel.setEnabled(true);
+
+                } else {
+                    category.setChoices(filter(eventCategoryTOs, type.getModelObject()));
+                    subcategory.setChoices(Collections.<String>emptyList());
+                    category.setEnabled(true);
+                    subcategory.setEnabled(true);
+                    custom.setVisible(false);
+                    custom.setEnabled(false);
+                    actionPanel.setVisible(false);
+                    actionPanel.setEnabled(false);
+                }
+                change.getTarget().add(categoryContainer);
+            } else if ("category".equals(changedPanel.getId())) {
+                subcategory.setChoices(filter(eventCategoryTOs, type.getModelObject(), category.getModelObject()));
+                eventCategoryTO.setCategory(category.getModelObject());
+                eventCategoryTO.setSubcategory(null);
+                change.getTarget().add(categoryContainer);
+            } else {
+                eventCategoryTO.setSubcategory(subcategory.getModelObject());
+            }
+
+            updateEventsContainer(change.getTarget());
+        } else if (event.getPayload() instanceof InspectSelectedEvent) {
+            // update objects ....
+            eventCategoryTO.getEvents().clear();
+
+            final InspectSelectedEvent inspectSelectedEvent = (InspectSelectedEvent) event.getPayload();
+
+            final Map.Entry<EventCategoryTO, AuditElements.Result> categoryEvent = AuditLoggerName.parseEventCategory(
+                    inspectSelectedEvent.getEvent());
+
+            eventCategoryTO.setType(categoryEvent.getKey().getType());
+            category.setChoices(filter(eventCategoryTOs, type.getModelObject()));
+
+            eventCategoryTO.setCategory(categoryEvent.getKey().getCategory());
+            subcategory.setChoices(filter(eventCategoryTOs, type.getModelObject(), category.getModelObject()));
+
+            eventCategoryTO.setSubcategory(categoryEvent.getKey().getSubcategory());
+
+            if (categoryEvent.getKey().getType() == EventCategoryType.CUSTOM) {
+                custom.setModelObject(AuditLoggerName.buildEvent(
+                        categoryEvent.getKey().getType(),
+                        categoryEvent.getKey().getCategory(),
+                        categoryEvent.getKey().getSubcategory(),
+                        categoryEvent.getKey().getEvents().isEmpty()
+                                ? StringUtils.EMPTY : categoryEvent.getKey().getEvents().iterator().next(),
+                        categoryEvent.getValue()));
+
+                category.setEnabled(false);
+                subcategory.setEnabled(false);
+                custom.setVisible(true);
+                custom.setEnabled(true);
+                actionPanel.setVisible(true);
+                actionPanel.setEnabled(true);
+            } else {
+                category.setEnabled(true);
+                subcategory.setEnabled(true);
+                custom.setVisible(false);
+                custom.setEnabled(false);
+                actionPanel.setVisible(false);
+                actionPanel.setEnabled(false);
+            }
+
+            inspectSelectedEvent.getTarget().add(categoryContainer);
+            updateEventsContainer(inspectSelectedEvent.getTarget());
+        }
+    }
+
+    private void setEvents() {
+        final Iterator<EventCategoryTO> itor = eventCategoryTOs.iterator();
+        while (itor.hasNext() && eventCategoryTO.getEvents().isEmpty()) {
+            final EventCategoryTO eventCategory = itor.next();
+            if (eventCategory.getType() == eventCategoryTO.getType()
+                    && StringUtils.equals(eventCategory.getCategory(), eventCategoryTO.getCategory())
+                    && StringUtils.equals(eventCategory.getSubcategory(), eventCategoryTO.getSubcategory())) {
+                eventCategoryTO.getEvents().addAll(eventCategory.getEvents());
+
+            }
+        }
+    }
+
+    private class ChangeCategoryEvent {
+
+        private final AjaxRequestTarget target;
+
+        private final Panel changedPanel;
+
+        ChangeCategoryEvent(final AjaxRequestTarget target, final Panel changedPanel) {
+            this.target = target;
+            this.changedPanel = changedPanel;
+        }
+
+        public AjaxRequestTarget getTarget() {
+            return target;
+        }
+
+        public Panel getChangedPanel() {
+            return changedPanel;
+        }
+    }
+
+    /**
+     * To be extended in order to add actions on events.
+     *
+     * @param event event.
+     */
+    protected void onEventAction(final IEvent<?> event) {
+        // nothing by default
+    }
+
+    private void authorizeList() {
+        for (String role : getListRoles()) {
+            MetaDataRoleAuthorizationStrategy.authorize(selectedEventsPanel, RENDER, role);
+        }
+    }
+
+    private void authorizeChanges() {
+        for (String role : getChangeRoles()) {
+            MetaDataRoleAuthorizationStrategy.authorize(categoryContainer, RENDER, role);
+            MetaDataRoleAuthorizationStrategy.authorize(eventsContainer, RENDER, role);
+        }
+    }
+
+    private void updateEventsContainer(final AjaxRequestTarget target) {
+        setEvents();
+
+        eventsContainer.addOrReplace(new EventSelectionPanel("eventsPanel", eventCategoryTO, model) {
+
+            private static final long serialVersionUID = 3513194801190026082L;
+
+            @Override
+            public void onEventAction(final IEvent<?> event) {
+                LoggerCategoryPanel.this.onEventAction(event);
+            }
+        });
+        target.add(eventsContainer);
+    }
+
+    protected abstract String[] getListRoles();
+
+    protected abstract String[] getChangeRoles();
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/notifications/MailTemplateContentModal.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/notifications/MailTemplateContentModal.java b/client/console/src/main/java/org/apache/syncope/client/console/notifications/MailTemplateContentModal.java
new file mode 100644
index 0000000..9603446
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/notifications/MailTemplateContentModal.java
@@ -0,0 +1,117 @@
+/*
+ * 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.notifications;
+
+import java.io.Serializable;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.panels.AbstractModalPanel;
+import org.apache.syncope.client.console.rest.NotificationRestClient;
+import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
+import org.apache.syncope.common.lib.AbstractBaseBean;
+import org.apache.syncope.common.lib.types.MailTemplateFormat;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.OnLoadHeaderItem;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.form.TextArea;
+import org.apache.wicket.model.PropertyModel;
+
+public class MailTemplateContentModal extends AbstractModalPanel<Serializable> {
+
+    private static final long serialVersionUID = 2053048734388383021L;
+
+    private final MailTemplateContentTO content;
+
+    public MailTemplateContentModal(
+            final BaseModal<Serializable> modal,
+            final MailTemplateContentTO content,
+            final PageReference pageRef) {
+        super(modal, pageRef);
+        this.content = content;
+
+        TextArea<String> templateDefArea = new TextArea<>("template", new PropertyModel<String>(content, "content"));
+        templateDefArea.setMarkupId("template").setOutputMarkupPlaceholderTag(true);
+        add(templateDefArea);
+    }
+
+    @Override
+    public void renderHead(final IHeaderResponse response) {
+        super.renderHead(response);
+        response.render(OnLoadHeaderItem.forScript(
+                "CodeMirror.fromTextArea(document.getElementById('template'), {"
+                + "  lineNumbers: true, "
+                + "  autoRefresh: true"
+                + "}).on('change', updateTextArea);"));
+    }
+
+    @Override
+    public MailTemplateContentTO getItem() {
+        return this.content;
+    }
+
+    @Override
+    public void onSubmit(final AjaxRequestTarget target, final Form<?> form) {
+        try {
+            new NotificationRestClient().updateTemplateFormat(
+                    content.getKey(), content.getContent(), content.getFormat());
+            info(getString(Constants.OPERATION_SUCCEEDED));
+            modal.show(false);
+            modal.close(target);
+        } catch (Exception e) {
+            LOG.error("While updating template for {}", content.getKey(), e);
+            error(StringUtils.isBlank(e.getMessage()) ? e.getClass().getName() : e.getMessage());
+        }
+        SyncopeConsoleSession.get().getNotificationPanel().refresh(target);
+    }
+
+    public static class MailTemplateContentTO extends AbstractBaseBean {
+
+        private static final long serialVersionUID = -1756961687134322845L;
+
+        private final String key;
+
+        private String content;
+
+        private final MailTemplateFormat format;
+
+        public MailTemplateContentTO(final String key, final MailTemplateFormat format) {
+            this.key = key;
+            this.format = format;
+        }
+
+        public String getKey() {
+            return key;
+        }
+
+        public String getContent() {
+            return content;
+        }
+
+        public void setContent(final String content) {
+            this.content = content;
+        }
+
+        public MailTemplateFormat getFormat() {
+            return format;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/notifications/MailTemplateDirectoryPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/notifications/MailTemplateDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/notifications/MailTemplateDirectoryPanel.java
new file mode 100644
index 0000000..1e6cbe2
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/notifications/MailTemplateDirectoryPanel.java
@@ -0,0 +1,222 @@
+/*
+ * 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.notifications;
+
+import static org.apache.wicket.Component.ENABLE;
+
+import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.commons.DirectoryDataProvider;
+import org.apache.syncope.client.console.commons.SortableDataProviderComparator;
+import org.apache.syncope.client.console.notifications.MailTemplateDirectoryPanel.MailTemplateProvider;
+import org.apache.syncope.client.console.panels.DirectoryPanel;
+import org.apache.syncope.client.console.panels.ModalPanel;
+import org.apache.syncope.client.console.rest.NotificationRestClient;
+import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.ActionColumn;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionLinksPanel;
+import org.apache.syncope.client.console.wizards.AbstractModalPanelBuilder;
+import org.apache.syncope.client.console.wizards.AjaxWizard;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.MailTemplateTO;
+import org.apache.syncope.common.lib.types.MailTemplateFormat;
+import org.apache.syncope.common.lib.types.StandardEntitlement;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
+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.model.AbstractReadOnlyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.model.StringResourceModel;
+
+public class MailTemplateDirectoryPanel
+        extends DirectoryPanel<MailTemplateTO, MailTemplateTO, MailTemplateProvider, NotificationRestClient> {
+
+    private static final long serialVersionUID = -3789392431954221446L;
+
+    public MailTemplateDirectoryPanel(final String id, final PageReference pageReference) {
+        super(id, pageReference, true);
+        disableCheckBoxes();
+
+        modal.size(Modal.Size.Small);
+        modal.addSumbitButton();
+        setFooterVisibility(true);
+
+        utilityModal.size(Modal.Size.Large);
+        utilityModal.addSumbitButton();
+
+        addNewItemPanelBuilder(new AbstractModalPanelBuilder<MailTemplateTO>(new MailTemplateTO(), pageRef) {
+
+            private static final long serialVersionUID = 1995192603527154740L;
+
+            @Override
+            public ModalPanel<MailTemplateTO> build(final String id, final int index, final AjaxWizard.Mode mode) {
+                return new MailTemplateModal(modal, new MailTemplateTO(), pageReference);
+            }
+        }, true);
+        restClient = new NotificationRestClient();
+
+        initResultTable();
+
+        MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, ENABLE, StandardEntitlement.MAIL_TEMPLATE_CREATE);
+    }
+
+    @Override
+    protected List<IColumn<MailTemplateTO, String>> getColumns() {
+
+        final List<IColumn<MailTemplateTO, String>> columns = new ArrayList<IColumn<MailTemplateTO, String>>();
+        columns.add(new PropertyColumn<MailTemplateTO, String>(
+                new StringResourceModel("key", this, null), "key", "key"));
+
+        columns.add(new ActionColumn<MailTemplateTO, String>(new ResourceModel("actions", "")) {
+
+            private static final long serialVersionUID = -3503023501954863131L;
+
+            @Override
+            public ActionLinksPanel<MailTemplateTO> getActions(
+                    final String componentId, final IModel<MailTemplateTO> model) {
+
+                final ActionLinksPanel.Builder<MailTemplateTO> panel = ActionLinksPanel.builder();
+
+                panel.add(new ActionLink<MailTemplateTO>() {
+
+                    private static final long serialVersionUID = -7978723352517770645L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target, final MailTemplateTO ignore) {
+                        MailTemplateContentModal.MailTemplateContentTO content
+                                = new MailTemplateContentModal.MailTemplateContentTO(
+                                        model.getObject().getKey(), MailTemplateFormat.HTML);
+                        content.setContent(
+                                restClient.readTemplateFormat(model.getObject().getKey(), MailTemplateFormat.HTML));
+
+                        utilityModal.header(new ResourceModel("mail.template.html", "HTML Content"));
+                        utilityModal.setContent(new MailTemplateContentModal(utilityModal, content, pageRef));
+                        utilityModal.show(true);
+                        target.add(utilityModal);
+                    }
+                }, ActionLink.ActionType.HTML_EDIT, StandardEntitlement.MAIL_TEMPLATE_UPDATE);
+
+                panel.add(new ActionLink<MailTemplateTO>() {
+
+                    private static final long serialVersionUID = -7978723352517770645L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target, final MailTemplateTO ignore) {
+                        MailTemplateContentModal.MailTemplateContentTO content
+                                = new MailTemplateContentModal.MailTemplateContentTO(
+                                        model.getObject().getKey(), MailTemplateFormat.TEXT);
+                        content.setContent(
+                                restClient.readTemplateFormat(model.getObject().getKey(), MailTemplateFormat.TEXT));
+                        
+                        utilityModal.setFormModel(content);
+                        utilityModal.header(new ResourceModel("mail.template.text", "TEXT Content"));
+                        utilityModal.setContent(new MailTemplateContentModal(utilityModal, content, pageRef));
+                        utilityModal.show(true);
+                        target.add(utilityModal);
+                    }
+                }, ActionLink.ActionType.TEXT_EDIT, StandardEntitlement.MAIL_TEMPLATE_UPDATE);
+
+                panel.add(new ActionLink<MailTemplateTO>() {
+
+                    private static final long serialVersionUID = -3722207913631435501L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target, final MailTemplateTO ignore) {
+                        try {
+                            restClient.deleteTemplate(model.getObject().getKey());
+                            info(getString(Constants.OPERATION_SUCCEEDED));
+                            target.add(container);
+                        } catch (SyncopeClientException e) {
+                            LOG.error("While deleting object {}", model.getObject().getKey(), e);
+                            error(StringUtils.isBlank(e.getMessage()) ? e.getClass().getName() : e.getMessage());
+                        }
+                        SyncopeConsoleSession.get().getNotificationPanel().refresh(target);
+                    }
+                }, ActionLink.ActionType.DELETE, StandardEntitlement.MAIL_TEMPLATE_DELETE);
+
+                return panel.build(componentId);
+            }
+        });
+        return columns;
+    }
+
+    @Override
+    protected MailTemplateProvider dataProvider() {
+        return new MailTemplateProvider(rows);
+    }
+
+    @Override
+    protected String paginatorRowsKey() {
+        return Constants.PREF_MAIL_TEMPLATE_PAGINATOR_ROWS;
+    }
+
+    @Override
+    protected Collection<ActionLink.ActionType> getBulkActions() {
+        return Collections.<ActionLink.ActionType>emptyList();
+    }
+
+    public class MailTemplateProvider extends DirectoryDataProvider<MailTemplateTO> {
+
+        private static final long serialVersionUID = -276043813563988590L;
+
+        private final SortableDataProviderComparator<MailTemplateTO> comparator;
+
+        public MailTemplateProvider(final int paginatorRows) {
+            super(paginatorRows);
+            setSort("key", SortOrder.ASCENDING);
+            comparator = new SortableDataProviderComparator<MailTemplateTO>(this);
+        }
+
+        @Override
+        public Iterator<MailTemplateTO> iterator(final long first, final long count) {
+            final List<MailTemplateTO> list = restClient.getAllAvailableTemplates();
+            Collections.sort(list, comparator);
+            return list.subList((int) first, (int) first + (int) count).iterator();
+        }
+
+        @Override
+        public long size() {
+            return restClient.getAllAvailableTemplates().size();
+        }
+
+        @Override
+        public IModel<MailTemplateTO> model(final MailTemplateTO mailTemplateTO) {
+            return new AbstractReadOnlyModel<MailTemplateTO>() {
+
+                private static final long serialVersionUID = 774694801558497248L;
+
+                @Override
+                public MailTemplateTO getObject() {
+                    return mailTemplateTO;
+                }
+            };
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/notifications/MailTemplateModal.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/notifications/MailTemplateModal.java b/client/console/src/main/java/org/apache/syncope/client/console/notifications/MailTemplateModal.java
new file mode 100644
index 0000000..69ec7aa
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/notifications/MailTemplateModal.java
@@ -0,0 +1,72 @@
+/*
+ * 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.notifications;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.panels.AbstractModalPanel;
+import org.apache.syncope.client.console.rest.NotificationRestClient;
+import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
+import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.MailTemplateTO;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.model.PropertyModel;
+
+public class MailTemplateModal extends AbstractModalPanel<MailTemplateTO> {
+
+    private static final long serialVersionUID = 2053048734388383021L;
+
+    private final MailTemplateTO mailTemplateTO;
+
+    public MailTemplateModal(
+            final BaseModal<MailTemplateTO> modal,
+            final MailTemplateTO mailTemplateTO,
+            final PageReference pageRef) {
+        super(modal, pageRef);
+        this.mailTemplateTO = mailTemplateTO;
+
+        final AjaxTextFieldPanel key
+                = new AjaxTextFieldPanel("key", "key", new PropertyModel<String>(mailTemplateTO, "key"), false);
+        key.setOutputMarkupPlaceholderTag(true);
+        add(key.setRenderBodyOnly(true));
+    }
+
+    @Override
+    public MailTemplateTO getItem() {
+        return this.mailTemplateTO;
+    }
+
+    @Override
+    public void onSubmit(final AjaxRequestTarget target, final Form<?> form) {
+        try {
+            new NotificationRestClient().createTemplate(mailTemplateTO);
+            info(getString(Constants.OPERATION_SUCCEEDED));
+            modal.show(false);
+            modal.close(target);
+        } catch (SyncopeClientException e) {
+            LOG.error("While creating template for {}", mailTemplateTO.getKey(), e);
+            error(StringUtils.isBlank(e.getMessage()) ? e.getClass().getName() : e.getMessage());
+        }
+        SyncopeConsoleSession.get().getNotificationPanel().refresh(target);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationDirectoryPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationDirectoryPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationDirectoryPanel.java
new file mode 100644
index 0000000..3bb90eb
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationDirectoryPanel.java
@@ -0,0 +1,208 @@
+/*
+ * 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.notifications;
+
+import static org.apache.wicket.Component.ENABLE;
+
+import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.commons.DirectoryDataProvider;
+import org.apache.syncope.client.console.commons.SortableDataProviderComparator;
+import org.apache.syncope.client.console.notifications.NotificationDirectoryPanel.NotificationProvider;
+import org.apache.syncope.client.console.panels.DirectoryPanel;
+import org.apache.syncope.client.console.rest.NotificationRestClient;
+import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.ActionColumn;
+import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.CollectionPropertyColumn;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionLinksPanel;
+import org.apache.syncope.client.console.wizards.AjaxWizard;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.NotificationTO;
+import org.apache.syncope.common.lib.types.StandardEntitlement;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
+import org.apache.wicket.event.Broadcast;
+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.model.AbstractReadOnlyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.model.StringResourceModel;
+
+public class NotificationDirectoryPanel
+        extends DirectoryPanel<NotificationTO, NotificationHandler, NotificationProvider, NotificationRestClient> {
+
+    private static final long serialVersionUID = -3789392431954221446L;
+
+    public NotificationDirectoryPanel(final String id, final PageReference pageReference) {
+        super(id, pageReference, true);
+        disableCheckBoxes();
+
+        modal.size(Modal.Size.Large);
+        altDefaultModal.size(Modal.Size.Large);
+
+        addNewItemPanelBuilder(new NotificationWizardBuilder(new NotificationTO(), pageReference), true);
+
+        restClient = new NotificationRestClient();
+
+        initResultTable();
+
+        MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, ENABLE, StandardEntitlement.NOTIFICATION_CREATE);
+    }
+
+    @Override
+    protected List<IColumn<NotificationTO, String>> getColumns() {
+
+        final List<IColumn<NotificationTO, String>> columns = new ArrayList<IColumn<NotificationTO, String>>();
+        columns.add(new PropertyColumn<NotificationTO, String>(
+                new StringResourceModel("key", this, null), "key", "key"));
+        columns.add(new CollectionPropertyColumn<NotificationTO>(
+                new StringResourceModel("events", this, null), "events", "events"));
+        columns.add(new PropertyColumn<NotificationTO, String>(
+                new StringResourceModel("subject", this, null), "subject", "subject"));
+        columns.add(new PropertyColumn<NotificationTO, String>(
+                new StringResourceModel("template", this, null), "template", "template"));
+        columns.add(new PropertyColumn<NotificationTO, String>(
+                new StringResourceModel("traceLevel", this, null), "traceLevel", "traceLevel"));
+        columns.add(new PropertyColumn<NotificationTO, String>(
+                new StringResourceModel("active", this, null), "active", "active"));
+
+        columns.add(new ActionColumn<NotificationTO, String>(new ResourceModel("actions", "")) {
+
+            private static final long serialVersionUID = -3503023501954863131L;
+
+            @Override
+            public ActionLinksPanel<NotificationTO> getActions(
+                    final String componentId, final IModel<NotificationTO> model) {
+
+                final ActionLinksPanel.Builder<NotificationTO> panel = ActionLinksPanel.builder();
+
+                panel.add(new ActionLink<NotificationTO>() {
+
+                    private static final long serialVersionUID = -7978723352517770645L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target, final NotificationTO ignore) {
+                        target.add(utilityModal.setContent(new NotificationTasks(
+                                utilityModal, pageRef, model.getObject())));
+                        utilityModal.header(new ResourceModel("notification.executions", "Tasks"));
+                        utilityModal.show(true);
+                        target.add(utilityModal);
+                    }
+                }, ActionLink.ActionType.VIEW, StandardEntitlement.TASK_LIST);
+
+                panel.add(new ActionLink<NotificationTO>() {
+
+                    private static final long serialVersionUID = -7978723352517770645L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target, final NotificationTO ignore) {
+                        send(NotificationDirectoryPanel.this, Broadcast.EXACT,
+                                new AjaxWizard.EditItemActionEvent<>(
+                                        new NotificationHandler(restClient.read(model.getObject().getKey())), target));
+                    }
+                }, ActionLink.ActionType.EDIT, StandardEntitlement.NOTIFICATION_UPDATE);
+
+                panel.add(new ActionLink<NotificationTO>() {
+
+                    private static final long serialVersionUID = -3722207913631435501L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target, final NotificationTO ignore) {
+                        try {
+                            restClient.delete(model.getObject().getKey());
+                            info(getString(Constants.OPERATION_SUCCEEDED));
+                            target.add(container);
+                        } catch (SyncopeClientException e) {
+                            LOG.error("While deleting object {}", model.getObject().getKey(), e);
+                            error(StringUtils.isBlank(e.getMessage()) ? e.getClass().getName() : e.getMessage());
+                        }
+                        SyncopeConsoleSession.get().getNotificationPanel().refresh(target);
+                    }
+                }, ActionLink.ActionType.DELETE, StandardEntitlement.NOTIFICATION_DELETE);
+
+                return panel.build(componentId);
+            }
+        });
+        return columns;
+    }
+
+    @Override
+    protected NotificationProvider dataProvider() {
+        return new NotificationProvider(rows);
+    }
+
+    @Override
+    protected String paginatorRowsKey() {
+        return Constants.PREF_NOTIFICATION_PAGINATOR_ROWS;
+    }
+
+    @Override
+    protected Collection<ActionLink.ActionType> getBulkActions() {
+        return Collections.<ActionLink.ActionType>emptyList();
+    }
+
+    public class NotificationProvider extends DirectoryDataProvider<NotificationTO> {
+
+        private static final long serialVersionUID = -276043813563988590L;
+
+        private final SortableDataProviderComparator<NotificationTO> comparator;
+
+        public NotificationProvider(final int paginatorRows) {
+            super(paginatorRows);
+            setSort("key", SortOrder.ASCENDING);
+            comparator = new SortableDataProviderComparator<NotificationTO>(this);
+        }
+
+        @Override
+        public Iterator<NotificationTO> iterator(final long first, final long count) {
+            final List<NotificationTO> list = restClient.getAllNotifications();
+            Collections.sort(list, comparator);
+            return list.subList((int) first, (int) first + (int) count).iterator();
+        }
+
+        @Override
+        public long size() {
+            return restClient.getAllNotifications().size();
+        }
+
+        @Override
+        public IModel<NotificationTO> model(final NotificationTO notification) {
+            return new AbstractReadOnlyModel<NotificationTO>() {
+
+                private static final long serialVersionUID = 774694801558497248L;
+
+                @Override
+                public NotificationTO getObject() {
+                    return notification;
+                }
+            };
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationHandler.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationHandler.java b/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationHandler.java
new file mode 100644
index 0000000..274eb13
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationHandler.java
@@ -0,0 +1,131 @@
+/*
+ * 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.notifications;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.syncope.client.console.panels.search.SearchClause;
+import org.apache.syncope.client.console.panels.search.SearchUtils;
+import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.common.lib.search.AbstractFiqlSearchConditionBuilder;
+import org.apache.syncope.common.lib.to.NotificationTO;
+
+public class NotificationHandler implements Serializable {
+
+    private static final long serialVersionUID = 8058288034211558376L;
+
+    private final NotificationTO notificationTO;
+
+    private List<Pair<String, List<SearchClause>>> aboutClauses;
+
+    private List<SearchClause> recipientClauses;
+
+    public NotificationHandler(final NotificationTO notificationTO) {
+        this.notificationTO = notificationTO;
+    }
+
+    public final Long getKey() {
+        return this.notificationTO.getKey();
+    }
+
+    public List<Pair<String, List<SearchClause>>> getAboutClauses() {
+        if (this.aboutClauses == null) {
+            this.aboutClauses = new ArrayList<>();
+            for (Map.Entry<String, String> entry : this.notificationTO.getAbouts().entrySet()) {
+                this.aboutClauses.add(Pair.of(entry.getKey(), SearchUtils.getSearchClauses(entry.getValue())));
+            }
+        }
+
+        return this.aboutClauses;
+    }
+
+    public void setAboutClauses(final List<Pair<String, List<SearchClause>>> dynClauses) {
+        this.aboutClauses = dynClauses;
+    }
+
+    public List<SearchClause> getRecipientClauses() {
+        if (this.recipientClauses == null) {
+            this.recipientClauses = SearchUtils.getSearchClauses(this.notificationTO.getRecipientsFIQL());
+        }
+        return this.recipientClauses;
+    }
+
+    public void setRecipientClauses(final List<SearchClause> dynClauses) {
+        this.recipientClauses = dynClauses;
+    }
+
+    public Map<String, String> getAboutFIQLs() {
+        if (CollectionUtils.isEmpty(this.aboutClauses)) {
+            return this.notificationTO.getAbouts();
+        } else {
+
+            final Map<String, String> res = new HashMap<>();
+            for (Pair<String, List<SearchClause>> pair : this.aboutClauses) {
+                AbstractFiqlSearchConditionBuilder builder;
+                switch (pair.getLeft()) {
+                    case "USER":
+                        builder = SyncopeClient.getUserSearchConditionBuilder();
+                        break;
+                    case "GROUP":
+                        builder = SyncopeClient.getGroupSearchConditionBuilder();
+                        break;
+                    default:
+                        builder = SyncopeClient.getAnyObjectSearchConditionBuilder(pair.getLeft());
+                        break;
+
+                }
+                res.put(pair.getLeft(), getFIQLString(pair.getRight(), builder));
+            }
+            return res;
+        }
+    }
+
+    private String getRecipientsFIQL() {
+        if (CollectionUtils.isEmpty(this.recipientClauses)) {
+            return StringUtils.EMPTY;
+        } else {
+            return getFIQLString(this.recipientClauses, SyncopeClient.getUserSearchConditionBuilder());
+        }
+    }
+
+    private String getFIQLString(final List<SearchClause> clauses, final AbstractFiqlSearchConditionBuilder bld) {
+        return SearchUtils.buildFIQL(clauses, bld);
+    }
+
+    public NotificationTO fillAboutConditions() {
+        this.notificationTO.getAbouts().clear();
+        this.notificationTO.getAbouts().putAll(this.getAboutFIQLs());
+        return this.notificationTO;
+    }
+
+    public NotificationTO fillRecipientConditions() {
+        this.notificationTO.setRecipientsFIQL(this.getRecipientsFIQL());
+        return this.notificationTO;
+    }
+
+    public NotificationTO getInnerObject() {
+        return this.notificationTO;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/8455cb96/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationTasks.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationTasks.java b/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationTasks.java
new file mode 100644
index 0000000..ead48bb
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/notifications/NotificationTasks.java
@@ -0,0 +1,71 @@
+/*
+ * 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.notifications;
+
+import java.io.Serializable;
+import org.apache.syncope.client.console.panels.ModalPanel;
+import org.apache.syncope.client.console.panels.MultilevelPanel;
+import org.apache.syncope.client.console.tasks.NotificationTaskDirectoryPanel;
+import org.apache.syncope.client.console.tasks.TaskExecutionDetails;
+import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
+import org.apache.syncope.common.lib.to.NotificationTO;
+import org.apache.syncope.common.lib.to.NotificationTaskTO;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.panel.Panel;
+
+public class NotificationTasks extends Panel implements ModalPanel<Serializable> {
+
+    private static final long serialVersionUID = 1066124171682570083L;
+
+    public NotificationTasks(
+            final BaseModal<?> baseModal, final PageReference pageReference, final NotificationTO notificationTO) {
+        super(BaseModal.CONTENT_ID);
+
+        final MultilevelPanel mlp = new MultilevelPanel("tasks");
+        add(mlp);
+
+        mlp.setFirstLevel(new NotificationTaskDirectoryPanel(null, mlp, pageReference) {
+
+            private static final long serialVersionUID = -2195387360323687302L;
+
+            @Override
+            protected void viewTask(final NotificationTaskTO taskTO, final AjaxRequestTarget target) {
+                mlp.next("task.view", new TaskExecutionDetails<>(null, taskTO, pageReference), target);
+            }
+        });
+    }
+
+    @Override
+    public void onSubmit(final AjaxRequestTarget target, final Form<?> form) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public void onError(final AjaxRequestTarget target, final Form<?> form) {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+    @Override
+    public NotificationTO getItem() {
+        throw new UnsupportedOperationException("Not supported yet.");
+    }
+
+}