From 431e916fd1c104a15cb971432226ba60948bebb1 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Fri, 3 Jan 2025 09:35:27 -0500 Subject: [PATCH 01/27] firehoes --- backend/index.js | 9 +++++++-- docker-compose.yml | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/backend/index.js b/backend/index.js index 2f328a3..31f9730 100644 --- a/backend/index.js +++ b/backend/index.js @@ -47,6 +47,7 @@ const mongo_host = typeof process.env['MONGO_HOST'] !== 'undefined' ? process.en const mongo_port = typeof process.env['MONGO_PORT'] !== 'undefined' ? process.env['MONGO_PORT'] : 27017; const mongo_user = process.env['MONGO_USER']; const mongo_password = process.env['MONGO_PASSWORD']; +const firehose_key = process.env['FIREHOSE_KEY'] !== 'undefined' ? process.env['FIREHOSE_KEY'] : null; let mongoUrl; @@ -211,7 +212,7 @@ function notify_clients(call) { if (clients.hasOwnProperty(key)) { var client = clients[key]; if (client.active) { - if (client.shortName == call.shortName.toLowerCase()) { + if ((client.firehose == true) || (client.shortName == call.shortName.toLowerCase())) { // if client is not filtering for stars, or if the client is filtering and the call has stars if (!client.filterStarred || call.star) { if (client.filterCode == "") { @@ -269,6 +270,7 @@ io.sockets.on('connection', function (client) { clients[client.id].filterType = String(data.filterType); clients[client.id].talkgroupNums = []; clients[client.id].timestamp = new Date(); + clients[client.id].firehose = false; if (typeof clients[client.id].filterCode != "string") { console.error("Error - Socket - Invalid filterCode: " + data.filterCode + " ShortName: " + data.shortName.toLowerCase()); @@ -276,7 +278,10 @@ io.sockets.on('connection', function (client) { return; } - if ((data.filterType == "group") && (typeof data.filterCode == "string") && (data.filterCode.indexOf(',') == -1)) { + if ((data.filterType == "firehose") && firehose_key && (typeof data.filterCode == "string") && (data.filterCode == firehose_key)) { + clients[client.id].firehose = true; + + } else if ((data.filterType == "group") && (typeof data.filterCode == "string") && (data.filterCode.indexOf(',') == -1)) { if (!ObjectId.isValid(data.filterCode)) { console.error("Error - Socket - Invalid Group ID: " + data.filterCode); delete clients[client.id]; diff --git a/docker-compose.yml b/docker-compose.yml index c96b4bd..7093c68 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -73,6 +73,7 @@ services: - MONGO_PASSWORD - MAILJET_KEY - MAILJET_SECRET + - FIREHOSE_KEY - REACT_APP_ADMIN_EMAIL - REACT_APP_SITE_NAME - NODE_ENV From 3d08ee65d41a65abeed3dbeee94518adc0ebf591 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Fri, 3 Jan 2025 11:10:11 -0500 Subject: [PATCH 02/27] tested --- backend/index.js | 159 +++++++++++++--------- prod.env.example | 2 +- utils/websocket_example/.gitignore | 1 + utils/websocket_example/index.js | 59 ++++++++ utils/websocket_example/package-lock.json | 116 ++++++++++++++++ utils/websocket_example/package.json | 14 ++ 6 files changed, 288 insertions(+), 63 deletions(-) create mode 100644 utils/websocket_example/.gitignore create mode 100644 utils/websocket_example/index.js create mode 100644 utils/websocket_example/package-lock.json create mode 100644 utils/websocket_example/package.json diff --git a/backend/index.js b/backend/index.js index 31f9730..080d023 100644 --- a/backend/index.js +++ b/backend/index.js @@ -60,20 +60,20 @@ if ((typeof mongo_user !== 'undefined') && (typeof mongo_password !== 'undefined const connect = async () => { - // Demonstrate the readyState and on event emitters - console.log(mongoose.connection.readyState); //logs 0 - mongoose.connection.on('connecting', () => { - console.log('Mongoose is connecting') - }); - mongoose.connection.on('connected', () => { - console.log('Mongoose is connected'); - }); - mongoose.connection.on('disconnecting', () => { - console.log('Mongoose is disconnecting'); - }); - - await mongoose.connect(mongoUrl, { useNewUrlParser: true, useUnifiedTopology: true, maxPoolSize: 50 }); - console.log("All Done"); + // Demonstrate the readyState and on event emitters + console.log(mongoose.connection.readyState); //logs 0 + mongoose.connection.on('connecting', () => { + console.log('Mongoose is connecting') + }); + mongoose.connection.on('connected', () => { + console.log('Mongoose is connected'); + }); + mongoose.connection.on('disconnecting', () => { + console.log('Mongoose is disconnecting'); + }); + + await mongoose.connect(mongoUrl, { useNewUrlParser: true, useUnifiedTopology: true, maxPoolSize: 50 }); + console.log("All Done"); } connect(); @@ -124,7 +124,7 @@ app.get('/:shortName/calls/newer', calls.get_newer_calls); app.get('/:shortName/calls/older', calls.get_older_calls); app.get('/:shortName/calls/date', calls.get_date_calls); app.get('/:shortName/calls/:time/older', calls.get_iphone_calls); -app.get('/:shortName/calls', calls.get_calls); +app.get('/:shortName/calls', calls.get_calls); /*------ UPLOADS ---------- upload.single('call'), uploads.upload,*/ @@ -213,40 +213,37 @@ function notify_clients(call) { var client = clients[key]; if (client.active) { if ((client.firehose == true) || (client.shortName == call.shortName.toLowerCase())) { - // if client is not filtering for stars, or if the client is filtering and the call has stars - if (!client.filterStarred || call.star) { - if (client.filterCode == "") { - sent++; - client.socket.emit("new message", JSON.stringify(call)); - } else if (client.filterType == "unit") { - var codeArray = client.filterCode.split(','); - var success = false; - for (var j = 0; j < codeArray.length; ++j) { - for (var k = 0; k < call.srcList.length; k++) { - if (codeArray[j] == call.srcList[k]) { - sent++; - client.socket.emit("new message", JSON.stringify(call)); - success = true; - break; - } - } - if (success) { + // if client + if ((client.firehose == true) || (client.filterType == "all")) { + sent++; + client.socket.emit("new message", JSON.stringify(call)); + } else if (client.filterType == "unit") { + var codeArray = client.filterCode.split(','); + var success = false; + for (var j = 0; j < codeArray.length; ++j) { + for (var k = 0; k < call.srcList.length; k++) { + if (codeArray[j] == call.srcList[k]) { + sent++; + client.socket.emit("new message", JSON.stringify(call)); + success = true; break; } } - - - } else { - var codeArray = client.talkgroupNums; - for (var j = 0; j < codeArray.length; ++j) { - if (codeArray[j] == call.talkgroupNum) { - client.socket.emit("new message", JSON.stringify(call)); - sent++ - break; - } + if (success) { + break; + } + } + } else { + var codeArray = client.talkgroupNums; + for (var j = 0; j < codeArray.length; ++j) { + if (codeArray[j] == call.talkgroupNum) { + client.socket.emit("new message", JSON.stringify(call)); + sent++ + break; } } } + } } } @@ -257,11 +254,24 @@ function notify_clients(call) { } } +function parseHeader(header) { + for (const directive of header.split(",")[0].split(";")) { + if (directive.startsWith("for=")) { + return directive.substring(4); + } + } +} + + io.sockets.on('connection', function (client) { + const header_ipAddress = parseHeader(client.handshake.headers["forwarded"] || ""); + const cf_ipAddress = client.handshake.headers["cf-connecting-ip"]; + const x_ipAddress = client.handshake.headers["x-forwarded-for"].split(",")[0]; + const ipAddress = cf_ipAddress || x_ipAddress || header_ipAddress; clients[client.id] = { socket: client, active: false }; clients[client.id].timestamp = new Date(); client.on('start', async function (data) { - if ((typeof clients[client.id] !== "undefined") && data.shortName) { + if (typeof clients[client.id] !== "undefined") { clients[client.id].active = true; clients[client.id].shortName = data.shortName.toLowerCase(); clients[client.id].filterCode = String(data.filterCode); @@ -271,19 +281,41 @@ io.sockets.on('connection', function (client) { clients[client.id].talkgroupNums = []; clients[client.id].timestamp = new Date(); clients[client.id].firehose = false; + clients[client.id].ipAddress = ipAddress; + // Validate the filterCode and filterType if (typeof clients[client.id].filterCode != "string") { - console.error("Error - Socket - Invalid filterCode: " + data.filterCode + " ShortName: " + data.shortName.toLowerCase()); + console.error("Error - Socket - Invalid filterCode: " + data.filterCode + " ShortName: " + data.shortName.toLowerCase() + " ClientID: " + client.id + " IP: " + ipAddress); delete clients[client.id]; return; } - if ((data.filterType == "firehose") && firehose_key && (typeof data.filterCode == "string") && (data.filterCode == firehose_key)) { - clients[client.id].firehose = true; - - } else if ((data.filterType == "group") && (typeof data.filterCode == "string") && (data.filterCode.indexOf(',') == -1)) { - if (!ObjectId.isValid(data.filterCode)) { - console.error("Error - Socket - Invalid Group ID: " + data.filterCode); + if (typeof clients[client.id].filterType != "string") { + console.error("Error - Socket - Invalid filterType: " + data.filterType + " ShortName: " + data.shortName.toLowerCase() + " ClientID: " + client.id + " IP: " + ipAddress); + delete clients[client.id]; + return; + } + + if ((data.filterType != "firehose") && (!data.shortName || (typeof data.shortName != "string"))) { // If it is not firehose, then it must have a shortName + console.error("Error - Socket - Invalid ShortName: " + data.shortName + " ClientID: " + client.id + " IP: " + ipAddress); + delete clients[client.id]; + return; + } + + + // Check if the filterType is firehose and the filterCode is the firehose key + if (data.filterType == "firehose") { + if (firehose_key && (typeof data.filterCode == "string") && (data.filterCode == firehose_key)) { + console.log("Enabling Firehose mode for Client: " + client.id + " IP: " + ipAddress); + clients[client.id].firehose = true; + } else { + console.error("Error - Socket - Invalid Firehose Key: " + data.filterCode + " ClientID: " + client.id + " Filter Name: " + client.filterName + " IP: " + ipAddress); + delete clients[client.id]; + return; + } + } else if ((data.filterType == "group")) { // Handle filterType == "group" + if ((typeof data.filterCode != "string") || (data.filterCode.indexOf(',') != -1) || !ObjectId.isValid(data.filterCode)) { + console.error("Error - Socket - Invalid Group ID: " + data.filterCode + " ShortName: " + data.shortName.toLowerCase() + " ClientID: " + client.id + " IP: " + ipAddress); delete clients[client.id]; return; } @@ -291,29 +323,32 @@ io.sockets.on('connection', function (client) { const group = await Group.findOne({ 'shortName': data.shortName.toLowerCase(), '_id': ObjectId.createFromHexString(data.filterCode) }); if (typeof clients[client.id] === "undefined") { - console.error("How can it be undefined here!!"); - console.error(client.id); + console.error("Error - Socket - Client not found ShortName: " + data.shortName + " ClientID: " + client.id + " IP: " + ipAddress); return; } if (group) { clients[client.id].talkgroupNums = group.talkgroups; } else { - console.error("Error - Socket: Invalid group " + data.filterCode + " Shortname: " + data.shortName + " ClientID: " + client.id); + console.error("Error - Socket - Invalid group " + data.filterCode + " Shortname: " + data.shortName + " ClientID: " + client.id + " IP: " + ipAddress); delete clients[client.id]; return; } - + // Handle filterType == "talkgroup" } else if ((data.filterType == "talkgroup") && Array.isArray(data.filterCode)) { clients[client.id].talkgroupNums = data.filterCode; + } else if ((data.filterType == "all") && (data.filterCode == "")) { + // Handle filterType == "all" + clients[client.id].talkgroupNums = []; } else { - // This is for ALL filters - //console.error("Invalid Socket Filter - Type: " + data.filterType + " Code: " + data.filterCode); + console.error("Error - Socket - Invalid Filter Type: " + data.filterType + " Filter Code: " + data.filterCode + " Filter Name: " + data.filterName + " ShortName: " + data.shortName.toLowerCase() + " ClientID: " + client.id + " IP: " + ipAddress); + delete clients[client.id]; + return; } - //console.log("[" + data.shortName.toLowerCase() + "] WebSocket Updating - Client: " + client.id + " code set to: " + data.filterCode + " type set to: " + data.filterType + " TGS: " + clients[client.id].talkgroupNums); } else { - console.error("Error - Socket.io [Start] either client not found or no Short Name"); + console.error("Error - Socket.io [Start] either client not found, shortName:" + data.shortName + " ClientID: " + client.id + " IP: " + ipAddress); } }); + client.on('stop', function (data) { if (clients[client.id]) { clients[client.id].active = false; @@ -329,15 +364,15 @@ io.sockets.on('connection', function (client) { stats.init_stats(); // This will run at 3am each day. // IF you don't set the minute to 0, it will run every minute while it is still 3, so at 3:01, 3:02... etc -schedule.scheduleJob('0 3 * * *', function() { +schedule.scheduleJob('0 3 * * *', function () { db.cleanOldCalls(); }); -schedule.scheduleJob('15 3 * * *', function() { +schedule.scheduleJob('15 3 * * *', function () { db.cleanOldEvents(); }); -schedule.scheduleJob('30 3 * * *', function() { +schedule.scheduleJob('30 3 * * *', function () { db.cleanOldPodcasts(); }); diff --git a/prod.env.example b/prod.env.example index 24ddb02..5203982 100644 --- a/prod.env.example +++ b/prod.env.example @@ -3,7 +3,7 @@ export MAILJET_SECRET="FILL IN HERE" export REACT_APP_SITE_NAME=OpenMHz export REACT_APP_ADMIN_EMAIL="admin@email.com" export DOMAIN_NAME="openmhz.com" - +export FIREHOSE_KEY="firehose_key" export S3_PROFILE='default' export S3_REGION='us-west-001' export S3_ENDPOINT='https://s3.us-west-1.wasabisys.com' diff --git a/utils/websocket_example/.gitignore b/utils/websocket_example/.gitignore new file mode 100644 index 0000000..40b878d --- /dev/null +++ b/utils/websocket_example/.gitignore @@ -0,0 +1 @@ +node_modules/ \ No newline at end of file diff --git a/utils/websocket_example/index.js b/utils/websocket_example/index.js new file mode 100644 index 0000000..6196a4c --- /dev/null +++ b/utils/websocket_example/index.js @@ -0,0 +1,59 @@ +// Install first: npm install socket.io-client +const io = require('socket.io-client'); +const serverUrl = 'http://api.openmhz.test'; // Update URL to match your server + +// Connect to the WebSocket server +const socket = io(serverUrl); // Update URL to match your server + + +// filterType can be "group", "talkgroup", "all", or "firehose" +// if filterType is "group" then filterCode is the group ID +// if filterType is "talkgroup" then filterCode is the talkgroup ID +// if filterType is "all" then filterCode is "" +// if filterType is "firehose" then filterCode is set to the FIREHOSE_KEY + +// shortName is the shortName for the System to monitor, unless it is in firehose mode, then it is set to "" +// filterStarred is ignored, so just set to false + + + +// Connection event handlers +socket.on('connect', () => { + console.log('Connected to server'); + socket.emit("start", { + filterCode: "", + filterType: "all", + filterName: "Test Client", + filterStarred: false, + shortName: "test" + }); +}); + +socket.on('disconnect', () => { + console.log('Disconnected from server'); +}); + +// Listen for incoming messages +socket.on('new message', (message) => { + message = JSON.parse(message); + const date = new Date(message.time); + console.log('\n=== Call Details ==='); + console.log(`Time: ${date.toLocaleString()}`); + console.log(`Talkgroup: ${message.talkgroupNum}`); + console.log(`System: ${message.shortName}`); + console.log(`Duration: ${message.len} seconds`); + console.log(`Frequency: ${(message.freq / 1000000).toFixed(3)} MHz`); + console.log(`Emergency: ${message.emergency ? 'Yes' : 'No'}`); + console.log('\nSource List:'); + message.srcList.forEach(src => { + console.log(`- Source ${src.src} at position ${src.pos}s`); + }); + console.log('\nAudio URL:', message.url); + console.log('==================\n'); +}); + +// Error handling +socket.on('connect_error', (error) => { + console.error('Connection error:', error); +}); + diff --git a/utils/websocket_example/package-lock.json b/utils/websocket_example/package-lock.json new file mode 100644 index 0000000..709abb6 --- /dev/null +++ b/utils/websocket_example/package-lock.json @@ -0,0 +1,116 @@ +{ + "name": "websocket_example", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "websocket_example", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "socket.io-client": "^4.8.1" + } + }, + "node_modules/@socket.io/component-emitter": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==" + }, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/engine.io-client": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.2.tgz", + "integrity": "sha512-TAr+NKeoVTjEVW8P3iHguO1LO6RlUz9O5Y8o7EY0fU+gY1NYqas7NN3slpFtbXEsLMHk0h90fJMfKjRkQ0qUIw==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.17.1", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/socket.io-client": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.1.tgz", + "integrity": "sha512-hJVXfu3E28NmzGk8o1sHhN3om52tRvwYeidbj7xKy2eIIse5IoKX3USlS6Tqt3BHAtflLIkCQBkzVrEEfWUyYQ==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.2", + "engine.io-client": "~6.6.1", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz", + "integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==", + "dependencies": { + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/xmlhttprequest-ssl": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", + "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", + "engines": { + "node": ">=0.4.0" + } + } + } +} diff --git a/utils/websocket_example/package.json b/utils/websocket_example/package.json new file mode 100644 index 0000000..52cd412 --- /dev/null +++ b/utils/websocket_example/package.json @@ -0,0 +1,14 @@ +{ + "name": "websocket_example", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Luke Berndt", + "license": "ISC", + "dependencies": { + "socket.io-client": "^4.8.1" + } +} From 24cc7ce6059ebb979f064587196fa1e718a2f6aa Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Mon, 6 Jan 2025 22:04:18 -0500 Subject: [PATCH 03/27] fixes for Patches / Star bug --- backend/controllers/calls.js | 1 + frontend/src/Call/components/CallInfoPane.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/controllers/calls.js b/backend/controllers/calls.js index 6c0696d..060e820 100644 --- a/backend/controllers/calls.js +++ b/backend/controllers/calls.js @@ -210,6 +210,7 @@ function package_call(item) { time: item.time, timeString: timeString, dateString: dateString, + patches: item.patches, path: item.path, name: item.name, freq: item.freq, diff --git a/frontend/src/Call/components/CallInfoPane.js b/frontend/src/Call/components/CallInfoPane.js index fdb581b..a92c368 100644 --- a/frontend/src/Call/components/CallInfoPane.js +++ b/frontend/src/Call/components/CallInfoPane.js @@ -55,7 +55,7 @@ function CallInfoPane(props) { talkgroupNum = currentCall.talkgroupNum; patches = currentCall.patches; - if(patches.length > 1) { + if(patches && (patches.length > 1)) { patchString = patches.join(", "); } else{ From 70e1c155ce4b73ef11ca586fc71401de37f8754e Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Thu, 6 Feb 2025 21:19:23 -0500 Subject: [PATCH 04/27] add system status --- backend/controllers/systems.js | 15 +++++++++++++++ backend/index.js | 4 +++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/backend/controllers/systems.js b/backend/controllers/systems.js index f54c75a..452d8a5 100644 --- a/backend/controllers/systems.js +++ b/backend/controllers/systems.js @@ -147,6 +147,21 @@ async function load_systems(systemClients) { // console.log("Loaded Systems: " + systemList.length + " that have been active since: " + fromDate); } +exports.get_system_status = async function (req, res) { + var shortName = req.params.shortName.toLowerCase(); + var system = systemList.find(function (element) { + return element.shortName == shortName; + }); + + var response = { + active: system.active, + lastActive: system.lastActive, + clientCount: system.clientCount, + callAvg: system.callAvg, + } + res.contentType('json'); + res.send(JSON.stringify(response)); +} exports.get_systems = async function (req, res) { /* going to use a cron job to update the system list every 2 minutes diff --git a/backend/index.js b/backend/index.js index 080d023..666a0c6 100644 --- a/backend/index.js +++ b/backend/index.js @@ -107,7 +107,6 @@ function addSystemClients(req, res, next) { } function addTotalClients(req, res, next) { - req.totalClients = Object.keys(clients).length; next(); } @@ -153,6 +152,9 @@ app.get('/events/:id', events.getEvent); app.get('/:shortName/stats', stats.get_stats); app.get('/stats', addTotalClients, sys_stats.siteStats) +/*------ SYSTEM STATUS ----------*/ +app.get('/status/:shortName', systems.get_system_status); + function get_clients(req, res) { if (req.params.shortName) { From 7dea9e614b7557e52ffe3524f99210d0868d9d64 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Thu, 6 Feb 2025 22:08:05 -0500 Subject: [PATCH 05/27] AWS S3 SDK fix --- backend/controllers/uploads.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/controllers/uploads.js b/backend/controllers/uploads.js index 3f051f6..919f343 100644 --- a/backend/controllers/uploads.js +++ b/backend/controllers/uploads.js @@ -52,6 +52,8 @@ const client = new S3Client({ httpsAgent: agent, }), credentials: fromIni({ profile: s3_profile }), + requestChecksumCalculation: 'WHEN_REQUIRED', + responseChecksumValidation: 'WHEN_REQUIRED', endpoint: s3_endpoint, region: s3_region, maxAttempts: 2, From 25240eeb033596cf45b68a32b63e5c397a9c3e7a Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Sun, 9 Feb 2025 08:15:08 -0500 Subject: [PATCH 06/27] include the current time in the status request --- backend/controllers/systems.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backend/controllers/systems.js b/backend/controllers/systems.js index 452d8a5..8ee5376 100644 --- a/backend/controllers/systems.js +++ b/backend/controllers/systems.js @@ -154,10 +154,13 @@ exports.get_system_status = async function (req, res) { }); var response = { + name: system.name, + description: system.description, active: system.active, lastActive: system.lastActive, clientCount: system.clientCount, callAvg: system.callAvg, + requestTime: Date.now() } res.contentType('json'); res.send(JSON.stringify(response)); From 0f309d5e431575b72c00df181b8c525064bf111f Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Sun, 9 Feb 2025 08:43:03 -0500 Subject: [PATCH 07/27] return a string --- backend/controllers/systems.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/controllers/systems.js b/backend/controllers/systems.js index 8ee5376..7990bf9 100644 --- a/backend/controllers/systems.js +++ b/backend/controllers/systems.js @@ -153,14 +153,16 @@ exports.get_system_status = async function (req, res) { return element.shortName == shortName; }); + var now_string = new Date().toLocaleString(); + var last_active_string = new Date(system.lastActive).toLocaleString(); var response = { name: system.name, description: system.description, active: system.active, - lastActive: system.lastActive, + lastActive: last_active_string, clientCount: system.clientCount, callAvg: system.callAvg, - requestTime: Date.now() + requestTime: now_string } res.contentType('json'); res.send(JSON.stringify(response)); From 1568aabfdaf4e7389a2be260a544a3215a4afc99 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Fri, 28 Feb 2025 08:10:33 -0500 Subject: [PATCH 08/27] fix for events --- backend/controllers/events.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/controllers/events.js b/backend/controllers/events.js index b8a3203..8d6897c 100644 --- a/backend/controllers/events.js +++ b/backend/controllers/events.js @@ -25,6 +25,8 @@ var s3_profile = process.env['S3_PROFILE'] != null ? process.env['S3_PROFILE'] : const client = new S3Client({ credentials: fromIni({ profile: s3_profile }), + requestChecksumCalculation: 'WHEN_REQUIRED', + responseChecksumValidation: 'WHEN_REQUIRED', endpoint: s3_endpoint, region: s3_region, maxAttempts: 2 From bc6868e1d41f161d946b2c0104c8bad1a7131fd0 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Wed, 25 Jun 2025 22:04:36 -0400 Subject: [PATCH 09/27] never sleep --- frontend/src/Call/CallPlayer.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/frontend/src/Call/CallPlayer.js b/frontend/src/Call/CallPlayer.js index 9ec0a84..c30bd9e 100644 --- a/frontend/src/Call/CallPlayer.js +++ b/frontend/src/Call/CallPlayer.js @@ -177,6 +177,15 @@ function CallPlayer(props) { } }, [system]) + useEffect(async () => { + try { + const wakeLock = await navigator.wakeLock.request("screen"); + } catch (err) { + // the wake lock request fails - usually system related, such being low on battery + console.log(`${err.name}, ${err.message}`); + } + }, []); + const handleStatusDismiss = () => { setStatusVisible(false); } From 5bec2a7809f027a749f8336216db248d6d23dbe3 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Fri, 11 Jul 2025 07:16:53 -0400 Subject: [PATCH 10/27] Update CallItem.js --- frontend/src/Call/components/CallItem.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/frontend/src/Call/components/CallItem.js b/frontend/src/Call/components/CallItem.js index 7c182d7..f48d670 100644 --- a/frontend/src/Call/components/CallItem.js +++ b/frontend/src/Call/components/CallItem.js @@ -105,6 +105,13 @@ const CallItem = (props) => { key: "blue", inverted: "true" } + } else if (call.emergency) { + rowSelected = { + positive: true, + color: "red", + key: "red", + inverted: "true" + } } let talkgroup; if ((typeof talkgroups== 'undefined') || (typeof talkgroups.talkgroups[call.talkgroupNum] == 'undefined')) { From 0f69a3e9dacdb9ad96b11a078f0fa40bcdc0dba5 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Sat, 12 Jul 2025 10:10:21 -0400 Subject: [PATCH 11/27] add destroy --- frontend/src/Call/components/MediaPlayer.js | 103 +++++++++++++++----- 1 file changed, 78 insertions(+), 25 deletions(-) diff --git a/frontend/src/Call/components/MediaPlayer.js b/frontend/src/Call/components/MediaPlayer.js index 5250e9b..2b14ff1 100644 --- a/frontend/src/Call/components/MediaPlayer.js +++ b/frontend/src/Call/components/MediaPlayer.js @@ -33,8 +33,15 @@ const MediaPlayer = (props) => { const [playTime, setPlayTime] = useState(0); const playSilence = props.playSilence; const parentHandlePlayPause = props.onPlayPause - const regionsPlugin = useMemo(() => RegionsPlugin.create(), []); - const plugins = useMemo(() => [regionsPlugin], [regionsPlugin]); + const regionsPlugin = useMemo(() => { + try { + return RegionsPlugin.create(); + } catch (error) { + console.warn('Failed to create regions plugin:', error); + return null; + } + }, []); + const plugins = useMemo(() => regionsPlugin ? [regionsPlugin] : [], [regionsPlugin]); useEffect(() => { @@ -52,47 +59,93 @@ const MediaPlayer = (props) => { }); } - wavesurfer && wavesurfer.load("/silence.m4a"); - regionsPlugin.clearRegions(); + if (wavesurfer) { + wavesurfer.load("/silence.m4a"); + } + if (regionsPlugin) { + try { + regionsPlugin.clearRegions(); + } catch (error) { + console.warn('Error clearing regions:', error); + } + } - // // In browsers that don’t yet support this functionality, - // // playPromise won’t be defined. - // if (playPromise !== undefined) { - // playPromise.then(function () { + // Cleanup function + return () => { + if (regionsPlugin) { + try { + regionsPlugin.clearRegions(); + } catch (error) { + console.warn('Error clearing regions in cleanup:', error); + } + } + }; - // }).catch(function (error) { - // console.log("Automatic playback failed: " + error); - // // Show a UI element to let the user manually start playback. - // }); - // } else { - // audio.src = false; - // } - - }, [playSilence]); + }, [playSilence, wavesurfer, regionsPlugin]); useEffect(() => { setSourceIndex(0); + + // Cleanup function + return () => { + // No cleanup needed for this effect + }; }, [call]); useEffect(() => { if (wavesurfer) { wavesurfer.setVolume(volume); } + + // Cleanup function + return () => { + // No cleanup needed for this effect + }; }, [volume, wavesurfer]); + // Cleanup effect for component unmount + useEffect(() => { + return () => { + if (wavesurfer) { + try { + wavesurfer.destroy(); + } catch (error) { + console.warn('Error destroying wavesurfer:', error); + } + } + if (regionsPlugin) { + try { + regionsPlugin.destroy(); + } catch (error) { + console.warn('Error destroying regions plugin:', error); + } + } + }; + }, [wavesurfer, regionsPlugin]); + const onReady = (ws) => { setWavesurfer(ws) setIsPlaying(false) - regionsPlugin.clearRegions(); - if (call) { + if (regionsPlugin) { + try { + regionsPlugin.clearRegions(); + } catch (error) { + console.warn('Error clearing regions:', error); + } + } + if (call && regionsPlugin) { call.srcList.forEach(src => { - regionsPlugin.addRegion({ - start: src.pos, - color: "rgba(128, 128, 128, 1.0)", - drag: false, - resize: false - }); + try { + regionsPlugin.addRegion({ + start: src.pos, + color: "rgba(128, 128, 128, 1.0)", + drag: false, + resize: false + }); + } catch (error) { + console.warn('Error adding region:', error); + } }); } } From 57d3cb87daae76e21faad33b3a4189aaa6554f2a Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Sat, 12 Jul 2025 10:43:04 -0400 Subject: [PATCH 12/27] acitivity is back and emergency is highlighted --- backend/controllers/calls.js | 3 +++ docker-compose.yml | 1 - frontend/src/Call/CallPlayer.js | 18 +++++++++++------- frontend/src/Call/components/CallInfoPane.js | 6 ++++++ frontend/src/Call/components/CallItem.js | 1 + frontend/src/Call/components/MediaPlayer.js | 4 ++-- nginx-proxy/Dockerfile | 4 ++++ nginx-proxy/entrypoint.sh | 20 ++++++++++++++++++++ 8 files changed, 47 insertions(+), 10 deletions(-) create mode 100644 nginx-proxy/entrypoint.sh diff --git a/backend/controllers/calls.js b/backend/controllers/calls.js index 060e820..a8fa53c 100644 --- a/backend/controllers/calls.js +++ b/backend/controllers/calls.js @@ -19,6 +19,7 @@ const build_call_list = (items) => { time: item.time, srcList: item.srcList, star: item.star, + emergency: item.emergency, freq: item.freq, patches: item.patches, len: Math.round(item.len) @@ -37,6 +38,7 @@ async function get_calls(query, numResults, middleDate, res) { path: true, name: true, time: true, + emergency: true, srcList: true, freq: true, patches: true, @@ -216,6 +218,7 @@ function package_call(item) { freq: item.freq, srcList: item.srcList, star: item.star, + emergency: item.emergency, len: Math.round(item.len) }; return call; diff --git a/docker-compose.yml b/docker-compose.yml index 7093c68..57ad39d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -25,7 +25,6 @@ services: - frontend - account - admin - command: /bin/bash -c "envsubst '$$DOMAIN_NAME' < /etc/nginx/conf.d/site.template > /etc/nginx/conf.d/default.conf && while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g 'daemon off;'" certbot: image: certbot/certbot:latest diff --git a/frontend/src/Call/CallPlayer.js b/frontend/src/Call/CallPlayer.js index c30bd9e..fe4c121 100644 --- a/frontend/src/Call/CallPlayer.js +++ b/frontend/src/Call/CallPlayer.js @@ -177,13 +177,17 @@ function CallPlayer(props) { } }, [system]) - useEffect(async () => { - try { - const wakeLock = await navigator.wakeLock.request("screen"); - } catch (err) { - // the wake lock request fails - usually system related, such being low on battery - console.log(`${err.name}, ${err.message}`); - } + useEffect(() => { + const requestWakeLock = async () => { + try { + const wakeLock = await navigator.wakeLock.request("screen"); + } catch (err) { + // the wake lock request fails - usually system related, such being low on battery + console.log(`${err.name}, ${err.message}`); + } + }; + + requestWakeLock(); }, []); const handleStatusDismiss = () => { diff --git a/frontend/src/Call/components/CallInfoPane.js b/frontend/src/Call/components/CallInfoPane.js index a92c368..e8b08aa 100644 --- a/frontend/src/Call/components/CallInfoPane.js +++ b/frontend/src/Call/components/CallInfoPane.js @@ -26,6 +26,7 @@ function CallInfoPane(props) { let patchString = ""; let header = "Call Info" let title = "" + let callEmergnecy = (<> ); const currentCall = props.call ? props.call : false; const { callLink, callDownload, callTweet } = useCallLink(props.call) const { data: allSystems, isSuccess } = useGetSystemsQuery(); @@ -54,6 +55,10 @@ function CallInfoPane(props) { callLength = currentCall.len; talkgroupNum = currentCall.talkgroupNum; patches = currentCall.patches; + + if (currentCall.emergency) { + callEmergnecy = (
Emergency
) + } if(patches && (patches.length > 1)) { patchString = patches.join(", "); @@ -100,6 +105,7 @@ function CallInfoPane(props) { return ( <>
{header}
+ {callEmergnecy} {srcList} diff --git a/frontend/src/Call/components/CallItem.js b/frontend/src/Call/components/CallItem.js index f48d670..74c87c6 100644 --- a/frontend/src/Call/components/CallItem.js +++ b/frontend/src/Call/components/CallItem.js @@ -108,6 +108,7 @@ const CallItem = (props) => { } else if (call.emergency) { rowSelected = { positive: true, + error: true, color: "red", key: "red", inverted: "true" diff --git a/frontend/src/Call/components/MediaPlayer.js b/frontend/src/Call/components/MediaPlayer.js index 2b14ff1..a9ff14e 100644 --- a/frontend/src/Call/components/MediaPlayer.js +++ b/frontend/src/Call/components/MediaPlayer.js @@ -115,9 +115,9 @@ const MediaPlayer = (props) => { } if (regionsPlugin) { try { - regionsPlugin.destroy(); + regionsPlugin.clearRegions(); } catch (error) { - console.warn('Error destroying regions plugin:', error); + console.warn('Error clearing regions in cleanup:', error); } } }; diff --git a/nginx-proxy/Dockerfile b/nginx-proxy/Dockerfile index 60005e1..776cfb0 100644 --- a/nginx-proxy/Dockerfile +++ b/nginx-proxy/Dockerfile @@ -24,3 +24,7 @@ COPY *.conf /etc/nginx/ # Add virtual hosts COPY ${STAGE}/vhosts/ /etc/nginx/conf.d/ + +COPY entrypoint.sh /entrypoint.sh +RUN chmod +x /entrypoint.sh +ENTRYPOINT ["/entrypoint.sh"] diff --git a/nginx-proxy/entrypoint.sh b/nginx-proxy/entrypoint.sh new file mode 100644 index 0000000..8ddf48b --- /dev/null +++ b/nginx-proxy/entrypoint.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -e + +# Render the config from template +envsubst '$DOMAIN_NAME' < /etc/nginx/conf.d/site.template > /etc/nginx/conf.d/default.conf + +# Test Nginx config +nginx -t + +# Background job: reload Nginx every 6 hours to pick up new certs +( + while true; do + sleep 6h + echo "[nginx] Reloading config..." + nginx -s reload && echo "[nginx] Reload succeeded" || echo "[nginx] Reload failed" + done +) & + +# Start Nginx in the foreground +exec nginx -g 'daemon off;' \ No newline at end of file From 4204e2d197525e3eeee6b2d9b0b710b3cddef4c0 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Sat, 12 Jul 2025 11:03:25 -0400 Subject: [PATCH 13/27] Update MediaPlayer.js --- frontend/src/Call/components/MediaPlayer.js | 103 +++++--------------- 1 file changed, 25 insertions(+), 78 deletions(-) diff --git a/frontend/src/Call/components/MediaPlayer.js b/frontend/src/Call/components/MediaPlayer.js index a9ff14e..5250e9b 100644 --- a/frontend/src/Call/components/MediaPlayer.js +++ b/frontend/src/Call/components/MediaPlayer.js @@ -33,15 +33,8 @@ const MediaPlayer = (props) => { const [playTime, setPlayTime] = useState(0); const playSilence = props.playSilence; const parentHandlePlayPause = props.onPlayPause - const regionsPlugin = useMemo(() => { - try { - return RegionsPlugin.create(); - } catch (error) { - console.warn('Failed to create regions plugin:', error); - return null; - } - }, []); - const plugins = useMemo(() => regionsPlugin ? [regionsPlugin] : [], [regionsPlugin]); + const regionsPlugin = useMemo(() => RegionsPlugin.create(), []); + const plugins = useMemo(() => [regionsPlugin], [regionsPlugin]); useEffect(() => { @@ -59,93 +52,47 @@ const MediaPlayer = (props) => { }); } - if (wavesurfer) { - wavesurfer.load("/silence.m4a"); - } - if (regionsPlugin) { - try { - regionsPlugin.clearRegions(); - } catch (error) { - console.warn('Error clearing regions:', error); - } - } + wavesurfer && wavesurfer.load("/silence.m4a"); + regionsPlugin.clearRegions(); - // Cleanup function - return () => { - if (regionsPlugin) { - try { - regionsPlugin.clearRegions(); - } catch (error) { - console.warn('Error clearing regions in cleanup:', error); - } - } - }; + // // In browsers that don’t yet support this functionality, + // // playPromise won’t be defined. + // if (playPromise !== undefined) { + // playPromise.then(function () { - }, [playSilence, wavesurfer, regionsPlugin]); + // }).catch(function (error) { + // console.log("Automatic playback failed: " + error); + // // Show a UI element to let the user manually start playback. + // }); + // } else { + // audio.src = false; + // } + + }, [playSilence]); useEffect(() => { setSourceIndex(0); - - // Cleanup function - return () => { - // No cleanup needed for this effect - }; }, [call]); useEffect(() => { if (wavesurfer) { wavesurfer.setVolume(volume); } - - // Cleanup function - return () => { - // No cleanup needed for this effect - }; }, [volume, wavesurfer]); - // Cleanup effect for component unmount - useEffect(() => { - return () => { - if (wavesurfer) { - try { - wavesurfer.destroy(); - } catch (error) { - console.warn('Error destroying wavesurfer:', error); - } - } - if (regionsPlugin) { - try { - regionsPlugin.clearRegions(); - } catch (error) { - console.warn('Error clearing regions in cleanup:', error); - } - } - }; - }, [wavesurfer, regionsPlugin]); - const onReady = (ws) => { setWavesurfer(ws) setIsPlaying(false) - if (regionsPlugin) { - try { - regionsPlugin.clearRegions(); - } catch (error) { - console.warn('Error clearing regions:', error); - } - } - if (call && regionsPlugin) { + regionsPlugin.clearRegions(); + if (call) { call.srcList.forEach(src => { - try { - regionsPlugin.addRegion({ - start: src.pos, - color: "rgba(128, 128, 128, 1.0)", - drag: false, - resize: false - }); - } catch (error) { - console.warn('Error adding region:', error); - } + regionsPlugin.addRegion({ + start: src.pos, + color: "rgba(128, 128, 128, 1.0)", + drag: false, + resize: false + }); }); } } From ba9224d2a4064ce4898c034189ff93b37428eeda Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Wed, 6 Aug 2025 07:28:45 -0400 Subject: [PATCH 14/27] fixed an error that allowed for duplicate shortnames --- admin/server/controllers/systems.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/admin/server/controllers/systems.js b/admin/server/controllers/systems.js index 44ef108..9dcfbd5 100644 --- a/admin/server/controllers/systems.js +++ b/admin/server/controllers/systems.js @@ -319,10 +319,14 @@ exports.uniqueShortName = async function (req, res, next) { res.status(500) res.json({ success: false, - message: "Short Name not provided in form" + message: "Shortname not provided in form" }); } + shortName = shortName.replace(/[^\w]/gi, ''); + shortName = shortName.toLowerCase(); + + try { var system = await System.findOne({ shortName: shortName.toLowerCase() @@ -333,7 +337,7 @@ exports.uniqueShortName = async function (req, res, next) { res.status(500) res.json({ success: false, - message: "Short Name already in use" + message: "Shortname already in use" }); return; } else { From 86b1f6383ddac3c665fa4594f81f251c68546046 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Thu, 25 Sep 2025 21:14:41 -0400 Subject: [PATCH 15/27] fix for cut off event calls --- frontend/src/About/AboutComponent.js | 8 +------- frontend/src/Common/SupportModal.js | 1 - frontend/src/Event/ListEventCalls.js | 2 +- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/frontend/src/About/AboutComponent.js b/frontend/src/About/AboutComponent.js index dc1c0d5..e25ebec 100644 --- a/frontend/src/About/AboutComponent.js +++ b/frontend/src/About/AboutComponent.js @@ -30,13 +30,7 @@ const AboutComponent = () => {
100% Open Source

Have changes you would like to suggest? Want to run your own server for your community? All of the code for OpenMHz is available as open source:

Trunk Server

-
Support
-

If OpenMHz brings you joy, think about becoming a supporter! It will cover hosting costs and help keep me focused on development.

- -

+
FAQ
diff --git a/frontend/src/Common/SupportModal.js b/frontend/src/Common/SupportModal.js index 9703095..3af3dfc 100644 --- a/frontend/src/Common/SupportModal.js +++ b/frontend/src/Common/SupportModal.js @@ -24,7 +24,6 @@ function SupportModal(props) {

If OpenMHz brings you joy, think about becoming a supporter! It will cover hosting costs and help keep me focused on development.

Donate - diff --git a/frontend/src/Event/ListEventCalls.js b/frontend/src/Event/ListEventCalls.js index 9581c3d..bf8c760 100644 --- a/frontend/src/Event/ListEventCalls.js +++ b/frontend/src/Event/ListEventCalls.js @@ -25,7 +25,7 @@ const ListEventCalls = (props) => { return ( - +
From 0024b711ea29e32044970d35445cef2ccd702f22 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Sat, 13 Dec 2025 22:44:30 -0500 Subject: [PATCH 16/27] fix for crash --- backend/controllers/systems.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/controllers/systems.js b/backend/controllers/systems.js index 7990bf9..70eee8f 100644 --- a/backend/controllers/systems.js +++ b/backend/controllers/systems.js @@ -153,6 +153,11 @@ exports.get_system_status = async function (req, res) { return element.shortName == shortName; }); + if (!system) { + res.status(404); + res.send("System not found"); + return; + } var now_string = new Date().toLocaleString(); var last_active_string = new Date(system.lastActive).toLocaleString(); var response = { From 9f3603e1f227fb2c02161c20fbff4174c18dbac2 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Mon, 12 Jan 2026 21:21:34 -0500 Subject: [PATCH 17/27] support for tags --- backend/models/callSchema.js | 2 +- frontend/src/Call/CallInfo.js | 2 +- frontend/src/Call/components/CallInfo.js | 2 +- frontend/src/Call/components/CallInfoPane.js | 2 +- frontend/src/Call/components/MediaPlayer.js | 4 ++++ 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/backend/models/callSchema.js b/backend/models/callSchema.js index c0e6559..4c845bf 100644 --- a/backend/models/callSchema.js +++ b/backend/models/callSchema.js @@ -1,6 +1,6 @@ var mongoose = require('mongoose'); -var srcSchema = mongoose.Schema({ pos: Number, src: String }); +var srcSchema = mongoose.Schema({ pos: Number, src: String, tag: String }); const callSchema = mongoose.Schema({ talkgroupNum: Number, diff --git a/frontend/src/Call/CallInfo.js b/frontend/src/Call/CallInfo.js index 7ba5b48..f8fb960 100644 --- a/frontend/src/Call/CallInfo.js +++ b/frontend/src/Call/CallInfo.js @@ -33,7 +33,7 @@ function CallInfo(props) { callFreq = Math.round(freq * 1000) / 1000; } - srcList = currentCall.srcList.map((source, index) => {source.src}[{source.pos}]); + srcList = currentCall.srcList.map((source, index) => {source.src}[{source.pos}] {source.tag ? "(" + source.tag + ")" : ""}); callLength = currentCall.len; talkgroupNum = currentCall.talkgroupNum; callDownload = currentCall.url; diff --git a/frontend/src/Call/components/CallInfo.js b/frontend/src/Call/components/CallInfo.js index ec677f5..fcb5348 100644 --- a/frontend/src/Call/components/CallInfo.js +++ b/frontend/src/Call/components/CallInfo.js @@ -44,7 +44,7 @@ function CallInfo(props) { } - srcList = currentCall.srcList.map((source, index) => {source.src}[{source.pos}]); + srcList = currentCall.srcList.map((source, index) => {source.src}[{source.pos}] {source.tag ? "(" + source.tag + ")" : ""}); callLength = currentCall.len; talkgroupNum = currentCall.talkgroupNum; diff --git a/frontend/src/Call/components/CallInfoPane.js b/frontend/src/Call/components/CallInfoPane.js index e8b08aa..450ccc8 100644 --- a/frontend/src/Call/components/CallInfoPane.js +++ b/frontend/src/Call/components/CallInfoPane.js @@ -51,7 +51,7 @@ function CallInfoPane(props) { const freq = currentCall.freq / 1000000; callFreq = Math.round(freq * 1000) / 1000; } - srcList = currentCall.srcList.map((source, index) => {source.src}[{source.pos}]); + srcList = currentCall.srcList.map((source, index) => {source.src}[{source.pos}] {source.tag ? "(" + source.tag + ")" : ""}); callLength = currentCall.len; talkgroupNum = currentCall.talkgroupNum; patches = currentCall.patches; diff --git a/frontend/src/Call/components/MediaPlayer.js b/frontend/src/Call/components/MediaPlayer.js index 5250e9b..7c9f861 100644 --- a/frontend/src/Call/components/MediaPlayer.js +++ b/frontend/src/Call/components/MediaPlayer.js @@ -136,10 +136,14 @@ const MediaPlayer = (props) => { let playEnabled = { "disabled": true } let sourceId = "-"; + let sourceTag = ""; if (call) { if (call.srcList.length > sourceIndex) { sourceId = call.srcList[sourceIndex].src; + if (call.srcList[sourceIndex].tag !== undefined && call.srcList[sourceIndex].tag !== null && call.srcList[sourceIndex].tag !== "") { + sourceTag = "(" + call.srcList[sourceIndex].tag + ")"; + } } playEnabled = {}; } From 75ad10eed4df3e45aa5ef936d8957f1ea272c9c4 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Mon, 12 Jan 2026 21:34:32 -0500 Subject: [PATCH 18/27] forgot the tag here --- frontend/src/Call/components/MediaPlayer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/Call/components/MediaPlayer.js b/frontend/src/Call/components/MediaPlayer.js index 7c9f861..0f60ff1 100644 --- a/frontend/src/Call/components/MediaPlayer.js +++ b/frontend/src/Call/components/MediaPlayer.js @@ -205,7 +205,7 @@ const MediaPlayer = (props) => { Sec From 319d20ea754d73d036149512e03bce0e143dd4a8 Mon Sep 17 00:00:00 2001 From: Luke Berndt Date: Mon, 12 Jan 2026 21:50:05 -0500 Subject: [PATCH 19/27] updaged formatting --- frontend/src/Call/components/MediaPlayer.css | 7 ++++++- frontend/src/Call/components/MediaPlayer.js | 4 ++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/frontend/src/Call/components/MediaPlayer.css b/frontend/src/Call/components/MediaPlayer.css index e9c4772..b6d3235 100644 --- a/frontend/src/Call/components/MediaPlayer.css +++ b/frontend/src/Call/components/MediaPlayer.css @@ -21,11 +21,16 @@ .label-item { padding: 9px 2px 5px 2px; - width: 130px; + width: 150px; color: white; font-weight: bold; text-align: center; } + .tag-label { + max-width: 75px; + overflow: clip; + text-wrap-mode: nowrap; + } .mediaplayer { flex-grow: 1; color: white; diff --git a/frontend/src/Call/components/MediaPlayer.js b/frontend/src/Call/components/MediaPlayer.js index 0f60ff1..6d4b358 100644 --- a/frontend/src/Call/components/MediaPlayer.js +++ b/frontend/src/Call/components/MediaPlayer.js @@ -142,7 +142,7 @@ const MediaPlayer = (props) => { if (call.srcList.length > sourceIndex) { sourceId = call.srcList[sourceIndex].src; if (call.srcList[sourceIndex].tag !== undefined && call.srcList[sourceIndex].tag !== null && call.srcList[sourceIndex].tag !== "") { - sourceTag = "(" + call.srcList[sourceIndex].tag + ")"; + sourceTag = call.srcList[sourceIndex].tag; } } playEnabled = {}; @@ -204,7 +204,7 @@ const MediaPlayer = (props) => { {playTime} Sec -