Compare commits

...
6 Commits
Author SHA1 Message Date
kuba c0d1fee257 "too dig of a bick" 2026-02-24 15:27:39 +01:00
kuba 5d524eba56 some changes 2026-02-24 15:03:56 +01:00
kuba 648ef00bed sync to upstream 2026-02-24 14:44:48 +01:00
kuba 8a53bf1027 oh brother 2026-02-24 14:17:30 +01:00
kuba 722277c41f whoops 2026-02-24 14:16:50 +01:00
kuba ee25214160 some changes again 2026-02-24 14:15:52 +01:00
32 changed files with 528 additions and 541 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
node_modules/ node_modules/
/*.json /*.json
/serverlog.txt /serverlog*.txt
/web/js/plugins/ /web/js/plugins/
/libraries/ /libraries/
/plugins/* /plugins/*
+1 -1
View File
@@ -3,7 +3,7 @@ require('./server/index.js');
/** /**
* FM-DX Webserver * FM-DX Webserver
* *
* Github repo: https://github.com/NoobishSVK/fm-dx-webserver * Github repo: https://github.com/KubaPro010/fm-dx-webserver
* Server files: /server * Server files: /server
* Client files (web): /web * Client files (web): /web
* Plugin files: /plugins * Plugin files: /plugins
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "fm-dx-webserver", "name": "fm-dx-webserver",
"version": "1.4.0", "version": "1.4.0a",
"description": "FM DX Webserver", "description": "FM DX Webserver",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
+35 -14
View File
@@ -3,12 +3,19 @@ const { serverConfig } = require('./server_config');
const { logChat } = require('./console'); const { logChat } = require('./console');
const helpers = require('./helpers'); const helpers = require('./helpers');
function heartbeat() { // WebSocket heartbeat helper
this.isAlive = true;
}
function createChatServer(storage) { function createChatServer(storage) {
if (!serverConfig.webserver.chatEnabled) return null; if (!serverConfig.webserver.chatEnabled) return null;
const chatWss = new WebSocket.Server({ noServer: true }); const chatWss = new WebSocket.Server({ noServer: true });
chatWss.on('connection', (ws, request) => { chatWss.on('connection', (ws, request) => {
ws.isAlive = true;
ws.on('pong', heartbeat);
const clientIp = request.headers['x-forwarded-for'] || request.socket.remoteAddress; const clientIp = request.headers['x-forwarded-for'] || request.socket.remoteAddress;
const userCommandHistory = {}; const userCommandHistory = {};
@@ -25,19 +32,18 @@ function createChatServer(storage) {
ws.send(JSON.stringify(historyMessage)); ws.send(JSON.stringify(historyMessage));
}); });
const ipMessage = { ws.send(JSON.stringify({
type: 'clientIp', type: 'clientIp',
ip: clientIp, ip: clientIp,
admin: request.session?.isAdminAuthenticated admin: request.session?.isAdminAuthenticated
}; }));
ws.send(JSON.stringify(ipMessage));
const userCommands = {}; const userCommands = {};
let lastWarn = { time: 0 }; let lastWarn = { time: 0 };
ws.on('message', (message) => { ws.on('message', (message) => {
helpers.antispamProtection( message = helpers.antispamProtection(
message, message,
clientIp, clientIp,
ws, ws,
@@ -45,9 +51,12 @@ function createChatServer(storage) {
lastWarn, lastWarn,
userCommandHistory, userCommandHistory,
'5', '5',
'chat' 'chat',
512
); );
if(!message) return;
let messageData; let messageData;
try { try {
@@ -57,23 +66,16 @@ function createChatServer(storage) {
return; return;
} }
console.log("Chat message:", messageData);
delete messageData.admin; delete messageData.admin;
delete messageData.ip; delete messageData.ip;
delete messageData.time; delete messageData.time;
if (messageData.nickname != null) { if (messageData.nickname != null) messageData.nickname = helpers.escapeHtml(String(messageData.nickname));
messageData.nickname = helpers.escapeHtml(String(messageData.nickname));
}
messageData.ip = clientIp; messageData.ip = clientIp;
const now = new Date(); const now = new Date();
messageData.time = messageData.time = String(now.getHours()).padStart(2, '0') + ":" + String(now.getMinutes()).padStart(2, '0');
String(now.getHours()).padStart(2, '0') +
":" +
String(now.getMinutes()).padStart(2, '0');
if (serverConfig.webserver.banlist?.includes(clientIp)) return; if (serverConfig.webserver.banlist?.includes(clientIp)) return;
@@ -95,6 +97,25 @@ function createChatServer(storage) {
} }
}); });
}); });
ws.on('close', () => {
ws.isAlive = false;
});
});
/**
* We will not always be receiving data, so some proxies may terminate the connection, this prevents it.
*/
const interval = setInterval(() => {
chatWss.clients.forEach((ws) => {
if (ws.isAlive === false) return ws.terminate();
ws.isAlive = false;
ws.ping();
});
}, 30000);
chatWss.on('close', () => {
clearInterval(interval);
}); });
return chatWss; return chatWss;
+3 -1
View File
@@ -3,7 +3,9 @@ const fs = require('fs').promises;
const verboseMode = process.argv.includes('--debug'); const verboseMode = process.argv.includes('--debug');
const verboseModeFfmpeg = process.argv.includes('--ffmpegdebug'); const verboseModeFfmpeg = process.argv.includes('--ffmpegdebug');
const LOG_FILE = 'serverlog.txt'; const LOG_FILE = process.argv.includes('--config') && process.argv[process.argv.indexOf('--config') + 1]
? `serverlog_${process.argv[process.argv.indexOf('--config') + 1]}.txt`
: 'serverlog.txt';
const ANSI_ESCAPE_CODE_PATTERN = /\x1b\[[0-9;]*m/g; const ANSI_ESCAPE_CODE_PATTERN = /\x1b\[[0-9;]*m/g;
const MAX_LOG_LINES = 5000; const MAX_LOG_LINES = 5000;
const FLUSH_INTERVAL = 60000; const FLUSH_INTERVAL = 60000;
+2 -3
View File
@@ -2,7 +2,7 @@
const RDSDecoder = require("./rds.js"); const RDSDecoder = require("./rds.js");
const { serverConfig } = require('./server_config'); const { serverConfig } = require('./server_config');
const { fetchTx } = require('./tx_search.js'); const fetchTx = require('./tx_search.js');
const updateInterval = 75; const updateInterval = 75;
// Initialize the data object // Initialize the data object
@@ -193,8 +193,7 @@ function handleData(wss, receivedData, rdsWss) {
data += (((errors & 0x03) == 0) ? modifiedData.slice(12, 16) : '----'); data += (((errors & 0x03) == 0) ? modifiedData.slice(12, 16) : '----');
const newDataString = "G:\r\n" + data + "\r\n\r\n"; const newDataString = "G:\r\n" + data + "\r\n\r\n";
const finalBuffer = Buffer.from(newDataString, 'utf-8'); client.send(newDataString);
client.send(finalBuffer);
}); });
rdsdec.decodeGroup(parseInt(modifiedData.slice(0, 4), 16), parseInt(modifiedData.slice(4, 8), 16), parseInt(modifiedData.slice(8, 12), 16), parseInt(modifiedData.slice(12, 16), 16)); rdsdec.decodeGroup(parseInt(modifiedData.slice(0, 4), 16), parseInt(modifiedData.slice(4, 8), 16), parseInt(modifiedData.slice(8, 12), 16), parseInt(modifiedData.slice(12, 16), 16));
+3 -2
View File
@@ -15,7 +15,7 @@ const tunerProfiles = require('./tuner_profiles');
const { logInfo, logs } = require('./console'); const { logInfo, 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'); const allPluginConfigs = require('./plugins');
// Endpoints // Endpoints
router.get('/', (req, res) => { router.get('/', (req, res) => {
@@ -87,7 +87,8 @@ router.get('/', (req, res) => {
}); });
router.get('/403', (req, res) => { router.get('/403', (req, res) => {
res.render('403'); const reason = req.query.reason || null;
res.render('403', { reason });
}) })
router.get('/wizard', (req, res) => { router.get('/wizard', (req, res) => {
+51 -19
View File
@@ -1,3 +1,5 @@
const fs = require('fs');
const path = require('path');
const http = require('http'); const http = require('http');
const https = require('https'); const https = require('https');
const net = require('net'); const net = require('net');
@@ -5,7 +7,7 @@ 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'); const { serverConfig, configSave } = require('./server_config');
function parseMarkdown(parsed) { function parseMarkdown(parsed) {
parsed = parsed.replace(/<\/?[^>]+(>|$)/g, ''); parsed = parsed.replace(/<\/?[^>]+(>|$)/g, '');
@@ -93,9 +95,7 @@ let bannedASCache = { data: null, timestamp: 0 };
function fetchBannedAS(callback) { function fetchBannedAS(callback) {
const now = Date.now(); const now = Date.now();
if (bannedASCache.data && now - bannedASCache.timestamp < 10 * 60 * 1000) { if (bannedASCache.data && now - bannedASCache.timestamp < 10 * 60 * 1000) return callback(null, bannedASCache.data);
return callback(null, bannedASCache.data);
}
const req = https.get("https://fmdx.org/banned_as.json", { family: 4 }, (banResponse) => { const req = https.get("https://fmdx.org/banned_as.json", { family: 4 }, (banResponse) => {
let banData = ""; let banData = "";
@@ -152,9 +152,7 @@ function processConnection(clientIp, locationInfo, currentUsers, ws, callback) {
} }
const userLocation = const userLocation =
locationInfo.country === undefined locationInfo.country === undefined ? "Unknown" : `${locationInfo.city}, ${locationInfo.regionName}, ${locationInfo.countryCode}`;
? "Unknown"
: `${locationInfo.city}, ${locationInfo.regionName}, ${locationInfo.countryCode}`;
storage.connectedUsers.push({ storage.connectedUsers.push({
ip: clientIp, ip: clientIp,
@@ -252,12 +250,18 @@ function checkLatency(host, port = 80, timeout = 2000) {
}); });
} }
function antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, lengthCommands, endpointName) { function antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, lengthCommands, endpointName, maxPayloadSize = 1024 * 1024) {
const command = message.toString(); const rawCommand = message.toString();
const command = rawCommand.replace(/[\r\n]+/g, '');
const now = Date.now(); const now = Date.now();
const normalizedClientIp = clientIp?.replace(/^::ffff:/, ''); const normalizedClientIp = clientIp?.replace(/^::ffff:/, '');
if (endpointName === 'text') consoleCmd.logDebug(`Command received from \x1b[90m${clientIp}\x1b[0m: ${command}`); if (endpointName === 'text') consoleCmd.logDebug(`Command received from \x1b[90m${clientIp}\x1b[0m: ${command}`);
if (command.length > maxPayloadSize) {
consoleCmd.logWarn(`Command from \x1b[90m${normalizedClientIp}\x1b[0m on \x1b[90m/${endpointName}\x1b[0m exceeded maximum payload size (${parseInt(command.length / 1024)} KB / ${parseInt(maxPayloadSize / 1024)} KB).`);
return "";
}
// Initialize user command history if not present // Initialize user command history if not present
if (!userCommandHistory[clientIp]) userCommandHistory[clientIp] = []; if (!userCommandHistory[clientIp]) userCommandHistory[clientIp] = [];
@@ -289,9 +293,7 @@ function antispamProtection(message, clientIp, ws, userCommands, lastWarn, userC
lastMessageTime = now; lastMessageTime = now;
// Initialize command history for rate-limiting checks // Initialize command history for rate-limiting checks
if (!userCommands[command]) { if (!userCommands[command]) userCommands[command] = [];
userCommands[command] = [];
}
// Record the current timestamp for this command // Record the current timestamp for this command
userCommands[command].push(now); userCommands[command].push(now);
@@ -313,15 +315,45 @@ function antispamProtection(message, clientIp, ws, userCommands, lastWarn, userC
} }
const escapeHtml = (unsafe) => { const escapeHtml = (unsafe) => {
return unsafe return unsafe.replace(/&/g, "&amp;")
.replace(/&/g, "&amp;") .replace(/</g, "&lt;").replace(/>/g, "&gt;")
.replace(/</g, "&lt;") .replace(/"/g, "&quot;").replace(/'/g, "&#039;");
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}; };
// Start plugins with delay
function startPluginsWithDelay(plugins, delay) {
plugins.forEach((pluginPath, index) => {
setTimeout(() => {
const pluginName = path.basename(pluginPath, '.js'); // Extract plugin name from path
consoleCmd.logInfo(`-----------------------------------------------------------------`);
consoleCmd.logInfo(`Plugin ${pluginName} loaded successfully!`);
require(pluginPath);
}, delay * index);
});
// Add final log line after all plugins are loaded
setTimeout(() => {
consoleCmd.logInfo(`-----------------------------------------------------------------`);
}, delay * plugins.length);
}
// Function to find server files based on the plugins listed in config
function findServerFiles(plugins) {
let results = [];
plugins.forEach(plugin => {
// Remove .js extension if present
if (plugin.endsWith('.js')) plugin = plugin.slice(0, -3);
const pluginPath = path.join(__dirname, '..', 'plugins', `${plugin}_server.js`);
if (fs.existsSync(pluginPath) && fs.statSync(pluginPath).isFile()) results.push(pluginPath);
});
return results;
}
module.exports = { module.exports = {
authenticateWithXdrd, parseMarkdown, handleConnect, removeMarkdown, formatUptime, resolveDataBuffer, kickClient, checkIPv6Support, checkLatency, antispamProtection, escapeHtml authenticateWithXdrd, parseMarkdown, handleConnect,
removeMarkdown, formatUptime, resolveDataBuffer,
kickClient, checkIPv6Support, checkLatency,
antispamProtection, escapeHtml, findServerFiles,
startPluginsWithDelay
} }
+11 -53
View File
@@ -1,4 +1,3 @@
// Library imports
const express = require('express'); const express = require('express');
const endpoints = require('./endpoints'); const endpoints = require('./endpoints');
const session = require('express-session'); const session = require('express-session');
@@ -8,21 +7,16 @@ const readline = require('readline');
const app = express(); const app = express();
const httpServer = http.createServer(app); const httpServer = http.createServer(app);
const WebSocket = require('ws'); const WebSocket = require('ws');
const wss = new WebSocket.Server({ noServer: true, perMessageDeflate: true });
const rdsWss = new WebSocket.Server({ noServer: true });
const pluginsWss = new WebSocket.Server({ noServer: true, perMessageDeflate: true });
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 { SerialPort } = require('serialport'); const { SerialPort } = require('serialport');
const tunnel = require('./tunnel'); const tunnel = require('./tunnel');
const { createChatServer } = require('./chat'); const { createChatServer } = require('./chat');
const { createAudioServer } = require('./stream/ws.js');
const figlet = require('figlet'); const figlet = require('figlet');
// File imports
const helpers = require('./helpers'); const helpers = require('./helpers');
const { findServerFiles, startPluginsWithDelay } = helpers;
const dataHandler = require('./datahandler'); const dataHandler = require('./datahandler');
const fmdxList = require('./fmdx_list'); const fmdxList = require('./fmdx_list');
const { logError, logInfo, logWarn } = require('./console'); const { logError, logInfo, logWarn } = require('./console');
@@ -31,35 +25,10 @@ const { serverConfig, configExists } = require('./server_config');
const pluginsApi = require('./plugins_api'); const pluginsApi = require('./plugins_api');
const pjson = require('../package.json'); const pjson = require('../package.json');
// Function to find server files based on the plugins listed in config const client = new net.Socket();
function findServerFiles(plugins) { const wss = new WebSocket.Server({ noServer: true, perMessageDeflate: true });
let results = []; const rdsWss = new WebSocket.Server({ noServer: true });
plugins.forEach(plugin => { const pluginsWss = new WebSocket.Server({ noServer: true, perMessageDeflate: true });
// Remove .js extension if present
if (plugin.endsWith('.js')) plugin = plugin.slice(0, -3);
const pluginPath = path.join(__dirname, '..', 'plugins', `${plugin}_server.js`);
if (fs.existsSync(pluginPath) && fs.statSync(pluginPath).isFile()) results.push(pluginPath);
});
return results;
}
// Start plugins with delay
function startPluginsWithDelay(plugins, delay) {
plugins.forEach((pluginPath, index) => {
setTimeout(() => {
const pluginName = path.basename(pluginPath, '.js'); // Extract plugin name from path
logInfo(`-----------------------------------------------------------------`);
logInfo(`Plugin ${pluginName} loaded successfully!`);
require(pluginPath);
}, delay * index);
});
// Add final log line after all plugins are loaded
setTimeout(() => {
logInfo(`-----------------------------------------------------------------`);
}, delay * plugins.length);
}
// Get all plugins from config and find corresponding server files // Get all plugins from config and find corresponding server files
const plugins = findServerFiles(serverConfig.plugins); const plugins = findServerFiles(serverConfig.plugins);
@@ -76,22 +45,12 @@ const terminalWidth = readline.createInterface({
output: process.stdout output: process.stdout
}).output.columns; }).output.columns;
console.log('\x1b[32m' + figlet.textSync("FM-DX Webserver"));
figlet("FM-DX Webserver", function (err, data) {
if (err) {
console.log("Something went wrong...");
console.dir(err);
return;
}
console.log('\x1b[32m' + data);
});
console.log('\x1b[32m\x1b[2mby Noobish @ \x1b[4mFMDX.org\x1b[0m'); console.log('\x1b[32m\x1b[2mby Noobish @ \x1b[4mFMDX.org\x1b[0m');
console.log("v" + pjson.version) console.log("v" + pjson.version)
console.log('\x1b[90m' + '─'.repeat(terminalWidth - 1) + '\x1b[0m'); console.log('\x1b[90m' + '─'.repeat(terminalWidth - 1) + '\x1b[0m');
const chatWss = createChatServer(storage); const audioWss = require('./stream/ws.js');
const audioWss = createAudioServer();
// Start ffmpeg
require('./stream/index'); require('./stream/index');
require('./plugins'); require('./plugins');
@@ -107,6 +66,7 @@ const sessionMiddleware = session({
}); });
app.use(sessionMiddleware); app.use(sessionMiddleware);
app.use(bodyParser.json()); app.use(bodyParser.json());
const chatWss = createChatServer(storage);
connectToXdrd(); connectToXdrd();
connectToSerial(); connectToSerial();
@@ -237,9 +197,7 @@ client.on('data', (data) => {
const { xdrd } = serverConfig; const { xdrd } = serverConfig;
helpers.resolveDataBuffer(data, wss, rdsWss); helpers.resolveDataBuffer(data, wss, rdsWss);
if (authFlags.authMsg == true && authFlags.messageCount > 1) { if (authFlags.authMsg == true && authFlags.messageCount > 1) return;
return;
}
authFlags.messageCount++; authFlags.messageCount++;
const receivedData = data.toString(); const receivedData = data.toString();
@@ -398,7 +356,7 @@ wss.on('connection', (ws, request) => {
let lastWarn = { time: 0 }; let lastWarn = { time: 0 };
ws.on('message', (message) => { ws.on('message', (message) => {
const command = helpers.antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, '18', 'text'); const command = helpers.antispamProtection(message, clientIp, ws, userCommands, lastWarn, userCommandHistory, '18', 'text', 16 * 1024);
if (!clientIp.includes("127.0.0.1")) { if (!clientIp.includes("127.0.0.1")) {
if (((command.startsWith('X') || command.startsWith('Y')) && !request.session.isAdminAuthenticated) || if (((command.startsWith('X') || command.startsWith('Y')) && !request.session.isAdminAuthenticated) ||
+1 -3
View File
@@ -93,6 +93,4 @@ function createLinks() {
const allPluginConfigs = collectPluginConfigs(); const allPluginConfigs = collectPluginConfigs();
createLinks(); createLinks();
module.exports = { module.exports = allPluginConfigs;
allPluginConfigs
};
+10 -10
View File
@@ -93,8 +93,8 @@ class RDSDecoder {
this.ps[idx * 2] = String.fromCharCode(blockD >> 8); this.ps[idx * 2] = String.fromCharCode(blockD >> 8);
this.ps[idx * 2 + 1] = String.fromCharCode(blockD & 0xFF); this.ps[idx * 2 + 1] = String.fromCharCode(blockD & 0xFF);
this.ps_errors[idx * 2] = error; this.ps_errors[idx * 2] = Math.ceil(d_error * (10/3));
this.ps_errors[idx * 2 + 1] = error; this.ps_errors[idx * 2 + 1] = Math.ceil(d_error * (10/3));
this.data.ps = this.ps.join(''); this.data.ps = this.ps.join('');
this.data.ps_errors = this.ps_errors.join(','); this.data.ps_errors = this.ps_errors.join(',');
@@ -124,15 +124,15 @@ class RDSDecoder {
if(c_error < 2 && multiplier !== 2) { if(c_error < 2 && multiplier !== 2) {
this.rt1[idx * multiplier] = String.fromCharCode(blockC >> 8); this.rt1[idx * multiplier] = String.fromCharCode(blockC >> 8);
this.rt1[idx * multiplier + 1] = String.fromCharCode(blockC & 0xFF); this.rt1[idx * multiplier + 1] = String.fromCharCode(blockC & 0xFF);
this.rt1_errors[idx * multiplier] = error; this.rt1_errors[idx * multiplier] = Math.ceil(c_error * (10/3));
this.rt1_errors[idx * multiplier + 1] = error; this.rt1_errors[idx * multiplier + 1] = Math.ceil(c_error * (10/3));
} }
if(d_error < 2) { if(d_error < 2) {
var offset = (multiplier == 2) ? 0 : 2; var offset = (multiplier == 2) ? 0 : 2;
this.rt1[idx * multiplier + offset] = String.fromCharCode(blockD >> 8); this.rt1[idx * multiplier + offset] = String.fromCharCode(blockD >> 8);
this.rt1[idx * multiplier + offset + 1] = String.fromCharCode(blockD & 0xFF); this.rt1[idx * multiplier + offset + 1] = String.fromCharCode(blockD & 0xFF);
this.rt1_errors[idx * multiplier + offset] = error; this.rt1_errors[idx * multiplier + offset] = Math.ceil(d_error * (10/3));
this.rt1_errors[idx * multiplier + offset + 1] = error; this.rt1_errors[idx * multiplier + offset + 1] = Math.ceil(d_error * (10/3));
} }
var i = this.rt1.indexOf("\r") var i = this.rt1.indexOf("\r")
@@ -155,15 +155,15 @@ class RDSDecoder {
if(c_error !== 3 && multiplier !== 2) { if(c_error !== 3 && multiplier !== 2) {
this.rt0[idx * multiplier] = String.fromCharCode(blockC >> 8); this.rt0[idx * multiplier] = String.fromCharCode(blockC >> 8);
this.rt0[idx * multiplier + 1] = String.fromCharCode(blockC & 0xFF); this.rt0[idx * multiplier + 1] = String.fromCharCode(blockC & 0xFF);
this.rt0_errors[idx * multiplier] = error; this.rt0_errors[idx * multiplier] = Math.ceil(c_error * (10/3));
this.rt0_errors[idx * multiplier + 1] = error; this.rt0_errors[idx * multiplier + 1] = Math.ceil(c_error * (10/3));
} }
if(d_error !== 3) { if(d_error !== 3) {
var offset = (multiplier == 2) ? 0 : 2; var offset = (multiplier == 2) ? 0 : 2;
this.rt0[idx * multiplier + offset] = String.fromCharCode(blockD >> 8); this.rt0[idx * multiplier + offset] = String.fromCharCode(blockD >> 8);
this.rt0[idx * multiplier + offset + 1] = String.fromCharCode(blockD & 0xFF); this.rt0[idx * multiplier + offset + 1] = String.fromCharCode(blockD & 0xFF);
this.rt0_errors[idx * multiplier + offset] = error; this.rt0_errors[idx * multiplier + offset] = Math.ceil(d_error * (10/3));
this.rt0_errors[idx * multiplier + offset + 1] = error; this.rt0_errors[idx * multiplier + offset + 1] = Math.ceil(d_error * (10/3));
} }
var i = this.rt0.indexOf("\r"); var i = this.rt0.indexOf("\r");
+2 -2
View File
@@ -21,7 +21,7 @@ checkFFmpeg().then((ffmpegPath) => {
logInfo(`${consoleLogTitle} Using ${ffmpegPath === 'ffmpeg' ? 'system-installed FFmpeg' : 'ffmpeg-static'}`); logInfo(`${consoleLogTitle} Using ${ffmpegPath === 'ffmpeg' ? 'system-installed FFmpeg' : 'ffmpeg-static'}`);
logInfo(`${consoleLogTitle} Starting audio stream on device: \x1b[35m${serverConfig.audio.audioDevice}\x1b[0m`); logInfo(`${consoleLogTitle} Starting audio stream on device: \x1b[35m${serverConfig.audio.audioDevice}\x1b[0m`);
const sampleRate = Number(serverConfig.audio.sampleRate || 44100) + Number(serverConfig.audio.samplerateOffset || 0); const sampleRate = Number(serverConfig.audio.sampleRate || 44100) + Number(serverConfig.audio.samplerateOffset || 0); // Maybe even do 32 khz, we do not need higher than 15 khz precision
const channels = Number(serverConfig.audio.audioChannels || 2); const channels = Number(serverConfig.audio.audioChannels || 2);
@@ -139,4 +139,4 @@ checkFFmpeg().then((ffmpegPath) => {
logError(`${consoleLogTitle} Error: ${err.message}`); logError(`${consoleLogTitle} Error: ${err.message}`);
}); });
module.exports.audio_pipe = audio_pipe; module.exports = audio_pipe;
+3 -7
View File
@@ -1,9 +1,8 @@
const WebSocket = require('ws'); const WebSocket = require('ws');
const { serverConfig } = require('../server_config'); const { serverConfig } = require('../server_config');
const { audio_pipe } = require('./index.js'); const audio_pipe = require('./index.js');
function createAudioServer() { const audioWss = new WebSocket.Server({ noServer: true, skipUTF8Validation: true });
const audioWss = new WebSocket.Server({ noServer: true });
audioWss.on('connection', (ws, request) => { audioWss.on('connection', (ws, request) => {
const clientIp = request.headers['x-forwarded-for'] || request.socket.remoteAddress; const clientIp = request.headers['x-forwarded-for'] || request.socket.remoteAddress;
@@ -26,7 +25,4 @@ function createAudioServer() {
}); });
}); });
return audioWss; module.exports = audioWss;
}
module.exports = { createAudioServer };
+6 -18
View File
@@ -80,9 +80,7 @@ async function buildTxDatabase() {
consoleCmd.logInfo('Fetching transmitter database...'); consoleCmd.logInfo('Fetching transmitter database...');
const response = await fetch(`https://maps.fmdx.org/api?qth=${serverConfig.identification.lat},${serverConfig.identification.lon}`, { const response = await fetch(`https://maps.fmdx.org/api?qth=${serverConfig.identification.lat},${serverConfig.identification.lon}`, {
method: 'GET', method: 'GET',
headers: { headers: {'Accept': 'application/json'}
'Accept': 'application/json'
}
}); });
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`); if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
localDb = await response.json(); localDb = await response.json();
@@ -169,9 +167,7 @@ function getStateForCoordinates(lat, lon) {
for (const feature of usStatesGeoJson.features) { for (const feature of usStatesGeoJson.features) {
const boundingBox = getStateBoundingBox(feature.geometry.coordinates); const boundingBox = getStateBoundingBox(feature.geometry.coordinates);
if (isCityInState(lat, lon, boundingBox)) { if (isCityInState(lat, lon, boundingBox)) return feature.properties.name; // Return the state's name if city is inside bounding box
return feature.properties.name; // Return the state's name if city is inside bounding box
}
} }
return null; return null;
} }
@@ -208,22 +204,16 @@ function validPsCompare(rdsPs, stationPs) {
for (let i = 0; i < standardizedRdsPs.length; i++) { for (let i = 0; i < standardizedRdsPs.length; i++) {
// Skip this position if the character in standardizedRdsPs is an underscore. // Skip this position if the character in standardizedRdsPs is an underscore.
if (standardizedRdsPs[i] === '_') continue; if (standardizedRdsPs[i] === '_') continue;
if (token[i] === standardizedRdsPs[i]) { if (token[i] === standardizedRdsPs[i]) matchCount++;
matchCount++;
}
}
if (matchCount >= minMatchLen) {
return true;
} }
if (matchCount >= minMatchLen) return true;
} }
return false; return false;
} }
function evaluateStation(station, esMode) { function evaluateStation(station, esMode) {
let weightDistance = station.distanceKm; let weightDistance = station.distanceKm;
if (esMode && station.distanceKm > 700) { if (esMode && station.distanceKm > 700) weightDistance = Math.abs(station.distanceKm - 1500) + 200;
weightDistance = Math.abs(station.distanceKm - 1500) + 200;
}
let erp = station.erp && station.erp > 0 ? station.erp : 1; let erp = station.erp && station.erp > 0 ? station.erp : 1;
let extraWeight = erp > weightedErp && station.distanceKm <= weightDistance ? 0.3 : 0; let extraWeight = erp > weightedErp && station.distanceKm <= weightDistance ? 0.3 : 0;
let score = 0; let score = 0;
@@ -394,6 +384,4 @@ function deg2rad(deg) {
return deg * (Math.PI / 180); return deg * (Math.PI / 180);
} }
module.exports = { module.exports = fetchTx;
fetchTx
};
+9 -2
View File
@@ -3,8 +3,11 @@
<head> <head>
<title>Unauthorized - FM-DX Webserver</title> <title>Unauthorized - FM-DX Webserver</title>
<link href="css/entry.css" type="text/css" rel="stylesheet"> <link href="css/entry.css" type="text/css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" type="text/css" rel="stylesheet"> <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" type="text/css"
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></script> rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js"
integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g=="
crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<link rel="icon" type="image/png" href="favicon2.png" /> <link rel="icon" type="image/png" href="favicon2.png" />
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
</head> </head>
@@ -20,6 +23,10 @@
<p> <p>
There's a possibility you were kicked by the system.<br> There's a possibility you were kicked by the system.<br>
Please try again later.</p> Please try again later.</p>
<% if (reason) { %>
<p><strong>Reason:</strong> too dig of a bick</p>
<% } %>
</div> </div>
</div> </div>
</div> </div>
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

+24
View File
@@ -0,0 +1,24 @@
<svg width="128" height="128" viewBox="0 0 128 128"
xmlns="http://www.w3.org/2000/svg">
<!-- Outer hollow circle -->
<circle
cx="64"
cy="64"
r="54"
fill="none"
stroke="#A7A88B"
stroke-width="20"
/>
<!-- Inner hollow circle -->
<circle
cx="64"
cy="64"
r="22"
fill="none"
stroke="#FFFFFF"
stroke-width="18"
/>
</svg>

After

Width:  |  Height:  |  Size: 382 B

+1 -1
View File
@@ -15,7 +15,7 @@
<script src="js/libs/chartjs-adapter-luxon.umd.min.js"></script> <script src="js/libs/chartjs-adapter-luxon.umd.min.js"></script>
<script src="js/libs/chartjs-plugin-streaming.min.js"></script> <script src="js/libs/chartjs-plugin-streaming.min.js"></script>
<link rel="icon" type="image/png" href="favicon.png" /> <link rel="icon" type="image/svg+xml" href="favicon.svg" id="favicon" />
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta property="og:title" content="FM-DX WebServer [<%= tunerName %>]"> <meta property="og:title" content="FM-DX WebServer [<%= tunerName %>]">
+5 -14
View File
@@ -27,11 +27,8 @@ function destroyStream() {
function OnConnectivityCallback(isConnected) { function OnConnectivityCallback(isConnected) {
console.log("Connectivity changed:", isConnected); console.log("Connectivity changed:", isConnected);
if (Stream) { if (Stream) Stream.Volume = $('#volumeSlider').val();
Stream.Volume = $('#volumeSlider').val(); else console.warn("Stream is not initialized.");
} else {
console.warn("Stream is not initialized.");
}
} }
@@ -44,18 +41,14 @@ function OnPlayButtonClick(_ev) {
shouldReconnect = false; shouldReconnect = false;
destroyStream(); destroyStream();
$playbutton.find('.fa-solid').toggleClass('fa-stop fa-play'); $playbutton.find('.fa-solid').toggleClass('fa-stop fa-play');
if (isAppleiOS && 'audioSession' in navigator) { if (isAppleiOS && 'audioSession' in navigator) navigator.audioSession.type = "none";
navigator.audioSession.type = "none";
}
} else { } else {
console.log("Starting stream..."); console.log("Starting stream...");
shouldReconnect = true; shouldReconnect = true;
createStream(); createStream();
Stream.Start(); Stream.Start();
$playbutton.find('.fa-solid').toggleClass('fa-play fa-stop'); $playbutton.find('.fa-solid').toggleClass('fa-play fa-stop');
if (isAppleiOS && 'audioSession' in navigator) { if (isAppleiOS && 'audioSession' in navigator) navigator.audioSession.type = "playback";
navigator.audioSession.type = "playback";
}
} }
$playbutton.addClass('bg-gray').prop('disabled', true); $playbutton.addClass('bg-gray').prop('disabled', true);
@@ -70,9 +63,7 @@ function updateVolume() {
newVolumeGlobal = newVolume; newVolumeGlobal = newVolume;
console.log("Volume updated to:", newVolume); console.log("Volume updated to:", newVolume);
Stream.Volume = newVolume; Stream.Volume = newVolume;
} else { } else console.warn("Stream is not initialized.");
console.warn("Stream is not initialized.");
}
} }
$(document).ready(Init); $(document).ready(Init);
+11 -21
View File
@@ -3,18 +3,13 @@ function tuneUp() {
if (socket.readyState === WebSocket.OPEN) { if (socket.readyState === WebSocket.OPEN) {
getCurrentFreq(); getCurrentFreq();
let addVal = 0; let addVal = 0;
if (currentFreq < 0.52) { if (currentFreq < 0.52) addVal = 9 - (Math.round(currentFreq*1000) % 9);
addVal = 9 - (Math.round(currentFreq*1000) % 9); else if (currentFreq < 1.71) {
} else if (currentFreq < 1.71) {
// TODO: Rework to replace 9 with 9 or 10 based on regionalisation setting // TODO: Rework to replace 9 with 9 or 10 based on regionalisation setting
addVal = 9 - (Math.round(currentFreq*1000) % 9); addVal = 9 - (Math.round(currentFreq*1000) % 9);
} else if (currentFreq < 29.6) { } else if (currentFreq < 29.6) addVal = 5 - (Math.round(currentFreq*1000) % 5);
addVal = 5 - (Math.round(currentFreq*1000) % 5); else if (currentFreq >= 65.9 && currentFreq < 74) addVal = 30 - ((Math.round(currentFreq*1000) - 65900) % 30);
} else if (currentFreq >= 65.9 && currentFreq < 74) { else addVal = 100 - (Math.round(currentFreq*1000) % 100);
addVal = 30 - ((Math.round(currentFreq*1000) - 65900) % 30);
} else {
addVal = 100 - (Math.round(currentFreq*1000) % 100);
}
socket.send("T" + (Math.round(currentFreq*1000) + addVal)); socket.send("T" + (Math.round(currentFreq*1000) + addVal));
} }
} }
@@ -23,18 +18,13 @@ function tuneDown() {
if (socket.readyState === WebSocket.OPEN) { if (socket.readyState === WebSocket.OPEN) {
getCurrentFreq(); getCurrentFreq();
let subVal = 0; let subVal = 0;
if (currentFreq < 0.52) { if (currentFreq < 0.52) subVal = (Math.round(currentFreq*1000) % 9 == 0) ? 9 : (Math.round(currentFreq*1000) % 9);
else if (currentFreq < 1.71) {
// TODO: Rework to replace 9 with 9 or 10 based on regionalisation setting (Americans use 10, because of dumbfuckinstan)
subVal = (Math.round(currentFreq*1000) % 9 == 0) ? 9 : (Math.round(currentFreq*1000) % 9); subVal = (Math.round(currentFreq*1000) % 9 == 0) ? 9 : (Math.round(currentFreq*1000) % 9);
} else if (currentFreq < 1.71) { } else if (currentFreq < 29.6) subVal = (Math.round(currentFreq*1000) % 5 == 0) ? 5 : (Math.round(currentFreq*1000) % 5);
// TODO: Rework to replace 9 with 9 or 10 based on regionalisation setting else if (currentFreq > 65.9 && currentFreq <= 74) subVal = ((Math.round(currentFreq*1000) - 65900) % 30 == 0) ? 30 : ((Math.round(currentFreq*1000) - 65900) % 30);
subVal = (Math.round(currentFreq*1000) % 9 == 0) ? 9 : (Math.round(currentFreq*1000) % 9); else subVal = (Math.round(currentFreq*1000) % 100 == 0) ? 100 : (Math.round(currentFreq*1000) % 100);
} else if (currentFreq < 29.6) {
subVal = (Math.round(currentFreq*1000) % 5 == 0) ? 5 : (Math.round(currentFreq*1000) % 5);
} else if (currentFreq > 65.9 && currentFreq <= 74) {
subVal = ((Math.round(currentFreq*1000) - 65900) % 30 == 0) ? 30 : ((Math.round(currentFreq*1000) - 65900) % 30);
} else {
subVal = (Math.round(currentFreq*1000) % 100 == 0) ? 100 : (Math.round(currentFreq*1000) % 100);
}
socket.send("T" + (Math.round(currentFreq*1000) - subVal)); socket.send("T" + (Math.round(currentFreq*1000) - subVal));
} }
} }
+2 -6
View File
@@ -19,9 +19,7 @@ $(document).ready(function() {
function generateRandomString(length) { function generateRandomString(length) {
const characters = 'ABCDEFGHJKMNOPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz0123456789'; const characters = 'ABCDEFGHJKMNOPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz0123456789';
let result = ''; let result = '';
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) result += characters.charAt(Math.floor(Math.random() * characters.length));
result += characters.charAt(Math.floor(Math.random() * characters.length));
}
return result; return result;
} }
@@ -87,9 +85,7 @@ $(document).ready(function() {
}); });
chatSendInput.keypress(function(event) { chatSendInput.keypress(function(event) {
if (event.which === 13) { if (event.which === 13) sendMessage();
sendMessage();
}
}); });
function sendMessage() { function sendMessage() {
+9 -25
View File
@@ -38,9 +38,7 @@ function fetchConfig() {
function populateFields(data, prefix = "") { function populateFields(data, prefix = "") {
$.each(data, (key, value) => { $.each(data, (key, value) => {
if (value === null) { if (value === null) value = ""; // Convert null to an empty string
value = ""; // Convert null to an empty string
}
let id = `${prefix}${prefix ? "-" : ""}${key}`; let id = `${prefix}${prefix ? "-" : ""}${key}`;
const $element = $(`#${id}`); const $element = $(`#${id}`);
@@ -50,11 +48,8 @@ function populateFields(data, prefix = "") {
$element.find('option').each(function() { $element.find('option').each(function() {
const $option = $(this); const $option = $(this);
const dataName = $option.data('name'); const dataName = $option.data('name');
if (value.includes(dataName)) { if (value.includes(dataName)) $option.prop('selected', true);
$option.prop('selected', true); else $option.prop('selected', false);
} else {
$option.prop('selected', false);
}
}); });
$element.trigger('change'); $element.trigger('change');
@@ -68,11 +63,8 @@ function populateFields(data, prefix = "") {
const arrayId = `${id}-${index + 1}`; const arrayId = `${id}-${index + 1}`;
const $arrayElement = $(`#${arrayId}`); const $arrayElement = $(`#${arrayId}`);
if ($arrayElement.length) { if ($arrayElement.length) $arrayElement.val(item);
$arrayElement.val(item); else console.log(`Element with id ${arrayId} not found`);
} else {
console.log(`Element with id ${arrayId} not found`);
}
}); });
return; return;
} else { } else {
@@ -92,9 +84,7 @@ function populateFields(data, prefix = "") {
const $dropdownOption = $element.siblings('ul.options').find(`li[data-value="${value}"]`); const $dropdownOption = $element.siblings('ul.options').find(`li[data-value="${value}"]`);
$element.val($dropdownOption.length ? $dropdownOption.text() : value); $element.val($dropdownOption.length ? $dropdownOption.text() : value);
$element.attr('data-value', value); $element.attr('data-value', value);
} else { } else $element.val(value);
$element.val(value);
}
}); });
} }
@@ -111,9 +101,7 @@ function updateConfigData(data, prefix = "") {
if ($presetElement.length) { if ($presetElement.length) {
data[key].push($presetElement.val() || null); // Allow null if necessary data[key].push($presetElement.val() || null); // Allow null if necessary
index++; index++;
} else { } else break;
break;
}
} }
return; return;
} }
@@ -123,16 +111,12 @@ function updateConfigData(data, prefix = "") {
const $selectedOptions = $element.find('option:selected'); const $selectedOptions = $element.find('option:selected');
$selectedOptions.each(function() { $selectedOptions.each(function() {
const dataName = $(this).attr('data-name'); const dataName = $(this).attr('data-name');
if (dataName) { if (dataName) data[key].push(dataName);
data[key].push(dataName);
}
}); });
return; return;
} }
if (typeof value === "object" && value !== null && !Array.isArray(value)) { if (typeof value === "object" && value !== null && !Array.isArray(value)) return updateConfigData(value, id);
return updateConfigData(value, id);
}
if ($element.length) { if ($element.length) {
const newValue = $element.attr("data-value") ?? $element.val() ?? null; const newValue = $element.attr("data-value") ?? $element.val() ?? null;
+1 -3
View File
@@ -106,9 +106,7 @@ $(document).ready(function() {
$listOfOptions.on('click', selectOption); $listOfOptions.on('click', selectOption);
$dropdowns.on('click', 'input', toggleDropdown); $dropdowns.on('click', 'input', toggleDropdown);
$dropdowns.on('keydown', 'input', function(event) { $dropdowns.on('keydown', 'input', function(event) {
if (event.key === 'Enter') { if (event.key === 'Enter') toggleDropdown(event);
toggleDropdown(event);
}
}); });
$dropdowns.on('keydown', '.option', navigateOptions); $dropdowns.on('keydown', '.option', navigateOptions);
+3 -10
View File
@@ -1028,15 +1028,10 @@ const updateDataElements = throttle(function(parsedData) {
updateHtmlIfChanged($('#alternative-txes'), altTxInfo); updateHtmlIfChanged($('#alternative-txes'), altTxInfo);
updateTextIfChanged($('#data-station-distance'), txDistance); updateTextIfChanged($('#data-station-distance'), txDistance);
$dataStationContainer.css('display', 'block'); $dataStationContainer.css('display', 'block');
} else { } else $dataStationContainer.removeAttr('style');
$dataStationContainer.removeAttr('style');
}
if(parsedData.txInfo.tx.length > 1 && parsedData.txInfo.dist > 150 && parsedData.txInfo.dist < 4000) { if(parsedData.txInfo.tx.length > 1 && parsedData.txInfo.dist > 150 && parsedData.txInfo.dist < 4000) $('.log-fmlist').removeAttr('disabled').removeClass('btn-disabled cursor-disabled');
$('.log-fmlist').removeAttr('disabled').removeClass('btn-disabled cursor-disabled'); else $('.log-fmlist').attr('disabled', 'true').addClass('btn-disabled cursor-disabled');
} else {
$('.log-fmlist').attr('disabled', 'true').addClass('btn-disabled cursor-disabled');
}
updateHtmlIfChanged($('#data-regular-pi'), parsedData.txInfo.reg === true ? parsedData.txInfo.pi : '&nbsp;'); updateHtmlIfChanged($('#data-regular-pi'), parsedData.txInfo.reg === true ? parsedData.txInfo.pi : '&nbsp;');
if (updateCounter % 8 === 0) { if (updateCounter % 8 === 0) {
@@ -1084,7 +1079,6 @@ function updatePanels(parsedData) {
} }
if (updateCounter % 3 === 0) { if (updateCounter % 3 === 0) {
updateButtonState("data-eq", parsedData.eq); updateButtonState("data-eq", parsedData.eq);
updateButtonState("data-ims", parsedData.ims); updateButtonState("data-ims", parsedData.ims);
@@ -1288,4 +1282,3 @@ function initTooltips(target = null) {
$('#preset1').parent().hide(); $('#preset1').parent().hide();
} }
} }
+4 -9
View File
@@ -31,15 +31,11 @@ $(document).ready(function() {
$(document).on("click", function(event) { // Close the modal when clicking outside of it $(document).on("click", function(event) { // Close the modal when clicking outside of it
if ($(event.target).is(modal)) { if ($(event.target).is(modal)) closeModal();
closeModal();
}
}); });
$(document).on("keydown", function(event) { // Close the modal when pressing ESC key $(document).on("keydown", function(event) { // Close the modal when pressing ESC key
if (event.key === "Escape") { if (event.key === "Escape") closeModal();
closeModal();
}
}); });
$(".tuner-mobile-settings").on("click", function () { $(".tuner-mobile-settings").on("click", function () {
@@ -69,9 +65,8 @@ function initPopups() {
function togglePopup(targetSelector) { function togglePopup(targetSelector) {
const $target = $(targetSelector); const $target = $(targetSelector);
if ($target.is(":visible")) { if ($target.is(":visible")) $target.fadeOut(200);
$target.fadeOut(200); else {
} else {
$(".popup-window").fadeOut(200); $(".popup-window").fadeOut(200);
$target.fadeIn(200); $target.fadeIn(200);
} }
+23 -3
View File
@@ -75,6 +75,27 @@ function getQueryParameter(name) {
return urlParams.get(name); return urlParams.get(name);
} }
function updateFavicon(color) {
function rgbToHex(rgb) {
const result = rgb.match(/\d+/g);
return "#" + result.slice(0, 3).map(x =>(+x).toString(16).padStart(2, "0")).join("");
}
const hex = rgbToHex(color);
const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<circle cx="64" cy="64" r="54" fill="none" stroke="${hex}" stroke-width="20"/>
<circle cx="64" cy="64" r="22" fill="none" stroke="white" stroke-width="18"/>
</svg>`;
const base64 = btoa(svg);
$('#favicon').attr(
'href',
`data:image/svg+xml;base64,${base64}`
);
}
function setTheme(themeName) { function setTheme(themeName) {
const themeColors = themes[themeName]; const themeColors = themes[themeName];
if (themeColors) { if (themeColors) {
@@ -94,6 +115,7 @@ function setTheme(themeName) {
$(':root').css('--color-text', themeColors[2]); $(':root').css('--color-text', themeColors[2]);
$(':root').css('--color-text-2', textColor2); $(':root').css('--color-text-2', textColor2);
$('.wrapper-outer').css('background-color', backgroundColorWithOpacity); $('.wrapper-outer').css('background-color', backgroundColorWithOpacity);
updateFavicon(themeColors[1]);
} }
} }
@@ -167,9 +189,7 @@ function loadInitialSettings() {
if(signalParameter && !localStorage.getItem('signalUnit')) { if(signalParameter && !localStorage.getItem('signalUnit')) {
signalSelector.find('input').val(signalSelector.find('.option[data-value="' + signalParameter + '"]').text()); signalSelector.find('input').val(signalSelector.find('.option[data-value="' + signalParameter + '"]').text());
localStorage.setItem('signalUnit', signalParameter); localStorage.setItem('signalUnit', signalParameter);
} else { } else signalSelector.find('input').val(signalSelector.find('.option[data-value="' + savedUnit + '"]').text());
signalSelector.find('input').val(signalSelector.find('.option[data-value="' + savedUnit + '"]').text());
}
signalSelector.on('click', '.option', (event) => { signalSelector.on('click', '.option', (event) => {
const selectedSignalUnit = $(event.target).data('value'); const selectedSignalUnit = $(event.target).data('value');
+2 -6
View File
@@ -27,9 +27,7 @@ function mapCreate() {
zoom: 3, zoom: 3,
worldCopyJump: true worldCopyJump: true
}); });
} else { } else map.setZoom(3).panTo([40, 0]);
map.setZoom(3).panTo([40, 0]);
}
L.tileLayer(tilesURL, { L.tileLayer(tilesURL, {
attribution: mapAttrib, attribution: mapAttrib,
@@ -272,9 +270,7 @@ function checkTunnelServers() {
// If this li is the currently selected one, update input text too // If this li is the currently selected one, update input text too
// Note: input.val() holds the label, so match by label is safer // Note: input.val() holds the label, so match by label is safer
if ($li.text() === selectedValue || server.value === selectedValue) { if ($li.text() === selectedValue || server.value === selectedValue) $input.val(server.label);
$input.val(server.label);
}
} }
}); });
}, },
+2 -2
View File
@@ -1,2 +1,2 @@
const versionDate = new Date('Feb 24, 2026 01:00:00'); const versionDate = new Date('Feb 24, 2026 15:00:00');
const currentVersion = `v1.4.0 [${versionDate.getDate()}/${versionDate.getMonth() + 1}/${versionDate.getFullYear()}]`; const currentVersion = `v1.4.0a [${versionDate.getDate()}/${versionDate.getMonth() + 1}/${versionDate.getFullYear()}]`;
+1 -3
View File
@@ -27,8 +27,6 @@ function navigateStep(isNext) {
currentStep.hide(); currentStep.hide();
targetStep.show(); targetStep.show();
updateProgressBar(targetStep); updateProgressBar(targetStep);
} else if (isNext) { } else if (isNext) submitConfig();
submitConfig();
}
updateWizardContent(); updateWizardContent();
} }
+2 -2
View File
@@ -8,7 +8,7 @@
<script src="js/libs/jquery.min.js"></script> <script src="js/libs/jquery.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css" type="text/css" rel="stylesheet"> <link href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css" type="text/css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script>
<link rel="icon" type="image/png" href="favicon.png" /> <link rel="icon" type="image/svg+xml" href="favicon.svg" id="favicon" />
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
</head> </head>
<body> <body>
@@ -16,7 +16,7 @@
<div class="wrapper-outer wrapper-full"> <div class="wrapper-outer wrapper-full">
<div id="wrapper"> <div id="wrapper">
<div class="panel-100 no-bg"> <div class="panel-100 no-bg">
<img class="top-25" src="favicon.png" height="64px"> <img class="top-25" src="favicon.svg" height="64px">
<p>You are currently not logged in as an administrator and therefore can't change the settings.</p> <p>You are currently not logged in as an administrator and therefore can't change the settings.</p>
<p>Please login below.</p> <p>Please login below.</p>
</div> </div>
+1 -1
View File
@@ -8,7 +8,7 @@
<script src="js/libs/jquery.min.js"></script> <script src="js/libs/jquery.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css" type="text/css" rel="stylesheet"> <link href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css" type="text/css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script>
<link rel="icon" type="image/png" href="favicon.png" /> <link rel="icon" type="image/svg+xml" href="favicon.svg" id="favicon" />
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
</head> </head>
<body> <body>
+1 -1
View File
@@ -8,7 +8,7 @@
<script src="js/libs/jquery.min.js"></script> <script src="js/libs/jquery.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css" type="text/css" rel="stylesheet"> <link href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css" type="text/css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script>
<link rel="icon" type="image/png" href="favicon.png" /> <link rel="icon" type="image/svg+xml" href="favicon.svg" id="favicon" />
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
</head> </head>
<body> <body>