Enhance scanning status controlling

This commit is contained in:
Steven Zou 2017-07-24 13:39:49 +08:00
parent b127ba391d
commit 97a9052050
32 changed files with 345 additions and 90 deletions

View File

@ -2,7 +2,7 @@ export const REGISTRY_CONFIG_HTML: string = `
<div>
<replication-config #replicationConfig [(replicationConfig)]="config" [showSubTitle]="true"></replication-config>
<system-settings #systemSettings [(systemSettings)]="config" [showSubTitle]="true" [hasAdminRole]="hasAdminRole" [hasCAFile]="hasCAFile"></system-settings>
<vulnerability-config *ngIf="withClair" #vulnerabilityConfig [(vulnerabilityConfig)]="config" [showSubTitle]="true" [clairDBStatus]="clairDB"></vulnerability-config>
<vulnerability-config *ngIf="withClair" #vulnerabilityConfig [(vulnerabilityConfig)]="config" [showSubTitle]="true"></vulnerability-config>
<div>
<button type="button" class="btn btn-primary" (click)="save()" [disabled]="shouldDisable">{{'BUTTON.SAVE' | translate}}</button>
<button type="button" class="btn btn-outline" (click)="cancel()" [disabled]="shouldDisable">{{'BUTTON.CANCEL' | translate}}</button>

View File

@ -52,7 +52,8 @@ describe('RegistryConfigComponent (inline template)', () => {
"project_creation_restriction": "everyone",
"self_registration": true,
"has_ca_root": true,
"harbor_version": "v1.1.1-rc1-160-g565110d"
"harbor_version": "v1.1.1-rc1-160-g565110d",
"next_scan_all": 0
};
beforeEach(async(() => {
@ -85,7 +86,7 @@ describe('RegistryConfigComponent (inline template)', () => {
systemInfoService = fixture.debugElement.injector.get(SystemInfoService);
spy = spyOn(cfgService, 'getConfigurations').and.returnValue(Promise.resolve(mockConfig));
saveSpy = spyOn(cfgService, 'saveConfigurations').and.returnValue(Promise.resolve(true));
spySystemInfo = spyOn(systemInfoService, 'getSystemInfo').and.returnValues(Promise.resolve(mockSystemInfo));
spySystemInfo = spyOn(systemInfoService, 'getSystemInfo').and.returnValue(Promise.resolve(mockSystemInfo));
fixture.detectChanges();
});

View File

@ -54,17 +54,8 @@ export class RegistryConfigComponent implements OnInit {
return this.systemInfo && this.systemInfo.with_clair;
}
get clairDB(): ClairDBStatus {
return this.systemInfo && this.systemInfo.clair_vulnerability_status ?
this.systemInfo.clair_vulnerability_status : null;
}
ngOnInit(): void {
//Get system info
toPromise<SystemInfo>(this.systemInfoService.getSystemInfo())
.then((info: SystemInfo) => this.systemInfo = info)
.catch(error => this.errorHandler.error(error));
this.loadSystemInfo();
//Initialize
this.load();
}
@ -82,20 +73,25 @@ export class RegistryConfigComponent implements OnInit {
return !this._isEmptyObject(this.getChanges());
}
//Get system info
loadSystemInfo(): void {
toPromise<SystemInfo>(this.systemInfoService.getSystemInfo())
.then((info: SystemInfo) => this.systemInfo = info)
.catch(error => this.errorHandler.error(error));
}
//Load configurations
load(): void {
this.onGoing = true;
toPromise<Configuration>(this.configService.getConfigurations())
.then((config: Configuration) => {
this.onGoing = false;
this.configCopy = this._clone(config);
this.config = config;
this.onGoing = false;
})
.catch(error => {
this.onGoing = false;
this.errorHandler.error(error);
this.onGoing = false;
});
}
@ -118,6 +114,8 @@ export class RegistryConfigComponent implements OnInit {
});
//Reload to fetch all the updates
this.load();
//Reload all system info
//this.loadSystemInfo();
})
.catch(error => {
this.onGoing = false;

View File

@ -12,13 +12,13 @@ export const VULNERABILITY_CONFIG_HTML: string = `
</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 }}
{{ updatedTimestamp | date:'MM/dd/y HH:mm:ss' }}
<clr-icon shape="caret down"></clr-icon>
</button>
<div class="dropdown-menu" style="min-width:300px;">
<div *ngFor="let nt of namespaceTimestamps" class="namespace">
<span class="label label-info">{{nt.namespace}}</span>
<span>{{convertToLocalTime(nt.last_update*1000)}}</span>
<span>{{ convertToLocalTime(nt.last_update) | date:'MM/dd/y HH:mm:ss'}}</span>
</div>
</div>
</clr-dropdown>
@ -38,7 +38,8 @@ export const VULNERABILITY_CONFIG_HTML: string = `
</a>
</div>
<div class="form-group form-group-override">
<button class="btn btn-primary btn-sm" style="width:160px;" (click)="scanNow()">{{ 'CONFIG.SCANNING.SCAN_NOW' | translate }}</button>
<button class="btn btn-primary btn-sm" style="width:160px;" (click)="scanNow()" [disabled]="!scanAvailable">{{ 'CONFIG.SCANNING.SCAN_NOW' | translate }}</button>
<span style="margin-top: 12px;" *ngIf="!scanAvailable">{{ 'CONFIG.SCANNING.NEXT_SCAN' | translate }} {{ nextScanTimestamp | date:'HH:mm' }}</span>
</div>
</section>
</form>

View File

@ -1,9 +1,13 @@
import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core';
import { Component, Input, Output, EventEmitter, ViewChild, OnInit } from '@angular/core';
import { NgForm } from '@angular/forms';
import { Configuration } from '../config';
import { VULNERABILITY_CONFIG_HTML, VULNERABILITY_CONFIG_STYLES } from './vulnerability-config.component.template';
import { ScanningResultService } from '../../service/scanning.service';
import {
ScanningResultService,
SystemInfo,
SystemInfoService
} from '../../service/index';
import { ErrorHandler } from '../../error-handler';
import { toPromise } from '../../utils';
import { TranslateService } from '@ngx-translate/core';
@ -19,9 +23,10 @@ const ONE_DAY_SECONDS: number = 24 * ONE_HOUR_SECONDS;
template: VULNERABILITY_CONFIG_HTML,
styles: [VULNERABILITY_CONFIG_STYLES, REGISTRY_CONFIG_STYLES]
})
export class VulnerabilityConfigComponent {
export class VulnerabilityConfigComponent implements OnInit {
_localTime: Date = new Date();
onSubmitting: boolean = false;
config: Configuration;
openState: boolean = false;
@Output() configChange: EventEmitter<Configuration> = new EventEmitter<Configuration>();
@ -46,21 +51,37 @@ export class VulnerabilityConfigComponent {
}
@Input() showSubTitle: boolean = false;
@Input() clairDBStatus: ClairDBStatus;
systemInfo: SystemInfo;
get updatedTimestamp(): string {
if (this.clairDBStatus && this.clairDBStatus.overall_last_update > 0) {
return this.convertToLocalTime(this.clairDBStatus.overall_last_update*1000);
get scanAvailable(): boolean {
let dt: Date = new Date();
return !this.onSubmitting && (this.nextScanTime <= 0 || dt.getTime() > ((this.nextScanTime + 30) * 1000));
}
get nextScanTimestamp(): Date {
return this.nextScanTime > 0 ? this.convertToLocalTime(this.nextScanTime) : null;
}
get nextScanTime(): number {
return this.systemInfo && this.systemInfo.next_scan_all ? this.systemInfo.next_scan_all : 0;
}
get updatedTimestamp(): Date {
if (this.systemInfo &&
this.systemInfo.clair_vulnerability_status &&
this.systemInfo.clair_vulnerability_status.overall_last_update > 0) {
return this.convertToLocalTime(this.systemInfo.clair_vulnerability_status.overall_last_update);
}
return "--";
return null;
}
get namespaceTimestamps(): ClairDetail[] {
if (this.clairDBStatus &&
this.clairDBStatus.details &&
this.clairDBStatus.details.length > 0) {
return this.clairDBStatus.details;
if (this.systemInfo &&
this.systemInfo.clair_vulnerability_status &&
this.systemInfo.clair_vulnerability_status.details &&
this.systemInfo.clair_vulnerability_status.details.length > 0) {
return this.systemInfo.clair_vulnerability_status.details;
}
return [];
@ -207,31 +228,57 @@ export class VulnerabilityConfigComponent {
}
get isClairDBFullyReady(): boolean {
return this.clairDBStatus && this.clairDBStatus.overall_last_update > 0;
return this.systemInfo &&
this.systemInfo.clair_vulnerability_status &&
this.systemInfo.clair_vulnerability_status.overall_last_update > 0;
}
constructor(
private scanningService: ScanningResultService,
private errorHandler: ErrorHandler,
private translate: TranslateService) { }
private translate: TranslateService,
private systemInfoService: SystemInfoService
) { }
convertToLocalTime(utcTime: number): string {
let offset: number = this._localTime.getTimezoneOffset() * 60;
let timeWithLocal: number = utcTime - offset;
let dt = new Date();
dt.setTime(timeWithLocal);
return dt.toLocaleString();
ngOnInit(): void {
this.getSystemInfo();
}
convertToLocalTime(utcTime: number): Date {
let dt: Date = new Date();
dt.setTime(utcTime * 1000);
return dt;
}
scanNow(): void {
if (this.onSubmitting) {
return;//Aoid duplicated submitting
}
this.onSubmitting = true;
toPromise<any>(this.scanningService.startScanningAll())
.then(() => {
this.translate.get("CONFIG.SCANNING.TRIGGER_SCAN_ALL_SUCCESS").subscribe((res: string) => {
this.errorHandler.info(res);
});
//TODO:
//Change button disable status.
//Update system info
this.getSystemInfo().then(() => {
this.onSubmitting = false;
}).catch(() => {
this.onSubmitting = false;
});
})
.catch(error => this.errorHandler.error(error))
.catch(error => {
this.errorHandler.error(error);
this.onSubmitting = false;
});
}
getSystemInfo(): Promise<SystemInfo> {
return toPromise<SystemInfo>(this.systemInfoService.getSystemInfo())
.then((info: SystemInfo) => this.systemInfo = info)
.catch(error => this.errorHandler.error(error));
}
}

View File

@ -18,7 +18,12 @@ import { ReplicationRule, ReplicationJob, Endpoint } from '../service/interface'
import { ErrorHandler } from '../error-handler/error-handler';
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
import { ReplicationService, ReplicationDefaultService } from '../service/replication.service';
import {
ReplicationService,
ReplicationDefaultService,
JobLogService,
JobLogDefaultService
} from '../service/index';
import { EndpointService, EndpointDefaultService } from '../service/endpoint.service';
import { JobLogViewerComponent } from '../job-log-viewer/job-log-viewer.component';
@ -183,7 +188,8 @@ describe('CreateEditRuleComponent (inline template)', ()=>{
ErrorHandler,
{ provide: SERVICE_CONFIG, useValue: config },
{ provide: ReplicationService, useClass: ReplicationDefaultService },
{ provide: EndpointService, useClass: EndpointDefaultService }
{ provide: EndpointService, useClass: EndpointDefaultService },
{ provide: JobLogService, useClass: JobLogDefaultService }
]
});
}));

View File

@ -41,7 +41,9 @@ import {
ScanningResultService,
ScanningResultDefaultService,
ConfigurationService,
ConfigurationDefaultService
ConfigurationDefaultService,
JobLogService,
JobLogDefaultService
} from './service/index';
import {
ErrorHandler,
@ -74,7 +76,8 @@ export const DefaultServiceConfig: IServiceConfig = {
langMessagePathForHttpLoader: "i18n/langs/",
langMessageFileSuffixForHttpLoader: "-lang.json",
localI18nMessageVariableMap: {},
configurationEndpoint: "/api/configurations"
configurationEndpoint: "/api/configurations",
scanJobEndpoint: "/api/jobs/scan"
};
/**
@ -112,7 +115,10 @@ export interface HarborModuleConfig {
scanningService?: Provider,
//Service implementation for configuration
configService?: Provider
configService?: Provider,
//Service implementation for job log
jobLogService?: Provider
}
/**
@ -197,6 +203,7 @@ export class HarborLibraryModule {
config.tagService || { provide: TagService, useClass: TagDefaultService },
config.scanningService || { provide: ScanningResultService, useClass: ScanningResultDefaultService },
config.configService || { provide: ConfigurationService, useClass: ConfigurationDefaultService },
config.jobLogService || { provide: JobLogService, useClass: JobLogDefaultService },
//Do initializing
TranslateServiceInitializer,
{
@ -224,6 +231,7 @@ export class HarborLibraryModule {
config.tagService || { provide: TagService, useClass: TagDefaultService },
config.scanningService || { provide: ScanningResultService, useClass: ScanningResultDefaultService },
config.configService || { provide: ConfigurationService, useClass: ConfigurationDefaultService },
config.jobLogService || { provide: JobLogService, useClass: JobLogDefaultService },
ChannelService
]
};

View File

@ -2,7 +2,7 @@ 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 { JobLogService, JobLogDefaultService } from '../service/index';
import { JobLogViewerComponent } from './job-log-viewer.component';
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
@ -13,7 +13,7 @@ describe('JobLogViewerComponent (inline template)', () => {
let component: JobLogViewerComponent;
let fixture: ComponentFixture<JobLogViewerComponent>;
let serviceConfig: IServiceConfig;
let replicationService: ReplicationService;
let jobLogService: JobLogDefaultService;
let spy: jasmine.Spy;
let testConfig: IServiceConfig = {
replicationJobEndpoint: "/api/jobs/replication/testing"
@ -29,7 +29,7 @@ describe('JobLogViewerComponent (inline template)', () => {
providers: [
ErrorHandler,
{ provide: SERVICE_CONFIG, useValue: testConfig },
{ provide: ReplicationService, useClass: ReplicationDefaultService }
{ provide: JobLogService, useClass: JobLogDefaultService }
]
});
}));
@ -39,9 +39,9 @@ describe('JobLogViewerComponent (inline template)', () => {
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"));
jobLogService = fixture.debugElement.injector.get(JobLogService);
spy = spyOn(jobLogService, 'getJobLog')
.and.returnValue(Promise.resolve("job log text"));
fixture.detectChanges();
});

View File

@ -1,6 +1,6 @@
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>
<h3 class="modal-title" class="log-viewer-title" style="margin-top: 0px;">{{title | translate }}</h3>
<div class="modal-body">
<div class="loading-back" [hidden]="!onGoing">
<span class="spinner spinner-md"></span>

View File

@ -11,13 +11,15 @@
// 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 { Component, Input } from '@angular/core';
import { JOB_LOG_VIEWER_TEMPLATE, JOB_LOG_VIEWER_STYLES } from './job-log-viewer.component.template';
import { ReplicationService } from '../service/index';
import { JobLogService } from '../service/index';
import { ErrorHandler } from '../error-handler/index';
import { toPromise } from '../utils';
const supportSet: string[] = ["replication", "scan"];
@Component({
selector: 'job-log-viewer',
template: JOB_LOG_VIEWER_TEMPLATE,
@ -25,12 +27,32 @@ import { toPromise } from '../utils';
})
export class JobLogViewerComponent {
_jobType: string = "replication";
opened: boolean = false;
log: string = '';
onGoing: boolean = true;
@Input()
get jobType(): string {
return this._jobType;
}
set jobType(v: string) {
if (supportSet.find((t: string) => t === v)) {
this._jobType = v;
}
}
get title(): string {
if(this.jobType === "scan"){
return "VULNERABILITY.JOB_LOG_VIEWER";
}
return "REPLICATION.JOB_LOG_VIEWER";
}
constructor(
private replicationService: ReplicationService,
private jobLogService: JobLogService,
private errorHandler: ErrorHandler
) { }
@ -47,7 +69,7 @@ export class JobLogViewerComponent {
load(jobId: number | string): void {
this.onGoing = true;
toPromise<string>(this.replicationService.getJobLog(jobId))
toPromise<string>(this.jobLogService.getJobLog(this.jobType, jobId))
.then((log: string) => {
this.onGoing = false;
this.log = log;

View File

@ -19,6 +19,7 @@ import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
import { ReplicationService, ReplicationDefaultService } from '../service/replication.service';
import { EndpointService, EndpointDefaultService } from '../service/endpoint.service';
import { JobLogViewerComponent } from '../job-log-viewer/job-log-viewer.component';
import { JobLogService, JobLogDefaultService } from '../service/index';
describe('Replication Component (inline template)', ()=>{
@ -183,7 +184,8 @@ describe('Replication Component (inline template)', ()=>{
ErrorHandler,
{ provide: SERVICE_CONFIG, useValue: config },
{ provide: ReplicationService, useClass: ReplicationDefaultService },
{ provide: EndpointService, useClass: EndpointDefaultService }
{ provide: EndpointService, useClass: EndpointDefaultService },
{ provide: JobLogService, useClass: JobLogDefaultService }
]
});
}));

View File

@ -17,6 +17,7 @@ import { SystemInfoService, SystemInfoDefaultService } from '../service/system-i
import { VULNERABILITY_DIRECTIVES } from '../vulnerability-scanning/index';
import { PUSH_IMAGE_BUTTON_DIRECTIVES } from '../push-image/index';
import { INLINE_ALERT_DIRECTIVES } from '../inline-alert/index';
import { JobLogViewerComponent } from '../job-log-viewer/index';
import { click } from '../utils';
@ -101,7 +102,8 @@ describe('RepositoryComponentStackview (inline template)', () => {
FilterComponent,
VULNERABILITY_DIRECTIVES,
PUSH_IMAGE_BUTTON_DIRECTIVES,
INLINE_ALERT_DIRECTIVES
INLINE_ALERT_DIRECTIVES,
JobLogViewerComponent
],
providers: [
ErrorHandler,

View File

@ -180,4 +180,12 @@ export interface IServiceConfig {
* @memberOf IServiceConfig
*/
configurationEndpoint?: string;
/**
* The base endpoint of scan job service.
*
* @type {string}
* @memberof IServiceConfig
*/
scanJobEndpoint?: string;
}

View File

@ -7,4 +7,5 @@ export * from './repository.service';
export * from './tag.service';
export * from './RequestQueryParams';
export * from './scanning.service';
export * from './configuration.service';
export * from './configuration.service';
export * from './job-log.service';

View File

@ -168,6 +168,7 @@ export interface SystemInfo {
has_ca_root?: boolean;
harbor_version?: string;
clair_vulnerability_status?: ClairDBStatus;
next_scan_all?: number;
}
/**

View File

@ -0,0 +1,39 @@
import { TestBed, inject } from '@angular/core/testing';
import { JobLogService, JobLogDefaultService } from './job-log.service';
import { SharedModule } from '../shared/shared.module';
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
describe('JobLogService', () => {
const mockConfig: IServiceConfig = {
replicationJobEndpoint: "/api/jobs/replication/testing",
scanJobEndpoint: "/api/jobs/scan/testing"
};
let config: IServiceConfig;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
SharedModule
],
providers: [
JobLogDefaultService,
{
provide: JobLogService,
useClass: JobLogDefaultService
}, {
provide: SERVICE_CONFIG,
useValue: mockConfig
}]
});
config = TestBed.get(SERVICE_CONFIG);
});
it('should be initialized', inject([JobLogDefaultService], (service: JobLogService) => {
expect(service).toBeTruthy();
expect(config.replicationJobEndpoint).toEqual("/api/jobs/replication/testing");
expect(config.scanJobEndpoint).toEqual("/api/jobs/scan/testing");
}));
});

View File

@ -0,0 +1,84 @@
import { Observable } from 'rxjs/Observable';
import { RequestQueryParams } from './RequestQueryParams';
import { ReplicationJob, ReplicationRule } from './interface';
import { Injectable, Inject } from "@angular/core";
import 'rxjs/add/observable/of';
import { Http, RequestOptions } from '@angular/http';
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
import { buildHttpRequestOptions, HTTP_JSON_OPTIONS } from '../utils';
/**
* Define the service methods to handle the job log related things.
*
* @export
* @abstract
* @class JobLogService
*/
export abstract class JobLogService {
/**
* Get the log of the specified job
*
* @abstract
* @param {string} jobType
* @param {(number | string)} jobId
* @returns {(Observable<string> | Promise<string> | string)}
* @memberof JobLogService
*/
abstract getJobLog(jobType: string, jobId: number | string): Observable<string> | Promise<string> | string;
}
/**
* Implement default service for job log service.
*
* @export
* @class JobLogDefaultService
* @extends {ReplicationService}
*/
@Injectable()
export class JobLogDefaultService extends JobLogService {
_replicationJobBaseUrl: string;
_scanningJobBaseUrl: string;
_supportedJobTypes: string[];
constructor(
private http: Http,
@Inject(SERVICE_CONFIG) config: IServiceConfig
) {
super();
this._replicationJobBaseUrl = config.replicationJobEndpoint ?
config.replicationJobEndpoint : '/api/jobs/replication';
this._scanningJobBaseUrl = config.scanJobEndpoint ? config.scanJobEndpoint : "/api/jobs/scan";
this._supportedJobTypes = ["replication", "scan"];
}
_getJobLog(logUrl: string): Observable<string> | Promise<string> | string {
return this.http.get(logUrl).toPromise()
.then(response => response.text())
.catch(error => Promise.reject(error));
}
_isSupportedJobType(jobType: string): boolean {
if (this._supportedJobTypes.find((t: string) => t === jobType)) {
return true;
}
return false;
}
public getJobLog(jobType: string, jobId: number | string): Observable<string> | Promise<string> | string {
if (!this._isSupportedJobType(jobType)) {
return Promise.reject("Unsupport job type: " + jobType);
}
if (!jobId || jobId <= 0) {
return Promise.reject('Bad argument');
}
let logUrl: string = `${this._replicationJobBaseUrl}/${jobId}/log`;
if (jobType === "scan") {
logUrl = `${this._scanningJobBaseUrl}/${jobId}/log`;
}
return this._getJobLog(logUrl);
}
}

View File

@ -14,7 +14,6 @@ export const TAG_STYLE = `
:host >>> .datagrid {
margin: 0;
border-bottom: none;
}
:host >>> .datagrid-placeholder {

View File

@ -17,6 +17,8 @@ import { FILTER_DIRECTIVES } from '../filter/index'
import { Observable, Subscription } from 'rxjs/Rx';
import { ChannelService } from '../channel/index';
import { JobLogViewerComponent } from '../job-log-viewer/index';
describe('TagComponent (inline template)', () => {
let comp: TagComponent;
@ -49,7 +51,8 @@ describe('TagComponent (inline template)', () => {
TagComponent,
ConfirmationDialogComponent,
VULNERABILITY_DIRECTIVES,
FILTER_DIRECTIVES
FILTER_DIRECTIVES,
JobLogViewerComponent
],
providers: [
ErrorHandler,

View File

@ -8,13 +8,16 @@ import {
ScanningResultService,
ScanningResultDefaultService,
TagService,
TagDefaultService
TagDefaultService,
JobLogService,
JobLogDefaultService
} from '../service/index';
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
import { ErrorHandler } from '../error-handler/index';
import { SharedModule } from '../shared/shared.module';
import { VULNERABILITY_SCAN_STATUS } from '../utils';
import { ChannelService } from '../channel/index';
import { JobLogViewerComponent } from '../job-log-viewer/index';
describe('ResultBarChartComponent (inline template)', () => {
let component: ResultBarChartComponent;
@ -52,13 +55,15 @@ describe('ResultBarChartComponent (inline template)', () => {
],
declarations: [
ResultBarChartComponent,
ResultTipComponent],
ResultTipComponent,
JobLogViewerComponent],
providers: [
ErrorHandler,
ChannelService,
{ provide: SERVICE_CONFIG, useValue: testConfig },
{ provide: TagService, useValue: TagDefaultService },
{ provide: ScanningResultService, useValue: ScanningResultDefaultService }
{ provide: ScanningResultService, useValue: ScanningResultDefaultService },
{ provide: JobLogService, useValue: JobLogDefaultService}
]
});

View File

@ -4,7 +4,8 @@ import {
OnInit,
OnDestroy,
ChangeDetectionStrategy,
ChangeDetectorRef
ChangeDetectorRef,
ViewChild
} from '@angular/core';
import { SCANNING_STYLES } from './scanning.css';
@ -21,6 +22,7 @@ import { ErrorHandler } from '../error-handler/index';
import { toPromise } from '../utils';
import { Observable, Subscription } from 'rxjs/Rx';
import { ChannelService } from '../channel/index';
import { JobLogViewerComponent } from '../job-log-viewer/index';
const STATE_CHECK_INTERVAL: number = 2000;//2s
const RETRY_TIMES: number = 3;
@ -39,6 +41,9 @@ export class ResultBarChartComponent implements OnInit, OnDestroy {
stateCheckTimer: Subscription;
timerHandler: any;
@ViewChild("scanningLogViewer")
scanningJobLogViewer: JobLogViewerComponent;
constructor(
private tagService: TagService,
private scanningService: ScanningResultService,
@ -188,4 +193,11 @@ export class ResultBarChartComponent implements OnInit, OnDestroy {
}
}, duration);
}
//Check error log
viewLog(): void {
if (this.summary && this.summary.job_id) {
this.scanningJobLogViewer.open(this.summary.job_id);
}
}
}

View File

@ -20,6 +20,7 @@ export const SCANNING_STYLES: string = `
position: relative;
top: 1px;
margin-left: -5px;
cursor: pointer;
}
.scanning-button {

View File

@ -96,8 +96,10 @@ export const BAR_CHART_COMPONENT_HTML: string = `
<span class="label label-orange">{{'VULNERABILITY.STATE.QUEUED' | translate}}</span>
</div>
<div *ngIf="error" class="bar-state bar-state-error">
<clr-icon shape="info-circle" class="is-error" size="24"></clr-icon>
<span class="error-text">{{'VULNERABILITY.STATE.ERROR' | translate}}</span>
<a href="javascript:void(0);" class="error-text" (click)="viewLog()">
<clr-icon shape="error" class="is-error" size="24"></clr-icon>
{{'VULNERABILITY.STATE.ERROR' | translate}}
</a>
</div>
<div *ngIf="scanning" class="bar-state bar-state-chart">
<div>{{'VULNERABILITY.STATE.SCANNING' | translate}}</div>
@ -110,5 +112,6 @@ export const BAR_CHART_COMPONENT_HTML: string = `
<clr-icon shape="warning" class="is-warning" size="24"></clr-icon>
<span style="margin-left:-5px;">{{'VULNERABILITY.STATE.UNKNOWN' | translate}}</span>
</div>
<job-log-viewer #scanningLogViewer [jobType]="'scan'"></job-log-viewer>
</div>
`;

View File

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

View File

@ -30,6 +30,7 @@ export class AppConfig {
overall_last_update: 0,
details: []
};
this.next_scan_all = 0;
}
with_notary: boolean;
@ -43,4 +44,5 @@ export class AppConfig {
has_ca_root: boolean;
harbor_version: string;
clair_vulnerability_status?: ClairDBStatus;
next_scan_all: number;
}

View File

@ -32,7 +32,7 @@
<system-settings [(systemSettings)]="allConfig" [hasAdminRole]="hasAdminRole" [hasCAFile]="hasCAFile"></system-settings>
</section>
<section id="vulnerability" *ngIf="withClair" role="tabpanel" aria-labelledby="config-vulnerability" [hidden]='!isCurrentTabContent("vulnerability")'>
<vulnerability-config [(vulnerabilityConfig)]="allConfig" [clairDBStatus]="clairDB"></vulnerability-config>
<vulnerability-config [(vulnerabilityConfig)]="allConfig"></vulnerability-config>
</section>
<div>
<button type="button" class="btn btn-primary" (click)="save()" [disabled]="!isValid() || !hasChanges()">{{'BUTTON.SAVE' | translate}}</button>

View File

@ -85,10 +85,6 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
return this.appConfigService.getConfig().with_clair;
}
public get clairDB(): ClairDBStatus {
return this.appConfigService.getConfig().clair_vulnerability_status;
}
isCurrentTabLink(tabId: string): boolean {
return this.currentTabId === tabId;
}

View File

@ -1,5 +1,5 @@
<div>
<a *ngIf="hasSignedIn" [routerLink]="['/harbor', 'projects', projectId, 'repositories']">&lt; {{'REPOSITORY.REPOSITORIES' | translate}}</a>
<a *ngIf="!hasSignedIn" [routerLink]="['/harbor', 'sign-in']">&lt; {{'SEARCH.BACK' | translate}}</a>
<hbr-tag (tagClickEvent)="watchTagClickEvt($event)" [repoName]="repoName" [registryUrl]="registryUrl" [withNotary]="withNotary" [hasSignedIn]="hasSignedIn" [hasProjectAdminRole]="hasProjectAdminRole" [projectId]="projectId" [isEmbedded]="false"></hbr-tag>
<hbr-tag (tagClickEvent)="watchTagClickEvt($event)" [repoName]="repoName" [registryUrl]="registryUrl" [withNotary]="withNotary" [hasSignedIn]="hasSignedIn" [hasProjectAdminRole]="hasProjectAdminRole" [projectId]="projectId" [isEmbedded]="false" [withClair]="withClair"></hbr-tag>
</div>

View File

@ -29,8 +29,6 @@ export class TagRepositoryComponent implements OnInit {
repoName: string;
hasProjectAdminRole: boolean = false;
registryUrl: string;
withNotary: boolean;
hasSignedIn: boolean;
constructor(
private route: ActivatedRoute,
@ -40,7 +38,6 @@ export class TagRepositoryComponent implements OnInit {
}
ngOnInit() {
this.hasSignedIn = (this.session.getCurrentUser() !== null);
let resolverData = this.route.snapshot.data;
if (resolverData) {
this.hasProjectAdminRole = (<Project>resolverData['projectResolver']).has_project_admin_role;
@ -49,7 +46,18 @@ export class TagRepositoryComponent implements OnInit {
this.repoName = this.route.snapshot.params['repo'];
this.registryUrl = this.appConfigService.getConfig().registry_url;
this.withNotary = this.appConfigService.getConfig().with_notary;
}
get withNotary(): boolean {
return this.appConfigService.getConfig().with_notary;
}
get withClair(): boolean {
return this.appConfigService.getConfig().with_clair;
}
get hasSignedIn(): boolean {
return this.session.getCurrentUser() !== null;
}
watchTagClickEvt(tagEvt: TagClickEvent): void {

View File

@ -416,7 +416,8 @@
"DAILY_POLICY": "Daily At",
"REFRESH_POLICY": "Upon Refresh",
"DB_REFRESH_TIME": "Database updated on",
"DB_NOT_READY": "Vulnerability database might not be fully ready!"
"DB_NOT_READY": "Vulnerability database might not be fully ready!",
"NEXT_SCAN": "Available after"
},
"TEST_MAIL_SUCCESS": "Connection to mail server is verified.",
"TEST_LDAP_SUCCESS": "Connection to LDAP server is verified.",
@ -464,7 +465,7 @@
"STATE": {
"STOPPED": "Not Scanned",
"QUEUED": "Queued",
"ERROR": "Error",
"ERROR": "View Log",
"SCANNING": "Scanning",
"UNKNOWN": "Unknown"
},
@ -496,7 +497,8 @@
"PLACEHOLDER": "Filter Vulnerabilities",
"PACKAGE": "Package with",
"PACKAGES": "Packages with",
"SCAN_NOW": "Scan"
"SCAN_NOW": "Scan",
"JOB_LOG_VIEWER": "View Scanning Job Log"
},
"PUSH_IMAGE": {
"TITLE": "Push Image",

View File

@ -417,7 +417,8 @@
"DAILY_POLICY": "Daily At",
"REFRESH_POLICY": "Upon Refresh",
"DB_REFRESH_TIME": "Database updated on",
"DB_NOT_READY": "Vulnerability database might not be fully ready!"
"DB_NOT_READY": "Vulnerability database might not be fully ready!",
"NEXT_SCAN": "Available after"
},
"TEST_MAIL_SUCCESS": "La conexión al servidor de correo ha sido verificada.",
"TEST_LDAP_SUCCESS": "La conexión al servidor LDAP ha sido verificada.",
@ -463,7 +464,7 @@
"STATE": {
"STOPPED": "Not Scanned",
"QUEUED": "Queued",
"ERROR": "Error",
"ERROR": "View Log",
"SCANNING": "Scanning",
"UNKNOWN": "Unknown"
},
@ -495,7 +496,8 @@
"PLACEHOLDER": "Filter Vulnerabilities",
"PACKAGE": "Package with",
"PACKAGES": "Packages with",
"SCAN_NOW": "Scan"
"SCAN_NOW": "Scan",
"JOB_LOG_VIEWER": "View Scanning Job Log"
},
"PUSH_IMAGE": {
"TITLE": "Push Image",

View File

@ -420,7 +420,8 @@
"DAILY_POLICY": "每日定时",
"REFRESH_POLICY": "缺陷库刷新后",
"DB_REFRESH_TIME": "数据库更新于",
"DB_NOT_READY": "缺陷数据库可能没有完全准备好!"
"DB_NOT_READY": "缺陷数据库可能没有完全准备好!",
"NEXT_SCAN": "下次可用时间"
},
"TEST_MAIL_SUCCESS": "邮件服务器的连通正常。",
"TEST_LDAP_SUCCESS": "LDAP服务器的连通正常。",
@ -468,7 +469,7 @@
"STATE": {
"STOPPED": "未扫描",
"QUEUED": "已入队列",
"ERROR": "错误",
"ERROR": "查看日志",
"SCANNING": "扫描中",
"UNKNOWN": "未知"
},
@ -500,7 +501,8 @@
"PLACEHOLDER": "过滤缺陷",
"PACKAGE": "个组件有",
"PACKAGES": "个组件有",
"SCAN_NOW": "扫描"
"SCAN_NOW": "扫描",
"JOB_LOG_VIEWER": "查看扫描日志"
},
"PUSH_IMAGE": {
"TITLE": "推送镜像",