1
0
mirror of https://github.com/KubaPro010/fm-dx-webserver.git synced 2026-02-26 22:13:53 +01:00

admin dashboard, bugfixes, cleanup

This commit is contained in:
NoobishSVK
2024-03-19 22:16:24 +01:00
parent fa20a50e3e
commit 9499d99c7b
12 changed files with 221 additions and 259 deletions

4
.gitignore vendored
View File

@@ -1,3 +1,3 @@
node_modules/
/example.js
/config.json
/*.json
/ffmpeg.exe

View File

@@ -281,9 +281,6 @@ app.get('/static_data', (req, res) => {
});
app.get('/server_time', (req, res) => {
/*const serverTime = new Date().toISOString(); // Get server time in ISO format
const serverTimezoneOffset = new Date().getTimezoneOffset(); // Get server timezone offset in minutes*/
const serverTime = new Date(); // Get current server time
const serverTimeUTC = new Date(serverTime.getTime() - (serverTime.getTimezoneOffset() * 60000)); // Adjust server time to UTC
res.json({
@@ -577,8 +574,6 @@ app.get('/getDevices', (req, res) => {
/**
* WEBSOCKET BLOCK
*/
let lastDisconnectTime = null;
wss.on('connection', (ws, request) => {
const clientIp = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
currentUsers++;
@@ -618,40 +613,57 @@ wss.on('connection', (ws, request) => {
});
ws.on('message', (message) => {
logDebug('Command received from \x1b[90m' + clientIp + '\x1b[0m:', message.toString());
command = message.toString();
const command = message.toString();
logDebug(`Command received from \x1b[90m${clientIp}\x1b[0m: ${command}`);
if(command.startsWith('X')) {
logWarn('Remote tuner shutdown attempted by \x1b[90m' + clientIp + '\x1b[0m. You may consider blocking this user.');
return;
}
if(command.includes('\'')) {
return;
}
if(command.startsWith('T')) {
let tuneFreq = Number(command.slice(1)) / 1000;
if(serverConfig.webserver.tuningLimit === true && (tuneFreq < serverConfig.webserver.tuningLowerLimit || tuneFreq > serverConfig.webserver.tuningUpperLimit) || isNaN(tuneFreq)) {
if (command.startsWith('X')) {
logWarn(`Remote tuner shutdown attempted by \x1b[90m${clientIp}\x1b[0m. You may consider blocking this user.`);
return;
}
}
if((serverConfig.publicTuner === true) || (request.session && request.session.isTuneAuthenticated === true && serverConfig.xdrd.wirelessConnection)) {
if (command.includes("'")) {
return;
}
if(serverConfig.lockToAdmin === true) {
if(request.session && request.session.isAdminAuthenticated === true) {
serverConfig.xdrd.wirelessConnection === true ? client.write(command + "\n") : serialport.write(command + "\n");
} else {
return;
if (command.startsWith('w') && request.session.isAdminAuthenticated) {
switch (command) {
case 'wL1':
serverConfig.lockToAdmin = true;
break;
case 'wL0':
serverConfig.lockToAdmin = false;
break;
case 'wT0':
serverConfig.publicTuner = true;
break;
case 'wT1':
serverConfig.publicTuner = false;
break;
default:
break;
}
} else {
serverConfig.xdrd.wirelessConnection === true ? client.write(command + "\n") : serialport.write(command + "\n");
}
}
if (command.startsWith('T')) {
const tuneFreq = Number(command.slice(1)) / 1000;
const { tuningLimit, tuningLowerLimit, tuningUpperLimit } = serverConfig.webserver;
if (tuningLimit && (tuneFreq < tuningLowerLimit || tuneFreq > tuningUpperLimit) || isNaN(tuneFreq)) {
return;
}
}
const { isAdminAuthenticated, isTuneAuthenticated } = request.session || {};
const { wirelessConnection } = serverConfig.xdrd;
if ((serverConfig.publicTuner || (isTuneAuthenticated && wirelessConnection)) &&
(!serverConfig.lockToAdmin || isAdminAuthenticated)) {
const output = serverConfig.xdrd.wirelessConnection ? client : serialport;
output.write(`${command}\n`);
}
});
ws.on('close', (code, reason) => {
currentUsers--;
dataHandler.showOnlineUsers(currentUsers);
@@ -683,7 +695,6 @@ wss.on('connection', (ws, request) => {
});
// CHAT WEBSOCKET BLOCK
// Assuming chatWss is your WebSocket server instance
// Initialize an array to store chat messages
let chatHistory = [];
@@ -713,22 +724,20 @@ chatWss.on('connection', (ws, request) => {
messageData.time = `${hours}:${minutes}`; // Adding current time to the message object in hours:minutes format
if (serverConfig.webserver.banlist?.includes(clientIp)) {
return; // Do not proceed further if banned
return;
}
if(request.session.isAdminAuthenticated === true) {
messageData.admin = true;
}
// Limit message length to 255 characters
if (messageData.message.length > 255) {
messageData.message = messageData.message.substring(0, 255);
}
// Add the new message to chat history and keep only the latest 50 messages
chatHistory.push(messageData);
if (chatHistory.length > 50) {
chatHistory.shift(); // Remove the oldest message if the history exceeds 50 messages
chatHistory.shift();
}
const modifiedMessage = JSON.stringify(messageData);

186
package-lock.json generated
View File

@@ -1,29 +1,28 @@
{
"name": "fm-dx-webserver",
"version": "1.1.3",
"version": "1.1.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "fm-dx-webserver",
"version": "1.1.3",
"version": "1.1.5",
"license": "ISC",
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.11",
"body-parser": "^1.20.2",
"command-exists-promise": "^2.0.2",
"ejs": "^3.1.9",
"express": "4.18.2",
"express-session": "^1.18.0",
"http": "^0.0.1-security",
"http-proxy": "^1.18.1",
"@mapbox/node-pre-gyp": "1.0.11",
"body-parser": "1.20.2",
"command-exists-promise": "2.0.2",
"ejs": "3.1.9",
"express": "4.18.3",
"express-session": "1.18.0",
"http": "0.0.1-security",
"http-proxy": "1.18.1",
"https": "1.0.0",
"koffi": "2.7.2",
"net": "1.0.2",
"serialport": "^12.0.0",
"websocket": "1.0.34",
"wrtc": "^0.4.7",
"ws": "^8.14.2"
"serialport": "12.0.0",
"wrtc": "0.4.7",
"ws": "8.14.2"
}
},
"node_modules/@mapbox/node-pre-gyp": {
@@ -447,6 +446,8 @@
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz",
"integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==",
"hasInstallScript": true,
"optional": true,
"peer": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
@@ -572,15 +573,6 @@
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
},
"node_modules/d": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
"integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
"dependencies": {
"es5-ext": "^0.10.50",
"type": "^1.0.1"
}
},
"node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
@@ -674,39 +666,6 @@
"node": ">= 0.8"
}
},
"node_modules/es5-ext": {
"version": "0.10.62",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz",
"integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==",
"hasInstallScript": true,
"dependencies": {
"es6-iterator": "^2.0.3",
"es6-symbol": "^3.1.3",
"next-tick": "^1.1.0"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/es6-iterator": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
"integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
"dependencies": {
"d": "1",
"es5-ext": "^0.10.35",
"es6-symbol": "^3.1.1"
}
},
"node_modules/es6-symbol": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz",
"integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
"dependencies": {
"d": "^1.0.1",
"ext": "^1.1.2"
}
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
@@ -726,13 +685,13 @@
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
},
"node_modules/express": {
"version": "4.18.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
"integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
"version": "4.18.3",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.3.tgz",
"integrity": "sha512-6VyCijWQ+9O7WuVMTRBTl+cjNNIzD5cY5mQ1WM8r/LEkI2u8EYpOotESNwzNlyCn3g+dmjKYI6BmNneSr/FSRw==",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.1",
"body-parser": "1.20.2",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.5.0",
@@ -797,56 +756,6 @@
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz",
"integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA=="
},
"node_modules/express/node_modules/body-parser": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
"integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.4",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
"raw-body": "2.5.1",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/express/node_modules/raw-body": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/ext": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz",
"integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
"dependencies": {
"type": "^2.7.2"
}
},
"node_modules/ext/node_modules/type": {
"version": "2.7.2",
"resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz",
"integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw=="
},
"node_modules/filelist": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
@@ -1193,11 +1102,6 @@
"node": ">=8"
}
},
"node_modules/is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="
},
"node_modules/jake": {
"version": "10.8.7",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.8.7.tgz",
@@ -1376,11 +1280,6 @@
"resolved": "https://registry.npmjs.org/net/-/net-1.0.2.tgz",
"integrity": "sha512-kbhcj2SVVR4caaVnGLJKmlk2+f+oLkjqdKeQlmUtz6nGzOpbcobwVIeSURNgraV/v3tlmGIX82OcPCl0K6RbHQ=="
},
"node_modules/next-tick": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
},
"node_modules/node-addon-api": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.0.0.tgz",
@@ -1409,6 +1308,8 @@
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.1.tgz",
"integrity": "sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==",
"optional": true,
"peer": true,
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
@@ -1617,9 +1518,9 @@
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/semver": {
"version": "7.5.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz",
"integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==",
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dependencies": {
"lru-cache": "^6.0.0"
},
@@ -1842,11 +1743,6 @@
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"node_modules/type": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
"integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg=="
},
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
@@ -1859,14 +1755,6 @@
"node": ">= 0.6"
}
},
"node_modules/typedarray-to-buffer": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
"integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
"dependencies": {
"is-typedarray": "^1.0.0"
}
},
"node_modules/uid-safe": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
@@ -1891,6 +1779,8 @@
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
"hasInstallScript": true,
"optional": true,
"peer": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
@@ -1925,22 +1815,6 @@
"integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==",
"optional": true
},
"node_modules/websocket": {
"version": "1.0.34",
"resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.34.tgz",
"integrity": "sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==",
"dependencies": {
"bufferutil": "^4.0.1",
"debug": "^2.2.0",
"es5-ext": "^0.10.50",
"typedarray-to-buffer": "^3.1.5",
"utf-8-validate": "^5.0.2",
"yaeti": "^0.0.6"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
@@ -2006,14 +1880,6 @@
}
}
},
"node_modules/yaeti": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
"integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==",
"engines": {
"node": ">=0.10.32"
}
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "fm-dx-webserver",
"version": "1.1.4",
"version": "1.1.5",
"description": "",
"main": "index.js",
"scripts": {
@@ -12,20 +12,19 @@
"author": "",
"license": "ISC",
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.11",
"body-parser": "^1.20.2",
"command-exists-promise": "^2.0.2",
"ejs": "^3.1.9",
"express": "4.18.2",
"express-session": "^1.18.0",
"http": "^0.0.1-security",
"http-proxy": "^1.18.1",
"@mapbox/node-pre-gyp": "1.0.11",
"body-parser": "1.20.2",
"command-exists-promise": "2.0.2",
"ejs": "3.1.9",
"express": "4.18.3",
"express-session": "1.18.0",
"http": "0.0.1-security",
"http-proxy": "1.18.1",
"https": "1.0.0",
"koffi": "2.7.2",
"net": "1.0.2",
"serialport": "^12.0.0",
"websocket": "1.0.34",
"wrtc": "^0.4.7",
"ws": "^8.14.2"
"serialport": "12.0.0",
"wrtc": "0.4.7",
"ws": "8.14.2"
}
}

View File

@@ -17,7 +17,7 @@ function enableAudioStream() {
ffmpegCommand = `${flags} -f dshow -audio_buffer_size 50 -i audio="${serverConfig.audio.audioDevice}" ${codec} ${output} pipe:1 | node stream/3las.server.js -port ${serverConfig.webserver.webserverPort + 10} -samplerate 48000 -channels ${serverConfig.audio.audioChannels}`;
} else {
// Linux
ffmpegCommand = `${flags} -f alsa -i "${serverConfig.audio.audioDevice}" ${codec} ${output} pipe:1 | node stream/3las.server.js -port ${serverConfig.webserver.webserverPort + 10} -samplerate 48000 -channels ${serverConfig.audio.audioChannels}`;
ffmpegCommand = `${flags} -f alsa -i "${serverConfig.audio.softwareMode && serverConfig.audio.softwareMode == true ? 'plug' : ''}${serverConfig.audio.audioDevice}" ${codec} ${output} pipe:1 | node stream/3las.server.js -port ${serverConfig.webserver.webserverPort + 10} -samplerate 48000 -channels ${serverConfig.audio.audioChannels}`;
}
consoleCmd.logInfo("Using audio device: " + serverConfig.audio.audioDevice);

View File

@@ -50,7 +50,7 @@ h4 {
border-radius: 30px;
padding: 5px 25px;
z-index: 1000;
opacity:var(--color-main);
opacity: 0;
transition: opacity 0.3s ease-in-out;
}
@@ -248,6 +248,42 @@ label {
opacity: 0; /* Make the overlay invisible */
}
.admin-quick-dashboard {
position: absolute;
top: 0;
bottom: 0;
left: -96px;
width: 96px;
height: 286px;
background-color: var(--color-2);
margin: auto 0;
border-radius: 30px;
padding-top: 15px;
}
.admin-quick-dashboard .icon {
width: 72px;
height: 72px;
margin: 10px auto;
color: var(--color-1);
background-color: var(--color-3);
display: flex;
justify-content: center;
align-items: center;
font-size: 24px;
border-radius: 10px;
transition: 0.3s ease-in-out background-color;
cursor: pointer;
}
.admin-quick-dashboard .icon.active {
background-color: var(--color-4);
}
.admin-quick-dashboard .icon:hover {
background-color: var(--color-5);
}
@media (max-width: 768px) {
canvas, #flags-container {
display: none;

View File

@@ -52,8 +52,10 @@
<audio id="audioTag"></audio>
<div id="wrapper">
<div class="panel-100 no-bg tuner-info">
<h1 id="tuner-name"><%= tunerName %> <% if (!publicTuner) { %><i class="fa-solid fa-key pointer" title="Only people with tune password can tune."></i>
<% } else if (tunerLock) { %><i class="fa-solid fa-lock pointer" title="Tuner is currently locked to admin."></i><% } %>
<h1 id="tuner-name"><%= tunerName %>
<% if (!publicTuner) { %><i class="fa-solid fa-key pointer tooltip" data-tooltip="Only people with tune password can tune."></i>
<% } if (tunerLock) { %><i class="fa-solid fa-lock pointer tooltip" data-tooltip="Tuner is currently locked to admin."></i>
<% } %>
</h1>
<p id="tuner-desc">
<%- tunerDesc %>
@@ -74,7 +76,7 @@
<div class="panel-10 no-bg h-100 m-0 m-right-20 hide-phone" style="width: 100px;margin-right: 20px !important;">
<button class="playbutton" aria-label="Play / Stop Button"><i class="fa-solid fa-play fa-lg"></i></button>
</div>
<div class="panel-100 m-0 hover-brighten flex-center" id="ps-container" style="height: 90px;" data-tooltip="Clicking on the RDS PS will copy the RDS info into the clipboard.">
<div class="panel-100 m-0 hover-brighten flex-center tooltip" id="ps-container" style="height: 90px;" data-tooltip="Clicking on the RDS PS will copy the RDS info into the clipboard.">
<span class="text-big" id="data-ps"></span>
</div>
</div>
@@ -91,7 +93,7 @@
</div>
<span id="stereo-container" class="pointer" style="position: relative;">
<span style="margin-left: 20px;" class="data-st">ST</span>
<span class="overlay" data-tooltip="Stereo / Mono toggle. <br><strong>Click to toggle."></span>
<span class="overlay tooltip" data-tooltip="Stereo / Mono toggle. <br><strong>Click to toggle."></span>
</span>
<span style="margin-left: 15px;" class="data-ms">MS</span>
</h3>
@@ -99,7 +101,7 @@
</div>
<div class="flex-container">
<div class="panel-33 hover-brighten" id="pi-code-container" data-tooltip="Clicking on the PI code will show the current station on a map.">
<div class="panel-33 hover-brighten tooltip" id="pi-code-container" data-tooltip="Clicking on the PI code will show the current station on a map.">
<h2>PI CODE</h2>
<span id="data-pi" class="text-big text-uppercase"></span>
</div>
@@ -144,12 +146,12 @@
<% } %>
<div class="panel-100 no-bg h-100 m-0 button-eq">
<% if (device == 'tef') { %><button id="data-eq" style="border-radius: 30px 0px 0px 30px;" aria-label="EQ Filter" data-tooltip="<strong>The cEQ filter can reduce bandwidth below 56 KHz.</strong><br><br>Useful for weak stations next to strong ones,<br>although it may pick up more interference."><span class="text-bold">cEQ</span></button><% } %>
<% if (device == 'xdr') { %><button id="data-eq" style="border-radius: 30px 0px 0px 30px;" aria-label="RF+ Filter" data-tooltip="<strong>The RF+ filter increases gain by 5dB</strong>"><span class="text-bold">RF+</span></button><% } %>
<% if (device == 'tef') { %><button id="data-eq" style="border-radius: 30px 0px 0px 30px;" class="tooltip" aria-label="EQ Filter" data-tooltip="<strong>The cEQ filter can reduce bandwidth below 56 KHz.</strong><br><br>Useful for weak stations next to strong ones,<br>although it may pick up more interference."><span class="text-bold">cEQ</span></button><% } %>
<% if (device == 'xdr') { %><button id="data-eq" style="border-radius: 30px 0px 0px 30px;" class="tooltip" aria-label="RF+ Filter" data-tooltip="<strong>The RF+ filter increases gain by 5dB</strong>"><span class="text-bold">RF+</span></button><% } %>
</div>
<div class="panel-100 no-bg h-100 m-0 button-ims">
<% if (device == 'tef') { %><button id="data-ims" style="border-radius: 0px 30px 30px 0px;" aria-label="iMS + Filter" data-tooltip="<strong>The iMS filter reduces multipath audio artifacts.</strong><br><br>It's recommended to leave it on most of the time."><span class="text-bold">iMS</span></button><% } %>
<% if (device == 'xdr') { %><button id="data-ims" style="border-radius: 0px 30px 30px 0px;" aria-label="IF+ Filter" data-tooltip="<strong>The IF+ filter increases gain by 6dB</strong>"><span class="text-bold">IF+</span></button><% } %>
<% if (device == 'tef') { %><button id="data-ims" style="border-radius: 0px 30px 30px 0px;" class="tooltip" aria-label="iMS + Filter" data-tooltip="<strong>The iMS filter reduces multipath audio artifacts.</strong><br><br>It's recommended to leave it on most of the time."><span class="text-bold">iMS</span></button><% } %>
<% if (device == 'xdr') { %><button id="data-ims" style="border-radius: 0px 30px 30px 0px;" class="tooltip" aria-label="IF+ Filter" data-tooltip="<strong>The IF+ filter increases gain by 6dB</strong>"><span class="text-bold">IF+</span></button><% } %>
</div>
</div>
</div>
@@ -173,7 +175,7 @@
<hr class="hide-desktop">
</div>
<div class="panel-33 hover-brighten" data-tooltip="This panel contains the current TX info when RDS is loaded.<br><strong>Clicking on this panel copies the info into the clipboard.</strong>">
<div class="panel-33 hover-brighten tooltip" data-tooltip="This panel contains the current TX info when RDS is loaded.<br><strong>Clicking on this panel copies the info into the clipboard.</strong>">
<div id="data-station-container">
<h2 style="margin-top: 0;" class="mb-0">
<span id="data-station-name"></span>
@@ -228,6 +230,20 @@
<% } %>
<button id="users-online-container" class="hide-phone" aria-label="Online users"><i class="fa-solid fa-user"></i> <span class="users-online"></span></button>
<% if (isAdminAuthenticated) { %>
<div class="admin-quick-dashboard hide-phone">
<div class="icon tooltip <% if (tunerLock) { %>active<% } %>" id="dashboard-lock-admin" onClick="toggleAdminLock()" data-tooltip="Toggle admin lock<br>Lasts until restart">
<i class="fa-solid fa-lock"></i>
</div>
<div class="icon tooltip <% if (!publicTuner) { %>active<% } %>" id="dashboard-lock-tune" onClick="togglePasswordLock()" data-tooltip="Toggle password lock<br>Lasts until restart">
<i class="fa-solid fa-key"></i>
</div>
<div class="icon tooltip" data-tooltip="Go to admin menu" onClick="window.open('./setup', '_blank').focus();">
<i class="fa-solid fa-user"></i>
</div>
</div>
<% } %>
<div id="myModal" class="modal">
<div class="modal-panel">
<div class="flex-container flex-phone" style="height: calc(100% - 100px)">
@@ -285,7 +301,7 @@
<% if (isAdminAuthenticated) { %>
<p>You are logged in as an adminstrator.<br>
<a href="./setup">Setup</a> • <a class="logout-link" href="#">Logout</a>
<a href="./setup" target="_blank">Setup</a> • <a class="logout-link" href="#">Logout</a>
</p>
<% } else if (isTuneAuthenticated) { %>
<p>You are logged in and can control the receiver.<br><a class="logout-link" href="#">Logout</a></p>
@@ -360,9 +376,5 @@
</div>
<script src="js/webserver.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/screenshot-capture/2.0.0/screenshot-capture.min.js"></script>
</body>
</html>

View File

@@ -45,6 +45,8 @@ function submitData() {
return $(this).text() === $('#device-type').val();
}).data('value') || "tef");
const softwareMode = $('#audio-software-mode').is("checked") || false;
const tunerName = $('#webserver-name').val() || 'FM Tuner';
const tunerDesc = $('#webserver-desc').val() || 'Default FM tuner description';
const broadcastTuner = $("#broadcast-tuner").is(":checked");
@@ -84,6 +86,7 @@ function submitData() {
audioDevice,
audioChannels,
audioBitrate,
softwareMode,
},
identification: {
tunerName,
@@ -202,6 +205,8 @@ function submitData() {
$("#audio-quality").val(selectedQuality.text());
}
$('#audio-software-switch').prop("checked", data.audio.softwareMode || false);
$('#webserver-name').val(data.identification.tunerName);
$('#webserver-desc').val(data.identification.tunerDesc);
$("#broadcast-tuner").prop("checked", data.identification.broadcastTuner);

View File

@@ -28,6 +28,21 @@ const usa_programmes = [
$(document).ready(function () {
var canvas = $('#signal-canvas')[0];
var $panel = $('.admin-quick-dashboard');
var panelWidth = $panel.outerWidth();
$(document).mousemove(function(e) {
var mouseX = e.pageX;
var panelLeft = parseInt($panel.css('left'));
if (mouseX <= 10 || (panelLeft === 4 && mouseX <= 100)) {
$panel.css('left', '4px');
} else {
$panel.css('left', -panelWidth);
}
});
canvas.width = canvas.parentElement.clientWidth;
canvas.height = canvas.parentElement.clientHeight;
@@ -49,9 +64,9 @@ $(document).ready(function () {
const textInput = $('#commandinput');
textInput.on('change', function (event) {
const inputValue = textInput.val();
const inputValue = Number(textInput.val());
// Check if the user agent contains 'iPhone'
if (/iPhone/i.test(navigator.userAgent) && socket.readyState === WebSocket.OPEN) {
if (/iPhone/i.test(navigator.userAgent)) {
socket.send("T" + (inputValue * 1000));
// Clear the input field if needed
textInput.val('');
@@ -384,26 +399,7 @@ function checkKey(e) {
case 82: // RDS Reset (R key)
tuneTo(Number(currentFreq));
break;
case 83: // Screenshot (S key)
screenshotCapture.capture().then(function (dataUrl) {
// Create an input element to hold the data URL temporarily
var aux = $('<input>').attr({
type: 'text',
value: dataUrl
});
// Append the input element to the body, select its contents, and copy them to the clipboard
$('body').append(aux);
aux.select();
document.execCommand('copy');
aux.remove();
// Alert the user that the screenshot has been copied to the clipboard
alert('Screenshot copied to clipboard!');
}).catch(function (error) {
console.error('Error capturing screenshot:', error);
});
case 83: // Screenshot (S key)
break;
case 38:
socket.send("T" + (Math.round(currentFreq*1000) + ((currentFreq > 30) ? 10 : 1)));
@@ -758,19 +754,51 @@ function toggleForcedStereo() {
socket.send(message);
}
function toggleAdminLock() {
let $adminLockButton = $('#dashboard-lock-admin');
if($adminLockButton.hasClass('active')) {
socket.send('wL0');
$adminLockButton.removeClass('active');
} else {
socket.send('wL1');
$adminLockButton.addClass('active');
}
}
function togglePasswordLock() {
let $passwordLockButton = $('#dashboard-lock-tune');
if($passwordLockButton.hasClass('active')) {
socket.send('wT0');
$passwordLockButton.removeClass('active');
} else {
socket.send('wT1');
$passwordLockButton.addClass('active');
}
}
function initTooltips() {
$('[data-tooltip]').hover(function(e){
$('.tooltip').hover(function(e){
var tooltipText = $(this).data('tooltip');
var tooltip = $('<div class="tooltiptext"></div>').html(tooltipText);
$('body').append(tooltip);
var tooltipWidth = tooltip.outerWidth();
var tooltipHeight = tooltip.outerHeight();
var posX = e.pageX - tooltipWidth / 2;
var posY = e.pageY - tooltipHeight - 10;
// Add a delay of 500 milliseconds before creating and appending the tooltip
$(this).data('timeout', setTimeout(() => {
var tooltip = $('<div class="tooltiptext"></div>').html(tooltipText);
$('body').append(tooltip);
tooltip.css({ top: posY, left: posX, opacity: 0.9 });
var posX = e.pageX;
var posY = e.pageY;
var tooltipWidth = tooltip.outerWidth();
var tooltipHeight = tooltip.outerHeight();
posX -= tooltipWidth / 2;
posY -= tooltipHeight + 10;
tooltip.css({ top: posY, left: posX, opacity: 1 }); // Set opacity to 1
}, 500));
}, function() {
// Clear the timeout if the mouse leaves before the delay completes
clearTimeout($(this).data('timeout'));
$('.tooltiptext').remove();
}).mousemove(function(e){
var tooltipWidth = $('.tooltiptext').outerWidth();

View File

@@ -3,7 +3,7 @@ var day = currentDate.getDate();
var month = currentDate.getMonth() + 1; // Months are zero-indexed, so add 1
var year = currentDate.getFullYear();
var formattedDate = day + '/' + month + '/' + year;
var currentVersion = 'v1.1.4 [' + formattedDate + ']';
var currentVersion = 'v1.1.5 [' + formattedDate + ']';
/**

View File

@@ -237,6 +237,11 @@
</ul>
</div>
</div>
<div class="form-group checkbox bottom-20">
<input type="checkbox" id="audio-software-switch">
<label for="audio-software-switch">ALSA software mode (plughw) - LINUX ONLY</label>
</div>
</div>
</div>
@@ -352,6 +357,7 @@
<li class="option" data-value="tef">TEF668x / TEA685x</li>
<li class="option" data-value="xdr">XDR (F1HD / S10HDiP)</li>
<li class="option" data-value="sdr">SDR (RTL-SDR / AirSpy)</li>
<li class="option" data-value="sdr">Other</li>
</ul>
</div>
</div>

View File

@@ -61,6 +61,7 @@
<li class="option" data-value="tef">TEF668x / TEA685x</li>
<li class="option" data-value="xdr">XDR (F1HD / S10HDiP)</li>
<li class="option" data-value="sdr">SDR (RTL-SDR / AirSpy)</li>
<li class="option" data-value="other">Other</li>
</ul>
</div>
</div>