You've already forked fm-dx-webserver
mirror of
https://github.com/KubaPro010/fm-dx-webserver.git
synced 2026-02-26 22:13:53 +01:00
new admin system, ui changes, bugfixes
This commit is contained in:
@@ -18,7 +18,7 @@ const MESSAGE_PREFIX = {
|
||||
|
||||
// Initialize an array to store logs
|
||||
const logs = [];
|
||||
const maxLogLines = 100;
|
||||
const maxLogLines = 250;
|
||||
|
||||
const logDebug = (...messages) => {
|
||||
if (verboseMode) {
|
||||
|
||||
@@ -210,7 +210,7 @@ var dataToSend = {
|
||||
st: false,
|
||||
st_forced: false,
|
||||
ps: '',
|
||||
tp: false,
|
||||
tp: 0,
|
||||
ta: 0,
|
||||
ms: -1,
|
||||
pty: 0,
|
||||
@@ -231,7 +231,7 @@ var dataToSend = {
|
||||
},
|
||||
country_name: '',
|
||||
country_iso: 'UN',
|
||||
users: '',
|
||||
users: 0,
|
||||
};
|
||||
|
||||
var legacyRdsPiBuffer = null;
|
||||
|
||||
@@ -31,7 +31,7 @@ function send(request) {
|
||||
}
|
||||
else
|
||||
{
|
||||
logInfo("FM-DX Server Map update successful.");
|
||||
logDebug("FM-DX Server Map update successful.");
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -66,6 +66,7 @@ function sendUpdate() {
|
||||
desc: serverConfig.identification.tunerDesc,
|
||||
audioChannels: serverConfig.audio.audioChannels,
|
||||
audioQuality: serverConfig.audio.audioBitrate,
|
||||
contact: serverConfig.identification.contact || ''
|
||||
};
|
||||
|
||||
if (serverConfig.identification.token)
|
||||
|
||||
74
index.js
74
index.js
@@ -11,6 +11,7 @@ const httpProxy = require('http-proxy');
|
||||
const https = require('https');
|
||||
const app = express();
|
||||
const httpServer = http.createServer(app);
|
||||
const process = require("process");
|
||||
|
||||
// Websocket handling
|
||||
const WebSocket = require('ws');
|
||||
@@ -33,7 +34,7 @@ const { logDebug, logError, logInfo, logWarn } = consoleCmd;
|
||||
|
||||
// Create a WebSocket proxy instance
|
||||
const proxy = httpProxy.createProxyServer({
|
||||
target: 'ws://localhost:'+ serverConfig.webserver.audioPort, // WebSocket httpServer's address
|
||||
target: 'ws://localhost:' + (Number(serverConfig.webserver.webserverPort) + 10), // WebSocket httpServer's address
|
||||
ws: true, // Enable WebSocket proxying
|
||||
changeOrigin: true // Change the origin of the host header to the target URL
|
||||
});
|
||||
@@ -98,7 +99,6 @@ function connectToXdrd() {
|
||||
const lines = receivedData.split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
|
||||
if (!authFlags.receivedPassword) {
|
||||
authFlags.receivedSalt = line.trim();
|
||||
authenticateWithXdrd(client, authFlags.receivedSalt, serverConfig.xdrd.xdrdPassword);
|
||||
@@ -225,7 +225,6 @@ app.get('/static_data', (req, res) => {
|
||||
res.json({
|
||||
qthLatitude: serverConfig.identification.lat,
|
||||
qthLongitude: serverConfig.identification.lon,
|
||||
audioPort: serverConfig.webserver.audioPort,
|
||||
streamEnabled: streamEnabled
|
||||
});
|
||||
});
|
||||
@@ -286,8 +285,7 @@ function parseMarkdown(parsed) {
|
||||
var linkRegex = /\[([^\]]+)]\(([^)]+)\)/g;
|
||||
parsed = parsed.replace(linkRegex, '<a href="$2">$1</a>');
|
||||
|
||||
var breakLineRegex = /\\n/g;
|
||||
parsed = parsed.replace(breakLineRegex, '<br>');
|
||||
parsed = parsed.replace(/\n/g, '<br>');
|
||||
|
||||
return parsed;
|
||||
}
|
||||
@@ -307,21 +305,17 @@ function removeMarkdown(parsed) {
|
||||
var linkRegex = /\[([^\]]+)]\(([^)]+)\)/g;
|
||||
parsed = parsed.replace(linkRegex, '$1');
|
||||
|
||||
var breakLineRegex = /\\n/g;
|
||||
parsed = parsed.replace(breakLineRegex, '');
|
||||
|
||||
return parsed;
|
||||
}
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
if (!fs.existsSync(configName + '.json')) {
|
||||
parseAudioDevice((result) => {
|
||||
res.render('setup', {
|
||||
res.render('wizard', {
|
||||
isAdminAuthenticated: true,
|
||||
videoDevices: result.audioDevices,
|
||||
audioDevices: result.videoDevices,
|
||||
consoleOutput: consoleCmd.logs });
|
||||
});;
|
||||
audioDevices: result.videoDevices });
|
||||
});
|
||||
} else {
|
||||
res.render('index', {
|
||||
isAdminAuthenticated: req.session.isAdminAuthenticated,
|
||||
@@ -330,21 +324,65 @@ app.get('/', (req, res) => {
|
||||
tunerDesc: parseMarkdown(serverConfig.identification.tunerDesc),
|
||||
tunerDescMeta: removeMarkdown(serverConfig.identification.tunerDesc),
|
||||
tunerLock: serverConfig.lockToAdmin,
|
||||
publicTuner: serverConfig.publicTuner
|
||||
publicTuner: serverConfig.publicTuner,
|
||||
antennaSwitch: serverConfig.antennaSwitch
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/wizard', (req, res) => {
|
||||
parseAudioDevice((result) => {
|
||||
res.render('wizard', {
|
||||
isAdminAuthenticated: req.session.isAdminAuthenticated,
|
||||
videoDevices: result.audioDevices,
|
||||
audioDevices: result.videoDevices });
|
||||
});
|
||||
})
|
||||
|
||||
app.get('/setup', (req, res) => {
|
||||
parseAudioDevice((result) => {
|
||||
res.render('setup', {
|
||||
isAdminAuthenticated: req.session.isAdminAuthenticated,
|
||||
videoDevices: result.audioDevices,
|
||||
audioDevices: result.videoDevices,
|
||||
consoleOutput: consoleCmd.logs });
|
||||
const processUptimeInSeconds = Math.floor(process.uptime());
|
||||
const formattedProcessUptime = formatUptime(processUptimeInSeconds);
|
||||
|
||||
res.render('setup', {
|
||||
isAdminAuthenticated: req.session.isAdminAuthenticated,
|
||||
videoDevices: result.audioDevices,
|
||||
audioDevices: result.videoDevices,
|
||||
memoryUsage: (process.memoryUsage.rss() / 1024 / 1024).toFixed(1) + ' MB',
|
||||
processUptime: formattedProcessUptime,
|
||||
consoleOutput: consoleCmd.logs,
|
||||
onlineUsers: dataHandler.dataToSend.users
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/api', (req, res) => {
|
||||
let data = dataHandler.dataToSend;
|
||||
delete data.ps_errors;
|
||||
delete data.rt0_errors;
|
||||
delete data.rt1_errors;
|
||||
delete data.ims;
|
||||
delete data.eq;
|
||||
delete data.ant;
|
||||
delete data.st_forced;
|
||||
delete data.previousFreq;
|
||||
delete data.txInfo;
|
||||
res.json(data)
|
||||
});
|
||||
|
||||
function formatUptime(uptimeInSeconds) {
|
||||
const secondsInMinute = 60;
|
||||
const secondsInHour = secondsInMinute * 60;
|
||||
const secondsInDay = secondsInHour * 24;
|
||||
|
||||
const days = Math.floor(uptimeInSeconds / secondsInDay);
|
||||
const hours = Math.floor((uptimeInSeconds % secondsInDay) / secondsInHour);
|
||||
const minutes = Math.floor((uptimeInSeconds % secondsInHour) / secondsInMinute);
|
||||
|
||||
return `${days}d ${hours}h ${minutes}m`;
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Route for login
|
||||
app.post('/login', authenticate, (req, res) => {
|
||||
|
||||
@@ -13,12 +13,11 @@ if (index !== -1 && index + 1 < process.argv.length) {
|
||||
let serverConfig = {
|
||||
webserver: {
|
||||
webserverIp: "0.0.0.0",
|
||||
webserverPort: "8080",
|
||||
audioPort: "8081"
|
||||
webserverPort: 8080
|
||||
},
|
||||
xdrd: {
|
||||
xdrdIp: "127.0.0.1",
|
||||
xdrdPort: "7373",
|
||||
xdrdPort: 7373,
|
||||
xdrdPassword: ""
|
||||
},
|
||||
audio: {
|
||||
|
||||
@@ -5,6 +5,7 @@ const { configName, serverConfig, configUpdate, configSave } = require('../serve
|
||||
|
||||
function enableAudioStream() {
|
||||
var ffmpegCommand;
|
||||
serverConfig.webserver.webserverPort = Number(serverConfig.webserver.webserverPort);
|
||||
// Specify the command and its arguments
|
||||
const command = 'ffmpeg';
|
||||
const flags = `-fflags +nobuffer+flush_packets -flags low_delay -rtbufsize 6192 -probesize 32`;
|
||||
@@ -13,14 +14,14 @@ function enableAudioStream() {
|
||||
// Combine all the settings for the ffmpeg command
|
||||
if (process.platform === 'win32') {
|
||||
// Windows
|
||||
ffmpegCommand = `${flags} -f dshow -i audio="${serverConfig.audio.audioDevice}" ${codec} ${output} pipe:1 | node stream/3las.server.js -port ${serverConfig.webserver.audioPort} -samplerate 48000 -channels ${serverConfig.audio.audioChannels}`;
|
||||
ffmpegCommand = `${flags} -f dshow -i audio="${serverConfig.audio.audioDevice}" ${codec} ${output} pipe:1 | node stream/3las.server.js -port ${serverConfig.webserver.webserverPort + 10} -samplerate 48000 -channels ${serverConfig.audio.audioChannels}`;
|
||||
} else {
|
||||
// Linux
|
||||
ffmpegCommand = `${flags} -f alsa -i "${serverConfig.audio.audioDevice}" ${codec} ${output} pipe:1 | node stream/3las.server.js -port ${serverConfig.webserver.audioPort} -samplerate 48000 -channels ${serverConfig.audio.audioChannels}`;
|
||||
ffmpegCommand = `${flags} -f alsa -i "${serverConfig.audio.audioDevice}" ${codec} ${output} pipe:1 | node stream/3las.server.js -port ${serverConfig.webserver.webserverPort + 10} -samplerate 48000 -channels ${serverConfig.audio.audioChannels}`;
|
||||
}
|
||||
|
||||
consoleCmd.logInfo("Using audio device: " + serverConfig.audio.audioDevice);
|
||||
consoleCmd.logInfo("Launching audio stream on port " + serverConfig.webserver.audioPort + ".");
|
||||
consoleCmd.logInfo(`Launching audio stream on internal port ${serverConfig.webserver.webserverPort + 10}.`);
|
||||
// Spawn the child process
|
||||
|
||||
if(serverConfig.audio.audioDevice.length > 2) {
|
||||
|
||||
@@ -48,7 +48,7 @@ function processData(data, piCode, rdsPs) {
|
||||
const city = data.locations[cityId];
|
||||
if (city.stations) {
|
||||
for (const station of city.stations) {
|
||||
if (station.pi === piCode && !station.extra && station.ps && station.ps.toLowerCase().includes(rdsPs.replace(/ /g, '_').replace(/^_*(.*?)_*$/, '$1').toLowerCase())) {
|
||||
if (station.pi === piCode.toUpperCase() && !station.extra && station.ps && station.ps.toLowerCase().includes(rdsPs.replace(/ /g, '_').replace(/^_*(.*?)_*$/, '$1').toLowerCase())) {
|
||||
const distance = haversine(serverConfig.identification.lat, serverConfig.identification.lon, city.lat, city.lon);
|
||||
const score = (10*Math.log10(station.erp*1000)) / distance.distanceKm; // Calculate score
|
||||
if (score > maxScore) {
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
h1 {
|
||||
color: var(--color-4);
|
||||
font-size: 52px;
|
||||
font-weight: 300;
|
||||
font-size: 32px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.modal-panel-content h1 {
|
||||
text-transform: initial;
|
||||
font-weight: 300;
|
||||
font-size: 42px;
|
||||
}
|
||||
|
||||
h1#tuner-name {
|
||||
font-size: 32px;
|
||||
font-weight: 300;
|
||||
text-transform: initial;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@@ -18,6 +27,7 @@ h2 {
|
||||
|
||||
h3 {
|
||||
font-size: 22px;
|
||||
color: var(--color-4);
|
||||
}
|
||||
|
||||
h4 {
|
||||
@@ -139,6 +149,9 @@ label {
|
||||
.checkbox label {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.checkbox label:before {
|
||||
@@ -155,14 +168,20 @@ label {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.form-group input:checked + label:before {
|
||||
background-color: var(--color-4);
|
||||
}
|
||||
|
||||
.form-group input:checked + label:after {
|
||||
content: '✓';
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 6px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
font-size: 18px;
|
||||
top: -1px;
|
||||
left: 5px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--color-main);
|
||||
}
|
||||
|
||||
.tuner-info {
|
||||
@@ -170,6 +189,12 @@ label {
|
||||
margin-bottom: 0px !important;
|
||||
}
|
||||
|
||||
.settings-heading {
|
||||
font-size: 32px;
|
||||
padding-top: 20px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
canvas, #flags-container {
|
||||
display: none;
|
||||
|
||||
@@ -18,6 +18,57 @@ button:hover {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.btn-next {
|
||||
width: 200px;
|
||||
padding: 10px;
|
||||
font-weight: bold;
|
||||
color: var(--color-main);
|
||||
margin: 30px 5px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.btn-prev {
|
||||
width: 48px;
|
||||
padding: 10px;
|
||||
color: var(--color-main);
|
||||
background-color: var(--color-3);
|
||||
margin: 30px 5px;
|
||||
}
|
||||
|
||||
.btn-rounded-cube {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background: var(--color-2);
|
||||
color: var(--color-main);
|
||||
border-radius: 30px;
|
||||
margin-right: 10px;
|
||||
margin-left: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
font-size: 24px;
|
||||
font-weight: 300;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.btn-rounded-cube:not(:first-child)::before {
|
||||
content: "";
|
||||
width: 20px;
|
||||
height: 2px;
|
||||
background: var(--color-2);
|
||||
position: absolute;
|
||||
right: 64px;
|
||||
}
|
||||
|
||||
.btn-rounded-cube.activated {
|
||||
background-color: var(--color-4);
|
||||
}
|
||||
|
||||
.btn-rounded-cube.activated::before {
|
||||
background-color: var(--color-4);
|
||||
}
|
||||
|
||||
input[type="text"], textarea, input[type="password"] {
|
||||
width: 300px;
|
||||
min-height: 46px;
|
||||
@@ -267,4 +318,4 @@ select option {
|
||||
|
||||
select:hover {
|
||||
background: var(--color-5);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,22 @@
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.bg-color-1 {
|
||||
background-color: var(--color-1);
|
||||
}
|
||||
|
||||
.bg-color-2 {
|
||||
background-color: var(--color-2);
|
||||
}
|
||||
|
||||
.bg-color-3 {
|
||||
background-color: var(--color-3);
|
||||
}
|
||||
|
||||
.bg-color-4 {
|
||||
background-color: var(--color-4);
|
||||
}
|
||||
|
||||
.color-1 {
|
||||
color: var(--color-1);
|
||||
}
|
||||
@@ -30,6 +46,11 @@
|
||||
color: var(--color-4);
|
||||
}
|
||||
|
||||
.color-5 {
|
||||
color: var(--color-5);
|
||||
}
|
||||
|
||||
|
||||
.br-0 {
|
||||
border-radius: 0px;
|
||||
}
|
||||
@@ -126,7 +147,7 @@
|
||||
}
|
||||
|
||||
.text-bold {
|
||||
font-weight: bold;
|
||||
font-weight: bold !important;
|
||||
}
|
||||
|
||||
.text-monospace {
|
||||
@@ -134,7 +155,7 @@
|
||||
}
|
||||
|
||||
.text-gray {
|
||||
color: #666;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.text-red {
|
||||
@@ -177,6 +198,10 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 960px) {
|
||||
.text-medium-big {
|
||||
font-size: 32px;
|
||||
|
||||
@@ -9,6 +9,13 @@
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.setup-wrapper h2 {
|
||||
font-size: 32px;
|
||||
font-weight: 300;
|
||||
padding: 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
|
||||
.setup-wrapper textarea {
|
||||
width: 100%;
|
||||
@@ -19,6 +26,34 @@
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
ul.nav {
|
||||
list-style-type: none;
|
||||
padding: 15px 0;
|
||||
background: var(--color-2);
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
ul.nav li {
|
||||
display: inline;
|
||||
padding: 15px;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s ease-in-out, background-color 0.3s ease-in-out;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
ul.nav li:hover {
|
||||
color: var(--color-main);
|
||||
background-color: var(--color-4);
|
||||
}
|
||||
|
||||
li.active {
|
||||
background-color: var(--color-3);
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#map {
|
||||
height:400px;
|
||||
width:100%;
|
||||
@@ -33,10 +68,37 @@
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
|
||||
#console-output {
|
||||
background-color: #111;
|
||||
height: 300px;
|
||||
overflow-y:auto;
|
||||
}
|
||||
.w-200 {
|
||||
width: 200px !important
|
||||
}
|
||||
|
||||
.w-150 {
|
||||
width: 150px !important
|
||||
}
|
||||
|
||||
.w-100 {
|
||||
width: 100px !important;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
ul.nav {
|
||||
display: flex;
|
||||
overflow-y: scroll;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
ul.nav li {
|
||||
background-color: var(--color-4);
|
||||
color: var(--color-main);
|
||||
margin: 0px 10px;
|
||||
padding: 15px 35px;
|
||||
border-radius: 30px;
|
||||
min-width: fit-content;
|
||||
}
|
||||
}
|
||||
BIN
web/images/openradio_logo_neutral.png
Normal file
BIN
web/images/openradio_logo_neutral.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
@@ -82,7 +82,7 @@
|
||||
<div style="display:inline-block">
|
||||
<span style="margin-left: 20px;display: block;margin-top: 2px;" class="data-flag"></span>
|
||||
</div>
|
||||
<span style="margin-left: 20px;" class="data-st">ST</span>
|
||||
<span id="stereo-container" class="pointer"><span style="margin-left: 20px;" class="data-st">ST</span></span>
|
||||
<span style="margin-left: 15px;" class="data-ms">MS</span>
|
||||
</h3>
|
||||
</div>
|
||||
@@ -115,6 +115,8 @@
|
||||
<div class="panel-75 no-bg h-100 m-0 hide-desktop m-right-20 button-play-mobile" style="margin-right: 20px;">
|
||||
<button class="playbutton" aria-label="Play/Stop Button"><i class="fa-solid fa-play"></i></button>
|
||||
</div>
|
||||
|
||||
<% if (antennaSwitch) { %>
|
||||
<div class="panel-50 no-bg h-100 m-0 dropdown" id="data-ant" style="margin-right: 25px;">
|
||||
<input type="text" placeholder="Ant A" readonly />
|
||||
<ul class="options">
|
||||
@@ -124,6 +126,8 @@
|
||||
<li data-value="3" class="option">Ant D</li>
|
||||
</ul>
|
||||
</div>
|
||||
<% } %>
|
||||
|
||||
<div class="panel-100 no-bg h-100 m-0 button-eq">
|
||||
<button id="data-eq" style="border-radius: 30px 0px 0px 30px;" aria-label="EQ / RF+ Filter"><span class="text-bold">EQ</span><br><span class="text-smaller">(RF+)</span></button>
|
||||
</div>
|
||||
@@ -211,7 +215,7 @@
|
||||
<div class="dropdown" id="theme-selector">
|
||||
<input type="text" placeholder="Theme" readonly />
|
||||
<ul class="options">
|
||||
<li class="option" data-value="theme1">Monochrome</li>
|
||||
<li class="option" data-value="theme1">Default</li>
|
||||
<li class="option" data-value="theme2">Red</li>
|
||||
<li class="option" data-value="theme3">Green</li>
|
||||
<li class="option" data-value="theme4">Cyan</li>
|
||||
@@ -219,7 +223,7 @@
|
||||
<li class="option" data-value="theme6">Pink</li>
|
||||
<li class="option" data-value="theme7">Blurple</li>
|
||||
<li class="option" data-value="theme8">Bee</li>
|
||||
<li class="option" data-value="theme9">Retro</li>
|
||||
<li class="option" data-value="theme9">AMOLED</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -244,6 +248,10 @@
|
||||
<input type="checkbox" id="ps-underscores">
|
||||
<label for="ps-underscores">Add underscores to RDS PS</label>
|
||||
</div>
|
||||
<div class="form-group checkbox">
|
||||
<input type="checkbox" id="smooth-signal">
|
||||
<label for="smooth-signal">Smooth signal</label>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
@@ -273,7 +281,8 @@
|
||||
|
||||
<div class="version-info">
|
||||
<p class="text-small">FM-DX WebServer <br>by <a href="https://noobish.eu" target="_blank">Noobish</a>, <a href="https://fmdx.pl" target="_blank">kkonradpl</a> & the OpenRadio community.</p>
|
||||
<span style="color: var(--color-3);">v1.0.9 [23/2/2024]</span>
|
||||
<span style="color: var(--color-3);" class="version-string"></span><br>
|
||||
<span class="text-small" style="color: var(--color-3);">[<a href="https://list.fmdx.pl" target="_blank">Receiver Map</a>]</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
var _3LAS_Settings = /** @class */ (function () {
|
||||
function _3LAS_Settings() {
|
||||
this.SocketHost = document.location.hostname ? document.location.hostname : "127.0.0.1";
|
||||
this.SocketPort = localStorage.getItem('audioPort') ? localStorage.getItem('audioPort') : 8081;
|
||||
this.SocketPath = "/";
|
||||
this.WebRTC = new WebRTC_Settings();
|
||||
this.Fallback = new Fallback_Settings();
|
||||
|
||||
145
web/js/confighandler.js
Normal file
145
web/js/confighandler.js
Normal file
@@ -0,0 +1,145 @@
|
||||
function submitData() {
|
||||
const webserverIp = $('#webserver-ip').val() || '0.0.0.0';
|
||||
const webserverPort = $('#webserver-port').val() || '8080';
|
||||
|
||||
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 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();
|
||||
|
||||
const publicTuner = $("#tuner-public").is(":checked");
|
||||
const lockToAdmin = $("#tuner-lock").is(":checked");
|
||||
const autoShutdown = $("#shutdown-tuner").is(":checked") || false;
|
||||
const antennaSwitch = $("#antenna-switch").is(":checked") || false;
|
||||
|
||||
const data = {
|
||||
webserver: {
|
||||
webserverIp,
|
||||
webserverPort,
|
||||
},
|
||||
xdrd: {
|
||||
xdrdIp,
|
||||
xdrdPort,
|
||||
xdrdPassword
|
||||
},
|
||||
audio: {
|
||||
audioDevice,
|
||||
audioChannels,
|
||||
audioBitrate,
|
||||
},
|
||||
identification: {
|
||||
tunerName,
|
||||
tunerDesc,
|
||||
broadcastTuner,
|
||||
contact,
|
||||
lat,
|
||||
lon,
|
||||
proxyIp
|
||||
},
|
||||
password: {
|
||||
tunePass,
|
||||
adminPass,
|
||||
},
|
||||
publicTuner,
|
||||
lockToAdmin,
|
||||
autoShutdown,
|
||||
antennaSwitch,
|
||||
};
|
||||
|
||||
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) {
|
||||
alert(message);
|
||||
},
|
||||
error: function (error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function fetchData() {
|
||||
// Make a GET request to retrieve the data.json file
|
||||
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);
|
||||
|
||||
$('#xdrd-ip').val(data.xdrd.xdrdIp);
|
||||
$('#xdrd-port').val(data.xdrd.xdrdPort);
|
||||
$('#xdrd-password').val(data.xdrd.xdrdPassword);
|
||||
|
||||
$('#audio-devices').val(data.audio.audioDevice);
|
||||
$('#audio-channels').val(data.audio.audioChannels);
|
||||
$('#audio-quality').val(data.audio.audioBitrate);
|
||||
|
||||
$('#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.antennaSwitch);
|
||||
|
||||
// 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));
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching data:', error.message);
|
||||
});
|
||||
}
|
||||
@@ -3,6 +3,7 @@ url.protocol = url.protocol.replace('http', 'ws');
|
||||
var socketAddress = url.href;
|
||||
var socket = new WebSocket(socketAddress);
|
||||
var parsedData, signalChart, previousFreq;
|
||||
var signalData = [];
|
||||
var data = [];
|
||||
let updateCounter = 0;
|
||||
|
||||
@@ -118,6 +119,7 @@ $(document).ready(function () {
|
||||
var piCodeContainer = $('#pi-code-container')[0];
|
||||
var freqContainer = $('#freq-container')[0];
|
||||
var txContainer = $('#data-station-container')[0];
|
||||
var stereoContainer = $('#stereo-container')[0];
|
||||
|
||||
$("#data-eq").click(function () {
|
||||
toggleButtonState("eq");
|
||||
@@ -133,6 +135,7 @@ $(document).ready(function () {
|
||||
$(rtContainer).on("click", copyRt);
|
||||
$(txContainer).on("click", copyTx);
|
||||
$(piCodeContainer).on("click", findOnMaps);
|
||||
$(stereoContainer).on("click", toggleForcedStereo);
|
||||
$(freqContainer).on("click", function () {
|
||||
textInput.focus();
|
||||
});
|
||||
@@ -514,24 +517,31 @@ function findOnMaps() {
|
||||
window.open(url, "_blank");
|
||||
}
|
||||
|
||||
function updateSignalUnits(parsedData) {
|
||||
function updateSignalUnits(parsedData, averageSignal) {
|
||||
const signalUnit = localStorage.getItem('signalUnit');
|
||||
let currentSignal;
|
||||
|
||||
if(localStorage.getItem("smoothSignal") == 'true') {
|
||||
currentSignal = averageSignal
|
||||
} else {
|
||||
currentSignal = parsedData.signal;
|
||||
}
|
||||
let signalText = $('#signal-units');
|
||||
let signalValue;
|
||||
|
||||
switch (signalUnit) {
|
||||
case 'dbuv':
|
||||
signalValue = parsedData.signal - 11.25;
|
||||
signalValue = currentSignal - 11.25;
|
||||
signalText.text('dBµV');
|
||||
break;
|
||||
|
||||
case 'dbm':
|
||||
signalValue = parsedData.signal - 120;
|
||||
signalValue = currentSignal - 120;
|
||||
signalText.text('dBm');
|
||||
break;
|
||||
|
||||
default:
|
||||
signalValue = parsedData.signal;
|
||||
signalValue = currentSignal;
|
||||
signalText.text('dBf');
|
||||
break;
|
||||
}
|
||||
@@ -551,15 +561,6 @@ function updateDataElements(parsedData) {
|
||||
parsedData.ps = parsedData.ps.replace(/\s/g, '_');
|
||||
}
|
||||
$('#data-ps').html(parsedData.ps === '?' ? "<span class='opacity-half'>?</span>" : processString(parsedData.ps, parsedData.ps_errors));
|
||||
$('.data-tp').html(parsedData.tp === 0 ? "<span class='opacity-half'>TP</span>" : "TP");
|
||||
$('.data-ta').html(parsedData.ta === 0 ? "<span class='opacity-half'>TA</span>" : "TA");
|
||||
$('.data-ms').html(parsedData.ms === 0
|
||||
? "<span class='opacity-half'>M</span><span class='opacity-full'>S</span>"
|
||||
: (parsedData.ms === -1
|
||||
? "<span class='opacity-half'>M</span><span class='opacity-half'>S</span>"
|
||||
: "<span class='opacity-full'>M</span><span class='opacity-half'>S</span>"
|
||||
)
|
||||
);
|
||||
$('.data-pty').html(europe_programmes[parsedData.pty]);
|
||||
|
||||
|
||||
@@ -580,6 +581,7 @@ function updateDataElements(parsedData) {
|
||||
$('#data-rt0').html(processString(parsedData.rt0, parsedData.rt0_errors));
|
||||
$('#data-rt1').html(processString(parsedData.rt1, parsedData.rt1_errors));
|
||||
$('.data-flag').html(`<i title="${parsedData.country_name}" class="flag-sm flag-sm-${parsedData.country_iso}"></i>`);
|
||||
$('.data-flag-big').html(`<i title="${parsedData.country_name}" class="flag-md flag-md-${parsedData.country_iso}"></i>`);
|
||||
$('#data-ant input').val($('#data-ant li[data-value="' + parsedData.ant + '"]').text());
|
||||
|
||||
if (parsedData.txInfo.station.length > 1) {
|
||||
@@ -596,6 +598,18 @@ function updateDataElements(parsedData) {
|
||||
}
|
||||
|
||||
updateCounter++;
|
||||
if(updateCounter % 8 === 0) {
|
||||
$('.data-tp').html(parsedData.tp === 0 ? "<span class='opacity-half'>TP</span>" : "TP");
|
||||
$('.data-ta').html(parsedData.ta === 0 ? "<span class='opacity-half'>TA</span>" : "TA");
|
||||
$('.data-ms').html(parsedData.ms === 0
|
||||
? "<span class='opacity-half'>M</span><span class='opacity-full'>S</span>"
|
||||
: (parsedData.ms === -1
|
||||
? "<span class='opacity-half'>M</span><span class='opacity-half'>S</span>"
|
||||
: "<span class='opacity-full'>M</span><span class='opacity-half'>S</span>"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (updateCounter % 30 === 0) {
|
||||
$('#data-ps').attr('aria-label', parsedData.ps);
|
||||
$('#data-rt0').attr('aria-label', parsedData.rt0);
|
||||
@@ -608,6 +622,13 @@ let isEventListenerAdded = false;
|
||||
function updatePanels(parsedData) {
|
||||
updateCounter++;
|
||||
|
||||
signalData.push(parsedData.signal);
|
||||
if (signalData.length > 8) {
|
||||
signalData.shift(); // Remove the oldest element
|
||||
}
|
||||
const sum = signalData.reduce((acc, strNum) => acc + parseFloat(strNum), 0);
|
||||
const averageSignal = sum / signalData.length;
|
||||
|
||||
const sortedAf = parsedData.af.sort(compareNumbers);
|
||||
const scaledArray = sortedAf.map(element => element / 1000);
|
||||
|
||||
@@ -642,9 +663,8 @@ function updatePanels(parsedData) {
|
||||
listContainer.scrollTop(scrollTop);
|
||||
}
|
||||
|
||||
// Update other elements every time
|
||||
updateDataElements(parsedData);
|
||||
updateSignalUnits(parsedData);
|
||||
updateSignalUnits(parsedData, averageSignal);
|
||||
$('.users-online').text(parsedData.users);
|
||||
}
|
||||
|
||||
@@ -669,3 +689,9 @@ function toggleButtonState(buttonId) {
|
||||
message += parsedData.ims ? "1" : "0";
|
||||
socket.send(message);
|
||||
}
|
||||
|
||||
function toggleForcedStereo() {
|
||||
var message = "B";
|
||||
message += parsedData.st_forced = (parsedData.st_forced == "1") ? "0" : "1";
|
||||
socket.send(message);
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
var currentVersion = 'v1.1.0 [29.2.2024]';
|
||||
|
||||
/**
|
||||
* Themes
|
||||
* @param first color
|
||||
* @param second color
|
||||
* @param text color
|
||||
*/
|
||||
* Themes
|
||||
* @param first color
|
||||
* @param second color
|
||||
* @param text color
|
||||
*/
|
||||
const themes = {
|
||||
theme1: [ 'rgba(0, 0, 0, 1)', 'rgba(204, 204, 204, 1)', 'rgba(255, 255, 255, 1)' ], // Monochrome (Default)
|
||||
theme1: ['rgba(32, 34, 40, 1)', 'rgba(88, 219, 171, 1)', 'rgba(255, 255, 255, 1)' ], // Retro (Default)
|
||||
theme2: [ 'rgba(31, 12, 12, 1)', 'rgba(255, 112, 112, 1)', 'rgba(255, 255, 255, 1)' ], // Red
|
||||
theme3: [ 'rgba(18, 28, 12, 1)', 'rgba(169, 255, 112, 1)', 'rgba(255, 255, 255, 1)' ], // Green
|
||||
theme4: [ 'rgba(12, 28, 27, 1)', 'rgba(104, 247, 238, 1)', 'rgba(255, 255, 255, 1)' ], // Cyan
|
||||
@@ -13,143 +15,154 @@
|
||||
theme6: [ 'rgba(33, 9, 29, 1)', 'rgba(237, 81, 211, 1)', 'rgba(255, 255, 255, 1)' ], // Pink
|
||||
theme7: [ 'rgba(13, 11, 26, 1)', 'rgba(128, 105, 250, 1)', 'rgba(255, 255, 255, 1)' ], // Blurple
|
||||
theme8: [ 'rgba(252, 186, 3, 1)', 'rgba(0, 0, 0, 1)', 'rgba(0, 0, 0, 1)' ], // Sunny
|
||||
theme9: ['rgba(32, 34, 40, 1)', 'rgba(88, 219, 171, 1)', 'rgba(255, 255, 255, 1)' ] // Retro
|
||||
};
|
||||
|
||||
// Signal Units
|
||||
const signalUnits = {
|
||||
dbf: ['dBf'],
|
||||
dbuv: ['dBµV'],
|
||||
dbm: ['dBm'],
|
||||
};
|
||||
|
||||
$(document).ready(() => {
|
||||
// Theme Selector
|
||||
const themeSelector = $('#theme-selector');
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
const savedUnit = localStorage.getItem('signalUnit');
|
||||
|
||||
if (savedTheme && themes[savedTheme]) {
|
||||
setTheme(savedTheme);
|
||||
themeSelector.find('input').val(themeSelector.find('.option[data-value="' + savedTheme + '"]').text());
|
||||
}
|
||||
|
||||
themeSelector.on('click', '.option', (event) => {
|
||||
const selectedTheme = $(event.target).data('value');
|
||||
setTheme(selectedTheme);
|
||||
themeSelector.find('input').val($(event.target).text()); // Set the text of the clicked option to the input
|
||||
localStorage.setItem('theme', selectedTheme);
|
||||
});
|
||||
|
||||
// Signal Selector
|
||||
const signalSelector = $('#signal-selector');
|
||||
|
||||
if (localStorage.getItem('signalUnit')) {
|
||||
signalSelector.find('input').val(signalSelector.find('.option[data-value="' + savedUnit + '"]').text());
|
||||
}
|
||||
|
||||
signalSelector.on('click', '.option', (event) => {
|
||||
const selectedSignalUnit = $(event.target).data('value');
|
||||
signalSelector.find('input').val($(event.target).text()); // Set the text of the clicked option to the input
|
||||
localStorage.setItem('signalUnit', selectedSignalUnit);
|
||||
});
|
||||
|
||||
$('#login-form').submit(function (event) {
|
||||
event.preventDefault();
|
||||
theme9: [ 'rgba(0, 0, 0, 1)', 'rgba(204, 204, 204, 1)', 'rgba(255, 255, 255, 1)' ], // AMOLED
|
||||
};
|
||||
|
||||
// Perform an AJAX request to the /login endpoint
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: './login',
|
||||
data: $(this).serialize(),
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Assuming you have an anchor tag with id 'logout-link'
|
||||
$('.logout-link').click(function (event) {
|
||||
event.preventDefault();
|
||||
// Signal Units
|
||||
const signalUnits = {
|
||||
dbf: ['dBf'],
|
||||
dbuv: ['dBµV'],
|
||||
dbm: ['dBm'],
|
||||
};
|
||||
|
||||
$(document).ready(() => {
|
||||
// Theme Selector
|
||||
const themeSelector = $('#theme-selector');
|
||||
const savedTheme = localStorage.getItem('theme');
|
||||
const savedUnit = localStorage.getItem('signalUnit');
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var extendedFreqRange = localStorage.getItem("extendedFreqRange");
|
||||
if (extendedFreqRange === "true") {
|
||||
$("#extended-frequency-range").prop("checked", true);
|
||||
}
|
||||
|
||||
// Save the value of the checkbox into local storage when its state changes
|
||||
$("#extended-frequency-range").change(function() {
|
||||
var isChecked = $(this).is(":checked");
|
||||
localStorage.setItem("extendedFreqRange", isChecked);
|
||||
});
|
||||
|
||||
var extendedFreqRange = localStorage.getItem("psUnderscores");
|
||||
if (extendedFreqRange === "true") {
|
||||
$("#ps-underscores").prop("checked", true);
|
||||
}
|
||||
|
||||
// Save the value of the checkbox into local storage when its state changes
|
||||
$("#ps-underscores").change(function() {
|
||||
var isChecked = $(this).is(":checked");
|
||||
localStorage.setItem("psUnderscores", isChecked);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
function setTheme(themeName) {
|
||||
const themeColors = themes[themeName];
|
||||
if (themeColors) {
|
||||
// Extracting the RGBA components and opacity value
|
||||
const rgbaComponents = themeColors[2].match(/(\d+(\.\d+)?)/g);
|
||||
const opacity = parseFloat(rgbaComponents[3]);
|
||||
// Calculating 80% of the opacity
|
||||
const newOpacity = opacity * 0.75;
|
||||
// Constructing the new RGBA string with the adjusted opacity
|
||||
const textColor2 = `rgba(${rgbaComponents[0]}, ${rgbaComponents[1]}, ${rgbaComponents[2]}, ${newOpacity})`;
|
||||
if (savedTheme && themes[savedTheme]) {
|
||||
setTheme(savedTheme);
|
||||
themeSelector.find('input').val(themeSelector.find('.option[data-value="' + savedTheme + '"]').text());
|
||||
}
|
||||
|
||||
$(':root').css('--color-main', themeColors[0]);
|
||||
$(':root').css('--color-main-bright', themeColors[1]);
|
||||
$(':root').css('--color-text', themeColors[2]);
|
||||
$(':root').css('--color-text-2', textColor2);
|
||||
}
|
||||
}
|
||||
themeSelector.on('click', '.option', (event) => {
|
||||
const selectedTheme = $(event.target).data('value');
|
||||
setTheme(selectedTheme);
|
||||
themeSelector.find('input').val($(event.target).text()); // Set the text of the clicked option to the input
|
||||
localStorage.setItem('theme', selectedTheme);
|
||||
});
|
||||
|
||||
// Signal Selector
|
||||
const signalSelector = $('#signal-selector');
|
||||
|
||||
if (localStorage.getItem('signalUnit')) {
|
||||
signalSelector.find('input').val(signalSelector.find('.option[data-value="' + savedUnit + '"]').text());
|
||||
}
|
||||
|
||||
signalSelector.on('click', '.option', (event) => {
|
||||
const selectedSignalUnit = $(event.target).data('value');
|
||||
signalSelector.find('input').val($(event.target).text()); // Set the text of the clicked option to the input
|
||||
localStorage.setItem('signalUnit', selectedSignalUnit);
|
||||
});
|
||||
|
||||
$('#login-form').submit(function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
// Perform an AJAX request to the /login endpoint
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
url: './login',
|
||||
data: $(this).serialize(),
|
||||
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);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Assuming you have an anchor tag with id 'logout-link'
|
||||
$('.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);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var extendedFreqRange = localStorage.getItem("extendedFreqRange");
|
||||
if (extendedFreqRange === "true") {
|
||||
$("#extended-frequency-range").prop("checked", true);
|
||||
}
|
||||
|
||||
// Save the value of the checkbox into local storage when its state changes
|
||||
$("#extended-frequency-range").change(function() {
|
||||
var isChecked = $(this).is(":checked");
|
||||
localStorage.setItem("extendedFreqRange", isChecked);
|
||||
});
|
||||
|
||||
var extendedFreqRange = localStorage.getItem("psUnderscores");
|
||||
if (extendedFreqRange === "true") {
|
||||
$("#ps-underscores").prop("checked", true);
|
||||
}
|
||||
|
||||
var smoothSignal = localStorage.getItem("smoothSignal");
|
||||
if (smoothSignal === "true") {
|
||||
$("#smooth-signal").prop("checked", true);
|
||||
}
|
||||
|
||||
// Save the value of the checkbox into local storage when its state changes
|
||||
$("#ps-underscores").change(function() {
|
||||
var isChecked = $(this).is(":checked");
|
||||
localStorage.setItem("psUnderscores", isChecked);
|
||||
});
|
||||
|
||||
$("#smooth-signal").change(function() {
|
||||
var isChecked = $(this).is(":checked");
|
||||
localStorage.setItem("smoothSignal", isChecked);
|
||||
});
|
||||
|
||||
$('.version-string').text(currentVersion);
|
||||
});
|
||||
|
||||
|
||||
function setTheme(themeName) {
|
||||
const themeColors = themes[themeName];
|
||||
if (themeColors) {
|
||||
// Extracting the RGBA components and opacity value
|
||||
const rgbaComponents = themeColors[2].match(/(\d+(\.\d+)?)/g);
|
||||
const opacity = parseFloat(rgbaComponents[3]);
|
||||
// Calculating 80% of the opacity
|
||||
const newOpacity = opacity * 0.75;
|
||||
// Constructing the new RGBA string with the adjusted opacity
|
||||
const textColor2 = `rgba(${rgbaComponents[0]}, ${rgbaComponents[1]}, ${rgbaComponents[2]}, ${newOpacity})`;
|
||||
|
||||
$(':root').css('--color-main', themeColors[0]);
|
||||
$(':root').css('--color-main-bright', themeColors[1]);
|
||||
$(':root').css('--color-text', themeColors[2]);
|
||||
$(':root').css('--color-text-2', textColor2);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
205
web/js/setup.js
205
web/js/setup.js
@@ -9,7 +9,12 @@ $(document).ready(function() {
|
||||
MapCreate();
|
||||
fetchData();
|
||||
|
||||
|
||||
setTimeout( function() {
|
||||
if ($('.nav li.active[data-panel="status"]').length > 0) {
|
||||
$('#submit-config').hide();
|
||||
}
|
||||
}, 50 )
|
||||
|
||||
map.on('click', function(ev) {
|
||||
$('#lat').val((ev.latlng.lat).toFixed(6));
|
||||
$('#lng').val((ev.latlng.lng).toFixed(6));
|
||||
@@ -25,7 +30,39 @@ $(document).ready(function() {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$('#status').show();
|
||||
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);
|
||||
}
|
||||
|
||||
if(panelId == 'status') {
|
||||
$('#submit-config').hide();
|
||||
} else {
|
||||
$('#submit-config').show();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$('#login-form').submit(function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
@@ -105,7 +142,9 @@ $(document).ready(function() {
|
||||
});
|
||||
});
|
||||
|
||||
$("#console-output").scrollTop($("#console-output")[0].scrollHeight);
|
||||
if($("#console-output").length > 0) {
|
||||
$("#console-output").scrollTop($("#console-output")[0].scrollHeight);
|
||||
}
|
||||
});
|
||||
|
||||
function MapCreate() {
|
||||
@@ -126,149 +165,19 @@ function MapCreate() {
|
||||
}).addTo(map);
|
||||
}
|
||||
|
||||
function fetchData() {
|
||||
// Make a GET request to retrieve the data.json file
|
||||
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);
|
||||
$('#audio-port').val(data.webserver.audioPort);
|
||||
|
||||
$('#xdrd-ip').val(data.xdrd.xdrdIp);
|
||||
$('#xdrd-port').val(data.xdrd.xdrdPort);
|
||||
$('#xdrd-password').val(data.xdrd.xdrdPassword);
|
||||
|
||||
$('#audio-devices').val(data.audio.audioDevice);
|
||||
$('#audio-channels').val(data.audio.audioChannels);
|
||||
$('#audio-quality').val(data.audio.audioBitrate);
|
||||
|
||||
$('#webserver-name').val(data.identification.tunerName);
|
||||
$('#webserver-desc').val(data.identification.tunerDesc);
|
||||
$('#lat').val(data.identification.lat);
|
||||
$('#lng').val(data.identification.lon);
|
||||
$("#broadcast-tuner").prop("checked", data.identification.broadcastTuner);
|
||||
$("#broadcast-address").val(data.identification.proxyIp);
|
||||
|
||||
$('#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);
|
||||
|
||||
// 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));
|
||||
});
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error fetching data:', error.message);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function submitData() {
|
||||
const webserverIp = $('#webserver-ip').val() || '0.0.0.0';
|
||||
const webserverPort = $('#webserver-port').val() || '8080';
|
||||
const audioPort = $('#audio-port').val() || '8081';
|
||||
|
||||
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 tunerName = $('#webserver-name').val() || 'FM Tuner';
|
||||
const tunerDesc = $('#webserver-desc').val() || 'Default FM tuner description';
|
||||
const lat = $('#lat').val();
|
||||
const lon = $('#lng').val();
|
||||
const broadcastTuner = $("#broadcast-tuner").is(":checked");
|
||||
const proxyIp = $("#broadcast-address").val();
|
||||
|
||||
const tunePass = $('#tune-pass').val();
|
||||
const adminPass = $('#admin-pass').val();
|
||||
|
||||
const publicTuner = $("#tuner-public").is(":checked");
|
||||
const lockToAdmin = $("#tuner-lock").is(":checked");
|
||||
const autoShutdown = $("#shutdown-tuner").is(":checked");
|
||||
|
||||
const data = {
|
||||
webserver: {
|
||||
webserverIp,
|
||||
webserverPort,
|
||||
audioPort
|
||||
},
|
||||
xdrd: {
|
||||
xdrdIp,
|
||||
xdrdPort,
|
||||
xdrdPassword
|
||||
},
|
||||
audio: {
|
||||
audioDevice,
|
||||
audioChannels,
|
||||
audioBitrate,
|
||||
},
|
||||
identification: {
|
||||
tunerName,
|
||||
tunerDesc,
|
||||
lat,
|
||||
lon,
|
||||
broadcastTuner,
|
||||
proxyIp
|
||||
},
|
||||
password: {
|
||||
tunePass,
|
||||
adminPass,
|
||||
},
|
||||
publicTuner,
|
||||
lockToAdmin,
|
||||
autoShutdown
|
||||
};
|
||||
|
||||
|
||||
if(adminPass.length < 1) {
|
||||
alert('You need to fill in the admin password before continuing further.');
|
||||
return;
|
||||
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');
|
||||
}
|
||||
// Send data to the server using jQuery
|
||||
$.ajax({
|
||||
url: './saveData',
|
||||
type: 'POST',
|
||||
contentType: 'application/json',
|
||||
data: JSON.stringify(data),
|
||||
success: function (message) {
|
||||
alert(message);
|
||||
},
|
||||
error: function (error) {
|
||||
console.error(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
62
web/js/wizard.js
Normal file
62
web/js/wizard.js
Normal file
@@ -0,0 +1,62 @@
|
||||
$(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();
|
||||
});
|
||||
});
|
||||
|
||||
// Function to update the progress bar buttons
|
||||
function updateProgressBar(currentStep) {
|
||||
var stepIndex = $('.step').index(currentStep) + 1;
|
||||
$('.btn-rounded-cube').removeClass('activated');
|
||||
$('.btn-rounded-cube:lt(' + stepIndex + ')').addClass('activated');
|
||||
}
|
||||
|
||||
function updateWizardContent() {
|
||||
if($('.step:visible').index() == 0) {
|
||||
$('.btn-prev').hide();
|
||||
} else {
|
||||
$('.btn-prev').show();
|
||||
}
|
||||
|
||||
if($('.step:visible').index() == 2) {
|
||||
setTimeout(function () {
|
||||
map.invalidateSize();
|
||||
}, 200);
|
||||
}
|
||||
|
||||
if($('.step:visible').index() == 3) {
|
||||
$('.btn-next').text('Save');
|
||||
} else {
|
||||
$('.btn-next').text('Next')
|
||||
}
|
||||
}
|
||||
180
web/setup.ejs
180
web/setup.ejs
@@ -15,15 +15,63 @@
|
||||
<div id="wrapper" class="setup-wrapper">
|
||||
<% if (isAdminAuthenticated) { %>
|
||||
<div class="panel-100 no-bg">
|
||||
<h1>FM-DX WebServer</h1>
|
||||
<img class="top-10" src="./images/openradio_logo_neutral.png" height="64px">
|
||||
<h2 class="text-monospace text-light">[ADMIN PANEL]</h2>
|
||||
<p>This web setup allows you to set up your entire tuner. <br>Some settings will only change after a server restart.</p>
|
||||
<p>In case you are setting up the webserver for the first time, we already filled fail-safe defaults for you.</p>
|
||||
</div>
|
||||
<div class="flex-container">
|
||||
<div class="panel-50" style="min-height: 120px;margin-bottom: 0;">
|
||||
<h2>BASIC SETTINGS</h2>
|
||||
<h3>Connection to xdrd:</h3>
|
||||
<div class="panel-100">
|
||||
<ul class="nav">
|
||||
<li data-panel="status" class="active">Status</li>
|
||||
<li data-panel="connection">Connection</li>
|
||||
<li data-panel="audio">Audio</li>
|
||||
<li data-panel="identification">Identification</li>
|
||||
<li data-panel="mapbroadcast">Online map</li>
|
||||
<li data-panel="maintenance">Maintenance</li>
|
||||
<li class="logout-link text-gray"><i class="fas fa-sign-out"></i></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="login-message"></div>
|
||||
<div class="panel-100">
|
||||
<div class="panel-100 tab-content" id="status">
|
||||
<h2>STATUS</h2>
|
||||
|
||||
<div class="panel-100 flex-container auto">
|
||||
<div class="panel-33 bg-color-2">
|
||||
<span class="text-medium-big color-5"><%= onlineUsers %></span>
|
||||
<p>Online users</p>
|
||||
</div>
|
||||
|
||||
<div class="panel-33 bg-color-2">
|
||||
<span class="text-medium-big color-5"><%= memoryUsage %></span>
|
||||
<p>Memory usage</p>
|
||||
</div>
|
||||
|
||||
<div class="panel-33 bg-color-2">
|
||||
<span class="text-medium-big color-5"><%= processUptime %></span>
|
||||
<p>Uptime</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2>Console output</h2>
|
||||
<% if (consoleOutput && consoleOutput.length > 0) { %>
|
||||
<div class="panel-100 br-5 p-10 text-small text-left top-10" id="console-output">
|
||||
<% consoleOutput.forEach(function(log) { %>
|
||||
<pre class="m-0" style="white-space:pre-wrap;"><%= log %></pre>
|
||||
<% }); %>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<p>No console output available.</p>
|
||||
<% } %>
|
||||
|
||||
<p>Version: <span class="version-string color-4"></span></p>
|
||||
<p class="p-bottom-20"><a href="https://github.com/noobishsvk/fm-dx-webserver" target="_blank">Check for the latest source code</a> • <a href="https://buymeacoffee.com/noobish" target="_blank">Support the developer</a></p>
|
||||
</div>
|
||||
|
||||
<div class="panel-100 tab-content" id="connection">
|
||||
<h2>Connection settings</h2>
|
||||
<p>You can set up your connection settings here. Changing these settings requires a server restart.</p>
|
||||
<h3>Tuner connection:</h3>
|
||||
<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">
|
||||
@@ -38,6 +86,7 @@
|
||||
</div>
|
||||
<br>
|
||||
<h3>Webserver 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">
|
||||
@@ -46,17 +95,18 @@
|
||||
<label for="webserver-port">Webserver port:</label>
|
||||
<input class="input-text w-100" type="text" name="webserver-port" id="webserver-port" placeholder="8080">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="audio-port">Audio port:</label>
|
||||
<input class="input-text w-150" type="text" name="audio-port" id="audio-port" placeholder="8081">
|
||||
</div>
|
||||
<br>
|
||||
</div>
|
||||
<div class="panel-50" style="min-height: 120px;margin-bottom: 0;">
|
||||
<h2>AUDIO SETTINGS</h2>
|
||||
|
||||
<div class="panel-100 tab-content" id="audio">
|
||||
<h2>Audio settings</h2>
|
||||
|
||||
<p>You can set up your audio settings here. Changing these settings requires a server restart.</p>
|
||||
<div class="panel-100 p-bottom-20">
|
||||
<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" style="width: 300px;">
|
||||
<input id="audio-devices" type="text" name="audio-devices" placeholder="Choose your audio device" readonly />
|
||||
@@ -72,6 +122,9 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<p class="text-left">Audio channel count.<br>
|
||||
<span class="text-gray">1: Mono • 2: Stereo</span>
|
||||
</p>
|
||||
<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 />
|
||||
@@ -83,6 +136,9 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<p class="text-left">The bitrate of the mp3 audio.<br>
|
||||
<span class="text-gray">Minimum: 64 Kbps • Maximum: 256 Kbps</span>
|
||||
</p>
|
||||
<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 />
|
||||
@@ -97,35 +153,21 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-container bottom-20">
|
||||
<div class="panel-100" style="padding-bottom: 20px;">
|
||||
<h2>TUNER IDENTIFICATION INFO</h2>
|
||||
|
||||
|
||||
<div class="panel-100 tab-content" id="identification">
|
||||
<h2>Tuner Identification info</h2>
|
||||
|
||||
<p class="text-gray">Set your tuner name and description here. This info will be visible to anyone who tunes in. </p>
|
||||
<div class="panel-100" 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.">
|
||||
<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">
|
||||
<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."></textarea>
|
||||
</div>
|
||||
|
||||
<h3>Map broadcast:</h3>
|
||||
<p class="m-0">If your tuner is set to public and ID information 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://list.fmdx.pl" target="_blank" class="color-4">list.fmdx.pl</a></strong>.</p>
|
||||
<p></p>
|
||||
<div class="form-group checkbox">
|
||||
<input type="checkbox" id="broadcast-tuner">
|
||||
<label for="broadcast-tuner">Broadcast to map</label>
|
||||
</div><br>
|
||||
<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">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<h3>Tuner location:</h3>
|
||||
|
||||
<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">
|
||||
@@ -137,49 +179,61 @@
|
||||
</div>
|
||||
|
||||
<div id="map"></div>
|
||||
<br>
|
||||
</div>
|
||||
|
||||
<div class="panel-33">
|
||||
<h2>MAINTENANCE</h2>
|
||||
<div class="text-left top-25 bottom-20" style="padding-left: 40px;">
|
||||
|
||||
<div class="panel-100 tab-content" id="mapbroadcast">
|
||||
<h2>Map broadcast</h2>
|
||||
<p class="m-0">If your location information is filled, you can add your tuner to a public list.</p>
|
||||
<p></p>
|
||||
<div class="form-group checkbox">
|
||||
<input type="checkbox" id="broadcast-tuner">
|
||||
<label for="broadcast-tuner">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">
|
||||
|
||||
<label for="broadcast-address">Broadcast address (if using a proxy):</label>
|
||||
<input class="input-text" type="text" name="broadcast-address" id="broadcast-address">
|
||||
</div>
|
||||
|
||||
<p>Check your tuner at <strong><a href="https://list.fmdx.pl" target="_blank" class="color-4">list.fmdx.pl</a></strong>.</p>
|
||||
</div>
|
||||
|
||||
<div class="panel-100 tab-content" id="maintenance">
|
||||
<h2>Maintenance</h2>
|
||||
<div class="text-left top-25 bottom-20 panel-33 auto">
|
||||
<div class="form-group checkbox">
|
||||
<input type="checkbox" id="tuner-public">
|
||||
<label for="tuner-public">Public tuner</label>
|
||||
<label for="tuner-public">Allow tuner control without password</label>
|
||||
</div><br>
|
||||
<div class="form-group checkbox">
|
||||
<input type="checkbox" id="tuner-lock">
|
||||
<label for="tuner-lock">Lock to admin</label>
|
||||
</div><br>
|
||||
<label for="tuner-lock">Lock the tuner [only admins can tune with this option]</label>
|
||||
</div><br><br>
|
||||
<div class="form-group checkbox">
|
||||
<input type="checkbox" id="shutdown-tuner">
|
||||
<label for="shutdown-tuner">Auto-shutdown [XDR Only]</label>
|
||||
</div><br>
|
||||
<div class="form-group checkbox">
|
||||
<input type="checkbox" id="antenna-switch">
|
||||
<label for="antenna-switch">Enable the antenna switch</label>
|
||||
</div><br>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="tune-pass">Tune password:</label>
|
||||
<input class="input-text w-150" type="password" name="tune-pass" id="tune-pass">
|
||||
<input class="input-text w-200" type="password" name="tune-pass" id="tune-pass">
|
||||
</div>
|
||||
<div class="form-group" style="margin-bottom: 40px;">
|
||||
<div class="form-group">
|
||||
<label for="admin-pass">Admin setup password:</label>
|
||||
<input class="input-text w-150" type="password" name="admin-pass" id="admin-pass">
|
||||
<input class="input-text w-200" type="password" name="admin-pass" id="admin-pass">
|
||||
</div><br>
|
||||
<button style="height:48px; width: 200px;margin-bottom:20px;" onclick="submitData();">Save settings</button>
|
||||
<button style="height: 48px; width: 200px;background:var(--color-3)" class="logout-link">Logout</button>
|
||||
<div id="login-message"></div>
|
||||
</div>
|
||||
|
||||
<button id="submit-config" style="height:48px; width: 200px;margin:20px;" onclick="submitData();">Save settings</button>
|
||||
</div>
|
||||
<div class="panel-100 p-bottom-20">
|
||||
<h2>CONSOLE OUTPUT</h2>
|
||||
<% if (consoleOutput && consoleOutput.length > 0) { %>
|
||||
<div class="panel-100 br-5 p-10 text-small text-left top-10" id="console-output" style="background-color: var(--color-main);height: 300px;overflow-y:auto;">
|
||||
<% consoleOutput.forEach(function(log) { %>
|
||||
<pre class="m-0" style="white-space:pre-wrap;"><%= log %></pre>
|
||||
<% }); %>
|
||||
</div>
|
||||
<% } else { %>
|
||||
<p>No console output available.</p>
|
||||
<% } %>
|
||||
</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>
|
||||
@@ -187,8 +241,7 @@
|
||||
<button onclick="document.location.href='./'" id="back-btn" aria-label="Go back to tuning"><i class="fa-solid fa-arrow-left"></i></button>
|
||||
<% } else { %>
|
||||
<div class="panel-100 no-bg">
|
||||
<img src="../favicon2.png">
|
||||
<h1>FM-DX WebServer</h1>
|
||||
<img class="top-10" src="./images/openradio_logo_neutral.png" height="64px">
|
||||
<h2 class="text-monospace text-light">[ADMIN PANEL]</h2>
|
||||
<p>You are currently not logged in as an administrator and therefore can't change the settings.</p>
|
||||
<p>Please login below.</p>
|
||||
@@ -206,5 +259,6 @@
|
||||
<script src="js/settings.js"></script>
|
||||
<script src="js/dropdown.js"></script>
|
||||
<script src="js/setup.js"></script>
|
||||
<script src="js/confighandler.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
208
web/wizard.ejs
Normal file
208
web/wizard.ejs
Normal file
@@ -0,0 +1,208 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>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">
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.7.1/jquery.min.js" integrity="sha512-v2CJ7UaYy4JwqLDIrZUI/4hqeoQieOmAZNXBeQyjo21dadnwR+8ZaIJVT8EE2iyI61OV8e6M8PP2/4hpQINQ/g==" crossorigin="anonymous" referrerpolicy="no-referrer"></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/png" href="favicon2.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body>
|
||||
<div id="wrapper" class="setup-wrapper">
|
||||
<% if (isAdminAuthenticated) { %>
|
||||
<div class="panel-100 no-bg">
|
||||
<img class="top-10" src="../images/openradio_logo_neutral.png" height="64px">
|
||||
<h1 class="top-10">FM-DX WebServer</h1>
|
||||
<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>
|
||||
|
||||
<div class="panel-100">
|
||||
|
||||
<!-- BASIC SETTINGS -->
|
||||
<div class="panel-100 step" id="step1">
|
||||
<h2 class="settings-heading">BASIC SETTINGS</h2>
|
||||
<p class="m-0">Welcome to the setup wizard!<br> Let's set up some basic things.</p>
|
||||
<h3>Tuner connection:</h3>
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<!-- BASIC SETTINGS END -->
|
||||
<!-- AUDIO SETTINGS -->
|
||||
<div id="step2" 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>
|
||||
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">
|
||||
<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="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>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- AUDIO SETTINGS END -->
|
||||
<!-- IDENTIFICATION START -->
|
||||
<div id="step3" 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">
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<div id="map"></div>
|
||||
|
||||
<h3>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://list.fmdx.pl" target="_blank" class="color-4">list.fmdx.pl</a></strong>.</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="form-group checkbox">
|
||||
<input type="checkbox" id="broadcast-tuner">
|
||||
<label for="broadcast-tuner">Show my tuner on the public list</label>
|
||||
</div>
|
||||
<div class="form-group checkbox">
|
||||
<input type="checkbox" id="tuner-public">
|
||||
<label for="tuner-public">Allow tuning without password</label>
|
||||
</div>
|
||||
<br>
|
||||
<div class="form-group">
|
||||
<label for="broadcast-address">Broadcast address:</label>
|
||||
<input class="input-text" type="text" name="broadcast-address" id="broadcast-address">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- IDENTIFICATION END -->
|
||||
<!-- ADMIN SETTINGS START -->
|
||||
<div id="step4" 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 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>
|
||||
<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>
|
||||
<p>You can now click the <strong>save button</strong> to save your settings.</p>
|
||||
</div>
|
||||
|
||||
<button class="btn-prev"><i class="fa-solid fa-arrow-left"></i></button>
|
||||
<button class="btn-next">Next</button>
|
||||
</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>
|
||||
<% } else { %>
|
||||
<div class="panel-100 no-bg">
|
||||
<img class="top-25" src="../images/openradio_logo_neutral.png" height="64px">
|
||||
<h2 class="text-monospace text-light">[ADMIN PANEL]</h2>
|
||||
<p>You are currently not logged in as an administrator and therefore can't change the settings.</p>
|
||||
<p>Please login below.</p>
|
||||
</div>
|
||||
<div class="panel-100 p-bottom-20">
|
||||
<h2>LOGIN</h2>
|
||||
<form action="./login" method="post" id="login-form">
|
||||
<input style="background-color: var(--color-2);" type="password" id="password" name="password" required>
|
||||
<button type="submit" class="br-0 w-100" style="height: 44px;">Login</button>
|
||||
</form>
|
||||
<div id="login-message"></div>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
<script src="js/settings.js"></script>
|
||||
<script src="js/dropdown.js"></script>
|
||||
<script src="js/setup.js"></script>
|
||||
<script src="js/wizard.js"></script>
|
||||
<script src="js/confighandler.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user