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

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

http://git-wip-us.apache.org/repos/asf/syncope/blob/34ec6712/client/console/src/main/java/org/apache/syncope/client/console/panels/ResourceMappingPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ResourceMappingPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ResourceMappingPanel.java
new file mode 100644
index 0000000..b00bc1b
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ResourceMappingPanel.java
@@ -0,0 +1,644 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.commons.JexlHelpUtils;
+import org.apache.syncope.client.console.panels.ResourceConnConfPanel.ConnConfModEvent;
+import org.apache.syncope.client.console.rest.ConnectorRestClient;
+import org.apache.syncope.client.console.rest.SchemaRestClient;
+import org.apache.syncope.client.console.wicket.markup.html.form.AjaxCheckBoxPanel;
+import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDecoratedCheckbox;
+import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel;
+import org.apache.syncope.client.console.wicket.markup.html.form.AjaxTextFieldPanel;
+import org.apache.syncope.client.console.wicket.markup.html.form.FieldPanel;
+import org.apache.syncope.client.console.wicket.markup.html.form.MappingPurposePanel;
+import org.apache.syncope.common.lib.to.ConnIdObjectClassTO;
+import org.apache.syncope.common.lib.to.ConnInstanceTO;
+import org.apache.syncope.common.lib.to.MappingItemTO;
+import org.apache.syncope.common.lib.to.MappingTO;
+import org.apache.syncope.common.lib.to.ResourceTO;
+import org.apache.syncope.common.lib.types.AttributableType;
+import org.apache.syncope.common.lib.types.ConnConfProperty;
+import org.apache.syncope.common.lib.types.IntMappingType;
+import org.apache.syncope.common.lib.types.MappingPurpose;
+import org.apache.wicket.Component;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.attributes.AjaxCallListener;
+import org.apache.wicket.ajax.attributes.AjaxRequestAttributes;
+import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import org.apache.wicket.ajax.markup.html.form.AjaxButton;
+import org.apache.wicket.event.IEvent;
+import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxButton;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.PropertyModel;
+import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.spring.injection.annot.SpringBean;
+
+/**
+ * Resource mapping panel.
+ */
+public class ResourceMappingPanel extends Panel {
+
+    private static final long serialVersionUID = -7982691107029848579L;
+
+    /**
+     * Mapping field style sheet.
+     */
+    private static final String FIELD_STYLE = "ui-widget-content ui-corner-all short_fixedsize";
+
+    /**
+     * Mapping field style sheet.
+     */
+    private static final String DEF_FIELD_STYLE = "ui-widget-content ui-corner-all";
+
+    /**
+     * Mapping field style sheet.
+     */
+    private static final String SHORT_FIELD_STYLE = "ui-widget-content ui-corner-all veryshort_fixedsize";
+
+    /**
+     * Schema rest client.
+     */
+    @SpringBean
+    private SchemaRestClient schemaRestClient;
+
+    /**
+     * ConnInstance rest client.
+     */
+    @SpringBean
+    private ConnectorRestClient connRestClient;
+
+    /**
+     * Resource schema name.
+     */
+    private final List<String> schemaNames;
+
+    /**
+     * Add mapping button.
+     */
+    private final AjaxButton addMappingBtn;
+
+    /**
+     * All mappings.
+     */
+    private final ListView<MappingItemTO> mappings;
+
+    /**
+     * External resource to be updated.
+     */
+    private final ResourceTO resourceTO;
+
+    /**
+     * User / group.
+     */
+    private final AttributableType attrType;
+
+    /**
+     * Mapping container.
+     */
+    private final WebMarkupContainer mappingContainer;
+
+    /**
+     * AccountLink container.
+     */
+    private final WebMarkupContainer accountLinkContainer;
+
+    private final AjaxCheckBoxPanel accountLinkCheckbox;
+
+    private MappingTO getMapping() {
+        MappingTO result = null;
+
+        if (AttributableType.USER == this.attrType) {
+            if (this.resourceTO.getUmapping() == null) {
+                this.resourceTO.setUmapping(new MappingTO());
+            }
+            result = this.resourceTO.getUmapping();
+        }
+        if (AttributableType.GROUP == this.attrType) {
+            if (this.resourceTO.getGmapping() == null) {
+                this.resourceTO.setGmapping(new MappingTO());
+            }
+            result = this.resourceTO.getGmapping();
+        }
+
+        return result;
+    }
+
+    /**
+     * Attribute Mapping Panel.
+     *
+     * @param id panel id
+     * @param resourceTO external resource
+     * @param attrType USER / GROUP
+     */
+    public ResourceMappingPanel(final String id, final ResourceTO resourceTO, final AttributableType attrType) {
+        super(id);
+        setOutputMarkupId(true);
+
+        this.resourceTO = resourceTO;
+        this.attrType = attrType;
+
+        this.mappingContainer = new WebMarkupContainer("mappingContainer");
+        this.mappingContainer.setOutputMarkupId(true);
+        add(this.mappingContainer);
+
+        this.accountLinkContainer = new WebMarkupContainer("accountLinkContainer");
+        this.accountLinkContainer.setOutputMarkupId(true);
+        add(this.accountLinkContainer);
+
+        if (this.resourceTO.getConnectorId() != null && this.resourceTO.getConnectorId() > 0) {
+            schemaNames = getSchemaNames(this.resourceTO.getConnectorId(), this.resourceTO.getConnConfProperties());
+
+            setEnabled();
+        } else {
+            schemaNames = Collections.<String>emptyList();
+        }
+
+        final WebMarkupContainer jexlHelp = JexlHelpUtils.getJexlHelpWebContainer("jexlHelp");
+
+        AjaxLink<Void> questionMarkJexlHelp = JexlHelpUtils.getAjaxLink(jexlHelp, "questionMarkJexlHelp");
+        mappingContainer.add(questionMarkJexlHelp);
+        questionMarkJexlHelp.add(jexlHelp);
+
+        final Label passwordLabel = new Label("passwordLabel", new ResourceModel("password"));
+        mappingContainer.add(passwordLabel);
+        if (AttributableType.USER != ResourceMappingPanel.this.attrType) {
+            passwordLabel.setVisible(false);
+        }
+
+        Collections.sort(getMapping().getItems(), new Comparator<MappingItemTO>() {
+
+            @Override
+            public int compare(final MappingItemTO left, final MappingItemTO right) {
+                int compared;
+                if (left == null && right == null) {
+                    compared = 0;
+                } else if (left == null) {
+                    compared = 1;
+                } else if (right == null) {
+                    compared = -1;
+                } else if (left.getPurpose() == MappingPurpose.BOTH && right.getPurpose() != MappingPurpose.BOTH) {
+                    compared = -1;
+                } else if (left.getPurpose() != MappingPurpose.BOTH && right.getPurpose() == MappingPurpose.BOTH) {
+                    compared = 1;
+                } else if (left.getPurpose() == MappingPurpose.PROPAGATION
+                        && (right.getPurpose() == MappingPurpose.SYNCHRONIZATION || right.getPurpose()
+                        == MappingPurpose.NONE)) {
+                    compared = -1;
+                } else if (left.getPurpose() == MappingPurpose.SYNCHRONIZATION
+                        && right.getPurpose() == MappingPurpose.PROPAGATION) {
+                    compared = 1;
+                } else if (left.getPurpose() == MappingPurpose.SYNCHRONIZATION
+                        && right.getPurpose() == MappingPurpose.NONE) {
+                    compared = -1;
+                } else if (left.getPurpose() == MappingPurpose.NONE
+                        && right.getPurpose() != MappingPurpose.NONE) {
+                    compared = 1;
+                } else if (left.isAccountid()) {
+                    compared = -1;
+                } else if (right.isAccountid()) {
+                    compared = 1;
+                } else if (left.isPassword()) {
+                    compared = -1;
+                } else if (right.isPassword()) {
+                    compared = 1;
+                } else {
+                    compared = left.getIntAttrName().compareTo(right.getIntAttrName());
+                }
+                return compared;
+            }
+        });
+
+        mappings = new ListView<MappingItemTO>("mappings", getMapping().getItems()) {
+
+            private static final long serialVersionUID = 4949588177564901031L;
+
+            @Override
+            protected void populateItem(final ListItem<MappingItemTO> item) {
+                final MappingItemTO mapItem = item.getModelObject();
+                if (mapItem.getPurpose() == null) {
+                    mapItem.setPurpose(MappingPurpose.BOTH);
+                }
+
+                AttributableType entity = null;
+                if (mapItem.getIntMappingType() != null) {
+                    entity = mapItem.getIntMappingType().getAttributableType();
+                }
+
+                final List<IntMappingType> attrTypes = new ArrayList<IntMappingType>(getAttributeTypes(entity));
+
+                item.add(new AjaxDecoratedCheckbox("toRemove", new Model<Boolean>(Boolean.FALSE)) {
+
+                    private static final long serialVersionUID = 7170946748485726506L;
+
+                    @Override
+                    protected void onUpdate(final AjaxRequestTarget target) {
+                        int index = -1;
+                        for (int i = 0; i < getMapping().getItems().size() && index == -1; i++) {
+                            if (mapItem.equals(getMapping().getItems().get(i))) {
+                                index = i;
+                            }
+                        }
+
+                        if (index != -1) {
+                            getMapping().getItems().remove(index);
+                            item.getParent().removeAll();
+                            target.add(ResourceMappingPanel.this);
+                        }
+                    }
+
+                    @Override
+                    protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) {
+                        super.updateAjaxAttributes(attributes);
+
+                        final AjaxCallListener ajaxCallListener = new AjaxCallListener() {
+
+                            private static final long serialVersionUID = 7160235486520935153L;
+
+                            @Override
+                            public CharSequence getPrecondition(final Component component) {
+                                return "if (!confirm('" + getString("confirmDelete") + "')) return false;";
+                            }
+                        };
+                        attributes.getAjaxCallListeners().add(ajaxCallListener);
+                    }
+                });
+
+                final AjaxDropDownChoicePanel<String> intAttrNames =
+                        new AjaxDropDownChoicePanel<String>("intAttrNames", getString("intAttrNames"),
+                                new PropertyModel<String>(mapItem, "intAttrName"), false);
+                intAttrNames.setChoices(schemaNames);
+                intAttrNames.setRequired(true);
+                intAttrNames.setStyleSheet(FIELD_STYLE);
+                intAttrNames.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+                    private static final long serialVersionUID = -1107858522700306810L;
+
+                    @Override
+                    protected void onUpdate(final AjaxRequestTarget target) {
+                    }
+                });
+                item.add(intAttrNames);
+
+                final AjaxDropDownChoicePanel<IntMappingType> intMappingTypes =
+                        new AjaxDropDownChoicePanel<IntMappingType>("intMappingTypes",
+                                new ResourceModel("intMappingTypes", "intMappingTypes").getObject(),
+                                new PropertyModel<IntMappingType>(mapItem, "intMappingType"));
+                intMappingTypes.setRequired(true);
+                intMappingTypes.setChoices(attrTypes);
+                intMappingTypes.setStyleSheet(FIELD_STYLE);
+                item.add(intMappingTypes);
+
+                final AjaxDropDownChoicePanel<AttributableType> entitiesPanel =
+                        new AjaxDropDownChoicePanel<AttributableType>("entities",
+                                new ResourceModel("entities", "entities").getObject(), new Model<AttributableType>(
+                                        entity));
+                entitiesPanel.setChoices(attrType == AttributableType.GROUP
+                        ? Collections.<AttributableType>singletonList(AttributableType.GROUP)
+                        : Arrays.asList(AttributableType.values()));
+                entitiesPanel.setStyleSheet(DEF_FIELD_STYLE);
+                entitiesPanel.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+                    private static final long serialVersionUID = -1107858522700306810L;
+
+                    @Override
+                    protected void onUpdate(final AjaxRequestTarget target) {
+                        attrTypes.clear();
+                        attrTypes.addAll(getAttributeTypes(entitiesPanel.getModelObject()));
+                        intMappingTypes.setChoices(attrTypes);
+
+                        intAttrNames.setChoices(Collections.<String>emptyList());
+
+                        target.add(intMappingTypes.getField());
+                        target.add(intAttrNames.getField());
+                    }
+                });
+                item.add(entitiesPanel);
+
+                final FieldPanel<String> extAttrNames = new AjaxTextFieldPanel("extAttrName",
+                        new ResourceModel("extAttrNames", "extAttrNames").getObject(),
+                        new PropertyModel<String>(mapItem, "extAttrName"));
+                ((AjaxTextFieldPanel) extAttrNames).setChoices(schemaNames);
+
+                boolean required = false;
+                if (mapItem.isPassword()) {
+                    ((AjaxTextFieldPanel) extAttrNames).setModelObject(null);
+                } else {
+                    required = true;
+                }
+                extAttrNames.setRequired(required);
+                extAttrNames.setEnabled(required);
+                extAttrNames.setStyleSheet(FIELD_STYLE);
+                item.add(extAttrNames);
+
+                final AjaxTextFieldPanel mandatory = new AjaxTextFieldPanel("mandatoryCondition",
+                        new ResourceModel("mandatoryCondition", "mandatoryCondition").getObject(),
+                        new PropertyModel<String>(mapItem, "mandatoryCondition"));
+                mandatory.setChoices(Arrays.asList(new String[] { "true", "false" }));
+                mandatory.setStyleSheet(SHORT_FIELD_STYLE);
+                item.add(mandatory);
+
+                final AjaxCheckBoxPanel accountId = new AjaxCheckBoxPanel("accountId",
+                        new ResourceModel("accountId", "accountId").getObject(),
+                        new PropertyModel<Boolean>(mapItem, "accountid"));
+                accountId.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+                    private static final long serialVersionUID = -1107858522700306810L;
+
+                    @Override
+                    protected void onUpdate(final AjaxRequestTarget target) {
+                        if (accountId.getModelObject()) {
+                            mapItem.setMandatoryCondition("true");
+                            mandatory.setEnabled(false);
+                        } else {
+                            mapItem.setMandatoryCondition("false");
+                            mandatory.setEnabled(true);
+                        }
+                        target.add(mandatory);
+                    }
+                });
+                item.add(accountId);
+
+                final AjaxCheckBoxPanel password = new AjaxCheckBoxPanel("password",
+                        new ResourceModel("password", "password").getObject(),
+                        new PropertyModel<Boolean>(mapItem, "password"));
+                password.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+                    private static final long serialVersionUID = -1107858522700306810L;
+
+                    @Override
+                    protected void onUpdate(final AjaxRequestTarget target) {
+                        extAttrNames.setEnabled(!mapItem.isAccountid() && !password.getModelObject());
+                        extAttrNames.setModelObject(null);
+                        extAttrNames.setRequired(!password.getModelObject());
+                        target.add(extAttrNames);
+
+                        setAccountId(intMappingTypes.getModelObject(), accountId, password);
+                        target.add(accountId);
+                    }
+                });
+                item.add(password);
+                if (AttributableType.USER != ResourceMappingPanel.this.attrType) {
+                    password.setVisible(false);
+                }
+
+                final WebMarkupContainer purpose = new WebMarkupContainer("purpose");
+                purpose.setOutputMarkupId(Boolean.TRUE);
+
+                final MappingPurposePanel panel = new MappingPurposePanel("purposeActions",
+                        new PropertyModel<MappingPurpose>(mapItem, "purpose"), purpose);
+
+                purpose.add(panel);
+
+                item.add(purpose);
+
+                intMappingTypes.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+                    private static final long serialVersionUID = -1107858522700306810L;
+
+                    @Override
+                    protected void onUpdate(final AjaxRequestTarget target) {
+                        setAttrNames(intMappingTypes.getModelObject(), intAttrNames);
+                        target.add(intAttrNames);
+
+                        setAccountId(intMappingTypes.getModelObject(), accountId, password);
+                        target.add(accountId);
+                    }
+                });
+
+                setAttrNames(mapItem.getIntMappingType(), intAttrNames);
+                setAccountId(mapItem.getIntMappingType(), accountId, password);
+            }
+        };
+
+        mappings.setReuseItems(true);
+        mappingContainer.add(mappings);
+
+        addMappingBtn = new IndicatingAjaxButton("addMappingBtn", new ResourceModel("add")) {
+
+            private static final long serialVersionUID = -4804368561204623354L;
+
+            @Override
+            protected void onSubmit(final AjaxRequestTarget target, final Form<?> form) {
+                getMapping().getItems().add(new MappingItemTO());
+                target.add(ResourceMappingPanel.this);
+            }
+        };
+        addMappingBtn.setDefaultFormProcessing(false);
+        addMappingBtn.setEnabled(this.resourceTO.getConnectorId() != null && this.resourceTO.getConnectorId() > 0);
+        mappingContainer.add(addMappingBtn);
+
+        boolean accountLinkEnabled = false;
+        if (getMapping().getAccountLink() != null) {
+            accountLinkEnabled = true;
+        }
+        accountLinkCheckbox = new AjaxCheckBoxPanel("accountLinkCheckbox",
+                new ResourceModel("accountLinkCheckbox", "accountLinkCheckbox").getObject(),
+                new Model<Boolean>(Boolean.valueOf(accountLinkEnabled)));
+        accountLinkCheckbox.setEnabled(true);
+
+        accountLinkContainer.add(accountLinkCheckbox);
+
+        final AjaxTextFieldPanel accountLink = new AjaxTextFieldPanel("accountLink",
+                new ResourceModel("accountLink", "accountLink").getObject(),
+                new PropertyModel<String>(getMapping(), "accountLink"));
+        accountLink.setEnabled(accountLinkEnabled);
+        accountLinkContainer.add(accountLink);
+
+        accountLinkCheckbox.getField().add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+            private static final long serialVersionUID = -1107858522700306810L;
+
+            @Override
+            protected void onUpdate(final AjaxRequestTarget target) {
+                if (accountLinkCheckbox.getModelObject()) {
+                    accountLink.setEnabled(Boolean.TRUE);
+                    accountLink.setModelObject("");
+                } else {
+                    accountLink.setEnabled(Boolean.FALSE);
+                    accountLink.setModelObject("");
+                }
+
+                target.add(accountLink);
+            }
+        });
+    }
+
+    private List<String> getSchemaNames(final Long connectorId, final Set<ConnConfProperty> conf) {
+        final ConnInstanceTO connInstanceTO = new ConnInstanceTO();
+        connInstanceTO.setKey(connectorId);
+        connInstanceTO.getConfiguration().addAll(conf);
+
+        return connRestClient.getSchemaNames(connInstanceTO);
+    }
+
+    private void setEnabled() {
+        final ConnInstanceTO connInstanceTO = new ConnInstanceTO();
+        connInstanceTO.setKey(this.resourceTO.getConnectorId());
+        connInstanceTO.getConfiguration().addAll(this.resourceTO.getConnConfProperties());
+
+        List<ConnIdObjectClassTO> objectClasses = connRestClient.getSupportedObjectClasses(connInstanceTO);
+
+        boolean enabled = objectClasses.isEmpty()
+                || (AttributableType.USER == attrType && objectClasses.contains(ConnIdObjectClassTO.ACCOUNT))
+                || (AttributableType.GROUP == attrType && objectClasses.contains(ConnIdObjectClassTO.GROUP));
+        this.mappingContainer.setEnabled(enabled);
+        this.mappingContainer.setVisible(enabled);
+        this.accountLinkContainer.setEnabled(enabled);
+        this.accountLinkContainer.setVisible(enabled);
+
+        if (!enabled) {
+            getMapping().getItems().clear();
+            getMapping().setAccountLink(null);
+            if (this.accountLinkCheckbox != null) {
+                this.accountLinkCheckbox.setModelObject(null);
+            }
+        }
+    }
+
+    @Override
+    public void onEvent(final IEvent<?> event) {
+        if (event.getPayload() instanceof ConnConfModEvent) {
+            final AjaxRequestTarget target = ((ConnConfModEvent) event.getPayload()).getTarget();
+
+            final List<ConnConfProperty> conf = ((ConnConfModEvent) event.getPayload()).getConfiguration();
+
+            mappings.removeAll();
+
+            addMappingBtn.setEnabled(resourceTO.getConnectorId() != null && resourceTO.getConnectorId() > 0);
+
+            schemaNames.clear();
+            schemaNames.addAll(getSchemaNames(resourceTO.getConnectorId(), new HashSet<ConnConfProperty>(conf)));
+
+            setEnabled();
+
+            target.add(this);
+        }
+    }
+
+    /**
+     * Set attribute names for a drop down choice list.
+     *
+     * @param type attribute type.
+     * @param toBeUpdated drop down choice to be updated.
+     */
+    private void setAttrNames(final IntMappingType type, final AjaxDropDownChoicePanel<String> toBeUpdated) {
+        toBeUpdated.setRequired(true);
+        toBeUpdated.setEnabled(true);
+
+        if (type == null || type.getAttributableType() == null) {
+            toBeUpdated.setChoices(Collections.<String>emptyList());
+        } else {
+            switch (type) {
+                // user attribute names
+                case UserPlainSchema:
+                case GroupPlainSchema:
+                case MembershipPlainSchema:
+                    toBeUpdated.setChoices(schemaRestClient.getPlainSchemaNames(type.getAttributableType()));
+                    break;
+
+                case UserDerivedSchema:
+                case GroupDerivedSchema:
+                case MembershipDerivedSchema:
+                    toBeUpdated.setChoices(schemaRestClient.getDerSchemaNames(type.getAttributableType()));
+                    break;
+
+                case UserVirtualSchema:
+                case GroupVirtualSchema:
+                case MembershipVirtualSchema:
+                    toBeUpdated.setChoices(schemaRestClient.getVirSchemaNames(type.getAttributableType()));
+                    break;
+
+                case UserId:
+                case Password:
+                case Username:
+                case GroupId:
+                case GroupName:
+                default:
+                    toBeUpdated.setRequired(false);
+                    toBeUpdated.setEnabled(false);
+                    toBeUpdated.setChoices(Collections.<String>emptyList());
+            }
+        }
+    }
+
+    /**
+     * Enable/Disable accountId checkbox.
+     *
+     * @param type attribute type.
+     * @param accountId accountId checkbox.
+     * @param password password checkbox.
+     */
+    private void setAccountId(final IntMappingType type, final AjaxCheckBoxPanel accountId,
+            final AjaxCheckBoxPanel password) {
+
+        if (type != null && type.getAttributableType() != null) {
+            switch (type) {
+                case UserVirtualSchema:
+                case GroupVirtualSchema:
+                case MembershipVirtualSchema:
+                // Virtual accountId is not permitted
+                case Password:
+                    // AccountId cannot be derived from password.
+                    accountId.setReadOnly(true);
+                    accountId.setModelObject(false);
+                    break;
+
+                default:
+                    if (password.getModelObject()) {
+                        accountId.setReadOnly(true);
+                        accountId.setModelObject(false);
+                    } else {
+                        accountId.setReadOnly(false);
+                    }
+            }
+        }
+    }
+
+    /**
+     * Get all attribute types from a selected attribute type.
+     *
+     * @param entity entity.
+     * @return all attribute types.
+     */
+    private List<IntMappingType> getAttributeTypes(final AttributableType entity) {
+        final List<IntMappingType> res = new ArrayList<>();
+
+        if (entity != null) {
+            res.addAll(IntMappingType.getAttributeTypes(AttributableType.valueOf(entity.name())));
+        }
+
+        return res;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/34ec6712/client/console/src/main/java/org/apache/syncope/client/console/panels/ResourceSecurityPanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/ResourceSecurityPanel.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/ResourceSecurityPanel.java
new file mode 100644
index 0000000..15d2fe8
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/ResourceSecurityPanel.java
@@ -0,0 +1,186 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.client.console.panels;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.syncope.client.console.rest.PolicyRestClient;
+import org.apache.syncope.client.console.wicket.markup.html.form.AjaxDropDownChoicePanel;
+import org.apache.syncope.common.lib.to.AbstractPolicyTO;
+import org.apache.syncope.common.lib.to.ResourceTO;
+import org.apache.syncope.common.lib.types.PolicyType;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.form.ChoiceRenderer;
+import org.apache.wicket.markup.html.form.DropDownChoice;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.LoadableDetachableModel;
+import org.apache.wicket.model.PropertyModel;
+import org.apache.wicket.model.ResourceModel;
+import org.apache.wicket.spring.injection.annot.SpringBean;
+
+public class ResourceSecurityPanel extends Panel {
+
+    private static final long serialVersionUID = -7982691107029848579L;
+
+    @SpringBean
+    private PolicyRestClient policyRestClient;
+
+    private IModel<Map<Long, String>> passwordPolicies = null;
+
+    private IModel<Map<Long, String>> accountPolicies = null;
+
+    private IModel<Map<Long, String>> syncPolicies = null;
+
+    public ResourceSecurityPanel(final String id, final ResourceTO resourceTO) {
+
+        super(id);
+
+        setOutputMarkupId(true);
+
+        passwordPolicies = new LoadableDetachableModel<Map<Long, String>>() {
+
+            private static final long serialVersionUID = 5275935387613157437L;
+
+            @Override
+            protected Map<Long, String> load() {
+                Map<Long, String> res = new HashMap<>();
+                for (AbstractPolicyTO policyTO : policyRestClient.getPolicies(PolicyType.PASSWORD)) {
+                    res.put(policyTO.getKey(), policyTO.getDescription());
+                }
+                return res;
+            }
+        };
+
+        accountPolicies = new LoadableDetachableModel<Map<Long, String>>() {
+
+            private static final long serialVersionUID = -2012833443695917883L;
+
+            @Override
+            protected Map<Long, String> load() {
+                Map<Long, String> res = new HashMap<>();
+                for (AbstractPolicyTO policyTO : policyRestClient.getPolicies(PolicyType.ACCOUNT)) {
+                    res.put(policyTO.getKey(), policyTO.getDescription());
+                }
+                return res;
+            }
+        };
+
+        syncPolicies = new LoadableDetachableModel<Map<Long, String>>() {
+
+            private static final long serialVersionUID = -2012833443695917883L;
+
+            @Override
+            protected Map<Long, String> load() {
+                Map<Long, String> res = new HashMap<>();
+                for (AbstractPolicyTO policyTO : policyRestClient.getPolicies(PolicyType.SYNC)) {
+                    res.put(policyTO.getKey(), policyTO.getDescription());
+                }
+                return res;
+            }
+        };
+
+        final WebMarkupContainer securityContainer = new WebMarkupContainer("security");
+
+        securityContainer.setOutputMarkupId(true);
+        add(securityContainer);
+
+        // -------------------------------
+        // Password policy specification
+        // -------------------------------
+        final AjaxDropDownChoicePanel<Long> passwordPolicy = new AjaxDropDownChoicePanel<Long>("passwordPolicy",
+                new ResourceModel("passwordPolicy", "passwordPolicy").getObject(), new PropertyModel<Long>(resourceTO,
+                        "passwordPolicy"));
+
+        passwordPolicy.setChoiceRenderer(new PolicyRenderer(PolicyType.PASSWORD));
+
+        passwordPolicy.setChoices(new ArrayList<>(passwordPolicies.getObject().keySet()));
+
+        ((DropDownChoice<?>) passwordPolicy.getField()).setNullValid(true);
+
+        securityContainer.add(passwordPolicy);
+        // -------------------------------
+
+        // -------------------------------
+        // Account policy specification
+        // -------------------------------
+        final AjaxDropDownChoicePanel<Long> accountPolicy = new AjaxDropDownChoicePanel<Long>("accountPolicy",
+                new ResourceModel("accountPolicy", "accountPolicy").getObject(), new PropertyModel<Long>(resourceTO,
+                        "accountPolicy"));
+
+        accountPolicy.setChoiceRenderer(new PolicyRenderer(PolicyType.ACCOUNT));
+
+        accountPolicy.setChoices(new ArrayList<Long>(accountPolicies.getObject().keySet()));
+
+        ((DropDownChoice<?>) accountPolicy.getField()).setNullValid(true);
+
+        securityContainer.add(accountPolicy);
+        // -------------------------------
+
+        // -------------------------------
+        // Sync policy specification
+        // -------------------------------
+        final AjaxDropDownChoicePanel<Long> syncPolicy = new AjaxDropDownChoicePanel<Long>("syncPolicy",
+                new ResourceModel("syncPolicy", "syncPolicy").getObject(), new PropertyModel<Long>(resourceTO,
+                        "syncPolicy"));
+
+        syncPolicy.setChoiceRenderer(new PolicyRenderer(PolicyType.SYNC));
+
+        syncPolicy.setChoices(new ArrayList<Long>(syncPolicies.getObject().keySet()));
+
+        ((DropDownChoice<?>) syncPolicy.getField()).setNullValid(true);
+
+        securityContainer.add(syncPolicy);
+        // -------------------------------
+    }
+
+    private class PolicyRenderer extends ChoiceRenderer<Long> {
+
+        private static final long serialVersionUID = 8060500161321947000L;
+
+        private PolicyType type;
+
+        public PolicyRenderer(final PolicyType type) {
+            super();
+            this.type = type;
+        }
+
+        @Override
+        public Object getDisplayValue(final Long object) {
+            switch (type) {
+                case ACCOUNT:
+                    return accountPolicies.getObject().get(object);
+                case PASSWORD:
+                    return passwordPolicies.getObject().get(object);
+                case SYNC:
+                    return syncPolicies.getObject().get(object);
+                default:
+                    return "";
+            }
+        }
+
+        @Override
+        public String getIdValue(final Long object, final int index) {
+            return String.valueOf(object != null
+                    ? object
+                    : 0L);
+        }
+    };
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/34ec6712/client/console/src/main/java/org/apache/syncope/client/console/panels/Resources.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/panels/Resources.java b/client/console/src/main/java/org/apache/syncope/client/console/panels/Resources.java
new file mode 100644
index 0000000..755b2d1
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/panels/Resources.java
@@ -0,0 +1,485 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import org.apache.syncope.client.console.PreferenceManager;
+import org.apache.syncope.client.console.commons.Constants;
+import org.apache.syncope.client.console.commons.SortableDataProviderComparator;
+import org.apache.syncope.client.console.pages.BasePage;
+import org.apache.syncope.client.console.pages.ConnectorModalPage;
+import org.apache.syncope.client.console.pages.ProvisioningModalPage;
+import org.apache.syncope.client.console.pages.ResourceModalPage;
+import org.apache.syncope.client.console.rest.ConnectorRestClient;
+import org.apache.syncope.client.console.rest.ResourceRestClient;
+import org.apache.syncope.client.console.wicket.ajax.markup.html.ClearIndicatingAjaxLink;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionLinksPanel;
+import org.apache.syncope.client.console.wicket.markup.html.form.LinkPanel;
+import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.to.ResourceTO;
+import org.apache.syncope.common.lib.to.GroupTO;
+import org.apache.syncope.common.lib.to.UserTO;
+import org.apache.syncope.common.lib.types.Entitlement;
+import org.apache.wicket.AttributeModifier;
+import org.apache.wicket.Page;
+import org.apache.wicket.PageReference;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
+import org.apache.wicket.ajax.markup.html.AjaxLink;
+import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy;
+import org.apache.wicket.event.IEvent;
+import org.apache.wicket.extensions.ajax.markup.html.modal.ModalWindow;
+import org.apache.wicket.extensions.markup.html.repeater.data.grid.ICellPopulator;
+import org.apache.wicket.extensions.markup.html.repeater.data.sort.SortOrder;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.AbstractColumn;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.IColumn;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.ISortableDataProvider;
+import org.apache.wicket.extensions.markup.html.repeater.data.table.PropertyColumn;
+import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.DropDownChoice;
+import org.apache.wicket.markup.html.form.Form;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.model.AbstractReadOnlyModel;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.model.PropertyModel;
+import org.apache.wicket.model.StringResourceModel;
+import org.apache.wicket.spring.injection.annot.SpringBean;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Resources WebPage.
+ */
+public class Resources extends Panel {
+
+    private static final long serialVersionUID = -3789252860990261728L;
+
+    protected static final Logger LOG = LoggerFactory.getLogger(Resources.class);
+
+    private static final int WIN_HEIGHT = 600;
+
+    private static final int WIN_WIDTH = 1100;
+
+    @SpringBean
+    private PreferenceManager prefMan;
+
+    @SpringBean
+    private ResourceRestClient resourceRestClient;
+
+    private final ModalWindow createResourceWin;
+
+    private final ModalWindow editResourceWin;
+
+    private final int resourcePaginatorRows;
+
+    private WebMarkupContainer resourceContainer;
+
+    private final ModalWindow editConnectorWin;
+
+    /**
+     * Modal window to be used for user status management.
+     */
+    protected final ModalWindow statusmodal = new ModalWindow("statusModal");
+
+    /**
+     * Schemas to be shown modal window height.
+     */
+    private static final int STATUS_MODAL_WIN_HEIGHT = 500;
+
+    /**
+     * Schemas to be shown modal window width.
+     */
+    private static final int STATUS_MODAL_WIN_WIDTH = 700;
+
+    @SpringBean
+    private ConnectorRestClient connectorRestClient;
+
+    private final PageReference pageRef;
+
+    public Resources(final String id, final PageReference pageRef) {
+        super(id);
+        this.pageRef = pageRef;
+
+        createResourceWin = new ModalWindow("createResourceWin");
+        add(createResourceWin);
+
+        editResourceWin = new ModalWindow("editResourceWin");
+        add(editResourceWin);
+
+        editConnectorWin = new ModalWindow("editConnectorWin");
+        add(editConnectorWin);
+
+        statusmodal.setCssClassName(ModalWindow.CSS_CLASS_GRAY);
+        statusmodal.setInitialHeight(STATUS_MODAL_WIN_HEIGHT);
+        statusmodal.setInitialWidth(STATUS_MODAL_WIN_WIDTH);
+        statusmodal.setCookieName("status-modal");
+        add(statusmodal);
+
+        resourcePaginatorRows = prefMan.getPaginatorRows(getRequest(), Constants.PREF_RESOURCES_PAGINATOR_ROWS);
+
+        setupResources();
+    }
+
+    private void setupResources() {
+        List<IColumn<ResourceTO, String>> columns = new ArrayList<>();
+
+        columns.add(new PropertyColumn<ResourceTO, String>(new StringResourceModel("key", this, null), "key", "key"));
+
+        columns.add(new AbstractColumn<ResourceTO, String>(
+                new StringResourceModel("connector", this, null, "connector")) {
+
+                    private static final long serialVersionUID = 8263694778917279290L;
+
+                    @Override
+                    public void populateItem(final Item<ICellPopulator<ResourceTO>> cellItem, final String componentId,
+                            final IModel<ResourceTO> rowModel) {
+
+                        final AjaxLink<String> editLink = new ClearIndicatingAjaxLink<String>("link", pageRef) {
+
+                            private static final long serialVersionUID = -7978723352517770644L;
+
+                            @Override
+                            protected void onClickInternal(final AjaxRequestTarget target) {
+
+                                editConnectorWin.setPageCreator(new ModalWindow.PageCreator() {
+
+                                    private static final long serialVersionUID = -7834632442532690940L;
+
+                                    @Override
+                                    public Page createPage() {
+                                        return new ConnectorModalPage(Resources.this.pageRef,
+                                                editConnectorWin,
+                                                connectorRestClient.read(rowModel.getObject().getConnectorId()));
+                                    }
+                                });
+
+                                editConnectorWin.show(target);
+                            }
+                        };
+                        editLink.add(new Label("linkTitle", rowModel.getObject().getConnectorDisplayName()));
+
+                        LinkPanel editConnPanel = new LinkPanel(componentId);
+                        editConnPanel.add(editLink);
+
+                        cellItem.add(editConnPanel);
+
+                        MetaDataRoleAuthorizationStrategy.authorize(editConnPanel, ENABLE, Entitlement.CONNECTOR_READ);
+                    }
+                });
+
+        columns.add(new AbstractColumn<ResourceTO, String>(
+                new StringResourceModel("propagationPrimary", this, null)) {
+
+                    private static final long serialVersionUID = -3503023501954863131L;
+
+                    @Override
+                    public void populateItem(final Item<ICellPopulator<ResourceTO>> item,
+                            final String componentId, final IModel<ResourceTO> model) {
+
+                        item.add(new Label(componentId, ""));
+                        item.add(new AttributeModifier("class", new Model<>(
+                                                Boolean.toString(model.getObject().isPropagationPrimary()))));
+                    }
+
+                    @Override
+                    public String getCssClass() {
+                        return "narrowcolumn";
+                    }
+                });
+
+        columns.add(new PropertyColumn<ResourceTO, String>(new StringResourceModel(
+                "propagationPriority", this, null), "propagationPriority", "propagationPriority") {
+
+                    private static final long serialVersionUID = 1L;
+
+                    @Override
+                    public String getCssClass() {
+                        return "narrowcolumn";
+                    }
+                });
+
+        columns.add(new AbstractColumn<ResourceTO, String>(new StringResourceModel("actions", this, null, "")) {
+
+            private static final long serialVersionUID = 2054811145491901166L;
+
+            @Override
+            public String getCssClass() {
+                return "action";
+            }
+
+            @Override
+            public void populateItem(final Item<ICellPopulator<ResourceTO>> cellItem, final String componentId,
+                    final IModel<ResourceTO> model) {
+
+                final ResourceTO resourceTO = model.getObject();
+
+                final ActionLinksPanel panel = new ActionLinksPanel(componentId, model, pageRef);
+                panel.add(new ActionLink() {
+
+                    private static final long serialVersionUID = -3722207913631435501L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target) {
+                        statusmodal.setPageCreator(new ModalWindow.PageCreator() {
+
+                            private static final long serialVersionUID = -7834632442532690940L;
+
+                            @Override
+                            public Page createPage() {
+                                return new ProvisioningModalPage<>(
+                                        pageRef, statusmodal, model.getObject(), UserTO.class);
+                            }
+                        });
+
+                        statusmodal.show(target);
+                    }
+                }, ActionLink.ActionType.MANAGE_USERS, "Resources");
+
+                panel.add(new ActionLink() {
+
+                    private static final long serialVersionUID = -3722207913631435501L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target) {
+
+                        statusmodal.setPageCreator(new ModalWindow.PageCreator() {
+
+                            private static final long serialVersionUID = -7834632442532690940L;
+
+                            @Override
+                            public Page createPage() {
+                                return new ProvisioningModalPage<>(
+                                        pageRef, statusmodal, model.getObject(), GroupTO.class);
+                            }
+                        });
+
+                        statusmodal.show(target);
+                    }
+                }, ActionLink.ActionType.MANAGE_GROUPS, "Resources");
+
+                panel.add(new ActionLink() {
+
+                    private static final long serialVersionUID = -3722207913631435501L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target) {
+                        resourceTO.setUsyncToken(null);
+                        resourceTO.setRsyncToken(null);
+                        try {
+                            resourceRestClient.update(resourceTO);
+                            info(getString(Constants.OPERATION_SUCCEEDED));
+                        } catch (SyncopeClientException e) {
+                            error(getString(Constants.ERROR) + ":" + e.getMessage());
+
+                            LOG.error("While resetting sync token from " + resourceTO.getKey(), e);
+                        }
+
+                        ((BasePage) pageRef.getPage()).getFeedbackPanel().refresh(target);
+                        target.add(resourceContainer);
+                    }
+                }, ActionLink.ActionType.RESET, "Resources");
+
+                panel.add(new ActionLink() {
+
+                    private static final long serialVersionUID = -3722207913631435501L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target) {
+                        editResourceWin.setPageCreator(new ModalWindow.PageCreator() {
+
+                            private static final long serialVersionUID = -7834632442532690940L;
+
+                            @Override
+                            public Page createPage() {
+                                return new ResourceModalPage(pageRef, editResourceWin, resourceTO, false);
+                            }
+                        });
+
+                        editResourceWin.show(target);
+                    }
+                }, ActionLink.ActionType.EDIT, "Resources");
+
+                panel.add(new ActionLink() {
+
+                    private static final long serialVersionUID = -3722207913631435501L;
+
+                    @Override
+                    public void onClick(final AjaxRequestTarget target) {
+                        try {
+                            resourceRestClient.delete(resourceTO.getKey());
+                            info(getString(Constants.OPERATION_SUCCEEDED));
+                        } catch (SyncopeClientException e) {
+                            error(getString(Constants.ERROR) + ": " + e.getMessage());
+
+                            LOG.error("While deleting resource " + resourceTO.getKey(), e);
+                        }
+
+                        ((BasePage) pageRef.getPage()).getFeedbackPanel().refresh(target);
+                        target.add(resourceContainer);
+                    }
+                }, ActionLink.ActionType.DELETE, "Resources");
+
+                cellItem.add(panel);
+            }
+        });
+
+        final AjaxDataTablePanel<ResourceTO, String> table = new AjaxDataTablePanel<>(
+                "resourceDatatable",
+                columns,
+                (ISortableDataProvider<ResourceTO, String>) new ResourcesProvider(),
+                resourcePaginatorRows,
+                Arrays.asList(new ActionLink.ActionType[] { ActionLink.ActionType.DELETE }),
+                resourceRestClient,
+                "key",
+                "Resources",
+                pageRef);
+
+        resourceContainer = new WebMarkupContainer("resourceContainer");
+        resourceContainer.add(table);
+        resourceContainer.setOutputMarkupId(true);
+
+        add(resourceContainer);
+
+        ((BasePage) pageRef.getPage()).setWindowClosedCallback(createResourceWin, resourceContainer);
+        ((BasePage) pageRef.getPage()).setWindowClosedCallback(editResourceWin, resourceContainer);
+
+        createResourceWin.setCssClassName(ModalWindow.CSS_CLASS_GRAY);
+        createResourceWin.setInitialHeight(WIN_HEIGHT);
+        createResourceWin.setInitialWidth(WIN_WIDTH);
+        createResourceWin.setCookieName("create-res-modal");
+
+        editResourceWin.setCssClassName(ModalWindow.CSS_CLASS_GRAY);
+        editResourceWin.setInitialHeight(WIN_HEIGHT);
+        editResourceWin.setInitialWidth(WIN_WIDTH);
+        editResourceWin.setCookieName("edit-res-modal");
+
+        editConnectorWin.setCssClassName(ModalWindow.CSS_CLASS_GRAY);
+        editConnectorWin.setInitialHeight(WIN_HEIGHT);
+        editConnectorWin.setInitialWidth(WIN_WIDTH);
+        editConnectorWin.setCookieName("edit-conn-modal");
+
+        AjaxLink<Void> createResourceLink
+                = new ClearIndicatingAjaxLink<Void>("createResourceLink", pageRef) {
+
+                    private static final long serialVersionUID = -7978723352517770644L;
+
+                    @Override
+                    protected void onClickInternal(final AjaxRequestTarget target) {
+                        createResourceWin.setPageCreator(new ModalWindow.PageCreator() {
+
+                            private static final long serialVersionUID = -7834632442532690940L;
+
+                            @Override
+                            public Page createPage() {
+                                final ResourceModalPage windows = new ResourceModalPage(Resources.this.pageRef,
+                                        editResourceWin, new ResourceTO(), true);
+                                return windows;
+                            }
+                        });
+
+                        createResourceWin.show(target);
+                    }
+                };
+
+        MetaDataRoleAuthorizationStrategy.authorize(createResourceLink, ENABLE, Entitlement.RESOURCE_CREATE);
+
+        add(createResourceLink);
+
+        @SuppressWarnings("rawtypes")
+        final Form paginatorForm = new Form("resourcePaginatorForm");
+
+        @SuppressWarnings({ "unchecked", "rawtypes" })
+        final DropDownChoice rowsChooser = new DropDownChoice("rowsChooser", new PropertyModel(this,
+                "resourcePaginatorRows"), prefMan.getPaginatorChoices());
+
+        rowsChooser.add(new AjaxFormComponentUpdatingBehavior(Constants.ON_CHANGE) {
+
+            private static final long serialVersionUID = -1107858522700306810L;
+
+            @Override
+            protected void onUpdate(final AjaxRequestTarget target) {
+                prefMan.set(getRequest(), getResponse(), Constants.PREF_RESOURCES_PAGINATOR_ROWS,
+                        String.valueOf(resourcePaginatorRows));
+
+                table.setItemsPerPage(resourcePaginatorRows);
+                target.add(resourceContainer);
+            }
+        });
+
+        paginatorForm.add(rowsChooser);
+        add(paginatorForm);
+    }
+
+    class ResourcesProvider extends SortableDataProvider<ResourceTO, String> {
+
+        private static final long serialVersionUID = -9055916672926643975L;
+
+        private final SortableDataProviderComparator<ResourceTO> comparator;
+
+        public ResourcesProvider() {
+            super();
+            //Default sorting
+            setSort("key", SortOrder.ASCENDING);
+            comparator = new SortableDataProviderComparator<>(this);
+        }
+
+        @Override
+        public Iterator<ResourceTO> iterator(final long first, final long count) {
+            List<ResourceTO> list = resourceRestClient.getAll();
+
+            Collections.sort(list, comparator);
+
+            return list.subList((int) first, (int) first + (int) count).iterator();
+        }
+
+        @Override
+        public long size() {
+            return resourceRestClient.getAll().size();
+        }
+
+        @Override
+        public IModel<ResourceTO> model(final ResourceTO resource) {
+            return new AbstractReadOnlyModel<ResourceTO>() {
+
+                private static final long serialVersionUID = 8952474152465381634L;
+
+                @Override
+                public ResourceTO getObject() {
+                    return resource;
+                }
+            };
+        }
+    }
+
+    @Override
+    public void onEvent(final IEvent<?> event) {
+        if (event.getPayload() instanceof AbstractSearchResultPanel.EventDataWrapper) {
+            ((AbstractSearchResultPanel.EventDataWrapper) event.getPayload()).getTarget().add(resourceContainer);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/34ec6712/client/console/src/main/java/org/apache/syncope/client/console/rest/ConnectorRestClient.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/ConnectorRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/ConnectorRestClient.java
index a093dbd..d5c133b 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/rest/ConnectorRestClient.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/ConnectorRestClient.java
@@ -125,14 +125,14 @@ public class ConnectorRestClient extends BaseRestClient {
     }
 
     private Set<ConnConfProperty> filterProperties(final Set<ConnConfProperty> properties) {
-        Set<ConnConfProperty> newProperties = new HashSet<ConnConfProperty>();
+        Set<ConnConfProperty> newProperties = new HashSet<>();
 
         for (ConnConfProperty property : properties) {
             ConnConfProperty prop = new ConnConfProperty();
             prop.setSchema(property.getSchema());
             prop.setOverridable(property.isOverridable());
 
-            final List<Object> parsed = new ArrayList<Object>();
+            final List<Object> parsed = new ArrayList<>();
             if (property.getValues() != null) {
                 for (Object obj : property.getValues()) {
                     if (obj != null && !obj.toString().isEmpty()) {
@@ -179,7 +179,7 @@ public class ConnectorRestClient extends BaseRestClient {
     }
 
     public List<String> getSchemaNames(final ConnInstanceTO connectorTO) {
-        List<String> schemaNames = new ArrayList<String>();
+        List<String> schemaNames = new ArrayList<>();
         try {
             List<PlainSchemaTO> response = getService(ConnectorService.class).
                     getSchemaNames(connectorTO.getKey(), connectorTO, false);

http://git-wip-us.apache.org/repos/asf/syncope/blob/34ec6712/client/console/src/main/java/org/apache/syncope/client/console/rest/RealmRestClient.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/rest/RealmRestClient.java b/client/console/src/main/java/org/apache/syncope/client/console/rest/RealmRestClient.java
new file mode 100644
index 0000000..06cba4d
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/rest/RealmRestClient.java
@@ -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.
+ */
+package org.apache.syncope.client.console.rest;
+
+import java.util.List;
+
+import org.apache.syncope.common.lib.to.RealmTO;
+import org.apache.syncope.common.rest.api.service.RealmService;
+import org.springframework.stereotype.Component;
+
+/**
+ * Console client for invoking Rest Group's services.
+ */
+@Component
+public class RealmRestClient extends BaseRestClient {
+
+    private static final long serialVersionUID = -8549081557283519638L;
+
+    public List<RealmTO> list() {
+        return getService(RealmService.class).list();
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/34ec6712/client/console/src/main/java/org/apache/syncope/client/console/topology/Topology.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/topology/Topology.java b/client/console/src/main/java/org/apache/syncope/client/console/topology/Topology.java
new file mode 100644
index 0000000..0b49f12
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/topology/Topology.java
@@ -0,0 +1,521 @@
+/*
+ * 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 static org.apache.syncope.client.console.topology.TopologyNode.Status.FAILURE;
+import static org.apache.syncope.client.console.topology.TopologyNode.Status.REACHABLE;
+import static org.apache.syncope.client.console.topology.TopologyNode.Status.UNKNOWN;
+import static org.apache.syncope.client.console.topology.TopologyNode.Status.UNREACHABLE;
+
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.syncope.client.console.SyncopeConsoleSession;
+import org.apache.syncope.client.console.pages.BasePage;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionLink;
+import org.apache.syncope.client.console.wicket.markup.html.form.ActionLinksPanel;
+import org.apache.syncope.common.lib.to.ConnInstanceTO;
+import org.apache.syncope.common.lib.to.ResourceTO;
+import org.apache.syncope.common.lib.types.Entitlement;
+import org.apache.syncope.common.rest.api.service.SyncopeService;
+import org.apache.wicket.Component;
+import org.apache.wicket.ajax.AbstractAjaxTimerBehavior;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.behavior.Behavior;
+import org.apache.wicket.markup.head.IHeaderResponse;
+import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
+import org.apache.wicket.markup.html.WebMarkupContainer;
+import org.apache.wicket.markup.html.list.ListItem;
+import org.apache.wicket.markup.html.list.ListView;
+import org.apache.wicket.markup.html.panel.Panel;
+import org.apache.wicket.model.LoadableDetachableModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.util.time.Duration;
+
+public class Topology extends BasePage {
+
+    private static final long serialVersionUID = -1100228004207271272L;
+
+    private static final String CONNECTOR_SERVER_LOCATION_PREFIX = "connid://";
+
+    private final int origX = 3100;
+
+    private final int origY = 2800;
+
+    private final LoadableDetachableModel<List<ResourceTO>> resModel
+            = new LoadableDetachableModel<List<ResourceTO>>() {
+
+                private static final long serialVersionUID = 5275935387613157431L;
+
+                @Override
+                protected List<ResourceTO> load() {
+                    final List<ResourceTO> result = resourceRestClient.getAll();
+                    return result;
+                }
+            };
+
+    private final LoadableDetachableModel<Pair<List<ConnInstanceTO>, List<ConnInstanceTO>>> connModel
+            = new LoadableDetachableModel<Pair<List<ConnInstanceTO>, List<ConnInstanceTO>>>() {
+
+                private static final long serialVersionUID = 5275935387613157432L;
+
+                @Override
+                protected Pair<List<ConnInstanceTO>, List<ConnInstanceTO>> load() {
+                    final List<ConnInstanceTO> level1 = new ArrayList<>();
+                    final List<ConnInstanceTO> level2 = new ArrayList<>();
+
+                    for (ConnInstanceTO conn : connectorRestClient.getAllConnectors()) {
+                        if (conn.getLocation().startsWith(CONNECTOR_SERVER_LOCATION_PREFIX)) {
+                            level2.add(conn);
+                        } else {
+                            level1.add(conn);
+                        }
+                    }
+
+                    return Pair.of(level1, level2);
+                }
+            };
+
+    private final LoadableDetachableModel<List<URI>> csModel = new LoadableDetachableModel<List<URI>>() {
+
+        private static final long serialVersionUID = 5275935387613157433L;
+
+        @Override
+        protected List<URI> load() {
+            final List<URI> locations = new ArrayList<>();
+
+            for (String location : SyncopeConsoleSession.get().getSyncopeTO().getConnIdLocations()) {
+                if (location.startsWith(CONNECTOR_SERVER_LOCATION_PREFIX)) {
+                    locations.add(URI.create(location));
+                }
+            }
+
+            return locations;
+        }
+    };
+
+    public Topology() {
+        // -----------------------------------------
+        // Add Zoom panel
+        // -----------------------------------------
+        final ActionLinksPanel zoomActionPanel = new ActionLinksPanel("zoom", new Model<String>(), getPageReference());
+        add(zoomActionPanel);
+
+        zoomActionPanel.add(new ActionLink() {
+
+            private static final long serialVersionUID = -3722207913631435501L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target) {
+                target.appendJavaScript("zoomIn($('#drawing')[0]);");
+            }
+
+        }, ActionLink.ActionType.ZOOM_IN, Entitlement.RESOURCE_LIST).add(new ActionLink() {
+
+            private static final long serialVersionUID = -3722207913631435501L;
+
+            @Override
+            public void onClick(final AjaxRequestTarget target) {
+                target.appendJavaScript("zoomOut($('#drawing')[0]);");
+            }
+        }, ActionLink.ActionType.ZOOM_OUT, Entitlement.RESOURCE_LIST);
+        // -----------------------------------------
+
+        final WebMarkupContainer jsPlace = new WebMarkupContainer("jsPlace");
+
+        // -----------------------------------------
+        // Add Syncope (root topologynode)
+        // -----------------------------------------
+        final TopologyNode syncopeTopologyNode = new TopologyNode("Syncope", "Syncope", TopologyNode.Kind.SYNCOPE);
+        syncopeTopologyNode.setX(origX);
+        syncopeTopologyNode.setY(origY);
+
+        final URI uri = WebClient.client(SyncopeConsoleSession.get().getService(SyncopeService.class)).getBaseURI();
+        syncopeTopologyNode.setHost(uri.getHost());
+        syncopeTopologyNode.setPort(uri.getPort());
+
+        add(topologyNodePanel("syncope", syncopeTopologyNode, null, false));
+
+        final Map<String, Map<String, TopologyNode>> connections = new HashMap<>();
+        final Map<String, TopologyNode> syncopeConnections = new HashMap<>();
+        connections.put(syncopeTopologyNode.getDisplayName(), syncopeConnections);
+
+        // required to retrieve parent positions
+        final Map<String, TopologyNode> servers = new HashMap<>();
+        final Map<String, TopologyNode> connectors = new HashMap<>();
+            // -----------------------------------------
+
+        // -----------------------------------------
+        // Add Connector Servers
+        // -----------------------------------------
+        final ListView<URI> connectorServers = new ListView<URI>("connectorServers", csModel) {
+
+            private static final long serialVersionUID = 6978621871488360380L;
+
+            private final int size = csModel.getObject().size() + 1;
+
+            @Override
+            protected void populateItem(final ListItem<URI> item) {
+                int kx = size >= 4 ? 800 : (200 * size);
+
+                int x = (int) Math.round(origX + kx * Math.cos(Math.PI + Math.PI * (item.getIndex() + 1) / size));
+                int y = (int) Math.round(origY + 100 * Math.sin(Math.PI + Math.PI * (item.getIndex() + 1) / size));
+
+                final URI location = item.getModelObject();
+                final String url = location.toASCIIString();
+
+                final TopologyNode topologynode = new TopologyNode(url, url, TopologyNode.Kind.CONNECTOR_SERVER);
+
+                topologynode.setHost(location.getHost());
+                topologynode.setPort(location.getPort());
+                topologynode.setX(x);
+                topologynode.setY(y);
+
+                servers.put(topologynode.getDisplayName(), topologynode);
+
+                item.add(topologyNodePanel("cs", topologynode, syncopeTopologyNode, false));
+
+                syncopeConnections.put(url, topologynode);
+                connections.put(url, new HashMap<String, TopologyNode>());
+            }
+        };
+
+        connectorServers.setOutputMarkupId(true);
+        add(connectorServers);
+            // -----------------------------------------
+
+        // -----------------------------------------
+        // Add Connector Intances (first level)
+        // -----------------------------------------
+        final ListView<ConnInstanceTO> conn1
+                = new ListView<ConnInstanceTO>("conn1", connModel.getObject().getLeft()) {
+
+                    private static final long serialVersionUID = 6978621871488360381L;
+
+                    private final int size = connModel.getObject().getLeft().size() + 1;
+
+                    @Override
+                    protected void populateItem(final ListItem<ConnInstanceTO> item) {
+                        int kx = size >= 6 ? 800 : (130 * size);
+
+                        int x = (int) Math.round(origX + kx * Math.cos(Math.PI * (item.getIndex() + 1) / size));
+                        int y = (int) Math.round(origY + 100 * Math.sin(Math.PI * (item.getIndex() + 1) / size));
+
+                        final ConnInstanceTO conn = item.getModelObject();
+                        final TopologyNode topologynode = new TopologyNode(
+                                Long.valueOf(conn.getKey()), conn.getDisplayName(), TopologyNode.Kind.CONNECTOR);
+                        topologynode.setConnectinDisplayName(conn.getBundleName());
+                        topologynode.setX(x);
+                        topologynode.setY(y);
+
+                        connectors.put(topologynode.getDisplayName(), topologynode);
+
+                        item.add(topologyNodePanel("conn", topologynode, syncopeTopologyNode, true));
+
+                        if (conn.getLocation().startsWith(CONNECTOR_SERVER_LOCATION_PREFIX)) {
+                            final Map<String, TopologyNode> remoteConnections;
+
+                            if (connections.containsKey(conn.getLocation())) {
+                                remoteConnections = connections.get(conn.getLocation());
+                            } else {
+                                remoteConnections = new HashMap<>();
+                                connections.put(conn.getLocation(), remoteConnections);
+                            }
+                            remoteConnections.put(conn.getDisplayName(), topologynode);
+                        } else {
+                            syncopeConnections.put(conn.getDisplayName(), topologynode);
+                        }
+                    }
+                };
+
+        conn1.setOutputMarkupId(true);
+        add(conn1);
+            // -----------------------------------------
+
+        // -----------------------------------------
+        // Add Connector Intances (second level)
+        // -----------------------------------------
+        final ListView<ConnInstanceTO> conn2
+                = new ListView<ConnInstanceTO>("conn2", connModel.getObject().getRight()) {
+
+                    private static final long serialVersionUID = 6978621871488360381L;
+
+                    private final int size = connModel.getObject().getRight().size() + 1;
+
+                    @Override
+                    protected void populateItem(final ListItem<ConnInstanceTO> item) {
+                        final ConnInstanceTO conn = item.getModelObject();
+
+                        final TopologyNode parent = servers.get(conn.getLocation());
+
+                        int kx = size >= 6 ? 800 : (130 * size);
+
+                        int x = (int) Math.round((parent == null ? origX : parent.getX())
+                                + kx * Math.cos(Math.PI + Math.PI * (item.getIndex() + 1) / size));
+                        int y = (int) Math.round((parent == null ? origY : parent.getY())
+                                + 100 * Math.sin(Math.PI + Math.PI * (item.getIndex() + 1) / size));
+
+                        final TopologyNode topologynode = new TopologyNode(
+                                Long.valueOf(conn.getKey()), conn.getDisplayName(), TopologyNode.Kind.CONNECTOR);
+                        topologynode.setConnectinDisplayName(conn.getBundleName());
+                        topologynode.setX(x);
+                        topologynode.setY(y);
+
+                        connectors.put(topologynode.getDisplayName(), topologynode);
+
+                        item.add(topologyNodePanel("conn", topologynode, parent, true));
+
+                        if (conn.getLocation().startsWith(CONNECTOR_SERVER_LOCATION_PREFIX)) {
+                            final Map<String, TopologyNode> remoteConnections;
+
+                            if (connections.containsKey(conn.getLocation())) {
+                                remoteConnections = connections.get(conn.getLocation());
+                            } else {
+                                remoteConnections = new HashMap<>();
+                                connections.put(conn.getLocation(), remoteConnections);
+                            }
+                            remoteConnections.put(conn.getDisplayName(), topologynode);
+                        } else {
+                            syncopeConnections.put(conn.getDisplayName(), topologynode);
+                        }
+                    }
+                };
+
+        conn2.setOutputMarkupId(true);
+        add(conn2);
+            // -----------------------------------------
+
+        // -----------------------------------------
+        // Add Resources
+        // -----------------------------------------
+        final List<String> connToBeProcessed = new ArrayList<>();
+        for (ResourceTO resourceTO : resModel.getObject()) {
+            final TopologyNode topologynode = new TopologyNode(
+                    resourceTO.getKey(), resourceTO.getKey(), TopologyNode.Kind.RESOURCE);
+            topologynode.setX(origX);
+            topologynode.setY(origY);
+
+            final Map<String, TopologyNode> remoteConnections;
+
+            if (connections.containsKey(resourceTO.getConnectorDisplayName())) {
+                remoteConnections = connections.get(resourceTO.getConnectorDisplayName());
+            } else {
+                remoteConnections = new HashMap<>();
+                connections.put(resourceTO.getConnectorDisplayName(), remoteConnections);
+            }
+
+            remoteConnections.put(topologynode.getDisplayName(), topologynode);
+
+            if (!connToBeProcessed.contains(resourceTO.getConnectorDisplayName())) {
+                connToBeProcessed.add(resourceTO.getConnectorDisplayName());
+            }
+        }
+
+        final ListView<String> resources = new ListView<String>("resources", connToBeProcessed) {
+
+            private static final long serialVersionUID = 697862187148836038L;
+
+            @Override
+            protected void populateItem(final ListItem<String> item) {
+                final String connectorDisplayName = item.getModelObject();
+
+                final ListView<TopologyNode> innerListView = new ListView<TopologyNode>("resources",
+                        new ArrayList<>(connections.get(connectorDisplayName).values())) {
+
+                            private static final long serialVersionUID = 1L;
+
+                            private final int size = getModelObject().size() + 1;
+
+                            @Override
+                            protected void populateItem(final ListItem<TopologyNode> item) {
+                                final TopologyNode topologynode = item.getModelObject();
+                                final TopologyNode parent = connectors.get(connectorDisplayName);
+
+                                final double k;
+
+                                if (parent == null || parent.getY() < syncopeTopologyNode.getY()) {
+                                    k = Math.PI;
+                                } else {
+                                    k = 0.0;
+                                }
+
+                                int kx = size >= 16 ? 800 : (48 * size);
+                                int ky = size < 4 ? 100 : size < 6 ? 350 : 750;
+
+                                int x = (int) Math.round((parent == null ? origX : parent.getX())
+                                        + kx * Math.cos(k + Math.PI * (item.getIndex() + 1) / size));
+                                int y = (int) Math.round((parent == null ? origY : parent.getY())
+                                        + ky * Math.sin(k + Math.PI * (item.getIndex() + 1) / size));
+
+                                topologynode.setX(x);
+                                topologynode.setY(y);
+
+                                final Panel panel = new TopologyNodePanel(
+                                        "res", topologynode, getPageReference(), resourceRestClient);
+                                panel.setMarkupId(topologynode.getDisplayName());
+                                panel.setOutputMarkupId(true);
+                                item.add(topologyNodePanel("res", topologynode, parent, true));
+                            }
+                        };
+
+                innerListView.setOutputMarkupId(true);
+                item.add(innerListView);
+            }
+        };
+
+        resources.setOutputMarkupId(true);
+        add(resources);
+            // -----------------------------------------
+
+        // -----------------------------------------
+        // Create connections
+        // -----------------------------------------
+        jsPlace.add(new Behavior() {
+
+            private static final long serialVersionUID = 2661717818979056044L;
+
+            @Override
+            public void renderHead(final Component component, final IHeaderResponse response) {
+                final StringBuilder jsPlumbConf = new StringBuilder();
+                jsPlumbConf.append(String.format(Locale.US, "activate(%.2f);", 0.68f));
+
+                for (String str : createConnections(connections)) {
+                    jsPlumbConf.append(str);
+                }
+
+                response.render(OnDomReadyHeaderItem.forScript(jsPlumbConf.toString()));
+            }
+        });
+
+        jsPlace.setOutputMarkupId(true);
+        add(jsPlace);
+        // -----------------------------------------
+    }
+
+    @Override
+    public String getAjaxIndicatorMarkupId() {
+        return StringUtils.EMPTY;
+    }
+
+    private List<String> createConnections(final Map<String, Map<String, TopologyNode>> targets) {
+        List<String> list = new ArrayList<>();
+
+        for (Map.Entry<String, Map<String, TopologyNode>> source : targets.entrySet()) {
+            for (Map.Entry<String, TopologyNode> target : source.getValue().entrySet()) {
+                list.add(String.format("jsPlumb.connect({source:'%s',target:'%s'},def);",
+                        source.getKey(),
+                        target.getKey()));
+            }
+        }
+        return list;
+    }
+
+    private Panel topologyNodePanel(
+            final String id, final TopologyNode node, final TopologyNode parent, final boolean active) {
+        final Panel panel = new TopologyNodePanel(id, node, getPageReference(), resourceRestClient);
+        panel.setMarkupId(node.getDisplayName());
+        panel.setOutputMarkupId(true);
+
+        panel.add(new Behavior() {
+
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            public void renderHead(final Component component, final IHeaderResponse response) {
+                response.render(OnDomReadyHeaderItem.forScript(String.format("setPosition('%s', %d, %d)",
+                        node.getDisplayName(), node.getX(), node.getY())));
+            }
+        });
+
+        final WebMarkupContainer timer = new WebMarkupContainer("timer");
+        timer.setOutputMarkupId(true);
+        panel.add(timer);
+
+        if (active) {
+            timer.add(new AbstractAjaxTimerBehavior(Duration.seconds(2)) {
+
+                private static final long serialVersionUID = 1L;
+
+                @Override
+                protected void onTimer(final AjaxRequestTarget target) {
+                    final List<TopologyReloadBehavior> behaviors = panel.getBehaviors(TopologyReloadBehavior.class);
+                    for (TopologyReloadBehavior behavior : behaviors) {
+                        panel.remove(behavior);
+                    }
+
+                    TopologyNode.Status status;
+
+                    final FutureTask<TopologyNode.Status> future = new FutureTask<>(
+                            new Callable<TopologyNode.Status>() {
+
+                                @Override
+                                public TopologyNode.Status call() throws Exception {
+                                    switch (node.getKind()) {
+                                        case CONNECTOR:
+                                            final ConnInstanceTO connector = connectorRestClient.
+                                            read(Long.class.cast(node.getKey()));
+                                            return connectorRestClient.check(connector) ? REACHABLE : UNREACHABLE;
+                                        case RESOURCE:
+                                            final ResourceTO resource = resourceRestClient.
+                                            read(String.class.cast(node.getKey()));
+                                            return connectorRestClient.check(resource) ? REACHABLE : UNREACHABLE;
+                                        default:
+                                            return UNKNOWN;
+                                    }
+                                }
+                            });
+
+                    future.run();
+
+                    try {
+                        status = future.get(10, TimeUnit.SECONDS);
+                    } catch (TimeoutException te) {
+                        LOG.warn("Check connection timed out");
+                        status = UNKNOWN;
+                    } catch (InterruptedException | ExecutionException ie) {
+                        // ignore
+                        LOG.warn("Check connection failed", ie);
+                        status = FAILURE;
+                    }
+
+                    timer.add(new TopologyReloadBehavior(parent.getDisplayName(), node.getDisplayName(), status));
+
+                    if (getUpdateInterval().seconds() < 60.0) {
+                        setUpdateInterval(Duration.seconds(60));
+                    }
+
+                    target.add(timer);
+                }
+            });
+        }
+
+        return panel;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/34ec6712/client/console/src/main/java/org/apache/syncope/client/console/topology/TopologyNode.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/topology/TopologyNode.java b/client/console/src/main/java/org/apache/syncope/client/console/topology/TopologyNode.java
new file mode 100644
index 0000000..f7c8aaa
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/topology/TopologyNode.java
@@ -0,0 +1,123 @@
+/*
+ * 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 java.io.Serializable;
+
+public class TopologyNode implements Serializable {
+
+    public enum Kind {
+
+        RESOURCE,
+        CONNECTOR,
+        CONNECTOR_SERVER,
+        SYNCOPE
+
+    }
+
+    public enum Status {
+
+        UNKNOWN,
+        REACHABLE,
+        UNREACHABLE,
+        FAILURE
+
+    }
+
+    private static final long serialVersionUID = -1506421230369224142L;
+
+    private final Serializable key;
+
+    private final String displayName;
+
+    private String connectionDisplayName;
+
+    private Kind kind;
+
+    private String host;
+
+    private int port;
+
+    private int x;
+
+    private int y;
+
+    public TopologyNode(final Serializable key, final String displayName, final Kind kind) {
+        this.key = key;
+        this.displayName = displayName;
+        this.kind = kind;
+    }
+
+    public Serializable getKey() {
+        return key;
+    }
+
+    public String getDisplayName() {
+        return displayName;
+    }
+
+    public String getConnectionDisplayName() {
+        return connectionDisplayName;
+    }
+
+    public void setConnectinDisplayName(final String connectinDisplayName) {
+        this.connectionDisplayName = connectinDisplayName;
+    }
+
+    public Kind getKind() {
+        return kind;
+    }
+
+    public void setKind(final Kind kind) {
+        this.kind = kind;
+    }
+
+    public int getX() {
+        return x;
+    }
+
+    public void setX(final int x) {
+        this.x = x;
+    }
+
+    public int getY() {
+        return y;
+    }
+
+    public void setY(final int y) {
+        this.y = y;
+    }
+
+    public String getHost() {
+        return host;
+    }
+
+    public void setHost(final String host) {
+        this.host = host;
+    }
+
+    public int getPort() {
+        return port;
+    }
+
+    public void setPort(final int port) {
+        this.port = port;
+    }
+
+}