diff --git a/.gitignore b/.gitignore index cd2319d..9f78711 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ node_modules/ -/example.js -/config.json \ No newline at end of file +/*.json +/ffmpeg.exe \ No newline at end of file diff --git a/index.js b/index.js index b0e3bfc..4948602 100644 --- a/index.js +++ b/index.js @@ -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); diff --git a/package-lock.json b/package-lock.json index c422591..44a447d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index 56d7b7f..83331df 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/stream/index.js b/stream/index.js index 30d1e01..3dccde0 100644 --- a/stream/index.js +++ b/stream/index.js @@ -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); diff --git a/web/css/breadcrumbs.css b/web/css/breadcrumbs.css index 64557df..3956e8f 100644 --- a/web/css/breadcrumbs.css +++ b/web/css/breadcrumbs.css @@ -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; diff --git a/web/index.ejs b/web/index.ejs index e600a30..73c4aa4 100644 --- a/web/index.ejs +++ b/web/index.ejs @@ -52,8 +52,10 @@
<%- tunerDesc %> @@ -74,7 +76,7 @@
-