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,7 +12,10 @@ 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: '',
canActivateChild: [AuthGuard],
children: [
{ path: '', component: HomeComponent, pathMatch: 'full' }, { path: '', component: HomeComponent, pathMatch: 'full' },
{ path: 'talkgroups', component: TalkgroupsComponent }, { path: 'talkgroups', component: TalkgroupsComponent },
{ path: 'talkgroups/import', component: ImportComponent }, { path: 'talkgroups/import', component: ImportComponent },
@ -20,5 +23,6 @@ export const routes: Routes = [
{ path: 'calls', component: CallsComponent }, { path: 'calls', component: CallsComponent },
{ path: 'incidents', component: IncidentsComponent }, { path: 'incidents', component: IncidentsComponent },
{ path: 'alerts', component: AlertsComponent }, { 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

@ -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

@ -5,9 +5,15 @@
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"
rows="15"
></textarea> ></textarea>
<input type="number" class="input input-bordered" formControlName="systemID" id="systemID" /> <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>
@ -16,7 +22,16 @@
<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';
@ -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,11 +52,11 @@ 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() {
@ -62,8 +68,9 @@ export class ImportComponent {
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

@ -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);
} }
} }