mirror of https://github.com/goharbor/harbor.git
378 lines
13 KiB
TypeScript
378 lines
13 KiB
TypeScript
import {
|
|
Component,
|
|
Input,
|
|
OnInit,
|
|
Output,
|
|
EventEmitter,
|
|
ViewChild,
|
|
Inject,
|
|
OnChanges,
|
|
SimpleChanges,
|
|
ElementRef
|
|
} from '@angular/core';
|
|
import {NgForm} from '@angular/forms';
|
|
import {Configuration, StringValueItem} from '../config';
|
|
import {SERVICE_CONFIG, IServiceConfig} from '../../../entities/service.config';
|
|
import {clone, isEmpty, getChanges, compareValue} from '../../../utils/utils';
|
|
import {ErrorHandler} from '../../../utils/error-handler';
|
|
import {ConfirmationMessage} from '../../confirmation-dialog/confirmation-message';
|
|
import {ConfirmationDialogComponent} from '../../confirmation-dialog/confirmation-dialog.component';
|
|
import {ConfirmationState, ConfirmationTargets} from '../../../entities/shared.const';
|
|
import {ConfirmationAcknowledgement} from '../../confirmation-dialog/confirmation-state-message';
|
|
import {
|
|
ConfigurationService, SystemCVEAllowlist, SystemInfo, SystemInfoService, VulnerabilityItem
|
|
} from '../../../services';
|
|
import {forkJoin} from "rxjs";
|
|
|
|
const fakePass = 'aWpLOSYkIzJTTU4wMDkx';
|
|
const ONE_HOUR_MINUTES: number = 60;
|
|
const ONE_DAY_MINUTES: number = 24 * ONE_HOUR_MINUTES;
|
|
const ONE_THOUSAND: number = 1000;
|
|
const CVE_DETAIL_PRE_URL = `https://nvd.nist.gov/vuln/detail/`;
|
|
const TARGET_BLANK = "_blank";
|
|
|
|
@Component({
|
|
selector: 'system-settings',
|
|
templateUrl: './system-settings.component.html',
|
|
styleUrls: ['./system-settings.component.scss', '../registry-config.component.scss']
|
|
})
|
|
export class SystemSettingsComponent implements OnChanges, OnInit {
|
|
config: Configuration = new Configuration();
|
|
onGoing = false;
|
|
private originalConfig: Configuration;
|
|
downloadLink: string;
|
|
robotTokenExpiration: string;
|
|
systemAllowlist: SystemCVEAllowlist;
|
|
systemAllowlistOrigin: SystemCVEAllowlist;
|
|
cveIds: string;
|
|
showAddModal: boolean = false;
|
|
systemInfo: SystemInfo;
|
|
@Output() configChange: EventEmitter<Configuration> = new EventEmitter<Configuration>();
|
|
@Output() readOnlyChange: EventEmitter<boolean> = new EventEmitter<boolean>();
|
|
@Output() reloadSystemConfig: EventEmitter<any> = new EventEmitter<any>();
|
|
|
|
@Input()
|
|
get systemSettings(): Configuration {
|
|
return this.config;
|
|
}
|
|
|
|
set systemSettings(cfg: Configuration) {
|
|
this.config = cfg;
|
|
this.configChange.emit(this.config);
|
|
}
|
|
|
|
@Input() showSubTitle: boolean = false;
|
|
@Input() hasAdminRole: boolean = false;
|
|
@Input() hasCAFile: boolean = false;
|
|
@Input() withAdmiral = false;
|
|
|
|
@ViewChild("systemConfigFrom", {static: false}) systemSettingsForm: NgForm;
|
|
@ViewChild("cfgConfirmationDialog", {static: false}) confirmationDlg: ConfirmationDialogComponent;
|
|
@ViewChild('dateInput', {static: false}) dateInput: ElementRef;
|
|
|
|
get editable(): boolean {
|
|
return this.systemSettings &&
|
|
this.systemSettings.token_expiration &&
|
|
this.systemSettings.token_expiration.editable;
|
|
}
|
|
|
|
get robotExpirationEditable(): boolean {
|
|
return this.systemSettings &&
|
|
this.systemSettings.robot_token_duration &&
|
|
this.systemSettings.robot_token_duration.editable;
|
|
}
|
|
|
|
public isValid(): boolean {
|
|
return this.systemSettingsForm && this.systemSettingsForm.valid;
|
|
}
|
|
|
|
public hasChanges(): boolean {
|
|
return !isEmpty(this.getChanges());
|
|
}
|
|
|
|
public getChanges() {
|
|
let allChanges = getChanges(this.originalConfig, this.config);
|
|
if (allChanges) {
|
|
return this.getSystemChanges(allChanges);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
ngOnChanges(changes: SimpleChanges): void {
|
|
if (changes && changes["systemSettings"]) {
|
|
this.originalConfig = clone(this.config);
|
|
}
|
|
}
|
|
|
|
public getSystemChanges(allChanges: any) {
|
|
let changes = {};
|
|
for (let prop in allChanges) {
|
|
if (prop === 'token_expiration' || prop === 'read_only' || prop === 'project_creation_restriction'
|
|
|| prop === 'robot_token_duration' || prop === 'notification_enable') {
|
|
changes[prop] = allChanges[prop];
|
|
}
|
|
}
|
|
return changes;
|
|
}
|
|
|
|
setRepoReadOnlyValue($event: any) {
|
|
this.systemSettings.read_only.value = $event;
|
|
}
|
|
|
|
setWebhookNotificationEnabledValue($event: any) {
|
|
this.systemSettings.notification_enable.value = $event;
|
|
}
|
|
|
|
disabled(prop: any): boolean {
|
|
return !(prop && prop.editable);
|
|
}
|
|
|
|
get canDownloadCert(): boolean {
|
|
return this.hasAdminRole && this.hasCAFile;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Save the changed values
|
|
*
|
|
* @memberOf ConfigurationComponent
|
|
*/
|
|
public save(): void {
|
|
let changes = this.getChanges();
|
|
if (!isEmpty(changes) || !compareValue(this.systemAllowlistOrigin, this.systemAllowlist)) {
|
|
this.onGoing = true;
|
|
let observables = [];
|
|
if (!isEmpty(changes)) {
|
|
observables.push(this.configService.saveConfigurations(changes));
|
|
}
|
|
if (!compareValue(this.systemAllowlistOrigin, this.systemAllowlist)) {
|
|
observables.push(this.systemInfoService.updateSystemAllowlist(this.systemAllowlist));
|
|
}
|
|
forkJoin(observables).subscribe(result => {
|
|
this.onGoing = false;
|
|
if (!isEmpty(changes)) {
|
|
// API should return the updated configurations here
|
|
// Unfortunately API does not do that
|
|
// To refresh the view, we can clone the original data copy
|
|
// or force refresh by calling service.
|
|
// HERE we choose force way
|
|
this.retrieveConfig();
|
|
if ('read_only' in changes) {
|
|
this.readOnlyChange.emit(changes['read_only']);
|
|
}
|
|
|
|
this.reloadSystemConfig.emit();
|
|
}
|
|
if (!compareValue(this.systemAllowlistOrigin, this.systemAllowlist)) {
|
|
this.systemAllowlistOrigin = clone(this.systemAllowlist);
|
|
}
|
|
this.errorHandler.info('CONFIG.SAVE_SUCCESS');
|
|
}, error => {
|
|
this.onGoing = false;
|
|
this.errorHandler.error(error);
|
|
});
|
|
} else {
|
|
// Inprop situation, should not come here
|
|
console.error('Save abort because nothing changed');
|
|
}
|
|
}
|
|
|
|
retrieveConfig(): void {
|
|
this.onGoing = true;
|
|
this.configService.getConfigurations()
|
|
.subscribe((configurations: Configuration) => {
|
|
this.onGoing = false;
|
|
// Add two password fields
|
|
configurations.email_password = new StringValueItem(fakePass, true);
|
|
this.config = configurations;
|
|
// Keep the original copy of the data
|
|
this.originalConfig = clone(configurations);
|
|
}, error => {
|
|
this.onGoing = false;
|
|
this.errorHandler.error(error);
|
|
});
|
|
}
|
|
|
|
reset(changes: any): void {
|
|
if (!isEmpty(changes)) {
|
|
for (let prop in changes) {
|
|
if (this.originalConfig[prop]) {
|
|
this.config[prop] = clone(this.originalConfig[prop]);
|
|
}
|
|
}
|
|
} else {
|
|
// force reset
|
|
this.retrieveConfig();
|
|
}
|
|
}
|
|
|
|
confirmCancel(ack: ConfirmationAcknowledgement): void {
|
|
if (ack && ack.source === ConfirmationTargets.CONFIG &&
|
|
ack.state === ConfirmationState.CONFIRMED) {
|
|
let changes = this.getChanges();
|
|
this.reset(changes);
|
|
this.initRobotToken();
|
|
if (!compareValue(this.systemAllowlistOrigin, this.systemAllowlist)) {
|
|
this.systemAllowlist = clone(this.systemAllowlistOrigin);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
public get inProgress(): boolean {
|
|
return this.onGoing;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* Discard current changes if have and reset
|
|
*
|
|
* @memberOf ConfigurationComponent
|
|
*/
|
|
public cancel(): void {
|
|
let changes = this.getChanges();
|
|
if (!isEmpty(changes) || !compareValue(this.systemAllowlistOrigin, this.systemAllowlist)) {
|
|
let msg = new ConfirmationMessage(
|
|
'CONFIG.CONFIRM_TITLE',
|
|
'CONFIG.CONFIRM_SUMMARY',
|
|
'',
|
|
{},
|
|
ConfirmationTargets.CONFIG
|
|
);
|
|
this.confirmationDlg.open(msg);
|
|
} else {
|
|
// Invalid situation, should not come here
|
|
console.error('Nothing changed');
|
|
}
|
|
}
|
|
|
|
constructor(@Inject(SERVICE_CONFIG) private configInfo: IServiceConfig,
|
|
private configService: ConfigurationService,
|
|
private errorHandler: ErrorHandler,
|
|
private systemInfoService: SystemInfoService) {
|
|
if (this.configInfo && this.configInfo.systemInfoEndpoint) {
|
|
this.downloadLink = this.configInfo.systemInfoEndpoint + "/getcert";
|
|
}
|
|
}
|
|
|
|
ngOnInit() {
|
|
this.initRobotToken();
|
|
this.getSystemAllowlist();
|
|
this.getSystemInfo();
|
|
}
|
|
|
|
getSystemInfo() {
|
|
this.systemInfoService.getSystemInfo()
|
|
.subscribe(systemInfo => this.systemInfo = systemInfo
|
|
, error => this.errorHandler.error(error));
|
|
}
|
|
getSystemAllowlist() {
|
|
this.onGoing = true;
|
|
this.systemInfoService.getSystemAllowlist()
|
|
.subscribe((systemAllowlist) => {
|
|
this.onGoing = false;
|
|
if (!systemAllowlist.items) {
|
|
systemAllowlist.items = [];
|
|
}
|
|
if (!systemAllowlist.expires_at) {
|
|
systemAllowlist.expires_at = null;
|
|
}
|
|
this.systemAllowlist = systemAllowlist;
|
|
this.systemAllowlistOrigin = clone(systemAllowlist);
|
|
}, error => {
|
|
this.onGoing = false;
|
|
console.error('An error occurred during getting systemAllowlist');
|
|
// this.errorHandler.error(error);
|
|
}
|
|
);
|
|
}
|
|
|
|
private initRobotToken(): void {
|
|
if (this.config &&
|
|
this.config.robot_token_duration) {
|
|
let robotExpiration = this.config.robot_token_duration.value;
|
|
this.robotTokenExpiration = Math.floor(robotExpiration / ONE_DAY_MINUTES) + '';
|
|
}
|
|
}
|
|
|
|
changeToken(v: string) {
|
|
if (!v || v === "") {
|
|
return;
|
|
}
|
|
if (!(this.config &&
|
|
this.config.robot_token_duration)) {
|
|
return;
|
|
}
|
|
this.config.robot_token_duration.value = +v * ONE_DAY_MINUTES;
|
|
}
|
|
|
|
deleteItem(index: number) {
|
|
this.systemAllowlist.items.splice(index, 1);
|
|
}
|
|
|
|
addToSystemAllowlist() {
|
|
// remove duplication and add to systemAllowlist
|
|
let map = {};
|
|
this.systemAllowlist.items.forEach(item => {
|
|
map[item.cve_id] = true;
|
|
});
|
|
this.cveIds.split(/[\n,]+/).forEach(id => {
|
|
let cveObj: any = {};
|
|
cveObj.cve_id = id.trim();
|
|
if (!map[cveObj.cve_id]) {
|
|
map[cveObj.cve_id] = true;
|
|
this.systemAllowlist.items.push(cveObj);
|
|
}
|
|
});
|
|
// clear modal and close modal
|
|
this.cveIds = null;
|
|
this.showAddModal = false;
|
|
}
|
|
|
|
get hasAllowlistChanged(): boolean {
|
|
return !compareValue(this.systemAllowlistOrigin, this.systemAllowlist);
|
|
}
|
|
|
|
isDisabled(): boolean {
|
|
let str = this.cveIds;
|
|
return !(str && str.trim());
|
|
}
|
|
|
|
get expiresDate() {
|
|
if (this.systemAllowlist && this.systemAllowlist.expires_at) {
|
|
return new Date(this.systemAllowlist.expires_at * ONE_THOUSAND);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
set expiresDate(date) {
|
|
if (this.systemAllowlist && date) {
|
|
this.systemAllowlist.expires_at = Math.floor(date.getTime() / ONE_THOUSAND);
|
|
}
|
|
}
|
|
|
|
get neverExpires(): boolean {
|
|
return !(this.systemAllowlist && this.systemAllowlist.expires_at);
|
|
}
|
|
|
|
set neverExpires(flag) {
|
|
if (flag) {
|
|
this.systemAllowlist.expires_at = null;
|
|
this.systemInfoService.resetDateInput(this.dateInput);
|
|
} else {
|
|
this.systemAllowlist.expires_at = Math.floor(new Date().getTime() / ONE_THOUSAND);
|
|
}
|
|
}
|
|
|
|
get hasExpired(): boolean {
|
|
if (this.systemAllowlistOrigin && this.systemAllowlistOrigin.expires_at) {
|
|
return new Date().getTime() > this.systemAllowlistOrigin.expires_at * ONE_THOUSAND;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
goToDetail(cveId) {
|
|
window.open(CVE_DETAIL_PRE_URL + `${cveId}`, TARGET_BLANK);
|
|
}
|
|
}
|