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 2015/06/08 22:28:06 UTC

[17/17] syncope git commit: [SYNCOPE-156] working with resource and connector topology

[SYNCOPE-156] working with resource and connector topology


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

Branch: refs/heads/master
Commit: 34ec6712df74f0e7267363f0ea044295af35b5aa
Parents: eb5915f
Author: fmartelli <fa...@gmail.com>
Authored: Mon Jun 8 22:26:38 2015 +0200
Committer: fmartelli <fa...@gmail.com>
Committed: Mon Jun 8 22:26:38 2015 +0200

----------------------------------------------------------------------
 client/console/pom.xml                          |   12 +
 .../client/console/SyncopeConsoleSession.java   |    3 +-
 .../console/commons/ActionTableCheckGroup.java  |   40 +
 .../commons/AttributableDataProvider.java       |  103 +
 .../client/console/commons/JexlHelpUtils.java   |   59 +
 .../client/console/pages/AbstractBasePage.java  |    6 +
 .../console/pages/AbstractStatusModalPage.java  |   30 +
 .../syncope/client/console/pages/BasePage.java  |    7 +-
 .../console/pages/BulkActionModalPage.java      |  166 +
 .../pages/BulkActionResultModalPage.java        |   99 +
 .../console/pages/ConnectorModalPage.java       |  477 ++
 .../console/pages/ProvisioningModalPage.java    |  253 ++
 .../syncope/client/console/pages/Realms.java    |  147 +
 .../client/console/pages/ResourceModalPage.java |  204 +
 .../panels/AbstractSearchResultPanel.java       |  346 ++
 .../client/console/panels/AccountPolicy.java    |   35 +
 .../console/panels/ActionDataTablePanel.java    |  145 +
 .../console/panels/AjaxDataTablePanel.java      |  142 +
 .../console/panels/AnnotatedBeanPanel.java      |   66 +
 .../syncope/client/console/panels/Any.java      |   35 +
 .../client/console/panels/Connectors.java       |  385 ++
 .../client/console/panels/DataTablePanel.java   |  113 +
 .../client/console/panels/PasswordPolicy.java   |   35 +
 .../syncope/client/console/panels/Realm.java    |   49 +
 .../client/console/panels/RealmDetails.java     |   41 +
 .../console/panels/ResourceConnConfPanel.java   |  189 +
 .../console/panels/ResourceDetailsPanel.java    |  306 ++
 .../console/panels/ResourceMappingPanel.java    |  644 +++
 .../console/panels/ResourceSecurityPanel.java   |  186 +
 .../client/console/panels/Resources.java        |  485 +++
 .../console/rest/ConnectorRestClient.java       |    6 +-
 .../client/console/rest/RealmRestClient.java    |   38 +
 .../client/console/topology/Topology.java       |  521 +++
 .../client/console/topology/TopologyNode.java   |  123 +
 .../console/topology/TopologyNodePanel.java     |  205 +
 .../topology/TopologyReloadBehavior.java        |   61 +
 .../markup/html/ClearIndicatingAjaxButton.java  |   76 +
 .../markup/html/ClearIndicatingAjaxLink.java    |   61 +
 .../repeater/data/table/ActionResultColumn.java |   77 +
 .../data/table/CheckBoxGroupSelectorPanel.java  |   34 +
 .../html/repeater/data/table/CheckBoxPanel.java |   43 +
 .../repeater/data/table/CheckGroupColumn.java   |   54 +
 .../wicket/markup/html/form/ActionLink.java     |   85 +
 .../markup/html/form/ActionLinksPanel.java      |  609 +++
 .../markup/html/form/AjaxTextFieldPanel.java    |    4 +-
 .../html/form/IndicatingOnConfirmAjaxLink.java  |   58 +
 .../META-INF/resources/css/console.css          | 4066 ------------------
 .../META-INF/resources/css/fieldstyle.css       |  191 +
 .../META-INF/resources/css/syncopeConsole.css   |  170 +-
 .../META-INF/resources/css/topology.css         |  160 +
 .../resources/META-INF/resources/js/topology.js |  244 ++
 .../console/pages/AbstractStatusModalPage.html  |  122 +
 .../pages/AbstractStatusModalPage.properties    |   19 +
 .../pages/AbstractStatusModalPage_it.properties |   19 +
 .../AbstractStatusModalPage_pt_BR.properties    |   19 +
 .../client/console/pages/BaseModalPage.html     |   23 +-
 .../syncope/client/console/pages/BasePage.html  |   11 +-
 .../console/pages/BulkActionModalPage.html      |   45 +
 .../pages/BulkActionModalPage.properties        |   17 +
 .../pages/BulkActionModalPage_it.properties     |   17 +
 .../pages/BulkActionModalPage_pt_BR.properties  |   17 +
 .../pages/BulkActionResultModalPage.html        |   57 +
 .../pages/BulkActionResultModalPage.properties  |   19 +
 .../BulkActionResultModalPage_it.properties     |   19 +
 .../BulkActionResultModalPage_pt_BR.properties  |   19 +
 .../console/pages/ConnectorModalPage.html       |  161 +
 .../console/pages/ConnectorModalPage.properties |   43 +
 .../pages/ConnectorModalPage_it.properties      |   43 +
 .../pages/ConnectorModalPage_pt_BR.properties   |   43 +
 .../syncope/client/console/pages/Realms.html    |   91 +-
 .../client/console/pages/ResourceModalPage.html |   56 +
 .../console/pages/ResourceModalPage.properties  |   60 +
 .../pages/ResourceModalPage_it.properties       |   60 +
 .../pages/ResourceModalPage_pt_BR.properties    |   60 +
 .../panels/AbstractSearchResultPanel.html       |   43 +
 .../panels/AbstractSearchResultPanel.properties |   30 +
 .../AbstractSearchResultPanel_it.properties     |   30 +
 .../AbstractSearchResultPanel_pt_BR.properties  |   30 +
 .../client/console/panels/AccountPolicy.html    |   29 +
 .../console/panels/ActionDataTablePanel.html    |   45 +
 .../console/panels/AjaxDataTablePanel.html      |   47 +
 .../console/panels/AnnotatedBeanPanel.html      |   98 +
 .../panels/AnnotatedBeanPanel.properties        |   20 +
 .../panels/AnnotatedBeanPanel_it.properties     |   20 +
 .../panels/AnnotatedBeanPanel_pt_BR.properties  |   20 +
 .../syncope/client/console/panels/Any.html      |   29 +
 .../client/console/panels/Connectors.html       |   57 +
 .../client/console/panels/Connectors.properties |   34 +
 .../console/panels/Connectors_it.properties     |   34 +
 .../console/panels/Connectors_pt_BR.properties  |   32 +
 .../client/console/panels/PasswordPolicy.html   |   29 +
 .../syncope/client/console/panels/Realm.html    |   93 +
 .../client/console/panels/RealmDetails.html     |   45 +
 .../console/panels/ResourceConnConfPanel.html   |   40 +
 .../console/panels/ResourceDetailsPanel.html    |  137 +
 .../console/panels/ResourceMappingPanel.html    |  114 +
 .../console/panels/ResourceSecurityPanel.html   |   56 +
 .../panels/ResourceSecurityPanel.properties     |   19 +
 .../panels/ResourceSecurityPanel_it.properties  |   19 +
 .../ResourceSecurityPanel_pt_BR.properties      |   19 +
 .../client/console/panels/Resources.html        |   52 +
 .../client/console/panels/Resources.properties  |   34 +
 .../console/panels/Resources_it.properties      |   34 +
 .../console/panels/Resources_pt_BR.properties   |   32 +
 .../client/console/topology/Topology.html       |   52 +
 .../client/console/topology/Topology.properties |   17 +
 .../console/topology/TopologyNodePanel.html     |   67 +
 .../topology/TopologyNodePanel.properties       |   20 +
 .../topology/TopologyNodePanel_it.properties    |   20 +
 .../topology/TopologyNodePanel_pt_BR.properties |   20 +
 .../console/topology/Topology_it.properties     |   17 +
 .../console/topology/Topology_pt_BR.properties  |   17 +
 .../data/table/CheckBoxGroupSelectorPanel.html  |   21 +
 .../html/repeater/data/table/CheckBoxPanel.html |   23 +
 .../markup/html/form/ActionLinksPanel.html      |  173 +
 .../markup/html/form/AjaxCheckBoxPanel.html     |   21 +
 .../html/form/AjaxDropDownChoicePanel.html      |   24 +
 .../markup/html/form/AjaxNumberFieldPanel.html  |   22 +
 .../markup/html/form/AjaxPalettePanel.html      |   23 +
 .../html/form/AjaxPasswordFieldPanel.html       |   22 +
 .../markup/html/form/AjaxTextFieldPanel.html    |   23 +
 .../markup/html/form/AjaxTextFieldPanel_1.html  |   23 +
 .../markup/html/form/BinaryFieldPanel.html      |   44 +
 .../form/CheckBoxMultipleChoiceFieldPanel.html  |   23 +
 .../markup/html/form/DateTextFieldPanel.html    |   22 +
 .../markup/html/form/DateTimeFieldPanel.html    |   21 +
 .../html/form/DateTimeFieldPanel.properties     |   17 +
 .../html/form/DateTimeFieldPanel_it.properties  |   17 +
 .../form/DateTimeFieldPanel_pt_BR.properties    |   17 +
 .../wicket/markup/html/form/FieldPanel.html     |   34 +
 .../wicket/markup/html/form/FieldPanel_1.html   |   34 +
 .../wicket/markup/html/form/LinkPanel.html      |   23 +
 .../wicket/markup/html/form/LinkPanel_1.html    |   23 +
 .../markup/html/form/MappingPurposePanel.html   |   31 +
 .../markup/html/form/MultiFieldPanel.html       |   35 +
 .../markup/html/form/MultiFieldPanel.properties |   22 +
 .../html/form/MultiFieldPanel_it.properties     |   22 +
 .../html/form/MultiFieldPanel_pt_BR.properties  |   22 +
 .../wicket/markup/html/form/NonI18nPalette.html |   69 +
 .../markup/html/form/SpinnerFieldPanel.html     |   29 +
 .../rest/api/service/ConnectorService.java      |    1 -
 pom.xml                                         |   13 +
 142 files changed, 10983 insertions(+), 4196 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/34ec6712/client/console/pom.xml
----------------------------------------------------------------------
diff --git a/client/console/pom.xml b/client/console/pom.xml
index a558d30..4ef7c9c 100644
--- a/client/console/pom.xml
+++ b/client/console/pom.xml
@@ -110,6 +110,18 @@ under the License.
       <groupId>org.webjars</groupId>
       <artifactId>codemirror</artifactId>
     </dependency>
+    <dependency>
+      <groupId>org.webjars</groupId>
+      <artifactId>jsplumb</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.webjars</groupId>
+      <artifactId>jquery-cookie</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.webjars</groupId>
+      <artifactId>jquery-ui</artifactId>
+    </dependency>
   </dependencies>
   
   <build>

http://git-wip-us.apache.org/repos/asf/syncope/blob/34ec6712/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java b/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
index f162357..25394c7 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleSession.java
@@ -157,7 +157,8 @@ public class SyncopeConsoleSession extends AuthenticatedWebSession {
 
     public <T> T getService(final String etag, final Class<T> serviceClass) {
         T serviceInstance = getCachedService(serviceClass);
-        WebClient.client(serviceInstance).match(new EntityTag(etag), false);
+        WebClient.client(serviceInstance).match(new EntityTag(etag), false).
+                type(MediaType.APPLICATION_JSON).accept(MediaType.APPLICATION_JSON);
 
         return serviceInstance;
     }

http://git-wip-us.apache.org/repos/asf/syncope/blob/34ec6712/client/console/src/main/java/org/apache/syncope/client/console/commons/ActionTableCheckGroup.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/ActionTableCheckGroup.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/ActionTableCheckGroup.java
new file mode 100644
index 0000000..453e0c0
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/ActionTableCheckGroup.java
@@ -0,0 +1,40 @@
+/*
+ * 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.commons;
+
+import java.util.Collection;
+import org.apache.wicket.markup.html.form.CheckGroup;
+import org.apache.wicket.model.IModel;
+
+public class ActionTableCheckGroup<T> extends CheckGroup<T> {
+
+    private static final long serialVersionUID = 1288270558573401394L;
+
+    public ActionTableCheckGroup(final String id, final Collection<T> collection) {
+        super(id, collection);
+    }
+
+    public ActionTableCheckGroup(final String id, final IModel<Collection<T>> model) {
+        super(id, model);
+    }
+
+    public boolean isCheckable(final T element) {
+        return true;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/34ec6712/client/console/src/main/java/org/apache/syncope/client/console/commons/AttributableDataProvider.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/AttributableDataProvider.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/AttributableDataProvider.java
new file mode 100644
index 0000000..1890a80
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/AttributableDataProvider.java
@@ -0,0 +1,103 @@
+/*
+ * 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.commons;
+
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import org.apache.syncope.client.console.rest.AbstractSubjectRestClient;
+import org.apache.syncope.common.lib.to.AbstractAttributableTO;
+import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder;
+import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+
+public class AttributableDataProvider extends SortableDataProvider<AbstractAttributableTO, String> {
+
+    private static final long serialVersionUID = 6267494272884913376L;
+
+    private final SortableAttributableProviderComparator comparator;
+
+    private String fiql = null;
+
+    private final int paginatorRows;
+
+    private final boolean filtered;
+
+    private final AbstractSubjectRestClient restClient;
+
+    private final String realm = "/";
+
+    public AttributableDataProvider(final AbstractSubjectRestClient restClient,
+            final int paginatorRows, final boolean filtered) {
+
+        super();
+
+        this.restClient = restClient;
+        this.filtered = filtered;
+        this.paginatorRows = paginatorRows;
+
+        // default sorting
+        setSort("key", SortOrder.ASCENDING);
+
+        this.comparator = new SortableAttributableProviderComparator(this);
+    }
+
+    public void setFIQL(final String fiql) {
+        this.fiql = fiql;
+    }
+
+    @Override
+    public Iterator<? extends AbstractAttributableTO> iterator(final long first, final long count) {
+        List<? extends AbstractAttributableTO> result;
+
+        final int page = ((int) first / paginatorRows);
+
+        if (filtered) {
+            result = fiql == null
+                    ? Collections.<AbstractAttributableTO>emptyList()
+                    : restClient.search(realm, fiql, (page < 0 ? 0 : page) + 1, paginatorRows, getSort());
+        } else {
+            result = restClient.list(realm, (page < 0 ? 0 : page) + 1, paginatorRows, getSort());
+        }
+
+        Collections.sort(result, comparator);
+        return result.iterator();
+    }
+
+    @Override
+    public long size() {
+        long result;
+
+        if (filtered) {
+            result = fiql == null
+                    ? 0
+                    : restClient.searchCount(realm, fiql);
+        } else {
+            result = restClient.count(realm);
+        }
+
+        return result;
+    }
+
+    @Override
+    public IModel<AbstractAttributableTO> model(final AbstractAttributableTO object) {
+        return new CompoundPropertyModel<>(object);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/34ec6712/client/console/src/main/java/org/apache/syncope/client/console/commons/JexlHelpUtils.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/JexlHelpUtils.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/JexlHelpUtils.java
new file mode 100644
index 0000000..ab8dcfb
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/JexlHelpUtils.java
@@ -0,0 +1,59 @@
+/*
+ * 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.commons;
+
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.link.ExternalLink;
+
+public final class JexlHelpUtils {
+
+    private static final String JEXL_SYNTAX_URL = "http://commons.apache.org/jexl/reference/syntax.html";
+
+    private JexlHelpUtils() {
+        // private constructor for static utility class
+    }
+
+    public static WebMarkupContainer getJexlHelpWebContainer(final String wicketId) {
+        final WebMarkupContainer jexlHelp = new WebMarkupContainer(wicketId);
+        jexlHelp.setVisible(false);
+        jexlHelp.setOutputMarkupPlaceholderTag(true);
+        jexlHelp.setOutputMarkupId(true);
+        jexlHelp.add(new ExternalLink("jexlLink", JEXL_SYNTAX_URL));
+        return jexlHelp;
+    }
+
+    public static AjaxLink<Void> getAjaxLink(final WebMarkupContainer wmc, final String wicketId) {
+        AjaxLink<Void> questionMarkJexlHelp = new AjaxLink<Void>(wicketId) {
+
+            private static final long serialVersionUID = -1838017408000591382L;
+
+            private boolean toogle = false;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target) {
+                toogle ^= true;
+                wmc.setVisible(toogle);
+                target.add(wmc);
+            }
+        };
+        return questionMarkJexlHelp;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/34ec6712/client/console/src/main/java/org/apache/syncope/client/console/pages/AbstractBasePage.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/AbstractBasePage.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/AbstractBasePage.java
index d63de02..bd88711 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/pages/AbstractBasePage.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/AbstractBasePage.java
@@ -22,6 +22,7 @@ import org.apache.syncope.client.console.commons.Constants;
 import org.apache.syncope.client.console.init.MIMETypesLoader;
 import org.apache.syncope.client.console.panels.NotificationPanel;
 import org.apache.syncope.client.console.rest.ConfigurationRestClient;
+import org.apache.syncope.client.console.rest.ConnectorRestClient;
 import org.apache.syncope.client.console.rest.ReportRestClient;
 import org.apache.syncope.client.console.rest.ResourceRestClient;
 import org.apache.syncope.client.console.rest.GroupRestClient;
@@ -51,6 +52,8 @@ public class AbstractBasePage extends WebPage {
 
     protected static final String APPLY = "apply";
 
+    protected static final String FORM = "form";
+
     protected final HeaderItem meta = new MetaHeaderItem("X-UA-Compatible", "IE=edge");
 
     @SpringBean
@@ -72,6 +75,9 @@ public class AbstractBasePage extends WebPage {
     protected ResourceRestClient resourceRestClient;
 
     @SpringBean
+    protected ConnectorRestClient connectorRestClient;
+
+    @SpringBean
     protected ReportRestClient reportRestClient;
 
     @SpringBean

http://git-wip-us.apache.org/repos/asf/syncope/blob/34ec6712/client/console/src/main/java/org/apache/syncope/client/console/pages/AbstractStatusModalPage.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/AbstractStatusModalPage.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/AbstractStatusModalPage.java
new file mode 100644
index 0000000..f5dfbca
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/AbstractStatusModalPage.java
@@ -0,0 +1,30 @@
+/*
+ * 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.wicket.markup.html.panel.Fragment;
+
+public class AbstractStatusModalPage extends BaseModalPage {
+
+    private static final long serialVersionUID = 6633408683036028540L;
+
+    public AbstractStatusModalPage() {
+        add(new Fragment("pwdMgtFields", "emptyFragment", this));
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/34ec6712/client/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
index 71a9c7f..20c3458 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/BasePage.java
@@ -21,6 +21,7 @@ package org.apache.syncope.client.console.pages;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.commons.Constants;
 import org.apache.syncope.client.console.rest.UserWorkflowRestClient;
+import org.apache.syncope.client.console.topology.Topology;
 import org.apache.syncope.common.lib.types.Entitlement;
 import org.apache.wicket.Component;
 import org.apache.wicket.Page;
@@ -92,6 +93,10 @@ public class BasePage extends AbstractBasePage implements IAjaxIndicatorAware {
         add(liContainer);
         liContainer.add(new BookmarkablePageLink<>("realms", Realms.class));
         
+        liContainer = new WebMarkupContainer(getLIContainerId("topology"));
+        add(liContainer);
+        liContainer.add(new BookmarkablePageLink<>("topology", Topology.class));
+        
         add(new BookmarkablePageLink<Page>("logout", Logout.class));
 
         // set 'active' menu item
@@ -150,7 +155,7 @@ public class BasePage extends AbstractBasePage implements IAjaxIndicatorAware {
      * @param window window
      * @param container container
      */
-    protected void setWindowClosedCallback(final ModalWindow window, final WebMarkupContainer container) {
+    public void setWindowClosedCallback(final ModalWindow window, final WebMarkupContainer container) {
         window.setWindowClosedCallback(new ModalWindow.WindowClosedCallback() {
 
             private static final long serialVersionUID = 8804221891699487139L;

http://git-wip-us.apache.org/repos/asf/syncope/blob/34ec6712/client/console/src/main/java/org/apache/syncope/client/console/pages/BulkActionModalPage.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/BulkActionModalPage.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/BulkActionModalPage.java
new file mode 100644
index 0000000..65dfa58
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/BulkActionModalPage.java
@@ -0,0 +1,166 @@
+/*
+ * 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.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.rest.BaseRestClient;
+import org.apache.syncope.client.console.wicket.ajax.markup.html.ClearIndicatingAjaxButton;
+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.common.lib.to.BulkAction;
+import org.apache.syncope.common.lib.to.BulkActionResult;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.form.AjaxButton;
+import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
+import org.apache.wicket.extensions.ajax.markup.html.repeater.data.table.AjaxFallbackDefaultDataTable;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
+import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.ResourceModel;
+import org.springframework.beans.BeanUtils;
+
+public class BulkActionModalPage<T, S> extends BaseModalPage {
+
+    private static final long serialVersionUID = 4114026480146090962L;
+
+    public BulkActionModalPage(
+            final ModalWindow window,
+            final Collection<T> items,
+            final List<IColumn<T, S>> columns,
+            final Collection<ActionLink.ActionType> actions,
+            final BaseRestClient bulkActionExecutor,
+            final String idFieldName,
+            final String pageId) {
+
+        super();
+
+        final SortableDataProvider<T, S> dataProvider = new SortableDataProvider<T, S>() {
+
+            private static final long serialVersionUID = 5291903859908641954L;
+
+            @Override
+            public Iterator<? extends T> iterator(final long first, final long count) {
+                return items.iterator();
+            }
+
+            @Override
+            public long size() {
+                return items.size();
+            }
+
+            @Override
+            public IModel<T> model(final T object) {
+                return new CompoundPropertyModel<>(object);
+            }
+        };
+
+        add(new AjaxFallbackDefaultDataTable<>(
+                "selectedObjects",
+                new ArrayList<>(columns.subList(1, columns.size() - 1)),
+                dataProvider,
+                Integer.MAX_VALUE).setVisible(items != null && !items.isEmpty()));
+
+        @SuppressWarnings("rawtypes")
+        final ActionLinksPanel actionPanel = new ActionLinksPanel("actions", new Model(), getPageReference());
+        add(actionPanel);
+
+        for (ActionLink.ActionType action : actions) {
+            final BulkAction bulkAction = new BulkAction();
+            for (T item : items) {
+                try {
+                    bulkAction.getTargets().add(getTargetId(item, idFieldName).toString());
+                } catch (IllegalAccessException | InvocationTargetException e) {
+                    LOG.error("Error retrieving item id {}", idFieldName, e);
+                }
+            }
+
+            switch (action) {
+                case DELETE:
+                    bulkAction.setOperation(BulkAction.Type.DELETE);
+                    break;
+                case SUSPEND:
+                    bulkAction.setOperation(BulkAction.Type.SUSPEND);
+                    break;
+                case REACTIVATE:
+                    bulkAction.setOperation(BulkAction.Type.REACTIVATE);
+                    break;
+                case EXECUTE:
+                    bulkAction.setOperation(BulkAction.Type.EXECUTE);
+                    break;
+                case DRYRUN:
+                    bulkAction.setOperation(BulkAction.Type.DRYRUN);
+                    break;
+                default:
+                    LOG.error("Bulk action type not supported");
+            }
+
+            actionPanel.add(new ActionLink() {
+
+                private static final long serialVersionUID = -3722207913631435501L;
+
+                @Override
+                public void onClick(final AjaxRequestTarget target) {
+                    try {
+                        final BulkActionResult res = (BulkActionResult) bulkActionExecutor.getClass().
+                                getMethod("bulkAction", BulkAction.class).invoke(bulkActionExecutor, bulkAction);
+
+                        setResponsePage(new BulkActionResultModalPage<>(window, items, columns, res, idFieldName));
+                    } catch (NoSuchMethodException | SecurityException | IllegalAccessException 
+                            | IllegalArgumentException | InvocationTargetException e) {
+                        error(getString(Constants.ERROR)
+                                + ": Operation " + bulkAction.getOperation() + " not supported");
+                        feedbackPanel.refresh(target);
+                    }
+
+                }
+            }, action, pageId, !items.isEmpty());
+        }
+
+        final Form<Void> form = new Form<>(FORM);
+        add(form);
+
+        final AjaxButton cancel = new ClearIndicatingAjaxButton(CANCEL, new ResourceModel(CANCEL), getPageReference()) {
+
+            private static final long serialVersionUID = -958724007591692537L;
+
+            @Override
+            protected void onSubmitInternal(final AjaxRequestTarget target, final Form<?> form) {
+                window.close(target);
+            }
+        };
+
+        cancel.setDefaultFormProcessing(false);
+        form.add(cancel);
+    }
+
+    private Object getTargetId(final Object target, final String idFieldName)
+            throws IllegalAccessException, InvocationTargetException {
+
+        return BeanUtils.getPropertyDescriptor(target.getClass(), idFieldName).
+                getReadMethod().invoke(target, new Object[0]);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/34ec6712/client/console/src/main/java/org/apache/syncope/client/console/pages/BulkActionResultModalPage.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/BulkActionResultModalPage.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/BulkActionResultModalPage.java
new file mode 100644
index 0000000..72a789f
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/BulkActionResultModalPage.java
@@ -0,0 +1,99 @@
+/*
+ * 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.Collection;
+import java.util.Iterator;
+import java.util.List;
+import org.apache.syncope.client.console.wicket.extensions.markup.html.repeater.data.table.ActionResultColumn;
+import org.apache.syncope.common.lib.to.BulkActionResult;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxLink;
+import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
+import org.apache.wicket.extensions.ajax.markup.html.repeater.data.table.AjaxFallbackDefaultDataTable;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
+import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+
+/**
+ * Show user or group status after performing a successful operation.
+ * @param <T>
+ * @param <S>
+ */
+public class BulkActionResultModalPage<T, S> extends BaseModalPage {
+
+    /**
+     * Serial version id.
+     */
+    private static final long serialVersionUID = 2646115294319713724L;
+
+    public BulkActionResultModalPage(
+            final ModalWindow window,
+            final Collection<T> items,
+            final List<IColumn<T, S>> columns,
+            final BulkActionResult results,
+            final String idFieldName) {
+
+        super();
+
+        final List<IColumn<T, S>> newColumnList = new ArrayList<>(columns.subList(1, columns.size() - 1));
+        newColumnList.add(newColumnList.size(), new ActionResultColumn<T, S>(results, idFieldName));
+
+        final SortableDataProvider<T, S> dataProvider = new SortableDataProvider<T, S>() {
+
+            private static final long serialVersionUID = 5291903859908641954L;
+
+            @Override
+            public Iterator<? extends T> iterator(final long first, final long count) {
+                return items.iterator();
+            }
+
+            @Override
+            public long size() {
+                return items.size();
+            }
+
+            @Override
+            public IModel<T> model(final T object) {
+                return new CompoundPropertyModel<>(object);
+            }
+        };
+
+        add(new AjaxFallbackDefaultDataTable<>(
+                "selectedObjects",
+                newColumnList,
+                dataProvider,
+                Integer.MAX_VALUE).setVisible(items != null && !items.isEmpty()));
+
+        final AjaxLink<Void> close = new IndicatingAjaxLink<Void>("close") {
+
+            private static final long serialVersionUID = -7978723352517770644L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target) {
+                window.close(target);
+            }
+        };
+
+        add(close);
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/34ec6712/client/console/src/main/java/org/apache/syncope/client/console/pages/ConnectorModalPage.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/ConnectorModalPage.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/ConnectorModalPage.java
new file mode 100644
index 0000000..f35da6a
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/ConnectorModalPage.java
@@ -0,0 +1,477 @@
+/*
+ * 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.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.rest.ConnectorRestClient;
+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.client.console.wicket.markup.html.form.SpinnerFieldPanel;
+import org.apache.syncope.client.console.wicket.markup.html.list.ConnConfPropertyListView;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.ConnBundleTO;
+import org.apache.syncope.common.lib.to.ConnInstanceTO;
+import org.apache.syncope.common.lib.to.ConnPoolConfTO;
+import org.apache.syncope.common.lib.types.ConnConfPropSchema;
+import org.apache.syncope.common.lib.types.ConnConfProperty;
+import org.apache.syncope.common.lib.types.ConnectorCapability;
+import org.apache.syncope.common.lib.types.Entitlement;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.form.AjaxFormChoiceComponentUpdatingBehavior;
+import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
+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.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.CheckBoxMultipleChoice;
+import org.apache.wicket.markup.html.form.DropDownChoice;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.LoadableDetachableModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.PropertyModel;
+import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.spring.injection.annot.SpringBean;
+import org.apache.wicket.validation.validator.RangeValidator;
+
+/**
+ * Modal window with Connector form.
+ */
+public class ConnectorModalPage extends BaseModalPage {
+
+    private static final long serialVersionUID = -2025535531121434050L;
+
+    @SpringBean
+    private ConnectorRestClient restClient;
+
+    private final Map<String, Map<String, Map<String, ConnBundleTO>>> mapConnBundleTOs;
+
+    private final List<ConnectorCapability> selectedCapabilities;
+
+    private ConnBundleTO bundleTO;
+
+    private List<ConnConfProperty> properties;
+
+    private final WebMarkupContainer propertiesContainer;
+
+    public ConnectorModalPage(final PageReference pageRef, final ModalWindow window,
+            final ConnInstanceTO connInstanceTO) {
+
+        super();
+
+        this.add(new Label("new", connInstanceTO.getKey() == 0
+                ? new ResourceModel("new")
+                : new Model<>(StringUtils.EMPTY)));
+        this.add(new Label("key", connInstanceTO.getKey() == 0
+                ? StringUtils.EMPTY
+                : connInstanceTO.getKey()));
+
+        // general data setup
+        selectedCapabilities = new ArrayList<>(connInstanceTO.getKey() == 0
+                ? EnumSet.noneOf(ConnectorCapability.class)
+                : connInstanceTO.getCapabilities());
+
+        mapConnBundleTOs = new HashMap<>();
+        for (ConnBundleTO connBundleTO : restClient.getAllBundles()) {
+            // by location
+            if (!mapConnBundleTOs.containsKey(connBundleTO.getLocation())) {
+                mapConnBundleTOs.put(connBundleTO.getLocation(), new HashMap<String, Map<String, ConnBundleTO>>());
+            }
+            final Map<String, Map<String, ConnBundleTO>> byLocation = mapConnBundleTOs.get(connBundleTO.getLocation());
+
+            // by name
+            if (!byLocation.containsKey(connBundleTO.getBundleName())) {
+                byLocation.put(connBundleTO.getBundleName(), new HashMap<String, ConnBundleTO>());
+            }
+            final Map<String, ConnBundleTO> byName = byLocation.get(connBundleTO.getBundleName());
+
+            // by version
+            if (!byName.containsKey(connBundleTO.getVersion())) {
+                byName.put(connBundleTO.getVersion(), connBundleTO);
+            }
+        }
+
+        bundleTO = getSelectedBundleTO(connInstanceTO);
+        properties = fillProperties(bundleTO, connInstanceTO);
+
+        // form - first tab
+        final Form<ConnInstanceTO> connectorForm = new Form<>(FORM);
+        connectorForm.setModel(new CompoundPropertyModel<>(connInstanceTO));
+        connectorForm.setOutputMarkupId(true);
+        add(connectorForm);
+
+        propertiesContainer = new WebMarkupContainer("container");
+        propertiesContainer.setOutputMarkupId(true);
+        connectorForm.add(propertiesContainer);
+
+        final Form<ConnInstanceTO> connectorPropForm = new Form<>("connectorPropForm");
+        connectorPropForm.setModel(new CompoundPropertyModel<>(connInstanceTO));
+        connectorPropForm.setOutputMarkupId(true);
+        propertiesContainer.add(connectorPropForm);
+
+        final AjaxTextFieldPanel displayName = new AjaxTextFieldPanel(
+                "displayName", "display name", new PropertyModel<String>(connInstanceTO, "displayName"));
+        displayName.setOutputMarkupId(true);
+        displayName.addRequiredLabel();
+        connectorForm.add(displayName);
+
+        final AjaxDropDownChoicePanel<String> location = new AjaxDropDownChoicePanel<>("location", "location",
+                new Model<>(bundleTO == null ? null : bundleTO.getLocation()));
+        ((DropDownChoice<String>) location.getField()).setNullValid(true);
+        location.setStyleSheet("long_dynamicsize");
+        location.setChoices(new ArrayList<>(mapConnBundleTOs.keySet()));
+        location.setRequired(true);
+        location.addRequiredLabel();
+        location.setOutputMarkupId(true);
+        location.setEnabled(connInstanceTO.getKey() == 0);
+        location.getField().setOutputMarkupId(true);
+        connectorForm.add(location);
+
+        final AjaxDropDownChoicePanel<String> connectorName = new AjaxDropDownChoicePanel<>("connectorName",
+                "connectorName",
+                new Model<>(bundleTO == null ? null : bundleTO.getBundleName()));
+        ((DropDownChoice<String>) connectorName.getField()).setNullValid(true);
+        connectorName.setStyleSheet("long_dynamicsize");
+        connectorName.setChoices(bundleTO == null
+                ? new ArrayList<String>()
+                : new ArrayList<>(mapConnBundleTOs.get(connInstanceTO.getLocation()).keySet()));
+        connectorName.setRequired(true);
+        connectorName.addRequiredLabel();
+        connectorName.setEnabled(connInstanceTO.getLocation() != null);
+        connectorName.setOutputMarkupId(true);
+        connectorName.setEnabled(connInstanceTO.getKey() == 0);
+        connectorName.getField().setOutputMarkupId(true);
+        connectorForm.add(connectorName);
+
+        final AjaxDropDownChoicePanel<String> version = new AjaxDropDownChoicePanel<>("version", "version",
+                new Model<>(bundleTO == null ? null : bundleTO.getVersion()));
+        version.setStyleSheet("long_dynamicsize");
+        version.setChoices(bundleTO == null
+                ? new ArrayList<String>()
+                : new ArrayList<>(mapConnBundleTOs.get(connInstanceTO.getLocation()).
+                        get(connInstanceTO.getBundleName()).keySet()));
+        version.setRequired(true);
+        version.addRequiredLabel();
+        version.setEnabled(connInstanceTO.getBundleName() != null);
+        version.setOutputMarkupId(true);
+        version.addRequiredLabel();
+        version.getField().setOutputMarkupId(true);
+        connectorForm.add(version);
+
+        final SpinnerFieldPanel<Integer> connRequestTimeout = new SpinnerFieldPanel<>("connRequestTimeout",
+                "connRequestTimeout", Integer.class,
+                new PropertyModel<Integer>(connInstanceTO, "connRequestTimeout"), 0, null);
+        connRequestTimeout.getField().add(new RangeValidator<>(0, Integer.MAX_VALUE));
+        connectorForm.add(connRequestTimeout);
+
+        if (connInstanceTO.getPoolConf() == null) {
+            connInstanceTO.setPoolConf(new ConnPoolConfTO());
+        }
+        final SpinnerFieldPanel<Integer> poolMaxObjects = new SpinnerFieldPanel<>("poolMaxObjects", "poolMaxObjects",
+                Integer.class,
+                new PropertyModel<Integer>(connInstanceTO.getPoolConf(), "maxObjects"), 0, null);
+        poolMaxObjects.getField().add(new RangeValidator<>(0, Integer.MAX_VALUE));
+        connectorForm.add(poolMaxObjects);
+        final SpinnerFieldPanel<Integer> poolMinIdle = new SpinnerFieldPanel<>("poolMinIdle", "poolMinIdle",
+                Integer.class,
+                new PropertyModel<Integer>(connInstanceTO.getPoolConf(), "minIdle"), 0, null);
+        poolMinIdle.getField().add(new RangeValidator<>(0, Integer.MAX_VALUE));
+        connectorForm.add(poolMinIdle);
+        final SpinnerFieldPanel<Integer> poolMaxIdle = new SpinnerFieldPanel<>("poolMaxIdle", "poolMaxIdle",
+                Integer.class,
+                new PropertyModel<Integer>(connInstanceTO.getPoolConf(), "maxIdle"), 0, null);
+        poolMaxIdle.getField().add(new RangeValidator<>(0, Integer.MAX_VALUE));
+        connectorForm.add(poolMaxIdle);
+        final SpinnerFieldPanel<Long> poolMaxWait = new SpinnerFieldPanel<>("poolMaxWait", "poolMaxWait", Long.class,
+                new PropertyModel<Long>(connInstanceTO.getPoolConf(), "maxWait"), 0L, null);
+        poolMaxWait.getField().add(new RangeValidator<>(0L, Long.MAX_VALUE));
+        connectorForm.add(poolMaxWait);
+        final SpinnerFieldPanel<Long> poolMinEvictableIdleTime = new SpinnerFieldPanel<>("poolMinEvictableIdleTime",
+                "poolMinEvictableIdleTime", Long.class,
+                new PropertyModel<Long>(connInstanceTO.getPoolConf(), "minEvictableIdleTimeMillis"),
+                0L, null);
+        poolMinEvictableIdleTime.getField().add(new RangeValidator<>(0L, Long.MAX_VALUE));
+        connectorForm.add(poolMinEvictableIdleTime);
+
+        // form - first tab - onchange()
+        location.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+            private static final long serialVersionUID = -1107858522700306810L;
+
+            @Override
+            protected void onUpdate(final AjaxRequestTarget target) {
+                ((DropDownChoice<String>) location.getField()).setNullValid(false);
+                connInstanceTO.setLocation(location.getModelObject());
+                target.add(location);
+
+                connectorName.setChoices(new ArrayList<>(
+                        mapConnBundleTOs.get(location.getModelObject()).keySet()));
+                connectorName.setEnabled(true);
+                connectorName.getField().setModelValue(null);
+                target.add(connectorName);
+
+                version.setChoices(new ArrayList<String>());
+                version.getField().setModelValue(null);
+                version.setEnabled(false);
+                target.add(version);
+
+                properties.clear();
+                target.add(propertiesContainer);
+            }
+        });
+        connectorName.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+            private static final long serialVersionUID = -1107858522700306810L;
+
+            @Override
+            protected void onUpdate(final AjaxRequestTarget target) {
+                ((DropDownChoice<String>) connectorName.getField()).setNullValid(false);
+                connInstanceTO.setBundleName(connectorName.getModelObject());
+                target.add(connectorName);
+
+                List<String> versions = new ArrayList<>(
+                        mapConnBundleTOs.get(location.getModelObject()).get(connectorName.getModelObject()).keySet());
+                version.setChoices(versions);
+                version.setEnabled(true);
+                if (versions.size() == 1) {
+                    selectVersion(target, connInstanceTO, version, versions.get(0));
+                    version.getField().setModelObject(versions.get(0));
+                } else {
+                    version.getField().setModelValue(null);
+                    properties.clear();
+                    target.add(propertiesContainer);
+                }
+                target.add(version);
+            }
+        });
+        version.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+            private static final long serialVersionUID = -1107858522700306810L;
+
+            @Override
+            protected void onUpdate(final AjaxRequestTarget target) {
+                selectVersion(target, connInstanceTO, version, version.getModelObject());
+            }
+        });
+
+        // form - second tab (properties)
+        final ListView<ConnConfProperty> connPropView = new ConnConfPropertyListView("connectorProperties",
+                new PropertyModel<List<ConnConfProperty>>(this, "properties"),
+                true, connInstanceTO.getConfiguration());
+        connPropView.setOutputMarkupId(true);
+        connectorPropForm.add(connPropView);
+
+        final AjaxButton check = new IndicatingAjaxButton("check", new ResourceModel("check")) {
+
+            private static final long serialVersionUID = -7978723352517770644L;
+
+            @Override
+            public void onSubmit(final AjaxRequestTarget target, final Form<?> form) {
+                final ConnInstanceTO conn = (ConnInstanceTO) form.getModelObject();
+
+                // ensure that connector bundle information is in sync
+                conn.setBundleName(bundleTO.getBundleName());
+                conn.setVersion(bundleTO.getVersion());
+                conn.setConnectorName(bundleTO.getConnectorName());
+
+                if (restClient.check(conn)) {
+                    info(getString("success_connection"));
+                } else {
+                    error(getString("error_connection"));
+                }
+
+                feedbackPanel.refresh(target);
+            }
+        };
+        connectorPropForm.add(check);
+
+        // form - third tab (capabilities)
+        final IModel<List<ConnectorCapability>> capabilities
+                = new LoadableDetachableModel<List<ConnectorCapability>>() {
+
+                    private static final long serialVersionUID = 5275935387613157437L;
+
+                    @Override
+                    protected List<ConnectorCapability> load() {
+                        return Arrays.asList(ConnectorCapability.values());
+                    }
+                };
+        CheckBoxMultipleChoice<ConnectorCapability> capabilitiesPalette = new CheckBoxMultipleChoice<>(
+                "capabilitiesPalette",
+                new PropertyModel<List<ConnectorCapability>>(this, "selectedCapabilities"), capabilities);
+
+        capabilitiesPalette.add(new AjaxFormChoiceComponentUpdatingBehavior() {
+
+            private static final long serialVersionUID = -1107858522700306810L;
+
+            @Override
+            protected void onUpdate(final AjaxRequestTarget target) {
+            }
+        });
+
+        connectorForm.add(capabilitiesPalette);
+
+        // form - submit / cancel buttons
+        final AjaxButton submit = new IndicatingAjaxButton(APPLY, new Model<>(getString(SUBMIT))) {
+
+            private static final long serialVersionUID = -958724007591692537L;
+
+            @Override
+            protected void onSubmit(final AjaxRequestTarget target, final Form<?> form) {
+                final ConnInstanceTO conn = (ConnInstanceTO) form.getModelObject();
+
+                conn.setConnectorName(bundleTO.getConnectorName());
+                conn.setBundleName(bundleTO.getBundleName());
+                conn.setVersion(bundleTO.getVersion());
+                conn.getConfiguration().clear();
+                conn.getConfiguration().addAll(connPropView.getModelObject());
+
+                // Set the model object's capabilities to capabilitiesPalette's converted Set
+                conn.getCapabilities().clear();
+                conn.getCapabilities().addAll(selectedCapabilities.isEmpty()
+                        ? EnumSet.noneOf(ConnectorCapability.class)
+                        : EnumSet.copyOf(selectedCapabilities));
+
+                // Reset pool configuration if all fields are null
+                if (conn.getPoolConf() != null
+                        && conn.getPoolConf().getMaxIdle() == null
+                        && conn.getPoolConf().getMaxObjects() == null
+                        && conn.getPoolConf().getMaxWait() == null
+                        && conn.getPoolConf().getMinEvictableIdleTimeMillis() == null
+                        && conn.getPoolConf().getMinIdle() == null) {
+
+                    conn.setPoolConf(null);
+                }
+
+                try {
+                    if (connInstanceTO.getKey() == 0) {
+                        restClient.create(conn);
+                    } else {
+                        restClient.update(conn);
+                    }
+
+                    ((BasePage) pageRef.getPage()).setModalResult(true);
+                    window.close(target);
+                } catch (SyncopeClientException e) {
+                    error(getString(Constants.ERROR) + ": " + e.getMessage());
+                    feedbackPanel.refresh(target);
+                    ((BasePage) pageRef.getPage()).setModalResult(false);
+                    LOG.error("While creating or updating connector {}", conn, e);
+                }
+            }
+
+            @Override
+            protected void onError(final AjaxRequestTarget target, final Form<?> form) {
+                feedbackPanel.refresh(target);
+            }
+        };
+        String entitlements = connInstanceTO.getKey() == 0
+                ? Entitlement.CONNECTOR_CREATE : Entitlement.CONNECTOR_UPDATE;
+
+        MetaDataRoleAuthorizationStrategy.authorize(submit, ENABLE, entitlements);
+        connectorForm.add(submit);
+
+        final IndicatingAjaxButton cancel = new IndicatingAjaxButton(CANCEL, new ResourceModel(CANCEL)) {
+
+            private static final long serialVersionUID = -958724007591692537L;
+
+            @Override
+            protected void onSubmit(final AjaxRequestTarget target, final Form<?> form) {
+                window.close(target);
+            }
+        };
+        cancel.setDefaultFormProcessing(false);
+        connectorForm.add(cancel);
+    }
+
+    private ConnBundleTO getSelectedBundleTO(final ConnInstanceTO connInstanceTO) {
+        ConnBundleTO result = null;
+        if (connInstanceTO != null
+                && StringUtils.isNotBlank(connInstanceTO.getLocation())
+                && StringUtils.isNotBlank(connInstanceTO.getBundleName())
+                && StringUtils.isNotBlank(connInstanceTO.getVersion())
+                && mapConnBundleTOs.containsKey(connInstanceTO.getLocation())) {
+
+            Map<String, Map<String, ConnBundleTO>> byLocation = mapConnBundleTOs.get(connInstanceTO.getLocation());
+            if (byLocation.containsKey(connInstanceTO.getBundleName())) {
+                Map<String, ConnBundleTO> byName = byLocation.get(connInstanceTO.getBundleName());
+                if (byName.containsKey(connInstanceTO.getVersion())) {
+                    result = byName.get(connInstanceTO.getVersion());
+                }
+            }
+        }
+        return result;
+    }
+
+    private List<ConnConfProperty> fillProperties(final ConnBundleTO bundleTO, final ConnInstanceTO connInstanceTO) {
+        final List<ConnConfProperty> props = new ArrayList<>();
+
+        if (bundleTO != null) {
+            for (ConnConfPropSchema key : bundleTO.getProperties()) {
+                final ConnConfProperty property = new ConnConfProperty();
+                property.setSchema(key);
+                if (connInstanceTO.getKey() != 0
+                        && connInstanceTO.getConfigurationMap().containsKey(key.getName())
+                        && connInstanceTO.getConfigurationMap().get(key.getName()).getValues() != null) {
+
+                    property.getValues().addAll(connInstanceTO.getConfigurationMap().get(key.getName()).getValues());
+                    property.setOverridable(connInstanceTO.getConfigurationMap().get(key.getName()).isOverridable());
+                }
+
+                if (property.getValues().isEmpty() && !key.getDefaultValues().isEmpty()) {
+                    property.getValues().addAll(key.getDefaultValues());
+                }
+
+                props.add(property);
+            }
+        }
+
+        // re-order properties (implements Comparable)
+        Collections.sort(props);
+        return props;
+    }
+
+    private void selectVersion(final AjaxRequestTarget target, final ConnInstanceTO connInstanceTO,
+            final AjaxDropDownChoicePanel<String> version, final String versionValue) {
+
+        connInstanceTO.setVersion(versionValue);
+        target.add(version);
+
+        bundleTO = getSelectedBundleTO(connInstanceTO);
+        properties = fillProperties(bundleTO, connInstanceTO);
+        target.add(propertiesContainer);
+    }
+
+    public List<ConnConfProperty> getProperties() {
+        return properties;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/34ec6712/client/console/src/main/java/org/apache/syncope/client/console/pages/ProvisioningModalPage.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/ProvisioningModalPage.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/ProvisioningModalPage.java
new file mode 100644
index 0000000..af1d0b5
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/ProvisioningModalPage.java
@@ -0,0 +1,253 @@
+/*
+ * 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.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.commons.status.AbstractStatusBeanProvider;
+import org.apache.syncope.client.console.commons.status.ConnObjectWrapper;
+import org.apache.syncope.client.console.commons.status.StatusBean;
+import org.apache.syncope.client.console.commons.status.StatusUtils;
+import org.apache.syncope.client.console.panels.ActionDataTablePanel;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
+import org.apache.syncope.client.lib.SyncopeClient;
+import org.apache.syncope.common.lib.to.AbstractAttributableTO;
+import org.apache.syncope.common.lib.to.AbstractSubjectTO;
+import org.apache.syncope.common.lib.to.BulkActionResult;
+import org.apache.syncope.common.lib.to.ResourceTO;
+import org.apache.syncope.common.lib.to.GroupTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.ResourceDeassociationActionType;
+import org.apache.syncope.common.lib.wrap.AbstractWrappable;
+import org.apache.syncope.common.lib.wrap.SubjectKey;
+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.extensions.markup.html.repeater.data.grid.ICellPopulator;
+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.SortParam;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.StringResourceModel;
+
+public class ProvisioningModalPage<T extends AbstractAttributableTO> extends AbstractStatusModalPage {
+
+    private static final long serialVersionUID = -4285220460543213901L;
+
+    private static final int ROWS_PER_PAGE = 10;
+
+    private final ResourceTO resourceTO;
+
+    private final Class<? extends AbstractAttributableTO> typeRef;
+
+    private final PageReference pageRef;
+
+    private final ModalWindow window;
+
+    private final StatusUtils statusUtils;
+
+    private final String realm = "/";
+
+    public ProvisioningModalPage(
+            final PageReference pageRef,
+            final ModalWindow window,
+            final ResourceTO resourceTO,
+            final Class<T> typeRef) {
+
+        super();
+
+        this.pageRef = pageRef;
+        this.window = window;
+        this.resourceTO = resourceTO;
+        this.typeRef = typeRef;
+
+        statusUtils = new StatusUtils((UserTO.class.isAssignableFrom(typeRef) ? userRestClient : groupRestClient));
+
+        add(new Label("displayName", StringUtils.EMPTY));
+
+        final List<IColumn<StatusBean, String>> columns = new ArrayList<>();
+        columns.add(new PropertyColumn<StatusBean, String>(
+                new StringResourceModel("key", this, null, "Attributable key"),
+                "attributableKey", "attributableKey"));
+        columns.add(new PropertyColumn<StatusBean, String>(
+                new StringResourceModel("name", this, null, "Attributable name"),
+                "attributableName", "attributableName"));
+        columns.add(new PropertyColumn<StatusBean, String>(
+                new StringResourceModel("resourceName", this, null, "Resource name"),
+                "resourceName", "resourceName"));
+        columns.add(new PropertyColumn<StatusBean, String>(
+                new StringResourceModel("accountLink", this, null, "Account link"),
+                "accountLink", "accountLink"));
+        columns.add(new AbstractColumn<StatusBean, String>(
+                new StringResourceModel("status", this, null, "")) {
+
+                    private static final long serialVersionUID = -3503023501954863131L;
+
+                    @Override
+                    public String getCssClass() {
+                        return "action";
+                    }
+
+                    @Override
+                    public void populateItem(
+                            final Item<ICellPopulator<StatusBean>> cellItem,
+                            final String componentId,
+                            final IModel<StatusBean> model) {
+                                cellItem.
+                                add(statusUtils.getStatusImagePanel(componentId, model.getObject().getStatus()));
+                            }
+                });
+
+        final ActionDataTablePanel<StatusBean, String> table = new ActionDataTablePanel<>(
+                "resourceDatatable",
+                columns,
+                (ISortableDataProvider<StatusBean, String>) new StatusBeanProvider(),
+                ROWS_PER_PAGE,
+                pageRef);
+
+        final String pageId = "Resources";
+
+        table.addAction(new ActionLink() {
+
+            private static final long serialVersionUID = -3722207913631435501L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target) {
+                try {
+                    bulkAssociationAction(target, ResourceDeassociationActionType.UNLINK, table, columns);
+                } catch (Exception e) {
+                    LOG.error("Error unlinkink resources", e);
+                    error(getString(Constants.ERROR) + ": " + e.getMessage());
+                    feedbackPanel.refresh(target);
+                }
+            }
+        }, ActionLink.ActionType.UNLINK, pageId);
+
+        table.addAction(new ActionLink() {
+
+            private static final long serialVersionUID = -3722207913631435501L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target) {
+                try {
+                    bulkAssociationAction(target, ResourceDeassociationActionType.DEPROVISION, table, columns);
+                } catch (Exception e) {
+                    LOG.error("Error de-provisioning user", e);
+                    error(getString(Constants.ERROR) + ": " + e.getMessage());
+                    feedbackPanel.refresh(target);
+                }
+            }
+        }, ActionLink.ActionType.DEPROVISION, pageId);
+
+        table.addAction(new ActionLink() {
+
+            private static final long serialVersionUID = -3722207913631435501L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target) {
+                try {
+                    bulkAssociationAction(target, ResourceDeassociationActionType.UNASSIGN, table, columns);
+                } catch (Exception e) {
+                    LOG.error("Error unassigning resources", e);
+                    error(getString(Constants.ERROR) + ": " + e.getMessage());
+                    feedbackPanel.refresh(target);
+                }
+            }
+        }, ActionLink.ActionType.UNASSIGN, pageId);
+
+        table.addCancelButton(window);
+
+        add(table);
+    }
+
+    private class StatusBeanProvider extends AbstractStatusBeanProvider {
+
+        private static final long serialVersionUID = 4287357360778016173L;
+
+        public StatusBeanProvider() {
+            super("accountLink");
+        }
+
+        @SuppressWarnings("unchecked")
+        @Override
+        public List<StatusBean> getStatusBeans() {
+            final String fiql = SyncopeClient.getUserSearchConditionBuilder().hasResources(resourceTO.getKey()).query();
+
+            final List<T> subjects = new ArrayList<>();
+            if (UserTO.class.isAssignableFrom(typeRef)) {
+                subjects.addAll((List<T>) userRestClient.search(
+                        realm, fiql, 1, ROWS_PER_PAGE, new SortParam<>("key", true)));
+            } else {
+                subjects.addAll((List<T>) groupRestClient.search(
+                        realm, fiql, 1, ROWS_PER_PAGE, new SortParam<>("key", true)));
+            }
+
+            final List<ConnObjectWrapper> connObjects = statusUtils.getConnectorObjects(
+                    (List<AbstractSubjectTO>) subjects, Collections.<String>singleton(resourceTO.getKey()));
+
+            final List<StatusBean> statusBeans = new ArrayList<>(connObjects.size() + 1);
+            final LinkedHashMap<String, StatusBean> initialStatusBeanMap = new LinkedHashMap<>(connObjects.size());
+
+            for (ConnObjectWrapper entry : connObjects) {
+                final StatusBean statusBean = statusUtils.getStatusBean(entry.getAttributable(),
+                        entry.getResourceName(),
+                        entry.getConnObjectTO(),
+                        GroupTO.class.isAssignableFrom(typeRef));
+
+                initialStatusBeanMap.put(entry.getResourceName(), statusBean);
+                statusBeans.add(statusBean);
+            }
+
+            return statusBeans;
+        }
+    }
+
+    private void bulkAssociationAction(
+            final AjaxRequestTarget target,
+            final ResourceDeassociationActionType type,
+            final ActionDataTablePanel<StatusBean, String> table,
+            final List<IColumn<StatusBean, String>> columns) {
+
+        final List<StatusBean> beans = new ArrayList<>(table.getModelObject());
+        List<SubjectKey> subjectKeys = new ArrayList<>();
+        for (StatusBean bean : beans) {
+            LOG.debug("Selected bean {}", bean);
+            subjectKeys.add(AbstractWrappable.getInstance(SubjectKey.class, bean.getAttributableId()));
+        }
+
+        if (beans.isEmpty()) {
+            window.close(target);
+        } else {
+            final BulkActionResult res = resourceRestClient.bulkAssociationAction(
+                    resourceTO.getKey(), typeRef, type, subjectKeys);
+
+            ((BasePage) pageRef.getPage()).setModalResult(true);
+
+            setResponsePage(new BulkActionResultModalPage<>(window, beans, columns, res, "attributableKey"));
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/34ec6712/client/console/src/main/java/org/apache/syncope/client/console/pages/Realms.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/Realms.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/Realms.java
index 6ff73f4..daee99c 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/pages/Realms.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/Realms.java
@@ -18,13 +18,160 @@
  */
 package org.apache.syncope.client.console.pages;
 
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.syncope.client.console.panels.Realm;
+import org.apache.syncope.client.console.rest.RealmRestClient;
+import org.apache.syncope.common.lib.to.RealmTO;
+import org.apache.wicket.MarkupContainer;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+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.panel.Fragment;
+import org.apache.wicket.markup.repeater.RepeatingView;
+import org.apache.wicket.model.PropertyModel;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.wicket.spring.injection.annot.SpringBean;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 public class Realms extends BasePage {
 
     private static final long serialVersionUID = -1100228004207271270L;
 
+    protected static final Logger LOG = LoggerFactory.getLogger(Realms.class);
+
+    @SpringBean
+    private RealmRestClient realmRestClient;
+
+    private final WebMarkupContainer content;
+
     public Realms(final PageParameters parameters) {
         super(parameters);
+
+        final List<RealmTO> realms = realmRestClient.list();
+        Collections.sort(realms, new RealmNameComparator());
+
+        add(getParentMap(realms), 0L, Realms.this);
+
+        content = new WebMarkupContainer("content");
+        content.setOutputMarkupId(true);
+        add(content);
+
+        content.add(new Label("header", "Root realm"));
+        content.add(new Label("body", "Root realm"));
+    }
+
+    private void add(final Map<Long, List<RealmTO>> parentMap, final Long key, final MarkupContainer container) {
+        final RepeatingView listItems = new RepeatingView("list");
+        container.add(listItems);
+
+        for (final RealmTO realm : parentMap.get(key)) {
+            final Fragment fragment;
+
+            final AjaxLink<Void> link = new AjaxLink<Void>("link") {
+
+                private static final long serialVersionUID = 1L;
+
+                @Override
+                public void onClick(final AjaxRequestTarget target) {
+                    send(Realms.this, Broadcast.EXACT, new ControlSidebarClick<>(realm, target));
+                }
+            };
+
+            link.add(new Label("name", new PropertyModel<String>(realm, "name")));
+
+            if (parentMap.containsKey(realm.getKey()) && !parentMap.get(realm.getKey()).isEmpty()) {
+                fragment = new Fragment(String.valueOf(realm.getKey()), "withChildren", Realms.this);
+                add(parentMap, realm.getKey(), fragment);
+            } else {
+                fragment = new Fragment(String.valueOf(realm.getKey()), "withoutChildren", Realms.this);
+            }
+
+            fragment.add(link);
+            listItems.add(fragment);
+        }
+    }
+
+    private Map<Long, List<RealmTO>> getParentMap(final List<RealmTO> realms) {
+        final Map<Long, List<RealmTO>> res = new HashMap<>();
+        res.put(0L, new ArrayList<RealmTO>());
+
+        final Map<Long, List<RealmTO>> cache = new HashMap<>();
+
+        for (RealmTO realm : realms) {
+            if (res.containsKey(realm.getParent())) {
+                res.get(realm.getParent()).add(realm);
+
+                final List<RealmTO> children = new ArrayList<>();
+                res.put(realm.getKey(), children);
+
+                if (cache.containsKey(realm.getKey())) {
+                    children.addAll(cache.get(realm.getKey()));
+                    cache.remove(realm.getKey());
+                }
+            } else if (cache.containsKey(realm.getParent())) {
+                cache.get(realm.getParent()).add(realm);
+            } else {
+                final List<RealmTO> children = new ArrayList<>();
+                children.add(realm);
+                cache.put(realm.getParent(), children);
+            }
+        }
+
+        return res;
+    }
+
+    private static class RealmNameComparator implements Comparator<RealmTO>, Serializable {
+
+        private static final long serialVersionUID = 7085057398406518811L;
+
+        @Override
+        public int compare(final RealmTO r1, final RealmTO r2) {
+            return r1.getName().compareTo(r2.getName());
+        }
+    }
+
+    @Override
+    public void onEvent(final IEvent<?> event) {
+        super.onEvent(event);
+
+        if (event.getPayload() instanceof ControlSidebarClick) {
+            @SuppressWarnings("unchecked")
+            final ControlSidebarClick<RealmTO> controlSidebarClick = ControlSidebarClick.class.cast(event.getPayload());
+            content.addOrReplace(new Label("header", controlSidebarClick.getObj().getName()));
+            content.addOrReplace(new Realm("body", controlSidebarClick.getObj()));
+            controlSidebarClick.getTarget().add(content);
+        }
+    }
+
+    private static class ControlSidebarClick<T> {
+
+        private final AjaxRequestTarget target;
+
+        private final T obj;
+
+        public ControlSidebarClick(
+                final T obj, final AjaxRequestTarget target) {
+            this.obj = obj;
+            this.target = target;
+        }
+
+        public T getObj() {
+            return obj;
+        }
+
+        public AjaxRequestTarget getTarget() {
+            return target;
+        }
+
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/34ec6712/client/console/src/main/java/org/apache/syncope/client/console/pages/ResourceModalPage.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/pages/ResourceModalPage.java b/client/console/src/main/java/org/apache/syncope/client/console/pages/ResourceModalPage.java
new file mode 100644
index 0000000..aa9dbfc
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/pages/ResourceModalPage.java
@@ -0,0 +1,204 @@
+/*
+ * 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.AnnotatedBeanPanel;
+import org.apache.syncope.client.console.panels.ResourceConnConfPanel;
+import org.apache.syncope.client.console.panels.ResourceDetailsPanel;
+import org.apache.syncope.client.console.panels.ResourceMappingPanel;
+import org.apache.syncope.client.console.panels.ResourceSecurityPanel;
+import org.apache.syncope.common.lib.to.MappingItemTO;
+import org.apache.syncope.common.lib.to.ResourceTO;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.common.lib.types.Entitlement;
+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.form.Form;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.ResourceModel;
+
+/**
+ * Modal window with Resource form.
+ */
+public class ResourceModalPage extends BaseModalPage {
+
+    private static final long serialVersionUID = 1734415311027284221L;
+
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    public ResourceModalPage(final PageReference pageRef, final ModalWindow window, final ResourceTO resourceTO,
+            final boolean createFlag) {
+
+        super();
+
+        final Form<ResourceTO> form = new Form<>(FORM);
+        form.setModel(new CompoundPropertyModel<>(resourceTO));
+
+        //--------------------------------
+        // Resource details panel
+        //--------------------------------
+        form.add(new ResourceDetailsPanel("details", resourceTO,
+                resourceRestClient.getPropagationActionsClasses(), createFlag));
+
+        form.add(new AnnotatedBeanPanel("systeminformation", resourceTO));
+        //--------------------------------
+
+        //--------------------------------
+        // Resource mapping panels
+        //--------------------------------
+        form.add(new ResourceMappingPanel("umapping", resourceTO, AttributableType.USER));
+        form.add(new ResourceMappingPanel("gmapping", resourceTO, AttributableType.GROUP));
+        //--------------------------------
+
+        //--------------------------------
+        // Resource connector configuration panel
+        //--------------------------------
+        ResourceConnConfPanel resourceConnConfPanel = new ResourceConnConfPanel("connconf", resourceTO, createFlag);
+        MetaDataRoleAuthorizationStrategy.authorize(resourceConnConfPanel, ENABLE, Entitlement.CONNECTOR_READ);
+        form.add(resourceConnConfPanel);
+        //--------------------------------
+
+        //--------------------------------
+        // Resource security panel
+        //--------------------------------
+        form.add(new ResourceSecurityPanel("security", resourceTO));
+        //--------------------------------
+
+        final AjaxButton submit = new IndicatingAjaxButton(APPLY, new ResourceModel(SUBMIT, SUBMIT)) {
+
+            private static final long serialVersionUID = -958724007591692537L;
+
+            @Override
+            protected void onSubmit(final AjaxRequestTarget target, final Form<?> form) {
+                final ResourceTO resourceTO = (ResourceTO) form.getDefaultModelObject();
+
+                boolean accountIdError = false;
+
+                if (resourceTO.getUmapping() == null || resourceTO.getUmapping().getItems().isEmpty()) {
+                    resourceTO.setUmapping(null);
+                } else {
+                    int uAccountIdCount = 0;
+                    for (MappingItemTO item : resourceTO.getUmapping().getItems()) {
+                        if (item.isAccountid()) {
+                            uAccountIdCount++;
+                        }
+                    }
+                    accountIdError = uAccountIdCount != 1;
+                }
+
+                if (resourceTO.getGmapping() == null || resourceTO.getGmapping().getItems().isEmpty()) {
+                    resourceTO.setGmapping(null);
+                } else {
+                    int rAccountIdCount = 0;
+                    for (MappingItemTO item : resourceTO.getGmapping().getItems()) {
+                        if (item.isAccountid()) {
+                            rAccountIdCount++;
+                        }
+                    }
+                    accountIdError |= rAccountIdCount != 1;
+                }
+
+                if (accountIdError) {
+                    error(getString("accountIdValidation"));
+                    feedbackPanel.refresh(target);
+                } else {
+                    try {
+                        if (createFlag) {
+                            resourceRestClient.create(resourceTO);
+                        } else {
+                            resourceRestClient.update(resourceTO);
+                        }
+
+                        if (pageRef != null && pageRef.getPage() instanceof AbstractBasePage) {
+                            ((AbstractBasePage) pageRef.getPage()).setModalResult(true);
+                        }
+                        window.close(target);
+                    } catch (Exception e) {
+                        LOG.error("Failure managing resource {}", resourceTO, 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) {
+                window.close(target);
+            }
+
+            @Override
+            protected void onError(final AjaxRequestTarget target, final Form<?> form) {
+            }
+        };
+
+        cancel.setDefaultFormProcessing(false);
+        form.add(cancel);
+
+        add(form);
+
+        MetaDataRoleAuthorizationStrategy.authorize(
+                submit, ENABLE, createFlag ? Entitlement.RESOURCE_CREATE : Entitlement.RESOURCE_UPDATE);
+    }
+
+    /**
+     * Generic resource event.
+     */
+    public static class ResourceEvent {
+
+        /**
+         * Request target.
+         */
+        private final AjaxRequestTarget target;
+
+        /**
+         * Constructor.
+         *
+         * @param target request target.
+         */
+        public ResourceEvent(final AjaxRequestTarget target) {
+            this.target = target;
+        }
+
+        /**
+         * Target getter.
+         *
+         * @return request target.
+         */
+        public AjaxRequestTarget getTarget() {
+            return target;
+        }
+    }
+}