Memory Usage
${currentMemory.toFixed(1)}%
Errors (24h)
${errorCount}
Active Users
${activeUsers}
`;
};
const renderSystemUsageChart = () => {
const ctx = document.getElementById('systemUsageChart').getContext('2d');
if (systemUsageChart) systemUsageChart.destroy();
systemUsageChart = new Chart(ctx, {
type: 'line',
data: {
labels: systemUsageData.labels,
datasets: [
{ label: 'CPU (%)', data: systemUsageData.cpu, borderColor: '#8b5cf6', backgroundColor: 'rgba(139, 92, 246, 0.1)', fill: true, tension: 0.3, yAxisID: 'yCpu' },
{ label: 'Memory (%)', data: systemUsageData.memory, borderColor: '#10b981', backgroundColor: 'rgba(16, 185, 129, 0.1)', fill: true, tension: 0.3, yAxisID: 'yMemory' }
]
},
options: {
responsive: true, maintainAspectRatio: false,
scales: {
x: { type: 'time', time: { unit: 'minute', displayFormats: { minute: 'HH:mm' } } },
yCpu: { type: 'linear', position: 'left', min: 0, max: 100, title: { display: true, text: 'CPU (%)' } },
yMemory: { type: 'linear', position: 'right', min: 0, max: 100, title: { display: true, text: 'Memory (%)' }, grid: { drawOnChartArea: false } }
}
}
});
};
const renderEventTypesChart = () => {
const ctx = document.getElementById('eventTypesChart').getContext('2d');
const eventTypes = ['INFO', 'WARN', 'ERROR'];
const typeCounts = eventTypes.map(type => events.filter(e => e.type === type).length);
if (eventTypesChart) eventTypesChart.destroy();
eventTypesChart = new Chart(ctx, {
type: 'doughnut',
data: {
labels: eventTypes,
datasets: [{ data: typeCounts, backgroundColor: ['#3b82f6', '#f59e0b', '#ef4444'] }]
},
options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { position: 'bottom' } } }
});
};
const renderEventLogTable = () => {
const container = document.getElementById('event-log-container');
const sortedEvents = [...events].sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
let tableHtml = `
| Timestamp |
Type |
Source |
Message |
`;
sortedEvents.forEach(event => {
let typeClass = event.type === 'ERROR' ? 'bg-red-100 text-red-800' : event.type === 'WARN' ? 'bg-amber-100 text-amber-800' : 'bg-blue-100 text-blue-800';
tableHtml += `
| ${formatTimestamp(event.timestamp)} |
${event.type} |
${event.source} |
${event.message} |
`;
});
tableHtml += `
`;
container.innerHTML = tableHtml;
};
const renderEventListEditor = () => {
const container = document.getElementById('event-list-editor').querySelector('.space-y-2');
container.innerHTML = events.map(event => `
${event.source}: ${event.message}
${formatTimestamp(event.timestamp)} | Type: ${event.type}
`).join('');
};
// --- EVENT HANDLERS ---
document.getElementById('event-form').addEventListener('submit', e => {
e.preventDefault();
const id = document.getElementById('event-id').value;
const newEvent = {
timestamp: new Date(document.getElementById('event-timestamp').value).toISOString(),
type: document.getElementById('event-type').value,
source: document.getElementById('event-source').value,
message: document.getElementById('event-message').value,
};
if (id) {
const index = events.findIndex(i => i.id == id);
events[index] = { ...events[index], ...newEvent };
} else {
newEvent.id = events.length > 0 ? Math.max(...events.map(i => i.id)) + 1 : 1;
events.push(newEvent);
}
resetForm();
renderAll();
});
document.getElementById('event-list-editor').addEventListener('click', e => {
if (e.target.classList.contains('edit-btn')) {
const id = e.target.dataset.id;
const event = events.find(i => i.id == id);
if (event) populateFormForEdit(event);
}
if (e.target.classList.contains('delete-btn')) {
const id = e.target.dataset.id;
if (confirm('Are you sure you want to delete this event?')) {
events = events.filter(i => i.id != id);
renderAll();
}
}
});
document.getElementById('cancel-edit-btn').addEventListener('click', resetForm);
document.getElementById('download-pdf-btn').addEventListener('click', () => {
const exportArea = document.getElementById('dashboard-export-area');
const mainTitleEl = document.querySelector('#telemetry-tool-container h1');
if (!exportArea || !mainTitleEl) return;
const btn = document.getElementById('download-pdf-btn');
const originalBtnText = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = `
Processing...`;
html2canvas(exportArea, { scale: 2, useCORS: true, windowWidth: exportArea.scrollWidth, windowHeight: exportArea.scrollHeight })
.then(canvas => {
const { jsPDF } = window.jspdf;
const imgData = canvas.toDataURL('image/png');
const pdf = new jsPDF({ orientation: 'landscape', unit: 'mm', format: 'a4' });
const pageMargin = 15;
const pdfPageWidth = pdf.internal.pageSize.getWidth();
const pdfPageHeight = pdf.internal.pageSize.getHeight();
const pdfContentWidth = pdfPageWidth - (2 * pageMargin);
const canvasAspectRatio = canvas.width / canvas.height;
const pdfImgHeight = pdfContentWidth / canvasAspectRatio;
let yPos = pageMargin;
pdf.setFontSize(18).setFont('helvetica', 'bold');
pdf.text(mainTitleEl.innerText, pdfPageWidth / 2, yPos, { align: 'center' });
yPos += 12;
let heightLeft = pdfImgHeight;
let positionOnCanvas = 0;
pdf.addImage(imgData, 'PNG', pageMargin, yPos, pdfContentWidth, pdfImgHeight);
heightLeft -= (pdfPageHeight - yPos - pageMargin);
while (heightLeft > 0) {
positionOnCanvas -= (pdfPageHeight - (2 * pageMargin));
pdf.addPage();
pdf.addImage(imgData, 'PNG', pageMargin, positionOnCanvas + pageMargin, pdfContentWidth, pdfImgHeight);
heightLeft -= (pdfPageHeight - (2 * pageMargin));
}
pdf.save(`telemetry-dashboard-${new Date().toISOString().slice(0,10)}.pdf`);
})
.catch(err => { console.error("PDF generation failed:", err); alert("Error generating PDF."); })
.finally(() => {
btn.disabled = false;
btn.innerHTML = originalBtnText;
});
});
// --- HELPER FUNCTIONS ---
function populateFormForEdit(event) {
document.getElementById('event-id').value = event.id;
// Format for datetime-local input: YYYY-MM-DDTHH:mm
document.getElementById('event-timestamp').value = new Date(event.timestamp).toISOString().slice(0, 16);
document.getElementById('event-type').value = event.type;
document.getElementById('event-source').value = event.source;
document.getElementById('event-message').value = event.message;
document.getElementById('event-form').querySelector('button[type="submit"]').textContent = 'Update Event';
document.getElementById('cancel-edit-btn').classList.remove('hidden');
document.getElementById('event-form').scrollIntoView({ behavior: 'smooth' });
}
function resetForm() {
document.getElementById('event-form').reset();
document.getElementById('event-id').value = '';
document.getElementById('event-form').querySelector('button[type="submit"]').textContent = 'Add Event';
document.getElementById('cancel-edit-btn').classList.add('hidden');
}
// --- INITIALIZATION ---
const switchTab = (tabName) => {
const tabDashboardBtn = document.getElementById('tab-dashboard-btn');
const tabConfigBtn = document.getElementById('tab-config-btn');
const tabDashboardContent = document.getElementById('tab-dashboard-content');
const tabConfigContent = document.getElementById('tab-config-content');
const prevTabBtn = document.getElementById('prev-tab-btn');
const nextTabBtn = document.getElementById('next-tab-btn');
if (tabName === 'dashboard') {
tabDashboardBtn.classList.add('active');
tabConfigBtn.classList.remove('active');
tabDashboardContent.classList.remove('hidden');
tabConfigContent.classList.add('hidden');
prevTabBtn.disabled = true;
nextTabBtn.disabled = false;
if (!realTimeInterval) {
realTimeInterval = setInterval(updateRealTimeData, 5000);
}
} else {
tabDashboardBtn.classList.remove('active');
tabConfigBtn.classList.add('active');
tabDashboardContent.classList.add('hidden');
tabConfigContent.classList.remove('hidden');
prevTabBtn.disabled = false;
nextTabBtn.disabled = true;
if (realTimeInterval) {
clearInterval(realTimeInterval);
realTimeInterval = null;
}
}
};
document.getElementById('tab-dashboard-btn').addEventListener('click', () => switchTab('dashboard'));
document.getElementById('tab-config-btn').addEventListener('click', () => switchTab('config'));
document.getElementById('prev-tab-btn').addEventListener('click', () => switchTab('dashboard'));
document.getElementById('next-tab-btn').addEventListener('click', () => switchTab('config'));
generateInitialData();
switchTab('dashboard');
renderAll();
});