From f08aab5c5e632431635e73b47b6096dc47243755 Mon Sep 17 00:00:00 2001 From: James Moger Date: Tue, 13 Dec 2011 08:37:02 -0500 Subject: [PATCH] Teams JSON-RPC support --- docs/02_rpc.mkd | 65 +-- src/com/gitblit/Constants.java | 10 +- src/com/gitblit/RpcFilter.java | 2 + src/com/gitblit/RpcServlet.java | 57 ++- .../gitblit/client/EditRepositoryDialog.java | 31 +- src/com/gitblit/client/EditTeamDialog.java | 269 +++++++++++++ src/com/gitblit/client/EditUserDialog.java | 66 ++- src/com/gitblit/client/GitblitClient.java | 93 ++++- src/com/gitblit/client/GitblitPanel.java | 44 +- src/com/gitblit/client/RepositoriesPanel.java | 22 +- src/com/gitblit/client/TeamsPanel.java | 378 ++++++++++++++++++ src/com/gitblit/client/TeamsTableModel.java | 105 +++++ src/com/gitblit/client/UsersPanel.java | 38 +- src/com/gitblit/client/UsersTableModel.java | 13 +- src/com/gitblit/utils/RpcUtils.java | 130 +++++- tests/com/gitblit/tests/RpcTests.java | 78 ++++ 16 files changed, 1334 insertions(+), 67 deletions(-) create mode 100644 src/com/gitblit/client/EditTeamDialog.java create mode 100644 src/com/gitblit/client/TeamsPanel.java create mode 100644 src/com/gitblit/client/TeamsTableModel.java diff --git a/docs/02_rpc.mkd b/docs/02_rpc.mkd index 7fe7634f..5cd0052d 100644 --- a/docs/02_rpc.mkd +++ b/docs/02_rpc.mkd @@ -57,32 +57,49 @@ The Gitblit API includes methods for retrieving and interpreting RSS feeds. The ## JSON Remote Procedure Call (RPC) Interface +### RPC Protocol Versions - + + + + + +
url parametersrequired
user
permission
json
ReleaseProtocol Version
Gitblit v0.7.01 (inferred version)
Gitblit v0.8.02
+ +### RPC Request and Response Types + + - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
url parametersrequired
user
permission
protocol
version
json
req=name=post bodyresponse body
web.enableRpcServlet=true
LIST_REPOSITORIES---Map<String, RepositoryModel>
LIST_BRANCHES---Map<String, List<String>>
LIST_SETTINGS---ServerSettings (basic keys)
web.enableRpcManagement=true
CREATE_REPOSITORYrepository nameadminRepositoryModel-
EDIT_REPOSITORYrepository nameadminRepositoryModel-
DELETE_REPOSITORYrepository nameadmin--
LIST_USERS-admin-List<UserModel>
CREATE_USERuser nameadminUserModel-
EDIT_USERuser nameadminUserModel-
DELETE_USERuser nameadmin--
LIST_REPOSITORY_MEMBERSrepository nameadmin-List<String>
SET_REPOSITORY_MEMBERSrepository nameadminList<String>-
LIST_SETTINGS-admin-ServerSettings (management keys)
web.enableRpcAdministration=true
LIST_FEDERATION_REGISTRATIONS-admin-List<FederationModel>
LIST_FEDERATION_RESULTS-admin-List<FederationModel>
LIST_FEDERATION_PROPOSALS-admin-List<FederationProposal>
LIST_FEDERATION_SETS-admin-List<FederationSet>
LIST_SETTINGS-admin-ServerSettings (all keys)
EDIT_SETTINGS-adminMap<String, String>-
LIST_STATUS-admin-ServerStatus (see example below)
web.enableRpcServlet=true
GET_PROTOCOL--2-Integer
LIST_REPOSITORIES--1-Map<String, RepositoryModel>
LIST_BRANCHES--1-Map<String, List<String>>
LIST_SETTINGS--1-ServerSettings (basic keys)
web.enableRpcManagement=true
CREATE_REPOSITORYrepository nameadmin1RepositoryModel-
EDIT_REPOSITORYrepository nameadmin1RepositoryModel-
DELETE_REPOSITORYrepository nameadmin1--
LIST_USERS-admin1-List<UserModel>
CREATE_USERuser nameadmin1UserModel-
EDIT_USERuser nameadmin1UserModel-
DELETE_USERuser nameadmin1--
LIST_TEAMS-admin2-List<TeamModel>
CREATE_TEAMteam nameadmin2TeamModel-
EDIT_TEAMteam nameadmin2TeamModel-
DELETE_TEAMteam nameadmin2--
LIST_REPOSITORY_MEMBERSrepository nameadmin1-List<String>
SET_REPOSITORY_MEMBERSrepository nameadmin1List<String>-
LIST_REPOSITORY_TEAMSrepository nameadmin2-List<String>
SET_REPOSITORY_TEAMSrepository nameadmin2List<String>-
LIST_SETTINGS-admin1-ServerSettings (management keys)
web.enableRpcAdministration=true
LIST_FEDERATION_REGISTRATIONS-admin1-List<FederationModel>
LIST_FEDERATION_RESULTS-admin1-List<FederationModel>
LIST_FEDERATION_PROPOSALS-admin1-List<FederationProposal>
LIST_FEDERATION_SETS-admin1-List<FederationSet>
LIST_SETTINGS-admin1-ServerSettings (all keys)
EDIT_SETTINGS-admin1Map<String, String>-
LIST_STATUS-admin1-ServerStatus (see example below)
### RPC/HTTP Response Codes diff --git a/src/com/gitblit/Constants.java b/src/com/gitblit/Constants.java index a2b9eebc..32799808 100644 --- a/src/com/gitblit/Constants.java +++ b/src/com/gitblit/Constants.java @@ -203,10 +203,12 @@ public class Constants { public static enum RpcRequest { // Order is important here. anything above LIST_SETTINGS requires // administrator privileges and web.allowRpcManagement. - LIST_REPOSITORIES, LIST_BRANCHES, LIST_SETTINGS, CREATE_REPOSITORY, EDIT_REPOSITORY, - DELETE_REPOSITORY, LIST_USERS, CREATE_USER, EDIT_USER, DELETE_USER, - LIST_REPOSITORY_MEMBERS, SET_REPOSITORY_MEMBERS, LIST_FEDERATION_REGISTRATIONS, - LIST_FEDERATION_RESULTS, LIST_FEDERATION_PROPOSALS, LIST_FEDERATION_SETS, + GET_PROTOCOL, LIST_REPOSITORIES, LIST_BRANCHES, LIST_SETTINGS, + CREATE_REPOSITORY, EDIT_REPOSITORY, DELETE_REPOSITORY, + LIST_USERS, CREATE_USER, EDIT_USER, DELETE_USER, + LIST_TEAMS, CREATE_TEAM, EDIT_TEAM, DELETE_TEAM, + LIST_REPOSITORY_MEMBERS, SET_REPOSITORY_MEMBERS, LIST_REPOSITORY_TEAMS, SET_REPOSITORY_TEAMS, + LIST_FEDERATION_REGISTRATIONS, LIST_FEDERATION_RESULTS, LIST_FEDERATION_PROPOSALS, LIST_FEDERATION_SETS, EDIT_SETTINGS, LIST_STATUS; public static RpcRequest fromName(String name) { diff --git a/src/com/gitblit/RpcFilter.java b/src/com/gitblit/RpcFilter.java index 2ffb0610..4c0f03df 100644 --- a/src/com/gitblit/RpcFilter.java +++ b/src/com/gitblit/RpcFilter.java @@ -135,6 +135,8 @@ public class RpcFilter extends AuthenticationFilter { private boolean canAccess(UserModel user, RpcRequest requestType) { switch (requestType) { + case GET_PROTOCOL: + return true; case LIST_REPOSITORIES: return true; default: diff --git a/src/com/gitblit/RpcServlet.java b/src/com/gitblit/RpcServlet.java index 068562e8..115d5536 100644 --- a/src/com/gitblit/RpcServlet.java +++ b/src/com/gitblit/RpcServlet.java @@ -33,6 +33,7 @@ import com.gitblit.Constants.RpcRequest; import com.gitblit.models.RefModel; import com.gitblit.models.RepositoryModel; import com.gitblit.models.ServerSettings; +import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.utils.HttpUtils; import com.gitblit.utils.JGitUtils; @@ -48,6 +49,8 @@ public class RpcServlet extends JsonServlet { private static final long serialVersionUID = 1L; + public static final int PROTOCOL_VERSION = 2; + public RpcServlet() { super(); } @@ -77,7 +80,10 @@ public class RpcServlet extends JsonServlet { && GitBlit.getBoolean(Keys.web.enableRpcAdministration, false); Object result = null; - if (RpcRequest.LIST_REPOSITORIES.equals(reqType)) { + if (RpcRequest.GET_PROTOCOL.equals(reqType)) { + // Return the protocol version + result = PROTOCOL_VERSION; + } else if (RpcRequest.LIST_REPOSITORIES.equals(reqType)) { // Determine the Gitblit clone url String gitblitUrl = HttpUtils.getGitblitURL(request); StringBuilder sb = new StringBuilder(); @@ -128,6 +134,14 @@ public class RpcServlet extends JsonServlet { users.add(GitBlit.self().getUserModel(name)); } result = users; + } else if (RpcRequest.LIST_TEAMS.equals(reqType)) { + // list teams + List names = GitBlit.self().getAllTeamnames(); + List teams = new ArrayList(); + for (String name : names) { + teams.add(GitBlit.self().getTeamModel(name)); + } + result = teams; } else if (RpcRequest.CREATE_REPOSITORY.equals(reqType)) { // create repository RepositoryModel model = deserialize(request, response, RepositoryModel.class); @@ -180,6 +194,33 @@ public class RpcServlet extends JsonServlet { if (!GitBlit.self().deleteUser(model.username)) { response.setStatus(failureCode); } + } else if (RpcRequest.CREATE_TEAM.equals(reqType)) { + // create team + TeamModel model = deserialize(request, response, TeamModel.class); + try { + GitBlit.self().updateTeamModel(model.name, model, true); + } catch (GitBlitException e) { + response.setStatus(failureCode); + } + } else if (RpcRequest.EDIT_TEAM.equals(reqType)) { + // edit team + TeamModel model = deserialize(request, response, TeamModel.class); + // name parameter specifies original team name in event of rename + String teamname = objectName; + if (teamname == null) { + teamname = model.name; + } + try { + GitBlit.self().updateTeamModel(teamname, model, false); + } catch (GitBlitException e) { + response.setStatus(failureCode); + } + } else if (RpcRequest.DELETE_TEAM.equals(reqType)) { + // delete team + TeamModel model = deserialize(request, response, TeamModel.class); + if (!GitBlit.self().deleteTeam(model.name)) { + response.setStatus(failureCode); + } } else if (RpcRequest.LIST_REPOSITORY_MEMBERS.equals(reqType)) { // get repository members RepositoryModel model = GitBlit.self().getRepositoryModel(objectName); @@ -192,6 +233,18 @@ public class RpcServlet extends JsonServlet { if (!GitBlit.self().setRepositoryUsers(model, users)) { response.setStatus(failureCode); } + } else if (RpcRequest.LIST_REPOSITORY_TEAMS.equals(reqType)) { + // get repository teams + RepositoryModel model = GitBlit.self().getRepositoryModel(objectName); + result = GitBlit.self().getRepositoryTeams(model); + } else if (RpcRequest.SET_REPOSITORY_TEAMS.equals(reqType)) { + // update repository team access list + RepositoryModel model = GitBlit.self().getRepositoryModel(objectName); + Collection names = deserialize(request, response, RpcUtils.NAMES_TYPE); + List teams = new ArrayList(names); + if (!GitBlit.self().setRepositoryTeams(model, teams)) { + response.setStatus(failureCode); + } } else if (RpcRequest.LIST_FEDERATION_REGISTRATIONS.equals(reqType)) { // return the list of federation registrations if (allowAdmin) { @@ -233,7 +286,7 @@ public class RpcServlet extends JsonServlet { keys.add(Keys.web.siteName); keys.add(Keys.web.mountParameters); keys.add(Keys.web.syndicationEntries); - + if (allowManagement) { // keys necessary for repository and/or user management keys.add(Keys.realm.minPasswordLength); diff --git a/src/com/gitblit/client/EditRepositoryDialog.java b/src/com/gitblit/client/EditRepositoryDialog.java index ef0f58ac..0b6ef59a 100644 --- a/src/com/gitblit/client/EditRepositoryDialog.java +++ b/src/com/gitblit/client/EditRepositoryDialog.java @@ -52,6 +52,7 @@ import javax.swing.ListCellRenderer; import com.gitblit.Constants.AccessRestrictionType; import com.gitblit.Constants.FederationStrategy; import com.gitblit.models.RepositoryModel; +import com.gitblit.models.TeamModel; import com.gitblit.utils.StringUtils; /** @@ -96,24 +97,26 @@ public class EditRepositoryDialog extends JDialog { private JComboBox ownerField; private JPalette usersPalette; - + private JPalette setsPalette; + + private JPalette teamsPalette; private Set repositoryNames; - public EditRepositoryDialog() { - this(new RepositoryModel()); + public EditRepositoryDialog(int protocolVersion) { + this(protocolVersion, new RepositoryModel()); this.isCreate = true; setTitle(Translation.get("gb.newRepository")); } - public EditRepositoryDialog(RepositoryModel aRepository) { + public EditRepositoryDialog(int protocolVersion, RepositoryModel aRepository) { super(); this.repositoryName = aRepository.name; this.repository = new RepositoryModel(); this.repositoryNames = new HashSet(); this.isCreate = false; - initialize(aRepository); + initialize(protocolVersion, aRepository); setModal(true); setResizable(false); setTitle(Translation.get("gb.edit") + ": " + aRepository.name); @@ -132,7 +135,7 @@ public class EditRepositoryDialog extends JDialog { return rootPane; } - private void initialize(RepositoryModel anRepository) { + private void initialize(int protocolVersion, RepositoryModel anRepository) { nameField = new JTextField(anRepository.name == null ? "" : anRepository.name, 35); descriptionField = new JTextField(anRepository.description == null ? "" : anRepository.description, 35); @@ -195,6 +198,11 @@ public class EditRepositoryDialog extends JDialog { accessPanel.add(newFieldPanel(Translation.get("gb.permittedUsers"), usersPalette), BorderLayout.CENTER); + teamsPalette = new JPalette(); + JPanel teamsPanel = new JPanel(new BorderLayout(5, 5)); + teamsPanel.add(newFieldPanel(Translation.get("gb.permittedTeams"), teamsPalette), + BorderLayout.CENTER); + setsPalette = new JPalette(); JPanel federationPanel = new JPanel(new BorderLayout(5, 5)); federationPanel.add( @@ -206,6 +214,9 @@ public class EditRepositoryDialog extends JDialog { JTabbedPane panel = new JTabbedPane(JTabbedPane.TOP); panel.addTab(Translation.get("gb.general"), fieldsPanel); panel.addTab(Translation.get("gb.accessRestriction"), accessPanel); + if (protocolVersion >= 2) { + panel.addTab(Translation.get("gb.teams"), teamsPanel); + } panel.addTab(Translation.get("gb.federation"), federationPanel); JButton createButton = new JButton(Translation.get("gb.save")); @@ -358,6 +369,10 @@ public class EditRepositoryDialog extends JDialog { } usersPalette.setObjects(all, selected); } + + public void setTeams(List all, List selected) { + teamsPalette.setObjects(all, selected); + } public void setRepositories(List repositories) { repositoryNames.clear(); @@ -385,6 +400,10 @@ public class EditRepositoryDialog extends JDialog { return usersPalette.getSelections(); } + public List getPermittedTeams() { + return teamsPalette.getSelections(); + } + /** * ListCellRenderer to display descriptive text about the access * restriction. diff --git a/src/com/gitblit/client/EditTeamDialog.java b/src/com/gitblit/client/EditTeamDialog.java new file mode 100644 index 00000000..4297599c --- /dev/null +++ b/src/com/gitblit/client/EditTeamDialog.java @@ -0,0 +1,269 @@ +/* + * Copyright 2011 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.client; + +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.GridLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyEvent; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JRootPane; +import javax.swing.JTabbedPane; +import javax.swing.JTextField; +import javax.swing.KeyStroke; + +import com.gitblit.Constants.AccessRestrictionType; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.ServerSettings; +import com.gitblit.models.TeamModel; +import com.gitblit.utils.StringUtils; + +public class EditTeamDialog extends JDialog { + + private static final long serialVersionUID = 1L; + + private final String teamname; + + private final TeamModel team; + + private final ServerSettings settings; + + private boolean isCreate; + + private boolean canceled = true; + + private JTextField teamnameField; + + private JPalette repositoryPalette; + + private JPalette userPalette; + + private Set teamnames; + + public EditTeamDialog(int protocolVersion, ServerSettings settings) { + this(protocolVersion, new TeamModel(""), settings); + this.isCreate = true; + setTitle(Translation.get("gb.newTeam")); + } + + public EditTeamDialog(int protocolVersion, TeamModel aTeam, ServerSettings settings) { + super(); + this.teamname = aTeam.name; + this.team = new TeamModel(""); + this.settings = settings; + this.teamnames = new HashSet(); + this.isCreate = false; + initialize(protocolVersion, aTeam); + setModal(true); + setTitle(Translation.get("gb.edit") + ": " + aTeam.name); + setIconImage(new ImageIcon(getClass().getResource("/gitblt-favicon.png")).getImage()); + } + + @Override + protected JRootPane createRootPane() { + KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0); + JRootPane rootPane = new JRootPane(); + rootPane.registerKeyboardAction(new ActionListener() { + public void actionPerformed(ActionEvent actionEvent) { + setVisible(false); + } + }, stroke, JComponent.WHEN_IN_FOCUSED_WINDOW); + return rootPane; + } + + private void initialize(int protocolVersion, TeamModel aTeam) { + teamnameField = new JTextField(aTeam.name == null ? "" : aTeam.name, 25); + + JPanel fieldsPanel = new JPanel(new GridLayout(0, 1)); + fieldsPanel.add(newFieldPanel(Translation.get("gb.teamName"), teamnameField)); + + final Insets _insets = new Insets(5, 5, 5, 5); + repositoryPalette = new JPalette(); + userPalette = new JPalette(); + + JPanel fieldsPanelTop = new JPanel(new BorderLayout()); + fieldsPanelTop.add(fieldsPanel, BorderLayout.NORTH); + + JPanel repositoriesPanel = new JPanel(new BorderLayout()) { + + private static final long serialVersionUID = 1L; + + public Insets getInsets() { + return _insets; + } + }; + repositoriesPanel.add(repositoryPalette, BorderLayout.CENTER); + + JPanel usersPanel = new JPanel(new BorderLayout()) { + + private static final long serialVersionUID = 1L; + + public Insets getInsets() { + return _insets; + } + }; + usersPanel.add(userPalette, BorderLayout.CENTER); + + JTabbedPane panel = new JTabbedPane(JTabbedPane.TOP); + panel.addTab(Translation.get("gb.general"), fieldsPanelTop); + panel.addTab(Translation.get("gb.teamMembers"), usersPanel); + panel.addTab(Translation.get("gb.restrictedRepositories"), repositoriesPanel); + + + JButton createButton = new JButton(Translation.get("gb.save")); + createButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent event) { + if (validateFields()) { + canceled = false; + setVisible(false); + } + } + }); + + JButton cancelButton = new JButton(Translation.get("gb.cancel")); + cancelButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent event) { + canceled = true; + setVisible(false); + } + }); + + JPanel controls = new JPanel(); + controls.add(cancelButton); + controls.add(createButton); + + JPanel centerPanel = new JPanel(new BorderLayout(5, 5)) { + + private static final long serialVersionUID = 1L; + + @Override + public Insets getInsets() { + return _insets; + } + }; + centerPanel.add(panel, BorderLayout.CENTER); + centerPanel.add(controls, BorderLayout.SOUTH); + + getContentPane().setLayout(new BorderLayout(5, 5)); + getContentPane().add(centerPanel, BorderLayout.CENTER); + pack(); + } + + private JPanel newFieldPanel(String label, JComponent comp) { + JLabel fieldLabel = new JLabel(label); + fieldLabel.setFont(fieldLabel.getFont().deriveFont(Font.BOLD)); + fieldLabel.setPreferredSize(new Dimension(150, 20)); + JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 0)); + panel.add(fieldLabel); + panel.add(comp); + return panel; + } + + private boolean validateFields() { + String tname = teamnameField.getText(); + if (StringUtils.isEmpty(tname)) { + error("Please enter a team name!"); + return false; + } + + boolean rename = false; + // verify teamname uniqueness on create + if (isCreate) { + if (teamnames.contains(tname.toLowerCase())) { + error(MessageFormat.format("Team name ''{0}'' is unavailable.", tname)); + return false; + } + } else { + // check rename collision + rename = !StringUtils.isEmpty(teamname) && !teamname.equalsIgnoreCase(tname); + if (rename) { + if (teamnames.contains(tname.toLowerCase())) { + error(MessageFormat.format( + "Failed to rename ''{0}'' because ''{1}'' already exists.", teamname, + tname)); + return false; + } + } + } + team.name = tname; + + team.repositories.clear(); + team.repositories.addAll(repositoryPalette.getSelections()); + + team.users.clear(); + team.users.addAll(userPalette.getSelections()); + return true; + } + + private void error(String message) { + JOptionPane.showMessageDialog(EditTeamDialog.this, message, Translation.get("gb.error"), + JOptionPane.ERROR_MESSAGE); + } + + public void setTeams(List teams) { + teamnames.clear(); + for (TeamModel team : teams) { + teamnames.add(team.name.toLowerCase()); + } + } + + public void setRepositories(List repositories, List selected) { + List restricted = new ArrayList(); + for (RepositoryModel repo : repositories) { + if (repo.accessRestriction.exceeds(AccessRestrictionType.NONE)) { + restricted.add(repo.name); + } + } + StringUtils.sortRepositorynames(restricted); + if (selected != null) { + StringUtils.sortRepositorynames(selected); + } + repositoryPalette.setObjects(restricted, selected); + } + + public void setUsers(List users, List selected) { + Collections.sort(users); + if (selected != null) { + Collections.sort(selected); + } + userPalette.setObjects(users, selected); + } + + public TeamModel getTeam() { + if (canceled) { + return null; + } + return team; + } +} diff --git a/src/com/gitblit/client/EditUserDialog.java b/src/com/gitblit/client/EditUserDialog.java index 246a0777..3f1b9291 100644 --- a/src/com/gitblit/client/EditUserDialog.java +++ b/src/com/gitblit/client/EditUserDialog.java @@ -26,6 +26,7 @@ import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.text.MessageFormat; import java.util.ArrayList; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -40,6 +41,7 @@ import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JPasswordField; import javax.swing.JRootPane; +import javax.swing.JTabbedPane; import javax.swing.JTextField; import javax.swing.KeyStroke; @@ -47,6 +49,7 @@ import com.gitblit.Constants.AccessRestrictionType; import com.gitblit.Keys; import com.gitblit.models.RepositoryModel; import com.gitblit.models.ServerSettings; +import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.utils.StringUtils; @@ -75,23 +78,25 @@ public class EditUserDialog extends JDialog { private JCheckBox notFederatedCheckbox; private JPalette repositoryPalette; + + private JPalette teamsPalette; private Set usernames; - public EditUserDialog(ServerSettings settings) { - this(new UserModel(""), settings); + public EditUserDialog(int protocolVersion, ServerSettings settings) { + this(protocolVersion, new UserModel(""), settings); this.isCreate = true; setTitle(Translation.get("gb.newUser")); } - public EditUserDialog(UserModel anUser, ServerSettings settings) { + public EditUserDialog(int protocolVersion, UserModel anUser, ServerSettings settings) { super(); this.username = anUser.username; this.user = new UserModel(""); this.settings = settings; this.usernames = new HashSet(); this.isCreate = false; - initialize(anUser); + initialize(protocolVersion, anUser); setModal(true); setTitle(Translation.get("gb.edit") + ": " + anUser.username); setIconImage(new ImageIcon(getClass().getResource("/gitblt-favicon.png")).getImage()); @@ -109,7 +114,7 @@ public class EditUserDialog extends JDialog { return rootPane; } - private void initialize(UserModel anUser) { + private void initialize(int protocolVersion, UserModel anUser) { usernameField = new JTextField(anUser.username == null ? "" : anUser.username, 25); passwordField = new JPasswordField(anUser.password == null ? "" : anUser.password, 25); confirmPasswordField = new JPasswordField(anUser.password == null ? "" : anUser.password, @@ -127,11 +132,40 @@ public class EditUserDialog extends JDialog { fieldsPanel.add(newFieldPanel(Translation.get("gb.excludeFromFederation"), notFederatedCheckbox)); + final Insets _insets = new Insets(5, 5, 5, 5); repositoryPalette = new JPalette(); - JPanel panel = new JPanel(new BorderLayout()); - panel.add(fieldsPanel, BorderLayout.NORTH); - panel.add(newFieldPanel(Translation.get("gb.restrictedRepositories"), repositoryPalette), - BorderLayout.CENTER); + teamsPalette = new JPalette(); + + JPanel fieldsPanelTop = new JPanel(new BorderLayout()); + fieldsPanelTop.add(fieldsPanel, BorderLayout.NORTH); + + JPanel repositoriesPanel = new JPanel(new BorderLayout()) { + + private static final long serialVersionUID = 1L; + + public Insets getInsets() { + return _insets; + } + }; + repositoriesPanel.add(repositoryPalette, BorderLayout.CENTER); + + JPanel teamsPanel = new JPanel(new BorderLayout()) { + + private static final long serialVersionUID = 1L; + + public Insets getInsets() { + return _insets; + } + }; + teamsPanel.add(teamsPalette, BorderLayout.CENTER); + + JTabbedPane panel = new JTabbedPane(JTabbedPane.TOP); + panel.addTab(Translation.get("gb.general"), fieldsPanelTop); + if (protocolVersion > 1) { + panel.addTab(Translation.get("gb.teamMemberships"), teamsPanel); + } + panel.addTab(Translation.get("gb.restrictedRepositories"), repositoriesPanel); + JButton createButton = new JButton(Translation.get("gb.save")); createButton.addActionListener(new ActionListener() { @@ -154,8 +188,7 @@ public class EditUserDialog extends JDialog { JPanel controls = new JPanel(); controls.add(cancelButton); controls.add(createButton); - - final Insets _insets = new Insets(5, 5, 5, 5); + JPanel centerPanel = new JPanel(new BorderLayout(5, 5)) { private static final long serialVersionUID = 1L; @@ -259,6 +292,9 @@ public class EditUserDialog extends JDialog { user.repositories.clear(); user.repositories.addAll(repositoryPalette.getSelections()); + + user.teams.clear(); + user.teams.addAll(teamsPalette.getSelections()); return true; } @@ -287,6 +323,14 @@ public class EditUserDialog extends JDialog { } repositoryPalette.setObjects(restricted, selected); } + + public void setTeams(List teams, List selected) { + Collections.sort(teams); + if (selected != null) { + Collections.sort(selected); + } + teamsPalette.setObjects(teams, selected); + } public UserModel getUser() { if (canceled) { diff --git a/src/com/gitblit/client/GitblitClient.java b/src/com/gitblit/client/GitblitClient.java index c027537a..b9444861 100644 --- a/src/com/gitblit/client/GitblitClient.java +++ b/src/com/gitblit/client/GitblitClient.java @@ -37,6 +37,7 @@ import com.gitblit.models.FeedModel; import com.gitblit.models.RepositoryModel; import com.gitblit.models.ServerSettings; import com.gitblit.models.ServerStatus; +import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.utils.RpcUtils; import com.gitblit.utils.StringUtils; @@ -63,6 +64,8 @@ public class GitblitClient implements Serializable { private final char[] password; + private volatile int protocolVersion; + private volatile boolean allowManagement; private volatile boolean allowAdministration; @@ -73,6 +76,8 @@ public class GitblitClient implements Serializable { private final List allUsers; + private final List allTeams; + private final List federationRegistrations; private final List availableFeeds; @@ -90,6 +95,7 @@ public class GitblitClient implements Serializable { this.password = reg.password; this.allUsers = new ArrayList(); + this.allTeams = new ArrayList(); this.allRepositories = new ArrayList(); this.federationRegistrations = new ArrayList(); this.availableFeeds = new ArrayList(); @@ -98,6 +104,7 @@ public class GitblitClient implements Serializable { } public void login() throws IOException { + protocolVersion = RpcUtils.getProtocolVersion(url, account, password); refreshSettings(); refreshAvailableFeeds(); refreshRepositories(); @@ -107,6 +114,9 @@ public class GitblitClient implements Serializable { // credentials may not have administrator access // or server may have disabled rpc management refreshUsers(); + if (protocolVersion > 1) { + refreshTeams(); + } allowManagement = true; } catch (UnauthorizedException e) { } catch (ForbiddenException e) { @@ -130,6 +140,10 @@ public class GitblitClient implements Serializable { } } + public int getProtocolVersion() { + return protocolVersion; + } + public boolean allowManagement() { return allowManagement; } @@ -198,6 +212,13 @@ public class GitblitClient implements Serializable { return allUsers; } + public List refreshTeams() throws IOException { + List teams = RpcUtils.getTeams(url, account, password); + allTeams.clear(); + allTeams.addAll(teams); + return allTeams; + } + public ServerSettings refreshSettings() throws IOException { settings = RpcUtils.getSettings(url, account, password); return settings; @@ -253,8 +274,8 @@ public class GitblitClient implements Serializable { for (FeedModel feed : reg.feeds) { feed.lastRefreshDate = feed.currentRefreshDate; feed.currentRefreshDate = new Date(); - List entries = SyndicationUtils.readFeed(url, - feed.repository, feed.branch, -1, page, account, password); + List entries = SyndicationUtils.readFeed(url, feed.repository, + feed.branch, -1, page, account, password); allEntries.addAll(entries); } } @@ -301,8 +322,8 @@ public class GitblitClient implements Serializable { return syndicatedEntries; } - public List log(String repository, String branch, int numberOfEntries, - int page) throws IOException { + public List log(String repository, String branch, int numberOfEntries, int page) + throws IOException { return SyndicationUtils.readFeed(url, repository, branch, numberOfEntries, page, account, password); } @@ -343,6 +364,29 @@ public class GitblitClient implements Serializable { return usernames; } + public List getTeams() { + return allTeams; + } + + public List getTeamnames() { + List teamnames = new ArrayList(); + for (TeamModel team : this.allTeams) { + teamnames.add(team.name); + } + Collections.sort(teamnames); + return teamnames; + } + + public List getPermittedTeamnames(RepositoryModel repository) { + List teamnames = new ArrayList(); + for (TeamModel team : this.allTeams) { + if (team.repositories.contains(repository.name)) { + teamnames.add(team.name); + } + } + return teamnames; + } + public List getFederationSets() { return settings.get(Keys.federation.sets).getStrings(); } @@ -353,23 +397,44 @@ public class GitblitClient implements Serializable { public boolean createRepository(RepositoryModel repository, List permittedUsers) throws IOException { + return createRepository(repository, permittedUsers, null); + } + + public boolean createRepository(RepositoryModel repository, List permittedUsers, + List permittedTeams) throws IOException { boolean success = true; success &= RpcUtils.createRepository(repository, url, account, password); - if (permittedUsers.size() > 0) { + if (permittedUsers != null && permittedUsers.size() > 0) { // if new repository has named members, set them success &= RpcUtils.setRepositoryMembers(repository, permittedUsers, url, account, password); } + if (permittedTeams != null && permittedTeams.size() > 0) { + // if new repository has named teams, set them + success &= RpcUtils.setRepositoryTeams(repository, permittedTeams, url, account, + password); + } return success; } public boolean updateRepository(String name, RepositoryModel repository, List permittedUsers) throws IOException { + return updateRepository(name, repository, permittedUsers, null); + } + + public boolean updateRepository(String name, RepositoryModel repository, + List permittedUsers, List permittedTeams) throws IOException { boolean success = true; success &= RpcUtils.updateRepository(name, repository, url, account, password); - // always set the repository members - success &= RpcUtils - .setRepositoryMembers(repository, permittedUsers, url, account, password); + // set the repository members + if (permittedUsers != null) { + success &= RpcUtils.setRepositoryMembers(repository, permittedUsers, url, account, + password); + } + if (permittedTeams != null) { + success &= RpcUtils.setRepositoryTeams(repository, permittedTeams, url, account, + password); + } return success; } @@ -389,6 +454,18 @@ public class GitblitClient implements Serializable { return RpcUtils.deleteUser(user, url, account, password); } + public boolean createTeam(TeamModel team) throws IOException { + return RpcUtils.createTeam(team, url, account, password); + } + + public boolean updateTeam(String name, TeamModel team) throws IOException { + return RpcUtils.updateTeam(name, team, url, account, password); + } + + public boolean deleteTeam(TeamModel team) throws IOException { + return RpcUtils.deleteTeam(team, url, account, password); + } + public boolean updateSettings(Map newSettings) throws IOException { return RpcUtils.updateSettings(newSettings, url, account, password); } diff --git a/src/com/gitblit/client/GitblitPanel.java b/src/com/gitblit/client/GitblitPanel.java index 0e670ae3..f14ce790 100644 --- a/src/com/gitblit/client/GitblitPanel.java +++ b/src/com/gitblit/client/GitblitPanel.java @@ -50,6 +50,8 @@ public class GitblitPanel extends JPanel implements CloseTabListener { private FeedsPanel feedsPanel; private UsersPanel usersPanel; + + private TeamsPanel teamsPanel; private SettingsPanel settingsPanel; @@ -62,6 +64,7 @@ public class GitblitPanel extends JPanel implements CloseTabListener { tabs = new JTabbedPane(JTabbedPane.BOTTOM); tabs.addTab(Translation.get("gb.repositories"), createRepositoriesPanel()); tabs.addTab(Translation.get("gb.activity"), createFeedsPanel()); + tabs.addTab(Translation.get("gb.teams"), createTeamsPanel()); tabs.addTab(Translation.get("gb.users"), createUsersPanel()); tabs.addTab(Translation.get("gb.settings"), createSettingsPanel()); tabs.addTab(Translation.get("gb.status"), createStatusPanel()); @@ -89,6 +92,11 @@ public class GitblitPanel extends JPanel implements CloseTabListener { protected void updateUsersTable() { usersPanel.updateTable(false); } + + @Override + protected void updateTeamsTable() { + teamsPanel.updateTable(false); + } }; return repositoriesPanel; @@ -107,9 +115,30 @@ public class GitblitPanel extends JPanel implements CloseTabListener { } private JPanel createUsersPanel() { - usersPanel = new UsersPanel(gitblit); + usersPanel = new UsersPanel(gitblit) { + + private static final long serialVersionUID = 1L; + + @Override + protected void updateTeamsTable() { + teamsPanel.updateTable(false); + } + }; return usersPanel; } + + private JPanel createTeamsPanel() { + teamsPanel = new TeamsPanel(gitblit) { + + private static final long serialVersionUID = 1L; + + @Override + protected void updateUsersTable() { + usersPanel.updateTable(false); + } + }; + return teamsPanel; + } private JPanel createSettingsPanel() { settingsPanel = new SettingsPanel(gitblit); @@ -128,6 +157,19 @@ public class GitblitPanel extends JPanel implements CloseTabListener { feedsPanel.updateTable(true); if (gitblit.allowManagement()) { + if (gitblit.getProtocolVersion() >= 2) { + // refresh teams panel + teamsPanel.updateTable(false); + } else { + // remove teams panel + String teams = Translation.get("gb.teams"); + for (int i = 0; i < tabs.getTabCount(); i++) { + if (teams.equals(tabs.getTitleAt(i))) { + tabs.removeTabAt(i); + break; + } + } + } usersPanel.updateTable(false); } else { // user does not have administrator privileges diff --git a/src/com/gitblit/client/RepositoriesPanel.java b/src/com/gitblit/client/RepositoriesPanel.java index 70ff6cfb..cd3f46be 100644 --- a/src/com/gitblit/client/RepositoriesPanel.java +++ b/src/com/gitblit/client/RepositoriesPanel.java @@ -277,6 +277,8 @@ public abstract class RepositoriesPanel extends JPanel { protected abstract void subscribeFeeds(List feeds); protected abstract void updateUsersTable(); + + protected abstract void updateTeamsTable(); protected void disableManagement() { createRepository.setVisible(false); @@ -349,14 +351,16 @@ public abstract class RepositoriesPanel extends JPanel { * */ protected void createRepository() { - EditRepositoryDialog dialog = new EditRepositoryDialog(); + EditRepositoryDialog dialog = new EditRepositoryDialog(gitblit.getProtocolVersion()); dialog.setLocationRelativeTo(RepositoriesPanel.this); dialog.setUsers(null, gitblit.getUsernames(), null); + dialog.setTeams(gitblit.getTeamnames(), null); dialog.setRepositories(gitblit.getRepositories()); dialog.setFederationSets(gitblit.getFederationSets(), null); dialog.setVisible(true); final RepositoryModel newRepository = dialog.getRepository(); final List permittedUsers = dialog.getPermittedUsers(); + final List permittedTeams = dialog.getPermittedTeams(); if (newRepository == null) { return; } @@ -365,12 +369,15 @@ public abstract class RepositoriesPanel extends JPanel { @Override protected Boolean doRequest() throws IOException { - boolean success = gitblit.createRepository(newRepository, permittedUsers); + boolean success = gitblit.createRepository(newRepository, permittedUsers, permittedTeams); if (success) { gitblit.refreshRepositories(); if (permittedUsers.size() > 0) { gitblit.refreshUsers(); } + if (permittedTeams.size() > 0) { + gitblit.refreshTeams(); + } } return success; } @@ -379,6 +386,7 @@ public abstract class RepositoriesPanel extends JPanel { protected void onSuccess() { updateTable(false); updateUsersTable(); + updateTeamsTable(); } @Override @@ -397,16 +405,18 @@ public abstract class RepositoriesPanel extends JPanel { * @param repository */ protected void editRepository(final RepositoryModel repository) { - EditRepositoryDialog dialog = new EditRepositoryDialog(repository); + EditRepositoryDialog dialog = new EditRepositoryDialog(gitblit.getProtocolVersion(), repository); dialog.setLocationRelativeTo(RepositoriesPanel.this); List usernames = gitblit.getUsernames(); List members = gitblit.getPermittedUsernames(repository); dialog.setUsers(repository.owner, usernames, members); + dialog.setTeams(gitblit.getTeamnames(), gitblit.getPermittedTeamnames(repository)); dialog.setRepositories(gitblit.getRepositories()); dialog.setFederationSets(gitblit.getFederationSets(), repository.federationSets); dialog.setVisible(true); final RepositoryModel revisedRepository = dialog.getRepository(); final List permittedUsers = dialog.getPermittedUsers(); + final List permittedTeams = dialog.getPermittedTeams(); if (revisedRepository == null) { return; } @@ -416,10 +426,11 @@ public abstract class RepositoriesPanel extends JPanel { @Override protected Boolean doRequest() throws IOException { boolean success = gitblit.updateRepository(repository.name, revisedRepository, - permittedUsers); + permittedUsers, permittedTeams); if (success) { gitblit.refreshRepositories(); gitblit.refreshUsers(); + gitblit.refreshTeams(); } return success; } @@ -428,6 +439,7 @@ public abstract class RepositoriesPanel extends JPanel { protected void onSuccess() { updateTable(false); updateUsersTable(); + updateTeamsTable(); } @Override @@ -460,6 +472,7 @@ public abstract class RepositoriesPanel extends JPanel { if (success) { gitblit.refreshRepositories(); gitblit.refreshUsers(); + gitblit.refreshTeams(); } return success; } @@ -468,6 +481,7 @@ public abstract class RepositoriesPanel extends JPanel { protected void onSuccess() { updateTable(false); updateUsersTable(); + updateTeamsTable(); } @Override diff --git a/src/com/gitblit/client/TeamsPanel.java b/src/com/gitblit/client/TeamsPanel.java new file mode 100644 index 00000000..2d1d914a --- /dev/null +++ b/src/com/gitblit/client/TeamsPanel.java @@ -0,0 +1,378 @@ +/* + * Copyright 2011 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.client; + +import java.awt.BorderLayout; +import java.awt.FlowLayout; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.JTextField; +import javax.swing.RowFilter; +import javax.swing.event.ListSelectionEvent; +import javax.swing.event.ListSelectionListener; +import javax.swing.table.TableRowSorter; + +import com.gitblit.Constants.RpcRequest; +import com.gitblit.models.TeamModel; +import com.gitblit.utils.StringUtils; + +/** + * Users panel displays a list of user accounts and allows management of those + * accounts. + * + * @author James Moger + * + */ +public abstract class TeamsPanel extends JPanel { + + private static final long serialVersionUID = 1L; + + private final GitblitClient gitblit; + + private HeaderPanel header; + + private JTable table; + + private TeamsTableModel tableModel; + + private TableRowSorter defaultSorter; + + private JTextField filterTextfield; + + public TeamsPanel(GitblitClient gitblit) { + super(); + this.gitblit = gitblit; + initialize(); + } + + private void initialize() { + JButton refreshTeams = new JButton(Translation.get("gb.refresh")); + refreshTeams.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + refreshTeams(); + } + }); + + JButton createTeam = new JButton(Translation.get("gb.create")); + createTeam.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + createTeam(); + } + }); + + final JButton editTeam = new JButton(Translation.get("gb.edit")); + editTeam.setEnabled(false); + editTeam.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + editTeam(getSelectedTeams().get(0)); + } + }); + + final JButton delTeam = new JButton(Translation.get("gb.delete")); + delTeam.setEnabled(false); + delTeam.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + deleteTeams(getSelectedTeams()); + } + }); + + NameRenderer nameRenderer = new NameRenderer(); + tableModel = new TeamsTableModel(); + defaultSorter = new TableRowSorter(tableModel); + table = Utils.newTable(tableModel, Utils.DATE_FORMAT); + String name = table.getColumnName(TeamsTableModel.Columns.Name.ordinal()); + table.setRowHeight(nameRenderer.getFont().getSize() + 8); + table.getColumn(name).setCellRenderer(nameRenderer); + + int w = 125; + name = table.getColumnName(TeamsTableModel.Columns.Members.ordinal()); + table.getColumn(name).setMinWidth(w); + table.getColumn(name).setMaxWidth(w); + name = table.getColumnName(TeamsTableModel.Columns.Repositories.ordinal()); + table.getColumn(name).setMinWidth(w); + table.getColumn(name).setMaxWidth(w); + + table.setRowSorter(defaultSorter); + table.getRowSorter().toggleSortOrder(TeamsTableModel.Columns.Name.ordinal()); + table.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + + @Override + public void valueChanged(ListSelectionEvent e) { + if (e.getValueIsAdjusting()) { + return; + } + boolean selected = table.getSelectedRow() > -1; + boolean singleSelection = table.getSelectedRows().length == 1; + editTeam.setEnabled(singleSelection && selected); + delTeam.setEnabled(selected); + } + }); + + table.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + editTeam(getSelectedTeams().get(0)); + } + } + }); + + filterTextfield = new JTextField(); + filterTextfield.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + filterTeams(filterTextfield.getText()); + } + }); + filterTextfield.addKeyListener(new KeyAdapter() { + public void keyReleased(KeyEvent e) { + filterTeams(filterTextfield.getText()); + } + }); + + JPanel teamFilterPanel = new JPanel(new BorderLayout(Utils.MARGIN, Utils.MARGIN)); + teamFilterPanel.add(new JLabel(Translation.get("gb.filter")), BorderLayout.WEST); + teamFilterPanel.add(filterTextfield, BorderLayout.CENTER); + + JPanel teamTablePanel = new JPanel(new BorderLayout(Utils.MARGIN, Utils.MARGIN)); + teamTablePanel.add(teamFilterPanel, BorderLayout.NORTH); + teamTablePanel.add(new JScrollPane(table), BorderLayout.CENTER); + + JPanel teamControls = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 0)); + teamControls.add(refreshTeams); + teamControls.add(createTeam); + teamControls.add(editTeam); + teamControls.add(delTeam); + + setLayout(new BorderLayout(Utils.MARGIN, Utils.MARGIN)); + header = new HeaderPanel(Translation.get("gb.teams"), "users_16x16.png"); + add(header, BorderLayout.NORTH); + add(teamTablePanel, BorderLayout.CENTER); + add(teamControls, BorderLayout.SOUTH); + } + + @Override + public void requestFocus() { + filterTextfield.requestFocus(); + } + + @Override + public Insets getInsets() { + return Utils.INSETS; + } + + protected abstract void updateUsersTable(); + + protected void updateTable(boolean pack) { + tableModel.list.clear(); + tableModel.list.addAll(gitblit.getTeams()); + tableModel.fireTableDataChanged(); + header.setText(Translation.get("gb.teams") + " (" + gitblit.getTeams().size() + ")"); + if (pack) { + Utils.packColumns(table, Utils.MARGIN); + } + } + + private void filterTeams(final String fragment) { + if (StringUtils.isEmpty(fragment)) { + table.setRowSorter(defaultSorter); + return; + } + RowFilter containsFilter = new RowFilter() { + public boolean include(Entry entry) { + for (int i = entry.getValueCount() - 1; i >= 0; i--) { + if (entry.getStringValue(i).toLowerCase().contains(fragment.toLowerCase())) { + return true; + } + } + return false; + } + }; + TableRowSorter sorter = new TableRowSorter(tableModel); + sorter.setRowFilter(containsFilter); + table.setRowSorter(sorter); + } + + private List getSelectedTeams() { + List teams = new ArrayList(); + for (int viewRow : table.getSelectedRows()) { + int modelRow = table.convertRowIndexToModel(viewRow); + TeamModel model = tableModel.list.get(modelRow); + teams.add(model); + } + return teams; + } + + protected void refreshTeams() { + GitblitWorker worker = new GitblitWorker(TeamsPanel.this, RpcRequest.LIST_TEAMS) { + @Override + protected Boolean doRequest() throws IOException { + gitblit.refreshTeams(); + return true; + } + + @Override + protected void onSuccess() { + updateTable(false); + } + }; + worker.execute(); + } + + /** + * Displays the create team dialog and fires a SwingWorker to update the + * server, if appropriate. + * + */ + protected void createTeam() { + EditTeamDialog dialog = new EditTeamDialog(gitblit.getProtocolVersion(), + gitblit.getSettings()); + dialog.setLocationRelativeTo(TeamsPanel.this); + dialog.setTeams(gitblit.getTeams()); + dialog.setRepositories(gitblit.getRepositories(), null); + dialog.setUsers(gitblit.getUsernames(), null); + dialog.setVisible(true); + final TeamModel newTeam = dialog.getTeam(); + if (newTeam == null) { + return; + } + + GitblitWorker worker = new GitblitWorker(this, RpcRequest.CREATE_TEAM) { + + @Override + protected Boolean doRequest() throws IOException { + boolean success = gitblit.createTeam(newTeam); + if (success) { + gitblit.refreshTeams(); + gitblit.refreshUsers(); + } + return success; + } + + @Override + protected void onSuccess() { + updateTable(false); + updateUsersTable(); + } + + @Override + protected void onFailure() { + showFailure("Failed to execute request \"{0}\" for team \"{1}\".", + getRequestType(), newTeam.name); + } + }; + worker.execute(); + } + + /** + * Displays the edit team dialog and fires a SwingWorker to update the + * server, if appropriate. + * + * @param user + */ + protected void editTeam(final TeamModel team) { + EditTeamDialog dialog = new EditTeamDialog(gitblit.getProtocolVersion(), team, + gitblit.getSettings()); + dialog.setLocationRelativeTo(TeamsPanel.this); + dialog.setTeams(gitblit.getTeams()); + dialog.setRepositories(gitblit.getRepositories(), new ArrayList(team.repositories)); + dialog.setUsers(gitblit.getUsernames(), team.users == null ? null : new ArrayList( + team.users)); + dialog.setVisible(true); + final TeamModel revisedTeam = dialog.getTeam(); + if (revisedTeam == null) { + return; + } + + GitblitWorker worker = new GitblitWorker(this, RpcRequest.EDIT_TEAM) { + @Override + protected Boolean doRequest() throws IOException { + boolean success = gitblit.updateTeam(team.name, revisedTeam); + if (success) { + gitblit.refreshTeams(); + gitblit.refreshUsers(); + } + return success; + } + + @Override + protected void onSuccess() { + updateTable(false); + updateUsersTable(); + } + + @Override + protected void onFailure() { + showFailure("Failed to execute request \"{0}\" for team \"{1}\".", + getRequestType(), team.name); + } + }; + worker.execute(); + } + + protected void deleteTeams(final List teams) { + if (teams == null || teams.size() == 0) { + return; + } + StringBuilder message = new StringBuilder("Delete the following teams?\n\n"); + for (TeamModel team : teams) { + message.append(team.name).append("\n"); + } + int result = JOptionPane.showConfirmDialog(TeamsPanel.this, message.toString(), + "Delete Teams?", JOptionPane.YES_NO_OPTION); + if (result == JOptionPane.YES_OPTION) { + GitblitWorker worker = new GitblitWorker(this, RpcRequest.DELETE_TEAM) { + @Override + protected Boolean doRequest() throws IOException { + boolean success = true; + for (TeamModel team : teams) { + success &= gitblit.deleteTeam(team); + } + if (success) { + gitblit.refreshTeams(); + gitblit.refreshUsers(); + } + return success; + } + + @Override + protected void onSuccess() { + updateTable(false); + updateUsersTable(); + } + + @Override + protected void onFailure() { + showFailure("Failed to delete specified teams!"); + } + }; + worker.execute(); + } + } +} diff --git a/src/com/gitblit/client/TeamsTableModel.java b/src/com/gitblit/client/TeamsTableModel.java new file mode 100644 index 00000000..e6d8a945 --- /dev/null +++ b/src/com/gitblit/client/TeamsTableModel.java @@ -0,0 +1,105 @@ +/* + * Copyright 2011 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.client; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import javax.swing.table.AbstractTableModel; + +import com.gitblit.models.TeamModel; + +/** + * Table model of a list of teams. + * + * @author James Moger + * + */ +public class TeamsTableModel extends AbstractTableModel { + + private static final long serialVersionUID = 1L; + + List list; + + enum Columns { + Name, Members, Repositories; + + @Override + public String toString() { + return name().replace('_', ' '); + } + } + + public TeamsTableModel() { + this(new ArrayList()); + } + + public TeamsTableModel(List teams) { + this.list = teams; + Collections.sort(this.list); + } + + @Override + public int getRowCount() { + return list.size(); + } + + @Override + public int getColumnCount() { + return Columns.values().length; + } + + @Override + public String getColumnName(int column) { + Columns col = Columns.values()[column]; + switch (col) { + case Name: + return Translation.get("gb.name"); + case Members: + return Translation.get("gb.teamMembers"); + case Repositories: + return Translation.get("gb.repositories"); + } + return ""; + } + + /** + * Returns Object.class regardless of columnIndex. + * + * @param columnIndex + * the column being queried + * @return the Object.class + */ + public Class getColumnClass(int columnIndex) { + return String.class; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + TeamModel model = list.get(rowIndex); + Columns col = Columns.values()[columnIndex]; + switch (col) { + case Name: + return model.name; + case Members: + return model.users.size() == 0 ? "" : String.valueOf(model.users.size()); + case Repositories: + return model.repositories.size() == 0 ? "" : String.valueOf(model.repositories.size()); + } + return null; + } +} diff --git a/src/com/gitblit/client/UsersPanel.java b/src/com/gitblit/client/UsersPanel.java index 5d31774f..0dfa0434 100644 --- a/src/com/gitblit/client/UsersPanel.java +++ b/src/com/gitblit/client/UsersPanel.java @@ -41,6 +41,7 @@ import javax.swing.event.ListSelectionListener; import javax.swing.table.TableRowSorter; import com.gitblit.Constants.RpcRequest; +import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.utils.StringUtils; @@ -51,7 +52,7 @@ import com.gitblit.utils.StringUtils; * @author James Moger * */ -public class UsersPanel extends JPanel { +public abstract class UsersPanel extends JPanel { private static final long serialVersionUID = 1L; @@ -111,6 +112,18 @@ public class UsersPanel extends JPanel { String name = table.getColumnName(UsersTableModel.Columns.Name.ordinal()); table.setRowHeight(nameRenderer.getFont().getSize() + 8); table.getColumn(name).setCellRenderer(nameRenderer); + + int w = 125; + name = table.getColumnName(UsersTableModel.Columns.AccessLevel.ordinal()); + table.getColumn(name).setMinWidth(w); + table.getColumn(name).setMaxWidth(w); + name = table.getColumnName(UsersTableModel.Columns.Teams.ordinal()); + table.getColumn(name).setMinWidth(w); + table.getColumn(name).setMaxWidth(w); + name = table.getColumnName(UsersTableModel.Columns.Repositories.ordinal()); + table.getColumn(name).setMinWidth(w); + table.getColumn(name).setMaxWidth(w); + table.setRowSorter(defaultSorter); table.getRowSorter().toggleSortOrder(UsersTableModel.Columns.Name.ordinal()); table.getSelectionModel().addListSelectionListener(new ListSelectionListener() { @@ -167,7 +180,7 @@ public class UsersPanel extends JPanel { add(userTablePanel, BorderLayout.CENTER); add(userControls, BorderLayout.SOUTH); } - + @Override public void requestFocus() { filterTextfield.requestFocus(); @@ -178,6 +191,8 @@ public class UsersPanel extends JPanel { return Utils.INSETS; } + protected abstract void updateTeamsTable(); + protected void updateTable(boolean pack) { tableModel.list.clear(); tableModel.list.addAll(gitblit.getUsers()); @@ -240,10 +255,12 @@ public class UsersPanel extends JPanel { * */ protected void createUser() { - EditUserDialog dialog = new EditUserDialog(gitblit.getSettings()); + EditUserDialog dialog = new EditUserDialog(gitblit.getProtocolVersion(), + gitblit.getSettings()); dialog.setLocationRelativeTo(UsersPanel.this); dialog.setUsers(gitblit.getUsers()); dialog.setRepositories(gitblit.getRepositories(), null); + dialog.setTeams(gitblit.getTeams(), null); dialog.setVisible(true); final UserModel newUser = dialog.getUser(); if (newUser == null) { @@ -257,6 +274,9 @@ public class UsersPanel extends JPanel { boolean success = gitblit.createUser(newUser); if (success) { gitblit.refreshUsers(); + if (newUser.teams.size() > 0) { + gitblit.refreshTeams(); + } } return success; } @@ -264,6 +284,9 @@ public class UsersPanel extends JPanel { @Override protected void onSuccess() { updateTable(false); + if (newUser.teams.size() > 0) { + updateTeamsTable(); + } } @Override @@ -282,10 +305,13 @@ public class UsersPanel extends JPanel { * @param user */ protected void editUser(final UserModel user) { - EditUserDialog dialog = new EditUserDialog(user, gitblit.getSettings()); + EditUserDialog dialog = new EditUserDialog(gitblit.getProtocolVersion(), user, + gitblit.getSettings()); dialog.setLocationRelativeTo(UsersPanel.this); dialog.setUsers(gitblit.getUsers()); dialog.setRepositories(gitblit.getRepositories(), new ArrayList(user.repositories)); + dialog.setTeams(gitblit.getTeams(), user.teams == null ? null : new ArrayList( + user.teams)); dialog.setVisible(true); final UserModel revisedUser = dialog.getUser(); if (revisedUser == null) { @@ -298,6 +324,7 @@ public class UsersPanel extends JPanel { boolean success = gitblit.updateUser(user.username, revisedUser); if (success) { gitblit.refreshUsers(); + gitblit.refreshTeams(); } return success; } @@ -305,6 +332,7 @@ public class UsersPanel extends JPanel { @Override protected void onSuccess() { updateTable(false); + updateTeamsTable(); } @Override @@ -336,6 +364,7 @@ public class UsersPanel extends JPanel { } if (success) { gitblit.refreshUsers(); + gitblit.refreshTeams(); } return success; } @@ -343,6 +372,7 @@ public class UsersPanel extends JPanel { @Override protected void onSuccess() { updateTable(false); + updateTeamsTable(); } @Override diff --git a/src/com/gitblit/client/UsersTableModel.java b/src/com/gitblit/client/UsersTableModel.java index de282b8e..fa86a13e 100644 --- a/src/com/gitblit/client/UsersTableModel.java +++ b/src/com/gitblit/client/UsersTableModel.java @@ -36,7 +36,7 @@ public class UsersTableModel extends AbstractTableModel { List list; enum Columns { - Name, AccessLevel; + Name, AccessLevel, Teams, Repositories; @Override public String toString() { @@ -71,6 +71,10 @@ public class UsersTableModel extends AbstractTableModel { return Translation.get("gb.name"); case AccessLevel: return Translation.get("gb.accessLevel"); + case Teams: + return Translation.get("gb.teamMemberships"); + case Repositories: + return Translation.get("gb.repositories"); } return ""; } @@ -97,6 +101,13 @@ public class UsersTableModel extends AbstractTableModel { if (model.canAdmin) { return "administrator"; } + return ""; + case Teams: + return (model.teams == null || model.teams.size() == 0) ? "" : String + .valueOf(model.teams.size()); + case Repositories: + return (model.repositories == null || model.repositories.size() == 0) ? "" : String + .valueOf(model.repositories.size()); } return null; } diff --git a/src/com/gitblit/utils/RpcUtils.java b/src/com/gitblit/utils/RpcUtils.java index 387433a0..02a63a48 100644 --- a/src/com/gitblit/utils/RpcUtils.java +++ b/src/com/gitblit/utils/RpcUtils.java @@ -24,6 +24,7 @@ import java.util.Map; import com.gitblit.Constants; import com.gitblit.Constants.RpcRequest; +import com.gitblit.GitBlitException.UnknownRequestException; import com.gitblit.models.FederationModel; import com.gitblit.models.FederationProposal; import com.gitblit.models.FederationSet; @@ -31,6 +32,7 @@ import com.gitblit.models.FeedModel; import com.gitblit.models.RepositoryModel; import com.gitblit.models.ServerSettings; import com.gitblit.models.ServerStatus; +import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.google.gson.reflect.TypeToken; @@ -54,6 +56,9 @@ public class RpcUtils { private static final Type USERS_TYPE = new TypeToken>() { }.getType(); + private static final Type TEAMS_TYPE = new TypeToken>() { + }.getType(); + private static final Type REGISTRATIONS_TYPE = new TypeToken>() { }.getType(); @@ -96,7 +101,28 @@ public class RpcUtils { req = RpcRequest.LIST_REPOSITORIES; } return remoteURL + Constants.RPC_PATH + "?req=" + req.name().toLowerCase() - + (name == null ? "" : ("&name=" + name)); + + (name == null ? "" : ("&name=" + StringUtils.encodeURL(name))); + } + + /** + * Returns the version of the RPC protocol on the server. + * + * @param serverUrl + * @param account + * @param password + * @return the protocol version + * @throws IOException + */ + public static int getProtocolVersion(String serverUrl, String account, char[] password) + throws IOException { + String url = asLink(serverUrl, RpcRequest.GET_PROTOCOL); + int protocol = 1; + try { + protocol = JsonUtils.retrieveJson(url, Integer.class, account, password); + } catch (UnknownRequestException e) { + // v0.7.0 (protocol 1) did not have this request type + } + return protocol; } /** @@ -134,6 +160,24 @@ public class RpcUtils { return list; } + /** + * Tries to pull the gitblit team definitions from the remote gitblit + * instance. + * + * @param serverUrl + * @param account + * @param password + * @return a collection of UserModel objects + * @throws IOException + */ + public static List getTeams(String serverUrl, String account, char[] password) + throws IOException { + String url = asLink(serverUrl, RpcRequest.LIST_TEAMS); + Collection models = JsonUtils.retrieveJson(url, TEAMS_TYPE, account, password); + List list = new ArrayList(models); + return list; + } + /** * Create a repository on the Gitblit server. * @@ -235,6 +279,53 @@ public class RpcUtils { return doAction(RpcRequest.DELETE_USER, null, user, serverUrl, account, password); } + /** + * Create a team on the Gitblit server. + * + * @param team + * @param serverUrl + * @param account + * @param password + * @return true if the action succeeded + * @throws IOException + */ + public static boolean createTeam(TeamModel team, String serverUrl, String account, + char[] password) throws IOException { + return doAction(RpcRequest.CREATE_TEAM, null, team, serverUrl, account, password); + + } + + /** + * Send a revised version of the team model to the Gitblit server. + * + * @param team + * @param serverUrl + * @param account + * @param password + * @return true if the action succeeded + * @throws IOException + */ + public static boolean updateTeam(String teamname, TeamModel team, String serverUrl, + String account, char[] password) throws IOException { + return doAction(RpcRequest.EDIT_TEAM, teamname, team, serverUrl, account, password); + + } + + /** + * Deletes a team from the Gitblit server. + * + * @param team + * @param serverUrl + * @param account + * @param password + * @return true if the action succeeded + * @throws IOException + */ + public static boolean deleteTeam(TeamModel team, String serverUrl, String account, + char[] password) throws IOException { + return doAction(RpcRequest.DELETE_TEAM, null, team, serverUrl, account, password); + } + /** * Retrieves the list of users that can access the specified repository. * @@ -253,7 +344,7 @@ public class RpcUtils { } /** - * Sets the repository membership list. + * Sets the repository user membership list. * * @param repository * @param memberships @@ -270,6 +361,41 @@ public class RpcUtils { account, password); } + /** + * Retrieves the list of teams that can access the specified repository. + * + * @param repository + * @param serverUrl + * @param account + * @param password + * @return list of teams + * @throws IOException + */ + public static List getRepositoryTeams(RepositoryModel repository, String serverUrl, + String account, char[] password) throws IOException { + String url = asLink(serverUrl, RpcRequest.LIST_REPOSITORY_TEAMS, repository.name); + Collection list = JsonUtils.retrieveJson(url, NAMES_TYPE, account, password); + return new ArrayList(list); + } + + /** + * Sets the repository team membership list. + * + * @param repository + * @param teams + * @param serverUrl + * @param account + * @param password + * @return true if the action succeeded + * @throws IOException + */ + public static boolean setRepositoryTeams(RepositoryModel repository, + List teams, String serverUrl, String account, char[] password) + throws IOException { + return doAction(RpcRequest.SET_REPOSITORY_TEAMS, repository.name, teams, serverUrl, + account, password); + } + /** * Retrieves the list of federation registrations. These are the list of * registrations that this Gitblit instance is pulling from. diff --git a/tests/com/gitblit/tests/RpcTests.java b/tests/com/gitblit/tests/RpcTests.java index 30dc84dc..123dd972 100644 --- a/tests/com/gitblit/tests/RpcTests.java +++ b/tests/com/gitblit/tests/RpcTests.java @@ -21,6 +21,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import java.io.IOException; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; @@ -34,12 +35,14 @@ import org.junit.Test; import com.gitblit.Constants.AccessRestrictionType; import com.gitblit.GitBlitException.UnauthorizedException; import com.gitblit.Keys; +import com.gitblit.RpcServlet; import com.gitblit.models.FederationModel; import com.gitblit.models.FederationProposal; import com.gitblit.models.FederationSet; import com.gitblit.models.RepositoryModel; import com.gitblit.models.ServerSettings; import com.gitblit.models.ServerStatus; +import com.gitblit.models.TeamModel; import com.gitblit.models.UserModel; import com.gitblit.utils.RpcUtils; @@ -69,6 +72,12 @@ public class RpcTests { } } + @Test + public void testGetProtocolVersion() throws IOException { + int protocol = RpcUtils.getProtocolVersion(url, null, null); + assertEquals(RpcServlet.PROTOCOL_VERSION, protocol); + } + @Test public void testListRepositories() throws IOException { Map map = RpcUtils.getRepositories(url, null, null); @@ -89,6 +98,20 @@ public class RpcTests { assertTrue("User list is empty!", list.size() > 0); } + @Test + public void testListTeams() throws IOException { + List list = null; + try { + list = RpcUtils.getTeams(url, null, null); + } catch (UnauthorizedException e) { + } + assertNull("Server allows anyone to admin!", list); + + list = RpcUtils.getTeams(url, "admin", "admin".toCharArray()); + assertTrue("Team list is empty!", list.size() > 0); + assertEquals("admins", list.get(0).name); + } + @Test public void testUserAdministration() throws IOException { UserModel user = new UserModel("garbage"); @@ -213,6 +236,61 @@ public class RpcTests { return retrievedRepository; } + @Test + public void testTeamAdministration() throws IOException { + List teams = RpcUtils.getTeams(url, account, password.toCharArray()); + assertEquals(1, teams.size()); + + // Create the A-Team + TeamModel aTeam = new TeamModel("A-Team"); + aTeam.users.add("admin"); + aTeam.repositories.add("helloworld.git"); + assertTrue(RpcUtils.createTeam(aTeam, url, account, password.toCharArray())); + + aTeam = null; + teams = RpcUtils.getTeams(url, account, password.toCharArray()); + assertEquals(2, teams.size()); + for (TeamModel team : teams) { + if (team.name.equals("A-Team")) { + aTeam = team; + break; + } + } + assertNotNull(aTeam); + assertTrue(aTeam.hasUser("admin")); + assertTrue(aTeam.hasRepository("helloworld.git")); + + RepositoryModel helloworld = null; + Map repositories = RpcUtils.getRepositories(url, account, + password.toCharArray()); + for (RepositoryModel repository : repositories.values()) { + if (repository.name.equals("helloworld.git")) { + helloworld = repository; + break; + } + } + assertNotNull(helloworld); + + // Confirm that we have added the team + List helloworldTeams = RpcUtils.getRepositoryTeams(helloworld, url, account, + password.toCharArray()); + assertEquals(1, helloworldTeams.size()); + assertTrue(helloworldTeams.contains(aTeam.name)); + + // set no teams + assertTrue(RpcUtils.setRepositoryTeams(helloworld, new ArrayList(), url, account, + password.toCharArray())); + helloworldTeams = RpcUtils.getRepositoryTeams(helloworld, url, account, + password.toCharArray()); + assertEquals(0, helloworldTeams.size()); + + // delete the A-Team + assertTrue(RpcUtils.deleteTeam(aTeam, url, account, password.toCharArray())); + + teams = RpcUtils.getTeams(url, account, password.toCharArray()); + assertEquals(1, teams.size()); + } + @Test public void testFederationRegistrations() throws Exception { List registrations = RpcUtils.getFederationRegistrations(url, account, -- 2.39.5