Merge pull request 'Begin new alert rule builder' (#100) from alertRuleBuilder91 into trunk
Reviewed-on: #100
This commit is contained in:
commit
66dc6e4b78
12 changed files with 90 additions and 46 deletions
|
@ -169,7 +169,11 @@
|
||||||
</td>
|
</td>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<tr mat-header-row *matHeaderRowDef="columns; sticky: true"></tr>
|
<tr mat-header-row *matHeaderRowDef="columns; sticky: true"></tr>
|
||||||
<tr mat-row *matRowDef="let row; columns: columns" [ngClass]="{'in-incident': row.incidents > 0}"></tr>
|
<tr
|
||||||
|
mat-row
|
||||||
|
*matRowDef="let row; columns: columns"
|
||||||
|
[ngClass]="{ 'in-incident': row.incidents > 0 }"
|
||||||
|
></tr>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
<div class="pagFoot">
|
<div class="pagFoot">
|
||||||
|
|
|
@ -394,7 +394,7 @@ export class CallsComponent {
|
||||||
add: this.selection.selected.map((s, i, a) => {
|
add: this.selection.selected.map((s, i, a) => {
|
||||||
s.incidents++;
|
s.incidents++;
|
||||||
return s.id;
|
return s.id;
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: () => {
|
next: () => {
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
@let inc = inc$ | async;
|
@let inc = inc$ | async;
|
||||||
<mat-card class="incident" appearance="outlined">
|
<mat-card class="incident" appearance="outlined">
|
||||||
<div class="cardHdr">
|
<div class="cardHdr">
|
||||||
<h1>{{ inc?.name }}</h1>
|
<h1>
|
||||||
|
{{ inc?.name }}
|
||||||
|
<a [href]="'/api/incident/' + incID + '.m3u'"
|
||||||
|
><mat-icon>playlist_play</mat-icon></a
|
||||||
|
>
|
||||||
|
</h1>
|
||||||
<button mat-icon-button (click)="editIncident(incID)">
|
<button mat-icon-button (click)="editIncident(incID)">
|
||||||
<mat-icon>edit</mat-icon>
|
<mat-icon>edit</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -15,12 +15,19 @@ export class AlertTime {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AlertRule {
|
export class AlertRule {
|
||||||
times: AlertTime[];
|
times!: string[];
|
||||||
mult: number;
|
mult!: number;
|
||||||
|
|
||||||
constructor(times: AlertTime[], mult: number) {
|
public getTimes(): AlertTime[] {
|
||||||
this.times = times;
|
let timesProc = <AlertTime[]>[];
|
||||||
this.mult = mult;
|
this.times.forEach((tm) => {
|
||||||
|
let sr = tm.split('+');
|
||||||
|
timesProc.push(<AlertTime>{
|
||||||
|
time: sr[0],
|
||||||
|
duration: sr[1],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return timesProc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,6 +91,10 @@ export class Talkgroup {
|
||||||
icon?: string,
|
icon?: string,
|
||||||
) {
|
) {
|
||||||
this.iconSvg = this.iconMap(this.metadata?.icon!);
|
this.iconSvg = this.iconMap(this.metadata?.icon!);
|
||||||
|
this.alert_rules = this.alert_rules.map((x) =>
|
||||||
|
Object.assign(new AlertRule(), x),
|
||||||
|
);
|
||||||
|
console.log(this.alert_rules);
|
||||||
}
|
}
|
||||||
|
|
||||||
iconMap(icon: string): string {
|
iconMap(icon: string): string {
|
||||||
|
|
|
@ -1,30 +1,21 @@
|
||||||
<div class="container flex">
|
<div class="container flex">
|
||||||
@for (rule of rules; track $index) {
|
@for (rule of rules; track $index) {
|
||||||
<div class="">
|
<div class="rule">
|
||||||
<table class="table">
|
<table mat-table [dataSource]="rule | ruleTimes">
|
||||||
<thead>
|
<ng-container matColumnDef="time">
|
||||||
<tr>
|
<th mat-header-cell *matHeaderCellDef>Time</th>
|
||||||
<td>Start</td>
|
<td mat-cell *matCellDef="let time">{{ time.time }}</td>
|
||||||
<td>Duration</td>
|
</ng-container>
|
||||||
<td></td>
|
<ng-container matColumnDef="duration">
|
||||||
</tr>
|
<th mat-header-cell *matHeaderCellDef>Duration</th>
|
||||||
</thead>
|
<td mat-cell *matCellDef="let time">{{ time.duration }}</td>
|
||||||
<tbody>
|
</ng-container>
|
||||||
@for (time of rule.times; track $index) {
|
<ng-container matColumnDef="multiplier">
|
||||||
<tr>
|
<th mat-header-cell *matHeaderCellDef>Multiplier</th>
|
||||||
<td>
|
<td mat-cell *matCellDef="let time">{{ rule.mult }}</td>
|
||||||
{{ time.time }}
|
</ng-container>
|
||||||
</td>
|
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
|
||||||
<td>
|
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
|
||||||
{{ time.duration }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
} @empty {
|
|
||||||
<tr>
|
|
||||||
<td><em>No times</em></td>
|
|
||||||
</tr>
|
|
||||||
}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
} @empty {
|
} @empty {
|
||||||
|
|
|
@ -1,18 +1,43 @@
|
||||||
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
import {
|
||||||
import { AlertRule } from '../../../talkgroup';
|
Component,
|
||||||
|
Input,
|
||||||
|
Output,
|
||||||
|
EventEmitter,
|
||||||
|
Pipe,
|
||||||
|
PipeTransform,
|
||||||
|
} from '@angular/core';
|
||||||
|
import { AlertRule, AlertTime } from '../../../talkgroup';
|
||||||
|
import { MatTableModule } from '@angular/material/table';
|
||||||
|
|
||||||
|
@Pipe({
|
||||||
|
name: 'ruleTimes',
|
||||||
|
standalone: true,
|
||||||
|
pure: true,
|
||||||
|
})
|
||||||
|
export class AlertRulePipe implements PipeTransform {
|
||||||
|
transform(rule: AlertRule, args?: any): AlertTime[] {
|
||||||
|
return rule.getTimes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'alert-rule-builder',
|
selector: 'alert-rule-builder',
|
||||||
imports: [],
|
imports: [MatTableModule, AlertRulePipe],
|
||||||
templateUrl: './alert-rule-builder.component.html',
|
templateUrl: './alert-rule-builder.component.html',
|
||||||
styleUrl: './alert-rule-builder.component.scss',
|
styleUrl: './alert-rule-builder.component.scss',
|
||||||
})
|
})
|
||||||
export class AlertRuleBuilderComponent {
|
export class AlertRuleBuilderComponent {
|
||||||
@Input() rules: AlertRule[] = [];
|
@Input() rules!: AlertRule[];
|
||||||
@Output() rulesChange: EventEmitter<AlertRule[]> = new EventEmitter<
|
@Output() rulesChange: EventEmitter<AlertRule[]> = new EventEmitter<
|
||||||
AlertRule[]
|
AlertRule[]
|
||||||
>();
|
>();
|
||||||
|
|
||||||
|
displayedColumns = ['time', 'duration', 'multiplier'];
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.rules = <AlertRule[]>this.rules;
|
||||||
|
}
|
||||||
|
|
||||||
emit() {
|
emit() {
|
||||||
this.rulesChange.emit(this.rules);
|
this.rulesChange.emit(this.rules);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
IconMap,
|
IconMap,
|
||||||
iconMapping,
|
iconMapping,
|
||||||
TGID,
|
TGID,
|
||||||
|
AlertRule,
|
||||||
} from '../../talkgroup';
|
} from '../../talkgroup';
|
||||||
import { COMMA, ENTER } from '@angular/cdk/keycodes';
|
import { COMMA, ENTER } from '@angular/cdk/keycodes';
|
||||||
import { TalkgroupService } from '../talkgroups.service';
|
import { TalkgroupService } from '../talkgroups.service';
|
||||||
|
@ -157,6 +158,10 @@ export class TalkgroupRecordComponent {
|
||||||
.getTalkgroup(Number(this.tgid.sys), Number(this.tgid.tg))
|
.getTalkgroup(Number(this.tgid.sys), Number(this.tgid.tg))
|
||||||
.pipe(
|
.pipe(
|
||||||
tap((tg) => {
|
tap((tg) => {
|
||||||
|
console.log('tap run');
|
||||||
|
tg.alert_rules = tg.alert_rules
|
||||||
|
? tg.alert_rules.map((x) => Object.assign(new AlertRule(), x))
|
||||||
|
: [];
|
||||||
this.form.patchValue(tg);
|
this.form.patchValue(tg);
|
||||||
this.form.controls['tagInput'].setValue('');
|
this.form.controls['tagInput'].setValue('');
|
||||||
this.form.controls['tagsControl'].setValue(this.tg?.tags ?? []);
|
this.form.controls['tagsControl'].setValue(this.tg?.tags ?? []);
|
||||||
|
|
|
@ -33,7 +33,10 @@ export class TalkgroupService {
|
||||||
private subscriptions = new Subscription();
|
private subscriptions = new Subscription();
|
||||||
constructor(private http: HttpClient) {
|
constructor(private http: HttpClient) {
|
||||||
this.tgs$ = this.fetchAll.pipe(switchMap(() => this.getTalkgroups()));
|
this.tgs$ = this.fetchAll.pipe(switchMap(() => this.getTalkgroups()));
|
||||||
this.tags$ = this.fetchAll.pipe(switchMap(() => this.getAllTags()));
|
this.tags$ = this.fetchAll.pipe(
|
||||||
|
switchMap(() => this.getAllTags()),
|
||||||
|
shareReplay(),
|
||||||
|
);
|
||||||
this.fillTgMap();
|
this.fillTgMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,11 +55,8 @@ export class TalkgroupService {
|
||||||
getTalkgroup(sys: number, tg: number): Observable<Talkgroup> {
|
getTalkgroup(sys: number, tg: number): Observable<Talkgroup> {
|
||||||
const key = this.tgKey(sys, tg);
|
const key = this.tgKey(sys, tg);
|
||||||
if (!this._getTalkgroup.get(key)) {
|
if (!this._getTalkgroup.get(key)) {
|
||||||
return this.tgs$.pipe(
|
let rs = new ReplaySubject<Talkgroup>();
|
||||||
switchMap((talkg) =>
|
this._getTalkgroup.set(key, rs);
|
||||||
talkg.filter((tgv) => tgv.tgid == tg && tgv.system_id == sys),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return this._getTalkgroup.get(key)!;
|
return this._getTalkgroup.get(key)!;
|
||||||
}
|
}
|
||||||
|
|
|
@ -245,6 +245,7 @@ func (a *Auth) routeAuth(w http.ResponseWriter, r *http.Request) {
|
||||||
Path: "/",
|
Path: "/",
|
||||||
HttpOnly: true,
|
HttpOnly: true,
|
||||||
Secure: true,
|
Secure: true,
|
||||||
|
MaxAge: 60 * 60 * 24 * 30, // one month
|
||||||
}
|
}
|
||||||
|
|
||||||
cookie.Domain = r.Host
|
cookie.Domain = r.Host
|
||||||
|
|
|
@ -180,6 +180,7 @@ FROM incidents_calls ic, LATERAL (
|
||||||
FROM swept_calls sc WHERE sc.id = ic.swept_call_id
|
FROM swept_calls sc WHERE sc.id = ic.swept_call_id
|
||||||
) c
|
) c
|
||||||
WHERE ic.incident_id = $1
|
WHERE ic.incident_id = $1
|
||||||
|
ORDER BY ic.call_date ASC
|
||||||
`
|
`
|
||||||
|
|
||||||
type GetIncidentCallsRow struct {
|
type GetIncidentCallsRow struct {
|
||||||
|
|
|
@ -202,7 +202,7 @@ func (ia *incidentsAPI) getCallsM3U(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var b bytes.Buffer
|
b := new(bytes.Buffer)
|
||||||
|
|
||||||
callUrl := common.PtrTo(*ia.baseURL)
|
callUrl := common.PtrTo(*ia.baseURL)
|
||||||
|
|
||||||
|
@ -220,7 +220,7 @@ func (ia *incidentsAPI) getCallsM3U(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
callUrl.Path = "/api/call/" + c.ID.String()
|
callUrl.Path = "/api/call/" + c.ID.String()
|
||||||
|
|
||||||
fmt.Fprintf(w, "#EXTINF:%d,%s%s (%s)\n%s\n\n",
|
fmt.Fprintf(b, "#EXTINF:%d,%s%s (%s)\n%s\n\n",
|
||||||
c.Duration.Seconds(),
|
c.Duration.Seconds(),
|
||||||
tg.StringTag(true),
|
tg.StringTag(true),
|
||||||
from,
|
from,
|
||||||
|
|
|
@ -141,7 +141,8 @@ FROM incidents_calls ic, LATERAL (
|
||||||
sc.transcript
|
sc.transcript
|
||||||
FROM swept_calls sc WHERE sc.id = ic.swept_call_id
|
FROM swept_calls sc WHERE sc.id = ic.swept_call_id
|
||||||
) c
|
) c
|
||||||
WHERE ic.incident_id = @id;
|
WHERE ic.incident_id = @id
|
||||||
|
ORDER BY ic.call_date ASC;
|
||||||
|
|
||||||
-- name: GetIncident :one
|
-- name: GetIncident :one
|
||||||
SELECT
|
SELECT
|
||||||
|
|
Loading…
Reference in a new issue