Added Login, TTS Login, Policies

This commit is contained in:
Tom 2024-10-25 19:09:34 +00:00
parent 0c7fbf1cb9
commit 65f4172bc2
10 changed files with 275 additions and 40 deletions

2
.gitignore vendored
View File

@ -40,3 +40,5 @@ testem.log
# System files
.DS_Store
Thumbs.db
src/environments/*

View File

@ -1,15 +1,13 @@
import { CommonModule, DatePipe, isPlatformBrowser } from '@angular/common';
import { Component, OnInit, Inject, PLATFORM_ID, NgZone, OnDestroy } from '@angular/core';
import { Router, RouterModule, RouterOutlet, Routes } from '@angular/router';
import { Router, RouterOutlet } from '@angular/router';
import { FormsModule } from '@angular/forms'
import { HermesClientService } from './hermes-client.service';
import { AuthGuard } from './shared/auth/auth.guard'
import { Subscription } from 'rxjs';
import { Policy, PolicyScope } from './shared/models/policy';
import { PolicyComponent } from "./policy/policy.component";
import { NavigationComponent } from "./navigation/navigation.component";
import EventService from './shared/services/EventService';
import { HttpClient } from '@angular/common/http';
import { ApiAuthenticationService } from './shared/services/api/api-authentication.service';
@Component({
@ -23,13 +21,14 @@ import { ApiAuthenticationService } from './shared/services/api/api-authenticati
export class AppComponent implements OnInit, OnDestroy {
private isBrowser: boolean;
private ngZone: NgZone;
private subscription: Subscription | undefined;
private subscriptions: Subscription[];
pipe = new DatePipe('en-US')
constructor(private auth: ApiAuthenticationService, private client: HermesClientService, private events: EventService, private http: HttpClient, ngZone: NgZone, @Inject(PLATFORM_ID) private platformId: Object) {
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)
this.isBrowser = isPlatformBrowser(this.platformId);
this.subscriptions = [];
}
ngOnInit(): void {
@ -38,13 +37,30 @@ export class AppComponent implements OnInit, OnDestroy {
this.auth.update();
const rand = Math.random() * 100000 | 0;
this.client.subscribe(4, (data) => console.log("Request ack received", rand, data));
this.addSubscription(this.events.listen('logoff', (message) => {
localStorage.removeItem('jwt');
if (!document.location.href.includes('/login')) {
this.router.navigate(['/login?warning=' + message]);
}
}));
this.addSubscription(this.events.listen('login', (_) => {
if (document.location.href.includes('/login')) {
this.router.navigate(['/tts-login']);
}
}));
this.client.connect();
this.ngZone.runOutsideAngular(() => setInterval(() => this.client.heartbeat(), 15000));
}
ngOnDestroy() {
for (let s of this.subscriptions) {
s.unsubscribe();
}
}
private addSubscription(s: Subscription) {
this.subscriptions.push(s);
}
}

View File

@ -1,6 +1,5 @@
import { Injectable } from '@angular/core';
import { DatePipe } from '@angular/common';
import { Subscription } from 'rxjs';
import { HermesSocketService } from './hermes-socket.service';
import EventService from './shared/services/EventService';
@ -39,6 +38,15 @@ export class HermesClientService {
return this.listen();
}
public disconnect() {
if (!this.connected)
return;
this.socket.close();
this.connected = false;
this.events.emit('tts_logoff', null);
}
private send(op: number, data: any) {
if (op != 0)
console.log("TX:", data);
@ -61,6 +69,54 @@ export class HermesClientService {
});
}
public createPolicy(groupId: string, path: string, usage: number, timespan: number) {
if (!this.logged_in)
return;
this.send(3, {
request_id: null,
type: "create_policy",
data: {
groupId, path, count: usage, span: timespan
},
});
}
public deletePolicy(id: string) {
if (!this.logged_in)
return;
this.send(3, {
request_id: null,
type: "delete_policy",
data: {
id
},
});
}
public fetchPolicies() {
if (!this.logged_in)
return;
this.send(3, {
request_id: null,
type: "get_policies",
data: null,
});
}
public fetchPermissionsAndGroups() {
if (!this.logged_in)
return;
this.send(3, {
request_id: null,
type: "get_permissions",
data: null,
});
}
public heartbeat() {
const date = new Date()
this.send(0, {
@ -75,6 +131,19 @@ export class HermesClientService {
this.subscriptions[code].push(action);
}
public updatePolicy(id: string, groupId: string, path: string, usage: number, timespan: number) {
if (!this.logged_in)
return;
this.send(3, {
request_id: null,
type: "update_policy",
data: {
id, groupId, path, count: usage, span: timespan
},
});
}
private listen() {
return this.socket.subscribe({
next: (message: any) => {

View File

@ -25,7 +25,7 @@ export class HermesSocketService implements OnInit {
private getNewWebSocket() {
return webSocket({
url: WS_ENDPOINT
url: environment.WSS_ENDPOINT
});
}

View File

@ -7,6 +7,7 @@ import { MatInputModule } from '@angular/material/input';
import { Policy } from '../shared/models/policy';
import EventService from '../shared/services/EventService';
import { map, Observable, startWith } from 'rxjs';
import { HermesClientService } from '../hermes-client.service';
const Policies = [
{ path: "tts", description: "Anything to do with TTS" },
@ -48,7 +49,7 @@ export class PolicyAddFormComponent {
newPolicyName: string = '';
filteredPolicies: Observable<string[]>;
constructor(private events: EventService) {
constructor(private events: EventService, private hermes: HermesClientService) {
this.filteredPolicies = this.myControl.valueChanges.pipe(
startWith(''),
map(value => this._filter(value || '')),
@ -69,7 +70,7 @@ export class PolicyAddFormComponent {
}
addNewPolicy() {
console.log('new policy name given', this.newPolicyName)
this.events.emit('addPolicy', this.newPolicyName)
this.events.emit('addPolicy', this.newPolicyName);
this.newPolicyName = "";
}
}

View File

@ -6,26 +6,47 @@
<!-- Position Column -->
<ng-container matColumnDef="path">
<th mat-header-cell *matHeaderCellDef>Path</th>
<td mat-cell *matCellDef="let policy">
{{policy.path}}
</td>
</ng-container>
<ng-container matColumnDef="group">
<th mat-header-cell *matHeaderCellDef>Group</th>
<td mat-cell *matCellDef="let policy">
@if (policy.editing) {
<input type="text" [(ngModel)]="policy.path" />
<input type="text" [(ngModel)]="policy.temp_group_name" />
}
@if (!policy.editing) {
{{policy.path}}
{{groups[policy.group_id].name}}
}
</td>
</ng-container>
<!-- Name Column -->
<ng-container matColumnDef="usage">
<th mat-header-cell *matHeaderCellDef> Usage </th>
<td mat-cell *matCellDef="let policy"> {{policy.name}} </td>
<th mat-header-cell *matHeaderCellDef>Usage per span</th>
<td mat-cell *matCellDef="let policy">
@if (policy.editing) {
<input type="number" [(ngModel)]="policy.usage" (keypress)="($event.charCode >= 48 && $event.charCode < 58)" />
}
@if (!policy.editing) {
{{policy.usage}}
}
</td>
</ng-container>
<!-- Weight Column -->
<ng-container matColumnDef="span">
<th mat-header-cell *matHeaderCellDef> Span </th>
<td mat-cell *matCellDef="let policy"> {{policy.weight}} </td>
<th mat-header-cell *matHeaderCellDef>Span (ms)</th>
<td mat-cell *matCellDef="let policy">
@if (policy.editing) {
<input type="number" [(ngModel)]="policy.span" (keypress)="($event.charCode >= 48 && $event.charCode < 58)" />
}
@if (!policy.editing) {
{{policy.span}}
}
</td>
</ng-container>
<!-- Symbol Column -->

View File

@ -5,6 +5,7 @@ import EventService from '../shared/services/EventService';
import { Policy } from '../shared/models/policy';
import { Subscription } from 'rxjs';
import { FormsModule } from '@angular/forms';
import { HermesClientService } from '../hermes-client.service';
@Component({
selector: 'policy-table',
@ -15,23 +16,78 @@ import { FormsModule } from '@angular/forms';
})
export class PolicyTableComponent implements OnInit, OnDestroy {
@Input() policies: Policy[] = []
displayedColumns = ['path', 'usage', 'span', 'actions']
displayedColumns = ['path', 'group', 'usage', 'span', 'actions']
groups: { [id: string]: { id: string, name: string, priority: number } }
@ViewChild(MatTable) table: MatTable<Policy>;
private subscription: Subscription | undefined;
constructor(private events: EventService) {
constructor(private events: EventService, private hermes: HermesClientService) {
this.table = {} as MatTable<Policy>;
this.groups = {};
}
ngOnInit(): void {
this.subscription = this.events.listen('addPolicy', (payload) => {
console.log('adding policy', payload);
this.policies.push(new Policy(payload, 1, 5000, true, true));
console.log(this.policies);
if (!payload)
return;
if (this.policies.map(p => p.path).includes(payload)) {
return;
}
this.policies.push(new Policy("", "", payload, 1, 5000, "", true, true));
this.table.renderRows();
});
this.hermes.subscribe(4, (response: any) => {
console.log('request received: ', response);
if (response.request.type == "get_policies") {
for (let policy of response.data) {
this.policies.push(new Policy(policy.id, policy.group_id, policy.path, policy.usage, policy.span, "", false, false));
}
this.table.renderRows();
} else if (response.request.type == "create_policy") {
console.log("create policy", response);
const policy = this.policies.find(p => this.groups[response.data.group_id].name == p.temp_group_name && p.path == response.data.path);
if (policy == null) {
this.policies.push(new Policy(response.data.id, response.data.group_id, response.data.path, response.data.usage, response.data.span));
} else {
policy.id = response.data.id;
policy.group_id = response.data.group_id;
policy.editing = false;
policy.isNew = false;
}
this.table.renderRows();
} else if (response.request.type == "update_policy") {
console.log("update policy", response);
const policy = this.policies.find(p => p.id == response.data.id);
if (policy == null) {
this.policies.push(new Policy(response.data.id, response.data.group_id, response.data.path, response.data.usage, response.data.span));
} else {
policy.id = response.data.id;
policy.group_id = response.data.group_id;
policy.editing = false;
policy.isNew = false;
}
this.table.renderRows();
} else if (response.request.type == "delete_policy") {
console.log('delete policy', response.request.data.id);
const policy = this.policies.find(p => p.id == response.request.data.id);
if (!policy) {
console.log('Could not find the policy by id. Already deleted.');
return;
}
const index = this.policies.indexOf(policy);
if (index >= 0) {
this.policies.splice(index, 1);
this.table.renderRows();
}
} else if (response.request.type == "get_permissions") {
this.groups = Object.assign({}, ...response.data.groups.map((g: any) => ({ [g.id]: g })));
console.log(this.groups);
}
});
this.hermes.fetchPolicies();
this.hermes.fetchPermissionsAndGroups();
}
ngOnDestroy(): void {
@ -40,27 +96,83 @@ export class PolicyTableComponent implements OnInit, OnDestroy {
}
cancel(policy: Policy) {
if (!policy.editing)
return;
policy.path = policy.old_path ?? '';
policy.usage = policy.old_usage ?? 1;
policy.span = policy.old_span ?? 5000;
policy.old_path = undefined;
policy.old_span = undefined;
policy.old_usage = undefined;
policy.editing = false;
this.table.renderRows();
}
delete(policy: Policy) {
const index = this.policies.indexOf(policy);
if (index >= 0) {
this.policies.splice(index, 1);
this.table.renderRows();
}
this.hermes.deletePolicy(policy.id);
}
edit(policy: Policy) {
console.log('prior', policy.editing)
policy.old_path = policy.path;
policy.old_span = policy.span;
policy.old_usage = policy.usage;
policy.temp_group_name = this.groups[policy.group_id].name
policy.editing = true;
}
save(policy: Policy) {
policy.editing = false;
policy.isNew = false;
this.table.renderRows();
if (!policy.temp_group_name) {
console.log('group must be valid.');
return;
}
const group = Object.values(this.groups).find(g => g.name);
if (group == null) {
console.log('group does not exist.');
return;
}
if (isNaN(policy.usage)) {
console.log('usage must be a whole number.');
return;
}
if (policy.usage < 1 || policy.usage > 99) {
console.error('usage must be between 1 and 99.');
return;
}
if (policy.usage % 1.0 != 0) {
console.error('usage must be a whole number.');
return;
}
if (isNaN(policy.span)) {
console.log('span must be a whole number.');
return;
}
if (policy.span < 1000 || policy.span > 1800000) {
console.error('span must be between 1 and 1800000.');
return;
}
if (policy.span % 1.0 != 0) {
console.error('span must be a whole number.');
return;
}
let group_id = policy?.group_id;
for (let groupId in this.groups) {
if (this.groups[groupId].name == policy?.temp_group_name) {
group_id = groupId;
break;
}
}
if (policy?.temp_group_name != this.groups[group_id].name) {
console.log('no group found.');
return;
}
if (policy.isNew) {
this.hermes.createPolicy(group.id, policy.path, policy.usage, policy.span);
} else {
this.hermes.updatePolicy(policy.id, group.id, policy.path, policy.usage, policy.span);
}
}
}

View File

@ -4,7 +4,11 @@ export enum PolicyScope {
}
export class Policy {
constructor(public path: string, public usage: number, public span: number, public editing: boolean = false, public isNew: boolean = false) {
public old_path: string|undefined;
public old_usage: number|undefined;
public old_span: number|undefined;
constructor(public id: string, public group_id: string, public path: string, public usage: number, public span: number, public temp_group_name: string = "", public editing: boolean = false, public isNew: boolean = false) {
}
}

View File

@ -1,5 +1,6 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import EventService from '../EventService';
@Injectable({
providedIn: 'root'
@ -8,7 +9,7 @@ export class ApiAuthenticationService {
private authenticated: boolean;
private lastCheck: Date;
constructor(private http: HttpClient) {
constructor(private http: HttpClient, private events: EventService) {
this.authenticated = false;
this.lastCheck = new Date();
}
@ -36,7 +37,16 @@ export class ApiAuthenticationService {
}
private updateAuthenticated(value: boolean) {
const previous = this.authenticated;
this.authenticated = value;
this.lastCheck = new Date();
if (previous != value) {
if (value) {
this.events.emit('login', null);
} else {
this.events.emit('logoff', null);
}
}
}
}

View File

@ -32,7 +32,7 @@ export class TtsLoginComponent implements OnInit, OnDestroy {
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('jwt')
}
}).subscribe((data: any) => this.api_keys = data.map((d: any) => d.id))
}).subscribe((data: any) => this.api_keys = data.map((d: any) => d.id));
this.subscription = this.events.listen('tts_login_ack', _ => {
if (document.location.href.includes('/tts-login')) {