diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 1bd3d438..b1ea9bda 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -277,6 +277,7 @@ import { WebConsoleFullWindowComponent } from './components/web-console-full-win import { ConsoleGuard } from './guards/console-guard'; import { NewTemplateDialogComponent } from './components/project-map/new-template-dialog/new-template-dialog.component'; import { ApplianceService } from './services/appliances.service'; +import { DataSourceFilter } from './filters/dataSourceFilter'; if (environment.production) { Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', { @@ -390,6 +391,7 @@ if (environment.production) { SearchFilter, DateFilter, NameFilter, + DataSourceFilter, TemplateFilter, ProjectsFilter, ListOfSnapshotsComponent, diff --git a/src/app/components/project-map/new-template-dialog/new-template-dialog.component.html b/src/app/components/project-map/new-template-dialog/new-template-dialog.component.html index 16d157a8..ffb72900 100644 --- a/src/app/components/project-map/new-template-dialog/new-template-dialog.component.html +++ b/src/app/components/project-map/new-template-dialog/new-template-dialog.component.html @@ -11,13 +11,14 @@
+
{{actionTitle}} - +
@@ -34,8 +35,9 @@ class="mat-table" #table matSort + multiTemplateDataRows (matSortChange)= "sortData($event)" - [dataSource]="appliances | namefilter: searchText"> + [dataSource]="dataSource | datasourcefilter: searchText"> Name {{row.name}} @@ -63,10 +65,43 @@ --> + + + + + + + + + + + diff --git a/src/app/components/project-map/new-template-dialog/new-template-dialog.component.scss b/src/app/components/project-map/new-template-dialog/new-template-dialog.component.scss index 3c8b2890..361c88d0 100644 --- a/src/app/components/project-map/new-template-dialog/new-template-dialog.component.scss +++ b/src/app/components/project-map/new-template-dialog/new-template-dialog.component.scss @@ -9,3 +9,31 @@ .filter-field { width: 100%; } + +.example-container { + display: flex; + flex-direction: column; + max-height: 500px; + min-width: 300px; +} + +.mat-table { + overflow: auto; + max-height: 500px; +} + +.element-row { + position: relative; +} + +.element-row:not(.expanded) { + cursor: pointer; +} + +.element-row:not(.expanded):hover { + background: #f5f5f5; +} + +.element-row.expanded { + border-bottom-color: transparent; +} 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 dfcfc9dd..82de2580 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 @@ -1,20 +1,32 @@ -import { Component, Input, OnInit, ChangeDetectorRef } from '@angular/core'; -import { MatDialogRef, Sort } from '@angular/material'; +import { Component, Input, OnInit, ChangeDetectorRef, ViewChild } from '@angular/core'; +import { MatDialogRef, Sort, MatTableDataSource, MatPaginator } from '@angular/material'; import { Server } from '../../../models/server'; import { Node } from '../../../cartography/models/node'; import { Project } from '../../../models/project'; import { ApplianceService } from '../../../services/appliances.service'; import { Appliance } from '../../../models/appliance'; +import { animate, state, style, transition, trigger } from '@angular/animations'; +import { FileUploader, FileItem, ParsedResponseHeaders } from 'ng2-file-upload'; +import { ToasterService } from '../../../services/toaster.service'; @Component({ selector: 'app-new-template-dialog', templateUrl: './new-template-dialog.component.html', - styleUrls: ['./new-template-dialog.component.scss'] + styleUrls: ['./new-template-dialog.component.scss'], + animations: [ + trigger('detailExpand', [ + state('collapsed', style({ height: '0px', minHeight: '0', visibility: 'hidden' })), + state('expanded', style({ height: '*', visibility: 'visible' })), + transition('expanded <=> collapsed', animate('225ms cubic-bezier(0.4, 0.0, 0.2, 1)')), + ]), + ], }) export class NewTemplateDialogComponent implements OnInit { @Input() server: Server; @Input() project: Project; + uploader: FileUploader; + public action: string = 'install'; public actionTitle: string = 'Install appliance from server'; @@ -26,10 +38,15 @@ export class NewTemplateDialogComponent implements OnInit { public category: string = 'all categories'; public displayedColumns: string[] = ['name', 'emulator', 'vendor']; + public dataSource: MatTableDataSource; + + @ViewChild(MatPaginator, {static: true}) paginator: MatPaginator; + constructor( public dialogRef: MatDialogRef, private applianceService: ApplianceService, - private changeDetector: ChangeDetectorRef + private changeDetector: ChangeDetectorRef, + private toasterService: ToasterService ) {} ngOnInit() { @@ -42,7 +59,49 @@ export class NewTemplateDialogComponent implements OnInit { if (appliance.qemu) appliance.emulator = 'Qemu'; }); this.allAppliances = appliances; + this.dataSource = new MatTableDataSource(this.allAppliances); + this.dataSource.paginator = this.paginator; }); + + this.uploader = new FileUploader({}); + this.uploader.onAfterAddingFile = file => { + file.withCredentials = false; + }; + + this.uploader.onErrorItem = (item: FileItem, response: string, status: number, headers: ParsedResponseHeaders) => { + this.toasterService.error('An error has occured'); + }; + + this.uploader.onSuccessItem = (item: FileItem, response: string, status: number, headers: ParsedResponseHeaders) => { + this.toasterService.success('Appliance imported succesfully'); + }; + } + + addAppliance(event): void { + let name = event.target.files[0].name.split('-')[0]; + let fileName = event.target.files[0].name; + let file = event.target.files[0]; + let fileReader: FileReader = new FileReader(); + let emulator; + + fileReader.onloadend = () => { + let appliance = JSON.parse(fileReader.result as string); + + if (appliance.docker) emulator = 'docker'; + if (appliance.dynamips) emulator = 'dynamips'; + if (appliance.iou) emulator = 'iou'; + if (appliance.qemu) emulator = 'qemu'; + + const url = this.applianceService.getUploadPath(this.server, emulator, fileName); + this.uploader.queue.forEach(elem => (elem.url = url)); + + const itemToUpload = this.uploader.queue[0]; + (itemToUpload as any).options.disableMultipart = true; + + this.uploader.uploadItem(itemToUpload); + }; + + fileReader.readAsText(file); } filterAppliances(event) { @@ -55,6 +114,9 @@ export class NewTemplateDialogComponent implements OnInit { } else { this.appliances = temporaryAppliances.filter(t => t.category === this.category); } + + this.dataSource = new MatTableDataSource(this.appliances); + this.dataSource.paginator = this.paginator; } setAction(action: string) { diff --git a/src/app/filters/dataSourceFilter.ts b/src/app/filters/dataSourceFilter.ts new file mode 100644 index 00000000..d5928004 --- /dev/null +++ b/src/app/filters/dataSourceFilter.ts @@ -0,0 +1,17 @@ +import { Pipe, PipeTransform } from '@angular/core'; + + +@Pipe({ + name: 'datasourcefilter' +}) +export class DataSourceFilter implements PipeTransform { + transform(items: any, searchText: string): any[] { + if(!items) return []; + if(!searchText) return items; + + searchText = searchText.toLowerCase(); + return items.filteredData.filter( item => { + return item.name.toLowerCase().includes(searchText); + }); + } +} diff --git a/src/app/material.imports.ts b/src/app/material.imports.ts index 359be1a5..c48a9632 100644 --- a/src/app/material.imports.ts +++ b/src/app/material.imports.ts @@ -23,10 +23,12 @@ import { MatTabsModule, MatTreeModule, MatBottomSheetModule, - MatChipsModule + MatChipsModule, + MatPaginatorModule } from '@angular/material'; export const MATERIAL_IMPORTS = [ + MatPaginatorModule, MatButtonModule, MatMenuModule, MatCardModule, diff --git a/src/app/services/appliances.service.ts b/src/app/services/appliances.service.ts index fe2d761e..803aaa26 100644 --- a/src/app/services/appliances.service.ts +++ b/src/app/services/appliances.service.ts @@ -13,4 +13,8 @@ export class ApplianceService { getAppliances(server: Server): Observable { return this.httpServer.get(server, '/appliances') as Observable; } + + getUploadPath(server: Server, emulator: string, filename: string) { + return `http://${server.host}:${server.port}/v2/${emulator}/images/${filename}`; + } }