172 lines
5.3 KiB
JavaScript
172 lines
5.3 KiB
JavaScript
// Server Observations display - shows fastfetch output
|
|
import { safeJson } from './utils/fetch.mjs';
|
|
import STATUS from './status.mjs';
|
|
import WeatherDisplay from './weatherdisplay.mjs';
|
|
import { registerDisplay } from './navigation.mjs';
|
|
import { debugFlag } from './utils/debug.mjs';
|
|
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;
|
|
|
|
const separators = [' == ', ': '];
|
|
const parsedLine = separators.map((separator) => {
|
|
const separatorIndex = trimmed.indexOf(separator);
|
|
if (separatorIndex > 0) {
|
|
const key = trimmed.slice(0, separatorIndex).trim();
|
|
const value = trimmed.slice(separatorIndex + separator.length).trim();
|
|
if (key && value && isAllowedObservationKey(key)) {
|
|
return { key, value: formatValue(key, value) };
|
|
}
|
|
}
|
|
return null;
|
|
}).find((entry) => entry);
|
|
|
|
return parsedLine ?? null;
|
|
};
|
|
|
|
class ServerObservations extends WeatherDisplay {
|
|
constructor(navId, elemId) {
|
|
super(navId, elemId, 'Server Observations', true);
|
|
this.timing.baseDelay = PAGE_DURATION_MS;
|
|
}
|
|
|
|
async getData(weatherParameters, refresh) {
|
|
if (!super.getData(weatherParameters, refresh)) return;
|
|
|
|
try {
|
|
// Fetch server info from the API
|
|
const response = await safeJson(withBasePath('api/server-info'), {
|
|
retryCount: 0,
|
|
});
|
|
|
|
// Check if fastfetch is available
|
|
if (!response || !response.success) {
|
|
if (debugFlag('serverobservations')) {
|
|
console.log('Server Observations: fastfetch not available');
|
|
}
|
|
this.setStatus(STATUS.noData);
|
|
return;
|
|
}
|
|
|
|
this.data = response.data;
|
|
this.setStatus(STATUS.loaded);
|
|
} catch (error) {
|
|
if (debugFlag('serverobservations')) {
|
|
console.log('Server Observations: error fetching data:', error.message);
|
|
}
|
|
this.setStatus(STATUS.noData);
|
|
}
|
|
}
|
|
|
|
async drawCanvas() {
|
|
super.drawCanvas();
|
|
|
|
// Get the output container
|
|
const outputElem = this.elem.querySelector('.server-output');
|
|
const container = this.elem.querySelector('.container');
|
|
|
|
// Split the fastfetch output into lines
|
|
const infoLines = this.data.split('\n')
|
|
.map((line) => parseServerObservationLine(line))
|
|
.filter((line) => line);
|
|
|
|
const pages = [];
|
|
for (let i = 0; i < infoLines.length; i += LINES_PER_PAGE) {
|
|
pages.push(infoLines.slice(i, i + LINES_PER_PAGE));
|
|
}
|
|
|
|
outputElem.innerHTML = '';
|
|
pages.forEach((pageLines) => {
|
|
const pageElem = document.createElement('div');
|
|
pageElem.className = 'server-page';
|
|
|
|
pageLines.forEach((line) => {
|
|
const lineDiv = document.createElement('div');
|
|
lineDiv.className = 'server-line';
|
|
lineDiv.textContent = truncateLine(`${line.key}: ${line.value}`);
|
|
pageElem.appendChild(lineDiv);
|
|
});
|
|
|
|
outputElem.appendChild(pageElem);
|
|
});
|
|
|
|
this.pageHeight = container.offsetHeight;
|
|
this.timing.totalScreens = Math.max(1, pages.length);
|
|
this.timing.delay = new Array(this.timing.totalScreens).fill(1);
|
|
this.calcNavTiming();
|
|
|
|
const top = -this.screenIndex * this.pageHeight;
|
|
outputElem.style.top = `${top}px`;
|
|
|
|
this.finishDraw();
|
|
}
|
|
}
|
|
|
|
// Register display with navId 13 (after other displays)
|
|
registerDisplay(new ServerObservations(13, 'server-observations'));
|