From a4d2498b7f94012cfdf481fcf151f8cfd7537a42 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Mon, 23 May 2011 16:46:09 -0400
Subject: [PATCH] User list. Revised home page. Updated Jetty. Secure cookies. Docs.

---
 src/com/gitblit/Build.java                           |    2 
 src/com/gitblit/wicket/pages/RepositoriesPage.java   |  260 ---------------
 .classpath                                           |   10 
 src/com/gitblit/BuildSite.java                       |   24 
 src/com/gitblit/wicket/resources/gitweb-favicon.png  |    0 
 src/com/gitblit/wicket/panels/RepositoriesPanel.java |  274 ++++++++++++++++
 docs/00_setup.mkd                                    |    2 
 docs/01_screenshots.mkd                              |    4 
 docs/01_faq.mkd                                      |   18 
 src/com/gitblit/wicket/pages/EditUserPage.html       |    2 
 src/com/gitblit/wicket/GitBlitWebApp.properties      |    4 
 src/com/gitblit/wicket/WicketUtils.java              |    4 
 src/com/gitblit/GitBlitServer.java                   |   11 
 src/com/gitblit/wicket/pages/RepositoriesPage.html   |   61 ---
 src/com/gitblit/wicket/panels/UsersPanel.html        |   48 ++
 src/com/gitblit/wicket/panels/RepositoriesPanel.html |   88 +++++
 docs/00_index.mkd                                    |   15 
 src/com/gitblit/Constants.java                       |    6 
 src/com/gitblit/wicket/panels/UsersPanel.java        |   46 ++
 docs/screenshots/thumbs/00.png                       |    0 
 distrib/gitblit.properties                           |    2 
 src/com/gitblit/wicket/resources/gitblit.css         |   54 ++
 src/com/gitblit/wicket/resources/user_16x16.png      |    0 
 src/com/gitblit/wicket/resources/add_16x16.png       |    0 
 build.xml                                            |   51 ++
 docs/screenshots/00.png                              |    0 
 docs/screenshots/raw/00.png                          |    0 
 27 files changed, 611 insertions(+), 375 deletions(-)

diff --git a/.classpath b/.classpath
index 5cddd20..4e3fa85 100644
--- a/.classpath
+++ b/.classpath
@@ -18,11 +18,6 @@
 			<attribute name="javadoc_location" value="jar:platform:/resource/gitblit/ext/slf4j-log4j12-1.6.1-javadoc.jar!/"/>
 		</attributes>
 	</classpathentry>
-	<classpathentry kind="lib" path="ext/jetty-all-7.2.2.v20101205.jar" sourcepath="ext/jetty-all-7.2.2.v20101205-sources.jar">
-		<attributes>
-			<attribute name="javadoc_location" value="jar:platform:/resource/gitblit/ext/jetty-all-7.2.2.v20101205-javadoc.jar!/"/>
-		</attributes>
-	</classpathentry>
 	<classpathentry kind="lib" path="ext/jcommander-1.17.jar" sourcepath="ext/jcommander-1.17-sources.jar">
 		<attributes>
 			<attribute name="javadoc_location" value="jar:platform:/resource/gitblit/ext/jcommander-1.17-javadoc.jar!/"/>
@@ -74,5 +69,10 @@
 			<attribute name="javadoc_location" value="jar:platform:/resource/gitblit/ext/bcmail-jdk16-1.46-javadoc.jar!/"/>
 		</attributes>
 	</classpathentry>
+	<classpathentry kind="lib" path="ext/jetty-all-7.4.1.v20110513.jar" sourcepath="ext/jetty-all-7.4.1.v20110513-sources.jar">
+		<attributes>
+			<attribute name="javadoc_location" value="jar:platform:/resource/gitblit/ext/jetty-all-7.2.2.v20101205-javadoc.jar!/"/>
+		</attributes>
+	</classpathentry>
 	<classpathentry kind="output" path="bin"/>
 </classpath>
diff --git a/build.xml b/build.xml
index f47e5e9..a3394c6 100644
--- a/build.xml
+++ b/build.xml
@@ -8,7 +8,12 @@
 
 	<target name="main">
 
-		<!-- extract version number from source code -->
+		<!-- build dsate -->
+		<tstamp>
+			<format property="gb.buildDate" pattern="yyyy-MM-dd" />
+		</tstamp>
+
+		<!-- extract Git:Blit version number from source code -->
 		<loadfile property="gb.version" srcfile="${basedir}/src/com/gitblit/Constants.java">
 			<filterchain>
 				<linecontains>
@@ -17,6 +22,21 @@
 				<striplinebreaks />
 				<tokenfilter>
 					<replacestring from="public final static String VERSION = &quot;" to="" />
+					<replacestring from="&quot;;" to="" />
+					<trim />
+				</tokenfilter>
+			</filterchain>			
+		</loadfile>
+
+		<!-- extract JGit version number from source code -->
+		<loadfile property="jgit.version" srcfile="${basedir}/src/com/gitblit/Constants.java">
+			<filterchain>
+				<linecontains>
+					<contains value="public final static String JGIT_VERSION = " />
+				</linecontains>
+				<striplinebreaks />
+				<tokenfilter>
+					<replacestring from="public final static String JGIT_VERSION = &quot;" to="" />
 					<replacestring from="&quot;;" to="" />
 					<trim />
 				</tokenfilter>
@@ -118,22 +138,22 @@
 				<include name="book_16x16.png" />
 				<include name="blank.png" />
 			</fileset>
-			
+
 			<!-- Copy Doc images -->
 			<fileset dir="${basedir}/docs">
 				<include name="*.png" />
 				<include name="*.js" />
 			</fileset>
 		</copy>
-			
+
 		<!-- Copy Fancybox -->
 		<mkdir dir="${basedir}/site/fancybox" />
-		<copy todir="${basedir}/site/fancybox">			
-			<fileset dir="${basedir}/docs/fancybox" >
+		<copy todir="${basedir}/site/fancybox">
+			<fileset dir="${basedir}/docs/fancybox">
 				<exclude name="thumbs.db" />
 			</fileset>
 		</copy>
-		
+
 		<!-- Copy screenshot thumbnails -->
 		<mkdir dir="${basedir}/site/thumbs" />
 		<copy todir="${basedir}/site/thumbs">
@@ -141,7 +161,7 @@
 				<include name="*.png" />
 			</fileset>
 		</copy>
-		
+
 		<!-- Copy screenshots -->
 		<mkdir dir="${basedir}/site/screenshots" />
 		<copy todir="${basedir}/site/screenshots">
@@ -150,11 +170,12 @@
 			</fileset>
 		</copy>
 
+		<!-- Build site pages -->
 		<java classpath="${project.build.dir}" classname="com.gitblit.BuildSite">
 			<classpath refid="master-classpath" />
 			<arg value="--sourceFolder" />
 			<arg value="${basedir}/docs" />
-			
+
 			<arg value="--outputFolder" />
 			<arg value="${basedir}/site" />
 
@@ -163,9 +184,21 @@
 
 			<arg value="--pageFooter" />
 			<arg value="${basedir}/docs/page_footer.html" />
-			
+
 			<arg value="--alias" />
 			<arg value="index=overview" />
+
+			<arg value="--substitute" />
+			<arg value="%VERSION%=${gb.version}" />
+
+			<arg value="--substitute" />
+			<arg value="%DISTRIBUTION%=${distribution.zipfile}" />
+
+			<arg value="--substitute" />
+			<arg value="%BUILDDATE%=${gb.buildDate}" />
+
+			<arg value="--substitute" />
+			<arg value="%JGIT%=${jgit.version}" />
 		</java>
 
 	</target>
diff --git a/distrib/gitblit.properties b/distrib/gitblit.properties
index de243d2..d0e6375 100644
--- a/distrib/gitblit.properties
+++ b/distrib/gitblit.properties
@@ -69,7 +69,7 @@
 # Choose how to present the repositories list.
 # grouped = group nested/subfolder repositories together (no sorting)
 # flat = flat list of repositories (sorting allowed)
-web.repositoryListType = flat
+web.repositoryListType = grouped
 
 # If using a grouped repository list and there are repositories at the
 # root level of your repositories folder, you may specify the displayed
diff --git a/docs/00_index.mkd b/docs/00_index.mkd
index 6fdc0a1..a43ffdc 100644
--- a/docs/00_index.mkd
+++ b/docs/00_index.mkd
@@ -1,19 +1,17 @@
 ## Overview
 Git:Blit is an open-source, integrated pure Java stack for managing, viewing, and serving [Git][git] repositories.
-Its designed primarily as a tool for small workgroups who want to host [Git][git] repositories on a Windows machine.
-
-Of course, since its pure Java it should run with any JVM on any platform, but there are already [many compelling Git solutions](https://git.wiki.kernel.org/index.php/InterfacesFrontendsAndTools) for non-Windows environments.
+Its designed primarily as a tool for small workgroups who want to host [Git][git] repositories on a Windows machine.  Having said that, it works equally well on any standard Linux distribution.
  
 ### Current Version
 
-[{0}](http://gitblit.com/{1}) based on [{2}][jgit] &nbsp; (*{3}*)
+[%VERSION%](http://gitblit.com/%DISTRIBUTION%) based on [%JGIT%][jgit] &nbsp; (*%BUILDDATE%*)
 
 sources @ [Github][gitbltsrc]
 
 ### Design Principles
 1. [KISS](http://en.wikipedia.org/wiki/KISS_principle)
 2. Offer useful features for serving Git repositories.  If feature is complex, refer to #1.
-3. All dependencies must be retrievable from a publicly accessible Maven repository.<br/>This is to ensure authenticity of dependencies and to keep the Git:Blit distribution svelte.  
+3. All dependencies must be retrievable from a publicly accessible [Maven](http://maven.apache.org) repository.<br/>This is to ensure authenticity of dependencies and to keep the Git:Blit distribution svelte.  
 
 ### Features
 - Out-of-the-box integrated stack requiring minimal configuration
@@ -29,7 +27,7 @@
     </ul>
 - Gitweb inspired UI
 - Administrators may create, edit, rename, or delete repositories through the web UI
-- Administrators may create, edit, rename, or delete users through the web UI
+- Administrators may create, edit, or delete users through the web UI
 - Repository Owners may edit repositories through the web UI
 - Automatically generates a self-signed certificate for https communications
 - Dates can optionally be displayed using the browser''s reported timezone
@@ -53,8 +51,6 @@
 - Git:Blit is an integrated, full-stack solution.  There is no WAR build at this time.
 
 ### Todo List
-- Manual certificate generation with BouncyCastle
-- User list with edit and delete links
 - Review spots where Git:Blit can cache data instead of abusing the disk
     - stats
     - users.properties access
@@ -90,8 +86,9 @@
 
 - [google-code-prettify](http://code.google.com/p/google-code-prettify) (Apache 2.0)
 - [JavaService](http://forge.ow2.org/projects/javaservice) (BSD and LGPL)
-- icons courtesy of [FatCow Hosting](http://www.fatcow.com/free-icons) (Creative Commons CC-BY)
 - magnifying glass search icon courtesy of [Gnome](http://gnome.org) (Creative Commons CC-BY)
+- modified Git logo originally designed by [Henrik Nyh](http://henrik.nyh.se/2007/06/alternative-git-logo-and-favicon)
+- other icons courtesy of [FatCow Hosting](http://www.fatcow.com/free-icons) (Creative Commons CC-BY)
 
 ### Downloaded Dependencies
 The following dependencies are automatically downloaded from the Apache Maven repository and from the Eclipse Maven repository when Git:Blit is launched for the first time.
diff --git a/docs/00_setup.mkd b/docs/00_setup.mkd
index 415ad47..a590b59 100644
--- a/docs/00_setup.mkd
+++ b/docs/00_setup.mkd
@@ -1,6 +1,6 @@
 ## Setup and Configuration
 
-1. Download and unzip Git:Blit.<br/>
+1. Download and unzip [%VERSION%](http://gitblit.com/%DISTRIBUTION%).<br/>
 *Its best to eliminate spaces in the path name as that can cause troubleshooting headaches.* 
 2. The server itself is configured through a simple text file.<br/>
 Open `gitblit.properties` in your favorite text editor and make sure to review and set:
diff --git a/docs/01_faq.mkd b/docs/01_faq.mkd
index 94c6dbf..23ee2b2 100644
--- a/docs/01_faq.mkd
+++ b/docs/01_faq.mkd
@@ -10,10 +10,17 @@
 No.  Git:Blit is based on [JGit][jgit] which is a pure Java implementation of the [Git version control system][git].<br/>
 Everything you need for Git:Blit is either in the zip distribution file or automatically downloaded on execution. 
 
-### Why doesn't Git:Blit support SSH?
-Git:Blit could integrate [Apache Mina](http://mina.apache.org) to provide SSH access.  However, doing so violates design principle #1: KISS.  SSH supports requires creating, exchanging, and managing SSH keys.  While this is doable, its not simple like JGit's SmartHTTP implementation.
+### Does Git:Blit use a database to store its data?
+No.  Git:Blit stores its repository configuration information within the `.git/config` file and its user information in `users.properties` or whatever filename is configured in `gitblit.properties`.
 
-You might consider [Gerrit](http://gerrit.googlecode.org) which supports SSH.
+### I want to deploy Git:Blit into my own servlet container.  Where is the WAR?
+At this time there is no WAR build available.
+
+### Why doesn't Git:Blit support SSH?
+Git:Blit could integrate [Apache Mina][mina] to provide SSH access.  However, doing so violates Git:Blit's first design principle: [KISS](http://en.wikipedia.org/wiki/KISS_principle).<br/>
+SSH supports requires creating, exchanging, and managing SSH keys.  While this is doable, its not simple like JGit's SmartHTTP implementation.
+
+You might consider running [Gerrit](http://gerrit.googlecode.org) which does integrate [Apache Mina][mina] and supports SSH or you might consider serving [Git][git] on Linux which would offer real SSH support and also allow use of [many other compelling Git solutions](https://git.wiki.kernel.org/index.php/InterfacesFrontendsAndTools).
 
 ### What types of Search does Git:Blit support?
 Git:Blit supports case-insensitive searches of *commit message* (default), *author*, and *committer*.<br/>
@@ -26,9 +33,10 @@
 Alternatively, you could enable the search type dropdown list in your `gitblit.properties` file.
 
 ### How do I run Git:Blit on port 80 or 443 in Linux?
-Tricky.  Linux requires root permissions to serve on ports < 1024.<br/>
+Linux requires root permissions to serve on ports < 1024.<br/>
 Run the server as *root* (security concern) or change the ports you are serving to 8080 (http) or 8443 (https). 
 
 [bitblt]: http://en.wikipedia.org/wiki/Bit_blit "Wikipedia Bitblt"
 [jgit]: http://eclipse.org/jgit "Eclipse JGit Site"
-[git]: http://git-scm.com "Official Git Site"
\ No newline at end of file
+[git]: http://git-scm.com "Official Git Site"
+[mina]: http://mina.apache.org " Apache Mina"
\ No newline at end of file
diff --git a/docs/01_screenshots.mkd b/docs/01_screenshots.mkd
index 3d2061e..14ec179 100644
--- a/docs/01_screenshots.mkd
+++ b/docs/01_screenshots.mkd
@@ -1,8 +1,8 @@
 ## Screenshots
 <table class="screenshots">
 <tr><td>
-	<a rel="screenshots_group" href="screenshots/00.png" title="Repository List">![Repositories](thumbs/00.png)</a>
-	<br/>Repository List
+	<a rel="screenshots_group" href="screenshots/00.png" title="Repository & User List">![Repositories](thumbs/00.png)</a>
+	<br/>Repository & User List
 </td><td>
 	<a rel="screenshots_group" href="screenshots/01.png" title="New User">![New User](thumbs/01.png)</a>
 	<br/>New User
diff --git a/docs/screenshots/00.png b/docs/screenshots/00.png
index 616b31c..c9653f1 100644
--- a/docs/screenshots/00.png
+++ b/docs/screenshots/00.png
Binary files differ
diff --git a/docs/screenshots/raw/00.png b/docs/screenshots/raw/00.png
index 314f521..eb50149 100644
--- a/docs/screenshots/raw/00.png
+++ b/docs/screenshots/raw/00.png
Binary files differ
diff --git a/docs/screenshots/thumbs/00.png b/docs/screenshots/thumbs/00.png
index 6aea693..636631f 100644
--- a/docs/screenshots/thumbs/00.png
+++ b/docs/screenshots/thumbs/00.png
Binary files differ
diff --git a/src/com/gitblit/Build.java b/src/com/gitblit/Build.java
index 8952726..1d2c0f1 100644
--- a/src/com/gitblit/Build.java
+++ b/src/com/gitblit/Build.java
@@ -268,7 +268,7 @@
 
 		public static final MavenObject JCOMMANDER = new MavenObject("jCommander", "com/beust", "jcommander", "1.17", 34000, 32000, 141000, "219a3540f3b27d7cc3b1d91d6ea046cd8723290e", "0bb50eec177acf0e94d58e0cf07262fe5164331d", "c7adc475ca40c288c93054e0f4fe58f3a98c0cb5");
 
-		public static final MavenObject JETTY = new MavenObject("Jetty", "org/eclipse/jetty/aggregate", "jetty-all", "7.2.2.v20101205", 1430000, 965000, 3871000, "b9b7c812a732721c427e208c54fbb71ca17a2ee1", "cbc4fc72c4a646d8822bf7369c2101d4d5d1ff98", "34c87e11bba426fe97bfe23ccff12eda477c8f57");
+		public static final MavenObject JETTY = new MavenObject("Jetty", "org/eclipse/jetty/aggregate", "jetty-all", "7.4.1.v20110513", 1500000, 1000000, 4100000, "1e2de9ed25a7c6ae38717d5ffdc7cfcd6be4bd46", "7b6279d16ce8f663537d9faf55ea353e748dbbaa", "fa06212e751296f1a7abc15c843b135bf49a112b");
 
 		public static final MavenObject SERVLET = new MavenObject("Servlet 2.5", "javax/servlet", "servlet-api", "2.5", 105000, 158000, 0, "5959582d97d8b61f4d154ca9e495aafd16726e34", "021599814ad9a605b86f3e6381571beccd861a32", null);
 
diff --git a/src/com/gitblit/BuildSite.java b/src/com/gitblit/BuildSite.java
index 6ea8048..dc42cf5 100644
--- a/src/com/gitblit/BuildSite.java
+++ b/src/com/gitblit/BuildSite.java
@@ -46,11 +46,11 @@
 		Arrays.sort(markdownFiles);
 
 		Map<String, String> aliasMap = new HashMap<String, String>();
-		for (String alias:params.aliases) {
-			String [] values = alias.split("=");
+		for (String alias : params.aliases) {
+			String[] values = alias.split("=");
 			aliasMap.put(values[0], values[1]);
 		}
-		
+
 		System.out.println(MessageFormat.format("Generating site from {0} Markdown Docs in {1} ", markdownFiles.length, sourceFolder.getAbsolutePath()));
 		String linkPattern = "<a href=''{0}''>{1}</a>";
 		StringBuilder sb = new StringBuilder();
@@ -66,7 +66,7 @@
 		}
 		sb.setLength(sb.length() - 3);
 		sb.trimToSize();
-		
+
 		String html_header = readContent(new File(params.pageHeader));
 		String html_footer = readContent(new File(params.pageFooter));
 		final String links = sb.toString();
@@ -76,16 +76,13 @@
 		for (File file : markdownFiles) {
 			try {
 				String documentName = getDocumentName(file);
-				String displayName = documentName;
-				if (aliasMap.containsKey(documentName)) {
-					displayName = aliasMap.get(documentName);
-				}
 				String fileName = documentName + ".html";
 				System.out.println(MessageFormat.format("  {0} => {1}", file.getName(), fileName));
 				InputStreamReader reader = new InputStreamReader(new FileInputStream(file), Charset.forName("UTF-8"));
 				String content = MarkdownUtils.transformMarkdown(reader);
-				if (displayName.equalsIgnoreCase("overview")) {
-					content = MessageFormat.format(content, Constants.VERSION, "gitblit-" + Constants.VERSION + ".zip", Constants.getJGitVersion(), date);
+				for (String token : params.substitutions) {
+					String [] kv = token.split("=");
+					content = content.replace(kv[0], kv[1]);
 				}
 				OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(new File(destinationFolder, fileName)), Charset.forName("UTF-8"));
 				writer.write(header);
@@ -122,7 +119,7 @@
 		// trim leading ##_ which is to control display order
 		return displayName.substring(3);
 	}
-	
+
 	private static void usage(JCommander jc, ParameterException t) {
 		System.out.println(Constants.getRunningVersion());
 		System.out.println();
@@ -135,7 +132,7 @@
 		}
 		System.exit(0);
 	}
-	
+
 	@Parameters(separators = " ")
 	private static class Params {
 
@@ -154,5 +151,8 @@
 		@Parameter(names = { "--alias" }, description = "Filename=Linkname aliases", required = false)
 		public List<String> aliases = new ArrayList<String>();
 
+		@Parameter(names = { "--substitute" }, description = "@TOKEN@=value", required = false)
+		public List<String> substitutions = new ArrayList<String>();
+
 	}
 }
diff --git a/src/com/gitblit/Constants.java b/src/com/gitblit/Constants.java
index 7e19cef..46f3208 100644
--- a/src/com/gitblit/Constants.java
+++ b/src/com/gitblit/Constants.java
@@ -10,6 +10,10 @@
 	// and only use A-Z a-z 0-9 .-_ in the string. 
 	public final static String VERSION = "0.1.0-SNAPSHOT";
 
+	// The build script extracts this exact line so be careful editing it
+	// and only use A-Z a-z 0-9 .-_ in the string.
+	public final static String JGIT_VERSION = "JGit 0.12.1";
+
 	public final static String ADMIN_ROLE = "#admin";
 
 	public final static String PROPERTIES_FILE = "gitblit.properties";
@@ -44,7 +48,7 @@
 	}
 
 	public static String getJGitVersion() {
-		return "JGit 0.12.1";
+		return JGIT_VERSION;
 	}
 
 	public static String getRunningVersion() {
diff --git a/src/com/gitblit/GitBlitServer.java b/src/com/gitblit/GitBlitServer.java
index 08c9b29..e9e4463 100644
--- a/src/com/gitblit/GitBlitServer.java
+++ b/src/com/gitblit/GitBlitServer.java
@@ -29,6 +29,7 @@
 import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.bio.SocketConnector;
 import org.eclipse.jetty.server.nio.SelectChannelConnector;
+import org.eclipse.jetty.server.session.HashSessionManager;
 import org.eclipse.jetty.server.ssl.SslConnector;
 import org.eclipse.jetty.server.ssl.SslSelectChannelConnector;
 import org.eclipse.jetty.server.ssl.SslSocketConnector;
@@ -192,6 +193,16 @@
 		rootContext.setServer(server);
 		rootContext.setWar(location.toExternalForm());
 		rootContext.setTempDirectory(tempDir);
+		
+		// Mark all cookies HttpOnly so they are not accessible to JavaScript
+		// engines.
+		// http://erlend.oftedal.no/blog/?blogid=33
+		// https://www.owasp.org/index.php/HttpOnly#Browsers_Supporting_HttpOnly
+		HashSessionManager sessionManager = new HashSessionManager();
+		sessionManager.setHttpOnly(true);
+		// Use secure cookies if only serving https
+		sessionManager.setSecureCookies(params.port <= 0 && params.securePort > 0);
+		rootContext.getSessionHandler().setSessionManager(sessionManager);
 
 		// Wicket Filter
 		String wicketPathSpec = "/*";
diff --git a/src/com/gitblit/wicket/GitBlitWebApp.properties b/src/com/gitblit/wicket/GitBlitWebApp.properties
index d07f0bc..b6dbc11 100644
--- a/src/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/com/gitblit/wicket/GitBlitWebApp.properties
@@ -43,8 +43,8 @@
 gb.blame = blame
 gb.login = Login
 gb.logout = Logout
-gb.username = Username
-gb.password = Password
+gb.username = username
+gb.password = password
 gb.tagger = tagger
 gb.moreHistory = more history...
 gb.difftocurrent = diff to current
diff --git a/src/com/gitblit/wicket/WicketUtils.java b/src/com/gitblit/wicket/WicketUtils.java
index f0ccbf4..761595b 100644
--- a/src/com/gitblit/wicket/WicketUtils.java
+++ b/src/com/gitblit/wicket/WicketUtils.java
@@ -153,6 +153,10 @@
 		return new ContextRelativeResource("/com/gitblit/wicket/resources/" + file);
 	}
 
+	public static PageParameters newUsernameParameter(String username) {
+		return new PageParameters("user=" + username);
+	}
+
 	public static PageParameters newRepositoryParameter(String repositoryName) {
 		return new PageParameters("r=" + repositoryName);
 	}
diff --git a/src/com/gitblit/wicket/pages/EditUserPage.html b/src/com/gitblit/wicket/pages/EditUserPage.html
index c50bdba..a27b712 100644
--- a/src/com/gitblit/wicket/pages/EditUserPage.html
+++ b/src/com/gitblit/wicket/pages/EditUserPage.html
@@ -15,7 +15,7 @@
 	<form wicket:id="editForm">
 		<table class="plain">
 			<tbody>
-				<tr><th><wicket:message key="gb.name"></wicket:message></th><td class="edit"><input type="text" wicket:id="username" id="username" size="30" tabindex="1" /></td></tr>
+				<tr><th><wicket:message key="gb.username"></wicket:message></th><td class="edit"><input type="text" wicket:id="username" id="username" size="30" tabindex="1" /></td></tr>
 				<tr><th><wicket:message key="gb.password"></wicket:message></th><td class="edit"><input type="password" wicket:id="password" size="30" tabindex="2" /></td></tr>
 				<tr><th><wicket:message key="gb.confirmPassword"></wicket:message></th><td class="edit"><input type="password" wicket:id="confirmPassword" size="30" tabindex="3" /></td></tr>
 				<tr><th><wicket:message key="gb.canAdmin"></wicket:message></th><td class="edit"><input type="checkbox" wicket:id="canAdmin" tabindex="6" /> &nbsp;<i><wicket:message key="gb.canAdminDescription"></wicket:message></i></td></tr>				
diff --git a/src/com/gitblit/wicket/pages/RepositoriesPage.html b/src/com/gitblit/wicket/pages/RepositoriesPage.html
index d00c498..da91cb2 100644
--- a/src/com/gitblit/wicket/pages/RepositoriesPage.html
+++ b/src/com/gitblit/wicket/pages/RepositoriesPage.html
@@ -18,67 +18,10 @@
 	
 	<div class="markdown" style="margin-top:-0.5em;padding-bottom:5px;" wicket:id="repositoriesMessage">[repositories message]</div>
 	
-	<div wicket:id="adminPanel">[admin links]</div>
-		
-	<table class="repositories">
-		<span wicket:id="headerContent"></span>
-		<tbody>		
-       		<tr wicket:id="row">
-       			<span wicket:id="rowContent"></span>
-       		</tr>
-    	</tbody>
-	</table>
-	
-	<wicket:fragment wicket:id="adminLinks">
-		<!-- page nav links -->	
-		<div style="text-align: right;" class="admin_nav">
-			<a wicket:id="newRepository"><wicket:message key="gb.newRepository"></wicket:message></a> | <a wicket:id="newUser"><wicket:message key="gb.newUser"></wicket:message></a> | <a wicket:id="editUsers"><wicket:message key="gb.editUsers"></wicket:message></a>
-		</div>	
-	</wicket:fragment>
-	
-	<wicket:fragment wicket:id="repositoryAdminLinks">
-		<span class="link"><a wicket:id="editRepository"><wicket:message key="gb.edit">[edit]</wicket:message></a> | <a wicket:id="renameRepository"><wicket:message key="gb.rename">[rename]</wicket:message></a> | <a wicket:id="deleteRepository"><wicket:message key="gb.delete">[delete]</wicket:message></a></span>
-	</wicket:fragment>
+	<div wicket:id="repositoriesPanel">[repositories panel]</div>
 
-	<wicket:fragment wicket:id="repositoryOwnerLinks">
-		<span class="link"><a wicket:id="editRepository"><wicket:message key="gb.edit">[edit]</wicket:message></a></span>
-	</wicket:fragment>
-
-	<wicket:fragment wicket:id="flatHeader">
-		<tr>
-			<th wicket:id="orderByRepository"><wicket:message key="gb.repository">Repository</wicket:message></th>
-			<th wicket:id="orderByDescription"><wicket:message key="gb.description">Description</wicket:message></th>
-			<th wicket:id="orderByOwner"><wicket:message key="gb.owner">Owner</wicket:message></th>
-			<th></th>
-			<th wicket:id="orderByDate"><wicket:message key="gb.lastChange">Last Change</wicket:message></th>
-			<th></th>
-		</tr>
-	</wicket:fragment>
-	
-	<wicket:fragment wicket:id="groupHeader">
-		<tr>
-			<th><wicket:message key="gb.repository">Repository</wicket:message></th>
-			<th><wicket:message key="gb.description">Description</wicket:message></th>
-			<th><wicket:message key="gb.owner">Owner</wicket:message></th>
-			<th></th>
-			<th><wicket:message key="gb.lastChange">Last Change</wicket:message></th>
-			<th></th>
-		</tr>
-	</wicket:fragment>
-	
-	<wicket:fragment wicket:id="groupRow">
-        <td colspan="6"><span wicket:id="groupName">[group name]</span></td>
-	</wicket:fragment>
+	<div style="padding-top: 10px;"wicket:id="usersPanel">[users panel]</div>
 		
-	<wicket:fragment wicket:id="repositoryRow">
-        <td><div class="list" wicket:id="repositoryName">[repository name]</div></td>
-        <td><div class="list" wicket:id="repositoryDescription">[repository description]</div></td>
-        <td class="author"><span wicket:id="repositoryOwner">[repository owner]</span></td>
-        <td style="text-align: right;padding-right:10px;"><img class="inlineIcon" wicket:id="ticketsIcon" /><img class="inlineIcon" wicket:id="docsIcon" /><img class="inlineIcon" wicket:id="frozenIcon" /><img class="inlineIcon" wicket:id="accessRestrictionIcon" /></td>
-        <td><span wicket:id="repositoryLastChange">[last change]</span></td>
-        <td class="rightAlign"><span wicket:id="repositoryLinks"></span></td>
-	</wicket:fragment>
-	
 </wicket:extend>
 </body>
 </html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/pages/RepositoriesPage.java b/src/com/gitblit/wicket/pages/RepositoriesPage.java
index 14a5426..32552f7 100644
--- a/src/com/gitblit/wicket/pages/RepositoriesPage.java
+++ b/src/com/gitblit/wicket/pages/RepositoriesPage.java
@@ -4,50 +4,27 @@
 import java.io.FileReader;
 import java.io.InputStream;
 import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
 
 import org.apache.wicket.Component;
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.extensions.markup.html.repeater.data.sort.OrderByBorder;
-import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
-import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
 import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.link.BookmarkablePageLink;
-import org.apache.wicket.markup.html.panel.Fragment;
-import org.apache.wicket.markup.repeater.Item;
-import org.apache.wicket.markup.repeater.data.DataView;
-import org.apache.wicket.markup.repeater.data.IDataProvider;
-import org.apache.wicket.markup.repeater.data.ListDataProvider;
-import org.apache.wicket.model.IModel;
-import org.apache.wicket.model.Model;
 import org.apache.wicket.resource.ContextRelativeResource;
 
-import com.gitblit.Constants.AccessRestrictionType;
 import com.gitblit.GitBlit;
 import com.gitblit.Keys;
 import com.gitblit.utils.MarkdownUtils;
 import com.gitblit.utils.StringUtils;
-import com.gitblit.utils.TimeUtils;
 import com.gitblit.wicket.BasePage;
 import com.gitblit.wicket.GitBlitWebSession;
-import com.gitblit.wicket.LinkPanel;
 import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.models.RepositoryModel;
-import com.gitblit.wicket.models.UserModel;
+import com.gitblit.wicket.panels.RepositoriesPanel;
+import com.gitblit.wicket.panels.UsersPanel;
 
 public class RepositoriesPage extends BasePage {
 
 	public RepositoriesPage() {
 		super();
 		setupPage("", "");
-		
+
 		final boolean showAdmin;
 		if (GitBlit.self().settings().getBoolean(Keys.web.authenticateAdminPages, true)) {
 			boolean allowAdmin = GitBlit.self().settings().getBoolean(Keys.web.allowAdministration, false);
@@ -64,12 +41,6 @@
 				setStatelessHint(true);
 			}
 		}
-
-		Fragment adminLinks = new Fragment("adminPanel", "adminLinks", this);
-		adminLinks.add(new BookmarkablePageLink<Void>("newRepository", EditRepositoryPage.class));
-		adminLinks.add(new BookmarkablePageLink<Void>("newUser", EditUserPage.class));
-		adminLinks.add(new BookmarkablePageLink<Void>("editUsers", RepositoriesPage.class));
-		add(adminLinks.setVisible(showAdmin));
 
 		// display an error message cached from a redirect
 		String cachedMessage = GitBlitWebSession.get().clearErrorMessage();
@@ -111,228 +82,7 @@
 		}
 		Component repositoriesMessage = new Label("repositoriesMessage", message).setEscapeModelStrings(false);
 		add(repositoriesMessage);
-
-		final Map<AccessRestrictionType, String> accessRestrictionTranslations = getAccessRestrictions();
-		final UserModel user = GitBlitWebSession.get().getUser();
-		List<RepositoryModel> models = GitBlit.self().getRepositoryModels(user);
-		IDataProvider<RepositoryModel> dp;
-		
-		if (GitBlit.self().settings().getString(Keys.web.repositoryListType, "flat").equalsIgnoreCase("grouped")) {
-			Map<String, List<RepositoryModel>> groups = new HashMap<String, List<RepositoryModel>>();
-			for (RepositoryModel model : models) {
-				String rootPath = StringUtils.getRootPath(model.name);
-				if (StringUtils.isEmpty(rootPath)) {
-					rootPath = GitBlit.self().settings().getString(Keys.web.repositoryRootGroupName, " ");
-				}
-				if (!groups.containsKey(rootPath)) {
-					groups.put(rootPath, new ArrayList<RepositoryModel>());
-				}
-				groups.get(rootPath).add(model);
-			}
-			List<String> roots = new ArrayList<String>(groups.keySet());
-			Collections.sort(roots);
-			List<RepositoryModel> groupedModels = new ArrayList<RepositoryModel>();
-			for (String root : roots) {
-				groupedModels.add(new GroupRepositoryModel(root));
-				groupedModels.addAll(groups.get(root));
-			}
-			dp = new ListDataProvider<RepositoryModel>(groupedModels);
-		} else {
-			dp = new DataProvider(models);
-		}
-
-		DataView<RepositoryModel> dataView = new DataView<RepositoryModel>("row", dp) {
-			private static final long serialVersionUID = 1L;
-			int counter = 0;
-
-			public void populateItem(final Item<RepositoryModel> item) {
-				final RepositoryModel entry = item.getModelObject();
-				if (entry instanceof GroupRepositoryModel) {
-					Fragment row = new Fragment("rowContent", "groupRow", this);
-					item.add(row);
-					row.add(new Label("groupName", entry.name));
-					WicketUtils.setCssClass(item, "group");
-					return;
-				}
-				Fragment row = new Fragment("rowContent", "repositoryRow", this);
-				item.add(row);
-				if (entry.hasCommits) {
-					// Existing repository
-					PageParameters pp = WicketUtils.newRepositoryParameter(entry.name);
-					row.add(new LinkPanel("repositoryName", "list", entry.name, SummaryPage.class, pp));
-					row.add(new LinkPanel("repositoryDescription", "list", entry.description, SummaryPage.class, pp));
-				} else {
-					// New repository
-					row.add(new Label("repositoryName", entry.name + "<span class='empty'>(empty)</span>").setEscapeModelStrings(false));
-					row.add(new Label("repositoryDescription", entry.description));
-				}
-
-				if (entry.useTickets) {
-					row.add(WicketUtils.newImage("ticketsIcon", "bug_16x16.png", getString("gb.tickets")));
-				} else {
-					row.add(WicketUtils.newBlankImage("ticketsIcon"));
-				}
-
-				if (entry.useDocs) {
-					row.add(WicketUtils.newImage("docsIcon", "book_16x16.png", getString("gb.docs")));
-				} else {
-					row.add(WicketUtils.newBlankImage("docsIcon"));
-				}
-
-				if (entry.isFrozen) {
-					row.add(WicketUtils.newImage("frozenIcon", "cold_16x16.png", getString("gb.isFrozen")));
-				} else {
-					row.add(WicketUtils.newClearPixel("frozenIcon").setVisible(false));
-				}
-				switch (entry.accessRestriction) {
-				case NONE:
-					row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
-					break;
-				case PUSH:
-					row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png", accessRestrictionTranslations.get(entry.accessRestriction)));
-					break;
-				case CLONE:
-					row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png", accessRestrictionTranslations.get(entry.accessRestriction)));
-					break;
-				case VIEW:
-					row.add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png", accessRestrictionTranslations.get(entry.accessRestriction)));
-					break;
-				default:
-					row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
-				}
-
-				row.add(new Label("repositoryOwner", entry.owner));
-
-				String lastChange = TimeUtils.timeAgo(entry.lastChange);
-				Label lastChangeLabel = new Label("repositoryLastChange", lastChange);
-				row.add(lastChangeLabel);
-				WicketUtils.setCssClass(lastChangeLabel, TimeUtils.timeAgoCss(entry.lastChange));
-
-				boolean showOwner = user != null && user.getUsername().equalsIgnoreCase(entry.owner);				
-				if (showAdmin) {
-					Fragment repositoryLinks = new Fragment("repositoryLinks", "repositoryAdminLinks", this);
-					repositoryLinks.add(new BookmarkablePageLink<Void>("editRepository", EditRepositoryPage.class, WicketUtils.newRepositoryParameter(entry.name)));
-					repositoryLinks.add(new BookmarkablePageLink<Void>("renameRepository", EditRepositoryPage.class, WicketUtils.newRepositoryParameter(entry.name)).setEnabled(false));
-					repositoryLinks.add(new BookmarkablePageLink<Void>("deleteRepository", EditRepositoryPage.class, WicketUtils.newRepositoryParameter(entry.name)).setEnabled(false));
-					row.add(repositoryLinks);
-				} else if (showOwner) {
-					Fragment repositoryLinks = new Fragment("repositoryLinks", "repositoryOwnerLinks", this);
-					repositoryLinks.add(new BookmarkablePageLink<Void>("editRepository", EditRepositoryPage.class, WicketUtils.newRepositoryParameter(entry.name)));
-					row.add(repositoryLinks);
-				} else {
-					row.add(new Label("repositoryLinks"));
-				}
-				WicketUtils.setAlternatingBackground(item, counter);
-				counter++;
-			}
-		};
-		add(dataView);
-
-		if (dp instanceof SortableDataProvider<?>) {
-			// add sortable header
-			SortableDataProvider<?> sdp = (SortableDataProvider<?>) dp;
-			Fragment fragment = new Fragment("headerContent", "flatHeader", this);
-			fragment.add(newSort("orderByRepository", SortBy.repository, sdp, dataView));
-			fragment.add(newSort("orderByDescription", SortBy.description, sdp, dataView));
-			fragment.add(newSort("orderByOwner", SortBy.owner, sdp, dataView));
-			fragment.add(newSort("orderByDate", SortBy.date, sdp, dataView));
-			add(fragment);
-		} else {
-			// not sortable
-			Fragment fragment = new Fragment("headerContent", "groupHeader", this);
-			add(fragment);
-		}
-	}
-
-	protected enum SortBy {
-		repository, description, owner, date;
-	}
-
-	protected OrderByBorder newSort(String wicketId, SortBy field, SortableDataProvider<?> dp, final DataView<?> dataView) {
-		return new OrderByBorder(wicketId, field.name(), dp) {
-			private static final long serialVersionUID = 1L;
-
-			@Override
-			protected void onSortChanged() {
-				dataView.setCurrentPage(0);
-			}
-		};
-	}
-
-	private class DataProvider extends SortableDataProvider<RepositoryModel> {
-		private static final long serialVersionUID = 1L;
-		private List<RepositoryModel> list = null;
-
-		protected DataProvider(List<RepositoryModel> list) {
-			this.list = list;
-			setSort(SortBy.date.name(), false);
-		}
-
-		@Override
-		public int size() {
-			if (list == null)
-				return 0;
-			return list.size();
-		}
-
-		@Override
-		public IModel<RepositoryModel> model(RepositoryModel header) {
-			return new Model<RepositoryModel>(header);
-		}
-
-		@Override
-		public Iterator<RepositoryModel> iterator(int first, int count) {
-			SortParam sp = getSort();
-			String prop = sp.getProperty();
-			final boolean asc = sp.isAscending();
-
-			if (prop == null || prop.equals(SortBy.date.name())) {
-				Collections.sort(list, new Comparator<RepositoryModel>() {
-					@Override
-					public int compare(RepositoryModel o1, RepositoryModel o2) {
-						if (asc)
-							return o1.lastChange.compareTo(o2.lastChange);
-						return o2.lastChange.compareTo(o1.lastChange);
-					}
-				});
-			} else if (prop.equals(SortBy.repository.name())) {
-				Collections.sort(list, new Comparator<RepositoryModel>() {
-					@Override
-					public int compare(RepositoryModel o1, RepositoryModel o2) {
-						if (asc)
-							return o1.name.compareTo(o2.name);
-						return o2.name.compareTo(o1.name);
-					}
-				});
-			} else if (prop.equals(SortBy.owner.name())) {
-				Collections.sort(list, new Comparator<RepositoryModel>() {
-					@Override
-					public int compare(RepositoryModel o1, RepositoryModel o2) {
-						if (asc)
-							return o1.owner.compareTo(o2.owner);
-						return o2.owner.compareTo(o1.owner);
-					}
-				});
-			} else if (prop.equals(SortBy.description.name())) {
-				Collections.sort(list, new Comparator<RepositoryModel>() {
-					@Override
-					public int compare(RepositoryModel o1, RepositoryModel o2) {
-						if (asc)
-							return o1.description.compareTo(o2.description);
-						return o2.description.compareTo(o1.description);
-					}
-				});
-			}
-			return list.subList(first, first + count).iterator();
-		}
-	}
-
-	private class GroupRepositoryModel extends RepositoryModel {
-
-		private static final long serialVersionUID = 1L;
-
-		GroupRepositoryModel(String name) {
-			super(name, "", "", new Date(0));
-		}
+		add(new RepositoriesPanel("repositoriesPanel", showAdmin, getAccessRestrictions()));		
+		add(new UsersPanel("usersPanel", showAdmin).setVisible(showAdmin));
 	}
 }
diff --git a/src/com/gitblit/wicket/panels/RepositoriesPanel.html b/src/com/gitblit/wicket/panels/RepositoriesPanel.html
new file mode 100644
index 0000000..a066b58
--- /dev/null
+++ b/src/com/gitblit/wicket/panels/RepositoriesPanel.html
@@ -0,0 +1,88 @@
+<!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"> 
+
+<body>
+<wicket:panel>
+
+	<div wicket:id="adminPanel">[admin links]</div>
+
+	<table class="repositories">
+		<span wicket:id="headerContent"></span>
+		<tbody>		
+       		<tr wicket:id="row">
+       			<span wicket:id="rowContent"></span>
+       		</tr>
+    	</tbody>
+	</table>
+	
+	<wicket:fragment wicket:id="adminLinks">
+		<!-- page nav links -->	
+		<div class="admin_nav">
+			<wicket:link>
+   				<img style="vertical-align: top;" src="/com/gitblit/wicket/resources/add_16x16.png"/>
+  			</wicket:link>	
+			<a wicket:id="newRepository">
+				<wicket:message key="gb.newRepository"></wicket:message>
+			</a>
+		</div>	
+	</wicket:fragment>
+	
+	<wicket:fragment wicket:id="repositoryAdminLinks">
+		<span class="link"><a wicket:id="editRepository"><wicket:message key="gb.edit">[edit]</wicket:message></a> | <a wicket:id="renameRepository"><wicket:message key="gb.rename">[rename]</wicket:message></a> | <a wicket:id="deleteRepository"><wicket:message key="gb.delete">[delete]</wicket:message></a></span>
+	</wicket:fragment>
+
+	<wicket:fragment wicket:id="repositoryOwnerLinks">
+		<span class="link"><a wicket:id="editRepository"><wicket:message key="gb.edit">[edit]</wicket:message></a></span>
+	</wicket:fragment>
+
+	<wicket:fragment wicket:id="flatRepositoryHeader">
+		<tr>
+			<th class="left" wicket:id="orderByRepository">
+				<wicket:link>
+   					<img style="vertical-align: top; border: 1px solid #888;" src="/com/gitblit/wicket/resources/gitweb-favicon.png"/>
+  				</wicket:link>
+				<wicket:message key="gb.repository">Repository</wicket:message>
+			</th>
+			<th wicket:id="orderByDescription"><wicket:message key="gb.description">Description</wicket:message></th>
+			<th wicket:id="orderByOwner"><wicket:message key="gb.owner">Owner</wicket:message></th>
+			<th></th>
+			<th wicket:id="orderByDate"><wicket:message key="gb.lastChange">Last Change</wicket:message></th>
+			<th clas="right"></th>
+		</tr>
+	</wicket:fragment>
+	
+	<wicket:fragment wicket:id="groupRepositoryHeader">
+		<tr>
+			<th class="left">
+				<wicket:link>
+   					<img style="vertical-align: top; border: 1px solid #888;" src="/com/gitblit/wicket/resources/gitweb-favicon.png"/>
+  				</wicket:link>
+				<wicket:message key="gb.repository">Repository</wicket:message>
+			</th>
+			<th><wicket:message key="gb.description">Description</wicket:message></th>
+			<th><wicket:message key="gb.owner">Owner</wicket:message></th>
+			<th></th>
+			<th><wicket:message key="gb.lastChange">Last Change</wicket:message></th>
+			<th class="right"></th>
+		</tr>
+	</wicket:fragment>
+	
+	<wicket:fragment wicket:id="groupRepositoryRow">
+        <td colspan="6"><span wicket:id="groupName">[group name]</span></td>
+	</wicket:fragment>
+		
+	<wicket:fragment wicket:id="repositoryRow">
+        <td class="left"><div class="list" wicket:id="repositoryName">[repository name]</div></td>
+        <td><div class="list" wicket:id="repositoryDescription">[repository description]</div></td>
+        <td class="author"><span wicket:id="repositoryOwner">[repository owner]</span></td>
+        <td style="text-align: right;padding-right:10px;"><img class="inlineIcon" wicket:id="ticketsIcon" /><img class="inlineIcon" wicket:id="docsIcon" /><img class="inlineIcon" wicket:id="frozenIcon" /><img class="inlineIcon" wicket:id="accessRestrictionIcon" /></td>
+        <td><span wicket:id="repositoryLastChange">[last change]</span></td>
+        <td class="rightAlign"><span wicket:id="repositoryLinks"></span></td>
+	</wicket:fragment>
+	
+</wicket:panel>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/panels/RepositoriesPanel.java b/src/com/gitblit/wicket/panels/RepositoriesPanel.java
new file mode 100644
index 0000000..6d05888
--- /dev/null
+++ b/src/com/gitblit/wicket/panels/RepositoriesPanel.java
@@ -0,0 +1,274 @@
+package com.gitblit.wicket.panels;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.extensions.markup.html.repeater.data.sort.OrderByBorder;
+import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
+import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.panel.Fragment;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.IDataProvider;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+
+import com.gitblit.Constants.AccessRestrictionType;
+import com.gitblit.GitBlit;
+import com.gitblit.Keys;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.utils.TimeUtils;
+import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.LinkPanel;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.models.RepositoryModel;
+import com.gitblit.wicket.models.UserModel;
+import com.gitblit.wicket.pages.EditRepositoryPage;
+import com.gitblit.wicket.pages.SummaryPage;
+
+
+public class RepositoriesPanel extends BasePanel {
+
+	private static final long serialVersionUID = 1L;
+	
+	public RepositoriesPanel(String wicketId, final boolean showAdmin, final Map<AccessRestrictionType, String> accessRestrictionTranslations) {
+		super(wicketId);
+		
+		final UserModel user = GitBlitWebSession.get().getUser();
+		List<RepositoryModel> models = GitBlit.self().getRepositoryModels(user);
+		IDataProvider<RepositoryModel> dp;
+		
+		Fragment adminLinks = new Fragment("adminPanel", "adminLinks", this);
+		adminLinks.add(new BookmarkablePageLink<Void>("newRepository", EditRepositoryPage.class));
+		add(adminLinks.setVisible(showAdmin));
+		
+		if (GitBlit.self().settings().getString(Keys.web.repositoryListType, "flat").equalsIgnoreCase("grouped")) {
+			Map<String, List<RepositoryModel>> groups = new HashMap<String, List<RepositoryModel>>();
+			for (RepositoryModel model : models) {
+				String rootPath = StringUtils.getRootPath(model.name);
+				if (StringUtils.isEmpty(rootPath)) {
+					rootPath = GitBlit.self().settings().getString(Keys.web.repositoryRootGroupName, " ");
+				}
+				if (!groups.containsKey(rootPath)) {
+					groups.put(rootPath, new ArrayList<RepositoryModel>());
+				}
+				groups.get(rootPath).add(model);
+			}
+			List<String> roots = new ArrayList<String>(groups.keySet());
+			Collections.sort(roots);
+			List<RepositoryModel> groupedModels = new ArrayList<RepositoryModel>();
+			for (String root : roots) {
+				List<RepositoryModel> subModels = groups.get(root);
+				groupedModels.add(new GroupRepositoryModel(root + " (" + subModels.size() + ")"));
+				groupedModels.addAll(subModels);
+			}
+			dp = new ListDataProvider<RepositoryModel>(groupedModels);
+		} else {
+			dp = new DataProvider(models);
+		}
+
+		DataView<RepositoryModel> dataView = new DataView<RepositoryModel>("row", dp) {
+			private static final long serialVersionUID = 1L;
+			int counter = 0;
+
+			public void populateItem(final Item<RepositoryModel> item) {
+				final RepositoryModel entry = item.getModelObject();
+				if (entry instanceof GroupRepositoryModel) {
+					Fragment row = new Fragment("rowContent", "groupRepositoryRow", this);
+					item.add(row);
+					row.add(new Label("groupName", entry.name));
+					WicketUtils.setCssClass(item, "group");
+					return;
+				}
+				Fragment row = new Fragment("rowContent", "repositoryRow", this);
+				item.add(row);
+				if (entry.hasCommits) {
+					// Existing repository
+					PageParameters pp = WicketUtils.newRepositoryParameter(entry.name);
+					row.add(new LinkPanel("repositoryName", "list", entry.name, SummaryPage.class, pp));
+					row.add(new LinkPanel("repositoryDescription", "list", entry.description, SummaryPage.class, pp));
+				} else {
+					// New repository
+					row.add(new Label("repositoryName", entry.name + "<span class='empty'>(empty)</span>").setEscapeModelStrings(false));
+					row.add(new Label("repositoryDescription", entry.description));
+				}
+
+				if (entry.useTickets) {
+					row.add(WicketUtils.newImage("ticketsIcon", "bug_16x16.png", getString("gb.tickets")));
+				} else {
+					row.add(WicketUtils.newBlankImage("ticketsIcon"));
+				}
+
+				if (entry.useDocs) {
+					row.add(WicketUtils.newImage("docsIcon", "book_16x16.png", getString("gb.docs")));
+				} else {
+					row.add(WicketUtils.newBlankImage("docsIcon"));
+				}
+
+				if (entry.isFrozen) {
+					row.add(WicketUtils.newImage("frozenIcon", "cold_16x16.png", getString("gb.isFrozen")));
+				} else {
+					row.add(WicketUtils.newClearPixel("frozenIcon").setVisible(false));
+				}
+				switch (entry.accessRestriction) {
+				case NONE:
+					row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
+					break;
+				case PUSH:
+					row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png", accessRestrictionTranslations.get(entry.accessRestriction)));
+					break;
+				case CLONE:
+					row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png", accessRestrictionTranslations.get(entry.accessRestriction)));
+					break;
+				case VIEW:
+					row.add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png", accessRestrictionTranslations.get(entry.accessRestriction)));
+					break;
+				default:
+					row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
+				}
+
+				row.add(new Label("repositoryOwner", entry.owner));
+
+				String lastChange = TimeUtils.timeAgo(entry.lastChange);
+				Label lastChangeLabel = new Label("repositoryLastChange", lastChange);
+				row.add(lastChangeLabel);
+				WicketUtils.setCssClass(lastChangeLabel, TimeUtils.timeAgoCss(entry.lastChange));
+
+				boolean showOwner = user != null && user.getUsername().equalsIgnoreCase(entry.owner);				
+				if (showAdmin) {
+					Fragment repositoryLinks = new Fragment("repositoryLinks", "repositoryAdminLinks", this);
+					repositoryLinks.add(new BookmarkablePageLink<Void>("editRepository", EditRepositoryPage.class, WicketUtils.newRepositoryParameter(entry.name)));
+					repositoryLinks.add(new BookmarkablePageLink<Void>("renameRepository", EditRepositoryPage.class, WicketUtils.newRepositoryParameter(entry.name)).setEnabled(false));
+					repositoryLinks.add(new BookmarkablePageLink<Void>("deleteRepository", EditRepositoryPage.class, WicketUtils.newRepositoryParameter(entry.name)).setEnabled(false));
+					row.add(repositoryLinks);
+				} else if (showOwner) {
+					Fragment repositoryLinks = new Fragment("repositoryLinks", "repositoryOwnerLinks", this);
+					repositoryLinks.add(new BookmarkablePageLink<Void>("editRepository", EditRepositoryPage.class, WicketUtils.newRepositoryParameter(entry.name)));
+					row.add(repositoryLinks);
+				} else {
+					row.add(new Label("repositoryLinks"));
+				}
+				WicketUtils.setAlternatingBackground(item, counter);
+				counter++;
+			}
+		};
+		add(dataView);
+
+		if (dp instanceof SortableDataProvider<?>) {
+			// add sortable header
+			SortableDataProvider<?> sdp = (SortableDataProvider<?>) dp;
+			Fragment fragment = new Fragment("headerContent", "flatRepositoryHeader", this);
+			fragment.add(newSort("orderByRepository", SortBy.repository, sdp, dataView));
+			fragment.add(newSort("orderByDescription", SortBy.description, sdp, dataView));
+			fragment.add(newSort("orderByOwner", SortBy.owner, sdp, dataView));
+			fragment.add(newSort("orderByDate", SortBy.date, sdp, dataView));
+			add(fragment);
+		} else {
+			// not sortable
+			Fragment fragment = new Fragment("headerContent", "groupRepositoryHeader", this);
+			add(fragment);
+		}
+	}
+	
+	private class GroupRepositoryModel extends RepositoryModel {
+
+		private static final long serialVersionUID = 1L;
+
+		GroupRepositoryModel(String name) {
+			super(name, "", "", new Date(0));
+		}
+	}
+	
+	protected enum SortBy {
+		repository, description, owner, date;
+	}
+
+	protected OrderByBorder newSort(String wicketId, SortBy field, SortableDataProvider<?> dp, final DataView<?> dataView) {
+		return new OrderByBorder(wicketId, field.name(), dp) {
+			private static final long serialVersionUID = 1L;
+
+			@Override
+			protected void onSortChanged() {
+				dataView.setCurrentPage(0);
+			}
+		};
+	}
+
+	private class DataProvider extends SortableDataProvider<RepositoryModel> {
+		private static final long serialVersionUID = 1L;
+		private List<RepositoryModel> list = null;
+
+		protected DataProvider(List<RepositoryModel> list) {
+			this.list = list;
+			setSort(SortBy.date.name(), false);
+		}
+
+		@Override
+		public int size() {
+			if (list == null)
+				return 0;
+			return list.size();
+		}
+
+		@Override
+		public IModel<RepositoryModel> model(RepositoryModel header) {
+			return new Model<RepositoryModel>(header);
+		}
+
+		@Override
+		public Iterator<RepositoryModel> iterator(int first, int count) {
+			SortParam sp = getSort();
+			String prop = sp.getProperty();
+			final boolean asc = sp.isAscending();
+
+			if (prop == null || prop.equals(SortBy.date.name())) {
+				Collections.sort(list, new Comparator<RepositoryModel>() {
+					@Override
+					public int compare(RepositoryModel o1, RepositoryModel o2) {
+						if (asc)
+							return o1.lastChange.compareTo(o2.lastChange);
+						return o2.lastChange.compareTo(o1.lastChange);
+					}
+				});
+			} else if (prop.equals(SortBy.repository.name())) {
+				Collections.sort(list, new Comparator<RepositoryModel>() {
+					@Override
+					public int compare(RepositoryModel o1, RepositoryModel o2) {
+						if (asc)
+							return o1.name.compareTo(o2.name);
+						return o2.name.compareTo(o1.name);
+					}
+				});
+			} else if (prop.equals(SortBy.owner.name())) {
+				Collections.sort(list, new Comparator<RepositoryModel>() {
+					@Override
+					public int compare(RepositoryModel o1, RepositoryModel o2) {
+						if (asc)
+							return o1.owner.compareTo(o2.owner);
+						return o2.owner.compareTo(o1.owner);
+					}
+				});
+			} else if (prop.equals(SortBy.description.name())) {
+				Collections.sort(list, new Comparator<RepositoryModel>() {
+					@Override
+					public int compare(RepositoryModel o1, RepositoryModel o2) {
+						if (asc)
+							return o1.description.compareTo(o2.description);
+						return o2.description.compareTo(o1.description);
+					}
+				});
+			}
+			return list.subList(first, first + count).iterator();
+		}
+	}
+}
diff --git a/src/com/gitblit/wicket/panels/UsersPanel.html b/src/com/gitblit/wicket/panels/UsersPanel.html
new file mode 100644
index 0000000..39074b2
--- /dev/null
+++ b/src/com/gitblit/wicket/panels/UsersPanel.html
@@ -0,0 +1,48 @@
+<!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"> 
+
+<body>
+<wicket:panel>
+
+		<div wicket:id="adminPanel">[admin links]</div>
+		
+		<table class="repositories">
+		<tr>
+			<th class="left">
+				<wicket:link>
+   					<img style="vertical-align: top; border: 1px solid #888; background-color: white;" src="/com/gitblit/wicket/resources/user_16x16.png"/>
+  				</wicket:link>
+				<wicket:message key="gb.username">[username]</wicket:message>
+			</th>
+			<th class="right"></th>
+		</tr>
+		<tbody>		
+       		<tr wicket:id="userRow">
+       			<td class="left" ><div class="list" wicket:id="username">[username]</div></td>
+       			<td class="rightAlign"><span wicket:id="userLinks"></span></td>      			
+       		</tr>
+    	</tbody>
+	</table>
+	
+	<wicket:fragment wicket:id="adminLinks">
+		<!-- page nav links -->	
+		<div class="admin_nav">
+			<wicket:link>
+   				<img style="vertical-align: top;" src="/com/gitblit/wicket/resources/add_16x16.png"/>
+  			</wicket:link>		
+			<a wicket:id="newUser">
+				<wicket:message key="gb.newUser"></wicket:message>
+			</a>
+		</div>	
+	</wicket:fragment>
+	
+	<wicket:fragment wicket:id="userAdminLinks">
+		<span class="link"><a wicket:id="editUser"><wicket:message key="gb.edit">[edit]</wicket:message></a> | <a wicket:id="deleteUser"><wicket:message key="gb.delete">[delete]</wicket:message></a></span>
+	</wicket:fragment>
+	
+</wicket:panel>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/com/gitblit/wicket/panels/UsersPanel.java b/src/com/gitblit/wicket/panels/UsersPanel.java
new file mode 100644
index 0000000..1721272
--- /dev/null
+++ b/src/com/gitblit/wicket/panels/UsersPanel.java
@@ -0,0 +1,46 @@
+package com.gitblit.wicket.panels;
+
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.apache.wicket.markup.html.panel.Fragment;
+import org.apache.wicket.markup.repeater.Item;
+import org.apache.wicket.markup.repeater.data.DataView;
+import org.apache.wicket.markup.repeater.data.ListDataProvider;
+
+import com.gitblit.GitBlit;
+import com.gitblit.wicket.LinkPanel;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.pages.EditUserPage;
+import com.gitblit.wicket.pages.RepositoriesPage;
+
+public class UsersPanel extends BasePanel {
+
+	private static final long serialVersionUID = 1L;
+
+	public UsersPanel(String wicketId, final boolean showAdmin) {
+		super(wicketId);
+
+		Fragment adminLinks = new Fragment("adminPanel", "adminLinks", this);
+		adminLinks.add(new BookmarkablePageLink<Void>("newUser", EditUserPage.class));
+		add(adminLinks.setVisible(showAdmin));
+		
+		DataView<String> usersView = new DataView<String>("userRow", new ListDataProvider<String>(GitBlit.self().getAllUsernames())) {
+			private static final long serialVersionUID = 1L;
+			private int counter = 0;
+
+			public void populateItem(final Item<String> item) {
+				final String entry = item.getModelObject();
+				LinkPanel editLink = new LinkPanel("username", "list", entry, EditUserPage.class, WicketUtils.newUsernameParameter(entry));
+				WicketUtils.setHtmlTooltip(editLink, getString("gb.edit") + " " + entry);
+				item.add(editLink);
+				Fragment userLinks = new Fragment("userLinks", "userAdminLinks", this);
+				userLinks.add(new BookmarkablePageLink<Void>("editUser", EditUserPage.class, WicketUtils.newUsernameParameter(entry)));
+				userLinks.add(new BookmarkablePageLink<Void>("deleteUser", RepositoriesPage.class, WicketUtils.newUsernameParameter(entry)).setEnabled(false));
+				item.add(userLinks);
+
+				WicketUtils.setAlternatingBackground(item, counter);
+				counter++;
+			}
+		};
+		add(usersView.setVisible(showAdmin));
+	}
+}
diff --git a/src/com/gitblit/wicket/resources/add_16x16.png b/src/com/gitblit/wicket/resources/add_16x16.png
new file mode 100644
index 0000000..0ea124a
--- /dev/null
+++ b/src/com/gitblit/wicket/resources/add_16x16.png
Binary files differ
diff --git a/src/com/gitblit/wicket/resources/gitblit.css b/src/com/gitblit/wicket/resources/gitblit.css
index 3fa1fcf..4a971a6 100644
--- a/src/com/gitblit/wicket/resources/gitblit.css
+++ b/src/com/gitblit/wicket/resources/gitblit.css
@@ -190,6 +190,10 @@
 }
 
 div.admin_nav {
+	border: 1px solid #888;
+	border-bottom: 0px;
+	background:#dae0d2;
+	text-align: right;
 	padding: 5px 5px 5px 2px;	
 }
 
@@ -493,7 +497,7 @@
 	border: 1px solid orange;
 }
 
-table.pretty, table.repositories, table.comments {
+table.pretty, table.comments {
 	margin-bottom:5px;
 	border-spacing: 0px;
 	border-left: 1px solid #bbb;
@@ -522,6 +526,11 @@
 	line-height: 17px;
 }
 
+table.repositories {
+	margin-bottom:5px;
+	border-spacing: 0px;
+}
+
 table.repositories th {
 	background-color:#D2C3AF;
 	padding: 4px;
@@ -529,9 +538,27 @@
 	border-bottom: 1px solid #808080;
 }
 
+table.repositories th.left, table.repositories td.left {
+	border-left: 1px solid #808080;
+	padding-left: 5px;
+}
+
+table.repositories td.left {
+	padding-left: 10px;
+}
+
+table.repositories th.right, table.repositories td.right {
+	border-right: 1px solid #808080;
+}
+
 table.repositories td {
 	padding: 2px;
 }
+
+table.repositories td.rightAlign {	
+	text-align: right;
+	border-right: 1px solid #808080;
+}	
 
 table.repositories td.icon img {
 	vertical-align: top;
@@ -552,6 +579,20 @@
 	font-weight: bold;
 }
 
+table.repositories tr.group {
+	background-color: #E66C2C;
+}
+
+table.repositories tr.group td {
+	font-weight: bold;	
+	border-bottom: 1px solid orange;
+	color: white;
+	background-color: #E66C2C;
+	border-left: 1px solid #808080;
+	border-right: 1px solid #808080;
+	padding-left: 5px;
+}
+
 table.palette { border:0;}
 table.palette td.header { 
 	font-weight: bold; 
@@ -569,17 +610,6 @@
 tr th.wicket_orderDown a {background-image: url(arrow_down.png); }
 tr th.wicket_orderUp a { background-image: url(arrow_up.png); }
 tr th.wicket_orderNone a { background-image: url(arrow_off.png); }
-
-tr.group {
-	background-color: #E66C2C;
-}
-
-tr.group td {
-	font-weight: bold;
-	border-bottom: 1px solid orange;
-	color: white;
-	background-color: #E66C2C;	
-}
 
 tr.light {
 	background-color: #ffffff;
diff --git a/src/com/gitblit/wicket/resources/gitweb-favicon.png b/src/com/gitblit/wicket/resources/gitweb-favicon.png
new file mode 100644
index 0000000..de637c0
--- /dev/null
+++ b/src/com/gitblit/wicket/resources/gitweb-favicon.png
Binary files differ
diff --git a/src/com/gitblit/wicket/resources/user_16x16.png b/src/com/gitblit/wicket/resources/user_16x16.png
new file mode 100644
index 0000000..d5edd4d
--- /dev/null
+++ b/src/com/gitblit/wicket/resources/user_16x16.png
Binary files differ

--
Gitblit v1.9.1