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>