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 + }); + } + }