Adds fastfetch/Server Observations as a screen

This commit is contained in:
mrkmntal 2026-04-04 15:12:08 -04:00
commit 57a766380a
11 changed files with 210 additions and 10 deletions

View file

@ -130,7 +130,7 @@ const getWeather = async (latLon, haveDataCallback) => {
}
// call for new data on each display
displays.forEach((display) => display.getData(weatherParameters));
displays.forEach((display) => display?.getData(weatherParameters));
} catch (error) {
console.error(`Failed to get weather data: ${error.message}`);
}
@ -172,12 +172,12 @@ const updateStatus = (value) => {
// the weather.gov api has long load times for some products when you are the first
// requester for the product after the cache expires
const countLoadedDisplays = () => displays.reduce((acc, display) => {
if (display.status !== STATUS.loading) return acc + 1;
if (display?.status !== STATUS.loading) return acc + 1;
return acc;
}, 0);
const hideAllCanvases = () => {
displays.forEach((display) => display.hideCanvas());
displays.forEach((display) => display?.hideCanvas());
};
// is playing interface
@ -250,7 +250,7 @@ const loadDisplay = (direction) => {
for (let i = 0; i < totalDisplays; i += 1) {
// convert form simple 0-10 to start at current display index +/-1 and wrap
idx = wrap(curIdx + (i + 1) * direction, totalDisplays);
if (displays[idx].status === STATUS.loaded && displays[idx].timing.totalScreens > 0) {
if (displays[idx]?.status === STATUS.loaded && displays[idx]?.timing.totalScreens > 0) {
// Prevent infinite recursion by ensuring we don't select the same display
if (idx !== curIdx) {
foundSuitableDisplay = true;
@ -272,6 +272,10 @@ const loadDisplay = (direction) => {
}
const newDisplay = displays[idx];
if (!newDisplay) {
console.warn('Selected display is undefined, aborting navigation');
return;
}
// hide all displays
hideAllCanvases();
// show the new display and navigate to an appropriate display
@ -280,7 +284,7 @@ const loadDisplay = (direction) => {
};
// get the current display index or value
const currentDisplayIndex = () => displays.findIndex((display) => display.active);
const currentDisplayIndex = () => displays.findIndex((display) => display?.active);
const currentDisplay = () => displays[currentDisplayIndex()];
const setPlaying = (newValue) => {
@ -586,7 +590,7 @@ const resize = (force = false) => {
// reset all statuses to loading on all displays, used to keep the progress bar accurate during refresh
const resetStatuses = () => {
displays.forEach((display) => { display.status = STATUS.loading; });
displays.forEach((display) => { if (display) display.status = STATUS.loading; });
};
// Apply scanline scaling to try and prevent banding by avoiding fractional scaling
@ -761,7 +765,7 @@ const generateCheckboxes = () => {
if (!availableDisplays) return;
// generate checkboxes
const checkboxes = displays.map((d) => d.generateCheckbox(d.defaultEnabled)).filter((d) => d);
const checkboxes = displays.map((d) => d?.generateCheckbox(d?.defaultEnabled)).filter((d) => d);
// write to page
availableDisplays.innerHTML = '';

View file

@ -0,0 +1,98 @@
// 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';
const LINES_PER_PAGE = 4;
const PAGE_DURATION_MS = 7000;
class ServerObservations extends WeatherDisplay {
constructor(navId, elemId) {
super(navId, elemId, 'Server Observations', true);
// Don't show on progress screen
this.showOnProgress = false;
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('/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 lines = this.data.split('\n');
// Filter to show only key system info lines (contain "==")
const infoLines = lines.filter((line) => {
const trimmed = line.trim();
// Only keep lines that have the "Key == Value" format
return trimmed && trimmed.includes(' == ');
});
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 = line.trim().replace(' == ', ': ');
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'));