You've already forked fm-dx-webserver
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:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,4 +1,5 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
/*.json
|
/*.json
|
||||||
/ffmpeg.exe
|
/ffmpeg.exe
|
||||||
/serverlog.txt
|
/serverlog.txt
|
||||||
|
/web/js/plugins/
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "fm-dx-webserver",
|
"name": "fm-dx-webserver",
|
||||||
"version": "1.1.9",
|
"version": "1.2.0",
|
||||||
"description": "FM DX Webserver",
|
"description": "FM DX Webserver",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ const verboseMode = process.argv.includes('--debug');
|
|||||||
const verboseModeFfmpeg = process.argv.includes('--ffmpegdebug');
|
const verboseModeFfmpeg = process.argv.includes('--ffmpegdebug');
|
||||||
|
|
||||||
const ANSI_ESCAPE_CODE_PATTERN = /\x1b\[[0-9;]*m/g;
|
const ANSI_ESCAPE_CODE_PATTERN = /\x1b\[[0-9;]*m/g;
|
||||||
const MAX_LOG_LINES = 100000;
|
const MAX_LOG_LINES = 5000;
|
||||||
|
|
||||||
const getCurrentTime = () => {
|
const getCurrentTime = () => {
|
||||||
const currentTime = new Date();
|
const currentTime = new Date();
|
||||||
@@ -90,7 +90,8 @@ const logWarn = (...messages) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function appendLogToFile(logMessage) {
|
function appendLogToFile(logMessage) {
|
||||||
const cleanLogMessage = removeANSIEscapeCodes(logMessage);
|
const date = new Date();
|
||||||
|
const cleanLogMessage = date.toLocaleDateString() + ' | ' + removeANSIEscapeCodes(logMessage);
|
||||||
|
|
||||||
fs.appendFile('serverlog.txt', cleanLogMessage + '\n', (err) => {
|
fs.appendFile('serverlog.txt', cleanLogMessage + '\n', (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ const storage = require('./storage');
|
|||||||
const { logInfo, logDebug, logWarn, logError, logFfmpeg, logs } = require('./console');
|
const { logInfo, logDebug, logWarn, logError, logFfmpeg, logs } = require('./console');
|
||||||
const dataHandler = require('./datahandler');
|
const dataHandler = require('./datahandler');
|
||||||
const fmdxList = require('./fmdx_list');
|
const fmdxList = require('./fmdx_list');
|
||||||
|
const { allPluginConfigs } = require('./plugins');
|
||||||
|
|
||||||
// Endpoints
|
// Endpoints
|
||||||
router.get('/', (req, res) => {
|
router.get('/', (req, res) => {
|
||||||
@@ -56,6 +57,7 @@ router.get('/', (req, res) => {
|
|||||||
tuningUpperLimit: serverConfig.webserver.tuningUpperLimit,
|
tuningUpperLimit: serverConfig.webserver.tuningUpperLimit,
|
||||||
chatEnabled: serverConfig.webserver.chatEnabled,
|
chatEnabled: serverConfig.webserver.chatEnabled,
|
||||||
device: serverConfig.device,
|
device: serverConfig.device,
|
||||||
|
plugins: serverConfig.plugins,
|
||||||
bwSwitch: serverConfig.bwSwitch ? serverConfig.bwSwitch : false
|
bwSwitch: serverConfig.bwSwitch ? serverConfig.bwSwitch : false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -108,6 +110,7 @@ router.get('/setup', (req, res) => {
|
|||||||
memoryUsage: (process.memoryUsage.rss() / 1024 / 1024).toFixed(1) + ' MB',
|
memoryUsage: (process.memoryUsage.rss() / 1024 / 1024).toFixed(1) + ' MB',
|
||||||
processUptime: formattedProcessUptime,
|
processUptime: formattedProcessUptime,
|
||||||
consoleOutput: logs,
|
consoleOutput: logs,
|
||||||
|
plugins: allPluginConfigs,
|
||||||
onlineUsers: dataHandler.dataToSend.users,
|
onlineUsers: dataHandler.dataToSend.users,
|
||||||
connectedUsers: storage.connectedUsers
|
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) => {
|
router.post('/saveData', (req, res) => {
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
let firstSetup;
|
let firstSetup;
|
||||||
@@ -215,7 +229,8 @@ router.get('/static_data', (req, res) => {
|
|||||||
qthLongitude: serverConfig.identification.lon,
|
qthLongitude: serverConfig.identification.lon,
|
||||||
presets: serverConfig.webserver.presets || [],
|
presets: serverConfig.webserver.presets || [],
|
||||||
defaultTheme: serverConfig.webserver.defaultTheme || 'theme1',
|
defaultTheme: serverConfig.webserver.defaultTheme || 'theme1',
|
||||||
bgImage: serverConfig.webserver.bgImage || ''
|
bgImage: serverConfig.webserver.bgImage || '',
|
||||||
|
rdsMode: serverConfig.webserver.rdsMode || false,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
const WebSocket = require('ws');
|
const WebSocket = require('ws');
|
||||||
const dataHandler = require('./datahandler');
|
const dataHandler = require('./datahandler');
|
||||||
|
const storage = require('./storage');
|
||||||
|
const consoleCmd = require('./console');
|
||||||
|
|
||||||
function parseMarkdown(parsed) {
|
function parseMarkdown(parsed) {
|
||||||
parsed = parsed.replace(/<\/?[^>]+(>|$)/g, '');
|
parsed = parsed.replace(/<\/?[^>]+(>|$)/g, '');
|
||||||
@@ -79,6 +81,23 @@ function resolveDataBuffer(data, wss) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 = {
|
module.exports = {
|
||||||
parseMarkdown, removeMarkdown, formatUptime, resolveDataBuffer
|
parseMarkdown, removeMarkdown, formatUptime, resolveDataBuffer, kickClient
|
||||||
}
|
}
|
||||||
@@ -38,6 +38,7 @@ console.log('\x1b[90m―――――――――――――――――――
|
|||||||
|
|
||||||
// Start ffmpeg
|
// Start ffmpeg
|
||||||
require('./stream/index');
|
require('./stream/index');
|
||||||
|
require('./plugins');
|
||||||
|
|
||||||
// Create a WebSocket proxy instance
|
// Create a WebSocket proxy instance
|
||||||
const proxy = httpProxy.createProxyServer({
|
const proxy = httpProxy.createProxyServer({
|
||||||
@@ -253,7 +254,7 @@ wss.on('connection', (ws, request) => {
|
|||||||
const connectionTime = new Date().toLocaleString([], options);
|
const connectionTime = new Date().toLocaleString([], options);
|
||||||
|
|
||||||
if(locationInfo.country === undefined) {
|
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);
|
storage.connectedUsers.push(userData);
|
||||||
logInfo(`Web client \x1b[32mconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]\x1b[0m`);
|
logInfo(`Web client \x1b[32mconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]\x1b[0m`);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ let serverConfig = {
|
|||||||
tunePass: "",
|
tunePass: "",
|
||||||
adminPass: ""
|
adminPass: ""
|
||||||
},
|
},
|
||||||
|
plugins: [],
|
||||||
device: 'tef',
|
device: 'tef',
|
||||||
defaultFreq: 87.5,
|
defaultFreq: 87.5,
|
||||||
publicTuner: true,
|
publicTuner: true,
|
||||||
@@ -65,9 +66,10 @@ function deepMerge(target, source)
|
|||||||
}
|
}
|
||||||
|
|
||||||
function configUpdate(newConfig) {
|
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
|
// If new banlist is provided, replace the existing one
|
||||||
serverConfig.webserver.banlist = newConfig.webserver.banlist;
|
serverConfig.webserver.banlist = newConfig.webserver.banlist;
|
||||||
|
serverConfig.plugins = newConfig.plugins;
|
||||||
delete newConfig.webserver.banlist; // Remove banlist from newConfig to avoid merging
|
delete newConfig.webserver.banlist; // Remove banlist from newConfig to avoid merging
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,4 +7,5 @@
|
|||||||
@import url("panels.css"); /* Different panels and their sizes */
|
@import url("panels.css"); /* Different panels and their sizes */
|
||||||
@import url("modal.css"); /* Modal window */
|
@import url("modal.css"); /* Modal window */
|
||||||
@import url("setup.css"); /* Web setup interface */
|
@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 */
|
@import url("helpers.css"); /* Stuff that is used often such as text changers etc */
|
||||||
@@ -468,5 +468,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="js/webserver.js"></script>
|
<script src="js/webserver.js"></script>
|
||||||
|
<% plugins.forEach(function(plugin) { %>
|
||||||
|
<script src="js/plugins/<%= plugin %>"></script>
|
||||||
|
<% }); %>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ function OnConnectivityCallback(isConnected) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function OnPlayButtonClick(_ev) {
|
function OnPlayButtonClick(_ev) {
|
||||||
|
const $playbutton = $('.playbutton');
|
||||||
|
$playbutton.find('.fa-solid').toggleClass('fa-play fa-stop');
|
||||||
try {
|
try {
|
||||||
if (Stream.ConnectivityFlag) {
|
if (Stream.ConnectivityFlag) {
|
||||||
Stream.Stop();
|
Stream.Stop();
|
||||||
@@ -29,8 +31,6 @@ function OnPlayButtonClick(_ev) {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
$playbutton.removeClass('bg-gray').prop('disabled', false);
|
$playbutton.removeClass('bg-gray').prop('disabled', false);
|
||||||
}, 3000);
|
}, 3000);
|
||||||
const $playbutton = $('.playbutton');
|
|
||||||
$playbutton.find('.fa-solid').toggleClass('fa-play fa-stop');
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ function submitData() {
|
|||||||
const defaultTheme = themeDataValue;
|
const defaultTheme = themeDataValue;
|
||||||
|
|
||||||
const bgImage = $("#bg-image").val() || '';
|
const bgImage = $("#bg-image").val() || '';
|
||||||
|
const rdsMode = $('#rds-mode').is(":checked") || false;
|
||||||
|
|
||||||
const ant1enabled = $('#ant1-enabled').is(":checked") || false;
|
const ant1enabled = $('#ant1-enabled').is(":checked") || false;
|
||||||
const ant2enabled = $('#ant2-enabled').is(":checked") || false;
|
const ant2enabled = $('#ant2-enabled').is(":checked") || false;
|
||||||
@@ -71,6 +72,11 @@ function submitData() {
|
|||||||
const tunePass = $('#tune-pass').val();
|
const tunePass = $('#tune-pass').val();
|
||||||
const adminPass = $('#admin-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 publicTuner = $("#tuner-public").is(":checked");
|
||||||
const lockToAdmin = $("#tuner-lock").is(":checked");
|
const lockToAdmin = $("#tuner-lock").is(":checked");
|
||||||
const autoShutdown = $("#shutdown-tuner").is(":checked") || false;
|
const autoShutdown = $("#shutdown-tuner").is(":checked") || false;
|
||||||
@@ -88,7 +94,8 @@ function submitData() {
|
|||||||
defaultTheme,
|
defaultTheme,
|
||||||
presets,
|
presets,
|
||||||
banlist,
|
banlist,
|
||||||
bgImage
|
bgImage,
|
||||||
|
rdsMode,
|
||||||
},
|
},
|
||||||
antennas: {
|
antennas: {
|
||||||
enabled: antennasEnabled,
|
enabled: antennasEnabled,
|
||||||
@@ -136,6 +143,7 @@ function submitData() {
|
|||||||
tunePass,
|
tunePass,
|
||||||
adminPass,
|
adminPass,
|
||||||
},
|
},
|
||||||
|
plugins,
|
||||||
device,
|
device,
|
||||||
publicTuner,
|
publicTuner,
|
||||||
lockToAdmin,
|
lockToAdmin,
|
||||||
@@ -181,6 +189,7 @@ function submitData() {
|
|||||||
$("#chat-switch").prop("checked", data.webserver.chatEnabled || false);
|
$("#chat-switch").prop("checked", data.webserver.chatEnabled || false);
|
||||||
|
|
||||||
$('#selected-theme').val(data.webserver.defaultTheme || 'Default');
|
$('#selected-theme').val(data.webserver.defaultTheme || 'Default');
|
||||||
|
$('#rds-mode').prop("checked", data.webserver.rdsMode || false);
|
||||||
|
|
||||||
var selectedTheme = $(".option[data-value='" + data.webserver.defaultTheme + "']");
|
var selectedTheme = $(".option[data-value='" + data.webserver.defaultTheme + "']");
|
||||||
|
|
||||||
@@ -273,6 +282,14 @@ function submitData() {
|
|||||||
$("#shutdown-tuner").prop("checked", data.autoShutdown);
|
$("#shutdown-tuner").prop("checked", data.autoShutdown);
|
||||||
$("#antenna-switch").prop("checked", data.antennas?.enabled);
|
$("#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
|
// Check if latitude and longitude are present in the data
|
||||||
if (data.identification.lat && data.identification.lon) {
|
if (data.identification.lat && data.identification.lon) {
|
||||||
// Set the map's center to the received coordinates
|
// Set the map's center to the received coordinates
|
||||||
|
|||||||
@@ -54,3 +54,18 @@ const closeDropdownFromOutside = (event) => {
|
|||||||
$(document).on('click', closeDropdownFromOutside);
|
$(document).on('click', closeDropdownFromOutside);
|
||||||
$listOfOptions.on('click', selectOption);
|
$listOfOptions.on('click', selectOption);
|
||||||
$dropdowns.on('click', toggleDropdown);
|
$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;
|
||||||
|
});
|
||||||
@@ -3,7 +3,7 @@ var day = currentDate.getDate();
|
|||||||
var month = currentDate.getMonth() + 1; // Months are zero-indexed, so add 1
|
var month = currentDate.getMonth() + 1; // Months are zero-indexed, so add 1
|
||||||
var year = currentDate.getFullYear();
|
var year = currentDate.getFullYear();
|
||||||
var formattedDate = day + '/' + month + '/' + year;
|
var formattedDate = day + '/' + month + '/' + year;
|
||||||
var currentVersion = 'v1.1.9c [' + formattedDate + ']';
|
var currentVersion = 'v1.2.0 [' + formattedDate + ']';
|
||||||
|
|
||||||
getInitialSettings();
|
getInitialSettings();
|
||||||
|
|
||||||
@@ -20,6 +20,7 @@ function getInitialSettings() {
|
|||||||
localStorage.setItem('preset3', data.presets[2]);
|
localStorage.setItem('preset3', data.presets[2]);
|
||||||
localStorage.setItem('preset4', data.presets[3]);
|
localStorage.setItem('preset4', data.presets[3]);
|
||||||
localStorage.setItem('bgImage', data.bgImage);
|
localStorage.setItem('bgImage', data.bgImage);
|
||||||
|
localStorage.setItem('rdsMode', data.rdsMode);
|
||||||
},
|
},
|
||||||
error: function (error) {
|
error: function (error) {
|
||||||
console.error('Error:', error);
|
console.error('Error:', error);
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ const usa_programmes = [
|
|||||||
"Spanish Talk", "Spanish Music", "Hip Hop", "", "", "Weather", "Emergency Test", "Emergency"
|
"Spanish Talk", "Spanish Music", "Hip Hop", "", "", "Weather", "Emergency Test", "Emergency"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const rdsMode = localStorage.getItem('rdsMode');
|
||||||
|
|
||||||
$(document).ready(function () {
|
$(document).ready(function () {
|
||||||
var canvas = $('#signal-canvas')[0];
|
var canvas = $('#signal-canvas')[0];
|
||||||
|
|
||||||
@@ -359,7 +361,16 @@ function updateCanvas(parsedData, signalChart) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
socket.onmessage = (event) => {
|
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);
|
parsedData = JSON.parse(event.data);
|
||||||
|
|
||||||
updatePanels(parsedData);
|
updatePanels(parsedData);
|
||||||
if(localStorage.getItem("smoothSignal") == 'true') {
|
if(localStorage.getItem("smoothSignal") == 'true') {
|
||||||
const sum = signalData.reduce((acc, strNum) => acc + parseFloat(strNum), 0);
|
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($dataRt0, processString(parsedData.rt0, parsedData.rt0_errors));
|
||||||
updateHtmlIfChanged($dataRt1, processString(parsedData.rt1, parsedData.rt1_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) {
|
if (parsedData.rds === true) {
|
||||||
$flagDesktopCointainer.css('background-color', 'var(--color-2)');
|
$flagDesktopCointainer.css('background-color', 'var(--color-2)');
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
<li data-panel="connection">Connection</li>
|
<li data-panel="connection">Connection</li>
|
||||||
<li data-panel="audio">Audio</li>
|
<li data-panel="audio">Audio</li>
|
||||||
<li data-panel="webserver">Webserver</li>
|
<li data-panel="webserver">Webserver</li>
|
||||||
|
<li data-panel="plugins">Plugins</li>
|
||||||
<li data-panel="identification">Info & Map</li>
|
<li data-panel="identification">Info & Map</li>
|
||||||
<li class="logout-link text-gray"><i class="fas fa-sign-out"></i></li>
|
<li class="logout-link text-gray"><i class="fas fa-sign-out"></i></li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -59,7 +60,7 @@
|
|||||||
<th>IP Address</th>
|
<th>IP Address</th>
|
||||||
<th>Location</th>
|
<th>Location</th>
|
||||||
<th>Online since</th>
|
<th>Online since</th>
|
||||||
<!--<th></th>-->
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -69,7 +70,7 @@
|
|||||||
<td><%= user.ip %></td>
|
<td><%= user.ip %></td>
|
||||||
<td><%= user.location %></td>
|
<td><%= user.location %></td>
|
||||||
<td><%= user.time %></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>
|
</tr>
|
||||||
<% }); %>
|
<% }); %>
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
@@ -78,7 +79,32 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<% } %>
|
<% } %>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</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>
|
<h3>Console</h3>
|
||||||
<% if (consoleOutput && consoleOutput.length > 0) { %>
|
<% if (consoleOutput && consoleOutput.length > 0) { %>
|
||||||
@@ -91,37 +117,8 @@
|
|||||||
<p>No console output available.</p>
|
<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="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>
|
||||||
|
|
||||||
<div class="panel-100 tab-content" id="connection">
|
<div class="panel-100 tab-content" id="connection">
|
||||||
@@ -355,6 +352,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel-50 no-bg">
|
<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>
|
<h3>Presets</h3>
|
||||||
<p>You can set up to 4 presets.<br>These presets are accessible with the F1-F4 buttons.<br>
|
<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>
|
<span class="text-gray">Enter frequencies in MHz.</span></p>
|
||||||
@@ -399,6 +403,21 @@
|
|||||||
|
|
||||||
</div>
|
</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">
|
<div class="panel-100 tab-content" id="tuner">
|
||||||
<h2>Tuner Specific Settings</h2>
|
<h2>Tuner Specific Settings</h2>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|||||||
Reference in New Issue
Block a user