kindof working
This commit is contained in:
parent
67905c92be
commit
de361cf23c
22 changed files with 320 additions and 88 deletions
|
@ -8,9 +8,8 @@ describe('AlertsComponent', () => {
|
|||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AlertsComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
imports: [AlertsComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(AlertsComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
|
|
@ -5,8 +5,6 @@ import { Component } from '@angular/core';
|
|||
standalone: true,
|
||||
imports: [],
|
||||
templateUrl: './alerts.component.html',
|
||||
styleUrl: './alerts.component.css'
|
||||
styleUrl: './alerts.component.css',
|
||||
})
|
||||
export class AlertsComponent {
|
||||
|
||||
}
|
||||
export class AlertsComponent {}
|
||||
|
|
|
@ -1,12 +1,26 @@
|
|||
<!-- Navbar -->
|
||||
<nav class="navbar justify-between bg-base-300">
|
||||
<!-- Logo -->
|
||||
<a class="btn btn-ghost text-lg">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M15.362 5.214A8.252 8.252 0 0 1 12 21 8.25 8.25 0 0 1 6.038 7.047 8.287 8.287 0 0 0 9 9.601a8.983 8.983 0 0 1 3.361-6.867 8.21 8.21 0 0 0 3 2.48Z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 18a3.75 3.75 0 0 0 .495-7.468 5.99 5.99 0 0 0-1.925 3.547 5.975 5.975 0 0 1-2.133-1.001A3.75 3.75 0 0 0 12 18Z" />
|
||||
<a class="btn btn-ghost text-lg btn-square">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="size-6"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M15.362 5.214A8.252 8.252 0 0 1 12 21 8.25 8.25 0 0 1 6.038 7.047 8.287 8.287 0 0 0 9 9.601a8.983 8.983 0 0 1 3.361-6.867 8.21 8.21 0 0 0 3 2.48Z"
|
||||
/>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M12 18a3.75 3.75 0 0 0 .495-7.468 5.99 5.99 0 0 0-1.925 3.547 5.975 5.975 0 0 1-2.133-1.001A3.75 3.75 0 0 0 12 18Z"
|
||||
/>
|
||||
</svg>
|
||||
|
||||
</a>
|
||||
|
||||
<!-- Menu for mobile -->
|
||||
|
@ -15,7 +29,10 @@
|
|||
<i class="fa-solid fa-bars text-lg"></i>
|
||||
</button>
|
||||
|
||||
<ul tabindex="0" class="dropdown-content menu z-[1] bg-base-200 p-6 rounded-box shadow w-56 gap-2">
|
||||
<ul
|
||||
tabindex="0"
|
||||
class="dropdown-content menu z-[1] bg-base-200 p-6 rounded-box shadow w-56 gap-2"
|
||||
>
|
||||
<li><a>Item</a></li>
|
||||
<a class="btn btn-sm btn-primary">Do</a>
|
||||
</ul>
|
||||
|
@ -24,31 +41,60 @@
|
|||
<!-- Menu for desktop -->
|
||||
<ul class="hidden menu sm:menu-horizontal gap-2">
|
||||
<li><a>Item</a></li>
|
||||
<a class="btn btn-sm btn-primary">Do</a>
|
||||
@if (auth.loggedIn) {
|
||||
<a (click)="logout()" class="btn btn-sm btn-primary">Logout</a>
|
||||
}
|
||||
</ul>
|
||||
</nav>
|
||||
<div class="flex overflow-hidden relative">
|
||||
<!-- Sidebar -->
|
||||
<aside class="h-screen sticky top-0 flex flex-col overflow-y-auto gap-2 py-6 px-2 bg-base-200">
|
||||
<a class="btn btn-square btn-ghost btn-secondary text-xl" title="Home" routerLink="/home" routerLinkActive="btn-active">
|
||||
<aside
|
||||
class="h-screen sticky top-0 flex flex-col overflow-y-auto gap-2 py-6 px-2 bg-base-200"
|
||||
>
|
||||
<a
|
||||
class="btn btn-square btn-ghost btn-secondary text-xl"
|
||||
title="Home"
|
||||
routerLink="/home"
|
||||
routerLinkActive="btn-active"
|
||||
>
|
||||
<ng-icon name="ionHome"></ng-icon>
|
||||
</a>
|
||||
|
||||
<a routerLink="/calls" routerLinkActive="btn-active" class="btn btn-ghost btn-secondary text-xl" title="Calls">
|
||||
<a
|
||||
routerLink="/calls"
|
||||
routerLinkActive="btn-active"
|
||||
class="btn btn-ghost btn-secondary text-xl"
|
||||
title="Calls"
|
||||
>
|
||||
<ng-icon name="ionMegaphoneOutline"></ng-icon>
|
||||
</a>
|
||||
|
||||
<a class="btn btn-square btn-ghost text-xl" title="Talkgroups" routerLink="/talkgroups" routerLinkActive="btn-active">
|
||||
<a
|
||||
class="btn btn-square btn-ghost text-xl"
|
||||
title="Talkgroups"
|
||||
routerLink="/talkgroups"
|
||||
routerLinkActive="btn-active"
|
||||
>
|
||||
<ng-icon name="ionChatbubbles"></ng-icon>
|
||||
</a>
|
||||
|
||||
<div class="divider my-0"></div>
|
||||
|
||||
<a class="btn btn-square btn-ghost text-xl" title="Incidents" routerLink="/incidents" routerLinkActive="btn-active">
|
||||
<a
|
||||
class="btn btn-square btn-ghost text-xl"
|
||||
title="Incidents"
|
||||
routerLink="/incidents"
|
||||
routerLinkActive="btn-active"
|
||||
>
|
||||
<ng-icon name="ionNewspaperOutline"></ng-icon>
|
||||
</a>
|
||||
|
||||
<a class="btn btn-square btn-ghost text-xl" title="Alerts" routerLink="/alerts" routerLinkActive="btn-active">
|
||||
<a
|
||||
class="btn btn-square btn-ghost text-xl"
|
||||
title="Alerts"
|
||||
routerLink="/alerts"
|
||||
routerLinkActive="btn-active"
|
||||
>
|
||||
<ng-icon name="ionAlertCircleOutline"></ng-icon>
|
||||
</a>
|
||||
<div class="divider my-0"></div>
|
||||
|
@ -57,7 +103,7 @@
|
|||
<ng-icon name="ionRadioOutline"></ng-icon>
|
||||
</a>
|
||||
</aside>
|
||||
<div class="container mx-auto px-4">
|
||||
<div class="container mx-auto px-4 flex overflow-auto">
|
||||
<router-outlet />
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -3,17 +3,47 @@ import { RouterModule, RouterOutlet, RouterLink } from '@angular/router';
|
|||
import { CommonModule } from '@angular/common';
|
||||
import { AuthService } from './login/auth.service';
|
||||
import { NgIconComponent, provideIcons } from '@ng-icons/core';
|
||||
import { ionMenuOutline, ionChatbubbles, ionNewspaperOutline, ionAlertCircleOutline, ionRadioOutline, ionHome, ionMegaphoneOutline } from '@ng-icons/ionicons';
|
||||
import {
|
||||
ionMenuOutline,
|
||||
ionChatbubbles,
|
||||
ionNewspaperOutline,
|
||||
ionAlertCircleOutline,
|
||||
ionRadioOutline,
|
||||
ionHome,
|
||||
ionMegaphoneOutline,
|
||||
ionCreateOutline,
|
||||
} from '@ng-icons/ionicons';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
standalone: true,
|
||||
imports: [CommonModule, RouterOutlet, RouterModule, RouterLink, NgIconComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterOutlet,
|
||||
RouterModule,
|
||||
RouterLink,
|
||||
NgIconComponent,
|
||||
],
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.css',
|
||||
providers: [ provideIcons({ ionMenuOutline, ionChatbubbles, ionNewspaperOutline, ionAlertCircleOutline, ionRadioOutline, ionHome, ionMegaphoneOutline })],
|
||||
providers: [
|
||||
provideIcons({
|
||||
ionMenuOutline,
|
||||
ionChatbubbles,
|
||||
ionNewspaperOutline,
|
||||
ionAlertCircleOutline,
|
||||
ionRadioOutline,
|
||||
ionHome,
|
||||
ionMegaphoneOutline,
|
||||
ionCreateOutline,
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class AppComponent {
|
||||
auth: AuthService = inject(AuthService);
|
||||
title = 'admin';
|
||||
|
||||
logout() {
|
||||
this.auth.logout();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,8 @@ import {
|
|||
withInterceptors,
|
||||
} from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { isDevMode } from '@angular/core';
|
||||
import { isDevMode, inject } from '@angular/core';
|
||||
import { AuthService } from './login/auth.service';
|
||||
|
||||
import { routes } from './app.routes';
|
||||
import { provideHttpClient } from '@angular/common/http';
|
||||
|
@ -26,10 +27,28 @@ export function apiBaseInterceptor(
|
|||
return next(apiReq);
|
||||
}
|
||||
|
||||
export function authIntercept(
|
||||
req: HttpRequest<unknown>,
|
||||
next: HttpHandlerFn,
|
||||
): Observable<HttpEvent<unknown>> {
|
||||
let authSvc: AuthService = inject(AuthService);
|
||||
if (authSvc.loggedIn) {
|
||||
req = req.clone({
|
||||
setHeaders: {
|
||||
'Content-Type': 'application/json; charset=utf-8',
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${authSvc.getToken()}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return next(req);
|
||||
}
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideZoneChangeDetection({ eventCoalescing: true }),
|
||||
provideRouter(routes),
|
||||
provideHttpClient(withInterceptors([apiBaseInterceptor])),
|
||||
provideHttpClient(withInterceptors([apiBaseInterceptor, authIntercept])),
|
||||
],
|
||||
};
|
||||
|
|
|
@ -8,9 +8,8 @@ describe('CallsComponent', () => {
|
|||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [CallsComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
imports: [CallsComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(CallsComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
|
|
@ -5,8 +5,6 @@ import { Component } from '@angular/core';
|
|||
standalone: true,
|
||||
imports: [],
|
||||
templateUrl: './calls.component.html',
|
||||
styleUrl: './calls.component.css'
|
||||
styleUrl: './calls.component.css',
|
||||
})
|
||||
export class CallsComponent {
|
||||
|
||||
}
|
||||
export class CallsComponent {}
|
||||
|
|
|
@ -1,3 +1 @@
|
|||
<p>
|
||||
This will be a dashboard someday.
|
||||
</p>
|
||||
<p>This will be a dashboard someday.</p>
|
||||
|
|
|
@ -7,5 +7,4 @@ import { Component } from '@angular/core';
|
|||
templateUrl: './home.component.html',
|
||||
styleUrl: './home.component.css',
|
||||
})
|
||||
export class HomeComponent {
|
||||
}
|
||||
export class HomeComponent {}
|
||||
|
|
|
@ -8,9 +8,8 @@ describe('IncidentsComponent', () => {
|
|||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [IncidentsComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
imports: [IncidentsComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(IncidentsComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
|
|
@ -5,8 +5,6 @@ import { Component } from '@angular/core';
|
|||
standalone: true,
|
||||
imports: [],
|
||||
templateUrl: './incidents.component.html',
|
||||
styleUrl: './incidents.component.css'
|
||||
styleUrl: './incidents.component.css',
|
||||
})
|
||||
export class IncidentsComponent {
|
||||
|
||||
}
|
||||
export class IncidentsComponent {}
|
||||
|
|
|
@ -41,4 +41,14 @@ export class AuthService {
|
|||
}),
|
||||
);
|
||||
}
|
||||
|
||||
getToken(): string | null {
|
||||
return sessionStorage.getItem('jwt');
|
||||
}
|
||||
|
||||
logout() {
|
||||
sessionStorage.removeItem('jwt');
|
||||
this.loggedIn = false;
|
||||
this._router.navigateByUrl('/login');
|
||||
}
|
||||
}
|
||||
|
|
30
client/admin/src/app/talkgroup.ts
Normal file
30
client/admin/src/app/talkgroup.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
export interface TGID {
|
||||
sys: number;
|
||||
tg: number;
|
||||
}
|
||||
|
||||
export interface AlertRule {
|
||||
times: string[];
|
||||
mult: number;
|
||||
}
|
||||
|
||||
export interface System {
|
||||
id: number;
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface Talkgroup {
|
||||
id: number;
|
||||
system_id: number;
|
||||
tgid: number;
|
||||
name: string;
|
||||
alpha_tag: string;
|
||||
tg_group: string;
|
||||
frequency: number;
|
||||
metadata: Object;
|
||||
tags: string[];
|
||||
alert: boolean;
|
||||
alert_config: Map<TGID, AlertRule[]>;
|
||||
system: System;
|
||||
weight: number;
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
<p>TG comp</p>
|
|
@ -0,0 +1,22 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TalkgroupRecordComponent } from './talkgroup-record.component';
|
||||
|
||||
describe('TalkgroupRecordComponent', () => {
|
||||
let component: TalkgroupRecordComponent;
|
||||
let fixture: ComponentFixture<TalkgroupRecordComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [TalkgroupRecordComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TalkgroupRecordComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { Talkgroup } from '../../talkgroup';
|
||||
|
||||
@Component({
|
||||
selector: 'talkgroup-record',
|
||||
standalone: true,
|
||||
imports: [],
|
||||
templateUrl: './talkgroup-record.component.html',
|
||||
styleUrl: './talkgroup-record.component.css',
|
||||
})
|
||||
export class TalkgroupRecordComponent {
|
||||
tg: Talkgroup;
|
||||
constructor(tg: Talkgroup) {
|
||||
this.tg = tg;
|
||||
}
|
||||
}
|
|
@ -1 +1,26 @@
|
|||
<p>talkgroups works!</p>
|
||||
<div class="w-3/5 justify-center overflow-x-auto">
|
||||
<table class="table">
|
||||
<!-- head -->
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Sys</th>
|
||||
<th>Sys ID</th>
|
||||
<th>Name</th>
|
||||
<th>TG ID</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<!-- row 1 -->
|
||||
@for (tg of tgs; track tg.id) {
|
||||
<tr>
|
||||
<td>{{ tg.system.name }}</td>
|
||||
<td>{{ tg.system.id }}</td>
|
||||
<td>{{ tg.name }}</td>
|
||||
<td>{{ tg.tgid }}</td>
|
||||
<td><ng-icon name="ionCreateOutline"></ng-icon></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
@ -8,9 +8,8 @@ describe('TalkgroupsComponent', () => {
|
|||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [TalkgroupsComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
imports: [TalkgroupsComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TalkgroupsComponent);
|
||||
component = fixture.componentInstance;
|
||||
|
|
|
@ -1,12 +1,26 @@
|
|||
import { Component } from '@angular/core';
|
||||
import { Component, inject } from '@angular/core';
|
||||
import { TalkgroupService } from './talkgroups.service';
|
||||
import { Talkgroup } from '../talkgroup';
|
||||
import { NgIconComponent, provideIcons } from '@ng-icons/core';
|
||||
import { ionCreateOutline } from '@ng-icons/ionicons';
|
||||
|
||||
@Component({
|
||||
selector: 'app-talkgroups',
|
||||
selector: 'talkgroups',
|
||||
standalone: true,
|
||||
imports: [],
|
||||
imports: [NgIconComponent],
|
||||
templateUrl: './talkgroups.component.html',
|
||||
styleUrl: './talkgroups.component.css'
|
||||
styleUrl: './talkgroups.component.css',
|
||||
providers: [provideIcons({ ionCreateOutline })],
|
||||
})
|
||||
export class TalkgroupsComponent {
|
||||
tgs: Talkgroup[] = [];
|
||||
tgService: TalkgroupService = inject(TalkgroupService);
|
||||
|
||||
ngOnInit() {
|
||||
this.getTalkgroups();
|
||||
}
|
||||
|
||||
getTalkgroups() {
|
||||
this.tgService.getTalkgroups().subscribe((tgs) => (this.tgs = tgs));
|
||||
}
|
||||
}
|
||||
|
|
16
client/admin/src/app/talkgroups/talkgroups.service.spec.ts
Normal file
16
client/admin/src/app/talkgroups/talkgroups.service.spec.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { APIService } from './api.service';
|
||||
|
||||
describe('APIService', () => {
|
||||
let service: APIService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(APIService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
16
client/admin/src/app/talkgroups/talkgroups.service.ts
Normal file
16
client/admin/src/app/talkgroups/talkgroups.service.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import { Observable } from 'rxjs';
|
||||
import { Talkgroup } from '../talkgroup';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class TalkgroupService {
|
||||
loggedIn: boolean = false;
|
||||
constructor(private http: HttpClient) {}
|
||||
|
||||
getTalkgroups(): Observable<Talkgroup[]> {
|
||||
return this.http.get<Talkgroup[]>('/api/talkgroup/');
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue