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 2022/02/17 14:17:09 UTC

[syncope] branch master updated: [SYNCOPE-1658] Tabular Topology (#316)

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

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


The following commit(s) were added to refs/heads/master by this push:
     new ab1e023  [SYNCOPE-1658] Tabular Topology (#316)
ab1e023 is described below

commit ab1e023e08116b0644a51c0139f6a6fc2fe27c02
Author: SamuelGaro <72...@users.noreply.github.com>
AuthorDate: Thu Feb 17 15:16:57 2022 +0100

    [SYNCOPE-1658] Tabular Topology (#316)
---
 .../console/commons/ConnectorDataProvider.java     | 153 ++++++++
 .../console/commons/ResourceDataProvider.java      | 153 ++++++++
 .../syncope/client/console/pages/Connectors.java   |  80 ++++
 .../syncope/client/console/pages/Resources.java    |  80 ++++
 .../console/panels/ConnectorDirectoryPanel.java    | 285 ++++++++++++++
 .../client/console/panels/ConnidLocations.java     | 163 ++++++++
 .../console/panels/ResourceDirectoryPanel.java     | 432 +++++++++++++++++++++
 .../client/console/topology/TabularTopology.java   | 116 ++++++
 .../wizards/resources/ConnectorDetailsPanel.java   |  70 +++-
 .../wizards/resources/ConnectorWizardBuilder.java  |  10 +-
 .../wizards/resources/ResourceConnConfPanel.java   |   2 +-
 .../wizards/resources/ResourceDetailsPanel.java    |  55 ++-
 .../wizards/resources/ResourceWizardBuilder.java   |  11 +-
 .../syncope/client/console/pages/Connectors.html   |  35 ++
 .../syncope/client/console/pages/Resources.html    |  35 ++
 .../panels/ConnectorDirectoryPanel.properties      |  27 ++
 .../ConnectorDirectoryPanel_fr_CA.properties       |  27 ++
 .../panels/ConnectorDirectoryPanel_it.properties   |  27 ++
 .../panels/ConnectorDirectoryPanel_ja.properties   |  27 ++
 .../ConnectorDirectoryPanel_pt_BR.properties       |  27 ++
 .../panels/ConnectorDirectoryPanel_ru.properties   |  27 ++
 .../console/panels/ConnidLocations.properties      |  18 +
 .../panels/ConnidLocations_fr_CA.properties        |  18 +
 .../console/panels/ConnidLocations_it.properties   |  18 +
 .../console/panels/ConnidLocations_ja.properties   |  18 +
 .../panels/ConnidLocations_pt_BR.properties        |  18 +
 .../console/panels/ConnidLocations_ru.properties   |  18 +
 .../panels/ResourceDirectoryPanel.properties       |  38 ++
 .../panels/ResourceDirectoryPanel_fr_CA.properties |  38 ++
 .../panels/ResourceDirectoryPanel_it.properties    |  38 ++
 .../panels/ResourceDirectoryPanel_ja.properties    |  38 ++
 .../panels/ResourceDirectoryPanel_pt_BR.properties |  38 ++
 .../panels/ResourceDirectoryPanel_ru.properties    |  38 ++
 .../client/console/topology/TabularTopology.html   |  29 ++
 .../client/console/SyncopeConsoleApplication.java  |   5 +-
 .../init/ClassPathScanImplementationLookup.java    |  12 +-
 .../console/tasks/SchedTaskDirectoryPanel.java     |  57 +++
 .../syncope/client/console/tasks/SchedTasks.java   |  21 +
 .../client/console/tasks/TaskDirectoryPanel.java   |  20 +
 .../wicket/markup/html/form/ActionLink.java        |   9 +-
 .../console/src/main/resources/console.properties  |   1 +
 .../markup/html/form/ActionsPanel.properties       |  23 ++
 .../markup/html/form/ActionsPanel_fr_CA.properties |  27 ++
 .../markup/html/form/ActionsPanel_it.properties    |  24 ++
 .../markup/html/form/ActionsPanel_ja.properties    |  26 +-
 .../markup/html/form/ActionsPanel_pt_BR.properties |  24 ++
 .../markup/html/form/ActionsPanel_ru.properties    |  26 +-
 .../syncope/client/console/AbstractTest.java       |   3 +-
 .../syncope/fit/console/AbstractConsoleITCase.java |   5 +-
 49 files changed, 2458 insertions(+), 32 deletions(-)

diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/ConnectorDataProvider.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/ConnectorDataProvider.java
new file mode 100644
index 0000000..f7a3718
--- /dev/null
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/ConnectorDataProvider.java
@@ -0,0 +1,153 @@
+/*
+ * 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.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.pages.BasePage;
+import org.apache.syncope.client.console.rest.ConnectorRestClient;
+import org.apache.syncope.client.ui.commons.DirectoryDataProvider;
+import org.apache.syncope.common.lib.to.ConnInstanceTO;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder;
+import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+public class ConnectorDataProvider extends DirectoryDataProvider<Serializable> {
+
+    private static final long serialVersionUID = 3122389673525690470L;
+
+    protected static final Logger LOG = LoggerFactory.getLogger(ConnectorDataProvider.class);
+
+    private final PageReference pageRef;
+
+    protected int currentPage;
+
+    private String keyword;
+
+    private final ConnectorRestClient restClient = new ConnectorRestClient();
+
+    public ConnectorDataProvider(
+            final int paginatorRows,
+            final PageReference pageRef,
+            final String keyword) {
+        super(paginatorRows);
+        setSort("displayNameSortParam", SortOrder.ASCENDING);
+        this.pageRef = pageRef;
+        this.keyword = keyword;
+    }
+
+    @Override
+    public Iterator<ConnInstanceTO> iterator(final long first, final long count) {
+        List<ConnInstanceTO> result = Collections.emptyList();
+
+        try {
+            currentPage = ((int) first / paginatorRows);
+            if (currentPage < 0) {
+                currentPage = 0;
+            }
+            if (StringUtils.isBlank(keyword)) {
+                result = restClient.getAllConnectors();
+            } else {
+                result = restClient.getAllConnectors().stream().filter(conn ->
+                        conn.getDisplayName().toLowerCase().contains(keyword)).collect(Collectors.toList());
+            }
+        } catch (Exception e) {
+            LOG.error("While searching", e);
+            SyncopeConsoleSession.get().onException(e);
+
+            Optional<AjaxRequestTarget> target = RequestCycle.get().find(AjaxRequestTarget.class);
+            target.ifPresent(ajaxRequestTarget ->
+                    ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(ajaxRequestTarget));
+        }
+
+        SortParam<String> sortParam = getSort();
+        if (sortParam != null) {
+            result.sort(getComparator(sortParam));
+        }
+
+        return result.subList((int) first, (int) first + (int) count).iterator();
+    }
+
+    private Comparator<ConnInstanceTO> getComparator(final SortParam<String> sortParam) {
+        Comparator<ConnInstanceTO> comparator;
+
+        switch (sortParam.getProperty()) {
+            case "displayNameSortParam":
+                comparator = Comparator.nullsFirst(Comparator.comparing(
+                        item -> item.getDisplayName().toLowerCase()));
+                break;
+            case "connectorNameSortParam":
+                comparator = Comparator.nullsFirst(Comparator.comparing(
+                        item -> item.getConnectorName().toLowerCase()));
+                break;
+            default:
+                throw new IllegalStateException("The sort param " + sortParam.getProperty() + " is not correct");
+        }
+
+        if (!sortParam.isAscending()) {
+            comparator = comparator.reversed();
+        }
+
+        return comparator;
+    }
+
+    @Override
+    public long size() {
+        long result = 0;
+
+        try {
+            if (StringUtils.isBlank(keyword)) {
+                result = restClient.getAllConnectors().size();
+            } else {
+                result = restClient.getAllConnectors().stream().filter(conn ->
+                        conn.getDisplayName().toLowerCase().contains(keyword)).count();
+            }
+        } catch (Exception e) {
+            LOG.error("While requesting for size()", e);
+            SyncopeConsoleSession.get().onException(e);
+
+            RequestCycle.get().find(AjaxRequestTarget.class).
+                    ifPresent(target -> ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target));
+        }
+
+        return result;
+    }
+
+    @Override
+    public IModel<Serializable> model(final Serializable object) {
+        return new CompoundPropertyModel<>((ConnInstanceTO) object);
+    }
+
+    public int getCurrentPage() {
+        return currentPage;
+    }
+}
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/ResourceDataProvider.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/ResourceDataProvider.java
new file mode 100644
index 0000000..04a2109
--- /dev/null
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/commons/ResourceDataProvider.java
@@ -0,0 +1,153 @@
+/*
+ * 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.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.pages.BasePage;
+import org.apache.syncope.client.console.rest.ResourceRestClient;
+import org.apache.syncope.client.ui.commons.DirectoryDataProvider;
+import org.apache.syncope.common.lib.to.ResourceTO;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder;
+import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.request.cycle.RequestCycle;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import java.io.Serializable;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+public class ResourceDataProvider extends DirectoryDataProvider<Serializable> {
+
+    private static final long serialVersionUID = 3189980210236051840L;
+
+    protected static final Logger LOG = LoggerFactory.getLogger(ResourceDataProvider.class);
+
+    private final PageReference pageRef;
+
+    protected int currentPage;
+
+    private String keyword;
+
+    private final ResourceRestClient restClient = new ResourceRestClient();
+
+    public ResourceDataProvider(
+            final int paginatorRows,
+            final PageReference pageRef,
+            final String keyword) {
+        super(paginatorRows);
+        setSort("keySortParam", SortOrder.ASCENDING);
+        this.pageRef = pageRef;
+        this.keyword = keyword;
+    }
+
+    @Override
+    public Iterator<ResourceTO> iterator(final long first, final long count) {
+        List<ResourceTO> result = Collections.emptyList();
+
+        try {
+            currentPage = ((int) first / paginatorRows);
+            if (currentPage < 0) {
+                currentPage = 0;
+            }
+            if (StringUtils.isBlank(keyword)) {
+                result = restClient.list();
+            } else {
+                result = restClient.list().stream().filter(resource ->
+                        resource.getKey().toLowerCase().contains(keyword)).collect(Collectors.toList());
+            }
+        } catch (Exception e) {
+            LOG.error("While searching", e);
+            SyncopeConsoleSession.get().onException(e);
+
+            Optional<AjaxRequestTarget> target = RequestCycle.get().find(AjaxRequestTarget.class);
+            target.ifPresent(ajaxRequestTarget ->
+                    ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(ajaxRequestTarget));
+        }
+
+        SortParam<String> sortParam = getSort();
+        if (sortParam != null) {
+            result.sort(getComparator(sortParam));
+        }
+
+        return result.subList((int) first, (int) first + (int) count).iterator();
+    }
+
+    private Comparator<ResourceTO> getComparator(final SortParam<String> sortParam) {
+        Comparator<ResourceTO> comparator;
+
+        switch (sortParam.getProperty()) {
+            case "keySortParam":
+                comparator = Comparator.nullsFirst(Comparator.comparing(
+                        item -> item.getKey().toLowerCase()));
+                break;
+            case "connectorDisplayNameSortParam":
+                comparator = Comparator.nullsFirst(Comparator.comparing(
+                        item -> item.getConnectorDisplayName().toLowerCase()));
+                break;
+            default:
+                throw new IllegalStateException("The sort param " + sortParam.getProperty() + " is not correct");
+        }
+
+        if (!sortParam.isAscending()) {
+            comparator = comparator.reversed();
+        }
+
+        return comparator;
+    }
+
+    @Override
+    public long size() {
+        long result = 0;
+
+        try {
+            if (StringUtils.isBlank(keyword)) {
+                result = restClient.list().size();
+            } else {
+                result = restClient.list().stream().filter(resource ->
+                        resource.getKey().toLowerCase().contains(keyword)).count();
+            }
+        } catch (Exception e) {
+            LOG.error("While requesting for size()", e);
+            SyncopeConsoleSession.get().onException(e);
+
+            RequestCycle.get().find(AjaxRequestTarget.class).
+                    ifPresent(target -> ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target));
+        }
+
+        return result;
+    }
+
+    @Override
+    public IModel<Serializable> model(final Serializable object) {
+        return new CompoundPropertyModel<>((ResourceTO) object);
+    }
+
+    public int getCurrentPage() {
+        return currentPage;
+    }
+}
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/pages/Connectors.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/pages/Connectors.java
new file mode 100644
index 0000000..21441e1
--- /dev/null
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/pages/Connectors.java
@@ -0,0 +1,80 @@
+/*
+ * 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.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.panels.ConnectorDirectoryPanel;
+import org.apache.syncope.client.console.wizards.WizardMgtPanel;
+import org.apache.syncope.client.console.wizards.resources.ConnectorWizardBuilder;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
+import org.apache.syncope.common.lib.to.ConnInstanceTO;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.form.AjaxButton;
+import org.apache.wicket.event.Broadcast;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.Model;
+import java.io.Serializable;
+
+public class Connectors extends Panel {
+
+    private static final long serialVersionUID = 305521359617401936L;
+
+    private final WizardMgtPanel<Serializable> connectorDirectoryPanel;
+
+    public Connectors(final String id, final PageReference pageRef) {
+        super(id);
+
+        Model<String> keywordModel = new Model<>(StringUtils.EMPTY);
+
+        WebMarkupContainer searchBoxContainer = new WebMarkupContainer("searchBox");
+        add(searchBoxContainer);
+
+        Form<?> form = new Form<>("form");
+        searchBoxContainer.add(form);
+
+        AjaxTextFieldPanel filter = new AjaxTextFieldPanel("filter", "filter", keywordModel, true);
+        form.add(filter.hideLabel().setOutputMarkupId(true));
+
+        AjaxButton search = new AjaxButton("search") {
+
+            private static final long serialVersionUID = 8390605330558248736L;
+
+            @Override
+            protected void onSubmit(final AjaxRequestTarget target) {
+                send(Connectors.this, Broadcast.DEPTH,
+                        new ConnectorDirectoryPanel.ConnectorSearchEvent(target, keywordModel.getObject()));
+            }
+        };
+        search.setOutputMarkupId(true);
+        form.add(search);
+        form.setDefaultButton(search);
+
+        connectorDirectoryPanel =
+                new ConnectorDirectoryPanel.Builder(pageRef).
+                        addNewItemPanelBuilder(new ConnectorWizardBuilder(
+                                new ConnInstanceTO(), pageRef), true).
+                        build("connectorDirectoryPanel");
+        connectorDirectoryPanel.setOutputMarkupId(true);
+
+        add(connectorDirectoryPanel);
+    }
+}
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/pages/Resources.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/pages/Resources.java
new file mode 100644
index 0000000..37dc5d6
--- /dev/null
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/pages/Resources.java
@@ -0,0 +1,80 @@
+/*
+ * 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.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.panels.ResourceDirectoryPanel;
+import org.apache.syncope.client.console.wizards.WizardMgtPanel;
+import org.apache.syncope.client.console.wizards.resources.ResourceWizardBuilder;
+import org.apache.syncope.client.ui.commons.markup.html.form.AjaxTextFieldPanel;
+import org.apache.syncope.common.lib.to.ResourceTO;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.form.AjaxButton;
+import org.apache.wicket.event.Broadcast;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.Model;
+import java.io.Serializable;
+
+public class Resources extends Panel {
+
+    private static final long serialVersionUID = 7240865652350993779L;
+
+    private final WizardMgtPanel<Serializable> resourceDirectoryPanel;
+
+    public Resources(final String id, final PageReference pageRef) {
+        super(id);
+
+        Model<String> keywordModel = new Model<>(StringUtils.EMPTY);
+
+        WebMarkupContainer searchBoxContainer = new WebMarkupContainer("searchBox");
+        add(searchBoxContainer);
+
+        Form<?> form = new Form<>("form");
+        searchBoxContainer.add(form);
+
+        AjaxTextFieldPanel filter = new AjaxTextFieldPanel("filter", "filter", keywordModel, true);
+        form.add(filter.hideLabel().setOutputMarkupId(true));
+
+        AjaxButton search = new AjaxButton("search") {
+
+            private static final long serialVersionUID = 8390605330558248736L;
+
+            @Override
+            protected void onSubmit(final AjaxRequestTarget target) {
+                send(Resources.this, Broadcast.DEPTH,
+                        new ResourceDirectoryPanel.ResourceSearchEvent(target, keywordModel.getObject()));
+            }
+        };
+        search.setOutputMarkupId(true);
+        form.add(search);
+        form.setDefaultButton(search);
+
+        resourceDirectoryPanel =
+                new ResourceDirectoryPanel.Builder(pageRef).
+                        addNewItemPanelBuilder(new ResourceWizardBuilder(
+                                new ResourceTO(), pageRef), true).
+                        build("resourceDirectoryPanel");
+        resourceDirectoryPanel.setOutputMarkupId(true);
+
+        add(resourceDirectoryPanel);
+    }
+}
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel.java
new file mode 100644
index 0000000..e446898
--- /dev/null
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel.java
@@ -0,0 +1,285 @@
+/*
+ * 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.panels;
+
+import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.audit.AuditHistoryModal;
+import org.apache.syncope.client.console.commons.ConnectorDataProvider;
+import org.apache.syncope.client.console.commons.IdRepoConstants;
+import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.client.console.pages.BasePage;
+import org.apache.syncope.client.console.rest.ConnectorRestClient;
+import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
+import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
+import org.apache.syncope.client.console.wizards.WizardMgtPanel;
+import org.apache.syncope.client.console.wizards.resources.ConnectorWizardBuilder;
+import org.apache.syncope.client.console.wizards.resources.ResourceWizardBuilder;
+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.types.AuditElements;
+import org.apache.syncope.common.lib.types.IdMEntitlement;
+import org.apache.syncope.common.lib.types.IdRepoEntitlement;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
+import org.apache.wicket.event.IEvent;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.ResourceModel;
+import java.io.Serializable;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+public class ConnectorDirectoryPanel extends
+        DirectoryPanel<Serializable, Serializable, ConnectorDataProvider, ConnectorRestClient> {
+
+    private static final long serialVersionUID = 2041468935602350821L;
+
+    private String keyword;
+
+    protected ConnectorDirectoryPanel(final String id, final ConnectorDirectoryPanel.Builder builder) {
+        super(id, builder);
+
+        if (SyncopeConsoleSession.get().owns("CONNECTOR_CREATE")) {
+            MetaDataRoleAuthorizationStrategy.authorizeAll(addAjaxLink, RENDER);
+        } else {
+            MetaDataRoleAuthorizationStrategy.unauthorizeAll(addAjaxLink, RENDER);
+        }
+
+        setShowResultPage(false);
+        modal.size(Modal.Size.Large);
+        initResultTable();
+
+        restClient = builder.restClient;
+    }
+
+    @Override
+    public void onEvent(final IEvent<?> event) {
+        if (event.getPayload() instanceof ConnectorSearchEvent) {
+            ConnectorSearchEvent payload = (ConnectorSearchEvent) event.getPayload();
+            AjaxRequestTarget target = payload.getTarget();
+            if (StringUtils.isNotBlank(payload.getKeyword())) {
+                keyword = payload.getKeyword().toLowerCase();
+            }
+            updateResultTable(target);
+        } else {
+            super.onEvent(event);
+        }
+    }
+
+    @Override
+    protected ConnectorDataProvider dataProvider() {
+        dataProvider = new ConnectorDataProvider(rows, pageRef, keyword);
+        return dataProvider;
+    }
+
+    public ConnectorDataProvider getDataProvider() {
+        return dataProvider;
+    }
+
+    @Override
+    protected String paginatorRowsKey() {
+        return IdRepoConstants.PREF_PARAMETERS_PAGINATOR_ROWS;
+    }
+
+    @Override
+    protected List<IColumn<Serializable, String>> getColumns() {
+        final List<IColumn<Serializable, String>> columns = new ArrayList<>();
+        columns.add(new PropertyColumn<>(
+                new ResourceModel("displayName"), "displayNameSortParam", "displayName"));
+        columns.add(new PropertyColumn<>(
+                new ResourceModel("connectorName"), "connectorNameSortParam", "connectorName"));
+        return columns;
+    }
+
+    @Override
+    protected Collection<ActionLink.ActionType> getBatches() {
+        return Collections.singletonList(ActionLink.ActionType.DELETE);
+    }
+
+    @Override
+    public ActionsPanel<Serializable> getActions(final IModel<Serializable> model) {
+        final ActionsPanel<Serializable> panel = super.getActions(model);
+
+        panel.add(new ActionLink<>() {
+
+            private static final long serialVersionUID = 8345646188740279483L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final Serializable ignore) {
+                final ResourceTO modelObject = new ResourceTO();
+                modelObject.setConnector(((ConnInstanceTO) model.getObject()).getKey());
+                modelObject.setConnectorDisplayName(((ConnInstanceTO) model.getObject()).getDisplayName());
+
+                final IModel<ResourceTO> model = new CompoundPropertyModel<>(modelObject);
+                modal.setFormModel(model.getObject());
+
+                target.add(modal.setContent(new ResourceWizardBuilder(modelObject, pageRef).
+                        build(BaseModal.CONTENT_ID, AjaxWizard.Mode.CREATE)));
+
+                modal.header(new Model<>(MessageFormat.format(getString("resource.new"),
+                        model.getObject().getKey())));
+                modal.show(true);
+
+                target.add(modal);
+            }
+
+        }, ActionLink.ActionType.CREATE_RESOURCE, String.format("%s", IdMEntitlement.RESOURCE_CREATE));
+
+        panel.add(new ActionLink<>() {
+
+            private static final long serialVersionUID = 8200500789152854321L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final Serializable ignore) {
+                ConnInstanceTO connInstance = ConnectorRestClient.read(((ConnInstanceTO) model.getObject()).getKey());
+
+                final IModel<ConnInstanceTO> model = new CompoundPropertyModel<>(connInstance);
+                modal.setFormModel(model);
+
+                target.add(modal.setContent(new ConnectorWizardBuilder(connInstance, pageRef).
+                        build(BaseModal.CONTENT_ID,
+                                SyncopeConsoleSession.get().
+                                        owns(IdMEntitlement.CONNECTOR_UPDATE, connInstance.getAdminRealm())
+                                        ? AjaxWizard.Mode.EDIT
+                                        : AjaxWizard.Mode.READONLY)));
+
+                modal.header(
+                        new Model<>(MessageFormat.format(getString("connector.edit"), connInstance.getDisplayName())));
+                modal.show(true);
+
+            }
+        }, ActionLink.ActionType.EDIT, String.format("%s,%s", IdMEntitlement.CONNECTOR_READ,
+                IdMEntitlement.CONNECTOR_UPDATE));
+
+        panel.add(new ActionLink<>() {
+
+            private static final long serialVersionUID = 1085863437941911947L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final Serializable ignore) {
+              ConnInstanceTO modelObject = ConnectorRestClient.read(((ConnInstanceTO) model.getObject()).getKey());
+            
+              target.add(altDefaultModal.setContent(new AuditHistoryModal<>(
+                      altDefaultModal,
+                      AuditElements.EventCategoryType.LOGIC,
+                      "ConnectorLogic",
+                      modelObject,
+                      IdMEntitlement.CONNECTOR_UPDATE,
+                      pageRef) {
+            
+                  private static final long serialVersionUID = -3225348282675513648L;
+
+                  @Override
+                  protected void restore(final String json, final AjaxRequestTarget target) {
+                      try {
+                          ConnInstanceTO updated = MAPPER.readValue(json, ConnInstanceTO.class);
+                          ConnectorRestClient.update(updated);
+
+                          SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
+                      } catch (Exception e) {
+                          LOG.error("While restoring connector {}",
+                                  ((ConnInstanceTO) model.getObject()).getKey(), e);
+                          SyncopeConsoleSession.get().onException(e);
+                      }
+                      ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
+                  }
+              }));
+
+              altDefaultModal.header(
+                      new Model<>(MessageFormat.format(getString("connector.menu.history"),
+                              ((ConnInstanceTO) model.getObject()).getDisplayName())));
+
+              altDefaultModal.show(true);
+            }
+
+            }, ActionLink.ActionType.VIEW_AUDIT_HISTORY,
+            String.format("%s,%s", IdMEntitlement.CONNECTOR_READ, IdRepoEntitlement.AUDIT_LIST));
+
+        panel.add(new ActionLink<>() {
+
+            private static final long serialVersionUID = -1544718936080799146L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final Serializable ignore) {
+                try {
+                    ConnectorRestClient.delete(((ConnInstanceTO) model.getObject()).getKey());
+                    target.appendJavaScript(String.format("jsPlumb.remove('%s');",
+                            ((ConnInstanceTO) model.getObject()).getKey()));
+                    SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
+                } catch (SyncopeClientException e) {
+                    LOG.error("While deleting resource {}", ((ConnInstanceTO) model.getObject()).getKey(), e);
+                    SyncopeConsoleSession.get().onException(e);
+                }
+                ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
+            }
+
+        }, ActionLink.ActionType.DELETE, IdMEntitlement.CONNECTOR_DELETE, true);
+
+        return panel;
+    }
+
+    public static class ConnectorSearchEvent implements Serializable {
+
+        private static final long serialVersionUID = -282052400565266028L;
+
+        private final AjaxRequestTarget target;
+
+        private final String keyword;
+
+        public ConnectorSearchEvent(final AjaxRequestTarget target, final String keyword) {
+            this.target = target;
+            this.keyword = keyword;
+        }
+
+        public AjaxRequestTarget getTarget() {
+            return target;
+        }
+
+        public String getKeyword() {
+            return keyword;
+        }
+    }
+
+    public static class Builder extends DirectoryPanel.Builder<Serializable, Serializable, ConnectorRestClient> {
+
+        private static final long serialVersionUID = 6128427903964630093L;
+
+        public Builder(final PageReference pageRef) {
+            super(new ConnectorRestClient(), pageRef);
+            setShowResultPage(false);
+        }
+
+        @Override
+        protected WizardMgtPanel<Serializable> newInstance(final String id, final boolean wizardInModal) {
+            return new ConnectorDirectoryPanel(id, this);
+        }
+    }
+}
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ConnidLocations.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ConnidLocations.java
new file mode 100644
index 0000000..8605c1c
--- /dev/null
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ConnidLocations.java
@@ -0,0 +1,163 @@
+/*
+ * 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.panels;
+
+import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.client.console.commons.IdRepoConstants;
+import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
+import org.apache.syncope.client.ui.commons.DirectoryDataProvider;
+import org.apache.syncope.client.ui.commons.rest.RestClient;
+import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
+import org.apache.syncope.client.console.wizards.WizardMgtPanel;
+import org.apache.syncope.client.console.wizards.resources.ConnectorWizardBuilder;
+import org.apache.syncope.common.lib.to.ConnInstanceTO;
+import org.apache.syncope.common.lib.types.IdMEntitlement;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+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.markup.html.basic.Label;
+import org.apache.wicket.markup.repeater.Item;
+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 java.io.Serializable;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+public class ConnidLocations extends
+        DirectoryPanel<Serializable, Serializable, ConnidLocations.ConnidLocationsDataProvider, RestClient> {
+    
+    public ConnidLocations(final String id, final Builder builder) {
+        super(id, builder);
+
+        disableCheckBoxes();
+        setShowResultPage(true);
+
+        modal.size(Modal.Size.Large);
+        initResultTable();
+    }
+
+    @Override
+    protected ConnidLocationsDataProvider dataProvider() {
+        return new ConnidLocationsDataProvider(rows);
+    }
+
+    @Override
+    protected String paginatorRowsKey() {
+        return IdRepoConstants.PREF_DYNREALM_PAGINATOR_ROWS;
+    }
+
+    @Override
+    protected List<IColumn<Serializable, String>> getColumns() {
+        final List<IColumn<Serializable, String>> columns = new ArrayList<>();
+
+        columns.add(new AbstractColumn<>(
+                new ResourceModel(Constants.KEY_FIELD_NAME), Constants.KEY_FIELD_NAME) {
+            @Override
+            public void populateItem(final Item cellItem, final String componentId, final IModel rowModel) {
+                cellItem.add(new Label(componentId, rowModel.getObject().toString()));
+            }
+        });
+
+        return columns;
+    }
+
+    @Override
+    public ActionsPanel<Serializable> getActions(final IModel<Serializable> model) {
+        final ActionsPanel<Serializable> panel = super.getActions(model);
+
+        panel.add(new ActionLink<>() {
+
+            private static final long serialVersionUID = 293293495682202660L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final Serializable ignore) {
+                final ConnInstanceTO modelObject = new ConnInstanceTO();
+                modelObject.setLocation((String) ignore);
+
+                final IModel<ConnInstanceTO> model = new CompoundPropertyModel<>(modelObject);
+                modal.setFormModel(model);
+
+                target.add(modal.setContent(new ConnectorWizardBuilder(modelObject, pageRef).
+                        build(BaseModal.CONTENT_ID, AjaxWizard.Mode.CREATE)));
+
+                modal.header(new Model<>(MessageFormat.format(getString("connector.new"), ignore)));
+                modal.show(true);
+            }
+
+        }, ActionLink.ActionType.CREATE, String.format("%s", IdMEntitlement.CONNECTOR_CREATE));
+
+        return panel;
+    }
+
+    @Override
+    protected Collection<ActionLink.ActionType> getBatches() {
+        return Collections.emptyList();
+    }
+
+    public abstract static class Builder
+            extends DirectoryPanel.Builder<Serializable, Serializable, RestClient> {
+
+        private static final long serialVersionUID = 4448348557808690524L;
+
+        public Builder(final PageReference pageRef) {
+            super(null, pageRef);
+        }
+
+        @Override
+        protected WizardMgtPanel<Serializable> newInstance(final String id, final boolean wizardInModal) {
+            return new ConnidLocations(id, this);
+        }
+    }
+
+    protected static class ConnidLocationsDataProvider extends DirectoryDataProvider<Serializable> {
+
+        private static final long serialVersionUID = 3161906945317209169L;
+
+        public ConnidLocationsDataProvider(final int paginatorRows) {
+            super(paginatorRows);
+        }
+
+        @Override
+        public Iterator<String> iterator(final long first, final long count) {
+            List<String> result = new ArrayList<>(SyncopeConsoleSession.get().getPlatformInfo().getConnIdLocations());
+            return result.subList((int) first, (int) first + (int) count).iterator();
+        }
+
+        @Override
+        public long size() {
+            return SyncopeConsoleSession.get().getPlatformInfo().getConnIdLocations().size();
+        }
+
+        @Override
+        public IModel<Serializable> model(final Serializable object) {
+            return new CompoundPropertyModel<>(object);
+        }
+    }
+}
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ResourceDirectoryPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ResourceDirectoryPanel.java
new file mode 100644
index 0000000..481f185
--- /dev/null
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/panels/ResourceDirectoryPanel.java
@@ -0,0 +1,432 @@
+/*
+ * 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.panels;
+
+import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.audit.AuditHistoryModal;
+import org.apache.syncope.client.console.commons.IdRepoConstants;
+import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.client.console.commons.ResourceDataProvider;
+import org.apache.syncope.client.console.pages.BasePage;
+import org.apache.syncope.client.console.rest.ConnectorRestClient;
+import org.apache.syncope.client.console.rest.ResourceRestClient;
+import org.apache.syncope.client.console.status.ResourceStatusModal;
+import org.apache.syncope.client.console.tasks.PropagationTasks;
+import org.apache.syncope.client.console.tasks.PullTasks;
+import org.apache.syncope.client.console.tasks.PushTasks;
+import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionsPanel;
+import org.apache.syncope.client.ui.commons.wizards.AjaxWizard;
+import org.apache.syncope.client.console.wizards.WizardMgtPanel;
+import org.apache.syncope.client.console.wizards.resources.ResourceProvisionPanel;
+import org.apache.syncope.client.console.wizards.resources.ResourceWizardBuilder;
+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.types.AuditElements;
+import org.apache.syncope.common.lib.types.IdMEntitlement;
+import org.apache.syncope.common.lib.types.IdRepoEntitlement;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
+import org.apache.wicket.event.IEvent;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.model.StringResourceModel;
+import java.io.Serializable;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+public class ResourceDirectoryPanel extends
+        DirectoryPanel<Serializable, Serializable, ResourceDataProvider, ResourceRestClient> {
+
+    private static final long serialVersionUID = -5223129956783782225L;
+
+    private String keyword;
+
+    private final BaseModal<Serializable> propTaskModal;
+
+    private final BaseModal<Serializable> schedTaskModal;
+
+    private final BaseModal<Serializable> provisionModal;
+
+    private final BaseModal<Serializable> historyModal;
+
+    protected ResourceDirectoryPanel(final String id, final ResourceDirectoryPanel.Builder builder) {
+        super(id, builder);
+
+        if (SyncopeConsoleSession.get().owns("RESOURCE_CREATE")) {
+            MetaDataRoleAuthorizationStrategy.authorizeAll(addAjaxLink, RENDER);
+        } else {
+            MetaDataRoleAuthorizationStrategy.unauthorizeAll(addAjaxLink, RENDER);
+        }
+
+        setShowResultPage(false);
+        modal.size(Modal.Size.Large);
+        initResultTable();
+
+        restClient = builder.restClient;
+
+        propTaskModal = new BaseModal<>(Constants.OUTER);
+        propTaskModal.size(Modal.Size.Large);
+        addOuterObject(propTaskModal);
+
+        schedTaskModal = new BaseModal<>(Constants.OUTER) {
+
+            private static final long serialVersionUID = -6165152045136958913L;
+
+            @Override
+            protected void onConfigure() {
+                super.onConfigure();
+                setFooterVisible(false);
+            }
+        };
+        schedTaskModal.size(Modal.Size.Large);
+        addOuterObject(schedTaskModal);
+
+        provisionModal = new BaseModal<>(Constants.OUTER);
+        provisionModal.size(Modal.Size.Large);
+        provisionModal.addSubmitButton();
+        addOuterObject(provisionModal);
+
+        historyModal = new BaseModal<>(Constants.OUTER);
+        historyModal.size(Modal.Size.Large);
+        addOuterObject(historyModal);
+    }
+
+    @Override
+    public void onEvent(final IEvent<?> event) {
+        if (event.getPayload() instanceof ResourceSearchEvent) {
+            ResourceSearchEvent payload = (ResourceSearchEvent) event.getPayload();
+            AjaxRequestTarget target = payload.getTarget();
+            if (StringUtils.isNotEmpty(payload.getKeyword())) {
+                keyword = payload.getKeyword().toLowerCase();
+            }
+            updateResultTable(target);
+        } else {
+            super.onEvent(event);
+        }
+    }
+
+    @Override
+    protected ResourceDataProvider dataProvider() {
+        dataProvider = new ResourceDataProvider(rows, pageRef, keyword);
+        return dataProvider;
+    }
+
+    public ResourceDataProvider getDataProvider() {
+        return dataProvider;
+    }
+
+    @Override
+    protected String paginatorRowsKey() {
+        return IdRepoConstants.PREF_PARAMETERS_PAGINATOR_ROWS;
+    }
+
+    @Override
+    protected List<IColumn<Serializable, String>> getColumns() {
+        final List<IColumn<Serializable, String>> columns = new ArrayList<>();
+        columns.add(new PropertyColumn<>(
+                new ResourceModel("key"), "keySortParam", "key"));
+        columns.add(new PropertyColumn<>(
+                new ResourceModel("connectorDisplayName"), "connectorDisplayNameSortParam", "connectorDisplayName"));
+        return columns;
+    }
+
+    @Override
+    protected Collection<ActionLink.ActionType> getBatches() {
+        return Collections.singletonList(ActionLink.ActionType.DELETE);
+    }
+
+    @Override
+    public ActionsPanel<Serializable> getActions(final IModel<Serializable> model) {
+        final ActionsPanel<Serializable> panel = super.getActions(model);
+
+        panel.add(new ActionLink<>() {
+
+            private static final long serialVersionUID = -7220222653598674870L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final Serializable ignore) {
+                ResourceTO resource = ResourceRestClient.read(((ResourceTO) model.getObject()).getKey());
+                ConnInstanceTO connInstance = ConnectorRestClient.read(resource.getConnector());
+
+                IModel<ResourceTO> model = new CompoundPropertyModel<>(resource);
+                modal.setFormModel(model);
+
+                target.add(modal.setContent(new ResourceWizardBuilder(resource, pageRef).
+                        build(BaseModal.CONTENT_ID,
+                                SyncopeConsoleSession.get().
+                                        owns(IdMEntitlement.RESOURCE_UPDATE, connInstance.getAdminRealm())
+                                        ? AjaxWizard.Mode.EDIT
+                                        : AjaxWizard.Mode.READONLY)));
+
+                modal.header(new Model<>(MessageFormat.format(getString("resource.edit"), model.getObject().getKey())));
+                modal.show(true);
+            }
+        }, ActionLink.ActionType.EDIT, String.format("%s,%s", IdMEntitlement.RESOURCE_READ,
+                IdMEntitlement.RESOURCE_UPDATE));
+
+        panel.add(new ActionLink<>() {
+
+            private static final long serialVersionUID = -6467344504797047254L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final Serializable ignore) {
+                ResourceTO resource = ResourceRestClient.read(((ResourceTO) model.getObject()).getKey());
+                ConnInstanceTO connInstance = ConnectorRestClient.read(resource.getConnector());
+
+                if (SyncopeConsoleSession.get().
+                        owns(IdMEntitlement.RESOURCE_UPDATE, connInstance.getAdminRealm())) {
+
+                    provisionModal.addSubmitButton();
+                } else {
+                    provisionModal.removeSubmitButton();
+                }
+
+                IModel<ResourceTO> model = new CompoundPropertyModel<>(resource);
+                provisionModal.setFormModel(model);
+
+                target.add(provisionModal.setContent(
+                        new ResourceProvisionPanel(provisionModal, resource, connInstance.getAdminRealm(), pageRef)));
+
+                provisionModal.header(new Model<>(MessageFormat.format(getString("resource.edit"),
+                        model.getObject().getKey())));
+                provisionModal.show(true);
+            }
+        }, ActionLink.ActionType.MAPPING, String.format("%s,%s", IdMEntitlement.RESOURCE_READ,
+                IdMEntitlement.RESOURCE_UPDATE));
+
+        panel.add(new ActionLink<>() {
+
+            private static final long serialVersionUID = -1448897313753684142L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final Serializable ignore) {
+                ResourceTO resource = ResourceRestClient.read(((ResourceTO) model.getObject()).getKey());
+
+                target.add(propTaskModal.setContent(new ConnObjects(resource, pageRef)));
+                propTaskModal.header(new StringResourceModel("resource.explore.list", Model.of(model.getObject())));
+                propTaskModal.show(true);
+            }
+        }, ActionLink.ActionType.EXPLORE_RESOURCE, IdMEntitlement.RESOURCE_LIST_CONNOBJECT);
+
+        panel.add(new ActionLink<>() {
+
+            private static final long serialVersionUID = 4800323783814856195L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final Serializable ignore) {
+                target.add(propTaskModal.setContent(
+                        new PropagationTasks(propTaskModal, ((ResourceTO) model.getObject()).getKey(), pageRef)));
+                propTaskModal.header(new Model<>(MessageFormat.format(getString("task.propagation.list"),
+                        ((ResourceTO) model.getObject()).getKey())));
+                propTaskModal.show(true);
+            }
+        }, ActionLink.ActionType.PROPAGATION_TASKS, IdRepoEntitlement.TASK_LIST);
+
+        panel.add(new ActionLink<>() {
+
+            private static final long serialVersionUID = -4699610013584898667L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final Serializable ignore) {
+                target.add(schedTaskModal.setContent(new PullTasks(schedTaskModal, pageRef,
+                        ((ResourceTO) model.getObject()).getKey())));
+                schedTaskModal.header(new Model<>(MessageFormat.format(getString("task.pull.list"),
+                        ((ResourceTO) model.getObject()).getKey())));
+                schedTaskModal.show(true);
+            }
+        }, ActionLink.ActionType.PULL_TASKS, IdRepoEntitlement.TASK_LIST);
+
+        panel.add(new ActionLink<>() {
+
+            private static final long serialVersionUID = 2042227976628604686L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final Serializable ignore) {
+                target.add(schedTaskModal.setContent(new PushTasks(schedTaskModal, pageRef,
+                        ((ResourceTO) model.getObject()).getKey())));
+                schedTaskModal.header(new Model<>(MessageFormat.format(getString("task.push.list"),
+                        ((ResourceTO) model.getObject()).getKey())));
+                schedTaskModal.show(true);
+            }
+        }, ActionLink.ActionType.PUSH_TASKS, IdRepoEntitlement.TASK_LIST);
+
+        panel.add(new ActionLink<>() {
+
+            private static final long serialVersionUID = -5962061673680621813L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final Serializable ignore) {
+                ResourceTO modelObject = ResourceRestClient.read(((ResourceTO) model.getObject()).getKey());
+                target.add(propTaskModal.setContent(
+                        new ResourceStatusModal(propTaskModal, pageRef, modelObject)));
+                propTaskModal.header(new Model<>(MessageFormat.format(getString("resource.reconciliation"),
+                        ((ResourceTO) model.getObject()).getKey())));
+                propTaskModal.show(true);
+            }
+        }, ActionLink.ActionType.RECONCILIATION_RESOURCE, IdRepoEntitlement.USER_UPDATE);
+
+        panel.add(new ActionLink<>() {
+
+            private static final long serialVersionUID = -5432034353017728766L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final Serializable ignore) {
+                ResourceTO modelObject = ResourceRestClient.read(((ResourceTO) model.getObject()).getKey());
+
+                target.add(historyModal.setContent(new AuditHistoryModal<>(
+                        historyModal,
+                        AuditElements.EventCategoryType.LOGIC,
+                        "ResourceLogic",
+                        modelObject,
+                        IdMEntitlement.RESOURCE_UPDATE,
+                        pageRef) {
+
+                    private static final long serialVersionUID = -3712506022627033811L;
+
+                    @Override
+                    protected void restore(final String json, final AjaxRequestTarget target) {
+                        try {
+                            ResourceTO updated = MAPPER.readValue(json, ResourceTO.class);
+                            ResourceRestClient.update(updated);
+
+                            SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
+                        } catch (Exception e) {
+                            LOG.error("While restoring resource {}", ((ResourceTO) model.getObject()).getKey(), e);
+                            SyncopeConsoleSession.get().onException(e);
+                        }
+                        ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
+                    }
+                }));
+
+                historyModal.header(
+                        new Model<>(MessageFormat.format(getString("resource.menu.history"),
+                                ((ResourceTO) model.getObject()).getKey())));
+
+                historyModal.show(true);
+            }
+        }, ActionLink.ActionType.VIEW_AUDIT_HISTORY, String.format("%s,%s", IdMEntitlement.RESOURCE_READ,
+                IdRepoEntitlement.AUDIT_LIST));
+
+        panel.add(new ActionLink<>() {
+
+            private static final long serialVersionUID = 7019899256702149874L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final Serializable ignore) {
+                try {
+                    ResourceTO resource = ResourceRestClient.read(((ResourceTO) model.getObject()).getKey());
+                    resource.setKey("Copy of " + resource.getKey());
+                    // reset some resource objects keys
+                    if (resource.getOrgUnit() != null) {
+                        resource.getOrgUnit().setKey(null);
+                        resource.getOrgUnit().getItems().forEach(item -> item.setKey(null));
+                    }
+                    resource.getProvisions().forEach(provision -> {
+                        provision.setKey(null);
+                        if (provision.getMapping() != null) {
+                            provision.getMapping().getItems().forEach(item -> item.setKey(null));
+                            provision.getMapping().getLinkingItems().clear();
+                        }
+                        provision.getVirSchemas().clear();
+                    });
+                    target.add(modal.setContent(new ResourceWizardBuilder(resource, pageRef).
+                            build(BaseModal.CONTENT_ID, AjaxWizard.Mode.CREATE)));
+
+                    modal.header(new Model<>(MessageFormat.format(getString("resource.clone"), resource.getKey())));
+                    modal.show(true);
+                } catch (SyncopeClientException e) {
+                    LOG.error("While cloning resource {}", ((ResourceTO) model.getObject()).getKey(), e);
+                    SyncopeConsoleSession.get().onException(e);
+                }
+                ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
+            }
+        }, ActionLink.ActionType.CLONE, IdMEntitlement.RESOURCE_CREATE);
+
+        panel.add(new ActionLink<>() {
+
+            private static final long serialVersionUID = 4516186028545701573L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target, final Serializable ignore) {
+                try {
+                    ResourceRestClient.delete(((ResourceTO) model.getObject()).getKey());
+                    target.appendJavaScript(String.format("jsPlumb.remove('%s');",
+                            ((ResourceTO) model.getObject()).getKey()));
+                    SyncopeConsoleSession.get().success(getString(Constants.OPERATION_SUCCEEDED));
+                } catch (SyncopeClientException e) {
+                    LOG.error("While deleting resource {}", ((ResourceTO) model.getObject()).getKey(), e);
+                    SyncopeConsoleSession.get().onException(e);
+                }
+                ((BasePage) pageRef.getPage()).getNotificationPanel().refresh(target);
+            }
+        }, ActionLink.ActionType.DELETE, IdMEntitlement.RESOURCE_DELETE, true);
+
+        return panel;
+    }
+
+    public static class ResourceSearchEvent implements Serializable {
+
+        private static final long serialVersionUID = 213974502541311941L;
+
+        private final AjaxRequestTarget target;
+
+        private final String keyword;
+
+        public ResourceSearchEvent(final AjaxRequestTarget target, final String keyword) {
+            this.target = target;
+            this.keyword = keyword;
+        }
+
+        public AjaxRequestTarget getTarget() {
+            return target;
+        }
+
+        public String getKeyword() {
+            return keyword;
+        }
+    }
+    
+    public static class Builder extends DirectoryPanel.Builder<Serializable, Serializable, ResourceRestClient> {
+
+        private static final long serialVersionUID = -1391308721262593468L;
+
+        public Builder(final PageReference pageRef) {
+            super(new ResourceRestClient(), pageRef);
+            setShowResultPage(false);
+        }
+
+        @Override
+        protected WizardMgtPanel<Serializable> newInstance(final String id, final boolean wizardInModal) {
+            return new ResourceDirectoryPanel(id, this);
+        }
+    }
+}
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/TabularTopology.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/TabularTopology.java
new file mode 100644
index 0000000..b39e047
--- /dev/null
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/topology/TabularTopology.java
@@ -0,0 +1,116 @@
+/*
+ * 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.topology;
+
+import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
+import de.agilecoders.wicket.core.markup.html.bootstrap.tabs.AjaxBootstrapTabbedPanel;
+import org.apache.syncope.client.console.annotations.IdMPage;
+import org.apache.syncope.client.ui.commons.Constants;
+import org.apache.syncope.client.console.pages.BasePage;
+import org.apache.syncope.client.console.pages.Connectors;
+import org.apache.syncope.client.console.pages.Resources;
+import org.apache.syncope.client.console.panels.ConnidLocations;
+import org.apache.syncope.client.console.tasks.SchedTasks;
+import org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog.BaseModal;
+import org.apache.syncope.common.lib.types.IdMEntitlement;
+import org.apache.wicket.extensions.markup.html.tabs.AbstractTab;
+import org.apache.wicket.extensions.markup.html.tabs.ITab;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.Model;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+@IdMPage(label = "TabularTopology", icon = "fas fa-plug", listEntitlement = IdMEntitlement.RESOURCE_LIST, priority = 1)
+public class TabularTopology extends BasePage {
+
+    private static final long serialVersionUID = -4434385801124981824L;
+
+    public TabularTopology() {
+        TopologyWebSocketBehavior websocket = new TopologyWebSocketBehavior();
+        body.add(websocket);
+
+        WebMarkupContainer content = new WebMarkupContainer("content");
+        content.setOutputMarkupId(true);
+        content.add(new AjaxBootstrapTabbedPanel<>("tabbedPanel", buildTabList()));
+        body.add(content);
+    }
+
+    private List<ITab> buildTabList() {
+        final List<ITab> tabs = new ArrayList<>();
+
+        tabs.add(new AbstractTab(new Model<>("CustomTasks")) {
+
+            private static final long serialVersionUID = -6815067322125799251L;
+
+            @Override
+            public Panel getPanel(final String panelId) {
+                BaseModal<Serializable> schedTaskModal = new BaseModal<>(Constants.OUTER) {
+
+                    private static final long serialVersionUID = -1673561782333149836L;
+
+                    @Override
+                    protected void onConfigure() {
+                        super.onConfigure();
+                        setFooterVisible(false);
+                    }
+                };
+                schedTaskModal.size(Modal.Size.Large);
+                return new SchedTasks(schedTaskModal, getPageReference(), true, panelId);
+            }
+        });
+
+        tabs.add(new AbstractTab(new Model<>("Resources")) {
+
+            private static final long serialVersionUID = -6815067322125799251L;
+
+            @Override
+            public Panel getPanel(final String panelId) {
+                return new Resources(panelId, getPageReference());
+            }
+        });
+
+        tabs.add(new AbstractTab(new Model<>("Connectors")) {
+
+            private static final long serialVersionUID = -6815067322125799251L;
+
+            @Override
+            public Panel getPanel(final String panelId) {
+                return new Connectors(panelId, getPageReference());
+            }
+        });
+
+        tabs.add(new AbstractTab(new Model<>("ConnectorServers")) {
+
+            private static final long serialVersionUID = -6815067322125799251L;
+
+            @Override
+            public Panel getPanel(final String panelId) {
+                return new ConnidLocations.Builder(getPageReference()) {
+
+                    private static final long serialVersionUID = -2555113973787214723L;
+
+                }.build(panelId);
+            }
+        });
+
+        return tabs;
+    }
+}
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ConnectorDetailsPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ConnectorDetailsPanel.java
index f9b2a85..32ecfd3 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ConnectorDetailsPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ConnectorDetailsPanel.java
@@ -22,8 +22,10 @@ import java.util.ArrayList;
 import java.util.Iterator;
 import java.util.List;
 import java.util.stream.Collectors;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.syncope.client.console.SyncopeConsoleSession;
 import org.apache.syncope.client.console.commons.RealmsUtils;
+import org.apache.syncope.client.console.rest.ConnectorRestClient;
 import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.console.rest.RealmRestClient;
 import org.apache.syncope.client.console.wicket.markup.html.form.AjaxSearchFieldPanel;
@@ -66,7 +68,7 @@ public class ConnectorDetailsPanel extends WizardStep {
                         ? RealmRestClient.search(RealmsUtils.buildQuery(input)).getResult()
                         : RealmRestClient.list()).
                         stream().filter(realm -> SyncopeConsoleSession.get().getAuthRealms().stream().anyMatch(
-                        authRealm -> realm.getFullPath().startsWith(authRealm))).
+                                authRealm -> realm.getFullPath().startsWith(authRealm))).
                         map(RealmTO::getFullPath).collect(Collectors.toList()).iterator();
             }
         };
@@ -81,17 +83,52 @@ public class ConnectorDetailsPanel extends WizardStep {
         displayName.addRequiredLabel();
         add(displayName);
 
-        AjaxTextFieldPanel location = new AjaxTextFieldPanel(
-                "location", "location", new PropertyModel<>(connInstanceTO, "location"), false);
-        location.addRequiredLabel();
-        location.setOutputMarkupId(true);
-        location.setEnabled(false);
-        add(location);
-
         final AjaxDropDownChoicePanel<String> bundleName = new AjaxDropDownChoicePanel<>(
                 "bundleName",
                 "bundleName",
                 new PropertyModel<>(connInstanceTO, "bundleName"), false);
+
+        if (StringUtils.isNotBlank(connInstanceTO.getLocation())) {
+            AjaxTextFieldPanel location = new AjaxTextFieldPanel(
+                    "location", "location", new PropertyModel<>(connInstanceTO, "location"), false);
+            location.addRequiredLabel();
+            location.setOutputMarkupId(true);
+            location.setEnabled(false);
+            add(location);
+        } else {
+            final AjaxDropDownChoicePanel<String> location = new AjaxDropDownChoicePanel<>(
+                    "location", "location", new PropertyModel<>(connInstanceTO, "location"), false);
+            location.setChoices(new ArrayList<>(SyncopeConsoleSession.get().getPlatformInfo().getConnIdLocations()));
+            location.addRequiredLabel();
+            location.setOutputMarkupId(true);
+            location.getField().setOutputMarkupId(true);
+            add(location);
+
+            location.getField().add(new IndicatorAjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+                private static final long serialVersionUID = -5609231641453245929L;
+
+                @Override
+                protected void onUpdate(final AjaxRequestTarget target) {
+                    ((DropDownChoice<String>) location.getField()).setNullValid(false);
+                    bundleName.setEnabled(true);
+
+                    List<ConnBundleTO> bundles = ConnectorRestClient.getAllBundles().stream().
+                            filter(object -> object.getLocation().equals(connInstanceTO.getLocation())).
+                            collect(Collectors.toList());
+
+                    List<String> listBundles = getBundles(connInstanceTO, bundles);
+                    if (listBundles.size() == 1) {
+                        connInstanceTO.setBundleName(listBundles.get(0));
+                        bundleName.getField().setModelObject(listBundles.get(0));
+                    }
+                    bundleName.setChoices(listBundles);
+
+                    target.add(bundleName);
+                }
+            });
+        }
+
         ((DropDownChoice<String>) bundleName.getField()).setNullValid(true);
 
         List<String> bundleNames = new ArrayList<>();
@@ -124,7 +161,15 @@ public class ConnectorDetailsPanel extends WizardStep {
                 ((DropDownChoice<String>) bundleName.getField()).setNullValid(false);
                 version.setEnabled(true);
 
-                List<String> versions = getVersions(connInstanceTO, bundles);
+                List<String> versions;
+                if (bundles.isEmpty()) {
+                    List<ConnBundleTO> bundles = ConnectorRestClient.getAllBundles().stream().
+                            filter(object -> object.getLocation().equals(connInstanceTO.getLocation())).
+                            collect(Collectors.toList());
+                    versions = getVersions(connInstanceTO, bundles);
+                } else {
+                    versions = getVersions(connInstanceTO, bundles);
+                }
                 if (versions.size() == 1) {
                     connInstanceTO.setVersion(versions.get(0));
                     version.getField().setModelObject(versions.get(0));
@@ -166,7 +211,12 @@ public class ConnectorDetailsPanel extends WizardStep {
 
     private static List<String> getVersions(final ConnInstanceTO connInstanceTO, final List<ConnBundleTO> bundles) {
         return bundles.stream().filter(object -> object.getLocation().equals(connInstanceTO.getLocation())
-                && object.getBundleName().equals(connInstanceTO.getBundleName())).
+                        && object.getBundleName().equals(connInstanceTO.getBundleName())).
                 map(ConnBundleTO::getVersion).collect(Collectors.toList());
     }
+
+    private List<String> getBundles(final ConnInstanceTO connInstanceTO, final List<ConnBundleTO> bundles) {
+        return bundles.stream().filter(object -> object.getLocation().equals(connInstanceTO.getLocation())).
+                map(ConnBundleTO::getBundleName).collect(Collectors.toList());
+    }
 }
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ConnectorWizardBuilder.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ConnectorWizardBuilder.java
index 7a5ae9c..4346106 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ConnectorWizardBuilder.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ConnectorWizardBuilder.java
@@ -122,7 +122,15 @@ public class ConnectorWizardBuilder extends AbstractResourceWizardBuilder<ConnIn
     }
 
     protected static ConnBundleTO getBundle(final ConnInstanceTO connInstanceTO, final List<ConnBundleTO> bundles) {
-        return bundles.stream().filter(bundle
+        List<ConnBundleTO> bundlesList;
+        if (bundles.isEmpty()) {
+            bundlesList = ConnectorRestClient.getAllBundles().stream().
+                    filter(object -> object.getLocation().equals(connInstanceTO.getLocation())).
+                    collect(Collectors.toList());
+        } else {
+            bundlesList = bundles;
+        }
+        return bundlesList.stream().filter(bundle
                 -> bundle.getBundleName().equals(connInstanceTO.getBundleName())
                 && bundle.getVersion().equals(connInstanceTO.getVersion())).
                 findFirst().orElse(null);
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceConnConfPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceConnConfPanel.java
index e86481c..207be00 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceConnConfPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceConnConfPanel.java
@@ -92,7 +92,7 @@ public abstract class ResourceConnConfPanel extends AbstractConnConfPanel<Resour
                     filter(ConnConfProperty::isOverridable).
                     forEachOrdered(props::add);
         }
-        if (createFlag || resourceTO.getConfOverride().isEmpty()) {
+        if (resourceTO.getConfOverride().isEmpty()) {
             resourceTO.getConfOverride().clear();
         } else {
             Map<String, ConnConfProperty> valuedProps = new HashMap<>();
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel.java
index 94ae8b0..b4bf9c8 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceDetailsPanel.java
@@ -18,9 +18,13 @@
  */
 package org.apache.syncope.client.console.wizards.resources;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.stream.Collectors;
+import org.apache.syncope.client.console.rest.ConnectorRestClient;
 import org.apache.syncope.client.console.rest.ImplementationRestClient;
 import org.apache.syncope.client.ui.commons.Constants;
 import org.apache.syncope.client.ui.commons.markup.html.form.AjaxCheckBoxPanel;
@@ -34,6 +38,7 @@ import org.apache.syncope.common.lib.types.IdMImplementationType;
 import org.apache.syncope.common.lib.types.TraceLevel;
 import org.apache.wicket.extensions.wizard.WizardStep;
 import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.form.IChoiceRenderer;
 import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.LoadableDetachableModel;
 import org.apache.wicket.model.Model;
@@ -52,7 +57,7 @@ public class ResourceDetailsPanel extends WizardStep {
         @Override
         protected List<String> load() {
             return ImplementationRestClient.list(IdMImplementationType.PROPAGATION_ACTIONS).stream().
-                map(EntityTO::getKey).sorted().collect(Collectors.toList());
+                    map(EntityTO::getKey).sorted().collect(Collectors.toList());
         }
     };
 
@@ -63,7 +68,7 @@ public class ResourceDetailsPanel extends WizardStep {
         @Override
         protected List<String> load() {
             return ImplementationRestClient.list(IdMImplementationType.PROVISION_SORTER).stream().
-                map(EntityTO::getKey).sorted().collect(Collectors.toList());
+                    map(EntityTO::getKey).sorted().collect(Collectors.toList());
         }
     };
 
@@ -139,10 +144,46 @@ public class ResourceDetailsPanel extends WizardStep {
                 false).
                 setChoices(Arrays.stream(TraceLevel.values()).collect(Collectors.toList())).setNullValid(false));
 
-        container.add(new AjaxTextFieldPanel(
-                "connector",
-                new ResourceModel("connector", "connector").getObject(),
-                new Model<>(resourceTO.getConnectorDisplayName()),
-                false).addRequiredLabel().setEnabled(false));
+        if (resourceTO.getConnector() != null) {
+            container.add(new AjaxTextFieldPanel(
+                    "connector",
+                    new ResourceModel("connector", "connector").getObject(),
+                    new Model<>(resourceTO.getConnectorDisplayName()),
+                    false).addRequiredLabel().setEnabled(false));
+        } else {
+            final AjaxDropDownChoicePanel<String> connector = new AjaxDropDownChoicePanel<>(
+                    "connector",
+                    new ResourceModel("connector", "connector").getObject(),
+                    new PropertyModel<>(resourceTO, "connector"), false);
+            Map<String, String> connectorsMap = new HashMap<>();
+            ConnectorRestClient.getAllConnectors().forEach(conn -> connectorsMap.put(conn.getKey(),
+                    conn.getDisplayName()));
+            connector.setChoices(new ArrayList<>(connectorsMap.keySet()));
+            connector.setChoiceRenderer(new IChoiceRenderer<>() {
+
+                private static final long serialVersionUID = 91313845533448846L;
+
+                private final Map<String, String> valueMap = connectorsMap;
+
+                @Override
+                public String getDisplayValue(final String value) {
+                    return valueMap.get(value) == null ? value : valueMap.get(value);
+                }
+
+                @Override
+                public String getIdValue(final String value, final int i) {
+                    return value;
+                }
+
+                @Override
+                public String getObject(final String id, final IModel<? extends List<? extends String>> choices) {
+                    return id;
+                }
+            });
+            connector.addRequiredLabel();
+            connector.setOutputMarkupId(true);
+            connector.getField().setOutputMarkupId(true);
+            container.add(connector);
+        }
     }
 }
diff --git a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceWizardBuilder.java b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceWizardBuilder.java
index d8c9198..229dd50 100644
--- a/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceWizardBuilder.java
+++ b/client/idm/console/src/main/java/org/apache/syncope/client/console/wizards/resources/ResourceWizardBuilder.java
@@ -19,6 +19,7 @@
 package org.apache.syncope.client.console.wizards.resources;
 
 import java.io.Serializable;
+import java.util.Collections;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.syncope.client.console.rest.ConnectorRestClient;
 import org.apache.syncope.client.console.rest.ResourceRestClient;
@@ -68,8 +69,12 @@ public class ResourceWizardBuilder extends AbstractResourceWizardBuilder<Resourc
             }
 
         });
-        wizardModel.add(new ResourceConnCapabilitiesPanel(
-                resourceTO, ConnectorRestClient.read(resourceTO.getConnector()).getCapabilities()));
+        if (resourceTO.getConnector() != null) {
+            wizardModel.add(new ResourceConnCapabilitiesPanel(
+                    resourceTO, ConnectorRestClient.read(resourceTO.getConnector()).getCapabilities()));
+        } else {
+            wizardModel.add(new ResourceConnCapabilitiesPanel(resourceTO, Collections.emptySet()));
+        }
 
         wizardModel.add(new ResourceSecurityPanel(resourceTO));
         return wizardModel;
@@ -77,7 +82,7 @@ public class ResourceWizardBuilder extends AbstractResourceWizardBuilder<Resourc
 
     @Override
     protected ResourceTO onApplyInternal(final Serializable modelObject) {
-        ResourceTO resourceTO = ResourceTO.class.cast(modelObject);
+        ResourceTO resourceTO = (ResourceTO) modelObject;
         if (createFlag) {
             resourceTO = ResourceRestClient.create(resourceTO);
         } else {
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/pages/Connectors.html b/client/idm/console/src/main/resources/org/apache/syncope/client/console/pages/Connectors.html
new file mode 100644
index 0000000..dcb224a
--- /dev/null
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/pages/Connectors.html
@@ -0,0 +1,35 @@
+<!--
+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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
+  <wicket:panel>
+    <div wicket:id="searchBox">
+      <form wicket:id="form">
+        <div class="input-group mb-3">
+          <span wicket:id="filter">[FILTER]</span>
+          <span class="input-group-btn">
+            <button type="button" class="btn btn-default btn-flat" wicket:id="search">
+              <span class="fas fa-search" aria-hidden="true"></span>
+            </button>
+          </span>
+        </div>
+      </form>
+    </div>
+    <div wicket:id="connectorDirectoryPanel"></div>
+  </wicket:panel>
+</html>
\ No newline at end of file
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/pages/Resources.html b/client/idm/console/src/main/resources/org/apache/syncope/client/console/pages/Resources.html
new file mode 100644
index 0000000..d56e1ab
--- /dev/null
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/pages/Resources.html
@@ -0,0 +1,35 @@
+<!--
+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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
+  <wicket:panel>
+    <div wicket:id="searchBox">
+      <form wicket:id="form">
+        <div class="input-group mb-3">
+          <span wicket:id="filter">[FILTER]</span>
+          <span class="input-group-btn">
+            <button type="button" class="btn btn-default btn-flat" wicket:id="search">
+              <span class="fas fa-search" aria-hidden="true"></span>
+            </button>
+          </span>
+        </div>
+      </form>
+    </div>
+    <div wicket:id="resourceDirectoryPanel"></div>
+  </wicket:panel>
+</html>
\ No newline at end of file
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel.properties
new file mode 100644
index 0000000..feec3b7
--- /dev/null
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel.properties
@@ -0,0 +1,27 @@
+# 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.
+displayName=Connector Name
+connectorName=Connector Type
+
+any.new=New connector
+connector.edit=Edit connector {0}
+connector.menu.add=Add new connector
+connector.menu.remove=Remove connector
+connector.menu.edit=Edit connector
+connector.menu.history=Configuration history
+
+resource.new=New resource
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel_fr_CA.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel_fr_CA.properties
new file mode 100644
index 0000000..c739b94
--- /dev/null
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel_fr_CA.properties
@@ -0,0 +1,27 @@
+# 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.
+displayName=Nom du connecteur
+connectorName=Type de connecteur
+
+any.new=Nouveau connecteur
+connector.edit=Modifier le connecteur {0}
+connector.menu.add=Ajouter un nouveau connecteur
+connector.menu.remove=Retirer le connecteur
+connector.menu.edit=Modifier le connecteur
+connector.menu.history=Historique des configurations
+
+resource.new=Nouvelle ressource
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel_it.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel_it.properties
new file mode 100644
index 0000000..9b3d7c0
--- /dev/null
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel_it.properties
@@ -0,0 +1,27 @@
+# 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.
+displayName=Nome Connettore
+connectorName=Tipo Connettore
+
+any.new=Nuovo connettore
+connector.edit=Modifica connettore {0}
+connector.menu.add=Aggiungi nuovo connettore
+connector.menu.remove=Rimuovi connettore
+connector.menu.edit=Modifica connettore
+connector.menu.history=Storico delle configurazioni
+
+resource.new=Nuova risorsa
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel_ja.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel_ja.properties
new file mode 100644
index 0000000..a2bd6fd
--- /dev/null
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel_ja.properties
@@ -0,0 +1,27 @@
+# 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.
+displayName=\u30b3\u30cd\u30af\u30bf\u540d
+connectorName=\u30b3\u30cd\u30af\u30bf\u30bf\u30a4\u30d7
+
+any.new=\u65b0\u3057\u3044\u30b3\u30cd\u30af\u30bf
+connector.edit=\u30b3\u30cd\u30af\u30bf\u3092\u7de8\u96c6\u3059\u308b {0}
+connector.menu.add=\u65b0\u3057\u3044\u30b3\u30cd\u30af\u30bf\u3092\u8ffd\u52a0\u3057\u307e\u3059
+connector.menu.remove=\u30b3\u30cd\u30af\u30bf\u3092\u53d6\u308a\u5916\u3057\u307e\u3059
+connector.menu.edit=\u30b3\u30cd\u30af\u30bf\u3092\u7de8\u96c6\u3059\u308b
+connector.menu.history=\u69cb\u6210\u5c65\u6b74
+
+resource.new=\u65b0\u3057\u3044\u30ea\u30bd\u30fc\u30b9
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel_pt_BR.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel_pt_BR.properties
new file mode 100644
index 0000000..307a0bb
--- /dev/null
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel_pt_BR.properties
@@ -0,0 +1,27 @@
+# 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.
+displayName=Nome do conector
+connectorName=Tipo de conector
+
+any.new=Novo conector
+connector.edit=Editar conector {0}
+connector.menu.add=Adicionar novo conector
+connector.menu.remove=Remova o conector
+connector.menu.edit=Editar conector
+connector.menu.history=Hist\u00f3rico de Configura\u00e7\u00c3o
+
+resource.new=Novo recurso
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel_ru.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel_ru.properties
new file mode 100644
index 0000000..ec6f84c
--- /dev/null
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnectorDirectoryPanel_ru.properties
@@ -0,0 +1,27 @@
+# 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.
+displayName=\u0418\u043c\u044f\u0020\u0441\u043e\u0435\u0434\u0438\u043d\u0438\u0442\u0435\u043b\u044f
+connectorName=\u0422\u0438\u043f\u0020\u0440\u0430\u0437\u044a\u0435\u043c\u0430
+
+any.new=\u041d\u043e\u0432\u044b\u0439\u0020\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440
+connector.edit=\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c\u0020\u0441\u043e\u0435\u0434\u0438\u043d\u0438\u0442\u0435\u043b\u044c {0}
+connector.menu.add=\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c\u0020\u043d\u043e\u0432\u044b\u0439\u0020\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440
+connector.menu.remove=\u0423\u0434\u0430\u043b\u0438\u0442\u044c\u0020\u0441\u043e\u0435\u0434\u0438\u043d\u0438\u0442\u0435\u043b\u044c
+connector.menu.edit=\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c\u0020\u0441\u043e\u0435\u0434\u0438\u043d\u0438\u0442\u0435\u043b\u044c
+connector.menu.history=\u0418\u0441\u0442\u043e\u0440\u0438\u044f\u0020\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438
+
+resource.new=\u041d\u043e\u0432\u044b\u0439\u0020\u0440\u0435\u0441\u0443\u0440\u0441
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnidLocations.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnidLocations.properties
new file mode 100644
index 0000000..1fd8e9e
--- /dev/null
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnidLocations.properties
@@ -0,0 +1,18 @@
+# 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.
+key=Connid locations
+connector.new=New connector
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnidLocations_fr_CA.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnidLocations_fr_CA.properties
new file mode 100644
index 0000000..6b31536
--- /dev/null
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnidLocations_fr_CA.properties
@@ -0,0 +1,18 @@
+# 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.
+key=Emplacements Connid
+connector.new=Nouveau connecteur
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnidLocations_it.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnidLocations_it.properties
new file mode 100644
index 0000000..c877b7e
--- /dev/null
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnidLocations_it.properties
@@ -0,0 +1,18 @@
+# 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.
+key=Connid locations
+connector.new=Nuovo connettore
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnidLocations_ja.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnidLocations_ja.properties
new file mode 100644
index 0000000..26559d2
--- /dev/null
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnidLocations_ja.properties
@@ -0,0 +1,18 @@
+# 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.
+key=Connid \u306e\u5834\u6240
+connector.new=\u65b0\u3057\u3044\u30b3\u30cd\u30af\u30bf
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnidLocations_pt_BR.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnidLocations_pt_BR.properties
new file mode 100644
index 0000000..3b78e3f
--- /dev/null
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnidLocations_pt_BR.properties
@@ -0,0 +1,18 @@
+# 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.
+key=Locais de Connid
+connector.new=Novo conector
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnidLocations_ru.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnidLocations_ru.properties
new file mode 100644
index 0000000..642498c
--- /dev/null
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ConnidLocations_ru.properties
@@ -0,0 +1,18 @@
+# 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.
+key=Connid \u043b\u043e\u043a\u0430\u0446\u0438\u0438
+connector.new=\u041d\u043e\u0432\u044b\u0439\u0020\u043a\u043e\u043d\u043d\u0435\u043a\u0442\u043e\u0440
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel.properties
new file mode 100644
index 0000000..85c330f
--- /dev/null
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel.properties
@@ -0,0 +1,38 @@
+# 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.
+key=Resource Name
+connectorDisplayName=Connector
+
+any.new=New resource
+resource.edit=Edit resource {0}
+resource.menu.add=Add new resource
+resource.menu.remove=Remove resource
+resource.menu.edit=Edit resource
+resource.menu.provision=Edit provision rules
+resource.menu.explore=Explore resource
+resource.menu.history=Configuration history
+resource.menu.clone=Clone resource
+
+task.propagation.list=Propagation tasks {0}
+task.pull.list=Pull tasks {0}
+task.push.list=Push tasks {0}
+resource.explore.list=Explore ${key}
+resource.reconciliation=Reconciliation {0}
+resource.menu.reconciliation=Reconciliation
+resource.menu.push.list=Push tasks
+resource.menu.pull.list=Pull tasks
+resource.menu.propagation.list=Propagation tasks
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_fr_CA.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_fr_CA.properties
new file mode 100644
index 0000000..ab162bb
--- /dev/null
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_fr_CA.properties
@@ -0,0 +1,38 @@
+# 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.
+key=Nom de la ressource
+connectorDisplayName=Connecteur
+
+any.new=Nouvelle ressource
+resource.edit=Modifier la ressource {0}
+resource.menu.add=Ajouter une nouvelle ressource
+resource.menu.remove=Supprimer la ressource
+resource.menu.edit=Modifier la ressource
+resource.menu.provision=Modifier les r\u00e8gles de mise \u00e0 disposition
+resource.menu.explore=Explorer la ressource
+resource.menu.history=Historique des configurations
+resource.menu.clone=Cloner la ressource
+
+task.propagation.list=T\u00e2ches de propagation {0}
+task.pull.list=T\u00e2ches d\u0027extraction {0}
+task.push.list=Pousser les t\u00e2ches {0}
+resource.explore.list=Explorer ${key}
+resource.reconciliation=R\u00e9conciliation {0}
+resource.menu.reconciliation=R\u00e9conciliation
+resource.menu.push.list=Pousser les t\u00e2ches
+resource.menu.pull.list=T\u00e2ches d\u0027extraction
+resource.menu.propagation.list=T\u00e2ches de propagation
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_it.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_it.properties
new file mode 100644
index 0000000..b5e97bc
--- /dev/null
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_it.properties
@@ -0,0 +1,38 @@
+# 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.
+key=Nome Risorsa
+connectorDisplayName=Connettore
+
+any.new=Nuova risorsa
+resource.edit=Modifica risorsa {0}
+resource.menu.add=Aggiungi nuova risorsa
+resource.menu.remove=Rimuovi risorsa
+resource.menu.edit=Modifica risorsa
+resource.menu.provision=Modifica regole di provisioning
+resource.menu.explore=Esplora risorsa
+resource.menu.history=Storico delle configurazioni
+resource.menu.clone=Duplica risorsa
+
+task.propagation.list=Task di propagazione {0}
+task.pull.list=Pull task {0}
+task.push.list=Push task {0}
+resource.explore.list=Esplora ${key}
+resource.reconciliation=Riconciliazione {0}
+resource.menu.reconciliation=Riconciliazione
+resource.menu.push.list=Push tasks
+resource.menu.pull.list=Pull tasks
+resource.menu.propagation.list=Task di propagazione
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_ja.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_ja.properties
new file mode 100644
index 0000000..e820162
--- /dev/null
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_ja.properties
@@ -0,0 +1,38 @@
+# 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.
+key=\u30ea\u30bd\u30fc\u30b9\u540d
+connectorDisplayName=\u30b3\u30cd\u30af\u30bf
+
+any.new=\u65b0\u3057\u3044\u30ea\u30bd\u30fc\u30b9
+resource.edit=\u30ea\u30bd\u30fc\u30b9\u306e\u7de8\u96c6 {0}
+resource.menu.add=\u65b0\u3057\u3044\u30ea\u30bd\u30fc\u30b9\u3092\u8ffd\u52a0\u3059\u308b
+resource.menu.remove=\u30ea\u30bd\u30fc\u30b9\u3092\u524a\u9664\u3059\u308b
+resource.menu.edit=\u30ea\u30bd\u30fc\u30b9\u306e\u7de8\u96c6
+resource.menu.provision=\u30d7\u30ed\u30d3\u30b8\u30e7\u30cb\u30f3\u30b0\u30eb\u30fc\u30eb\u306e\u7de8\u96c6
+resource.menu.explore=\u30ea\u30bd\u30fc\u30b9\u3092\u63a2\u7d22\u3059\u308b
+resource.menu.history=\u69cb\u6210\u5c65\u6b74
+resource.menu.clone=\u30ea\u30bd\u30fc\u30b9\u306e\u30af\u30ed\u30fc\u30f3
+
+task.propagation.list=\u4f1d\u64ad\u30bf\u30b9\u30af {0}
+task.pull.list=\u30bf\u30b9\u30af\u3092\u30d7\u30eb\u3059\u308b {0}
+task.push.list=\u30d7\u30c3\u30b7\u30e5\u30bf\u30b9\u30af {0}
+resource.explore.list=\u63a2\u691c ${key}
+resource.reconciliation=\u548c\u89e3 {0}
+resource.menu.reconciliation=\u548c\u89e3
+resource.menu.push.list=\u30d7\u30c3\u30b7\u30e5\u30bf\u30b9\u30af
+resource.menu.pull.list=\u30bf\u30b9\u30af\u3092\u30d7\u30eb\u3059\u308b
+resource.menu.propagation.list=\u4f1d\u64ad\u30bf\u30b9\u30af
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_pt_BR.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_pt_BR.properties
new file mode 100644
index 0000000..1600a2c
--- /dev/null
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_pt_BR.properties
@@ -0,0 +1,38 @@
+# 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.
+key=Nome do recurso
+connectorDisplayName=Conector
+
+any.new=Novo recurso
+resource.edit=Editar recurso {0}
+resource.menu.add=Adicionar novo recurso
+resource.menu.remove=Remover recurso
+resource.menu.edit=Editar recurso
+resource.menu.provision=Editar regras de provis\u00e3o
+resource.menu.explore=Explorar recurso
+resource.menu.history=Hist\u00f3rico de Configura\u00e7\u00e3o
+resource.menu.clone=Clonar recurso
+
+task.propagation.list=Tarefas de propaga\u00e7\u00e3o {0}
+task.pull.list=Puxe tarefas {0}
+task.push.list=Tarefas push {0}
+resource.explore.list=Explorar ${key}
+resource.reconciliation=Reconcilia\u00e7\u00e3o {0}
+resource.menu.reconciliation=Reconcilia\u00e7\u00e3o
+resource.menu.push.list=Tarefas push
+resource.menu.pull.list=Puxe tarefas
+resource.menu.propagation.list=Tarefas de propaga\u00e7\u00e3o
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_ru.properties b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_ru.properties
new file mode 100644
index 0000000..b712405
--- /dev/null
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/panels/ResourceDirectoryPanel_ru.properties
@@ -0,0 +1,38 @@
+# 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.
+key=\u0418\u043c\u044f\u0020\u0440\u0435\u0441\u0443\u0440\u0441\u0430
+connectorDisplayName=\u0421\u043e\u0435\u0434\u0438\u043d\u0438\u0442\u0435\u043b\u044c
+
+any.new=\u041d\u043e\u0432\u044b\u0439\u0020\u0440\u0435\u0441\u0443\u0440\u0441
+resource.edit=\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c\u0020\u0440\u0435\u0441\u0443\u0440\u0441 {0}
+resource.menu.add=\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c\u0020\u043d\u043e\u0432\u044b\u0439\u0020\u0440\u0435\u0441\u0443\u0440\u0441
+resource.menu.remove=\u0423\u0434\u0430\u043b\u0438\u0442\u044c\u0020\u0440\u0435\u0441\u0443\u0440\u0441
+resource.menu.edit=\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c\u0020\u0440\u0435\u0441\u0443\u0440\u0441
+resource.menu.provision=\u0418\u0437\u043c\u0435\u043d\u0438\u0442\u044c\u0020\u043f\u0440\u0430\u0432\u0438\u043b\u0430\u0020\u043f\u0440\u0435\u0434\u043e\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f
+resource.menu.explore=\u0418\u0441\u0441\u043b\u0435\u0434\u0443\u0439\u0442\u0435\u0020\u0440\u0435\u0441\u0443\u0440\u0441
+resource.menu.history=\u0418\u0441\u0442\u043e\u0440\u0438\u044f\u0020\u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438
+resource.menu.clone=\u041a\u043b\u043e\u043d\u0438\u0440\u043e\u0432\u0430\u0442\u044c\u0020\u0440\u0435\u0441\u0443\u0440\u0441
+
+task.propagation.list=\u0417\u0430\u0434\u0430\u0447\u0438\u0020\u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0435\u043d\u0438\u044f {0}
+task.pull.list=\u0412\u044b\u0442\u044f\u0433\u0438\u0432\u0430\u0439\u0442\u0435\u0020\u0437\u0430\u0434\u0430\u0447\u0438 {0}
+task.push.list=Push-\u0437\u0430\u0434\u0430\u0447\u0438 {0}
+resource.explore.list=\u041f\u0440\u043e\u0432\u043e\u0434\u0438\u0442\u044c\u0020\u0438\u0441\u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u044f ${key}
+resource.reconciliation=\u041f\u0440\u0438\u043c\u0438\u0440\u0435\u043d\u0438\u0435 {0}
+resource.menu.reconciliation=\u041f\u0440\u0438\u043c\u0438\u0440\u0435\u043d\u0438\u0435
+resource.menu.push.list=Push-\u0437\u0430\u0434\u0430\u0447\u0438
+resource.menu.pull.list=\u0412\u044b\u0442\u044f\u0433\u0438\u0432\u0430\u0439\u0442\u0435\u0020\u0437\u0430\u0434\u0430\u0447\u0438
+resource.menu.propagation.list=\u0417\u0430\u0434\u0430\u0447\u0438\u0020\u0440\u0430\u0441\u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0435\u043d\u0438\u044f
diff --git a/client/idm/console/src/main/resources/org/apache/syncope/client/console/topology/TabularTopology.html b/client/idm/console/src/main/resources/org/apache/syncope/client/console/topology/TabularTopology.html
new file mode 100644
index 0000000..f52d2c3
--- /dev/null
+++ b/client/idm/console/src/main/resources/org/apache/syncope/client/console/topology/TabularTopology.html
@@ -0,0 +1,29 @@
+<!--
+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.
+-->
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
+<wicket:extend>
+  <section class="content" wicket:id="content">
+    <div class="container-fluid">
+      <div class="card card-outline">
+        <div class="card-body" wicket:id="tabbedPanel"/>
+      </div>
+    </div>
+  </section>
+</wicket:extend>
+</html>
\ No newline at end of file
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java
index 9ee4764..f1b3630 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/SyncopeConsoleApplication.java
@@ -104,9 +104,10 @@ public class SyncopeConsoleApplication extends SpringBootServletInitializer {
 
     @ConditionalOnMissingBean(name = "classPathScanImplementationLookup")
     @Bean
-    public ClassPathScanImplementationLookup classPathScanImplementationLookup(final ApplicationContext ctx) {
+    public ClassPathScanImplementationLookup classPathScanImplementationLookup(final ApplicationContext ctx,
+                                                                               final ConsoleProperties props) {
         ClassPathScanImplementationLookup lookup = new ClassPathScanImplementationLookup(
-                ctx.getBeansOfType(ClassPathScanImplementationContributor.class).values());
+                ctx.getBeansOfType(ClassPathScanImplementationContributor.class).values(), props);
         lookup.load();
         return lookup;
     }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationLookup.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationLookup.java
index 65df3a8..11e6edb 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationLookup.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/init/ClassPathScanImplementationLookup.java
@@ -32,6 +32,7 @@ import java.util.Objects;
 import java.util.Set;
 import java.util.stream.Collectors;
 import org.apache.commons.lang3.ArrayUtils;
+import org.apache.syncope.client.console.ConsoleProperties;
 import org.apache.syncope.client.console.annotations.AMPage;
 import org.apache.syncope.client.ui.commons.annotations.ExtPage;
 import org.apache.syncope.client.console.pages.BaseExtPage;
@@ -112,8 +113,12 @@ public class ClassPathScanImplementationLookup {
 
     private List<Class<? extends BasePage>> amPages;
 
-    public ClassPathScanImplementationLookup(final Collection<ClassPathScanImplementationContributor> contributors) {
+    private final ConsoleProperties props;
+
+    public ClassPathScanImplementationLookup(final Collection<ClassPathScanImplementationContributor> contributors,
+                                             final ConsoleProperties props) {
         this.contributors = contributors;
+        this.props = props;
     }
 
     protected ClassPathScanningCandidateComponentProvider scanner() {
@@ -180,7 +185,10 @@ public class ClassPathScanImplementationLookup {
                         }
                     } else if (BasePage.class.isAssignableFrom(clazz)) {
                         if (clazz.isAnnotationPresent(IdMPage.class)) {
-                            idmPages.add((Class<? extends BasePage>) clazz);
+                            if (!clazz.getName().endsWith("Topology")
+                                    || (clazz.getName().equals(props.getPage().get("topology").getName()))) {
+                                idmPages.add((Class<? extends BasePage>) clazz);
+                            }
                         } else if (clazz.isAnnotationPresent(AMPage.class)) {
                             amPages.add((Class<? extends BasePage>) clazz);
                         } else {
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
index 27e2eaf..8d6ec33 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTaskDirectoryPanel.java
@@ -145,6 +145,63 @@ public abstract class SchedTaskDirectoryPanel<T extends SchedTaskTO>
         addInnerObject(templates);
     }
 
+    protected SchedTaskDirectoryPanel(
+            final BaseModal<?> baseModal,
+            final MultilevelPanel multiLevelPanelRef,
+            final TaskType taskType,
+            final Class<T> reference,
+            final PageReference pageRef,
+            final boolean wizardInModal) {
+
+        super(baseModal, multiLevelPanelRef, pageRef, wizardInModal);
+        this.taskType = taskType;
+        this.reference = reference;
+
+        try {
+            schedTaskTO = reference.getDeclaredConstructor().newInstance();
+        } catch (Exception e) {
+            LOG.error("Failure instantiating task", e);
+        }
+
+        this.addNewItemPanelBuilder(new SchedTaskWizardBuilder<>(taskType, schedTaskTO, pageRef), true);
+
+        MetaDataRoleAuthorizationStrategy.authorize(addAjaxLink, RENDER, IdRepoEntitlement.TASK_CREATE);
+
+        enableUtilityButton();
+        setFooterVisibility(false);
+
+        initResultTable();
+
+        container.add(new IndicatorAjaxTimerBehavior(Duration.of(10, ChronoUnit.SECONDS)) {
+
+            private static final long serialVersionUID = -4661303265651934868L;
+
+            @Override
+            protected void onTimer(final AjaxRequestTarget target) {
+                container.modelChanged();
+                target.add(container);
+            }
+        });
+
+        startAt = new TaskStartAtTogglePanel(container, pageRef);
+        addInnerObject(startAt);
+
+        templates = new TemplatesTogglePanel(getActualId(), this, pageRef) {
+
+            private static final long serialVersionUID = -8765794727538618705L;
+
+            @Override
+            protected Serializable onApplyInternal(
+                    final TemplatableTO targetObject, final String type, final AnyTO anyTO) {
+
+                targetObject.getTemplates().put(type, anyTO);
+                TaskRestClient.update(taskType, SchedTaskTO.class.cast(targetObject));
+                return targetObject;
+            }
+        };
+        addInnerObject(templates);
+    }
+
     protected List<IColumn<T, String>> getFieldColumns() {
         List<IColumn<T, String>> columns = new ArrayList<>();
 
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTasks.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTasks.java
index 10eed40..b19b0ef 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTasks.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/SchedTasks.java
@@ -52,4 +52,25 @@ public class SchedTasks extends AbstractTasks {
             }
         });
     }
+
+    public <T extends AnyTO> SchedTasks(final BaseModal<?> baseModal, final PageReference pageReference,
+                                        final boolean wizardInModal, final String id) {
+        super(id);
+
+        final MultilevelPanel mlp = new MultilevelPanel("tasks");
+        add(mlp);
+
+        mlp.setFirstLevel(new SchedTaskDirectoryPanel<>(
+                baseModal, mlp, TaskType.SCHEDULED, SchedTaskTO.class, pageReference, wizardInModal) {
+
+            private static final long serialVersionUID = -2195387360323687302L;
+
+            @Override
+            protected void viewTask(final SchedTaskTO taskTO, final AjaxRequestTarget target) {
+                mlp.next(
+                        new StringResourceModel("task.view", this, new Model<>(Pair.of(null, taskTO))).getObject(),
+                        new TaskExecutionDetails<>(baseModal, taskTO, pageReference), target);
+            }
+        });
+    }
 }
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/TaskDirectoryPanel.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/TaskDirectoryPanel.java
index 1486ab6..26d20db 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/TaskDirectoryPanel.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/tasks/TaskDirectoryPanel.java
@@ -59,6 +59,26 @@ public abstract class TaskDirectoryPanel<T extends TaskTO>
         setShowResultPage(false);
     }
 
+    protected TaskDirectoryPanel(
+            final BaseModal<?> baseModal, final MultilevelPanel multiLevelPanelRef, final PageReference pageRef,
+            final boolean wizardInModal) {
+        super(MultilevelPanel.FIRST_LEVEL_ID, pageRef, wizardInModal);
+        this.baseModal = baseModal;
+        this.multiLevelPanelRef = multiLevelPanelRef;
+        restClient = new TaskRestClient();
+        setShowResultPage(false);
+    }
+
+    protected TaskDirectoryPanel(
+            final BaseModal<?> baseModal, final MultilevelPanel multiLevelPanelRef, final PageReference pageRef,
+            final String id) {
+        super(id, pageRef, false);
+        this.baseModal = baseModal;
+        this.multiLevelPanelRef = multiLevelPanelRef;
+        restClient = new TaskRestClient();
+        setShowResultPage(false);
+    }
+
     @Override
     protected void resultTableCustomChanges(final AjaxDataTablePanel.Builder<T, String> resultTableBuilder) {
         resultTableBuilder.setMultiLevelPanel(baseModal, multiLevelPanelRef);
diff --git a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLink.java b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLink.java
index 762ffe7..0c15ac9 100644
--- a/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLink.java
+++ b/client/idrepo/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/ActionLink.java
@@ -52,6 +52,8 @@ public abstract class ActionLink<T extends Serializable> implements Serializable
         REMOVE_SYNC_TOKEN("update"),
         CLONE("create"),
         CREATE("create"),
+        CREATE_CONNECTOR("create"),
+        CREATE_RESOURCE("create"),
         TEMPLATE("read"),
         EDIT("read"),
         TYPE_EXTENSIONS("read"),
@@ -95,6 +97,7 @@ public abstract class ActionLink<T extends Serializable> implements Serializable
         PROVISION_MEMBERS("update"),
         RECONCILIATION_PUSH("update"),
         RECONCILIATION_PULL("update"),
+        RECONCILIATION_RESOURCE("update"),
         MANAGE_RESOURCES("update"),
         MANAGE_ACCOUNTS("update"),
         MERGE_ACCOUNTS("update"),
@@ -102,6 +105,8 @@ public abstract class ActionLink<T extends Serializable> implements Serializable
         MANAGE_GROUPS("update"),
         PROPAGATION_TASKS("read"),
         NOTIFICATION_TASKS("read"),
+        PULL_TASKS("read"),
+        PUSH_TASKS("read"),
         ZOOM_IN("zoomin"),
         ZOOM_OUT("zoomout"),
         VIEW_EXECUTIONS("read"),
@@ -109,7 +114,8 @@ public abstract class ActionLink<T extends Serializable> implements Serializable
         MANAGE_APPROVAL("edit"),
         EDIT_APPROVAL("edit"),
         VIEW_AUDIT_HISTORY("read"),
-        EXTERNAL_EDITOR("externalEditor");
+        EXTERNAL_EDITOR("externalEditor"),
+        EXPLORE_RESOURCE("search");
 
         private final String actionId;
 
@@ -174,4 +180,3 @@ public abstract class ActionLink<T extends Serializable> implements Serializable
         return this;
     }
 }
-
diff --git a/client/idrepo/console/src/main/resources/console.properties b/client/idrepo/console/src/main/resources/console.properties
index 41407e7..51a37c6 100644
--- a/client/idrepo/console/src/main/resources/console.properties
+++ b/client/idrepo/console/src/main/resources/console.properties
@@ -56,6 +56,7 @@ console.page.types=org.apache.syncope.client.console.pages.Types
 console.page.policies=org.apache.syncope.client.console.pages.Policies
 console.page.notifications=org.apache.syncope.client.console.pages.Notifications
 console.page.parameters=org.apache.syncope.client.console.pages.Parameters
+console.page.topology=org.apache.syncope.client.console.topology.Topology
 
 console.default-any-panel-class=org.apache.syncope.client.console.panels.AnyPanel
 
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel.properties
index b655b41..80667bb 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel.properties
@@ -38,6 +38,14 @@ create.class=fa fa-plus
 create.title=create
 create.alt=create icon
 
+create_connector.class=glyphicon glyphicon-plus
+create_connector.title=create connector
+create_connector.alt=create icon
+
+create_resource.class=glyphicon glyphicon-plus
+create_resource.title=create resource
+create_resource.alt=create icon
+
 template.class=fa fa-list-alt
 template.title=template
 template.alt=template icon
@@ -241,6 +249,10 @@ reconciliation_pull.class=fa fa-chevron-circle-left
 reconciliation_pull.title=pull
 reconciliation_pull.alt=reconciliation pull icon
 
+reconciliation_resource.class=fa fa-chevron-circle-left
+reconciliation_resource.title=reconciliation
+reconciliation_resource.alt=reconciliation resource icon
+
 manage_resources.class=fa fa-sitemap
 manage_resources.title=manage resources
 manage_resources.alt=manage resources icon
@@ -261,6 +273,14 @@ notification_tasks.class=fas fa-envelope
 notification_tasks.title=notification tasks
 notification_tasks.alt=notification tasks icon
 
+pull_tasks.class=fa fa-chevron-circle-left
+pull_tasks.title=pull tasks
+pull_tasks.alt=pull tasks icon
+
+push_tasks.class=fa fa-chevron-circle-right
+push_tasks.title=push tasks
+push_tasks.alt=push tasks icon
+
 zoom_in.class=fa fa-search-plus
 zoom_in.title=zoom-in
 zoom_in.alt=zoom-in icon
@@ -285,3 +305,6 @@ merge_accounts.class=fa fa-compress-alt
 merge_accounts.title=merge accounts
 merge_accounts.alt=merge accounts icon
 
+explore_resource.class=fa fa-eye
+explore_resource.title=explore resource
+explore_resource.alt=explore resource icon
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_fr_CA.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_fr_CA.properties
index 5b4c348..61b6b36 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_fr_CA.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_fr_CA.properties
@@ -32,6 +32,15 @@ clone.alt=ic\u00f4ne cl\u00f4ner
 create.class=fa fa-plus
 create.title=cr\u00e9er
 create.alt=ic\u00f4ne cr\u00e9er
+
+create_connector.class=glyphicon glyphicon-plus
+create_connector.title=cr\u00e9er un connecteur
+create_connector.alt=ic\u00f4ne cr\u00e9er
+
+create_resource.class=glyphicon glyphicon-plus
+create_resource.title=cr\u00e9er une ressource
+create_resource.alt=ic\u00f4ne cr\u00e9er
+
 template.class=fa fa-list-alt
 template.title=mod\u00e8le
 template.alt=ic\u00f4ne mod\u00e8le
@@ -185,6 +194,11 @@ reconciliation_push.alt=ic\u00f4ne push r\u00e9conciliation
 reconciliation_pull.class=fa fa-chevron-circle-left
 reconciliation_pull.title=pull
 reconciliation_pull.alt=ic\u00f4ne pull r\u00e9conciliation
+
+reconciliation_resource.class=fa fa-chevron-circle-left
+reconciliation_resource.title=r\u00e9conciliation
+reconciliation_resource.alt=ic\u00f4ne de ressource de r\u00e9conciliation
+
 manage_resources.class=fa fa-sitemap
 manage_resources.title=g\u00e9rer ressources
 manage_resources.alt=ic\u00f4ne g\u00e9rer ressources
@@ -200,6 +214,15 @@ propagation_tasks.alt=ic\u00f4ne t\u00e2ches de propagation
 notification_tasks.class=fas fa-envelope
 notification_tasks.title=t\u00e2ches de notification
 notification_tasks.alt=ic\u00f4ne t\u00e2ches de notification
+
+pull_tasks.class=fa fa-chevron-circle-left
+pull_tasks.title=t\u00c2ches d\u0027extraction
+pull_tasks.alt=ic\u00f4ne t\u00c2ches d\u0027extraction
+
+push_tasks.class=fa fa-chevron-circle-right
+push_tasks.title=pousser des t\u00c2ches
+push_tasks.alt=ic\u00f4ne pousser des t\u00c2ches
+
 zoom_in.class=fa fa-search-plus
 zoom_in.title=zoom-in
 zoom_in.alt=ic\u00f4ne zoom-in
@@ -218,3 +241,7 @@ external_editor.alt=external editor icon
 merge_accounts.class=fa fa-compress-alt
 merge_accounts.title=merge accounts
 merge_accounts.alt=merge accounts icon
+
+explore_resource.class=fa fa-eye
+explore_resource.title=explorer la ressource
+explore_resource.alt=ic\u00f4ne explorer la ressource
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_it.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_it.properties
index 4800f58..e3f3524 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_it.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_it.properties
@@ -38,6 +38,14 @@ create.class=fa fa-plus
 create.title=crea
 create.alt=create icon
 
+create_connector.class=glyphicon glyphicon-plus
+create_connector.title=crea connettore
+create_connector.alt=create icon
+
+create_resource.class=glyphicon glyphicon-plus
+create_resource.title=crea risorsa
+create_resource.alt=create icon
+
 template.class=fa fa-list-alt
 template.title=modello
 template.alt=template icon
@@ -250,6 +258,14 @@ notification_tasks.class=fas fa-envelope
 notification_tasks.title=task di notifica
 notification_tasks.alt=notification tasks icon
 
+pull_tasks.class=fa fa-chevron-circle-left
+pull_tasks.title=pull tasks
+pull_tasks.alt=pull tasks icon
+
+push_tasks.class=fa fa-chevron-circle-right
+push_tasks.title=push tasks
+push_tasks.alt=push tasks icon
+
 zoom_in.class=fa fa-search-plus
 zoom_in.title=ingrandisci
 zoom_in.alt=zoom-in icon
@@ -265,6 +281,10 @@ reconciliation_pull.class=fa fa-chevron-circle-left
 reconciliation_pull.title=pull
 reconciliation_pull.alt=reconciliation pull icon
 
+reconciliation_resource.class=fa fa-chevron-circle-left
+reconciliation_resource.title=riconciliazione
+reconciliation_resource.alt=reconciliation resource icon
+
 manage_accounts.class=fas fa-users
 manage_accounts.title=gestisci account
 manage_accounts.alt=manage accounts icon
@@ -284,3 +304,7 @@ external_editor.alt=external editor icon
 merge_accounts.class=fa fa-compress-alt
 merge_accounts.title=merge accounts
 merge_accounts.alt=merge accounts icon
+
+explore_resource.class=fa fa-eye
+explore_resource.title=esplora risorsa
+explore_resource.alt=explore resource icon
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_ja.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_ja.properties
index e0ec802..3d05e7b 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_ja.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_ja.properties
@@ -38,6 +38,14 @@ create.class=fa fa-plus
 create.title=\u4f5c\u6210
 create.alt=\u4f5c\u6210
 
+create_connector.class=glyphicon glyphicon-plus
+create_connector.title= \u30b3\u30cd\u30af\u30bf\u3092\u4f5c\u6210\u3059\u308b
+create_connector.alt=\u4f5c\u6210
+
+create_resource.class=glyphicon glyphicon-plus
+create_resource.title= \u30ea\u30bd\u30fc\u30b9\u3092\u4f5c\u6210\u3059\u308b
+create_resource.alt=\u4f5c\u6210
+
 template.class=fa fa-list-alt
 template.title=\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8
 template.alt=\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8
@@ -250,6 +258,14 @@ notification_tasks.class=fas fa-envelope
 notification_tasks.title=\u901a\u77e5\u30bf\u30b9\u30af
 notification_tasks.alt=\u901a\u77e5\u30bf\u30b9\u30af
 
+pull_tasks.class=fa fa-chevron-circle-left
+pull_tasks.title=\u30bf\u30b9\u30af\u3092\u30d7\u30eb\u3059\u308b
+pull_tasks.alt=\u30bf\u30b9\u30af\u30a2\u30a4\u30b3\u30f3\u3092\u5f15\u3063\u5f35\u308b
+
+push_tasks.class=fa fa-chevron-circle-right
+push_tasks.title=\u30d7\u30c3\u30b7\u30e5\u30bf\u30b9\u30af
+push_tasks.alt=\u30bf\u30b9\u30af\u30a2\u30a4\u30b3\u30f3\u3092\u62bc\u3059
+
 zoom_in.class=fa fa-search-plus
 zoom_in.title=\u30ba\u30fc\u30e0\u30a4\u30f3
 zoom_in.alt=\u30ba\u30fc\u30e0\u30a4\u30f3
@@ -266,7 +282,11 @@ reconciliation_pull.class=fa fa-chevron-circle-left
 reconciliation_pull.title=\u30d7\u30eb
 reconciliation_pull.alt=\u7167\u5408\u30d7\u30eb icon
 
-manage_accounts.class=fas fa-users
+reconciliation_resource.class=fa fa-chevron-circle-left
+reconciliation_resource.title=\u548c\u89e3
+reconciliation_resource.alt=\u8abf\u6574\u30ea\u30bd\u30fc\u30b9\u30a2\u30a4\u30b3\u30f3
+
+manage_accounts.class=fa fa-users
 manage_accounts.title=\u30a2\u30ab\u30a6\u30f3\u30c8\u3092\u7ba1\u7406\u3059\u308b
 manage_accounts.alt=\u30a2\u30ab\u30a6\u30f3\u30c8\u7ba1\u7406\u30a2\u30a4\u30b3\u30f3
 
@@ -285,3 +305,7 @@ external_editor.alt=external editor icon
 merge_accounts.class=fa fa-compress-alt
 merge_accounts.title=merge accounts
 merge_accounts.alt=merge accounts icon
+
+explore_resource.class=fa fa-eye
+explore_resource.title=\u30ea\u30bd\u30fc\u30b9\u3092\u63a2\u7d22\u3059\u308b
+explore_resource.alt=\u30ea\u30bd\u30fc\u30b9\u30a2\u30a4\u30b3\u30f3\u3092\u63a2\u3059
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_pt_BR.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_pt_BR.properties
index 6002660..b7c4993 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_pt_BR.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_pt_BR.properties
@@ -38,6 +38,14 @@ create.class=fa fa-plus
 create.title=create
 create.alt=create icon
 
+create_connector.class=glyphicon glyphicon-plus
+create_connector.title=criar conector
+create_connector.alt=criar \u00edcone
+
+create_resource.class=glyphicon glyphicon-plus
+create_resource.title=criar recurso
+create_resource.alt=criar \u00edcone
+
 template.class=fa fa-list-alt
 template.title=template
 template.alt=template icon
@@ -241,6 +249,10 @@ reconciliation_pull.class=fa fa-chevron-circle-left
 reconciliation_pull.title=pull
 reconciliation_pull.alt=reconciliation pull icon
 
+reconciliation_resource.class=fa fa-chevron-circle-left
+reconciliation_resource.title=reconcilia\u00e7\u00c3o
+reconciliation_resource.alt=\u00edcone de recurso de reconcilia\u00e7\u00c3o
+
 manage_resources.class=fa fa-sitemap
 manage_resources.title=manage resources
 manage_resources.alt=manage resources icon
@@ -261,6 +273,14 @@ notification_tasks.class=fas fa-envelope
 notification_tasks.title=notification tasks
 notification_tasks.alt=notification tasks icon
 
+pull_tasks.class=fa fa-chevron-circle-left
+pull_tasks.title=puxar tarefas
+pull_tasks.alt=\u00edcone de puxar tarefas
+
+push_tasks.class=fa fa-chevron-circle-right
+push_tasks.title=tarefas push
+push_tasks.alt=\u00edcone de tarefas push
+
 zoom_in.class=fa fa-search-plus
 zoom_in.title=zoom-in
 zoom_in.alt=zoom-in icon
@@ -289,3 +309,7 @@ external_editor.alt=external editor icon
 merge_accounts.class=fa fa-compress-alt
 merge_accounts.title=merge accounts
 merge_accounts.alt=merge accounts icon
+
+explore_resource.class=fa fa-eye
+explore_resource.title=explorar recurso
+explore_resource.alt=\u00edcone de explorar recurso
diff --git a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_ru.properties b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_ru.properties
index 050558b..e215cd7 100644
--- a/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_ru.properties
+++ b/client/idrepo/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/ActionsPanel_ru.properties
@@ -38,6 +38,14 @@ create.class=fa fa-plus
 create.title=create
 create.alt=create icon
 
+create_connector.class=glyphicon glyphicon-plus
+create_connector.title=\u0441\u043e\u0437\u0434\u0430\u0442\u044c\u0020\u0441\u043e\u0435\u0434\u0438\u043d\u0438\u0442\u0435\u043b\u044c
+create_connector.alt=\u0441\u043e\u0437\u0434\u0430\u0442\u044c\u0020\u0437\u043d\u0430\u0447\u043e\u043a
+
+create_resource.class=glyphicon glyphicon-plus
+create_resource.title=\u0441\u043e\u0437\u0434\u0430\u0442\u044c\u0020\u0440\u0435\u0441\u0443\u0440\u0441
+create_resource.alt=\u0441\u043e\u0437\u0434\u0430\u0442\u044c\u0020\u0437\u043d\u0430\u0447\u043e\u043a
+
 template.class=fa fa-list-alt
 template.title=template
 template.alt=template icon
@@ -250,6 +258,14 @@ notification_tasks.class=fas fa-envelope
 notification_tasks.title=notification tasks
 notification_tasks.alt=notification tasks icon
 
+pull_tasks.class=fa fa-chevron-circle-left
+pull_tasks.title=\u0442\u044f\u043d\u0443\u0442\u044c\u0020\u0437\u0430\u0434\u0430\u0447\u0438
+pull_tasks.alt=\u0432\u044b\u0442\u0430\u0449\u0438\u0442\u044c\u0020\u0437\u043d\u0430\u0447\u043e\u043a\u0020\u0437\u0430\u0434\u0430\u0447\u0438
+
+push_tasks.class=fa fa-chevron-circle-right
+push_tasks.title=push-\u0437\u0430\u0434\u0430\u0447\u0438
+push_tasks.alt=\u0437\u043d\u0430\u0447\u043e\u043a push-\u0437\u0430\u0434\u0430\u0447\u0438
+
 zoom_in.class=fa fa-search-plus
 zoom_in.title=zoom-in
 zoom_in.alt=zoom-in icon
@@ -266,7 +282,11 @@ reconciliation_pull.class=fa fa-chevron-circle-left
 reconciliation_pull.title=pull
 reconciliation_pull.alt=reconciliation pull icon
 
-manage_accounts.class=fas fa-users
+reconciliation_resource.class=fa fa-chevron-circle-left
+reconciliation_resource.title=\u043f\u0440\u0438\u043c\u0438\u0440\u0435\u043d\u0438\u0435
+reconciliation_resource.alt=\u0437\u043d\u0430\u0447\u043e\u043a\u0020\u0440\u0435\u0441\u0443\u0440\u0441\u0430\u0020\u0441\u043e\u0433\u043b\u0430\u0441\u043e\u0432\u0430\u043d\u0438\u044f
+
+manage_accounts.class=fa fa-users
 manage_accounts.title=manage accounts
 manage_accounts.alt=manage accounts icon
 
@@ -285,3 +305,7 @@ external_editor.alt=external editor icon
 merge_accounts.class=fa fa-compress-alt
 merge_accounts.title=merge accounts
 merge_accounts.alt=merge accounts icon
+
+explore_resource.class=fa fa-eye
+explore_resource.title=\u0438\u0437\u0443\u0447\u0438\u0442\u044c\u0020\u0440\u0435\u0441\u0443\u0440\u0441
+explore_resource.alt=\u0438\u0437\u0443\u0447\u0438\u0442\u044c\u0020\u0437\u043d\u0430\u0447\u043e\u043a\u0020\u0440\u0435\u0441\u0443\u0440\u0441\u0430
diff --git a/client/idrepo/console/src/test/java/org/apache/syncope/client/console/AbstractTest.java b/client/idrepo/console/src/test/java/org/apache/syncope/client/console/AbstractTest.java
index f33eb5f..71892b7 100644
--- a/client/idrepo/console/src/test/java/org/apache/syncope/client/console/AbstractTest.java
+++ b/client/idrepo/console/src/test/java/org/apache/syncope/client/console/AbstractTest.java
@@ -137,7 +137,8 @@ public abstract class AbstractTest {
 
         @Bean
         public ClassPathScanImplementationLookup classPathScanImplementationLookup() {
-            ClassPathScanImplementationLookup lookup = new ClassPathScanImplementationLookup(Set.of());
+            ClassPathScanImplementationLookup lookup = new ClassPathScanImplementationLookup(Set.of(),
+                    consoleProperties());
             lookup.load();
             return lookup;
         }
diff --git a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AbstractConsoleITCase.java b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AbstractConsoleITCase.java
index 985da5c..708b81c 100644
--- a/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AbstractConsoleITCase.java
+++ b/fit/core-reference/src/test/java/org/apache/syncope/fit/console/AbstractConsoleITCase.java
@@ -39,6 +39,7 @@ import org.apache.syncope.client.console.commons.PolicyTabProvider;
 import org.apache.syncope.client.console.commons.PreviewUtils;
 import org.apache.syncope.client.console.init.ClassPathScanImplementationLookup;
 import org.apache.syncope.client.console.pages.Login;
+import org.apache.syncope.client.console.topology.Topology;
 import org.apache.syncope.client.console.wizards.any.UserFormFinalizerUtils;
 import org.apache.syncope.client.lib.SyncopeClientFactoryBean;
 import org.apache.syncope.client.ui.commons.ApplicationContextProvider;
@@ -71,6 +72,7 @@ public abstract class AbstractConsoleITCase extends AbstractUIITCase {
             consoleProperties.setAnonymousKey(ANONYMOUS_KEY);
 
             consoleProperties.setCsrf(false);
+            consoleProperties.getPage().put("topology", Topology.class);
 
             return consoleProperties;
         }
@@ -102,7 +104,8 @@ public abstract class AbstractConsoleITCase extends AbstractUIITCase {
 
         @Bean
         public ClassPathScanImplementationLookup classPathScanImplementationLookup() {
-            ClassPathScanImplementationLookup lookup = new ClassPathScanImplementationLookup(Set.of());
+            ClassPathScanImplementationLookup lookup = new ClassPathScanImplementationLookup(Set.of(),
+                    consoleProperties());
             lookup.load();
             return lookup;
         }