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/11/27 16:17:58 UTC

syncope git commit: [SYNCOPE-156] changed AjaxPalette in order to support filtering

Repository: syncope
Updated Branches:
  refs/heads/master 992f6002f -> 9840cd0d9


[SYNCOPE-156] changed AjaxPalette in order to support filtering


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

Branch: refs/heads/master
Commit: 9840cd0d93f1138167d8ffc3c2bc66ac0e7585fc
Parents: 992f600
Author: fmartelli <fa...@gmail.com>
Authored: Fri Nov 27 16:17:24 2015 +0100
Committer: fmartelli <fa...@gmail.com>
Committed: Fri Nov 27 16:17:24 2015 +0100

----------------------------------------------------------------------
 .../client/console/commons/Constants.java       |   2 +
 .../markup/html/form/AjaxPaletteConf.java       |  35 +++
 .../markup/html/form/AjaxPalettePanel.java      | 215 ++++++++++++++++---
 .../client/console/wizards/any/AuxClasses.java  | 120 ++++++-----
 .../console/wizards/any/UserWizardBuilder.java  |   2 +-
 .../META-INF/resources/css/syncopeConsole.css   |   2 +-
 .../markup/html/form/AjaxPalettePanel.html      |   8 +
 7 files changed, 302 insertions(+), 82 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/syncope/blob/9840cd0d/client/console/src/main/java/org/apache/syncope/client/console/commons/Constants.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/Constants.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/Constants.java
index 9d0043c..473e348 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/commons/Constants.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/Constants.java
@@ -24,6 +24,8 @@ public final class Constants {
 
     public static final String ON_CHANGE = "onchange";
 
+    public static final String ON_KEYUP = "onkeyup";
+
     public static final String ON_BLUR = "onblur";
 
     public static final String PNG_EXT = ".png";

http://git-wip-us.apache.org/repos/asf/syncope/blob/9840cd0d/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPaletteConf.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPaletteConf.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPaletteConf.java
new file mode 100644
index 0000000..e18f788
--- /dev/null
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPaletteConf.java
@@ -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.
+ */
+package org.apache.syncope.client.console.wicket.markup.html.form;
+
+import java.io.Serializable;
+
+/**
+ * To be overridden in order to change the default filter option.
+ */
+public class AjaxPaletteConf implements Serializable {
+
+    private static final long serialVersionUID = -1;
+
+    private static final String DEFAULT_FILTER = "*";
+
+    public String getDefaultFilter() {
+        return DEFAULT_FILTER;
+    }
+}

http://git-wip-us.apache.org/repos/asf/syncope/blob/9840cd0d/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPalettePanel.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPalettePanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPalettePanel.java
index 33b04da..76f6285 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPalettePanel.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPalettePanel.java
@@ -19,54 +19,113 @@
 package org.apache.syncope.client.console.wicket.markup.html.form;
 
 import java.io.Serializable;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
+import java.util.regex.Pattern;
+import org.apache.commons.collections4.ListUtils;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.wicket.Component;
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink;
 import org.apache.wicket.extensions.markup.html.form.palette.Palette;
 import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.form.Form;
 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;
 import org.apache.wicket.model.ResourceModel;
 import org.apache.wicket.model.util.ListModel;
+import org.apache.wicket.util.string.Strings;
 
-public class AjaxPalettePanel<T> extends AbstractFieldPanel<List<T>> {
+public class AjaxPalettePanel<T extends Serializable> extends AbstractFieldPanel<List<T>> {
 
     private static final long serialVersionUID = 7738499668258805567L;
 
-    protected final Palette<T> palette;
+    protected Palette<T> palette;
 
-    public AjaxPalettePanel(final String id,
-            final IModel<List<T>> model, final ListModel<T> choices,
-            final IChoiceRenderer<T> renderer, final boolean allowOrder,
-            final boolean allowMoveAll, final String availableLabel, final String selectedLabel) {
+    private final Model<String> queryFilter = new Model<String>(StringUtils.EMPTY);
 
+    private final List<T> availableBefore = new ArrayList<T>();
+
+    private final LoadableDetachableModel<List<T>> choicesModel;
+
+    public AjaxPalettePanel(
+            final String id, final IModel<List<T>> model, final Builder.Query<T> choices, final Builder<T> builder) {
         super(id, id, model);
 
-        this.palette = createPalette(model, choices, renderer, allowOrder, allowMoveAll, availableLabel, selectedLabel);
-        add(palette.setOutputMarkupId(true));
-        setOutputMarkupId(true);
+        choicesModel = new PaletteLoadableDetachableModel(builder) {
+
+            private static final long serialVersionUID = 1L;
+
+            @Override
+            protected List<T> getChoices() {
+                return choices.execute(getFilter());
+            }
+        };
+        initialize(model, builder);
     }
 
-    protected final Palette<T> createPalette(
-            final IModel<List<T>> model, final ListModel<T> choices,
-            final IChoiceRenderer<T> renderer,
-            final boolean allowOrder, final boolean allowMoveAll,
-            final String availableLabel, final String selectedLabel) {
+    public AjaxPalettePanel(
+            final String id, final IModel<List<T>> model, final ListModel<T> choices, final Builder<T> builder) {
+        super(id, id, model);
 
-        return new NonI18nPalette<T>("paletteField", model, choices, renderer, 8, allowOrder, allowMoveAll) {
+        choicesModel = new PaletteLoadableDetachableModel(builder) {
 
-            private static final long serialVersionUID = -3074655279011678437L;
+            private static final long serialVersionUID = 1L;
 
             @Override
-            protected Component newAvailableHeader(final String componentId) {
-                return new Label(componentId, new ResourceModel("palette.available", availableLabel));
+            protected List<T> getChoices() {
+                return builder.filtered
+                        ? getFilteredList(choices.getObject(), getFilter().replaceAll("\\*", "\\.\\*"))
+                        : choices.getObject();
             }
+        };
+        initialize(model, builder);
+    }
+
+    private void initialize(final IModel<List<T>> model, final Builder<T> builder) {
+        setOutputMarkupId(true);
+
+        this.palette = new NonI18nPalette<T>(
+                "paletteField", model, choicesModel, builder.renderer, 8, builder.allowOrder, builder.allowMoveAll) {
+
+                    private static final long serialVersionUID = -3074655279011678437L;
+
+                    @Override
+                    protected Component newAvailableHeader(final String componentId) {
+                        return new Label(componentId, new ResourceModel("palette.available", builder.availableLabel));
+                    }
+
+                    @Override
+                    protected Component newSelectedHeader(final String componentId) {
+                        return new Label(componentId, new ResourceModel("palette.selected", builder.selectedLabel));
+                    }
+                };
+
+        add(palette.setOutputMarkupId(true));
+
+        final Form<?> form = new Form<>("form");
+        add(form.setEnabled(builder.filtered).setVisible(builder.filtered));
+
+        final AjaxTextFieldPanel filter = new AjaxTextFieldPanel("filter", "filter", queryFilter, false);
+        filter.hideLabel().setOutputMarkupId(true);
+        form.add(filter);
+
+        form.add(new AjaxSubmitLink("search") {
+
+            private static final long serialVersionUID = 1L;
 
             @Override
-            protected Component newSelectedHeader(final String componentId) {
-                return new Label(componentId, new ResourceModel("palette.selected", selectedLabel));
+            protected void onAfterSubmit(final AjaxRequestTarget target, final Form<?> form) {
+                super.onAfterSubmit(target, form);
+                target.add(palette);
             }
-        };
+        });
     }
 
     @Override
@@ -79,7 +138,9 @@ public class AjaxPalettePanel<T> extends AbstractFieldPanel<List<T>> {
         return palette.getModelCollection();
     }
 
-    public static class Builder<T extends Serializable> {
+    public static class Builder<T extends Serializable> implements Serializable {
+
+        private static final long serialVersionUID = 1L;
 
         private IChoiceRenderer<T> renderer;
 
@@ -91,9 +152,16 @@ public class AjaxPalettePanel<T> extends AbstractFieldPanel<List<T>> {
 
         private String availableLabel;
 
+        private boolean filtered;
+
+        private final AjaxPaletteConf conf = new AjaxPaletteConf();
+
+        private String filter = conf.getDefaultFilter();
+
         public Builder() {
             this.allowMoveAll = false;
             this.allowOrder = false;
+            this.filtered = false;
             this.renderer = new SelectChoiceRenderer<>();
         }
 
@@ -122,10 +190,105 @@ public class AjaxPalettePanel<T> extends AbstractFieldPanel<List<T>> {
             return this;
         }
 
-        public AjaxPalettePanel<T> build(
-                final String id, final IModel<List<T>> model, final ListModel<T> choices) {
-            return new AjaxPalettePanel<>(id, model,
-                    choices, renderer, allowOrder, allowMoveAll, availableLabel, selectedLabel);
+        public Builder<T> withFilter() {
+            this.filtered = true;
+            return this;
+        }
+
+        public Builder<T> withFilter(final String defaultFilter) {
+            this.filtered = true;
+            this.filter = defaultFilter;
+            return this;
+        }
+
+        public AjaxPalettePanel<T> build(final String id, final IModel<List<T>> model, final ListModel<T> choices) {
+            return new AjaxPalettePanel<>(id, model, choices, this);
+        }
+
+        public AjaxPalettePanel<T> build(final String id, final IModel<List<T>> model, final Query<T> choices) {
+            return new AjaxPalettePanel<>(id, model, choices, this);
+        }
+
+        public abstract static class Query<T extends Serializable> implements Serializable {
+
+            private static final long serialVersionUID = 1L;
+
+            public abstract List<T> execute(final String filter);
+        }
+    }
+
+    private abstract class PaletteLoadableDetachableModel extends LoadableDetachableModel<List<T>> {
+
+        private static final long serialVersionUID = 1L;
+
+        private final Builder<T> builder;
+
+        PaletteLoadableDetachableModel(final Builder<T> builder) {
+            super();
+            this.builder = builder;
+        }
+
+        protected abstract List<T> getChoices();
+
+        protected String getFilter() {
+            return StringUtils.isBlank(queryFilter.getObject()) ? builder.filter : queryFilter.getObject();
+        }
+
+        @Override
+        protected List<T> load() {
+            final List<T> selected = availableBefore.isEmpty()
+                    ? new ArrayList<>(palette.getModelCollection())
+                    : getSelectedList(availableBefore, palette.getRecorderComponent().getValue());
+
+            availableBefore.clear();
+            availableBefore.addAll(ListUtils.sum(selected, getChoices()));
+            return availableBefore;
+        }
+
+        private List<T> getSelectedList(final Collection<T> choices, final String selection) {
+            final IChoiceRenderer<? super T> renderer = palette.getChoiceRenderer();
+            final List<T> selected = new ArrayList<>();
+
+            final Map<T, String> idForChoice = new HashMap<>();
+            for (final T choice : choices) {
+                idForChoice.put(choice, renderer.getIdValue(choice, 0));
+            }
+
+            for (final String id : Strings.split(selection, ',')) {
+                final Iterator<T> iter = choices.iterator();
+                boolean found = false;
+                while (!found && iter.hasNext()) {
+                    final T choice = iter.next();
+                    final String idValue = idForChoice.get(choice);
+                    if (id.equals(idValue)) {
+                        selected.add(choice);
+                        found = true;
+                    }
+                }
+            }
+
+            return selected;
+        }
+
+        protected List<T> getFilteredList(final Collection<T> choices, final String filter) {
+            final IChoiceRenderer<? super T> renderer = palette.getChoiceRenderer();
+            final List<T> selected = new ArrayList<>(choices.size());
+
+            final Map<T, String> idForChoice = new HashMap<>();
+            for (final T choice : choices) {
+                idForChoice.put(choice, renderer.getIdValue(choice, 0));
+            }
+
+            final Pattern pattern = Pattern.compile(filter, Pattern.CASE_INSENSITIVE);
+
+            for (T choice : choices) {
+                final String idValue = idForChoice.get(choice);
+                if (pattern.matcher(idValue).matches()) {
+                    selected.add(choice);
+                }
+            }
+
+            return selected;
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/syncope/blob/9840cd0d/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AuxClasses.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AuxClasses.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AuxClasses.java
index 8c091a8..e90be42 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AuxClasses.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AuxClasses.java
@@ -22,13 +22,12 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.collections4.Transformer;
 import org.apache.syncope.client.console.rest.AnyTypeRestClient;
 import org.apache.syncope.client.console.rest.GroupRestClient;
 import org.apache.syncope.client.console.wicket.markup.html.form.AjaxPalettePanel;
+import org.apache.syncope.client.lib.SyncopeClient;
 import org.apache.syncope.common.lib.to.AnyObjectTO;
 import org.apache.syncope.common.lib.to.AnyTO;
 import org.apache.syncope.common.lib.to.AnyTypeClassTO;
@@ -37,7 +36,9 @@ import org.apache.syncope.common.lib.to.MembershipTO;
 import org.apache.syncope.common.lib.to.UserTO;
 import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
 import org.apache.wicket.extensions.wizard.WizardStep;
+import org.apache.wicket.markup.html.form.IChoiceRenderer;
 import org.apache.wicket.markup.html.panel.Fragment;
+import org.apache.wicket.model.IModel;
 import org.apache.wicket.model.PropertyModel;
 import org.apache.wicket.model.util.ListModel;
 
@@ -47,8 +48,6 @@ public class AuxClasses extends WizardStep {
 
     private final GroupRestClient groupRestClient = new GroupRestClient();
 
-    private static final Pattern GROUP_ID_PATTERN = Pattern.compile("\\[(\\d*)\\]? (.*)");
-
     public <T extends AnyTO> AuxClasses(final T entityTO, final String... anyTypeClass) {
         this.setOutputMarkupId(true);
 
@@ -58,16 +57,6 @@ public class AuxClasses extends WizardStep {
         } else {
             fragment = new Fragment("groups", "groupsFragment", this);
 
-            final ArrayList<String> available = CollectionUtils.collect(
-                    groupRestClient.list(entityTO.getRealm(), -1, -1, new SortParam<>("name", true), null),
-                    new Transformer<GroupTO, String>() {
-
-                        @Override
-                        public String transform(final GroupTO input) {
-                            return String.format("[%d] %s", input.getKey(), input.getName());
-                        }
-                    }, new ArrayList<String>());
-
             final List<MembershipTO> memberships;
             final List<Long> dyngroups;
 
@@ -82,56 +71,79 @@ public class AuxClasses extends WizardStep {
                 dyngroups = Collections.<Long>emptyList();
             }
 
-            fragment.add(new AjaxPalettePanel.Builder<String>().setAllowOrder(true).build(
-                    "groups", new ListModel<String>(CollectionUtils.collect(memberships,
-                                    new Transformer<MembershipTO, String>() {
-
-                                        @Override
-                                        public String transform(final MembershipTO input) {
-                                            return String.format("[%d] %s", input.getRightKey(), input.getGroupName());
-                                        }
-                                    }, new ArrayList<String>())) {
+            final AjaxPalettePanel.Builder<MembershipTO> builder
+                    = new AjaxPalettePanel.Builder<MembershipTO>().setRenderer(new IChoiceRenderer<MembershipTO>() {
 
                         private static final long serialVersionUID = 1L;
 
                         @Override
-                        public void setObject(final List<String> object) {
-                            super.setObject(object);
-                            memberships.clear();
-                            CollectionUtils.collect(getObject(), new Transformer<String, MembershipTO>() {
-
-                                @Override
-                                public MembershipTO transform(final String input) {
-                                    final Matcher m = GROUP_ID_PATTERN.matcher(input);
-                                    final String name;
-                                    final long key;
-                                    if (m.matches()) {
-                                        key = Long.parseLong(m.group(1));
-                                        name = m.group(2);
-                                    } else {
-                                        key = -1L;
-                                        name = input;
-                                    }
+                        public Object getDisplayValue(final MembershipTO object) {
+                            return object.getGroupName();
+                        }
 
-                                    return new MembershipTO.Builder().
-                                    left(entityTO.getType(), entityTO.getKey()).group(key, name).build();
-                                }
-                            }, memberships);
+                        @Override
+                        public String getIdValue(final MembershipTO object, final int index) {
+                            return object.getGroupName();
                         }
-                    },
-                    new ListModel<>(available)).setOutputMarkupId(true));
 
-            fragment.add(new AjaxPalettePanel.Builder<String>().setAllowOrder(true).build(
-                    "dyngroups", new ListModel<String>(CollectionUtils.collect(dyngroups,
-                                    new Transformer<Long, String>() {
+                        @Override
+                        public MembershipTO getObject(
+                                final String id, final IModel<? extends List<? extends MembershipTO>> choices) {
+                                    for (MembershipTO membershipTO : choices.getObject()) {
+                                        if (id.equalsIgnoreCase(membershipTO.getGroupName())) {
+                                            return membershipTO;
+                                        }
+                                    }
+                                    return null;
+                                }
+                    });
+
+            fragment.add(builder.setAllowOrder(true).withFilter().build(
+                    "groups", new ListModel<MembershipTO>(memberships),
+                    new AjaxPalettePanel.Builder.Query<MembershipTO>() {
+
+                        private static final long serialVersionUID = 1L;
+
+                        @Override
+                        public List<MembershipTO> execute(final String filter) {
+                            return CollectionUtils.collect(
+                                    groupRestClient.search(
+                                            entityTO.getRealm(),
+                                            SyncopeClient.getGroupSearchConditionBuilder().
+                                            isAssignable().and().is("name").equalTo(filter).query(),
+                                            -1, -1,
+                                            new SortParam<>("name", true),
+                                            null),
+                                    new Transformer<GroupTO, MembershipTO>() {
 
                                         @Override
-                                        public String transform(final Long input) {
-                                            final GroupTO groupTO = groupRestClient.read(input);
-                                            return String.format("[%d] %s", groupTO.getKey(), groupTO.getName());
+                                        public MembershipTO transform(final GroupTO input) {
+                                            final MembershipTO membershipTO = new MembershipTO();
+                                            membershipTO.setGroupName(input.getName());
+                                            membershipTO.setRightKey(input.getKey());
+                                            membershipTO.setRightType(input.getType());
+                                            membershipTO.setLeftKey(entityTO.getKey());
+                                            membershipTO.setLeftType(entityTO.getType());
+                                            return membershipTO;
                                         }
-                                    }, new ArrayList<String>())),
-                    new ListModel<>(available)).setEnabled(false).setOutputMarkupId(true));
+                                    }, new ArrayList<MembershipTO>());
+                        }
+                    }).setOutputMarkupId(true));
+
+            final ArrayList<String> dynamics = CollectionUtils.collect(dyngroups,
+                    new Transformer<Long, String>() {
+
+                        @Override
+                        public String transform(final Long input) {
+                            final GroupTO groupTO = groupRestClient.read(input);
+                            return String.format("[%d] %s", groupTO.getKey(), groupTO.getName());
+                        }
+                    }, new ArrayList<String>());
+
+            fragment.add(new AjaxPalettePanel.Builder<String>().setAllowOrder(true).build(
+                    "dyngroups",
+                    new ListModel<String>(dynamics),
+                    new ListModel<>(dynamics)).setEnabled(false).setOutputMarkupId(true));
         }
         add(fragment);
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/9840cd0d/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserWizardBuilder.java
----------------------------------------------------------------------
diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserWizardBuilder.java
index f18e6dd..56fd134 100644
--- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserWizardBuilder.java
+++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserWizardBuilder.java
@@ -59,7 +59,7 @@ public class UserWizardBuilder extends AnyWizardBuilder<UserTO> {
         if (modelObject.getKey() == 0) {
             actual = userRestClient.create(modelObject, StringUtils.isNotBlank(modelObject.getPassword()));
         } else {
-            final UserPatch patch = AnyOperations.diff(modelObject, getOriginalItem(), true);
+            final UserPatch patch = AnyOperations.diff(modelObject, getOriginalItem(), false);
             if (!statusModel.getObject().isEmpty()) {
                 patch.setPassword(StatusUtils.buildPasswordPatch(modelObject.getPassword(), statusModel.getObject()));
             }

http://git-wip-us.apache.org/repos/asf/syncope/blob/9840cd0d/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css b/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css
index e491bb5..ed2cda2 100644
--- a/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css
+++ b/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css
@@ -223,7 +223,7 @@ div.basepage-content{
 .wizard-buttons {
   padding: 10px 0px 5px 0px;
   position: fixed;
-  top: 590px;
+  top: 605px;
   right: 30px;
 }
 

http://git-wip-us.apache.org/repos/asf/syncope/blob/9840cd0d/client/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPalettePanel.html
----------------------------------------------------------------------
diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPalettePanel.html b/client/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPalettePanel.html
index dad3a36..17b6919 100644
--- a/client/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPalettePanel.html
+++ b/client/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPalettePanel.html
@@ -19,6 +19,14 @@ under the License.
 -->
 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org">
   <wicket:extend>
+    <form wicket:id="form">
+      <div class="form-group input-group">
+        <span wicket:id="filter">[FILTER]</span>
+        <span class="input-group-addon">
+          <a href="#" wicket:id="search"><i class="glyphicon glyphicon-search"></i></a>
+        </span>
+      </div>
+    </form>
     <span wicket:id="paletteField">[Palette]</span>
   </wicket:extend>
 </html>