mirror of
https://github.com/goharbor/harbor.git
synced 2024-12-26 18:48:02 +01:00
Refactor config component (#16064)
Signed-off-by: AllForNothing <sshijun@vmware.com>
This commit is contained in:
parent
43912674b1
commit
846d690b85
@ -443,9 +443,9 @@
|
||||
</form>
|
||||
<div>
|
||||
<button type="button" id="config_auth_save" class="btn btn-primary" (click)="save()"
|
||||
[disabled]="!isValid() || !hasChanges()">{{'BUTTON.SAVE' | translate}}</button>
|
||||
[disabled]="!isValid() || !hasChanges() || inProcess()">{{'BUTTON.SAVE' | translate}}</button>
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()"
|
||||
[disabled]="!isValid() || !hasChanges()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
[disabled]="!isValid() || !hasChanges() || inProcess()">{{'BUTTON.CANCEL' | translate}}</button>
|
||||
<button type="button" id="ping-test" class="btn btn-outline" (click)="pingTestServer()" *ngIf="showTestingServerBtn"
|
||||
[disabled]="!isConfigValidForTesting()">{{(showLdap?'BUTTON.TEST_LDAP':'BUTTON.TEST_OIDC') | translate}}</button>
|
||||
<span id="forTestingLDAP" class="spinner spinner-inline" [hidden]="hideTestingSpinner"></span>
|
||||
|
@ -1,17 +1,16 @@
|
||||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MessageHandlerService } from '../../../../shared/services/message-handler.service';
|
||||
import { ConfirmMessageHandler } from '../config.msg.utils';
|
||||
import { AppConfigService } from '../../../../services/app-config.service';
|
||||
import { ConfigurationService } from '../../../../services/config.service';
|
||||
import { ConfigurationAuthComponent } from './config-auth.component';
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { of } from 'rxjs';
|
||||
import { ErrorHandler } from "../../../../shared/units/error-handler";
|
||||
import { SystemInfoService } from "../../../../shared/services";
|
||||
import { clone } from '../../../../shared/units/utils';
|
||||
import { CONFIG_AUTH_MODE } from '../../../../shared/entities/shared.const';
|
||||
import { ConfigService } from "../config.service";
|
||||
import { Configuration } from '../config';
|
||||
import { SharedTestingModule } from "../../../../shared/shared.module";
|
||||
|
||||
describe('ConfigurationAuthComponent', () => {
|
||||
let component: ConfigurationAuthComponent;
|
||||
@ -27,7 +26,25 @@ describe('ConfigurationAuthComponent', () => {
|
||||
let fakeAppConfigService = {
|
||||
load: () => of(null)
|
||||
};
|
||||
let fakeConfirmMessageService = null;
|
||||
const fakeConfigService = {
|
||||
config: new Configuration(),
|
||||
getConfig() {
|
||||
return this.config;
|
||||
},
|
||||
setConfig(c) {
|
||||
this.config = c;
|
||||
},
|
||||
getOriginalConfig() {
|
||||
return new Configuration();
|
||||
},
|
||||
getLoadingConfigStatus() {
|
||||
return false;
|
||||
},
|
||||
updateConfig() {
|
||||
},
|
||||
resetConfig() {
|
||||
}
|
||||
};
|
||||
let fakeSystemInfoService = {
|
||||
getSystemInfo: function () {
|
||||
return of({
|
||||
@ -39,17 +56,14 @@ describe('ConfigurationAuthComponent', () => {
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
FormsModule
|
||||
SharedTestingModule
|
||||
],
|
||||
declarations: [ConfigurationAuthComponent],
|
||||
providers: [
|
||||
ErrorHandler,
|
||||
TranslateService,
|
||||
{ provide: MessageHandlerService, useValue: fakeMessageHandlerService },
|
||||
{ provide: ConfigurationService, useValue: fakeConfigurationService },
|
||||
{ provide: AppConfigService, useValue: fakeAppConfigService },
|
||||
{ provide: ConfirmMessageHandler, useValue: fakeConfirmMessageService },
|
||||
{ provide: ConfigService, useValue: fakeConfigService },
|
||||
{ provide: SystemInfoService, useValue: fakeSystemInfoService }
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
@ -59,46 +73,10 @@ describe('ConfigurationAuthComponent', () => {
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ConfigurationAuthComponent);
|
||||
component = fixture.componentInstance;
|
||||
(component as any).originalConfig = clone(component.currentConfig);
|
||||
fixture.autoDetectChanges();
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should save configuration', async () => {
|
||||
const selfRegInput: HTMLInputElement = fixture.nativeElement.querySelector("#selfReg");
|
||||
selfRegInput.dispatchEvent(new Event('click'));
|
||||
component.currentConfig.self_registration.value = true;
|
||||
await fixture.whenStable();
|
||||
const configAuthSaveBtn: HTMLButtonElement = fixture.nativeElement.querySelector("#config_auth_save");
|
||||
component.onGoing = true;
|
||||
configAuthSaveBtn.dispatchEvent(new Event('click'));
|
||||
await fixture.whenStable();
|
||||
expect(component.onGoing).toBeFalsy();
|
||||
});
|
||||
it('should select ldap or uaa', () => {
|
||||
component.handleOnChange({target: {value: 'ldap_auth'}});
|
||||
expect(component.currentConfig.self_registration.value).toEqual(false);
|
||||
});
|
||||
it('should ping test server when ldap', async () => {
|
||||
component.currentConfig.auth_mode.value = CONFIG_AUTH_MODE.LDAP_AUTH;
|
||||
component.currentConfig.ldap_scope.value = 123456;
|
||||
await fixture.whenStable();
|
||||
const pingTestBtn = fixture.nativeElement.querySelector("#ping-test");
|
||||
expect(pingTestBtn).toBeTruthy();
|
||||
pingTestBtn.dispatchEvent(new Event('click'));
|
||||
await fixture.whenStable();
|
||||
expect(component.testingOnGoing).toBeFalsy();
|
||||
});
|
||||
it('should ping test server when oidc', async () => {
|
||||
component.currentConfig.auth_mode.value = CONFIG_AUTH_MODE.OIDC_AUTH;
|
||||
await fixture.whenStable();
|
||||
const pingTestBtn = fixture.nativeElement.querySelector("#ping-test");
|
||||
expect(pingTestBtn).toBeTruthy();
|
||||
pingTestBtn.dispatchEvent(new Event('click'));
|
||||
await fixture.whenStable();
|
||||
expect(component.testingOnGoing).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
@ -11,68 +11,60 @@
|
||||
// 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, Input, ViewChild, SimpleChanges, OnChanges, OnInit, Output, EventEmitter } from '@angular/core';
|
||||
import { Component, ViewChild, OnInit } from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { Subscription } from "rxjs";
|
||||
import { MessageHandlerService } from '../../../../shared/services/message-handler.service';
|
||||
import { ConfirmMessageHandler } from '../config.msg.utils';
|
||||
import { AppConfigService } from '../../../../services/app-config.service';
|
||||
import { ConfigurationService } from '../../../../services/config.service';
|
||||
import { ErrorHandler } from "../../../../shared/units/error-handler";
|
||||
import { SystemInfoService } from "../../../../shared/services";
|
||||
import { clone, isEmpty, getChanges as getChangesFunc } from "../../../../shared/units/utils";
|
||||
import { isEmpty, getChanges as getChangesFunc } from "../../../../shared/units/utils";
|
||||
import { CONFIG_AUTH_MODE } from "../../../../shared/entities/shared.const";
|
||||
import { errorHandler } from "../../../../shared/units/shared.utils";
|
||||
import { Configuration } from "../config";
|
||||
import { finalize } from "rxjs/operators";
|
||||
const fakePass = 'aWpLOSYkIzJTTU4wMDkx';
|
||||
import { ConfigService } from "../config.service";
|
||||
|
||||
@Component({
|
||||
selector: 'config-auth',
|
||||
templateUrl: 'config-auth.component.html',
|
||||
styleUrls: ['./config-auth.component.scss', '../config.component.scss']
|
||||
})
|
||||
export class ConfigurationAuthComponent implements OnChanges, OnInit {
|
||||
changeSub: Subscription;
|
||||
export class ConfigurationAuthComponent implements OnInit {
|
||||
testingOnGoing = false;
|
||||
onGoing = false;
|
||||
redirectUrl: string;
|
||||
// tslint:disable-next-line:no-input-rename
|
||||
@Input('allConfig') currentConfig: Configuration = new Configuration();
|
||||
private originalConfig: Configuration;
|
||||
@ViewChild('authConfigFrom') authForm: NgForm;
|
||||
@Output() refreshAllconfig = new EventEmitter<any>();
|
||||
|
||||
get currentConfig(): Configuration {
|
||||
return this.conf.getConfig();
|
||||
}
|
||||
|
||||
set currentConfig(c: Configuration) {
|
||||
this.conf.setConfig(c);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private msgHandler: MessageHandlerService,
|
||||
private configService: ConfigurationService,
|
||||
private appConfigService: AppConfigService,
|
||||
private confirmMessageHandler: ConfirmMessageHandler,
|
||||
private conf: ConfigService,
|
||||
private systemInfo: SystemInfoService,
|
||||
private errorHandlerEntity: ErrorHandler,
|
||||
) {
|
||||
}
|
||||
ngOnInit() {
|
||||
this.conf.resetConfig();
|
||||
this.getSystemInfo();
|
||||
}
|
||||
getSystemInfo(): void {
|
||||
this.systemInfo.getSystemInfo()
|
||||
.subscribe(systemInfo => (this.redirectUrl = systemInfo.external_url)
|
||||
, error => this.errorHandlerEntity.error(error));
|
||||
, error => this.msgHandler.error(error));
|
||||
}
|
||||
get checkable() {
|
||||
return this.currentConfig &&
|
||||
this.currentConfig.self_registration &&
|
||||
this.currentConfig.self_registration.value === true;
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes && changes["currentConfig"]) {
|
||||
this.originalConfig = clone(this.currentConfig);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public get showLdap(): boolean {
|
||||
return this.currentConfig &&
|
||||
this.currentConfig.auth_mode &&
|
||||
@ -99,11 +91,15 @@ export class ConfigurationAuthComponent implements OnChanges, OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
public isValid(): boolean {
|
||||
isValid(): boolean {
|
||||
return this.authForm && this.authForm.valid;
|
||||
}
|
||||
|
||||
public hasChanges(): boolean {
|
||||
inProcess(): boolean {
|
||||
return this.onGoing || this.conf.getLoadingConfigStatus();
|
||||
}
|
||||
|
||||
hasChanges(): boolean {
|
||||
return !isEmpty(this.getChanges());
|
||||
}
|
||||
|
||||
@ -168,7 +164,7 @@ export class ConfigurationAuthComponent implements OnChanges, OnInit {
|
||||
this.msgHandler.showSuccess('CONFIG.TEST_OIDC_SUCCESS');
|
||||
}, error => {
|
||||
this.testingOnGoing = false;
|
||||
this.errorHandlerEntity.error(error);
|
||||
this.msgHandler.error(error);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -180,12 +176,15 @@ export class ConfigurationAuthComponent implements OnChanges, OnInit {
|
||||
}
|
||||
|
||||
public isConfigValidForTesting(): boolean {
|
||||
if (!this.authForm || !this.currentConfig) {
|
||||
return true;
|
||||
}
|
||||
return this.isValid() &&
|
||||
!this.testingOnGoing;
|
||||
!this.testingOnGoing && !this.inProcess();
|
||||
}
|
||||
|
||||
public getChanges() {
|
||||
let allChanges = getChangesFunc(this.originalConfig, this.currentConfig);
|
||||
let allChanges = getChangesFunc(this.conf.getOriginalConfig(), this.currentConfig);
|
||||
let changes = {};
|
||||
for (let prop in allChanges) {
|
||||
if (prop.startsWith('ldap_')
|
||||
@ -235,7 +234,7 @@ export class ConfigurationAuthComponent implements OnChanges, OnInit {
|
||||
this.configService.saveConfiguration(changes)
|
||||
.subscribe(response => {
|
||||
this.onGoing = false;
|
||||
this.refreshAllconfig.emit();
|
||||
this.conf.updateConfig();
|
||||
// Reload bootstrap option
|
||||
this.appConfigService.load().subscribe(() => { }
|
||||
, error => console.error('Failed to reload bootstrap option with error: ', error));
|
||||
@ -259,7 +258,7 @@ export class ConfigurationAuthComponent implements OnChanges, OnInit {
|
||||
public cancel(): void {
|
||||
let changes = this.getChanges();
|
||||
if (!isEmpty(changes)) {
|
||||
this.confirmMessageHandler.confirmUnsavedChanges(changes);
|
||||
this.conf.confirmUnsavedChanges(changes);
|
||||
} else {
|
||||
// Invalid situation, should not come here
|
||||
console.error('Nothing changed');
|
||||
|
@ -1,25 +1,23 @@
|
||||
<div class="row">
|
||||
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
|
||||
<h2 class="custom-h2 config-title">{{'CONFIG.TITLE' | translate }}<span class="spinner spinner-inline ml-1 v-mid" [hidden]="inProgress === false"></span></h2>
|
||||
<clr-tabs>
|
||||
<clr-tab>
|
||||
<button id="config-auth" clrTabLink>{{'CONFIG.AUTH' | translate}}</button>
|
||||
<clr-tab-content id="authentication" *clrIfActive>
|
||||
<config-auth [allConfig]="allConfig" (refreshAllconfig)="refreshAllconfig()"></config-auth>
|
||||
</clr-tab-content>
|
||||
</clr-tab>
|
||||
<clr-tab>
|
||||
<button id="config-email" clrTabLink>{{'CONFIG.EMAIL' | translate }}</button>
|
||||
<clr-tab-content id="email" *clrIfActive>
|
||||
<config-email [mailConfig]="allConfig" (refreshAllconfig)="refreshAllconfig()"></config-email>
|
||||
</clr-tab-content>
|
||||
</clr-tab>
|
||||
<clr-tab>
|
||||
<button id="config-system" clrTabLink>{{'CONFIG.SYSTEM' | translate }}</button>
|
||||
<clr-tab-content id="system_settings" *clrIfActive>
|
||||
<system-settings [(systemSettings)]="allConfig" [hasAdminRole]="hasAdminRole" (reloadSystemConfig)="handleAppConfig($event)" (readOnlyChange)="handleReadyOnlyChange($event)" [hasCAFile]="hasCAFile"></system-settings>
|
||||
</clr-tab-content>
|
||||
</clr-tab>
|
||||
</clr-tabs>
|
||||
<ul class="nav" role="tablist">
|
||||
<li role="presentation" class="nav-item" >
|
||||
<button id="config-auth" class="btn btn-link nav-link" type="button"
|
||||
routerLink="auth"
|
||||
routerLinkActive="active">{{'CONFIG.AUTH' | translate}}</button>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item" >
|
||||
<button id="config-email" class="btn btn-link nav-link" type="button"
|
||||
routerLink="email"
|
||||
routerLinkActive="active">{{'CONFIG.EMAIL' | translate}}</button>
|
||||
</li>
|
||||
<li role="presentation" class="nav-item" >
|
||||
<button id="config-system" class="btn btn-link nav-link" type="button"
|
||||
routerLink="setting"
|
||||
routerLinkActive="active">{{'CONFIG.SYSTEM' | translate}}</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<router-outlet></router-outlet>
|
||||
|
@ -1,85 +1,44 @@
|
||||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { SessionService } from '../../../shared/services/session.service';
|
||||
import { MessageHandlerService } from '../../../shared/services/message-handler.service';
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { ClarityModule } from "@clr/angular";
|
||||
import { AppConfigService } from '../../../services/app-config.service';
|
||||
import { ConfigurationService } from '../../../services/config.service';
|
||||
import { ConfigurationComponent } from './config.component';
|
||||
import { of } from 'rxjs';
|
||||
import { Configuration } from './config';
|
||||
import { ConfirmationState, ConfirmationTargets } from "../../../shared/entities/shared.const";
|
||||
import { ConfirmationDialogService } from "../../global-confirmation-dialog/confirmation-dialog.service";
|
||||
import { ConfirmationAcknowledgement } from "../../global-confirmation-dialog/confirmation-state-message";
|
||||
import { SharedTestingModule } from "../../../shared/shared.module";
|
||||
import { ConfigService } from "./config.service";
|
||||
import { Configuration } from "./config";
|
||||
|
||||
describe('ConfigurationComponent', () => {
|
||||
let component: ConfigurationComponent;
|
||||
let fixture: ComponentFixture<ConfigurationComponent>;
|
||||
let confirmationConfirmFlag = true;
|
||||
let confirmationConfirm = () => {
|
||||
return confirmationConfirmFlag ? of(new ConfirmationAcknowledgement(ConfirmationState.CONFIRMED, {}, ConfirmationTargets.CONFIG))
|
||||
: of(new ConfirmationAcknowledgement(ConfirmationState.CONFIRMED
|
||||
, {change: { email_password: 'Harbor12345' }, tabId: '1'}, ConfirmationTargets.CONFIG_TAB));
|
||||
};
|
||||
let fakeConfirmationDialogService = {
|
||||
confirmationConfirm$: confirmationConfirm()
|
||||
};
|
||||
let mockConfigurationService = {
|
||||
getConfiguration: () => of(new Configuration()),
|
||||
confirmationConfirm$: of(new ConfirmationAcknowledgement(ConfirmationState.CONFIRMED, {}, ConfirmationTargets.CONFIG))
|
||||
};
|
||||
let fakeSessionService = {
|
||||
getCurrentUser: function () {
|
||||
return {
|
||||
has_admin_role: true,
|
||||
user_id: 1,
|
||||
username: 'admin',
|
||||
email: "",
|
||||
realname: "admin",
|
||||
role_name: "admin",
|
||||
role_id: 1,
|
||||
comment: "string",
|
||||
};
|
||||
const fakeConfigService = {
|
||||
getConfig() {
|
||||
return new Configuration();
|
||||
},
|
||||
updateAccountSettings: () => of(null),
|
||||
renameAdmin: () => of(null),
|
||||
getOriginalConfig() {
|
||||
return new Configuration();
|
||||
},
|
||||
getLoadingConfigStatus() {
|
||||
return false;
|
||||
},
|
||||
updateConfig() {
|
||||
},
|
||||
initConfig() {
|
||||
}
|
||||
};
|
||||
let initSpy: jasmine.Spy;
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
ClarityModule
|
||||
SharedTestingModule
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
declarations: [ConfigurationComponent],
|
||||
providers: [
|
||||
TranslateService,
|
||||
{
|
||||
provide: SessionService, useValue: {
|
||||
getCurrentUser: function () {
|
||||
return "admin";
|
||||
}
|
||||
}
|
||||
},
|
||||
{ provide: ConfirmationDialogService, useValue: fakeConfirmationDialogService },
|
||||
{ provide: SessionService, useValue: fakeSessionService },
|
||||
{ provide: MessageHandlerService, useValue: null },
|
||||
{
|
||||
provide: AppConfigService, useValue: {
|
||||
getConfig: function () {
|
||||
return { has_ca_root: true };
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
provide: ConfigurationService, useValue: mockConfigurationService
|
||||
}
|
||||
{ provide: ConfigService, useValue: fakeConfigService },
|
||||
]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
initSpy = spyOn(fakeConfigService, "initConfig").and.returnValue(undefined);
|
||||
fixture = TestBed.createComponent(ConfigurationComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
@ -88,16 +47,8 @@ describe('ConfigurationComponent', () => {
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('should reset part of allConfig ', async () => {
|
||||
confirmationConfirmFlag = false;
|
||||
component.originalCopy.email_password.value = 'Harbor12345';
|
||||
component.reset({
|
||||
email_password: {
|
||||
value: 'Harbor12345',
|
||||
editable: true
|
||||
}
|
||||
});
|
||||
it('should init config', async () => {
|
||||
await fixture.whenStable();
|
||||
expect(component.allConfig.email_password.value).toEqual('Harbor12345');
|
||||
expect(initSpy.calls.count()).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
@ -11,155 +11,25 @@
|
||||
// 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, OnInit, OnDestroy, ViewChild } from '@angular/core';
|
||||
import { Subscription } from "rxjs";
|
||||
import { SessionService } from '../../../shared/services/session.service';
|
||||
import { MessageHandlerService } from '../../../shared/services/message-handler.service';
|
||||
import { AppConfigService } from '../../../services/app-config.service';
|
||||
import { ConfigurationAuthComponent } from './auth/config-auth.component';
|
||||
import { ConfigurationEmailComponent } from './email/config-email.component';
|
||||
import { ConfigurationService } from '../../../services/config.service';
|
||||
import { Configuration, StringValueItem } from "./config";
|
||||
import { SystemSettingsComponent } from "./system/system-settings.component";
|
||||
import { clone, isEmpty } from "../../../shared/units/utils";
|
||||
import { ConfirmationDialogService } from "../../global-confirmation-dialog/confirmation-dialog.service";
|
||||
import { ConfirmationState, ConfirmationTargets } from "../../../shared/entities/shared.const";
|
||||
|
||||
const fakePass = 'aWpLOSYkIzJTTU4wMDkx';
|
||||
const TabLinkContentMap = {
|
||||
'config-auth': 'authentication',
|
||||
'config-replication': 'replication',
|
||||
'config-email': 'email',
|
||||
'config-system': 'system_settings',
|
||||
'config-label': 'system_label',
|
||||
};
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { ConfigService } from "./config.service";
|
||||
|
||||
@Component({
|
||||
selector: 'config',
|
||||
templateUrl: 'config.component.html',
|
||||
styleUrls: ['config.component.scss']
|
||||
})
|
||||
export class ConfigurationComponent implements OnInit, OnDestroy {
|
||||
allConfig: Configuration = new Configuration();
|
||||
onGoing = false;
|
||||
currentTabId = 'config-auth'; // default tab
|
||||
originalCopy: Configuration = new Configuration();
|
||||
confirmSub: Subscription;
|
||||
|
||||
@ViewChild(SystemSettingsComponent) systemSettingsConfig: SystemSettingsComponent;
|
||||
@ViewChild(ConfigurationEmailComponent) mailConfig: ConfigurationEmailComponent;
|
||||
@ViewChild(ConfigurationAuthComponent) authConfig: ConfigurationAuthComponent;
|
||||
|
||||
constructor(
|
||||
private msgHandler: MessageHandlerService,
|
||||
private configService: ConfigurationService,
|
||||
private confirmService: ConfirmationDialogService,
|
||||
private appConfigService: AppConfigService,
|
||||
private session: SessionService) { }
|
||||
|
||||
public get hasAdminRole(): boolean {
|
||||
return this.session.getCurrentUser() &&
|
||||
this.session.getCurrentUser().has_admin_role;
|
||||
export class ConfigurationComponent implements OnInit {
|
||||
get inProgress(): boolean {
|
||||
return this.conf.getLoadingConfigStatus();
|
||||
}
|
||||
|
||||
public get hasCAFile(): boolean {
|
||||
return this.appConfigService.getConfig().has_ca_root;
|
||||
constructor(private conf: ConfigService) {
|
||||
}
|
||||
|
||||
public get withAdmiral(): boolean {
|
||||
return this.appConfigService.getConfig().with_admiral;
|
||||
}
|
||||
|
||||
refreshAllconfig() {
|
||||
this.retrieveConfig();
|
||||
}
|
||||
ngOnInit(): void {
|
||||
// First load
|
||||
// Double confirm the current use has admin role
|
||||
let currentUser = this.session.getCurrentUser();
|
||||
if (currentUser && currentUser.has_admin_role) {
|
||||
this.retrieveConfig();
|
||||
}
|
||||
|
||||
this.confirmSub = this.confirmService.confirmationConfirm$.subscribe(confirmation => {
|
||||
if (confirmation &&
|
||||
confirmation.state === ConfirmationState.CONFIRMED) {
|
||||
if (confirmation.source === ConfirmationTargets.CONFIG) {
|
||||
this.reset(confirmation.data);
|
||||
} else if (confirmation.source === ConfirmationTargets.CONFIG_TAB) {
|
||||
this.reset(confirmation.data['changes']);
|
||||
this.currentTabId = confirmation.data['tabId'];
|
||||
}
|
||||
}
|
||||
});
|
||||
this.conf.initConfig();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.confirmSub) {
|
||||
this.confirmSub.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
public get inProgress(): boolean {
|
||||
return this.onGoing;
|
||||
}
|
||||
|
||||
handleReadyOnlyChange(event) {
|
||||
this.msgHandler.handleReadOnly();
|
||||
if (!event) {
|
||||
this.msgHandler.clear();
|
||||
}
|
||||
}
|
||||
|
||||
handleAppConfig(event) {
|
||||
// Reload bootstrap option
|
||||
this.appConfigService.load().subscribe(() => {}
|
||||
, error => console.error('Failed to reload bootstrap option with error: ', error));
|
||||
}
|
||||
|
||||
retrieveConfig(): void {
|
||||
this.onGoing = true;
|
||||
this.configService.getConfiguration()
|
||||
.subscribe((configurations: Configuration) => {
|
||||
this.onGoing = false;
|
||||
|
||||
// Add two password fields
|
||||
configurations.email_password = new StringValueItem(fakePass, true);
|
||||
configurations.ldap_search_password = new StringValueItem(fakePass, true);
|
||||
configurations.uaa_client_secret = new StringValueItem(fakePass, true);
|
||||
configurations.oidc_client_secret = new StringValueItem(fakePass, true);
|
||||
this.allConfig = configurations;
|
||||
// Keep the original copy of the data
|
||||
this.originalCopy = clone(configurations);
|
||||
}, error => {
|
||||
this.onGoing = false;
|
||||
this.msgHandler.handleError(error);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Reset the configuration form
|
||||
*
|
||||
* @private
|
||||
* ** deprecated param {*} changes
|
||||
*
|
||||
* @memberOf ConfigurationComponent
|
||||
*/
|
||||
reset(changes: any): void {
|
||||
if (!isEmpty(changes)) {
|
||||
for (let prop in changes) {
|
||||
if (this.originalCopy[prop]) {
|
||||
this.allConfig[prop] = clone(this.originalCopy[prop]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// force reset
|
||||
this.retrieveConfig();
|
||||
}
|
||||
}
|
||||
|
||||
disabled(prop: any): boolean {
|
||||
return !(prop && prop.editable);
|
||||
}
|
||||
}
|
||||
|
@ -15,16 +15,35 @@
|
||||
import { NgModule } from "@angular/core";
|
||||
import { SharedModule } from "../../../shared/shared.module";
|
||||
import { ConfigurationComponent } from "./config.component";
|
||||
import { ConfirmMessageHandler } from "./config.msg.utils";
|
||||
import { ConfigurationAuthComponent } from "./auth/config-auth.component";
|
||||
import { ConfigurationEmailComponent } from "./email/config-email.component";
|
||||
import { SystemSettingsComponent } from "./system/system-settings.component";
|
||||
import { RouterModule, Routes } from "@angular/router";
|
||||
import { ConfigService } from "./config.service";
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: ConfigurationComponent
|
||||
component: ConfigurationComponent,
|
||||
children: [
|
||||
{
|
||||
path: 'auth',
|
||||
component: ConfigurationAuthComponent
|
||||
},
|
||||
{
|
||||
path: 'email',
|
||||
component: ConfigurationEmailComponent
|
||||
},
|
||||
{
|
||||
path: 'setting',
|
||||
component: SystemSettingsComponent
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'auth',
|
||||
pathMatch: 'full'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
@NgModule({
|
||||
@ -39,7 +58,7 @@ const routes: Routes = [
|
||||
SystemSettingsComponent
|
||||
],
|
||||
providers: [
|
||||
ConfirmMessageHandler,
|
||||
ConfigService,
|
||||
]
|
||||
})
|
||||
export class ConfigurationModule {
|
||||
|
@ -1,22 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ConfirmationDialogService } from "../../global-confirmation-dialog/confirmation-dialog.service";
|
||||
import { ConfirmationTargets } from "../../../shared/entities/shared.const";
|
||||
import { ConfirmationMessage } from "../../global-confirmation-dialog/confirmation-message";
|
||||
|
||||
@Injectable()
|
||||
export class ConfirmMessageHandler {
|
||||
constructor(private confirmService: ConfirmationDialogService) {
|
||||
}
|
||||
|
||||
public confirmUnsavedChanges(changes: any) {
|
||||
let msg = new ConfirmationMessage(
|
||||
'CONFIG.CONFIRM_TITLE',
|
||||
'CONFIG.CONFIRM_SUMMARY',
|
||||
'',
|
||||
changes,
|
||||
ConfirmationTargets.CONFIG
|
||||
);
|
||||
this.confirmService.openComfirmDialog(msg);
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,41 @@
|
||||
import { TestBed, inject } from '@angular/core/testing';
|
||||
import { ConfigureService } from 'ng-swagger-gen/services/configure.service';
|
||||
import { of } from 'rxjs';
|
||||
import { SharedTestingModule } from "../../../shared/shared.module";
|
||||
import { Configuration } from './config';
|
||||
import { ConfigService } from "./config.service";
|
||||
|
||||
describe('ConfigService', () => {
|
||||
const fakedConfigureService = {
|
||||
getConfigurations() {
|
||||
return of(null);
|
||||
}
|
||||
};
|
||||
let getConfigSpy: jasmine.Spy;
|
||||
beforeEach(() => {
|
||||
getConfigSpy = spyOn(fakedConfigureService, 'getConfigurations').and.returnValue(of(new Configuration()));
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
SharedTestingModule
|
||||
],
|
||||
providers: [
|
||||
ConfigService,
|
||||
{ provide: ConfigureService, useValue: fakedConfigureService },
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created', inject([ConfigService], (service: ConfigService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
|
||||
it('should init config', inject([ConfigService], (service: ConfigService) => {
|
||||
expect(getConfigSpy.calls.count()).toEqual(0);
|
||||
service.initConfig();
|
||||
expect(getConfigSpy.calls.count()).toEqual(1);
|
||||
// only init once
|
||||
service.initConfig();
|
||||
expect(getConfigSpy.calls.count()).toEqual(1);
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
});
|
102
src/portal/src/app/base/left-side-nav/config/config.service.ts
Normal file
102
src/portal/src/app/base/left-side-nav/config/config.service.ts
Normal file
@ -0,0 +1,102 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ConfirmationDialogService } from "../../global-confirmation-dialog/confirmation-dialog.service";
|
||||
import { ConfirmationState, ConfirmationTargets } from "../../../shared/entities/shared.const";
|
||||
import { ConfirmationMessage } from "../../global-confirmation-dialog/confirmation-message";
|
||||
import { Configuration, StringValueItem } from "./config";
|
||||
import { ConfigureService } from 'ng-swagger-gen/services/configure.service';
|
||||
import { clone } from "../../../shared/units/utils";
|
||||
import { MessageHandlerService } from "../../../shared/services/message-handler.service";
|
||||
import { finalize } from "rxjs/operators";
|
||||
import { Subscription } from "rxjs";
|
||||
|
||||
const fakePass = 'aWpLOSYkIzJTTU4wMDkx';
|
||||
|
||||
@Injectable()
|
||||
export class ConfigService {
|
||||
private _loadingConfig: boolean = false;
|
||||
private _hasInit: boolean = false;
|
||||
private _confirmSub: Subscription;
|
||||
private _currentConfig: Configuration = new Configuration();
|
||||
private _originalConfig: Configuration;
|
||||
|
||||
constructor(private confirmService: ConfirmationDialogService,
|
||||
private configureService: ConfigureService,
|
||||
private msgHandler: MessageHandlerService) {
|
||||
this._confirmSub = this.confirmService.confirmationConfirm$.subscribe(confirmation => {
|
||||
if (confirmation &&
|
||||
confirmation.state === ConfirmationState.CONFIRMED) {
|
||||
this.resetConfig();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getConfig(): Configuration {
|
||||
return this._currentConfig;
|
||||
}
|
||||
|
||||
setConfig(c: Configuration) {
|
||||
this._currentConfig = c;
|
||||
}
|
||||
|
||||
|
||||
getOriginalConfig(): Configuration {
|
||||
return this._originalConfig;
|
||||
}
|
||||
|
||||
setOriginalConfig(c: Configuration) {
|
||||
this._originalConfig = c;
|
||||
}
|
||||
|
||||
getLoadingConfigStatus(): boolean {
|
||||
return this._loadingConfig;
|
||||
}
|
||||
|
||||
initConfig() {
|
||||
if (!this._hasInit) {
|
||||
this.updateConfig();
|
||||
this._hasInit = true;
|
||||
}
|
||||
}
|
||||
|
||||
updateConfig() {
|
||||
this._loadingConfig = true;
|
||||
this.configureService.getConfigurations()
|
||||
.pipe(finalize(() => this._loadingConfig = false))
|
||||
.subscribe(res => {
|
||||
this._currentConfig = res as Configuration;
|
||||
// Add password fields
|
||||
this._currentConfig.email_password = new StringValueItem(fakePass, true);
|
||||
this._currentConfig.ldap_search_password = new StringValueItem(fakePass, true);
|
||||
this._currentConfig.uaa_client_secret = new StringValueItem(fakePass, true);
|
||||
this._currentConfig.oidc_client_secret = new StringValueItem(fakePass, true);
|
||||
// Keep the original copy of the data
|
||||
this._originalConfig = clone(this._currentConfig);
|
||||
// Handle read only
|
||||
if (this._originalConfig?.read_only?.value) {
|
||||
this.msgHandler.handleReadOnly();
|
||||
} else {
|
||||
this.msgHandler.clear();
|
||||
}
|
||||
}, error => {
|
||||
this.msgHandler.handleError(error);
|
||||
});
|
||||
}
|
||||
|
||||
resetConfig() {
|
||||
if (this._originalConfig) {
|
||||
this._currentConfig = clone(this._originalConfig);
|
||||
}
|
||||
}
|
||||
|
||||
confirmUnsavedChanges(changes: any) {
|
||||
let msg = new ConfirmationMessage(
|
||||
'CONFIG.CONFIRM_TITLE',
|
||||
'CONFIG.CONFIRM_SUMMARY',
|
||||
'',
|
||||
changes,
|
||||
ConfirmationTargets.CONFIG
|
||||
);
|
||||
this.confirmService.openComfirmDialog(msg);
|
||||
}
|
||||
}
|
||||
|
@ -76,9 +76,9 @@
|
||||
</form>
|
||||
<div>
|
||||
<button type="button" id="config_email_save" class="btn btn-primary" (click)="save()"
|
||||
[disabled]="!isValid() || !hasChanges()">{{'BUTTON.SAVE'
|
||||
[disabled]="!isValid() || !hasChanges() || inProgress()">{{'BUTTON.SAVE'
|
||||
| translate}}</button>
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()" [disabled]="!isValid() || !hasChanges()">{{'BUTTON.CANCEL'
|
||||
<button type="button" class="btn btn-outline" (click)="cancel()" [disabled]="!isValid() || !hasChanges() || inProgress()">{{'BUTTON.CANCEL'
|
||||
| translate}}</button>
|
||||
<button type="button" id="ping-test" class="btn btn-outline" (click)="testMailServer()" [disabled]="!isMailConfigValid()">{{'BUTTON.TEST_MAIL'
|
||||
| translate}}</button>
|
||||
|
@ -1,13 +1,13 @@
|
||||
import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { MessageHandlerService } from '../../../../shared/services/message-handler.service';
|
||||
import { ConfirmMessageHandler } from '../config.msg.utils';
|
||||
import { ConfigurationService } from '../../../../services/config.service';
|
||||
import { ConfigurationEmailComponent } from './config-email.component';
|
||||
import { TranslateModule, TranslateService } from '@ngx-translate/core';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { clone } from '../../../../shared/units/utils';
|
||||
import { of } from 'rxjs';
|
||||
import { ConfigService } from "../config.service";
|
||||
import { SharedTestingModule } from "../../../../shared/shared.module";
|
||||
import { Configuration } from "../config";
|
||||
|
||||
describe('ConfigurationEmailComponent', () => {
|
||||
let component: ConfigurationEmailComponent;
|
||||
@ -19,17 +19,34 @@ describe('ConfigurationEmailComponent', () => {
|
||||
let fakeMessageHandlerService = {
|
||||
showSuccess: () => null
|
||||
};
|
||||
const fakeConfigService = {
|
||||
config: new Configuration(),
|
||||
getConfig() {
|
||||
return this.config;
|
||||
},
|
||||
setConfig(c) {
|
||||
this.config = c;
|
||||
},
|
||||
getOriginalConfig() {
|
||||
return new Configuration();
|
||||
},
|
||||
getLoadingConfigStatus() {
|
||||
return false;
|
||||
},
|
||||
updateConfig() {
|
||||
},
|
||||
resetConfig() {
|
||||
}
|
||||
};
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
TranslateModule.forRoot(),
|
||||
FormsModule
|
||||
SharedTestingModule
|
||||
],
|
||||
declarations: [ConfigurationEmailComponent],
|
||||
providers: [
|
||||
{ provide: MessageHandlerService, useValue: fakeMessageHandlerService },
|
||||
TranslateService,
|
||||
{ provide: ConfirmMessageHandler, useValue: null },
|
||||
{ provide: ConfigService, useValue: fakeConfigService },
|
||||
{ provide: ConfigurationService, useValue: fakeConfigurationService }
|
||||
],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
@ -39,7 +56,6 @@ describe('ConfigurationEmailComponent', () => {
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ConfigurationEmailComponent);
|
||||
component = fixture.componentInstance;
|
||||
(component as any).originalConfig = clone(component.currentConfig);
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
|
@ -11,33 +11,40 @@
|
||||
// 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, Input, ViewChild, SimpleChanges, OnChanges, Output, EventEmitter } from '@angular/core';
|
||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { MessageHandlerService } from '../../../../shared/services/message-handler.service';
|
||||
import { ConfirmMessageHandler } from '../config.msg.utils';
|
||||
import { ConfigurationService } from '../../../../services/config.service';
|
||||
import { Configuration } from "../config";
|
||||
import { isEmpty, getChanges as getChangesFunc, clone } from "../../../../shared/units/utils";
|
||||
import { isEmpty, getChanges as getChangesFunc } from "../../../../shared/units/utils";
|
||||
import { errorHandler } from "../../../../shared/units/shared.utils";
|
||||
import { ConfigService } from "../config.service";
|
||||
|
||||
@Component({
|
||||
selector: 'config-email',
|
||||
templateUrl: "config-email.component.html",
|
||||
styleUrls: ['./config-email.component.scss', '../config.component.scss']
|
||||
})
|
||||
export class ConfigurationEmailComponent implements OnChanges {
|
||||
// tslint:disable-next-line:no-input-rename
|
||||
@Input("mailConfig") currentConfig: Configuration = new Configuration();
|
||||
@Output() refreshAllconfig = new EventEmitter<any>();
|
||||
private originalConfig: Configuration;
|
||||
export class ConfigurationEmailComponent implements OnInit {
|
||||
testingMailOnGoing = false;
|
||||
onGoing = false;
|
||||
@ViewChild("mailConfigFrom", {static: true}) mailForm: NgForm;
|
||||
get currentConfig(): Configuration {
|
||||
return this.conf.getConfig();
|
||||
}
|
||||
|
||||
set currentConfig(c: Configuration) {
|
||||
this.conf.setConfig(c);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private msgHandler: MessageHandlerService,
|
||||
private configService: ConfigurationService,
|
||||
private confirmMessageHandler: ConfirmMessageHandler) {
|
||||
private conf: ConfigService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.conf.resetConfig();
|
||||
}
|
||||
|
||||
disabled(prop: any): boolean {
|
||||
@ -48,16 +55,20 @@ export class ConfigurationEmailComponent implements OnChanges {
|
||||
this.currentConfig.email_insecure.value = !$event;
|
||||
}
|
||||
|
||||
public isValid(): boolean {
|
||||
isValid(): boolean {
|
||||
return this.mailForm && this.mailForm.valid;
|
||||
}
|
||||
|
||||
inProgress(): boolean {
|
||||
return this.onGoing || this.conf.getLoadingConfigStatus();
|
||||
}
|
||||
|
||||
public hasChanges(): boolean {
|
||||
return !isEmpty(this.getChanges());
|
||||
}
|
||||
|
||||
public getChanges() {
|
||||
let allChanges = getChangesFunc(this.originalConfig, this.currentConfig);
|
||||
let allChanges = getChangesFunc(this.conf.getOriginalConfig(), this.currentConfig);
|
||||
let changes = {};
|
||||
for (let prop in allChanges) {
|
||||
if (prop.startsWith('email_')) {
|
||||
@ -66,13 +77,6 @@ export class ConfigurationEmailComponent implements OnChanges {
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
if (changes && changes["currentConfig"]) {
|
||||
this.originalConfig = clone(this.currentConfig);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Test the connection of specified mail server
|
||||
@ -121,7 +125,7 @@ export class ConfigurationEmailComponent implements OnChanges {
|
||||
|
||||
public isMailConfigValid(): boolean {
|
||||
return this.isValid() &&
|
||||
!this.testingMailOnGoing;
|
||||
!this.testingMailOnGoing && !this.inProgress();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -138,7 +142,7 @@ export class ConfigurationEmailComponent implements OnChanges {
|
||||
.subscribe(response => {
|
||||
this.onGoing = false;
|
||||
// refresh allConfig
|
||||
this.refreshAllconfig.emit();
|
||||
this.conf.updateConfig();
|
||||
this.msgHandler.showSuccess('CONFIG.SAVE_SUCCESS');
|
||||
}, error => {
|
||||
this.onGoing = false;
|
||||
@ -159,7 +163,7 @@ export class ConfigurationEmailComponent implements OnChanges {
|
||||
public cancel(): void {
|
||||
let changes = this.getChanges();
|
||||
if (!isEmpty(changes)) {
|
||||
this.confirmMessageHandler.confirmUnsavedChanges(changes);
|
||||
this.conf.confirmUnsavedChanges(changes);
|
||||
} else {
|
||||
// Invalid situation, should not come here
|
||||
console.error('Nothing changed');
|
||||
|
@ -1,6 +1,5 @@
|
||||
<form #systemConfigFrom="ngForm" class="clr-form clr-form-horizontal">
|
||||
<section>
|
||||
<label class="subtitle" *ngIf="showSubTitle">{{'CONFIG.SYSTEM' | translate}}</label>
|
||||
<clr-select-container>
|
||||
<label for="proCreation">{{'CONFIG.PRO_CREATION_RESTRICTION' | translate}}
|
||||
<clr-tooltip>
|
||||
@ -11,8 +10,8 @@
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
<select clrSelect id="proCreation" name="proCreation"
|
||||
[(ngModel)]="systemSettings.project_creation_restriction.value"
|
||||
[disabled]="disabled(systemSettings.project_creation_restriction)">
|
||||
[(ngModel)]="currentConfig.project_creation_restriction.value"
|
||||
[disabled]="disabled(currentConfig.project_creation_restriction)">
|
||||
<option value="everyone">{{'CONFIG.PRO_CREATION_EVERYONE' | translate }}</option>
|
||||
<option value="adminonly">{{'CONFIG.PRO_CREATION_ADMIN' | translate }}</option>
|
||||
</select>
|
||||
@ -42,7 +41,7 @@
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
<input clrInput name="robotNamePrefix" type="text"
|
||||
[(ngModel)]="systemSettings.robot_name_prefix.value"
|
||||
[(ngModel)]="currentConfig.robot_name_prefix.value"
|
||||
required
|
||||
autocomplete="off"
|
||||
id="robotNamePrefix" size="20" [disabled]="!robotNamePrefixEditable()" />
|
||||
@ -73,7 +72,7 @@
|
||||
</clr-tooltip>
|
||||
<a rel='noopener noreferrer' #certDownloadLink class="cert-down" [href]="downloadLink" target="_blank">{{'CONFIG.ROOT_CERT_LINK' | translate}}</a>
|
||||
</label>
|
||||
<clr-checkbox-container *ngIf="!withAdmiral">
|
||||
<clr-checkbox-container>
|
||||
<label id="repo_read_only_lbl" for="repoReadOnly">{{'CONFIG.REPO_READ_ONLY' | translate}}
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
@ -83,8 +82,8 @@
|
||||
</clr-tooltip>
|
||||
</label>
|
||||
<clr-checkbox-wrapper>
|
||||
<input type="checkbox" [disabled]="!systemSettings.read_only.editable" clrCheckbox name="repoReadOnly" id="repoReadOnly"
|
||||
[ngModel]="systemSettings.read_only.value" (ngModelChange)="setRepoReadOnlyValue($event)" />
|
||||
<input type="checkbox" [disabled]="!currentConfig.read_only.editable" clrCheckbox name="repoReadOnly" id="repoReadOnly"
|
||||
[ngModel]="currentConfig.read_only.value" (ngModelChange)="setRepoReadOnlyValue($event)" />
|
||||
</clr-checkbox-wrapper>
|
||||
</clr-checkbox-container>
|
||||
|
||||
@ -162,7 +161,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<clr-checkbox-container *ngIf="!withAdmiral">
|
||||
<clr-checkbox-container>
|
||||
<label for="webhookNotificationEnabled">{{'CONFIG.WEBHOOK_NOTIFICATION_ENABLED' | translate}}
|
||||
<clr-tooltip>
|
||||
<clr-icon clrTooltipTrigger shape="info-circle" size="24"></clr-icon>
|
||||
@ -173,9 +172,9 @@
|
||||
</label>
|
||||
<clr-checkbox-wrapper>
|
||||
<input type="checkbox" clrCheckbox name="webhookNotificationEnabled" id="webhookNotificationEnabled"
|
||||
[ngModel]="systemSettings.notification_enable.value"
|
||||
[ngModel]="currentConfig.notification_enable.value"
|
||||
(ngModelChange)="setWebhookNotificationEnabledValue($event)"
|
||||
[disabled]="!systemSettings.notification_enable.editable" />
|
||||
[disabled]="!currentConfig.notification_enable.editable" />
|
||||
</clr-checkbox-wrapper>
|
||||
</clr-checkbox-container>
|
||||
</section>
|
||||
@ -188,4 +187,3 @@
|
||||
[disabled]="(!isValid() || !hasChanges()) && (!hasAllowlistChanged) || inProgress">{{'BUTTON.CANCEL'
|
||||
| translate}}</button>
|
||||
</div>
|
||||
<confirmation-dialog #cfgConfirmationDialog (confirmAction)="confirmCancel($event)"></confirmation-dialog>
|
||||
|
@ -3,9 +3,10 @@ import { SystemSettingsComponent } from "./system-settings.component";
|
||||
import { SystemInfoService } from "../../../../shared/services";
|
||||
import { ErrorHandler } from "../../../../shared/units/error-handler";
|
||||
import { of } from "rxjs";
|
||||
import { StringValueItem } from "../config";
|
||||
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
|
||||
import { Configuration, StringValueItem } from "../config";
|
||||
import { SharedTestingModule } from "../../../../shared/shared.module";
|
||||
import { ConfigService } from "../config.service";
|
||||
import { AppConfigService } from "../../../../services/app-config.service";
|
||||
describe('SystemSettingsComponent', () => {
|
||||
let component: SystemSettingsComponent;
|
||||
let fixture: ComponentFixture<SystemSettingsComponent>;
|
||||
@ -33,13 +34,43 @@ describe('SystemSettingsComponent', () => {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
const fakeConfigService = {
|
||||
config: new Configuration(),
|
||||
getConfig() {
|
||||
return this.config;
|
||||
},
|
||||
setConfig(c) {
|
||||
this.config = c;
|
||||
},
|
||||
getOriginalConfig() {
|
||||
return new Configuration();
|
||||
},
|
||||
getLoadingConfigStatus() {
|
||||
return false;
|
||||
},
|
||||
confirmUnsavedChanges() {
|
||||
},
|
||||
updateConfig() {
|
||||
},
|
||||
resetConfig() {
|
||||
}
|
||||
};
|
||||
const fakedAppConfigService = {
|
||||
getConfig() {
|
||||
return {};
|
||||
},
|
||||
load() {
|
||||
return of(null);
|
||||
}
|
||||
};
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
SharedTestingModule,
|
||||
BrowserAnimationsModule
|
||||
],
|
||||
providers: [
|
||||
{ provide: AppConfigService, useValue: fakedAppConfigService },
|
||||
{ provide: ConfigService, useValue: fakeConfigService },
|
||||
{ provide: ErrorHandler, useValue: fakedErrorHandler },
|
||||
{ provide: SystemInfoService, useValue: fakedSystemInfoService },
|
||||
// open auto detect
|
||||
@ -53,7 +84,7 @@ describe('SystemSettingsComponent', () => {
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(SystemSettingsComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.config.auth_mode = new StringValueItem("db_auth", false );
|
||||
component.currentConfig.auth_mode = new StringValueItem("db_auth", false );
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
@ -61,6 +92,7 @@ describe('SystemSettingsComponent', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
it('cancel button should works', () => {
|
||||
const spy: jasmine.Spy = spyOn(fakeConfigService, 'confirmUnsavedChanges').and.returnValue(undefined);
|
||||
component.systemAllowlist.items.push({cve_id: 'CVE-2019-456'});
|
||||
const readOnly: HTMLElement = fixture.nativeElement.querySelector('#repoReadOnly');
|
||||
readOnly.click();
|
||||
@ -68,7 +100,7 @@ describe('SystemSettingsComponent', () => {
|
||||
const cancel: HTMLButtonElement = fixture.nativeElement.querySelector('#config_system_cancel');
|
||||
cancel.click();
|
||||
fixture.detectChanges();
|
||||
expect(component.confirmationDlg.opened).toBeTruthy();
|
||||
expect(spy.calls.count()).toEqual(1);
|
||||
});
|
||||
it('save button should works', () => {
|
||||
component.systemAllowlist.items[0].cve_id = 'CVE-2019-789';
|
||||
|
@ -1,28 +1,16 @@
|
||||
import {
|
||||
Component,
|
||||
Input,
|
||||
OnInit,
|
||||
Output,
|
||||
EventEmitter,
|
||||
ViewChild,
|
||||
OnChanges,
|
||||
SimpleChanges,
|
||||
ElementRef
|
||||
} from '@angular/core';
|
||||
import {NgForm} from '@angular/forms';
|
||||
import {Configuration, StringValueItem} from '../config';
|
||||
import { clone, isEmpty, getChanges, compareValue, CURRENT_BASE_HREF } from '../../../../shared/units/utils';
|
||||
import {ErrorHandler} from '../../../../shared/units/error-handler';
|
||||
import {ConfirmationMessage} from '../../../global-confirmation-dialog/confirmation-message';
|
||||
import {ConfirmationDialogComponent} from '../../../../shared/components/confirmation-dialog';
|
||||
import {ConfirmationState, ConfirmationTargets} from '../../../../shared/entities/shared.const';
|
||||
import {ConfirmationAcknowledgement} from '../../../global-confirmation-dialog/confirmation-state-message';
|
||||
import { SystemCVEAllowlist, SystemInfo, SystemInfoService,
|
||||
} from '../../../../shared/services';
|
||||
import {forkJoin} from "rxjs";
|
||||
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
|
||||
import { NgForm } from '@angular/forms';
|
||||
import { Configuration } from '../config';
|
||||
import { clone, compareValue, CURRENT_BASE_HREF, getChanges, isEmpty } from '../../../../shared/units/utils';
|
||||
import { ErrorHandler } from '../../../../shared/units/error-handler';
|
||||
import { ConfirmationState, ConfirmationTargets } from '../../../../shared/entities/shared.const';
|
||||
import { ConfirmationAcknowledgement } from '../../../global-confirmation-dialog/confirmation-state-message';
|
||||
import { SystemCVEAllowlist, SystemInfo, SystemInfoService, } from '../../../../shared/services';
|
||||
import { forkJoin } from "rxjs";
|
||||
import { ConfigurationService } from "../../../../services/config.service";
|
||||
import { ConfigService } from "../config.service";
|
||||
import { AppConfigService } from "../../../../services/app-config.service";
|
||||
|
||||
const fakePass = 'aWpLOSYkIzJTTU4wMDkx';
|
||||
const ONE_THOUSAND: number = 1000;
|
||||
const CVE_DETAIL_PRE_URL = `https://nvd.nist.gov/vuln/detail/`;
|
||||
const TARGET_BLANK = "_blank";
|
||||
@ -32,70 +20,58 @@ const TARGET_BLANK = "_blank";
|
||||
templateUrl: './system-settings.component.html',
|
||||
styleUrls: ['./system-settings.component.scss']
|
||||
})
|
||||
export class SystemSettingsComponent implements OnChanges, OnInit {
|
||||
config: Configuration = new Configuration();
|
||||
export class SystemSettingsComponent implements OnInit {
|
||||
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;
|
||||
get currentConfig(): Configuration {
|
||||
return this.conf.getConfig();
|
||||
}
|
||||
|
||||
set systemSettings(cfg: Configuration) {
|
||||
this.config = cfg;
|
||||
this.configChange.emit(this.config);
|
||||
set currentConfig(cfg: Configuration) {
|
||||
this.conf.setConfig(cfg);
|
||||
}
|
||||
|
||||
@Input() showSubTitle: boolean = false;
|
||||
@Input() hasAdminRole: boolean = false;
|
||||
@Input() hasCAFile: boolean = false;
|
||||
@Input() withAdmiral = false;
|
||||
|
||||
@ViewChild("systemConfigFrom") systemSettingsForm: NgForm;
|
||||
@ViewChild("cfgConfirmationDialog") confirmationDlg: ConfirmationDialogComponent;
|
||||
@ViewChild('dateInput') dateInput: ElementRef;
|
||||
|
||||
get editable(): boolean {
|
||||
return this.systemSettings &&
|
||||
this.systemSettings.token_expiration &&
|
||||
this.systemSettings.token_expiration.editable;
|
||||
return this.currentConfig &&
|
||||
this.currentConfig.token_expiration &&
|
||||
this.currentConfig.token_expiration.editable;
|
||||
}
|
||||
|
||||
get robotExpirationEditable(): boolean {
|
||||
return this.systemSettings &&
|
||||
this.systemSettings.robot_token_duration &&
|
||||
this.systemSettings.robot_token_duration.editable;
|
||||
return this.currentConfig &&
|
||||
this.currentConfig.robot_token_duration &&
|
||||
this.currentConfig.robot_token_duration.editable;
|
||||
}
|
||||
|
||||
get tokenExpirationValue() {
|
||||
return this.systemSettings.token_expiration.value;
|
||||
return this.currentConfig.token_expiration.value;
|
||||
}
|
||||
|
||||
set tokenExpirationValue(v) {
|
||||
// convert string to number
|
||||
this.systemSettings.token_expiration.value = +v;
|
||||
this.currentConfig.token_expiration.value = +v;
|
||||
}
|
||||
|
||||
get robotTokenExpirationValue() {
|
||||
return this.systemSettings.robot_token_duration.value;
|
||||
return this.currentConfig.robot_token_duration.value;
|
||||
}
|
||||
|
||||
set robotTokenExpirationValue(v) {
|
||||
// convert string to number
|
||||
this.systemSettings.robot_token_duration.value = +v;
|
||||
this.currentConfig.robot_token_duration.value = +v;
|
||||
}
|
||||
|
||||
robotNamePrefixEditable(): boolean {
|
||||
return this.systemSettings &&
|
||||
this.systemSettings.robot_name_prefix &&
|
||||
this.systemSettings.robot_name_prefix.editable;
|
||||
return this.currentConfig &&
|
||||
this.currentConfig.robot_name_prefix &&
|
||||
this.currentConfig.robot_name_prefix.editable;
|
||||
}
|
||||
|
||||
public isValid(): boolean {
|
||||
@ -107,19 +83,13 @@ export class SystemSettingsComponent implements OnChanges, OnInit {
|
||||
}
|
||||
|
||||
public getChanges() {
|
||||
let allChanges = getChanges(this.originalConfig, this.config);
|
||||
let allChanges = getChanges(this.conf.getOriginalConfig(), this.currentConfig);
|
||||
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) {
|
||||
@ -132,11 +102,11 @@ export class SystemSettingsComponent implements OnChanges, OnInit {
|
||||
}
|
||||
|
||||
setRepoReadOnlyValue($event: any) {
|
||||
this.systemSettings.read_only.value = $event;
|
||||
this.currentConfig.read_only.value = $event;
|
||||
}
|
||||
|
||||
setWebhookNotificationEnabledValue($event: any) {
|
||||
this.systemSettings.notification_enable.value = $event;
|
||||
this.currentConfig.notification_enable.value = $event;
|
||||
}
|
||||
|
||||
disabled(prop: any): boolean {
|
||||
@ -144,7 +114,7 @@ export class SystemSettingsComponent implements OnChanges, OnInit {
|
||||
}
|
||||
|
||||
get canDownloadCert(): boolean {
|
||||
return this.hasAdminRole && this.hasCAFile;
|
||||
return this.appConfigService.getConfig().has_ca_root;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -172,12 +142,11 @@ export class SystemSettingsComponent implements OnChanges, OnInit {
|
||||
// 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();
|
||||
this.conf.updateConfig();
|
||||
// Reload bootstrap option
|
||||
this.appConfigService.load().subscribe(() => {
|
||||
}
|
||||
, error => console.error('Failed to reload bootstrap option with error: ', error));
|
||||
}
|
||||
if (!compareValue(this.systemAllowlistOrigin, this.systemAllowlist)) {
|
||||
this.systemAllowlistOrigin = clone(this.systemAllowlist);
|
||||
@ -193,40 +162,10 @@ export class SystemSettingsComponent implements OnChanges, OnInit {
|
||||
}
|
||||
}
|
||||
|
||||
retrieveConfig(): void {
|
||||
this.onGoing = true;
|
||||
this.configService.getConfiguration()
|
||||
.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.conf.resetConfig();
|
||||
if (!compareValue(this.systemAllowlistOrigin, this.systemAllowlist)) {
|
||||
this.systemAllowlist = clone(this.systemAllowlistOrigin);
|
||||
}
|
||||
@ -235,7 +174,7 @@ export class SystemSettingsComponent implements OnChanges, OnInit {
|
||||
|
||||
|
||||
public get inProgress(): boolean {
|
||||
return this.onGoing;
|
||||
return this.onGoing || this.conf.getLoadingConfigStatus();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -247,28 +186,23 @@ export class SystemSettingsComponent implements OnChanges, OnInit {
|
||||
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);
|
||||
this.conf.confirmUnsavedChanges(changes);
|
||||
} else {
|
||||
// Invalid situation, should not come here
|
||||
console.error('Nothing changed');
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
constructor(private appConfigService: AppConfigService,
|
||||
private configService: ConfigurationService,
|
||||
private errorHandler: ErrorHandler,
|
||||
private systemInfoService: SystemInfoService) {
|
||||
private systemInfoService: SystemInfoService,
|
||||
private conf: ConfigService) {
|
||||
this.downloadLink = CURRENT_BASE_HREF + "/systeminfo/getcert";
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.conf.resetConfig();
|
||||
this.getSystemAllowlist();
|
||||
this.getSystemInfo();
|
||||
}
|
||||
@ -278,6 +212,7 @@ export class SystemSettingsComponent implements OnChanges, OnInit {
|
||||
.subscribe(systemInfo => this.systemInfo = systemInfo
|
||||
, error => this.errorHandler.error(error));
|
||||
}
|
||||
|
||||
getSystemAllowlist() {
|
||||
this.onGoing = true;
|
||||
this.systemInfoService.getSystemAllowlist()
|
||||
@ -298,6 +233,7 @@ export class SystemSettingsComponent implements OnChanges, OnInit {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
deleteItem(index: number) {
|
||||
this.systemAllowlist.items.splice(index, 1);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user