I recently needed a way to visualize server monitoring data from a daemon script. The daemon generates CSV reports, and I wanted a clean web interface that could dynamically load and display the latest server status without manual file management.
The original post covering the collector is on the link here
Visual Walkthrough
First lets look at the health card dashboard which will lookup the CSV file display the information in reliable format as below:
Here you can see we have 3x Healthy servers and 1x server is warning as the process is not running, the script in the original article would have taken care of that issue but its handy for illustrative purposes.
My monitoring daemon runs continuously and outputs server status data to a CSV file with this structure:
"Server","IP","Status","PID","User","ProcessInfo","Timestamp"
"Manchester","192.168.15.42","Already Running","7992","root","root 7992 0.0 0.0 410607968 1552 s001 S Thu03PM 0:13.58 /bin/bash ./cache_monitor.py start","2025-07-21 11:04:37"
"Birmingham","10.33.187.91","Already Running","41772","root","root 41772 0.0 0.0 410607888 1648 ?? S 10:46AM 0:00.04 /bin/bash ./cache_monitor.py start","2025-07-21 11:04:38"
The daemon polls each server, checks process status using system commands, and writes the results to a time stamped CSV file. Each row contains the server name, IP address, current process status, PID, user context, full process information from ps
output, and the timestamp of the check.
Dynamic File Discovery
The web dashboard needs to automatically find and load the latest CSV file without requiring manual file selection. I implemented this by having the JavaScript search for common CSV filenames:
this.csvFiles = [
'server_status.csv',
'monitoring.csv',
'health_check.csv'
];
This approach ensures the dashboard can find the CSV file regardless of the exact filename my daemon uses, as long as it follows common naming conventions.
CSV Parsing Implementation
The CSV parser handles quoted fields properly since the ProcessInfo column often contains spaces and special characters:
parseCSVLine(line) {
const result = [];
let current = '';
let inQuotes = false;
for (let i = 0; i < line.length; i++) {
const char = line[i];
if (char === '"') {
inQuotes = !inQuotes;
} else if (char === ',' && !inQuotes) {
result.push(current);
current = '';
} else {
current += char;
}
}
result.push(current);
return result;
}
The parser creates objects from each row by mapping column headers to values:
header.forEach((col, index) => {
row[col.trim()] = values[index].trim();
});
Status Classification Logic
The script categorizes server status based on the Status column content:
getStatusClass(status) {
const statusLower = status.toLowerCase();
if (statusLower.includes('running')) {
return 'running';
} else if (statusLower.includes('stopped') || statusLower.includes('error')) {
return 'stopped';
} else {
return 'warning';
}
}
This classification drives the visual styling - running servers get green indicators, stopped/error states get red, and anything else gets orange warning styling.
Data Visualization Structure
Each server gets rendered as a card with structured data display:
const gridHTML = this.serverData.map(server => `
<div class="server-card ${this.getStatusClass(server.Status || '')}">
<div class="server-header">
<div class="server-name">${server.Server || 'Unknown'}</div>
<div class="status-badge ${this.getStatusBadgeClass(server.Status || '')}">
${server.Status || 'Unknown'}
</div>
</div>
// ... additional server details
</div>
`).join('');
The ProcessInfo field gets special formatting since it contains raw ps
command output:
.process-info .detail-value {
font-family: 'Courier New', monospace;
font-size: 0.8rem;
background: #ecf0f1;
padding: 0.5rem;
border-radius: 4px;
white-space: pre-wrap;
}
Automatic Refresh Mechanism
The dashboard includes both automatic and manual refresh capabilities:
// Auto-refresh every 30 seconds
setInterval(() => this.loadData(), 30000);
// Manual refresh button
<button class="refresh-btn" onclick="dashboard.loadData()">Refresh Data</button>
When refresh is triggered, the script re-fetches the CSV file and re-parses the data. This means I get the latest report from my monitoring daemon without needing to reload the entire page.
Status Summary Calculation
The dashboard calculates and displays summary statistics:
renderStatusSummary() {
const summary = this.serverData.reduce((acc, server) => {
const status = this.getStatusClass(server.Status || '');
acc[status] = (acc[status] || 0) + 1;
return acc;
}, {});
// ... render summary cards
}
This provides counts of running, stopped, warning, and total servers at the top of the dashboard.
Error Handling - on no CSV
If no CSV file is found, the script displays a clear error message indicating which filenames it searched for:
if (data) {
this.serverData = data;
this.renderDashboard();
} else {
this.showError('No CSV file found. Please ensure a CSV file exists in the current directory with one of these names: ' + this.csvFiles.join(', '));
}
This ensures I know exactly what went wrong if the dashboard can't find the monitoring daemon's output file.