From 67ff5af34153ef2f8a6c95a4b69e09e533ecf84b Mon Sep 17 00:00:00 2001 From: NoobishSVK Date: Fri, 1 Mar 2024 14:15:05 +0100 Subject: [PATCH] chat, theme changes, bugfixes --- index.js | 128 +++++++++++++++++++++++++++++++++++++--- package.json | 2 +- server_config.js | 10 +++- web/css/breadcrumbs.css | 43 +++++++++++++- web/css/main.css | 57 +++++++++++++++--- web/css/modal.css | 29 ++++++++- web/css/panels.css | 11 ++-- web/index.ejs | 72 ++++++++++++++++------ web/js/chat.js | 101 +++++++++++++++++++++++++++++++ web/js/confighandler.js | 44 ++++++++++++++ web/js/init.js | 5 +- web/js/main.js | 30 ++++++++-- web/js/modal.js | 53 ++++++++++++----- web/js/settings.js | 14 ++--- web/js/webserver.js | 1 + web/setup.ejs | 94 ++++++++++++++++++++++++++--- 16 files changed, 612 insertions(+), 82 deletions(-) create mode 100644 web/js/chat.js diff --git a/index.js b/index.js index 08c95d3..3ee74b4 100644 --- a/index.js +++ b/index.js @@ -16,6 +16,7 @@ const process = require("process"); // Websocket handling const WebSocket = require('ws'); const wss = new WebSocket.Server({ noServer: true }); +const chatWss = new WebSocket.Server({ noServer: true }); const path = require('path'); const net = require('net'); const client = new net.Socket(); @@ -31,6 +32,19 @@ const audioStream = require('./stream/index.js'); const { parseAudioDevice } = require('./stream/parser.js'); const { configName, serverConfig, configUpdate, configSave } = require('./server_config'); const { logDebug, logError, logInfo, logWarn } = consoleCmd; +var pjson = require('./package.json'); + +console.log(`\x1b[32m + _____ __ __ ______ __ __ __ _ +| ___| \\/ | | _ \\ \\/ / \\ \\ / /__| |__ ___ ___ _ ____ _____ _ __ +| |_ | |\\/| |_____| | | \\ / \\ \\ /\\ / / _ \\ '_ \\/ __|/ _ \\ '__\\ \\ / / _ \\ '__| +| _| | | | |_____| |_| / \\ \\ V V / __/ |_) \\__ \\ __/ | \\ V / __/ | +|_| |_| |_| |____/_/\\_\\ \\_/\\_/ \\___|_.__/|___/\\___|_| \\_/ \\___|_| +`); +console.log('\x1b[0mFM-DX-Webserver', pjson.version); +console.log('\x1b[90m======================================================'); + + // Create a WebSocket proxy instance const proxy = httpProxy.createProxyServer({ @@ -40,6 +54,7 @@ const proxy = httpProxy.createProxyServer({ }); let currentUsers = 0; +let connectedUsers = []; let streamEnabled = false; let incompleteDataBuffer = ''; @@ -225,7 +240,8 @@ app.get('/static_data', (req, res) => { res.json({ qthLatitude: serverConfig.identification.lat, qthLongitude: serverConfig.identification.lon, - streamEnabled: streamEnabled + streamEnabled: streamEnabled, + presets: serverConfig.webserver.presets || [] }); }); @@ -325,7 +341,11 @@ app.get('/', (req, res) => { tunerDescMeta: removeMarkdown(serverConfig.identification.tunerDesc), tunerLock: serverConfig.lockToAdmin, publicTuner: serverConfig.publicTuner, - antennaSwitch: serverConfig.antennaSwitch + ownerContact: serverConfig.identification.contact, + antennaSwitch: serverConfig.antennaSwitch, + tuningLimit: serverConfig.webserver.tuningLimit, + tuningLowerLimit: serverConfig.webserver.tuningLowerLimit, + tuningUpperLimit: serverConfig.webserver.tuningUpperLimit }) } }); @@ -351,7 +371,8 @@ app.get('/setup', (req, res) => { memoryUsage: (process.memoryUsage.rss() / 1024 / 1024).toFixed(1) + ' MB', processUptime: formattedProcessUptime, consoleOutput: consoleCmd.logs, - onlineUsers: dataHandler.dataToSend.users + onlineUsers: dataHandler.dataToSend.users, + connectedUsers: connectedUsers }); }); }); @@ -475,9 +496,17 @@ wss.on('connection', (ws, request) => { response.on('end', () => { try { const locationInfo = JSON.parse(data); + const options = { year: 'numeric', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit' }; + const connectionTime = new Date().toLocaleString([], options); + if(locationInfo.country === undefined) { + const userData = { ip: clientIp, location: 'Unknown', time: connectionTime }; + connectedUsers.push(userData); logInfo(`Web client \x1b[32mconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]\x1b[0m`); } else { + const userLocation = `${locationInfo.city}, ${locationInfo.region}, ${locationInfo.country}`; + const userData = { ip: clientIp, location: userLocation, time: connectionTime }; + connectedUsers.push(userData); logInfo(`Web client \x1b[32mconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]\x1b[0m Location: ${locationInfo.city}, ${locationInfo.region}, ${locationInfo.country}`); } } catch (error) { @@ -495,6 +524,14 @@ wss.on('connection', (ws, request) => { 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)) { + return; + } + } + if((serverConfig.publicTuner === true) || (request.session && request.session.isTuneAuthenticated === true)) { if(serverConfig.lockToAdmin === true) { @@ -512,15 +549,85 @@ wss.on('connection', (ws, request) => { ws.on('close', (code, reason) => { currentUsers--; dataHandler.showOnlineUsers(currentUsers); - if(currentUsers === 0 && serverConfig.autoShutdown === true) { - client.write('X\n'); + + // Find the index of the user's data in connectedUsers array + const index = connectedUsers.findIndex(user => user.ip === clientIp); + if (index !== -1) { + connectedUsers.splice(index, 1); // Remove the user's data from connectedUsers array + } + + if (currentUsers === 0 && serverConfig.autoShutdown === true) { + client.write('X\n'); } logInfo(`Web client \x1b[31mdisconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]`); - }); + }); ws.on('error', console.error); }); +// CHAT WEBSOCKET BLOCK +// Assuming chatWss is your WebSocket server instance +// Initialize an array to store chat messages +let chatHistory = []; + +chatWss.on('connection', (ws, request) => { + const clientIp = request.headers['x-forwarded-for'] || request.connection.remoteAddress; + + // Send chat history to the newly connected client + chatHistory.forEach(function(message) { + message.history = true; // Adding the history parameter + ws.send(JSON.stringify(message)); + }); + + const ipMessage = { + type: 'clientIp', + ip: clientIp, + admin: request.session.isAdminAuthenticated + }; + ws.send(JSON.stringify(ipMessage)); + + ws.on('message', function incoming(message) { + const messageData = JSON.parse(message); + messageData.ip = clientIp; // Adding IP address to the message object + const currentTime = new Date(); + + const hours = String(currentTime.getHours()).padStart(2, '0'); + const minutes = String(currentTime.getMinutes()).padStart(2, '0'); + 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 + } + + 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 + } + + const modifiedMessage = JSON.stringify(messageData); + + chatWss.clients.forEach(function each(client) { + if (client.readyState === WebSocket.OPEN) { + client.send(modifiedMessage); + } + }); +}); + + ws.on('close', function close() { + }); +}); + + // Handle upgrade requests to /text and proxy /audio WebSocket connections httpServer.on('upgrade', (request, socket, head) => { if (request.url === '/text') { @@ -531,11 +638,16 @@ httpServer.on('upgrade', (request, socket, head) => { }); } else if (request.url === '/audio') { proxy.ws(request, socket, head); + } else if (request.url === '/chat') { + sessionMiddleware(request, {}, () => { + chatWss.handleUpgrade(request, socket, head, (ws) => { + chatWss.emit('connection', ws, request); + }); + }); } else { socket.destroy(); } -} -); +}); /* Serving of HTML files */ app.use(express.static(path.join(__dirname, 'web'))); diff --git a/package.json b/package.json index e421a8b..1856a0c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fm-dx-webserver", - "version": "1.1.0", + "version": "1.1.1", "description": "", "main": "index.js", "scripts": { diff --git a/server_config.js b/server_config.js index bc78bdf..b82f687 100644 --- a/server_config.js +++ b/server_config.js @@ -13,7 +13,8 @@ if (index !== -1 && index + 1 < process.argv.length) { let serverConfig = { webserver: { webserverIp: "0.0.0.0", - webserverPort: 8080 + webserverPort: 8080, + banlist: [] }, xdrd: { xdrdIp: "127.0.0.1", @@ -55,9 +56,16 @@ function deepMerge(target, source) } function configUpdate(newConfig) { + if (newConfig.webserver && newConfig.webserver.banlist !== undefined) { + // If new banlist is provided, replace the existing one + serverConfig.webserver.banlist = newConfig.webserver.banlist; + delete newConfig.webserver.banlist; // Remove banlist from newConfig to avoid merging + } + deepMerge(serverConfig, newConfig); } + function configSave() { fs.writeFile(configName + '.json', JSON.stringify(serverConfig, null, 2), (err) => { if (err) { diff --git a/web/css/breadcrumbs.css b/web/css/breadcrumbs.css index ed9d0c0..062f26c 100644 --- a/web/css/breadcrumbs.css +++ b/web/css/breadcrumbs.css @@ -102,6 +102,23 @@ label { cursor: pointer; } +.chatbutton.hide-desktop { + background: transparent; + border: 0; + color: var(--color-text); + position: absolute; + top: 15px; + left: 15px; + font-size: 16px; + width: 64px; + height: 64px; + line-height: 64px; + text-align: center; + border-radius: 50%; + transition: 500ms ease-in-out background; + cursor: pointer; +} + #settings:hover, #back-btn:hover, #users-online-container:hover { background: var(--color-3); } @@ -199,6 +216,9 @@ label { canvas, #flags-container { display: none; } + #tuner-desc { + margin-bottom: 20px !important; + } #ps-container { background-color: var(--color-1); height: 100px !important; @@ -213,8 +233,8 @@ label { } #data-pi { font-size: 24px; - margin-top: 50px; - color: var(--color-text-2) + margin-top: 20px; + color: var(--color-text-2); } h2.show-phone { display: inline; @@ -230,6 +250,7 @@ label { font-size: 10px; text-align: left; width: 100%; + word-break: break-all; } #rt-container { height: 32px !important; @@ -272,6 +293,13 @@ label { .tuner-info { margin-bottom: -60px !important; } + #af-list ul { + height: auto !important; + } + + #rt-container { + order: 2; + } } @media only screen and (min-width: 769px) and (max-height: 860px) { @@ -287,6 +315,12 @@ label { .tuner-info #tuner-name { float: left; font-size: 24px; + text-align: left; + } + + .tuner-info #tuner-limit { + float: left; + text-align: left; } .tuner-info #tuner-desc { @@ -303,6 +337,9 @@ label { margin-top: 2px !important; } #af-list ul { - max-height: 330px; + height: 225px !important; + } + .chatbutton { + height: 86px !important; } } \ No newline at end of file diff --git a/web/css/main.css b/web/css/main.css index c430a97..29de039 100644 --- a/web/css/main.css +++ b/web/css/main.css @@ -64,15 +64,6 @@ body { transform: none; } -@media (max-width: 1180px) { - #wrapper { - position: static; - transform: none; - margin: 50px auto; - width: 100%; - } -} - a { text-decoration: none; color: var(--color-text-2); @@ -80,4 +71,52 @@ a { a:hover { border-bottom: 1px solid var(--color-4); +} + +hr { + color: var(--color-4); +} + +table { + border-radius: 30px; + background-color: var(--color-2); + padding: 20px; + margin: auto; +} + +table th { + padding: 8px 20px; + outline: 1px solid var(--color-3); + background-color: var(--color-3); +} + +table td { + padding: 8px 20px; +} + +table td:nth-child(1) { + text-align: left; +} + +table td:nth-child(2) { + color: var(--color-main-bright); + text-align: left; +} + + +table th:nth-child(1) { + border-radius: 30px 0px 0px 30px; +} + +table th:nth-last-child(1){ + border-radius: 0px 30px 30px 0px; +} + +@media (max-width: 1180px) { + #wrapper { + position: static; + transform: none; + margin: 50px auto; + width: 100%; + } } \ No newline at end of file diff --git a/web/css/modal.css b/web/css/modal.css index 4c8cd8a..a3bc0d9 100644 --- a/web/css/modal.css +++ b/web/css/modal.css @@ -74,6 +74,7 @@ position: absolute; right: 0; text-align: center; + display: none; background-color: var(--color-main); } @@ -122,6 +123,26 @@ margin: auto; } +.modal-panel-chat { + width: 100%; + max-width: 960px; + height: 450px; + position: absolute; + bottom: 0; + margin: auto; + left: 0; + right: 0; + text-align: center; + display: none; + background-color: var(--color-main); + border-radius: 30px 30px 0px 0px; +} + +.modal-panel-chat .modal-panel-sidebar { + width: 100%; + border-radius: 30px 30px 0px 0px; +} + @media only screen and (max-width: 768px) { .modal-content { min-width: 90% !important; @@ -134,12 +155,18 @@ .modal-title { position: static; } - #closeModalButton { + .closeModalButton { position: static; } .modal-panel { width: 100%; } + .modal-panel-chat { + height: 500px; + } + #chat-chatbox { + height: 333px !important; + } } @media only screen and (max-height: 768px) { diff --git a/web/css/panels.css b/web/css/panels.css index 53571fb..0993861 100644 --- a/web/css/panels.css +++ b/web/css/panels.css @@ -10,7 +10,7 @@ .panel-10 { width: 10%; - margin-bottom: 30px; + margin-top: 30px; } .panel-33 { @@ -44,9 +44,9 @@ padding: 20px; padding-top: 5px; } - .panel-100 { - width: 90%; - margin: auto; + .panel-100, .panel-100.w-100 { + width: 90% !important; + margin: auto !important; } [class^="panel-"] { margin: auto; @@ -67,11 +67,10 @@ *[class^="panel-"] { margin-top: 20px; } - .panel-10, .panel-90 { + .panel-90 { margin-top: 0; } .panel-10 { - margin: 0; padding-bottom: 20px; padding-right: 20px; } diff --git a/web/index.ejs b/web/index.ejs index 1a38164..812e186 100644 --- a/web/index.ejs +++ b/web/index.ejs @@ -52,8 +52,14 @@

<%= tunerName %> <% if (!publicTuner) { %> - <% } else if (tunerLock) { %><% } %>

-

<%- tunerDesc %>

+ <% } else if (tunerLock) { %><% } %> + +

+ <%- tunerDesc %> + <% if(tuningLimit && tuningLimit == true){ %> +
Limit: <%= tuningLowerLimit %> MHz - <%= tuningUpperLimit %> MHz
+ <% } %> +

@@ -61,7 +67,7 @@
-
+
@@ -148,17 +154,17 @@
-
-
+
+

RADIOTEXT

- +
-

+

@@ -174,14 +180,17 @@

-
+

AF

-
-
    +
    +
+
+ +
@@ -201,12 +210,13 @@
+ @@ -305,7 +321,27 @@
- + +
diff --git a/web/js/chat.js b/web/js/chat.js new file mode 100644 index 0000000..3157e77 --- /dev/null +++ b/web/js/chat.js @@ -0,0 +1,101 @@ +var chatUrl = new URL('chat', window.location.href); +chatUrl.protocol = chatUrl.protocol.replace('http', 'ws'); +var chatSocketAddress = chatUrl.href; +var chatSocket = new WebSocket(chatSocketAddress); +let chatMessageCount = 0; + +$(document).ready(function() { + chatSocket.onopen = function() { + }; + + chatSocket.onmessage = function(event) { + var messages = $('#chat-chatbox'); + let messageData = JSON.parse(event.data); // Parse event.data to access its properties + + let isAdmin = messageData.admin ? '[ADMIN]' : ''; // Add '[ADMIN] ' if messageData.admin is true, otherwise empty string + // Check if the message type is 'clientIp' + if (messageData.type === 'clientIp') { + // Fill the client IP into the element with ID #chat-ip + $('#chat-admin').html(isAdmin); + $('#chat-identity-nickname').attr('title', messageData.ip) + } else { + let chatMessage = ` + [${messageData.time}] + ${isAdmin} ${messageData.nickname}: ${$('
').text(messageData.message).html()}
+ `; + + messages.append(chatMessage); + + if($('#chat-chatbox').is(':visible')) { + $('#chat-chatbox').scrollTop($('#chat-chatbox')[0].scrollHeight); + } else { + if(messageData.history !== true) { + chatMessageCount++; + $('.chat-messages-count').text(chatMessageCount); + $('.chatbutton').removeClass('bg-color-2').addClass('bg-color-4'); + } + } + } + console.log(messageData); + }; + + $('.chat-send-message-btn').click(function() { + sendMessage(); + }); + + $('#chat-nickname-save').click(function() { + let currentNickname = $('#chat-nickname').val(); + localStorage.setItem('nickname', currentNickname); + $('#chat-identity-nickname').text(localStorage.getItem('nickname')); + $('#chat-nickname').blur(); + }); + + $('.chatbutton').click(function() { + $('#chat-chatbox').scrollTop($('#chat-chatbox')[0].scrollHeight); + chatMessageCount = 0; + $('#chat-messages-count').text(chatMessageCount); + $('.chatbutton').removeClass('bg-color-4').addClass('bg-color-2'); + $('#chat-send-message').focus(); + }); + + $('#chat-nickname').keypress(function(event) { + if (event.which == 13) { // 13 is the keycode for Enter key + $('#chat-nickname-save').trigger('click'); + } + }); + + $('#chat-send-message').keypress(function(event) { + if (event.which == 13) { // 13 is the keycode for Enter key + sendMessage(); + } + }); + + if(localStorage.getItem('nickname').length > 0) { + $('#chat-nickname').val(localStorage.getItem('nickname')); + $('#chat-identity-nickname').text(localStorage.getItem('nickname')); + } + +}); + +function sendMessage() { + var input = $('#chat-send-message'); + var nickname = localStorage.getItem('nickname'); + if (nickname && nickname.length > 1) { + // Only assign the nickname if it exists in localStorage and is longer than one character + nickname = nickname; + } else { + // Otherwise, use the default nickname + nickname = 'Anonymous user'; + } + + + if (input.val().trim() !== '') { + var messageData = { + nickname: nickname, + message: input.val() + }; + + chatSocket.send(JSON.stringify(messageData)); + input.val(''); + } + } \ No newline at end of file diff --git a/web/js/confighandler.js b/web/js/confighandler.js index 5a60e02..8f68163 100644 --- a/web/js/confighandler.js +++ b/web/js/confighandler.js @@ -1,6 +1,17 @@ function submitData() { const webserverIp = $('#webserver-ip').val() || '0.0.0.0'; const webserverPort = $('#webserver-port').val() || '8080'; + const tuningLimit = $('#tuning-limit').is(":checked") || false; + const tuningLowerLimit = $('#tuning-lower-limit').val() || '0'; + const tuningUpperLimit = $('#tuning-upper-limit').val() || '108'; + let presets = []; + presets.push($('#preset1').val() || '87.5'); + presets.push($('#preset2').val() || '87.5'); + presets.push($('#preset3').val() || '87.5'); + presets.push($('#preset4').val() || '87.5'); + + let banlist = []; + validateAndAdd(banlist); const xdrdIp = $('#xdrd-ip').val() || '127.0.0.1'; const xdrdPort = $('#xdrd-port').val() || '7373'; @@ -34,6 +45,11 @@ function submitData() { webserver: { webserverIp, webserverPort, + tuningLimit, + tuningLowerLimit, + tuningUpperLimit, + presets, + banlist }, xdrd: { xdrdIp, @@ -76,6 +92,7 @@ function submitData() { data: JSON.stringify(data), success: function (message) { alert(message); + console.log(data); }, error: function (error) { console.error(error); @@ -96,6 +113,18 @@ function submitData() { .then(data => { $('#webserver-ip').val(data.webserver.webserverIp); $('#webserver-port').val(data.webserver.webserverPort); + $('#tuning-limit').prop("checked", data.webserver.tuningLimit); + $('#tuning-lower-limit').val(data.webserver.tuningLowerLimit || ""); + $('#tuning-upper-limit').val(data.webserver.tuningUpperLimit || ""); + + if(Array.isArray(data.webserver.presets)) { + $('#preset1').val(data.webserver.presets[0] || ""); + $('#preset2').val(data.webserver.presets[1] || ""); + $('#preset3').val(data.webserver.presets[2] || ""); + $('#preset4').val(data.webserver.presets[3] || ""); + } + + $('#ip-addresses').val(data.webserver.banlist?.join('\n') || ""); $('#xdrd-ip').val(data.xdrd.xdrdIp); $('#xdrd-port').val(data.xdrd.xdrdPort); @@ -143,3 +172,18 @@ function submitData() { console.error('Error fetching data:', error.message); }); } + + +function validateAndAdd(banlist) { + var textarea = $('#ip-addresses'); + var ipAddresses = textarea.val().split('\n'); + + // Regular expression to validate IP address + var ipRegex = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/; + + ipAddresses.forEach(function(ip) { + if (ipRegex.test(ip)) { + banlist.push(ip); + } + }); +} \ No newline at end of file diff --git a/web/js/init.js b/web/js/init.js index b2b73f2..17f4af9 100644 --- a/web/js/init.js +++ b/web/js/init.js @@ -8,8 +8,11 @@ function getInitialSettings() { // Use the received data (data.qthLatitude, data.qthLongitude) as needed localStorage.setItem('qthLatitude', data.qthLatitude); localStorage.setItem('qthLongitude', data.qthLongitude); - localStorage.setItem('audioPort', data.audioPort); localStorage.setItem('streamEnabled', data.streamEnabled); + localStorage.setItem('preset1', data.presets[0]); + localStorage.setItem('preset2', data.presets[1]); + localStorage.setItem('preset3', data.presets[2]); + localStorage.setItem('preset4', data.presets[3]); }, error: function (error) { console.error('Error:', error); diff --git a/web/js/main.js b/web/js/main.js index db2459b..804eeb4 100644 --- a/web/js/main.js +++ b/web/js/main.js @@ -313,7 +313,13 @@ function updateCanvas(parsedData, signalChart) { socket.onmessage = (event) => { parsedData = JSON.parse(event.data); updatePanels(parsedData); - data.push(parsedData.signal); + if(localStorage.getItem("smoothSignal") == 'true') { + const sum = signalData.reduce((acc, strNum) => acc + parseFloat(strNum), 0); + const averageSignal = sum / signalData.length; + data.push(averageSignal); + } else { + data.push(parsedData.signal); + } }; function compareNumbers(a, b) { @@ -365,13 +371,13 @@ function checkKey(e) { if (socket.readyState === WebSocket.OPEN) { switch (e.keyCode) { case 66: // Back to previous frequency - socket.send("T" + (previousFreq * 1000)); + tuneTo(previousFreq); break; case 82: // RDS Reset (R key) - socket.send("T" + (currentFreq.toFixed(1) * 1000)); + tuneTo(Number(currentFreq)); break; case 38: - socket.send("T" + (Math.round(currentFreq*1000) + ((currentFreq > 30) ? 10 : 1))); + socket.send("T" + (Math.round(currentFreq*1000) + ((currentFreq > 30) ? 10 : 1))); break; case 40: socket.send("T" + (Math.round(currentFreq*1000) - ((currentFreq > 30) ? 10 : 1))); @@ -382,6 +388,22 @@ function checkKey(e) { case 39: tuneUp(); break; + case 112: // F1 + e.preventDefault(); + tuneTo(Number(localStorage.getItem('preset1'))); + break; + case 113: // F2 + e.preventDefault(); + tuneTo(Number(localStorage.getItem('preset2'))); + break; + case 114: // F3 + e.preventDefault(); + tuneTo(Number(localStorage.getItem('preset3'))); + break; + case 115: // F4 + e.preventDefault(); + tuneTo(Number(localStorage.getItem('preset4'))); + break; default: // Handle default case if needed break; diff --git a/web/js/modal.js b/web/js/modal.js index 830dc43..953ac9f 100644 --- a/web/js/modal.js +++ b/web/js/modal.js @@ -1,33 +1,56 @@ $(document).ready(function() { // Cache jQuery objects for reuse var modal = $("#myModal"); + var modalPanel = $(".modal-panel"); + var chatPanel = $(".modal-panel-chat"); + var chatOpenBtn = $(".chatbutton"); var openBtn = $("#settings"); - var closeBtn = $("#closeModal, #closeModalButton"); + var closeBtn = $(".closeModal, .closeModalButton"); // Function to open the modal function openModal() { modal.css("display", "block"); + modalPanel.css("display", "block"); setTimeout(function() { modal.css("opacity", 1); }, 10); } - // Function to close the modal - function closeModal() { - modal.css("opacity", 0); + function openChat() { + modal.css("display", "block"); + chatPanel.css("display", "block"); setTimeout(function() { - modal.css("display", "none"); - }, 300); + modal.css("opacity", 1); + }, 10); } - // Event listeners for the open and close buttons - openBtn.on("click", openModal); - closeBtn.on("click", closeModal); - // Close the modal when clicking outside of it - $(document).on("click", function(event) { - if ($(event.target).is(modal)) { - closeModal(); - } - }); +// Function to close the modal +function closeModal() { + modal.css("opacity", 0); + setTimeout(function() { + modal.css("display", "none"); + modalPanel.css("display", "none"); + chatPanel.css("display", "none"); + }, 300); +} + +// Event listeners for the open and close buttons +openBtn.on("click", openModal); +chatOpenBtn.on("click", openChat); +closeBtn.on("click", closeModal); + +// Close the modal when clicking outside of it +$(document).on("click", function(event) { + if ($(event.target).is(modal)) { + closeModal(); + } +}); + +// Close the modal when pressing ESC key +$(document).on("keydown", function(event) { + if (event.key === "Escape") { + closeModal(); + } +}); }); \ No newline at end of file diff --git a/web/js/settings.js b/web/js/settings.js index b090e53..b84f0e1 100644 --- a/web/js/settings.js +++ b/web/js/settings.js @@ -1,4 +1,4 @@ - var currentVersion = 'v1.1.0 [27.2.2024]'; + var currentVersion = 'v1.1.1 [1.3.2024]'; /** * Themes @@ -8,13 +8,13 @@ */ const themes = { theme1: ['rgba(32, 34, 40, 1)', 'rgba(88, 219, 171, 1)', 'rgba(255, 255, 255, 1)' ], // Retro (Default) - theme2: [ 'rgba(31, 12, 12, 1)', 'rgba(255, 112, 112, 1)', 'rgba(255, 255, 255, 1)' ], // Red - theme3: [ 'rgba(18, 28, 12, 1)', 'rgba(169, 255, 112, 1)', 'rgba(255, 255, 255, 1)' ], // Green - theme4: [ 'rgba(12, 28, 27, 1)', 'rgba(104, 247, 238, 1)', 'rgba(255, 255, 255, 1)' ], // Cyan - theme5: [ 'rgba(23, 17, 6, 1)', 'rgba(245, 182, 66, 1)', 'rgba(255, 255, 255, 1)' ], // Orange - theme6: [ 'rgba(33, 9, 29, 1)', 'rgba(237, 81, 211, 1)', 'rgba(255, 255, 255, 1)' ], // Pink + theme2: [ 'rgba(21, 32, 33, 1)', 'rgba(203, 202, 165, 1)', 'rgba(255, 255, 255, 1)' ], // Cappuccino + theme3: [ 'rgba(18, 18, 12, 1)', 'rgba(169, 255, 112, 1)', 'rgba(255, 255, 255, 1)' ], // Nature + theme4: [ 'rgba(12, 28, 27, 1)', 'rgba(104, 247, 238, 1)', 'rgba(255, 255, 255, 1)' ], // Ocean + theme5: [ 'rgba(23, 17, 6, 1)', 'rgba(245, 182, 66, 1)', 'rgba(255, 255, 255, 1)' ], // Terminal + theme6: [ 'rgba(33, 9, 29, 1)', 'rgba(250, 82, 141, 1)', 'rgba(255, 255, 255, 1)' ], // Nightlife theme7: [ 'rgba(13, 11, 26, 1)', 'rgba(128, 105, 250, 1)', 'rgba(255, 255, 255, 1)' ], // Blurple - theme8: [ 'rgba(252, 186, 3, 1)', 'rgba(0, 0, 0, 1)', 'rgba(0, 0, 0, 1)' ], // Sunny + theme8: [ 'rgba(252, 186, 3, 1)', 'rgba(0, 0, 0, 1)', 'rgba(0, 0, 0, 1)' ], // Construction theme9: [ 'rgba(0, 0, 0, 1)', 'rgba(204, 204, 204, 1)', 'rgba(255, 255, 255, 1)' ], // AMOLED }; diff --git a/web/js/webserver.js b/web/js/webserver.js index ae228c2..6484944 100644 --- a/web/js/webserver.js +++ b/web/js/webserver.js @@ -2,3 +2,4 @@ $.getScript('./js/main.js'); $.getScript('./js/dropdown.js'); $.getScript('./js/modal.js'); $.getScript('./js/settings.js'); +$.getScript('./js/chat.js'); \ No newline at end of file diff --git a/web/setup.ejs b/web/setup.ejs index 3d522ee..49d93eb 100644 --- a/web/setup.ejs +++ b/web/setup.ejs @@ -23,6 +23,7 @@
  • Status
  • Connection
  • Audio
  • +
  • Webserver
  • Identification
  • Online map
  • Maintenance
  • @@ -36,23 +37,49 @@

    STATUS

    -
    +
    <%= onlineUsers %>

    Online users

    -
    +
    <%= memoryUsage %>

    Memory usage

    -
    +
    <%= processUptime %>

    Uptime

    -

    Console output

    +

    Current users

    + + + + + + + + + + <% if (connectedUsers.length > 0) { %> + <% connectedUsers.forEach(user => { %> + + + + + + <% }); %> + <% } else { %> + + + + <% } %> + +
    IP AddressLocationOnline since
    <%= user.ip %><%= user.location %><%= user.time %>
    No users online
    + +

    Console

    <% if (consoleOutput && consoleOutput.length > 0) { %>
    <% consoleOutput.forEach(function(log) { %> @@ -153,6 +180,61 @@
    + +
    +

    Webserver settings

    +

    Antenna options

    +
    + + +

    + +

    Tuning options

    +

    If you want to limit which frequencies the users can tune to, you can set the lower and upper limit here.
    + Enter frequencies in MHz. +

    +
    + + +

    +
    + + +
    +
    + + +
    + +

    Presets

    +

    You can set up to 4 presets. These presets are accessible with the F1-F4 buttons.
    + Enter frequencies in MHz.

    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    + +

    Banlist

    +

    If you have users that don't behave in your chat, you can choose to ban them by their IP address.
    + You can see their IP address by hovering over their nickname. One IP per row.

    +
    + + +
    + +

    Tuner Identification info

    @@ -216,10 +298,6 @@

    -
    - - -