From b0e164283fee6f993589cce849ba1fc7d294e89d Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Fri, 19 Oct 2012 22:47:33 -0400
Subject: [PATCH] New permissions UI for EditUser and EditTeam (issue 36)

---
 src/com/gitblit/models/RepositoryAccessPermission.java        |   52 +++++++
 src/com/gitblit/wicket/pages/EditUserPage.java                |   24 +-
 src/com/gitblit/wicket/panels/RepositoryPermissionsPanel.java |  157 ++++++++++++++++++++++
 src/com/gitblit/wicket/pages/EditTeamPage.html                |    2 
 src/com/gitblit/models/UserModel.java                         |   18 ++
 src/com/gitblit/wicket/pages/EditUserPage.html                |    9 +
 src/com/gitblit/wicket/GitBlitWebApp.properties               |   10 +
 src/com/gitblit/wicket/pages/EditTeamPage.java                |   25 +--
 src/com/gitblit/wicket/panels/RepositoryPermissionsPanel.html |   24 +++
 src/com/gitblit/wicket/pages/BasePage.java                    |   32 ++++
 src/com/gitblit/models/TeamModel.java                         |   16 ++
 resources/gitblit.css                                         |    9 +
 src/com/gitblit/Constants.java                                |    2 
 13 files changed, 348 insertions(+), 32 deletions(-)

diff --git a/resources/gitblit.css b/resources/gitblit.css
index 1d17dc8..4d7e3ab 100644
--- a/resources/gitblit.css
+++ b/resources/gitblit.css
@@ -180,6 +180,15 @@
 	vertical-align: middle;
 }
 
+div.odd {
+	
+}
+
+div.even {
+	background-color: whiteSmoke;
+	vertical-align: middle;
+}
+
 div.page_footer {
 	clear: both;
 	height: 17px;
diff --git a/src/com/gitblit/Constants.java b/src/com/gitblit/Constants.java
index ed48bd2..0e68355 100644
--- a/src/com/gitblit/Constants.java
+++ b/src/com/gitblit/Constants.java
@@ -320,6 +320,8 @@
 	public static enum AccessPermission {
 		NONE("N"), VIEW("V"), CLONE("R"), PUSH("RW"), CREATE("RWC"), DELETE("RWD"), REWIND("RW+");
 		
+		public static final AccessPermission [] NEWPERMISSIONS = { VIEW, CLONE, PUSH, CREATE, DELETE, REWIND };
+		
 		public static AccessPermission LEGACY = REWIND;
 		
 		public final String code;
diff --git a/src/com/gitblit/models/RepositoryAccessPermission.java b/src/com/gitblit/models/RepositoryAccessPermission.java
new file mode 100644
index 0000000..06f5c05
--- /dev/null
+++ b/src/com/gitblit/models/RepositoryAccessPermission.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2012 gitblit.com.
+ *
+ * Licensed 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 com.gitblit.models;
+
+import java.io.Serializable;
+
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Represents a Repository-AccessPermission tuple.
+ * 
+ * @author James Moger
+ */
+public class RepositoryAccessPermission implements Serializable, Comparable<RepositoryAccessPermission> {
+
+	private static final long serialVersionUID = 1L;
+
+	public String repository;
+	public AccessPermission permission;
+
+	public RepositoryAccessPermission() {
+	}
+	
+	public RepositoryAccessPermission(String repository, AccessPermission permission) {
+		this.repository = repository;
+		this.permission = permission;
+	}
+	
+	@Override
+	public int compareTo(RepositoryAccessPermission p) {
+		return StringUtils.compareRepositoryNames(repository, p.repository);
+	}
+	
+	@Override
+	public String toString() {
+		return permission.asRole(repository);
+	}
+}
\ No newline at end of file
diff --git a/src/com/gitblit/models/TeamModel.java b/src/com/gitblit/models/TeamModel.java
index 149c765..95e6ef4 100644
--- a/src/com/gitblit/models/TeamModel.java
+++ b/src/com/gitblit/models/TeamModel.java
@@ -18,6 +18,7 @@
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -85,6 +86,21 @@
 	public void removeRepository(String name) {
 		removeRepositoryPermission(name);
 	}
+
+	
+	/**
+	 * Returns a list of repository permissions for this team.
+	 * 
+	 * @return the team's list of permissions
+	 */
+	public List<RepositoryAccessPermission> getRepositoryPermissions() {
+		List<RepositoryAccessPermission> list = new ArrayList<RepositoryAccessPermission>();
+		for (Map.Entry<String, AccessPermission> entry : permissions.entrySet()) {
+			list.add(new RepositoryAccessPermission(entry.getKey(), entry.getValue()));
+		}
+		Collections.sort(list);
+		return list;
+	}
 	
 	/**
 	 * Returns true if the team has any type of specified access permission for
diff --git a/src/com/gitblit/models/UserModel.java b/src/com/gitblit/models/UserModel.java
index f14c1ae..38a7aae 100644
--- a/src/com/gitblit/models/UserModel.java
+++ b/src/com/gitblit/models/UserModel.java
@@ -17,8 +17,11 @@
 
 import java.io.Serializable;
 import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -125,6 +128,21 @@
 	}
 	
 	/**
+	 * Returns a list of repository permissions for this user exclusive of
+	 * permissions inherited from team memberships.
+	 * 
+	 * @return the user's list of permissions
+	 */
+	public List<RepositoryAccessPermission> getRepositoryPermissions() {
+		List<RepositoryAccessPermission> list = new ArrayList<RepositoryAccessPermission>();
+		for (Map.Entry<String, AccessPermission> entry : permissions.entrySet()) {
+			list.add(new RepositoryAccessPermission(entry.getKey(), entry.getValue()));
+		}
+		Collections.sort(list);
+		return list;
+	}
+	
+	/**
 	 * Returns true if the user has any type of specified access permission for
 	 * this repository.
 	 * 
diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties
index f6d60dc..eb7d772 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -345,4 +345,12 @@
 gb.verifyCommitterDescription = require committer identity to match pushing Gitblt user account (all merges require "--no-ff" to enforce committer identity)
 gb.repositoryPermissions = repository permissions
 gb.userPermissions = user permissions
-gb.teamPermissions = team permissions
\ No newline at end of file
+gb.teamPermissions = team permissions
+gb.add = add
+gb.noPermission = NO ACCESS
+gb.viewPermission = {0} (view)
+gb.clonePermission = {0} (clone)
+gb.pushPermission = {0} (push)
+gb.createPermission = {0} (push, ref creation)
+gb.deletePermission = {0} (push, ref creation+deletion)
+gb.rewindPermission = {0} (push, ref creation+deletion+rewind)
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/BasePage.java b/src/com/gitblit/wicket/pages/BasePage.java
index 4d37611..48a872a 100644
--- a/src/com/gitblit/wicket/pages/BasePage.java
+++ b/src/com/gitblit/wicket/pages/BasePage.java
@@ -15,6 +15,7 @@
  */
 package com.gitblit.wicket.pages;
 
+import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Collections;
@@ -52,6 +53,7 @@
 import org.slf4j.LoggerFactory;
 
 import com.gitblit.Constants;
+import com.gitblit.Constants.AccessPermission;
 import com.gitblit.Constants.AccessRestrictionType;
 import com.gitblit.Constants.FederationStrategy;
 import com.gitblit.GitBlit;
@@ -203,6 +205,36 @@
 		return map;
 	}
 	
+	protected Map<AccessPermission, String> getAccessPermissions() {
+		Map<AccessPermission, String> map = new LinkedHashMap<AccessPermission, String>();
+		for (AccessPermission type : AccessPermission.values()) {
+			switch (type) {
+			case NONE:
+				map.put(type, MessageFormat.format(getString("gb.noPermission"), type.code));
+				break;
+			case VIEW:
+				map.put(type, MessageFormat.format(getString("gb.viewPermission"), type.code));
+				break;
+			case CLONE:
+				map.put(type, MessageFormat.format(getString("gb.clonePermission"), type.code));
+				break;
+			case PUSH:
+				map.put(type, MessageFormat.format(getString("gb.pushPermission"), type.code));
+				break;
+			case CREATE:
+				map.put(type, MessageFormat.format(getString("gb.createPermission"), type.code));
+				break;
+			case DELETE:
+				map.put(type, MessageFormat.format(getString("gb.deletePermission"), type.code));
+				break;
+			case REWIND:
+				map.put(type, MessageFormat.format(getString("gb.rewindPermission"), type.code));
+				break;
+			}
+		}
+		return map;
+	}
+	
 	protected Map<FederationStrategy, String> getFederationTypes() {
 		Map<FederationStrategy, String> map = new LinkedHashMap<FederationStrategy, String>();
 		for (FederationStrategy type : FederationStrategy.values()) {
diff --git a/src/com/gitblit/wicket/pages/EditTeamPage.html b/src/com/gitblit/wicket/pages/EditTeamPage.html
index 30d2a94..fb0819f 100644
--- a/src/com/gitblit/wicket/pages/EditTeamPage.html
+++ b/src/com/gitblit/wicket/pages/EditTeamPage.html
@@ -19,7 +19,7 @@
 				<tr><td colspan="2" style="padding-top:15px;"><h3><wicket:message key="gb.accessPermissions"></wicket:message> &nbsp;<small><wicket:message key="gb.accessPermissionsForTeamDescription"></wicket:message></small></h3></td></tr>	
 				<tr><th style="vertical-align: top;"><wicket:message key="gb.teamMembers"></wicket:message></th><td style="padding:2px;"><span wicket:id="users"></span></td></tr>
 				<tr><td colspan="2"><hr></hr></td></tr>
-				<tr><th style="vertical-align: top;"><wicket:message key="gb.restrictedRepositories"></wicket:message></th><td style="padding:2px;"><span wicket:id="repositories"></span></td></tr>
+				<tr><th style="vertical-align: top;"><wicket:message key="gb.repositoryPermissions"></wicket:message></th><td style="padding:2px;"><span wicket:id="repositories"></span></td></tr>
 				<tr><td colspan="2" style="padding-top:10px;"><h3><wicket:message key="gb.hookScripts"></wicket:message> &nbsp;<small><wicket:message key="gb.hookScriptsDescription"></wicket:message></small></h3></td></tr>	
 				<tr><th style="vertical-align: top;"><wicket:message key="gb.preReceiveScripts"></wicket:message><p></p><span wicket:id="inheritedPreReceive"></span></th><td style="padding:2px;"><span wicket:id="preReceiveScripts"></span></td></tr>
 				<tr><th style="vertical-align: top;"><wicket:message key="gb.postReceiveScripts"></wicket:message><p></p><span wicket:id="inheritedPostReceive"></span></th><td style="padding:2px;"><span wicket:id="postReceiveScripts"></span></td></tr>
diff --git a/src/com/gitblit/wicket/pages/EditTeamPage.java b/src/com/gitblit/wicket/pages/EditTeamPage.java
index 9cbccb5..05c9121 100644
--- a/src/com/gitblit/wicket/pages/EditTeamPage.java
+++ b/src/com/gitblit/wicket/pages/EditTeamPage.java
@@ -40,12 +40,14 @@
 import com.gitblit.GitBlit;
 import com.gitblit.GitBlitException;
 import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.RepositoryAccessPermission;
 import com.gitblit.models.TeamModel;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.RequiresAdminRole;
 import com.gitblit.wicket.StringChoiceRenderer;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.panels.BulletListPanel;
+import com.gitblit.wicket.panels.RepositoryPermissionsPanel;
 
 @RequiresAdminRole
 public class EditTeamPage extends RootSubPage {
@@ -59,6 +61,7 @@
 		super();
 		isCreate = true;
 		setupPage(new TeamModel(""));
+		setStatelessHint(false);
 	}
 
 	public EditTeamPage(PageParameters params) {
@@ -68,6 +71,7 @@
 		String name = WicketUtils.getTeamname(params);
 		TeamModel model = GitBlit.self().getTeamModel(name);
 		setupPage(model);
+		setStatelessHint(false);
 	}
 
 	protected void setupPage(final TeamModel teamModel) {
@@ -94,11 +98,7 @@
 		List<String> postReceiveScripts = new ArrayList<String>();
 
 		final String oldName = teamModel.name;
-
-		// repositories palette
-		final Palette<String> repositories = new Palette<String>("repositories",
-				new ListModel<String>(new ArrayList<String>(teamModel.repositories)),
-				new CollectionModel<String>(repos), new StringChoiceRenderer(), 10, false);
+		final List<RepositoryAccessPermission> permissions = teamModel.getRepositoryPermissions();
 
 		// users palette
 		final Palette<String> users = new Palette<String>("users", new ListModel<String>(
@@ -146,17 +146,10 @@
 						return;
 					}
 				}
-				Iterator<String> selectedRepositories = repositories.getSelectedChoices();
-				List<String> repos = new ArrayList<String>();
-				while (selectedRepositories.hasNext()) {
-					repos.add(selectedRepositories.next().toLowerCase());
+				// update team permissions
+				for (RepositoryAccessPermission repositoryPermission : permissions) {
+					teamModel.setRepositoryPermission(repositoryPermission.repository, repositoryPermission.permission);
 				}
-				if (repos.size() == 0) {
-					error(getString("gb.teamMustSpecifyRepository"));
-					return;
-				}
-				teamModel.repositories.clear();
-				teamModel.repositories.addAll(repos);
 
 				Iterator<String> selectedUsers = users.getSelectedChoices();
 				List<String> members = new ArrayList<String>();
@@ -231,7 +224,7 @@
 				: StringUtils.flattenStrings(teamModel.mailingLists, " "));
 		form.add(new TextField<String>("mailingLists", mailingLists));
 
-		form.add(repositories);
+		form.add(new RepositoryPermissionsPanel("repositories", permissions, getAccessPermissions()));
 		form.add(preReceivePalette);
 		form.add(new BulletListPanel("inheritedPreReceive", "inherited", GitBlit.self()
 				.getPreReceiveScriptsInherited(null)));
diff --git a/src/com/gitblit/wicket/pages/EditUserPage.html b/src/com/gitblit/wicket/pages/EditUserPage.html
index 9f178df..f993d46 100644
--- a/src/com/gitblit/wicket/pages/EditUserPage.html
+++ b/src/com/gitblit/wicket/pages/EditUserPage.html
@@ -23,11 +23,16 @@
 				<tr><td colspan="2" style="padding-top:15px;"><h3><wicket:message key="gb.accessPermissions"></wicket:message> &nbsp;<small><wicket:message key="gb.accessPermissionsForUserDescription"></wicket:message></small></h3></td></tr>	
 				<tr><th style="vertical-align: top;"><wicket:message key="gb.teamMemberships"></wicket:message></th><td style="padding:2px;"><span wicket:id="teams"></span></td></tr>
 				<tr><td colspan="2"><hr></hr></td></tr>
-				<tr><th style="vertical-align: top;"><wicket:message key="gb.restrictedRepositories"></wicket:message></th><td style="padding:2px;"><span wicket:id="repositories"></span></td></tr>
+				<tr><th style="vertical-align: top;"><wicket:message key="gb.repositoryPermissions"></wicket:message></th>
+					<td style="padding:2px;">
+						<div wicket:id="repositories"></div>
+					</td>
+				</tr>
 				<tr><td colspan='2'><div class="form-actions"><input class="btn btn-primary" type="submit" value="Save" wicket:message="value:gb.save" wicket:id="save" tabindex="9" /> &nbsp; <input class="btn" type="submit" value="Cancel" wicket:message="value:gb.cancel" wicket:id="cancel" tabindex="10" /></div></td></tr>
 			</tbody>
 		</table>
-	</form>	
+	</form>
 </body>
+
 </wicket:extend>
 </html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/EditUserPage.java b/src/com/gitblit/wicket/pages/EditUserPage.java
index 49515fb..6e35354 100644
--- a/src/com/gitblit/wicket/pages/EditUserPage.java
+++ b/src/com/gitblit/wicket/pages/EditUserPage.java
@@ -39,18 +39,20 @@
 import com.gitblit.GitBlitException;
 import com.gitblit.Keys;
 import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.RepositoryAccessPermission;
 import com.gitblit.models.TeamModel;
 import com.gitblit.models.UserModel;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.RequiresAdminRole;
 import com.gitblit.wicket.StringChoiceRenderer;
 import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.panels.RepositoryPermissionsPanel;
 
 @RequiresAdminRole
 public class EditUserPage extends RootSubPage {
 
 	private final boolean isCreate;
-
+	
 	public EditUserPage() {
 		// create constructor
 		super();
@@ -60,6 +62,7 @@
 		}
 		isCreate = true;
 		setupPage(new UserModel(""));
+		setStatelessHint(false);
 	}
 
 	public EditUserPage(PageParameters params) {
@@ -69,6 +72,7 @@
 		String name = WicketUtils.getUsername(params);
 		UserModel model = GitBlit.self().getUserModel(name);
 		setupPage(model);
+		setStatelessHint(false);
 	}
 
 	protected void setupPage(final UserModel userModel) {
@@ -96,16 +100,15 @@
 		Collections.sort(userTeams);
 		
 		final String oldName = userModel.username;
-		final Palette<String> repositories = new Palette<String>("repositories",
-				new ListModel<String>(new ArrayList<String>(userModel.repositories)),
-				new CollectionModel<String>(repos), new StringChoiceRenderer(), 10, false);
+		final List<RepositoryAccessPermission> permissions = userModel.getRepositoryPermissions();
+
 		final Palette<String> teams = new Palette<String>("teams", new ListModel<String>(
 				new ArrayList<String>(userTeams)), new CollectionModel<String>(GitBlit.self()
 				.getAllTeamnames()), new StringChoiceRenderer(), 10, false);
 		Form<UserModel> form = new Form<UserModel>("editForm", model) {
 
 			private static final long serialVersionUID = 1L;
-
+			
 			/*
 			 * (non-Javadoc)
 			 * 
@@ -167,13 +170,10 @@
 					}
 				}
 
-				Iterator<String> selectedRepositories = repositories.getSelectedChoices();
-				List<String> repos = new ArrayList<String>();
-				while (selectedRepositories.hasNext()) {
-					repos.add(selectedRepositories.next().toLowerCase());
+				// update user permissions
+				for (RepositoryAccessPermission repositoryPermission : permissions) {
+					userModel.setRepositoryPermission(repositoryPermission.repository, repositoryPermission.permission);
 				}
-				userModel.repositories.clear();
-				userModel.repositories.addAll(repos);
 
 				Iterator<String> selectedTeams = teams.getSelectedChoices();
 				userModel.teams.clear();
@@ -234,7 +234,7 @@
 		form.add(new CheckBox("canFork"));
 		form.add(new CheckBox("canCreate"));
 		form.add(new CheckBox("excludeFromFederation"));
-		form.add(repositories);
+		form.add(new RepositoryPermissionsPanel("repositories", permissions, getAccessPermissions()));
 		form.add(teams.setEnabled(editTeams));
 
 		form.add(new Button("save"));
diff --git a/src/com/gitblit/wicket/panels/RepositoryPermissionsPanel.html b/src/com/gitblit/wicket/panels/RepositoryPermissionsPanel.html
new file mode 100644
index 0000000..1c7e44e
--- /dev/null
+++ b/src/com/gitblit/wicket/panels/RepositoryPermissionsPanel.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml"  
+      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
+      xml:lang="en"  
+      lang="en"> 
+
+<body>
+<wicket:panel>
+
+	<div wicket:id="permissionRow">
+		<div style="padding-top:10px" class="row-fluid">
+			<span class="span8" wicket:id="repository"></span> <select class="input-medium" wicket:id="permission"></select>
+		</div>
+	</div>
+
+	<div style="padding-top:15px;" class="row-fluid">
+		<form class="well form-inline" wicket:id="addPermissionForm">
+			<select class="input-large" wicket:id="repository"></select> <select class="input-medium" wicket:id="permission"></select> <input class="btn btn-success" type="submit" value="Add" wicket:message="value:gb.add" wicket:id="addPermissionButton"/>
+		</form>
+	</div>	
+	
+</wicket:panel>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/panels/RepositoryPermissionsPanel.java b/src/com/gitblit/wicket/panels/RepositoryPermissionsPanel.java
new file mode 100644
index 0000000..3d967d3
--- /dev/null
+++ b/src/com/gitblit/wicket/panels/RepositoryPermissionsPanel.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2012 gitblit.com.
+ *
+ * Licensed 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 com.gitblit.wicket.panels;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.wicket.ajax.AjaxRequestTarget;
+import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior;
+import org.apache.wicket.ajax.markup.html.form.AjaxButton;
+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.form.IChoiceRenderer;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.OddEvenItem;
+import org.apache.wicket.markup.repeater.RefreshingView;
+import org.apache.wicket.markup.repeater.util.ModelIteratorAdapter;
+import org.apache.wicket.model.CompoundPropertyModel;
+import org.apache.wicket.model.IModel;
+
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.GitBlit;
+import com.gitblit.models.RepositoryAccessPermission;
+import com.gitblit.utils.DeepCopier;
+
+/**
+ * Allows user to manipulate repository access permissions.
+ * 
+ * @author James Moger
+ *
+ */
+public class RepositoryPermissionsPanel extends BasePanel {
+
+	private static final long serialVersionUID = 1L;
+
+	public RepositoryPermissionsPanel(String wicketId, final List<RepositoryAccessPermission> permissions, final Map<AccessPermission, String> translations) {
+		super(wicketId);
+		
+		// update existing permissions repeater
+		RefreshingView<RepositoryAccessPermission> dataView = new RefreshingView<RepositoryAccessPermission>("permissionRow") {
+			private static final long serialVersionUID = 1L;
+		
+			@Override
+            protected Iterator<IModel<RepositoryAccessPermission>> getItemModels() {
+                // the iterator returns RepositoryPermission objects, but we need it to
+                // return models
+                return new ModelIteratorAdapter<RepositoryAccessPermission>(permissions.iterator()) {
+                    @Override
+                    protected IModel<RepositoryAccessPermission> model(RepositoryAccessPermission permission) {
+                        return new CompoundPropertyModel<RepositoryAccessPermission>(permission);
+                    }
+                };
+            }
+
+            @Override
+            protected Item<RepositoryAccessPermission> newItem(String id, int index, IModel<RepositoryAccessPermission> model) {
+                // this item sets markup class attribute to either 'odd' or
+                // 'even' for decoration
+                return new OddEvenItem<RepositoryAccessPermission>(id, index, model);
+            }
+            
+			public void populateItem(final Item<RepositoryAccessPermission> item) {
+				final RepositoryAccessPermission entry = item.getModelObject();
+				item.add(new Label("repository", entry.repository));
+
+				// use ajax to get immediate update of permission level change
+				// otherwise we can lose it if they change levels and then add
+				// a new repository permission
+				final DropDownChoice<AccessPermission> permissionChoice = new DropDownChoice<AccessPermission>(
+						"permission", Arrays.asList(AccessPermission.values()), new AccessPermissionRenderer(translations));
+				permissionChoice.add(new AjaxFormComponentUpdatingBehavior("onchange") {
+		           
+					private static final long serialVersionUID = 1L;
+
+					protected void onUpdate(AjaxRequestTarget target) {
+		                target.addComponent(permissionChoice);
+		            }
+		        });
+
+				item.add(permissionChoice);
+			}
+		};
+		add(dataView);
+		setOutputMarkupId(true);
+
+		// filter out repositories we already have permissions for
+		final List<String> repositories = GitBlit.self().getRepositoryList();
+		for (RepositoryAccessPermission rp : permissions) {
+			repositories.remove(rp.repository);
+		}
+
+		// add new permission form
+		IModel<RepositoryAccessPermission> addPermissionModel = new CompoundPropertyModel<RepositoryAccessPermission>(new RepositoryAccessPermission());
+		Form<RepositoryAccessPermission> addPermissionForm = new Form<RepositoryAccessPermission>("addPermissionForm", addPermissionModel);
+		addPermissionForm.add(new DropDownChoice<String>("repository", repositories));
+		addPermissionForm.add(new DropDownChoice<AccessPermission>("permission", Arrays
+				.asList(AccessPermission.NEWPERMISSIONS), new AccessPermissionRenderer(translations)));
+		AjaxButton button = new AjaxButton("addPermissionButton", addPermissionForm) {
+
+			private static final long serialVersionUID = 1L;
+
+			@Override
+			protected void onSubmit(AjaxRequestTarget target, Form<?> form) {
+				// add permission to our list
+				RepositoryAccessPermission rp = (RepositoryAccessPermission) form.getModel().getObject();
+				permissions.add(DeepCopier.copy(rp));
+				
+				// remove repository from available choices
+				repositories.remove(rp.repository);
+				
+				// force the panel to refresh
+				target.addComponent(RepositoryPermissionsPanel.this);
+			}
+		};
+		addPermissionForm.add(button);
+		
+		// only show add permission form if we have a repository choice
+		add(addPermissionForm.setVisible(repositories.size() > 0));
+	}
+	
+	private class AccessPermissionRenderer implements IChoiceRenderer<AccessPermission> {
+
+		private static final long serialVersionUID = 1L;
+
+		private final Map<AccessPermission, String> map;
+
+		public AccessPermissionRenderer(Map<AccessPermission, String> map) {
+			this.map = map;
+		}
+
+		@Override
+		public String getDisplayValue(AccessPermission type) {
+			return map.get(type);
+		}
+
+		@Override
+		public String getIdValue(AccessPermission type, int index) {
+			return Integer.toString(index);
+		}
+	}
+}

--
Gitblit v1.9.1