Skip to content

Commit eeed166

Browse files
authored
Add console user role update and minor fixes to delete (#2610)
1 parent e54075f commit eeed166

File tree

12 files changed

+380
-183
lines changed

12 files changed

+380
-183
lines changed

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

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

175-
deleteUser(registrarId: string, emailAddress: string): Observable<any> {
175+
deleteUser(registrarId: string, user: User): Observable<any> {
176176
return this.http
177177
.delete<any>(`/console-api/users?registrarId=${registrarId}`, {
178-
body: JSON.stringify({ emailAddress }),
178+
body: JSON.stringify(user),
179179
})
180180
.pipe(catchError((err) => this.errorCatcher<any>(err)));
181181
}
182182

183+
updateUser(registrarId: string, updatedUser: User): Observable<any> {
184+
return this.http
185+
.put<User>(`/console-api/users?registrarId=${registrarId}`, updatedUser)
186+
.pipe(catchError((err) => this.errorCatcher<any>(err)));
187+
}
188+
183189
getUserData(): Observable<UserData> {
184190
return this.http
185191
.get<UserData>('/console-api/userdata')
Lines changed: 72 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,77 @@
11
<div class="console-app__user-details">
2-
@if(isNewUser) {
2+
@if(isEditing) {
3+
<h1 class="mat-headline-4">Editing {{ userDetails().emailAddress }}</h1>
4+
<mat-divider></mat-divider>
5+
<div class="console-app__user-details-controls">
6+
<button
7+
mat-icon-button
8+
aria-label="Back to view user"
9+
(click)="isEditing = false"
10+
>
11+
<mat-icon>arrow_back</mat-icon>
12+
</button>
13+
</div>
14+
<form (ngSubmit)="saveEdit()">
15+
<p>
16+
<mat-form-field appearance="outline">
17+
<mat-label
18+
>User Role:
19+
<mat-icon
20+
matTooltip="Viewer role doesn't allow making updates; Editor role allows updates, like Contacts delete or SSL certificate change"
21+
>help_outline</mat-icon
22+
></mat-label
23+
>
24+
<mat-select [(ngModel)]="userRole" name="userRole">
25+
<mat-option value="PRIMARY_CONTACT">Editor</mat-option>
26+
<mat-option value="ACCOUNT_MANAGER">Viewer</mat-option>
27+
</mat-select>
28+
</mat-form-field>
29+
</p>
30+
<button
31+
mat-flat-button
32+
color="primary"
33+
aria-label="Save user"
34+
type="submit"
35+
>
36+
Save
37+
</button>
38+
</form>
39+
} @else { @if(isNewUser) {
340
<h1 class="mat-headline-4">
4-
{{ userDetails.emailAddress + " succesfully created" }}
41+
{{ userDetails().emailAddress + " successfully created" }}
542
</h1>
643
} @else {
744
<h1 class="mat-headline-4">User details</h1>
8-
}
945
<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>
46+
<div class="console-app__user-details-controls">
47+
<button mat-icon-button aria-label="Back to users list" (click)="goBack()">
48+
<mat-icon>arrow_back</mat-icon>
49+
</button>
50+
<div class="spacer"></div>
51+
<button
52+
mat-flat-button
53+
color="primary"
54+
aria-label="Edit User"
55+
(click)="userRole = userDetails().role; isEditing = true"
56+
>
57+
<mat-icon>edit</mat-icon>
58+
Edit
59+
</button>
60+
<button
61+
mat-icon-button
62+
aria-label="Delete User"
63+
(click)="deleteUser()"
64+
[disabled]="isLoading"
65+
>
66+
<mat-icon>delete</mat-icon>
67+
</button>
2968
</div>
69+
<div *ngIf="isNewUser" class="console-app__user-details-save-password">
70+
<mat-icon>priority_high</mat-icon>
71+
Please save the password. For your security, we do not store passwords in a
72+
recoverable format.
73+
</div>
74+
3075
<p *ngIf="isLoading">
3176
<mat-progress-bar mode="query"></mat-progress-bar>
3277
</p>
@@ -41,17 +86,17 @@ <h2>User details</h2>
4186
<mat-list-item role="listitem">
4287
<span class="console-app__list-key">User email</span>
4388
<span class="console-app__list-value">{{
44-
userDetails.emailAddress
89+
userDetails().emailAddress
4590
}}</span>
4691
</mat-list-item>
4792
<mat-divider></mat-divider>
4893
<mat-list-item role="listitem">
4994
<span class="console-app__list-key">User role</span>
5095
<span class="console-app__list-value">{{
51-
roleToDescription(userDetails.role)
96+
roleToDescription(userDetails().role)
5297
}}</span>
5398
</mat-list-item>
54-
@if (userDetails.password) {
99+
@if (userDetails().password) {
55100
<mat-divider></mat-divider>
56101
<mat-list-item role="listitem">
57102
<span class="console-app__list-key">Password</span>
@@ -60,7 +105,7 @@ <h2>User details</h2>
60105
>
61106
<input
62107
[type]="isPasswordVisible ? 'text' : 'password'"
63-
[value]="userDetails.password"
108+
[value]="userDetails().password"
64109
disabled
65110
/>
66111
<button
@@ -76,4 +121,5 @@ <h2>User details</h2>
76121
</mat-list>
77122
</mat-card-content>
78123
</mat-card>
124+
}}
79125
</div>

console-webapp/src/app/users/userEdit.component.scss

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@
2525
background: transparent;
2626
}
2727
}
28+
&-save-password {
29+
display: flex;
30+
justify-content: center;
31+
align-items: center;
32+
padding: 15px 10px;
33+
margin-bottom: 20px;
34+
border: 1px solid #ddd;
35+
border-radius: 10px;
36+
}
2837
max-width: 616px;
2938
}
3039
}

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

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,22 @@
1313
// limitations under the License.
1414

1515
import { CommonModule } from '@angular/common';
16-
import { Component } from '@angular/core';
16+
import { Component, computed } from '@angular/core';
1717
import { MatSnackBar } from '@angular/material/snack-bar';
1818
import { SelectedRegistrarModule } from '../app.module';
1919
import { MaterialModule } from '../material.module';
2020
import { RegistrarService } from '../registrar/registrar.service';
2121
import { SnackBarModule } from '../snackbar.module';
2222
import { User, UsersService, roleToDescription } from './users.service';
23+
import { FormsModule } from '@angular/forms';
2324

2425
@Component({
2526
selector: 'app-user-edit',
2627
templateUrl: './userEdit.component.html',
2728
styleUrls: ['./userEdit.component.scss'],
2829
standalone: true,
2930
imports: [
31+
FormsModule,
3032
MaterialModule,
3133
SnackBarModule,
3234
CommonModule,
@@ -35,22 +37,25 @@ import { User, UsersService, roleToDescription } from './users.service';
3537
providers: [],
3638
})
3739
export class UserEditComponent {
38-
inEdit = false;
40+
isEditing = false;
3941
isPasswordVisible = false;
4042
isNewUser = false;
4143
isLoading = false;
42-
userDetails: User;
44+
userRole = '';
45+
46+
userDetails = computed(() => {
47+
return this.usersService
48+
.users()
49+
.filter(
50+
(u) => u.emailAddress === this.usersService.currentlyOpenUserEmail()
51+
)[0];
52+
});
4353

4454
constructor(
4555
protected registrarService: RegistrarService,
4656
protected usersService: UsersService,
4757
private _snackBar: MatSnackBar
4858
) {
49-
this.userDetails = this.usersService
50-
.users()
51-
.filter(
52-
(u) => u.emailAddress === this.usersService.currentlyOpenUserEmail()
53-
)[0];
5459
if (this.usersService.isNewUser) {
5560
this.isNewUser = true;
5661
this.usersService.isNewUser = false;
@@ -63,7 +68,7 @@ export class UserEditComponent {
6368

6469
deleteUser() {
6570
this.isLoading = true;
66-
this.usersService.deleteUser(this.userDetails.emailAddress).subscribe({
71+
this.usersService.deleteUser(this.userDetails()).subscribe({
6772
error: (err) => {
6873
this._snackBar.open(err.error || err.message);
6974
this.isLoading = false;
@@ -78,4 +83,23 @@ export class UserEditComponent {
7883
goBack() {
7984
this.usersService.currentlyOpenUserEmail.set('');
8085
}
86+
87+
saveEdit() {
88+
this.isLoading = true;
89+
this.usersService
90+
.updateUser({
91+
role: this.userRole,
92+
emailAddress: this.userDetails().emailAddress,
93+
})
94+
.subscribe({
95+
error: (err) => {
96+
this._snackBar.open(err.error || err.message);
97+
this.isLoading = false;
98+
},
99+
complete: () => {
100+
this.isLoading = false;
101+
this.isEditing = false;
102+
},
103+
});
104+
}
81105
}

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

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@
1313
// limitations under the License.
1414

1515
import { Injectable, signal } from '@angular/core';
16-
import { tap } from 'rxjs';
16+
import { switchMap, tap } from 'rxjs';
1717
import { RegistrarService } from '../registrar/registrar.service';
1818
import { BackendService } from '../shared/services/backend.service';
1919

2020
export const roleToDescription = (role: string) => {
2121
if (!role) return 'N/A';
22-
else if (role.toLowerCase().startsWith('account_manager')) {
22+
else if (role === 'ACCOUNT_MANAGER') {
2323
return 'Viewer';
2424
}
2525
return 'Editor';
@@ -68,9 +68,15 @@ export class UsersService {
6868
);
6969
}
7070

71-
deleteUser(emailAddress: string) {
71+
deleteUser(user: User) {
7272
return this.backendService
73-
.deleteUser(this.registrarService.registrarId(), emailAddress)
74-
.pipe(tap((_) => this.fetchUsers()));
73+
.deleteUser(this.registrarService.registrarId(), user)
74+
.pipe(switchMap((_) => this.fetchUsers()));
75+
}
76+
77+
updateUser(updatedUser: User) {
78+
return this.backendService
79+
.updateUser(this.registrarService.registrarId(), updatedUser)
80+
.pipe(switchMap((_) => this.fetchUsers()));
7581
}
7682
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ enum Method {
3434
GET,
3535
HEAD,
3636
POST,
37+
PUT,
3738
DELETE
3839
}
3940

core/src/main/java/google/registry/ui/server/console/ConsoleApiAction.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import static google.registry.request.Action.Method.GET;
2121
import static google.registry.request.Action.Method.HEAD;
2222
import static google.registry.request.Action.Method.POST;
23+
import static google.registry.request.Action.Method.PUT;
2324
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
2425
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
2526
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
@@ -87,6 +88,8 @@ public final void run() {
8788
if (verifyXSRF(user)) {
8889
if (requestMethod.equals(DELETE.toString())) {
8990
deleteHandler(user);
91+
} else if (requestMethod.equals(PUT.toString())) {
92+
putHandler(user);
9093
} else {
9194
postHandler(user);
9295
}
@@ -117,6 +120,10 @@ protected void postHandler(User user) {
117120
throw new UnsupportedOperationException("Console API POST handler not implemented");
118121
}
119122

123+
protected void putHandler(User user) {
124+
throw new UnsupportedOperationException("Console API PUT handler not implemented");
125+
}
126+
120127
protected void getHandler(User user) {
121128
throw new UnsupportedOperationException("Console API GET handler not implemented");
122129
}

core/src/main/java/google/registry/ui/server/console/ConsoleModule.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
import google.registry.ui.server.console.ConsoleEppPasswordAction.EppPasswordData;
3737
import google.registry.ui.server.console.ConsoleOteAction.OteCreateData;
3838
import google.registry.ui.server.console.ConsoleRegistryLockAction.ConsoleRegistryLockPostInput;
39-
import google.registry.ui.server.console.ConsoleUsersAction.UserDeleteData;
39+
import google.registry.ui.server.console.ConsoleUsersAction.UserData;
4040
import jakarta.servlet.http.HttpServletRequest;
4141
import java.util.Optional;
4242
import org.joda.time.DateTime;
@@ -247,10 +247,10 @@ public static Optional<EppPasswordData> provideEppPasswordChangeRequest(
247247
}
248248

249249
@Provides
250-
@Parameter("userDeleteData")
251-
public static Optional<UserDeleteData> provideUserDeleteData(
250+
@Parameter("userData")
251+
public static Optional<UserData> provideUserData(
252252
Gson gson, @OptionalJsonPayload Optional<JsonElement> payload) {
253-
return payload.map(s -> gson.fromJson(s, UserDeleteData.class));
253+
return payload.map(s -> gson.fromJson(s, UserData.class));
254254
}
255255

256256
@Provides

0 commit comments

Comments
 (0)