From fdd82f02dddd2d4211e9df4239f3be6c8595b2dd Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Thu, 01 May 2014 19:53:53 -0400
Subject: [PATCH] Refactored common code out of My Tickets and Tickets

---
 src/main/java/com/gitblit/wicket/pages/RepositoryPage.java    |    3 
 src/main/java/com/gitblit/wicket/pages/TicketsPage.java       |  316 ----------
 src/main/java/com/gitblit/wicket/panels/TicketSearchForm.java |   78 ++
 src/main/java/com/gitblit/wicket/panels/UserTitlePanel.html   |   16 
 src/main/java/com/gitblit/wicket/pages/MyTicketsPage.html     |   56 -
 src/main/java/com/gitblit/wicket/pages/TicketPage.java        |   21 
 src/main/java/com/gitblit/wicket/GitBlitWebApp.properties     |    4 
 src/main/java/com/gitblit/wicket/pages/TicketsPage.html       |   46 -
 src/main/java/com/gitblit/wicket/panels/TicketListPanel.java  |  243 ++++++++
 /dev/null                                                     |  126 ----
 src/main/java/com/gitblit/wicket/SessionlessForm.java         |   15 
 src/main/java/com/gitblit/wicket/panels/TicketListPanel.html  |   55 +
 src/main/java/com/gitblit/wicket/TicketsUI.java               |  211 +++++++
 src/main/java/com/gitblit/wicket/WicketUtils.java             |    4 
 src/main/java/com/gitblit/wicket/pages/RepositoryPage.html    |    2 
 src/main/java/com/gitblit/wicket/pages/MyTicketsPage.java     |  435 +--------------
 src/main/java/com/gitblit/wicket/panels/UserTitlePanel.java   |   32 +
 17 files changed, 732 insertions(+), 931 deletions(-)

diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
index 0c56df5..0ed2ed5 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.properties
@@ -672,11 +672,11 @@
 gb.mergeToDescription = default integration branch for merging ticket patchsets
 gb.anonymousCanNotPropose = Anonymous users can not propose patchsets.
 gb.youDoNotHaveClonePermission = You are not permitted to clone this repository.
+gb.myTickets = my tickets
+gb.yourAssignedTickets = assigned to you
 gb.newMilestone = new milestone
 gb.editMilestone = edit milestone
 gb.notifyChangedOpenTickets = send notification for changed open tickets
 gb.overdue = overdue
 gb.openMilestones = open milestones
 gb.closedMilestones = closed milestones
-gb.myTickets = my tickets
-gb.yourAssignedTickets = assigned to you
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/SessionlessForm.java b/src/main/java/com/gitblit/wicket/SessionlessForm.java
index d228a2e..6f79071 100644
--- a/src/main/java/com/gitblit/wicket/SessionlessForm.java
+++ b/src/main/java/com/gitblit/wicket/SessionlessForm.java
@@ -22,6 +22,7 @@
 import org.apache.wicket.markup.ComponentTag;
 import org.apache.wicket.markup.MarkupStream;
 import org.apache.wicket.markup.html.form.StatelessForm;
+import org.apache.wicket.protocol.http.RequestUtils;
 import org.apache.wicket.protocol.http.WicketURLDecoder;
 import org.apache.wicket.protocol.http.request.WebRequestCodingStrategy;
 import org.apache.wicket.util.string.AppendingStringBuffer;
@@ -53,9 +54,9 @@
 
 	private static final String HIDDEN_DIV_START = "<div style=\"width:0px;height:0px;position:absolute;left:-100px;top:-100px;overflow:hidden\">";
 
-	private final Class<? extends BasePage> pageClass;
+	protected final Class<? extends BasePage> pageClass;
 
-	private final PageParameters pageParameters;
+	protected final PageParameters pageParameters;
 
 	private final Logger log = LoggerFactory.getLogger(SessionlessForm.class);
 
@@ -145,4 +146,14 @@
 		String un = WicketURLDecoder.QUERY_INSTANCE.decode(s);
 		return Strings.escapeMarkup(un).toString();
 	}
+
+	protected String getAbsoluteUrl() {
+		return getAbsoluteUrl(pageClass, pageParameters);
+	}
+
+	protected String getAbsoluteUrl(Class<? extends BasePage> pageClass, PageParameters pageParameters) {
+		String relativeUrl = urlFor(pageClass, pageParameters).toString();
+		String absoluteUrl = RequestUtils.toAbsolutePath(relativeUrl);
+		return absoluteUrl;
+	}
 }
diff --git a/src/main/java/com/gitblit/wicket/TicketsUI.java b/src/main/java/com/gitblit/wicket/TicketsUI.java
new file mode 100644
index 0000000..a243a7b
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/TicketsUI.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright 2014 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket;
+
+import java.io.Serializable;
+import java.text.MessageFormat;
+
+import org.apache.wicket.markup.html.basic.Label;
+
+import com.gitblit.models.TicketModel;
+import com.gitblit.models.TicketModel.Status;
+import com.gitblit.models.TicketModel.Type;
+import com.gitblit.utils.StringUtils;
+
+/**
+ * Common tickets ui methods and classes.
+ *
+ * @author James Moger
+ *
+ */
+public class TicketsUI {
+
+	public static final String [] openStatii = new String [] { Status.New.name().toLowerCase(), Status.Open.name().toLowerCase() };
+
+	public static final String [] closedStatii = new String [] { "!" + Status.New.name().toLowerCase(), "!" + Status.Open.name().toLowerCase() };
+
+	public static Label getStateIcon(String wicketId, TicketModel ticket) {
+		return getStateIcon(wicketId, ticket.type, ticket.status);
+	}
+
+	public static Label getStateIcon(String wicketId, Type type, Status state) {
+		Label label = new Label(wicketId);
+		if (type == null) {
+			type = Type.defaultType;
+		}
+		switch (type) {
+		case Proposal:
+			WicketUtils.setCssClass(label, "fa fa-code-fork");
+			break;
+		case Bug:
+			WicketUtils.setCssClass(label, "fa fa-bug");
+			break;
+		case Enhancement:
+			WicketUtils.setCssClass(label, "fa fa-magic");
+			break;
+		case Question:
+			WicketUtils.setCssClass(label, "fa fa-question");
+			break;
+		default:
+			// standard ticket
+			WicketUtils.setCssClass(label, "fa fa-ticket");
+		}
+		WicketUtils.setHtmlTooltip(label, getTypeState(type, state));
+		return label;
+	}
+
+	public static String getTypeState(Type type, Status state) {
+		return state.toString() + " " + type.toString();
+	}
+
+	public static String getLozengeClass(Status status, boolean subtle) {
+		if (status == null) {
+			status = Status.New;
+		}
+		String css = "";
+		switch (status) {
+		case Declined:
+		case Duplicate:
+		case Invalid:
+		case Wontfix:
+		case Abandoned:
+			css = "aui-lozenge-error";
+			break;
+		case Fixed:
+		case Merged:
+		case Resolved:
+			css = "aui-lozenge-success";
+			break;
+		case New:
+			css = "aui-lozenge-complete";
+			break;
+		case On_Hold:
+			css = "aui-lozenge-current";
+			break;
+		default:
+			css = "";
+			break;
+		}
+
+		return "aui-lozenge" + (subtle ? " aui-lozenge-subtle": "") + (css.isEmpty() ? "" : " ") + css;
+	}
+
+	public static String getStatusClass(Status status) {
+		String css = "";
+		switch (status) {
+		case Declined:
+		case Duplicate:
+		case Invalid:
+		case Wontfix:
+		case Abandoned:
+			css = "resolution-error";
+			break;
+		case Fixed:
+		case Merged:
+		case Resolved:
+			css = "resolution-success";
+			break;
+		case New:
+			css = "resolution-complete";
+			break;
+		case On_Hold:
+			css = "resolution-current";
+			break;
+		default:
+			css = "";
+			break;
+		}
+
+		return "resolution" + (css.isEmpty() ? "" : " ") + css;
+	}
+
+	public static class TicketSort implements Serializable {
+
+		private static final long serialVersionUID = 1L;
+
+		public final String name;
+		public final String sortBy;
+		public final boolean desc;
+
+		public TicketSort(String name, String sortBy, boolean desc) {
+			this.name = name;
+			this.sortBy = sortBy;
+			this.desc = desc;
+		}
+	}
+
+	public static class Indicator implements Serializable {
+
+		private static final long serialVersionUID = 1L;
+
+		public final String css;
+		public final int count;
+		public final String tooltip;
+
+		public Indicator(String css, String tooltip) {
+			this.css = css;
+			this.tooltip = tooltip;
+			this.count = 0;
+		}
+
+		public Indicator(String css, int count, String pattern) {
+			this.css = css;
+			this.count = count;
+			this.tooltip = StringUtils.isEmpty(pattern) ? "" : MessageFormat.format(pattern, count);
+		}
+
+		public String getTooltip() {
+			return tooltip;
+		}
+	}
+
+	public static class TicketQuery implements Serializable, Comparable<TicketQuery> {
+
+		private static final long serialVersionUID = 1L;
+
+		public final String name;
+		public final String query;
+		public String color;
+
+		public TicketQuery(String name, String query) {
+			this.name = name;
+			this.query = query;
+		}
+
+		public TicketQuery color(String value) {
+			this.color = value;
+			return this;
+		}
+
+		@Override
+		public boolean equals(Object o) {
+			if (o instanceof TicketQuery) {
+				return ((TicketQuery) o).query.equals(query);
+			}
+			return false;
+		}
+
+		@Override
+		public int hashCode() {
+			return query.hashCode();
+		}
+
+		@Override
+		public int compareTo(TicketQuery o) {
+			return query.compareTo(o.query);
+		}
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/WicketUtils.java b/src/main/java/com/gitblit/wicket/WicketUtils.java
index 2a34ca8..10b2146 100644
--- a/src/main/java/com/gitblit/wicket/WicketUtils.java
+++ b/src/main/java/com/gitblit/wicket/WicketUtils.java
@@ -300,7 +300,9 @@
 
 	public static PageParameters newRepositoryParameter(String repositoryName) {
 		Map<String, String> parameterMap = new HashMap<String, String>();
-		parameterMap.put("r", repositoryName);
+		if (!StringUtils.isEmpty(repositoryName)) {
+			parameterMap.put("r", repositoryName);
+		}
 		return new PageParameters(parameterMap);
 	}
 
diff --git a/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.html b/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.html
index f4e62b4..b0bc194 100644
--- a/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.html
@@ -7,19 +7,12 @@
 <body>
 	<wicket:extend>
 		<div class="container">
-		
 			<div class="row" style="padding-top:15px;min-height:500px;" >
 				<div class="tab-pane active" id="tickets">
 					<!-- query controls -->
 					<div class="span3">
 						<div class="hidden-phone">
-							<div>
-								<div style="display:inline-block;vertical-align:top;padding: 0px 2px 2px;"><img wicket:id="userGravatar"></img></div>
-								<div style="display:inline-block;">
-									<div style="font-size:1.5em;" wicket:id="userDisplayName"></div>
-									<div style="color:#888;font-size:1.2em;padding-top:4px;"><wicket:message key="gb.myTickets"></wicket:message></div>
-								</div>
-							</div>
+							<div wicket:id="userTitlePanel"></div>
 							
 							<!-- search tickets form -->
 							<form class="form-search" style="margin: 10px 0px;" wicket:id="ticketSearchForm">
@@ -79,54 +72,13 @@
 								</div>
 							</div>
 						</div>
-						<table class="table tickets">	
-							<tbody>
-       							<tr wicket:id="row">
-       								<td class="ticket-list-icon">
-       									<i wicket:id="state"></i>
-       								</td>
-        							<td>
-        								<span wicket:id="title">[title]</span> <span wicket:id="labels" style="font-weight: normal;color:white;"><span class="label" wicket:id="label"></span></span>
-        								<div class="ticket-list-details">
-        									<span style="padding-right: 10px;" class="hidden-phone">
-        										<wicket:message key="gb.createdBy"></wicket:message>
-        										<span style="padding: 0px 2px" wicket:id="createdBy">[createdBy]</span> <span class="date" wicket:id="createDate">[create date]</span>
-        									</span>
-        									<span wicket:id="indicators" style="white-space:nowrap;"><i wicket:id="icon"></i> <span style="padding-right:10px;" wicket:id="count"></span></span>
-        								</div>
-        								<div class="hidden-phone" wicket:id="updated"></div>
-        								<div class="ticket-list-details" wicket:id="repositoryLink">[repository link]</div>
-        							</td>
-        							<td class="ticket-list-state">
-       									<span class="badge badge-info" wicket:id="votes"></span>
-        							</td>
-        							<td class="hidden-phone ticket-list-state">
-       									<i wicket:message="title:gb.watching" style="color:#888;" class="fa fa-eye" wicket:id="watching"></i>
-        							</td>
-        							<td class="ticket-list-state">
-       									<div wicket:id="status"></div>
-        							</td>
-        							<td class="indicators">
-        								<div>        	 		        	 		
-        	 								<b>#<span wicket:id="id">[id]</span></b>
-        	 							</div>
-        								<div wicket:id="responsible"></div>
-        							</td>
-       							</tr>
-    						</tbody>
-						</table>						
+						
+						<div wicket:id="ticketList"></div>					
 					</div>
 				</div>
 			</div>
 		</div>
-		
-<wicket:fragment wicket:id="updatedFragment">
-	<div class="ticket-list-details">
-		<wicket:message key="gb.updatedBy"></wicket:message>
-		<span style="padding: 0px 2px" wicket:id="updatedBy">[updatedBy]</span> <span class="date" wicket:id="updateDate">[update date]</span>
-	</div>
-</wicket:fragment>
-		
+				
 </wicket:extend>
 </body>
 </html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.java b/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.java
index e268858..c207d56 100644
--- a/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/MyTicketsPage.java
@@ -1,72 +1,74 @@
+/*
+ * Copyright 2014 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 package com.gitblit.wicket.pages;
 
-import java.io.Serializable;
-import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
 import org.apache.wicket.PageParameters;
-import org.apache.wicket.behavior.SimpleAttributeModifier;
 import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.form.TextField;
 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 org.apache.wicket.model.IModel;
-import org.apache.wicket.model.Model;
-import org.apache.wicket.request.target.basic.RedirectRequestTarget;
-import org.eclipse.jgit.lib.Repository;
 
-import com.gitblit.Constants;
 import com.gitblit.Keys;
-import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.TicketModel;
 import com.gitblit.models.TicketModel.Status;
-import com.gitblit.models.TicketModel.Type;
 import com.gitblit.models.UserModel;
-import com.gitblit.tickets.ITicketService;
 import com.gitblit.tickets.QueryBuilder;
 import com.gitblit.tickets.QueryResult;
 import com.gitblit.tickets.TicketIndexer.Lucene;
-import com.gitblit.tickets.TicketLabel;
 import com.gitblit.utils.ArrayUtils;
-import com.gitblit.utils.BugtraqProcessor;
 import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.GitBlitWebApp;
 import com.gitblit.wicket.GitBlitWebSession;
-import com.gitblit.wicket.SessionlessForm;
+import com.gitblit.wicket.TicketsUI;
+import com.gitblit.wicket.TicketsUI.TicketSort;
 import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.GravatarImage;
 import com.gitblit.wicket.panels.LinkPanel;
+import com.gitblit.wicket.panels.TicketListPanel;
+import com.gitblit.wicket.panels.TicketSearchForm;
+import com.gitblit.wicket.panels.UserTitlePanel;
 
+/**
+ * My Tickets page
+ *
+ * @author Christian Buisson
+ * @author James Moger
+ */
 public class MyTicketsPage extends RootPage {
 
-	public static final String [] openStatii = new String [] { Status.New.name().toLowerCase(), Status.Open.name().toLowerCase() };
-
-	public static final String [] closedStatii = new String [] { "!" + Status.New.name().toLowerCase(), "!" + Status.Open.name().toLowerCase() };
-
-	public MyTicketsPage()
-	{
+	public MyTicketsPage() {
 		this(null);
 	}
 
-	public MyTicketsPage(PageParameters params)
-	{
+	public MyTicketsPage(PageParameters params)	{
 		super(params);
 		setupPage("", getString("gb.myTickets"));
 
 		UserModel currentUser = GitBlitWebSession.get().getUser();
-		if (currentUser == null) {
+		if (currentUser == null || UserModel.ANONYMOUS.equals(currentUser)) {
 			setRedirect(true);
 			setResponsePage(getApplication().getHomePage());
 			return;
 		}
 
 		final String username = currentUser.getName();
-		final String[] statiiParam = (params == null) ? openStatii : params.getStringArray(Lucene.status.name());
+		final String[] statiiParam = (params == null) ? TicketsUI.openStatii : params.getStringArray(Lucene.status.name());
 		final String assignedToParam = (params == null) ? "" : params.getString(Lucene.responsible.name(), null);
 		final String milestoneParam = (params == null) ? "" : params.getString(Lucene.milestone.name(), null);
 		final String queryParam = (params == null || StringUtils.isEmpty(params.getString("q", null))) ? "watchedby:" + username : params.getString("q", null);
@@ -74,13 +76,11 @@
 		final String sortBy = (params == null) ? "" : Lucene.fromString(params.getString("sort", Lucene.created.name())).name();
 		final boolean desc = (params == null) ? true : !"asc".equals(params.getString("direction", "desc"));
 
-		add(new GravatarImage("userGravatar", currentUser, "gravatar", 36, false));
-		add(new Label("userDisplayName", currentUser.getDisplayName()));
+		// add the user title panel
+		add(new UserTitlePanel("userTitlePanel", currentUser, getString("gb.myTickets")));
 
 		// add search form
-		TicketSearchForm searchForm = new TicketSearchForm("ticketSearchForm", searchParam);
-		add(searchForm);
-		searchForm.setTranslatedAttributes();
+		add(new TicketSearchForm("ticketSearchForm", null, searchParam, getClass(), params));
 
 		// standard queries
 		add(new BookmarkablePageLink<Void>("changesQuery", MyTicketsPage.class,
@@ -137,7 +137,7 @@
 				queryParameters(
 						null,
 						milestoneParam,
-						openStatii,
+						TicketsUI.openStatii,
 						null,
 						null,
 						true,
@@ -188,8 +188,8 @@
 		} else {
 			add(new Label("selectedStatii", StringUtils.flattenStrings(Arrays.asList(statiiParam), ",")));
 		}
-		add(new BookmarkablePageLink<Void>("openTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, openStatii, assignedToParam, sortBy, desc, 1)));
-		add(new BookmarkablePageLink<Void>("closedTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, closedStatii, assignedToParam, sortBy, desc, 1)));
+		add(new BookmarkablePageLink<Void>("openTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, TicketsUI.openStatii, assignedToParam, sortBy, desc, 1)));
+		add(new BookmarkablePageLink<Void>("closedTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, TicketsUI.closedStatii, assignedToParam, sortBy, desc, 1)));
 		add(new BookmarkablePageLink<Void>("allTickets", MyTicketsPage.class, queryParameters(queryParam, milestoneParam, null, assignedToParam, sortBy, desc, 1)));
 
 		// by status
@@ -203,7 +203,7 @@
 			public void populateItem(final Item<Status> item) {
 				final Status status = item.getModelObject();
 				PageParameters p = queryParameters(queryParam, milestoneParam, new String [] { status.name().toLowerCase() }, assignedToParam, sortBy, desc, 1);
-				String css = getStatusClass(status);
+				String css = TicketsUI.getStatusClass(status);
 				item.add(new LinkPanel("statusLink", css, status.toString(), MyTicketsPage.class, p).setRenderBodyOnly(true));
 			}
 		};
@@ -284,342 +284,18 @@
 		int page = (params != null) ? Math.max(1, WicketUtils.getPage(params)) : 1;
 		int pageSize = app().settings().getInteger(Keys.tickets.perPage, 25);
 
-		ITicketService tickets = GitBlitWebApp.get().tickets();
 		List<QueryResult> results;
-		if(StringUtils.isEmpty(searchParam))
-		{
-			results = tickets.queryFor(luceneQuery, page, pageSize, sortBy, desc);
+		if(StringUtils.isEmpty(searchParam)) {
+			results = app().tickets().queryFor(luceneQuery, page, pageSize, sortBy, desc);
+		} else {
+			results = app().tickets().searchFor(null, searchParam, page, pageSize);
 		}
-		else
-		{
-			results = tickets.searchFor(null, searchParam, page, pageSize);
-		}
+
 		int totalResults = results.size() == 0 ? 0 : results.get(0).totalResults;
 		buildPager(queryParam, milestoneParam, statiiParam, assignedToParam, sortBy, desc, page, pageSize, results.size(), totalResults);
 
 		final boolean showSwatch = app().settings().getBoolean(Keys.web.repositoryListSwatches, true);
-		final ListDataProvider<QueryResult> dp = new ListDataProvider<QueryResult>(results);
-
-		DataView<QueryResult> dataView = new DataView<QueryResult>("row", dp) {
-			private static final long serialVersionUID = 1L;
-
-			@Override
-			protected void populateItem(Item<QueryResult> item) {
-				final QueryResult ticket = item.getModelObject();
-				final RepositoryModel repository = app().repositories().getRepositoryModel(ticket.repository);
-
-				if (showSwatch) {
-					// set repository color
-					String color = StringUtils.getColor(StringUtils.stripDotGit(repository.name));
-					WicketUtils.setCssStyle(item, MessageFormat.format("border-left: 2px solid {0};", color));
-				}
-
-				PageParameters rp = WicketUtils.newRepositoryParameter(ticket.repository);
-				PageParameters tp = WicketUtils.newObjectParameter(ticket.repository, "" + ticket.number);
-				item.add(new LinkPanel("repositoryLink", null, StringUtils.stripDotGit(ticket.repository), SummaryPage.class, rp));
-
-				item.add(getStateIcon("state", ticket.type, ticket.status));
-				item.add(new Label("id", "" + ticket.number));
-				UserModel creator = app().users().getUserModel(ticket.createdBy);
-				if (creator != null) {
-					item.add(new LinkPanel("createdBy", null, creator.getDisplayName(),
-						UserPage.class, WicketUtils.newUsernameParameter(ticket.createdBy)));
-				} else {
-					item.add(new Label("createdBy", ticket.createdBy));
-				}
-				item.add(WicketUtils.createDateLabel("createDate", ticket.createdAt, GitBlitWebSession
-						.get().getTimezone(), getTimeUtils(), false));
-
-				if (ticket.updatedAt == null) {
-					item.add(new Label("updated").setVisible(false));
-				} else {
-					Fragment updated = new Fragment("updated", "updatedFragment", this);
-					UserModel updater = app().users().getUserModel(ticket.updatedBy);
-					if (updater != null) {
-						updated.add(new LinkPanel("updatedBy", null, updater.getDisplayName(),
-								UserPage.class, WicketUtils.newUsernameParameter(ticket.updatedBy)));
-					} else {
-						updated.add(new Label("updatedBy", ticket.updatedBy));
-					}
-					updated.add(WicketUtils.createDateLabel("updateDate", ticket.updatedAt, GitBlitWebSession
-							.get().getTimezone(), getTimeUtils(), false));
-					item.add(updated);
-				}
-
-				item.add(new LinkPanel("title", "list subject", StringUtils.trimString(
-						ticket.title, Constants.LEN_SHORTLOG), TicketsPage.class, tp));
-
-				ListDataProvider<String> labelsProvider = new ListDataProvider<String>(ticket.getLabels());
-				DataView<String> labelsView = new DataView<String>("labels", labelsProvider) {
-					private static final long serialVersionUID = 1L;
-
-					@Override
-					public void populateItem(final Item<String> labelItem) {
-						BugtraqProcessor btp  = new BugtraqProcessor(app().settings());
-						Repository db = app().repositories().getRepository(repository.name);
-						String content = btp.processPlainCommitMessage(db, repository.name, labelItem.getModelObject());
-						db.close();
-						Label label = new Label("label", content);
-						label.setEscapeModelStrings(false);
-						TicketLabel tLabel = app().tickets().getLabel(repository, labelItem.getModelObject());
-						String background = MessageFormat.format("background-color:{0};", tLabel.color);
-						label.add(new SimpleAttributeModifier("style", background));
-						labelItem.add(label);
-					}
-				};
-				item.add(labelsView);
-
-				if (StringUtils.isEmpty(ticket.responsible)) {
-					item.add(new Label("responsible").setVisible(false));
-				} else {
-					UserModel responsible = app().users().getUserModel(ticket.responsible);
-					if (responsible == null) {
-						responsible = new UserModel(ticket.responsible);
-					}
-					GravatarImage avatar = new GravatarImage("responsible", responsible.getDisplayName(),
-							responsible.emailAddress, null, 16, true);
-					avatar.setTooltip(getString("gb.responsible") + ": " + responsible.getDisplayName());
-					item.add(avatar);
-				}
-
-				// votes indicator
-				Label v = new Label("votes", "" + ticket.votesCount);
-				WicketUtils.setHtmlTooltip(v, getString("gb.votes"));
-				item.add(v.setVisible(ticket.votesCount > 0));
-
-				// watching indicator
-				item.add(new Label("watching").setVisible(ticket.isWatching(GitBlitWebSession.get().getUsername())));
-
-				// status indicator
-				String css = getLozengeClass(ticket.status, true);
-				Label l = new Label("status", ticket.status.toString());
-				WicketUtils.setCssClass(l, css);
-				item.add(l);
-
-				// add the ticket indicators/icons
-				List<Indicator> indicators = new ArrayList<Indicator>();
-
-				// comments
-				if (ticket.commentsCount > 0) {
-					int count = ticket.commentsCount;
-					String pattern = "gb.nComments";
-					if (count == 1) {
-						pattern = "gb.oneComment";
-					}
-					indicators.add(new Indicator("fa fa-comment", count, pattern));
-				}
-
-				// participants
-				if (!ArrayUtils.isEmpty(ticket.participants)) {
-					int count = ticket.participants.size();
-					if (count > 1) {
-						String pattern = "gb.nParticipants";
-						indicators.add(new Indicator("fa fa-user", count, pattern));
-					}
-				}
-
-				// attachments
-				if (!ArrayUtils.isEmpty(ticket.attachments)) {
-					int count = ticket.attachments.size();
-					String pattern = "gb.nAttachments";
-					if (count == 1) {
-						pattern = "gb.oneAttachment";
-					}
-					indicators.add(new Indicator("fa fa-file", count, pattern));
-				}
-
-				// patchset revisions
-				if (ticket.patchset != null) {
-					int count = ticket.patchset.commits;
-					String pattern = "gb.nCommits";
-					if (count == 1) {
-						pattern = "gb.oneCommit";
-					}
-					indicators.add(new Indicator("fa fa-code", count, pattern));
-				}
-
-				// milestone
-				if (!StringUtils.isEmpty(ticket.milestone)) {
-					indicators.add(new Indicator("fa fa-bullseye", ticket.milestone));
-				}
-
-				ListDataProvider<Indicator> indicatorsDp = new ListDataProvider<Indicator>(indicators);
-				DataView<Indicator> indicatorsView = new DataView<Indicator>("indicators", indicatorsDp) {
-					private static final long serialVersionUID = 1L;
-
-					@Override
-					public void populateItem(final Item<Indicator> item) {
-						Indicator indicator = item.getModelObject();
-						String tooltip = indicator.getTooltip();
-
-						Label icon = new Label("icon");
-						WicketUtils.setCssClass(icon, indicator.css);
-						item.add(icon);
-
-						if (indicator.count > 0) {
-							Label count = new Label("count", "" + indicator.count);
-							item.add(count.setVisible(!StringUtils.isEmpty(tooltip)));
-						} else {
-							item.add(new Label("count").setVisible(false));
-						}
-
-						WicketUtils.setHtmlTooltip(item, tooltip);
-					}
-				};
-				item.add(indicatorsView);
-			}
-		};
-
-		add(dataView);
-	}
-
-	protected Label getStateIcon(String wicketId, TicketModel ticket) {
-		return getStateIcon(wicketId, ticket.type, ticket.status);
-	}
-
-	protected Label getStateIcon(String wicketId, Type type, Status state) {
-		Label label = new Label(wicketId);
-		if (type == null) {
-			type = Type.defaultType;
-		}
-		switch (type) {
-		case Proposal:
-			WicketUtils.setCssClass(label, "fa fa-code-fork");
-			break;
-		case Bug:
-			WicketUtils.setCssClass(label, "fa fa-bug");
-			break;
-		case Enhancement:
-			WicketUtils.setCssClass(label, "fa fa-magic");
-			break;
-		case Question:
-			WicketUtils.setCssClass(label, "fa fa-question");
-			break;
-		default:
-			// standard ticket
-			WicketUtils.setCssClass(label, "fa fa-ticket");
-		}
-		WicketUtils.setHtmlTooltip(label, getTypeState(type, state));
-		return label;
-	}
-
-	protected String getTypeState(Type type, Status state) {
-		return state.toString() + " " + type.toString();
-	}
-
-	protected String getLozengeClass(Status status, boolean subtle) {
-		if (status == null) {
-			status = Status.New;
-		}
-		String css = "";
-		switch (status) {
-		case Declined:
-		case Duplicate:
-		case Invalid:
-		case Wontfix:
-		case Abandoned:
-			css = "aui-lozenge-error";
-			break;
-		case Fixed:
-		case Merged:
-		case Resolved:
-			css = "aui-lozenge-success";
-			break;
-		case New:
-			css = "aui-lozenge-complete";
-			break;
-		case On_Hold:
-			css = "aui-lozenge-current";
-			break;
-		default:
-			css = "";
-			break;
-		}
-
-		return "aui-lozenge" + (subtle ? " aui-lozenge-subtle": "") + (css.isEmpty() ? "" : " ") + css;
-	}
-
-	protected String getStatusClass(Status status) {
-		String css = "";
-		switch (status) {
-		case Declined:
-		case Duplicate:
-		case Invalid:
-		case Wontfix:
-		case Abandoned:
-			css = "resolution-error";
-			break;
-		case Fixed:
-		case Merged:
-		case Resolved:
-			css = "resolution-success";
-			break;
-		case New:
-			css = "resolution-complete";
-			break;
-		case On_Hold:
-			css = "resolution-current";
-			break;
-		default:
-			css = "";
-			break;
-		}
-
-		return "resolution" + (css.isEmpty() ? "" : " ") + css;
-	}
-
-	private class TicketSort implements Serializable {
-
-		private static final long serialVersionUID = 1L;
-
-		final String name;
-		final String sortBy;
-		final boolean desc;
-
-		TicketSort(String name, String sortBy, boolean desc) {
-			this.name = name;
-			this.sortBy = sortBy;
-			this.desc = desc;
-		}
-	}
-
-	private class TicketSearchForm extends SessionlessForm<Void> implements Serializable {
-		private static final long serialVersionUID = 1L;
-
-		private final IModel<String> searchBoxModel;;
-
-		public TicketSearchForm(String id, String text) {
-			super(id, MyTicketsPage.this.getClass(), MyTicketsPage.this.getPageParameters());
-
-			this.searchBoxModel = new Model<String>(text == null ? "" : text);
-
-			TextField<String> searchBox = new TextField<String>("ticketSearchBox", searchBoxModel);
-			add(searchBox);
-		}
-
-		void setTranslatedAttributes() {
-			WicketUtils.setHtmlTooltip(get("ticketSearchBox"),
-					MessageFormat.format(getString("gb.searchTicketsTooltip"), ""));
-			WicketUtils.setInputPlaceholder(get("ticketSearchBox"), getString("gb.searchTickets"));
-		}
-
-		@Override
-		public void onSubmit() {
-			String searchString = searchBoxModel.getObject();
-			if (StringUtils.isEmpty(searchString)) {
-				// redirect to self to avoid wicket page update bug
-				String absoluteUrl = getCanonicalUrl();
-				getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
-				return;
-			}
-
-			// use an absolute url to workaround Wicket-Tomcat problems with
-			// mounted url parameters (issue-111)
-			PageParameters params = WicketUtils.newRepositoryParameter("");
-			params.add("s", searchString);
-			String absoluteUrl = getCanonicalUrl(MyTicketsPage.class, params);
-			getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
-		}
+		add(new TicketListPanel("ticketList", results, showSwatch, true));
 	}
 
 	protected PageParameters queryParameters(
@@ -712,30 +388,5 @@
 			}
 		};
 		add(pagesView);
-	}
-
-	private class Indicator implements Serializable {
-
-		private static final long serialVersionUID = 1L;
-
-		final String css;
-		final int count;
-		final String tooltip;
-
-		Indicator(String css, String tooltip) {
-			this.css = css;
-			this.tooltip = tooltip;
-			this.count = 0;
-		}
-
-		Indicator(String css, int count, String pattern) {
-			this.css = css;
-			this.count = count;
-			this.tooltip = StringUtils.isEmpty(pattern) ? "" : MessageFormat.format(getString(pattern), count);
-		}
-
-		String getTooltip() {
-			return tooltip;
-		}
 	}
 }
diff --git a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.html b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.html
index cb4f1b6..22544bc 100644
--- a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.html
@@ -17,7 +17,7 @@
 								<form class="form-search" style="margin: 0px;" wicket:id="searchForm">
 									<div class="input-append">
 										<select class="span2" style="border-radius: 4px;" wicket:id="searchType"/>
-										<input type="text" class="search-query" style="width: 170px;border-radius: 14px 0 0 14px; padding-left: 14px;" id="searchBox" wicket:id="searchBox" value=""/>
+										<input type="text" class="input-medium search-query" style="border-radius: 14px 0 0 14px; padding-left: 14px;" id="searchBox" wicket:id="searchBox" value=""/>
 										<button class="btn" style="border-radius: 0 14px 14px 0px;margin-left:-5px;" type="submit"><i class="icon-search"></i></button>
 									</div>
 								</form>
diff --git a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
index 2b97bc1..5ea99fd 100644
--- a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
@@ -69,6 +69,7 @@
 import com.gitblit.wicket.PageRegistration;
 import com.gitblit.wicket.PageRegistration.OtherPageLink;
 import com.gitblit.wicket.SessionlessForm;
+import com.gitblit.wicket.TicketsUI;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.panels.LinkPanel;
 import com.gitblit.wicket.panels.NavigationPanel;
@@ -204,7 +205,7 @@
 		pages.put("tree", new PageRegistration("gb.tree", TreePage.class, params));
 		if (app().tickets().isReady() && (app().tickets().isAcceptingNewTickets(getRepositoryModel()) || app().tickets().hasTickets(getRepositoryModel()))) {
 			PageParameters tParams = new PageParameters(params);
-			for (String state : TicketsPage.openStatii) {
+			for (String state : TicketsUI.openStatii) {
 				tParams.add(Lucene.status.name(), state);
 			}
 			pages.put("tickets", new PageRegistration("gb.tickets", TicketsPage.class, tParams));
diff --git a/src/main/java/com/gitblit/wicket/pages/TicketBasePage.java b/src/main/java/com/gitblit/wicket/pages/TicketBasePage.java
deleted file mode 100644
index 60fa638..0000000
--- a/src/main/java/com/gitblit/wicket/pages/TicketBasePage.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright 2013 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.wicket.pages;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.basic.Label;
-
-import com.gitblit.models.TicketModel;
-import com.gitblit.models.TicketModel.Status;
-import com.gitblit.models.TicketModel.Type;
-import com.gitblit.wicket.WicketUtils;
-
-public abstract class TicketBasePage extends RepositoryPage {
-
-	public TicketBasePage(PageParameters params) {
-		super(params);
-	}
-
-	protected Label getStateIcon(String wicketId, TicketModel ticket) {
-		return getStateIcon(wicketId, ticket.type, ticket.status);
-	}
-
-	protected Label getStateIcon(String wicketId, Type type, Status state) {
-		Label label = new Label(wicketId);
-		if (type == null) {
-			type = Type.defaultType;
-		}
-		switch (type) {
-		case Proposal:
-			WicketUtils.setCssClass(label, "fa fa-code-fork");
-			break;
-		case Bug:
-			WicketUtils.setCssClass(label, "fa fa-bug");
-			break;
-		case Enhancement:
-			WicketUtils.setCssClass(label, "fa fa-magic");
-			break;
-		case Question:
-			WicketUtils.setCssClass(label, "fa fa-question");
-			break;
-		default:
-			// standard ticket
-			WicketUtils.setCssClass(label, "fa fa-ticket");
-		}
-		WicketUtils.setHtmlTooltip(label, getTypeState(type, state));
-		return label;
-	}
-
-	protected String getTypeState(Type type, Status state) {
-		return state.toString() + " " + type.toString();
-	}
-
-	protected String getLozengeClass(Status status, boolean subtle) {
-		if (status == null) {
-			status = Status.New;
-		}
-		String css = "";
-		switch (status) {
-		case Declined:
-		case Duplicate:
-		case Invalid:
-		case Wontfix:
-		case Abandoned:
-			css = "aui-lozenge-error";
-			break;
-		case Fixed:
-		case Merged:
-		case Resolved:
-			css = "aui-lozenge-success";
-			break;
-		case New:
-			css = "aui-lozenge-complete";
-			break;
-		case On_Hold:
-			css = "aui-lozenge-current";
-			break;
-		default:
-			css = "";
-			break;
-		}
-
-		return "aui-lozenge" + (subtle ? " aui-lozenge-subtle": "") + (css.isEmpty() ? "" : " ") + css;
-	}
-
-	protected String getStatusClass(Status status) {
-		String css = "";
-		switch (status) {
-		case Declined:
-		case Duplicate:
-		case Invalid:
-		case Wontfix:
-		case Abandoned:
-			css = "resolution-error";
-			break;
-		case Fixed:
-		case Merged:
-		case Resolved:
-			css = "resolution-success";
-			break;
-		case New:
-			css = "resolution-complete";
-			break;
-		case On_Hold:
-			css = "resolution-current";
-			break;
-		default:
-			css = "";
-			break;
-		}
-
-		return "resolution" + (css.isEmpty() ? "" : " ") + css;
-	}
-}
diff --git a/src/main/java/com/gitblit/wicket/pages/TicketPage.java b/src/main/java/com/gitblit/wicket/pages/TicketPage.java
index 659acad..c066f24 100644
--- a/src/main/java/com/gitblit/wicket/pages/TicketPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/TicketPage.java
@@ -86,6 +86,7 @@
 import com.gitblit.utils.StringUtils;
 import com.gitblit.utils.TimeUtils;
 import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.TicketsUI;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.panels.BasePanel.JavascriptTextPrompt;
 import com.gitblit.wicket.panels.CommentPanel;
@@ -102,7 +103,7 @@
  * @author James Moger
  *
  */
-public class TicketPage extends TicketBasePage {
+public class TicketPage extends RepositoryPage {
 
 	static final String NIL = "<nil>";
 
@@ -154,7 +155,7 @@
 		String href = urlFor(TicketsPage.class, params).toString();
 		add(new ExternalLink("ticketNumber", href, "#" + ticket.number));
 		Label headerStatus = new Label("headerStatus", ticket.status.toString());
-		WicketUtils.setCssClass(headerStatus, getLozengeClass(ticket.status, false));
+		WicketUtils.setCssClass(headerStatus, TicketsUI.getLozengeClass(ticket.status, false));
 		add(headerStatus);
 		add(new Label("ticketTitle", ticket.title));
 		if (currentPatchset == null) {
@@ -317,10 +318,10 @@
 		 * LARGE STATUS INDICATOR WITH ICON (DISCUSSION TAB->SIDE BAR)
 		 */
 		Fragment ticketStatus = new Fragment("ticketStatus", "ticketStatusFragment", this);
-		Label ticketIcon = getStateIcon("ticketIcon", ticket);
+		Label ticketIcon = TicketsUI.getStateIcon("ticketIcon", ticket);
 		ticketStatus.add(ticketIcon);
 		ticketStatus.add(new Label("ticketStatus", ticket.status.toString()));
-		WicketUtils.setCssClass(ticketStatus, getLozengeClass(ticket.status, false));
+		WicketUtils.setCssClass(ticketStatus, TicketsUI.getLozengeClass(ticket.status, false));
 		add(ticketStatus);
 
 
@@ -370,7 +371,7 @@
 								setResponsePage(TicketsPage.class, getPageParameters());
 							}
 						};
-						String css = getStatusClass(item.getModel().getObject());
+						String css = TicketsUI.getStatusClass(item.getModel().getObject());
 						WicketUtils.setCssClass(link, css);
 						item.add(link);
 					}
@@ -665,7 +666,7 @@
 						 */
 						Fragment frag = new Fragment("entry", "statusFragment", this);
 						Label status = new Label("statusChange", entry.getStatus().toString());
-						String css = getLozengeClass(entry.getStatus(), false);
+						String css = TicketsUI.getLozengeClass(entry.getStatus(), false);
 						WicketUtils.setCssClass(status, css);
 						for (IBehavior b : status.getBehaviors()) {
 							if (b instanceof SimpleAttributeModifier) {
@@ -936,7 +937,7 @@
 							case status:
 								// special handling for status
 								Status status = event.getStatus();
-								String css = getLozengeClass(status, true);
+								String css = TicketsUI.getLozengeClass(status, true);
 								value = String.format("<span class=\"%1$s\">%2$s</span>", css, status.toString());
 								break;
 							default:
@@ -1525,14 +1526,14 @@
 		switch (type) {
 			case Rebase:
 			case Rebase_Squash:
-				typeCss = getLozengeClass(Status.Declined, false);
+				typeCss = TicketsUI.getLozengeClass(Status.Declined, false);
 				break;
 			case Squash:
 			case Amend:
-				typeCss = getLozengeClass(Status.On_Hold, false);
+				typeCss = TicketsUI.getLozengeClass(Status.On_Hold, false);
 				break;
 			case Proposal:
-				typeCss = getLozengeClass(Status.New, false);
+				typeCss = TicketsUI.getLozengeClass(Status.New, false);
 				break;
 			case FastForward:
 			default:
diff --git a/src/main/java/com/gitblit/wicket/pages/TicketsPage.html b/src/main/java/com/gitblit/wicket/pages/TicketsPage.html
index a40d312..40060f3 100644
--- a/src/main/java/com/gitblit/wicket/pages/TicketsPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/TicketsPage.html
@@ -11,7 +11,7 @@
 	<div class="hidden-phone pull-right">
 		<form class="form-search" style="margin: 0px;" wicket:id="ticketSearchForm">
 			<div class="input-append">
-				<input type="text" class="search-query" style="width: 170px;border-radius: 14px 0 0 14px; padding-left: 14px;" id="ticketSearchBox" wicket:id="ticketSearchBox" value=""/>
+				<input type="text" class="input-medium search-query" style="border-radius: 14px 0 0 14px; padding-left: 14px;" id="ticketSearchBox" wicket:id="ticketSearchBox" value=""/>
 				<button class="btn" style="border-radius: 0 14px 14px 0px;margin-left:-5px;" type="submit"><i class="icon-search"></i></button>
 			</div>
 		</form>
@@ -88,42 +88,7 @@
 				</div>
 			</div>
 		
-		
-			<table class="table tickets">			
-			<tbody>
-       		<tr wicket:id="ticket">
-       			<td class="ticket-list-icon">
-       				<i wicket:id="state"></i>
-       			</td>
-        		<td>
-        			<span wicket:id="title">[title]</span> <span wicket:id="labels" style="font-weight: normal;color:white;"><span class="label" wicket:id="label"></span></span>
-        			<div class="ticket-list-details">
-        				<span style="padding-right: 10px;" class="hidden-phone">
-        					<wicket:message key="gb.createdBy"></wicket:message>
-        					<span style="padding: 0px 2px" wicket:id="createdBy">[createdBy]</span> <span class="date" wicket:id="createDate">[create date]</span>
-        				</span>
-        				<span wicket:id="indicators" style="white-space:nowrap;"><i wicket:id="icon"></i> <span style="padding-right:10px;" wicket:id="count"></span></span>
-        			</div>
-        			<div class="hidden-phone" wicket:id="updated"></div>
-        		</td>
-        		<td class="ticket-list-state">
-       				<span class="badge badge-info" wicket:id="votes"></span>
-        		</td>
-        		<td class="hidden-phone ticket-list-state">
-       				<i wicket:message="title:gb.watching" style="color:#888;" class="fa fa-eye" wicket:id="watching"></i>
-        		</td>
-        		<td class="ticket-list-state">
-       				<div wicket:id="status"></div>
-        		</td>
-        		<td class="indicators">
-        			<div>        	 		        	 		
-        	 			<b>#<span wicket:id="id">[id]</span></b>
-        	 		</div>
-        			<div wicket:id="responsible"></div>
-        		</td>
-       		</tr>
-    		</tbody>
-			</table>
+			<div wicket:id="ticketList"></div>
 		
 			<div class="btn-group pull-right">
 					<div class="pagination pagination-right pagination-small">
@@ -238,13 +203,6 @@
 		<li class="nav-header"><wicket:message key="gb.topicsAndLabels"></wicket:message></li>
 		<li class="dynamicQuery" wicket:id="dynamicQuery"><span><span wicket:id="swatch"></span> <span wicket:id="link"></span></span><span class="pull-right"><i style="font-size: 18px;" wicket:id="checked"></i></span></li>
 	</ul>
-</wicket:fragment>
-
-<wicket:fragment wicket:id="updatedFragment">
-	<div class="ticket-list-details">
-		<wicket:message key="gb.updatedBy"></wicket:message>
-		<span style="padding: 0px 2px" wicket:id="updatedBy">[updatedBy]</span> <span class="date" wicket:id="updateDate">[update date]</span>
-	</div>
 </wicket:fragment>
 
 </wicket:extend>
diff --git a/src/main/java/com/gitblit/wicket/pages/TicketsPage.java b/src/main/java/com/gitblit/wicket/pages/TicketsPage.java
index 5973d47..d88ccb6 100644
--- a/src/main/java/com/gitblit/wicket/pages/TicketsPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/TicketsPage.java
@@ -15,7 +15,6 @@
  */
 package com.gitblit.wicket.pages;
 
-import java.io.Serializable;
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -29,17 +28,12 @@
 import org.apache.wicket.PageParameters;
 import org.apache.wicket.behavior.SimpleAttributeModifier;
 import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.form.TextField;
 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 org.apache.wicket.model.IModel;
-import org.apache.wicket.model.Model;
-import org.apache.wicket.request.target.basic.RedirectRequestTarget;
 
-import com.gitblit.Constants;
 import com.gitblit.Constants.AccessPermission;
 import com.gitblit.Keys;
 import com.gitblit.models.RegistrantAccessPermission;
@@ -56,18 +50,17 @@
 import com.gitblit.utils.ArrayUtils;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.GitBlitWebSession;
-import com.gitblit.wicket.SessionlessForm;
+import com.gitblit.wicket.TicketsUI;
+import com.gitblit.wicket.TicketsUI.TicketQuery;
+import com.gitblit.wicket.TicketsUI.TicketSort;
 import com.gitblit.wicket.WicketUtils;
-import com.gitblit.wicket.panels.GravatarImage;
 import com.gitblit.wicket.panels.LinkPanel;
+import com.gitblit.wicket.panels.TicketListPanel;
+import com.gitblit.wicket.panels.TicketSearchForm;
 
-public class TicketsPage extends TicketBasePage {
+public class TicketsPage extends RepositoryPage {
 
 	final TicketResponsible any;
-
-	public static final String [] openStatii = new String [] { Status.New.name().toLowerCase(), Status.Open.name().toLowerCase() };
-
-	public static final String [] closedStatii = new String [] { "!" + Status.New.name().toLowerCase(), "!" + Status.Open.name().toLowerCase() };
 
 	public TicketsPage(PageParameters params) {
 		super(params);
@@ -102,11 +95,8 @@
 		final String sortBy = Lucene.fromString(params.getString("sort", Lucene.created.name())).name();
 		final boolean desc = !"asc".equals(params.getString("direction", "desc"));
 
-
 		// add search form
-		TicketSearchForm searchForm = new TicketSearchForm("ticketSearchForm", repositoryName, searchParam);
-		add(searchForm);
-		searchForm.setTranslatedAttributes();
+		add(new TicketSearchForm("ticketSearchForm", repositoryName, searchParam, getClass(), params));
 
 		final String activeQuery;
 		if (!StringUtils.isEmpty(searchParam)) {
@@ -192,12 +182,12 @@
 			milestonePanel.add(new LinkPanel("openTickets", null,
 					MessageFormat.format(getString("gb.nOpenTickets"), currentMilestone.getOpenTickets()),
 					TicketsPage.class,
-					queryParameters(null, currentMilestone.name, openStatii, null, sortBy, desc, 1)));
+					queryParameters(null, currentMilestone.name, TicketsUI.openStatii, null, sortBy, desc, 1)));
 
 			milestonePanel.add(new LinkPanel("closedTickets", null,
 					MessageFormat.format(getString("gb.nClosedTickets"), currentMilestone.getClosedTickets()),
 					TicketsPage.class,
-					queryParameters(null, currentMilestone.name, closedStatii, null, sortBy, desc, 1)));
+					queryParameters(null, currentMilestone.name, TicketsUI.closedStatii, null, sortBy, desc, 1)));
 
 			milestonePanel.add(new Label("totalTickets", MessageFormat.format(getString("gb.nTotalTickets"), currentMilestone.getTotalTickets())));
 			add(milestonePanel);
@@ -287,7 +277,7 @@
 				queryParameters(
 						null,
 						milestoneParam,
-						openStatii,
+						TicketsUI.openStatii,
 						null,
 						null,
 						true,
@@ -397,8 +387,8 @@
 		} else {
 			add(new Label("selectedStatii", StringUtils.flattenStrings(Arrays.asList(statiiParam), ",")));
 		}
-		add(new BookmarkablePageLink<Void>("openTickets", TicketsPage.class, queryParameters(queryParam, milestoneParam, openStatii, assignedToParam, sortBy, desc, 1)));
-		add(new BookmarkablePageLink<Void>("closedTickets", TicketsPage.class, queryParameters(queryParam, milestoneParam, closedStatii, assignedToParam, sortBy, desc, 1)));
+		add(new BookmarkablePageLink<Void>("openTickets", TicketsPage.class, queryParameters(queryParam, milestoneParam, TicketsUI.openStatii, assignedToParam, sortBy, desc, 1)));
+		add(new BookmarkablePageLink<Void>("closedTickets", TicketsPage.class, queryParameters(queryParam, milestoneParam, TicketsUI.closedStatii, assignedToParam, sortBy, desc, 1)));
 		add(new BookmarkablePageLink<Void>("allTickets", TicketsPage.class, queryParameters(queryParam, milestoneParam, null, assignedToParam, sortBy, desc, 1)));
 
 		// by status
@@ -412,7 +402,7 @@
 			public void populateItem(final Item<Status> item) {
 				final Status status = item.getModelObject();
 				PageParameters p = queryParameters(queryParam, milestoneParam, new String [] { status.name().toLowerCase() }, assignedToParam, sortBy, desc, 1);
-				String css = getStatusClass(status);
+				String css = TicketsUI.getStatusClass(status);
 				item.add(new LinkPanel("statusLink", css, status.toString(), TicketsPage.class, p).setRenderBodyOnly(true));
 			}
 		};
@@ -491,162 +481,7 @@
 		// paging links
 		buildPager(queryParam, milestoneParam, statiiParam, assignedToParam, sortBy, desc, page, pageSize, results.size(), totalResults);
 
-		ListDataProvider<QueryResult> resultsDataProvider = new ListDataProvider<QueryResult>(results);
-		DataView<QueryResult> ticketsView = new DataView<QueryResult>("ticket", resultsDataProvider) {
-			private static final long serialVersionUID = 1L;
-
-			@Override
-			public void populateItem(final Item<QueryResult> item) {
-				final QueryResult ticket = item.getModelObject();
-				item.add(getStateIcon("state", ticket.type, ticket.status));
-				item.add(new Label("id", "" + ticket.number));
-				UserModel creator = app().users().getUserModel(ticket.createdBy);
-				if (creator != null) {
-					item.add(new LinkPanel("createdBy", null, creator.getDisplayName(),
-						UserPage.class, WicketUtils.newUsernameParameter(ticket.createdBy)));
-				} else {
-					item.add(new Label("createdBy", ticket.createdBy));
-				}
-				item.add(WicketUtils.createDateLabel("createDate", ticket.createdAt, GitBlitWebSession
-						.get().getTimezone(), getTimeUtils(), false));
-
-				if (ticket.updatedAt == null) {
-					item.add(new Label("updated").setVisible(false));
-				} else {
-					Fragment updated = new Fragment("updated", "updatedFragment", this);
-					UserModel updater = app().users().getUserModel(ticket.updatedBy);
-					if (updater != null) {
-						updated.add(new LinkPanel("updatedBy", null, updater.getDisplayName(),
-								UserPage.class, WicketUtils.newUsernameParameter(ticket.updatedBy)));
-					} else {
-						updated.add(new Label("updatedBy", ticket.updatedBy));
-					}
-					updated.add(WicketUtils.createDateLabel("updateDate", ticket.updatedAt, GitBlitWebSession
-							.get().getTimezone(), getTimeUtils(), false));
-					item.add(updated);
-				}
-
-				item.add(new LinkPanel("title", "list subject", StringUtils.trimString(
-						ticket.title, Constants.LEN_SHORTLOG), TicketsPage.class, newTicketParameter(ticket)));
-
-				ListDataProvider<String> labelsProvider = new ListDataProvider<String>(ticket.getLabels());
-				DataView<String> labelsView = new DataView<String>("labels", labelsProvider) {
-					private static final long serialVersionUID = 1L;
-
-					@Override
-					public void populateItem(final Item<String> labelItem) {
-						String content = bugtraqProcessor().processPlainCommitMessage(getRepository(), repositoryName, labelItem.getModelObject());
-						Label label = new Label("label", content);
-						label.setEscapeModelStrings(false);
-						TicketLabel tLabel = app().tickets().getLabel(getRepositoryModel(), labelItem.getModelObject());
-						String background = MessageFormat.format("background-color:{0};", tLabel.color);
-						label.add(new SimpleAttributeModifier("style", background));
-						labelItem.add(label);
-					}
-				};
-				item.add(labelsView);
-
-				if (StringUtils.isEmpty(ticket.responsible)) {
-					item.add(new Label("responsible").setVisible(false));
-				} else {
-					UserModel responsible = app().users().getUserModel(ticket.responsible);
-					if (responsible == null) {
-						responsible = new UserModel(ticket.responsible);
-					}
-					GravatarImage avatar = new GravatarImage("responsible", responsible.getDisplayName(),
-							responsible.emailAddress, null, 16, true);
-					avatar.setTooltip(getString("gb.responsible") + ": " + responsible.getDisplayName());
-					item.add(avatar);
-				}
-
-				// votes indicator
-				Label v = new Label("votes", "" + ticket.votesCount);
-				WicketUtils.setHtmlTooltip(v, getString("gb.votes"));
-				item.add(v.setVisible(ticket.votesCount > 0));
-
-				// watching indicator
-				item.add(new Label("watching").setVisible(ticket.isWatching(GitBlitWebSession.get().getUsername())));
-
-				// status indicator
-				String css = getLozengeClass(ticket.status, true);
-				Label l = new Label("status", ticket.status.toString());
-				WicketUtils.setCssClass(l, css);
-				item.add(l);
-
-				// add the ticket indicators/icons
-				List<Indicator> indicators = new ArrayList<Indicator>();
-
-				// comments
-				if (ticket.commentsCount > 0) {
-					int count = ticket.commentsCount;
-					String pattern = "gb.nComments";
-					if (count == 1) {
-						pattern = "gb.oneComment";
-					}
-					indicators.add(new Indicator("fa fa-comment", count, pattern));
-				}
-
-				// participants
-				if (!ArrayUtils.isEmpty(ticket.participants)) {
-					int count = ticket.participants.size();
-					if (count > 1) {
-						String pattern = "gb.nParticipants";
-						indicators.add(new Indicator("fa fa-user", count, pattern));
-					}
-				}
-
-				// attachments
-				if (!ArrayUtils.isEmpty(ticket.attachments)) {
-					int count = ticket.attachments.size();
-					String pattern = "gb.nAttachments";
-					if (count == 1) {
-						pattern = "gb.oneAttachment";
-					}
-					indicators.add(new Indicator("fa fa-file", count, pattern));
-				}
-
-				// patchset revisions
-				if (ticket.patchset != null) {
-					int count = ticket.patchset.commits;
-					String pattern = "gb.nCommits";
-					if (count == 1) {
-						pattern = "gb.oneCommit";
-					}
-					indicators.add(new Indicator("fa fa-code", count, pattern));
-				}
-
-				// milestone
-				if (!StringUtils.isEmpty(ticket.milestone)) {
-					indicators.add(new Indicator("fa fa-bullseye", ticket.milestone));
-				}
-
-				ListDataProvider<Indicator> indicatorsDp = new ListDataProvider<Indicator>(indicators);
-				DataView<Indicator> indicatorsView = new DataView<Indicator>("indicators", indicatorsDp) {
-					private static final long serialVersionUID = 1L;
-
-					@Override
-					public void populateItem(final Item<Indicator> item) {
-						Indicator indicator = item.getModelObject();
-						String tooltip = indicator.getTooltip();
-
-						Label icon = new Label("icon");
-						WicketUtils.setCssClass(icon, indicator.css);
-						item.add(icon);
-
-						if (indicator.count > 0) {
-							Label count = new Label("count", "" + indicator.count);
-							item.add(count.setVisible(!StringUtils.isEmpty(tooltip)));
-						} else {
-							item.add(new Label("count").setVisible(false));
-						}
-
-						WicketUtils.setHtmlTooltip(item, tooltip);
-					}
-				};
-				item.add(indicatorsView);
-			}
-		};
-		add(ticketsView);
+		add(new TicketListPanel("ticketList", results, false, false));
 
 		// new milestone link
 		RepositoryModel repositoryModel = getRepositoryModel();
@@ -747,12 +582,12 @@
 					milestonePanel.add(new LinkPanel("openTickets", null,
 							MessageFormat.format(getString("gb.nOpenTickets"), m.getOpenTickets()),
 							TicketsPage.class,
-							queryParameters(null, tm.name, openStatii, null, null, true, 1)));
+							queryParameters(null, tm.name, TicketsUI.openStatii, null, null, true, 1)));
 
 					milestonePanel.add(new LinkPanel("closedTickets", null,
 							MessageFormat.format(getString("gb.nClosedTickets"), m.getClosedTickets()),
 							TicketsPage.class,
-							queryParameters(null, tm.name, closedStatii, null, null, true, 1)));
+							queryParameters(null, tm.name, TicketsUI.closedStatii, null, null, true, 1)));
 
 					milestonePanel.add(new Label("totalTickets", MessageFormat.format(getString("gb.nTotalTickets"), m.getTotalTickets())));
 					entryPanel.add(milestonePanel);
@@ -863,124 +698,5 @@
 			}
 		};
 		add(pagesView);
-	}
-
-	private class Indicator implements Serializable {
-
-		private static final long serialVersionUID = 1L;
-
-		final String css;
-		final int count;
-		final String tooltip;
-
-		Indicator(String css, String tooltip) {
-			this.css = css;
-			this.tooltip = tooltip;
-			this.count = 0;
-		}
-
-		Indicator(String css, int count, String pattern) {
-			this.css = css;
-			this.count = count;
-			this.tooltip = StringUtils.isEmpty(pattern) ? "" : MessageFormat.format(getString(pattern), count);
-		}
-
-		String getTooltip() {
-			return tooltip;
-		}
-	}
-
-	private class TicketQuery implements Serializable, Comparable<TicketQuery> {
-
-		private static final long serialVersionUID = 1L;
-
-		final String name;
-		final String query;
-		String color;
-
-		TicketQuery(String name, String query) {
-			this.name = name;
-			this.query = query;
-		}
-
-		TicketQuery color(String value) {
-			this.color = value;
-			return this;
-		}
-
-		@Override
-		public boolean equals(Object o) {
-			if (o instanceof TicketQuery) {
-				return ((TicketQuery) o).query.equals(query);
-			}
-			return false;
-		}
-
-		@Override
-		public int hashCode() {
-			return query.hashCode();
-		}
-
-		@Override
-		public int compareTo(TicketQuery o) {
-			return query.compareTo(o.query);
-		}
-	}
-
-	private class TicketSort implements Serializable {
-
-		private static final long serialVersionUID = 1L;
-
-		final String name;
-		final String sortBy;
-		final boolean desc;
-
-		TicketSort(String name, String sortBy, boolean desc) {
-			this.name = name;
-			this.sortBy = sortBy;
-			this.desc = desc;
-		}
-	}
-
-	private class TicketSearchForm extends SessionlessForm<Void> implements Serializable {
-		private static final long serialVersionUID = 1L;
-
-		private final String repositoryName;
-
-		private final IModel<String> searchBoxModel;;
-
-		public TicketSearchForm(String id, String repositoryName, String text) {
-			super(id, TicketsPage.this.getClass(), TicketsPage.this.getPageParameters());
-
-			this.repositoryName = repositoryName;
-			this.searchBoxModel = new Model<String>(text == null ? "" : text);
-
-			TextField<String> searchBox = new TextField<String>("ticketSearchBox", searchBoxModel);
-			add(searchBox);
-		}
-
-		void setTranslatedAttributes() {
-			WicketUtils.setHtmlTooltip(get("ticketSearchBox"),
-					MessageFormat.format(getString("gb.searchTicketsTooltip"), repositoryName));
-			WicketUtils.setInputPlaceholder(get("ticketSearchBox"), getString("gb.searchTickets"));
-		}
-
-		@Override
-		public void onSubmit() {
-			String searchString = searchBoxModel.getObject();
-			if (StringUtils.isEmpty(searchString)) {
-				// redirect to self to avoid wicket page update bug
-				String absoluteUrl = getCanonicalUrl();
-				getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
-				return;
-			}
-
-			// use an absolute url to workaround Wicket-Tomcat problems with
-			// mounted url parameters (issue-111)
-			PageParameters params = WicketUtils.newRepositoryParameter(repositoryName);
-			params.add("s", searchString);
-			String absoluteUrl = getCanonicalUrl(TicketsPage.class, params);
-			getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
-		}
 	}
 }
diff --git a/src/main/java/com/gitblit/wicket/panels/TicketListPanel.html b/src/main/java/com/gitblit/wicket/panels/TicketListPanel.html
new file mode 100644
index 0000000..6e6d209
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/TicketListPanel.html
@@ -0,0 +1,55 @@
+<!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>
+<table class="table tickets">	
+	<tbody>
+		<tr wicket:id="row">
+       		<td class="ticket-list-icon">
+       			<i wicket:id="state"></i>
+       		</td>
+        	<td>
+        		<span wicket:id="title">[title]</span> <span wicket:id="labels" style="font-weight: normal;color:white;"><span class="label" wicket:id="label"></span></span>
+        		<div class="ticket-list-details">
+        			<span style="padding-right: 10px;" class="hidden-phone">
+        				<wicket:message key="gb.createdBy"></wicket:message>
+        				<span style="padding: 0px 2px" wicket:id="createdBy">[createdBy]</span> <span class="date" wicket:id="createDate">[create date]</span>
+        			</span>
+        			<span wicket:id="indicators" style="white-space:nowrap;"><i wicket:id="icon"></i> <span style="padding-right:10px;" wicket:id="count"></span></span>
+        		</div>
+        		<div class="hidden-phone" wicket:id="updated"></div>
+        		<div class="ticket-list-details"><span class="activitySwatch" wicket:id="repositoryLink">[repository link]</span></div>
+        	</td>
+        	<td class="ticket-list-state">
+       			<span class="badge badge-info" wicket:id="votes"></span>
+        	</td>
+        	<td class="hidden-phone ticket-list-state">
+       			<i wicket:message="title:gb.watching" style="color:#888;" class="fa fa-eye" wicket:id="watching"></i>
+        	</td>
+        	<td class="ticket-list-state">
+       			<div wicket:id="status"></div>
+        	</td>
+        	<td class="indicators">
+        		<div>        	 		        	 		
+        				<b>#<span wicket:id="id">[id]</span></b>
+        			</div>
+        		<div wicket:id="responsible"></div>
+        	</td>
+       	</tr>
+    </tbody>
+</table>
+
+<wicket:fragment wicket:id="updatedFragment">
+	<div class="ticket-list-details">
+		<wicket:message key="gb.updatedBy"></wicket:message>
+		<span style="padding: 0px 2px" wicket:id="updatedBy">[updatedBy]</span> <span class="date" wicket:id="updateDate">[update date]</span>
+	</div>
+</wicket:fragment>
+
+</wicket:panel>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/panels/TicketListPanel.java b/src/main/java/com/gitblit/wicket/panels/TicketListPanel.java
new file mode 100644
index 0000000..fc0431f
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/TicketListPanel.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2014 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket.panels;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.behavior.SimpleAttributeModifier;
+import org.apache.wicket.markup.html.basic.Label;
+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 org.eclipse.jgit.lib.Repository;
+
+import com.gitblit.Constants;
+import com.gitblit.models.RepositoryModel;
+import com.gitblit.models.UserModel;
+import com.gitblit.tickets.QueryResult;
+import com.gitblit.tickets.TicketLabel;
+import com.gitblit.utils.ArrayUtils;
+import com.gitblit.utils.BugtraqProcessor;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.TicketsUI;
+import com.gitblit.wicket.TicketsUI.Indicator;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.pages.SummaryPage;
+import com.gitblit.wicket.pages.TicketsPage;
+import com.gitblit.wicket.pages.UserPage;
+
+/**
+ *
+ * The ticket list panel lists tickets in a table.
+ *
+ * @author James Moger
+ *
+ */
+public class TicketListPanel extends BasePanel {
+
+	private static final long serialVersionUID = 1L;
+
+	public TicketListPanel(String wicketId, List<QueryResult> list, final boolean showSwatch, final boolean showRepository) {
+		super(wicketId);
+
+		final ListDataProvider<QueryResult> dp = new ListDataProvider<QueryResult>(list);
+		DataView<QueryResult> dataView = new DataView<QueryResult>("row", dp) {
+			private static final long serialVersionUID = 1L;
+
+			@Override
+			protected void populateItem(Item<QueryResult> item) {
+				final QueryResult ticket = item.getModelObject();
+				final RepositoryModel repository = app().repositories().getRepositoryModel(ticket.repository);
+
+				if (showSwatch) {
+					// set repository color
+					String color = StringUtils.getColor(StringUtils.stripDotGit(repository.name));
+					WicketUtils.setCssStyle(item, MessageFormat.format("border-left: 2px solid {0};", color));
+				}
+
+				PageParameters rp = WicketUtils.newRepositoryParameter(ticket.repository);
+				PageParameters tp = WicketUtils.newObjectParameter(ticket.repository, "" + ticket.number);
+
+				if (showRepository) {
+					String name = StringUtils.stripDotGit(ticket.repository);
+					LinkPanel link = new LinkPanel("repositoryLink", null, name, SummaryPage.class, rp);
+					WicketUtils.setCssBackground(link, name);
+					item.add(link);
+				} else {
+					item.add(new Label("repositoryLink").setVisible(false));
+				}
+
+				item.add(TicketsUI.getStateIcon("state", ticket.type, ticket.status));
+				item.add(new Label("id", "" + ticket.number));
+				UserModel creator = app().users().getUserModel(ticket.createdBy);
+				if (creator != null) {
+					item.add(new LinkPanel("createdBy", null, creator.getDisplayName(),
+							UserPage.class, WicketUtils.newUsernameParameter(ticket.createdBy)));
+				} else {
+					item.add(new Label("createdBy", ticket.createdBy));
+				}
+				item.add(WicketUtils.createDateLabel("createDate", ticket.createdAt, GitBlitWebSession
+						.get().getTimezone(), getTimeUtils(), false));
+
+				if (ticket.updatedAt == null) {
+					item.add(new Label("updated").setVisible(false));
+				} else {
+					Fragment updated = new Fragment("updated", "updatedFragment", this);
+					UserModel updater = app().users().getUserModel(ticket.updatedBy);
+					if (updater != null) {
+						updated.add(new LinkPanel("updatedBy", null, updater.getDisplayName(),
+								UserPage.class, WicketUtils.newUsernameParameter(ticket.updatedBy)));
+					} else {
+						updated.add(new Label("updatedBy", ticket.updatedBy));
+					}
+					updated.add(WicketUtils.createDateLabel("updateDate", ticket.updatedAt, GitBlitWebSession
+							.get().getTimezone(), getTimeUtils(), false));
+					item.add(updated);
+				}
+
+				item.add(new LinkPanel("title", "list subject", StringUtils.trimString(
+						ticket.title, Constants.LEN_SHORTLOG), TicketsPage.class, tp));
+
+				ListDataProvider<String> labelsProvider = new ListDataProvider<String>(ticket.getLabels());
+				DataView<String> labelsView = new DataView<String>("labels", labelsProvider) {
+					private static final long serialVersionUID = 1L;
+
+					@Override
+					public void populateItem(final Item<String> labelItem) {
+						BugtraqProcessor btp  = new BugtraqProcessor(app().settings());
+						Repository db = app().repositories().getRepository(repository.name);
+						String content = btp.processPlainCommitMessage(db, repository.name, labelItem.getModelObject());
+						db.close();
+						Label label = new Label("label", content);
+						label.setEscapeModelStrings(false);
+						TicketLabel tLabel = app().tickets().getLabel(repository, labelItem.getModelObject());
+						String background = MessageFormat.format("background-color:{0};", tLabel.color);
+						label.add(new SimpleAttributeModifier("style", background));
+						labelItem.add(label);
+					}
+				};
+				item.add(labelsView);
+
+				if (StringUtils.isEmpty(ticket.responsible)) {
+					item.add(new Label("responsible").setVisible(false));
+				} else {
+					UserModel responsible = app().users().getUserModel(ticket.responsible);
+					if (responsible == null) {
+						responsible = new UserModel(ticket.responsible);
+					}
+					GravatarImage avatar = new GravatarImage("responsible", responsible.getDisplayName(),
+							responsible.emailAddress, null, 16, true);
+					avatar.setTooltip(getString("gb.responsible") + ": " + responsible.getDisplayName());
+					item.add(avatar);
+				}
+
+				// votes indicator
+				Label v = new Label("votes", "" + ticket.votesCount);
+				WicketUtils.setHtmlTooltip(v, getString("gb.votes"));
+				item.add(v.setVisible(ticket.votesCount > 0));
+
+				// watching indicator
+				item.add(new Label("watching").setVisible(ticket.isWatching(GitBlitWebSession.get().getUsername())));
+
+				// status indicator
+				String css = TicketsUI.getLozengeClass(ticket.status, true);
+				Label l = new Label("status", ticket.status.toString());
+				WicketUtils.setCssClass(l, css);
+				item.add(l);
+
+				// add the ticket indicators/icons
+				List<Indicator> indicators = new ArrayList<Indicator>();
+
+				// comments
+				if (ticket.commentsCount > 0) {
+					int count = ticket.commentsCount;
+					String pattern = getString("gb.nComments");
+					if (count == 1) {
+						pattern = getString("gb.oneComment");
+					}
+					indicators.add(new Indicator("fa fa-comment", count, pattern));
+				}
+
+				// participants
+				if (!ArrayUtils.isEmpty(ticket.participants)) {
+					int count = ticket.participants.size();
+					if (count > 1) {
+						String pattern = getString("gb.nParticipants");
+						indicators.add(new Indicator("fa fa-user", count, pattern));
+					}
+				}
+
+				// attachments
+				if (!ArrayUtils.isEmpty(ticket.attachments)) {
+					int count = ticket.attachments.size();
+					String pattern = getString("gb.nAttachments");
+					if (count == 1) {
+						pattern = getString("gb.oneAttachment");
+					}
+					indicators.add(new Indicator("fa fa-file", count, pattern));
+				}
+
+				// patchset revisions
+				if (ticket.patchset != null) {
+					int count = ticket.patchset.commits;
+					String pattern = getString("gb.nCommits");
+					if (count == 1) {
+						pattern = getString("gb.oneCommit");
+					}
+					indicators.add(new Indicator("fa fa-code", count, pattern));
+				}
+
+				// milestone
+				if (!StringUtils.isEmpty(ticket.milestone)) {
+					indicators.add(new Indicator("fa fa-bullseye", ticket.milestone));
+				}
+
+				ListDataProvider<Indicator> indicatorsDp = new ListDataProvider<Indicator>(indicators);
+				DataView<Indicator> indicatorsView = new DataView<Indicator>("indicators", indicatorsDp) {
+					private static final long serialVersionUID = 1L;
+
+					@Override
+					public void populateItem(final Item<Indicator> item) {
+						Indicator indicator = item.getModelObject();
+						String tooltip = indicator.getTooltip();
+
+						Label icon = new Label("icon");
+						WicketUtils.setCssClass(icon, indicator.css);
+						item.add(icon);
+
+						if (indicator.count > 0) {
+							Label count = new Label("count", "" + indicator.count);
+							item.add(count.setVisible(!StringUtils.isEmpty(tooltip)));
+						} else {
+							item.add(new Label("count").setVisible(false));
+						}
+
+						WicketUtils.setHtmlTooltip(item, tooltip);
+					}
+				};
+				item.add(indicatorsView);
+			}
+		};
+
+		add(dataView);
+	}
+}
+
diff --git a/src/main/java/com/gitblit/wicket/panels/TicketSearchForm.java b/src/main/java/com/gitblit/wicket/panels/TicketSearchForm.java
new file mode 100644
index 0000000..21bf1ba
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/TicketSearchForm.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2014 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket.panels;
+
+import java.io.Serializable;
+import java.text.MessageFormat;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.form.TextField;
+import org.apache.wicket.model.IModel;
+import org.apache.wicket.model.Model;
+import org.apache.wicket.request.target.basic.RedirectRequestTarget;
+
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.SessionlessForm;
+import com.gitblit.wicket.WicketUtils;
+import com.gitblit.wicket.pages.BasePage;
+
+public class TicketSearchForm extends SessionlessForm<Void> implements Serializable {
+
+	private static final long serialVersionUID = 1L;
+
+	private final String repositoryName;
+
+	private final IModel<String> searchBoxModel;
+
+	public TicketSearchForm(String id, String repositoryName, String text,
+			Class<? extends BasePage> pageClass, PageParameters params) {
+
+		super(id, pageClass, params);
+
+		this.repositoryName = repositoryName;
+		this.searchBoxModel = new Model<String>(text == null ? "" : text);
+
+		TextField<String> searchBox = new TextField<String>("ticketSearchBox", searchBoxModel);
+		add(searchBox);
+	}
+
+	@Override
+	protected
+	void onInitialize() {
+		super.onInitialize();
+		WicketUtils.setHtmlTooltip(get("ticketSearchBox"),
+				MessageFormat.format(getString("gb.searchTicketsTooltip"), ""));
+		WicketUtils.setInputPlaceholder(get("ticketSearchBox"), getString("gb.searchTickets"));
+	}
+
+	@Override
+	public void onSubmit() {
+		String searchString = searchBoxModel.getObject();
+		if (StringUtils.isEmpty(searchString)) {
+			// redirect to self to avoid wicket page update bug
+			String absoluteUrl = getAbsoluteUrl();
+			getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
+			return;
+		}
+
+		// use an absolute url to workaround Wicket-Tomcat problems with
+		// mounted url parameters (issue-111)
+		PageParameters params = WicketUtils.newRepositoryParameter(repositoryName);
+		params.add("s", searchString);
+		String absoluteUrl = getAbsoluteUrl(pageClass, params);
+		getRequestCycle().setRequestTarget(new RedirectRequestTarget(absoluteUrl));
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/panels/UserTitlePanel.html b/src/main/java/com/gitblit/wicket/panels/UserTitlePanel.html
new file mode 100644
index 0000000..432c880
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/UserTitlePanel.html
@@ -0,0 +1,16 @@
+<!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 style="display:inline-block;vertical-align:top;padding: 0px 2px 2px;"><img wicket:id="userGravatar"></img></div>
+	<div style="display:inline-block;">
+	<div style="font-size:1.5em;" wicket:id="userDisplayName"></div>
+	<div style="color:#888;font-size:1.2em;padding-top:4px;"><span wicket:id="userTitle"></span></div>
+</div>
+</wicket:panel>
+</body>
+</html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/panels/UserTitlePanel.java b/src/main/java/com/gitblit/wicket/panels/UserTitlePanel.java
new file mode 100644
index 0000000..2bf5ee7
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/panels/UserTitlePanel.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket.panels;
+
+import org.apache.wicket.markup.html.basic.Label;
+
+import com.gitblit.models.UserModel;
+
+public class UserTitlePanel extends BasePanel {
+
+	private static final long serialVersionUID = 1L;
+
+	public UserTitlePanel(String wicketId, UserModel user, String title) {
+		super(wicketId);
+		add(new GravatarImage("userGravatar", user, "gravatar", 36, false));
+		add(new Label("userDisplayName", user.getDisplayName()));
+		add(new Label("userTitle", title));
+	}
+}

--
Gitblit v1.9.1