Added top bar on all pages. Simplified TTS login component. Fixed some issues. Removed redirects for now.
This commit is contained in:
parent
d44ec50a6a
commit
055885837c
@ -1,4 +1,10 @@
|
||||
<main class="main">
|
||||
<navigation class="navigation" />
|
||||
<router-outlet class="content" />
|
||||
<main>
|
||||
<topbar class="top" />
|
||||
<div [class.container]="isSidebarOpen"
|
||||
[class.full]="!isSidebarOpen">
|
||||
@if (isSidebarOpen) {
|
||||
<sidebar class="navigation" />
|
||||
}
|
||||
<router-outlet class="content" />
|
||||
</div>
|
||||
</main>
|
@ -1,4 +1,9 @@
|
||||
.main {
|
||||
.container {
|
||||
display: grid;
|
||||
grid-template-columns: 20em 0px 1fr;
|
||||
}
|
||||
|
||||
.full {
|
||||
width: 80%;
|
||||
margin: 0 auto;
|
||||
}
|
@ -1,10 +1,9 @@
|
||||
import { isPlatformBrowser } from '@angular/common';
|
||||
import { Component, OnInit, Inject, PLATFORM_ID, NgZone, OnDestroy, inject, HostBinding } from '@angular/core';
|
||||
import { ActivatedRoute, Router, RouterOutlet } from '@angular/router';
|
||||
import { Router, RouterOutlet } from '@angular/router';
|
||||
import { HermesClientService } from './hermes-client.service';
|
||||
import { AuthUserGuard } from './shared/auth/auth.user.guard'
|
||||
import { first, Subscription, timeout } from 'rxjs';
|
||||
import { NavigationComponent } from "./navigation/navigation.component";
|
||||
import EventService from './shared/services/EventService';
|
||||
import { ApiAuthenticationService } from './shared/services/api/api-authentication.service';
|
||||
import { AuthModule } from './auth/auth.module';
|
||||
@ -12,14 +11,23 @@ import { ApiKeyService } from './shared/services/api/api-key.service';
|
||||
import ApiKey from './shared/models/api-key';
|
||||
import { ThemeService } from './shared/services/theme.service';
|
||||
import { OverlayContainer } from '@angular/cdk/overlay';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { SidebarComponent } from "./navigation/sidebar/sidebar.component";
|
||||
import { Topbar as TopbarComponent } from "./navigation/topbar/topbar.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: true,
|
||||
imports: [
|
||||
RouterOutlet,
|
||||
AuthModule,
|
||||
NavigationComponent
|
||||
RouterOutlet,
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
MatToolbarModule,
|
||||
SidebarComponent,
|
||||
TopbarComponent,
|
||||
],
|
||||
providers: [AuthUserGuard],
|
||||
templateUrl: './app.component.html',
|
||||
@ -34,6 +42,8 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
private ngZone: NgZone;
|
||||
private subscriptions: Subscription[];
|
||||
|
||||
authentication = inject(ApiAuthenticationService);
|
||||
isSidebarOpen: boolean = true
|
||||
|
||||
@HostBinding('class.dark-theme')
|
||||
get isDarkTheme() {
|
||||
@ -45,7 +55,6 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
return this.themeService.isLightTheme();
|
||||
}
|
||||
|
||||
|
||||
constructor(private auth: ApiAuthenticationService, private client: HermesClientService, private events: EventService, private router: Router, ngZone: NgZone, @Inject(PLATFORM_ID) private platformId: Object) {
|
||||
this.ngZone = ngZone;
|
||||
this.isBrowser = isPlatformBrowser(this.platformId);
|
||||
@ -54,20 +63,17 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||
this.subscriptions.push(this.events.listen('tts_login_ack', async _ => {
|
||||
const url = router.url;
|
||||
const params = router.parseUrl(url).queryParams;
|
||||
const redirect = params['rd'];
|
||||
|
||||
if (params && 'rd' in params) {
|
||||
await this.router.navigate([params['rd']]);
|
||||
if (redirect && !(url.startsWith(redirect) || redirect.startsWith(url))) {
|
||||
await this.router.navigate([redirect]);
|
||||
} else if (url == '/' || url.startsWith('/login') || url.startsWith('/tts-login')) {
|
||||
await this.router.navigate(['policies']);
|
||||
}
|
||||
}));
|
||||
this.subscriptions.push(this.events.listen('tts_logoff', async _ => {
|
||||
await this.router.navigate(['tts-login'], {
|
||||
queryParams: {
|
||||
rd: this.router.url.substring(1)
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
this.subscriptions.push(this.events.listen('tts_logoff', async _ => await this.router.navigate(['tts-login'])));
|
||||
this.subscriptions.push(this.events.listen('toggle_sidebar', () => this.isSidebarOpen = !this.isSidebarOpen))
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
@ -2,7 +2,6 @@ import { Routes } from '@angular/router';
|
||||
import { PolicyComponent } from './policies/policy/policy.component';
|
||||
import { AuthUserGuard } from './shared/auth/auth.user.guard';
|
||||
import { LoginComponent } from './auth/login/login.component';
|
||||
import { TtsLoginComponent } from './auth/tts-login/tts-login.component';
|
||||
import { TwitchAuthCallbackComponent } from './twitch-auth-callback/twitch-auth-callback.component';
|
||||
import { FiltersComponent } from './tts-filters/filters/filters.component';
|
||||
import { AuthAdminGuard } from './shared/auth/auth.admin.guard';
|
||||
@ -24,6 +23,7 @@ import { ConnectionsComponent } from './connections/connections/connections.comp
|
||||
import ConnectionResolver from './shared/resolvers/connection-resolver';
|
||||
import { ConnectionCallbackComponent } from './connections/callback/callback.component';
|
||||
import { KeysComponent } from './keys/keys/keys.component';
|
||||
import { TtsLoginComponent } from './auth/tts-login/tts-login.component';
|
||||
|
||||
export const routes: Routes = [
|
||||
{
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { LoginComponent } from './login/login.component';
|
||||
import { TtsLoginComponent } from './tts-login/tts-login.component';
|
||||
import { ImpersonationComponent } from './impersonation/impersonation.component';
|
||||
import { UserCardComponent } from './user-card/user-card.component';
|
||||
|
||||
@ -8,7 +7,6 @@ import { UserCardComponent } from './user-card/user-card.component';
|
||||
declarations: [],
|
||||
imports: [
|
||||
LoginComponent,
|
||||
TtsLoginComponent,
|
||||
ImpersonationComponent,
|
||||
UserCardComponent,
|
||||
]
|
||||
|
@ -1,9 +1,9 @@
|
||||
@if (isAdmin()) {
|
||||
<main>
|
||||
<mat-form-field>
|
||||
<mat-form-field class="mat-small"
|
||||
subscriptSizing="dynamic">
|
||||
<mat-label>User to impersonate</mat-label>
|
||||
<mat-select (selectionChange)="onChange($event)"
|
||||
[(value)]="impersonated">
|
||||
<mat-select [formControl]="impersonationControl">
|
||||
<mat-option>{{getUsername()}}</mat-option>
|
||||
@for (user of users; track user.id) {
|
||||
<mat-option [value]="user.id">{{ user.name }}</mat-option>
|
||||
|
@ -2,5 +2,4 @@ main {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 1em;
|
||||
}
|
@ -8,56 +8,76 @@ import { environment } from '../../../environments/environment';
|
||||
import EventService from '../../shared/services/EventService';
|
||||
import { HermesClientService } from '../../hermes-client.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { timeout, first } from 'rxjs';
|
||||
import ApiKey from '../../shared/models/api-key';
|
||||
import { ApiKeyService } from '../../shared/services/api/api-key.service';
|
||||
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||
import { User } from '../../shared/models/user';
|
||||
import { UserService } from '../../shared/services/user.service';
|
||||
|
||||
@Component({
|
||||
selector: 'impersonation',
|
||||
standalone: true,
|
||||
imports: [MatCardModule, MatSelectModule],
|
||||
imports: [
|
||||
MatCardModule,
|
||||
MatSelectModule,
|
||||
ReactiveFormsModule,
|
||||
],
|
||||
templateUrl: './impersonation.component.html',
|
||||
styleUrl: './impersonation.component.scss'
|
||||
})
|
||||
export class ImpersonationComponent implements OnInit {
|
||||
private readonly keyService = inject(ApiKeyService);
|
||||
private readonly events = inject(EventService);
|
||||
private readonly userService = inject(UserService);
|
||||
|
||||
impersonated: string | undefined;
|
||||
users: { id: string, name: string }[];
|
||||
impersonationControl = new FormControl<string | undefined>(undefined);
|
||||
users: User[];
|
||||
|
||||
constructor(private client: HermesClientService, private auth: ApiAuthenticationService, private router: Router, private events: EventService, private http: HttpClient, @Inject(PLATFORM_ID) private platformId: Object) {
|
||||
this.users = []
|
||||
constructor(private client: HermesClientService, private auth: ApiAuthenticationService, private router: Router, private http: HttpClient, @Inject(PLATFORM_ID) private platformId: Object) {
|
||||
this.users = [];
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (!isPlatformBrowser(this.platformId) || !this.auth.isAdmin()) {
|
||||
if (!isPlatformBrowser(this.platformId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.http.get(environment.API_HOST + '/admin/users', {
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + localStorage.getItem('jwt')
|
||||
}
|
||||
}).subscribe((data: any) => {
|
||||
this.users = data.filter((d: any) => d.name != this.auth.getUsername());
|
||||
this.userService.fetch().subscribe(users => {
|
||||
this.users = users.filter((d: any) => d.name != this.auth.getUsername());
|
||||
const id = this.auth.getImpersonatedId();
|
||||
if (id && this.users.find(u => u.id == id)) {
|
||||
this.impersonated = id;
|
||||
this.impersonationControl.setValue(id);
|
||||
}
|
||||
});
|
||||
|
||||
this.events.listen('impersonation', (userId) => {
|
||||
const url = this.router.url;
|
||||
this.client.first(d => d.op == 2 && !d.d.another_client)
|
||||
.subscribe(async _ =>
|
||||
await setTimeout(async () =>
|
||||
await this.router.navigate([url.substring(1)]), 500));
|
||||
this.keyService.fetch()
|
||||
.pipe(timeout(3000), first())
|
||||
.subscribe(async (d: ApiKey[]) => {
|
||||
if (d.length > 0)
|
||||
this.client.login(d[0].id);
|
||||
this.impersonationControl.valueChanges.subscribe((impersonationId) => {
|
||||
if (!this.auth.isAdmin() || impersonationId == this.auth.getImpersonatedId())
|
||||
return;
|
||||
|
||||
if (!impersonationId) {
|
||||
this.http.delete(environment.API_HOST + '/admin/impersonate', {
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + localStorage.getItem('jwt')
|
||||
},
|
||||
body: {
|
||||
impersonation: impersonationId
|
||||
}
|
||||
}).subscribe(async (data: any) => {
|
||||
this.impersonationControl.setValue(undefined);
|
||||
this.client.disconnect(true);
|
||||
this.events.emit('impersonation', undefined);
|
||||
});
|
||||
} else {
|
||||
this.http.put(environment.API_HOST + '/admin/impersonate', {
|
||||
impersonation: impersonationId
|
||||
}, {
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + localStorage.getItem('jwt')
|
||||
}
|
||||
}).subscribe(async (data: any) => {
|
||||
this.impersonationControl.setValue(impersonationId);
|
||||
this.client.disconnect(true);
|
||||
this.events.emit('impersonation', impersonationId);
|
||||
await this.router.navigate(['tts-login']);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -68,35 +88,4 @@ export class ImpersonationComponent implements OnInit {
|
||||
public getUsername() {
|
||||
return this.auth.getUsername();
|
||||
}
|
||||
|
||||
public onChange(e: any) {
|
||||
if (!this.auth.isAdmin())
|
||||
return;
|
||||
|
||||
if (!e.value) {
|
||||
this.http.delete(environment.API_HOST + '/admin/impersonate', {
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + localStorage.getItem('jwt')
|
||||
},
|
||||
body: {
|
||||
impersonation: e.value
|
||||
}
|
||||
}).subscribe(async (data: any) => {
|
||||
this.client.disconnect();
|
||||
this.events.emit('impersonation', e.value);
|
||||
});
|
||||
} else {
|
||||
this.http.put(environment.API_HOST + '/admin/impersonate', {
|
||||
impersonation: e.value
|
||||
}, {
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + localStorage.getItem('jwt')
|
||||
}
|
||||
}).subscribe(async (data: any) => {
|
||||
this.client.disconnect();
|
||||
this.events.emit('impersonation', e.value);
|
||||
await this.router.navigate(['tts-login']);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,32 +1,15 @@
|
||||
import { Component, OnDestroy, OnInit } from '@angular/core';
|
||||
import { Component } from '@angular/core';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { Router, RouterModule } from '@angular/router';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { environment } from '../../../environments/environment';
|
||||
|
||||
@Component({
|
||||
selector: 'login',
|
||||
standalone: true,
|
||||
imports: [MatCardModule, RouterModule],
|
||||
imports: [MatCardModule],
|
||||
templateUrl: './login.component.html',
|
||||
styleUrl: './login.component.scss'
|
||||
})
|
||||
export class LoginComponent implements OnInit, OnDestroy {
|
||||
subscription: Subscription | null;
|
||||
|
||||
constructor(private router: Router) {
|
||||
this.subscription = null;
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (this.subscription)
|
||||
this.subscription.unsubscribe()
|
||||
}
|
||||
|
||||
export class LoginComponent {
|
||||
login() {
|
||||
document.location.replace(environment.API_HOST + '/auth');
|
||||
}
|
||||
|
@ -9,7 +9,7 @@
|
||||
<mat-card-content class="content">
|
||||
<mat-form-field>
|
||||
<mat-label>API Key</mat-label>
|
||||
<mat-select [(value)]="selected_api_key">
|
||||
<mat-select [formControl]="keyControl">
|
||||
@for (key of api_keys; track key.id) {
|
||||
<mat-option [value]="key.id">{{key.label}}</mat-option>
|
||||
}
|
||||
|
@ -1,58 +1,41 @@
|
||||
import { Component, inject, OnDestroy, OnInit } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { MatInputModule } from '@angular/material/input';
|
||||
import { Component, inject, OnInit } from '@angular/core';
|
||||
import { FormControl, ReactiveFormsModule } from '@angular/forms';
|
||||
import { MatSelectModule } from '@angular/material/select';
|
||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import EventService from '../../shared/services/EventService';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { first, Subscription, timeout } from 'rxjs';
|
||||
import { HermesClientService } from '../../hermes-client.service';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { ApiKeyService } from '../../shared/services/api/api-key.service';
|
||||
|
||||
@Component({
|
||||
selector: 'tts-login',
|
||||
standalone: true,
|
||||
imports: [MatButtonModule, MatCardModule, MatFormFieldModule, MatSelectModule, MatInputModule, FormsModule],
|
||||
imports: [
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatFormFieldModule,
|
||||
MatSelectModule,
|
||||
ReactiveFormsModule,
|
||||
],
|
||||
templateUrl: './tts-login.component.html',
|
||||
styleUrl: './tts-login.component.scss'
|
||||
})
|
||||
export class TtsLoginComponent implements OnInit, OnDestroy {
|
||||
export class TtsLoginComponent implements OnInit {
|
||||
private readonly client = inject(HermesClientService);
|
||||
private readonly keyService = inject(ApiKeyService);
|
||||
private readonly events = inject(EventService);
|
||||
private readonly route = inject(ActivatedRoute);
|
||||
|
||||
keyControl = new FormControl<string | null>('');
|
||||
api_keys: { id: string, label: string }[] = [];
|
||||
selected_api_key: string | undefined;
|
||||
|
||||
private subscriptions: Subscription[] = [];
|
||||
|
||||
|
||||
ngOnInit(): void {
|
||||
this.route.data.subscribe(d => this.api_keys = d['keys']);
|
||||
|
||||
this.subscriptions.push(this.events.listen('tts_logoff', async _ => {
|
||||
this.selected_api_key = undefined;
|
||||
}));
|
||||
this.subscriptions.push(this.events.listen('impersonation', _ => {
|
||||
this.selected_api_key = undefined;
|
||||
|
||||
this.keyService.fetch()
|
||||
.pipe(timeout(3000), first())
|
||||
.subscribe(d => this.api_keys = d);
|
||||
}));
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subscriptions.forEach(s => s.unsubscribe());
|
||||
}
|
||||
|
||||
login(): void {
|
||||
if (!this.selected_api_key)
|
||||
if (!this.keyControl.value)
|
||||
return;
|
||||
|
||||
this.client.login(this.selected_api_key);
|
||||
this.client.login(this.keyControl.value);
|
||||
}
|
||||
}
|
||||
|
@ -35,14 +35,20 @@ export class ConnectionCallbackComponent implements OnInit {
|
||||
return;
|
||||
}
|
||||
|
||||
this.http.get(`${environment.API_HOST}/auth/connections?token=${params['access_token']}&state=${params['state']}&expires_in=${params['expires_in']}`).subscribe(async (d: any) => {
|
||||
const data = d.data;
|
||||
this.success = true;
|
||||
console.log(params);
|
||||
this.http.get(`${environment.API_HOST}/auth/connections?token=${params['access_token']}&state=${params['state']}&expires_in=${params['expires_in']}`).subscribe({
|
||||
next: async (d: any) => {
|
||||
const data = d.data;
|
||||
this.success = true;
|
||||
|
||||
await setTimeout(async () => {
|
||||
this.client.createConnection(data.connection.name, data.connection.type, data.connection.clientId, params['access_token'], data.connection.grantType, params['scope'], data.expires_at);
|
||||
await this.router.navigate(['connections']);
|
||||
}, 2000)
|
||||
console.log('about to wait for 2 seconds')
|
||||
await setTimeout(async () => {
|
||||
console.log('create connection')
|
||||
this.client.createConnection(data.connection.name, data.connection.type, data.connection.clientId, params['access_token'], data.connection.grantType, params['scope'], data.expires_at);
|
||||
await this.router.navigate(['connections'])
|
||||
}, 2000)
|
||||
},
|
||||
error: async () => await this.router.navigate(['connections'])
|
||||
});
|
||||
;
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ export class HermesClientService {
|
||||
return this.listen();
|
||||
}
|
||||
|
||||
public disconnect() {
|
||||
public disconnect(impersonated: boolean = false) {
|
||||
if (!this.connected)
|
||||
return;
|
||||
|
||||
@ -43,7 +43,7 @@ export class HermesClientService {
|
||||
this.session_id = undefined;
|
||||
this.api_key = undefined;
|
||||
this.socket.close();
|
||||
this.events.emit('tts_logoff', null);
|
||||
this.events.emit('tts_logoff', impersonated);
|
||||
}
|
||||
|
||||
public filter(predicate: (data: any) => boolean): Observable<any> | undefined {
|
||||
|
12
src/app/navigation/navigation.module.ts
Normal file
12
src/app/navigation/navigation.module.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
|
||||
|
||||
|
||||
@NgModule({
|
||||
declarations: [],
|
||||
imports: [
|
||||
CommonModule
|
||||
]
|
||||
})
|
||||
export class NavigationModule { }
|
@ -1,24 +1,7 @@
|
||||
<nav>
|
||||
<user-card class="card" />
|
||||
<ul class="">
|
||||
@if (!isLoggedIn()) {
|
||||
<li>
|
||||
<a routerLink="/login"
|
||||
routerLinkActive="active">
|
||||
Login
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
@if (isLoggedIn() && !isTTSLoggedIn()) {
|
||||
<li>
|
||||
<a mat-raised-button
|
||||
routerLink="/tts-login"
|
||||
routerLinkActive="active">
|
||||
TTS Login
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
@if (isLoggedIn() && isTTSLoggedIn()) {
|
||||
<ul>
|
||||
@if (isLoggedIn()) {
|
||||
@if (isTTSLoggedIn()) {
|
||||
<li>
|
||||
<a mat-raised-button
|
||||
routerLink="/policies"
|
||||
@ -61,8 +44,15 @@
|
||||
Connections
|
||||
</a>
|
||||
</li>
|
||||
} @else {
|
||||
<li>
|
||||
<a mat-raised-button
|
||||
routerLink="/tts-login"
|
||||
routerLinkActive="active">
|
||||
TTS Login
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
@if (isLoggedIn()) {
|
||||
<li>
|
||||
<a mat-raised-button
|
||||
routerLink="/keys"
|
||||
@ -70,6 +60,13 @@
|
||||
API Keys
|
||||
</a>
|
||||
</li>
|
||||
} @else {
|
||||
<li>
|
||||
<a routerLink="/login"
|
||||
routerLinkActive="active">
|
||||
Login
|
||||
</a>
|
||||
</li>
|
||||
}
|
||||
</ul>
|
||||
</nav>
|
@ -1,18 +1,18 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { NavigationComponent } from './navigation.component';
|
||||
import { SidebarComponent } from './sidebar.component';
|
||||
|
||||
describe('NavigationComponent', () => {
|
||||
let component: NavigationComponent;
|
||||
let fixture: ComponentFixture<NavigationComponent>;
|
||||
let component: SidebarComponent;
|
||||
let fixture: ComponentFixture<SidebarComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [NavigationComponent]
|
||||
imports: [SidebarComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(NavigationComponent);
|
||||
fixture = TestBed.createComponent(SidebarComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
@ -1,26 +1,30 @@
|
||||
import { Component } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { HermesClientService } from '../hermes-client.service';
|
||||
import { ApiAuthenticationService } from '../shared/services/api/api-authentication.service';
|
||||
import { HermesClientService } from '../../hermes-client.service';
|
||||
import { ApiAuthenticationService } from '../../shared/services/api/api-authentication.service';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
import { AuthModule } from '../auth/auth.module';
|
||||
import { UserCardComponent } from "../auth/user-card/user-card.component";
|
||||
import { AuthModule } from '../../auth/auth.module';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
|
||||
@Component({
|
||||
selector: 'navigation',
|
||||
selector: 'sidebar',
|
||||
standalone: true,
|
||||
imports: [
|
||||
AuthModule,
|
||||
MatButtonModule,
|
||||
MatCardModule,
|
||||
MatIconModule,
|
||||
MatSidenavModule,
|
||||
MatToolbarModule,
|
||||
RouterModule,
|
||||
UserCardComponent,
|
||||
],
|
||||
templateUrl: './navigation.component.html',
|
||||
styleUrl: './navigation.component.scss'
|
||||
templateUrl: './sidebar.component.html',
|
||||
styleUrl: './sidebar.component.scss'
|
||||
})
|
||||
export class NavigationComponent {
|
||||
export class SidebarComponent {
|
||||
constructor(private auth: ApiAuthenticationService, private hermes: HermesClientService) { }
|
||||
|
||||
isLoggedIn() {
|
28
src/app/navigation/topbar/topbar.component.html
Normal file
28
src/app/navigation/topbar/topbar.component.html
Normal file
@ -0,0 +1,28 @@
|
||||
<mat-toolbar class="top">
|
||||
<button mat-icon-button
|
||||
(click)="toggleSidebar()">
|
||||
<mat-icon>menu</mat-icon>
|
||||
</button>
|
||||
|
||||
<span>Tom-to-Speech</span>
|
||||
@if (isTTSLoggedIn) {
|
||||
<span class="spacer"></span>
|
||||
<div class="links">
|
||||
@if (showImpersonation) {
|
||||
<impersonation />
|
||||
} @else {
|
||||
<div class="userInfo">
|
||||
<span class="username">{{impersonatedName ?? username}}</span>
|
||||
@if (impersonatedId) {
|
||||
<br />
|
||||
<span class="impersonated">Impersonating from {{username}}</span>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<button mat-icon-button
|
||||
(click)="showImpersonation = !showImpersonation">
|
||||
<mat-icon>supervisor_account</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</mat-toolbar>
|
22
src/app/navigation/topbar/topbar.component.scss
Normal file
22
src/app/navigation/topbar/topbar.component.scss
Normal file
@ -0,0 +1,22 @@
|
||||
.spacer {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.links > * {
|
||||
margin: 0 0.5em;
|
||||
}
|
||||
|
||||
.userInfo {
|
||||
display: inline-block;
|
||||
line-height: 10px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.impersonated {
|
||||
font-size: x-small;
|
||||
}
|
||||
|
||||
impersonation {
|
||||
display: inline-block;
|
||||
}
|
23
src/app/navigation/topbar/topbar.component.spec.ts
Normal file
23
src/app/navigation/topbar/topbar.component.spec.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TopbarComponent } from './topbar.component';
|
||||
|
||||
describe('TopbarComponent', () => {
|
||||
let component: TopbarComponent;
|
||||
let fixture: ComponentFixture<TopbarComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [TopbarComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TopbarComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
76
src/app/navigation/topbar/topbar.component.ts
Normal file
76
src/app/navigation/topbar/topbar.component.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import { Component, inject, OnDestroy } from '@angular/core';
|
||||
import { AuthVisitorGuard } from '../../shared/auth/auth.visitor.guard';
|
||||
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { MatButtonModule } from '@angular/material/button';
|
||||
import { AuthModule } from '../../auth/auth.module';
|
||||
import { ApiAuthenticationService } from '../../shared/services/api/api-authentication.service';
|
||||
import { ImpersonationComponent } from '../../auth/impersonation/impersonation.component';
|
||||
import { HermesClientService } from '../../hermes-client.service';
|
||||
import EventService from '../../shared/services/EventService';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'topbar',
|
||||
standalone: true,
|
||||
imports: [
|
||||
AuthModule,
|
||||
ImpersonationComponent,
|
||||
MatButtonModule,
|
||||
MatIconModule,
|
||||
MatToolbarModule,
|
||||
],
|
||||
providers: [AuthVisitorGuard],
|
||||
templateUrl: './topbar.component.html',
|
||||
styleUrl: './topbar.component.scss'
|
||||
})
|
||||
export class Topbar implements OnDestroy {
|
||||
private readonly auth = inject(ApiAuthenticationService);
|
||||
private readonly client = inject(HermesClientService);
|
||||
private readonly events = inject(EventService);
|
||||
|
||||
private subscriptions: (Subscription | null)[] = [];
|
||||
private _showImpersonation: boolean = false
|
||||
|
||||
constructor() {
|
||||
this.subscriptions.push(this.events.listen('impersonation', () => this.showImpersonation = false));
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
for (let subscription of this.subscriptions) {
|
||||
if (subscription) {
|
||||
subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get isTTSLoggedIn() {
|
||||
return this.client.logged_in;
|
||||
}
|
||||
|
||||
get username() {
|
||||
return this.auth.getUsername();
|
||||
}
|
||||
|
||||
get impersonatedId() {
|
||||
return this.auth.getImpersonatedId();
|
||||
}
|
||||
|
||||
get impersonatedName() {
|
||||
return this.auth.getImpersonatedName();
|
||||
}
|
||||
|
||||
get showImpersonation() {
|
||||
return this._showImpersonation;
|
||||
}
|
||||
|
||||
set showImpersonation(value: any) {
|
||||
if (this.auth.isAdmin()) {
|
||||
this._showImpersonation = !!value;
|
||||
}
|
||||
}
|
||||
|
||||
toggleSidebar() {
|
||||
this.events.emit('toggle_sidebar', undefined);
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { Component, inject, OnDestroy, OnInit, signal } from '@angular/core';
|
||||
import { Component, inject, OnDestroy, signal } from '@angular/core';
|
||||
import RedemptionService from '../../shared/services/redemption.service';
|
||||
import Redemption from '../../shared/models/redemption';
|
||||
import { MatCardModule } from '@angular/material/card';
|
||||
|
4
src/app/shared/models/user.ts
Normal file
4
src/app/shared/models/user.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface User {
|
||||
id: string;
|
||||
name: string;
|
||||
}
|
@ -14,6 +14,8 @@ export class ApiAuthenticationService {
|
||||
this.authenticated = false;
|
||||
this.user = null;
|
||||
this.lastCheck = new Date();
|
||||
|
||||
this.events.listen('impersonation', _ => this.update());
|
||||
}
|
||||
|
||||
isAuthenticated() {
|
||||
@ -28,6 +30,10 @@ export class ApiAuthenticationService {
|
||||
return this.user?.impersonation?.id;
|
||||
}
|
||||
|
||||
getImpersonatedName() {
|
||||
return this.user?.impersonation?.name;
|
||||
}
|
||||
|
||||
getUsername() {
|
||||
return this.user?.name;
|
||||
}
|
||||
|
@ -16,7 +16,16 @@ export class ApiKeyService {
|
||||
|
||||
|
||||
constructor() {
|
||||
this.events.listen('tts_logoff', (impersonation) => {
|
||||
console.log('tts_logoff triggered:', impersonation);
|
||||
if (impersonation) {
|
||||
this.keys = [];
|
||||
this.loaded = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.events.listen('logoff', () => {
|
||||
console.log('logoff triggered');
|
||||
this.keys = [];
|
||||
this.loaded = false;
|
||||
});
|
||||
|
@ -41,7 +41,7 @@ export default class TtsFilterService {
|
||||
|
||||
fetch() {
|
||||
if (this.loaded) {
|
||||
return of(this.data).pipe(first());
|
||||
return of(this.data);
|
||||
}
|
||||
|
||||
const $ = this.client.first(d => d.op == 4 && d.d.request.type == 'get_tts_word_filters')!.pipe(map(d => d.d.data));
|
||||
|
@ -2,7 +2,7 @@ import { HttpClient } from '@angular/common/http';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import TwitchRedemption from '../models/twitch-redemption';
|
||||
import { of } from 'rxjs';
|
||||
import { catchError, EMPTY, Observable, of } from 'rxjs';
|
||||
import EventService from './EventService';
|
||||
|
||||
@Injectable({
|
||||
@ -31,10 +31,12 @@ export default class TwitchRedemptionService {
|
||||
'Authorization': 'Bearer ' + localStorage.getItem('jwt'),
|
||||
}
|
||||
});
|
||||
$.subscribe(d => {
|
||||
this.twitchRedemptions = d;
|
||||
this.loaded = true;
|
||||
});
|
||||
$.pipe(catchError(() => EMPTY))
|
||||
.subscribe({
|
||||
next: d => this.twitchRedemptions = d,
|
||||
error: d => console.log('Twitch API redemptions:', d.error),
|
||||
complete: () => this.loaded = true,
|
||||
});
|
||||
return $;
|
||||
}
|
||||
}
|
16
src/app/shared/services/user.service.spec.ts
Normal file
16
src/app/shared/services/user.service.spec.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { UserService } from './user.service';
|
||||
|
||||
describe('UsersService', () => {
|
||||
let service: UserService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(UserService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
42
src/app/shared/services/user.service.ts
Normal file
42
src/app/shared/services/user.service.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { of, catchError, EMPTY } from 'rxjs';
|
||||
import { environment } from '../../../environments/environment';
|
||||
import EventService from './EventService';
|
||||
import { User } from '../models/user';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UserService {
|
||||
private readonly http = inject(HttpClient);
|
||||
private readonly events = inject(EventService);
|
||||
private users: User[] = [];
|
||||
private loaded = false;
|
||||
|
||||
|
||||
constructor() {
|
||||
this.events.listen('logoff', () => {
|
||||
this.users = [];
|
||||
this.loaded = false;
|
||||
});
|
||||
}
|
||||
|
||||
fetch() {
|
||||
if (this.loaded)
|
||||
return of(this.users);
|
||||
|
||||
const $ = this.http.get<User[]>(environment.API_HOST + '/admin/users', {
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + localStorage.getItem('jwt'),
|
||||
}
|
||||
});
|
||||
$.pipe(catchError(() => EMPTY))
|
||||
.subscribe({
|
||||
next: d => this.users = d,
|
||||
error: d => console.log('user service error:', d.error),
|
||||
complete: () => this.loaded = true,
|
||||
});
|
||||
return $;
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user