diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 0db71c4f..0201bfcf 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -279,6 +279,7 @@ import { ChangeHostnameActionComponent } from './components/project-map/context- import { ChangeHostnameDialogComponent } from './components/project-map/change-hostname-dialog/change-hostname-dialog.component'; import { ApplianceInfoDialogComponent } from './components/project-map/new-template-dialog/appliance-info-dialog/appliance-info-dialog.component'; import { InformationDialogComponent } from './components/dialogs/information-dialog.component'; +import { TemplateNameDialogComponent } from './components/project-map/new-template-dialog/template-name-dialog/template-name-dialog.component'; @NgModule({ declarations: [ @@ -462,7 +463,8 @@ import { InformationDialogComponent } from './components/dialogs/information-dia ChangeHostnameActionComponent, ChangeHostnameDialogComponent, ApplianceInfoDialogComponent, - InformationDialogComponent + InformationDialogComponent, + TemplateNameDialogComponent ], imports: [ BrowserModule, diff --git a/src/app/components/project-map/new-template-dialog/new-template-dialog.component.ts b/src/app/components/project-map/new-template-dialog/new-template-dialog.component.ts index 87b42376..3e224b32 100644 --- a/src/app/components/project-map/new-template-dialog/new-template-dialog.component.ts +++ b/src/app/components/project-map/new-template-dialog/new-template-dialog.component.ts @@ -29,6 +29,7 @@ import { Template } from '../../../models/template'; import { ComputeService } from '../../../services/compute.service'; import { InformationDialogComponent } from '../../../components/dialogs/information-dialog.component'; import { ProgressService } from '../../../common/progress/progress.service'; +import { TemplateNameDialogComponent } from './template-name-dialog/template-name-dialog.component'; @Component({ selector: 'app-new-template-dialog', @@ -78,6 +79,8 @@ export class NewTemplateDialogComponent implements OnInit { private iosImages: Image[] = []; private iouImages: Image[] = []; + private templates: Template[] = []; + @ViewChild(MatPaginator, {static: true}) paginator: MatPaginator; @ViewChild('stepper', {static: true}) stepper: MatStepper; @@ -98,6 +101,10 @@ export class NewTemplateDialogComponent implements OnInit { ) {} ngOnInit() { + this.templateService.list(this.server).subscribe((templates) => { + this.templates = templates; + }); + this.computeService.getComputes(this.server).subscribe((computes) => { computes.forEach(compute => { if (compute.compute_id === 'vm') { @@ -423,6 +430,8 @@ export class NewTemplateDialogComponent implements OnInit { } createIouTemplate (image: Image) { + if (!this.validateTemplateName()) return; + let iouTemplate: IouTemplate = new IouTemplate(); iouTemplate.name = this.applianceToInstall.name; iouTemplate.nvram = this.applianceToInstall.iou.nvram; @@ -447,6 +456,8 @@ export class NewTemplateDialogComponent implements OnInit { } createIosTemplate(image: Image) { + if (!this.validateTemplateName()) return; + let iosTemplate: IosTemplate = new IosTemplate(); iosTemplate.name = this.applianceToInstall.name; iosTemplate.chassis = this.applianceToInstall.dynamips.chassis; @@ -479,6 +490,8 @@ export class NewTemplateDialogComponent implements OnInit { } createDockerTemplate() { + if (!this.validateTemplateName()) return; + let dockerTemplate: DockerTemplate = new DockerTemplate(); dockerTemplate.name = this.applianceToInstall.name; dockerTemplate.adapters = this.applianceToInstall.docker.adapters; @@ -509,8 +522,8 @@ export class NewTemplateDialogComponent implements OnInit { this.toasterService.error('Please select QEMU binary first'); return; } + let qemuTemplate: QemuTemplate = new QemuTemplate(); - qemuTemplate.name = this.applianceToInstall.name; qemuTemplate.ram = this.applianceToInstall.qemu.ram; qemuTemplate.adapters = this.applianceToInstall.qemu.adapters; qemuTemplate.adapter_type = this.applianceToInstall.qemu.adapter_type; @@ -533,11 +546,58 @@ export class NewTemplateDialogComponent implements OnInit { qemuTemplate.template_type = 'qemu'; qemuTemplate.usage = this.applianceToInstall.usage; - this.qemuService.addTemplate(this.server, qemuTemplate).subscribe((template) => { - this.templateService.newTemplateCreated.next(template as any as Template); - this.toasterService.success('Template added'); - this.dialogRef.close(); - }); + if (this.templates.filter(t => t.name === this.applianceToInstall.name).length === 0) { + qemuTemplate.name = this.applianceToInstall.name; + + this.qemuService.addTemplate(this.server, qemuTemplate).subscribe((template) => { + this.templateService.newTemplateCreated.next(template as any as Template); + this.toasterService.success('Template added'); + this.dialogRef.close(); + }); + } else { + const dialogRef = this.dialog.open(TemplateNameDialogComponent, { + width: '400px', + height: '250px', + autoFocus: false, + disableClose: true + }); + dialogRef.componentInstance.server = this.server; + dialogRef.afterClosed().subscribe((answer: string) => { + if (answer) { + qemuTemplate.name = answer; + + this.qemuService.addTemplate(this.server, qemuTemplate).subscribe((template) => { + this.templateService.newTemplateCreated.next(template as any as Template); + this.toasterService.success('Template added'); + this.dialogRef.close(); + }); + } else{ + return false; + } + }); + } + } + + validateTemplateName() { + if (this.templates.filter(t => t.name === this.applianceToInstall.name).length === 0) { + return true; + } else { + const dialogRef = this.dialog.open(TemplateNameDialogComponent, { + width: '400px', + height: '300px', + autoFocus: false, + disableClose: true + }); + dialogRef.componentInstance.server = this.server; + dialogRef.afterClosed().subscribe((answer: string) => { + if (answer) { + this.applianceToInstall.name = answer; + return true; + } else{ + return false; + } + }); + } } } diff --git a/src/app/components/project-map/new-template-dialog/template-name-dialog/template-name-dialog.component.html b/src/app/components/project-map/new-template-dialog/template-name-dialog/template-name-dialog.component.html new file mode 100644 index 00000000..72de353f --- /dev/null +++ b/src/app/components/project-map/new-template-dialog/template-name-dialog/template-name-dialog.component.html @@ -0,0 +1,26 @@ +

Please enter name for the new template

+
+ + + Template name is required + Template name is incorrect + Template with this name exists + +
+ + +
+
diff --git a/src/app/components/project-map/new-template-dialog/template-name-dialog/template-name-dialog.component.scss b/src/app/components/project-map/new-template-dialog/template-name-dialog/template-name-dialog.component.scss new file mode 100644 index 00000000..acf081db --- /dev/null +++ b/src/app/components/project-map/new-template-dialog/template-name-dialog/template-name-dialog.component.scss @@ -0,0 +1,7 @@ +.file-name-form-field { + width: 100%; +} + +.project-snackbar { + background: #2196F3; +} diff --git a/src/app/components/project-map/new-template-dialog/template-name-dialog/template-name-dialog.component.ts b/src/app/components/project-map/new-template-dialog/template-name-dialog/template-name-dialog.component.ts new file mode 100644 index 00000000..ef1ea9cf --- /dev/null +++ b/src/app/components/project-map/new-template-dialog/template-name-dialog/template-name-dialog.component.ts @@ -0,0 +1,70 @@ +import { Component, OnInit, EventEmitter } from '@angular/core'; +import { Router } from '@angular/router'; +import { MatDialog, MatDialogRef } from '@angular/material/dialog'; +import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms'; +import { Server } from '../../../../models/server'; +import { v4 as uuid } from 'uuid'; +import { ProjectNameValidator } from '../../../projects/models/projectNameValidator'; +import { ToasterService } from '../../../../services/toaster.service'; +import { TemplateService } from '../../../../services/template.service'; +import { templateNameAsyncValidator } from '../../../../validators/template-name-async-validator'; +import { Template } from '../../../../models/template'; + + +@Component({ + selector: 'app-template-name-dialog', + templateUrl: './template-name-dialog.component.html', + styleUrls: ['./template-name-dialog.component.scss'], + providers: [ProjectNameValidator] +}) +export class TemplateNameDialogComponent implements OnInit { + server: Server; + templateNameForm: FormGroup; + + constructor( + public dialogRef: MatDialogRef, + private router: Router, + private dialog: MatDialog, + private toasterService: ToasterService, + private formBuilder: FormBuilder, + private templateNameValidator: ProjectNameValidator, + private templateService: TemplateService + ) {} + + ngOnInit() { + this.templateNameForm = this.formBuilder.group({ + templateName: new FormControl(null, [Validators.required, this.templateNameValidator.get], [templateNameAsyncValidator(this.server, this.templateService)]) + }); + } + + get form() { + return this.templateNameForm.controls; + } + + onAddClick(): void { + if (this.templateNameForm.invalid) { + this.toasterService.error('Please enter correct name for new template'); + return; + } + this.templateService.list(this.server).subscribe((templates: Template[]) => { + const templateName = this.templateNameForm.controls['templateName'].value; + let existingProject = templates.find(t => t.name === templateName); + + if (existingProject) { + this.toasterService.error('Template with this name exists'); + } else { + this.dialogRef.close(this.templateNameForm.controls['templateName'].value); + } + }); + } + + onNoClick(): void { + this.dialogRef.close(); + } + + onKeyDown(event) { + if (event.key === "Enter") { + this.onAddClick(); + } + } +} diff --git a/src/app/validators/template-name-async-validator.ts b/src/app/validators/template-name-async-validator.ts new file mode 100644 index 00000000..160c10da --- /dev/null +++ b/src/app/validators/template-name-async-validator.ts @@ -0,0 +1,14 @@ +import { TemplateService } from '../services/template.service'; +import { FormControl } from '@angular/forms'; +import { timer } from 'rxjs'; +import { map, switchMap } from 'rxjs/operators'; +import { Server } from '../models/server'; + +export const templateNameAsyncValidator = (server: Server, templateService: TemplateService) => { + return (control: FormControl) => { + return timer(500).pipe( + switchMap(() => templateService.list(server)), + map(response => (response.find(n => n.name === control.value) ? {templateExist: true} : null)) + ); + } +}