You've already forked fm-dx-webserver
mirror of
https://github.com/KubaPro010/fm-dx-webserver.git
synced 2026-02-26 14:11:59 +01:00
optimization pack
This commit is contained in:
@@ -1,22 +1,17 @@
|
||||
const fs = require('fs');
|
||||
const fs = require('fs').promises;
|
||||
|
||||
const verboseMode = process.argv.includes('--debug');
|
||||
const verboseModeFfmpeg = process.argv.includes('--ffmpegdebug');
|
||||
|
||||
const LOG_FILE = 'serverlog.txt';
|
||||
const ANSI_ESCAPE_CODE_PATTERN = /\x1b\[[0-9;]*m/g;
|
||||
const MAX_LOG_LINES = 5000;
|
||||
const FLUSH_INTERVAL = 60000;
|
||||
const logs = [];
|
||||
const maxConsoleLogLines = 250;
|
||||
let logBuffer = [];
|
||||
|
||||
const getCurrentTime = () => {
|
||||
const currentTime = new Date();
|
||||
const hours = currentTime.getHours().toString().padStart(2, '0');
|
||||
const minutes = currentTime.getMinutes().toString().padStart(2, '0');
|
||||
return `\x1b[90m[${hours}:${minutes}]\x1b[0m`;
|
||||
};
|
||||
|
||||
const removeANSIEscapeCodes = (str) => {
|
||||
return str.replace(ANSI_ESCAPE_CODE_PATTERN, '');
|
||||
};
|
||||
|
||||
// Message prefixes with ANSI codes
|
||||
const MESSAGE_PREFIX = {
|
||||
CHAT: "\x1b[36m[CHAT]\x1b[0m",
|
||||
DEBUG: "\x1b[36m[DEBUG]\x1b[0m",
|
||||
@@ -26,97 +21,67 @@ const MESSAGE_PREFIX = {
|
||||
WARN: "\x1b[33m[WARN]\x1b[0m",
|
||||
};
|
||||
|
||||
// Initialize an array to store logs
|
||||
const logs = [];
|
||||
const maxLogLines = 250;
|
||||
const getCurrentTime = () => {
|
||||
const currentTime = new Date();
|
||||
const date = currentTime.toLocaleDateString().replace(/\ /g, '');
|
||||
const time = currentTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
|
||||
return `\x1b[90m[${date} ${time}]\x1b[0m`;
|
||||
};
|
||||
|
||||
const logDebug = (...messages) => {
|
||||
const logMessage = `${getCurrentTime()} ${MESSAGE_PREFIX.DEBUG} ${messages.join(' ')}`;
|
||||
if (verboseMode) {
|
||||
const removeANSIEscapeCodes = (str) => str.replace(ANSI_ESCAPE_CODE_PATTERN, ''); // Strip ANSI escape codes from a string
|
||||
|
||||
const logMessage = (type, messages, verbose = false) => {
|
||||
const logMessage = `${getCurrentTime()} ${MESSAGE_PREFIX[type]} ${messages.join(' ')}`;
|
||||
|
||||
if (type === 'DEBUG' && verboseMode || type === 'FFMPEG' && verboseModeFfmpeg || type !== 'DEBUG' && type !== 'FFMPEG') {
|
||||
logs.push(logMessage);
|
||||
if (logs.length > maxLogLines) {
|
||||
logs.shift();
|
||||
}
|
||||
console.log(logMessage);
|
||||
if (logs.length > maxConsoleLogLines) logs.shift();
|
||||
console.log(logMessage);
|
||||
}
|
||||
appendLogToFile(logMessage);
|
||||
appendLogToBuffer(logMessage);
|
||||
};
|
||||
|
||||
const logChat = (...messages) => {
|
||||
const logMessage = `${getCurrentTime()} ${MESSAGE_PREFIX.CHAT} ${messages[0].nickname} (${messages[0].ip}) sent a chat message: ${messages[0].message}`;
|
||||
appendLogToFile(logMessage);
|
||||
};
|
||||
const logDebug = (...messages) => logMessage('DEBUG', messages, verboseMode);
|
||||
const logChat = (message) => logMessage('CHAT', [`${message.nickname} (${message.ip}) sent a chat message: ${message.message}`]);
|
||||
const logError = (...messages) => logMessage('ERROR', messages);
|
||||
const logFfmpeg = (...messages) => logMessage('FFMPEG', messages, verboseModeFfmpeg);
|
||||
const logInfo = (...messages) => logMessage('INFO', messages);
|
||||
const logWarn = (...messages) => logMessage('WARN', messages);
|
||||
|
||||
const logError = (...messages) => {
|
||||
const logMessage = `${getCurrentTime()} ${MESSAGE_PREFIX.ERROR} ${messages.join(' ')}`;
|
||||
logs.push(logMessage);
|
||||
if (logs.length > maxLogLines) {
|
||||
logs.shift();
|
||||
}
|
||||
console.log(logMessage);
|
||||
appendLogToFile(logMessage);
|
||||
};
|
||||
|
||||
const logFfmpeg = (...messages) => {
|
||||
if (verboseModeFfmpeg) {
|
||||
const logMessage = `${getCurrentTime()} ${MESSAGE_PREFIX.FFMPEG} ${messages.join(' ')}`;
|
||||
logs.push(logMessage);
|
||||
if (logs.length > maxLogLines) {
|
||||
logs.shift();
|
||||
}
|
||||
console.log(logMessage);
|
||||
appendLogToFile(logMessage);
|
||||
}
|
||||
};
|
||||
|
||||
const logInfo = (...messages) => {
|
||||
const logMessage = `${getCurrentTime()} ${MESSAGE_PREFIX.INFO} ${messages.join(' ')}`;
|
||||
logs.push(logMessage);
|
||||
if (logs.length > maxLogLines) {
|
||||
logs.shift();
|
||||
}
|
||||
console.log(logMessage);
|
||||
appendLogToFile(logMessage);
|
||||
};
|
||||
|
||||
const logWarn = (...messages) => {
|
||||
const logMessage = `${getCurrentTime()} ${MESSAGE_PREFIX.WARN} ${messages.join(' ')}`;
|
||||
logs.push(logMessage);
|
||||
if (logs.length > maxLogLines) {
|
||||
logs.shift();
|
||||
}
|
||||
console.log(logMessage);
|
||||
appendLogToFile(logMessage);
|
||||
};
|
||||
|
||||
function appendLogToFile(logMessage) {
|
||||
const date = new Date();
|
||||
const cleanLogMessage = date.toLocaleDateString() + ' | ' + removeANSIEscapeCodes(logMessage);
|
||||
|
||||
fs.appendFile('serverlog.txt', cleanLogMessage + '\n', (err) => {
|
||||
if (err) {
|
||||
console.error('Error writing to server log:', err);
|
||||
} else {
|
||||
fs.readFile('serverlog.txt', 'utf8', (err, data) => {
|
||||
if (err) {
|
||||
console.error('Error reading server log:', err);
|
||||
} else {
|
||||
const lineCount = data.split('\n').length;
|
||||
if (lineCount > MAX_LOG_LINES) {
|
||||
const excessLines = lineCount - MAX_LOG_LINES;
|
||||
const truncatedContent = data.split('\n').slice(excessLines).join('\n');
|
||||
fs.writeFile('serverlog.txt', truncatedContent, (err) => {
|
||||
if (err) {
|
||||
console.error('Error truncating server log:', err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
function appendLogToBuffer(logMessage) {
|
||||
const cleanLogMessage = removeANSIEscapeCodes(logMessage);
|
||||
logBuffer.push(cleanLogMessage + '\n');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
logError, logDebug, logFfmpeg, logInfo, logWarn, logs, logChat
|
||||
async function flushLogBuffer() {
|
||||
if (logBuffer.length === 0) return;
|
||||
|
||||
const logContent = logBuffer.join('');
|
||||
logBuffer = [];
|
||||
|
||||
try {
|
||||
await fs.appendFile(LOG_FILE, logContent);
|
||||
|
||||
const data = await fs.readFile(LOG_FILE, 'utf8');
|
||||
const lines = data.split('\n');
|
||||
if (lines.length > MAX_LOG_LINES) {
|
||||
const truncatedContent = lines.slice(-MAX_LOG_LINES).join('\n');
|
||||
await fs.writeFile(LOG_FILE, truncatedContent);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error flushing log buffer:', err);
|
||||
}
|
||||
}
|
||||
|
||||
setInterval(flushLogBuffer, FLUSH_INTERVAL);
|
||||
|
||||
const gracefulExit = async () => {
|
||||
await flushLogBuffer();
|
||||
process.exit();
|
||||
};
|
||||
|
||||
process.on('exit', flushLogBuffer);
|
||||
process.on('SIGINT', gracefulExit);
|
||||
process.on('SIGTERM', gracefulExit);
|
||||
|
||||
module.exports = { logError, logDebug, logFfmpeg, logInfo, logWarn, logs, logChat };
|
||||
@@ -9,10 +9,10 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrapper" class="setup-wrapper">
|
||||
<div id="wrapper" class="auto">
|
||||
<div class="panel-100 no-bg">
|
||||
<img class="top-10" src="./images/openradio_logo_neutral.png" height="64px">
|
||||
<h2 class="text-monospace text-light">[Unauthorized]</h2>
|
||||
<h2 class="text-monospace text-light text-center">[Unauthorized]</h2>
|
||||
|
||||
<div class="panel-100 p-10">
|
||||
<br>
|
||||
|
||||
74
web/_components.ejs
Normal file
74
web/_components.ejs
Normal file
@@ -0,0 +1,74 @@
|
||||
<%
|
||||
switch (component) {
|
||||
/**
|
||||
* Text input & password input tag
|
||||
* @param label Label text
|
||||
* @param id Unique element ID
|
||||
* @param password If true, the element will be rendered as a password instead of plain text input
|
||||
* @param placeholder Placeholder text
|
||||
* @param cssClass Custom CSS class if needed
|
||||
*/
|
||||
case 'text':
|
||||
%>
|
||||
<div class="form-group">
|
||||
<label for="<%= id %>"><%= label %></label>
|
||||
<input class="input-text <%= cssClass %>"
|
||||
type="<%= (typeof password !== 'undefined' && password == true) ? 'password' : 'text' %>"
|
||||
placeholder="<%= typeof placeholder !== 'undefined' ? placeholder : '' %>"
|
||||
name="<%= id %>"
|
||||
id="<%= id %>">
|
||||
</div>
|
||||
<%
|
||||
break;
|
||||
|
||||
/**
|
||||
* Checkbox (toggle button) tag
|
||||
* @param label Label text
|
||||
* @param id Unique element ID
|
||||
* @param iconClass Additional CSS Class for the icon inside the button
|
||||
* @param cssClass Custom CSS class if needed
|
||||
*/
|
||||
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>
|
||||
<%
|
||||
break;
|
||||
|
||||
/**
|
||||
* Dropdown menus
|
||||
* @param inputId Unique ID for the hidden text input of the dropdown
|
||||
* @param id Unique element ID
|
||||
* @param iconClass Additional CSS Class for the icon next to the title (if you want to have one)
|
||||
* @param placeholder Placeholder text
|
||||
* @param cssClass Custom CSS class for the dropdown menu itself if needed
|
||||
*/
|
||||
case 'dropdown':
|
||||
%>
|
||||
<div class="form-group">
|
||||
<label for="<%= inputId %>"><i class="<%= typeof iconClass !== 'undefined' ? iconClass : '' %>"></i> <%= label %></label>
|
||||
<div class="dropdown <%= cssClass %>" id="<%= id %>" style="margin-right: 0;">
|
||||
<input type="text" placeholder="<%= placeholder %>" id="<%= inputId %>" readonly tabindex="0">
|
||||
<ul class="options" tabindex="0">
|
||||
<%
|
||||
options.forEach(function(option) {
|
||||
%>
|
||||
<li class="option" tabindex="0" data-value="<%= option.value %>"><%= option.label %></li>
|
||||
<%
|
||||
});
|
||||
%>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<%
|
||||
break;
|
||||
|
||||
default:
|
||||
%>
|
||||
<p>Unknown component: <%= component %></p>
|
||||
<%
|
||||
break;
|
||||
}
|
||||
%>
|
||||
@@ -215,10 +215,17 @@ label {
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
|
||||
.settings-heading {
|
||||
font-size: 32px;
|
||||
padding-top: 20px;
|
||||
h2.settings-heading {
|
||||
font-size: 42px;
|
||||
padding: 10px 0;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
h3.settings-heading {
|
||||
font-size: 24px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 300;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
#tuner-wireless {
|
||||
|
||||
@@ -222,6 +222,10 @@
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.clearfix {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 960px) {
|
||||
.text-medium-big {
|
||||
font-size: 32px;
|
||||
|
||||
@@ -137,6 +137,10 @@ li.active {
|
||||
height: 300px;
|
||||
overflow-y:auto;
|
||||
}
|
||||
.w-250 {
|
||||
width: 250px !important
|
||||
}
|
||||
|
||||
.w-200 {
|
||||
width: 200px !important
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>FM-DX Webserver [<%= tunerName %>]</title>
|
||||
<title><%= tunerName %> - FM-DX Webserver</title>
|
||||
<link href="css/entry.css" type="text/css" rel="stylesheet">
|
||||
<link href="css/flags.min.css" type="text/css" rel="stylesheet">
|
||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" type="text/css" rel="stylesheet">
|
||||
@@ -322,51 +322,38 @@
|
||||
<div class="modal-panel-content">
|
||||
<h1 class="top-25">Settings</h1>
|
||||
|
||||
<div class="form-group top-25">
|
||||
<label for="themes"><i class="fa-solid fa-palette"></i> Theme</label>
|
||||
<div class="dropdown" id="theme-selector">
|
||||
<input type="text" placeholder="Default" readonly tabindex="0">
|
||||
<ul class="options" tabindex="-1">
|
||||
<li class="option" tabindex="0" data-value="theme1">Default</li>
|
||||
<li class="option" tabindex="0" data-value="theme2">Cappuccino</li>
|
||||
<li class="option" tabindex="0" data-value="theme3">Nature</li>
|
||||
<li class="option" tabindex="0" data-value="theme4">Ocean</li>
|
||||
<li class="option" tabindex="0" data-value="theme5">Terminal</li>
|
||||
<li class="option" tabindex="0" data-value="theme6">Nightlife</li>
|
||||
<li class="option" tabindex="0" data-value="theme7">Blurple</li>
|
||||
<li class="option" tabindex="0" data-value="theme8">Construction</li>
|
||||
<li class="option" tabindex="0" data-value="theme9">AMOLED</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="panel-full flex-center no-bg m-0">
|
||||
<%- include('_components', { component: 'dropdown', id: 'theme-selector', inputId: 'theme-selector-input', label: 'Theme', cssClass: '', placeholder: 'Default',
|
||||
options: [
|
||||
{ value: 'theme1', label: 'Default' },
|
||||
{ value: 'theme2', label: 'Cappuccino' },
|
||||
{ value: 'theme3', label: 'Nature' },
|
||||
{ value: 'theme4', label: 'Ocean' },
|
||||
{ value: 'theme5', label: 'Terminal' },
|
||||
{ value: 'theme6', label: 'Nightlife' },
|
||||
{ value: 'theme7', label: 'Blurple' },
|
||||
{ value: 'theme8', label: 'Construction' },
|
||||
{ value: 'theme9', label: 'Amoled' },
|
||||
]
|
||||
}) %>
|
||||
</div>
|
||||
|
||||
<% if (device !== 'sdr') { %>
|
||||
<div class="form-group top-25">
|
||||
<label for="signal"><i class="fa-solid fa-signal"></i> Signal units</label>
|
||||
<div class="dropdown" id="signal-selector">
|
||||
<input type="text" placeholder="dBf" readonly tabindex="0">
|
||||
<ul class="options" tabindex="-1">
|
||||
<li class="option" tabindex="0" data-value="dbf">dBf</li>
|
||||
<li class="option" tabindex="0" data-value="dbuv">dBuV</li>
|
||||
<li class="option" tabindex="0" data-value="dbm">dBm</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div><br>
|
||||
<div class="panel-full flex-center no-bg m-0">
|
||||
<%- include('_components', { component: 'dropdown', id: 'signal-selector', inputId: 'signal-selector-input', label: 'Signal units', cssClass: '', placeholder: 'dBf',
|
||||
options: [
|
||||
{ value: 'dbf', label: 'dBf' },
|
||||
{ value: 'dbuv', label: 'dBuV' },
|
||||
{ value: 'dbm', label: 'dBm' },
|
||||
]
|
||||
}) %>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div class="form-group checkbox">
|
||||
<input type="checkbox" tabindex="0" id="extended-frequency-range" aria-label="Add decimals manually">
|
||||
<label for="extended-frequency-range" class="tooltip" data-tooltip="Enabling this will allow you to enter the decimal point manually.<br>Useful for servers that use upconverters."><i class="fa-solid fa-toggle-off m-right-10"></i> Manual decimals</label>
|
||||
</div>
|
||||
<div class="form-group checkbox">
|
||||
<input type="checkbox" tabindex="0" id="ps-underscores" aria-label="Add underscores to RDS PS">
|
||||
<label for="ps-underscores" class="tooltip" data-tooltip="Enabling this option replaces spaces in RDS PS with underscores."><i class="fa-solid fa-toggle-off m-right-10"></i> RDS PS Underscores</label>
|
||||
</div>
|
||||
<div class="form-group checkbox">
|
||||
<input type="checkbox" tabindex="0" id="imperial-units" aria-label="Imperial units">
|
||||
<label for="imperial-units" class="tooltip" data-tooltip="Enabling this option will show miles in distances instead of kilometers."><i class="fa-solid fa-toggle-off m-right-10"></i> Imperial Units</label>
|
||||
</div>
|
||||
|
||||
<%- 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 class="form-group bottom-20 hide-desktop" style="float: none;">
|
||||
<label for="users-online"><i class="fa-solid fa-user"></i> Users online</label>
|
||||
<span class="users-online" name="users-online">0</span>
|
||||
@@ -375,10 +362,10 @@
|
||||
<% if (isAdminAuthenticated) { %>
|
||||
<p class="color-3">You are logged in as an adminstrator.</p>
|
||||
<div class="admin-quick-dashboard">
|
||||
<div class="icon tooltip <% if (tunerLock) { %>active<% } %>" id="dashboard-lock-admin" onClick="toggleAdminLock()" role="button" aria-label="Toggle admin lock until restart" tabindex="0" data-tooltip="Toggle admin lock<br>Lasts until restart">
|
||||
<div class="icon tooltip <% if (tunerLock) { %>active<% } %>" id="dashboard-lock-admin" onClick="toggleLock('#dashboard-lock-admin', 'wL1', 'wL0', 'Unlock Tuner (Admin)', 'Lock Tuner (Admin)');" role="button" aria-label="Toggle admin lock until restart" tabindex="0" data-tooltip="Toggle admin lock<br>Lasts until restart">
|
||||
<i class="fa-solid fa-lock"></i>
|
||||
</div>
|
||||
<div class="icon tooltip <% if (!publicTuner) { %>active<% } %>" id="dashboard-lock-tune" onClick="togglePasswordLock()" role="button" aria-label="Toggle password lock until restart" tabindex="0" data-tooltip="Toggle password lock<br>Lasts until restart">
|
||||
<div class="icon tooltip <% if (!publicTuner) { %>active<% } %>" id="dashboard-lock-tune" onClick="toggleLock('#dashboard-lock-tune', 'wT1', 'wT0', 'Unlock Tuner (Password tune)', 'Lock Tuner (Password tune)');" role="button" aria-label="Toggle password lock until restart" tabindex="0" data-tooltip="Toggle password lock<br>Lasts until restart">
|
||||
<i class="fa-solid fa-key"></i>
|
||||
</div>
|
||||
<div class="icon tooltip" role="button" aria-label="Go to admin panel" tabindex="0" data-tooltip="Go to admin panel" onClick="window.open('./setup', '_blank').focus();">
|
||||
|
||||
48
web/js/api.js
Normal file
48
web/js/api.js
Normal file
@@ -0,0 +1,48 @@
|
||||
|
||||
function tuneUp() {
|
||||
if (socket.readyState === WebSocket.OPEN) {
|
||||
getCurrentFreq();
|
||||
let addVal = 0;
|
||||
if (currentFreq < 0.52) {
|
||||
addVal = 9 - (Math.round(currentFreq*1000) % 9);
|
||||
} else if (currentFreq < 1.71) {
|
||||
// TODO: Rework to replace 9 with 9 or 10 based on regionalisation setting
|
||||
addVal = 9 - (Math.round(currentFreq*1000) % 9);
|
||||
} else if (currentFreq < 29.6) {
|
||||
addVal = 5 - (Math.round(currentFreq*1000) % 5);
|
||||
} else if (currentFreq >= 65.9 && currentFreq < 74) {
|
||||
addVal = 30 - ((Math.round(currentFreq*1000) - 65900) % 30);
|
||||
} else {
|
||||
addVal = 100 - (Math.round(currentFreq*1000) % 100);
|
||||
}
|
||||
socket.send("T" + (Math.round(currentFreq*1000) + addVal));
|
||||
}
|
||||
}
|
||||
|
||||
function tuneDown() {
|
||||
if (socket.readyState === WebSocket.OPEN) {
|
||||
getCurrentFreq();
|
||||
let subVal = 0;
|
||||
if (currentFreq < 0.52) {
|
||||
subVal = (Math.round(currentFreq*1000) % 9 == 0) ? 9 : (Math.round(currentFreq*1000) % 9);
|
||||
} else if (currentFreq < 1.71) {
|
||||
// TODO: Rework to replace 9 with 9 or 10 based on regionalisation setting
|
||||
subVal = (Math.round(currentFreq*1000) % 9 == 0) ? 9 : (Math.round(currentFreq*1000) % 9);
|
||||
} else if (currentFreq < 29.6) {
|
||||
subVal = (Math.round(currentFreq*1000) % 5 == 0) ? 5 : (Math.round(currentFreq*1000) % 5);
|
||||
} else if (currentFreq > 65.9 && currentFreq <= 74) {
|
||||
subVal = ((Math.round(currentFreq*1000) - 65900) % 30 == 0) ? 30 : ((Math.round(currentFreq*1000) - 65900) % 30);
|
||||
} else {
|
||||
subVal = (Math.round(currentFreq*1000) % 100 == 0) ? 100 : (Math.round(currentFreq*1000) % 100);
|
||||
}
|
||||
socket.send("T" + (Math.round(currentFreq*1000) - subVal));
|
||||
}
|
||||
}
|
||||
|
||||
function tuneTo(freq) {
|
||||
socket.send("T" + ((freq).toFixed(1) * 1000));
|
||||
}
|
||||
|
||||
function resetRDS() {
|
||||
socket.send("T0");
|
||||
}
|
||||
@@ -1,358 +1,123 @@
|
||||
function submitData() {
|
||||
const webserverIp = $('#webserver-ip').val() || '0.0.0.0';
|
||||
const webserverPort = $('#webserver-port').val() || '8080';
|
||||
const tuningLimit = $('#tuning-limit').is(":checked") || false;
|
||||
const tuningLowerLimit = $('#tuning-lower-limit').val() || '0';
|
||||
const tuningUpperLimit = $('#tuning-upper-limit').val() || '108';
|
||||
const chatEnabled = $("#chat-switch").length > 0 ? $("#chat-switch").is(":checked") : true;
|
||||
let configData = {}; // Store the original data structure globally
|
||||
|
||||
var themeSelectedValue = $("#selected-theme").val();
|
||||
var themeDataValue = $(".option:contains('" + themeSelectedValue + "')").attr('data-value') || 'theme1';
|
||||
const defaultTheme = themeDataValue;
|
||||
$(document).ready(function() {
|
||||
fetchConfig();
|
||||
});
|
||||
|
||||
const bgImage = $("#bg-image").val() || '';
|
||||
const rdsMode = $('#rds-mode').is(":checked") || false;
|
||||
|
||||
const ant1enabled = $('#ant1-enabled').is(":checked") || false;
|
||||
const ant2enabled = $('#ant2-enabled').is(":checked") || false;
|
||||
const ant3enabled = $('#ant3-enabled').is(":checked") || false;
|
||||
const ant4enabled = $('#ant4-enabled').is(":checked") || false;
|
||||
|
||||
const ant1name = $("#ant1-name").val() || 'Ant A';
|
||||
const ant2name = $("#ant2-name").val() || 'Ant B';
|
||||
const ant3name = $("#ant3-name").val() || 'Ant C';
|
||||
const ant4name = $("#ant4-name").val() || 'Ant D';
|
||||
|
||||
let presets = [];
|
||||
presets.push($('#preset1').val() || '87.5');
|
||||
presets.push($('#preset2').val() || '87.5');
|
||||
presets.push($('#preset3').val() || '87.5');
|
||||
presets.push($('#preset4').val() || '87.5');
|
||||
|
||||
const enableDefaultFreq = $('#default-freq-enable').is(":checked") || false;
|
||||
const defaultFreq = $('#default-freq').val() || '87.5';
|
||||
|
||||
let banlist = [];
|
||||
if($('#ip-addresses').length > 0) {
|
||||
validateAndAdd(banlist);
|
||||
}
|
||||
|
||||
|
||||
var comDevicesValue = $("#com-devices").val();
|
||||
var comDevicesDataValue = $(".option:contains('" + comDevicesValue + "')").attr('data-value') || '';
|
||||
const comPort = comDevicesDataValue;
|
||||
const wirelessConnection = $('#connection-type-toggle').is(":checked") || false;
|
||||
const xdrdIp = $('#xdrd-ip').val() || '127.0.0.1';
|
||||
const xdrdPort = $('#xdrd-port').val() || '7373';
|
||||
const xdrdPassword = $('#xdrd-password').val() || 'password';
|
||||
|
||||
const audioDevice = $('#audio-devices').val() || 'Microphone (High Definition Audio Device)';
|
||||
const audioChannels = ($('.options .option').filter(function() {
|
||||
return $(this).text() === $('#audio-channels').val();
|
||||
}).data('value') || 2);
|
||||
const audioBitrate = ($('.options .option').filter(function() {
|
||||
return $(this).text() === $('#audio-quality').val();
|
||||
}).data('value') || "192k");
|
||||
|
||||
const device = ($('.options .option').filter(function() {
|
||||
return $(this).text() === $('#device-type').val();
|
||||
}).data('value') || "tef");
|
||||
|
||||
const softwareMode = $('#audio-software-mode').is(":checked") || false;
|
||||
const startupVolume = $('#startup-volume').val() || '1';
|
||||
|
||||
const tunerName = $('#webserver-name').val() || 'FM Tuner';
|
||||
const tunerDesc = $('#webserver-desc').val() || 'Default FM tuner description';
|
||||
const broadcastTuner = $("#broadcast-tuner").is(":checked");
|
||||
const contact = $("#owner-contact").val() || '';
|
||||
const lat = $('#lat').val();
|
||||
const lon = $('#lng').val();
|
||||
const proxyIp = $("#broadcast-address").val();
|
||||
|
||||
const tunePass = $('#tune-pass').val();
|
||||
const adminPass = $('#admin-pass').val();
|
||||
|
||||
let plugins = [];
|
||||
$('#plugin-list option:selected').each(function() {
|
||||
plugins.push($(this).data('name'));
|
||||
});
|
||||
|
||||
const fmlistIntegration = $("#fmlist-integration").is(":checked") || false;
|
||||
const fmlistOmid = $('#fmlist-omid').val();
|
||||
|
||||
const tunnelUsername = $('#tunnel-username').val();
|
||||
const tunnelSubdomain = $('#tunnel-subdomain').val();
|
||||
const tunnelToken = $('#tunnel-token').val();
|
||||
const tunnelEnabled = $("#tunnel-enable").is(":checked");
|
||||
const tunnelLowLatency = $("#tunnel-lowlatency").is(":checked");
|
||||
|
||||
const publicTuner = $("#tuner-public").is(":checked");
|
||||
const lockToAdmin = $("#tuner-lock").is(":checked");
|
||||
const autoShutdown = $("#shutdown-tuner").is(":checked") || false;
|
||||
const antennasEnabled = $("#antenna-switch").is(":checked") || false;
|
||||
const bwSwitch = $("#toggle-bw").is(":checked") || false;
|
||||
|
||||
const data = {
|
||||
webserver: {
|
||||
webserverIp,
|
||||
webserverPort,
|
||||
tuningLimit,
|
||||
tuningLowerLimit,
|
||||
tuningUpperLimit,
|
||||
chatEnabled,
|
||||
defaultTheme,
|
||||
presets,
|
||||
banlist,
|
||||
bgImage,
|
||||
rdsMode,
|
||||
},
|
||||
antennas: {
|
||||
enabled: antennasEnabled,
|
||||
ant1: {
|
||||
enabled: ant1enabled,
|
||||
name: ant1name
|
||||
},
|
||||
ant2: {
|
||||
enabled: ant2enabled,
|
||||
name: ant2name
|
||||
},
|
||||
ant3: {
|
||||
enabled: ant3enabled,
|
||||
name: ant3name
|
||||
},
|
||||
ant4: {
|
||||
enabled: ant4enabled,
|
||||
name: ant4name
|
||||
},
|
||||
},
|
||||
xdrd: {
|
||||
comPort,
|
||||
wirelessConnection,
|
||||
xdrdIp,
|
||||
xdrdPort,
|
||||
xdrdPassword
|
||||
},
|
||||
audio: {
|
||||
audioDevice,
|
||||
audioChannels,
|
||||
audioBitrate,
|
||||
softwareMode,
|
||||
startupVolume,
|
||||
},
|
||||
identification: {
|
||||
tunerName,
|
||||
tunerDesc,
|
||||
broadcastTuner,
|
||||
contact,
|
||||
lat,
|
||||
lon,
|
||||
proxyIp
|
||||
},
|
||||
password: {
|
||||
tunePass,
|
||||
adminPass,
|
||||
},
|
||||
extras: {
|
||||
fmlistIntegration,
|
||||
fmlistOmid,
|
||||
},
|
||||
tunnel: {
|
||||
enabled: tunnelEnabled,
|
||||
username: tunnelUsername,
|
||||
token: tunnelToken,
|
||||
lowLatencyMode: tunnelLowLatency,
|
||||
subdomain: tunnelSubdomain,
|
||||
},
|
||||
plugins,
|
||||
device,
|
||||
publicTuner,
|
||||
lockToAdmin,
|
||||
autoShutdown,
|
||||
enableDefaultFreq,
|
||||
defaultFreq,
|
||||
bwSwitch,
|
||||
};
|
||||
|
||||
if(adminPass.length < 1) {
|
||||
alert('You need to fill in the admin password before continuing further.');
|
||||
return;
|
||||
}
|
||||
// Send data to the server using jQuery
|
||||
$.ajax({
|
||||
url: './saveData',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(data),
|
||||
success: function (message) {
|
||||
sendToast('success', 'Data saved!', message, true, true);
|
||||
},
|
||||
error: function (error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
function submitConfig() {
|
||||
updateConfigData(configData);
|
||||
if (!configData.password || !configData.password.adminPass) {
|
||||
alert('You need to fill in the admin password before continuing further.');
|
||||
return;
|
||||
}
|
||||
|
||||
function fetchData() {
|
||||
fetch("./getData")
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
$('#webserver-ip').val(data.webserver.webserverIp);
|
||||
$('#webserver-port').val(data.webserver.webserverPort);
|
||||
$('#tuning-limit').prop("checked", data.webserver.tuningLimit);
|
||||
$('#tuning-lower-limit').val(data.webserver.tuningLowerLimit || "");
|
||||
$('#tuning-upper-limit').val(data.webserver.tuningUpperLimit || "");
|
||||
$("#chat-switch").prop("checked", data.webserver.chatEnabled || false);
|
||||
|
||||
$('#selected-theme').val(data.webserver.defaultTheme || 'Default');
|
||||
$('#rds-mode').prop("checked", data.webserver.rdsMode || false);
|
||||
|
||||
var selectedTheme = $(".option[data-value='" + data.webserver.defaultTheme + "']");
|
||||
|
||||
// If the option exists, set its text as the value of the input
|
||||
if (selectedTheme.length > 0) {
|
||||
$("#selected-theme").val(selectedTheme.text());
|
||||
}
|
||||
|
||||
if (data.webserver.bgImage && data.webserver.bgImage.length > 0) {
|
||||
$("#bg-image").val(data.webserver.bgImage);
|
||||
}
|
||||
|
||||
if(Array.isArray(data.webserver.presets)) {
|
||||
$('#preset1').val(data.webserver.presets[0] || "");
|
||||
$('#preset2').val(data.webserver.presets[1] || "");
|
||||
$('#preset3').val(data.webserver.presets[2] || "");
|
||||
$('#preset4').val(data.webserver.presets[3] || "");
|
||||
}
|
||||
|
||||
$("#default-freq-enable").prop("checked", data.enableDefaultFreq || false);
|
||||
$('#default-freq').val(data.defaultFreq || "87.5");
|
||||
|
||||
$('#ip-addresses').val(data.webserver.banlist?.join('\n') || "");
|
||||
|
||||
$('#connection-type-toggle').prop("checked", data.xdrd.wirelessConnection || false);
|
||||
|
||||
if($('#connection-type-toggle').is(":checked")) {
|
||||
$('#tuner-usb').hide();
|
||||
$('#tuner-wireless').show();
|
||||
} else {
|
||||
$('#tuner-wireless').hide();
|
||||
$('#tuner-usb').show();
|
||||
}
|
||||
|
||||
$('#xdrd-ip').val(data.xdrd.xdrdIp);
|
||||
$('#xdrd-port').val(data.xdrd.xdrdPort);
|
||||
$('#xdrd-password').val(data.xdrd.xdrdPassword);
|
||||
$('#com-devices').val(data.xdrd.comPort);
|
||||
var selectedDevice = $(".option[data-value='" + data.xdrd.comPort + "']");
|
||||
if (selectedDevice.length > 0) {
|
||||
$("#com-devices").val(selectedDevice.text());
|
||||
}
|
||||
|
||||
$('#device-type').val(data.device);
|
||||
var selectedDevice = $(".option[data-value='" + data.device + "']");
|
||||
if (selectedDevice.length > 0) {
|
||||
$("#device-type").val(selectedDevice.text());
|
||||
}
|
||||
|
||||
$('#toggle-bw').prop("checked", data.bwSwitch ? data.bwSwitch : false);
|
||||
|
||||
$('#audio-devices').val(data.audio.audioDevice);
|
||||
$('#audio-channels').val(data.audio.audioChannels);
|
||||
var selectedChannels = $(".option[data-value='" + data.audio.audioChannels + "']");
|
||||
if (selectedChannels.length > 0) {
|
||||
$("#audio-channels").val(selectedChannels.text());
|
||||
}
|
||||
$('#audio-quality').val(data.audio.audioBitrate);
|
||||
var selectedQuality = $(".option[data-value='" + data.audio.audioBitrate + "']");
|
||||
if (selectedQuality.length > 0) {
|
||||
$("#audio-quality").val(selectedQuality.text());
|
||||
}
|
||||
|
||||
var antennaNames = ['ant1', 'ant2', 'ant3', 'ant4'];
|
||||
|
||||
// Iterate over each antenna name
|
||||
antennaNames.forEach(function(antenna) {
|
||||
// Set values based on the antenna name
|
||||
$('#' + antenna + '-name').val(data.antennas?.[antenna]?.name || '');
|
||||
$('#' + antenna + '-enabled').prop("checked", data.antennas?.[antenna]?.enabled || false);
|
||||
});
|
||||
|
||||
$('#audio-software-mode').prop("checked", data.audio.softwareMode || false);
|
||||
$('#startup-volume').val(data.audio.startupVolume || 1);
|
||||
$('#volume-percentage-value').text(data.audio.startupVolume !== undefined ? (data.audio.startupVolume * 100).toFixed(0) + '%' : '100%');
|
||||
|
||||
$('#webserver-name').val(data.identification.tunerName);
|
||||
$('#webserver-desc').val(data.identification.tunerDesc);
|
||||
$("#broadcast-tuner").prop("checked", data.identification.broadcastTuner);
|
||||
$("#broadcast-address").val(data.identification.proxyIp);
|
||||
$("#owner-contact").val(data.identification.contact);
|
||||
$('#lat').val(data.identification.lat);
|
||||
$('#lng').val(data.identification.lon);
|
||||
|
||||
$('#tune-pass').val(data.password.tunePass);
|
||||
$('#admin-pass').val(data.password.adminPass);
|
||||
|
||||
$("#tuner-public").prop("checked", data.publicTuner);
|
||||
$("#tuner-lock").prop("checked", data.lockToAdmin);
|
||||
$("#shutdown-tuner").prop("checked", data.autoShutdown);
|
||||
$("#antenna-switch").prop("checked", data.antennas?.enabled);
|
||||
|
||||
data.plugins.forEach(function(name) {
|
||||
// Find the option with the corresponding data-name attribute and mark it as selected
|
||||
$('#plugin-list option[data-name="' + name + '"]').prop('selected', true);
|
||||
});
|
||||
|
||||
// Update the multi-select element to reflect the changes
|
||||
$('#plugin-list').trigger('change');
|
||||
|
||||
// Check if latitude and longitude are present in the data
|
||||
if (data.identification.lat && data.identification.lon) {
|
||||
// Set the map's center to the received coordinates
|
||||
map.setView([data.identification.lat, data.identification.lon], 13);
|
||||
|
||||
// Add a pin to the map
|
||||
if (typeof pin == "object") {
|
||||
pin.setLatLng([data.identification.lat, data.identification.lon]);
|
||||
} else {
|
||||
pin = L.marker([data.identification.lat, data.identification.lon], { riseOnHover:true, draggable:true });
|
||||
pin.addTo(map);
|
||||
pin.on('drag',function(ev) {
|
||||
$('#lat').val((ev.latlng.lat).toFixed(6));
|
||||
$('#lng').val((ev.latlng.lng).toFixed(6));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
$("#fmlist-integration").prop("checked", !!(data.extras && data.extras?.fmlistIntegration));
|
||||
$('#fmlist-omid').val(data.extras?.fmlistOmid);
|
||||
|
||||
$("#tunnel-enable").prop("checked", !!(data.tunnel && data.tunnel?.enabled));
|
||||
$("#tunnel-lowlatency").prop("checked", !!(data.tunnel && data.tunnel?.lowLatencyMode));
|
||||
$('#tunnel-token').val(data.tunnel?.token);
|
||||
$('#tunnel-subdomain').val(data.tunnel?.subdomain);
|
||||
$('#tunnel-username').val(data.tunnel?.username);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching data:', error.message);
|
||||
});
|
||||
$.ajax({
|
||||
url: './saveData',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(configData),
|
||||
success: function (message) {
|
||||
sendToast('success', 'Data saved!', message, true, true);
|
||||
},
|
||||
error: function (error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function fetchConfig() {
|
||||
$.getJSON("./getData")
|
||||
.done(data => {
|
||||
configData = data;
|
||||
populateFields(configData);
|
||||
initVolumeSlider();
|
||||
initConnectionToggle();
|
||||
})
|
||||
.fail(error => console.error("Error fetching data:", error.message));
|
||||
}
|
||||
|
||||
function validateAndAdd(banlist) {
|
||||
var textarea = $('#ip-addresses');
|
||||
var ipAddresses = textarea.val().split('\n');
|
||||
function populateFields(data, prefix = "") {
|
||||
$.each(data, (key, value) => {
|
||||
|
||||
// Regular expression to validate IP address
|
||||
var ipRegex = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/;
|
||||
if (key === "presets" && Array.isArray(value)) {
|
||||
value.forEach((item, index) => {
|
||||
const presetId = `${prefix}${prefix ? "-" : ""}${key}-${index + 1}`;
|
||||
const $element = $(`#${presetId}`);
|
||||
|
||||
ipAddresses.forEach(function(ip) {
|
||||
if (ipRegex.test(ip)) {
|
||||
banlist.push(ip);
|
||||
if ($element.length) {
|
||||
$element.val(item);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === "banlist" && Array.isArray(value)) {
|
||||
const $textarea = $(`#${prefix}${prefix ? "-" : ""}${key}`);
|
||||
if ($textarea.length && $textarea.is("textarea")) {
|
||||
$textarea.val(value.join("\n"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const id = `${prefix}${prefix ? "-" : ""}${key}`;
|
||||
const $element = $(`#${id}`);
|
||||
|
||||
if (typeof value === "object" && !Array.isArray(value)) {
|
||||
populateFields(value, id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$element.length) {
|
||||
console.log(`Element with id ${id} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof value === "boolean") {
|
||||
$element.prop("checked", value);
|
||||
} else if ($element.is('input[type="text"]') && $element.closest('.dropdown').length) {
|
||||
const $dropdownOption = $element.siblings('ul.options').find(`li[data-value="${value}"]`);
|
||||
$element.val($dropdownOption.length ? $dropdownOption.text() : value);
|
||||
$element.attr('data-value', value);
|
||||
} else {
|
||||
$element.val(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateConfigData(data, prefix = "") {
|
||||
$.each(data, (key, value) => {
|
||||
const id = `${prefix}${prefix ? "-" : ""}${key}`;
|
||||
const $element = $(`#${id}`);
|
||||
|
||||
if (key === "presets") {
|
||||
data[key] = [];
|
||||
let index = 1;
|
||||
while (true) {
|
||||
const $presetElement = $(`#${prefix}${prefix ? "-" : ""}${key}-${index}`);
|
||||
if ($presetElement.length) {
|
||||
data[key].push($presetElement.val());
|
||||
index++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (key === "banlist") {
|
||||
const $textarea = $(`#${prefix}${prefix ? "-" : ""}${key}`);
|
||||
if ($textarea.length && $textarea.is("textarea")) {
|
||||
data[key] = $textarea.val().split("\n").filter(line => line.trim() !== ""); // Split lines into an array and filter out empty lines
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof value === "object" && !Array.isArray(value)) {
|
||||
return updateConfigData(value, id);
|
||||
}
|
||||
|
||||
if ($element.length) {
|
||||
data[key] = typeof value === "boolean" ? $element.is(":checked") : $element.attr("data-value") ?? $element.val();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -33,7 +33,9 @@ $(document).ready(function() {
|
||||
$currentDropdown.find('input').val($(event.currentTarget).text());
|
||||
break;
|
||||
default:
|
||||
$currentDropdown.find('input').val($(event.currentTarget).text());
|
||||
$currentDropdown.find('input')
|
||||
.val($(event.currentTarget).text())
|
||||
.attr('data-value', $(event.currentTarget).data('value'));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +1,28 @@
|
||||
var currentDate = new Date('Sep 26, 2024 12:00:00');
|
||||
var currentDate = new Date('Nov 5, 2024 21: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.3.1 [' + formattedDate + ']';
|
||||
var currentVersion = 'v1.3.2 [' + formattedDate + ']';
|
||||
|
||||
getInitialSettings();
|
||||
removeUrlParameters(); // Call this function to remove URL parameters
|
||||
removeUrlParameters();
|
||||
|
||||
function getInitialSettings() {
|
||||
$.ajax({
|
||||
url: './static_data',
|
||||
dataType: 'json',
|
||||
success: function (data) {
|
||||
localStorage.setItem('qthLatitude', data.qthLatitude);
|
||||
localStorage.setItem('qthLongitude', data.qthLongitude);
|
||||
localStorage.setItem('defaultTheme', data.defaultTheme);
|
||||
localStorage.setItem('preset1', data.presets[0]);
|
||||
localStorage.setItem('preset2', data.presets[1]);
|
||||
localStorage.setItem('preset3', data.presets[2]);
|
||||
localStorage.setItem('preset4', data.presets[3]);
|
||||
localStorage.setItem('bgImage', data.bgImage);
|
||||
localStorage.setItem('rdsMode', data.rdsMode);
|
||||
|
||||
['qthLatitude', 'qthLongitude', 'defaultTheme', 'bgImage', 'rdsMode'].forEach(key => {
|
||||
if (data[key] !== undefined) {
|
||||
localStorage.setItem(key, data[key]);
|
||||
}
|
||||
});
|
||||
|
||||
data.presets.forEach((preset, index) => {
|
||||
localStorage.setItem(`preset${index + 1}`, preset);
|
||||
});
|
||||
},
|
||||
error: function (error) {
|
||||
console.error('Error:', error);
|
||||
|
||||
@@ -124,9 +124,26 @@ $(document).ready(function () {
|
||||
}
|
||||
});
|
||||
|
||||
document.onkeydown = checkKey;
|
||||
document.onkeydown = function(event) {
|
||||
if (!event.repeat) {
|
||||
checkKey(event);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
let lastExecutionTime = 0;
|
||||
const throttleDelay = 100; // Time in ms
|
||||
$('#freq-container').on('wheel keypress', function (e) {
|
||||
e.preventDefault();
|
||||
const now = Date.now();
|
||||
|
||||
if (now - lastExecutionTime < throttleDelay) {
|
||||
// Ignore this event as it's within the throttle delay
|
||||
return;
|
||||
}
|
||||
|
||||
lastExecutionTime = now; // Update the last execution time
|
||||
|
||||
getCurrentFreq();
|
||||
var delta = e.originalEvent.deltaY;
|
||||
var adjustment = 0;
|
||||
@@ -654,54 +671,6 @@ function checkKey(e) {
|
||||
}
|
||||
}
|
||||
|
||||
function tuneUp() {
|
||||
if (socket.readyState === WebSocket.OPEN) {
|
||||
getCurrentFreq();
|
||||
let addVal = 0;
|
||||
if (currentFreq < 0.52) {
|
||||
addVal = 9 - (Math.round(currentFreq*1000) % 9);
|
||||
} else if (currentFreq < 1.71) {
|
||||
// TODO: Rework to replace 9 with 9 or 10 based on regionalisation setting
|
||||
addVal = 9 - (Math.round(currentFreq*1000) % 9);
|
||||
} else if (currentFreq < 29.6) {
|
||||
addVal = 5 - (Math.round(currentFreq*1000) % 5);
|
||||
} else if (currentFreq >= 65.9 && currentFreq < 74) {
|
||||
addVal = 30 - ((Math.round(currentFreq*1000) - 65900) % 30);
|
||||
} else {
|
||||
addVal = 100 - (Math.round(currentFreq*1000) % 100);
|
||||
}
|
||||
socket.send("T" + (Math.round(currentFreq*1000) + addVal));
|
||||
}
|
||||
}
|
||||
|
||||
function tuneDown() {
|
||||
if (socket.readyState === WebSocket.OPEN) {
|
||||
getCurrentFreq();
|
||||
let subVal = 0;
|
||||
if (currentFreq < 0.52) {
|
||||
subVal = (Math.round(currentFreq*1000) % 9 == 0) ? 9 : (Math.round(currentFreq*1000) % 9);
|
||||
} else if (currentFreq < 1.71) {
|
||||
// TODO: Rework to replace 9 with 9 or 10 based on regionalisation setting
|
||||
subVal = (Math.round(currentFreq*1000) % 9 == 0) ? 9 : (Math.round(currentFreq*1000) % 9);
|
||||
} else if (currentFreq < 29.6) {
|
||||
subVal = (Math.round(currentFreq*1000) % 5 == 0) ? 5 : (Math.round(currentFreq*1000) % 5);
|
||||
} else if (currentFreq > 65.9 && currentFreq <= 74) {
|
||||
subVal = ((Math.round(currentFreq*1000) - 65900) % 30 == 0) ? 30 : ((Math.round(currentFreq*1000) - 65900) % 30);
|
||||
} else {
|
||||
subVal = (Math.round(currentFreq*1000) % 100 == 0) ? 100 : (Math.round(currentFreq*1000) % 100);
|
||||
}
|
||||
socket.send("T" + (Math.round(currentFreq*1000) - subVal));
|
||||
}
|
||||
}
|
||||
|
||||
function tuneTo(freq) {
|
||||
socket.send("T" + ((freq).toFixed(1) * 1000));
|
||||
}
|
||||
|
||||
function resetRDS(freq) {
|
||||
socket.send("T" + ((freq).toFixed(3) * 1000));
|
||||
}
|
||||
|
||||
async function copyPs() {
|
||||
var frequency = $('#data-frequency').text();
|
||||
var pi = $('#data-pi').text();
|
||||
@@ -1055,33 +1024,20 @@ function toggleForcedStereo() {
|
||||
socket.send(message);
|
||||
}
|
||||
|
||||
function toggleAdminLock() {
|
||||
let $adminLockButton = $('#dashboard-lock-admin');
|
||||
function toggleLock(buttonSelector, activeMessage, inactiveMessage, activeLabel, inactiveLabel) {
|
||||
let $lockButton = $(buttonSelector);
|
||||
|
||||
if($adminLockButton.hasClass('active')) {
|
||||
socket.send('wL0');
|
||||
$adminLockButton.attr('aria-label', 'Lock Tuner (Admin)')
|
||||
$adminLockButton.removeClass('active');
|
||||
if ($lockButton.hasClass('active')) {
|
||||
socket.send(inactiveMessage);
|
||||
$lockButton.attr('aria-label', inactiveLabel);
|
||||
$lockButton.removeClass('active');
|
||||
} else {
|
||||
socket.send('wL1');
|
||||
$adminLockButton.attr('aria-label', 'Unlock Tuner (Admin)')
|
||||
$adminLockButton.addClass('active');
|
||||
socket.send(activeMessage);
|
||||
$lockButton.attr('aria-label', activeLabel);
|
||||
$lockButton.addClass('active');
|
||||
}
|
||||
}
|
||||
|
||||
function togglePasswordLock() {
|
||||
let $passwordLockButton = $('#dashboard-lock-tune');
|
||||
|
||||
if($passwordLockButton.hasClass('active')) {
|
||||
socket.send('wT0');
|
||||
$passwordLockButton.removeClass('active');
|
||||
$passwordLockButton.attr('aria-label', 'Lock Tuner (Password tune)')
|
||||
} else {
|
||||
socket.send('wT1');
|
||||
$passwordLockButton.addClass('active');
|
||||
$passwordLockButton.attr('aria-label', 'Unlock Tuner (Password tune)')
|
||||
}
|
||||
}
|
||||
|
||||
function initTooltips() {
|
||||
$('.tooltip').hover(function(e){
|
||||
|
||||
302
web/js/setup.js
302
web/js/setup.js
@@ -3,161 +3,20 @@ var pin;
|
||||
var tilesURL=' https://tile.openstreetmap.org/{z}/{x}/{y}.png';
|
||||
var mapAttrib='© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>';
|
||||
|
||||
// add map container
|
||||
|
||||
$(document).ready(function() {
|
||||
MapCreate();
|
||||
fetchData();
|
||||
|
||||
$('#startup-volume').on('change', function() {
|
||||
var value = $(this).val(); // Get the value of the range input
|
||||
var percentage = value * 100; // Convert to percentage
|
||||
$('#volume-percentage-value').text(percentage.toFixed(0) + '%'); // Display the percentage value
|
||||
});
|
||||
|
||||
map.on('click', function(ev) {
|
||||
$('#lat').val((ev.latlng.lat).toFixed(6));
|
||||
$('#lng').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('drag',function(ev) {
|
||||
$('#lat').val((ev.latlng.lat).toFixed(6));
|
||||
$('#lng').val((ev.latlng.lng).toFixed(6));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
$('#dashboard').show();
|
||||
mapCreate();
|
||||
loadConsoleLogs();
|
||||
|
||||
showPanelFromHash();
|
||||
$('.nav li').click(function() {
|
||||
// Remove background color from all li elements
|
||||
$('.nav li').removeClass('active');
|
||||
|
||||
// Add background color to the clicked li element
|
||||
$(this).addClass('active');
|
||||
|
||||
// Get the data-panel attribute value
|
||||
var panelId = $(this).data('panel');
|
||||
window.location.hash = panelId;
|
||||
// Hide all panels
|
||||
$('.tab-content').hide();
|
||||
|
||||
// Show the corresponding panel
|
||||
$('#' + panelId).show();
|
||||
|
||||
if(panelId == 'identification') {
|
||||
setTimeout(function () {
|
||||
map.invalidateSize();
|
||||
}, 200);
|
||||
}
|
||||
});
|
||||
|
||||
$('#connection-type-toggle').change(function(){
|
||||
if($(this).is(":checked")) {
|
||||
$('#tuner-usb').hide();
|
||||
$('#tuner-wireless').show();
|
||||
} else {
|
||||
$('#tuner-wireless').hide();
|
||||
$('#tuner-usb').show();
|
||||
}
|
||||
});
|
||||
|
||||
$('.logout-link').click(function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
// Perform an AJAX request to the /logout endpoint
|
||||
$.ajax({
|
||||
type: 'GET', // Assuming the logout is a GET request, adjust accordingly
|
||||
url: './logout',
|
||||
success: function (data) {
|
||||
// Update the content on the page with the message from the response
|
||||
$('#login-message').text(data.message);
|
||||
setTimeout(function () {
|
||||
location.reload(true);
|
||||
}, 1750);
|
||||
},
|
||||
error: function (xhr, status, error) {
|
||||
// Handle error response
|
||||
if (xhr.status === 403) {
|
||||
// Update the content on the page with the message from the error response
|
||||
$('#login-message').text(xhr.responseJSON.message);
|
||||
} else {
|
||||
// Handle other types of errors if needed
|
||||
console.error('Error:', status, error);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function stripAnsi(str) {
|
||||
return str.replace(/\u001b\[\d+m/g, '');
|
||||
}
|
||||
|
||||
$("pre").html(function(_, html) {
|
||||
html = stripAnsi(html);
|
||||
return html.replace(/\[(\d{2}:\d{2})\]|\[(INFO|DEBUG|WARN|ERROR)\]/g, function(match, time, level) {
|
||||
if (time) {
|
||||
return "<span style='color: gray;'>" + match + "</span>";
|
||||
} else if (level === "INFO") {
|
||||
return "<span style='color: lime;'>" + match + "</span>";
|
||||
} else if (level === "DEBUG") {
|
||||
return "<span style='color: cyan;'>" + match + "</span>";
|
||||
} else if (level === "WARN") {
|
||||
return "<span style='color: yellow;'>" + match + "</span>";
|
||||
} else if (level === "ERROR") {
|
||||
return "<span style='color: red;'>" + match + "</span>";
|
||||
} else {
|
||||
return "<span style='color: white;'>" + match + "</span>";
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if($("#console-output").length > 0) {
|
||||
$("#console-output").scrollTop($("#console-output")[0].scrollHeight);
|
||||
}
|
||||
|
||||
const $tabs = $('.nav li[role="presentation"]');
|
||||
let currentTabIndex = 0;
|
||||
|
||||
function updateTabFocus(index) {
|
||||
$tabs.each(function(i) {
|
||||
const $link = $(this).find('a');
|
||||
if (i === index) {
|
||||
$(this).attr('aria-selected', 'true');
|
||||
$link.attr('tabindex', '0').focus();
|
||||
} else {
|
||||
$(this).attr('aria-selected', 'false');
|
||||
$link.attr('tabindex', '-1');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function handleKeyDown(event) {
|
||||
if (event.key === 'ArrowRight') {
|
||||
event.preventDefault();
|
||||
currentTabIndex = (currentTabIndex + 1) % $tabs.length;
|
||||
updateTabFocus(currentTabIndex);
|
||||
} else if (event.key === 'ArrowLeft') {
|
||||
event.preventDefault();
|
||||
currentTabIndex = (currentTabIndex - 1 + $tabs.length) % $tabs.length;
|
||||
updateTabFocus(currentTabIndex);
|
||||
} else if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
$tabs.eq(currentTabIndex).find('a')[0].click();
|
||||
}
|
||||
}
|
||||
|
||||
updateTabFocus(currentTabIndex);
|
||||
$tabs.on('keydown', handleKeyDown);
|
||||
//toggleNav();
|
||||
initNav();
|
||||
});
|
||||
|
||||
function MapCreate() {
|
||||
// create map instance
|
||||
/**
|
||||
* Function to create & handle maps.
|
||||
* Also contains map handling such as reloading / pin click registering.
|
||||
*/
|
||||
|
||||
function mapCreate() {
|
||||
if (!(typeof map == "object")) {
|
||||
map = L.map('map', {
|
||||
center: [40,0],
|
||||
@@ -167,39 +26,70 @@ function MapCreate() {
|
||||
else {
|
||||
map.setZoom(3).panTo([40,0]);
|
||||
}
|
||||
// create the tile layer with correct attribution
|
||||
|
||||
L.tileLayer(tilesURL, {
|
||||
attribution: mapAttrib,
|
||||
maxZoom: 19
|
||||
}).addTo(map);
|
||||
|
||||
map.on('click', function(ev) {
|
||||
$('#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));
|
||||
});
|
||||
}
|
||||
});
|
||||
mapReload();
|
||||
}
|
||||
|
||||
function showPanelFromHash() {
|
||||
var panelId = window.location.hash.substring(1);
|
||||
if (panelId) {
|
||||
// Hide all panels
|
||||
$('.tab-content').hide();
|
||||
|
||||
// Show the panel corresponding to the hash fragment
|
||||
$('#' + panelId).show();
|
||||
|
||||
// Remove active class from all li elements
|
||||
$('.nav li').removeClass('active');
|
||||
|
||||
// Add active class to the corresponding li element
|
||||
$('.nav li[data-panel="' + panelId + '"]').addClass('active');
|
||||
}
|
||||
if(window.location.hash.length == 0) {
|
||||
$('.nav li[data-panel="dashboard"]').addClass('active');
|
||||
function mapReload() {
|
||||
setTimeout(function () {
|
||||
map.invalidateSize();
|
||||
}, 200);
|
||||
}
|
||||
|
||||
function showPanelFromHash() {
|
||||
var panelId = window.location.hash.substring(1) || 'dashboard';
|
||||
|
||||
$('.tab-content').hide();
|
||||
$('#' + panelId).show();
|
||||
|
||||
$('.nav li').removeClass('active');
|
||||
$('.nav li[data-panel="' + panelId + '"]').addClass('active');
|
||||
}
|
||||
|
||||
function initNav() {
|
||||
$('.nav li').click(function() {
|
||||
$('.nav li').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
var panelId = $(this).data('panel');
|
||||
window.location.hash = panelId;
|
||||
$('.tab-content').hide();
|
||||
$('#' + panelId).show();
|
||||
|
||||
panelId == 'identification' ? mapReload() : null;
|
||||
});
|
||||
|
||||
$('[role="tab"]').on('keydown', function(event) {
|
||||
if (event.key === 'Enter') {
|
||||
$(this).find('a').click();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
const isMobile = window.innerWidth <= 768;
|
||||
|
||||
if (navOpen) {
|
||||
// Close the navigation
|
||||
if (isMobile) {
|
||||
// Do nothing to .admin-wrapper on mobile (since we're overlaying)
|
||||
$(".admin-wrapper").css({
|
||||
@@ -217,10 +107,8 @@ function toggleNav() {
|
||||
}
|
||||
$(".sidenav-close").html('<i class="fa-solid fa-chevron-right"></i>');
|
||||
} 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
|
||||
@@ -235,3 +123,69 @@ function toggleNav() {
|
||||
$(".sidenav-close").html('<i class="fa-solid fa-chevron-left"></i>');
|
||||
}
|
||||
}
|
||||
|
||||
function initVolumeSlider() {
|
||||
const $volumeInput = $('#audio-startupVolume');
|
||||
const $percentageValue = $('#volume-percentage-value');
|
||||
|
||||
const updateDisplay = () => {
|
||||
$percentageValue.text(($volumeInput.val() * 100).toFixed(0) + '%');
|
||||
};
|
||||
|
||||
updateDisplay();
|
||||
$volumeInput.on('change', updateDisplay);
|
||||
}
|
||||
|
||||
function initConnectionToggle() {
|
||||
const connectionToggle = $('#xdrd-wirelessConnection');
|
||||
const tunerUSB = $('#tuner-usb');
|
||||
const tunerWireless = $('#tuner-wireless');
|
||||
|
||||
function toggleType() {
|
||||
if (connectionToggle.is(":checked")) {
|
||||
tunerUSB.hide();
|
||||
tunerWireless.show();
|
||||
} else {
|
||||
tunerWireless.hide();
|
||||
tunerUSB.show();
|
||||
}
|
||||
}
|
||||
|
||||
toggleType();
|
||||
connectionToggle.change(toggleType);
|
||||
}
|
||||
|
||||
function stripAnsi(str) {
|
||||
return str.replace(/\u001b\[\d+m/g, '');
|
||||
}
|
||||
|
||||
async function loadConsoleLogs() {
|
||||
await new Promise((resolve) => {
|
||||
$("pre").html(function (_, html) {
|
||||
html = stripAnsi(html);
|
||||
|
||||
const logColors = {
|
||||
INFO: "lime",
|
||||
DEBUG: "cyan",
|
||||
WARN: "yellow",
|
||||
ERROR: "red"
|
||||
};
|
||||
|
||||
let firstBracketProcessed = false;
|
||||
|
||||
const processedHtml = html.replace(/\[([^\]]+)\]/g, function (match, content) {
|
||||
if (!firstBracketProcessed) {
|
||||
firstBracketProcessed = true;
|
||||
return `<span style='color: gray;'>${match}</span>`;
|
||||
}
|
||||
|
||||
const color = logColors[content] || "white";
|
||||
return `<span style='color: ${color};'>${match}</span>`;
|
||||
});
|
||||
|
||||
return processedHtml;
|
||||
});
|
||||
resolve();
|
||||
});
|
||||
$("#console-output").scrollTop($("#console-output")[0].scrollHeight);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
function sendToast(type, title, message, persistent, important) {
|
||||
var toastId = 'toast-' + new Date().getTime(); // Unique ID for each toast
|
||||
var toastId = 'toast-' + new Date().getTime();
|
||||
|
||||
// If title isn't provided, use the type as the title
|
||||
var toastTitle = title ? title : capitalizeFirstLetter(type);
|
||||
|
||||
// Icon mapping based on the toast type
|
||||
var toastIcons = {
|
||||
success: 'fa-check-circle',
|
||||
error: 'fa-times-circle',
|
||||
warning: 'fa-exclamation-triangle',
|
||||
info: 'fa-info-circle',
|
||||
default: 'fa-bell' // Default icon if the type is not found
|
||||
default: 'fa-bell'
|
||||
};
|
||||
|
||||
// Get the icon class based on the toast type, fallback to 'default' if type doesn't exist
|
||||
var iconClass = toastIcons[type] || toastIcons['default'];
|
||||
var iconClass = toastIcons[type] || toastIcons['default']; // Get the icon class based on the toast type, fallback to 'default' if type doesn't exist
|
||||
|
||||
// Create the toast element
|
||||
var $toast = $(`
|
||||
<div class="toast ${type} flex-container flex-phone ${important ? 'important' : ''}" id="${toastId}">
|
||||
<div class="toast-icon"><i class="fa-solid ${iconClass}"></i></div>
|
||||
@@ -28,33 +24,28 @@ function sendToast(type, title, message, persistent, important) {
|
||||
</div>
|
||||
`);
|
||||
|
||||
// Append the toast to the container
|
||||
$('#toast-container').append($toast);
|
||||
|
||||
// Add the 'show' class after appending for fade-in effect
|
||||
setTimeout(function () {
|
||||
$toast.addClass('show');
|
||||
}, 10); // Timeout to ensure the element is appended before the animation triggers
|
||||
}, 10);
|
||||
|
||||
// Close button functionality
|
||||
$toast.find('.close-btn').click(function () {
|
||||
closeToast($toast);
|
||||
});
|
||||
|
||||
// If not persistent, remove it after 5 seconds
|
||||
if (!persistent) {
|
||||
setTimeout(function () {
|
||||
closeToast($toast);
|
||||
}, 5000); // 5000 ms = 5 seconds
|
||||
}, 5000);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to close and remove the toast
|
||||
function closeToast($toast) {
|
||||
$toast.removeClass('show'); // Trigger fade-out
|
||||
$toast.removeClass('show');
|
||||
setTimeout(function () {
|
||||
$toast.remove(); // Remove the element from DOM after the animation
|
||||
}, 300); // Timeout matches the CSS transition duration
|
||||
$toast.remove();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function capitalizeFirstLetter(string) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
$.getScript('./js/api.js');
|
||||
$.getScript('./js/main.js');
|
||||
$.getScript('./js/dropdown.js');
|
||||
$.getScript('./js/modal.js');
|
||||
|
||||
@@ -1,40 +1,9 @@
|
||||
$(document).ready(function() {
|
||||
if($('.step:visible').index() == 0) {
|
||||
$('.btn-prev').hide();
|
||||
}
|
||||
|
||||
$('.btn-next').click(function() {
|
||||
var currentStep = $('.step:visible');
|
||||
var nextStep = currentStep.next('.step');
|
||||
|
||||
if (nextStep.length !== 0) {
|
||||
currentStep.hide();
|
||||
nextStep.show();
|
||||
updateProgressBar(nextStep);
|
||||
} else {
|
||||
submitData();
|
||||
}
|
||||
|
||||
updateWizardContent();
|
||||
});
|
||||
|
||||
$('.btn-prev').click(function() {
|
||||
var currentStep = $('.step:visible');
|
||||
var nextStep = currentStep.prev('.step');
|
||||
|
||||
if (nextStep.length !== 0) {
|
||||
currentStep.hide();
|
||||
nextStep.show();
|
||||
updateProgressBar(nextStep);
|
||||
} else {
|
||||
alert('You have reached the beginning of the wizard.');
|
||||
}
|
||||
|
||||
updateWizardContent();
|
||||
});
|
||||
$('.btn-prev').toggle($('.step:visible').index() !== 0);
|
||||
$('.btn-next').click(() => navigateStep(true));
|
||||
$('.btn-prev').click(() => navigateStep(false));
|
||||
});
|
||||
|
||||
// Function to update the progress bar buttons
|
||||
function updateProgressBar(currentStep) {
|
||||
var stepIndex = $('.step').index(currentStep) + 1;
|
||||
$('.btn-rounded-cube').removeClass('activated');
|
||||
@@ -42,21 +11,24 @@ function updateProgressBar(currentStep) {
|
||||
}
|
||||
|
||||
function updateWizardContent() {
|
||||
if($('.step:visible').index() == 0) {
|
||||
$('.btn-prev').hide();
|
||||
} else {
|
||||
$('.btn-prev').show();
|
||||
}
|
||||
var visibleStepIndex = $('.step:visible').index();
|
||||
|
||||
if($('.step:visible').index() == 3) {
|
||||
setTimeout(function () {
|
||||
map.invalidateSize();
|
||||
}, 200);
|
||||
}
|
||||
$('.btn-prev').toggle(visibleStepIndex !== 0);
|
||||
$('.btn-next').text(visibleStepIndex === 4 ? 'Save' : 'Next');
|
||||
|
||||
visibleStepIndex === 3 && mapReload();
|
||||
}
|
||||
|
||||
function navigateStep(isNext) {
|
||||
var currentStep = $('.step:visible');
|
||||
var targetStep = isNext ? currentStep.next('.step') : currentStep.prev('.step');
|
||||
|
||||
if($('.step:visible').index() == 4) {
|
||||
$('.btn-next').text('Save');
|
||||
} else {
|
||||
$('.btn-next').text('Next')
|
||||
if (targetStep.length !== 0) {
|
||||
currentStep.hide();
|
||||
targetStep.show();
|
||||
updateProgressBar(targetStep);
|
||||
} else if (isNext) {
|
||||
submitData();
|
||||
}
|
||||
updateWizardContent();
|
||||
}
|
||||
443
web/setup.ejs
443
web/setup.ejs
@@ -18,25 +18,25 @@
|
||||
<div class="sidenav-content">
|
||||
<h1 class="top-25">Settings</h1>
|
||||
<ul class="nav" role="tablist" style="border-radius: 15px 15px 0 0;">
|
||||
<li role="presentation" data-panel="dashboard" aria-selected="true">
|
||||
<a href="#" role="tab" tabindex="0" aria-controls="dashboard"><i class="fa-solid fa-fw fa-chart-line"></i> Dashboard</a>
|
||||
<li role="tab" data-panel="dashboard" aria-selected="true" tabindex="0">
|
||||
<a href="#" role="tab" aria-controls="dashboard"><i class="fa-solid fa-fw fa-chart-line"></i> Dashboard</a>
|
||||
</li>
|
||||
<li role="presentation" data-panel="tuner">
|
||||
<li role="tab" data-panel="tuner" tabindex="0">
|
||||
<a href="#" role="tab" tabindex="-1" aria-controls="tuner"><i class="fa-solid fa-fw fa-radio"></i> Tuner</a>
|
||||
</li>
|
||||
<li role="presentation" data-panel="audio">
|
||||
<li role="tab" data-panel="audio" tabindex="0">
|
||||
<a href="#" role="tab" tabindex="-1" aria-controls="audio"><i class="fa-solid fa-fw fa-volume-high"></i> Audio</a>
|
||||
</li>
|
||||
<li role="presentation" data-panel="webserver">
|
||||
<li role="tab" data-panel="webserver" tabindex="0">
|
||||
<a href="#" role="tab" tabindex="-1" aria-controls="webserver"><i class="fa-solid fa-fw fa-server"></i> Webserver</a>
|
||||
</li>
|
||||
<li role="presentation" data-panel="plugins">
|
||||
<li role="tab" data-panel="plugins" tabindex="0">
|
||||
<a href="#" role="tab" tabindex="-1" aria-controls="plugins"><i class="fa-solid fa-fw fa-puzzle-piece"></i> Plugins</a>
|
||||
</li>
|
||||
<li role="presentation" data-panel="identification">
|
||||
<li role="tab" data-panel="identification" tabindex="0">
|
||||
<a href="#" role="tab" tabindex="-1" aria-controls="identification"><i class="fa-solid fa-fw fa-circle-info"></i> Identification & Map</a>
|
||||
</li>
|
||||
<li role="presentation" data-panel="extras">
|
||||
<li role="tab" data-panel="extras" tabindex="0">
|
||||
<a href="#" role="tab" tabindex="-1" aria-controls="extras"><i class="fa-solid fa-fw fa-star"></i> Extras</a>
|
||||
</li>
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<div class="icon tooltip" role="button" aria-label="Go back to the main screen" tabindex="0" data-tooltip="Go back to the main screen" onclick="document.location.href='./'">
|
||||
<i class="fa-solid fa-arrow-left"></i>
|
||||
</div>
|
||||
<div class="icon tooltip" id="submit-config" role="button" aria-label="Save settings" tabindex="0" data-tooltip="Save settings" onclick="submitData()">
|
||||
<div class="icon tooltip" id="submit-config" role="button" aria-label="Save settings" tabindex="0" data-tooltip="Save settings" onclick="submitConfig()">
|
||||
<i class="fa-solid fa-save"></i>
|
||||
</div>
|
||||
<div class="icon tooltip logout-link" role="button" aria-label="Sign out" tabindex="0" data-tooltip="Sign out">
|
||||
@@ -116,24 +116,12 @@
|
||||
<div class="panel-50 p-bottom-20">
|
||||
<h3>Quick settings</h3>
|
||||
<div class="flex-container flex-center" style="margin: 30px;">
|
||||
<div class="form-group checkbox">
|
||||
<input type="checkbox" tabindex="0" id="tuner-public" aria-label="Public tuner (no password)">
|
||||
<label for="tuner-public"><i class="fa-solid fa-toggle-off m-right-10"></i> Public tuner</label>
|
||||
</div>
|
||||
<div class="form-group checkbox">
|
||||
<input type="checkbox" tabindex="0" id="tuner-lock" aria-label="Admin lock (only admins can tune)">
|
||||
<label for="tuner-lock"><i class="fa-solid fa-toggle-off m-right-10"></i> Admin lock</label>
|
||||
</div><br>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Public Tuner', id: 'publicTuner'}) %>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Admin lock', id: 'lockToAdmin'}) %><br>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="tune-pass">Tune password:</label>
|
||||
<input class="input-text w-200" type="password" name="tune-pass" id="tune-pass">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="admin-pass">Admin setup password:</label>
|
||||
<input class="input-text w-200" type="password" name="admin-pass" id="admin-pass">
|
||||
</div><br>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-150', placeholder: '', label: 'Tune password', id: 'password-tunePass', password: true}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-150', placeholder: '', label: 'Admin password', id: 'password-adminPass', password: true}) %><br>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -159,69 +147,64 @@
|
||||
<div class="flex-container contains-dropdown">
|
||||
<div class="panel-33 p-bottom-20">
|
||||
<h3>Device</h3>
|
||||
<div class="form-group">
|
||||
<p class="text-left">Your audio device port.<br>
|
||||
<span class="text-gray">This is where your tuner is plugged in.</span>
|
||||
</p>
|
||||
<label for="audio-devices"><i class="fa-solid fa-headphones"></i> STREAM AUDIO FROM:</label>
|
||||
<div class="dropdown">
|
||||
<input id="audio-devices" type="text" name="audio-devices" placeholder="Choose your audio device" readonly tabindex="0" />
|
||||
<ul class="options" tabindex="-1" id="deviceList">
|
||||
<% videoDevices.forEach(device => { %>
|
||||
<li data-value="<%= device.name %>" class="option" tabindex="0"><%= device.name %></li>
|
||||
<% }); %>
|
||||
<% audioDevices.forEach(device => { %>
|
||||
<li data-value="<%= device.name %>" class="option" tabindex="0"><%= device.name %></li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p>Your audio device port.<br>
|
||||
<span class="text-gray">This is where your tuner is plugged in.</span>
|
||||
</p>
|
||||
|
||||
<%- include('_components', {
|
||||
component: 'dropdown',
|
||||
id: 'audioList',
|
||||
inputId: 'audio-audioDevice',
|
||||
label: 'Audio device',
|
||||
cssClass: '',
|
||||
placeholder: 'Choose your audio device',
|
||||
options: [
|
||||
...videoDevices.map(device => ({
|
||||
value: device.name,
|
||||
label: `${device.name}`
|
||||
})),
|
||||
...audioDevices.map(device => ({
|
||||
value: device.name,
|
||||
label: `${device.name}`
|
||||
}))
|
||||
]
|
||||
}) %>
|
||||
</div>
|
||||
<div class="panel-33 p-bottom-20">
|
||||
<h3>Channels</h3>
|
||||
<div class="form-group">
|
||||
<p class="text-left">Audio channel count.<br>
|
||||
<span class="text-gray">Choose between Mono / Stereo.</span>
|
||||
</p>
|
||||
<label for="audio-devices"><i class="fa-solid fa-microphone-lines"></i> Audio channels:</label>
|
||||
<div class="dropdown">
|
||||
<input id="audio-channels" type="text" name="audio-channels" placeholder="Stereo" readonly tabindex="0" />
|
||||
<ul class="options" tabindex="-1">
|
||||
<li data-value="2" class="option" tabindex="0">Stereo</li>
|
||||
<li data-value="1" class="option" tabindex="0">Mono</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<p>Audio channel count.<br>
|
||||
<span class="text-gray">Choose between Mono / Stereo.</span>
|
||||
</p>
|
||||
<%- include('_components', { component: 'dropdown', id: 'audio-channels-dropdown', inputId: 'audio-audioChannels', label: 'Audio channels', cssClass: '', placeholder: 'Stereo',
|
||||
options: [
|
||||
{ value: '2', label: 'Stereo' },
|
||||
{ value: '1', label: 'Mono' }
|
||||
]
|
||||
}) %><br>
|
||||
</div>
|
||||
<div class="panel-33 p-bottom-20">
|
||||
<h3>Bitrate</h3>
|
||||
<div class="form-group">
|
||||
<p class="text-left">The bitrate of the mp3 audio.<br>
|
||||
<p>The bitrate of the mp3 audio.<br>
|
||||
<span class="text-gray">Minimum: 64 Kbps • Maximum: 320 Kbps</span>
|
||||
</p>
|
||||
<label for="audio-quality"><i class="fa-solid fa-wave-square"></i> Audio quality:</label>
|
||||
<div class="dropdown">
|
||||
<input id="audio-quality" type="text" name="audio-quality" placeholder="128k (standard)" readonly tabindex="0" />
|
||||
<ul class="options" tabindex="-1">
|
||||
<li data-value="64k" class="option" tabindex="0">64k (lowest quality)</li>
|
||||
<li data-value="96k" class="option" tabindex="0">96k (low quality)</li>
|
||||
<li data-value="128k" class="option" tabindex="0">128k (standard)</li>
|
||||
<li data-value="192k" class="option" tabindex="0">192k (higher quality)</li>
|
||||
<li data-value="256k" class="option" tabindex="0">256k (very high quality)</li>
|
||||
<li data-value="320k" class="option" tabindex="0">320k (ultra quality)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
<%- include('_components', { component: 'dropdown', id: 'audio-quality-dropdown', inputId: 'audio-audioBitrate', label: 'Audio quality', cssClass: '', placeholder: '128kbps (standard)',
|
||||
options: [
|
||||
{ value: '64k', label: '64kbps (lowest quality)' },
|
||||
{ value: '96k', label: '96kbps (low quality)' },
|
||||
{ value: '128k', label: '128kbps (standard)' },
|
||||
{ value: '192k', label: '192kbps (high quality)' },
|
||||
{ value: '256k', label: '256kbps (very high quality)' },
|
||||
{ value: '320k', label: '320kbps (ultra quality)' }
|
||||
]
|
||||
}) %><br>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-container">
|
||||
<div class="panel-100-real p-bottom-20">
|
||||
<h3>Miscellaneous</h3>
|
||||
<div class="form-group checkbox bottom-20">
|
||||
<input type="checkbox" tabindex="0" id="audio-software-mode" aria-label="ALSA Software mode (plughw) - Linux only">
|
||||
<label for="audio-software-mode"><i class="fa-solid fa-toggle-off m-right-10"></i> ALSA software mode</label>
|
||||
</div>
|
||||
<p>If you use an USB audio card on Linux, enabling this option might fix your audio issues.</p>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'ALSA Software mode', id: 'audio-softwareMode'}) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -232,88 +215,44 @@
|
||||
<div class="panel-33 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>
|
||||
<div class="form-group">
|
||||
<label for="webserver-ip">Webserver IP:</label>
|
||||
<input class="input-text w-150" type="text" name="webserver-ip" id="webserver-ip" placeholder="0.0.0.0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="webserver-port">Webserver port:</label>
|
||||
<input class="input-text w-100" type="text" name="webserver-port" id="webserver-port" placeholder="8080">
|
||||
</div>
|
||||
<br>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-150', placeholder: '0.0.0.0', label: 'Webserver IP', id: 'webserver-webserverIp'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100', placeholder: '8080', label: 'Webserver port', id: 'webserver-webserverPort'}) %><br>
|
||||
</div>
|
||||
<div class="panel-33 p-bottom-20">
|
||||
<h3>Design</h3>
|
||||
<h4>Background image</h4>
|
||||
<div class="form-group">
|
||||
<label for="bg-image">Image link:</label>
|
||||
<input class="input-text w-200" type="text" placeholder="" name="bg-image" id="bg-image">
|
||||
</div>
|
||||
<h4>Background image</h4>
|
||||
<%- include('_components', {component: 'text', cssClass: '', placeholder: 'Direct image link', label: 'Image link', id: 'webserver-bgImage'}) %><br>
|
||||
|
||||
<h4 class="top-25">Themes</h4>
|
||||
<div class="form-group top-10">
|
||||
<label for="selected-theme"><i class="fa-solid fa-palette"></i> Default server theme</label>
|
||||
<div class="dropdown" id="server-theme-selector" style="margin-right: 0;">
|
||||
<input type="text" placeholder="Default" id="selected-theme" readonly tabindex="0">
|
||||
<ul class="options" tabindex="-1">
|
||||
<li class="option" tabindex="0" data-value="theme1">Default</li>
|
||||
<li class="option" tabindex="0" data-value="theme2">Cappuccino</li>
|
||||
<li class="option" tabindex="0" data-value="theme3">Nature</li>
|
||||
<li class="option" tabindex="0" data-value="theme4">Ocean</li>
|
||||
<li class="option" tabindex="0" data-value="theme5">Terminal</li>
|
||||
<li class="option" tabindex="0" data-value="theme6">Nightlife</li>
|
||||
<li class="option" tabindex="0" data-value="theme7">Blurple</li>
|
||||
<li class="option" tabindex="0" data-value="theme8">Construction</li>
|
||||
<li class="option" tabindex="0" data-value="theme9">AMOLED</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<%- include('_components', { component: 'dropdown', id: 'server-theme-selector', inputId: 'webserver-defaultTheme', label: 'Default server theme', cssClass: '', placeholder: 'Default',
|
||||
options: [
|
||||
{ value: 'theme1', label: 'Default' },
|
||||
{ value: 'theme2', label: 'Cappuccino' },
|
||||
{ value: 'theme3', label: 'Nature' },
|
||||
{ value: 'theme4', label: 'Ocean' },
|
||||
{ value: 'theme5', label: 'Terminal' },
|
||||
{ value: 'theme6', label: 'Nightlife' },
|
||||
{ value: 'theme7', label: 'Blurple' },
|
||||
{ value: 'theme8', label: 'Construction' },
|
||||
{ value: 'theme9', label: 'Amoled' },
|
||||
]
|
||||
}) %><br>
|
||||
</div>
|
||||
<div class="panel-33 p-bottom-20">
|
||||
<h3>Antennas</h3>
|
||||
<div class="flex-container">
|
||||
|
||||
</div>
|
||||
<div class="form-group checkbox bottom-20">
|
||||
<input type="checkbox" tabindex="0" id="antenna-switch" aria-label="Antenna switch">
|
||||
<label for="antenna-switch"><i class="fa-solid fa-toggle-off m-right-10"></i> Antenna switch</label>
|
||||
</div><br>
|
||||
|
||||
<div class="form-group checkbox">
|
||||
<input type="checkbox" tabindex="0" id="ant1-enabled" aria-label="Enable antenna 1">
|
||||
<label for="ant1-enabled"><i class="fa-solid fa-check"></i></label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ant1-name">Antenna 1 name:</label>
|
||||
<input class="input-text w-100" type="text" placeholder="Ant A" name="ant1-name" id="ant1-name">
|
||||
</div><br>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: 'bottom-20', label: 'Antenna switch', id: 'antennas-enabled'}) %><br>
|
||||
|
||||
<div class="form-group checkbox">
|
||||
<input type="checkbox" tabindex="0" id="ant2-enabled" aria-label="Enable antenna 2">
|
||||
<label for="ant2-enabled"><i class="fa-solid fa-check"></i></label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ant2-name">Antenna 2 name:</label>
|
||||
<input class="input-text w-100" type="text" placeholder="Ant B" name="ant2-name" id="ant2-name">
|
||||
</div><br>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Antenna 1', id: 'antennas-ant1-enabled'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100', placeholder: 'Ant A', label: 'Antenna 1 name', id: 'antennas-ant1-name'}) %><br>
|
||||
|
||||
<div class="form-group checkbox">
|
||||
<input type="checkbox" tabindex="0" id="ant3-enabled" aria-label="Enable antenna 3">
|
||||
<label for="ant3-enabled"><i class="fa-solid fa-check"></i></label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ant3-name">Antenna 3 name:</label>
|
||||
<input class="input-text w-100" type="text" placeholder="Ant C" name="ant3-name" id="ant3-name">
|
||||
</div><br>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Antenna 2', id: 'antennas-ant2-enabled'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100', placeholder: 'Ant B', label: 'Antenna 2 name', id: 'antennas-ant2-name'}) %><br>
|
||||
|
||||
<div class="form-group checkbox">
|
||||
<input type="checkbox" tabindex="0" id="ant4-enabled" aria-label="Enable antenna 4">
|
||||
<label for="ant4-enabled"><i class="fa-solid fa-check"></i></label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="ant4-name">Antenna 4 name:</label>
|
||||
<input class="input-text w-100" type="text" placeholder="Ant D" name="ant4-name" id="ant4-name">
|
||||
</div><br>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Antenna 3', id: 'antennas-ant3-enabled'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100', placeholder: 'Ant C', label: 'Antenna 3 name', id: 'antennas-ant3-name'}) %><br>
|
||||
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Antenna 4', id: 'antennas-ant4-enabled'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100', placeholder: 'Ant D', label: 'Antenna 4 name', id: 'antennas-ant4-name'}) %><br>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -323,40 +262,21 @@
|
||||
<p>If you want to limit which frequencies the users can tune to,<br>you can set the lower and upper limit here.<br>
|
||||
<span class="text-gray">Enter frequencies in MHz.</span>
|
||||
</p>
|
||||
<div class="form-group checkbox" aria-label="Limit tuning">
|
||||
<input type="checkbox" tabindex="0" id="tuning-limit">
|
||||
<label for="tuning-limit"><i class="fa-solid fa-toggle-off m-right-10"></i> Limit tuning</label>
|
||||
</div><br>
|
||||
<div class="form-group">
|
||||
<label for="tuning-lower-limit">Lower limit:</label>
|
||||
<input class="input-text w-100" type="text" placeholder="0" name="tuning-lower-limit" id="tuning-lower-limit">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="tuning-upper-limit">Upper Limit:</label>
|
||||
<input class="input-text w-100" type="text" placeholder="108" name="tuning-upper-limit" id="tuning-upper-limit">
|
||||
</div>
|
||||
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Limit tuning', id: 'webserver-tuningLimit'}) %><br>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100', placeholder: '0', label: 'Lower limit', id: 'webserver-tuningLowerLimit'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100', placeholder: '108', label: 'Upper limit', id: 'webserver-tuningUpperLimit'}) %>
|
||||
</div>
|
||||
|
||||
<div class="panel-50 p-bottom-20">
|
||||
<h3>Presets</h3>
|
||||
<p>You can set up to 4 presets.<br>These presets are accessible with the F1-F4 buttons.<br>
|
||||
<span class="text-gray">Enter frequencies in MHz.</span></p>
|
||||
<div class="form-group">
|
||||
<label for="preset1">Preset 1:</label>
|
||||
<input class="input-text w-100" type="text" placeholder="87.5" name="preset1" id="preset1">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="preset2">Preset 2:</label>
|
||||
<input class="input-text w-100" type="text" placeholder="87.5" name="preset2" id="preset2">
|
||||
</div><br>
|
||||
<div class="form-group">
|
||||
<label for="preset1">Preset 3:</label>
|
||||
<input class="input-text w-100" type="text" placeholder="87.5" name="preset3" id="preset3">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="preset1">Preset 4:</label>
|
||||
<input class="input-text w-100" type="text" placeholder="87.5" name="preset4" id="preset4">
|
||||
</div>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100', placeholder: '87.5', label: 'Preset 1', id: 'webserver-presets-1'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100', placeholder: '87.5', label: 'Preset 2', id: 'webserver-presets-2'}) %>
|
||||
<br>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100', placeholder: '87.5', label: 'Preset 3', id: 'webserver-presets-3'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100', placeholder: '87.5', label: 'Preset 4', id: 'webserver-presets-4'}) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -364,17 +284,11 @@
|
||||
<div class="panel-33 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>
|
||||
<div class="form-group checkbox bottom-20">
|
||||
<input type="checkbox" tabindex="0" id="rds-mode" aria-label="Enable american RDS mode (RBDS)">
|
||||
<label for="rds-mode"><i class="fa-solid fa-toggle-off m-right-10"></i> American Mode (RBDS)</label>
|
||||
</div>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: 'bottom-20', iconClass: '', label: 'American RDS mode (RBDS)', id: 'webserver-rdsMode'}) %><br>
|
||||
</div>
|
||||
<div class="panel-33">
|
||||
<h3>Chat options</h3>
|
||||
<div class="form-group checkbox bottom-20">
|
||||
<input type="checkbox" tabindex="0" id="chat-switch" aria-label="Enable chat">
|
||||
<label for="chat-switch"><i class="fa-solid fa-toggle-off m-right-10"></i> Chat</label>
|
||||
</div>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Chat', id: 'webserver-chatEnabled'}) %>
|
||||
</div>
|
||||
|
||||
<div class="panel-33 p-bottom-20" style="padding-left: 20px; padding-right: 20px;">
|
||||
@@ -383,7 +297,7 @@
|
||||
<span class="text-gray">You can see their IP address by hovering over their nickname. One IP per row.</span></p>
|
||||
<div class="form-group">
|
||||
<label for="preset1">Banned users:</label>
|
||||
<textarea id="ip-addresses" placeholder="123.45.67.8"></textarea>
|
||||
<textarea id="webserver-banlist" placeholder="123.45.67.8"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -417,18 +331,15 @@
|
||||
<div class="flex-container contains-dropdown">
|
||||
<div class="panel-33 p-bottom-20">
|
||||
<h3>Device type</h3>
|
||||
<div class="form-group">
|
||||
<label for="device-type"><i class="fa-solid fa-radio"></i> Device</label>
|
||||
<div class="dropdown" id="device-selector" style="margin-right: 0;">
|
||||
<input type="text" placeholder="TEF6686 / TEA685x" id="device-type" readonly tabindex="0">
|
||||
<ul class="options" tabindex="0">
|
||||
<li class="option" tabindex="0" data-value="tef">TEF668x / TEA685x</li>
|
||||
<li class="option" tabindex="0" data-value="xdr">XDR (F1HD / S10HDiP)</li>
|
||||
<li class="option" tabindex="0" data-value="sdr">SDR (RTL-SDR / AirSpy)</li>
|
||||
<li class="option" tabindex="0" data-value="other">Other</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div><br>
|
||||
<%- include('_components', { component: 'dropdown', id: 'device-selector', inputId: 'device', label: 'Device', cssClass: '', placeholder: 'TEF668x / TEA685x',
|
||||
options: [
|
||||
{ value: 'tef', label: 'TEF668x / TEA685x' },
|
||||
{ value: 'xdr', label: 'XDR (F1HD / S10HDiP)' },
|
||||
{ value: 'sdr', label: 'SDR (RTL-SDR / AirSpy)' },
|
||||
{ value: 'other', label: 'Other' }
|
||||
]
|
||||
}) %><br>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="panel-33 p-bottom-20" style="padding-right: 20px; padding-left: 20px;">
|
||||
@@ -436,7 +347,7 @@
|
||||
<p class="text-gray">If you want to choose the COM port directly, choose "Direct".<br>If you use xdrd or your receiver is connected via Wi-Fi, choose TCP/IP.</p>
|
||||
<div class="auto top-10">
|
||||
<label class="toggleSwitch nolabel" onclick="">
|
||||
<input id="connection-type-toggle" type="checkbox" tabindex="0" aria-label="Connection type"/>
|
||||
<input id="xdrd-wirelessConnection" type="checkbox" tabindex="0" aria-label="Connection type"/>
|
||||
<a></a>
|
||||
<span>
|
||||
<span class="left-span">Direct</span>
|
||||
@@ -450,33 +361,25 @@
|
||||
|
||||
<div id="tuner-usb">
|
||||
<p class="text-gray">Choose your desired <strong>COM port</strong><br> </p>
|
||||
<div class="form-group">
|
||||
<label for="com-devices"><i class="fa-brands fa-usb"></i> USB Device:</label>
|
||||
<div class="dropdown" style="margin-right: 0;">
|
||||
<input id="com-devices" type="text" name="com-devices" placeholder="Choose your USB device" readonly tabindex="0" />
|
||||
<ul class="options" tabindex="-1" id="deviceList">
|
||||
<% serialPorts.forEach(serialPort => { %>
|
||||
<li data-value="<%= serialPort.path %>" class="option" tabindex="0"><%= serialPort.path %> - <%= serialPort.friendlyName %></li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<%- include('_components', {
|
||||
component: 'dropdown',
|
||||
id: 'deviceList',
|
||||
inputId: 'xdrd-comPort',
|
||||
label: 'USB Device',
|
||||
cssClass: '',
|
||||
placeholder: 'Choose your USB device',
|
||||
options: serialPorts.map(serialPort => ({
|
||||
value: serialPort.path,
|
||||
label: `${serialPort.path} - ${serialPort.friendlyName}`
|
||||
}))
|
||||
}) %>
|
||||
</div>
|
||||
|
||||
<div id="tuner-wireless">
|
||||
<p class="text-gray">If you are connecting your tuner <strong>wirelessly</strong>, enter the tuner IP. <br> If you use <strong>xdrd</strong>, use 127.0.0.1 as your IP.</p>
|
||||
<div class="form-group">
|
||||
<label for="xdrd-ip">xdrd ip address:</label>
|
||||
<input class="input-text w-150" type="text" name="xdrd-ip" id="xdrd-ip" placeholder="127.0.0.1">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="xdrd-port">xdrd port:</label>
|
||||
<input class="input-text w-100" type="text" name="xdrd-port" id="xdrd-port" placeholder="7373">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="xdrd-password">xdrd server password:</label>
|
||||
<input class="input-text w-150" type="password" name="xdrd-password" id="xdrd-password">
|
||||
</div>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-150', label: 'xdrd IP address', id: 'xdrd-xdrdIp'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100', label: 'xdrd port', id: 'xdrd-xdrdPort'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-150', label: 'xdrd password', id: 'xdrd-xdrdPassword', password: true}) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -485,20 +388,14 @@
|
||||
<h3>Startup</h3>
|
||||
<h4>Startup volume</h4>
|
||||
<div class="panel-75 auto" style="height: 48px;">
|
||||
<input type="range" id="startup-volume" min="0" max="1" step="0.01" value="1" aria-label="Startup Volume slider">
|
||||
<input type="range" id="audio-startupVolume" min="0" max="1" step="0.01" value="1" aria-label="Startup Volume slider">
|
||||
</div>
|
||||
<h4 class="top-10 text-gray" id="volume-percentage-value"></h4>
|
||||
|
||||
<hr>
|
||||
<h4 class="bottom-20">Default frequency</h4>
|
||||
<div class="form-group checkbox">
|
||||
<input type="checkbox" tabindex="0" id="default-freq-enable" aria-label="Enable deafult frequency for first client">
|
||||
<label for="default-freq-enable"><i class="fa-solid fa-toggle-off m-right-10"></i> Default frequency for first client</label>
|
||||
</div><br>
|
||||
<div class="form-group">
|
||||
<label for="default-freq">Default frequency</label>
|
||||
<input class="input-text w-150" type="text" placeholder="87.5" name="default-freq" id="default-freq">
|
||||
</div>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Default frequency for first client', id: 'enableDefaultFreq'}) %><br>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100', placeholder: '87.5', label: 'Default frequency', id: 'defaultFreq'}) %>
|
||||
</div>
|
||||
<div class="panel-50 p-bottom-20">
|
||||
<h3>Miscellaneous</h3>
|
||||
@@ -506,18 +403,12 @@
|
||||
<div class="panel-50 no-bg">
|
||||
<h4>Bandwidth switch</h4>
|
||||
<p>Bandwidth switch allows the user to set the bandwidth manually.</p>
|
||||
<div class="form-group checkbox">
|
||||
<input type="checkbox" tabindex="0" id="toggle-bw" aria-label="Toggle bandwidth switch">
|
||||
<label for="toggle-bw"><i class="fa-solid fa-toggle-off m-right-10"></i> Bandwidth switch</label>
|
||||
</div><br>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Bandwidth switch', id: 'bwSwitch'}) %><br>
|
||||
</div>
|
||||
<div class="panel-50 no-bg">
|
||||
<h4>Automatic shutdown</h4>
|
||||
<p>Toggling this option will put the tuner to sleep when no clients are connected.</p>
|
||||
<div class="form-group checkbox">
|
||||
<input type="checkbox" tabindex="0" id="shutdown-tuner" aria-label="Auto shutdown">
|
||||
<label for="shutdown-tuner"><i class="fa-solid fa-toggle-off m-right-10"></i> Auto-shutdown</label>
|
||||
</div>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Auto-shutdown', id: 'autoShutdown'}) %><br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -533,30 +424,21 @@
|
||||
|
||||
<p>Set your tuner name and description here.<br>This info will be visible to anyone who tunes in. </p>
|
||||
<div class="panel-full no-bg" style="padding-left: 20px; padding-right: 20px;">
|
||||
<label for="webserver-name" style="width: 100%;max-width: 768px; margin:auto;">Webserver name:</label>
|
||||
<input style="width: 100%; max-width: 768px;" class="input-text" type="text" name="webserver-name" id="webserver-name" placeholder="Fill your server name here." maxlength="32">
|
||||
<label for="identification-tunerName" style="width: 100%;max-width: 768px; margin:auto;">Webserver name:</label>
|
||||
<input style="width: 100%; max-width: 768px;" class="input-text" type="text" name="identification-tunerName" id="identification-tunerName" placeholder="Fill your server name here." maxlength="32">
|
||||
<br>
|
||||
<label for="webserver-desc" style="width: 100%;max-width: 768px; margin: auto;">Webserver description:</label>
|
||||
<textarea id="webserver-desc" name="webserver-desc" placeholder="Fill the server description here. You can put useful info here such as your antenna setup. You can use simple markdown." maxlength="255"></textarea>
|
||||
<label for="identification-tunerDesc" style="width: 100%;max-width: 768px; margin: auto;">Webserver description:</label>
|
||||
<textarea id="identification-tunerDesc" name="webserver-desc" placeholder="Fill the server description here. You can put useful info here such as your antenna setup. You can use simple markdown." maxlength="255"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-50">
|
||||
<h3>Online map</h3>
|
||||
<p>If your location information is filled,<br>you can add your tuner to a public list.</p>
|
||||
<p></p>
|
||||
<div class="form-group checkbox">
|
||||
<input type="checkbox" tabindex="0" id="broadcast-tuner" aria-label="Broadcast to map">
|
||||
<label for="broadcast-tuner"><i class="fa-solid fa-toggle-off m-right-10"></i> Broadcast to map</label>
|
||||
</div><br>
|
||||
<div class="form-group">
|
||||
<label for="owner-contact">Owner contact:</label>
|
||||
<input class="input-text" type="text" placeholder="Your e-mail, discord..." name="owner-contact" id="owner-contact">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="broadcast-address">Broadcast address (if using a proxy):</label>
|
||||
<input class="input-text" type="text" name="broadcast-address" id="broadcast-address">
|
||||
</div>
|
||||
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Broadcast to map', id: 'identification-broadcastTuner'}) %><br>
|
||||
<%- include('_components', {component: 'text', cssClass: '', placeholder: 'Your e-mail or Discord...', label: 'Owner contact', id: 'identification-contact'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: '', label: 'Broadcast address (if using a proxy)', id: 'identification-proxyIp'}) %>
|
||||
|
||||
<p>Check your tuner at <strong><a href="https://servers.fmdx.org" target="_blank" class="color-4">servers.fmdx.org</a></strong>.</p>
|
||||
<p class="text-small text-gray">By activating the <strong>Broadcast to map</strong> option,<br>you agree to the <a href="https://fmdx.org/projects/webserver.php#rules" target="_blank">Terms of Service</a>.</p>
|
||||
@@ -566,15 +448,9 @@
|
||||
<div class="panel-100">
|
||||
<h3>Location</h3>
|
||||
<p class="text-gray">Location info is useful for automatic identification of stations using RDS.</p>
|
||||
<div class="form-group">
|
||||
<label for="lat">Latitude:</label>
|
||||
<input class="input-text" type="text" name="lat" id="lat">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="lng">Longitude:</label>
|
||||
<input class="input-text" type="text" name="lng" id="lng">
|
||||
</div>
|
||||
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-150', placeholder: '', label: 'Latitude', id: 'identification-lat'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-150', placeholder: '', label: 'Longitude', id: 'identification-lon'}) %>
|
||||
|
||||
<div id="map"></div>
|
||||
<br>
|
||||
@@ -587,46 +463,25 @@
|
||||
<h3>FMLIST Integration</h3>
|
||||
<p>FMLIST integration allows you to get potential DXes logged on the <a href="http://fmlist.org/fm_logmap.php?hours=900" target="_blank" class="text-bold color-4">FMLIST Visual Logbook</a>.<br>
|
||||
Your server also needs to have a valid UUID, which is obtained by registering on maps in the <strong>Identification & Map</strong> tab.</p>
|
||||
<div class="form-group checkbox">
|
||||
<input type="checkbox" tabindex="0" id="fmlist-integration" aria-label="FMLIST integration">
|
||||
<label for="fmlist-integration"><i class="fa-solid fa-toggle-off m-right-10"></i> FMLIST integration</label>
|
||||
</div><br>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: 'm-right-10', label: 'FMLIST integration', id: 'extras-fmlistIntegration'}) %><br>
|
||||
|
||||
<p>You can also fill in your OMID from FMLIST.org, if you want the logs to be saved to your account.</p>
|
||||
<div class="form-group">
|
||||
<label for="fmlist-omid">OMID</label>
|
||||
<input class="input-text w-100" type="text" name="fmlist-omid" id="fmlist-omid" maxlength="5">
|
||||
</div>
|
||||
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100', placeholder: '', label: 'OMID', id: 'extras-fmlistOmid'}) %>
|
||||
</div>
|
||||
|
||||
<div class="panel-100 p-bottom-20">
|
||||
<h3>Tunnel</h3>
|
||||
<p>When you become a <a href="https://buymeacoffee.com/fmdx" target="_blank"><strong>FMDX.org supporter</strong></a>, you can host your webserver without the need of a public IP address.<br>
|
||||
When you become a supporter, you can message the Founders on Discord for your login details.</p>
|
||||
<div class="form-group checkbox">
|
||||
<input type="checkbox" tabindex="0" id="tunnel-enable" aria-label="Enable tunnel">
|
||||
<label for="tunnel-enable"><i class="fa-solid fa-toggle-off m-right-10"></i> Enable tunnel</label>
|
||||
</div><br>
|
||||
<div class="form-group">
|
||||
<label for="tunnel-subdomain">subdomain name</label>
|
||||
<input class="input-text w-150" type="text" name="tunnel-subdomain" id="tunnel-subdomain" maxlength="20">.fmtuner.org
|
||||
</div><br>
|
||||
<div class="form-group">
|
||||
<label for="tunnel-username">Username</label>
|
||||
<input class="input-text w-150" type="text" name="tunnel-username" id="tunnel-username">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="tunnel-password">Token</label>
|
||||
<input class="input-text" style="width: 100%; max-width: 400px;" type="password" name="tunnel-token" id="tunnel-token" maxlength="36">
|
||||
</div><br>
|
||||
|
||||
<%- include('_components', {component: 'checkbox', cssClass: 'm-right-10', label: 'Enable tunnel', id: 'tunnel-enabled'}) %><br>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-150', placeholder: '', label: 'Subdomain name', id: 'tunnel-subdomain'}) %>.fmtuner.org<br>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-150', placeholder: '', label: 'Username', id: 'tunnel-username'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-250', password: true, placeholder: '', label: 'Token', id: 'tunnel-token'}) %>
|
||||
|
||||
<p>Enabling low latency mode may provide better experience, however it will also use more bandwidth.</p>
|
||||
<div class="form-group checkbox">
|
||||
<input type="checkbox" tabindex="0" id="tunnel-lowlatency" aria-label="Low latency mode">
|
||||
<label for="tunnel-lowlatency"><i class="fa-solid fa-toggle-off m-right-10"></i> Low latency mode</label>
|
||||
</div>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: 'm-right-10', label: 'Low latency mode', id: 'tunnel-lowLatencyMode'}) %><br>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -640,4 +495,4 @@
|
||||
<script src="js/plugins/<%= plugin %>"></script>
|
||||
<% }); %>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
250
web/wizard.ejs
250
web/wizard.ejs
@@ -28,50 +28,40 @@
|
||||
</div>
|
||||
|
||||
<div class="panel-100">
|
||||
|
||||
<!-- BASIC SETTINGS -->
|
||||
<div class="panel-100 step" id="step1">
|
||||
<div class="panel-100 step no-bg" id="step1">
|
||||
<h2 class="settings-heading">Basic settings</h2>
|
||||
<p class="m-0">Welcome to the setup wizard! Let's set up some basic things.</p>
|
||||
|
||||
<h3>Webserver connection:</h3>
|
||||
<p class="m-0 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>
|
||||
<h3 class="settings-heading">Webserver connection</h3>
|
||||
<p class="m-0">Leave the IP at 0.0.0.0 unless you explicitly know you have to change it.<br>DO NOT enter your public IP here.</p>
|
||||
<div class="flex-center top-25">
|
||||
<div class="form-group">
|
||||
<label for="webserver-ip">Webserver IP</label>
|
||||
<input class="input-text w-150" type="text" name="webserver-ip" id="webserver-ip" placeholder="0.0.0.0">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="webserver-port">Webserver port</label>
|
||||
<input class="input-text w-100" type="text" name="webserver-port" id="webserver-port" placeholder="8080">
|
||||
</div>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-150', placeholder: '0.0.0.0', label: 'Webserver IP', id: 'webserver-webserverIp'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100', placeholder: '8080', label: 'Webserver port', id: 'webserver-webserverPort'}) %><br>
|
||||
</div>
|
||||
</div>
|
||||
<!-- BASIC SETTINGS END -->
|
||||
<!-- TUNER SETTINGS -->
|
||||
<div id="step2" class="step" style="display: none">
|
||||
<h2>Tuner settings</h2>
|
||||
<h2 class="settings-heading">Tuner settings</h2>
|
||||
|
||||
<h3>Tuner type:</h3>
|
||||
<p class="text-gray">Settings a proper device type ensures that the correct interface and settings will load.</p>
|
||||
<div class="panel-100 no-bg text-center">
|
||||
<div class="form-group" style="float: none;">
|
||||
<label for="device-selector"><i class="fa-solid fa-radio"></i> Device</label>
|
||||
<div class="dropdown" id="device-selector" style="margin: auto;">
|
||||
<input type="text" placeholder="TEF6686 / TEA685x" id="device-type" readonly>
|
||||
<ul class="options">
|
||||
<li class="option" data-value="tef">TEF668x / TEA685x</li>
|
||||
<li class="option" data-value="xdr">XDR (F1HD / S10HDiP)</li>
|
||||
<li class="option" data-value="sdr">SDR (RTL-SDR / AirSpy)</li>
|
||||
<li class="option" data-value="other">Other</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="settings-heading">Tuner type</h3>
|
||||
<p class="m-0">Settings a proper device type ensures that the correct interface and settings will load.</p>
|
||||
<div class="panel-100 no-bg flex-center">
|
||||
<%- include('_components', { component: 'dropdown', id: 'device-selector', inputId: 'device', label: 'Device', cssClass: '', placeholder: 'TEF668x / TEA685x',
|
||||
options: [
|
||||
{ value: 'tef', label: 'TEF668x / TEA685x' },
|
||||
{ value: 'xdr', label: 'XDR (F1HD / S10HDiP)' },
|
||||
{ value: 'sdr', label: 'SDR (RTL-SDR / AirSpy)' },
|
||||
{ value: 'other', label: 'Other' }
|
||||
]
|
||||
}) %><br>
|
||||
</div>
|
||||
<h3>Tuner connection:</h3>
|
||||
<div class="clearfix"></div>
|
||||
<h3 class="settings-heading">Tuner connection</h3>
|
||||
<div style="width: 300px;" class="auto top-10">
|
||||
<label class="toggleSwitch nolabel" onclick="">
|
||||
<input id="connection-type-toggle" type="checkbox" aria-label="Tuner connection type"/>
|
||||
<input id="xdrd-wirelessConnection" type="checkbox" aria-label="Tuner connection type"/>
|
||||
<a></a>
|
||||
<span>
|
||||
<span class="left-span">Direct</span>
|
||||
@@ -81,137 +71,115 @@
|
||||
</div>
|
||||
<div id="tuner-usb" class="top-25">
|
||||
<p>It's time to choose your USB device.</p>
|
||||
<div class="form-group">
|
||||
<label for="com-devices"><i class="fa-brands fa-usb"></i> USB Device</label>
|
||||
<div class="dropdown" style="width: 300px;margin-right: 0;">
|
||||
<input id="com-devices" type="text" name="com-devices" placeholder="Choose your USB device" readonly />
|
||||
<ul class="options" id="deviceList">
|
||||
<% serialPorts.forEach(serialPort => { %>
|
||||
<li data-value="<%= serialPort.path %>" class="option"><%= serialPort.path %> - <%= serialPort.friendlyName %></li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="panel-100 no-bg flex-center">
|
||||
<%- include('_components', {
|
||||
component: 'dropdown',
|
||||
id: 'deviceList',
|
||||
inputId: 'xdrd-comPort',
|
||||
label: 'USB Device',
|
||||
cssClass: '',
|
||||
placeholder: 'Choose your USB device',
|
||||
options: serialPorts.map(serialPort => ({
|
||||
value: serialPort.path,
|
||||
label: `${serialPort.path} - ${serialPort.friendlyName}`
|
||||
}))
|
||||
}) %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
<div id="tuner-wireless" class="top-25">
|
||||
<p class="m-0 text-gray">If you are connecting your tuner <strong>wirelessly</strong>, enter the tuner IP. <br> If you use <strong>xdrd</strong>, use 127.0.0.1 as your IP.</p>
|
||||
<p class="m-0">If you are connecting your tuner <strong>wirelessly</strong>, enter the tuner IP. <br> If you use <strong>xdrd</strong>, use 127.0.0.1 as your IP.</p>
|
||||
<div class="flex-center top-25">
|
||||
<div class="form-group">
|
||||
<label for="xdrd-ip">xdrd ip address</label>
|
||||
<input class="input-text w-150" type="text" name="xdrd-ip" id="xdrd-ip" placeholder="127.0.0.1">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="xdrd-port">xdrd port</label>
|
||||
<input class="input-text w-100" type="text" name="xdrd-port" id="xdrd-port" placeholder="7373">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="xdrd-password">xdrd server password</label>
|
||||
<input class="input-text w-150" type="password" name="xdrd-password" id="xdrd-password">
|
||||
</div>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-150', label: 'xdrd IP address', id: 'xdrd-xdrdIp'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100', label: 'xdrd port', id: 'xdrd-xdrdPort'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-150', label: 'xdrd password', id: 'xdrd-xdrdPassword', password: true}) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- TUNER SETTINGS END -->
|
||||
<!-- AUDIO SETTINGS -->
|
||||
<div id="step3" class="step" style="display: none;">
|
||||
<div class="panel-100" style="min-height: 120px;margin-bottom: 0;">
|
||||
<h2 class="settings-heading">AUDIO SETTINGS</h2>
|
||||
<p class="m-0 text-gray">In this section, we will set up the audio.<br>
|
||||
<div class="panel-100 no-bg" style="min-height: 120px;margin-bottom: 0;">
|
||||
<h2 class="settings-heading">Audio settings</h2>
|
||||
<p class="m-0">In this section, we will set up the audio.<br>
|
||||
Choose the audio port your tuner is connected to and desired audio settings here.</p>
|
||||
<p class="text-gray">Recommended defaults have already been set for the audio quality, you can keep them as-is.</p>
|
||||
|
||||
<div class="panel-100 p-bottom-20 flex-container flex-center">
|
||||
<div class="form-group">
|
||||
<label for="audio-devices"><i class="fa-solid fa-headphones"></i> Stream audio from</label>
|
||||
<div class="dropdown" style="width: 300px;">
|
||||
<input id="audio-devices" type="text" name="audio-devices" placeholder="Choose your audio device" readonly />
|
||||
<ul class="options" id="deviceList">
|
||||
<% videoDevices.forEach(device => { %>
|
||||
<li data-value="<%= device.name %>" class="option"><%= device.name %></li>
|
||||
<% }); %>
|
||||
<% audioDevices.forEach(device => { %>
|
||||
<li data-value="<%= device.name %>" class="option"><%= device.name %></li>
|
||||
<% }); %>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-100 no-bg p-bottom-20 flex-container flex-center">
|
||||
<%- include('_components', {
|
||||
component: 'dropdown',
|
||||
id: 'audioList',
|
||||
inputId: 'audio-audioDevice',
|
||||
label: 'Audio device',
|
||||
cssClass: '',
|
||||
placeholder: 'Choose your audio device',
|
||||
options: [
|
||||
...videoDevices.map(device => ({
|
||||
value: device.name,
|
||||
label: `${device.name}`
|
||||
})),
|
||||
...audioDevices.map(device => ({
|
||||
value: device.name,
|
||||
label: `${device.name}`
|
||||
}))
|
||||
]
|
||||
}) %>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="audio-devices"><i class="fa-solid fa-microphone-lines"></i> Audio channels</label>
|
||||
<div class="dropdown" style="width: 300px;">
|
||||
<input id="audio-channels" type="text" name="audio-channels" placeholder="Stereo" readonly />
|
||||
<ul class="options">
|
||||
<li data-value="2" class="option">Stereo</li>
|
||||
<li data-value="1" class="option">Mono</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<%- include('_components', { component: 'dropdown', id: 'audio-channels-dropdown', inputId: 'audio-audioChannels', label: 'Audio channels', cssClass: '', placeholder: 'Stereo',
|
||||
options: [
|
||||
{ value: '2', label: 'Stereo' },
|
||||
{ value: '1', label: 'Mono' }
|
||||
]
|
||||
}) %>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="audio-quality"><i class="fa-solid fa-wave-square"></i> Audio quality</label>
|
||||
<div class="dropdown" style="width: 300px;">
|
||||
<input id="audio-quality" type="text" name="audio-quality" placeholder="128k (standard)" readonly />
|
||||
<ul class="options">
|
||||
<li data-value="64k" class="option">64k (lowest quality)</li>
|
||||
<li data-value="96k" class="option">96k (low quality)</li>
|
||||
<li data-value="128k" class="option">128k (standard)</li>
|
||||
<li data-value="192k" class="option">192k (higher quality)</li>
|
||||
<li data-value="256k" class="option">256k (highest quality)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<%- include('_components', { component: 'dropdown', id: 'audio-quality-dropdown', inputId: 'audio-audioBitrate', label: 'Audio quality', cssClass: '', placeholder: '128kbps (standard)',
|
||||
options: [
|
||||
{ value: '64k', label: '64kbps (lowest quality)' },
|
||||
{ value: '96k', label: '96kbps (low quality)' },
|
||||
{ value: '128k', label: '128kbps (standard)' },
|
||||
{ value: '192k', label: '192kbps (high quality)' },
|
||||
{ value: '256k', label: '256kbps (very high quality)' },
|
||||
{ value: '320k', label: '320kbps (ultra quality)' }
|
||||
]
|
||||
}) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- AUDIO SETTINGS END -->
|
||||
<!-- IDENTIFICATION START -->
|
||||
<div id="step4" class="step" style="display: none;">
|
||||
<div class="panel-100" style="padding-bottom: 20px;">
|
||||
<h2 class="settings-heading">IDENTIFICATION INFO</h2>
|
||||
<p class="text-gray">In this part, we will set up your indentification info, such as the server name, description and location.</p>
|
||||
<label for="webserver-name" style="width: 100%;max-width: 768px; margin:auto;">Webserver name:</label>
|
||||
<input style="width: 100%; max-width: 768px;" class="input-text" type="text" name="webserver-name" id="webserver-name" placeholder="Fill your server name here." maxlength="32">
|
||||
<div class="panel-100 no-bg" style="padding-bottom: 20px;">
|
||||
<h2 class="settings-heading">Webserver info</h2>
|
||||
<p class="m-0">In this part, we will set up your webserver info, such as the server name, description and location.</p>
|
||||
<label for="identification-tunerName" style="width: 100%;max-width: 768px; margin:auto;">Webserver name:</label>
|
||||
<input style="width: 100%; max-width: 768px;" class="input-text" type="text" name="identification-tunerName" id="identification-tunerName" placeholder="Fill your server name here." maxlength="32">
|
||||
<br>
|
||||
<label for="webserver-desc" style="width: 100%;max-width: 768px; margin: auto;">Webserver description:</label>
|
||||
<textarea id="webserver-desc" name="webserver-desc" placeholder="Fill the server description here. You can put useful info here such as your antenna setup. You can use simple markdown." maxlength="255"></textarea>
|
||||
<label for="identification-tunerDesc" style="width: 100%;max-width: 768px; margin: auto;">Webserver description:</label>
|
||||
<textarea id="identification-tunerDesc" name="webserver-desc" placeholder="Fill the server description here. You can put useful info here such as your antenna setup. You can use simple markdown." maxlength="255"></textarea>
|
||||
|
||||
<h3>Location:</h3>
|
||||
<p class="text-gray">Location info is useful for automatic identification of stations using RDS.</p>
|
||||
<h3 class="settings-heading">Location</h3>
|
||||
<p>Location info is useful for automatic identification of stations using RDS.</p>
|
||||
<div class="panel-100 no-bg flex-container flex-center">
|
||||
<div class="form-group">
|
||||
<label for="lat">Latitude:</label>
|
||||
<input class="input-text" type="text" name="lat" id="lat">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="lng">Longitude:</label>
|
||||
<input class="input-text" type="text" name="lng" id="lng">
|
||||
</div>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-250', placeholder: '', label: 'Latitude', id: 'identification-lat'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-250', placeholder: '', label: 'Longitude', id: 'identification-lon'}) %>
|
||||
</div>
|
||||
<div id="map"></div>
|
||||
|
||||
<h3>Map broadcast:</h3>
|
||||
<h3 class="settings-heading">Map broadcast</h3>
|
||||
<p class="m-0">If your location info is filled, you can add your tuner to a public list.</p>
|
||||
<p class="m-0">The list is available at <strong><a href="https://servers.fmdx.org" target="_blank" class="color-4">servers.fmdx.org</a></strong>.</p>
|
||||
<p class="text-small text-gray">By activating the <strong>Broadcast to map</strong> option, you agree to the <a href="https://fmdx.org/projects/webserver.php#rules" target="_blank">Terms of Service</a>.</p>
|
||||
|
||||
<p class="text-gray">Only fill up your broadcast address if you are using a proxy. If you don't know what a proxy is, leave it empty.</p>
|
||||
|
||||
<div class="panel-100 flex-container flex-center">
|
||||
<div class="form-group checkbox">
|
||||
<input type="checkbox" id="broadcast-tuner" aria-label="Broadcast to map">
|
||||
<label for="broadcast-tuner">Show my tuner on the public list</label>
|
||||
</div>
|
||||
<div class="form-group checkbox">
|
||||
<input type="checkbox" id="tuner-public" aria-label="Allow tuning wihtout password">
|
||||
<label for="tuner-public">Allow tuning without password</label>
|
||||
</div>
|
||||
<div class="panel-100 no-bg flex-container flex-center">
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Broadcast to map', id: 'identification-broadcastTuner'}) %><br>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Allow tuning without password', id: 'publicTuner'}) %>
|
||||
</div>
|
||||
|
||||
<div class="panel-100 flex-container flex-center">
|
||||
<div class="form-group">
|
||||
<label for="broadcast-address">Broadcast address:</label>
|
||||
<input class="input-text" type="text" name="broadcast-address" id="broadcast-address">
|
||||
</div>
|
||||
<p class="text-gray">If you use a proxy / tunnel service, enter the access link here. If you don't know what a proxy is, leave it empty.</p>
|
||||
|
||||
<div class="panel-100 no-bg flex-container flex-center">
|
||||
<%- include('_components', {component: 'text', cssClass: '', label: 'Broadcast address (if using a proxy)', id: 'identification-proxyIp'}) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -219,24 +187,20 @@
|
||||
<!-- ADMIN SETTINGS START -->
|
||||
<div id="step5" class="step" style="display: none;">
|
||||
<h2 class="settings-heading">Admin panel settings</h2>
|
||||
<p>We are at the last and final step of the settings.</p>
|
||||
<p>We are at the last and final step of the wizard.</p>
|
||||
|
||||
<p class="text-gray">Here we can set the password. Tune password is optional.<br>Setting an admin password allows you to change settings later and setting one up is mandatory.</p>
|
||||
<p>Here we can set the password. Tune password is optional.<br>Setting an admin password allows you to change settings later and setting one up is mandatory.</p>
|
||||
<div class="flex-container flex-center">
|
||||
<div class="form-group">
|
||||
<label for="tune-pass">Tune password:</label>
|
||||
<input class="input-text w-200" type="password" name="tune-pass" id="tune-pass">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="admin-pass">Admin setup password:</label>
|
||||
<input class="input-text w-200" type="password" name="admin-pass" id="admin-pass">
|
||||
</div>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-150', placeholder: '', label: 'Tune password', id: 'password-tunePass', password: true}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-150', placeholder: '', label: 'Admin password', id: 'password-adminPass', password: true}) %><br>
|
||||
</div>
|
||||
<p>You can now click the <strong>save button</strong> to save your settings.</p>
|
||||
<p>You can now click the <strong>save button</strong> to save your settings. After that, you will need to restart the webserver.</p>
|
||||
</div>
|
||||
|
||||
<button class="btn-prev"><i class="fa-solid fa-arrow-left"></i></button>
|
||||
<button class="btn-next">Next</button>
|
||||
<div class="panel-100 no-bg">
|
||||
<button class="btn-prev"><i class="fa-solid fa-arrow-left"></i></button>
|
||||
<button class="btn-next">Next</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-100 no-bg">
|
||||
<p>Feel free to contact us on <a href="https://discord.gg/ZAVNdS74mC" target="_blank"><strong><i class="fa-brands fa-discord"></i> Discord</strong></a> for community support.</p>
|
||||
@@ -252,4 +216,4 @@
|
||||
<script src="js/wizard.js"></script>
|
||||
<script src="js/confighandler.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
Reference in New Issue
Block a user