From d46502b80491e699d50367643e4749951920f1d1 Mon Sep 17 00:00:00 2001 From: Lebeau Elise Date: Wed, 8 Dec 2021 14:27:56 +0000 Subject: [PATCH 01/50] Add User management and group management template --- src/app/app-routing.module.ts | 12 +- src/app/app.module.ts | 4 +- .../add-user-dialog.component.html | 26 ++++ .../add-user-dialog.component.scss | 12 ++ .../add-user-dialog.component.spec.ts | 25 ++++ .../add-user-dialog.component.ts | 68 ++++++++++ .../user-management.component.html | 45 ++++++- .../user-management.component.scss | 5 + .../user-management.component.ts | 119 +++++++++++++++++- .../default-layout.component.html | 2 +- .../default-layout.component.ts | 9 +- src/app/services/user.service.ts | 19 ++- 12 files changed, 332 insertions(+), 14 deletions(-) create mode 100644 src/app/components/user-management/add-user-dialog/add-user-dialog.component.html create mode 100644 src/app/components/user-management/add-user-dialog/add-user-dialog.component.scss create mode 100644 src/app/components/user-management/add-user-dialog/add-user-dialog.component.spec.ts create mode 100644 src/app/components/user-management/add-user-dialog/add-user-dialog.component.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 9b2b3650..48cd5074 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -76,6 +76,12 @@ const routes: Routes = [ { path: 'help', component: HelpComponent }, { path: 'settings', component: SettingsComponent }, { path: 'settings/console', component: ConsoleComponent }, + { + path: 'server/:server_id/user_management', + component: UserManagementComponent, + canActivate: [LoginGuard], + resolve: { server: ServerResolve }, + }, { path: 'installed-software', component: InstalledSoftwareComponent }, { path: 'server/:server_id/systemstatus', component: SystemStatusComponent, canActivate: [LoginGuard] }, @@ -208,15 +214,11 @@ const routes: Routes = [ component: WebConsoleFullWindowComponent, canActivate: [LoginGuard] }, - { - path: 'user_management', - component: UserManagementComponent - }, { path: '**', component: PageNotFoundComponent, } - + ]; @NgModule({ diff --git a/src/app/app.module.ts b/src/app/app.module.ts index caa42a53..b96c4095 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -272,6 +272,7 @@ import { HttpRequestsInterceptor } from './interceptors/http.interceptor'; import { UserManagementComponent } from './components/user-management/user-management.component' import { UserService } from './services/user.service'; import { LoggedUserComponent } from './components/users/logged-user/logged-user.component'; +import { AddUserDialogComponent } from './components/user-management/add-user-dialog/add-user-dialog.component'; @NgModule({ declarations: [ @@ -459,7 +460,8 @@ import { LoggedUserComponent } from './components/users/logged-user/logged-user. ConfigureCustomAdaptersDialogComponent, EditNetworkConfigurationDialogComponent, UserManagementComponent, - ProjectReadmeComponent + ProjectReadmeComponent, + AddUserDialogComponent, ], imports: [ BrowserModule, diff --git a/src/app/components/user-management/add-user-dialog/add-user-dialog.component.html b/src/app/components/user-management/add-user-dialog/add-user-dialog.component.html new file mode 100644 index 00000000..c9ce0f5b --- /dev/null +++ b/src/app/components/user-management/add-user-dialog/add-user-dialog.component.html @@ -0,0 +1,26 @@ +

Create new user

+
+ + + + + + + + + + + + + + Is active + Is admin + +
+ + +
+
diff --git a/src/app/components/user-management/add-user-dialog/add-user-dialog.component.scss b/src/app/components/user-management/add-user-dialog/add-user-dialog.component.scss new file mode 100644 index 00000000..0907b1ef --- /dev/null +++ b/src/app/components/user-management/add-user-dialog/add-user-dialog.component.scss @@ -0,0 +1,12 @@ +.input-field { + width: 100%; +} + +.checkbox-field { + display: block; + margin-bottom: 5px; +} + +.button-div { + float: right; +} diff --git a/src/app/components/user-management/add-user-dialog/add-user-dialog.component.spec.ts b/src/app/components/user-management/add-user-dialog/add-user-dialog.component.spec.ts new file mode 100644 index 00000000..2b92fda2 --- /dev/null +++ b/src/app/components/user-management/add-user-dialog/add-user-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AddUserDialogComponent } from './add-user-dialog.component'; + +describe('AddUserDialogComponent', () => { + let component: AddUserDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AddUserDialogComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AddUserDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/user-management/add-user-dialog/add-user-dialog.component.ts b/src/app/components/user-management/add-user-dialog/add-user-dialog.component.ts new file mode 100644 index 00000000..771c84a1 --- /dev/null +++ b/src/app/components/user-management/add-user-dialog/add-user-dialog.component.ts @@ -0,0 +1,68 @@ +/* +* 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 {FormControl, FormGroup, Validators} from "@angular/forms"; +import {MatDialogRef} from "@angular/material/dialog"; +import {UserService} from "@services/user.service"; +import {Server} from "@models/server"; +import {User} from "@models/users/user"; +import {ToasterService} from "@services/toaster.service"; + +@Component({ + selector: 'app-add-user-dialog', + templateUrl: './add-user-dialog.component.html', + styleUrls: ['./add-user-dialog.component.scss'] +}) +export class AddUserDialogComponent implements OnInit { + addUserForm: FormGroup; + server: Server; + + constructor(public dialogRef: MatDialogRef, + public userService: UserService, + private toasterService: ToasterService) { } + + ngOnInit(): void { + this.addUserForm = new FormGroup({ + username: new FormControl(null, [ + Validators.required, + Validators.minLength(3), + Validators.pattern("[a-zA-Z0-9_-]+$")]), + full_name: new FormControl(), + email: new FormControl(null, [Validators.email, Validators.required]), + password: new FormControl(null, + [Validators.required, Validators.minLength(6), Validators.maxLength(100)]), + is_active: new FormControl(true), + is_superadmin: new FormControl(false) + }); + } + + onCancelClick() { + this.dialogRef.close(); + } + + onAddClick() { + if (!this.addUserForm.valid) { + return; + } + const newUser = this.addUserForm.value; + this.userService.add(this.server, newUser) + .subscribe((user: User) => { + console.log("Done ", user) + this.toasterService.success(`User ${user.username} added`); + this.dialogRef.close(); + }, + (error) => { + this.toasterService.error('Cannot create user : ' + error); + }) + } +} diff --git a/src/app/components/user-management/user-management.component.html b/src/app/components/user-management/user-management.component.html index e453d528..4a7d8f47 100644 --- a/src/app/components/user-management/user-management.component.html +++ b/src/app/components/user-management/user-management.component.html @@ -1,3 +1,42 @@ -

- user-management works! -

+
+
+
+

User Management

+ +
+
+ +
+
+ + + Name + + {{ row.username }} + + + + Mail + {{ row.email }} + + + Active + {{row.is_active}} + + + Admin + {{row.is_superadmin}} + + + Last Update + {{row.updated_at ? row.updated_at : row.created_at}} + + + + + +
+
+
diff --git a/src/app/components/user-management/user-management.component.scss b/src/app/components/user-management/user-management.component.scss index e69de29b..5819f4a2 100644 --- a/src/app/components/user-management/user-management.component.scss +++ b/src/app/components/user-management/user-management.component.scss @@ -0,0 +1,5 @@ +.add-button { + height: 40px; + width: 160px; + margin: 20px; +} diff --git a/src/app/components/user-management/user-management.component.ts b/src/app/components/user-management/user-management.component.ts index a374bb28..c06bd280 100644 --- a/src/app/components/user-management/user-management.component.ts +++ b/src/app/components/user-management/user-management.component.ts @@ -1,4 +1,28 @@ -import { Component, OnInit } from '@angular/core'; +/* +* 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, ViewChild} from '@angular/core'; +import {ActivatedRoute, Router} from "@angular/router"; +import {Server} from "@models/server"; +import {MatSort, MatSortable} from "@angular/material/sort"; +import {UserService} from "@services/user.service"; +import {ProgressService} from "../../common/progress/progress.service"; +import {User} from "@models/users/user"; +import {BehaviorSubject, merge, Observable} from "rxjs"; +import {DataSource} from "@angular/cdk/collections"; +import {map} from "rxjs/operators"; +import {AddBlankProjectDialogComponent} from "@components/projects/add-blank-project-dialog/add-blank-project-dialog.component"; +import {AddUserDialogComponent} from "@components/user-management/add-user-dialog/add-user-dialog.component"; +import {MatDialog} from "@angular/material/dialog"; @Component({ selector: 'app-user-management', @@ -6,10 +30,101 @@ import { Component, OnInit } from '@angular/core'; styleUrls: ['./user-management.component.scss'] }) export class UserManagementComponent implements OnInit { + server: Server; + dataSource: UserDataSource; + userDatabase = new UserDatabase(); + displayedColumns = ['name', 'email', 'is_active', 'is_superadmin', 'last']; - constructor() { } + @ViewChild(MatSort, { static: true }) sort: MatSort; + + constructor( + private route: ActivatedRoute, + private router: Router, + private userService: UserService, + private progressService: ProgressService, + public dialog: MatDialog) { } ngOnInit() { + this.server = this.route.snapshot.data['server']; + if (!this.server) this.router.navigate(['/servers']); + + this.refresh(); + this.sort.sort({ + id: 'name', + start: 'asc', + }); + this.dataSource = new UserDataSource(this.userDatabase, this.sort); } + refresh() { + this.userService.list(this.server).subscribe( + (users: User[]) => { + this.userDatabase.addUsers(users); + }, + (error) => { + this.progressService.setError(error); + } + ); + } + + addUser() { + const dialogRef = this.dialog.open(AddUserDialogComponent, { + width: '400px', + autoFocus: false, + disableClose: true, + }); + let instance = dialogRef.componentInstance; + instance.server = this.server; + dialogRef.afterClosed().subscribe(() => this.refresh()); + } +} + +export class UserDatabase { + dataChange: BehaviorSubject = new BehaviorSubject([]); + + get data(): User[] { + return this.dataChange.value; + } + + public addUsers(users: User[]) { + this.dataChange.next(users); + } + + public remove(user: User) { + const index = this.data.indexOf(user); + if (index >= 0) { + this.data.splice(index, 1); + this.dataChange.next(this.data.slice()); + } + } +} + +export class UserDataSource extends DataSource { + constructor(public userDatabase: UserDatabase, private sort: MatSort) { + super(); + } + + connect(): Observable { + const displayDataChanges = [this.userDatabase.dataChange, this.sort.sortChange]; + + return merge(...displayDataChanges).pipe( + map(() => { + if (!this.sort.active || this.sort.direction === '') { + return this.userDatabase.data; + } + + return this.userDatabase.data.sort((a, b) => { + const propertyA = a[this.sort.active]; + const propertyB = b[this.sort.active]; + + const valueA = isNaN(+propertyA) ? propertyA : +propertyA; + const valueB = isNaN(+propertyB) ? propertyB : +propertyB; + + return (valueA < valueB ? -1 : 1) * (this.sort.direction === 'asc' ? 1 : -1); + }); + }) + ); + } + + disconnect() {} } diff --git a/src/app/layouts/default-layout/default-layout.component.html b/src/app/layouts/default-layout/default-layout.component.html index 483f6310..ba883a12 100644 --- a/src/app/layouts/default-layout/default-layout.component.html +++ b/src/app/layouts/default-layout/default-layout.component.html @@ -23,7 +23,7 @@ settings Settings - diff --git a/src/app/layouts/default-layout/default-layout.component.ts b/src/app/layouts/default-layout/default-layout.component.ts index 57e47444..dcf4915e 100644 --- a/src/app/layouts/default-layout/default-layout.component.ts +++ b/src/app/layouts/default-layout/default-layout.component.ts @@ -45,7 +45,7 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy { this.routeSubscription = this.router.events.subscribe((val) => { if (val instanceof NavigationEnd) this.checkIfUserIsLoginPage(); }); - + this.recentlyOpenedServerId = this.recentlyOpenedProjectService.getServerId(); this.recentlyOpenedProjectId = this.recentlyOpenedProjectService.getProjectId(); this.serverIdProjectList = this.recentlyOpenedProjectService.getServerIdProjectList(); @@ -129,4 +129,11 @@ export class DefaultLayoutComponent implements OnInit, OnDestroy { this.serverStatusSubscription.unsubscribe(); this.routeSubscription.unsubscribe(); } + + goToUserManagement() { + let serverId = this.router.url.split("/server/")[1].split("/")[0]; + this.serverService.get(+serverId).then((server: Server) => { + this.router.navigate(['/server', server.id, 'user_management']); + }); + } } diff --git a/src/app/services/user.service.ts b/src/app/services/user.service.ts index 8cd1d4e5..e90cf9a1 100644 --- a/src/app/services/user.service.ts +++ b/src/app/services/user.service.ts @@ -1,8 +1,9 @@ import { Injectable } from '@angular/core'; -import { Subject } from 'rxjs'; +import {Observable, Subject} from 'rxjs'; import { Server } from '../models/server'; import { HttpServer } from './http-server.service'; import { User } from '../models/users/user'; +import {Project} from "@models/project"; @Injectable() export class UserService { @@ -13,4 +14,20 @@ export class UserService { getInformationAboutLoggedUser(server: Server) { return this.httpServer.get(server, '/users/me/'); } + + list(server: Server) { + return this.httpServer.get(server, '/users'); + } + + add(server: Server, user: any): Observable { + console.log(user) + return this.httpServer.post(server, `/users`, { + username: user.username, + is_active: user.is_active, + email: user.email, + full_name: user.full_name, + password: user.password + }); + } + } From 4e2d0435619a0fd4bf36889f28f3f081905faf4d Mon Sep 17 00:00:00 2001 From: Lebeau Elise Date: Thu, 9 Dec 2021 15:13:02 +0000 Subject: [PATCH 02/50] Add search filter to user management --- src/app/app.module.ts | 2 ++ .../user-management.component.html | 8 ++++- .../user-management.component.scss | 6 ++++ .../user-management.component.ts | 3 ++ src/app/filters/user-filter.pipe.spec.ts | 8 +++++ src/app/filters/user-filter.pipe.ts | 31 +++++++++++++++++++ 6 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 src/app/filters/user-filter.pipe.spec.ts create mode 100644 src/app/filters/user-filter.pipe.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index b96c4095..7e0df83a 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -273,6 +273,7 @@ import { UserManagementComponent } from './components/user-management/user-manag import { UserService } from './services/user.service'; import { LoggedUserComponent } from './components/users/logged-user/logged-user.component'; import { AddUserDialogComponent } from './components/user-management/add-user-dialog/add-user-dialog.component'; +import { UserFilterPipe } from './filters/user-filter.pipe'; @NgModule({ declarations: [ @@ -462,6 +463,7 @@ import { AddUserDialogComponent } from './components/user-management/add-user-di UserManagementComponent, ProjectReadmeComponent, AddUserDialogComponent, + UserFilterPipe, ], imports: [ BrowserModule, diff --git a/src/app/components/user-management/user-management.component.html b/src/app/components/user-management/user-management.component.html index 4a7d8f47..bf14d26c 100644 --- a/src/app/components/user-management/user-management.component.html +++ b/src/app/components/user-management/user-management.component.html @@ -8,9 +8,15 @@ +
+ + + +
+
- + Name diff --git a/src/app/components/user-management/user-management.component.scss b/src/app/components/user-management/user-management.component.scss index 5819f4a2..b264352a 100644 --- a/src/app/components/user-management/user-management.component.scss +++ b/src/app/components/user-management/user-management.component.scss @@ -3,3 +3,9 @@ width: 160px; margin: 20px; } + +.full-width { + width: 940px; + margin-left: -470px; + left: 50%; +} diff --git a/src/app/components/user-management/user-management.component.ts b/src/app/components/user-management/user-management.component.ts index c06bd280..936d16fc 100644 --- a/src/app/components/user-management/user-management.component.ts +++ b/src/app/components/user-management/user-management.component.ts @@ -35,6 +35,8 @@ export class UserManagementComponent implements OnInit { userDatabase = new UserDatabase(); displayedColumns = ['name', 'email', 'is_active', 'is_superadmin', 'last']; + searchText: string = ''; + @ViewChild(MatSort, { static: true }) sort: MatSort; constructor( @@ -77,6 +79,7 @@ export class UserManagementComponent implements OnInit { instance.server = this.server; dialogRef.afterClosed().subscribe(() => this.refresh()); } + } export class UserDatabase { diff --git a/src/app/filters/user-filter.pipe.spec.ts b/src/app/filters/user-filter.pipe.spec.ts new file mode 100644 index 00000000..46766027 --- /dev/null +++ b/src/app/filters/user-filter.pipe.spec.ts @@ -0,0 +1,8 @@ +import { UserFilterPipe } from './user-filter.pipe'; + +describe('UserFilterPipe', () => { + it('create an instance', () => { + const pipe = new UserFilterPipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/src/app/filters/user-filter.pipe.ts b/src/app/filters/user-filter.pipe.ts new file mode 100644 index 00000000..0086bdb3 --- /dev/null +++ b/src/app/filters/user-filter.pipe.ts @@ -0,0 +1,31 @@ +/* +* 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 {UserDataSource} from "@components/user-management/user-management.component"; + +@Pipe({ + name: 'userFilter' +}) +export class UserFilterPipe implements PipeTransform { + + transform(items: UserDataSource, searchText: string) { + if (!items) return []; + if (!searchText) return items; + searchText = searchText.toLowerCase(); + return items.userDatabase.data.filter((item) => { + return (item.username && item.username.toLowerCase().includes(searchText)) + || (item.email && item.email.toLowerCase().includes(searchText)); + }); + } + +} From e37f1e97fa506ffcc8f181667107a94772c8d508 Mon Sep 17 00:00:00 2001 From: Sylvain MATHIEU Date: Thu, 9 Dec 2021 10:45:45 +0100 Subject: [PATCH 03/50] Add group management list --- src/app/app-routing.module.ts | 2 + src/app/app.module.ts | 4 +- .../group-management.component.html | 43 +++++++++++ .../group-management.component.scss | 4 + .../group-management.component.spec.ts | 25 +++++++ .../group-management.component.ts | 74 +++++++++++++++++++ .../default-layout.component.html | 4 + .../default-layout.component.ts | 7 ++ src/app/models/groups/group.ts | 19 +++++ src/app/services/group.service.spec.ts | 16 ++++ src/app/services/group.service.ts | 35 +++++++++ 11 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 src/app/components/group-management/group-management.component.html create mode 100644 src/app/components/group-management/group-management.component.scss create mode 100644 src/app/components/group-management/group-management.component.spec.ts create mode 100644 src/app/components/group-management/group-management.component.ts create mode 100644 src/app/models/groups/group.ts create mode 100644 src/app/services/group.service.spec.ts create mode 100644 src/app/services/group.service.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 48cd5074..c83e3179 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -56,6 +56,7 @@ import { DefaultLayoutComponent } from './layouts/default-layout/default-layout. import { ServerResolve } from './resolvers/server-resolve'; import { UserManagementComponent } from './components/user-management/user-management.component'; import { LoggedUserComponent } from './components/users/logged-user/logged-user.component'; +import {GroupManagementComponent} from "./components/group-management/group-management.component"; const routes: Routes = [ { @@ -196,6 +197,7 @@ const routes: Routes = [ { path: 'server/:server_id/preferences/iou/templates/:template_id', component: IouTemplateDetailsComponent, canActivate: [LoginGuard] }, { path: 'server/:server_id/preferences/iou/templates/:template_id/copy', component: CopyIouTemplateComponent, canActivate: [LoginGuard] }, { path: 'server/:server_id/preferences/iou/addtemplate', component: AddIouTemplateComponent, canActivate: [LoginGuard] }, + { path: 'server/:server_id/group_management', component: GroupManagementComponent}, ], }, { diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 7e0df83a..58f46eac 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -269,9 +269,10 @@ import { MarkedDirective } from './directives/marked.directive'; import { LoginComponent } from './components/login/login.component'; import { LoginService } from './services/login.service'; import { HttpRequestsInterceptor } from './interceptors/http.interceptor'; -import { UserManagementComponent } from './components/user-management/user-management.component' +import { UserManagementComponent } from './components/user-management/user-management.component'; import { UserService } from './services/user.service'; import { LoggedUserComponent } from './components/users/logged-user/logged-user.component'; +import { GroupManagementComponent } from './components/group-management/group-management.component'; import { AddUserDialogComponent } from './components/user-management/add-user-dialog/add-user-dialog.component'; import { UserFilterPipe } from './filters/user-filter.pipe'; @@ -462,6 +463,7 @@ import { UserFilterPipe } from './filters/user-filter.pipe'; EditNetworkConfigurationDialogComponent, UserManagementComponent, ProjectReadmeComponent, + GroupManagementComponent, AddUserDialogComponent, UserFilterPipe, ], 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..e3eb9982 --- /dev/null +++ b/src/app/components/group-management/group-management.component.html @@ -0,0 +1,43 @@ +
+
+
+

Groups management

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Name {{element.name}} Creation date {{element.created_at}} last update {{element.updated_at}} is build in {{element.is_builtin}} delete
+
+
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..6a98db4f --- /dev/null +++ b/src/app/components/group-management/group-management.component.scss @@ -0,0 +1,4 @@ +table { + width: 100%; +} + 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..374c0a34 --- /dev/null +++ b/src/app/components/group-management/group-management.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { GroupManagementComponent } from './group-management.component'; + +describe('GroupManagementComponent', () => { + let component: GroupManagementComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ GroupManagementComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(GroupManagementComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); 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..e20ee78f --- /dev/null +++ b/src/app/components/group-management/group-management.component.ts @@ -0,0 +1,74 @@ +/* +* 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 {ServerService} from "../../services/server.service"; +import {ToasterService} from "../../services/toaster.service"; +import {GroupService} from "../../services/group.service"; +import {Server} from "../../models/server"; +import {Group} from "../../models/groups/group"; +import {Sort} from "@angular/material/sort"; + +@Component({ + selector: 'app-group-management', + templateUrl: './group-management.component.html', + styleUrls: ['./group-management.component.scss'] +}) +export class GroupManagementComponent implements OnInit { + server: Server; + + public displayedColumns = ['name', 'created_at', 'updated_at', 'is_builtin', 'delete']; + groups: Group[]; + sortedGroups: Group[]; + + constructor( + private route: ActivatedRoute, + private serverService: ServerService, + private toasterService: ToasterService, + public groupService: GroupService, + ) { + } + + ngOnInit(): void { + const serverId = this.route.snapshot.paramMap.get('server_id'); + this.serverService.get(+serverId).then((server: Server) => { + this.server = server; + this.groupService.getGroups(server).subscribe((groups: Group[]) => { + this.groups = groups; + this.sortedGroups = groups; + }); + }); + } + + sort(sort: Sort) { + const data = this.groups.slice(); + if (!sort.active || sort.direction === '') { + this.sortedGroups = data; + return; + } + + this.sortedGroups = data.sort((a, b) => { + const isAsc = sort.direction === 'asc'; + switch (sort.active) { + case 'name': + return compare(a.name, b.name, isAsc); + default: + return 0; + } + }); + + function compare(a: number | string, b: number | string, isAsc: boolean) { + return (a < b ? -1 : 1) * (isAsc ? 1 : -1); + } + } +} diff --git a/src/app/layouts/default-layout/default-layout.component.html b/src/app/layouts/default-layout/default-layout.component.html index ba883a12..925ab581 100644 --- a/src/app/layouts/default-layout/default-layout.component.html +++ b/src/app/layouts/default-layout/default-layout.component.html @@ -27,6 +27,10 @@ groups User management +
+ +
+ + + +
+
- +
diff --git a/src/app/components/group-management/group-management.component.scss b/src/app/components/group-management/group-management.component.scss index 6a98db4f..48b24ba6 100644 --- a/src/app/components/group-management/group-management.component.scss +++ b/src/app/components/group-management/group-management.component.scss @@ -2,3 +2,9 @@ table { width: 100%; } +.full-width { + width: 940px; + margin-left: -470px; + left: 50%; +} + diff --git a/src/app/components/group-management/group-management.component.ts b/src/app/components/group-management/group-management.component.ts index e20ee78f..2311ca36 100644 --- a/src/app/components/group-management/group-management.component.ts +++ b/src/app/components/group-management/group-management.component.ts @@ -30,6 +30,7 @@ export class GroupManagementComponent implements OnInit { public displayedColumns = ['name', 'created_at', 'updated_at', 'is_builtin', 'delete']; groups: Group[]; sortedGroups: Group[]; + searchText: string; constructor( private route: ActivatedRoute, diff --git a/src/app/filters/group-filter.pipe.spec.ts b/src/app/filters/group-filter.pipe.spec.ts new file mode 100644 index 00000000..32b6e979 --- /dev/null +++ b/src/app/filters/group-filter.pipe.spec.ts @@ -0,0 +1,8 @@ +import { GroupFilterPipe } from './group-filter.pipe'; + +describe('GroupFilterPipe', () => { + it('create an instance', () => { + const pipe = new GroupFilterPipe(); + expect(pipe).toBeTruthy(); + }); +}); diff --git a/src/app/filters/group-filter.pipe.ts b/src/app/filters/group-filter.pipe.ts new file mode 100644 index 00000000..25653143 --- /dev/null +++ b/src/app/filters/group-filter.pipe.ts @@ -0,0 +1,35 @@ +/* +* 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 {Group} from "../models/groups/group"; + +@Pipe({ + name: 'groupFilter' +}) +export class GroupFilterPipe implements PipeTransform { + + transform(groups: Group[], searchText: string): Group[] { + if (!groups) { + return []; + } + if (!searchText) { + return groups; + } + + searchText = searchText.toLowerCase(); + return groups.filter((group: Group) => { + return group.name.toLowerCase().includes(searchText); + }); + } + +} From 7219f02783e856fceb048bb9d021606fdd718485 Mon Sep 17 00:00:00 2001 From: Sylvain MATHIEU Date: Thu, 9 Dec 2021 15:23:57 +0100 Subject: [PATCH 05/50] group management, add create new group functionality --- src/app/app.module.ts | 2 + .../add-group-dialog/GroupNameValidator.ts | 14 +++++ .../add-group-dialog.component.html | 28 +++++++++ .../add-group-dialog.component.scss | 7 +++ .../add-group-dialog.component.spec.ts | 25 ++++++++ .../add-group-dialog.component.ts | 57 +++++++++++++++++++ .../groupNameAsyncValidator.ts | 17 ++++++ .../group-management.component.html | 3 + .../group-management.component.scss | 5 ++ .../group-management.component.ts | 23 ++++++++ src/app/services/group.service.ts | 10 +++- 11 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 src/app/components/group-management/add-group-dialog/GroupNameValidator.ts create mode 100644 src/app/components/group-management/add-group-dialog/add-group-dialog.component.html create mode 100644 src/app/components/group-management/add-group-dialog/add-group-dialog.component.scss create mode 100644 src/app/components/group-management/add-group-dialog/add-group-dialog.component.spec.ts create mode 100644 src/app/components/group-management/add-group-dialog/add-group-dialog.component.ts create mode 100644 src/app/components/group-management/add-group-dialog/groupNameAsyncValidator.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 4fce5525..691c6210 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -275,6 +275,7 @@ import { UserService } from './services/user.service'; import { LoggedUserComponent } from './components/users/logged-user/logged-user.component'; import { GroupManagementComponent } from './components/group-management/group-management.component'; import { GroupFilterPipe } from './filters/group-filter.pipe'; +import { AddGroupDialogComponent } from './components/group-management/add-group-dialog/add-group-dialog.component'; import { AddUserDialogComponent } from './components/user-management/add-user-dialog/add-user-dialog.component'; import { UserFilterPipe } from './filters/user-filter.pipe'; @@ -465,6 +466,7 @@ import { UserFilterPipe } from './filters/user-filter.pipe'; EditNetworkConfigurationDialogComponent, UserManagementComponent, ProjectReadmeComponent, + AddGroupDialogComponent, GroupFilterPipe, GroupManagementComponent, AddUserDialogComponent, 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..3450506d --- /dev/null +++ b/src/app/components/group-management/add-group-dialog/GroupNameValidator.ts @@ -0,0 +1,14 @@ +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..bc1026c2 --- /dev/null +++ b/src/app/components/group-management/add-group-dialog/add-group-dialog.component.html @@ -0,0 +1,28 @@ +

Create new group

+
+ + + Group name is required + Group name is incorrect + Group with this name exists + +
+ + +
+ 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..2fa952b3 --- /dev/null +++ b/src/app/components/group-management/add-group-dialog/add-group-dialog.component.scss @@ -0,0 +1,7 @@ +.file-name-form-field { + width: 100%; +} + +.project-snackbar { + background: #2196f3; +} 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..4efbdf61 --- /dev/null +++ b/src/app/components/group-management/add-group-dialog/add-group-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AddGroupDialogComponent } from './add-group-dialog.component'; + +describe('AddGroupDialogComponent', () => { + let component: AddGroupDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AddGroupDialogComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AddGroupDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); 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..981b9521 --- /dev/null +++ b/src/app/components/group-management/add-group-dialog/add-group-dialog.component.ts @@ -0,0 +1,57 @@ +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 {Server} from "../../../models/server"; + +@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; + + constructor(private dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: { server: Server }, + private formBuilder: FormBuilder, + private groupNameValidator: GroupNameValidator, + private groupService: GroupService) { + } + + ngOnInit(): void { + this.groupNameForm = this.formBuilder.group({ + groupName: new FormControl( + null, + [Validators.required, this.groupNameValidator.get], + [groupNameAsyncValidator(this.data.server, this.groupService)] + ), + }); + } + + 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; + this.dialogRef.close(groupName); + } + + onNoClick() { + this.dialogRef.close(); + } +} 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..d8ea0fd9 --- /dev/null +++ b/src/app/components/group-management/add-group-dialog/groupNameAsyncValidator.ts @@ -0,0 +1,17 @@ +import { FormControl } from '@angular/forms'; +import { timer } from 'rxjs'; +import {map, switchMap, tap} from 'rxjs/operators'; +import {Server} from "../../../models/server"; +import {GroupService} from "../../../services/group.service"; + +export const groupNameAsyncValidator = (server: Server, groupService: GroupService) => { + return (control: FormControl) => { + return timer(500).pipe( + switchMap(() => groupService.getGroups(server)), + map((response) => { + console.log(response); + return (response.find((n) => n.name === control.value) ? { projectExist: true } : null); + }) + ); + }; +}; diff --git a/src/app/components/group-management/group-management.component.html b/src/app/components/group-management/group-management.component.html index 29cd1fc1..9bef23b8 100644 --- a/src/app/components/group-management/group-management.component.html +++ b/src/app/components/group-management/group-management.component.html @@ -2,6 +2,9 @@

Groups management

+
diff --git a/src/app/components/group-management/group-management.component.scss b/src/app/components/group-management/group-management.component.scss index 48b24ba6..acc51935 100644 --- a/src/app/components/group-management/group-management.component.scss +++ b/src/app/components/group-management/group-management.component.scss @@ -8,3 +8,8 @@ table { left: 50%; } +.add-group-button { + height: 40px; + width: 160px; + margin: 20px; +} diff --git a/src/app/components/group-management/group-management.component.ts b/src/app/components/group-management/group-management.component.ts index 2311ca36..04ecc37c 100644 --- a/src/app/components/group-management/group-management.component.ts +++ b/src/app/components/group-management/group-management.component.ts @@ -18,6 +18,8 @@ import {GroupService} from "../../services/group.service"; import {Server} from "../../models/server"; import {Group} from "../../models/groups/group"; import {Sort} from "@angular/material/sort"; +import {MatDialog} from "@angular/material/dialog"; +import {AddGroupDialogComponent} from "@components/group-management/add-group-dialog/add-group-dialog.component"; @Component({ selector: 'app-group-management', @@ -37,6 +39,7 @@ export class GroupManagementComponent implements OnInit { private serverService: ServerService, private toasterService: ToasterService, public groupService: GroupService, + public dialog: MatDialog ) { } @@ -72,4 +75,24 @@ export class GroupManagementComponent implements OnInit { return (a < b ? -1 : 1) * (isAsc ? 1 : -1); } } + + + addGroup() { + this.dialog + .open(AddGroupDialogComponent, {width: '250px', height: '250px', data: {server: this.server}}) + .afterClosed() + .subscribe((name: string) => { + if (name) { + this.groupService.addGroup(this.server, name) + .subscribe(() => { + this.groupService.getGroups(this.server).subscribe((groups: Group[]) => { + this.groups = groups; + this.sortedGroups = groups; + }); + }, (error) => { + this.toasterService.error(`An error occur while trying to create new group ${name}`); + }); + } + }); + } } diff --git a/src/app/services/group.service.ts b/src/app/services/group.service.ts index 3a5e0b08..bd549359 100644 --- a/src/app/services/group.service.ts +++ b/src/app/services/group.service.ts @@ -10,11 +10,12 @@ * * Author: Sylvain MATHIEU, Elise LEBEAU */ -import { Injectable } from '@angular/core'; +import {Injectable} from '@angular/core'; import {HttpServer} from "./http-server.service"; import {Server} from "../models/server"; import {Group} from "../models/groups/group"; import {User} from "../models/users/user"; +import {Observable} from "rxjs"; @Injectable({ providedIn: 'root' @@ -23,7 +24,8 @@ export class GroupService { constructor( private httpServer: HttpServer - ) { } + ) { + } getGroups(server: Server) { return this.httpServer.get(server, '/groups'); @@ -32,4 +34,8 @@ export class GroupService { getGroupMember(server: Server, groupId: string) { return this.httpServer.get(server, `/groups/${groupId}/members`); } + + addGroup(server: Server, name: string): Observable { + return this.httpServer.post(server, `/groups`, {name}); + } } From 9546ed94fef023ac5607e0d21180642c3af2d508 Mon Sep 17 00:00:00 2001 From: Sylvain MATHIEU Date: Thu, 9 Dec 2021 15:47:33 +0100 Subject: [PATCH 06/50] group management, add trash icon on each group to delete it --- src/app/app.module.ts | 2 ++ .../add-group-dialog/GroupNameValidator.ts | 12 +++++++++ .../add-group-dialog.component.ts | 12 +++++++++ .../groupNameAsyncValidator.ts | 12 +++++++++ .../delete-group-dialog.component.html | 7 ++++++ .../delete-group-dialog.component.scss | 6 +++++ .../delete-group-dialog.component.spec.ts | 25 +++++++++++++++++++ .../delete-group-dialog.component.ts | 25 +++++++++++++++++++ .../group-management.component.html | 2 +- .../group-management.component.ts | 20 +++++++++++++++ src/app/services/group.service.ts | 4 +++ 11 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 src/app/components/group-management/delete-group-dialog/delete-group-dialog.component.html create mode 100644 src/app/components/group-management/delete-group-dialog/delete-group-dialog.component.scss create mode 100644 src/app/components/group-management/delete-group-dialog/delete-group-dialog.component.spec.ts create mode 100644 src/app/components/group-management/delete-group-dialog/delete-group-dialog.component.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 691c6210..9a36ea70 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -276,6 +276,7 @@ import { LoggedUserComponent } from './components/users/logged-user/logged-user. import { GroupManagementComponent } from './components/group-management/group-management.component'; import { GroupFilterPipe } from './filters/group-filter.pipe'; 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 { AddUserDialogComponent } from './components/user-management/add-user-dialog/add-user-dialog.component'; import { UserFilterPipe } from './filters/user-filter.pipe'; @@ -466,6 +467,7 @@ import { UserFilterPipe } from './filters/user-filter.pipe'; EditNetworkConfigurationDialogComponent, UserManagementComponent, ProjectReadmeComponent, + DeleteGroupDialogComponent, AddGroupDialogComponent, GroupFilterPipe, GroupManagementComponent, diff --git a/src/app/components/group-management/add-group-dialog/GroupNameValidator.ts b/src/app/components/group-management/add-group-dialog/GroupNameValidator.ts index 3450506d..09b9cda8 100644 --- a/src/app/components/group-management/add-group-dialog/GroupNameValidator.ts +++ b/src/app/components/group-management/add-group-dialog/GroupNameValidator.ts @@ -1,3 +1,15 @@ +/* +* 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() 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 index 981b9521..c73e745e 100644 --- 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 @@ -1,3 +1,15 @@ +/* +* 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"; diff --git a/src/app/components/group-management/add-group-dialog/groupNameAsyncValidator.ts b/src/app/components/group-management/add-group-dialog/groupNameAsyncValidator.ts index d8ea0fd9..a511d16c 100644 --- a/src/app/components/group-management/add-group-dialog/groupNameAsyncValidator.ts +++ b/src/app/components/group-management/add-group-dialog/groupNameAsyncValidator.ts @@ -1,3 +1,15 @@ +/* +* 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'; 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..a0f3c00f --- /dev/null +++ b/src/app/components/group-management/delete-group-dialog/delete-group-dialog.component.html @@ -0,0 +1,7 @@ +

Are you sure to delete group named: '{{data.groupName}}' ?

+
+ + +
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..b82479f3 --- /dev/null +++ b/src/app/components/group-management/delete-group-dialog/delete-group-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DeleteGroupDialogComponent } from './delete-group-dialog.component'; + +describe('DeleteGroupDialogComponent', () => { + let component: DeleteGroupDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ DeleteGroupDialogComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(DeleteGroupDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); 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..acdb01e4 --- /dev/null +++ b/src/app/components/group-management/delete-group-dialog/delete-group-dialog.component.ts @@ -0,0 +1,25 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {Server} from "@models/server"; + +@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: { groupName: string }) { } + + 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 index 9bef23b8..9d512124 100644 --- a/src/app/components/group-management/group-management.component.html +++ b/src/app/components/group-management/group-management.component.html @@ -41,7 +41,7 @@
- + diff --git a/src/app/components/group-management/group-management.component.ts b/src/app/components/group-management/group-management.component.ts index 04ecc37c..e1f9fb7a 100644 --- a/src/app/components/group-management/group-management.component.ts +++ b/src/app/components/group-management/group-management.component.ts @@ -20,6 +20,7 @@ import {Group} from "../../models/groups/group"; import {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"; @Component({ selector: 'app-group-management', @@ -95,4 +96,23 @@ export class GroupManagementComponent implements OnInit { } }); } + + onDelete(group: Group) { + this.dialog + .open(DeleteGroupDialogComponent, {width: '500px', height: '250px', data: {groupName: group.name}}) + .afterClosed() + .subscribe((isDeletedConfirm) => { + if (isDeletedConfirm) { + this.groupService.delete(this.server, group.user_group_id) + .subscribe(() => { + this.groupService.getGroups(this.server).subscribe((groups: Group[]) => { + this.groups = groups; + this.sortedGroups = groups; + }); + }, (error) => { + this.toasterService.error(`An error occur while trying to delete group ${group.name}`); + }); + } + }); + } } diff --git a/src/app/services/group.service.ts b/src/app/services/group.service.ts index bd549359..62f14eb4 100644 --- a/src/app/services/group.service.ts +++ b/src/app/services/group.service.ts @@ -38,4 +38,8 @@ export class GroupService { addGroup(server: Server, name: string): Observable { return this.httpServer.post(server, `/groups`, {name}); } + + delete(server: Server, user_group_id: string) { + return this.httpServer.delete(server, `/groups/${user_group_id}`); + } } From 4e207d270e00e3697cf4f7bbb6251ff9db254b78 Mon Sep 17 00:00:00 2001 From: Lebeau Elise Date: Thu, 9 Dec 2021 15:58:22 +0000 Subject: [PATCH 07/50] user management, add delete button on each row --- src/app/app.module.ts | 8 +++--- .../delete-group-dialog.component.ts | 12 +++++++++ .../delete-user-dialog.component.html | 7 +++++ .../delete-user-dialog.component.scss | 3 +++ .../delete-user-dialog.component.spec.ts | 25 ++++++++++++++++++ .../delete-user-dialog.component.ts | 26 +++++++++++++++++++ .../user-management.component.html | 7 ++++- .../user-management.component.ts | 23 +++++++++++++--- src/app/services/user.service.ts | 3 +++ 9 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 src/app/components/user-management/delete-user-dialog/delete-user-dialog.component.html create mode 100644 src/app/components/user-management/delete-user-dialog/delete-user-dialog.component.scss create mode 100644 src/app/components/user-management/delete-user-dialog/delete-user-dialog.component.spec.ts create mode 100644 src/app/components/user-management/delete-user-dialog/delete-user-dialog.component.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 9a36ea70..d5b43685 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -273,12 +273,13 @@ import { HttpRequestsInterceptor } from './interceptors/http.interceptor'; import { UserManagementComponent } from './components/user-management/user-management.component'; import { UserService } from './services/user.service'; import { LoggedUserComponent } from './components/users/logged-user/logged-user.component'; +import { AddUserDialogComponent } from './components/user-management/add-user-dialog/add-user-dialog.component'; +import { UserFilterPipe } from './filters/user-filter.pipe'; import { GroupManagementComponent } from './components/group-management/group-management.component'; import { GroupFilterPipe } from './filters/group-filter.pipe'; 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 { AddUserDialogComponent } from './components/user-management/add-user-dialog/add-user-dialog.component'; -import { UserFilterPipe } from './filters/user-filter.pipe'; +import { DeleteUserDialogComponent } from './components/user-management/delete-user-dialog/delete-user-dialog.component'; @NgModule({ declarations: [ @@ -467,12 +468,13 @@ import { UserFilterPipe } from './filters/user-filter.pipe'; EditNetworkConfigurationDialogComponent, UserManagementComponent, ProjectReadmeComponent, - DeleteGroupDialogComponent, AddGroupDialogComponent, GroupFilterPipe, GroupManagementComponent, AddUserDialogComponent, UserFilterPipe, + DeleteGroupDialogComponent, + DeleteUserDialogComponent ], imports: [ BrowserModule, 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 index acdb01e4..e5a80d35 100644 --- 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 @@ -1,3 +1,15 @@ +/* +* 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 {Server} from "@models/server"; diff --git a/src/app/components/user-management/delete-user-dialog/delete-user-dialog.component.html b/src/app/components/user-management/delete-user-dialog/delete-user-dialog.component.html new file mode 100644 index 00000000..dc1287c1 --- /dev/null +++ b/src/app/components/user-management/delete-user-dialog/delete-user-dialog.component.html @@ -0,0 +1,7 @@ +

Are you sure to delete user named: '{{data.user.username}}' ?

+
+ + +
diff --git a/src/app/components/user-management/delete-user-dialog/delete-user-dialog.component.scss b/src/app/components/user-management/delete-user-dialog/delete-user-dialog.component.scss new file mode 100644 index 00000000..4b5fa184 --- /dev/null +++ b/src/app/components/user-management/delete-user-dialog/delete-user-dialog.component.scss @@ -0,0 +1,3 @@ +.button-div { + float: right; +} diff --git a/src/app/components/user-management/delete-user-dialog/delete-user-dialog.component.spec.ts b/src/app/components/user-management/delete-user-dialog/delete-user-dialog.component.spec.ts new file mode 100644 index 00000000..98ea5694 --- /dev/null +++ b/src/app/components/user-management/delete-user-dialog/delete-user-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { DeleteUserDialogComponent } from './delete-user-dialog.component'; + +describe('DeleteUserDialogComponent', () => { + let component: DeleteUserDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ DeleteUserDialogComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(DeleteUserDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/user-management/delete-user-dialog/delete-user-dialog.component.ts b/src/app/components/user-management/delete-user-dialog/delete-user-dialog.component.ts new file mode 100644 index 00000000..9bdda601 --- /dev/null +++ b/src/app/components/user-management/delete-user-dialog/delete-user-dialog.component.ts @@ -0,0 +1,26 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {User} from "@models/users/user"; +import {UserService} from "@services/user.service"; + +@Component({ + selector: 'app-delete-user-dialog', + templateUrl: './delete-user-dialog.component.html', + styleUrls: ['./delete-user-dialog.component.scss'] +}) +export class DeleteUserDialogComponent implements OnInit { + + constructor(private dialogRef: MatDialogRef, + @Inject(MAT_DIALOG_DATA) public data: { user: User }) { } + + ngOnInit(): void { + } + + onCancel() { + this.dialogRef.close(); + } + + onDelete() { + this.dialogRef.close(true); + } +} diff --git a/src/app/components/user-management/user-management.component.html b/src/app/components/user-management/user-management.component.html index bf14d26c..20754a0c 100644 --- a/src/app/components/user-management/user-management.component.html +++ b/src/app/components/user-management/user-management.component.html @@ -35,11 +35,16 @@ Admin {{row.is_superadmin}} - + Last Update {{row.updated_at ? row.updated_at : row.created_at}} + + + + + diff --git a/src/app/components/user-management/user-management.component.ts b/src/app/components/user-management/user-management.component.ts index 936d16fc..ce3c2ae3 100644 --- a/src/app/components/user-management/user-management.component.ts +++ b/src/app/components/user-management/user-management.component.ts @@ -20,9 +20,10 @@ import {User} from "@models/users/user"; import {BehaviorSubject, merge, Observable} from "rxjs"; import {DataSource} from "@angular/cdk/collections"; import {map} from "rxjs/operators"; -import {AddBlankProjectDialogComponent} from "@components/projects/add-blank-project-dialog/add-blank-project-dialog.component"; import {AddUserDialogComponent} from "@components/user-management/add-user-dialog/add-user-dialog.component"; import {MatDialog} from "@angular/material/dialog"; +import {DeleteUserDialogComponent} from "@components/user-management/delete-user-dialog/delete-user-dialog.component"; +import {ToasterService} from "@services/toaster.service"; @Component({ selector: 'app-user-management', @@ -33,7 +34,7 @@ export class UserManagementComponent implements OnInit { server: Server; dataSource: UserDataSource; userDatabase = new UserDatabase(); - displayedColumns = ['name', 'email', 'is_active', 'is_superadmin', 'last']; + displayedColumns = ['name', 'email', 'is_active', 'is_superadmin', 'updated_at', 'delete']; searchText: string = ''; @@ -44,7 +45,8 @@ export class UserManagementComponent implements OnInit { private router: Router, private userService: UserService, private progressService: ProgressService, - public dialog: MatDialog) { } + public dialog: MatDialog, + private toasterService: ToasterService) { } ngOnInit() { this.server = this.route.snapshot.data['server']; @@ -80,6 +82,21 @@ export class UserManagementComponent implements OnInit { dialogRef.afterClosed().subscribe(() => this.refresh()); } + onDelete(user: User) { + this.dialog + .open(DeleteUserDialogComponent, {width: '500px', data: {user: user}}) + .afterClosed() + .subscribe((isDeletedConfirm) => { + if (isDeletedConfirm) { + this.userService.delete(this.server, user.user_id) + .subscribe(() => { + this.refresh() + }, (error) => { + this.toasterService.error(`An error occur while trying to delete user ${user.username}`); + }); + } + }); + } } export class UserDatabase { diff --git a/src/app/services/user.service.ts b/src/app/services/user.service.ts index e90cf9a1..e49d76fd 100644 --- a/src/app/services/user.service.ts +++ b/src/app/services/user.service.ts @@ -30,4 +30,7 @@ export class UserService { }); } + delete(server: Server, user_id: string) { + return this.httpServer.delete(server, `/users/${user_id}`); + } } From 0c0d77e22006077742e901ddfcfe74f5939c130a Mon Sep 17 00:00:00 2001 From: Lebeau Elise Date: Fri, 10 Dec 2021 11:28:12 +0000 Subject: [PATCH 08/50] user management, add multiple user selection --- .../delete-user-dialog.component.html | 5 ++- .../delete-user-dialog.component.scss | 4 ++ .../delete-user-dialog.component.ts | 14 ++++++- .../user-management.component.html | 18 +++++++++ .../user-management.component.scss | 4 ++ .../user-management.component.ts | 40 +++++++++++++++++-- src/app/models/users/user.ts | 1 + src/app/services/user.service.ts | 1 - 8 files changed, 80 insertions(+), 7 deletions(-) diff --git a/src/app/components/user-management/delete-user-dialog/delete-user-dialog.component.html b/src/app/components/user-management/delete-user-dialog/delete-user-dialog.component.html index dc1287c1..d15b5b40 100644 --- a/src/app/components/user-management/delete-user-dialog/delete-user-dialog.component.html +++ b/src/app/components/user-management/delete-user-dialog/delete-user-dialog.component.html @@ -1,4 +1,7 @@ -

Are you sure to delete user named: '{{data.user.username}}' ?

+

Are you sure you want to delete the following users ?

+
    +
  • {{ user.username }} {{ user.full_name ? '- ' + user.full_name : '' }}
  • +
@@ -17,6 +20,21 @@
+ + + + + + + + + + + Name diff --git a/src/app/components/user-management/user-management.component.scss b/src/app/components/user-management/user-management.component.scss index b264352a..03241487 100644 --- a/src/app/components/user-management/user-management.component.scss +++ b/src/app/components/user-management/user-management.component.scss @@ -9,3 +9,7 @@ margin-left: -470px; left: 50%; } + +.small-col { + flex-grow: 0.5; +} diff --git a/src/app/components/user-management/user-management.component.ts b/src/app/components/user-management/user-management.component.ts index ce3c2ae3..6c82fce0 100644 --- a/src/app/components/user-management/user-management.component.ts +++ b/src/app/components/user-management/user-management.component.ts @@ -18,7 +18,7 @@ import {UserService} from "@services/user.service"; import {ProgressService} from "../../common/progress/progress.service"; import {User} from "@models/users/user"; import {BehaviorSubject, merge, Observable} from "rxjs"; -import {DataSource} from "@angular/cdk/collections"; +import {DataSource, SelectionModel} from "@angular/cdk/collections"; import {map} from "rxjs/operators"; import {AddUserDialogComponent} from "@components/user-management/add-user-dialog/add-user-dialog.component"; import {MatDialog} from "@angular/material/dialog"; @@ -34,8 +34,8 @@ export class UserManagementComponent implements OnInit { server: Server; dataSource: UserDataSource; userDatabase = new UserDatabase(); - displayedColumns = ['name', 'email', 'is_active', 'is_superadmin', 'updated_at', 'delete']; - + displayedColumns = ['select', 'name', 'email', 'is_active', 'is_superadmin', 'updated_at', 'delete']; + selection = new SelectionModel(true, []); searchText: string = ''; @ViewChild(MatSort, { static: true }) sort: MatSort; @@ -84,7 +84,7 @@ export class UserManagementComponent implements OnInit { onDelete(user: User) { this.dialog - .open(DeleteUserDialogComponent, {width: '500px', data: {user: user}}) + .open(DeleteUserDialogComponent, {width: '500px', data: {users: [user]}}) .afterClosed() .subscribe((isDeletedConfirm) => { if (isDeletedConfirm) { @@ -97,6 +97,38 @@ export class UserManagementComponent implements OnInit { } }); } + + isAllSelected() { + const numSelected = this.selection.selected.length; + const numRows = this.userDatabase.data.length; + return numSelected === numRows; + } + + masterToggle() { + this.isAllSelected() ? + this.selection.clear() : + this.userDatabase.data.forEach(row => this.selection.select(row)); + } + + deleteMultiple() { + this.dialog + .open(DeleteUserDialogComponent, {width: '500px', data: {users: this.selection.selected}}) + .afterClosed() + .subscribe((isDeletedConfirm) => { + if (isDeletedConfirm) { + this.selection.selected.forEach((user: User) => { + this.userService.delete(this.server, user.user_id) + .subscribe(() => { + this.refresh() + }, (error) => { + this.toasterService.error(`An error occur while trying to delete user ${user.username}`); + }); + }) + this.selection.clear(); + } + }); + + } } export class UserDatabase { diff --git a/src/app/models/users/user.ts b/src/app/models/users/user.ts index 880734dd..a385cc85 100644 --- a/src/app/models/users/user.ts +++ b/src/app/models/users/user.ts @@ -2,6 +2,7 @@ export interface User { created_at: string; email: string; full_name: string; + last_login: string; is_active: boolean; is_superadmin: boolean; updated_at: string; diff --git a/src/app/services/user.service.ts b/src/app/services/user.service.ts index e49d76fd..8cd4c0b4 100644 --- a/src/app/services/user.service.ts +++ b/src/app/services/user.service.ts @@ -20,7 +20,6 @@ export class UserService { } add(server: Server, user: any): Observable { - console.log(user) return this.httpServer.post(server, `/users`, { username: user.username, is_active: user.is_active, From 11029db9561bb9b20bada4ad27b4960f27f27db6 Mon Sep 17 00:00:00 2001 From: Lebeau Elise Date: Fri, 10 Dec 2021 13:51:19 +0000 Subject: [PATCH 09/50] remove is_admin field in user management list and add field validation on create a new user --- .../add-user-dialog.component.html | 24 +++++++++++--- .../add-user-dialog.component.scss | 4 --- .../add-user-dialog.component.ts | 16 +++++++--- .../userEmailAsyncValidator.ts | 16 ++++++++++ .../add-user-dialog/userNameAsyncValidator.ts | 16 ++++++++++ .../user-management.component.html | 32 ++++++++++++------- .../user-management.component.scss | 15 +++++++++ .../user-management.component.ts | 2 +- src/app/filters/user-filter.pipe.ts | 1 + 9 files changed, 100 insertions(+), 26 deletions(-) create mode 100644 src/app/components/user-management/add-user-dialog/userEmailAsyncValidator.ts create mode 100644 src/app/components/user-management/add-user-dialog/userNameAsyncValidator.ts diff --git a/src/app/components/user-management/add-user-dialog/add-user-dialog.component.html b/src/app/components/user-management/add-user-dialog/add-user-dialog.component.html index c9ce0f5b..2f565edb 100644 --- a/src/app/components/user-management/add-user-dialog/add-user-dialog.component.html +++ b/src/app/components/user-management/add-user-dialog/add-user-dialog.component.html @@ -2,19 +2,33 @@
+ Username is required + Username is incorrect + User with this username exists - - - + Email is required + Email is invalid - Is active - Is admin + + + + A password between 6 and 100 characters is required. + + + + Is active
diff --git a/src/app/components/user-management/add-user-dialog/add-user-dialog.component.scss b/src/app/components/user-management/add-user-dialog/add-user-dialog.component.scss index 0907b1ef..469c3dda 100644 --- a/src/app/components/user-management/add-user-dialog/add-user-dialog.component.scss +++ b/src/app/components/user-management/add-user-dialog/add-user-dialog.component.scss @@ -2,10 +2,6 @@ width: 100%; } -.checkbox-field { - display: block; - margin-bottom: 5px; -} .button-div { float: right; diff --git a/src/app/components/user-management/add-user-dialog/add-user-dialog.component.ts b/src/app/components/user-management/add-user-dialog/add-user-dialog.component.ts index 771c84a1..2bc64b1b 100644 --- a/src/app/components/user-management/add-user-dialog/add-user-dialog.component.ts +++ b/src/app/components/user-management/add-user-dialog/add-user-dialog.component.ts @@ -17,6 +17,8 @@ import {UserService} from "@services/user.service"; import {Server} from "@models/server"; import {User} from "@models/users/user"; import {ToasterService} from "@services/toaster.service"; +import {userNameAsyncValidator} from "@components/user-management/add-user-dialog/userNameAsyncValidator"; +import {userEmailAsyncValidator} from "@components/user-management/add-user-dialog/userEmailAsyncValidator"; @Component({ selector: 'app-add-user-dialog', @@ -36,16 +38,22 @@ export class AddUserDialogComponent implements OnInit { username: new FormControl(null, [ Validators.required, Validators.minLength(3), - Validators.pattern("[a-zA-Z0-9_-]+$")]), + Validators.pattern("[a-zA-Z0-9_-]+$")], + [userNameAsyncValidator(this.server, this.userService)]), full_name: new FormControl(), - email: new FormControl(null, [Validators.email, Validators.required]), + email: new FormControl(null, + [Validators.email, Validators.required], + [userEmailAsyncValidator(this.server, this.userService)]), password: new FormControl(null, [Validators.required, Validators.minLength(6), Validators.maxLength(100)]), - is_active: new FormControl(true), - is_superadmin: new FormControl(false) + is_active: new FormControl(true) }); } + get form() { + return this.addUserForm.controls; + } + onCancelClick() { this.dialogRef.close(); } diff --git a/src/app/components/user-management/add-user-dialog/userEmailAsyncValidator.ts b/src/app/components/user-management/add-user-dialog/userEmailAsyncValidator.ts new file mode 100644 index 00000000..429cd32e --- /dev/null +++ b/src/app/components/user-management/add-user-dialog/userEmailAsyncValidator.ts @@ -0,0 +1,16 @@ +import {Server} from "../../../models/server"; +import {UserService} from "../../../services/user.service"; +import {FormControl} from "@angular/forms"; +import {timer} from "rxjs"; +import {map, switchMap} from "rxjs/operators"; + +export const userEmailAsyncValidator = (server: Server, userService: UserService) => { + return (control: FormControl) => { + return timer(500).pipe( + switchMap(() => userService.list(server)), + map((response) => { + return (response.find((n) => n.email === control.value) ? { emailExists: true } : null); + }) + ); + }; +}; diff --git a/src/app/components/user-management/add-user-dialog/userNameAsyncValidator.ts b/src/app/components/user-management/add-user-dialog/userNameAsyncValidator.ts new file mode 100644 index 00000000..d88eb38c --- /dev/null +++ b/src/app/components/user-management/add-user-dialog/userNameAsyncValidator.ts @@ -0,0 +1,16 @@ +import {Server} from "../../../models/server"; +import {FormControl} from "@angular/forms"; +import {timer} from "rxjs"; +import {map, switchMap} from "rxjs/operators"; +import {UserService} from "../../../services/user.service"; + +export const userNameAsyncValidator = (server: Server, userService: UserService) => { + return (control: FormControl) => { + return timer(500).pipe( + switchMap(() => userService.list(server)), + map((response) => { + return (response.find((n) => n.username === control.value) ? { userExists: true } : null); + }) + ); + }; +}; diff --git a/src/app/components/user-management/user-management.component.html b/src/app/components/user-management/user-management.component.html index dc489135..17ca0c9f 100644 --- a/src/app/components/user-management/user-management.component.html +++ b/src/app/components/user-management/user-management.component.html @@ -35,32 +35,40 @@ - - Name + + Username - {{ row.username }} + {{ row.username }} + + + + Full Name + +
{{ row.full_name }}
Mail - {{ row.email }} + +
{{ row.email }}
+
- Active - {{row.is_active}} + Active + {{row.is_active}} - - Admin - {{row.is_superadmin}} + + Last Login + {{row.last_login}} Last Update - {{row.updated_at ? row.updated_at : row.created_at}} + {{row.updated_at ? row.updated_at : row.created_at}} - - + + diff --git a/src/app/components/user-management/user-management.component.scss b/src/app/components/user-management/user-management.component.scss index 03241487..9ed45f4a 100644 --- a/src/app/components/user-management/user-management.component.scss +++ b/src/app/components/user-management/user-management.component.scss @@ -11,5 +11,20 @@ } .small-col { + flex-grow: 0.3; +} + +.active-col { flex-grow: 0.5; } + +.overflow-col { + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + padding-right: 5px; +} + +.custom-tooltip { + font-size:100px; +} diff --git a/src/app/components/user-management/user-management.component.ts b/src/app/components/user-management/user-management.component.ts index 6c82fce0..2f010b40 100644 --- a/src/app/components/user-management/user-management.component.ts +++ b/src/app/components/user-management/user-management.component.ts @@ -34,7 +34,7 @@ export class UserManagementComponent implements OnInit { server: Server; dataSource: UserDataSource; userDatabase = new UserDatabase(); - displayedColumns = ['select', 'name', 'email', 'is_active', 'is_superadmin', 'updated_at', 'delete']; + displayedColumns = ['select', 'username', 'full_name', 'email', 'is_active', 'last_login', 'updated_at', 'delete']; selection = new SelectionModel(true, []); searchText: string = ''; diff --git a/src/app/filters/user-filter.pipe.ts b/src/app/filters/user-filter.pipe.ts index 0086bdb3..f8b8e86e 100644 --- a/src/app/filters/user-filter.pipe.ts +++ b/src/app/filters/user-filter.pipe.ts @@ -24,6 +24,7 @@ export class UserFilterPipe implements PipeTransform { searchText = searchText.toLowerCase(); return items.userDatabase.data.filter((item) => { return (item.username && item.username.toLowerCase().includes(searchText)) + || (item.full_name && item.full_name.toLowerCase().includes(searchText)) || (item.email && item.email.toLowerCase().includes(searchText)); }); } From 596a11210f9b9778ac94ee92ed1c8a9c7da6ea60 Mon Sep 17 00:00:00 2001 From: Lebeau Elise Date: Mon, 13 Dec 2021 13:53:52 +0000 Subject: [PATCH 10/50] user management, add paginator --- .../userEmailAsyncValidator.ts | 12 +++ .../add-user-dialog/userNameAsyncValidator.ts | 12 +++ .../user-management.component.html | 9 +- .../user-management.component.ts | 86 ++++++------------- src/app/filters/user-filter.pipe.ts | 7 +- 5 files changed, 60 insertions(+), 66 deletions(-) diff --git a/src/app/components/user-management/add-user-dialog/userEmailAsyncValidator.ts b/src/app/components/user-management/add-user-dialog/userEmailAsyncValidator.ts index 429cd32e..41f4aedd 100644 --- a/src/app/components/user-management/add-user-dialog/userEmailAsyncValidator.ts +++ b/src/app/components/user-management/add-user-dialog/userEmailAsyncValidator.ts @@ -1,3 +1,15 @@ +/* +* 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 {Server} from "../../../models/server"; import {UserService} from "../../../services/user.service"; import {FormControl} from "@angular/forms"; diff --git a/src/app/components/user-management/add-user-dialog/userNameAsyncValidator.ts b/src/app/components/user-management/add-user-dialog/userNameAsyncValidator.ts index d88eb38c..52503e8a 100644 --- a/src/app/components/user-management/add-user-dialog/userNameAsyncValidator.ts +++ b/src/app/components/user-management/add-user-dialog/userNameAsyncValidator.ts @@ -1,3 +1,15 @@ +/* +* 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 {Server} from "../../../models/server"; import {FormControl} from "@angular/forms"; import {timer} from "rxjs"; diff --git a/src/app/components/user-management/user-management.component.html b/src/app/components/user-management/user-management.component.html index 17ca0c9f..a155cdf0 100644 --- a/src/app/components/user-management/user-management.component.html +++ b/src/app/components/user-management/user-management.component.html @@ -13,7 +13,7 @@ - + @@ -44,7 +44,7 @@ Full Name -
{{ row.full_name }}
+
{{ row.full_name }}
@@ -74,6 +74,11 @@ + + +
diff --git a/src/app/components/user-management/user-management.component.ts b/src/app/components/user-management/user-management.component.ts index 2f010b40..83798abb 100644 --- a/src/app/components/user-management/user-management.component.ts +++ b/src/app/components/user-management/user-management.component.ts @@ -13,7 +13,7 @@ import {Component, OnInit, ViewChild} from '@angular/core'; import {ActivatedRoute, Router} from "@angular/router"; import {Server} from "@models/server"; -import {MatSort, MatSortable} from "@angular/material/sort"; +import {MatSort, MatSortable, Sort} from "@angular/material/sort"; import {UserService} from "@services/user.service"; import {ProgressService} from "../../common/progress/progress.service"; import {User} from "@models/users/user"; @@ -24,6 +24,8 @@ import {AddUserDialogComponent} from "@components/user-management/add-user-dialo import {MatDialog} from "@angular/material/dialog"; import {DeleteUserDialogComponent} from "@components/user-management/delete-user-dialog/delete-user-dialog.component"; import {ToasterService} from "@services/toaster.service"; +import {MatPaginator} from "@angular/material/paginator"; +import {MatTableDataSource} from "@angular/material/table"; @Component({ selector: 'app-user-management', @@ -32,12 +34,12 @@ import {ToasterService} from "@services/toaster.service"; }) export class UserManagementComponent implements OnInit { server: Server; - dataSource: UserDataSource; - userDatabase = new UserDatabase(); + dataSource = new MatTableDataSource(); displayedColumns = ['select', 'username', 'full_name', 'email', 'is_active', 'last_login', 'updated_at', 'delete']; selection = new SelectionModel(true, []); searchText: string = ''; + @ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatSort, { static: true }) sort: MatSort; constructor( @@ -53,17 +55,27 @@ export class UserManagementComponent implements OnInit { if (!this.server) this.router.navigate(['/servers']); this.refresh(); - this.sort.sort({ - id: 'name', - start: 'asc', - }); - this.dataSource = new UserDataSource(this.userDatabase, this.sort); + } + + ngAfterViewInit() { + this.dataSource.paginator = this.paginator; + this.dataSource.sort = this.sort; + this.dataSource.sortingDataAccessor = (item, property) => { + switch (property) { + case 'username': + case 'full_name': + case 'email': + return item[property] ? item[property].toLowerCase() : ''; + default: + return item[property]; + } + } } refresh() { this.userService.list(this.server).subscribe( (users: User[]) => { - this.userDatabase.addUsers(users); + this.dataSource.data = users; }, (error) => { this.progressService.setError(error); @@ -98,16 +110,18 @@ export class UserManagementComponent implements OnInit { }); } + + isAllSelected() { const numSelected = this.selection.selected.length; - const numRows = this.userDatabase.data.length; + const numRows = this.dataSource.data.length; return numSelected === numRows; } masterToggle() { this.isAllSelected() ? this.selection.clear() : - this.userDatabase.data.forEach(row => this.selection.select(row)); + this.dataSource.data.forEach(row => this.selection.select(row)); } deleteMultiple() { @@ -130,53 +144,3 @@ export class UserManagementComponent implements OnInit { } } - -export class UserDatabase { - dataChange: BehaviorSubject = new BehaviorSubject([]); - - get data(): User[] { - return this.dataChange.value; - } - - public addUsers(users: User[]) { - this.dataChange.next(users); - } - - public remove(user: User) { - const index = this.data.indexOf(user); - if (index >= 0) { - this.data.splice(index, 1); - this.dataChange.next(this.data.slice()); - } - } -} - -export class UserDataSource extends DataSource { - constructor(public userDatabase: UserDatabase, private sort: MatSort) { - super(); - } - - connect(): Observable { - const displayDataChanges = [this.userDatabase.dataChange, this.sort.sortChange]; - - return merge(...displayDataChanges).pipe( - map(() => { - if (!this.sort.active || this.sort.direction === '') { - return this.userDatabase.data; - } - - return this.userDatabase.data.sort((a, b) => { - const propertyA = a[this.sort.active]; - const propertyB = b[this.sort.active]; - - const valueA = isNaN(+propertyA) ? propertyA : +propertyA; - const valueB = isNaN(+propertyB) ? propertyB : +propertyB; - - return (valueA < valueB ? -1 : 1) * (this.sort.direction === 'asc' ? 1 : -1); - }); - }) - ); - } - - disconnect() {} -} diff --git a/src/app/filters/user-filter.pipe.ts b/src/app/filters/user-filter.pipe.ts index f8b8e86e..fec797e9 100644 --- a/src/app/filters/user-filter.pipe.ts +++ b/src/app/filters/user-filter.pipe.ts @@ -11,18 +11,19 @@ * Author: Sylvain MATHIEU, Elise LEBEAU */ import { Pipe, PipeTransform } from '@angular/core'; -import {UserDataSource} from "@components/user-management/user-management.component"; +import {User} from "@models/users/user"; +import {MatTableDataSource} from "@angular/material/table"; @Pipe({ name: 'userFilter' }) export class UserFilterPipe implements PipeTransform { - transform(items: UserDataSource, searchText: string) { + transform(items: MatTableDataSource, searchText: string) { if (!items) return []; if (!searchText) return items; searchText = searchText.toLowerCase(); - return items.userDatabase.data.filter((item) => { + return items.data.filter((item: User) => { return (item.username && item.username.toLowerCase().includes(searchText)) || (item.full_name && item.full_name.toLowerCase().includes(searchText)) || (item.email && item.email.toLowerCase().includes(searchText)); From 2e2a59f6f2cb2b12ab741eea4a3c12e9dc4b297f Mon Sep 17 00:00:00 2001 From: Sylvain MATHIEU Date: Mon, 13 Dec 2021 15:32:51 +0100 Subject: [PATCH 11/50] group management, add sort on all columns --- .../group-management/group-management.component.html | 6 +++--- .../group-management/group-management.component.ts | 9 ++++++++- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/app/components/group-management/group-management.component.html b/src/app/components/group-management/group-management.component.html index 9d512124..08aff060 100644 --- a/src/app/components/group-management/group-management.component.html +++ b/src/app/components/group-management/group-management.component.html @@ -23,19 +23,19 @@ -
+ - + - + diff --git a/src/app/components/group-management/group-management.component.ts b/src/app/components/group-management/group-management.component.ts index e1f9fb7a..a716ce6d 100644 --- a/src/app/components/group-management/group-management.component.ts +++ b/src/app/components/group-management/group-management.component.ts @@ -66,13 +66,20 @@ export class GroupManagementComponent implements OnInit { const isAsc = sort.direction === 'asc'; switch (sort.active) { case 'name': - return compare(a.name, b.name, isAsc); + return compare(a.name.toLowerCase(), b.name.toLowerCase(), isAsc); + case 'created_at': + return compare(a.created_at, b.created_at, isAsc); + case 'updated_at': + return compare(a.updated_at, b.updated_at, isAsc); + case 'is_builtin': + return compare(a.is_builtin.toString(), b.is_builtin.toString(), isAsc); default: return 0; } }); function compare(a: number | string, b: number | string, isAsc: boolean) { + return (a < b ? -1 : 1) * (isAsc ? 1 : -1); } } From eb5437d005d2901024937fdc2ff88dd3564e65bc Mon Sep 17 00:00:00 2001 From: Sylvain MATHIEU Date: Wed, 15 Dec 2021 09:02:38 +0100 Subject: [PATCH 12/50] group management, add multiple delete --- .../delete-group-dialog.component.html | 3 +- .../delete-group-dialog.component.ts | 4 +- .../group-management.component.html | 20 +++++++- .../group-management.component.ts | 48 +++++++++++++------ 4 files changed, 57 insertions(+), 18 deletions(-) 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 index a0f3c00f..4ab22367 100644 --- 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 @@ -1,4 +1,5 @@ -

Are you sure to delete group named: '{{data.groupName}}' ?

+

Are you sure to delete group named:

+

{{group.name}}

@@ -17,6 +20,21 @@
Name delete
Creation date Creation date {{element.created_at}} last update last update {{element.updated_at}} is build in is build in {{element.is_builtin}}
+ + + + + + + + + + + @@ -41,7 +59,7 @@ - + diff --git a/src/app/components/group-management/group-management.component.ts b/src/app/components/group-management/group-management.component.ts index a716ce6d..52609152 100644 --- a/src/app/components/group-management/group-management.component.ts +++ b/src/app/components/group-management/group-management.component.ts @@ -21,6 +21,10 @@ import {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 {DeleteUserDialogComponent} from "@components/user-management/delete-user-dialog/delete-user-dialog.component"; +import {User} from "@models/users/user"; +import {forkJoin} from "rxjs"; @Component({ selector: 'app-group-management', @@ -30,7 +34,8 @@ import {DeleteGroupDialogComponent} from "@components/group-management/delete-gr export class GroupManagementComponent implements OnInit { server: Server; - public displayedColumns = ['name', 'created_at', 'updated_at', 'is_builtin', 'delete']; + public displayedColumns = ['select', 'name', 'created_at', 'updated_at', 'is_builtin', 'delete']; + selection = new SelectionModel(true, []); groups: Group[]; sortedGroups: Group[]; searchText: string; @@ -84,6 +89,17 @@ export class GroupManagementComponent implements OnInit { } } + 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 @@ -93,10 +109,7 @@ export class GroupManagementComponent implements OnInit { if (name) { this.groupService.addGroup(this.server, name) .subscribe(() => { - this.groupService.getGroups(this.server).subscribe((groups: Group[]) => { - this.groups = groups; - this.sortedGroups = groups; - }); + this.refresh(); }, (error) => { this.toasterService.error(`An error occur while trying to create new group ${name}`); }); @@ -104,21 +117,28 @@ export class GroupManagementComponent implements OnInit { }); } - onDelete(group: Group) { + refresh() { + this.groupService.getGroups(this.server).subscribe((groups: Group[]) => { + this.groups = groups; + this.sortedGroups = groups; + this.selection.clear(); + }); + } + + onDelete(groupsToDelete: Group[]) { this.dialog - .open(DeleteGroupDialogComponent, {width: '500px', height: '250px', data: {groupName: group.name}}) + .open(DeleteGroupDialogComponent, {width: '500px', height: '250px', data: {groups: groupsToDelete}}) .afterClosed() .subscribe((isDeletedConfirm) => { if (isDeletedConfirm) { - this.groupService.delete(this.server, group.user_group_id) + const observables = groupsToDelete.map((group: Group) => this.groupService.delete(this.server, group.user_group_id)); + forkJoin(observables) .subscribe(() => { - this.groupService.getGroups(this.server).subscribe((groups: Group[]) => { - this.groups = groups; - this.sortedGroups = groups; + this.refresh(); + }, + (error) => { + this.toasterService.error(`An error occur while trying to delete group`); }); - }, (error) => { - this.toasterService.error(`An error occur while trying to delete group ${group.name}`); - }); } }); } From cbeca9d0ca95d6d2e5c245694790780dc6e55933 Mon Sep 17 00:00:00 2001 From: Lebeau Elise Date: Tue, 14 Dec 2021 14:22:35 +0000 Subject: [PATCH 13/50] user management, create edit user detail dialog --- src/app/app.module.ts | 2 +- .../add-user-dialog.component.html | 2 +- .../add-user-dialog.component.ts | 1 - .../userEmailAsyncValidator.ts | 4 +- .../add-user-dialog/userNameAsyncValidator.ts | 4 +- .../edit-user-dialog.component.html | 42 ++++++++++ .../edit-user-dialog.component.scss | 8 ++ .../edit-user-dialog.component.spec.ts | 25 ++++++ .../edit-user-dialog.component.ts | 81 +++++++++++++++++++ .../user-management.component.html | 2 +- .../user-management.component.ts | 10 +++ src/app/services/user.service.ts | 12 ++- 12 files changed, 178 insertions(+), 15 deletions(-) create mode 100644 src/app/components/user-management/edit-user-dialog/edit-user-dialog.component.html create mode 100644 src/app/components/user-management/edit-user-dialog/edit-user-dialog.component.scss create mode 100644 src/app/components/user-management/edit-user-dialog/edit-user-dialog.component.spec.ts create mode 100644 src/app/components/user-management/edit-user-dialog/edit-user-dialog.component.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index d5b43685..11aff1fc 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -474,7 +474,7 @@ import { DeleteUserDialogComponent } from './components/user-management/delete-u AddUserDialogComponent, UserFilterPipe, DeleteGroupDialogComponent, - DeleteUserDialogComponent + DeleteUserDialogComponent, ], imports: [ BrowserModule, diff --git a/src/app/components/user-management/add-user-dialog/add-user-dialog.component.html b/src/app/components/user-management/add-user-dialog/add-user-dialog.component.html index 2f565edb..c94ac91c 100644 --- a/src/app/components/user-management/add-user-dialog/add-user-dialog.component.html +++ b/src/app/components/user-management/add-user-dialog/add-user-dialog.component.html @@ -22,7 +22,7 @@ - + A password between 6 and 100 characters is required. diff --git a/src/app/components/user-management/add-user-dialog/add-user-dialog.component.ts b/src/app/components/user-management/add-user-dialog/add-user-dialog.component.ts index 2bc64b1b..1a94a1a9 100644 --- a/src/app/components/user-management/add-user-dialog/add-user-dialog.component.ts +++ b/src/app/components/user-management/add-user-dialog/add-user-dialog.component.ts @@ -65,7 +65,6 @@ export class AddUserDialogComponent implements OnInit { const newUser = this.addUserForm.value; this.userService.add(this.server, newUser) .subscribe((user: User) => { - console.log("Done ", user) this.toasterService.success(`User ${user.username} added`); this.dialogRef.close(); }, diff --git a/src/app/components/user-management/add-user-dialog/userEmailAsyncValidator.ts b/src/app/components/user-management/add-user-dialog/userEmailAsyncValidator.ts index 41f4aedd..302febae 100644 --- a/src/app/components/user-management/add-user-dialog/userEmailAsyncValidator.ts +++ b/src/app/components/user-management/add-user-dialog/userEmailAsyncValidator.ts @@ -16,12 +16,12 @@ import {FormControl} from "@angular/forms"; import {timer} from "rxjs"; import {map, switchMap} from "rxjs/operators"; -export const userEmailAsyncValidator = (server: Server, userService: UserService) => { +export const userEmailAsyncValidator = (server: Server, userService: UserService, except: string = '') => { return (control: FormControl) => { return timer(500).pipe( switchMap(() => userService.list(server)), map((response) => { - return (response.find((n) => n.email === control.value) ? { emailExists: true } : null); + return (response.find((n) => n.email === control.value && control.value !== except) ? { emailExists: true } : null); }) ); }; diff --git a/src/app/components/user-management/add-user-dialog/userNameAsyncValidator.ts b/src/app/components/user-management/add-user-dialog/userNameAsyncValidator.ts index 52503e8a..fb2dd5bb 100644 --- a/src/app/components/user-management/add-user-dialog/userNameAsyncValidator.ts +++ b/src/app/components/user-management/add-user-dialog/userNameAsyncValidator.ts @@ -16,12 +16,12 @@ import {timer} from "rxjs"; import {map, switchMap} from "rxjs/operators"; import {UserService} from "../../../services/user.service"; -export const userNameAsyncValidator = (server: Server, userService: UserService) => { +export const userNameAsyncValidator = (server: Server, userService: UserService, except: string = '') => { return (control: FormControl) => { return timer(500).pipe( switchMap(() => userService.list(server)), map((response) => { - return (response.find((n) => n.username === control.value) ? { userExists: true } : null); + return (response.find((n) => n.username === control.value && control.value !== except) ? { userExists: true } : null); }) ); }; diff --git a/src/app/components/user-management/edit-user-dialog/edit-user-dialog.component.html b/src/app/components/user-management/edit-user-dialog/edit-user-dialog.component.html new file mode 100644 index 00000000..8b1e6e1c --- /dev/null +++ b/src/app/components/user-management/edit-user-dialog/edit-user-dialog.component.html @@ -0,0 +1,42 @@ +

Update user

+
+ + + Username is required + Username is incorrect + User with this username exists + + + + + + + Email is required + Email is invalid + User with this email exists + + + + + + Password must be between 6 and 100 characters + + + + Is active + +
+ + +
+ diff --git a/src/app/components/user-management/edit-user-dialog/edit-user-dialog.component.scss b/src/app/components/user-management/edit-user-dialog/edit-user-dialog.component.scss new file mode 100644 index 00000000..469c3dda --- /dev/null +++ b/src/app/components/user-management/edit-user-dialog/edit-user-dialog.component.scss @@ -0,0 +1,8 @@ +.input-field { + width: 100%; +} + + +.button-div { + float: right; +} diff --git a/src/app/components/user-management/edit-user-dialog/edit-user-dialog.component.spec.ts b/src/app/components/user-management/edit-user-dialog/edit-user-dialog.component.spec.ts new file mode 100644 index 00000000..7c6e36da --- /dev/null +++ b/src/app/components/user-management/edit-user-dialog/edit-user-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { EditUserDialogComponent } from './edit-user-dialog.component'; + +describe('EditUserDialogComponent', () => { + let component: EditUserDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ EditUserDialogComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(EditUserDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/user-management/edit-user-dialog/edit-user-dialog.component.ts b/src/app/components/user-management/edit-user-dialog/edit-user-dialog.component.ts new file mode 100644 index 00000000..0a5129d1 --- /dev/null +++ b/src/app/components/user-management/edit-user-dialog/edit-user-dialog.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, Inject, OnInit} from '@angular/core'; +import {FormControl, FormGroup, Validators} from "@angular/forms"; +import {Server} from "@models/server"; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {UserService} from "@services/user.service"; +import {ToasterService} from "@services/toaster.service"; +import {userNameAsyncValidator} from "@components/user-management/add-user-dialog/userNameAsyncValidator"; +import {userEmailAsyncValidator} from "@components/user-management/add-user-dialog/userEmailAsyncValidator"; +import {User} from "@models/users/user"; + +@Component({ + selector: 'app-edit-user-dialog', + templateUrl: './edit-user-dialog.component.html', + styleUrls: ['./edit-user-dialog.component.scss'] +}) +export class EditUserDialogComponent implements OnInit { + + editUserForm: FormGroup; + + constructor(public dialogRef: MatDialogRef, + public userService: UserService, + private toasterService: ToasterService, + @Inject(MAT_DIALOG_DATA) public data: { user: User, server: Server }) {} + + ngOnInit(): void { + this.editUserForm = new FormGroup({ + username: new FormControl(this.data.user.username, [ + Validators.required, + Validators.minLength(3), + Validators.pattern("[a-zA-Z0-9_-]+$")], + [userNameAsyncValidator(this.data.server, this.userService, this.data.user.username)]), + full_name: new FormControl(this.data.user.full_name), + email: new FormControl(this.data.user.email, + [Validators.email, Validators.required], + [userEmailAsyncValidator(this.data.server, this.userService, this.data.user.email)]), + password: new FormControl(null, + [Validators.minLength(6), Validators.maxLength(100)]), + is_active: new FormControl(this.data.user.is_active) + }); + } + + get form() { + return this.editUserForm.controls; + } + + onCancelClick() { + this.dialogRef.close(); + } + + onEditClick() { + if (!this.editUserForm.valid) { + return; + } + const updatedUser = this.editUserForm.value; + + updatedUser.user_id = this.data.user.user_id; + console.log(updatedUser) + this.userService.update(this.data.server, updatedUser) + .subscribe((user: User) => { + console.log("Done ", user) + this.toasterService.success(`User ${user.username} updated`); + this.dialogRef.close(); + }, + (error) => { + this.toasterService.error('Cannot update user : ' + error); + }) + } + +} diff --git a/src/app/components/user-management/user-management.component.html b/src/app/components/user-management/user-management.component.html index a155cdf0..72cc8dbb 100644 --- a/src/app/components/user-management/user-management.component.html +++ b/src/app/components/user-management/user-management.component.html @@ -38,7 +38,7 @@ Username - {{ row.username }} + {{ row.username }} diff --git a/src/app/components/user-management/user-management.component.ts b/src/app/components/user-management/user-management.component.ts index 83798abb..98631aaa 100644 --- a/src/app/components/user-management/user-management.component.ts +++ b/src/app/components/user-management/user-management.component.ts @@ -26,6 +26,7 @@ import {DeleteUserDialogComponent} from "@components/user-management/delete-user import {ToasterService} from "@services/toaster.service"; import {MatPaginator} from "@angular/material/paginator"; import {MatTableDataSource} from "@angular/material/table"; +import {EditUserDialogComponent} from "@components/user-management/edit-user-dialog/edit-user-dialog.component"; @Component({ selector: 'app-user-management', @@ -143,4 +144,13 @@ export class UserManagementComponent implements OnInit { }); } + + onEdit(user: User) { + this.dialog + .open(EditUserDialogComponent, {width: '500px', data: {user: user, server: this.server}}) + .afterClosed() + .subscribe(() => { + this.refresh(); + }); + } } diff --git a/src/app/services/user.service.ts b/src/app/services/user.service.ts index 8cd4c0b4..72ca9ab3 100644 --- a/src/app/services/user.service.ts +++ b/src/app/services/user.service.ts @@ -20,16 +20,14 @@ export class UserService { } add(server: Server, user: any): Observable { - return this.httpServer.post(server, `/users`, { - username: user.username, - is_active: user.is_active, - email: user.email, - full_name: user.full_name, - password: user.password - }); + return this.httpServer.post(server, `/users`, user); } delete(server: Server, user_id: string) { return this.httpServer.delete(server, `/users/${user_id}`); } + + update(server: Server, user: User): Observable { + return this.httpServer.put(server, `/users/${user.user_id}`, user); + } } From c3f3fafbef94000014b6731e8db548672706a711 Mon Sep 17 00:00:00 2001 From: Lebeau Elise Date: Thu, 16 Dec 2021 14:10:28 +0000 Subject: [PATCH 14/50] user management detail refactor --- src/app/app-routing.module.ts | 7 ++ src/app/app.module.ts | 2 + .../edit-user-dialog.component.html | 42 ------- .../edit-user-dialog.component.scss | 8 -- .../user-detail/user-detail.component.html | 74 +++++++++++++ .../user-detail/user-detail.component.scss | 16 +++ .../user-detail.component.spec.ts} | 12 +- .../user-detail/user-detail.component.ts | 103 ++++++++++++++++++ .../user-management.component.html | 3 +- .../user-management.component.ts | 16 +-- src/app/services/user.service.ts | 11 +- 11 files changed, 222 insertions(+), 72 deletions(-) delete mode 100644 src/app/components/user-management/edit-user-dialog/edit-user-dialog.component.html delete mode 100644 src/app/components/user-management/edit-user-dialog/edit-user-dialog.component.scss create mode 100644 src/app/components/user-management/user-detail/user-detail.component.html create mode 100644 src/app/components/user-management/user-detail/user-detail.component.scss rename src/app/components/user-management/{edit-user-dialog/edit-user-dialog.component.spec.ts => user-detail/user-detail.component.spec.ts} (52%) create mode 100644 src/app/components/user-management/user-detail/user-detail.component.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index c83e3179..b3d367d1 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -57,6 +57,7 @@ import { ServerResolve } from './resolvers/server-resolve'; import { UserManagementComponent } from './components/user-management/user-management.component'; import { LoggedUserComponent } from './components/users/logged-user/logged-user.component'; import {GroupManagementComponent} from "./components/group-management/group-management.component"; +import {UserDetailComponent} from "@components/user-management/user-detail/user-detail.component"; const routes: Routes = [ { @@ -83,6 +84,12 @@ const routes: Routes = [ canActivate: [LoginGuard], resolve: { server: ServerResolve }, }, + { + path: 'server/:server_id/user_management/:user_id', + component: UserDetailComponent, + canActivate: [LoginGuard], + resolve: { server: ServerResolve }, + }, { path: 'installed-software', component: InstalledSoftwareComponent }, { path: 'server/:server_id/systemstatus', component: SystemStatusComponent, canActivate: [LoginGuard] }, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 11aff1fc..d64b149b 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -280,6 +280,7 @@ import { GroupFilterPipe } from './filters/group-filter.pipe'; 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 { DeleteUserDialogComponent } from './components/user-management/delete-user-dialog/delete-user-dialog.component'; +import { UserDetailComponent } from './components/user-management/user-detail/user-detail.component'; @NgModule({ declarations: [ @@ -475,6 +476,7 @@ import { DeleteUserDialogComponent } from './components/user-management/delete-u UserFilterPipe, DeleteGroupDialogComponent, DeleteUserDialogComponent, + UserDetailComponent ], imports: [ BrowserModule, diff --git a/src/app/components/user-management/edit-user-dialog/edit-user-dialog.component.html b/src/app/components/user-management/edit-user-dialog/edit-user-dialog.component.html deleted file mode 100644 index 8b1e6e1c..00000000 --- a/src/app/components/user-management/edit-user-dialog/edit-user-dialog.component.html +++ /dev/null @@ -1,42 +0,0 @@ -

Update user

-
- - - Username is required - Username is incorrect - User with this username exists - - - - - - - Email is required - Email is invalid - User with this email exists - - - - - - Password must be between 6 and 100 characters - - - - Is active - -
- - -
- diff --git a/src/app/components/user-management/edit-user-dialog/edit-user-dialog.component.scss b/src/app/components/user-management/edit-user-dialog/edit-user-dialog.component.scss deleted file mode 100644 index 469c3dda..00000000 --- a/src/app/components/user-management/edit-user-dialog/edit-user-dialog.component.scss +++ /dev/null @@ -1,8 +0,0 @@ -.input-field { - width: 100%; -} - - -.button-div { - float: right; -} diff --git a/src/app/components/user-management/user-detail/user-detail.component.html b/src/app/components/user-management/user-detail/user-detail.component.html new file mode 100644 index 00000000..5d24dc14 --- /dev/null +++ b/src/app/components/user-management/user-detail/user-detail.component.html @@ -0,0 +1,74 @@ +
+
+
+ +

User Details

+
+
+
+
+

Update user

+
+
+ + + Username is required + Username is incorrect + User with this username exists + + + + + + + Email is required + Email is invalid + User with this email exists + + + + + + Password must be between 6 and 100 characters + + + + Is active + +
+ +
+ +
+ +
+ +
+
Groups
+ +
+
+
+ + diff --git a/src/app/components/user-management/user-detail/user-detail.component.scss b/src/app/components/user-management/user-detail/user-detail.component.scss new file mode 100644 index 00000000..a1d07ca9 --- /dev/null +++ b/src/app/components/user-management/user-detail/user-detail.component.scss @@ -0,0 +1,16 @@ +.input-field { + width: 100%; +} + +.button-div { + float: right; +} + +.user-edit , +.user-groups { + flex: 1; +} + +.default-content { + display: flex; +} diff --git a/src/app/components/user-management/edit-user-dialog/edit-user-dialog.component.spec.ts b/src/app/components/user-management/user-detail/user-detail.component.spec.ts similarity index 52% rename from src/app/components/user-management/edit-user-dialog/edit-user-dialog.component.spec.ts rename to src/app/components/user-management/user-detail/user-detail.component.spec.ts index 7c6e36da..66c3622e 100644 --- a/src/app/components/user-management/edit-user-dialog/edit-user-dialog.component.spec.ts +++ b/src/app/components/user-management/user-detail/user-detail.component.spec.ts @@ -1,20 +1,20 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { EditUserDialogComponent } from './edit-user-dialog.component'; +import { UserDetailComponent } from './user-detail.component'; -describe('EditUserDialogComponent', () => { - let component: EditUserDialogComponent; - let fixture: ComponentFixture; +describe('UserDetailComponent', () => { + let component: UserDetailComponent; + let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ EditUserDialogComponent ] + declarations: [ UserDetailComponent ] }) .compileComponents(); }); beforeEach(() => { - fixture = TestBed.createComponent(EditUserDialogComponent); + fixture = TestBed.createComponent(UserDetailComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/src/app/components/user-management/user-detail/user-detail.component.ts b/src/app/components/user-management/user-detail/user-detail.component.ts new file mode 100644 index 00000000..9e206946 --- /dev/null +++ b/src/app/components/user-management/user-detail/user-detail.component.ts @@ -0,0 +1,103 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {FormControl, FormGroup, Validators} from "@angular/forms"; +import {Group} from "@models/groups/group"; +import {UserService} from "@services/user.service"; +import {ToasterService} from "@services/toaster.service"; +import {User} from "@models/users/user"; +import {Server} from "@models/server"; +import {userNameAsyncValidator} from "@components/user-management/add-user-dialog/userNameAsyncValidator"; +import {userEmailAsyncValidator} from "@components/user-management/add-user-dialog/userEmailAsyncValidator"; +import {ActivatedRoute, Router} from "@angular/router"; + +@Component({ + selector: 'app-user-detail', + templateUrl: './user-detail.component.html', + styleUrls: ['./user-detail.component.scss'] +}) +export class UserDetailComponent implements OnInit { + + editUserForm: FormGroup; + groups: Group[]; + user: User; + server: Server; + user_id: string; + + constructor(public userService: UserService, + private toasterService: ToasterService, + private route: ActivatedRoute, + private router: Router) { + + } + + ngOnInit(): void { + this.server = this.route.snapshot.data['server']; + if (!this.server) this.router.navigate(['/servers']); + + this.user_id = this.route.snapshot.paramMap.get('user_id'); + this.userService.get(this.server, this.user_id).subscribe((user: User) => { + console.log(user) + this.user = user; + this.userService.getGroupsByUserId(this.server, this.user.user_id).subscribe( + (groups: Group[]) => { + this.groups = groups; + }); + this.initForm(); + }) + } + + initForm() { + this.editUserForm = new FormGroup({ + username: new FormControl(this.user.username, [ + Validators.required, + Validators.minLength(3), + Validators.pattern("[a-zA-Z0-9_-]+$")], + [userNameAsyncValidator(this.server, this.userService, this.user.username)]), + full_name: new FormControl(this.user.full_name), + email: new FormControl(this.user.email, + [Validators.email, Validators.required], + [userEmailAsyncValidator(this.server, this.userService, this.user.email)]), + password: new FormControl(null, + [Validators.minLength(6), Validators.maxLength(100)]), + is_active: new FormControl(this.user.is_active) + }); + } + + get form() { + return this.editUserForm.controls; + } + + onEditClick() { + if (!this.editUserForm.valid) { + return; + } + + const updatedUser = this.getUpdatedValues(); + updatedUser['user_id'] = this.user.user_id; + + this.userService.update(this.server, updatedUser) + .subscribe((user: User) => { + console.log("Done ", user) + this.toasterService.success(`User ${user.username} updated`); + }, + (error) => { + this.toasterService.error('Cannot update user : ' + error); + }) + } + + getUpdatedValues() { + let dirtyValues = {}; + + Object.keys(this.editUserForm.controls) + .forEach(key => { + const currentControl = this.editUserForm.get(key); + + if (currentControl.dirty && currentControl.value !== this.user[key] && currentControl.value !== '') { + dirtyValues[key] = currentControl.value; + } + }); + + return dirtyValues; + } + + +} diff --git a/src/app/components/user-management/user-management.component.html b/src/app/components/user-management/user-management.component.html index 72cc8dbb..6ab59098 100644 --- a/src/app/components/user-management/user-management.component.html +++ b/src/app/components/user-management/user-management.component.html @@ -38,7 +38,8 @@ Username - {{ row.username }} + {{ row.username }} diff --git a/src/app/components/user-management/user-management.component.ts b/src/app/components/user-management/user-management.component.ts index 98631aaa..cdfa47ad 100644 --- a/src/app/components/user-management/user-management.component.ts +++ b/src/app/components/user-management/user-management.component.ts @@ -13,20 +13,17 @@ import {Component, OnInit, ViewChild} from '@angular/core'; import {ActivatedRoute, Router} from "@angular/router"; import {Server} from "@models/server"; -import {MatSort, MatSortable, Sort} from "@angular/material/sort"; +import {MatSort} from "@angular/material/sort"; import {UserService} from "@services/user.service"; import {ProgressService} from "../../common/progress/progress.service"; import {User} from "@models/users/user"; -import {BehaviorSubject, merge, Observable} from "rxjs"; -import {DataSource, SelectionModel} from "@angular/cdk/collections"; -import {map} from "rxjs/operators"; +import {SelectionModel} from "@angular/cdk/collections"; import {AddUserDialogComponent} from "@components/user-management/add-user-dialog/add-user-dialog.component"; import {MatDialog} from "@angular/material/dialog"; import {DeleteUserDialogComponent} from "@components/user-management/delete-user-dialog/delete-user-dialog.component"; import {ToasterService} from "@services/toaster.service"; import {MatPaginator} from "@angular/material/paginator"; import {MatTableDataSource} from "@angular/material/table"; -import {EditUserDialogComponent} from "@components/user-management/edit-user-dialog/edit-user-dialog.component"; @Component({ selector: 'app-user-management', @@ -144,13 +141,4 @@ export class UserManagementComponent implements OnInit { }); } - - onEdit(user: User) { - this.dialog - .open(EditUserDialogComponent, {width: '500px', data: {user: user, server: this.server}}) - .afterClosed() - .subscribe(() => { - this.refresh(); - }); - } } diff --git a/src/app/services/user.service.ts b/src/app/services/user.service.ts index 72ca9ab3..e0540d59 100644 --- a/src/app/services/user.service.ts +++ b/src/app/services/user.service.ts @@ -4,6 +4,7 @@ import { Server } from '../models/server'; import { HttpServer } from './http-server.service'; import { User } from '../models/users/user'; import {Project} from "@models/project"; +import {Group} from "@models/groups/group"; @Injectable() export class UserService { @@ -15,6 +16,10 @@ export class UserService { return this.httpServer.get(server, '/users/me/'); } + get(server:Server, user_id: string) { + return this.httpServer.get(server, `/users/${user_id}`); + } + list(server: Server) { return this.httpServer.get(server, '/users'); } @@ -27,7 +32,11 @@ export class UserService { return this.httpServer.delete(server, `/users/${user_id}`); } - update(server: Server, user: User): Observable { + update(server: Server, user: any): Observable { return this.httpServer.put(server, `/users/${user.user_id}`, user); } + + getGroupsByUserId(server: Server, user_id: string) { + return this.httpServer.get(server, `/users/${user_id}/groups`); + } } From 6122801f901de6a11be8ab0c2683c104b5275238 Mon Sep 17 00:00:00 2001 From: Sylvain MATHIEU Date: Tue, 28 Dec 2021 09:27:42 +0100 Subject: [PATCH 15/50] group management, add group details --- src/app/app-routing.module.ts | 4 + src/app/app.module.ts | 8 +- .../add-user-to-group-dialog.component.html | 16 +++ .../add-user-to-group-dialog.component.scss | 31 ++++++ ...add-user-to-group-dialog.component.spec.ts | 25 +++++ .../add-user-to-group-dialog.component.ts | 79 +++++++++++++++ .../group-details.component.html | 40 ++++++++ .../group-details.component.scss | 46 +++++++++ .../group-details.component.spec.ts | 25 +++++ .../group-details/group-details.component.ts | 98 +++++++++++++++++++ ...remove-user-to-group-dialog.component.html | 9 ++ ...remove-user-to-group-dialog.component.scss | 20 ++++ ...ove-user-to-group-dialog.component.spec.ts | 25 +++++ .../remove-user-to-group-dialog.component.ts | 25 +++++ .../group-management.component.html | 8 +- .../group-management.component.ts | 59 +++++------ src/app/filters/group-filter.pipe.ts | 14 ++- .../resolvers/group-details.resolver.spec.ts | 16 +++ src/app/resolvers/group-details.resolver.ts | 40 ++++++++ src/app/services/group.service.ts | 16 +++ 20 files changed, 557 insertions(+), 47 deletions(-) create mode 100644 src/app/components/group-details/add-user-to-group-dialog/add-user-to-group-dialog.component.html create mode 100644 src/app/components/group-details/add-user-to-group-dialog/add-user-to-group-dialog.component.scss create mode 100644 src/app/components/group-details/add-user-to-group-dialog/add-user-to-group-dialog.component.spec.ts create mode 100644 src/app/components/group-details/add-user-to-group-dialog/add-user-to-group-dialog.component.ts create mode 100644 src/app/components/group-details/group-details.component.html create mode 100644 src/app/components/group-details/group-details.component.scss create mode 100644 src/app/components/group-details/group-details.component.spec.ts create mode 100644 src/app/components/group-details/group-details.component.ts create mode 100644 src/app/components/group-details/remove-user-to-group-dialog/remove-user-to-group-dialog.component.html create mode 100644 src/app/components/group-details/remove-user-to-group-dialog/remove-user-to-group-dialog.component.scss create mode 100644 src/app/components/group-details/remove-user-to-group-dialog/remove-user-to-group-dialog.component.spec.ts create mode 100644 src/app/components/group-details/remove-user-to-group-dialog/remove-user-to-group-dialog.component.ts create mode 100644 src/app/resolvers/group-details.resolver.spec.ts create mode 100644 src/app/resolvers/group-details.resolver.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index b3d367d1..79fa95cf 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -57,7 +57,9 @@ import { ServerResolve } from './resolvers/server-resolve'; import { UserManagementComponent } from './components/user-management/user-management.component'; import { LoggedUserComponent } from './components/users/logged-user/logged-user.component'; import {GroupManagementComponent} from "./components/group-management/group-management.component"; +import {GroupDetailsComponent} from "@components/group-details/group-details.component"; import {UserDetailComponent} from "@components/user-management/user-detail/user-detail.component"; +import {GroupDetailsResolver} from "@resolvers/group-details.resolver"; const routes: Routes = [ { @@ -205,6 +207,8 @@ const routes: Routes = [ { path: 'server/:server_id/preferences/iou/templates/:template_id/copy', component: CopyIouTemplateComponent, canActivate: [LoginGuard] }, { path: 'server/:server_id/preferences/iou/addtemplate', component: AddIouTemplateComponent, canActivate: [LoginGuard] }, { path: 'server/:server_id/group_management', component: GroupManagementComponent}, + { path: 'server/:server_id/group_management/:user_group_id', component: GroupDetailsComponent, resolve: {group: GroupDetailsResolver}}, + ], }, { diff --git a/src/app/app.module.ts b/src/app/app.module.ts index d64b149b..8280698c 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -280,7 +280,10 @@ import { GroupFilterPipe } from './filters/group-filter.pipe'; 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 { DeleteUserDialogComponent } from './components/user-management/delete-user-dialog/delete-user-dialog.component'; +import { GroupDetailsComponent } from './components/group-details/group-details.component'; import { UserDetailComponent } from './components/user-management/user-detail/user-detail.component'; +import { AddUserToGroupDialogComponent } from './components/group-details/add-user-to-group-dialog/add-user-to-group-dialog.component'; +import { RemoveUserToGroupDialogComponent } from './components/group-details/remove-user-to-group-dialog/remove-user-to-group-dialog.component'; @NgModule({ declarations: [ @@ -476,7 +479,10 @@ import { UserDetailComponent } from './components/user-management/user-detail/us UserFilterPipe, DeleteGroupDialogComponent, DeleteUserDialogComponent, - UserDetailComponent + GroupDetailsComponent, + UserDetailComponent, + AddUserToGroupDialogComponent, + RemoveUserToGroupDialogComponent ], imports: [ BrowserModule, 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..51781781 --- /dev/null +++ b/src/app/components/group-details/add-user-to-group-dialog/add-user-to-group-dialog.component.scss @@ -0,0 +1,31 @@ +: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; +} 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..ead6fde4 --- /dev/null +++ b/src/app/components/group-details/add-user-to-group-dialog/add-user-to-group-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { AddUserToGroupDialogComponent } from './add-user-to-group-dialog.component'; + +describe('AddUserToGroupDialogComponent', () => { + let component: AddUserToGroupDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ AddUserToGroupDialogComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(AddUserToGroupDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); 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..2a83777a --- /dev/null +++ b/src/app/components/group-details/add-user-to-group-dialog/add-user-to-group-dialog.component.ts @@ -0,0 +1,79 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogRef} from "@angular/material/dialog"; +import {UserService} from "@services/user.service"; +import {Server} from "@models/server"; +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: { server: Server; 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.server), + this.groupService.getGroupMember(this.data.server, 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.server, 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..fa22aec5 --- /dev/null +++ b/src/app/components/group-details/group-details.component.html @@ -0,0 +1,40 @@ +
+
+
+

Groups {{group.name}} details

+
+
+
+
+ + Group name: + + +
+
Creation date: {{group.created_at}}
+
Last update Date: {{group.updated_at}}
+
UUID: {{group.user_group_id}}
+
+ Is build in +
+
+ +
+
+
+
+
Members:
+ person_add +
+
+
{{user.username}}
+ delete +
+
+ +
+
+
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..54ea87ef --- /dev/null +++ b/src/app/components/group-details/group-details.component.scss @@ -0,0 +1,46 @@ +.main { + display: flex; + justify-content: space-between; +} + +.details { + width: 30vw; + display: flex; + flex-direction: column; + justify-content: center; +} + +.members { + width: 40vw; + 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; +} + +.members .title { + font-size: 2em; + text-decoration: underline; +} + +.details > div { + margin-bottom: 20px; +} + +.input-field { + width: 100%; +} + +.button-div { + float: right; +} 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..f536c6c2 --- /dev/null +++ b/src/app/components/group-details/group-details.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { GroupDetailsComponent } from './group-details.component'; + +describe('GroupDetailsComponent', () => { + let component: GroupDetailsComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ GroupDetailsComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(GroupDetailsComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); 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..4115c571 --- /dev/null +++ b/src/app/components/group-details/group-details.component.ts @@ -0,0 +1,98 @@ +import {Component, Inject, OnInit} from '@angular/core'; +import {ActivatedRoute, Router} from "@angular/router"; +import {Server} from "@models/server"; +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 {RemoveUserToGroupDialogComponent} from "@components/group-details/remove-user-to-group-dialog/remove-user-to-group-dialog.component"; +import {GroupService} from "@services/group.service"; +import {ToasterService} from "@services/toaster.service"; + +@Component({ + selector: 'app-group-details', + templateUrl: './group-details.component.html', + styleUrls: ['./group-details.component.scss'] +}) +export class GroupDetailsComponent implements OnInit { + server: Server; + group: Group; + members: User[]; + editGroupForm: FormGroup; + + 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: { group: { server: Server; group: Group, users: User[] } }) => { + + this.server = d.group.server; + this.group = d.group.group; + this.members = d.group.users; + this.editGroupForm.setValue({groupname: this.group.name}); + }); + + + } + + ngOnInit(): void { + + } + + onUpdate() { + this.groupService.update(this.server, this.group) + .subscribe(() => { + this.toastService.success(`group updated`); + }, (error) => { + this.toastService.error('Error: Cannot update group'); + console.log(error); + }); + } + + openAddUserDialog() { + this.dialog + .open(AddUserToGroupDialogComponent, + { + width: '700px', height: '500px', + data: {server: this.server, group: this.group} + }) + .afterClosed() + .subscribe(() => { + this.reloadMembers(); + }); + } + + openRemoveUserDialog(user: User) { + this.dialog + .open(RemoveUserToGroupDialogComponent, + {width: '500px', height: '200px', data: {user}}) + .afterClosed() + .subscribe((userToRemove: User) => { + console.log(userToRemove); + if (userToRemove) { + this.groupService.removeUser(this.server, this.group, userToRemove) + .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); + }); + } + }); + } + + reloadMembers() { + this.groupService.getGroupMember(this.server, this.group.user_group_id) + .subscribe((members: User[]) => { + this.members = members; + }); + } +} diff --git a/src/app/components/group-details/remove-user-to-group-dialog/remove-user-to-group-dialog.component.html b/src/app/components/group-details/remove-user-to-group-dialog/remove-user-to-group-dialog.component.html new file mode 100644 index 00000000..a87d964d --- /dev/null +++ b/src/app/components/group-details/remove-user-to-group-dialog/remove-user-to-group-dialog.component.html @@ -0,0 +1,9 @@ +
+
Confirm ?
+
Removing user: {{data.user.username}}
+
+
+ + +
+ diff --git a/src/app/components/group-details/remove-user-to-group-dialog/remove-user-to-group-dialog.component.scss b/src/app/components/group-details/remove-user-to-group-dialog/remove-user-to-group-dialog.component.scss new file mode 100644 index 00000000..8ebc2b8a --- /dev/null +++ b/src/app/components/group-details/remove-user-to-group-dialog/remove-user-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-user-to-group-dialog/remove-user-to-group-dialog.component.spec.ts b/src/app/components/group-details/remove-user-to-group-dialog/remove-user-to-group-dialog.component.spec.ts new file mode 100644 index 00000000..61d76e2f --- /dev/null +++ b/src/app/components/group-details/remove-user-to-group-dialog/remove-user-to-group-dialog.component.spec.ts @@ -0,0 +1,25 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { RemoveUserToGroupDialogComponent } from './remove-user-to-group-dialog.component'; + +describe('RemoveUserToGroupDialogComponent', () => { + let component: RemoveUserToGroupDialogComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [ RemoveUserToGroupDialogComponent ] + }) + .compileComponents(); + }); + + beforeEach(() => { + fixture = TestBed.createComponent(RemoveUserToGroupDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); 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..089389d0 --- /dev/null +++ b/src/app/components/group-details/remove-user-to-group-dialog/remove-user-to-group-dialog.component.ts @@ -0,0 +1,25 @@ +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/group-management.component.html b/src/app/components/group-management/group-management.component.html index f17dcea8..4b4be62e 100644 --- a/src/app/components/group-management/group-management.component.html +++ b/src/app/components/group-management/group-management.component.html @@ -18,7 +18,7 @@
-
Name {{element.name}}
+
@@ -37,7 +37,7 @@ - + @@ -67,5 +67,9 @@
Name {{element.name}} {{element.name}}
+ +
diff --git a/src/app/components/group-management/group-management.component.ts b/src/app/components/group-management/group-management.component.ts index 52609152..20a4d107 100644 --- a/src/app/components/group-management/group-management.component.ts +++ b/src/app/components/group-management/group-management.component.ts @@ -10,21 +10,21 @@ * * Author: Sylvain MATHIEU, Elise LEBEAU */ -import {Component, OnInit} from '@angular/core'; -import {ActivatedRoute} from "@angular/router"; +import {Component, OnInit, ViewChild} from '@angular/core'; +import {ActivatedRoute, Router} from "@angular/router"; import {ServerService} from "../../services/server.service"; import {ToasterService} from "../../services/toaster.service"; import {GroupService} from "../../services/group.service"; import {Server} from "../../models/server"; import {Group} from "../../models/groups/group"; -import {Sort} from "@angular/material/sort"; +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 {DeleteUserDialogComponent} from "@components/user-management/delete-user-dialog/delete-user-dialog.component"; -import {User} from "@models/users/user"; import {forkJoin} from "rxjs"; +import {MatPaginator} from "@angular/material/paginator"; +import {MatTableDataSource} from "@angular/material/table"; @Component({ selector: 'app-group-management', @@ -34,10 +34,13 @@ import {forkJoin} from "rxjs"; export class GroupManagementComponent implements OnInit { server: Server; + @ViewChild(MatPaginator) paginator: MatPaginator; + @ViewChild(MatSort, { static: true }) sort: MatSort; + public displayedColumns = ['select', 'name', 'created_at', 'updated_at', 'is_builtin', 'delete']; selection = new SelectionModel(true, []); groups: Group[]; - sortedGroups: Group[]; + dataSource = new MatTableDataSource(); searchText: string; constructor( @@ -49,44 +52,28 @@ export class GroupManagementComponent implements OnInit { ) { } + ngOnInit(): void { const serverId = this.route.snapshot.paramMap.get('server_id'); this.serverService.get(+serverId).then((server: Server) => { this.server = server; - this.groupService.getGroups(server).subscribe((groups: Group[]) => { - this.groups = groups; - this.sortedGroups = groups; - }); + this.refresh(); }); } - sort(sort: Sort) { - const data = this.groups.slice(); - if (!sort.active || sort.direction === '') { - this.sortedGroups = data; - return; - } - - this.sortedGroups = data.sort((a, b) => { - const isAsc = sort.direction === 'asc'; - switch (sort.active) { - case 'name': - return compare(a.name.toLowerCase(), b.name.toLowerCase(), isAsc); - case 'created_at': - return compare(a.created_at, b.created_at, isAsc); - case 'updated_at': - return compare(a.updated_at, b.updated_at, isAsc); - case 'is_builtin': - return compare(a.is_builtin.toString(), b.is_builtin.toString(), isAsc); + ngAfterViewInit() { + this.dataSource.paginator = this.paginator; + this.dataSource.sort = this.sort; + this.dataSource.sortingDataAccessor = (item, property) => { + switch (property) { + case 'username': + case 'full_name': + case 'email': + return item[property] ? item[property].toLowerCase() : ''; default: - return 0; + return item[property]; } - }); - - function compare(a: number | string, b: number | string, isAsc: boolean) { - - return (a < b ? -1 : 1) * (isAsc ? 1 : -1); - } + }; } isAllSelected() { @@ -120,7 +107,7 @@ export class GroupManagementComponent implements OnInit { refresh() { this.groupService.getGroups(this.server).subscribe((groups: Group[]) => { this.groups = groups; - this.sortedGroups = groups; + this.dataSource.data = groups; this.selection.clear(); }); } diff --git a/src/app/filters/group-filter.pipe.ts b/src/app/filters/group-filter.pipe.ts index 25653143..8bda5a94 100644 --- a/src/app/filters/group-filter.pipe.ts +++ b/src/app/filters/group-filter.pipe.ts @@ -12,24 +12,22 @@ */ import {Pipe, PipeTransform} from '@angular/core'; import {Group} from "../models/groups/group"; +import {MatTableDataSource} from "@angular/material/table"; @Pipe({ name: 'groupFilter' }) export class GroupFilterPipe implements PipeTransform { - transform(groups: Group[], searchText: string): Group[] { - if (!groups) { - return []; - } + transform(groups: MatTableDataSource, searchText: string): MatTableDataSource { + if (!searchText) { return groups; } - searchText = searchText.toLowerCase(); - return groups.filter((group: Group) => { - return group.name.toLowerCase().includes(searchText); - }); + searchText = searchText.trim().toLowerCase(); + groups.filter = searchText; + return groups; } } diff --git a/src/app/resolvers/group-details.resolver.spec.ts b/src/app/resolvers/group-details.resolver.spec.ts new file mode 100644 index 00000000..f57fe67f --- /dev/null +++ b/src/app/resolvers/group-details.resolver.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { GroupDetailsResolver } from './group-details.resolver'; + +describe('GroupDetailsResolver', () => { + let resolver: GroupDetailsResolver; + + beforeEach(() => { + TestBed.configureTestingModule({}); + resolver = TestBed.inject(GroupDetailsResolver); + }); + + it('should be created', () => { + expect(resolver).toBeTruthy(); + }); +}); diff --git a/src/app/resolvers/group-details.resolver.ts b/src/app/resolvers/group-details.resolver.ts new file mode 100644 index 00000000..ae1c5fc7 --- /dev/null +++ b/src/app/resolvers/group-details.resolver.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@angular/core'; +import { + Router, Resolve, + RouterStateSnapshot, + ActivatedRouteSnapshot +} from '@angular/router'; +import {Observable, of, Subscriber} from 'rxjs'; +import {ServerService} from "../services/server.service"; +import {GroupService} from "../services/group.service"; +import {Server} from "../models/server"; +import {Group} from "../models/groups/group"; +import {User} from "../models/users/user"; + +@Injectable({ + providedIn: 'root' +}) +export class GroupDetailsResolver implements Resolve<{server: Server; group: Group; users: User[]}> { + + constructor(private serverService: ServerService, + private groupService: GroupService) { + } + + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<{server: Server; group: Group; users: User[]}> { + + return new Observable<{server: Server; group: Group; users: User[]}>((subscriber: Subscriber) => { + + const serverId = route.paramMap.get('server_id'); + const groupId = route.paramMap.get('user_group_id'); + + this.serverService.get(+serverId).then((server: Server) => { + this.groupService.get(server, groupId).subscribe((group: Group) => { + this.groupService.getGroupMember(server, groupId).subscribe((users: User[]) => { + subscriber.next({server, group, users}); + subscriber.complete(); + }); + }); + }); + }); + } +} diff --git a/src/app/services/group.service.ts b/src/app/services/group.service.ts index 62f14eb4..d7f5914e 100644 --- a/src/app/services/group.service.ts +++ b/src/app/services/group.service.ts @@ -42,4 +42,20 @@ export class GroupService { delete(server: Server, user_group_id: string) { return this.httpServer.delete(server, `/groups/${user_group_id}`); } + + get(server: Server, user_group_id: string) { + return this.httpServer.get(server, `/groups/${user_group_id}`); + } + + addMemberToGroup(server: Server, group: Group, user: User) { + return this.httpServer.put(server, `/groups/${group.user_group_id}/members/${user.user_id}`, {}); + } + + removeUser(server: Server, group: Group, user: User) { + return this.httpServer.delete(server, `/groups/${group.user_group_id}/members/${user.user_id}`); + } + + update(server: Server, group: Group) { + return this.httpServer.put(server, `/groups/${group.user_group_id}`, {name: group.name}); + } } From 4911b0da66db5a41d31ba61fc47012ee44452e0b Mon Sep 17 00:00:00 2001 From: Lebeau Elise Date: Fri, 7 Jan 2022 09:42:20 +0000 Subject: [PATCH 16/50] create permission service --- .../add-user-to-group-dialog.component.ts | 12 ++++++++ .../group-details/group-details.component.ts | 12 ++++++++ .../remove-user-to-group-dialog.component.ts | 12 ++++++++ src/app/models/permission.ts | 23 +++++++++++++++ src/app/resolvers/group-details.resolver.ts | 12 ++++++++ src/app/services/permissions.service.spec.ts | 16 ++++++++++ src/app/services/permissions.service.ts | 29 +++++++++++++++++++ 7 files changed, 116 insertions(+) create mode 100644 src/app/models/permission.ts create mode 100644 src/app/services/permissions.service.spec.ts create mode 100644 src/app/services/permissions.service.ts 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 index 2a83777a..3f1483cb 100644 --- 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 @@ -1,3 +1,15 @@ +/* +* 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"; diff --git a/src/app/components/group-details/group-details.component.ts b/src/app/components/group-details/group-details.component.ts index 4115c571..d773634a 100644 --- a/src/app/components/group-details/group-details.component.ts +++ b/src/app/components/group-details/group-details.component.ts @@ -1,3 +1,15 @@ +/* +* 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 {ActivatedRoute, Router} from "@angular/router"; import {Server} from "@models/server"; 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 index 089389d0..ef70a89f 100644 --- 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 @@ -1,3 +1,15 @@ +/* +* 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"; diff --git a/src/app/models/permission.ts b/src/app/models/permission.ts new file mode 100644 index 00000000..4462cd33 --- /dev/null +++ b/src/app/models/permission.ts @@ -0,0 +1,23 @@ +export enum Methods { + GET = 'GET', + HEAD = 'HEAD', + POST = 'POST', + PATCH = 'PATCH', + PUT = 'PUT', + DELETE = 'DELETE' +} + +export enum PermissionActions { + ALLOW = 'ALLOW', + DENY = 'DENY' +} + +export interface Permission { + methods: Methods[], + path: string, + action: PermissionActions, + description: string, + created_at: string, + updated_at: string, + permission_id: string +} diff --git a/src/app/resolvers/group-details.resolver.ts b/src/app/resolvers/group-details.resolver.ts index ae1c5fc7..89049766 100644 --- a/src/app/resolvers/group-details.resolver.ts +++ b/src/app/resolvers/group-details.resolver.ts @@ -1,3 +1,15 @@ +/* +* 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'; import { Router, Resolve, diff --git a/src/app/services/permissions.service.spec.ts b/src/app/services/permissions.service.spec.ts new file mode 100644 index 00000000..f5972c66 --- /dev/null +++ b/src/app/services/permissions.service.spec.ts @@ -0,0 +1,16 @@ +import { TestBed } from '@angular/core/testing'; + +import { PermissionsService } from './permissions.service'; + +describe('PermissionsService', () => { + let service: PermissionsService; + + beforeEach(() => { + TestBed.configureTestingModule({}); + service = TestBed.inject(PermissionsService); + }); + + it('should be created', () => { + expect(service).toBeTruthy(); + }); +}); diff --git a/src/app/services/permissions.service.ts b/src/app/services/permissions.service.ts new file mode 100644 index 00000000..0de4e409 --- /dev/null +++ b/src/app/services/permissions.service.ts @@ -0,0 +1,29 @@ +import { Injectable } from '@angular/core'; +import {HttpServer} from "./http-server.service"; +import {Server} from "../models/server"; +import {Permission} from "../models/permission"; +import {Observable} from "rxjs/Rx"; + +@Injectable({ + providedIn: 'root' +}) +export class PermissionsService { + + constructor(private httpServer: HttpServer) { } + + list(server: Server) { + return this.httpServer.get(server, '/permissions'); + } + + add(server: Server, permission: Permission): Observable { + return this.httpServer.post(server, '/permissions', permission); + } + + update(server: Server, permission: Permission): Observable { + return this.httpServer.put(server, `/permissions/${permission.permission_id}`, permission); + } + + delete(server: Server, permission_id: string) { + return this.httpServer.delete(server, `/permissions/${permission_id}`); + } +} From a4e8dee2e140f3ff96886c57274d25441b367f07 Mon Sep 17 00:00:00 2001 From: Sylvain MATHIEU Date: Tue, 4 Jan 2022 14:20:41 +0100 Subject: [PATCH 17/50] group detail, page refactoring --- .../group-details/group-details.component.html | 1 + .../group-details/group-details.component.scss | 4 ++-- src/app/models/permission.ts | 12 ++++++++++++ src/app/services/permissions.service.ts | 12 ++++++++++++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/app/components/group-details/group-details.component.html b/src/app/components/group-details/group-details.component.html index fa22aec5..5368644d 100644 --- a/src/app/components/group-details/group-details.component.html +++ b/src/app/components/group-details/group-details.component.html @@ -24,6 +24,7 @@ +
Members:
diff --git a/src/app/components/group-details/group-details.component.scss b/src/app/components/group-details/group-details.component.scss index 54ea87ef..ac8538fc 100644 --- a/src/app/components/group-details/group-details.component.scss +++ b/src/app/components/group-details/group-details.component.scss @@ -1,6 +1,6 @@ .main { display: flex; - justify-content: space-between; + justify-content: space-around; } .details { @@ -11,7 +11,7 @@ } .members { - width: 40vw; + width: 30vw; display: flex; flex-direction: column; justify-content: stretch; diff --git a/src/app/models/permission.ts b/src/app/models/permission.ts index 4462cd33..0e93a35a 100644 --- a/src/app/models/permission.ts +++ b/src/app/models/permission.ts @@ -1,3 +1,15 @@ +/* +* 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 enum Methods { GET = 'GET', HEAD = 'HEAD', diff --git a/src/app/services/permissions.service.ts b/src/app/services/permissions.service.ts index 0de4e409..dfde68cf 100644 --- a/src/app/services/permissions.service.ts +++ b/src/app/services/permissions.service.ts @@ -1,3 +1,15 @@ +/* +* 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'; import {HttpServer} from "./http-server.service"; import {Server} from "../models/server"; From a7f6743860907547c2b587229ca431812eb11ddd Mon Sep 17 00:00:00 2001 From: Sylvain MATHIEU Date: Tue, 4 Jan 2022 15:16:07 +0100 Subject: [PATCH 18/50] group details, add members pagination --- src/app/app.module.ts | 4 ++- .../group-details.component.html | 3 +- .../group-details/group-details.component.ts | 2 ++ .../group-details/paginator.pipe.spec.ts | 8 +++++ .../group-details/paginator.pipe.ts | 29 +++++++++++++++++++ 5 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 src/app/components/group-details/paginator.pipe.spec.ts create mode 100644 src/app/components/group-details/paginator.pipe.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 8280698c..7aff44bb 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -284,6 +284,7 @@ import { GroupDetailsComponent } from './components/group-details/group-details. import { UserDetailComponent } from './components/user-management/user-detail/user-detail.component'; import { AddUserToGroupDialogComponent } from './components/group-details/add-user-to-group-dialog/add-user-to-group-dialog.component'; import { RemoveUserToGroupDialogComponent } from './components/group-details/remove-user-to-group-dialog/remove-user-to-group-dialog.component'; +import { PaginatorPipe } from './components/group-details/paginator.pipe'; @NgModule({ declarations: [ @@ -482,7 +483,8 @@ import { RemoveUserToGroupDialogComponent } from './components/group-details/rem GroupDetailsComponent, UserDetailComponent, AddUserToGroupDialogComponent, - RemoveUserToGroupDialogComponent + RemoveUserToGroupDialogComponent, + PaginatorPipe ], imports: [ BrowserModule, diff --git a/src/app/components/group-details/group-details.component.html b/src/app/components/group-details/group-details.component.html index 5368644d..5b6c370c 100644 --- a/src/app/components/group-details/group-details.component.html +++ b/src/app/components/group-details/group-details.component.html @@ -30,10 +30,11 @@
Members:
person_add
-
+ +
diff --git a/src/app/components/group-details/group-details.component.ts b/src/app/components/group-details/group-details.component.ts index d773634a..7107e920 100644 --- a/src/app/components/group-details/group-details.component.ts +++ b/src/app/components/group-details/group-details.component.ts @@ -21,6 +21,7 @@ import {AddUserToGroupDialogComponent} from "@components/group-details/add-user- import {RemoveUserToGroupDialogComponent} from "@components/group-details/remove-user-to-group-dialog/remove-user-to-group-dialog.component"; import {GroupService} from "@services/group.service"; import {ToasterService} from "@services/toaster.service"; +import {PageEvent} from "@angular/material/paginator"; @Component({ selector: 'app-group-details', @@ -32,6 +33,7 @@ export class GroupDetailsComponent implements OnInit { group: Group; members: User[]; editGroupForm: FormGroup; + pageEvent: PageEvent | undefined; constructor(private route: ActivatedRoute, private dialog: MatDialog, 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..233d4a9b --- /dev/null +++ b/src/app/components/group-details/paginator.pipe.spec.ts @@ -0,0 +1,8 @@ +import { PaginatorPipe } from './paginator.pipe'; + +describe('PaginatorPipe', () => { + it('create an instance', () => { + const pipe = new PaginatorPipe(); + expect(pipe).toBeTruthy(); + }); +}); 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..59a3ca45 --- /dev/null +++ b/src/app/components/group-details/paginator.pipe.ts @@ -0,0 +1,29 @@ +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(members: User[] | undefined, paginatorEvent: PageEvent | undefined): User[] { + if (!members) { + return []; + } + + if (!paginatorEvent) { + paginatorEvent = { + length: members.length, + pageIndex: 0, + pageSize: 5 + }; + } + + + return members.slice( + paginatorEvent.pageIndex * paginatorEvent.pageSize, + (paginatorEvent.pageIndex + 1) * paginatorEvent.pageSize); + } + +} From fb7845bbcef1e01e3f47e6d0c9e8917b08e9e6d5 Mon Sep 17 00:00:00 2001 From: Sylvain MATHIEU Date: Tue, 4 Jan 2022 15:33:20 +0100 Subject: [PATCH 19/50] group details, add filter on members list --- src/app/app.module.ts | 4 +++- .../group-details.component.html | 18 ++++++++++++----- .../group-details.component.scss | 14 +++++++++---- .../group-details/group-details.component.ts | 1 + .../group-details/members-filter.pipe.spec.ts | 8 ++++++++ .../group-details/members-filter.pipe.ts | 20 +++++++++++++++++++ .../group-details/paginator.pipe.ts | 12 +++++++++++ 7 files changed, 67 insertions(+), 10 deletions(-) create mode 100644 src/app/components/group-details/members-filter.pipe.spec.ts create mode 100644 src/app/components/group-details/members-filter.pipe.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 7aff44bb..2517da92 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -285,6 +285,7 @@ import { UserDetailComponent } from './components/user-management/user-detail/us import { AddUserToGroupDialogComponent } from './components/group-details/add-user-to-group-dialog/add-user-to-group-dialog.component'; import { RemoveUserToGroupDialogComponent } from './components/group-details/remove-user-to-group-dialog/remove-user-to-group-dialog.component'; import { PaginatorPipe } from './components/group-details/paginator.pipe'; +import { MembersFilterPipe } from './components/group-details/members-filter.pipe'; @NgModule({ declarations: [ @@ -484,7 +485,8 @@ import { PaginatorPipe } from './components/group-details/paginator.pipe'; UserDetailComponent, AddUserToGroupDialogComponent, RemoveUserToGroupDialogComponent, - PaginatorPipe + PaginatorPipe, + MembersFilterPipe ], imports: [ BrowserModule, diff --git a/src/app/components/group-details/group-details.component.html b/src/app/components/group-details/group-details.component.html index 5b6c370c..dc1709b1 100644 --- a/src/app/components/group-details/group-details.component.html +++ b/src/app/components/group-details/group-details.component.html @@ -6,7 +6,7 @@
- + Group name: @@ -27,14 +27,22 @@
-
Members:
+
Members:
person_add
-
-
{{user.username}}
+ + - +
diff --git a/src/app/components/group-details/group-details.component.scss b/src/app/components/group-details/group-details.component.scss index ac8538fc..c5fe588a 100644 --- a/src/app/components/group-details/group-details.component.scss +++ b/src/app/components/group-details/group-details.component.scss @@ -37,10 +37,16 @@ margin-bottom: 20px; } -.input-field { - width: 100%; -} - .button-div { float: right; } + +.members > .search { + display: flex; + flex-direction: row; + justify-content: stretch; + width: 100%; +} +mat-form-field { + width: 100%; +} diff --git a/src/app/components/group-details/group-details.component.ts b/src/app/components/group-details/group-details.component.ts index 7107e920..5578fb65 100644 --- a/src/app/components/group-details/group-details.component.ts +++ b/src/app/components/group-details/group-details.component.ts @@ -34,6 +34,7 @@ export class GroupDetailsComponent implements OnInit { members: User[]; editGroupForm: FormGroup; pageEvent: PageEvent | undefined; + searchMembers: string; constructor(private route: ActivatedRoute, private dialog: MatDialog, 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..f632ee84 --- /dev/null +++ b/src/app/components/group-details/members-filter.pipe.spec.ts @@ -0,0 +1,8 @@ +import { MembersFilterPipe } from './members-filter.pipe'; + +describe('MembersFilterPipe', () => { + it('create an instance', () => { + const pipe = new MembersFilterPipe(); + expect(pipe).toBeTruthy(); + }); +}); 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..56cb3850 --- /dev/null +++ b/src/app/components/group-details/members-filter.pipe.ts @@ -0,0 +1,20 @@ +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.ts b/src/app/components/group-details/paginator.pipe.ts index 59a3ca45..86dba9f8 100644 --- a/src/app/components/group-details/paginator.pipe.ts +++ b/src/app/components/group-details/paginator.pipe.ts @@ -1,3 +1,15 @@ +/* +* 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"; From b8b7e4d151b318da7fb24f216138819afb4db9f1 Mon Sep 17 00:00:00 2001 From: Sylvain MATHIEU Date: Tue, 4 Jan 2022 15:43:04 +0100 Subject: [PATCH 20/50] group detail, members none case-sensitive sorted a to z --- .../group-details/group-details.component.html | 4 +++- .../group-details/group-details.component.ts | 2 +- .../components/group-details/members-filter.pipe.ts | 12 ++++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/app/components/group-details/group-details.component.html b/src/app/components/group-details/group-details.component.html index dc1709b1..cd5f121b 100644 --- a/src/app/components/group-details/group-details.component.html +++ b/src/app/components/group-details/group-details.component.html @@ -27,7 +27,9 @@
-
Members:
+
+
Members:
+
person_add