From b952a0df11ef84dfb9769b93dfa6556684bf5dbd Mon Sep 17 00:00:00 2001 From: Tatjana Dehler Date: Mon, 25 Jun 2018 16:30:16 +0200 Subject: [PATCH] mgr/dashboard: add config options form Fixes: http://tracker.ceph.com/issues/24455 Signed-off-by: Tatjana Dehler --- .../frontend/src/app/app-routing.module.ts | 13 +- .../src/app/ceph/cluster/cluster.module.ts | 4 +- .../configuration-form.component.html | 167 +++++++++++ .../configuration-form.component.scss | 8 + .../configuration-form.component.spec.ts | 275 ++++++++++++++++++ .../configuration-form.component.ts | 242 +++++++++++++++ .../configuration-form.model.ts | 12 + .../configuration.component.html | 7 +- .../configuration/configuration.component.ts | 21 +- .../app/shared/api/configuration.service.ts | 4 + 10 files changed, 747 insertions(+), 6 deletions(-) create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.html create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.scss create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.spec.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.ts create mode 100644 src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.model.ts diff --git a/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts index efe4ed58db8..84af029ac4c 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/app-routing.module.ts @@ -6,6 +6,7 @@ import { MirroringComponent } from './ceph/block/mirroring/mirroring.component'; import { RbdFormComponent } from './ceph/block/rbd-form/rbd-form.component'; import { RbdImagesComponent } from './ceph/block/rbd-images/rbd-images.component'; import { CephfsListComponent } from './ceph/cephfs/cephfs-list/cephfs-list.component'; +import { ConfigurationFormComponent } from './ceph/cluster/configuration/configuration-form/configuration-form.component'; import { ConfigurationComponent } from './ceph/cluster/configuration/configuration.component'; import { HostsComponent } from './ceph/cluster/hosts/hosts.component'; import { MonitorComponent } from './ceph/cluster/monitor/monitor.component'; @@ -77,9 +78,15 @@ const routes: Routes = [ }, { path: 'configuration', - component: ConfigurationComponent, - canActivate: [AuthGuardService], - data: { breadcrumbs: 'Cluster/Configuration Documentation' } + data: { breadcrumbs: 'Cluster/Configuration Documentation' }, + children: [ + { path: '', component: ConfigurationComponent }, + { + path: 'edit/:name', + component: ConfigurationFormComponent, + data: { breadcrumbs: 'Edit' } + } + ] }, { path: 'perf_counters/:type/:id', diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts index 563871f5321..748568eb768 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/cluster.module.ts @@ -10,6 +10,7 @@ import { TabsModule } from 'ngx-bootstrap/tabs'; import { SharedModule } from '../../shared/shared.module'; import { PerformanceCounterModule } from '../performance-counter/performance-counter.module'; import { ConfigurationDetailsComponent } from './configuration/configuration-details/configuration-details.component'; +import { ConfigurationFormComponent } from './configuration/configuration-form/configuration-form.component'; import { ConfigurationComponent } from './configuration/configuration.component'; import { HostDetailsComponent } from './hosts/host-details/host-details.component'; import { HostsComponent } from './hosts/hosts.component'; @@ -43,7 +44,8 @@ import { OsdScrubModalComponent } from './osd/osd-scrub-modal/osd-scrub-modal.co OsdScrubModalComponent, OsdFlagsModalComponent, HostDetailsComponent, - ConfigurationDetailsComponent + ConfigurationDetailsComponent, + ConfigurationFormComponent ] }) export class ClusterModule {} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.html new file mode 100644 index 00000000000..74edf15e8d6 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.html @@ -0,0 +1,167 @@ +
+
+
+
+

+ Edit {{ configForm.getValue('name') }} +

+
+
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+ +
+ +
+
+ + +
+ +
+ + {{ service }} + +
+
+ + +
+ +
+
+
+
+ + +
+
+
+
+ +
+ + + {{ patternHelpText }} + + + The entered value is too high! It must not be greater than {{ maxValue }}. + + + The entered value is too low! It must not be lower than {{ minValue }}. + +
+
+
+
+
+ + +
+
+
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.scss b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.scss new file mode 100644 index 00000000000..c9a160dc048 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.scss @@ -0,0 +1,8 @@ +.form-component-badge { + height: 34px; + display: block; + + span { + margin-top: 7px; + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.spec.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.spec.ts new file mode 100644 index 00000000000..4b98d7f5148 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.spec.ts @@ -0,0 +1,275 @@ +import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { ReactiveFormsModule } from '@angular/forms'; +import { ActivatedRoute } from '@angular/router'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { configureTestBed } from '../../../../../testing/unit-test-helper'; +import { SharedModule } from '../../../../shared/shared.module'; +import { ConfigurationFormComponent } from './configuration-form.component'; +import { ConfigFormModel } from './configuration-form.model'; + +describe('ConfigurationFormComponent', () => { + let component: ConfigurationFormComponent; + let fixture: ComponentFixture; + let activatedRoute: ActivatedRoute; + + configureTestBed({ + imports: [HttpClientTestingModule, ReactiveFormsModule, RouterTestingModule, SharedModule], + declarations: [ConfigurationFormComponent], + providers: [ + { + provide: ActivatedRoute + } + ] + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ConfigurationFormComponent); + component = fixture.componentInstance; + activatedRoute = TestBed.get(ActivatedRoute); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + describe('getType', () => { + it('should return uint64_t type', () => { + const ret = component.getType('uint64_t'); + expect(ret).toBeTruthy(); + expect(ret.name).toBe('uint64_t'); + expect(ret.inputType).toBe('number'); + expect(ret.humanReadable).toBe('Positive integer value'); + expect(ret.defaultMin).toBe(0); + expect(ret.patternHelpText).toBe('The entered value needs to be a positive number.'); + expect(ret.isNumberType).toBe(true); + expect(ret.allowsNegative).toBe(false); + }); + + it('should return int64_t type', () => { + const ret = component.getType('int64_t'); + expect(ret).toBeTruthy(); + expect(ret.name).toBe('int64_t'); + expect(ret.inputType).toBe('number'); + expect(ret.humanReadable).toBe('Integer value'); + expect(ret.defaultMin).toBeUndefined(); + expect(ret.patternHelpText).toBe('The entered value needs to be a number.'); + expect(ret.isNumberType).toBe(true); + expect(ret.allowsNegative).toBe(true); + }); + + it('should return size_t type', () => { + const ret = component.getType('size_t'); + expect(ret).toBeTruthy(); + expect(ret.name).toBe('size_t'); + expect(ret.inputType).toBe('number'); + expect(ret.humanReadable).toBe('Positive integer value (size)'); + expect(ret.defaultMin).toBe(0); + expect(ret.patternHelpText).toBe('The entered value needs to be a positive number.'); + expect(ret.isNumberType).toBe(true); + expect(ret.allowsNegative).toBe(false); + }); + + it('should return secs type', () => { + const ret = component.getType('secs'); + expect(ret).toBeTruthy(); + expect(ret.name).toBe('secs'); + expect(ret.inputType).toBe('number'); + expect(ret.humanReadable).toBe('Positive integer value (secs)'); + expect(ret.defaultMin).toBe(1); + expect(ret.patternHelpText).toBe('The entered value needs to be a positive number.'); + expect(ret.isNumberType).toBe(true); + expect(ret.allowsNegative).toBe(false); + }); + + it('should return double type', () => { + const ret = component.getType('double'); + expect(ret).toBeTruthy(); + expect(ret.name).toBe('double'); + expect(ret.inputType).toBe('number'); + expect(ret.humanReadable).toBe('Decimal value'); + expect(ret.defaultMin).toBeUndefined(); + expect(ret.patternHelpText).toBe('The entered value needs to be a number or decimal.'); + expect(ret.isNumberType).toBe(true); + expect(ret.allowsNegative).toBe(true); + }); + + it('should return std::string type', () => { + const ret = component.getType('std::string'); + expect(ret).toBeTruthy(); + expect(ret.name).toBe('std::string'); + expect(ret.inputType).toBe('text'); + expect(ret.humanReadable).toBe('Text'); + expect(ret.defaultMin).toBeUndefined(); + expect(ret.patternHelpText).toBeUndefined(); + expect(ret.isNumberType).toBe(false); + expect(ret.allowsNegative).toBeUndefined(); + }); + + it('should return entity_addr_t type', () => { + const ret = component.getType('entity_addr_t'); + expect(ret).toBeTruthy(); + expect(ret.name).toBe('entity_addr_t'); + expect(ret.inputType).toBe('text'); + expect(ret.humanReadable).toBe('IPv4 or IPv6 address'); + expect(ret.defaultMin).toBeUndefined(); + expect(ret.patternHelpText).toBe('The entered value needs to be a valid IP address.'); + expect(ret.isNumberType).toBe(false); + expect(ret.allowsNegative).toBeUndefined(); + }); + + it('should return uuid_d type', () => { + const ret = component.getType('uuid_d'); + expect(ret).toBeTruthy(); + expect(ret.name).toBe('uuid_d'); + expect(ret.inputType).toBe('text'); + expect(ret.humanReadable).toBe('UUID'); + expect(ret.defaultMin).toBeUndefined(); + expect(ret.patternHelpText).toBe( + 'The entered value is not a valid UUID, e.g.: 67dcac9f-2c03-4d6c-b7bd-1210b3a259a8' + ); + expect(ret.isNumberType).toBe(false); + expect(ret.allowsNegative).toBeUndefined(); + }); + + it('should return bool type', () => { + const ret = component.getType('bool'); + expect(ret).toBeTruthy(); + expect(ret.name).toBe('bool'); + expect(ret.inputType).toBe('checkbox'); + expect(ret.humanReadable).toBe('Boolean value'); + expect(ret.defaultMin).toBeUndefined(); + expect(ret.patternHelpText).toBeUndefined(); + expect(ret.isNumberType).toBe(false); + expect(ret.allowsNegative).toBeUndefined(); + }); + + it('should throw an error for unknown type', () => { + expect(() => + component.getType('unknown').toThrowError('Found unknown type "unknown" for config option.') + ); + }); + }); + + describe('getValidators', () => { + it('should return a validator for types double, entity_addr_t and uuid_d', () => { + const types = ['double', 'entity_addr_t', 'uuid_d']; + + types.forEach((valType) => { + const configOption = new ConfigFormModel(); + configOption.type = valType; + + const ret = component.getValidators(configOption); + expect(ret).toBeTruthy(); + expect(ret.length).toBe(1); + }); + }); + + it('should not return a validator for types std::string and bool', () => { + const types = ['std::string', 'bool']; + + types.forEach((valType) => { + const configOption = new ConfigFormModel(); + configOption.type = valType; + + const ret = component.getValidators(configOption); + expect(ret).toBeUndefined(); + }); + }); + + it('should return a pattern and a min validator', () => { + const configOption = new ConfigFormModel(); + configOption.type = 'int64_t'; + configOption.min = 2; + + const ret = component.getValidators(configOption); + expect(ret).toBeTruthy(); + expect(ret.length).toBe(2); + expect(component.minValue).toBe(2); + expect(component.maxValue).toBeUndefined(); + }); + + it('should return a pattern and a max validator', () => { + const configOption = new ConfigFormModel(); + configOption.type = 'int64_t'; + configOption.max = 5; + + const ret = component.getValidators(configOption); + expect(ret).toBeTruthy(); + expect(ret.length).toBe(2); + expect(component.minValue).toBeUndefined(); + expect(component.maxValue).toBe(5); + }); + + it('should return multiple validators', () => { + const configOption = new ConfigFormModel(); + configOption.type = 'double'; + configOption.max = 5.2; + configOption.min = 1.5; + + const ret = component.getValidators(configOption); + expect(ret).toBeTruthy(); + expect(ret.length).toBe(3); + expect(component.minValue).toBe(1.5); + expect(component.maxValue).toBe(5.2); + }); + }); + + describe('getStep', () => { + it('should return the correct step for type uint64_t and value 0', () => { + const ret = component.getStep('uint64_t', 0); + expect(ret).toBe(1); + }); + + it('should return the correct step for type int64_t and value 1', () => { + const ret = component.getStep('int64_t', 1); + expect(ret).toBe(1); + }); + + it('should return the correct step for type int64_t and value null', () => { + const ret = component.getStep('int64_t', null); + expect(ret).toBe(1); + }); + + it('should return the correct step for type size_t and value 2', () => { + const ret = component.getStep('size_t', 2); + expect(ret).toBe(1); + }); + + it('should return the correct step for type secs and value 3', () => { + const ret = component.getStep('secs', 3); + expect(ret).toBe(1); + }); + + it('should return the correct step for type double and value 1', () => { + const ret = component.getStep('double', 1); + expect(ret).toBe(0.1); + }); + + it('should return the correct step for type double and value 0.1', () => { + const ret = component.getStep('double', 0.1); + expect(ret).toBe(0.1); + }); + + it('should return the correct step for type double and value 0.02', () => { + const ret = component.getStep('double', 0.02); + expect(ret).toBe(0.01); + }); + + it('should return the correct step for type double and value 0.003', () => { + const ret = component.getStep('double', 0.003); + expect(ret).toBe(0.001); + }); + + it('should return the correct step for type double and value null', () => { + const ret = component.getStep('double', null); + expect(ret).toBe(0.1); + }); + + it('should return undefined for unknown type', () => { + const ret = component.getStep('unknown', 1); + expect(ret).toBeUndefined(); + }); + }); +}); diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.ts new file mode 100644 index 00000000000..2e654db5993 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.component.ts @@ -0,0 +1,242 @@ +import { Component, OnInit } from '@angular/core'; +import { FormControl, FormGroup, ValidatorFn, Validators } from '@angular/forms'; +import { ActivatedRoute, Router } from '@angular/router'; + +import { ConfigurationService } from '../../../../shared/api/configuration.service'; +import { CdFormGroup } from '../../../../shared/forms/cd-form-group'; +import { CdValidators } from '../../../../shared/forms/cd-validators'; +import { ConfigFormModel } from './configuration-form.model'; + +@Component({ + selector: 'cd-configuration-form', + templateUrl: './configuration-form.component.html', + styleUrls: ['./configuration-form.component.scss'] +}) +export class ConfigurationFormComponent implements OnInit { + configForm: CdFormGroup; + response: ConfigFormModel; + type: string; + inputType: string; + humanReadableType: string; + minValue: number; + maxValue: number; + patternHelpText: string; + availSections = ['global', 'mon', 'mgr', 'osd', 'mds', 'client']; + + constructor( + private route: ActivatedRoute, + private router: Router, + private configService: ConfigurationService + ) { + this.createForm(); + } + + createForm() { + const formControls = { + name: new FormControl({ value: null }), + desc: new FormControl({ value: null }), + long_desc: new FormControl({ value: null }), + values: new FormGroup({}), + default: new FormControl({ value: null }), + daemon_default: new FormControl({ value: null }), + services: new FormControl([]) + }; + + this.availSections.forEach((section) => { + formControls.values.controls[section] = new FormControl(null); + }); + + this.configForm = new CdFormGroup(formControls); + this.configForm._filterValue = (value) => { + return value; + }; + } + + ngOnInit() { + this.route.params.subscribe((params: { name: string }) => { + const configName = params.name; + this.configService.get(configName).subscribe((resp: ConfigFormModel) => { + this.setResponse(resp); + }); + }); + } + + getType(type: string): any { + const knownTypes = [ + { + name: 'uint64_t', + inputType: 'number', + humanReadable: 'Positive integer value', + defaultMin: 0, + patternHelpText: 'The entered value needs to be a positive number.', + isNumberType: true, + allowsNegative: false + }, + { + name: 'int64_t', + inputType: 'number', + humanReadable: 'Integer value', + patternHelpText: 'The entered value needs to be a number.', + isNumberType: true, + allowsNegative: true + }, + { + name: 'size_t', + inputType: 'number', + humanReadable: 'Positive integer value (size)', + defaultMin: 0, + patternHelpText: 'The entered value needs to be a positive number.', + isNumberType: true, + allowsNegative: false + }, + { + name: 'secs', + inputType: 'number', + humanReadable: 'Positive integer value (secs)', + defaultMin: 1, + patternHelpText: 'The entered value needs to be a positive number.', + isNumberType: true, + allowsNegative: false + }, + { + name: 'double', + inputType: 'number', + humanReadable: 'Decimal value', + patternHelpText: 'The entered value needs to be a number or decimal.', + isNumberType: true, + allowsNegative: true + }, + { name: 'std::string', inputType: 'text', humanReadable: 'Text', isNumberType: false }, + { + name: 'entity_addr_t', + inputType: 'text', + humanReadable: 'IPv4 or IPv6 address', + patternHelpText: 'The entered value needs to be a valid IP address.', + isNumberType: false + }, + { + name: 'uuid_d', + inputType: 'text', + humanReadable: 'UUID', + patternHelpText: + 'The entered value is not a valid UUID, e.g.: 67dcac9f-2c03-4d6c-b7bd-1210b3a259a8', + isNumberType: false + }, + { name: 'bool', inputType: 'checkbox', humanReadable: 'Boolean value', isNumberType: false } + ]; + + let currentType = null; + + knownTypes.forEach((knownType) => { + if (knownType.name === type) { + currentType = knownType; + } + }); + + if (currentType !== null) { + return currentType; + } + + throw new Error('Found unknown type "' + type + '" for config option.'); + } + + getValidators(configOption: any): ValidatorFn[] { + const typeParams = this.getType(configOption.type); + this.patternHelpText = typeParams.patternHelpText; + + if (typeParams.isNumberType) { + const validators = []; + + if (configOption.max && configOption.max !== '') { + this.maxValue = configOption.max; + validators.push(Validators.max(configOption.max)); + } + + if ('min' in configOption && configOption.min !== '') { + this.minValue = configOption.min; + validators.push(Validators.min(configOption.min)); + } else if ('defaultMin' in typeParams) { + this.minValue = typeParams.defaultMin; + validators.push(Validators.min(typeParams.defaultMin)); + } + + if (configOption.type === 'double') { + validators.push(CdValidators.decimalNumber()); + } else { + validators.push(CdValidators.number(typeParams.allowsNegative)); + } + + return validators; + } else if (configOption.type === 'entity_addr_t') { + return [CdValidators.ip()]; + } else if (configOption.type === 'uuid_d') { + return [CdValidators.uuid()]; + } + } + + getStep(type: string, value: number): number | undefined { + const numberTypes = ['uint64_t', 'int64_t', 'size_t', 'secs']; + + if (numberTypes.includes(type)) { + return 1; + } + + if (type === 'double') { + if (value !== null) { + const stringVal = value.toString(); + if (stringVal.indexOf('.') !== -1) { + // Value type double and contains decimal characters + const decimal = value.toString().split('.'); + return Math.pow(10, -decimal[1].length); + } + } + + return 0.1; + } + + return undefined; + } + + setResponse(response: ConfigFormModel) { + this.response = response; + const validators = this.getValidators(response); + + this.configForm.get('name').setValue(response.name); + this.configForm.get('desc').setValue(response.desc); + this.configForm.get('long_desc').setValue(response.long_desc); + this.configForm.get('default').setValue(response.default); + this.configForm.get('daemon_default').setValue(response.daemon_default); + this.configForm.get('services').setValue(response.services); + + if (this.response.value) { + this.response.value.forEach((value) => { + // Check value type. If it's a boolean value we need to convert it because otherwise we + // would use the string representation. That would cause issues for e.g. checkboxes. + let sectionValue = null; + if (value.value === 'true') { + sectionValue = true; + } else if (value.value === 'false') { + sectionValue = false; + } else { + sectionValue = value.value; + } + this.configForm + .get('values') + .get(value.section) + .setValue(sectionValue); + }); + } + + this.availSections.forEach((section) => { + this.configForm + .get('values') + .get(section) + .setValidators(validators); + }); + + const currentType = this.getType(response.type); + this.type = currentType.name; + this.inputType = currentType.inputType; + this.humanReadableType = currentType.humanReadable; + } +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.model.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.model.ts new file mode 100644 index 00000000000..d3ebc5f37c6 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration-form/configuration-form.model.ts @@ -0,0 +1,12 @@ +export class ConfigFormModel { + name: string; + desc: string; + long_desc: string; + type: string; + value: Array; + default: any; + daemon_default: any; + min: any; + max: any; + services: Array; +} diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.html b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.html index b7012699ece..88b4a2a3e4e 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.html +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.html @@ -3,7 +3,12 @@ [columns]="columns" selectionType="single" (updateSelection)="updateSelection($event)"> -
+ + +
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.ts index bf82c9583ba..a9a983e647b 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/configuration/configuration.component.ts @@ -1,9 +1,12 @@ import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core'; import { ConfigurationService } from '../../../shared/api/configuration.service'; +import { CdTableAction } from '../../../shared/models/cd-table-action'; import { CdTableColumn } from '../../../shared/models/cd-table-column'; import { CdTableFetchDataContext } from '../../../shared/models/cd-table-fetch-data-context'; import { CdTableSelection } from '../../../shared/models/cd-table-selection'; +import { Permission } from '../../../shared/models/permissions'; +import { AuthStorageService } from '../../../shared/services/auth-storage.service'; @Component({ selector: 'cd-configuration', @@ -11,6 +14,8 @@ import { CdTableSelection } from '../../../shared/models/cd-table-selection'; styleUrls: ['./configuration.component.scss'] }) export class ConfigurationComponent implements OnInit { + permission: Permission; + tableActions: CdTableAction[]; data = []; columns: CdTableColumn[]; selection = new CdTableSelection(); @@ -79,7 +84,21 @@ export class ConfigurationComponent implements OnInit { @ViewChild('confFlagTpl') public confFlagTpl: TemplateRef; - constructor(private configurationService: ConfigurationService) {} + constructor( + private authStorageService: AuthStorageService, + private configurationService: ConfigurationService + ) { + this.permission = this.authStorageService.getPermissions().configOpt; + const getConfigOptUri = () => + this.selection.first() && `${encodeURI(this.selection.first().name)}`; + const editAction: CdTableAction = { + permission: 'update', + icon: 'fa-pencil', + routerLink: () => `/configuration/edit/${getConfigOptUri()}`, + name: 'Edit' + }; + this.tableActions = [editAction]; + } ngOnInit() { this.columns = [ diff --git a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/configuration.service.ts b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/configuration.service.ts index 5dd305cdc86..dfc68eba2da 100644 --- a/src/pybind/mgr/dashboard/frontend/src/app/shared/api/configuration.service.ts +++ b/src/pybind/mgr/dashboard/frontend/src/app/shared/api/configuration.service.ts @@ -12,4 +12,8 @@ export class ConfigurationService { getConfigData() { return this.http.get('api/cluster_conf/'); } + + get(configOption) { + return this.http.get(`api/cluster_conf/${configOption}`); + } } -- 2.39.5