Compare commits

..

1 Commits

Author SHA1 Message Date
b510f3dc8f Fix error when taking PNG screenshots 2024-12-23 15:52:17 +07:00
64 changed files with 531 additions and 1069 deletions

View File

@ -1,6 +1,6 @@
{
"name": "gns3-web-ui",
"version": "3.0.4",
"version": "3.1.0.dev1",
"author": {
"name": "GNS3 Technology Inc.",
"email": "developers@gns3.com"

View File

@ -31,8 +31,7 @@ import { ProgressComponent } from './common/progress/progress.component';
import { ProgressService } from './common/progress/progress.service';
import { AdbutlerComponent } from './components/adbutler/adbutler.component';
import { BundledControllerFinderComponent } from './components/bundled-controller-finder/bundled-controller-finder.component';
import { InformationDialogComponent } from './components/dialogs/information-dialog/information-dialog.component';
import { QuestionDialogComponent } from "./components/dialogs/question-dialog/question-dialog.component";
import { InformationDialogComponent } from './components/dialogs/information-dialog.component';
import { DirectLinkComponent } from './components/direct-link/direct-link.component';
import { DrawingAddedComponent } from './components/drawings-listeners/drawing-added/drawing-added.component';
import { DrawingDraggedComponent } from './components/drawings-listeners/drawing-dragged/drawing-dragged.component';
@ -119,8 +118,6 @@ import { EditTextActionComponent } from './components/project-map/context-menu/a
import { ExportConfigActionComponent } from './components/project-map/context-menu/actions/export-config/export-config-action.component';
import { HttpConsoleNewTabActionComponent } from './components/project-map/context-menu/actions/http-console-new-tab/http-console-new-tab-action.component';
import { HttpConsoleActionComponent } from './components/project-map/context-menu/actions/http-console/http-console-action.component';
import { IdlePcActionComponent } from "./components/project-map/context-menu/actions/idle-pc-action/idle-pc-action.component";
import { AutoIdlePcActionComponent } from "./components/project-map/context-menu/actions/auto-idle-pc-action/auto-idle-pc-action.component";
import { ImportConfigActionComponent } from './components/project-map/context-menu/actions/import-config/import-config-action.component';
import { LockActionComponent } from './components/project-map/context-menu/actions/lock-action/lock-action.component';
import { MoveLayerDownActionComponent } from './components/project-map/context-menu/actions/move-layer-down-action/move-layer-down-action.component';
@ -136,12 +133,11 @@ import { StartNodeActionComponent } from './components/project-map/context-menu/
import { StopCaptureActionComponent } from './components/project-map/context-menu/actions/stop-capture/stop-capture-action.component';
import { IsolateNodeActionComponent } from './components/project-map/context-menu/actions/isolate-node-action/isolate-node-action.component';
import { UnisolateNodeActionComponent } from './components/project-map/context-menu/actions/unisolate-node-action/unisolate-node-action.component';
import { StopNodeActionComponent } from './components/project-map/context-menu/actions/stop-node-action/stop-node-action.component';
import {StopNodeActionComponent } from './components/project-map/context-menu/actions/stop-node-action/stop-node-action.component';
import { SuspendLinkActionComponent } from './components/project-map/context-menu/actions/suspend-link/suspend-link-action.component';
import { SuspendNodeActionComponent } from './components/project-map/context-menu/actions/suspend-node-action/suspend-node-action.component';
import { ContextMenuComponent } from './components/project-map/context-menu/context-menu.component';
import { ConfigDialogComponent } from './components/project-map/context-menu/dialogs/config-dialog/config-dialog.component';
import { IdlePCDialogComponent } from "./components/project-map/context-menu/dialogs/idle-pc-dialog/idle-pc-dialog.component";
import { DrawLinkToolComponent } from './components/project-map/draw-link-tool/draw-link-tool.component';
import { StyleEditorDialogComponent } from './components/project-map/drawings-editors/style-editor/style-editor.component';
import { LinkStyleEditorDialogComponent } from './components/project-map/drawings-editors/link-style-editor/link-style-editor.component';
@ -497,7 +493,6 @@ import { DeleteResourceConfirmationDialogComponent } from './components/resource
AlignVerticallyActionComponent,
ConfirmationBottomSheetComponent,
ConfigDialogComponent,
IdlePCDialogComponent,
ImportApplianceComponent,
DirectLinkComponent,
SystemStatusComponent,
@ -505,8 +500,6 @@ import { DeleteResourceConfirmationDialogComponent } from './components/resource
StatusChartComponent,
OpenFileExplorerActionComponent,
HttpConsoleActionComponent,
IdlePcActionComponent,
AutoIdlePcActionComponent,
WebConsoleComponent,
ConsoleWrapperComponent,
HttpConsoleNewTabActionComponent,
@ -518,7 +511,6 @@ import { DeleteResourceConfirmationDialogComponent } from './components/resource
ReadmeEditorComponent,
MarkedDirective,
InformationDialogComponent,
QuestionDialogComponent,
TemplateNameDialogComponent,
ConfigureCustomAdaptersDialogComponent,
EditNetworkConfigurationDialogComponent,

View File

@ -27,7 +27,6 @@ export class MapNodeToNodeConverter implements Converter<MapNode, Node> {
node.port_name_format = mapNode.portNameFormat;
node.port_segment_size = mapNode.portSegmentSize;
node.ports = mapNode.ports ? mapNode.ports.map((mapPort) => this.mapPortToPort.convert(mapPort)) : [];
node.properties = mapNode.properties;
node.project_id = mapNode.projectId;
node.status = mapNode.status;
node.symbol = mapNode.symbol;

View File

@ -38,7 +38,6 @@ export class NodeToMapNodeConverter implements Converter<Node, MapNode> {
mapNode.portNameFormat = node.port_name_format;
mapNode.portSegmentSize = node.port_segment_size;
mapNode.ports = node.ports ? node.ports.map((port) => this.portToMapPort.convert(port)) : [];
mapNode.properties = node.properties;
mapNode.projectId = node.project_id;
mapNode.status = node.status;
mapNode.symbol = node.symbol;

View File

@ -1,7 +1,6 @@
import { Indexed } from '../../datasources/map-datasource';
import { MapLabel } from './map-label';
import { MapPort } from './map-port';
import { Properties } from '../node';
export class MapNode implements Indexed {
id: string;
@ -20,7 +19,6 @@ export class MapNode implements Indexed {
portNameFormat: string;
portSegmentSize: number;
ports: MapPort[];
properties: Properties;
projectId: string;
status: string;
symbol: string;

View File

@ -14,15 +14,10 @@ export class Properties {
headless: boolean;
linked_clone: boolean;
on_close: string;
aux: number;
aux_type: boolean;
aux: number;
ram: number;
system_id: string;
dynamips_id?: number;
npe?: string;
midplane?: string;
nvram: number;
image: string;
usage: string;
use_any_adapter: boolean;
vmname: string;
@ -53,40 +48,16 @@ export class Properties {
kernel_image: string;
kernel_image_md5sum?: any;
mac_address: string;
mac_addr: string;
options: string;
platform: string;
chassis?: string;
iomem?: number;
disk0: number;
disk1: number;
idlepc: string;
idlemax: number;
idlesleep: number;
exec_area: number;
mmap: boolean;
sparsemem: boolean;
auto_delete_disks: boolean;
process_priority: string;
qemu_path: string;
environment: string;
extra_hosts: string;
start_command: string;
replicate_network_connection_state: boolean;
memory: number;
tpm: boolean;
uefi: boolean;
slot0?: string;
slot1?: string;
slot2?: string;
slot3?: string;
slot4?: string;
slot5?: string;
slot6?: string;
slot7?: string;
wic0?: string;
wic1?: string;
wic2?: string;
}
export class Node {

View File

@ -1,8 +0,0 @@
<h1 mat-dialog-title>{{ data.title }}</h1>
<div mat-dialog-content>
<p>{{ data.question }}</p>
</div>
<div mat-dialog-actions>
<button mat-button (click)="onNoClick()">No</button>
<button mat-button (click)="onYesClick()">Yes</button>
</div>

View File

@ -1,21 +0,0 @@
import { Component, Inject } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
@Component({
selector: 'app-question-dialog',
templateUrl: './question-dialog.component.html',
})
export class QuestionDialogComponent {
constructor(
public dialogRef: MatDialogRef<QuestionDialogComponent>,
@Inject(MAT_DIALOG_DATA) public data: { title: string, question: string}
) {}
onNoClick(): void {
this.dialogRef.close(false);
}
onYesClick(): void {
this.dialogRef.close(true);
}
}

View File

@ -79,7 +79,6 @@ describe('LinkCreatedComponent', () => {
portNameFormat: 'samplePortNameFormat',
portSegmentSize: 0,
ports: [],
properties: undefined,
projectId: 'sampleProjectId',
status: 'sampleStatus',
symbol: 'sampleSymbol',

View File

@ -59,7 +59,6 @@ describe('NodeDraggedComponent', () => {
portNameFormat: 'samplePortNameFormat',
portSegmentSize: 0,
ports: [],
properties: undefined,
projectId: 'sampleProjectId',
status: 'sampleStatus',
symbol: 'sampleSymbol',

View File

@ -1,19 +1,19 @@
<div class="content">
<div class="default-header">
<div class="row">
<h1 class="col">Image Manager</h1>
<button mat-raised-button color="primary" (click)="installAllImages()" class="install-all-button">
<mat-icon>done_all</mat-icon> Install all images
</button>
<button mat-raised-button color="primary" (click)="pruneImages()" class="prune-button">
<mat-icon>delete</mat-icon> Prune Images
</button>
<button mat-raised-button color="primary" (click)="addImageDialog()" class="add-button">
<mat-icon>add</mat-icon> Add Image
</button>
<div class="col-md-9">
<h1>Image Manager</h1>
</div>
<div class="col-md-3 btn-box">
<button class="img-btn" mat-button
(click)="addImageDialog()">
<mat-icon>add</mat-icon> Add Image
</button>
</div>
</div>
</div>
</div>
<div class="default-content">
<app-controller-discovery></app-controller-discovery>
@ -49,9 +49,9 @@
<mat-header-cell *matHeaderCellDef> Image Size </mat-header-cell>
<mat-cell *matCellDef="let row"> {{ (row.image_size/1000000).toFixed()}} MB </mat-cell>
</ng-container>
<ng-container matColumnDef="delete" >
<mat-header-cell *matHeaderCellDef>
<mat-header-cell *matHeaderCellDef>
<button mat-button *ngIf="(selection.hasValue() && isAllSelected()) || selection.selected.length > 1" (click)="deleteAllFiles()" aria-label="Example icon button with a delete icon">
<mat-icon>delete</mat-icon>
</button>
@ -67,4 +67,4 @@
</mat-table>
</div>
</div>
</div>
</div>

View File

@ -2,22 +2,12 @@
display: none;
}
.install-all-button {
height: 40px;
width: 160px;
margin: 20px;
}
.prune-button {
height: 40px;
width: 160px;
margin: 20px;
}
.add-button {
height: 40px;
width: 160px;
margin: 20px;
.img-btn{
margin: auto;
}
.btn-box{
display: flex;
margin-top: 10px;
}
mat-header-cell, mat-cell {
@ -31,3 +21,4 @@ mat-cell, mat-header-cell, mat-footer-cell {
min-height: inherit;
}

View File

@ -6,13 +6,12 @@ import { ProgressService } from 'app/common/progress/progress.service';
import { Image } from '../../models/images';
import { Controller } from '../../models/controller';
import { ImageManagerService } from "../../services/image-manager.service";
import { SelectionModel } from '@angular/cdk/collections';
import { DataSource, SelectionModel } from '@angular/cdk/collections';
import { AddImageDialogComponent } from './add-image-dialog/add-image-dialog.component';
import { MatDialog } from '@angular/material/dialog';
import { ToasterService } from '../../services/toaster.service';
import { DeleteAllImageFilesDialogComponent } from './deleteallfiles-dialog/deleteallfiles-dialog.component';
import { imageDataSource, imageDatabase } from "./image-database-file";
import { QuestionDialogComponent } from "@components/dialogs/question-dialog/question-dialog.component";
@Component({
selector: 'app-image-manager',
@ -37,6 +36,7 @@ export class ImageManagerComponent implements OnInit {
private versionService: VersionService,
private dialog: MatDialog,
private toasterService: ToasterService,
) { }
ngOnInit(): void {
@ -60,6 +60,7 @@ export class ImageManagerComponent implements OnInit {
},
(error) => {
this.toasterService.error(error.error.message)
}
);
}
@ -99,49 +100,6 @@ export class ImageManagerComponent implements OnInit {
this.isAllDelete = true;
}
installAllImages() {
const dialogRef = this.dialog.open(QuestionDialogComponent, {
width: '450px',
data: { title: 'Install all images', question: 'This will attempt to automatically create templates based on image checksums. Continue?'}
});
dialogRef.afterClosed().subscribe((result: boolean) => {
if (result) {
this.imageService.installImages(this.controller).subscribe(() => {
this.toasterService.success('Images installed');
},
(error) => {
this.toasterService.error(error.error.message)
}
);
}
});
}
pruneImages() {
const dialogRef = this.dialog.open(QuestionDialogComponent, {
width: '450px',
data: { title: 'Prune images', question: 'Delete all images not used by a template? This cannot be reverted.'}
});
dialogRef.afterClosed().subscribe((result: boolean) => {
if (result) {
this.imageService.pruneImages(this.controller).subscribe(
() => {
this.getImages()
this.unChecked()
this.toasterService.success('Images pruned');
},
(error) => {
this.getImages()
this.unChecked()
this.toasterService.error(error.error.message)
}
);
}
});
}
public addImageDialog() {
const dialogRef = this.dialog.open(AddImageDialogComponent, {
width: '600px',

View File

@ -1,7 +1,6 @@
import { Component, Input, OnInit } from '@angular/core';
import { PortsMappingEntity } from '../../../../models/ethernetHub/ports-mapping-enity';
import { BuiltInTemplatesConfigurationService } from '../../../../services/built-in-templates-configuration.service';
import { ToasterService } from "@services/toaster.service";
@Component({
selector: 'app-ports',
@ -13,23 +12,16 @@ export class PortsComponent implements OnInit {
newPort: PortsMappingEntity = {
name: '',
port_number: 0,
vlan: 1,
type: 'access',
ethertype: '0x8100',
};
portTypes: string[] = [];
etherTypes: string[] = [];
displayedColumns: string[] = ['port_number', 'vlan', 'type', 'ethertype', 'action'];
constructor(
private builtInTemplatesConfigurationService: BuiltInTemplatesConfigurationService,
private toasterService: ToasterService,
) {}
constructor(private builtInTemplatesConfigurationService: BuiltInTemplatesConfigurationService) {}
ngOnInit() {
this.getConfiguration();
this.newPort.port_number = this.ethernetPorts.length;
}
getConfiguration() {
@ -38,18 +30,13 @@ export class PortsComponent implements OnInit {
}
onAdd() {
this.newPort.name = "Ethernet" + this.newPort.port_number;
this.ethernetPorts.push(this.newPort);
const portExists = this.ethernetPorts.some(p => p.port_number === this.newPort.port_number);
if (portExists) {
this.toasterService.error(`Port number ${this.newPort.port_number} already exists.`);
return;
}
const port: PortsMappingEntity = { ...this.newPort };
port.name = "Ethernet" + port.port_number;
this.ethernetPorts.push(port);
this.ethernetPorts = [].concat(this.ethernetPorts); // this forces the refresh of the table
this.newPort.port_number = this.ethernetPorts.length;
this.newPort = {
name: '',
port_number: 0,
};
}
delete(port: PortsMappingEntity) {

View File

@ -109,24 +109,32 @@
</mat-step>
<mat-step label="Network adapters">
<div *ngIf="iosNameForm.get('platform').value">
<div *ngFor="let index of [0, 1, 2, 3, 4, 5, 6]">
<!-- <div *ngIf="chassis[iosNameForm.get('platform').value]">
<div *ngFor="let index of [0,1,2,3,4,5,6,7]">
<mat-select
placeholder="Slot {{index}}"
[(ngModel)]="networkAdaptersForTemplate[index]"
[ngModelOptions]="{standalone: true}"
*ngIf="networkAdapters[iosNameForm.get('chassis').value] && networkAdapters[iosNameForm.get('chassis').value][index]">
<mat-option *ngFor="let option of networkAdapters[iosNameForm.get('chassis').value][index]" [value]="option">
{{option}}
</mat-option>
</mat-select>
</div>
</div> -->
<div *ngIf="selectedPlatform">
<div *ngFor="let index of [0, 1, 2, 3, 4, 5, 6, 7]">
<mat-select
placeholder="Slot {{ index }}"
[(ngModel)]="networkAdaptersForTemplate[index]"
[ngModelOptions]="{ standalone: true }"
*ngIf="adapterMatrix[iosNameForm.get('platform').value][iosNameForm.get('chassis').value || '']
&& adapterMatrix[iosNameForm.get('platform').value][iosNameForm.get('chassis').value || ''][index]"
*ngIf="
networkAdaptersForPlatform[iosNameForm.get('platform').value] &&
networkAdaptersForPlatform[iosNameForm.get('platform').value][index]
"
>
<mat-option
*ngIf="adapterMatrix[iosNameForm.get('platform').value][iosNameForm.get('chassis').value|| ''][index].length > 1 &&
!adapterMatrix[iosNameForm.get('platform').value][iosNameForm.get('chassis').value|| ''][index][0].startsWith('C7200')"
[value]=""
>
{{ "" }}
</mat-option>
<mat-option
*ngFor="let option of adapterMatrix[iosNameForm.get('platform').value][iosNameForm.get('chassis').value || ''][index]"
*ngFor="let option of networkAdaptersForPlatform[iosNameForm.get('platform').value][index]"
[value]="option"
>
{{ option }}
@ -136,33 +144,33 @@
</div>
</mat-step>
<mat-step label="WIC modules" *ngIf="iosNameForm.get('platform').value && wicMatrix[iosNameForm.get('platform').value]">
<div *ngFor="let index of [0, 1, 2]">
<mat-select
placeholder="WIC {{ index }}"
[(ngModel)]="wicsForTemplate[index]"
[ngModelOptions]="{ standalone: true }"
*ngIf="wicMatrix[iosNameForm.get('platform').value] && wicMatrix[iosNameForm.get('platform').value][index]"
>
<mat-option [value]="">
{{ "" }}
</mat-option>
<mat-option
*ngFor="let option of wicMatrix[iosNameForm.get('platform').value][index]"
[value]="option"
<mat-step label="WIC modules">
<div *ngIf="iosNameForm.get('platform').value">
<div *ngFor="let index of [0, 1, 2, 3]">
<mat-select
placeholder="WIC {{ index }}"
[(ngModel)]="networkModulesForTemplate[index]"
[ngModelOptions]="{ standalone: true }"
*ngIf="
networkModules[iosNameForm.get('platform').value] &&
networkModules[iosNameForm.get('platform').value][index]
"
>
{{ option }}
</mat-option>
</mat-select>
<mat-option
*ngFor="let option of networkModules[iosNameForm.get('platform').value][index]"
[value]="option"
>
{{ option }}
</mat-option>
</mat-select>
</div>
</div>
</mat-step>
<mat-step label="Idle-PC">
<form [formGroup]="iosIdlePCForm">
<mat-form-field class="form-field">
<input matInput type="text" formControlName="idlepc" [(ngModel)]="iosTemplate.idlepc" placeholder="Idle-PC" />
</mat-form-field>
</form>
<mat-form-field class="form-field">
<input matInput type="text" [(ngModel)]="iosTemplate.idlepc" placeholder="Idle-PC" />
</mat-form-field>
</mat-step>
</mat-vertical-stepper>
</div>

View File

@ -32,8 +32,11 @@ export class AddIosTemplateComponent implements OnInit, OnDestroy {
iosImageForm: UntypedFormGroup;
iosNameForm: UntypedFormGroup;
iosMemoryForm: UntypedFormGroup;
iosIdlePCForm: UntypedFormGroup;
selectedPlatform: string;
networkAdaptersForTemplate: string[] = [];
networkModulesForTemplate: string[] = [];
iosImages: IosImage[] = [];
platforms: string[] = [];
platformsWithEtherSwitchRouterOption = {};
@ -41,10 +44,9 @@ export class AddIosTemplateComponent implements OnInit, OnDestroy {
chassis = {};
defaultRam = {};
defaultNvram = {};
networkAdaptersForTemplate: string[] = [];
wicsForTemplate: string[] = [];
adapterMatrix = {};
wicMatrix = {};
networkAdapters = {};
networkAdaptersForPlatform = {};
networkModules = {};
ciscoUrl: string = 'https://cfn.cloudapps.cisco.com/ITDIT/CFN/jsp/SearchBySoftware.jsp';
uploader: FileUploader;
@ -80,10 +82,6 @@ export class AddIosTemplateComponent implements OnInit, OnDestroy {
this.iosMemoryForm = this.formBuilder.group({
memory: new UntypedFormControl(null, [Validators.required]),
});
this.iosIdlePCForm = this.formBuilder.group({
idlepc: new UntypedFormControl(null, [Validators.pattern(this.iosConfigurationService.getIdlepcRegex())]),
});
}
ngOnInit() {
@ -122,31 +120,19 @@ export class AddIosTemplateComponent implements OnInit, OnDestroy {
this.templateMocksService.getIosTemplate().subscribe((iosTemplate: IosTemplate) => {
this.iosTemplate = iosTemplate;
this.networkModules = this.iosConfigurationService.getNetworkModules();
this.networkAdaptersForPlatform = this.iosConfigurationService.getNetworkAdaptersForPlatform();
this.networkAdapters = this.iosConfigurationService.getNetworkAdapters();
this.platforms = this.iosConfigurationService.getAvailablePlatforms();
this.platformsWithEtherSwitchRouterOption = this.iosConfigurationService.getPlatformsWithEtherSwitchRouterOption();
this.platformsWithChassis = this.iosConfigurationService.getPlatformsWithChassis();
this.chassis = this.iosConfigurationService.getChassis();
this.defaultRam = this.iosConfigurationService.getDefaultRamSettings();
this.adapterMatrix = this.iosConfigurationService.getAdapterMatrix();
this.wicMatrix = this.iosConfigurationService.getWicMatrix();
});
});
}
fillDefaultSlots() {
console.log("Fill default slots");
if (this.iosNameForm.get('platform').value) {
for (let i = 0; i <= 6; i++) {
let adapters = this.adapterMatrix[this.iosNameForm.get('platform').value][this.iosNameForm.get('chassis').value || ''][i];
if (adapters && (adapters.length === 1 || adapters[0].startsWith('C7200'))) {
console.log("Set default adapter for slot" + i + " to " + adapters[0]);
this.networkAdaptersForTemplate[i] = adapters[0];
}
}
}
}
setControllerType(controllerType: string) {
if (controllerType === 'local') {
this.isLocalComputerChosen = true;
@ -180,7 +166,6 @@ export class AddIosTemplateComponent implements OnInit, OnDestroy {
if (
!this.iosImageForm.invalid &&
!this.iosMemoryForm.invalid &&
!this.iosIdlePCForm.invalid &&
this.iosNameForm.get('templateName').value &&
this.iosNameForm.get('platform').value
) {
@ -199,7 +184,7 @@ export class AddIosTemplateComponent implements OnInit, OnDestroy {
}
if (this.networkAdaptersForTemplate.length > 0) this.completeAdaptersData();
if (this.wicsForTemplate.length > 0) this.completeWicsData();
if (this.networkModulesForTemplate.length > 0) this.completeModulesData();
this.iosTemplate.compute_id = 'local';
this.iosService.addTemplate(this.controller, this.iosTemplate).subscribe((template: IosTemplate) => {
@ -211,25 +196,44 @@ export class AddIosTemplateComponent implements OnInit, OnDestroy {
}
completeAdaptersData() {
for (let i = 0; i <= 6; i++) {
if (this.adapterMatrix[this.iosTemplate.platform][this.iosTemplate.chassis || ''][i]) {
if (this.networkAdaptersForTemplate[i] === undefined)
this.iosTemplate[`slot${i}`] = ""
else
this.iosTemplate[`slot${i}`] = this.networkAdaptersForTemplate[i];
if (this.chassis[this.iosTemplate.platform]) {
if (Object.keys(this.networkAdapters[this.iosTemplate.chassis])) {
for (let i = 0; i < Object.keys(this.networkAdapters[this.iosTemplate.chassis]).length; i++) {
if (!this.networkAdaptersForTemplate[i]) this.networkAdaptersForTemplate[i] = '';
}
}
} else {
if (this.networkAdaptersForPlatform[this.iosNameForm.get('platform').value]) {
for (
let i = 0;
i < Object.keys(this.networkAdaptersForPlatform[this.iosNameForm.get('platform').value]).length;
i++
) {
if (!this.networkAdaptersForTemplate[i]) this.networkAdaptersForTemplate[i] = '';
}
}
}
if (this.networkAdaptersForTemplate[0]) this.iosTemplate.slot0 = this.networkAdaptersForTemplate[0];
if (this.networkAdaptersForTemplate[1]) this.iosTemplate.slot1 = this.networkAdaptersForTemplate[1];
if (this.networkAdaptersForTemplate[2]) this.iosTemplate.slot2 = this.networkAdaptersForTemplate[2];
if (this.networkAdaptersForTemplate[3]) this.iosTemplate.slot3 = this.networkAdaptersForTemplate[3];
if (this.networkAdaptersForTemplate[4]) this.iosTemplate.slot4 = this.networkAdaptersForTemplate[4];
if (this.networkAdaptersForTemplate[5]) this.iosTemplate.slot5 = this.networkAdaptersForTemplate[5];
if (this.networkAdaptersForTemplate[6]) this.iosTemplate.slot6 = this.networkAdaptersForTemplate[6];
if (this.networkAdaptersForTemplate[7]) this.iosTemplate.slot7 = this.networkAdaptersForTemplate[7];
}
completeWicsData() {
for (let i = 0; i <= 3; i++) {
if (this.wicMatrix[this.iosTemplate.platform][i]) {
if (this.wicsForTemplate[i] === undefined)
this.iosTemplate[`wic${i}`] = ""
else
this.iosTemplate[`wic${i}`] = this.wicsForTemplate[i];
completeModulesData() {
if (Object.keys(this.networkModules[this.iosTemplate.platform])) {
for (let i = 0; i < Object.keys(this.networkModules[this.iosTemplate.platform]).length; i++) {
if (!this.networkModulesForTemplate[i]) this.networkModulesForTemplate[i] = '';
}
}
if (this.networkModulesForTemplate[0]) this.iosTemplate.wic0 = this.networkModulesForTemplate[0];
if (this.networkModulesForTemplate[1]) this.iosTemplate.wic1 = this.networkModulesForTemplate[1];
if (this.networkModulesForTemplate[2]) this.iosTemplate.wic2 = this.networkModulesForTemplate[2];
}
goBack() {
@ -248,31 +252,25 @@ export class AddIosTemplateComponent implements OnInit, OnDestroy {
this.selectedPlatform = name;
}
if (name === 'c3620' || name === 'c3640' || name === 'c3660')
this.iosNameForm.controls['chassis'].setValue(name.substring(1));
else if (name === 'c1700') {
this.iosNameForm.controls['chassis'].setValue('1760');
if (name === 'c1700') {
this.iosNameForm.controls['chassis'].setValue('1720');
} else if (name === 'c2600') {
this.iosNameForm.controls['chassis'].setValue('2651XM');
this.iosNameForm.controls['chassis'].setValue('2610');
} else {
this.iosNameForm.controls['chassis'].setValue('');
}
this.iosMemoryForm.controls['memory'].setValue(this.defaultRam[this.selectedPlatform]);
this.fillDefaultSlots();
this.iosMemoryForm.controls['memory'].setValue(this.defaultRam[name]);
}
onPlatformChosen() {
this.iosTemplate.chassis = '';
this.networkAdaptersForTemplate = [];
this.wicsForTemplate = [];
if (!this.chassis[this.iosNameForm.get('platform').value])
this.fillDefaultSlots();
this.networkModulesForTemplate = [];
}
onChassisChosen() {
this.networkAdaptersForTemplate = [];
if (this.chassis[this.iosNameForm.get('platform').value])
this.fillDefaultSlots();
}
cancelUploading() {

View File

@ -169,47 +169,60 @@
<mat-panel-title> Slots </mat-panel-title>
</mat-expansion-panel-header>
<h6>Adapters</h6>
<div *ngFor="let index of [0, 1, 2, 3, 4, 5, 6]">
<mat-select
placeholder="Slot {{ index }}"
[(ngModel)]="networkAdaptersForTemplate[index]"
[ngModelOptions]="{ standalone: true }"
*ngIf="adapterMatrix[iosTemplate.platform][iosTemplate.chassis || ''][index]"
>
<mat-option
*ngIf="adapterMatrix[iosTemplate.platform][iosTemplate.chassis || ''][index].length > 1 &&
!adapterMatrix[iosTemplate.platform][iosTemplate.chassis || ''][index][0].startsWith('C7200')"
[value]=""
>
{{ "" }}
</mat-option>
<mat-option
*ngFor="let option of adapterMatrix[iosTemplate.platform][iosTemplate.chassis || ''][index]"
[value]="option"
>
{{ option }}
</mat-option>
</mat-select>
</div>
<br />
<div *ngIf="wicMatrix[iosTemplate.platform]">
<h6>WICs</h6>
<div *ngFor="let index of [0, 1, 2]">
<div *ngIf="iosTemplate.chassis && chassis[iosTemplate.platform]">
<div *ngFor="let index of [0, 1, 2, 3, 4, 5, 6, 7]">
<mat-select
placeholder="WIC {{ index }}"
[(ngModel)]="wicsForTemplate[index]"
placeholder="Slot {{ index }}"
[(ngModel)]="networkAdaptersForTemplate[index]"
[ngModelOptions]="{ standalone: true }"
*ngIf="wicMatrix[iosTemplate.platform][index]"
*ngIf="networkAdapters[iosTemplate.chassis][index]"
>
<mat-option [value]="">
{{ "" }}
</mat-option>
<mat-option *ngFor="let option of wicMatrix[iosTemplate.platform][index]" [value]="option">
<mat-option *ngFor="let option of networkAdapters[iosTemplate.chassis][index]" [value]="option">
{{ option }}
</mat-option>
</mat-select>
</div>
</div>
<div *ngIf="iosTemplate.platform && !chassis[iosTemplate.platform]">
<div *ngFor="let index of [0, 1, 2, 3, 4, 5, 6, 7]">
<mat-select
placeholder="Slot {{ index }}"
[(ngModel)]="networkAdaptersForTemplate[index]"
[ngModelOptions]="{ standalone: true }"
*ngIf="networkAdaptersForPlatform[iosTemplate.platform][index]"
>
<mat-option
*ngFor="let option of networkAdaptersForPlatform[iosTemplate.platform][index]"
[value]="option"
>
{{ option }}
</mat-option>
</mat-select>
</div>
</div>
<br /><br />
<h6>WICs</h6>
<div *ngIf="iosTemplate.wic0 || iosTemplate.wic0 === ''">
<mat-select placeholder="WIC 0" [(ngModel)]="iosTemplate.wic0" [ngModelOptions]="{ standalone: true }">
<mat-option *ngFor="let option of networkModules[iosTemplate.platform][0]" [value]="option">
{{ option }}
</mat-option>
</mat-select>
</div>
<div *ngIf="iosTemplate.wic1 || iosTemplate.wic1 === ''">
<mat-select placeholder="WIC 1" [(ngModel)]="iosTemplate.wic1" [ngModelOptions]="{ standalone: true }">
<mat-option *ngFor="let option of networkModules[iosTemplate.platform][1]" [value]="option">
{{ option }}
</mat-option>
</mat-select>
</div>
<div *ngIf="iosTemplate.wic2 || iosTemplate.wic2 === ''">
<mat-select placeholder="WIC 2" [(ngModel)]="iosTemplate.wic2" [ngModelOptions]="{ standalone: true }">
<mat-option *ngFor="let option of networkModules[iosTemplate.platform][2]" [value]="option">
{{ option }}
</mat-option>
</mat-select>
</div>
</mat-expansion-panel>
<mat-expansion-panel>
<mat-expansion-panel-header>
@ -227,8 +240,8 @@
</mat-form-field>
<mat-form-field class="form-field">
<input
[ngModelOptions]="{ standalone: true }"
matInput
formControlName="mac_addr"
type="text"
[(ngModel)]="iosTemplate.mac_addr"
placeholder="Base MAC"
@ -236,14 +249,13 @@
</mat-form-field>
<mat-form-field class="form-field">
<input
[ngModelOptions]="{ standalone: true }"
matInput
formControlName="idlepc"
type="text"
[(ngModel)]="iosTemplate.idlepc"
placeholder="Idle-PC"
/>
</mat-form-field>
<button mat-button class="idlePCFinderButton" (click)="findIdlePC()">Idle-PC finder</button><br /><br />
<mat-form-field class="form-field">
<input
matInput

View File

@ -1,3 +0,0 @@
.idlePCFinderButton {
width: 100%;
}

View File

@ -20,8 +20,6 @@ import { ToasterService } from '../../../../services/toaster.service';
import { MockedToasterService } from '../../../../services/toaster.service.spec';
import { MockedActivatedRoute } from '../../preferences.component.spec';
import { IosTemplateDetailsComponent } from './ios-template-details.component';
import { MockedProgressService } from "@components/project-map/project-map.component.spec";
import {ProgressService} from "../../../../common/progress/progress.service";
export class MockedIosService {
public getTemplate(controller:Controller , template_id: string) {
@ -40,7 +38,6 @@ describe('IosTemplateDetailsComponent', () => {
let mockedControllerService = new MockedControllerService();
let mockedIosService = new MockedIosService();
let mockedToasterService = new MockedToasterService();
let mockedProgressService = new MockedProgressService()
let activatedRoute = new MockedActivatedRoute().get();
beforeEach(async() => {
@ -64,7 +61,6 @@ describe('IosTemplateDetailsComponent', () => {
{ provide: ControllerService, useValue: mockedControllerService },
{ provide: IosService, useValue: mockedIosService },
{ provide: ToasterService, useValue: mockedToasterService },
{ provide: ProgressService, useValue: mockedProgressService },
{ provide: IosConfigurationService, useClass: IosConfigurationService },
],
declarations: [IosTemplateDetailsComponent],
@ -98,10 +94,9 @@ describe('IosTemplateDetailsComponent', () => {
component.advancedForm.controls['idlemax'].setValue('0');
component.advancedForm.controls['idlesleep'].setValue('0');
component.advancedForm.controls['execarea'].setValue('0');
component.advancedForm.controls['idlepc'].setValue('0x0');
component.advancedForm.controls['mac_addr'].setValue('');
spyOn(component, 'saveSlotsData').and.returnValue();
component.onSave();
expect(mockedIosService.saveTemplate).toHaveBeenCalled();
});
});

View File

@ -7,7 +7,6 @@ import { IosConfigurationService } from '../../../../services/ios-configuration.
import { IosService } from '../../../../services/ios.service';
import { ControllerService } from '../../../../services/controller.service';
import { ToasterService } from '../../../../services/toaster.service';
import { ProgressService } from "../../../../common/progress/progress.service";
@Component({
selector: 'app-ios-template-details',
@ -15,9 +14,12 @@ import { ProgressService } from "../../../../common/progress/progress.service";
styleUrls: ['./ios-template-details.component.scss', '../../preferences.component.scss'],
})
export class IosTemplateDetailsComponent implements OnInit {
controller: Controller;
controller:Controller ;
iosTemplate: IosTemplate;
isSymbolSelectionOpened: boolean = false;
networkAdaptersForTemplate: string[] = [];
platforms: string[] = [];
consoleTypes: string[] = [];
categories = [];
@ -26,10 +28,9 @@ export class IosTemplateDetailsComponent implements OnInit {
chassis = {};
defaultRam = {};
defaultNvram = {};
networkAdaptersForTemplate: string[] = [];
wicsForTemplate: string[] = [];
adapterMatrix = {};
wicMatrix = {};
networkAdapters = {};
networkAdaptersForPlatform = {};
networkModules = {};
generalSettingsForm: UntypedFormGroup;
memoryForm: UntypedFormGroup;
@ -42,7 +43,6 @@ export class IosTemplateDetailsComponent implements OnInit {
private toasterService: ToasterService,
private formBuilder: UntypedFormBuilder,
private iosConfigurationService: IosConfigurationService,
private progressService: ProgressService,
private router: Router
) {
this.generalSettingsForm = this.formBuilder.group({
@ -66,8 +66,6 @@ export class IosTemplateDetailsComponent implements OnInit {
idlemax: new UntypedFormControl('', Validators.required),
idlesleep: new UntypedFormControl('', Validators.required),
execarea: new UntypedFormControl('', Validators.required),
idlepc: new UntypedFormControl('', Validators.pattern(this.iosConfigurationService.getIdlepcRegex())),
mac_addr: new UntypedFormControl('', Validators.pattern(this.iosConfigurationService.getMacAddrRegex())),
});
}
@ -80,12 +78,16 @@ export class IosTemplateDetailsComponent implements OnInit {
this.getConfiguration();
this.iosService.getTemplate(this.controller, template_id).subscribe((iosTemplate: IosTemplate) => {
this.iosTemplate = iosTemplate;
this.fillSlotsData();
this.fillAdaptersData();
});
});
}
getConfiguration() {
this.networkModules = this.iosConfigurationService.getNetworkModules();
this.networkAdaptersForPlatform = this.iosConfigurationService.getNetworkAdaptersForPlatform();
this.networkAdapters = this.iosConfigurationService.getNetworkAdapters();
this.platforms = this.iosConfigurationService.getAvailablePlatforms();
this.platformsWithEtherSwitchRouterOption = this.iosConfigurationService.getPlatformsWithEtherSwitchRouterOption();
this.platformsWithChassis = this.iosConfigurationService.getPlatformsWithChassis();
@ -93,76 +95,35 @@ export class IosTemplateDetailsComponent implements OnInit {
this.defaultRam = this.iosConfigurationService.getDefaultRamSettings();
this.consoleTypes = this.iosConfigurationService.getConsoleTypes();
this.categories = this.iosConfigurationService.getCategories();
this.adapterMatrix = this.iosConfigurationService.getAdapterMatrix();
this.wicMatrix = this.iosConfigurationService.getWicMatrix();
}
findIdlePC() {
let data = {
"image": this.iosTemplate.image,
"platform": this.iosTemplate.platform,
"ram": this.iosTemplate.ram
};
this.progressService.activate();
this.iosService.findIdlePC(this.controller, data).subscribe((result: any) => {
this.progressService.deactivate();
if (result.idlepc !== null) {
this.iosTemplate.idlepc = result.idlepc;
this.toasterService.success(`Idle-PC value found: ${result.idlepc}`);
}
},
(error) => {
this.progressService.deactivate();
this.toasterService.error(`Error while finding an idle-PC value`);
}
);
fillAdaptersData() {
if (this.iosTemplate.slot0) this.networkAdaptersForTemplate[0] = this.iosTemplate.slot0;
if (this.iosTemplate.slot1) this.networkAdaptersForTemplate[1] = this.iosTemplate.slot1;
if (this.iosTemplate.slot2) this.networkAdaptersForTemplate[2] = this.iosTemplate.slot2;
if (this.iosTemplate.slot3) this.networkAdaptersForTemplate[3] = this.iosTemplate.slot3;
if (this.iosTemplate.slot4) this.networkAdaptersForTemplate[4] = this.iosTemplate.slot4;
if (this.iosTemplate.slot5) this.networkAdaptersForTemplate[5] = this.iosTemplate.slot5;
if (this.iosTemplate.slot6) this.networkAdaptersForTemplate[6] = this.iosTemplate.slot6;
if (this.iosTemplate.slot7) this.networkAdaptersForTemplate[7] = this.iosTemplate.slot7;
}
fillSlotsData() {
// load network adapters
for (let i = 0; i <= 6; i++) {
if (this.iosTemplate[`slot${i}`]) {
this.networkAdaptersForTemplate[i] = this.iosTemplate[`slot${i}`];
}
}
// load WICs
for (let i = 0; i <= 3; i++) {
if (this.iosTemplate[`wic${i}`]) {
this.wicsForTemplate[i] = this.iosTemplate[`wic${i}`];
}
}
}
saveSlotsData() {
// save network adapters
for (let i = 0; i <= 6; i++) {
if (this.adapterMatrix[this.iosTemplate.platform][this.iosTemplate.chassis || ''][i]) {
if (this.networkAdaptersForTemplate[i] === undefined)
this.iosTemplate[`slot${i}`] = ""
else
this.iosTemplate[`slot${i}`] = this.networkAdaptersForTemplate[i];
}
}
// save WICs
for (let i = 0; i <= 3; i++) {
if (this.wicMatrix[this.iosTemplate.platform][i]) {
if (this.wicsForTemplate[i] === undefined)
this.iosTemplate[`wic${i}`] = ""
else
this.iosTemplate[`wic${i}`] = this.wicsForTemplate[i];
}
}
completeAdaptersData() {
if (this.networkAdaptersForTemplate[0]) this.iosTemplate.slot0 = this.networkAdaptersForTemplate[0];
if (this.networkAdaptersForTemplate[1]) this.iosTemplate.slot1 = this.networkAdaptersForTemplate[1];
if (this.networkAdaptersForTemplate[2]) this.iosTemplate.slot2 = this.networkAdaptersForTemplate[2];
if (this.networkAdaptersForTemplate[3]) this.iosTemplate.slot3 = this.networkAdaptersForTemplate[3];
if (this.networkAdaptersForTemplate[4]) this.iosTemplate.slot4 = this.networkAdaptersForTemplate[4];
if (this.networkAdaptersForTemplate[5]) this.iosTemplate.slot5 = this.networkAdaptersForTemplate[5];
if (this.networkAdaptersForTemplate[6]) this.iosTemplate.slot6 = this.networkAdaptersForTemplate[6];
if (this.networkAdaptersForTemplate[7]) this.iosTemplate.slot7 = this.networkAdaptersForTemplate[7];
}
onSave() {
if (this.generalSettingsForm.invalid || this.memoryForm.invalid || this.advancedForm.invalid) {
this.toasterService.error(`Fill all required fields`);
} else {
this.saveSlotsData();
this.completeAdaptersData();
this.iosService.saveTemplate(this.controller, this.iosTemplate).subscribe((iosTemplate: IosTemplate) => {
this.toasterService.success('Changes saved');

View File

@ -1,9 +0,0 @@
<button
mat-menu-item
*ngIf="node.node_type === 'dynamips'"
(click)="autoIdlePC()"
>
<mat-icon>query_builder</mat-icon>
<span>Auto Idle-PC</span>
</button>

View File

@ -1,36 +0,0 @@
import { Component, Input } from '@angular/core';
import { Node } from '../../../../../cartography/models/node';
import { Controller } from '../../../../../models/controller';
import { NodeService } from "@services/node.service";
import { ToasterService } from "@services/toaster.service";
import { ProgressService } from "../../../../../common/progress/progress.service";
@Component({
selector: 'app-auto-idle-pc-action',
templateUrl: './auto-idle-pc-action.component.html',
})
export class AutoIdlePcActionComponent {
@Input() controller:Controller ;
@Input() node: Node;
constructor(
private nodeService: NodeService,
private toasterService: ToasterService,
private progressService: ProgressService,
) {}
autoIdlePC() {
this.progressService.activate();
this.nodeService.getAutoIdlePC(this.controller, this.node).subscribe((result: any) => {
this.progressService.deactivate();
if (result.idlepc !== null) {
this.toasterService.success(`Node ${this.node.name} updated with idle-PC value ${result.idlepc}`);
}
},
(error) => {
this.progressService.deactivate();
this.toasterService.error(`Error while updating idle-PC value for node ${this.node.name}`);
}
);
}
}

View File

@ -1,8 +0,0 @@
<button
mat-menu-item
*ngIf="node.node_type === 'dynamips'"
(click)="idlePC()"
>
<mat-icon>query_builder</mat-icon>
<span>Idle-PC</span>
</button>

View File

@ -1,28 +0,0 @@
import { Component, Input } from '@angular/core';
import { MatDialog } from '@angular/material/dialog';
import { Node } from '../../../../../cartography/models/node';
import { Controller } from '../../../../../models/controller';
import { IdlePCDialogComponent } from "@components/project-map/context-menu/dialogs/idle-pc-dialog/idle-pc-dialog.component";
import { NodeService } from "@services/node.service";
@Component({
selector: 'app-idle-pc-action',
templateUrl: './idle-pc-action.component.html',
})
export class IdlePcActionComponent {
@Input() controller:Controller ;
@Input() node: Node;
constructor(private nodeService: NodeService, private dialog: MatDialog) {}
idlePC() {
const dialogRef = this.dialog.open(IdlePCDialogComponent, {
width: '500px',
autoFocus: false,
disableClose: true,
});
let instance = dialogRef.componentInstance;
instance.controller = this.controller;
instance.node = this.node;
}
}

View File

@ -1,4 +1,4 @@
<input type="file" accept=".txt, .vpc, .cfg" class="non-visible" #fileInput (change)="importConfig($event)" />
<input type="file" accept=".txt, .vpc" class="non-visible" #fileInput (change)="importConfig($event)" />
<button mat-menu-item (click)="triggerClick()">
<mat-icon>call_received</mat-icon>
<span>Import config</span>

View File

@ -94,16 +94,6 @@
[controller]="controller"
[node]="nodes[0]"
></app-import-config-action>
<app-idle-pc-action
*ngIf="nodes.length === 1 && nodes[0].node_type === 'dynamips'"
[controller]="controller"
[node]="nodes[0]"
></app-idle-pc-action>
<app-auto-idle-pc-action
*ngIf="nodes.length === 1 && nodes[0].node_type === 'dynamips'"
[controller]="controller"
[node]="nodes[0]"
></app-auto-idle-pc-action>
<app-move-layer-up-action
*ngIf="!projectService.isReadOnly(project) && (drawings.length || nodes.length)"
[controller]="controller"

View File

@ -1,25 +0,0 @@
<div *ngIf="isComputing">
<h6 align="center">Computing Idle-PC values, please wait...</h6>
<div mat-dialog-content align="center">
<mat-spinner color="accent"></mat-spinner>
</div>
</div>
<div *ngIf="!isComputing">
<h1 mat-dialog-title>Choose an Idle-PC value</h1>
<mat-form-field class="form-field">
<mat-select
placeholder="Idle-PC"
[ngModelOptions]="{standalone: true}"
[(ngModel)]="idlePC"
[matTooltip]="getTooltip()"
matTooltipClass="multiline-tooltip"
>
<mat-option *ngFor="let idlepc of idlepcs" [value]="idlepc.key"> {{ idlepc.name }} </mat-option>
</mat-select>
</mat-form-field>
</div>
<div mat-dialog-actions>
<button mat-button *ngIf="!isComputing" (click)="onCompute()" color="accent">Compute</button>
<button mat-button *ngIf="!isComputing" (click)="onApply()" color="accent">Apply</button>
<button mat-button (click)="onClose()" color="accent">Close</button>
</div>

View File

@ -1,11 +0,0 @@
.container {
width: 100%;
display: flex;
justify-content: space-between;
}
.multiline-tooltip {
background-color: grey;
color: #ffffff;
white-space: pre-line;
}

View File

@ -1,72 +0,0 @@
import {Component, Input, OnInit, ViewEncapsulation} from '@angular/core';
import {MatDialogRef} from '@angular/material/dialog';
import {Controller} from "@models/controller";
import {Node} from '../../../../../cartography/models/node';
import {NodeService} from "@services/node.service";
import {ToasterService} from "@services/toaster.service";
@Component({
selector: 'app-idle-pc-dialog',
templateUrl: './idle-pc-dialog.component.html',
styleUrls: ['./idle-pc-dialog.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class IdlePCDialogComponent implements OnInit {
@Input() controller: Controller;
@Input() node: Node;
idlepcs = [];
idlePC: string = '';
isComputing: boolean = false;
constructor(
private nodeService: NodeService,
public dialogRef: MatDialogRef<IdlePCDialogComponent>,
private toasterService: ToasterService
) {}
ngOnInit() {
this.onCompute();
}
getTooltip(){
return "Best Idle-PC values are obtained when IOS is in idle state, after the 'Press RETURN to get started' message has appeared on the console, messages have finished displaying on the console and you have have actually pressed the RETURN key.\n\nFinding the right idle-pc value is a trial and error process, consisting of applying different Idle-PC values and monitoring the CPU usage.\n\nSelect each value that appears in the list and click Apply, and note the CPU usage a few moments later. When you have found the value that minimises the CPU usage, apply that value.";
}
onCompute() {
this.isComputing = true;
this.nodeService.getIdlePCProposals(this.controller, this.node).subscribe((idlepcs: any) => {
let idlepcs_values = [];
for (let value of idlepcs) {
// validate idle-pc format, e.g. 0x60c09aa0
const match = value.match(/^(0x[0-9a-f]{8})\s+\[(\d+)\]$/);
if (match) {
const idlepc = match[1];
const count = parseInt(match[2], 10);
if (50 <= count && count <= 60) {
value += "*";
}
idlepcs_values.push({'key': idlepc, 'name': value})
}
}
this.idlepcs = idlepcs_values;
if (this.idlepcs.length > 0) {
this.idlePC = this.idlepcs[0].key;
}
this.isComputing = false;
});
}
onClose() {
this.dialogRef.close();
}
onApply() {
if (this.idlePC && this.idlePC !== '0x0') {
this.node.properties.idlepc = this.idlePC;
this.nodeService.updateNode(this.controller, this.node).subscribe(() => {
this.toasterService.success(`Node ${this.node.name} updated with idle-PC value ${this.idlePC}`);
});
}
}
}

View File

@ -142,7 +142,7 @@ export class ImportApplianceComponent implements OnInit {
}
this.template = template;
const url = this.getUploadPath(this.controller, name);
const url = this.getUploadPath(this.controller, template.template_type, name);
this.uploader.queue.forEach((elem) => (elem.url = url));
const itemToUpload = this.uploader.queue[0];
this.uploader.uploadItem(itemToUpload);
@ -150,7 +150,7 @@ export class ImportApplianceComponent implements OnInit {
fileReader.readAsText(file);
}
private getUploadPath(controller:Controller , filename: string) {
return `${controller.protocol}//${controller.host}:${controller.port}/${environment.current_version}/images/upload/${filename}`;
private getUploadPath(controller:Controller , emulator: string, filename: string) {
return `${controller.protocol}//${controller.host}:${controller.port}/${environment.current_version}/${emulator}/images/${filename}`;
}
}

View File

@ -132,7 +132,7 @@
<mat-card [hidden]="!(!isLinuxPlatform || applianceToInstall.dynamips)">
<div *ngIf="applianceToInstall.qemu">
<div>
Install the required files
Install required files
<button
mat-icon-button
matTooltip="Refresh list of images"
@ -380,11 +380,11 @@
{{ image.filename }}
</span>
<div>
<span *ngIf="checkImageFromVersion(image.filename)">
<mat-icon matTooltip="Ready to install" matTooltipClass="custom-tooltip">check</mat-icon>
<span *ngIf="checkImageFromVersion(image.filename)"
><mat-icon matTooltip="Ready to install" matTooltipClass="custom-tooltip">check</mat-icon>
</span>
<span *ngIf="!checkImageFromVersion(image.filename)">
<mat-icon matTooltip="Missing" matTooltipClass="custom-tooltip">close</mat-icon>
<span *ngIf="!checkImageFromVersion(image.filename)"
><mat-icon matTooltip="Missing" matTooltipClass="custom-tooltip">close</mat-icon>
</span>
<input
type="file"
@ -407,7 +407,7 @@
<div *ngIf="applianceToInstall.iou">
<div>
Install the required images
Install required images
<button
mat-icon-button
matTooltip="Refresh list of images"
@ -419,16 +419,16 @@
<mat-list>
<mat-list-item *ngFor="let image of applianceToInstall.images">
<div class="list-item">
<span>
{{ image.filename }}
</span>
<span *ngIf="checkImageFromVersion(image.filename)">
<mat-icon matTooltip="Ready to install" matTooltipClass="custom-tooltip">check</mat-icon>
</span>
<span *ngIf="!checkImageFromVersion(image.filename)">
<mat-icon matTooltip="Missing" matTooltipClass="custom-tooltip">close</mat-icon>
</span>
<div>
{{ image.filename }}
</div>
<div>
<span *ngIf="checkImageFromVersion(image.filename)"
><mat-icon matTooltip="Ready to install" matTooltipClass="custom-tooltip">check</mat-icon>
</span>
<span *ngIf="!checkImageFromVersion(image.filename)"
><mat-icon matTooltip="Missing" matTooltipClass="custom-tooltip">close</mat-icon>
</span>
<input
type="file"
class="non-visible"

View File

@ -11,7 +11,7 @@ import { FileItem, FileUploader, ParsedResponseHeaders } from 'ng2-file-upload';
import * as SparkMD5 from 'spark-md5';
import { v4 as uuid } from 'uuid';
import { ProgressService } from '../../../common/progress/progress.service';
import { InformationDialogComponent } from '../../../components/dialogs/information-dialog/information-dialog.component';
import { InformationDialogComponent } from '../../../components/dialogs/information-dialog.component';
import { Appliance, Image, Version } from '../../../models/appliance';
import { Project } from '../../../models/project';
import { QemuBinary } from '../../../models/qemu/qemu-binary';
@ -266,7 +266,7 @@ export class NewTemplateDialogComponent implements OnInit {
if (appliance.iou) emulator = 'iou';
if (appliance.qemu) emulator = 'qemu';
const url = this.applianceService.getUploadPath(this.controller, fileName);
const url = this.applianceService.getUploadPath(this.controller, emulator, fileName);
this.uploader.queue.forEach((elem) => (elem.url = url));
const itemToUpload = this.uploader.queue[0];
@ -341,6 +341,8 @@ export class NewTemplateDialogComponent implements OnInit {
dialogRef.componentInstance.appliance = object;
}
importImage(event, imageName) {
this.computeChecksumMd5(event.target.files[0], false).then((output) => {
let imageToInstall = this.applianceToInstall.images.filter((n) => n.filename === imageName)[0];
@ -357,21 +359,22 @@ export class NewTemplateDialogComponent implements OnInit {
The MD5 sum is ${output} and should be ${imageToInstall.md5sum}. Do you want to accept it at your own risks?`;
dialogRef.afterClosed().subscribe((answer: boolean) => {
if (answer) {
this.importImageFile(event, imageName);
this.importImageFile(event);
this.openSnackBar()
} else {
this.uploaderImage.clearQueue();
}
});
} else {
this.importImageFile(event, imageName);
this.importImageFile(event);
this.openSnackBar()
}
});
}
importImageFile(event, imageName) {
importImageFile(event) {
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;
@ -381,7 +384,7 @@ export class NewTemplateDialogComponent implements OnInit {
if (this.applianceToInstall.dynamips) emulator = 'dynamips';
if (this.applianceToInstall.iou) emulator = 'iou';
const url = this.applianceService.getUploadPath(this.controller, imageName);
const url = this.applianceService.getUploadPath(this.controller, emulator, fileName);
this.uploaderImage.queue.forEach((elem) => (elem.url = url));
const itemToUpload = this.uploaderImage.queue[0];

View File

@ -33,27 +33,17 @@
</mat-form-field>
<mat-form-field class="form-field">
<input
matInput
formControlName="mac_address"
type="text"
[(ngModel)]="node.properties.mac_address"
placeholder="Base MAC"
/>
</mat-form-field>
<mat-form-field class="form-field">
<input formControlName="memory" matInput type="number" min="0" [(ngModel)]="node.properties.memory" placeholder="Maximum memory">
<input formControlName="memory" matInput type="number" min="1" [(ngModel)]="node.properties.memory" placeholder="Maximum memory">
<span matSuffix>MB</span>
</mat-form-field>
<mat-form-field class="form-field">
<input formControlName="cpus" matInput type="number" min="0" [(ngModel)]="node.properties.cpus" placeholder="Maximum CPUs">
<input formControlName="cpus" matInput type="number" min="1" [(ngModel)]="node.properties.cpus" placeholder="Maximum CPUs">
</mat-form-field>
<!-- <button mat-button class="form-field" (click)="configureCustomAdapters()">-->
<!-- Configure custom adapters-->
<!-- </button>-->
<button mat-button class="form-field" (click)="configureCustomAdapters()">
Configure custom adapters
</button>
<mat-form-field class="select">
<mat-select

View File

@ -42,7 +42,6 @@ export class ConfiguratorDialogDockerComponent implements OnInit {
this.generalSettingsForm = this.formBuilder.group({
name: new UntypedFormControl('', Validators.required),
adapter: new UntypedFormControl('', Validators.required),
mac_address: new UntypedFormControl('', Validators.pattern(this.dockerConfigurationService.getMacAddrRegex())),
memory: new UntypedFormControl('', nonNegativeValidator.get),
cpus: new UntypedFormControl('', nonNegativeValidator.get),
startCommand: new UntypedFormControl(''),

View File

@ -7,31 +7,11 @@
<mat-tab-group *ngIf="name">
<mat-tab label="General settings">
<br />
<mat-label>Platform: {{ node.properties.platform }}</mat-label>
<mat-label *ngIf="node.properties.chassis"> (chassis: {{ node.properties.chassis }})</mat-label>
<br /><br />
<form [formGroup]="generalSettingsForm">
<mat-form-field class="form-field">
<input matInput type="text" formControlName="name" [(ngModel)]="node.name" placeholder="Name" />
</mat-form-field>
<mat-form-field class="form-field">
<input matInput type="text" formControlName="path" [(ngModel)]="node.properties.image" placeholder="IOS image path"/>
</mat-form-field>
</form>
<mat-form-field class="select" *ngIf="node.properties.midplane">
<mat-select placeholder="Midplane" [(ngModel)]="node.properties.midplane">
<mat-option *ngFor="let type of MidplaneTypes" [value]="type">
{{ type }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="select" *ngIf="node.properties.npe">
<mat-select placeholder="NPE" [(ngModel)]="node.properties.npe">
<mat-option *ngFor="let type of NPETypes" [value]="type">
{{ type }}
</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="select">
<mat-select placeholder="Console type" [(ngModel)]="node.console_type">
<mat-option *ngFor="let type of consoleTypes" [value]="type">
@ -40,7 +20,7 @@
</mat-select>
</mat-form-field>
<mat-form-field class="select">
<mat-select placeholder="Auxiliary console type" [(ngModel)]="node.properties.aux_type">
<mat-select placeholder="Auxiliary console type" [(ngModel)]="node.aux_type">
<mat-option *ngFor="let type of consoleTypes" [value]="type">
{{ type }}
</mat-option>
@ -56,144 +36,23 @@
<input
matInput
type="number"
min="0"
formControlName="ram"
[(ngModel)]="node.properties.ram"
placeholder="RAM size"
/>
<span matSuffix>MiB</span>
<span matSuffix>MB</span>
</mat-form-field>
<mat-form-field class="form-field">
<input
matInput
type="number"
min="0"
formControlName="nvram"
[(ngModel)]="node.properties.nvram"
placeholder="NVRAM size"
/>
<span matSuffix>MiB</span>
<span matSuffix>MB</span>
</mat-form-field>
</form>
<mat-form-field class="form-field" *ngIf="node.properties.iomem">
<input
matInput
type="number"
min="0"
max="100"
[(ngModel)]="node.properties.iomem"
placeholder="I/O memory"
/>
<span matSuffix>%</span>
</mat-form-field>
<mat-form-field class="form-field">
<input
matInput
type="number"
min="0"
[(ngModel)]="node.properties.disk0"
placeholder="PCMCIA disk0"
/>
<span matSuffix>MiB</span>
</mat-form-field>
<mat-form-field class="form-field">
<input
matInput
type="number"
min="0"
[(ngModel)]="node.properties.disk1"
placeholder="PCMCIA disk1"
/>
<span matSuffix>MiB</span>
</mat-form-field>
<mat-checkbox [(ngModel)]="node.properties.auto_delete_disks"> Automatically delete NVRAM and disk files </mat-checkbox><br />
</mat-tab>
<mat-tab label="Slots">
<br />
<h6>Adapters</h6>
<div *ngFor="let index of [0, 1, 2, 3, 4, 5, 6]">
<mat-select
placeholder="Slot {{ index }}"
[(ngModel)]="networkAdaptersForNode[index]"
[ngModelOptions]="{ standalone: true }"
*ngIf="adapterMatrix[node.properties.platform][node.properties.chassis || ''][index]"
>
<mat-option
*ngIf="adapterMatrix[node.properties.platform][node.properties.chassis || ''][index].length > 1 &&
!adapterMatrix[node.properties.platform][node.properties.chassis || ''][index][0].startsWith('C7200')"
[value]=""
>
{{ "" }}
</mat-option>
<mat-option
*ngFor="let option of adapterMatrix[node.properties.platform][node.properties.chassis || ''][index]"
[value]="option"
>
{{ option }}
</mat-option>
</mat-select>
</div>
<br />
<div *ngIf="wicMatrix[node.properties.platform]">
<h6>WICs</h6>
<div *ngFor="let index of [0, 1, 2]">
<mat-select
placeholder="WIC {{ index }}"
[(ngModel)]="wicsForNode[index]"
[ngModelOptions]="{ standalone: true }"
*ngIf="wicMatrix[node.properties.platform][index]"
>
<mat-option [value]="">
{{ "" }}
</mat-option>
<mat-option *ngFor="let option of wicMatrix[node.properties.platform][index]" [value]="option">
{{ option }}
</mat-option>
</mat-select>
</div>
</div>
</mat-tab>
<mat-tab label="Advanced">
<mat-card>
<mat-card-title></mat-card-title>
<mat-card-subtitle> System </mat-card-subtitle>
<mat-card-content>
<mat-form-field class="form-field">
<input matInput type="text" [(ngModel)]="node.properties.system_id" placeholder="System ID" />
</mat-form-field>
<form [formGroup]="advancedSettingsForm">
<mat-form-field class="form-field">
<input matInput formControlName="mac_addr" type="text" [(ngModel)]="node.properties.mac_addr" placeholder="Base MAC" />
</mat-form-field>
</form>
</mat-card-content>
</mat-card>
<mat-card>
<mat-card-title></mat-card-title>
<mat-card-subtitle> Optimizations </mat-card-subtitle>
<mat-card-content>
<form [formGroup]="advancedSettingsForm">
<mat-form-field class="form-field">
<input matInput type="text" formControlName="idlepc" [(ngModel)]="node.properties.idlepc" placeholder="Idle-PC" />
</mat-form-field>
</form>
<mat-form-field class="form-field">
<input matInput type="number" min="0" [(ngModel)]="node.properties.idlemax" placeholder="Idlemax" />
</mat-form-field>
<mat-form-field class="form-field">
<input matInput type="number" min="0" [(ngModel)]="node.properties.idlesleep" placeholder="Idlesleep" />
<span matSuffix>ms</span>
</mat-form-field>
<mat-form-field class="form-field">
<input matInput type="number" min="0" [(ngModel)]="node.properties.exec_area" placeholder="Exec area" />
<span matSuffix>MiB</span>
</mat-form-field>
<mat-checkbox [(ngModel)]="node.properties.mmap"> Enable mmap support </mat-checkbox><br />
<mat-checkbox [(ngModel)]="node.properties.sparsemem"> Enable sparse memory support </mat-checkbox><br />
</mat-card-content>
</mat-card>
</mat-tab>
<mat-tab label="Usage">

View File

@ -18,14 +18,7 @@ export class ConfiguratorDialogIosComponent implements OnInit {
name: string;
generalSettingsForm: UntypedFormGroup;
memoryForm: UntypedFormGroup;
advancedSettingsForm: UntypedFormGroup;
consoleTypes: string[] = [];
NPETypes: string[] = [];
MidplaneTypes: string[] = [];
networkAdaptersForNode: string[] = [];
wicsForNode: string[] = [];
adapterMatrix = {};
wicMatrix = {};
constructor(
public dialogRef: MatDialogRef<ConfiguratorDialogIosComponent>,
@ -36,18 +29,12 @@ export class ConfiguratorDialogIosComponent implements OnInit {
) {
this.generalSettingsForm = this.formBuilder.group({
name: new UntypedFormControl('', Validators.required),
path: new UntypedFormControl('', Validators.required),
});
this.memoryForm = this.formBuilder.group({
ram: new UntypedFormControl('', Validators.required),
nvram: new UntypedFormControl('', Validators.required),
});
this.advancedSettingsForm = this.formBuilder.group({
mac_addr: new UntypedFormControl('', Validators.pattern(this.configurationService.getMacAddrRegex())),
idlepc: new UntypedFormControl('', Validators.pattern(this.configurationService.getIdlepcRegex())),
});
}
ngOnInit() {
@ -55,61 +42,15 @@ export class ConfiguratorDialogIosComponent implements OnInit {
this.node = node;
this.name = node.name;
this.getConfiguration();
this.fillSlotsData();
});
}
getConfiguration() {
this.consoleTypes = this.configurationService.getConsoleTypes();
this.NPETypes = this.configurationService.getNPETypes();
this.MidplaneTypes = this.configurationService.getMidplaneTypes();
this.adapterMatrix = this.configurationService.getAdapterMatrix();
this.wicMatrix = this.configurationService.getWicMatrix();
}
fillSlotsData() {
// load network adapters
for (let i = 0; i <= 6; i++) {
if (this.node.properties[`slot${i}`]) {
this.networkAdaptersForNode[i] = this.node.properties[`slot${i}`];
}
}
// load WICs
for (let i = 0; i <= 3; i++) {
if (this.node.properties[`wic${i}`]) {
this.wicsForNode[i] = this.node.properties[`wic${i}`];
}
}
}
saveSlotsData() {
// save network adapters
for (let i = 0; i <= 6; i++) {
if (this.adapterMatrix[this.node.properties.platform][this.node.properties.chassis || ''][i]) {
if (this.networkAdaptersForNode[i] === undefined)
this.node.properties[`slot${i}`] = ""
else
this.node.properties[`slot${i}`] = this.networkAdaptersForNode[i];
}
}
// save WICs
for (let i = 0; i <= 3; i++) {
if (this.wicMatrix[this.node.properties.platform][i]) {
if (this.wicsForNode[i] === undefined)
this.node.properties[`wic${i}`] = ""
else
this.node.properties[`wic${i}`] = this.wicsForNode[i];
}
}
}
onSaveClick() {
if (this.generalSettingsForm.valid && this.memoryForm.valid && this.advancedSettingsForm.valid) {
this.saveSlotsData();
if (this.generalSettingsForm.valid && this.memoryForm.valid) {
this.nodeService.updateNode(this.controller, this.node).subscribe(() => {
this.toasterService.success(`Node ${this.node.name} updated.`);
this.onCancelClick();

View File

@ -152,43 +152,15 @@
</mat-tab>
<mat-tab label="Network">
<br />
<mat-form-field class="form-field">
<input
matInput
min=0
type="number"
[(ngModel)]="node.properties.adapters"
placeholder="Adapters"
/>
</mat-form-field>
<form [formGroup]="networkSettingsForm">
<mat-form-field class="form-field">
<input
matInput
formControlName="mac_address"
type="text"
[(ngModel)]="node.properties.mac_address"
placeholder="Base MAC"
/>
</mat-form-field>
</form>
<mat-select placeholder="Type" [(ngModel)]="node.properties.adapter_type">
<mat-option *ngFor="let type of networkTypes" [value]="type.value">
{{ type.name }} ({{ type.value }})
</mat-option>
</mat-select>
<!-- <button mat-button class="form-field" (click)="setCustomAdaptersConfiguratorState(true)">-->
<!-- Configure custom adapters-->
<!-- </button>-->
<br /><br /><mat-checkbox [(ngModel)]="node.properties.replicate_network_connection_state">
<br /><mat-checkbox [(ngModel)]="node.properties.replicate_network_connection_state">
Replicate network connection state
</mat-checkbox>
<!-- <app-custom-adapters-table-->
<!-- #customAdapters-->
<!-- [networkTypes]="networkTypes"-->
<!-- [displayedColumns]="displayedColumns"-->
<!-- [adapters]="node.ports"-->
<!-- ></app-custom-adapters-table>-->
<app-custom-adapters-table
#customAdapters
[networkTypes]="networkTypes"
[displayedColumns]="displayedColumns"
[adapters]="node.ports"
></app-custom-adapters-table>
</mat-tab>
<mat-tab label="Advanced">
<mat-card>

View File

@ -22,7 +22,6 @@ export class ConfiguratorDialogQemuComponent implements OnInit {
node: Node;
name: string;
generalSettingsForm: UntypedFormGroup;
networkSettingsForm: UntypedFormGroup;
consoleTypes: string[] = [];
onCloseOptions = [];
bootPriorities = [];
@ -55,10 +54,6 @@ export class ConfiguratorDialogQemuComponent implements OnInit {
name: new UntypedFormControl('', Validators.required),
ram: new UntypedFormControl('', Validators.required),
});
this.networkSettingsForm = this.formBuilder.group({
mac_address: new UntypedFormControl('', Validators.pattern(this.qemuConfigurationService.getMacAddrRegex())),
});
}
ngOnInit() {
@ -108,16 +103,16 @@ export class ConfiguratorDialogQemuComponent implements OnInit {
}
onSaveClick() {
if (this.generalSettingsForm.valid && this.networkSettingsForm.valid) {
// this.node.custom_adapters = [];
// this.customAdapters.adapters.forEach((n) => {
// this.node.custom_adapters.push({
// adapter_number: n.adapter_number,
// adapter_type: n.adapter_type,
// });
// });
//
// this.node.properties.adapters = this.node.custom_adapters.length;
if (this.generalSettingsForm.valid) {
this.node.custom_adapters = [];
this.customAdapters.adapters.forEach((n) => {
this.node.custom_adapters.push({
adapter_number: n.adapter_number,
adapter_type: n.adapter_type,
});
});
this.node.properties.adapters = this.node.custom_adapters.length;
this.nodeService.updateNodeWithCustomAdapters(this.controller, this.node).subscribe(() => {
this.toasterService.success(`Node ${this.node.name} updated.`);

View File

@ -98,25 +98,42 @@ export class ProjectMapMenuComponent implements OnInit, OnDestroy {
private async saveImage(screenshotProperties: Screenshot) {
if (screenshotProperties.filetype === 'png') {
let splittedSvg = document.getElementsByTagName('svg')[0].outerHTML.split('image');
let i = 1;
try {
// Get the SVG element and clone it to avoid modifying the original
const originalSvg = document.getElementsByTagName('svg')[0];
const svgClone = originalSvg.cloneNode(true) as SVGElement;
while (i < splittedSvg.length) {
let splittedImage = splittedSvg[i].split('"');
let splittedUrl = splittedImage[1].split('/');
// Process any embedded images
const images = svgClone.getElementsByTagName('image');
for (let i = 0; i < images.length; i++) {
const image = images[i];
const href = image.getAttribute('href') || image.getAttribute('xlink:href');
if (href) {
const urlParts = href.split('/');
const symbolId = urlParts[urlParts.length - 1];
try {
const rawSvg = await this.symbolService.raw(this.controller, symbolId).toPromise();
if (rawSvg) {
// Extract SVG content, fallback to raw content if parsing fails
const svgContent = rawSvg.includes('-->') ?
rawSvg.split('-->')[1].trim() :
rawSvg.trim();
image.outerHTML = svgContent;
}
} catch (err) {
console.warn(`Failed to process embedded image: ${symbolId}`, err);
}
}
}
let elem = await this.symbolService.raw(this.controller, splittedUrl[7]).toPromise();
let splittedElement = elem.split('-->');
splittedSvg[i] = splittedElement[1].substring(2);
i += 2;
// Create a temporary container and save as PNG
const container = document.createElement('div');
container.appendChild(svgClone);
svg.saveSvgAsPng(svgClone, `${screenshotProperties.name}.png`);
} catch (err) {
console.error('Failed to save PNG:', err);
throw err;
}
let svgString = splittedSvg.join();
let placeholder = document.createElement('div');
placeholder.innerHTML = svgString;
let element = placeholder.firstChild;
svg.saveSvgAsPng(element, `${screenshotProperties.name}.png`);
} else {
var svg_el = select('svg').attr('version', 1.1).attr('xmlns', 'http://www.w3.org/2000/svg').node();
downloadSvg(select('svg').node(), `${screenshotProperties.name}`);

View File

@ -25,7 +25,7 @@ export class ScreenshotDialogComponent implements OnInit {
this.nameForm = this.formBuilder.group({
screenshotName: new UntypedFormControl(`screenshot-${Date.now()}`, [Validators.required]),
});
this.isPngAvailable = this.electronService.isWindows || this.deviceService.getDeviceInfo().os === 'Windows';
this.isPngAvailable = true;
}
ngOnInit() {}

View File

@ -10,7 +10,7 @@ import { ProjectService } from '../../../services/project.service';
styleUrls: ['./choose-name-dialog.component.scss'],
})
export class ChooseNameDialogComponent implements OnInit {
@Input() controller: Controller;
@Input() controller:Controller ;
@Input() project: Project;
name: string;

View File

@ -21,18 +21,8 @@ export class ReadmeEditorComponent implements OnInit {
) {}
ngOnInit() {
this.projectService.getReadmeFile(this.controller, this.project.project_id).subscribe({
next: (file) => {
if (file) {
this.markdown = file;
}
},
error: (err) => {
if (err.status === 404) {
// File doesn't exist yet, which is fine
this.markdown = '';
}
}
this.projectService.getReadmeFile(this.controller, this.project.project_id).subscribe(file => {
if (file) this.markdown = file;
});
}
}

View File

@ -3,7 +3,7 @@ import { Injectable } from '@angular/core';
@Injectable()
export class ProjectNameValidator {
get(projectName) {
var pattern = new RegExp(/[~`!#$%\^&*+=\[\]\\';,/{}|\\"<>\?]/);
var pattern = new RegExp(/[~`!#$%\^&*+=\[\]\\';,/{}|\\":<>\?]/);
if (!pattern.test(projectName.value)) {
return null;

View File

@ -67,7 +67,7 @@ export class EditUserDialogComponent implements OnInit {
updatedUser.user_id = this.data.user.user_id;
console.log(updatedUser)
this.userService.update(this.data.controller, updatedUser, false)
this.userService.update(this.data.controller, updatedUser)
.subscribe((user: User) => {
console.log("Done ", user)
this.toasterService.success(`User ${user.username} updated`);

View File

@ -1,10 +1,10 @@
<h1 mat-dialog-title>Change password for {{ user.username }}</h1>
<h1 mat-dialog-title>Change password for user : </h1>
<div>
<form [formGroup]="editPasswordForm" class="input-field">
<mat-form-field class="input-field">
<input matInput type="password" formControlName="password" placeholder="Password"/>
<mat-error *ngIf="passwordForm.password?.touched && passwordForm.password?.errors"
>Password must be at least 8 characters long and contain at least one digit, one lowercase letter and one uppercase letter.
>Password must be between 6 and 100 characters.
</mat-error>
</mat-form-field>
<mat-form-field class="input-field">

View File

@ -18,18 +18,17 @@ export class ChangeUserPasswordComponent implements OnInit {
user: User;
constructor(private dialogRef: MatDialogRef<ChangeUserPasswordComponent>,
@Inject(MAT_DIALOG_DATA) public data: { user: User, controller: Controller, self_update: boolean },
@Inject(MAT_DIALOG_DATA) public data: { user: User, controller: Controller },
private userService: UserService,
private toasterService: ToasterService) { }
ngOnInit(): void {
const password_regex = /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{8}$/;
this.user = this.data.user;
this.editPasswordForm = new UntypedFormGroup({
password: new UntypedFormControl(null,
[Validators.minLength(6), Validators.maxLength(100), Validators.pattern(password_regex), Validators.required] ),
[Validators.minLength(6), Validators.maxLength(100), Validators.required] ),
confirmPassword: new UntypedFormControl(null,
[Validators.minLength(6), Validators.maxLength(100), Validators.pattern(password_regex), Validators.required] ),
[Validators.minLength(6), Validators.maxLength(100), Validators.required] ),
},{
validators: [matchingPassword]
})
@ -53,14 +52,16 @@ export class ChangeUserPasswordComponent implements OnInit {
updatedUser['password'] = this.editPasswordForm.get('password').value;
updatedUser['user_id'] = this.user.user_id;
this.userService.update(this.data.controller, updatedUser, this.data.self_update)
console.log(updatedUser);
this.userService.update(this.data.controller, updatedUser)
.subscribe((user: User) => {
this.toasterService.success(`User ${user.username} password updated`);
this.editPasswordForm.reset();
this.dialogRef.close(true);
},
(error) => {
this.toasterService.error('Cannot update password for user: ' + error);
this.toasterService.error('Cannot update password for user : ' + error);
})
}
}

View File

@ -96,7 +96,7 @@ export class UserDetailComponent implements OnInit {
const updatedUser = this.getUpdatedValues();
updatedUser['user_id'] = this.user.user_id;
this.userService.update(this.controller, updatedUser, false)
this.userService.update(this.controller, updatedUser)
.subscribe((user: User) => {
this.toasterService.success(`User ${user.username} updated`);
},

View File

@ -11,7 +11,6 @@
* Author: Sylvain MATHIEU, Elise LEBEAU
*/
import {Component, OnInit, QueryList, ViewChild, ViewChildren} from '@angular/core';
import {Location} from '@angular/common';
import {ActivatedRoute, Router} from "@angular/router";
import {Controller} from "@models/controller";
import {MatSort} from "@angular/material/sort";
@ -50,8 +49,7 @@ export class UserManagementComponent implements OnInit {
private progressService: ProgressService,
private controllerService: ControllerService,
public dialog: MatDialog,
private toasterService: ToasterService,
private location: Location) { }
private toasterService: ToasterService) { }
ngOnInit() {
const controllerId = this.route.parent.snapshot.paramMap.get('controller_id');
@ -90,8 +88,6 @@ export class UserManagementComponent implements OnInit {
},
(error) => {
this.progressService.setError(error);
this.toasterService.error(`Cannot open the user management page`);
this.location.back();
}
);
}

View File

@ -7,13 +7,11 @@
<div class="default-content">
<mat-card *ngIf="user">
<mat-list>
<mat-list-item> Username: {{ user.username }} </mat-list-item>
<mat-list-item> Full name: {{ user.full_name }} </mat-list-item>
<mat-list-item> Email: {{ user.email }} </mat-list-item>
<mat-list-item> Username: {{user.username}} </mat-list-item>
<mat-list-item> Full name: {{user.full_name}} </mat-list-item>
<mat-list-item> Email: {{user.email}} </mat-list-item>
</mat-list>
<div class="buttons-bar">
<button mat-raised-button color="primary" class="full_width" (click)="changePassword()">Change password</button><br />
</div>
<div class="buttons-bar">
<button mat-raised-button color="primary" class="full_width" (click)="copyToken()">Click to copy access token</button><br />
</div>

View File

@ -5,8 +5,6 @@ import { UserService } from '../../../services/user.service';
import { ToasterService } from '../../../services/toaster.service';
import { User } from '../../../models/users/user';
import { Controller } from '../../../models/controller';
import { ChangeUserPasswordComponent } from "@components/user-management/user-detail/change-user-password/change-user-password.component";
import { MatDialog } from "@angular/material/dialog";
@Component({
selector: 'app-logged-user',
@ -21,8 +19,7 @@ export class LoggedUserComponent implements OnInit {
private route: ActivatedRoute,
private controllerService: ControllerService,
private userService: UserService,
private toasterService: ToasterService,
public dialog: MatDialog
private toasterService: ToasterService
) {}
ngOnInit() {
@ -35,11 +32,6 @@ export class LoggedUserComponent implements OnInit {
});
}
changePassword() {
this.dialog.open<ChangeUserPasswordComponent>(ChangeUserPasswordComponent,
{width: '500px', height: '300px', data: {user: this.user, controller: this.controller, self_update: true}});
}
copyToken() {
const selBox = document.createElement('textarea');
selBox.style.position = 'fixed';

View File

@ -17,8 +17,8 @@ export class ApplianceService {
return this.httpController.get<Appliance>(controller, url) as Observable<Appliance>;
}
getUploadPath(controller:Controller, filename: string) {
return `${controller.protocol}//${controller.host}:${controller.port}/${environment.current_version}/images/upload/${filename}`;
getUploadPath(controller:Controller , emulator: string, filename: string) {
return `${controller.protocol}//${controller.host}:${controller.port}/${environment.current_version}/images/upload/${filename}?allow_raw_image=true`;
}
updateAppliances(controller:Controller ): Observable<Appliance[]> {

View File

@ -14,6 +14,10 @@ export class ComputeService {
return this.httpController.get<Compute[]>(controller, '/computes') as Observable<Compute[]>;
}
getUploadPath(controller:Controller , emulator: string, filename: string) {
return `${controller.protocol}//${controller.host}:${controller.port}/${environment.current_version}/${emulator}/images/${filename}`;
}
getStatistics(controller:Controller ): Observable<ComputeStatistics[]> {
return this.httpController.get(controller, `/statistics`);
}

View File

@ -27,8 +27,4 @@ export class DockerConfigurationService {
return consoleResolutions;
}
getMacAddrRegex() {
return /^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$/;
}
}

View File

@ -12,27 +12,22 @@ export class ImageManagerService {
constructor(private httpController: HttpController) { }
getImages(controller: Controller) {
getImages(controller:Controller ) {
return this.httpController.get<Image[]>(controller, '/images') as Observable<Image[]>;
}
getImagePath(controller: Controller, install_appliance, image_path){
getImagePath(controller :Controller, install_appliance, image_path){
return `${controller.protocol}//${controller.host}:${controller.port}/${environment.current_version}/images/upload/${image_path}?install_appliances=${install_appliance}`;
}
uploadedImage(controller: Controller, install_appliance, image_path, file){
return this.httpController.post<Image[]>(controller, `/images/upload/${image_path}?install_appliances=${install_appliance}`, file) as Observable<Image[]>;
getUploadPath(controller:Controller , emulator: string, filename: string) {
return `${controller.protocol}//${controller.host}:${controller.port}/${environment.current_version}/images/upload/${filename}`;
}
deleteFile(controller: Controller, image_path){
uploadedImage(controller :Controller, install_appliance, image_path, flie){
return this.httpController.post<Image[]>(controller, `/images/upload/${image_path}?install_appliances=${install_appliance}`,flie) as Observable<Image[]>;
}
deleteFile(controller :Controller, image_path){
return this.httpController.delete<Image[]>(controller, `/images/${image_path}`) as Observable<Image[]>;
}
pruneImages(controller: Controller){
return this.httpController.delete(controller, '/images/prune');
}
installImages(controller: Controller) {
return this.httpController.post(controller, '/images/install', {});
}
}

View File

@ -2,6 +2,9 @@ import { Injectable } from '@angular/core';
@Injectable()
export class IosConfigurationService {
c1700_wics = ['WIC-1T', 'WIC-2T', 'WIC-1ENET'];
c2600_wics = ['WIC-1T', 'WIC-2T'];
c3700_wics = ['WIC-1T', 'WIC-2T'];
c2600_nms = ['NM-1FE-TX', 'NM-1E', 'NM-4E', 'NM-16ESW'];
c3600_nms = ['NM-1FE-TX', 'NM-1E', 'NM-4E', 'NM-16ESW', 'NM-4T'];
@ -9,10 +12,6 @@ export class IosConfigurationService {
c7200_pas = ['PA-A1', 'PA-FE-TX', 'PA-2FE-TX', 'PA-GE', 'PA-4T+', 'PA-8T', 'PA-4E', 'PA-8E', 'PA-POS-OC3'];
c7200_io = ['C7200-IO-FE', 'C7200-IO-2FE', 'C7200-IO-GE-E'];
c1700_wics = ['WIC-1T', 'WIC-2T', 'WIC-1ENET'];
c2600_wics = ['WIC-1T', 'WIC-2T'];
c3700_wics = ['WIC-1T', 'WIC-2T'];
getConsoleTypes() {
return ['telnet', 'none'];
}
@ -46,7 +45,10 @@ export class IosConfigurationService {
c1700: 128,
c2600: 128,
c2691: 256,
c3600: 256,
c3600: 192,
c3620: 192,
c3640: 192,
c3660: 192,
c3725: 256,
c3745: 256,
c7200: 512,
@ -89,101 +91,170 @@ export class IosConfigurationService {
};
}
getNPETypes() {
return ['npe-100', 'npe-150', 'npe-175', 'npe-200', 'npe-225', 'npe-300', 'npe-400', 'npe-g2'];
getNetworkModules() {
return {
c1700: {
0: this.c1700_wics,
1: this.c1700_wics,
},
c2600: {
0: this.c2600_wics,
1: this.c2600_wics,
2: this.c2600_wics,
},
c2691: {
0: this.c3700_wics,
1: this.c3700_wics,
2: this.c3700_wics,
},
c3725: {
0: this.c3700_wics,
1: this.c3700_wics,
2: this.c3700_wics,
},
c3745: {
0: this.c3700_wics,
1: this.c3700_wics,
2: this.c3700_wics,
},
};
}
getMidplaneTypes() {
return ['std', 'vxr'];
getNetworkAdapters() {
return {
'1720': {
0: ['C1700-MB-1FE'],
},
'1721': {
0: ['C1700-MB-1FE'],
},
'1750': {
0: ['C1700-MB-1FE'],
},
'1751': {
0: ['C1700-MB-1FE'],
1: ['C1700-MB-WIC1'],
},
'1760': {
0: ['C1700-MB-1FE'],
1: ['C1700-MB-WIC1'],
},
'2610': {
0: ['C2600-MB-1E'],
1: this.c2600_nms,
},
'2611': {
0: ['C2600-MB-2E'],
1: this.c2600_nms,
},
'2620': {
0: ['C2600-MB-1FE'],
1: this.c2600_nms,
},
'2621': {
0: ['C2600-MB-2FE'],
1: this.c2600_nms,
},
'2610XM': {
0: ['C2600-MB-1FE'],
1: this.c2600_nms,
},
'2611XM': {
0: ['C2600-MB-2FE'],
1: this.c2600_nms,
},
'2620XM': {
0: ['C2600-MB-1FE'],
1: this.c2600_nms,
},
'2621XM': {
0: ['C2600-MB-2FE'],
1: this.c2600_nms,
},
'2650XM': {
0: ['C2600-MB-1FE'],
1: this.c2600_nms,
},
'2651XM': {
0: ['C2600-MB-2FE'],
1: this.c2600_nms,
},
'3620': {
0: this.c3600_nms,
1: this.c3600_nms,
},
'3640': {
0: this.c3600_nms,
1: this.c3600_nms,
2: this.c3600_nms,
3: this.c3600_nms,
},
'3660': {
0: ['Leopard-2FE'],
1: this.c3600_nms,
2: this.c3600_nms,
3: this.c3600_nms,
4: this.c3600_nms,
5: this.c3600_nms,
6: this.c3600_nms,
},
};
}
getAdapterMatrix() {
getNetworkAdaptersForPlatform() {
let networkAdaptersForPlatform = {};
networkAdaptersForPlatform['c2691'] = {
0: ['GT96100-FE'],
1: this.c3700_nms,
};
networkAdaptersForPlatform['c3725'] = {
0: ['GT96100-FE'],
1: this.c3700_nms,
2: this.c3700_nms,
};
networkAdaptersForPlatform['c3745'] = {
0: ['GT96100-FE'],
1: this.c3700_nms,
2: this.c3700_nms,
3: this.c3700_nms,
4: this.c3700_nms,
};
networkAdaptersForPlatform['c7200'] = {
0: this.c7200_io,
1: this.c7200_pas,
2: this.c7200_pas,
3: this.c7200_pas,
4: this.c7200_pas,
5: this.c7200_pas,
6: this.c7200_pas,
};
let adapter_matrix: any = {};
for (let platform of ["c1700", "c2600", "c2691", "c3725", "c3745", "c3600", "c7200"]) {
adapter_matrix[platform] = {};
}
// 1700s have one interface on the MB, 2 sub-slots for WICs, and no NM slots
for (let chassis of ["1720", "1721", "1750", "1751", "1760"]) {
adapter_matrix["c1700"][chassis] = { 0: ["C1700-MB-1FE"] };
}
// Add a fake NM in slot 1 on 1751s and 1760s to provide two WIC slots
for (let chassis of ["1751", "1760"]) {
adapter_matrix["c1700"][chassis][1] = ["C1700-MB-WIC1"];
}
// 2600s have one or more interfaces on the MB, 2 subslots for WICs, and an available NM slot 1
for (let chassis of ["2620", "2610XM", "2620XM", "2650XM"]) {
adapter_matrix["c2600"][chassis] = { 0: ["C2600-MB-1FE"], 1: this.c2600_nms };
}
for (let chassis of ["2621", "2611XM", "2621XM", "2651XM"]) {
adapter_matrix["c2600"][chassis] = { 0: ["C2600-MB-2FE"], 1: this.c2600_nms };
}
adapter_matrix["c2600"]["2610"] = { 0: ["C2600-MB-1E"], 1: this.c2600_nms };
adapter_matrix["c2600"]["2611"] = { 0: ["C2600-MB-2E"], 1: this.c2600_nms };
// 2691s have two FEs on the motherboard and one NM slot
adapter_matrix["c2691"][""] = { 0: ["GT96100-FE"], 1: this.c3700_nms };
// 3620s have two generic NM slots
adapter_matrix["c3600"]["3620"] = {};
for (let slot = 0; slot < 2; slot++) {
adapter_matrix["c3600"]["3620"][slot] = this.c3600_nms;
}
// 3640s have four generic NM slots
adapter_matrix["c3600"]["3640"] = {};
for (let slot = 0; slot < 4; slot++) {
adapter_matrix["c3600"]["3640"][slot] = this.c3600_nms;
}
// 3660s have 2 FEs on the motherboard and 6 generic NM slots
adapter_matrix["c3600"]["3660"] = { 0: ["Leopard-2FE"] };
for (let slot = 1; slot < 7; slot++) {
adapter_matrix["c3600"]["3660"][slot] = this.c3600_nms;
}
// 3725s have 2 FEs on the motherboard and 2 generic NM slots
adapter_matrix["c3725"][""] = { 0: ["GT96100-FE"] };
for (let slot = 1; slot < 3; slot++) {
adapter_matrix["c3725"][""][slot] = this.c3700_nms;
}
// 3745s have 2 FEs on the motherboard and 4 generic NM slots
adapter_matrix["c3745"][""] = { 0: ["GT96100-FE"] };
for (let slot = 1; slot < 5; slot++) {
adapter_matrix["c3745"][""][slot] = this.c3700_nms;
}
// 7206s allow an IO controller in slot 0, and a generic PA in slots 1-6
adapter_matrix["c7200"][""] = { 0: this.c7200_io };
for (let slot = 1; slot < 7; slot++) {
adapter_matrix["c7200"][""][slot] = this.c7200_pas;
}
return adapter_matrix;
}
getWicMatrix() {
let wic_matrix: any = {};
wic_matrix["c1700"] = { 0: this.c1700_wics, 1: this.c1700_wics };
wic_matrix["c2600"] = { 0: this.c2600_wics, 1: this.c2600_wics, 2: this.c2600_wics };
wic_matrix["c2691"] = { 0: this.c3700_wics, 1: this.c3700_wics, 2: this.c3700_wics };
wic_matrix["c3725"] = { 0: this.c3700_wics, 1: this.c3700_wics, 2: this.c3700_wics };
wic_matrix["c3745"] = { 0: this.c3700_wics, 1: this.c3700_wics, 2: this.c3700_wics };
return wic_matrix;
}
getIdlepcRegex() {
return /^(0x[0-9a-fA-F]+)?$|^$/;
}
getMacAddrRegex() {
return /^([0-9a-fA-F]{4}\.){2}[0-9a-fA-F]{4}$|^$/;
return {
c2691: {
0: ['GT96100-FE'],
1: this.c3700_nms,
},
c3725: {
0: ['GT96100-FE'],
1: this.c3700_nms,
2: this.c3700_nms,
},
c3745: {
0: ['GT96100-FE'],
1: this.c3700_nms,
2: this.c3700_nms,
3: this.c3700_nms,
4: this.c3700_nms,
},
c7200: {
0: this.c7200_io,
1: this.c7200_pas,
2: this.c7200_pas,
3: this.c7200_pas,
4: this.c7200_pas,
5: this.c7200_pas,
6: this.c7200_pas,
},
};
}
}

View File

@ -37,9 +37,4 @@ export class IosService {
iosTemplate
) as Observable<IosTemplate>;
}
findIdlePC(controller:Controller, body: any) {
return this.httpController.post(controller, `/computes/${environment.compute_id}/dynamips/auto_idlepc`, body);
}
}

View File

@ -189,7 +189,7 @@ export class NodeService {
} else if (node.node_type === 'iou') {
urlPath += '/files/startup-config.cfg';
} else if (node.node_type === 'dynamips') {
urlPath += `/files/configs/i${node.properties.dynamips_id}_startup-config.cfg`;
urlPath += `/files/configs/i${node.node_id}_startup-config.cfg`;
}
return this.httpController.get(controller, urlPath, { responseType: 'text' as 'json' });
@ -201,7 +201,7 @@ export class NodeService {
if (node.node_type === 'iou') {
urlPath += '/files/private-config.cfg';
} else if (node.node_type === 'dynamips') {
urlPath += `/files/configs/i${node.properties.dynamips_id}_private-config.cfg`;
urlPath += `/files/configs/i${node.node_id}_private-config.cfg`;
}
return this.httpController.get(controller, urlPath, { responseType: 'text' as 'json' });
@ -215,7 +215,7 @@ export class NodeService {
} else if (node.node_type === 'iou') {
urlPath += '/files/startup-config.cfg';
} else if (node.node_type === 'dynamips') {
urlPath += `/files/configs/i${node.properties.dynamips_id}_startup-config.cfg`;
urlPath += `/files/configs/i${node.node_id}_startup-config.cfg`;
}
return this.httpController.post(controller, urlPath, configuration);
@ -227,17 +227,9 @@ export class NodeService {
if (node.node_type === 'iou') {
urlPath += '/files/private-config.cfg';
} else if (node.node_type === 'dynamips') {
urlPath += `/files/configs/i${node.properties.dynamips_id}_private-config.cfg`;
urlPath += `/files/configs/i${node.node_id}_private-config.cfg`;
}
return this.httpController.post(controller, urlPath, configuration);
}
getIdlePCProposals(controller:Controller, node: Node) {
return this.httpController.get(controller, `/projects/${node.project_id}/nodes/${node.node_id}/dynamips/idlepc_proposals`);
}
getAutoIdlePC(controller:Controller, node: Node) {
return this.httpController.get(controller, `/projects/${node.project_id}/nodes/${node.node_id}/dynamips/auto_idlepc`);
}
}

View File

@ -76,6 +76,34 @@ export class QemuConfigurationService {
{ value: 'vmxnet3', name: 'VMWare Paravirtualized Ethernet v3' },
];
// let networkTypes = [
// 'e1000',
// 'e1000-82544gc',
// 'e1000-82545em',
// 'e1000e',
// 'rocker',
// 'Intel Gigabit Ethernet',
// 'i82550',
// 'i82551',
// 'i82557a',
// 'i82557b',
// 'i82557c',
// 'i82558a',
// 'i82558b',
// 'i82559a',
// 'i82559b',
// 'i82559c',
// 'i82559er',
// 'i82562',
// 'i82801',
// 'ne2k_pci',
// 'pcnet',
// 'rtl8139',
// 'virtio',
// 'virtio-net-pci',
// 'vmxnet3',
// ];
return networkTypes;
}
@ -118,8 +146,4 @@ export class QemuConfigurationService {
return priorities;
}
getMacAddrRegex() {
return /^([0-9a-fA-F]{2}[:]){5}([0-9a-fA-F]{2})$/;
}
}

View File

@ -5,7 +5,6 @@ import { Controller } from '../models/controller';
import { HttpController } from './http-controller.service';
import { User } from '../models/users/user';
import { Group } from "@models/groups/group";
import {Image} from "@models/images";
@Injectable()
export class UserService {
@ -33,10 +32,7 @@ export class UserService {
return this.httpController.delete(controller, `/access/users/${user_id}`);
}
update(controller: Controller, user: any, self_update: boolean): Observable<User> {
if (self_update) {
return this.httpController.put<User>(controller, `/access/users/me`, user);
}
update(controller: Controller, user: any): Observable<User> {
return this.httpController.put<User>(controller, `/access/users/${user.user_id}`, user);
}