Fixed impersonation not updating the UI correctly. Self is selected and shown by default. Using async pipes & custom pipe.

This commit is contained in:
Tom 2025-04-10 12:31:05 +00:00
parent daa500111c
commit 9338e7e624
9 changed files with 54 additions and 36 deletions

View File

@ -1,14 +1,12 @@
@if (isAdmin()) { @if (isAdmin()) {
<main>
<mat-form-field class="mat-small" <mat-form-field class="mat-small"
subscriptSizing="dynamic"> subscriptSizing="dynamic">
<mat-label>User to impersonate</mat-label> <mat-label>User to impersonate</mat-label>
<mat-select [formControl]="impersonationControl"> <mat-select [formControl]="impersonationControl">
<mat-option>{{getUsername()}}</mat-option> <mat-option [value]="auth.getUserId()">{{getUsername()}}</mat-option>
@for (user of users; track user.id) { @for (user of (users$ | async | excludeById : auth.getUserId()); track user.id) {
<mat-option [value]="user.id">{{ user.name }}</mat-option> <mat-option [value]="user.id">{{ user.name }}</mat-option>
} }
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</main>
} }

View File

@ -1,21 +1,22 @@
import { Component, inject, Inject, OnInit, PLATFORM_ID } from '@angular/core'; import { Component, inject, OnInit } from '@angular/core';
import { ApiAuthenticationService } from '../../shared/services/api/api-authentication.service'; import { ApiAuthenticationService } from '../../shared/services/api/api-authentication.service';
import { MatCardModule } from '@angular/material/card'; import { MatCardModule } from '@angular/material/card';
import { MatSelectModule } from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { isPlatformBrowser } from '@angular/common';
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
import EventService from '../../shared/services/EventService'; import EventService from '../../shared/services/EventService';
import { HermesClientService } from '../../hermes-client.service'; import { HermesClientService } from '../../hermes-client.service';
import { Router } from '@angular/router';
import { FormControl, ReactiveFormsModule } from '@angular/forms'; import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { User } from '../../shared/models/user';
import { UserService } from '../../shared/services/user.service'; import { UserService } from '../../shared/services/user.service';
import { AsyncPipe } from '@angular/common';
import { ExcludeByIdPipe } from '../../shared/pipes/exclude-by-id.pipe';
@Component({ @Component({
selector: 'impersonation', selector: 'impersonation',
standalone: true, standalone: true,
imports: [ imports: [
AsyncPipe,
ExcludeByIdPipe,
MatCardModule, MatCardModule,
MatSelectModule, MatSelectModule,
ReactiveFormsModule, ReactiveFormsModule,
@ -24,34 +25,33 @@ import { UserService } from '../../shared/services/user.service';
styleUrl: './impersonation.component.scss' styleUrl: './impersonation.component.scss'
}) })
export class ImpersonationComponent implements OnInit { export class ImpersonationComponent implements OnInit {
private readonly events = inject(EventService); private readonly client = inject(HermesClientService);
private readonly userService = inject(UserService); private readonly userService = inject(UserService);
private readonly events = inject(EventService);
private readonly http = inject(HttpClient);
impersonationControl = new FormControl<string | undefined>(undefined); readonly auth = inject(ApiAuthenticationService);
users: User[];
constructor(private client: HermesClientService, private auth: ApiAuthenticationService, private router: Router, private http: HttpClient, @Inject(PLATFORM_ID) private platformId: Object) { impersonationControl = new FormControl<string>(this.auth.getUserId());
this.users = []; users$ = this.userService.fetch();
}
ngOnInit(): void { ngOnInit(): void {
if (!isPlatformBrowser(this.platformId)) { if (!this.auth.isAdmin()) {
return; return;
} }
this.userService.fetch().subscribe(users => { this.users$.subscribe(users => {
this.users = users.filter((d: any) => d.name != this.auth.getUsername());
const id = this.auth.getImpersonatedId(); const id = this.auth.getImpersonatedId();
if (id && this.users.find(u => u.id == id)) { if (id && users.find(u => u.id == id)) {
this.impersonationControl.setValue(id); this.impersonationControl.setValue(id);
} }
}); });
this.impersonationControl.valueChanges.subscribe((impersonationId) => { this.impersonationControl.valueChanges.subscribe((impersonationId) => {
if (!this.auth.isAdmin() || impersonationId == this.auth.getImpersonatedId()) if (impersonationId == this.auth.getImpersonatedId())
return; return;
if (!impersonationId) { if (impersonationId == this.auth.getUserId()) {
this.http.delete(environment.API_HOST + '/admin/impersonate', { this.http.delete(environment.API_HOST + '/admin/impersonate', {
headers: { headers: {
'Authorization': 'Bearer ' + localStorage.getItem('jwt') 'Authorization': 'Bearer ' + localStorage.getItem('jwt')
@ -73,7 +73,6 @@ export class ImpersonationComponent implements OnInit {
}).subscribe(async (data: any) => { }).subscribe(async (data: any) => {
this.client.disconnect(true); this.client.disconnect(true);
this.events.emit('impersonation', impersonationId); this.events.emit('impersonation', impersonationId);
await this.router.navigate(['tts-login']);
}); });
} }
}); });

View File

@ -6,7 +6,6 @@ import { MatButtonModule } from '@angular/material/button';
import { AuthModule } from '../../auth/auth.module'; import { AuthModule } from '../../auth/auth.module';
import { ApiAuthenticationService } from '../../shared/services/api/api-authentication.service'; import { ApiAuthenticationService } from '../../shared/services/api/api-authentication.service';
import { ImpersonationComponent } from '../../auth/impersonation/impersonation.component'; import { ImpersonationComponent } from '../../auth/impersonation/impersonation.component';
import { HermesClientService } from '../../hermes-client.service';
import EventService from '../../shared/services/EventService'; import EventService from '../../shared/services/EventService';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { ThemeComponent } from "../../theme/theme.component"; import { ThemeComponent } from "../../theme/theme.component";
@ -32,14 +31,13 @@ import { LoginButtonComponent } from "../../auth/login-button/login-button.compo
}) })
export class Topbar implements OnDestroy { export class Topbar implements OnDestroy {
private readonly auth = inject(ApiAuthenticationService); private readonly auth = inject(ApiAuthenticationService);
private readonly client = inject(HermesClientService);
private readonly events = inject(EventService); private readonly events = inject(EventService);
private subscriptions: (Subscription | null)[] = []; private subscriptions: (Subscription | null)[] = [];
private _showImpersonation: boolean = false private _showImpersonation: boolean = false
constructor() { constructor() {
this.subscriptions.push(this.events.listen('impersonation', () => this.showImpersonation = false)); this.subscriptions.push(this.events.listen('impersonation', () => { this.auth.update(localStorage.getItem('jwt')); this.showImpersonation = false; }));
} }
ngOnDestroy(): void { ngOnDestroy(): void {

View File

@ -44,7 +44,6 @@ export class RedemptionListComponent {
alias: 'twitchRedemptions', alias: 'twitchRedemptions',
transform: toTwitchRedemptionDict, transform: toTwitchRedemptionDict,
}); });
//twitchRedemptions = computed<{ [id: string]: string } | null>(() => ({}));
actions = input.required<RedeemableAction[]>(); actions = input.required<RedeemableAction[]>();

View File

@ -0,0 +1,13 @@
import {Pipe, PipeTransform} from '@angular/core';
@Pipe({
name: 'excludeById',
})
export class ExcludeByIdPipe implements PipeTransform {
transform(values: any[] | null, id: string): any[] | null {
if (!values) {
return values;
}
return values.filter(value => value.id != id);
}
}

View File

@ -36,6 +36,10 @@ export class ApiAuthenticationService {
return this.user?.impersonation?.name; return this.user?.impersonation?.name;
} }
getUserId() {
return this.user?.id;
}
getUsername() { getUsername() {
return this.user?.name; return this.user?.name;
} }

View File

@ -2,7 +2,7 @@ import { inject, Injectable } from '@angular/core';
import { HermesClientService } from '../../hermes-client.service'; import { HermesClientService } from '../../hermes-client.service';
import EventService from './EventService'; import EventService from './EventService';
import { Permission } from '../models/permission'; import { Permission } from '../models/permission';
import { map, Observable, of } from 'rxjs'; import { filter, map, merge, Observable, of, startWith } from 'rxjs';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -12,6 +12,7 @@ export class PermissionService {
private readonly events = inject(EventService); private readonly events = inject(EventService);
private data: Permission[] = []; private data: Permission[] = [];
private loaded = false; private loaded = false;
changes$: Observable<any>;
create$: Observable<any> | undefined; create$: Observable<any> | undefined;
update$: Observable<any> | undefined; update$: Observable<any> | undefined;
delete$: Observable<any> | undefined; delete$: Observable<any> | undefined;
@ -20,6 +21,12 @@ export class PermissionService {
this.create$ = this.client.filterByRequestType('create_group_permission'); this.create$ = this.client.filterByRequestType('create_group_permission');
this.update$ = this.client.filterByRequestType('update_group_permission'); this.update$ = this.client.filterByRequestType('update_group_permission');
this.delete$ = this.client.filterByRequestType('delete_group_permission'); this.delete$ = this.client.filterByRequestType('delete_group_permission');
this.changes$ = merge<any>(this.create$, this.update$, this.delete$)
.pipe(
startWith(null),
filter(d => !d.error),
map(_ => this.data.slice()),
);
this.create$?.subscribe(d => { this.create$?.subscribe(d => {
if (d.error) { if (d.error) {

View File

@ -25,7 +25,7 @@ export default class RedeemableActionService {
this.changes$ = merge<any>(this.create$, this.update$, this.delete$) this.changes$ = merge<any>(this.create$, this.update$, this.delete$)
.pipe( .pipe(
startWith(null), startWith(null),
map(d => this.data.slice()), map(_ => this.data.slice()),
); );
this.create$?.subscribe(d => this.data.push(d.data)); this.create$?.subscribe(d => this.data.push(d.data));

View File

@ -24,7 +24,7 @@ export default class RedemptionService {
this.changes$ = merge<any>(this.create$, this.update$, this.delete$) this.changes$ = merge<any>(this.create$, this.update$, this.delete$)
.pipe( .pipe(
startWith(null), startWith(null),
map(d => this.data.slice()), map(_ => this.data.slice()),
); );
this.create$?.subscribe(d => this.data.push(d.data)); this.create$?.subscribe(d => this.data.push(d.data));