Skip to content

Commit

Permalink
Add console user role update and minor fixes to delete (#2610)
Browse files Browse the repository at this point in the history
  • Loading branch information
ptkach authored Nov 15, 2024
1 parent e54075f commit eeed166
Show file tree
Hide file tree
Showing 12 changed files with 380 additions and 183 deletions.
10 changes: 8 additions & 2 deletions console-webapp/src/app/shared/services/backend.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,14 +172,20 @@ export class BackendService {
.pipe(catchError((err) => this.errorCatcher<User>(err)));
}

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

updateUser(registrarId: string, updatedUser: User): Observable<any> {
return this.http
.put<User>(`/console-api/users?registrarId=${registrarId}`, updatedUser)
.pipe(catchError((err) => this.errorCatcher<any>(err)));
}

getUserData(): Observable<UserData> {
return this.http
.get<UserData>('/console-api/userdata')
Expand Down
98 changes: 72 additions & 26 deletions console-webapp/src/app/users/userEdit.component.html
Original file line number Diff line number Diff line change
@@ -1,32 +1,77 @@
<div class="console-app__user-details">
@if(isNewUser) {
@if(isEditing) {
<h1 class="mat-headline-4">Editing {{ userDetails().emailAddress }}</h1>
<mat-divider></mat-divider>
<div class="console-app__user-details-controls">
<button
mat-icon-button
aria-label="Back to view user"
(click)="isEditing = false"
>
<mat-icon>arrow_back</mat-icon>
</button>
</div>
<form (ngSubmit)="saveEdit()">
<p>
<mat-form-field appearance="outline">
<mat-label
>User Role:
<mat-icon
matTooltip="Viewer role doesn't allow making updates; Editor role allows updates, like Contacts delete or SSL certificate change"
>help_outline</mat-icon
></mat-label
>
<mat-select [(ngModel)]="userRole" name="userRole">
<mat-option value="PRIMARY_CONTACT">Editor</mat-option>
<mat-option value="ACCOUNT_MANAGER">Viewer</mat-option>
</mat-select>
</mat-form-field>
</p>
<button
mat-flat-button
color="primary"
aria-label="Save user"
type="submit"
>
Save
</button>
</form>
} @else { @if(isNewUser) {
<h1 class="mat-headline-4">
{{ userDetails.emailAddress + " succesfully created" }}
{{ userDetails().emailAddress + " successfully created" }}
</h1>
} @else {
<h1 class="mat-headline-4">User details</h1>
}
<mat-divider></mat-divider>
<div>
<div class="console-app__user-details-controls">
<button
mat-icon-button
aria-label="Back to users list"
(click)="goBack()"
>
<mat-icon>arrow_back</mat-icon>
</button>
<div class="spacer"></div>
<button
mat-icon-button
aria-label="Delete User"
(click)="deleteUser()"
[disabled]="isLoading"
>
<mat-icon>delete</mat-icon>
</button>
</div>
<div class="console-app__user-details-controls">
<button mat-icon-button aria-label="Back to users list" (click)="goBack()">
<mat-icon>arrow_back</mat-icon>
</button>
<div class="spacer"></div>
<button
mat-flat-button
color="primary"
aria-label="Edit User"
(click)="userRole = userDetails().role; isEditing = true"
>
<mat-icon>edit</mat-icon>
Edit
</button>
<button
mat-icon-button
aria-label="Delete User"
(click)="deleteUser()"
[disabled]="isLoading"
>
<mat-icon>delete</mat-icon>
</button>
</div>
<div *ngIf="isNewUser" class="console-app__user-details-save-password">
<mat-icon>priority_high</mat-icon>
Please save the password. For your security, we do not store passwords in a
recoverable format.
</div>

<p *ngIf="isLoading">
<mat-progress-bar mode="query"></mat-progress-bar>
</p>
Expand All @@ -41,17 +86,17 @@ <h2>User details</h2>
<mat-list-item role="listitem">
<span class="console-app__list-key">User email</span>
<span class="console-app__list-value">{{
userDetails.emailAddress
userDetails().emailAddress
}}</span>
</mat-list-item>
<mat-divider></mat-divider>
<mat-list-item role="listitem">
<span class="console-app__list-key">User role</span>
<span class="console-app__list-value">{{
roleToDescription(userDetails.role)
roleToDescription(userDetails().role)
}}</span>
</mat-list-item>
@if (userDetails.password) {
@if (userDetails().password) {
<mat-divider></mat-divider>
<mat-list-item role="listitem">
<span class="console-app__list-key">Password</span>
Expand All @@ -60,7 +105,7 @@ <h2>User details</h2>
>
<input
[type]="isPasswordVisible ? 'text' : 'password'"
[value]="userDetails.password"
[value]="userDetails().password"
disabled
/>
<button
Expand All @@ -76,4 +121,5 @@ <h2>User details</h2>
</mat-list>
</mat-card-content>
</mat-card>
}}
</div>
9 changes: 9 additions & 0 deletions console-webapp/src/app/users/userEdit.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@
background: transparent;
}
}
&-save-password {
display: flex;
justify-content: center;
align-items: center;
padding: 15px 10px;
margin-bottom: 20px;
border: 1px solid #ddd;
border-radius: 10px;
}
max-width: 616px;
}
}
42 changes: 33 additions & 9 deletions console-webapp/src/app/users/userEdit.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,22 @@
// limitations under the License.

import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { Component, computed } from '@angular/core';
import { MatSnackBar } from '@angular/material/snack-bar';
import { SelectedRegistrarModule } from '../app.module';
import { MaterialModule } from '../material.module';
import { RegistrarService } from '../registrar/registrar.service';
import { SnackBarModule } from '../snackbar.module';
import { User, UsersService, roleToDescription } from './users.service';
import { FormsModule } from '@angular/forms';

@Component({
selector: 'app-user-edit',
templateUrl: './userEdit.component.html',
styleUrls: ['./userEdit.component.scss'],
standalone: true,
imports: [
FormsModule,
MaterialModule,
SnackBarModule,
CommonModule,
Expand All @@ -35,22 +37,25 @@ import { User, UsersService, roleToDescription } from './users.service';
providers: [],
})
export class UserEditComponent {
inEdit = false;
isEditing = false;
isPasswordVisible = false;
isNewUser = false;
isLoading = false;
userDetails: User;
userRole = '';

userDetails = computed(() => {
return this.usersService
.users()
.filter(
(u) => u.emailAddress === this.usersService.currentlyOpenUserEmail()
)[0];
});

constructor(
protected registrarService: RegistrarService,
protected usersService: UsersService,
private _snackBar: MatSnackBar
) {
this.userDetails = this.usersService
.users()
.filter(
(u) => u.emailAddress === this.usersService.currentlyOpenUserEmail()
)[0];
if (this.usersService.isNewUser) {
this.isNewUser = true;
this.usersService.isNewUser = false;
Expand All @@ -63,7 +68,7 @@ export class UserEditComponent {

deleteUser() {
this.isLoading = true;
this.usersService.deleteUser(this.userDetails.emailAddress).subscribe({
this.usersService.deleteUser(this.userDetails()).subscribe({
error: (err) => {
this._snackBar.open(err.error || err.message);
this.isLoading = false;
Expand All @@ -78,4 +83,23 @@ export class UserEditComponent {
goBack() {
this.usersService.currentlyOpenUserEmail.set('');
}

saveEdit() {
this.isLoading = true;
this.usersService
.updateUser({
role: this.userRole,
emailAddress: this.userDetails().emailAddress,
})
.subscribe({
error: (err) => {
this._snackBar.open(err.error || err.message);
this.isLoading = false;
},
complete: () => {
this.isLoading = false;
this.isEditing = false;
},
});
}
}
16 changes: 11 additions & 5 deletions console-webapp/src/app/users/users.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
// limitations under the License.

import { Injectable, signal } from '@angular/core';
import { tap } from 'rxjs';
import { switchMap, tap } from 'rxjs';
import { RegistrarService } from '../registrar/registrar.service';
import { BackendService } from '../shared/services/backend.service';

export const roleToDescription = (role: string) => {
if (!role) return 'N/A';
else if (role.toLowerCase().startsWith('account_manager')) {
else if (role === 'ACCOUNT_MANAGER') {
return 'Viewer';
}
return 'Editor';
Expand Down Expand Up @@ -68,9 +68,15 @@ export class UsersService {
);
}

deleteUser(emailAddress: string) {
deleteUser(user: User) {
return this.backendService
.deleteUser(this.registrarService.registrarId(), emailAddress)
.pipe(tap((_) => this.fetchUsers()));
.deleteUser(this.registrarService.registrarId(), user)
.pipe(switchMap((_) => this.fetchUsers()));
}

updateUser(updatedUser: User) {
return this.backendService
.updateUser(this.registrarService.registrarId(), updatedUser)
.pipe(switchMap((_) => this.fetchUsers()));
}
}
1 change: 1 addition & 0 deletions core/src/main/java/google/registry/request/Action.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ enum Method {
GET,
HEAD,
POST,
PUT,
DELETE
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import static google.registry.request.Action.Method.GET;
import static google.registry.request.Action.Method.HEAD;
import static google.registry.request.Action.Method.POST;
import static google.registry.request.Action.Method.PUT;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
import static jakarta.servlet.http.HttpServletResponse.SC_FORBIDDEN;
import static jakarta.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
Expand Down Expand Up @@ -87,6 +88,8 @@ public final void run() {
if (verifyXSRF(user)) {
if (requestMethod.equals(DELETE.toString())) {
deleteHandler(user);
} else if (requestMethod.equals(PUT.toString())) {
putHandler(user);
} else {
postHandler(user);
}
Expand Down Expand Up @@ -117,6 +120,10 @@ protected void postHandler(User user) {
throw new UnsupportedOperationException("Console API POST handler not implemented");
}

protected void putHandler(User user) {
throw new UnsupportedOperationException("Console API PUT handler not implemented");
}

protected void getHandler(User user) {
throw new UnsupportedOperationException("Console API GET handler not implemented");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
import google.registry.ui.server.console.ConsoleEppPasswordAction.EppPasswordData;
import google.registry.ui.server.console.ConsoleOteAction.OteCreateData;
import google.registry.ui.server.console.ConsoleRegistryLockAction.ConsoleRegistryLockPostInput;
import google.registry.ui.server.console.ConsoleUsersAction.UserDeleteData;
import google.registry.ui.server.console.ConsoleUsersAction.UserData;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Optional;
import org.joda.time.DateTime;
Expand Down Expand Up @@ -247,10 +247,10 @@ public static Optional<EppPasswordData> provideEppPasswordChangeRequest(
}

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

@Provides
Expand Down
Loading

0 comments on commit eeed166

Please sign in to comment.