Skip to content

Commit 35f95bb

Browse files
authored
Add delete user to the console (#2603)
* Add delete user to the console * Add delete user to the console * Add delete user to the console
1 parent ae61cd4 commit 35f95bb

File tree

14 files changed

+584
-167
lines changed

14 files changed

+584
-167
lines changed

console-webapp/src/app/shared/services/backend.service.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,14 @@ export class BackendService {
172172
.pipe(catchError((err) => this.errorCatcher<User>(err)));
173173
}
174174

175+
deleteUser(registrarId: string, emailAddress: string): Observable<any> {
176+
return this.http
177+
.delete<any>(`/console-api/users?registrarId=${registrarId}`, {
178+
body: JSON.stringify({ emailAddress }),
179+
})
180+
.pipe(catchError((err) => this.errorCatcher<any>(err)));
181+
}
182+
175183
getUserData(): Observable<UserData> {
176184
return this.http
177185
.get<UserData>('/console-api/userdata')
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
<div class="console-app__user-details">
2+
@if(isNewUser) {
3+
<h1 class="mat-headline-4">
4+
{{ userDetails.emailAddress + " succesfully created" }}
5+
</h1>
6+
} @else {
7+
<h1 class="mat-headline-4">User details</h1>
8+
}
9+
<mat-divider></mat-divider>
10+
<div>
11+
<div class="console-app__user-details-controls">
12+
<button
13+
mat-icon-button
14+
aria-label="Back to users list"
15+
(click)="goBack()"
16+
>
17+
<mat-icon>arrow_back</mat-icon>
18+
</button>
19+
<div class="spacer"></div>
20+
<button
21+
mat-icon-button
22+
aria-label="Delete User"
23+
(click)="deleteUser()"
24+
[disabled]="isLoading"
25+
>
26+
<mat-icon>delete</mat-icon>
27+
</button>
28+
</div>
29+
</div>
30+
<p *ngIf="isLoading">
31+
<mat-progress-bar mode="query"></mat-progress-bar>
32+
</p>
33+
34+
<mat-card appearance="outlined">
35+
<mat-card-content>
36+
<mat-list role="list">
37+
<mat-list-item role="listitem">
38+
<h2>User details</h2>
39+
</mat-list-item>
40+
<mat-divider></mat-divider>
41+
<mat-list-item role="listitem">
42+
<span class="console-app__list-key">User email</span>
43+
<span class="console-app__list-value">{{
44+
userDetails.emailAddress
45+
}}</span>
46+
</mat-list-item>
47+
<mat-divider></mat-divider>
48+
<mat-list-item role="listitem">
49+
<span class="console-app__list-key">User role</span>
50+
<span class="console-app__list-value">{{
51+
roleToDescription(userDetails.role)
52+
}}</span>
53+
</mat-list-item>
54+
@if (userDetails.password) {
55+
<mat-divider></mat-divider>
56+
<mat-list-item role="listitem">
57+
<span class="console-app__list-key">Password</span>
58+
<span
59+
class="console-app__list-value console-app__user-details-password"
60+
>
61+
<input
62+
[type]="isPasswordVisible ? 'text' : 'password'"
63+
[value]="userDetails.password"
64+
disabled
65+
/>
66+
<button
67+
mat-button
68+
aria-label="Show password"
69+
(click)="isPasswordVisible = !isPasswordVisible"
70+
>
71+
{{ isPasswordVisible ? "Hide" : "View" }} password
72+
</button>
73+
</span>
74+
</mat-list-item>
75+
}
76+
</mat-list>
77+
</mat-card-content>
78+
</mat-card>
79+
</div>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
.console-app {
16+
&__user-details {
17+
&-controls {
18+
display: flex;
19+
align-items: center;
20+
margin: 20px 0;
21+
}
22+
&-password {
23+
input {
24+
border: none;
25+
background: transparent;
26+
}
27+
}
28+
max-width: 616px;
29+
}
30+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// Copyright 2024 The Nomulus Authors. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import { CommonModule } from '@angular/common';
16+
import { Component } from '@angular/core';
17+
import { MatSnackBar } from '@angular/material/snack-bar';
18+
import { SelectedRegistrarModule } from '../app.module';
19+
import { MaterialModule } from '../material.module';
20+
import { RegistrarService } from '../registrar/registrar.service';
21+
import { SnackBarModule } from '../snackbar.module';
22+
import { User, UsersService, roleToDescription } from './users.service';
23+
24+
@Component({
25+
selector: 'app-user-edit',
26+
templateUrl: './userEdit.component.html',
27+
styleUrls: ['./userEdit.component.scss'],
28+
standalone: true,
29+
imports: [
30+
MaterialModule,
31+
SnackBarModule,
32+
CommonModule,
33+
SelectedRegistrarModule,
34+
],
35+
providers: [],
36+
})
37+
export class UserEditComponent {
38+
inEdit = false;
39+
isPasswordVisible = false;
40+
isNewUser = false;
41+
isLoading = false;
42+
userDetails: User;
43+
44+
constructor(
45+
protected registrarService: RegistrarService,
46+
protected usersService: UsersService,
47+
private _snackBar: MatSnackBar
48+
) {
49+
this.userDetails = this.usersService
50+
.users()
51+
.filter(
52+
(u) => u.emailAddress === this.usersService.currentlyOpenUserEmail()
53+
)[0];
54+
if (this.usersService.isNewUser) {
55+
this.isNewUser = true;
56+
this.usersService.isNewUser = false;
57+
}
58+
}
59+
60+
roleToDescription(role: string) {
61+
return roleToDescription(role);
62+
}
63+
64+
deleteUser() {
65+
this.isLoading = true;
66+
this.usersService.deleteUser(this.userDetails.emailAddress).subscribe({
67+
error: (err) => {
68+
this._snackBar.open(err.error || err.message);
69+
this.isLoading = false;
70+
},
71+
complete: () => {
72+
this.isLoading = false;
73+
this.goBack();
74+
},
75+
});
76+
}
77+
78+
goBack() {
79+
this.usersService.currentlyOpenUserEmail.set('');
80+
}
81+
}

console-webapp/src/app/users/users.component.html

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
<app-selected-registrar-wrapper>
2-
@if (!isLoading) {
2+
@if(isLoading) {
3+
<div class="console-app__users-spinner">
4+
<mat-spinner />
5+
</div>
6+
} @else if(usersService.currentlyOpenUserEmail()) {
7+
<app-user-edit></app-user-edit>
8+
} @else {
39
<div class="console-app__users">
410
<div class="console-app__users-header">
511
<h1 class="mat-headline-4">Users</h1>
@@ -32,12 +38,11 @@ <h1 class="mat-headline-4">Users</h1>
3238
></mat-cell>
3339
</ng-container>
3440
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
35-
<mat-row *matRowDef="let row; columns: displayedColumns"></mat-row>
41+
<mat-row
42+
*matRowDef="let row; columns: displayedColumns"
43+
(click)="openDetails(row.emailAddress)"
44+
></mat-row>
3645
</mat-table>
3746
</div>
38-
} @else {
39-
<div class="console-app__users-spinner">
40-
<mat-spinner />
41-
</div>
4247
}
4348
</app-selected-registrar-wrapper>

console-webapp/src/app/users/users.component.ts

Lines changed: 9 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,8 @@ import { SelectedRegistrarModule } from '../app.module';
2222
import { MaterialModule } from '../material.module';
2323
import { RegistrarService } from '../registrar/registrar.service';
2424
import { SnackBarModule } from '../snackbar.module';
25-
import { User, UsersService } from './users.service';
26-
27-
const roleToDescription = (role: String) => {
28-
if (!role) return 'N/A';
29-
else if (role.toLowerCase().startsWith('account_manager')) {
30-
return 'Viewer';
31-
}
32-
return 'Editor';
33-
};
25+
import { UserEditComponent } from './userEdit.component';
26+
import { roleToDescription, User, UsersService } from './users.service';
3427

3528
export const columns = [
3629
{
@@ -55,6 +48,7 @@ export const columns = [
5548
SnackBarModule,
5649
CommonModule,
5750
SelectedRegistrarModule,
51+
UserEditComponent,
5852
],
5953
providers: [UsersService],
6054
})
@@ -92,6 +86,7 @@ export class UsersComponent {
9286
this.usersService.fetchUsers().subscribe({
9387
error: (err: HttpErrorResponse) => {
9488
this._snackBar.open(err.error || err.message);
89+
this.isLoading = false;
9590
},
9691
complete: () => {
9792
this.isLoading = false;
@@ -102,21 +97,17 @@ export class UsersComponent {
10297
createNewUser() {
10398
this.isLoading = true;
10499
this.usersService.createNewUser().subscribe({
105-
next: (newUser) => {
106-
this._snackBar.open(
107-
`New user with email ${newUser.emailAddress} has been created.`,
108-
'',
109-
{
110-
duration: 2000,
111-
}
112-
);
113-
},
114100
error: (err: HttpErrorResponse) => {
115101
this._snackBar.open(err.error || err.message);
102+
this.isLoading = false;
116103
},
117104
complete: () => {
118105
this.isLoading = false;
119106
},
120107
});
121108
}
109+
110+
openDetails(emailAddress: string) {
111+
this.usersService.currentlyOpenUserEmail.set(emailAddress);
112+
}
122113
}

console-webapp/src/app/users/users.service.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,29 @@ import { tap } from 'rxjs';
1717
import { RegistrarService } from '../registrar/registrar.service';
1818
import { BackendService } from '../shared/services/backend.service';
1919

20+
export const roleToDescription = (role: string) => {
21+
if (!role) return 'N/A';
22+
else if (role.toLowerCase().startsWith('account_manager')) {
23+
return 'Viewer';
24+
}
25+
return 'Editor';
26+
};
27+
2028
export interface CreateAutoTimestamp {
2129
creationTime: string;
2230
}
2331

2432
export interface User {
25-
emailAddress: String;
26-
role: String;
27-
password?: String;
33+
emailAddress: string;
34+
role: string;
35+
password?: string;
2836
}
2937

3038
@Injectable()
3139
export class UsersService {
3240
users = signal<User[]>([]);
41+
currentlyOpenUserEmail = signal<string>('');
42+
isNewUser: boolean = false;
3343

3444
constructor(
3545
private backendService: BackendService,
@@ -52,7 +62,15 @@ export class UsersService {
5262
.pipe(
5363
tap((newUser: User) => {
5464
this.users.set([...this.users(), newUser]);
65+
this.currentlyOpenUserEmail.set(newUser.emailAddress);
66+
this.isNewUser = true;
5567
})
5668
);
5769
}
70+
71+
deleteUser(emailAddress: string) {
72+
return this.backendService
73+
.deleteUser(this.registrarService.registrarId(), emailAddress)
74+
.pipe(tap((_) => this.fetchUsers()));
75+
}
5876
}

core/src/main/java/google/registry/request/Action.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@
3333
enum Method {
3434
GET,
3535
HEAD,
36-
POST
36+
POST,
37+
DELETE
3738
}
3839

3940
interface Service {

0 commit comments

Comments
 (0)