<artifactId>archiva-repository-scanner</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.archiva</groupId>
+ <artifactId>repository-statistics</artifactId>
+ </dependency>
+
<dependency>
<groupId>org.apache.archiva.redback</groupId>
<artifactId>redback-authorization-api</artifactId>
<ref bean="commonServices#rest"/>
<ref bean="browseService#rest"/>
<ref bean="systemStatusService#rest"/>
+ <ref bean="reportRepositoriesService#rest" />
</jaxrs:serviceBeans>
<jaxrs:outInterceptors>
menu.system-status=System Status
menu.appearance-configuration=Appearance
menu.ui-configuration=UI Configuration
+menu.reports=Reports
#user
user.change.password.required=Change password required
fileupload.upload.required=You must upload your files first.
fileupload.artifacts.saved=Artifacts uploaded and saved on Server side.
-
+#reports
+report.title = Reports
+report.statistics.title = Repository statistics
+report.statistics.selected-repositories.label = Selected repositories
+repository.groups.available-repositories.label = Available repositories
+report.statistics.row-count.label = Row count
+report.statistics.start-date.label = Start date
+report.statistics.end-date.label = End date
+report.statistics.btn-view = View Statistics
+report.statistics.endDate.explanations-title=Info
+report.statistics.rowCount.explanations-title=Info
+report.statistics.rowCount.explanations=set the number of report you want to read. If you have selected two or more \
+ repositories, only the latest report of those repositories will be displayed.
+report.statistics.repositories.required=You must select at least one repository
+report.health.title = Repository Health
+report.select.all-repositories = All repositories
+report.health.groupId.label = GroupId
+report.health.repositoryId.label = RepositoryId
+report.health.btn-view = Show Report
+report.repository.illegal-access = You have no access to the repository {0}
+report.message.no-report=There is no report available.
+report.health.result.id=ID
+report.health.result.namespace=Namespace
+report.health.result.project=Project
+report.health.result.version=Version
+report.health.result.name=Name
+report.health.result.problem=Problem
+report.health.result.message=Message
+report.result.title=Result
* under the License.
*/
define("archiva.general-admin",["jquery","i18n","order!utils","order!jquery.tmpl","order!knockout","order!knockout.simpleGrid",
- "jquery.validate","bootstrap"]
+ "knockout.sortable","jquery.validate","bootstrap"]
, function() {
//-------------------------
});
}
+ //---------------------------
+ // report configuration page
+ //---------------------------
+ StatisticsReportRequest=function() {
+ this.repositories = ko.observableArray( [] );
+ this.rowCount = ko.observable(100);
+ this.startDate = ko.observable();
+ this.endDate = ko.observable();
+ }
+
+ reportStatisticsFormValidator=function(){
+ var validate = $("#report-statistics-form-id").validate({
+ rules: {
+ rowCountStatistics: {
+ required:true,
+ number: true,
+ min: 10
+ },
+ startDate: {
+ date: true
+ },
+ endDate: {
+ date: true
+ }
+ },
+ showErrors: function(validator, errorMap, errorList) {
+ customShowError("#report-statistics-form-id", validator, errorMap, errorMap);
+ }
+ })
+ }
+ ReportStatisticsViewModel=function(repositoriesAvailable){
+ reportStatisticsFormValidator();
+
+ var self=this;
+ this.availableRepositories = ko.observableArray( repositoriesAvailable );
+ this.statisticsReport = ko.observable( new StatisticsReportRequest() );
+
+ $("#startDate" ).datepicker();
+ $("#endDate" ).datepicker();
+ $("#rowCount-info-button" ).popover();
+
+ this.showStatistics=function() {
+ if (!$("#report-statistics-form-id").valid()) {
+ return;
+ }
+ if(this.statisticsReport().repositories().length==0){
+ displayErrorMessage( $.i18n.prop('report.statistics.repositories.required'), "repositoriesErrorMessage" );
+ return;
+ }
+ clearUserMessages( "repositoriesErrorMessage" );
+ var resultTabContent = $("#report-result");
+
+ url = "restServices/archivaServices/reportServices/getStatisticsReport/?rowCount="
+ + this.statisticsReport().rowCount();
+
+ for(var i=0;i<this.statisticsReport().repositories().length;i++){
+ url += "&repository=" + this.statisticsReport().repositories()[i];
+ }
+
+ if(this.statisticsReport().startDate()!=null){
+ url += "&startDate=" + this.statisticsReport().startDate();
+ }
+ if(this.statisticsReport().endDate()!=null){
+ url += "&endDate=" + this.statisticsReport().endDate();
+ }
+
+ $.ajax(url, {
+ type: "GET",
+ contentType: 'application/json',
+ dataType: 'json',
+ success: function(data){
+ resultTabContent.html( $( "#report-statistics" ).tmpl() );
+ var reportStatistics = new ReportStatisticsResultViewModel( data );
+ ko.applyBindings( reportStatistics, resultTabContent.get( 0 ) );
+ $( "#report-result-tab-li" ).removeClass( "hide" );
+ $( "#report-result-tab-li" ).addClass( "active" );
+ $( "#report-stat-tab-li" ).removeClass( "active" );
+ $( "#report-stat-tab-content" ).removeClass( "active" );
+ resultTabContent.addClass( "active" );
+ },
+ error: function(data){
+ var res = $.parseJSON(data.responseText);
+ displayErrorMessage($.i18n.prop(res.errorMessage));
+ }
+ });
+ }
+ }
+ ReportStatisticsResultViewModel=function(report){
+ this.reports = ko.observableArray( report );
+ var self = this;
+
+ this.tableReportViewModel = new ko.simpleGrid.viewModel({
+ data: this.reports,
+ viewModel: this,
+ columns: [
+ { headerText: "Repository ID", rowText: "repositoryId" },
+ { headerText: "Start Date", rowText: function(item){return new Date(item.scanStartTime);}},
+ { headerText: "Total File Count", rowText: "totalFileCount" },
+ { headerText: "Total Size", rowText: "totalArtifactFileSize" },
+ { headerText: "Artifact Count", rowText: "totalArtifactCount" },
+ { headerText: "Group Count", rowText: "totalGroupCount" },
+ { headerText: "Project Count", rowText: "totalProjectCount" },
+ { headerText: "Archetypes", rowText: function (item) { return item.totalCountForType.pom === "" ? item.totalCountForType.pom : "0"} },
+ { headerText: "Jars", rowText: function (item) { return item.totalCountForType.jar === "" ? item.totalCountForType.jar : "0" } },
+ { headerText: "Wars", rowText: function (item) { return item.totalCountForType.war === "" ? item.totalCountForType.war : "0" } },
+ { headerText: "Ears", rowText: function (item) { return item.totalCountForType.ear === "" ? item.totalCountForType.ear : "0" } },
+ { headerText: "Exes", rowText: function (item) { return item.totalCountForType.exe === "" ? item.totalCountForType.exe : "0" } },
+ { headerText: "Dlls", rowText: function (item) { return item.totalCountForType.dll === "" ? item.totalCountForType.dll : "0" } },
+ { headerText: "Zips", rowText: function (item) { return item.totalCountForType.zip === "" ? item.totalCountForType.zip : "0" } }
+ ],
+ pageSize: 10
+ });
+ }
+
+ HealthReportRequest=function(){
+ this.repositoryId = ko.observable();
+ this.rowCount = ko.observable(100);
+ this.groupId = ko.observable();
+ }
+ HealthReportResult=function(repositoryId,namespace,project,version,id,message,problem,name,facetId){
+ this.repositoryId = repositoryId;
+ this.namespace = namespace;
+ this.project = project;
+ this.version = version;
+ this.id = id;
+ this.message = message;
+ this.problem = problem;
+ this.name = name;
+ this.facetId = facetId;
+ }
+ mapHealthReportResult=function(data){
+ if(data==null) return;
+ return new HealthReportResult( data.repositoryId, data.namespace, data.project, data.version, data.id, data.message,
+ data.problem, data.name, data.facetId );
+ }
+ mapHealthReportResults=function(data){
+ if (data != null)
+ {
+ return $.isArray(data)? $.map(data, function(item){
+ return mapHealthReportResult(item);
+ }):[mapHealthReportResult(data)];
+ }
+ return [];
+ }
+ ReportHealthResultViewModel=function(report){
+ this.reports = ko.observableArray( report );
+ var self = this;
+ this.tableReportViewModel = new ko.simpleGrid.viewModel({
+ data: this.reports,
+ viewModel: this,
+ columns: [
+ { headerText: "ID", rowText: "id" },
+ { headerText: "Namespace", rowText: "namespace" },
+ { headerText: "Project", rowText: "project" },
+ { headerText: "Version", rowText: "version" },
+ { headerText: "Name", rowText: "name" },
+ { headerText: "Problem", rowText: "problem" },
+ { headerText: "Message", rowText: "message" }
+ ],
+ pageSize: 10
+ });
+ }
+
+ reportHealthFormValidator=function(){
+ var validate = $("#main-content #report-health-form-id").validate({
+ rules: {
+ rowCountHealth: {
+ required: true,
+ number: true,
+ min: 10
+ },
+ repositoryId: {
+ required: true
+ }
+ },
+ showErrors: function(validator, errorMap, errorList) {
+ customShowError("#main-content #report-health-form-id", validator, errorMap, errorMap);
+ }
+ })
+ }
+ ReportHealthViewModel=function(){
+ reportHealthFormValidator();
+ this.healthReport = ko.observable(new HealthReportRequest());
+
+ this.showHealth=function() {
+ if (!$("#main-content #report-health-form-id").valid()) {
+ return;
+ }
+
+ var resultTabContent = $("#report-result");
+
+ var url =
+ "restServices/archivaServices/reportServices/getHealthReports/" + this.healthReport().repositoryId() + "/"
+ + this.healthReport().rowCount();
+
+ if (this.healthReport().groupId())
+ {
+ url += "?groupId=" + this.healthReport().groupId();
+ }
+
+ $.ajax(url, {
+ type: "GET",
+ contentType: 'application/json',
+ dataType: 'json',
+ success: function(data){
+ var reports = new ReportHealthResultViewModel( mapHealthReportResults( data ) );
+ resultTabContent.html( $( "#report-health" ).tmpl() );
+ ko.applyBindings( reports, resultTabContent.get( 0 ) );
+ $( "#report-result-tab-li" ).removeClass( "hide" );
+ $( "#report-result-tab-li" ).addClass( "active" );
+ $( "#report-health-tab-li" ).removeClass( "active" );
+ $( "#report-health-tab-content" ).removeClass( "active" );
+ resultTabContent.addClass( "active" );
+ },
+ error: function(data){
+ var res = $.parseJSON(data.responseText);
+ displayRestError(res);
+ }
+ });
+ }
+ }
+
+ displayReportsPage=function(){
+ screenChange();
+ clearUserMessages();
+ var mainContent = $("#main-content");
+ mainContent.html(mediumSpinnerImg());
+ $.ajax("restServices/archivaServices/searchService/observableRepoIds", {
+ type: "GET",
+ dataType: 'json',
+ success: function(data) {
+ var repos = mapStringList( data );
+ mainContent.html( $( "#report-base" ).tmpl( {repositoriesList:repos} ) );
+ var statisticsReportViewModel = ReportStatisticsViewModel( repos );
+ var healthReportViewModel = ReportHealthViewModel( );
+ ko.applyBindings( statisticsReportViewModel, mainContent.get( 0 ) );
+ ko.applyBindings( healthReportViewModel, mainContent.get( 0 ) );
+ }
+ })
+ }
+
});
\ No newline at end of file
<p>
${$.i18n.prop('apperance-configuration.details-description')}
</p>
-
+
<form id="appearance-configuration-form-id" class="well form-horizontal">
<fieldset id="appearance-configuration-fielset-id">
<div class="control-group">
</tr>
{% } %}
</script>
+
+<script id="report-base" type="text/html">
+ <div class="page-header">
+ <h2>${$.i18n.prop('report.title')}</h2>
+ </div>
+
+ <ul class="nav nav-tabs">
+ <li class="active" id="report-stat-tab-li"><a href="#report-stat-tab-content" data-toggle="tab">${$.i18n.prop('report.statistics.title')}</a></li>
+ <li id="report-health-tab-li"><a href="#report-health-tab-content" data-toggle="tab">${$.i18n.prop('report.health.title')}</a></li>
+ <li id="report-result-tab-li" class="hide"><a href="#report-result" data-toggle="tab">${$.i18n.prop('report.result.title')}</a></li>
+ </ul>
+
+ <div class="tab-content">
+ <div class="tab-pane active" id="report-stat-tab-content">
+ <form class="form-horizontal" id="report-statistics-form-id">
+ <fieldset id="form-statistics-report">
+ <div class="row-fluid">
+ <div class="span6 row-fluid">
+ <div class="row-fluid" id="repositoriesErrorMessage"></div>
+ <div class="row-fluid">
+ <div class="span6 dotted">
+ <h5>${$.i18n.prop('report.statistics.selected-repositories.label')}</h5>
+ <hr/>
+ <div style="min-height: 40px"
+ data-bind="sortable: { template: 'statistics-repositories-order-tmpl', data:statisticsReport().repositories}">
+ </div>
+ </div>
+ <div class="span6 dotted">
+ <h5>${$.i18n.prop('repository.groups.available-repositories.label')}</h5>
+ <hr/>
+ <div style="min-height: 40px"
+ data-bind="sortable: {template: 'statistics-repositories-order-tmpl',data:availableRepositories}">
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="span6 well">
+ <div class="control-group">
+ <label for="rowCountStatistics" class="control-label">
+ ${$.i18n.prop('report.statistics.row-count.label')}
+ </label>
+ <div class="controls">
+ <input type="text" id="rowCountStatistics" name="rowCountStatistics" class="input-small"
+ data-bind="value: statisticsReport().rowCount"/>
+
+ <button class="btn btn-warning btn-mini" id="rowCount-info-button"
+ data-original-title="${$.i18n.prop('report.statistics.rowCount.explanations-title')}"
+ data-content="${$.i18n.prop('report.statistics.rowCount.explanations')}">
+ <i class="icon-question-sign icon-white"></i>
+ </button>
+ </div>
+ </div>
+ <div class="control-group">
+ <label for="startDate" class="control-label">
+ ${$.i18n.prop('report.statistics.start-date.label')}
+ </label>
+ <div class="controls">
+ <input type="text" id="startDate" name="startDate" class="input-small"
+ data-bind="value: statisticsReport().startDate"/>
+ </div>
+ </div>
+ <div class="control-group">
+ <label for="endDate" class="control-label">
+ ${$.i18n.prop('report.statistics.end-date.label')}
+ </label>
+ <div class="controls">
+ <input type="text" id="endDate" name="endDate" class="input-small"
+ data-bind="value: statisticsReport().endDate"/>
+ </div>
+ </div>
+ </div>
+ </div>
+ <div class="form-actions">
+ <button class="btn btn-primary" data-bind="click: showStatistics">
+ ${$.i18n.prop('report.statistics.btn-view')}
+ </button>
+ </div>
+ </fieldset>
+ </form>
+ </div>
+
+ <div class="tab-pane" id="report-health-tab-content">
+ <form class="form-horizontal" id="report-health-form-id">
+ <fieldset id="form-health-report">
+ <div class="control-group">
+ <label for="rowCountHealth" class="control-label">
+ ${$.i18n.prop('report.statistics.row-count.label')}
+ </label>
+ <div class="controls">
+ <input type="text" id="rowCountHealth" name="rowCountHealth" class="input-small required"
+ data-bind="value: healthReport().rowCount"/>
+ </div>
+ </div>
+ <div class="control-group">
+ <label for="groupId" class="control-label">
+ ${$.i18n.prop('report.health.groupId.label')}
+ </label>
+ <div class="controls">
+ <input type="text" id="groupId" name="groupId" data-bind="value: healthReport().groupId"/>
+ </div>
+ </div>
+ <div class="control-group">
+ <label for="repositoryId" class="control-label">
+ ${$.i18n.prop('report.health.repositoryId.label')}
+ </label>
+ <div class="controls">
+ <select id="repositoryId" name="repositoryId" data-bind="value: healthReport().repositoryId"
+ class="required">
+ <option value="all">${$.i18n.prop('report.select.all-repositories')}</option>
+ {{each(i, repoId) repositoriesList}}
+ <option value="${repoId}">${repoId}</option>
+ {{/each}}
+ </select>
+ </div>
+ </div>
+ <div class="form-actions">
+ <a href="#" class="btn btn-primary" data-bind="click: showHealth">
+ ${$.i18n.prop('report.health.btn-view')}
+ </a>
+ </div>
+ </fieldset>
+ </form>
+ </div>
+
+ <div class="tab-pane" id="report-result">
+ </div>
+</script>
+
+<script id="statistics-repositories-order-tmpl" type="text/html">
+ <div class="well draggable-item">
+ ${$data}
+ </div>
+</script>
+
+<script id="report-health" type="text/html">
+ <div class="page-header">
+ <h3>${$.i18n.prop('report.health.title')}</h3>
+ </div>
+ <table class="table table-bordered table-striped"
+ data-bind="simpleGrid: tableReportViewModel,simpleGridTemplate:'table-report-tmpl',pageLinksId:'reportHealthPageLinkId'">
+ </table>
+ <div id="reportHealthPageLinkId"></div>
+</script>
+
+<script id="report-statistics" type="text/html">
+ <div class="page-header">
+ <h3>${$.i18n.prop('report.statistics.title')}</h3>
+ </div>
+ <table class="table table-bordered table-striped"
+ data-bind="simpleGrid: tableReportViewModel,simpleGridTemplate:'table-report-tmpl',pageLinksId:'reportStatisticsPageLinkId'">
+ </table>
+ <div id="reportStatisticsPageLinkId"></div>
+</script>
+<script id="table-report-tmpl" type="text/html">
+ <thead>
+ {{each(i, columnDefinition) columns}}
+ <th>${ columnDefinition.headerText }</th>
+ {{/each}}
+ </thead>
+ <tbody>
+ {{each(i, row) itemsOnCurrentPage()}}
+ <tr>
+ {{each(i, columnDefinition) columns}}
+ {{var val = (typeof columnDefinition.rowText == 'function' ? columnDefinition.rowText(row) : row[columnDefinition.rowText])}}
+ <td>
+ ${val}
+ </td>
+ {{/each}}
+ </tr>
+ {{/each}}
+ </tbody>
+</script>
<li style="display:none" redback-permissions="{permissions: ['archiva-manage-configuration']}">
<a href="#" id="menu-ui-configuration-list-a" onclick="displayUiConfiguration()">${$.i18n.prop('menu.ui-configuration')}</a>
</li>
+ <li style="display:none" redback-permissions="{permissions: ['archiva-manage-configuration']}">
+ <a href="#" id="menu-report-list-a" onclick="displayReportsPage()">${$.i18n.prop('menu.reports')}</a>
+ </li>
+
</ul>
throws MetadataRepositoryException
{
RepositoryStatistics repositoryStatistics = new RepositoryStatistics();
+ repositoryStatistics.setRepositoryId( repositoryId );
repositoryStatistics.setScanStartTime( startTime );
repositoryStatistics.setScanEndTime( endTime );
repositoryStatistics.setTotalFileCount( totalFiles );
private static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone( "UTC" );
+ private String repositoryId;
+
public Date getScanEndTime()
{
return scanEndTime;
return scanEndTime.getTime() - scanStartTime.getTime();
}
+ public String getRepositoryId()
+ {
+ return repositoryId;
+ }
+
+ public void setRepositoryId( String repositoryId )
+ {
+ this.repositoryId = repositoryId;
+ }
+
public String getFacetId()
{
return FACET_ID;
properties.put( "totalGroupCount", String.valueOf( totalGroupCount ) );
properties.put( "totalProjectCount", String.valueOf( totalProjectCount ) );
properties.put( "newFileCount", String.valueOf( newFileCount ) );
+ properties.put( "repositoryId", repositoryId );
for ( Map.Entry<String, Long> entry : totalCountForType.entrySet() )
{
properties.put( "count-" + entry.getKey(), String.valueOf( entry.getValue() ) );
totalGroupCount = Long.parseLong( properties.get( "totalGroupCount" ) );
totalProjectCount = Long.parseLong( properties.get( "totalProjectCount" ) );
newFileCount = Long.parseLong( properties.get( "newFileCount" ) );
+ repositoryId = properties.get( "repositoryId" );
totalCountForType.clear();
for ( Map.Entry<String, String> entry : properties.entrySet() )
{
{
return false;
}
+ if ( !repositoryId.equals( that.repositoryId ) )
+ {
+ return false;
+ }
return true;
}
result = 31 * result + (int) ( totalProjectCount ^ ( totalProjectCount >>> 32 ) );
result = 31 * result + (int) ( newFileCount ^ ( newFileCount >>> 32 ) );
result = 31 * result + totalCountForType.hashCode();
+ result = 31 * result + repositoryId.hashCode();
return result;
}
return "RepositoryStatistics{" + "scanEndTime=" + scanEndTime + ", scanStartTime=" + scanStartTime +
", totalArtifactCount=" + totalArtifactCount + ", totalArtifactFileSize=" + totalArtifactFileSize +
", totalFileCount=" + totalFileCount + ", totalGroupCount=" + totalGroupCount + ", totalProjectCount=" +
- totalProjectCount + ", newFileCount=" + newFileCount + ", totalCountForType=" + totalCountForType + '}';
+ totalProjectCount + ", newFileCount=" + newFileCount + ", totalCountForType=" + totalCountForType + ", " +
+ "repositoryId=" + repositoryId + '}';
}
public Map<String, Long> getTotalCountForType()
public void setTotalCountForType( String type, long count )
{
- totalCountForType.put( type, count );
+ totalCountForType.put( type.replaceAll( "-", "_" ).replaceAll( "\\.", "_" ), count );
}
private static final class ZeroForNullHashMap<K, V extends Long> extends HashMap<K, V>