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" /> @@ -412,6 +416,53 @@ <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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> <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Build the Gitblit Website ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> @@ -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> 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. # 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 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 src/com/gitblit/RpcFilter.java
@@ -57,16 +57,17 @@ 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); @@ -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) { src/com/gitblit/build/Build.java
@@ -49,6 +49,10 @@ */ 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); src/com/gitblit/client/ClosableTabComponent.java
New file @@ -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()); } } } 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; } 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); setSize(950, 600); } 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 void setupMenu() { MenuBar menuBar = new MenuBar(); setMenuBar(menuBar); Menu serversMenu = new Menu("Servers"); 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); src/com/gitblit/client/GitblitClientLauncher.java
New file @@ -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(); } } } 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) { } } src/com/gitblit/client/GitblitRegistration.java
New file @@ -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; } } } src/com/gitblit/client/NameRenderer.java
New file @@ -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; } } 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: src/com/gitblit/client/TypeRenderer.java
New file @@ -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; } } src/com/gitblit/client/splash.png