From ab000412fa820ab9b380d66ede7799a12ad0af37 Mon Sep 17 00:00:00 2001 From: NoobishSVK Date: Sun, 15 Sep 2024 00:00:42 +0200 Subject: [PATCH] fmlist integration, new admin panel, bugfixes --- package.json | 2 +- server/datahandler.js | 37 +- server/endpoints.js | 22 +- server/index.js | 2 +- server/server_config.js | 4 + server/tx_search.js | 97 ++++-- web/css/breadcrumbs.css | 46 +-- web/css/buttons.css | 4 + web/css/helpers.css | 4 + web/css/main.css | 14 +- web/css/panels.css | 13 + web/css/setup.css | 110 ++++-- web/css/toast.css | 21 +- web/index.ejs | 30 +- web/js/confighandler.js | 12 +- web/js/init.js | 13 +- web/js/main.js | 8 +- web/js/setup.js | 48 ++- web/js/toast.js | 2 +- web/login.ejs | 37 ++ web/setup.ejs | 742 +++++++++++++++++++++------------------- web/wizard.ejs | 106 +++--- 22 files changed, 834 insertions(+), 540 deletions(-) create mode 100644 web/login.ejs diff --git a/package.json b/package.json index 06f3245..1669514 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "fm-dx-webserver", - "version": "1.2.8.1", + "version": "1.3.0", "description": "FM DX Webserver", "main": "index.js", "scripts": { diff --git a/server/datahandler.js b/server/datahandler.js index a9c0855..4b0f16a 100644 --- a/server/datahandler.js +++ b/server/datahandler.js @@ -235,7 +235,7 @@ var dataToSend = { dist: '', azi: '', id: '', - reg: '', + reg: false, pi: '', }, country_name: '', @@ -394,21 +394,26 @@ function handleData(wss, receivedData, rdsWss) { } // Get the received TX info - const currentTx = fetchTx(parseFloat(dataToSend.freq).toFixed(1), dataToSend.pi, dataToSend.ps); - if(currentTx && currentTx.station !== undefined) { - dataToSend.txInfo = { - tx: currentTx.station, - pol: currentTx.pol, - erp: currentTx.erp, - city: currentTx.city, - itu: currentTx.itu, - dist: currentTx.distance, - azi: currentTx.azimuth, - id: currentTx.id, - pi: currentTx.pi, - reg: currentTx.reg - } - } + fetchTx(parseFloat(dataToSend.freq).toFixed(1), dataToSend.pi, dataToSend.ps) + .then((currentTx) => { + if (currentTx && currentTx.station !== undefined) { + dataToSend.txInfo = { + tx: currentTx.station, + pol: currentTx.pol, + erp: currentTx.erp, + city: currentTx.city, + itu: currentTx.itu, + dist: currentTx.distance, + azi: currentTx.azimuth, + id: currentTx.id, + pi: currentTx.pi, + reg: currentTx.reg + }; + } + }) + .catch((error) => { + logError("Error fetching Tx info:", error); + }); // Send the updated data to the client const dataToSendJSON = JSON.stringify(dataToSend); diff --git a/server/endpoints.js b/server/endpoints.js index 2d4adb4..c8a30da 100644 --- a/server/endpoints.js +++ b/server/endpoints.js @@ -62,6 +62,7 @@ router.get('/', (req, res) => { device: serverConfig.device, noPlugins, plugins: serverConfig.plugins, + fmlist_integration: serverConfig.fmlist_integration ? serverConfig.fmlist_integration : true, bwSwitch: serverConfig.bwSwitch ? serverConfig.bwSwitch : false }); } @@ -74,6 +75,11 @@ router.get('/403', (req, res) => { router.get('/wizard', (req, res) => { let serialPorts; + if(!req.session.isAdminAuthenticated) { + res.render('login'); + return; + } + SerialPort.list() .then((deviceList) => { serialPorts = deviceList.map(port => ({ @@ -94,6 +100,11 @@ router.get('/wizard', (req, res) => { router.get('/setup', (req, res) => { let serialPorts; + + if(!req.session.isAdminAuthenticated) { + res.render('login'); + return; + } SerialPort.list() .then((deviceList) => { @@ -260,6 +271,14 @@ router.get('/ping', (req, res) => { }); router.get('/log_fmlist', (req, res) => { + if(dataHandler.dataToSend.txInfo.tx.length === 0) { + res.status(500).send('No suitable transmitter to log.'); + return; + } + + if(serverConfig.extras?.fmlist_integration == false) { + res.status(500).send('FMLIST Integration is not enabled on this server.'); + } const clientIp = req.headers['x-forwarded-for'] || req.connection.remoteAddress; const postData = JSON.stringify({ station: { @@ -278,11 +297,12 @@ router.get('/log_fmlist', (req, res) => { longitude: serverConfig.identification.lon, address: serverConfig.identification.proxyIp.length > 1 ? serverConfig.identification.proxyIp : ('Matches request IP with port ' + serverConfig.webserver.port), webserver_name: serverConfig.identification.tunerName, + omid: serverConfig.extras?.fmlist_omid || '', }, client: { request_ip: clientIp }, - log_msg: `PS: ${dataHandler.dataToSend.ps}, PI: ${dataHandler.dataToSend.pi}, Signal: ${dataHandler.dataToSend.sig.toFixed(0)} dBf` + log_msg: `Logged PS: ${dataHandler.dataToSend.ps.replace(/\s+/g, '_')}, PI: ${dataHandler.dataToSend.pi}, Signal: ${dataHandler.dataToSend.sig.toFixed(0)} dBf` }); const options = { diff --git a/server/index.js b/server/index.js index fa1e542..fa0742d 100644 --- a/server/index.js +++ b/server/index.js @@ -52,7 +52,7 @@ function startPluginsWithDelay(plugins, delay) { setTimeout(() => { const pluginName = path.basename(pluginPath, '.js'); // Extract plugin name from path logInfo(`-----------------------------------------------------------------`); - logInfo(`Plugin ${pluginName} is loaded`); + logInfo(`Plugin ${pluginName} loaded successfully!`); require(pluginPath); }, delay * index); }); diff --git a/server/server_config.js b/server/server_config.js index 38d7a43..2553d94 100644 --- a/server/server_config.js +++ b/server/server_config.js @@ -45,6 +45,10 @@ let serverConfig = { tunePass: "", adminPass: "" }, + extras: { + fmlist_integration: true, + fmlist_omid: "", + }, plugins: [], device: 'tef', defaultFreq: 87.5, diff --git a/server/tx_search.js b/server/tx_search.js index c39c1aa..f44bbe9 100644 --- a/server/tx_search.js +++ b/server/tx_search.js @@ -3,29 +3,69 @@ const { serverConfig } = require('./server_config'); const consoleCmd = require('./console'); let cachedData = {}; - let lastFetchTime = 0; -const fetchInterval = 3000; - +const fetchInterval = 1000; const esSwitchCache = {"lastCheck":0, "esSwitch":false}; const esFetchInterval = 300000; +const usStatesGeoJsonUrl = "https://raw.githubusercontent.com/PublicaMundi/MappingAPI/master/data/geojson/us-states.json"; +let usStatesGeoJson = null; // To cache the GeoJSON data for US states + +// Load the US states GeoJSON data +async function loadUsStatesGeoJson() { + if (!usStatesGeoJson) { + const response = await fetch(usStatesGeoJsonUrl); + usStatesGeoJson = await response.json(); + } +} + +// Function to get bounding box of a state +function getStateBoundingBox(coordinates) { + let minLat = Infinity, maxLat = -Infinity, minLon = Infinity, maxLon = -Infinity; + for (const polygon of coordinates) { + for (const coord of polygon[0]) { // First level in case of MultiPolygon + const [lon, lat] = coord; + if (lat < minLat) minLat = lat; + if (lat > maxLat) maxLat = lat; + if (lon < minLon) minLon = lon; + if (lon > maxLon) maxLon = lon; + } + } + return { minLat, maxLat, minLon, maxLon }; +} + +// Function to check if a city (lat, lon) falls within the bounding box of a state +function isCityInState(lat, lon, boundingBox) { + return lat >= boundingBox.minLat && lat <= boundingBox.maxLat && + lon >= boundingBox.minLon && lon <= boundingBox.maxLon; +} + +// Function to check if a city (lat, lon) is inside any US state and return the state name +function getStateForCoordinates(lat, lon) { + if (!usStatesGeoJson) return null; + + for (const feature of usStatesGeoJson.features) { + const boundingBox = getStateBoundingBox(feature.geometry.coordinates); + if (isCityInState(lat, lon, boundingBox)) { + return feature.properties.name; // Return the state's name if city is inside bounding box + } + } + return null; +} // Fetch data from maps -function fetchTx(freq, piCode, rdsPs) { +async function fetchTx(freq, piCode, rdsPs) { const now = Date.now(); freq = parseFloat(freq); - if(isNaN(freq)) { + if (isNaN(freq)) { return; } - // Check if it's been at least 3 seconds since the last fetch and if the QTH is correct if (now - lastFetchTime < fetchInterval || serverConfig.identification.lat.length < 2 || freq < 87) { return Promise.resolve(); } lastFetchTime = now; - // Check if data for the given frequency is already cached if (cachedData[freq]) { return processData(cachedData[freq], piCode, rdsPs); } @@ -34,32 +74,32 @@ function fetchTx(freq, piCode, rdsPs) { return fetch(url) .then(response => response.json()) - .then(data => { - // Cache the fetched data for the specific frequency + .then(async (data) => { cachedData[freq] = data; + await loadUsStatesGeoJson(); return processData(data, piCode, rdsPs); }) .catch(error => { + console.error("Error fetching data:", error); }); } -function processData(data, piCode, rdsPs) { +async function processData(data, piCode, rdsPs) { let matchingStation = null; let matchingCity = null; - let maxScore = -Infinity; // Initialize maxScore with a very low value + let maxScore = -Infinity; let txAzimuth; let maxDistance; let esMode = checkEs(); - let detectedByPireg = false; // To track if the station was found by pireg + let detectedByPireg = false; - // Helper function to calculate score and update matching station/city function evaluateStation(station, city, distance) { let weightDistance = distance.distanceKm; if (esMode && distance.distanceKm > 500) { weightDistance = Math.abs(distance.distanceKm - 1500); } let erp = station.erp && station.erp > 0 ? station.erp : 1; - const score = (10 * Math.log10(erp * 1000)) / weightDistance; // Calculate score + const score = (10 * Math.log10(erp * 1000)) / weightDistance; if (score > maxScore) { maxScore = score; txAzimuth = distance.azimuth; @@ -77,13 +117,13 @@ function processData(data, piCode, rdsPs) { 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); evaluateStation(station, city, distance); - detectedByPireg = false; // Detected by pi, not pireg + detectedByPireg = false; } } } } - // If no matching station is found, fallback to pireg + // Fallback to pireg if no match is found if (!matchingStation) { for (const cityId in data.locations) { const city = data.locations[cityId]; @@ -92,27 +132,34 @@ function processData(data, piCode, rdsPs) { if (station.pireg && station.pireg.toUpperCase() === 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); evaluateStation(station, city, distance); - detectedByPireg = true; // Detected by pireg + detectedByPireg = true; } } } } } - // Return the results if a station was found, otherwise return undefined + // Determine the state if the city is in the USA + if (matchingStation && matchingCity.itu === 'USA') { + const state = getStateForCoordinates(matchingCity.lat, matchingCity.lon); + if (state) { + matchingCity.state = state; // Add state to matchingCity + } + } + if (matchingStation) { return { station: matchingStation.station.replace("R.", "Radio "), pol: matchingStation.pol.toUpperCase(), erp: matchingStation.erp && matchingStation.erp > 0 ? matchingStation.erp : '?', city: matchingCity.name, - itu: matchingCity.itu, + itu: matchingCity.state ? matchingCity.state + ', ' + matchingCity.itu : matchingCity.itu, distance: maxDistance.toFixed(0), azimuth: txAzimuth.toFixed(0), id: matchingStation.id, pi: matchingStation.pi, foundStation: true, - reg: detectedByPireg // Indicates if it was detected by pireg + reg: detectedByPireg }; } else { return; @@ -151,26 +198,19 @@ function checkEs() { } function haversine(lat1, lon1, lat2, lon2) { - const R = 6371; // Earth radius in kilometers + const R = 6371; const dLat = deg2rad(lat2 - lat1); const dLon = deg2rad(lon2 - lon1); - const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); - const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); - - // Distance in kilometers const distance = R * c; - // Azimuth calculation const y = Math.sin(dLon) * Math.cos(deg2rad(lat2)); const x = Math.cos(deg2rad(lat1)) * Math.sin(deg2rad(lat2)) - Math.sin(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * Math.cos(dLon); const azimuth = Math.atan2(y, x); - - // Convert azimuth from radians to degrees const azimuthDegrees = (azimuth * 180 / Math.PI + 360) % 360; return { @@ -179,7 +219,6 @@ function haversine(lat1, lon1, lat2, lon2) { }; } - function deg2rad(deg) { return deg * (Math.PI / 180); } diff --git a/web/css/breadcrumbs.css b/web/css/breadcrumbs.css index 0338bec..d16da01 100644 --- a/web/css/breadcrumbs.css +++ b/web/css/breadcrumbs.css @@ -191,40 +191,22 @@ label { } .checkbox label { - position: relative; cursor: pointer; - display: flex; - align-items: center; - user-select: none; - } - - .checkbox label:before { - content:''; - appearance: none; - -webkit-appearance: none; - background-color: transparent; - border: 2px solid var(--color-4); - padding: 10px; - display: inline-block; - position: relative; - vertical-align: middle; - cursor: pointer; - 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; - font-size: 18px; - top: -1px; - left: 5px; - width: 18px; - height: 18px; + user-select: none; + padding: 7px 20px; + border-radius: 15px; + text-align: center; + border: 2px solid var(--color-4); + box-sizing: border-box; + transition: 0.35s ease background-color, 0.35s ease color; + } + .checkbox label:hover { + background-color: var(--color-2); + } + + .form-group input:checked + label { + background-color: var(--color-4); color: var(--color-main); } diff --git a/web/css/buttons.css b/web/css/buttons.css index 506ab85..9948f05 100644 --- a/web/css/buttons.css +++ b/web/css/buttons.css @@ -18,6 +18,10 @@ button:hover { opacity: 0.6; } +.cursor-disabled { + cursor: not-allowed; +} + .btn-next { width: 200px; padding: 10px; diff --git a/web/css/helpers.css b/web/css/helpers.css index 049bbbc..c408a3e 100644 --- a/web/css/helpers.css +++ b/web/css/helpers.css @@ -190,6 +190,10 @@ padding: 10px; } +.p-20 { + padding: 20px; +} + .p-left-10 { padding-left: 10px; } diff --git a/web/css/main.css b/web/css/main.css index eb9e425..77501b1 100644 --- a/web/css/main.css +++ b/web/css/main.css @@ -73,7 +73,6 @@ body { .wrapper-outer-static { display: block !important; padding-top: 10px; - padding-bottom: 10px; } #wrapper { @@ -84,6 +83,8 @@ body { margin: auto; position: static; transform: none; + width: calc(100% - 420px); + margin-left: 420px; } a { @@ -141,4 +142,15 @@ hr { margin: 50px auto; width: 100%; } +} + +@media (max-width: 768px) { + #wrapper.setup-wrapper { + width: 100%; + margin-left: 0; + } + + .setup-wrapper h2 { + display: initial; + } } \ No newline at end of file diff --git a/web/css/panels.css b/web/css/panels.css index e5b4677..762bcb4 100644 --- a/web/css/panels.css +++ b/web/css/panels.css @@ -52,6 +52,19 @@ width: 98%; } +.panel-100-real { + width: 100%; +} + +.panel-full { + margin-left: 0; + margin-right: 0; + width: 100%; + max-width: 100% !important; + transition: 0.35s ease; + box-sizing: border-box; +} + @media only screen and (max-width: 768px) { .panel-75 { width: 90%; diff --git a/web/css/setup.css b/web/css/setup.css index 4fd140a..f080068 100644 --- a/web/css/setup.css +++ b/web/css/setup.css @@ -10,14 +10,13 @@ } .setup-wrapper h2 { - font-size: 32px; + font-size: 42px; font-weight: 300; - padding: 10px; - text-transform: uppercase; + padding: 20px 15px; + text-align: left; } - -.setup-wrapper textarea { +#wrapper textarea { width: 100%; max-width: 768px; background-color: var(--color-2); @@ -26,34 +25,92 @@ padding-top: 10px; } +.sidenav { + background-color: var(--color-main); + } + + .sidenav li a:focus { + outline: none; + } + + .sidenav-content { + flex: 1; + position: relative; + overflow-y: auto; + } + + .sidenav .closebtn { + position: absolute; + top: 0; + right: 25px; + font-size: 36px; + margin-left: 50px; + } + + .sidenav h1 { + font-size: 42px; + text-transform: initial; + font-weight: 300; + text-align: center; + } ul.nav { list-style-type: none; padding: 15px 0; - background: var(--color-2); border-radius: 15px; } ul.nav li { - display: inline; - padding: 15px; + padding: 12px 20px; cursor: pointer; transition: color 0.3s ease, background-color 0.3s ease; user-select: none; } -ul.nav li:hover { - color: var(--color-main); - background-color: var(--color-4); +ul.nav li a { + color: var(--color-5) !important; } -li.active { + +ul.nav li:hover { background-color: var(--color-3); } +ul.nav li:hover a { + color: var(--color-main) !important; +} + +ul.nav li.active a { + color: var(--color-main) !important; + font-weight: bold; +} + + +li.active { + background-color: var(--color-4); +} + .tab-content { display: none; } +#navigation { + position: fixed; + top: 0; + left: 0; + width: 420px; /* Width of the sidenav */ + height: 100%; + z-index: 1000; /* Ensure it's above other content */ + transition: margin-left 0.3s ease; /* Smooth transition */ + } + + .admin-wrapper { + transition: margin-left 0.3s ease, width 0.3s ease; + } + + .admin-wrapper > .panel-full > .panel-full { + min-height: 100vh; + } + #map { height:400px; width:100%; @@ -66,6 +123,12 @@ li.active { .setup-wrapper h3 { font-weight: 300; margin: 8px; + font-size: 36px; + color: var(--color-5) +} + +.setup-wrapper h4 { + color: var(--color-4); } @@ -87,18 +150,15 @@ li.active { } @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: 15px; - min-width: fit-content; + .setup-wrapper .panel-33, .setup-wrapper .panel-50 { + background: var(--color-1-transparent); } + #navigation { + width: 100vw; /* You can make the sidenav full width on mobile if you want */ + } + + .admin-wrapper { + margin-left: 0; + width: 100%; + } } \ No newline at end of file diff --git a/web/css/toast.css b/web/css/toast.css index a3682e8..dd73970 100644 --- a/web/css/toast.css +++ b/web/css/toast.css @@ -1,4 +1,11 @@ /* Basic Toast Styling */ + #toast-container { + position: fixed; + top: 20px; + right: 96px; + z-index: 9999; + } + .toast { padding: 15px; margin-top: 10px; @@ -8,7 +15,7 @@ position: relative; transition: opacity 0.3s ease, transform 0.3s ease, filter 0.3s ease; transform: translateY(-10px); /* Initial animation state */ - backdrop-filter: blur(10px); + backdrop-filter: blur(50px); } .toast:hover { @@ -87,4 +94,14 @@ font-size: 16px; color: #fff; cursor: pointer; - } \ No newline at end of file + } + + @media only screen and (max-width: 768px) { + #toast-container { + left: 0; + right: 0; + } + .toast { + margin: auto; + } +} \ No newline at end of file diff --git a/web/index.ejs b/web/index.ejs index 43422b6..f808779 100644 --- a/web/index.ejs +++ b/web/index.ejs @@ -157,17 +157,11 @@
- <% if(device === 'other' || bwSwitch == false) { %> - - - - <% } else { %> - - - - <% } %> + + + <% if (device == 'tef' && bwSwitch == true) { %> - -
+
<% if (!noPlugins) { %> diff --git a/web/js/confighandler.js b/web/js/confighandler.js index 44ebedc..1e46725 100644 --- a/web/js/confighandler.js +++ b/web/js/confighandler.js @@ -77,6 +77,9 @@ function submitData() { plugins.push($(this).data('name')); }); + const fmlistIntegration = $("#fmlist-integration").is(":checked") || false; + const fmlistOmid = $('#fmlist-omid').val(); + const publicTuner = $("#tuner-public").is(":checked"); const lockToAdmin = $("#tuner-lock").is(":checked"); const autoShutdown = $("#shutdown-tuner").is(":checked") || false; @@ -143,6 +146,10 @@ function submitData() { tunePass, adminPass, }, + extras: { + fmlistIntegration, + fmlistOmid, + }, plugins, device, publicTuner, @@ -164,7 +171,7 @@ function submitData() { contentType: 'application/json', data: JSON.stringify(data), success: function (message) { - alert(message); + sendToast('success', 'Data saved!', message, true, true); }, error: function (error) { console.error(error); @@ -307,6 +314,9 @@ function submitData() { }); } } + + $("#fmlist-integration").prop("checked", data.extras ? data.extras?.fmlist_integration : "true"); + $('#fmlist-omid').val(data.extras?.fmlist_omid); }) .catch(error => { console.error('Error fetching data:', error.message); diff --git a/web/js/init.js b/web/js/init.js index 6c3ef96..d73378c 100644 --- a/web/js/init.js +++ b/web/js/init.js @@ -1,9 +1,9 @@ -var currentDate = new Date('Sep 12, 2024 21:30:00'); +var currentDate = new Date('Sep 15, 2024 00:00:00'); var day = currentDate.getDate(); var month = currentDate.getMonth() + 1; // Months are zero-indexed, so add 1 var year = currentDate.getFullYear(); var formattedDate = day + '/' + month + '/' + year; -var currentVersion = 'v1.2.8.1 [' + formattedDate + ']'; +var currentVersion = 'v1.3.0 [' + formattedDate + ']'; getInitialSettings(); removeUrlParameters(); // Call this function to remove URL parameters @@ -30,9 +30,8 @@ function getInitialSettings() { } function removeUrlParameters() { - // Get the current URL without the query parameters - var urlWithoutParams = window.location.protocol + "//" + window.location.host + window.location.pathname; - - // Replace the current URL with the new one, without reloading the page - window.history.replaceState({ path: urlWithoutParams }, '', urlWithoutParams); + if (window.location.pathname === "/") { + var urlWithoutParams = window.location.protocol + "//" + window.location.host + window.location.pathname; + window.history.replaceState({ path: urlWithoutParams }, '', urlWithoutParams); + } } diff --git a/web/js/main.js b/web/js/main.js index ae7ee2b..30b1c6e 100644 --- a/web/js/main.js +++ b/web/js/main.js @@ -828,12 +828,18 @@ const updateDataElements = throttle(function(parsedData) { updateTextIfChanged($('#data-station-pol'), parsedData.txInfo.pol); updateTextIfChanged($('#data-station-distance'), parsedData.txInfo.dist); updateHtmlIfChanged($('#data-station-azimuth'), parsedData.txInfo.azi + '°'); - updateHtmlIfChanged($('#data-regular-pi'), parsedData.txInfo.reg === true ? parsedData.txInfo.pi : ' '); $dataStationContainer.css('display', 'block'); } else { $dataStationContainer.removeAttr('style'); } + if(parsedData.txInfo.tx.length > 1 && parsedData.txInfo.dist > 150 && parsedData.txInfo.dist < 4000) { + $('#log-fmlist').attr('disabled', 'false').removeClass('btn-disabled cursor-disabled'); + } else { + $('#log-fmlist').attr('disabled', 'true').addClass('btn-disabled cursor-disabled'); + } + updateHtmlIfChanged($('#data-regular-pi'), parsedData.txInfo.reg === true ? parsedData.txInfo.pi : ' '); + updateCounter++; if (updateCounter % 8 === 0) { $dataTp.html(parsedData.tp === 0 ? "TP" : "TP"); diff --git a/web/js/setup.js b/web/js/setup.js index 0386936..2621da9 100644 --- a/web/js/setup.js +++ b/web/js/setup.js @@ -153,6 +153,7 @@ $(document).ready(function() { updateTabFocus(currentTabIndex); $tabs.on('keydown', handleKeyDown); + //toggleNav(); }); function MapCreate() { @@ -188,4 +189,49 @@ function MapCreate() { // Add active class to the corresponding li element $('.nav li[data-panel="' + panelId + '"]').addClass('active'); } -} \ No newline at end of file + if(window.location.hash.length == 0) { + $('.nav li[data-panel="dashboard"]').addClass('active'); + } +} + +function toggleNav() { + const navOpen = $("#navigation").css('margin-left') === '0px'; + const isMobile = window.innerWidth <= 768; // Define mobile screen width threshold (you can adjust this as needed) + + if (navOpen) { + // Close the navigation + if (isMobile) { + // Do nothing to .admin-wrapper on mobile (since we're overlaying) + $(".admin-wrapper").css({ + 'margin-left': '0', + 'width': '100%' // Reset content to full width on close + }); + $("#navigation").css('margin-left', 'calc(64px - 100vw)'); + } else { + // On desktop, adjust the content margin and width + $(".admin-wrapper").css({ + 'margin-left': '64px', + 'width': 'calc(100% - 64px)' + }); + $("#navigation").css('margin-left', '-356px'); + } + $(".sidenav-close").html(''); + } else { + // Open the navigation + $("#navigation").css('margin-left', '0'); + if (isMobile) { + // On mobile, overlay the navigation + $(".admin-wrapper").css({ + 'margin-left': '0', // Keep content in place when sidenav is open + 'width': '100%' // Keep content at full width + }); + } else { + // On desktop, push the content + $(".admin-wrapper").css({ + 'margin-left': '420px', + 'width': 'calc(100% - 420px)' + }); + } + $(".sidenav-close").html(''); + } +} diff --git a/web/js/toast.js b/web/js/toast.js index ac7c3e8..e0d4613 100644 --- a/web/js/toast.js +++ b/web/js/toast.js @@ -18,7 +18,7 @@ function sendToast(type, title, message, persistent, important) { // Create the toast element var $toast = $(` -
+
${toastTitle}
diff --git a/web/login.ejs b/web/login.ejs new file mode 100644 index 0000000..9f433bb --- /dev/null +++ b/web/login.ejs @@ -0,0 +1,37 @@ + + + + Login - FM-DX Webserver + + + + + + + + + + +
+
+
+
+ +

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

+

Please login below.

+
+
+

Login

+
+ + +
+
+
+
+ + + + diff --git a/web/setup.ejs b/web/setup.ejs index 59c1d32..3258075 100644 --- a/web/setup.ejs +++ b/web/setup.ejs @@ -12,268 +12,244 @@ -
+
-
- <% if (isAdminAuthenticated) { %> -
- -

[ADMIN PANEL]

-
-
- -
+ +
+
+

Dashboard

-
-
+
+
<%= onlineUsers %> -

Online users

+

Online users

-
+
<%= memoryUsage %> -

Memory usage

+

Memory usage

-
+
<%= processUptime %> -

Uptime

+

Uptime

-

Current users

- - - - - - - - - - - <% if (connectedUsers.length > 0) { %> - <% connectedUsers.forEach(user => { %> +
+
+

Current users

+
IP AddressLocationOnline since
+ - - - - + + + + - <% }); %> - <% } else { %> - - - - <% } %> - -
<%= user.ip %><%= user.location %><%= user.time %>KickIP AddressLocationOnline since
No users online
- -

Maintenance

-
-
- - + + + <% if (connectedUsers.length > 0) { %> + <% connectedUsers.forEach(user => { %> + + <%= user.ip %> + <%= user.location %> + <%= user.time %> + Kick + + <% }); %> + <% } else { %> + + No users online + + <% } %> + +
-
- - -
-
- - -

-
- -
- - -
-
- - -

- -

Console

- <% 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.

-
- - -
-
- - -
-
- - -
+
+
+

Console

+ <% if (consoleOutput && consoleOutput.length > 0) { %> +
+ <% consoleOutput.forEach(function(log) { %> +
<%= log %>
+ <% }); %> +
+ <% } else { %> +

No console output available.

+ <% } %> +
- -
- -

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.

-
- - -
-
- - -
-
- -
+ +

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. -

- -