From: James Moger Date: Thu, 13 Oct 2011 02:14:10 +0000 (-0400) Subject: RPC Client: Create/Edit Repository & User. Partially working. X-Git-Tag: v0.7.0~83 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=da0269b4bd57bf90877446d9f991247bc1ad2f64;p=gitblit.git RPC Client: Create/Edit Repository & User. Partially working. Added new request type to retrieve Gitblit settings to implement the password preferences (minLength, storage type) and federation sets. --- diff --git a/docs/02_rpc.mkd b/docs/02_rpc.mkd index c3c59e62..4a547d00 100644 --- a/docs/02_rpc.mkd +++ b/docs/02_rpc.mkd @@ -30,6 +30,7 @@ The Gitblit RPC mechanism, like the Gitblit JGit servlet, syndication/feed servl LIST_FEDERATION_RESULTS-admin-List FederationModel LIST_FEDERATION_PROPOSALS-admin-List FederationProposal LIST_FEDERATION_SETS-admin-List FederationSet +LIST_SETTINGS-admin-Properties ### RPC Client diff --git a/src/com/gitblit/Constants.java b/src/com/gitblit/Constants.java index 52af3e91..6300fa2e 100644 --- a/src/com/gitblit/Constants.java +++ b/src/com/gitblit/Constants.java @@ -204,7 +204,7 @@ public class Constants { LIST_REPOSITORIES, 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; + LIST_FEDERATION_PROPOSALS, LIST_FEDERATION_SETS, LIST_SETTINGS; public static RpcRequest fromName(String name) { for (RpcRequest type : values()) { diff --git a/src/com/gitblit/RpcServlet.java b/src/com/gitblit/RpcServlet.java index 4dee3190..de5d94e0 100644 --- a/src/com/gitblit/RpcServlet.java +++ b/src/com/gitblit/RpcServlet.java @@ -22,6 +22,7 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Properties; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -163,6 +164,17 @@ public class RpcServlet extends JsonServlet { } else { response.sendError(HttpServletResponse.SC_FORBIDDEN); } + } else if (RpcRequest.LIST_SETTINGS.equals(reqType)) { + // return the server's settings + Properties settings = new Properties(); + List keys = GitBlit.getAllKeys(null); + for (String key:keys) { + String value = GitBlit.getString(key, null); + if (value != null) { + settings.put(key, value); + } + } + result = settings; } // send the result of the request diff --git a/src/com/gitblit/client/EditRepositoryDialog.java b/src/com/gitblit/client/EditRepositoryDialog.java new file mode 100644 index 00000000..756128a3 --- /dev/null +++ b/src/com/gitblit/client/EditRepositoryDialog.java @@ -0,0 +1,224 @@ +/* + * 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.util.List; + +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComboBox; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JTextField; + +import com.gitblit.Constants.AccessRestrictionType; +import com.gitblit.Constants.FederationStrategy; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.UserModel; +import com.gitblit.utils.StringUtils; + +public class EditRepositoryDialog extends JDialog { + + private static final long serialVersionUID = 1L; + + private final RepositoryModel repository; + + private boolean canceled = true; + + private JTextField nameField; + + private JTextField descriptionField; + + private JCheckBox useTickets; + + private JCheckBox useDocs; + + private JCheckBox showRemoteBranches; + + private JCheckBox showReadme; + + private JCheckBox isFrozen; + + private JComboBox accessRestriction; + + private JComboBox federationStrategy; + + private JComboBox owner; + + private JPalette usersPalette; + + private JPalette setsPalette; + + public EditRepositoryDialog(List allusers) { + this(new RepositoryModel(), allusers); + setTitle("Create Repository"); + } + + public EditRepositoryDialog(RepositoryModel aRepository, List allUsers) { + super(); + this.repository = new RepositoryModel(); + initialize(aRepository, allUsers); + setModal(true); + setTitle("Edit Repository: " + aRepository.name); + setIconImage(new ImageIcon(getClass().getResource("/gitblt-favicon.png")).getImage()); + } + + private void initialize(RepositoryModel anRepository, List allUsers) { + nameField = new JTextField(anRepository.name == null ? "" : anRepository.name, 35); + descriptionField = new JTextField(anRepository.description == null ? "" + : anRepository.description, 35); + + owner = new JComboBox(allUsers.toArray()); + if (!StringUtils.isEmpty(anRepository.owner)) { + UserModel currentOwner = null; + for (UserModel user : allUsers) { + if (user.username.equalsIgnoreCase(anRepository.owner)) { + currentOwner = user; + break; + } + } + owner.setSelectedItem(currentOwner); + } + + useTickets = new JCheckBox("distributed Ticgit issues", anRepository.useTickets); + useDocs = new JCheckBox("enumerates Markdown documentation in repository", + anRepository.useDocs); + showRemoteBranches = new JCheckBox("show remote branches", anRepository.showRemoteBranches); + showReadme = new JCheckBox("show a \"readme\" Markdown file on the summary page", + anRepository.showReadme); + isFrozen = new JCheckBox("deny push operations", anRepository.isFrozen); + + accessRestriction = new JComboBox(AccessRestrictionType.values()); + accessRestriction.setSelectedItem(anRepository.accessRestriction); + + federationStrategy = new JComboBox(FederationStrategy.values()); + federationStrategy.setSelectedItem(anRepository.federationStrategy); + + JPanel fieldsPanel = new JPanel(new GridLayout(0, 1)); + fieldsPanel.add(newFieldPanel("name", nameField)); + fieldsPanel.add(newFieldPanel("description", descriptionField)); + fieldsPanel.add(newFieldPanel("owner", owner)); + + fieldsPanel.add(newFieldPanel("enable tickets", useTickets)); + fieldsPanel.add(newFieldPanel("enable docs", useDocs)); + fieldsPanel.add(newFieldPanel("show remote branches", showRemoteBranches)); + fieldsPanel.add(newFieldPanel("show readme", showReadme)); + fieldsPanel.add(newFieldPanel("is frozen", isFrozen)); + + usersPalette = new JPalette(); + JPanel accessPanel = new JPanel(new BorderLayout(5, 5)); + accessPanel.add(newFieldPanel("access restriction", accessRestriction), BorderLayout.NORTH); + accessPanel.add(newFieldPanel("permitted users", usersPalette), BorderLayout.CENTER); + + setsPalette = new JPalette(); + JPanel federationPanel = new JPanel(new BorderLayout(5, 5)); + federationPanel.add(newFieldPanel("federation strategy", federationStrategy), + BorderLayout.NORTH); + federationPanel.add(newFieldPanel("federation sets", setsPalette), BorderLayout.CENTER); + + JPanel panel = new JPanel(new BorderLayout(5, 5)); + panel.add(fieldsPanel, BorderLayout.NORTH); + panel.add(accessPanel, BorderLayout.CENTER); + panel.add(federationPanel, BorderLayout.SOUTH); + + JButton createButton = new JButton("Save"); + createButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent event) { + if (validateFields()) { + canceled = false; + setVisible(false); + } + } + }); + + JButton cancelButton = new JButton("Cancel"); + cancelButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent event) { + canceled = true; + setVisible(false); + } + }); + + 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; + + @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(); + setLocationRelativeTo(null); + } + + 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() { + // TODO validate input and populate model + return true; + } + + private void showValidationError(String message) { + JOptionPane.showMessageDialog(EditRepositoryDialog.this, message, "Validation Error", + JOptionPane.ERROR_MESSAGE); + } + + public void setUsers(List all, List selected) { + usersPalette.setObjects(all, selected); + } + + public void setFederationSets(List all, List selected) { + setsPalette.setObjects(all, selected); + } + + public RepositoryModel getRepository() { + if (canceled) { + return null; + } + return repository; + } +} diff --git a/src/com/gitblit/client/EditUserDialog.java b/src/com/gitblit/client/EditUserDialog.java new file mode 100644 index 00000000..5fce8c54 --- /dev/null +++ b/src/com/gitblit/client/EditUserDialog.java @@ -0,0 +1,237 @@ +/* + * 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.text.MessageFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.swing.ImageIcon; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JComponent; +import javax.swing.JDialog; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JTextField; + +import com.gitblit.Constants.AccessRestrictionType; +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; +import com.gitblit.models.RepositoryModel; +import com.gitblit.models.UserModel; +import com.gitblit.utils.StringUtils; + +public class EditUserDialog extends JDialog { + + private static final long serialVersionUID = 1L; + + private final UserModel user; + + private final IStoredSettings settings; + + private boolean canceled = true; + + private JTextField usernameField; + + private JPasswordField passwordField; + + private JPasswordField confirmPasswordField; + + private JCheckBox canAdminCheckbox; + + private JCheckBox notFederatedCheckbox; + + private JPalette repositoryPalette; + + public EditUserDialog(IStoredSettings settings) { + this(new UserModel(""), settings); + setTitle("Create User"); + } + + public EditUserDialog(UserModel anUser, IStoredSettings settings) { + super(); + this.user = new UserModel(""); + this.settings = settings; + initialize(anUser); + setModal(true); + setTitle("Edit User: " + anUser.username); + setIconImage(new ImageIcon(getClass().getResource("/gitblt-favicon.png")).getImage()); + } + + private void initialize(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, + 25); + canAdminCheckbox = new JCheckBox("can administer Gitblit server", anUser.canAdmin); + notFederatedCheckbox = new JCheckBox( + "block federated Gitblit instances from pulling this account", + anUser.excludeFromFederation); + + JPanel fieldsPanel = new JPanel(new GridLayout(0, 1)); + fieldsPanel.add(newFieldPanel("username", usernameField)); + fieldsPanel.add(newFieldPanel("password", passwordField)); + fieldsPanel.add(newFieldPanel("confirm password", confirmPasswordField)); + fieldsPanel.add(newFieldPanel("can admin", canAdminCheckbox)); + fieldsPanel.add(newFieldPanel("exclude from federation", notFederatedCheckbox)); + + repositoryPalette = new JPalette(); + JPanel panel = new JPanel(new BorderLayout()); + panel.add(fieldsPanel, BorderLayout.NORTH); + panel.add(newFieldPanel("restricted repositories", repositoryPalette), BorderLayout.CENTER); + + JButton createButton = new JButton("Save"); + createButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent event) { + if (validateFields()) { + canceled = false; + setVisible(false); + } + } + }); + + JButton cancelButton = new JButton("Cancel"); + cancelButton.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent event) { + canceled = true; + setVisible(false); + } + }); + + 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; + + @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(); + setLocationRelativeTo(null); + } + + 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 uname = usernameField.getText(); + if (StringUtils.isEmpty(uname)) { + showValidationError("Please enter a username!"); + return false; + } + + // TODO verify username uniqueness on create + + // if (isCreate) { + // UserModel model = GitBlit.self().getUserModel(username); + // if (model != null) { + // error(MessageFormat.format("Username ''{0}'' is unavailable.", + // username)); + // return; + // } + // } + + int minLength = settings.getInteger(Keys.realm.minPasswordLength, 5); + if (minLength < 4) { + minLength = 4; + } + char[] pw = passwordField.getPassword(); + if (pw == null || pw.length < minLength) { + showValidationError(MessageFormat.format( + "Password is too short. Minimum length is {0} characters.", minLength)); + return false; + } + char[] cpw = confirmPasswordField.getPassword(); + if (cpw == null || cpw.length != pw.length) { + showValidationError("Please confirm the password!"); + return false; + } + if (!Arrays.equals(pw, cpw)) { + showValidationError("Passwords do not match!"); + return false; + } + user.username = uname; + String type = settings.getString(Keys.realm.passwordStorage, "md5"); + if (type.equalsIgnoreCase("md5")) { + // store MD5 digest of password + user.password = StringUtils.MD5_TYPE + StringUtils.getMD5(new String(pw)); + } else { + user.password = new String(pw); + } + user.canAdmin = canAdminCheckbox.isSelected(); + user.excludeFromFederation = notFederatedCheckbox.isSelected(); + + user.repositories.clear(); + user.repositories.addAll(repositoryPalette.getSelections()); + return true; + } + + private void showValidationError(String message) { + JOptionPane.showMessageDialog(EditUserDialog.this, message, "Validation Error", + JOptionPane.ERROR_MESSAGE); + } + + 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 UserModel getUser() { + if (canceled) { + return null; + } + return user; + } +} diff --git a/src/com/gitblit/client/GitblitClient.java b/src/com/gitblit/client/GitblitClient.java index 51d8e7eb..65a8c36a 100644 --- a/src/com/gitblit/client/GitblitClient.java +++ b/src/com/gitblit/client/GitblitClient.java @@ -148,13 +148,13 @@ public class GitblitClient extends JFrame { } reg = new GitblitRegistration(nameField.getText(), url, accountField.getText(), passwordField.getPassword()); - login(reg); + boolean success = login(reg); registrations.add(0, reg); rebuildRecentMenu(); - return true; + return success; } - private void login(GitblitRegistration reg) { + private boolean login(GitblitRegistration reg) { try { GitblitPanel panel = new GitblitPanel(reg); panel.login(); @@ -163,10 +163,12 @@ public class GitblitClient extends JFrame { serverTabs.setSelectedIndex(idx); serverTabs.setTabComponentAt(idx, new ClosableTabComponent(reg.name, null, serverTabs, panel)); + return true; } catch (IOException e) { JOptionPane.showMessageDialog(GitblitClient.this, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); } + return false; } private void rebuildRecentMenu() { diff --git a/src/com/gitblit/client/GitblitClientLauncher.java b/src/com/gitblit/client/GitblitClientLauncher.java index 19e9efd8..24832d19 100644 --- a/src/com/gitblit/client/GitblitClientLauncher.java +++ b/src/com/gitblit/client/GitblitClientLauncher.java @@ -25,6 +25,7 @@ import java.io.IOException; import java.util.Collections; import java.util.List; +import com.gitblit.Constants; import com.gitblit.Launcher; import com.gitblit.build.Build; import com.gitblit.build.Build.DownloadListener; @@ -82,6 +83,8 @@ public class GitblitClientLauncher { if (g != null) { // Splash is 320x120 FontMetrics fm = g.getFontMetrics(); + + // paint startup status g.setColor(Color.darkGray); int h = fm.getHeight() + fm.getMaxDescent(); int x = 5; @@ -93,6 +96,11 @@ public class GitblitClientLauncher { g.setColor(Color.WHITE); int xw = fm.stringWidth(string); g.drawString(string, x + ((w - xw) / 2), y - 5); + + // paint version + String ver = "v" + Constants.VERSION; + int vw = g.getFontMetrics().stringWidth(ver); + g.drawString(ver, 320 - vw - 5, 34); g.dispose(); splash.update(); } diff --git a/src/com/gitblit/client/GitblitPanel.java b/src/com/gitblit/client/GitblitPanel.java index 5482593d..d29f2a90 100644 --- a/src/com/gitblit/client/GitblitPanel.java +++ b/src/com/gitblit/client/GitblitPanel.java @@ -20,6 +20,7 @@ import java.awt.Color; import java.awt.Component; import java.awt.Desktop; import java.awt.Dimension; +import java.awt.GridLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -27,6 +28,7 @@ import java.io.IOException; import java.net.URI; import java.text.MessageFormat; import java.util.ArrayList; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; @@ -34,6 +36,7 @@ import java.util.Map; import javax.swing.JButton; import javax.swing.JLabel; import javax.swing.JList; +import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; @@ -41,6 +44,7 @@ import javax.swing.JTable; import javax.swing.JTextField; import javax.swing.RowFilter; import javax.swing.SwingConstants; +import javax.swing.SwingWorker; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.table.DefaultTableCellRenderer; @@ -49,7 +53,11 @@ import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; import javax.swing.table.TableRowSorter; +import com.gitblit.Constants.RpcRequest; import com.gitblit.GitBlitException.ForbiddenException; +import com.gitblit.GitBlitException.UnauthorizedException; +import com.gitblit.IStoredSettings; +import com.gitblit.Keys; import com.gitblit.client.ClosableTabComponent.CloseTabListener; import com.gitblit.models.FederationModel; import com.gitblit.models.RepositoryModel; @@ -72,13 +80,17 @@ public class GitblitPanel extends JPanel implements CloseTabListener { private final Insets insets = new Insets(margin, margin, margin, margin); - private String url; + private final String url; - private String account; + private final String account; - private char[] password; + private final char[] password; - private boolean isAdmin; + private volatile boolean isAdmin; + + private volatile List allUsers; + + private volatile IStoredSettings settings; private JTabbedPane tabs; @@ -104,6 +116,8 @@ public class GitblitPanel extends JPanel implements CloseTabListener { private TableRowSorter defaultSorter; + private List allRepositories; + public GitblitPanel(GitblitRegistration reg) { this(reg.url, reg.account, reg.password); } @@ -128,10 +142,25 @@ public class GitblitPanel extends JPanel implements CloseTabListener { } }); + JButton refreshRepositories = new JButton("Refresh"); + refreshRepositories.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + try { + refreshRepositoriesTable(); + } catch (ForbiddenException x) { + explainForbidden(RpcRequest.LIST_REPOSITORIES); + } catch (UnauthorizedException x) { + explainUnauthorized(RpcRequest.LIST_REPOSITORIES); + } catch (Throwable t) { + showException(t); + } + } + }); + createRepository = new JButton("Create"); createRepository.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - System.out.println("TODO Create Repository"); + createRepository(); } }); @@ -139,9 +168,7 @@ public class GitblitPanel extends JPanel implements CloseTabListener { editRepository.setEnabled(false); editRepository.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - for (RepositoryModel model : getSelectedRepositories()) { - System.out.println("TODO Edit " + model); - } + editRepository(getSelectedRepositories().get(0)); } }); @@ -149,9 +176,7 @@ public class GitblitPanel extends JPanel implements CloseTabListener { delRepository.setEnabled(false); delRepository.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - for (RepositoryModel model : getSelectedRepositories()) { - System.out.println("TODO Delete " + model); - } + deleteRepositories(getSelectedRepositories()); } }); @@ -165,7 +190,7 @@ public class GitblitPanel extends JPanel implements CloseTabListener { } }); - nameRenderer = new NameRenderer(Color.gray, new Color(0x00, 0x69, 0xD6)); + nameRenderer = new NameRenderer(); typeRenderer = new TypeRenderer(); sizeRenderer = new DefaultTableCellRenderer(); @@ -246,10 +271,25 @@ public class GitblitPanel extends JPanel implements CloseTabListener { repositoriesPanel.add(tablePanel, BorderLayout.CENTER); repositoriesPanel.add(repositoryControls, BorderLayout.SOUTH); + JButton refreshUsers = new JButton("Refresh"); + refreshUsers.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + try { + refreshUsersTable(); + } catch (ForbiddenException x) { + explainForbidden(RpcRequest.LIST_USERS); + } catch (UnauthorizedException x) { + explainUnauthorized(RpcRequest.LIST_USERS); + } catch (Throwable t) { + showException(t); + } + } + }); + JButton createUser = new JButton("Create"); createUser.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - System.out.println("TODO Create User"); + createUser(); } }); @@ -257,9 +297,7 @@ public class GitblitPanel extends JPanel implements CloseTabListener { editUser.setEnabled(false); editUser.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - for (UserModel user : getSelectedUsers()) { - System.out.println("TODO Edit " + user); - } + editUser(getSelectedUsers().get(0)); } }); @@ -267,9 +305,7 @@ public class GitblitPanel extends JPanel implements CloseTabListener { delUser.setEnabled(false); delUser.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { - for (UserModel user : getSelectedUsers()) { - System.out.println("TODO Delete " + user); - } + deleteUsers(getSelectedUsers()); } }); @@ -288,7 +324,8 @@ public class GitblitPanel extends JPanel implements CloseTabListener { } }); - JPanel userControls = new JPanel(); + JPanel userControls = new JPanel(new GridLayout(0, 2)); + userControls.add(refreshUsers); userControls.add(createUser); userControls.add(editUser); userControls.add(delUser); @@ -327,6 +364,7 @@ public class GitblitPanel extends JPanel implements CloseTabListener { try { refreshUsersTable(); + refreshSettings(); isAdmin = true; refreshFederationPanel(); } catch (ForbiddenException e) { @@ -348,8 +386,10 @@ public class GitblitPanel extends JPanel implements CloseTabListener { private void refreshRepositoriesTable() throws IOException { Map repositories = RpcUtils .getRepositories(url, account, password); + allRepositories = new ArrayList(repositories.values()); + Collections.sort(allRepositories); repositoriesModel.list.clear(); - repositoriesModel.list.addAll(repositories.values()); + repositoriesModel.list.addAll(allRepositories); repositoriesModel.fireTableDataChanged(); packColumns(repositoriesTable, 2); } @@ -360,8 +400,12 @@ public class GitblitPanel extends JPanel implements CloseTabListener { } private void refreshUsersTable() throws IOException { - List users = RpcUtils.getUsers(url, account, password); - usersList.setListData(users.toArray()); + allUsers = RpcUtils.getUsers(url, account, password); + usersList.setListData(allUsers.toArray()); + } + + private void refreshSettings() throws IOException { + settings = RpcUtils.getSettings(url, account, password); } private void refreshFederationPanel() throws IOException { @@ -465,4 +509,313 @@ public class GitblitPanel extends JPanel implements CloseTabListener { @Override public void closeTab(Component c) { } + + /** + * Displays the create repository dialog and fires a SwingWorker to update + * the server, if appropriate. + * + */ + protected void createRepository() { + EditRepositoryDialog dialog = new EditRepositoryDialog(allUsers); + dialog.setVisible(true); + final RepositoryModel newRepository = dialog.getRepository(); + if (newRepository == null) { + return; + } + + final RpcRequest request = RpcRequest.CREATE_REPOSITORY; + SwingWorker worker = new SwingWorker() { + + @Override + protected Boolean doInBackground() throws IOException { + return RpcUtils.createRepository(newRepository, url, account, password); + } + + @Override + protected void done() { + try { + boolean success = get(); + if (success) { + refreshRepositoriesTable(); + } else { + String msg = MessageFormat.format( + "Failed to execute request \"{0}\" for repository \"{1}\".", + request.name(), newRepository.name); + JOptionPane.showMessageDialog(GitblitPanel.this, msg, "Error!", + JOptionPane.ERROR_MESSAGE); + } + } catch (ForbiddenException e) { + explainForbidden(request); + } catch (UnauthorizedException e) { + explainUnauthorized(request); + } catch (Throwable t) { + showException(t); + } + } + }; + worker.execute(); + } + + /** + * Displays the edit repository dialog and fires a SwingWorker to update the + * server, if appropriate. + * + * @param repository + */ + protected void editRepository(final RepositoryModel repository) { + EditRepositoryDialog dialog = new EditRepositoryDialog(repository, allUsers); + List usernames = new ArrayList(); + for (UserModel user : this.allUsers) { + usernames.add(user.username); + } + Collections.sort(usernames); + dialog.setUsers(usernames, null); + dialog.setFederationSets(settings.getStrings(Keys.federation.sets), + repository.federationSets); + dialog.setVisible(true); + final RepositoryModel revisedRepository = dialog.getRepository(); + if (revisedRepository == null) { + return; + } + + final RpcRequest request = RpcRequest.EDIT_REPOSITORY; + SwingWorker worker = new SwingWorker() { + + @Override + protected Boolean doInBackground() throws IOException { + return RpcUtils.updateRepository(repository.name, revisedRepository, url, account, + password); + } + + @Override + protected void done() { + try { + boolean success = get(); + if (success) { + refreshRepositoriesTable(); + } else { + String msg = MessageFormat.format( + "Failed to execute request \"{0}\" for repository \"{1}\".", + request.name(), repository.name); + JOptionPane.showMessageDialog(GitblitPanel.this, msg, "Error!", + JOptionPane.ERROR_MESSAGE); + } + } catch (ForbiddenException e) { + explainForbidden(request); + } catch (UnauthorizedException e) { + explainUnauthorized(request); + } catch (Throwable t) { + showException(t); + } + } + }; + worker.execute(); + } + + protected void deleteRepositories(final List repositories) { + if (repositories == null || repositories.size() == 0) { + return; + } + StringBuilder message = new StringBuilder("Delete the following repositories?\n\n"); + for (RepositoryModel repository : repositories) { + message.append(repository.name).append("\n"); + } + int result = JOptionPane.showConfirmDialog(GitblitPanel.this, message.toString(), + "Delete Repositories?", JOptionPane.YES_NO_OPTION); + if (result == JOptionPane.YES_OPTION) { + final RpcRequest request = RpcRequest.DELETE_REPOSITORY; + SwingWorker worker = new SwingWorker() { + @Override + protected Boolean doInBackground() throws Exception { + boolean success = true; + for (RepositoryModel repository : repositories) { + success &= RpcUtils.deleteRepository(repository, url, account, password); + } + return success; + } + + @Override + protected void done() { + try { + boolean success = get(); + if (success) { + refreshRepositoriesTable(); + } else { + String msg = "Failed to delete specified repositories!"; + JOptionPane.showMessageDialog(GitblitPanel.this, msg, "Error!", + JOptionPane.ERROR_MESSAGE); + } + } catch (ForbiddenException e) { + explainForbidden(request); + } catch (UnauthorizedException e) { + explainUnauthorized(request); + } catch (Throwable t) { + showException(t); + } + } + }; + worker.execute(); + } + } + + /** + * Displays the create user dialog and fires a SwingWorker to update the + * server, if appropriate. + * + */ + protected void createUser() { + EditUserDialog dialog = new EditUserDialog(settings); + dialog.setRepositories(allRepositories, null); + dialog.setVisible(true); + final UserModel newUser = dialog.getUser(); + if (newUser == null) { + return; + } + + final RpcRequest request = RpcRequest.CREATE_USER; + SwingWorker worker = new SwingWorker() { + + @Override + protected Boolean doInBackground() throws IOException { + return RpcUtils.createUser(newUser, url, account, password); + } + + @Override + protected void done() { + try { + boolean success = get(); + if (success) { + refreshUsersTable(); + } else { + String msg = MessageFormat.format( + "Failed to execute request \"{0}\" for user \"{1}\".", + request.name(), newUser.username); + JOptionPane.showMessageDialog(GitblitPanel.this, msg, "Error!", + JOptionPane.ERROR_MESSAGE); + } + } catch (ForbiddenException e) { + explainForbidden(request); + } catch (UnauthorizedException e) { + explainUnauthorized(request); + } catch (Throwable t) { + showException(t); + } + } + }; + worker.execute(); + } + + /** + * Displays the edit user dialog and fires a SwingWorker to update the + * server, if appropriate. + * + * @param user + */ + protected void editUser(final UserModel user) { + EditUserDialog dialog = new EditUserDialog(user, settings); + dialog.setRepositories(allRepositories, new ArrayList(user.repositories)); + dialog.setVisible(true); + final UserModel revisedUser = dialog.getUser(); + if (revisedUser == null) { + return; + } + + final RpcRequest request = RpcRequest.EDIT_USER; + SwingWorker worker = new SwingWorker() { + + @Override + protected Boolean doInBackground() throws IOException { + return RpcUtils.updateUser(user.username, revisedUser, url, account, password); + } + + @Override + protected void done() { + try { + boolean success = get(); + if (success) { + refreshUsersTable(); + } else { + String msg = MessageFormat.format( + "Failed to execute request \"{0}\" for user \"{1}\".", + request.name(), user.username); + JOptionPane.showMessageDialog(GitblitPanel.this, msg, "Error!", + JOptionPane.ERROR_MESSAGE); + } + } catch (ForbiddenException e) { + explainForbidden(request); + } catch (UnauthorizedException e) { + explainUnauthorized(request); + } catch (Throwable t) { + showException(t); + } + } + }; + worker.execute(); + } + + protected void deleteUsers(final List users) { + if (users == null || users.size() == 0) { + return; + } + StringBuilder message = new StringBuilder("Delete the following users?\n\n"); + for (UserModel user : users) { + message.append(user.username).append("\n"); + } + int result = JOptionPane.showConfirmDialog(GitblitPanel.this, message.toString(), + "Delete Users?", JOptionPane.YES_NO_OPTION); + if (result == JOptionPane.YES_OPTION) { + final RpcRequest request = RpcRequest.DELETE_USER; + SwingWorker worker = new SwingWorker() { + @Override + protected Boolean doInBackground() throws Exception { + boolean success = true; + for (UserModel user : users) { + success &= RpcUtils.deleteUser(user, url, account, password); + } + return success; + } + + @Override + protected void done() { + try { + boolean success = get(); + if (success) { + refreshUsersTable(); + } else { + String msg = "Failed to delete specified users!"; + JOptionPane.showMessageDialog(GitblitPanel.this, msg, "Error!", + JOptionPane.ERROR_MESSAGE); + } + } catch (ForbiddenException e) { + explainForbidden(request); + } catch (UnauthorizedException e) { + explainUnauthorized(request); + } catch (Throwable t) { + showException(t); + } + } + }; + worker.execute(); + } + } + + private void explainForbidden(RpcRequest request) { + String msg = MessageFormat.format( + "The request \"{0}\" has been forbidden by the Gitblit server @ {1}.", + request.name(), url); + JOptionPane.showMessageDialog(GitblitPanel.this, msg, "Forbidden", + JOptionPane.ERROR_MESSAGE); + } + + private void explainUnauthorized(RpcRequest request) { + String msg = MessageFormat.format( + "The account \"{0}\" is not authorized to execute the request \"{1}\".", account, + request.name()); + JOptionPane.showMessageDialog(GitblitPanel.this, msg, "Unauthorized", + JOptionPane.ERROR_MESSAGE); + } + + private void showException(Throwable t) { + // TODO show the unexpected exception + } } diff --git a/src/com/gitblit/client/JPalette.java b/src/com/gitblit/client/JPalette.java new file mode 100644 index 00000000..19456e1b --- /dev/null +++ b/src/com/gitblit/client/JPalette.java @@ -0,0 +1,177 @@ +/* + * 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.Color; +import java.awt.Dimension; +import java.awt.GridBagLayout; +import java.awt.GridLayout; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.util.ArrayList; +import java.util.List; + +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JTable; +import javax.swing.table.AbstractTableModel; + +public class JPalette extends JPanel { + + private static final long serialVersionUID = 1L; + private PaletteModel availableModel; + private PaletteModel selectedModel; + + public JPalette() { + super(new BorderLayout(5, 5)); + + availableModel = new PaletteModel(); + selectedModel = new PaletteModel(); + + final JTable available = new JTable(availableModel); + final JTable selected = new JTable(selectedModel); + + JButton add = new JButton("->"); + add.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent event) { + List move = new ArrayList(); + if (available.getSelectedRowCount() <= 0) { + return; + } + for (int row : available.getSelectedRows()) { + int modelIndex = available.convertRowIndexToModel(row); + T item = (T) availableModel.list.get(modelIndex); + move.add(item); + } + availableModel.list.removeAll(move); + selectedModel.list.addAll(move); + availableModel.fireTableDataChanged(); + selectedModel.fireTableDataChanged(); + } + }); + JButton subtract = new JButton("<-"); + subtract.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent event) { + List move = new ArrayList(); + if (selected.getSelectedRowCount() <= 0) { + return; + } + for (int row : selected.getSelectedRows()) { + int modelIndex = selected.convertRowIndexToModel(row); + T item = (T) selectedModel.list.get(modelIndex); + move.add(item); + } + selectedModel.list.removeAll(move); + availableModel.list.addAll(move); + + selectedModel.fireTableDataChanged(); + availableModel.fireTableDataChanged(); + } + }); + + JPanel controls = new JPanel(new GridLayout(0, 1, 0, 5)); + controls.add(add); + controls.add(subtract); + + JPanel center = new JPanel(new GridBagLayout()); + center.add(controls); + + add(newListPanel("Available", available), BorderLayout.WEST); + add(center, BorderLayout.CENTER); + add(newListPanel("Selected", selected), BorderLayout.EAST); + } + + private JPanel newListPanel(String label, JTable table) { + NameRenderer nameRenderer = new NameRenderer(); + table.setCellSelectionEnabled(false); + table.setRowSelectionAllowed(true); + table.setRowHeight(nameRenderer.getFont().getSize() + 8); + table.getTableHeader().setReorderingAllowed(false); + table.setGridColor(new Color(0xd9d9d9)); + table.setBackground(Color.white); + table.getColumn(table.getColumnName(0)).setCellRenderer(nameRenderer); + + JScrollPane jsp = new JScrollPane(table); + jsp.setPreferredSize(new Dimension(225, 175)); + JPanel panel = new JPanel(new BorderLayout()); + panel.add(new JLabel(label), BorderLayout.NORTH); + panel.add(jsp, BorderLayout.CENTER); + return panel; + } + + public void setObjects(List all, List selected) { + List available = new ArrayList(all); + if (selected != null) { + available.removeAll(selected); + } + availableModel.list.clear(); + availableModel.list.addAll(available); + availableModel.fireTableDataChanged(); + + if (selected != null) { + selectedModel.list.clear(); + selectedModel.list.addAll(selected); + selectedModel.fireTableDataChanged(); + } + } + + public List getSelections() { + return new ArrayList(selectedModel.list); + } + + public class PaletteModel extends AbstractTableModel { + + private static final long serialVersionUID = 1L; + + List list; + + public PaletteModel() { + this(new ArrayList()); + } + + public PaletteModel(List list) { + this.list = new ArrayList(list); + } + + @Override + public int getRowCount() { + return list.size(); + } + + @Override + public int getColumnCount() { + return 1; + } + + @Override + public String getColumnName(int column) { + return "Name"; + } + + public Class getColumnClass(int columnIndex) { + return String.class; + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + K o = list.get(rowIndex); + return o.toString(); + } + } +} diff --git a/src/com/gitblit/client/NameRenderer.java b/src/com/gitblit/client/NameRenderer.java index 41393fb5..5b1a1736 100644 --- a/src/com/gitblit/client/NameRenderer.java +++ b/src/com/gitblit/client/NameRenderer.java @@ -34,6 +34,10 @@ public class NameRenderer extends DefaultTableCellRenderer { final String groupSpan; + public NameRenderer() { + this(Color.gray, new Color(0x00, 0x69, 0xD6)); + } + public NameRenderer(Color group, Color repo) { groupSpan = ""; setForeground(repo); diff --git a/src/com/gitblit/utils/JsonUtils.java b/src/com/gitblit/utils/JsonUtils.java index 3834c8ed..fee79901 100644 --- a/src/com/gitblit/utils/JsonUtils.java +++ b/src/com/gitblit/utils/JsonUtils.java @@ -157,6 +157,25 @@ public class JsonUtils { } return gson().fromJson(json, type); } + + /** + * Reads a gson object from the specified url. + * + * @param url + * @param clazz + * @param username + * @param password + * @return the deserialized object + * @throws {@link IOException} + */ + public static X retrieveJson(String url, Class clazz, String username, char[] password) + throws IOException { + String json = retrieveJsonString(url, username, password); + if (StringUtils.isEmpty(json)) { + return null; + } + return gson().fromJson(json, clazz); + } /** * Retrieves a JSON message. diff --git a/src/com/gitblit/utils/RpcUtils.java b/src/com/gitblit/utils/RpcUtils.java index 715ecb57..eb28c0f9 100644 --- a/src/com/gitblit/utils/RpcUtils.java +++ b/src/com/gitblit/utils/RpcUtils.java @@ -21,9 +21,11 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Properties; import com.gitblit.Constants; import com.gitblit.Constants.RpcRequest; +import com.gitblit.IStoredSettings; import com.gitblit.models.FederationModel; import com.gitblit.models.FederationProposal; import com.gitblit.models.FederationSet; @@ -294,7 +296,7 @@ public class RpcUtils { List list = new ArrayList(registrations); return list; } - + /** * Retrieves the list of federation proposals. * @@ -304,15 +306,15 @@ public class RpcUtils { * @return a collection of FederationProposal objects * @throws IOException */ - public static List getFederationProposals(String serverUrl, - String account, char[] password) throws IOException { + public static List getFederationProposals(String serverUrl, String account, + char[] password) throws IOException { String url = asLink(serverUrl, RpcRequest.LIST_FEDERATION_PROPOSALS); Collection proposals = JsonUtils.retrieveJson(url, PROPOSALS_TYPE, account, password); List list = new ArrayList(proposals); return list; } - + /** * Retrieves the list of federation repository sets. * @@ -322,15 +324,31 @@ public class RpcUtils { * @return a collection of FederationSet objects * @throws IOException */ - public static List getFederationSets(String serverUrl, - String account, char[] password) throws IOException { + public static List getFederationSets(String serverUrl, String account, + char[] password) throws IOException { String url = asLink(serverUrl, RpcRequest.LIST_FEDERATION_SETS); - Collection sets = JsonUtils.retrieveJson(url, SETS_TYPE, - account, password); + Collection sets = JsonUtils.retrieveJson(url, SETS_TYPE, account, password); List list = new ArrayList(sets); return list; } + /** + * Retrieves the settings of the Gitblit server. + * + * @param serverUrl + * @param account + * @param password + * @return an IStoredSettings object + * @throws IOException + */ + public static IStoredSettings getSettings(String serverUrl, String account, char[] password) + throws IOException { + String url = asLink(serverUrl, RpcRequest.LIST_SETTINGS); + Properties props = JsonUtils.retrieveJson(url, Properties.class, account, password); + RpcSettings settings = new RpcSettings(props); + return settings; + } + /** * Do the specified administrative action on the Gitblit server. * @@ -351,4 +369,31 @@ public class RpcUtils { int resultCode = JsonUtils.sendJsonString(url, json, account, password); return resultCode == 200; } + + /** + * Settings implementation that wraps a retrieved properties instance. This + * class is used for RPC communication. + * + * @author James Moger + * + */ + private static class RpcSettings extends IStoredSettings { + + private final Properties properties = new Properties(); + + public RpcSettings(Properties props) { + super(RpcSettings.class); + properties.putAll(props); + } + + @Override + protected Properties read() { + return properties; + } + + @Override + public String toString() { + return "RpcSettings"; + } + } } diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties index 2aa51079..30fac600 100644 --- a/src/com/gitblit/wicket/GitBlitWebApp.properties +++ b/src/com/gitblit/wicket/GitBlitWebApp.properties @@ -92,7 +92,7 @@ gb.isFrozen = is frozen gb.isFrozenDescription = deny push operations gb.zip = zip gb.showReadme = show readme -gb.showReadmeDescription = show a \"readme\" markdown file on the summary page +gb.showReadmeDescription = show a \"readme\" Markdown file on the summary page gb.nameDescription = use '/' to group repositories. e.g. libraries/mycoollib.git gb.ownerDescription = the owner may edit repository settings gb.blob = blob @@ -106,7 +106,7 @@ gb.isFederated = is federated gb.federateThis = federate this repository gb.federateOrigin = federate the origin gb.excludeFromFederation = exclude from federation -gb.excludeFromFederationDescription = block federated Gitblit instances from pulling this object +gb.excludeFromFederationDescription = block federated Gitblit instances from pulling this account gb.tokens = federation tokens gb.tokenAllDescription = all repositories, users, & settings gb.tokenUnrDescription = all repositories & users diff --git a/tests/com/gitblit/tests/RpcTests.java b/tests/com/gitblit/tests/RpcTests.java index a20b918c..450b5978 100644 --- a/tests/com/gitblit/tests/RpcTests.java +++ b/tests/com/gitblit/tests/RpcTests.java @@ -23,6 +23,7 @@ import junit.framework.TestCase; import com.gitblit.Constants.AccessRestrictionType; import com.gitblit.GitBlitException.UnauthorizedException; +import com.gitblit.IStoredSettings; import com.gitblit.models.FederationModel; import com.gitblit.models.FederationProposal; import com.gitblit.models.FederationSet; @@ -203,4 +204,9 @@ public class RpcTests extends TestCase { List sets = RpcUtils.getFederationSets(url, account, password.toCharArray()); assertTrue("No federation sets were retrieved!", sets.size() > 0); } + + public void testSettings() throws Exception { + IStoredSettings settings = RpcUtils.getSettings(url, account, password.toCharArray()); + assertTrue("No settings were retrieved!", settings.getAllKeys(null).size() > 0); + } }