diff --git a/src/ui_ng/lib/package.json b/src/ui_ng/lib/package.json index 8c30651f1..280154a47 100644 --- a/src/ui_ng/lib/package.json +++ b/src/ui_ng/lib/package.json @@ -1,6 +1,6 @@ { "name": "harbor-ui", - "version": "0.2.0", + "version": "0.3.0", "description": "Harbor shared UI components based on Clarity and Angular4", "scripts": { "start": "ng serve --host 0.0.0.0 --port 4500 --proxy-config proxy.config.json", diff --git a/src/ui_ng/lib/pkg/package.json b/src/ui_ng/lib/pkg/package.json index 67ff13f07..063ab5530 100644 --- a/src/ui_ng/lib/pkg/package.json +++ b/src/ui_ng/lib/pkg/package.json @@ -1,6 +1,6 @@ { "name": "harbor-ui", - "version": "0.2.0", + "version": "0.3.0", "description": "Harbor shared UI components based on Clarity and Angular4", "author": "VMware", "module": "index.js", @@ -43,4 +43,4 @@ "web-animations-js": "^2.2.1", "zone.js": "^0.8.4" } -} \ No newline at end of file +} diff --git a/src/ui_ng/lib/src/config/vulnerability/vulnerability-config.component.template.ts b/src/ui_ng/lib/src/config/vulnerability/vulnerability-config.component.template.ts index 75517b71d..d6f750647 100644 --- a/src/ui_ng/lib/src/config/vulnerability/vulnerability-config.component.template.ts +++ b/src/ui_ng/lib/src/config/vulnerability/vulnerability-config.component.template.ts @@ -4,7 +4,13 @@ export const VULNERABILITY_CONFIG_HTML: string = `
- + + + + {{'CONFIG.SCANNING.DB_NOT_READY' | translate }} + + +
diff --git a/src/ui_ng/lib/src/config/vulnerability/vulnerability-config.component.ts b/src/ui_ng/lib/src/config/vulnerability/vulnerability-config.component.ts index 5ad9d2ed3..6d30ca18a 100644 --- a/src/ui_ng/lib/src/config/vulnerability/vulnerability-config.component.ts +++ b/src/ui_ng/lib/src/config/vulnerability/vulnerability-config.component.ts @@ -206,6 +206,10 @@ export class VulnerabilityConfigComponent { this.vulnerabilityConfig.scan_all_policy.value.type === "daily"; } + get isClairDBFullyReady(): boolean { + return this.clairDBStatus && this.clairDBStatus.overall_last_update > 0; + } + constructor( private scanningService: ScanningResultService, private errorHandler: ErrorHandler, diff --git a/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.spec.ts b/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.spec.ts index f07154c89..0c88b2f17 100644 --- a/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.spec.ts +++ b/src/ui_ng/lib/src/create-edit-rule/create-edit-rule.component.spec.ts @@ -20,6 +20,7 @@ import { ErrorHandler } from '../error-handler/error-handler'; 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'; describe('CreateEditRuleComponent (inline template)', ()=>{ @@ -175,7 +176,8 @@ describe('CreateEditRuleComponent (inline template)', ()=>{ ConfirmationDialogComponent, DatePickerComponent, FilterComponent, - InlineAlertComponent + InlineAlertComponent, + JobLogViewerComponent ], providers: [ ErrorHandler, diff --git a/src/ui_ng/lib/src/harbor-library.module.ts b/src/ui_ng/lib/src/harbor-library.module.ts index cb3123449..8d309caeb 100644 --- a/src/ui_ng/lib/src/harbor-library.module.ts +++ b/src/ui_ng/lib/src/harbor-library.module.ts @@ -23,6 +23,7 @@ import { DATETIME_PICKER_DIRECTIVES } from './datetime-picker/index'; import { VULNERABILITY_DIRECTIVES } from './vulnerability-scanning/index'; import { PUSH_IMAGE_BUTTON_DIRECTIVES } from './push-image/index'; import { CONFIGURATION_DIRECTIVES } from './config/index'; +import { JOB_LOG_VIEWER_DIRECTIVES } from './job-log-viewer/index'; import { SystemInfoService, @@ -153,7 +154,8 @@ export function initConfig(translateInitializer: TranslateServiceInitializer, co DATETIME_PICKER_DIRECTIVES, VULNERABILITY_DIRECTIVES, PUSH_IMAGE_BUTTON_DIRECTIVES, - CONFIGURATION_DIRECTIVES + CONFIGURATION_DIRECTIVES, + JOB_LOG_VIEWER_DIRECTIVES ], exports: [ LOG_DIRECTIVES, @@ -173,6 +175,7 @@ export function initConfig(translateInitializer: TranslateServiceInitializer, co VULNERABILITY_DIRECTIVES, PUSH_IMAGE_BUTTON_DIRECTIVES, CONFIGURATION_DIRECTIVES, + JOB_LOG_VIEWER_DIRECTIVES, TranslateModule ], providers: [] diff --git a/src/ui_ng/lib/src/index.ts b/src/ui_ng/lib/src/index.ts index a74986038..ad746c21a 100644 --- a/src/ui_ng/lib/src/index.ts +++ b/src/ui_ng/lib/src/index.ts @@ -15,4 +15,5 @@ export * from './vulnerability-scanning/index'; export * from './i18n/index'; export * from './push-image/index'; export * from './third-party/index'; -export * from './config/index'; \ No newline at end of file +export * from './config/index'; +export * from './job-log-viewer/index'; \ No newline at end of file diff --git a/src/ui_ng/lib/src/job-log-viewer/index.ts b/src/ui_ng/lib/src/job-log-viewer/index.ts new file mode 100644 index 000000000..5035328e6 --- /dev/null +++ b/src/ui_ng/lib/src/job-log-viewer/index.ts @@ -0,0 +1,9 @@ +import { Type } from '@angular/core'; + +import { JobLogViewerComponent } from './job-log-viewer.component'; + +export * from './job-log-viewer.component'; + +export const JOB_LOG_VIEWER_DIRECTIVES: Type[] = [ + JobLogViewerComponent +]; \ No newline at end of file diff --git a/src/ui_ng/lib/src/job-log-viewer/job-log-viewer.component.spec.ts b/src/ui_ng/lib/src/job-log-viewer/job-log-viewer.component.spec.ts new file mode 100644 index 000000000..02defb2d5 --- /dev/null +++ b/src/ui_ng/lib/src/job-log-viewer/job-log-viewer.component.spec.ts @@ -0,0 +1,58 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { DebugElement } from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import { ReplicationService, ReplicationDefaultService } from '../service/index'; + +import { JobLogViewerComponent } from './job-log-viewer.component'; +import { SERVICE_CONFIG, IServiceConfig } from '../service.config'; +import { ErrorHandler } from '../error-handler/index'; +import { SharedModule } from '../shared/shared.module'; + +describe('JobLogViewerComponent (inline template)', () => { + let component: JobLogViewerComponent; + let fixture: ComponentFixture; + let serviceConfig: IServiceConfig; + let replicationService: ReplicationService; + let spy: jasmine.Spy; + let testConfig: IServiceConfig = { + replicationJobEndpoint: "/api/jobs/replication/testing" + }; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + SharedModule, + BrowserAnimationsModule + ], + declarations: [JobLogViewerComponent], + providers: [ + ErrorHandler, + { provide: SERVICE_CONFIG, useValue: testConfig }, + { provide: ReplicationService, useClass: ReplicationDefaultService } + ] + }); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(JobLogViewerComponent); + component = fixture.componentInstance; + + serviceConfig = TestBed.get(SERVICE_CONFIG); + replicationService = fixture.debugElement.injector.get(ReplicationService); + spy = spyOn(replicationService, 'getJobLog') + .and.returnValues(Promise.resolve("job log text")); + fixture.detectChanges(); + }); + + it('should be created', () => { + fixture.detectChanges(); + + expect(component).toBeTruthy(); + expect(serviceConfig).toBeTruthy(); + expect(serviceConfig.replicationJobEndpoint).toEqual("/api/jobs/replication/testing"); + + component.open(16); + }); + +}); diff --git a/src/ui_ng/lib/src/job-log-viewer/job-log-viewer.component.template.ts b/src/ui_ng/lib/src/job-log-viewer/job-log-viewer.component.template.ts new file mode 100644 index 000000000..5b4624edb --- /dev/null +++ b/src/ui_ng/lib/src/job-log-viewer/job-log-viewer.component.template.ts @@ -0,0 +1,33 @@ +export const JOB_LOG_VIEWER_TEMPLATE: string = ` + + + + + +`; + +export const JOB_LOG_VIEWER_STYLES: string = ` +.log-viewer-title { + line-height: 24px; + color: #000000; + font-size: 22px; +} + +.loading-back { + height: 358px; + display: flex; + align-items: center; + justify-content: center; +} +`; \ No newline at end of file diff --git a/src/ui_ng/lib/src/job-log-viewer/job-log-viewer.component.ts b/src/ui_ng/lib/src/job-log-viewer/job-log-viewer.component.ts new file mode 100644 index 000000000..f17ff3a8d --- /dev/null +++ b/src/ui_ng/lib/src/job-log-viewer/job-log-viewer.component.ts @@ -0,0 +1,60 @@ +// Copyright (c) 2017 VMware, Inc. All Rights Reserved. +// +// 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. +import { Component } from '@angular/core'; + +import { JOB_LOG_VIEWER_TEMPLATE, JOB_LOG_VIEWER_STYLES } from './job-log-viewer.component.template'; +import { ReplicationService } from '../service/index'; +import { ErrorHandler } from '../error-handler/index'; +import { toPromise } from '../utils'; + +@Component({ + selector: 'job-log-viewer', + template: JOB_LOG_VIEWER_TEMPLATE, + styles: [JOB_LOG_VIEWER_STYLES] +}) + +export class JobLogViewerComponent { + opened: boolean = false; + log: string = ''; + onGoing: boolean = true; + + constructor( + private replicationService: ReplicationService, + private errorHandler: ErrorHandler + ) { } + + open(jobId: number | string): void { + this.opened = true; + this.load(jobId); + } + + close(): void { + this.opened = false; + this.log = ""; + } + + load(jobId: number | string): void { + this.onGoing = true; + + toPromise(this.replicationService.getJobLog(jobId)) + .then((log: string) => { + this.onGoing = false; + this.log = log; + }) + .catch(error => { + this.onGoing = false; + this.errorHandler.error(error); + }); + } +} \ No newline at end of file diff --git a/src/ui_ng/lib/src/replication/replication.component.html.ts b/src/ui_ng/lib/src/replication/replication.component.html.ts index b8a0ebc4c..8d1621b13 100644 --- a/src/ui_ng/lib/src/replication/replication.component.html.ts +++ b/src/ui_ng/lib/src/replication/replication.component.html.ts @@ -61,7 +61,7 @@ export const REPLICATION_TEMPLATE: string = ` {{j.creation_time | date: 'short'}} {{j.update_time | date: 'short'}} - + @@ -73,4 +73,5 @@ export const REPLICATION_TEMPLATE: string = ` + `; \ No newline at end of file diff --git a/src/ui_ng/lib/src/replication/replication.component.spec.ts b/src/ui_ng/lib/src/replication/replication.component.spec.ts index ab4dd0692..9967f2c8e 100644 --- a/src/ui_ng/lib/src/replication/replication.component.spec.ts +++ b/src/ui_ng/lib/src/replication/replication.component.spec.ts @@ -18,6 +18,7 @@ import { ErrorHandler } from '../error-handler/error-handler'; 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'; describe('Replication Component (inline template)', ()=>{ @@ -175,7 +176,8 @@ describe('Replication Component (inline template)', ()=>{ ConfirmationDialogComponent, DatePickerComponent, FilterComponent, - InlineAlertComponent + InlineAlertComponent, + JobLogViewerComponent ], providers: [ ErrorHandler, diff --git a/src/ui_ng/lib/src/replication/replication.component.ts b/src/ui_ng/lib/src/replication/replication.component.ts index 06f42e638..59007c770 100644 --- a/src/ui_ng/lib/src/replication/replication.component.ts +++ b/src/ui_ng/lib/src/replication/replication.component.ts @@ -17,7 +17,7 @@ import { NgModel } from '@angular/forms'; import { TranslateService } from '@ngx-translate/core'; -import { ListReplicationRuleComponent} from '../list-replication-rule/list-replication-rule.component'; +import { ListReplicationRuleComponent } from '../list-replication-rule/list-replication-rule.component'; import { CreateEditRuleComponent } from '../create-edit-rule/create-edit-rule.component'; import { ErrorHandler } from '../error-handler/error-handler'; @@ -32,24 +32,26 @@ import { Comparator } from 'clarity-angular'; import { REPLICATION_TEMPLATE } from './replication.component.html'; import { REPLICATION_STYLE } from './replication.component.css'; -const ruleStatus: {[key: string]: any} = [ - { 'key': 'all', 'description': 'REPLICATION.ALL_STATUS'}, - { 'key': '1', 'description': 'REPLICATION.ENABLED'}, - { 'key': '0', 'description': 'REPLICATION.DISABLED'} +import { JobLogViewerComponent } from '../job-log-viewer/index'; + +const ruleStatus: { [key: string]: any } = [ + { 'key': 'all', 'description': 'REPLICATION.ALL_STATUS' }, + { 'key': '1', 'description': 'REPLICATION.ENABLED' }, + { 'key': '0', 'description': 'REPLICATION.DISABLED' } ]; -const jobStatus: {[key: string]: any} = [ +const jobStatus: { [key: string]: any } = [ { 'key': 'all', 'description': 'REPLICATION.ALL' }, - { 'key': 'pending', 'description': 'REPLICATION.PENDING' }, - { 'key': 'running', 'description': 'REPLICATION.RUNNING' }, - { 'key': 'error', 'description': 'REPLICATION.ERROR' }, + { 'key': 'pending', 'description': 'REPLICATION.PENDING' }, + { 'key': 'running', 'description': 'REPLICATION.RUNNING' }, + { 'key': 'error', 'description': 'REPLICATION.ERROR' }, { 'key': 'retrying', 'description': 'REPLICATION.RETRYING' }, - { 'key': 'stopped' , 'description': 'REPLICATION.STOPPED' }, + { 'key': 'stopped', 'description': 'REPLICATION.STOPPED' }, { 'key': 'finished', 'description': 'REPLICATION.FINISHED' }, - { 'key': 'canceled', 'description': 'REPLICATION.CANCELED' } + { 'key': 'canceled', 'description': 'REPLICATION.CANCELED' } ]; -const optionalSearch: {} = {0: 'REPLICATION.ADVANCED', 1: 'REPLICATION.SIMPLE'}; +const optionalSearch: {} = { 0: 'REPLICATION.ADVANCED', 1: 'REPLICATION.SIMPLE' }; export class SearchOption { ruleId: number | string; @@ -67,163 +69,172 @@ export class SearchOption { @Component({ selector: 'hbr-replication', template: REPLICATION_TEMPLATE, - styles: [ REPLICATION_STYLE ] + styles: [REPLICATION_STYLE] }) export class ReplicationComponent implements OnInit { - - @Input() projectId: number | string; - @Input() withReplicationJob: boolean; - @Output() redirect = new EventEmitter(); + @Input() projectId: number | string; + @Input() withReplicationJob: boolean; - search: SearchOption = new SearchOption(); + @Output() redirect = new EventEmitter(); - ruleStatus = ruleStatus; - currentRuleStatus: {key: string, description: string}; + search: SearchOption = new SearchOption(); - jobStatus = jobStatus; - currentJobStatus: {key: string, description: string}; + ruleStatus = ruleStatus; + currentRuleStatus: { key: string, description: string }; - changedRules: ReplicationRule[]; - initSelectedId: number | string; + jobStatus = jobStatus; + currentJobStatus: { key: string, description: string }; - rules: ReplicationRule[]; - loading: boolean; + changedRules: ReplicationRule[]; + initSelectedId: number | string; - jobs: ReplicationJob[]; + rules: ReplicationRule[]; + loading: boolean; - jobsTotalRecordCount: number; - jobsTotalPage: number; + jobs: ReplicationJob[]; - toggleJobSearchOption = optionalSearch; - currentJobSearchOption: number; + jobsTotalRecordCount: number; + jobsTotalPage: number; - @ViewChild(ListReplicationRuleComponent) - listReplicationRule: ListReplicationRuleComponent; + toggleJobSearchOption = optionalSearch; + currentJobSearchOption: number; - @ViewChild(CreateEditRuleComponent) - createEditPolicyComponent: CreateEditRuleComponent; + @ViewChild(ListReplicationRuleComponent) + listReplicationRule: ListReplicationRuleComponent; - creationTimeComparator: Comparator = new CustomComparator('creation_time', 'date'); - updateTimeComparator: Comparator = new CustomComparator('update_time', 'date'); + @ViewChild(CreateEditRuleComponent) + createEditPolicyComponent: CreateEditRuleComponent; - constructor( - private errorHandler: ErrorHandler, - private replicationService: ReplicationService, - private translateService: TranslateService) { - } + @ViewChild("replicationLogViewer") + replicationLogViewer: JobLogViewerComponent; - ngOnInit() { - this.currentRuleStatus = this.ruleStatus[0]; - this.currentJobStatus = this.jobStatus[0]; - this.currentJobSearchOption = 0; - } + creationTimeComparator: Comparator = new CustomComparator('creation_time', 'date'); + updateTimeComparator: Comparator = new CustomComparator('update_time', 'date'); - openModal(): void { - this.createEditPolicyComponent.openCreateEditRule(true); - } + constructor( + private errorHandler: ErrorHandler, + private replicationService: ReplicationService, + private translateService: TranslateService) { + } - openEditRule(rule: ReplicationRule) { - if(rule) { - let editable = true; - if(rule.enabled === 1) { - editable = false; - } - this.createEditPolicyComponent.openCreateEditRule(editable, rule.id); - } - } + ngOnInit() { + this.currentRuleStatus = this.ruleStatus[0]; + this.currentJobStatus = this.jobStatus[0]; + this.currentJobSearchOption = 0; + } - fetchReplicationJobs() { - - let params: RequestQueryParams = new RequestQueryParams(); - params.set('status', this.search.status); - params.set('repository', this.search.repoName); - params.set('start_time', this.search.startTimestamp); - params.set('end_time', this.search.endTimestamp); - - toPromise(this.replicationService - .getJobs(this.search.ruleId, params)) - .then( - response=>{ - this.jobs = response; - }).catch(error=>{ - this.errorHandler.error(error); - }); - } + openModal(): void { + this.createEditPolicyComponent.openCreateEditRule(true); + } - selectOneRule(rule: ReplicationRule) { - if (rule) { - this.search.ruleId = rule.id || ''; - this.search.repoName = ''; - this.search.status = ''; - this.currentJobSearchOption = 0; - this.currentJobStatus = { 'key': 'all', 'description': 'REPLICATION.ALL' }; - this.fetchReplicationJobs(); - } - } + openEditRule(rule: ReplicationRule) { + if (rule) { + let editable = true; + if (rule.enabled === 1) { + editable = false; + } + this.createEditPolicyComponent.openCreateEditRule(editable, rule.id); + } + } - customRedirect(rule: ReplicationRule) { - this.redirect.emit(rule); - } - - doSearchRules(ruleName: string) { - this.search.ruleName = ruleName; - this.listReplicationRule.retrieveRules(ruleName); - } + fetchReplicationJobs() { - doFilterRuleStatus($event: any) { - if ($event && $event.target && $event.target["value"]) { - let status = $event.target["value"]; - this.currentRuleStatus = this.ruleStatus.find((r: any)=>r.key === status); - this.listReplicationRule.filterRuleStatus(this.currentRuleStatus.key); - } - } + let params: RequestQueryParams = new RequestQueryParams(); + params.set('status', this.search.status); + params.set('repository', this.search.repoName); + params.set('start_time', this.search.startTimestamp); + params.set('end_time', this.search.endTimestamp); - doFilterJobStatus($event: any) { - if ($event && $event.target && $event.target["value"]) { - let status = $event.target["value"]; - - this.currentJobStatus = this.jobStatus.find((r: any)=>r.key === status); - if(this.currentJobStatus.key === 'all') { - status = ''; - } - this.search.status = status; - this.doSearchJobs(this.search.repoName); - - } - } + toPromise(this.replicationService + .getJobs(this.search.ruleId, params)) + .then( + response => { + this.jobs = response; + }).catch(error => { + this.errorHandler.error(error); + }); + } - doSearchJobs(repoName: string) { - this.search.repoName = repoName; - this.fetchReplicationJobs(); - } + selectOneRule(rule: ReplicationRule) { + if (rule) { + this.search.ruleId = rule.id || ''; + this.search.repoName = ''; + this.search.status = ''; + this.currentJobSearchOption = 0; + this.currentJobStatus = { 'key': 'all', 'description': 'REPLICATION.ALL' }; + this.fetchReplicationJobs(); + } + } - reloadRules(isReady: boolean) { - if(isReady) { - this.search.ruleName = ''; - this.listReplicationRule.retrieveRules(this.search.ruleName); - } - } + customRedirect(rule: ReplicationRule) { + this.redirect.emit(rule); + } - refreshRules() { - this.listReplicationRule.retrieveRules(); - } + doSearchRules(ruleName: string) { + this.search.ruleName = ruleName; + this.listReplicationRule.retrieveRules(ruleName); + } - refreshJobs() { - this.fetchReplicationJobs(); - } + doFilterRuleStatus($event: any) { + if ($event && $event.target && $event.target["value"]) { + let status = $event.target["value"]; + this.currentRuleStatus = this.ruleStatus.find((r: any) => r.key === status); + this.listReplicationRule.filterRuleStatus(this.currentRuleStatus.key); + } + } - toggleSearchJobOptionalName(option: number) { - (option === 1) ? this.currentJobSearchOption = 0 : this.currentJobSearchOption = 1; - } + doFilterJobStatus($event: any) { + if ($event && $event.target && $event.target["value"]) { + let status = $event.target["value"]; - doJobSearchByStartTime(fromTimestamp: string) { - this.search.startTimestamp = fromTimestamp; - this.fetchReplicationJobs(); - } + this.currentJobStatus = this.jobStatus.find((r: any) => r.key === status); + if (this.currentJobStatus.key === 'all') { + status = ''; + } + this.search.status = status; + this.doSearchJobs(this.search.repoName); - doJobSearchByEndTime(toTimestamp: string) { - this.search.endTimestamp = toTimestamp; - this.fetchReplicationJobs(); - } + } + } + + doSearchJobs(repoName: string) { + this.search.repoName = repoName; + this.fetchReplicationJobs(); + } + + reloadRules(isReady: boolean) { + if (isReady) { + this.search.ruleName = ''; + this.listReplicationRule.retrieveRules(this.search.ruleName); + } + } + + refreshRules() { + this.listReplicationRule.retrieveRules(); + } + + refreshJobs() { + this.fetchReplicationJobs(); + } + + toggleSearchJobOptionalName(option: number) { + (option === 1) ? this.currentJobSearchOption = 0 : this.currentJobSearchOption = 1; + } + + doJobSearchByStartTime(fromTimestamp: string) { + this.search.startTimestamp = fromTimestamp; + this.fetchReplicationJobs(); + } + + doJobSearchByEndTime(toTimestamp: string) { + this.search.endTimestamp = toTimestamp; + this.fetchReplicationJobs(); + } + + viewLog(jobId: number | string): void { + if (this.replicationLogViewer) { + this.replicationLogViewer.open(jobId); + } + } } \ No newline at end of file diff --git a/src/ui_ng/lib/src/service/replication.service.ts b/src/ui_ng/lib/src/service/replication.service.ts index 8d7bfb64d..f631b56ea 100644 --- a/src/ui_ng/lib/src/service/replication.service.ts +++ b/src/ui_ng/lib/src/service/replication.service.ts @@ -114,6 +114,16 @@ export abstract class ReplicationService { * @memberOf ReplicationService */ abstract getJobs(ruleId: number | string, queryParams?: RequestQueryParams): Observable | Promise | ReplicationJob[]; + + /** + * Get the log of the specified job. + * + * @abstract + * @param {(number | string)} jobId + * @returns {(Observable | Promise | string)} + * @memberof ReplicationService + */ + abstract getJobLog(jobId: number | string): Observable | Promise | string; } /** @@ -242,4 +252,15 @@ export class ReplicationDefaultService extends ReplicationService { .then(response => response.json() as ReplicationJob[]) .catch(error => Promise.reject(error)); } + + public getJobLog(jobId: number | string): Observable | Promise | string { + if (!jobId || jobId <= 0) { + return Promise.reject('Bad argument'); + } + + let logUrl: string = `${this._jobBaseUrl}/${jobId}/log`; + return this.http.get(logUrl).toPromise() + .then(response => response.text()) + .catch(error => Promise.reject(error)); + } } \ No newline at end of file diff --git a/src/ui_ng/lib/src/vulnerability-scanning/scanning.html.ts b/src/ui_ng/lib/src/vulnerability-scanning/scanning.html.ts index 9bf12093b..d88d6bd8e 100644 --- a/src/ui_ng/lib/src/vulnerability-scanning/scanning.html.ts +++ b/src/ui_ng/lib/src/vulnerability-scanning/scanning.html.ts @@ -16,7 +16,7 @@ export const TIP_COMPONENT_HTML: string = ` {{highCount}} {{packageText(highCount) | translate }} {{'VULNERABILITY.SEVERITY.HIGH' | translate }}
- + {{mediumCount}} {{packageText(mediumCount) | translate }} {{'VULNERABILITY.SEVERITY.MEDIUM' | translate }}
@@ -24,7 +24,7 @@ export const TIP_COMPONENT_HTML: string = ` {{lowCount}} {{packageText(lowCount) | translate }} {{'VULNERABILITY.SEVERITY.LOW' | translate }}
- + {{unknownCount}} {{packageText(unknownCount) | translate }} {{'VULNERABILITY.SEVERITY.UNKNOWN' | translate }}
diff --git a/src/ui_ng/package.json b/src/ui_ng/package.json index 7a049eee5..eb295ad43 100644 --- a/src/ui_ng/package.json +++ b/src/ui_ng/package.json @@ -31,7 +31,7 @@ "clarity-icons": "^0.9.8", "clarity-ui": "^0.9.8", "core-js": "^2.4.1", - "harbor-ui": "0.2.87", + "harbor-ui": "0.3.2", "intl": "^1.2.5", "mutationobserver-shim": "^0.3.2", "ngx-cookie": "^1.0.0", diff --git a/src/ui_ng/src/app/user/user.component.html b/src/ui_ng/src/app/user/user.component.html index 85fb85292..2c37dbc79 100644 --- a/src/ui_ng/src/app/user/user.component.html +++ b/src/ui_ng/src/app/user/user.component.html @@ -6,8 +6,8 @@ - - + +
diff --git a/src/ui_ng/src/app/user/user.component.ts b/src/ui_ng/src/app/user/user.component.ts index 95a097ca9..d2fc1b1bf 100644 --- a/src/ui_ng/src/app/user/user.component.ts +++ b/src/ui_ng/src/app/user/user.component.ts @@ -60,6 +60,8 @@ export class UserComponent implements OnInit, OnDestroy { @ViewChild(NewUserModalComponent) newUserDialog: NewUserModalComponent; + timerHandler: any; + constructor( private userService: UserService, private translate: TranslateService, @@ -75,8 +77,6 @@ export class UserComponent implements OnInit, OnDestroy { this.delUser(confirmed.data); } }); - let hnd = setInterval(() => ref.markForCheck(), 100); - setTimeout(() => clearInterval(hnd), 1000); } isMySelf(uid: number): boolean { @@ -126,12 +126,18 @@ export class UserComponent implements OnInit, OnDestroy { } ngOnInit(): void { + this.forceRefreshView(5000); } ngOnDestroy(): void { if (this.deletionSubscription) { this.deletionSubscription.unsubscribe(); } + + if (this.timerHandler) { + clearInterval(this.timerHandler); + this.timerHandler = null; + } } //Filter items by keywords @@ -143,11 +149,10 @@ export class UserComponent implements OnInit, OnDestroy { } else { this.users = users.filter(user => { return this.isMatchFilterTerm(terms, user.username); - }) + }); + this.forceRefreshView(5000); } }); - let hnd = setInterval(() => this.ref.markForCheck(), 100); - setTimeout(() => clearInterval(hnd), 1000); } //Disable the admin role for the specified user @@ -175,8 +180,7 @@ export class UserComponent implements OnInit, OnDestroy { .then(() => { //Change view now user.has_admin_role = updatedUser.has_admin_role; - let hnd = setInterval(() => this.ref.markForCheck(), 100); - setTimeout(() => clearInterval(hnd), 1000); + this.forceRefreshView(5000); }) .catch(error => { this.msgHandler.handleError(error); @@ -233,14 +237,15 @@ export class UserComponent implements OnInit, OnDestroy { this.totalCount = users.length; this.users = users.slice(from, to);//First page + this.forceRefreshView(5000); + return users; }) .catch(error => { this.onGoing = false; this.msgHandler.handleError(error); + this.forceRefreshView(5000); }); - let hnd = setInterval(() => this.ref.markForCheck(), 100); - setTimeout(() => clearInterval(hnd), 1000); } //Add new user @@ -264,6 +269,7 @@ export class UserComponent implements OnInit, OnDestroy { this.originalUsers.then(users => { this.users = users.slice(state.page.from, state.page.to + 1); }); + this.forceRefreshView(5000); } else { this.refreshUser(state.page.from, state.page.to + 1); } @@ -278,4 +284,18 @@ export class UserComponent implements OnInit, OnDestroy { this.refreshUser(0, 15); } + forceRefreshView(duration: number): void { + //Reset timer + if (this.timerHandler) { + clearInterval(this.timerHandler); + } + this.timerHandler = setInterval(() => this.ref.markForCheck(), 100); + setTimeout(() => { + if (this.timerHandler) { + clearInterval(this.timerHandler); + this.timerHandler = null; + } + }, duration); + } + } diff --git a/src/ui_ng/src/i18n/lang/en-us-lang.json b/src/ui_ng/src/i18n/lang/en-us-lang.json index 31824de7e..586ca4855 100644 --- a/src/ui_ng/src/i18n/lang/en-us-lang.json +++ b/src/ui_ng/src/i18n/lang/en-us-lang.json @@ -273,7 +273,8 @@ "FOUND_ERROR_IN_JOBS": "Found errors in the replication job(s), please check.", "INVALID_DATE": "Invalid date.", "PLACEHOLDER": "We couldn't find any replication rules!", - "JOB_PLACEHOLDER": "We couldn't find any replication jobs!" + "JOB_PLACEHOLDER": "We couldn't find any replication jobs!", + "JOB_LOG_VIEWER": "View Replication Job Log" }, "DESTINATION": { "NEW_ENDPOINT": "New Endpoint", @@ -395,7 +396,7 @@ "TOKEN_EXPIRATION": "The expiration time (in minutes) of a token created by the token service. Default is 30 minutes.", "PRO_CREATION_RESTRICTION": "The flag to define what users have permission to create projects. By default, everyone can create a project. Set to 'Admin Only' so that only an administrator can create a project.", "ROOT_CERT_DOWNLOAD": "Download the root certificate of registry.", - "SCANNING_POLICY": "Set image scanning policy based on different requirements. 'None': No active policy; 'Daily At': Triggering scanning at the specified time everyday; 'Upon Refresh': Triggering scanning when database refreshed." + "SCANNING_POLICY": "Set image scanning policy based on different requirements. 'None': No active policy; 'Daily At': Triggering scanning at the specified time everyday." }, "LDAP": { "URL": "LDAP URL", diff --git a/src/ui_ng/src/i18n/lang/es-es-lang.json b/src/ui_ng/src/i18n/lang/es-es-lang.json index 80b68196e..25f525f16 100644 --- a/src/ui_ng/src/i18n/lang/es-es-lang.json +++ b/src/ui_ng/src/i18n/lang/es-es-lang.json @@ -273,7 +273,8 @@ "FOUND_ERROR_IN_JOBS": "Se han encontrado errores en el trabajo de replicación. Por favor, compruébelos.", "INVALID_DATE": "Fecha invalida.", "PLACEHOLDER": "We couldn't find any replication rules!", - "JOB_PLACEHOLDER": "We couldn't find any replication jobs!" + "JOB_PLACEHOLDER": "We couldn't find any replication jobs!", + "JOB_LOG_VIEWER": "View Replication Job Log" }, "DESTINATION": { "NEW_ENDPOINT": "Nuevo Endpoint", @@ -396,7 +397,7 @@ "TOKEN_EXPIRATION": "El tiempo de expiración (en minutos) del token creado por el servicio de tokens. Por defecto son 30 minutos.", "PRO_CREATION_RESTRICTION": "Marca para definir qué usuarios tienen permisos para crear proyectos. Por defecto, todos pueden crear proyectos. Seleccione 'Solo Administradores' para que solamente los administradores puedan crear proyectos.", "ROOT_CERT_DOWNLOAD": "Download the root certificate of registry.", - "SCANNING_POLICY": "Set image scanning policy based on different requirements. 'None': No active policy; 'Daily At': Triggering scanning at the specified time everyday; 'Upon Refresh': Triggering scanning when database refreshed." + "SCANNING_POLICY": "Set image scanning policy based on different requirements. 'None': No active policy; 'Daily At': Triggering scanning at the specified time everyday." }, "LDAP": { "URL": "LDAP URL", diff --git a/src/ui_ng/src/i18n/lang/zh-cn-lang.json b/src/ui_ng/src/i18n/lang/zh-cn-lang.json index a99ff7abf..42fb598b7 100644 --- a/src/ui_ng/src/i18n/lang/zh-cn-lang.json +++ b/src/ui_ng/src/i18n/lang/zh-cn-lang.json @@ -277,7 +277,8 @@ "FOUND_ERROR_IN_JOBS": "复制任务中包含错误,请检查。", "INVALID_DATE": "无效日期。", "PLACEHOLDER": "未发现任何复制规则!", - "JOB_PLACEHOLDER": "未发现任何复制任务!" + "JOB_PLACEHOLDER": "未发现任何复制任务!", + "JOB_LOG_VIEWER": "查看复制任务日志" }, "DESTINATION": { "NEW_ENDPOINT": "新建目标", @@ -399,7 +400,7 @@ "TOKEN_EXPIRATION": "由令牌服务创建的令牌的过期时间(分钟),默认为30分钟。", "PRO_CREATION_RESTRICTION": "用来确定哪些用户有权限创建项目,默认为’所有人‘,设置为’仅管理员‘则只有管理员可以创建项目。", "ROOT_CERT_DOWNLOAD": "下载镜像库根证书.", - "SCANNING_POLICY": "基于不同需求设置镜像扫描策略。‘无’:不设置任何策略;‘每日定时’:每天在设置的时间定时执行扫描;‘缺陷库刷新后’:当缺陷数据库刷新后。" + "SCANNING_POLICY": "基于不同需求设置镜像扫描策略。‘无’:不设置任何策略;‘每日定时’:每天在设置的时间定时执行扫描。" }, "LDAP": { "URL": "LDAP URL",