mirror of
https://github.com/chuot/rdio-scanner.git
synced 2026-08-16 01:43:01 -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
|
# 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
|
## Version 6.4
|
||||||
|
|
||||||
- New `-cmd` command line options to allow advanced administrative tasks.
|
- 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.
|
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-amd64-v6.5.0.zip
|
||||||
rdio-scanner-darwin-arm64-v6.4.5.zip
|
rdio-scanner-darwin-arm64-v6.5.0.zip
|
||||||
rdio-scanner-freebsd-amd64-v6.4.5.zip
|
rdio-scanner-freebsd-amd64-v6.5.0.zip
|
||||||
rdio-scanner-linux-386-v6.4.5.zip
|
rdio-scanner-linux-386-v6.5.0.zip
|
||||||
rdio-scanner-linux-amd64-v6.4.5.zip
|
rdio-scanner-linux-amd64-v6.5.0.zip
|
||||||
rdio-scanner-linux-arm64-v6.4.5.zip
|
rdio-scanner-linux-arm64-v6.5.0.zip
|
||||||
rdio-scanner-linux-arm-v6.4.5.zip
|
rdio-scanner-linux-arm-v6.5.0.zip
|
||||||
rdio-scanner-windows-amd64-v6.4.5.zip
|
rdio-scanner-windows-amd64-v6.5.0.zip
|
||||||
|
|
||||||
**Happy Rdio scanning !**
|
**Happy Rdio scanning !**
|
||||||
6
Makefile
6
Makefile
|
|
@ -16,14 +16,14 @@
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
app := rdio-scanner
|
app := rdio-scanner
|
||||||
date := 2022/06/14
|
date := 2022/06/27
|
||||||
ver := 6.4.5
|
ver := 6.5.0
|
||||||
|
|
||||||
client := $(wildcard client/*.json client/*.ts)
|
client := $(wildcard client/*.json client/*.ts)
|
||||||
server := $(wildcard server/*.go)
|
server := $(wildcard server/*.go)
|
||||||
|
|
||||||
build = @cd server && GOOS=$(1) GOARCH=$(2) go build -o ../dist/$(1)-$(2)/$(3)
|
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 ..
|
zip = @cd dist/$(1)-$(2) && zip -q ../$(app)-$(1)-$(2)-v$(ver).zip * && cd ..
|
||||||
|
|
||||||
.PHONY: all clean container dist sed
|
.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",
|
"name": "rdio-scanner",
|
||||||
"version": "6.4.5",
|
"version": "6.5.0",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "rdio-scanner",
|
"name": "rdio-scanner",
|
||||||
"version": "6.4.5",
|
"version": "6.5.0",
|
||||||
"license": "LICENSE",
|
"license": "LICENSE",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^13.3.10",
|
"@angular/animations": "^13.3.10",
|
||||||
|
|
|
||||||
|
|
@ -38,5 +38,5 @@
|
||||||
"build": "ng build --base-href ./ --configuration production",
|
"build": "ng build --base-href ./ --configuration production",
|
||||||
"start": "ng serve"
|
"start": "ng serve"
|
||||||
},
|
},
|
||||||
"version": "6.4.5"
|
"version": "6.5.0"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -132,9 +132,11 @@ export interface Options {
|
||||||
afsSystems?: string;
|
afsSystems?: string;
|
||||||
audioConversion?: 0 | 1 | 2 | 3;
|
audioConversion?: 0 | 1 | 2 | 3;
|
||||||
autoPopulate?: boolean;
|
autoPopulate?: boolean;
|
||||||
|
branding?: string;
|
||||||
dimmerDelay?: number;
|
dimmerDelay?: number;
|
||||||
disableDuplicateDetection?: boolean;
|
disableDuplicateDetection?: boolean;
|
||||||
duplicateDetectionTimeFrame?: number;
|
duplicateDetectionTimeFrame?: number;
|
||||||
|
email?: string;
|
||||||
keypadBeeps?: string;
|
keypadBeeps?: string;
|
||||||
maxClients?: number;
|
maxClients?: number;
|
||||||
playbackGoesLive?: boolean;
|
playbackGoesLive?: boolean;
|
||||||
|
|
@ -497,9 +499,11 @@ export class RdioScannerAdminService implements OnDestroy {
|
||||||
afsSystems: [options?.afsSystems, this.validateAfsSystems()],
|
afsSystems: [options?.afsSystems, this.validateAfsSystems()],
|
||||||
audioConversion: [options?.audioConversion],
|
audioConversion: [options?.audioConversion],
|
||||||
autoPopulate: [options?.autoPopulate],
|
autoPopulate: [options?.autoPopulate],
|
||||||
|
branding: [options?.branding],
|
||||||
dimmerDelay: [options?.dimmerDelay, [Validators.required, Validators.min(0)]],
|
dimmerDelay: [options?.dimmerDelay, [Validators.required, Validators.min(0)]],
|
||||||
disableDuplicateDetection: [options?.disableDuplicateDetection],
|
disableDuplicateDetection: [options?.disableDuplicateDetection],
|
||||||
duplicateDetectionTimeFrame: [options?.duplicateDetectionTimeFrame, [Validators.required, Validators.min(0)]],
|
duplicateDetectionTimeFrame: [options?.duplicateDetectionTimeFrame, [Validators.required, Validators.min(0)]],
|
||||||
|
email: [options?.email],
|
||||||
keypadBeeps: [options?.keypadBeeps, Validators.required],
|
keypadBeeps: [options?.keypadBeeps, Validators.required],
|
||||||
maxClients: [options?.maxClients, [Validators.required, Validators.min(1)]],
|
maxClients: [options?.maxClients, [Validators.required, Validators.min(1)]],
|
||||||
playbackGoesLive: [options?.playbackGoesLive],
|
playbackGoesLive: [options?.playbackGoesLive],
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,16 @@
|
||||||
<mat-slide-toggle color="primary" formControlName="autoPopulate"></mat-slide-toggle>
|
<mat-slide-toggle color="primary" formControlName="autoPopulate"></mat-slide-toggle>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="row">
|
||||||
<p>
|
<p>
|
||||||
<span class="mat-body">Dimmer Delay</span><br>
|
<span class="mat-body">Dimmer Delay</span><br>
|
||||||
|
|
@ -86,6 +96,15 @@
|
||||||
</mat-error>
|
</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</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">
|
<div class="row">
|
||||||
<p>
|
<p>
|
||||||
<span class="mat-body">Keypad Beep Style</span><br>
|
<span class="mat-body">Keypad Beep Style</span><br>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
.rdio-button,
|
.rdio-button,
|
||||||
.rdio-button-mini {
|
.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-style: solid;
|
||||||
border-width: 1px;
|
border-width: 1px;
|
||||||
border-bottom-color: rgba(0, 0, 0, 0.87);
|
border-bottom-color: rgba(0, 0, 0, 0.87);
|
||||||
|
|
@ -41,24 +46,71 @@
|
||||||
right: 4px;
|
right: 4px;
|
||||||
top: 4px;
|
top: 4px;
|
||||||
width: 6px;
|
width: 6px;
|
||||||
|
|
||||||
|
animation: blink 1s linear infinite both;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.off::after {
|
&.off::after {
|
||||||
background: rgb(255, 23, 68);
|
background: var(--red);
|
||||||
box-shadow: 1px 1px 1px rgba(255, 255, 255, 0.7) inset,
|
box-shadow: 1px 1px 1px rgba(255, 255, 255, 0.7) inset, 0 0 3px 1px var(--red);
|
||||||
0 0 3px 1px rgb(255, 23, 68);
|
}
|
||||||
|
|
||||||
|
&.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 {
|
&.on::after {
|
||||||
background: rgb(0, 230, 118);;
|
background: var(--green);
|
||||||
box-shadow: 1px 1px 1px rgba(255, 255, 255, 0.7) inset,
|
box-shadow: 1px 1px 1px rgba(255, 255, 255, 0.7) inset, 0 0 3px 1px var(--green);
|
||||||
0 0 3px 1px rgb(0, 230, 118);;
|
}
|
||||||
|
|
||||||
|
&.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 {
|
&.partial::after {
|
||||||
background: rgb(255, 234, 0);
|
background: var(--yellow);
|
||||||
box-shadow: 1px 1px 1px rgba(255, 255, 255, 0.7) inset,
|
box-shadow: 1px 1px 1px rgba(255, 255, 255, 0.7) inset, 0 0 3px 1px var(--yellow);
|
||||||
0 0 3px 1px rgb(255, 234, 0);
|
}
|
||||||
|
|
||||||
|
&.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="rdio-status">
|
||||||
|
<div class="branding">{{ branding }}</div>
|
||||||
<div class="led" [ngClass]="ledStyle"></div>
|
<div class="led" [ngClass]="ledStyle"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rdio-display" [ngClass]="{ idle: !dimmer }" (dblclick)="toggleFullscreen.emit()">
|
<div class="rdio-display" [ngClass]="{ idle: !dimmer }" (dblclick)="toggleFullscreen.emit()">
|
||||||
|
|
@ -53,6 +54,9 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row right small">
|
<div class="row right small">
|
||||||
|
<div *ngIf="tempAvoid">
|
||||||
|
<span class="flag" [ngClass]="{ flaged: avoided || patched }">⏲ {{ tempAvoid }}M</span>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<span class="flag" [ngClass]="{ flaged: avoided }">AVOID</span>
|
<span class="flag" [ngClass]="{ flaged: avoided }">AVOID</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -154,3 +158,8 @@
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</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 {
|
.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 {
|
.led {
|
||||||
background: rgb(80, 80, 80);
|
background: rgb(80, 80, 80);
|
||||||
display: block;
|
display: block;
|
||||||
height: 12px;
|
height: 12px;
|
||||||
margin-left: auto;
|
margin-left: 24px;
|
||||||
width: 24px;
|
width: 24px;
|
||||||
|
|
||||||
&.on {
|
&.on {
|
||||||
|
|
@ -240,3 +259,10 @@
|
||||||
.rdio-status {
|
.rdio-status {
|
||||||
margin-bottom: 24px;
|
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 { ChangeDetectorRef, Component, EventEmitter, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
|
||||||
import { FormBuilder } from '@angular/forms';
|
import { FormBuilder } from '@angular/forms';
|
||||||
import { MatInput } from '@angular/material/input';
|
import { MatInput } from '@angular/material/input';
|
||||||
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { Subscription, timer } from 'rxjs';
|
import { Subscription, timer } from 'rxjs';
|
||||||
import packageInfo from '../../../../../package.json';
|
import packageInfo from '../../../../../package.json';
|
||||||
import {
|
import {
|
||||||
|
|
@ -32,6 +33,7 @@ import {
|
||||||
RdioScannerLivefeedMode,
|
RdioScannerLivefeedMode,
|
||||||
} from '../rdio-scanner';
|
} from '../rdio-scanner';
|
||||||
import { RdioScannerService } from '../rdio-scanner.service';
|
import { RdioScannerService } from '../rdio-scanner.service';
|
||||||
|
import { RdioScannerSupportComponent } from './support/support.component';
|
||||||
|
|
||||||
const LOCAL_STORAGE_KEY = RdioScannerService.LOCAL_STORAGE_KEY + '-pin';
|
const LOCAL_STORAGE_KEY = RdioScannerService.LOCAL_STORAGE_KEY + '-pin';
|
||||||
|
|
||||||
|
|
@ -49,6 +51,8 @@ export class RdioScannerMainComponent implements OnDestroy, OnInit {
|
||||||
|
|
||||||
avoided = false;
|
avoided = false;
|
||||||
|
|
||||||
|
branding = '';
|
||||||
|
|
||||||
call: RdioScannerCall | undefined;
|
call: RdioScannerCall | undefined;
|
||||||
callError = '0';
|
callError = '0';
|
||||||
callFrequency: string = this.formatFrequency(0);
|
callFrequency: string = this.formatFrequency(0);
|
||||||
|
|
@ -61,20 +65,21 @@ export class RdioScannerMainComponent implements OnDestroy, OnInit {
|
||||||
callTag = 'Tag';
|
callTag = 'Tag';
|
||||||
callTalkgroup = 'Talkgroup';
|
callTalkgroup = 'Talkgroup';
|
||||||
callTalkgroupId = '0';
|
callTalkgroupId = '0';
|
||||||
/*
|
|
||||||
* BEGIN OF RED TAPE:
|
//
|
||||||
*
|
// 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
|
// By modifying, deleting or disabling the following lines, you harm
|
||||||
* investment in time, support, testing and hardware.
|
// 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.
|
//
|
||||||
*
|
// Be respectful, sponsor the project if you can, use native apps when possible.
|
||||||
*/
|
//
|
||||||
callTalkgroupName = `Rdio Scanner v${packageInfo.version}`;
|
callTalkgroupName = `Rdio Scanner v${packageInfo.version}`;
|
||||||
/**
|
//
|
||||||
* END OF RED TAPE.
|
// END OF RED TAPE.
|
||||||
*/
|
//
|
||||||
|
|
||||||
callTime = 0;
|
callTime = 0;
|
||||||
callUnit = '0';
|
callUnit = '0';
|
||||||
|
|
||||||
|
|
@ -82,6 +87,8 @@ export class RdioScannerMainComponent implements OnDestroy, OnInit {
|
||||||
|
|
||||||
dimmer = false;
|
dimmer = false;
|
||||||
|
|
||||||
|
email = '';
|
||||||
|
|
||||||
holdSys = false;
|
holdSys = false;
|
||||||
holdTg = false;
|
holdTg = false;
|
||||||
|
|
||||||
|
|
@ -104,6 +111,8 @@ export class RdioScannerMainComponent implements OnDestroy, OnInit {
|
||||||
replayOffset = 0;
|
replayOffset = 0;
|
||||||
replayTimer: Subscription | undefined;
|
replayTimer: Subscription | undefined;
|
||||||
|
|
||||||
|
tempAvoid = 0;
|
||||||
|
|
||||||
timeFormat = 'HH:mm';
|
timeFormat = 'HH:mm';
|
||||||
|
|
||||||
get showListenersCount(): boolean {
|
get showListenersCount(): boolean {
|
||||||
|
|
@ -128,6 +137,7 @@ export class RdioScannerMainComponent implements OnDestroy, OnInit {
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private rdioScannerService: RdioScannerService,
|
private rdioScannerService: RdioScannerService,
|
||||||
|
private matSnackBar: MatSnackBar,
|
||||||
private ngChangeDetectorRef: ChangeDetectorRef,
|
private ngChangeDetectorRef: ChangeDetectorRef,
|
||||||
private ngFormBuilder: FormBuilder,
|
private ngFormBuilder: FormBuilder,
|
||||||
) { }
|
) { }
|
||||||
|
|
@ -145,28 +155,43 @@ export class RdioScannerMainComponent implements OnDestroy, OnInit {
|
||||||
}
|
}
|
||||||
|
|
||||||
avoid(options?: RdioScannerAvoidOptions): void {
|
avoid(options?: RdioScannerAvoidOptions): void {
|
||||||
|
const call = this.call || this.callPrevious;
|
||||||
|
|
||||||
if (this.auth) {
|
if (this.auth) {
|
||||||
this.authFocus();
|
this.authFocus();
|
||||||
|
|
||||||
} else {
|
} else if (options || call) {
|
||||||
const call = this.call || this.callPrevious;
|
if (options) {
|
||||||
|
|
||||||
if (options || call) {
|
|
||||||
this.rdioScannerService.avoid(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.beep(RdioScannerBeepStyle.Activate);
|
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 {
|
} else {
|
||||||
this.rdioScannerService.beep(RdioScannerBeepStyle.Deactivate);
|
this.rdioScannerService.avoid({ status: true });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (call && this.rdioScannerService.isAvoided(call)) {
|
||||||
|
this.rdioScannerService.beep(RdioScannerBeepStyle.Activate);
|
||||||
} else {
|
} else {
|
||||||
this.rdioScannerService.beep(RdioScannerBeepStyle.Denied);
|
this.rdioScannerService.beep(RdioScannerBeepStyle.Deactivate);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateDimmer();
|
this.updateDimmer();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
this.rdioScannerService.beep(RdioScannerBeepStyle.Denied);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
holdSystem(): void {
|
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 {
|
showSearchPanel(): void {
|
||||||
if (!this.config) {
|
if (!this.config) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -378,6 +410,10 @@ export class RdioScannerMainComponent implements OnDestroy, OnInit {
|
||||||
if ('config' in event) {
|
if ('config' in event) {
|
||||||
this.config = event.config;
|
this.config = event.config;
|
||||||
|
|
||||||
|
this.branding = this.config?.branding ?? '';
|
||||||
|
|
||||||
|
this.email = this.config?.email ?? '';
|
||||||
|
|
||||||
this.timeFormat = this.config?.time12hFormat ? 'h:mm a' : 'HH:mm';
|
this.timeFormat = this.config?.time12hFormat ? 'h:mm a' : 'HH:mm';
|
||||||
|
|
||||||
const password = this.authForm.get('password')?.value;
|
const password = this.authForm.get('password')?.value;
|
||||||
|
|
@ -566,9 +602,12 @@ export class RdioScannerMainComponent implements OnDestroy, OnInit {
|
||||||
const call = this.call || this.callPrevious;
|
const call = this.call || this.callPrevious;
|
||||||
|
|
||||||
if (call) {
|
if (call) {
|
||||||
|
this.tempAvoid = this.rdioScannerService.isAvoidedTimer(call);
|
||||||
|
|
||||||
if (this.rdioScannerService.isPatched(call)) {
|
if (this.rdioScannerService.isPatched(call)) {
|
||||||
this.avoided = false;
|
this.avoided = false;
|
||||||
this.patched = true;
|
this.patched = true;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this.avoided = this.rdioScannerService.isAvoided(call);
|
this.avoided = this.rdioScannerService.isAvoided(call);
|
||||||
this.patched = false;
|
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({
|
@Component({
|
||||||
selector: 'RdioScannerNative',
|
selector: 'RdioScannerNative',
|
||||||
styleUrls: ['./native.scss'],
|
styleUrls: ['./native.component.scss'],
|
||||||
templateUrl: './native.component.html',
|
templateUrl: './native.component.html',
|
||||||
})
|
})
|
||||||
export class RdioScannerNativeComponent implements OnInit {
|
export class RdioScannerNativeComponent implements OnInit {
|
||||||
|
|
@ -34,8 +34,7 @@ export class RdioScannerNativeComponent implements OnInit {
|
||||||
isAndroid: boolean = false;
|
isAndroid: boolean = false;
|
||||||
isApple: boolean = false;
|
isApple: boolean = false;
|
||||||
|
|
||||||
constructor(@Optional() private matSnackBarRef: MatSnackBarRef<RdioScannerNativeComponent>) {
|
constructor(@Optional() private matSnackBarRef: MatSnackBarRef<RdioScannerNativeComponent>) { }
|
||||||
}
|
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
const ua = navigator.userAgent;
|
const ua = navigator.userAgent;
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ $background-color: rgb(30, 30, 30);
|
||||||
display: block;
|
display: block;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-width: 320px;
|
min-width: 320px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
.mat-sidenav {
|
.mat-sidenav {
|
||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import { AppSharedModule } from '../../shared/shared.module';
|
||||||
import { RdioScannerComponent } from './rdio-scanner.component';
|
import { RdioScannerComponent } from './rdio-scanner.component';
|
||||||
import { RdioScannerService } from './rdio-scanner.service';
|
import { RdioScannerService } from './rdio-scanner.service';
|
||||||
import { RdioScannerMainComponent } from './main/main.component';
|
import { RdioScannerMainComponent } from './main/main.component';
|
||||||
|
import { RdioScannerSupportComponent } from './main/support/support.component';
|
||||||
import { RdioScannerNativeModule } from './native/native.module';
|
import { RdioScannerNativeModule } from './native/native.module';
|
||||||
import { RdioScannerSearchComponent } from './search/search.component';
|
import { RdioScannerSearchComponent } from './search/search.component';
|
||||||
import { RdioScannerSelectComponent } from './select/select.component';
|
import { RdioScannerSelectComponent } from './select/select.component';
|
||||||
|
|
@ -33,9 +34,13 @@ import { RdioScannerSelectComponent } from './select/select.component';
|
||||||
RdioScannerMainComponent,
|
RdioScannerMainComponent,
|
||||||
RdioScannerSearchComponent,
|
RdioScannerSearchComponent,
|
||||||
RdioScannerSelectComponent,
|
RdioScannerSelectComponent,
|
||||||
|
RdioScannerSupportComponent,
|
||||||
],
|
],
|
||||||
exports: [RdioScannerComponent],
|
exports: [RdioScannerComponent],
|
||||||
imports: [AppSharedModule, RdioScannerNativeModule],
|
imports: [
|
||||||
|
AppSharedModule,
|
||||||
|
RdioScannerNativeModule,
|
||||||
|
],
|
||||||
providers: [
|
providers: [
|
||||||
RdioScannerService,
|
RdioScannerService,
|
||||||
{ provide: OverlayContainer, useClass: FullscreenOverlayContainer },
|
{ provide: OverlayContainer, useClass: FullscreenOverlayContainer },
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ import {
|
||||||
RdioScannerCategoryType,
|
RdioScannerCategoryType,
|
||||||
RdioScannerConfig,
|
RdioScannerConfig,
|
||||||
RdioScannerEvent,
|
RdioScannerEvent,
|
||||||
|
RdioScannerLivefeed,
|
||||||
RdioScannerLivefeedMap,
|
RdioScannerLivefeedMap,
|
||||||
RdioScannerLivefeedMode,
|
RdioScannerLivefeedMode,
|
||||||
RdioScannerPlaybackList,
|
RdioScannerPlaybackList,
|
||||||
|
|
@ -57,6 +58,7 @@ enum WebsocketCommand {
|
||||||
LivefeedMap = 'LFM',
|
LivefeedMap = 'LFM',
|
||||||
Max = 'MAX',
|
Max = 'MAX',
|
||||||
Pin = 'PIN',
|
Pin = 'PIN',
|
||||||
|
Version = 'VER',
|
||||||
}
|
}
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
|
|
@ -100,8 +102,6 @@ export class RdioScannerService implements OnDestroy {
|
||||||
private playbackPending: number | undefined;
|
private playbackPending: number | undefined;
|
||||||
private playbackRefreshing = false;
|
private playbackRefreshing = false;
|
||||||
|
|
||||||
private replayDelay: Subscription | undefined;
|
|
||||||
|
|
||||||
private skipDelay: Subscription | undefined;
|
private skipDelay: Subscription | undefined;
|
||||||
|
|
||||||
private websocket: WebSocket | undefined;
|
private websocket: WebSocket | undefined;
|
||||||
|
|
@ -122,6 +122,29 @@ export class RdioScannerService implements OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
avoid(options: RdioScannerAvoidOptions = {}): void {
|
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) {
|
if (this.livefeedMapPriorToHoldSystem) {
|
||||||
this.livefeedMapPriorToHoldSystem = undefined;
|
this.livefeedMapPriorToHoldSystem = undefined;
|
||||||
}
|
}
|
||||||
|
|
@ -133,37 +156,39 @@ export class RdioScannerService implements OnDestroy {
|
||||||
if (typeof options.all === 'boolean') {
|
if (typeof options.all === 'boolean') {
|
||||||
Object.keys(this.livefeedMap).map((sys: string) => +sys).forEach((sys: number) => {
|
Object.keys(this.livefeedMap).map((sys: string) => +sys).forEach((sys: number) => {
|
||||||
Object.keys(this.livefeedMap[sys]).map((tg: string) => +tg).forEach((tg: 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) {
|
} else if (options.call) {
|
||||||
const sys = options.call.system;
|
const lfm = this.livefeedMap[options.call.system][options.call.talkgroup];
|
||||||
const tg = options.call.talkgroup;
|
clearTimer(lfm);
|
||||||
|
lfm.active = typeof options.status === 'boolean' ? options.status : !lfm.active;
|
||||||
this.livefeedMap[sys][tg] = typeof options.status === 'boolean' ? options.status : !this.livefeedMap[sys][tg];
|
if (typeof options.minutes === 'number') setTimer(lfm, options.minutes);
|
||||||
|
|
||||||
} else if (options.system && options.talkgroup) {
|
} else if (options.system && options.talkgroup) {
|
||||||
const sys = options.system.id;
|
const lfm = this.livefeedMap[options.system.id][options.talkgroup.id];
|
||||||
const tg = options.talkgroup.id;
|
clearTimer(lfm);
|
||||||
|
lfm.active = typeof options.status === 'boolean' ? options.status : !lfm.active;
|
||||||
this.livefeedMap[sys][tg] = typeof options.status === 'boolean' ? options.status : !this.livefeedMap[sys][tg];
|
if (typeof options.minutes === 'number') setTimer(lfm, options.minutes);
|
||||||
|
|
||||||
} else if (options.system && !options.talkgroup) {
|
} else if (options.system && !options.talkgroup) {
|
||||||
const sys = options.system.id;
|
const sys = options.system.id;
|
||||||
|
|
||||||
Object.keys(this.livefeedMap[sys]).map((tg: string) => +tg).forEach((tg: number) => {
|
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 {
|
} else {
|
||||||
const call = this.call || this.callPrevious;
|
const call = this.call || this.callPrevious;
|
||||||
|
|
||||||
if (call) {
|
if (call) {
|
||||||
const sys = call.system;
|
const lfm = this.livefeedMap[call.system][call.talkgroup];
|
||||||
const tg = call.talkgroup;
|
clearTimer(lfm);
|
||||||
|
lfm.active = typeof options.status === 'boolean' ? options.status : !lfm.active;
|
||||||
this.livefeedMap[sys][tg] = typeof options.status === 'boolean' ? options.status : !this.livefeedMap[sys][tg];
|
if (typeof options.minutes === 'number') setTimer(lfm, options.minutes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -243,18 +268,20 @@ export class RdioScannerService implements OnDestroy {
|
||||||
this.livefeedMapPriorToHoldSystem = this.livefeedMap;
|
this.livefeedMapPriorToHoldSystem = this.livefeedMap;
|
||||||
|
|
||||||
this.livefeedMap = Object.keys(this.livefeedMap).map((sys) => +sys).reduce((sysMap, sys) => {
|
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) => {
|
sysMap[sys] = Object.keys(this.livefeedMap[sys]).map((tg) => +tg).reduce((tgMap, tg) => {
|
||||||
if (sys === call.system) {
|
tgMap[tg].minutes = undefined;
|
||||||
tgMap[tg] = allOn || this.livefeedMap[sys][tg];
|
tgMap[tg].timer?.unsubscribe();
|
||||||
|
tgMap[tg].timer = undefined;
|
||||||
|
|
||||||
} else {
|
if (sys === call.system)
|
||||||
tgMap[tg] = false;
|
tgMap[tg].active = allOn || this.livefeedMap[sys][tg].active;
|
||||||
}
|
else
|
||||||
|
tgMap[tg].active = false;
|
||||||
|
|
||||||
return tgMap;
|
return tgMap;
|
||||||
}, {} as { [key: number]: boolean });
|
}, {} as { [key: number]: RdioScannerLivefeed });
|
||||||
|
|
||||||
return sysMap;
|
return sysMap;
|
||||||
}, {} as RdioScannerLivefeedMap);
|
}, {} as RdioScannerLivefeedMap);
|
||||||
|
|
@ -298,15 +325,17 @@ export class RdioScannerService implements OnDestroy {
|
||||||
|
|
||||||
this.livefeedMap = Object.keys(this.livefeedMap).map((sys) => +sys).reduce((sysMap, sys) => {
|
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) => {
|
sysMap[sys] = Object.keys(this.livefeedMap[sys]).map((tg) => +tg).reduce((tgMap, tg) => {
|
||||||
if (sys === call.system) {
|
tgMap[tg].minutes = undefined;
|
||||||
tgMap[tg] = tg === call.talkgroup;
|
tgMap[tg].timer?.unsubscribe();
|
||||||
|
tgMap[tg].timer = undefined;
|
||||||
|
|
||||||
} else {
|
if (sys === call.system)
|
||||||
tgMap[tg] = false;
|
tgMap[tg].active = tg === call.talkgroup;
|
||||||
}
|
else
|
||||||
|
tgMap[tg].active = false;
|
||||||
|
|
||||||
return tgMap;
|
return tgMap;
|
||||||
}, {} as { [key: number]: boolean });
|
}, {} as { [key: number]: RdioScannerLivefeed });
|
||||||
|
|
||||||
return sysMap;
|
return sysMap;
|
||||||
}, {} as RdioScannerLivefeedMap);
|
}, {} as RdioScannerLivefeedMap);
|
||||||
|
|
@ -333,12 +362,19 @@ export class RdioScannerService implements OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
isAvoided(call: RdioScannerCall): boolean {
|
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 {
|
isPatched(call: RdioScannerCall): boolean {
|
||||||
return this.isAvoided(call) && call.patches.some((tg) => {
|
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 {
|
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.livefeedMode = RdioScannerLivefeedMode.Online;
|
||||||
|
|
||||||
this.event.emit({ livefeedMode: this.livefeedMode });
|
this.event.emit({ livefeedMode: this.livefeedMode });
|
||||||
|
|
||||||
this.sendtoWebsocket(WebsocketCommand.LivefeedMap, this.livefeedMap);
|
this.sendtoWebsocket(WebsocketCommand.LivefeedMap, lfm);
|
||||||
}
|
}
|
||||||
|
|
||||||
stop(options?: { emit?: boolean }): void {
|
stop(options?: { emit?: boolean }): void {
|
||||||
|
|
@ -595,6 +639,12 @@ export class RdioScannerService implements OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleCategory(category: RdioScannerCategory): void {
|
toggleCategory(category: RdioScannerCategory): void {
|
||||||
|
const clearTimer = (lfm: RdioScannerLivefeed): void => {
|
||||||
|
lfm.minutes = 0;
|
||||||
|
lfm.timer?.unsubscribe();
|
||||||
|
lfm.timer = undefined;
|
||||||
|
};
|
||||||
|
|
||||||
if (category) {
|
if (category) {
|
||||||
if (this.livefeedMapPriorToHoldSystem) {
|
if (this.livefeedMapPriorToHoldSystem) {
|
||||||
this.livefeedMapPriorToHoldSystem = undefined;
|
this.livefeedMapPriorToHoldSystem = undefined;
|
||||||
|
|
@ -608,20 +658,22 @@ export class RdioScannerService implements OnDestroy {
|
||||||
|
|
||||||
this.config?.systems.forEach((sys) => {
|
this.config?.systems.forEach((sys) => {
|
||||||
sys.talkgroups?.forEach((tg) => {
|
sys.talkgroups?.forEach((tg) => {
|
||||||
if (category.type == RdioScannerCategoryType.Group && tg.group === category.label) {
|
const lfm = this.livefeedMap[sys.id][tg.id];
|
||||||
this.livefeedMap[sys.id][tg.id] = status;
|
|
||||||
|
|
||||||
|
if (category.type == RdioScannerCategoryType.Group && tg.group === category.label) {
|
||||||
|
clearTimer(lfm);
|
||||||
|
lfm.active = status;
|
||||||
} else if (category.type == RdioScannerCategoryType.Tag && tg.tag === category.label) {
|
} else if (category.type == RdioScannerCategoryType.Tag && tg.tag === category.label) {
|
||||||
this.livefeedMap[sys.id][tg.id] = status;
|
clearTimer(lfm);
|
||||||
|
lfm.active = status;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.rebuildCategories();
|
this.rebuildCategories();
|
||||||
|
|
||||||
if (this.call && !this.livefeedMap[this.call.system] &&
|
if (this.call && !this.livefeedMap[this.call.system] && this.livefeedMap[this.call.system][this.call.talkgroup]) {
|
||||||
this.livefeedMap[this.call.system][this.call.talkgroup]) {
|
clearTimer(this.livefeedMap[this.call.system][this.call.talkgroup]);
|
||||||
|
|
||||||
this.skip();
|
this.skip();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -691,7 +743,7 @@ export class RdioScannerService implements OnDestroy {
|
||||||
|
|
||||||
private cleanQueue(): void {
|
private cleanQueue(): void {
|
||||||
let isActive = (call: RdioScannerCall) => {
|
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);
|
let active = lfm(call.system, call.talkgroup);
|
||||||
if (!active && Array.isArray(call.patches)) {
|
if (!active && Array.isArray(call.patches)) {
|
||||||
for (let i = 0; i < call.patches.length; i++) {
|
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.websocket.onmessage = (ev: MessageEvent) => this.parseWebsocketMessage(ev.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.sendtoWebsocket(WebsocketCommand.Version);
|
||||||
this.sendtoWebsocket(WebsocketCommand.Config);
|
this.sendtoWebsocket(WebsocketCommand.Config);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -831,7 +884,9 @@ export class RdioScannerService implements OnDestroy {
|
||||||
const config = message[1];
|
const config = message[1];
|
||||||
|
|
||||||
this.config = {
|
this.config = {
|
||||||
|
branding: typeof config.branding === 'string' ? config.branding : '',
|
||||||
dimmerDelay: typeof config.dimmerDelay === 'number' ? config.dimmerDelay : 5000,
|
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 : {},
|
groups: typeof config.groups !== null && typeof config.groups === 'object' ? config.groups : {},
|
||||||
keypadBeeps: config.keypadBeeps !== null && typeof config.keypadBeeps === 'object' ? config.keypadBeeps : {},
|
keypadBeeps: config.keypadBeeps !== null && typeof config.keypadBeeps === 'object' ? config.keypadBeeps : {},
|
||||||
playbackGoesLive: typeof config.playbackGoesLive === 'boolean' ? config.playbackGoesLive : false,
|
playbackGoesLive: typeof config.playbackGoesLive === 'boolean' ? config.playbackGoesLive : false,
|
||||||
|
|
@ -897,6 +952,28 @@ export class RdioScannerService implements OnDestroy {
|
||||||
case WebsocketCommand.Pin:
|
case WebsocketCommand.Pin:
|
||||||
this.event.emit({ auth: true });
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -969,11 +1046,11 @@ export class RdioScannerService implements OnDestroy {
|
||||||
this.categories = Object.keys(this.config.groups || []).map((label) => {
|
this.categories = Object.keys(this.config.groups || []).map((label) => {
|
||||||
const allOff = Object.keys(this.config.groups[label]).map((sys) => +sys)
|
const allOff = Object.keys(this.config.groups[label]).map((sys) => +sys)
|
||||||
.every((sys: number) => this.config.groups[label] && this.config.groups[label][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)
|
const allOn = Object.keys(this.config.groups[label]).map((sys) => +sys)
|
||||||
.every((sys: number) => this.config.groups[label] && this.config.groups[label][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;
|
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) => {
|
this.categories = this.categories.concat(Object.keys(this.config.tags || []).map((label) => {
|
||||||
const allOff = Object.keys(this.config.tags[label]).map((sys) => +sys)
|
const allOff = Object.keys(this.config.tags[label]).map((sys) => +sys)
|
||||||
.every((sys: number) => this.config.tags[label] && this.config.tags[label][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)
|
const allOn = Object.keys(this.config.tags[label]).map((sys) => +sys)
|
||||||
.every((sys: number) => this.config.tags[label] && this.config.tags[label][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;
|
const status = allOff ? RdioScannerCategoryStatus.Off : allOn ? RdioScannerCategoryStatus.On : RdioScannerCategoryStatus.Partial;
|
||||||
|
|
||||||
|
|
@ -1000,24 +1077,28 @@ export class RdioScannerService implements OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
private rebuildLivefeedMap(): void {
|
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) => {
|
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;
|
return tgMap;
|
||||||
}, sysMap[sys.id] || {});
|
}, sysMap[sys.id] || {} as { [key: number]: RdioScannerLivefeed });
|
||||||
|
|
||||||
return sysMap;
|
return sysMap;
|
||||||
}, {} as RdioScannerLivefeedMap);
|
}, {} as RdioScannerLivefeedMap);
|
||||||
|
|
||||||
if (this.livefeedMapPriorToHoldSystem != null) {
|
if (this.livefeedMapPriorToHoldSystem != null) {
|
||||||
this.livefeedMapPriorToHoldSystem = livefeedMap;
|
this.livefeedMapPriorToHoldSystem = lfm;
|
||||||
} else if (this.livefeedMapPriorToHoldTalkgroup != null) {
|
} else if (this.livefeedMapPriorToHoldTalkgroup != null) {
|
||||||
this.livefeedMapPriorToHoldTalkgroup = livefeedMap;
|
this.livefeedMapPriorToHoldTalkgroup = lfm;
|
||||||
} else {
|
} else {
|
||||||
this.livefeedMap = livefeedMap;
|
this.livefeedMap = lfm;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.storeLivefeedMap();
|
this.storeLivefeedMap();
|
||||||
|
|
@ -1032,16 +1113,17 @@ export class RdioScannerService implements OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
private restoreLivefeed(): void {
|
private restoreLivefeed(): void {
|
||||||
const map = window?.localStorage?.getItem(RdioScannerService.LOCAL_STORAGE_KEY);
|
try {
|
||||||
|
const lfm = JSON.parse(window?.localStorage?.getItem(RdioScannerService.LOCAL_STORAGE_KEY) || "{}");
|
||||||
|
|
||||||
if (map) {
|
Object.keys(lfm).forEach((sys) => {
|
||||||
try {
|
Object.keys(lfm[sys]).forEach((tg) => {
|
||||||
this.livefeedMap = JSON.parse(map);
|
if (!this.livefeedMap[+sys]) this.livefeedMap[+sys] = {};
|
||||||
|
if (!this.livefeedMap[+sys][+tg]) this.livefeedMap[+sys][+tg] = {} as RdioScannerLivefeed;
|
||||||
} catch (err) {
|
this.livefeedMap[+sys][+tg].active = lfm[sys][tg];
|
||||||
this.livefeedMap = {};
|
});
|
||||||
}
|
});
|
||||||
}
|
} catch (_) { }
|
||||||
}
|
}
|
||||||
|
|
||||||
private sendtoWebsocket(command: string, payload?: unknown, flags?: string): void {
|
private sendtoWebsocket(command: string, payload?: unknown, flags?: string): void {
|
||||||
|
|
@ -1061,7 +1143,15 @@ export class RdioScannerService implements OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
private storeLivefeedMap(): void {
|
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 {
|
private transformCall(call: RdioScannerCall): RdioScannerCall {
|
||||||
|
|
|
||||||
|
|
@ -17,12 +17,15 @@
|
||||||
* ****************************************************************************
|
* ****************************************************************************
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { Subscription } from "rxjs";
|
||||||
|
|
||||||
export interface RdioScannerAvoidOptions {
|
export interface RdioScannerAvoidOptions {
|
||||||
all?: boolean;
|
all?: boolean;
|
||||||
call?: RdioScannerCall;
|
call?: RdioScannerCall;
|
||||||
|
minutes?: number;
|
||||||
|
status?: boolean;
|
||||||
system?: RdioScannerSystem;
|
system?: RdioScannerSystem;
|
||||||
talkgroup?: RdioScannerTalkgroup;
|
talkgroup?: RdioScannerTalkgroup;
|
||||||
status?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RdioScannerBeep {
|
export interface RdioScannerBeep {
|
||||||
|
|
@ -90,7 +93,9 @@ export enum RdioScannerCategoryType {
|
||||||
|
|
||||||
export interface RdioScannerConfig {
|
export interface RdioScannerConfig {
|
||||||
afs?: string;
|
afs?: string;
|
||||||
|
branding?: string;
|
||||||
dimmerDelay: number | false;
|
dimmerDelay: number | false;
|
||||||
|
email?: string;
|
||||||
groups: { [key: string]: { [key: number]: number[] } };
|
groups: { [key: string]: { [key: number]: number[] } };
|
||||||
keypadBeeps: RdioScannerKeypadBeeps | false;
|
keypadBeeps: RdioScannerKeypadBeeps | false;
|
||||||
playbackGoesLive: boolean;
|
playbackGoesLive: boolean;
|
||||||
|
|
@ -127,9 +132,15 @@ export interface RdioScannerKeypadBeeps {
|
||||||
[RdioScannerBeepStyle.Denied]: RdioScannerBeep[];
|
[RdioScannerBeepStyle.Denied]: RdioScannerBeep[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface RdioScannerLivefeed {
|
||||||
|
active: boolean;
|
||||||
|
minutes: number | undefined;
|
||||||
|
timer: Subscription | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export interface RdioScannerLivefeedMap {
|
export interface RdioScannerLivefeedMap {
|
||||||
[key: string]: {
|
[key: number]: {
|
||||||
[key: string]: boolean;
|
[key: number]: RdioScannerLivefeed;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
</legend>
|
</legend>
|
||||||
<div>
|
<div>
|
||||||
<button *ngFor="let talkgroup of system.talkgroups" class="rdio-button"
|
<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 })">
|
(click)="avoid({ system: system, talkgroup: talkgroup })">
|
||||||
{{ talkgroup.label }}
|
{{ talkgroup.label }}
|
||||||
</button>
|
</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 ~ % mkdir rdio-scanner
|
||||||
rdio@macos ~ % cd rdio-scanner
|
rdio@macos ~ % cd rdio-scanner
|
||||||
rdio@macos rdio-scanner % unzip \
|
rdio@macos rdio-scanner % unzip \
|
||||||
> ~/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.4.5.zip
|
Archive: /Users/rdio/Downloads/rdio-scanner-darwin-arm64-v6.5.0.zip
|
||||||
inflating: rdio-scanner
|
inflating: rdio-scanner
|
||||||
inflating: rdio-scanner.pdf
|
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@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/27 08:38:06 server started
|
||||||
2022/06/14 08:38:06 main interface at http://macos.local:3000
|
2022/06/27 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 admin interface at http://macos.local:3000/admin
|
||||||
|
|
||||||
4. Access the administrative dashboard to finalize the configuration.
|
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@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/27 08:48:03 server started
|
||||||
2022/06/14 08:48:03 main interface at http://macos.local
|
2022/06/27 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 admin interface at http://macos.local/admin
|
||||||
|
|
||||||
## Listening on a SSL port
|
## 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_key_file mykey.key \
|
||||||
> -ssl_listen :443
|
> -ssl_listen :443
|
||||||
|
|
||||||
Rdio Scanner v6.4.5
|
Rdio Scanner v6.5.0
|
||||||
----------------------------------
|
----------------------------------
|
||||||
2022/06/14 08:50:58 server started
|
2022/06/27 08:50:58 server started
|
||||||
2022/06/14 08:50:58 main interface at http://macos.local
|
2022/06/27 08:50:58 main interface at http://macos.local
|
||||||
2022/06/14 08:50:58 main interface at https://macos.local
|
2022/06/27 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 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.
|
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_auto_cert mydomain.com \
|
||||||
> -ssl_listen :443 \
|
> -ssl_listen :443 \
|
||||||
> -config_save
|
> -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.
|
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@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/27 08:54:08 server started
|
||||||
2022/06/14 08:54:08 main interface at http://macos.local
|
2022/06/27 08:54:08 main interface at http://macos.local
|
||||||
2022/06/14 08:54:08 main interface at https://macos.local
|
2022/06/27 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 admin interface at https://macos.local/admin
|
||||||
|
|
||||||
## Install Rdio Scanner as a service
|
## 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:~ $ mkdir rdio-scanner
|
||||||
rdio@pc-freebsd:~ $ cd rdio-scanner
|
rdio@pc-freebsd:~ $ cd rdio-scanner
|
||||||
rdio@pc-freebsd:~/rdio-scanner $ unzip \
|
rdio@pc-freebsd:~/rdio-scanner $ unzip \
|
||||||
> ~/rdio-scanner-freebsd-amd64-v6.4.5.zip
|
> ~/rdio-scanner-freebsd-amd64-v6.5.0.zip
|
||||||
Archive: ../rdio-scanner-freebsd-amd64-v6.4.5.zip
|
Archive: ../rdio-scanner-freebsd-amd64-v6.5.0.zip
|
||||||
extracting: rdio-scanner
|
extracting: rdio-scanner
|
||||||
extracting: rdio-scanner.pdf
|
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@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/27 08:16:36 server started
|
||||||
2022/06/14 08:16:36 main interface at http://pc-freebsd:3000
|
2022/06/27 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 admin interface at http://pc-freebsd:3000/admin
|
||||||
|
|
||||||
4. Access the administrative dashboard to finalize the configuration.
|
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:
|
Password:
|
||||||
root@pc-freebsd:/home/rdio/rdio-scanner # ./rdio-scanner -listen :80
|
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/27 08:19:38 server started
|
||||||
2022/06/14 08:19:38 main interface at http://pc-freebsd
|
2022/06/27 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 admin interface at http://pc-freebsd/admin
|
||||||
|
|
||||||
## Listening on a SSL port
|
## 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_key_file mykey.key \
|
||||||
> -ssl_listen :443
|
> -ssl_listen :443
|
||||||
|
|
||||||
Rdio Scanner v6.4.5
|
Rdio Scanner v6.5.0
|
||||||
----------------------------------
|
----------------------------------
|
||||||
2022/06/14 10:34:29 server started
|
2022/06/27 10:34:29 server started
|
||||||
2022/06/14 10:34:29 main interface at http://pc-freebsd
|
2022/06/27 10:34:29 main interface at http://pc-freebsd
|
||||||
2022/06/14 10:34:29 main interface at https://pc-freebsd
|
2022/06/27 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 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.
|
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_auto_cert mydomain.com \
|
||||||
> -ssl_listen :443 \
|
> -ssl_listen :443 \
|
||||||
> -config_save
|
> -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.
|
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:
|
Password:
|
||||||
root@pc-freebsd:/home/rdio/rdio-scanner # ./rdio-scanner
|
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/27 10:38:28 server started
|
||||||
2022/06/14 10:38:29 main interface at http://pc-freebsd
|
2022/06/27 10:38:29 main interface at http://pc-freebsd
|
||||||
2022/06/14 10:38:29 main interface at https://pc-freebsd
|
2022/06/27 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:29 admin interface at https://pc-freebsd/admin
|
||||||
|
|
||||||
## Install Rdio Scanner as a service
|
## 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 ~]$ mkdir rdio-scanner
|
||||||
[rdio@pc-linux ~]$ cd rdio-scanner
|
[rdio@pc-linux ~]$ cd rdio-scanner
|
||||||
[rdio@pc-linux rdio-scanner]$ unzip \
|
[rdio@pc-linux rdio-scanner]$ unzip \
|
||||||
> ~/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.4.5.zip
|
Archive: /home/rdio/Downloads/rdio-scanner-linux-amd64-v6.5.0.zip
|
||||||
inflating: rdio-scanner
|
inflating: rdio-scanner
|
||||||
inflating: rdio-scanner.pdf
|
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@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/27 09:11:48 server started
|
||||||
2022/06/14 09:11:48 main interface at http://pc-linux:3000
|
2022/06/27 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 admin interface at http://pc-linux:3000/admin
|
||||||
|
|
||||||
4. Access the administrative dashboard to finalize the configuration.
|
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
|
[rdio@pc-linux rdio-scanner]$ sudo ./rdio-scanner -listen :80
|
||||||
[sudo] password for rdio:
|
[sudo] password for rdio:
|
||||||
|
|
||||||
Rdio Scanner v6.4.5
|
Rdio Scanner v6.5.0
|
||||||
----------------------------------
|
----------------------------------
|
||||||
2022/06/14 09:14:00 server started
|
2022/06/27 09:14:00 server started
|
||||||
2022/06/14 09:14:00 main interface at http://pc-linux
|
2022/06/27 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 admin interface at http://pc-linux/admin
|
||||||
|
|
||||||
## Listening on a SSL port
|
## 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
|
> -ssl_listen :443
|
||||||
[sudo] password for rdio:
|
[sudo] password for rdio:
|
||||||
|
|
||||||
Rdio Scanner v6.4.5
|
Rdio Scanner v6.5.0
|
||||||
----------------------------------
|
----------------------------------
|
||||||
2022/06/14 09:16:47 server started
|
2022/06/27 09:16:47 server started
|
||||||
2022/06/14 09:16:47 main interface at http://pc-linux
|
2022/06/27 09:16:47 main interface at http://pc-linux
|
||||||
2022/06/14 09:16:47 main interface at https://pc-linux
|
2022/06/27 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 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.
|
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_auto_cert mydomain.com \
|
||||||
> -ssl_listen :443 \
|
> -ssl_listen :443 \
|
||||||
> -config_save
|
> -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.
|
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
|
[rdio@pc-linux rdio-scanner]$ sudo ./rdio-scanner
|
||||||
[sudo] Mot de passe de rdio :
|
[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/27 09:20:40 server started
|
||||||
2022/06/14 09:20:40 main interface at http://pc-linux
|
2022/06/27 09:20:40 main interface at http://pc-linux
|
||||||
2022/06/14 09:20:40 main interface at https://pc-linux
|
2022/06/27 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 admin interface at https://pc-linux/admin
|
||||||
|
|
||||||
## Install Rdio Scanner as a service
|
## 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> cd rdio-scanner
|
||||||
|
|
||||||
C:\Users\rdio\rdio-scanner> tar -xvf ^
|
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.exe
|
||||||
x rdio-scanner.pdf
|
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
|
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/27 13:48:48 server started
|
||||||
2022/06/14 13:48:48 main interface at http://pc-windows:3000
|
2022/06/27 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 admin interface at http://pc-windows:3000/admin
|
||||||
|
|
||||||
4. Access the administrative dashboard to finalize the configuration.
|
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
|
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/27 10:53:31 server started
|
||||||
2022/06/14 10:53:31 main interface at http://pc-windows
|
2022/06/27 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 admin interface at http://pc-windows/admin
|
||||||
|
|
||||||
## Listening on a SSL port
|
## 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_key_file meykey.key ^
|
||||||
-ssl_listen :443
|
-ssl_listen :443
|
||||||
|
|
||||||
Rdio Scanner v6.4.5
|
Rdio Scanner v6.5.0
|
||||||
----------------------------------
|
----------------------------------
|
||||||
2022/06/14 11:05:43 server started
|
2022/06/27 11:05:43 server started
|
||||||
2022/06/14 11:05:43 main interface at http://pc-windows
|
2022/06/27 11:05:43 main interface at http://pc-windows
|
||||||
2022/06/14 11:05:43 main interface at https://pc-windows
|
2022/06/27 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 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.
|
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_auto_cert mydomain.com ^
|
||||||
-ssl_listen :443 ^
|
-ssl_listen :443 ^
|
||||||
-config_save
|
-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.
|
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
|
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/27 11:11:28 server started
|
||||||
2022/06/14 11:11:28 main interface at http://pc-windows
|
2022/06/27 11:11:28 main interface at http://pc-windows
|
||||||
2022/06/14 11:11:28 main interface at https://pc-windows
|
2022/06/27 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 admin interface at https://pc-windows/admin
|
||||||
|
|
||||||
\pagebreak{}
|
\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. |
|
|  | 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. |
|
|  | 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. |
|
|  | 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. |
|
|  | 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. |
|
|  | 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. |
|
|  | Display the systems/talkgroups selection panel where you decide which audio you want to listen to in _LIVE FEED_ mode, not in playback mode. |
|
||||||
|
|
@ -82,12 +82,12 @@ 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:
|
This is much like the group section, but for each system. There is also just two states for each button:
|
||||||
|
|
||||||
| Button | State |
|
| 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 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 inactive every talkgroups from this system. |
|
||||||
|  | Make active every talkgroups from this system. |
|
|  | Make active every talkgroups from this system. |
|
||||||
|
|
||||||
## Search panel
|
## Search panel
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,20 +25,20 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Access struct {
|
type Access struct {
|
||||||
Id interface{} `json:"_id"`
|
Id any `json:"_id"`
|
||||||
Code string `json:"code"`
|
Code string `json:"code"`
|
||||||
Expiration interface{} `json:"expiration"`
|
Expiration any `json:"expiration"`
|
||||||
Ident string `json:"ident"`
|
Ident string `json:"ident"`
|
||||||
Limit interface{} `json:"limit"`
|
Limit any `json:"limit"`
|
||||||
Order interface{} `json:"order"`
|
Order any `json:"order"`
|
||||||
Systems interface{} `json:"systems"`
|
Systems any `json:"systems"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAccess() *Access {
|
func NewAccess() *Access {
|
||||||
return &Access{Systems: "*"}
|
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) {
|
switch v := m["_id"].(type) {
|
||||||
case float64:
|
case float64:
|
||||||
access.Id = uint(v)
|
access.Id = uint(v)
|
||||||
|
|
@ -72,7 +72,7 @@ func (access *Access) FromMap(m map[string]interface{}) *Access {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := m["systems"].(type) {
|
switch v := m["systems"].(type) {
|
||||||
case []interface{}:
|
case []any:
|
||||||
if b, err := json.Marshal(v); err == nil {
|
if b, err := json.Marshal(v); err == nil {
|
||||||
access.Systems = string(b)
|
access.Systems = string(b)
|
||||||
}
|
}
|
||||||
|
|
@ -86,10 +86,10 @@ func (access *Access) FromMap(m map[string]interface{}) *Access {
|
||||||
func (access *Access) HasAccess(call *Call) bool {
|
func (access *Access) HasAccess(call *Call) bool {
|
||||||
if access.Systems != nil {
|
if access.Systems != nil {
|
||||||
switch v := access.Systems.(type) {
|
switch v := access.Systems.(type) {
|
||||||
case []interface{}:
|
case []any:
|
||||||
for _, f := range v {
|
for _, f := range v {
|
||||||
switch v := f.(type) {
|
switch v := f.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
switch id := v["id"].(type) {
|
switch id := v["id"].(type) {
|
||||||
case float64:
|
case float64:
|
||||||
if id == float64(call.System) {
|
if id == float64(call.System) {
|
||||||
|
|
@ -98,7 +98,7 @@ func (access *Access) HasAccess(call *Call) bool {
|
||||||
if tg == "*" {
|
if tg == "*" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case []interface{}:
|
case []any:
|
||||||
for _, f := range tg {
|
for _, f := range tg {
|
||||||
switch tg := f.(type) {
|
switch tg := f.(type) {
|
||||||
case float64:
|
case float64:
|
||||||
|
|
@ -166,7 +166,7 @@ func (accesses *Accesses) Add(access *Access) (*Accesses, bool) {
|
||||||
return accesses, added
|
return accesses, added
|
||||||
}
|
}
|
||||||
|
|
||||||
func (accesses *Accesses) FromMap(f []interface{}) *Accesses {
|
func (accesses *Accesses) FromMap(f []any) *Accesses {
|
||||||
accesses.mutex.Lock()
|
accesses.mutex.Lock()
|
||||||
defer accesses.mutex.Unlock()
|
defer accesses.mutex.Unlock()
|
||||||
|
|
||||||
|
|
@ -174,7 +174,7 @@ func (accesses *Accesses) FromMap(f []interface{}) *Accesses {
|
||||||
|
|
||||||
for _, r := range f {
|
for _, r := range f {
|
||||||
switch m := r.(type) {
|
switch m := r.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
access := &Access{}
|
access := &Access{}
|
||||||
access.FromMap(m)
|
access.FromMap(m)
|
||||||
accesses.List = append(accesses.List, access)
|
accesses.List = append(accesses.List, access)
|
||||||
|
|
@ -207,7 +207,7 @@ func (accesses *Accesses) IsRestricted() bool {
|
||||||
func (accesses *Accesses) Read(db *Database) error {
|
func (accesses *Accesses) Read(db *Database) error {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
expiration interface{}
|
expiration any
|
||||||
id sql.NullFloat64
|
id sql.NullFloat64
|
||||||
limit sql.NullFloat64
|
limit sql.NullFloat64
|
||||||
order 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 {
|
if err = json.Unmarshal([]byte(systems), &access.Systems); err != nil {
|
||||||
access.Systems = []interface{}{}
|
access.Systems = []any{}
|
||||||
}
|
}
|
||||||
|
|
||||||
accesses.List = append(accesses.List, access)
|
accesses.List = append(accesses.List, access)
|
||||||
|
|
@ -298,7 +298,7 @@ func (accesses *Accesses) Write(db *Database) error {
|
||||||
err error
|
err error
|
||||||
rows *sql.Rows
|
rows *sql.Rows
|
||||||
rowIds = []uint{}
|
rowIds = []uint{}
|
||||||
systems interface{}
|
systems any
|
||||||
)
|
)
|
||||||
|
|
||||||
accesses.mutex.Lock()
|
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 (
|
var (
|
||||||
err error
|
err error
|
||||||
hash []byte
|
hash []byte
|
||||||
|
|
@ -158,20 +158,20 @@ func (admin *Admin) ConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
admin.SendConfig(w)
|
admin.SendConfig(w)
|
||||||
|
|
||||||
case http.MethodPut:
|
case http.MethodPut:
|
||||||
m := map[string]interface{}{}
|
m := map[string]any{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&m)
|
err := json.NewDecoder(r.Body).Decode(&m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
admin.Controller.IngestLock()
|
|
||||||
admin.mutex.Lock()
|
admin.mutex.Lock()
|
||||||
|
defer admin.mutex.Unlock()
|
||||||
|
|
||||||
admin.Controller.Dirwatches.Stop()
|
admin.Controller.Dirwatches.Stop()
|
||||||
|
|
||||||
switch v := m["access"].(type) {
|
switch v := m["access"].(type) {
|
||||||
case []interface{}:
|
case []any:
|
||||||
admin.Controller.Accesses.FromMap(v)
|
admin.Controller.Accesses.FromMap(v)
|
||||||
err := admin.Controller.Accesses.Write(admin.Controller.Database)
|
err := admin.Controller.Accesses.Write(admin.Controller.Database)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -185,7 +185,7 @@ func (admin *Admin) ConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := m["apiKeys"].(type) {
|
switch v := m["apiKeys"].(type) {
|
||||||
case []interface{}:
|
case []any:
|
||||||
admin.Controller.Apikeys.FromMap(v)
|
admin.Controller.Apikeys.FromMap(v)
|
||||||
err = admin.Controller.Apikeys.Write(admin.Controller.Database)
|
err = admin.Controller.Apikeys.Write(admin.Controller.Database)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -199,7 +199,7 @@ func (admin *Admin) ConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := m["dirWatch"].(type) {
|
switch v := m["dirWatch"].(type) {
|
||||||
case []interface{}:
|
case []any:
|
||||||
admin.Controller.Dirwatches.FromMap(v)
|
admin.Controller.Dirwatches.FromMap(v)
|
||||||
err = admin.Controller.Dirwatches.Write(admin.Controller.Database)
|
err = admin.Controller.Dirwatches.Write(admin.Controller.Database)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -213,7 +213,7 @@ func (admin *Admin) ConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := m["downstreams"].(type) {
|
switch v := m["downstreams"].(type) {
|
||||||
case []interface{}:
|
case []any:
|
||||||
admin.Controller.Downstreams.FromMap(v)
|
admin.Controller.Downstreams.FromMap(v)
|
||||||
err = admin.Controller.Downstreams.Write(admin.Controller.Database)
|
err = admin.Controller.Downstreams.Write(admin.Controller.Database)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -227,7 +227,7 @@ func (admin *Admin) ConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := m["groups"].(type) {
|
switch v := m["groups"].(type) {
|
||||||
case []interface{}:
|
case []any:
|
||||||
admin.Controller.Groups.FromMap(v)
|
admin.Controller.Groups.FromMap(v)
|
||||||
err = admin.Controller.Groups.Write(admin.Controller.Database)
|
err = admin.Controller.Groups.Write(admin.Controller.Database)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -241,7 +241,7 @@ func (admin *Admin) ConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := m["options"].(type) {
|
switch v := m["options"].(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
admin.Controller.Options.FromMap(v)
|
admin.Controller.Options.FromMap(v)
|
||||||
err = admin.Controller.Options.Write(admin.Controller.Database)
|
err = admin.Controller.Options.Write(admin.Controller.Database)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -250,7 +250,7 @@ func (admin *Admin) ConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := m["systems"].(type) {
|
switch v := m["systems"].(type) {
|
||||||
case []interface{}:
|
case []any:
|
||||||
admin.Controller.Systems.FromMap(v)
|
admin.Controller.Systems.FromMap(v)
|
||||||
err = admin.Controller.Systems.Write(admin.Controller.Database)
|
err = admin.Controller.Systems.Write(admin.Controller.Database)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -264,7 +264,7 @@ func (admin *Admin) ConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := m["tags"].(type) {
|
switch v := m["tags"].(type) {
|
||||||
case []interface{}:
|
case []any:
|
||||||
admin.Controller.Tags.FromMap(v)
|
admin.Controller.Tags.FromMap(v)
|
||||||
err = admin.Controller.Tags.Write(admin.Controller.Database)
|
err = admin.Controller.Tags.Write(admin.Controller.Database)
|
||||||
if err != nil {
|
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.EmitConfig()
|
||||||
admin.Controller.Dirwatches.Start(admin.Controller)
|
admin.Controller.Dirwatches.Start(admin.Controller)
|
||||||
|
|
||||||
|
|
@ -297,10 +294,10 @@ func (admin *Admin) GetAuthorization(r *http.Request) string {
|
||||||
return r.Header.Get("Authorization")
|
return r.Header.Get("Authorization")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (admin *Admin) GetConfig() map[string]interface{} {
|
func (admin *Admin) GetConfig() map[string]any {
|
||||||
systems := []map[string]interface{}{}
|
systems := []map[string]any{}
|
||||||
for _, system := range admin.Controller.Systems.List {
|
for _, system := range admin.Controller.Systems.List {
|
||||||
systems = append(systems, map[string]interface{}{
|
systems = append(systems, map[string]any{
|
||||||
"_id": system.RowId,
|
"_id": system.RowId,
|
||||||
"autoPopulate": system.AutoPopulate,
|
"autoPopulate": system.AutoPopulate,
|
||||||
"blacklists": system.Blacklists,
|
"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,
|
"access": admin.Controller.Accesses.List,
|
||||||
"apiKeys": admin.Controller.Apikeys.List,
|
"apiKeys": admin.Controller.Apikeys.List,
|
||||||
"dirWatch": admin.Controller.Dirwatches.List,
|
"dirWatch": admin.Controller.Dirwatches.List,
|
||||||
|
|
@ -334,7 +331,7 @@ func (admin *Admin) LogsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodPost:
|
case http.MethodPost:
|
||||||
m := map[string]interface{}{}
|
m := map[string]any{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&m)
|
err := json.NewDecoder(r.Body).Decode(&m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
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) {
|
func (admin *Admin) LoginHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
switch r.Method {
|
switch r.Method {
|
||||||
case http.MethodPost:
|
case http.MethodPost:
|
||||||
m := map[string]interface{}{}
|
m := map[string]any{}
|
||||||
|
|
||||||
if err := json.NewDecoder(r.Body).Decode(&m); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&m); err != nil {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
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)
|
admin.Tokens = append(admin.Tokens[1:], sToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := json.Marshal(map[string]interface{}{
|
b, err := json.Marshal(map[string]any{
|
||||||
"passwordNeedChange": true,
|
"passwordNeedChange": true,
|
||||||
"token": sToken,
|
"token": sToken,
|
||||||
})
|
})
|
||||||
|
|
@ -488,7 +485,7 @@ func (admin *Admin) PasswordHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
case http.MethodPost:
|
case http.MethodPost:
|
||||||
var (
|
var (
|
||||||
b []byte
|
b []byte
|
||||||
currentPassword interface{}
|
currentPassword any
|
||||||
newPassword string
|
newPassword string
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -502,7 +499,7 @@ func (admin *Admin) PasswordHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
m := map[string]interface{}{}
|
m := map[string]any{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&m)
|
err := json.NewDecoder(r.Body).Decode(&m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
|
@ -528,7 +525,7 @@ func (admin *Admin) PasswordHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
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)
|
w.Write(b)
|
||||||
} else {
|
} else {
|
||||||
w.WriteHeader(http.StatusExpectationFailed)
|
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) {
|
func (admin *Admin) SendConfig(w http.ResponseWriter) {
|
||||||
var m map[string]interface{}
|
var m map[string]any
|
||||||
_, docker := os.LookupEnv("DOCKER")
|
_, docker := os.LookupEnv("DOCKER")
|
||||||
if docker {
|
if docker {
|
||||||
m = map[string]interface{}{
|
m = map[string]any{
|
||||||
"config": admin.GetConfig(),
|
"config": admin.GetConfig(),
|
||||||
"docker": docker,
|
"docker": docker,
|
||||||
"passwordNeedChange": admin.Controller.Options.adminPasswordNeedChange,
|
"passwordNeedChange": admin.Controller.Options.adminPasswordNeedChange,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
m = map[string]interface{}{
|
m = map[string]any{
|
||||||
"config": admin.GetConfig(),
|
"config": admin.GetConfig(),
|
||||||
"passwordNeedChange": admin.Controller.Options.adminPasswordNeedChange,
|
"passwordNeedChange": admin.Controller.Options.adminPasswordNeedChange,
|
||||||
}
|
}
|
||||||
|
|
@ -611,7 +608,7 @@ func (admin *Admin) UserAddHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
m := map[string]interface{}{}
|
m := map[string]any{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&m)
|
err := json.NewDecoder(r.Body).Decode(&m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
|
@ -651,7 +648,7 @@ func (admin *Admin) UserRemoveHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
m := map[string]interface{}{}
|
m := map[string]any{}
|
||||||
err := json.NewDecoder(r.Body).Decode(&m)
|
err := json.NewDecoder(r.Body).Decode(&m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
w.WriteHeader(http.StatusBadRequest)
|
||||||
|
|
@ -690,7 +687,7 @@ func (admin *Admin) ValidateToken(sToken string) bool {
|
||||||
return false
|
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 {
|
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
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"))
|
mediaType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
api.exitWithError(w, http.StatusBadRequest, "Invalid content-type")
|
||||||
w.Write([]byte("Invalid content-type\n"))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(mediaType, "multipart/") {
|
if !strings.HasPrefix(mediaType, "multipart/") {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
api.exitWithError(w, http.StatusBadRequest, "Not a multipart content")
|
||||||
w.Write([]byte("Not a multipart content\n"))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,12 +58,14 @@ func (api *Api) CallUploadHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
continue
|
api.exitWithError(w, http.StatusExpectationFailed, fmt.Sprintf("multipart: %s\n", err.Error()))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := io.ReadAll(p)
|
b, err := io.ReadAll(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
api.exitWithError(w, http.StatusExpectationFailed, fmt.Sprintf("ioread: %s\n", err.Error()))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch p.FormName() {
|
switch p.FormName() {
|
||||||
|
|
@ -79,8 +79,7 @@ func (api *Api) CallUploadHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
if ok, err := call.IsValid(); ok {
|
if ok, err := call.IsValid(); ok {
|
||||||
api.HandleCall(key, call, w)
|
api.HandleCall(key, call, w)
|
||||||
} else {
|
} else {
|
||||||
w.WriteHeader(http.StatusExpectationFailed)
|
api.exitWithError(w, http.StatusExpectationFailed, fmt.Sprintf("Incomplete call data: %s\n", err.Error()))
|
||||||
w.Write([]byte(fmt.Sprintf("Incomplete call data: %s\n", err.Error())))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
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"))
|
mediaType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
api.exitWithError(w, http.StatusBadRequest, "Invalid content-type")
|
||||||
w.Write([]byte("Invalid content-type\n"))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !strings.HasPrefix(mediaType, "multipart/") {
|
if !strings.HasPrefix(mediaType, "multipart/") {
|
||||||
w.WriteHeader(http.StatusBadRequest)
|
api.exitWithError(w, http.StatusBadRequest, "Not a multipart content")
|
||||||
w.Write([]byte("Not a multipart content\n"))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,12 +138,14 @@ func (api *Api) TrunkRecorderCallUploadHandler(w http.ResponseWriter, r *http.Re
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
break
|
break
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
continue
|
api.exitWithError(w, http.StatusExpectationFailed, fmt.Sprintf("multipart: %s", err.Error()))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
b, err := io.ReadAll(p)
|
b, err := io.ReadAll(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
api.exitWithError(w, http.StatusExpectationFailed, fmt.Sprintf("ioread: %s", err.Error()))
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch p.FormName() {
|
switch p.FormName() {
|
||||||
|
|
@ -154,8 +153,7 @@ func (api *Api) TrunkRecorderCallUploadHandler(w http.ResponseWriter, r *http.Re
|
||||||
key = string(b)
|
key = string(b)
|
||||||
case "meta":
|
case "meta":
|
||||||
if err := ParseTrunkRecorderMeta(call, b); err != nil {
|
if err := ParseTrunkRecorderMeta(call, b); err != nil {
|
||||||
w.WriteHeader(http.StatusExpectationFailed)
|
api.exitWithError(w, http.StatusExpectationFailed, "Invalid call data")
|
||||||
w.Write([]byte("Invalid call data\n"))
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|
@ -171,8 +169,7 @@ func (api *Api) TrunkRecorderCallUploadHandler(w http.ResponseWriter, r *http.Re
|
||||||
api.HandleCall(key, call, w)
|
api.HandleCall(key, call, w)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
w.WriteHeader(http.StatusExpectationFailed)
|
api.exitWithError(w, http.StatusExpectationFailed, fmt.Sprintf("Incomplete call data: %s\n", err.Error()))
|
||||||
w.Write([]byte(fmt.Sprintf("Incomplete call data: %s\n", err.Error())))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|
@ -180,3 +177,10 @@ func (api *Api) TrunkRecorderCallUploadHandler(w http.ResponseWriter, r *http.Re
|
||||||
w.Write([]byte("Unsupported method\n"))
|
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 {
|
type Apikey struct {
|
||||||
Id interface{} `json:"_id"`
|
Id any `json:"_id"`
|
||||||
Disabled bool `json:"disabled"`
|
Disabled bool `json:"disabled"`
|
||||||
Ident string `json:"ident"`
|
Ident string `json:"ident"`
|
||||||
Key string `json:"key"`
|
Key string `json:"key"`
|
||||||
Order interface{} `json:"order"`
|
Order any `json:"order"`
|
||||||
Systems interface{} `json:"systems"`
|
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) {
|
switch v := m["_id"].(type) {
|
||||||
case float64:
|
case float64:
|
||||||
apikey.Id = uint(v)
|
apikey.Id = uint(v)
|
||||||
|
|
@ -61,7 +61,7 @@ func (apikey *Apikey) FromMap(m map[string]interface{}) *Apikey {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := m["systems"].(type) {
|
switch v := m["systems"].(type) {
|
||||||
case []interface{}:
|
case []any:
|
||||||
if b, err := json.Marshal(v); err == nil {
|
if b, err := json.Marshal(v); err == nil {
|
||||||
apikey.Systems = string(b)
|
apikey.Systems = string(b)
|
||||||
}
|
}
|
||||||
|
|
@ -74,10 +74,10 @@ func (apikey *Apikey) FromMap(m map[string]interface{}) *Apikey {
|
||||||
|
|
||||||
func (apikey *Apikey) HasAccess(call *Call) bool {
|
func (apikey *Apikey) HasAccess(call *Call) bool {
|
||||||
switch v := apikey.Systems.(type) {
|
switch v := apikey.Systems.(type) {
|
||||||
case []interface{}:
|
case []any:
|
||||||
for _, f := range v {
|
for _, f := range v {
|
||||||
switch v := f.(type) {
|
switch v := f.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
switch id := v["id"].(type) {
|
switch id := v["id"].(type) {
|
||||||
case float64:
|
case float64:
|
||||||
if id == float64(call.System) {
|
if id == float64(call.System) {
|
||||||
|
|
@ -86,7 +86,7 @@ func (apikey *Apikey) HasAccess(call *Call) bool {
|
||||||
if tg == "*" {
|
if tg == "*" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case []interface{}:
|
case []any:
|
||||||
for _, f := range tg {
|
for _, f := range tg {
|
||||||
switch tg := f.(type) {
|
switch tg := f.(type) {
|
||||||
case float64:
|
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()
|
apikeys.mutex.Lock()
|
||||||
defer apikeys.mutex.Unlock()
|
defer apikeys.mutex.Unlock()
|
||||||
|
|
||||||
|
|
@ -130,7 +130,7 @@ func (apikeys *Apikeys) FromMap(f []interface{}) *Apikeys {
|
||||||
|
|
||||||
for _, r := range f {
|
for _, r := range f {
|
||||||
switch m := r.(type) {
|
switch m := r.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
apikey := &Apikey{}
|
apikey := &Apikey{}
|
||||||
apikey.FromMap(m)
|
apikey.FromMap(m)
|
||||||
apikeys.List = append(apikeys.List, apikey)
|
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 {
|
if err = json.Unmarshal([]byte(systems), &apikey.Systems); err != nil {
|
||||||
apikey.Systems = []interface{}{}
|
apikey.Systems = []any{}
|
||||||
}
|
}
|
||||||
|
|
||||||
apikeys.List = append(apikeys.List, apikey)
|
apikeys.List = append(apikeys.List, apikey)
|
||||||
|
|
@ -219,7 +219,7 @@ func (apikeys *Apikeys) Write(db *Database) error {
|
||||||
err error
|
err error
|
||||||
rows *sql.Rows
|
rows *sql.Rows
|
||||||
rowIds = []uint{}
|
rowIds = []uint{}
|
||||||
systems interface{}
|
systems any
|
||||||
)
|
)
|
||||||
|
|
||||||
apikeys.mutex.Lock()
|
apikeys.mutex.Lock()
|
||||||
|
|
|
||||||
|
|
@ -27,31 +27,31 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Call struct {
|
type Call struct {
|
||||||
Id interface{} `json:"id"`
|
Id any `json:"id"`
|
||||||
Audio []byte `json:"audio"`
|
Audio []byte `json:"audio"`
|
||||||
AudioName interface{} `json:"audioName"`
|
AudioName any `json:"audioName"`
|
||||||
AudioType interface{} `json:"audioType"`
|
AudioType any `json:"audioType"`
|
||||||
DateTime time.Time `json:"dateTime"`
|
DateTime time.Time `json:"dateTime"`
|
||||||
Frequencies interface{} `json:"frequencies"`
|
Frequencies any `json:"frequencies"`
|
||||||
Frequency interface{} `json:"frequency"`
|
Frequency any `json:"frequency"`
|
||||||
Patches interface{} `json:"patches"`
|
Patches any `json:"patches"`
|
||||||
Source interface{} `json:"source"`
|
Source any `json:"source"`
|
||||||
Sources interface{} `json:"sources"`
|
Sources any `json:"sources"`
|
||||||
System uint `json:"system"`
|
System uint `json:"system"`
|
||||||
Talkgroup uint `json:"talkgroup"`
|
Talkgroup uint `json:"talkgroup"`
|
||||||
systemLabel interface{}
|
systemLabel any
|
||||||
talkgroupGroup interface{}
|
talkgroupGroup any
|
||||||
talkgroupLabel interface{}
|
talkgroupLabel any
|
||||||
talkgroupName interface{}
|
talkgroupName any
|
||||||
talkgroupTag interface{}
|
talkgroupTag any
|
||||||
units interface{}
|
units any
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCall() *Call {
|
func NewCall() *Call {
|
||||||
return &Call{
|
return &Call{
|
||||||
Frequencies: []map[string]interface{}{},
|
Frequencies: []map[string]any{},
|
||||||
Patches: []uint{},
|
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 := fmt.Sprintf("%v", call.Audio)
|
||||||
audio = strings.ReplaceAll(audio, " ", ",")
|
audio = strings.ReplaceAll(audio, " ", ",")
|
||||||
|
|
||||||
return json.Marshal(map[string]interface{}{
|
return json.Marshal(map[string]any{
|
||||||
"id": call.Id,
|
"id": call.Id,
|
||||||
"audio": map[string]interface{}{
|
"audio": map[string]any{
|
||||||
"data": json.RawMessage(audio),
|
"data": json.RawMessage(audio),
|
||||||
"type": "Buffer",
|
"type": "Buffer",
|
||||||
},
|
},
|
||||||
|
|
@ -144,7 +144,7 @@ func (calls *Calls) GetCall(id uint, db *Database) (*Call, error) {
|
||||||
var (
|
var (
|
||||||
audioName sql.NullString
|
audioName sql.NullString
|
||||||
audioType sql.NullString
|
audioType sql.NullString
|
||||||
dateTime interface{}
|
dateTime any
|
||||||
frequency sql.NullFloat64
|
frequency sql.NullFloat64
|
||||||
source sql.NullFloat64
|
source sql.NullFloat64
|
||||||
frequencies string
|
frequencies string
|
||||||
|
|
@ -184,13 +184,13 @@ func (calls *Calls) GetCall(id uint, db *Database) (*Call, error) {
|
||||||
|
|
||||||
if len(frequencies) > 0 {
|
if len(frequencies) > 0 {
|
||||||
if err = json.Unmarshal([]byte(frequencies), &call.Frequencies); err != nil {
|
if err = json.Unmarshal([]byte(frequencies), &call.Frequencies); err != nil {
|
||||||
call.Frequencies = []interface{}{}
|
call.Frequencies = []any{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(patches) > 0 {
|
if len(patches) > 0 {
|
||||||
if err = json.Unmarshal([]byte(patches), &call.Patches); err != nil {
|
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 len(sources) > 0 {
|
||||||
if err = json.Unmarshal([]byte(sources), &call.Sources); err != nil {
|
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 (
|
var (
|
||||||
dateTime interface{}
|
dateTime any
|
||||||
err error
|
err error
|
||||||
id sql.NullFloat64
|
id sql.NullFloat64
|
||||||
limit uint
|
limit uint
|
||||||
|
|
@ -252,14 +252,14 @@ func (calls *Calls) Search(searchOptions *CallsSearchOptions, client *Client) (*
|
||||||
|
|
||||||
if client.Access != nil {
|
if client.Access != nil {
|
||||||
switch v := client.Access.Systems.(type) {
|
switch v := client.Access.Systems.(type) {
|
||||||
case []interface{}:
|
case []any:
|
||||||
a := []string{}
|
a := []string{}
|
||||||
for _, scope := range v {
|
for _, scope := range v {
|
||||||
var c string
|
var c string
|
||||||
switch v := scope.(type) {
|
switch v := scope.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
switch v["talkgroups"].(type) {
|
switch v["talkgroups"].(type) {
|
||||||
case []interface{}:
|
case []any:
|
||||||
b := strings.ReplaceAll(fmt.Sprintf("%v", v["talkgroups"]), " ", ", ")
|
b := strings.ReplaceAll(fmt.Sprintf("%v", v["talkgroups"]), " ", ", ")
|
||||||
b = strings.ReplaceAll(b, "[", "(")
|
b = strings.ReplaceAll(b, "[", "(")
|
||||||
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) {
|
switch v := call.Frequencies.(type) {
|
||||||
case []map[string]interface{}:
|
case []map[string]any:
|
||||||
if b, err = json.Marshal(v); err == nil {
|
if b, err = json.Marshal(v); err == nil {
|
||||||
frequencies = string(b)
|
frequencies = string(b)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -459,7 +459,7 @@ func (calls *Calls) WriteCall(call *Call, db *Database) (uint, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := call.Sources.(type) {
|
switch v := call.Sources.(type) {
|
||||||
case []map[string]interface{}:
|
case []map[string]any:
|
||||||
if b, err = json.Marshal(v); err == nil {
|
if b, err = json.Marshal(v); err == nil {
|
||||||
sources = string(b)
|
sources = string(b)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -479,18 +479,18 @@ func (calls *Calls) WriteCall(call *Call, db *Database) (uint, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type CallsSearchOptions struct {
|
type CallsSearchOptions struct {
|
||||||
Date interface{} `json:"date,omitempty"`
|
Date any `json:"date,omitempty"`
|
||||||
Group interface{} `json:"group,omitempty"`
|
Group any `json:"group,omitempty"`
|
||||||
Limit interface{} `json:"limit,omitempty"`
|
Limit any `json:"limit,omitempty"`
|
||||||
Offset interface{} `json:"offset,omitempty"`
|
Offset any `json:"offset,omitempty"`
|
||||||
Sort interface{} `json:"sort,omitempty"`
|
Sort any `json:"sort,omitempty"`
|
||||||
System interface{} `json:"system,omitempty"`
|
System any `json:"system,omitempty"`
|
||||||
Tag interface{} `json:"tag,omitempty"`
|
Tag any `json:"tag,omitempty"`
|
||||||
Talkgroup interface{} `json:"talkgroup,omitempty"`
|
Talkgroup any `json:"talkgroup,omitempty"`
|
||||||
searchPatchedTalkgroups bool
|
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) {
|
switch v := m["date"].(type) {
|
||||||
case string:
|
case string:
|
||||||
if t, err := time.Parse(time.RFC3339, v); err == nil {
|
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.Controller = controller
|
||||||
client.Conn = conn
|
client.Conn = conn
|
||||||
client.Livefeed = NewLivefeed()
|
client.Livefeed = NewLivefeed()
|
||||||
client.Send = make(chan *Message)
|
client.Send = make(chan *Message, 1024)
|
||||||
client.request = request
|
client.request = request
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
defer func() {
|
defer func() {
|
||||||
recover()
|
|
||||||
|
|
||||||
controller.Unregister <- client
|
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))
|
client.Conn.SetReadDeadline(time.Now().Add(pongWait))
|
||||||
|
|
@ -106,12 +111,9 @@ func (client *Client) Init(controller *Controller, request *http.Request, conn *
|
||||||
})
|
})
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
recover()
|
|
||||||
|
|
||||||
ticker.Stop()
|
ticker.Stop()
|
||||||
timer.Stop()
|
timer.Stop()
|
||||||
|
|
||||||
client.Conn.WriteMessage(websocket.CloseMessage, []byte{})
|
|
||||||
client.Conn.Close()
|
client.Conn.Close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -124,6 +126,16 @@ func (client *Client) Init(controller *Controller, request *http.Request, conn *
|
||||||
|
|
||||||
if message.Command == MessageCommandConfig {
|
if message.Command == MessageCommandConfig {
|
||||||
timer.Stop()
|
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()
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,16 +168,14 @@ func (client *Client) GetRemoteAddr() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *Client) SendConfig(groups *Groups, options *Options, systems *Systems, tags *Tags) {
|
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.SystemsMap = systems.GetScopedSystems(client, groups, tags, options.SortTalkgroups)
|
||||||
client.GroupsMap = groups.GetGroupsMap(&client.SystemsMap)
|
client.GroupsMap = groups.GetGroupsMap(&client.SystemsMap)
|
||||||
client.TagsMap = tags.GetTagsMap(&client.SystemsMap)
|
client.TagsMap = tags.GetTagsMap(&client.SystemsMap)
|
||||||
|
|
||||||
var payload = map[string]interface{}{
|
var payload = map[string]any{
|
||||||
|
"branding": options.Branding,
|
||||||
"dimmerDelay": options.DimmerDelay,
|
"dimmerDelay": options.DimmerDelay,
|
||||||
|
"email": options.Email,
|
||||||
"groups": client.GroupsMap,
|
"groups": client.GroupsMap,
|
||||||
"keypadBeeps": GetKeypadBeeps(options),
|
"keypadBeeps": GetKeypadBeeps(options),
|
||||||
"playbackGoesLive": options.PlaybackGoesLive,
|
"playbackGoesLive": options.PlaybackGoesLive,
|
||||||
|
|
@ -188,10 +194,6 @@ func (client *Client) SendConfig(groups *Groups, options *Options, systems *Syst
|
||||||
}
|
}
|
||||||
|
|
||||||
func (client *Client) SendListenersCount(count int) {
|
func (client *Client) SendListenersCount(count int) {
|
||||||
defer func() {
|
|
||||||
recover()
|
|
||||||
}()
|
|
||||||
|
|
||||||
client.Send <- &Message{
|
client.Send <- &Message{
|
||||||
Command: MessagecommandListenersCount,
|
Command: MessagecommandListenersCount,
|
||||||
Payload: count,
|
Payload: count,
|
||||||
|
|
@ -209,7 +211,7 @@ func NewClients() *Clients {
|
||||||
func (clients *Clients) AccessCount(client *Client) int {
|
func (clients *Clients) AccessCount(client *Client) int {
|
||||||
count := 0
|
count := 0
|
||||||
|
|
||||||
clients.Map.Range(func(k interface{}, _ interface{}) bool {
|
clients.Map.Range(func(k any, _ any) bool {
|
||||||
switch c := k.(type) {
|
switch c := k.(type) {
|
||||||
case *Client:
|
case *Client:
|
||||||
if c.Access == client.Access {
|
if c.Access == client.Access {
|
||||||
|
|
@ -229,7 +231,7 @@ func (clients *Clients) Add(client *Client) {
|
||||||
func (clients *Clients) Count() int {
|
func (clients *Clients) Count() int {
|
||||||
count := 0
|
count := 0
|
||||||
|
|
||||||
clients.Map.Range(func(k, v interface{}) bool {
|
clients.Map.Range(func(k, v any) bool {
|
||||||
count++
|
count++
|
||||||
|
|
||||||
return true
|
return true
|
||||||
|
|
@ -239,11 +241,7 @@ func (clients *Clients) Count() int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (clients *Clients) EmitCall(call *Call, restricted bool) {
|
func (clients *Clients) EmitCall(call *Call, restricted bool) {
|
||||||
defer func() {
|
clients.Map.Range(func(k any, _ any) bool {
|
||||||
recover()
|
|
||||||
}()
|
|
||||||
|
|
||||||
clients.Map.Range(func(k interface{}, _ interface{}) bool {
|
|
||||||
switch c := k.(type) {
|
switch c := k.(type) {
|
||||||
case *Client:
|
case *Client:
|
||||||
if (!restricted || c.Access.HasAccess(call)) && c.Livefeed.IsEnabled(call) {
|
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) {
|
func (clients *Clients) EmitConfig(groups *Groups, options *Options, systems *Systems, tags *Tags, restricted bool) {
|
||||||
defer func() {
|
|
||||||
recover()
|
|
||||||
}()
|
|
||||||
|
|
||||||
count := clients.Count()
|
count := clients.Count()
|
||||||
|
|
||||||
clients.Map.Range(func(k interface{}, _ interface{}) bool {
|
clients.Map.Range(func(k any, _ any) bool {
|
||||||
switch c := k.(type) {
|
switch c := k.(type) {
|
||||||
case *Client:
|
case *Client:
|
||||||
if restricted {
|
if restricted {
|
||||||
|
|
@ -283,7 +277,7 @@ func (clients *Clients) EmitConfig(groups *Groups, options *Options, systems *Sy
|
||||||
func (clients *Clients) EmitListenersCount() {
|
func (clients *Clients) EmitListenersCount() {
|
||||||
count := clients.Count()
|
count := clients.Count()
|
||||||
|
|
||||||
clients.Map.Range(func(k interface{}, _ interface{}) bool {
|
clients.Map.Range(func(k any, _ any) bool {
|
||||||
switch c := k.(type) {
|
switch c := k.(type) {
|
||||||
case *Client:
|
case *Client:
|
||||||
c.SendListenersCount(count)
|
c.SendListenersCount(count)
|
||||||
|
|
@ -294,11 +288,5 @@ func (clients *Clients) EmitListenersCount() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (clients *Clients) Remove(client *Client) {
|
func (clients *Clients) Remove(client *Client) {
|
||||||
defer func() {
|
|
||||||
recover()
|
|
||||||
}()
|
|
||||||
|
|
||||||
clients.Map.Delete(client)
|
clients.Map.Delete(client)
|
||||||
|
|
||||||
close(client.Send)
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
@ -81,10 +81,10 @@ func NewCommand(baseDir string) *Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Command{
|
return &Command{
|
||||||
app: path.Base(app),
|
app: filepath.Base(app),
|
||||||
command: COMMAND_HELP,
|
command: COMMAND_HELP,
|
||||||
password: pass,
|
password: pass,
|
||||||
tokenFile: baseDir + path.Base(app) + ".token",
|
tokenFile: baseDir + filepath.Base(app) + ".token",
|
||||||
url: COMMAND_DEF_URL,
|
url: COMMAND_DEF_URL,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -224,7 +224,7 @@ func (command *Command) adminPassword() {
|
||||||
command.exitWithError(fmt.Sprintf("Missing %s <password> arguments.", COMMAND_ARG_PASSWORD))
|
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, err := command.submit(http.MethodPost, "/api/admin/password", body, true); err == nil {
|
||||||
if res.StatusCode == http.StatusOK {
|
if res.StatusCode == http.StatusOK {
|
||||||
fmt.Println("New admin password applied")
|
fmt.Println("New admin password applied")
|
||||||
|
|
@ -246,9 +246,9 @@ func (command *Command) configGet() {
|
||||||
if res.StatusCode == http.StatusOK {
|
if res.StatusCode == http.StatusOK {
|
||||||
if data, err := command.readBody(res.Body); err == nil {
|
if data, err := command.readBody(res.Body); err == nil {
|
||||||
switch v := data.(type) {
|
switch v := data.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
switch v := v["config"].(type) {
|
switch v := v["config"].(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
if f, err := os.Create(command.out); err == nil {
|
if f, err := os.Create(command.out); err == nil {
|
||||||
j := json.NewEncoder(f)
|
j := json.NewEncoder(f)
|
||||||
j.SetIndent("", " ")
|
j.SetIndent("", " ")
|
||||||
|
|
@ -276,10 +276,10 @@ func (command *Command) configSet() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if f, err := os.Open(command.in); err == nil {
|
if f, err := os.Open(command.in); err == nil {
|
||||||
var d interface{}
|
var d any
|
||||||
json.NewDecoder(f).Decode(&d)
|
json.NewDecoder(f).Decode(&d)
|
||||||
switch v := d.(type) {
|
switch v := d.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
if body, err := command.writeBody(v); err == nil {
|
if body, err := command.writeBody(v); err == nil {
|
||||||
if res, err := command.submit(http.MethodPut, "/api/admin/config", body, true); err == nil {
|
if res, err := command.submit(http.MethodPut, "/api/admin/config", body, true); err == nil {
|
||||||
if res.StatusCode == http.StatusOK {
|
if res.StatusCode == http.StatusOK {
|
||||||
|
|
@ -300,12 +300,12 @@ func (command *Command) configSet() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (command *Command) login() {
|
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, err := command.submit(http.MethodPost, "/api/admin/login", body, false); err == nil {
|
||||||
if res.StatusCode == http.StatusOK {
|
if res.StatusCode == http.StatusOK {
|
||||||
if data, err := command.readBody(res.Body); err == nil {
|
if data, err := command.readBody(res.Body); err == nil {
|
||||||
switch v := data.(type) {
|
switch v := data.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
switch v := v["token"].(type) {
|
switch v := v["token"].(type) {
|
||||||
case string:
|
case string:
|
||||||
command.token = v
|
command.token = v
|
||||||
|
|
@ -360,7 +360,7 @@ func (command *Command) userAdd() {
|
||||||
command.exitWithError(fmt.Sprintf("Missing %s <code> arguments.", COMMAND_ARG_CODE))
|
command.exitWithError(fmt.Sprintf("Missing %s <code> arguments.", COMMAND_ARG_CODE))
|
||||||
}
|
}
|
||||||
|
|
||||||
u := map[string]interface{}{
|
u := map[string]any{
|
||||||
"code": command.code,
|
"code": command.code,
|
||||||
"ident": command.ident,
|
"ident": command.ident,
|
||||||
"systems": "*",
|
"systems": "*",
|
||||||
|
|
@ -392,9 +392,9 @@ func (command *Command) userAdd() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(s) > 0 {
|
if len(s) > 0 {
|
||||||
systems := []map[string]interface{}{}
|
systems := []map[string]any{}
|
||||||
for _, i := range s {
|
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
|
u["systems"] = systems
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -419,7 +419,7 @@ func (command *Command) userRemove() {
|
||||||
command.exitWithError(fmt.Sprintf("Missing %s <ident> arguments.", COMMAND_ARG_IDENT))
|
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, err := command.submit(http.MethodPost, "/api/admin/user-remove", body, true); err == nil {
|
||||||
if res.StatusCode == http.StatusOK {
|
if res.StatusCode == http.StatusOK {
|
||||||
fmt.Printf("User %s removed.\n", command.ident)
|
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)
|
err = json.NewDecoder(body).Decode(&data)
|
||||||
return data, err
|
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
|
var b []byte
|
||||||
if b, err = json.Marshal(data); err == nil {
|
if b, err = json.Marshal(data); err == nil {
|
||||||
body = bytes.NewReader(b)
|
body = bytes.NewReader(b)
|
||||||
|
|
@ -464,7 +464,7 @@ func (c *Command) submit(method string, url string, body io.Reader, auth bool) (
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Command) exitWithError(err interface{}) {
|
func (c *Command) exitWithError(err any) {
|
||||||
fmt.Printf("%v\n", err)
|
fmt.Printf("%v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,10 +67,9 @@ func NewController(config *Config) *Controller {
|
||||||
Systems: NewSystems(),
|
Systems: NewSystems(),
|
||||||
Tags: NewTags(),
|
Tags: NewTags(),
|
||||||
Clients: NewClients(),
|
Clients: NewClients(),
|
||||||
Register: make(chan *Client),
|
Register: make(chan *Client, 4096),
|
||||||
Unregister: make(chan *Client),
|
Unregister: make(chan *Client, 4096),
|
||||||
Ingest: make(chan *Call),
|
Ingest: make(chan *Call, 4096),
|
||||||
ingestMutex: sync.Mutex{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
controller.Admin = NewAdmin(controller)
|
controller.Admin = NewAdmin(controller)
|
||||||
|
|
@ -110,9 +109,6 @@ func (controller *Controller) IngestCall(call *Call) {
|
||||||
talkgroup *Talkgroup
|
talkgroup *Talkgroup
|
||||||
)
|
)
|
||||||
|
|
||||||
controller.IngestLock()
|
|
||||||
defer controller.IngestUnlock()
|
|
||||||
|
|
||||||
logCall := func(call *Call, level string, message string) {
|
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))
|
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() {
|
func (controller *Controller) LogClientsCount() {
|
||||||
controller.Logs.LogEvent(LogLevelInfo, fmt.Sprintf("listeners count is %v", controller.Clients.Count()))
|
controller.Logs.LogEvent(LogLevelInfo, fmt.Sprintf("listeners count is %v", controller.Clients.Count()))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (controller *Controller) ProcessMessage(client *Client, message *Message) error {
|
func (controller *Controller) ProcessMessage(client *Client, message *Message) error {
|
||||||
if message.Command == MessageCommandVersion {
|
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 {
|
} else if controller.Accesses.IsRestricted() && client.Access.Systems == nil && message.Command != MessageCommandPin {
|
||||||
client.Send <- &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 {
|
func (controller *Controller) ProcessMessageCommandListCall(client *Client, message *Message) error {
|
||||||
switch v := message.Payload.(type) {
|
switch v := message.Payload.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
searchOptions := CallsSearchOptions{searchPatchedTalkgroups: controller.Options.SearchPatchedTalkgroups}
|
searchOptions := CallsSearchOptions{searchPatchedTalkgroups: controller.Options.SearchPatchedTalkgroups}
|
||||||
searchOptions.fromMap(v)
|
searchOptions.fromMap(v)
|
||||||
if searchResults, err := controller.Calls.Search(&searchOptions, client); err == nil {
|
if searchResults, err := controller.Calls.Search(&searchOptions, client); err == nil {
|
||||||
|
|
@ -467,6 +455,20 @@ func (controller *Controller) ProcessMessageCommandPin(client *Client, message *
|
||||||
return nil
|
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 {
|
func (controller *Controller) Start() error {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -76,7 +76,7 @@ func NewDatabase(config *Config) *Database {
|
||||||
return 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) {
|
switch v := f.(type) {
|
||||||
case []uint8:
|
case []uint8:
|
||||||
return time.Parse(db.DateTimeFormat, string(v))
|
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 {
|
func (db *Database) migration20220101070000(verbose bool) error {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
frequency interface{}
|
frequency any
|
||||||
id uint
|
id uint
|
||||||
label string
|
label string
|
||||||
led interface{}
|
led any
|
||||||
name string
|
name string
|
||||||
queries []string
|
queries []string
|
||||||
rows *sql.Rows
|
rows *sql.Rows
|
||||||
|
|
|
||||||
|
|
@ -44,25 +44,25 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Dirwatch struct {
|
type Dirwatch struct {
|
||||||
Id interface{} `json:"_id"`
|
Id any `json:"_id"`
|
||||||
Delay interface{} `json:"delay"`
|
Delay any `json:"delay"`
|
||||||
DeleteAfter bool `json:"deleteAfter"`
|
DeleteAfter bool `json:"deleteAfter"`
|
||||||
Directory string `json:"directory"`
|
Directory string `json:"directory"`
|
||||||
Disabled bool `json:"disabled"`
|
Disabled bool `json:"disabled"`
|
||||||
Extension interface{} `json:"extension"`
|
Extension any `json:"extension"`
|
||||||
Frequency interface{} `json:"frequency"`
|
Frequency any `json:"frequency"`
|
||||||
Mask interface{} `json:"mask"`
|
Mask any `json:"mask"`
|
||||||
Order interface{} `json:"order"`
|
Order any `json:"order"`
|
||||||
SystemId interface{} `json:"systemId"`
|
SystemId any `json:"systemId"`
|
||||||
TalkgroupId interface{} `json:"talkgroupId"`
|
TalkgroupId any `json:"talkgroupId"`
|
||||||
Kind interface{} `json:"type"`
|
Kind any `json:"type"`
|
||||||
UsePolling bool `json:"usePolling"`
|
UsePolling bool `json:"usePolling"`
|
||||||
controller *Controller
|
controller *Controller
|
||||||
dirs map[string]bool
|
dirs map[string]bool
|
||||||
watcher *fsnotify.Watcher
|
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) {
|
switch v := m["_id"].(type) {
|
||||||
case float64:
|
case float64:
|
||||||
dirwatch.Id = uint(v)
|
dirwatch.Id = uint(v)
|
||||||
|
|
@ -164,7 +164,7 @@ func (dirwatch *Dirwatch) ingestDefault(p string) error {
|
||||||
if strings.EqualFold(path.Ext(p), ext) {
|
if strings.EqualFold(path.Ext(p), ext) {
|
||||||
call := NewCall()
|
call := NewCall()
|
||||||
|
|
||||||
call.AudioName = path.Base(p)
|
call.AudioName = filepath.Base(p)
|
||||||
call.AudioType = mime.TypeByExtension(path.Ext(p))
|
call.AudioType = mime.TypeByExtension(path.Ext(p))
|
||||||
call.Frequency = dirwatch.Frequency
|
call.Frequency = dirwatch.Frequency
|
||||||
|
|
||||||
|
|
@ -224,7 +224,7 @@ func (dirwatch *Dirwatch) ingestSdrTrunk(p string) error {
|
||||||
|
|
||||||
call := NewCall()
|
call := NewCall()
|
||||||
|
|
||||||
call.AudioName = path.Base(p)
|
call.AudioName = filepath.Base(p)
|
||||||
call.AudioType = mime.TypeByExtension(path.Ext(p))
|
call.AudioType = mime.TypeByExtension(path.Ext(p))
|
||||||
call.Frequency = dirwatch.Frequency
|
call.Frequency = dirwatch.Frequency
|
||||||
|
|
||||||
|
|
@ -280,7 +280,7 @@ func (dirwatch *Dirwatch) ingestTrunkRecorder(p string) error {
|
||||||
|
|
||||||
call := NewCall()
|
call := NewCall()
|
||||||
|
|
||||||
call.AudioName = path.Base(audioName)
|
call.AudioName = filepath.Base(audioName)
|
||||||
call.AudioType = mime.TypeByExtension(path.Ext(audioName))
|
call.AudioType = mime.TypeByExtension(path.Ext(audioName))
|
||||||
call.Frequency = dirwatch.Frequency
|
call.Frequency = dirwatch.Frequency
|
||||||
|
|
||||||
|
|
@ -344,8 +344,8 @@ func (dirwatch *Dirwatch) parseMask(call *Call) {
|
||||||
var (
|
var (
|
||||||
filename string
|
filename string
|
||||||
mask string
|
mask string
|
||||||
metapos = [][]interface{}{}
|
metapos = [][]any{}
|
||||||
metaval = map[string]interface{}{}
|
metaval = map[string]any{}
|
||||||
)
|
)
|
||||||
|
|
||||||
switch v := dirwatch.Mask.(type) {
|
switch v := dirwatch.Mask.(type) {
|
||||||
|
|
@ -364,7 +364,7 @@ func (dirwatch *Dirwatch) parseMask(call *Call) {
|
||||||
|
|
||||||
for _, v := range meta {
|
for _, v := range meta {
|
||||||
if i := strings.Index(mask, v[1]); i != -1 {
|
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)
|
mask = strings.Replace(mask, v[1], fmt.Sprintf("(%v)", v[2]), 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -513,8 +513,8 @@ func (dirwatch *Dirwatch) parseMask(call *Call) {
|
||||||
case string:
|
case string:
|
||||||
if i, err := strconv.Atoi(v); err == nil {
|
if i, err := strconv.Atoi(v); err == nil {
|
||||||
switch sources := call.Sources.(type) {
|
switch sources := call.Sources.(type) {
|
||||||
case []map[string]interface{}:
|
case []map[string]any:
|
||||||
call.Sources = append(sources, map[string]interface{}{"pos": 0, "src": uint(i)})
|
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() {
|
go func() {
|
||||||
// var timers = map[string]*time.Timer{}
|
|
||||||
var timers = sync.Map{}
|
var timers = sync.Map{}
|
||||||
|
|
||||||
logError := func(err error) {
|
logError := func(err error) {
|
||||||
|
|
@ -567,7 +566,7 @@ func (dirwatch *Dirwatch) Start(controller *Controller) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
timers.Range(func(k interface{}, t interface{}) bool {
|
timers.Range(func(k any, t any) bool {
|
||||||
switch v := t.(type) {
|
switch v := t.(type) {
|
||||||
case *time.Timer:
|
case *time.Timer:
|
||||||
v.Stop()
|
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()
|
dirwatches.mutex.Lock()
|
||||||
defer dirwatches.mutex.Unlock()
|
defer dirwatches.mutex.Unlock()
|
||||||
|
|
||||||
|
|
@ -698,7 +697,7 @@ func (dirwatches *Dirwatches) FromMap(f []interface{}) *Dirwatches {
|
||||||
|
|
||||||
for _, f := range f {
|
for _, f := range f {
|
||||||
switch v := f.(type) {
|
switch v := f.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
dirwatch := &Dirwatch{}
|
dirwatch := &Dirwatch{}
|
||||||
dirwatch.FromMap(v)
|
dirwatch.FromMap(v)
|
||||||
dirwatches.List = append(dirwatches.List, dirwatch)
|
dirwatches.List = append(dirwatches.List, dirwatch)
|
||||||
|
|
|
||||||
|
|
@ -32,15 +32,15 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Downstream struct {
|
type Downstream struct {
|
||||||
Id interface{} `json:"_id"`
|
Id any `json:"_id"`
|
||||||
Apikey string `json:"apiKey"`
|
Apikey string `json:"apiKey"`
|
||||||
Disabled bool `json:"disabled"`
|
Disabled bool `json:"disabled"`
|
||||||
Order interface{} `json:"order"`
|
Order any `json:"order"`
|
||||||
Systems interface{} `json:"systems"`
|
Systems any `json:"systems"`
|
||||||
Url string `json:"url"`
|
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) {
|
switch v := m["_id"].(type) {
|
||||||
case float64:
|
case float64:
|
||||||
downstream.Id = uint(v)
|
downstream.Id = uint(v)
|
||||||
|
|
@ -62,7 +62,7 @@ func (downstream *Downstream) FromMap(m map[string]interface{}) *Downstream {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := m["systems"].(type) {
|
switch v := m["systems"].(type) {
|
||||||
case []interface{}:
|
case []any:
|
||||||
if b, err := json.Marshal(v); err == nil {
|
if b, err := json.Marshal(v); err == nil {
|
||||||
downstream.Systems = string(b)
|
downstream.Systems = string(b)
|
||||||
}
|
}
|
||||||
|
|
@ -84,10 +84,10 @@ func (downstream *Downstream) HasAccess(call *Call) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := downstream.Systems.(type) {
|
switch v := downstream.Systems.(type) {
|
||||||
case []interface{}:
|
case []any:
|
||||||
for _, f := range v {
|
for _, f := range v {
|
||||||
switch v := f.(type) {
|
switch v := f.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
switch id := v["id"].(type) {
|
switch id := v["id"].(type) {
|
||||||
case float64:
|
case float64:
|
||||||
if id == float64(call.System) {
|
if id == float64(call.System) {
|
||||||
|
|
@ -96,7 +96,7 @@ func (downstream *Downstream) HasAccess(call *Call) bool {
|
||||||
if tg == "*" {
|
if tg == "*" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
case []interface{}:
|
case []any:
|
||||||
for _, f := range tg {
|
for _, f := range tg {
|
||||||
switch tg := f.(type) {
|
switch tg := f.(type) {
|
||||||
case float64:
|
case float64:
|
||||||
|
|
@ -181,7 +181,7 @@ func (downstream *Downstream) Send(call *Call) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := call.Frequencies.(type) {
|
switch v := call.Frequencies.(type) {
|
||||||
case []map[string]interface{}:
|
case []map[string]any:
|
||||||
if w, err := mw.CreateFormField("frequencies"); err == nil {
|
if w, err := mw.CreateFormField("frequencies"); err == nil {
|
||||||
if b, err := json.Marshal(v); err == nil {
|
if b, err := json.Marshal(v); err == nil {
|
||||||
if _, err = w.Write(b); 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) {
|
switch v := call.Sources.(type) {
|
||||||
case []map[string]interface{}:
|
case []map[string]any:
|
||||||
if w, err := mw.CreateFormField("sources"); err == nil {
|
if w, err := mw.CreateFormField("sources"); err == nil {
|
||||||
if b, err := json.Marshal(v); err == nil {
|
if b, err := json.Marshal(v); err == nil {
|
||||||
if _, err = w.Write(b); 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()
|
downstreams.mutex.Lock()
|
||||||
defer downstreams.mutex.Unlock()
|
defer downstreams.mutex.Unlock()
|
||||||
|
|
||||||
|
|
@ -371,7 +371,7 @@ func (downstreams *Downstreams) FromMap(f []interface{}) *Downstreams {
|
||||||
|
|
||||||
for _, r := range f {
|
for _, r := range f {
|
||||||
switch m := r.(type) {
|
switch m := r.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
downstream := &Downstream{}
|
downstream := &Downstream{}
|
||||||
downstream.FromMap(m)
|
downstream.FromMap(m)
|
||||||
downstreams.List = append(downstreams.List, downstream)
|
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 {
|
if err = json.Unmarshal([]byte(systems), &downstream.Systems); err != nil {
|
||||||
downstream.Systems = []interface{}{}
|
downstream.Systems = []any{}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(downstream.Url) == 0 {
|
if len(downstream.Url) == 0 {
|
||||||
|
|
@ -464,7 +464,7 @@ func (downstreams *Downstreams) Write(db *Database) error {
|
||||||
err error
|
err error
|
||||||
rows *sql.Rows
|
rows *sql.Rows
|
||||||
rowIds = []uint{}
|
rowIds = []uint{}
|
||||||
systems interface{}
|
systems any
|
||||||
)
|
)
|
||||||
|
|
||||||
downstreams.mutex.Lock()
|
downstreams.mutex.Lock()
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
module rdio-scanner/server
|
module rdio-scanner/server
|
||||||
|
|
||||||
go 1.17
|
go 1.18
|
||||||
|
|
||||||
require (
|
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/fsnotify/fsnotify v1.5.4
|
||||||
github.com/go-sql-driver/mysql v1.6.0
|
github.com/go-sql-driver/mysql v1.6.0
|
||||||
github.com/golang-jwt/jwt/v4 v4.4.1
|
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/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect
|
||||||
github.com/stretchr/testify v1.7.0 // indirect
|
github.com/stretchr/testify v1.7.0 // indirect
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // 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/net v0.0.0-20220617184016-355a448f1bc9 // indirect
|
||||||
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d // indirect
|
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect
|
||||||
golang.org/x/text v0.3.7 // indirect
|
golang.org/x/text v0.3.7 // indirect
|
||||||
golang.org/x/tools v0.1.11 // indirect
|
golang.org/x/tools v0.1.11 // indirect
|
||||||
lukechampine.com/uint128 v1.2.0 // 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/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 h1:jD1A7flCBuwFb5PvK3B2u/jmUUZ2j3n3puR0F7peZWM=
|
||||||
github.com/dhowden/tag v0.0.0-20220530110423-77907a30b7f1/go.mod h1:SniNVYuaD1jmdEEvi+7ywb1QFR7agjeTdGKyFb0p7Rw=
|
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 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI=
|
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-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 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-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-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-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/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-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 h1:Zu/JngovGLVi6t2J3nmAf3AoTDwuzw85YZ3b9o4yU7s=
|
||||||
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
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=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Group struct {
|
type Group struct {
|
||||||
Id interface{} `json:"_id"`
|
Id any `json:"_id"`
|
||||||
Label string `json:"label"`
|
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) {
|
switch v := m["_id"].(type) {
|
||||||
case float64:
|
case float64:
|
||||||
group.Id = uint(v)
|
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()
|
groups.mutex.Lock()
|
||||||
defer groups.mutex.Unlock()
|
defer groups.mutex.Unlock()
|
||||||
|
|
||||||
|
|
@ -62,7 +62,7 @@ func (groups *Groups) FromMap(f []interface{}) *Groups {
|
||||||
|
|
||||||
for _, r := range f {
|
for _, r := range f {
|
||||||
switch m := r.(type) {
|
switch m := r.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
group := &Group{}
|
group := &Group{}
|
||||||
group.FromMap(m)
|
group.FromMap(m)
|
||||||
groups.List = append(groups.List, group)
|
groups.List = append(groups.List, group)
|
||||||
|
|
@ -72,7 +72,7 @@ func (groups *Groups) FromMap(f []interface{}) *Groups {
|
||||||
return 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()
|
groups.mutex.Lock()
|
||||||
defer groups.mutex.Unlock()
|
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()
|
livefeed.mutex.Lock()
|
||||||
defer livefeed.mutex.Unlock()
|
defer livefeed.mutex.Unlock()
|
||||||
|
|
||||||
|
|
@ -41,12 +41,12 @@ func (livefeed *Livefeed) FromMap(f interface{}) *Livefeed {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := f.(type) {
|
switch v := f.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
for s, n := range v {
|
for s, n := range v {
|
||||||
if sysId, err := strconv.Atoi(s); err == nil {
|
if sysId, err := strconv.Atoi(s); err == nil {
|
||||||
sysId := uint(sysId)
|
sysId := uint(sysId)
|
||||||
switch v := n.(type) {
|
switch v := n.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
for t, b := range v {
|
for t, b := range v {
|
||||||
switch v := b.(type) {
|
switch v := b.(type) {
|
||||||
case bool:
|
case bool:
|
||||||
|
|
|
||||||
|
|
@ -31,10 +31,10 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Log struct {
|
type Log struct {
|
||||||
Id interface{} `json:"_id"`
|
Id any `json:"_id"`
|
||||||
DateTime time.Time `json:"dateTime"`
|
DateTime time.Time `json:"dateTime"`
|
||||||
Level string `json:"level"`
|
Level string `json:"level"`
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Logs struct {
|
type Logs struct {
|
||||||
|
|
@ -99,7 +99,7 @@ func (logs *Logs) Search(searchOptions *LogsSearchOptions, db *Database) (*LogsS
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
dateTime interface{}
|
dateTime any
|
||||||
err error
|
err error
|
||||||
id sql.NullFloat64
|
id sql.NullFloat64
|
||||||
limit uint
|
limit uint
|
||||||
|
|
@ -240,14 +240,14 @@ func (logs *Logs) setDatabase(d *Database) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type LogsSearchOptions struct {
|
type LogsSearchOptions struct {
|
||||||
Date interface{} `json:"date,omitempty"`
|
Date any `json:"date,omitempty"`
|
||||||
Level interface{} `json:"level,omitempty"`
|
Level any `json:"level,omitempty"`
|
||||||
Limit interface{} `json:"limit,omitempty"`
|
Limit any `json:"limit,omitempty"`
|
||||||
Offset interface{} `json:"offset,omitempty"`
|
Offset any `json:"offset,omitempty"`
|
||||||
Sort interface{} `json:"sort,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) {
|
switch v := m["date"].(type) {
|
||||||
case string:
|
case string:
|
||||||
if t, err := time.Parse(time.RFC3339, v); err == nil {
|
if t, err := time.Parse(time.RFC3339, v); err == nil {
|
||||||
|
|
|
||||||
|
|
@ -229,7 +229,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetRemoteAddr(r *http.Request) string {
|
func GetRemoteAddr(r *http.Request) string {
|
||||||
re := regexp.MustCompile(`(\[[^\]]+\]|[^:]+):.*`)
|
re := regexp.MustCompile(`(.+):.*$`)
|
||||||
|
|
||||||
for _, addr := range strings.Split(r.Header.Get("X-Forwarded-For"), ",") {
|
for _, addr := range strings.Split(r.Header.Get("X-Forwarded-For"), ",") {
|
||||||
if ip := re.ReplaceAllString(addr, "$1"); len(ip) > 0 {
|
if ip := re.ReplaceAllString(addr, "$1"); len(ip) > 0 {
|
||||||
|
|
@ -241,5 +241,5 @@ func GetRemoteAddr(r *http.Request) string {
|
||||||
return ip
|
return ip
|
||||||
}
|
}
|
||||||
|
|
||||||
return "unknown"
|
return r.RemoteAddr
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,15 +35,15 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Message struct {
|
type Message struct {
|
||||||
Command interface{}
|
Command any
|
||||||
Payload interface{}
|
Payload any
|
||||||
Flag interface{}
|
Flag any
|
||||||
}
|
}
|
||||||
|
|
||||||
func (message *Message) FromJson(b []byte) error {
|
func (message *Message) FromJson(b []byte) error {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
f []interface{}
|
f []any
|
||||||
)
|
)
|
||||||
|
|
||||||
if err = json.Unmarshal(b, &f); err != nil {
|
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) {
|
func (message *Message) ToJson() ([]byte, error) {
|
||||||
str := []interface{}{message.Command}
|
str := []any{message.Command}
|
||||||
|
|
||||||
if message.Payload != nil && message.Payload != "" {
|
if message.Payload != nil && message.Payload != "" {
|
||||||
str = append(str, message.Payload)
|
str = append(str, message.Payload)
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,11 @@ type Options struct {
|
||||||
AfsSystems string `json:"afsSystems"`
|
AfsSystems string `json:"afsSystems"`
|
||||||
AudioConversion uint `json:"audioConversion"`
|
AudioConversion uint `json:"audioConversion"`
|
||||||
AutoPopulate bool `json:"autoPopulate"`
|
AutoPopulate bool `json:"autoPopulate"`
|
||||||
|
Branding string `json:"branding"`
|
||||||
DimmerDelay uint `json:"dimmerDelay"`
|
DimmerDelay uint `json:"dimmerDelay"`
|
||||||
DisableDuplicateDetection bool `json:"disableDuplicateDetection"`
|
DisableDuplicateDetection bool `json:"disableDuplicateDetection"`
|
||||||
DuplicateDetectionTimeFrame uint `json:"duplicateDetectionTimeFrame"`
|
DuplicateDetectionTimeFrame uint `json:"duplicateDetectionTimeFrame"`
|
||||||
|
Email string `json:"email"`
|
||||||
KeypadBeeps string `json:"keypadBeeps"`
|
KeypadBeeps string `json:"keypadBeeps"`
|
||||||
MaxClients uint `json:"maxClients"`
|
MaxClients uint `json:"maxClients"`
|
||||||
PlaybackGoesLive bool `json:"playbackGoesLive"`
|
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()
|
options.mutex.Lock()
|
||||||
defer options.mutex.Unlock()
|
defer options.mutex.Unlock()
|
||||||
|
|
||||||
|
|
@ -82,6 +84,11 @@ func (options *Options) FromMap(m map[string]interface{}) *Options {
|
||||||
options.AutoPopulate = defaults.options.autoPopulate
|
options.AutoPopulate = defaults.options.autoPopulate
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch v := m["branding"].(type) {
|
||||||
|
case string:
|
||||||
|
options.Branding = v
|
||||||
|
}
|
||||||
|
|
||||||
switch v := m["dimmerDelay"].(type) {
|
switch v := m["dimmerDelay"].(type) {
|
||||||
case float64:
|
case float64:
|
||||||
options.DimmerDelay = uint(v)
|
options.DimmerDelay = uint(v)
|
||||||
|
|
@ -112,6 +119,11 @@ func (options *Options) FromMap(m map[string]interface{}) *Options {
|
||||||
options.DuplicateDetectionTimeFrame = defaults.options.duplicateDetectionTimeFrame
|
options.DuplicateDetectionTimeFrame = defaults.options.duplicateDetectionTimeFrame
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch v := m["email"].(type) {
|
||||||
|
case string:
|
||||||
|
options.Email = v
|
||||||
|
}
|
||||||
|
|
||||||
switch v := m["keypadBeeps"].(type) {
|
switch v := m["keypadBeeps"].(type) {
|
||||||
case string:
|
case string:
|
||||||
options.KeypadBeeps = v
|
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)
|
err = db.Sql.QueryRow("select `val` from `rdioScannerConfigs` where `key` = 'options'").Scan(&s)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
var m map[string]interface{}
|
var m map[string]any
|
||||||
|
|
||||||
if err = json.Unmarshal([]byte(s), &m); err == nil {
|
if err = json.Unmarshal([]byte(s), &m); err == nil {
|
||||||
switch v := m["afsSystems"].(type) {
|
switch v := m["afsSystems"].(type) {
|
||||||
|
|
@ -239,6 +251,11 @@ func (options *Options) Read(db *Database) error {
|
||||||
options.AutoPopulate = v
|
options.AutoPopulate = v
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch v := m["branding"].(type) {
|
||||||
|
case string:
|
||||||
|
options.Branding = v
|
||||||
|
}
|
||||||
|
|
||||||
switch v := m["dimmerDelay"].(type) {
|
switch v := m["dimmerDelay"].(type) {
|
||||||
case float64:
|
case float64:
|
||||||
options.DimmerDelay = uint(v)
|
options.DimmerDelay = uint(v)
|
||||||
|
|
@ -254,6 +271,11 @@ func (options *Options) Read(db *Database) error {
|
||||||
options.DuplicateDetectionTimeFrame = uint(v)
|
options.DuplicateDetectionTimeFrame = uint(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch v := m["email"].(type) {
|
||||||
|
case string:
|
||||||
|
options.Email = v
|
||||||
|
}
|
||||||
|
|
||||||
switch v := m["keypadBeeps"].(type) {
|
switch v := m["keypadBeeps"].(type) {
|
||||||
case string:
|
case string:
|
||||||
options.KeypadBeeps = v
|
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))
|
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,
|
"afsSystems": options.AfsSystems,
|
||||||
"audioConversion": options.AudioConversion,
|
"audioConversion": options.AudioConversion,
|
||||||
"autoPopulate": options.AutoPopulate,
|
"autoPopulate": options.AutoPopulate,
|
||||||
|
"branding": options.Branding,
|
||||||
"dimmerDelay": options.DimmerDelay,
|
"dimmerDelay": options.DimmerDelay,
|
||||||
"disableDuplicateDetection": options.DisableDuplicateDetection,
|
"disableDuplicateDetection": options.DisableDuplicateDetection,
|
||||||
"duplicateDetectionTimeFrame": options.DuplicateDetectionTimeFrame,
|
"duplicateDetectionTimeFrame": options.DuplicateDetectionTimeFrame,
|
||||||
|
"email": options.Email,
|
||||||
"keypadBeeps": options.KeypadBeeps,
|
"keypadBeeps": options.KeypadBeeps,
|
||||||
"maxClients": options.MaxClients,
|
"maxClients": options.MaxClients,
|
||||||
"playbackGoesLive": options.PlaybackGoesLive,
|
"playbackGoesLive": options.PlaybackGoesLive,
|
||||||
|
|
|
||||||
|
|
@ -101,10 +101,10 @@ func ParseSdrTrunkMeta(call *Call, controller *Controller) error {
|
||||||
|
|
||||||
s = regexp.MustCompile(`(\[.+\])`).FindStringSubmatch(m.Title())
|
s = regexp.MustCompile(`(\[.+\])`).FindStringSubmatch(m.Title())
|
||||||
if len(s) > 1 && len(s[1]) > 0 {
|
if len(s) > 1 && len(s[1]) > 0 {
|
||||||
var f interface{}
|
var f any
|
||||||
if err = json.Unmarshal([]byte(s[1]), &f); err == nil {
|
if err = json.Unmarshal([]byte(s[1]), &f); err == nil {
|
||||||
switch v := f.(type) {
|
switch v := f.(type) {
|
||||||
case []interface{}:
|
case []any:
|
||||||
patches := []uint{}
|
patches := []uint{}
|
||||||
for _, patch := range v {
|
for _, patch := range v {
|
||||||
switch v := patch.(type) {
|
switch v := patch.(type) {
|
||||||
|
|
@ -151,15 +151,15 @@ func ParseMultipartContent(call *Call, p *multipart.Part, b []byte) {
|
||||||
}
|
}
|
||||||
|
|
||||||
case "frequencies":
|
case "frequencies":
|
||||||
var f interface{}
|
var f any
|
||||||
if err := json.Unmarshal(b, &f); err == nil {
|
if err := json.Unmarshal(b, &f); err == nil {
|
||||||
switch v := f.(type) {
|
switch v := f.(type) {
|
||||||
case []interface{}:
|
case []any:
|
||||||
var frequencies = []map[string]interface{}{}
|
var frequencies = []map[string]any{}
|
||||||
for _, f := range v {
|
for _, f := range v {
|
||||||
freq := map[string]interface{}{}
|
freq := map[string]any{}
|
||||||
switch v := f.(type) {
|
switch v := f.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
switch v := v["errorCount"].(type) {
|
switch v := v["errorCount"].(type) {
|
||||||
case float64:
|
case float64:
|
||||||
if v >= 0 {
|
if v >= 0 {
|
||||||
|
|
@ -204,12 +204,12 @@ func ParseMultipartContent(call *Call, p *multipart.Part, b []byte) {
|
||||||
|
|
||||||
case "patches", "patched_talkgroups":
|
case "patches", "patched_talkgroups":
|
||||||
var (
|
var (
|
||||||
f interface{}
|
f any
|
||||||
patches = []uint{}
|
patches = []uint{}
|
||||||
)
|
)
|
||||||
if err := json.Unmarshal(b, &f); err == nil {
|
if err := json.Unmarshal(b, &f); err == nil {
|
||||||
switch v := f.(type) {
|
switch v := f.(type) {
|
||||||
case []interface{}:
|
case []any:
|
||||||
for _, patch := range v {
|
for _, patch := range v {
|
||||||
switch v := patch.(type) {
|
switch v := patch.(type) {
|
||||||
case float64:
|
case float64:
|
||||||
|
|
@ -229,17 +229,17 @@ func ParseMultipartContent(call *Call, p *multipart.Part, b []byte) {
|
||||||
|
|
||||||
case "sources":
|
case "sources":
|
||||||
var (
|
var (
|
||||||
f interface{}
|
f any
|
||||||
units *Units
|
units *Units
|
||||||
)
|
)
|
||||||
if err := json.Unmarshal(b, &f); err == nil {
|
if err := json.Unmarshal(b, &f); err == nil {
|
||||||
switch v := f.(type) {
|
switch v := f.(type) {
|
||||||
case []interface{}:
|
case []any:
|
||||||
var sources = []map[string]interface{}{}
|
var sources = []map[string]any{}
|
||||||
for _, f := range v {
|
for _, f := range v {
|
||||||
src := map[string]interface{}{}
|
src := map[string]any{}
|
||||||
switch v := f.(type) {
|
switch v := f.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
switch v := v["pos"].(type) {
|
switch v := v["pos"].(type) {
|
||||||
case float64:
|
case float64:
|
||||||
if v >= 0 {
|
if v >= 0 {
|
||||||
|
|
@ -306,7 +306,7 @@ func ParseMultipartContent(call *Call, p *multipart.Part, b []byte) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseTrunkRecorderMeta(call *Call, b []byte) error {
|
func ParseTrunkRecorderMeta(call *Call, b []byte) error {
|
||||||
m := map[string]interface{}{}
|
m := map[string]any{}
|
||||||
|
|
||||||
if err := json.Unmarshal(b, &m); err != nil {
|
if err := json.Unmarshal(b, &m); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -320,12 +320,12 @@ func ParseTrunkRecorderMeta(call *Call, b []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := m["freqList"].(type) {
|
switch v := m["freqList"].(type) {
|
||||||
case []interface{}:
|
case []any:
|
||||||
freqs := []map[string]interface{}{}
|
freqs := []map[string]any{}
|
||||||
for _, f := range v {
|
for _, f := range v {
|
||||||
freq := map[string]interface{}{}
|
freq := map[string]any{}
|
||||||
switch v := f.(type) {
|
switch v := f.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
switch v := v["error_count"].(type) {
|
switch v := v["error_count"].(type) {
|
||||||
case float64:
|
case float64:
|
||||||
if v >= 0 {
|
if v >= 0 {
|
||||||
|
|
@ -368,7 +368,7 @@ func ParseTrunkRecorderMeta(call *Call, b []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := m["patched_talkgroups"].(type) {
|
switch v := m["patched_talkgroups"].(type) {
|
||||||
case []interface{}:
|
case []any:
|
||||||
patches := []uint{}
|
patches := []uint{}
|
||||||
for _, f := range v {
|
for _, f := range v {
|
||||||
switch v := f.(type) {
|
switch v := f.(type) {
|
||||||
|
|
@ -384,12 +384,12 @@ func ParseTrunkRecorderMeta(call *Call, b []byte) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := m["srcList"].(type) {
|
switch v := m["srcList"].(type) {
|
||||||
case []interface{}:
|
case []any:
|
||||||
sources := []map[string]interface{}{}
|
sources := []map[string]any{}
|
||||||
for _, f := range v {
|
for _, f := range v {
|
||||||
source := map[string]interface{}{}
|
source := map[string]any{}
|
||||||
switch v := f.(type) {
|
switch v := f.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
switch v := v["pos"].(type) {
|
switch v := v["pos"].(type) {
|
||||||
case float64:
|
case float64:
|
||||||
if v >= 0 {
|
if v >= 0 {
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ import (
|
||||||
type Scheduler struct {
|
type Scheduler struct {
|
||||||
Controller *Controller
|
Controller *Controller
|
||||||
Ticker *time.Ticker
|
Ticker *time.Ticker
|
||||||
cancel chan interface{}
|
cancel chan any
|
||||||
mutex sync.Mutex
|
mutex sync.Mutex
|
||||||
started bool
|
started bool
|
||||||
}
|
}
|
||||||
|
|
@ -33,7 +33,7 @@ type Scheduler struct {
|
||||||
func NewScheduler(controller *Controller) *Scheduler {
|
func NewScheduler(controller *Controller) *Scheduler {
|
||||||
return &Scheduler{
|
return &Scheduler{
|
||||||
Controller: controller,
|
Controller: controller,
|
||||||
cancel: make(chan interface{}),
|
cancel: make(chan any),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,9 +42,6 @@ func (scheduler *Scheduler) pruneDatabase() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduler.Controller.IngestLock()
|
|
||||||
defer scheduler.Controller.IngestUnlock()
|
|
||||||
|
|
||||||
scheduler.Controller.Logs.LogEvent(LogLevelInfo, "database pruning")
|
scheduler.Controller.Logs.LogEvent(LogLevelInfo, "database pruning")
|
||||||
|
|
||||||
if err := scheduler.Controller.Calls.Prune(scheduler.Controller.Database, scheduler.Controller.Options.PruneDays); err != nil {
|
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"`
|
AutoPopulate bool `json:"autoPopulate"`
|
||||||
Blacklists Blacklists `json:"blacklists"`
|
Blacklists Blacklists `json:"blacklists"`
|
||||||
Label string `json:"label"`
|
Label string `json:"label"`
|
||||||
Led interface{} `json:"led"`
|
Led any `json:"led"`
|
||||||
Order uint `json:"order"`
|
Order uint `json:"order"`
|
||||||
RowId interface{} `json:"_id"`
|
RowId any `json:"_id"`
|
||||||
Talkgroups *Talkgroups `json:"talkgroups"`
|
Talkgroups *Talkgroups `json:"talkgroups"`
|
||||||
Units *Units `json:"units"`
|
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) {
|
switch v := m["_id"].(type) {
|
||||||
case float64:
|
case float64:
|
||||||
system.RowId = uint(v)
|
system.RowId = uint(v)
|
||||||
|
|
@ -81,19 +81,19 @@ func (system *System) FromMap(m map[string]interface{}) *System {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := m["talkgroups"].(type) {
|
switch v := m["talkgroups"].(type) {
|
||||||
case []interface{}:
|
case []any:
|
||||||
system.Talkgroups.FromMap(v)
|
system.Talkgroups.FromMap(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch v := m["units"].(type) {
|
switch v := m["units"].(type) {
|
||||||
case []interface{}:
|
case []any:
|
||||||
system.Units.FromMap(v)
|
system.Units.FromMap(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
return system
|
return system
|
||||||
}
|
}
|
||||||
|
|
||||||
type SystemMap map[string]interface{}
|
type SystemMap map[string]any
|
||||||
|
|
||||||
type Systems struct {
|
type Systems struct {
|
||||||
List []*System
|
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()
|
systems.mutex.Lock()
|
||||||
defer systems.mutex.Unlock()
|
defer systems.mutex.Unlock()
|
||||||
|
|
||||||
|
|
@ -115,7 +115,7 @@ func (systems *Systems) FromMap(f []interface{}) *Systems {
|
||||||
|
|
||||||
for _, r := range f {
|
for _, r := range f {
|
||||||
switch m := r.(type) {
|
switch m := r.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
system := NewSystem()
|
system := NewSystem()
|
||||||
system.FromMap(m)
|
system.FromMap(m)
|
||||||
systems.List = append(systems.List, system)
|
systems.List = append(systems.List, system)
|
||||||
|
|
@ -141,7 +141,7 @@ NextId:
|
||||||
return 0
|
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()
|
systems.mutex.Lock()
|
||||||
defer systems.mutex.Unlock()
|
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 {
|
for _, fSystem := range v {
|
||||||
switch v := fSystem.(type) {
|
switch v := fSystem.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
var (
|
var (
|
||||||
mSystemId = v["id"]
|
mSystemId = v["id"]
|
||||||
mTalkgroups = v["talkgroups"]
|
mTalkgroups = v["talkgroups"]
|
||||||
|
|
@ -216,7 +216,7 @@ func (systems *Systems) GetScopedSystems(client *Client, groups *Groups, tags *T
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
case []interface{}:
|
case []any:
|
||||||
rawSystem := *system
|
rawSystem := *system
|
||||||
rawSystem.Talkgroups = NewTalkgroups()
|
rawSystem.Talkgroups = NewTalkgroups()
|
||||||
for _, fTalkgroupId := range v {
|
for _, fTalkgroupId := range v {
|
||||||
|
|
|
||||||
|
|
@ -24,11 +24,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Tag struct {
|
type Tag struct {
|
||||||
Id interface{} `json:"_id"`
|
Id any `json:"_id"`
|
||||||
Label string `json:"label"`
|
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) {
|
switch v := m["_id"].(type) {
|
||||||
case float64:
|
case float64:
|
||||||
tag.Id = uint(v)
|
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()
|
tags.mutex.Lock()
|
||||||
defer tags.mutex.Unlock()
|
defer tags.mutex.Unlock()
|
||||||
|
|
||||||
|
|
@ -62,7 +62,7 @@ func (tags *Tags) FromMap(f []interface{}) *Tags {
|
||||||
|
|
||||||
for _, r := range f {
|
for _, r := range f {
|
||||||
switch m := r.(type) {
|
switch m := r.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
tag := &Tag{}
|
tag := &Tag{}
|
||||||
tag.FromMap(m)
|
tag.FromMap(m)
|
||||||
tags.List = append(tags.List, tag)
|
tags.List = append(tags.List, tag)
|
||||||
|
|
@ -72,7 +72,7 @@ func (tags *Tags) FromMap(f []interface{}) *Tags {
|
||||||
return 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()
|
tags.mutex.Lock()
|
||||||
defer tags.mutex.Unlock()
|
defer tags.mutex.Unlock()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,19 +25,19 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Talkgroup struct {
|
type Talkgroup struct {
|
||||||
Frequency interface{} `json:"frequency"`
|
Frequency any `json:"frequency"`
|
||||||
group string
|
group string
|
||||||
GroupId uint `json:"groupId"`
|
GroupId uint `json:"groupId"`
|
||||||
Id uint `json:"id"`
|
Id uint `json:"id"`
|
||||||
Label string `json:"label"`
|
Label string `json:"label"`
|
||||||
Led interface{} `json:"led"`
|
Led any `json:"led"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Order uint `json:"order"`
|
Order uint `json:"order"`
|
||||||
TagId uint `json:"tagId"`
|
TagId uint `json:"tagId"`
|
||||||
tag string
|
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) {
|
switch v := m["id"].(type) {
|
||||||
case float64:
|
case float64:
|
||||||
talkgroup.Id = uint(v)
|
talkgroup.Id = uint(v)
|
||||||
|
|
@ -91,7 +91,7 @@ func (talkgroup *Talkgroup) FromMap(m map[string]interface{}) *Talkgroup {
|
||||||
return talkgroup
|
return talkgroup
|
||||||
}
|
}
|
||||||
|
|
||||||
type TalkgroupMap map[string]interface{}
|
type TalkgroupMap map[string]any
|
||||||
|
|
||||||
type Talkgroups struct {
|
type Talkgroups struct {
|
||||||
List []*Talkgroup
|
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()
|
talkgroups.mutex.Lock()
|
||||||
defer talkgroups.mutex.Unlock()
|
defer talkgroups.mutex.Unlock()
|
||||||
|
|
||||||
|
|
@ -113,7 +113,7 @@ func (talkgroups *Talkgroups) FromMap(f []interface{}) *Talkgroups {
|
||||||
|
|
||||||
for _, r := range f {
|
for _, r := range f {
|
||||||
switch m := r.(type) {
|
switch m := r.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
talkgroup := &Talkgroup{}
|
talkgroup := &Talkgroup{}
|
||||||
talkgroup.FromMap(m)
|
talkgroup.FromMap(m)
|
||||||
talkgroups.List = append(talkgroups.List, talkgroup)
|
talkgroups.List = append(talkgroups.List, talkgroup)
|
||||||
|
|
@ -123,7 +123,7 @@ func (talkgroups *Talkgroups) FromMap(f []interface{}) *Talkgroups {
|
||||||
return 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()
|
talkgroups.mutex.Lock()
|
||||||
defer talkgroups.mutex.Unlock()
|
defer talkgroups.mutex.Unlock()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ type Unit struct {
|
||||||
Order uint `json:"order"`
|
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) {
|
switch v := m["id"].(type) {
|
||||||
case float64:
|
case float64:
|
||||||
unit.Id = uint(v)
|
unit.Id = uint(v)
|
||||||
|
|
@ -78,7 +78,7 @@ func (units *Units) Add(id uint, label string) (*Units, bool) {
|
||||||
return units, added
|
return units, added
|
||||||
}
|
}
|
||||||
|
|
||||||
func (units *Units) FromMap(f []interface{}) *Units {
|
func (units *Units) FromMap(f []any) *Units {
|
||||||
units.mutex.Lock()
|
units.mutex.Lock()
|
||||||
defer units.mutex.Unlock()
|
defer units.mutex.Unlock()
|
||||||
|
|
||||||
|
|
@ -86,7 +86,7 @@ func (units *Units) FromMap(f []interface{}) *Units {
|
||||||
|
|
||||||
for _, r := range f {
|
for _, r := range f {
|
||||||
switch m := r.(type) {
|
switch m := r.(type) {
|
||||||
case map[string]interface{}:
|
case map[string]any:
|
||||||
unit := &Unit{}
|
unit := &Unit{}
|
||||||
unit.FromMap(m)
|
unit.FromMap(m)
|
||||||
units.List = append(units.List, unit)
|
units.List = append(units.List, unit)
|
||||||
|
|
|
||||||
|
|
@ -15,4 +15,4 @@
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
const Version = "6.4.5"
|
const Version = "6.5.0"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue