Admin UI initial #47

Merged
amigan merged 12 commits from adminui into trunk 2024-11-22 17:07:14 -05:00
9 changed files with 105 additions and 45 deletions
Showing only changes of commit 55fdeaf086 - Show all commits

View file

@ -1,5 +1,10 @@
import { Component, inject } from '@angular/core'; import { Component, inject } from '@angular/core';
import { RouterModule, RouterOutlet, RouterLink, RouterLinkActive } from '@angular/router'; import {
RouterModule,
RouterOutlet,
RouterLink,
RouterLinkActive,
} from '@angular/router';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { AuthService } from './login/auth.service'; import { AuthService } from './login/auth.service';
import { NgIconComponent, provideIcons } from '@ng-icons/core'; import { NgIconComponent, provideIcons } from '@ng-icons/core';

View file

@ -12,13 +12,17 @@ import { AuthGuard } from './auth.guard';
export const routes: Routes = [ export const routes: Routes = [
{ path: 'login', component: LoginComponent }, { path: 'login', component: LoginComponent },
{ path: '', canActivateChild: [AuthGuard], children: [ {
{ path: '', component: HomeComponent, pathMatch: 'full' }, path: '',
{ path: 'talkgroups', component: TalkgroupsComponent }, canActivateChild: [AuthGuard],
{ path: 'talkgroups/import', component: ImportComponent }, children: [
{ path: 'talkgroups/:sys/:tg', component: TalkgroupRecordComponent }, { path: '', component: HomeComponent, pathMatch: 'full' },
{ path: 'calls', component: CallsComponent }, { path: 'talkgroups', component: TalkgroupsComponent },
{ path: 'incidents', component: IncidentsComponent }, { path: 'talkgroups/import', component: ImportComponent },
{ path: 'alerts', component: AlertsComponent }, { path: 'talkgroups/:sys/:tg', component: TalkgroupRecordComponent },
]}, { path: 'calls', component: CallsComponent },
{ path: 'incidents', component: IncidentsComponent },
{ path: 'alerts', component: AlertsComponent },
],
},
]; ];

View file

@ -1,12 +1,22 @@
import { Router, CanActivateFn } from '@angular/router'; import { Router, CanActivateFn } from '@angular/router';
import { AuthService } from './login/auth.service'
import { inject } from '@angular/core'; import { inject } from '@angular/core';
export const AuthGuard: CanActivateFn = (route, state) => { export const AuthGuard: CanActivateFn = (route, state) => {
const router: Router = inject(Router); const router: Router = inject(Router);
const authSvc: AuthService = inject(AuthService);
if (sessionStorage.getItem('jwt') == null) { if (sessionStorage.getItem('jwt') == null) {
let success = false;
authSvc.refresh()
.subscribe((event) => {
if (event?.status == 200) {
success = true;
}
});
router.navigate(['/login']); router.navigate(['/login']);
return false; return success;
} else { } else {
return true; return true;
} }
}; };

View file

@ -41,6 +41,17 @@ export class AuthService {
); );
} }
refresh(): Observable<HttpResponse<Jwt>> {
return this.http.get<Jwt>('/api/refresh', { withCredentials: true, observe: 'response' }).pipe(
tap((event) => {
if (event.status == 200) {
sessionStorage.setItem('jwt', event.body?.jwt.toString() ?? '');
this.loggedIn = true;
}
}),
);
}
getToken(): string | null { getToken(): string | null {
return sessionStorage.getItem('jwt'); return sessionStorage.getItem('jwt');
} }

View file

@ -30,7 +30,7 @@ export interface System {
} }
export interface Metadata { export interface Metadata {
encrypted: boolean|null; encrypted: boolean | null;
} }
export interface Talkgroup { export interface Talkgroup {
@ -41,7 +41,7 @@ export interface Talkgroup {
alpha_tag: string; alpha_tag: string;
tg_group: string; tg_group: string;
frequency: number; frequency: number;
metadata: Metadata|null; metadata: Metadata | null;
tags: string[]; tags: string[];
alert: boolean; alert: boolean;
system?: System; system?: System;
@ -52,7 +52,7 @@ export interface Talkgroup {
export interface TalkgroupUI extends Talkgroup { export interface TalkgroupUI extends Talkgroup {
selected?: boolean; selected?: boolean;
}; }
export interface TalkgroupUpdate { export interface TalkgroupUpdate {
id: number; id: number;

View file

@ -1,22 +1,37 @@
<div class="flex"> <div class="flex">
<form [formGroup]="form" (ngSubmit)="submit()"> <form [formGroup]="form" (ngSubmit)="submit()">
<textarea <textarea
id="contents" id="contents"
class="w-full textarea textarea-bordered" class="w-full textarea textarea-bordered"
placeholder="Paste RadioReference page here" placeholder="Paste RadioReference page here"
formControlName="contents" formControlName="contents"
cols="40" rows="15" cols="40"
></textarea> rows="15"
<input type="number" class="input input-bordered" formControlName="systemID" id="systemID" /> ></textarea>
<input
type="number"
class="input input-bordered"
formControlName="systemID"
id="systemID"
/>
<input type="submit" class="btn btn-primary" value="Preview" /> <input type="submit" class="btn btn-primary" value="Preview" />
</form> </form>
<button class="btn btn-secondary" (click)="save()">Save</button> <button class="btn btn-secondary" (click)="save()">Save</button>
</div> </div>
<div class="w-100 justify-center overflow-x-auto"> <div class="w-100 justify-center overflow-x-auto">
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th><input type="checkbox" class="checkbox" [checked]="isAllSelected()" (change)="selectAllTGs($event)" [(ngModel)]="selectAll" name="selectAll" /></th> <th>
<input
type="checkbox"
class="checkbox"
[checked]="isAllSelected()"
(change)="selectAllTGs($event)"
[(ngModel)]="selectAll"
name="selectAll"
/>
</th>
<th>Sys</th> <th>Sys</th>
<th>Sys ID</th> <th>Sys ID</th>
<th>Group</th> <th>Group</th>
@ -29,7 +44,14 @@
<tbody> <tbody>
@for (tg of tgs; track tg.id) { @for (tg of tgs; track tg.id) {
<tr> <tr>
<td><input type="checkbox" class="checkbox" [(ngModel)]="tg.selected" value="{{tg.name}}" (change)="isAllSelected()"> <td>
<input
type="checkbox"
class="checkbox"
[(ngModel)]="tg.selected"
value="{{ tg.name }}"
(change)="isAllSelected()"
/>
</td> </td>
<td>{{ tg.system?.name }}</td> <td>{{ tg.system?.name }}</td>
<td>{{ tg.system?.id }}</td> <td>{{ tg.system?.id }}</td>
@ -37,7 +59,7 @@
<td>{{ tg.alpha_tag }}</td> <td>{{ tg.alpha_tag }}</td>
<td>{{ tg.name }}</td> <td>{{ tg.name }}</td>
<td>{{ tg.tgid }}</td> <td>{{ tg.tgid }}</td>
<td>{{ tg?.metadata?.encrypted ? 'E' : '' }}</td> <td>{{ tg?.metadata?.encrypted ? "E" : "" }}</td>
</tr> </tr>
} }
</tbody> </tbody>

View file

@ -1,7 +1,12 @@
import { Component, inject } from '@angular/core'; import { Component, inject } from '@angular/core';
import { TalkgroupService } from '../talkgroups.service'; import { TalkgroupService } from '../talkgroups.service';
import { Talkgroup, TalkgroupUI, TalkgroupUpdate } from '../../talkgroup'; import { Talkgroup, TalkgroupUI, TalkgroupUpdate } from '../../talkgroup';
import { FormGroup, FormControl, ReactiveFormsModule, FormsModule } from '@angular/forms'; import {
FormGroup,
FormControl,
ReactiveFormsModule,
FormsModule,
} from '@angular/forms';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Router, ActivatedRoute } from '@angular/router'; import { Router, ActivatedRoute } from '@angular/router';
import { catchError, of } from 'rxjs'; import { catchError, of } from 'rxjs';
@ -9,7 +14,7 @@ import { catchError, of } from 'rxjs';
@Component({ @Component({
selector: 'app-import', selector: 'app-import',
standalone: true, standalone: true,
imports: [CommonModule, ReactiveFormsModule, FormsModule ], imports: [CommonModule, ReactiveFormsModule, FormsModule],
templateUrl: './import.component.html', templateUrl: './import.component.html',
styleUrl: './import.component.css', styleUrl: './import.component.css',
}) })
@ -33,7 +38,8 @@ export class ImportComponent {
submit() { submit() {
let content = this.form.controls['contents'].value; let content = this.form.controls['contents'].value;
let sysID = Number(this.form.controls['systemID'].value); let sysID = Number(this.form.controls['systemID'].value);
this.tgService.importRR(sysID, content) this.tgService
.importRR(sysID, content)
.pipe( .pipe(
catchError(() => { catchError(() => {
return of(null); return of(null);
@ -46,24 +52,25 @@ export class ImportComponent {
} }
isAllSelected() { isAllSelected() {
return this.tgs.every(_ => _.selected); return this.tgs.every((_) => _.selected);
} }
selectAllTGs(ev: any) { selectAllTGs(ev: any) {
this.tgs.forEach(x => x.selected = ev.target.checked); this.tgs.forEach((x) => (x.selected = ev.target.checked));
} }
save() { save() {
let toImport: TalkgroupUpdate[] = []; let toImport: TalkgroupUpdate[] = [];
let sysID = Number(this.form.controls['systemID'].value); let sysID = Number(this.form.controls['systemID'].value);
this.tgs.forEach((x) => { this.tgs.forEach((x) => {
if(x.selected) { if (x.selected) {
let ct: TalkgroupUpdate = x; let ct: TalkgroupUpdate = x;
toImport.push(ct); toImport.push(ct);
} }
}); });
this.tgService.putTalkgroups(sysID, toImport). this.tgService
pipe( .putTalkgroups(sysID, toImport)
.pipe(
catchError(() => { catchError(() => {
return of(null); return of(null);
}), }),
@ -71,7 +78,5 @@ export class ImportComponent {
.subscribe((event) => { .subscribe((event) => {
this.tgs = event!; this.tgs = event!;
}); });
} }
} }

View file

@ -7,7 +7,7 @@
<th>Sys ID</th> <th>Sys ID</th>
<th>Name</th> <th>Name</th>
<th>TG ID</th> <th>TG ID</th>
<th>Learned</th> <th>Learned</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
@ -18,7 +18,7 @@
<td>{{ tg.system?.id }}</td> <td>{{ tg.system?.id }}</td>
<td>{{ tg.name }}</td> <td>{{ tg.name }}</td>
<td>{{ tg.tgid }}</td> <td>{{ tg.tgid }}</td>
<td>{{ tg?.learned ? 'Y' : '' }}</td> <td>{{ tg?.learned ? "Y" : "" }}</td>
<td> <td>
<a routerLink="/talkgroups/{{ tg.system?.id }}/{{ tg.tgid }}" <a routerLink="/talkgroups/{{ tg.system?.id }}/{{ tg.tgid }}"
><ng-icon name="ionCreateOutline"></ng-icon ><ng-icon name="ionCreateOutline"></ng-icon

View file

@ -19,8 +19,11 @@ export class TalkgroupService {
} }
importRR(sysID: number, content: string): Observable<Talkgroup[]> { importRR(sysID: number, content: string): Observable<Talkgroup[]> {
return this.http.post<Talkgroup[]>('/api/talkgroup/import', return this.http.post<Talkgroup[]>('/api/talkgroup/import', {
{systemID: sysID, type: 'radioreference', body: content}); systemID: sysID,
type: 'radioreference',
body: content,
});
} }
putTalkgroup(tu: TalkgroupUpdate): Observable<Talkgroup> { putTalkgroup(tu: TalkgroupUpdate): Observable<Talkgroup> {
@ -30,10 +33,10 @@ export class TalkgroupService {
); );
} }
putTalkgroups(sysID: Number, tgs: TalkgroupUpdate[]): Observable<Talkgroup[]> { putTalkgroups(
return this.http.put<Talkgroup[]>( sysID: Number,
`/api/talkgroup/${sysID}`, tgs: TalkgroupUpdate[],
tgs, ): Observable<Talkgroup[]> {
); return this.http.put<Talkgroup[]>(`/api/talkgroup/${sysID}`, tgs);
} }
} }