From 841651baee2181c1543555d1eabcd0e4fee48827 Mon Sep 17 00:00:00 2001 From: James Moger <james.moger@gitblit.com> Date: Wed, 05 Oct 2011 22:22:43 -0400 Subject: [PATCH] New setting to disable RPC administration. Advancing the RPC client. --- src/com/gitblit/client/GitblitClient.java | 141 +++++- src/com/gitblit/client/GitblitPanel.java | 378 ++++++++++++++++++ src/com/gitblit/client/DateCellRenderer.java | 19 src/com/gitblit/client/splash.png | 0 distrib/gitblit.properties | 12 src/com/gitblit/build/Build.java | 19 src/com/gitblit/client/GitblitRegistration.java | 47 ++ src/com/gitblit/client/TypeRenderer.java | 119 +++++ src/com/gitblit/client/GitblitClientLauncher.java | 105 +++++ src/com/gitblit/RpcFilter.java | 22 docs/04_releases.mkd | 11 src/com/gitblit/client/ClosableTabComponent.java | 149 +++++++ src/com/gitblit/client/NameRenderer.java | 65 +++ build.xml | 68 +++ src/com/gitblit/client/RepositoriesModel.java | 31 + docs/00_index.mkd | 12 16 files changed, 1,133 insertions(+), 65 deletions(-) diff --git a/build.xml b/build.xml index 9a58971..fa40abd 100644 --- a/build.xml +++ b/build.xml @@ -85,6 +85,7 @@ <property name="distribution.zipfile" value="gitblit-${gb.version}.zip" /> <property name="distribution.warfile" value="gitblit-${gb.version}.war" /> <property name="fedclient.zipfile" value="fedclient-${gb.version}.zip" /> + <property name="rpcclient.zipfile" value="rpcclient-${gb.version}.zip" /> </target> @@ -265,6 +266,9 @@ <arg value="%FEDCLIENT%=${fedclient.zipfile}" /> <arg value="--substitute" /> + <arg value="%RPCCLIENT%=${rpcclient.zipfile}" /> + + <arg value="--substitute" /> <arg value="%BUILDDATE%=${gb.versionDate}" /> <arg value="--substitute" /> @@ -409,6 +413,53 @@ </zip> </target> + + <!-- + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + Build the stand-alone, Gitblit RPC Client + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + --> + <target name="buildRpcClient" depends="compile" description="Builds the stand-alone Gitblit RPC client"> + <echo>Building Gitblit RPC Client ${gb.version}</echo> + + <genjar jarfile="rpcclient.jar"> + <resource file="${basedir}/src/com/gitblit/client/splash.png" /> + <resource file="${basedir}/resources/gitblt-favicon.png" /> + <resource file="${basedir}/resources/lock_go_16x16.png" /> + <resource file="${basedir}/resources/lock_pull_16x16.png" /> + <resource file="${basedir}/resources/shield_16x16.png" /> + <resource file="${basedir}/resources/federated_16x16.png" /> + <resource file="${basedir}/resources/cold_16x16.png" /> + <resource file="${basedir}/resources/book_16x16.png" /> + <resource file="${basedir}/resources/bug_16x16.png" /> + <resource file="${basedir}/resources/blank.png" /> + + <class name="com.gitblit.client.GitblitClientLauncher" /> + <classfilter> + <exclude name="org.apache." /> + <exclude name="org.bouncycastle." /> + <exclude name="org.eclipse." /> + <exclude name="org.slf4j." /> + <exclude name="com.beust." /> + <exclude name="com.google." /> + </classfilter> + <classpath refid="master-classpath" /> + <manifest> + <attribute name="Main-Class" value="com.gitblit.client.GitblitClientLauncher" /> + <attribute name="SplashScreen-Image" value="splash.png" /> + <attribute name="Specification-Version" value="${gb.version}" /> + <attribute name="Release-Date" value="${gb.versionDate}" /> + </manifest> + </genjar> + + <!-- Build the rpc client zip file --> + <zip destfile="${rpcclient.zipfile}"> + <fileset dir="${basedir}"> + <include name="rpcclient.jar" /> + </fileset> + </zip> + </target> + <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -525,6 +576,9 @@ <arg value="%FEDCLIENT%=${fedclient.zipfile}" /> <arg value="--substitute" /> + <arg value="%RPCCLIENT%=${rpcclient.zipfile}" /> + + <arg value="--substitute" /> <arg value="%BUILDDATE%=${gb.versionDate}" /> <arg value="--substitute" /> @@ -554,7 +608,7 @@ Compile from source, publish binaries, and build & deploy site ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> - <target name="buildAll" depends="buildGO,buildWAR,buildFederationClient,buildSite"> + <target name="buildAll" depends="buildGO,buildWAR,buildFederationClient,buildRpcClient,buildSite"> <!-- Cleanup --> <delete dir="${project.build.dir}" /> <delete dir="${project.war.dir}" /> @@ -567,7 +621,7 @@ Publish binaries to Google Code ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> - <target name="publishBinaries" depends="buildGO,buildWAR,buildFederationClient" description="Publish the Gitblit binaries to Google Code"> + <target name="publishBinaries" depends="buildGO,buildWAR,buildFederationClient,buildRpcClient" description="Publish the Gitblit binaries to Google Code"> <echo>Uploading Gitblit ${gb.version} binaries</echo> @@ -600,6 +654,16 @@ targetfilename="fedclient-${gb.version}.zip" summary="Gitblit Federation Client v${gb.version} (command-line tool to clone data from federated Gitblit instances)" labels="Featured, Type-Package, OpSys-All" /> + + <!-- Upload RpcClient --> + <gcupload + username="${googlecode.user}" + password="${googlecode.password}" + projectname="gitblit" + filename="${rpcclient.zipfile}" + targetfilename="rpcclient-${gb.version}.zip" + summary="Gitblit RPC Client v${gb.version} (Swing tool to clone repositories and remotely administer a Gitblit server)" + labels="Featured, Type-Package, OpSys-All" /> </target> diff --git a/distrib/gitblit.properties b/distrib/gitblit.properties index 3de1475..14ec79f 100644 --- a/distrib/gitblit.properties +++ b/distrib/gitblit.properties @@ -87,11 +87,17 @@ # SINCE 0.5.0 web.allowAdministration = true -# Allows remote clients to list repositories and administer the Gitblit instance -# if they have administrator permissions. +# Allows remote clients to list repositories and possibly administer the Gitblit +# server, if the authenticated account has administrator permissions. # # SINCE 0.6.1 -web.enableRpcServlet = false +web.enableRpcServlet = true + +# Allows remote clients to administer the Gitblit instance, if the authenticated +# account has administrator permissions. Requires *web.enableRpcServlet=true*. +# +# SINCE 0.6.1 +web.enableRpcAdministration = false # Allow dynamic zip downloads. # diff --git a/docs/00_index.mkd b/docs/00_index.mkd index ee54b18..489f84a 100644 --- a/docs/00_index.mkd +++ b/docs/00_index.mkd @@ -17,6 +17,7 @@ ### Tools <ul class='noBullets'> +<li>*Gitblit RPC Client* - a Java Swing tool to clone repositories and remotely administer a Gitblit server <li>*Gitblit Federation Client* - a command line tool to clone/pull groups of repositories and optionally users and settings </ul> @@ -26,11 +27,16 @@ ### Current Release -**%VERSION%** ([go](http://code.google.com/p/gitblit/downloads/detail?name=%GO%)|[war](http://code.google.com/p/gitblit/downloads/detail?name=%WAR%)|[fedclient](http://code.google.com/p/gitblit/downloads/detail?name=%FEDCLIENT%)) based on [%JGIT%][jgit] *released %BUILDDATE%* +**%VERSION%** ([go](http://code.google.com/p/gitblit/downloads/detail?name=%GO%)|[war](http://code.google.com/p/gitblit/downloads/detail?name=%WAR%)|[fedclient](http://code.google.com/p/gitblit/downloads/detail?name=%FEDCLIENT%)|[rpcclient](http://code.google.com/p/gitblit/downloads/detail?name=%RPCCLIENT%)) based on [%JGIT%][jgit] *released %BUILDDATE%* -- fixed/broke: federation protocol. serialized dates now include the timezone. This breaks 0.6.0 clients/servers. -- improved: updated ui with Twitter's Bootstrap CSS toolkit +- improved: overhauled web ui with Twitter's Bootstrap CSS toolkit <br/>**New:** *web.loginMessage = gitblit* +- added: authenticated JSON RPC mechanism +<br/>**New:** *web.enableRpcServlet = true* +<br/>**New:** *web.enableRpcAdministration = false* +- added: reusable JSON RPC client class +- added: Swing RPC Client application for cloning and administration of repositories, users, and federation proposals. +- fixed/broke: federation protocol. dates are now serialized to the [iso8601](http://en.wikipedia.org/wiki/ISO_8601) standard. This breaks 0.6.0 federation clients/servers. - fixed: Null pointer exception if did not set federation strategy (issue 20) - fixed: Gitblit GO allows SSL renegotiation if running on Java 1.6.0_22 or later - added: IUserService.setup(IStoredSettings) for custom user service implementations diff --git a/docs/04_releases.mkd b/docs/04_releases.mkd index 68a0193..43168dd 100644 --- a/docs/04_releases.mkd +++ b/docs/04_releases.mkd @@ -1,11 +1,16 @@ ## Release History ### Current Release -**%VERSION%** ([go](http://code.google.com/p/gitblit/downloads/detail?name=%GO%)|[war](http://code.google.com/p/gitblit/downloads/detail?name=%WAR%)|[fedclient](http://code.google.com/p/gitblit/downloads/detail?name=%FEDCLIENT%)) based on [%JGIT%][jgit] *released %BUILDDATE%* +**%VERSION%** ([go](http://code.google.com/p/gitblit/downloads/detail?name=%GO%)|[war](http://code.google.com/p/gitblit/downloads/detail?name=%WAR%)|[fedclient](http://code.google.com/p/gitblit/downloads/detail?name=%FEDCLIENT%)|[rpcclient](http://code.google.com/p/gitblit/downloads/detail?name=%RPCCLIENT%)) based on [%JGIT%][jgit] *released %BUILDDATE%* -- fixed/broke: federation protocol. serialized dates now include the timezone. This breaks 0.6.0 clients/servers. -- improved: updated ui with Twitter's Bootstrap CSS toolkit +- improved: overhauled web ui with Twitter's Bootstrap CSS toolkit <br/>**New:** *web.loginMessage = gitblit* +- added: authenticated JSON RPC mechanism +<br/>**New:** *web.enableRpcServlet = true* +<br/>**New:** *web.enableRpcAdministration = false* +- added: reusable JSON RPC client class +- added: Swing RPC Client application for cloning and administration of repositories, users, and federation proposals. +- fixed/broke: federation protocol. dates are now serialized to the [iso8601](http://en.wikipedia.org/wiki/ISO_8601) standard. This breaks 0.6.0 federation clients/servers. - fixed: Null pointer exception if did not set federation strategy (issue 20) - fixed: Gitblit GO allows SSL renegotiation if running on Java 1.6.0_22 or later - added: IUserService.setup(IStoredSettings) for custom user service implementations diff --git a/src/com/gitblit/RpcFilter.java b/src/com/gitblit/RpcFilter.java index 49df844..f92dd96 100644 --- a/src/com/gitblit/RpcFilter.java +++ b/src/com/gitblit/RpcFilter.java @@ -57,20 +57,21 @@ HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; - if (!GitBlit.getBoolean(Keys.web.enableRpcServlet, false)) { - logger.warn(Keys.web.enableRpcServlet + " must be set TRUE for rpc requests."); - httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN); - return; - } - String fullUrl = getFullUrl(httpRequest); RpcRequest requestType = RpcRequest.fromName(httpRequest.getParameter("req")); boolean adminRequest = requestType.exceeds(RpcRequest.LIST_REPOSITORIES); + // conditionally reject all rpc requests + if (!GitBlit.getBoolean(Keys.web.enableRpcServlet, true)) { + logger.warn(Keys.web.enableRpcServlet + " must be set TRUE for rpc requests."); + httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } + boolean authenticateView = GitBlit.getBoolean(Keys.web.authenticateViewPages, false); boolean authenticateAdmin = GitBlit.getBoolean(Keys.web.authenticateAdminPages, true); - + // Wrap the HttpServletRequest with the RpcServletnRequest which // overrides the servlet container user principal methods. AuthenticatedRequest authenticatedRequest = new AuthenticatedRequest(httpRequest); @@ -79,6 +80,13 @@ authenticatedRequest.setUser(user); } + // conditionally reject rpc administration requests + if (adminRequest && !GitBlit.getBoolean(Keys.web.enableRpcAdministration, false)) { + logger.warn(Keys.web.enableRpcAdministration + " must be set TRUE for administrative rpc requests."); + httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN); + return; + } + // BASIC authentication challenge and response processing if ((adminRequest && authenticateAdmin) || (!adminRequest && authenticateView)) { if (user == null) { diff --git a/src/com/gitblit/build/Build.java b/src/com/gitblit/build/Build.java index 684f278..862c295 100644 --- a/src/com/gitblit/build/Build.java +++ b/src/com/gitblit/build/Build.java @@ -48,7 +48,11 @@ * */ public class Build { - + + public interface DownloadListener { + public void downloading(String name); + } + /** * BuildType enumeration representing compile-time or runtime. This is used * to download dependencies either for Gitblit GO runtime or for setting up @@ -57,6 +61,8 @@ public static enum BuildType { RUNTIME, COMPILETIME; } + + private static DownloadListener downloadListener; public static void main(String... args) { runtime(); @@ -123,6 +129,14 @@ downloadFromApache(MavenObject.SLF4JAPI, BuildType.RUNTIME); downloadFromApache(MavenObject.SLF4LOG4J, BuildType.RUNTIME); downloadFromApache(MavenObject.LOG4J, BuildType.RUNTIME); + downloadFromApache(MavenObject.GSON, BuildType.RUNTIME); + downloadFromApache(MavenObject.JSCH, BuildType.RUNTIME); + + downloadFromEclipse(MavenObject.JGIT, BuildType.RUNTIME); + } + + public static void rpcClient(DownloadListener listener) { + downloadListener = listener; downloadFromApache(MavenObject.GSON, BuildType.RUNTIME); downloadFromApache(MavenObject.JSCH, BuildType.RUNTIME); @@ -273,6 +287,9 @@ throw new RuntimeException("Failed to create destination folder structure!"); } } + if (downloadListener != null) { + downloadListener.downloading(mo.name); + } ByteArrayOutputStream buff = new ByteArrayOutputStream(); try { URL url = new URL(mavenURL); diff --git a/src/com/gitblit/client/ClosableTabComponent.java b/src/com/gitblit/client/ClosableTabComponent.java new file mode 100644 index 0000000..a121806 --- /dev/null +++ b/src/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/com/gitblit/client/DateCellRenderer.java b/src/com/gitblit/client/DateCellRenderer.java index 591926b..053cf52 100644 --- a/src/com/gitblit/client/DateCellRenderer.java +++ b/src/com/gitblit/client/DateCellRenderer.java @@ -15,29 +15,44 @@ */ 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; +import com.gitblit.utils.TimeUtils; + +/** + * 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) { + 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 timeAgo = TimeUtils.timeAgo(date); String strDate = new SimpleDateFormat(pattern).format((Date) value); - this.setText(strDate); + this.setText(timeAgo); + this.setToolTipText(strDate); } return this; } diff --git a/src/com/gitblit/client/GitblitClient.java b/src/com/gitblit/client/GitblitClient.java index d10cede..51d8e7e 100644 --- a/src/com/gitblit/client/GitblitClient.java +++ b/src/com/gitblit/client/GitblitClient.java @@ -16,84 +16,179 @@ package com.gitblit.client; import java.awt.BorderLayout; +import java.awt.Dimension; import java.awt.EventQueue; -import java.awt.Menu; -import java.awt.MenuBar; -import java.awt.MenuItem; -import java.awt.MenuShortcut; +import java.awt.GridLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import javax.swing.ImageIcon; import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JMenu; +import javax.swing.JMenuBar; +import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; +import javax.swing.JPasswordField; import javax.swing.JTabbedPane; +import javax.swing.JTextField; +import javax.swing.KeyStroke; +import javax.swing.UIManager; import com.gitblit.Constants; import com.gitblit.utils.StringUtils; +/** + * Sample RPC application. + * + * @author James Moger + * + */ public class GitblitClient extends JFrame { private static final long serialVersionUID = 1L; private JTabbedPane serverTabs; + private GitblitRegistration localhost = new GitblitRegistration("default", + "https://localhost:8443", "admin", "admin".toCharArray()); + + private List<GitblitRegistration> registrations = new ArrayList<GitblitRegistration>(); + private JMenu recentMenu; private GitblitClient() { super(); } private void initialize() { - setupMenu(); setContentPane(getCenterPanel()); + setIconImage(new ImageIcon(getClass().getResource("/gitblt-favicon.png")).getImage()); - setTitle("Gitblit Client v" + Constants.VERSION + " (" + Constants.VERSION_DATE + ")"); + setTitle("Gitblit RPC Client v" + Constants.VERSION + " (" + Constants.VERSION_DATE + ")"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - setSize(800, 600); - setLocationRelativeTo(null); + setSize(950, 600); } - private void setupMenu() { - MenuBar menuBar = new MenuBar(); - setMenuBar(menuBar); - Menu serversMenu = new Menu("Servers"); + public void setVisible(boolean value) { + if (value) { + if (registrations.size() == 0) { + // default prompt + if (loginPrompt(localhost)) { + pack(); + } + } else if (registrations.size() == 1) { + // single registration prompt + if (loginPrompt(registrations.get(0))) { + pack(); + } + } + super.setVisible(value); + setLocationRelativeTo(null); + } + } + + private JMenuBar setupMenu() { + JMenuBar menuBar = new JMenuBar(); + JMenu serversMenu = new JMenu("Servers"); menuBar.add(serversMenu); - MenuItem login = new MenuItem("Login...", new MenuShortcut(KeyEvent.VK_L, false)); + recentMenu = new JMenu("Recent"); + serversMenu.add(recentMenu); + JMenuItem login = new JMenuItem("Login..."); + login.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_L, KeyEvent.CTRL_DOWN_MASK, false)); login.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent event) { - String url = JOptionPane.showInputDialog(GitblitClient.this, - "Please enter Gitblit server URL", "https://localhost:8443"); - if (StringUtils.isEmpty(url)) { - return; - } - login(url, "admin", "admin".toCharArray()); + loginPrompt(localhost); } }); serversMenu.add(login); + return menuBar; + } + + private JPanel newLabelPanel(String text, JTextField field) { + JLabel label = new JLabel(text); + 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 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 login(String url, String account, char[] password) { + private boolean loginPrompt(GitblitRegistration reg) { + JTextField urlField = new JTextField(reg.url, 30); + JTextField nameField = new JTextField(reg.name); + JTextField accountField = new JTextField(reg.account); + JPasswordField passwordField = new JPasswordField(new String(reg.password)); + + JPanel panel = new JPanel(new GridLayout(0, 1, 5, 5)); + panel.add(newLabelPanel("name", nameField)); + panel.add(newLabelPanel("url", urlField)); + panel.add(newLabelPanel("account", accountField)); + panel.add(newLabelPanel("password", passwordField)); + + int result = JOptionPane.showConfirmDialog(GitblitClient.this, panel, "Login", + JOptionPane.OK_CANCEL_OPTION); + if (result != JOptionPane.OK_OPTION) { + return false; + } + String url = urlField.getText(); + if (StringUtils.isEmpty(url)) { + return false; + } + reg = new GitblitRegistration(nameField.getText(), url, accountField.getText(), + passwordField.getPassword()); + login(reg); + registrations.add(0, reg); + rebuildRecentMenu(); + return true; + } + + private void login(GitblitRegistration reg) { try { - GitblitPanel panel = new GitblitPanel(url, account, password); + GitblitPanel panel = new GitblitPanel(reg); panel.login(); - serverTabs.addTab(url.substring(url.indexOf("//") + 2), panel); - serverTabs.setSelectedIndex(serverTabs.getTabCount() - 1); + serverTabs.addTab(reg.name, panel); + int idx = serverTabs.getTabCount() - 1; + serverTabs.setSelectedIndex(idx); + serverTabs.setTabComponentAt(idx, new ClosableTabComponent(reg.name, null, serverTabs, + panel)); } catch (IOException e) { JOptionPane.showMessageDialog(GitblitClient.this, e.getMessage(), "Error", JOptionPane.ERROR_MESSAGE); } } + private void rebuildRecentMenu() { + recentMenu.removeAll(); + for (final GitblitRegistration reg : registrations) { + JMenuItem item = new JMenuItem(reg.name); + item.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + login(reg); + } + }); + recentMenu.add(item); + } + } + public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (Exception e) { + } GitblitClient frame = new GitblitClient(); frame.initialize(); frame.setVisible(true); diff --git a/src/com/gitblit/client/GitblitClientLauncher.java b/src/com/gitblit/client/GitblitClientLauncher.java new file mode 100644 index 0000000..19e9efd --- /dev/null +++ b/src/com/gitblit/client/GitblitClientLauncher.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.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.IOException; +import java.util.Collections; +import java.util.List; + +import com.gitblit.Launcher; +import com.gitblit.build.Build; +import com.gitblit.build.Build.DownloadListener; + +/** + * Downloads dependencies and launches RPC client. + * + * @author James Moger + * + */ +public class GitblitClientLauncher { + + public static void main(String[] args) { + final SplashScreen splash = SplashScreen.getSplashScreen(); + + DownloadListener downloadListener = new DownloadListener() { + @Override + public void downloading(String name) { + updateSplash(splash, "Downloading " + name + "..."); + } + }; + + // download rpc client runtime dependencies + Build.rpcClient(downloadListener); + + updateSplash(splash, "Scanning Library Folder..."); + File libFolder = new File("ext"); + List<File> jars = Launcher.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, "Loading " + jar.getName() + "..."); + Launcher.addJarFile(jar); + } catch (IOException e) { + + } + } + + updateSplash(splash, "Starting Gitblit RPC Client..."); + GitblitClient.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(); + 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); + g.dispose(); + splash.update(); + } + } + }); + } catch (Throwable t) { + t.printStackTrace(); + } + } +} diff --git a/src/com/gitblit/client/GitblitPanel.java b/src/com/gitblit/client/GitblitPanel.java index 911ec0c..5482593 100644 --- a/src/com/gitblit/client/GitblitPanel.java +++ b/src/com/gitblit/client/GitblitPanel.java @@ -16,59 +16,398 @@ package com.gitblit.client; import java.awt.BorderLayout; +import java.awt.Color; import java.awt.Component; +import java.awt.Desktop; +import java.awt.Dimension; +import java.awt.Insets; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; import java.io.IOException; +import java.net.URI; +import java.text.MessageFormat; +import java.util.ArrayList; import java.util.Date; +import java.util.List; import java.util.Map; +import javax.swing.JButton; +import javax.swing.JLabel; +import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; 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.DefaultTableColumnModel; import javax.swing.table.TableCellRenderer; import javax.swing.table.TableColumn; +import javax.swing.table.TableRowSorter; +import com.gitblit.GitBlitException.ForbiddenException; +import com.gitblit.client.ClosableTabComponent.CloseTabListener; +import com.gitblit.models.FederationModel; import com.gitblit.models.RepositoryModel; +import com.gitblit.models.UserModel; import com.gitblit.utils.RpcUtils; +import com.gitblit.utils.StringUtils; -public class GitblitPanel extends JPanel { +/** + * GitblitPanel performs the login, all business logic, and contains all widgets + * to represent the state of a repository for the given account credentials. + * + * @author James Moger + * + */ +public class GitblitPanel extends JPanel implements CloseTabListener { private static final long serialVersionUID = 1L; - String url; - String account; - char[] password; + private final int margin = 5; - JTabbedPane tabs; + private final Insets insets = new Insets(margin, margin, margin, margin); + + private String url; + + private String account; + + private char[] password; + + private boolean isAdmin; + + private JTabbedPane tabs; private JTable repositoriesTable; + + private RepositoriesModel repositoriesModel; + + private JList usersList; + + private JPanel usersPanel; + + private JButton createRepository; + + private JButton delRepository; + + private NameRenderer nameRenderer; + + private TypeRenderer typeRenderer; + + private DefaultTableCellRenderer ownerRenderer; + + private DefaultTableCellRenderer sizeRenderer; + + private TableRowSorter<RepositoriesModel> defaultSorter; + + public GitblitPanel(GitblitRegistration reg) { + this(reg.url, reg.account, reg.password); + } public GitblitPanel(String url, String account, char[] password) { this.url = url; this.account = account; this.password = password; - tabs = new JTabbedPane(JTabbedPane.TOP); - repositoriesTable = new JTable(); - repositoriesTable.setDefaultRenderer(Date.class, new DateCellRenderer(null)); + final JButton browseRepository = new JButton("Browse"); + browseRepository.setEnabled(false); + browseRepository.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + RepositoryModel model = getSelectedRepositories().get(0); + String u = MessageFormat.format("{0}/summary/{1}", GitblitPanel.this.url, + StringUtils.encodeURL(model.name)); + try { + Desktop.getDesktop().browse(new URI(u)); + } catch (Exception x) { + x.printStackTrace(); + } + } + }); - tabs.addTab("Repositories", new JScrollPane(repositoriesTable)); + createRepository = new JButton("Create"); + createRepository.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + System.out.println("TODO Create Repository"); + } + }); + + final JButton editRepository = new JButton("Edit"); + editRepository.setEnabled(false); + editRepository.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + for (RepositoryModel model : getSelectedRepositories()) { + System.out.println("TODO Edit " + model); + } + } + }); + + delRepository = new JButton("Delete"); + delRepository.setEnabled(false); + delRepository.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + for (RepositoryModel model : getSelectedRepositories()) { + System.out.println("TODO Delete " + model); + } + } + }); + + final JButton cloneRepository = new JButton("Clone"); + cloneRepository.setEnabled(false); + cloneRepository.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + for (RepositoryModel model : getSelectedRepositories()) { + System.out.println("TODO Clone " + model); + } + } + }); + + nameRenderer = new NameRenderer(Color.gray, new Color(0x00, 0x69, 0xD6)); + typeRenderer = new TypeRenderer(); + + sizeRenderer = new DefaultTableCellRenderer(); + sizeRenderer.setHorizontalAlignment(SwingConstants.RIGHT); + sizeRenderer.setForeground(new Color(0, 0x80, 0)); + + ownerRenderer = new DefaultTableCellRenderer(); + ownerRenderer.setForeground(Color.gray); + ownerRenderer.setHorizontalAlignment(SwingConstants.CENTER); + + repositoriesModel = new RepositoriesModel(); + defaultSorter = new TableRowSorter<RepositoriesModel>(repositoriesModel); + repositoriesTable = new JTable(repositoriesModel); + repositoriesTable.setRowSorter(defaultSorter); + repositoriesTable.getRowSorter().toggleSortOrder(RepositoriesModel.Columns.Name.ordinal()); + + repositoriesTable.setCellSelectionEnabled(false); + repositoriesTable.setRowSelectionAllowed(true); + repositoriesTable.setRowHeight(nameRenderer.getFont().getSize() + 8); + repositoriesTable.getTableHeader().setReorderingAllowed(false); + repositoriesTable.setGridColor(new Color(0xd9d9d9)); + repositoriesTable.setBackground(Color.white); + repositoriesTable.setDefaultRenderer(Date.class, + new DateCellRenderer(null, Color.orange.darker())); + setRenderer(RepositoriesModel.Columns.Name, nameRenderer); + setRenderer(RepositoriesModel.Columns.Type, typeRenderer); + setRenderer(RepositoriesModel.Columns.Owner, ownerRenderer); + setRenderer(RepositoriesModel.Columns.Size, sizeRenderer); + + repositoriesTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() { + @Override + public void valueChanged(ListSelectionEvent e) { + if (e.getValueIsAdjusting()) { + return; + } + boolean singleSelection = repositoriesTable.getSelectedRowCount() == 1; + boolean selected = repositoriesTable.getSelectedRow() > -1; + browseRepository.setEnabled(singleSelection); + delRepository.setEnabled(selected); + cloneRepository.setEnabled(selected); + if (selected) { + int viewRow = repositoriesTable.getSelectedRow(); + int modelRow = repositoriesTable.convertRowIndexToModel(viewRow); + RepositoryModel model = ((RepositoriesModel) repositoriesTable.getModel()).list + .get(modelRow); + editRepository.setEnabled(singleSelection + && (isAdmin || model.owner.equalsIgnoreCase(GitblitPanel.this.account))); + } else { + editRepository.setEnabled(false); + } + } + }); + + final JTextField repositoryFilter = new JTextField(); + repositoryFilter.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + filterRepositories(repositoryFilter.getText()); + } + }); + + JPanel filterPanel = new JPanel(new BorderLayout(margin, margin)); + filterPanel.add(new JLabel("Filter"), BorderLayout.WEST); + filterPanel.add(repositoryFilter, BorderLayout.CENTER); + + JPanel tablePanel = new JPanel(new BorderLayout(margin, margin)); + tablePanel.add(filterPanel, BorderLayout.NORTH); + tablePanel.add(new JScrollPane(repositoriesTable), BorderLayout.CENTER); + + JPanel repositoryControls = new JPanel(); + repositoryControls.add(browseRepository); + repositoryControls.add(cloneRepository); + repositoryControls.add(createRepository); + repositoryControls.add(editRepository); + repositoryControls.add(delRepository); + + JPanel repositoriesPanel = new JPanel(new BorderLayout(margin, margin)); + repositoriesPanel.add(newHeaderLabel("Repositories"), BorderLayout.NORTH); + repositoriesPanel.add(tablePanel, BorderLayout.CENTER); + repositoriesPanel.add(repositoryControls, BorderLayout.SOUTH); + + JButton createUser = new JButton("Create"); + createUser.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + System.out.println("TODO Create User"); + } + }); + + final JButton editUser = new JButton("Edit"); + editUser.setEnabled(false); + editUser.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + for (UserModel user : getSelectedUsers()) { + System.out.println("TODO Edit " + user); + } + } + }); + + final JButton delUser = new JButton("Delete"); + delUser.setEnabled(false); + delUser.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + for (UserModel user : getSelectedUsers()) { + System.out.println("TODO Delete " + user); + } + } + }); + + usersList = new JList(); + usersList.addListSelectionListener(new ListSelectionListener() { + + @Override + public void valueChanged(ListSelectionEvent e) { + if (e.getValueIsAdjusting()) { + return; + } + boolean selected = usersList.getSelectedIndex() > -1; + boolean singleSelection = usersList.getSelectedIndices().length == 1; + editUser.setEnabled(singleSelection && selected); + delUser.setEnabled(selected); + } + }); + + JPanel userControls = new JPanel(); + userControls.add(createUser); + userControls.add(editUser); + userControls.add(delUser); + + usersPanel = new JPanel(new BorderLayout(margin, margin)); + usersPanel.add(newHeaderLabel("Users"), BorderLayout.NORTH); + usersPanel.add(new JScrollPane(usersList), BorderLayout.CENTER); + usersPanel.add(userControls, BorderLayout.SOUTH); + + /* + * Assemble the main panel + */ + JPanel mainPanel = new JPanel(new BorderLayout(margin, margin)); + mainPanel.add(repositoriesPanel, BorderLayout.CENTER); + mainPanel.add(usersPanel, BorderLayout.EAST); + + tabs = new JTabbedPane(JTabbedPane.BOTTOM); + tabs.addTab("Main", mainPanel); + tabs.addTab("Federation", new JPanel()); setLayout(new BorderLayout()); add(tabs, BorderLayout.CENTER); } + private JLabel newHeaderLabel(String text) { + JLabel label = new JLabel(text); + label.setOpaque(true); + label.setForeground(Color.white); + label.setBackground(Color.gray); + label.setFont(label.getFont().deriveFont(14f)); + return label; + } + public void login() throws IOException { refreshRepositoriesTable(); + + try { + refreshUsersTable(); + isAdmin = true; + refreshFederationPanel(); + } catch (ForbiddenException e) { + // user does not have administrator privileges + // hide admin repository buttons + createRepository.setVisible(false); + delRepository.setVisible(false); + + // hide users panel + usersPanel.setVisible(false); + + // remove federation tab + tabs.removeTabAt(1); + } catch (IOException e) { + System.err.println(e.getMessage()); + } } private void refreshRepositoriesTable() throws IOException { Map<String, RepositoryModel> repositories = RpcUtils .getRepositories(url, account, password); - repositoriesTable.setModel(new RepositoriesModel(repositories)); - + repositoriesModel.list.clear(); + repositoriesModel.list.addAll(repositories.values()); + repositoriesModel.fireTableDataChanged(); packColumns(repositoriesTable, 2); + } + + private void setRenderer(RepositoriesModel.Columns col, TableCellRenderer renderer) { + String name = repositoriesTable.getColumnName(col.ordinal()); + repositoriesTable.getColumn(name).setCellRenderer(renderer); + } + + private void refreshUsersTable() throws IOException { + List<UserModel> users = RpcUtils.getUsers(url, account, password); + usersList.setListData(users.toArray()); + } + + private void refreshFederationPanel() throws IOException { + List<FederationModel> registrations = RpcUtils.getFederationRegistrations(url, account, + password); + } + + private void filterRepositories(final String fragment) { + if (StringUtils.isEmpty(fragment)) { + repositoriesTable.setRowSorter(defaultSorter); + return; + } + RowFilter<RepositoriesModel, Object> containsFilter = new RowFilter<RepositoriesModel, Object>() { + public boolean include(Entry<? extends RepositoriesModel, ? extends Object> entry) { + for (int i = entry.getValueCount() - 1; i >= 0; i--) { + if (entry.getStringValue(i).toLowerCase().contains(fragment.toLowerCase())) { + return true; + } + } + return false; + } + }; + RepositoriesModel model = (RepositoriesModel) repositoriesTable.getModel(); + TableRowSorter<RepositoriesModel> sorter = new TableRowSorter<RepositoriesModel>(model); + sorter.setRowFilter(containsFilter); + repositoriesTable.setRowSorter(sorter); + } + + private List<RepositoryModel> getSelectedRepositories() { + List<RepositoryModel> repositories = new ArrayList<RepositoryModel>(); + for (int viewRow : repositoriesTable.getSelectedRows()) { + int modelRow = repositoriesTable.convertRowIndexToModel(viewRow); + RepositoryModel model = ((RepositoriesModel) repositoriesTable.getModel()).list + .get(modelRow); + repositories.add(model); + } + return repositories; + } + + private List<UserModel> getSelectedUsers() { + List<UserModel> users = new ArrayList<UserModel>(); + for (int viewRow : usersList.getSelectedIndices()) { + UserModel model = (UserModel) usersList.getModel().getElementAt(viewRow); + users.add(model); + } + return users; } private void packColumns(JTable table, int margin) { @@ -109,4 +448,21 @@ // Set the width col.setPreferredWidth(width); } + + @Override + public Insets getInsets() { + return insets; + } + + @Override + public Dimension getPreferredSize() { + if (isAdmin) { + return new Dimension(950, 550); + } + return new Dimension(775, 450); + } + + @Override + public void closeTab(Component c) { + } } diff --git a/src/com/gitblit/client/GitblitRegistration.java b/src/com/gitblit/client/GitblitRegistration.java new file mode 100644 index 0000000..482bf8f --- /dev/null +++ b/src/com/gitblit/client/GitblitRegistration.java @@ -0,0 +1,47 @@ +/* + * 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 com.gitblit.utils.StringUtils; + +/** + * Simple class to encapsulate a Gitblit server registration. + * + * @author James Moger + * + */ +public class GitblitRegistration implements Serializable { + + private static final long serialVersionUID = 1L; + + String name; + String url; + String account; + char[] password; + + public GitblitRegistration(String name, String url, String account, char[] password) { + this.url = url; + this.account = account; + this.password = password; + if (StringUtils.isEmpty(name)) { + this.name = url.substring(url.indexOf("//") + 2); + } else { + this.name = name; + } + } +} diff --git a/src/com/gitblit/client/NameRenderer.java b/src/com/gitblit/client/NameRenderer.java new file mode 100644 index 0000000..41393fb --- /dev/null +++ b/src/com/gitblit/client/NameRenderer.java @@ -0,0 +1,65 @@ +/* + * 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.JTable; +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 { + + private static final long serialVersionUID = 1L; + + final String groupSpan; + + public 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); + 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); + } + return this; + } +} \ No newline at end of file diff --git a/src/com/gitblit/client/RepositoriesModel.java b/src/com/gitblit/client/RepositoriesModel.java index 2a439fb..d8e448f 100644 --- a/src/com/gitblit/client/RepositoriesModel.java +++ b/src/com/gitblit/client/RepositoriesModel.java @@ -19,22 +19,25 @@ import java.util.Collections; import java.util.Date; import java.util.List; -import java.util.Map; import javax.swing.table.AbstractTableModel; import com.gitblit.models.RepositoryModel; +/** + * Table model of a list of repositories. + * + * @author James Moger + * + */ public class RepositoriesModel extends AbstractTableModel { private static final long serialVersionUID = 1L; - Map<String, RepositoryModel> repositories; - List<RepositoryModel> list; enum Columns { - Name, Description, Owner, Last_Change, Size; + Name, Description, Owner, Type, Last_Change, Size; @Override public String toString() { @@ -42,15 +45,18 @@ } } - public RepositoriesModel(Map<String, RepositoryModel> repositories) { - this.repositories = repositories; - list = new ArrayList<RepositoryModel>(repositories.values()); - Collections.sort(list); + public RepositoriesModel() { + this(new ArrayList<RepositoryModel>()); + } + + public RepositoriesModel(List<RepositoryModel> repositories) { + this.list = repositories; + Collections.sort(this.list); } @Override public int getRowCount() { - return repositories.size(); + return list.size(); } @Override @@ -74,6 +80,9 @@ public Class<?> getColumnClass(int columnIndex) { Columns col = Columns.values()[columnIndex]; switch (col) { + case Name: + case Type: + return RepositoryModel.class; case Last_Change: return Date.class; } @@ -86,11 +95,13 @@ Columns col = Columns.values()[columnIndex]; switch (col) { case Name: - return model.name; + return model; case Description: return model.description; case Owner: return model.owner; + case Type: + return model; case Last_Change: return model.lastChange; case Size: diff --git a/src/com/gitblit/client/TypeRenderer.java b/src/com/gitblit/client/TypeRenderer.java new file mode 100644 index 0000000..8f92dcf --- /dev/null +++ b/src/com/gitblit/client/TypeRenderer.java @@ -0,0 +1,119 @@ +/* + * 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.GridLayout; +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 TypeRenderer 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; + + public TypeRenderer() { + super(new GridLayout(1, 0, 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")); + } + + @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) { + RepositoryModel model = (RepositoryModel) value; + if (model.useTickets) { + add(new JLabel(tixIcon)); + } else { + add(new JLabel(blankIcon)); + } + if (model.useDocs) { + add(new JLabel(doxIcon)); + } else { + add(new JLabel(blankIcon)); + } + if (model.isFrozen) { + add(new JLabel(frozenIcon)); + } else { + add(new JLabel(blankIcon)); + } + if (model.isFederated) { + add(new JLabel(federatedIcon)); + } else { + add(new JLabel(blankIcon)); + } + + switch (model.accessRestriction) { + case NONE: + add(new JLabel(blankIcon)); + break; + case PUSH: + add(new JLabel(pushIcon)); + break; + case CLONE: + add(new JLabel(pullIcon)); + break; + case VIEW: + add(new JLabel(viewIcon)); + break; + default: + add(new JLabel(blankIcon)); + } + } + return this; + } +} \ No newline at end of file diff --git a/src/com/gitblit/client/splash.png b/src/com/gitblit/client/splash.png new file mode 100644 index 0000000..d63932f --- /dev/null +++ b/src/com/gitblit/client/splash.png Binary files differ -- Gitblit v1.9.1