diff --git a/console.js b/console.js
index 6fe3fc9..e25bc5b 100644
--- a/console.js
+++ b/console.js
@@ -18,7 +18,7 @@ const MESSAGE_PREFIX = {
// Initialize an array to store logs
const logs = [];
-const maxLogLines = 100;
+const maxLogLines = 250;
const logDebug = (...messages) => {
if (verboseMode) {
diff --git a/datahandler.js b/datahandler.js
index 1a23561..d989dcb 100644
--- a/datahandler.js
+++ b/datahandler.js
@@ -210,7 +210,7 @@ var dataToSend = {
st: false,
st_forced: false,
ps: '',
- tp: false,
+ tp: 0,
ta: 0,
ms: -1,
pty: 0,
@@ -231,7 +231,7 @@ var dataToSend = {
},
country_name: '',
country_iso: 'UN',
- users: '',
+ users: 0,
};
var legacyRdsPiBuffer = null;
diff --git a/fmdx_list.js b/fmdx_list.js
index c3f8409..e73d467 100644
--- a/fmdx_list.js
+++ b/fmdx_list.js
@@ -31,7 +31,7 @@ function send(request) {
}
else
{
- logInfo("FM-DX Server Map update successful.");
+ logDebug("FM-DX Server Map update successful.");
}
}
else
@@ -66,6 +66,7 @@ function sendUpdate() {
desc: serverConfig.identification.tunerDesc,
audioChannels: serverConfig.audio.audioChannels,
audioQuality: serverConfig.audio.audioBitrate,
+ contact: serverConfig.identification.contact || ''
};
if (serverConfig.identification.token)
diff --git a/index.js b/index.js
index e2bcf3f..579ce9e 100644
--- a/index.js
+++ b/index.js
@@ -11,6 +11,7 @@ const httpProxy = require('http-proxy');
const https = require('https');
const app = express();
const httpServer = http.createServer(app);
+const process = require("process");
// Websocket handling
const WebSocket = require('ws');
@@ -33,7 +34,7 @@ const { logDebug, logError, logInfo, logWarn } = consoleCmd;
// Create a WebSocket proxy instance
const proxy = httpProxy.createProxyServer({
- target: 'ws://localhost:'+ serverConfig.webserver.audioPort, // WebSocket httpServer's address
+ target: 'ws://localhost:' + (Number(serverConfig.webserver.webserverPort) + 10), // WebSocket httpServer's address
ws: true, // Enable WebSocket proxying
changeOrigin: true // Change the origin of the host header to the target URL
});
@@ -98,7 +99,6 @@ function connectToXdrd() {
const lines = receivedData.split('\n');
for (const line of lines) {
-
if (!authFlags.receivedPassword) {
authFlags.receivedSalt = line.trim();
authenticateWithXdrd(client, authFlags.receivedSalt, serverConfig.xdrd.xdrdPassword);
@@ -225,7 +225,6 @@ app.get('/static_data', (req, res) => {
res.json({
qthLatitude: serverConfig.identification.lat,
qthLongitude: serverConfig.identification.lon,
- audioPort: serverConfig.webserver.audioPort,
streamEnabled: streamEnabled
});
});
@@ -286,8 +285,7 @@ function parseMarkdown(parsed) {
var linkRegex = /\[([^\]]+)]\(([^)]+)\)/g;
parsed = parsed.replace(linkRegex, '$1');
- var breakLineRegex = /\\n/g;
- parsed = parsed.replace(breakLineRegex, '
');
+ parsed = parsed.replace(/\n/g, '
');
return parsed;
}
@@ -307,21 +305,17 @@ function removeMarkdown(parsed) {
var linkRegex = /\[([^\]]+)]\(([^)]+)\)/g;
parsed = parsed.replace(linkRegex, '$1');
- var breakLineRegex = /\\n/g;
- parsed = parsed.replace(breakLineRegex, '');
-
return parsed;
}
app.get('/', (req, res) => {
if (!fs.existsSync(configName + '.json')) {
parseAudioDevice((result) => {
- res.render('setup', {
+ res.render('wizard', {
isAdminAuthenticated: true,
videoDevices: result.audioDevices,
- audioDevices: result.videoDevices,
- consoleOutput: consoleCmd.logs });
- });;
+ audioDevices: result.videoDevices });
+ });
} else {
res.render('index', {
isAdminAuthenticated: req.session.isAdminAuthenticated,
@@ -330,21 +324,65 @@ app.get('/', (req, res) => {
tunerDesc: parseMarkdown(serverConfig.identification.tunerDesc),
tunerDescMeta: removeMarkdown(serverConfig.identification.tunerDesc),
tunerLock: serverConfig.lockToAdmin,
- publicTuner: serverConfig.publicTuner
+ publicTuner: serverConfig.publicTuner,
+ antennaSwitch: serverConfig.antennaSwitch
})
}
});
+app.get('/wizard', (req, res) => {
+ parseAudioDevice((result) => {
+ res.render('wizard', {
+ isAdminAuthenticated: req.session.isAdminAuthenticated,
+ videoDevices: result.audioDevices,
+ audioDevices: result.videoDevices });
+ });
+})
+
app.get('/setup', (req, res) => {
parseAudioDevice((result) => {
- res.render('setup', {
- isAdminAuthenticated: req.session.isAdminAuthenticated,
- videoDevices: result.audioDevices,
- audioDevices: result.videoDevices,
- consoleOutput: consoleCmd.logs });
+ const processUptimeInSeconds = Math.floor(process.uptime());
+ const formattedProcessUptime = formatUptime(processUptimeInSeconds);
+
+ res.render('setup', {
+ isAdminAuthenticated: req.session.isAdminAuthenticated,
+ videoDevices: result.audioDevices,
+ audioDevices: result.videoDevices,
+ memoryUsage: (process.memoryUsage.rss() / 1024 / 1024).toFixed(1) + ' MB',
+ processUptime: formattedProcessUptime,
+ consoleOutput: consoleCmd.logs,
+ onlineUsers: dataHandler.dataToSend.users
+ });
});
});
+app.get('/api', (req, res) => {
+ let data = dataHandler.dataToSend;
+ delete data.ps_errors;
+ delete data.rt0_errors;
+ delete data.rt1_errors;
+ delete data.ims;
+ delete data.eq;
+ delete data.ant;
+ delete data.st_forced;
+ delete data.previousFreq;
+ delete data.txInfo;
+ res.json(data)
+});
+
+function formatUptime(uptimeInSeconds) {
+ const secondsInMinute = 60;
+ const secondsInHour = secondsInMinute * 60;
+ const secondsInDay = secondsInHour * 24;
+
+ const days = Math.floor(uptimeInSeconds / secondsInDay);
+ const hours = Math.floor((uptimeInSeconds % secondsInDay) / secondsInHour);
+ const minutes = Math.floor((uptimeInSeconds % secondsInHour) / secondsInMinute);
+
+ return `${days}d ${hours}h ${minutes}m`;
+}
+
+
// Route for login
app.post('/login', authenticate, (req, res) => {
diff --git a/server_config.js b/server_config.js
index b80dac7..bc78bdf 100644
--- a/server_config.js
+++ b/server_config.js
@@ -13,12 +13,11 @@ if (index !== -1 && index + 1 < process.argv.length) {
let serverConfig = {
webserver: {
webserverIp: "0.0.0.0",
- webserverPort: "8080",
- audioPort: "8081"
+ webserverPort: 8080
},
xdrd: {
xdrdIp: "127.0.0.1",
- xdrdPort: "7373",
+ xdrdPort: 7373,
xdrdPassword: ""
},
audio: {
diff --git a/stream/index.js b/stream/index.js
index b76521e..1ad10fe 100644
--- a/stream/index.js
+++ b/stream/index.js
@@ -5,6 +5,7 @@ const { configName, serverConfig, configUpdate, configSave } = require('../serve
function enableAudioStream() {
var ffmpegCommand;
+ serverConfig.webserver.webserverPort = Number(serverConfig.webserver.webserverPort);
// Specify the command and its arguments
const command = 'ffmpeg';
const flags = `-fflags +nobuffer+flush_packets -flags low_delay -rtbufsize 6192 -probesize 32`;
@@ -13,14 +14,14 @@ function enableAudioStream() {
// Combine all the settings for the ffmpeg command
if (process.platform === 'win32') {
// Windows
- ffmpegCommand = `${flags} -f dshow -i audio="${serverConfig.audio.audioDevice}" ${codec} ${output} pipe:1 | node stream/3las.server.js -port ${serverConfig.webserver.audioPort} -samplerate 48000 -channels ${serverConfig.audio.audioChannels}`;
+ ffmpegCommand = `${flags} -f dshow -i audio="${serverConfig.audio.audioDevice}" ${codec} ${output} pipe:1 | node stream/3las.server.js -port ${serverConfig.webserver.webserverPort + 10} -samplerate 48000 -channels ${serverConfig.audio.audioChannels}`;
} else {
// Linux
- ffmpegCommand = `${flags} -f alsa -i "${serverConfig.audio.audioDevice}" ${codec} ${output} pipe:1 | node stream/3las.server.js -port ${serverConfig.webserver.audioPort} -samplerate 48000 -channels ${serverConfig.audio.audioChannels}`;
+ ffmpegCommand = `${flags} -f alsa -i "${serverConfig.audio.audioDevice}" ${codec} ${output} pipe:1 | node stream/3las.server.js -port ${serverConfig.webserver.webserverPort + 10} -samplerate 48000 -channels ${serverConfig.audio.audioChannels}`;
}
consoleCmd.logInfo("Using audio device: " + serverConfig.audio.audioDevice);
- consoleCmd.logInfo("Launching audio stream on port " + serverConfig.webserver.audioPort + ".");
+ consoleCmd.logInfo(`Launching audio stream on internal port ${serverConfig.webserver.webserverPort + 10}.`);
// Spawn the child process
if(serverConfig.audio.audioDevice.length > 2) {
diff --git a/tx_search.js b/tx_search.js
index df8ea02..b3c0d25 100644
--- a/tx_search.js
+++ b/tx_search.js
@@ -48,7 +48,7 @@ function processData(data, piCode, rdsPs) {
const city = data.locations[cityId];
if (city.stations) {
for (const station of city.stations) {
- if (station.pi === piCode && !station.extra && station.ps && station.ps.toLowerCase().includes(rdsPs.replace(/ /g, '_').replace(/^_*(.*?)_*$/, '$1').toLowerCase())) {
+ if (station.pi === piCode.toUpperCase() && !station.extra && station.ps && station.ps.toLowerCase().includes(rdsPs.replace(/ /g, '_').replace(/^_*(.*?)_*$/, '$1').toLowerCase())) {
const distance = haversine(serverConfig.identification.lat, serverConfig.identification.lon, city.lat, city.lon);
const score = (10*Math.log10(station.erp*1000)) / distance.distanceKm; // Calculate score
if (score > maxScore) {
diff --git a/web/css/breadcrumbs.css b/web/css/breadcrumbs.css
index e7f0ab7..ed9d0c0 100644
--- a/web/css/breadcrumbs.css
+++ b/web/css/breadcrumbs.css
@@ -1,13 +1,22 @@
h1 {
color: var(--color-4);
- font-size: 52px;
- font-weight: 300;
+ font-size: 32px;
+ font-weight: 700;
+ text-transform: uppercase;
margin-top: 0;
margin-bottom: 0;
}
+.modal-panel-content h1 {
+ text-transform: initial;
+ font-weight: 300;
+ font-size: 42px;
+}
+
h1#tuner-name {
font-size: 32px;
+ font-weight: 300;
+ text-transform: initial;
}
h2 {
@@ -18,6 +27,7 @@ h2 {
h3 {
font-size: 22px;
+ color: var(--color-4);
}
h4 {
@@ -139,6 +149,9 @@ label {
.checkbox label {
position: relative;
cursor: pointer;
+ display: flex;
+ align-items: center;
+ user-select: none;
}
.checkbox label:before {
@@ -155,14 +168,20 @@ label {
margin-right: 5px;
}
+ .form-group input:checked + label:before {
+ background-color: var(--color-4);
+ }
+
.form-group input:checked + label:after {
content: '✓';
display: block;
position: absolute;
- top: 2px;
- left: 6px;
- width: 16px;
- height: 16px;
+ font-size: 18px;
+ top: -1px;
+ left: 5px;
+ width: 18px;
+ height: 18px;
+ color: var(--color-main);
}
.tuner-info {
@@ -170,6 +189,12 @@ label {
margin-bottom: 0px !important;
}
+ .settings-heading {
+ font-size: 32px;
+ padding-top: 20px;
+ text-transform: uppercase;
+ }
+
@media (max-width: 768px) {
canvas, #flags-container {
display: none;
diff --git a/web/css/buttons.css b/web/css/buttons.css
index ea34e92..fe37328 100644
--- a/web/css/buttons.css
+++ b/web/css/buttons.css
@@ -18,6 +18,57 @@ button:hover {
opacity: 0.6;
}
+.btn-next {
+ width: 200px;
+ padding: 10px;
+ font-weight: bold;
+ color: var(--color-main);
+ margin: 30px 5px;
+ text-transform: uppercase;
+}
+
+.btn-prev {
+ width: 48px;
+ padding: 10px;
+ color: var(--color-main);
+ background-color: var(--color-3);
+ margin: 30px 5px;
+}
+
+.btn-rounded-cube {
+ width: 64px;
+ height: 64px;
+ background: var(--color-2);
+ color: var(--color-main);
+ border-radius: 30px;
+ margin-right: 10px;
+ margin-left: 10px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ position: relative;
+ font-size: 24px;
+ font-weight: 300;
+ cursor: default;
+}
+
+.btn-rounded-cube:not(:first-child)::before {
+ content: "";
+ width: 20px;
+ height: 2px;
+ background: var(--color-2);
+ position: absolute;
+ right: 64px;
+}
+
+.btn-rounded-cube.activated {
+ background-color: var(--color-4);
+}
+
+.btn-rounded-cube.activated::before {
+ background-color: var(--color-4);
+}
+
input[type="text"], textarea, input[type="password"] {
width: 300px;
min-height: 46px;
@@ -267,4 +318,4 @@ select option {
select:hover {
background: var(--color-5);
-}
+}
\ No newline at end of file
diff --git a/web/css/helpers.css b/web/css/helpers.css
index fe47ce9..d4bf2ff 100644
--- a/web/css/helpers.css
+++ b/web/css/helpers.css
@@ -14,6 +14,22 @@
background: transparent !important;
}
+.bg-color-1 {
+ background-color: var(--color-1);
+}
+
+.bg-color-2 {
+ background-color: var(--color-2);
+}
+
+.bg-color-3 {
+ background-color: var(--color-3);
+}
+
+.bg-color-4 {
+ background-color: var(--color-4);
+}
+
.color-1 {
color: var(--color-1);
}
@@ -30,6 +46,11 @@
color: var(--color-4);
}
+.color-5 {
+ color: var(--color-5);
+}
+
+
.br-0 {
border-radius: 0px;
}
@@ -126,7 +147,7 @@
}
.text-bold {
- font-weight: bold;
+ font-weight: bold !important;
}
.text-monospace {
@@ -134,7 +155,7 @@
}
.text-gray {
- color: #666;
+ opacity: 0.7;
}
.text-red {
@@ -177,6 +198,10 @@
cursor: pointer;
}
+.hidden {
+ display: none;
+}
+
@media only screen and (max-width: 960px) {
.text-medium-big {
font-size: 32px;
diff --git a/web/css/setup.css b/web/css/setup.css
index 95d4d0d..e6de6cd 100644
--- a/web/css/setup.css
+++ b/web/css/setup.css
@@ -9,6 +9,13 @@
margin-left: 5px;
}
+.setup-wrapper h2 {
+ font-size: 32px;
+ font-weight: 300;
+ padding: 10px;
+ text-transform: uppercase;
+}
+
.setup-wrapper textarea {
width: 100%;
@@ -19,6 +26,34 @@
padding-top: 10px;
}
+ul.nav {
+ list-style-type: none;
+ padding: 15px 0;
+ background: var(--color-2);
+ border-radius: 30px;
+}
+
+ul.nav li {
+ display: inline;
+ padding: 15px;
+ cursor: pointer;
+ transition: color 0.3s ease-in-out, background-color 0.3s ease-in-out;
+ user-select: none;
+}
+
+ul.nav li:hover {
+ color: var(--color-main);
+ background-color: var(--color-4);
+}
+
+li.active {
+ background-color: var(--color-3);
+}
+
+.tab-content {
+ display: none;
+}
+
#map {
height:400px;
width:100%;
@@ -33,10 +68,37 @@
margin: 8px;
}
+
+#console-output {
+ background-color: #111;
+ height: 300px;
+ overflow-y:auto;
+}
+.w-200 {
+ width: 200px !important
+}
+
.w-150 {
width: 150px !important
}
.w-100 {
width: 100px !important;
+}
+
+@media only screen and (max-width: 768px) {
+ ul.nav {
+ display: flex;
+ overflow-y: scroll;
+ background: transparent;
+ }
+
+ ul.nav li {
+ background-color: var(--color-4);
+ color: var(--color-main);
+ margin: 0px 10px;
+ padding: 15px 35px;
+ border-radius: 30px;
+ min-width: fit-content;
+ }
}
\ No newline at end of file
diff --git a/web/images/openradio_logo_neutral.png b/web/images/openradio_logo_neutral.png
new file mode 100644
index 0000000..3333b5e
Binary files /dev/null and b/web/images/openradio_logo_neutral.png differ
diff --git a/web/index.ejs b/web/index.ejs
index b1674cd..1a38164 100644
--- a/web/index.ejs
+++ b/web/index.ejs
@@ -82,7 +82,7 @@
- ST
+ ST
MS
@@ -115,6 +115,8 @@
+
+ <% if (antennaSwitch) { %>
+ <% } %>
+
@@ -211,7 +215,7 @@
@@ -244,6 +248,10 @@
+
+
+
+
diff --git a/web/js/3las/3las.js b/web/js/3las/3las.js
index 53686a2..022a5d4 100644
--- a/web/js/3las/3las.js
+++ b/web/js/3las/3las.js
@@ -1,7 +1,6 @@
var _3LAS_Settings = /** @class */ (function () {
function _3LAS_Settings() {
this.SocketHost = document.location.hostname ? document.location.hostname : "127.0.0.1";
- this.SocketPort = localStorage.getItem('audioPort') ? localStorage.getItem('audioPort') : 8081;
this.SocketPath = "/";
this.WebRTC = new WebRTC_Settings();
this.Fallback = new Fallback_Settings();
diff --git a/web/js/confighandler.js b/web/js/confighandler.js
new file mode 100644
index 0000000..5a60e02
--- /dev/null
+++ b/web/js/confighandler.js
@@ -0,0 +1,145 @@
+function submitData() {
+ const webserverIp = $('#webserver-ip').val() || '0.0.0.0';
+ const webserverPort = $('#webserver-port').val() || '8080';
+
+ const xdrdIp = $('#xdrd-ip').val() || '127.0.0.1';
+ const xdrdPort = $('#xdrd-port').val() || '7373';
+ const xdrdPassword = $('#xdrd-password').val() || 'password';
+
+ const audioDevice = $('#audio-devices').val() || 'Microphone (High Definition Audio Device)';
+ const audioChannels = ($('.options .option').filter(function() {
+ return $(this).text() === $('#audio-channels').val();
+ }).data('value') || 2);
+ const audioBitrate = ($('.options .option').filter(function() {
+ return $(this).text() === $('#audio-quality').val();
+ }).data('value') || "192k");
+
+ const tunerName = $('#webserver-name').val() || 'FM Tuner';
+ const tunerDesc = $('#webserver-desc').val() || 'Default FM tuner description';
+ const broadcastTuner = $("#broadcast-tuner").is(":checked");
+ const contact = $("#owner-contact").val() || '';
+ const lat = $('#lat').val();
+ const lon = $('#lng').val();
+ const proxyIp = $("#broadcast-address").val();
+
+ const tunePass = $('#tune-pass').val();
+ const adminPass = $('#admin-pass').val();
+
+ const publicTuner = $("#tuner-public").is(":checked");
+ const lockToAdmin = $("#tuner-lock").is(":checked");
+ const autoShutdown = $("#shutdown-tuner").is(":checked") || false;
+ const antennaSwitch = $("#antenna-switch").is(":checked") || false;
+
+ const data = {
+ webserver: {
+ webserverIp,
+ webserverPort,
+ },
+ xdrd: {
+ xdrdIp,
+ xdrdPort,
+ xdrdPassword
+ },
+ audio: {
+ audioDevice,
+ audioChannels,
+ audioBitrate,
+ },
+ identification: {
+ tunerName,
+ tunerDesc,
+ broadcastTuner,
+ contact,
+ lat,
+ lon,
+ proxyIp
+ },
+ password: {
+ tunePass,
+ adminPass,
+ },
+ publicTuner,
+ lockToAdmin,
+ autoShutdown,
+ antennaSwitch,
+ };
+
+ if(adminPass.length < 1) {
+ alert('You need to fill in the admin password before continuing further.');
+ return;
+ }
+ // Send data to the server using jQuery
+ $.ajax({
+ url: './saveData',
+ type: 'POST',
+ contentType: 'application/json',
+ data: JSON.stringify(data),
+ success: function (message) {
+ alert(message);
+ },
+ error: function (error) {
+ console.error(error);
+ }
+ });
+ }
+
+
+ function fetchData() {
+ // Make a GET request to retrieve the data.json file
+ fetch("./getData")
+ .then(response => {
+ if (!response.ok) {
+ throw new Error(`HTTP error! Status: ${response.status}`);
+ }
+ return response.json();
+ })
+ .then(data => {
+ $('#webserver-ip').val(data.webserver.webserverIp);
+ $('#webserver-port').val(data.webserver.webserverPort);
+
+ $('#xdrd-ip').val(data.xdrd.xdrdIp);
+ $('#xdrd-port').val(data.xdrd.xdrdPort);
+ $('#xdrd-password').val(data.xdrd.xdrdPassword);
+
+ $('#audio-devices').val(data.audio.audioDevice);
+ $('#audio-channels').val(data.audio.audioChannels);
+ $('#audio-quality').val(data.audio.audioBitrate);
+
+ $('#webserver-name').val(data.identification.tunerName);
+ $('#webserver-desc').val(data.identification.tunerDesc);
+ $("#broadcast-tuner").prop("checked", data.identification.broadcastTuner);
+ $("#broadcast-address").val(data.identification.proxyIp);
+ $("#owner-contact").val(data.identification.contact);
+ $('#lat').val(data.identification.lat);
+ $('#lng').val(data.identification.lon);
+
+ $('#tune-pass').val(data.password.tunePass);
+ $('#admin-pass').val(data.password.adminPass);
+
+ $("#tuner-public").prop("checked", data.publicTuner);
+ $("#tuner-lock").prop("checked", data.lockToAdmin);
+ $("#shutdown-tuner").prop("checked", data.autoShutdown);
+ $("#antenna-switch").prop("checked", data.antennaSwitch);
+
+ // Check if latitude and longitude are present in the data
+ if (data.identification.lat && data.identification.lon) {
+ // Set the map's center to the received coordinates
+ map.setView([data.identification.lat, data.identification.lon], 13);
+
+ // Add a pin to the map
+ if (typeof pin == "object") {
+ pin.setLatLng([data.identification.lat, data.identification.lon]);
+ } else {
+ pin = L.marker([data.identification.lat, data.identification.lon], { riseOnHover:true, draggable:true });
+ pin.addTo(map);
+ pin.on('drag',function(ev) {
+ $('#lat').val((ev.latlng.lat).toFixed(6));
+ $('#lng').val((ev.latlng.lng).toFixed(6));
+ });
+ }
+ }
+ })
+ .catch(error => {
+ console.error('Error fetching data:', error.message);
+ });
+}
diff --git a/web/js/main.js b/web/js/main.js
index 8d2dc4f..db2459b 100644
--- a/web/js/main.js
+++ b/web/js/main.js
@@ -3,6 +3,7 @@ url.protocol = url.protocol.replace('http', 'ws');
var socketAddress = url.href;
var socket = new WebSocket(socketAddress);
var parsedData, signalChart, previousFreq;
+var signalData = [];
var data = [];
let updateCounter = 0;
@@ -118,6 +119,7 @@ $(document).ready(function () {
var piCodeContainer = $('#pi-code-container')[0];
var freqContainer = $('#freq-container')[0];
var txContainer = $('#data-station-container')[0];
+ var stereoContainer = $('#stereo-container')[0];
$("#data-eq").click(function () {
toggleButtonState("eq");
@@ -133,6 +135,7 @@ $(document).ready(function () {
$(rtContainer).on("click", copyRt);
$(txContainer).on("click", copyTx);
$(piCodeContainer).on("click", findOnMaps);
+ $(stereoContainer).on("click", toggleForcedStereo);
$(freqContainer).on("click", function () {
textInput.focus();
});
@@ -514,24 +517,31 @@ function findOnMaps() {
window.open(url, "_blank");
}
-function updateSignalUnits(parsedData) {
+function updateSignalUnits(parsedData, averageSignal) {
const signalUnit = localStorage.getItem('signalUnit');
+ let currentSignal;
+
+ if(localStorage.getItem("smoothSignal") == 'true') {
+ currentSignal = averageSignal
+ } else {
+ currentSignal = parsedData.signal;
+ }
let signalText = $('#signal-units');
let signalValue;
switch (signalUnit) {
case 'dbuv':
- signalValue = parsedData.signal - 11.25;
+ signalValue = currentSignal - 11.25;
signalText.text('dBµV');
break;
case 'dbm':
- signalValue = parsedData.signal - 120;
+ signalValue = currentSignal - 120;
signalText.text('dBm');
break;
default:
- signalValue = parsedData.signal;
+ signalValue = currentSignal;
signalText.text('dBf');
break;
}
@@ -551,15 +561,6 @@ function updateDataElements(parsedData) {
parsedData.ps = parsedData.ps.replace(/\s/g, '_');
}
$('#data-ps').html(parsedData.ps === '?' ? "?" : processString(parsedData.ps, parsedData.ps_errors));
- $('.data-tp').html(parsedData.tp === 0 ? "TP" : "TP");
- $('.data-ta').html(parsedData.ta === 0 ? "TA" : "TA");
- $('.data-ms').html(parsedData.ms === 0
- ? "MS"
- : (parsedData.ms === -1
- ? "MS"
- : "MS"
- )
- );
$('.data-pty').html(europe_programmes[parsedData.pty]);
@@ -580,6 +581,7 @@ function updateDataElements(parsedData) {
$('#data-rt0').html(processString(parsedData.rt0, parsedData.rt0_errors));
$('#data-rt1').html(processString(parsedData.rt1, parsedData.rt1_errors));
$('.data-flag').html(``);
+ $('.data-flag-big').html(``);
$('#data-ant input').val($('#data-ant li[data-value="' + parsedData.ant + '"]').text());
if (parsedData.txInfo.station.length > 1) {
@@ -596,6 +598,18 @@ function updateDataElements(parsedData) {
}
updateCounter++;
+ if(updateCounter % 8 === 0) {
+ $('.data-tp').html(parsedData.tp === 0 ? "TP" : "TP");
+ $('.data-ta').html(parsedData.ta === 0 ? "TA" : "TA");
+ $('.data-ms').html(parsedData.ms === 0
+ ? "MS"
+ : (parsedData.ms === -1
+ ? "MS"
+ : "MS"
+ )
+ );
+ }
+
if (updateCounter % 30 === 0) {
$('#data-ps').attr('aria-label', parsedData.ps);
$('#data-rt0').attr('aria-label', parsedData.rt0);
@@ -608,6 +622,13 @@ let isEventListenerAdded = false;
function updatePanels(parsedData) {
updateCounter++;
+ signalData.push(parsedData.signal);
+ if (signalData.length > 8) {
+ signalData.shift(); // Remove the oldest element
+ }
+ const sum = signalData.reduce((acc, strNum) => acc + parseFloat(strNum), 0);
+ const averageSignal = sum / signalData.length;
+
const sortedAf = parsedData.af.sort(compareNumbers);
const scaledArray = sortedAf.map(element => element / 1000);
@@ -642,9 +663,8 @@ function updatePanels(parsedData) {
listContainer.scrollTop(scrollTop);
}
- // Update other elements every time
updateDataElements(parsedData);
- updateSignalUnits(parsedData);
+ updateSignalUnits(parsedData, averageSignal);
$('.users-online').text(parsedData.users);
}
@@ -669,3 +689,9 @@ function toggleButtonState(buttonId) {
message += parsedData.ims ? "1" : "0";
socket.send(message);
}
+
+function toggleForcedStereo() {
+ var message = "B";
+ message += parsedData.st_forced = (parsedData.st_forced == "1") ? "0" : "1";
+ socket.send(message);
+}
\ No newline at end of file
diff --git a/web/js/settings.js b/web/js/settings.js
index 8bec139..42eb8b4 100644
--- a/web/js/settings.js
+++ b/web/js/settings.js
@@ -1,11 +1,13 @@
+ var currentVersion = 'v1.1.0 [29.2.2024]';
+
/**
- * Themes
- * @param first color
- * @param second color
- * @param text color
- */
+ * Themes
+ * @param first color
+ * @param second color
+ * @param text color
+ */
const themes = {
- theme1: [ 'rgba(0, 0, 0, 1)', 'rgba(204, 204, 204, 1)', 'rgba(255, 255, 255, 1)' ], // Monochrome (Default)
+ theme1: ['rgba(32, 34, 40, 1)', 'rgba(88, 219, 171, 1)', 'rgba(255, 255, 255, 1)' ], // Retro (Default)
theme2: [ 'rgba(31, 12, 12, 1)', 'rgba(255, 112, 112, 1)', 'rgba(255, 255, 255, 1)' ], // Red
theme3: [ 'rgba(18, 28, 12, 1)', 'rgba(169, 255, 112, 1)', 'rgba(255, 255, 255, 1)' ], // Green
theme4: [ 'rgba(12, 28, 27, 1)', 'rgba(104, 247, 238, 1)', 'rgba(255, 255, 255, 1)' ], // Cyan
@@ -13,143 +15,154 @@
theme6: [ 'rgba(33, 9, 29, 1)', 'rgba(237, 81, 211, 1)', 'rgba(255, 255, 255, 1)' ], // Pink
theme7: [ 'rgba(13, 11, 26, 1)', 'rgba(128, 105, 250, 1)', 'rgba(255, 255, 255, 1)' ], // Blurple
theme8: [ 'rgba(252, 186, 3, 1)', 'rgba(0, 0, 0, 1)', 'rgba(0, 0, 0, 1)' ], // Sunny
- theme9: ['rgba(32, 34, 40, 1)', 'rgba(88, 219, 171, 1)', 'rgba(255, 255, 255, 1)' ] // Retro
- };
-
- // Signal Units
- const signalUnits = {
- dbf: ['dBf'],
- dbuv: ['dBµV'],
- dbm: ['dBm'],
- };
-
- $(document).ready(() => {
- // Theme Selector
- const themeSelector = $('#theme-selector');
- const savedTheme = localStorage.getItem('theme');
- const savedUnit = localStorage.getItem('signalUnit');
-
- if (savedTheme && themes[savedTheme]) {
- setTheme(savedTheme);
- themeSelector.find('input').val(themeSelector.find('.option[data-value="' + savedTheme + '"]').text());
- }
-
- themeSelector.on('click', '.option', (event) => {
- const selectedTheme = $(event.target).data('value');
- setTheme(selectedTheme);
- themeSelector.find('input').val($(event.target).text()); // Set the text of the clicked option to the input
- localStorage.setItem('theme', selectedTheme);
- });
-
- // Signal Selector
- const signalSelector = $('#signal-selector');
-
- if (localStorage.getItem('signalUnit')) {
- signalSelector.find('input').val(signalSelector.find('.option[data-value="' + savedUnit + '"]').text());
- }
-
- signalSelector.on('click', '.option', (event) => {
- const selectedSignalUnit = $(event.target).data('value');
- signalSelector.find('input').val($(event.target).text()); // Set the text of the clicked option to the input
- localStorage.setItem('signalUnit', selectedSignalUnit);
- });
-
- $('#login-form').submit(function (event) {
- event.preventDefault();
+ theme9: [ 'rgba(0, 0, 0, 1)', 'rgba(204, 204, 204, 1)', 'rgba(255, 255, 255, 1)' ], // AMOLED
+ };
- // Perform an AJAX request to the /login endpoint
- $.ajax({
- type: 'POST',
- url: './login',
- data: $(this).serialize(),
- success: function (data) {
- // Update the content on the page with the message from the response
- $('#login-message').text(data.message);
- setTimeout(function () {
- location.reload(true);
- }, 1750);
- },
- error: function (xhr, status, error) {
- // Handle error response
- if (xhr.status === 403) {
- // Update the content on the page with the message from the error response
- $('#login-message').text(xhr.responseJSON.message);
- } else {
- // Handle other types of errors if needed
- console.error('Error:', status, error);
- }
- }
- });
- });
-
- // Assuming you have an anchor tag with id 'logout-link'
- $('.logout-link').click(function (event) {
- event.preventDefault();
+ // Signal Units
+ const signalUnits = {
+ dbf: ['dBf'],
+ dbuv: ['dBµV'],
+ dbm: ['dBm'],
+ };
+
+ $(document).ready(() => {
+ // Theme Selector
+ const themeSelector = $('#theme-selector');
+ const savedTheme = localStorage.getItem('theme');
+ const savedUnit = localStorage.getItem('signalUnit');
- // Perform an AJAX request to the /logout endpoint
- $.ajax({
- type: 'GET', // Assuming the logout is a GET request, adjust accordingly
- url: './logout',
- success: function (data) {
- // Update the content on the page with the message from the response
- $('#login-message').text(data.message);
- setTimeout(function () {
- location.reload(true);
- }, 1750);
- },
- error: function (xhr, status, error) {
- // Handle error response
- if (xhr.status === 403) {
- // Update the content on the page with the message from the error response
- $('#login-message').text(xhr.responseJSON.message);
- } else {
- // Handle other types of errors if needed
- console.error('Error:', status, error);
- }
- }
- });
- });
-
- var extendedFreqRange = localStorage.getItem("extendedFreqRange");
- if (extendedFreqRange === "true") {
- $("#extended-frequency-range").prop("checked", true);
- }
-
- // Save the value of the checkbox into local storage when its state changes
- $("#extended-frequency-range").change(function() {
- var isChecked = $(this).is(":checked");
- localStorage.setItem("extendedFreqRange", isChecked);
- });
-
- var extendedFreqRange = localStorage.getItem("psUnderscores");
- if (extendedFreqRange === "true") {
- $("#ps-underscores").prop("checked", true);
- }
-
- // Save the value of the checkbox into local storage when its state changes
- $("#ps-underscores").change(function() {
- var isChecked = $(this).is(":checked");
- localStorage.setItem("psUnderscores", isChecked);
- });
-
-});
-
-
-function setTheme(themeName) {
- const themeColors = themes[themeName];
- if (themeColors) {
- // Extracting the RGBA components and opacity value
- const rgbaComponents = themeColors[2].match(/(\d+(\.\d+)?)/g);
- const opacity = parseFloat(rgbaComponents[3]);
- // Calculating 80% of the opacity
- const newOpacity = opacity * 0.75;
- // Constructing the new RGBA string with the adjusted opacity
- const textColor2 = `rgba(${rgbaComponents[0]}, ${rgbaComponents[1]}, ${rgbaComponents[2]}, ${newOpacity})`;
+ if (savedTheme && themes[savedTheme]) {
+ setTheme(savedTheme);
+ themeSelector.find('input').val(themeSelector.find('.option[data-value="' + savedTheme + '"]').text());
+ }
- $(':root').css('--color-main', themeColors[0]);
- $(':root').css('--color-main-bright', themeColors[1]);
- $(':root').css('--color-text', themeColors[2]);
- $(':root').css('--color-text-2', textColor2);
- }
-}
+ themeSelector.on('click', '.option', (event) => {
+ const selectedTheme = $(event.target).data('value');
+ setTheme(selectedTheme);
+ themeSelector.find('input').val($(event.target).text()); // Set the text of the clicked option to the input
+ localStorage.setItem('theme', selectedTheme);
+ });
+
+ // Signal Selector
+ const signalSelector = $('#signal-selector');
+
+ if (localStorage.getItem('signalUnit')) {
+ signalSelector.find('input').val(signalSelector.find('.option[data-value="' + savedUnit + '"]').text());
+ }
+
+ signalSelector.on('click', '.option', (event) => {
+ const selectedSignalUnit = $(event.target).data('value');
+ signalSelector.find('input').val($(event.target).text()); // Set the text of the clicked option to the input
+ localStorage.setItem('signalUnit', selectedSignalUnit);
+ });
+
+ $('#login-form').submit(function (event) {
+ event.preventDefault();
+
+ // Perform an AJAX request to the /login endpoint
+ $.ajax({
+ type: 'POST',
+ url: './login',
+ data: $(this).serialize(),
+ success: function (data) {
+ // Update the content on the page with the message from the response
+ $('#login-message').text(data.message);
+ setTimeout(function () {
+ location.reload(true);
+ }, 1750);
+ },
+ error: function (xhr, status, error) {
+ // Handle error response
+ if (xhr.status === 403) {
+ // Update the content on the page with the message from the error response
+ $('#login-message').text(xhr.responseJSON.message);
+ } else {
+ // Handle other types of errors if needed
+ console.error('Error:', status, error);
+ }
+ }
+ });
+ });
+
+ // Assuming you have an anchor tag with id 'logout-link'
+ $('.logout-link').click(function (event) {
+ event.preventDefault();
+
+ // Perform an AJAX request to the /logout endpoint
+ $.ajax({
+ type: 'GET', // Assuming the logout is a GET request, adjust accordingly
+ url: './logout',
+ success: function (data) {
+ // Update the content on the page with the message from the response
+ $('#login-message').text(data.message);
+ setTimeout(function () {
+ location.reload(true);
+ }, 1750);
+ },
+ error: function (xhr, status, error) {
+ // Handle error response
+ if (xhr.status === 403) {
+ // Update the content on the page with the message from the error response
+ $('#login-message').text(xhr.responseJSON.message);
+ } else {
+ // Handle other types of errors if needed
+ console.error('Error:', status, error);
+ }
+ }
+ });
+ });
+
+ var extendedFreqRange = localStorage.getItem("extendedFreqRange");
+ if (extendedFreqRange === "true") {
+ $("#extended-frequency-range").prop("checked", true);
+ }
+
+ // Save the value of the checkbox into local storage when its state changes
+ $("#extended-frequency-range").change(function() {
+ var isChecked = $(this).is(":checked");
+ localStorage.setItem("extendedFreqRange", isChecked);
+ });
+
+ var extendedFreqRange = localStorage.getItem("psUnderscores");
+ if (extendedFreqRange === "true") {
+ $("#ps-underscores").prop("checked", true);
+ }
+ var smoothSignal = localStorage.getItem("smoothSignal");
+ if (smoothSignal === "true") {
+ $("#smooth-signal").prop("checked", true);
+ }
+
+ // Save the value of the checkbox into local storage when its state changes
+ $("#ps-underscores").change(function() {
+ var isChecked = $(this).is(":checked");
+ localStorage.setItem("psUnderscores", isChecked);
+ });
+
+ $("#smooth-signal").change(function() {
+ var isChecked = $(this).is(":checked");
+ localStorage.setItem("smoothSignal", isChecked);
+ });
+
+ $('.version-string').text(currentVersion);
+ });
+
+
+ function setTheme(themeName) {
+ const themeColors = themes[themeName];
+ if (themeColors) {
+ // Extracting the RGBA components and opacity value
+ const rgbaComponents = themeColors[2].match(/(\d+(\.\d+)?)/g);
+ const opacity = parseFloat(rgbaComponents[3]);
+ // Calculating 80% of the opacity
+ const newOpacity = opacity * 0.75;
+ // Constructing the new RGBA string with the adjusted opacity
+ const textColor2 = `rgba(${rgbaComponents[0]}, ${rgbaComponents[1]}, ${rgbaComponents[2]}, ${newOpacity})`;
+
+ $(':root').css('--color-main', themeColors[0]);
+ $(':root').css('--color-main-bright', themeColors[1]);
+ $(':root').css('--color-text', themeColors[2]);
+ $(':root').css('--color-text-2', textColor2);
+ }
+ }
+
diff --git a/web/js/setup.js b/web/js/setup.js
index 14dece7..e6646e8 100644
--- a/web/js/setup.js
+++ b/web/js/setup.js
@@ -9,7 +9,12 @@ $(document).ready(function() {
MapCreate();
fetchData();
-
+ setTimeout( function() {
+ if ($('.nav li.active[data-panel="status"]').length > 0) {
+ $('#submit-config').hide();
+ }
+ }, 50 )
+
map.on('click', function(ev) {
$('#lat').val((ev.latlng.lat).toFixed(6));
$('#lng').val((ev.latlng.lng).toFixed(6));
@@ -25,7 +30,39 @@ $(document).ready(function() {
});
}
});
-
+
+ $('#status').show();
+ showPanelFromHash();
+ $('.nav li').click(function() {
+ // Remove background color from all li elements
+ $('.nav li').removeClass('active');
+
+ // Add background color to the clicked li element
+ $(this).addClass('active');
+
+ // Get the data-panel attribute value
+ var panelId = $(this).data('panel');
+ window.location.hash = panelId;
+ // Hide all panels
+ $('.tab-content').hide();
+
+ // Show the corresponding panel
+ $('#' + panelId).show();
+
+ if(panelId == 'identification') {
+ setTimeout(function () {
+ map.invalidateSize();
+ }, 200);
+ }
+
+ if(panelId == 'status') {
+ $('#submit-config').hide();
+ } else {
+ $('#submit-config').show();
+ }
+ });
+
+
$('#login-form').submit(function (event) {
event.preventDefault();
@@ -105,7 +142,9 @@ $(document).ready(function() {
});
});
- $("#console-output").scrollTop($("#console-output")[0].scrollHeight);
+ if($("#console-output").length > 0) {
+ $("#console-output").scrollTop($("#console-output")[0].scrollHeight);
+ }
});
function MapCreate() {
@@ -126,149 +165,19 @@ function MapCreate() {
}).addTo(map);
}
-function fetchData() {
- // Make a GET request to retrieve the data.json file
- fetch("./getData")
- .then(response => {
- if (!response.ok) {
- throw new Error(`HTTP error! Status: ${response.status}`);
- }
- return response.json();
- })
- .then(data => {
- $('#webserver-ip').val(data.webserver.webserverIp);
- $('#webserver-port').val(data.webserver.webserverPort);
- $('#audio-port').val(data.webserver.audioPort);
-
- $('#xdrd-ip').val(data.xdrd.xdrdIp);
- $('#xdrd-port').val(data.xdrd.xdrdPort);
- $('#xdrd-password').val(data.xdrd.xdrdPassword);
-
- $('#audio-devices').val(data.audio.audioDevice);
- $('#audio-channels').val(data.audio.audioChannels);
- $('#audio-quality').val(data.audio.audioBitrate);
-
- $('#webserver-name').val(data.identification.tunerName);
- $('#webserver-desc').val(data.identification.tunerDesc);
- $('#lat').val(data.identification.lat);
- $('#lng').val(data.identification.lon);
- $("#broadcast-tuner").prop("checked", data.identification.broadcastTuner);
- $("#broadcast-address").val(data.identification.proxyIp);
-
- $('#tune-pass').val(data.password.tunePass);
- $('#admin-pass').val(data.password.adminPass);
-
- $("#tuner-public").prop("checked", data.publicTuner);
- $("#tuner-lock").prop("checked", data.lockToAdmin);
- $("#shutdown-tuner").prop("checked", data.autoShutdown);
-
- // Check if latitude and longitude are present in the data
- if (data.identification.lat && data.identification.lon) {
- // Set the map's center to the received coordinates
- map.setView([data.identification.lat, data.identification.lon], 13);
-
- // Add a pin to the map
- if (typeof pin == "object") {
- pin.setLatLng([data.identification.lat, data.identification.lon]);
- } else {
- pin = L.marker([data.identification.lat, data.identification.lon], { riseOnHover:true, draggable:true });
- pin.addTo(map);
- pin.on('drag',function(ev) {
- $('#lat').val((ev.latlng.lat).toFixed(6));
- $('#lng').val((ev.latlng.lng).toFixed(6));
- });
- }
- }
- })
- .catch(error => {
- console.error('Error fetching data:', error.message);
- });
-}
-
-
-function submitData() {
- const webserverIp = $('#webserver-ip').val() || '0.0.0.0';
- const webserverPort = $('#webserver-port').val() || '8080';
- const audioPort = $('#audio-port').val() || '8081';
-
- const xdrdIp = $('#xdrd-ip').val() || '127.0.0.1';
- const xdrdPort = $('#xdrd-port').val() || '7373';
- const xdrdPassword = $('#xdrd-password').val() || 'password';
-
- const audioDevice = $('#audio-devices').val() || 'Microphone (High Definition Audio Device)';
- const audioChannels = ($('.options .option').filter(function() {
- return $(this).text() === $('#audio-channels').val();
- }).data('value') || 2);
- const audioBitrate = ($('.options .option').filter(function() {
- return $(this).text() === $('#audio-quality').val();
- }).data('value') || "192k");
-
- const tunerName = $('#webserver-name').val() || 'FM Tuner';
- const tunerDesc = $('#webserver-desc').val() || 'Default FM tuner description';
- const lat = $('#lat').val();
- const lon = $('#lng').val();
- const broadcastTuner = $("#broadcast-tuner").is(":checked");
- const proxyIp = $("#broadcast-address").val();
-
- const tunePass = $('#tune-pass').val();
- const adminPass = $('#admin-pass').val();
-
- const publicTuner = $("#tuner-public").is(":checked");
- const lockToAdmin = $("#tuner-lock").is(":checked");
- const autoShutdown = $("#shutdown-tuner").is(":checked");
-
- const data = {
- webserver: {
- webserverIp,
- webserverPort,
- audioPort
- },
- xdrd: {
- xdrdIp,
- xdrdPort,
- xdrdPassword
- },
- audio: {
- audioDevice,
- audioChannels,
- audioBitrate,
- },
- identification: {
- tunerName,
- tunerDesc,
- lat,
- lon,
- broadcastTuner,
- proxyIp
- },
- password: {
- tunePass,
- adminPass,
- },
- publicTuner,
- lockToAdmin,
- autoShutdown
- };
-
-
- if(adminPass.length < 1) {
- alert('You need to fill in the admin password before continuing further.');
- return;
+ function showPanelFromHash() {
+ var panelId = window.location.hash.substring(1);
+ if (panelId) {
+ // Hide all panels
+ $('.tab-content').hide();
+
+ // Show the panel corresponding to the hash fragment
+ $('#' + panelId).show();
+
+ // Remove active class from all li elements
+ $('.nav li').removeClass('active');
+
+ // Add active class to the corresponding li element
+ $('.nav li[data-panel="' + panelId + '"]').addClass('active');
}
- // Send data to the server using jQuery
- $.ajax({
- url: './saveData',
- type: 'POST',
- contentType: 'application/json',
- data: JSON.stringify(data),
- success: function (message) {
- alert(message);
- },
- error: function (error) {
- console.error(error);
- }
- });
- }
-
-
-
\ No newline at end of file
+}
\ No newline at end of file
diff --git a/web/js/wizard.js b/web/js/wizard.js
new file mode 100644
index 0000000..07da06f
--- /dev/null
+++ b/web/js/wizard.js
@@ -0,0 +1,62 @@
+$(document).ready(function() {
+ if($('.step:visible').index() == 0) {
+ $('.btn-prev').hide();
+ }
+
+ $('.btn-next').click(function() {
+ var currentStep = $('.step:visible');
+ var nextStep = currentStep.next('.step');
+
+ if (nextStep.length !== 0) {
+ currentStep.hide();
+ nextStep.show();
+ updateProgressBar(nextStep);
+ } else {
+ submitData();
+ }
+
+ updateWizardContent();
+ });
+
+ $('.btn-prev').click(function() {
+ var currentStep = $('.step:visible');
+ var nextStep = currentStep.prev('.step');
+
+ if (nextStep.length !== 0) {
+ currentStep.hide();
+ nextStep.show();
+ updateProgressBar(nextStep);
+ } else {
+ alert('You have reached the beginning of the wizard.');
+ }
+
+ updateWizardContent();
+ });
+});
+
+// Function to update the progress bar buttons
+function updateProgressBar(currentStep) {
+ var stepIndex = $('.step').index(currentStep) + 1;
+ $('.btn-rounded-cube').removeClass('activated');
+ $('.btn-rounded-cube:lt(' + stepIndex + ')').addClass('activated');
+}
+
+function updateWizardContent() {
+ if($('.step:visible').index() == 0) {
+ $('.btn-prev').hide();
+ } else {
+ $('.btn-prev').show();
+ }
+
+ if($('.step:visible').index() == 2) {
+ setTimeout(function () {
+ map.invalidateSize();
+ }, 200);
+ }
+
+ if($('.step:visible').index() == 3) {
+ $('.btn-next').text('Save');
+ } else {
+ $('.btn-next').text('Next')
+ }
+}
\ No newline at end of file
diff --git a/web/setup.ejs b/web/setup.ejs
index 5c64ef6..3d522ee 100644
--- a/web/setup.ejs
+++ b/web/setup.ejs
@@ -15,15 +15,63 @@
<% if (isAdminAuthenticated) { %>
-
FM-DX WebServer
+
[ADMIN PANEL]
-
This web setup allows you to set up your entire tuner.
Some settings will only change after a server restart.
-
In case you are setting up the webserver for the first time, we already filled fail-safe defaults for you.
-
-
-
BASIC SETTINGS
-
Connection to xdrd:
+
+
+ - Status
+ - Connection
+ - Audio
+ - Identification
+ - Online map
+ - Maintenance
+
+
+
+
+
+
+
+
STATUS
+
+
+
+
<%= onlineUsers %>
+
Online users
+
+
+
+
<%= memoryUsage %>
+
Memory usage
+
+
+
+
<%= processUptime %>
+
Uptime
+
+
+
+
Console output
+ <% if (consoleOutput && consoleOutput.length > 0) { %>
+
+ <% consoleOutput.forEach(function(log) { %>
+
<%= log %>
+ <% }); %>
+
+ <% } else { %>
+
No console output available.
+ <% } %>
+
+
Version:
+
Check for the latest source code • Support the developer
+
+
+
+
Connection settings
+
You can set up your connection settings here. Changing these settings requires a server restart.
+
Tuner connection:
+
If you are connecting your tuner wirelessly, enter the tuner IP.
If you use xdrd, use 127.0.0.1 as your IP.
@@ -38,6 +86,7 @@
Webserver connection:
+
Leave the IP at 0.0.0.0 unless you explicitly know you have to change it.
Don't enter your public IP here.
@@ -46,17 +95,18 @@
-
-
-
-
-
-
AUDIO SETTINGS
+
+
+
Audio settings
+
You can set up your audio settings here. Changing these settings requires a server restart.
-
-
-
-
TUNER IDENTIFICATION INFO
-
+
+
+
Tuner Identification info
+
+
Set your tuner name and description here. This info will be visible to anyone who tunes in.
-
+
-
-
-
-
Map broadcast:
-
If your tuner is set to public and ID information is filled, you can add your tuner to a public list.
-
The list is available at list.fmdx.pl.
-
-
-
-
-
-
-
-
+
-
Tuner location:
-
+
Location:
+
Location info is useful for automatic identification of stations using RDS.
@@ -137,49 +179,61 @@
+
-
-
-
MAINTENANCE
-
+
+
+
Map broadcast
+
If your location information is filled, you can add your tuner to a public list.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Check your tuner at list.fmdx.pl.
+
+
+
-
+
-
+
+
-
-
CONSOLE OUTPUT
- <% if (consoleOutput && consoleOutput.length > 0) { %>
-
- <% consoleOutput.forEach(function(log) { %>
-
<%= log %>
- <% }); %>
-
- <% } else { %>
-
No console output available.
- <% } %>
-
+
Feel free to contact us on Discord for community support.
@@ -187,8 +241,7 @@
<% } else { %>
-

-
FM-DX WebServer
+
[ADMIN PANEL]
You are currently not logged in as an administrator and therefore can't change the settings.
Please login below.
@@ -206,5 +259,6 @@
+