From 841651baee2181c1543555d1eabcd0e4fee48827 Mon Sep 17 00:00:00 2001 From: James Moger Date: Wed, 5 Oct 2011 22:22:43 -0400 Subject: [PATCH] New setting to disable RPC administration. Advancing the RPC client. --- build.xml | 68 +++- distrib/gitblit.properties | 12 +- docs/00_index.mkd | 12 +- docs/04_releases.mkd | 11 +- src/com/gitblit/RpcFilter.java | 22 +- src/com/gitblit/build/Build.java | 19 +- .../gitblit/client/ClosableTabComponent.java | 149 +++++++ src/com/gitblit/client/DateCellRenderer.java | 19 +- src/com/gitblit/client/GitblitClient.java | 141 +++++-- .../gitblit/client/GitblitClientLauncher.java | 105 +++++ src/com/gitblit/client/GitblitPanel.java | 378 +++++++++++++++++- .../gitblit/client/GitblitRegistration.java | 47 +++ src/com/gitblit/client/NameRenderer.java | 65 +++ src/com/gitblit/client/RepositoriesModel.java | 31 +- src/com/gitblit/client/TypeRenderer.java | 119 ++++++ src/com/gitblit/client/splash.png | Bin 0 -> 11593 bytes 16 files changed, 1133 insertions(+), 65 deletions(-) create mode 100644 src/com/gitblit/client/ClosableTabComponent.java create mode 100644 src/com/gitblit/client/GitblitClientLauncher.java create mode 100644 src/com/gitblit/client/GitblitRegistration.java create mode 100644 src/com/gitblit/client/NameRenderer.java create mode 100644 src/com/gitblit/client/TypeRenderer.java create mode 100644 src/com/gitblit/client/splash.png diff --git a/build.xml b/build.xml index 9a589711..fa40abd0 100644 --- a/build.xml +++ b/build.xml @@ -85,6 +85,7 @@ + @@ -264,6 +265,9 @@ + + + @@ -409,6 +413,53 @@ + + + + Building Gitblit RPC Client ${gb.version} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -567,7 +621,7 @@ Publish binaries to Google Code ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ --> - + Uploading Gitblit ${gb.version} binaries @@ -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" /> + + + diff --git a/distrib/gitblit.properties b/distrib/gitblit.properties index 3de14751..14ec79f2 100644 --- a/distrib/gitblit.properties +++ b/distrib/gitblit.properties @@ -87,11 +87,17 @@ web.siteName = # 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 ee54b18f..489f84a1 100644 --- a/docs/00_index.mkd +++ b/docs/00_index.mkd @@ -17,6 +17,7 @@ Gitblit is available in two variations: ### Tools
    +
  • *Gitblit RPC Client* - a Java Swing tool to clone repositories and remotely administer a Gitblit server
  • *Gitblit Federation Client* - a command line tool to clone/pull groups of repositories and optionally users and settings
@@ -26,11 +27,16 @@ Gitblit requires a Java 6 Runtime Environment (JRE) or a Java 6 Development Kit ### 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
**New:** *web.loginMessage = gitblit* +- added: authenticated JSON RPC mechanism +
**New:** *web.enableRpcServlet = true* +
**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 68a01936..43168dd2 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
**New:** *web.loginMessage = gitblit* +- added: authenticated JSON RPC mechanism +
**New:** *web.enableRpcServlet = true* +
**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 49df8444..f92dd962 100644 --- a/src/com/gitblit/RpcFilter.java +++ b/src/com/gitblit/RpcFilter.java @@ -57,20 +57,21 @@ public class RpcFilter extends AuthenticationFilter { 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 @@ public class RpcFilter extends AuthenticationFilter { 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 684f2787..862c295d 100644 --- a/src/com/gitblit/build/Build.java +++ b/src/com/gitblit/build/Build.java @@ -48,7 +48,11 @@ import com.gitblit.utils.StringUtils; * */ 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 class Build { public static enum BuildType { RUNTIME, COMPILETIME; } + + private static DownloadListener downloadListener; public static void main(String... args) { runtime(); @@ -128,6 +134,14 @@ public class Build { downloadFromEclipse(MavenObject.JGIT, BuildType.RUNTIME); } + + public static void rpcClient(DownloadListener listener) { + downloadListener = listener; + downloadFromApache(MavenObject.GSON, BuildType.RUNTIME); + downloadFromApache(MavenObject.JSCH, BuildType.RUNTIME); + + downloadFromEclipse(MavenObject.JGIT, BuildType.RUNTIME); + } /** * Builds the Keys class based on the gitblit.properties file and inserts @@ -273,6 +287,9 @@ public class Build { 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 00000000..a121806a --- /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 591926b7..053cf52f 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 d10cedeb..51d8e7eb 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 registrations = new ArrayList(); + 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 00000000..19e9efd8 --- /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 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 911ec0c6..5482593d 100644 --- a/src/com/gitblit/client/GitblitPanel.java +++ b/src/com/gitblit/client/GitblitPanel.java @@ -16,61 +16,400 @@ 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; + + private final Insets insets = new Insets(margin, margin, margin, margin); + + private String url; + + private String account; + + private char[] password; - JTabbedPane tabs; + 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 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(); + } + } + }); + + 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); + 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); + } + } + }); - tabs.addTab("Repositories", new JScrollPane(repositoriesTable)); + 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 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 users = RpcUtils.getUsers(url, account, password); + usersList.setListData(users.toArray()); + } + + private void refreshFederationPanel() throws IOException { + List registrations = RpcUtils.getFederationRegistrations(url, account, + password); + } + + private void filterRepositories(final String fragment) { + if (StringUtils.isEmpty(fragment)) { + repositoriesTable.setRowSorter(defaultSorter); + return; + } + RowFilter containsFilter = new RowFilter() { + public boolean include(Entry entry) { + for (int i = entry.getValueCount() - 1; i >= 0; i--) { + if (entry.getStringValue(i).toLowerCase().contains(fragment.toLowerCase())) { + return true; + } + } + return false; + } + }; + RepositoriesModel model = (RepositoriesModel) repositoriesTable.getModel(); + TableRowSorter sorter = new TableRowSorter(model); + sorter.setRowFilter(containsFilter); + repositoriesTable.setRowSorter(sorter); + } + + private List getSelectedRepositories() { + List repositories = new ArrayList(); + 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 getSelectedUsers() { + List users = new ArrayList(); + for (int viewRow : usersList.getSelectedIndices()) { + UserModel model = (UserModel) usersList.getModel().getElementAt(viewRow); + users.add(model); + } + return users; + } + private void packColumns(JTable table, int margin) { for (int c = 0; c < table.getColumnCount(); c++) { packColumn(table, c, 4); @@ -109,4 +448,21 @@ public class GitblitPanel extends JPanel { // 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 00000000..482bf8f2 --- /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 00000000..41393fb5 --- /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 = ""; + 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("" + groupSpan + group + "" + 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 2a439fb9..d8e448fc 100644 --- a/src/com/gitblit/client/RepositoriesModel.java +++ b/src/com/gitblit/client/RepositoriesModel.java @@ -19,22 +19,25 @@ import java.util.ArrayList; 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 repositories; - List 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 class RepositoriesModel extends AbstractTableModel { } } - public RepositoriesModel(Map repositories) { - this.repositories = repositories; - list = new ArrayList(repositories.values()); - Collections.sort(list); + public RepositoriesModel() { + this(new ArrayList()); + } + + public RepositoriesModel(List 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 RepositoriesModel extends AbstractTableModel { 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 @@ public class RepositoriesModel extends AbstractTableModel { 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 00000000..8f92dcf4 --- /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 0000000000000000000000000000000000000000..d63932fb55fdf6dc17335b2c946e44c6a10348ec GIT binary patch literal 11593 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 Hu0mjf`<