mirror of
https://github.com/KubaPro010/fm-dx-webserver.git
synced 2026-07-30 16:59:15 +02:00
new chat window, bugfixes, component update
This commit is contained in:
+5
-5
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "fm-dx-webserver",
|
"name": "fm-dx-webserver",
|
||||||
"version": "1.3.6.1",
|
"version": "1.3.7",
|
||||||
"description": "FM DX Webserver",
|
"description": "FM DX Webserver",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -12,10 +12,10 @@
|
|||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mapbox/node-pre-gyp": "1.0.11",
|
"@mapbox/node-pre-gyp": "2.0.0",
|
||||||
"body-parser": "1.20.3",
|
"body-parser": "2.2.0",
|
||||||
"ejs": "3.1.10",
|
"ejs": "3.1.10",
|
||||||
"express": "4.21.2",
|
"express": "5.1.0",
|
||||||
"express-session": "1.18.1",
|
"express-session": "1.18.1",
|
||||||
"ffmpeg-static": "5.2.0",
|
"ffmpeg-static": "5.2.0",
|
||||||
"http": "0.0.1-security",
|
"http": "0.0.1-security",
|
||||||
@@ -23,6 +23,6 @@
|
|||||||
"koffi": "2.7.2",
|
"koffi": "2.7.2",
|
||||||
"net": "1.0.2",
|
"net": "1.0.2",
|
||||||
"serialport": "12.0.0",
|
"serialport": "12.0.0",
|
||||||
"ws": "8.18.0"
|
"ws": "8.18.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+27
-4
@@ -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 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;
|
const { password } = req.body;
|
||||||
|
|
||||||
// Check if the entered password matches the admin password
|
loginAttempts[ip].lastAttempt = now;
|
||||||
|
|
||||||
if (password === serverConfig.password.adminPass) {
|
if (password === serverConfig.password.adminPass) {
|
||||||
req.session.isAdminAuthenticated = true;
|
req.session.isAdminAuthenticated = true;
|
||||||
req.session.isTuneAuthenticated = 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();
|
next();
|
||||||
} else if (password === serverConfig.password.tunePass) {
|
} else if (password === serverConfig.password.tunePass) {
|
||||||
req.session.isAdminAuthenticated = false;
|
req.session.isAdminAuthenticated = false;
|
||||||
req.session.isTuneAuthenticated = true;
|
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();
|
next();
|
||||||
} else {
|
} else {
|
||||||
|
loginAttempts[ip].count += 1;
|
||||||
res.status(403).json({ message: 'Login failed. Wrong password?' });
|
res.status(403).json({ message: 'Login failed. Wrong password?' });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+2
-1
@@ -132,6 +132,7 @@ function fetchBannedAS(callback) {
|
|||||||
function processConnection(clientIp, locationInfo, currentUsers, ws, callback) {
|
function processConnection(clientIp, locationInfo, currentUsers, ws, callback) {
|
||||||
const options = { year: "numeric", month: "numeric", day: "numeric", hour: "2-digit", minute: "2-digit" };
|
const options = { year: "numeric", month: "numeric", day: "numeric", hour: "2-digit", minute: "2-digit" };
|
||||||
const connectionTime = new Date().toLocaleString([], options);
|
const connectionTime = new Date().toLocaleString([], options);
|
||||||
|
const normalizedClientIp = clientIp?.replace(/^::ffff:/, '');
|
||||||
|
|
||||||
fetchBannedAS((error, bannedAS) => {
|
fetchBannedAS((error, bannedAS) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -155,7 +156,7 @@ function processConnection(clientIp, locationInfo, currentUsers, ws, callback) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
consoleCmd.logInfo(
|
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");
|
callback("User allowed");
|
||||||
|
|||||||
+2
-1
@@ -388,6 +388,7 @@ wss.on('connection', (ws, request) => {
|
|||||||
const output = serverConfig.xdrd.wirelessConnection ? client : serialport;
|
const output = serverConfig.xdrd.wirelessConnection ? client : serialport;
|
||||||
let clientIp = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
|
let clientIp = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
|
||||||
const userCommandHistory = {};
|
const userCommandHistory = {};
|
||||||
|
const normalizedClientIp = clientIp?.replace(/^::ffff:/, '');
|
||||||
|
|
||||||
if (serverConfig.webserver.banlist?.includes(clientIp)) {
|
if (serverConfig.webserver.banlist?.includes(clientIp)) {
|
||||||
ws.close(1008, 'Banned IP');
|
ws.close(1008, 'Banned IP');
|
||||||
@@ -509,7 +510,7 @@ wss.on('connection', (ws, request) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (code !== 1008) {
|
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}]`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+27
-12
@@ -87,14 +87,11 @@ async function fetchTx(freq, piCode, rdsPs) {
|
|||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
freq = parseFloat(freq);
|
freq = parseFloat(freq);
|
||||||
|
|
||||||
if (isNaN(freq)) {
|
if (isNaN(freq)) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (now - lastFetchTime < fetchInterval
|
if (now - lastFetchTime < fetchInterval
|
||||||
|| serverConfig.identification.lat.length < 2
|
|| serverConfig.identification.lat.length < 2
|
||||||
|| freq < 87
|
|| freq < 87
|
||||||
|| (currentPiCode == piCode && currentRdsPs == rdsPs))
|
|| (currentPiCode == piCode && currentRdsPs == rdsPs)) {
|
||||||
{
|
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,15 +104,33 @@ async function fetchTx(freq, piCode, rdsPs) {
|
|||||||
const url = "https://maps.fmdx.org/api/?freq=" + freq;
|
const url = "https://maps.fmdx.org/api/?freq=" + freq;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url, { redirect: 'manual' });
|
// Try POST first
|
||||||
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
|
const postResponse = await fetch(url, {
|
||||||
const data = await response.json();
|
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;
|
cachedData[freq] = data;
|
||||||
if(serverConfig.webserver.rdsMode == true) await loadUsStatesGeoJson();
|
if (serverConfig.webserver.rdsMode === true) await loadUsStatesGeoJson();
|
||||||
return processData(data, piCode, rdsPs);
|
return processData(data, piCode, rdsPs);
|
||||||
} catch (error) {
|
} catch (postError) {
|
||||||
console.error("Error fetching data:", error);
|
console.warn("POST failed, trying GET:", postError);
|
||||||
return null; // Return null to indicate failure
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
-4
@@ -29,10 +29,13 @@ switch (component) {
|
|||||||
* @param cssClass Custom CSS class if needed
|
* @param cssClass Custom CSS class if needed
|
||||||
*/
|
*/
|
||||||
case 'checkbox':
|
case 'checkbox':
|
||||||
%>
|
%>
|
||||||
<div class="form-group checkbox <%= cssClass %>">
|
<div class="form-group">
|
||||||
<input type="checkbox" tabindex="0" id="<%= id %>" aria-label="<%= label %>">
|
<div class="switch flex-container flex-phone flex-phone-column flex-phone-center">
|
||||||
<label for="<%= id %>"><i class="fa-solid fa-toggle-off m-right-10 <%= typeof iconClass !== 'undefined' ? iconClass : '' %>"></i> <%= label %></label>
|
<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>
|
</div>
|
||||||
<%
|
<%
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -308,6 +308,47 @@ input[type="range"]::-moz-range-thumb {
|
|||||||
|
|
||||||
/* End Toggle Switch */
|
/* 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 {
|
select {
|
||||||
height: 42px;
|
height: 42px;
|
||||||
width: 150px;
|
width: 150px;
|
||||||
|
|||||||
@@ -119,6 +119,10 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flex-column {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
.hover-brighten {
|
.hover-brighten {
|
||||||
transition: 0.3s ease background-color;
|
transition: 0.3s ease background-color;
|
||||||
}
|
}
|
||||||
@@ -302,6 +306,10 @@ table .input-text {
|
|||||||
.flex-phone-column {
|
.flex-phone-column {
|
||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
}
|
}
|
||||||
|
.flex-phone-center {
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
.hide-phone {
|
.hide-phone {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
+34
-10
@@ -86,10 +86,6 @@ body.modal-open {
|
|||||||
background-color: var(--color-main);
|
background-color: var(--color-main);
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-panel .flex-container {
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-panel h1 {
|
.modal-panel h1 {
|
||||||
font-size: 42px;
|
font-size: 42px;
|
||||||
}
|
}
|
||||||
@@ -124,7 +120,6 @@ body.modal-open {
|
|||||||
|
|
||||||
.modal-panel label {
|
.modal-panel label {
|
||||||
width: 200px;
|
width: 200px;
|
||||||
margin: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-panel-chat {
|
.modal-panel-chat {
|
||||||
@@ -147,6 +142,37 @@ body.modal-open {
|
|||||||
border-radius: 15px 15px 0px 0px;
|
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) {
|
@media only screen and (max-width: 768px) {
|
||||||
.modal-content {
|
.modal-content {
|
||||||
min-width: 90% !important;
|
min-width: 90% !important;
|
||||||
@@ -165,11 +191,9 @@ body.modal-open {
|
|||||||
.modal-panel {
|
.modal-panel {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
.modal-panel-chat {
|
|
||||||
height: 510px;
|
.popup-window {
|
||||||
}
|
width: 90%;
|
||||||
#chat-chatbox {
|
|
||||||
height: 333px !important;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-1
@@ -69,6 +69,9 @@
|
|||||||
.panel-75 {
|
.panel-75 {
|
||||||
width: 90%;
|
width: 90%;
|
||||||
}
|
}
|
||||||
|
.no-bg-phone {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
.panel-33 h2 {
|
.panel-33 h2 {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
@@ -81,7 +84,6 @@
|
|||||||
margin: auto;
|
margin: auto;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
background-color: transparent;
|
|
||||||
}
|
}
|
||||||
*[class^="panel-"]:not(.no-bg):not(.no-filter):not(#ps-container),
|
*[class^="panel-"]:not(.no-bg):not(.no-filter):not(#ps-container),
|
||||||
.panel-100.w-100::before {
|
.panel-100.w-100::before {
|
||||||
|
|||||||
+39
-33
@@ -5,7 +5,10 @@
|
|||||||
<link href="css/entry.css" type="text/css" rel="stylesheet">
|
<link href="css/entry.css" type="text/css" rel="stylesheet">
|
||||||
<link href="css/flags.min.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/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.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/chart.umd.min.js"></script>
|
||||||
<script src="js/libs/luxon.min.js"></script>
|
<script src="js/libs/luxon.min.js"></script>
|
||||||
<script src="js/libs/chartjs-adapter-luxon.umd.min.js"></script>
|
<script src="js/libs/chartjs-adapter-luxon.umd.min.js"></script>
|
||||||
@@ -178,7 +181,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-container">
|
<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>
|
<h2 class="signal-heading">PI CODE</h2>
|
||||||
<div class="text-small text-gray highest-signal-container">
|
<div class="text-small text-gray highest-signal-container">
|
||||||
<span id="data-regular-pi"> </span>
|
<span id="data-regular-pi"> </span>
|
||||||
@@ -186,12 +189,12 @@
|
|||||||
<span id="data-pi" class="text-big text-uppercase"></span>
|
<span id="data-pi" class="text-big text-uppercase"></span>
|
||||||
</div>
|
</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>
|
<h2>FREQUENCY</h2>
|
||||||
<span id="data-frequency" class="text-big"></span>
|
<span id="data-frequency" class="text-big"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel-33">
|
<div class="panel-33 no-bg-phone">
|
||||||
<h2 class="signal-heading">SIGNAL</h2>
|
<h2 class="signal-heading">SIGNAL</h2>
|
||||||
<div class="text-small text-gray highest-signal-container">
|
<div class="text-small text-gray highest-signal-container">
|
||||||
<i class="fa-solid fa-arrow-up"></i>
|
<i class="fa-solid fa-arrow-up"></i>
|
||||||
@@ -334,7 +337,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-container flex-phone flex-phone-column">
|
<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>
|
<h2 style="margin-top: 4px;">RADIOTEXT</h2>
|
||||||
<div id="data-rt0">
|
<div id="data-rt0">
|
||||||
<span></span>
|
<span></span>
|
||||||
@@ -345,7 +348,7 @@
|
|||||||
<hr class="hide-desktop">
|
<hr class="hide-desktop">
|
||||||
</div>
|
</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>">
|
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">
|
<div id="data-station-container">
|
||||||
<h2 style="margin-top: 0;" class="mb-0">
|
<h2 style="margin-top: 0;" class="mb-0">
|
||||||
@@ -364,7 +367,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel-10 no-bg center-phone" style="margin-left: 0; margin-top: 0; margin-right: 0;display:flex;">
|
<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>
|
<h2 class="bottom-10">AF</h2>
|
||||||
<div id="af-list" style="text-align: center;">
|
<div id="af-list" style="text-align: center;">
|
||||||
<ul> </ul>
|
<ul> </ul>
|
||||||
@@ -373,7 +376,7 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<h2 class="show-phone">
|
||||||
<div class="data-pty text-color-default"></div>
|
<div class="data-pty text-color-default"></div>
|
||||||
</h2>
|
</h2>
|
||||||
@@ -394,6 +397,30 @@
|
|||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
</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 id="myModal" class="modal">
|
||||||
<div class="modal-panel">
|
<div class="modal-panel">
|
||||||
@@ -430,9 +457,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
<%- include('_components', {component: 'checkbox', cssClass: 'top-25', label: 'Manual decimals', id: 'extended-frequency-range'}) %>
|
<div class="auto" style="max-width: 215px;">
|
||||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'RDS PS Underscores', id: 'ps-underscores'}) %>
|
<%- include('_components', {component: 'checkbox', cssClass: 'top-25', label: 'Manual decimals', id: 'extended-frequency-range'}) %>
|
||||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Imperial units', id: 'imperial-units'}) %>
|
<%- 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) { %>
|
<% if (isAdminAuthenticated) { %>
|
||||||
<p class="color-3">You are logged in as an adminstrator.</p>
|
<p class="color-3">You are logged in as an adminstrator.</p>
|
||||||
@@ -500,29 +529,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
<div id="toast-container"></div>
|
<div id="toast-container"></div>
|
||||||
|
|||||||
+3
-6
@@ -34,20 +34,17 @@ function OnConnectivityCallback(isConnected) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isIOS() {
|
|
||||||
return /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
|
|
||||||
}
|
|
||||||
|
|
||||||
function OnPlayButtonClick(_ev) {
|
function OnPlayButtonClick(_ev) {
|
||||||
const $playbutton = $('.playbutton');
|
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) {
|
if (Stream) {
|
||||||
console.log("Stopping stream...");
|
console.log("Stopping stream...");
|
||||||
shouldReconnect = false;
|
shouldReconnect = false;
|
||||||
destroyStream();
|
destroyStream();
|
||||||
$playbutton.find('.fa-solid').toggleClass('fa-stop fa-play');
|
$playbutton.find('.fa-solid').toggleClass('fa-stop fa-play');
|
||||||
if (isiOS && 'audioSession' in navigator) {
|
if (isAppleiOS && 'audioSession' in navigator) {
|
||||||
navigator.audioSession.type = "none";
|
navigator.audioSession.type = "none";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -56,7 +53,7 @@ function OnPlayButtonClick(_ev) {
|
|||||||
createStream();
|
createStream();
|
||||||
Stream.Start();
|
Stream.Start();
|
||||||
$playbutton.find('.fa-solid').toggleClass('fa-play fa-stop');
|
$playbutton.find('.fa-solid').toggleClass('fa-play fa-stop');
|
||||||
if (isiOS && 'audioSession' in navigator) {
|
if (isAppleiOS && 'audioSession' in navigator) {
|
||||||
navigator.audioSession.type = "playback";
|
navigator.audioSession.type = "playback";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+22
-2
@@ -10,9 +10,29 @@ $(document).ready(function() {
|
|||||||
const chatNicknameInput = $('#chat-nickname');
|
const chatNicknameInput = $('#chat-nickname');
|
||||||
const chatNicknameSave = $('#chat-nickname-save');
|
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 to generate a random string
|
||||||
function generateRandomString(length) {
|
function generateRandomString(length) {
|
||||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
const characters = 'ABCDEFGHJKMNOPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz0123456789';
|
||||||
let result = '';
|
let result = '';
|
||||||
for (let i = 0; i < length; i++) {
|
for (let i = 0; i < length; i++) {
|
||||||
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
result += characters.charAt(Math.floor(Math.random() * characters.length));
|
||||||
@@ -21,7 +41,7 @@ $(document).ready(function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Load nickname from localStorage on page load
|
// 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);
|
chatNicknameInput.val(savedNickname);
|
||||||
chatIdentityNickname.text(savedNickname);
|
chatIdentityNickname.text(savedNickname);
|
||||||
|
|
||||||
|
|||||||
@@ -96,8 +96,6 @@ function populateFields(data, prefix = "") {
|
|||||||
$element.val(value);
|
$element.val(value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
updateIconState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateConfigData(data, prefix = "") {
|
function updateConfigData(data, prefix = "") {
|
||||||
|
|||||||
+2
-2
@@ -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 day = currentDate.getDate();
|
||||||
var month = currentDate.getMonth() + 1; // Months are zero-indexed, so add 1
|
var month = currentDate.getMonth() + 1; // Months are zero-indexed, so add 1
|
||||||
var year = currentDate.getFullYear();
|
var year = currentDate.getFullYear();
|
||||||
var formattedDate = day + '/' + month + '/' + year;
|
var formattedDate = day + '/' + month + '/' + year;
|
||||||
var currentVersion = 'v1.3.6.1 [' + formattedDate + ']';
|
var currentVersion = 'v1.3.7 [' + formattedDate + ']';
|
||||||
|
|
||||||
getInitialSettings();
|
getInitialSettings();
|
||||||
removeUrlParameters();
|
removeUrlParameters();
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ $(document).ready(function() {
|
|||||||
// Cache jQuery objects for reuse
|
// Cache jQuery objects for reuse
|
||||||
var modal = $("#myModal");
|
var modal = $("#myModal");
|
||||||
var modalPanel = $(".modal-panel");
|
var modalPanel = $(".modal-panel");
|
||||||
var chatPanel = $(".modal-panel-chat");
|
|
||||||
var openBtn = $("#settings");
|
var openBtn = $("#settings");
|
||||||
var closeBtn = $(".closeModal, .closeModalButton");
|
var closeBtn = $(".closeModal, .closeModalButton");
|
||||||
|
|
||||||
@@ -20,7 +19,6 @@ $(document).ready(function() {
|
|||||||
modal.css("opacity", 0);
|
modal.css("opacity", 0);
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
modal.css("display", "none");
|
modal.css("display", "none");
|
||||||
modalPanel.add(chatPanel).css("display", "none");
|
|
||||||
$("body").removeClass("modal-open"); // Enable body scrolling
|
$("body").removeClass("modal-open"); // Enable body scrolling
|
||||||
}, 300);
|
}, 300);
|
||||||
}
|
}
|
||||||
@@ -30,10 +28,6 @@ $(document).ready(function() {
|
|||||||
openModal(modalPanel);
|
openModal(modalPanel);
|
||||||
});
|
});
|
||||||
|
|
||||||
$(".chatbutton").on("click", function() {
|
|
||||||
openModal(chatPanel);
|
|
||||||
});
|
|
||||||
|
|
||||||
closeBtn.on("click", closeModal);
|
closeBtn.on("click", closeModal);
|
||||||
|
|
||||||
// Close the modal when clicking outside of it
|
// Close the modal when clicking outside of it
|
||||||
|
|||||||
@@ -166,11 +166,6 @@ $(document).ready(() => {
|
|||||||
$('.version-string').text(currentVersion);
|
$('.version-string').text(currentVersion);
|
||||||
|
|
||||||
setBg();
|
setBg();
|
||||||
|
|
||||||
// Update icons when the checkbox state changes
|
|
||||||
$('input[type="checkbox"]').change(function() {
|
|
||||||
updateIconState(this);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function getQueryParameter(name) {
|
function getQueryParameter(name) {
|
||||||
@@ -178,29 +173,6 @@ function getQueryParameter(name) {
|
|||||||
return urlParams.get(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) {
|
function setTheme(themeName) {
|
||||||
const themeColors = themes[themeName];
|
const themeColors = themes[themeName];
|
||||||
if (themeColors) {
|
if (themeColors) {
|
||||||
|
|||||||
+27
-13
@@ -20,34 +20,48 @@ $(document).ready(function() {
|
|||||||
function mapCreate() {
|
function mapCreate() {
|
||||||
if (!(typeof map == "object")) {
|
if (!(typeof map == "object")) {
|
||||||
map = L.map('map', {
|
map = L.map('map', {
|
||||||
center: [40,0],
|
center: [40, 0],
|
||||||
zoom: 3
|
zoom: 3
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
map.setZoom(3).panTo([40, 0]);
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
map.setZoom(3).panTo([40,0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
L.tileLayer(tilesURL, {
|
L.tileLayer(tilesURL, {
|
||||||
attribution: mapAttrib,
|
attribution: mapAttrib,
|
||||||
maxZoom: 19
|
maxZoom: 19
|
||||||
}).addTo(map);
|
}).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) {
|
map.on('click', function(ev) {
|
||||||
$('#identification-lat').val((ev.latlng.lat).toFixed(6));
|
$('#identification-lat').val(ev.latlng.lat.toFixed(6));
|
||||||
$('#identification-lon').val((ev.latlng.lng).toFixed(6));
|
$('#identification-lon').val(ev.latlng.lng.toFixed(6));
|
||||||
|
|
||||||
if (typeof pin == "object") {
|
if (typeof pin == "object") {
|
||||||
pin.setLatLng(ev.latlng);
|
pin.setLatLng(ev.latlng);
|
||||||
} else {
|
} else {
|
||||||
pin = L.marker(ev.latlng,{ riseOnHover:true,draggable:true });
|
pin = L.marker(ev.latlng, { riseOnHover: true, draggable: true }).addTo(map);
|
||||||
pin.addTo(map);
|
pin.on('dragend', function(ev) {
|
||||||
pin.on('dragend',function(ev) {
|
$('#identification-lat').val(ev.target.getLatLng().lat.toFixed(6));
|
||||||
$('#identification-lat').val((ev.latlng.lat).toFixed(6));
|
$('#identification-lon').val(ev.target.getLatLng().lng.toFixed(6));
|
||||||
$('#identification.lon').val((ev.latlng.lng).toFixed(6));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mapReload();
|
mapReload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+37
-20
@@ -85,6 +85,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-container">
|
||||||
<div class="panel-100-real p-bottom-20" style="overflow-x: auto;">
|
<div class="panel-100-real p-bottom-20" style="overflow-x: auto;">
|
||||||
<h3>Current users</h3>
|
<h3>Current users</h3>
|
||||||
<table class="table-big">
|
<table class="table-big">
|
||||||
@@ -118,7 +119,9 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex-container">
|
||||||
<div class="panel-100-real p-bottom-20">
|
<div class="panel-100-real p-bottom-20">
|
||||||
<h3>Quick settings</h3>
|
<h3>Quick settings</h3>
|
||||||
<div class="flex-container flex-center" style="margin: 30px;">
|
<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: '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>
|
<%- include('_components', {component: 'text', cssClass: 'w-150 br-15', placeholder: '', label: 'Admin password', id: 'password-adminPass', password: true}) %><br>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex-container">
|
<div class="flex-container">
|
||||||
<div class="panel-100-real p-bottom-20 bottom-20">
|
<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>
|
<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'}) %>
|
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Additional FFmpeg', id: 'audio-ffmpeg'}) %>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-50 p-bottom-20">
|
<div class="panel-50 p-botom-20">
|
||||||
<h3>Samplerate Offset</h3>
|
<h3>Sample rate Offset</h3>
|
||||||
<p>Using a negative value could eliminate audio buffering issues during long periods of listening. However, a value that’s too low might increase the buffer over time.</p>
|
<p>Using a negative value could eliminate audio buffering issues during long periods of listening. <br>
|
||||||
|
However, a value that’s 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>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
@@ -234,13 +239,13 @@
|
|||||||
<div class="panel-full m-0 tab-content no-bg" id="webserver" role="tabpanel">
|
<div class="panel-full m-0 tab-content no-bg" id="webserver" role="tabpanel">
|
||||||
<h2>Webserver settings</h2>
|
<h2>Webserver settings</h2>
|
||||||
<div class="flex-container contains-dropdown">
|
<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>
|
<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>
|
<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-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>
|
<%- include('_components', {component: 'text', cssClass: 'w-100 br-15', placeholder: '8080', label: 'Webserver port', id: 'webserver-webserverPort'}) %><br>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-33 p-bottom-20">
|
<div class="panel-50 p-bottom-20">
|
||||||
<h3>Design</h3>
|
<h3>Design</h3>
|
||||||
<h4>Background image</h4>
|
<h4>Background image</h4>
|
||||||
<%- include('_components', {component: 'text', cssClass: 'br-15', placeholder: 'Direct image link', label: 'Image link', id: 'webserver-bgImage'}) %><br>
|
<%- include('_components', {component: 'text', cssClass: 'br-15', placeholder: 'Direct image link', label: 'Image link', id: 'webserver-bgImage'}) %><br>
|
||||||
@@ -260,21 +265,33 @@
|
|||||||
]
|
]
|
||||||
}) %><br>
|
}) %><br>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-33 p-bottom-20">
|
</div>
|
||||||
|
<div class="flex-container">
|
||||||
|
<div class="panel-100-real p-bottom-20">
|
||||||
<h3>Antennas</h3>
|
<h3>Antennas</h3>
|
||||||
<%- include('_components', {component: 'checkbox', cssClass: 'bottom-20', label: 'Antenna switch', id: 'antennas-enabled'}) %><br>
|
<%- include('_components', {component: 'checkbox', cssClass: 'bottom-20', label: 'Antenna switch', id: 'antennas-enabled'}) %><br>
|
||||||
|
|
||||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Antenna 1', id: 'antennas-ant1-enabled'}) %>
|
<div class="flex-container flex-center p-20">
|
||||||
<%- include('_components', {component: 'text', cssClass: 'w-100 br-15', placeholder: 'Ant A', label: 'Antenna 1 name', id: 'antennas-ant1-name'}) %><br>
|
<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: 'checkbox', cssClass: '', label: 'Antenna 2', id: 'antennas-ant2-enabled'}) %>
|
<%- include('_components', {component: 'text', cssClass: 'w-100 br-15', placeholder: 'Ant A', label: 'Antenna 1 name', id: 'antennas-ant1-name'}) %><br>
|
||||||
<%- include('_components', {component: 'text', cssClass: 'w-100 br-15', placeholder: 'Ant B', label: 'Antenna 2 name', id: 'antennas-ant2-name'}) %><br>
|
</div>
|
||||||
|
|
||||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Antenna 3', id: 'antennas-ant3-enabled'}) %>
|
<div class="flex-container flex-phone flex-column bottom-20" style="margin-left: 15px; margin-right: 15px;">
|
||||||
<%- include('_components', {component: 'text', cssClass: 'w-100 br-15', placeholder: 'Ant C', label: 'Antenna 3 name', id: 'antennas-ant3-name'}) %><br>
|
<%- 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>
|
||||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Antenna 4', id: 'antennas-ant4-enabled'}) %>
|
</div>
|
||||||
<%- include('_components', {component: 'text', cssClass: 'w-100 br-15', placeholder: 'Ant D', label: 'Antenna 4 name', id: 'antennas-ant4-name'}) %><br>
|
|
||||||
|
<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>
|
</div>
|
||||||
|
|
||||||
@@ -303,12 +320,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-container p-bottom-20">
|
<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>
|
<h3>RDS Mode</h3>
|
||||||
<p>You can switch between American (RBDS) / Global (RDS) mode here.</p>
|
<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>
|
<%- include('_components', {component: 'checkbox', cssClass: 'bottom-20', iconClass: '', label: 'American RDS mode (RBDS)', id: 'webserver-rdsMode'}) %><br>
|
||||||
</div>
|
</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>
|
<h3>Transmitter Search Algorithm</h3>
|
||||||
<p>Different modes may help with more accurate transmitter identification depending on your region.</p>
|
<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',
|
<%- 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>
|
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: '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-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-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>
|
<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>
|
<%- include('_components', {component: 'checkbox', cssClass: 'm-right-10', label: 'Low latency mode', id: 'tunnel-lowLatencyMode'}) %><br>
|
||||||
|
|||||||
Reference in New Issue
Block a user