Skip to content

Commit e8a75a7

Browse files
changes in admin.js for resource monitoring
1 parent 1e6590b commit e8a75a7

File tree

1 file changed

+298
-0
lines changed

1 file changed

+298
-0
lines changed

bin/lib/admin.js

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,10 @@ function generateDashboardHTML(servicesWithStatus, refreshInterval = 5000) {
513513
scheduleCountdown();
514514
setTimeout(fetchStatus, REFRESH_MS); // initial schedule
515515
initializeLogs();
516+
initializeMetricsCharts();
517+
518+
// Start metrics updates
519+
setInterval(updateMetricsCharts, 5000); // Update every 5 seconds
516520
517521
// Event delegation for service control buttons
518522
document.addEventListener('click', function(event) {
@@ -763,6 +767,166 @@ function generateDashboardHTML(servicesWithStatus, refreshInterval = 5000) {
763767
}, 3000);
764768
}
765769
</script>
770+
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
771+
<script>
772+
// Metrics visualization with Chart.js
773+
let metricsCharts = {};
774+
775+
function initializeMetricsCharts() {
776+
// CPU Chart
777+
const cpuCtx = document.getElementById('cpu-chart');
778+
if (cpuCtx) {
779+
metricsCharts.cpu = new Chart(cpuCtx, {
780+
type: 'line',
781+
data: {
782+
labels: [],
783+
datasets: [{
784+
label: 'CPU Usage %',
785+
data: [],
786+
borderColor: 'rgb(75, 192, 192)',
787+
backgroundColor: 'rgba(75, 192, 192, 0.1)',
788+
tension: 0.1
789+
}]
790+
},
791+
options: {
792+
responsive: true,
793+
scales: { y: { beginAtZero: true, max: 100 } }
794+
}
795+
});
796+
}
797+
798+
// Memory Chart
799+
const memoryCtx = document.getElementById('memory-chart');
800+
if (memoryCtx) {
801+
metricsCharts.memory = new Chart(memoryCtx, {
802+
type: 'line',
803+
data: {
804+
labels: [],
805+
datasets: [{
806+
label: 'Memory Usage %',
807+
data: [],
808+
borderColor: 'rgb(255, 99, 132)',
809+
backgroundColor: 'rgba(255, 99, 132, 0.1)',
810+
tension: 0.1
811+
}]
812+
},
813+
options: {
814+
responsive: true,
815+
scales: { y: { beginAtZero: true, max: 100 } }
816+
}
817+
});
818+
}
819+
820+
// Network Chart
821+
const networkCtx = document.getElementById('network-chart');
822+
if (networkCtx) {
823+
metricsCharts.network = new Chart(networkCtx, {
824+
type: 'line',
825+
data: {
826+
labels: [],
827+
datasets: [{
828+
label: 'Network RX (MB/s)',
829+
data: [],
830+
borderColor: 'rgb(54, 162, 235)',
831+
backgroundColor: 'rgba(54, 162, 235, 0.1)',
832+
tension: 0.1
833+
}, {
834+
label: 'Network TX (MB/s)',
835+
data: [],
836+
borderColor: 'rgb(255, 206, 86)',
837+
backgroundColor: 'rgba(255, 206, 86, 0.1)',
838+
tension: 0.1
839+
}]
840+
},
841+
options: {
842+
responsive: true,
843+
scales: { y: { beginAtZero: true } }
844+
}
845+
});
846+
}
847+
848+
// Disk Chart
849+
const diskCtx = document.getElementById('disk-chart');
850+
if (diskCtx) {
851+
metricsCharts.disk = new Chart(diskCtx, {
852+
type: 'doughnut',
853+
data: {
854+
labels: ['Used', 'Available'],
855+
datasets: [{
856+
data: [0, 100],
857+
backgroundColor: ['rgb(255, 99, 132)', 'rgb(75, 192, 192)']
858+
}]
859+
},
860+
options: {
861+
responsive: true,
862+
plugins: {
863+
legend: { position: 'bottom' }
864+
}
865+
}
866+
});
867+
}
868+
}
869+
870+
async function updateMetricsCharts() {
871+
try {
872+
const response = await fetch('/api/metrics');
873+
if (!response.ok) return;
874+
875+
const data = await response.json();
876+
const timestamp = new Date().toLocaleTimeString();
877+
878+
// Update CPU chart
879+
if (metricsCharts.cpu && data.metrics.cpu) {
880+
const chart = metricsCharts.cpu;
881+
chart.data.labels.push(timestamp);
882+
chart.data.datasets[0].data.push(data.metrics.cpu.percent || 0);
883+
if (chart.data.labels.length > 20) {
884+
chart.data.labels.shift();
885+
chart.data.datasets[0].data.shift();
886+
}
887+
chart.update('none');
888+
}
889+
890+
// Update Memory chart
891+
if (metricsCharts.memory && data.metrics.memory) {
892+
const chart = metricsCharts.memory;
893+
chart.data.labels.push(timestamp);
894+
chart.data.datasets[0].data.push(data.metrics.memory.percent || 0);
895+
if (chart.data.labels.length > 20) {
896+
chart.data.labels.shift();
897+
chart.data.datasets[0].data.shift();
898+
}
899+
chart.update('none');
900+
}
901+
902+
// Update Network chart
903+
if (metricsCharts.network && data.metrics.network) {
904+
const chart = metricsCharts.network;
905+
chart.data.labels.push(timestamp);
906+
chart.data.datasets[0].data.push((data.metrics.network.rx_sec || 0) / (1024 * 1024)); // MB/s
907+
chart.data.datasets[1].data.push((data.metrics.network.tx_sec || 0) / (1024 * 1024)); // MB/s
908+
if (chart.data.labels.length > 20) {
909+
chart.data.labels.shift();
910+
chart.data.datasets[0].data.shift();
911+
chart.data.datasets[1].data.shift();
912+
}
913+
chart.update('none');
914+
}
915+
916+
// Update Disk chart
917+
if (metricsCharts.disk && data.metrics.disk) {
918+
const chart = metricsCharts.disk;
919+
const used = data.metrics.disk.used || 0;
920+
const total = data.metrics.disk.total || 1;
921+
const available = total - used;
922+
chart.data.datasets[0].data = [used, available];
923+
chart.update('none');
924+
}
925+
} catch (e) {
926+
console.warn('Failed to update metrics charts:', e);
927+
}
928+
}
929+
</script>
766930
</head>
767931
<body>
768932
<header>
@@ -843,6 +1007,33 @@ function generateDashboardHTML(servicesWithStatus, refreshInterval = 5000) {
8431007
</tbody>
8441008
</table>`}
8451009
1010+
<!-- Metrics Section -->
1011+
<div class="metrics-section" style="margin-top:30px;">
1012+
<div style="margin-bottom:20px;">
1013+
<h2 style="font-size:1rem; font-weight:600; color:#334155; margin:0 0 16px;">Resource Monitoring</h2>
1014+
<div style="display:grid; grid-template-columns: 1fr 1fr; gap:20px; margin-bottom:20px;">
1015+
<div style="background:#fff; padding:16px; border-radius:8px; box-shadow:0 1px 3px rgba(0,0,0,0.1);">
1016+
<h3 style="font-size:0.9rem; margin:0 0 12px; color:#374151;">CPU Usage</h3>
1017+
<canvas id="cpu-chart" width="400" height="200"></canvas>
1018+
</div>
1019+
<div style="background:#fff; padding:16px; border-radius:8px; box-shadow:0 1px 3px rgba(0,0,0,0.1);">
1020+
<h3 style="font-size:0.9rem; margin:0 0 12px; color:#374151;">Memory Usage</h3>
1021+
<canvas id="memory-chart" width="400" height="200"></canvas>
1022+
</div>
1023+
</div>
1024+
<div style="display:grid; grid-template-columns: 1fr 1fr; gap:20px;">
1025+
<div style="background:#fff; padding:16px; border-radius:8px; box-shadow:0 1px 3px rgba(0,0,0,0.1);">
1026+
<h3 style="font-size:0.9rem; margin:0 0 12px; color:#374151;">Network I/O</h3>
1027+
<canvas id="network-chart" width="400" height="200"></canvas>
1028+
</div>
1029+
<div style="background:#fff; padding:16px; border-radius:8px; box-shadow:0 1px 3px rgba(0,0,0,0.1);">
1030+
<h3 style="font-size:0.9rem; margin:0 0 12px; color:#374151;">Disk I/O</h3>
1031+
<canvas id="disk-chart" width="400" height="200"></canvas>
1032+
</div>
1033+
</div>
1034+
</div>
1035+
</div>
1036+
8461037
<!-- Logs Section -->
8471038
<div class="logs-section">
8481039
<div class="logs-header">
@@ -907,6 +1098,79 @@ export async function startAdminDashboard(options = {}) {
9071098
console.log(chalk.yellow(' Press Ctrl+C to stop\n'));
9081099

9091100
// Initialize log file watcher
1101+
// Helper function to get system metrics
1102+
async function getSystemMetrics() {
1103+
try {
1104+
// Import systeminformation dynamically to avoid dependency issues in tests
1105+
const si = await import('systeminformation');
1106+
1107+
const [cpuInfo, cpuLoad, memory, networkStats, disk] = await Promise.all([
1108+
si.cpu().catch(() => ({})),
1109+
si.currentLoad().catch(() => ({ currentLoad: 0 })),
1110+
si.mem().catch(() => ({})),
1111+
si.networkStats().catch(() => ([])),
1112+
si.fsSize().catch(() => ([]))
1113+
]);
1114+
1115+
// Calculate network totals
1116+
const networkTotals = networkStats.reduce((totals, iface) => {
1117+
if (iface.iface && !iface.iface.startsWith('lo')) { // Skip loopback
1118+
totals.rx_bytes += iface.rx_bytes || 0;
1119+
totals.tx_bytes += iface.tx_bytes || 0;
1120+
totals.rx_sec += iface.rx_sec || 0;
1121+
totals.tx_sec += iface.tx_sec || 0;
1122+
}
1123+
return totals;
1124+
}, { rx_bytes: 0, tx_bytes: 0, rx_sec: 0, tx_sec: 0 });
1125+
1126+
// Calculate disk totals
1127+
const diskTotals = disk.reduce((totals, volume) => {
1128+
if (volume.mount === '/' || volume.mount.startsWith('/')) {
1129+
totals.total += volume.size || 0;
1130+
totals.used += volume.used || 0;
1131+
totals.available += volume.available || 0;
1132+
}
1133+
return totals;
1134+
}, { total: 0, used: 0, available: 0 });
1135+
1136+
return {
1137+
cpu: {
1138+
cores: cpuInfo.cores || 0,
1139+
model: cpuInfo.model || 'Unknown',
1140+
speed: cpuInfo.speed || 0,
1141+
percent: Math.round(cpuLoad.currentLoad || 0)
1142+
},
1143+
memory: {
1144+
total: memory.total || 0,
1145+
used: memory.used || 0,
1146+
available: memory.available || memory.total - memory.used || 0,
1147+
percent: memory.total ? ((memory.used / memory.total) * 100) : 0
1148+
},
1149+
network: {
1150+
interfaces: networkStats.map(iface => iface.iface).filter(name => name && !name.startsWith('lo')),
1151+
total_rx: networkTotals.rx_bytes,
1152+
total_tx: networkTotals.tx_bytes,
1153+
rx_sec: networkTotals.rx_sec,
1154+
tx_sec: networkTotals.tx_sec
1155+
},
1156+
disk: {
1157+
total: diskTotals.total,
1158+
used: diskTotals.used,
1159+
available: diskTotals.available,
1160+
percent: diskTotals.total ? ((diskTotals.used / diskTotals.total) * 100) : 0
1161+
}
1162+
};
1163+
} catch (error) {
1164+
console.warn('Failed to get system metrics:', error.message);
1165+
return {
1166+
cpu: { cores: 0, model: 'Unknown', speed: 0, percent: 0 },
1167+
memory: { total: 0, used: 0, available: 0, percent: 0 },
1168+
network: { interfaces: [], total_rx: 0, total_tx: 0, rx_sec: 0, tx_sec: 0 },
1169+
disk: { total: 0, used: 0, available: 0, percent: 0 }
1170+
};
1171+
}
1172+
}
1173+
9101174
globalLogWatcher = new LogFileWatcher(cwd);
9111175
try {
9121176
await globalLogWatcher.startWatching();
@@ -972,6 +1236,40 @@ export async function startAdminDashboard(options = {}) {
9721236
return;
9731237
}
9741238

1239+
if (url.pathname === '/api/metrics') {
1240+
// API endpoint for system and service metrics
1241+
try {
1242+
const metrics = await getSystemMetrics();
1243+
res.writeHead(200, {
1244+
'Content-Type': 'application/json',
1245+
'Access-Control-Allow-Origin': '*'
1246+
});
1247+
res.end(JSON.stringify({
1248+
metrics: {
1249+
timestamp: new Date().toISOString(),
1250+
cpu: metrics.cpu || {},
1251+
memory: metrics.memory || {},
1252+
network: metrics.network || {},
1253+
disk: metrics.disk || {}
1254+
},
1255+
systemInfo: {
1256+
cpu: { cores: metrics.cpu?.cores || 0, model: metrics.cpu?.model || 'Unknown' },
1257+
memory: { total: metrics.memory?.total || 0 },
1258+
disk: { total: metrics.disk?.total || 0, available: metrics.disk?.available || 0 },
1259+
network: { interfaces: metrics.network?.interfaces || [] }
1260+
}
1261+
}, null, 2));
1262+
} catch (e) {
1263+
console.error('❌ Metrics API error:', e.message);
1264+
res.writeHead(500, {
1265+
'Content-Type': 'application/json',
1266+
'Access-Control-Allow-Origin': '*'
1267+
});
1268+
res.end(JSON.stringify({ error: e.message }));
1269+
}
1270+
return;
1271+
}
1272+
9751273
// Service management endpoints
9761274
if (url.pathname === '/api/services/start' && req.method === 'POST') {
9771275
try {

0 commit comments

Comments
 (0)