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 => { %> - <%= user.ip %> + + + <%= user.ip.replace('::ffff:', '') %> + + <%= user.location %> <%= user.time %> Kick @@ -288,23 +292,6 @@

RDS Mode

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'}) %>
-<<<<<<< HEAD - -
-

Chat options

- <%- include('_components', {component: 'checkbox', cssClass: '', label: 'Chat', id: 'webserver-chatEnabled'}) %> -
- -
-

Banlist

-

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.

-
- - -
-
-======= @@ -324,7 +311,6 @@ <% }); %>

Download new plugins here! ->>>>>>> 4b6d011 (rewrite update)
@@ -444,98 +430,6 @@
-
-

Tuner settings

-
-
-

Device type

- <%- include('_components', { component: 'dropdown', id: 'device-selector', inputId: 'device', label: 'Device', cssClass: '', placeholder: 'TEF668x / TEA685x', - options: [ - { value: 'tef', label: 'TEF668x / TEA685x' }, - { value: 'xdr', label: 'XDR (F1HD / S10HDiP)' }, - { value: 'sdr', label: 'SDR (RTL-SDR / AirSpy)' }, - { value: 'other', label: 'Other' } - ] - }) %>
- -
- -
-

Connection type

-

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.

-
- -
-
-
-

Device / Server

- -
-

Choose your desired COM port
 

- <%- include('_components', { - component: 'dropdown', - id: 'deviceList', - inputId: 'xdrd-comPort', - label: 'USB Device', - cssClass: '', - placeholder: 'Choose your USB device', - options: serialPorts.map(serialPort => ({ - value: serialPort.path, - label: `${serialPort.path} - ${serialPort.friendlyName}` - })) - }) %> -
- -<<<<<<< HEAD -
-

If you are connecting your tuner wirelessly, enter the tuner IP.
If you use xdrd, use 127.0.0.1 as your IP.

- <%- include('_components', {component: 'text', cssClass: 'w-150', label: 'xdrd IP address', id: 'xdrd-xdrdIp'}) %> - <%- include('_components', {component: 'text', cssClass: 'w-100', label: 'xdrd port', id: 'xdrd-xdrdPort'}) %> - <%- include('_components', {component: 'text', cssClass: 'w-150', label: 'xdrd password', id: 'xdrd-xdrdPassword', password: true}) %> -
-
-
-
-
-

Startup

-

Startup volume

-
- -
-

- -
-

Default frequency

- <%- include('_components', {component: 'checkbox', cssClass: '', label: 'Default frequency for first client', id: 'enableDefaultFreq'}) %>
- <%- include('_components', {component: 'text', cssClass: 'w-100', placeholder: '87.5', label: 'Default frequency', id: 'defaultFreq'}) %> -
-
-

Miscellaneous

-
-
-

Bandwidth switch

-

Bandwidth switch allows the user to set the bandwidth manually.

- <%- include('_components', {component: 'checkbox', cssClass: '', label: 'Bandwidth switch', id: 'bwSwitch'}) %>
-
-
-

Automatic shutdown

-

Toggling this option will put the tuner to sleep when no clients are connected.

- <%- include('_components', {component: 'checkbox', cssClass: '', label: 'Auto-shutdown', id: 'autoShutdown'}) %>
-
-
-
-
-
- -======= ->>>>>>> 4b6d011 (rewrite update)

Identification & Map

@@ -578,16 +472,6 @@
-<<<<<<< HEAD -
-

Extras

-
-

FMLIST Integration

-

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.

- <%- include('_components', {component: 'checkbox', cssClass: 'm-right-10', label: 'FMLIST integration', id: 'extras-fmlistIntegration'}) %>
- -=======

User management

@@ -607,32 +491,41 @@ Location Ban date Reason - + + + <%- include('_components', {component: 'text', cssClass: 'w-100', placeholder: 'IP address', label: '', id: 'banlist-add-ip'}) %> + + + <%- include('_components', {component: 'text', cssClass: 'w-150', placeholder: 'Ban reason (note)', label: '', id: 'banlist-add-reason'}) %> + + + + <% if (banlist.length > 0) { %> <% banlist.forEach(bannedUser => { %> <% if (Array.isArray(bannedUser)) { %> - <%= bannedUser[0] %> + <%= bannedUser[0] %> <%= bannedUser[1] %> - <%= new Date(parseInt(bannedUser[2]) * 1000).toLocaleString() %> + <%= new Date(parseInt(bannedUser[2])).toLocaleString() %> <%= bannedUser[3] %> <% } else { %> - <%= bannedUser %> + <%= bannedUser %> Unknown Unknown Unknown <% } %> - + <% }); %> <% } else { %> - The banlist is empty. + The banlist is empty. <% } %> @@ -648,7 +541,6 @@ Your server also needs to have a valid UUID, which is obtained by registering on maps in the Identification & Map tab.

<%- include('_components', {component: 'checkbox', cssClass: 'm-right-10', label: 'FMLIST integration', id: 'extras-fmlistIntegration'}) %>
->>>>>>> 4b6d011 (rewrite update)

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'}) %>