You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@syncope.apache.org by il...@apache.org on 2015/02/13 12:44:44 UTC

[40/51] [partial] syncope git commit: [SYNCOPE-620] Re-organization completed

http://git-wip-us.apache.org/repos/asf/syncope/blob/2d194636/client/console/src/main/java/org/apache/syncope/client/console/pages/Resources.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/Resources.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/Resources.java
new file mode 100644
index 0000000..0fabe59
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/Resources.java
@@ -0,0 +1,723 @@
+/*
+ * 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.pages;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.commons.PreferenceManager;
+import org.apache.syncope.client.console.commons.SortableDataProviderComparator;
+import org.apache.syncope.client.console.panels.AbstractSearchResultPanel;
+import org.apache.syncope.client.console.panels.AjaxDataTablePanel;
+import org.apache.syncope.client.console.rest.ConnectorRestClient;
+import org.apache.syncope.client.console.wicket.ajax.markup.html.ClearIndicatingAjaxLink;
+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.LinkPanel;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.ConnInstanceTO;
+import org.apache.syncope.common.lib.to.ResourceTO;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.wicket.AttributeModifier;
+import org.apache.wicket.Component;
+import org.apache.wicket.Page;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.attributes.AjaxCallListener;
+import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
+import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
+import org.apache.wicket.event.IEvent;
+import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
+import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
+import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.ISortableDataProvider;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
+import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.DropDownChoice;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.model.AbstractReadOnlyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.PropertyModel;
+import org.apache.wicket.model.StringResourceModel;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.spring.injection.annot.SpringBean;
+
+/**
+ * Resources WebPage.
+ */
+public class Resources extends BasePage {
+
+    private static final long serialVersionUID = -3789252860990261728L;
+
+    private static final int WIN_HEIGHT = 600;
+
+    private static final int WIN_WIDTH = 1100;
+
+    @SpringBean
+    private ConnectorRestClient connectorRestClient;
+
+    @SpringBean
+    private PreferenceManager prefMan;
+
+    private final ModalWindow createResourceWin;
+
+    private final ModalWindow editResourceWin;
+
+    private final ModalWindow createConnectorWin;
+
+    private final ModalWindow editConnectorWin;
+
+    private final int resourcePaginatorRows;
+
+    private final int connectorPaginatorRows;
+
+    private WebMarkupContainer resourceContainer;
+
+    private WebMarkupContainer connectorContainer;
+
+    /**
+     * Modal window to be used for user status management.
+     */
+    protected final ModalWindow statusmodal = new ModalWindow("statusModal");
+
+    /**
+     * Schemas to be shown modal window height.
+     */
+    private final static int STATUS_MODAL_WIN_HEIGHT = 500;
+
+    /**
+     * Schemas to be shown modal window width.
+     */
+    private final static int STATUS_MODAL_WIN_WIDTH = 700;
+
+    public Resources(final PageParameters parameters) {
+        super(parameters);
+
+        add(createResourceWin = new ModalWindow("createResourceWin"));
+        add(editResourceWin = new ModalWindow("editResourceWin"));
+        add(createConnectorWin = new ModalWindow("createConnectorWin"));
+        add(editConnectorWin = new ModalWindow("editConnectorWin"));
+
+        statusmodal.setCssClassName(ModalWindow.CSS_CLASS_GRAY);
+        statusmodal.setInitialHeight(STATUS_MODAL_WIN_HEIGHT);
+        statusmodal.setInitialWidth(STATUS_MODAL_WIN_WIDTH);
+        statusmodal.setCookieName("status-modal");
+        add(statusmodal);
+
+        AjaxLink<Void> reloadLink = new ClearIndicatingAjaxLink<Void>("reloadLink", getPageReference()) {
+
+            private static final long serialVersionUID = 3109256773218160485L;
+
+            @Override
+            protected void onClickInternal(final AjaxRequestTarget target) {
+                try {
+                    connectorRestClient.reload();
+                    info(getString(Constants.OPERATION_SUCCEEDED));
+                } catch (Exception e) {
+                    error(getString(Constants.ERROR) + ": " + e.getMessage());
+                }
+                feedbackPanel.refresh(target);
+                target.add(connectorContainer);
+            }
+
+            @Override
+            protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) {
+                super.updateAjaxAttributes(attributes);
+
+                final AjaxCallListener ajaxCallListener = new AjaxCallListener() {
+
+                    private static final long serialVersionUID = 7160235486520935153L;
+
+                    @Override
+                    public CharSequence getPrecondition(final Component component) {
+                        return "if (!confirm('" + getString("confirmReloadConnectors") + "')) "
+                                + "{return false;} else {return true;}";
+                    }
+                };
+                attributes.getAjaxCallListeners().add(ajaxCallListener);
+            }
+        };
+        MetaDataRoleAuthorizationStrategy.authorize(reloadLink, ENABLE, xmlRolesReader.getEntitlement(
+                "Connectors", "reload"));
+        add(reloadLink);
+
+        resourcePaginatorRows = prefMan.getPaginatorRows(getRequest(), Constants.PREF_RESOURCES_PAGINATOR_ROWS);
+        connectorPaginatorRows = prefMan.getPaginatorRows(getRequest(), Constants.PREF_CONNECTORS_PAGINATOR_ROWS);
+
+        setupResources();
+        setupConnectors();
+    }
+
+    private void setupResources() {
+        List<IColumn<ResourceTO, String>> columns = new ArrayList<>();
+
+        columns.add(new PropertyColumn<ResourceTO, String>(new StringResourceModel("key", this, null), "key", "key"));
+
+        columns.add(new AbstractColumn<ResourceTO, String>(
+                new StringResourceModel("connector", this, null, "connector")) {
+
+                    private static final long serialVersionUID = 8263694778917279290L;
+
+                    @Override
+                    public void populateItem(final Item<ICellPopulator<ResourceTO>> cellItem, final String componentId,
+                            final IModel<ResourceTO> rowModel) {
+
+                        final AjaxLink<String> editLink =
+                        new ClearIndicatingAjaxLink<String>("link", getPageReference()) {
+
+                            private static final long serialVersionUID = -7978723352517770644L;
+
+                            @Override
+                            protected void onClickInternal(final AjaxRequestTarget target) {
+
+                                editConnectorWin.setPageCreator(new ModalWindow.PageCreator() {
+
+                                    private static final long serialVersionUID = -7834632442532690940L;
+
+                                    @Override
+                                    public Page createPage() {
+                                        return new ConnectorModalPage(Resources.this.getPageReference(),
+                                                editConnectorWin,
+                                                connectorRestClient.read(rowModel.getObject().getConnectorId()));
+                                    }
+                                });
+
+                                editConnectorWin.show(target);
+                            }
+                        };
+                        editLink.add(new Label("linkTitle", rowModel.getObject().getConnectorDisplayName()));
+
+                        LinkPanel editConnPanel = new LinkPanel(componentId);
+                        editConnPanel.add(editLink);
+
+                        cellItem.add(editConnPanel);
+
+                        MetaDataRoleAuthorizationStrategy.authorize(editConnPanel, ENABLE, xmlRolesReader.
+                                getEntitlement(
+                                        "Connectors", "read"));
+                    }
+                });
+
+        columns.add(new AbstractColumn<ResourceTO, String>(
+                new StringResourceModel("propagationPrimary", this, null)) {
+
+                    private static final long serialVersionUID = -3503023501954863131L;
+
+                    @Override
+                    public void populateItem(final Item<ICellPopulator<ResourceTO>> item,
+                            final String componentId, final IModel<ResourceTO> model) {
+
+                        item.add(new Label(componentId, ""));
+                        item.add(new AttributeModifier("class", new Model<>(
+                                                Boolean.toString(model.getObject().isPropagationPrimary()))));
+                    }
+
+                    @Override
+                    public String getCssClass() {
+                        return "narrowcolumn";
+                    }
+                });
+
+        columns.add(new PropertyColumn<ResourceTO, String>(new StringResourceModel(
+                "propagationPriority", this, null), "propagationPriority", "propagationPriority") {
+
+                    @Override
+                    public String getCssClass() {
+                        return "narrowcolumn";
+                    }
+                });
+
+        columns.add(new AbstractColumn<ResourceTO, String>(new StringResourceModel("actions", this, null, "")) {
+
+            private static final long serialVersionUID = 2054811145491901166L;
+
+            @Override
+            public String getCssClass() {
+                return "action";
+            }
+
+            @Override
+            public void populateItem(final Item<ICellPopulator<ResourceTO>> cellItem, final String componentId,
+                    final IModel<ResourceTO> model) {
+
+                final ResourceTO resourceTO = model.getObject();
+
+                final ActionLinksPanel panel = new ActionLinksPanel(componentId, model, getPageReference());
+                panel.add(new ActionLink() {
+
+                    private static final long serialVersionUID = -3722207913631435501L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target) {
+                        statusmodal.setPageCreator(new ModalWindow.PageCreator() {
+
+                            private static final long serialVersionUID = -7834632442532690940L;
+
+                            @Override
+                            public Page createPage() {
+                                return new ProvisioningModalPage<>(
+                                        getPageReference(), statusmodal, model.getObject(), UserTO.class);
+                            }
+                        });
+
+                        statusmodal.show(target);
+                    }
+                }, ActionLink.ActionType.MANAGE_USERS, "Resources");
+
+                panel.add(new ActionLink() {
+
+                    private static final long serialVersionUID = -3722207913631435501L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target) {
+
+                        statusmodal.setPageCreator(new ModalWindow.PageCreator() {
+
+                            private static final long serialVersionUID = -7834632442532690940L;
+
+                            @Override
+                            public Page createPage() {
+                                return new ProvisioningModalPage<>(
+                                        getPageReference(), statusmodal, model.getObject(), RoleTO.class);
+                            }
+                        });
+
+                        statusmodal.show(target);
+                    }
+                }, ActionLink.ActionType.MANAGE_ROLES, "Resources");
+
+                panel.add(new ActionLink() {
+
+                    private static final long serialVersionUID = -3722207913631435501L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target) {
+                        resourceTO.setUsyncToken(null);
+                        resourceTO.setRsyncToken(null);
+                        try {
+                            resourceRestClient.update(resourceTO);
+                            info(getString(Constants.OPERATION_SUCCEEDED));
+                        } catch (SyncopeClientException e) {
+                            error(getString(Constants.ERROR) + ":" + e.getMessage());
+
+                            LOG.error("While resetting sync token from " + resourceTO.getKey(), e);
+                        }
+
+                        feedbackPanel.refresh(target);
+                        target.add(resourceContainer);
+                    }
+                }, ActionLink.ActionType.RESET, "Resources");
+
+                panel.add(new ActionLink() {
+
+                    private static final long serialVersionUID = -3722207913631435501L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target) {
+                        editResourceWin.setPageCreator(new ModalWindow.PageCreator() {
+
+                            private static final long serialVersionUID = -7834632442532690940L;
+
+                            @Override
+                            public Page createPage() {
+                                return new ResourceModalPage(Resources.this.getPageReference(),
+                                        editResourceWin, resourceTO, false);
+                            }
+                        });
+
+                        editResourceWin.show(target);
+                    }
+                }, ActionLink.ActionType.EDIT, "Resources");
+
+                panel.add(new ActionLink() {
+
+                    private static final long serialVersionUID = -3722207913631435501L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target) {
+                        try {
+                            resourceRestClient.delete(resourceTO.getKey());
+                            info(getString(Constants.OPERATION_SUCCEEDED));
+                        } catch (SyncopeClientException e) {
+                            error(getString(Constants.ERROR) + ": " + e.getMessage());
+
+                            LOG.error("While deleting resource " + resourceTO.getKey(), e);
+                        }
+
+                        feedbackPanel.refresh(target);
+                        target.add(resourceContainer);
+                    }
+                }, ActionLink.ActionType.DELETE, "Resources");
+
+                cellItem.add(panel);
+            }
+        });
+
+        final AjaxDataTablePanel<ResourceTO, String> table = new AjaxDataTablePanel<>(
+                "resourceDatatable",
+                columns,
+                (ISortableDataProvider<ResourceTO, String>) new ResourcesProvider(),
+                resourcePaginatorRows,
+                Arrays.asList(new ActionLink.ActionType[] { ActionLink.ActionType.DELETE }),
+                resourceRestClient,
+                "key",
+                "Resources",
+                getPageReference());
+
+        resourceContainer = new WebMarkupContainer("resourceContainer");
+        resourceContainer.add(table);
+        resourceContainer.setOutputMarkupId(true);
+
+        add(resourceContainer);
+
+        setWindowClosedCallback(createResourceWin, resourceContainer);
+        setWindowClosedCallback(editResourceWin, resourceContainer);
+
+        createResourceWin.setCssClassName(ModalWindow.CSS_CLASS_GRAY);
+        createResourceWin.setInitialHeight(WIN_HEIGHT);
+        createResourceWin.setInitialWidth(WIN_WIDTH);
+        createResourceWin.setCookieName("create-res-modal");
+
+        editResourceWin.setCssClassName(ModalWindow.CSS_CLASS_GRAY);
+        editResourceWin.setInitialHeight(WIN_HEIGHT);
+        editResourceWin.setInitialWidth(WIN_WIDTH);
+        editResourceWin.setCookieName("edit-res-modal");
+
+        AjaxLink<Void> createResourceLink =
+                new ClearIndicatingAjaxLink<Void>("createResourceLink", getPageReference()) {
+
+                    private static final long serialVersionUID = -7978723352517770644L;
+
+                    @Override
+                    protected void onClickInternal(final AjaxRequestTarget target) {
+                        createResourceWin.setPageCreator(new ModalWindow.PageCreator() {
+
+                            private static final long serialVersionUID = -7834632442532690940L;
+
+                            @Override
+                            public Page createPage() {
+                                final ResourceModalPage windows = new ResourceModalPage(Resources.this.
+                                        getPageReference(),
+                                        editResourceWin, new ResourceTO(), true);
+                                return windows;
+                            }
+                        });
+
+                        createResourceWin.show(target);
+                    }
+                };
+
+        MetaDataRoleAuthorizationStrategy.authorize(createResourceLink, ENABLE, xmlRolesReader.getEntitlement(
+                "Resources", "create"));
+
+        add(createResourceLink);
+
+        @SuppressWarnings("rawtypes")
+        final Form paginatorForm = new Form("resourcePaginatorForm");
+
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        final DropDownChoice rowsChooser = new DropDownChoice("rowsChooser", new PropertyModel(this,
+                "resourcePaginatorRows"), prefMan.getPaginatorChoices());
+
+        rowsChooser.add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+            private static final long serialVersionUID = -1107858522700306810L;
+
+            @Override
+            protected void onUpdate(final AjaxRequestTarget target) {
+                prefMan.set(getRequest(), getResponse(), Constants.PREF_RESOURCES_PAGINATOR_ROWS,
+                        String.valueOf(resourcePaginatorRows));
+
+                table.setItemsPerPage(resourcePaginatorRows);
+                target.add(resourceContainer);
+            }
+        });
+
+        paginatorForm.add(rowsChooser);
+        add(paginatorForm);
+    }
+
+    private void setupConnectors() {
+        List<IColumn<ConnInstanceTO, String>> columns = new ArrayList<>();
+
+        columns.add(new PropertyColumn<ConnInstanceTO, String>(
+                new StringResourceModel("key", this, null), "key", "key"));
+        columns.add(new PropertyColumn<ConnInstanceTO, String>(
+                new StringResourceModel("name", this, null), "connectorName", "connectorName"));
+        columns.add(new PropertyColumn<ConnInstanceTO, String>(
+                new StringResourceModel("displayName", this, null), "displayName", "displayName"));
+        columns.add(new PropertyColumn<ConnInstanceTO, String>(
+                new StringResourceModel("bundleName", this, null), "bundleName", "bundleName"));
+        columns.add(new PropertyColumn<ConnInstanceTO, String>(
+                new StringResourceModel("version", this, null), "version", "version"));
+        columns.add(new AbstractColumn<ConnInstanceTO, String>(new StringResourceModel("actions", this, null, "")) {
+
+            private static final long serialVersionUID = 2054811145491901166L;
+
+            @Override
+            public String getCssClass() {
+                return "action";
+            }
+
+            @Override
+            public void populateItem(final Item<ICellPopulator<ConnInstanceTO>> cellItem, final String componentId,
+                    final IModel<ConnInstanceTO> model) {
+
+                final ConnInstanceTO connectorTO = model.getObject();
+
+                final ActionLinksPanel panel = new ActionLinksPanel(componentId, model, getPageReference());
+
+                panel.add(new ActionLink() {
+
+                    private static final long serialVersionUID = -3722207913631435501L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target) {
+                        editConnectorWin.setPageCreator(new ModalWindow.PageCreator() {
+
+                            private static final long serialVersionUID = -7834632442532690940L;
+
+                            @Override
+                            public Page createPage() {
+                                return new ConnectorModalPage(Resources.this.getPageReference(), editConnectorWin,
+                                        connectorTO);
+                            }
+                        });
+
+                        editConnectorWin.show(target);
+                    }
+                }, ActionLink.ActionType.EDIT, "Connectors");
+
+                panel.add(new ActionLink() {
+
+                    private static final long serialVersionUID = -3722207913631435501L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target) {
+                        try {
+                            connectorRestClient.delete(connectorTO.getKey());
+                            info(getString(Constants.OPERATION_SUCCEEDED));
+                        } catch (SyncopeClientException e) {
+                            error(getString(Constants.ERROR) + ": " + e.getMessage());
+
+                            LOG.error("While deleting connector " + connectorTO.getKey(), e);
+                        }
+
+                        target.add(connectorContainer);
+                        feedbackPanel.refresh(target);
+                    }
+                }, ActionLink.ActionType.DELETE, "Connectors");
+
+                cellItem.add(panel);
+            }
+        });
+
+        final AjaxDataTablePanel<ConnInstanceTO, String> table = new AjaxDataTablePanel<ConnInstanceTO, String>(
+                "connectorDatatable",
+                columns,
+                (ISortableDataProvider<ConnInstanceTO, String>) new ConnectorsProvider(),
+                connectorPaginatorRows,
+                Arrays.asList(new ActionLink.ActionType[] { ActionLink.ActionType.DELETE }),
+                connectorRestClient,
+                "key",
+                "Connectors",
+                getPageReference());
+
+        connectorContainer = new WebMarkupContainer("connectorContainer");
+        connectorContainer.add(table);
+        connectorContainer.setOutputMarkupId(true);
+
+        MetaDataRoleAuthorizationStrategy.authorize(connectorContainer, RENDER, xmlRolesReader.getEntitlement(
+                "Connectors", "list"));
+
+        add(connectorContainer);
+
+        setWindowClosedCallback(createConnectorWin, connectorContainer);
+        setWindowClosedCallback(editConnectorWin, connectorContainer);
+
+        createConnectorWin.setCssClassName(ModalWindow.CSS_CLASS_GRAY);
+        createConnectorWin.setInitialHeight(WIN_HEIGHT);
+        createConnectorWin.setInitialWidth(WIN_WIDTH);
+        createConnectorWin.setCookieName("create-conn-modal");
+
+        editConnectorWin.setCssClassName(ModalWindow.CSS_CLASS_GRAY);
+        editConnectorWin.setInitialHeight(WIN_HEIGHT);
+        editConnectorWin.setInitialWidth(WIN_WIDTH);
+        editConnectorWin.setCookieName("edit-conn-modal");
+
+        AjaxLink<Void> createConnectorLink =
+                new ClearIndicatingAjaxLink<Void>("createConnectorLink", getPageReference()) {
+
+                    private static final long serialVersionUID = -7978723352517770644L;
+
+                    @Override
+                    protected void onClickInternal(final AjaxRequestTarget target) {
+                        createConnectorWin.setPageCreator(new ModalWindow.PageCreator() {
+
+                            private static final long serialVersionUID = -7834632442532690940L;
+
+                            @Override
+                            public Page createPage() {
+                                ConnectorModalPage form = new ConnectorModalPage(Resources.this.getPageReference(),
+                                        editConnectorWin, new ConnInstanceTO());
+                                return form;
+                            }
+                        });
+
+                        createConnectorWin.show(target);
+                    }
+                };
+
+        MetaDataRoleAuthorizationStrategy.authorize(createConnectorLink, ENABLE, xmlRolesReader.getEntitlement(
+                "Connectors", "create"));
+
+        add(createConnectorLink);
+
+        @SuppressWarnings("rawtypes")
+        Form paginatorForm = new Form("connectorPaginatorForm");
+
+        MetaDataRoleAuthorizationStrategy.authorize(paginatorForm, RENDER, xmlRolesReader.getEntitlement(
+                "Connectors", "list"));
+
+        final DropDownChoice<Integer> rowsChooser = new DropDownChoice<Integer>(
+                "rowsChooser",
+                new PropertyModel<Integer>(this,
+                        "connectorPaginatorRows"),
+                prefMan.getPaginatorChoices());
+
+        rowsChooser.add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+            private static final long serialVersionUID = -1107858522700306810L;
+
+            @Override
+            protected void onUpdate(AjaxRequestTarget target) {
+                prefMan.set(getRequest(), getResponse(), Constants.PREF_CONNECTORS_PAGINATOR_ROWS,
+                        String.valueOf(connectorPaginatorRows));
+                table.setItemsPerPage(connectorPaginatorRows);
+
+                target.add(connectorContainer);
+            }
+        });
+
+        paginatorForm.add(rowsChooser);
+        add(paginatorForm);
+    }
+
+    class ResourcesProvider extends SortableDataProvider<ResourceTO, String> {
+
+        private static final long serialVersionUID = -9055916672926643975L;
+
+        private final SortableDataProviderComparator<ResourceTO> comparator;
+
+        public ResourcesProvider() {
+            super();
+            //Default sorting
+            setSort("key", SortOrder.ASCENDING);
+            comparator = new SortableDataProviderComparator<>(this);
+        }
+
+        @Override
+        public Iterator<ResourceTO> iterator(final long first, final long count) {
+            List<ResourceTO> list = resourceRestClient.getAll();
+
+            Collections.sort(list, comparator);
+
+            return list.subList((int) first, (int) first + (int) count).iterator();
+        }
+
+        @Override
+        public long size() {
+            return resourceRestClient.getAll().size();
+        }
+
+        @Override
+        public IModel<ResourceTO> model(final ResourceTO resource) {
+            return new AbstractReadOnlyModel<ResourceTO>() {
+
+                private static final long serialVersionUID = 8952474152465381634L;
+
+                @Override
+                public ResourceTO getObject() {
+                    return resource;
+                }
+            };
+        }
+    }
+
+    private class ConnectorsProvider extends SortableDataProvider<ConnInstanceTO, String> {
+
+        private static final long serialVersionUID = 4445909568349448518L;
+
+        private final SortableDataProviderComparator<ConnInstanceTO> comparator;
+
+        public ConnectorsProvider() {
+            super();
+            //Default sorting
+            setSort("key", SortOrder.ASCENDING);
+            comparator = new SortableDataProviderComparator<>(this);
+        }
+
+        @Override
+        public Iterator<ConnInstanceTO> iterator(long first, long count) {
+            List<ConnInstanceTO> list = connectorRestClient.getAllConnectors();
+
+            Collections.sort(list, comparator);
+
+            return list.subList((int) first, (int) first + (int) count).iterator();
+        }
+
+        @Override
+        public long size() {
+            return connectorRestClient.getAllConnectors().size();
+        }
+
+        @Override
+        public IModel<ConnInstanceTO> model(final ConnInstanceTO connector) {
+
+            return new AbstractReadOnlyModel<ConnInstanceTO>() {
+
+                private static final long serialVersionUID = -6033068018293569398L;
+
+                @Override
+                public ConnInstanceTO getObject() {
+                    return connector;
+                }
+            };
+        }
+    }
+
+    @Override
+    public void onEvent(final IEvent<?> event) {
+        if (event.getPayload() instanceof AbstractSearchResultPanel.EventDataWrapper) {
+            ((AbstractSearchResultPanel.EventDataWrapper) event.getPayload()).getTarget().add(resourceContainer);
+            ((AbstractSearchResultPanel.EventDataWrapper) event.getPayload()).getTarget().add(connectorContainer);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2d194636/client/console/src/main/java/org/apache/syncope/client/console/pages/ResultStatusModalPage.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/ResultStatusModalPage.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/ResultStatusModalPage.java
new file mode 100644
index 0000000..b6ecb9e
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/ResultStatusModalPage.java
@@ -0,0 +1,425 @@
+/*
+ * 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.pages;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.commons.ConnIdSpecialAttributeName;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.commons.Mode;
+import org.apache.syncope.client.console.commons.status.Status;
+import org.apache.syncope.client.console.commons.status.StatusUtils;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.common.lib.to.AttrTO;
+import org.apache.syncope.common.lib.to.ConnObjectTO;
+import org.apache.syncope.common.lib.to.PropagationStatus;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.PropagationTaskExecStatus;
+import org.apache.wicket.Component;
+import org.apache.wicket.Page;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import org.apache.wicket.behavior.Behavior;
+import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxLink;
+import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
+import org.apache.wicket.markup.ComponentTag;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.image.Image;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.markup.html.panel.Fragment;
+import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.request.resource.ContextRelativeResource;
+
+/**
+ * Show user or role status after performing a successful operation.
+ */
+public class ResultStatusModalPage extends BaseModalPage {
+
+    private static final long serialVersionUID = 2646115294319713723L;
+
+    private static final String IMG_PREFIX = "/img/statuses/";
+
+    private final AbstractSubjectTO subject;
+
+    private final Mode mode;
+
+    /**
+     * Status management utilities.
+     */
+    private final StatusUtils statusUtils;
+
+    public static class Builder implements Serializable {
+
+        private static final long serialVersionUID = 220361441802274899L;
+
+        private ModalWindow window;
+
+        private Mode mode;
+
+        private AbstractSubjectTO subject;
+
+        public Builder(final ModalWindow window, final AbstractSubjectTO attributable) {
+            this.window = window;
+            this.subject = attributable;
+        }
+
+        public ResultStatusModalPage.Builder mode(final Mode mode) {
+            this.mode = mode;
+            return this;
+        }
+
+        public ResultStatusModalPage build() {
+            return new ResultStatusModalPage(this);
+        }
+    }
+
+    private ResultStatusModalPage(final Builder builder) {
+        super();
+        this.subject = builder.subject;
+        statusUtils = new StatusUtils(this.userRestClient);
+        if (builder.mode == null) {
+            this.mode = Mode.ADMIN;
+        } else {
+            this.mode = builder.mode;
+        }
+
+        final BaseModalPage page = this;
+
+        final WebMarkupContainer container = new WebMarkupContainer("container");
+        container.setOutputMarkupId(true);
+        add(container);
+
+        final Fragment fragment = new Fragment("resultFrag", mode == Mode.SELF
+                ? "userSelfResultFrag"
+                : "propagationResultFrag", this);
+        fragment.setOutputMarkupId(true);
+        container.add(fragment);
+
+        if (mode == Mode.ADMIN) {
+            // add Syncope propagation status
+            PropagationStatus syncope = new PropagationStatus();
+            syncope.setResource("Syncope");
+            syncope.setStatus(PropagationTaskExecStatus.SUCCESS);
+
+            List<PropagationStatus> propagations = new ArrayList<PropagationStatus>();
+            propagations.add(syncope);
+            propagations.addAll(subject.getPropagationStatusTOs());
+
+            fragment.add(new Label("info",
+                    ((subject instanceof UserTO) && ((UserTO) subject).getUsername() != null)
+                            ? ((UserTO) subject).getUsername()
+                            : ((subject instanceof RoleTO) && ((RoleTO) subject).getName() != null)
+                                    ? ((RoleTO) subject).getName()
+                                    : String.valueOf(subject.getKey())));
+
+            final ListView<PropagationStatus> propRes = new ListView<PropagationStatus>("resources",
+                    propagations) {
+
+                        private static final long serialVersionUID = -1020475259727720708L;
+
+                        @Override
+                        protected void populateItem(final ListItem<PropagationStatus> item) {
+                            final PropagationStatus propTO = (PropagationStatus) item.getDefaultModelObject();
+
+                            final ListView attributes = getConnObjectView(propTO);
+
+                            final Fragment attrhead;
+                            if (attributes.getModelObject() == null || attributes.getModelObject().isEmpty()) {
+                                attrhead = new Fragment("attrhead", "emptyAttrHeadFrag", page);
+                            } else {
+                                attrhead = new Fragment("attrhead", "attrHeadFrag", page);
+                            }
+
+                            item.add(attrhead);
+                            item.add(attributes);
+
+                            attrhead.add(new Label("resource", propTO.getResource()));
+
+                            attrhead.add(new Label("propagation", propTO.getStatus() == null
+                                                    ? "UNDEFINED" : propTO.getStatus().toString()));
+
+                            final Image image;
+                            final String alt, title;
+                            final ModalWindow failureWindow = new ModalWindow("failureWindow");
+                            final AjaxLink<?> failureWindowLink = new AjaxLink<Void>("showFailureWindow") {
+
+                                private static final long serialVersionUID = -7978723352517770644L;
+
+                                @Override
+                                public void onClick(AjaxRequestTarget target) {
+                                    failureWindow.show(target);
+                                }
+                            };
+
+                            switch (propTO.getStatus()) {
+
+                                case SUCCESS:
+                                case SUBMITTED:
+                                case CREATED:
+                                    image = new Image("icon",
+                                            new ContextRelativeResource(IMG_PREFIX + Status.ACTIVE.toString()
+                                                    + Constants.PNG_EXT));
+                                    alt = "success icon";
+                                    title = "success";
+                                    failureWindow.setVisible(false);
+                                    failureWindowLink.setEnabled(false);
+                                    break;
+
+                                default:
+                                    image = new Image("icon",
+                                            new ContextRelativeResource(IMG_PREFIX + Status.SUSPENDED.toString()
+                                                    + Constants.PNG_EXT));
+                                    alt = "failure icon";
+                                    title = "failure";
+                            }
+
+                            image.add(new Behavior() {
+
+                                private static final long serialVersionUID = 1469628524240283489L;
+
+                                @Override
+                                public void onComponentTag(final Component component, final ComponentTag tag) {
+                                    tag.put("alt", alt);
+                                    tag.put("title", title);
+                                }
+                            });
+                            final FailureMessageModalPage executionFailureMessagePage;
+                            if (propTO.getFailureReason() == null) {
+                                executionFailureMessagePage =
+                                new FailureMessageModalPage(failureWindow.getContentId(), StringUtils.EMPTY);
+                            } else {
+                                executionFailureMessagePage =
+                                new FailureMessageModalPage(failureWindow.getContentId(), propTO.getFailureReason());
+                            }
+
+                            failureWindow.setPageCreator(new ModalWindow.PageCreator() {
+
+                                private static final long serialVersionUID = -7834632442532690940L;
+
+                                @Override
+                                public Page createPage() {
+                                    return executionFailureMessagePage;
+                                }
+                            });
+                            failureWindow.setCookieName("failureWindow");
+                            failureWindow.setCssClassName(ModalWindow.CSS_CLASS_GRAY);
+                            failureWindowLink.add(image);
+                            attrhead.add(failureWindowLink);
+                            attrhead.add(failureWindow);
+                        }
+                    };
+            fragment.add(propRes);
+        }
+
+        final AjaxLink<Void> close = new IndicatingAjaxLink<Void>("close") {
+
+            private static final long serialVersionUID = -7978723352517770644L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target) {
+                builder.window.close(target);
+            }
+        };
+        container.add(close);
+
+        setOutputMarkupId(true);
+    }
+
+    /**
+     * Get remote attributes list view.
+     *
+     * @param propTO propagation TO.
+     * @return list view.
+     */
+    private ListView<String> getConnObjectView(final PropagationStatus propTO) {
+        final ConnObjectTO before = propTO.getBeforeObj();
+        final ConnObjectTO after = propTO.getAfterObj();
+
+        // sorted in reversed presentation order
+        final List<String> head = new ArrayList<String>();
+        if (subject instanceof UserTO) {
+            head.add(ConnIdSpecialAttributeName.PASSWORD);
+            head.add(ConnIdSpecialAttributeName.ENABLE);
+        }
+        head.add(ConnIdSpecialAttributeName.UID);
+        head.add(ConnIdSpecialAttributeName.NAME);
+
+        final Map<String, AttrTO> beforeAttrMap = before == null
+                ? Collections.<String, AttrTO>emptyMap()
+                : before.getPlainAttrMap();
+
+        final Map<String, AttrTO> afterAttrMap = after == null
+                ? Collections.<String, AttrTO>emptyMap()
+                : after.getPlainAttrMap();
+
+        final Set<String> attributes = new HashSet<String>();
+        attributes.addAll(beforeAttrMap.keySet());
+        attributes.addAll(afterAttrMap.keySet());
+
+        if (!(subject instanceof UserTO)) {
+            attributes.remove(ConnIdSpecialAttributeName.PASSWORD);
+            attributes.remove(ConnIdSpecialAttributeName.ENABLE);
+        }
+
+        final List<String> profile = new ArrayList<String>();
+        profile.addAll(attributes);
+        profile.removeAll(head);
+        Collections.sort(profile);
+
+        for (String attr : head) {
+            if (attributes.contains(attr)) {
+                profile.add(0, attr);
+            }
+        }
+
+        return new ListView<String>("attrs", profile) {
+
+            private static final long serialVersionUID = 4949588177564901031L;
+
+            @Override
+            protected void populateItem(final ListItem<String> item) {
+                String name = item.getModelObject();
+
+                final Fragment beforeValue;
+                final Fragment afterValue;
+                if (ConnIdSpecialAttributeName.ENABLE.equals(name)) {
+                    beforeValue = getStatusIcon("beforeValue", propTO.getResource(), before);
+                    afterValue = getStatusIcon("afterValue", propTO.getResource(), after);
+                } else {
+                    beforeValue = getLabelValue("beforeValue", name, beforeAttrMap);
+                    afterValue = getLabelValue("afterValue", name, afterAttrMap);
+                }
+
+                item.add(new Label("attrName", new ResourceModel(name, name)));
+
+                item.add(beforeValue);
+                item.add(afterValue);
+            }
+        };
+    }
+
+    /**
+     * Get fragment for attribute value (not remote status).
+     *
+     * @param id component id to be replaced with the fragment content.
+     * @param attrName remote attribute name
+     * @param attrMap remote attributes map.
+     * @return fragment.
+     */
+    private Fragment getLabelValue(final String id, final String attrName, final Map<String, AttrTO> attrMap) {
+        final String value;
+
+        final AttrTO attr = attrMap.get(attrName);
+
+        if (attr == null || attr.getValues() == null || attr.getValues().isEmpty()) {
+            value = "";
+        } else {
+            if (ConnIdSpecialAttributeName.PASSWORD.equals(attrName)) {
+                value = "********";
+            } else {
+                value = attr.getValues().size() > 1
+                        ? attr.getValues().toString()
+                        : attr.getValues().get(0);
+            }
+        }
+
+        Component label = new Label("value", value.length() > 50 ? value.substring(0, 50) + "..." : value).
+                add(new Behavior() {
+
+                    private static final long serialVersionUID = 1469628524240283489L;
+
+                    @Override
+                    public void onComponentTag(final Component component, final ComponentTag tag) {
+                        tag.put("title", value);
+                    }
+                });
+
+        final Fragment frag = new Fragment(id, "attrValueFrag", this);
+        frag.add(label);
+
+        return frag;
+    }
+
+    /**
+     * Get fragment for user status icon.
+     *
+     * @param id component id to be replaced with the fragment content
+     * @param resourceName resource name
+     * @param objectTO connector object TO
+     * @return fragment.
+     */
+    private Fragment getStatusIcon(final String id, final String resourceName, final ConnObjectTO objectTO) {
+        final Image image;
+        final String alt, title;
+        switch (statusUtils.getStatusBean(
+                subject, resourceName, objectTO, this.subject instanceof RoleTO).getStatus()) {
+
+            case ACTIVE:
+                image = new Image("status",
+                        new ContextRelativeResource(IMG_PREFIX + Status.ACTIVE.toString() + Constants.PNG_EXT));
+                alt = "active icon";
+                title = "Enabled";
+                break;
+
+            case SUSPENDED:
+                image = new Image("status",
+                        new ContextRelativeResource(IMG_PREFIX + Status.SUSPENDED.toString() + Constants.PNG_EXT));
+                alt = "inactive icon";
+                title = "Disabled";
+                break;
+
+            default:
+                image = null;
+                alt = null;
+                title = null;
+        }
+
+        final Fragment frag;
+        if (image == null) {
+            frag = new Fragment(id, "emptyFrag", this);
+        } else {
+            image.add(new Behavior() {
+
+                private static final long serialVersionUID = 1469628524240283489L;
+
+                @Override
+                public void onComponentTag(final Component component, final ComponentTag tag) {
+                    tag.put("alt", alt);
+                    tag.put("title", title);
+                    tag.put("width", "12px");
+                    tag.put("height", "12px");
+                }
+            });
+
+            frag = new Fragment(id, "remoteStatusFrag", this);
+            frag.add(image);
+        }
+
+        return frag;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2d194636/client/console/src/main/java/org/apache/syncope/client/console/pages/RoleModalPage.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/RoleModalPage.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/RoleModalPage.java
new file mode 100644
index 0000000..3cb9148
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/RoleModalPage.java
@@ -0,0 +1,162 @@
+/*
+ * 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.pages;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.apache.commons.lang3.SerializationUtils;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.commons.Mode;
+import org.apache.syncope.client.console.panels.RolePanel;
+import org.apache.syncope.common.lib.AttributableOperations;
+import org.apache.syncope.common.lib.mod.RoleMod;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.form.AjaxButton;
+import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
+import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxButton;
+import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.ResourceModel;
+
+/**
+ * Modal window with Role form.
+ */
+public class RoleModalPage extends BaseModalPage {
+
+    private static final long serialVersionUID = -1732493223434085205L;
+
+    protected final PageReference pageRef;
+
+    protected final ModalWindow window;
+
+    protected final Mode mode;
+
+    protected final boolean createFlag;
+
+    protected final RolePanel rolePanel;
+
+    protected RoleTO originalRoleTO;
+
+    public RoleModalPage(final PageReference pageRef, final ModalWindow window, final RoleTO roleTO) {
+        this(pageRef, window, roleTO, Mode.ADMIN);
+    }
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public RoleModalPage(final PageReference pageRef, final ModalWindow window, final RoleTO roleTO, final Mode mode) {
+        super();
+
+        this.pageRef = pageRef;
+        this.window = window;
+        this.mode = mode;
+
+        this.createFlag = roleTO.getKey() == 0;
+        if (!createFlag) {
+            originalRoleTO = SerializationUtils.clone(roleTO);
+        }
+
+        final Form<RoleTO> form = new Form<RoleTO>("roleForm");
+        form.setMultiPart(true);
+
+        add(new Label("displayName", roleTO.getKey() == 0 ? "" : roleTO.getDisplayName()));
+
+        form.setModel(new CompoundPropertyModel<RoleTO>(roleTO));
+
+        this.rolePanel = new RolePanel.Builder("rolePanel").form(form).roleTO(roleTO).
+                roleModalPageMode(mode).pageRef(getPageReference()).build();
+        form.add(rolePanel);
+
+        final AjaxButton submit = new IndicatingAjaxButton(SUBMIT, new ResourceModel(SUBMIT)) {
+
+            private static final long serialVersionUID = -958724007591692537L;
+
+            @Override
+            protected void onSubmit(final AjaxRequestTarget target, final Form<?> form) {
+                try {
+                    submitAction(target, form);
+
+                    if (pageRef.getPage() instanceof BasePage) {
+                        ((BasePage) pageRef.getPage()).setModalResult(true);
+                    }
+
+                    closeAction(target, form);
+                } catch (Exception e) {
+                    error(getString(Constants.ERROR) + ": " + e.getMessage());
+                    feedbackPanel.refresh(target);
+                }
+            }
+
+            @Override
+            protected void onError(final AjaxRequestTarget target, final Form<?> form) {
+                feedbackPanel.refresh(target);
+            }
+        };
+        form.add(submit);
+        form.setDefaultButton(submit);
+
+        final AjaxButton cancel = new IndicatingAjaxButton(CANCEL, new ResourceModel(CANCEL)) {
+
+            private static final long serialVersionUID = -958724007591692537L;
+
+            @Override
+            protected void onSubmit(final AjaxRequestTarget target, final Form<?> form) {
+                closeAction(target, form);
+            }
+        };
+        cancel.setDefaultFormProcessing(false);
+        form.add(cancel);
+
+        MetaDataRoleAuthorizationStrategy.authorize(submit, ENABLE, xmlRolesReader.getEntitlement("Roles",
+                createFlag
+                        ? "create"
+                        : "update"));
+
+        add(form);
+    }
+
+    protected void submitAction(final AjaxRequestTarget target, final Form<?> form) {
+        final RoleTO roleTO = (RoleTO) form.getDefaultModelObject();
+        final List<String> entitlementList = new ArrayList<String>(rolePanel.getSelectedEntitlements());
+        roleTO.getEntitlements().clear();
+        roleTO.getEntitlements().addAll(entitlementList);
+
+        RoleTO result;
+        if (createFlag) {
+            result = roleRestClient.create(roleTO);
+        } else {
+            RoleMod roleMod = AttributableOperations.diff(roleTO, originalRoleTO);
+
+            // update role just if it is changed
+            if (roleMod.isEmpty()) {
+                result = roleTO;
+            } else {
+                result = roleRestClient.update(originalRoleTO.getETagValue(), roleMod);
+            }
+        }
+
+        setResponsePage(new ResultStatusModalPage.Builder(window, result).build());
+    }
+
+    protected void closeAction(final AjaxRequestTarget target, final Form<?> form) {
+        window.close(target);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2d194636/client/console/src/main/java/org/apache/syncope/client/console/pages/RoleSelectModalPage.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/RoleSelectModalPage.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/RoleSelectModalPage.java
new file mode 100644
index 0000000..26b1aea
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/RoleSelectModalPage.java
@@ -0,0 +1,105 @@
+/*
+ * 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.pages;
+
+import java.lang.reflect.Constructor;
+import javax.swing.tree.DefaultMutableTreeNode;
+import org.apache.syncope.client.console.commons.RoleTreeBuilder;
+import org.apache.syncope.client.console.wicket.markup.html.tree.DefaultMutableTreeNodeExpansion;
+import org.apache.syncope.client.console.wicket.markup.html.tree.DefaultMutableTreeNodeExpansionModel;
+import org.apache.syncope.client.console.wicket.markup.html.tree.TreeRoleProvider;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.wicket.Component;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.event.Broadcast;
+import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
+import org.apache.wicket.extensions.markup.html.repeater.tree.DefaultNestedTree;
+import org.apache.wicket.extensions.markup.html.repeater.tree.ITreeProvider;
+import org.apache.wicket.extensions.markup.html.repeater.tree.NestedTree;
+import org.apache.wicket.extensions.markup.html.repeater.tree.content.Folder;
+import org.apache.wicket.extensions.markup.html.repeater.tree.theme.WindowsTheme;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.spring.injection.annot.SpringBean;
+
+public class RoleSelectModalPage extends BaseModalPage {
+
+    private static final long serialVersionUID = 2106489458494696439L;
+
+    @SpringBean
+    private RoleTreeBuilder roleTreeBuilder;
+
+    private final NestedTree<DefaultMutableTreeNode> tree;
+
+    public RoleSelectModalPage(final PageReference pageRef, final ModalWindow window, final Class<?> payloadClass) {
+        super();
+
+        final ITreeProvider<DefaultMutableTreeNode> treeProvider = new TreeRoleProvider(roleTreeBuilder, true);
+        final DefaultMutableTreeNodeExpansionModel treeModel = new DefaultMutableTreeNodeExpansionModel();
+
+        tree = new DefaultNestedTree<DefaultMutableTreeNode>("treeTable", treeProvider, treeModel) {
+
+            private static final long serialVersionUID = 7137658050662575546L;
+
+            @Override
+            protected Component newContentComponent(final String id, final IModel<DefaultMutableTreeNode> node) {
+                final DefaultMutableTreeNode treeNode = node.getObject();
+                final RoleTO roleTO = (RoleTO) treeNode.getUserObject();
+
+                return new Folder<DefaultMutableTreeNode>(id, RoleSelectModalPage.this.tree, node) {
+
+                    private static final long serialVersionUID = 9046323319920426493L;
+
+                    @Override
+                    protected boolean isClickable() {
+                        return true;
+                    }
+
+                    @Override
+                    protected IModel<?> newLabelModel(final IModel<DefaultMutableTreeNode> model) {
+                        return new Model<>(roleTO.getDisplayName());
+                    }
+
+                    @Override
+                    protected void onClick(final AjaxRequestTarget target) {
+                        super.onClick(target);
+
+                        try {
+                            Constructor<?> constructor = payloadClass.getConstructor(Long.class);
+                            Object payload = constructor.newInstance(roleTO.getKey());
+
+                            send(pageRef.getPage(), Broadcast.BREADTH, payload);
+                        } catch (Exception e) {
+                            LOG.error("Could not send role select event", e);
+                        }
+
+                        window.close(target);
+                    }
+                };
+            }
+        };
+        tree.add(new WindowsTheme());
+        tree.setOutputMarkupId(true);
+
+        DefaultMutableTreeNodeExpansion.get().expandAll();
+
+        this.add(tree);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2d194636/client/console/src/main/java/org/apache/syncope/client/console/pages/RoleTemplateModalPage.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/RoleTemplateModalPage.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/RoleTemplateModalPage.java
new file mode 100644
index 0000000..6639517
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/RoleTemplateModalPage.java
@@ -0,0 +1,50 @@
+/*
+ * 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.pages;
+
+import org.apache.syncope.client.console.commons.Mode;
+import org.apache.syncope.common.lib.to.RoleTO;
+import org.apache.syncope.common.lib.to.SyncTaskTO;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
+import org.apache.wicket.markup.html.form.Form;
+
+public class RoleTemplateModalPage extends RoleModalPage {
+
+    private static final long serialVersionUID = -3849135555203409845L;
+
+    private final SyncTaskTO syncTaskTO;
+
+    public RoleTemplateModalPage(final PageReference callerPageRef, final ModalWindow window,
+            final SyncTaskTO syncTaskTO) {
+
+        super(callerPageRef, window, syncTaskTO.getRoleTemplate() == null
+                ? new RoleTO()
+                : syncTaskTO.getRoleTemplate(), Mode.TEMPLATE);
+
+        this.syncTaskTO = syncTaskTO;
+    }
+
+    @Override
+    protected void submitAction(final AjaxRequestTarget target, final Form form) {
+        syncTaskTO.setRoleTemplate((RoleTO) form.getModelObject());
+        taskRestClient.updateSyncTask(syncTaskTO);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2d194636/client/console/src/main/java/org/apache/syncope/client/console/pages/Roles.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/Roles.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/Roles.java
new file mode 100644
index 0000000..8310792
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/Roles.java
@@ -0,0 +1,186 @@
+/*
+ * 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.pages;
+
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.panels.AbstractSearchResultPanel;
+import org.apache.syncope.client.console.panels.RoleSearchPanel;
+import org.apache.syncope.client.console.panels.RoleSearchResultPanel;
+import org.apache.syncope.client.console.panels.RoleSummaryPanel;
+import org.apache.syncope.client.console.rest.RoleRestClient;
+import org.apache.syncope.client.console.wicket.ajax.markup.html.ClearIndicatingAjaxButton;
+import org.apache.syncope.client.console.wicket.markup.html.tree.TreeRolePanel;
+import org.apache.wicket.Session;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.event.Broadcast;
+import org.apache.wicket.event.IEvent;
+import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.spring.injection.annot.SpringBean;
+
+/**
+ * Roles WebPage.
+ */
+public class Roles extends BasePage {
+
+    private static final long serialVersionUID = -2147758241610831969L;
+
+    private static final int WIN_HEIGHT = 500;
+
+    private static final int WIN_WIDTH = 800;
+
+    @SpringBean
+    private RoleRestClient restClient;
+
+    private final ModalWindow editRoleWin;
+
+    private final WebMarkupContainer roleTabsContainer;
+
+    public Roles(final PageParameters parameters) {
+        super(parameters);
+
+        roleTabsContainer = new WebMarkupContainer("roleTabsContainer");
+        roleTabsContainer.setOutputMarkupId(true);
+        add(roleTabsContainer);
+
+        editRoleWin = new ModalWindow("editRoleWin");
+        editRoleWin.setCssClassName(ModalWindow.CSS_CLASS_GRAY);
+        editRoleWin.setInitialHeight(WIN_HEIGHT);
+        editRoleWin.setInitialWidth(WIN_WIDTH);
+        editRoleWin.setCookieName("edit-role-modal");
+        add(editRoleWin);
+
+        final TreeRolePanel treePanel = new TreeRolePanel("treePanel");
+        treePanel.setOutputMarkupId(true);
+        roleTabsContainer.add(treePanel);
+
+        final RoleSummaryPanel summaryPanel = new RoleSummaryPanel.Builder("summaryPanel")
+                .window(editRoleWin).callerPageRef(Roles.this.getPageReference()).build();
+        roleTabsContainer.add(summaryPanel);
+
+        editRoleWin.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() {
+
+            private static final long serialVersionUID = 8804221891699487139L;
+
+            @Override
+            public void onClose(final AjaxRequestTarget target) {
+                final RoleSummaryPanel summaryPanel = (RoleSummaryPanel) roleTabsContainer.get("summaryPanel");
+
+                final TreeNodeClickUpdate data = new TreeNodeClickUpdate(target,
+                        summaryPanel == null || summaryPanel.getSelectedNode() == null
+                        ? 0
+                        : summaryPanel.getSelectedNode().getKey());
+
+                send(getPage(), Broadcast.BREADTH, data);
+
+                if (modalResult) {
+                    getSession().info(getString(Constants.OPERATION_SUCCEEDED));
+                    feedbackPanel.refresh(target);
+                    modalResult = false;
+                }
+
+            }
+        });
+
+        final AbstractSearchResultPanel searchResult =
+                new RoleSearchResultPanel("searchResult", true, null, getPageReference(), restClient);
+        add(searchResult);
+
+        final Form searchForm = new Form("searchForm");
+        add(searchForm);
+
+        final RoleSearchPanel searchPanel = new RoleSearchPanel.Builder("searchPanel").build();
+        searchForm.add(searchPanel);
+
+        searchForm.add(new ClearIndicatingAjaxButton("search", new ResourceModel("search"), getPageReference()) {
+
+            private static final long serialVersionUID = -958724007591692537L;
+
+            @Override
+            protected void onSubmitInternal(final AjaxRequestTarget target, final Form<?> form) {
+                final String fiql = searchPanel.buildFIQL();
+                LOG.debug("Node condition {}", fiql);
+
+                doSearch(target, fiql, searchResult);
+
+                Session.get().getFeedbackMessages().clear();
+                searchPanel.getSearchFeedback().refresh(target);
+            }
+
+            @Override
+            protected void onError(final AjaxRequestTarget target, final Form<?> form) {
+                searchPanel.getSearchFeedback().refresh(target);
+            }
+        });
+    }
+
+    private void doSearch(final AjaxRequestTarget target, final String fiql,
+            final AbstractSearchResultPanel resultsetPanel) {
+
+        if (fiql == null) {
+            error(getString(Constants.SEARCH_ERROR));
+            return;
+        }
+
+        resultsetPanel.search(fiql, target);
+    }
+
+    @Override
+    public void onEvent(final IEvent<?> event) {
+        super.onEvent(event);
+
+        if (event.getPayload() instanceof TreeNodeClickUpdate) {
+            final TreeNodeClickUpdate update = (TreeNodeClickUpdate) event.getPayload();
+
+            final RoleSummaryPanel summaryPanel = new RoleSummaryPanel.Builder("summaryPanel")
+                    .window(editRoleWin).callerPageRef(Roles.this.getPageReference())
+                    .selectedNodeId(update.getSelectedNodeId()).build();
+
+            roleTabsContainer.addOrReplace(summaryPanel);
+            update.getTarget().add(roleTabsContainer);
+        }
+    }
+
+    public static class TreeNodeClickUpdate {
+
+        private final AjaxRequestTarget target;
+
+        private Long selectedNodeId;
+
+        public TreeNodeClickUpdate(final AjaxRequestTarget target, final Long selectedNodeId) {
+            this.target = target;
+            this.selectedNodeId = selectedNodeId;
+        }
+
+        public AjaxRequestTarget getTarget() {
+            return target;
+        }
+
+        public Long getSelectedNodeId() {
+            return selectedNodeId;
+        }
+
+        public void setSelectedNodeId(final Long selectedNodeId) {
+            this.selectedNodeId = selectedNodeId;
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/2d194636/client/console/src/main/java/org/apache/syncope/client/console/pages/SchedTaskModalPage.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/SchedTaskModalPage.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/SchedTaskModalPage.java
new file mode 100644
index 0000000..6222717
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/SchedTaskModalPage.java
@@ -0,0 +1,68 @@
+/*
+ * 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.pages;
+
+import java.util.List;
+import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel;
+import org.apache.syncope.common.lib.to.SchedTaskTO;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.LoadableDetachableModel;
+import org.apache.wicket.model.PropertyModel;
+
+/**
+ * Modal window with Task form (to stop and start execution).
+ */
+public class SchedTaskModalPage extends AbstractSchedTaskModalPage {
+
+    private static final long serialVersionUID = -2501860242590060867L;
+
+    public SchedTaskModalPage(final ModalWindow window, final SchedTaskTO taskTO, final PageReference callerPageRef) {
+
+        super(window, taskTO, callerPageRef);
+
+        final IModel<List<String>> classNames = new LoadableDetachableModel<List<String>>() {
+
+            private static final long serialVersionUID = 5275935387613157437L;
+
+            @Override
+            protected List<String> load() {
+                return taskRestClient.getJobClasses();
+            }
+        };
+
+        final AjaxDropDownChoicePanel<String> className = new AjaxDropDownChoicePanel<String>("jobClassName",
+                getString("class"), new PropertyModel<String>(taskTO, "jobClassName"));
+        className.setChoices(classNames.getObject());
+        className.addRequiredLabel();
+        className.setEnabled(taskTO.getKey() == 0);
+        className.setStyleSheet("ui-widget-content ui-corner-all long_dynamicsize");
+        profile.add(className);
+    }
+
+    @Override
+    public void submitAction(final SchedTaskTO taskTO) {
+        if (taskTO.getKey() > 0) {
+            taskRestClient.updateSchedTask(taskTO);
+        } else {
+            taskRestClient.createSchedTask(taskTO);
+        }
+    }
+}