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

user kick, plugins, bugfixes

This commit is contained in:
NoobishSVK
2024-04-25 20:41:05 +02:00
parent 981c0f25e2
commit 510ed6b8f3
15 changed files with 152 additions and 46 deletions

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@ node_modules/
/*.json
/ffmpeg.exe
/serverlog.txt
/web/js/plugins/

View File

@@ -1,6 +1,6 @@
{
"name": "fm-dx-webserver",
"version": "1.1.9",
"version": "1.2.0",
"description": "FM DX Webserver",
"main": "index.js",
"scripts": {

View File

@@ -4,7 +4,7 @@ const verboseMode = process.argv.includes('--debug');
const verboseModeFfmpeg = process.argv.includes('--ffmpegdebug');
const ANSI_ESCAPE_CODE_PATTERN = /\x1b\[[0-9;]*m/g;
const MAX_LOG_LINES = 100000;
const MAX_LOG_LINES = 5000;
const getCurrentTime = () => {
const currentTime = new Date();
@@ -90,7 +90,8 @@ const logWarn = (...messages) => {
};
function appendLogToFile(logMessage) {
const cleanLogMessage = removeANSIEscapeCodes(logMessage);
const date = new Date();
const cleanLogMessage = date.toLocaleDateString() + ' | ' + removeANSIEscapeCodes(logMessage);
fs.appendFile('serverlog.txt', cleanLogMessage + '\n', (err) => {
if (err) {

View File

@@ -13,6 +13,7 @@ const storage = require('./storage');
const { logInfo, logDebug, logWarn, logError, logFfmpeg, logs } = require('./console');
const dataHandler = require('./datahandler');
const fmdxList = require('./fmdx_list');
const { allPluginConfigs } = require('./plugins');
// Endpoints
router.get('/', (req, res) => {
@@ -56,6 +57,7 @@ router.get('/', (req, res) => {
tuningUpperLimit: serverConfig.webserver.tuningUpperLimit,
chatEnabled: serverConfig.webserver.chatEnabled,
device: serverConfig.device,
plugins: serverConfig.plugins,
bwSwitch: serverConfig.bwSwitch ? serverConfig.bwSwitch : false
});
}
@@ -108,6 +110,7 @@ router.get('/setup', (req, res) => {
memoryUsage: (process.memoryUsage.rss() / 1024 / 1024).toFixed(1) + ' MB',
processUptime: formattedProcessUptime,
consoleOutput: logs,
plugins: allPluginConfigs,
onlineUsers: dataHandler.dataToSend.users,
connectedUsers: storage.connectedUsers
});
@@ -154,6 +157,17 @@ router.get('/logout', (req, res) => {
});
});
router.get('/kick', (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);
}
setTimeout(() => {
res.redirect('/setup');
}, 500);
});
router.post('/saveData', (req, res) => {
const data = req.body;
let firstSetup;
@@ -215,7 +229,8 @@ router.get('/static_data', (req, res) => {
qthLongitude: serverConfig.identification.lon,
presets: serverConfig.webserver.presets || [],
defaultTheme: serverConfig.webserver.defaultTheme || 'theme1',
bgImage: serverConfig.webserver.bgImage || ''
bgImage: serverConfig.webserver.bgImage || '',
rdsMode: serverConfig.webserver.rdsMode || false,
});
});

View File

@@ -1,5 +1,7 @@
const WebSocket = require('ws');
const dataHandler = require('./datahandler');
const storage = require('./storage');
const consoleCmd = require('./console');
function parseMarkdown(parsed) {
parsed = parsed.replace(/<\/?[^>]+(>|$)/g, '');
@@ -79,6 +81,23 @@ function resolveDataBuffer(data, wss) {
}
}
module.exports = {
parseMarkdown, removeMarkdown, formatUptime, resolveDataBuffer
function kickClient(ipAddress) {
// Find the entry in connectedClients associated with the provided IP address
const targetClient = storage.connectedUsers.find(client => client.ip === ipAddress);
if (targetClient && targetClient.instance) {
// Send a termination message to the client
targetClient.instance.send('KICK');
// Close the WebSocket connection after a short delay to allow the client to receive the message
setTimeout(() => {
targetClient.instance.close();
consoleCmd.logInfo(`Web client kicked (${ipAddress})`);
}, 500);
} else {
consoleCmd.logInfo(`Kicking client ${ipAddress} failed. No suitable client found.`);
}
}
module.exports = {
parseMarkdown, removeMarkdown, formatUptime, resolveDataBuffer, kickClient
}

View File

@@ -38,6 +38,7 @@ console.log('\x1b[90m―――――――――――――――――――
// Start ffmpeg
require('./stream/index');
require('./plugins');
// Create a WebSocket proxy instance
const proxy = httpProxy.createProxyServer({
@@ -253,7 +254,7 @@ wss.on('connection', (ws, request) => {
const connectionTime = new Date().toLocaleString([], options);
if(locationInfo.country === undefined) {
const userData = { ip: clientIp, location: 'Unknown', time: connectionTime };
const userData = { ip: clientIp, location: 'Unknown', time: connectionTime, instance: ws };
storage.connectedUsers.push(userData);
logInfo(`Web client \x1b[32mconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]\x1b[0m`);
} else {

View File

@@ -45,6 +45,7 @@ let serverConfig = {
tunePass: "",
adminPass: ""
},
plugins: [],
device: 'tef',
defaultFreq: 87.5,
publicTuner: true,
@@ -65,9 +66,10 @@ function deepMerge(target, source)
}
function configUpdate(newConfig) {
if (newConfig.webserver && newConfig.webserver.banlist !== undefined) {
if (newConfig.webserver && (newConfig.webserver.banlist !== undefined || newConfig.plugins !== undefined)) {
// If new banlist is provided, replace the existing one
serverConfig.webserver.banlist = newConfig.webserver.banlist;
serverConfig.plugins = newConfig.plugins;
delete newConfig.webserver.banlist; // Remove banlist from newConfig to avoid merging
}

View File

@@ -7,4 +7,5 @@
@import url("panels.css"); /* Different panels and their sizes */
@import url("modal.css"); /* Modal window */
@import url("setup.css"); /* Web setup interface */
@import url("multiselect.css"); /* Multiselect tags */
@import url("helpers.css"); /* Stuff that is used often such as text changers etc */

View File

@@ -468,5 +468,8 @@
</div>
<script src="js/webserver.js"></script>
<% plugins.forEach(function(plugin) { %>
<script src="js/plugins/<%= plugin %>"></script>
<% }); %>
</body>
</html>

View File

@@ -20,6 +20,8 @@ function OnConnectivityCallback(isConnected) {
}
function OnPlayButtonClick(_ev) {
const $playbutton = $('.playbutton');
$playbutton.find('.fa-solid').toggleClass('fa-play fa-stop');
try {
if (Stream.ConnectivityFlag) {
Stream.Stop();
@@ -29,8 +31,6 @@ function OnPlayButtonClick(_ev) {
setTimeout(() => {
$playbutton.removeClass('bg-gray').prop('disabled', false);
}, 3000);
const $playbutton = $('.playbutton');
$playbutton.find('.fa-solid').toggleClass('fa-play fa-stop');
}
} catch (error) {
console.error(error);

View File

@@ -11,6 +11,7 @@ function submitData() {
const defaultTheme = themeDataValue;
const bgImage = $("#bg-image").val() || '';
const rdsMode = $('#rds-mode').is(":checked") || false;
const ant1enabled = $('#ant1-enabled').is(":checked") || false;
const ant2enabled = $('#ant2-enabled').is(":checked") || false;
@@ -71,6 +72,11 @@ function submitData() {
const tunePass = $('#tune-pass').val();
const adminPass = $('#admin-pass').val();
let plugins = [];
$('#plugin-list option:selected').each(function() {
plugins.push($(this).data('name'));
});
const publicTuner = $("#tuner-public").is(":checked");
const lockToAdmin = $("#tuner-lock").is(":checked");
const autoShutdown = $("#shutdown-tuner").is(":checked") || false;
@@ -88,7 +94,8 @@ function submitData() {
defaultTheme,
presets,
banlist,
bgImage
bgImage,
rdsMode,
},
antennas: {
enabled: antennasEnabled,
@@ -136,6 +143,7 @@ function submitData() {
tunePass,
adminPass,
},
plugins,
device,
publicTuner,
lockToAdmin,
@@ -181,6 +189,7 @@ function submitData() {
$("#chat-switch").prop("checked", data.webserver.chatEnabled || false);
$('#selected-theme').val(data.webserver.defaultTheme || 'Default');
$('#rds-mode').prop("checked", data.webserver.rdsMode || false);
var selectedTheme = $(".option[data-value='" + data.webserver.defaultTheme + "']");
@@ -273,6 +282,14 @@ function submitData() {
$("#shutdown-tuner").prop("checked", data.autoShutdown);
$("#antenna-switch").prop("checked", data.antennas?.enabled);
data.plugins.forEach(function(name) {
// Find the option with the corresponding data-name attribute and mark it as selected
$('#plugin-list option[data-name="' + name + '"]').prop('selected', true);
});
// Update the multi-select element to reflect the changes
$('#plugin-list').trigger('change');
// Check if latitude and longitude are present in the data
if (data.identification.lat && data.identification.lon) {
// Set the map's center to the received coordinates

View File

@@ -54,3 +54,18 @@ const closeDropdownFromOutside = (event) => {
$(document).on('click', closeDropdownFromOutside);
$listOfOptions.on('click', selectOption);
$dropdowns.on('click', toggleDropdown);
// MULTISELECT
$('.multiselect option').mousedown(function(e) {
e.preventDefault();
var originalScrollTop = $(this).parent().scrollTop();
console.log(originalScrollTop);
$(this).prop('selected', $(this).prop('selected') ? false : true);
var self = this;
$(this).parent().focus();
setTimeout(function() {
$(self).parent().scrollTop(originalScrollTop);
}, 0);
return false;
});

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.9c [' + formattedDate + ']';
var currentVersion = 'v1.2.0 [' + formattedDate + ']';
getInitialSettings();
@@ -20,6 +20,7 @@ function getInitialSettings() {
localStorage.setItem('preset3', data.presets[2]);
localStorage.setItem('preset4', data.presets[3]);
localStorage.setItem('bgImage', data.bgImage);
localStorage.setItem('rdsMode', data.rdsMode);
},
error: function (error) {
console.error('Error:', error);

View File

@@ -25,6 +25,8 @@ const usa_programmes = [
"Spanish Talk", "Spanish Music", "Hip Hop", "", "", "Weather", "Emergency Test", "Emergency"
];
const rdsMode = localStorage.getItem('rdsMode');
$(document).ready(function () {
var canvas = $('#signal-canvas')[0];
@@ -359,7 +361,16 @@ function updateCanvas(parsedData, signalChart) {
}
socket.onmessage = (event) => {
if (event.data == 'KICK') {
console.log('Kick iniitiated.')
setTimeout(() => {
window.location.href = '/403';
}, 500); // Adjust the delay as needed
return;
}
parsedData = JSON.parse(event.data);
updatePanels(parsedData);
if(localStorage.getItem("smoothSignal") == 'true') {
const sum = signalData.reduce((acc, strNum) => acc + parseFloat(strNum), 0);
@@ -701,7 +712,7 @@ const updateDataElements = throttle(function(parsedData) {
updateHtmlIfChanged($dataRt0, processString(parsedData.rt0, parsedData.rt0_errors));
updateHtmlIfChanged($dataRt1, processString(parsedData.rt1, parsedData.rt1_errors));
updateTextIfChanged($dataPty, europe_programmes[parsedData.pty]);
updateTextIfChanged($dataPty, rdsMode == 'true' ? usa_programmes[parsedData.pty] : europe_programmes[parsedData.pty]);
if (parsedData.rds === true) {
$flagDesktopCointainer.css('background-color', 'var(--color-2)');

View File

@@ -25,6 +25,7 @@
<li data-panel="connection">Connection</li>
<li data-panel="audio">Audio</li>
<li data-panel="webserver">Webserver</li>
<li data-panel="plugins">Plugins</li>
<li data-panel="identification">Info & Map</li>
<li class="logout-link text-gray"><i class="fas fa-sign-out"></i></li>
</ul>
@@ -59,7 +60,7 @@
<th>IP Address</th>
<th>Location</th>
<th>Online since</th>
<!--<th></th>-->
<th></th>
</tr>
</thead>
<tbody>
@@ -69,7 +70,7 @@
<td><%= user.ip %></td>
<td><%= user.location %></td>
<td><%= user.time %></td>
<!--<td><a href="./kick?user=<% user.ip %>">Kick</a></td>-->
<td><a href="./kick?ip=<%= user.ip %>">Kick</a></td>
</tr>
<% }); %>
<% } else { %>
@@ -80,6 +81,31 @@
</tbody>
</table>
<h3>Maintenance</h3>
<div class="flex-container flex-center" style="margin: 30px;">
<div class="form-group checkbox">
<input type="checkbox" id="tuner-public">
<label for="tuner-public">Public tuner (no password)</label>
</div>
<div class="form-group checkbox">
<input type="checkbox" id="tuner-lock">
<label for="tuner-lock">Admin lock [only admins can tune]</label>
</div>
<div class="form-group checkbox">
<input type="checkbox" id="shutdown-tuner">
<label for="shutdown-tuner">Auto-shutdown</label>
</div><br>
</div>
<div class="form-group">
<label for="tune-pass">Tune password:</label>
<input class="input-text w-200" type="password" name="tune-pass" id="tune-pass">
</div>
<div class="form-group">
<label for="admin-pass">Admin setup password:</label>
<input class="input-text w-200" type="password" name="admin-pass" id="admin-pass">
</div><br>
<h3>Console</h3>
<% if (consoleOutput && consoleOutput.length > 0) { %>
<div class="panel-100 br-5 p-10 text-small text-left top-10" id="console-output">
@@ -91,37 +117,8 @@
<p>No console output available.</p>
<% } %>
<h3>Maintenance</h3>
<div class="flex-container">
<div class="panel-50 no-bg">
<div class="form-group checkbox">
<input type="checkbox" id="tuner-public">
<label for="tuner-public">Allow tuner control without password</label>
</div>
<div class="form-group checkbox">
<input type="checkbox" id="tuner-lock">
<label for="tuner-lock">Lock the tuner [only admins can tune with this option]</label>
</div>
</div>
<div class="panel-50 no-bg">
<div class="form-group checkbox">
<input type="checkbox" id="shutdown-tuner">
<label for="shutdown-tuner">Auto-shutdown tuner when no one is online</label>
</div><br>
</div>
</div>
<div class="form-group">
<label for="tune-pass">Tune password:</label>
<input class="input-text w-200" type="password" name="tune-pass" id="tune-pass">
</div>
<div class="form-group">
<label for="admin-pass">Admin setup password:</label>
<input class="input-text w-200" type="password" name="admin-pass" id="admin-pass">
</div><br>
<p class="text-small">Version: <span class="version-string color-4"></span></p>
<p class="p-bottom-20"><a href="https://github.com/noobishsvk/fm-dx-webserver" target="_blank">Check for the latest source code</a> • <a href="https://buymeacoffee.com/noobish" target="_blank">Support the developer</a></p>
<p><a href="https://github.com/noobishsvk/fm-dx-webserver" target="_blank">Check for the latest source code</a> • <a href="https://buymeacoffee.com/noobish" target="_blank">Support the developer</a></p>
</div>
<div class="panel-100 tab-content" id="connection">
@@ -355,6 +352,13 @@
</div>
<div class="panel-50 no-bg">
<h3>RDS Mode</h3>
<p>You can switch between American (RBDS) / Global (RDS) mode here.</p>
<div class="form-group checkbox bottom-20">
<input type="checkbox" id="rds-mode">
<label for="rds-mode">Enable American Mode (RBDS)</label>
</div>
<h3>Presets</h3>
<p>You can set up to 4 presets.<br>These presets are accessible with the F1-F4 buttons.<br>
<span class="text-gray">Enter frequencies in MHz.</span></p>
@@ -399,6 +403,21 @@
</div>
<div class="panel-100 tab-content" id="plugins">
<h2>Plugins</h2>
<p>Any compatible <strong>.js</strong> plugin, which is in the "plugins" folder, will be listed here.<br>
Click on the individual plugins to enable/disable them.</p>
<select id="plugin-list" class="multiselect" multiple>
<% plugins.forEach(function(plugin) { %>
<option data-name="<%= plugin.frontEndPath %>" title="<%= plugin.name %> by <%= plugin.author %> [v<%= plugin.version %>]">
<%= plugin.name %> by <%= plugin.author %> [v<%= plugin.version %>]
</option>
<% }); %>
</select><br><br>
<a href="https://github.com/NoobishSVK/fm-dx-webserver/wiki/Plugin-List" target="_blank">Download new plugins here!</a>
</div>
<div class="panel-100 tab-content" id="tuner">
<h2>Tuner Specific Settings</h2>
<div class="form-group">