* Change vul result bar texts

* Support server-driven pagination for replication jobs
This commit is contained in:
Steven Zou 2017-08-03 15:09:13 +08:00 committed by Yan
parent b4a81db15c
commit baa863d026
14 changed files with 232 additions and 100 deletions

View File

@ -14,7 +14,7 @@ import { DatePickerComponent } from '../datetime-picker/datetime-picker.componen
import { DateValidatorDirective } from '../datetime-picker/date-validator.directive';
import { FilterComponent } from '../filter/filter.component';
import { InlineAlertComponent } from '../inline-alert/inline-alert.component';
import { ReplicationRule, ReplicationJob, Endpoint } from '../service/interface';
import { ReplicationRule, ReplicationJob, Endpoint, ReplicationJobItem } from '../service/interface';
import { ErrorHandler } from '../error-handler/error-handler';
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
@ -71,7 +71,7 @@ describe('CreateEditRuleComponent (inline template)', ()=>{
}
];
let mockJobs: ReplicationJob[] = [
let mockJobs: ReplicationJobItem[] = [
{
"id": 1,
"status": "stopped",
@ -98,6 +98,11 @@ describe('CreateEditRuleComponent (inline template)', ()=>{
}
];
let mockJob: ReplicationJob = {
metadata: {xTotalCount: 3},
data: mockJobs
};
let mockEndpoints: Endpoint[] = [
{
"id": 1,
@ -205,7 +210,7 @@ describe('CreateEditRuleComponent (inline template)', ()=>{
spyRules = spyOn(replicationService, 'getReplicationRules').and.returnValues(Promise.resolve(mockRules));
spyOneRule = spyOn(replicationService, 'getReplicationRule').and.returnValue(Promise.resolve(mockRule));
spyJobs = spyOn(replicationService, 'getJobs').and.returnValues(Promise.resolve(mockJobs));
spyJobs = spyOn(replicationService, 'getJobs').and.returnValues(Promise.resolve(mockJob));
fixture.detectChanges();
});

View File

@ -46,7 +46,7 @@ export const REPLICATION_TEMPLATE: string = `
</div>
</div>
<div *ngIf="withReplicationJob" class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<clr-datagrid [clrDgLoading]="loading">
<clr-datagrid [clrDgLoading]="jobsLoading" (clrDgRefresh)="clrLoadJobs($event)">
<clr-dg-column [clrDgField]="'repository'">{{'REPLICATION.NAME' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'status'">{{'REPLICATION.STATUS' | translate}}</clr-dg-column>
<clr-dg-column [clrDgField]="'operation'">{{'REPLICATION.OPERATION' | translate}}</clr-dg-column>
@ -54,7 +54,7 @@ export const REPLICATION_TEMPLATE: string = `
<clr-dg-column [clrDgSortBy]="updateTimeComparator">{{'REPLICATION.UPDATE_TIME' | translate}}</clr-dg-column>
<clr-dg-column>{{'REPLICATION.LOGS' | translate}}</clr-dg-column>
<clr-dg-placeholder>{{'REPLICATION.JOB_PLACEHOLDER' | translate }}</clr-dg-placeholder>
<clr-dg-row *clrDgItems="let j of jobs" [clrDgItem]='j'>
<clr-dg-row *ngFor="let j of jobs">
<clr-dg-cell>{{j.repository}}</clr-dg-cell>
<clr-dg-cell>{{j.status}}</clr-dg-cell>
<clr-dg-cell>{{j.operation}}</clr-dg-cell>
@ -66,13 +66,12 @@ export const REPLICATION_TEMPLATE: string = `
<a href="javascript:void(0);" (click)="viewLog(j.id)">
<clr-icon shape="clipboard"></clr-icon>
</a></ng-template>
</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPLICATION.OF' | translate}}</span>
<span *ngIf="showPaginationIndex">{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} {{'REPLICATION.OF' | translate}}</span>
{{pagination.totalItems}} {{'REPLICATION.ITEMS' | translate}}
<clr-dg-pagination #pagination [clrDgPageSize]="5"></clr-dg-pagination>
<clr-dg-pagination #pagination [(clrDgPage)]="currentPage" [clrDgPageSize]="pageSize" [clrDgTotalItems]="totalCount"></clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>

View File

@ -19,7 +19,7 @@ import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
import { ReplicationService, ReplicationDefaultService } from '../service/replication.service';
import { EndpointService, EndpointDefaultService } from '../service/endpoint.service';
import { JobLogViewerComponent } from '../job-log-viewer/job-log-viewer.component';
import { JobLogService, JobLogDefaultService } from '../service/index';
import { JobLogService, JobLogDefaultService, ReplicationJobItem } from '../service/index';
describe('Replication Component (inline template)', ()=>{
@ -65,7 +65,7 @@ describe('Replication Component (inline template)', ()=>{
}
];
let mockJobs: ReplicationJob[] = [
let mockJobs: ReplicationJobItem[] = [
{
"id": 1,
"status": "error",
@ -95,6 +95,11 @@ describe('Replication Component (inline template)', ()=>{
}
];
let mockJob: ReplicationJob = {
metadata: {xTotalCount: 3},
data: mockJobs
};
let mockEndpoints: Endpoint[] = [
{
"id": 1,
@ -200,7 +205,7 @@ describe('Replication Component (inline template)', ()=>{
replicationService = fixture.debugElement.injector.get(ReplicationService);
spyRules = spyOn(replicationService, 'getReplicationRules').and.returnValues(Promise.resolve(mockRules));
spyJobs = spyOn(replicationService, 'getJobs').and.returnValues(Promise.resolve(mockJobs));
spyJobs = spyOn(replicationService, 'getJobs').and.returnValues(Promise.resolve(mockJob));
fixture.detectChanges();
fixture.whenStable().then(()=>{

View File

@ -23,9 +23,16 @@ import { ErrorHandler } from '../error-handler/error-handler';
import { ReplicationService } from '../service/replication.service';
import { RequestQueryParams } from '../service/RequestQueryParams';
import { ReplicationRule, ReplicationJob, Endpoint } from '../service/interface';
import { ReplicationRule, ReplicationJob, Endpoint, ReplicationJobItem } from '../service/interface';
import { toPromise, CustomComparator } from '../utils';
import {
toPromise,
CustomComparator,
DEFAULT_PAGE_SIZE,
doFiltering,
doSorting,
calculatePage
} from '../utils';
import { Comparator } from 'clarity-angular';
@ -33,6 +40,7 @@ import { REPLICATION_TEMPLATE } from './replication.component.html';
import { REPLICATION_STYLE } from './replication.component.css';
import { JobLogViewerComponent } from '../job-log-viewer/index';
import { State } from "clarity-angular";
const ruleStatus: { [key: string]: any } = [
{ 'key': 'all', 'description': 'REPLICATION.ALL_STATUS' },
@ -63,7 +71,7 @@ export class SearchOption {
endTime: string = '';
endTimestamp: string = '';
page: number = 1;
pageSize: number = 5;
pageSize: number = DEFAULT_PAGE_SIZE;
}
@Component({
@ -93,10 +101,7 @@ export class ReplicationComponent implements OnInit {
rules: ReplicationRule[];
loading: boolean;
jobs: ReplicationJob[];
jobsTotalRecordCount: number;
jobsTotalPage: number;
jobs: ReplicationJobItem[];
toggleJobSearchOption = optionalSearch;
currentJobSearchOption: number;
@ -113,6 +118,13 @@ export class ReplicationComponent implements OnInit {
creationTimeComparator: Comparator<ReplicationJob> = new CustomComparator<ReplicationJob>('creation_time', 'date');
updateTimeComparator: Comparator<ReplicationJob> = new CustomComparator<ReplicationJob>('update_time', 'date');
//Server driven pagination
currentPage: number = 1;
totalCount: number = 0;
pageSize: number = DEFAULT_PAGE_SIZE;
currentState: State;
jobsLoading: boolean = false;
constructor(
private errorHandler: ErrorHandler,
private replicationService: ReplicationService,
@ -123,6 +135,10 @@ export class ReplicationComponent implements OnInit {
return !this.readonly && this.projectId ? true : false;
}
public get showPaginationIndex(): boolean {
return this.totalCount > 0;
}
ngOnInit() {
this.currentRuleStatus = this.ruleStatus[0];
this.currentJobStatus = this.jobStatus[0];
@ -143,32 +159,78 @@ export class ReplicationComponent implements OnInit {
}
}
fetchReplicationJobs() {
//Server driven data loading
clrLoadJobs(state: State): void {
if (!state || !state.page || !this.search.ruleId) {
return;
}
this.currentState = state;
let pageNumber: number = calculatePage(state);
if (pageNumber <= 0) { pageNumber = 1; }
let params: RequestQueryParams = new RequestQueryParams();
//Pagination
params.set("page", '' + pageNumber);
params.set("page_size", '' + this.pageSize);
//Search by status
if (this.search.status.trim()) {
params.set('status', this.search.status);
}
//Search by repository
if (this.search.repoName.trim()) {
params.set('repository', this.search.repoName);
}
//Search by timestamps
if (this.search.startTimestamp.trim()) {
params.set('start_time', this.search.startTimestamp);
}
if (this.search.endTimestamp.trim()) {
params.set('end_time', this.search.endTimestamp);
}
toPromise<ReplicationJob[]>(this.replicationService
this.jobsLoading = true;
toPromise<ReplicationJob>(this.replicationService
.getJobs(this.search.ruleId, params))
.then(
response => {
this.jobs = response;
this.totalCount = response.metadata.xTotalCount;
this.jobs = response.data;
//Do filtering and sorting
this.jobs = doFiltering<ReplicationJobItem>(this.jobs, state);
this.jobs = doSorting<ReplicationJobItem>(this.jobs, state);
this.jobsLoading = false;
}).catch(error => {
this.jobsLoading = false;
this.errorHandler.error(error);
});
}
loadFirstPage(): void {
let st: State = this.currentState;
if (!st) {
st = {
page: {}
};
}
st.page.size = this.pageSize;
st.page.from = 0;
st.page.to = this.pageSize - 1;
this.clrLoadJobs(st);
}
selectOneRule(rule: ReplicationRule) {
if (rule) {
if (rule && rule.id) {
this.search.ruleId = rule.id || '';
this.search.repoName = '';
this.search.status = '';
this.currentJobSearchOption = 0;
this.currentJobStatus = { 'key': 'all', 'description': 'REPLICATION.ALL' };
this.fetchReplicationJobs();
this.loadFirstPage();
}
}
@ -205,7 +267,7 @@ export class ReplicationComponent implements OnInit {
doSearchJobs(repoName: string) {
this.search.repoName = repoName;
this.fetchReplicationJobs();
this.loadFirstPage();
}
reloadRules(isReady: boolean) {
@ -220,7 +282,21 @@ export class ReplicationComponent implements OnInit {
}
refreshJobs() {
this.fetchReplicationJobs();
this.search.repoName = "";
this.search.startTimestamp = "";
this.search.endTimestamp = "";
this.search.status = "";
this.currentPage = 1;
let st: State = {
page: {
from: 0,
to: this.pageSize - 1,
size: this.pageSize
}
};
this.clrLoadJobs(st);
}
toggleSearchJobOptionalName(option: number) {
@ -229,12 +305,12 @@ export class ReplicationComponent implements OnInit {
doJobSearchByStartTime(fromTimestamp: string) {
this.search.startTimestamp = fromTimestamp;
this.fetchReplicationJobs();
this.loadFirstPage();
}
doJobSearchByEndTime(toTimestamp: string) {
this.search.endTimestamp = toTimestamp;
this.fetchReplicationJobs();
this.loadFirstPage();
}
viewLog(jobId: number | string): void {

View File

@ -100,7 +100,19 @@ export interface ReplicationRule extends Base {
* @export
* @interface ReplicationJob
*/
export interface ReplicationJob extends Base {
export interface ReplicationJob {
metadata?: Metadata;
data: ReplicationJobItem[];
}
/**
* Interface for replication job item.
*
* @export
* @interface ReplicationJob
*/
export interface ReplicationJobItem extends Base {
[key: string]: any | any[]
status: string;
repository: string;
policy_id: number;

View File

@ -1,6 +1,6 @@
import { Observable } from 'rxjs/Observable';
import { RequestQueryParams } from './RequestQueryParams';
import { ReplicationJob, ReplicationRule } from './interface';
import { ReplicationJob, ReplicationRule, ReplicationJobItem } from './interface';
import { Injectable, Inject } from "@angular/core";
import 'rxjs/add/observable/of';
import { Http, RequestOptions } from '@angular/http';
@ -109,11 +109,11 @@ export abstract class ReplicationService {
* @abstract
* @param {(number | string)} ruleId
* @param {RequestQueryParams} [queryParams]
* @returns {(Observable<ReplicationJob> | Promise<ReplicationJob[]> | ReplicationJob)}
* @returns {(Observable<ReplicationJob> | Promise<ReplicationJob> | ReplicationJob)}
*
* @memberOf ReplicationService
*/
abstract getJobs(ruleId: number | string, queryParams?: RequestQueryParams): Observable<ReplicationJob[]> | Promise<ReplicationJob[]> | ReplicationJob[];
abstract getJobs(ruleId: number | string, queryParams?: RequestQueryParams): Observable<ReplicationJob> | Promise<ReplicationJob> | ReplicationJob;
/**
* Get the log of the specified job.
@ -238,7 +238,7 @@ export class ReplicationDefaultService extends ReplicationService {
.catch(error => Promise.reject(error));
}
public getJobs(ruleId: number | string, queryParams?: RequestQueryParams): Observable<ReplicationJob[]> | Promise<ReplicationJob[]> | ReplicationJob[] {
public getJobs(ruleId: number | string, queryParams?: RequestQueryParams): Observable<ReplicationJob> | Promise<ReplicationJob> | ReplicationJob {
if (!ruleId || ruleId <= 0) {
return Promise.reject('Bad argument');
}
@ -249,7 +249,29 @@ export class ReplicationDefaultService extends ReplicationService {
queryParams.set('policy_id', '' + ruleId);
return this.http.get(this._jobBaseUrl, buildHttpRequestOptions(queryParams)).toPromise()
.then(response => response.json() as ReplicationJob[])
.then(response => {
let result: ReplicationJob = {
metadata: {
xTotalCount: 0
},
data: []
};
if (response && response.headers) {
let xHeader: string = response.headers.get("X-Total-Count");
if (xHeader) {
result.metadata.xTotalCount = parseInt(xHeader, 0);
}
}
result.data = response.json() as ReplicationJobItem[];
if (result.metadata.xTotalCount === 0) {
if (result.data && result.data.length > 0) {
result.metadata.xTotalCount = result.data.length;
}
}
return result;
})
.catch(error => Promise.reject(error));
}

View File

@ -92,6 +92,12 @@ export class RepositoryDefaultService extends RepositoryService {
result.data = response.json() as RepositoryItem[];
if (result.metadata.xTotalCount === 0) {
if (result.data && result.data.length > 0) {
result.metadata.xTotalCount = result.data.length;
}
}
return result;
})
.catch(error => Promise.reject(error));

View File

@ -67,20 +67,21 @@ export class ResultTipComponent implements OnInit {
});
}
this.translate.get(this.packageText(this.totalPackages)).subscribe((p1: string) => {
this.translate.get(this.packageText(this.packagesWithVul)).subscribe((p2: string) => {
this.translate.get(this.unitText(this.packagesWithVul)).subscribe((vul: string) => {
this.translate.get('VULNERABILITY.CHART.TOOLTIPS_TITLE',
let messageKey: string = "VULNERABILITY.CHART.TOOLTIPS_TITLE";
if (this.packagesWithVul > 1) {
messageKey = "VULNERABILITY.CHART.TOOLTIPS_TITLE_SINGULAR";
}
this.translate.get(messageKey,
{
totalVulnerability: this.packagesWithVul,
totalPackages: this.totalPackages,
package: p1,
packageExt: p2,
vulnerability: vul
})
.subscribe((res: string) => this._tipTitle = res);
});
});
});
}
tipWidth(severity: VulnerabilitySeverity): string {

View File

@ -31,7 +31,7 @@
"clarity-icons": "^0.9.8",
"clarity-ui": "^0.9.8",
"core-js": "^2.4.1",
"harbor-ui": "0.3.66",
"harbor-ui": "0.3.91",
"intl": "^1.2.5",
"mutationobserver-shim": "^0.3.2",
"ngx-cookie": "^1.0.0",

View File

@ -56,8 +56,7 @@
<clr-dg-cell>{{l.op_time | date: 'short'}}</clr-dg-cell>
</clr-dg-row>
<clr-dg-footer>
<span *ngIf="pagination.totalItems">{{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'AUDIT_LOG.OF' | translate}} </span>
{{pagination.totalItems }} {{'AUDIT_LOG.ITEMS' | translate}}
<span *ngIf="showPaginationIndex">{{pagination.firstItem + 1}} - {{pagination.lastItem +1 }} {{'AUDIT_LOG.OF' | translate}} </span> {{pagination.totalItems }} {{'AUDIT_LOG.ITEMS' | translate}}
<clr-dg-pagination #pagination [clrDgPageSize]="15" [(clrDgPage)]="currentPage" [clrDgTotalItems]="totalRecordCount"></clr-dg-pagination>
<!--{{totalRecordCount}} {{'AUDIT_LOG.ITEMS' | translate}}
<clr-dg-pagination [clrDgPageSize]="pageOffset" [clrDgTotalItems]="totalPage"></clr-dg-pagination>-->

View File

@ -68,7 +68,7 @@ export class AuditLogComponent implements OnInit {
pageOffset: number = 1;
pageSize: number = 15;
totalRecordCount: number;
totalRecordCount: number = 0;
currentPage: number;
totalPage: number;
@ -83,6 +83,10 @@ export class AuditLogComponent implements OnInit {
return this.toTimeInput.errors && this.toTimeInput.errors.dateValidator && (this.toTimeInput.dirty || this.toTimeInput.touched);
}
get showPaginationIndex(): boolean {
return this.totalRecordCount > 0;
}
constructor(private route: ActivatedRoute, private router: Router, private auditLogService: AuditLogService, private messageHandlerService: MessageHandlerService) {
//Get current user from registered resolver.
this.route.data.subscribe(data => this.currentUser = <SessionUser>data['auditLogResolver']);

View File

@ -487,8 +487,9 @@
"FOOT_OF": "of"
},
"CHART": {
"SCANNING_TIME": "Scan completed datetime",
"TOOLTIPS_TITLE": "This image includes {{totalPackages}} {{package}} with {{vulnerability}} in {{totalVulnerability}} of the {{packageExt}}."
"SCANNING_TIME": "Scan completed time:",
"TOOLTIPS_TITLE": "{{totalVulnerability}} of {{totalPackages}} {{package}} have known {{vulnerability}}.",
"TOOLTIPS_TITLE_SINGULAR": "{{totalVulnerability}} of {{totalPackages}} {{package}} has known {{vulnerability}}."
},
"SEVERITY": {
"HIGH": "high",

View File

@ -486,8 +486,9 @@
"FOOT_OF": "of"
},
"CHART": {
"SCANNING_TIME": "Scan completed datetime",
"TOOLTIPS_TITLE": "This image includes {{totalPackages}} {{package}} with {{vulnerability}} in {{totalVulnerability}} of the {{packageExt}}."
"SCANNING_TIME": "Scan completed time:",
"TOOLTIPS_TITLE": "{{totalVulnerability}} of {{totalPackages}} {{package}} have known {{vulnerability}}.",
"TOOLTIPS_TITLE_SINGULAR": "{{totalVulnerability}} of {{totalPackages}} {{package}} has known {{vulnerability}}."
},
"SEVERITY": {
"HIGH": "high",

View File

@ -487,8 +487,9 @@
"FOOT_OF": "总共"
},
"CHART": {
"SCANNING_TIME": "扫描完成",
"TOOLTIPS_TITLE": "在此镜像总共包含{{totalPackages}}{{package}},其中{{totalVulnerability}}个{{packageExt}}含有{{vulnerability}}。"
"SCANNING_TIME": "扫描完成时间:",
"TOOLTIPS_TITLE": "{{totalPackages}}个{{package}}中的{{totalVulnerability}}个含有{{vulnerability}}.",
"TOOLTIPS_TITLE_SINGULAR": "{{totalPackages}}个{{package}}中的{{totalVulnerability}}个含有{{vulnerability}}."
},
"SEVERITY": {
"HIGH": "严重",