diff --git a/package.json b/package.json index d2e9fb3..2e21a9a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fm-dx-webserver", - "version": "1.3.2", + "version": "1.3.3", "description": "FM DX Webserver", "main": "index.js", "scripts": { diff --git a/server/endpoints.js b/server/endpoints.js index 094f1ac..d9518d2 100644 --- a/server/endpoints.js +++ b/server/endpoints.js @@ -19,7 +19,10 @@ const { allPluginConfigs } = require('./plugins'); // Endpoints router.get('/', (req, res) => { let requestIp = req.headers['x-forwarded-for'] || req.connection.remoteAddress; - if(serverConfig.webserver.banlist.includes(requestIp)) { + const normalizedIp = requestIp.replace(/^::ffff:/, ''); + const isBanned = serverConfig.webserver.banlist.some(banEntry => banEntry[0] === normalizedIp); + + if (isBanned) { res.render('403'); logInfo(`Web client (${requestIp}) is banned`); return; @@ -164,7 +167,12 @@ router.get('/rdsspy', (req, res) => { router.get('/api', (req, res) => { const { ps_errors, rt0_errors, rt1_errors, ims, eq, ant, st_forced, previousFreq, txInfo, ...dataToSend } = dataHandler.dataToSend; - res.json(dataToSend); + res.json({ + ...dataToSend, + txInfo: txInfo, + ps_errors: ps_errors, + ant: ant + }); }); @@ -212,16 +220,47 @@ router.get('/kick', (req, res) => { }); router.get('/addToBanlist', (req, res) => { - const ipAddress = req.query.ip; // Extract the IP address parameter from the query string - // Terminate the WebSocket connection for the specified IP address - if(req.session.isAdminAuthenticated) { - helpers.kickClient(ipAddress); + const ipAddress = req.query.ip; + const location = 'Unknown'; + const date = Date.now(); + const reason = req.query.reason; + + userBanData = [ipAddress, location, date, reason]; + + if (typeof serverConfig.webserver.banlist !== 'object') { + serverConfig.webserver.banlist = []; + } + + if (req.session.isAdminAuthenticated) { + serverConfig.webserver.banlist.push(userBanData); + configSave(); + res.json({ success: true, message: 'IP address added to banlist.' }); + helpers.kickClient(ipAddress); + } else { + res.status(403).json({ success: false, message: 'Unauthorized access.' }); } - setTimeout(() => { - res.redirect('/setup'); - }, 500); }); +router.get('/removeFromBanlist', (req, res) => { + const ipAddress = req.query.ip; + + if (typeof serverConfig.webserver.banlist !== 'object') { + serverConfig.webserver.banlist = []; + } + + const banIndex = serverConfig.webserver.banlist.findIndex(ban => ban[0] === ipAddress); + + if (banIndex === -1) { + return res.status(404).json({ success: false, message: 'IP address not found in banlist.' }); + } + + serverConfig.webserver.banlist.splice(banIndex, 1); + configSave(); + + res.json({ success: true, message: 'IP address removed from banlist.' }); +}); + + router.post('/saveData', (req, res) => { const data = req.body; let firstSetup; @@ -290,6 +329,7 @@ router.get('/static_data', (req, res) => { rdsMode: serverConfig.webserver.rdsMode || false, tunerName: serverConfig.identification.tunerName || '', tunerDesc: serverConfig.identification.tunerDesc || '', + ant: serverConfig.antennas || {} }); }); diff --git a/server/helpers.js b/server/helpers.js index c2f3f7b..4bbf442 100644 --- a/server/helpers.js +++ b/server/helpers.js @@ -181,12 +181,15 @@ function antispamProtection(message, clientIp, ws, userCommands, lastWarn, userC if (userCommandHistory[clientIp].length >= 8) { consoleCmd.logWarn(`User \x1b[90m${clientIp}\x1b[0m is spamming with rapid commands. Connection will be terminated and user will be banned.`); - // Add to banlist if not already banned - if (!serverConfig.webserver.banlist.includes(clientIp)) { - serverConfig.webserver.banlist.push(clientIp); - consoleCmd.logInfo(`User \x1b[90m${clientIp}\x1b[0m has been added to the banlist due to extreme spam.`); - configSave(); - } + // Check if the normalized IP is already in the banlist + const isAlreadyBanned = serverConfig.webserver.banlist.some(banEntry => banEntry[0] === normalizedClientIp); + + if (!isAlreadyBanned) { + // Add the normalized IP to the banlist + serverConfig.webserver.banlist.push([normalizedClientIp, 'Unknown', Date.now(), '[Auto ban] Spam']); + consoleCmd.logInfo(`User \x1b[90m${normalizedClientIp}\x1b[0m has been added to the banlist due to extreme spam.`); + configSave(); + } ws.close(1008, 'Bot-like behavior detected'); return command; // Return command value before closing connection diff --git a/server/server_config.js b/server/server_config.js index 02c77af..4142841 100644 --- a/server/server_config.js +++ b/server/server_config.js @@ -8,7 +8,7 @@ let configName = 'config'; const index = process.argv.indexOf('--config'); if (index !== -1 && index + 1 < process.argv.length) { configName = process.argv[index + 1]; - logInfo('Loading with a custom config file:', configName + '.json') + logInfo('Loading with a custom config file:', configName + '.json'); } const configPath = path.join(__dirname, '../' + configName + '.json'); @@ -99,16 +99,19 @@ let serverConfig = { lockToAdmin: false, autoShutdown: false, enableDefaultFreq: false, - defaultFreq: "87.5" + defaultFreq: "87.5", + testThing: "yes it works" }; -function deepMerge(target, source) -{ +function deepMerge(target, source) { Object.keys(source).forEach(function(key) { - if (typeof target[key] === 'object' && target[key] !== null) { - deepMerge(target[key], source[key]); + if (typeof source[key] === 'object' && source[key] !== null) { + if (!target[key]) target[key] = {}; // Create missing object + deepMerge(target[key], source[key]); // Recursively merge } else { - target[key] = source[key]; + if (target[key] === undefined) { + target[key] = source[key]; // Add missing fields + } } }); } @@ -124,7 +127,6 @@ function configUpdate(newConfig) { deepMerge(serverConfig, newConfig); } - function configSave() { fs.writeFile(configPath, JSON.stringify(serverConfig, null, 2), (err) => { if (err) { @@ -139,9 +141,16 @@ function configExists() { return fs.existsSync(configPath); } -if (fs.existsSync(configPath)) { +if (configExists()) { const configFileContents = fs.readFileSync(configPath, 'utf8'); - serverConfig = JSON.parse(configFileContents); + try { + const configFile = JSON.parse(configFileContents); + deepMerge(configFile, serverConfig); + serverConfig = configFile; + configSave(); + } catch (err) { + logError('Error parsing config file:', err); + } } module.exports = { diff --git a/web/css/breadcrumbs.css b/web/css/breadcrumbs.css index 72a64ad..f53d1dc 100644 --- a/web/css/breadcrumbs.css +++ b/web/css/breadcrumbs.css @@ -112,6 +112,10 @@ label { margin-right: 5px; } +table .form-group { + margin: 0; +} + #settings, #back-btn, #users-online-container { background: transparent; border: 0; diff --git a/web/css/helpers.css b/web/css/helpers.css index f269f30..1b48c71 100644 --- a/web/css/helpers.css +++ b/web/css/helpers.css @@ -210,6 +210,10 @@ background-color: var(--color-2) !important; } +table .input-text { + background-color: var(--color-1) !important; +} + .pointer { cursor: pointer; } diff --git a/web/js/init.js b/web/js/init.js index 3c66a94..8a62a34 100644 --- a/web/js/init.js +++ b/web/js/init.js @@ -1,9 +1,9 @@ -var currentDate = new Date('Nov 5, 2024 21:00:00'); +var currentDate = new Date('Jan 11, 2025 21:00:00'); 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.3.2 [' + formattedDate + ']'; +var currentVersion = 'v1.3.3 [' + formattedDate + ']'; getInitialSettings(); removeUrlParameters(); diff --git a/web/js/setup.js b/web/js/setup.js index 3424a4b..43d6edb 100644 --- a/web/js/setup.js +++ b/web/js/setup.js @@ -9,6 +9,7 @@ $(document).ready(function() { showPanelFromHash(); initNav(); + initBanlist(); }); /** @@ -85,6 +86,54 @@ function initNav() { }); } +function initBanlist() { + $('.banlist-add').on('click', function(e) { + e.preventDefault(); + + const ipAddress = $('#banlist-add-ip').val(); + const reason = $('#banlist-add-reason').val(); + + $.ajax({ + url: '/addToBanlist', + method: 'GET', + data: { ip: ipAddress, reason: reason }, + success: function(response) { + // Refresh the page if the request was successful + if (response.success) { + location.reload(); + } else { + console.error('Failed to add to banlist'); + } + }, + error: function() { + console.error('Error occurred during the request'); + } + }); +}); + +$('.banlist-remove').on('click', function(e) { + e.preventDefault(); + + const ipAddress = $(this).closest('tr').find('td').first().text(); + + $.ajax({ + url: '/removeFromBanlist', + method: 'GET', + data: { ip: ipAddress }, + success: function(response) { + if (response.success) { + location.reload(); + } else { + console.error('Failed to remove from banlist'); + } + }, + error: function() { + console.error('Error occurred during the request'); + } + }); +}); +} + function toggleNav() { const navOpen = $("#navigation").css('margin-left') === '0px'; const isMobile = window.innerWidth <= 768; diff --git a/web/setup.ejs b/web/setup.ejs index 5afa6a4..5e74f1a 100644 --- a/web/setup.ejs +++ b/web/setup.ejs @@ -101,7 +101,11 @@ <% if (connectedUsers.length > 0) { %> <% connectedUsers.forEach(user => { %>
You can switch between American (RBDS) / Global (RDS) mode here.
<%- include('_components', {component: 'checkbox', cssClass: 'bottom-20', iconClass: '', label: 'American RDS mode (RBDS)', id: 'webserver-rdsMode'}) %>If you have users that don't behave on your server, 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.
If you want to choose the COM port directly, choose "Direct".
If you use xdrd or your receiver is connected via Wi-Fi, choose TCP/IP.
Choose your desired COM port
If you are connecting your tuner wirelessly, enter the tuner IP.
If you use xdrd, use 127.0.0.1 as your IP.
Bandwidth switch allows the user to set the bandwidth manually.
- <%- include('_components', {component: 'checkbox', cssClass: '', label: 'Bandwidth switch', id: 'bwSwitch'}) %>Toggling this option will put the tuner to sleep when no clients are connected.
- <%- include('_components', {component: 'checkbox', cssClass: '', label: 'Auto-shutdown', id: 'autoShutdown'}) %>FMLIST integration allows you to get potential DXes logged on the FMLIST Visual Logbook.
- Your server also needs to have a valid UUID, which is obtained by registering on maps in the Identification & Map tab.
You can also fill in your OMID from FMLIST.org, if you want the logs to be saved to your account.
<%- include('_components', {component: 'text', cssClass: 'w-100', placeholder: '', label: 'OMID', id: 'extras-fmlistOmid'}) %>