kindof working

This commit is contained in:
Daniel 2024-11-05 10:05:45 -05:00
parent c891a94c7b
commit 6e0aade9d2
22 changed files with 320 additions and 88 deletions

View file

@ -8,9 +8,8 @@ describe('AlertsComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [AlertsComponent] imports: [AlertsComponent],
}) }).compileComponents();
.compileComponents();
fixture = TestBed.createComponent(AlertsComponent); fixture = TestBed.createComponent(AlertsComponent);
component = fixture.componentInstance; component = fixture.componentInstance;

View file

@ -5,8 +5,6 @@ import { Component } from '@angular/core';
standalone: true, standalone: true,
imports: [], imports: [],
templateUrl: './alerts.component.html', templateUrl: './alerts.component.html',
styleUrl: './alerts.component.css' styleUrl: './alerts.component.css',
}) })
export class AlertsComponent { export class AlertsComponent {}
}

View file

@ -1,63 +1,109 @@
<!-- Navbar --> <!-- Navbar -->
<nav class="navbar justify-between bg-base-300"> <nav class="navbar justify-between bg-base-300">
<!-- Logo --> <!-- Logo -->
<a class="btn btn-ghost text-lg"> <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"> <svg
<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" /> xmlns="http://www.w3.org/2000/svg"
<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" /> fill="none"
</svg> 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>
</a> <!-- Menu for mobile -->
<div class="dropdown dropdown-end sm:hidden">
<button class="btn btn-ghost">
<i class="fa-solid fa-bars text-lg"></i>
</button>
<!-- Menu for mobile --> <ul
<div class="dropdown dropdown-end sm:hidden"> tabindex="0"
<button class="btn btn-ghost"> class="dropdown-content menu z-[1] bg-base-200 p-6 rounded-box shadow w-56 gap-2"
<i class="fa-solid fa-bars text-lg"></i> >
</button> <li><a>Item</a></li>
<a class="btn btn-sm btn-primary">Do</a>
<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>
</div>
<!-- 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>
</ul> </ul>
</div>
<!-- Menu for desktop -->
<ul class="hidden menu sm:menu-horizontal gap-2">
<li><a>Item</a></li>
@if (auth.loggedIn) {
<a (click)="logout()" class="btn btn-sm btn-primary">Logout</a>
}
</ul>
</nav> </nav>
<div class="flex overflow-hidden relative"> <div class="flex overflow-hidden relative">
<!-- Sidebar --> <!-- Sidebar -->
<aside class="h-screen sticky top-0 flex flex-col overflow-y-auto gap-2 py-6 px-2 bg-base-200"> <aside
<a class="btn btn-square btn-ghost btn-secondary text-xl" title="Home" routerLink="/home" routerLinkActive="btn-active"> class="h-screen sticky top-0 flex flex-col overflow-y-auto gap-2 py-6 px-2 bg-base-200"
<ng-icon name="ionHome"></ng-icon> >
</a> <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
<ng-icon name="ionMegaphoneOutline"></ng-icon> routerLink="/calls"
</a> 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
<ng-icon name="ionChatbubbles"></ng-icon> class="btn btn-square btn-ghost text-xl"
</a> title="Talkgroups"
routerLink="/talkgroups"
routerLinkActive="btn-active"
>
<ng-icon name="ionChatbubbles"></ng-icon>
</a>
<div class="divider my-0"></div> <div class="divider my-0"></div>
<a class="btn btn-square btn-ghost text-xl" title="Incidents" routerLink="/incidents" routerLinkActive="btn-active"> <a
<ng-icon name="ionNewspaperOutline"></ng-icon> class="btn btn-square btn-ghost text-xl"
</a> 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
<ng-icon name="ionAlertCircleOutline"></ng-icon> class="btn btn-square btn-ghost text-xl"
</a> title="Alerts"
<div class="divider my-0"></div> routerLink="/alerts"
routerLinkActive="btn-active"
>
<ng-icon name="ionAlertCircleOutline"></ng-icon>
</a>
<div class="divider my-0"></div>
<a class="btn btn-circle btn-ghost text-xl" title="Listen"> <a class="btn btn-circle btn-ghost text-xl" title="Listen">
<ng-icon name="ionRadioOutline"></ng-icon> <ng-icon name="ionRadioOutline"></ng-icon>
</a> </a>
</aside> </aside>
<div class="container mx-auto px-4"> <div class="container mx-auto px-4 flex overflow-auto">
<router-outlet /> <router-outlet />
</div> </div>
</div> </div>

View file

@ -3,17 +3,47 @@ import { RouterModule, RouterOutlet, RouterLink } 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';
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({ @Component({
selector: 'app-root', selector: 'app-root',
standalone: true, standalone: true,
imports: [CommonModule, RouterOutlet, RouterModule, RouterLink, NgIconComponent], imports: [
CommonModule,
RouterOutlet,
RouterModule,
RouterLink,
NgIconComponent,
],
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrl: './app.component.css', 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 { export class AppComponent {
auth: AuthService = inject(AuthService); auth: AuthService = inject(AuthService);
title = 'admin'; title = 'admin';
logout() {
this.auth.logout();
}
} }

View file

@ -7,7 +7,8 @@ import {
withInterceptors, withInterceptors,
} from '@angular/common/http'; } from '@angular/common/http';
import { Observable } from 'rxjs'; 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 { routes } from './app.routes';
import { provideHttpClient } from '@angular/common/http'; import { provideHttpClient } from '@angular/common/http';
@ -26,10 +27,28 @@ export function apiBaseInterceptor(
return next(apiReq); 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 = { export const appConfig: ApplicationConfig = {
providers: [ providers: [
provideZoneChangeDetection({ eventCoalescing: true }), provideZoneChangeDetection({ eventCoalescing: true }),
provideRouter(routes), provideRouter(routes),
provideHttpClient(withInterceptors([apiBaseInterceptor])), provideHttpClient(withInterceptors([apiBaseInterceptor, authIntercept])),
], ],
}; };

View file

@ -8,9 +8,8 @@ describe('CallsComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [CallsComponent] imports: [CallsComponent],
}) }).compileComponents();
.compileComponents();
fixture = TestBed.createComponent(CallsComponent); fixture = TestBed.createComponent(CallsComponent);
component = fixture.componentInstance; component = fixture.componentInstance;

View file

@ -5,8 +5,6 @@ import { Component } from '@angular/core';
standalone: true, standalone: true,
imports: [], imports: [],
templateUrl: './calls.component.html', templateUrl: './calls.component.html',
styleUrl: './calls.component.css' styleUrl: './calls.component.css',
}) })
export class CallsComponent { export class CallsComponent {}
}

View file

@ -1,3 +1 @@
<p> <p>This will be a dashboard someday.</p>
This will be a dashboard someday.
</p>

View file

@ -7,5 +7,4 @@ import { Component } from '@angular/core';
templateUrl: './home.component.html', templateUrl: './home.component.html',
styleUrl: './home.component.css', styleUrl: './home.component.css',
}) })
export class HomeComponent { export class HomeComponent {}
}

View file

@ -8,9 +8,8 @@ describe('IncidentsComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [IncidentsComponent] imports: [IncidentsComponent],
}) }).compileComponents();
.compileComponents();
fixture = TestBed.createComponent(IncidentsComponent); fixture = TestBed.createComponent(IncidentsComponent);
component = fixture.componentInstance; component = fixture.componentInstance;

View file

@ -5,8 +5,6 @@ import { Component } from '@angular/core';
standalone: true, standalone: true,
imports: [], imports: [],
templateUrl: './incidents.component.html', templateUrl: './incidents.component.html',
styleUrl: './incidents.component.css' styleUrl: './incidents.component.css',
}) })
export class IncidentsComponent { export class IncidentsComponent {}
}

View file

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

View 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;
}

View file

@ -0,0 +1 @@
<p>TG comp</p>

View file

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

View file

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

View file

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

View file

@ -8,9 +8,8 @@ describe('TalkgroupsComponent', () => {
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [TalkgroupsComponent] imports: [TalkgroupsComponent],
}) }).compileComponents();
.compileComponents();
fixture = TestBed.createComponent(TalkgroupsComponent); fixture = TestBed.createComponent(TalkgroupsComponent);
component = fixture.componentInstance; component = fixture.componentInstance;

View file

@ -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({ @Component({
selector: 'app-talkgroups', selector: 'talkgroups',
standalone: true, standalone: true,
imports: [], imports: [NgIconComponent],
templateUrl: './talkgroups.component.html', templateUrl: './talkgroups.component.html',
styleUrl: './talkgroups.component.css' styleUrl: './talkgroups.component.css',
providers: [provideIcons({ ionCreateOutline })],
}) })
export class TalkgroupsComponent { export class TalkgroupsComponent {
tgs: Talkgroup[] = [];
tgService: TalkgroupService = inject(TalkgroupService);
ngOnInit() {
this.getTalkgroups();
}
getTalkgroups() {
this.tgService.getTalkgroups().subscribe((tgs) => (this.tgs = tgs));
}
} }

View 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();
});
});

View 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/');
}
}