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 = `
+
+ {{'REPLICATION.JOB_LOG_VIEWER' | translate }}
+
+
+
+
+
+
+ {{log}}
+
+
+
+
+
+`;
+
+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",