From c82d7648ab5945d0a850cb67c16cecd866d27a50 Mon Sep 17 00:00:00 2001 From: mrkmntal Date: Fri, 21 Aug 2026 19:19:27 -0400 Subject: [PATCH] Stabilize fastfetch/server observations behavior --- index.mjs | 12 +++- server/scripts/modules/serverobservations.mjs | 62 ++++++++++++++++++- 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/index.mjs b/index.mjs index 6e94477..654bf74 100644 --- a/index.mjs +++ b/index.mjs @@ -221,16 +221,22 @@ if (!process.env?.STATIC) { app.get('/api/server-info', async (req, res) => { try { // Use --structure to show only essential info: OS, Kernel, Uptime, CPU, GPU, Memory, Disk - const { stdout } = await execAsync('fastfetch --structure "Title:OS:Kernel:Uptime:CPU:GPU:Memory:Disk" --pipe false'); - // Strip all ANSI escape sequences (color codes, cursor positioning, etc.) + // --logo none prevents image/ascii logo rendering issues on headless systems + // --pipe disables colors for clean text output + const { stdout } = await execAsync( + 'fastfetch --structure "Title:OS:Kernel:Uptime:CPU:GPU:Memory:Disk" --logo none --pipe', + { timeout: 10000 }, + ); + // Strip all ANSI escape sequences (color codes, cursor positioning, OSC sequences, etc.) // eslint-disable-next-line no-control-regex - const cleanOutput = stdout.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, ''); + const cleanOutput = stdout.replace(/\x1b(?:\[[0-?]*[ -/]*[@-~]|\][^\x07]*(?:\x07|\x1b\\))/g, ''); res.json({ success: true, data: cleanOutput, }); } catch (error) { // fastfetch not available or other error + console.error('fastfetch failed:', error.message); res.json({ success: false, data: null, diff --git a/server/scripts/modules/serverobservations.mjs b/server/scripts/modules/serverobservations.mjs index f124900..aeb0912 100644 --- a/server/scripts/modules/serverobservations.mjs +++ b/server/scripts/modules/serverobservations.mjs @@ -9,9 +9,67 @@ import { withBasePath } from './utils/base-path.mjs'; const LINES_PER_PAGE = 4; const PAGE_DURATION_MS = 7000; const ALLOWED_KEYS = new Set(['OS', 'Kernel', 'Uptime', 'CPU', 'GPU', 'Memory', 'Disk']); +const MAX_LINE_LENGTH = 34; const isAllowedObservationKey = (key) => ALLOWED_KEYS.has(key) || key.startsWith('Disk'); +const truncateLine = (text, maxLength = MAX_LINE_LENGTH) => { + if (text.length <= maxLength) return text; + return `${text.slice(0, maxLength - 3)}...`; +}; + +const roundOneDecimal = (value) => { + const rounded = Number(value).toFixed(1); + return rounded.endsWith('.0') ? rounded.slice(0, -2) : rounded; +}; + +const formatMemoryOrDisk = (value) => { + const cleaned = value.replace(/\s*\(\d+%\)/, '').replace(/\s*- .*/, '').trim(); + const unitMatch = cleaned.match(/([\d.]+)\s*(TiB|GiB|MiB|KiB|TB|GB|MB|KB|B)\s*\/\s*([\d.]+)\s*(TiB|GiB|MiB|KiB|TB|GB|MB|KB|B)/); + if (!unitMatch) return cleaned; + return `${roundOneDecimal(unitMatch[1])} / ${roundOneDecimal(unitMatch[3])} ${unitMatch[2]}`; +}; + +const formatValue = (key, value) => { + switch (key) { + case 'OS': + return value + .replace(/\s*\([^)]*\)\s*(x86_64|aarch64|arm64|i386|i686|amd64)/gi, '') + .replace(/\s*(x86_64|aarch64|arm64|i386|i686|amd64)/gi, '') + .trim(); + case 'Kernel': + return value.replace(/(-[a-zA-Z0-9._-]+)+$/, '').trim(); + case 'Uptime': + return value + .replace(/ days?/g, 'd') + .replace(/ hours?/g, 'h') + .replace(/ mins?/g, 'm') + .replace(/ secs?/g, 's'); + case 'CPU': { + const match = value.match(/(Intel Core i\d+(?:-\d+\w*)?|Intel Xeon|Intel Pentium|Intel Celeron|AMD Ryzen \d+(?: \d+\w*)?|AMD FX|AMD Athlon|Apple M\d+|ARM Cortex|Qualcomm Snapdragon)/i); + if (match) return match[1]; + return value + .replace(/\(R\)/g, '') + .replace(/\(TM\)/g, '') + .replace(/\s*\(\d+\)\s*@\s*[\d.]+ GHz.*/, '') + .trim(); + } + case 'GPU': + return value + .replace(/\(R\)/g, '') + .replace(/\(TM\)/g, '') + .replace(/ Integrated Graphics Controller.*/i, '') + .replace(/ @ [\d.]+ GHz.*/, '') + .replace(/ \[.*\]$/, '') + .trim(); + case 'Memory': + return formatMemoryOrDisk(value); + default: + if (key.startsWith('Disk')) return formatMemoryOrDisk(value); + return value; + } +}; + const parseServerObservationLine = (line) => { const trimmed = line.trim(); if (!trimmed) return null; @@ -23,7 +81,7 @@ const parseServerObservationLine = (line) => { const key = trimmed.slice(0, separatorIndex).trim(); const value = trimmed.slice(separatorIndex + separator.length).trim(); if (key && value && isAllowedObservationKey(key)) { - return { key, value }; + return { key, value: formatValue(key, value) }; } } return null; @@ -91,7 +149,7 @@ class ServerObservations extends WeatherDisplay { pageLines.forEach((line) => { const lineDiv = document.createElement('div'); lineDiv.className = 'server-line'; - lineDiv.textContent = `${line.key}: ${line.value}`; + lineDiv.textContent = truncateLine(`${line.key}: ${line.value}`); pageElem.appendChild(lineDiv); });