From 1fb9b99b9592d3e7e3aa4eda6e121d7e28490b10 Mon Sep 17 00:00:00 2001 From: NoobishSVK Date: Tue, 27 Feb 2024 23:04:26 +0100 Subject: [PATCH] new admin system, ui changes, bugfixes --- console.js | 2 +- datahandler.js | 4 +- fmdx_list.js | 3 +- index.js | 74 +++++-- server_config.js | 5 +- stream/index.js | 7 +- tx_search.js | 2 +- web/css/breadcrumbs.css | 37 +++- web/css/buttons.css | 53 ++++- web/css/helpers.css | 29 ++- web/css/setup.css | 62 ++++++ web/images/openradio_logo_neutral.png | Bin 0 -> 13498 bytes web/index.ejs | 17 +- web/js/3las/3las.js | 1 - web/js/confighandler.js | 145 +++++++++++++ web/js/main.js | 56 +++-- web/js/settings.js | 297 ++++++++++++++------------ web/js/setup.js | 205 +++++------------- web/js/wizard.js | 62 ++++++ web/setup.ejs | 180 ++++++++++------ web/wizard.ejs | 208 ++++++++++++++++++ 21 files changed, 1038 insertions(+), 411 deletions(-) create mode 100644 web/images/openradio_logo_neutral.png create mode 100644 web/js/confighandler.js create mode 100644 web/js/wizard.js create mode 100644 web/wizard.ejs 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 0000000000000000000000000000000000000000..3333b5e6b57f9e75a2936cb09a9bd15db7165e57 GIT binary patch literal 13498 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 h6 - ST + ST MS @@ -115,6 +115,8 @@
+ + <% if (antennaSwitch) { %> + <% } %> +
@@ -211,7 +215,7 @@ @@ -244,6 +248,10 @@ +
+ + +
@@ -273,7 +281,8 @@

FM-DX WebServer
by Noobish, kkonradpl & the OpenRadio community.

- v1.0.9 [23/2/2024] +
+ [Receiver Map]
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

+ +
+
+ <%= 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 codeSupport 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.

+

Your audio device port.
+ This is where your tuner is plugged in. +

+

Audio channel count.
+ 1: Mono • 2: Stereo +

+

The bitrate of the mp3 audio.
+ Minimum: 64 Kbps • Maximum: 256 Kbps +

-
- -
-
-

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.

+
+ +
+

Maintenance

+
- +

- -

+ +



+
+ + +

- +
-
+
- +

- - -
+ +
-
-

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 @@ + diff --git a/web/wizard.ejs b/web/wizard.ejs new file mode 100644 index 0000000..a2754c4 --- /dev/null +++ b/web/wizard.ejs @@ -0,0 +1,208 @@ + + + + FM-DX Webserver + + + + + + + + + + +
+ <% if (isAdminAuthenticated) { %> +
+ +

FM-DX WebServer

+

[SETUP WIZARD]

+
+
+
1
+
2
+
3
+
4
+
+ +
+ + +
+

BASIC SETTINGS

+

Welcome to the setup wizard!
Let's set up some basic things.

+

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.

+
+
+ + +
+
+ + +
+
+ + +
+
+

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.

+
+
+ + +
+
+ + +
+
+
+ + + + + + + + + + + + +
+
+

Feel free to contact us on Discord for community support.

+
+ <% } else { %> +
+ +

[ADMIN PANEL]

+

You are currently not logged in as an administrator and therefore can't change the settings.

+

Please login below.

+
+
+

LOGIN

+
+ + +
+
+
+ <% } %> +
+ + + + + + +