fuck the wizard too, what is he gonna do? magic?

This commit is contained in:
2026-07-02 13:45:55 +02:00
parent dc8ece6034
commit f84567425b
5 changed files with 1 additions and 634 deletions
+1 -52
View File
@@ -34,28 +34,7 @@ router.get('/', (req, res) => {
const noPlugins = req.query.noPlugins === 'true';
if (configExists() === false) {
let serialPorts;
SerialPort.list().then((deviceList) => {
serialPorts = deviceList.map(port => ({
path: port.path,
friendlyName: port.friendlyName,
}));
parseAudioDevice((result) => {
res.render('wizard', { // Magical utility wizard
isAdminAuthenticated: true,
videoDevices: result.audioDevices,
audioDevices: result.videoDevices,
serialPorts: serialPorts,
tunerProfiles: tunerProfiles.map((profile) => ({
id: profile.id,
label: profile.label,
detailsHtml: helpers.parseMarkdown(profile.details || '')
}))
});
});
});
res.status(500).send("not configured");
} else {
res.render('index', {
isAdminAuthenticated: helpers.isAdmin(req),
@@ -90,36 +69,6 @@ router.get('/403', (req, res) => {
router.get('/audioonly', (req, res) => res.render('audioonly'))
router.get('/wizard', (req, res) => {
let serialPorts;
if(!helpers.isAdmin(req)) {
res.render('login');
return;
}
SerialPort.list().then((deviceList) => {
serialPorts = deviceList.map(port => ({
path: port.path,
friendlyName: port.friendlyName,
}));
parseAudioDevice((result) => {
res.render('wizard', {
isAdminAuthenticated: helpers.isAdmin(req),
videoDevices: result.audioDevices,
audioDevices: result.videoDevices,
serialPorts: serialPorts,
tunerProfiles: tunerProfiles.map((profile) => ({
id: profile.id,
label: profile.label,
detailsHtml: helpers.parseMarkdown(profile.details || '')
}))
});
});
})
})
router.get('/setup', (req, res) => {
let serialPorts;
function loadConfig() {
-215
View File
@@ -1,215 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Wizard - 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="css/libs/fontawesome.css" type="text/css" rel="stylesheet">
<script src="js/libs/jquery.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css" type="text/css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script>
<link rel="icon" type="image/svg+xml" href="favicon.svg" id="favicon" />
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div id="toast-container" style="position: fixed; top: 20px; right: 20px; z-index: 9999;"></div>
<div class="wrapper-outer wrapper-full">
<div id="wrapper">
<div class="panel-100 no-bg">
<img class="top-10" src="../images/openradio_logo_neutral.png" height="64px">
<h2 class="text-monospace text-light text-gray">[SETUP WIZARD]</h2>
</div>
<div class="panel-100 no-bg flex-container flex-center flex-phone">
<div class="btn-rounded-cube activated">1</div>
<div class="btn-rounded-cube">2</div>
<div class="btn-rounded-cube">3</div>
<div class="btn-rounded-cube">4</div>
<div class="btn-rounded-cube">5</div>
</div>
<div class="panel-100">
<!-- BASIC SETTINGS -->
<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 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">
<%- include('_components', {component: 'text', cssClass: 'w-150 br-15', placeholder: '0.0.0.0', label: 'Webserver IP', id: 'webserver-webserverIp'}) %>
<%- include('_components', {component: 'text', cssClass: 'w-100 br-15', placeholder: '8080', label: 'Webserver port', id: 'webserver-webserverPort'}) %><br>
</div>
</div>
<!-- BASIC SETTINGS END -->
<!-- TUNER SETTINGS -->
<div id="step2" class="step" style="display: none">
<h2 class="settings-heading">Tuner settings</h2>
<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" style="max-width: 520px; margin: 10px auto 0;">
<%- include('_components', { component: 'dropdown', id: 'device-selector', inputId: 'device', label: 'Device', cssClass: '', placeholder: 'TEF668x / TEA685x',
options: tunerProfiles.map(profile => ({
value: profile.id,
label: profile.label
}))
}) %><br>
</div>
<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="xdrd-wirelessConnection" type="checkbox" aria-label="Tuner connection type"/>
<a></a>
<span>
<span class="left-span">Direct</span>
<span class="right-span">TCP/IP</span>
</span>
</label>
</div>
<div id="tuner-usb" class="top-25">
<p>It's time to choose your serial port.</p>
<div class="panel-100 no-bg flex-center">
<%- include('_components', {
component: 'dropdown',
id: 'deviceList',
inputId: 'xdrd-comPort',
label: 'Serial port',
cssClass: '',
placeholder: 'Choose your serial port',
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">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">
<%- include('_components', {component: 'text', cssClass: 'w-150 br-15', label: 'xdrd IP address', id: 'xdrd-xdrdIp'}) %>
<%- include('_components', {component: 'text', cssClass: 'w-100 br-15', label: 'xdrd port', id: 'xdrd-xdrdPort'}) %>
<%- include('_components', {component: 'text', cssClass: 'w-150 br-15', 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 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 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}`
}))
]
}) %>
<%- 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' }
]
}) %>
<%- 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 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 br-15" type="text" name="identification-tunerName" id="identification-tunerName" placeholder="Fill your server name here." maxlength="32">
<br>
<label for="identification-tunerDesc" style="width: 100%;max-width: 768px; margin: auto;">Webserver description:</label>
<textarea id="identification-tunerDesc" name="webserver-desc" class="br-15" 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 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">
<%- include('_components', {component: 'text', cssClass: 'w-250 br-15', placeholder: '', label: 'Latitude', id: 'identification-lat'}) %>
<%- include('_components', {component: 'text', cssClass: 'w-250 br-15', placeholder: '', label: 'Longitude', id: 'identification-lon'}) %>
</div>
<div id="map"></div>
<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>
<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>
<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: 'br-15', label: 'Broadcast address (if using a proxy)', id: 'identification-proxyIp'}) %>
</div>
</div>
</div>
<!-- IDENTIFICATION END -->
<!-- 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 wizard.</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">
<%- include('_components', {component: 'text', cssClass: 'w-150 br-15', placeholder: '', label: 'Tune password', id: 'password-tunePass', password: true}) %>
<%- include('_components', {component: 'text', cssClass: 'w-150 br-15', placeholder: '', label: 'Admin password', id: 'password-adminPass', password: true}) %><br>
</div>
<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>
<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>
</div>
</div>
</div>
<script src="js/settings.js"></script>
<script src="js/dropdown.js"></script>
<script src="js/toast.js"></script>
<script src="js/setup.js"></script>
<script src="js/wizard.js"></script>
<script src="js/confighandler.js"></script>
</body>
</html>
-133
View File
@@ -1,133 +0,0 @@
let configData = {}; // Store the original data structure globally
$(document).ready(function() {
fetchConfig();
});
function submitConfig() {
updateConfigData(configData);
if ($("#password-adminPass").val().length < 1) {
alert('You need to fill in the admin password before continuing further.');
return;
}
$.ajax({
url: './saveData',
type: 'POST',
contentType: 'application/json',
data: JSON.stringify(configData),
success: function (message) {
sendToast('success', 'Data saved!', message, true, true);
setTimeout(function () {
location.reload(true);
}, 1500);
},
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 populateFields(data, prefix = "") {
$.each(data, (key, value) => {
if (value === null) value = ""; // Convert null to an empty string
let id = `${prefix}${prefix ? "-" : ""}${key}`;
const $element = $(`#${id}`);
if (key === "plugins" && $element.is('select[multiple]')) {
if (Array.isArray(value)) {
$element.find('option').each(function() {
const $option = $(this);
const dataName = $option.data('name');
if (value.includes(dataName)) $option.prop('selected', true);
else $option.prop('selected', false);
});
$element.trigger('change');
}
return;
}
if (typeof value === "object" && value !== null) {
if (Array.isArray(value)) {
value.forEach((item, index) => {
const arrayId = `${id}-${index + 1}`;
const $arrayElement = $(`#${arrayId}`);
if ($arrayElement.length) $arrayElement.val(item);
else console.log(`Element with id ${arrayId} not found`);
});
return;
} else {
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 safeId(str) {
return str.replace(/[^a-zA-Z0-9_-]/g, "_");
}
function updateConfigData(data, prefix = "") {
$.each(data, (key, value) => {
let id = `${prefix}${prefix ? "-" : ""}${safeId(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() || null); // Allow null if necessary
index++;
} else break;
}
return;
}
if (key === "plugins") {
data[key] = [];
const $selectedOptions = $element.find('option:selected');
$selectedOptions.each(function() {
const dataName = $(this).attr('data-name');
if (dataName) data[key].push(dataName);
});
return;
}
if (typeof value === "object" && value !== null && !Array.isArray(value)) return updateConfigData(value, id);
if ($element.length) {
const newValue = $element.attr("data-value") ?? $element.val() ?? null;
data[key] = typeof value === "boolean" ? $element.is(":checked") : newValue;
}
});
}
-232
View File
@@ -4,214 +4,9 @@ var tilesURL=' https://tile.openstreetmap.org/{z}/{x}/{y}.png';
var mapAttrib='&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>';
$(document).ready(function() {
mapCreate();
loadConsoleLogs();
showPanelFromHash();
initNav();
initBanlist();
checkTunnelServers();
setInterval(checkTunnelServers, 10000);
});
function mapCreate() {
if (!(typeof map == "object")) {
map = L.map('map', {
center: [40, 0],
zoom: 3,
worldCopyJump: true
});
} else map.setZoom(3).panTo([40, 0]);
L.tileLayer(tilesURL, {
attribution: mapAttrib,
maxZoom: 19
}).addTo(map);
// Check for initial lat/lon values
const latVal = parseFloat($('#identification-lat').val());
const lonVal = parseFloat($('#identification-lon').val());
if (!isNaN(latVal) && !isNaN(lonVal)) {
const initialLatLng = L.latLng(latVal, lonVal);
pin = L.marker(initialLatLng, { riseOnHover: true, draggable: true }).addTo(map);
map.setView(initialLatLng, 8); // Optional: Zoom in closer to the pin
pin.on('dragend', function(ev) {
$('#identification-lat').val(ev.target.getLatLng().lat.toFixed(6));
$('#identification-lon').val(ev.target.getLatLng().lng.toFixed(6));
});
}
map.on('click', function(ev) {
$('#identification-lat').val(ev.latlng.lat.toFixed(6));
$('#identification-lon').val(ev.latlng.lng.toFixed(6));
if (typeof pin == "object") {
pin.setLatLng(ev.latlng.wrap());
} else {
pin = L.marker(ev.latlng.wrap(), { riseOnHover: true, draggable: true }).addTo(map);
pin.on('dragend', function(ev) {
$('#identification-lat').val(ev.target.getLatLng().lat.toFixed(6));
$('#identification-lon').val(ev.target.getLatLng().lng.toFixed(6));
});
}
});
mapReload();
}
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 initBanlist() {
$('.banlist-add').on('click', function(e) {
e.preventDefault();
const ipAddress = $('#banlist-add-ip').val();
const reason = $('#banlist-add-reason').val();
$.ajax({
url: '/addToBanlist',
method: 'GET',
data: { ip: ipAddress, reason: reason },
success: function(response) {
// Refresh the page if the request was successful
if (response.success) location.reload();
else console.error('Failed to add to banlist');
},
error: function() {
console.error('Error occurred during the request');
}
});
});
$('.banlist-remove').on('click', function(e) {
e.preventDefault();
const ipAddress = $(this).closest('tr').find('td').first().text();
$.ajax({
url: '/removeFromBanlist',
method: 'GET',
data: { ip: ipAddress },
success: function(response) {
if (response.success) {
location.reload();
} else {
console.error('Failed to remove from banlist');
}
},
error: function() {
console.error('Error occurred during the request');
}
});
});
}
function toggleNav() {
const navOpen = $("#navigation").css('margin-left') === '0px';
const isMobile = window.innerWidth <= 768;
if (navOpen) {
if (isMobile) {
// Do nothing to .admin-wrapper on mobile (since we're overlaying)
$(".admin-wrapper").css({
'margin-left': '32px',
'width': '100%' // Reset content to full width on close
});
$("#navigation").css('margin-left', 'calc(64px - 100vw)');
} else {
// On desktop, adjust the content margin and width
$(".admin-wrapper").css({
'margin-left': '64px',
'width': 'calc(100% - 64px)'
});
$("#navigation").css('margin-left', '-356px');
}
$(".sidenav-close").html('<i class="fa-solid fa-chevron-right"></i>');
} else {
$("#navigation").css('margin-left', '0');
if (isMobile) {
$(".admin-wrapper").css({
'margin-left': '0', // Keep content in place when sidenav is open
'width': '100%' // Keep content at full width
});
} else {
// On desktop, push the content
$(".admin-wrapper").css({
'margin-left': '420px',
'width': 'calc(100% - 420px)'
});
}
$(".sidenav-close").html('<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, '');
}
@@ -247,30 +42,3 @@ async function loadConsoleLogs() {
});
$("#console-output").length ? $("#console-output").scrollTop($("#console-output")[0].scrollHeight) : null;
}
function checkTunnelServers() {
$.ajax({
url: '/tunnelservers',
method: 'GET',
success: function(servers) {
const $options = $('#tunnel-regionselect ul.options');
const $input = $('#tunnel-region');
const selectedValue = $input.val(); // currently selected value (label or value?)
servers.forEach(server => {
const $li = $options.find(`li[data-value="${server.value}"]`);
if ($li.length) {
$li.text(server.label);
// If this li is the currently selected one, update input text too
// Note: input.val() holds the label, so match by label is safer
if ($li.text() === selectedValue || server.value === selectedValue) $input.val(server.label);
}
});
},
error: function() {
console.error('Failed to load server latency data');
}
});
}
-2
View File
@@ -15,8 +15,6 @@ function updateWizardContent() {
$('.btn-prev').toggle(visibleStepIndex !== 0);
$('.btn-next').text(visibleStepIndex === 4 ? 'Save' : 'Next');
visibleStepIndex === 3 && mapReload(); // What is this? Javascript?
}
function navigateStep(isNext) {