mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-24 01:27:49 +01:00
Enhance scanning status controlling
This commit is contained in:
parent
b127ba391d
commit
97a9052050
@ -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>
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
@ -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 }
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
@ -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
|
||||
]
|
||||
};
|
||||
|
@ -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();
|
||||
});
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
@ -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 }
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
@ -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,
|
||||
|
@ -180,4 +180,12 @@ export interface IServiceConfig {
|
||||
* @memberOf IServiceConfig
|
||||
*/
|
||||
configurationEndpoint?: string;
|
||||
|
||||
/**
|
||||
* The base endpoint of scan job service.
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof IServiceConfig
|
||||
*/
|
||||
scanJobEndpoint?: string;
|
||||
}
|
@ -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';
|
@ -168,6 +168,7 @@ export interface SystemInfo {
|
||||
has_ca_root?: boolean;
|
||||
harbor_version?: string;
|
||||
clair_vulnerability_status?: ClairDBStatus;
|
||||
next_scan_all?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
39
src/ui_ng/lib/src/service/job-log.service.spec.ts
Normal file
39
src/ui_ng/lib/src/service/job-log.service.spec.ts
Normal 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");
|
||||
}));
|
||||
});
|
84
src/ui_ng/lib/src/service/job-log.service.ts
Normal file
84
src/ui_ng/lib/src/service/job-log.service.ts
Normal 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);
|
||||
}
|
||||
}
|
@ -14,7 +14,6 @@ export const TAG_STYLE = `
|
||||
|
||||
:host >>> .datagrid {
|
||||
margin: 0;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
:host >>> .datagrid-placeholder {
|
||||
|
@ -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,
|
||||
|
@ -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}
|
||||
]
|
||||
});
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ export const SCANNING_STYLES: string = `
|
||||
position: relative;
|
||||
top: 1px;
|
||||
margin-left: -5px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.scanning-button {
|
||||
|
@ -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>
|
||||
`;
|
@ -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",
|
||||
|
@ -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;
|
||||
}
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
<div>
|
||||
<a *ngIf="hasSignedIn" [routerLink]="['/harbor', 'projects', projectId, 'repositories']">< {{'REPOSITORY.REPOSITORIES' | translate}}</a>
|
||||
<a *ngIf="!hasSignedIn" [routerLink]="['/harbor', 'sign-in']">< {{'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>
|
@ -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 {
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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": "推送镜像",
|
||||
|
Loading…
Reference in New Issue
Block a user