mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-24 17:47:46 +01:00
Merge pull request #2670 from steven-zou/master
Implement scan all policy configuration
This commit is contained in:
commit
5cfe6540b7
@ -41,7 +41,18 @@ export class BoolValueItem {
|
||||
}
|
||||
}
|
||||
|
||||
export class ComplexValueItem {
|
||||
value: any | { [key: string]: any | any[] };
|
||||
editable: boolean;
|
||||
|
||||
public constructor(v: any | { [key: string]: any | any[] }, e: boolean) {
|
||||
this.value = v;
|
||||
this.editable = e;
|
||||
}
|
||||
}
|
||||
|
||||
export class Configuration {
|
||||
[key: string]: any | any[]
|
||||
auth_mode: StringValueItem;
|
||||
project_creation_restriction: StringValueItem;
|
||||
self_registration: BoolValueItem;
|
||||
@ -63,6 +74,7 @@ export class Configuration {
|
||||
verify_remote_cert: BoolValueItem;
|
||||
token_expiration: NumberValueItem;
|
||||
cfg_expiration: NumberValueItem;
|
||||
scan_all_policy: ComplexValueItem;
|
||||
|
||||
public constructor() {
|
||||
this.auth_mode = new StringValueItem("db_auth", true);
|
||||
@ -83,8 +95,14 @@ export class Configuration {
|
||||
this.email_ssl = new BoolValueItem(false, true);
|
||||
this.email_username = new StringValueItem("", true);
|
||||
this.email_password = new StringValueItem("", true);
|
||||
this.token_expiration = new NumberValueItem(5, true);
|
||||
this.token_expiration = new NumberValueItem(30, true);
|
||||
this.cfg_expiration = new NumberValueItem(30, true);
|
||||
this.verify_remote_cert = new BoolValueItem(false, true);
|
||||
this.scan_all_policy = new ComplexValueItem({
|
||||
type: "daily",
|
||||
parameters: {
|
||||
daily_time: 0
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
}
|
19
src/ui_ng/lib/src/config/index.ts
Normal file
19
src/ui_ng/lib/src/config/index.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { Type } from '@angular/core';
|
||||
|
||||
import { ReplicationConfigComponent } from './replication/replication-config.component';
|
||||
import { SystemSettingsComponent } from './system/system-settings.component';
|
||||
import { VulnerabilityConfigComponent } from './vulnerability/vulnerability-config.component';
|
||||
import { RegistryConfigComponent } from './registry-config.component';
|
||||
|
||||
export * from './config';
|
||||
export * from './replication/replication-config.component';
|
||||
export * from './system/system-settings.component';
|
||||
export * from './vulnerability/vulnerability-config.component';
|
||||
export * from './registry-config.component';
|
||||
|
||||
export const CONFIGURATION_DIRECTIVES: Type<any>[] = [
|
||||
ReplicationConfigComponent,
|
||||
SystemSettingsComponent,
|
||||
VulnerabilityConfigComponent,
|
||||
RegistryConfigComponent
|
||||
];
|
@ -0,0 +1,7 @@
|
||||
export const REGISTRY_CONFIG_HTML: string = `
|
||||
<div>
|
||||
<replication-config [(replicationConfig)]="config"></replication-config>
|
||||
<system-settings [(systemSettings)]="config"></system-settings>
|
||||
<vulnerability-config [(vulnerabilityConfig)]="config"></vulnerability-config>
|
||||
</div>
|
||||
`;
|
98
src/ui_ng/lib/src/config/registry-config.component.spec.ts
Normal file
98
src/ui_ng/lib/src/config/registry-config.component.spec.ts
Normal file
@ -0,0 +1,98 @@
|
||||
import { ComponentFixture, TestBed, async } from '@angular/core/testing';
|
||||
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { ErrorHandler } from '../error-handler/error-handler';
|
||||
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||
|
||||
import { ReplicationConfigComponent } from './replication/replication-config.component';
|
||||
import { SystemSettingsComponent } from './system/system-settings.component';
|
||||
import { VulnerabilityConfigComponent } from './vulnerability/vulnerability-config.component';
|
||||
import { RegistryConfigComponent } from './registry-config.component';
|
||||
|
||||
import {
|
||||
ConfigurationService,
|
||||
ConfigurationDefaultService,
|
||||
ScanningResultService,
|
||||
ScanningResultDefaultService
|
||||
} from '../service/index';
|
||||
import { Configuration } from './config';
|
||||
|
||||
describe('RegistryConfigComponent (inline template)', () => {
|
||||
|
||||
let comp: RegistryConfigComponent;
|
||||
let fixture: ComponentFixture<RegistryConfigComponent>;
|
||||
let cfgService: ConfigurationService;
|
||||
let spy: jasmine.Spy;
|
||||
let saveSpy: jasmine.Spy;
|
||||
let mockConfig: Configuration = new Configuration();
|
||||
mockConfig.token_expiration.value = 90;
|
||||
mockConfig.verify_remote_cert.value = true;
|
||||
mockConfig.scan_all_policy.value = {
|
||||
type: "daily",
|
||||
parameters: {
|
||||
daily_time: 0
|
||||
}
|
||||
};
|
||||
let config: IServiceConfig = {
|
||||
configurationEndpoint: '/api/configurations/testing'
|
||||
};
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
SharedModule
|
||||
],
|
||||
declarations: [
|
||||
ReplicationConfigComponent,
|
||||
SystemSettingsComponent,
|
||||
VulnerabilityConfigComponent,
|
||||
RegistryConfigComponent
|
||||
],
|
||||
providers: [
|
||||
ErrorHandler,
|
||||
{ provide: SERVICE_CONFIG, useValue: config },
|
||||
{ provide: ConfigurationService, useClass: ConfigurationDefaultService },
|
||||
{ provide: ScanningResultService, useClass: ScanningResultDefaultService }
|
||||
]
|
||||
});
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(RegistryConfigComponent);
|
||||
comp = fixture.componentInstance;
|
||||
|
||||
cfgService = fixture.debugElement.injector.get(ConfigurationService);
|
||||
spy = spyOn(cfgService, 'getConfigurations').and.returnValue(Promise.resolve(mockConfig));
|
||||
saveSpy = spyOn(cfgService, 'saveConfigurations').and.returnValue(Promise.resolve(true));
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should render configurations to the view', async(() => {
|
||||
expect(spy.calls.count()).toEqual(1);
|
||||
fixture.detectChanges();
|
||||
|
||||
fixture.whenStable().then(() => {
|
||||
fixture.detectChanges();
|
||||
|
||||
let el: HTMLInputElement = fixture.nativeElement.querySelector('input[type="text"]');
|
||||
expect(el).toBeTruthy();
|
||||
expect(el.value).toEqual('30');
|
||||
|
||||
let el2: HTMLInputElement = fixture.nativeElement.querySelector('input[type="checkbox"]');
|
||||
expect(el2).toBeTruthy();
|
||||
expect(el2.value).toEqual('on');
|
||||
|
||||
let el3: HTMLInputElement = fixture.nativeElement.querySelector('input[type="time"]');
|
||||
expect(el3).toBeTruthy();
|
||||
expect(el3.value).toBeTruthy();
|
||||
});
|
||||
}));
|
||||
|
||||
it('should save the configuration changes', async(() => {
|
||||
comp.save();
|
||||
fixture.detectChanges();
|
||||
|
||||
expect(saveSpy.calls.any).toBeTruthy();
|
||||
}));
|
||||
});
|
110
src/ui_ng/lib/src/config/registry-config.component.ts
Normal file
110
src/ui_ng/lib/src/config/registry-config.component.ts
Normal file
@ -0,0 +1,110 @@
|
||||
import { Component, OnInit, EventEmitter, Output } from '@angular/core';
|
||||
|
||||
import { Configuration, ComplexValueItem } from './config';
|
||||
import { REGISTRY_CONFIG_HTML } from './registry-config.component.html';
|
||||
import { ConfigurationService } from '../service/index';
|
||||
import { toPromise } from '../utils';
|
||||
import { ErrorHandler } from '../error-handler';
|
||||
|
||||
@Component({
|
||||
selector: 'hbr-registry-config',
|
||||
template: REGISTRY_CONFIG_HTML
|
||||
})
|
||||
export class RegistryConfigComponent implements OnInit {
|
||||
config: Configuration = new Configuration();
|
||||
configCopy: Configuration;
|
||||
|
||||
@Output() configChanged: EventEmitter<any> = new EventEmitter<any>();
|
||||
|
||||
constructor(
|
||||
private configService: ConfigurationService,
|
||||
private errorHandler: ErrorHandler
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
//Initialize
|
||||
this.load();
|
||||
}
|
||||
|
||||
//Load configurations
|
||||
load(): void {
|
||||
toPromise<Configuration>(this.configService.getConfigurations())
|
||||
.then((config: Configuration) => {
|
||||
this.configCopy = Object.assign({}, config);
|
||||
this.config = config;
|
||||
})
|
||||
.catch(error => this.errorHandler.error(error));
|
||||
}
|
||||
|
||||
//Save configuration changes
|
||||
save(): void {
|
||||
let changes: { [key: string]: any | any[] } = this.getChanges();
|
||||
|
||||
if (this._isEmptyObject(changes)) {
|
||||
//Guard code, do nothing
|
||||
return;
|
||||
}
|
||||
|
||||
//Fix policy parameters issue
|
||||
let scanningAllPolicy = changes["scan_all_policy"];
|
||||
if (scanningAllPolicy &&
|
||||
scanningAllPolicy.type !== "daily" &&
|
||||
scanningAllPolicy.parameters) {
|
||||
delete (scanningAllPolicy.parameters);
|
||||
}
|
||||
|
||||
toPromise<any>(this.configService.saveConfigurations(changes))
|
||||
.then(() => {
|
||||
this.configChanged.emit(changes);
|
||||
})
|
||||
.catch(error => this.errorHandler.error(error));
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
//Reset to the values of copy
|
||||
let changes: { [key: string]: any | any[] } = this.getChanges();
|
||||
for (let prop in changes) {
|
||||
this.config[prop] = Object.assign({}, this.configCopy[prop]);
|
||||
}
|
||||
}
|
||||
|
||||
getChanges(): { [key: string]: any | any[] } {
|
||||
let changes: { [key: string]: any | any[] } = {};
|
||||
if (!this.config || !this.configCopy) {
|
||||
return changes;
|
||||
}
|
||||
|
||||
for (let prop in this.config) {
|
||||
let field = this.configCopy[prop];
|
||||
if (field && field.editable) {
|
||||
if (!this._compareValue(field.value, this.config[prop].value)) {
|
||||
changes[prop] = this.config[prop].value;
|
||||
//Number
|
||||
if (typeof field.value === "number") {
|
||||
changes[prop] = +changes[prop];
|
||||
}
|
||||
|
||||
//Trim string value
|
||||
if (typeof field.value === "string") {
|
||||
changes[prop] = ('' + changes[prop]).trim();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return changes;
|
||||
}
|
||||
|
||||
//private
|
||||
_compareValue(a: any, b: any): boolean {
|
||||
if ((a && !b) || (!a && b)) return false;
|
||||
if (!a && !b) return true;
|
||||
|
||||
return JSON.stringify(a) === JSON.stringify(b);
|
||||
}
|
||||
|
||||
//private
|
||||
_isEmptyObject(obj: any): boolean {
|
||||
return !obj || JSON.stringify(obj) === "{}";
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
export const REPLICATION_CONFIG_HTML: string = `
|
||||
<form #replicationConfigFrom="ngForm" class="compact">
|
||||
<section class="form-block" style="margin-top:0px;margin-bottom:0px;">
|
||||
<label style="font-size:14px;font-weight:600;">Image Replication</label>
|
||||
<div class="form-group">
|
||||
<label for="verifyRemoteCert">{{'CONFIG.VERIFY_REMOTE_CERT' | translate }}</label>
|
||||
<clr-checkbox name="verifyRemoteCert" id="verifyRemoteCert" [(ngModel)]="replicationConfig.verify_remote_cert.value" [disabled]="!editable">
|
||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-lg tooltip-top-right" style="top:-8px;">
|
||||
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
|
||||
<span class="tooltip-content">{{'CONFIG.TOOLTIP.VERIFY_REMOTE_CERT' | translate }}</span>
|
||||
</a>
|
||||
</clr-checkbox>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
`;
|
@ -0,0 +1,35 @@
|
||||
import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
|
||||
import { REPLICATION_CONFIG_HTML } from './replication-config.component.html';
|
||||
import { Configuration } from '../config';
|
||||
|
||||
@Component({
|
||||
selector: 'replication-config',
|
||||
template: REPLICATION_CONFIG_HTML
|
||||
})
|
||||
export class ReplicationConfigComponent {
|
||||
config: Configuration;
|
||||
@Output() configChange: EventEmitter<Configuration> = new EventEmitter<Configuration>();
|
||||
|
||||
@Input()
|
||||
get replicationConfig(): Configuration {
|
||||
return this.config;
|
||||
}
|
||||
set replicationConfig(cfg: Configuration) {
|
||||
this.config = cfg;
|
||||
this.configChange.emit(this.config);
|
||||
}
|
||||
|
||||
@ViewChild("replicationConfigFrom") replicationConfigForm: NgForm;
|
||||
|
||||
get editable(): boolean {
|
||||
return this.replicationConfig &&
|
||||
this.replicationConfig.verify_remote_cert &&
|
||||
this.replicationConfig.verify_remote_cert.editable;
|
||||
}
|
||||
|
||||
get isValid(): boolean {
|
||||
return this.replicationConfigForm && this.replicationConfigForm.valid;
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
export const SYSTEM_SETTINGS_HTML: string = `
|
||||
<form #systemConfigFrom="ngForm" class="compact">
|
||||
<section class="form-block" style="margin-top:0px;margin-bottom:0px;">
|
||||
<label style="font-size:14px;font-weight:600;">System Settings</label>
|
||||
<div class="form-group">
|
||||
<label for="tokenExpiration" class="required">{{'CONFIG.TOKEN_EXPIRATION' | translate}}</label>
|
||||
<label for="tokenExpiration" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-right" [class.invalid]="tokenExpirationInput.invalid && (tokenExpirationInput.dirty || tokenExpirationInput.touched)">
|
||||
<input name="tokenExpiration" type="text" #tokenExpirationInput="ngModel" [(ngModel)]="systemSettings.token_expiration.value"
|
||||
required
|
||||
pattern="^[1-9]{1}[0-9]*$"
|
||||
id="tokenExpiration"
|
||||
size="20" [disabled]="!editable">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.NUMBER_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right">
|
||||
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
|
||||
<span class="tooltip-content">{{'CONFIG.TOOLTIP.TOKEN_EXPIRATION' | translate}}</span>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
`;
|
35
src/ui_ng/lib/src/config/system/system-settings.component.ts
Normal file
35
src/ui_ng/lib/src/config/system/system-settings.component.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { Component, Input, Output, EventEmitter, ViewChild } from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
|
||||
import { SYSTEM_SETTINGS_HTML } from './system-settings.component.html';
|
||||
import { Configuration } from '../config';
|
||||
|
||||
@Component({
|
||||
selector: 'system-settings',
|
||||
template: SYSTEM_SETTINGS_HTML
|
||||
})
|
||||
export class SystemSettingsComponent {
|
||||
config: Configuration;
|
||||
@Output() configChange: EventEmitter<Configuration> = new EventEmitter<Configuration>();
|
||||
|
||||
@Input()
|
||||
get systemSettings(): Configuration {
|
||||
return this.config;
|
||||
}
|
||||
set systemSettings(cfg: Configuration) {
|
||||
this.config = cfg;
|
||||
this.configChange.emit(this.config);
|
||||
}
|
||||
|
||||
@ViewChild("systemConfigFrom") systemSettingsForm: NgForm;
|
||||
|
||||
get editable(): boolean {
|
||||
return this.systemSettings &&
|
||||
this.systemSettings.token_expiration &&
|
||||
this.systemSettings.token_expiration.editable;
|
||||
}
|
||||
|
||||
get isValid(): boolean {
|
||||
return this.systemSettingsForm && this.systemSettingsForm.valid;
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
export const VULNERABILITY_CONFIG_HTML: string = `
|
||||
<form #systemConfigFrom="ngForm" class="compact">
|
||||
<section class="form-block" style="margin-top:0px;margin-bottom:0px;">
|
||||
<label class="section-title">{{ 'CONFIG.SCANNING.TITLE' | translate }}</label>
|
||||
<div class="form-group">
|
||||
<label for="scanAllPolicy">{{ 'CONFIG.SCANNING.SCAN_ALL' | translate }}</label>
|
||||
<div class="select">
|
||||
<select id="scanAllPolicy" name="scanAllPolicy" [disabled]="!editable" [(ngModel)]="vulnerabilityConfig.scan_all_policy.value.type">
|
||||
<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" />
|
||||
</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>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
`;
|
||||
|
||||
export const VULNERABILITY_CONFIG_STYLES: string = `
|
||||
.form-group-override {
|
||||
padding-left: 0px !important;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 14px !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
`;
|
@ -0,0 +1,165 @@
|
||||
import { Component, Input, Output, EventEmitter, ViewChild } 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 { ErrorHandler } from '../../error-handler';
|
||||
import { toPromise } from '../../utils';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
const ONE_HOUR_SECONDS: number = 3600;
|
||||
const ONE_DAY_SECONDS: number = 24 * ONE_HOUR_SECONDS;
|
||||
|
||||
@Component({
|
||||
selector: 'vulnerability-config',
|
||||
template: VULNERABILITY_CONFIG_HTML,
|
||||
styles: [VULNERABILITY_CONFIG_STYLES]
|
||||
})
|
||||
export class VulnerabilityConfigComponent {
|
||||
_localTime: Date = new Date();
|
||||
|
||||
config: Configuration;
|
||||
@Output() configChange: EventEmitter<Configuration> = new EventEmitter<Configuration>();
|
||||
|
||||
@Input()
|
||||
get vulnerabilityConfig(): Configuration {
|
||||
return this.config;
|
||||
}
|
||||
set vulnerabilityConfig(cfg: Configuration) {
|
||||
this.config = cfg;
|
||||
if (this.config.scan_all_policy &&
|
||||
this.config.scan_all_policy.value) {
|
||||
if (this.config.scan_all_policy.value.type === "daily"){
|
||||
if(!this.config.scan_all_policy.value.parameters){
|
||||
this.config.scan_all_policy.value.parameters = {
|
||||
daily_time: 0
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
this.configChange.emit(this.config);
|
||||
}
|
||||
|
||||
//UTC time
|
||||
get dailyTime(): string {
|
||||
if (!(this.config &&
|
||||
this.config.scan_all_policy &&
|
||||
this.config.scan_all_policy.value &&
|
||||
this.config.scan_all_policy.value.type === "daily")) {
|
||||
return "00:00";
|
||||
}
|
||||
|
||||
let timeOffset: number = 0;//seconds
|
||||
if (this.config.scan_all_policy.value.parameters) {
|
||||
let daily_time = this.config.scan_all_policy.value.parameters.daily_time;
|
||||
if (daily_time && typeof daily_time === "number") {
|
||||
timeOffset = +daily_time;
|
||||
}
|
||||
}
|
||||
|
||||
//Convert to current time
|
||||
let timezoneOffset: number = this._localTime.getTimezoneOffset();
|
||||
//Local time
|
||||
timeOffset = timeOffset - timezoneOffset * 60;
|
||||
if (timeOffset < 0) {
|
||||
timeOffset = timeOffset + ONE_DAY_SECONDS;
|
||||
}
|
||||
|
||||
if (timeOffset >= ONE_DAY_SECONDS) {
|
||||
timeOffset -= ONE_DAY_SECONDS;
|
||||
}
|
||||
|
||||
//To time string
|
||||
let hours: number = Math.floor(timeOffset / ONE_HOUR_SECONDS);
|
||||
let minutes: number = Math.floor((timeOffset - hours * ONE_HOUR_SECONDS) / 60);
|
||||
|
||||
let timeStr: string = "" + hours;
|
||||
if (hours < 10) {
|
||||
timeStr = "0" + timeStr;
|
||||
}
|
||||
if (minutes < 10) {
|
||||
timeStr += ":0";
|
||||
} else {
|
||||
timeStr += ":";
|
||||
}
|
||||
timeStr += minutes;
|
||||
|
||||
return timeStr;
|
||||
}
|
||||
set dailyTime(v: string) {
|
||||
if (!v || v === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(this.config &&
|
||||
this.config.scan_all_policy &&
|
||||
this.config.scan_all_policy.value &&
|
||||
this.config.scan_all_policy.value.type === "daily")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.config.scan_all_policy.value.parameters) {
|
||||
this.config.scan_all_policy.value.parameters = {
|
||||
daily_time: 0
|
||||
};
|
||||
}
|
||||
|
||||
let values: string[] = v.split(":");
|
||||
if (!values || values.length !== 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
let hours: number = +values[0];
|
||||
let minutes: number = +values[1];
|
||||
//Convert to UTC time
|
||||
let timezoneOffset: number = this._localTime.getTimezoneOffset();
|
||||
let utcTimes: number = hours * ONE_HOUR_SECONDS + minutes * 60;
|
||||
utcTimes += timezoneOffset * 60;
|
||||
if (utcTimes < 0) {
|
||||
utcTimes += ONE_DAY_SECONDS;
|
||||
}
|
||||
|
||||
if (utcTimes >= ONE_DAY_SECONDS) {
|
||||
utcTimes -= ONE_DAY_SECONDS;
|
||||
}
|
||||
|
||||
this.config.scan_all_policy.value.parameters.daily_time = utcTimes;
|
||||
}
|
||||
|
||||
@ViewChild("systemConfigFrom") systemSettingsForm: NgForm;
|
||||
|
||||
get editable(): boolean {
|
||||
return this.vulnerabilityConfig &&
|
||||
this.vulnerabilityConfig.scan_all_policy &&
|
||||
this.vulnerabilityConfig.scan_all_policy.editable;
|
||||
}
|
||||
|
||||
get isValid(): boolean {
|
||||
return this.systemSettingsForm && this.systemSettingsForm.valid;
|
||||
}
|
||||
|
||||
get showTimePicker(): boolean {
|
||||
return this.vulnerabilityConfig &&
|
||||
this.vulnerabilityConfig.scan_all_policy &&
|
||||
this.vulnerabilityConfig.scan_all_policy.value &&
|
||||
this.vulnerabilityConfig.scan_all_policy.value.type === "daily";
|
||||
}
|
||||
|
||||
constructor(
|
||||
private scanningService: ScanningResultService,
|
||||
private errorHandler: ErrorHandler,
|
||||
private translate: TranslateService) { }
|
||||
|
||||
scanNow(): void {
|
||||
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.
|
||||
})
|
||||
.catch(error => this.errorHandler.error(error))
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ import { INLINE_ALERT_DIRECTIVES } from './inline-alert/index';
|
||||
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 {
|
||||
SystemInfoService,
|
||||
@ -37,7 +38,9 @@ import {
|
||||
TagService,
|
||||
TagDefaultService,
|
||||
ScanningResultService,
|
||||
ScanningResultDefaultService
|
||||
ScanningResultDefaultService,
|
||||
ConfigurationService,
|
||||
ConfigurationDefaultService
|
||||
} from './service/index';
|
||||
import {
|
||||
ErrorHandler,
|
||||
@ -68,7 +71,8 @@ export const DefaultServiceConfig: IServiceConfig = {
|
||||
langMessageLoader: "local",
|
||||
langMessagePathForHttpLoader: "i18n/langs/",
|
||||
langMessageFileSuffixForHttpLoader: "-lang.json",
|
||||
localI18nMessageVariableMap: {}
|
||||
localI18nMessageVariableMap: {},
|
||||
configurationEndpoint: "/api/configurations"
|
||||
};
|
||||
|
||||
/**
|
||||
@ -103,7 +107,10 @@ export interface HarborModuleConfig {
|
||||
tagService?: Provider,
|
||||
|
||||
//Service implementation for vulnerability scanning
|
||||
scanningService?: Provider
|
||||
scanningService?: Provider,
|
||||
|
||||
//Service implementation for configuration
|
||||
configService?: Provider
|
||||
}
|
||||
|
||||
/**
|
||||
@ -145,7 +152,8 @@ export function initConfig(translateInitializer: TranslateServiceInitializer, co
|
||||
CREATE_EDIT_RULE_DIRECTIVES,
|
||||
DATETIME_PICKER_DIRECTIVES,
|
||||
VULNERABILITY_DIRECTIVES,
|
||||
PUSH_IMAGE_BUTTON_DIRECTIVES
|
||||
PUSH_IMAGE_BUTTON_DIRECTIVES,
|
||||
CONFIGURATION_DIRECTIVES
|
||||
],
|
||||
exports: [
|
||||
LOG_DIRECTIVES,
|
||||
@ -164,6 +172,7 @@ export function initConfig(translateInitializer: TranslateServiceInitializer, co
|
||||
DATETIME_PICKER_DIRECTIVES,
|
||||
VULNERABILITY_DIRECTIVES,
|
||||
PUSH_IMAGE_BUTTON_DIRECTIVES,
|
||||
CONFIGURATION_DIRECTIVES,
|
||||
TranslateModule
|
||||
],
|
||||
providers: []
|
||||
@ -183,6 +192,7 @@ export class HarborLibraryModule {
|
||||
config.repositoryService || { provide: RepositoryService, useClass: RepositoryDefaultService },
|
||||
config.tagService || { provide: TagService, useClass: TagDefaultService },
|
||||
config.scanningService || { provide: ScanningResultService, useClass: ScanningResultDefaultService },
|
||||
config.configService || { provide: ConfigurationService, useClass: ConfigurationDefaultService },
|
||||
//Do initializing
|
||||
TranslateServiceInitializer,
|
||||
{
|
||||
@ -208,6 +218,7 @@ export class HarborLibraryModule {
|
||||
config.repositoryService || { provide: RepositoryService, useClass: RepositoryDefaultService },
|
||||
config.tagService || { provide: TagService, useClass: TagDefaultService },
|
||||
config.scanningService || { provide: ScanningResultService, useClass: ScanningResultDefaultService },
|
||||
config.configService || { provide: ConfigurationService, useClass: ConfigurationDefaultService }
|
||||
]
|
||||
};
|
||||
}
|
||||
|
@ -15,3 +15,4 @@ export * from './vulnerability-scanning/index';
|
||||
export * from './i18n/index';
|
||||
export * from './push-image/index';
|
||||
export * from './third-party/index';
|
||||
export * from './config/index';
|
@ -172,4 +172,12 @@ export interface IServiceConfig {
|
||||
* @memberOf IServiceConfig
|
||||
*/
|
||||
localI18nMessageVariableMap?: { [key: string]: any };
|
||||
|
||||
/**
|
||||
* The base endpoint of configuration service.
|
||||
*
|
||||
* @type {string}
|
||||
* @memberOf IServiceConfig
|
||||
*/
|
||||
configurationEndpoint?: string;
|
||||
}
|
42
src/ui_ng/lib/src/service/configuration.service.spec.ts
Normal file
42
src/ui_ng/lib/src/service/configuration.service.spec.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { TestBed, inject } from '@angular/core/testing';
|
||||
|
||||
import { ConfigurationService, ConfigurationDefaultService } from './configuration.service';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||
|
||||
describe('ConfigurationService', () => {
|
||||
const mockConfig: IServiceConfig = {
|
||||
configurationEndpoint: "/api/configurations/testing"
|
||||
};
|
||||
|
||||
let config: IServiceConfig;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
SharedModule
|
||||
],
|
||||
providers: [
|
||||
ConfigurationDefaultService,
|
||||
{
|
||||
provide: ConfigurationService,
|
||||
useClass: ConfigurationDefaultService
|
||||
}, {
|
||||
provide: SERVICE_CONFIG,
|
||||
useValue: mockConfig
|
||||
}]
|
||||
});
|
||||
|
||||
config = TestBed.get(SERVICE_CONFIG);
|
||||
});
|
||||
|
||||
it('should be initialized', inject([ConfigurationDefaultService], (service: ConfigurationService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should inject the right config', () => {
|
||||
expect(config).toBeTruthy();
|
||||
expect(config.configurationEndpoint).toEqual("/api/configurations/testing");
|
||||
});
|
||||
|
||||
});
|
69
src/ui_ng/lib/src/service/configuration.service.ts
Normal file
69
src/ui_ng/lib/src/service/configuration.service.ts
Normal file
@ -0,0 +1,69 @@
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Injectable, Inject } from "@angular/core";
|
||||
import 'rxjs/add/observable/of';
|
||||
import { SERVICE_CONFIG, IServiceConfig } from '../service.config';
|
||||
import { Http } from '@angular/http';
|
||||
import { HTTP_JSON_OPTIONS } from '../utils';
|
||||
import { Configuration } from '../config/config'
|
||||
|
||||
|
||||
/**
|
||||
* Service used to get and save registry-related configurations.
|
||||
*
|
||||
* @export
|
||||
* @abstract
|
||||
* @class ConfigurationService
|
||||
*/
|
||||
export abstract class ConfigurationService {
|
||||
|
||||
/**
|
||||
* Get configurations.
|
||||
*
|
||||
* @abstract
|
||||
* @returns {(Observable<Configuration> | Promise<Configuration> | Configuration)}
|
||||
*
|
||||
* @memberOf ConfigurationService
|
||||
*/
|
||||
abstract getConfigurations(): Observable<Configuration> | Promise<Configuration> | Configuration;
|
||||
|
||||
/**
|
||||
* Save configurations.
|
||||
*
|
||||
* @abstract
|
||||
* @returns {(Observable<Configuration> | Promise<Configuration> | Configuration)}
|
||||
*
|
||||
* @memberOf ConfigurationService
|
||||
*/
|
||||
abstract saveConfigurations(changedConfigs: any | { [key: string]: any | any[] }): Observable<any> | Promise<any> | any;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ConfigurationDefaultService extends ConfigurationService {
|
||||
_baseUrl: string;
|
||||
|
||||
constructor(
|
||||
private http: Http,
|
||||
@Inject(SERVICE_CONFIG) private config: IServiceConfig) {
|
||||
super();
|
||||
|
||||
this._baseUrl = this.config && this.config.configurationEndpoint ?
|
||||
this.config.configurationEndpoint : "/api/configurations";
|
||||
}
|
||||
|
||||
getConfigurations(): Observable<Configuration> | Promise<Configuration> | Configuration {
|
||||
return this.http.get(this._baseUrl, HTTP_JSON_OPTIONS).toPromise()
|
||||
.then(response => response.json() as Configuration)
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
|
||||
saveConfigurations(changedConfigs: any | { [key: string]: any | any[] }): Observable<any> | Promise<any> | any {
|
||||
if (!changedConfigs) {
|
||||
return Promise.reject("Bad argument!");
|
||||
}
|
||||
|
||||
return this.http.put(this._baseUrl, JSON.stringify(changedConfigs), HTTP_JSON_OPTIONS)
|
||||
.toPromise()
|
||||
.then(() => { })
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
}
|
@ -7,3 +7,4 @@ export * from './repository.service';
|
||||
export * from './tag.service';
|
||||
export * from './RequestQueryParams';
|
||||
export * from './scanning.service';
|
||||
export * from './configuration.service';
|
@ -53,6 +53,16 @@ export abstract class ScanningResultService {
|
||||
* @memberOf ScanningResultService
|
||||
*/
|
||||
abstract startVulnerabilityScanning(repoName: string, tagId: string): Observable<any> | Promise<any> | any;
|
||||
|
||||
/**
|
||||
* Trigger the scanning all action.
|
||||
*
|
||||
* @abstract
|
||||
* @returns {(Observable<any> | Promise<any> | any)}
|
||||
*
|
||||
* @memberOf ScanningResultService
|
||||
*/
|
||||
abstract startScanningAll(): Observable<any> | Promise<any> | any;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
@ -95,4 +105,10 @@ export class ScanningResultDefaultService extends ScanningResultService {
|
||||
.then(() => { return true })
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
|
||||
startScanningAll(): Observable<any> | Promise<any> | any {
|
||||
return this.http.post(`${this._baseUrl}/scanAll`,{}).toPromise()
|
||||
.then(() => {return true})
|
||||
.catch(error => Promise.reject(error));
|
||||
}
|
||||
}
|
@ -117,6 +117,10 @@ export class ResultTipComponent implements OnInit {
|
||||
return "VULNERABILITY.SINGULAR";
|
||||
}
|
||||
|
||||
packageText(count: number): string {
|
||||
return count > 1 ? "VULNERABILITY.PACKAGES" : "VULNERABILITY.PACKAGE";
|
||||
}
|
||||
|
||||
public get completeTimestamp(): Date {
|
||||
return this.summary && this.summary.update_time ? this.summary.update_time : new Date();
|
||||
}
|
||||
|
@ -13,23 +13,23 @@ export const TIP_COMPONENT_HTML: string = `
|
||||
<div class="bar-summary bar-tooltip-fon">
|
||||
<div *ngIf="hasHigh" class="bar-summary-item">
|
||||
<clr-icon shape="exclamation-circle" class="is-error" size="24"></clr-icon>
|
||||
<span>{{highCount}} {{'VULNERABILITY.SEVERITY.HIGH' | translate }} {{ highSuffix | translate }}</span>
|
||||
<span>{{highCount}} {{packageText(highCount) | translate }} {{'VULNERABILITY.SEVERITY.HIGH' | translate }} {{ highSuffix | translate }}</span>
|
||||
</div>
|
||||
<div *ngIf="hasMedium" class="bar-summary-item">
|
||||
<clr-icon *ngIf="hasMedium" shape="exclamation-triangle" class="is-warning" size="24"></clr-icon>
|
||||
<span>{{mediumCount}} {{'VULNERABILITY.SEVERITY.MEDIUM' | translate }} {{ mediumSuffix | translate }}</span>
|
||||
<span>{{mediumCount}} {{packageText(mediumCount) | translate }} {{'VULNERABILITY.SEVERITY.MEDIUM' | translate }} {{ mediumSuffix | translate }}</span>
|
||||
</div>
|
||||
<div *ngIf="hasLow" class="bar-summary-item">
|
||||
<clr-icon shape="info-circle" class="is-info" size="24"></clr-icon>
|
||||
<span>{{lowCount}} {{'VULNERABILITY.SEVERITY.LOW' | translate }} {{ lowSuffix | translate }}</span>
|
||||
<span>{{lowCount}} {{packageText(lowCount) | translate }} {{'VULNERABILITY.SEVERITY.LOW' | translate }} {{ lowSuffix | translate }}</span>
|
||||
</div>
|
||||
<div *ngIf="hasUnknown" class="bar-summary-item">
|
||||
<clr-icon shape="help" size="24"></clr-icon>
|
||||
<span>{{unknownCount}} {{'VULNERABILITY.SEVERITY.UNKNOWN' | translate }} {{ unknownSuffix | translate }}</span>
|
||||
<span>{{unknownCount}} {{packageText(unknownCount) | translate }} {{'VULNERABILITY.SEVERITY.UNKNOWN' | translate }} {{ unknownSuffix | translate }}</span>
|
||||
</div>
|
||||
<div *ngIf="hasNone" class="bar-summary-item">
|
||||
<clr-icon shape="check-circle" class="is-success" size="24"></clr-icon>
|
||||
<span>{{noneCount}} {{'VULNERABILITY.SEVERITY.NONE' | translate }} {{ noneSuffix | translate }}</span>
|
||||
<span>{{noneCount}} {{packageText(noneCount) | translate }} {{'VULNERABILITY.SEVERITY.NONE' | translate }} {{ noneSuffix | translate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
|
@ -31,7 +31,7 @@
|
||||
"clarity-icons": "^0.9.8",
|
||||
"clarity-ui": "^0.9.8",
|
||||
"core-js": "^2.4.1",
|
||||
"harbor-ui": "^0.2.25",
|
||||
"harbor-ui": "^0.2.40",
|
||||
"intl": "^1.2.5",
|
||||
"mutationobserver-shim": "^0.3.2",
|
||||
"ngx-cookie": "^1.0.0",
|
||||
|
@ -15,7 +15,7 @@ import { Component, Input, ViewChild } from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
import { Configuration } from '../config';
|
||||
import { Configuration } from 'harbor-ui';
|
||||
|
||||
@Component({
|
||||
selector: 'config-auth',
|
||||
|
@ -15,50 +15,24 @@
|
||||
<li role="presentation" class="nav-item">
|
||||
<button id="config-system" class="btn btn-link nav-link" aria-controls="system_settings" [class.active]='isCurrentTabLink("config-system")' type="button" (click)='tabLinkClick("config-system")'>{{'CONFIG.SYSTEM' | translate }}</button>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item">
|
||||
<button id="config-vulnerability" class="btn btn-link nav-link" aria-controls="vulnerability" [class.active]='isCurrentTabLink("config-vulnerability")' type="button" (click)='tabLinkClick("config-vulnerability")'>Vulnerability</button>
|
||||
</li>
|
||||
</ul>
|
||||
<section id="authentication" role="tabpanel" aria-labelledby="config-auth" [hidden]='!isCurrentTabContent("authentication")'>
|
||||
<config-auth [ldapConfig]="allConfig"></config-auth>
|
||||
</section>
|
||||
<section id="replication" role="tabpanel" aria-labelledby="config-replication" [hidden]='!isCurrentTabContent("replication")'>
|
||||
<form #repoConfigFrom="ngForm" class="form">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="verifyRemoteCert">{{'CONFIG.VERIFY_REMOTE_CERT' | translate }}</label>
|
||||
<clr-checkbox name="verifyRemoteCert" id="verifyRemoteCert" [(ngModel)]="allConfig.verify_remote_cert.value" [disabled]="disabled(allConfig.verify_remote_cert)">
|
||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-lg tooltip-top-right" style="top:-8px;">
|
||||
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
|
||||
<span class="tooltip-content">{{'CONFIG.TOOLTIP.VERIFY_REMOTE_CERT' | translate }}</span>
|
||||
</a>
|
||||
</clr-checkbox>
|
||||
</div>
|
||||
</section>
|
||||
</form>
|
||||
<replication-config [(replicationConfig)]="allConfig"></replication-config>
|
||||
</section>
|
||||
<section id="email" role="tabpanel" aria-labelledby="config-email" [hidden]='!isCurrentTabContent("email")'>
|
||||
<config-email [mailConfig]="allConfig"></config-email>
|
||||
</section>
|
||||
<section id="system_settings" role="tabpanel" aria-labelledby="config-system" [hidden]='!isCurrentTabContent("system_settings")'>
|
||||
<form #systemConfigFrom="ngForm" class="form">
|
||||
<section class="form-block">
|
||||
<div class="form-group">
|
||||
<label for="tokenExpiration" class="required">{{'CONFIG.TOKEN_EXPIRATION' | translate}}</label>
|
||||
<label for="tokenExpiration" aria-haspopup="true" role="tooltip" class="tooltip tooltip-validation tooltip-md tooltip-top-right" [class.invalid]="tokenExpirationInput.invalid && (tokenExpirationInput.dirty || tokenExpirationInput.touched)">
|
||||
<input name="tokenExpiration" type="text" #tokenExpirationInput="ngModel" [(ngModel)]="allConfig.token_expiration.value"
|
||||
required
|
||||
pattern="^[1-9]{1}[\d]*$"
|
||||
id="tokenExpiration"
|
||||
size="40" [disabled]="disabled(allConfig.token_expiration)">
|
||||
<span class="tooltip-content">
|
||||
{{'TOOLTIP.NUMBER_REQUIRED' | translate}}
|
||||
</span>
|
||||
</label>
|
||||
<a href="javascript:void(0)" role="tooltip" aria-haspopup="true" class="tooltip tooltip-top-right">
|
||||
<clr-icon shape="info-circle" class="info-tips-icon" size="24"></clr-icon>
|
||||
<span class="tooltip-content">{{'CONFIG.TOOLTIP.TOKEN_EXPIRATION' | translate}}</span>
|
||||
</a>
|
||||
</div>
|
||||
<system-settings [(systemSettings)]="allConfig"></system-settings>
|
||||
</section>
|
||||
</form>
|
||||
<section id="vulnerability" role="tabpanel" aria-labelledby="config-vulnerability" [hidden]='!isCurrentTabContent("vulnerability")'>
|
||||
<vulnerability-config [(vulnerabilityConfig)]="allConfig"></vulnerability-config>
|
||||
</section>
|
||||
<div>
|
||||
<button type="button" class="btn btn-primary" (click)="save()" [disabled]="!isValid() || !hasChanges()">{{'BUTTON.SAVE' | translate}}</button>
|
||||
@ -67,6 +41,7 @@
|
||||
<button type="button" class="btn btn-outline" (click)="testLDAPServer()" *ngIf="showLdapServerBtn" [disabled]="!isLDAPConfigValid()">{{'BUTTON.TEST_LDAP' | translate}}</button>
|
||||
<span id="forTestingMail" class="spinner spinner-inline" [hidden]="hideMailTestingSpinner"></span>
|
||||
<span id="forTestingLDAP" class="spinner spinner-inline" [hidden]="hideLDAPTestingSpinner"></span>
|
||||
<button type="button" class="btn btn-primary" (click)="consoleTest()">CONSOLE</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -13,12 +13,9 @@
|
||||
// limitations under the License.
|
||||
import { Component, OnInit, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { Router } from '@angular/router';
|
||||
import { NgForm } from '@angular/forms';
|
||||
|
||||
import { ConfigurationService } from './config.service';
|
||||
import { Configuration } from './config';
|
||||
import { ConfirmationTargets, ConfirmationState } from '../shared/shared.const';;
|
||||
import { StringValueItem } from './config';
|
||||
import { ConfirmationDialogService } from '../shared/confirmation-dialog/confirmation-dialog.service';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
import { ConfirmationMessage } from '../shared/confirmation-dialog/confirmation-message'
|
||||
@ -29,13 +26,22 @@ import { ConfigurationEmailComponent } from './email/config-email.component';
|
||||
import { AppConfigService } from '../app-config.service';
|
||||
import { SessionService } from '../shared/session.service';
|
||||
import { MessageHandlerService } from '../shared/message-handler/message-handler.service';
|
||||
import {
|
||||
Configuration,
|
||||
StringValueItem,
|
||||
ComplexValueItem,
|
||||
ReplicationConfigComponent,
|
||||
SystemSettingsComponent,
|
||||
VulnerabilityConfigComponent
|
||||
} from 'harbor-ui';
|
||||
|
||||
const fakePass = "aWpLOSYkIzJTTU4wMDkx";
|
||||
const TabLinkContentMap = {
|
||||
"config-auth": "authentication",
|
||||
"config-replication": "replication",
|
||||
"config-email": "email",
|
||||
"config-system": "system_settings"
|
||||
"config-system": "system_settings",
|
||||
"config-vulnerability": "vulnerability"
|
||||
};
|
||||
|
||||
@Component({
|
||||
@ -52,8 +58,9 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
testingMailOnGoing: boolean = false;
|
||||
testingLDAPOnGoing: boolean = false;
|
||||
|
||||
@ViewChild("repoConfigFrom") repoConfigForm: NgForm;
|
||||
@ViewChild("systemConfigFrom") systemConfigForm: NgForm;
|
||||
@ViewChild(ReplicationConfigComponent) replicationConfig: ReplicationConfigComponent;
|
||||
@ViewChild(SystemSettingsComponent) systemSettingsConfig: SystemSettingsComponent;
|
||||
@ViewChild(VulnerabilityConfigComponent) vulnerabilityConfig: VulnerabilityConfigComponent;
|
||||
@ViewChild(ConfigurationEmailComponent) mailConfig: ConfigurationEmailComponent;
|
||||
@ViewChild(ConfigurationAuthComponent) authConfig: ConfigurationAuthComponent;
|
||||
|
||||
@ -64,6 +71,11 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
private appConfigService: AppConfigService,
|
||||
private session: SessionService) { }
|
||||
|
||||
consoleTest(): void {
|
||||
console.log(this.allConfig, this.originalCopy);
|
||||
console.log("-------------");
|
||||
console.log(this.getChanges());
|
||||
}
|
||||
isCurrentTabLink(tabId: string): boolean {
|
||||
return this.currentTabId === tabId;
|
||||
}
|
||||
@ -101,6 +113,9 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
case "config-system":
|
||||
properties = ["token_expiration"];
|
||||
break;
|
||||
case "config-vulnerability":
|
||||
properties = ["scan_all_policy"];
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
@ -146,10 +161,12 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public isValid(): boolean {
|
||||
return this.repoConfigForm &&
|
||||
this.repoConfigForm.valid &&
|
||||
this.systemConfigForm &&
|
||||
this.systemConfigForm.valid &&
|
||||
return this.replicationConfig &&
|
||||
this.replicationConfig.isValid &&
|
||||
this.systemSettingsConfig &&
|
||||
this.systemSettingsConfig.isValid &&
|
||||
this.vulnerabilityConfig &&
|
||||
this.vulnerabilityConfig.isValid &&
|
||||
this.mailConfig &&
|
||||
this.mailConfig.isValid() &&
|
||||
this.authConfig &&
|
||||
@ -191,7 +208,7 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public tabLinkClick(tabLink: string) {
|
||||
//Whether has unsave changes in current tab
|
||||
//Whether has unsaved changes in current tab
|
||||
let changes = this.hasUnsavedChangesOfCurrentTab();
|
||||
if (!changes) {
|
||||
this.currentTabId = tabLink;
|
||||
@ -210,6 +227,14 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
public save(): void {
|
||||
let changes = this.getChanges();
|
||||
if (!this.isEmpty(changes)) {
|
||||
//Fix policy parameters issue
|
||||
let scanningAllPolicy = changes["scan_all_policy"];
|
||||
if (scanningAllPolicy &&
|
||||
scanningAllPolicy.type !== "daily" &&
|
||||
scanningAllPolicy.parameters) {
|
||||
delete (scanningAllPolicy.parameters);
|
||||
}
|
||||
|
||||
this.onGoing = true;
|
||||
this.configService.saveConfiguration(changes)
|
||||
.then(response => {
|
||||
@ -247,7 +272,7 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
if (!this.isEmpty(changes)) {
|
||||
this.confirmUnsavedChanges(changes);
|
||||
} else {
|
||||
//Inprop situation, should not come here
|
||||
//Invalid situation, should not come here
|
||||
console.error("Nothing changed");
|
||||
}
|
||||
}
|
||||
@ -364,14 +389,14 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
retrieveConfig(): void {
|
||||
this.onGoing = true;
|
||||
this.configService.getConfiguration()
|
||||
.then(configurations => {
|
||||
.then((configurations: Configuration) => {
|
||||
this.onGoing = false;
|
||||
|
||||
//Add two password fields
|
||||
configurations.email_password = new StringValueItem(fakePass, true);
|
||||
configurations.ldap_search_password = new StringValueItem(fakePass, true);
|
||||
this.allConfig = configurations;
|
||||
|
||||
this.allConfig = configurations;
|
||||
//Keep the original copy of the data
|
||||
this.originalCopy = this.clone(configurations);
|
||||
})
|
||||
@ -390,8 +415,8 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
*
|
||||
* @memberOf ConfigurationComponent
|
||||
*/
|
||||
getChanges(): any {
|
||||
let changes = {};
|
||||
getChanges(): { [key: string]: any | any[] } {
|
||||
let changes: { [key: string]: any | any[] } = {};
|
||||
if (!this.allConfig || !this.originalCopy) {
|
||||
return changes;
|
||||
}
|
||||
@ -399,11 +424,11 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
for (let prop in this.allConfig) {
|
||||
let field = this.originalCopy[prop];
|
||||
if (field && field.editable) {
|
||||
if (field.value != this.allConfig[prop].value) {
|
||||
if (!this.compareValue(field.value, this.allConfig[prop].value)) {
|
||||
changes[prop] = this.allConfig[prop].value;
|
||||
//Fix boolean issue
|
||||
if (typeof field.value === "boolean") {
|
||||
changes[prop] = changes[prop] ? "1" : "0";
|
||||
//Number
|
||||
if (typeof field.value === "number") {
|
||||
changes[prop] = +changes[prop];
|
||||
}
|
||||
|
||||
//Trim string value
|
||||
@ -417,6 +442,19 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
return changes;
|
||||
}
|
||||
|
||||
//private
|
||||
compareValue(a: any, b: any): boolean {
|
||||
if ((a && !b) || (!a && b)) return false;
|
||||
if (!a && !b) return true;
|
||||
|
||||
return JSON.stringify(a) === JSON.stringify(b);
|
||||
}
|
||||
|
||||
//private
|
||||
isEmpty(obj: any): boolean {
|
||||
return !obj || JSON.stringify(obj) === "{}";
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Deep clone the configuration object
|
||||
@ -428,18 +466,11 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
* @memberOf ConfigurationComponent
|
||||
*/
|
||||
clone(src: Configuration): Configuration {
|
||||
let dest = new Configuration();
|
||||
if (!src) {
|
||||
return dest;//Empty
|
||||
return new Configuration();//Empty
|
||||
}
|
||||
|
||||
for (let prop in src) {
|
||||
if (src[prop]) {
|
||||
dest[prop] = Object.assign({}, src[prop]); //Deep copy inner object
|
||||
}
|
||||
}
|
||||
|
||||
return dest;
|
||||
return JSON.parse(JSON.stringify(src));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -464,14 +495,6 @@ export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
isEmpty(obj: any) {
|
||||
for (let key in obj) {
|
||||
if (obj.hasOwnProperty(key))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
disabled(prop: any): boolean {
|
||||
return !(prop && prop.editable);
|
||||
}
|
||||
|
@ -15,7 +15,7 @@ import { Injectable } from '@angular/core';
|
||||
import { Headers, Http, RequestOptions } from '@angular/http';
|
||||
import 'rxjs/add/operator/toPromise';
|
||||
|
||||
import { Configuration } from './config';
|
||||
import { Configuration } from 'harbor-ui';
|
||||
|
||||
const configEndpoint = "/api/configurations";
|
||||
const emailEndpoint = "/api/email/ping";
|
||||
|
@ -14,7 +14,7 @@
|
||||
import { Component, Input, ViewChild } from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
|
||||
import { Configuration } from '../config';
|
||||
import { Configuration } from 'harbor-ui';
|
||||
|
||||
@Component({
|
||||
selector: 'config-email',
|
||||
|
@ -403,6 +403,15 @@
|
||||
"UID": "LDAP UID",
|
||||
"SCOPE": "LDAP Scope"
|
||||
},
|
||||
"SCANNING": {
|
||||
"TRIGGER_SCAN_ALL_SUCCESS": "Trigger scan all successfully!",
|
||||
"TITLE": "Vulnerability Scanning",
|
||||
"SCAN_ALL": "Scan All",
|
||||
"SCAN_NOW": "SCAN NOW",
|
||||
"NONE_POLICY": "None",
|
||||
"DAILY_POLICY": "Daily At",
|
||||
"REFRESH_POLICY": "Upon Refresh"
|
||||
},
|
||||
"TEST_MAIL_SUCCESS": "Connection to mail server is verified.",
|
||||
"TEST_LDAP_SUCCESS": "Connection to LDAP server is verified.",
|
||||
"TEST_MAIL_FAILED": "Failed to verify mail server with error: {{param}}.",
|
||||
|
@ -404,6 +404,15 @@
|
||||
"UID": "LDAP UID",
|
||||
"SCOPE": "LDAP Ámbito"
|
||||
},
|
||||
"SCANNING": {
|
||||
"TRIGGER_SCAN_ALL_SUCCESS": "Trigger scan all successfully!",
|
||||
"TITLE": "Vulnerability Scanning",
|
||||
"SCAN_ALL": "Scan All",
|
||||
"SCAN_NOW": "SCAN NOW",
|
||||
"NONE_POLICY": "None",
|
||||
"DAILY_POLICY": "Daily At",
|
||||
"REFRESH_POLICY": "Upon Refresh"
|
||||
},
|
||||
"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.",
|
||||
"TEST_MAIL_FAILED": "Fallo al verificar el servidor de correo con el error: {{param}}.",
|
||||
|
@ -403,6 +403,15 @@
|
||||
"UID": "LDAP用户UID的属性",
|
||||
"SCOPE": "LDAP搜索范围"
|
||||
},
|
||||
"SCANNING": {
|
||||
"TRIGGER_SCAN_ALL_SUCCESS": "成功启动扫描所有镜像任务!",
|
||||
"TITLE": "缺陷扫描",
|
||||
"SCAN_ALL": "扫描所有",
|
||||
"SCAN_NOW": "开始扫描",
|
||||
"NONE_POLICY": "无",
|
||||
"DAILY_POLICY": "每日",
|
||||
"REFRESH_POLICY": "缺陷库刷新后"
|
||||
},
|
||||
"TEST_MAIL_SUCCESS": "邮件服务器的连通正常。",
|
||||
"TEST_LDAP_SUCCESS": "LDAP服务器的连通正常。",
|
||||
"TEST_MAIL_FAILED": "验证邮件服务器失败,错误: {{param}}。",
|
||||
|
Loading…
Reference in New Issue
Block a user