Merge pull request #2809 from steven-zou/master

Add job log reviewer
This commit is contained in:
Steven Zou 2017-07-19 16:43:23 +08:00 committed by GitHub
commit 3120abad8e
22 changed files with 404 additions and 171 deletions

View File

@ -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",

View File

@ -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"
}
}
}

View File

@ -4,7 +4,13 @@ export const VULNERABILITY_CONFIG_HTML: string = `
<label class="section-title" *ngIf="showSubTitle">{{ 'CONFIG.SCANNING.TITLE' | translate }}</label>
<div class="form-group">
<label>{{ 'CONFIG.SCANNING.DB_REFRESH_TIME' | translate }}</label>
<clr-dropdown [clrMenuPosition]="'bottom-right'" style="margin-top:-8px;" class="clr-dropdown-override">
<clr-tooltip *ngIf="!isClairDBFullyReady" [clrTooltipDirection]="'top-right'" [clrTooltipSize]="'md'">
<clr-icon shape="warning" class="is-warning" size="22"></clr-icon>
<clr-tooltip-content>
<span>{{'CONFIG.SCANNING.DB_NOT_READY' | translate }}</span>
</clr-tooltip-content>
</clr-tooltip>
<clr-dropdown *ngIf="isClairDBFullyReady" [clrMenuPosition]="'bottom-right'" style="margin-top:-8px;" class="clr-dropdown-override">
<button class="btn btn-link btn-font" clrDropdownToggle>
{{ updatedTimestamp }}
<clr-icon shape="caret down"></clr-icon>
@ -23,7 +29,6 @@ export const VULNERABILITY_CONFIG_HTML: string = `
<select id="scanAllPolicy" name="scanAllPolicy" [disabled]="!editable" [(ngModel)]="scanningType">
<option value="none">{{ 'CONFIG.SCANNING.NONE_POLICY' | translate }}</option>
<option value="daily">{{ 'CONFIG.SCANNING.DAILY_POLICY' | translate }}</option>
<option value="on_refresh">{{ 'CONFIG.SCANNING.REFRESH_POLICY' | translate }}</option>
</select>
</div>
<input type="time" name="dailyTimePicker" [disabled]="!editable" [hidden]="!showTimePicker" [(ngModel)]="dailyTime" />

View File

@ -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,

View File

@ -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,

View File

@ -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: []

View File

@ -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';
export * from './config/index';
export * from './job-log-viewer/index';

View File

@ -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<any>[] = [
JobLogViewerComponent
];

View File

@ -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<JobLogViewerComponent>;
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);
});
});

View File

@ -0,0 +1,33 @@
export const JOB_LOG_VIEWER_TEMPLATE: string = `
<clr-modal [(clrModalOpen)]="opened" [clrModalStaticBackdrop]="true" [clrModalSize]="'xl'">
<h3 class="modal-title" class="log-viewer-title" style="margin-top: 0px;">{{'REPLICATION.JOB_LOG_VIEWER' | translate }}</h3>
<div class="modal-body">
<div class="loading-back" [hidden]="!onGoing">
<span class="spinner spinner-md"></span>
</div>
<pre [hidden]="onGoing">
<code>
{{log}}
</code>
</pre>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" (click)="close()">{{ 'BUTTON.CLOSE' | translate}}</button>
</div>
</clr-modal>
`;
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;
}
`;

View File

@ -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<string>(this.replicationService.getJobLog(jobId))
.then((log: string) => {
this.onGoing = false;
this.log = log;
})
.catch(error => {
this.onGoing = false;
this.errorHandler.error(error);
});
}
}

View File

@ -61,7 +61,7 @@ export const REPLICATION_TEMPLATE: string = `
<clr-dg-cell>{{j.creation_time | date: 'short'}}</clr-dg-cell>
<clr-dg-cell>{{j.update_time | date: 'short'}}</clr-dg-cell>
<clr-dg-cell>
<a href="/api/jobs/replication/{{j.id}}/log" target="_BLANK">
<a href="javascript:void(0);" (click)="viewLog(j.id)">
<clr-icon shape="clipboard"></clr-icon>
</a>
</clr-dg-cell>
@ -73,4 +73,5 @@ export const REPLICATION_TEMPLATE: string = `
</clr-dg-footer>
</clr-datagrid>
</div>
<job-log-viewer #replicationLogViewer></job-log-viewer>
</div>`;

View File

@ -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,

View File

@ -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<ReplicationRule>();
@Input() projectId: number | string;
@Input() withReplicationJob: boolean;
search: SearchOption = new SearchOption();
@Output() redirect = new EventEmitter<ReplicationRule>();
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<ReplicationJob> = new CustomComparator<ReplicationJob>('creation_time', 'date');
updateTimeComparator: Comparator<ReplicationJob> = new CustomComparator<ReplicationJob>('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<ReplicationJob> = new CustomComparator<ReplicationJob>('creation_time', 'date');
updateTimeComparator: Comparator<ReplicationJob> = new CustomComparator<ReplicationJob>('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<ReplicationJob[]>(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<ReplicationJob[]>(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);
}
}
}

View File

@ -114,6 +114,16 @@ export abstract class ReplicationService {
* @memberOf ReplicationService
*/
abstract getJobs(ruleId: number | string, queryParams?: RequestQueryParams): Observable<ReplicationJob[]> | Promise<ReplicationJob[]> | ReplicationJob[];
/**
* Get the log of the specified job.
*
* @abstract
* @param {(number | string)} jobId
* @returns {(Observable<string> | Promise<string> | string)}
* @memberof ReplicationService
*/
abstract getJobLog(jobId: number | string): Observable<string> | Promise<string> | 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<string> | Promise<string> | 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));
}
}

View File

@ -16,7 +16,7 @@ export const TIP_COMPONENT_HTML: string = `
<span>{{highCount}} {{packageText(highCount) | translate }} {{'VULNERABILITY.SEVERITY.HIGH' | translate }}</span>
</div>
<div *ngIf="hasMedium" class="bar-summary-item">
<clr-icon *ngIf="hasMedium" shape="exclamation-triangle" class="is-warning" size="20"></clr-icon>
<clr-icon *ngIf="hasMedium" shape="exclamation-triangle" class="is-warning" size="22"></clr-icon>
<span>{{mediumCount}} {{packageText(mediumCount) | translate }} {{'VULNERABILITY.SEVERITY.MEDIUM' | translate }}</span>
</div>
<div *ngIf="hasLow" class="bar-summary-item">
@ -24,7 +24,7 @@ export const TIP_COMPONENT_HTML: string = `
<span>{{lowCount}} {{packageText(lowCount) | translate }} {{'VULNERABILITY.SEVERITY.LOW' | translate }}</span>
</div>
<div *ngIf="hasUnknown" class="bar-summary-item">
<clr-icon shape="help" size="24"></clr-icon>
<clr-icon shape="help" size="20"></clr-icon>
<span>{{unknownCount}} {{packageText(unknownCount) | translate }} {{'VULNERABILITY.SEVERITY.UNKNOWN' | translate }}</span>
</div>
<div *ngIf="hasNone" class="bar-summary-item">

View File

@ -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",

View File

@ -6,8 +6,8 @@
<button [class.hide-create]="!canCreateUser" type="submit" class="btn btn-link custom-add-button" (click)="addNewUser()"><clr-icon shape="add"></clr-icon> {{'USER.ADD_ACTION' | translate}}</button>
</span>
<hbr-filter [withDivider]="true" class="filter-pos" filterPlaceholder='{{"USER.FILTER_PLACEHOLDER" | translate}}' (filter)="doFilter($event)" [currentValue]="currentTerm"></hbr-filter>
<span class="refresh-btn" (click)="refresh()">
<clr-icon shape="refresh" [hidden]="inProgress" ng-disabled="inProgress"></clr-icon>
<span class="refresh-btn">
<clr-icon shape="refresh" [hidden]="inProgress" ng-disabled="inProgress" (click)="refresh()"></clr-icon>
<span class="spinner spinner-inline" [hidden]="inProgress === false"></span>
</span>
</div>

View File

@ -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);
}
}

View File

@ -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",

View File

@ -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",

View File

@ -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",