239 lines
11 KiB
JavaScript
239 lines
11 KiB
JavaScript
// frontend/js/common-header.js
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// --- CONFIGURATION ---
|
|
const SOCKET_URL = "http://192.168.1.10:5000";
|
|
const API_BASE = "http://192.168.1.10:5000/api";
|
|
|
|
// --- STATE & SELECTED STATION ---
|
|
let selectedStation = null;
|
|
let socket;
|
|
let statusPollingInterval;
|
|
|
|
try {
|
|
selectedStation = JSON.parse(localStorage.getItem('selected_station'));
|
|
if (!selectedStation || !selectedStation.id) {
|
|
window.location.href = './station_selection.html';
|
|
return;
|
|
}
|
|
} catch (e) {
|
|
window.location.href = './station_selection.html';
|
|
return;
|
|
}
|
|
|
|
// --- HEADER DOM ELEMENTS ---
|
|
const stationNameEl = document.getElementById('station-name');
|
|
const stationLocationEl = document.getElementById('station-location');
|
|
const stationIdEl = document.getElementById('station-id-display');
|
|
const productIdEl = document.getElementById('product-id-display');
|
|
const connChip = document.getElementById('connection-status-chip');
|
|
const lastUpdateEl = document.getElementById('last-update-status');
|
|
const logoutBtn = document.getElementById('logout-btn');
|
|
const navLinks = document.getElementById('main-nav');
|
|
const downloadBtn = document.getElementById('downloadBtn');
|
|
|
|
// --- INITIALIZATION ---
|
|
function initializeHeader() {
|
|
// Populate header with initial data
|
|
if (stationNameEl) stationNameEl.textContent = selectedStation.name || 'Unknown';
|
|
if (stationLocationEl) stationLocationEl.textContent = selectedStation.location || 'No location';
|
|
if (stationIdEl) stationIdEl.textContent = selectedStation.id;
|
|
if (productIdEl) productIdEl.textContent = selectedStation.product_id;
|
|
|
|
// Highlight the active tab
|
|
if (navLinks) {
|
|
const currentPage = window.location.pathname.split('/').pop();
|
|
const activeLink = navLinks.querySelector(`a[href="./${currentPage}"]`);
|
|
if (activeLink) {
|
|
activeLink.classList.remove('text-gray-400', 'hover:text-gray-200');
|
|
activeLink.classList.add('border-b-2', 'border-emerald-400/70', 'text-white');
|
|
}
|
|
}
|
|
|
|
// Start polling and connect WebSocket
|
|
checkStationStatus();
|
|
statusPollingInterval = setInterval(checkStationStatus, 10000);
|
|
connectSocket();
|
|
|
|
// Add event listener for the logout button
|
|
if (logoutBtn) {
|
|
logoutBtn.addEventListener('click', () => {
|
|
localStorage.clear();
|
|
window.location.href = './index.html';
|
|
});
|
|
}
|
|
|
|
if (downloadBtn) {
|
|
downloadBtn.addEventListener('click', showDownloadModal);
|
|
}
|
|
}
|
|
|
|
// --- POLLING FOR ONLINE/OFFLINE STATUS ---
|
|
const checkStationStatus = async () => {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/stations`);
|
|
if (!response.ok) return;
|
|
const stations = await response.json();
|
|
const thisStation = stations.find(s => s.id === selectedStation.id);
|
|
|
|
if (thisStation && connChip) {
|
|
if (thisStation.status === 'Online') {
|
|
connChip.innerHTML = `<span class="h-2 w-2 rounded-full bg-emerald-400 animate-pulseDot"></span> Online`;
|
|
connChip.className = 'cham_chip cham_chip-emerald';
|
|
} else {
|
|
connChip.innerHTML = `<span class="h-2 w-2 rounded-full bg-rose-500"></span> Offline`;
|
|
connChip.className = 'cham_chip cham_chip-rose';
|
|
if (lastUpdateEl) lastUpdateEl.textContent = "Waiting for data...";
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Failed to fetch station status:", error);
|
|
}
|
|
};
|
|
|
|
const showDownloadModal = () => {
|
|
const modalOverlay = document.createElement('div');
|
|
modalOverlay.className = "fixed inset-0 bg-black/70 backdrop-blur-sm flex items-center justify-center z-50";
|
|
|
|
modalOverlay.innerHTML = `
|
|
<div class="bg-slate-800 border border-slate-700 rounded-lg shadow-xl p-6 w-full max-w-md">
|
|
<h3 class="text-lg font-bold text-white mb-4">Export Logs</h3>
|
|
<div class="space-y-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-300 mb-2">Quick Time Ranges</label>
|
|
<div class="grid grid-cols-3 gap-2">
|
|
<button data-range="1" class="time-range-btn btn btn-ghost !py-1.5">Last Hour</button>
|
|
<button data-range="6" class="time-range-btn btn btn-ghost !py-1.5">Last 6 Hours</button>
|
|
<button data-range="24" class="time-range-btn btn btn-ghost !py-1.5">Last 24 Hours</button>
|
|
<button data-range="today" class="time-range-btn btn btn-ghost !py-1.5">Today</button>
|
|
<button data-range="yesterday" class="time-range-btn btn btn-ghost !py-1.5">Yesterday</button>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label for="log-type" class="block text-sm font-medium text-gray-300">Log Type</label>
|
|
<select id="log-type" class="mt-1 block w-full bg-slate-700 border-slate-600 rounded-md p-2 text-white">
|
|
<option value="PERIODIC">Periodic Data</option>
|
|
<option value="EVENT">Events & RPC</option>
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label for="start-datetime" class="block text-sm font-medium text-gray-300">Start Date & Time</label>
|
|
<input type="text" id="start-datetime" class="mt-1 block w-full bg-slate-700 border-slate-600 rounded-md p-2 text-white">
|
|
</div>
|
|
<div>
|
|
<label for="end-datetime" class="block text-sm font-medium text-gray-300">End Date & Time</label>
|
|
<input type="text" id="end-datetime" class="mt-1 block w-full bg-slate-700 border-slate-600 rounded-md p-2 text-white">
|
|
</div>
|
|
</div>
|
|
<div class="mt-6 flex justify-end gap-3">
|
|
<button id="cancel-download" class="btn btn-ghost px-4 py-2">Cancel</button>
|
|
<button id="confirm-download" class="btn btn-primary px-4 py-2">Download CSV</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
document.body.appendChild(modalOverlay);
|
|
|
|
const startInput = document.getElementById('start-datetime');
|
|
const endInput = document.getElementById('end-datetime');
|
|
|
|
const fpConfig = {
|
|
enableTime: true,
|
|
dateFormat: "Y-m-d\\TH:i",
|
|
time_24hr: true
|
|
};
|
|
const fpStart = flatpickr(startInput, fpConfig);
|
|
const fpEnd = flatpickr(endInput, fpConfig);
|
|
|
|
const now = new Date();
|
|
fpStart.setDate(new Date(now.getTime() - 3600 * 1000), true);
|
|
fpEnd.setDate(now, true);
|
|
|
|
modalOverlay.querySelectorAll('.time-range-btn').forEach(button => {
|
|
button.addEventListener('click', () => {
|
|
const range = button.dataset.range;
|
|
const now = new Date();
|
|
let start = new Date();
|
|
if (range === 'today') {
|
|
start.setHours(0, 0, 0, 0);
|
|
} else if (range === 'yesterday') {
|
|
start.setDate(start.getDate() - 1);
|
|
start.setHours(0, 0, 0, 0);
|
|
now.setDate(now.getDate() - 1);
|
|
now.setHours(23, 59, 59, 999);
|
|
} else {
|
|
start.setHours(now.getHours() - parseInt(range, 10));
|
|
}
|
|
fpStart.setDate(start, true);
|
|
fpEnd.setDate(now, true);
|
|
});
|
|
});
|
|
|
|
document.getElementById('cancel-download').onclick = () => document.body.removeChild(modalOverlay);
|
|
|
|
document.getElementById('confirm-download').onclick = async () => {
|
|
const logType = document.getElementById('log-type').value;
|
|
const startDateStr = document.getElementById('start-datetime').value;
|
|
const endDateStr = document.getElementById('end-datetime').value;
|
|
const confirmBtn = document.getElementById('confirm-download');
|
|
|
|
if (!startDateStr || !endDateStr) {
|
|
return alert('Please select both a start and end date/time.');
|
|
}
|
|
|
|
confirmBtn.textContent = 'Fetching...';
|
|
confirmBtn.disabled = true;
|
|
|
|
const downloadUrl = `${API_BASE}/logs/export?station_id=${selectedStation.id}&start_datetime=${startDateStr}&end_datetime=${endDateStr}&log_type=${logType}`;
|
|
|
|
try {
|
|
const response = await fetch(downloadUrl);
|
|
if (response.ok) {
|
|
const blob = await response.blob();
|
|
const url = window.URL.createObjectURL(blob);
|
|
const a = document.createElement('a');
|
|
a.style.display = 'none';
|
|
a.href = url;
|
|
|
|
let filename = `${selectedStation.name || selectedStation.id}_${logType}_${startDateStr.split('T')[0]}.csv`;
|
|
const disposition = response.headers.get('Content-Disposition');
|
|
if (disposition && disposition.includes('attachment')) {
|
|
const filenameMatch = disposition.match(/filename="(.+?)"/);
|
|
if (filenameMatch && filenameMatch.length === 2) filename = filenameMatch[1];
|
|
}
|
|
a.download = filename;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
window.URL.revokeObjectURL(url);
|
|
document.body.removeChild(a);
|
|
document.body.removeChild(modalOverlay);
|
|
} else {
|
|
const errorData = await response.json();
|
|
alert(`Could not download: ${errorData.message}`);
|
|
}
|
|
} catch (error) {
|
|
alert('An unexpected error occurred. Please check the console.');
|
|
console.error('Download error:', error);
|
|
} finally {
|
|
confirmBtn.textContent = 'Download CSV';
|
|
confirmBtn.disabled = false;
|
|
}
|
|
};
|
|
};
|
|
|
|
// --- WEBSOCKET CONNECTION ---
|
|
const connectSocket = () => {
|
|
socket = io(SOCKET_URL);
|
|
socket.on('connect', () => {
|
|
console.log("Header: Connected to WebSocket.");
|
|
socket.emit('join_station_room', { station_id: selectedStation.id });
|
|
});
|
|
socket.on('disconnect', () => console.log("Header: Disconnected from WebSocket."));
|
|
socket.on('dashboard_update', (message) => {
|
|
if (message.stationId === selectedStation.id && lastUpdateEl) {
|
|
lastUpdateEl.textContent = `Last Recv ${new Date().toLocaleTimeString()}`;
|
|
}
|
|
});
|
|
};
|
|
|
|
// --- START THE SCRIPT ---
|
|
initializeHeader();
|
|
}); |