You've already forked fm-dx-webserver
mirror of
https://github.com/KubaPro010/fm-dx-webserver.git
synced 2026-02-27 06:23:53 +01:00
rewrite update
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -5,4 +5,5 @@ node_modules/
|
|||||||
/libraries/**
|
/libraries/**
|
||||||
/plugins/*
|
/plugins/*
|
||||||
!/plugins/example/frontend.js
|
!/plugins/example/frontend.js
|
||||||
!/plugins/example.js
|
!/plugins/example.js
|
||||||
|
/plugins_configs
|
||||||
|
|||||||
@@ -99,42 +99,59 @@ router.get('/wizard', (req, res) => {
|
|||||||
});
|
});
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
router.get('/setup', (req, res) => {
|
||||||
|
let serialPorts;
|
||||||
|
function loadConfig() {
|
||||||
|
if (fs.existsSync(configPath)) {
|
||||||
|
const configFileContents = fs.readFileSync(configPath, 'utf8');
|
||||||
|
return JSON.parse(configFileContents);
|
||||||
|
}
|
||||||
|
return serverConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!req.session.isAdminAuthenticated) {
|
||||||
|
res.render('login');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SerialPort.list()
|
||||||
|
.then((deviceList) => {
|
||||||
|
serialPorts = deviceList.map(port => ({
|
||||||
|
path: port.path,
|
||||||
|
friendlyName: port.friendlyName,
|
||||||
|
}));
|
||||||
|
|
||||||
|
parseAudioDevice((result) => {
|
||||||
|
const processUptimeInSeconds = Math.floor(process.uptime());
|
||||||
|
const formattedProcessUptime = helpers.formatUptime(processUptimeInSeconds);
|
||||||
|
|
||||||
|
const updatedConfig = loadConfig(); // Reload the config every time
|
||||||
|
res.render('setup', {
|
||||||
|
isAdminAuthenticated: req.session.isAdminAuthenticated,
|
||||||
|
videoDevices: result.audioDevices,
|
||||||
|
audioDevices: result.videoDevices,
|
||||||
|
serialPorts: serialPorts,
|
||||||
|
memoryUsage: (process.memoryUsage.rss() / 1024 / 1024).toFixed(1) + ' MB',
|
||||||
|
processUptime: formattedProcessUptime,
|
||||||
|
consoleOutput: logs,
|
||||||
|
plugins: allPluginConfigs,
|
||||||
|
enabledPlugins: updatedConfig.plugins,
|
||||||
|
onlineUsers: dataHandler.dataToSend.users,
|
||||||
|
connectedUsers: storage.connectedUsers,
|
||||||
|
banlist: updatedConfig.webserver.banlist // Updated banlist from the latest config
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
router.get('/setup', (req, res) => {
|
router.get('/rds', (req, res) => {
|
||||||
let serialPorts;
|
res.send('Please connect using a WebSocket compatible app to obtain RDS stream.');
|
||||||
|
});
|
||||||
|
|
||||||
if(!req.session.isAdminAuthenticated) {
|
router.get('/rdsspy', (req, res) => {
|
||||||
res.render('login');
|
res.send('Please connect using a WebSocket compatible app to obtain RDS stream.');
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SerialPort.list()
|
|
||||||
.then((deviceList) => {
|
|
||||||
serialPorts = deviceList.map(port => ({
|
|
||||||
path: port.path,
|
|
||||||
friendlyName: port.friendlyName,
|
|
||||||
}));
|
|
||||||
|
|
||||||
parseAudioDevice((result) => {
|
|
||||||
const processUptimeInSeconds = Math.floor(process.uptime());
|
|
||||||
const formattedProcessUptime = helpers.formatUptime(processUptimeInSeconds);
|
|
||||||
|
|
||||||
res.render('setup', {
|
|
||||||
isAdminAuthenticated: req.session.isAdminAuthenticated,
|
|
||||||
videoDevices: result.audioDevices,
|
|
||||||
audioDevices: result.videoDevices,
|
|
||||||
serialPorts: serialPorts,
|
|
||||||
memoryUsage: (process.memoryUsage.rss() / 1024 / 1024).toFixed(1) + ' MB',
|
|
||||||
processUptime: formattedProcessUptime,
|
|
||||||
consoleOutput: logs,
|
|
||||||
plugins: allPluginConfigs,
|
|
||||||
enabledPlugins: serverConfig.plugins,
|
|
||||||
onlineUsers: dataHandler.dataToSend.users,
|
|
||||||
connectedUsers: storage.connectedUsers
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
router.get('/rds', (req, res) => {
|
router.get('/rds', (req, res) => {
|
||||||
@@ -194,6 +211,17 @@ router.get('/kick', (req, res) => {
|
|||||||
}, 500);
|
}, 500);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
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;
|
||||||
@@ -260,6 +288,8 @@ router.get('/static_data', (req, res) => {
|
|||||||
defaultTheme: serverConfig.webserver.defaultTheme || 'theme1',
|
defaultTheme: serverConfig.webserver.defaultTheme || 'theme1',
|
||||||
bgImage: serverConfig.webserver.bgImage || '',
|
bgImage: serverConfig.webserver.bgImage || '',
|
||||||
rdsMode: serverConfig.webserver.rdsMode || false,
|
rdsMode: serverConfig.webserver.rdsMode || false,
|
||||||
|
tunerName: serverConfig.identification.tunerName || '',
|
||||||
|
tunerDesc: serverConfig.identification.tunerDesc || '',
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
|
const https = require('https');
|
||||||
|
const net = require('net');
|
||||||
|
const crypto = require('crypto');
|
||||||
const dataHandler = require('./datahandler');
|
const dataHandler = require('./datahandler');
|
||||||
const storage = require('./storage');
|
const storage = require('./storage');
|
||||||
const consoleCmd = require('./console');
|
const consoleCmd = require('./console');
|
||||||
|
const { serverConfig, configExists, configSave } = require('./server_config');
|
||||||
|
|
||||||
function parseMarkdown(parsed) {
|
function parseMarkdown(parsed) {
|
||||||
parsed = parsed.replace(/<\/?[^>]+(>|$)/g, '');
|
parsed = parsed.replace(/<\/?[^>]+(>|$)/g, '');
|
||||||
@@ -40,6 +44,56 @@ function removeMarkdown(parsed) {
|
|||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function authenticateWithXdrd(client, salt, password) {
|
||||||
|
const sha1 = crypto.createHash('sha1');
|
||||||
|
const saltBuffer = Buffer.from(salt, 'utf-8');
|
||||||
|
const passwordBuffer = Buffer.from(password, 'utf-8');
|
||||||
|
sha1.update(saltBuffer);
|
||||||
|
sha1.update(passwordBuffer);
|
||||||
|
|
||||||
|
const hashedPassword = sha1.digest('hex');
|
||||||
|
client.write(hashedPassword + '\n');
|
||||||
|
client.write('x\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleConnect(clientIp, currentUsers, ws) {
|
||||||
|
https.get(`https://ipinfo.io/${clientIp}/json`, (response) => {
|
||||||
|
let data = '';
|
||||||
|
|
||||||
|
response.on('data', (chunk) => {
|
||||||
|
data += chunk;
|
||||||
|
});
|
||||||
|
|
||||||
|
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.org?.includes("AS205016 HERN Labs AB")) { // anti opera VPN block
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(locationInfo.country === undefined) {
|
||||||
|
const userData = { ip: clientIp, location: 'Unknown', time: connectionTime, instance: ws };
|
||||||
|
storage.connectedUsers.push(userData);
|
||||||
|
consoleCmd.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, instance: ws };
|
||||||
|
storage.connectedUsers.push(userData);
|
||||||
|
consoleCmd.logInfo(`Web client \x1b[32mconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]\x1b[0m Location: ${locationInfo.city}, ${locationInfo.region}, ${locationInfo.country}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
consoleCmd.logInfo(`Web client \x1b[32mconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]\x1b[0m`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).on('error', (err) => {
|
||||||
|
consoleCmd.chunklogInfo(`Web client \x1b[32mconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]\x1b[0m`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function formatUptime(uptimeInSeconds) {
|
function formatUptime(uptimeInSeconds) {
|
||||||
const secondsInMinute = 60;
|
const secondsInMinute = 60;
|
||||||
const secondsInHour = secondsInMinute * 60;
|
const secondsInHour = secondsInMinute * 60;
|
||||||
@@ -93,6 +147,79 @@ function kickClient(ipAddress) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkIPv6Support(callback) {
|
||||||
|
const server = net.createServer();
|
||||||
|
|
||||||
|
server.listen(0, '::1', () => {
|
||||||
|
server.close(() => callback(true));
|
||||||
|
}).on('error', (error) => {
|
||||||
|
if (error.code === 'EADDRNOTAVAIL') {
|
||||||
|
callback(false);
|
||||||
|
} else {
|
||||||
|
callback(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, lengthCommands, endpointName) {
|
||||||
|
const command = message.toString();
|
||||||
|
const now = Date.now();
|
||||||
|
if (endpointName === 'text') consoleCmd.logDebug(`Command received from \x1b[90m${clientIp}\x1b[0m: ${command}`);
|
||||||
|
|
||||||
|
// Initialize user command history if not present
|
||||||
|
if (!userCommandHistory[clientIp]) {
|
||||||
|
userCommandHistory[clientIp] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record the current timestamp for the user
|
||||||
|
userCommandHistory[clientIp].push(now);
|
||||||
|
|
||||||
|
// Remove timestamps older than 20 ms from the history
|
||||||
|
userCommandHistory[clientIp] = userCommandHistory[clientIp].filter(timestamp => now - timestamp <= 20);
|
||||||
|
|
||||||
|
// Check if there are 8 or more commands in the last 20 ms
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
ws.close(1008, 'Bot-like behavior detected');
|
||||||
|
return command; // Return command value before closing connection
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the last message time for general spam detection
|
||||||
|
lastMessageTime = now;
|
||||||
|
|
||||||
|
// Initialize command history for rate-limiting checks
|
||||||
|
if (!userCommands[command]) {
|
||||||
|
userCommands[command] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record the current timestamp for this command
|
||||||
|
userCommands[command].push(now);
|
||||||
|
|
||||||
|
// Remove timestamps older than 1 second
|
||||||
|
userCommands[command] = userCommands[command].filter(timestamp => now - timestamp <= 1000);
|
||||||
|
|
||||||
|
// If command count exceeds limit, close connection
|
||||||
|
if (userCommands[command].length > lengthCommands) {
|
||||||
|
if (now - lastWarn.time > 1000) { // Check if 1 second has passed
|
||||||
|
consoleCmd.logWarn(`User \x1b[90m${clientIp}\x1b[0m is spamming command "${command}" in /${endpointName}. Connection will be terminated.`);
|
||||||
|
lastWarn.time = now; // Update the last warning time
|
||||||
|
}
|
||||||
|
ws.close(1008, 'Spamming detected');
|
||||||
|
return command; // Return command value before closing connection
|
||||||
|
}
|
||||||
|
|
||||||
|
return command; // Return command value for normal execution
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
parseMarkdown, removeMarkdown, formatUptime, resolveDataBuffer, kickClient
|
authenticateWithXdrd, parseMarkdown, handleConnect, removeMarkdown, formatUptime, resolveDataBuffer, kickClient, checkIPv6Support, antispamProtection
|
||||||
}
|
}
|
||||||
196
server/index.js
196
server/index.js
@@ -5,7 +5,6 @@ const session = require('express-session');
|
|||||||
const bodyParser = require('body-parser');
|
const bodyParser = require('body-parser');
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
const httpProxy = require('http-proxy');
|
const httpProxy = require('http-proxy');
|
||||||
const https = require('https');
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const httpServer = http.createServer(app);
|
const httpServer = http.createServer(app);
|
||||||
const WebSocket = require('ws');
|
const WebSocket = require('ws');
|
||||||
@@ -17,7 +16,6 @@ const fs = require('fs');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const net = require('net');
|
const net = require('net');
|
||||||
const client = new net.Socket();
|
const client = new net.Socket();
|
||||||
const crypto = require('crypto');
|
|
||||||
const { SerialPort } = require('serialport');
|
const { SerialPort } = require('serialport');
|
||||||
const tunnel = require('./tunnel')
|
const tunnel = require('./tunnel')
|
||||||
|
|
||||||
@@ -113,21 +111,6 @@ connectToXdrd();
|
|||||||
connectToSerial();
|
connectToSerial();
|
||||||
tunnel.connect();
|
tunnel.connect();
|
||||||
|
|
||||||
// Check for working IPv6
|
|
||||||
function checkIPv6Support(callback) {
|
|
||||||
const server = net.createServer();
|
|
||||||
|
|
||||||
server.listen(0, '::1', () => {
|
|
||||||
server.close(() => callback(true));
|
|
||||||
}).on('error', (error) => {
|
|
||||||
if (error.code === 'EADDRNOTAVAIL') {
|
|
||||||
callback(false);
|
|
||||||
} else {
|
|
||||||
callback(false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialport retry code when port is open but communication is lost (additional code in datahandler.js)
|
// Serialport retry code when port is open but communication is lost (additional code in datahandler.js)
|
||||||
isSerialportRetrying = false;
|
isSerialportRetrying = false;
|
||||||
|
|
||||||
@@ -250,7 +233,7 @@ function connectToXdrd() {
|
|||||||
if (authFlags.receivedPassword === false) {
|
if (authFlags.receivedPassword === false) {
|
||||||
authFlags.receivedSalt = line.trim();
|
authFlags.receivedSalt = line.trim();
|
||||||
authFlags.receivedPassword = true;
|
authFlags.receivedPassword = true;
|
||||||
authenticateWithXdrd(client, authFlags.receivedSalt, xdrd.xdrdPassword);
|
helpers.authenticateWithXdrd(client, authFlags.receivedSalt, xdrd.xdrdPassword);
|
||||||
} else {
|
} else {
|
||||||
if (line.startsWith('a')) {
|
if (line.startsWith('a')) {
|
||||||
authFlags.authMsg = true;
|
authFlags.authMsg = true;
|
||||||
@@ -333,18 +316,6 @@ client.on('error', (err) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function authenticateWithXdrd(client, salt, password) {
|
|
||||||
const sha1 = crypto.createHash('sha1');
|
|
||||||
const saltBuffer = Buffer.from(salt, 'utf-8');
|
|
||||||
const passwordBuffer = Buffer.from(password, 'utf-8');
|
|
||||||
sha1.update(saltBuffer);
|
|
||||||
sha1.update(passwordBuffer);
|
|
||||||
|
|
||||||
const hashedPassword = sha1.digest('hex');
|
|
||||||
client.write(hashedPassword + '\n');
|
|
||||||
client.write('x\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
app.set('view engine', 'ejs');
|
app.set('view engine', 'ejs');
|
||||||
app.set('views', path.join(__dirname, '../web'));
|
app.set('views', path.join(__dirname, '../web'));
|
||||||
app.use('/', endpoints);
|
app.use('/', endpoints);
|
||||||
@@ -435,50 +406,15 @@ wss.on('connection', (ws, request) => {
|
|||||||
serverConfig.xdrd.wirelessConnection === true ? connectToXdrd() : serialport.write('x\n');
|
serverConfig.xdrd.wirelessConnection === true ? connectToXdrd() : serialport.write('x\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
https.get(`https://ipinfo.io/${clientIp}/json`, (response) => {
|
helpers.handleConnect(clientIp, currentUsers, ws);
|
||||||
let data = '';
|
|
||||||
|
|
||||||
response.on('data', (chunk) => {
|
|
||||||
data += chunk;
|
|
||||||
});
|
|
||||||
|
|
||||||
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.org?.includes("AS205016 HERN Labs AB")) { // anti opera VPN block
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(locationInfo.country === undefined) {
|
|
||||||
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 {
|
|
||||||
const userLocation = `${locationInfo.city}, ${locationInfo.region}, ${locationInfo.country}`;
|
|
||||||
const userData = { ip: clientIp, location: userLocation, time: connectionTime, instance: ws };
|
|
||||||
storage.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) {
|
|
||||||
logInfo(`Web client \x1b[32mconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]\x1b[0m`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}).on('error', (err) => {
|
|
||||||
logInfo(`Web client \x1b[32mconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]\x1b[0m`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Anti-spam tracking for each client
|
// Anti-spam tracking for each client
|
||||||
const userCommands = {};
|
const userCommands = {};
|
||||||
let lastWarn = { time: 0 };
|
let lastWarn = { time: 0 };
|
||||||
|
|
||||||
ws.on('message', (message) => {
|
ws.on('message', (message) => {
|
||||||
// Anti-spam
|
const command = helpers.antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, '18', 'text');
|
||||||
const command = antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, '18', 'text');
|
|
||||||
|
|
||||||
// Existing command processing logic
|
|
||||||
if (((command.startsWith('X') || command.startsWith('Y')) && !request.session.isAdminAuthenticated) ||
|
if (((command.startsWith('X') || command.startsWith('Y')) && !request.session.isAdminAuthenticated) ||
|
||||||
((command.startsWith('F') || command.startsWith('W')) && serverConfig.bwSwitch === false)) {
|
((command.startsWith('F') || command.startsWith('W')) && serverConfig.bwSwitch === false)) {
|
||||||
logWarn(`User \x1b[90m${clientIp}\x1b[0m attempted to send a potentially dangerous command. You may consider blocking this user.`);
|
logWarn(`User \x1b[90m${clientIp}\x1b[0m attempted to send a potentially dangerous command. You may consider blocking this user.`);
|
||||||
@@ -491,20 +427,11 @@ wss.on('connection', (ws, request) => {
|
|||||||
|
|
||||||
if (command.startsWith('w') && request.session.isAdminAuthenticated) {
|
if (command.startsWith('w') && request.session.isAdminAuthenticated) {
|
||||||
switch (command) {
|
switch (command) {
|
||||||
case 'wL1':
|
case 'wL1': serverConfig.lockToAdmin = true; break;
|
||||||
serverConfig.lockToAdmin = true;
|
case 'wL0': serverConfig.lockToAdmin = false; break;
|
||||||
break;
|
case 'wT0': serverConfig.publicTuner = true; break;
|
||||||
case 'wL0':
|
case 'wT1': serverConfig.publicTuner = false; break;
|
||||||
serverConfig.lockToAdmin = false;
|
default: break;
|
||||||
break;
|
|
||||||
case 'wT0':
|
|
||||||
serverConfig.publicTuner = true;
|
|
||||||
break;
|
|
||||||
case 'wT1':
|
|
||||||
serverConfig.publicTuner = false;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -519,19 +446,9 @@ wss.on('connection', (ws, request) => {
|
|||||||
|
|
||||||
const { isAdminAuthenticated, isTuneAuthenticated } = request.session || {};
|
const { isAdminAuthenticated, isTuneAuthenticated } = request.session || {};
|
||||||
|
|
||||||
if (serverConfig.publicTuner && !serverConfig.lockToAdmin) {
|
if (serverConfig.publicTuner || (serverConfig.lockToAdmin && isAdminAuthenticated) || (!serverConfig.lockToAdmin && isTuneAuthenticated)) {
|
||||||
output.write(`${command}\n`);
|
output.write(`${command}\n`);
|
||||||
} else {
|
}
|
||||||
if (serverConfig.lockToAdmin) {
|
|
||||||
if(isAdminAuthenticated) {
|
|
||||||
output.write(`${command}\n`);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if(isTuneAuthenticated) {
|
|
||||||
output.write(`${command}\n`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -600,55 +517,92 @@ chatWss.on('connection', (ws, request) => {
|
|||||||
|
|
||||||
ws.on('message', function incoming(message) {
|
ws.on('message', function incoming(message) {
|
||||||
// Anti-spam
|
// Anti-spam
|
||||||
const command = antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, '5', 'chat');
|
const command = helpers.antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, '5', 'chat');
|
||||||
|
|
||||||
let messageData;
|
let messageData;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
messageData = JSON.parse(message);
|
messageData = JSON.parse(message);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error("Failed to parse message:", error);
|
|
||||||
// Optionally, send an error response back to the client
|
|
||||||
ws.send(JSON.stringify({ error: "Invalid message format" }));
|
ws.send(JSON.stringify({ error: "Invalid message format" }));
|
||||||
return; // Stop processing if JSON parsing fails
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
messageData.ip = clientIp; // Adding IP address to the message object
|
messageData.ip = clientIp;
|
||||||
const currentTime = new Date();
|
const currentTime = new Date();
|
||||||
|
|
||||||
const hours = String(currentTime.getHours()).padStart(2, '0');
|
const hours = String(currentTime.getHours()).padStart(2, '0');
|
||||||
const minutes = String(currentTime.getMinutes()).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
|
messageData.time = `${hours}:${minutes}`; // Adding current time to the message object in hours:minutes format
|
||||||
|
|
||||||
if (serverConfig.webserver.banlist?.includes(clientIp)) {
|
if (serverConfig.webserver.banlist?.includes(clientIp)) { return; }
|
||||||
return;
|
if (request.session.isAdminAuthenticated === true) { messageData.admin = true; }
|
||||||
}
|
if (messageData.message.length > 255) { messageData.message = messageData.message.substring(0, 255); }
|
||||||
|
|
||||||
if (request.session.isAdminAuthenticated === true) {
|
|
||||||
messageData.admin = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (messageData.message.length > 255) {
|
|
||||||
messageData.message = messageData.message.substring(0, 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
storage.chatHistory.push(messageData);
|
storage.chatHistory.push(messageData);
|
||||||
if (storage.chatHistory.length > 50) {
|
if (storage.chatHistory.length > 50) { storage.chatHistory.shift(); }
|
||||||
storage.chatHistory.shift();
|
|
||||||
}
|
|
||||||
logChat(messageData);
|
logChat(messageData);
|
||||||
|
|
||||||
const modifiedMessage = JSON.stringify(messageData);
|
|
||||||
|
|
||||||
chatWss.clients.forEach(function each(client) {
|
chatWss.clients.forEach(function each(client) {
|
||||||
if (client.readyState === WebSocket.OPEN) {
|
if (client.readyState === WebSocket.OPEN) {
|
||||||
client.send(modifiedMessage);
|
// Only include IP for admin clients
|
||||||
|
let responseMessage = { ...messageData };
|
||||||
|
|
||||||
|
if (request.session.isAdminAuthenticated !== true) {
|
||||||
|
delete responseMessage.ip;
|
||||||
|
}
|
||||||
|
|
||||||
|
const modifiedMessage = JSON.stringify(responseMessage);
|
||||||
|
client.send(modifiedMessage);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.on('close', function close() {
|
ws.on('close', function close() {});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Additional web socket for using plugins
|
||||||
|
pluginsWss.on('connection', (ws, request) => {
|
||||||
|
const clientIp = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
|
||||||
|
const userCommandHistory = {};
|
||||||
|
if (serverConfig.webserver.banlist?.includes(clientIp)) {
|
||||||
|
ws.close(1008, 'Banned IP');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Anti-spam tracking for each client
|
||||||
|
const userCommands = {};
|
||||||
|
let lastWarn = { time: 0 };
|
||||||
|
|
||||||
|
ws.on('message', message => {
|
||||||
|
// Anti-spam
|
||||||
|
const command = helpers.antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, '10', 'data_plugins');
|
||||||
|
|
||||||
|
let messageData;
|
||||||
|
|
||||||
|
try {
|
||||||
|
messageData = JSON.parse(message); // Attempt to parse the JSON
|
||||||
|
} catch (error) {
|
||||||
|
// console.error("Failed to parse message:", error); // Log the error
|
||||||
|
return; // Exit if parsing fails
|
||||||
|
}
|
||||||
|
|
||||||
|
const modifiedMessage = JSON.stringify(messageData);
|
||||||
|
|
||||||
|
// Broadcast the message to all other clients
|
||||||
|
pluginsWss.clients.forEach(client => {
|
||||||
|
if (client.readyState === WebSocket.OPEN) {
|
||||||
|
client.send(modifiedMessage); // Send the message to all clients
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('close', () => {
|
||||||
|
// logInfo('WebSocket Extra connection closed'); // Use custom logInfo function
|
||||||
|
});
|
||||||
|
|
||||||
|
ws.on('error', error => {
|
||||||
|
logError('WebSocket Extra error: ' + error); // Use custom logError function
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Additional web socket for using plugins
|
// Additional web socket for using plugins
|
||||||
@@ -729,7 +683,7 @@ httpServer.on('upgrade', (request, socket, head) => {
|
|||||||
|
|
||||||
ws.on('message', function incoming(message) {
|
ws.on('message', function incoming(message) {
|
||||||
// Anti-spam
|
// Anti-spam
|
||||||
const command = antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, '5', 'rds');
|
const command = helpers.antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, '5', 'rds');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -746,9 +700,9 @@ httpServer.on('upgrade', (request, socket, head) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.use(express.static(path.join(__dirname, '../web'))); // Serve the entire web folder to the user
|
app.use(express.static(path.join(__dirname, '../web'))); // Serve the entire web folder to the user
|
||||||
|
fmdxList.update();
|
||||||
|
|
||||||
// Determine ip stack support and start server accordingly
|
helpers.checkIPv6Support((isIPv6Supported) => {
|
||||||
checkIPv6Support((isIPv6Supported) => {
|
|
||||||
const ipv4Address = serverConfig.webserver.webserverIp === '0.0.0.0' ? 'localhost' : serverConfig.webserver.webserverIp;
|
const ipv4Address = serverConfig.webserver.webserverIp === '0.0.0.0' ? 'localhost' : serverConfig.webserver.webserverIp;
|
||||||
const ipv6Address = '::'; // This will bind to all available IPv6 interfaces
|
const ipv6Address = '::'; // This will bind to all available IPv6 interfaces
|
||||||
const port = serverConfig.webserver.webserverPort;
|
const port = serverConfig.webserver.webserverPort;
|
||||||
@@ -775,5 +729,3 @@ checkIPv6Support((isIPv6Supported) => {
|
|||||||
startServer(ipv4Address, false); // Start only on IPv4
|
startServer(ipv4Address, false); // Start only on IPv4
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
fmdxList.update();
|
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ $(document).ready(function() {
|
|||||||
} else {
|
} else {
|
||||||
const chatMessage = `
|
const chatMessage = `
|
||||||
<span class="color-2">[${messageData.time}]</span>
|
<span class="color-2">[${messageData.time}]</span>
|
||||||
${isAdmin} <strong class="color-5" title="IP Address: ${messageData.ip}">${messageData.nickname}</strong>:
|
${isAdmin} <strong class="color-5" title="${typeof messageData.ip !== "undefined" ? 'IP Address: ' + messageData.ip : ''}">${messageData.nickname}</strong>:
|
||||||
<span style="color: var(--color-text-2);">${$('<div/>').text(messageData.message).html()}</span><br>
|
<span style="color: var(--color-text-2);">${$('<div/>').text(messageData.message).html()}</span><br>
|
||||||
`;
|
`;
|
||||||
chatMessages.append(chatMessage);
|
chatMessages.append(chatMessage);
|
||||||
|
|||||||
@@ -50,14 +50,6 @@ function populateFields(data, prefix = "") {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === "banlist" && Array.isArray(value)) {
|
|
||||||
const $textarea = $(`#${prefix}${prefix ? "-" : ""}${key}`);
|
|
||||||
if ($textarea.length && $textarea.is("textarea")) {
|
|
||||||
$textarea.val(value.join("\n"));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = `${prefix}${prefix ? "-" : ""}${key}`;
|
const id = `${prefix}${prefix ? "-" : ""}${key}`;
|
||||||
const $element = $(`#${id}`);
|
const $element = $(`#${id}`);
|
||||||
|
|
||||||
@@ -113,14 +105,6 @@ function updateConfigData(data, prefix = "") {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key === "banlist") {
|
|
||||||
const $textarea = $(`#${prefix}${prefix ? "-" : ""}${key}`);
|
|
||||||
if ($textarea.length && $textarea.is("textarea")) {
|
|
||||||
data[key] = $textarea.val().split("\n").filter(line => line.trim() !== "");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (key === "plugins") {
|
if (key === "plugins") {
|
||||||
data[key] = [];
|
data[key] = [];
|
||||||
const $selectedOptions = $element.find('option:selected');
|
const $selectedOptions = $element.find('option:selected');
|
||||||
|
|||||||
188
web/setup.ejs
188
web/setup.ejs
@@ -30,11 +30,14 @@
|
|||||||
<li role="tab" data-panel="webserver" tabindex="0">
|
<li role="tab" data-panel="webserver" tabindex="0">
|
||||||
<a href="#" role="tab" tabindex="-1" aria-controls="webserver"><i class="fa-solid fa-fw fa-server"></i> Webserver</a>
|
<a href="#" role="tab" tabindex="-1" aria-controls="webserver"><i class="fa-solid fa-fw fa-server"></i> Webserver</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li role="tab" data-panel="identification" tabindex="0">
|
||||||
|
<a href="#" role="tab" tabindex="-1" aria-controls="identification"><i class="fa-solid fa-fw fa-circle-info"></i> Identification & Map</a>
|
||||||
|
</li>
|
||||||
<li role="tab" data-panel="plugins" tabindex="0">
|
<li role="tab" data-panel="plugins" tabindex="0">
|
||||||
<a href="#" role="tab" tabindex="-1" aria-controls="plugins"><i class="fa-solid fa-fw fa-puzzle-piece"></i> Plugins</a>
|
<a href="#" role="tab" tabindex="-1" aria-controls="plugins"><i class="fa-solid fa-fw fa-puzzle-piece"></i> Plugins</a>
|
||||||
</li>
|
</li>
|
||||||
<li role="tab" data-panel="identification" tabindex="0">
|
<li role="tab" data-panel="users" tabindex="0">
|
||||||
<a href="#" role="tab" tabindex="-1" aria-controls="identification"><i class="fa-solid fa-fw fa-circle-info"></i> Identification & Map</a>
|
<a href="#" role="tab" tabindex="-1" aria-controls="users"><i class="fa-solid fa-fw fa-user"></i> User management</a>
|
||||||
</li>
|
</li>
|
||||||
<li role="tab" data-panel="extras" tabindex="0">
|
<li role="tab" data-panel="extras" tabindex="0">
|
||||||
<a href="#" role="tab" tabindex="-1" aria-controls="extras"><i class="fa-solid fa-fw fa-star"></i> Extras</a>
|
<a href="#" role="tab" tabindex="-1" aria-controls="extras"><i class="fa-solid fa-fw fa-star"></i> Extras</a>
|
||||||
@@ -285,6 +288,7 @@
|
|||||||
<h3>RDS Mode</h3>
|
<h3>RDS Mode</h3>
|
||||||
<p>You can switch between American (RBDS) / Global (RDS) mode here.</p>
|
<p>You can switch between American (RBDS) / Global (RDS) mode here.</p>
|
||||||
<%- include('_components', {component: 'checkbox', cssClass: 'bottom-20', iconClass: '', label: 'American RDS mode (RBDS)', id: 'webserver-rdsMode'}) %><br>
|
<%- include('_components', {component: 'checkbox', cssClass: 'bottom-20', iconClass: '', label: 'American RDS mode (RBDS)', id: 'webserver-rdsMode'}) %><br>
|
||||||
|
<<<<<<< HEAD
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-33">
|
<div class="panel-33">
|
||||||
<h3>Chat options</h3>
|
<h3>Chat options</h3>
|
||||||
@@ -300,6 +304,8 @@
|
|||||||
<textarea id="webserver-banlist" placeholder="123.45.67.8"></textarea>
|
<textarea id="webserver-banlist" placeholder="123.45.67.8"></textarea>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
=======
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -318,6 +324,7 @@
|
|||||||
<% }); %>
|
<% }); %>
|
||||||
</select><br><br>
|
</select><br><br>
|
||||||
<a href="https://github.com/NoobishSVK/fm-dx-webserver/wiki/Plugin-List" target="_blank">Download new plugins here!</a>
|
<a href="https://github.com/NoobishSVK/fm-dx-webserver/wiki/Plugin-List" target="_blank">Download new plugins here!</a>
|
||||||
|
>>>>>>> 4b6d011 (rewrite update)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel-100 p-bottom-20">
|
<div class="panel-100 p-bottom-20">
|
||||||
@@ -414,7 +421,121 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="panel-full m-0 tab-content no-bg" id="plugins" role="tabpanel">
|
||||||
|
<h2>Plugins</h2>
|
||||||
|
<div class="panel-100 p-bottom-20">
|
||||||
|
<h3>Plugin list</h3>
|
||||||
|
<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 p-bottom-20">
|
||||||
|
<h3>Plugin settings</h3>
|
||||||
|
<div id="plugin-settings">No plugin settings are available.</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel-full m-0 tab-content no-bg" id="tuner" role="tabpanel">
|
||||||
|
<h2>Tuner settings</h2>
|
||||||
|
<div class="flex-container contains-dropdown">
|
||||||
|
<div class="panel-33 p-bottom-20">
|
||||||
|
<h3>Device type</h3>
|
||||||
|
<%- 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' }
|
||||||
|
]
|
||||||
|
}) %><br>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel-33 p-bottom-20" style="padding-right: 20px; padding-left: 20px;">
|
||||||
|
<h3>Connection type</h3>
|
||||||
|
<p class="text-gray">If you want to choose the COM port directly, choose "Direct".<br>If you use xdrd or your receiver is connected via Wi-Fi, choose TCP/IP.</p>
|
||||||
|
<div class="auto top-10">
|
||||||
|
<label class="toggleSwitch nolabel" onclick="">
|
||||||
|
<input id="xdrd-wirelessConnection" type="checkbox" tabindex="0" aria-label="Connection type"/>
|
||||||
|
<a></a>
|
||||||
|
<span>
|
||||||
|
<span class="left-span">Direct</span>
|
||||||
|
<span class="right-span">TCP/IP</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="panel-33 p-bottom-20">
|
||||||
|
<h3>Device / Server</h3>
|
||||||
|
|
||||||
|
<div id="tuner-usb">
|
||||||
|
<p class="text-gray">Choose your desired <strong>COM port</strong><br> </p>
|
||||||
|
<%- 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}`
|
||||||
|
}))
|
||||||
|
}) %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
|
<div id="tuner-wireless">
|
||||||
|
<p class="text-gray">If you are connecting your tuner <strong>wirelessly</strong>, enter the tuner IP. <br> If you use <strong>xdrd</strong>, use 127.0.0.1 as your IP.</p>
|
||||||
|
<%- 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}) %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-container">
|
||||||
|
<div class="panel-50 p-bottom-20">
|
||||||
|
<h3>Startup</h3>
|
||||||
|
<h4>Startup volume</h4>
|
||||||
|
<div class="panel-75 auto" style="height: 48px;">
|
||||||
|
<input type="range" id="audio-startupVolume" min="0" max="1" step="0.01" value="1" aria-label="Startup Volume slider">
|
||||||
|
</div>
|
||||||
|
<h4 class="top-10 text-gray" id="volume-percentage-value"></h4>
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<h4 class="bottom-20">Default frequency</h4>
|
||||||
|
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Default frequency for first client', id: 'enableDefaultFreq'}) %><br>
|
||||||
|
<%- include('_components', {component: 'text', cssClass: 'w-100', placeholder: '87.5', label: 'Default frequency', id: 'defaultFreq'}) %>
|
||||||
|
</div>
|
||||||
|
<div class="panel-50 p-bottom-20">
|
||||||
|
<h3>Miscellaneous</h3>
|
||||||
|
<div class="flex-container">
|
||||||
|
<div class="panel-50 no-bg">
|
||||||
|
<h4>Bandwidth switch</h4>
|
||||||
|
<p>Bandwidth switch allows the user to set the bandwidth manually.</p>
|
||||||
|
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Bandwidth switch', id: 'bwSwitch'}) %><br>
|
||||||
|
</div>
|
||||||
|
<div class="panel-50 no-bg">
|
||||||
|
<h4>Automatic shutdown</h4>
|
||||||
|
<p>Toggling this option will put the tuner to sleep when no clients are connected.</p>
|
||||||
|
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Auto-shutdown', id: 'autoShutdown'}) %><br>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
=======
|
||||||
|
>>>>>>> 4b6d011 (rewrite update)
|
||||||
<div class="panel-full m-0 tab-content no-bg" id="identification" role="tabpanel">
|
<div class="panel-full m-0 tab-content no-bg" id="identification" role="tabpanel">
|
||||||
<h2>Identification & Map</h2>
|
<h2>Identification & Map</h2>
|
||||||
|
|
||||||
@@ -457,6 +578,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<<<<<<< HEAD
|
||||||
<div class="panel-full m-0 tab-content no-bg" id="extras" role="tabpanel">
|
<div class="panel-full m-0 tab-content no-bg" id="extras" role="tabpanel">
|
||||||
<h2>Extras</h2>
|
<h2>Extras</h2>
|
||||||
<div class="panel-100 p-bottom-20">
|
<div class="panel-100 p-bottom-20">
|
||||||
@@ -465,6 +587,68 @@
|
|||||||
Your server also needs to have a valid UUID, which is obtained by registering on maps in the <strong>Identification & Map</strong> tab.</p>
|
Your server also needs to have a valid UUID, which is obtained by registering on maps in the <strong>Identification & Map</strong> tab.</p>
|
||||||
<%- include('_components', {component: 'checkbox', cssClass: 'm-right-10', label: 'FMLIST integration', id: 'extras-fmlistIntegration'}) %><br>
|
<%- include('_components', {component: 'checkbox', cssClass: 'm-right-10', label: 'FMLIST integration', id: 'extras-fmlistIntegration'}) %><br>
|
||||||
|
|
||||||
|
=======
|
||||||
|
<div class="panel-full m-0 tab-content no-bg" id="users" role="tabpanel">
|
||||||
|
<h2>User management</h2>
|
||||||
|
<div class="panel-100">
|
||||||
|
<h3>Chat options</h3>
|
||||||
|
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Chat', id: 'webserver-chatEnabled'}) %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel-100 p-bottom-20">
|
||||||
|
<h3>Banlist</h3>
|
||||||
|
<p>If you have users that don't behave on your server, you can choose to ban them by their IP address.<br>
|
||||||
|
<span class="text-gray">You can see their IP address by hovering over their nickname. One IP per row.</span></p>
|
||||||
|
|
||||||
|
<table class="table-big">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>IP Address</th>
|
||||||
|
<th>Location</th>
|
||||||
|
<th>Ban date</th>
|
||||||
|
<th>Reason</th>
|
||||||
|
<th class="color-5"><i class="fa-solid fa-plus-circle"></i></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<% if (banlist.length > 0) { %>
|
||||||
|
<% banlist.forEach(bannedUser => { %>
|
||||||
|
<tr>
|
||||||
|
<% if (Array.isArray(bannedUser)) { %>
|
||||||
|
<!-- If it's an array, use its values -->
|
||||||
|
<td><a href="https://dnschecker.org/ip-location.php?ip=<%= bannedUser[0] %>" target="_blank"><%= bannedUser[0] %></a></td>
|
||||||
|
<td><%= bannedUser[1] %></td>
|
||||||
|
<td class="text-bold"><%= new Date(parseInt(bannedUser[2]) * 1000).toLocaleString() %></td> <!-- Assuming the ban date is a timestamp in seconds -->
|
||||||
|
<td><%= bannedUser[3] %></td>
|
||||||
|
<% } else { %>
|
||||||
|
<!-- If it's just an IP address without additional data, show it as is -->
|
||||||
|
<td><a href="https://dnschecker.org/ip-location.php?ip=<%= bannedUser %>" target="_blank"><%= bannedUser %></a></td>
|
||||||
|
<td>Unknown</td>
|
||||||
|
<td class="text-bold">Unknown</td>
|
||||||
|
<td>Unknown</td>
|
||||||
|
<% } %>
|
||||||
|
<td><a href="./kick?ip=<%= Array.isArray(bannedUser) ? bannedUser[0] : bannedUser %>"><i class="fa-solid fa-lock-open text-gray"></i></a></td>
|
||||||
|
</tr>
|
||||||
|
<% }); %>
|
||||||
|
<% } else { %>
|
||||||
|
<tr>
|
||||||
|
<td colspan="6" style="text-align: center">The banlist is empty.</td>
|
||||||
|
</tr>
|
||||||
|
<% } %>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel-full m-0 tab-content no-bg" id="extras" role="tabpanel">
|
||||||
|
<h2>Extras</h2>
|
||||||
|
<div class="panel-100 p-bottom-20">
|
||||||
|
<h3>FMLIST Integration</h3>
|
||||||
|
<p>FMLIST integration allows you to get potential DXes logged on the <a href="http://fmlist.org/fm_logmap.php?hours=900" target="_blank" class="text-bold color-4">FMLIST Visual Logbook</a>.<br>
|
||||||
|
Your server also needs to have a valid UUID, which is obtained by registering on maps in the <strong>Identification & Map</strong> tab.</p>
|
||||||
|
<%- include('_components', {component: 'checkbox', cssClass: 'm-right-10', label: 'FMLIST integration', id: 'extras-fmlistIntegration'}) %><br>
|
||||||
|
|
||||||
|
>>>>>>> 4b6d011 (rewrite update)
|
||||||
<p>You can also fill in your OMID from FMLIST.org, if you want the logs to be saved to your account.</p>
|
<p>You can also fill in your OMID from FMLIST.org, if you want the logs to be saved to your account.</p>
|
||||||
<%- include('_components', {component: 'text', cssClass: 'w-100', placeholder: '', label: 'OMID', id: 'extras-fmlistOmid'}) %>
|
<%- include('_components', {component: 'text', cssClass: 'w-100', placeholder: '', label: 'OMID', id: 'extras-fmlistOmid'}) %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user