mirror of
https://github.com/GNS3/gns3-web-ui.git
synced 2025-02-08 04:10:16 +00:00
Option to upload appliance added
This commit is contained in:
parent
d15b4db9fe
commit
25b1edd178
@ -277,6 +277,7 @@ import { WebConsoleFullWindowComponent } from './components/web-console-full-win
|
|||||||
import { ConsoleGuard } from './guards/console-guard';
|
import { ConsoleGuard } from './guards/console-guard';
|
||||||
import { NewTemplateDialogComponent } from './components/project-map/new-template-dialog/new-template-dialog.component';
|
import { NewTemplateDialogComponent } from './components/project-map/new-template-dialog/new-template-dialog.component';
|
||||||
import { ApplianceService } from './services/appliances.service';
|
import { ApplianceService } from './services/appliances.service';
|
||||||
|
import { DataSourceFilter } from './filters/dataSourceFilter';
|
||||||
|
|
||||||
if (environment.production) {
|
if (environment.production) {
|
||||||
Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', {
|
Raven.config('https://b2b1cfd9b043491eb6b566fd8acee358@sentry.io/842726', {
|
||||||
@ -390,6 +391,7 @@ if (environment.production) {
|
|||||||
SearchFilter,
|
SearchFilter,
|
||||||
DateFilter,
|
DateFilter,
|
||||||
NameFilter,
|
NameFilter,
|
||||||
|
DataSourceFilter,
|
||||||
TemplateFilter,
|
TemplateFilter,
|
||||||
ProjectsFilter,
|
ProjectsFilter,
|
||||||
ListOfSnapshotsComponent,
|
ListOfSnapshotsComponent,
|
||||||
|
@ -11,13 +11,14 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<button mat-button matStepperNext>Next</button>
|
<button mat-button matStepperNext>Next</button>
|
||||||
|
<button mat-button (click)="onCloseClick()">Cancel</button>
|
||||||
</div>
|
</div>
|
||||||
</mat-step>
|
</mat-step>
|
||||||
|
|
||||||
<mat-step [stepControl]="secondFormGroup">
|
<mat-step [stepControl]="secondFormGroup">
|
||||||
<ng-template matStepLabel>{{actionTitle}}</ng-template>
|
<ng-template matStepLabel>{{actionTitle}}</ng-template>
|
||||||
|
|
||||||
<mat-card>
|
<mat-card [hidden]="!(action==='install')">
|
||||||
<div class="tableHeader">
|
<div class="tableHeader">
|
||||||
<mat-form-field class="filter-field">
|
<mat-form-field class="filter-field">
|
||||||
<input matInput [(ngModel)]="searchText" placeholder="Filter">
|
<input matInput [(ngModel)]="searchText" placeholder="Filter">
|
||||||
@ -34,8 +35,9 @@
|
|||||||
class="mat-table"
|
class="mat-table"
|
||||||
#table
|
#table
|
||||||
matSort
|
matSort
|
||||||
|
multiTemplateDataRows
|
||||||
(matSortChange)= "sortData($event)"
|
(matSortChange)= "sortData($event)"
|
||||||
[dataSource]="appliances | namefilter: searchText">
|
[dataSource]="dataSource | datasourcefilter: searchText">
|
||||||
<ng-container matColumnDef="name">
|
<ng-container matColumnDef="name">
|
||||||
<mat-header-cell *matHeaderCellDef mat-sort-header> Name </mat-header-cell>
|
<mat-header-cell *matHeaderCellDef mat-sort-header> Name </mat-header-cell>
|
||||||
<mat-cell *matCellDef="let row"> {{row.name}} </mat-cell>
|
<mat-cell *matCellDef="let row"> {{row.name}} </mat-cell>
|
||||||
@ -64,9 +66,42 @@
|
|||||||
</mat-cell>
|
</mat-cell>
|
||||||
</ng-container> -->
|
</ng-container> -->
|
||||||
|
|
||||||
|
<!-- <ng-container matColumnDef="expandedDetail">
|
||||||
|
<mat-cell *matCellDef="let detail">
|
||||||
|
The symbol for {{detail.element}}
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container> -->
|
||||||
|
|
||||||
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
|
||||||
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
|
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
|
||||||
|
|
||||||
|
<!-- <mat-row
|
||||||
|
*matRowDef="let row; columns: displayedColumns;"
|
||||||
|
matRipple
|
||||||
|
class="element-row"
|
||||||
|
[class.expanded]="expandedElement == row"
|
||||||
|
(click)="expandedElement = row">
|
||||||
|
</mat-row>
|
||||||
|
<mat-row
|
||||||
|
*matRowDef="let row; columns: ['expandedDetail']; when: isExpansionDetailRow"
|
||||||
|
[@detailExpand]="row.element == expandedElement ? 'expanded' : 'collapsed'"
|
||||||
|
style="overflow: hidden">
|
||||||
|
</mat-row> -->
|
||||||
</mat-table>
|
</mat-table>
|
||||||
|
|
||||||
|
<mat-paginator [pageSizeOptions]="[5, 10, 20, 50, 100]" showFirstLastButtons></mat-paginator>
|
||||||
|
</mat-card>
|
||||||
|
|
||||||
|
<mat-card [hidden]="action==='install'">
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
accept=".gns3appliance, .gns3a"
|
||||||
|
class="non-visible"
|
||||||
|
#file
|
||||||
|
(change)="addAppliance($event)"
|
||||||
|
ng2FileSelect
|
||||||
|
[uploader]="uploader"/>
|
||||||
|
<button mat-raised-button color="primary" (click)="file.click()">Click to import appliance</button>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
</mat-step>
|
</mat-step>
|
||||||
|
|
||||||
|
@ -9,3 +9,31 @@
|
|||||||
.filter-field {
|
.filter-field {
|
||||||
width: 100%;
|
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;
|
||||||
|
}
|
||||||
|
@ -1,20 +1,32 @@
|
|||||||
import { Component, Input, OnInit, ChangeDetectorRef } from '@angular/core';
|
import { Component, Input, OnInit, ChangeDetectorRef, ViewChild } from '@angular/core';
|
||||||
import { MatDialogRef, Sort } from '@angular/material';
|
import { MatDialogRef, Sort, MatTableDataSource, MatPaginator } from '@angular/material';
|
||||||
import { Server } from '../../../models/server';
|
import { Server } from '../../../models/server';
|
||||||
import { Node } from '../../../cartography/models/node';
|
import { Node } from '../../../cartography/models/node';
|
||||||
import { Project } from '../../../models/project';
|
import { Project } from '../../../models/project';
|
||||||
import { ApplianceService } from '../../../services/appliances.service';
|
import { ApplianceService } from '../../../services/appliances.service';
|
||||||
import { Appliance } from '../../../models/appliance';
|
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({
|
@Component({
|
||||||
selector: 'app-new-template-dialog',
|
selector: 'app-new-template-dialog',
|
||||||
templateUrl: './new-template-dialog.component.html',
|
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 {
|
export class NewTemplateDialogComponent implements OnInit {
|
||||||
@Input() server: Server;
|
@Input() server: Server;
|
||||||
@Input() project: Project;
|
@Input() project: Project;
|
||||||
|
|
||||||
|
uploader: FileUploader;
|
||||||
|
|
||||||
public action: string = 'install';
|
public action: string = 'install';
|
||||||
public actionTitle: string = 'Install appliance from server';
|
public actionTitle: string = 'Install appliance from server';
|
||||||
|
|
||||||
@ -26,10 +38,15 @@ export class NewTemplateDialogComponent implements OnInit {
|
|||||||
public category: string = 'all categories';
|
public category: string = 'all categories';
|
||||||
public displayedColumns: string[] = ['name', 'emulator', 'vendor'];
|
public displayedColumns: string[] = ['name', 'emulator', 'vendor'];
|
||||||
|
|
||||||
|
public dataSource: MatTableDataSource<Appliance>;
|
||||||
|
|
||||||
|
@ViewChild(MatPaginator, {static: true}) paginator: MatPaginator;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public dialogRef: MatDialogRef<NewTemplateDialogComponent>,
|
public dialogRef: MatDialogRef<NewTemplateDialogComponent>,
|
||||||
private applianceService: ApplianceService,
|
private applianceService: ApplianceService,
|
||||||
private changeDetector: ChangeDetectorRef
|
private changeDetector: ChangeDetectorRef,
|
||||||
|
private toasterService: ToasterService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
@ -42,7 +59,49 @@ export class NewTemplateDialogComponent implements OnInit {
|
|||||||
if (appliance.qemu) appliance.emulator = 'Qemu';
|
if (appliance.qemu) appliance.emulator = 'Qemu';
|
||||||
});
|
});
|
||||||
this.allAppliances = appliances;
|
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) {
|
filterAppliances(event) {
|
||||||
@ -55,6 +114,9 @@ export class NewTemplateDialogComponent implements OnInit {
|
|||||||
} else {
|
} else {
|
||||||
this.appliances = temporaryAppliances.filter(t => t.category === this.category);
|
this.appliances = temporaryAppliances.filter(t => t.category === this.category);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.dataSource = new MatTableDataSource(this.appliances);
|
||||||
|
this.dataSource.paginator = this.paginator;
|
||||||
}
|
}
|
||||||
|
|
||||||
setAction(action: string) {
|
setAction(action: string) {
|
||||||
|
17
src/app/filters/dataSourceFilter.ts
Normal file
17
src/app/filters/dataSourceFilter.ts
Normal file
@ -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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -23,10 +23,12 @@ import {
|
|||||||
MatTabsModule,
|
MatTabsModule,
|
||||||
MatTreeModule,
|
MatTreeModule,
|
||||||
MatBottomSheetModule,
|
MatBottomSheetModule,
|
||||||
MatChipsModule
|
MatChipsModule,
|
||||||
|
MatPaginatorModule
|
||||||
} from '@angular/material';
|
} from '@angular/material';
|
||||||
|
|
||||||
export const MATERIAL_IMPORTS = [
|
export const MATERIAL_IMPORTS = [
|
||||||
|
MatPaginatorModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatMenuModule,
|
MatMenuModule,
|
||||||
MatCardModule,
|
MatCardModule,
|
||||||
|
@ -13,4 +13,8 @@ export class ApplianceService {
|
|||||||
getAppliances(server: Server): Observable<Appliance[]> {
|
getAppliances(server: Server): Observable<Appliance[]> {
|
||||||
return this.httpServer.get<Appliance[]>(server, '/appliances') as Observable<Appliance[]>;
|
return this.httpServer.get<Appliance[]>(server, '/appliances') as Observable<Appliance[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getUploadPath(server: Server, emulator: string, filename: string) {
|
||||||
|
return `http://${server.host}:${server.port}/v2/${emulator}/images/${filename}`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user