mirror of
https://github.com/chuot/rdio-scanner.git
synced 2026-08-13 08:33:00 -06:00
Version 6.5.0
This commit is contained in:
parent
05747a0428
commit
0e088ad534
13
CHANGELOG.md
13
CHANGELOG.md
|
|
@ -1,5 +1,18 @@
|
|||
# Change log
|
||||
|
||||
## Version 6.5
|
||||
|
||||
- Fixed API looping on malformed or invalid multipart content (issue #181, #212).
|
||||
- Source code updated to GO 1.18 with `interface{}` replaced by `any`.
|
||||
- Removed ingest mutex for performance reasons.
|
||||
- Replaced all `path.Base()` by `filepath.Base()` to fix an issue with audio filenames on Windows.
|
||||
- New `Branding Label` and `Email Support` options to show on main screen (issue #220).
|
||||
- New temporary avoid feature (discussion #218).
|
||||
- Fix remote address regexp (issue #225).
|
||||
- Add the `ident` to `new listener` log message (discussion #226).
|
||||
- New populated talkgroups won't be activated on the client if its group (or tag) is turned off (issue #227).
|
||||
- Removed the duplicated webapp section from the PDF document.
|
||||
|
||||
## Version 6.4
|
||||
|
||||
- New `-cmd` command line options to allow advanced administrative tasks.
|
||||
|
|
|
|||
16
COMPILING.md
16
COMPILING.md
|
|
@ -27,13 +27,13 @@ Clone the official repository on your computer and start the build process.
|
|||
|
||||
When finished, you will find the precompiled versions for various platforms in the `dist` folder.
|
||||
|
||||
rdio-scanner-darwin-amd64-v6.4.5.zip
|
||||
rdio-scanner-darwin-arm64-v6.4.5.zip
|
||||
rdio-scanner-freebsd-amd64-v6.4.5.zip
|
||||
rdio-scanner-linux-386-v6.4.5.zip
|
||||
rdio-scanner-linux-amd64-v6.4.5.zip
|
||||
rdio-scanner-linux-arm64-v6.4.5.zip
|
||||
rdio-scanner-linux-arm-v6.4.5.zip
|
||||
rdio-scanner-windows-amd64-v6.4.5.zip
|
||||
rdio-scanner-darwin-amd64-v6.5.0.zip
|
||||
rdio-scanner-darwin-arm64-v6.5.0.zip
|
||||
rdio-scanner-freebsd-amd64-v6.5.0.zip
|
||||
rdio-scanner-linux-386-v6.5.0.zip
|
||||
rdio-scanner-linux-amd64-v6.5.0.zip
|
||||
rdio-scanner-linux-arm64-v6.5.0.zip
|
||||
rdio-scanner-linux-arm-v6.5.0.zip
|
||||
rdio-scanner-windows-amd64-v6.5.0.zip
|
||||
|
||||
**Happy Rdio scanning !**
|
||||
6
Makefile
6
Makefile
|
|
@ -16,14 +16,14 @@
|
|||
################################################################################
|
||||
|
||||
app := rdio-scanner
|
||||
date := 2022/06/14
|
||||
ver := 6.4.5
|
||||
date := 2022/06/27
|
||||
ver := 6.5.0
|
||||
|
||||
client := $(wildcard client/*.json client/*.ts)
|
||||
server := $(wildcard server/*.go)
|
||||
|
||||
build = @cd server && GOOS=$(1) GOARCH=$(2) go build -o ../dist/$(1)-$(2)/$(3)
|
||||
pandoc = @test -d dist/$(1)-$(2) || mkdir -p dist/$(1)-$(2) && pandoc -f markdown -o dist/$(1)-$(2)/$(3) --resource-path docs:docs/platforms $(4) docs/webapp.md docs/webapp.md docs/faq.md CHANGELOG.md
|
||||
pandoc = @test -d dist/$(1)-$(2) || mkdir -p dist/$(1)-$(2) && pandoc -f markdown -o dist/$(1)-$(2)/$(3) --resource-path docs:docs/platforms $(4) docs/webapp.md docs/faq.md CHANGELOG.md
|
||||
zip = @cd dist/$(1)-$(2) && zip -q ../$(app)-$(1)-$(2)-v$(ver).zip * && cd ..
|
||||
|
||||
.PHONY: all clean container dist sed
|
||||
|
|
|
|||
4
client/package-lock.json
generated
4
client/package-lock.json
generated
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"name": "rdio-scanner",
|
||||
"version": "6.4.5",
|
||||
"version": "6.5.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "rdio-scanner",
|
||||
"version": "6.4.5",
|
||||
"version": "6.5.0",
|
||||
"license": "LICENSE",
|
||||
"dependencies": {
|
||||
"@angular/animations": "^13.3.10",
|
||||
|
|
|
|||
|
|
@ -38,5 +38,5 @@
|
|||
"build": "ng build --base-href ./ --configuration production",
|
||||
"start": "ng serve"
|
||||
},
|
||||
"version": "6.4.5"
|
||||
"version": "6.5.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -132,9 +132,11 @@ export interface Options {
|
|||
afsSystems?: string;
|
||||
audioConversion?: 0 | 1 | 2 | 3;
|
||||
autoPopulate?: boolean;
|
||||
branding?: string;
|
||||
dimmerDelay?: number;
|
||||
disableDuplicateDetection?: boolean;
|
||||
duplicateDetectionTimeFrame?: number;
|
||||
email?: string;
|
||||
keypadBeeps?: string;
|
||||
maxClients?: number;
|
||||
playbackGoesLive?: boolean;
|
||||
|
|
@ -497,9 +499,11 @@ export class RdioScannerAdminService implements OnDestroy {
|
|||
afsSystems: [options?.afsSystems, this.validateAfsSystems()],
|
||||
audioConversion: [options?.audioConversion],
|
||||
autoPopulate: [options?.autoPopulate],
|
||||
branding: [options?.branding],
|
||||
dimmerDelay: [options?.dimmerDelay, [Validators.required, Validators.min(0)]],
|
||||
disableDuplicateDetection: [options?.disableDuplicateDetection],
|
||||
duplicateDetectionTimeFrame: [options?.duplicateDetectionTimeFrame, [Validators.required, Validators.min(0)]],
|
||||
email: [options?.email],
|
||||
keypadBeeps: [options?.keypadBeeps, Validators.required],
|
||||
maxClients: [options?.maxClients, [Validators.required, Validators.min(1)]],
|
||||
playbackGoesLive: [options?.playbackGoesLive],
|
||||
|
|
|
|||
|
|
@ -45,6 +45,16 @@
|
|||
<mat-slide-toggle color="primary" formControlName="autoPopulate"></mat-slide-toggle>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<p>
|
||||
<span class="mat-body">Branding Label</span><br>
|
||||
<span class="mat-caption">A simple line of text displayed above the main screen to help identify the Rdio
|
||||
Scanner instance.</span>
|
||||
</p>
|
||||
<mat-form-field floatLabel="never">
|
||||
<input type="text" matInput formControlName="branding" placeholder="Branding">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row">
|
||||
<p>
|
||||
<span class="mat-body">Dimmer Delay</span><br>
|
||||
|
|
@ -86,6 +96,15 @@
|
|||
</mat-error>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row">
|
||||
<p>
|
||||
<span class="mat-body">Email Support</span><br>
|
||||
<span class="mat-caption">Email address where users can write to to get support.</span>
|
||||
</p>
|
||||
<mat-form-field floatLabel="never">
|
||||
<input type="text" matInput formControlName="email" placeholder="Email">
|
||||
</mat-form-field>
|
||||
</div>
|
||||
<div class="row">
|
||||
<p>
|
||||
<span class="mat-body">Keypad Beep Style</span><br>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
.rdio-button,
|
||||
.rdio-button-mini {
|
||||
background: rgb(45, 45, 45);
|
||||
--def: rgb(45, 45, 45);
|
||||
--green: rgb(0, 230, 118);
|
||||
--red: rgb(255, 23, 68);
|
||||
--yellow: rgb(255, 234, 0);
|
||||
|
||||
background: var(--def);
|
||||
border-style: solid;
|
||||
border-width: 1px;
|
||||
border-bottom-color: rgba(0, 0, 0, 0.87);
|
||||
|
|
@ -41,24 +46,71 @@
|
|||
right: 4px;
|
||||
top: 4px;
|
||||
width: 6px;
|
||||
|
||||
animation: blink 1s linear infinite both;
|
||||
}
|
||||
|
||||
&.off::after {
|
||||
background: rgb(255, 23, 68);
|
||||
box-shadow: 1px 1px 1px rgba(255, 255, 255, 0.7) inset,
|
||||
0 0 3px 1px rgb(255, 23, 68);
|
||||
background: var(--red);
|
||||
box-shadow: 1px 1px 1px rgba(255, 255, 255, 0.7) inset, 0 0 3px 1px var(--red);
|
||||
}
|
||||
|
||||
&.off.blink::after {
|
||||
animation: blink-off 500ms linear infinite;
|
||||
}
|
||||
|
||||
@keyframes blink-off {
|
||||
0%, 50% {
|
||||
background: var(--def);
|
||||
box-shadow: 1px 1px 1px rgba(255, 255, 255, 0.7) inset, 0 0 0px 0px var(--def);
|
||||
}
|
||||
|
||||
50%, 100% {
|
||||
background: var(--red);
|
||||
box-shadow: 1px 1px 1px rgba(255, 255, 255, 0.7) inset, 0 0 3px 1px var(--red);
|
||||
}
|
||||
}
|
||||
|
||||
&.on::after {
|
||||
background: rgb(0, 230, 118);;
|
||||
box-shadow: 1px 1px 1px rgba(255, 255, 255, 0.7) inset,
|
||||
0 0 3px 1px rgb(0, 230, 118);;
|
||||
background: var(--green);
|
||||
box-shadow: 1px 1px 1px rgba(255, 255, 255, 0.7) inset, 0 0 3px 1px var(--green);
|
||||
}
|
||||
|
||||
&.on.blink::after {
|
||||
animation: blink-on 500ms linear infinite;
|
||||
}
|
||||
|
||||
@keyframes blink-on {
|
||||
0%, 50% {
|
||||
background: var(--def);
|
||||
box-shadow: 1px 1px 1px rgba(255, 255, 255, 0.7) inset, 0 0 0px 0px var(--def);
|
||||
}
|
||||
|
||||
50%, 100% {
|
||||
background: var(--def);
|
||||
box-shadow: 1px 1px 1px rgba(255, 255, 255, 0.7) inset, 0 0 3px 1px var(--green);
|
||||
}
|
||||
}
|
||||
|
||||
&.partial::after {
|
||||
background: rgb(255, 234, 0);
|
||||
box-shadow: 1px 1px 1px rgba(255, 255, 255, 0.7) inset,
|
||||
0 0 3px 1px rgb(255, 234, 0);
|
||||
background: var(--yellow);
|
||||
box-shadow: 1px 1px 1px rgba(255, 255, 255, 0.7) inset, 0 0 3px 1px var(--yellow);
|
||||
}
|
||||
|
||||
&.partial.blink::after {
|
||||
animation: blink-partial 500ms linear infinite;
|
||||
}
|
||||
|
||||
@keyframes blink-partial {
|
||||
0%, 50% {
|
||||
background: var(--def);
|
||||
box-shadow: 1px 1px 1px rgba(255, 255, 255, 0.7) inset, 0 0 0px 0px var(--def);
|
||||
}
|
||||
|
||||
50%, 100% {
|
||||
background: var(--yellow);
|
||||
box-shadow: 1px 1px 1px rgba(255, 255, 255, 0.7) inset, 0 0 3px 1px var(--yellow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<div class="rdio-status">
|
||||
<div class="branding">{{ branding }}</div>
|
||||
<div class="led" [ngClass]="ledStyle"></div>
|
||||
</div>
|
||||
<div class="rdio-display" [ngClass]="{ idle: !dimmer }" (dblclick)="toggleFullscreen.emit()">
|
||||
|
|
@ -53,6 +54,9 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="row right small">
|
||||
<div *ngIf="tempAvoid">
|
||||
<span class="flag" [ngClass]="{ flaged: avoided || patched }">⏲ {{ tempAvoid }}M</span>
|
||||
</div>
|
||||
<div>
|
||||
<span class="flag" [ngClass]="{ flaged: avoided }">AVOID</span>
|
||||
</div>
|
||||
|
|
@ -154,3 +158,8 @@
|
|||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rdio-help" *ngIf="email">
|
||||
<button mat-icon-button (click)="showHelp()">
|
||||
<mat-icon>help_center</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -171,11 +171,30 @@
|
|||
}
|
||||
|
||||
.rdio-status {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
min-height: 1.5rem;
|
||||
|
||||
.branding {
|
||||
color: rgb(64, 64, 64);
|
||||
flex: 1;
|
||||
font-size: 18px;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0.25rem;
|
||||
line-height: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-shadow: -1px -1px 1px rgb(0, 0, 0), 1px 1px 1px rgba(255, 255, 255, 0.5);
|
||||
text-transform: uppercase;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.led {
|
||||
background: rgb(80, 80, 80);
|
||||
display: block;
|
||||
height: 12px;
|
||||
margin-left: auto;
|
||||
margin-left: 24px;
|
||||
width: 24px;
|
||||
|
||||
&.on {
|
||||
|
|
@ -240,3 +259,10 @@
|
|||
.rdio-status {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.rdio-help {
|
||||
bottom: 24px;
|
||||
opacity: 0.3;
|
||||
position: absolute;
|
||||
right: 24px;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
import { ChangeDetectorRef, Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
|
||||
import { FormBuilder } from '@angular/forms';
|
||||
import { MatInput } from '@angular/material/input';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { Subscription, timer } from 'rxjs';
|
||||
import packageInfo from '../../../../../package.json';
|
||||
import {
|
||||
|
|
@ -32,6 +33,7 @@ import {
|
|||
RdioScannerLivefeedMode,
|
||||
} from '../rdio-scanner';
|
||||
import { RdioScannerService } from '../rdio-scanner.service';
|
||||
import { RdioScannerSupportComponent } from './support/support.component';
|
||||
|
||||
const LOCAL_STORAGE_KEY = RdioScannerService.LOCAL_STORAGE_KEY + '-pin';
|
||||
|
||||
|
|
@ -49,6 +51,8 @@ export class RdioScannerMainComponent implements OnDestroy, OnInit {
|
|||
|
||||
avoided = false;
|
||||
|
||||
branding = '';
|
||||
|
||||
call: RdioScannerCall | undefined;
|
||||
callError = '0';
|
||||
callFrequency: string = this.formatFrequency(0);
|
||||
|
|
@ -61,20 +65,21 @@ export class RdioScannerMainComponent implements OnDestroy, OnInit {
|
|||
callTag = 'Tag';
|
||||
callTalkgroup = 'Talkgroup';
|
||||
callTalkgroupId = '0';
|
||||
/*
|
||||
* BEGIN OF RED TAPE:
|
||||
*
|
||||
* By modifying, deleting or disabling the following lines, you harm
|
||||
* the open source project and its author. Rdio Scanner represents a lot of
|
||||
* investment in time, support, testing and hardware.
|
||||
*
|
||||
* Be respectful, sponsor the project if you can, use native apps when possible.
|
||||
*
|
||||
*/
|
||||
|
||||
//
|
||||
// BEGIN OF RED TAPE:
|
||||
//
|
||||
// By modifying, deleting or disabling the following lines, you harm
|
||||
// the open source project and its author. Rdio Scanner represents a lot of
|
||||
// investment in time, support, testing and hardware.
|
||||
//
|
||||
// Be respectful, sponsor the project if you can, use native apps when possible.
|
||||
//
|
||||
callTalkgroupName = `Rdio Scanner v${packageInfo.version}`;
|
||||
/**
|
||||
* END OF RED TAPE.
|
||||
*/
|
||||
//
|
||||
// END OF RED TAPE.
|
||||
//
|
||||
|
||||
callTime = 0;
|
||||
callUnit = '0';
|
||||
|
||||
|
|
@ -82,6 +87,8 @@ export class RdioScannerMainComponent implements OnDestroy, OnInit {
|
|||
|
||||
dimmer = false;
|
||||
|
||||
email = '';
|
||||
|
||||
holdSys = false;
|
||||
holdTg = false;
|
||||
|
||||
|
|
@ -104,6 +111,8 @@ export class RdioScannerMainComponent implements OnDestroy, OnInit {
|
|||
replayOffset = 0;
|
||||
replayTimer: Subscription | undefined;
|
||||
|
||||
tempAvoid = 0;
|
||||
|
||||
timeFormat = 'HH:mm';
|
||||
|
||||
get showListenersCount(): boolean {
|
||||
|
|
@ -128,6 +137,7 @@ export class RdioScannerMainComponent implements OnDestroy, OnInit {
|
|||
|
||||
constructor(
|
||||
private rdioScannerService: RdioScannerService,
|
||||
private matSnackBar: MatSnackBar,
|
||||
private ngChangeDetectorRef: ChangeDetectorRef,
|
||||
private ngFormBuilder: FormBuilder,
|
||||
) { }
|
||||
|
|
@ -145,28 +155,43 @@ export class RdioScannerMainComponent implements OnDestroy, OnInit {
|
|||
}
|
||||
|
||||
avoid(options?: RdioScannerAvoidOptions): void {
|
||||
const call = this.call || this.callPrevious;
|
||||
|
||||
if (this.auth) {
|
||||
this.authFocus();
|
||||
|
||||
} else {
|
||||
const call = this.call || this.callPrevious;
|
||||
|
||||
if (options || call) {
|
||||
} else if (options || call) {
|
||||
if (options) {
|
||||
this.rdioScannerService.avoid(options);
|
||||
} else if (call) {
|
||||
const avoided = this.rdioScannerService.isAvoided(call);
|
||||
const minutes = this.rdioScannerService.isAvoidedTimer(call);
|
||||
|
||||
if (call && !this.map[call.system][call.talkgroup]) {
|
||||
if (!avoided) {
|
||||
this.rdioScannerService.avoid({ status: false });
|
||||
} else if (!minutes) {
|
||||
this.rdioScannerService.avoid({ minutes: 30, status: false });
|
||||
} else if (minutes === 30) {
|
||||
this.rdioScannerService.avoid({ minutes: 60, status: false });
|
||||
} else if (minutes === 60) {
|
||||
this.rdioScannerService.avoid({ minutes: 120, status: false });
|
||||
} else {
|
||||
this.rdioScannerService.avoid({ status: true });
|
||||
}
|
||||
}
|
||||
|
||||
if (call && this.rdioScannerService.isAvoided(call)) {
|
||||
this.rdioScannerService.beep(RdioScannerBeepStyle.Activate);
|
||||
|
||||
} else {
|
||||
this.rdioScannerService.beep(RdioScannerBeepStyle.Deactivate);
|
||||
}
|
||||
|
||||
this.updateDimmer();
|
||||
|
||||
} else {
|
||||
this.rdioScannerService.beep(RdioScannerBeepStyle.Denied);
|
||||
}
|
||||
|
||||
this.updateDimmer();
|
||||
}
|
||||
}
|
||||
|
||||
holdSystem(): void {
|
||||
|
|
@ -286,6 +311,13 @@ export class RdioScannerMainComponent implements OnDestroy, OnInit {
|
|||
}
|
||||
}
|
||||
|
||||
showHelp(): void {
|
||||
this.matSnackBar.openFromComponent(RdioScannerSupportComponent, {
|
||||
data: { email: this.email },
|
||||
panelClass: 'snackbar-white',
|
||||
});
|
||||
}
|
||||
|
||||
showSearchPanel(): void {
|
||||
if (!this.config) {
|
||||
return;
|
||||
|
|
@ -378,6 +410,10 @@ export class RdioScannerMainComponent implements OnDestroy, OnInit {
|
|||
if ('config' in event) {
|
||||
this.config = event.config;
|
||||
|
||||
this.branding = this.config?.branding ?? '';
|
||||
|
||||
this.email = this.config?.email ?? '';
|
||||
|
||||
this.timeFormat = this.config?.time12hFormat ? 'h:mm a' : 'HH:mm';
|
||||
|
||||
const password = this.authForm.get('password')?.value;
|
||||
|
|
@ -566,9 +602,12 @@ export class RdioScannerMainComponent implements OnDestroy, OnInit {
|
|||
const call = this.call || this.callPrevious;
|
||||
|
||||
if (call) {
|
||||
this.tempAvoid = this.rdioScannerService.isAvoidedTimer(call);
|
||||
|
||||
if (this.rdioScannerService.isPatched(call)) {
|
||||
this.avoided = false;
|
||||
this.patched = true;
|
||||
|
||||
} else {
|
||||
this.avoided = this.rdioScannerService.isAvoided(call);
|
||||
this.patched = false;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
<div>
|
||||
<ng-container *ngIf="email">
|
||||
For further assistance please contact <b><a href="mailto:{{ email }}" target="_blank">{{ email }}</a></b>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!email">
|
||||
For further assistance please go to <b><a href="https://github.com/chuot/rdio-scanner/discussions"
|
||||
target="_blank">Rdio Scanner discussions</a></b>
|
||||
</ng-container>
|
||||
</div>
|
||||
<div class="countdown">{{ countdown }}</div>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
:host {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> div {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
color: currentColor;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
b {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.countdown {
|
||||
bottom: 4px;
|
||||
color: rgba(0, 0, 0, 0.67);
|
||||
font-size: 12px;
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
* *****************************************************************************
|
||||
* Copyright (C) 2019-2022 Chrystian Huot <chrystian.huot@saubeo.solutions>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
import { Component, Inject, Optional } from '@angular/core';
|
||||
import { MAT_SNACK_BAR_DATA, MatSnackBarRef } from '@angular/material/snack-bar';
|
||||
import { timer } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'RdioScannerSupport',
|
||||
styleUrls: ['./support.component.scss'],
|
||||
templateUrl: './support.component.html',
|
||||
})
|
||||
export class RdioScannerSupportComponent {
|
||||
countdown: number = 10;
|
||||
|
||||
email: string | undefined;
|
||||
|
||||
constructor(
|
||||
@Optional() private matSnackBarRef: MatSnackBarRef<RdioScannerSupportComponent>,
|
||||
@Inject(MAT_SNACK_BAR_DATA) public data: { email: string },
|
||||
) {
|
||||
this.email = data?.email;
|
||||
|
||||
this.wait();
|
||||
}
|
||||
|
||||
private wait(): void {
|
||||
timer(1000).subscribe(() => {
|
||||
this.countdown--;
|
||||
|
||||
if (this.countdown < 1) {
|
||||
this.matSnackBarRef?.dismiss();
|
||||
|
||||
} else {
|
||||
this.wait();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ import { timer } from 'rxjs';
|
|||
|
||||
@Component({
|
||||
selector: 'RdioScannerNative',
|
||||
styleUrls: ['./native.scss'],
|
||||
styleUrls: ['./native.component.scss'],
|
||||
templateUrl: './native.component.html',
|
||||
})
|
||||
export class RdioScannerNativeComponent implements OnInit {
|
||||
|
|
@ -34,8 +34,7 @@ export class RdioScannerNativeComponent implements OnInit {
|
|||
isAndroid: boolean = false;
|
||||
isApple: boolean = false;
|
||||
|
||||
constructor(@Optional() private matSnackBarRef: MatSnackBarRef<RdioScannerNativeComponent>) {
|
||||
}
|
||||
constructor(@Optional() private matSnackBarRef: MatSnackBarRef<RdioScannerNativeComponent>) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
const ua = navigator.userAgent;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ $background-color: rgb(30, 30, 30);
|
|||
display: block;
|
||||
height: 100%;
|
||||
min-width: 320px;
|
||||
position: relative;
|
||||
|
||||
.mat-sidenav {
|
||||
background-color: inherit;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import { AppSharedModule } from '../../shared/shared.module';
|
|||
import { RdioScannerComponent } from './rdio-scanner.component';
|
||||
import { RdioScannerService } from './rdio-scanner.service';
|
||||
import { RdioScannerMainComponent } from './main/main.component';
|
||||
import { RdioScannerSupportComponent } from './main/support/support.component';
|
||||
import { RdioScannerNativeModule } from './native/native.module';
|
||||
import { RdioScannerSearchComponent } from './search/search.component';
|
||||
import { RdioScannerSelectComponent } from './select/select.component';
|
||||
|
|
@ -33,9 +34,13 @@ import { RdioScannerSelectComponent } from './select/select.component';
|
|||
RdioScannerMainComponent,
|
||||
RdioScannerSearchComponent,
|
||||
RdioScannerSelectComponent,
|
||||
RdioScannerSupportComponent,
|
||||
],
|
||||
exports: [RdioScannerComponent],
|
||||
imports: [AppSharedModule, RdioScannerNativeModule],
|
||||
imports: [
|
||||
AppSharedModule,
|
||||
RdioScannerNativeModule,
|
||||
],
|
||||
providers: [
|
||||
RdioScannerService,
|
||||
{ provide: OverlayContainer, useClass: FullscreenOverlayContainer },
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ import {
|
|||
RdioScannerCategoryType,
|
||||
RdioScannerConfig,
|
||||
RdioScannerEvent,
|
||||
RdioScannerLivefeed,
|
||||
RdioScannerLivefeedMap,
|
||||
RdioScannerLivefeedMode,
|
||||
RdioScannerPlaybackList,
|
||||
|
|
@ -57,6 +58,7 @@ enum WebsocketCommand {
|
|||
LivefeedMap = 'LFM',
|
||||
Max = 'MAX',
|
||||
Pin = 'PIN',
|
||||
Version = 'VER',
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
|
|
@ -100,8 +102,6 @@ export class RdioScannerService implements OnDestroy {
|
|||
private playbackPending: number | undefined;
|
||||
private playbackRefreshing = false;
|
||||
|
||||
private replayDelay: Subscription | undefined;
|
||||
|
||||
private skipDelay: Subscription | undefined;
|
||||
|
||||
private websocket: WebSocket | undefined;
|
||||
|
|
@ -122,6 +122,29 @@ export class RdioScannerService implements OnDestroy {
|
|||
}
|
||||
|
||||
avoid(options: RdioScannerAvoidOptions = {}): void {
|
||||
const clearTimer = (lfm: RdioScannerLivefeed): void => {
|
||||
lfm.minutes = undefined;
|
||||
lfm.timer?.unsubscribe();
|
||||
lfm.timer = undefined;
|
||||
};
|
||||
|
||||
const setTimer = (lfm: RdioScannerLivefeed, minutes: number): void => {
|
||||
lfm.minutes = minutes;
|
||||
lfm.timer = timer(minutes * 60 * 1000).subscribe(() => {
|
||||
lfm.active = true;
|
||||
lfm.minutes = undefined;
|
||||
lfm.timer = undefined;
|
||||
|
||||
this.rebuildCategories();
|
||||
this.storeLivefeedMap();
|
||||
|
||||
this.event.emit({
|
||||
categories: this.categories,
|
||||
map: this.livefeedMap,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
if (this.livefeedMapPriorToHoldSystem) {
|
||||
this.livefeedMapPriorToHoldSystem = undefined;
|
||||
}
|
||||
|
|
@ -133,37 +156,39 @@ export class RdioScannerService implements OnDestroy {
|
|||
if (typeof options.all === 'boolean') {
|
||||
Object.keys(this.livefeedMap).map((sys: string) => +sys).forEach((sys: number) => {
|
||||
Object.keys(this.livefeedMap[sys]).map((tg: string) => +tg).forEach((tg: number) => {
|
||||
this.livefeedMap[sys][tg] = typeof options.status === 'boolean' ? options.status : !!options.all;
|
||||
const lfm = this.livefeedMap[sys][tg];
|
||||
clearTimer(lfm);
|
||||
lfm.active = typeof options.status === 'boolean' ? options.status : !!options.all;
|
||||
});
|
||||
});
|
||||
|
||||
} else if (options.call) {
|
||||
const sys = options.call.system;
|
||||
const tg = options.call.talkgroup;
|
||||
|
||||
this.livefeedMap[sys][tg] = typeof options.status === 'boolean' ? options.status : !this.livefeedMap[sys][tg];
|
||||
const lfm = this.livefeedMap[options.call.system][options.call.talkgroup];
|
||||
clearTimer(lfm);
|
||||
lfm.active = typeof options.status === 'boolean' ? options.status : !lfm.active;
|
||||
if (typeof options.minutes === 'number') setTimer(lfm, options.minutes);
|
||||
|
||||
} else if (options.system && options.talkgroup) {
|
||||
const sys = options.system.id;
|
||||
const tg = options.talkgroup.id;
|
||||
|
||||
this.livefeedMap[sys][tg] = typeof options.status === 'boolean' ? options.status : !this.livefeedMap[sys][tg];
|
||||
const lfm = this.livefeedMap[options.system.id][options.talkgroup.id];
|
||||
clearTimer(lfm);
|
||||
lfm.active = typeof options.status === 'boolean' ? options.status : !lfm.active;
|
||||
if (typeof options.minutes === 'number') setTimer(lfm, options.minutes);
|
||||
|
||||
} else if (options.system && !options.talkgroup) {
|
||||
const sys = options.system.id;
|
||||
|
||||
Object.keys(this.livefeedMap[sys]).map((tg: string) => +tg).forEach((tg: number) => {
|
||||
this.livefeedMap[sys][tg] = typeof options.status === 'boolean' ? options.status : !this.livefeedMap[sys][tg];
|
||||
const lfm = this.livefeedMap[sys][tg];
|
||||
clearTimer(lfm);
|
||||
lfm.active = typeof options.status === 'boolean' ? options.status : !lfm.active;
|
||||
});
|
||||
|
||||
} else {
|
||||
const call = this.call || this.callPrevious;
|
||||
|
||||
if (call) {
|
||||
const sys = call.system;
|
||||
const tg = call.talkgroup;
|
||||
|
||||
this.livefeedMap[sys][tg] = typeof options.status === 'boolean' ? options.status : !this.livefeedMap[sys][tg];
|
||||
const lfm = this.livefeedMap[call.system][call.talkgroup];
|
||||
clearTimer(lfm);
|
||||
lfm.active = typeof options.status === 'boolean' ? options.status : !lfm.active;
|
||||
if (typeof options.minutes === 'number') setTimer(lfm, options.minutes);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -243,18 +268,20 @@ export class RdioScannerService implements OnDestroy {
|
|||
this.livefeedMapPriorToHoldSystem = this.livefeedMap;
|
||||
|
||||
this.livefeedMap = Object.keys(this.livefeedMap).map((sys) => +sys).reduce((sysMap, sys) => {
|
||||
const allOn = Object.keys(this.livefeedMap[sys]).every((tg) => !this.livefeedMap[sys][tg]);
|
||||
const allOn = Object.keys(this.livefeedMap[sys]).map((tg) => +tg).every((tg) => !this.livefeedMap[sys][tg]);
|
||||
|
||||
sysMap[sys] = Object.keys(this.livefeedMap[sys]).map((tg) => +tg).reduce((tgMap, tg) => {
|
||||
if (sys === call.system) {
|
||||
tgMap[tg] = allOn || this.livefeedMap[sys][tg];
|
||||
tgMap[tg].minutes = undefined;
|
||||
tgMap[tg].timer?.unsubscribe();
|
||||
tgMap[tg].timer = undefined;
|
||||
|
||||
} else {
|
||||
tgMap[tg] = false;
|
||||
}
|
||||
if (sys === call.system)
|
||||
tgMap[tg].active = allOn || this.livefeedMap[sys][tg].active;
|
||||
else
|
||||
tgMap[tg].active = false;
|
||||
|
||||
return tgMap;
|
||||
}, {} as { [key: number]: boolean });
|
||||
}, {} as { [key: number]: RdioScannerLivefeed });
|
||||
|
||||
return sysMap;
|
||||
}, {} as RdioScannerLivefeedMap);
|
||||
|
|
@ -298,15 +325,17 @@ export class RdioScannerService implements OnDestroy {
|
|||
|
||||
this.livefeedMap = Object.keys(this.livefeedMap).map((sys) => +sys).reduce((sysMap, sys) => {
|
||||
sysMap[sys] = Object.keys(this.livefeedMap[sys]).map((tg) => +tg).reduce((tgMap, tg) => {
|
||||
if (sys === call.system) {
|
||||
tgMap[tg] = tg === call.talkgroup;
|
||||
tgMap[tg].minutes = undefined;
|
||||
tgMap[tg].timer?.unsubscribe();
|
||||
tgMap[tg].timer = undefined;
|
||||
|
||||
} else {
|
||||
tgMap[tg] = false;
|
||||
}
|
||||
if (sys === call.system)
|
||||
tgMap[tg].active = tg === call.talkgroup;
|
||||
else
|
||||
tgMap[tg].active = false;
|
||||
|
||||
return tgMap;
|
||||
}, {} as { [key: number]: boolean });
|
||||
}, {} as { [key: number]: RdioScannerLivefeed });
|
||||
|
||||
return sysMap;
|
||||
}, {} as RdioScannerLivefeedMap);
|
||||
|
|
@ -333,12 +362,19 @@ export class RdioScannerService implements OnDestroy {
|
|||
}
|
||||
|
||||
isAvoided(call: RdioScannerCall): boolean {
|
||||
return !!this.livefeedMap[call.system] && this.livefeedMap[call.system][call.talkgroup] === false;
|
||||
return !!this.livefeedMap[call.system] && this.livefeedMap[call.system][call.talkgroup]?.active !== true;
|
||||
}
|
||||
|
||||
isAvoidedTimer(call: RdioScannerCall): number {
|
||||
if (!!this.livefeedMap[call.system] && this.livefeedMap[call.system][call.talkgroup]?.minutes !== undefined) {
|
||||
return this.livefeedMap[call.system][call.talkgroup]?.minutes || 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
isPatched(call: RdioScannerCall): boolean {
|
||||
return this.isAvoided(call) && call.patches.some((tg) => {
|
||||
return !!this.livefeedMap[call.system] && this.livefeedMap[call.system][tg];
|
||||
return !!this.livefeedMap[call.system] && this.livefeedMap[call.system][tg]?.active || false;
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -543,11 +579,19 @@ export class RdioScannerService implements OnDestroy {
|
|||
}
|
||||
|
||||
startLivefeed(): void {
|
||||
const lfm = Object.keys(this.livefeedMap).reduce((sysMap: { [key: number]: { [key: number]: boolean } }, sys) => {
|
||||
sysMap[+sys] = Object.keys(this.livefeedMap[+sys]).reduce((tgMap: { [key: number]: boolean }, tg: string) => {
|
||||
tgMap[+tg] = this.livefeedMap[+sys][+tg].active;
|
||||
return tgMap;
|
||||
}, {});
|
||||
return sysMap;
|
||||
}, {});
|
||||
|
||||
this.livefeedMode = RdioScannerLivefeedMode.Online;
|
||||
|
||||
this.event.emit({ livefeedMode: this.livefeedMode });
|
||||
|
||||
this.sendtoWebsocket(WebsocketCommand.LivefeedMap, this.livefeedMap);
|
||||
this.sendtoWebsocket(WebsocketCommand.LivefeedMap, lfm);
|
||||
}
|
||||
|
||||
stop(options?: { emit?: boolean }): void {
|
||||
|
|
@ -595,6 +639,12 @@ export class RdioScannerService implements OnDestroy {
|
|||
}
|
||||
|
||||
toggleCategory(category: RdioScannerCategory): void {
|
||||
const clearTimer = (lfm: RdioScannerLivefeed): void => {
|
||||
lfm.minutes = 0;
|
||||
lfm.timer?.unsubscribe();
|
||||
lfm.timer = undefined;
|
||||
};
|
||||
|
||||
if (category) {
|
||||
if (this.livefeedMapPriorToHoldSystem) {
|
||||
this.livefeedMapPriorToHoldSystem = undefined;
|
||||
|
|
@ -608,20 +658,22 @@ export class RdioScannerService implements OnDestroy {
|
|||
|
||||
this.config?.systems.forEach((sys) => {
|
||||
sys.talkgroups?.forEach((tg) => {
|
||||
if (category.type == RdioScannerCategoryType.Group && tg.group === category.label) {
|
||||
this.livefeedMap[sys.id][tg.id] = status;
|
||||
const lfm = this.livefeedMap[sys.id][tg.id];
|
||||
|
||||
if (category.type == RdioScannerCategoryType.Group && tg.group === category.label) {
|
||||
clearTimer(lfm);
|
||||
lfm.active = status;
|
||||
} else if (category.type == RdioScannerCategoryType.Tag && tg.tag === category.label) {
|
||||
this.livefeedMap[sys.id][tg.id] = status;
|
||||
clearTimer(lfm);
|
||||
lfm.active = status;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.rebuildCategories();
|
||||
|
||||
if (this.call && !this.livefeedMap[this.call.system] &&
|
||||
this.livefeedMap[this.call.system][this.call.talkgroup]) {
|
||||
|
||||
if (this.call && !this.livefeedMap[this.call.system] && this.livefeedMap[this.call.system][this.call.talkgroup]) {
|
||||
clearTimer(this.livefeedMap[this.call.system][this.call.talkgroup]);
|
||||
this.skip();
|
||||
}
|
||||
|
||||
|
|
@ -691,7 +743,7 @@ export class RdioScannerService implements OnDestroy {
|
|||
|
||||
private cleanQueue(): void {
|
||||
let isActive = (call: RdioScannerCall) => {
|
||||
let lfm = (sys: number, tg: number) => this.livefeedMap && this.livefeedMap[sys] && this.livefeedMap[sys][tg];
|
||||
let lfm = (sys: number, tg: number): boolean => this.livefeedMap && this.livefeedMap[sys] && this.livefeedMap[sys][tg]?.active;
|
||||
let active = lfm(call.system, call.talkgroup);
|
||||
if (!active && Array.isArray(call.patches)) {
|
||||
for (let i = 0; i < call.patches.length; i++) {
|
||||
|
|
@ -793,6 +845,7 @@ export class RdioScannerService implements OnDestroy {
|
|||
this.websocket.onmessage = (ev: MessageEvent) => this.parseWebsocketMessage(ev.data);
|
||||
}
|
||||
|
||||
this.sendtoWebsocket(WebsocketCommand.Version);
|
||||
this.sendtoWebsocket(WebsocketCommand.Config);
|
||||
};
|
||||
}
|
||||
|
|
@ -831,7 +884,9 @@ export class RdioScannerService implements OnDestroy {
|
|||
const config = message[1];
|
||||
|
||||
this.config = {
|
||||
branding: typeof config.branding === 'string' ? config.branding : '',
|
||||
dimmerDelay: typeof config.dimmerDelay === 'number' ? config.dimmerDelay : 5000,
|
||||
email: typeof config.email === 'string' ? config.email : '',
|
||||
groups: typeof config.groups !== null && typeof config.groups === 'object' ? config.groups : {},
|
||||
keypadBeeps: config.keypadBeeps !== null && typeof config.keypadBeeps === 'object' ? config.keypadBeeps : {},
|
||||
playbackGoesLive: typeof config.playbackGoesLive === 'boolean' ? config.playbackGoesLive : false,
|
||||
|
|
@ -897,6 +952,28 @@ export class RdioScannerService implements OnDestroy {
|
|||
case WebsocketCommand.Pin:
|
||||
this.event.emit({ auth: true });
|
||||
|
||||
break;
|
||||
|
||||
case WebsocketCommand.Version:
|
||||
var data = message[1];
|
||||
|
||||
if (data !== null && typeof data === 'object') {
|
||||
var branding = data['branding'];
|
||||
var email = data['email'];
|
||||
|
||||
if (typeof branding === 'string') {
|
||||
this.config.branding = branding;
|
||||
}
|
||||
|
||||
if (typeof email === 'string') {
|
||||
this.config.email = email;
|
||||
}
|
||||
|
||||
if (this.config.branding || this.config.email) {
|
||||
this.event.emit({ config: this.config });
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -969,11 +1046,11 @@ export class RdioScannerService implements OnDestroy {
|
|||
this.categories = Object.keys(this.config.groups || []).map((label) => {
|
||||
const allOff = Object.keys(this.config.groups[label]).map((sys) => +sys)
|
||||
.every((sys: number) => this.config.groups[label] && this.config.groups[label][sys]
|
||||
.every((tg) => this.livefeedMap[sys] && !this.livefeedMap[sys][tg]));
|
||||
.every((tg) => this.livefeedMap[sys] && !this.livefeedMap[sys][tg].active));
|
||||
|
||||
const allOn = Object.keys(this.config.groups[label]).map((sys) => +sys)
|
||||
.every((sys: number) => this.config.groups[label] && this.config.groups[label][sys]
|
||||
.every((tg) => this.livefeedMap[sys] && this.livefeedMap[sys][tg]));
|
||||
.every((tg) => this.livefeedMap[sys] && this.livefeedMap[sys][tg].active));
|
||||
|
||||
const status = allOff ? RdioScannerCategoryStatus.Off : allOn ? RdioScannerCategoryStatus.On : RdioScannerCategoryStatus.Partial;
|
||||
|
||||
|
|
@ -984,11 +1061,11 @@ export class RdioScannerService implements OnDestroy {
|
|||
this.categories = this.categories.concat(Object.keys(this.config.tags || []).map((label) => {
|
||||
const allOff = Object.keys(this.config.tags[label]).map((sys) => +sys)
|
||||
.every((sys: number) => this.config.tags[label] && this.config.tags[label][sys]
|
||||
.every((tg) => this.livefeedMap[sys] && !this.livefeedMap[sys][tg]));
|
||||
.every((tg) => this.livefeedMap[sys] && !this.livefeedMap[sys][tg].active));
|
||||
|
||||
const allOn = Object.keys(this.config.tags[label]).map((sys) => +sys)
|
||||
.every((sys: number) => this.config.tags[label] && this.config.tags[label][sys]
|
||||
.every((tg) => this.livefeedMap[sys] && this.livefeedMap[sys][tg]));
|
||||
.every((tg) => this.livefeedMap[sys] && this.livefeedMap[sys][tg].active));
|
||||
|
||||
const status = allOff ? RdioScannerCategoryStatus.Off : allOn ? RdioScannerCategoryStatus.On : RdioScannerCategoryStatus.Partial;
|
||||
|
||||
|
|
@ -1000,24 +1077,28 @@ export class RdioScannerService implements OnDestroy {
|
|||
}
|
||||
|
||||
private rebuildLivefeedMap(): void {
|
||||
const livefeedMap = this.config.systems.reduce((sysMap, sys) => {
|
||||
const lfm = this.config.systems.reduce((sysMap, sys) => {
|
||||
sysMap[sys.id] = sys.talkgroups.reduce((tgMap, tg) => {
|
||||
const state = this.livefeedMap && this.livefeedMap[sys.id] && this.livefeedMap[sys.id][tg.id];
|
||||
const group = this.categories.find((cat) => cat.label === tg.group);
|
||||
const tag = this.categories.find((cat) => cat.label === tg.tag);
|
||||
|
||||
tgMap[tg.id] = typeof state === 'boolean' ? state : true;
|
||||
tgMap[tg.id] = (this.livefeedMap[sys.id] && this.livefeedMap[sys.id][tg.id])
|
||||
? this.livefeedMap[sys.id][tg.id]
|
||||
: {
|
||||
active: !(group?.status === RdioScannerCategoryStatus.Off || tag?.status === RdioScannerCategoryStatus.Off),
|
||||
} as RdioScannerLivefeed;
|
||||
|
||||
return tgMap;
|
||||
}, sysMap[sys.id] || {});
|
||||
|
||||
}, sysMap[sys.id] || {} as { [key: number]: RdioScannerLivefeed });
|
||||
return sysMap;
|
||||
}, {} as RdioScannerLivefeedMap);
|
||||
|
||||
if (this.livefeedMapPriorToHoldSystem != null) {
|
||||
this.livefeedMapPriorToHoldSystem = livefeedMap;
|
||||
this.livefeedMapPriorToHoldSystem = lfm;
|
||||
} else if (this.livefeedMapPriorToHoldTalkgroup != null) {
|
||||
this.livefeedMapPriorToHoldTalkgroup = livefeedMap;
|
||||
this.livefeedMapPriorToHoldTalkgroup = lfm;
|
||||
} else {
|
||||
this.livefeedMap = livefeedMap;
|
||||
this.livefeedMap = lfm;
|
||||
}
|
||||
|
||||
this.storeLivefeedMap();
|
||||
|
|
@ -1032,16 +1113,17 @@ export class RdioScannerService implements OnDestroy {
|
|||
}
|
||||
|
||||
private restoreLivefeed(): void {
|
||||
const map = window?.localStorage?.getItem(RdioScannerService.LOCAL_STORAGE_KEY);
|
||||
|
||||
if (map) {
|
||||
try {
|
||||
this.livefeedMap = JSON.parse(map);
|
||||
const lfm = JSON.parse(window?.localStorage?.getItem(RdioScannerService.LOCAL_STORAGE_KEY) || "{}");
|
||||
|
||||
} catch (err) {
|
||||
this.livefeedMap = {};
|
||||
}
|
||||
}
|
||||
Object.keys(lfm).forEach((sys) => {
|
||||
Object.keys(lfm[sys]).forEach((tg) => {
|
||||
if (!this.livefeedMap[+sys]) this.livefeedMap[+sys] = {};
|
||||
if (!this.livefeedMap[+sys][+tg]) this.livefeedMap[+sys][+tg] = {} as RdioScannerLivefeed;
|
||||
this.livefeedMap[+sys][+tg].active = lfm[sys][tg];
|
||||
});
|
||||
});
|
||||
} catch (_) { }
|
||||
}
|
||||
|
||||
private sendtoWebsocket(command: string, payload?: unknown, flags?: string): void {
|
||||
|
|
@ -1061,7 +1143,15 @@ export class RdioScannerService implements OnDestroy {
|
|||
}
|
||||
|
||||
private storeLivefeedMap(): void {
|
||||
window?.localStorage?.setItem(RdioScannerService.LOCAL_STORAGE_KEY, JSON.stringify(this.livefeedMap));
|
||||
const lfm = Object.keys(this.livefeedMap).reduce((sysMap: { [key: number]: { [key: number]: boolean } }, sys: string) => {
|
||||
sysMap[+sys] = Object.keys(this.livefeedMap[+sys]).reduce((tgMap: { [key: number]: boolean }, tg: string) => {
|
||||
tgMap[+tg] = this.livefeedMap[+sys][+tg].active;
|
||||
return tgMap;
|
||||
}, {});
|
||||
return sysMap;
|
||||
}, {});
|
||||
|
||||
window?.localStorage?.setItem(RdioScannerService.LOCAL_STORAGE_KEY, JSON.stringify(lfm));
|
||||
}
|
||||
|
||||
private transformCall(call: RdioScannerCall): RdioScannerCall {
|
||||
|
|
|
|||
|
|
@ -17,12 +17,15 @@
|
|||
* ****************************************************************************
|
||||
*/
|
||||
|
||||
import { Subscription } from "rxjs";
|
||||
|
||||
export interface RdioScannerAvoidOptions {
|
||||
all?: boolean;
|
||||
call?: RdioScannerCall;
|
||||
minutes?: number;
|
||||
status?: boolean;
|
||||
system?: RdioScannerSystem;
|
||||
talkgroup?: RdioScannerTalkgroup;
|
||||
status?: boolean;
|
||||
}
|
||||
|
||||
export interface RdioScannerBeep {
|
||||
|
|
@ -90,7 +93,9 @@ export enum RdioScannerCategoryType {
|
|||
|
||||
export interface RdioScannerConfig {
|
||||
afs?: string;
|
||||
branding?: string;
|
||||
dimmerDelay: number | false;
|
||||
email?: string;
|
||||
groups: { [key: string]: { [key: number]: number[] } };
|
||||
keypadBeeps: RdioScannerKeypadBeeps | false;
|
||||
playbackGoesLive: boolean;
|
||||
|
|
@ -127,9 +132,15 @@ export interface RdioScannerKeypadBeeps {
|
|||
[RdioScannerBeepStyle.Denied]: RdioScannerBeep[];
|
||||
}
|
||||
|
||||
export interface RdioScannerLivefeed {
|
||||
active: boolean;
|
||||
minutes: number | undefined;
|
||||
timer: Subscription | undefined;
|
||||
}
|
||||
|
||||
export interface RdioScannerLivefeedMap {
|
||||
[key: string]: {
|
||||
[key: string]: boolean;
|
||||
[key: number]: {
|
||||
[key: number]: RdioScannerLivefeed;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
</legend>
|
||||
<div>
|
||||
<button *ngFor="let talkgroup of system.talkgroups" class="rdio-button"
|
||||
[ngClass]="{ off: !(map[system.id] && map[system.id][talkgroup.id]), on: map[system.id] && map[system.id][talkgroup.id] }"
|
||||
[ngClass]="{ off: !(map[system.id] && map[system.id][talkgroup.id])?.active, on: map[system.id] && map[system.id][talkgroup.id]?.active, blink: map[system.id][talkgroup.id]?.minutes }"
|
||||
(click)="avoid({ system: system, talkgroup: talkgroup })">
|
||||
{{ talkgroup.label }}
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
|
|||
rdio@macos ~ % mkdir rdio-scanner
|
||||
rdio@macos ~ % cd rdio-scanner
|
||||
rdio@macos rdio-scanner % unzip \
|
||||
> ~/Downloads/rdio-scanner-darwin-arm64-v6.4.5.zip
|
||||
Archive: /Users/rdio/Downloads/rdio-scanner-darwin-arm64-v6.4.5.zip
|
||||
> ~/Downloads/rdio-scanner-darwin-arm64-v6.5.0.zip
|
||||
Archive: /Users/rdio/Downloads/rdio-scanner-darwin-arm64-v6.5.0.zip
|
||||
inflating: rdio-scanner
|
||||
inflating: rdio-scanner.pdf
|
||||
|
||||
|
|
@ -51,11 +51,11 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
|
|||
|
||||
rdio@macos rdio-scanner % ./rdio-scanner
|
||||
|
||||
Rdio Scanner v6.4.5
|
||||
Rdio Scanner v6.5.0
|
||||
----------------------------------
|
||||
2022/06/14 08:38:06 server started
|
||||
2022/06/14 08:38:06 main interface at http://macos.local:3000
|
||||
2022/06/14 08:38:06 admin interface at http://macos.local:3000/admin
|
||||
2022/06/27 08:38:06 server started
|
||||
2022/06/27 08:38:06 main interface at http://macos.local:3000
|
||||
2022/06/27 08:38:06 admin interface at http://macos.local:3000/admin
|
||||
|
||||
4. Access the administrative dashboard to finalize the configuration.
|
||||
|
||||
|
|
@ -77,11 +77,11 @@ Here we want our [Rdio Scanner](https://guthub.com/chuot/rdio-scanner) instance
|
|||
|
||||
rdio@macos rdio-scanner % ./rdio-scanner --listen :80
|
||||
|
||||
Rdio Scanner v6.4.5
|
||||
Rdio Scanner v6.5.0
|
||||
----------------------------------
|
||||
2022/06/14 08:48:03 server started
|
||||
2022/06/14 08:48:03 main interface at http://macos.local
|
||||
2022/06/14 08:48:03 admin interface at http://macos.local/admin
|
||||
2022/06/27 08:48:03 server started
|
||||
2022/06/27 08:48:03 main interface at http://macos.local
|
||||
2022/06/27 08:48:03 admin interface at http://macos.local/admin
|
||||
|
||||
## Listening on a SSL port
|
||||
|
||||
|
|
@ -95,12 +95,12 @@ You can use your own SSL certificates that match your domain name with `-ssl_cer
|
|||
> -ssl_key_file mykey.key \
|
||||
> -ssl_listen :443
|
||||
|
||||
Rdio Scanner v6.4.5
|
||||
Rdio Scanner v6.5.0
|
||||
----------------------------------
|
||||
2022/06/14 08:50:58 server started
|
||||
2022/06/14 08:50:58 main interface at http://macos.local
|
||||
2022/06/14 08:50:58 main interface at https://macos.local
|
||||
2022/06/14 08:50:58 admin interface at https://macos.local/admin
|
||||
2022/06/27 08:50:58 server started
|
||||
2022/06/27 08:50:58 main interface at http://macos.local
|
||||
2022/06/27 08:50:58 main interface at https://macos.local
|
||||
2022/06/27 08:50:58 admin interface at https://macos.local/admin
|
||||
|
||||
If you don't want to worry about SSL certificates, you can use the built-in Let's Encrypt auto-cert feature. This requires that you have both port 80 (HTTP) and port 443 (HTTPS) open to the world. Also, your domain name should point to your IP address where [Rdio Scanner](https://github.com/chuot/rdio-scanner/) is running. The advantage of this approach is that everything is done automatically, no certificate request, no certificate renewal.
|
||||
|
||||
|
|
@ -118,7 +118,7 @@ You don't want to have to type everytime a long list of arguments. No problem, y
|
|||
> -ssl_auto_cert mydomain.com \
|
||||
> -ssl_listen :443 \
|
||||
> -config_save
|
||||
2022/06/14 08:52:24 rdio-scanner.ini file created
|
||||
2022/06/27 08:52:24 rdio-scanner.ini file created
|
||||
|
||||
All of your parameters passed as arguments to [Rdio Scanner](https://github.com/chuot/rdio-scanner) have been saved to an INI file which has the same arguments/values list.
|
||||
|
||||
|
|
@ -132,12 +132,12 @@ Then simply run [Rdio Scanner](https://github.com/chuot/rdio-scanner) without an
|
|||
|
||||
rdio@macos rdio-scanner % ./rdio-scanner
|
||||
|
||||
Rdio Scanner v6.4.5
|
||||
Rdio Scanner v6.5.0
|
||||
----------------------------------
|
||||
2022/06/14 08:54:08 server started
|
||||
2022/06/14 08:54:08 main interface at http://macos.local
|
||||
2022/06/14 08:54:08 main interface at https://macos.local
|
||||
2022/06/14 08:54:08 admin interface at https://macos.local/admin
|
||||
2022/06/27 08:54:08 server started
|
||||
2022/06/27 08:54:08 main interface at http://macos.local
|
||||
2022/06/27 08:54:08 main interface at https://macos.local
|
||||
2022/06/27 08:54:08 admin interface at https://macos.local/admin
|
||||
|
||||
## Install Rdio Scanner as a service
|
||||
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
|
|||
rdio@pc-freebsd:~ $ mkdir rdio-scanner
|
||||
rdio@pc-freebsd:~ $ cd rdio-scanner
|
||||
rdio@pc-freebsd:~/rdio-scanner $ unzip \
|
||||
> ~/rdio-scanner-freebsd-amd64-v6.4.5.zip
|
||||
Archive: ../rdio-scanner-freebsd-amd64-v6.4.5.zip
|
||||
> ~/rdio-scanner-freebsd-amd64-v6.5.0.zip
|
||||
Archive: ../rdio-scanner-freebsd-amd64-v6.5.0.zip
|
||||
extracting: rdio-scanner
|
||||
extracting: rdio-scanner.pdf
|
||||
|
||||
|
|
@ -51,11 +51,11 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
|
|||
|
||||
rdio@pc-freebsd:~/rdio-scanner $ ./rdio-scanner
|
||||
|
||||
Rdio Scanner v6.4.5
|
||||
Rdio Scanner v6.5.0
|
||||
----------------------------------
|
||||
2022/06/14 08:16:36 server started
|
||||
2022/06/14 08:16:36 main interface at http://pc-freebsd:3000
|
||||
2022/06/14 08:16:36 admin interface at http://pc-freebsd:3000/admin
|
||||
2022/06/27 08:16:36 server started
|
||||
2022/06/27 08:16:36 main interface at http://pc-freebsd:3000
|
||||
2022/06/27 08:16:36 admin interface at http://pc-freebsd:3000/admin
|
||||
|
||||
4. Access the administrative dashboard to finalize the configuration.
|
||||
|
||||
|
|
@ -81,11 +81,11 @@ Here we want our [Rdio Scanner](https://guthub.com/chuot/rdio-scanner) instance
|
|||
Password:
|
||||
root@pc-freebsd:/home/rdio/rdio-scanner # ./rdio-scanner -listen :80
|
||||
|
||||
Rdio Scanner v6.4.5
|
||||
Rdio Scanner v6.5.0
|
||||
----------------------------------
|
||||
2022/06/14 08:19:38 server started
|
||||
2022/06/14 08:19:38 main interface at http://pc-freebsd
|
||||
2022/06/14 08:19:38 admin interface at http://pc-freebsd/admin
|
||||
2022/06/27 08:19:38 server started
|
||||
2022/06/27 08:19:38 main interface at http://pc-freebsd
|
||||
2022/06/27 08:19:38 admin interface at http://pc-freebsd/admin
|
||||
|
||||
## Listening on a SSL port
|
||||
|
||||
|
|
@ -101,12 +101,12 @@ You can use your own SSL certificates that match your domain name with `-ssl_cer
|
|||
> -ssl_key_file mykey.key \
|
||||
> -ssl_listen :443
|
||||
|
||||
Rdio Scanner v6.4.5
|
||||
Rdio Scanner v6.5.0
|
||||
----------------------------------
|
||||
2022/06/14 10:34:29 server started
|
||||
2022/06/14 10:34:29 main interface at http://pc-freebsd
|
||||
2022/06/14 10:34:29 main interface at https://pc-freebsd
|
||||
2022/06/14 10:34:29 admin interface at https://pc-freebsd/admin
|
||||
2022/06/27 10:34:29 server started
|
||||
2022/06/27 10:34:29 main interface at http://pc-freebsd
|
||||
2022/06/27 10:34:29 main interface at https://pc-freebsd
|
||||
2022/06/27 10:34:29 admin interface at https://pc-freebsd/admin
|
||||
|
||||
If you don't want to worry about SSL certificates, you can use the built-in Let's Encrypt auto-cert feature. This requires that you have both port 80 (HTTP) and port 443 (HTTPS) open to the world. Also, your domain name should point to your IP address where [Rdio Scanner](https://github.com/chuot/rdio-scanner/) is running. The advantage of this approach is that everything is done automatically, no certificate request, no certificate renewal.
|
||||
|
||||
|
|
@ -126,7 +126,7 @@ You don't want to have to type everytime a long list of arguments. No problem, y
|
|||
> -ssl_auto_cert mydomain.com \
|
||||
> -ssl_listen :443 \
|
||||
> -config_save
|
||||
2022/06/14 10:37:00 rdio-scanner.ini file created
|
||||
2022/06/27 10:37:00 rdio-scanner.ini file created
|
||||
|
||||
All of your parameters passed as arguments to [Rdio Scanner](https://github.com/chuot/rdio-scanner) have been saved to an INI file which has the same arguments/values list.
|
||||
|
||||
|
|
@ -142,12 +142,12 @@ Then simply run [Rdio Scanner](https://github.com/chuot/rdio-scanner) without an
|
|||
Password:
|
||||
root@pc-freebsd:/home/rdio/rdio-scanner # ./rdio-scanner
|
||||
|
||||
Rdio Scanner v6.4.5
|
||||
Rdio Scanner v6.5.0
|
||||
----------------------------------
|
||||
2022/06/14 10:38:28 server started
|
||||
2022/06/14 10:38:29 main interface at http://pc-freebsd
|
||||
2022/06/14 10:38:29 main interface at https://pc-freebsd
|
||||
2022/06/14 10:38:29 admin interface at https://pc-freebsd/admin
|
||||
2022/06/27 10:38:28 server started
|
||||
2022/06/27 10:38:29 main interface at http://pc-freebsd
|
||||
2022/06/27 10:38:29 main interface at https://pc-freebsd
|
||||
2022/06/27 10:38:29 admin interface at https://pc-freebsd/admin
|
||||
|
||||
## Install Rdio Scanner as a service
|
||||
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
|
|||
[rdio@pc-linux ~]$ mkdir rdio-scanner
|
||||
[rdio@pc-linux ~]$ cd rdio-scanner
|
||||
[rdio@pc-linux rdio-scanner]$ unzip \
|
||||
> ~/Downloads/rdio-scanner-linux-amd64-v6.4.5.zip
|
||||
Archive: /home/rdio/Downloads/rdio-scanner-linux-amd64-v6.4.5.zip
|
||||
> ~/Downloads/rdio-scanner-linux-amd64-v6.5.0.zip
|
||||
Archive: /home/rdio/Downloads/rdio-scanner-linux-amd64-v6.5.0.zip
|
||||
inflating: rdio-scanner
|
||||
inflating: rdio-scanner.pdf
|
||||
|
||||
|
|
@ -51,11 +51,11 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
|
|||
|
||||
[rdio@pc-linux rdio-scanner]$ ./rdio-scanner
|
||||
|
||||
Rdio Scanner v6.4.5
|
||||
Rdio Scanner v6.5.0
|
||||
----------------------------------
|
||||
2022/06/14 09:11:48 server started
|
||||
2022/06/14 09:11:48 main interface at http://pc-linux:3000
|
||||
2022/06/14 09:11:48 admin interface at http://pc-linux:3000/admin
|
||||
2022/06/27 09:11:48 server started
|
||||
2022/06/27 09:11:48 main interface at http://pc-linux:3000
|
||||
2022/06/27 09:11:48 admin interface at http://pc-linux:3000/admin
|
||||
|
||||
4. Access the administrative dashboard to finalize the configuration.
|
||||
|
||||
|
|
@ -78,11 +78,11 @@ Here we want our [Rdio Scanner](https://guthub.com/chuot/rdio-scanner) instance
|
|||
[rdio@pc-linux rdio-scanner]$ sudo ./rdio-scanner -listen :80
|
||||
[sudo] password for rdio:
|
||||
|
||||
Rdio Scanner v6.4.5
|
||||
Rdio Scanner v6.5.0
|
||||
----------------------------------
|
||||
2022/06/14 09:14:00 server started
|
||||
2022/06/14 09:14:00 main interface at http://pc-linux
|
||||
2022/06/14 09:14:00 admin interface at http://pc-linux/admin
|
||||
2022/06/27 09:14:00 server started
|
||||
2022/06/27 09:14:00 main interface at http://pc-linux
|
||||
2022/06/27 09:14:00 admin interface at http://pc-linux/admin
|
||||
|
||||
## Listening on a SSL port
|
||||
|
||||
|
|
@ -97,12 +97,12 @@ You can use your own SSL certificates that match your domain name with `-ssl_cer
|
|||
> -ssl_listen :443
|
||||
[sudo] password for rdio:
|
||||
|
||||
Rdio Scanner v6.4.5
|
||||
Rdio Scanner v6.5.0
|
||||
----------------------------------
|
||||
2022/06/14 09:16:47 server started
|
||||
2022/06/14 09:16:47 main interface at http://pc-linux
|
||||
2022/06/14 09:16:47 main interface at https://pc-linux
|
||||
2022/06/14 09:16:47 admin interface at https://pc-linux/admin
|
||||
2022/06/27 09:16:47 server started
|
||||
2022/06/27 09:16:47 main interface at http://pc-linux
|
||||
2022/06/27 09:16:47 main interface at https://pc-linux
|
||||
2022/06/27 09:16:47 admin interface at https://pc-linux/admin
|
||||
|
||||
If you don't want to worry about SSL certificates, you can use the built-in Let's Encrypt auto-cert feature. This requires that you have both port 80 (HTTP) and port 443 (HTTPS) open to the world. Also, your domain name should point to your IP address where [Rdio Scanner](https://github.com/chuot/rdio-scanner/) is running. The advantage of this approach is that everything is done automatically, no certificate request, no certificate renewal.
|
||||
|
||||
|
|
@ -120,7 +120,7 @@ You don't want to have to type everytime a long list of arguments. No problem, y
|
|||
> -ssl_auto_cert mydomain.com \
|
||||
> -ssl_listen :443 \
|
||||
> -config_save
|
||||
2022/06/14 09:19:29 rdio-scanner.ini file created
|
||||
2022/06/27 09:19:29 rdio-scanner.ini file created
|
||||
|
||||
All of your parameters passed as arguments to [Rdio Scanner](https://github.com/chuot/rdio-scanner) have been saved to an INI file which has the same arguments/values list.
|
||||
|
||||
|
|
@ -135,12 +135,12 @@ Then simply run [Rdio Scanner](https://github.com/chuot/rdio-scanner) without an
|
|||
[rdio@pc-linux rdio-scanner]$ sudo ./rdio-scanner
|
||||
[sudo] Mot de passe de rdio :
|
||||
|
||||
Rdio Scanner v6.4.5
|
||||
Rdio Scanner v6.5.0
|
||||
----------------------------------
|
||||
2022/06/14 09:20:40 server started
|
||||
2022/06/14 09:20:40 main interface at http://pc-linux
|
||||
2022/06/14 09:20:40 main interface at https://pc-linux
|
||||
2022/06/14 09:20:40 admin interface at https://pc-linux/admin
|
||||
2022/06/27 09:20:40 server started
|
||||
2022/06/27 09:20:40 main interface at http://pc-linux
|
||||
2022/06/27 09:20:40 main interface at https://pc-linux
|
||||
2022/06/27 09:20:40 admin interface at https://pc-linux/admin
|
||||
|
||||
## Install Rdio Scanner as a service
|
||||
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
|
|||
C:\Users\rdio> cd rdio-scanner
|
||||
|
||||
C:\Users\rdio\rdio-scanner> tar -xvf ^
|
||||
..\downloads\rdio-scanner-windows-amd64-v6.4.5.zip
|
||||
..\downloads\rdio-scanner-windows-amd64-v6.5.0.zip
|
||||
x rdio-scanner.exe
|
||||
x rdio-scanner.pdf
|
||||
|
||||
|
|
@ -52,11 +52,11 @@ ALWAYS DOWNLOAD THE LATEST VERSION OF [RDIO SCANNER](https://github.com/chuot/rd
|
|||
|
||||
C:\Users\rdio\rdio-scanner>rdio-scanner
|
||||
|
||||
Rdio Scanner v6.4.5
|
||||
Rdio Scanner v6.5.0
|
||||
----------------------------------
|
||||
2022/06/14 13:48:48 server started
|
||||
2022/06/14 13:48:48 main interface at http://pc-windows:3000
|
||||
2022/06/14 13:48:48 admin interface at http://pc-windows:3000/admin
|
||||
2022/06/27 13:48:48 server started
|
||||
2022/06/27 13:48:48 main interface at http://pc-windows:3000
|
||||
2022/06/27 13:48:48 admin interface at http://pc-windows:3000/admin
|
||||
|
||||
4. Access the administrative dashboard to finalize the configuration.
|
||||
|
||||
|
|
@ -78,11 +78,11 @@ Here we want our [Rdio Scanner](https://guthub.com/chuot/rdio-scanner) instance
|
|||
|
||||
C:\Users\rdio\rdio-scanner>rdio-scanner -listen :80
|
||||
|
||||
Rdio Scanner v6.4.5
|
||||
Rdio Scanner v6.5.0
|
||||
----------------------------------
|
||||
2022/06/14 10:53:31 server started
|
||||
2022/06/14 10:53:31 main interface at http://pc-windows
|
||||
2022/06/14 10:53:31 admin interface at http://pc-windows/admin
|
||||
2022/06/27 10:53:31 server started
|
||||
2022/06/27 10:53:31 main interface at http://pc-windows
|
||||
2022/06/27 10:53:31 admin interface at http://pc-windows/admin
|
||||
|
||||
## Listening on a SSL port
|
||||
|
||||
|
|
@ -96,12 +96,12 @@ You can use your own SSL certificates that match your domain name with `-ssl_cer
|
|||
-ssl_key_file meykey.key ^
|
||||
-ssl_listen :443
|
||||
|
||||
Rdio Scanner v6.4.5
|
||||
Rdio Scanner v6.5.0
|
||||
----------------------------------
|
||||
2022/06/14 11:05:43 server started
|
||||
2022/06/14 11:05:43 main interface at http://pc-windows
|
||||
2022/06/14 11:05:43 main interface at https://pc-windows
|
||||
2022/06/14 11:05:43 admin interface at https://pc-windows/admin
|
||||
2022/06/27 11:05:43 server started
|
||||
2022/06/27 11:05:43 main interface at http://pc-windows
|
||||
2022/06/27 11:05:43 main interface at https://pc-windows
|
||||
2022/06/27 11:05:43 admin interface at https://pc-windows/admin
|
||||
|
||||
If you don't want to worry about SSL certificates, you can use the built-in Let's Encrypt auto-cert feature. This requires that you have both port 80 (HTTP) and port 443 (HTTPS) open to the world. Also, your domain name should point to your IP address where [Rdio Scanner](https://github.com/chuot/rdio-scanner/) is running. The advantage of this approach is that everything is done automatically, no certificate request, no certificate renewal.
|
||||
|
||||
|
|
@ -119,7 +119,7 @@ You don't want to have to type everytime a long list of arguments. No problem, y
|
|||
-ssl_auto_cert mydomain.com ^
|
||||
-ssl_listen :443 ^
|
||||
-config_save
|
||||
2022/06/14 11:08:28 rdio-scanner.ini file created
|
||||
2022/06/27 11:08:28 rdio-scanner.ini file created
|
||||
|
||||
All of your parameters passed as arguments to [Rdio Scanner](https://github.com/chuot/rdio-scanner) have been saved to an INI file which has the same arguments/values list.
|
||||
|
||||
|
|
@ -133,12 +133,12 @@ Then simply run [Rdio Scanner](https://github.com/chuot/rdio-scanner) without an
|
|||
|
||||
C:\Users\rdio\rdio-scanner>rdio-scanner
|
||||
|
||||
Rdio Scanner v6.4.5
|
||||
Rdio Scanner v6.5.0
|
||||
----------------------------------
|
||||
2022/06/14 11:11:28 server started
|
||||
2022/06/14 11:11:28 main interface at http://pc-windows
|
||||
2022/06/14 11:11:28 main interface at https://pc-windows
|
||||
2022/06/14 11:11:28 admin interface at https://pc-windows/admin
|
||||
2022/06/27 11:11:28 server started
|
||||
2022/06/27 11:11:28 main interface at http://pc-windows
|
||||
2022/06/27 11:11:28 main interface at https://pc-windows
|
||||
2022/06/27 11:11:28 admin interface at https://pc-windows/admin
|
||||
|
||||
\pagebreak{}
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ The color is _green_ by default, but can be customized by the system or by the t
|
|||
|  | Temporarily maintain the current talkgroup in live feed mode. |
|
||||
|  | Replay the current audio from the beginning or the previous one if there is none active. Press repeatedly to replay items displayed in the history section. For example, press three times to replay the third history item. |
|
||||
|  | Stop the audio currently playing and play the next one in the listening queue. This is useful when playing boring or encrypted sound. |
|
||||
|  | Activate and deactivate the talkgroup from the current or previous audio. |
|
||||
|  | Activate and deactivate the talkgroup from the current or previous audio. Pressing the **AVOID** button multiple times when on the same call will go through **AVOID** -> **AVOID 30M** -> **AVOID 60M** -> **AVOID 120M** where the last ones will be AVOID for a specific number of minutes. |
|
||||
|  | Display the archived audio panel. |
|
||||
|  | Stop playing queue audio. Useful if you have to answer the phone without losing what's queued up for playing. |
|
||||
|  | Display the systems/talkgroups selection panel where you decide which audio you want to listen to in _LIVE FEED_ mode, not in playback mode. |
|
||||
|
|
@ -83,9 +83,9 @@ The first section concerns group selection. This section can be disabled from th
|
|||
This is much like the group section, but for each system. There is also just two states for each button:
|
||||
|
||||
| Button | State |
|
||||
| ------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
||||
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
|  | The talkgroup from this system is active. If you press it while it is active, it will make the talkgroup inactive. |
|
||||
|  | The talkgroup from this system is inactive. If you press it while it is inactive, it will make the talkgroup active. |
|
||||
|  | The talkgroup from this system is inactive. If you press it while it is inactive, it will make the talkgroup active. Note that if the LED is flashing, it means that the talkgroup is avoided for a limited period. |
|
||||
|  | Make inactive every talkgroups from this system. |
|
||||
|  | Make active every talkgroups from this system. |
|
||||
|
||||
|
|
|
|||
|
|
@ -25,20 +25,20 @@ import (
|
|||
)
|
||||
|
||||
type Access struct {
|
||||
Id interface{} `json:"_id"`
|
||||
Id any `json:"_id"`
|
||||
Code string `json:"code"`
|
||||
Expiration interface{} `json:"expiration"`
|
||||
Expiration any `json:"expiration"`
|
||||
Ident string `json:"ident"`
|
||||
Limit interface{} `json:"limit"`
|
||||
Order interface{} `json:"order"`
|
||||
Systems interface{} `json:"systems"`
|
||||
Limit any `json:"limit"`
|
||||
Order any `json:"order"`
|
||||
Systems any `json:"systems"`
|
||||
}
|
||||
|
||||
func NewAccess() *Access {
|
||||
return &Access{Systems: "*"}
|
||||
}
|
||||
|
||||
func (access *Access) FromMap(m map[string]interface{}) *Access {
|
||||
func (access *Access) FromMap(m map[string]any) *Access {
|
||||
switch v := m["_id"].(type) {
|
||||
case float64:
|
||||
access.Id = uint(v)
|
||||
|
|
@ -72,7 +72,7 @@ func (access *Access) FromMap(m map[string]interface{}) *Access {
|
|||
}
|
||||
|
||||
switch v := m["systems"].(type) {
|
||||
case []interface{}:
|
||||
case []any:
|
||||
if b, err := json.Marshal(v); err == nil {
|
||||
access.Systems = string(b)
|
||||
}
|
||||
|
|
@ -86,10 +86,10 @@ func (access *Access) FromMap(m map[string]interface{}) *Access {
|
|||
func (access *Access) HasAccess(call *Call) bool {
|
||||
if access.Systems != nil {
|
||||
switch v := access.Systems.(type) {
|
||||
case []interface{}:
|
||||
case []any:
|
||||
for _, f := range v {
|
||||
switch v := f.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
switch id := v["id"].(type) {
|
||||
case float64:
|
||||
if id == float64(call.System) {
|
||||
|
|
@ -98,7 +98,7 @@ func (access *Access) HasAccess(call *Call) bool {
|
|||
if tg == "*" {
|
||||
return true
|
||||
}
|
||||
case []interface{}:
|
||||
case []any:
|
||||
for _, f := range tg {
|
||||
switch tg := f.(type) {
|
||||
case float64:
|
||||
|
|
@ -166,7 +166,7 @@ func (accesses *Accesses) Add(access *Access) (*Accesses, bool) {
|
|||
return accesses, added
|
||||
}
|
||||
|
||||
func (accesses *Accesses) FromMap(f []interface{}) *Accesses {
|
||||
func (accesses *Accesses) FromMap(f []any) *Accesses {
|
||||
accesses.mutex.Lock()
|
||||
defer accesses.mutex.Unlock()
|
||||
|
||||
|
|
@ -174,7 +174,7 @@ func (accesses *Accesses) FromMap(f []interface{}) *Accesses {
|
|||
|
||||
for _, r := range f {
|
||||
switch m := r.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
access := &Access{}
|
||||
access.FromMap(m)
|
||||
accesses.List = append(accesses.List, access)
|
||||
|
|
@ -207,7 +207,7 @@ func (accesses *Accesses) IsRestricted() bool {
|
|||
func (accesses *Accesses) Read(db *Database) error {
|
||||
var (
|
||||
err error
|
||||
expiration interface{}
|
||||
expiration any
|
||||
id sql.NullFloat64
|
||||
limit sql.NullFloat64
|
||||
order sql.NullFloat64
|
||||
|
|
@ -261,7 +261,7 @@ func (accesses *Accesses) Read(db *Database) error {
|
|||
}
|
||||
|
||||
if err = json.Unmarshal([]byte(systems), &access.Systems); err != nil {
|
||||
access.Systems = []interface{}{}
|
||||
access.Systems = []any{}
|
||||
}
|
||||
|
||||
accesses.List = append(accesses.List, access)
|
||||
|
|
@ -298,7 +298,7 @@ func (accesses *Accesses) Write(db *Database) error {
|
|||
err error
|
||||
rows *sql.Rows
|
||||
rowIds = []uint{}
|
||||
systems interface{}
|
||||
systems any
|
||||
)
|
||||
|
||||
accesses.mutex.Lock()
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ func (admin *Admin) BroadcastConfig() {
|
|||
}
|
||||
}
|
||||
|
||||
func (admin *Admin) ChangePassword(currentPassword interface{}, newPassword string) error {
|
||||
func (admin *Admin) ChangePassword(currentPassword any, newPassword string) error {
|
||||
var (
|
||||
err error
|
||||
hash []byte
|
||||
|
|
@ -158,20 +158,20 @@ func (admin *Admin) ConfigHandler(w http.ResponseWriter, r *http.Request) {
|
|||
admin.SendConfig(w)
|
||||
|
||||
case http.MethodPut:
|
||||
m := map[string]interface{}{}
|
||||
m := map[string]any{}
|
||||
err := json.NewDecoder(r.Body).Decode(&m)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
admin.Controller.IngestLock()
|
||||
admin.mutex.Lock()
|
||||
defer admin.mutex.Unlock()
|
||||
|
||||
admin.Controller.Dirwatches.Stop()
|
||||
|
||||
switch v := m["access"].(type) {
|
||||
case []interface{}:
|
||||
case []any:
|
||||
admin.Controller.Accesses.FromMap(v)
|
||||
err := admin.Controller.Accesses.Write(admin.Controller.Database)
|
||||
if err != nil {
|
||||
|
|
@ -185,7 +185,7 @@ func (admin *Admin) ConfigHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
switch v := m["apiKeys"].(type) {
|
||||
case []interface{}:
|
||||
case []any:
|
||||
admin.Controller.Apikeys.FromMap(v)
|
||||
err = admin.Controller.Apikeys.Write(admin.Controller.Database)
|
||||
if err != nil {
|
||||
|
|
@ -199,7 +199,7 @@ func (admin *Admin) ConfigHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
switch v := m["dirWatch"].(type) {
|
||||
case []interface{}:
|
||||
case []any:
|
||||
admin.Controller.Dirwatches.FromMap(v)
|
||||
err = admin.Controller.Dirwatches.Write(admin.Controller.Database)
|
||||
if err != nil {
|
||||
|
|
@ -213,7 +213,7 @@ func (admin *Admin) ConfigHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
switch v := m["downstreams"].(type) {
|
||||
case []interface{}:
|
||||
case []any:
|
||||
admin.Controller.Downstreams.FromMap(v)
|
||||
err = admin.Controller.Downstreams.Write(admin.Controller.Database)
|
||||
if err != nil {
|
||||
|
|
@ -227,7 +227,7 @@ func (admin *Admin) ConfigHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
switch v := m["groups"].(type) {
|
||||
case []interface{}:
|
||||
case []any:
|
||||
admin.Controller.Groups.FromMap(v)
|
||||
err = admin.Controller.Groups.Write(admin.Controller.Database)
|
||||
if err != nil {
|
||||
|
|
@ -241,7 +241,7 @@ func (admin *Admin) ConfigHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
switch v := m["options"].(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
admin.Controller.Options.FromMap(v)
|
||||
err = admin.Controller.Options.Write(admin.Controller.Database)
|
||||
if err != nil {
|
||||
|
|
@ -250,7 +250,7 @@ func (admin *Admin) ConfigHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
switch v := m["systems"].(type) {
|
||||
case []interface{}:
|
||||
case []any:
|
||||
admin.Controller.Systems.FromMap(v)
|
||||
err = admin.Controller.Systems.Write(admin.Controller.Database)
|
||||
if err != nil {
|
||||
|
|
@ -264,7 +264,7 @@ func (admin *Admin) ConfigHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
switch v := m["tags"].(type) {
|
||||
case []interface{}:
|
||||
case []any:
|
||||
admin.Controller.Tags.FromMap(v)
|
||||
err = admin.Controller.Tags.Write(admin.Controller.Database)
|
||||
if err != nil {
|
||||
|
|
@ -277,9 +277,6 @@ func (admin *Admin) ConfigHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
admin.mutex.Unlock()
|
||||
admin.Controller.IngestUnlock()
|
||||
|
||||
admin.Controller.EmitConfig()
|
||||
admin.Controller.Dirwatches.Start(admin.Controller)
|
||||
|
||||
|
|
@ -297,10 +294,10 @@ func (admin *Admin) GetAuthorization(r *http.Request) string {
|
|||
return r.Header.Get("Authorization")
|
||||
}
|
||||
|
||||
func (admin *Admin) GetConfig() map[string]interface{} {
|
||||
systems := []map[string]interface{}{}
|
||||
func (admin *Admin) GetConfig() map[string]any {
|
||||
systems := []map[string]any{}
|
||||
for _, system := range admin.Controller.Systems.List {
|
||||
systems = append(systems, map[string]interface{}{
|
||||
systems = append(systems, map[string]any{
|
||||
"_id": system.RowId,
|
||||
"autoPopulate": system.AutoPopulate,
|
||||
"blacklists": system.Blacklists,
|
||||
|
|
@ -313,7 +310,7 @@ func (admin *Admin) GetConfig() map[string]interface{} {
|
|||
})
|
||||
}
|
||||
|
||||
return map[string]interface{}{
|
||||
return map[string]any{
|
||||
"access": admin.Controller.Accesses.List,
|
||||
"apiKeys": admin.Controller.Apikeys.List,
|
||||
"dirWatch": admin.Controller.Dirwatches.List,
|
||||
|
|
@ -334,7 +331,7 @@ func (admin *Admin) LogsHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
switch r.Method {
|
||||
case http.MethodPost:
|
||||
m := map[string]interface{}{}
|
||||
m := map[string]any{}
|
||||
err := json.NewDecoder(r.Body).Decode(&m)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
|
|
@ -372,7 +369,7 @@ func (admin *Admin) LogsHandler(w http.ResponseWriter, r *http.Request) {
|
|||
func (admin *Admin) LoginHandler(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case http.MethodPost:
|
||||
m := map[string]interface{}{}
|
||||
m := map[string]any{}
|
||||
|
||||
if err := json.NewDecoder(r.Body).Decode(&m); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
|
|
@ -441,7 +438,7 @@ func (admin *Admin) LoginHandler(w http.ResponseWriter, r *http.Request) {
|
|||
admin.Tokens = append(admin.Tokens[1:], sToken)
|
||||
}
|
||||
|
||||
b, err := json.Marshal(map[string]interface{}{
|
||||
b, err := json.Marshal(map[string]any{
|
||||
"passwordNeedChange": true,
|
||||
"token": sToken,
|
||||
})
|
||||
|
|
@ -488,7 +485,7 @@ func (admin *Admin) PasswordHandler(w http.ResponseWriter, r *http.Request) {
|
|||
case http.MethodPost:
|
||||
var (
|
||||
b []byte
|
||||
currentPassword interface{}
|
||||
currentPassword any
|
||||
newPassword string
|
||||
)
|
||||
|
||||
|
|
@ -502,7 +499,7 @@ func (admin *Admin) PasswordHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
m := map[string]interface{}{}
|
||||
m := map[string]any{}
|
||||
err := json.NewDecoder(r.Body).Decode(&m)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
|
|
@ -528,7 +525,7 @@ func (admin *Admin) PasswordHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if b, err = json.Marshal(map[string]interface{}{"passwordNeedChange": admin.Controller.Options.adminPasswordNeedChange}); err == nil {
|
||||
if b, err = json.Marshal(map[string]any{"passwordNeedChange": admin.Controller.Options.adminPasswordNeedChange}); err == nil {
|
||||
w.Write(b)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusExpectationFailed)
|
||||
|
|
@ -540,16 +537,16 @@ func (admin *Admin) PasswordHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func (admin *Admin) SendConfig(w http.ResponseWriter) {
|
||||
var m map[string]interface{}
|
||||
var m map[string]any
|
||||
_, docker := os.LookupEnv("DOCKER")
|
||||
if docker {
|
||||
m = map[string]interface{}{
|
||||
m = map[string]any{
|
||||
"config": admin.GetConfig(),
|
||||
"docker": docker,
|
||||
"passwordNeedChange": admin.Controller.Options.adminPasswordNeedChange,
|
||||
}
|
||||
} else {
|
||||
m = map[string]interface{}{
|
||||
m = map[string]any{
|
||||
"config": admin.GetConfig(),
|
||||
"passwordNeedChange": admin.Controller.Options.adminPasswordNeedChange,
|
||||
}
|
||||
|
|
@ -611,7 +608,7 @@ func (admin *Admin) UserAddHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
m := map[string]interface{}{}
|
||||
m := map[string]any{}
|
||||
err := json.NewDecoder(r.Body).Decode(&m)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
|
|
@ -651,7 +648,7 @@ func (admin *Admin) UserRemoveHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
m := map[string]interface{}{}
|
||||
m := map[string]any{}
|
||||
err := json.NewDecoder(r.Body).Decode(&m)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
|
|
@ -690,7 +687,7 @@ func (admin *Admin) ValidateToken(sToken string) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
token, err := jwt.Parse(sToken, func(token *jwt.Token) (interface{}, error) {
|
||||
token, err := jwt.Parse(sToken, func(token *jwt.Token) (any, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,14 +42,12 @@ func (api *Api) CallUploadHandler(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
mediaType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("Invalid content-type\n"))
|
||||
api.exitWithError(w, http.StatusBadRequest, "Invalid content-type")
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(mediaType, "multipart/") {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("Not a multipart content\n"))
|
||||
api.exitWithError(w, http.StatusBadRequest, "Not a multipart content")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -60,12 +58,14 @@ func (api *Api) CallUploadHandler(w http.ResponseWriter, r *http.Request) {
|
|||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
continue
|
||||
api.exitWithError(w, http.StatusExpectationFailed, fmt.Sprintf("multipart: %s\n", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(p)
|
||||
if err != nil {
|
||||
continue
|
||||
api.exitWithError(w, http.StatusExpectationFailed, fmt.Sprintf("ioread: %s\n", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
switch p.FormName() {
|
||||
|
|
@ -79,8 +79,7 @@ func (api *Api) CallUploadHandler(w http.ResponseWriter, r *http.Request) {
|
|||
if ok, err := call.IsValid(); ok {
|
||||
api.HandleCall(key, call, w)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusExpectationFailed)
|
||||
w.Write([]byte(fmt.Sprintf("Incomplete call data: %s\n", err.Error())))
|
||||
api.exitWithError(w, http.StatusExpectationFailed, fmt.Sprintf("Incomplete call data: %s\n", err.Error()))
|
||||
}
|
||||
|
||||
default:
|
||||
|
|
@ -121,14 +120,12 @@ func (api *Api) TrunkRecorderCallUploadHandler(w http.ResponseWriter, r *http.Re
|
|||
|
||||
mediaType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("Invalid content-type\n"))
|
||||
api.exitWithError(w, http.StatusBadRequest, "Invalid content-type")
|
||||
return
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(mediaType, "multipart/") {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("Not a multipart content\n"))
|
||||
api.exitWithError(w, http.StatusBadRequest, "Not a multipart content")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -141,12 +138,14 @@ func (api *Api) TrunkRecorderCallUploadHandler(w http.ResponseWriter, r *http.Re
|
|||
if err == io.EOF {
|
||||
break
|
||||
} else if err != nil {
|
||||
continue
|
||||
api.exitWithError(w, http.StatusExpectationFailed, fmt.Sprintf("multipart: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
b, err := io.ReadAll(p)
|
||||
if err != nil {
|
||||
continue
|
||||
api.exitWithError(w, http.StatusExpectationFailed, fmt.Sprintf("ioread: %s", err.Error()))
|
||||
return
|
||||
}
|
||||
|
||||
switch p.FormName() {
|
||||
|
|
@ -154,8 +153,7 @@ func (api *Api) TrunkRecorderCallUploadHandler(w http.ResponseWriter, r *http.Re
|
|||
key = string(b)
|
||||
case "meta":
|
||||
if err := ParseTrunkRecorderMeta(call, b); err != nil {
|
||||
w.WriteHeader(http.StatusExpectationFailed)
|
||||
w.Write([]byte("Invalid call data\n"))
|
||||
api.exitWithError(w, http.StatusExpectationFailed, "Invalid call data")
|
||||
return
|
||||
}
|
||||
default:
|
||||
|
|
@ -171,8 +169,7 @@ func (api *Api) TrunkRecorderCallUploadHandler(w http.ResponseWriter, r *http.Re
|
|||
api.HandleCall(key, call, w)
|
||||
|
||||
} else {
|
||||
w.WriteHeader(http.StatusExpectationFailed)
|
||||
w.Write([]byte(fmt.Sprintf("Incomplete call data: %s\n", err.Error())))
|
||||
api.exitWithError(w, http.StatusExpectationFailed, fmt.Sprintf("Incomplete call data: %s\n", err.Error()))
|
||||
}
|
||||
|
||||
default:
|
||||
|
|
@ -180,3 +177,10 @@ func (api *Api) TrunkRecorderCallUploadHandler(w http.ResponseWriter, r *http.Re
|
|||
w.Write([]byte("Unsupported method\n"))
|
||||
}
|
||||
}
|
||||
|
||||
func (api *Api) exitWithError(w http.ResponseWriter, status int, message string) {
|
||||
api.Controller.Logs.LogEvent(LogLevelError, fmt.Sprintf("api: %s", message))
|
||||
|
||||
w.WriteHeader(status)
|
||||
w.Write([]byte(fmt.Sprintf("%s\n", message)))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,15 +26,15 @@ import (
|
|||
)
|
||||
|
||||
type Apikey struct {
|
||||
Id interface{} `json:"_id"`
|
||||
Id any `json:"_id"`
|
||||
Disabled bool `json:"disabled"`
|
||||
Ident string `json:"ident"`
|
||||
Key string `json:"key"`
|
||||
Order interface{} `json:"order"`
|
||||
Systems interface{} `json:"systems"`
|
||||
Order any `json:"order"`
|
||||
Systems any `json:"systems"`
|
||||
}
|
||||
|
||||
func (apikey *Apikey) FromMap(m map[string]interface{}) *Apikey {
|
||||
func (apikey *Apikey) FromMap(m map[string]any) *Apikey {
|
||||
switch v := m["_id"].(type) {
|
||||
case float64:
|
||||
apikey.Id = uint(v)
|
||||
|
|
@ -61,7 +61,7 @@ func (apikey *Apikey) FromMap(m map[string]interface{}) *Apikey {
|
|||
}
|
||||
|
||||
switch v := m["systems"].(type) {
|
||||
case []interface{}:
|
||||
case []any:
|
||||
if b, err := json.Marshal(v); err == nil {
|
||||
apikey.Systems = string(b)
|
||||
}
|
||||
|
|
@ -74,10 +74,10 @@ func (apikey *Apikey) FromMap(m map[string]interface{}) *Apikey {
|
|||
|
||||
func (apikey *Apikey) HasAccess(call *Call) bool {
|
||||
switch v := apikey.Systems.(type) {
|
||||
case []interface{}:
|
||||
case []any:
|
||||
for _, f := range v {
|
||||
switch v := f.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
switch id := v["id"].(type) {
|
||||
case float64:
|
||||
if id == float64(call.System) {
|
||||
|
|
@ -86,7 +86,7 @@ func (apikey *Apikey) HasAccess(call *Call) bool {
|
|||
if tg == "*" {
|
||||
return true
|
||||
}
|
||||
case []interface{}:
|
||||
case []any:
|
||||
for _, f := range tg {
|
||||
switch tg := f.(type) {
|
||||
case float64:
|
||||
|
|
@ -122,7 +122,7 @@ func NewApikeys() *Apikeys {
|
|||
}
|
||||
}
|
||||
|
||||
func (apikeys *Apikeys) FromMap(f []interface{}) *Apikeys {
|
||||
func (apikeys *Apikeys) FromMap(f []any) *Apikeys {
|
||||
apikeys.mutex.Lock()
|
||||
defer apikeys.mutex.Unlock()
|
||||
|
||||
|
|
@ -130,7 +130,7 @@ func (apikeys *Apikeys) FromMap(f []interface{}) *Apikeys {
|
|||
|
||||
for _, r := range f {
|
||||
switch m := r.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
apikey := &Apikey{}
|
||||
apikey.FromMap(m)
|
||||
apikeys.List = append(apikeys.List, apikey)
|
||||
|
|
@ -198,7 +198,7 @@ func (apikeys *Apikeys) Read(db *Database) error {
|
|||
}
|
||||
|
||||
if err = json.Unmarshal([]byte(systems), &apikey.Systems); err != nil {
|
||||
apikey.Systems = []interface{}{}
|
||||
apikey.Systems = []any{}
|
||||
}
|
||||
|
||||
apikeys.List = append(apikeys.List, apikey)
|
||||
|
|
@ -219,7 +219,7 @@ func (apikeys *Apikeys) Write(db *Database) error {
|
|||
err error
|
||||
rows *sql.Rows
|
||||
rowIds = []uint{}
|
||||
systems interface{}
|
||||
systems any
|
||||
)
|
||||
|
||||
apikeys.mutex.Lock()
|
||||
|
|
|
|||
|
|
@ -27,31 +27,31 @@ import (
|
|||
)
|
||||
|
||||
type Call struct {
|
||||
Id interface{} `json:"id"`
|
||||
Id any `json:"id"`
|
||||
Audio []byte `json:"audio"`
|
||||
AudioName interface{} `json:"audioName"`
|
||||
AudioType interface{} `json:"audioType"`
|
||||
AudioName any `json:"audioName"`
|
||||
AudioType any `json:"audioType"`
|
||||
DateTime time.Time `json:"dateTime"`
|
||||
Frequencies interface{} `json:"frequencies"`
|
||||
Frequency interface{} `json:"frequency"`
|
||||
Patches interface{} `json:"patches"`
|
||||
Source interface{} `json:"source"`
|
||||
Sources interface{} `json:"sources"`
|
||||
Frequencies any `json:"frequencies"`
|
||||
Frequency any `json:"frequency"`
|
||||
Patches any `json:"patches"`
|
||||
Source any `json:"source"`
|
||||
Sources any `json:"sources"`
|
||||
System uint `json:"system"`
|
||||
Talkgroup uint `json:"talkgroup"`
|
||||
systemLabel interface{}
|
||||
talkgroupGroup interface{}
|
||||
talkgroupLabel interface{}
|
||||
talkgroupName interface{}
|
||||
talkgroupTag interface{}
|
||||
units interface{}
|
||||
systemLabel any
|
||||
talkgroupGroup any
|
||||
talkgroupLabel any
|
||||
talkgroupName any
|
||||
talkgroupTag any
|
||||
units any
|
||||
}
|
||||
|
||||
func NewCall() *Call {
|
||||
return &Call{
|
||||
Frequencies: []map[string]interface{}{},
|
||||
Frequencies: []map[string]any{},
|
||||
Patches: []uint{},
|
||||
Sources: []map[string]interface{}{},
|
||||
Sources: []map[string]any{},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -85,9 +85,9 @@ func (call *Call) MarshalJSON() ([]byte, error) {
|
|||
audio := fmt.Sprintf("%v", call.Audio)
|
||||
audio = strings.ReplaceAll(audio, " ", ",")
|
||||
|
||||
return json.Marshal(map[string]interface{}{
|
||||
return json.Marshal(map[string]any{
|
||||
"id": call.Id,
|
||||
"audio": map[string]interface{}{
|
||||
"audio": map[string]any{
|
||||
"data": json.RawMessage(audio),
|
||||
"type": "Buffer",
|
||||
},
|
||||
|
|
@ -144,7 +144,7 @@ func (calls *Calls) GetCall(id uint, db *Database) (*Call, error) {
|
|||
var (
|
||||
audioName sql.NullString
|
||||
audioType sql.NullString
|
||||
dateTime interface{}
|
||||
dateTime any
|
||||
frequency sql.NullFloat64
|
||||
source sql.NullFloat64
|
||||
frequencies string
|
||||
|
|
@ -184,13 +184,13 @@ func (calls *Calls) GetCall(id uint, db *Database) (*Call, error) {
|
|||
|
||||
if len(frequencies) > 0 {
|
||||
if err = json.Unmarshal([]byte(frequencies), &call.Frequencies); err != nil {
|
||||
call.Frequencies = []interface{}{}
|
||||
call.Frequencies = []any{}
|
||||
}
|
||||
}
|
||||
|
||||
if len(patches) > 0 {
|
||||
if err = json.Unmarshal([]byte(patches), &call.Patches); err != nil {
|
||||
call.Patches = []interface{}{}
|
||||
call.Patches = []any{}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -200,7 +200,7 @@ func (calls *Calls) GetCall(id uint, db *Database) (*Call, error) {
|
|||
|
||||
if len(sources) > 0 {
|
||||
if err = json.Unmarshal([]byte(sources), &call.Sources); err != nil {
|
||||
call.Sources = []interface{}{}
|
||||
call.Sources = []any{}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -224,7 +224,7 @@ func (calls *Calls) Search(searchOptions *CallsSearchOptions, client *Client) (*
|
|||
)
|
||||
|
||||
var (
|
||||
dateTime interface{}
|
||||
dateTime any
|
||||
err error
|
||||
id sql.NullFloat64
|
||||
limit uint
|
||||
|
|
@ -252,14 +252,14 @@ func (calls *Calls) Search(searchOptions *CallsSearchOptions, client *Client) (*
|
|||
|
||||
if client.Access != nil {
|
||||
switch v := client.Access.Systems.(type) {
|
||||
case []interface{}:
|
||||
case []any:
|
||||
a := []string{}
|
||||
for _, scope := range v {
|
||||
var c string
|
||||
switch v := scope.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
switch v["talkgroups"].(type) {
|
||||
case []interface{}:
|
||||
case []any:
|
||||
b := strings.ReplaceAll(fmt.Sprintf("%v", v["talkgroups"]), " ", ", ")
|
||||
b = strings.ReplaceAll(b, "[", "(")
|
||||
b = strings.ReplaceAll(b, "]", ")")
|
||||
|
|
@ -441,7 +441,7 @@ func (calls *Calls) WriteCall(call *Call, db *Database) (uint, error) {
|
|||
}
|
||||
|
||||
switch v := call.Frequencies.(type) {
|
||||
case []map[string]interface{}:
|
||||
case []map[string]any:
|
||||
if b, err = json.Marshal(v); err == nil {
|
||||
frequencies = string(b)
|
||||
} else {
|
||||
|
|
@ -459,7 +459,7 @@ func (calls *Calls) WriteCall(call *Call, db *Database) (uint, error) {
|
|||
}
|
||||
|
||||
switch v := call.Sources.(type) {
|
||||
case []map[string]interface{}:
|
||||
case []map[string]any:
|
||||
if b, err = json.Marshal(v); err == nil {
|
||||
sources = string(b)
|
||||
} else {
|
||||
|
|
@ -479,18 +479,18 @@ func (calls *Calls) WriteCall(call *Call, db *Database) (uint, error) {
|
|||
}
|
||||
|
||||
type CallsSearchOptions struct {
|
||||
Date interface{} `json:"date,omitempty"`
|
||||
Group interface{} `json:"group,omitempty"`
|
||||
Limit interface{} `json:"limit,omitempty"`
|
||||
Offset interface{} `json:"offset,omitempty"`
|
||||
Sort interface{} `json:"sort,omitempty"`
|
||||
System interface{} `json:"system,omitempty"`
|
||||
Tag interface{} `json:"tag,omitempty"`
|
||||
Talkgroup interface{} `json:"talkgroup,omitempty"`
|
||||
Date any `json:"date,omitempty"`
|
||||
Group any `json:"group,omitempty"`
|
||||
Limit any `json:"limit,omitempty"`
|
||||
Offset any `json:"offset,omitempty"`
|
||||
Sort any `json:"sort,omitempty"`
|
||||
System any `json:"system,omitempty"`
|
||||
Tag any `json:"tag,omitempty"`
|
||||
Talkgroup any `json:"talkgroup,omitempty"`
|
||||
searchPatchedTalkgroups bool
|
||||
}
|
||||
|
||||
func (searchOptions *CallsSearchOptions) fromMap(m map[string]interface{}) error {
|
||||
func (searchOptions *CallsSearchOptions) fromMap(m map[string]any) error {
|
||||
switch v := m["date"].(type) {
|
||||
case string:
|
||||
if t, err := time.Parse(time.RFC3339, v); err == nil {
|
||||
|
|
|
|||
|
|
@ -60,16 +60,21 @@ func (client *Client) Init(controller *Controller, request *http.Request, conn *
|
|||
client.Controller = controller
|
||||
client.Conn = conn
|
||||
client.Livefeed = NewLivefeed()
|
||||
client.Send = make(chan *Message)
|
||||
client.Send = make(chan *Message, 1024)
|
||||
client.request = request
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
recover()
|
||||
|
||||
controller.Unregister <- client
|
||||
|
||||
controller.Logs.LogEvent(LogLevelInfo, fmt.Sprintf("listener disconnected from ip %v", client.GetRemoteAddr()))
|
||||
if len(client.Access.Ident) > 0 {
|
||||
controller.Logs.LogEvent(LogLevelInfo, fmt.Sprintf("listener disconnected from ip %s with ident %s", client.GetRemoteAddr(), client.Access.Ident))
|
||||
|
||||
} else {
|
||||
controller.Logs.LogEvent(LogLevelInfo, fmt.Sprintf("listener disconnected from ip %s", client.GetRemoteAddr()))
|
||||
}
|
||||
|
||||
client.Conn.Close()
|
||||
}()
|
||||
|
||||
client.Conn.SetReadDeadline(time.Now().Add(pongWait))
|
||||
|
|
@ -106,12 +111,9 @@ func (client *Client) Init(controller *Controller, request *http.Request, conn *
|
|||
})
|
||||
|
||||
defer func() {
|
||||
recover()
|
||||
|
||||
ticker.Stop()
|
||||
timer.Stop()
|
||||
|
||||
client.Conn.WriteMessage(websocket.CloseMessage, []byte{})
|
||||
client.Conn.Close()
|
||||
}()
|
||||
|
||||
|
|
@ -124,6 +126,16 @@ func (client *Client) Init(controller *Controller, request *http.Request, conn *
|
|||
|
||||
if message.Command == MessageCommandConfig {
|
||||
timer.Stop()
|
||||
|
||||
controller.Register <- client
|
||||
|
||||
if len(client.Access.Ident) > 0 {
|
||||
controller.Logs.LogEvent(LogLevelInfo, fmt.Sprintf("new listener from ip %s with ident %s", client.GetRemoteAddr(), client.Access.Ident))
|
||||
|
||||
} else {
|
||||
controller.Logs.LogEvent(LogLevelInfo, fmt.Sprintf("new listener from ip %s", client.GetRemoteAddr()))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
b, err := message.ToJson()
|
||||
|
|
@ -148,10 +160,6 @@ func (client *Client) Init(controller *Controller, request *http.Request, conn *
|
|||
}
|
||||
}()
|
||||
|
||||
controller.Register <- client
|
||||
|
||||
controller.Logs.LogEvent(LogLevelInfo, fmt.Sprintf("new listener from ip %v", client.GetRemoteAddr()))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -160,16 +168,14 @@ func (client *Client) GetRemoteAddr() string {
|
|||
}
|
||||
|
||||
func (client *Client) SendConfig(groups *Groups, options *Options, systems *Systems, tags *Tags) {
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
|
||||
client.SystemsMap = systems.GetScopedSystems(client, groups, tags, options.SortTalkgroups)
|
||||
client.GroupsMap = groups.GetGroupsMap(&client.SystemsMap)
|
||||
client.TagsMap = tags.GetTagsMap(&client.SystemsMap)
|
||||
|
||||
var payload = map[string]interface{}{
|
||||
var payload = map[string]any{
|
||||
"branding": options.Branding,
|
||||
"dimmerDelay": options.DimmerDelay,
|
||||
"email": options.Email,
|
||||
"groups": client.GroupsMap,
|
||||
"keypadBeeps": GetKeypadBeeps(options),
|
||||
"playbackGoesLive": options.PlaybackGoesLive,
|
||||
|
|
@ -188,10 +194,6 @@ func (client *Client) SendConfig(groups *Groups, options *Options, systems *Syst
|
|||
}
|
||||
|
||||
func (client *Client) SendListenersCount(count int) {
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
|
||||
client.Send <- &Message{
|
||||
Command: MessagecommandListenersCount,
|
||||
Payload: count,
|
||||
|
|
@ -209,7 +211,7 @@ func NewClients() *Clients {
|
|||
func (clients *Clients) AccessCount(client *Client) int {
|
||||
count := 0
|
||||
|
||||
clients.Map.Range(func(k interface{}, _ interface{}) bool {
|
||||
clients.Map.Range(func(k any, _ any) bool {
|
||||
switch c := k.(type) {
|
||||
case *Client:
|
||||
if c.Access == client.Access {
|
||||
|
|
@ -229,7 +231,7 @@ func (clients *Clients) Add(client *Client) {
|
|||
func (clients *Clients) Count() int {
|
||||
count := 0
|
||||
|
||||
clients.Map.Range(func(k, v interface{}) bool {
|
||||
clients.Map.Range(func(k, v any) bool {
|
||||
count++
|
||||
|
||||
return true
|
||||
|
|
@ -239,11 +241,7 @@ func (clients *Clients) Count() int {
|
|||
}
|
||||
|
||||
func (clients *Clients) EmitCall(call *Call, restricted bool) {
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
|
||||
clients.Map.Range(func(k interface{}, _ interface{}) bool {
|
||||
clients.Map.Range(func(k any, _ any) bool {
|
||||
switch c := k.(type) {
|
||||
case *Client:
|
||||
if (!restricted || c.Access.HasAccess(call)) && c.Livefeed.IsEnabled(call) {
|
||||
|
|
@ -256,13 +254,9 @@ func (clients *Clients) EmitCall(call *Call, restricted bool) {
|
|||
}
|
||||
|
||||
func (clients *Clients) EmitConfig(groups *Groups, options *Options, systems *Systems, tags *Tags, restricted bool) {
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
|
||||
count := clients.Count()
|
||||
|
||||
clients.Map.Range(func(k interface{}, _ interface{}) bool {
|
||||
clients.Map.Range(func(k any, _ any) bool {
|
||||
switch c := k.(type) {
|
||||
case *Client:
|
||||
if restricted {
|
||||
|
|
@ -283,7 +277,7 @@ func (clients *Clients) EmitConfig(groups *Groups, options *Options, systems *Sy
|
|||
func (clients *Clients) EmitListenersCount() {
|
||||
count := clients.Count()
|
||||
|
||||
clients.Map.Range(func(k interface{}, _ interface{}) bool {
|
||||
clients.Map.Range(func(k any, _ any) bool {
|
||||
switch c := k.(type) {
|
||||
case *Client:
|
||||
c.SendListenersCount(count)
|
||||
|
|
@ -294,11 +288,5 @@ func (clients *Clients) EmitListenersCount() {
|
|||
}
|
||||
|
||||
func (clients *Clients) Remove(client *Client) {
|
||||
defer func() {
|
||||
recover()
|
||||
}()
|
||||
|
||||
clients.Map.Delete(client)
|
||||
|
||||
close(client.Send)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import (
|
|||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
|
@ -81,10 +81,10 @@ func NewCommand(baseDir string) *Command {
|
|||
}
|
||||
|
||||
return &Command{
|
||||
app: path.Base(app),
|
||||
app: filepath.Base(app),
|
||||
command: COMMAND_HELP,
|
||||
password: pass,
|
||||
tokenFile: baseDir + path.Base(app) + ".token",
|
||||
tokenFile: baseDir + filepath.Base(app) + ".token",
|
||||
url: COMMAND_DEF_URL,
|
||||
}
|
||||
}
|
||||
|
|
@ -224,7 +224,7 @@ func (command *Command) adminPassword() {
|
|||
command.exitWithError(fmt.Sprintf("Missing %s <password> arguments.", COMMAND_ARG_PASSWORD))
|
||||
}
|
||||
|
||||
if body, err := command.writeBody(map[string]interface{}{"newPassword": command.password}); err == nil {
|
||||
if body, err := command.writeBody(map[string]any{"newPassword": command.password}); err == nil {
|
||||
if res, err := command.submit(http.MethodPost, "/api/admin/password", body, true); err == nil {
|
||||
if res.StatusCode == http.StatusOK {
|
||||
fmt.Println("New admin password applied")
|
||||
|
|
@ -246,9 +246,9 @@ func (command *Command) configGet() {
|
|||
if res.StatusCode == http.StatusOK {
|
||||
if data, err := command.readBody(res.Body); err == nil {
|
||||
switch v := data.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
switch v := v["config"].(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
if f, err := os.Create(command.out); err == nil {
|
||||
j := json.NewEncoder(f)
|
||||
j.SetIndent("", " ")
|
||||
|
|
@ -276,10 +276,10 @@ func (command *Command) configSet() {
|
|||
}
|
||||
|
||||
if f, err := os.Open(command.in); err == nil {
|
||||
var d interface{}
|
||||
var d any
|
||||
json.NewDecoder(f).Decode(&d)
|
||||
switch v := d.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
if body, err := command.writeBody(v); err == nil {
|
||||
if res, err := command.submit(http.MethodPut, "/api/admin/config", body, true); err == nil {
|
||||
if res.StatusCode == http.StatusOK {
|
||||
|
|
@ -300,12 +300,12 @@ func (command *Command) configSet() {
|
|||
}
|
||||
|
||||
func (command *Command) login() {
|
||||
if body, err := command.writeBody(map[string]interface{}{"password": command.password}); err == nil {
|
||||
if body, err := command.writeBody(map[string]any{"password": command.password}); err == nil {
|
||||
if res, err := command.submit(http.MethodPost, "/api/admin/login", body, false); err == nil {
|
||||
if res.StatusCode == http.StatusOK {
|
||||
if data, err := command.readBody(res.Body); err == nil {
|
||||
switch v := data.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
switch v := v["token"].(type) {
|
||||
case string:
|
||||
command.token = v
|
||||
|
|
@ -360,7 +360,7 @@ func (command *Command) userAdd() {
|
|||
command.exitWithError(fmt.Sprintf("Missing %s <code> arguments.", COMMAND_ARG_CODE))
|
||||
}
|
||||
|
||||
u := map[string]interface{}{
|
||||
u := map[string]any{
|
||||
"code": command.code,
|
||||
"ident": command.ident,
|
||||
"systems": "*",
|
||||
|
|
@ -392,9 +392,9 @@ func (command *Command) userAdd() {
|
|||
}
|
||||
}
|
||||
if len(s) > 0 {
|
||||
systems := []map[string]interface{}{}
|
||||
systems := []map[string]any{}
|
||||
for _, i := range s {
|
||||
systems = append(systems, map[string]interface{}{"id": i, "talkgroups": "*"})
|
||||
systems = append(systems, map[string]any{"id": i, "talkgroups": "*"})
|
||||
}
|
||||
u["systems"] = systems
|
||||
} else {
|
||||
|
|
@ -419,7 +419,7 @@ func (command *Command) userRemove() {
|
|||
command.exitWithError(fmt.Sprintf("Missing %s <ident> arguments.", COMMAND_ARG_IDENT))
|
||||
}
|
||||
|
||||
if body, err := command.writeBody(map[string]interface{}{"ident": command.ident}); err == nil {
|
||||
if body, err := command.writeBody(map[string]any{"ident": command.ident}); err == nil {
|
||||
if res, err := command.submit(http.MethodPost, "/api/admin/user-remove", body, true); err == nil {
|
||||
if res.StatusCode == http.StatusOK {
|
||||
fmt.Printf("User %s removed.\n", command.ident)
|
||||
|
|
@ -432,12 +432,12 @@ func (command *Command) userRemove() {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *Command) readBody(body io.ReadCloser) (data interface{}, err error) {
|
||||
func (c *Command) readBody(body io.ReadCloser) (data any, err error) {
|
||||
err = json.NewDecoder(body).Decode(&data)
|
||||
return data, err
|
||||
}
|
||||
|
||||
func (c *Command) writeBody(data interface{}) (body io.Reader, err error) {
|
||||
func (c *Command) writeBody(data any) (body io.Reader, err error) {
|
||||
var b []byte
|
||||
if b, err = json.Marshal(data); err == nil {
|
||||
body = bytes.NewReader(b)
|
||||
|
|
@ -464,7 +464,7 @@ func (c *Command) submit(method string, url string, body io.Reader, auth bool) (
|
|||
return res, err
|
||||
}
|
||||
|
||||
func (c *Command) exitWithError(err interface{}) {
|
||||
func (c *Command) exitWithError(err any) {
|
||||
fmt.Printf("%v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,10 +67,9 @@ func NewController(config *Config) *Controller {
|
|||
Systems: NewSystems(),
|
||||
Tags: NewTags(),
|
||||
Clients: NewClients(),
|
||||
Register: make(chan *Client),
|
||||
Unregister: make(chan *Client),
|
||||
Ingest: make(chan *Call),
|
||||
ingestMutex: sync.Mutex{},
|
||||
Register: make(chan *Client, 4096),
|
||||
Unregister: make(chan *Client, 4096),
|
||||
Ingest: make(chan *Call, 4096),
|
||||
}
|
||||
|
||||
controller.Admin = NewAdmin(controller)
|
||||
|
|
@ -110,9 +109,6 @@ func (controller *Controller) IngestCall(call *Call) {
|
|||
talkgroup *Talkgroup
|
||||
)
|
||||
|
||||
controller.IngestLock()
|
||||
defer controller.IngestUnlock()
|
||||
|
||||
logCall := func(call *Call, level string, message string) {
|
||||
controller.Logs.LogEvent(level, fmt.Sprintf("newcall: system=%v talkgroup=%v file=%v %v", call.System, call.Talkgroup, call.AudioName, message))
|
||||
}
|
||||
|
|
@ -317,21 +313,13 @@ func (controller *Controller) IngestCall(call *Call) {
|
|||
}
|
||||
}
|
||||
|
||||
func (controller *Controller) IngestLock() {
|
||||
controller.ingestMutex.Lock()
|
||||
}
|
||||
|
||||
func (controller *Controller) IngestUnlock() {
|
||||
controller.ingestMutex.Unlock()
|
||||
}
|
||||
|
||||
func (controller *Controller) LogClientsCount() {
|
||||
controller.Logs.LogEvent(LogLevelInfo, fmt.Sprintf("listeners count is %v", controller.Clients.Count()))
|
||||
}
|
||||
|
||||
func (controller *Controller) ProcessMessage(client *Client, message *Message) error {
|
||||
if message.Command == MessageCommandVersion {
|
||||
client.Send <- &Message{Command: MessageCommandVersion, Payload: Version}
|
||||
controller.ProcessMessageCommandVersion(client)
|
||||
|
||||
} else if controller.Accesses.IsRestricted() && client.Access.Systems == nil && message.Command != MessageCommandPin {
|
||||
client.Send <- &Message{Command: MessageCommandPin}
|
||||
|
|
@ -394,7 +382,7 @@ func (controller *Controller) ProcessMessageCommandCall(client *Client, message
|
|||
|
||||
func (controller *Controller) ProcessMessageCommandListCall(client *Client, message *Message) error {
|
||||
switch v := message.Payload.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
searchOptions := CallsSearchOptions{searchPatchedTalkgroups: controller.Options.SearchPatchedTalkgroups}
|
||||
searchOptions.fromMap(v)
|
||||
if searchResults, err := controller.Calls.Search(&searchOptions, client); err == nil {
|
||||
|
|
@ -467,6 +455,20 @@ func (controller *Controller) ProcessMessageCommandPin(client *Client, message *
|
|||
return nil
|
||||
}
|
||||
|
||||
func (controller *Controller) ProcessMessageCommandVersion(client *Client) {
|
||||
p := map[string]string{"version": Version}
|
||||
|
||||
if len(controller.Options.Branding) > 0 {
|
||||
p["branding"] = controller.Options.Branding
|
||||
}
|
||||
|
||||
if len(controller.Options.Email) > 0 {
|
||||
p["email"] = controller.Options.Email
|
||||
}
|
||||
|
||||
client.Send <- &Message{Command: MessageCommandVersion, Payload: p}
|
||||
}
|
||||
|
||||
func (controller *Controller) Start() error {
|
||||
var err error
|
||||
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ func NewDatabase(config *Config) *Database {
|
|||
return database
|
||||
}
|
||||
|
||||
func (db *Database) ParseDateTime(f interface{}) (time.Time, error) {
|
||||
func (db *Database) ParseDateTime(f any) (time.Time, error) {
|
||||
switch v := f.(type) {
|
||||
case []uint8:
|
||||
return time.Parse(db.DateTimeFormat, string(v))
|
||||
|
|
@ -377,10 +377,10 @@ func (db *Database) migration20211202094819(verbose bool) error {
|
|||
func (db *Database) migration20220101070000(verbose bool) error {
|
||||
var (
|
||||
err error
|
||||
frequency interface{}
|
||||
frequency any
|
||||
id uint
|
||||
label string
|
||||
led interface{}
|
||||
led any
|
||||
name string
|
||||
queries []string
|
||||
rows *sql.Rows
|
||||
|
|
|
|||
|
|
@ -44,25 +44,25 @@ const (
|
|||
)
|
||||
|
||||
type Dirwatch struct {
|
||||
Id interface{} `json:"_id"`
|
||||
Delay interface{} `json:"delay"`
|
||||
Id any `json:"_id"`
|
||||
Delay any `json:"delay"`
|
||||
DeleteAfter bool `json:"deleteAfter"`
|
||||
Directory string `json:"directory"`
|
||||
Disabled bool `json:"disabled"`
|
||||
Extension interface{} `json:"extension"`
|
||||
Frequency interface{} `json:"frequency"`
|
||||
Mask interface{} `json:"mask"`
|
||||
Order interface{} `json:"order"`
|
||||
SystemId interface{} `json:"systemId"`
|
||||
TalkgroupId interface{} `json:"talkgroupId"`
|
||||
Kind interface{} `json:"type"`
|
||||
Extension any `json:"extension"`
|
||||
Frequency any `json:"frequency"`
|
||||
Mask any `json:"mask"`
|
||||
Order any `json:"order"`
|
||||
SystemId any `json:"systemId"`
|
||||
TalkgroupId any `json:"talkgroupId"`
|
||||
Kind any `json:"type"`
|
||||
UsePolling bool `json:"usePolling"`
|
||||
controller *Controller
|
||||
dirs map[string]bool
|
||||
watcher *fsnotify.Watcher
|
||||
}
|
||||
|
||||
func (dirwatch *Dirwatch) FromMap(m map[string]interface{}) *Dirwatch {
|
||||
func (dirwatch *Dirwatch) FromMap(m map[string]any) *Dirwatch {
|
||||
switch v := m["_id"].(type) {
|
||||
case float64:
|
||||
dirwatch.Id = uint(v)
|
||||
|
|
@ -164,7 +164,7 @@ func (dirwatch *Dirwatch) ingestDefault(p string) error {
|
|||
if strings.EqualFold(path.Ext(p), ext) {
|
||||
call := NewCall()
|
||||
|
||||
call.AudioName = path.Base(p)
|
||||
call.AudioName = filepath.Base(p)
|
||||
call.AudioType = mime.TypeByExtension(path.Ext(p))
|
||||
call.Frequency = dirwatch.Frequency
|
||||
|
||||
|
|
@ -224,7 +224,7 @@ func (dirwatch *Dirwatch) ingestSdrTrunk(p string) error {
|
|||
|
||||
call := NewCall()
|
||||
|
||||
call.AudioName = path.Base(p)
|
||||
call.AudioName = filepath.Base(p)
|
||||
call.AudioType = mime.TypeByExtension(path.Ext(p))
|
||||
call.Frequency = dirwatch.Frequency
|
||||
|
||||
|
|
@ -280,7 +280,7 @@ func (dirwatch *Dirwatch) ingestTrunkRecorder(p string) error {
|
|||
|
||||
call := NewCall()
|
||||
|
||||
call.AudioName = path.Base(audioName)
|
||||
call.AudioName = filepath.Base(audioName)
|
||||
call.AudioType = mime.TypeByExtension(path.Ext(audioName))
|
||||
call.Frequency = dirwatch.Frequency
|
||||
|
||||
|
|
@ -344,8 +344,8 @@ func (dirwatch *Dirwatch) parseMask(call *Call) {
|
|||
var (
|
||||
filename string
|
||||
mask string
|
||||
metapos = [][]interface{}{}
|
||||
metaval = map[string]interface{}{}
|
||||
metapos = [][]any{}
|
||||
metaval = map[string]any{}
|
||||
)
|
||||
|
||||
switch v := dirwatch.Mask.(type) {
|
||||
|
|
@ -364,7 +364,7 @@ func (dirwatch *Dirwatch) parseMask(call *Call) {
|
|||
|
||||
for _, v := range meta {
|
||||
if i := strings.Index(mask, v[1]); i != -1 {
|
||||
metapos = append(metapos, []interface{}{v[0], i})
|
||||
metapos = append(metapos, []any{v[0], i})
|
||||
mask = strings.Replace(mask, v[1], fmt.Sprintf("(%v)", v[2]), 1)
|
||||
}
|
||||
}
|
||||
|
|
@ -513,8 +513,8 @@ func (dirwatch *Dirwatch) parseMask(call *Call) {
|
|||
case string:
|
||||
if i, err := strconv.Atoi(v); err == nil {
|
||||
switch sources := call.Sources.(type) {
|
||||
case []map[string]interface{}:
|
||||
call.Sources = append(sources, map[string]interface{}{"pos": 0, "src": uint(i)})
|
||||
case []map[string]any:
|
||||
call.Sources = append(sources, map[string]any{"pos": 0, "src": uint(i)})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -549,7 +549,6 @@ func (dirwatch *Dirwatch) Start(controller *Controller) error {
|
|||
}
|
||||
|
||||
go func() {
|
||||
// var timers = map[string]*time.Timer{}
|
||||
var timers = sync.Map{}
|
||||
|
||||
logError := func(err error) {
|
||||
|
|
@ -567,7 +566,7 @@ func (dirwatch *Dirwatch) Start(controller *Controller) error {
|
|||
}
|
||||
|
||||
defer func() {
|
||||
timers.Range(func(k interface{}, t interface{}) bool {
|
||||
timers.Range(func(k any, t any) bool {
|
||||
switch v := t.(type) {
|
||||
case *time.Timer:
|
||||
v.Stop()
|
||||
|
|
@ -688,7 +687,7 @@ func NewDirwatches() *Dirwatches {
|
|||
}
|
||||
}
|
||||
|
||||
func (dirwatches *Dirwatches) FromMap(f []interface{}) *Dirwatches {
|
||||
func (dirwatches *Dirwatches) FromMap(f []any) *Dirwatches {
|
||||
dirwatches.mutex.Lock()
|
||||
defer dirwatches.mutex.Unlock()
|
||||
|
||||
|
|
@ -698,7 +697,7 @@ func (dirwatches *Dirwatches) FromMap(f []interface{}) *Dirwatches {
|
|||
|
||||
for _, f := range f {
|
||||
switch v := f.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
dirwatch := &Dirwatch{}
|
||||
dirwatch.FromMap(v)
|
||||
dirwatches.List = append(dirwatches.List, dirwatch)
|
||||
|
|
|
|||
|
|
@ -32,15 +32,15 @@ import (
|
|||
)
|
||||
|
||||
type Downstream struct {
|
||||
Id interface{} `json:"_id"`
|
||||
Id any `json:"_id"`
|
||||
Apikey string `json:"apiKey"`
|
||||
Disabled bool `json:"disabled"`
|
||||
Order interface{} `json:"order"`
|
||||
Systems interface{} `json:"systems"`
|
||||
Order any `json:"order"`
|
||||
Systems any `json:"systems"`
|
||||
Url string `json:"url"`
|
||||
}
|
||||
|
||||
func (downstream *Downstream) FromMap(m map[string]interface{}) *Downstream {
|
||||
func (downstream *Downstream) FromMap(m map[string]any) *Downstream {
|
||||
switch v := m["_id"].(type) {
|
||||
case float64:
|
||||
downstream.Id = uint(v)
|
||||
|
|
@ -62,7 +62,7 @@ func (downstream *Downstream) FromMap(m map[string]interface{}) *Downstream {
|
|||
}
|
||||
|
||||
switch v := m["systems"].(type) {
|
||||
case []interface{}:
|
||||
case []any:
|
||||
if b, err := json.Marshal(v); err == nil {
|
||||
downstream.Systems = string(b)
|
||||
}
|
||||
|
|
@ -84,10 +84,10 @@ func (downstream *Downstream) HasAccess(call *Call) bool {
|
|||
}
|
||||
|
||||
switch v := downstream.Systems.(type) {
|
||||
case []interface{}:
|
||||
case []any:
|
||||
for _, f := range v {
|
||||
switch v := f.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
switch id := v["id"].(type) {
|
||||
case float64:
|
||||
if id == float64(call.System) {
|
||||
|
|
@ -96,7 +96,7 @@ func (downstream *Downstream) HasAccess(call *Call) bool {
|
|||
if tg == "*" {
|
||||
return true
|
||||
}
|
||||
case []interface{}:
|
||||
case []any:
|
||||
for _, f := range tg {
|
||||
switch tg := f.(type) {
|
||||
case float64:
|
||||
|
|
@ -181,7 +181,7 @@ func (downstream *Downstream) Send(call *Call) error {
|
|||
}
|
||||
|
||||
switch v := call.Frequencies.(type) {
|
||||
case []map[string]interface{}:
|
||||
case []map[string]any:
|
||||
if w, err := mw.CreateFormField("frequencies"); err == nil {
|
||||
if b, err := json.Marshal(v); err == nil {
|
||||
if _, err = w.Write(b); err != nil {
|
||||
|
|
@ -241,7 +241,7 @@ func (downstream *Downstream) Send(call *Call) error {
|
|||
}
|
||||
|
||||
switch v := call.Sources.(type) {
|
||||
case []map[string]interface{}:
|
||||
case []map[string]any:
|
||||
if w, err := mw.CreateFormField("sources"); err == nil {
|
||||
if b, err := json.Marshal(v); err == nil {
|
||||
if _, err = w.Write(b); err != nil {
|
||||
|
|
@ -363,7 +363,7 @@ func NewDownstreams() *Downstreams {
|
|||
}
|
||||
}
|
||||
|
||||
func (downstreams *Downstreams) FromMap(f []interface{}) *Downstreams {
|
||||
func (downstreams *Downstreams) FromMap(f []any) *Downstreams {
|
||||
downstreams.mutex.Lock()
|
||||
defer downstreams.mutex.Unlock()
|
||||
|
||||
|
|
@ -371,7 +371,7 @@ func (downstreams *Downstreams) FromMap(f []interface{}) *Downstreams {
|
|||
|
||||
for _, r := range f {
|
||||
switch m := r.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
downstream := &Downstream{}
|
||||
downstream.FromMap(m)
|
||||
downstreams.List = append(downstreams.List, downstream)
|
||||
|
|
@ -423,7 +423,7 @@ func (downstreams *Downstreams) Read(db *Database) error {
|
|||
}
|
||||
|
||||
if err = json.Unmarshal([]byte(systems), &downstream.Systems); err != nil {
|
||||
downstream.Systems = []interface{}{}
|
||||
downstream.Systems = []any{}
|
||||
}
|
||||
|
||||
if len(downstream.Url) == 0 {
|
||||
|
|
@ -464,7 +464,7 @@ func (downstreams *Downstreams) Write(db *Database) error {
|
|||
err error
|
||||
rows *sql.Rows
|
||||
rowIds = []uint{}
|
||||
systems interface{}
|
||||
systems any
|
||||
)
|
||||
|
||||
downstreams.mutex.Lock()
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
module rdio-scanner/server
|
||||
|
||||
go 1.17
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/dhowden/tag v0.0.0-20220530110423-77907a30b7f1
|
||||
github.com/dhowden/tag v0.0.0-20220618230019-adf36e896086
|
||||
github.com/fsnotify/fsnotify v1.5.4
|
||||
github.com/go-sql-driver/mysql v1.6.0
|
||||
github.com/golang-jwt/jwt/v4 v4.4.1
|
||||
|
|
@ -21,8 +21,8 @@ require (
|
|||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||
github.com/stretchr/testify v1.7.0 // indirect
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d // indirect
|
||||
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d // indirect
|
||||
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 // indirect
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/tools v0.1.11 // indirect
|
||||
lukechampine.com/uint128 v1.2.0 // indirect
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dhowden/tag v0.0.0-20220530110423-77907a30b7f1 h1:jD1A7flCBuwFb5PvK3B2u/jmUUZ2j3n3puR0F7peZWM=
|
||||
github.com/dhowden/tag v0.0.0-20220530110423-77907a30b7f1/go.mod h1:SniNVYuaD1jmdEEvi+7ywb1QFR7agjeTdGKyFb0p7Rw=
|
||||
github.com/dhowden/tag v0.0.0-20220618230019-adf36e896086 h1:ORubSQoKnncsBnR4zD9CuYFJCPOCuSNEpWEZrDdBXkc=
|
||||
github.com/dhowden/tag v0.0.0-20220618230019-adf36e896086/go.mod h1:Z3Lomva4pyMWYezjMAU5QWRh0p1VvO4199OHlFnyKkM=
|
||||
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
||||
|
|
@ -51,6 +53,8 @@ golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qx
|
|||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d h1:4SFsTMi4UahlKoloni7L4eYzhFRifURQLw+yv0QDCx8=
|
||||
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9 h1:Yqz/iviulwKwAREEeUd3nbBFn0XuyJqkoft2IlrvOhc=
|
||||
golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
|
@ -69,6 +73,8 @@ golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d h1:Zu/JngovGLVi6t2J3nmAf3AoTDwuzw85YZ3b9o4yU7s=
|
||||
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c h1:aFV+BgZ4svzjfabn8ERpuB4JI4N6/rdy1iusx77G3oU=
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
|
|
|
|||
|
|
@ -24,11 +24,11 @@ import (
|
|||
)
|
||||
|
||||
type Group struct {
|
||||
Id interface{} `json:"_id"`
|
||||
Id any `json:"_id"`
|
||||
Label string `json:"label"`
|
||||
}
|
||||
|
||||
func (group *Group) FromMap(m map[string]interface{}) *Group {
|
||||
func (group *Group) FromMap(m map[string]any) *Group {
|
||||
switch v := m["_id"].(type) {
|
||||
case float64:
|
||||
group.Id = uint(v)
|
||||
|
|
@ -54,7 +54,7 @@ func NewGroups() *Groups {
|
|||
}
|
||||
}
|
||||
|
||||
func (groups *Groups) FromMap(f []interface{}) *Groups {
|
||||
func (groups *Groups) FromMap(f []any) *Groups {
|
||||
groups.mutex.Lock()
|
||||
defer groups.mutex.Unlock()
|
||||
|
||||
|
|
@ -62,7 +62,7 @@ func (groups *Groups) FromMap(f []interface{}) *Groups {
|
|||
|
||||
for _, r := range f {
|
||||
switch m := r.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
group := &Group{}
|
||||
group.FromMap(m)
|
||||
groups.List = append(groups.List, group)
|
||||
|
|
@ -72,7 +72,7 @@ func (groups *Groups) FromMap(f []interface{}) *Groups {
|
|||
return groups
|
||||
}
|
||||
|
||||
func (groups *Groups) GetGroup(f interface{}) (group *Group, ok bool) {
|
||||
func (groups *Groups) GetGroup(f any) (group *Group, ok bool) {
|
||||
groups.mutex.Lock()
|
||||
defer groups.mutex.Unlock()
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ func NewLivefeed() *Livefeed {
|
|||
}
|
||||
}
|
||||
|
||||
func (livefeed *Livefeed) FromMap(f interface{}) *Livefeed {
|
||||
func (livefeed *Livefeed) FromMap(f any) *Livefeed {
|
||||
livefeed.mutex.Lock()
|
||||
defer livefeed.mutex.Unlock()
|
||||
|
||||
|
|
@ -41,12 +41,12 @@ func (livefeed *Livefeed) FromMap(f interface{}) *Livefeed {
|
|||
}
|
||||
|
||||
switch v := f.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
for s, n := range v {
|
||||
if sysId, err := strconv.Atoi(s); err == nil {
|
||||
sysId := uint(sysId)
|
||||
switch v := n.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
for t, b := range v {
|
||||
switch v := b.(type) {
|
||||
case bool:
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ const (
|
|||
)
|
||||
|
||||
type Log struct {
|
||||
Id interface{} `json:"_id"`
|
||||
Id any `json:"_id"`
|
||||
DateTime time.Time `json:"dateTime"`
|
||||
Level string `json:"level"`
|
||||
Message string `json:"message"`
|
||||
|
|
@ -99,7 +99,7 @@ func (logs *Logs) Search(searchOptions *LogsSearchOptions, db *Database) (*LogsS
|
|||
)
|
||||
|
||||
var (
|
||||
dateTime interface{}
|
||||
dateTime any
|
||||
err error
|
||||
id sql.NullFloat64
|
||||
limit uint
|
||||
|
|
@ -240,14 +240,14 @@ func (logs *Logs) setDatabase(d *Database) {
|
|||
}
|
||||
|
||||
type LogsSearchOptions struct {
|
||||
Date interface{} `json:"date,omitempty"`
|
||||
Level interface{} `json:"level,omitempty"`
|
||||
Limit interface{} `json:"limit,omitempty"`
|
||||
Offset interface{} `json:"offset,omitempty"`
|
||||
Sort interface{} `json:"sort,omitempty"`
|
||||
Date any `json:"date,omitempty"`
|
||||
Level any `json:"level,omitempty"`
|
||||
Limit any `json:"limit,omitempty"`
|
||||
Offset any `json:"offset,omitempty"`
|
||||
Sort any `json:"sort,omitempty"`
|
||||
}
|
||||
|
||||
func (searchOptions *LogsSearchOptions) FromMap(m map[string]interface{}) error {
|
||||
func (searchOptions *LogsSearchOptions) FromMap(m map[string]any) error {
|
||||
switch v := m["date"].(type) {
|
||||
case string:
|
||||
if t, err := time.Parse(time.RFC3339, v); err == nil {
|
||||
|
|
|
|||
|
|
@ -229,7 +229,7 @@ func main() {
|
|||
}
|
||||
|
||||
func GetRemoteAddr(r *http.Request) string {
|
||||
re := regexp.MustCompile(`(\[[^\]]+\]|[^:]+):.*`)
|
||||
re := regexp.MustCompile(`(.+):.*$`)
|
||||
|
||||
for _, addr := range strings.Split(r.Header.Get("X-Forwarded-For"), ",") {
|
||||
if ip := re.ReplaceAllString(addr, "$1"); len(ip) > 0 {
|
||||
|
|
@ -241,5 +241,5 @@ func GetRemoteAddr(r *http.Request) string {
|
|||
return ip
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
return r.RemoteAddr
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,15 +35,15 @@ const (
|
|||
)
|
||||
|
||||
type Message struct {
|
||||
Command interface{}
|
||||
Payload interface{}
|
||||
Flag interface{}
|
||||
Command any
|
||||
Payload any
|
||||
Flag any
|
||||
}
|
||||
|
||||
func (message *Message) FromJson(b []byte) error {
|
||||
var (
|
||||
err error
|
||||
f []interface{}
|
||||
f []any
|
||||
)
|
||||
|
||||
if err = json.Unmarshal(b, &f); err != nil {
|
||||
|
|
@ -68,7 +68,7 @@ func (message *Message) FromJson(b []byte) error {
|
|||
}
|
||||
|
||||
func (message *Message) ToJson() ([]byte, error) {
|
||||
str := []interface{}{message.Command}
|
||||
str := []any{message.Command}
|
||||
|
||||
if message.Payload != nil && message.Payload != "" {
|
||||
str = append(str, message.Payload)
|
||||
|
|
|
|||
|
|
@ -28,9 +28,11 @@ type Options struct {
|
|||
AfsSystems string `json:"afsSystems"`
|
||||
AudioConversion uint `json:"audioConversion"`
|
||||
AutoPopulate bool `json:"autoPopulate"`
|
||||
Branding string `json:"branding"`
|
||||
DimmerDelay uint `json:"dimmerDelay"`
|
||||
DisableDuplicateDetection bool `json:"disableDuplicateDetection"`
|
||||
DuplicateDetectionTimeFrame uint `json:"duplicateDetectionTimeFrame"`
|
||||
Email string `json:"email"`
|
||||
KeypadBeeps string `json:"keypadBeeps"`
|
||||
MaxClients uint `json:"maxClients"`
|
||||
PlaybackGoesLive bool `json:"playbackGoesLive"`
|
||||
|
|
@ -59,7 +61,7 @@ func NewOptions() *Options {
|
|||
}
|
||||
}
|
||||
|
||||
func (options *Options) FromMap(m map[string]interface{}) *Options {
|
||||
func (options *Options) FromMap(m map[string]any) *Options {
|
||||
options.mutex.Lock()
|
||||
defer options.mutex.Unlock()
|
||||
|
||||
|
|
@ -82,6 +84,11 @@ func (options *Options) FromMap(m map[string]interface{}) *Options {
|
|||
options.AutoPopulate = defaults.options.autoPopulate
|
||||
}
|
||||
|
||||
switch v := m["branding"].(type) {
|
||||
case string:
|
||||
options.Branding = v
|
||||
}
|
||||
|
||||
switch v := m["dimmerDelay"].(type) {
|
||||
case float64:
|
||||
options.DimmerDelay = uint(v)
|
||||
|
|
@ -112,6 +119,11 @@ func (options *Options) FromMap(m map[string]interface{}) *Options {
|
|||
options.DuplicateDetectionTimeFrame = defaults.options.duplicateDetectionTimeFrame
|
||||
}
|
||||
|
||||
switch v := m["email"].(type) {
|
||||
case string:
|
||||
options.Email = v
|
||||
}
|
||||
|
||||
switch v := m["keypadBeeps"].(type) {
|
||||
case string:
|
||||
options.KeypadBeeps = v
|
||||
|
|
@ -221,7 +233,7 @@ func (options *Options) Read(db *Database) error {
|
|||
|
||||
err = db.Sql.QueryRow("select `val` from `rdioScannerConfigs` where `key` = 'options'").Scan(&s)
|
||||
if err == nil {
|
||||
var m map[string]interface{}
|
||||
var m map[string]any
|
||||
|
||||
if err = json.Unmarshal([]byte(s), &m); err == nil {
|
||||
switch v := m["afsSystems"].(type) {
|
||||
|
|
@ -239,6 +251,11 @@ func (options *Options) Read(db *Database) error {
|
|||
options.AutoPopulate = v
|
||||
}
|
||||
|
||||
switch v := m["branding"].(type) {
|
||||
case string:
|
||||
options.Branding = v
|
||||
}
|
||||
|
||||
switch v := m["dimmerDelay"].(type) {
|
||||
case float64:
|
||||
options.DimmerDelay = uint(v)
|
||||
|
|
@ -254,6 +271,11 @@ func (options *Options) Read(db *Database) error {
|
|||
options.DuplicateDetectionTimeFrame = uint(v)
|
||||
}
|
||||
|
||||
switch v := m["email"].(type) {
|
||||
case string:
|
||||
options.Email = v
|
||||
}
|
||||
|
||||
switch v := m["keypadBeeps"].(type) {
|
||||
case string:
|
||||
options.KeypadBeeps = v
|
||||
|
|
@ -350,13 +372,15 @@ func (options *Options) Write(db *Database) error {
|
|||
db.Sql.Exec("insert into `rdioScannerConfigs` (`key`, `val`) values (?, ?)", "adminPasswordNeedChange", string(b))
|
||||
}
|
||||
|
||||
if b, err = json.Marshal(map[string]interface{}{
|
||||
if b, err = json.Marshal(map[string]any{
|
||||
"afsSystems": options.AfsSystems,
|
||||
"audioConversion": options.AudioConversion,
|
||||
"autoPopulate": options.AutoPopulate,
|
||||
"branding": options.Branding,
|
||||
"dimmerDelay": options.DimmerDelay,
|
||||
"disableDuplicateDetection": options.DisableDuplicateDetection,
|
||||
"duplicateDetectionTimeFrame": options.DuplicateDetectionTimeFrame,
|
||||
"email": options.Email,
|
||||
"keypadBeeps": options.KeypadBeeps,
|
||||
"maxClients": options.MaxClients,
|
||||
"playbackGoesLive": options.PlaybackGoesLive,
|
||||
|
|
|
|||
|
|
@ -101,10 +101,10 @@ func ParseSdrTrunkMeta(call *Call, controller *Controller) error {
|
|||
|
||||
s = regexp.MustCompile(`(\[.+\])`).FindStringSubmatch(m.Title())
|
||||
if len(s) > 1 && len(s[1]) > 0 {
|
||||
var f interface{}
|
||||
var f any
|
||||
if err = json.Unmarshal([]byte(s[1]), &f); err == nil {
|
||||
switch v := f.(type) {
|
||||
case []interface{}:
|
||||
case []any:
|
||||
patches := []uint{}
|
||||
for _, patch := range v {
|
||||
switch v := patch.(type) {
|
||||
|
|
@ -151,15 +151,15 @@ func ParseMultipartContent(call *Call, p *multipart.Part, b []byte) {
|
|||
}
|
||||
|
||||
case "frequencies":
|
||||
var f interface{}
|
||||
var f any
|
||||
if err := json.Unmarshal(b, &f); err == nil {
|
||||
switch v := f.(type) {
|
||||
case []interface{}:
|
||||
var frequencies = []map[string]interface{}{}
|
||||
case []any:
|
||||
var frequencies = []map[string]any{}
|
||||
for _, f := range v {
|
||||
freq := map[string]interface{}{}
|
||||
freq := map[string]any{}
|
||||
switch v := f.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
switch v := v["errorCount"].(type) {
|
||||
case float64:
|
||||
if v >= 0 {
|
||||
|
|
@ -204,12 +204,12 @@ func ParseMultipartContent(call *Call, p *multipart.Part, b []byte) {
|
|||
|
||||
case "patches", "patched_talkgroups":
|
||||
var (
|
||||
f interface{}
|
||||
f any
|
||||
patches = []uint{}
|
||||
)
|
||||
if err := json.Unmarshal(b, &f); err == nil {
|
||||
switch v := f.(type) {
|
||||
case []interface{}:
|
||||
case []any:
|
||||
for _, patch := range v {
|
||||
switch v := patch.(type) {
|
||||
case float64:
|
||||
|
|
@ -229,17 +229,17 @@ func ParseMultipartContent(call *Call, p *multipart.Part, b []byte) {
|
|||
|
||||
case "sources":
|
||||
var (
|
||||
f interface{}
|
||||
f any
|
||||
units *Units
|
||||
)
|
||||
if err := json.Unmarshal(b, &f); err == nil {
|
||||
switch v := f.(type) {
|
||||
case []interface{}:
|
||||
var sources = []map[string]interface{}{}
|
||||
case []any:
|
||||
var sources = []map[string]any{}
|
||||
for _, f := range v {
|
||||
src := map[string]interface{}{}
|
||||
src := map[string]any{}
|
||||
switch v := f.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
switch v := v["pos"].(type) {
|
||||
case float64:
|
||||
if v >= 0 {
|
||||
|
|
@ -306,7 +306,7 @@ func ParseMultipartContent(call *Call, p *multipart.Part, b []byte) {
|
|||
}
|
||||
|
||||
func ParseTrunkRecorderMeta(call *Call, b []byte) error {
|
||||
m := map[string]interface{}{}
|
||||
m := map[string]any{}
|
||||
|
||||
if err := json.Unmarshal(b, &m); err != nil {
|
||||
return err
|
||||
|
|
@ -320,12 +320,12 @@ func ParseTrunkRecorderMeta(call *Call, b []byte) error {
|
|||
}
|
||||
|
||||
switch v := m["freqList"].(type) {
|
||||
case []interface{}:
|
||||
freqs := []map[string]interface{}{}
|
||||
case []any:
|
||||
freqs := []map[string]any{}
|
||||
for _, f := range v {
|
||||
freq := map[string]interface{}{}
|
||||
freq := map[string]any{}
|
||||
switch v := f.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
switch v := v["error_count"].(type) {
|
||||
case float64:
|
||||
if v >= 0 {
|
||||
|
|
@ -368,7 +368,7 @@ func ParseTrunkRecorderMeta(call *Call, b []byte) error {
|
|||
}
|
||||
|
||||
switch v := m["patched_talkgroups"].(type) {
|
||||
case []interface{}:
|
||||
case []any:
|
||||
patches := []uint{}
|
||||
for _, f := range v {
|
||||
switch v := f.(type) {
|
||||
|
|
@ -384,12 +384,12 @@ func ParseTrunkRecorderMeta(call *Call, b []byte) error {
|
|||
}
|
||||
|
||||
switch v := m["srcList"].(type) {
|
||||
case []interface{}:
|
||||
sources := []map[string]interface{}{}
|
||||
case []any:
|
||||
sources := []map[string]any{}
|
||||
for _, f := range v {
|
||||
source := map[string]interface{}{}
|
||||
source := map[string]any{}
|
||||
switch v := f.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
switch v := v["pos"].(type) {
|
||||
case float64:
|
||||
if v >= 0 {
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import (
|
|||
type Scheduler struct {
|
||||
Controller *Controller
|
||||
Ticker *time.Ticker
|
||||
cancel chan interface{}
|
||||
cancel chan any
|
||||
mutex sync.Mutex
|
||||
started bool
|
||||
}
|
||||
|
|
@ -33,7 +33,7 @@ type Scheduler struct {
|
|||
func NewScheduler(controller *Controller) *Scheduler {
|
||||
return &Scheduler{
|
||||
Controller: controller,
|
||||
cancel: make(chan interface{}),
|
||||
cancel: make(chan any),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -42,9 +42,6 @@ func (scheduler *Scheduler) pruneDatabase() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
scheduler.Controller.IngestLock()
|
||||
defer scheduler.Controller.IngestUnlock()
|
||||
|
||||
scheduler.Controller.Logs.LogEvent(LogLevelInfo, "database pruning")
|
||||
|
||||
if err := scheduler.Controller.Calls.Prune(scheduler.Controller.Database, scheduler.Controller.Options.PruneDays); err != nil {
|
||||
|
|
|
|||
|
|
@ -30,9 +30,9 @@ type System struct {
|
|||
AutoPopulate bool `json:"autoPopulate"`
|
||||
Blacklists Blacklists `json:"blacklists"`
|
||||
Label string `json:"label"`
|
||||
Led interface{} `json:"led"`
|
||||
Led any `json:"led"`
|
||||
Order uint `json:"order"`
|
||||
RowId interface{} `json:"_id"`
|
||||
RowId any `json:"_id"`
|
||||
Talkgroups *Talkgroups `json:"talkgroups"`
|
||||
Units *Units `json:"units"`
|
||||
}
|
||||
|
|
@ -44,7 +44,7 @@ func NewSystem() *System {
|
|||
}
|
||||
}
|
||||
|
||||
func (system *System) FromMap(m map[string]interface{}) *System {
|
||||
func (system *System) FromMap(m map[string]any) *System {
|
||||
switch v := m["_id"].(type) {
|
||||
case float64:
|
||||
system.RowId = uint(v)
|
||||
|
|
@ -81,19 +81,19 @@ func (system *System) FromMap(m map[string]interface{}) *System {
|
|||
}
|
||||
|
||||
switch v := m["talkgroups"].(type) {
|
||||
case []interface{}:
|
||||
case []any:
|
||||
system.Talkgroups.FromMap(v)
|
||||
}
|
||||
|
||||
switch v := m["units"].(type) {
|
||||
case []interface{}:
|
||||
case []any:
|
||||
system.Units.FromMap(v)
|
||||
}
|
||||
|
||||
return system
|
||||
}
|
||||
|
||||
type SystemMap map[string]interface{}
|
||||
type SystemMap map[string]any
|
||||
|
||||
type Systems struct {
|
||||
List []*System
|
||||
|
|
@ -107,7 +107,7 @@ func NewSystems() *Systems {
|
|||
}
|
||||
}
|
||||
|
||||
func (systems *Systems) FromMap(f []interface{}) *Systems {
|
||||
func (systems *Systems) FromMap(f []any) *Systems {
|
||||
systems.mutex.Lock()
|
||||
defer systems.mutex.Unlock()
|
||||
|
||||
|
|
@ -115,7 +115,7 @@ func (systems *Systems) FromMap(f []interface{}) *Systems {
|
|||
|
||||
for _, r := range f {
|
||||
switch m := r.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
system := NewSystem()
|
||||
system.FromMap(m)
|
||||
systems.List = append(systems.List, system)
|
||||
|
|
@ -141,7 +141,7 @@ NextId:
|
|||
return 0
|
||||
}
|
||||
|
||||
func (systems *Systems) GetSystem(f interface{}) (system *System, ok bool) {
|
||||
func (systems *Systems) GetSystem(f any) (system *System, ok bool) {
|
||||
systems.mutex.Lock()
|
||||
defer systems.mutex.Unlock()
|
||||
|
||||
|
|
@ -187,10 +187,10 @@ func (systems *Systems) GetScopedSystems(client *Client, groups *Groups, tags *T
|
|||
}
|
||||
}
|
||||
|
||||
case []interface{}:
|
||||
case []any:
|
||||
for _, fSystem := range v {
|
||||
switch v := fSystem.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
var (
|
||||
mSystemId = v["id"]
|
||||
mTalkgroups = v["talkgroups"]
|
||||
|
|
@ -216,7 +216,7 @@ func (systems *Systems) GetScopedSystems(client *Client, groups *Groups, tags *T
|
|||
continue
|
||||
}
|
||||
|
||||
case []interface{}:
|
||||
case []any:
|
||||
rawSystem := *system
|
||||
rawSystem.Talkgroups = NewTalkgroups()
|
||||
for _, fTalkgroupId := range v {
|
||||
|
|
|
|||
|
|
@ -24,11 +24,11 @@ import (
|
|||
)
|
||||
|
||||
type Tag struct {
|
||||
Id interface{} `json:"_id"`
|
||||
Id any `json:"_id"`
|
||||
Label string `json:"label"`
|
||||
}
|
||||
|
||||
func (tag *Tag) FromMap(m map[string]interface{}) *Tag {
|
||||
func (tag *Tag) FromMap(m map[string]any) *Tag {
|
||||
switch v := m["_id"].(type) {
|
||||
case float64:
|
||||
tag.Id = uint(v)
|
||||
|
|
@ -54,7 +54,7 @@ func NewTags() *Tags {
|
|||
}
|
||||
}
|
||||
|
||||
func (tags *Tags) FromMap(f []interface{}) *Tags {
|
||||
func (tags *Tags) FromMap(f []any) *Tags {
|
||||
tags.mutex.Lock()
|
||||
defer tags.mutex.Unlock()
|
||||
|
||||
|
|
@ -62,7 +62,7 @@ func (tags *Tags) FromMap(f []interface{}) *Tags {
|
|||
|
||||
for _, r := range f {
|
||||
switch m := r.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
tag := &Tag{}
|
||||
tag.FromMap(m)
|
||||
tags.List = append(tags.List, tag)
|
||||
|
|
@ -72,7 +72,7 @@ func (tags *Tags) FromMap(f []interface{}) *Tags {
|
|||
return tags
|
||||
}
|
||||
|
||||
func (tags *Tags) GetTag(f interface{}) (tag *Tag, ok bool) {
|
||||
func (tags *Tags) GetTag(f any) (tag *Tag, ok bool) {
|
||||
tags.mutex.Lock()
|
||||
defer tags.mutex.Unlock()
|
||||
|
||||
|
|
|
|||
|
|
@ -25,19 +25,19 @@ import (
|
|||
)
|
||||
|
||||
type Talkgroup struct {
|
||||
Frequency interface{} `json:"frequency"`
|
||||
Frequency any `json:"frequency"`
|
||||
group string
|
||||
GroupId uint `json:"groupId"`
|
||||
Id uint `json:"id"`
|
||||
Label string `json:"label"`
|
||||
Led interface{} `json:"led"`
|
||||
Led any `json:"led"`
|
||||
Name string `json:"name"`
|
||||
Order uint `json:"order"`
|
||||
TagId uint `json:"tagId"`
|
||||
tag string
|
||||
}
|
||||
|
||||
func (talkgroup *Talkgroup) FromMap(m map[string]interface{}) *Talkgroup {
|
||||
func (talkgroup *Talkgroup) FromMap(m map[string]any) *Talkgroup {
|
||||
switch v := m["id"].(type) {
|
||||
case float64:
|
||||
talkgroup.Id = uint(v)
|
||||
|
|
@ -91,7 +91,7 @@ func (talkgroup *Talkgroup) FromMap(m map[string]interface{}) *Talkgroup {
|
|||
return talkgroup
|
||||
}
|
||||
|
||||
type TalkgroupMap map[string]interface{}
|
||||
type TalkgroupMap map[string]any
|
||||
|
||||
type Talkgroups struct {
|
||||
List []*Talkgroup
|
||||
|
|
@ -105,7 +105,7 @@ func NewTalkgroups() *Talkgroups {
|
|||
}
|
||||
}
|
||||
|
||||
func (talkgroups *Talkgroups) FromMap(f []interface{}) *Talkgroups {
|
||||
func (talkgroups *Talkgroups) FromMap(f []any) *Talkgroups {
|
||||
talkgroups.mutex.Lock()
|
||||
defer talkgroups.mutex.Unlock()
|
||||
|
||||
|
|
@ -113,7 +113,7 @@ func (talkgroups *Talkgroups) FromMap(f []interface{}) *Talkgroups {
|
|||
|
||||
for _, r := range f {
|
||||
switch m := r.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
talkgroup := &Talkgroup{}
|
||||
talkgroup.FromMap(m)
|
||||
talkgroups.List = append(talkgroups.List, talkgroup)
|
||||
|
|
@ -123,7 +123,7 @@ func (talkgroups *Talkgroups) FromMap(f []interface{}) *Talkgroups {
|
|||
return talkgroups
|
||||
}
|
||||
|
||||
func (talkgroups *Talkgroups) GetTalkgroup(f interface{}) (system *Talkgroup, ok bool) {
|
||||
func (talkgroups *Talkgroups) GetTalkgroup(f any) (system *Talkgroup, ok bool) {
|
||||
talkgroups.mutex.Lock()
|
||||
defer talkgroups.mutex.Unlock()
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ type Unit struct {
|
|||
Order uint `json:"order"`
|
||||
}
|
||||
|
||||
func (unit *Unit) FromMap(m map[string]interface{}) *Unit {
|
||||
func (unit *Unit) FromMap(m map[string]any) *Unit {
|
||||
switch v := m["id"].(type) {
|
||||
case float64:
|
||||
unit.Id = uint(v)
|
||||
|
|
@ -78,7 +78,7 @@ func (units *Units) Add(id uint, label string) (*Units, bool) {
|
|||
return units, added
|
||||
}
|
||||
|
||||
func (units *Units) FromMap(f []interface{}) *Units {
|
||||
func (units *Units) FromMap(f []any) *Units {
|
||||
units.mutex.Lock()
|
||||
defer units.mutex.Unlock()
|
||||
|
||||
|
|
@ -86,7 +86,7 @@ func (units *Units) FromMap(f []interface{}) *Units {
|
|||
|
||||
for _, r := range f {
|
||||
switch m := r.(type) {
|
||||
case map[string]interface{}:
|
||||
case map[string]any:
|
||||
unit := &Unit{}
|
||||
unit.FromMap(m)
|
||||
units.List = append(units.List, unit)
|
||||
|
|
|
|||
|
|
@ -15,4 +15,4 @@
|
|||
|
||||
package main
|
||||
|
||||
const Version = "6.4.5"
|
||||
const Version = "6.5.0"
|
||||
|
|
|
|||
Loading…
Reference in a new issue