1
0
mirror of https://github.com/KubaPro010/fm-dx-webserver.git synced 2026-02-26 22:13:53 +01:00

new chat window, bugfixes, component update

This commit is contained in:
Marek Farkaš
2025-04-22 21:23:11 +02:00
parent 79e4205612
commit 008441f93a
19 changed files with 286 additions and 150 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "fm-dx-webserver",
"version": "1.3.6.1",
"version": "1.3.7",
"description": "FM DX Webserver",
"main": "index.js",
"scripts": {
@@ -12,10 +12,10 @@
"author": "",
"license": "ISC",
"dependencies": {
"@mapbox/node-pre-gyp": "1.0.11",
"body-parser": "1.20.3",
"@mapbox/node-pre-gyp": "2.0.0",
"body-parser": "2.2.0",
"ejs": "3.1.10",
"express": "4.21.2",
"express": "5.1.0",
"express-session": "1.18.1",
"ffmpeg-static": "5.2.0",
"http": "0.0.1-security",
@@ -23,6 +23,6 @@
"koffi": "2.7.2",
"net": "1.0.2",
"serialport": "12.0.0",
"ws": "8.18.0"
"ws": "8.18.1"
}
}

View File

@@ -179,21 +179,44 @@ router.get('/api', (req, res) => {
});
const loginAttempts = {}; // Format: { 'ip': { count: 1, lastAttempt: 1234567890 } }
const MAX_ATTEMPTS = 25;
const WINDOW_MS = 15 * 60 * 1000;
const authenticate = (req, res, next) => {
const ip = req.ip || req.connection.remoteAddress;
const now = Date.now();
if (!loginAttempts[ip]) {
loginAttempts[ip] = { count: 0, lastAttempt: now };
} else if (now - loginAttempts[ip].lastAttempt > WINDOW_MS) {
loginAttempts[ip] = { count: 0, lastAttempt: now };
}
if (loginAttempts[ip].count >= MAX_ATTEMPTS) {
return res.status(403).json({
message: 'Too many login attempts. Please try again later.'
});
}
const { password } = req.body;
// Check if the entered password matches the admin password
loginAttempts[ip].lastAttempt = now;
if (password === serverConfig.password.adminPass) {
req.session.isAdminAuthenticated = true;
req.session.isTuneAuthenticated = true;
logInfo('User from ' + req.connection.remoteAddress + ' logged in as an administrator.');
logInfo(`User from ${ip} logged in as an administrator.`);
loginAttempts[ip].count = 0;
next();
} else if (password === serverConfig.password.tunePass) {
req.session.isAdminAuthenticated = false;
req.session.isTuneAuthenticated = true;
logInfo('User from ' + req.connection.remoteAddress + ' logged in with tune permissions.');
logInfo(`User from ${ip} logged in with tune permissions.`);
loginAttempts[ip].count = 0;
next();
} else {
loginAttempts[ip].count += 1;
res.status(403).json({ message: 'Login failed. Wrong password?' });
}
};

View File

@@ -132,6 +132,7 @@ function fetchBannedAS(callback) {
function processConnection(clientIp, locationInfo, currentUsers, ws, callback) {
const options = { year: "numeric", month: "numeric", day: "numeric", hour: "2-digit", minute: "2-digit" };
const connectionTime = new Date().toLocaleString([], options);
const normalizedClientIp = clientIp?.replace(/^::ffff:/, '');
fetchBannedAS((error, bannedAS) => {
if (error) {
@@ -155,7 +156,7 @@ function processConnection(clientIp, locationInfo, currentUsers, ws, callback) {
});
consoleCmd.logInfo(
`Web client \x1b[32mconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]\x1b[0m Location: ${userLocation}`
`Web client \x1b[32mconnected\x1b[0m (${normalizedClientIp}) \x1b[90m[${currentUsers}]\x1b[0m Location: ${userLocation}`
);
callback("User allowed");

View File

@@ -388,6 +388,7 @@ wss.on('connection', (ws, request) => {
const output = serverConfig.xdrd.wirelessConnection ? client : serialport;
let clientIp = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
const userCommandHistory = {};
const normalizedClientIp = clientIp?.replace(/^::ffff:/, '');
if (serverConfig.webserver.banlist?.includes(clientIp)) {
ws.close(1008, 'Banned IP');
@@ -509,7 +510,7 @@ wss.on('connection', (ws, request) => {
}
if (code !== 1008) {
logInfo(`Web client \x1b[31mdisconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]`);
logInfo(`Web client \x1b[31mdisconnected\x1b[0m (${normalizedClientIp}) \x1b[90m[${currentUsers}]`);
}
});

View File

@@ -87,14 +87,11 @@ async function fetchTx(freq, piCode, rdsPs) {
const now = Date.now();
freq = parseFloat(freq);
if (isNaN(freq)) {
return;
}
if (isNaN(freq)) return;
if (now - lastFetchTime < fetchInterval
|| serverConfig.identification.lat.length < 2
|| freq < 87
|| (currentPiCode == piCode && currentRdsPs == rdsPs))
{
|| (currentPiCode == piCode && currentRdsPs == rdsPs)) {
return Promise.resolve();
}
@@ -107,15 +104,33 @@ async function fetchTx(freq, piCode, rdsPs) {
const url = "https://maps.fmdx.org/api/?freq=" + freq;
try {
const response = await fetch(url, { redirect: 'manual' });
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
const data = await response.json();
// Try POST first
const postResponse = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ freq }), // You can omit or customize this
redirect: 'manual'
});
if (!postResponse.ok) throw new Error(`POST failed: ${postResponse.status}`);
const data = await postResponse.json();
cachedData[freq] = data;
if(serverConfig.webserver.rdsMode == true) await loadUsStatesGeoJson();
if (serverConfig.webserver.rdsMode === true) await loadUsStatesGeoJson();
return processData(data, piCode, rdsPs);
} catch (error) {
console.error("Error fetching data:", error);
return null; // Return null to indicate failure
} catch (postError) {
console.warn("POST failed, trying GET:", postError);
try {
const getResponse = await fetch(url, { redirect: 'manual' });
if (!getResponse.ok) throw new Error(`GET failed: ${getResponse.status}`);
const data = await getResponse.json();
cachedData[freq] = data;
if (serverConfig.webserver.rdsMode === true) await loadUsStatesGeoJson();
return processData(data, piCode, rdsPs);
} catch (getError) {
console.error("GET also failed:", getError);
return null;
}
}
}

View File

@@ -30,9 +30,12 @@ switch (component) {
*/
case 'checkbox':
%>
<div class="form-group checkbox <%= cssClass %>">
<input type="checkbox" tabindex="0" id="<%= id %>" aria-label="<%= label %>">
<label for="<%= id %>"><i class="fa-solid fa-toggle-off m-right-10 <%= typeof iconClass !== 'undefined' ? iconClass : '' %>"></i> <%= label %></label>
<div class="form-group">
<div class="switch flex-container flex-phone flex-phone-column flex-phone-center">
<input type="checkbox" tabindex="0" id="<%= id %>" aria-label="<%= label %>" />
<label for="<%= id %>"></label>
<span class="text-smaller text-uppercase text-bold color-4 p-10"><%= label %></span>
</div>
</div>
<%
break;

View File

@@ -308,6 +308,47 @@ input[type="range"]::-moz-range-thumb {
/* End Toggle Switch */
.switch {
user-select: none;
}
.switch input[type=checkbox] {
height: 0;
width: 0;
visibility: hidden;
}
.switch input[type=checkbox]:checked + label {
background: var(--color-4);
}
.switch input[type=checkbox]:checked + label::after {
left: calc(100% - 4px);
transform: translateX(-100%);
background-color: var(--color-1);
}
.switch label {
cursor: pointer;
min-width: 64px;
max-width: 64px;
height: 38px;
background-color: var(--color-1);
transition: 0.35s background-color;
display: block;
border-radius: 24px;
margin: 0;
position: relative;
border: 2px solid var(--color-3);
}
.switch label::after {
content: "";
position: absolute;
top: 4px;
left: 4px;
width: 26px;
height: 26px;
background: var(--color-5);
border-radius: 16px;
transition: 0.3s;
}
select {
height: 42px;
width: 150px;

View File

@@ -119,6 +119,10 @@
align-items: center;
}
.flex-column {
flex-direction: column;
}
.hover-brighten {
transition: 0.3s ease background-color;
}
@@ -302,6 +306,10 @@ table .input-text {
.flex-phone-column {
flex-flow: column;
}
.flex-phone-center {
align-items: center;
justify-content: center;
}
.hide-phone {
display: none;
}

View File

@@ -86,10 +86,6 @@ body.modal-open {
background-color: var(--color-main);
}
.modal-panel .flex-container {
align-items: stretch;
}
.modal-panel h1 {
font-size: 42px;
}
@@ -124,7 +120,6 @@ body.modal-open {
.modal-panel label {
width: 200px;
margin: auto;
}
.modal-panel-chat {
@@ -147,6 +142,37 @@ body.modal-open {
border-radius: 15px 15px 0px 0px;
}
/* Popup Windows */
.popup-window {
width: 500px;
background: var(--color-1-transparent);
backdrop-filter: blur(5px);
position: absolute !important;
border-radius: 15px;
display: none;
overflow: hidden;
border: 3px solid var(--color-1);
z-index: 100;
}
.popup-header {
padding: 8px;
cursor: move;
display: flex;
justify-content: space-between;
align-items: center;
}
.popup-close {
background: none;
border: none;
color: var(--color-3);
font-size: 16px;
cursor: pointer;
max-width: 24px;
margin-left: auto;
}
@media only screen and (max-width: 768px) {
.modal-content {
min-width: 90% !important;
@@ -165,11 +191,9 @@ body.modal-open {
.modal-panel {
width: 100%;
}
.modal-panel-chat {
height: 510px;
}
#chat-chatbox {
height: 333px !important;
.popup-window {
width: 90%;
}
}

View File

@@ -69,6 +69,9 @@
.panel-75 {
width: 90%;
}
.no-bg-phone {
background-color: transparent;
}
.panel-33 h2 {
padding: 20px;
padding-top: 5px;
@@ -81,7 +84,6 @@
margin: auto;
width: 90%;
margin-bottom: 20px;
background-color: transparent;
}
*[class^="panel-"]:not(.no-bg):not(.no-filter):not(#ps-container),
.panel-100.w-100::before {

View File

@@ -5,7 +5,10 @@
<link href="css/entry.css" type="text/css" rel="stylesheet">
<link href="css/flags.min.css" type="text/css" rel="stylesheet">
<link href="css/libs/fontawesome.css" type="text/css" rel="stylesheet">
<link href="css/libs/jquery-ui.min.css" type="text/css" rel="stylesheet">
<!--<link href="css/libs/jquery-ui.theme.min.css" type="text/css" rel="stylesheet">-->
<script src="js/libs/jquery.min.js"></script>
<script src="js/libs/jquery-ui.min.js"></script>
<script src="js/libs/chart.umd.min.js"></script>
<script src="js/libs/luxon.min.js"></script>
<script src="js/libs/chartjs-adapter-luxon.umd.min.js"></script>
@@ -178,7 +181,7 @@
</div>
<div class="flex-container">
<div class="panel-33 hover-brighten tooltip" id="pi-code-container" data-tooltip="Clicking on the PI code will show the current station on a map.">
<div class="panel-33 hover-brighten tooltip no-bg-phone" id="pi-code-container" data-tooltip="Clicking on the PI code will show the current station on a map.">
<h2 class="signal-heading">PI CODE</h2>
<div class="text-small text-gray highest-signal-container">
<span id="data-regular-pi">&nbsp;</span>
@@ -186,12 +189,12 @@
<span id="data-pi" class="text-big text-uppercase"></span>
</div>
<div class="panel-33 hover-brighten" id="freq-container">
<div class="panel-33 hover-brighten no-bg-phone" id="freq-container">
<h2>FREQUENCY</h2>
<span id="data-frequency" class="text-big"></span>
</div>
<div class="panel-33">
<div class="panel-33 no-bg-phone">
<h2 class="signal-heading">SIGNAL</h2>
<div class="text-small text-gray highest-signal-container">
<i class="fa-solid fa-arrow-up"></i>
@@ -334,7 +337,7 @@
</div>
<div class="flex-container flex-phone flex-phone-column">
<div class="panel-75 hover-brighten" id="rt-container" style="height: 100px;">
<div class="panel-75 hover-brighten no-bg-phone" id="rt-container" style="height: 100px;">
<h2 style="margin-top: 4px;">RADIOTEXT</h2>
<div id="data-rt0">
<span></span>
@@ -345,7 +348,7 @@
<hr class="hide-desktop">
</div>
<div class="panel-33 hover-brighten tooltip" style="min-height: 91px;"
<div class="panel-33 hover-brighten tooltip no-bg-phone" style="min-height: 91px;"
data-tooltip="This panel contains the current TX info when RDS is loaded.<br><strong>Clicking on this panel copies the info into the clipboard.</strong>">
<div id="data-station-container">
<h2 style="margin-top: 0;" class="mb-0">
@@ -364,7 +367,7 @@
</div>
<div class="panel-10 no-bg center-phone" style="margin-left: 0; margin-top: 0; margin-right: 0;display:flex;">
<div class="panel-100" style="margin-left: 0;">
<div class="panel-100 no-bg-phone" style="margin-left: 0;">
<h2 class="bottom-10">AF</h2>
<div id="af-list" style="text-align: center;">
<ul> </ul>
@@ -373,7 +376,7 @@
</div>
</div>
<div id="flags-container-phone" class="panel-33">
<div id="flags-container-phone" class="panel-33 no-bg-phone">
<h2 class="show-phone">
<div class="data-pty text-color-default"></div>
</h2>
@@ -395,6 +398,30 @@
</div>
</div>
<div class="popup-window" id="popup-panel-chat">
<div class="flex-container flex-column flex-phone flex-phone-column" style="height: calc(100%);">
<div class="popup-header hover-brighten flex-center text-medium-big"><button class="popup-close">✖</button></div>
<div class="popup-content text-left flex-container flex-phone flex-column" style="flex: 1;">
<div style="text-align: center;white-space-collapse: collapse;">
<div class="flex-phone flex-container flex-center top-10">
<input type="text" id="chat-nickname" name="chat-nickname" placeholder="Nickname" style="border-radius: 15px 0 0 15px;padding-top:0;padding-bottom:0;border: 2px solid var(--color-4)">
<button class="br-0 w-100" style="height: 48px; border-radius: 0 15px 15px 0;margin-left:-3px;" id="chat-nickname-save">Save</button>
</div>
<p style="margin: 5px;" class="text-small">
Current identity: <span style="color: #bada55;" id="chat-admin"></span> <strong id="chat-identity-nickname"></strong>
</p>
</div>
<div id="chat-chatbox" class="bg-color-1" style="padding: 10px; overflow-y: auto; flex-grow: 1; min-height: 0; flex-basis: 0; min-height: 120px;"></div>
<div class="flex-container flex-phone">
<input class="bg-color-2" type="text" id="chat-send-message" name="chat-send-message" placeholder="Send message..." style="background-color: var(--color-2);width: 100%;">
<button aria-label="Send message" class="chat-send-message-btn br-0" style="width: 80px; height: 48px;"><i class="fa-solid fa-paper-plane"></i></button>
</div>
</div>
</div>
</div>
<div id="myModal" class="modal">
<div class="modal-panel">
<div class="flex-container flex-phone" style="height: calc(100% - 100px)">
@@ -430,9 +457,11 @@
</div>
<% } %>
<div class="auto" style="max-width: 215px;">
<%- include('_components', {component: 'checkbox', cssClass: 'top-25', label: 'Manual decimals', id: 'extended-frequency-range'}) %>
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'RDS PS Underscores', id: 'ps-underscores'}) %>
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Imperial units', id: 'imperial-units'}) %>
</div>
<% if (isAdminAuthenticated) { %>
<p class="color-3">You are logged in as an adminstrator.</p>
@@ -500,29 +529,6 @@
</div>
</div>
</div>
<div class="modal-panel-chat">
<div class="modal-panel-sidebar hover-brighten flex-center text-medium-big closeModal" role="button" aria-label="Close chat" tabindex="0"><i class="fa-solid fa-chevron-down"></i></div>
<div class="modal-panel-content text-left">
<div style="text-align: center;white-space-collapse: collapse;">
<div class="flex-phone flex-container flex-center top-10">
<input type="text" id="chat-nickname" name="chat-nickname" placeholder="Nickname" style="border-radius: 15px 0 0 15px;padding-top:0;padding-bottom:0;border: 2px solid var(--color-4)">
<button class="br-0 w-100" style="height: 48px; border-radius: 0 15px 15px 0;margin-left:-3px;" id="chat-nickname-save">Save</button>
</div>
<p style="margin: 5px;" class="text-small">
Current identity: <span style="color: #bada55;" id="chat-admin"></span> <strong id="chat-identity-nickname"></strong>
</p>
</div>
<div id="chat-chatbox" class="bg-color-1" style="height: 258px;padding: 10px;overflow-y: auto;">
</div>
<div class="flex-container flex-phone" style="align-content: stretch;">
<input class="bg-color-2" type="text" id="chat-send-message" name="chat-send-message" placeholder="Send message..." style="background-color: var(--color-2);width: 100%;">
<button aria-label="Send message" class="chat-send-message-btn br-0" style="width: 80px; height: 48px;"><i class="fa-solid fa-paper-plane"></i></button>
</div>
</div>
</div>
</div>
</div>
<div id="toast-container"></div>

View File

@@ -34,20 +34,17 @@ function OnConnectivityCallback(isConnected) {
}
}
function isIOS() {
return /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
}
function OnPlayButtonClick(_ev) {
const $playbutton = $('.playbutton');
const isiOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
const isAppleiOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
if (Stream) {
console.log("Stopping stream...");
shouldReconnect = false;
destroyStream();
$playbutton.find('.fa-solid').toggleClass('fa-stop fa-play');
if (isiOS && 'audioSession' in navigator) {
if (isAppleiOS && 'audioSession' in navigator) {
navigator.audioSession.type = "none";
}
} else {
@@ -56,7 +53,7 @@ function OnPlayButtonClick(_ev) {
createStream();
Stream.Start();
$playbutton.find('.fa-solid').toggleClass('fa-play fa-stop');
if (isiOS && 'audioSession' in navigator) {
if (isAppleiOS && 'audioSession' in navigator) {
navigator.audioSession.type = "playback";
}
}

View File

@@ -10,9 +10,29 @@ $(document).ready(function() {
const chatNicknameInput = $('#chat-nickname');
const chatNicknameSave = $('#chat-nickname-save');
$(function () {
$("#popup-panel-chat").draggable({
handle: ".popup-header"
}).resizable({
minHeight: 300,
minWidth: 250
});
$(".chatbutton").on("click", function () {
$("#popup-panel-chat").fadeIn(200, function () {
chatMessages.scrollTop(chatMessages[0].scrollHeight);
});
});
$("#popup-panel-chat .popup-close").on("click", function () {
$("#popup-panel-chat").fadeOut(200);
});
});
// Function to generate a random string
function generateRandomString(length) {
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const characters = 'ABCDEFGHJKMNOPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz0123456789';
let result = '';
for (let i = 0; i < length; i++) {
result += characters.charAt(Math.floor(Math.random() * characters.length));
@@ -21,7 +41,7 @@ $(document).ready(function() {
}
// Load nickname from localStorage on page load
let savedNickname = localStorage.getItem('nickname') || `Anonymous User ${generateRandomString(5)}`;
let savedNickname = localStorage.getItem('nickname') || `User ${generateRandomString(5)}`;
chatNicknameInput.val(savedNickname);
chatIdentityNickname.text(savedNickname);

View File

@@ -96,8 +96,6 @@ function populateFields(data, prefix = "") {
$element.val(value);
}
});
updateIconState();
}
function updateConfigData(data, prefix = "") {

View File

@@ -1,9 +1,9 @@
var currentDate = new Date('Apr 19, 2025 21:30:00');
var currentDate = new Date('Apr 22, 2025 21:30: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.3.6.1 [' + formattedDate + ']';
var currentVersion = 'v1.3.7 [' + formattedDate + ']';
getInitialSettings();
removeUrlParameters();

View File

@@ -2,7 +2,6 @@ $(document).ready(function() {
// Cache jQuery objects for reuse
var modal = $("#myModal");
var modalPanel = $(".modal-panel");
var chatPanel = $(".modal-panel-chat");
var openBtn = $("#settings");
var closeBtn = $(".closeModal, .closeModalButton");
@@ -20,7 +19,6 @@ $(document).ready(function() {
modal.css("opacity", 0);
setTimeout(function() {
modal.css("display", "none");
modalPanel.add(chatPanel).css("display", "none");
$("body").removeClass("modal-open"); // Enable body scrolling
}, 300);
}
@@ -30,10 +28,6 @@ $(document).ready(function() {
openModal(modalPanel);
});
$(".chatbutton").on("click", function() {
openModal(chatPanel);
});
closeBtn.on("click", closeModal);
// Close the modal when clicking outside of it

View File

@@ -166,11 +166,6 @@ $(document).ready(() => {
$('.version-string').text(currentVersion);
setBg();
// Update icons when the checkbox state changes
$('input[type="checkbox"]').change(function() {
updateIconState(this);
});
});
function getQueryParameter(name) {
@@ -178,29 +173,6 @@ function getQueryParameter(name) {
return urlParams.get(name);
}
function updateIconState(el) {
// If an element is passed, update only that one
if (el) {
var $checkbox = $(el);
var icon = $checkbox.siblings('label').find('i');
if ($checkbox.is(':checked')) {
icon.removeClass('fa-toggle-off').addClass('fa-toggle-on');
} else {
icon.removeClass('fa-toggle-on').addClass('fa-toggle-off');
}
} else {
// Otherwise, update all checkboxes
$('input[type="checkbox"]').each(function() {
var icon = $(this).siblings('label').find('i');
if ($(this).is(':checked')) {
icon.removeClass('fa-toggle-off').addClass('fa-toggle-on');
} else {
icon.removeClass('fa-toggle-on').addClass('fa-toggle-off');
}
});
}
}
function setTheme(themeName) {
const themeColors = themes[themeName];
if (themeColors) {

View File

@@ -20,12 +20,11 @@ $(document).ready(function() {
function mapCreate() {
if (!(typeof map == "object")) {
map = L.map('map', {
center: [40,0],
center: [40, 0],
zoom: 3
});
}
else {
map.setZoom(3).panTo([40,0]);
} else {
map.setZoom(3).panTo([40, 0]);
}
L.tileLayer(tilesURL, {
@@ -33,21 +32,36 @@ function mapCreate() {
maxZoom: 19
}).addTo(map);
// Check for initial lat/lon values
const latVal = parseFloat($('#identification-lat').val());
const lonVal = parseFloat($('#identification-lon').val());
if (!isNaN(latVal) && !isNaN(lonVal)) {
const initialLatLng = L.latLng(latVal, lonVal);
pin = L.marker(initialLatLng, { riseOnHover: true, draggable: true }).addTo(map);
map.setView(initialLatLng, 8); // Optional: Zoom in closer to the pin
pin.on('dragend', function(ev) {
$('#identification-lat').val(ev.target.getLatLng().lat.toFixed(6));
$('#identification-lon').val(ev.target.getLatLng().lng.toFixed(6));
});
}
map.on('click', function(ev) {
$('#identification-lat').val((ev.latlng.lat).toFixed(6));
$('#identification-lon').val((ev.latlng.lng).toFixed(6));
$('#identification-lat').val(ev.latlng.lat.toFixed(6));
$('#identification-lon').val(ev.latlng.lng.toFixed(6));
if (typeof pin == "object") {
pin.setLatLng(ev.latlng);
} else {
pin = L.marker(ev.latlng,{ riseOnHover:true,draggable:true });
pin.addTo(map);
pin.on('dragend',function(ev) {
$('#identification-lat').val((ev.latlng.lat).toFixed(6));
$('#identification.lon').val((ev.latlng.lng).toFixed(6));
pin = L.marker(ev.latlng, { riseOnHover: true, draggable: true }).addTo(map);
pin.on('dragend', function(ev) {
$('#identification-lat').val(ev.target.getLatLng().lat.toFixed(6));
$('#identification-lon').val(ev.target.getLatLng().lng.toFixed(6));
});
}
});
mapReload();
}

View File

@@ -85,6 +85,7 @@
</div>
</div>
<div class="flex-container">
<div class="panel-100-real p-bottom-20" style="overflow-x: auto;">
<h3>Current users</h3>
<table class="table-big">
@@ -118,7 +119,9 @@
</tbody>
</table>
</div>
</div>
<div class="flex-container">
<div class="panel-100-real p-bottom-20">
<h3>Quick settings</h3>
<div class="flex-container flex-center" style="margin: 30px;">
@@ -129,6 +132,7 @@
<%- include('_components', {component: 'text', cssClass: 'w-150 br-15', placeholder: '', label: 'Tune password', id: 'password-tunePass', password: true}) %>
<%- include('_components', {component: 'text', cssClass: 'w-150 br-15', placeholder: '', label: 'Admin password', id: 'password-adminPass', password: true}) %><br>
</div>
</div>
<div class="flex-container">
<div class="panel-100-real p-bottom-20 bottom-20">
@@ -223,9 +227,10 @@
<p>Legacy option for Linux / macOS that could resolve audio issues, but will consume additional CPU and RAM usage.</p>
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Additional FFmpeg', id: 'audio-ffmpeg'}) %>
</div>
<div class="panel-50 p-bottom-20">
<h3>Samplerate Offset</h3>
<p>Using a negative value could eliminate audio buffering issues during long periods of listening. However, a value thats too low might increase the buffer over time.</p>
<div class="panel-50 p-botom-20">
<h3>Sample rate Offset</h3>
<p>Using a negative value could eliminate audio buffering issues during long periods of listening. <br>
However, a value thats too low might increase the buffer over time.</p>
<p><input class="panel-33 input-text w-100 auto" type="number" style="min-height: 40px; color: var(--color-text); padding: 10px; padding-left: 10px; box-sizing: border-box; border: 2px solid transparent; font-family: 'Titillium Web', sans-serif;" id="audio-samplerateOffset" min="-10" max="10" step="1" value="0" aria-label="Samplerate offset"></p>
</div>
</div>
@@ -234,13 +239,13 @@
<div class="panel-full m-0 tab-content no-bg" id="webserver" role="tabpanel">
<h2>Webserver settings</h2>
<div class="flex-container contains-dropdown">
<div class="panel-33 p-bottom-20" style="padding-left: 20px; padding-right: 20px;">
<div class="panel-50 p-bottom-20" style="padding-left: 20px; padding-right: 20px;">
<h3>Connection</h3>
<p class="text-gray">Leave the IP at 0.0.0.0 unless you explicitly know you have to change it.<br>Don't enter your public IP here.</p>
<%- include('_components', {component: 'text', cssClass: 'w-150 br-15', placeholder: '0.0.0.0', label: 'Webserver IP', id: 'webserver-webserverIp'}) %>
<%- include('_components', {component: 'text', cssClass: 'w-100 br-15', placeholder: '8080', label: 'Webserver port', id: 'webserver-webserverPort'}) %><br>
</div>
<div class="panel-33 p-bottom-20">
<div class="panel-50 p-bottom-20">
<h3>Design</h3>
<h4>Background image</h4>
<%- include('_components', {component: 'text', cssClass: 'br-15', placeholder: 'Direct image link', label: 'Image link', id: 'webserver-bgImage'}) %><br>
@@ -260,23 +265,35 @@
]
}) %><br>
</div>
<div class="panel-33 p-bottom-20">
</div>
<div class="flex-container">
<div class="panel-100-real p-bottom-20">
<h3>Antennas</h3>
<%- include('_components', {component: 'checkbox', cssClass: 'bottom-20', label: 'Antenna switch', id: 'antennas-enabled'}) %><br>
<div class="flex-container flex-center p-20">
<div class="flex-container flex-phone flex-column bottom-20" style="margin-left: 15px; margin-right: 15px;">
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Antenna 1', id: 'antennas-ant1-enabled'}) %>
<%- include('_components', {component: 'text', cssClass: 'w-100 br-15', placeholder: 'Ant A', label: 'Antenna 1 name', id: 'antennas-ant1-name'}) %><br>
</div>
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Antenna 2', id: 'antennas-ant2-enabled'}) %>
<div class="flex-container flex-phone flex-column bottom-20" style="margin-left: 15px; margin-right: 15px;">
<%- include('_components', {component: 'checkbox', cssClass: 'top-25', label: 'Antenna 2', id: 'antennas-ant2-enabled'}) %>
<%- include('_components', {component: 'text', cssClass: 'w-100 br-15', placeholder: 'Ant B', label: 'Antenna 2 name', id: 'antennas-ant2-name'}) %><br>
</div>
<div class="flex-container flex-phone flex-column bottom-20" style="margin-left: 15px; margin-right: 15px;">
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Antenna 3', id: 'antennas-ant3-enabled'}) %>
<%- include('_components', {component: 'text', cssClass: 'w-100 br-15', placeholder: 'Ant C', label: 'Antenna 3 name', id: 'antennas-ant3-name'}) %><br>
</div>
<div class="flex-container flex-phone flex-column bottom-20" style="margin-left: 15px; margin-right: 15px;">
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Antenna 4', id: 'antennas-ant4-enabled'}) %>
<%- include('_components', {component: 'text', cssClass: 'w-100 br-15', placeholder: 'Ant D', label: 'Antenna 4 name', id: 'antennas-ant4-name'}) %><br>
</div>
</div>
</div>
</div>
<div class="flex-container">
<div class="panel-50" style="padding-left: 20px; padding-right: 20px;">
@@ -303,12 +320,12 @@
</div>
<div class="flex-container p-bottom-20">
<div class="panel-33 p-bottom-20" style="padding-left: 20px; padding-right: 20px;">
<div class="panel-50 p-bottom-20" style="padding-left: 20px; padding-right: 20px;">
<h3>RDS Mode</h3>
<p>You can switch between American (RBDS) / Global (RDS) mode here.</p>
<%- include('_components', {component: 'checkbox', cssClass: 'bottom-20', iconClass: '', label: 'American RDS mode (RBDS)', id: 'webserver-rdsMode'}) %><br>
</div>
<div class="panel-33 p-bottom-20" style="padding-left: 20px; padding-right: 20px;">
<div class="panel-50 p-bottom-20" style="padding-left: 20px; padding-right: 20px;">
<h3>Transmitter Search Algorithm</h3>
<p>Different modes may help with more accurate transmitter identification depending on your region.</p>
<%- include('_components', { component: 'dropdown', id: 'server-tx-id-algo', inputId: 'webserver-txIdAlgorithm', label: 'Transmitter ID Algorithm', cssClass: '', placeholder: 'Algorithm 1',
@@ -553,9 +570,9 @@
When you become a supporter, you can message the Founders on Discord for your login details.</p>
<%- include('_components', {component: 'checkbox', cssClass: 'm-right-10', label: 'Enable tunnel', id: 'tunnel-enabled'}) %><br>
<%- include('_components', {component: 'text', cssClass: 'w-150 br-15', placeholder: '', label: 'Subdomain name', id: 'tunnel-subdomain'}) %>.fmtuner.org<br>
<%- include('_components', {component: 'text', cssClass: 'w-150 br-15', placeholder: '', label: 'Username', id: 'tunnel-username'}) %>
<%- include('_components', {component: 'text', cssClass: 'w-250 br-15', password: true, placeholder: '', label: 'Token', id: 'tunnel-token'}) %>
<%- include('_components', {component: 'text', cssClass: 'w-150 br-15', placeholder: '', label: 'Subdomain name', id: 'tunnel-subdomain'}) %>.fmtuner.org
<p>Enabling low latency mode may provide better experience, however it will also use more bandwidth.</p>
<%- include('_components', {component: 'checkbox', cssClass: 'm-right-10', label: 'Low latency mode', id: 'tunnel-lowLatencyMode'}) %><br>