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
chat, theme changes, bugfixes
This commit is contained in:
128
index.js
128
index.js
@@ -16,6 +16,7 @@ const process = require("process");
|
|||||||
// Websocket handling
|
// Websocket handling
|
||||||
const WebSocket = require('ws');
|
const WebSocket = require('ws');
|
||||||
const wss = new WebSocket.Server({ noServer: true });
|
const wss = new WebSocket.Server({ noServer: true });
|
||||||
|
const chatWss = new WebSocket.Server({ noServer: true });
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const net = require('net');
|
const net = require('net');
|
||||||
const client = new net.Socket();
|
const client = new net.Socket();
|
||||||
@@ -31,6 +32,19 @@ const audioStream = require('./stream/index.js');
|
|||||||
const { parseAudioDevice } = require('./stream/parser.js');
|
const { parseAudioDevice } = require('./stream/parser.js');
|
||||||
const { configName, serverConfig, configUpdate, configSave } = require('./server_config');
|
const { configName, serverConfig, configUpdate, configSave } = require('./server_config');
|
||||||
const { logDebug, logError, logInfo, logWarn } = consoleCmd;
|
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
|
// Create a WebSocket proxy instance
|
||||||
const proxy = httpProxy.createProxyServer({
|
const proxy = httpProxy.createProxyServer({
|
||||||
@@ -40,6 +54,7 @@ const proxy = httpProxy.createProxyServer({
|
|||||||
});
|
});
|
||||||
|
|
||||||
let currentUsers = 0;
|
let currentUsers = 0;
|
||||||
|
let connectedUsers = [];
|
||||||
let streamEnabled = false;
|
let streamEnabled = false;
|
||||||
let incompleteDataBuffer = '';
|
let incompleteDataBuffer = '';
|
||||||
|
|
||||||
@@ -225,7 +240,8 @@ app.get('/static_data', (req, res) => {
|
|||||||
res.json({
|
res.json({
|
||||||
qthLatitude: serverConfig.identification.lat,
|
qthLatitude: serverConfig.identification.lat,
|
||||||
qthLongitude: serverConfig.identification.lon,
|
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),
|
tunerDescMeta: removeMarkdown(serverConfig.identification.tunerDesc),
|
||||||
tunerLock: serverConfig.lockToAdmin,
|
tunerLock: serverConfig.lockToAdmin,
|
||||||
publicTuner: serverConfig.publicTuner,
|
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',
|
memoryUsage: (process.memoryUsage.rss() / 1024 / 1024).toFixed(1) + ' MB',
|
||||||
processUptime: formattedProcessUptime,
|
processUptime: formattedProcessUptime,
|
||||||
consoleOutput: consoleCmd.logs,
|
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', () => {
|
response.on('end', () => {
|
||||||
try {
|
try {
|
||||||
const locationInfo = JSON.parse(data);
|
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) {
|
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`);
|
logInfo(`Web client \x1b[32mconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]\x1b[0m`);
|
||||||
} else {
|
} 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}`);
|
logInfo(`Web client \x1b[32mconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]\x1b[0m Location: ${locationInfo.city}, ${locationInfo.region}, ${locationInfo.country}`);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -495,6 +524,14 @@ wss.on('connection', (ws, request) => {
|
|||||||
return;
|
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.publicTuner === true) || (request.session && request.session.isTuneAuthenticated === true)) {
|
||||||
|
|
||||||
if(serverConfig.lockToAdmin === true) {
|
if(serverConfig.lockToAdmin === true) {
|
||||||
@@ -512,15 +549,85 @@ wss.on('connection', (ws, request) => {
|
|||||||
ws.on('close', (code, reason) => {
|
ws.on('close', (code, reason) => {
|
||||||
currentUsers--;
|
currentUsers--;
|
||||||
dataHandler.showOnlineUsers(currentUsers);
|
dataHandler.showOnlineUsers(currentUsers);
|
||||||
if(currentUsers === 0 && serverConfig.autoShutdown === true) {
|
|
||||||
client.write('X\n');
|
// 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}]`);
|
logInfo(`Web client \x1b[31mdisconnected\x1b[0m (${clientIp}) \x1b[90m[${currentUsers}]`);
|
||||||
});
|
});
|
||||||
|
|
||||||
ws.on('error', console.error);
|
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
|
// Handle upgrade requests to /text and proxy /audio WebSocket connections
|
||||||
httpServer.on('upgrade', (request, socket, head) => {
|
httpServer.on('upgrade', (request, socket, head) => {
|
||||||
if (request.url === '/text') {
|
if (request.url === '/text') {
|
||||||
@@ -531,11 +638,16 @@ httpServer.on('upgrade', (request, socket, head) => {
|
|||||||
});
|
});
|
||||||
} else if (request.url === '/audio') {
|
} else if (request.url === '/audio') {
|
||||||
proxy.ws(request, socket, head);
|
proxy.ws(request, socket, head);
|
||||||
|
} else if (request.url === '/chat') {
|
||||||
|
sessionMiddleware(request, {}, () => {
|
||||||
|
chatWss.handleUpgrade(request, socket, head, (ws) => {
|
||||||
|
chatWss.emit('connection', ws, request);
|
||||||
|
});
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
socket.destroy();
|
socket.destroy();
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
);
|
|
||||||
|
|
||||||
/* Serving of HTML files */
|
/* Serving of HTML files */
|
||||||
app.use(express.static(path.join(__dirname, 'web')));
|
app.use(express.static(path.join(__dirname, 'web')));
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "fm-dx-webserver",
|
"name": "fm-dx-webserver",
|
||||||
"version": "1.1.0",
|
"version": "1.1.1",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ if (index !== -1 && index + 1 < process.argv.length) {
|
|||||||
let serverConfig = {
|
let serverConfig = {
|
||||||
webserver: {
|
webserver: {
|
||||||
webserverIp: "0.0.0.0",
|
webserverIp: "0.0.0.0",
|
||||||
webserverPort: 8080
|
webserverPort: 8080,
|
||||||
|
banlist: []
|
||||||
},
|
},
|
||||||
xdrd: {
|
xdrd: {
|
||||||
xdrdIp: "127.0.0.1",
|
xdrdIp: "127.0.0.1",
|
||||||
@@ -55,9 +56,16 @@ function deepMerge(target, source)
|
|||||||
}
|
}
|
||||||
|
|
||||||
function configUpdate(newConfig) {
|
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);
|
deepMerge(serverConfig, newConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function configSave() {
|
function configSave() {
|
||||||
fs.writeFile(configName + '.json', JSON.stringify(serverConfig, null, 2), (err) => {
|
fs.writeFile(configName + '.json', JSON.stringify(serverConfig, null, 2), (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|||||||
@@ -102,6 +102,23 @@ label {
|
|||||||
cursor: pointer;
|
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 {
|
#settings:hover, #back-btn:hover, #users-online-container:hover {
|
||||||
background: var(--color-3);
|
background: var(--color-3);
|
||||||
}
|
}
|
||||||
@@ -199,6 +216,9 @@ label {
|
|||||||
canvas, #flags-container {
|
canvas, #flags-container {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
#tuner-desc {
|
||||||
|
margin-bottom: 20px !important;
|
||||||
|
}
|
||||||
#ps-container {
|
#ps-container {
|
||||||
background-color: var(--color-1);
|
background-color: var(--color-1);
|
||||||
height: 100px !important;
|
height: 100px !important;
|
||||||
@@ -213,8 +233,8 @@ label {
|
|||||||
}
|
}
|
||||||
#data-pi {
|
#data-pi {
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
margin-top: 50px;
|
margin-top: 20px;
|
||||||
color: var(--color-text-2)
|
color: var(--color-text-2);
|
||||||
}
|
}
|
||||||
h2.show-phone {
|
h2.show-phone {
|
||||||
display: inline;
|
display: inline;
|
||||||
@@ -230,6 +250,7 @@ label {
|
|||||||
font-size: 10px;
|
font-size: 10px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
#rt-container {
|
#rt-container {
|
||||||
height: 32px !important;
|
height: 32px !important;
|
||||||
@@ -272,6 +293,13 @@ label {
|
|||||||
.tuner-info {
|
.tuner-info {
|
||||||
margin-bottom: -60px !important;
|
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) {
|
@media only screen and (min-width: 769px) and (max-height: 860px) {
|
||||||
@@ -287,6 +315,12 @@ label {
|
|||||||
.tuner-info #tuner-name {
|
.tuner-info #tuner-name {
|
||||||
float: left;
|
float: left;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tuner-info #tuner-limit {
|
||||||
|
float: left;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tuner-info #tuner-desc {
|
.tuner-info #tuner-desc {
|
||||||
@@ -303,6 +337,9 @@ label {
|
|||||||
margin-top: 2px !important;
|
margin-top: 2px !important;
|
||||||
}
|
}
|
||||||
#af-list ul {
|
#af-list ul {
|
||||||
max-height: 330px;
|
height: 225px !important;
|
||||||
|
}
|
||||||
|
.chatbutton {
|
||||||
|
height: 86px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -64,15 +64,6 @@ body {
|
|||||||
transform: none;
|
transform: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 1180px) {
|
|
||||||
#wrapper {
|
|
||||||
position: static;
|
|
||||||
transform: none;
|
|
||||||
margin: 50px auto;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
a {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--color-text-2);
|
color: var(--color-text-2);
|
||||||
@@ -80,4 +71,52 @@ a {
|
|||||||
|
|
||||||
a:hover {
|
a:hover {
|
||||||
border-bottom: 1px solid var(--color-4);
|
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%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -74,6 +74,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
right: 0;
|
right: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
display: none;
|
||||||
background-color: var(--color-main);
|
background-color: var(--color-main);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,6 +123,26 @@
|
|||||||
margin: auto;
|
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) {
|
@media only screen and (max-width: 768px) {
|
||||||
.modal-content {
|
.modal-content {
|
||||||
min-width: 90% !important;
|
min-width: 90% !important;
|
||||||
@@ -134,12 +155,18 @@
|
|||||||
.modal-title {
|
.modal-title {
|
||||||
position: static;
|
position: static;
|
||||||
}
|
}
|
||||||
#closeModalButton {
|
.closeModalButton {
|
||||||
position: static;
|
position: static;
|
||||||
}
|
}
|
||||||
.modal-panel {
|
.modal-panel {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
.modal-panel-chat {
|
||||||
|
height: 500px;
|
||||||
|
}
|
||||||
|
#chat-chatbox {
|
||||||
|
height: 333px !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-height: 768px) {
|
@media only screen and (max-height: 768px) {
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
.panel-10 {
|
.panel-10 {
|
||||||
width: 10%;
|
width: 10%;
|
||||||
margin-bottom: 30px;
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-33 {
|
.panel-33 {
|
||||||
@@ -44,9 +44,9 @@
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
padding-top: 5px;
|
padding-top: 5px;
|
||||||
}
|
}
|
||||||
.panel-100 {
|
.panel-100, .panel-100.w-100 {
|
||||||
width: 90%;
|
width: 90% !important;
|
||||||
margin: auto;
|
margin: auto !important;
|
||||||
}
|
}
|
||||||
[class^="panel-"] {
|
[class^="panel-"] {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
@@ -67,11 +67,10 @@
|
|||||||
*[class^="panel-"] {
|
*[class^="panel-"] {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
.panel-10, .panel-90 {
|
.panel-90 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
.panel-10 {
|
.panel-10 {
|
||||||
margin: 0;
|
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,8 +52,14 @@
|
|||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
<div class="panel-100 no-bg tuner-info">
|
<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>
|
<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>
|
<% } else if (tunerLock) { %><i class="fa-solid fa-lock pointer" title="Tuner is currently locked to admin."></i><% } %>
|
||||||
<p id="tuner-desc"><%- tunerDesc %></p>
|
</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 style="clear: both"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="canvas-container hide-phone">
|
<div class="canvas-container hide-phone">
|
||||||
@@ -61,7 +67,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex-container">
|
<div class="flex-container">
|
||||||
<div class="panel-90 bg-none">
|
<div class="panel-100 bg-none">
|
||||||
<div class="flex-container">
|
<div class="flex-container">
|
||||||
<div class="panel-75 flex-container no-bg">
|
<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;">
|
<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>
|
</div>
|
||||||
|
|
||||||
<div class="flex-container">
|
<div class="flex-container flex-phone flex-phone-column">
|
||||||
<div class="panel-75 hover-brighten" id="rt-container" style="height: 110px;">
|
<div class="panel-75 hover-brighten" id="rt-container" style="height: 100px;">
|
||||||
<h2 style="margin-top: 4px;">RADIOTEXT</h2>
|
<h2 style="margin-top: 4px;">RADIOTEXT</h2>
|
||||||
<div id="data-rt0"></div>
|
<div id="data-rt0"></div>
|
||||||
<div id="data-rt1"></div>
|
<div id="data-rt1"></div>
|
||||||
<div id="data-container" style="display: none;"></div>
|
<hr class="hide-desktop">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel-33 hover-brighten">
|
<div class="panel-33 hover-brighten">
|
||||||
<div id="data-station-container">
|
<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>
|
<span id="data-station-name"></span>
|
||||||
</h2>
|
</h2>
|
||||||
<h4 class="m-0">
|
<h4 class="m-0">
|
||||||
@@ -174,14 +180,17 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel-10 bg-none">
|
<div class="panel-10 bg-none">
|
||||||
<div class="panel-100" style="height: 100%;">
|
<div class="panel-100 w-100">
|
||||||
<h2>AF</h2>
|
<h2>AF</h2>
|
||||||
<div id="af-list" style="text-align: center;">
|
<div id="af-list" class="p-bottom-20" style="text-align: center;">
|
||||||
<ul>
|
<ul style="height: 251px;">
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
<div id="flags-container-phone" class="panel-33">
|
<div id="flags-container-phone" class="panel-33">
|
||||||
@@ -201,12 +210,13 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button id="settings" aria-label="Settings"><i class="fa-solid fa-gear"></i></button>
|
<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>
|
<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 id="myModal" class="modal">
|
||||||
<div class="modal-panel">
|
<div class="modal-panel">
|
||||||
<div class="flex-container flex-phone" style="height: calc(100% - 100px)">
|
<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">
|
<div class="modal-panel-content">
|
||||||
<h1 class="top-25">Settings</h1>
|
<h1 class="top-25">Settings</h1>
|
||||||
|
|
||||||
@@ -216,13 +226,13 @@
|
|||||||
<input type="text" placeholder="Theme" readonly />
|
<input type="text" placeholder="Theme" readonly />
|
||||||
<ul class="options">
|
<ul class="options">
|
||||||
<li class="option" data-value="theme1">Default</li>
|
<li class="option" data-value="theme1">Default</li>
|
||||||
<li class="option" data-value="theme2">Red</li>
|
<li class="option" data-value="theme2">Cappuccino</li>
|
||||||
<li class="option" data-value="theme3">Green</li>
|
<li class="option" data-value="theme3">Nature</li>
|
||||||
<li class="option" data-value="theme4">Cyan</li>
|
<li class="option" data-value="theme4">Ocean</li>
|
||||||
<li class="option" data-value="theme5">Orange</li>
|
<li class="option" data-value="theme5">Terminal</li>
|
||||||
<li class="option" data-value="theme6">Pink</li>
|
<li class="option" data-value="theme6">Nightlife</li>
|
||||||
<li class="option" data-value="theme7">Blurple</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>
|
<li class="option" data-value="theme9">AMOLED</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</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>
|
<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 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>
|
<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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -305,7 +321,27 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
|
|
||||||
<script src="js/webserver.js"></script>
|
<script src="js/webserver.js"></script>
|
||||||
|
|||||||
101
web/js/chat.js
Normal file
101
web/js/chat.js
Normal 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('');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,17 @@
|
|||||||
function submitData() {
|
function submitData() {
|
||||||
const webserverIp = $('#webserver-ip').val() || '0.0.0.0';
|
const webserverIp = $('#webserver-ip').val() || '0.0.0.0';
|
||||||
const webserverPort = $('#webserver-port').val() || '8080';
|
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 xdrdIp = $('#xdrd-ip').val() || '127.0.0.1';
|
||||||
const xdrdPort = $('#xdrd-port').val() || '7373';
|
const xdrdPort = $('#xdrd-port').val() || '7373';
|
||||||
@@ -34,6 +45,11 @@ function submitData() {
|
|||||||
webserver: {
|
webserver: {
|
||||||
webserverIp,
|
webserverIp,
|
||||||
webserverPort,
|
webserverPort,
|
||||||
|
tuningLimit,
|
||||||
|
tuningLowerLimit,
|
||||||
|
tuningUpperLimit,
|
||||||
|
presets,
|
||||||
|
banlist
|
||||||
},
|
},
|
||||||
xdrd: {
|
xdrd: {
|
||||||
xdrdIp,
|
xdrdIp,
|
||||||
@@ -76,6 +92,7 @@ function submitData() {
|
|||||||
data: JSON.stringify(data),
|
data: JSON.stringify(data),
|
||||||
success: function (message) {
|
success: function (message) {
|
||||||
alert(message);
|
alert(message);
|
||||||
|
console.log(data);
|
||||||
},
|
},
|
||||||
error: function (error) {
|
error: function (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -96,6 +113,18 @@ function submitData() {
|
|||||||
.then(data => {
|
.then(data => {
|
||||||
$('#webserver-ip').val(data.webserver.webserverIp);
|
$('#webserver-ip').val(data.webserver.webserverIp);
|
||||||
$('#webserver-port').val(data.webserver.webserverPort);
|
$('#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-ip').val(data.xdrd.xdrdIp);
|
||||||
$('#xdrd-port').val(data.xdrd.xdrdPort);
|
$('#xdrd-port').val(data.xdrd.xdrdPort);
|
||||||
@@ -143,3 +172,18 @@ function submitData() {
|
|||||||
console.error('Error fetching data:', error.message);
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -8,8 +8,11 @@ function getInitialSettings() {
|
|||||||
// Use the received data (data.qthLatitude, data.qthLongitude) as needed
|
// Use the received data (data.qthLatitude, data.qthLongitude) as needed
|
||||||
localStorage.setItem('qthLatitude', data.qthLatitude);
|
localStorage.setItem('qthLatitude', data.qthLatitude);
|
||||||
localStorage.setItem('qthLongitude', data.qthLongitude);
|
localStorage.setItem('qthLongitude', data.qthLongitude);
|
||||||
localStorage.setItem('audioPort', data.audioPort);
|
|
||||||
localStorage.setItem('streamEnabled', data.streamEnabled);
|
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) {
|
error: function (error) {
|
||||||
console.error('Error:', error);
|
console.error('Error:', error);
|
||||||
|
|||||||
@@ -313,7 +313,13 @@ function updateCanvas(parsedData, signalChart) {
|
|||||||
socket.onmessage = (event) => {
|
socket.onmessage = (event) => {
|
||||||
parsedData = JSON.parse(event.data);
|
parsedData = JSON.parse(event.data);
|
||||||
updatePanels(parsedData);
|
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) {
|
function compareNumbers(a, b) {
|
||||||
@@ -365,13 +371,13 @@ function checkKey(e) {
|
|||||||
if (socket.readyState === WebSocket.OPEN) {
|
if (socket.readyState === WebSocket.OPEN) {
|
||||||
switch (e.keyCode) {
|
switch (e.keyCode) {
|
||||||
case 66: // Back to previous frequency
|
case 66: // Back to previous frequency
|
||||||
socket.send("T" + (previousFreq * 1000));
|
tuneTo(previousFreq);
|
||||||
break;
|
break;
|
||||||
case 82: // RDS Reset (R key)
|
case 82: // RDS Reset (R key)
|
||||||
socket.send("T" + (currentFreq.toFixed(1) * 1000));
|
tuneTo(Number(currentFreq));
|
||||||
break;
|
break;
|
||||||
case 38:
|
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;
|
break;
|
||||||
case 40:
|
case 40:
|
||||||
socket.send("T" + (Math.round(currentFreq*1000) - ((currentFreq > 30) ? 10 : 1)));
|
socket.send("T" + (Math.round(currentFreq*1000) - ((currentFreq > 30) ? 10 : 1)));
|
||||||
@@ -382,6 +388,22 @@ function checkKey(e) {
|
|||||||
case 39:
|
case 39:
|
||||||
tuneUp();
|
tuneUp();
|
||||||
break;
|
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:
|
default:
|
||||||
// Handle default case if needed
|
// Handle default case if needed
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -1,33 +1,56 @@
|
|||||||
$(document).ready(function() {
|
$(document).ready(function() {
|
||||||
// Cache jQuery objects for reuse
|
// Cache jQuery objects for reuse
|
||||||
var modal = $("#myModal");
|
var modal = $("#myModal");
|
||||||
|
var modalPanel = $(".modal-panel");
|
||||||
|
var chatPanel = $(".modal-panel-chat");
|
||||||
|
var chatOpenBtn = $(".chatbutton");
|
||||||
var openBtn = $("#settings");
|
var openBtn = $("#settings");
|
||||||
var closeBtn = $("#closeModal, #closeModalButton");
|
var closeBtn = $(".closeModal, .closeModalButton");
|
||||||
|
|
||||||
// Function to open the modal
|
// Function to open the modal
|
||||||
function openModal() {
|
function openModal() {
|
||||||
modal.css("display", "block");
|
modal.css("display", "block");
|
||||||
|
modalPanel.css("display", "block");
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
modal.css("opacity", 1);
|
modal.css("opacity", 1);
|
||||||
}, 10);
|
}, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to close the modal
|
function openChat() {
|
||||||
function closeModal() {
|
modal.css("display", "block");
|
||||||
modal.css("opacity", 0);
|
chatPanel.css("display", "block");
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
modal.css("display", "none");
|
modal.css("opacity", 1);
|
||||||
}, 300);
|
}, 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
|
// Function to close the modal
|
||||||
$(document).on("click", function(event) {
|
function closeModal() {
|
||||||
if ($(event.target).is(modal)) {
|
modal.css("opacity", 0);
|
||||||
closeModal();
|
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();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
var currentVersion = 'v1.1.0 [27.2.2024]';
|
var currentVersion = 'v1.1.1 [1.3.2024]';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Themes
|
* Themes
|
||||||
@@ -8,13 +8,13 @@
|
|||||||
*/
|
*/
|
||||||
const themes = {
|
const themes = {
|
||||||
theme1: ['rgba(32, 34, 40, 1)', 'rgba(88, 219, 171, 1)', 'rgba(255, 255, 255, 1)' ], // Retro (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
|
theme2: [ 'rgba(21, 32, 33, 1)', 'rgba(203, 202, 165, 1)', 'rgba(255, 255, 255, 1)' ], // Cappuccino
|
||||||
theme3: [ 'rgba(18, 28, 12, 1)', 'rgba(169, 255, 112, 1)', 'rgba(255, 255, 255, 1)' ], // Green
|
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)' ], // Cyan
|
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)' ], // Orange
|
theme5: [ 'rgba(23, 17, 6, 1)', 'rgba(245, 182, 66, 1)', 'rgba(255, 255, 255, 1)' ], // Terminal
|
||||||
theme6: [ 'rgba(33, 9, 29, 1)', 'rgba(237, 81, 211, 1)', 'rgba(255, 255, 255, 1)' ], // Pink
|
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
|
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
|
theme9: [ 'rgba(0, 0, 0, 1)', 'rgba(204, 204, 204, 1)', 'rgba(255, 255, 255, 1)' ], // AMOLED
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,3 +2,4 @@ $.getScript('./js/main.js');
|
|||||||
$.getScript('./js/dropdown.js');
|
$.getScript('./js/dropdown.js');
|
||||||
$.getScript('./js/modal.js');
|
$.getScript('./js/modal.js');
|
||||||
$.getScript('./js/settings.js');
|
$.getScript('./js/settings.js');
|
||||||
|
$.getScript('./js/chat.js');
|
||||||
@@ -23,6 +23,7 @@
|
|||||||
<li data-panel="status" class="active">Status</li>
|
<li data-panel="status" class="active">Status</li>
|
||||||
<li data-panel="connection">Connection</li>
|
<li data-panel="connection">Connection</li>
|
||||||
<li data-panel="audio">Audio</li>
|
<li data-panel="audio">Audio</li>
|
||||||
|
<li data-panel="webserver">Webserver</li>
|
||||||
<li data-panel="identification">Identification</li>
|
<li data-panel="identification">Identification</li>
|
||||||
<li data-panel="mapbroadcast">Online map</li>
|
<li data-panel="mapbroadcast">Online map</li>
|
||||||
<li data-panel="maintenance">Maintenance</li>
|
<li data-panel="maintenance">Maintenance</li>
|
||||||
@@ -36,23 +37,49 @@
|
|||||||
<h2>STATUS</h2>
|
<h2>STATUS</h2>
|
||||||
|
|
||||||
<div class="panel-100 flex-container auto">
|
<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>
|
<span class="text-medium-big color-5"><%= onlineUsers %></span>
|
||||||
<p>Online users</p>
|
<p>Online users</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel-33 bg-color-2">
|
<div class="panel-33">
|
||||||
<span class="text-medium-big color-5"><%= memoryUsage %></span>
|
<span class="text-medium-big color-5"><%= memoryUsage %></span>
|
||||||
<p>Memory usage</p>
|
<p>Memory usage</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel-33 bg-color-2">
|
<div class="panel-33">
|
||||||
<span class="text-medium-big color-5"><%= processUptime %></span>
|
<span class="text-medium-big color-5"><%= processUptime %></span>
|
||||||
<p>Uptime</p>
|
<p>Uptime</p>
|
||||||
</div>
|
</div>
|
||||||
</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) { %>
|
<% if (consoleOutput && consoleOutput.length > 0) { %>
|
||||||
<div class="panel-100 br-5 p-10 text-small text-left top-10" id="console-output">
|
<div class="panel-100 br-5 p-10 text-small text-left top-10" id="console-output">
|
||||||
<% consoleOutput.forEach(function(log) { %>
|
<% consoleOutput.forEach(function(log) { %>
|
||||||
@@ -153,6 +180,61 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<div class="panel-100 tab-content" id="identification">
|
||||||
<h2>Tuner Identification info</h2>
|
<h2>Tuner Identification info</h2>
|
||||||
@@ -216,10 +298,6 @@
|
|||||||
<input type="checkbox" id="shutdown-tuner">
|
<input type="checkbox" id="shutdown-tuner">
|
||||||
<label for="shutdown-tuner">Auto-shutdown [XDR Only]</label>
|
<label for="shutdown-tuner">Auto-shutdown [XDR Only]</label>
|
||||||
</div><br>
|
</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>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="tune-pass">Tune password:</label>
|
<label for="tune-pass">Tune password:</label>
|
||||||
|
|||||||
Reference in New Issue
Block a user