Admin UI initial #47
9 changed files with 105 additions and 45 deletions
|
@ -1,5 +1,10 @@
|
|||
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 { AuthService } from './login/auth.service';
|
||||
import { NgIconComponent, provideIcons } from '@ng-icons/core';
|
||||
|
|
|
@ -12,13 +12,17 @@ import { AuthGuard } from './auth.guard';
|
|||
|
||||
export const routes: Routes = [
|
||||
{ path: 'login', component: LoginComponent },
|
||||
{ path: '', canActivateChild: [AuthGuard], children: [
|
||||
{ path: '', component: HomeComponent, pathMatch: 'full' },
|
||||
{ path: 'talkgroups', component: TalkgroupsComponent },
|
||||
{ path: 'talkgroups/import', component: ImportComponent },
|
||||
{ path: 'talkgroups/:sys/:tg', component: TalkgroupRecordComponent },
|
||||
{ path: 'calls', component: CallsComponent },
|
||||
{ path: 'incidents', component: IncidentsComponent },
|
||||
{ path: 'alerts', component: AlertsComponent },
|
||||
]},
|
||||
{
|
||||
path: '',
|
||||
canActivateChild: [AuthGuard],
|
||||
children: [
|
||||
{ path: '', component: HomeComponent, pathMatch: 'full' },
|
||||
{ path: 'talkgroups', component: TalkgroupsComponent },
|
||||
{ path: 'talkgroups/import', component: ImportComponent },
|
||||
{ path: 'talkgroups/:sys/:tg', component: TalkgroupRecordComponent },
|
||||
{ path: 'calls', component: CallsComponent },
|
||||
{ path: 'incidents', component: IncidentsComponent },
|
||||
{ path: 'alerts', component: AlertsComponent },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
@ -1,12 +1,22 @@
|
|||
import { Router, CanActivateFn } from '@angular/router';
|
||||
import { AuthService } from './login/auth.service'
|
||||
import { inject } from '@angular/core';
|
||||
|
||||
export const AuthGuard: CanActivateFn = (route, state) => {
|
||||
const router: Router = inject(Router);
|
||||
const authSvc: AuthService = inject(AuthService);
|
||||
if (sessionStorage.getItem('jwt') == null) {
|
||||
let success = false;
|
||||
authSvc.refresh()
|
||||
.subscribe((event) => {
|
||||
if (event?.status == 200) {
|
||||
success = true;
|
||||
}
|
||||
});
|
||||
router.navigate(['/login']);
|
||||
return false;
|
||||
return success;
|
||||
} else {
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
return sessionStorage.getItem('jwt');
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ export interface System {
|
|||
}
|
||||
|
||||
export interface Metadata {
|
||||
encrypted: boolean|null;
|
||||
encrypted: boolean | null;
|
||||
}
|
||||
|
||||
export interface Talkgroup {
|
||||
|
@ -41,7 +41,7 @@ export interface Talkgroup {
|
|||
alpha_tag: string;
|
||||
tg_group: string;
|
||||
frequency: number;
|
||||
metadata: Metadata|null;
|
||||
metadata: Metadata | null;
|
||||
tags: string[];
|
||||
alert: boolean;
|
||||
system?: System;
|
||||
|
@ -52,7 +52,7 @@ export interface Talkgroup {
|
|||
|
||||
export interface TalkgroupUI extends Talkgroup {
|
||||
selected?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export interface TalkgroupUpdate {
|
||||
id: number;
|
||||
|
|
|
@ -1,22 +1,37 @@
|
|||
<div class="flex">
|
||||
<form [formGroup]="form" (ngSubmit)="submit()">
|
||||
<textarea
|
||||
id="contents"
|
||||
class="w-full textarea textarea-bordered"
|
||||
placeholder="Paste RadioReference page here"
|
||||
formControlName="contents"
|
||||
cols="40" rows="15"
|
||||
></textarea>
|
||||
<input type="number" class="input input-bordered" formControlName="systemID" id="systemID" />
|
||||
<textarea
|
||||
id="contents"
|
||||
class="w-full textarea textarea-bordered"
|
||||
placeholder="Paste RadioReference page here"
|
||||
formControlName="contents"
|
||||
cols="40"
|
||||
rows="15"
|
||||
></textarea>
|
||||
<input
|
||||
type="number"
|
||||
class="input input-bordered"
|
||||
formControlName="systemID"
|
||||
id="systemID"
|
||||
/>
|
||||
<input type="submit" class="btn btn-primary" value="Preview" />
|
||||
</form>
|
||||
<button class="btn btn-secondary" (click)="save()">Save</button>
|
||||
<button class="btn btn-secondary" (click)="save()">Save</button>
|
||||
</div>
|
||||
<div class="w-100 justify-center overflow-x-auto">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<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 ID</th>
|
||||
<th>Group</th>
|
||||
|
@ -29,7 +44,14 @@
|
|||
<tbody>
|
||||
@for (tg of tgs; track tg.id) {
|
||||
<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>{{ tg.system?.name }}</td>
|
||||
<td>{{ tg.system?.id }}</td>
|
||||
|
@ -37,7 +59,7 @@
|
|||
<td>{{ tg.alpha_tag }}</td>
|
||||
<td>{{ tg.name }}</td>
|
||||
<td>{{ tg.tgid }}</td>
|
||||
<td>{{ tg?.metadata?.encrypted ? 'E' : '' }}</td>
|
||||
<td>{{ tg?.metadata?.encrypted ? "E" : "" }}</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
import { Component, inject } from '@angular/core';
|
||||
import { TalkgroupService } from '../talkgroups.service';
|
||||
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 { Router, ActivatedRoute } from '@angular/router';
|
||||
import { catchError, of } from 'rxjs';
|
||||
|
@ -9,7 +14,7 @@ import { catchError, of } from 'rxjs';
|
|||
@Component({
|
||||
selector: 'app-import',
|
||||
standalone: true,
|
||||
imports: [CommonModule, ReactiveFormsModule, FormsModule ],
|
||||
imports: [CommonModule, ReactiveFormsModule, FormsModule],
|
||||
templateUrl: './import.component.html',
|
||||
styleUrl: './import.component.css',
|
||||
})
|
||||
|
@ -33,7 +38,8 @@ export class ImportComponent {
|
|||
submit() {
|
||||
let content = this.form.controls['contents'].value;
|
||||
let sysID = Number(this.form.controls['systemID'].value);
|
||||
this.tgService.importRR(sysID, content)
|
||||
this.tgService
|
||||
.importRR(sysID, content)
|
||||
.pipe(
|
||||
catchError(() => {
|
||||
return of(null);
|
||||
|
@ -46,24 +52,25 @@ export class ImportComponent {
|
|||
}
|
||||
|
||||
isAllSelected() {
|
||||
return this.tgs.every(_ => _.selected);
|
||||
return this.tgs.every((_) => _.selected);
|
||||
}
|
||||
|
||||
selectAllTGs(ev: any) {
|
||||
this.tgs.forEach(x => x.selected = ev.target.checked);
|
||||
this.tgs.forEach((x) => (x.selected = ev.target.checked));
|
||||
}
|
||||
|
||||
save() {
|
||||
let toImport: TalkgroupUpdate[] = [];
|
||||
let sysID = Number(this.form.controls['systemID'].value);
|
||||
this.tgs.forEach((x) => {
|
||||
if(x.selected) {
|
||||
if (x.selected) {
|
||||
let ct: TalkgroupUpdate = x;
|
||||
toImport.push(ct);
|
||||
}
|
||||
});
|
||||
this.tgService.putTalkgroups(sysID, toImport).
|
||||
pipe(
|
||||
this.tgService
|
||||
.putTalkgroups(sysID, toImport)
|
||||
.pipe(
|
||||
catchError(() => {
|
||||
return of(null);
|
||||
}),
|
||||
|
@ -71,7 +78,5 @@ export class ImportComponent {
|
|||
.subscribe((event) => {
|
||||
this.tgs = event!;
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<th>Sys ID</th>
|
||||
<th>Name</th>
|
||||
<th>TG ID</th>
|
||||
<th>Learned</th>
|
||||
<th>Learned</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -18,7 +18,7 @@
|
|||
<td>{{ tg.system?.id }}</td>
|
||||
<td>{{ tg.name }}</td>
|
||||
<td>{{ tg.tgid }}</td>
|
||||
<td>{{ tg?.learned ? 'Y' : '' }}</td>
|
||||
<td>{{ tg?.learned ? "Y" : "" }}</td>
|
||||
<td>
|
||||
<a routerLink="/talkgroups/{{ tg.system?.id }}/{{ tg.tgid }}"
|
||||
><ng-icon name="ionCreateOutline"></ng-icon
|
||||
|
|
|
@ -19,8 +19,11 @@ export class TalkgroupService {
|
|||
}
|
||||
|
||||
importRR(sysID: number, content: string): Observable<Talkgroup[]> {
|
||||
return this.http.post<Talkgroup[]>('/api/talkgroup/import',
|
||||
{systemID: sysID, type: 'radioreference', body: content});
|
||||
return this.http.post<Talkgroup[]>('/api/talkgroup/import', {
|
||||
systemID: sysID,
|
||||
type: 'radioreference',
|
||||
body: content,
|
||||
});
|
||||
}
|
||||
|
||||
putTalkgroup(tu: TalkgroupUpdate): Observable<Talkgroup> {
|
||||
|
@ -30,10 +33,10 @@ export class TalkgroupService {
|
|||
);
|
||||
}
|
||||
|
||||
putTalkgroups(sysID: Number, tgs: TalkgroupUpdate[]): Observable<Talkgroup[]> {
|
||||
return this.http.put<Talkgroup[]>(
|
||||
`/api/talkgroup/${sysID}`,
|
||||
tgs,
|
||||
);
|
||||
putTalkgroups(
|
||||
sysID: Number,
|
||||
tgs: TalkgroupUpdate[],
|
||||
): Observable<Talkgroup[]> {
|
||||
return this.http.put<Talkgroup[]>(`/api/talkgroup/${sysID}`, tgs);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue