diff --git a/server/console.js b/server/console.js index b7e819f..64362f1 100644 --- a/server/console.js +++ b/server/console.js @@ -1,22 +1,17 @@ -const fs = require('fs'); +const fs = require('fs').promises; const verboseMode = process.argv.includes('--debug'); const verboseModeFfmpeg = process.argv.includes('--ffmpegdebug'); +const LOG_FILE = 'serverlog.txt'; const ANSI_ESCAPE_CODE_PATTERN = /\x1b\[[0-9;]*m/g; const MAX_LOG_LINES = 5000; +const FLUSH_INTERVAL = 60000; +const logs = []; +const maxConsoleLogLines = 250; +let logBuffer = []; -const getCurrentTime = () => { - const currentTime = new Date(); - const hours = currentTime.getHours().toString().padStart(2, '0'); - const minutes = currentTime.getMinutes().toString().padStart(2, '0'); - return `\x1b[90m[${hours}:${minutes}]\x1b[0m`; -}; - -const removeANSIEscapeCodes = (str) => { - return str.replace(ANSI_ESCAPE_CODE_PATTERN, ''); -}; - +// Message prefixes with ANSI codes const MESSAGE_PREFIX = { CHAT: "\x1b[36m[CHAT]\x1b[0m", DEBUG: "\x1b[36m[DEBUG]\x1b[0m", @@ -26,97 +21,67 @@ const MESSAGE_PREFIX = { WARN: "\x1b[33m[WARN]\x1b[0m", }; -// Initialize an array to store logs -const logs = []; -const maxLogLines = 250; +const getCurrentTime = () => { + const currentTime = new Date(); + const date = currentTime.toLocaleDateString().replace(/\ /g, ''); + const time = currentTime.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); + return `\x1b[90m[${date} ${time}]\x1b[0m`; +}; -const logDebug = (...messages) => { - const logMessage = `${getCurrentTime()} ${MESSAGE_PREFIX.DEBUG} ${messages.join(' ')}`; - if (verboseMode) { +const removeANSIEscapeCodes = (str) => str.replace(ANSI_ESCAPE_CODE_PATTERN, ''); // Strip ANSI escape codes from a string + +const logMessage = (type, messages, verbose = false) => { + const logMessage = `${getCurrentTime()} ${MESSAGE_PREFIX[type]} ${messages.join(' ')}`; + + if (type === 'DEBUG' && verboseMode || type === 'FFMPEG' && verboseModeFfmpeg || type !== 'DEBUG' && type !== 'FFMPEG') { logs.push(logMessage); - if (logs.length > maxLogLines) { - logs.shift(); - } - console.log(logMessage); + if (logs.length > maxConsoleLogLines) logs.shift(); + console.log(logMessage); } - appendLogToFile(logMessage); + appendLogToBuffer(logMessage); }; -const logChat = (...messages) => { - const logMessage = `${getCurrentTime()} ${MESSAGE_PREFIX.CHAT} ${messages[0].nickname} (${messages[0].ip}) sent a chat message: ${messages[0].message}`; - appendLogToFile(logMessage); -}; +const logDebug = (...messages) => logMessage('DEBUG', messages, verboseMode); +const logChat = (message) => logMessage('CHAT', [`${message.nickname} (${message.ip}) sent a chat message: ${message.message}`]); +const logError = (...messages) => logMessage('ERROR', messages); +const logFfmpeg = (...messages) => logMessage('FFMPEG', messages, verboseModeFfmpeg); +const logInfo = (...messages) => logMessage('INFO', messages); +const logWarn = (...messages) => logMessage('WARN', messages); -const logError = (...messages) => { - const logMessage = `${getCurrentTime()} ${MESSAGE_PREFIX.ERROR} ${messages.join(' ')}`; - logs.push(logMessage); - if (logs.length > maxLogLines) { - logs.shift(); - } - console.log(logMessage); - appendLogToFile(logMessage); -}; - -const logFfmpeg = (...messages) => { - if (verboseModeFfmpeg) { - const logMessage = `${getCurrentTime()} ${MESSAGE_PREFIX.FFMPEG} ${messages.join(' ')}`; - logs.push(logMessage); - if (logs.length > maxLogLines) { - logs.shift(); - } - console.log(logMessage); - appendLogToFile(logMessage); - } -}; - -const logInfo = (...messages) => { - const logMessage = `${getCurrentTime()} ${MESSAGE_PREFIX.INFO} ${messages.join(' ')}`; - logs.push(logMessage); - if (logs.length > maxLogLines) { - logs.shift(); - } - console.log(logMessage); - appendLogToFile(logMessage); -}; - -const logWarn = (...messages) => { - const logMessage = `${getCurrentTime()} ${MESSAGE_PREFIX.WARN} ${messages.join(' ')}`; - logs.push(logMessage); - if (logs.length > maxLogLines) { - logs.shift(); - } - console.log(logMessage); - appendLogToFile(logMessage); -}; - -function appendLogToFile(logMessage) { - const date = new Date(); - const cleanLogMessage = date.toLocaleDateString() + ' | ' + removeANSIEscapeCodes(logMessage); - - fs.appendFile('serverlog.txt', cleanLogMessage + '\n', (err) => { - if (err) { - console.error('Error writing to server log:', err); - } else { - fs.readFile('serverlog.txt', 'utf8', (err, data) => { - if (err) { - console.error('Error reading server log:', err); - } else { - const lineCount = data.split('\n').length; - if (lineCount > MAX_LOG_LINES) { - const excessLines = lineCount - MAX_LOG_LINES; - const truncatedContent = data.split('\n').slice(excessLines).join('\n'); - fs.writeFile('serverlog.txt', truncatedContent, (err) => { - if (err) { - console.error('Error truncating server log:', err); - } - }); - } - } - }); - } - }); +function appendLogToBuffer(logMessage) { + const cleanLogMessage = removeANSIEscapeCodes(logMessage); + logBuffer.push(cleanLogMessage + '\n'); } -module.exports = { - logError, logDebug, logFfmpeg, logInfo, logWarn, logs, logChat +async function flushLogBuffer() { + if (logBuffer.length === 0) return; + + const logContent = logBuffer.join(''); + logBuffer = []; + + try { + await fs.appendFile(LOG_FILE, logContent); + + const data = await fs.readFile(LOG_FILE, 'utf8'); + const lines = data.split('\n'); + if (lines.length > MAX_LOG_LINES) { + const truncatedContent = lines.slice(-MAX_LOG_LINES).join('\n'); + await fs.writeFile(LOG_FILE, truncatedContent); + } + } catch (err) { + console.error('Error flushing log buffer:', err); + } +} + +setInterval(flushLogBuffer, FLUSH_INTERVAL); + +const gracefulExit = async () => { + await flushLogBuffer(); + process.exit(); }; + +process.on('exit', flushLogBuffer); +process.on('SIGINT', gracefulExit); +process.on('SIGTERM', gracefulExit); + +module.exports = { logError, logDebug, logFfmpeg, logInfo, logWarn, logs, logChat }; \ No newline at end of file diff --git a/web/403.ejs b/web/403.ejs index 8dcfaad..ad5aac2 100644 --- a/web/403.ejs +++ b/web/403.ejs @@ -9,10 +9,10 @@ -
+
-

[Unauthorized]

+

[Unauthorized]


diff --git a/web/_components.ejs b/web/_components.ejs new file mode 100644 index 0000000..64f003e --- /dev/null +++ b/web/_components.ejs @@ -0,0 +1,74 @@ +<% +switch (component) { + /** + * Text input & password input tag + * @param label Label text + * @param id Unique element ID + * @param password If true, the element will be rendered as a password instead of plain text input + * @param placeholder Placeholder text + * @param cssClass Custom CSS class if needed + */ + case 'text': +%> +
+ + +
+<% + break; + + /** + * Checkbox (toggle button) tag + * @param label Label text + * @param id Unique element ID + * @param iconClass Additional CSS Class for the icon inside the button + * @param cssClass Custom CSS class if needed + */ + case 'checkbox': +%> +
+ + +
+<% + break; + + /** + * Dropdown menus + * @param inputId Unique ID for the hidden text input of the dropdown + * @param id Unique element ID + * @param iconClass Additional CSS Class for the icon next to the title (if you want to have one) + * @param placeholder Placeholder text + * @param cssClass Custom CSS class for the dropdown menu itself if needed + */ + case 'dropdown': +%> +
+ + +
+<% + break; + + default: +%> +

Unknown component: <%= component %>

+<% + break; +} +%> diff --git a/web/css/breadcrumbs.css b/web/css/breadcrumbs.css index 2cc5ba6..72a64ad 100644 --- a/web/css/breadcrumbs.css +++ b/web/css/breadcrumbs.css @@ -215,10 +215,17 @@ label { margin-bottom: 0px !important; } - .settings-heading { - font-size: 32px; - padding-top: 20px; + h2.settings-heading { + font-size: 42px; + padding: 10px 0; + font-weight: 300; + } + + h3.settings-heading { + font-size: 24px; text-transform: uppercase; + font-weight: 300; + margin-bottom: 5px; } #tuner-wireless { diff --git a/web/css/helpers.css b/web/css/helpers.css index ce84def..f269f30 100644 --- a/web/css/helpers.css +++ b/web/css/helpers.css @@ -222,6 +222,10 @@ user-select: none; } +.clearfix { + clear: both; +} + @media only screen and (max-width: 960px) { .text-medium-big { font-size: 32px; diff --git a/web/css/setup.css b/web/css/setup.css index f080068..e177b00 100644 --- a/web/css/setup.css +++ b/web/css/setup.css @@ -137,6 +137,10 @@ li.active { height: 300px; overflow-y:auto; } +.w-250 { + width: 250px !important +} + .w-200 { width: 200px !important } diff --git a/web/index.ejs b/web/index.ejs index 0a885c7..d3ba924 100644 --- a/web/index.ejs +++ b/web/index.ejs @@ -1,7 +1,7 @@ - FM-DX Webserver [<%= tunerName %>] + <%= tunerName %> - FM-DX Webserver @@ -322,51 +322,38 @@
@@ -640,4 +495,4 @@ <% }); %> - + \ No newline at end of file diff --git a/web/wizard.ejs b/web/wizard.ejs index b77d0de..8eb61ba 100644 --- a/web/wizard.ejs +++ b/web/wizard.ejs @@ -28,50 +28,40 @@
- -
+

Basic settings

Welcome to the setup wizard! Let's set up some basic things.

-

Webserver connection:

-

Leave the IP at 0.0.0.0 unless you explicitly know you have to change it.
Don't enter your public IP here.

+

Webserver connection

+

Leave the IP at 0.0.0.0 unless you explicitly know you have to change it.
DO NOT enter your public IP here.

-
- - -
-
- - -
+ <%- include('_components', {component: 'text', cssClass: 'w-150', placeholder: '0.0.0.0', label: 'Webserver IP', id: 'webserver-webserverIp'}) %> + <%- include('_components', {component: 'text', cssClass: 'w-100', placeholder: '8080', label: 'Webserver port', id: 'webserver-webserverPort'}) %>