diff options
Diffstat (limited to 'src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-form/host-form.component.ts')
-rw-r--r-- | src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-form/host-form.component.ts | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-form/host-form.component.ts b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-form/host-form.component.ts new file mode 100644 index 000000000..6bfb79d67 --- /dev/null +++ b/src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/hosts/host-form/host-form.component.ts @@ -0,0 +1,171 @@ +import { Component, OnInit } from '@angular/core'; +import { FormControl, Validators } from '@angular/forms'; +import { Router } from '@angular/router'; + +import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap'; +import expand from 'brace-expansion'; + +import { HostService } from '~/app/shared/api/host.service'; +import { SelectMessages } from '~/app/shared/components/select/select-messages.model'; +import { SelectOption } from '~/app/shared/components/select/select-option.model'; +import { ActionLabelsI18n, URLVerbs } from '~/app/shared/constants/app.constants'; +import { CdForm } from '~/app/shared/forms/cd-form'; +import { CdFormGroup } from '~/app/shared/forms/cd-form-group'; +import { CdValidators } from '~/app/shared/forms/cd-validators'; +import { FinishedTask } from '~/app/shared/models/finished-task'; +import { TaskWrapperService } from '~/app/shared/services/task-wrapper.service'; + +@Component({ + selector: 'cd-host-form', + templateUrl: './host-form.component.html', + styleUrls: ['./host-form.component.scss'] +}) +export class HostFormComponent extends CdForm implements OnInit { + hostForm: CdFormGroup; + action: string; + resource: string; + hostnames: string[]; + hostnameArray: string[] = []; + addr: string; + status: string; + allLabels: string[]; + pageURL: string; + hostPattern = false; + labelsOption: Array<SelectOption> = []; + + messages = new SelectMessages({ + empty: $localize`There are no labels.`, + filter: $localize`Filter or add labels`, + add: $localize`Add label` + }); + + constructor( + private router: Router, + private actionLabels: ActionLabelsI18n, + private hostService: HostService, + private taskWrapper: TaskWrapperService, + public activeModal: NgbActiveModal + ) { + super(); + this.resource = $localize`host`; + this.action = this.actionLabels.ADD; + } + + ngOnInit() { + if (this.router.url.includes('hosts')) { + this.pageURL = 'hosts'; + } + this.createForm(); + this.hostService.list('false').subscribe((resp: any[]) => { + this.hostnames = resp.map((host) => { + return host['hostname']; + }); + this.loadingReady(); + }); + + this.hostService.getLabels().subscribe((resp: string[]) => { + const uniqueLabels = new Set(resp.concat(this.hostService.predefinedLabels)); + this.labelsOption = Array.from(uniqueLabels).map((label) => { + return { enabled: true, name: label, selected: false, description: null }; + }); + }); + } + + // check if hostname is a single value or pattern to hide network address field + checkHostNameValue() { + const hostNames = this.hostForm.get('hostname').value; + hostNames.match(/[()\[\]{},]/g) ? (this.hostPattern = true) : (this.hostPattern = false); + } + + private createForm() { + this.hostForm = new CdFormGroup({ + hostname: new FormControl('', { + validators: [ + Validators.required, + CdValidators.custom('uniqueName', (hostname: string) => { + return this.hostnames && this.hostnames.indexOf(hostname) !== -1; + }) + ] + }), + addr: new FormControl('', { + validators: [CdValidators.ip()] + }), + labels: new FormControl([]), + maintenance: new FormControl({ value: false, disabled: this.pageURL !== 'hosts' }) + }); + } + + private isCommaSeparatedPattern(hostname: string) { + // eg. ceph-node-01.cephlab.com,ceph-node-02.cephlab.com + return hostname.includes(','); + } + + private isRangeTypePattern(hostname: string) { + // check if it is a range expression or comma separated range expression + // eg. ceph-mon-[01-05].lab.com,ceph-osd-[02-08].lab.com,ceph-rgw-[01-09] + return hostname.includes('[') && hostname.includes(']') && !hostname.match(/(?![^(]*\)),/g); + } + + private replaceBraces(hostname: string) { + // pattern to replace range [0-5] to [0..5](valid expression for brace expansion) + // replace any kind of brackets with curly braces + return hostname + .replace(/(\d)\s*-\s*(\d)/g, '$1..$2') + .replace(/\(/g, '{') + .replace(/\)/g, '}') + .replace(/\[/g, '{') + .replace(/]/g, '}'); + } + + // expand hostnames in case hostname is a pattern + private checkHostNamePattern(hostname: string) { + if (this.isRangeTypePattern(hostname)) { + const hostnameRange = this.replaceBraces(hostname); + this.hostnameArray = expand(hostnameRange); + } else if (this.isCommaSeparatedPattern(hostname)) { + let hostArray = []; + hostArray = hostname.split(','); + hostArray.forEach((host: string) => { + if (this.isRangeTypePattern(host)) { + const hostnameRange = this.replaceBraces(host); + this.hostnameArray = this.hostnameArray.concat(expand(hostnameRange)); + } else { + this.hostnameArray.push(host); + } + }); + } else { + // single hostname + this.hostnameArray.push(hostname); + } + } + + submit() { + const hostname = this.hostForm.get('hostname').value; + this.checkHostNamePattern(hostname); + this.addr = this.hostForm.get('addr').value; + this.status = this.hostForm.get('maintenance').value ? 'maintenance' : ''; + this.allLabels = this.hostForm.get('labels').value; + if (this.pageURL !== 'hosts' && !this.allLabels.includes('_no_schedule')) { + this.allLabels.push('_no_schedule'); + } + this.hostnameArray.forEach((hostName: string) => { + this.taskWrapper + .wrapTaskAroundCall({ + task: new FinishedTask('host/' + URLVerbs.ADD, { + hostname: hostName + }), + call: this.hostService.create(hostName, this.addr, this.allLabels, this.status) + }) + .subscribe({ + error: () => { + this.hostForm.setErrors({ cdSubmitButton: true }); + }, + complete: () => { + this.pageURL === 'hosts' + ? this.router.navigate([this.pageURL, { outlets: { modal: null } }]) + : this.activeModal.close(); + } + }); + }); + } +} |