You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@archiva.apache.org by br...@apache.org on 2010/12/06 14:17:49 UTC
svn commit: r1042625 - in
/archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main:
java/org/apache/maven/archiva/web/action/admin/ resources/
webapp/WEB-INF/jsp/admin/ webapp/WEB-INF/jsp/decorators/
Author: brett
Date: Mon Dec 6 13:17:48 2010
New Revision: 1042625
URL: http://svn.apache.org/viewvc?rev=1042625&view=rev
Log:
[MRM-1440] add a system status page
Added:
archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/action/admin/SystemStatusAction.java
- copied, changed from r1042516, archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/action/admin/repositories/RepositoriesAction.java
archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/jsp/admin/systemStatus.jsp
- copied, changed from r1042516, archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/jsp/admin/repositories.jsp
Modified:
archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/resources/struts.xml
archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/jsp/decorators/default.jsp
Copied: archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/action/admin/SystemStatusAction.java (from r1042516, archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/action/admin/repositories/RepositoriesAction.java)
URL: http://svn.apache.org/viewvc/archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/action/admin/SystemStatusAction.java?p2=archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/action/admin/SystemStatusAction.java&p1=archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/action/admin/repositories/RepositoriesAction.java&r1=1042516&r2=1042625&rev=1042625&view=diff
==============================================================================
--- archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/action/admin/repositories/RepositoriesAction.java (original)
+++ archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/java/org/apache/maven/archiva/web/action/admin/SystemStatusAction.java Mon Dec 6 13:17:48 2010
@@ -1,4 +1,4 @@
-package org.apache.maven.archiva.web.action.admin.repositories;
+package org.apache.maven.archiva.web.action.admin;
/*
* Licensed to the Apache Software Foundation (ASF) under one
@@ -19,7 +19,6 @@ package org.apache.maven.archiva.web.act
* under the License.
*/
-import org.apache.struts2.interceptor.ServletRequestAware;
import com.opensymphony.xwork2.Preparable;
import org.apache.maven.archiva.configuration.ArchivaConfiguration;
import org.apache.maven.archiva.configuration.Configuration;
@@ -29,59 +28,46 @@ import org.apache.maven.archiva.configur
import org.apache.maven.archiva.database.ArchivaDAO;
import org.apache.maven.archiva.database.constraints.MostRecentRepositoryScanStatistics;
import org.apache.maven.archiva.model.RepositoryContentStatistics;
+import org.apache.maven.archiva.repository.scanner.RepositoryScanner;
import org.apache.maven.archiva.security.ArchivaRoleConstants;
-import org.apache.maven.archiva.web.util.ContextUtils;
import org.apache.maven.archiva.web.action.PlexusActionSupport;
+import org.apache.maven.archiva.web.util.ContextUtils;
+import org.apache.struts2.interceptor.ServletRequestAware;
+import org.codehaus.plexus.cache.Cache;
import org.codehaus.plexus.redback.rbac.Resource;
+import org.codehaus.plexus.taskqueue.TaskQueue;
import org.codehaus.redback.integration.interceptor.SecureAction;
import org.codehaus.redback.integration.interceptor.SecureActionBundle;
import org.codehaus.redback.integration.interceptor.SecureActionException;
-import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import javax.servlet.http.HttpServletRequest;
/**
- * Shows the Repositories Tab for the administrator.
+ * Shows system status information for the administrator.
*
* @version $Id$
- * @plexus.component role="com.opensymphony.xwork2.Action" role-hint="repositoriesAction" instantiation-strategy="per-lookup"
+ * @plexus.component role="com.opensymphony.xwork2.Action" role-hint="systemStatus" instantiation-strategy="per-lookup"
*/
-public class RepositoriesAction
+public class SystemStatusAction
extends PlexusActionSupport
- implements SecureAction, ServletRequestAware, Preparable
+ implements SecureAction
{
/**
- * @plexus.requirement
- */
- private ArchivaConfiguration archivaConfiguration;
-
- private List<ManagedRepositoryConfiguration> managedRepositories;
-
- private List<RemoteRepositoryConfiguration> remoteRepositories;
-
- private Map<String, RepositoryContentStatistics> repositoryStatistics;
-
- private Map<String, List<String>> repositoryToGroupMap;
-
- /**
- * @plexus.requirement role-hint="jdo"
+ * @plexus.requirement role="org.codehaus.plexus.taskqueue.TaskQueue"
*/
- private ArchivaDAO dao;
+ private Map<String,TaskQueue> queues;
/**
- * Used to construct the repository WebDAV URL in the repository action.
+ * @plexus.requirement role="org.codehaus.plexus.cache.Cache"
*/
- private String baseUrl;
+ private Map<String,Cache> caches;
- public void setServletRequest( HttpServletRequest request )
- {
- // TODO: is there a better way to do this?
- this.baseUrl = ContextUtils.getBaseURL( request, "repository" );
- }
+ private String memoryStatus;
public SecureActionBundle getSecureActionBundle()
throws SecureActionException
@@ -94,52 +80,35 @@ public class RepositoriesAction
return bundle;
}
- @SuppressWarnings("unchecked")
- public void prepare()
- {
- Configuration config = archivaConfiguration.getConfiguration();
-
- remoteRepositories = new ArrayList<RemoteRepositoryConfiguration>( config.getRemoteRepositories() );
- managedRepositories = new ArrayList<ManagedRepositoryConfiguration>( config.getManagedRepositories() );
- repositoryToGroupMap = config.getRepositoryToGroupMap();
-
- Collections.sort( managedRepositories, new RepositoryConfigurationComparator() );
- Collections.sort( remoteRepositories, new RepositoryConfigurationComparator() );
-
- repositoryStatistics = new HashMap<String, RepositoryContentStatistics>();
- for ( ManagedRepositoryConfiguration repo : managedRepositories )
- {
- List<RepositoryContentStatistics> results =
- (List<RepositoryContentStatistics>) dao.query( new MostRecentRepositoryScanStatistics( repo.getId() ) );
- if ( !results.isEmpty() )
- {
- repositoryStatistics.put( repo.getId(), results.get( 0 ) );
- }
- }
- }
-
- public List<ManagedRepositoryConfiguration> getManagedRepositories()
+ public String execute()
{
- return managedRepositories;
+ Runtime runtime = Runtime.getRuntime();
+ runtime.gc();
+ long total = runtime.totalMemory();
+ long used = total - runtime.freeMemory();
+ long max = runtime.maxMemory();
+ memoryStatus = formatMemory(used) + "/" + formatMemory(total) + " (Max: " + formatMemory(max) + ")";
+
+ return SUCCESS;
}
- public List<RemoteRepositoryConfiguration> getRemoteRepositories()
+ private static String formatMemory( long l )
{
- return remoteRepositories;
+ return l / ( 1024 * 1024 ) + "M";
}
- public Map<String, RepositoryContentStatistics> getRepositoryStatistics()
+ public String getMemoryStatus()
{
- return repositoryStatistics;
+ return memoryStatus;
}
- public String getBaseUrl()
+ public Map<String, Cache> getCaches()
{
- return baseUrl;
+ return caches;
}
- public Map<String, List<String>> getRepositoryToGroupMap()
+ public Map<String, TaskQueue> getQueues()
{
- return repositoryToGroupMap;
+ return queues;
}
}
Modified: archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/resources/struts.xml
URL: http://svn.apache.org/viewvc/archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/resources/struts.xml?rev=1042625&r1=1042624&r2=1042625&view=diff
==============================================================================
--- archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/resources/struts.xml (original)
+++ archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/resources/struts.xml Mon Dec 6 13:17:48 2010
@@ -475,6 +475,10 @@
</result>
</action>
+ <action name="systemStatus" class="systemStatus">
+ <result name="success">/WEB-INF/jsp/admin/systemStatus.jsp</result>
+ </action>
+
<!-- .\ LEGACY SUPPORT \.__________________________________________ -->
<action name="legacyArtifactPath" class="legacyArtifactPathAction" method="input">
Copied: archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/jsp/admin/systemStatus.jsp (from r1042516, archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/jsp/admin/repositories.jsp)
URL: http://svn.apache.org/viewvc/archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/jsp/admin/systemStatus.jsp?p2=archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/jsp/admin/systemStatus.jsp&p1=archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/jsp/admin/repositories.jsp&r1=1042516&r2=1042625&rev=1042625&view=diff
==============================================================================
--- archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/jsp/admin/repositories.jsp (original)
+++ archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/jsp/admin/systemStatus.jsp Mon Dec 6 13:17:48 2010
@@ -20,342 +20,66 @@
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib prefix="redback" uri="http://plexus.codehaus.org/redback/taglib-1.0" %>
-<%@ taglib prefix="archiva" uri="/WEB-INF/taglib.tld" %>
+<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
+<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>
<html>
<head>
- <title>Administration - Repositories</title>
+ <title>Administration - System Status</title>
<s:head/>
- <script type="text/javascript" src="<c:url value='/js/jquery-1.3.2.min.js'/>"></script>
- <script type="text/javascript">
- $(document).ready(function(){
-
- $(".pom").hide();
- $("a.expand").click(function(event){
- event.preventDefault();
- $(this).siblings("pre").toggle("slow");
- });
-
- });
- </script>
</head>
<body>
-<h1>Administration - Repositories</h1>
+<h1>Administration - System Status</h1>
<div id="contentArea">
-<s:actionerror/>
-<s:actionmessage/>
+ <s:actionerror/>
+ <s:actionmessage/>
+
+ <h2>Queues</h2>
+
+ <table>
+ <tr>
+ <th>Queue</th>
+ <th>Size</th>
+ </tr>
+ <c:forEach var="queueEntry" items="${queues}">
+ <c:set var="queue" value="${queueEntry.value.queueSnapshot}"/>
+ <tr>
+ <td>${queueEntry.key}</td>
+ <td>${fn:length(queue)}</td>
+ </tr>
+ </c:forEach>
+ </table>
-<div class="admin">
-<div class="controls">
- <redback:ifAuthorized permission="archiva-manage-configuration">
- <s:url id="addRepositoryUrl" action="addRepository"/>
- <s:a href="%{addRepositoryUrl}">
- <img src="<c:url value="/images/icons/create.png" />" alt="" width="16" height="16"/>
- Add
- </s:a>
- </redback:ifAuthorized>
-</div>
-<h2>Managed Repositories</h2>
-
-<c:choose>
-<c:when test="${empty (managedRepositories)}">
- <%-- No Managed Repositories. --%>
- <strong>There are no managed repositories configured yet.</strong>
-</c:when>
-<c:otherwise>
-<%-- Display the repositories. --%>
-
-<c:forEach items="${managedRepositories}" var="repository" varStatus="i">
-<c:choose>
- <c:when test='${(i.index)%2 eq 0}'>
- <c:set var="rowColor" value="dark" scope="page"/>
- </c:when>
- <c:otherwise>
- <c:set var="rowColor" value="lite" scope="page"/>
- </c:otherwise>
-</c:choose>
-
-<div class="repository ${rowColor}">
-
-<div class="controls">
- <%-- TODO: make some icons --%>
- <redback:ifAnyAuthorized permissions="archiva-manage-configuration">
- <s:url id="editRepositoryUrl" action="editRepository">
- <s:param name="repoid" value="%{#attr.repository.id}"/>
- </s:url>
- <s:url id="deleteRepositoryUrl" action="confirmDeleteRepository">
- <s:param name="repoid" value="%{#attr.repository.id}"/>
- </s:url>
- <s:a href="%{editRepositoryUrl}">
- <img src="<c:url value="/images/icons/edit.png" />" alt="" width="16" height="16"/>
- Edit
- </s:a>
- <s:a href="%{deleteRepositoryUrl}">
- <img src="<c:url value="/images/icons/delete.gif" />" alt="" width="16" height="16"/>
- Delete
- </s:a>
- </redback:ifAnyAuthorized>
- <c:url var="rssFeedIconUrl" value="/images/icons/rss-feed.png"/>
- <a href="/archiva/feeds/${repository.id}">
- <img src="${rssFeedIconUrl}" />
- </a>
-</div>
-
-<div style="float: left">
- <img src="<c:url value="/images/archiva-splat-32.gif"/>" alt="" width="32" height="32"/>
-</div>
-
-<h3 class="repository">${repository.name}</h3>
-
-<table class="infoTable">
-<tr>
- <th>Identifier</th>
- <td>
- <code>${repository.id}</code>
- </td>
-</tr>
-<tr>
- <th>Name</th>
- <td>
- <code>${repository.name}</code>
- </td>
-</tr>
-<tr>
- <th>Directory</th>
- <td>${repository.location}</td>
-</tr>
-<c:if test="${!empty (repository.indexDir)}">
- <tr>
- <th>Index Directory</th>
- <td>${repository.indexDir}</td>
- </tr>
-</c:if>
-<tr>
- <th>WebDAV URL</th>
- <td><a href="${baseUrl}/${repository.id}/">${baseUrl}/${repository.id}/</a></td>
-</tr>
-<tr>
- <th>Type</th>
- <%-- TODO: can probably just use layout appended to a key prefix in i18n to simplify this --%>
- <td>
- <c:choose>
- <c:when test="${repository.layout == 'default'}">
- Maven 2.x Repository
- </c:when>
- <c:otherwise>
- Maven 1.x Repository
- </c:otherwise>
- </c:choose>
- </td>
-</tr>
-<c:if test="${!empty (repositoryToGroupMap[repository.id])}">
- <tr>
- <th>Groups</th>
- <td>
- <c:forEach items="${repositoryToGroupMap[repository.id]}" varStatus="i" var="group">
- ${group}<c:if test="${!i.last}">,</c:if>
- </c:forEach>
- </td>
- </tr>
-</c:if>
-<tr>
- <th>Releases Included</th>
- <td class="${repository.releases ? 'donemark' : 'errormark'} booleanIcon"> </td>
-</tr>
-<tr>
- <th>Snapshots Included</th>
- <td class="${repository.snapshots ? 'donemark' : 'errormark'} booleanIcon"> </td>
-</tr>
-<c:if test="${repository.snapshots}">
- <tr>
- <th>Delete Released Snapshots</th>
- <td class="${repository.deleteReleasedSnapshots ? 'donemark' : 'errormark'} booleanIcon"> </td>
- </tr>
- <tr>
- <th>Repository Purge By Days Older Than</th>
- <td>${repository.daysOlder}</td>
- </tr>
- <tr>
- <th>Repository Purge By Retention Count</th>
- <td>${repository.retentionCount}</td>
- </tr>
-</c:if>
-<tr>
- <th>Scanned</th>
- <td class="${repository.scanned ? 'donemark' : 'errormark'} booleanIcon"> </td>
-</tr>
-<c:if test="${repository.scanned}">
- <tr>
- <th>Scanning Cron</th>
- <td>${repository.refreshCronExpression}</td>
- </tr>
- <tr>
- <th>
- Actions
- </th>
- <td>
- <redback:ifAuthorized permission="archiva-run-indexer">
- <s:form action="indexRepository" theme="simple">
- <s:hidden name="repoid" value="%{#attr.repository.id}"/>
- <table>
- <tr>
- <td><s:checkbox name="scanAll" value="scanAll"/>Process All Artifacts</td>
- </tr>
- <tr>
- <td><s:submit value="Scan Repository Now"/></td>
- </tr>
- </table>
- </s:form>
- </redback:ifAuthorized>
- </td>
- </tr>
- <tr>
- <th>Stats</th>
- <td>
- <c:set var="stats" value="${repositoryStatistics[repository.id]}"/>
- <c:choose>
- <c:when test="${empty (stats)}">
- No Statistics Available.
- </c:when>
- <c:otherwise>
- <table>
- <tr>
- <th>Last Scanned</th>
- <td>${stats.whenGathered}</td>
- </tr>
- <tr>
- <th>Duration</th>
- <td>${stats.duration} ms</td>
- </tr>
- <tr>
- <th>Total File Count</th>
- <td>${stats.totalFileCount}
- </tr>
- <tr>
- <th>New Files Found</th>
- <td>${stats.newFileCount}
- </tr>
- </table>
- </c:otherwise>
- </c:choose>
- </td>
- </tr>
-</c:if>
-<tr>
- <th>POM Snippet</th>
- <td>
- <archiva:copy-paste-snippet object="${repository}" wrapper="toggle" />
- </td>
-</tr>
-</table>
-
-</div>
-</c:forEach>
-
-</c:otherwise>
-</c:choose>
-
-<div class="controls">
- <redback:ifAuthorized permission="archiva-manage-configuration">
- <s:url id="addRepositoryUrl" action="addRemoteRepository"/>
- <s:a href="%{addRepositoryUrl}">
- <img src="<c:url value="/images/icons/create.png" />" alt="" width="16" height="16"/>
- Add
- </s:a>
- </redback:ifAuthorized>
-</div>
-<h2>Remote Repositories</h2>
-
-<c:choose>
- <c:when test="${empty (remoteRepositories)}">
- <%-- No Remote Repositories. --%>
- <strong>There are no remote repositories configured yet.</strong>
- </c:when>
- <c:otherwise>
- <%-- Display the repositories. --%>
- <c:forEach items="${remoteRepositories}" var="repository" varStatus="i">
- <c:choose>
- <c:when test='${(i.index)%2 eq 0}'>
- <c:set var="rowColor" value="dark" scope="page"/>
- </c:when>
- <c:otherwise>
- <c:set var="rowColor" value="lite" scope="page"/>
- </c:otherwise>
- </c:choose>
-
- <div class="repository ${rowColor}">
-
- <div class="controls">
- <redback:ifAnyAuthorized permissions="archiva-manage-configuration">
- <s:url id="editRepositoryUrl" action="editRemoteRepository">
- <s:param name="repoid" value="%{#attr.repository.id}"/>
- </s:url>
- <s:a href="%{editRepositoryUrl}">
- <img src="<c:url value="/images/icons/edit.png" />" alt="" width="16" height="16"/>
- Edit
- </s:a>
- <s:url id="deleteRepositoryUrl" action="confirmDeleteRemoteRepository">
- <s:param name="repoid" value="%{#attr.repository.id}"/>
- </s:url>
- <s:a href="%{deleteRepositoryUrl}">
- <img src="<c:url value="/images/icons/delete.gif" />" alt="" width="16" height="16"/>
- Delete
- </s:a>
- </redback:ifAnyAuthorized>
- </div>
-
- <div style="float: left">
- <img src="<c:url value="/images/archiva-world.png"/>" alt="" width="32" height="32"/>
- </div>
-
- <h3 class="repository">${repository.name}</h3>
-
- <table class="infoTable">
- <tr>
- <th>Identifier</th>
- <td>
- <code>${repository.id}</code>
- </td>
- </tr>
- <tr>
- <th>Name</th>
- <td>
- <code>${repository.name}</code>
- </td>
- </tr>
- <tr>
- <th>URL</th>
- <td>${repository.url}</td>
- </tr>
- <tr>
- <th>Type</th>
- <%-- TODO: can probably just use layout appended to a key prefix in i18n to simplify this --%>
- <td>
- <c:choose>
- <c:when test="${repository.layout == 'default'}">
- Maven 2.x Repository
- </c:when>
- <c:otherwise>
- Maven 1.x Repository
- </c:otherwise>
- </c:choose>
- </td>
- </tr>
- </table>
+ <h2>Caches</h2>
- </div>
+ <table>
+ <tr>
+ <th>Cache</th>
+ <th>Size</th>
+ <th>Hits</th>
+ <th>Misses</th>
+ <th>Hit Ratio</th>
+ <th> </th>
+ </tr>
+ <c:forEach var="cacheEntry" items="${caches}">
+ <tr>
+ <td>${cacheEntry.key}</td>
+ <td>${cacheEntry.value.statistics.size}</td>
+ <td>${cacheEntry.value.statistics.cacheHits}</td>
+ <td>${cacheEntry.value.statistics.cacheMiss}</td>
+ <td><fmt:formatNumber value="${cacheEntry.value.statistics.cacheHitRate}" pattern="#%"/></td>
+ <td><a href="javascript:alert('Not yet implemented')">Flush</a></td>
+ </tr>
</c:forEach>
- </c:otherwise>
-</c:choose>
+ </table>
-</div>
+ <h2>Memory</h2>
-</div>
+ <p>${memoryStatus}</p>
</body>
</html>
Modified: archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/jsp/decorators/default.jsp
URL: http://svn.apache.org/viewvc/archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/jsp/decorators/default.jsp?rev=1042625&r1=1042624&r2=1042625&view=diff
==============================================================================
--- archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/jsp/decorators/default.jsp (original)
+++ archiva/branches/archiva-1.3.x/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/WEB-INF/jsp/decorators/default.jsp Mon Dec 6 13:17:48 2010
@@ -150,6 +150,9 @@
<li class="none">
<my:currentWWUrl action="database" namespace="/admin">Database</my:currentWWUrl>
</li>
+ <li class="none">
+ <my:currentWWUrl action="systemStatus" namespace="/admin">System Statu</my:currentWWUrl>
+ </li>
<%-- TODO: future options here.
* Repository Syncing Connectors. (rsync, ftp, scp, etc...)
* Web Services (enable / disable), role based?