User list. Revised home page. Updated Jetty. Secure cookies. Docs.
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>

@@ -20,6 +25,21 @@
 					<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>

 			</filterchain>

 		</loadfile>

 		<echo>Building Git:Blit ${gb.version}</echo>

@@ -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);

@@ -65,12 +42,6 @@
 			}

 		}

 

-		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();

 		if (!StringUtils.isEmpty(cachedMessage)) {

@@ -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,10 +538,28 @@
 	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; 

@@ -570,17 +611,6 @@
 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