summaryrefslogtreecommitdiffstats
path: root/src/main/java/com/gitblit/client
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/gitblit/client')
-rw-r--r--src/main/java/com/gitblit/client/BooleanCellRenderer.java50
-rw-r--r--src/main/java/com/gitblit/client/BranchRenderer.java78
-rw-r--r--src/main/java/com/gitblit/client/ClosableTabComponent.java149
-rw-r--r--src/main/java/com/gitblit/client/DateCellRenderer.java76
-rw-r--r--src/main/java/com/gitblit/client/EditRegistrationDialog.java196
-rw-r--r--src/main/java/com/gitblit/client/EditRepositoryDialog.java804
-rw-r--r--src/main/java/com/gitblit/client/EditTeamDialog.java397
-rw-r--r--src/main/java/com/gitblit/client/EditUserDialog.java466
-rw-r--r--src/main/java/com/gitblit/client/FeedEntryTableModel.java123
-rw-r--r--src/main/java/com/gitblit/client/FeedsPanel.java405
-rw-r--r--src/main/java/com/gitblit/client/FeedsTableModel.java122
-rw-r--r--src/main/java/com/gitblit/client/GitblitClient.java717
-rw-r--r--src/main/java/com/gitblit/client/GitblitManager.java458
-rw-r--r--src/main/java/com/gitblit/client/GitblitManagerLauncher.java164
-rw-r--r--src/main/java/com/gitblit/client/GitblitPanel.java228
-rw-r--r--src/main/java/com/gitblit/client/GitblitRegistration.java86
-rw-r--r--src/main/java/com/gitblit/client/GitblitWorker.java89
-rw-r--r--src/main/java/com/gitblit/client/HeaderPanel.java93
-rw-r--r--src/main/java/com/gitblit/client/IndicatorsRenderer.java150
-rw-r--r--src/main/java/com/gitblit/client/JPalette.java224
-rw-r--r--src/main/java/com/gitblit/client/MessageRenderer.java205
-rw-r--r--src/main/java/com/gitblit/client/NameRenderer.java91
-rw-r--r--src/main/java/com/gitblit/client/PropertiesTableModel.java106
-rw-r--r--src/main/java/com/gitblit/client/RegistrantPermissionsPanel.java235
-rw-r--r--src/main/java/com/gitblit/client/RegistrantPermissionsTableModel.java134
-rw-r--r--src/main/java/com/gitblit/client/RegistrationsDialog.java228
-rw-r--r--src/main/java/com/gitblit/client/RegistrationsTableModel.java102
-rw-r--r--src/main/java/com/gitblit/client/RepositoriesPanel.java560
-rw-r--r--src/main/java/com/gitblit/client/RepositoriesTableModel.java128
-rw-r--r--src/main/java/com/gitblit/client/SearchDialog.java404
-rw-r--r--src/main/java/com/gitblit/client/SettingCellRenderer.java67
-rw-r--r--src/main/java/com/gitblit/client/SettingPanel.java120
-rw-r--r--src/main/java/com/gitblit/client/SettingsPanel.java274
-rw-r--r--src/main/java/com/gitblit/client/SettingsTableModel.java124
-rw-r--r--src/main/java/com/gitblit/client/StatusPanel.java169
-rw-r--r--src/main/java/com/gitblit/client/SubscribedRepositoryRenderer.java62
-rw-r--r--src/main/java/com/gitblit/client/SubscriptionsDialog.java158
-rw-r--r--src/main/java/com/gitblit/client/TeamsPanel.java385
-rw-r--r--src/main/java/com/gitblit/client/TeamsTableModel.java105
-rw-r--r--src/main/java/com/gitblit/client/Translation.java59
-rw-r--r--src/main/java/com/gitblit/client/UsersPanel.java385
-rw-r--r--src/main/java/com/gitblit/client/UsersTableModel.java125
-rw-r--r--src/main/java/com/gitblit/client/Utils.java173
-rw-r--r--src/main/java/com/gitblit/client/splash.pngbin0 -> 11593 bytes
44 files changed, 9474 insertions, 0 deletions
diff --git a/src/main/java/com/gitblit/client/BooleanCellRenderer.java b/src/main/java/com/gitblit/client/BooleanCellRenderer.java
new file mode 100644
index 00000000..c8341df6
--- /dev/null
+++ b/src/main/java/com/gitblit/client/BooleanCellRenderer.java
@@ -0,0 +1,50 @@
+/*
+ * 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.Component;
+import java.io.Serializable;
+
+import javax.swing.JCheckBox;
+import javax.swing.JTable;
+import javax.swing.SwingConstants;
+import javax.swing.table.TableCellRenderer;
+
+/**
+ * Boolean checkbox cell renderer.
+ *
+ * @author James Moger
+ *
+ */
+public class BooleanCellRenderer extends JCheckBox implements TableCellRenderer, Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ public BooleanCellRenderer() {
+ super();
+ setOpaque(false);
+ setHorizontalAlignment(SwingConstants.CENTER);
+ }
+
+ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
+ boolean hasFocus, int row, int column) {
+ if (value instanceof Boolean) {
+ boolean checked = (Boolean) value;
+ this.setSelected(checked);
+ }
+ return this;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/gitblit/client/BranchRenderer.java b/src/main/java/com/gitblit/client/BranchRenderer.java
new file mode 100644
index 00000000..9a303c38
--- /dev/null
+++ b/src/main/java/com/gitblit/client/BranchRenderer.java
@@ -0,0 +1,78 @@
+/*
+ * 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.Color;
+import java.awt.Component;
+
+import javax.swing.JList;
+import javax.swing.JTable;
+import javax.swing.ListCellRenderer;
+import javax.swing.table.DefaultTableCellRenderer;
+
+/**
+ * Branch renderer displays refs/heads and refs/remotes in a color similar to
+ * the site.
+ *
+ * @author James Moger
+ *
+ */
+public class BranchRenderer extends DefaultTableCellRenderer implements ListCellRenderer {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final String R_HEADS = "refs/heads/";
+
+ private static final String R_REMOTES = "refs/remotes/";
+
+ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
+ boolean hasFocus, int row, int column) {
+ super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
+ setText(value == null ? "" : value.toString());
+ if (isSelected) {
+ setForeground(table.getSelectionForeground());
+ }
+ return this;
+ }
+
+ @Override
+ public Component getListCellRendererComponent(JList list, Object value, int index,
+ boolean isSelected, boolean cellHasFocus) {
+ setText(value == null ? "" : value.toString());
+ if (isSelected) {
+ setBackground(list.getSelectionBackground());
+ setForeground(list.getSelectionForeground());
+ } else {
+ setBackground(list.getBackground());
+ }
+ return this;
+ }
+
+ @Override
+ public void setText(String text) {
+ String name = text;
+ Color fg = getForeground();
+ if (name.startsWith(R_HEADS)) {
+ name = name.substring(R_HEADS.length());
+ fg = new Color(0, 0x80, 0);
+ } else if (name.startsWith(R_REMOTES)) {
+ name = name.substring(R_REMOTES.length());
+ fg = Color.decode("#6C6CBF");
+ }
+ setForeground(fg);
+ super.setText(name);
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/gitblit/client/ClosableTabComponent.java b/src/main/java/com/gitblit/client/ClosableTabComponent.java
new file mode 100644
index 00000000..a121806a
--- /dev/null
+++ b/src/main/java/com/gitblit/client/ClosableTabComponent.java
@@ -0,0 +1,149 @@
+/*
+ * 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.BasicStroke;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.FlowLayout;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Stroke;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+
+import javax.swing.AbstractButton;
+import javax.swing.BorderFactory;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTabbedPane;
+import javax.swing.plaf.basic.BasicButtonUI;
+
+/**
+ * Closable tab control.
+ */
+public class ClosableTabComponent extends JPanel {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final MouseListener BUTTON_MOUSE_LISTENER = new MouseAdapter() {
+ public void mouseEntered(MouseEvent e) {
+ Component component = e.getComponent();
+ if (component instanceof AbstractButton) {
+ AbstractButton button = (AbstractButton) component;
+ button.setBorderPainted(true);
+ }
+ }
+
+ public void mouseExited(MouseEvent e) {
+ Component component = e.getComponent();
+ if (component instanceof AbstractButton) {
+ AbstractButton button = (AbstractButton) component;
+ button.setBorderPainted(false);
+ }
+ }
+ };
+
+ private final JTabbedPane pane;
+ private final JLabel label;
+ private final JButton button = new TabButton();
+
+ private final CloseTabListener closeListener;
+
+ public interface CloseTabListener {
+ void closeTab(Component c);
+ }
+
+ public ClosableTabComponent(String title, ImageIcon icon, JTabbedPane pane,
+ CloseTabListener closeListener) {
+ super(new FlowLayout(FlowLayout.LEFT, 0, 0));
+ this.closeListener = closeListener;
+
+ if (pane == null) {
+ throw new NullPointerException("TabbedPane is null");
+ }
+ this.pane = pane;
+ setOpaque(false);
+ label = new JLabel(title);
+ if (icon != null) {
+ label.setIcon(icon);
+ }
+
+ add(label);
+ label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5));
+ add(button);
+ setBorder(BorderFactory.createEmptyBorder(2, 0, 0, 0));
+ }
+
+ private class TabButton extends JButton implements ActionListener {
+
+ private static final long serialVersionUID = 1L;
+
+ public TabButton() {
+ int size = 17;
+ setPreferredSize(new Dimension(size, size));
+ setToolTipText("Close");
+ setUI(new BasicButtonUI());
+ setContentAreaFilled(false);
+ setFocusable(false);
+ setBorder(BorderFactory.createEtchedBorder());
+ setBorderPainted(false);
+ addMouseListener(BUTTON_MOUSE_LISTENER);
+ setRolloverEnabled(true);
+ addActionListener(this);
+ }
+
+ public void actionPerformed(ActionEvent e) {
+ int i = pane.indexOfTabComponent(ClosableTabComponent.this);
+ Component c = pane.getComponentAt(i);
+ if (i != -1) {
+ pane.remove(i);
+ }
+ if (closeListener != null) {
+ closeListener.closeTab(c);
+ }
+ }
+
+ public void updateUI() {
+ }
+
+ @Override
+ protected void paintComponent(Graphics g) {
+ super.paintComponent(g);
+ Graphics2D g2 = (Graphics2D) g;
+ Stroke stroke = g2.getStroke();
+ g2.setStroke(new BasicStroke(2));
+ g.setColor(Color.BLACK);
+ if (getModel().isRollover()) {
+ Color highlight = new Color(0, 51, 153);
+ g.setColor(highlight);
+ }
+ int delta = 5;
+ g.drawLine(delta, delta, getWidth() - delta - 1, getHeight() - delta - 1);
+ g.drawLine(getWidth() - delta - 1, delta, delta, getHeight() - delta - 1);
+ g2.setStroke(stroke);
+
+ int i = pane.indexOfTabComponent(ClosableTabComponent.this);
+ pane.setTitleAt(i, label.getText());
+ }
+ }
+}
diff --git a/src/main/java/com/gitblit/client/DateCellRenderer.java b/src/main/java/com/gitblit/client/DateCellRenderer.java
new file mode 100644
index 00000000..751c7dbb
--- /dev/null
+++ b/src/main/java/com/gitblit/client/DateCellRenderer.java
@@ -0,0 +1,76 @@
+/*
+ * 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.Color;
+import java.awt.Component;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.swing.JTable;
+import javax.swing.SwingConstants;
+import javax.swing.table.DefaultTableCellRenderer;
+
+/**
+ * Time ago cell renderer with real date tooltip.
+ *
+ * @author James Moger
+ *
+ */
+public class DateCellRenderer extends DefaultTableCellRenderer {
+
+ private static final long serialVersionUID = 1L;
+
+ private final String pattern;
+
+ public DateCellRenderer(String pattern, Color foreground) {
+ this.pattern = (pattern == null ? "yyyy-MM-dd HH:mm" : pattern);
+ setForeground(foreground);
+ setHorizontalAlignment(SwingConstants.CENTER);
+ }
+
+ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
+ boolean hasFocus, int row, int column) {
+ super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
+ if (value instanceof Date) {
+ Date date = (Date) value;
+ String title;
+ String dateString;
+ if (date.getTime() == 0) {
+ title = "--";
+ dateString = "never";
+ } else {
+ if (date.getTime() - System.currentTimeMillis() > 0) {
+ // future
+ title = Translation.getTimeUtils().inFuture(date);
+ } else {
+ // past
+ title = Translation.getTimeUtils().timeAgo(date);
+ }
+ dateString = new SimpleDateFormat(pattern).format((Date) value);
+ }
+
+ if ((System.currentTimeMillis() - date.getTime()) > 10 * 24 * 60 * 60 * 1000L) {
+ String tmp = dateString;
+ dateString = title;
+ title = tmp;
+ }
+ this.setText(title);
+ this.setToolTipText(dateString);
+ }
+ return this;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/gitblit/client/EditRegistrationDialog.java b/src/main/java/com/gitblit/client/EditRegistrationDialog.java
new file mode 100644
index 00000000..99cd36fa
--- /dev/null
+++ b/src/main/java/com/gitblit/client/EditRegistrationDialog.java
@@ -0,0 +1,196 @@
+/*
+ * 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.Font;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.Window;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+
+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.JRootPane;
+import javax.swing.JTextField;
+import javax.swing.KeyStroke;
+
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Dialog to create or edit a Gitblit registration.
+ *
+ * @author James Moger
+ *
+ */
+public class EditRegistrationDialog extends JDialog {
+
+ private static final long serialVersionUID = 1L;
+ private JTextField urlField;
+ private JTextField nameField;
+ private JTextField accountField;
+ private JPasswordField passwordField;
+ private JCheckBox savePassword;
+ private boolean canceled;
+ private HeaderPanel headerPanel;
+
+ public EditRegistrationDialog(Window owner) {
+ this(owner, null, false);
+ }
+
+ public EditRegistrationDialog(Window owner, GitblitRegistration reg, boolean isLogin) {
+ super(owner);
+ initialize(reg, isLogin);
+ }
+
+ @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(GitblitRegistration reg, boolean isLogin) {
+ setIconImage(new ImageIcon(getClass().getResource("/gitblt-favicon.png")).getImage());
+ canceled = true;
+ urlField = new JTextField(reg == null ? "" : reg.url, 30);
+ nameField = new JTextField(reg == null ? "" : reg.name);
+ accountField = new JTextField(reg == null ? "" : reg.account);
+ passwordField = new JPasswordField(reg == null ? "" : new String(reg.password));
+ savePassword = new JCheckBox("save password (passwords are NOT encrypted!)");
+ savePassword.setSelected(reg == null ? false
+ : (reg.password != null && reg.password.length > 0));
+
+ JPanel panel = new JPanel(new GridLayout(0, 1, 5, 5));
+ panel.add(newLabelPanel(Translation.get("gb.name"), nameField));
+ panel.add(newLabelPanel(Translation.get("gb.url"), urlField));
+ panel.add(newLabelPanel(Translation.get("gb.username"), accountField));
+ panel.add(newLabelPanel(Translation.get("gb.password"), passwordField));
+ panel.add(newLabelPanel("", savePassword));
+
+ JButton cancel = new JButton(Translation.get("gb.cancel"));
+ cancel.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ setVisible(false);
+ }
+ });
+
+ final JButton save = new JButton(Translation.get(isLogin ? "gb.login" : "gb.save"));
+ save.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ if (validateFields()) {
+ canceled = false;
+ setVisible(false);
+ }
+ }
+ });
+
+ // on enter in password field, save or login
+ passwordField.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ save.doClick();
+ }
+ });
+
+ JPanel controls = new JPanel();
+ controls.add(cancel);
+ controls.add(save);
+
+ if (reg == null) {
+ this.setTitle(Translation.get("gb.create"));
+ headerPanel = new HeaderPanel(Translation.get("gb.create"), null);
+ } else {
+ this.setTitle(Translation.get(isLogin ? "gb.login" : "gb.edit"));
+ headerPanel = new HeaderPanel(reg.name, null);
+ }
+
+ 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(headerPanel, BorderLayout.NORTH);
+ centerPanel.add(panel, BorderLayout.CENTER);
+ centerPanel.add(controls, BorderLayout.SOUTH);
+
+ getContentPane().setLayout(new BorderLayout());
+ getContentPane().add(centerPanel, BorderLayout.CENTER);
+ pack();
+ setModal(true);
+ if (isLogin) {
+ passwordField.requestFocus();
+ }
+ }
+
+ private JPanel newLabelPanel(String text, JComponent field) {
+ JLabel label = new JLabel(text);
+ label.setFont(label.getFont().deriveFont(Font.BOLD));
+ label.setPreferredSize(new Dimension(75, 10));
+ JPanel jpanel = new JPanel(new BorderLayout());
+ jpanel.add(label, BorderLayout.WEST);
+ jpanel.add(field, BorderLayout.CENTER);
+ return jpanel;
+ }
+
+ private boolean validateFields() {
+ String name = nameField.getText();
+ if (StringUtils.isEmpty(name)) {
+ error("Please enter a name for this registration!");
+ return false;
+ }
+ String url = urlField.getText();
+ if (StringUtils.isEmpty(url)) {
+ error("Please enter a url for this registration!");
+ return false;
+ }
+ return true;
+ }
+
+ private void error(String message) {
+ JOptionPane.showMessageDialog(EditRegistrationDialog.this, message,
+ Translation.get("gb.error"), JOptionPane.ERROR_MESSAGE);
+ }
+
+ public GitblitRegistration getRegistration() {
+ if (canceled) {
+ return null;
+ }
+ GitblitRegistration reg = new GitblitRegistration(nameField.getText(), urlField.getText(),
+ accountField.getText(), passwordField.getPassword());
+ reg.savePassword = savePassword.isSelected();
+ return reg;
+ }
+}
diff --git a/src/main/java/com/gitblit/client/EditRepositoryDialog.java b/src/main/java/com/gitblit/client/EditRepositoryDialog.java
new file mode 100644
index 00000000..8851de43
--- /dev/null
+++ b/src/main/java/com/gitblit/client/EditRepositoryDialog.java
@@ -0,0 +1,804 @@
+/*
+ * 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.Component;
+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.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.KeyEvent;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.swing.BoxLayout;
+import javax.swing.ButtonGroup;
+import javax.swing.DefaultListCellRenderer;
+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.JList;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JRadioButton;
+import javax.swing.JRootPane;
+import javax.swing.JScrollPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JTextField;
+import javax.swing.KeyStroke;
+import javax.swing.ListCellRenderer;
+import javax.swing.ScrollPaneConstants;
+
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.AuthorizationControl;
+import com.gitblit.Constants.FederationStrategy;
+import com.gitblit.Constants.RegistrantType;
+import com.gitblit.models.RegistrantAccessPermission;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Dialog to create/edit a repository.
+ *
+ * @author James Moger
+ */
+public class EditRepositoryDialog extends JDialog {
+
+ private static final long serialVersionUID = 1L;
+
+ private final String repositoryName;
+
+ private final RepositoryModel repository;
+
+ private boolean isCreate;
+
+ private boolean canceled = true;
+
+ private JTextField nameField;
+
+ private JTextField descriptionField;
+
+ private JCheckBox useTickets;
+
+ private JCheckBox useDocs;
+
+ private JCheckBox showRemoteBranches;
+
+ private JCheckBox showReadme;
+
+ private JCheckBox skipSizeCalculation;
+
+ private JCheckBox skipSummaryMetrics;
+
+ private JCheckBox isFrozen;
+
+ private JTextField mailingListsField;
+
+ private JComboBox accessRestriction;
+
+ private JRadioButton allowAuthenticated;
+
+ private JRadioButton allowNamed;
+
+ private JCheckBox allowForks;
+
+ private JCheckBox verifyCommitter;
+
+ private JComboBox federationStrategy;
+
+ private JPalette<String> ownersPalette;
+
+ private JComboBox headRefField;
+
+ private JComboBox gcPeriod;
+
+ private JTextField gcThreshold;
+
+ private JComboBox maxActivityCommits;
+
+ private RegistrantPermissionsPanel usersPalette;
+
+ private JPalette<String> setsPalette;
+
+ private RegistrantPermissionsPanel teamsPalette;
+
+ private JPalette<String> indexedBranchesPalette;
+
+ private JPalette<String> preReceivePalette;
+
+ private JLabel preReceiveInherited;
+
+ private JPalette<String> postReceivePalette;
+
+ private JLabel postReceiveInherited;
+
+ private Set<String> repositoryNames;
+
+ private JPanel customFieldsPanel;
+
+ private List<JTextField> customTextfields;
+
+ public EditRepositoryDialog(int protocolVersion) {
+ this(protocolVersion, new RepositoryModel());
+ this.isCreate = true;
+ setTitle(Translation.get("gb.newRepository"));
+ }
+
+ public EditRepositoryDialog(int protocolVersion, RepositoryModel aRepository) {
+ super();
+ this.repositoryName = aRepository.name;
+ this.repository = new RepositoryModel();
+ this.repositoryNames = new HashSet<String>();
+ this.isCreate = false;
+ initialize(protocolVersion, aRepository);
+ setModal(true);
+ setResizable(false);
+ setTitle(Translation.get("gb.edit") + ": " + aRepository.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, RepositoryModel anRepository) {
+ nameField = new JTextField(anRepository.name == null ? ""
+ : anRepository.name, 35);
+ descriptionField = new JTextField(anRepository.description == null ? ""
+ : anRepository.description, 35);
+
+ JTextField originField = new JTextField(
+ anRepository.origin == null ? "" : anRepository.origin, 40);
+ originField.setEditable(false);
+
+ if (ArrayUtils.isEmpty(anRepository.availableRefs)) {
+ headRefField = new JComboBox();
+ headRefField.setEnabled(false);
+ } else {
+ headRefField = new JComboBox(
+ anRepository.availableRefs.toArray());
+ headRefField.setSelectedItem(anRepository.HEAD);
+ }
+
+ Integer [] gcPeriods = { 1, 2, 3, 4, 5, 7, 10, 14 };
+ gcPeriod = new JComboBox(gcPeriods);
+ gcPeriod.setSelectedItem(anRepository.gcPeriod);
+
+ gcThreshold = new JTextField(8);
+ gcThreshold.setText(anRepository.gcThreshold);
+
+ ownersPalette = new JPalette<String>(true);
+
+ useTickets = new JCheckBox(Translation.get("gb.useTicketsDescription"),
+ anRepository.useTickets);
+ useDocs = new JCheckBox(Translation.get("gb.useDocsDescription"),
+ anRepository.useDocs);
+ showRemoteBranches = new JCheckBox(
+ Translation.get("gb.showRemoteBranchesDescription"),
+ anRepository.showRemoteBranches);
+ showReadme = new JCheckBox(Translation.get("gb.showReadmeDescription"),
+ anRepository.showReadme);
+ skipSizeCalculation = new JCheckBox(
+ Translation.get("gb.skipSizeCalculationDescription"),
+ anRepository.skipSizeCalculation);
+ skipSummaryMetrics = new JCheckBox(
+ Translation.get("gb.skipSummaryMetricsDescription"),
+ anRepository.skipSummaryMetrics);
+ isFrozen = new JCheckBox(Translation.get("gb.isFrozenDescription"),
+ anRepository.isFrozen);
+
+ maxActivityCommits = new JComboBox(new Integer [] { -1, 0, 25, 50, 75, 100, 150, 250, 500 });
+ maxActivityCommits.setSelectedItem(anRepository.maxActivityCommits);
+
+ mailingListsField = new JTextField(
+ ArrayUtils.isEmpty(anRepository.mailingLists) ? ""
+ : StringUtils.flattenStrings(anRepository.mailingLists,
+ " "), 50);
+
+ accessRestriction = new JComboBox(AccessRestrictionType.values());
+ accessRestriction.setRenderer(new AccessRestrictionRenderer());
+ accessRestriction.setSelectedItem(anRepository.accessRestriction);
+ accessRestriction.addItemListener(new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ if (e.getStateChange() == ItemEvent.SELECTED) {
+ AccessRestrictionType art = (AccessRestrictionType) accessRestriction.getSelectedItem();
+ EditRepositoryDialog.this.setupAccessPermissions(art);
+ }
+ }
+ });
+
+ boolean authenticated = anRepository.authorizationControl != null
+ && AuthorizationControl.AUTHENTICATED.equals(anRepository.authorizationControl);
+ allowAuthenticated = new JRadioButton(Translation.get("gb.allowAuthenticatedDescription"));
+ allowAuthenticated.setSelected(authenticated);
+ allowAuthenticated.addItemListener(new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ if (e.getStateChange() == ItemEvent.SELECTED) {
+ usersPalette.setEnabled(false);
+ teamsPalette.setEnabled(false);
+ }
+ }
+ });
+
+ allowNamed = new JRadioButton(Translation.get("gb.allowNamedDescription"));
+ allowNamed.setSelected(!authenticated);
+ allowNamed.addItemListener(new ItemListener() {
+ @Override
+ public void itemStateChanged(ItemEvent e) {
+ if (e.getStateChange() == ItemEvent.SELECTED) {
+ usersPalette.setEnabled(true);
+ teamsPalette.setEnabled(true);
+ }
+ }
+ });
+
+ ButtonGroup group = new ButtonGroup();
+ group.add(allowAuthenticated);
+ group.add(allowNamed);
+
+ JPanel authorizationPanel = new JPanel(new GridLayout(0, 1));
+ authorizationPanel.add(allowAuthenticated);
+ authorizationPanel.add(allowNamed);
+
+ allowForks = new JCheckBox(Translation.get("gb.allowForksDescription"), anRepository.allowForks);
+ verifyCommitter = new JCheckBox(Translation.get("gb.verifyCommitterDescription"), anRepository.verifyCommitter);
+
+ // federation strategies - remove ORIGIN choice if this repository has
+ // no origin.
+ List<FederationStrategy> federationStrategies = new ArrayList<FederationStrategy>(
+ Arrays.asList(FederationStrategy.values()));
+ if (StringUtils.isEmpty(anRepository.origin)) {
+ federationStrategies.remove(FederationStrategy.FEDERATE_ORIGIN);
+ }
+ federationStrategy = new JComboBox(federationStrategies.toArray());
+ federationStrategy.setRenderer(new FederationStrategyRenderer());
+ federationStrategy.setSelectedItem(anRepository.federationStrategy);
+
+ JPanel fieldsPanel = new JPanel(new GridLayout(0, 1));
+ fieldsPanel.add(newFieldPanel(Translation.get("gb.name"), nameField));
+ fieldsPanel.add(newFieldPanel(Translation.get("gb.description"),
+ descriptionField));
+ fieldsPanel
+ .add(newFieldPanel(Translation.get("gb.origin"), originField));
+ fieldsPanel.add(newFieldPanel(Translation.get("gb.headRef"), headRefField));
+ fieldsPanel.add(newFieldPanel(Translation.get("gb.gcPeriod"), gcPeriod));
+ fieldsPanel.add(newFieldPanel(Translation.get("gb.gcThreshold"), gcThreshold));
+
+ fieldsPanel.add(newFieldPanel(Translation.get("gb.enableTickets"),
+ useTickets));
+ fieldsPanel
+ .add(newFieldPanel(Translation.get("gb.enableDocs"), useDocs));
+ fieldsPanel.add(newFieldPanel(Translation.get("gb.showRemoteBranches"),
+ showRemoteBranches));
+ fieldsPanel.add(newFieldPanel(Translation.get("gb.showReadme"),
+ showReadme));
+ fieldsPanel
+ .add(newFieldPanel(Translation.get("gb.skipSizeCalculation"),
+ skipSizeCalculation));
+ fieldsPanel.add(newFieldPanel(Translation.get("gb.skipSummaryMetrics"),
+ skipSummaryMetrics));
+ fieldsPanel.add(newFieldPanel(Translation.get("gb.maxActivityCommits"),
+ maxActivityCommits));
+ fieldsPanel.add(newFieldPanel(Translation.get("gb.mailingLists"),
+ mailingListsField));
+
+ JPanel clonePushPanel = new JPanel(new GridLayout(0, 1));
+ clonePushPanel
+ .add(newFieldPanel(Translation.get("gb.isFrozen"), isFrozen));
+ clonePushPanel
+ .add(newFieldPanel(Translation.get("gb.allowForks"), allowForks));
+ clonePushPanel
+ .add(newFieldPanel(Translation.get("gb.verifyCommitter"), verifyCommitter));
+
+ usersPalette = new RegistrantPermissionsPanel(RegistrantType.USER);
+
+ JPanel northFieldsPanel = new JPanel(new BorderLayout(0, 5));
+ northFieldsPanel.add(newFieldPanel(Translation.get("gb.owners"), ownersPalette), BorderLayout.NORTH);
+ northFieldsPanel.add(newFieldPanel(Translation.get("gb.accessRestriction"),
+ accessRestriction), BorderLayout.CENTER);
+
+ JPanel northAccessPanel = new JPanel(new BorderLayout(5, 5));
+ northAccessPanel.add(northFieldsPanel, BorderLayout.NORTH);
+ northAccessPanel.add(newFieldPanel(Translation.get("gb.authorizationControl"),
+ authorizationPanel), BorderLayout.CENTER);
+ northAccessPanel.add(clonePushPanel, BorderLayout.SOUTH);
+
+ JPanel accessPanel = new JPanel(new BorderLayout(5, 5));
+ accessPanel.add(northAccessPanel, BorderLayout.NORTH);
+ accessPanel.add(newFieldPanel(Translation.get("gb.userPermissions"),
+ usersPalette), BorderLayout.CENTER);
+
+ teamsPalette = new RegistrantPermissionsPanel(RegistrantType.TEAM);
+ JPanel teamsPanel = new JPanel(new BorderLayout(5, 5));
+ teamsPanel.add(
+ newFieldPanel(Translation.get("gb.teamPermissions"),
+ teamsPalette), BorderLayout.CENTER);
+
+ setsPalette = new JPalette<String>();
+ JPanel federationPanel = new JPanel(new BorderLayout(5, 5));
+ federationPanel.add(
+ newFieldPanel(Translation.get("gb.federationStrategy"),
+ federationStrategy), BorderLayout.NORTH);
+ federationPanel
+ .add(newFieldPanel(Translation.get("gb.federationSets"),
+ setsPalette), BorderLayout.CENTER);
+
+ indexedBranchesPalette = new JPalette<String>();
+ JPanel indexedBranchesPanel = new JPanel(new BorderLayout(5, 5));
+ indexedBranchesPanel
+ .add(newFieldPanel(Translation.get("gb.indexedBranches"),
+ indexedBranchesPalette), BorderLayout.CENTER);
+
+ preReceivePalette = new JPalette<String>(true);
+ preReceiveInherited = new JLabel();
+ JPanel preReceivePanel = new JPanel(new BorderLayout(5, 5));
+ preReceivePanel.add(preReceivePalette, BorderLayout.CENTER);
+ preReceivePanel.add(preReceiveInherited, BorderLayout.WEST);
+
+ postReceivePalette = new JPalette<String>(true);
+ postReceiveInherited = new JLabel();
+ JPanel postReceivePanel = new JPanel(new BorderLayout(5, 5));
+ postReceivePanel.add(postReceivePalette, BorderLayout.CENTER);
+ postReceivePanel.add(postReceiveInherited, BorderLayout.WEST);
+
+ customFieldsPanel = new JPanel();
+ customFieldsPanel.setLayout(new BoxLayout(customFieldsPanel, BoxLayout.Y_AXIS));
+ JScrollPane customFieldsScrollPane = new JScrollPane(customFieldsPanel);
+ customFieldsScrollPane.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
+ customFieldsScrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
+
+ 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);
+ if (protocolVersion >= 3) {
+ panel.addTab(Translation.get("gb.indexedBranches"), indexedBranchesPanel);
+ }
+ panel.addTab(Translation.get("gb.preReceiveScripts"), preReceivePanel);
+ panel.addTab(Translation.get("gb.postReceiveScripts"), postReceivePanel);
+
+ panel.addTab(Translation.get("gb.customFields"), customFieldsScrollPane);
+
+
+ setupAccessPermissions(anRepository.accessRestriction);
+
+ 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);
+
+ 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();
+ nameField.requestFocus();
+ }
+
+ private JPanel newFieldPanel(String label, JComponent comp) {
+ return newFieldPanel(label, 150, comp);
+ }
+
+ private JPanel newFieldPanel(String label, int labelSize, JComponent comp) {
+ JLabel fieldLabel = new JLabel(label);
+ fieldLabel.setFont(fieldLabel.getFont().deriveFont(Font.BOLD));
+ fieldLabel.setPreferredSize(new Dimension(labelSize, 20));
+ JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 0));
+ panel.add(fieldLabel);
+ panel.add(comp);
+ return panel;
+ }
+
+ private void setupAccessPermissions(AccessRestrictionType art) {
+ if (AccessRestrictionType.NONE.equals(art)) {
+ usersPalette.setEnabled(false);
+ teamsPalette.setEnabled(false);
+
+ allowAuthenticated.setEnabled(false);
+ allowNamed.setEnabled(false);
+ verifyCommitter.setEnabled(false);
+ } else {
+ allowAuthenticated.setEnabled(true);
+ allowNamed.setEnabled(true);
+ verifyCommitter.setEnabled(true);
+
+ if (allowNamed.isSelected()) {
+ usersPalette.setEnabled(true);
+ teamsPalette.setEnabled(true);
+ }
+ }
+
+ }
+
+ private boolean validateFields() {
+ String rname = nameField.getText();
+ if (StringUtils.isEmpty(rname)) {
+ error("Please enter a repository name!");
+ return false;
+ }
+
+ // automatically convert backslashes to forward slashes
+ rname = rname.replace('\\', '/');
+ // Automatically replace // with /
+ rname = rname.replace("//", "/");
+
+ // prohibit folder paths
+ if (rname.startsWith("/")) {
+ error("Leading root folder references (/) are prohibited.");
+ return false;
+ }
+ if (rname.startsWith("../")) {
+ error("Relative folder references (../) are prohibited.");
+ return false;
+ }
+ if (rname.contains("/../")) {
+ error("Relative folder references (../) are prohibited.");
+ return false;
+ }
+ if (rname.endsWith("/")) {
+ rname = rname.substring(0, rname.length() - 1);
+ }
+
+ // confirm valid characters in repository name
+ Character c = StringUtils.findInvalidCharacter(rname);
+ if (c != null) {
+ error(MessageFormat.format(
+ "Illegal character ''{0}'' in repository name!", c));
+ return false;
+ }
+
+ // verify repository name uniqueness on create
+ if (isCreate) {
+ // force repo names to lowercase
+ // this means that repository name checking for rpc creation
+ // is case-insensitive, regardless of the Gitblit server's
+ // filesystem
+ if (repositoryNames.contains(rname.toLowerCase())) {
+ error(MessageFormat
+ .format("Can not create repository ''{0}'' because it already exists.",
+ rname));
+ return false;
+ }
+ } else {
+ // check rename collision
+ if (!repositoryName.equalsIgnoreCase(rname)) {
+ if (repositoryNames.contains(rname.toLowerCase())) {
+ error(MessageFormat
+ .format("Failed to rename ''{0}'' because ''{1}'' already exists.",
+ repositoryName, rname));
+ return false;
+ }
+ }
+ }
+
+ if (accessRestriction.getSelectedItem() == null) {
+ error("Please select access restriction!");
+ return false;
+ }
+
+ if (federationStrategy.getSelectedItem() == null) {
+ error("Please select federation strategy!");
+ return false;
+ }
+
+ repository.name = rname;
+ repository.description = descriptionField.getText();
+ repository.owners.clear();
+ repository.owners.addAll(ownersPalette.getSelections());
+ repository.HEAD = headRefField.getSelectedItem() == null ? null
+ : headRefField.getSelectedItem().toString();
+ repository.gcPeriod = (Integer) gcPeriod.getSelectedItem();
+ repository.gcThreshold = gcThreshold.getText();
+ repository.useTickets = useTickets.isSelected();
+ repository.useDocs = useDocs.isSelected();
+ repository.showRemoteBranches = showRemoteBranches.isSelected();
+ repository.showReadme = showReadme.isSelected();
+ repository.skipSizeCalculation = skipSizeCalculation.isSelected();
+ repository.skipSummaryMetrics = skipSummaryMetrics.isSelected();
+ repository.maxActivityCommits = (Integer) maxActivityCommits.getSelectedItem();
+
+ repository.isFrozen = isFrozen.isSelected();
+ repository.allowForks = allowForks.isSelected();
+ repository.verifyCommitter = verifyCommitter.isSelected();
+
+ String ml = mailingListsField.getText();
+ if (!StringUtils.isEmpty(ml)) {
+ Set<String> list = new HashSet<String>();
+ for (String address : ml.split("(,|\\s)")) {
+ if (StringUtils.isEmpty(address)) {
+ continue;
+ }
+ list.add(address.toLowerCase());
+ }
+ repository.mailingLists = new ArrayList<String>(list);
+ }
+
+ repository.accessRestriction = (AccessRestrictionType) accessRestriction
+ .getSelectedItem();
+ repository.authorizationControl = allowAuthenticated.isSelected() ?
+ AuthorizationControl.AUTHENTICATED : AuthorizationControl.NAMED;
+ repository.federationStrategy = (FederationStrategy) federationStrategy
+ .getSelectedItem();
+
+ if (repository.federationStrategy.exceeds(FederationStrategy.EXCLUDE)) {
+ repository.federationSets = setsPalette.getSelections();
+ }
+
+ repository.indexedBranches = indexedBranchesPalette.getSelections();
+ repository.preReceiveScripts = preReceivePalette.getSelections();
+ repository.postReceiveScripts = postReceivePalette.getSelections();
+
+ // Custom Fields
+ repository.customFields = new LinkedHashMap<String, String>();
+ if (customTextfields != null) {
+ for (JTextField field : customTextfields) {
+ String key = field.getName();
+ String value = field.getText();
+ repository.customFields.put(key, value);
+ }
+ }
+ return true;
+ }
+
+ private void error(String message) {
+ JOptionPane.showMessageDialog(EditRepositoryDialog.this, message,
+ Translation.get("gb.error"), JOptionPane.ERROR_MESSAGE);
+ }
+
+ public void setAccessRestriction(AccessRestrictionType restriction) {
+ this.accessRestriction.setSelectedItem(restriction);
+ setupAccessPermissions(restriction);
+ }
+
+ public void setAuthorizationControl(AuthorizationControl authorization) {
+ boolean authenticated = authorization != null && AuthorizationControl.AUTHENTICATED.equals(authorization);
+ this.allowAuthenticated.setSelected(authenticated);
+ this.allowNamed.setSelected(!authenticated);
+ }
+
+ public void setUsers(List<String> owners, List<String> all, List<RegistrantAccessPermission> permissions) {
+ ownersPalette.setObjects(all, owners);
+ usersPalette.setObjects(all, permissions);
+ }
+
+ public void setTeams(List<String> all, List<RegistrantAccessPermission> permissions) {
+ teamsPalette.setObjects(all, permissions);
+ }
+
+ public void setRepositories(List<RepositoryModel> repositories) {
+ repositoryNames.clear();
+ for (RepositoryModel repository : repositories) {
+ // force repo names to lowercase
+ // this means that repository name checking for rpc creation
+ // is case-insensitive, regardless of the Gitblit server's
+ // filesystem
+ repositoryNames.add(repository.name.toLowerCase());
+ }
+ }
+
+ public void setFederationSets(List<String> all, List<String> selected) {
+ setsPalette.setObjects(all, selected);
+ }
+
+ public void setIndexedBranches(List<String> all, List<String> selected) {
+ indexedBranchesPalette.setObjects(all, selected);
+ }
+
+ public void setPreReceiveScripts(List<String> all, List<String> inherited,
+ List<String> selected) {
+ preReceivePalette.setObjects(all, selected);
+ showInherited(inherited, preReceiveInherited);
+ }
+
+ public void setPostReceiveScripts(List<String> all, List<String> inherited,
+ List<String> selected) {
+ postReceivePalette.setObjects(all, selected);
+ showInherited(inherited, postReceiveInherited);
+ }
+
+ private void showInherited(List<String> list, JLabel label) {
+ StringBuilder sb = new StringBuilder();
+ if (list != null && list.size() > 0) {
+ sb.append("<html><body><b>INHERITED</b><ul style=\"margin-left:5px;list-style-type: none;\">");
+ for (String script : list) {
+ sb.append("<li>").append(script).append("</li>");
+ }
+ sb.append("</ul></body></html>");
+ }
+ label.setText(sb.toString());
+ }
+
+ public RepositoryModel getRepository() {
+ if (canceled) {
+ return null;
+ }
+ return repository;
+ }
+
+ public List<RegistrantAccessPermission> getUserAccessPermissions() {
+ return usersPalette.getPermissions();
+ }
+
+ public List<RegistrantAccessPermission> getTeamAccessPermissions() {
+ return teamsPalette.getPermissions();
+ }
+
+ public void setCustomFields(RepositoryModel repository, Map<String, String> customFields) {
+ customFieldsPanel.removeAll();
+ customTextfields = new ArrayList<JTextField>();
+
+ final Insets insets = new Insets(5, 5, 5, 5);
+ JPanel fields = new JPanel(new GridLayout(0, 1, 0, 5)) {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Insets getInsets() {
+ return insets;
+ }
+ };
+
+ for (Map.Entry<String, String> entry : customFields.entrySet()) {
+ String field = entry.getKey();
+ String value = "";
+ if (repository.customFields != null && repository.customFields.containsKey(field)) {
+ value = repository.customFields.get(field);
+ }
+ JTextField textField = new JTextField(value);
+ textField.setName(field);
+
+ textField.setPreferredSize(new Dimension(450, 26));
+
+ fields.add(newFieldPanel(entry.getValue(), 250, textField));
+
+ customTextfields.add(textField);
+ }
+ JScrollPane jsp = new JScrollPane(fields);
+ jsp.getVerticalScrollBar().setBlockIncrement(100);
+ jsp.getVerticalScrollBar().setUnitIncrement(100);
+ jsp.setViewportBorder(null);
+ customFieldsPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
+ customFieldsPanel.add(jsp);
+ }
+
+ /**
+ * ListCellRenderer to display descriptive text about the access
+ * restriction.
+ *
+ */
+ private class AccessRestrictionRenderer extends DefaultListCellRenderer {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Component getListCellRendererComponent(JList list, Object value,
+ int index, boolean isSelected, boolean cellHasFocus) {
+ super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
+
+ if (value instanceof AccessRestrictionType) {
+ AccessRestrictionType restriction = (AccessRestrictionType) value;
+ switch (restriction) {
+ case NONE:
+ setText(Translation.get("gb.notRestricted"));
+ break;
+ case PUSH:
+ setText(Translation.get("gb.pushRestricted"));
+ break;
+ case CLONE:
+ setText(Translation.get("gb.cloneRestricted"));
+ break;
+ case VIEW:
+ setText(Translation.get("gb.viewRestricted"));
+ break;
+ }
+ } else {
+ setText(value.toString());
+ }
+ return this;
+ }
+ }
+
+ /**
+ * ListCellRenderer to display descriptive text about the federation
+ * strategy.
+ */
+ private class FederationStrategyRenderer extends JLabel implements
+ ListCellRenderer {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Component getListCellRendererComponent(JList list, Object value,
+ int index, boolean isSelected, boolean cellHasFocus) {
+ if (value instanceof FederationStrategy) {
+ FederationStrategy strategy = (FederationStrategy) value;
+ switch (strategy) {
+ case EXCLUDE:
+ setText(Translation.get("gb.excludeFromFederation"));
+ break;
+ case FEDERATE_THIS:
+ setText(Translation.get("gb.federateThis"));
+ break;
+ case FEDERATE_ORIGIN:
+ setText(Translation.get("gb.federateOrigin"));
+ break;
+ }
+ } else {
+ setText(value.toString());
+ }
+ return this;
+ }
+ }
+}
diff --git a/src/main/java/com/gitblit/client/EditTeamDialog.java b/src/main/java/com/gitblit/client/EditTeamDialog.java
new file mode 100644
index 00000000..4d7af261
--- /dev/null
+++ b/src/main/java/com/gitblit/client/EditTeamDialog.java
@@ -0,0 +1,397 @@
+/*
+ * 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.JCheckBox;
+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.Constants.AuthorizationControl;
+import com.gitblit.Constants.RegistrantType;
+import com.gitblit.models.RegistrantAccessPermission;
+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 JCheckBox canAdminCheckbox;
+
+ private JCheckBox canForkCheckbox;
+
+ private JCheckBox canCreateCheckbox;
+
+ private JTextField mailingListsField;
+
+ private RegistrantPermissionsPanel repositoryPalette;
+
+ private JPalette<String> userPalette;
+
+ private JPalette<String> preReceivePalette;
+
+ private JLabel preReceiveInherited;
+
+ private JPalette<String> postReceivePalette;
+
+ private JLabel postReceiveInherited;
+
+ private Set<String> 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<String>();
+ 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);
+
+ canAdminCheckbox = new JCheckBox(Translation.get("gb.canAdminDescription"), aTeam.canAdmin);
+ canForkCheckbox = new JCheckBox(Translation.get("gb.canForkDescription"), aTeam.canFork);
+ canCreateCheckbox = new JCheckBox(Translation.get("gb.canCreateDescription"), aTeam.canCreate);
+
+ mailingListsField = new JTextField(aTeam.mailingLists == null ? ""
+ : StringUtils.flattenStrings(aTeam.mailingLists, " "), 50);
+
+ JPanel fieldsPanel = new JPanel(new GridLayout(0, 1));
+ fieldsPanel.add(newFieldPanel(Translation.get("gb.teamName"), teamnameField));
+ fieldsPanel.add(newFieldPanel(Translation.get("gb.canAdmin"), canAdminCheckbox));
+ fieldsPanel.add(newFieldPanel(Translation.get("gb.canFork"), canForkCheckbox));
+ fieldsPanel.add(newFieldPanel(Translation.get("gb.canCreate"), canCreateCheckbox));
+
+ fieldsPanel.add(newFieldPanel(Translation.get("gb.mailingLists"), mailingListsField));
+
+ final Insets _insets = new Insets(5, 5, 5, 5);
+ repositoryPalette = new RegistrantPermissionsPanel(RegistrantType.REPOSITORY);
+ userPalette = new JPalette<String>();
+ userPalette.setEnabled(settings.supportsTeamMembershipChanges);
+
+ 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);
+
+ preReceivePalette = new JPalette<String>(true);
+ preReceiveInherited = new JLabel();
+ JPanel preReceivePanel = new JPanel(new BorderLayout(5, 5));
+ preReceivePanel.add(preReceivePalette, BorderLayout.CENTER);
+ preReceivePanel.add(preReceiveInherited, BorderLayout.WEST);
+
+ postReceivePalette = new JPalette<String>(true);
+ postReceiveInherited = new JLabel();
+ JPanel postReceivePanel = new JPanel(new BorderLayout(5, 5));
+ postReceivePanel.add(postReceivePalette, BorderLayout.CENTER);
+ postReceivePanel.add(postReceiveInherited, BorderLayout.WEST);
+
+ 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);
+ panel.addTab(Translation.get("gb.preReceiveScripts"), preReceivePanel);
+ panel.addTab(Translation.get("gb.postReceiveScripts"), postReceivePanel);
+
+ 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.canAdmin = canAdminCheckbox.isSelected();
+ team.canFork = canForkCheckbox.isSelected();
+ team.canCreate = canCreateCheckbox.isSelected();
+
+ String ml = mailingListsField.getText();
+ if (!StringUtils.isEmpty(ml)) {
+ Set<String> list = new HashSet<String>();
+ for (String address : ml.split("(,|\\s)")) {
+ if (StringUtils.isEmpty(address)) {
+ continue;
+ }
+ list.add(address.toLowerCase());
+ }
+ team.mailingLists.clear();
+ team.mailingLists.addAll(list);
+ }
+
+ for (RegistrantAccessPermission rp : repositoryPalette.getPermissions()) {
+ team.setRepositoryPermission(rp.registrant, rp.permission);
+ }
+
+ team.users.clear();
+ team.users.addAll(userPalette.getSelections());
+
+ team.preReceiveScripts.clear();
+ team.preReceiveScripts.addAll(preReceivePalette.getSelections());
+
+ team.postReceiveScripts.clear();
+ team.postReceiveScripts.addAll(postReceivePalette.getSelections());
+
+ return true;
+ }
+
+ private void error(String message) {
+ JOptionPane.showMessageDialog(EditTeamDialog.this, message, Translation.get("gb.error"),
+ JOptionPane.ERROR_MESSAGE);
+ }
+
+ public void setTeams(List<TeamModel> teams) {
+ teamnames.clear();
+ for (TeamModel team : teams) {
+ teamnames.add(team.name.toLowerCase());
+ }
+ }
+
+ public void setRepositories(List<RepositoryModel> repositories, List<RegistrantAccessPermission> permissions) {
+ List<String> restricted = new ArrayList<String>();
+ for (RepositoryModel repo : repositories) {
+ if (repo.accessRestriction.exceeds(AccessRestrictionType.NONE)
+ && repo.authorizationControl.equals(AuthorizationControl.NAMED)) {
+ restricted.add(repo.name);
+ }
+ }
+ StringUtils.sortRepositorynames(restricted);
+
+ List<String> list = new ArrayList<String>();
+ // repositories
+ list.add(".*");
+ // all repositories excluding personal repositories
+ list.add("[^~].*");
+ String lastProject = null;
+ for (String repo : restricted) {
+ String projectPath = StringUtils.getFirstPathElement(repo);
+ if (lastProject == null || !lastProject.equalsIgnoreCase(projectPath)) {
+ lastProject = projectPath;
+ if (!StringUtils.isEmpty(projectPath)) {
+ // regex for all repositories within a project
+ list.add(projectPath + "/.*");
+ }
+ list.add(repo);
+ }
+ }
+
+ // remove repositories for which user already has a permission
+ if (permissions == null) {
+ permissions = new ArrayList<RegistrantAccessPermission>();
+ } else {
+ for (RegistrantAccessPermission rp : permissions) {
+ list.remove(rp.registrant);
+ }
+ }
+ repositoryPalette.setObjects(list, permissions);
+ }
+
+ public void setUsers(List<String> users, List<String> selected) {
+ Collections.sort(users);
+ if (selected != null) {
+ Collections.sort(selected);
+ }
+ userPalette.setObjects(users, selected);
+ }
+
+ public void setPreReceiveScripts(List<String> unused, List<String> inherited,
+ List<String> selected) {
+ Collections.sort(unused);
+ if (selected != null) {
+ Collections.sort(selected);
+ }
+ preReceivePalette.setObjects(unused, selected);
+ showInherited(inherited, preReceiveInherited);
+ }
+
+ public void setPostReceiveScripts(List<String> unused, List<String> inherited,
+ List<String> selected) {
+ Collections.sort(unused);
+ if (selected != null) {
+ Collections.sort(selected);
+ }
+ postReceivePalette.setObjects(unused, selected);
+ showInherited(inherited, postReceiveInherited);
+ }
+
+ private void showInherited(List<String> list, JLabel label) {
+ StringBuilder sb = new StringBuilder();
+ if (list != null && list.size() > 0) {
+ sb.append("<html><body><b>INHERITED</b><ul style=\"margin-left:5px;list-style-type: none;\">");
+ for (String script : list) {
+ sb.append("<li>").append(script).append("</li>");
+ }
+ sb.append("</ul></body></html>");
+ }
+ label.setText(sb.toString());
+ }
+
+ public TeamModel getTeam() {
+ if (canceled) {
+ return null;
+ }
+ return team;
+ }
+}
diff --git a/src/main/java/com/gitblit/client/EditUserDialog.java b/src/main/java/com/gitblit/client/EditUserDialog.java
new file mode 100644
index 00000000..0400f5c9
--- /dev/null
+++ b/src/main/java/com/gitblit/client/EditUserDialog.java
@@ -0,0 +1,466 @@
+/*
+ * 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.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+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.JRootPane;
+import javax.swing.JTabbedPane;
+import javax.swing.JTextField;
+import javax.swing.KeyStroke;
+
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.AuthorizationControl;
+import com.gitblit.Constants.PermissionType;
+import com.gitblit.Constants.RegistrantType;
+import com.gitblit.Keys;
+import com.gitblit.models.RegistrantAccessPermission;
+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;
+
+public class EditUserDialog extends JDialog {
+
+ private static final long serialVersionUID = 1L;
+
+ private final String username;
+
+ private final UserModel user;
+
+ private final ServerSettings settings;
+
+ private boolean isCreate;
+
+ private boolean canceled = true;
+
+ private JTextField usernameField;
+
+ private JPasswordField passwordField;
+
+ private JPasswordField confirmPasswordField;
+
+ private JTextField displayNameField;
+
+ private JTextField emailAddressField;
+
+ private JCheckBox canAdminCheckbox;
+
+ private JCheckBox canForkCheckbox;
+
+ private JCheckBox canCreateCheckbox;
+
+ private JCheckBox notFederatedCheckbox;
+
+ private JTextField organizationalUnitField;
+
+ private JTextField organizationField;
+
+ private JTextField localityField;
+
+ private JTextField stateProvinceField;
+
+ private JTextField countryCodeField;
+
+ private RegistrantPermissionsPanel repositoryPalette;
+
+ private JPalette<TeamModel> teamsPalette;
+
+ private Set<String> usernames;
+
+ public EditUserDialog(int protocolVersion, ServerSettings settings) {
+ this(protocolVersion, new UserModel(""), settings);
+ this.isCreate = true;
+ setTitle(Translation.get("gb.newUser"));
+ }
+
+ public EditUserDialog(int protocolVersion, UserModel anUser, ServerSettings settings) {
+ super();
+ this.username = anUser.username;
+ this.user = new UserModel("");
+ this.settings = settings;
+ this.usernames = new HashSet<String>();
+ this.isCreate = false;
+ initialize(protocolVersion, anUser);
+ setModal(true);
+ setTitle(Translation.get("gb.edit") + ": " + anUser.username);
+ 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, 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);
+ displayNameField = new JTextField(anUser.displayName == null ? "" : anUser.displayName, 25);
+ emailAddressField = new JTextField(anUser.emailAddress == null ? "" : anUser.emailAddress, 25);
+ canAdminCheckbox = new JCheckBox(Translation.get("gb.canAdminDescription"), anUser.canAdmin);
+ canForkCheckbox = new JCheckBox(Translation.get("gb.canForkDescription"), anUser.canFork);
+ canCreateCheckbox = new JCheckBox(Translation.get("gb.canCreateDescription"), anUser.canCreate);
+ notFederatedCheckbox = new JCheckBox(
+ Translation.get("gb.excludeFromFederationDescription"),
+ anUser.excludeFromFederation);
+
+ organizationalUnitField = new JTextField(anUser.organizationalUnit == null ? "" : anUser.organizationalUnit, 25);
+ organizationField = new JTextField(anUser.organization == null ? "" : anUser.organization, 25);
+ localityField = new JTextField(anUser.locality == null ? "" : anUser.locality, 25);
+ stateProvinceField = new JTextField(anUser.stateProvince == null ? "" : anUser.stateProvince, 25);
+ countryCodeField = new JTextField(anUser.countryCode == null ? "" : anUser.countryCode, 15);
+
+ // credentials are optionally controlled by 3rd-party authentication
+ usernameField.setEnabled(settings.supportsCredentialChanges);
+ passwordField.setEnabled(settings.supportsCredentialChanges);
+ confirmPasswordField.setEnabled(settings.supportsCredentialChanges);
+
+ displayNameField.setEnabled(settings.supportsDisplayNameChanges);
+ emailAddressField.setEnabled(settings.supportsEmailAddressChanges);
+
+ organizationalUnitField.setEnabled(settings.supportsDisplayNameChanges);
+ organizationField.setEnabled(settings.supportsDisplayNameChanges);
+ localityField.setEnabled(settings.supportsDisplayNameChanges);
+ stateProvinceField.setEnabled(settings.supportsDisplayNameChanges);
+ countryCodeField.setEnabled(settings.supportsDisplayNameChanges);
+
+ JPanel fieldsPanel = new JPanel(new GridLayout(0, 1));
+ fieldsPanel.add(newFieldPanel(Translation.get("gb.username"), usernameField));
+ fieldsPanel.add(newFieldPanel(Translation.get("gb.password"), passwordField));
+ fieldsPanel.add(newFieldPanel(Translation.get("gb.confirmPassword"), confirmPasswordField));
+ fieldsPanel.add(newFieldPanel(Translation.get("gb.displayName"), displayNameField));
+ fieldsPanel.add(newFieldPanel(Translation.get("gb.emailAddress"), emailAddressField));
+ fieldsPanel.add(newFieldPanel(Translation.get("gb.canAdmin"), canAdminCheckbox));
+ fieldsPanel.add(newFieldPanel(Translation.get("gb.canFork"), canForkCheckbox));
+ fieldsPanel.add(newFieldPanel(Translation.get("gb.canCreate"), canCreateCheckbox));
+ fieldsPanel.add(newFieldPanel(Translation.get("gb.excludeFromFederation"),
+ notFederatedCheckbox));
+
+ JPanel attributesPanel = new JPanel(new GridLayout(0, 1, 5, 2));
+ attributesPanel.add(newFieldPanel(Translation.get("gb.organizationalUnit") + " (OU)", organizationalUnitField));
+ attributesPanel.add(newFieldPanel(Translation.get("gb.organization") + " (O)", organizationField));
+ attributesPanel.add(newFieldPanel(Translation.get("gb.locality") + " (L)", localityField));
+ attributesPanel.add(newFieldPanel(Translation.get("gb.stateProvince") + " (ST)", stateProvinceField));
+ attributesPanel.add(newFieldPanel(Translation.get("gb.countryCode") + " (C)", countryCodeField));
+
+ final Insets _insets = new Insets(5, 5, 5, 5);
+ repositoryPalette = new RegistrantPermissionsPanel(RegistrantType.REPOSITORY);
+ teamsPalette = new JPalette<TeamModel>();
+ teamsPalette.setEnabled(settings.supportsTeamMembershipChanges);
+
+ JPanel fieldsPanelTop = new JPanel(new BorderLayout());
+ fieldsPanelTop.add(fieldsPanel, BorderLayout.NORTH);
+
+ JPanel attributesPanelTop = new JPanel(new BorderLayout());
+ attributesPanelTop.add(attributesPanel, 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);
+ panel.addTab(Translation.get("gb.attributes"), attributesPanelTop);
+ 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() {
+ 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() {
+ if (StringUtils.isEmpty(usernameField.getText())) {
+ error("Please enter a username!");
+ return false;
+ }
+ String uname = usernameField.getText().toLowerCase();
+ boolean rename = false;
+ // verify username uniqueness on create
+ if (isCreate) {
+ if (usernames.contains(uname)) {
+ error(MessageFormat.format("Username ''{0}'' is unavailable.", uname));
+ return false;
+ }
+ } else {
+ // check rename collision
+ rename = !StringUtils.isEmpty(username) && !username.equalsIgnoreCase(uname);
+ if (rename) {
+ if (usernames.contains(uname)) {
+ error(MessageFormat.format(
+ "Failed to rename ''{0}'' because ''{1}'' already exists.", username,
+ uname));
+ return false;
+ }
+ }
+ }
+ user.username = uname;
+
+ int minLength = settings.get(Keys.realm.minPasswordLength).getInteger(5);
+ if (minLength < 4) {
+ minLength = 4;
+ }
+
+ String password = new String(passwordField.getPassword());
+ if (StringUtils.isEmpty(password) || password.length() < minLength) {
+ error(MessageFormat.format("Password is too short. Minimum length is {0} characters.",
+ minLength));
+ return false;
+ }
+ if (!password.toUpperCase().startsWith(StringUtils.MD5_TYPE)
+ && !password.toUpperCase().startsWith(StringUtils.COMBINED_MD5_TYPE)) {
+ String cpw = new String(confirmPasswordField.getPassword());
+ if (cpw == null || cpw.length() != password.length()) {
+ error("Please confirm the password!");
+ return false;
+ }
+ if (!password.equals(cpw)) {
+ error("Passwords do not match!");
+ return false;
+ }
+
+ String type = settings.get(Keys.realm.passwordStorage).getString("md5");
+ if (type.equalsIgnoreCase("md5")) {
+ // store MD5 digest of password
+ user.password = StringUtils.MD5_TYPE + StringUtils.getMD5(password);
+ } else if (type.equalsIgnoreCase("combined-md5")) {
+ // store MD5 digest of username+password
+ user.password = StringUtils.COMBINED_MD5_TYPE
+ + StringUtils.getMD5(user.username + password);
+ } else {
+ // plain-text password
+ user.password = password;
+ }
+ } else if (rename && password.toUpperCase().startsWith(StringUtils.COMBINED_MD5_TYPE)) {
+ error("Gitblit is configured for combined-md5 password hashing. You must enter a new password on account rename.");
+ return false;
+ } else {
+ // no change in password
+ user.password = password;
+ }
+
+ user.displayName = displayNameField.getText().trim();
+ user.emailAddress = emailAddressField.getText().trim();
+
+ user.canAdmin = canAdminCheckbox.isSelected();
+ user.canFork = canForkCheckbox.isSelected();
+ user.canCreate = canCreateCheckbox.isSelected();
+ user.excludeFromFederation = notFederatedCheckbox.isSelected();
+
+ user.organizationalUnit = organizationalUnitField.getText().trim();
+ user.organization = organizationField.getText().trim();
+ user.locality = localityField.getText().trim();
+ user.stateProvince = stateProvinceField.getText().trim();
+ user.countryCode = countryCodeField.getText().trim();
+
+ for (RegistrantAccessPermission rp : repositoryPalette.getPermissions()) {
+ user.setRepositoryPermission(rp.registrant, rp.permission);
+ }
+
+ user.teams.clear();
+ user.teams.addAll(teamsPalette.getSelections());
+ return true;
+ }
+
+ private void error(String message) {
+ JOptionPane.showMessageDialog(EditUserDialog.this, message, Translation.get("gb.error"),
+ JOptionPane.ERROR_MESSAGE);
+ }
+
+ public void setUsers(List<UserModel> users) {
+ usernames.clear();
+ for (UserModel user : users) {
+ usernames.add(user.username.toLowerCase());
+ }
+ }
+
+ public void setRepositories(List<RepositoryModel> repositories, List<RegistrantAccessPermission> permissions) {
+ Map<String, RepositoryModel> repoMap = new HashMap<String, RepositoryModel>();
+ List<String> restricted = new ArrayList<String>();
+ for (RepositoryModel repo : repositories) {
+ // exclude Owner or personal repositories
+ if (!repo.isOwner(username) && !repo.isUsersPersonalRepository(username)) {
+ if (repo.accessRestriction.exceeds(AccessRestrictionType.NONE)
+ && repo.authorizationControl.equals(AuthorizationControl.NAMED)) {
+ restricted.add(repo.name);
+ }
+ }
+ repoMap.put(repo.name.toLowerCase(), repo);
+ }
+ StringUtils.sortRepositorynames(restricted);
+
+ List<String> list = new ArrayList<String>();
+ // repositories
+ list.add(".*");
+ // all repositories excluding personal repositories
+ list.add("[^~].*");
+ String lastProject = null;
+ for (String repo : restricted) {
+ String projectPath = StringUtils.getFirstPathElement(repo).toLowerCase();
+ if (lastProject == null || !lastProject.equalsIgnoreCase(projectPath)) {
+ lastProject = projectPath;
+ if (!StringUtils.isEmpty(projectPath)) {
+ // regex for all repositories within a project
+ list.add(projectPath + "/.*");
+ }
+ }
+ list.add(repo);
+ }
+
+ // remove repositories for which user already has a permission
+ if (permissions == null) {
+ permissions = new ArrayList<RegistrantAccessPermission>();
+ } else {
+ for (RegistrantAccessPermission rp : permissions) {
+ list.remove(rp.registrant.toLowerCase());
+ }
+ }
+
+ // update owner and missing permissions for editing
+ for (RegistrantAccessPermission permission : permissions) {
+ if (permission.mutable && PermissionType.EXPLICIT.equals(permission.permissionType)) {
+ // Ensure this is NOT an owner permission - which is non-editable
+ // We don't know this from within the usermodel, ownership is a
+ // property of a repository.
+ RepositoryModel rm = repoMap.get(permission.registrant.toLowerCase());
+ if (rm == null) {
+ permission.permissionType = PermissionType.MISSING;
+ permission.mutable = false;
+ continue;
+ }
+ boolean isOwner = rm.isOwner(username);
+ if (isOwner) {
+ permission.permissionType = PermissionType.OWNER;
+ permission.mutable = false;
+ }
+ }
+ }
+
+ repositoryPalette.setObjects(list, permissions);
+ }
+
+ public void setTeams(List<TeamModel> teams, List<TeamModel> selected) {
+ Collections.sort(teams);
+ if (selected != null) {
+ Collections.sort(selected);
+ }
+ teamsPalette.setObjects(teams, selected);
+ }
+
+ public UserModel getUser() {
+ if (canceled) {
+ return null;
+ }
+ return user;
+ }
+}
diff --git a/src/main/java/com/gitblit/client/FeedEntryTableModel.java b/src/main/java/com/gitblit/client/FeedEntryTableModel.java
new file mode 100644
index 00000000..0b0ef178
--- /dev/null
+++ b/src/main/java/com/gitblit/client/FeedEntryTableModel.java
@@ -0,0 +1,123 @@
+/*
+ * 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.Date;
+import java.util.List;
+
+import javax.swing.table.AbstractTableModel;
+
+import com.gitblit.models.FeedEntryModel;
+
+/**
+ * Table model for a list of retrieved feed entries.
+ *
+ * @author James Moger
+ *
+ */
+public class FeedEntryTableModel extends AbstractTableModel {
+
+ private static final long serialVersionUID = 1L;
+
+ List<FeedEntryModel> entries;
+
+ enum Columns {
+ Date, Repository, Branch, Author, Message;
+
+ @Override
+ public String toString() {
+ return name().replace('_', ' ');
+ }
+ }
+
+ public FeedEntryTableModel() {
+ this.entries = new ArrayList<FeedEntryModel>();
+ }
+
+ public void setEntries(List<FeedEntryModel> entries) {
+ this.entries = entries;
+ Collections.sort(entries);
+ }
+
+ @Override
+ public int getRowCount() {
+ return entries.size();
+ }
+
+ @Override
+ public int getColumnCount() {
+ return Columns.values().length;
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ Columns col = Columns.values()[column];
+ switch (col) {
+ case Date:
+ return Translation.get("gb.date");
+ case Repository:
+ return Translation.get("gb.repository");
+ case Branch:
+ return Translation.get("gb.branch");
+ case Author:
+ return Translation.get("gb.author");
+ case Message:
+ return Translation.get("gb.message");
+ }
+ return "";
+ }
+
+ /**
+ * Returns <code>Object.class</code> regardless of <code>columnIndex</code>.
+ *
+ * @param columnIndex
+ * the column being queried
+ * @return the Object.class
+ */
+ public Class<?> getColumnClass(int columnIndex) {
+ if (Columns.Date.ordinal() == columnIndex) {
+ return Date.class;
+ } else if (Columns.Message.ordinal() == columnIndex) {
+ return FeedEntryModel.class;
+ }
+ return String.class;
+ }
+
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ FeedEntryModel entry = entries.get(rowIndex);
+ Columns col = Columns.values()[columnIndex];
+ switch (col) {
+ case Date:
+ return entry.published;
+ case Repository:
+ return entry.repository;
+ case Branch:
+ return entry.branch;
+ case Author:
+ return entry.author;
+ case Message:
+ return entry;
+ }
+ return null;
+ }
+
+ public FeedEntryModel get(int modelRow) {
+ return entries.get(modelRow);
+ }
+}
diff --git a/src/main/java/com/gitblit/client/FeedsPanel.java b/src/main/java/com/gitblit/client/FeedsPanel.java
new file mode 100644
index 00000000..392636e4
--- /dev/null
+++ b/src/main/java/com/gitblit/client/FeedsPanel.java
@@ -0,0 +1,405 @@
+/*
+ * 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.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.RowFilter;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.TableRowSorter;
+
+import com.gitblit.models.FeedEntryModel;
+import com.gitblit.models.FeedModel;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * RSS Feeds Panel displays recent entries and launches the browser to view the
+ * commit. commitdiff, or tree of a commit.
+ *
+ * @author James Moger
+ *
+ */
+public abstract class FeedsPanel extends JPanel {
+
+ private static final long serialVersionUID = 1L;
+
+ private final GitblitClient gitblit;
+
+ private final String ALL = "*";
+
+ private FeedEntryTableModel tableModel;
+
+ private TableRowSorter<FeedEntryTableModel> defaultSorter;
+
+ private HeaderPanel header;
+
+ private JTable table;
+
+ private DefaultComboBoxModel repositoryChoices;
+
+ private JComboBox repositorySelector;
+
+ private DefaultComboBoxModel authorChoices;
+
+ private JComboBox authorSelector;
+
+ private int page;
+
+ private JButton prev;
+
+ private JButton next;
+
+ public FeedsPanel(GitblitClient gitblit) {
+ super();
+ this.gitblit = gitblit;
+ initialize();
+ }
+
+ private void initialize() {
+
+ prev = new JButton("<");
+ prev.setToolTipText(Translation.get("gb.pagePrevious"));
+ prev.setEnabled(false);
+ prev.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ refreshFeeds(--page);
+ }
+ });
+
+ next = new JButton(">");
+ next.setToolTipText(Translation.get("gb.pageNext"));
+ next.setEnabled(false);
+ next.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ refreshFeeds(++page);
+ }
+ });
+
+ JButton refreshFeeds = new JButton(Translation.get("gb.refresh"));
+ refreshFeeds.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ refreshFeeds(0);
+ }
+ });
+
+ final JButton viewCommit = new JButton(Translation.get("gb.view"));
+ viewCommit.setEnabled(false);
+ viewCommit.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ viewCommit();
+ }
+ });
+
+ final JButton viewCommitDiff = new JButton(Translation.get("gb.commitdiff"));
+ viewCommitDiff.setEnabled(false);
+ viewCommitDiff.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ viewCommitDiff();
+ }
+ });
+
+ final JButton viewTree = new JButton(Translation.get("gb.tree"));
+ viewTree.setEnabled(false);
+ viewTree.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ viewTree();
+ }
+ });
+
+ JButton subscribeFeeds = new JButton(Translation.get("gb.subscribe") + "...");
+ subscribeFeeds.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ subscribeFeeds(gitblit.getAvailableFeeds());
+ }
+ });
+
+ JPanel controls = new JPanel(new FlowLayout(FlowLayout.CENTER, Utils.MARGIN, 0));
+ controls.add(refreshFeeds);
+ controls.add(subscribeFeeds);
+ controls.add(viewCommit);
+ controls.add(viewCommitDiff);
+ controls.add(viewTree);
+
+ NameRenderer nameRenderer = new NameRenderer();
+ tableModel = new FeedEntryTableModel();
+ header = new HeaderPanel(Translation.get("gb.activity"), "feed_16x16.png");
+ table = Utils.newTable(tableModel, Utils.DATE_FORMAT);
+ defaultSorter = new TableRowSorter<FeedEntryTableModel>(tableModel);
+ String name = table.getColumnName(FeedEntryTableModel.Columns.Author.ordinal());
+ table.getColumn(name).setCellRenderer(nameRenderer);
+ name = table.getColumnName(FeedEntryTableModel.Columns.Repository.ordinal());
+ table.getColumn(name).setCellRenderer(nameRenderer);
+
+ name = table.getColumnName(FeedEntryTableModel.Columns.Branch.ordinal());
+ table.getColumn(name).setCellRenderer(new BranchRenderer());
+
+ name = table.getColumnName(FeedEntryTableModel.Columns.Message.ordinal());
+ table.getColumn(name).setCellRenderer(new MessageRenderer(gitblit));
+
+ table.addMouseListener(new MouseAdapter() {
+ public void mouseClicked(MouseEvent e) {
+ if (e.getClickCount() == 2) {
+ if (e.isControlDown()) {
+ viewCommitDiff();
+ } else {
+ viewCommit();
+ }
+ }
+ }
+ });
+
+ table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ if (e.getValueIsAdjusting()) {
+ return;
+ }
+ boolean singleSelection = table.getSelectedRowCount() == 1;
+ viewCommit.setEnabled(singleSelection);
+ viewCommitDiff.setEnabled(singleSelection);
+ viewTree.setEnabled(singleSelection);
+ }
+ });
+
+ repositoryChoices = new DefaultComboBoxModel();
+ repositorySelector = new JComboBox(repositoryChoices);
+ repositorySelector.setRenderer(nameRenderer);
+ repositorySelector.setForeground(nameRenderer.getForeground());
+ repositorySelector.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ // repopulate the author list based on repository selection
+ // preserve author selection, if possible
+ String selectedAuthor = null;
+ if (authorSelector.getSelectedIndex() > -1) {
+ selectedAuthor = authorSelector.getSelectedItem().toString();
+ }
+ updateAuthors();
+ if (selectedAuthor != null) {
+ if (authorChoices.getIndexOf(selectedAuthor) > -1) {
+ authorChoices.setSelectedItem(selectedAuthor);
+ }
+ }
+ filterFeeds();
+ }
+ });
+ authorChoices = new DefaultComboBoxModel();
+ authorSelector = new JComboBox(authorChoices);
+ authorSelector.setRenderer(nameRenderer);
+ authorSelector.setForeground(nameRenderer.getForeground());
+ authorSelector.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ filterFeeds();
+ }
+ });
+ JPanel northControls = new JPanel(new FlowLayout(FlowLayout.LEFT, Utils.MARGIN, 0));
+ northControls.add(new JLabel(Translation.get("gb.repository")));
+ northControls.add(repositorySelector);
+ northControls.add(new JLabel(Translation.get("gb.author")));
+ northControls.add(authorSelector);
+// northControls.add(prev);
+// northControls.add(next);
+
+ JPanel northPanel = new JPanel(new BorderLayout(0, Utils.MARGIN));
+ northPanel.add(header, BorderLayout.NORTH);
+ northPanel.add(northControls, BorderLayout.CENTER);
+
+ setLayout(new BorderLayout(Utils.MARGIN, Utils.MARGIN));
+ add(northPanel, BorderLayout.NORTH);
+ add(new JScrollPane(table), BorderLayout.CENTER);
+ add(controls, BorderLayout.SOUTH);
+ }
+
+ @Override
+ public Insets getInsets() {
+ return Utils.INSETS;
+ }
+
+ protected void refreshFeeds(final int page) {
+ this.page = page;
+ GitblitWorker worker = new GitblitWorker(FeedsPanel.this, null) {
+ @Override
+ protected Boolean doRequest() throws IOException {
+ gitblit.refreshSubscribedFeeds(page);
+ return true;
+ }
+
+ @Override
+ protected void onSuccess() {
+ updateTable(false);
+ }
+ };
+ worker.execute();
+ }
+
+ protected abstract void subscribeFeeds(List<FeedModel> feeds);
+
+ protected void updateTable(boolean pack) {
+ tableModel.entries.clear();
+ tableModel.entries.addAll(gitblit.getSyndicatedEntries());
+ tableModel.fireTableDataChanged();
+ header.setText(Translation.get("gb.activity") + " ("
+ + gitblit.getSyndicatedEntries().size() + (page > 0 ? (", pg " + (page + 1)) : "")
+ + ")");
+ if (pack) {
+ Utils.packColumns(table, Utils.MARGIN);
+ }
+ table.scrollRectToVisible(new Rectangle(table.getCellRect(0, 0, true)));
+
+ if (page == 0) {
+ // determine unique repositories
+ Set<String> uniqueRepositories = new HashSet<String>();
+ for (FeedEntryModel entry : tableModel.entries) {
+ uniqueRepositories.add(entry.repository);
+ }
+
+ // repositories
+ List<String> sortedRespositories = new ArrayList<String>(uniqueRepositories);
+ StringUtils.sortRepositorynames(sortedRespositories);
+ repositoryChoices.removeAllElements();
+ repositoryChoices.addElement(ALL);
+ for (String repo : sortedRespositories) {
+ repositoryChoices.addElement(repo);
+ }
+ }
+
+ // update pagination buttons
+ next.setEnabled(tableModel.entries.size() > 0);
+ prev.setEnabled(page > 0);
+ }
+
+ private void updateAuthors() {
+ String repository = ALL;
+ if (repositorySelector.getSelectedIndex() > -1) {
+ repository = repositorySelector.getSelectedItem().toString();
+ }
+
+ // determine unique repositories and authors
+ Set<String> uniqueAuthors = new HashSet<String>();
+ for (FeedEntryModel entry : tableModel.entries) {
+ if (repository.equals(ALL) || entry.repository.equalsIgnoreCase(repository)) {
+ uniqueAuthors.add(entry.author);
+ }
+ }
+ // authors
+ List<String> sortedAuthors = new ArrayList<String>(uniqueAuthors);
+ Collections.sort(sortedAuthors);
+ authorChoices.removeAllElements();
+ authorChoices.addElement(ALL);
+ for (String author : sortedAuthors) {
+ authorChoices.addElement(author);
+ }
+ }
+
+ protected FeedEntryModel getSelectedSyndicatedEntry() {
+ int viewRow = table.getSelectedRow();
+ int modelRow = table.convertRowIndexToModel(viewRow);
+ FeedEntryModel entry = tableModel.get(modelRow);
+ return entry;
+ }
+
+ protected void viewCommit() {
+ FeedEntryModel entry = getSelectedSyndicatedEntry();
+ Utils.browse(entry.link);
+ }
+
+ protected void viewCommitDiff() {
+ FeedEntryModel entry = getSelectedSyndicatedEntry();
+ Utils.browse(entry.link.replace("/commit/", "/commitdiff/"));
+ }
+
+ protected void viewTree() {
+ FeedEntryModel entry = getSelectedSyndicatedEntry();
+ Utils.browse(entry.link.replace("/commit/", "/tree/"));
+ }
+
+ protected void filterFeeds() {
+ final String repository;
+ if (repositorySelector.getSelectedIndex() > -1) {
+ repository = repositorySelector.getSelectedItem().toString();
+ } else {
+ repository = ALL;
+ }
+
+ final String author;
+ if (authorSelector.getSelectedIndex() > -1) {
+ author = authorSelector.getSelectedItem().toString();
+ } else {
+ author = ALL;
+ }
+
+ if (repository.equals(ALL) && author.equals(ALL)) {
+ table.setRowSorter(defaultSorter);
+ return;
+ }
+ final int repositoryIndex = FeedEntryTableModel.Columns.Repository.ordinal();
+ final int authorIndex = FeedEntryTableModel.Columns.Author.ordinal();
+ RowFilter<FeedEntryTableModel, Object> containsFilter;
+ if (repository.equals(ALL)) {
+ // author filter
+ containsFilter = new RowFilter<FeedEntryTableModel, Object>() {
+ public boolean include(
+ Entry<? extends FeedEntryTableModel, ? extends Object> entry) {
+ return entry.getStringValue(authorIndex).equalsIgnoreCase(author);
+ }
+ };
+ } else if (author.equals(ALL)) {
+ // repository filter
+ containsFilter = new RowFilter<FeedEntryTableModel, Object>() {
+ public boolean include(
+ Entry<? extends FeedEntryTableModel, ? extends Object> entry) {
+ return entry.getStringValue(repositoryIndex).equalsIgnoreCase(repository);
+ }
+ };
+ } else {
+ // repository-author filter
+ containsFilter = new RowFilter<FeedEntryTableModel, Object>() {
+ public boolean include(
+ Entry<? extends FeedEntryTableModel, ? extends Object> entry) {
+ boolean authorMatch = entry.getStringValue(authorIndex)
+ .equalsIgnoreCase(author);
+ boolean repositoryMatch = entry.getStringValue(repositoryIndex)
+ .equalsIgnoreCase(repository);
+ return authorMatch && repositoryMatch;
+ }
+ };
+ }
+ TableRowSorter<FeedEntryTableModel> sorter = new TableRowSorter<FeedEntryTableModel>(
+ tableModel);
+ sorter.setRowFilter(containsFilter);
+ table.setRowSorter(sorter);
+ }
+}
diff --git a/src/main/java/com/gitblit/client/FeedsTableModel.java b/src/main/java/com/gitblit/client/FeedsTableModel.java
new file mode 100644
index 00000000..0979a4c9
--- /dev/null
+++ b/src/main/java/com/gitblit/client/FeedsTableModel.java
@@ -0,0 +1,122 @@
+/*
+ * 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.FeedModel;
+
+/**
+ * Table model of a list of available feeds.
+ *
+ * @author James Moger
+ *
+ */
+public class FeedsTableModel extends AbstractTableModel {
+
+ private static final long serialVersionUID = 1L;
+
+ List<FeedModel> list;
+
+ enum Columns {
+ Subscribed, Repository, Branch;
+
+ @Override
+ public String toString() {
+ return name().replace('_', ' ');
+ }
+ }
+
+ public FeedsTableModel() {
+ this(new ArrayList<FeedModel>());
+ }
+
+ public FeedsTableModel(List<FeedModel> feeds) {
+ this.list = feeds;
+ 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 Repository:
+ return Translation.get("gb.repository");
+ case Branch:
+ return Translation.get("gb.branch");
+ }
+ return "";
+ }
+
+ /**
+ * Returns <code>Object.class</code> regardless of <code>columnIndex</code>.
+ *
+ * @param columnIndex
+ * the column being queried
+ * @return the Object.class
+ */
+ public Class<?> getColumnClass(int columnIndex) {
+ Columns col = Columns.values()[columnIndex];
+ switch (col) {
+ case Subscribed:
+ return Boolean.class;
+ }
+ return String.class;
+ }
+
+ @Override
+ public boolean isCellEditable(int rowIndex, int columnIndex) {
+ Columns col = Columns.values()[columnIndex];
+ switch (col) {
+ case Subscribed:
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ FeedModel model = list.get(rowIndex);
+ Columns col = Columns.values()[columnIndex];
+ switch (col) {
+ case Repository:
+ return model.repository;
+ case Branch:
+ return model.branch;
+ case Subscribed:
+ return model.subscribed;
+ }
+ return null;
+ }
+
+ public FeedModel get(int modelRow) {
+ return list.get(modelRow);
+ }
+}
diff --git a/src/main/java/com/gitblit/client/GitblitClient.java b/src/main/java/com/gitblit/client/GitblitClient.java
new file mode 100644
index 00000000..cc7d58a6
--- /dev/null
+++ b/src/main/java/com/gitblit/client/GitblitClient.java
@@ -0,0 +1,717 @@
+/*
+ * 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.io.IOException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import com.gitblit.Constants;
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.Constants.AuthorizationControl;
+import com.gitblit.Constants.PermissionType;
+import com.gitblit.Constants.RegistrantType;
+import com.gitblit.GitBlitException.ForbiddenException;
+import com.gitblit.GitBlitException.NotAllowedException;
+import com.gitblit.GitBlitException.UnauthorizedException;
+import com.gitblit.GitBlitException.UnknownRequestException;
+import com.gitblit.Keys;
+import com.gitblit.models.FederationModel;
+import com.gitblit.models.FeedEntryModel;
+import com.gitblit.models.FeedModel;
+import com.gitblit.models.RegistrantAccessPermission;
+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.ArrayUtils;
+import com.gitblit.utils.RpcUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.SyndicationUtils;
+
+/**
+ * GitblitClient is a object that retrieves data from a Gitblit server, caches
+ * it for local operations, and allows updating or creating Gitblit objects.
+ *
+ * @author James Moger
+ *
+ */
+public class GitblitClient implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final Date NEVER = new Date(0);
+
+ protected final GitblitRegistration reg;
+
+ public final String url;
+
+ public final String account;
+
+ private final char[] password;
+
+ private volatile int protocolVersion;
+
+ private volatile boolean allowManagement;
+
+ private volatile boolean allowAdministration;
+
+ private volatile ServerSettings settings;
+
+ private final List<RepositoryModel> allRepositories;
+
+ private final List<UserModel> allUsers;
+
+ private final List<TeamModel> allTeams;
+
+ private final List<FederationModel> federationRegistrations;
+
+ private final List<FeedModel> availableFeeds;
+
+ private final List<FeedEntryModel> syndicatedEntries;
+
+ private final Set<String> subscribedRepositories;
+
+ private ServerStatus status;
+
+ public GitblitClient(GitblitRegistration reg) {
+ this.reg = reg;
+ this.url = reg.url;
+ this.account = reg.account;
+ this.password = reg.password;
+
+ this.allUsers = new ArrayList<UserModel>();
+ this.allTeams = new ArrayList<TeamModel>();
+ this.allRepositories = new ArrayList<RepositoryModel>();
+ this.federationRegistrations = new ArrayList<FederationModel>();
+ this.availableFeeds = new ArrayList<FeedModel>();
+ this.syndicatedEntries = new ArrayList<FeedEntryModel>();
+ this.subscribedRepositories = new HashSet<String>();
+ }
+
+ public void login() throws IOException {
+ protocolVersion = RpcUtils.getProtocolVersion(url, account, password);
+ refreshSettings();
+ refreshAvailableFeeds();
+ refreshRepositories();
+ refreshSubscribedFeeds(0);
+
+ try {
+ // 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) {
+ } catch (NotAllowedException e) {
+ } catch (UnknownRequestException e) {
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ try {
+ // credentials may not have administrator access
+ // or server may have disabled rpc administration
+ refreshStatus();
+ allowAdministration = true;
+ } catch (UnauthorizedException e) {
+ } catch (ForbiddenException e) {
+ } catch (NotAllowedException e) {
+ } catch (UnknownRequestException e) {
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public int getProtocolVersion() {
+ return protocolVersion;
+ }
+
+ public boolean allowManagement() {
+ return allowManagement;
+ }
+
+ public boolean allowAdministration() {
+ return allowAdministration;
+ }
+
+ public boolean isOwner(RepositoryModel model) {
+ return model.isOwner(account);
+ }
+
+ public String getURL(String action, String repository, String objectId) {
+ boolean mounted = settings.get(Keys.web.mountParameters).getBoolean(true);
+ StringBuilder sb = new StringBuilder();
+ sb.append(url);
+ sb.append('/');
+ sb.append(action);
+ sb.append('/');
+ if (mounted) {
+ // mounted url/action/repository/objectId
+ sb.append(StringUtils.encodeURL(repository));
+ if (!StringUtils.isEmpty(objectId)) {
+ sb.append('/');
+ sb.append(objectId);
+ }
+ return sb.toString();
+ } else {
+ // parameterized url/action/&r=repository&h=objectId
+ sb.append("?r=");
+ sb.append(repository);
+ if (!StringUtils.isEmpty(objectId)) {
+ sb.append("&h=");
+ sb.append(objectId);
+ }
+ return sb.toString();
+ }
+ }
+
+ public AccessRestrictionType getDefaultAccessRestriction() {
+ String restriction = null;
+ if (settings.hasKey(Keys.git.defaultAccessRestriction)) {
+ restriction = settings.get(Keys.git.defaultAccessRestriction).currentValue;
+ }
+ return AccessRestrictionType.fromName(restriction);
+ }
+
+ public AuthorizationControl getDefaultAuthorizationControl() {
+ String authorization = null;
+ if (settings.hasKey(Keys.git.defaultAuthorizationControl)) {
+ authorization = settings.get(Keys.git.defaultAuthorizationControl).currentValue;
+ }
+ return AuthorizationControl.fromName(authorization);
+ }
+
+ /**
+ * Returns the list of pre-receive scripts the repository inherited from the
+ * global settings and team affiliations.
+ *
+ * @param repository
+ * if null only the globally specified scripts are returned
+ * @return a list of scripts
+ */
+ public List<String> getPreReceiveScriptsInherited(RepositoryModel repository) {
+ Set<String> scripts = new LinkedHashSet<String>();
+ // Globals
+ for (String script : settings.get(Keys.groovy.preReceiveScripts).getStrings()) {
+ if (script.endsWith(".groovy")) {
+ scripts.add(script.substring(0, script.lastIndexOf('.')));
+ } else {
+ scripts.add(script);
+ }
+ }
+
+ // Team Scripts
+ if (repository != null) {
+ for (String teamname : getPermittedTeamnames(repository)) {
+ TeamModel team = getTeamModel(teamname);
+ if (!ArrayUtils.isEmpty(team.preReceiveScripts)) {
+ scripts.addAll(team.preReceiveScripts);
+ }
+ }
+ }
+ return new ArrayList<String>(scripts);
+ }
+
+ /**
+ * Returns the list of all available Groovy pre-receive push hook scripts
+ * that are not already inherited by the repository. Script files must have
+ * .groovy extension
+ *
+ * @param repository
+ * optional parameter
+ * @return list of available hook scripts
+ */
+ public List<String> getPreReceiveScriptsUnused(RepositoryModel repository) {
+ Set<String> inherited = new TreeSet<String>(getPreReceiveScriptsInherited(repository));
+
+ // create list of available scripts by excluding inherited scripts
+ List<String> scripts = new ArrayList<String>();
+ for (String script : settings.pushScripts) {
+ if (!inherited.contains(script)) {
+ scripts.add(script);
+ }
+ }
+ return scripts;
+ }
+
+ /**
+ * Returns the list of post-receive scripts the repository inherited from
+ * the global settings and team affiliations.
+ *
+ * @param repository
+ * if null only the globally specified scripts are returned
+ * @return a list of scripts
+ */
+ public List<String> getPostReceiveScriptsInherited(RepositoryModel repository) {
+ Set<String> scripts = new LinkedHashSet<String>();
+ // Global Scripts
+ for (String script : settings.get(Keys.groovy.postReceiveScripts).getStrings()) {
+ if (script.endsWith(".groovy")) {
+ scripts.add(script.substring(0, script.lastIndexOf('.')));
+ } else {
+ scripts.add(script);
+ }
+ }
+ // Team Scripts
+ if (repository != null) {
+ for (String teamname : getPermittedTeamnames(repository)) {
+ TeamModel team = getTeamModel(teamname);
+ if (!ArrayUtils.isEmpty(team.postReceiveScripts)) {
+ scripts.addAll(team.postReceiveScripts);
+ }
+ }
+ }
+ return new ArrayList<String>(scripts);
+ }
+
+ /**
+ * Returns the list of unused Groovy post-receive push hook scripts that are
+ * not already inherited by the repository. Script files must have .groovy
+ * extension
+ *
+ * @param repository
+ * optional parameter
+ * @return list of available hook scripts
+ */
+ public List<String> getPostReceiveScriptsUnused(RepositoryModel repository) {
+ Set<String> inherited = new TreeSet<String>(getPostReceiveScriptsInherited(repository));
+
+ // create list of available scripts by excluding inherited scripts
+ List<String> scripts = new ArrayList<String>();
+ if (!ArrayUtils.isEmpty(settings.pushScripts)) {
+ for (String script : settings.pushScripts) {
+ if (!inherited.contains(script)) {
+ scripts.add(script);
+ }
+ }
+ }
+ return scripts;
+ }
+
+ public ServerSettings getSettings() {
+ return settings;
+ }
+
+ public ServerStatus getStatus() {
+ return status;
+ }
+
+ public String getSettingDescription(String key) {
+ return settings.get(key).description;
+ }
+
+ public List<RepositoryModel> refreshRepositories() throws IOException {
+ Map<String, RepositoryModel> repositories = RpcUtils
+ .getRepositories(url, account, password);
+ allRepositories.clear();
+ allRepositories.addAll(repositories.values());
+ Collections.sort(allRepositories);
+ markSubscribedFeeds();
+ return allRepositories;
+ }
+
+ public List<UserModel> refreshUsers() throws IOException {
+ List<UserModel> users = RpcUtils.getUsers(url, account, password);
+ allUsers.clear();
+ allUsers.addAll(users);
+ Collections.sort(users);
+ return allUsers;
+ }
+
+ public List<TeamModel> refreshTeams() throws IOException {
+ List<TeamModel> teams = RpcUtils.getTeams(url, account, password);
+ allTeams.clear();
+ allTeams.addAll(teams);
+ Collections.sort(teams);
+ return allTeams;
+ }
+
+ public ServerSettings refreshSettings() throws IOException {
+ settings = RpcUtils.getSettings(url, account, password);
+ return settings;
+ }
+
+ public ServerStatus refreshStatus() throws IOException {
+ status = RpcUtils.getStatus(url, account, password);
+ return status;
+ }
+
+ public List<String> getBranches(String repository) {
+ List<FeedModel> feeds = getAvailableFeeds(repository);
+ List<String> branches = new ArrayList<String>();
+ for (FeedModel feed : feeds) {
+ branches.add(feed.branch);
+ }
+ Collections.sort(branches);
+ return branches;
+ }
+
+ public List<FeedModel> getAvailableFeeds() {
+ return availableFeeds;
+ }
+
+ public List<FeedModel> getAvailableFeeds(RepositoryModel repository) {
+ return getAvailableFeeds(repository.name);
+ }
+
+ public List<FeedModel> getAvailableFeeds(String repository) {
+ List<FeedModel> repositoryFeeds = new ArrayList<FeedModel>();
+ if (repository == null) {
+ return repositoryFeeds;
+ }
+ for (FeedModel feed : availableFeeds) {
+ if (feed.repository.equalsIgnoreCase(repository)) {
+ repositoryFeeds.add(feed);
+ }
+ }
+ return repositoryFeeds;
+ }
+
+ public List<FeedModel> refreshAvailableFeeds() throws IOException {
+ List<FeedModel> feeds = RpcUtils.getBranchFeeds(url, account, password);
+ availableFeeds.clear();
+ availableFeeds.addAll(feeds);
+ markSubscribedFeeds();
+ return availableFeeds;
+ }
+
+ public List<FeedEntryModel> refreshSubscribedFeeds(int page) throws IOException {
+ Set<FeedEntryModel> allEntries = new HashSet<FeedEntryModel>();
+ if (reg.feeds.size() > 0) {
+ for (FeedModel feed : reg.feeds) {
+ feed.lastRefreshDate = feed.currentRefreshDate;
+ feed.currentRefreshDate = new Date();
+ List<FeedEntryModel> entries = SyndicationUtils.readFeed(url, feed.repository,
+ feed.branch, -1, page, account, password);
+ allEntries.addAll(entries);
+ }
+ }
+ reg.cacheFeeds();
+ syndicatedEntries.clear();
+ syndicatedEntries.addAll(allEntries);
+ Collections.sort(syndicatedEntries);
+ return syndicatedEntries;
+ }
+
+ public void updateSubscribedFeeds(List<FeedModel> list) {
+ reg.updateSubscribedFeeds(list);
+ markSubscribedFeeds();
+ }
+
+ private void markSubscribedFeeds() {
+ subscribedRepositories.clear();
+ for (FeedModel feed : availableFeeds) {
+ // mark feed in the available list as subscribed
+ feed.subscribed = reg.feeds.contains(feed);
+ if (feed.subscribed) {
+ subscribedRepositories.add(feed.repository.toLowerCase());
+ }
+ }
+ }
+
+ public Date getLastFeedRefresh(String repository, String branch) {
+ FeedModel feed = new FeedModel();
+ feed.repository = repository;
+ feed.branch = branch;
+ if (reg.feeds.contains(feed)) {
+ int idx = reg.feeds.indexOf(feed);
+ feed = reg.feeds.get(idx);
+ return feed.lastRefreshDate;
+ }
+ return NEVER;
+ }
+
+ public boolean isSubscribed(RepositoryModel repository) {
+ return subscribedRepositories.contains(repository.name.toLowerCase());
+ }
+
+ public List<FeedEntryModel> getSyndicatedEntries() {
+ return syndicatedEntries;
+ }
+
+ public List<FeedEntryModel> log(String repository, String branch, int numberOfEntries, int page)
+ throws IOException {
+ return SyndicationUtils.readFeed(url, repository, branch, numberOfEntries, page, account,
+ password);
+ }
+
+ public List<FeedEntryModel> search(String repository, String branch, String fragment,
+ Constants.SearchType type, int numberOfEntries, int page) throws IOException {
+ return SyndicationUtils.readSearchFeed(url, repository, branch, fragment, type,
+ numberOfEntries, page, account, password);
+ }
+
+ public List<FederationModel> refreshFederationRegistrations() throws IOException {
+ List<FederationModel> list = RpcUtils.getFederationRegistrations(url, account, password);
+ federationRegistrations.clear();
+ federationRegistrations.addAll(list);
+ return federationRegistrations;
+ }
+
+ public List<UserModel> getUsers() {
+ return allUsers;
+ }
+
+ public UserModel getUser(String username) {
+ for (UserModel user : getUsers()) {
+ if (user.username.equalsIgnoreCase(username)) {
+ return user;
+ }
+ }
+ return null;
+ }
+
+ public List<String> getUsernames() {
+ List<String> usernames = new ArrayList<String>();
+ for (UserModel user : this.allUsers) {
+ usernames.add(user.username);
+ }
+ Collections.sort(usernames);
+ return usernames;
+ }
+
+ public List<String> getPermittedUsernames(RepositoryModel repository) {
+ List<String> usernames = new ArrayList<String>();
+ for (UserModel user : this.allUsers) {
+ if (user.hasRepositoryPermission(repository.name)) {
+ usernames.add(user.username);
+ }
+ }
+ return usernames;
+ }
+
+ /**
+ * Returns the effective list of permissions for this user, taking into account
+ * team memberships, ownerships.
+ *
+ * @param user
+ * @return the effective list of permissions for the user
+ */
+ public List<RegistrantAccessPermission> getUserAccessPermissions(UserModel user) {
+ Set<RegistrantAccessPermission> set = new LinkedHashSet<RegistrantAccessPermission>();
+ set.addAll(user.getRepositoryPermissions());
+ // Flag missing repositories
+ for (RegistrantAccessPermission permission : set) {
+ if (permission.mutable && PermissionType.EXPLICIT.equals(permission.permissionType)) {
+ RepositoryModel rm = getRepository(permission.registrant);
+ if (rm == null) {
+ permission.permissionType = PermissionType.MISSING;
+ permission.mutable = false;
+ continue;
+ }
+ }
+ }
+
+ // TODO reconsider ownership as a user property
+ // manually specify personal repository ownerships
+ for (RepositoryModel rm : allRepositories) {
+ if (rm.isUsersPersonalRepository(user.username) || rm.isOwner(user.username)) {
+ RegistrantAccessPermission rp = new RegistrantAccessPermission(rm.name, AccessPermission.REWIND,
+ PermissionType.OWNER, RegistrantType.REPOSITORY, null, false);
+ // user may be owner of a repository to which they've inherited
+ // a team permission, replace any existing perm with owner perm
+ set.remove(rp);
+ set.add(rp);
+ }
+ }
+
+ List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>(set);
+ Collections.sort(list);
+ return list;
+ }
+
+ public List<RegistrantAccessPermission> getUserAccessPermissions(RepositoryModel repository) {
+ List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
+ if (AccessRestrictionType.NONE.equals(repository.accessRestriction)) {
+ // no permissions needed, REWIND for everyone!
+ return list;
+ }
+ if (AuthorizationControl.AUTHENTICATED.equals(repository.authorizationControl)) {
+ // no permissions needed, REWIND for authenticated!
+ return list;
+ }
+ // NAMED users and teams
+ for (UserModel user : allUsers) {
+ RegistrantAccessPermission ap = user.getRepositoryPermission(repository);
+ if (ap.permission.exceeds(AccessPermission.NONE)) {
+ list.add(ap);
+ }
+ }
+ return list;
+ }
+
+ public boolean setUserAccessPermissions(RepositoryModel repository, List<RegistrantAccessPermission> permissions) throws IOException {
+ return RpcUtils.setRepositoryMemberPermissions(repository, permissions, url, account, password);
+ }
+
+ public List<TeamModel> getTeams() {
+ return allTeams;
+ }
+
+ public List<String> getTeamnames() {
+ List<String> teamnames = new ArrayList<String>();
+ for (TeamModel team : this.allTeams) {
+ teamnames.add(team.name);
+ }
+ Collections.sort(teamnames);
+ return teamnames;
+ }
+
+ public List<String> getPermittedTeamnames(RepositoryModel repository) {
+ List<String> teamnames = new ArrayList<String>();
+ for (TeamModel team : this.allTeams) {
+ if (team.hasRepositoryPermission(repository.name)) {
+ teamnames.add(team.name);
+ }
+ }
+ return teamnames;
+ }
+
+ public List<RegistrantAccessPermission> getTeamAccessPermissions(RepositoryModel repository) {
+ List<RegistrantAccessPermission> list = new ArrayList<RegistrantAccessPermission>();
+ for (TeamModel team : allTeams) {
+ RegistrantAccessPermission ap = team.getRepositoryPermission(repository);
+ if (ap.permission.exceeds(AccessPermission.NONE)) {
+ list.add(ap);
+ }
+ }
+ Collections.sort(list);
+ return list;
+ }
+
+ public boolean setTeamAccessPermissions(RepositoryModel repository, List<RegistrantAccessPermission> permissions) throws IOException {
+ return RpcUtils.setRepositoryTeamPermissions(repository, permissions, url, account, password);
+ }
+
+ public TeamModel getTeamModel(String name) {
+ for (TeamModel team : allTeams) {
+ if (team.name.equalsIgnoreCase(name)) {
+ return team;
+ }
+ }
+ return null;
+ }
+
+ public List<String> getFederationSets() {
+ return settings.get(Keys.federation.sets).getStrings();
+ }
+
+ public List<RepositoryModel> getRepositories() {
+ return allRepositories;
+ }
+
+ public RepositoryModel getRepository(String name) {
+ for (RepositoryModel repository : allRepositories) {
+ if (repository.name.equalsIgnoreCase(name)) {
+ return repository;
+ }
+ }
+ return null;
+ }
+
+ public boolean createRepository(RepositoryModel repository, List<RegistrantAccessPermission> userPermissions)
+ throws IOException {
+ return createRepository(repository, userPermissions, null);
+ }
+
+ public boolean createRepository(RepositoryModel repository, List<RegistrantAccessPermission> userPermissions,
+ List<RegistrantAccessPermission> teamPermissions) throws IOException {
+ boolean success = true;
+ success &= RpcUtils.createRepository(repository, url, account, password);
+ if (userPermissions != null && userPermissions.size() > 0) {
+ // if new repository has named members, set them
+ success &= RpcUtils.setRepositoryMemberPermissions(repository, userPermissions, url, account,
+ password);
+ }
+ if (teamPermissions != null && teamPermissions.size() > 0) {
+ // if new repository has named teams, set them
+ success &= RpcUtils.setRepositoryTeamPermissions(repository, teamPermissions, url, account,
+ password);
+ }
+ return success;
+ }
+
+ public boolean updateRepository(String name, RepositoryModel repository,
+ List<RegistrantAccessPermission> userPermissions) throws IOException {
+ return updateRepository(name, repository, userPermissions, null);
+ }
+
+ public boolean updateRepository(String name, RepositoryModel repository,
+ List<RegistrantAccessPermission> userPermissions, List<RegistrantAccessPermission> teamPermissions) throws IOException {
+ boolean success = true;
+ success &= RpcUtils.updateRepository(name, repository, url, account, password);
+ // set the repository members
+ if (userPermissions != null) {
+ success &= RpcUtils.setRepositoryMemberPermissions(repository, userPermissions, url, account,
+ password);
+ }
+ if (teamPermissions != null) {
+ success &= RpcUtils.setRepositoryTeamPermissions(repository, teamPermissions, url, account,
+ password);
+ }
+ return success;
+ }
+
+ public boolean deleteRepository(RepositoryModel repository) throws IOException {
+ return RpcUtils.deleteRepository(repository, url, account, password);
+ }
+
+ public boolean clearRepositoryCache() throws IOException {
+ return RpcUtils.clearRepositoryCache(url, account, password);
+ }
+
+ public boolean createUser(UserModel user) throws IOException {
+ return RpcUtils.createUser(user, url, account, password);
+ }
+
+ public boolean updateUser(String name, UserModel user) throws IOException {
+ return RpcUtils.updateUser(name, user, url, account, password);
+ }
+
+ public boolean deleteUser(UserModel user) throws IOException {
+ 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<String, String> newSettings) throws IOException {
+ return RpcUtils.updateSettings(newSettings, url, account, password);
+ }
+}
diff --git a/src/main/java/com/gitblit/client/GitblitManager.java b/src/main/java/com/gitblit/client/GitblitManager.java
new file mode 100644
index 00000000..d2fd7f7b
--- /dev/null
+++ b/src/main/java/com/gitblit/client/GitblitManager.java
@@ -0,0 +1,458 @@
+/*
+ * 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.Cursor;
+import java.awt.Dimension;
+import java.awt.EventQueue;
+import java.awt.Point;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.net.ConnectException;
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.TimeZone;
+
+import javax.swing.ImageIcon;
+import javax.swing.JFrame;
+import javax.swing.JMenu;
+import javax.swing.JMenuBar;
+import javax.swing.JMenuItem;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.JTabbedPane;
+import javax.swing.KeyStroke;
+import javax.swing.SwingWorker;
+import javax.swing.UIManager;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+
+import com.gitblit.Constants;
+import com.gitblit.GitBlitException.ForbiddenException;
+import com.gitblit.models.FeedModel;
+import com.gitblit.utils.Base64;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Gitblit Manager issues JSON RPC requests to a Gitblit server.
+ *
+ * @author James Moger
+ *
+ */
+public class GitblitManager extends JFrame implements RegistrationsDialog.RegistrationListener {
+
+ private static final long serialVersionUID = 1L;
+ private static final String SERVER = "server";
+ private static final String FEED = "feed";
+ private final SimpleDateFormat dateFormat;
+ private JTabbedPane serverTabs;
+ private File configFile = new File(System.getProperty("user.home"), ".gitblit/config");
+
+ private Map<String, GitblitRegistration> registrations = new LinkedHashMap<String, GitblitRegistration>();
+ private JMenu recentMenu;
+ private int maxRecentCount = 5;
+
+ private GitblitManager() {
+ super();
+ dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
+ dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
+ }
+
+ private void initialize() {
+ setContentPane(getCenterPanel());
+ setIconImage(new ImageIcon(getClass().getResource("/gitblt-favicon.png")).getImage());
+ setTitle("Gitblit Manager v" + Constants.getVersion() + " (" + Constants.getBuildDate() + ")");
+ setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowClosing(WindowEvent event) {
+ saveSizeAndPosition();
+ }
+
+ @Override
+ public void windowOpened(WindowEvent event) {
+ manageRegistrations();
+ }
+ });
+
+ setSizeAndPosition();
+ loadRegistrations();
+ rebuildRecentMenu();
+ }
+
+ private void setSizeAndPosition() {
+ String sz = null;
+ String pos = null;
+ try {
+ StoredConfig config = getConfig();
+ sz = config.getString("ui", null, "size");
+ pos = config.getString("ui", null, "position");
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+
+ // try to restore saved window size
+ if (StringUtils.isEmpty(sz)) {
+ setSize(850, 500);
+ } else {
+ String[] chunks = sz.split("x");
+ int width = Integer.parseInt(chunks[0]);
+ int height = Integer.parseInt(chunks[1]);
+ setSize(width, height);
+ }
+
+ // try to restore saved window position
+ if (StringUtils.isEmpty(pos)) {
+ setLocationRelativeTo(null);
+ } else {
+ String[] chunks = pos.split(",");
+ int x = Integer.parseInt(chunks[0]);
+ int y = Integer.parseInt(chunks[1]);
+ setLocation(x, y);
+ }
+ }
+
+ private void saveSizeAndPosition() {
+ try {
+ // save window size and position
+ StoredConfig config = getConfig();
+ Dimension sz = GitblitManager.this.getSize();
+ config.setString("ui", null, "size",
+ MessageFormat.format("{0,number,0}x{1,number,0}", sz.width, sz.height));
+ Point pos = GitblitManager.this.getLocationOnScreen();
+ config.setString("ui", null, "position",
+ MessageFormat.format("{0,number,0},{1,number,0}", pos.x, pos.y));
+ config.save();
+ } catch (Throwable t) {
+ Utils.showException(GitblitManager.this, t);
+ }
+ }
+
+ private JMenuBar setupMenu() {
+ JMenuBar menuBar = new JMenuBar();
+ JMenu serversMenu = new JMenu(Translation.get("gb.servers"));
+ menuBar.add(serversMenu);
+ recentMenu = new JMenu(Translation.get("gb.recent"));
+ serversMenu.add(recentMenu);
+
+ JMenuItem manage = new JMenuItem(Translation.get("gb.manage") + "...");
+ manage.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, KeyEvent.CTRL_DOWN_MASK, false));
+ manage.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ manageRegistrations();
+ }
+ });
+ serversMenu.add(manage);
+
+ return menuBar;
+ }
+
+ private JPanel getCenterPanel() {
+ serverTabs = new JTabbedPane(JTabbedPane.TOP);
+ JMenuBar menubar = setupMenu();
+ JPanel panel = new JPanel(new BorderLayout());
+ panel.add(menubar, BorderLayout.NORTH);
+ panel.add(serverTabs, BorderLayout.CENTER);
+ return panel;
+ }
+
+ private void manageRegistrations() {
+ RegistrationsDialog dialog = new RegistrationsDialog(new ArrayList<GitblitRegistration>(
+ registrations.values()), this);
+ dialog.setLocationRelativeTo(GitblitManager.this);
+ dialog.setVisible(true);
+ }
+
+ @Override
+ public void login(GitblitRegistration reg) {
+ if (!reg.savePassword && (reg.password == null || reg.password.length == 0)) {
+ // prompt for password
+ EditRegistrationDialog dialog = new EditRegistrationDialog(this, reg, true);
+ dialog.setLocationRelativeTo(GitblitManager.this);
+ dialog.setVisible(true);
+ GitblitRegistration newReg = dialog.getRegistration();
+ if (newReg == null) {
+ // user canceled
+ return;
+ }
+ // preserve feeds
+ newReg.feeds.addAll(reg.feeds);
+
+ // use new reg
+ reg = newReg;
+ }
+
+ // login
+ setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+ final GitblitRegistration registration = reg;
+ final GitblitPanel panel = new GitblitPanel(registration, this);
+ SwingWorker<Boolean, Void> worker = new SwingWorker<Boolean, Void>() {
+
+ @Override
+ protected Boolean doInBackground() throws IOException {
+ panel.login();
+ return true;
+ }
+
+ @Override
+ protected void done() {
+ try {
+ boolean success = get();
+ serverTabs.addTab(registration.name, panel);
+ int idx = serverTabs.getTabCount() - 1;
+ serverTabs.setSelectedIndex(idx);
+ serverTabs.setTabComponentAt(idx, new ClosableTabComponent(registration.name,
+ null, serverTabs, panel));
+ registration.lastLogin = new Date();
+ saveRegistration(registration.name, registration);
+ registrations.put(registration.name, registration);
+ rebuildRecentMenu();
+ if (!registration.savePassword) {
+ // clear password
+ registration.password = null;
+ }
+ } catch (Throwable t) {
+ Throwable cause = t.getCause();
+ if (cause instanceof ConnectException) {
+ JOptionPane.showMessageDialog(GitblitManager.this, cause.getMessage(),
+ Translation.get("gb.error"), JOptionPane.ERROR_MESSAGE);
+ } else if (cause instanceof ForbiddenException) {
+ JOptionPane
+ .showMessageDialog(
+ GitblitManager.this,
+ "This Gitblit server does not allow RPC Management or Administration",
+ Translation.get("gb.error"), JOptionPane.ERROR_MESSAGE);
+ } else {
+ Utils.showException(GitblitManager.this, t);
+ }
+ } finally {
+ setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ }
+ }
+ };
+ worker.execute();
+ }
+
+ private void rebuildRecentMenu() {
+ recentMenu.removeAll();
+ ImageIcon icon = new ImageIcon(getClass().getResource("/gitblt-favicon.png"));
+ List<GitblitRegistration> list = new ArrayList<GitblitRegistration>(registrations.values());
+ Collections.sort(list, new Comparator<GitblitRegistration>() {
+ @Override
+ public int compare(GitblitRegistration o1, GitblitRegistration o2) {
+ return o2.lastLogin.compareTo(o1.lastLogin);
+ }
+ });
+ if (list.size() > maxRecentCount) {
+ list = list.subList(0, maxRecentCount);
+ }
+ for (int i = 0; i < list.size(); i++) {
+ final GitblitRegistration reg = list.get(i);
+ JMenuItem item = new JMenuItem(reg.name, icon);
+ item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_1 + i, KeyEvent.CTRL_DOWN_MASK,
+ false));
+ item.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ login(reg);
+ }
+ });
+ recentMenu.add(item);
+ }
+ }
+
+ private void loadRegistrations() {
+ try {
+ StoredConfig config = getConfig();
+ Set<String> servers = config.getSubsections(SERVER);
+ for (String server : servers) {
+ Date lastLogin = new Date(0);
+ String date = config.getString(SERVER, server, "lastLogin");
+ if (!StringUtils.isEmpty(date)) {
+ lastLogin = dateFormat.parse(date);
+ }
+ String url = config.getString(SERVER, server, "url");
+ String account = config.getString(SERVER, server, "account");
+ char[] password;
+ String pw = config.getString(SERVER, server, "password");
+ if (StringUtils.isEmpty(pw)) {
+ password = new char[0];
+ } else {
+ password = new String(Base64.decode(pw)).toCharArray();
+ }
+ GitblitRegistration reg = new GitblitRegistration(server, url, account, password) {
+ private static final long serialVersionUID = 1L;
+
+ protected void cacheFeeds() {
+ writeFeedCache(this);
+ }
+ };
+ String[] feeds = config.getStringList(SERVER, server, FEED);
+ if (feeds != null) {
+ // deserialize the field definitions
+ for (String definition : feeds) {
+ FeedModel feed = new FeedModel(definition);
+ reg.feeds.add(feed);
+ }
+ }
+ reg.lastLogin = lastLogin;
+ loadFeedCache(reg);
+ registrations.put(reg.name, reg);
+ }
+ } catch (Throwable t) {
+ Utils.showException(GitblitManager.this, t);
+ }
+ }
+
+ @Override
+ public boolean saveRegistration(String name, GitblitRegistration reg) {
+ try {
+ StoredConfig config = getConfig();
+ if (!StringUtils.isEmpty(name) && !name.equals(reg.name)) {
+ // delete old registration
+ registrations.remove(name);
+ config.unsetSection(SERVER, name);
+ }
+
+ // update registration
+ config.setString(SERVER, reg.name, "url", reg.url);
+ config.setString(SERVER, reg.name, "account", reg.account);
+ if (reg.savePassword) {
+ config.setString(SERVER, reg.name, "password",
+ Base64.encodeBytes(new String(reg.password).getBytes("UTF-8")));
+ } else {
+ config.setString(SERVER, reg.name, "password", "");
+ }
+ if (reg.lastLogin != null) {
+ config.setString(SERVER, reg.name, "lastLogin", dateFormat.format(reg.lastLogin));
+ }
+ // serialize the feed definitions
+ List<String> definitions = new ArrayList<String>();
+ for (FeedModel feed : reg.feeds) {
+ definitions.add(feed.toString());
+ }
+ if (definitions.size() > 0) {
+ config.setStringList(SERVER, reg.name, FEED, definitions);
+ }
+ config.save();
+ return true;
+ } catch (Throwable t) {
+ Utils.showException(GitblitManager.this, t);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean deleteRegistrations(List<GitblitRegistration> list) {
+ boolean success = false;
+ try {
+ StoredConfig config = getConfig();
+ for (GitblitRegistration reg : list) {
+ config.unsetSection(SERVER, reg.name);
+ registrations.remove(reg.name);
+ }
+ config.save();
+ success = true;
+ } catch (Throwable t) {
+ Utils.showException(GitblitManager.this, t);
+ }
+ return success;
+ }
+
+ private StoredConfig getConfig() throws IOException, ConfigInvalidException {
+ FileBasedConfig config = new FileBasedConfig(configFile, FS.detect());
+ config.load();
+ return config;
+ }
+
+ private void loadFeedCache(GitblitRegistration reg) {
+ File feedCache = new File(configFile.getParentFile(), StringUtils.getSHA1(reg.toString())
+ + ".cache");
+ if (!feedCache.exists()) {
+ // no cache for this registration
+ return;
+ }
+ try {
+ BufferedReader reader = new BufferedReader(new FileReader(feedCache));
+ Map<String, Date> cache = new HashMap<String, Date>();
+ SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
+ String line = null;
+ while ((line = reader.readLine()) != null) {
+ String[] kvp = line.split("=");
+ cache.put(kvp[0], df.parse(kvp[1]));
+ }
+ reader.close();
+ for (FeedModel feed : reg.feeds) {
+ String name = feed.toString();
+ if (cache.containsKey(name)) {
+ feed.currentRefreshDate = cache.get(name);
+ }
+ }
+ } catch (Exception e) {
+ Utils.showException(GitblitManager.this, e);
+ }
+ }
+
+ private void writeFeedCache(GitblitRegistration reg) {
+ try {
+ File feedCache = new File(configFile.getParentFile(), StringUtils.getSHA1(reg
+ .toString()) + ".cache");
+ FileWriter writer = new FileWriter(feedCache);
+ for (FeedModel feed : reg.feeds) {
+ writer.append(MessageFormat.format("{0}={1,date,yyyy-MM-dd'T'HH:mm:ss}\n",
+ feed.toString(), feed.currentRefreshDate));
+ }
+ writer.close();
+ } catch (Exception e) {
+ Utils.showException(GitblitManager.this, e);
+ }
+ }
+
+ public static void main(String[] args) {
+ EventQueue.invokeLater(new Runnable() {
+ public void run() {
+ try {
+ UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
+ } catch (Exception e) {
+ }
+ GitblitManager frame = new GitblitManager();
+ frame.initialize();
+ frame.setVisible(true);
+ }
+ });
+ }
+}
diff --git a/src/main/java/com/gitblit/client/GitblitManagerLauncher.java b/src/main/java/com/gitblit/client/GitblitManagerLauncher.java
new file mode 100644
index 00000000..d0cc8393
--- /dev/null
+++ b/src/main/java/com/gitblit/client/GitblitManagerLauncher.java
@@ -0,0 +1,164 @@
+/*
+ * 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.Color;
+import java.awt.EventQueue;
+import java.awt.FontMetrics;
+import java.awt.Graphics2D;
+import java.awt.SplashScreen;
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import com.gitblit.Constants;
+
+/**
+ * Downloads dependencies and launches Gitblit Manager.
+ *
+ * @author James Moger
+ *
+ */
+public class GitblitManagerLauncher {
+
+ public static final boolean DEBUG = false;
+
+ /**
+ * Parameters of the method to add an URL to the System classes.
+ */
+ private static final Class<?>[] PARAMETERS = new Class[] { URL.class };
+
+ public static void main(String[] args) {
+ final SplashScreen splash = SplashScreen.getSplashScreen();
+
+ File libFolder = new File("ext");
+ List<File> jars = findJars(libFolder.getAbsoluteFile());
+
+ // sort the jars by name and then reverse the order so the newer version
+ // of the library gets loaded in the event that this is an upgrade
+ Collections.sort(jars);
+ Collections.reverse(jars);
+ for (File jar : jars) {
+ try {
+ updateSplash(splash, Translation.get("gb.loading") + " " + jar.getName() + "...");
+ addJarFile(jar);
+ } catch (IOException e) {
+
+ }
+ }
+
+ updateSplash(splash, Translation.get("gb.starting") + " Gitblit Manager...");
+ GitblitManager.main(args);
+ }
+
+ private static void updateSplash(final SplashScreen splash, final String string) {
+ if (splash == null) {
+ return;
+ }
+ try {
+ EventQueue.invokeAndWait(new Runnable() {
+ public void run() {
+ Graphics2D g = splash.createGraphics();
+ 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;
+ int y = 115;
+ int w = 320 - 2 * x;
+ g.fillRect(x, y - h, w, h);
+ g.setColor(Color.lightGray);
+ g.drawRect(x, y - h, w, h);
+ g.setColor(Color.WHITE);
+ int xw = fm.stringWidth(string);
+ g.drawString(string, x + ((w - xw) / 2), y - 5);
+
+ // paint version
+ String ver = "v" + Constants.getVersion();
+ int vw = g.getFontMetrics().stringWidth(ver);
+ g.drawString(ver, 320 - vw - 5, 34);
+ g.dispose();
+ splash.update();
+ }
+ }
+ });
+ } catch (Throwable t) {
+ t.printStackTrace();
+ }
+ }
+
+ public static List<File> findJars(File folder) {
+ List<File> jars = new ArrayList<File>();
+ if (folder.exists()) {
+ File[] libs = folder.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File file) {
+ return !file.isDirectory() && file.getName().toLowerCase().endsWith(".jar");
+ }
+ });
+ if (libs != null && libs.length > 0) {
+ jars.addAll(Arrays.asList(libs));
+ if (DEBUG) {
+ for (File jar : jars) {
+ System.out.println("found " + jar);
+ }
+ }
+ }
+ }
+
+ return jars;
+ }
+
+ /**
+ * Adds a file to the classpath
+ *
+ * @param f
+ * the file to be added
+ * @throws IOException
+ */
+ public static void addJarFile(File f) throws IOException {
+ if (f.getName().indexOf("-sources") > -1 || f.getName().indexOf("-javadoc") > -1) {
+ // don't add source or javadoc jars to runtime classpath
+ return;
+ }
+ URL u = f.toURI().toURL();
+ if (DEBUG) {
+ System.out.println("load=" + u.toExternalForm());
+ }
+ URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
+ Class<?> sysclass = URLClassLoader.class;
+ try {
+ Method method = sysclass.getDeclaredMethod("addURL", PARAMETERS);
+ method.setAccessible(true);
+ method.invoke(sysloader, new Object[] { u });
+ } catch (Throwable t) {
+ throw new IOException(MessageFormat.format(
+ "Error, could not add {0} to system classloader", f.getPath()), t);
+ }
+ }
+
+}
diff --git a/src/main/java/com/gitblit/client/GitblitPanel.java b/src/main/java/com/gitblit/client/GitblitPanel.java
new file mode 100644
index 00000000..f14ce790
--- /dev/null
+++ b/src/main/java/com/gitblit/client/GitblitPanel.java
@@ -0,0 +1,228 @@
+/*
+ * 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.Component;
+import java.awt.Insets;
+import java.io.IOException;
+import java.util.List;
+
+import javax.swing.JPanel;
+import javax.swing.JTabbedPane;
+import javax.swing.event.ChangeEvent;
+import javax.swing.event.ChangeListener;
+
+import com.gitblit.client.ClosableTabComponent.CloseTabListener;
+import com.gitblit.models.FeedModel;
+
+/**
+ * GitblitPanel is a container for the repository, users, settings, etc panels.
+ *
+ * @author James Moger
+ *
+ */
+public class GitblitPanel extends JPanel implements CloseTabListener {
+
+ private static final long serialVersionUID = 1L;
+
+ private final RegistrationsDialog.RegistrationListener listener;
+
+ private GitblitClient gitblit;
+
+ private JTabbedPane tabs;
+
+ private RepositoriesPanel repositoriesPanel;
+
+ private FeedsPanel feedsPanel;
+
+ private UsersPanel usersPanel;
+
+ private TeamsPanel teamsPanel;
+
+ private SettingsPanel settingsPanel;
+
+ private StatusPanel statusPanel;
+
+ public GitblitPanel(GitblitRegistration reg, RegistrationsDialog.RegistrationListener listener) {
+ this.gitblit = new GitblitClient(reg);
+ this.listener = listener;
+
+ 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());
+ tabs.addChangeListener(new ChangeListener() {
+ public void stateChanged(ChangeEvent e) {
+ tabs.getSelectedComponent().requestFocus();
+ }
+ });
+
+ setLayout(new BorderLayout());
+ add(tabs, BorderLayout.CENTER);
+ }
+
+ private JPanel createRepositoriesPanel() {
+ repositoriesPanel = new RepositoriesPanel(gitblit) {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void subscribeFeeds(List<FeedModel> feeds) {
+ GitblitPanel.this.subscribeFeeds(feeds);
+ }
+
+ @Override
+ protected void updateUsersTable() {
+ usersPanel.updateTable(false);
+ }
+
+ @Override
+ protected void updateTeamsTable() {
+ teamsPanel.updateTable(false);
+ }
+
+ };
+ return repositoriesPanel;
+ }
+
+ private JPanel createFeedsPanel() {
+ feedsPanel = new FeedsPanel(gitblit) {
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ protected void subscribeFeeds(List<FeedModel> feeds) {
+ GitblitPanel.this.subscribeFeeds(feeds);
+ }
+ };
+ return feedsPanel;
+ }
+
+ private JPanel createUsersPanel() {
+ 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);
+ return settingsPanel;
+ }
+
+ private JPanel createStatusPanel() {
+ statusPanel = new StatusPanel(gitblit);
+ return statusPanel;
+ }
+
+ public void login() throws IOException {
+ gitblit.login();
+
+ repositoriesPanel.updateTable(true);
+ 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
+ // hide admin repository buttons
+ repositoriesPanel.disableManagement();
+
+ while (tabs.getTabCount() > 2) {
+ // remove all management/administration tabs
+ tabs.removeTabAt(2);
+ }
+ }
+
+ if (gitblit.allowAdministration()) {
+ settingsPanel.updateTable(true);
+ statusPanel.updateTable(false);
+ } else {
+ // remove the settings and status tab
+ String[] titles = { Translation.get("gb.settings"), Translation.get("gb.status") };
+ for (String title : titles) {
+ for (int i = 0; i < tabs.getTabCount(); i++) {
+ if (tabs.getTitleAt(i).equals(title)) {
+ tabs.removeTabAt(i);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public Insets getInsets() {
+ return Utils.INSETS;
+ }
+
+ @Override
+ public void closeTab(Component c) {
+ gitblit = null;
+ }
+
+ protected void subscribeFeeds(final List<FeedModel> feeds) {
+ SubscriptionsDialog dialog = new SubscriptionsDialog(feeds) {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public void save() {
+ gitblit.updateSubscribedFeeds(feeds);
+ listener.saveRegistration(gitblit.reg.name, gitblit.reg);
+ setVisible(false);
+ repositoriesPanel.updateTable(false);
+ }
+ };
+ dialog.setLocationRelativeTo(GitblitPanel.this);
+ dialog.setVisible(true);
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/gitblit/client/GitblitRegistration.java b/src/main/java/com/gitblit/client/GitblitRegistration.java
new file mode 100644
index 00000000..f9d07488
--- /dev/null
+++ b/src/main/java/com/gitblit/client/GitblitRegistration.java
@@ -0,0 +1,86 @@
+/*
+ * 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.io.Serializable;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import com.gitblit.models.FeedModel;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Simple class to encapsulate a Gitblit server registration.
+ *
+ * @author James Moger
+ *
+ */
+public class GitblitRegistration implements Serializable, Comparable<GitblitRegistration> {
+
+ private static final long serialVersionUID = 1L;
+
+ String name;
+ String url;
+ String account;
+ char[] password;
+ boolean savePassword;
+ Date lastLogin;
+ final List<FeedModel> feeds;
+
+ public GitblitRegistration(String name, String url, String account, char[] password) {
+ this.url = url;
+ this.account = account;
+ this.password = password;
+ this.savePassword = password != null && password.length > 0;
+ if (StringUtils.isEmpty(name)) {
+ this.name = url.substring(url.indexOf("//") + 2);
+ } else {
+ this.name = name;
+ }
+ feeds = new ArrayList<FeedModel>();
+ }
+
+ public void updateSubscribedFeeds(List<FeedModel> list) {
+ for (FeedModel feed : list) {
+ if (feeds.contains(feed)) {
+ // possibly unsubscribe/remove feed
+ int index = feeds.indexOf(feed);
+ FeedModel existingFeed = feeds.get(index);
+ existingFeed.subscribed = feed.subscribed;
+ if (!existingFeed.subscribed) {
+ feeds.remove(index);
+ }
+ } else if (feed.subscribed) {
+ // new subscription
+ feeds.add(feed);
+ }
+ }
+ }
+
+ protected void cacheFeeds() {
+ }
+
+ @Override
+ public int compareTo(GitblitRegistration o) {
+ return name.toLowerCase().compareTo(o.name.toLowerCase());
+ }
+
+ @Override
+ public String toString() {
+ return name + " (" + url + ")";
+ }
+}
diff --git a/src/main/java/com/gitblit/client/GitblitWorker.java b/src/main/java/com/gitblit/client/GitblitWorker.java
new file mode 100644
index 00000000..93c35d6b
--- /dev/null
+++ b/src/main/java/com/gitblit/client/GitblitWorker.java
@@ -0,0 +1,89 @@
+/*
+ * 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.Component;
+import java.awt.Cursor;
+import java.io.IOException;
+import java.text.MessageFormat;
+
+import javax.swing.JOptionPane;
+import javax.swing.SwingWorker;
+
+import com.gitblit.Constants.RpcRequest;
+import com.gitblit.GitBlitException.ForbiddenException;
+import com.gitblit.GitBlitException.NotAllowedException;
+import com.gitblit.GitBlitException.UnauthorizedException;
+import com.gitblit.GitBlitException.UnknownRequestException;
+
+public abstract class GitblitWorker extends SwingWorker<Boolean, Void> {
+
+ private final Component parent;
+
+ private final RpcRequest request;
+
+ public GitblitWorker(Component parent, RpcRequest request) {
+ this.parent = parent;
+ this.request = request;
+ parent.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+ }
+
+ protected RpcRequest getRequestType() {
+ return request;
+ }
+
+ @Override
+ protected Boolean doInBackground() throws IOException {
+ return doRequest();
+ }
+
+ protected void done() {
+ parent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ try {
+ Boolean success = get();
+ if (success) {
+ onSuccess();
+ } else {
+ onFailure();
+ }
+ } catch (Throwable t) {
+ if (t instanceof ForbiddenException) {
+ Utils.explainForbidden(parent, request);
+ } else if (t instanceof UnauthorizedException) {
+ Utils.explainUnauthorized(parent, request);
+ } else if (t instanceof NotAllowedException) {
+ Utils.explainNotAllowed(parent, request);
+ } else if (t instanceof UnknownRequestException) {
+ Utils.explainNotAllowed(parent, request);
+ } else {
+ Utils.showException(parent, t);
+ }
+ }
+ }
+
+ protected abstract Boolean doRequest() throws IOException;
+
+ protected abstract void onSuccess();
+
+ protected void onFailure() {
+ }
+
+ protected void showFailure(String message, Object... args) {
+ String msg = MessageFormat.format(message, args);
+ JOptionPane.showMessageDialog(parent, msg, Translation.get("gb.error"),
+ JOptionPane.ERROR_MESSAGE);
+ }
+}
diff --git a/src/main/java/com/gitblit/client/HeaderPanel.java b/src/main/java/com/gitblit/client/HeaderPanel.java
new file mode 100644
index 00000000..3cd89a70
--- /dev/null
+++ b/src/main/java/com/gitblit/client/HeaderPanel.java
@@ -0,0 +1,93 @@
+/*
+ * 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.BasicStroke;
+import java.awt.Color;
+import java.awt.GradientPaint;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.GridLayout;
+import java.awt.Insets;
+import java.awt.Paint;
+import java.awt.geom.Point2D;
+import java.awt.geom.Rectangle2D;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+
+import com.gitblit.utils.StringUtils;
+
+public class HeaderPanel extends JPanel {
+
+ private static final long serialVersionUID = 1L;
+
+ private final Insets insets = new Insets(5, 5, 5, 5);
+
+ private Color lightColor = new Color(0, 0, 0x60);
+
+ private JLabel headerLabel;
+
+ private JLabel refreshLabel;
+
+ public HeaderPanel(String text, String icon) {
+ // super(new FlowLayout(FlowLayout.LEFT), true);
+ super(new GridLayout(1, 2, 5, 5), true);
+ setOpaque(true);
+ setBackground(new Color(0, 0, 0x20));
+
+ headerLabel = new JLabel(text);
+ if (!StringUtils.isEmpty(icon)) {
+ headerLabel.setIcon(new ImageIcon(getClass().getResource("/" + icon)));
+ }
+ headerLabel.setForeground(Color.white);
+ headerLabel.setFont(headerLabel.getFont().deriveFont(14f));
+ add(headerLabel);
+
+ refreshLabel = new JLabel("", JLabel.RIGHT);
+ refreshLabel.setForeground(Color.white);
+ add(refreshLabel);
+ }
+
+ public void setText(String text) {
+ headerLabel.setText(text);
+ SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
+ refreshLabel.setText("refreshed " + df.format(new Date()));
+ }
+
+ @Override
+ public Insets getInsets() {
+ return insets;
+ }
+
+ @Override
+ public void paintComponent(Graphics oldG) {
+ Graphics2D g = (Graphics2D) oldG;
+ Point2D startPoint = new Point2D.Float(0, 0);
+ Point2D endPoint = new Point2D.Float(0, getHeight());
+ Paint gradientPaint = new GradientPaint(startPoint, lightColor, endPoint, getBackground(),
+ false);
+ g.setPaint(gradientPaint);
+ g.fill(new Rectangle2D.Double(0, 0, getWidth(), getHeight()));
+ g.setColor(new Color(0xff, 0x99, 0x00));
+ int stroke = 2;
+ g.setStroke(new BasicStroke(stroke));
+ g.drawLine(0, getHeight() - 1, getWidth(), getHeight() - 1);
+ }
+}
diff --git a/src/main/java/com/gitblit/client/IndicatorsRenderer.java b/src/main/java/com/gitblit/client/IndicatorsRenderer.java
new file mode 100644
index 00000000..44b39d01
--- /dev/null
+++ b/src/main/java/com/gitblit/client/IndicatorsRenderer.java
@@ -0,0 +1,150 @@
+/*
+ * 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.Component;
+import java.awt.FlowLayout;
+import java.io.Serializable;
+
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTable;
+import javax.swing.table.TableCellRenderer;
+
+import com.gitblit.models.RepositoryModel;
+
+/**
+ * Renders the type indicators (tickets, frozen, access restriction, etc) in a
+ * single cell.
+ *
+ * @author James Moger
+ *
+ */
+public class IndicatorsRenderer extends JPanel implements TableCellRenderer, Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private final ImageIcon blankIcon;
+
+ private final ImageIcon pushIcon;
+
+ private final ImageIcon pullIcon;
+
+ private final ImageIcon viewIcon;
+
+ private final ImageIcon tixIcon;
+
+ private final ImageIcon doxIcon;
+
+ private final ImageIcon frozenIcon;
+
+ private final ImageIcon federatedIcon;
+
+ private final ImageIcon forkIcon;
+
+ private final ImageIcon sparkleshareIcon;
+
+ public IndicatorsRenderer() {
+ super(new FlowLayout(FlowLayout.RIGHT, 1, 0));
+ blankIcon = new ImageIcon(getClass().getResource("/blank.png"));
+ pushIcon = new ImageIcon(getClass().getResource("/lock_go_16x16.png"));
+ pullIcon = new ImageIcon(getClass().getResource("/lock_pull_16x16.png"));
+ viewIcon = new ImageIcon(getClass().getResource("/shield_16x16.png"));
+ tixIcon = new ImageIcon(getClass().getResource("/bug_16x16.png"));
+ doxIcon = new ImageIcon(getClass().getResource("/book_16x16.png"));
+ frozenIcon = new ImageIcon(getClass().getResource("/cold_16x16.png"));
+ federatedIcon = new ImageIcon(getClass().getResource("/federated_16x16.png"));
+ forkIcon = new ImageIcon(getClass().getResource("/commit_divide_16x16.png"));
+ sparkleshareIcon = new ImageIcon(getClass().getResource("/star_16x16.png"));
+ }
+
+ @Override
+ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
+ boolean hasFocus, int row, int column) {
+ if (isSelected)
+ setBackground(table.getSelectionBackground());
+ else
+ setBackground(table.getBackground());
+ removeAll();
+ if (value instanceof RepositoryModel) {
+ StringBuilder tooltip = new StringBuilder();
+ RepositoryModel model = (RepositoryModel) value;
+ if (model.isSparkleshared()) {
+ JLabel icon = new JLabel(sparkleshareIcon);
+ tooltip.append(Translation.get("gb.isSparkleshared")).append("<br/>");
+ add(icon);
+ }
+ if (model.isFork()) {
+ JLabel icon = new JLabel(forkIcon);
+ tooltip.append(Translation.get("gb.isFork")).append("<br/>");
+ add(icon);
+ }
+ if (model.useTickets) {
+ JLabel icon = new JLabel(tixIcon);
+ tooltip.append(Translation.get("gb.tickets")).append("<br/>");
+ add(icon);
+ }
+ if (model.useDocs) {
+ JLabel icon = new JLabel(doxIcon);
+ tooltip.append(Translation.get("gb.docs")).append("<br/>");
+ add(icon);
+ }
+ if (model.isFrozen) {
+ JLabel icon = new JLabel(frozenIcon);
+ tooltip.append(Translation.get("gb.isFrozen")).append("<br/>");
+ add(icon);
+ }
+ if (model.isFederated) {
+ JLabel icon = new JLabel(federatedIcon);
+ tooltip.append(Translation.get("gb.isFederated")).append("<br/>");
+ add(icon);
+ }
+
+ switch (model.accessRestriction) {
+ case NONE: {
+ add(new JLabel(blankIcon));
+ break;
+ }
+ case PUSH: {
+ JLabel icon = new JLabel(pushIcon);
+ tooltip.append(Translation.get("gb.pushRestricted")).append("<br/>");
+ add(icon);
+ break;
+ }
+ case CLONE: {
+ JLabel icon = new JLabel(pullIcon);
+ tooltip.append(Translation.get("gb.cloneRestricted")).append("<br/>");
+ add(icon);
+ break;
+ }
+ case VIEW: {
+ JLabel icon = new JLabel(viewIcon);
+ tooltip.append(Translation.get("gb.viewRestricted")).append("<br/>");
+ add(icon);
+ break;
+ }
+ default:
+ add(new JLabel(blankIcon));
+ }
+ if (tooltip.length() > 0) {
+ tooltip.insert(0, "<html><body>");
+ setToolTipText(tooltip.toString().trim());
+ }
+ }
+ return this;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/gitblit/client/JPalette.java b/src/main/java/com/gitblit/client/JPalette.java
new file mode 100644
index 00000000..a0c2b258
--- /dev/null
+++ b/src/main/java/com/gitblit/client/JPalette.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.Color;
+import java.awt.Dimension;
+import java.awt.Font;
+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<T> extends JPanel {
+
+ private static final long serialVersionUID = 1L;
+ private PaletteModel<T> availableModel;
+ private PaletteModel<T> selectedModel;
+ private JButton add;
+ private JButton subtract;
+ private JButton up;
+ private JButton down;
+
+ public JPalette() {
+ this(false);
+ }
+
+ public JPalette(boolean controlOrder) {
+ super(new BorderLayout(5, 5));
+
+ availableModel = new PaletteModel<T>();
+ selectedModel = new PaletteModel<T>();
+
+ final JTable available = new JTable(availableModel);
+ final JTable selected = new JTable(selectedModel);
+
+ add = new JButton("->");
+ add.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ List<T> move = new ArrayList<T>();
+ 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();
+ }
+ });
+ subtract = new JButton("<-");
+ subtract.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ List<T> move = new ArrayList<T>();
+ 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();
+ }
+ });
+
+ up = new JButton("\u2191");
+ up.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ int row = selected.getSelectedRow();
+ if (row > 0) {
+ T o = selectedModel.list.remove(row);
+ selectedModel.list.add(row - 1, o);
+ selectedModel.fireTableDataChanged();
+ }
+ }
+ });
+
+ down = new JButton("\u2193");
+ down.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ int row = selected.getSelectedRow();
+ if (row < selected.getRowCount() - 1) {
+ T o = selectedModel.list.remove(row);
+ selectedModel.list.add(row + 1, o);
+ selectedModel.fireTableDataChanged();
+ }
+ }
+ });
+
+ JPanel controls = new JPanel(new GridLayout(0, 1, 0, 5));
+ controls.add(add);
+ controls.add(subtract);
+ if (controlOrder) {
+ controls.add(up);
+ controls.add(down);
+ }
+
+ JPanel center = new JPanel(new GridBagLayout());
+ center.add(controls);
+
+ add(newListPanel(Translation.get("gb.available"), available), BorderLayout.WEST);
+ add(center, BorderLayout.CENTER);
+ add(newListPanel(Translation.get("gb.selected"), selected), BorderLayout.EAST);
+ }
+
+ private JPanel newListPanel(String label, JTable table) {
+ NameRenderer nameRenderer = new NameRenderer();
+ table.setCellSelectionEnabled(false);
+ table.setRowSelectionAllowed(true);
+ 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, 160));
+ JPanel panel = new JPanel(new BorderLayout());
+ JLabel jlabel = new JLabel(label);
+ jlabel.setFont(jlabel.getFont().deriveFont(Font.BOLD));
+ panel.add(jlabel, BorderLayout.NORTH);
+ panel.add(jsp, BorderLayout.CENTER);
+ return panel;
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ add.setEnabled(enabled);
+ subtract.setEnabled(enabled);
+ up.setEnabled(enabled);
+ down.setEnabled(enabled);
+ }
+
+ public void setObjects(List<T> all, List<T> selected) {
+ List<T> available = new ArrayList<T>(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<T> getSelections() {
+ return new ArrayList<T>(selectedModel.list);
+ }
+
+ public class PaletteModel<K> extends AbstractTableModel {
+
+ private static final long serialVersionUID = 1L;
+
+ List<K> list;
+
+ public PaletteModel() {
+ this(new ArrayList<K>());
+ }
+
+ public PaletteModel(List<K> list) {
+ this.list = new ArrayList<K>(list);
+ }
+
+ @Override
+ public int getRowCount() {
+ return list.size();
+ }
+
+ @Override
+ public int getColumnCount() {
+ return 1;
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ return Translation.get("gb.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/main/java/com/gitblit/client/MessageRenderer.java b/src/main/java/com/gitblit/client/MessageRenderer.java
new file mode 100644
index 00000000..2fe3415a
--- /dev/null
+++ b/src/main/java/com/gitblit/client/MessageRenderer.java
@@ -0,0 +1,205 @@
+/*
+ * 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.Color;
+import java.awt.Component;
+import java.awt.FlowLayout;
+import java.awt.Font;
+import java.io.Serializable;
+
+import javax.swing.ImageIcon;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTable;
+import javax.swing.border.Border;
+import javax.swing.border.LineBorder;
+import javax.swing.table.TableCellRenderer;
+
+import org.eclipse.jgit.lib.Constants;
+
+import com.gitblit.models.FeedEntryModel;
+
+/**
+ * Message renderer displays the short log message and then any refs in a style
+ * like the site.
+ *
+ * @author James Moger
+ *
+ */
+public class MessageRenderer extends JPanel implements TableCellRenderer, Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private final GitblitClient gitblit;
+
+ private final ImageIcon mergeIcon;
+
+ private final ImageIcon blankIcon;
+
+ private final JLabel messageLabel;
+
+ private final JLabel headLabel;
+
+ private final JLabel branchLabel;
+
+ private final JLabel remoteLabel;
+
+ private final JLabel tagLabel;
+
+ public MessageRenderer() {
+ this(null);
+ }
+
+ public MessageRenderer(GitblitClient gitblit) {
+ super(new FlowLayout(FlowLayout.LEFT, Utils.MARGIN, 1));
+ this.gitblit = gitblit;
+
+ mergeIcon = new ImageIcon(getClass().getResource("/commit_merge_16x16.png"));
+ blankIcon = new ImageIcon(getClass().getResource("/blank.png"));
+
+ messageLabel = new JLabel();
+
+ headLabel = newRefLabel();
+ branchLabel = newRefLabel();
+ remoteLabel = newRefLabel();
+ tagLabel = newRefLabel();
+
+ add(messageLabel);
+ add(headLabel);
+ add(branchLabel);
+ add(remoteLabel);
+ add(tagLabel);
+ }
+
+ private JLabel newRefLabel() {
+ JLabel label = new JLabel();
+ label.setOpaque(true);
+ Font font = label.getFont();
+ label.setFont(font.deriveFont(font.getSize2D() - 1f));
+ return label;
+ }
+
+ private void resetRef(JLabel label) {
+ label.setText("");
+ label.setBackground(messageLabel.getBackground());
+ label.setBorder(null);
+ label.setVisible(false);
+ }
+
+ private void showRef(String ref, JLabel label) {
+ String name = ref;
+ Color bg = getBackground();
+ Border border = null;
+ if (name.startsWith(Constants.R_HEADS)) {
+ // local branch
+ bg = Color.decode("#CCFFCC");
+ name = name.substring(Constants.R_HEADS.length());
+ border = new LineBorder(Color.decode("#00CC33"), 1);
+ } else if (name.startsWith(Constants.R_REMOTES)) {
+ // remote branch
+ bg = Color.decode("#CAC2F5");
+ name = name.substring(Constants.R_REMOTES.length());
+ border = new LineBorder(Color.decode("#6C6CBF"), 1);
+ } else if (name.startsWith(Constants.R_TAGS)) {
+ // tag
+ bg = Color.decode("#FFFFAA");
+ name = name.substring(Constants.R_TAGS.length());
+ border = new LineBorder(Color.decode("#FFCC00"), 1);
+ } else if (name.equals(Constants.HEAD)) {
+ // HEAD
+ bg = Color.decode("#FFAAFF");
+ border = new LineBorder(Color.decode("#FF00EE"), 1);
+ } else {
+ }
+ label.setText(name);
+ label.setBackground(bg);
+ label.setBorder(border);
+ label.setVisible(true);
+ }
+
+ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
+ boolean hasFocus, int row, int column) {
+ if (isSelected)
+ setBackground(table.getSelectionBackground());
+ else
+ setBackground(table.getBackground());
+ messageLabel.setForeground(isSelected ? table.getSelectionForeground() : table
+ .getForeground());
+ if (value == null) {
+ return this;
+ }
+ FeedEntryModel entry = (FeedEntryModel) value;
+
+ if (gitblit == null) {
+ // no gitblit client, just display message
+ messageLabel.setText(entry.title);
+ } else {
+ // show message in BOLD if its a new entry
+ if (entry.published.after(gitblit.getLastFeedRefresh(entry.repository, entry.branch))) {
+ messageLabel.setText("<html><body><b>" + entry.title);
+ } else {
+ messageLabel.setText(entry.title);
+ }
+ }
+
+ // reset ref label
+ resetRef(headLabel);
+ resetRef(branchLabel);
+ resetRef(remoteLabel);
+ resetRef(tagLabel);
+
+ int parentCount = 0;
+ if (entry.tags != null) {
+ for (String tag : entry.tags) {
+ if (tag.startsWith("ref:")) {
+ // strip ref:
+ tag = tag.substring("ref:".length());
+ } else {
+ // count parents
+ if (tag.startsWith("parent:")) {
+ parentCount++;
+ }
+ }
+ if (tag.equals(entry.branch)) {
+ // skip current branch label
+ continue;
+ }
+ if (tag.startsWith(Constants.R_HEADS)) {
+ // local branch
+ showRef(tag, branchLabel);
+ } else if (tag.startsWith(Constants.R_REMOTES)) {
+ // remote branch
+ showRef(tag, remoteLabel);
+ } else if (tag.startsWith(Constants.R_TAGS)) {
+ // tag
+ showRef(tag, tagLabel);
+ } else if (tag.equals(Constants.HEAD)) {
+ // HEAD
+ showRef(tag, headLabel);
+ }
+ }
+ }
+
+ if (parentCount > 1) {
+ // multiple parents, show merge icon
+ messageLabel.setIcon(mergeIcon);
+ } else {
+ messageLabel.setIcon(blankIcon);
+ }
+ return this;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/gitblit/client/NameRenderer.java b/src/main/java/com/gitblit/client/NameRenderer.java
new file mode 100644
index 00000000..4cbb5906
--- /dev/null
+++ b/src/main/java/com/gitblit/client/NameRenderer.java
@@ -0,0 +1,91 @@
+/*
+ * 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.Color;
+import java.awt.Component;
+
+import javax.swing.JList;
+import javax.swing.JTable;
+import javax.swing.ListCellRenderer;
+import javax.swing.table.DefaultTableCellRenderer;
+
+/**
+ * Repository name cell renderer. This renderer shows the group name in a gray
+ * color and accentuates the repository name in a cornflower blue color.
+ *
+ * @author James Moger
+ *
+ */
+public class NameRenderer extends DefaultTableCellRenderer implements ListCellRenderer {
+
+ private static final long serialVersionUID = 1L;
+
+ private static final Color CORNFLOWER = new Color(0x00, 0x69, 0xD6);
+
+ private final String groupSpan;
+
+ public NameRenderer() {
+ this(Color.gray, CORNFLOWER);
+ }
+
+ private NameRenderer(Color group, Color repo) {
+ groupSpan = "<span style='color:" + getHexColor(group) + "'>";
+ setForeground(repo);
+ }
+
+ String getHexColor(Color c) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(Integer.toHexString((c.getRGB() & 0x00FFFFFF)));
+ while (sb.length() < 6)
+ sb.insert(0, '0');
+ sb.insert(0, '#');
+ return sb.toString();
+ }
+
+ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
+ boolean hasFocus, int row, int column) {
+ super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
+ setValue(value == null ? "" : value, isSelected);
+ return this;
+ }
+
+ @Override
+ public Component getListCellRendererComponent(JList list, Object value, int index,
+ boolean isSelected, boolean cellHasFocus) {
+ setValue(value == null ? "" : value, isSelected);
+ if (isSelected) {
+ setBackground(list.getSelectionBackground());
+ setForeground(list.getSelectionForeground());
+ } else {
+ setBackground(list.getBackground());
+ setForeground(CORNFLOWER);
+ }
+ return this;
+ }
+
+ private void setValue(Object value, boolean isSelected) {
+ String name = value.toString();
+ int lastSlash = name.lastIndexOf('/');
+ if (!isSelected && lastSlash > -1) {
+ String group = name.substring(0, lastSlash + 1);
+ String repo = name.substring(lastSlash + 1);
+ setText("<html><body>" + groupSpan + group + "</span>" + repo);
+ } else {
+ this.setText(name);
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/gitblit/client/PropertiesTableModel.java b/src/main/java/com/gitblit/client/PropertiesTableModel.java
new file mode 100644
index 00000000..0c803f47
--- /dev/null
+++ b/src/main/java/com/gitblit/client/PropertiesTableModel.java
@@ -0,0 +1,106 @@
+/*
+ * 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.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.swing.table.AbstractTableModel;
+
+/**
+ * Table model of a map of properties.
+ *
+ * @author James Moger
+ *
+ */
+public class PropertiesTableModel extends AbstractTableModel {
+
+ private static final long serialVersionUID = 1L;
+
+ List<String> keys;
+
+ Map<String, String> map;
+
+ enum Columns {
+ Name, Value;
+
+ @Override
+ public String toString() {
+ return name().replace('_', ' ');
+ }
+ }
+
+ public PropertiesTableModel() {
+ this(new HashMap<String, String>());
+ }
+
+ public PropertiesTableModel(Map<String, String> map) {
+ setProperties(map);
+ }
+
+ public void setProperties(Map<String, String> map) {
+ this.map = map;
+ keys = new ArrayList<String>(map.keySet());
+ Collections.sort(this.keys);
+ }
+
+ @Override
+ public int getRowCount() {
+ return keys.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");
+ }
+ return "";
+ }
+
+ /**
+ * Returns <code>Object.class</code> regardless of <code>columnIndex</code>.
+ *
+ * @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) {
+ String key = keys.get(rowIndex);
+ Columns col = Columns.values()[columnIndex];
+ switch (col) {
+ case Name:
+ return key;
+ case Value:
+ return map.get(key);
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/gitblit/client/RegistrantPermissionsPanel.java b/src/main/java/com/gitblit/client/RegistrantPermissionsPanel.java
new file mode 100644
index 00000000..98dbfb72
--- /dev/null
+++ b/src/main/java/com/gitblit/client/RegistrantPermissionsPanel.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2012 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.client;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import javax.swing.DefaultCellEditor;
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.SwingConstants;
+import javax.swing.table.DefaultTableCellRenderer;
+
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.Constants.PermissionType;
+import com.gitblit.Constants.RegistrantType;
+import com.gitblit.client.Utils.RowRenderer;
+import com.gitblit.models.RegistrantAccessPermission;
+import com.gitblit.utils.StringUtils;
+
+public class RegistrantPermissionsPanel extends JPanel {
+
+ private static final long serialVersionUID = 1L;
+
+ private JTable permissionsTable;
+
+ private RegistrantPermissionsTableModel tableModel;
+
+ private DefaultComboBoxModel registrantModel;
+
+ private JComboBox registrantSelector;
+
+ private JComboBox permissionSelector;
+
+ private JButton addButton;
+
+ private JPanel addPanel;
+
+ public RegistrantPermissionsPanel(final RegistrantType registrantType) {
+ super(new BorderLayout(5, 5));
+ tableModel = new RegistrantPermissionsTableModel();
+ permissionsTable = Utils.newTable(tableModel, Utils.DATE_FORMAT, new RowRenderer() {
+ Color clear = new Color(0, 0, 0, 0);
+ Color iceGray = new Color(0xf0, 0xf0, 0xf0);
+
+ @Override
+ public void prepareRow(Component c, boolean isSelected, int row, int column) {
+ if (isSelected) {
+ c.setBackground(permissionsTable.getSelectionBackground());
+ } else {
+ if (tableModel.permissions.get(row).mutable) {
+ c.setBackground(clear);
+ } else {
+ c.setBackground(iceGray);
+ }
+ }
+ }
+ });
+ permissionsTable.setModel(tableModel);
+ permissionsTable.setPreferredScrollableViewportSize(new Dimension(400, 150));
+ JScrollPane jsp = new JScrollPane(permissionsTable);
+ add(jsp, BorderLayout.CENTER);
+
+ permissionsTable.getColumnModel().getColumn(RegistrantPermissionsTableModel.Columns.Registrant.ordinal())
+ .setCellRenderer(new NameRenderer());
+ permissionsTable.getColumnModel().getColumn(RegistrantPermissionsTableModel.Columns.Type.ordinal())
+ .setCellRenderer(new PermissionTypeRenderer());
+ permissionsTable.getColumnModel().getColumn(RegistrantPermissionsTableModel.Columns.Permission.ordinal())
+ .setCellEditor(new AccessPermissionEditor());
+
+ registrantModel = new DefaultComboBoxModel();
+ registrantSelector = new JComboBox(registrantModel);
+ permissionSelector = new JComboBox(AccessPermission.NEWPERMISSIONS);
+ addButton = new JButton(Translation.get("gb.add"));
+ addButton.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ if (registrantSelector.getSelectedIndex() < 0) {
+ return;
+ }
+ if (permissionSelector.getSelectedIndex() < 0) {
+ return;
+ }
+
+ RegistrantAccessPermission rp = new RegistrantAccessPermission(registrantType);
+ rp.registrant = registrantSelector.getSelectedItem().toString();
+ rp.permission = (AccessPermission) permissionSelector.getSelectedItem();
+ if (StringUtils.findInvalidCharacter(rp.registrant) != null) {
+ rp.permissionType = PermissionType.REGEX;
+ rp.source = rp.registrant;
+ } else {
+ rp.permissionType = PermissionType.EXPLICIT;
+ }
+
+ tableModel.permissions.add(rp);
+ // resort permissions after insert to convey idea of eval order
+ Collections.sort(tableModel.permissions);
+
+ registrantModel.removeElement(rp.registrant);
+ registrantSelector.setSelectedIndex(-1);
+ registrantSelector.invalidate();
+ addPanel.setVisible(registrantModel.getSize() > 0);
+
+ tableModel.fireTableDataChanged();
+ }
+ });
+
+ addPanel = new JPanel();
+ addPanel.add(registrantSelector);
+ addPanel.add(permissionSelector);
+ addPanel.add(addButton);
+ add(addPanel, BorderLayout.SOUTH);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ super.setEnabled(enabled);
+ permissionsTable.setEnabled(enabled);
+ registrantSelector.setEnabled(enabled);
+ permissionSelector.setEnabled(enabled);
+ addButton.setEnabled(enabled);
+ }
+
+ public void setObjects(List<String> registrants, List<RegistrantAccessPermission> permissions) {
+ List<String> filtered;
+ if (registrants == null) {
+ filtered = new ArrayList<String>();
+ } else {
+ filtered = new ArrayList<String>(registrants);
+ }
+ if (permissions == null) {
+ permissions = new ArrayList<RegistrantAccessPermission>();
+ }
+ for (RegistrantAccessPermission rp : permissions) {
+ if (rp.mutable) {
+ // only remove editable duplicates
+ // this allows for specifying an explicit permission
+ filtered.remove(rp.registrant);
+ } else if (rp.isAdmin()) {
+ // administrators can not have their permission changed
+ filtered.remove(rp.registrant);
+ } else if (rp.isOwner()) {
+ // owners can not have their permission changed
+ filtered.remove(rp.registrant);
+ }
+ }
+ for (String registrant : filtered) {
+ registrantModel.addElement(registrant);
+ }
+ tableModel.setPermissions(permissions);
+
+ registrantSelector.setSelectedIndex(-1);
+ permissionSelector.setSelectedIndex(-1);
+ addPanel.setVisible(filtered.size() > 0);
+ }
+
+ public List<RegistrantAccessPermission> getPermissions() {
+ return tableModel.permissions;
+ }
+
+ private class AccessPermissionEditor extends DefaultCellEditor {
+
+ private static final long serialVersionUID = 1L;
+
+ public AccessPermissionEditor() {
+ super(new JComboBox(AccessPermission.values()));
+ }
+ }
+
+ private class PermissionTypeRenderer extends DefaultTableCellRenderer {
+
+ private static final long serialVersionUID = 1L;
+
+ public PermissionTypeRenderer() {
+ super();
+ setHorizontalAlignment(SwingConstants.CENTER);
+ }
+
+ @Override
+ protected void setValue(Object value) {
+ RegistrantAccessPermission ap = (RegistrantAccessPermission) value;
+ switch (ap.permissionType) {
+ case ADMINISTRATOR:
+ setText(ap.source == null ? Translation.get("gb.administrator") : ap.source);
+ setToolTipText(Translation.get("gb.administratorPermission"));
+ break;
+ case OWNER:
+ setText(Translation.get("gb.owner"));
+ setToolTipText(Translation.get("gb.ownerPermission"));
+ break;
+ case TEAM:
+ setText(ap.source == null ? Translation.get("gb.team") : ap.source);
+ setToolTipText(MessageFormat.format(Translation.get("gb.teamPermission"), ap.source));
+ break;
+ case REGEX:
+ setText("regex");
+ setToolTipText(MessageFormat.format(Translation.get("gb.regexPermission"), ap.source));
+ break;
+ default:
+ if (ap.isMissing()) {
+ setText(Translation.get("gb.missing"));
+ setToolTipText(Translation.get("gb.missingPermission"));
+ } else {
+ setText("");
+ setToolTipText(null);
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/gitblit/client/RegistrantPermissionsTableModel.java b/src/main/java/com/gitblit/client/RegistrantPermissionsTableModel.java
new file mode 100644
index 00000000..28d25345
--- /dev/null
+++ b/src/main/java/com/gitblit/client/RegistrantPermissionsTableModel.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2012 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.client;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.table.AbstractTableModel;
+
+import com.gitblit.Constants.AccessPermission;
+import com.gitblit.models.RegistrantAccessPermission;
+
+/**
+ * Table model of a registrant permissions.
+ *
+ * @author James Moger
+ *
+ */
+public class RegistrantPermissionsTableModel extends AbstractTableModel {
+
+ private static final long serialVersionUID = 1L;
+
+ List<RegistrantAccessPermission> permissions;
+
+ enum Columns {
+ Registrant, Type, Permission;
+
+ @Override
+ public String toString() {
+ return name().replace('_', ' ');
+ }
+ }
+
+ public RegistrantPermissionsTableModel() {
+ this(new ArrayList<RegistrantAccessPermission>());
+ }
+
+ public RegistrantPermissionsTableModel(List<RegistrantAccessPermission> list) {
+ setPermissions(list);
+ }
+
+ public void setPermissions(List<RegistrantAccessPermission> list) {
+ this.permissions = list;
+ }
+
+ @Override
+ public int getRowCount() {
+ return permissions.size();
+ }
+
+ @Override
+ public int getColumnCount() {
+ return Columns.values().length;
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ Columns col = Columns.values()[column];
+ switch (col) {
+ case Registrant:
+ return Translation.get("gb.name");
+ case Type:
+ return Translation.get("gb.type");
+ case Permission:
+ return Translation.get("gb.permission");
+ }
+ return "";
+ }
+
+ /**
+ * Returns <code>Object.class</code> regardless of <code>columnIndex</code>.
+ *
+ * @param columnIndex
+ * the column being queried
+ * @return the Object.class
+ */
+ public Class<?> getColumnClass(int columnIndex) {
+ if (columnIndex == Columns.Permission.ordinal()) {
+ return AccessPermission.class;
+ } else if (columnIndex == Columns.Type.ordinal()) {
+ return RegistrantAccessPermission.class;
+ }
+ return String.class;
+ }
+
+ @Override
+ public boolean isCellEditable(int rowIndex, int columnIndex) {
+ if (columnIndex == Columns.Permission.ordinal()) {
+ // in order for the permission to be editable it must be
+ // explicitly defined on the object. regex permissions are inherited
+ // and therefore can not be directly manipulated unless the current
+ // object is the source of the regex (i.e. a user or team with explicit
+ // regex definition)
+ return permissions.get(rowIndex).mutable;
+ }
+ return false;
+ }
+
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ RegistrantAccessPermission rp = permissions.get(rowIndex);
+ Columns col = Columns.values()[columnIndex];
+ switch (col) {
+ case Registrant:
+ return rp.registrant;
+ case Type:
+ return rp;
+ case Permission:
+ return rp.permission;
+ }
+ return null;
+ }
+
+ @Override
+ public void setValueAt(Object o, int rowIndex, int columnIndex) {
+ RegistrantAccessPermission rp = permissions.get(rowIndex);
+ if (columnIndex == Columns.Permission.ordinal()) {
+ rp.permission = (AccessPermission) o;
+ }
+ }
+}
diff --git a/src/main/java/com/gitblit/client/RegistrationsDialog.java b/src/main/java/com/gitblit/client/RegistrationsDialog.java
new file mode 100644
index 00000000..9550e97a
--- /dev/null
+++ b/src/main/java/com/gitblit/client/RegistrationsDialog.java
@@ -0,0 +1,228 @@
+/*
+ * 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.KeyEvent;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JPanel;
+import javax.swing.JRootPane;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.KeyStroke;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+/**
+ * Displays a list of registrations and allows management of server
+ * registrations.
+ *
+ * @author James Moger
+ *
+ */
+public class RegistrationsDialog extends JDialog {
+
+ interface RegistrationListener {
+
+ void login(GitblitRegistration reg);
+
+ boolean saveRegistration(String name, GitblitRegistration reg);
+
+ boolean deleteRegistrations(List<GitblitRegistration> list);
+ }
+
+ private static final long serialVersionUID = 1L;
+
+ private final List<GitblitRegistration> registrations;
+
+ private final RegistrationListener listener;
+
+ private JTable registrationsTable;
+
+ private RegistrationsTableModel model;
+
+ public RegistrationsDialog(List<GitblitRegistration> registrations,
+ RegistrationListener listener) {
+ super();
+ this.registrations = registrations;
+ this.listener = listener;
+ setTitle(Translation.get("gb.manage"));
+ setIconImage(new ImageIcon(getClass().getResource("/gitblt-favicon.png")).getImage());
+ initialize();
+ setSize(600, 400);
+ }
+
+ @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() {
+ NameRenderer nameRenderer = new NameRenderer();
+ model = new RegistrationsTableModel(registrations);
+ registrationsTable = Utils.newTable(model, Utils.DATE_FORMAT);
+
+ String id = registrationsTable
+ .getColumnName(RegistrationsTableModel.Columns.Name.ordinal());
+ registrationsTable.getColumn(id).setCellRenderer(nameRenderer);
+ registrationsTable.addMouseListener(new MouseAdapter() {
+ public void mouseClicked(MouseEvent e) {
+ if (e.getClickCount() == 2) {
+ login();
+ }
+ }
+ });
+
+ final JButton create = new JButton(Translation.get("gb.create"));
+ create.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ create();
+ }
+ });
+
+ final JButton login = new JButton(Translation.get("gb.login"));
+ login.setEnabled(false);
+ login.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ login();
+ }
+ });
+
+ final JButton edit = new JButton(Translation.get("gb.edit"));
+ edit.setEnabled(false);
+ edit.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ edit();
+ }
+ });
+
+ final JButton delete = new JButton(Translation.get("gb.delete"));
+ delete.setEnabled(false);
+ delete.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ delete();
+ }
+ });
+
+ registrationsTable.getSelectionModel().addListSelectionListener(
+ new ListSelectionListener() {
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ if (e.getValueIsAdjusting()) {
+ return;
+ }
+ boolean singleSelection = registrationsTable.getSelectedRowCount() == 1;
+ boolean selected = registrationsTable.getSelectedRow() > -1;
+ login.setEnabled(singleSelection);
+ edit.setEnabled(singleSelection);
+ delete.setEnabled(selected);
+ }
+ });
+
+ JPanel controls = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 0));
+ controls.add(create);
+ controls.add(login);
+ controls.add(edit);
+ controls.add(delete);
+
+ final Insets insets = new Insets(5, 5, 5, 5);
+ JPanel centerPanel = new JPanel(new BorderLayout(5, 5)) {
+
+ private static final long serialVersionUID = 1L;
+
+ public Insets getInsets() {
+ return insets;
+ }
+ };
+ centerPanel.add(new HeaderPanel(Translation.get("gb.servers"), null), BorderLayout.NORTH);
+ centerPanel.add(new JScrollPane(registrationsTable), BorderLayout.CENTER);
+ centerPanel.add(controls, BorderLayout.SOUTH);
+
+ getContentPane().setLayout(new BorderLayout(5, 5));
+ getContentPane().add(centerPanel, BorderLayout.CENTER);
+ }
+
+ private void login() {
+ int viewRow = registrationsTable.getSelectedRow();
+ int modelRow = registrationsTable.convertRowIndexToModel(viewRow);
+ GitblitRegistration reg = registrations.get(modelRow);
+ RegistrationsDialog.this.setVisible(false);
+ listener.login(reg);
+ }
+
+ private void create() {
+ EditRegistrationDialog dialog = new EditRegistrationDialog(getOwner());
+ dialog.setLocationRelativeTo(this);
+ dialog.setVisible(true);
+ GitblitRegistration reg = dialog.getRegistration();
+ if (reg == null) {
+ return;
+ }
+ if (listener.saveRegistration(reg.name, reg)) {
+ model.list.add(reg);
+ model.fireTableDataChanged();
+ }
+ }
+
+ private void edit() {
+ int viewRow = registrationsTable.getSelectedRow();
+ int modelRow = registrationsTable.convertRowIndexToModel(viewRow);
+ GitblitRegistration reg = registrations.get(modelRow);
+ String originalName = reg.name;
+ EditRegistrationDialog dialog = new EditRegistrationDialog(getOwner(), reg, false);
+ dialog.setLocationRelativeTo(this);
+ dialog.setVisible(true);
+ reg = dialog.getRegistration();
+ if (reg == null) {
+ return;
+ }
+ if (listener.saveRegistration(originalName, reg)) {
+ model.fireTableDataChanged();
+ }
+ }
+
+ private void delete() {
+ List<GitblitRegistration> list = new ArrayList<GitblitRegistration>();
+ for (int i : registrationsTable.getSelectedRows()) {
+ int model = registrationsTable.convertRowIndexToModel(i);
+ GitblitRegistration reg = registrations.get(model);
+ list.add(reg);
+ }
+ if (listener.deleteRegistrations(list)) {
+ registrations.removeAll(list);
+ model.fireTableDataChanged();
+ }
+ }
+}
diff --git a/src/main/java/com/gitblit/client/RegistrationsTableModel.java b/src/main/java/com/gitblit/client/RegistrationsTableModel.java
new file mode 100644
index 00000000..8c6b34ff
--- /dev/null
+++ b/src/main/java/com/gitblit/client/RegistrationsTableModel.java
@@ -0,0 +1,102 @@
+/*
+ * 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.Collections;
+import java.util.Date;
+import java.util.List;
+
+import javax.swing.table.AbstractTableModel;
+
+/**
+ * Table model of a list of Gitblit server registrations.
+ *
+ * @author James Moger
+ *
+ */
+public class RegistrationsTableModel extends AbstractTableModel {
+
+ private static final long serialVersionUID = 1L;
+
+ List<GitblitRegistration> list;
+
+ enum Columns {
+ Name, URL, Last_Login;
+
+ @Override
+ public String toString() {
+ return name().replace('_', ' ');
+ }
+ }
+
+ public RegistrationsTableModel(List<GitblitRegistration> list) {
+ this.list = list;
+ 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 URL:
+ return Translation.get("gb.url");
+ case Last_Login:
+ return Translation.get("gb.lastLogin");
+ }
+ return "";
+ }
+
+ /**
+ * Returns <code>Object.class</code> regardless of <code>columnIndex</code>.
+ *
+ * @param columnIndex
+ * the column being queried
+ * @return the Object.class
+ */
+ public Class<?> getColumnClass(int columnIndex) {
+ if (columnIndex == Columns.Last_Login.ordinal()) {
+ return Date.class;
+ }
+ return String.class;
+ }
+
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ GitblitRegistration model = list.get(rowIndex);
+ Columns col = Columns.values()[columnIndex];
+ switch (col) {
+ case Name:
+ return model.name;
+ case URL:
+ return model.url;
+ case Last_Login:
+ return model.lastLogin;
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/gitblit/client/RepositoriesPanel.java b/src/main/java/com/gitblit/client/RepositoriesPanel.java
new file mode 100644
index 00000000..64bde9b8
--- /dev/null
+++ b/src/main/java/com/gitblit/client/RepositoriesPanel.java
@@ -0,0 +1,560 @@
+/*
+ * 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.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.Arrays;
+import java.util.List;
+import java.util.Map;
+
+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.SwingConstants;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableRowSorter;
+
+import com.gitblit.Constants;
+import com.gitblit.Constants.RpcRequest;
+import com.gitblit.Keys;
+import com.gitblit.models.FeedModel;
+import com.gitblit.models.RegistrantAccessPermission;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * RSS Feeds Panel displays recent entries and launches the browser to view the
+ * commit. commitdiff, or tree of a commit.
+ *
+ * @author James Moger
+ *
+ */
+public abstract class RepositoriesPanel extends JPanel {
+
+ private static final long serialVersionUID = 1L;
+
+ private final GitblitClient gitblit;
+
+ private HeaderPanel header;
+
+ private JTable table;
+
+ private RepositoriesTableModel tableModel;
+
+ private TableRowSorter<RepositoriesTableModel> defaultSorter;
+
+ private JButton createRepository;
+
+ private JButton editRepository;
+
+ private JButton delRepository;
+
+ private JTextField filterTextfield;
+
+ private JButton clearCache;
+
+ public RepositoriesPanel(GitblitClient gitblit) {
+ super();
+ this.gitblit = gitblit;
+ initialize();
+ }
+
+ private void initialize() {
+ final JButton browseRepository = new JButton(Translation.get("gb.browse"));
+ browseRepository.setEnabled(false);
+ browseRepository.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ RepositoryModel model = getSelectedRepositories().get(0);
+ String url = gitblit.getURL("summary", model.name, null);
+ Utils.browse(url);
+ }
+ });
+
+ JButton refreshRepositories = new JButton(Translation.get("gb.refresh"));
+ refreshRepositories.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ refreshRepositories();
+ }
+ });
+
+ clearCache = new JButton(Translation.get("gb.clearCache"));
+ clearCache.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ clearCache();
+ }
+ });
+
+ createRepository = new JButton(Translation.get("gb.create"));
+ createRepository.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ createRepository();
+ }
+ });
+
+ editRepository = new JButton(Translation.get("gb.edit"));
+ editRepository.setEnabled(false);
+ editRepository.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ editRepository(getSelectedRepositories().get(0));
+ }
+ });
+
+ delRepository = new JButton(Translation.get("gb.delete"));
+ delRepository.setEnabled(false);
+ delRepository.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ deleteRepositories(getSelectedRepositories());
+ }
+ });
+
+ final JButton subscribeRepository = new JButton(Translation.get("gb.subscribe") + "...");
+ subscribeRepository.setEnabled(false);
+ subscribeRepository.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ List<FeedModel> feeds = gitblit.getAvailableFeeds(getSelectedRepositories().get(0));
+ subscribeFeeds(feeds);
+ }
+ });
+
+ final JButton logRepository = new JButton(Translation.get("gb.log") + "...");
+ logRepository.setEnabled(false);
+ logRepository.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ RepositoryModel model = getSelectedRepositories().get(0);
+ showSearchDialog(false, model);
+ }
+ });
+
+ final JButton searchRepository = new JButton(Translation.get("gb.search") + "...");
+ searchRepository.setEnabled(false);
+ searchRepository.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ RepositoryModel model = getSelectedRepositories().get(0);
+ showSearchDialog(true, model);
+ }
+ });
+
+ SubscribedRepositoryRenderer nameRenderer = new SubscribedRepositoryRenderer(gitblit);
+ IndicatorsRenderer typeRenderer = new IndicatorsRenderer();
+
+ DefaultTableCellRenderer sizeRenderer = new DefaultTableCellRenderer();
+ sizeRenderer.setHorizontalAlignment(SwingConstants.RIGHT);
+ sizeRenderer.setForeground(new Color(0, 0x80, 0));
+
+ DefaultTableCellRenderer ownerRenderer = new DefaultTableCellRenderer();
+ ownerRenderer.setForeground(Color.gray);
+ ownerRenderer.setHorizontalAlignment(SwingConstants.CENTER);
+
+ tableModel = new RepositoriesTableModel();
+ defaultSorter = new TableRowSorter<RepositoriesTableModel>(tableModel);
+ table = Utils.newTable(tableModel, Utils.DATE_FORMAT);
+ table.setRowSorter(defaultSorter);
+ table.getRowSorter().toggleSortOrder(RepositoriesTableModel.Columns.Name.ordinal());
+
+ setRepositoryRenderer(RepositoriesTableModel.Columns.Name, nameRenderer, -1);
+ setRepositoryRenderer(RepositoriesTableModel.Columns.Indicators, typeRenderer, 100);
+ setRepositoryRenderer(RepositoriesTableModel.Columns.Owner, ownerRenderer, -1);
+ setRepositoryRenderer(RepositoriesTableModel.Columns.Size, sizeRenderer, 60);
+
+ table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ if (e.getValueIsAdjusting()) {
+ return;
+ }
+ boolean singleSelection = table.getSelectedRowCount() == 1;
+ boolean selected = table.getSelectedRow() > -1;
+ if (singleSelection) {
+ RepositoryModel repository = getSelectedRepositories().get(0);
+ browseRepository.setEnabled(repository.hasCommits);
+ logRepository.setEnabled(repository.hasCommits);
+ searchRepository.setEnabled(repository.hasCommits);
+ subscribeRepository.setEnabled(repository.hasCommits);
+ } else {
+ browseRepository.setEnabled(false);
+ logRepository.setEnabled(false);
+ searchRepository.setEnabled(false);
+ subscribeRepository.setEnabled(false);
+ }
+ delRepository.setEnabled(selected);
+ if (selected) {
+ int viewRow = table.getSelectedRow();
+ int modelRow = table.convertRowIndexToModel(viewRow);
+ RepositoryModel model = ((RepositoriesTableModel) table.getModel()).list
+ .get(modelRow);
+ editRepository.setEnabled(singleSelection
+ && (gitblit.allowManagement() || gitblit.isOwner(model)));
+ } else {
+ editRepository.setEnabled(false);
+ }
+ }
+ });
+
+ table.addMouseListener(new MouseAdapter() {
+ public void mouseClicked(MouseEvent e) {
+ if (e.getClickCount() == 2 && gitblit.allowManagement()) {
+ editRepository(getSelectedRepositories().get(0));
+ }
+ }
+ });
+
+ filterTextfield = new JTextField();
+ filterTextfield.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ filterRepositories(filterTextfield.getText());
+ }
+ });
+ filterTextfield.addKeyListener(new KeyAdapter() {
+ public void keyReleased(KeyEvent e) {
+ filterRepositories(filterTextfield.getText());
+ }
+ });
+
+ JPanel repositoryFilterPanel = new JPanel(new BorderLayout(Utils.MARGIN, Utils.MARGIN));
+ repositoryFilterPanel.add(new JLabel(Translation.get("gb.filter")), BorderLayout.WEST);
+ repositoryFilterPanel.add(filterTextfield, BorderLayout.CENTER);
+
+ JPanel repositoryTablePanel = new JPanel(new BorderLayout(Utils.MARGIN, Utils.MARGIN));
+ repositoryTablePanel.add(repositoryFilterPanel, BorderLayout.NORTH);
+ repositoryTablePanel.add(new JScrollPane(table), BorderLayout.CENTER);
+
+ JPanel repositoryControls = new JPanel(new FlowLayout(FlowLayout.CENTER, Utils.MARGIN, 0));
+ repositoryControls.add(clearCache);
+ repositoryControls.add(refreshRepositories);
+ repositoryControls.add(browseRepository);
+ repositoryControls.add(createRepository);
+ repositoryControls.add(editRepository);
+ repositoryControls.add(delRepository);
+ repositoryControls.add(subscribeRepository);
+ repositoryControls.add(logRepository);
+ repositoryControls.add(searchRepository);
+
+ setLayout(new BorderLayout(Utils.MARGIN, Utils.MARGIN));
+ header = new HeaderPanel(Translation.get("gb.repositories"), "git-orange-16x16.png");
+ add(header, BorderLayout.NORTH);
+ add(repositoryTablePanel, BorderLayout.CENTER);
+ add(repositoryControls, BorderLayout.SOUTH);
+ }
+
+ @Override
+ public void requestFocus() {
+ filterTextfield.requestFocus();
+ }
+
+ @Override
+ public Insets getInsets() {
+ return Utils.INSETS;
+ }
+
+ private void setRepositoryRenderer(RepositoriesTableModel.Columns col,
+ TableCellRenderer renderer, int maxWidth) {
+ String name = table.getColumnName(col.ordinal());
+ table.getColumn(name).setCellRenderer(renderer);
+ if (maxWidth > 0) {
+ table.getColumn(name).setMinWidth(maxWidth);
+ table.getColumn(name).setMaxWidth(maxWidth);
+ }
+ }
+
+ protected abstract void subscribeFeeds(List<FeedModel> feeds);
+
+ protected abstract void updateUsersTable();
+
+ protected abstract void updateTeamsTable();
+
+ protected void disableManagement() {
+ clearCache.setVisible(false);
+ createRepository.setVisible(false);
+ editRepository.setVisible(false);
+ delRepository.setVisible(false);
+ }
+
+ protected void updateTable(boolean pack) {
+ tableModel.list.clear();
+ tableModel.list.addAll(gitblit.getRepositories());
+ tableModel.fireTableDataChanged();
+ header.setText(Translation.get("gb.repositories") + " (" + gitblit.getRepositories().size()
+ + ")");
+ if (pack) {
+ Utils.packColumns(table, Utils.MARGIN);
+ }
+ }
+
+ private void filterRepositories(final String fragment) {
+ if (StringUtils.isEmpty(fragment)) {
+ table.setRowSorter(defaultSorter);
+ return;
+ }
+ RowFilter<RepositoriesTableModel, Object> containsFilter = new RowFilter<RepositoriesTableModel, Object>() {
+ public boolean include(Entry<? extends RepositoriesTableModel, ? extends Object> entry) {
+ for (int i = entry.getValueCount() - 1; i >= 0; i--) {
+ if (entry.getStringValue(i).toLowerCase().contains(fragment.toLowerCase())) {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+ TableRowSorter<RepositoriesTableModel> sorter = new TableRowSorter<RepositoriesTableModel>(
+ tableModel);
+ sorter.setRowFilter(containsFilter);
+ table.setRowSorter(sorter);
+ }
+
+ private List<RepositoryModel> getSelectedRepositories() {
+ List<RepositoryModel> repositories = new ArrayList<RepositoryModel>();
+ for (int viewRow : table.getSelectedRows()) {
+ int modelRow = table.convertRowIndexToModel(viewRow);
+ RepositoryModel model = tableModel.list.get(modelRow);
+ repositories.add(model);
+ }
+ return repositories;
+ }
+
+ protected void refreshRepositories() {
+ GitblitWorker worker = new GitblitWorker(RepositoriesPanel.this,
+ RpcRequest.LIST_REPOSITORIES) {
+ @Override
+ protected Boolean doRequest() throws IOException {
+ gitblit.refreshRepositories();
+ return true;
+ }
+
+ @Override
+ protected void onSuccess() {
+ updateTable(false);
+ }
+ };
+ worker.execute();
+ }
+
+ protected void clearCache() {
+ GitblitWorker worker = new GitblitWorker(RepositoriesPanel.this,
+ RpcRequest.CLEAR_REPOSITORY_CACHE) {
+ @Override
+ protected Boolean doRequest() throws IOException {
+ if (gitblit.clearRepositoryCache()) {
+ gitblit.refreshRepositories();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected void onSuccess() {
+ updateTable(false);
+ }
+ };
+ worker.execute();
+ }
+
+ /**
+ * Displays the create repository dialog and fires a SwingWorker to update
+ * the server, if appropriate.
+ *
+ */
+ protected void createRepository() {
+ EditRepositoryDialog dialog = new EditRepositoryDialog(gitblit.getProtocolVersion());
+ dialog.setLocationRelativeTo(RepositoriesPanel.this);
+ dialog.setAccessRestriction(gitblit.getDefaultAccessRestriction());
+ dialog.setAuthorizationControl(gitblit.getDefaultAuthorizationControl());
+ dialog.setUsers(null, gitblit.getUsernames(), null);
+ dialog.setTeams(gitblit.getTeamnames(), null);
+ dialog.setRepositories(gitblit.getRepositories());
+ dialog.setFederationSets(gitblit.getFederationSets(), null);
+ dialog.setIndexedBranches(new ArrayList<String>(Arrays.asList(Constants.DEFAULT_BRANCH)), null);
+ dialog.setPreReceiveScripts(gitblit.getPreReceiveScriptsUnused(null),
+ gitblit.getPreReceiveScriptsInherited(null), null);
+ dialog.setPostReceiveScripts(gitblit.getPostReceiveScriptsUnused(null),
+ gitblit.getPostReceiveScriptsInherited(null), null);
+ dialog.setVisible(true);
+ final RepositoryModel newRepository = dialog.getRepository();
+ final List<RegistrantAccessPermission> permittedUsers = dialog.getUserAccessPermissions();
+ final List<RegistrantAccessPermission> permittedTeams = dialog.getTeamAccessPermissions();
+ if (newRepository == null) {
+ return;
+ }
+
+ GitblitWorker worker = new GitblitWorker(this, RpcRequest.CREATE_REPOSITORY) {
+
+ @Override
+ protected Boolean doRequest() throws IOException {
+ 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;
+ }
+
+ @Override
+ protected void onSuccess() {
+ updateTable(false);
+ updateUsersTable();
+ updateTeamsTable();
+ }
+
+ @Override
+ protected void onFailure() {
+ showFailure("Failed to execute request \"{0}\" for repository \"{1}\".",
+ getRequestType(), newRepository.name);
+ }
+ };
+ 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(gitblit.getProtocolVersion(),
+ repository);
+ dialog.setLocationRelativeTo(RepositoriesPanel.this);
+ List<String> usernames = gitblit.getUsernames();
+ List<RegistrantAccessPermission> members = gitblit.getUserAccessPermissions(repository);
+ dialog.setUsers(new ArrayList<String>(repository.owners), usernames, members);
+ dialog.setTeams(gitblit.getTeamnames(), gitblit.getTeamAccessPermissions(repository));
+ dialog.setRepositories(gitblit.getRepositories());
+ dialog.setFederationSets(gitblit.getFederationSets(), repository.federationSets);
+ List<String> allLocalBranches = new ArrayList<String>();
+ allLocalBranches.add(Constants.DEFAULT_BRANCH);
+ allLocalBranches.addAll(repository.getLocalBranches());
+ dialog.setIndexedBranches(allLocalBranches, repository.indexedBranches);
+ dialog.setPreReceiveScripts(gitblit.getPreReceiveScriptsUnused(repository),
+ gitblit.getPreReceiveScriptsInherited(repository), repository.preReceiveScripts);
+ dialog.setPostReceiveScripts(gitblit.getPostReceiveScriptsUnused(repository),
+ gitblit.getPostReceiveScriptsInherited(repository), repository.postReceiveScripts);
+ if (gitblit.getSettings().hasKey(Keys.groovy.customFields)) {
+ Map<String, String> map = gitblit.getSettings().get(Keys.groovy.customFields).getMap();
+ dialog.setCustomFields(repository, map);
+ }
+ dialog.setVisible(true);
+ final RepositoryModel revisedRepository = dialog.getRepository();
+ final List<RegistrantAccessPermission> permittedUsers = dialog.getUserAccessPermissions();
+ final List<RegistrantAccessPermission> permittedTeams = dialog.getTeamAccessPermissions();
+ if (revisedRepository == null) {
+ return;
+ }
+
+ GitblitWorker worker = new GitblitWorker(this, RpcRequest.EDIT_REPOSITORY) {
+
+ @Override
+ protected Boolean doRequest() throws IOException {
+ boolean success = gitblit.updateRepository(repository.name, revisedRepository,
+ permittedUsers, permittedTeams);
+ if (success) {
+ gitblit.refreshRepositories();
+ gitblit.refreshUsers();
+ gitblit.refreshTeams();
+ }
+ return success;
+ }
+
+ @Override
+ protected void onSuccess() {
+ updateTable(false);
+ updateUsersTable();
+ updateTeamsTable();
+ }
+
+ @Override
+ protected void onFailure() {
+ showFailure("Failed to execute request \"{0}\" for repository \"{1}\".",
+ getRequestType(), repository.name);
+ }
+ };
+ worker.execute();
+ }
+
+ protected void deleteRepositories(final List<RepositoryModel> 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(RepositoriesPanel.this, message.toString(),
+ "Delete Repositories?", JOptionPane.YES_NO_OPTION);
+ if (result == JOptionPane.YES_OPTION) {
+ GitblitWorker worker = new GitblitWorker(this, RpcRequest.DELETE_REPOSITORY) {
+ @Override
+ protected Boolean doRequest() throws IOException {
+ boolean success = true;
+ for (RepositoryModel repository : repositories) {
+ success &= gitblit.deleteRepository(repository);
+ }
+ if (success) {
+ gitblit.refreshRepositories();
+ gitblit.refreshUsers();
+ gitblit.refreshTeams();
+ }
+ return success;
+ }
+
+ @Override
+ protected void onSuccess() {
+ updateTable(false);
+ updateUsersTable();
+ updateTeamsTable();
+ }
+
+ @Override
+ protected void onFailure() {
+ showFailure("Failed to delete specified repositories!");
+ }
+ };
+ worker.execute();
+ }
+ }
+
+ private void showSearchDialog(boolean isSearch, final RepositoryModel repository) {
+ final SearchDialog dialog = new SearchDialog(gitblit, isSearch);
+ if (repository != null) {
+ dialog.selectRepository(repository);
+ }
+ dialog.setLocationRelativeTo(this);
+ dialog.setVisible(true);
+ }
+}
diff --git a/src/main/java/com/gitblit/client/RepositoriesTableModel.java b/src/main/java/com/gitblit/client/RepositoriesTableModel.java
new file mode 100644
index 00000000..6b295a4b
--- /dev/null
+++ b/src/main/java/com/gitblit/client/RepositoriesTableModel.java
@@ -0,0 +1,128 @@
+/*
+ * 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.Date;
+import java.util.List;
+
+import javax.swing.table.AbstractTableModel;
+
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.utils.ArrayUtils;
+
+/**
+ * Table model of a list of repositories.
+ *
+ * @author James Moger
+ *
+ */
+public class RepositoriesTableModel extends AbstractTableModel {
+
+ private static final long serialVersionUID = 1L;
+
+ List<RepositoryModel> list;
+
+ enum Columns {
+ Name, Description, Owner, Indicators, Last_Change, Size;
+
+ @Override
+ public String toString() {
+ return name().replace('_', ' ');
+ }
+ }
+
+ public RepositoriesTableModel() {
+ this(new ArrayList<RepositoryModel>());
+ }
+
+ public RepositoriesTableModel(List<RepositoryModel> repositories) {
+ this.list = repositories;
+ 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 Description:
+ return Translation.get("gb.description");
+ case Owner:
+ return Translation.get("gb.owner");
+ case Last_Change:
+ return Translation.get("gb.lastChange");
+ case Size:
+ return Translation.get("gb.size");
+ }
+ return "";
+ }
+
+ /**
+ * Returns <code>Object.class</code> regardless of <code>columnIndex</code>.
+ *
+ * @param columnIndex
+ * the column being queried
+ * @return the Object.class
+ */
+ public Class<?> getColumnClass(int columnIndex) {
+ Columns col = Columns.values()[columnIndex];
+ switch (col) {
+ case Name:
+ case Indicators:
+ return RepositoryModel.class;
+ case Last_Change:
+ return Date.class;
+ }
+ return String.class;
+ }
+
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ RepositoryModel model = list.get(rowIndex);
+ Columns col = Columns.values()[columnIndex];
+ switch (col) {
+ case Name:
+ return model;
+ case Description:
+ return model.description;
+ case Owner:
+ return ArrayUtils.toString(model.owners);
+ case Indicators:
+ return model;
+ case Last_Change:
+ return model.lastChange;
+ case Size:
+ if (model.hasCommits) {
+ return model.size;
+ }
+ return "(empty)";
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/gitblit/client/SearchDialog.java b/src/main/java/com/gitblit/client/SearchDialog.java
new file mode 100644
index 00000000..829bc52a
--- /dev/null
+++ b/src/main/java/com/gitblit/client/SearchDialog.java
@@ -0,0 +1,404 @@
+/*
+ * 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.Cursor;
+import java.awt.FlowLayout;
+import java.awt.Insets;
+import java.awt.Rectangle;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.io.IOException;
+import java.util.List;
+
+import javax.swing.DefaultComboBoxModel;
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JComboBox;
+import javax.swing.JFrame;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.JTextField;
+import javax.swing.SwingWorker;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import com.gitblit.Constants;
+import com.gitblit.models.FeedEntryModel;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * The search dialog allows searching of a repository branch. This matches the
+ * search implementation of the site.
+ *
+ * @author James Moger
+ *
+ */
+public class SearchDialog extends JFrame {
+
+ private static final long serialVersionUID = 1L;
+
+ private final boolean isSearch;
+
+ private final GitblitClient gitblit;
+
+ private FeedEntryTableModel tableModel;
+
+ private HeaderPanel header;
+
+ private JTable table;
+
+ private JComboBox repositorySelector;
+
+ private DefaultComboBoxModel branchChoices;
+
+ private JComboBox branchSelector;
+
+ private JComboBox searchTypeSelector;
+
+ private JTextField searchFragment;
+
+ private JComboBox maxHitsSelector;
+
+ private int page;
+
+ private JButton prev;
+
+ private JButton next;
+
+ public SearchDialog(GitblitClient gitblit, boolean isSearch) {
+ super();
+ this.gitblit = gitblit;
+ this.isSearch = isSearch;
+ setTitle(Translation.get(isSearch ? "gb.search" : "gb.log"));
+ setIconImage(new ImageIcon(getClass().getResource("/gitblt-favicon.png")).getImage());
+ initialize();
+ setSize(900, 550);
+ }
+
+ private void initialize() {
+
+ prev = new JButton("<");
+ prev.setToolTipText(Translation.get("gb.pagePrevious"));
+ prev.setEnabled(false);
+ prev.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ search(--page);
+ }
+ });
+
+ next = new JButton(">");
+ next.setToolTipText(Translation.get("gb.pageNext"));
+ next.setEnabled(false);
+ next.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ search(++page);
+ }
+ });
+
+ final JButton search = new JButton(Translation.get(isSearch ? "gb.search" : "gb.refresh"));
+ search.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ search(0);
+ }
+ });
+
+ final JButton viewCommit = new JButton(Translation.get("gb.view"));
+ viewCommit.setEnabled(false);
+ viewCommit.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ viewCommit();
+ }
+ });
+
+ final JButton viewCommitDiff = new JButton(Translation.get("gb.commitdiff"));
+ viewCommitDiff.setEnabled(false);
+ viewCommitDiff.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ viewCommitDiff();
+ }
+ });
+
+ final JButton viewTree = new JButton(Translation.get("gb.tree"));
+ viewTree.setEnabled(false);
+ viewTree.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ viewTree();
+ }
+ });
+
+ JPanel controls = new JPanel(new FlowLayout(FlowLayout.CENTER, Utils.MARGIN, 0));
+ controls.add(viewCommit);
+ controls.add(viewCommitDiff);
+ controls.add(viewTree);
+
+ NameRenderer nameRenderer = new NameRenderer();
+ tableModel = new FeedEntryTableModel();
+ header = new HeaderPanel(Translation.get(isSearch ? "gb.search" : "gb.log"),
+ isSearch ? "search-icon.png" : "commit_changes_16x16.png");
+ table = Utils.newTable(tableModel, Utils.DATE_FORMAT);
+
+ String name = table.getColumnName(FeedEntryTableModel.Columns.Author.ordinal());
+ table.getColumn(name).setCellRenderer(nameRenderer);
+ name = table.getColumnName(FeedEntryTableModel.Columns.Repository.ordinal());
+ table.getColumn(name).setCellRenderer(nameRenderer);
+
+ name = table.getColumnName(FeedEntryTableModel.Columns.Branch.ordinal());
+ table.getColumn(name).setCellRenderer(new BranchRenderer());
+
+ name = table.getColumnName(FeedEntryTableModel.Columns.Message.ordinal());
+ table.getColumn(name).setCellRenderer(new MessageRenderer());
+
+ table.addMouseListener(new MouseAdapter() {
+ public void mouseClicked(MouseEvent e) {
+ if (e.getClickCount() == 2) {
+ if (e.isControlDown()) {
+ viewCommitDiff();
+ } else {
+ viewCommit();
+ }
+ }
+ }
+ });
+
+ table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ if (e.getValueIsAdjusting()) {
+ return;
+ }
+ boolean singleSelection = table.getSelectedRowCount() == 1;
+ viewCommit.setEnabled(singleSelection);
+ viewCommitDiff.setEnabled(singleSelection);
+ viewTree.setEnabled(singleSelection);
+ }
+ });
+
+ repositorySelector = new JComboBox(gitblit.getRepositories().toArray());
+ repositorySelector.setRenderer(nameRenderer);
+ repositorySelector.setForeground(nameRenderer.getForeground());
+ repositorySelector.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ // repopulate the branch list based on repository selection
+ // preserve branch selection, if possible
+ String selectedBranch = null;
+ if (branchSelector.getSelectedIndex() > -1) {
+ selectedBranch = branchSelector.getSelectedItem().toString();
+ }
+ updateBranches();
+ if (StringUtils.isEmpty(selectedBranch)) {
+ // do not select branch
+ branchSelector.setSelectedIndex(-1);
+ } else {
+ if (branchChoices.getIndexOf(selectedBranch) > -1) {
+ // select branch
+ branchChoices.setSelectedItem(selectedBranch);
+ } else {
+ // branch does not exist, do not select branch
+ branchSelector.setSelectedIndex(-1);
+ }
+ }
+ }
+ });
+
+ branchChoices = new DefaultComboBoxModel();
+ branchSelector = new JComboBox(branchChoices);
+ branchSelector.setRenderer(new BranchRenderer());
+
+ searchTypeSelector = new JComboBox(Constants.SearchType.values());
+ searchTypeSelector.setSelectedItem(Constants.SearchType.COMMIT);
+
+ maxHitsSelector = new JComboBox(new Integer[] { 25, 50, 75, 100 });
+ maxHitsSelector.setSelectedIndex(0);
+
+ searchFragment = new JTextField(25);
+ searchFragment.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ search(0);
+ }
+ });
+
+ JPanel queryPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, Utils.MARGIN, 0));
+ queryPanel.add(new JLabel(Translation.get("gb.repository")));
+ queryPanel.add(repositorySelector);
+ queryPanel.add(new JLabel(Translation.get("gb.branch")));
+ queryPanel.add(branchSelector);
+ if (isSearch) {
+ queryPanel.add(new JLabel(Translation.get("gb.type")));
+ queryPanel.add(searchTypeSelector);
+ }
+ queryPanel.add(new JLabel(Translation.get("gb.maxHits")));
+ queryPanel.add(maxHitsSelector);
+
+ JPanel actionsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, Utils.MARGIN, 0));
+ actionsPanel.add(search);
+ actionsPanel.add(prev);
+ actionsPanel.add(next);
+
+ JPanel northControls = new JPanel(new BorderLayout(Utils.MARGIN, Utils.MARGIN));
+ northControls.add(queryPanel, BorderLayout.WEST);
+ if (isSearch) {
+ northControls.add(searchFragment, BorderLayout.CENTER);
+ }
+ northControls.add(actionsPanel, BorderLayout.EAST);
+
+ JPanel northPanel = new JPanel(new BorderLayout(0, Utils.MARGIN));
+ northPanel.add(header, BorderLayout.NORTH);
+ northPanel.add(northControls, BorderLayout.CENTER);
+
+ JPanel contentPanel = new JPanel() {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Insets getInsets() {
+ return Utils.INSETS;
+ }
+ };
+ contentPanel.setLayout(new BorderLayout(Utils.MARGIN, Utils.MARGIN));
+ contentPanel.add(northPanel, BorderLayout.NORTH);
+ contentPanel.add(new JScrollPane(table), BorderLayout.CENTER);
+ contentPanel.add(controls, BorderLayout.SOUTH);
+ setLayout(new BorderLayout());
+ add(contentPanel, BorderLayout.CENTER);
+ addWindowListener(new WindowAdapter() {
+ @Override
+ public void windowOpened(WindowEvent event) {
+ if (isSearch) {
+ searchFragment.requestFocus();
+ } else {
+ search(0);
+ }
+ }
+
+ @Override
+ public void windowActivated(WindowEvent event) {
+ if (isSearch) {
+ searchFragment.requestFocus();
+ }
+ }
+ });
+ }
+
+ public void selectRepository(RepositoryModel repository) {
+ repositorySelector.setSelectedItem(repository);
+ }
+
+ private void updateBranches() {
+ String repository = null;
+ if (repositorySelector.getSelectedIndex() > -1) {
+ repository = repositorySelector.getSelectedItem().toString();
+ }
+ List<String> branches = gitblit.getBranches(repository);
+ branchChoices.removeAllElements();
+ for (String branch : branches) {
+ branchChoices.addElement(branch);
+ }
+ }
+
+ protected void search(final int page) {
+ this.page = page;
+ final String repository = repositorySelector.getSelectedItem().toString();
+ final String branch = branchSelector.getSelectedIndex() > -1 ? branchSelector
+ .getSelectedItem().toString() : null;
+ final Constants.SearchType searchType = (Constants.SearchType) searchTypeSelector
+ .getSelectedItem();
+ final String fragment = isSearch ? searchFragment.getText() : null;
+ final int maxEntryCount = maxHitsSelector.getSelectedIndex() > -1 ? ((Integer) maxHitsSelector
+ .getSelectedItem()) : -1;
+
+ if (isSearch && StringUtils.isEmpty(fragment)) {
+ return;
+ }
+ setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+ SwingWorker<List<FeedEntryModel>, Void> worker = new SwingWorker<List<FeedEntryModel>, Void>() {
+ @Override
+ protected List<FeedEntryModel> doInBackground() throws IOException {
+ if (isSearch) {
+ return gitblit.search(repository, branch, fragment, searchType, maxEntryCount,
+ page);
+ } else {
+ return gitblit.log(repository, branch, maxEntryCount, page);
+ }
+ }
+
+ @Override
+ protected void done() {
+ setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
+ try {
+ List<FeedEntryModel> results = get();
+ if (isSearch) {
+ updateTable(true, fragment, results);
+ } else {
+ updateTable(true, branch == null ? "" : branch, results);
+ }
+ } catch (Throwable t) {
+ Utils.showException(SearchDialog.this, t);
+ }
+ }
+ };
+ worker.execute();
+ }
+
+ protected void updateTable(boolean pack, String text, List<FeedEntryModel> entries) {
+ tableModel.entries.clear();
+ tableModel.entries.addAll(entries);
+ tableModel.fireTableDataChanged();
+ setTitle(Translation.get(isSearch ? "gb.search" : "gb.log")
+ + (StringUtils.isEmpty(text) ? "" : (": " + text)) + " (" + entries.size()
+ + (page > 0 ? (", pg " + (page + 1)) : "") + ")");
+ header.setText(getTitle());
+ if (pack) {
+ Utils.packColumns(table, Utils.MARGIN);
+ }
+ table.scrollRectToVisible(new Rectangle(table.getCellRect(0, 0, true)));
+
+ // update pagination buttons
+ int maxHits = (Integer) maxHitsSelector.getSelectedItem();
+ next.setEnabled(entries.size() == maxHits);
+ prev.setEnabled(page > 0);
+ }
+
+ protected FeedEntryModel getSelectedSyndicatedEntry() {
+ int viewRow = table.getSelectedRow();
+ int modelRow = table.convertRowIndexToModel(viewRow);
+ FeedEntryModel entry = tableModel.get(modelRow);
+ return entry;
+ }
+
+ protected void viewCommit() {
+ FeedEntryModel entry = getSelectedSyndicatedEntry();
+ Utils.browse(entry.link);
+ }
+
+ protected void viewCommitDiff() {
+ FeedEntryModel entry = getSelectedSyndicatedEntry();
+ Utils.browse(entry.link.replace("/commit/", "/commitdiff/"));
+ }
+
+ protected void viewTree() {
+ FeedEntryModel entry = getSelectedSyndicatedEntry();
+ Utils.browse(entry.link.replace("/commit/", "/tree/"));
+ }
+}
diff --git a/src/main/java/com/gitblit/client/SettingCellRenderer.java b/src/main/java/com/gitblit/client/SettingCellRenderer.java
new file mode 100644
index 00000000..d164fb1a
--- /dev/null
+++ b/src/main/java/com/gitblit/client/SettingCellRenderer.java
@@ -0,0 +1,67 @@
+/*
+ * 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.Color;
+import java.awt.Component;
+import java.awt.Font;
+
+import javax.swing.JTable;
+import javax.swing.table.DefaultTableCellRenderer;
+
+import com.gitblit.models.SettingModel;
+
+/**
+ * SettingModel cell renderer that indicates if a setting is the default or
+ * modified.
+ *
+ * @author James Moger
+ *
+ */
+public class SettingCellRenderer extends DefaultTableCellRenderer {
+
+ private static final long serialVersionUID = 1L;
+
+ private final Font defaultFont;
+
+ private final Font modifiedFont;
+
+ public SettingCellRenderer() {
+ defaultFont = getFont();
+ modifiedFont = defaultFont.deriveFont(Font.BOLD);
+ }
+
+ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
+ boolean hasFocus, int row, int column) {
+ super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
+ if (value instanceof SettingModel) {
+ SettingModel setting = (SettingModel) value;
+ if (setting.isDefaultValue()) {
+ this.setFont(defaultFont);
+ if (!isSelected) {
+ this.setForeground(Color.BLACK);
+ }
+ } else {
+ this.setFont(modifiedFont);
+ if (!isSelected) {
+ this.setForeground(Color.BLUE);
+ }
+ }
+ this.setText(setting.getString(""));
+ }
+ return this;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/gitblit/client/SettingPanel.java b/src/main/java/com/gitblit/client/SettingPanel.java
new file mode 100644
index 00000000..6da09e18
--- /dev/null
+++ b/src/main/java/com/gitblit/client/SettingPanel.java
@@ -0,0 +1,120 @@
+/*
+ * 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.Font;
+import java.awt.GridLayout;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextArea;
+import javax.swing.SwingConstants;
+
+import com.gitblit.models.SettingModel;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * This panel displays the metadata for a particular setting.
+ *
+ * @author James Moger
+ */
+public class SettingPanel extends JPanel {
+
+ private static final long serialVersionUID = 1L;
+ private JTextArea descriptionArea;
+ private JLabel settingName;
+ private JLabel settingDefault;
+ private JLabel sinceVersion;
+ private JLabel directives;
+
+ public SettingPanel() {
+ super();
+ initialize();
+ }
+
+ public SettingPanel(SettingModel setting) {
+ this();
+ setSetting(setting);
+ }
+
+ private void initialize() {
+ descriptionArea = new JTextArea();
+ descriptionArea.setRows(6);
+ descriptionArea.setFont(new Font("monospaced", Font.PLAIN, 11));
+ descriptionArea.setEditable(false);
+
+ settingName = new JLabel(" ");
+ settingName.setFont(settingName.getFont().deriveFont(Font.BOLD));
+
+ settingDefault = new JLabel(" ");
+
+ sinceVersion = new JLabel(" ", SwingConstants.RIGHT);
+ sinceVersion.setForeground(new Color(0, 0x80, 0));
+
+ directives = new JLabel(" ", SwingConstants.RIGHT);
+ directives.setFont(directives.getFont().deriveFont(Font.ITALIC));
+
+ JPanel settingParameters = new JPanel(new GridLayout(2, 2, 0, 0));
+ settingParameters.add(settingName);
+ settingParameters.add(sinceVersion);
+ settingParameters.add(settingDefault, BorderLayout.CENTER);
+ settingParameters.add(directives);
+
+ JPanel settingPanel = new JPanel(new BorderLayout(5, 5));
+ settingPanel.add(settingParameters, BorderLayout.NORTH);
+ settingPanel.add(new JScrollPane(descriptionArea), BorderLayout.CENTER);
+ setLayout(new BorderLayout(0, 0));
+ add(settingPanel, BorderLayout.CENTER);
+ }
+
+ public void setSetting(SettingModel setting) {
+ settingName.setText(setting.name);
+ if (setting.since == null) {
+ sinceVersion.setText("custom");
+ } else {
+ sinceVersion.setText("since " + setting.since);
+ }
+ settingDefault.setText(Translation.get("gb.default") + ": " + setting.defaultValue);
+
+ List<String> values = new ArrayList<String>();
+ if (setting.caseSensitive) {
+ values.add("CASE-SENSITIVE");
+ }
+ if (setting.spaceDelimited) {
+ values.add("SPACE-DELIMITED");
+ }
+ if (setting.restartRequired) {
+ values.add("RESTART REQUIRED");
+ }
+ directives.setText(StringUtils.flattenStrings(values, ", "));
+
+ descriptionArea.setText(setting.description);
+ descriptionArea.setCaretPosition(0);
+ }
+
+ public void clear() {
+ settingName.setText(" ");
+ settingDefault.setText(" ");
+ sinceVersion.setText(" ");
+ directives.setText(" ");
+ descriptionArea.setText("");
+ }
+}
diff --git a/src/main/java/com/gitblit/client/SettingsPanel.java b/src/main/java/com/gitblit/client/SettingsPanel.java
new file mode 100644
index 00000000..b0adc0c7
--- /dev/null
+++ b/src/main/java/com/gitblit/client/SettingsPanel.java
@@ -0,0 +1,274 @@
+/*
+ * 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.GridLayout;
+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.HashMap;
+import java.util.Map;
+
+import javax.swing.ImageIcon;
+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.SettingModel;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Settings panel displays a list of server settings and their associated
+ * metadata. This panel also allows editing of a setting.
+ *
+ * @author James Moger
+ *
+ */
+public class SettingsPanel extends JPanel {
+
+ private static final long serialVersionUID = 1L;
+
+ private final GitblitClient gitblit;
+
+ private HeaderPanel header;
+
+ private JTable table;
+
+ private SettingsTableModel tableModel;
+
+ private TableRowSorter<SettingsTableModel> defaultSorter;
+
+ private JTextField filterTextfield;
+
+ public SettingsPanel(GitblitClient gitblit) {
+ super();
+ this.gitblit = gitblit;
+ initialize();
+ }
+
+ private void initialize() {
+ JButton refreshSettings = new JButton(Translation.get("gb.refresh"));
+ refreshSettings.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ refreshSettings();
+ }
+ });
+
+ final JButton editSetting = new JButton(Translation.get("gb.edit"));
+ editSetting.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ int viewRow = table.getSelectedRow();
+ int modelRow = table.convertRowIndexToModel(viewRow);
+ String key = tableModel.keys.get(modelRow);
+ SettingModel setting = tableModel.settings.get(key);
+ editSetting(setting);
+ }
+ });
+
+ NameRenderer nameRenderer = new NameRenderer();
+ final SettingPanel settingPanel = new SettingPanel();
+ tableModel = new SettingsTableModel();
+ defaultSorter = new TableRowSorter<SettingsTableModel>(tableModel);
+ table = Utils.newTable(tableModel, Utils.DATE_FORMAT);
+ table.setDefaultRenderer(SettingModel.class, new SettingCellRenderer());
+ String name = table.getColumnName(UsersTableModel.Columns.Name.ordinal());
+ table.getColumn(name).setCellRenderer(nameRenderer);
+ table.setRowSorter(defaultSorter);
+ table.getRowSorter().toggleSortOrder(SettingsTableModel.Columns.Name.ordinal());
+ table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ if (e.getValueIsAdjusting()) {
+ return;
+ }
+ boolean singleSelection = table.getSelectedRows().length == 1;
+ editSetting.setEnabled(singleSelection);
+ if (singleSelection) {
+ int viewRow = table.getSelectedRow();
+ int modelRow = table.convertRowIndexToModel(viewRow);
+ SettingModel setting = tableModel.get(modelRow);
+ settingPanel.setSetting(setting);
+ } else {
+ settingPanel.clear();
+ }
+ }
+ });
+ table.addMouseListener(new MouseAdapter() {
+ public void mouseClicked(MouseEvent e) {
+ if (e.getClickCount() == 2) {
+ int viewRow = table.getSelectedRow();
+ int modelRow = table.convertRowIndexToModel(viewRow);
+ SettingModel setting = tableModel.get(modelRow);
+ editSetting(setting);
+ }
+ }
+ });
+
+ filterTextfield = new JTextField();
+ filterTextfield.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ filterSettings(filterTextfield.getText());
+ }
+ });
+ filterTextfield.addKeyListener(new KeyAdapter() {
+ public void keyReleased(KeyEvent e) {
+ filterSettings(filterTextfield.getText());
+ }
+ });
+
+ JPanel settingFilterPanel = new JPanel(new BorderLayout(Utils.MARGIN, Utils.MARGIN));
+ settingFilterPanel.add(new JLabel(Translation.get("gb.filter")), BorderLayout.WEST);
+ settingFilterPanel.add(filterTextfield, BorderLayout.CENTER);
+
+ JPanel settingsTablePanel = new JPanel(new BorderLayout(Utils.MARGIN, Utils.MARGIN));
+ settingsTablePanel.add(settingFilterPanel, BorderLayout.NORTH);
+ settingsTablePanel.add(new JScrollPane(table), BorderLayout.CENTER);
+ settingsTablePanel.add(settingPanel, BorderLayout.SOUTH);
+
+ JPanel settingsControls = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 0));
+ settingsControls.add(refreshSettings);
+ settingsControls.add(editSetting);
+
+ setLayout(new BorderLayout(Utils.MARGIN, Utils.MARGIN));
+ header = new HeaderPanel(Translation.get("gb.settings"), "settings_16x16.png");
+ add(header, BorderLayout.NORTH);
+ add(settingsTablePanel, BorderLayout.CENTER);
+ add(settingsControls, BorderLayout.SOUTH);
+ }
+
+ @Override
+ public void requestFocus() {
+ filterTextfield.requestFocus();
+ }
+
+ @Override
+ public Insets getInsets() {
+ return Utils.INSETS;
+ }
+
+ protected void updateTable(boolean pack) {
+ tableModel.setSettings(gitblit.getSettings());
+ tableModel.fireTableDataChanged();
+ header.setText(Translation.get("gb.settings"));
+ if (pack) {
+ Utils.packColumns(table, Utils.MARGIN);
+ }
+ }
+
+ private void filterSettings(final String fragment) {
+ if (StringUtils.isEmpty(fragment)) {
+ table.setRowSorter(defaultSorter);
+ return;
+ }
+ RowFilter<SettingsTableModel, Object> containsFilter = new RowFilter<SettingsTableModel, Object>() {
+ public boolean include(Entry<? extends SettingsTableModel, ? extends Object> entry) {
+ for (int i = entry.getValueCount() - 1; i >= 0; i--) {
+ if (entry.getStringValue(i).toLowerCase().contains(fragment.toLowerCase())) {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+ TableRowSorter<SettingsTableModel> sorter = new TableRowSorter<SettingsTableModel>(
+ tableModel);
+ sorter.setRowFilter(containsFilter);
+ table.setRowSorter(sorter);
+ }
+
+ protected void refreshSettings() {
+ GitblitWorker worker = new GitblitWorker(SettingsPanel.this, RpcRequest.LIST_SETTINGS) {
+ @Override
+ protected Boolean doRequest() throws IOException {
+ gitblit.refreshSettings();
+ return true;
+ }
+
+ @Override
+ protected void onSuccess() {
+ updateTable(false);
+ }
+ };
+ worker.execute();
+ }
+
+ protected void editSetting(final SettingModel settingModel) {
+ final JTextField textField = new JTextField(settingModel.currentValue);
+ JPanel editPanel = new JPanel(new GridLayout(0, 1));
+ editPanel.add(new JLabel("New Value"));
+ editPanel.add(textField);
+
+ JPanel settingPanel = new JPanel(new BorderLayout());
+ settingPanel.add(new SettingPanel(settingModel), BorderLayout.CENTER);
+ settingPanel.add(editPanel, BorderLayout.SOUTH);
+ settingPanel.setPreferredSize(new Dimension(800, 200));
+
+ String[] options;
+ if (settingModel.currentValue.equals(settingModel.defaultValue)) {
+ options = new String[] { Translation.get("gb.cancel"), Translation.get("gb.save") };
+ } else {
+ options = new String[] { Translation.get("gb.cancel"),
+ Translation.get("gb.setDefault"), Translation.get("gb.save") };
+ }
+ String defaultOption = options[0];
+ int selection = JOptionPane.showOptionDialog(SettingsPanel.this, settingPanel,
+ settingModel.name, JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE,
+ new ImageIcon(getClass().getResource("/settings_16x16.png")), options,
+ defaultOption);
+ if (selection <= 0) {
+ return;
+ }
+ if (options[selection].equals(Translation.get("gb.setDefault"))) {
+ textField.setText(settingModel.defaultValue);
+ }
+ final Map<String, String> newSettings = new HashMap<String, String>();
+ newSettings.put(settingModel.name, textField.getText().trim());
+ GitblitWorker worker = new GitblitWorker(SettingsPanel.this, RpcRequest.EDIT_SETTINGS) {
+ @Override
+ protected Boolean doRequest() throws IOException {
+ boolean success = gitblit.updateSettings(newSettings);
+ if (success) {
+ gitblit.refreshSettings();
+ }
+ return success;
+ }
+
+ @Override
+ protected void onSuccess() {
+ updateTable(false);
+ }
+ };
+ worker.execute();
+ }
+}
diff --git a/src/main/java/com/gitblit/client/SettingsTableModel.java b/src/main/java/com/gitblit/client/SettingsTableModel.java
new file mode 100644
index 00000000..f14eae4b
--- /dev/null
+++ b/src/main/java/com/gitblit/client/SettingsTableModel.java
@@ -0,0 +1,124 @@
+/*
+ * 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.ServerSettings;
+import com.gitblit.models.SettingModel;
+
+/**
+ * Table model of Map<String, SettingModel>.
+ *
+ * @author James Moger
+ *
+ */
+public class SettingsTableModel extends AbstractTableModel {
+
+ private static final long serialVersionUID = 1L;
+
+ ServerSettings settings;
+
+ List<String> keys;
+
+ enum Columns {
+ Name, Value, Since;
+
+ @Override
+ public String toString() {
+ return name().replace('_', ' ');
+ }
+ }
+
+ public SettingsTableModel() {
+ this(null);
+ }
+
+ public SettingsTableModel(ServerSettings settings) {
+ setSettings(settings);
+ }
+
+ public void setSettings(ServerSettings settings) {
+ this.settings = settings;
+ if (settings == null) {
+ keys = new ArrayList<String>();
+ } else {
+ keys = new ArrayList<String>(settings.getKeys());
+ Collections.sort(keys);
+ }
+ }
+
+ @Override
+ public int getRowCount() {
+ return keys.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 Since:
+ return Translation.get("gb.since");
+ }
+ return "";
+ }
+
+ /**
+ * Returns <code>Object.class</code> regardless of <code>columnIndex</code>.
+ *
+ * @param columnIndex
+ * the column being queried
+ * @return the Object.class
+ */
+ public Class<?> getColumnClass(int columnIndex) {
+ if (Columns.Value.ordinal() == columnIndex) {
+ return SettingModel.class;
+ }
+ return String.class;
+ }
+
+ @Override
+ public Object getValueAt(int rowIndex, int columnIndex) {
+ String key = keys.get(rowIndex);
+ SettingModel setting = settings.get(key);
+ Columns col = Columns.values()[columnIndex];
+ switch (col) {
+ case Name:
+ return key;
+ case Value:
+ return setting;
+ case Since:
+ return setting.since;
+ }
+ return null;
+ }
+
+ public SettingModel get(int modelRow) {
+ String key = keys.get(modelRow);
+ return settings.get(key);
+ }
+}
diff --git a/src/main/java/com/gitblit/client/StatusPanel.java b/src/main/java/com/gitblit/client/StatusPanel.java
new file mode 100644
index 00000000..6d004f16
--- /dev/null
+++ b/src/main/java/com/gitblit/client/StatusPanel.java
@@ -0,0 +1,169 @@
+/*
+ * 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.io.IOException;
+
+import javax.swing.JButton;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+
+import com.gitblit.Constants;
+import com.gitblit.Constants.RpcRequest;
+import com.gitblit.models.ServerStatus;
+import com.gitblit.utils.ByteFormat;
+
+/**
+ * This panel displays the server status.
+ *
+ * @author James Moger
+ */
+public class StatusPanel extends JPanel {
+
+ private static final long serialVersionUID = 1L;
+ private final GitblitClient gitblit;
+ private JLabel bootDate;
+ private JLabel url;
+ private JLabel servletContainer;
+ private JLabel heapMaximum;
+ private JLabel heapAllocated;
+ private JLabel heapUsed;
+ private PropertiesTableModel tableModel;
+ private HeaderPanel header;
+ private JLabel version;
+ private JLabel releaseDate;
+
+ public StatusPanel(GitblitClient gitblit) {
+ super();
+ this.gitblit = gitblit;
+ initialize();
+ }
+
+ private void initialize() {
+ JButton refreshStatus = new JButton(Translation.get("gb.refresh"));
+ refreshStatus.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ refreshStatus();
+ }
+ });
+
+ version = new JLabel();
+ releaseDate = new JLabel();
+ bootDate = new JLabel();
+ url = new JLabel();
+ servletContainer = new JLabel();
+
+ heapMaximum = new JLabel();
+ heapAllocated = new JLabel();
+ heapUsed = new JLabel();
+
+ JPanel fieldsPanel = new JPanel(new GridLayout(0, 1, 0, Utils.MARGIN)) {
+
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public Insets getInsets() {
+ return Utils.INSETS;
+ }
+ };
+ fieldsPanel.add(createFieldPanel("gb.version", version));
+ fieldsPanel.add(createFieldPanel("gb.releaseDate", releaseDate));
+ fieldsPanel.add(createFieldPanel("gb.bootDate", bootDate));
+ fieldsPanel.add(createFieldPanel("gb.url", url));
+ fieldsPanel.add(createFieldPanel("gb.servletContainer", servletContainer));
+ fieldsPanel.add(createFieldPanel("gb.heapUsed", heapUsed));
+ fieldsPanel.add(createFieldPanel("gb.heapAllocated", heapAllocated));
+ fieldsPanel.add(createFieldPanel("gb.heapMaximum", heapMaximum));
+
+ tableModel = new PropertiesTableModel();
+ JTable propertiesTable = Utils.newTable(tableModel, Utils.DATE_FORMAT);
+ String name = propertiesTable.getColumnName(PropertiesTableModel.Columns.Name.ordinal());
+ NameRenderer nameRenderer = new NameRenderer();
+ propertiesTable.getColumn(name).setCellRenderer(nameRenderer);
+
+ JPanel centerPanel = new JPanel(new BorderLayout(Utils.MARGIN, Utils.MARGIN));
+ centerPanel.add(fieldsPanel, BorderLayout.NORTH);
+ centerPanel.add(new JScrollPane(propertiesTable), BorderLayout.CENTER);
+
+ JPanel controls = new JPanel(new FlowLayout(FlowLayout.CENTER, Utils.MARGIN, 0));
+ controls.add(refreshStatus);
+
+ header = new HeaderPanel(Translation.get("gb.status"), "health_16x16.png");
+ setLayout(new BorderLayout(Utils.MARGIN, Utils.MARGIN));
+ add(header, BorderLayout.NORTH);
+ add(centerPanel, BorderLayout.CENTER);
+ add(controls, BorderLayout.SOUTH);
+ }
+
+ private JPanel createFieldPanel(String key, JLabel valueLabel) {
+ JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT, Utils.MARGIN, 0));
+ JLabel textLabel = new JLabel(Translation.get(key));
+ textLabel.setFont(textLabel.getFont().deriveFont(Font.BOLD));
+ textLabel.setPreferredSize(new Dimension(120, 10));
+ panel.add(textLabel);
+ panel.add(valueLabel);
+ return panel;
+ }
+
+ @Override
+ public Insets getInsets() {
+ return Utils.INSETS;
+ }
+
+ protected void refreshStatus() {
+ GitblitWorker worker = new GitblitWorker(StatusPanel.this, RpcRequest.LIST_STATUS) {
+ @Override
+ protected Boolean doRequest() throws IOException {
+ gitblit.refreshStatus();
+ return true;
+ }
+
+ @Override
+ protected void onSuccess() {
+ updateTable(false);
+ }
+ };
+ worker.execute();
+ }
+
+ protected void updateTable(boolean pack) {
+ ServerStatus status = gitblit.getStatus();
+ header.setText(Translation.get("gb.status"));
+ version.setText(Constants.NAME + (status.isGO ? " GO v" : " WAR v") + status.version);
+ releaseDate.setText(status.releaseDate);
+ bootDate.setText(status.bootDate.toString() + " (" + Translation.getTimeUtils().timeAgo(status.bootDate)
+ + ")");
+ url.setText(gitblit.url);
+ servletContainer.setText(status.servletContainer);
+ ByteFormat byteFormat = new ByteFormat();
+ heapMaximum.setText(byteFormat.format(status.heapMaximum));
+ heapAllocated.setText(byteFormat.format(status.heapAllocated));
+ heapUsed.setText(byteFormat.format(status.heapAllocated - status.heapFree) + " ("
+ + byteFormat.format(status.heapFree) + " " + Translation.get("gb.free") + ")");
+ tableModel.setProperties(status.systemProperties);
+ tableModel.fireTableDataChanged();
+ }
+}
diff --git a/src/main/java/com/gitblit/client/SubscribedRepositoryRenderer.java b/src/main/java/com/gitblit/client/SubscribedRepositoryRenderer.java
new file mode 100644
index 00000000..9943333f
--- /dev/null
+++ b/src/main/java/com/gitblit/client/SubscribedRepositoryRenderer.java
@@ -0,0 +1,62 @@
+/*
+ * 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.Component;
+
+import javax.swing.ImageIcon;
+import javax.swing.JTable;
+
+import com.gitblit.models.RepositoryModel;
+
+/**
+ * Displays a subscribed icon on the left of the repository name, if there is at
+ * least one subscribed branch.
+ *
+ * @author James Moger
+ *
+ */
+public class SubscribedRepositoryRenderer extends NameRenderer {
+
+ private static final long serialVersionUID = 1L;
+
+ private final GitblitClient gitblit;
+
+ private final ImageIcon blankIcon;
+
+ private final ImageIcon subscribedIcon;
+
+ public SubscribedRepositoryRenderer(GitblitClient gitblit) {
+ super();
+ this.gitblit = gitblit;
+ blankIcon = new ImageIcon(getClass().getResource("/blank.png"));
+ subscribedIcon = new ImageIcon(getClass().getResource("/bullet_feed.png"));
+ }
+
+ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
+ boolean hasFocus, int row, int column) {
+ super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
+ if (value instanceof RepositoryModel) {
+ RepositoryModel model = (RepositoryModel) value;
+ if (gitblit.isSubscribed(model)) {
+ setIcon(subscribedIcon);
+ } else {
+ setIcon(blankIcon);
+ }
+ }
+ return this;
+ }
+}
diff --git a/src/main/java/com/gitblit/client/SubscriptionsDialog.java b/src/main/java/com/gitblit/client/SubscriptionsDialog.java
new file mode 100644
index 00000000..5ae836a5
--- /dev/null
+++ b/src/main/java/com/gitblit/client/SubscriptionsDialog.java
@@ -0,0 +1,158 @@
+/*
+ * 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.KeyEvent;
+import java.util.List;
+
+import javax.swing.ImageIcon;
+import javax.swing.JButton;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JPanel;
+import javax.swing.JRootPane;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.KeyStroke;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+
+import com.gitblit.models.FeedModel;
+
+/**
+ * Displays a list of repository branches and allows the user to check or
+ * uncheck branches.
+ *
+ * @author James Moger
+ *
+ */
+public abstract class SubscriptionsDialog extends JDialog {
+
+ private static final long serialVersionUID = 1L;
+
+ private final List<FeedModel> feeds;
+
+ private JTable feedsTable;
+
+ private FeedsTableModel model;
+
+ public SubscriptionsDialog(List<FeedModel> registrations) {
+ super();
+ this.feeds = registrations;
+ setTitle(Translation.get("gb.subscribe"));
+ setIconImage(new ImageIcon(getClass().getResource("/gitblt-favicon.png")).getImage());
+ initialize();
+ setSize(600, 400);
+ }
+
+ @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() {
+ NameRenderer nameRenderer = new NameRenderer();
+ model = new FeedsTableModel(feeds);
+ feedsTable = Utils.newTable(model, Utils.DATE_FORMAT);
+ feedsTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ if (e.getValueIsAdjusting()) {
+ return;
+ }
+ int viewRow = feedsTable.getSelectedRow();
+ if (viewRow == -1) {
+ return;
+ }
+ int modelRow = feedsTable.convertRowIndexToModel(viewRow);
+ FeedModel feed = model.get(modelRow);
+ feed.subscribed = !feed.subscribed;
+ model.fireTableDataChanged();
+ }
+ });
+
+ String repository = feedsTable.getColumnName(FeedsTableModel.Columns.Repository.ordinal());
+ feedsTable.getColumn(repository).setCellRenderer(nameRenderer);
+
+ String branch = feedsTable.getColumnName(FeedsTableModel.Columns.Branch.ordinal());
+ feedsTable.getColumn(branch).setCellRenderer(new BranchRenderer());
+
+ String subscribed = feedsTable.getColumnName(FeedsTableModel.Columns.Subscribed.ordinal());
+ feedsTable.getColumn(subscribed).setCellRenderer(new BooleanCellRenderer());
+ feedsTable.getColumn(subscribed).setMinWidth(30);
+ feedsTable.getColumn(subscribed).setMaxWidth(30);
+
+ Utils.packColumns(feedsTable, 5);
+
+ final JButton cancel = new JButton(Translation.get("gb.cancel"));
+ cancel.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ setVisible(false);
+ }
+ });
+
+ final JButton save = new JButton(Translation.get("gb.save"));
+ save.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent event) {
+ save();
+ }
+ });
+
+ feedsTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ if (e.getValueIsAdjusting()) {
+ return;
+ }
+ }
+ });
+
+ JPanel controls = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 0));
+ controls.add(cancel);
+ controls.add(save);
+
+ final Insets insets = new Insets(5, 5, 5, 5);
+ JPanel centerPanel = new JPanel(new BorderLayout(5, 5)) {
+
+ private static final long serialVersionUID = 1L;
+
+ public Insets getInsets() {
+ return insets;
+ }
+ };
+ centerPanel.add(new HeaderPanel(Translation.get("gb.subscribe") + "...", "feed_16x16.png"),
+ BorderLayout.NORTH);
+ centerPanel.add(new JScrollPane(feedsTable), BorderLayout.CENTER);
+ centerPanel.add(controls, BorderLayout.SOUTH);
+
+ getContentPane().setLayout(new BorderLayout(5, 5));
+ getContentPane().add(centerPanel, BorderLayout.CENTER);
+ }
+
+ public abstract void save();
+}
diff --git a/src/main/java/com/gitblit/client/TeamsPanel.java b/src/main/java/com/gitblit/client/TeamsPanel.java
new file mode 100644
index 00000000..92182222
--- /dev/null
+++ b/src/main/java/com/gitblit/client/TeamsPanel.java
@@ -0,0 +1,385 @@
+/*
+ * 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<TeamsTableModel> 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<TeamsTableModel>(tableModel);
+ table = Utils.newTable(tableModel, Utils.DATE_FORMAT);
+ String name = table.getColumnName(TeamsTableModel.Columns.Name.ordinal());
+ 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<TeamsTableModel, Object> containsFilter = new RowFilter<TeamsTableModel, Object>() {
+ public boolean include(Entry<? extends TeamsTableModel, ? extends Object> entry) {
+ for (int i = entry.getValueCount() - 1; i >= 0; i--) {
+ if (entry.getStringValue(i).toLowerCase().contains(fragment.toLowerCase())) {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+ TableRowSorter<TeamsTableModel> sorter = new TableRowSorter<TeamsTableModel>(tableModel);
+ sorter.setRowFilter(containsFilter);
+ table.setRowSorter(sorter);
+ }
+
+ private List<TeamModel> getSelectedTeams() {
+ List<TeamModel> teams = new ArrayList<TeamModel>();
+ 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.setPreReceiveScripts(gitblit.getPreReceiveScriptsUnused(null),
+ gitblit.getPreReceiveScriptsInherited(null), null);
+ dialog.setPostReceiveScripts(gitblit.getPostReceiveScriptsUnused(null),
+ gitblit.getPostReceiveScriptsInherited(null), 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(), team.getRepositoryPermissions());
+ dialog.setUsers(gitblit.getUsernames(), team.users == null ? null : new ArrayList<String>(
+ team.users));
+ dialog.setPreReceiveScripts(gitblit.getPreReceiveScriptsUnused(null),
+ gitblit.getPreReceiveScriptsInherited(null), team.preReceiveScripts);
+ dialog.setPostReceiveScripts(gitblit.getPostReceiveScriptsUnused(null),
+ gitblit.getPostReceiveScriptsInherited(null), team.postReceiveScripts);
+ 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<TeamModel> 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/main/java/com/gitblit/client/TeamsTableModel.java b/src/main/java/com/gitblit/client/TeamsTableModel.java
new file mode 100644
index 00000000..e6d8a945
--- /dev/null
+++ b/src/main/java/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<TeamModel> list;
+
+ enum Columns {
+ Name, Members, Repositories;
+
+ @Override
+ public String toString() {
+ return name().replace('_', ' ');
+ }
+ }
+
+ public TeamsTableModel() {
+ this(new ArrayList<TeamModel>());
+ }
+
+ public TeamsTableModel(List<TeamModel> 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 <code>Object.class</code> regardless of <code>columnIndex</code>.
+ *
+ * @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/main/java/com/gitblit/client/Translation.java b/src/main/java/com/gitblit/client/Translation.java
new file mode 100644
index 00000000..16ef20d4
--- /dev/null
+++ b/src/main/java/com/gitblit/client/Translation.java
@@ -0,0 +1,59 @@
+/*
+ * 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.MissingResourceException;
+import java.util.ResourceBundle;
+
+import com.gitblit.utils.TimeUtils;
+
+/**
+ * Loads the Gitblit language resource file.
+ *
+ * @author James Moger
+ *
+ */
+public class Translation {
+
+ private final static ResourceBundle translation;
+
+ private final static TimeUtils timeUtils;
+
+ static {
+ ResourceBundle bundle;
+ try {
+ // development location
+ bundle = ResourceBundle.getBundle("com/gitblit/wicket/GitBlitWebApp");
+ } catch (MissingResourceException e) {
+ // runtime location
+ bundle = ResourceBundle.getBundle("GitBlitWebApp");
+ }
+ translation = bundle;
+
+ timeUtils = new TimeUtils(translation);
+ }
+
+ public static String get(String key) {
+ if (translation.containsKey(key)) {
+ return translation.getString(key).trim();
+ }
+ return key;
+ }
+
+ public static TimeUtils getTimeUtils() {
+ return timeUtils;
+ }
+}
diff --git a/src/main/java/com/gitblit/client/UsersPanel.java b/src/main/java/com/gitblit/client/UsersPanel.java
new file mode 100644
index 00000000..c53a5791
--- /dev/null
+++ b/src/main/java/com/gitblit/client/UsersPanel.java
@@ -0,0 +1,385 @@
+/*
+ * 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.models.UserModel;
+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 UsersPanel extends JPanel {
+
+ private static final long serialVersionUID = 1L;
+
+ private final GitblitClient gitblit;
+
+ private HeaderPanel header;
+
+ private JTable table;
+
+ private UsersTableModel tableModel;
+
+ private TableRowSorter<UsersTableModel> defaultSorter;
+
+ private JTextField filterTextfield;
+
+ public UsersPanel(GitblitClient gitblit) {
+ super();
+ this.gitblit = gitblit;
+ initialize();
+ }
+
+ private void initialize() {
+ JButton refreshUsers = new JButton(Translation.get("gb.refresh"));
+ refreshUsers.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ refreshUsers();
+ }
+ });
+
+ JButton createUser = new JButton(Translation.get("gb.create"));
+ createUser.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ createUser();
+ }
+ });
+
+ final JButton editUser = new JButton(Translation.get("gb.edit"));
+ editUser.setEnabled(false);
+ editUser.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ editUser(getSelectedUsers().get(0));
+ }
+ });
+
+ final JButton delUser = new JButton(Translation.get("gb.delete"));
+ delUser.setEnabled(false);
+ delUser.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ deleteUsers(getSelectedUsers());
+ }
+ });
+
+ NameRenderer nameRenderer = new NameRenderer();
+ tableModel = new UsersTableModel();
+ defaultSorter = new TableRowSorter<UsersTableModel>(tableModel);
+ table = Utils.newTable(tableModel, Utils.DATE_FORMAT);
+ String name = table.getColumnName(UsersTableModel.Columns.Name.ordinal());
+ table.getColumn(name).setCellRenderer(nameRenderer);
+
+ int w = 130;
+ name = table.getColumnName(UsersTableModel.Columns.Type.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() {
+
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ if (e.getValueIsAdjusting()) {
+ return;
+ }
+ boolean selected = table.getSelectedRow() > -1;
+ boolean singleSelection = table.getSelectedRows().length == 1;
+ editUser.setEnabled(singleSelection && selected);
+ delUser.setEnabled(selected);
+ }
+ });
+
+ table.addMouseListener(new MouseAdapter() {
+ public void mouseClicked(MouseEvent e) {
+ if (e.getClickCount() == 2) {
+ editUser(getSelectedUsers().get(0));
+ }
+ }
+ });
+
+ filterTextfield = new JTextField();
+ filterTextfield.addActionListener(new ActionListener() {
+ public void actionPerformed(ActionEvent e) {
+ filterUsers(filterTextfield.getText());
+ }
+ });
+ filterTextfield.addKeyListener(new KeyAdapter() {
+ public void keyReleased(KeyEvent e) {
+ filterUsers(filterTextfield.getText());
+ }
+ });
+
+ JPanel userFilterPanel = new JPanel(new BorderLayout(Utils.MARGIN, Utils.MARGIN));
+ userFilterPanel.add(new JLabel(Translation.get("gb.filter")), BorderLayout.WEST);
+ userFilterPanel.add(filterTextfield, BorderLayout.CENTER);
+
+ JPanel userTablePanel = new JPanel(new BorderLayout(Utils.MARGIN, Utils.MARGIN));
+ userTablePanel.add(userFilterPanel, BorderLayout.NORTH);
+ userTablePanel.add(new JScrollPane(table), BorderLayout.CENTER);
+
+ JPanel userControls = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 0));
+ userControls.add(refreshUsers);
+ userControls.add(createUser);
+ userControls.add(editUser);
+ userControls.add(delUser);
+
+ setLayout(new BorderLayout(Utils.MARGIN, Utils.MARGIN));
+ header = new HeaderPanel(Translation.get("gb.users"), "user_16x16.png");
+ add(header, BorderLayout.NORTH);
+ add(userTablePanel, BorderLayout.CENTER);
+ add(userControls, BorderLayout.SOUTH);
+ }
+
+ @Override
+ public void requestFocus() {
+ filterTextfield.requestFocus();
+ }
+
+ @Override
+ public Insets getInsets() {
+ return Utils.INSETS;
+ }
+
+ protected abstract void updateTeamsTable();
+
+ protected void updateTable(boolean pack) {
+ tableModel.list.clear();
+ tableModel.list.addAll(gitblit.getUsers());
+ tableModel.fireTableDataChanged();
+ header.setText(Translation.get("gb.users") + " (" + gitblit.getUsers().size() + ")");
+ if (pack) {
+ Utils.packColumns(table, Utils.MARGIN);
+ }
+ }
+
+ private void filterUsers(final String fragment) {
+ if (StringUtils.isEmpty(fragment)) {
+ table.setRowSorter(defaultSorter);
+ return;
+ }
+ RowFilter<UsersTableModel, Object> containsFilter = new RowFilter<UsersTableModel, Object>() {
+ public boolean include(Entry<? extends UsersTableModel, ? extends Object> entry) {
+ for (int i = entry.getValueCount() - 1; i >= 0; i--) {
+ if (entry.getStringValue(i).toLowerCase().contains(fragment.toLowerCase())) {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+ TableRowSorter<UsersTableModel> sorter = new TableRowSorter<UsersTableModel>(tableModel);
+ sorter.setRowFilter(containsFilter);
+ table.setRowSorter(sorter);
+ }
+
+ private List<UserModel> getSelectedUsers() {
+ List<UserModel> users = new ArrayList<UserModel>();
+ for (int viewRow : table.getSelectedRows()) {
+ int modelRow = table.convertRowIndexToModel(viewRow);
+ UserModel model = tableModel.list.get(modelRow);
+ users.add(model);
+ }
+ return users;
+ }
+
+ protected void refreshUsers() {
+ GitblitWorker worker = new GitblitWorker(UsersPanel.this, RpcRequest.LIST_USERS) {
+ @Override
+ protected Boolean doRequest() throws IOException {
+ gitblit.refreshUsers();
+ return true;
+ }
+
+ @Override
+ protected void onSuccess() {
+ updateTable(false);
+ }
+ };
+ worker.execute();
+ }
+
+ /**
+ * Displays the create user dialog and fires a SwingWorker to update the
+ * server, if appropriate.
+ *
+ */
+ protected void createUser() {
+ 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) {
+ return;
+ }
+
+ GitblitWorker worker = new GitblitWorker(this, RpcRequest.CREATE_USER) {
+
+ @Override
+ protected Boolean doRequest() throws IOException {
+ boolean success = gitblit.createUser(newUser);
+ if (success) {
+ gitblit.refreshUsers();
+ if (newUser.teams.size() > 0) {
+ gitblit.refreshTeams();
+ }
+ }
+ return success;
+ }
+
+ @Override
+ protected void onSuccess() {
+ updateTable(false);
+ if (newUser.teams.size() > 0) {
+ updateTeamsTable();
+ }
+ }
+
+ @Override
+ protected void onFailure() {
+ showFailure("Failed to execute request \"{0}\" for user \"{1}\".",
+ getRequestType(), newUser.username);
+ }
+ };
+ 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(gitblit.getProtocolVersion(), user,
+ gitblit.getSettings());
+ dialog.setLocationRelativeTo(UsersPanel.this);
+ dialog.setUsers(gitblit.getUsers());
+ dialog.setRepositories(gitblit.getRepositories(), gitblit.getUserAccessPermissions(user));
+ dialog.setTeams(gitblit.getTeams(), user.teams == null ? null : new ArrayList<TeamModel>(
+ user.teams));
+ dialog.setVisible(true);
+ final UserModel revisedUser = dialog.getUser();
+ if (revisedUser == null) {
+ return;
+ }
+
+ GitblitWorker worker = new GitblitWorker(this, RpcRequest.EDIT_USER) {
+ @Override
+ protected Boolean doRequest() throws IOException {
+ boolean success = gitblit.updateUser(user.username, revisedUser);
+ if (success) {
+ gitblit.refreshUsers();
+ gitblit.refreshTeams();
+ }
+ return success;
+ }
+
+ @Override
+ protected void onSuccess() {
+ updateTable(false);
+ updateTeamsTable();
+ }
+
+ @Override
+ protected void onFailure() {
+ showFailure("Failed to execute request \"{0}\" for user \"{1}\".",
+ getRequestType(), user.username);
+ }
+ };
+ worker.execute();
+ }
+
+ protected void deleteUsers(final List<UserModel> 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(UsersPanel.this, message.toString(),
+ "Delete Users?", JOptionPane.YES_NO_OPTION);
+ if (result == JOptionPane.YES_OPTION) {
+ GitblitWorker worker = new GitblitWorker(this, RpcRequest.DELETE_USER) {
+ @Override
+ protected Boolean doRequest() throws IOException {
+ boolean success = true;
+ for (UserModel user : users) {
+ success &= gitblit.deleteUser(user);
+ }
+ if (success) {
+ gitblit.refreshUsers();
+ gitblit.refreshTeams();
+ }
+ return success;
+ }
+
+ @Override
+ protected void onSuccess() {
+ updateTable(false);
+ updateTeamsTable();
+ }
+
+ @Override
+ protected void onFailure() {
+ showFailure("Failed to delete specified users!");
+ }
+ };
+ worker.execute();
+ }
+ }
+}
diff --git a/src/main/java/com/gitblit/client/UsersTableModel.java b/src/main/java/com/gitblit/client/UsersTableModel.java
new file mode 100644
index 00000000..439d5afb
--- /dev/null
+++ b/src/main/java/com/gitblit/client/UsersTableModel.java
@@ -0,0 +1,125 @@
+/*
+ * 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.UserModel;
+
+/**
+ * Table model of a list of users.
+ *
+ * @author James Moger
+ *
+ */
+public class UsersTableModel extends AbstractTableModel {
+
+ private static final long serialVersionUID = 1L;
+
+ List<UserModel> list;
+
+ enum Columns {
+ Name, Display_Name, Type, Teams, Repositories;
+
+ @Override
+ public String toString() {
+ return name().replace('_', ' ');
+ }
+ }
+
+ public UsersTableModel() {
+ this(new ArrayList<UserModel>());
+ }
+
+ public UsersTableModel(List<UserModel> users) {
+ this.list = users;
+ 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 Display_Name:
+ return Translation.get("gb.displayName");
+ case Type:
+ return Translation.get("gb.type");
+ case Teams:
+ return Translation.get("gb.teamMemberships");
+ case Repositories:
+ return Translation.get("gb.repositories");
+ }
+ return "";
+ }
+
+ /**
+ * Returns <code>Object.class</code> regardless of <code>columnIndex</code>.
+ *
+ * @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) {
+ UserModel model = list.get(rowIndex);
+ Columns col = Columns.values()[columnIndex];
+ switch (col) {
+ case Name:
+ return model.username;
+ case Display_Name:
+ return model.displayName;
+ case Type:
+ StringBuilder sb = new StringBuilder();
+ if (model.accountType != null) {
+ sb.append(model.accountType.name());
+ }
+ if (model.canAdmin()) {
+ if (sb.length() > 0) {
+ sb.append(", ");
+ }
+ sb.append("admin");
+ }
+ return sb.toString();
+ case Teams:
+ return (model.teams == null || model.teams.size() == 0) ? "" : String
+ .valueOf(model.teams.size());
+ case Repositories:
+ return (model.permissions == null || model.permissions.size() == 0) ? "" : String
+ .valueOf(model.permissions.size());
+ }
+ return null;
+ }
+}
diff --git a/src/main/java/com/gitblit/client/Utils.java b/src/main/java/com/gitblit/client/Utils.java
new file mode 100644
index 00000000..1e6ab2bf
--- /dev/null
+++ b/src/main/java/com/gitblit/client/Utils.java
@@ -0,0 +1,173 @@
+/*
+ * 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.Color;
+import java.awt.Component;
+import java.awt.Desktop;
+import java.awt.Dimension;
+import java.awt.Font;
+import java.awt.Insets;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.net.URI;
+import java.text.MessageFormat;
+import java.util.Date;
+
+import javax.swing.JOptionPane;
+import javax.swing.JScrollPane;
+import javax.swing.JTable;
+import javax.swing.JTextArea;
+import javax.swing.table.DefaultTableColumnModel;
+import javax.swing.table.TableCellRenderer;
+import javax.swing.table.TableColumn;
+import javax.swing.table.TableModel;
+
+import com.gitblit.Constants.RpcRequest;
+
+public class Utils {
+
+ public final static int MARGIN = 5;
+
+ public final static Insets INSETS = new Insets(MARGIN, MARGIN, MARGIN, MARGIN);
+
+ public final static String TIMESTAMP_FORMAT = "yyyy-MM-dd HH:mm";
+
+ public final static String DATE_FORMAT = "yyyy-MM-dd";
+
+ public static JTable newTable(TableModel model, String datePattern) {
+ return newTable(model, datePattern, null);
+ }
+
+ public static JTable newTable(TableModel model, String datePattern, final RowRenderer rowRenderer) {
+ JTable table;
+ if (rowRenderer == null) {
+ table = new JTable(model);
+ } else {
+ table = new JTable(model) {
+
+ @Override
+ public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
+ Component c = super.prepareRenderer(renderer, row, column);
+ boolean isSelected = isCellSelected(row, column);
+ rowRenderer.prepareRow(c, isSelected, row, column);
+ return c;
+ }
+ };
+ }
+ table.setRowHeight(table.getFont().getSize() + 8);
+ table.setCellSelectionEnabled(false);
+ table.setRowSelectionAllowed(true);
+ table.getTableHeader().setReorderingAllowed(false);
+ table.setGridColor(new Color(0xd9d9d9));
+ table.setBackground(Color.white);
+ table.setDefaultRenderer(Date.class,
+ new DateCellRenderer(datePattern, Color.orange.darker()));
+ return table;
+ }
+
+ public static void explainNotAllowed(Component c, RpcRequest request) {
+ String msg = MessageFormat.format("The Gitblit server does not allow the request \"{0}\".",
+ request.name());
+ JOptionPane.showMessageDialog(c, msg, "Not Allowed", JOptionPane.ERROR_MESSAGE);
+ }
+
+ public static void explainForbidden(Component c, RpcRequest request) {
+ String msg = MessageFormat.format(
+ "The request \"{0}\" has been forbidden for the account by the Gitblit server.",
+ request.name());
+ JOptionPane.showMessageDialog(c, msg, "Forbidden", JOptionPane.ERROR_MESSAGE);
+ }
+
+ public static void explainUnauthorized(Component c, RpcRequest request) {
+ String msg = MessageFormat.format(
+ "This account is not authorized to execute the request \"{0}\".", request.name());
+ JOptionPane.showMessageDialog(c, msg, "Unauthorized", JOptionPane.ERROR_MESSAGE);
+ }
+
+ public static void explainUnknown(Component c, RpcRequest request) {
+ String msg = MessageFormat.format(
+ "The request \"{0}\" is not recognized by the Gitblit server.", request.name());
+ JOptionPane.showMessageDialog(c, msg, "Unknown Request", JOptionPane.ERROR_MESSAGE);
+ }
+
+ public static void showException(Component c, Throwable t) {
+ StringWriter writer = new StringWriter();
+ t.printStackTrace(new PrintWriter(writer));
+ String stacktrace = writer.toString();
+ try {
+ writer.close();
+ } catch (Throwable x) {
+ }
+ JTextArea textArea = new JTextArea(stacktrace);
+ textArea.setFont(new Font("monospaced", Font.PLAIN, 11));
+ JScrollPane jsp = new JScrollPane(textArea);
+ jsp.setPreferredSize(new Dimension(800, 400));
+ JOptionPane.showMessageDialog(c, jsp, Translation.get("gb.error"),
+ JOptionPane.ERROR_MESSAGE);
+ }
+
+ public static void packColumns(JTable table, int margin) {
+ for (int c = 0; c < table.getColumnCount(); c++) {
+ packColumn(table, c, 4);
+ }
+ }
+
+ // Sets the preferred width of the visible column specified by vColIndex.
+ // The column will be just wide enough to show the column head and the
+ // widest cell in the column. margin pixels are added to the left and right
+ // (resulting in an additional width of 2*margin pixels).
+ private static void packColumn(JTable table, int vColIndex, int margin) {
+ DefaultTableColumnModel colModel = (DefaultTableColumnModel) table.getColumnModel();
+ TableColumn col = colModel.getColumn(vColIndex);
+ int width = 0;
+
+ // Get width of column header
+ TableCellRenderer renderer = col.getHeaderRenderer();
+ if (renderer == null) {
+ renderer = table.getTableHeader().getDefaultRenderer();
+ }
+ Component comp = renderer.getTableCellRendererComponent(table, col.getHeaderValue(), false,
+ false, 0, 0);
+ width = comp.getPreferredSize().width;
+
+ // Get maximum width of column data
+ for (int r = 0; r < table.getRowCount(); r++) {
+ renderer = table.getCellRenderer(r, vColIndex);
+ comp = renderer.getTableCellRendererComponent(table, table.getValueAt(r, vColIndex),
+ false, false, r, vColIndex);
+ width = Math.max(width, comp.getPreferredSize().width);
+ }
+
+ // Add margin
+ width += 2 * margin;
+
+ // Set the width
+ col.setPreferredWidth(width);
+ }
+
+ public static void browse(String url) {
+ try {
+ Desktop.getDesktop().browse(new URI(url));
+ } catch (Exception x) {
+ showException(null, x);
+ }
+ }
+
+ public static abstract class RowRenderer {
+ public abstract void prepareRow(Component c, boolean isSelected, int row, int column);
+ }
+}
diff --git a/src/main/java/com/gitblit/client/splash.png b/src/main/java/com/gitblit/client/splash.png
new file mode 100644
index 00000000..d63932fb
--- /dev/null
+++ b/src/main/java/com/gitblit/client/splash.png
Binary files differ