1
0
mirror of https://github.com/KubaPro010/fm-dx-webserver.git synced 2026-02-26 22:13:53 +01:00

chat, theme changes, bugfixes

This commit is contained in:
NoobishSVK
2024-03-01 14:15:05 +01:00
parent e9b7c2a77f
commit 67ff5af341
16 changed files with 612 additions and 82 deletions

124
index.js
View File

@@ -16,6 +16,7 @@ const process = require("process");
// Websocket handling
const WebSocket = require('ws');
const wss = new WebSocket.Server({ noServer: true });
const chatWss = new WebSocket.Server({ noServer: true });
const path = require('path');
const net = require('net');
const client = new net.Socket();
@@ -31,6 +32,19 @@ const audioStream = require('./stream/index.js');
const { parseAudioDevice } = require('./stream/parser.js');
const { configName, serverConfig, configUpdate, configSave } = require('./server_config');
const { logDebug, logError, logInfo, logWarn } = consoleCmd;
var pjson = require('./package.json');
console.log(`\x1b[32m
_____ __ __ ______ __ __ __ _
| ___| \\/ | | _ \\ \\/ / \\ \\ / /__| |__ ___ ___ _ ____ _____ _ __
| |_ | |\\/| |_____| | | \\ / \\ \\ /\\ / / _ \\ '_ \\/ __|/ _ \\ '__\\ \\ / / _ \\ '__|
| _| | | | |_____| |_| / \\ \\ V V / __/ |_) \\__ \\ __/ | \\ V / __/ |
|_| |_| |_| |____/_/\\_\\ \\_/\\_/ \\___|_.__/|___/\\___|_| \\_/ \\___|_|
`);
console.log('\x1b[0mFM-DX-Webserver', pjson.version);
console.log('\x1b[90m======================================================');
// Create a WebSocket proxy instance
const proxy = httpProxy.createProxyServer({
@@ -40,6 +54,7 @@ const proxy = httpProxy.createProxyServer({
});
let currentUsers = 0;
let connectedUsers = [];
let streamEnabled = false;
let incompleteDataBuffer = '';
@@ -225,7 +240,8 @@ app.get('/static_data', (req, res) => {
res.json({
qthLatitude: serverConfig.identification.lat,
qthLongitude: serverConfig.identification.lon,
streamEnabled: streamEnabled
streamEnabled: streamEnabled,
presets: serverConfig.webserver.presets || []
});
});
@@ -325,7 +341,11 @@ app.get('/', (req, res) => {
tunerDescMeta: removeMarkdown(serverConfig.identification.tunerDesc),
tunerLock: serverConfig.lockToAdmin,
publicTuner: serverConfig.publicTuner,
antennaSwitch: serverConfig.antennaSwitch
ownerContact: serverConfig.identification.contact,
antennaSwitch: serverConfig.antennaSwitch,
tuningLimit: serverConfig.webserver.tuningLimit,
tuningLowerLimit: serverConfig.webserver.tuningLowerLimit,
tuningUpperLimit: serverConfig.webserver.tuningUpperLimit
})
}
});
@@ -351,7 +371,8 @@ app.get('/setup', (req, res) => {
memoryUsage: (process.memoryUsage.rss() / 1024 / 1024).toFixed(1) + ' MB',
processUptime: formattedProcessUptime,
consoleOutput: consoleCmd.logs,
onlineUsers: dataHandler.dataToSend.users
onlineUsers: dataHandler.dataToSend.users,
connectedUsers: connectedUsers
});
});
});
@@ -475,9 +496,17 @@ wss.on('connection', (ws, request) => {
response.on('end', () => {
try {
const locationInfo = JSON.parse(data);
const options = { year: 'numeric', month: 'numeric', day: 'numeric', hour: '2-digit', minute: '2-digit' };
const connectionTime = new Date().toLocaleString([], options);
if(locationInfo.country === undefined) {
const userData = { ip: clientIp, location: 'Unknown', time: connectionTime };
connectedUsers.push(userData);
logInfo(`Web client \x1b[32mconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]\x1b[0m`);
} else {
const userLocation = `${locationInfo.city}, ${locationInfo.region}, ${locationInfo.country}`;
const userData = { ip: clientIp, location: userLocation, time: connectionTime };
connectedUsers.push(userData);
logInfo(`Web client \x1b[32mconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]\x1b[0m Location: ${locationInfo.city}, ${locationInfo.region}, ${locationInfo.country}`);
}
} catch (error) {
@@ -495,6 +524,14 @@ wss.on('connection', (ws, request) => {
return;
}
if(command.startsWith('T')) {
let tuneFreq = Number(command.slice(1)) / 1000;
if(serverConfig.webserver.tuningLimit === true && (tuneFreq < serverConfig.webserver.tuningLowerLimit || tuneFreq > serverConfig.webserver.tuningUpperLimit)) {
return;
}
}
if((serverConfig.publicTuner === true) || (request.session && request.session.isTuneAuthenticated === true)) {
if(serverConfig.lockToAdmin === true) {
@@ -512,7 +549,14 @@ wss.on('connection', (ws, request) => {
ws.on('close', (code, reason) => {
currentUsers--;
dataHandler.showOnlineUsers(currentUsers);
if(currentUsers === 0 && serverConfig.autoShutdown === true) {
// Find the index of the user's data in connectedUsers array
const index = connectedUsers.findIndex(user => user.ip === clientIp);
if (index !== -1) {
connectedUsers.splice(index, 1); // Remove the user's data from connectedUsers array
}
if (currentUsers === 0 && serverConfig.autoShutdown === true) {
client.write('X\n');
}
logInfo(`Web client \x1b[31mdisconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]`);
@@ -521,6 +565,69 @@ wss.on('connection', (ws, request) => {
ws.on('error', console.error);
});
// CHAT WEBSOCKET BLOCK
// Assuming chatWss is your WebSocket server instance
// Initialize an array to store chat messages
let chatHistory = [];
chatWss.on('connection', (ws, request) => {
const clientIp = request.headers['x-forwarded-for'] || request.connection.remoteAddress;
// Send chat history to the newly connected client
chatHistory.forEach(function(message) {
message.history = true; // Adding the history parameter
ws.send(JSON.stringify(message));
});
const ipMessage = {
type: 'clientIp',
ip: clientIp,
admin: request.session.isAdminAuthenticated
};
ws.send(JSON.stringify(ipMessage));
ws.on('message', function incoming(message) {
const messageData = JSON.parse(message);
messageData.ip = clientIp; // Adding IP address to the message object
const currentTime = new Date();
const hours = String(currentTime.getHours()).padStart(2, '0');
const minutes = String(currentTime.getMinutes()).padStart(2, '0');
messageData.time = `${hours}:${minutes}`; // Adding current time to the message object in hours:minutes format
if (serverConfig.webserver.banlist.includes(clientIp)) {
return; // Do not proceed further if banned
}
if(request.session.isAdminAuthenticated === true) {
messageData.admin = true;
}
// Limit message length to 255 characters
if (messageData.message.length > 255) {
messageData.message = messageData.message.substring(0, 255);
}
// Add the new message to chat history and keep only the latest 50 messages
chatHistory.push(messageData);
if (chatHistory.length > 50) {
chatHistory.shift(); // Remove the oldest message if the history exceeds 50 messages
}
const modifiedMessage = JSON.stringify(messageData);
chatWss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
client.send(modifiedMessage);
}
});
});
ws.on('close', function close() {
});
});
// Handle upgrade requests to /text and proxy /audio WebSocket connections
httpServer.on('upgrade', (request, socket, head) => {
if (request.url === '/text') {
@@ -531,11 +638,16 @@ httpServer.on('upgrade', (request, socket, head) => {
});
} else if (request.url === '/audio') {
proxy.ws(request, socket, head);
} else if (request.url === '/chat') {
sessionMiddleware(request, {}, () => {
chatWss.handleUpgrade(request, socket, head, (ws) => {
chatWss.emit('connection', ws, request);
});
});
} else {
socket.destroy();
}
}
);
});
/* Serving of HTML files */
app.use(express.static(path.join(__dirname, 'web')));

View File

@@ -1,6 +1,6 @@
{
"name": "fm-dx-webserver",
"version": "1.1.0",
"version": "1.1.1",
"description": "",
"main": "index.js",
"scripts": {

View File

@@ -13,7 +13,8 @@ if (index !== -1 && index + 1 < process.argv.length) {
let serverConfig = {
webserver: {
webserverIp: "0.0.0.0",
webserverPort: 8080
webserverPort: 8080,
banlist: []
},
xdrd: {
xdrdIp: "127.0.0.1",
@@ -55,9 +56,16 @@ function deepMerge(target, source)
}
function configUpdate(newConfig) {
if (newConfig.webserver && newConfig.webserver.banlist !== undefined) {
// If new banlist is provided, replace the existing one
serverConfig.webserver.banlist = newConfig.webserver.banlist;
delete newConfig.webserver.banlist; // Remove banlist from newConfig to avoid merging
}
deepMerge(serverConfig, newConfig);
}
function configSave() {
fs.writeFile(configName + '.json', JSON.stringify(serverConfig, null, 2), (err) => {
if (err) {

View File

@@ -102,6 +102,23 @@ label {
cursor: pointer;
}
.chatbutton.hide-desktop {
background: transparent;
border: 0;
color: var(--color-text);
position: absolute;
top: 15px;
left: 15px;
font-size: 16px;
width: 64px;
height: 64px;
line-height: 64px;
text-align: center;
border-radius: 50%;
transition: 500ms ease-in-out background;
cursor: pointer;
}
#settings:hover, #back-btn:hover, #users-online-container:hover {
background: var(--color-3);
}
@@ -199,6 +216,9 @@ label {
canvas, #flags-container {
display: none;
}
#tuner-desc {
margin-bottom: 20px !important;
}
#ps-container {
background-color: var(--color-1);
height: 100px !important;
@@ -213,8 +233,8 @@ label {
}
#data-pi {
font-size: 24px;
margin-top: 50px;
color: var(--color-text-2)
margin-top: 20px;
color: var(--color-text-2);
}
h2.show-phone {
display: inline;
@@ -230,6 +250,7 @@ label {
font-size: 10px;
text-align: left;
width: 100%;
word-break: break-all;
}
#rt-container {
height: 32px !important;
@@ -272,6 +293,13 @@ label {
.tuner-info {
margin-bottom: -60px !important;
}
#af-list ul {
height: auto !important;
}
#rt-container {
order: 2;
}
}
@media only screen and (min-width: 769px) and (max-height: 860px) {
@@ -287,6 +315,12 @@ label {
.tuner-info #tuner-name {
float: left;
font-size: 24px;
text-align: left;
}
.tuner-info #tuner-limit {
float: left;
text-align: left;
}
.tuner-info #tuner-desc {
@@ -303,6 +337,9 @@ label {
margin-top: 2px !important;
}
#af-list ul {
max-height: 330px;
height: 225px !important;
}
.chatbutton {
height: 86px !important;
}
}

View File

@@ -64,15 +64,6 @@ body {
transform: none;
}
@media (max-width: 1180px) {
#wrapper {
position: static;
transform: none;
margin: 50px auto;
width: 100%;
}
}
a {
text-decoration: none;
color: var(--color-text-2);
@@ -81,3 +72,51 @@ a {
a:hover {
border-bottom: 1px solid var(--color-4);
}
hr {
color: var(--color-4);
}
table {
border-radius: 30px;
background-color: var(--color-2);
padding: 20px;
margin: auto;
}
table th {
padding: 8px 20px;
outline: 1px solid var(--color-3);
background-color: var(--color-3);
}
table td {
padding: 8px 20px;
}
table td:nth-child(1) {
text-align: left;
}
table td:nth-child(2) {
color: var(--color-main-bright);
text-align: left;
}
table th:nth-child(1) {
border-radius: 30px 0px 0px 30px;
}
table th:nth-last-child(1){
border-radius: 0px 30px 30px 0px;
}
@media (max-width: 1180px) {
#wrapper {
position: static;
transform: none;
margin: 50px auto;
width: 100%;
}
}

View File

@@ -74,6 +74,7 @@
position: absolute;
right: 0;
text-align: center;
display: none;
background-color: var(--color-main);
}
@@ -122,6 +123,26 @@
margin: auto;
}
.modal-panel-chat {
width: 100%;
max-width: 960px;
height: 450px;
position: absolute;
bottom: 0;
margin: auto;
left: 0;
right: 0;
text-align: center;
display: none;
background-color: var(--color-main);
border-radius: 30px 30px 0px 0px;
}
.modal-panel-chat .modal-panel-sidebar {
width: 100%;
border-radius: 30px 30px 0px 0px;
}
@media only screen and (max-width: 768px) {
.modal-content {
min-width: 90% !important;
@@ -134,12 +155,18 @@
.modal-title {
position: static;
}
#closeModalButton {
.closeModalButton {
position: static;
}
.modal-panel {
width: 100%;
}
.modal-panel-chat {
height: 500px;
}
#chat-chatbox {
height: 333px !important;
}
}
@media only screen and (max-height: 768px) {

View File

@@ -10,7 +10,7 @@
.panel-10 {
width: 10%;
margin-bottom: 30px;
margin-top: 30px;
}
.panel-33 {
@@ -44,9 +44,9 @@
padding: 20px;
padding-top: 5px;
}
.panel-100 {
width: 90%;
margin: auto;
.panel-100, .panel-100.w-100 {
width: 90% !important;
margin: auto !important;
}
[class^="panel-"] {
margin: auto;
@@ -67,11 +67,10 @@
*[class^="panel-"] {
margin-top: 20px;
}
.panel-10, .panel-90 {
.panel-90 {
margin-top: 0;
}
.panel-10 {
margin: 0;
padding-bottom: 20px;
padding-right: 20px;
}

View File

@@ -52,8 +52,14 @@
<div id="wrapper">
<div class="panel-100 no-bg tuner-info">
<h1 id="tuner-name"><%= tunerName %> <% if (!publicTuner) { %><i class="fa-solid fa-key pointer" title="Only people with tune password can tune."></i>
<% } else if (tunerLock) { %><i class="fa-solid fa-lock pointer" title="Tuner is currently locked to admin."></i><% } %></h1>
<p id="tuner-desc"><%- tunerDesc %></p>
<% } else if (tunerLock) { %><i class="fa-solid fa-lock pointer" title="Tuner is currently locked to admin."></i><% } %>
</h1>
<p id="tuner-desc">
<%- tunerDesc %>
<% if(tuningLimit && tuningLimit == true){ %>
<br><span class="text-small">Limit: <span class="color-4"><%= tuningLowerLimit %> MHz - <%= tuningUpperLimit %> MHz</span></span><br>
<% } %>
</p>
<div style="clear: both"></div>
</div>
<div class="canvas-container hide-phone">
@@ -61,7 +67,7 @@
</div>
<div class="flex-container">
<div class="panel-90 bg-none">
<div class="panel-100 bg-none">
<div class="flex-container">
<div class="panel-75 flex-container no-bg">
<div class="panel-10 no-bg h-100 m-0 m-right-20 hide-phone" style="width: 100px;margin-right: 20px !important;">
@@ -148,17 +154,17 @@
</div>
</div>
<div class="flex-container">
<div class="panel-75 hover-brighten" id="rt-container" style="height: 110px;">
<div class="flex-container flex-phone flex-phone-column">
<div class="panel-75 hover-brighten" id="rt-container" style="height: 100px;">
<h2 style="margin-top: 4px;">RADIOTEXT</h2>
<div id="data-rt0"></div>
<div id="data-rt1"></div>
<div id="data-container" style="display: none;"></div>
<hr class="hide-desktop">
</div>
<div class="panel-33 hover-brighten">
<div id="data-station-container">
<h2 style="margin-top: 4px;" class="mb-0">
<h2 style="margin-top: 0;" class="mb-0">
<span id="data-station-name"></span>
</h2>
<h4 class="m-0">
@@ -174,14 +180,17 @@
</div>
<div class="panel-10 bg-none">
<div class="panel-100" style="height: 100%;">
<div class="panel-100 w-100">
<h2>AF</h2>
<div id="af-list" style="text-align: center;">
<ul>
<div id="af-list" class="p-bottom-20" style="text-align: center;">
<ul style="height: 251px;">
</ul>
</div>
</div>
<div class="panel-10 no-bg h-100 hide-phone" style="width: 100px; height: 100px;">
<button class="chatbutton bg-color-2"><i class="fa-solid fa-comments fa-lg"></i> (<span class="chat-messages-count">0</span>)</button>
</div>
</div>
</div>
<div id="flags-container-phone" class="panel-33">
@@ -201,12 +210,13 @@
</div>
<button id="settings" aria-label="Settings"><i class="fa-solid fa-gear"></i></button>
<button class="chatbutton hide-desktop bg-color-2"><i class="fa-solid fa-comments fa-lg"></i> (<span class="chat-messages-count">0</span>)</button>
<button id="users-online-container" class="hide-phone" aria-label="Online users"><i class="fa-solid fa-user"></i> <span class="users-online"></span></button>
<div id="myModal" class="modal">
<div class="modal-panel">
<div class="flex-container flex-phone" style="height: calc(100% - 100px)">
<div class="modal-panel-sidebar hover-brighten flex-center text-medium-big" id="closeModal"><i class="fa-solid fa-chevron-right"></i></div>
<div class="modal-panel-sidebar hover-brighten flex-center text-medium-big closeModal"><i class="fa-solid fa-chevron-right"></i></div>
<div class="modal-panel-content">
<h1 class="top-25">Settings</h1>
@@ -216,13 +226,13 @@
<input type="text" placeholder="Theme" readonly />
<ul class="options">
<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>
<li class="option" data-value="theme5">Orange</li>
<li class="option" data-value="theme6">Pink</li>
<li class="option" data-value="theme2">Cappuccino</li>
<li class="option" data-value="theme3">Nature</li>
<li class="option" data-value="theme4">Ocean</li>
<li class="option" data-value="theme5">Terminal</li>
<li class="option" data-value="theme6">Nightlife</li>
<li class="option" data-value="theme7">Blurple</li>
<li class="option" data-value="theme8">Bee</li>
<li class="option" data-value="theme8">Construction</li>
<li class="option" data-value="theme9">AMOLED</li>
</ul>
</div>
@@ -283,6 +293,12 @@
<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);" 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>
<br>
<br>
<% if(ownerContact){ %>
<span>Owner contact:<span><br>
<span class="text-small m-0"><%= ownerContact %></span>
<% } %>
</div>
</div>
</div>
@@ -306,6 +322,26 @@
</div>
</div>
<div class="modal-panel-chat">
<div class="modal-panel-sidebar hover-brighten flex-center text-medium-big closeModal"><i class="fa-solid fa-chevron-down"></i></div>
<div class="modal-panel-content text-left">
<div style="text-align: center;">
<input type="text" id="chat-nickname" name="chat-nickname" placeholder="Nickname">
<button class="br-0 w-100 top-10" style="height: 44px" id="chat-nickname-save">Save</button>
<p style="margin: 5px;">
Current identity: <span style="color: lime;" id="chat-admin"></span> <strong id="chat-identity-nickname">Anonymous User</strong>
</p>
</div>
<div id="chat-chatbox" class="bg-color-1" style="height: 270px;padding: 10px;overflow-y: auto;">
</div>
<div class="flex-container flex-phone" style="align-content: stretch;">
<input class="bg-color-2" type="text" id="chat-send-message" name="chat-send-message" placeholder="Send message..." style="background-color: var(--color-2);width: 100%;">
<button class="chat-send-message-btn br-0" style="width: 80px; height: 45px;"><i class="fa-solid fa-paper-plane"></i></button>
</div>
</div>
</div>
</div>
<script src="js/webserver.js"></script>

101
web/js/chat.js Normal file
View File

@@ -0,0 +1,101 @@
var chatUrl = new URL('chat', window.location.href);
chatUrl.protocol = chatUrl.protocol.replace('http', 'ws');
var chatSocketAddress = chatUrl.href;
var chatSocket = new WebSocket(chatSocketAddress);
let chatMessageCount = 0;
$(document).ready(function() {
chatSocket.onopen = function() {
};
chatSocket.onmessage = function(event) {
var messages = $('#chat-chatbox');
let messageData = JSON.parse(event.data); // Parse event.data to access its properties
let isAdmin = messageData.admin ? '<span style="color: lime">[ADMIN]</span>' : ''; // Add '[ADMIN] ' if messageData.admin is true, otherwise empty string
// Check if the message type is 'clientIp'
if (messageData.type === 'clientIp') {
// Fill the client IP into the element with ID #chat-ip
$('#chat-admin').html(isAdmin);
$('#chat-identity-nickname').attr('title', messageData.ip)
} else {
let chatMessage = `
<span class="color-2">[${messageData.time}]</span>
${isAdmin} <strong class="color-5" title="IP Address: ${messageData.ip}">${messageData.nickname}</strong>: <span style="color: var(--color-text-2);">${$('<div/>').text(messageData.message).html()}</span><br>
`;
messages.append(chatMessage);
if($('#chat-chatbox').is(':visible')) {
$('#chat-chatbox').scrollTop($('#chat-chatbox')[0].scrollHeight);
} else {
if(messageData.history !== true) {
chatMessageCount++;
$('.chat-messages-count').text(chatMessageCount);
$('.chatbutton').removeClass('bg-color-2').addClass('bg-color-4');
}
}
}
console.log(messageData);
};
$('.chat-send-message-btn').click(function() {
sendMessage();
});
$('#chat-nickname-save').click(function() {
let currentNickname = $('#chat-nickname').val();
localStorage.setItem('nickname', currentNickname);
$('#chat-identity-nickname').text(localStorage.getItem('nickname'));
$('#chat-nickname').blur();
});
$('.chatbutton').click(function() {
$('#chat-chatbox').scrollTop($('#chat-chatbox')[0].scrollHeight);
chatMessageCount = 0;
$('#chat-messages-count').text(chatMessageCount);
$('.chatbutton').removeClass('bg-color-4').addClass('bg-color-2');
$('#chat-send-message').focus();
});
$('#chat-nickname').keypress(function(event) {
if (event.which == 13) { // 13 is the keycode for Enter key
$('#chat-nickname-save').trigger('click');
}
});
$('#chat-send-message').keypress(function(event) {
if (event.which == 13) { // 13 is the keycode for Enter key
sendMessage();
}
});
if(localStorage.getItem('nickname').length > 0) {
$('#chat-nickname').val(localStorage.getItem('nickname'));
$('#chat-identity-nickname').text(localStorage.getItem('nickname'));
}
});
function sendMessage() {
var input = $('#chat-send-message');
var nickname = localStorage.getItem('nickname');
if (nickname && nickname.length > 1) {
// Only assign the nickname if it exists in localStorage and is longer than one character
nickname = nickname;
} else {
// Otherwise, use the default nickname
nickname = 'Anonymous user';
}
if (input.val().trim() !== '') {
var messageData = {
nickname: nickname,
message: input.val()
};
chatSocket.send(JSON.stringify(messageData));
input.val('');
}
}

View File

@@ -1,6 +1,17 @@
function submitData() {
const webserverIp = $('#webserver-ip').val() || '0.0.0.0';
const webserverPort = $('#webserver-port').val() || '8080';
const tuningLimit = $('#tuning-limit').is(":checked") || false;
const tuningLowerLimit = $('#tuning-lower-limit').val() || '0';
const tuningUpperLimit = $('#tuning-upper-limit').val() || '108';
let presets = [];
presets.push($('#preset1').val() || '87.5');
presets.push($('#preset2').val() || '87.5');
presets.push($('#preset3').val() || '87.5');
presets.push($('#preset4').val() || '87.5');
let banlist = [];
validateAndAdd(banlist);
const xdrdIp = $('#xdrd-ip').val() || '127.0.0.1';
const xdrdPort = $('#xdrd-port').val() || '7373';
@@ -34,6 +45,11 @@ function submitData() {
webserver: {
webserverIp,
webserverPort,
tuningLimit,
tuningLowerLimit,
tuningUpperLimit,
presets,
banlist
},
xdrd: {
xdrdIp,
@@ -76,6 +92,7 @@ function submitData() {
data: JSON.stringify(data),
success: function (message) {
alert(message);
console.log(data);
},
error: function (error) {
console.error(error);
@@ -96,6 +113,18 @@ function submitData() {
.then(data => {
$('#webserver-ip').val(data.webserver.webserverIp);
$('#webserver-port').val(data.webserver.webserverPort);
$('#tuning-limit').prop("checked", data.webserver.tuningLimit);
$('#tuning-lower-limit').val(data.webserver.tuningLowerLimit || "");
$('#tuning-upper-limit').val(data.webserver.tuningUpperLimit || "");
if(Array.isArray(data.webserver.presets)) {
$('#preset1').val(data.webserver.presets[0] || "");
$('#preset2').val(data.webserver.presets[1] || "");
$('#preset3').val(data.webserver.presets[2] || "");
$('#preset4').val(data.webserver.presets[3] || "");
}
$('#ip-addresses').val(data.webserver.banlist?.join('\n') || "");
$('#xdrd-ip').val(data.xdrd.xdrdIp);
$('#xdrd-port').val(data.xdrd.xdrdPort);
@@ -143,3 +172,18 @@ function submitData() {
console.error('Error fetching data:', error.message);
});
}
function validateAndAdd(banlist) {
var textarea = $('#ip-addresses');
var ipAddresses = textarea.val().split('\n');
// Regular expression to validate IP address
var ipRegex = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/;
ipAddresses.forEach(function(ip) {
if (ipRegex.test(ip)) {
banlist.push(ip);
}
});
}

View File

@@ -8,8 +8,11 @@ function getInitialSettings() {
// Use the received data (data.qthLatitude, data.qthLongitude) as needed
localStorage.setItem('qthLatitude', data.qthLatitude);
localStorage.setItem('qthLongitude', data.qthLongitude);
localStorage.setItem('audioPort', data.audioPort);
localStorage.setItem('streamEnabled', data.streamEnabled);
localStorage.setItem('preset1', data.presets[0]);
localStorage.setItem('preset2', data.presets[1]);
localStorage.setItem('preset3', data.presets[2]);
localStorage.setItem('preset4', data.presets[3]);
},
error: function (error) {
console.error('Error:', error);

View File

@@ -313,7 +313,13 @@ function updateCanvas(parsedData, signalChart) {
socket.onmessage = (event) => {
parsedData = JSON.parse(event.data);
updatePanels(parsedData);
data.push(parsedData.signal);
if(localStorage.getItem("smoothSignal") == 'true') {
const sum = signalData.reduce((acc, strNum) => acc + parseFloat(strNum), 0);
const averageSignal = sum / signalData.length;
data.push(averageSignal);
} else {
data.push(parsedData.signal);
}
};
function compareNumbers(a, b) {
@@ -365,13 +371,13 @@ function checkKey(e) {
if (socket.readyState === WebSocket.OPEN) {
switch (e.keyCode) {
case 66: // Back to previous frequency
socket.send("T" + (previousFreq * 1000));
tuneTo(previousFreq);
break;
case 82: // RDS Reset (R key)
socket.send("T" + (currentFreq.toFixed(1) * 1000));
tuneTo(Number(currentFreq));
break;
case 38:
socket.send("T" + (Math.round(currentFreq*1000) + ((currentFreq > 30) ? 10 : 1)));
socket.send("T" + (Math.round(currentFreq*1000) + ((currentFreq > 30) ? 10 : 1)));
break;
case 40:
socket.send("T" + (Math.round(currentFreq*1000) - ((currentFreq > 30) ? 10 : 1)));
@@ -382,6 +388,22 @@ function checkKey(e) {
case 39:
tuneUp();
break;
case 112: // F1
e.preventDefault();
tuneTo(Number(localStorage.getItem('preset1')));
break;
case 113: // F2
e.preventDefault();
tuneTo(Number(localStorage.getItem('preset2')));
break;
case 114: // F3
e.preventDefault();
tuneTo(Number(localStorage.getItem('preset3')));
break;
case 115: // F4
e.preventDefault();
tuneTo(Number(localStorage.getItem('preset4')));
break;
default:
// Handle default case if needed
break;

View File

@@ -1,33 +1,56 @@
$(document).ready(function() {
// Cache jQuery objects for reuse
var modal = $("#myModal");
var modalPanel = $(".modal-panel");
var chatPanel = $(".modal-panel-chat");
var chatOpenBtn = $(".chatbutton");
var openBtn = $("#settings");
var closeBtn = $("#closeModal, #closeModalButton");
var closeBtn = $(".closeModal, .closeModalButton");
// Function to open the modal
function openModal() {
modal.css("display", "block");
modalPanel.css("display", "block");
setTimeout(function() {
modal.css("opacity", 1);
}, 10);
}
// Function to close the modal
function closeModal() {
modal.css("opacity", 0);
function openChat() {
modal.css("display", "block");
chatPanel.css("display", "block");
setTimeout(function() {
modal.css("display", "none");
}, 300);
modal.css("opacity", 1);
}, 10);
}
// Event listeners for the open and close buttons
openBtn.on("click", openModal);
closeBtn.on("click", closeModal);
// Close the modal when clicking outside of it
$(document).on("click", function(event) {
if ($(event.target).is(modal)) {
closeModal();
}
});
// Function to close the modal
function closeModal() {
modal.css("opacity", 0);
setTimeout(function() {
modal.css("display", "none");
modalPanel.css("display", "none");
chatPanel.css("display", "none");
}, 300);
}
// Event listeners for the open and close buttons
openBtn.on("click", openModal);
chatOpenBtn.on("click", openChat);
closeBtn.on("click", closeModal);
// Close the modal when clicking outside of it
$(document).on("click", function(event) {
if ($(event.target).is(modal)) {
closeModal();
}
});
// Close the modal when pressing ESC key
$(document).on("keydown", function(event) {
if (event.key === "Escape") {
closeModal();
}
});
});

View File

@@ -1,4 +1,4 @@
var currentVersion = 'v1.1.0 [27.2.2024]';
var currentVersion = 'v1.1.1 [1.3.2024]';
/**
* Themes
@@ -8,13 +8,13 @@
*/
const themes = {
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
theme5: [ 'rgba(23, 17, 6, 1)', 'rgba(245, 182, 66, 1)', 'rgba(255, 255, 255, 1)' ], // Orange
theme6: [ 'rgba(33, 9, 29, 1)', 'rgba(237, 81, 211, 1)', 'rgba(255, 255, 255, 1)' ], // Pink
theme2: [ 'rgba(21, 32, 33, 1)', 'rgba(203, 202, 165, 1)', 'rgba(255, 255, 255, 1)' ], // Cappuccino
theme3: [ 'rgba(18, 18, 12, 1)', 'rgba(169, 255, 112, 1)', 'rgba(255, 255, 255, 1)' ], // Nature
theme4: [ 'rgba(12, 28, 27, 1)', 'rgba(104, 247, 238, 1)', 'rgba(255, 255, 255, 1)' ], // Ocean
theme5: [ 'rgba(23, 17, 6, 1)', 'rgba(245, 182, 66, 1)', 'rgba(255, 255, 255, 1)' ], // Terminal
theme6: [ 'rgba(33, 9, 29, 1)', 'rgba(250, 82, 141, 1)', 'rgba(255, 255, 255, 1)' ], // Nightlife
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
theme8: [ 'rgba(252, 186, 3, 1)', 'rgba(0, 0, 0, 1)', 'rgba(0, 0, 0, 1)' ], // Construction
theme9: [ 'rgba(0, 0, 0, 1)', 'rgba(204, 204, 204, 1)', 'rgba(255, 255, 255, 1)' ], // AMOLED
};

View File

@@ -2,3 +2,4 @@ $.getScript('./js/main.js');
$.getScript('./js/dropdown.js');
$.getScript('./js/modal.js');
$.getScript('./js/settings.js');
$.getScript('./js/chat.js');

View File

@@ -23,6 +23,7 @@
<li data-panel="status" class="active">Status</li>
<li data-panel="connection">Connection</li>
<li data-panel="audio">Audio</li>
<li data-panel="webserver">Webserver</li>
<li data-panel="identification">Identification</li>
<li data-panel="mapbroadcast">Online map</li>
<li data-panel="maintenance">Maintenance</li>
@@ -36,23 +37,49 @@
<h2>STATUS</h2>
<div class="panel-100 flex-container auto">
<div class="panel-33 bg-color-2">
<div class="panel-33">
<span class="text-medium-big color-5"><%= onlineUsers %></span>
<p>Online users</p>
</div>
<div class="panel-33 bg-color-2">
<div class="panel-33">
<span class="text-medium-big color-5"><%= memoryUsage %></span>
<p>Memory usage</p>
</div>
<div class="panel-33 bg-color-2">
<div class="panel-33">
<span class="text-medium-big color-5"><%= processUptime %></span>
<p>Uptime</p>
</div>
</div>
<h2>Console output</h2>
<h3>Current users</h3>
<table>
<thead>
<tr>
<th>IP Address</th>
<th>Location</th>
<th>Online since</th>
</tr>
</thead>
<tbody>
<% if (connectedUsers.length > 0) { %>
<% connectedUsers.forEach(user => { %>
<tr>
<td><%= user.ip %></td>
<td><%= user.location %></td>
<td><%= user.time %></td>
</tr>
<% }); %>
<% } else { %>
<tr>
<td colspan="3" style="text-align: center">No users online</td>
</tr>
<% } %>
</tbody>
</table>
<h3>Console</h3>
<% 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) { %>
@@ -154,6 +181,61 @@
</div>
</div>
<div class="panel-100 tab-content" id="webserver">
<h2>Webserver settings</h2>
<h3>Antenna options</h3>
<div class="form-group checkbox bottom-20">
<input type="checkbox" id="antenna-switch">
<label for="antenna-switch">Enable the antenna switch</label>
</div><br>
<h3>Tuning options</h3>
<p>If you want to limit which frequencies the users can tune to, you can set the lower and upper limit here.<br>
<span class="text-gray">Enter frequencies in MHz.</span>
</p>
<div class="form-group checkbox">
<input type="checkbox" id="tuning-limit">
<label for="tuning-limit">Limit tuning</label>
</div><br>
<div class="form-group">
<label for="tuning-lower-limit">Lower limit:</label>
<input class="input-text w-100" type="text" placeholder="0" name="tuning-lower-limit" id="tuning-lower-limit">
</div>
<div class="form-group">
<label for="tuning-upper-limit">Upper Limit:</label>
<input class="input-text w-100" type="text" placeholder="108" name="tuning-upper-limit" id="tuning-upper-limit">
</div>
<h3>Presets</h3>
<p>You can set up to 4 presets. These presets are accessible with the F1-F4 buttons.<br>
<span class="text-gray">Enter frequencies in MHz.</span></p>
<div class="form-group">
<label for="preset1">Preset 1:</label>
<input class="input-text w-100" type="text" placeholder="87.5" name="preset1" id="preset1">
</div>
<div class="form-group">
<label for="preset2">Preset 2:</label>
<input class="input-text w-100" type="text" placeholder="87.5" name="preset2" id="preset2">
</div>
<div class="form-group">
<label for="preset1">Preset 3:</label>
<input class="input-text w-100" type="text" placeholder="87.5" name="preset3" id="preset3">
</div>
<div class="form-group">
<label for="preset1">Preset 4:</label>
<input class="input-text w-100" type="text" placeholder="87.5" name="preset4" id="preset4">
</div>
<h3>Banlist</h3>
<p>If you have users that don't behave in your chat, 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>
<div class="form-group">
<label for="preset1">Banned users:</label>
<textarea id="ip-addresses" placeholder="123.45.67.8"></textarea>
</div>
</div>
<div class="panel-100 tab-content" id="identification">
<h2>Tuner Identification info</h2>
@@ -216,10 +298,6 @@
<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>