257 lines
10 KiB
JavaScript
257 lines
10 KiB
JavaScript
class GPONDashboard {
|
|
constructor() {
|
|
this.updateInterval = 5000;
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
this.updateCurrent();
|
|
this.updateAlerts();
|
|
|
|
setInterval(() => this.updateCurrent(), this.updateInterval);
|
|
setInterval(() => this.updateAlerts(), 10000);
|
|
|
|
console.log('[Dashboard] Initialized');
|
|
}
|
|
|
|
async updateCurrent() {
|
|
try {
|
|
const response = await fetch('/api/current');
|
|
const data = await response.json();
|
|
|
|
console.log('[Dashboard] Data received:', data);
|
|
|
|
this.updateMetrics(data);
|
|
this.updateDeviceInfo(data);
|
|
this.updateStatus(data);
|
|
|
|
} catch (error) {
|
|
console.error('[Dashboard] Error:', error);
|
|
this.updateStatus({ status: 'error' });
|
|
}
|
|
}
|
|
|
|
updateMetrics(data) {
|
|
if (data.rx_power !== undefined && data.rx_power !== null) {
|
|
document.getElementById('rx-power').textContent = data.rx_power.toFixed(2);
|
|
const rxPercent = ((data.rx_power + 30) / 22) * 100;
|
|
const bar = document.getElementById('rx-power-bar');
|
|
bar.style.width = Math.max(0, Math.min(100, rxPercent)) + '%';
|
|
|
|
if (data.rx_power < -28) {
|
|
bar.className = 'progress-bar bg-danger';
|
|
} else if (data.rx_power < -25) {
|
|
bar.className = 'progress-bar bg-warning';
|
|
} else {
|
|
bar.className = 'progress-bar bg-info';
|
|
}
|
|
}
|
|
|
|
if (data.tx_power !== undefined && data.tx_power !== null) {
|
|
document.getElementById('tx-power').textContent = data.tx_power.toFixed(2);
|
|
const txPercent = ((data.tx_power + 5) / 10) * 100;
|
|
document.getElementById('tx-power-bar').style.width = Math.max(0, Math.min(100, txPercent)) + '%';
|
|
}
|
|
|
|
if (data.temperature !== undefined && data.temperature !== null) {
|
|
document.getElementById('temperature').textContent = data.temperature.toFixed(1);
|
|
const tempPercent = (data.temperature / 100) * 100;
|
|
const bar = document.getElementById('temp-bar');
|
|
bar.style.width = Math.max(0, Math.min(100, tempPercent)) + '%';
|
|
|
|
if (data.temperature > 80) {
|
|
bar.className = 'progress-bar bg-danger';
|
|
} else if (data.temperature > 60) {
|
|
bar.className = 'progress-bar bg-warning';
|
|
} else {
|
|
bar.className = 'progress-bar bg-success';
|
|
}
|
|
}
|
|
|
|
const uptimeElem = document.getElementById('uptime');
|
|
if (uptimeElem) {
|
|
if (data.uptime && data.uptime > 0) {
|
|
const seconds = parseInt(data.uptime);
|
|
const days = Math.floor(seconds / 86400);
|
|
const hours = Math.floor((seconds % 86400) / 3600);
|
|
const minutes = Math.floor((seconds % 3600) / 60);
|
|
|
|
let uptimeStr = '';
|
|
if (days > 0) uptimeStr += days + 'd ';
|
|
if (hours > 0 || days > 0) uptimeStr += hours + 'h ';
|
|
uptimeStr += minutes + 'm';
|
|
|
|
uptimeElem.textContent = uptimeStr.trim();
|
|
} else {
|
|
uptimeElem.textContent = '--';
|
|
}
|
|
}
|
|
}
|
|
|
|
updateDeviceInfo(data) {
|
|
this.setElementText('vendor-id', data.vendor_id);
|
|
this.setElementText('serial-number', data.serial_number);
|
|
this.setElementText('version', data.version);
|
|
this.setElementText('mac-address', data.mac_address);
|
|
|
|
this.setElementText('olt-vendor-info', data.olt_vendor_info);
|
|
this.setElementText('olt-version-info', data.olt_version_info);
|
|
|
|
const connTimeElem = document.getElementById('connection-time');
|
|
if (connTimeElem) {
|
|
if (data.connection_time) {
|
|
connTimeElem.textContent = data.connection_time;
|
|
} else if (data.uptime && data.uptime > 0) {
|
|
const seconds = parseInt(data.uptime);
|
|
const days = Math.floor(seconds / 86400);
|
|
const hours = Math.floor((seconds % 86400) / 3600);
|
|
const minutes = Math.floor((seconds % 3600) / 60);
|
|
|
|
let connTime = '';
|
|
if (days > 0) connTime += days + 'd ';
|
|
if (hours > 0 || days > 0) connTime += hours + 'h ';
|
|
connTime += minutes + 'm';
|
|
|
|
connTimeElem.textContent = connTime.trim();
|
|
} else {
|
|
connTimeElem.textContent = '--';
|
|
}
|
|
}
|
|
|
|
this.setElementText('rx-packets', (data.rx_packets || 0).toLocaleString('en-US'));
|
|
this.setElementText('tx-packets', (data.tx_packets || 0).toLocaleString('en-US'));
|
|
this.setElementText('fec-corrected', (data.fec_corrected || 0).toLocaleString('en-US'));
|
|
this.setElementText('fec-uncorrected', (data.fec_uncorrected || 0).toLocaleString('en-US'));
|
|
|
|
if (data.voltage !== undefined && data.voltage !== null) {
|
|
this.setElementText('voltage', data.voltage.toFixed(2) + ' V');
|
|
} else {
|
|
this.setElementText('voltage', 'N/A');
|
|
}
|
|
|
|
if (data.tx_bias_current !== undefined && data.tx_bias_current !== null) {
|
|
this.setElementText('tx-bias', data.tx_bias_current.toFixed(2) + ' mA');
|
|
} else {
|
|
this.setElementText('tx-bias', 'N/A');
|
|
}
|
|
|
|
const volumeElem = document.getElementById('data-volume-total');
|
|
if (volumeElem) {
|
|
if (data.rx_bytes && data.tx_bytes) {
|
|
const totalBytes = parseInt(data.rx_bytes) + parseInt(data.tx_bytes);
|
|
volumeElem.textContent = this.formatBytes(totalBytes);
|
|
} else {
|
|
volumeElem.textContent = '--';
|
|
}
|
|
}
|
|
|
|
const statusElem = document.getElementById('device-status');
|
|
if (statusElem) {
|
|
if (data.status === 'online') {
|
|
statusElem.innerHTML = '<span class="badge bg-success">Online</span>';
|
|
} else {
|
|
statusElem.innerHTML = '<span class="badge bg-danger">Offline</span>';
|
|
}
|
|
}
|
|
|
|
const lastUpdateElem = document.getElementById('last-update');
|
|
if (lastUpdateElem) {
|
|
if (data.timestamp) {
|
|
try {
|
|
const date = new Date(data.timestamp);
|
|
if (!isNaN(date.getTime())) {
|
|
lastUpdateElem.textContent = date.toLocaleTimeString('en-US');
|
|
} else {
|
|
lastUpdateElem.textContent = '--';
|
|
}
|
|
} catch (e) {
|
|
console.error('[Dashboard] Timestamp error:', e);
|
|
lastUpdateElem.textContent = '--';
|
|
}
|
|
} else {
|
|
lastUpdateElem.textContent = '--';
|
|
}
|
|
}
|
|
}
|
|
|
|
updateStatus(data) {
|
|
const indicator = document.getElementById('status-indicator');
|
|
if (!indicator) return;
|
|
|
|
if (data.status === 'online') {
|
|
indicator.innerHTML = '<i class="bi bi-circle-fill text-success"></i> Online';
|
|
} else if (data.status === 'error') {
|
|
indicator.innerHTML = '<i class="bi bi-circle-fill text-danger"></i> Error';
|
|
} else {
|
|
indicator.innerHTML = '<i class="bi bi-circle-fill text-warning"></i> Unknown';
|
|
}
|
|
}
|
|
|
|
async updateAlerts() {
|
|
try {
|
|
const response = await fetch('/api/alerts');
|
|
const alerts = await response.json();
|
|
|
|
const container = document.getElementById('alerts-container');
|
|
if (!container || !alerts || alerts.length === 0) return;
|
|
|
|
container.innerHTML = '';
|
|
|
|
alerts.forEach(alert => {
|
|
const alertDiv = document.createElement('div');
|
|
|
|
let alertClass = 'alert-warning';
|
|
if (alert.severity === 'critical') alertClass = 'alert-danger';
|
|
else if (alert.severity === 'info') alertClass = 'alert-info';
|
|
|
|
alertDiv.className = `alert ${alertClass} alert-dismissible fade show`;
|
|
|
|
const icon = alert.severity === 'critical' ? 'bi-exclamation-octagon' :
|
|
(alert.severity === 'info' ? 'bi-info-circle' : 'bi-exclamation-triangle');
|
|
|
|
let timestamp = '';
|
|
if (alert.timestamp) {
|
|
try {
|
|
const date = new Date(alert.timestamp);
|
|
timestamp = date.toLocaleTimeString('en-US');
|
|
} catch (e) {}
|
|
}
|
|
|
|
const category = (alert.category || 'system').toUpperCase();
|
|
|
|
alertDiv.innerHTML = `
|
|
<i class="bi ${icon}"></i>
|
|
<strong>${category}:</strong> ${alert.message}
|
|
${timestamp ? `<small class="float-end">${timestamp}</small>` : ''}
|
|
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
|
`;
|
|
|
|
container.appendChild(alertDiv);
|
|
});
|
|
|
|
} catch (error) {
|
|
console.error('[Dashboard] Error updating alerts:', error);
|
|
}
|
|
}
|
|
|
|
setElementText(id, value) {
|
|
const elem = document.getElementById(id);
|
|
if (elem) {
|
|
elem.textContent = (value !== undefined && value !== null && value !== '') ? value : '--';
|
|
}
|
|
}
|
|
|
|
formatBytes(bytes, decimals = 2) {
|
|
if (bytes === 0) return '0 B';
|
|
const k = 1024;
|
|
const dm = decimals < 0 ? 0 : decimals;
|
|
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
|
}
|
|
}
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
window.dashboard = new GPONDashboard();
|
|
});
|