Admin UI initial #47
9 changed files with 105 additions and 45 deletions
|
@ -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';
|
||||||
|
|
|
@ -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 },
|
||||||
|
],
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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');
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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!;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue