+
-
-
+
diff --git a/src/app/components/direct-link/direct-link.component.ts b/src/app/components/direct-link/direct-link.component.ts
index d8f6a3b9..f7cee146 100644
--- a/src/app/components/direct-link/direct-link.component.ts
+++ b/src/app/components/direct-link/direct-link.component.ts
@@ -1,9 +1,9 @@
import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
-import { Server } from '../../models/server';
-import { ServerDatabase } from '../../services/server.database';
-import { ServerService } from '../../services/server.service';
+import{ Controller } from '../../models/controller';
+import { ControllerDatabase } from '../../services/controller.database';
+import { ControllerService } from '../../services/controller.service';
import { ToasterService } from '../../services/toaster.service';
@Component({
@@ -13,9 +13,9 @@ import { ToasterService } from '../../services/toaster.service';
encapsulation: ViewEncapsulation.None,
})
export class DirectLinkComponent implements OnInit {
- public serverOptionsVisibility = false;
- public serverIp;
- public serverPort;
+ public controllerOptionsVisibility = false;
+ public controllerIp;
+ public controllerPort;
public projectId;
protocols = [
@@ -27,61 +27,61 @@ export class DirectLinkComponent implements OnInit {
{ key: 'remote', name: 'Remote' },
];
- serverForm = new FormGroup({
+ controllerForm = new FormGroup({
name: new FormControl('', [Validators.required]),
location: new FormControl(''),
protocol: new FormControl('http:')
});
constructor(
- private serverService: ServerService,
- private serverDatabase: ServerDatabase,
+ private controllerService: ControllerService,
+ private controllerDatabase: ControllerDatabase,
private route: ActivatedRoute,
private router: Router,
private toasterService: ToasterService
) {}
async ngOnInit() {
- if (this.serverService.isServiceInitialized) this.getServers();
+ if (this.controllerService.isServiceInitialized) this.getControllers();
- this.serverService.serviceInitialized.subscribe(async (value: boolean) => {
+ this.controllerService.serviceInitialized.subscribe(async (value: boolean) => {
if (value) {
- this.getServers();
+ this.getControllers();
}
});
}
- private async getServers() {
- this.serverIp = this.route.snapshot.paramMap.get('server_ip');
- this.serverPort = +this.route.snapshot.paramMap.get('server_port');
+ private async getControllers() {
+ this.controllerIp = this.route.snapshot.paramMap.get('controller_ip');
+ this.controllerPort = +this.route.snapshot.paramMap.get('controller_port');
this.projectId = this.route.snapshot.paramMap.get('project_id');
- const servers = await this.serverService.findAll();
- const server = servers.filter((server) => server.host === this.serverIp && server.port === this.serverPort)[0];
+ const controllers = await this.controllerService.findAll();
+ const controller = controllers.filter((controller) => controller.host === this.controllerIp && controller.port === this.controllerPort)[0];
- if (server) {
- this.router.navigate(['/server', server.id, 'project', this.projectId]);
+ if (controller) {
+ this.router.navigate(['/controller', controller.id, 'project', this.projectId]);
} else {
- this.serverOptionsVisibility = true;
+ this.controllerOptionsVisibility = true;
}
}
- public createServer() {
- if (!this.serverForm.get('name').hasError && !this.serverForm.get('location').hasError && !this.serverForm.get('protocol').hasError) {
+ public createController() {
+ if (!this.controllerForm.get('name').hasError && !this.controllerForm.get('location').hasError && !this.controllerForm.get('protocol').hasError) {
this.toasterService.error('Please use correct values');
return;
}
- let serverToAdd: Server = new Server();
- serverToAdd.host = this.serverIp;
- serverToAdd.port = this.serverPort;
+ let controllerToAdd:Controller = new Controller ();
+ controllerToAdd.host = this.controllerIp;
+ controllerToAdd.port = this.controllerPort;
- serverToAdd.name = this.serverForm.get('name').value;
- serverToAdd.location = this.serverForm.get('location').value;
- serverToAdd.protocol = this.serverForm.get('protocol').value;
+ controllerToAdd.name = this.controllerForm.get('name').value;
+ controllerToAdd.location = this.controllerForm.get('location').value;
+ controllerToAdd.protocol = this.controllerForm.get('protocol').value;
- this.serverService.create(serverToAdd).then((addedServer: Server) => {
- this.router.navigate(['/server', addedServer.id, 'project', this.projectId]);
+ this.controllerService.create(controllerToAdd).then((addedController:Controller ) => {
+ this.router.navigate(['/controller', addedController.id, 'project', this.projectId]);
});
}
}
diff --git a/src/app/components/drawings-listeners/drawing-added/drawing-added.component.ts b/src/app/components/drawings-listeners/drawing-added/drawing-added.component.ts
index f43c825f..fd0dfb33 100644
--- a/src/app/components/drawings-listeners/drawing-added/drawing-added.component.ts
+++ b/src/app/components/drawings-listeners/drawing-added/drawing-added.component.ts
@@ -7,7 +7,7 @@ import { AddedDataEvent } from '../../../cartography/events/event-source';
import { DefaultDrawingsFactory } from '../../../cartography/helpers/default-drawings-factory';
import { Drawing } from '../../../cartography/models/drawing';
import { Project } from '../../../models/project';
-import { Server } from '../../../models/server';
+import{ Controller } from '../../../models/controller';
import { DrawingService } from '../../../services/drawing.service';
@Component({
@@ -16,7 +16,7 @@ import { DrawingService } from '../../../services/drawing.service';
styleUrls: ['./drawing-added.component.scss'],
})
export class DrawingAddedComponent implements OnInit, OnDestroy {
- @Input() server: Server;
+ @Input() controller: Controller
@Input() project: Project;
@Input() selectedDrawing: string;
@Output() drawingSaved = new EventEmitter
();
@@ -49,9 +49,9 @@ export class DrawingAddedComponent implements OnInit, OnDestroy {
let svgText = this.mapDrawingToSvgConverter.convert(drawing);
this.drawingService
- .add(this.server, this.project.project_id, evt.x, evt.y, svgText)
- .subscribe((serverDrawing: Drawing) => {
- this.drawingsDataSource.add(serverDrawing);
+ .add(this.controller, this.project.project_id, evt.x, evt.y, svgText)
+ .subscribe((controllerDrawing: Drawing) => {
+ this.drawingsDataSource.add(controllerDrawing);
this.drawingSaved.emit(true);
});
}
diff --git a/src/app/components/drawings-listeners/drawing-dragged/drawing-dragged.component.ts b/src/app/components/drawings-listeners/drawing-dragged/drawing-dragged.component.ts
index dc33b8fa..8954e944 100644
--- a/src/app/components/drawings-listeners/drawing-dragged/drawing-dragged.component.ts
+++ b/src/app/components/drawings-listeners/drawing-dragged/drawing-dragged.component.ts
@@ -6,7 +6,7 @@ import { DraggedDataEvent } from '../../../cartography/events/event-source';
import { Drawing } from '../../../cartography/models/drawing';
import { MapDrawing } from '../../../cartography/models/map/map-drawing';
import { Project } from '../../../models/project';
-import { Server } from '../../../models/server';
+import{ Controller } from '../../../models/controller';
import { DrawingService } from '../../../services/drawing.service';
@Component({
@@ -15,7 +15,7 @@ import { DrawingService } from '../../../services/drawing.service';
styleUrls: ['./drawing-dragged.component.scss'],
})
export class DrawingDraggedComponent implements OnInit, OnDestroy {
- @Input() server: Server;
+ @Input() controller:Controller ;
@Input() project: Project;
private drawingDragged: Subscription;
@@ -35,9 +35,9 @@ export class DrawingDraggedComponent implements OnInit, OnDestroy {
drawing.y += draggedEvent.dy;
this.drawingService
- .updatePosition(this.server, this.project, drawing, drawing.x, drawing.y)
- .subscribe((serverDrawing: Drawing) => {
- this.drawingsDataSource.update(serverDrawing);
+ .updatePosition(this.controller, this.project, drawing, drawing.x, drawing.y)
+ .subscribe((controllerDrawing: Drawing) => {
+ this.drawingsDataSource.update(controllerDrawing);
});
}
diff --git a/src/app/components/drawings-listeners/drawing-resized/drawing-resized.component.ts b/src/app/components/drawings-listeners/drawing-resized/drawing-resized.component.ts
index ef8fc203..1bfabf80 100644
--- a/src/app/components/drawings-listeners/drawing-resized/drawing-resized.component.ts
+++ b/src/app/components/drawings-listeners/drawing-resized/drawing-resized.component.ts
@@ -6,7 +6,7 @@ import { DrawingsEventSource } from '../../../cartography/events/drawings-event-
import { ResizedDataEvent } from '../../../cartography/events/event-source';
import { Drawing } from '../../../cartography/models/drawing';
import { MapDrawing } from '../../../cartography/models/map/map-drawing';
-import { Server } from '../../../models/server';
+import{ Controller } from '../../../models/controller';
import { DrawingService } from '../../../services/drawing.service';
@Component({
@@ -15,7 +15,7 @@ import { DrawingService } from '../../../services/drawing.service';
styleUrls: ['./drawing-resized.component.scss'],
})
export class DrawingResizedComponent implements OnInit, OnDestroy {
- @Input() server: Server;
+ @Input() controller:Controller ;
private drawingResized: Subscription;
constructor(
@@ -34,9 +34,9 @@ export class DrawingResizedComponent implements OnInit, OnDestroy {
let svgString = this.mapDrawingToSvgConverter.convert(resizedEvent.datum);
this.drawingService
- .updateSizeAndPosition(this.server, drawing, resizedEvent.x, resizedEvent.y, svgString)
- .subscribe((serverDrawing: Drawing) => {
- this.drawingsDataSource.update(serverDrawing);
+ .updateSizeAndPosition(this.controller, drawing, resizedEvent.x, resizedEvent.y, svgString)
+ .subscribe((controllerDrawing: Drawing) => {
+ this.drawingsDataSource.update(controllerDrawing);
});
}
diff --git a/src/app/components/drawings-listeners/interface-label-dragged/interface-label-dragged.component.ts b/src/app/components/drawings-listeners/interface-label-dragged/interface-label-dragged.component.ts
index 124674ea..0e82d866 100644
--- a/src/app/components/drawings-listeners/interface-label-dragged/interface-label-dragged.component.ts
+++ b/src/app/components/drawings-listeners/interface-label-dragged/interface-label-dragged.component.ts
@@ -5,7 +5,7 @@ import { DraggedDataEvent } from '../../../cartography/events/event-source';
import { LinksEventSource } from '../../../cartography/events/links-event-source';
import { MapLinkNode } from '../../../cartography/models/map/map-link-node';
import { Link } from '../../../models/link';
-import { Server } from '../../../models/server';
+import{ Controller } from '../../../models/controller';
import { LinkService } from '../../../services/link.service';
@Component({
@@ -14,7 +14,7 @@ import { LinkService } from '../../../services/link.service';
styleUrls: ['./interface-label-dragged.component.scss'],
})
export class InterfaceLabelDraggedComponent {
- @Input() server: Server;
+ @Input() controller:Controller ;
private interfaceDragged: Subscription;
constructor(
@@ -40,8 +40,8 @@ export class InterfaceLabelDraggedComponent {
link.nodes[1].label.y += draggedEvent.dy;
}
- this.linkService.updateNodes(this.server, link, link.nodes).subscribe((serverLink: Link) => {
- this.linksDataSource.update(serverLink);
+ this.linkService.updateNodes(this.controller, link, link.nodes).subscribe((controllerLink: Link) => {
+ this.linksDataSource.update(controllerLink);
});
}
diff --git a/src/app/components/drawings-listeners/link-created/link-created.component.ts b/src/app/components/drawings-listeners/link-created/link-created.component.ts
index f115ca90..a5c027c6 100644
--- a/src/app/components/drawings-listeners/link-created/link-created.component.ts
+++ b/src/app/components/drawings-listeners/link-created/link-created.component.ts
@@ -7,7 +7,7 @@ import { MapLinkCreated } from '../../../cartography/events/links';
import { LinksEventSource } from '../../../cartography/events/links-event-source';
import { Link } from '../../../models/link';
import { Project } from '../../../models/project';
-import { Server } from '../../../models/server';
+import{ Controller } from '../../../models/controller';
import { LinkService } from '../../../services/link.service';
import { ProjectService } from '../../../services/project.service';
@@ -17,7 +17,7 @@ import { ProjectService } from '../../../services/project.service';
styleUrls: ['./link-created.component.scss'],
})
export class LinkCreatedComponent implements OnInit, OnDestroy {
- @Input() server: Server;
+ @Input() controller:Controller ;
@Input() project: Project;
private linkCreated: Subscription;
@@ -87,7 +87,7 @@ export class LinkCreatedComponent implements OnInit, OnDestroy {
this.linkService
.createLink(
- this.server,
+ this.controller,
sourceNode,
sourcePort,
targetNode,
@@ -98,7 +98,7 @@ export class LinkCreatedComponent implements OnInit, OnDestroy {
yLabelTargetNode
)
.subscribe(() => {
- this.projectService.links(this.server, this.project.project_id).subscribe((links: Link[]) => {
+ this.projectService.links(this.controller, this.project.project_id).subscribe((links: Link[]) => {
this.linksDataSource.set(links);
});
});
diff --git a/src/app/components/drawings-listeners/node-dragged/node-dragged.component.ts b/src/app/components/drawings-listeners/node-dragged/node-dragged.component.ts
index 7730d4b2..f651e94c 100644
--- a/src/app/components/drawings-listeners/node-dragged/node-dragged.component.ts
+++ b/src/app/components/drawings-listeners/node-dragged/node-dragged.component.ts
@@ -6,7 +6,7 @@ import { NodesEventSource } from '../../../cartography/events/nodes-event-source
import { MapNode } from '../../../cartography/models/map/map-node';
import { Node } from '../../../cartography/models/node';
import { Project } from '../../../models/project';
-import { Server } from '../../../models/server';
+import{ Controller } from '../../../models/controller';
import { NodeService } from '../../../services/node.service';
@Component({
@@ -15,7 +15,7 @@ import { NodeService } from '../../../services/node.service';
styleUrls: ['./node-dragged.component.scss'],
})
export class NodeDraggedComponent implements OnInit, OnDestroy {
- @Input() server: Server;
+ @Input() controller:Controller ;
@Input() project: Project;
private nodeDragged: Subscription;
@@ -34,8 +34,8 @@ export class NodeDraggedComponent implements OnInit, OnDestroy {
node.x += draggedEvent.dx;
node.y += draggedEvent.dy;
- this.nodeService.updatePosition(this.server, this.project, node, node.x, node.y).subscribe((serverNode: Node) => {
- this.nodesDataSource.update(serverNode);
+ this.nodeService.updatePosition(this.controller, this.project, node, node.x, node.y).subscribe((controllerNode: Node) => {
+ this.nodesDataSource.update(controllerNode);
});
}
diff --git a/src/app/components/drawings-listeners/node-label-dragged/node-label-dragged.component.ts b/src/app/components/drawings-listeners/node-label-dragged/node-label-dragged.component.ts
index 9a3bcc47..4f878032 100644
--- a/src/app/components/drawings-listeners/node-label-dragged/node-label-dragged.component.ts
+++ b/src/app/components/drawings-listeners/node-label-dragged/node-label-dragged.component.ts
@@ -6,7 +6,7 @@ import { DraggedDataEvent } from '../../../cartography/events/event-source';
import { NodesEventSource } from '../../../cartography/events/nodes-event-source';
import { MapLabel } from '../../../cartography/models/map/map-label';
import { Node } from '../../../cartography/models/node';
-import { Server } from '../../../models/server';
+import{ Controller } from '../../../models/controller';
import { NodeService } from '../../../services/node.service';
@Component({
@@ -15,7 +15,7 @@ import { NodeService } from '../../../services/node.service';
styleUrls: ['./node-label-dragged.component.scss'],
})
export class NodeLabelDraggedComponent implements OnInit, OnDestroy {
- @Input() server: Server;
+ @Input() controller:Controller ;
private nodeLabelDragged: Subscription;
constructor(
@@ -38,8 +38,8 @@ export class NodeLabelDraggedComponent implements OnInit, OnDestroy {
const label = this.mapLabelToLabel.convert(mapLabel);
node.label = label;
- this.nodeService.updateLabel(this.server, node, node.label).subscribe((serverNode: Node) => {
- this.nodesDataSource.update(serverNode);
+ this.nodeService.updateLabel(this.controller, node, node.label).subscribe((controllerNode: Node) => {
+ this.nodesDataSource.update(controllerNode);
});
}
diff --git a/src/app/components/drawings-listeners/text-added/text-added.component.ts b/src/app/components/drawings-listeners/text-added/text-added.component.ts
index d14311ee..dfd05038 100644
--- a/src/app/components/drawings-listeners/text-added/text-added.component.ts
+++ b/src/app/components/drawings-listeners/text-added/text-added.component.ts
@@ -9,7 +9,7 @@ import { Context } from '../../../cartography/models/context';
import { Drawing } from '../../../cartography/models/drawing';
import { TextElement } from '../../../cartography/models/drawings/text-element';
import { Project } from '../../../models/project';
-import { Server } from '../../../models/server';
+import{ Controller } from '../../../models/controller';
import { DrawingService } from '../../../services/drawing.service';
@Component({
@@ -18,7 +18,7 @@ import { DrawingService } from '../../../services/drawing.service';
styleUrls: ['./text-added.component.scss'],
})
export class TextAddedComponent implements OnInit, OnDestroy {
- @Input() server: Server;
+ @Input() controller: Controller;
@Input() project: Project;
@Output() drawingSaved = new EventEmitter();
private textAdded: Subscription;
@@ -43,7 +43,7 @@ export class TextAddedComponent implements OnInit, OnDestroy {
this.drawingService
.add(
- this.server,
+ this.controller,
this.project.project_id,
(evt.x - (this.context.getZeroZeroTransformationPoint().x + this.context.transformation.x)) /
this.context.transformation.k,
@@ -51,8 +51,8 @@ export class TextAddedComponent implements OnInit, OnDestroy {
this.context.transformation.k,
svgText
)
- .subscribe((serverDrawing: Drawing) => {
- this.drawingsDataSource.add(serverDrawing);
+ .subscribe((controllerDrawing: Drawing) => {
+ this.drawingsDataSource.add(controllerDrawing);
this.drawingSaved.emit(true);
});
}
diff --git a/src/app/components/drawings-listeners/text-edited/text-edited.component.ts b/src/app/components/drawings-listeners/text-edited/text-edited.component.ts
index d5d5eaec..32a138e5 100644
--- a/src/app/components/drawings-listeners/text-edited/text-edited.component.ts
+++ b/src/app/components/drawings-listeners/text-edited/text-edited.component.ts
@@ -7,7 +7,7 @@ import { TextEditedDataEvent } from '../../../cartography/events/event-source';
import { Drawing } from '../../../cartography/models/drawing';
import { TextElement } from '../../../cartography/models/drawings/text-element';
import { MapDrawing } from '../../../cartography/models/map/map-drawing';
-import { Server } from '../../../models/server';
+import{ Controller } from '../../../models/controller';
import { DrawingService } from '../../../services/drawing.service';
@Component({
@@ -16,7 +16,7 @@ import { DrawingService } from '../../../services/drawing.service';
styleUrls: ['./text-edited.component.scss'],
})
export class TextEditedComponent implements OnInit, OnDestroy {
- @Input() server: Server;
+ @Input() controller: Controller;
private textEdited: Subscription;
constructor(
@@ -38,8 +38,8 @@ export class TextEditedComponent implements OnInit, OnDestroy {
let drawing = this.drawingsDataSource.get(evt.textDrawingId);
- this.drawingService.updateText(this.server, drawing, svgString).subscribe((serverDrawing: Drawing) => {
- this.drawingsDataSource.update(serverDrawing);
+ this.drawingService.updateText(this.controller, drawing, svgString).subscribe((controllerDrawing: Drawing) => {
+ this.drawingsDataSource.update(controllerDrawing);
this.drawingsEventSource.textSaved.emit(true);
});
}
diff --git a/src/app/components/export-portable-project/export-portable-project.component.html b/src/app/components/export-portable-project/export-portable-project.component.html
index f232dba0..de20ebe8 100644
--- a/src/app/components/export-portable-project/export-portable-project.component.html
+++ b/src/app/components/export-portable-project/export-portable-project.component.html
@@ -21,7 +21,7 @@
{{
- compressionValue.name
+ compressionValue?.name
}}
diff --git a/src/app/components/export-portable-project/export-portable-project.component.ts b/src/app/components/export-portable-project/export-portable-project.component.ts
index b4f05311..3f348646 100644
--- a/src/app/components/export-portable-project/export-portable-project.component.ts
+++ b/src/app/components/export-portable-project/export-portable-project.component.ts
@@ -2,7 +2,7 @@ import { Component, Inject, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { Project } from '../../models/project';
-import { Server } from '../../models/server';
+import{ Controller } from '../../models/controller';
import { ProjectService } from '../../services/project.service';
@Component({
@@ -16,7 +16,7 @@ export class ExportPortableProjectComponent implements OnInit {
compression_methods: any = [];
compression_level: any = [];
compression_filter_value: any = [];
- server: Server;
+ controller:Controller ;
project: Project;
index: number = 4;
fileName: string;
@@ -30,7 +30,7 @@ export class ExportPortableProjectComponent implements OnInit {
) {}
async ngOnInit() {
- this.server = this.data.serverDetails;
+ this.controller = this.data.controllerDetails;
this.project = this.data.projectDetails;
this.fileName = this.project.name + '.gns3project';
await this.formControls();
@@ -65,7 +65,7 @@ export class ExportPortableProjectComponent implements OnInit {
exportPortableProject() {
this.isExport = true;
this.export_project_form.value.compression = this.export_project_form.value.compression.value ?? 'zstd';
- window.location.assign(this.projectService.getexportPortableProjectPath(this.server, this.project.project_id, this.export_project_form.value))
+ window.location.assign(this.projectService.getexportPortableProjectPath(this.controller, this.project.project_id, this.export_project_form.value))
this.dialogRef.close();
}
}
diff --git a/src/app/components/group-details/add-role-to-group/add-role-to-group.component.html b/src/app/components/group-details/add-role-to-group/add-role-to-group.component.html
new file mode 100644
index 00000000..dff5bcd0
--- /dev/null
+++ b/src/app/components/group-details/add-role-to-group/add-role-to-group.component.html
@@ -0,0 +1,15 @@
+
+
Add Role To group: {{data.group.name}}
+
+
+
+ Search user
+
+
+
+
+
+
{{role.name}}
+
add
+
+
diff --git a/src/app/components/group-details/add-role-to-group/add-role-to-group.component.scss b/src/app/components/group-details/add-role-to-group/add-role-to-group.component.scss
new file mode 100644
index 00000000..01cd6a62
--- /dev/null
+++ b/src/app/components/group-details/add-role-to-group/add-role-to-group.component.scss
@@ -0,0 +1,35 @@
+:host {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+}
+
+.title {
+ width: 100%;
+ text-align: center;
+}
+
+.filter {
+ display: flex;
+ width: 600px;
+ justify-content: center;
+ margin-bottom: 50px;
+}
+
+mat-form-field {
+ width: 600px;
+}
+
+input {
+ width: 100%;
+}
+
+.userList {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 10px;
+}
+
+mat-spinner {
+ width: 36px;
+}
diff --git a/src/app/components/group-details/add-role-to-group/add-role-to-group.component.spec.ts b/src/app/components/group-details/add-role-to-group/add-role-to-group.component.spec.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/components/group-details/add-role-to-group/add-role-to-group.component.ts b/src/app/components/group-details/add-role-to-group/add-role-to-group.component.ts
new file mode 100644
index 00000000..e6bf8350
--- /dev/null
+++ b/src/app/components/group-details/add-role-to-group/add-role-to-group.component.ts
@@ -0,0 +1,90 @@
+/*
+* Software Name : GNS3 Web UI
+* Version: 3
+* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
+* SPDX-License-Identifier: GPL-3.0-or-later
+*
+* This software is distributed under the GPL-3.0 or any later version,
+* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
+* or see the "LICENSE" file for more details.
+*
+* Author: Sylvain MATHIEU, Elise LEBEAU
+*/
+import {Component, Inject, OnInit} from '@angular/core';
+import {BehaviorSubject, forkJoin, timer} from "rxjs";
+import {User} from "@models/users/user";
+import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
+import {Controller} from "@models/controller";
+import {Group} from "@models/groups/group";
+import {UserService} from "@services/user.service";
+import {GroupService} from "@services/group.service";
+import {ToasterService} from "@services/toaster.service";
+import {Role} from "@models/api/role";
+import {RoleService} from "@services/role.service";
+
+@Component({
+ selector: 'app-add-role-to-group',
+ templateUrl: './add-role-to-group.component.html',
+ styleUrls: ['./add-role-to-group.component.scss']
+})
+export class AddRoleToGroupComponent implements OnInit {
+ roles = new BehaviorSubject([]);
+ displayedRoles = new BehaviorSubject([]);
+
+ searchText: string;
+ loading = false;
+
+ constructor(private dialog: MatDialogRef,
+ @Inject(MAT_DIALOG_DATA) public data: { controller: Controller; group: Group },
+ private groupService: GroupService,
+ private roleService: RoleService,
+ private toastService: ToasterService) {
+ }
+
+ ngOnInit(): void {
+ this.getRoles();
+ }
+
+ onSearch() {
+ timer(500)
+ .subscribe(() => {
+ const displayedUsers = this.roles.value.filter((roles: Role) => {
+ return roles.name.includes(this.searchText);
+ });
+
+ this.displayedRoles.next(displayedUsers);
+ });
+ }
+
+ getRoles() {
+ forkJoin([
+ this.roleService.get(this.data.controller),
+ this.groupService.getGroupRole(this.data.controller, this.data.group.user_group_id)
+ ]).subscribe((results) => {
+ const [globalRoles, groupRoles] = results;
+ const roles = globalRoles.filter((role: Role) => {
+ return !groupRoles.find((r: Role) => r.role_id === role.role_id);
+ });
+
+ this.roles.next(roles);
+ this.displayedRoles.next(roles);
+
+ });
+
+ }
+
+ addRole(role: Role) {
+ this.loading = true;
+ this.groupService
+ .addRoleToGroup(this.data.controller, this.data.group, role)
+ .subscribe(() => {
+ this.toastService.success(`role ${role.name} was added`);
+ this.getRoles();
+ this.loading = false;
+ }, (err) => {
+ console.log(err);
+ this.toastService.error(`error while adding role ${role.name} to group ${this.data.group.name}`);
+ this.loading = false;
+ });
+ }
+}
diff --git a/src/app/components/group-details/add-user-to-group-dialog/add-user-to-group-dialog.component.html b/src/app/components/group-details/add-user-to-group-dialog/add-user-to-group-dialog.component.html
new file mode 100644
index 00000000..5c397473
--- /dev/null
+++ b/src/app/components/group-details/add-user-to-group-dialog/add-user-to-group-dialog.component.html
@@ -0,0 +1,16 @@
+
+
Add User To group: {{data.group.name}}
+
+
+
+ Search user
+
+
+
+
+
+
{{user.username}}
+
{{user.email}}
+
add
+
+
diff --git a/src/app/components/group-details/add-user-to-group-dialog/add-user-to-group-dialog.component.scss b/src/app/components/group-details/add-user-to-group-dialog/add-user-to-group-dialog.component.scss
new file mode 100644
index 00000000..01cd6a62
--- /dev/null
+++ b/src/app/components/group-details/add-user-to-group-dialog/add-user-to-group-dialog.component.scss
@@ -0,0 +1,35 @@
+:host {
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+}
+
+.title {
+ width: 100%;
+ text-align: center;
+}
+
+.filter {
+ display: flex;
+ width: 600px;
+ justify-content: center;
+ margin-bottom: 50px;
+}
+
+mat-form-field {
+ width: 600px;
+}
+
+input {
+ width: 100%;
+}
+
+.userList {
+ display: flex;
+ justify-content: space-between;
+ margin-bottom: 10px;
+}
+
+mat-spinner {
+ width: 36px;
+}
diff --git a/src/app/components/group-details/add-user-to-group-dialog/add-user-to-group-dialog.component.spec.ts b/src/app/components/group-details/add-user-to-group-dialog/add-user-to-group-dialog.component.spec.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/components/group-details/add-user-to-group-dialog/add-user-to-group-dialog.component.ts b/src/app/components/group-details/add-user-to-group-dialog/add-user-to-group-dialog.component.ts
new file mode 100644
index 00000000..4aac2293
--- /dev/null
+++ b/src/app/components/group-details/add-user-to-group-dialog/add-user-to-group-dialog.component.ts
@@ -0,0 +1,91 @@
+/*
+* Software Name : GNS3 Web UI
+* Version: 3
+* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
+* SPDX-License-Identifier: GPL-3.0-or-later
+*
+* This software is distributed under the GPL-3.0 or any later version,
+* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
+* or see the "LICENSE" file for more details.
+*
+* Author: Sylvain MATHIEU, Elise LEBEAU
+*/
+import {Component, Inject, OnInit} from '@angular/core';
+import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
+import {UserService} from "@services/user.service";
+import {Controller} from "@models/controller";
+import {BehaviorSubject, forkJoin, observable, Observable, timer} from "rxjs";
+import {User} from "@models/users/user";
+import {GroupService} from "@services/group.service";
+import {Group} from "@models/groups/group";
+import {tap} from "rxjs/operators";
+import {ToasterService} from "@services/toaster.service";
+
+@Component({
+ selector: 'app-add-user-to-group-dialog',
+ templateUrl: './add-user-to-group-dialog.component.html',
+ styleUrls: ['./add-user-to-group-dialog.component.scss']
+})
+export class AddUserToGroupDialogComponent implements OnInit {
+ users = new BehaviorSubject([]);
+ displayedUsers = new BehaviorSubject([]);
+
+ searchText: string;
+ loading = false;
+
+ constructor(private dialog: MatDialogRef,
+ @Inject(MAT_DIALOG_DATA) public data: { controller: Controller; group: Group },
+ private userService: UserService,
+ private groupService: GroupService,
+ private toastService: ToasterService) {
+ }
+
+ ngOnInit(): void {
+ this.getUsers();
+ }
+
+ onSearch() {
+ timer(500)
+ .subscribe(() => {
+ const displayedUsers = this.users.value.filter((user: User) => {
+ return user.username.includes(this.searchText) || user.email?.includes(this.searchText);
+ });
+
+ this.displayedUsers.next(displayedUsers);
+ });
+ }
+
+ getUsers() {
+ forkJoin([
+ this.userService.list(this.data.controller),
+ this.groupService.getGroupMember(this.data.controller, this.data.group.user_group_id)
+ ]).subscribe((results) => {
+ const [userList, members] = results;
+ const users = userList.filter((user: User) => {
+ return !members.find((u: User) => u.user_id === user.user_id);
+ });
+
+ this.users.next(users);
+ this.displayedUsers.next(users);
+
+ });
+
+ }
+
+ addUser(user: User) {
+ this.loading = true;
+ this.groupService
+ .addMemberToGroup(this.data.controller, this.data.group, user)
+ .subscribe(() => {
+ this.toastService.success(`user ${user.username} was added`);
+ this.getUsers();
+ this.loading = false;
+ }, (err) => {
+ console.log(err);
+ this.toastService.error(`error while adding user ${user.username} to group ${this.data.group.name}`);
+ this.loading = false;
+ });
+
+
+ }
+}
diff --git a/src/app/components/group-details/group-details.component.html b/src/app/components/group-details/group-details.component.html
new file mode 100644
index 00000000..9bec7a3a
--- /dev/null
+++ b/src/app/components/group-details/group-details.component.html
@@ -0,0 +1,72 @@
+
+
+
diff --git a/src/app/components/group-details/group-details.component.scss b/src/app/components/group-details/group-details.component.scss
new file mode 100644
index 00000000..e8417eab
--- /dev/null
+++ b/src/app/components/group-details/group-details.component.scss
@@ -0,0 +1,51 @@
+.main {
+ display: flex;
+ justify-content: space-around;
+}
+
+.details {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+}
+
+.members {
+ display: flex;
+ flex-direction: column;
+ justify-content: stretch;
+}
+
+.members > div {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ margin-bottom: 5px;
+}
+
+.clickable {
+ cursor: pointer;
+}
+
+.details > div {
+ margin-bottom: 20px;
+}
+
+.button-div {
+ float: right;
+}
+
+.members > .search {
+ display: flex;
+ flex-direction: row;
+ justify-content: stretch;
+ width: 100%;
+}
+mat-form-field {
+ width: 100%;
+}
+
+.roles {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+}
diff --git a/src/app/components/group-details/group-details.component.spec.ts b/src/app/components/group-details/group-details.component.spec.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/components/group-details/group-details.component.ts b/src/app/components/group-details/group-details.component.ts
new file mode 100644
index 00000000..da341a1f
--- /dev/null
+++ b/src/app/components/group-details/group-details.component.ts
@@ -0,0 +1,152 @@
+/*
+* Software Name : GNS3 Web UI
+* Version: 3
+* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
+* SPDX-License-Identifier: GPL-3.0-or-later
+*
+* This software is distributed under the GPL-3.0 or any later version,
+* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
+* or see the "LICENSE" file for more details.
+*
+* Author: Sylvain MATHIEU, Elise LEBEAU
+*/
+import {Component, OnInit} from '@angular/core';
+import {ActivatedRoute} from "@angular/router";
+import {Controller} from "@models/controller";
+import {Group} from "@models/groups/group";
+import {User} from "@models/users/user";
+import {FormControl, FormGroup} from "@angular/forms";
+import {MatDialog} from "@angular/material/dialog";
+import {AddUserToGroupDialogComponent} from "@components/group-details/add-user-to-group-dialog/add-user-to-group-dialog.component";
+import {RemoveToGroupDialogComponent} from "@components/group-details/remove-to-group-dialog/remove-to-group-dialog.component";
+import {GroupService} from "@services/group.service";
+import {ToasterService} from "@services/toaster.service";
+import {PageEvent} from "@angular/material/paginator";
+import {Role} from "@models/api/role";
+import {AddRoleToGroupComponent} from "@components/group-details/add-role-to-group/add-role-to-group.component";
+
+@Component({
+ selector: 'app-group-details',
+ templateUrl: './group-details.component.html',
+ styleUrls: ['./group-details.component.scss']
+})
+export class GroupDetailsComponent implements OnInit {
+ controller: Controller;
+ group: Group;
+ members: User[];
+ editGroupForm: FormGroup;
+ pageEvent: PageEvent | undefined;
+ searchMembers: string;
+ roles: Role[];
+
+ constructor(private route: ActivatedRoute,
+ private dialog: MatDialog,
+ private groupService: GroupService,
+ private toastService: ToasterService) {
+
+ this.editGroupForm = new FormGroup({
+ groupname: new FormControl(''),
+ });
+
+ this.route.data.subscribe((d: { controller: Controller; group: Group, members: User[], roles: Role[] }) => {
+
+ this.controller = d.controller;
+ this.group = d.group;
+ this.roles = d.roles;
+ this.members = d.members.sort((a: User, b: User) => a.username.toLowerCase().localeCompare(b.username.toLowerCase()));
+ this.editGroupForm.setValue({groupname: this.group.name});
+ });
+ }
+
+ ngOnInit(): void {
+
+ }
+
+ onUpdate() {
+ this.groupService.update(this.controller, this.group)
+ .subscribe(() => {
+ this.toastService.success(`group updated`);
+ }, (error) => {
+ this.toastService.error('Error: Cannot update group');
+ console.log(error);
+ });
+ }
+
+ openAddRoleDialog() {
+ this.dialog
+ .open(AddRoleToGroupComponent,
+ {
+ width: '700px', height: '500px',
+ data: {controller: this.controller, group: this.group}
+ })
+ .afterClosed()
+ .subscribe(() => {
+ this.reloadRoles();
+ });
+ }
+ openAddUserDialog() {
+ this.dialog
+ .open(AddUserToGroupDialogComponent,
+ {
+ width: '700px', height: '500px',
+ data: {controller: this.controller, group: this.group}
+ })
+ .afterClosed()
+ .subscribe(() => {
+ this.reloadMembers();
+ });
+ }
+
+ openRemoveUserDialog(user: User) {
+ this.dialog.open(RemoveToGroupDialogComponent,
+ {width: '500px', height: '200px', data: {name: user.username}})
+ .afterClosed()
+ .subscribe((confirm: boolean) => {
+ if (confirm) {
+ this.groupService.removeUser(this.controller, this.group, user)
+ .subscribe(() => {
+ this.toastService.success(`User ${user.username} was removed`);
+ this.reloadMembers();
+ },
+ (error) => {
+ this.toastService.error(`Error while removing user ${user.username} from ${this.group.name}`);
+ console.log(error);
+ });
+ }
+ });
+ }
+
+
+ openRemoveRoleDialog(role: Role) {
+ this.dialog.open(RemoveToGroupDialogComponent,
+ {width: '500px', height: '200px', data: {name: role.name}})
+ .afterClosed()
+ .subscribe((confirm: string) => {
+ if (confirm) {
+ this.groupService.removeRole(this.controller, this.group, role)
+ .subscribe(() => {
+ this.toastService.success(`Role ${role.name} was removed`);
+ this.reloadRoles();
+ },
+ (error) => {
+ this.toastService.error(`Error while removing role ${role.name} from ${this.group.name}`);
+ console.log(error);
+ });
+ }
+ });
+ }
+
+ reloadMembers() {
+ this.groupService.getGroupMember(this.controller, this.group.user_group_id)
+ .subscribe((members: User[]) => {
+ this.members = members;
+ });
+ }
+
+ reloadRoles() {
+ this.groupService.getGroupRole(this.controller, this.group.user_group_id)
+ .subscribe((roles: Role[]) => {
+ this.roles = roles;
+ });
+ }
+}
diff --git a/src/app/components/group-details/members-filter.pipe.spec.ts b/src/app/components/group-details/members-filter.pipe.spec.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/components/group-details/members-filter.pipe.ts b/src/app/components/group-details/members-filter.pipe.ts
new file mode 100644
index 00000000..570a1456
--- /dev/null
+++ b/src/app/components/group-details/members-filter.pipe.ts
@@ -0,0 +1,32 @@
+/*
+* Software Name : GNS3 Web UI
+* Version: 3
+* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
+* SPDX-License-Identifier: GPL-3.0-or-later
+*
+* This software is distributed under the GPL-3.0 or any later version,
+* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
+* or see the "LICENSE" file for more details.
+*
+* Author: Sylvain MATHIEU, Elise LEBEAU
+*/
+import {Pipe, PipeTransform} from '@angular/core';
+import {User} from "@models/users/user";
+
+@Pipe({
+ name: 'membersFilter'
+})
+export class MembersFilterPipe implements PipeTransform {
+
+ transform(members: User[], filterText: string): User[] {
+ if (!members) {
+ return [];
+ }
+ if (filterText === undefined || filterText === '') {
+ return members;
+ }
+
+ return members.filter((member: User) => member.username.toLowerCase().includes(filterText.toLowerCase()));
+ }
+
+}
diff --git a/src/app/components/group-details/paginator.pipe.spec.ts b/src/app/components/group-details/paginator.pipe.spec.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/components/group-details/paginator.pipe.ts b/src/app/components/group-details/paginator.pipe.ts
new file mode 100644
index 00000000..7bc69424
--- /dev/null
+++ b/src/app/components/group-details/paginator.pipe.ts
@@ -0,0 +1,41 @@
+/*
+* Software Name : GNS3 Web UI
+* Version: 3
+* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
+* SPDX-License-Identifier: GPL-3.0-or-later
+*
+* This software is distributed under the GPL-3.0 or any later version,
+* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
+* or see the "LICENSE" file for more details.
+*
+* Author: Sylvain MATHIEU, Elise LEBEAU
+*/
+import {Pipe, PipeTransform} from '@angular/core';
+import {User} from "@models/users/user";
+import {PageEvent} from "@angular/material/paginator";
+
+@Pipe({
+ name: 'paginator'
+})
+export class PaginatorPipe implements PipeTransform {
+
+ transform(elements: T[] | undefined, paginatorEvent: PageEvent | undefined): T[] {
+ if (!elements) {
+ return [];
+ }
+
+ if (!paginatorEvent) {
+ paginatorEvent = {
+ length: elements.length,
+ pageIndex: 0,
+ pageSize: 5
+ };
+ }
+
+
+ return elements.slice(
+ paginatorEvent.pageIndex * paginatorEvent.pageSize,
+ (paginatorEvent.pageIndex + 1) * paginatorEvent.pageSize);
+ }
+
+}
diff --git a/src/app/components/group-details/remove-to-group-dialog/remove-to-group-dialog.component.html b/src/app/components/group-details/remove-to-group-dialog/remove-to-group-dialog.component.html
new file mode 100644
index 00000000..e5811dad
--- /dev/null
+++ b/src/app/components/group-details/remove-to-group-dialog/remove-to-group-dialog.component.html
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/src/app/components/group-details/remove-to-group-dialog/remove-to-group-dialog.component.scss b/src/app/components/group-details/remove-to-group-dialog/remove-to-group-dialog.component.scss
new file mode 100644
index 00000000..8ebc2b8a
--- /dev/null
+++ b/src/app/components/group-details/remove-to-group-dialog/remove-to-group-dialog.component.scss
@@ -0,0 +1,20 @@
+:host {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+}
+
+.header {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ text-align: center;
+ margin-bottom: 20px;
+}
+
+.button {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-evenly;
+}
diff --git a/src/app/components/group-details/remove-to-group-dialog/remove-to-group-dialog.component.spec.ts b/src/app/components/group-details/remove-to-group-dialog/remove-to-group-dialog.component.spec.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/components/group-details/remove-to-group-dialog/remove-to-group-dialog.component.ts b/src/app/components/group-details/remove-to-group-dialog/remove-to-group-dialog.component.ts
new file mode 100644
index 00000000..2e86af2e
--- /dev/null
+++ b/src/app/components/group-details/remove-to-group-dialog/remove-to-group-dialog.component.ts
@@ -0,0 +1,37 @@
+/*
+* Software Name : GNS3 Web UI
+* Version: 3
+* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
+* SPDX-License-Identifier: GPL-3.0-or-later
+*
+* This software is distributed under the GPL-3.0 or any later version,
+* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
+* or see the "LICENSE" file for more details.
+*
+* Author: Sylvain MATHIEU, Elise LEBEAU
+*/
+import {Component, Inject, OnInit} from '@angular/core';
+import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
+import {User} from "@models/users/user";
+
+@Component({
+ selector: 'app-remove-user-to-group-dialog',
+ templateUrl: './remove-to-group-dialog.component.html',
+ styleUrls: ['./remove-to-group-dialog.component.scss']
+})
+export class RemoveToGroupDialogComponent implements OnInit {
+
+ constructor(private dialogRef: MatDialogRef,
+ @Inject(MAT_DIALOG_DATA) public data: { name: string }) { }
+
+ ngOnInit(): void {
+ }
+
+ onCancel() {
+ this.dialogRef.close(false);
+ }
+
+ onConfirm() {
+ this.dialogRef.close(true);
+ }
+}
diff --git a/src/app/components/group-details/remove-user-to-group-dialog/remove-user-to-group-dialog.component.ts b/src/app/components/group-details/remove-user-to-group-dialog/remove-user-to-group-dialog.component.ts
new file mode 100644
index 00000000..ef70a89f
--- /dev/null
+++ b/src/app/components/group-details/remove-user-to-group-dialog/remove-user-to-group-dialog.component.ts
@@ -0,0 +1,37 @@
+/*
+* Software Name : GNS3 Web UI
+* Version: 3
+* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
+* SPDX-License-Identifier: GPL-3.0-or-later
+*
+* This software is distributed under the GPL-3.0 or any later version,
+* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
+* or see the "LICENSE" file for more details.
+*
+* Author: Sylvain MATHIEU, Elise LEBEAU
+*/
+import {Component, Inject, OnInit} from '@angular/core';
+import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
+import {User} from "@models/users/user";
+
+@Component({
+ selector: 'app-remove-user-to-group-dialog',
+ templateUrl: './remove-user-to-group-dialog.component.html',
+ styleUrls: ['./remove-user-to-group-dialog.component.scss']
+})
+export class RemoveUserToGroupDialogComponent implements OnInit {
+
+ constructor(private dialogRef: MatDialogRef,
+ @Inject(MAT_DIALOG_DATA) public data: { user: User }) { }
+
+ ngOnInit(): void {
+ }
+
+ onCancel() {
+ this.dialogRef.close();
+ }
+
+ onConfirm() {
+ this.dialogRef.close(this.data.user);
+ }
+}
diff --git a/src/app/components/group-management/add-group-dialog/GroupNameValidator.ts b/src/app/components/group-management/add-group-dialog/GroupNameValidator.ts
new file mode 100644
index 00000000..09b9cda8
--- /dev/null
+++ b/src/app/components/group-management/add-group-dialog/GroupNameValidator.ts
@@ -0,0 +1,26 @@
+/*
+* Software Name : GNS3 Web UI
+* Version: 3
+* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
+* SPDX-License-Identifier: GPL-3.0-or-later
+*
+* This software is distributed under the GPL-3.0 or any later version,
+* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
+* or see the "LICENSE" file for more details.
+*
+* Author: Sylvain MATHIEU, Elise LEBEAU
+*/
+import { Injectable } from '@angular/core';
+
+@Injectable()
+export class GroupNameValidator {
+ get(groupName) {
+ const pattern = new RegExp(/[~`!#$%\^&*+=\[\]\\';,/{}|\\":<>\?]/);
+
+ if (!pattern.test(groupName.value)) {
+ return null;
+ }
+
+ return { invalidName: true };
+ }
+}
diff --git a/src/app/components/group-management/add-group-dialog/add-group-dialog.component.html b/src/app/components/group-management/add-group-dialog/add-group-dialog.component.html
new file mode 100644
index 00000000..8bb21d51
--- /dev/null
+++ b/src/app/components/group-management/add-group-dialog/add-group-dialog.component.html
@@ -0,0 +1,57 @@
+Create new group
+
+
+ Add users to group:
+
+ Users
+
+
+
+ {{user.username}} - {{user.email}}
+
+
+
+
+
+
+
+
{{user.username}}
+
{{user.email}}
+
delete
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/components/group-management/add-group-dialog/add-group-dialog.component.scss b/src/app/components/group-management/add-group-dialog/add-group-dialog.component.scss
new file mode 100644
index 00000000..0ab6fbfb
--- /dev/null
+++ b/src/app/components/group-management/add-group-dialog/add-group-dialog.component.scss
@@ -0,0 +1,25 @@
+.file-name-form-field {
+ width: 100%;
+}
+
+.project-snackbar {
+ background: #2196f3;
+}
+
+.userList {
+ display: flex;
+ margin: 10px;
+ justify-content: space-between;
+ flex: 1 1 auto;
+}
+
+.users {
+ display: flex;
+ height: 180px;
+ overflow: auto;
+ flex-direction: column;
+}
+
+.button-div {
+ float: right;
+}
diff --git a/src/app/components/group-management/add-group-dialog/add-group-dialog.component.spec.ts b/src/app/components/group-management/add-group-dialog/add-group-dialog.component.spec.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/components/group-management/add-group-dialog/add-group-dialog.component.ts b/src/app/components/group-management/add-group-dialog/add-group-dialog.component.ts
new file mode 100644
index 00000000..48deaeda
--- /dev/null
+++ b/src/app/components/group-management/add-group-dialog/add-group-dialog.component.ts
@@ -0,0 +1,137 @@
+/*
+* Software Name : GNS3 Web UI
+* Version: 3
+* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
+* SPDX-License-Identifier: GPL-3.0-or-later
+*
+* This software is distributed under the GPL-3.0 or any later version,
+* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
+* or see the "LICENSE" file for more details.
+*
+* Author: Sylvain MATHIEU, Elise LEBEAU
+*/
+import {Component, Inject, OnInit} from '@angular/core';
+import {MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
+import {FormBuilder, FormControl, FormGroup, Validators} from "@angular/forms";
+import {groupNameAsyncValidator} from "@components/group-management/add-group-dialog/groupNameAsyncValidator";
+import {GroupNameValidator} from "@components/group-management/add-group-dialog/GroupNameValidator";
+import {GroupService} from "../../../services/group.service";
+import {Controller} from "../../../models/controller";
+import {BehaviorSubject, forkJoin, timer} from "rxjs";
+import {User} from "@models/users/user";
+import {UserService} from "@services/user.service";
+import {ToasterService} from "@services/toaster.service";
+import {PageEvent} from "@angular/material/paginator";
+import {Observable} from "rxjs/Rx";
+import {Group} from "@models/groups/group";
+import {map, startWith} from "rxjs/operators";
+
+@Component({
+ selector: 'app-add-group-dialog',
+ templateUrl: './add-group-dialog.component.html',
+ styleUrls: ['./add-group-dialog.component.scss'],
+ providers: [GroupNameValidator]
+})
+export class AddGroupDialogComponent implements OnInit {
+
+ groupNameForm: FormGroup;
+ controller: Controller;
+
+ users: User[];
+ usersToAdd: Set = new Set([]);
+ filteredUsers: Observable
+ loading = false;
+ autocompleteControl = new FormControl();
+
+
+
+ constructor(private dialogRef: MatDialogRef,
+ @Inject(MAT_DIALOG_DATA) public data: { controller: Controller },
+ private formBuilder: FormBuilder,
+ private groupNameValidator: GroupNameValidator,
+ private groupService: GroupService,
+ private userService: UserService,
+ private toasterService: ToasterService) {
+ }
+
+ ngOnInit(): void {
+ this.controller = this.data.controller;
+ this.groupNameForm = this.formBuilder.group({
+ groupName: new FormControl(
+ null,
+ [Validators.required, this.groupNameValidator.get],
+ [groupNameAsyncValidator(this.data.controller, this.groupService)]
+ ),
+ });
+ this.userService.list(this.controller)
+ .subscribe((users: User[]) => {
+ this.users = users;
+ this.filteredUsers = this.autocompleteControl.valueChanges.pipe(
+ startWith(''),
+ map(value => this._filter(value)),
+ );
+ })
+ }
+
+ onKeyDown(event) {
+ if (event.key === 'Enter') {
+ this.onAddClick();
+ }
+ }
+
+ get form() {
+ return this.groupNameForm.controls;
+ }
+
+ onAddClick() {
+ if (this.groupNameForm.invalid) {
+ return;
+ }
+ const groupName = this.groupNameForm.controls['groupName'].value;
+ const toAdd = Array.from(this.usersToAdd.values());
+
+
+ this.groupService.addGroup(this.controller, groupName)
+ .subscribe((group) => {
+ toAdd.forEach((user: User) => {
+ this.groupService.addMemberToGroup(this.controller, group, user)
+ .subscribe(() => {
+ this.toasterService.success(`user ${user.username} was added`);
+ },
+ (error) => {
+ this.toasterService.error(`An error occur while trying to add user ${user.username} to group ${groupName}`);
+ })
+ })
+ this.dialogRef.close(true);
+ }, (error) => {
+ this.toasterService.error(`An error occur while trying to create new group ${groupName}`);
+ this.dialogRef.close(false);
+ });
+ }
+
+ onNoClick() {
+ this.dialogRef.close();
+ }
+
+ selectedUser(user: User) {
+ this.usersToAdd.add(user);
+ }
+
+ delUser(user: User) {
+ this.usersToAdd.delete(user);
+ }
+
+ private _filter(value: string): User[] {
+ if (typeof value === 'string') {
+ const filterValue = value.toLowerCase();
+
+ return this.users.filter(option => option.username.toLowerCase().includes(filterValue)
+ || (option.email?.toLowerCase().includes(filterValue)));
+ }
+ }
+
+ displayFn(value): string {
+ return value && value.username ? value.username : '';
+ }
+
+}
diff --git a/src/app/components/group-management/add-group-dialog/groupNameAsyncValidator.ts b/src/app/components/group-management/add-group-dialog/groupNameAsyncValidator.ts
new file mode 100644
index 00000000..7ca6a54b
--- /dev/null
+++ b/src/app/components/group-management/add-group-dialog/groupNameAsyncValidator.ts
@@ -0,0 +1,29 @@
+/*
+* Software Name : GNS3 Web UI
+* Version: 3
+* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
+* SPDX-License-Identifier: GPL-3.0-or-later
+*
+* This software is distributed under the GPL-3.0 or any later version,
+* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
+* or see the "LICENSE" file for more details.
+*
+* Author: Sylvain MATHIEU, Elise LEBEAU
+*/
+import { FormControl } from '@angular/forms';
+import { timer } from 'rxjs';
+import { map, switchMap, tap } from 'rxjs/operators';
+import { Controller } from "../../../models/controller";
+import { GroupService } from "../../../services/group.service";
+
+export const groupNameAsyncValidator = (controller: Controller, groupService: GroupService) => {
+ return (control: FormControl) => {
+ return timer(500).pipe(
+ switchMap(() => groupService.getGroups(controller)),
+ map((response) => {
+ console.log(response);
+ return (response.find((n) => n.name === control.value) ? { projectExist: true } : null);
+ })
+ );
+ };
+};
diff --git a/src/app/components/group-management/delete-group-dialog/delete-group-dialog.component.html b/src/app/components/group-management/delete-group-dialog/delete-group-dialog.component.html
new file mode 100644
index 00000000..4ab22367
--- /dev/null
+++ b/src/app/components/group-management/delete-group-dialog/delete-group-dialog.component.html
@@ -0,0 +1,8 @@
+Are you sure to delete group named:
+{{group.name}}
+
+
+
+
diff --git a/src/app/components/group-management/delete-group-dialog/delete-group-dialog.component.scss b/src/app/components/group-management/delete-group-dialog/delete-group-dialog.component.scss
new file mode 100644
index 00000000..1b0fdabd
--- /dev/null
+++ b/src/app/components/group-management/delete-group-dialog/delete-group-dialog.component.scss
@@ -0,0 +1,6 @@
+:host {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
diff --git a/src/app/components/group-management/delete-group-dialog/delete-group-dialog.component.spec.ts b/src/app/components/group-management/delete-group-dialog/delete-group-dialog.component.spec.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/components/group-management/delete-group-dialog/delete-group-dialog.component.ts b/src/app/components/group-management/delete-group-dialog/delete-group-dialog.component.ts
new file mode 100644
index 00000000..b4af6d15
--- /dev/null
+++ b/src/app/components/group-management/delete-group-dialog/delete-group-dialog.component.ts
@@ -0,0 +1,37 @@
+/*
+* Software Name : GNS3 Web UI
+* Version: 3
+* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
+* SPDX-License-Identifier: GPL-3.0-or-later
+*
+* This software is distributed under the GPL-3.0 or any later version,
+* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
+* or see the "LICENSE" file for more details.
+*
+* Author: Sylvain MATHIEU, Elise LEBEAU
+*/
+import {Component, Inject, OnInit} from '@angular/core';
+import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
+import {Group} from "@models/groups/group";
+
+@Component({
+ selector: 'app-delete-group-dialog',
+ templateUrl: './delete-group-dialog.component.html',
+ styleUrls: ['./delete-group-dialog.component.scss']
+})
+export class DeleteGroupDialogComponent implements OnInit {
+
+ constructor(private dialogRef: MatDialogRef,
+ @Inject(MAT_DIALOG_DATA) public data: { groups: Group[] }) { }
+
+ ngOnInit(): void {
+ }
+
+ onCancel() {
+ this.dialogRef.close();
+ }
+
+ onDelete() {
+ this.dialogRef.close(true);
+ }
+}
diff --git a/src/app/components/group-management/group-management.component.html b/src/app/components/group-management/group-management.component.html
new file mode 100644
index 00000000..5773ec49
--- /dev/null
+++ b/src/app/components/group-management/group-management.component.html
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ |
+
+
+
+ |
+
+
+
+ Name |
+ {{element.name}} |
+
+
+
+ Creation date |
+ {{element.created_at}} |
+
+
+
+
+ Last update |
+ {{element.updated_at}} |
+
+
+
+
+ is build in |
+ {{element.is_builtin}} |
+
+
+
+ |
+ |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/components/group-management/group-management.component.scss b/src/app/components/group-management/group-management.component.scss
new file mode 100644
index 00000000..2b5547ab
--- /dev/null
+++ b/src/app/components/group-management/group-management.component.scss
@@ -0,0 +1,26 @@
+table {
+ width: 100%;
+}
+
+.full-width {
+ width: 940px;
+ margin-left: -470px;
+ left: 50%;
+}
+
+.add-group-button {
+ height: 40px;
+ width: 160px;
+ margin: 20px;
+}
+
+.loader {
+ position: absolute;
+ margin: auto;
+ height: 175px;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ top: 0;
+ width: 175px;
+}
diff --git a/src/app/components/group-management/group-management.component.spec.ts b/src/app/components/group-management/group-management.component.spec.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/components/group-management/group-management.component.ts b/src/app/components/group-management/group-management.component.ts
new file mode 100644
index 00000000..6a459b5e
--- /dev/null
+++ b/src/app/components/group-management/group-management.component.ts
@@ -0,0 +1,133 @@
+/*
+* Software Name : GNS3 Web UI
+* Version: 3
+* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
+* SPDX-License-Identifier: GPL-3.0-or-later
+*
+* This software is distributed under the GPL-3.0 or any later version,
+* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
+* or see the "LICENSE" file for more details.
+*
+* Author: Sylvain MATHIEU, Elise LEBEAU
+*/
+import {Component, OnInit, QueryList, ViewChild, ViewChildren} from '@angular/core';
+import {ActivatedRoute, Router} from "@angular/router";
+import {ControllerService} from "../../services/controller.service";
+import {ToasterService} from "../../services/toaster.service";
+import {GroupService} from "../../services/group.service";
+import {Controller} from "../../models/controller";
+import {Group} from "../../models/groups/group";
+import {MatSort, Sort} from "@angular/material/sort";
+import {MatDialog} from "@angular/material/dialog";
+import {AddGroupDialogComponent} from "@components/group-management/add-group-dialog/add-group-dialog.component";
+import {DeleteGroupDialogComponent} from "@components/group-management/delete-group-dialog/delete-group-dialog.component";
+import {SelectionModel} from "@angular/cdk/collections";
+import {forkJoin} from "rxjs";
+import {MatPaginator} from "@angular/material/paginator";
+import {MatTableDataSource} from "@angular/material/table";
+
+
+@Component({
+ selector: 'app-group-management',
+ templateUrl: './group-management.component.html',
+ styleUrls: ['./group-management.component.scss']
+})
+export class GroupManagementComponent implements OnInit {
+ controller: Controller;
+
+ @ViewChildren('groupsPaginator') groupsPaginator: QueryList;
+ @ViewChildren('groupsSort') groupsSort: QueryList;
+
+ public displayedColumns = ['select', 'name', 'created_at', 'updated_at', 'is_builtin', 'delete'];
+ selection = new SelectionModel(true, []);
+ groups: Group[];
+ dataSource = new MatTableDataSource();
+ searchText: string;
+ isReady = false;
+
+ constructor(
+ private route: ActivatedRoute,
+ private controllerService: ControllerService,
+ private toasterService: ToasterService,
+ public groupService: GroupService,
+ public dialog: MatDialog
+ ) {
+ }
+
+
+ ngOnInit(): void {
+ const controllerId = this.route.parent.snapshot.paramMap.get('controller_id');
+ this.controllerService.get(+controllerId).then((controller: Controller) => {
+ this.controller = controller;
+ this.refresh();
+ });
+ }
+
+ ngAfterViewInit() {
+ this.groupsPaginator.changes.subscribe((comps: QueryList ) =>
+ {
+ this.dataSource.paginator = comps.first;
+ });
+ this.groupsSort.changes.subscribe((comps: QueryList) => {
+ this.dataSource.sort = comps.first;
+ });
+ this.dataSource.sortingDataAccessor = (item, property) => {
+ switch (property) {
+ case 'name':
+ return item[property] ? item[property].toLowerCase() : '';
+ default:
+ return item[property];
+ }
+ };
+ }
+
+ isAllSelected() {
+ const numSelected = this.selection.selected.length;
+ const numRows = this.groups.length;
+ return numSelected === numRows;
+ }
+
+ masterToggle() {
+ this.isAllSelected() ?
+ this.selection.clear() :
+ this.groups.forEach(row => this.selection.select(row));
+ }
+
+ addGroup() {
+ this.dialog
+ .open(AddGroupDialogComponent, {width: '600px', height: '500px', data: {controller: this.controller}})
+ .afterClosed()
+ .subscribe((added: boolean) => {
+ if (added) {
+ this.refresh();
+ }
+ });
+ }
+
+ refresh() {
+ this.groupService.getGroups(this.controller).subscribe((groups: Group[]) => {
+ this.isReady = true;
+ this.groups = groups;
+ this.dataSource.data = groups;
+ this.selection.clear();
+ });
+ }
+
+ onDelete(groupsToDelete: Group[]) {
+ this.dialog
+ .open(DeleteGroupDialogComponent, {width: '500px', height: '250px', data: {groups: groupsToDelete}})
+ .afterClosed()
+ .subscribe((isDeletedConfirm) => {
+ if (isDeletedConfirm) {
+ const observables = groupsToDelete.map((group: Group) => this.groupService.delete(this.controller, group.user_group_id));
+ forkJoin(observables)
+ .subscribe(() => {
+ this.refresh();
+ },
+ (error) => {
+ this.toasterService.error(`An error occur while trying to delete group`);
+ });
+ }
+ });
+ }
+}
diff --git a/src/app/components/image-manager/add-image-dialog/add-image-dialog.component.spec.ts b/src/app/components/image-manager/add-image-dialog/add-image-dialog.component.spec.ts
index d56eb2d5..0e319041 100644
--- a/src/app/components/image-manager/add-image-dialog/add-image-dialog.component.spec.ts
+++ b/src/app/components/image-manager/add-image-dialog/add-image-dialog.component.spec.ts
@@ -5,10 +5,10 @@ import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatToolbarModule } from '@angular/material/toolbar';
import { ImageManagerService } from 'app/services/image-manager.service';
-import { ServerService } from '../../../services/server.service';
-import { MockedServerService } from '../../../services/server.service.spec';
+import { ControllerService } from '../../../services/controller.service';
+import { MockedControllerService } from '../../../services/controller.service.spec';
import { of } from 'rxjs';
-import { Server } from '../../../models/server';
+import{ Controller } from '../../../models/controller';
import { AddImageDialogComponent } from './add-image-dialog.component';
import { NO_ERRORS_SCHEMA } from '@angular/core';
@@ -17,7 +17,7 @@ import { MockedToasterService } from 'app/services/toaster.service.spec';
import { MatSnackBarModule } from '@angular/material/snack-bar';
export class MockedImageManagerService {
- public getImages(server: Server) {
+ public getImages(controller:Controller ) {
return of();
}
@@ -26,8 +26,8 @@ export class MockedImageManagerService {
describe('AddImageDialogComponent', () => {
let component: AddImageDialogComponent;
let fixture: ComponentFixture;
-
- let mockedServerService = new MockedServerService();
+
+ let mockedControllerService = new MockedControllerService();
let mockedImageManagerService = new MockedImageManagerService()
let mockedToasterService = new MockedToasterService()
@@ -42,7 +42,7 @@ describe('AddImageDialogComponent', () => {
MatSnackBarModule
],
providers: [
- { provide: ServerService, useValue: mockedServerService },
+ { provide: ControllerService, useValue: mockedControllerService },
{ provide: ImageManagerService, useValue: mockedImageManagerService },
{ provide: MAT_DIALOG_DATA, useValue: {} },
{ provide: MatDialogRef, useValue: {} },
diff --git a/src/app/components/image-manager/add-image-dialog/add-image-dialog.component.ts b/src/app/components/image-manager/add-image-dialog/add-image-dialog.component.ts
index 60099892..f796ac48 100644
--- a/src/app/components/image-manager/add-image-dialog/add-image-dialog.component.ts
+++ b/src/app/components/image-manager/add-image-dialog/add-image-dialog.component.ts
@@ -3,7 +3,7 @@ import { Component, Inject, OnInit } from '@angular/core';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { UploadServiceService } from 'app/common/uploading-processbar/upload-service.service';
import { FileItem, FileUploader, ParsedResponseHeaders } from 'ng2-file-upload';
-import { Server } from '../../../models/server';
+import{ Controller } from '../../../models/controller';
import { ImageManagerService } from '../../../services/image-manager.service';
import { ToasterService } from '../../../services/toaster.service';
@@ -20,7 +20,7 @@ import { ToasterService } from '../../../services/toaster.service';
],
})
export class AddImageDialogComponent implements OnInit {
- server: Server;
+ controller:Controller ;
isInstallAppliance: boolean = false;
install_appliance: boolean = false;
selectFile: any = [];
@@ -36,7 +36,7 @@ export class AddImageDialogComponent implements OnInit {
) {}
public ngOnInit() {
- this.server = this.data;
+ this.controller =this.data;
this.uploaderImage = new FileUploader({});
this.uploaderImage.onAfterAddingFile = (file) => {
@@ -98,11 +98,11 @@ export class AddImageDialogComponent implements OnInit {
let file = event;
let fileReader: FileReader = new FileReader();
fileReader.onloadend = () => {
- const url = this.imageService.getImagePath(this.server, this.install_appliance, fileName);
+ const url = this.imageService.getImagePath(this.controller, this.install_appliance, fileName);
const itemToUpload = this.uploaderImage.queue[i];
itemToUpload.url = url;
if ((itemToUpload as any).options) (itemToUpload as any).options.disableMultipart = true;
- (itemToUpload as any).options.headers = [{ name: 'Authorization', value: 'Bearer ' + this.server.authToken }];
+ (itemToUpload as any).options.headers = [{ name: 'Authorization', value: 'Bearer ' + this.controller.authToken }];
this.uploaderImage.uploadItem(itemToUpload);
};
fileReader.readAsText(file);
diff --git a/src/app/components/image-manager/deleteallfiles-dialog/deleteallfiles-dialog.component.spec.ts b/src/app/components/image-manager/deleteallfiles-dialog/deleteallfiles-dialog.component.spec.ts
index 43aea89c..eeb3a8d9 100644
--- a/src/app/components/image-manager/deleteallfiles-dialog/deleteallfiles-dialog.component.spec.ts
+++ b/src/app/components/image-manager/deleteallfiles-dialog/deleteallfiles-dialog.component.spec.ts
@@ -10,14 +10,14 @@ import { MockedToasterService } from 'app/services/toaster.service.spec';
import { Server } from 'http';
import { of } from 'rxjs';
import { ImageManagerService } from '../../../services/image-manager.service';
-import { ServerService } from '../../../services/server.service';
-import { MockedServerService } from '../../../services/server.service.spec';
+import { ControllerService } from '../../../services/controller.service';
+import { MockedControllerService } from '../../../services/controller.service.spec';
import { ImageManagerComponent } from '../image-manager.component';
import { DeleteAllImageFilesDialogComponent } from './deleteallfiles-dialog.component';
export class MockedImageManagerService {
- public deleteALLFile(server: Server, image_path) {
+ public deleteALLFile(controller:Server , image_path) {
return of();
}
}
@@ -25,7 +25,7 @@ export class MockedImageManagerService {
describe('DeleteAllImageFilesDialogComponent', () => {
let component: DeleteAllImageFilesDialogComponent;
let fixture: ComponentFixture;
- let mockedServerService = new MockedServerService();
+ let mockedControllerService = new MockedControllerService();
let mockedImageManagerService = new MockedImageManagerService()
let mockedToasterService = new MockedToasterService()
@@ -39,7 +39,7 @@ export class MockedImageManagerService {
MatDialogModule,
],
providers: [
- { provide: ServerService, useValue: mockedServerService },
+ { provide: ControllerService, useValue: mockedControllerService },
{ provide: ImageManagerService, useValue: mockedImageManagerService },
{ provide: MAT_DIALOG_DATA, useValue: {} },
{ provide: MatDialogRef, useValue: {} },
diff --git a/src/app/components/image-manager/deleteallfiles-dialog/deleteallfiles-dialog.component.ts b/src/app/components/image-manager/deleteallfiles-dialog/deleteallfiles-dialog.component.ts
index 6fd33fca..99077c72 100644
--- a/src/app/components/image-manager/deleteallfiles-dialog/deleteallfiles-dialog.component.ts
+++ b/src/app/components/image-manager/deleteallfiles-dialog/deleteallfiles-dialog.component.ts
@@ -35,7 +35,7 @@ export class DeleteAllImageFilesDialogComponent implements OnInit {
deleteFile() {
const calls = [];
this.deleteData.deleteFilesPaths.forEach(pathElement => {
- calls.push(this.imageService.deleteFile(this.deleteData.server, pathElement.filename).pipe(catchError(error => of(error))))
+ calls.push(this.imageService.deleteFile(this.deleteData.controller, pathElement.filename).pipe(catchError(error => of(error))))
});
Observable.forkJoin(calls).subscribe(responses => {
this.deleteFliesDetails = responses.filter(x => x !== null)
diff --git a/src/app/components/image-manager/image-database-file.ts b/src/app/components/image-manager/image-database-file.ts
index 09415d7b..d328229a 100644
--- a/src/app/components/image-manager/image-database-file.ts
+++ b/src/app/components/image-manager/image-database-file.ts
@@ -20,14 +20,14 @@ export class imageDatabase {
}
export class imageDataSource extends DataSource {
- constructor(private serverDatabase: imageDatabase) {
+ constructor(private controllerDatabase: imageDatabase) {
super();
}
connect(): Observable {
- return merge(this.serverDatabase.dataChange).pipe(
+ return merge(this.controllerDatabase.dataChange).pipe(
map(() => {
- return this.serverDatabase.data;
+ return this.controllerDatabase.data;
})
);
}
diff --git a/src/app/components/image-manager/image-manager.component.html b/src/app/components/image-manager/image-manager.component.html
index 5dde19d3..1dfc888c 100644
--- a/src/app/components/image-manager/image-manager.component.html
+++ b/src/app/components/image-manager/image-manager.component.html
@@ -15,7 +15,7 @@
-
+
diff --git a/src/app/components/image-manager/image-manager.component.spec.ts b/src/app/components/image-manager/image-manager.component.spec.ts
index bdba7db1..4d1d9e86 100644
--- a/src/app/components/image-manager/image-manager.component.spec.ts
+++ b/src/app/components/image-manager/image-manager.component.spec.ts
@@ -6,10 +6,10 @@ import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatToolbarModule } from '@angular/material/toolbar';
import { ImageManagerService } from 'app/services/image-manager.service';
-import { ServerService } from 'app/services/server.service';
-import { MockedServerService } from 'app/services/server.service.spec';
+import { ControllerService } from 'app/services/controller.service';
+import { MockedControllerService } from 'app/services/controller.service.spec';
import { of } from 'rxjs';
-import { Server } from '../../models/server';
+import{ Controller } from '../../models/controller';
import { ImageManagerComponent } from './image-manager.component';
import { Image } from '../../models/images';
@@ -23,11 +23,11 @@ import { ToasterService } from 'app/services/toaster.service';
import { MockedToasterService } from 'app/services/toaster.service.spec';
export class MockedImageManagerService {
- public getImages(server: Server) {
+ public getImages(controller:Controller ) {
return of();
}
- public deleteFile(server: Server, image_path) {
+ public deleteFile(controller:Controller , image_path) {
return of();
}
@@ -37,7 +37,7 @@ describe('ImageManagerComponent', () => {
let component: ImageManagerComponent;
let fixture: ComponentFixture;
- let mockedServerService = new MockedServerService();
+ let mockedControllerService = new MockedControllerService();
let mockedImageManagerService = new MockedImageManagerService()
let mockedProgressService = new MockedProgressService()
let mockedVersionService = new MockedVersionService()
@@ -59,7 +59,7 @@ describe('ImageManagerComponent', () => {
provide: ActivatedRoute,
useValue: activatedRoute,
},
- { provide: ServerService, useValue: mockedServerService },
+ { provide: ControllerService, useValue: mockedControllerService },
{ provide: ImageManagerService, useValue: mockedImageManagerService },
{ provide: ProgressService, useValue: mockedProgressService },
{ provide: VersionService, useValue: mockedVersionService },
diff --git a/src/app/components/image-manager/image-manager.component.ts b/src/app/components/image-manager/image-manager.component.ts
index 5a3c09eb..7ccd3d31 100644
--- a/src/app/components/image-manager/image-manager.component.ts
+++ b/src/app/components/image-manager/image-manager.component.ts
@@ -1,10 +1,10 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
-import { ServerService } from '../../services/server.service';
+import { ControllerService } from '../../services/controller.service';
import { VersionService } from '../../services/version.service';
import { ProgressService } from 'app/common/progress/progress.service';
import { Image } from '../../models/images';
-import { Server } from '../../models/server';
+import{ Controller } from '../../models/controller';
import { ImageManagerService } from "../../services/image-manager.service";
import { DataSource, SelectionModel } from '@angular/cdk/collections';
import { AddImageDialogComponent } from './add-image-dialog/add-image-dialog.component';
@@ -19,7 +19,7 @@ import { imageDataSource, imageDatabase } from "./image-database-file";
styleUrls: ['./image-manager.component.scss']
})
export class ImageManagerComponent implements OnInit {
- server: Server;
+ controller:Controller ;
public version: string;
dataSource: imageDataSource;
imageDatabase = new imageDatabase();
@@ -32,7 +32,7 @@ export class ImageManagerComponent implements OnInit {
private imageService: ImageManagerService,
private progressService: ProgressService,
private route: ActivatedRoute,
- private serverService: ServerService,
+ private controllerService: ControllerService,
private versionService: VersionService,
private dialog: MatDialog,
private toasterService: ToasterService,
@@ -40,13 +40,13 @@ export class ImageManagerComponent implements OnInit {
) { }
ngOnInit(): void {
- let server_id = parseInt(this.route.snapshot.paramMap.get('server_id'));
- this.serverService.get(server_id).then((server: Server) => {
- this.server = server;
- if (server.authToken) {
+ let controller_id = parseInt(this.route.snapshot.paramMap.get('controller_id'));
+ this.controllerService.get(controller_id).then((controller:Controller ) => {
+ this.controller = controller;
+ if (controller.authToken) {
this.getImages()
}
- // this.versionService.get(this.server).subscribe((version: Version) => {
+ // this.versionService.get(this.controller).subscribe((version: Version) => {
// this.version = version.version;
// });
});
@@ -54,7 +54,7 @@ export class ImageManagerComponent implements OnInit {
}
getImages() {
- this.imageService.getImages(this.server).subscribe(
+ this.imageService.getImages(this.controller).subscribe(
(images: Image[]) => {
this.imageDatabase.addImages(images)
},
@@ -66,7 +66,7 @@ export class ImageManagerComponent implements OnInit {
}
deleteFile(path) {
- this.imageService.deleteFile(this.server, path).subscribe(
+ this.imageService.deleteFile(this.controller, path).subscribe(
(res) => {
this.getImages()
this.unChecked()
@@ -106,7 +106,7 @@ export class ImageManagerComponent implements OnInit {
maxHeight: '550px',
autoFocus: false,
disableClose: true,
- data: this.server
+ data: this.controller
});
dialogRef.afterClosed().subscribe((isAddes: boolean) => {
@@ -128,7 +128,7 @@ export class ImageManagerComponent implements OnInit {
autoFocus: false,
disableClose: true,
data: {
- server: this.server,
+ controller: this.controller,
deleteFilesPaths: this.selection.selected
}
});
diff --git a/src/app/components/login/login.component.ts b/src/app/components/login/login.component.ts
index 3d9ab2bf..0e431d42 100644
--- a/src/app/components/login/login.component.ts
+++ b/src/app/components/login/login.component.ts
@@ -2,11 +2,11 @@ import { Component, DoCheck, OnInit, ViewEncapsulation } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { AuthResponse } from '../../models/authResponse';
-import { Server } from '../../models/server';
+import{ Controller } from '../../models/controller';
import { Version } from '../../models/version';
import { LoginService } from '../../services/login.service';
-import { ServerDatabase } from '../../services/server.database';
-import { ServerService } from '../../services/server.service';
+import { ControllerDatabase } from '../../services/controller.database';
+import { ControllerService } from '../../services/controller.service';
import { ThemeService } from '../../services/theme.service';
import { ToasterService } from '../../services/toaster.service';
import { VersionService } from '../../services/version.service';
@@ -18,7 +18,7 @@ import { VersionService } from '../../services/version.service';
encapsulation: ViewEncapsulation.None,
})
export class LoginComponent implements OnInit, DoCheck {
- private server: Server;
+ private controller:Controller ;
public version: string;
public isLightThemeEnabled: boolean = false;
public loginError: boolean = false;
@@ -33,8 +33,8 @@ export class LoginComponent implements OnInit, DoCheck {
constructor(
private loginService: LoginService,
- private serverService: ServerService,
- private serverDatabase: ServerDatabase,
+ private controllerService: ControllerService,
+ private controllerDatabase: ControllerDatabase,
private route: ActivatedRoute,
private router: Router,
private toasterService: ToasterService,
@@ -43,16 +43,16 @@ export class LoginComponent implements OnInit, DoCheck {
) {}
async ngOnInit() {
- const server_id = this.route.snapshot.paramMap.get('server_id');
+ const controller_id = this.route.snapshot.paramMap.get('controller_id');
this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/';
- this.serverService.get(parseInt(server_id, 10)).then((server: Server) => {
- this.server = server;
+ this.controllerService.get(parseInt(controller_id, 10)).then((controller:Controller ) => {
+ this.controller = controller;
- if (server.authToken) {
- this.router.navigate(['/server', this.server.id, 'projects']);
+ if (controller.authToken) {
+ this.router.navigate(['/controller', this.controller.id, 'projects']);
}
- this.versionService.get(this.server).subscribe((version: Version) => {
+ this.versionService.get(this.controller).subscribe((version: Version) => {
this.version = version.version;
});
});
@@ -78,17 +78,17 @@ export class LoginComponent implements OnInit, DoCheck {
let username = this.loginForm.get('username').value;
let password = this.loginForm.get('password').value;
- this.loginService.login(this.server, username, password).subscribe(
+ this.loginService.login(this.controller, username, password).subscribe(
async (response: AuthResponse) => {
- let server = this.server;
- server.authToken = response.access_token;
- server.username = username;
- server.password = password;
- server.tokenExpired = false;
- await this.serverService.update(server);
+ let controller = this.controller;
+ controller.authToken = response.access_token;
+ controller.username = username;
+ controller.password = password;
+ controller.tokenExpired = false;
+ await this.controllerService.update(controller);
if (this.returnUrl.length <= 1) {
- this.router.navigate(['/server', this.server.id, 'projects']);
+ this.router.navigate(['/controller', this.controller.id, 'projects']);
} else {
this.router.navigateByUrl(this.returnUrl);
}
diff --git a/src/app/components/management/management.component.html b/src/app/components/management/management.component.html
new file mode 100644
index 00000000..24f7e2ed
--- /dev/null
+++ b/src/app/components/management/management.component.html
@@ -0,0 +1,9 @@
+
+
diff --git a/src/app/components/management/management.component.scss b/src/app/components/management/management.component.scss
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/components/management/management.component.spec.ts b/src/app/components/management/management.component.spec.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/components/management/management.component.ts b/src/app/components/management/management.component.ts
new file mode 100644
index 00000000..8bb190e6
--- /dev/null
+++ b/src/app/components/management/management.component.ts
@@ -0,0 +1,40 @@
+/*
+* Software Name : GNS3 Web UI
+* Version: 3
+* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
+* SPDX-License-Identifier: GPL-3.0-or-later
+*
+* This software is distributed under the GPL-3.0 or any later version,
+* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
+* or see the "LICENSE" file for more details.
+*
+* Author: Sylvain MATHIEU, Elise LEBEAU
+*/
+import { Component, OnInit } from '@angular/core';
+import {ActivatedRoute, Router} from "@angular/router";
+import {Controller} from "@models/controller";
+import {ControllerService} from "@services/controller.service";
+
+@Component({
+ selector: 'app-management',
+ templateUrl: './management.component.html',
+ styleUrls: ['./management.component.scss']
+})
+export class ManagementComponent implements OnInit {
+
+ controller: Controller;
+ links = ['users', 'groups', 'roles', 'permissions'];
+ activeLink: string = this.links[0];
+
+ constructor(
+ private route: ActivatedRoute,
+ public router: Router,
+ private controllerService: ControllerService) { }
+
+ ngOnInit(): void {
+ const controllerId = this.route.snapshot.paramMap.get('controller_id');
+ this.controllerService.get(+controllerId).then((controller: Controller) => {
+ this.controller = controller;
+ });
+ }
+}
diff --git a/src/app/components/page-not-found/page-not-found.component.html b/src/app/components/page-not-found/page-not-found.component.html
index d4b33a2d..22598e3c 100644
--- a/src/app/components/page-not-found/page-not-found.component.html
+++ b/src/app/components/page-not-found/page-not-found.component.html
@@ -2,6 +2,6 @@
diff --git a/src/app/components/permissions-management/action-button/action-button.component.html b/src/app/components/permissions-management/action-button/action-button.component.html
new file mode 100644
index 00000000..cac08740
--- /dev/null
+++ b/src/app/components/permissions-management/action-button/action-button.component.html
@@ -0,0 +1,6 @@
+
diff --git a/src/app/components/permissions-management/action-button/action-button.component.scss b/src/app/components/permissions-management/action-button/action-button.component.scss
new file mode 100644
index 00000000..fe2111dc
--- /dev/null
+++ b/src/app/components/permissions-management/action-button/action-button.component.scss
@@ -0,0 +1,8 @@
+.allow {
+ background-color: green;
+ border-radius: unset !important;
+}
+
+.deny {
+ background-color: darkred;
+}
diff --git a/src/app/components/permissions-management/action-button/action-button.component.spec.ts b/src/app/components/permissions-management/action-button/action-button.component.spec.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/components/permissions-management/action-button/action-button.component.ts b/src/app/components/permissions-management/action-button/action-button.component.ts
new file mode 100644
index 00000000..9cf23619
--- /dev/null
+++ b/src/app/components/permissions-management/action-button/action-button.component.ts
@@ -0,0 +1,38 @@
+/*
+* Software Name : GNS3 Web UI
+* Version: 3
+* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
+* SPDX-License-Identifier: GPL-3.0-or-later
+*
+* This software is distributed under the GPL-3.0 or any later version,
+* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
+* or see the "LICENSE" file for more details.
+*
+* Author: Sylvain MATHIEU, Elise LEBEAU
+*/
+import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
+import {PermissionActions} from "@models/api/permission";
+
+@Component({
+ selector: 'app-action-button',
+ templateUrl: './action-button.component.html',
+ styleUrls: ['./action-button.component.scss']
+})
+export class ActionButtonComponent implements OnInit {
+
+ readonly DENY = 'DENY';
+ readonly ALLOW = 'ALLOW';
+ @Input() action: PermissionActions;
+ @Input() disabled = true;
+ @Output() update = new EventEmitter
();
+
+ constructor() { }
+
+ ngOnInit(): void {
+ }
+
+ change() {
+ this.action === PermissionActions.DENY ? this.action = PermissionActions.ALLOW : this.action = PermissionActions.DENY;
+ this.update.emit(this.action);
+ }
+}
diff --git a/src/app/components/permissions-management/add-permission-line/add-permission-line.component.html b/src/app/components/permissions-management/add-permission-line/add-permission-line.component.html
new file mode 100644
index 00000000..4a743b78
--- /dev/null
+++ b/src/app/components/permissions-management/add-permission-line/add-permission-line.component.html
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/components/permissions-management/add-permission-line/add-permission-line.component.scss b/src/app/components/permissions-management/add-permission-line/add-permission-line.component.scss
new file mode 100644
index 00000000..aed624da
--- /dev/null
+++ b/src/app/components/permissions-management/add-permission-line/add-permission-line.component.scss
@@ -0,0 +1,49 @@
+.box-border {
+ width: 100%;
+ margin-top: 20px;
+ border-bottom: 1px solid;
+}
+
+.edit-mode {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-end;
+}
+
+.information-box {
+ margin-left: 10px;
+ width: 100%;
+}
+
+.information-box > div {
+ margin-bottom: 10px;
+}
+
+.methods {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+}
+
+.button-box {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-around;
+}
+
+.description {
+ width: 100%;
+ margin-left: 10px;
+ margin-right: 10px;
+}
+
+.description > mat-form-field {
+ width: 100%;
+}
+
+.not-edit {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-end;
+ align-items: center;
+}
diff --git a/src/app/components/permissions-management/add-permission-line/add-permission-line.component.spec.ts b/src/app/components/permissions-management/add-permission-line/add-permission-line.component.spec.ts
new file mode 100644
index 00000000..bea06b2b
--- /dev/null
+++ b/src/app/components/permissions-management/add-permission-line/add-permission-line.component.spec.ts
@@ -0,0 +1,132 @@
+/* tslint:disable:no-shadowed-variable */
+import {fakeAsync, TestBed, tick} from "@angular/core/testing";
+import {AddPermissionLineComponent} from "@components/permissions-management/add-permission-line/add-permission-line.component";
+import {ApiInformationService} from "@services/ApiInformation/api-information.service";
+import {PermissionsService} from "@services/permissions.service";
+import {ToasterService} from "@services/toaster.service";
+import {Methods, Permission, PermissionActions} from "@models/api/permission";
+import {Controller} from "@models/controller";
+import {Observable, of, throwError} from "rxjs";
+import {HttpErrorResponse} from "@angular/common/http";
+
+class MockApiInformationService {
+
+}
+
+class MockPermissionService {
+}
+
+class MockToasterService {
+
+}
+
+
+describe('AddPermissionLineComponent', () => {
+ beforeEach(async () => {
+ TestBed.configureTestingModule({
+ providers: [
+ AddPermissionLineComponent,
+ {provide: ApiInformationService, useClass: MockApiInformationService},
+ {provide: PermissionsService, useClass: MockPermissionService},
+ {provide: ToasterService, useClass: MockToasterService}
+ ]
+ });
+ });
+
+ it('Should add GET method to method list', () => {
+ const comp = TestBed.inject(AddPermissionLineComponent);
+ comp.updateMethod({name: Methods.GET, enable: true});
+ expect(comp.permission.methods).toContain(Methods.GET);
+ });
+
+ it('Should remove GET Method from list', () => {
+ const comp = TestBed.inject(AddPermissionLineComponent);
+ comp.permission.methods = [Methods.GET, Methods.PUT, Methods.POST, Methods.DELETE];
+ comp.updateMethod({name: Methods.GET, enable: false});
+
+ expect(comp.permission.methods).not.toContain(Methods.GET);
+ });
+
+ it('Should not add same GET method a second time', () => {
+ const comp = TestBed.inject(AddPermissionLineComponent);
+ comp.permission.methods = [Methods.GET, Methods.PUT, Methods.POST, Methods.DELETE];
+ comp.updateMethod({name: Methods.GET, enable: true});
+
+ expect(comp.permission.methods).toEqual([Methods.GET, Methods.PUT, Methods.POST, Methods.DELETE]);
+ });
+
+ it('Should reset permission values', () => {
+ const comp = TestBed.inject(AddPermissionLineComponent);
+ comp.permission.methods = [Methods.GET, Methods.PUT, Methods.POST, Methods.DELETE];
+ comp.permission.path = "/test/path";
+ comp.permission.action = PermissionActions.DENY;
+ comp.permission.description = "john doe is here";
+
+ comp.reset();
+ const p = comp.permission;
+
+ expect(p.methods).toEqual([]);
+ expect(p.action).toEqual(PermissionActions.ALLOW);
+ expect(p.description).toEqual('');
+ });
+
+ it('Should save permission with success', fakeAsync(() => {
+ const comp = TestBed.inject(AddPermissionLineComponent);
+ const permissionService = TestBed.inject(PermissionsService);
+ const toasterService = TestBed.inject(ToasterService);
+ comp.permission.methods = [Methods.GET, Methods.PUT, Methods.POST, Methods.DELETE];
+ comp.permission.path = "/test/path";
+ comp.permission.action = PermissionActions.DENY;
+ comp.permission.description = "john doe is here";
+
+ permissionService.add = (controller: Controller, permission: Permission): Observable => {
+ return of(permission);
+ };
+
+ let message: string;
+
+ toasterService.success = (m: string) => {
+ message = m;
+ };
+
+ comp.save();
+ const p = comp.permission;
+
+ tick();
+ expect(message).toBeTruthy();
+ expect(p.methods).toEqual([]);
+ expect(p.action).toEqual(PermissionActions.ALLOW);
+ expect(p.description).toEqual('');
+
+ }));
+
+ it('Should throw error on rejected permission', fakeAsync(() => {
+ const comp = TestBed.inject(AddPermissionLineComponent);
+ const permissionService = TestBed.inject(PermissionsService);
+ const toasterService = TestBed.inject(ToasterService);
+ comp.permission.methods = [Methods.GET, Methods.PUT, Methods.POST, Methods.DELETE];
+ comp.permission.path = "/test/path";
+ comp.permission.action = PermissionActions.DENY;
+ comp.permission.description = "john doe is here";
+
+ let errorMessage: string;
+
+ permissionService.add = (controller: Controller, permission: Permission): Observable => {
+ const error = new HttpErrorResponse({
+ error: new Error("An error occur"),
+ headers: undefined,
+ status: 500,
+ statusText: 'error from controller'
+ });
+ return throwError(error);
+ };
+
+ toasterService.error = (message: string) => {
+ errorMessage = message;
+ };
+
+ comp.save();
+ tick();
+ expect(errorMessage).toBeTruthy();
+ }));
+});
diff --git a/src/app/components/permissions-management/add-permission-line/add-permission-line.component.ts b/src/app/components/permissions-management/add-permission-line/add-permission-line.component.ts
new file mode 100644
index 00000000..5e6436fd
--- /dev/null
+++ b/src/app/components/permissions-management/add-permission-line/add-permission-line.component.ts
@@ -0,0 +1,82 @@
+/*
+* Software Name : GNS3 Web UI
+* Version: 3
+* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
+* SPDX-License-Identifier: GPL-3.0-or-later
+*
+* This software is distributed under the GPL-3.0 or any later version,
+* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
+* or see the "LICENSE" file for more details.
+*
+* Author: Sylvain MATHIEU, Elise LEBEAU
+*/
+import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
+import {Controller} from "@models/controller";
+import {ApiInformationService} from "@services/ApiInformation/api-information.service";
+import {Methods, Permission, PermissionActions} from "@models/api/permission";
+import {PermissionsService} from "@services/permissions.service";
+import {ToasterService} from "@services/toaster.service";
+import {HttpErrorResponse} from "@angular/common/http";
+
+@Component({
+ selector: 'app-add-permission-line',
+ templateUrl: './add-permission-line.component.html',
+ styleUrls: ['./add-permission-line.component.scss']
+})
+export class AddPermissionLineComponent implements OnInit {
+
+ @Input() controller: Controller;
+ @Output() addPermissionEvent = new EventEmitter();
+ permission: Permission = {
+ action: PermissionActions.ALLOW,
+ description: "",
+ methods: [],
+ path: "/"
+ };
+ edit = false;
+
+ constructor(public apiInformation: ApiInformationService,
+ private permissionService: PermissionsService,
+ private toasterService: ToasterService) {
+
+ }
+
+ ngOnInit(): void {
+
+ }
+
+
+ updateMethod(data: { name: Methods; enable: boolean }) {
+ const set = new Set(this.permission.methods);
+ if (data.enable) {
+ set.add(data.name);
+ } else {
+ set.delete(data.name);
+ }
+
+ this.permission.methods = Array.from(set);
+ }
+
+ reset() {
+ this.permission = {
+ action: PermissionActions.ALLOW,
+ description: "",
+ methods: [],
+ path: "/",
+ };
+
+ this.edit = false;
+ }
+
+ save() {
+ this.permissionService.add(this.controller, this.permission)
+ .subscribe(() => {
+ this.toasterService.success(`permission was created`);
+ this.reset();
+ }, (error: HttpErrorResponse) => {
+ this.toasterService.error(`
+ ${error.message}
+ ${error.error.message}`);
+ });
+ }
+}
diff --git a/src/app/components/permissions-management/add-permission-line/path-auto-complete/PermissionPath.spec.ts b/src/app/components/permissions-management/add-permission-line/path-auto-complete/PermissionPath.spec.ts
new file mode 100644
index 00000000..9c1f35fb
--- /dev/null
+++ b/src/app/components/permissions-management/add-permission-line/path-auto-complete/PermissionPath.spec.ts
@@ -0,0 +1,82 @@
+import {PermissionPath} from "@components/permissions-management/add-permission-line/path-auto-complete/PermissionPath";
+import {SubPath} from "@components/permissions-management/add-permission-line/path-auto-complete/SubPath";
+
+describe('PermissionPath', () => {
+
+ it('Should add subPath to path', () => {
+ const path = new PermissionPath();
+ path.add(new SubPath('projects', 'projects', undefined));
+ path.add(new SubPath('1111-2222-3333-4444', 'my project', 'project_id'));
+
+ expect(path.getPath()).toEqual(['projects', '1111-2222-3333-4444']);
+ });
+
+ it('Should return display path', () => {
+ const path = new PermissionPath();
+ path.add(new SubPath('projects', 'projects', undefined));
+ path.add(new SubPath('1111-2222-3333-4444', 'my project', 'project_id'));
+
+ expect(path.getDisplayPath()).toEqual(['projects', 'my project']);
+ });
+
+ it('Should remove last element', () => {
+ const path = new PermissionPath();
+ path.add(new SubPath('projects', 'projects', undefined));
+ path.add(new SubPath('1111-2222-3333-4444', 'my project', 'project_id'));
+ path.add(new SubPath('nodes', 'nodes'));
+ path.add(new SubPath('6666-7777-8888-9999', 'myFirstNode', 'node_id'));
+
+ path.removeLast();
+ expect(path.getPath()).toEqual(['projects', '1111-2222-3333-4444', 'nodes']);
+ });
+
+ it('Should return path variables', () => {
+ const path = new PermissionPath();
+ path.add(new SubPath('projects', 'projects', undefined));
+ path.add(new SubPath('1111-2222-3333-4444', 'my project', 'project_id'));
+ path.add(new SubPath('nodes', 'nodes'));
+ path.add(new SubPath('6666-7777-8888-9999', 'myFirstNode', 'node_id'));
+
+ expect(path.getVariables())
+ .toEqual([{key: 'project_id', value: '1111-2222-3333-4444'}, { key: 'node_id', value: '6666-7777-8888-9999'}]);
+ });
+
+
+ it('Should return true if subPath contain *', () => {
+ const path = new PermissionPath();
+ path.add(new SubPath('projects', 'projects', undefined));
+ path.add(new SubPath('1111-2222-3333-4444', 'my project', 'project_id'));
+ path.add(new SubPath('nodes', 'nodes'));
+ path.add(new SubPath('*', 'myFirstNode', 'node_id'));
+
+ expect(path.containStar()).toBeTruthy();
+ });
+
+ it('Should return false if subPath does not contain *', () => {
+ const path = new PermissionPath();
+ path.add(new SubPath('projects', 'projects', undefined));
+ path.add(new SubPath('1111-2222-3333-4444', 'my project', 'project_id'));
+ path.add(new SubPath('nodes', 'nodes'));
+ path.add(new SubPath('6666-7777-8888-999', 'myFirstNode', 'node_id'));
+
+ expect(path.containStar()).toBeFalsy();
+ });
+
+
+ it('Should return true if path is empty', () => {
+ const path = new PermissionPath();
+ expect(path.isEmpty()).toBeTruthy();
+ });
+
+
+ it('Should return false if path is not empty', () => {
+ const path = new PermissionPath();
+ path.add(new SubPath('projects', 'projects', undefined));
+ path.add(new SubPath('1111-2222-3333-4444', 'my project', 'project_id'));
+ path.add(new SubPath('nodes', 'nodes'));
+ path.add(new SubPath('6666-7777-8888-999', 'myFirstNode', 'node_id'));
+
+ expect(path.isEmpty()).toBeFalsy();
+ });
+
+});
diff --git a/src/app/components/permissions-management/add-permission-line/path-auto-complete/PermissionPath.ts b/src/app/components/permissions-management/add-permission-line/path-auto-complete/PermissionPath.ts
new file mode 100644
index 00000000..5e84941b
--- /dev/null
+++ b/src/app/components/permissions-management/add-permission-line/path-auto-complete/PermissionPath.ts
@@ -0,0 +1,56 @@
+/*
+* Software Name : GNS3 Web UI
+* Version: 3
+* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
+* SPDX-License-Identifier: GPL-3.0-or-later
+*
+* This software is distributed under the GPL-3.0 or any later version,
+* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
+* or see the "LICENSE" file for more details.
+*
+* Author: Sylvain MATHIEU, Elise LEBEAU
+*/
+import {SubPath} from "./SubPath";
+
+export class PermissionPath {
+ private subPath: SubPath[] = [];
+
+ constructor() {
+ }
+
+ add(subPath: SubPath) {
+ this.subPath.push(subPath);
+ }
+
+ getDisplayPath() {
+ return this.subPath
+ .map((subPath) => subPath.displayValue);
+ }
+
+ removeLast() {
+ this.subPath.pop();
+ }
+
+ getPath() {
+ return this.subPath.map((subPath) => subPath.value);
+ }
+
+ isEmpty() {
+ return this.subPath.length === 0;
+ }
+
+ getVariables(): { key: string; value: string }[] {
+ return this.subPath
+ .filter((path) => path.key)
+ .map((path) => {
+ return {key: path.key, value: path.value};
+ });
+ }
+
+
+ containStar() {
+ return this.subPath
+ .map(subPath => subPath.value === '*')
+ .reduce((previous, next) => previous || next, false);
+ }
+}
diff --git a/src/app/components/permissions-management/add-permission-line/path-auto-complete/SubPath.ts b/src/app/components/permissions-management/add-permission-line/path-auto-complete/SubPath.ts
new file mode 100644
index 00000000..11fe2675
--- /dev/null
+++ b/src/app/components/permissions-management/add-permission-line/path-auto-complete/SubPath.ts
@@ -0,0 +1,24 @@
+/*
+* Software Name : GNS3 Web UI
+* Version: 3
+* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
+* SPDX-License-Identifier: GPL-3.0-or-later
+*
+* This software is distributed under the GPL-3.0 or any later version,
+* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
+* or see the "LICENSE" file for more details.
+*
+* Author: Sylvain MATHIEU, Elise LEBEAU
+*/
+export class SubPath {
+
+ /**
+ * @param {value} original subPath value from gns3 api
+ * @param {displayValue} displayed value can replace a UUID from original URL
+ * @param {key} associate key ex: 'project_id'
+ */
+ constructor(public value: string,
+ public displayValue: string,
+ public key?: string) {
+ }
+}
diff --git a/src/app/components/permissions-management/add-permission-line/path-auto-complete/filter-complete.pipe.spec.ts b/src/app/components/permissions-management/add-permission-line/path-auto-complete/filter-complete.pipe.spec.ts
new file mode 100644
index 00000000..fb5e7afa
--- /dev/null
+++ b/src/app/components/permissions-management/add-permission-line/path-auto-complete/filter-complete.pipe.spec.ts
@@ -0,0 +1,37 @@
+import {FilterCompletePipe} from "@components/permissions-management/add-permission-line/path-auto-complete/filter-complete.pipe";
+import {IGenericApiObject} from "@services/ApiInformation/IGenericApiObject";
+
+describe('FilterCompletePipe', () => {
+ it('should remove items which not match searchText', function () {
+ const filter = new FilterCompletePipe();
+
+ const items: IGenericApiObject[] = [
+ {id: 'b2afe0da-b83e-42a8-bcb6-e46ca1bd1747', name: 'test project 1'},
+ {id: '698d35c1-9fd0-4b89-86dc-336a958b1f70', name: 'test project 2'},
+ {id: '4bbd57e6-bf99-4387-8948-7e7d8e96de9b', name: 'test project 3'},
+ {id: '29e9ddb6-1ba0-422d-b767-92592821f011', name: 'test project 4'},
+ {id: '5a522134-0bfd-4864-b8b3-520bcecd4fc9', name: 'test project 5'},
+ {id: '7e27f67a-2b63-4d00-936b-e3d8c7e2b751', name: 'test project 6'},
+ ];
+
+ expect(filter.transform(items, 'test project 6'))
+ .toEqual([{id: '7e27f67a-2b63-4d00-936b-e3d8c7e2b751', name: 'test project 6'}]);
+ });
+
+
+ it('should return entire list if searchText is empty', function () {
+ const filter = new FilterCompletePipe();
+
+ const items: IGenericApiObject[] = [
+ {id: 'b2afe0da-b83e-42a8-bcb6-e46ca1bd1747', name: 'test project 1'},
+ {id: '698d35c1-9fd0-4b89-86dc-336a958b1f70', name: 'test project 2'},
+ {id: '4bbd57e6-bf99-4387-8948-7e7d8e96de9b', name: 'test project 3'},
+ {id: '29e9ddb6-1ba0-422d-b767-92592821f011', name: 'test project 4'},
+ {id: '5a522134-0bfd-4864-b8b3-520bcecd4fc9', name: 'test project 5'},
+ {id: '7e27f67a-2b63-4d00-936b-e3d8c7e2b751', name: 'test project 6'},
+ ];
+
+ expect(filter.transform(items, '')).toEqual(items);
+ expect(filter.transform(items, undefined)).toEqual(items);
+ });
+});
diff --git a/src/app/components/permissions-management/add-permission-line/path-auto-complete/filter-complete.pipe.ts b/src/app/components/permissions-management/add-permission-line/path-auto-complete/filter-complete.pipe.ts
new file mode 100644
index 00000000..9fcf0be0
--- /dev/null
+++ b/src/app/components/permissions-management/add-permission-line/path-auto-complete/filter-complete.pipe.ts
@@ -0,0 +1,32 @@
+/*
+* Software Name : GNS3 Web UI
+* Version: 3
+* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
+* SPDX-License-Identifier: GPL-3.0-or-later
+*
+* This software is distributed under the GPL-3.0 or any later version,
+* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
+* or see the "LICENSE" file for more details.
+*
+* Author: Sylvain MATHIEU, Elise LEBEAU
+*/
+import { Pipe, PipeTransform } from '@angular/core';
+import {IGenericApiObject} from "@services/ApiInformation/IGenericApiObject";
+
+/**
+ * Pipe to filter autocomplete proposals
+ */
+@Pipe({
+ name: 'filterComplete'
+})
+export class FilterCompletePipe implements PipeTransform {
+
+ transform(value: IGenericApiObject[], searchText: string): IGenericApiObject[] {
+ if (!searchText || searchText === '') { return value; }
+
+ return value.filter((v) => {
+ return v.name.includes(searchText) || v.id.includes(searchText);
+ });
+ }
+
+}
diff --git a/src/app/components/permissions-management/add-permission-line/path-auto-complete/path-auto-complete.component.html b/src/app/components/permissions-management/add-permission-line/path-auto-complete/path-auto-complete.component.html
new file mode 100644
index 00000000..b4297c5e
--- /dev/null
+++ b/src/app/components/permissions-management/add-permission-line/path-auto-complete/path-auto-complete.component.html
@@ -0,0 +1,37 @@
+
+
Path: /
+
{{p}}/
+
+
+
+
+ {{value}}
+
+
+
+
+
+ *
+
+ {{data.name}}
+
+
+
+
+
+ cancel
+ add_circle_outline
+ check_circle
+
+
+
+
diff --git a/src/app/components/permissions-management/add-permission-line/path-auto-complete/path-auto-complete.component.scss b/src/app/components/permissions-management/add-permission-line/path-auto-complete/path-auto-complete.component.scss
new file mode 100644
index 00000000..2720f01e
--- /dev/null
+++ b/src/app/components/permissions-management/add-permission-line/path-auto-complete/path-auto-complete.component.scss
@@ -0,0 +1,22 @@
+.path {
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+}
+
+mat-select {
+ width: 150px;
+}
+
+.edit-area {
+ border: 1px solid;
+}
+
+.command-button {
+ margin-left: 5px;
+}
+
+.path-edit-line {
+ display: flex;
+ flex-direction: row;
+}
diff --git a/src/app/components/permissions-management/add-permission-line/path-auto-complete/path-auto-complete.component.spec.ts b/src/app/components/permissions-management/add-permission-line/path-auto-complete/path-auto-complete.component.spec.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/components/permissions-management/add-permission-line/path-auto-complete/path-auto-complete.component.ts b/src/app/components/permissions-management/add-permission-line/path-auto-complete/path-auto-complete.component.ts
new file mode 100644
index 00000000..c7441b3f
--- /dev/null
+++ b/src/app/components/permissions-management/add-permission-line/path-auto-complete/path-auto-complete.component.ts
@@ -0,0 +1,98 @@
+/*
+* Software Name : GNS3 Web UI
+* Version: 3
+* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
+* SPDX-License-Identifier: GPL-3.0-or-later
+*
+* This software is distributed under the GPL-3.0 or any later version,
+* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
+* or see the "LICENSE" file for more details.
+*
+* Author: Sylvain MATHIEU, Elise LEBEAU
+*/
+import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
+import {ApiInformationService} from "@services/ApiInformation/api-information.service";
+import {Controller} from "@models/controller";
+import {PermissionPath} from "@components/permissions-management/add-permission-line/path-auto-complete/PermissionPath";
+import {SubPath} from "@components/permissions-management/add-permission-line/path-auto-complete/SubPath";
+import {IGenericApiObject} from "@services/ApiInformation/IGenericApiObject";
+
+@Component({
+ selector: 'app-path-auto-complete',
+ templateUrl: './path-auto-complete.component.html',
+ styleUrls: ['./path-auto-complete.component.scss']
+})
+export class PathAutoCompleteComponent implements OnInit {
+
+
+ @Output() update = new EventEmitter();
+ @Input() controller: Controller;
+ path: PermissionPath = new PermissionPath();
+ values: string[] = [];
+ private completeData: { data: IGenericApiObject[]; key: string };
+ public mode: 'SELECT' | 'COMPLETE' | undefined;
+ completeField: string;
+
+ constructor(private apiInformationService: ApiInformationService) {
+
+ }
+
+ updatePath(name: string, value: string, key?: string) {
+ this.path.add(new SubPath(name, value, key));
+ this.update.emit('/' + this.path.getPath().join("/"));
+ }
+
+ popPath() {
+ this.path.removeLast();
+ this.update.emit('/' + this.path.getPath().join("/"));
+ }
+
+ ngOnInit(): void {
+ }
+
+ getNext() {
+ this.apiInformationService
+ .getPathNextElement(this.path.getPath())
+ .subscribe((next: string[]) => {
+ if (this.path.containStar()) {
+ next = next.filter(item => !item.match(this.apiInformationService.bracketIdRegex));
+ }
+ this.values = next;
+ this.mode = 'SELECT';
+ });
+ }
+
+ removePrevious() {
+ if (this.mode) {
+ return this.mode = undefined;
+ }
+ if (!this.path.isEmpty()) {
+ return this.popPath();
+ }
+ }
+
+ valueChanged(value: string) {
+ if (value.match(this.apiInformationService.bracketIdRegex) && !this.path.containStar()) {
+ this.apiInformationService.getListByObjectId(this.controller, value, undefined, this.path.getVariables())
+ .subscribe((data) => {
+ this.mode = 'COMPLETE';
+ this.completeData = {data, key: value};
+ });
+
+ } else {
+ this.updatePath(value, value);
+ this.mode = undefined;
+ }
+ }
+
+ validComplete() {
+ if (this.completeField === '*') {
+ this.updatePath('*', '*');
+ } else {
+ const data = this.completeData.data.find((d) => this.completeField === d.name);
+ this.updatePath(data.id, data.name, this.completeData.key);
+ }
+ this.mode = undefined;
+ this.completeField = undefined;
+ }
+}
diff --git a/src/app/components/permissions-management/delete-permission-dialog/delete-permission-dialog.component.html b/src/app/components/permissions-management/delete-permission-dialog/delete-permission-dialog.component.html
new file mode 100644
index 00000000..1983ebb8
--- /dev/null
+++ b/src/app/components/permissions-management/delete-permission-dialog/delete-permission-dialog.component.html
@@ -0,0 +1,12 @@
+
+
confirm deleting permission:
+
{{data.permission_id}}
+
{{data.path}}
+
{{data.methods.join(',')}}
+
{{data.action}}
+
{{data.description}}
+
+
+
+
+
diff --git a/src/app/components/permissions-management/delete-permission-dialog/delete-permission-dialog.component.scss b/src/app/components/permissions-management/delete-permission-dialog/delete-permission-dialog.component.scss
new file mode 100644
index 00000000..51a31794
--- /dev/null
+++ b/src/app/components/permissions-management/delete-permission-dialog/delete-permission-dialog.component.scss
@@ -0,0 +1,18 @@
+.description {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ text-align: center;
+ margin-top: 20px;
+}
+
+.description > div {
+ margin-bottom: 10px;
+}
+
+.button {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-around;
+ margin-top: 20px;
+}
diff --git a/src/app/components/permissions-management/delete-permission-dialog/delete-permission-dialog.component.spec.ts b/src/app/components/permissions-management/delete-permission-dialog/delete-permission-dialog.component.spec.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/components/permissions-management/delete-permission-dialog/delete-permission-dialog.component.ts b/src/app/components/permissions-management/delete-permission-dialog/delete-permission-dialog.component.ts
new file mode 100644
index 00000000..cc1a149c
--- /dev/null
+++ b/src/app/components/permissions-management/delete-permission-dialog/delete-permission-dialog.component.ts
@@ -0,0 +1,37 @@
+/*
+* Software Name : GNS3 Web UI
+* Version: 3
+* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
+* SPDX-License-Identifier: GPL-3.0-or-later
+*
+* This software is distributed under the GPL-3.0 or any later version,
+* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
+* or see the "LICENSE" file for more details.
+*
+* Author: Sylvain MATHIEU, Elise LEBEAU
+*/
+import {Component, Inject, OnInit} from '@angular/core';
+import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog";
+import {Permission} from "@models/api/permission";
+
+@Component({
+ selector: 'app-delete-permission-dialog',
+ templateUrl: './delete-permission-dialog.component.html',
+ styleUrls: ['./delete-permission-dialog.component.scss']
+})
+export class DeletePermissionDialogComponent implements OnInit {
+
+ constructor(private dialog: MatDialogRef,
+ @Inject(MAT_DIALOG_DATA) public data: Permission) { }
+
+ ngOnInit(): void {
+ }
+
+ cancel() {
+ this.dialog.close(false);
+ }
+
+ confirm() {
+ this.dialog.close(true);
+ }
+}
diff --git a/src/app/components/permissions-management/display-path.pipe.spec.ts b/src/app/components/permissions-management/display-path.pipe.spec.ts
new file mode 100644
index 00000000..d72a8516
--- /dev/null
+++ b/src/app/components/permissions-management/display-path.pipe.spec.ts
@@ -0,0 +1,57 @@
+import {async, fakeAsync, TestBed, tick} from "@angular/core/testing";
+import {DisplayPathPipe} from "@components/permissions-management/display-path.pipe";
+import {ApiInformationService} from "@services/ApiInformation/api-information.service";
+import {Controller} from "@models/controller";
+import {Observable, of} from "rxjs";
+import {IExtraParams} from "@services/ApiInformation/IExtraParams";
+import {IGenericApiObject} from "@services/ApiInformation/IGenericApiObject";
+
+class MockApiInformationService {
+
+}
+
+
+describe('DisplayPathPipe', () => {
+
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ providers: [
+ DisplayPathPipe,
+ {provide: ApiInformationService, useClass: MockApiInformationService}]
+ });
+ }));
+
+ it('Should display human readable path', fakeAsync(() => {
+ const comp = TestBed.inject(DisplayPathPipe);
+ const apiService = TestBed.inject(ApiInformationService);
+
+ apiService.getKeysForPath = (path: string): Observable<{ key: string; value: string }[]> => {
+ return of([
+ {key: 'project_id', value: '1111-2222-3333'},
+ {key: 'node_id', value: '2222-2222-2222'}
+ ]);
+ };
+
+ apiService
+ .getListByObjectId = (controller: Controller, key: string, value?: string, extraParams?: IExtraParams[]): Observable => {
+ if (key === 'project_id') {
+ return of([{id: '1111-2222-3333', name: 'myProject'}]);
+ }
+ if (key === 'node_id') {
+ return of([{id: '2222-2222-2222', name: 'node1'}]);
+ }
+ };
+
+ let result: string;
+
+ const controller = new Controller();
+ comp
+ .transform('/project/1111-2222-3333/nodes/2222-2222-2222', controller)
+ .subscribe((res: string) => {
+ result = res;
+ });
+
+ tick();
+ expect(result).toEqual('/project/myProject/nodes/node1');
+ }));
+});
diff --git a/src/app/components/permissions-management/display-path.pipe.ts b/src/app/components/permissions-management/display-path.pipe.ts
new file mode 100644
index 00000000..c7abac9f
--- /dev/null
+++ b/src/app/components/permissions-management/display-path.pipe.ts
@@ -0,0 +1,54 @@
+/*
+* Software Name : GNS3 Web UI
+* Version: 3
+* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
+* SPDX-License-Identifier: GPL-3.0-or-later
+*
+* This software is distributed under the GPL-3.0 or any later version,
+* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
+* or see the "LICENSE" file for more details.
+*
+* Author: Sylvain MATHIEU, Elise LEBEAU
+*/
+import {Pipe, PipeTransform} from '@angular/core';
+import {map, switchMap} from "rxjs/operators";
+import {forkJoin, Observable, of} from "rxjs";
+import {ApiInformationService} from "@services/ApiInformation/api-information.service";
+import {Controller} from "@models/controller";
+
+@Pipe({
+ name: 'displayPath'
+})
+export class DisplayPathPipe implements PipeTransform {
+
+ constructor(private apiInformation: ApiInformationService) {
+ }
+
+ transform(originalPath: string, controller: Controller): Observable {
+ if (!controller) {
+ return of(originalPath);
+ }
+ return this.apiInformation
+ .getKeysForPath(originalPath)
+ .pipe(switchMap((values) => {
+ if (values.length === 0) {
+ return of([]);
+ }
+ const obs = values.map((k) => this.apiInformation.getListByObjectId(controller, k.key, k.value, values));
+ return forkJoin(obs);
+ }),
+ map((values: { id: string; name: string }[][]) => {
+ let displayPath = `${originalPath}`;
+ values.forEach((value) => {
+ if (value[0].id && value[0].name) {
+ displayPath = displayPath.replace(value[0].id, value[0].name);
+ } else {
+ }
+
+ });
+ return displayPath;
+ })
+ );
+ }
+
+}
diff --git a/src/app/components/permissions-management/method-button/method-button.component.html b/src/app/components/permissions-management/method-button/method-button.component.html
new file mode 100644
index 00000000..4e4a10cc
--- /dev/null
+++ b/src/app/components/permissions-management/method-button/method-button.component.html
@@ -0,0 +1,7 @@
+
diff --git a/src/app/components/permissions-management/method-button/method-button.component.scss b/src/app/components/permissions-management/method-button/method-button.component.scss
new file mode 100644
index 00000000..cc1b1778
--- /dev/null
+++ b/src/app/components/permissions-management/method-button/method-button.component.scss
@@ -0,0 +1,10 @@
+:host {
+ padding: unset !important;
+}
+
+.enable {
+ color: green !important;
+}
+.disabled {
+ color: dimgrey;
+}
diff --git a/src/app/components/permissions-management/method-button/method-button.component.spec.ts b/src/app/components/permissions-management/method-button/method-button.component.spec.ts
new file mode 100644
index 00000000..087ced03
--- /dev/null
+++ b/src/app/components/permissions-management/method-button/method-button.component.spec.ts
@@ -0,0 +1,53 @@
+import {async, fakeAsync, TestBed} from "@angular/core/testing";
+import {MethodButtonComponent} from "@components/permissions-management/method-button/method-button.component";
+import {Methods} from "@models/api/permission";
+
+describe('MethodButtonComponent', () => {
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({declarations: [MethodButtonComponent]});
+ }));
+
+ it('Should set text color to green when button is enable', fakeAsync(() => {
+ const fixture = TestBed.createComponent(MethodButtonComponent);
+ const component = fixture.componentInstance;
+ const debugElement = fixture.debugElement;
+
+ component.enable = true;
+
+ fixture.detectChanges();
+
+ expect(debugElement.nativeElement.querySelector('button').classList).toContain('enable');
+
+ }));
+
+ it('Should switch to enable on button click', (() => {
+ const fixture = TestBed.createComponent(MethodButtonComponent);
+ const component = fixture.componentInstance;
+ fixture.detectChanges();
+
+ component.enable = false;
+ component.change();
+
+ expect(component.enable).toEqual(true);
+
+ }));
+
+
+ it('Should emit event enable on button click', (() => {
+ const fixture = TestBed.createComponent(MethodButtonComponent);
+ const component = fixture.componentInstance;
+ fixture.detectChanges();
+
+ component.update.subscribe((data) => {
+ expect(data.enable).toEqual(true);
+ expect(data.name).toEqual(Methods.GET);
+ });
+
+ component.name = Methods.GET;
+ component.enable = false;
+ component.change();
+
+ }));
+
+
+});
diff --git a/src/app/components/permissions-management/method-button/method-button.component.ts b/src/app/components/permissions-management/method-button/method-button.component.ts
new file mode 100644
index 00000000..b9a2c373
--- /dev/null
+++ b/src/app/components/permissions-management/method-button/method-button.component.ts
@@ -0,0 +1,38 @@
+/*
+* Software Name : GNS3 Web UI
+* Version: 3
+* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
+* SPDX-License-Identifier: GPL-3.0-or-later
+*
+* This software is distributed under the GPL-3.0 or any later version,
+* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
+* or see the "LICENSE" file for more details.
+*
+* Author: Sylvain MATHIEU, Elise LEBEAU
+*/
+import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
+import {Methods} from "@models/api/permission";
+
+@Component({
+ selector: 'app-method-button',
+ templateUrl: './method-button.component.html',
+ styleUrls: ['./method-button.component.scss']
+})
+export class MethodButtonComponent implements OnInit {
+
+ @Input() enable = false;
+ @Input() name: Methods;
+ @Input() disabled = true;
+
+ @Output() update = new EventEmitter<{name: Methods; enable: boolean}>();
+
+ constructor() { }
+
+ ngOnInit(): void {
+ }
+
+ change() {
+ this.enable = !this.enable;
+ this.update.emit({name: this.name, enable: this.enable});
+ }
+}
diff --git a/src/app/components/permissions-management/permission-edit-line/permission-edit-line.component.html b/src/app/components/permissions-management/permission-edit-line/permission-edit-line.component.html
new file mode 100644
index 00000000..b2a58b23
--- /dev/null
+++ b/src/app/components/permissions-management/permission-edit-line/permission-edit-line.component.html
@@ -0,0 +1,57 @@
+
+
+
+
+ {{permission.path | displayPath: controller | async}}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/components/permissions-management/permission-edit-line/permission-edit-line.component.scss b/src/app/components/permissions-management/permission-edit-line/permission-edit-line.component.scss
new file mode 100644
index 00000000..8d6e5890
--- /dev/null
+++ b/src/app/components/permissions-management/permission-edit-line/permission-edit-line.component.scss
@@ -0,0 +1,28 @@
+.permission {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ border-bottom: solid 1px;
+ margin-top: 10px;
+ align-items: center;
+}
+
+.action-button-bar {
+ display: flex;
+ flex-direction: row;
+}
+
+.methods {
+ display: flex;
+ flex-direction: row;
+ border: 1px solid;
+}
+
+.permission-input {
+ width: 300px;
+}
+
+.button-bar > div > button {
+ padding: unset !important;
+}
+
diff --git a/src/app/components/permissions-management/permission-edit-line/permission-edit-line.component.spec.ts b/src/app/components/permissions-management/permission-edit-line/permission-edit-line.component.spec.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/components/permissions-management/permission-edit-line/permission-edit-line.component.ts b/src/app/components/permissions-management/permission-edit-line/permission-edit-line.component.ts
new file mode 100644
index 00000000..df2fa857
--- /dev/null
+++ b/src/app/components/permissions-management/permission-edit-line/permission-edit-line.component.ts
@@ -0,0 +1,81 @@
+/*
+* Software Name : GNS3 Web UI
+* Version: 3
+* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
+* SPDX-License-Identifier: GPL-3.0-or-later
+*
+* This software is distributed under the GPL-3.0 or any later version,
+* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
+* or see the "LICENSE" file for more details.
+*
+* Author: Sylvain MATHIEU, Elise LEBEAU
+*/
+import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core';
+import {Methods, Permission} from "@models/api/permission";
+import {Controller} from '@models/controller';
+import {ApiInformationService} from "@services/ApiInformation/api-information.service";
+import {PermissionsService} from "@services/permissions.service";
+import {ToasterService} from "@services/toaster.service";
+import {MatDialog} from "@angular/material/dialog";
+import {DeletePermissionDialogComponent} from "@components/permissions-management/delete-permission-dialog/delete-permission-dialog.component";
+
+@Component({
+ selector: 'app-permission-add-edit-line',
+ templateUrl: './permission-edit-line.component.html',
+ styleUrls: ['./permission-edit-line.component.scss']
+})
+export class PermissionEditLineComponent {
+ @Input() permission: Permission;
+ @Input() controller: Controller;
+
+ isEditable = false;
+ @Output() update = new EventEmitter();
+
+ constructor(public apiInformation: ApiInformationService,
+ private permissionService: PermissionsService,
+ private toasterService: ToasterService,
+ private dialog: MatDialog) {
+ }
+
+
+ onDelete() {
+ this.dialog.open(DeletePermissionDialogComponent,
+ {width: '700px', height: '500px', data: this.permission})
+ .afterClosed()
+ .subscribe((confirm: boolean) => {
+ if (confirm) {
+ this.permissionService.delete(this.controller, this.permission.permission_id)
+ .subscribe(() => {
+ this.toasterService.success(`Permission was deleted`);
+ this.update.emit();
+ }, (e) => {
+ this.toasterService.error(e);
+ this.update.emit();
+ });
+ }
+ });
+
+ }
+
+ onSave() {
+ this.permissionService.update(this.controller, this.permission)
+ .subscribe(() => {
+ this.toasterService.success(`Permission was updated`);
+ this.update.emit();
+ }, (e) => {
+ this.toasterService.error(e);
+ this.update.emit();
+ });
+ }
+
+ onCancel() {
+ this.update.emit();
+ }
+
+
+ onMethodUpdate(event: { name: Methods; enable: boolean }) {
+ const set = new Set(this.permission.methods);
+ event.enable ? set.add(event.name) : set.delete(event.name);
+ this.permission.methods = Array.from(set);
+ }
+}
diff --git a/src/app/components/permissions-management/permissions-filter.pipe.spec.ts b/src/app/components/permissions-management/permissions-filter.pipe.spec.ts
new file mode 100644
index 00000000..41802817
--- /dev/null
+++ b/src/app/components/permissions-management/permissions-filter.pipe.spec.ts
@@ -0,0 +1,57 @@
+import {PermissionsFilterPipe} from './permissions-filter.pipe';
+import {Methods, Permission, PermissionActions} from "../../models/api/permission";
+
+const testPermissions: Permission[] = [
+ {
+ methods: [Methods.GET, Methods.PUT],
+ path: '/projects/projet-test',
+ action: PermissionActions.ALLOW,
+ description: 'description of permission 1',
+ created_at: "2022-03-15T09:45:36.531Z",
+ updated_at: "2022-03-15T09:45:36.531Z",
+ permission_id: '1'
+ },
+ {
+ methods: [Methods.GET, Methods.PUT],
+ path: '/projects/projet-test/nodes',
+ action: PermissionActions.ALLOW,
+ description: 'permission on projet-test nodes',
+ created_at: "2022-03-15T09:45:36.531Z",
+ updated_at: "2022-03-15T09:45:36.531Z",
+ permission_id: '2'
+ },
+ {
+ methods: [Methods.GET, Methods.PUT],
+ path: '/projects/projet-bidule',
+ action: PermissionActions.ALLOW,
+ description: 'permission on biduler project',
+ created_at: "2022-03-15T09:45:36.531Z",
+ updated_at: "2022-03-15T09:45:36.531Z",
+ permission_id: '3'
+ }
+]
+
+describe('PermissionsFilterPipe', () => {
+ const pipe = new PermissionsFilterPipe();
+
+ it('create an instance', () => {
+ expect(pipe).toBeTruthy();
+ });
+
+ it('Should return all test permissions', () => {
+ const res = pipe.transform(testPermissions, '');
+ expect(res.length).toBe(3);
+ });
+
+ it('Should return both permissions concerning project projet-test', () => {
+ const res = pipe.transform(testPermissions, 'test');
+ expect(res.length).toBe(2);
+ expect(res).toContain(testPermissions[0]);
+ expect(res).toContain(testPermissions[1]);
+ });
+
+ it('Should return no permissions', () => {
+ const res = pipe.transform(testPermissions, 'aaaaaa');
+ expect(res.length).toBe(0);
+ });
+});
diff --git a/src/app/components/permissions-management/permissions-filter.pipe.ts b/src/app/components/permissions-management/permissions-filter.pipe.ts
new file mode 100644
index 00000000..71b4e75f
--- /dev/null
+++ b/src/app/components/permissions-management/permissions-filter.pipe.ts
@@ -0,0 +1,32 @@
+/*
+* Software Name : GNS3 Web UI
+* Version: 3
+* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
+* SPDX-License-Identifier: GPL-3.0-or-later
+*
+* This software is distributed under the GPL-3.0 or any later version,
+* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
+* or see the "LICENSE" file for more details.
+*
+* Author: Sylvain MATHIEU, Elise LEBEAU
+*/
+import { Pipe, PipeTransform } from '@angular/core';
+import {Permission} from "@models/api/permission";
+
+@Pipe({
+ name: 'permissionsFilter'
+})
+export class PermissionsFilterPipe implements PipeTransform {
+
+ transform(permissions: Permission[], filterText: string): Permission[] {
+ if (!permissions) {
+ return [];
+ }
+ if (filterText === undefined || filterText === null || filterText === '') {
+ return permissions;
+ }
+
+ return permissions.filter((permissions: Permission) => permissions.path.toLowerCase().includes(filterText.toLowerCase()));
+ }
+
+}
diff --git a/src/app/components/permissions-management/permissions-management.component.html b/src/app/components/permissions-management/permissions-management.component.html
new file mode 100644
index 00000000..e0ae94c1
--- /dev/null
+++ b/src/app/components/permissions-management/permissions-management.component.html
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+ {{option.name}}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/app/components/permissions-management/permissions-management.component.scss b/src/app/components/permissions-management/permissions-management.component.scss
new file mode 100644
index 00000000..e7b3efa5
--- /dev/null
+++ b/src/app/components/permissions-management/permissions-management.component.scss
@@ -0,0 +1,36 @@
+.permission-content {
+ max-width: 1400px;
+}
+
+.add-button {
+ height: 40px;
+ width: 160px;
+ margin: 20px;
+}
+
+.loader {
+ position: absolute;
+ margin: auto;
+ height: 175px;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ top: 0;
+ width: 175px;
+}
+
+.add {
+ /* display: flex;
+ flex-direction: row;
+ justify-content: flex-end;
+ padding-right: 20px;
+ padding-bottom: 20px;
+ border-bottom: 1px solid;
+ align-items: center;*/
+}
+
+.permission-filter {
+ border-bottom: 1px solid;
+ margin: 5px;
+ border-bottom-color: #b0bec5;
+}
diff --git a/src/app/components/permissions-management/permissions-management.component.spec.ts b/src/app/components/permissions-management/permissions-management.component.spec.ts
new file mode 100644
index 00000000..e69de29b
diff --git a/src/app/components/permissions-management/permissions-management.component.ts b/src/app/components/permissions-management/permissions-management.component.ts
new file mode 100644
index 00000000..b62f708d
--- /dev/null
+++ b/src/app/components/permissions-management/permissions-management.component.ts
@@ -0,0 +1,80 @@
+/*
+* Software Name : GNS3 Web UI
+* Version: 3
+* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
+* SPDX-License-Identifier: GPL-3.0-or-later
+*
+* This software is distributed under the GPL-3.0 or any later version,
+* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
+* or see the "LICENSE" file for more details.
+*
+* Author: Sylvain MATHIEU, Elise LEBEAU
+*/
+import {Component, ComponentFactoryResolver, OnInit, ViewChild, ViewContainerRef} from '@angular/core';
+import {ActivatedRoute, Router} from "@angular/router";
+import {Controller} from "@models/controller";
+import {PermissionsService} from "@services/permissions.service";
+import {ProgressService} from "../../common/progress/progress.service";
+import {Permission} from "@models/api/permission";
+import {AddPermissionLineComponent} from "@components/permissions-management/add-permission-line/add-permission-line.component";
+import {ControllerService} from "@services/controller.service";
+import {PageEvent} from "@angular/material/paginator";
+import {ApiInformationService} from "@services/ApiInformation/api-information.service";
+import {IGenericApiObject} from "@services/ApiInformation/IGenericApiObject";
+
+@Component({
+ selector: 'app-permissions-management',
+ templateUrl: './permissions-management.component.html',
+ styleUrls: ['./permissions-management.component.scss']
+})
+export class PermissionsManagementComponent implements OnInit {
+ controller: Controller;
+ permissions: Permission[];
+ addPermissionLineComp = AddPermissionLineComponent;
+ newPermissionEdit = false;
+ searchPermissions: any;
+ pageEvent: PageEvent | undefined;
+ filteredOptions: IGenericApiObject[];
+ options: string[] = [];
+
+ @ViewChild('dynamic', {
+ read: ViewContainerRef
+ }) viewContainerRef: ViewContainerRef;
+ isReady = false;
+
+ constructor(private route: ActivatedRoute,
+ private router: Router,
+ private permissionService: PermissionsService,
+ private progressService: ProgressService,
+ private controllerService: ControllerService,
+ private apiInformationService: ApiInformationService) { }
+
+ ngOnInit(): void {
+ const controllerId = this.route.parent.snapshot.paramMap.get('controller_id');
+ this.controllerService.get(+controllerId).then((controller: Controller) => {
+ this.controller = controller;
+ this.refresh();
+ });
+ }
+
+ refresh() {
+ this.permissionService.list(this.controller).subscribe(
+ (permissions: Permission[]) => {
+ this.permissions = permissions;
+ this.isReady = true;
+ },
+ (error) => {
+ this.progressService.setError(error);
+ }
+ );
+ }
+
+ displayFn(value): string {
+ return value && value.name ? value.name : '';
+ }
+
+ changeAutocomplete(inputText) {
+ this.filteredOptions = this.apiInformationService.getIdByObjNameFromCache(inputText);
+ }
+
+}
diff --git a/src/app/components/permissions-management/permissions-type-filter.pipe.ts b/src/app/components/permissions-management/permissions-type-filter.pipe.ts
new file mode 100644
index 00000000..a5ce9fe0
--- /dev/null
+++ b/src/app/components/permissions-management/permissions-type-filter.pipe.ts
@@ -0,0 +1,32 @@
+/*
+* Software Name : GNS3 Web UI
+* Version: 3
+* SPDX-FileCopyrightText: Copyright (c) 2022 Orange Business Services
+* SPDX-License-Identifier: GPL-3.0-or-later
+*
+* This software is distributed under the GPL-3.0 or any later version,
+* the text of which is available at https://www.gnu.org/licenses/gpl-3.0.txt
+* or see the "LICENSE" file for more details.
+*
+* Author: Sylvain MATHIEU, Elise LEBEAU
+*/
+import { Pipe, PipeTransform } from '@angular/core';
+import {Permission} from "@models/api/permission";
+
+@Pipe({
+ name: 'permissionsTypeFilter'
+})
+export class PermissionsTypeFilterPipe implements PipeTransform {
+
+ transform(permissions: Permission[], filterTypeText: string): Permission[] {
+ if (!permissions) {
+ return [];
+ }
+ if (filterTypeText === undefined || filterTypeText === null || filterTypeText === '') {
+ return permissions;
+ }
+
+ return permissions.filter((permissions: Permission) => permissions.path.toLowerCase().includes(filterTypeText.toLowerCase()));
+ }
+
+}
diff --git a/src/app/components/preferences/built-in/built-in-preferences.component.html b/src/app/components/preferences/built-in/built-in-preferences.component.html
index 04a5bb21..c914afb5 100644
--- a/src/app/components/preferences/built-in/built-in-preferences.component.html
+++ b/src/app/components/preferences/built-in/built-in-preferences.component.html
@@ -2,7 +2,7 @@