James Moger
2012-10-18 b0e164283fee6f993589cce849ba1fc7d294e89d
New permissions UI for EditUser and EditTeam (issue-36)
3 files added
10 files modified
372 ■■■■■ changed files
resources/gitblit.css 9 ●●●●● patch | view | raw | blame | history
src/com/gitblit/Constants.java 2 ●●●●● patch | view | raw | blame | history
src/com/gitblit/models/RepositoryAccessPermission.java 52 ●●●●● patch | view | raw | blame | history
src/com/gitblit/models/TeamModel.java 16 ●●●●● patch | view | raw | blame | history
src/com/gitblit/models/UserModel.java 18 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/GitBlitWebApp.properties 8 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/BasePage.java 32 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditTeamPage.html 2 ●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditTeamPage.java 25 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditUserPage.html 7 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/pages/EditUserPage.java 20 ●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/RepositoryPermissionsPanel.html 24 ●●●●● patch | view | raw | blame | history
src/com/gitblit/wicket/panels/RepositoryPermissionsPanel.java 157 ●●●●● patch | view | raw | blame | history
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;
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;
src/com/gitblit/models/RepositoryAccessPermission.java
New file
@@ -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);
    }
}
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;
@@ -86,6 +87,21 @@
        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
     * this repository.
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.
     * 
src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -346,3 +346,11 @@
gb.repositoryPermissions = repository permissions
gb.userPermissions = user permissions
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)
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()) {
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>
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)));
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>    
</body>
</wicket:extend>
</html>
src/com/gitblit/wicket/pages/EditUserPage.java
@@ -39,12 +39,14 @@
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 {
@@ -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,9 +100,8 @@
        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);
@@ -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"));
src/com/gitblit/wicket/panels/RepositoryPermissionsPanel.html
New file
@@ -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>
src/com/gitblit/wicket/panels/RepositoryPermissionsPanel.java
New file
@@ -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);
        }
    }
}