From 828add77e71a567be98490403bb92d6f1afb9930 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Thu, 09 May 2013 22:43:57 -0400
Subject: [PATCH] Implemented application menus for repository url panel

---
 src/main/java/com/gitblit/git/GitDaemon.java                        |   14 
 src/main/java/com/gitblit/models/GitClientApplication.java          |   13 
 src/main/resources/smartgithg_32x32.png                             |    0 
 src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java     |  536 ++++++++++++++-----------
 src/main/java/com/gitblit/GitBlit.java                              |  112 ++++
 src/main/java/com/gitblit/wicket/GitBlitWebApp.properties           |    7 
 src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java     |    2 
 src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java |   21 
 src/main/java/com/gitblit/wicket/pages/BasePage.java                |   59 --
 /dev/null                                                           |  119 -----
 src/main/resources/sourcetree_32x32.png                             |    0 
 src/main/distrib/data/clientapps.json                               |   36 +
 src/main/java/com/gitblit/wicket/pages/SummaryPage.java             |   28 -
 src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.html |    1 
 src/main/java/com/gitblit/models/RepositoryUrl.java                 |   49 ++
 src/main/resources/gitblit.css                                      |  112 ++++
 src/main/java/com/gitblit/wicket/WicketUtils.java                   |   24 +
 src/main/resources/git-black_32x32.png                              |    0 
 src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html     |   85 ++-
 src/main/java/com/gitblit/wicket/pages/SummaryPage.html             |    6 
 20 files changed, 688 insertions(+), 536 deletions(-)

diff --git a/src/main/distrib/data/clientapps.json b/src/main/distrib/data/clientapps.json
index 95e45f9..9aa1e58 100644
--- a/src/main/distrib/data/clientapps.json
+++ b/src/main/distrib/data/clientapps.json
@@ -1,41 +1,63 @@
 [
 	{
+		"name": "Git",
+		"title": "Git",
+		"description": "a fast, open-source, distributed VCS",
+		"legal": "released under the GPLv2 open source license",
+		"command": "git clone {0}",
+		"productUrl": "http://git-scm.com",
+		"icon": "git-black_32x32.png",
+		"isActive": true
+	},
+	{
 		"name": "SmartGit/Hg",
+		"title": "syntevo SmartGit/Hg\u2122",
+		"description": "a Git client for Windows, Mac, & Linux",
+		"legal": "\u00a9 2013 syntevo GmbH. All rights reserved.",
 		"cloneUrl": "smartgit://cloneRepo/{0}",
 		"productUrl": "http://www.syntevo.com/smartgithg",
-		"attribution": "Syntevo SmartGit/Hg\u2122",
 		"platforms": [ "windows", "macintosh", "linux" ],
+		"icon": "smartgithg_32x32.png",
 		"isActive": false
 	},
 	{
 		"name": "SourceTree",
+		"title": "Atlassian SourceTree\u2122",
+		"description": "a free Git client for Windows or Mac",
+		"legal": "\u00a9 2013 Atlassian. All rights reserved.",
 		"cloneUrl": "sourcetree://cloneRepo/{0}",
 		"productUrl": "http://sourcetreeapp.com",
-		"attribution": "Atlassian SourceTree\u2122",
 		"platforms": [ "windows", "macintosh" ],
+		"icon": "sourcetree_32x32.png",
 		"isActive": true
 	},
 	{
 		"name": "Tower",
+		"title": "fournova Tower\u2122",
+		"description": "a Git client for Mac",
+		"legal": "\u00a9 2013 fournova Software GmbH. All rights reserved.",
 		"cloneUrl": "gittower://openRepo/{0}",
 		"productUrl": "http://www.git-tower.com",
-		"attribution": "fournova Tower\u2122",
 		"platforms": [ "macintosh" ],
 		"isActive": true
 	},
 	{
-		"name": "GitHub for Macintosh",
+		"name": "GitHub",
+		"title": "GitHub\u2122 for Macintosh",
+		"description": "a free Git client for Mac OS X",
+		"legal": "\u00a9 2013 GitHub. All rights reserved.",
 		"cloneUrl": "github-mac://openRepo/{0}",
 		"productUrl": "http://mac.github.com",
-		"attribution": "GitHub\u2122 for Macintosh",
 		"platforms": [ "macintosh" ],
 		"isActive": false
 	},
 	{
-		"name": "GitHub for Windows",
+		"name": "GitHub",
+		"title": "GitHub\u2122 for Windows",
+		"description": "a free Git client for Windows",
+		"legal": "\u00a9 2013 GitHub. All rights reserved.",
 		"cloneUrl": "github-windows://openRepo/{0}",
 		"productUrl": "http://windows.github.com",
-		"attribution": "GitHub\u2122 for Windows",
 		"platforms": [ "windows" ],
 		"isActive": false
 	}
diff --git a/src/main/java/com/gitblit/GitBlit.java b/src/main/java/com/gitblit/GitBlit.java
index 42a1434..9346e0a 100644
--- a/src/main/java/com/gitblit/GitBlit.java
+++ b/src/main/java/com/gitblit/GitBlit.java
@@ -91,15 +91,16 @@
 import com.gitblit.fanout.FanoutService;
 import com.gitblit.fanout.FanoutSocketService;
 import com.gitblit.git.GitDaemon;
-import com.gitblit.models.GitClientApplication;
 import com.gitblit.models.FederationModel;
 import com.gitblit.models.FederationProposal;
 import com.gitblit.models.FederationSet;
 import com.gitblit.models.ForkModel;
+import com.gitblit.models.GitClientApplication;
 import com.gitblit.models.Metric;
 import com.gitblit.models.ProjectModel;
 import com.gitblit.models.RegistrantAccessPermission;
 import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.RepositoryUrl;
 import com.gitblit.models.SearchResult;
 import com.gitblit.models.ServerSettings;
 import com.gitblit.models.ServerStatus;
@@ -201,7 +202,7 @@
 	private FanoutService fanoutService;
 
 	private GitDaemon gitDaemon;
-	
+
 	public GitBlit() {
 		if (gitblit == null) {
 			// set the static singleton reference
@@ -460,23 +461,106 @@
 		serverStatus.heapFree = Runtime.getRuntime().freeMemory();
 		return serverStatus;
 	}
-
+	
 	/**
-	 * Returns the list of non-Gitblit clone urls. This allows Gitblit to
-	 * advertise alternative urls for Git client repository access.
+	 * Returns a list of repository URLs and the user access permission.
 	 * 
-	 * @param repositoryName
-	 * @param userName
-	 * @return list of non-gitblit clone urls
+	 * @param request
+	 * @param user
+	 * @param repository
+	 * @return a list of repository urls
 	 */
-	public List<String> getOtherCloneUrls(String repositoryName, String username) {
-		List<String> cloneUrls = new ArrayList<String>();
-		for (String url : settings.getStrings(Keys.web.otherUrls)) {
-			cloneUrls.add(MessageFormat.format(url, repositoryName, username));
+	public List<RepositoryUrl> getRepositoryUrls(HttpServletRequest request, UserModel user, RepositoryModel repository) {
+		if (user == null) {
+			user = UserModel.ANONYMOUS;
 		}
-		return cloneUrls;
+		String username = UserModel.ANONYMOUS.equals(user) ? "" : user.username;
+
+		List<RepositoryUrl> list = new ArrayList<RepositoryUrl>();
+		// http/https url
+		if (settings.getBoolean(Keys.git.enableGitServlet, true)) {
+			AccessPermission permission = user.getRepositoryPermission(repository).permission;
+			if (permission.exceeds(AccessPermission.NONE)) {
+				list.add(new RepositoryUrl(getRepositoryUrl(request, username, repository), permission));
+			}
+		}
+
+		// git daemon url
+		String gitDaemonUrl = getGitDaemonUrl(request, user, repository);
+		if (!StringUtils.isEmpty(gitDaemonUrl)) {
+			AccessPermission permission = getGitDaemonAccessPermission(user, repository);
+			if (permission.exceeds(AccessPermission.NONE)) {
+				list.add(new RepositoryUrl(gitDaemonUrl, permission));
+			}
+		}
+
+		// add all other urls
+		// {0} = repository
+		// {1} = username
+		for (String url : settings.getStrings(Keys.web.otherUrls)) {
+			if (url.contains("{1}")) {
+				// external url requires username, only add url IF we have one
+				if(!StringUtils.isEmpty(username)) {
+					list.add(new RepositoryUrl(MessageFormat.format(url, repository.name, username), null));
+				}
+			} else {
+				// external url does not require username
+				list.add(new RepositoryUrl(MessageFormat.format(url, repository.name), null));
+			}
+		}
+		return list;
 	}
 	
+	protected String getRepositoryUrl(HttpServletRequest request, String username, RepositoryModel repository) {
+		StringBuilder sb = new StringBuilder();
+		sb.append(HttpUtils.getGitblitURL(request));
+		sb.append(Constants.GIT_PATH);
+		sb.append(repository.name);
+		
+		// inject username into repository url if authentication is required
+		if (repository.accessRestriction.exceeds(AccessRestrictionType.NONE)
+				&& !StringUtils.isEmpty(username)) {
+			sb.insert(sb.indexOf("://") + 3, username + "@");
+		}
+		return sb.toString();
+	}
+	
+	protected String getGitDaemonUrl(HttpServletRequest request, UserModel user, RepositoryModel repository) {
+		if (gitDaemon != null) {
+			String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost");
+			if (bindInterface.equals("localhost")
+					&& (!request.getServerName().equals("localhost") && !request.getServerName().equals("127.0.0.1"))) {
+				// git daemon is bound to localhost and the request is from elsewhere
+				return null;
+			}
+			if (user.canClone(repository)) {
+				String servername = request.getServerName();
+				String url = gitDaemon.formatUrl(servername, repository.name);
+				return url;
+			}
+		}
+		return null;
+	}
+	
+	protected AccessPermission getGitDaemonAccessPermission(UserModel user, RepositoryModel repository) {
+		if (gitDaemon != null && user.canClone(repository)) {
+			AccessPermission gitDaemonPermission = user.getRepositoryPermission(repository).permission;
+			if (gitDaemonPermission.atLeast(AccessPermission.CLONE)) {
+				if (repository.accessRestriction.atLeast(AccessRestrictionType.CLONE)) {
+					// can not authenticate clone via anonymous git protocol
+					gitDaemonPermission = AccessPermission.NONE;
+				} else if (repository.accessRestriction.atLeast(AccessRestrictionType.PUSH)) {
+					// can not authenticate push via anonymous git protocol
+					gitDaemonPermission = AccessPermission.CLONE;
+				} else {
+					// normal user permission
+				}
+			}
+			return gitDaemonPermission;
+		}
+		return AccessPermission.NONE;
+	}
+
 	/**
 	 * Returns the list of custom client applications to be used for the
 	 * repository url panel;
@@ -3283,8 +3367,8 @@
 	}
 	
 	protected void configureGitDaemon() {
-		String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost");
 		int port = settings.getInteger(Keys.git.daemonPort, 0);
+		String bindInterface = settings.getString(Keys.git.daemonBindInterface, "localhost");
 		if (port > 0) {
 			try {
 				gitDaemon = new GitDaemon(bindInterface, port, getRepositoriesFolder());
diff --git a/src/main/java/com/gitblit/git/GitDaemon.java b/src/main/java/com/gitblit/git/GitDaemon.java
index 7050f87..3c45171 100644
--- a/src/main/java/com/gitblit/git/GitDaemon.java
+++ b/src/main/java/com/gitblit/git/GitDaemon.java
@@ -177,6 +177,20 @@
 					}
 				} };
 	}
+	
+	public int getPort() {
+		return myAddress.getPort();
+	}
+	
+	public String formatUrl(String servername, String repository) {
+		if (getPort() == 9418) {
+			// standard port
+			return MessageFormat.format("git://{0}/{1}", servername, repository);
+		} else {
+			// non-standard port
+			return MessageFormat.format("git://{0}:{1,number,0}/{2}", servername, getPort(), repository);
+		}
+	}
 
 	/** @return timeout (in seconds) before aborting an IO operation. */
 	public int getTimeout() {
diff --git a/src/main/java/com/gitblit/models/GitClientApplication.java b/src/main/java/com/gitblit/models/GitClientApplication.java
index dbdfa39..fd53059 100644
--- a/src/main/java/com/gitblit/models/GitClientApplication.java
+++ b/src/main/java/com/gitblit/models/GitClientApplication.java
@@ -31,13 +31,15 @@
 	private static final long serialVersionUID = 1L;
 
 	public String name;
+	public String title;
+	public String description;
+	public String legal;
+	public String icon;
 	public String cloneUrl;
 	public String command;
 	public String productUrl;
-	public String attribution;
-	public boolean isApplication = true;
-	public boolean isActive = true;
 	public String[] platforms;
+	public boolean isActive;
 
 	public boolean allowsPlatform(String p) {
 		if (ArrayUtils.isEmpty(platforms)) {
@@ -55,4 +57,9 @@
 		}
 		return false;
 	}
+	
+	@Override
+	public String toString() {
+		return StringUtils.isEmpty(title) ? name : title;
+	}
 }
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/models/RepositoryUrl.java b/src/main/java/com/gitblit/models/RepositoryUrl.java
new file mode 100644
index 0000000..d72959a
--- /dev/null
+++ b/src/main/java/com/gitblit/models/RepositoryUrl.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013 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.models;
+
+import java.io.Serializable;
+
+import com.gitblit.Constants.AccessPermission;
+
+/**
+ * Represents a git repository url and it's associated access permission for the
+ * current user.
+ *  
+ * @author James Moger
+ *
+ */
+public class RepositoryUrl implements Serializable {
+
+	private static final long serialVersionUID = 1L;
+
+	public final String url;
+	public final AccessPermission permission;
+
+	public RepositoryUrl(String url, AccessPermission permission) {
+		this.url = url;
+		this.permission = permission;
+	}
+	
+	public boolean isExternal() {
+		return permission == null;
+	}
+
+	@Override
+	public String toString() {
+		return url;
+	}
+}
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
index 9355b80..7ebea4e 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
@@ -449,8 +449,5 @@
 gb.enableIncrementalPushTags = enable incremental push tags
 gb.useIncrementalPushTagsDescription = on push, automatically tag each branch tip with an incremental revision number
 gb.incrementalPushTagMessage = Auto-tagged [{0}] branch on push
-gb.externalPermissions = {0} access permissions for {1} are externally maintained
-gb.viewAccess = You do not have Gitblit read or write access
-gb.yourProtocolPermissionIs = Your {0} access permission for {1} is {2}
-gb.cloneUrl = clone {0}
-gb.visitSite = visit {0} website
\ No newline at end of file
+gb.externalPermissions = {0} access permissions are externally maintained
+gb.viewAccess = You do not have Gitblit read or write access
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/WicketUtils.java b/src/main/java/com/gitblit/wicket/WicketUtils.java
index 6e03032..2170d0b 100644
--- a/src/main/java/com/gitblit/wicket/WicketUtils.java
+++ b/src/main/java/com/gitblit/wicket/WicketUtils.java
@@ -41,6 +41,7 @@
 import org.wicketstuff.googlecharts.IChartData;
 
 import com.gitblit.Constants;
+import com.gitblit.Constants.AccessPermission;
 import com.gitblit.Constants.FederationPullStatus;
 import com.gitblit.GitBlit;
 import com.gitblit.Keys;
@@ -107,6 +108,29 @@
 			setCssClass(container, css);
 		}
 	}
+	
+	public static void setPermissionClass(Component container, AccessPermission permission) {
+		if (permission == null) {
+			setCssClass(container, "badge");
+			return;
+		}
+		switch (permission) {
+		case REWIND:
+		case DELETE:
+		case CREATE:
+			setCssClass(container, "badge badge-success");
+			break;
+		case PUSH:
+			setCssClass(container, "badge badge-info");
+			break;
+		case CLONE:
+			setCssClass(container, "badge badge-inverse");
+			break;
+		default:
+			setCssClass(container, "badge");
+			break;
+		}	
+	}
 
 	public static void setAlternatingBackground(Component c, int i) {
 		String clazz = i % 2 == 0 ? "light" : "dark";
diff --git a/src/main/java/com/gitblit/wicket/pages/BasePage.java b/src/main/java/com/gitblit/wicket/pages/BasePage.java
index 19fa749..ca3ea90 100644
--- a/src/main/java/com/gitblit/wicket/pages/BasePage.java
+++ b/src/main/java/com/gitblit/wicket/pages/BasePage.java
@@ -32,11 +32,9 @@
 import javax.servlet.http.HttpServletRequest;
 
 import org.apache.wicket.Application;
-import org.apache.wicket.Component;
 import org.apache.wicket.MarkupContainer;
 import org.apache.wicket.PageParameters;
 import org.apache.wicket.RedirectToUrlException;
-import org.apache.wicket.RequestCycle;
 import org.apache.wicket.RestartResponseException;
 import org.apache.wicket.markup.html.CSSPackageResource;
 import org.apache.wicket.markup.html.basic.Label;
@@ -45,7 +43,6 @@
 import org.apache.wicket.markup.html.panel.FeedbackPanel;
 import org.apache.wicket.markup.html.panel.Fragment;
 import org.apache.wicket.protocol.http.RequestUtils;
-import org.apache.wicket.protocol.http.WebRequest;
 import org.apache.wicket.protocol.http.servlet.ServletWebRequest;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -58,14 +55,12 @@
 import com.gitblit.GitBlit;
 import com.gitblit.Keys;
 import com.gitblit.models.ProjectModel;
-import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.TeamModel;
 import com.gitblit.models.UserModel;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.utils.TimeUtils;
 import com.gitblit.wicket.GitBlitWebSession;
 import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.DetailedRepositoryUrlPanel;
 import com.gitblit.wicket.panels.LinkPanel;
 
 public abstract class BasePage extends SessionPage {
@@ -258,60 +253,6 @@
 		return req.getServerName();
 	}
 	
-	public static String getRepositoryUrl(RepositoryModel repository) {
-		StringBuilder sb = new StringBuilder();
-		sb.append(WicketUtils.getGitblitURL(RequestCycle.get().getRequest()));
-		sb.append(Constants.GIT_PATH);
-		sb.append(repository.name);
-		
-		// inject username into repository url if authentication is required
-		if (repository.accessRestriction.exceeds(AccessRestrictionType.NONE)
-				&& GitBlitWebSession.get().isLoggedIn()) {
-			String username = GitBlitWebSession.get().getUsername();
-			sb.insert(sb.indexOf("://") + 3, username + "@");
-		}
-		return sb.toString();
-	}
-	
-	protected Component createGitDaemonUrlPanel(String wicketId, UserModel user, RepositoryModel repository) {
-		int gitDaemonPort = GitBlit.getInteger(Keys.git.daemonPort, 0);
-		if (gitDaemonPort > 0 && user.canClone(repository)) {
-			String servername = ((WebRequest) getRequest()).getHttpServletRequest().getServerName();
-			String gitDaemonUrl;
-			if (gitDaemonPort == 9418) {
-				// standard port
-				gitDaemonUrl = MessageFormat.format("git://{0}/{1}", servername, repository.name);
-			} else {
-				// non-standard port
-				gitDaemonUrl = MessageFormat.format("git://{0}:{1,number,0}/{2}", servername, gitDaemonPort, repository.name);
-			}
-			
-			AccessPermission gitDaemonPermission = user.getRepositoryPermission(repository).permission;;
-			if (gitDaemonPermission.atLeast(AccessPermission.CLONE)) {
-				if (repository.accessRestriction.atLeast(AccessRestrictionType.CLONE)) {
-					// can not authenticate clone via anonymous git protocol
-					gitDaemonPermission = AccessPermission.NONE;
-				} else if (repository.accessRestriction.atLeast(AccessRestrictionType.PUSH)) {
-					// can not authenticate push via anonymous git protocol
-					gitDaemonPermission = AccessPermission.CLONE;
-				} else {
-					// normal user permission
-				}
-			}
-			
-			if (AccessPermission.NONE.equals(gitDaemonPermission)) {
-				// repository prohibits all anonymous access
-				return new Label(wicketId).setVisible(false);
-			} else {
-				// repository allows some form of anonymous access
-				return new DetailedRepositoryUrlPanel(wicketId, getLocalizer(), this, repository.name, gitDaemonUrl, gitDaemonPermission);
-			}
-		} else {
-			// git daemon is not running
-			return new Label(wicketId).setVisible(false);
-		}
-	}
-
 	protected List<ProjectModel> getProjectModels() {
 		final UserModel user = GitBlitWebSession.get().getUser();
 		List<ProjectModel> projects = GitBlit.self().getProjectModels(user, true);
diff --git a/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java
index f7e8848..f704203 100644
--- a/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/EmptyRepositoryPage.java
@@ -53,7 +53,7 @@
 			user = UserModel.ANONYMOUS;
 		}
 		
-		RepositoryUrlPanel urlPanel = new RepositoryUrlPanel("pushurl", false, user, repository, getLocalizer(), this);
+		RepositoryUrlPanel urlPanel = new RepositoryUrlPanel("pushurl", false, user, repository);
 		String primaryUrl = urlPanel.getPrimaryUrl();
 		
 		add(new Label("repository", repositoryName));
diff --git a/src/main/java/com/gitblit/wicket/pages/SummaryPage.html b/src/main/java/com/gitblit/wicket/pages/SummaryPage.html
index 1527436..78c8f4d 100644
--- a/src/main/java/com/gitblit/wicket/pages/SummaryPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/SummaryPage.html
@@ -19,10 +19,8 @@
 				<tr><th><wicket:message key="gb.owners">[owner]</wicket:message></th><td><span wicket:id="repositoryOwners"><span wicket:id="owner"></span><span wicket:id="comma"></span></span></td></tr>
 				<tr><th><wicket:message key="gb.lastChange">[last change]</wicket:message></th><td><span wicket:id="repositoryLastChange">[repository last change]</span></td></tr>
 				<tr><th><wicket:message key="gb.stats">[stats]</wicket:message></th><td><span wicket:id="branchStats">[branch stats]</span> <span class="link"><a wicket:id="metrics"><wicket:message key="gb.metrics">[metrics]</wicket:message></a></span></td></tr>
-				<tr><th style="vertical-align:top;padding-top:4px;"><wicket:message key="gb.repositoryUrl">[URL]</wicket:message>&nbsp;<img style="vertical-align: top;padding-left:3px;" wicket:id="accessRestrictionIcon" /></th>
-				    <td style="padding-top:4px;">
-				    	<div wicket:id="repositoryUrlPanel">[repository url panel]</div>
-				    </td>
+				<tr><th style="vertical-align:top;padding-top:4px;"><wicket:message key="gb.repositoryUrl">[URL]</wicket:message></th>
+				    <td><div wicket:id="repositoryUrlPanel">[repository url panel]</div></td>
 				</tr>
 			</table>
 		</div>
diff --git a/src/main/java/com/gitblit/wicket/pages/SummaryPage.java b/src/main/java/com/gitblit/wicket/pages/SummaryPage.java
index 54445f8..321dff3 100644
--- a/src/main/java/com/gitblit/wicket/pages/SummaryPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/SummaryPage.java
@@ -42,7 +42,6 @@
 import org.wicketstuff.googlecharts.MarkerType;
 import org.wicketstuff.googlecharts.ShapeMarker;
 
-import com.gitblit.Constants.AccessRestrictionType;
 import com.gitblit.GitBlit;
 import com.gitblit.Keys;
 import com.gitblit.models.Metric;
@@ -124,32 +123,7 @@
 		add(new BookmarkablePageLink<Void>("metrics", MetricsPage.class,
 				WicketUtils.newRepositoryParameter(repositoryName)));
 
-		if (GitBlit.getBoolean(Keys.git.enableGitServlet, true)) {			
-			AccessRestrictionType accessRestriction = getRepositoryModel().accessRestriction;
-			switch (accessRestriction) {
-			case NONE:
-				add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
-				break;
-			case PUSH:
-				add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png",
-						getAccessRestrictions().get(accessRestriction)));
-				break;
-			case CLONE:
-				add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png",
-						getAccessRestrictions().get(accessRestriction)));
-				break;
-			case VIEW:
-				add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png",
-						getAccessRestrictions().get(accessRestriction)));
-				break;
-			default:
-				add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
-			}
-		} else {
-			add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
-		}
-		
-		add(new RepositoryUrlPanel("repositoryUrlPanel", false, user, model, getLocalizer(), this));
+		add(new RepositoryUrlPanel("repositoryUrlPanel", false, user, model));
 				
 		add(new LogPanel("commitsPanel", repositoryName, getRepositoryModel().HEAD, r, numberCommits, 0, getRepositoryModel().showRemoteBranches));
 		add(new TagsPanel("tagsPanel", repositoryName, r, numberRefs).hideIfEmpty());
diff --git a/src/main/java/com/gitblit/wicket/panels/DetailedRepositoryUrlPanel.html b/src/main/java/com/gitblit/wicket/panels/DetailedRepositoryUrlPanel.html
deleted file mode 100644
index e671435..0000000
--- a/src/main/java/com/gitblit/wicket/panels/DetailedRepositoryUrlPanel.html
+++ /dev/null
@@ -1,42 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
-<html xmlns="http://www.w3.org/1999/xhtml"  
-      xmlns:wicket="http://wicket.apache.org/dtds.data/wicket-xhtml1.3-strict.dtd"  
-      xml:lang="en"  
-      lang="en"> 
-
-<wicket:panel>
-	<span wicket:id="urlPanel"></span>
-    
-    <!--  Repository url panel -->
-    <wicket:fragment wicket:id="repositoryUrlPanel">
-    	<span class="repositoryUrlContainer">
-			<span wicket:id="repositoryProtocol" class="repositoryUrlEndCap">[protocol]</span>
-			<span class="repositoryUrl">
-				<span wicket:id="repositoryUrl">[repository url]</span>
-				<span class="hidden-phone hidden-tablet" wicket:id="copyFunction"></span>
-			</span>
-			<span class="hidden-phone hidden-tablet repositoryUrlEndCap" wicket:id="repositoryUrlPermission">[repository url permission]</span>
-		</span>
-	</wicket:fragment>
-	
-    <!-- Plain JavaScript manual copy & paste -->
-    <wicket:fragment wicket:id="jsPanel">
-    	<span style="vertical-align:baseline;">
-    		<img wicket:id="copyIcon" wicket:message="title:gb.copyToClipboard"></img>
-    	</span>
-    </wicket:fragment>
-    
-    <!-- flash-based button-press copy & paste -->
-    <wicket:fragment wicket:id="clippyPanel">
-   		<object wicket:message="title:gb.copyToClipboard" style="vertical-align:middle;"
-   			wicket:id="clippy"
-   			width="14" 
-   			height="14"
-   			bgcolor="#ffffff" 
-       		quality="high"
-       		wmode="transparent"
-       		scale="noscale"
-       		allowScriptAccess="always"></object>
-	</wicket:fragment>
-</wicket:panel>
-</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/panels/DetailedRepositoryUrlPanel.java b/src/main/java/com/gitblit/wicket/panels/DetailedRepositoryUrlPanel.java
deleted file mode 100644
index 92c51e4..0000000
--- a/src/main/java/com/gitblit/wicket/panels/DetailedRepositoryUrlPanel.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright 2013 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.wicket.panels;
-
-import java.text.MessageFormat;
-
-import org.apache.wicket.Component;
-import org.apache.wicket.Localizer;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.image.ContextImage;
-import org.apache.wicket.markup.html.panel.Fragment;
-
-import com.gitblit.Constants.AccessPermission;
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.WicketUtils;
-
-public class DetailedRepositoryUrlPanel extends BasePanel {
-
-	private static final long serialVersionUID = 1L;
-	public DetailedRepositoryUrlPanel(String wicketId, Localizer localizer, Component parent, String repository, String url) {
-		this(wicketId, localizer, parent, repository, url, null);
-	}
-	
-	public DetailedRepositoryUrlPanel(String wicketId, Localizer localizer, Component parent, String repository, String url, AccessPermission ap) {
-		super(wicketId);
-		
-		String protocol = url.substring(0, url.indexOf(':'));
-		String note;
-		String permission;
-		
-		if (ap == null) {
-			note = MessageFormat.format(localizer.getString("gb.externalPermissions", parent), protocol, repository);
-			permission = "";
-		} else {
-			note = null;
-			permission = ap.toString();
-			String key;
-			switch (ap) {
-				case OWNER:
-				case REWIND:
-					key = "gb.rewindPermission";
-					break;
-				case DELETE:
-					key = "gb.deletePermission";
-					break;
-				case CREATE:
-					key = "gb.createPermission";
-					break;
-				case PUSH:
-					key = "gb.pushPermission";
-					break;
-				case CLONE:
-					key = "gb.clonePermission";
-					break;
-				default:
-					key = null;
-					note = localizer.getString("gb.viewAccess", parent);
-					break;
-			}
-			
-			if (note == null) {
-				String pattern = localizer.getString(key, parent);
-				String description = MessageFormat.format(pattern, permission);
-				String permissionPattern = localizer.getString("gb.yourProtocolPermissionIs", parent);
-				note = MessageFormat.format(permissionPattern, protocol.toUpperCase(), repository, description);
-			}
-		}
-		
-		if (!StringUtils.isEmpty(url) && ((ap == null) || ap.atLeast(AccessPermission.CLONE))) {
-			// valid repository url
-			Fragment fragment = new Fragment("urlPanel", "repositoryUrlPanel", this);
-			add(fragment);
-			Label protocolLabel = new Label("repositoryProtocol", protocol + "://");
-			WicketUtils.setHtmlTooltip(protocolLabel, note);
-			fragment.add(protocolLabel);
-			fragment.add(new Label("repositoryUrl", url.substring(url.indexOf("://") + 3)));
-			Label permissionLabel = new Label("repositoryUrlPermission", permission);
-			WicketUtils.setHtmlTooltip(permissionLabel, note);
-			fragment.add(permissionLabel);
-
-			if (StringUtils.isEmpty(url)) {
-				fragment.add(new Label("copyFunction").setVisible(false));
-			} else if (GitBlit.getBoolean(Keys.web.allowFlashCopyToClipboard, true)) {
-				// clippy: flash-based copy & paste
-				Fragment copyFragment = new Fragment("copyFunction", "clippyPanel", this);
-				String baseUrl = WicketUtils.getGitblitURL(getRequest());
-				ShockWaveComponent clippy = new ShockWaveComponent("clippy", baseUrl + "/clippy.swf");
-				clippy.setValue("flashVars", "text=" + StringUtils.encodeURL(url));
-				copyFragment.add(clippy);
-				fragment.add(copyFragment);
-			} else {
-				// javascript: manual copy & paste with modal browser prompt dialog
-				Fragment copyFragment = new Fragment("copyFunction", "jsPanel", this);
-				ContextImage img = WicketUtils.newImage("copyIcon", "clippy.png");
-				img.add(new JavascriptTextPrompt("onclick", "Copy to Clipboard (Ctrl+C, Enter)", url));
-				copyFragment.add(img);
-				fragment.add(copyFragment);
-			}
-		} else {
-			// no Git url, there may be a message
-			add(new Label("urlPanel", MessageFormat.format("<i>{0}</i>", note)).setEscapeModelStrings(false).setVisible(!StringUtils.isEmpty(note)));
-		}
-	}
-}
diff --git a/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.html b/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.html
index e67e641..e9196cd 100644
--- a/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.html
+++ b/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.html
@@ -59,7 +59,6 @@
 				<div>
 					<span class="repositorySwatch" wicket:id="repositorySwatch"></span>
 					<span class="repository" style="padding-left:3px;color:black;" wicket:id="repositoryName">[repository name]</span>
-					<img class="inlineIcon" style="vertical-align:baseline" wicket:id="accessRestrictionIcon" />
 				</div>
 				<span wicket:id="originRepository">[origin repository]</span>
 			</div>
diff --git a/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java b/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java
index 7cce74f..e7fe017 100644
--- a/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/ProjectRepositoryPanel.java
@@ -103,25 +103,6 @@
 		} else {
 			add(WicketUtils.newClearPixel("federatedIcon").setVisible(false));
 		}
-		switch (entry.accessRestriction) {
-		case NONE:
-			add(WicketUtils.newBlankImage("accessRestrictionIcon").setVisible(false));
-			break;
-		case PUSH:
-			add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png",
-					accessRestrictions.get(entry.accessRestriction)));
-			break;
-		case CLONE:
-			add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png",
-					accessRestrictions.get(entry.accessRestriction)));
-			break;
-		case VIEW:
-			add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png",
-					accessRestrictions.get(entry.accessRestriction)));
-			break;
-		default:
-			add(WicketUtils.newBlankImage("accessRestrictionIcon"));
-		}
 
 		if (ArrayUtils.isEmpty(entry.owners)) {
 			add(new Label("repositoryOwner").setVisible(false));
@@ -212,6 +193,6 @@
 
 		add(new ExternalLink("syndication", SyndicationServlet.asLink("", entry.name, null, 0)));
 
-		add(new RepositoryUrlPanel("repositoryPrimaryUrl", true, user, entry, localizer, parent));
+		add(new RepositoryUrlPanel("repositoryPrimaryUrl", true, user, entry));
 	}
 }
diff --git a/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html b/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html
index 675ebb5..b22aa71 100644
--- a/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html
+++ b/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.html
@@ -5,33 +5,66 @@
       lang="en"> 
 
 <wicket:panel>
-	<div wicket:id="repositoryPrimaryUrl">[repository primary url]</div>
-	<div class="btn-toolbar" style="margin-bottom: 0px;">
-		<div class="btn-group" wicket:id="urlMenus">
-   			<a class="btn btn-mini btn-action" data-toggle="dropdown" href="#">
-   				<i class="icon-download icon-black"></i>
-    			<span wicket:id="productName"></span>
-    			<span class="caret"></span>
-   			</a>
-   			<ul class="dropdown-menu">
-   				<li><div style="padding-left: 15px; font-style: italic;" wicket:id="productAttribution"></div></li>
-   				<li class="divider"></li>
-   				
-   				<li wicket:id="repoLinks">
-   					<span wicket:id="repoLink"></span>
-   				</li>
-   				   				
-   				<li style="border-top: 1px solid #eee; margin-top:5px;padding-top:5px;"><span wicket:id="productLink"></span></li>
-   			</ul>
-   		</div>
-	</div>
-	
-	<wicket:fragment wicket:id="commandFragment">
-		<span wicket:id="content"></span><span class="hidden-phone hidden-tablet" wicket:id="copyFunction"></span>
-	</wicket:fragment>
 
-	<wicket:fragment wicket:id="linkFragment">
-		<span wicket:id="content"></span>
+	<div wicket:id="repositoryUrlPanel"></div>
+	<div wicket:id="applicationMenusPanel"></div>
+
+	
+	<wicket:fragment wicket:id="repositoryUrlFragment">
+		<div class="btn-toolbar" style="margin: 0px;">
+			<div class="btn-group repositoryUrlContainer">
+				<img style="vertical-align: middle;padding: 0px 0px 1px 3px;" wicket:id="accessRestrictionIcon"></img>
+				<span wicket:id="menu"></span>
+   				<span class="repositoryUrl">
+   					<span wicket:id="primaryUrl">[repository primary url]</span>
+   					<span class="hidden-phone hidden-tablet" wicket:id="copyFunction"></span>
+   				</span>
+   				<span class="hidden-phone hidden-tablet repositoryUrlRightCap" wicket:id="primaryUrlPermission">[repository primary url permission]</span>
+   			</div>
+		</div>
+	</wicket:fragment>
+	
+	<wicket:fragment wicket:id="applicationMenusFragment">
+		<div class="btn-toolbar" style="margin: 4px 0px 0px 0px;">
+			<div class="btn-group" wicket:id="appMenus">
+   				<a class="btn btn-mini btn-inverse" data-toggle="dropdown" href="#">   				
+	    			<span wicket:id="applicationName"></span>
+    				<span class="caret"></span>
+   				</a>
+   				<ul class="dropdown-menu applicationMenu">
+   					<li>
+   						<div class="applicationHeaderMenuItem">
+   							<div style="float:right">
+   								<img style="padding-right: 5px;vertical-align: middle;" wicket:id="applicationIcon"></img>
+   							</div>
+   							<span class="applicationTitle" wicket:id="applicationTitle"></span>
+   						</div>
+   					</li>
+	   				<li><div class="applicationHeaderMenuItem"><span wicket:id="applicationDescription"></span></div></li>
+   					<li><div class="applicationLegalMenuItem"><span wicket:id="applicationLegal"></span></div></li>
+   					
+   					<li class="divider" style="margin: 5px 1px 0px 1px;clear:both;" ></li>
+   				
+   					<li class="action" wicket:id="actionItems">
+   						<span wicket:id="actionItem"></span>
+   					</li>
+   				</ul>
+   			</div>
+		</div>
+	</wicket:fragment>
+	
+	<wicket:fragment wicket:id="urlProtocolMenuFragment">
+		<a class="" data-toggle="dropdown" href="#">   				
+    		<span class="repositoryUrlLeftCap" wicket:id="menuText">URLs</span>
+	   		<span class="caret" style="vertical-align: middle;"></span>
+   		</a>
+   		<ul class="dropdown-menu urlMenu">
+   			<li class="url" wicket:id="repoUrls"><span wicket:id="repoUrl"></span></li>
+	   	</ul>
+	</wicket:fragment>
+	
+	<wicket:fragment wicket:id="actionFragment">
+		<span wicket:id="permission" style="margin: 0px 10px 0px 5px;"></span><span wicket:id="content"></span><span class="hidden-phone hidden-tablet" wicket:id="copyFunction"></span>
 	</wicket:fragment>
 
     <!-- Plain JavaScript manual copy & paste -->
diff --git a/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java b/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java
index 9640ab0..00c7cf7 100644
--- a/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java
+++ b/src/main/java/com/gitblit/wicket/panels/RepositoryUrlPanel.java
@@ -15,13 +15,15 @@
  */
 package com.gitblit.wicket.panels;
 
-import java.io.Serializable;
 import java.text.MessageFormat;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
 
 import org.apache.wicket.Component;
-import org.apache.wicket.Localizer;
 import org.apache.wicket.RequestCycle;
 import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.image.ContextImage;
@@ -32,7 +34,6 @@
 import org.apache.wicket.protocol.http.WebRequest;
 import org.apache.wicket.protocol.http.request.WebClientInfo;
 
-import com.gitblit.Constants;
 import com.gitblit.Constants.AccessPermission;
 import com.gitblit.Constants.AccessRestrictionType;
 import com.gitblit.GitBlit;
@@ -40,6 +41,7 @@
 import com.gitblit.SparkleShareInviteServlet;
 import com.gitblit.models.GitClientApplication;
 import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.RepositoryUrl;
 import com.gitblit.models.UserModel;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.GitBlitWebSession;
@@ -55,260 +57,314 @@
 public class RepositoryUrlPanel extends BasePanel {
 
 	private static final long serialVersionUID = 1L;
+
+	private final String externalPermission = "?";
+
+	private boolean onlyUrls;
+	private UserModel user; 
+	private RepositoryModel repository;
+	private RepositoryUrl primaryUrl;
+	private Map<String, String> urlPermissionsMap;
+	private Map<AccessRestrictionType, String> accessRestrictionsMap;
 	
-	private final RepoUrl primaryUrl;
-
-	public RepositoryUrlPanel(String wicketId, boolean onlyPrimary, UserModel user, 
-			final RepositoryModel repository, Localizer localizer, Component owner) {
+	public RepositoryUrlPanel(String wicketId, boolean onlyUrls, UserModel user, RepositoryModel repository) {
 		super(wicketId);
-		if (user == null) {
-			user = UserModel.ANONYMOUS;
-		}
-		List<RepoUrl> repositoryUrls = new ArrayList<RepoUrl>();
+		this.onlyUrls = onlyUrls;
+		this.user = user == null ? UserModel.ANONYMOUS : user;
+		this.repository = repository;
+		this.urlPermissionsMap = new HashMap<String, String>();
+	}
+	
+	@Override
+	protected void onInitialize() {
+		super.onInitialize();
 
-		// http/https url
-		if (GitBlit.getBoolean(Keys.git.enableGitServlet, true)) {
-			AccessPermission permission = user.getRepositoryPermission(repository).permission;
-			if (permission.exceeds(AccessPermission.NONE)) {
-				repositoryUrls.add(new RepoUrl(getRepositoryUrl(repository), permission));
-			}
-		}
-		
-		// git daemon url
-		String gitDaemonUrl = getGitDaemonUrl(user, repository);
-		if (!StringUtils.isEmpty(gitDaemonUrl)) {
-			AccessPermission permission = getGitDaemonAccessPermission(user, repository);
-			if (permission.exceeds(AccessPermission.NONE)) {
-				repositoryUrls.add(new RepoUrl(gitDaemonUrl, permission));
-			}
-		}
-		
-		// add all other urls
-		for (String url : GitBlit.self().getOtherCloneUrls(repository.name, UserModel.ANONYMOUS.equals(user) ? "" : user.username)) {
-			repositoryUrls.add(new RepoUrl(url, null));
-		}
-		
+		HttpServletRequest req = ((WebRequest) getRequest()).getHttpServletRequest();
+
+		List<RepositoryUrl> repositoryUrls = GitBlit.self().getRepositoryUrls(req, user, repository);
 		// grab primary url from the top of the list
 		primaryUrl = repositoryUrls.size() == 0 ? null : repositoryUrls.get(0);
 
-		add(new DetailedRepositoryUrlPanel("repositoryPrimaryUrl", localizer, owner, 
-				repository.name, primaryUrl == null ? "" : primaryUrl.url,
-				primaryUrl == null ? null : primaryUrl.permission));
-		
-		if (onlyPrimary) {
-			// only displaying the primary url
-			add(new Label("urlMenus").setVisible(false));
+		boolean canClone = ((primaryUrl.permission == null) || primaryUrl.permission.atLeast(AccessPermission.CLONE));
+
+		if (repositoryUrls.size() == 0 || !canClone) {
+			// no urls, nothing to show.
+			add(new Label("repositoryUrlPanel").setVisible(false));
+			add(new Label("applicationMenusPanel").setVisible(false));
 			return;
 		}
 		
-		final String clonePattern = localizer.getString("gb.cloneUrl", owner);
-		final String visitSitePattern = localizer.getString("gb.visitSite", owner);
-		
-		GitClientApplication URLS = new GitClientApplication();
-		URLS.name = "URLs";
-		URLS.command = "{0}";
-		URLS.attribution = "Repository URLs";
-		URLS.isApplication = false;
-		URLS.isActive = true;
-		
-		GitClientApplication GIT = new GitClientApplication();
-		GIT.name = "Git";
-		GIT.command = "git clone {0}";
-		GIT.productUrl = "http://git-scm.org";
-		GIT.attribution = "Git Syntax";
-		GIT.isApplication = false;
-		GIT.isActive = true;
-		
-		final List<GitClientApplication> clientApps = new ArrayList<GitClientApplication>();
-		clientApps.add(URLS);
-		clientApps.add(GIT);
-		
-		final String userAgent = ((WebClientInfo) GitBlitWebSession.get().getClientInfo()).getUserAgent();
+		// display primary url
+		add(createPrimaryUrlPanel("repositoryUrlPanel", repository, repositoryUrls));
+
 		boolean allowAppLinks = GitBlit.getBoolean(Keys.web.allowAppCloneLinks, true);
-		if (user.canClone(repository)) {
-			for (GitClientApplication app : GitBlit.self().getClientApplications()) {
-				if (app.isActive && app.allowsPlatform(userAgent) && (!app.isApplication || (app.isApplication && allowAppLinks))) {
-					clientApps.add(app);
-				}
-			}
-
-			// sparkleshare invite url
-			String sparkleshareUrl = getSparkleShareInviteUrl(user, repository);
-			if (!StringUtils.isEmpty(sparkleshareUrl) && allowAppLinks) {
-				GitClientApplication link = new GitClientApplication();
-				link.name = "SparkleShare";
-				link.cloneUrl = sparkleshareUrl;
-				link.attribution = "SparkleShare\u2122";
-				link.platforms = new String [] { "windows", "macintosh", "linux" };
-				link.productUrl = "http://sparkleshare.org";
-				link.isApplication = true;
-				link.isActive = true;
-				clientApps.add(link);
-			}
+		if (onlyUrls || !canClone || !allowAppLinks) {
+			// only display the url(s)
+			add(new Label("applicationMenusPanel").setVisible(false));
+			return;
 		}
-		
-		final ListDataProvider<RepoUrl> repoUrls = new ListDataProvider<RepoUrl>(repositoryUrls);
-
-		// app clone links
-		ListDataProvider<GitClientApplication> appLinks = new ListDataProvider<GitClientApplication>(clientApps);
-		DataView<GitClientApplication> urlMenus = new DataView<GitClientApplication>("urlMenus", appLinks) {
-			private static final long serialVersionUID = 1L;
-			
-			public void populateItem(final Item<GitClientApplication> item) {
-				final GitClientApplication cloneLink = item.getModelObject();
-				item.add(new Label("productName", cloneLink.name));
-				
-				// a nested repeater for all repo links
-				DataView<RepoUrl> repoLinks = new DataView<RepoUrl>("repoLinks", repoUrls) {
-					private static final long serialVersionUID = 1L;
-
-					public void populateItem(final Item<RepoUrl> repoLinkItem) {
-						RepoUrl repoUrl = repoLinkItem.getModelObject();
-						if (!StringUtils.isEmpty(cloneLink.cloneUrl)) {
-							// custom registered url
-							Fragment fragment = new Fragment("repoLink", "linkFragment", this);
-							String name;
-							if (repoUrl.permission != null) {
-								name = MessageFormat.format("{0} ({1})", repoUrl.url, repoUrl.permission);
-							} else {
-								name = repoUrl.url;
-							}
-							String url = MessageFormat.format(cloneLink.cloneUrl, repoUrl);
-							fragment.add(new LinkPanel("content", null, MessageFormat.format(clonePattern, name), url));
-							repoLinkItem.add(fragment);
-							String tooltip = getProtocolPermissionDescription(repository, repoUrl);
-							WicketUtils.setHtmlTooltip(fragment, tooltip);
-						} else if (!StringUtils.isEmpty(cloneLink.command)) {
-							// command-line
-							Fragment fragment = new Fragment("repoLink", "commandFragment", this);
-							WicketUtils.setCssClass(fragment, "repositoryUrlMenuItem");
-							String command = MessageFormat.format(cloneLink.command, repoUrl);
-							fragment.add(new Label("content", command));
-							repoLinkItem.add(fragment);
-							String tooltip = getProtocolPermissionDescription(repository, repoUrl);
-							WicketUtils.setHtmlTooltip(fragment, tooltip);
-							
-							// copy function for command
-							if (GitBlit.getBoolean(Keys.web.allowFlashCopyToClipboard, true)) {
-								// clippy: flash-based copy & paste
-								Fragment copyFragment = new Fragment("copyFunction", "clippyPanel", this);
-								String baseUrl = WicketUtils.getGitblitURL(getRequest());
-								ShockWaveComponent clippy = new ShockWaveComponent("clippy", baseUrl + "/clippy.swf");
-								clippy.setValue("flashVars", "text=" + StringUtils.encodeURL(command));
-								copyFragment.add(clippy);
-								fragment.add(copyFragment);
-							} else {
-								// javascript: manual copy & paste with modal browser prompt dialog
-								Fragment copyFragment = new Fragment("copyFunction", "jsPanel", this);
-								ContextImage img = WicketUtils.newImage("copyIcon", "clippy.png");
-								img.add(new JavascriptTextPrompt("onclick", "Copy to Clipboard (Ctrl+C, Enter)", command));
-								copyFragment.add(img);
-								fragment.add(copyFragment);
-							}
-						}
-					}};
-				item.add(repoLinks);
-				
-				item.add(new Label("productAttribution", cloneLink.attribution));
-				if (!StringUtils.isEmpty(cloneLink.productUrl)) {
-					LinkPanel productlinkPanel = new LinkPanel("productLink", null,
-							MessageFormat.format(visitSitePattern, cloneLink.name), cloneLink.productUrl, true);
-					item.add(productlinkPanel);
-				} else {
-					item.add(new Label("productLink").setVisible(false));
-				}
-			}
-		};
-		add(urlMenus);
+		// create the git client application menus
+		add(createApplicationMenus("applicationMenusPanel", user, repository, repositoryUrls));
 	}
-	
+
 	public String getPrimaryUrl() {
 		return primaryUrl == null ? "" : primaryUrl.url;
 	}
-	
-	protected String getRepositoryUrl(RepositoryModel repository) {
-		StringBuilder sb = new StringBuilder();
-		sb.append(WicketUtils.getGitblitURL(RequestCycle.get().getRequest()));
-		sb.append(Constants.GIT_PATH);
-		sb.append(repository.name);
+
+	protected Fragment createPrimaryUrlPanel(String wicketId, final RepositoryModel repository, List<RepositoryUrl> repositoryUrls) {
+
+		Fragment urlPanel = new Fragment(wicketId, "repositoryUrlFragment", this);
+		urlPanel.setRenderBodyOnly(true);
 		
-		// inject username into repository url if authentication is required
-		if (repository.accessRestriction.exceeds(AccessRestrictionType.NONE)
-				&& GitBlitWebSession.get().isLoggedIn()) {
-			String username = GitBlitWebSession.get().getUsername();
-			sb.insert(sb.indexOf("://") + 3, username + "@");
+		if (repositoryUrls.size() == 1) {
+			//
+			// Single repository url, no dropdown menu
+			//
+			urlPanel.add(new Label("menu").setVisible(false));
+		} else {
+			//
+			// Multiple repository urls, show url drop down menu
+			//
+			ListDataProvider<RepositoryUrl> urlsDp = new ListDataProvider<RepositoryUrl>(repositoryUrls);
+			DataView<RepositoryUrl> repoUrlMenuItems = new DataView<RepositoryUrl>("repoUrls", urlsDp) {
+				private static final long serialVersionUID = 1L;
+
+				public void populateItem(final Item<RepositoryUrl> item) {
+					RepositoryUrl repoUrl = item.getModelObject();
+					// repository url
+					Fragment fragment = new Fragment("repoUrl", "actionFragment", this);					
+					Component content = new Label("content", repoUrl.url).setRenderBodyOnly(true);
+					WicketUtils.setCssClass(content, "commandMenuItem");
+					fragment.add(content);
+					item.add(fragment);
+					
+					Label permissionLabel = new Label("permission", repoUrl.isExternal() ? externalPermission : repoUrl.permission.toString());
+					WicketUtils.setPermissionClass(permissionLabel, repoUrl.permission);
+					String tooltip = getProtocolPermissionDescription(repository, repoUrl);
+					WicketUtils.setHtmlTooltip(permissionLabel, tooltip);
+					fragment.add(permissionLabel);
+					fragment.add(createCopyFragment(repoUrl.url));
+				}
+			};
+
+			Fragment urlMenuFragment = new Fragment("menu", "urlProtocolMenuFragment", this);
+			urlMenuFragment.setRenderBodyOnly(true);
+			urlMenuFragment.add(new Label("menuText", getString("gb.url")));
+			urlMenuFragment.add(repoUrlMenuItems);
+			urlPanel.add(urlMenuFragment);
 		}
-		return sb.toString();
-	}
-	
-	protected String getGitDaemonUrl(UserModel user, RepositoryModel repository) {
-		int gitDaemonPort = GitBlit.getInteger(Keys.git.daemonPort, 0);
-		if (gitDaemonPort > 0 && user.canClone(repository)) {
-			String servername = ((WebRequest) getRequest()).getHttpServletRequest().getServerName();
-			String gitDaemonUrl;
-			if (gitDaemonPort == 9418) {
-				// standard port
-				gitDaemonUrl = MessageFormat.format("git://{0}/{1}", servername, repository.name);
-			} else {
-				// non-standard port
-				gitDaemonUrl = MessageFormat.format("git://{0}:{1,number,0}/{2}", servername, gitDaemonPort, repository.name);
+
+		// access restriction icon and tooltip
+		if (isGitblitServingRepositories()) {
+			switch (repository.accessRestriction) {
+			case NONE:
+				urlPanel.add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
+				break;
+			case PUSH:
+				urlPanel.add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png",
+						getAccessRestrictions().get(repository.accessRestriction)));
+				break;
+			case CLONE:
+				urlPanel.add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png",
+						getAccessRestrictions().get(repository.accessRestriction)));
+				break;
+			case VIEW:
+				urlPanel.add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png",
+						getAccessRestrictions().get(repository.accessRestriction)));
+				break;
+			default:
+				urlPanel.add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
 			}
-			return gitDaemonUrl;
+		} else {
+			urlPanel.add(WicketUtils.newClearPixel("accessRestrictionIcon").setVisible(false));
 		}
-		return null;
+		
+		urlPanel.add(new Label("primaryUrl", primaryUrl.url).setRenderBodyOnly(true));
+
+		Label permissionLabel = new Label("primaryUrlPermission", primaryUrl.isExternal() ? externalPermission : primaryUrl.permission.toString());		
+		String tooltip = getProtocolPermissionDescription(repository, primaryUrl);
+		WicketUtils.setHtmlTooltip(permissionLabel, tooltip);
+		urlPanel.add(permissionLabel);
+		urlPanel.add(createCopyFragment(primaryUrl.url));
+		
+		return urlPanel;
 	}
 	
-	protected AccessPermission getGitDaemonAccessPermission(UserModel user, RepositoryModel repository) {
-		int gitDaemonPort = GitBlit.getInteger(Keys.git.daemonPort, 0);
-		if (gitDaemonPort > 0 && user.canClone(repository)) {
-			AccessPermission gitDaemonPermission = user.getRepositoryPermission(repository).permission;;
-			if (gitDaemonPermission.atLeast(AccessPermission.CLONE)) {
-				if (repository.accessRestriction.atLeast(AccessRestrictionType.CLONE)) {
-					// can not authenticate clone via anonymous git protocol
-					gitDaemonPermission = AccessPermission.NONE;
-				} else if (repository.accessRestriction.atLeast(AccessRestrictionType.PUSH)) {
-					// can not authenticate push via anonymous git protocol
-					gitDaemonPermission = AccessPermission.CLONE;
-				} else {
-					// normal user permission
+	protected Fragment createApplicationMenus(String wicketId, UserModel user, final RepositoryModel repository, List<RepositoryUrl> repositoryUrls) {
+		final List<GitClientApplication> displayedApps = new ArrayList<GitClientApplication>();
+		final String userAgent = ((WebClientInfo) GitBlitWebSession.get().getClientInfo()).getUserAgent();
+		
+		if (user.canClone(repository)) {
+			for (GitClientApplication app : GitBlit.self().getClientApplications()) {
+				if (app.isActive && app.allowsPlatform(userAgent)) {
+					displayedApps.add(app);
 				}
 			}
-			return gitDaemonPermission;
-		}
-		return AccessPermission.NONE;
-	}
 
-	protected String getSparkleShareInviteUrl(UserModel user, RepositoryModel repository) {
+			GitClientApplication sparkleshare = getSparkleShareAppMenu(user, repository);
+			if (sparkleshare != null) {
+				displayedApps.add(sparkleshare);
+			}
+		}
+
+		final ListDataProvider<RepositoryUrl> urlsDp = new ListDataProvider<RepositoryUrl>(repositoryUrls);
+		ListDataProvider<GitClientApplication> displayedAppsDp = new ListDataProvider<GitClientApplication>(displayedApps);
+		DataView<GitClientApplication> appMenus = new DataView<GitClientApplication>("appMenus", displayedAppsDp) {
+			private static final long serialVersionUID = 1L;
+
+			public void populateItem(final Item<GitClientApplication> item) {
+				final GitClientApplication clientApp = item.getModelObject();
+
+				// menu button
+				item.add(new Label("applicationName", clientApp.name));
+				
+				// application icon
+				Component img;
+				if (StringUtils.isEmpty(clientApp.icon)) {
+					img = WicketUtils.newClearPixel("applicationIcon").setVisible(false);	
+				} else {
+					img = WicketUtils.newImage("applicationIcon", clientApp.icon);	
+				}				
+				item.add(img);
+				
+				// application menu title, may be a link
+				if (StringUtils.isEmpty(clientApp.productUrl)) {
+					item.add(new Label("applicationTitle", clientApp.toString()));
+				} else {
+					item.add(new LinkPanel("applicationTitle", null, clientApp.toString(), clientApp.productUrl, true));
+				}
+				
+				// brief application description
+				if (StringUtils.isEmpty(clientApp.description)) {
+					item.add(new Label("applicationDescription").setVisible(false));
+				} else {
+					item.add(new Label("applicationDescription", clientApp.description));
+				}
+				
+				// brief application legal info, copyright, license, etc
+				if (StringUtils.isEmpty(clientApp.legal)) {
+					item.add(new Label("applicationLegal").setVisible(false));
+				} else {
+					item.add(new Label("applicationLegal", clientApp.legal));
+				}
+				
+				// a nested repeater for all action items
+				DataView<RepositoryUrl> actionItems = new DataView<RepositoryUrl>("actionItems", urlsDp) {
+					private static final long serialVersionUID = 1L;
+
+					public void populateItem(final Item<RepositoryUrl> repoLinkItem) {
+						RepositoryUrl repoUrl = repoLinkItem.getModelObject();
+						
+						Fragment fragment = new Fragment("actionItem", "actionFragment", this);
+						fragment.add(createPermissionBadge("permission", repoUrl));
+
+						if (!StringUtils.isEmpty(clientApp.cloneUrl)) {
+							// custom registered url
+							String url = MessageFormat.format(clientApp.cloneUrl, repoUrl);
+							fragment.add(new LinkPanel("content", "applicationMenuItem", getString("gb.clone") + " " + repoUrl.url, url));
+							repoLinkItem.add(fragment);
+							fragment.add(new Label("copyFunction").setVisible(false));
+						} else if (!StringUtils.isEmpty(clientApp.command)) {
+							// command-line
+							String command = MessageFormat.format(clientApp.command, repoUrl);
+							Label content = new Label("content", command);
+							WicketUtils.setCssClass(content, "commandMenuItem");
+							fragment.add(content);
+							repoLinkItem.add(fragment);
+							
+							// copy function for command
+							fragment.add(createCopyFragment(command));
+						}
+					}};
+					item.add(actionItems);
+			}
+		};
+		
+		Fragment applicationMenus = new Fragment(wicketId, "applicationMenusFragment", this);
+		applicationMenus.add(appMenus);
+		return applicationMenus;
+	}
+	
+	protected GitClientApplication getSparkleShareAppMenu(UserModel user, RepositoryModel repository) {
+		String url = null;
 		if (repository.isBare && repository.isSparkleshared()) {
 			String username = null;
 			if (UserModel.ANONYMOUS != user) {
 				username = user.username;
 			}
-			if (GitBlit.getBoolean(Keys.git.enableGitServlet, true) || (GitBlit.getInteger(Keys.git.daemonPort, 0) > 0)) {
+			if (isGitblitServingRepositories()) {
 				// Gitblit as server
 				// ensure user can rewind
 				if (user.canRewindRef(repository)) {
 					String baseURL = WicketUtils.getGitblitURL(RequestCycle.get().getRequest());
-					return SparkleShareInviteServlet.asLink(baseURL, repository.name, username);
+					url = SparkleShareInviteServlet.asLink(baseURL, repository.name, username);
 				}
 			} else {
 				// Gitblit as viewer, assume RW+ permission
 				String baseURL = WicketUtils.getGitblitURL(RequestCycle.get().getRequest());
-				return SparkleShareInviteServlet.asLink(baseURL, repository.name, username);
+				url = SparkleShareInviteServlet.asLink(baseURL, repository.name, username);
 			}
+		}
+
+		// sparkleshare invite url
+		if (!StringUtils.isEmpty(url)) {
+			GitClientApplication app = new GitClientApplication();
+			app.name = "SparkleShare";
+			app.title = "SparkleShare\u2122";
+			app.description = "an open source collaboration and sharing tool";
+			app.legal = "released under the GPLv3 open source license";
+			app.cloneUrl = url;
+			app.platforms = new String [] { "windows", "macintosh", "linux" };
+			app.productUrl = "http://sparkleshare.org";
+			app.icon = "star_32x32.png";
+			app.isActive = true;
+			return app;
 		}
 		return null;
 	}
 	
-	protected String getProtocolPermissionDescription(RepositoryModel repository, RepoUrl repoUrl) {
-		String protocol = repoUrl.url.substring(0, repoUrl.url.indexOf("://"));
-		String note;
-		if (repoUrl.permission == null) {
-			note = MessageFormat.format(getString("gb.externalPermissions"), protocol, repository.name);			
+	protected boolean isGitblitServingRepositories() {
+		return GitBlit.getBoolean(Keys.git.enableGitServlet, true) || (GitBlit.getInteger(Keys.git.daemonPort, 0) > 0);
+	}
+	
+	protected Label createPermissionBadge(String wicketId, RepositoryUrl repoUrl) {
+		Label permissionLabel = new Label(wicketId, repoUrl.isExternal() ? externalPermission : repoUrl.permission.toString());
+		WicketUtils.setPermissionClass(permissionLabel, repoUrl.permission);
+		String tooltip = getProtocolPermissionDescription(repository, repoUrl);
+		WicketUtils.setHtmlTooltip(permissionLabel, tooltip);
+		return permissionLabel;
+	}
+	
+	protected Fragment createCopyFragment(String text) {
+		if (GitBlit.getBoolean(Keys.web.allowFlashCopyToClipboard, true)) {
+			// clippy: flash-based copy & paste
+			Fragment copyFragment = new Fragment("copyFunction", "clippyPanel", this);
+			String baseUrl = WicketUtils.getGitblitURL(getRequest());
+			ShockWaveComponent clippy = new ShockWaveComponent("clippy", baseUrl + "/clippy.swf");
+			clippy.setValue("flashVars", "text=" + StringUtils.encodeURL(text));
+			copyFragment.add(clippy);
+			return copyFragment;
 		} else {
-			note = null;			
-			String key;
-			switch (repoUrl.permission) {
+			// javascript: manual copy & paste with modal browser prompt dialog
+			Fragment copyFragment = new Fragment("copyFunction", "jsPanel", this);
+			ContextImage img = WicketUtils.newImage("copyIcon", "clippy.png");
+			img.add(new JavascriptTextPrompt("onclick", "Copy to Clipboard (Ctrl+C, Enter)", text));
+			copyFragment.add(img);
+			return copyFragment;
+		}
+	}
+	
+	protected String getProtocolPermissionDescription(RepositoryModel repository,
+			RepositoryUrl repoUrl) {
+		if (!urlPermissionsMap.containsKey(repoUrl.url)) {
+			String note;
+			if (repoUrl.isExternal()) {
+				String protocol = repoUrl.url.substring(0, repoUrl.url.indexOf("://"));
+				note = MessageFormat.format(getString("gb.externalPermissions"), protocol);			
+			} else {
+				note = null;			
+				String key;
+				switch (repoUrl.permission) {
 				case OWNER:
 				case REWIND:
 					key = "gb.rewindPermission";
@@ -329,33 +385,39 @@
 					key = null;
 					note = getString("gb.viewAccess");
 					break;
+				}
+
+				if (note == null) {
+					String pattern = getString(key);
+					String description = MessageFormat.format(pattern, repoUrl.permission.toString());
+					note = description;
+				}
 			}
-			
-			if (note == null) {
-				String pattern = getString(key);
-				String description = MessageFormat.format(pattern, repoUrl.permission.toString());
-				String permissionPattern = getString("gb.yourProtocolPermissionIs");
-				note = MessageFormat.format(permissionPattern, protocol.toUpperCase(), repository, description);
-			}
+			urlPermissionsMap.put(repoUrl.url, note);
 		}
-		return note;
+		return urlPermissionsMap.get(repoUrl.url);
 	}
 	
-	private class RepoUrl implements Serializable {
-		
-		private static final long serialVersionUID = 1L;
-		
-		final String url;
-		final AccessPermission permission;
-		
-		RepoUrl(String url, AccessPermission permission) {
-			this.url = url;
-			this.permission = permission;
+	protected Map<AccessRestrictionType, String> getAccessRestrictions() {
+		if (accessRestrictionsMap == null) {
+			accessRestrictionsMap = new HashMap<AccessRestrictionType, String>();
+			for (AccessRestrictionType type : AccessRestrictionType.values()) {
+				switch (type) {
+				case NONE:
+					accessRestrictionsMap.put(type, getString("gb.notRestricted"));
+					break;
+				case PUSH:
+					accessRestrictionsMap.put(type, getString("gb.pushRestricted"));
+					break;
+				case CLONE:
+					accessRestrictionsMap.put(type, getString("gb.cloneRestricted"));
+					break;
+				case VIEW:
+					accessRestrictionsMap.put(type, getString("gb.viewRestricted"));
+					break;
+				}
+			}
 		}
-		
-		@Override
-		public String toString() {
-			return url;
-		}
+		return accessRestrictionsMap;
 	}
 }
diff --git a/src/main/resources/git-black_32x32.png b/src/main/resources/git-black_32x32.png
new file mode 100644
index 0000000..037f61a
--- /dev/null
+++ b/src/main/resources/git-black_32x32.png
Binary files differ
diff --git a/src/main/resources/gitblit.css b/src/main/resources/gitblit.css
index c22793d..cfb6cf7 100644
--- a/src/main/resources/gitblit.css
+++ b/src/main/resources/gitblit.css
@@ -117,6 +117,19 @@
 	color: #ffffff !important;
 }
 
+.btn:first-child {
+	border-radius: 4px;
+}
+
+.btn-appmenu {
+    /*background-color: rgb(73, 175, 205);
+    background-image: -moz-linear-gradient(center top , rgb(91, 192, 222), rgb(47, 150, 180));*/
+    background-color: rgb(73, 175, 205);
+    background-image: -moz-linear-gradient(center top , rgb(91, 192, 222), rgb(47, 150, 180));
+    background-repeat: repeat-x;
+    border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
+}
+
 .breadcrumb {
 	margin-top: 5px !important;
 	margin-bottom: 5px !important;
@@ -179,33 +192,106 @@
 	vertical-align: middle;
 }
 
-span.repositoryUrlContainer {
-	color: black;
-	background-color: whiteSmoke; 
-	padding: 4px;
-	border: 1px solid #ddd;
-	border-radius: 3px 
+div.repositoryUrlContainer {
+	padding: 2px;
+	background-color: #F5F5F5;
+    background-image: -moz-linear-gradient(center top , #FFFFFF, #E6E6E6);
+    background-repeat: repeat-x;
+    border-color: #E6E6E6 #E6E6E6 #B3B3B3;
+    border-image: none;
+    border-radius: 4px;
+    border-style: solid;
+    border-width: 1px;
+    box-shadow: 0 1px 0 rgba(255, 255, 255, 0.2) inset, 0 1px 2px rgba(0, 0, 0, 0.05);
+    color: #333333;    
+    vertical-align: middle;
+    border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
 }
 
-span.repositoryUrlEndCap {	
-	padding: 4px;
+div.repositoryUrlContainer:hover {
+	background-color: #E6E6E6;
+    background-position: 0 -15px;
+    color: #333333;
+    text-decoration: none;
+    transition: background-position 0.1s linear 0s;
+}
+
+div.repositoryUrlContainer:hover .caret {
+    opacity: 1;
+}
+
+div.repositoryUrlContainer:hover a:hover {
+	text-decoration: none;
+}
+
+span.repositoryUrlLeftCap, span.repositoryUrlRightCap {	
+	text-align: center;
+	color: black;
+	padding: 3px;
+	font-size: 11px;
+}
+
+span.repositoryUrlRightCap {	
 	font-weight: bold;
-	font-size: 0.85em;
 	font-family:menlo,consolas,monospace;
 }
 
 span.repositoryUrl {
 	font-size: 1em;
-	padding: 4px;
-	color: blue;
+	padding: 2px 4px 3px 4px;	
 	background-color: #fff;
 	border-left: 1px solid #ddd;
 	border-right: 1px solid #ddd;
 }
 
-span.repositoryUrlMenuItem {
+ul.urlMenu {
+	min-width: 350px;
+}
+
+ul.urlMenu li.url {
+	background-color: white;
+	padding: 0px 5px;
 	line-height: 24px;
-	padding: 3px 15px;
+}
+
+ul.applicationMenu {
+	background-color: whiteSmoke;
+	min-width: 400px;
+}
+
+ul.applicationMenu li.action {
+	background-color: white;
+	padding: 0px 5px;
+	line-height: 24px;
+}
+
+span.applicationTitle, span.applicationTitle a {
+	display: inline;
+	font-weight: bold;
+	font-size:1.1em;
+	color: black !important;
+	padding: 0px;
+}
+
+div.applicationHeaderMenuItem {
+	padding-left: 10px;
+	color: black;
+}
+
+div.applicationLegalMenuItem {
+	padding-left: 10px;
+	color: #999;
+	font-size: 0.85em;
+}
+
+a.applicationMenuItem, span.commandMenuItem {
+	padding: 3px 10px;	
+	color: black;
+	display: inline;
+	padding: 0px;
+}
+
+span.commandMenuItem {
 	font-size: 0.85em;
 	font-family: menlo,consolas,monospace;
 }
diff --git a/src/main/resources/smartgithg_32x32.png b/src/main/resources/smartgithg_32x32.png
new file mode 100644
index 0000000..63d8692
--- /dev/null
+++ b/src/main/resources/smartgithg_32x32.png
Binary files differ
diff --git a/src/main/resources/sourcetree_32x32.png b/src/main/resources/sourcetree_32x32.png
new file mode 100644
index 0000000..a5dd96f
--- /dev/null
+++ b/src/main/resources/sourcetree_32x32.png
Binary files differ

--
Gitblit v1.9.1