mirror of
https://github.com/KubaPro010/fm-dx-webserver.git
synced 2026-07-31 09:19:16 +02:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67d70eef43
|
||
|
|
f84567425b
|
||
|
|
dc8ece6034
|
||
|
|
84934b044d
|
+1
-52
@@ -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() {
|
||||
|
||||
@@ -125,7 +125,7 @@ let serverConfig = {
|
||||
antennaNoUsersDelay: false,
|
||||
trustedProxies: [],
|
||||
webRtc: {
|
||||
flags: {disable3las: false},
|
||||
flags: {disablews: false},
|
||||
audio: {}
|
||||
}
|
||||
};
|
||||
|
||||
+2
-624
@@ -13,62 +13,8 @@
|
||||
</head>
|
||||
<body>
|
||||
<div id="toast-container"></div>
|
||||
<div class="wrapper-outer wrapper-full wrapper-outer-static">
|
||||
<div id="navigation" class="sidenav flex-container flex-phone">
|
||||
<div class="sidenav-content">
|
||||
<h1 class="top-25">Settings</h1>
|
||||
<ul class="nav" role="tablist" style="border-radius: 15px 15px 0 0;">
|
||||
<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="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="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="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="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="tab" data-panel="plugins-tab" tabindex="0">
|
||||
<a href="#" role="tab" tabindex="-1" aria-controls="plugins-tab"><i class="fa-solid fa-fw fa-puzzle-piece"></i> Plugins</a>
|
||||
</li>
|
||||
<li role="tab" data-panel="users" tabindex="0">
|
||||
<a href="#" role="tab" tabindex="-1" aria-controls="users"><i class="fa-solid fa-fw fa-user"></i> User management</a>
|
||||
</li>
|
||||
<li role="tab" data-panel="startup" tabindex="0">
|
||||
<a href="#" role="tab" tabindex="-1" aria-controls="startup"><i class="fa-solid fa-fw fa-plug"></i> Startup</a>
|
||||
</li>
|
||||
<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>
|
||||
|
||||
<div class="admin-quick-dashboard top-25">
|
||||
<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="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">
|
||||
<i class="fa-solid fa-sign-out"></i>
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="panel-100-real m-0 p-10 no-bg">
|
||||
<p class="color-4">Feel free to contact us on <a href="https://discord.gg/ZAVNdS74mC" target="_blank"><strong class="color-5"><i class="fa-brands fa-discord"></i> Discord</strong></a> for community support.</p>
|
||||
<a href="https://github.com/noobishsvk/fm-dx-webserver" target="_blank" class="text-small">Version: <span class="version-string color-4"></span></a>
|
||||
</div>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="sidenav-close text-medium-big hover-brighten flex-center color-4 modal-panel-sidebar" role="button" onclick="toggleNav()" aria-label="Toggle navigation" tabindex="0"><i class="fa-solid fa-chevron-left"></i></div>
|
||||
</div>
|
||||
<div id="wrapper" class="setup-wrapper admin-wrapper panel-full">
|
||||
<div class="panel-full" style="margin-top: -20px; border-radius: 0; padding-left: 20px;padding-right: 20px;">
|
||||
<div class="panel-full tab-content no-bg m-0" id="dashboard" role="tabpanel">
|
||||
<div id="wrapper" class="setup-wrapper admin-wrapper">
|
||||
<div class="m-0 no-bg">
|
||||
<h2>Dashboard</h2>
|
||||
|
||||
<div class="flex-container">
|
||||
@@ -127,19 +73,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-container">
|
||||
<div class="panel-100-real p-bottom-20">
|
||||
<h3>Quick settings</h3>
|
||||
<div class="flex-container flex-center" style="margin: 30px;">
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Unlocked Tuner', id: 'publicTuner'}) %>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Admin lock', id: 'lockToAdmin'}) %><br>
|
||||
</div>
|
||||
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-150 br-15', placeholder: '', label: 'Tune password', id: 'password-tunePass', password: true}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-150 br-15', placeholder: '', label: 'Admin password', id: 'password-adminPass', password: true}) %><br>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-container">
|
||||
<div class="panel-100-real p-bottom-20 bottom-20">
|
||||
<h3>Console</h3>
|
||||
@@ -155,551 +88,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-full tab-content no-bg m-0" id="audio" role="tabpanel">
|
||||
<h2>Audio settings</h2>
|
||||
|
||||
<div class="flex-container contains-dropdown">
|
||||
<div class="panel-33 p-bottom-20">
|
||||
<h3>Device</h3>
|
||||
<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>
|
||||
<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>
|
||||
<p>The bitrate of the mp3 audio.<br>
|
||||
<span class="text-gray">Minimum: 64 Kbps • Maximum: 320 Kbps</span>
|
||||
</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>
|
||||
|
||||
<div class="panel-full m-0 tab-content no-bg" id="webserver" role="tabpanel">
|
||||
<h2>Webserver settings</h2>
|
||||
<div class="flex-container contains-dropdown">
|
||||
<div class="panel-50 p-bottom-20" style="padding-left: 20px; padding-right: 20px;">
|
||||
<h3>Connection</h3>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-150 br-15', placeholder: '0.0.0.0', label: 'Webserver IP', id: 'webserver-webserverIp'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100 br-15', placeholder: '8080', label: 'Webserver port', id: 'webserver-webserverPort'}) %><br>
|
||||
</div>
|
||||
<div class="panel-50 p-bottom-20">
|
||||
<h3>Design</h3>
|
||||
<h4>Background image</h4>
|
||||
<%- include('_components', {component: 'text', cssClass: 'br-15', placeholder: 'Direct image link', label: 'Image link', id: 'webserver-bgImage'}) %><br>
|
||||
|
||||
<h4 class="top-25">Themes</h4>
|
||||
<%- include('_components', { component: 'dropdown', id: 'server-theme-selector', inputId: 'webserver-defaultTheme', label: 'Default server theme', cssClass: '', placeholder: 'Default',
|
||||
options: [
|
||||
{ value: 'theme1', label: 'Mint' },
|
||||
{ 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>
|
||||
<div class="flex-container">
|
||||
<div class="panel-100-real p-bottom-20">
|
||||
<h3>Antennas</h3>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: 'bottom-20', label: 'Antenna switch', id: 'antennas-enabled'}) %><br>
|
||||
|
||||
<div class="flex-container flex-center p-20">
|
||||
<div class="flex-container flex-phone flex-column bottom-20" style="margin-left: 15px; margin-right: 15px;">
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Antenna 1', id: 'antennas-ant1-enabled'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100 br-15', placeholder: 'Ant A', label: 'Antenna 1 name', id: 'antennas-ant1-name'}) %><br>
|
||||
</div>
|
||||
|
||||
<div class="flex-container flex-phone flex-column bottom-20" style="margin-left: 15px; margin-right: 15px;">
|
||||
<%- include('_components', {component: 'checkbox', cssClass: 'top-25', label: 'Antenna 2', id: 'antennas-ant2-enabled'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100 br-15', placeholder: 'Ant B', label: 'Antenna 2 name', id: 'antennas-ant2-name'}) %><br>
|
||||
</div>
|
||||
|
||||
<div class="flex-container flex-phone flex-column bottom-20" style="margin-left: 15px; margin-right: 15px;">
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Antenna 3', id: 'antennas-ant3-enabled'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100 br-15', placeholder: 'Ant C', label: 'Antenna 3 name', id: 'antennas-ant3-name'}) %><br>
|
||||
</div>
|
||||
|
||||
<div class="flex-container flex-phone flex-column bottom-20" style="margin-left: 15px; margin-right: 15px;">
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Antenna 4', id: 'antennas-ant4-enabled'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100 br-15', placeholder: 'Ant D', label: 'Antenna 4 name', id: 'antennas-ant4-name'}) %><br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-container">
|
||||
<div class="panel-50" style="padding-left: 20px; padding-right: 20px;">
|
||||
<h3>Tuning options</h3>
|
||||
<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>
|
||||
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Limit tuning', id: 'webserver-tuningLimit'}) %><br>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100 br-15', placeholder: '0', label: 'Lower limit', id: 'webserver-tuningLowerLimit'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100 br-15', 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>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100 br-15', placeholder: '87.5', label: 'Preset 1', id: 'webserver-presets-1'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100 br-15', placeholder: '87.5', label: 'Preset 2', id: 'webserver-presets-2'}) %>
|
||||
<br>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100 br-15', placeholder: '87.5', label: 'Preset 3', id: 'webserver-presets-3'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100 br-15', placeholder: '87.5', label: 'Preset 4', id: 'webserver-presets-4'}) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-container p-bottom-20">
|
||||
<div class="panel-50 p-bottom-20" style="padding-left: 20px; padding-right: 20px;">
|
||||
<h3>RDS Mode</h3>
|
||||
<p>You can switch between American (RBDS) / Global (RDS) mode here.</p>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: 'bottom-20', iconClass: '', label: 'American RDS mode (RBDS)', id: 'webserver-rdsMode'}) %>
|
||||
<h3>RDS Timeout</h3>
|
||||
<p>If no data is received, RDS will be automatically cleared after a timeout.<br>
|
||||
<span class="text-gray">Enter timeout in seconds or 0 to disable.</span><br></p>
|
||||
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100', placeholder: '0', label: 'RDS Timeout', id: 'webserver-rdsTimeout'}) %>
|
||||
</div>
|
||||
<div class="panel-50 p-bottom-20" style="padding-left: 20px; padding-right: 20px; padding-bottom: 80px;">
|
||||
<h3>Transmitter Search Algorithm</h3>
|
||||
<p>Different modes may help with more accurate transmitter identification depending on your region.</p>
|
||||
<%- include('_components', { component: 'dropdown', id: 'server-tx-id-algo', inputId: 'webserver-txIdAlgorithm', label: 'Transmitter ID Algorithm', cssClass: '', placeholder: 'Algorithm 1',
|
||||
options: [
|
||||
{ value: '0', label: 'Algorithm 1' },
|
||||
{ value: '1', label: 'Algorithm 2' },
|
||||
{ value: '2', label: 'Algorithm 3' },
|
||||
]
|
||||
}) %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="panel-full m-0 tab-content no-bg" id="tuner" role="tabpanel">
|
||||
<h2>Tuner settings</h2>
|
||||
<div class="panel-100 p-bottom-20 contains-dropdown" style="z-index: 991;">
|
||||
<h3>Device type</h3>
|
||||
<div class="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>
|
||||
|
||||
<div class="flex-container contains-dropdown">
|
||||
<div class="panel-100 p-bottom-20" style="padding-right: 20px; padding-left: 20px;">
|
||||
<h3>Connection type</h3>
|
||||
<p class="text-gray">If you want to choose the serial 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" style="max-width: 350px;">
|
||||
<label class="toggleSwitch nolabel" onclick="">
|
||||
<input id="xdrd-wirelessConnection" type="checkbox" tabindex="0" aria-label="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">
|
||||
<p class="text-gray">Choose your desired <strong>serial port</strong><br> </p>
|
||||
<%- 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 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>
|
||||
<%- 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>
|
||||
<div class="flex-container">
|
||||
<div class="panel-50 p-bottom-20">
|
||||
<h3>Startup</h3>
|
||||
<h4>Startup volume</h4>
|
||||
<div class="panel-75 auto" style="height: 48px;">
|
||||
<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>
|
||||
<%- 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>
|
||||
<div class="flex-container">
|
||||
<div class="panel-33 no-bg">
|
||||
<h4>Bandwidth switch</h4>
|
||||
<p>Bandwidth switch allows the user to set the bandwidth manually.</p>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Bandwidth switch', id: 'bwSwitch'}) %><br>
|
||||
</div>
|
||||
<div class="panel-33 no-bg">
|
||||
<h4>Automatic shutdown</h4>
|
||||
<p>Toggling this option will put the tuner to sleep when no clients are connected.</p>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Auto-shutdown', id: 'autoShutdown'}) %><br>
|
||||
</div>
|
||||
<div class="panel-33 no-bg">
|
||||
<h4>SI47XX AGC control</h4>
|
||||
<p>Allow users to change SI47XX AGC mode from the main UI.</p>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Enable AGC control', id: 'si47xx-agcControl'}) %><br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-full m-0 tab-content no-bg" id="plugins-tab" role="tabpanel">
|
||||
<h2>Plugins</h2>
|
||||
<div class="panel-100 p-bottom-20">
|
||||
<h3>Plugin list</h3>
|
||||
<p>Any compatible <strong>.js</strong> plugin, which is in the "plugins" folder, will be listed here.<br>
|
||||
Click on the individual plugins to enable/disable them.</p>
|
||||
<select id="plugins" class="multiselect" multiple>
|
||||
<% plugins.forEach(function(plugin) {
|
||||
if (plugin.frontEndPath) { %>
|
||||
<option data-name="<%= plugin.prefix %><%= plugin.frontEndPath %>" title="<%= plugin.name %> by <%= plugin.author %> [v<%= plugin.version %>]">
|
||||
<%= plugin.name %> by <%= plugin.author %> [v<%= plugin.version %>]
|
||||
</option>
|
||||
<% }
|
||||
}); %>
|
||||
</select><br><br>
|
||||
<a href="https://github.com/NoobishSVK/fm-dx-webserver/wiki/Plugin-List" target="_blank">Download new plugins here!</a>
|
||||
</div>
|
||||
|
||||
<div class="panel-100 p-bottom-20">
|
||||
<h3>Plugin settings</h3>
|
||||
<div id="plugin-settings">No plugin settings are available.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-full m-0 tab-content no-bg" id="identification" role="tabpanel">
|
||||
<h2>Identification & Map</h2>
|
||||
|
||||
<div class="flex-container">
|
||||
<div class="panel-50 p-bottom-20">
|
||||
<h3>Basic info</h3>
|
||||
|
||||
<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="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" class="br-15" 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>
|
||||
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Broadcast to map', id: 'identification-broadcastTuner'}) %><br>
|
||||
<%- include('_components', {component: 'text', cssClass: 'br-15', placeholder: 'Your e-mail or Discord...', label: 'Owner contact', id: 'identification-contact'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'br-15', label: 'Proxy address', 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>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-100">
|
||||
<h3>Location</h3>
|
||||
<p class="text-gray">Location info is useful for automatic identification of stations using RDS.</p>
|
||||
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-150 br-15', placeholder: '', label: 'Latitude', id: 'identification-lat'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-150 br-15', placeholder: '', label: 'Longitude', id: 'identification-lon'}) %>
|
||||
|
||||
<div id="map"></div>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-full m-0 tab-content no-bg" id="users" role="tabpanel">
|
||||
<h2>User management</h2>
|
||||
<div class="panel-100">
|
||||
<h3>Chat options</h3>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Chat', id: 'webserver-chatEnabled'}) %>
|
||||
</div>
|
||||
|
||||
<div class="panel-100 p-bottom-20">
|
||||
<h3>Banlist</h3>
|
||||
<p>If you have users that don't behave on your server, you can choose to ban them by their IP address.<br>
|
||||
<span class="text-gray">You can see their IP address by hovering over their nickname. One IP per row.</span></p>
|
||||
|
||||
<table class="table-big">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>IP Address</th>
|
||||
<th>Location</th>
|
||||
<th>Ban date</th>
|
||||
<th>Reason</th>
|
||||
<th class="color-5"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><%- include('_components', {component: 'text', cssClass: 'w-100 br-15', placeholder: 'IP address', label: '', id: 'banlist-add-ip'}) %></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td><%- include('_components', {component: 'text', cssClass: 'w-150 br-15', placeholder: 'Ban reason (note)', label: '', id: 'banlist-add-reason'}) %></td>
|
||||
<td class="color-5">
|
||||
<a href="#" id="banlist-add-link"><i class="fa-solid fa-plus-circle banlist-add"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
<% if (banlist.length > 0) { %>
|
||||
<% banlist.forEach(bannedUser => { %>
|
||||
<tr>
|
||||
<% if (Array.isArray(bannedUser)) { %>
|
||||
<!-- If it's an array, use its values -->
|
||||
<td style="text-align: center !important;"><a href="https://ipinfo.io/<%= bannedUser[0] %>" target="_blank"><%= bannedUser[0] %></a></td>
|
||||
<td><%= bannedUser[1] %></td>
|
||||
<td class="text-bold"><%= new Date(parseInt(bannedUser[2])).toLocaleString() %></td> <!-- Assuming the ban date is a timestamp in seconds -->
|
||||
<td><%= bannedUser[3] %></td>
|
||||
<% } else { %>
|
||||
<!-- If it's just an IP address without additional data, show it as is -->
|
||||
<td style="text-align: center !important;"><a href="https://ipinfo.io/<%= bannedUser %>" target="_blank"><%= bannedUser %></a></td>
|
||||
<td>Unknown</td>
|
||||
<td class="text-bold">Unknown</td>
|
||||
<td>Unknown</td>
|
||||
<% } %>
|
||||
<td><a href="#" class="banlist-remove"><i class="fa-solid fa-lock-open text-gray"></i></a></td>
|
||||
</tr>
|
||||
<% }); %>
|
||||
<% } else { %>
|
||||
<tr>
|
||||
<td colspan="5" style="text-align: center">The banlist is empty.</td>
|
||||
</tr>
|
||||
<% } %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-full m-0 tab-content no-bg" id="startup" role="tabpanel">
|
||||
<h2>Startup settings</h2>
|
||||
<div class="flex-container">
|
||||
<div class="panel-100-real p-bottom-20" style="z-index: 10; padding-bottom: 120px;">
|
||||
<h3>On startup</h3>
|
||||
<p>These settings will be applied after a server launch or restart.</p>
|
||||
<div class="flex-container flex-center p-20">
|
||||
<% if (device === 'tef') { %>
|
||||
<%- include('_components', { component: 'dropdown', id: 'ceqStartup-dropdown', inputId: 'ceqStartup', label: 'cEQ', cssClass: '', placeholder: 'Disabled',
|
||||
options: [
|
||||
{ value: '0', label: 'Disabled' },
|
||||
{ value: '1', label: 'Enabled' },
|
||||
]
|
||||
}) %><br>
|
||||
<%- include('_components', { component: 'dropdown', id: 'imsStartup-dropdown', inputId: 'imsStartup', label: 'iMS', cssClass: '', placeholder: 'Disabled',
|
||||
options: [
|
||||
{ value: '0', label: 'Disabled' },
|
||||
{ value: '1', label: 'Enabled' },
|
||||
]
|
||||
}) %><br>
|
||||
<% } else if (device === 'xdr') { %>
|
||||
<%- include('_components', { component: 'dropdown', id: 'rfStartup-dropdown', inputId: 'ceqStartup', label: 'RF+', cssClass: '', placeholder: 'Disabled',
|
||||
options: [
|
||||
{ value: '0', label: 'Disabled' },
|
||||
{ value: '1', label: 'Enabled' },
|
||||
]
|
||||
}) %><br>
|
||||
<%- include('_components', { component: 'dropdown', id: 'ifStartup-dropdown', inputId: 'imsStartup', label: 'IF+', cssClass: '', placeholder: 'Disabled',
|
||||
options: [
|
||||
{ value: '0', label: 'Disabled' },
|
||||
{ value: '1', label: 'Enabled' },
|
||||
]
|
||||
}) %><br>
|
||||
<% } %>
|
||||
<%- include('_components', { component: 'dropdown', id: 'stereoStartup-dropdown', inputId: 'stereoStartup', label: 'Stereo Mode', cssClass: '', placeholder: 'Stereo (Default)',
|
||||
options: [
|
||||
{ value: '0', label: 'Stereo (Default)' },
|
||||
{ value: '1', label: 'Mono' },
|
||||
]
|
||||
}) %><br>
|
||||
</div>
|
||||
<div class="panel-100-real p-bottom-20 no-bg">
|
||||
<%- include('_components', { component: 'dropdown', id: 'antennaStartup-dropdown', inputId: 'antennaStartup', label: 'Antenna', cssClass: '', placeholder: 'Antenna 0 (Default)',
|
||||
options: [
|
||||
{ value: '0', label: 'Antenna 0 (Default)' },
|
||||
{ value: '1', label: 'Antenna 1' },
|
||||
{ value: '2', label: 'Antenna 2' },
|
||||
{ value: '3', label: 'Antenna 3' },
|
||||
]
|
||||
}) %><br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
<div class="panel-100-real p-bottom-20 bottom-20" style="z-index: 9; padding-bottom: 180px;">
|
||||
<h3>Empty server defaults</h3>
|
||||
<p>These settings will apply once the last user disconnects from the server, so the server can be ready for a new user with default settings.</p>
|
||||
<div class="flex-container flex-center p-20">
|
||||
<%- include('_components', { component: 'dropdown', id: 'bwAutoNoUsers-dropdown', inputId: 'bwAutoNoUsers', label: 'Auto BW', cssClass: '', placeholder: 'Unchanged',
|
||||
options: [
|
||||
{ value: '0', label: 'Unchanged' },
|
||||
{ value: '1', label: 'Enabled' },
|
||||
]
|
||||
}) %><br>
|
||||
<% if (device === 'tef') { %>
|
||||
<%- include('_components', { component: 'dropdown', id: 'ceqNoUsers-dropdown', inputId: 'ceqNoUsers', label: 'cEQ', cssClass: '', placeholder: 'Unchanged',
|
||||
options: [
|
||||
{ value: '0', label: 'Unchanged' },
|
||||
{ value: '1', label: 'Disabled' },
|
||||
{ value: '2', label: 'Enabled' },
|
||||
]
|
||||
}) %><br>
|
||||
<%- include('_components', { component: 'dropdown', id: 'imsNoUsers-dropdown', inputId: 'imsNoUsers', label: 'iMS', cssClass: '', placeholder: 'Unchanged',
|
||||
options: [
|
||||
{ value: '0', label: 'Unchanged' },
|
||||
{ value: '1', label: 'Disabled' },
|
||||
{ value: '2', label: 'Enabled' },
|
||||
]
|
||||
}) %><br>
|
||||
<% } else if (device === 'xdr') { %>
|
||||
<%- include('_components', { component: 'dropdown', id: 'rfNoUsers-dropdown', inputId: 'ceqNoUsers', label: 'RF+', cssClass: '', placeholder: 'Unchanged',
|
||||
options: [
|
||||
{ value: '0', label: 'Unchanged' },
|
||||
{ value: '1', label: 'Disabled' },
|
||||
{ value: '2', label: 'Enabled' },
|
||||
]
|
||||
}) %><br>
|
||||
<%- include('_components', { component: 'dropdown', id: 'ifNoUsers-dropdown', inputId: 'imsNoUsers', label: 'IF+', cssClass: '', placeholder: 'Unchanged',
|
||||
options: [
|
||||
{ value: '0', label: 'Unchanged' },
|
||||
{ value: '1', label: 'Disabled' },
|
||||
{ value: '2', label: 'Enabled' },
|
||||
]
|
||||
}) %><br>
|
||||
<% } %>
|
||||
<%- include('_components', { component: 'dropdown', id: 'stereoNoUsers-dropdown', inputId: 'stereoNoUsers', label: 'Stereo Mode', cssClass: '', placeholder: 'Unchanged',
|
||||
options: [
|
||||
{ value: '0', label: 'Unchanged' },
|
||||
{ value: '1', label: 'Stereo' },
|
||||
{ value: '2', label: 'Mono' },
|
||||
]
|
||||
}) %><br>
|
||||
</div>
|
||||
<div class="panel-100-real p-bottom-20 no-bg">
|
||||
<%- include('_components', {component: 'checkbox', cssClass: '', label: 'Delayed Antenna Change', id: 'antennaNoUsersDelay'}) %><br>
|
||||
<%- include('_components', { component: 'dropdown', id: 'antennaNoUsers-dropdown', inputId: 'antennaNoUsers', label: 'Antenna', cssClass: '', placeholder: 'Unchanged',
|
||||
options: [
|
||||
{ value: '0', label: 'Unchanged' },
|
||||
{ value: '1', label: 'Antenna 0' },
|
||||
{ value: '2', label: 'Antenna 1' },
|
||||
{ value: '3', label: 'Antenna 2' },
|
||||
{ value: '4', label: 'Antenna 3' },
|
||||
]
|
||||
}) %>
|
||||
<br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel-full m-0 tab-content no-bg" id="extras" role="tabpanel">
|
||||
<h2>Extras</h2>
|
||||
<div class="panel-100 p-bottom-20">
|
||||
<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>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: 'm-right-10', label: 'FMLIST integration', id: 'extras-fmlistIntegration'}) %><br>
|
||||
<p>If you don't feel comfortable with the general public logging on your server, you can make this feature available only for people with a password</p>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: 'm-right-10', label: 'Admin-only logging', id: 'extras-fmlistAdminOnly'}) %><br>
|
||||
|
||||
<p>You can also fill in your OMID from FMLIST.org, if you want the logs to be saved to your account.</p>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-100 br-15', placeholder: '', label: 'OMID', id: 'extras-fmlistOmid'}) %>
|
||||
</div>
|
||||
<div class="panel-100 p-bottom-20">
|
||||
<h3>Tunnel</h3>
|
||||
<p>When you become an <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 & port forwarding.<br>
|
||||
When you become a supporter, you can message the Founders on Discord for your login details.</p><br>
|
||||
<p>You can also get an tunnel from kuba201 discord, one of the contributors of this version of the application.</p>
|
||||
<h4>Main tunnel settings</h4>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: 'm-right-10', label: 'Enable tunnel', id: 'tunnel-enabled'}) %><br>
|
||||
<%- include('_components', { component: 'dropdown', id: 'tunnel-regionSelect', inputId: 'tunnel-region', label: 'Official server region', cssClass: '', placeholder: 'Europe',
|
||||
options: [
|
||||
{ value: 'eu', label: 'Europe' },
|
||||
{ value: 'us', label: 'Americas' },
|
||||
{ value: 'sg', label: 'Asia & Oceania' },
|
||||
]
|
||||
}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-150 br-15', placeholder: '', label: 'Username', id: 'tunnel-username'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-250 br-15', password: true, placeholder: '', label: 'Token', id: 'tunnel-token'}) %>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-150 br-15', placeholder: '', label: 'Subdomain name', id: 'tunnel-subdomain'}) %><br>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: 'm-right-10', label: 'Low latency mode', id: 'tunnel-lowLatencyMode'}) %><br>
|
||||
<p>Enabling low latency mode may provide better experience, however it will also use more bandwidth.</p>
|
||||
|
||||
<h4 class="top-25">Community tunnel settings</h4>
|
||||
<p>You can also self-host or ask other people to provide you a token. In this case, the server owner is responsible for any potential security issues.</p>
|
||||
<%- include('_components', {component: 'checkbox', cssClass: 'm-right-10', label: 'Use a community tunnel', id: 'tunnel-community-enabled'}) %><br>
|
||||
<%- include('_components', {component: 'text', cssClass: 'w-250 br-15', placeholder: '', label: 'Community Tunnel host (IP or hostname)', id: 'tunnel-community-host'}) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="js/ver.js"></script>
|
||||
@@ -711,15 +99,5 @@
|
||||
<% enabledPlugins?.forEach(function(plugin) { %>
|
||||
<script src="js/plugins/<%= plugin %>"></script>
|
||||
<% }); %>
|
||||
|
||||
<script>
|
||||
document.addEventListener('keydown', function(event) {
|
||||
if (event.ctrlKey && event.key === 's') {
|
||||
event.preventDefault();
|
||||
if (event.repeat) return;
|
||||
submitConfig();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
});
|
||||
}
|
||||
+8
-7
@@ -3,6 +3,7 @@
|
||||
* FM-DX Webserver WHEP Audio Plugin
|
||||
* ************************************************
|
||||
* Created by techkrzysiek
|
||||
* Stolen by me (thanks man)
|
||||
* ************************************************
|
||||
*/
|
||||
|
||||
@@ -152,7 +153,7 @@ window.addEventListener('load', (() => {
|
||||
}
|
||||
}`;
|
||||
|
||||
let disable3las = false;
|
||||
let disablews = false;
|
||||
let disableMobileSelect = true;
|
||||
|
||||
let peerConnection = null;
|
||||
@@ -231,7 +232,7 @@ window.addEventListener('load', (() => {
|
||||
return;
|
||||
}
|
||||
const flags = await response.json();
|
||||
disable3las = Boolean(flags.disable3las);
|
||||
disablews = Boolean(flags.disablews);
|
||||
} catch (error) {
|
||||
console.warn('[WHEP] Failed to fetch flags:', error);
|
||||
}
|
||||
@@ -243,8 +244,8 @@ window.addEventListener('load', (() => {
|
||||
|
||||
function getCookieDomainAttribute() {
|
||||
const hostname = String(window.location.hostname || '').toLowerCase();
|
||||
if (hostname === 'fmdx.pro' || hostname.endsWith('.fmdx.pro')) {
|
||||
return '; domain=.fmdx.pro';
|
||||
if (hostname === 'fmtuner.org' || hostname.endsWith('.fmtuner.org')) {
|
||||
return '; domain=.fmtuner.org';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
@@ -616,7 +617,7 @@ window.addEventListener('load', (() => {
|
||||
|
||||
function getDefaultSourceValue() {
|
||||
const storedValue = normalizeStoredSourceValue(getStoredSource());
|
||||
if (storedValue === LEGACY_SOURCE_VALUE && !disable3las) {
|
||||
if (storedValue === LEGACY_SOURCE_VALUE && !disablews) {
|
||||
return LEGACY_SOURCE_VALUE;
|
||||
}
|
||||
|
||||
@@ -636,7 +637,7 @@ window.addEventListener('load', (() => {
|
||||
const $selects = $(`.fmdx-webrtc-mode-select`);
|
||||
const buttonState = getActivePlaybackBackend() ? 'connected' : 'disconnected';
|
||||
|
||||
if (value === LEGACY_SOURCE_VALUE && !disable3las) {
|
||||
if (value === LEGACY_SOURCE_VALUE && !disablews) {
|
||||
if (syncSelect) {
|
||||
$selects.val(LEGACY_SOURCE_VALUE);
|
||||
}
|
||||
@@ -745,7 +746,7 @@ window.addEventListener('load', (() => {
|
||||
});
|
||||
}
|
||||
|
||||
if (!disable3las) {
|
||||
if (!disablews) {
|
||||
options.push({
|
||||
value: LEGACY_SOURCE_VALUE,
|
||||
label: getLegacyOptionLabel()
|
||||
|
||||
-232
@@ -4,214 +4,9 @@ var tilesURL=' https://tile.openstreetmap.org/{z}/{x}/{y}.png';
|
||||
var mapAttrib='© <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');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user