415 lines
19 KiB
JavaScript
415 lines
19 KiB
JavaScript
// document.addEventListener('DOMContentLoaded', () => {
|
|
// // --- DOM ELEMENTS ---
|
|
// const stationsGrid = document.getElementById('stations-grid');
|
|
// const stationTemplate = document.getElementById('stationCardTemplate');
|
|
// const errorMessage = document.getElementById('error-message');
|
|
// const stationCountEl = document.getElementById('station-count');
|
|
// // Note: SocketIO is not needed on this page anymore
|
|
|
|
// let allStations = []; // To store the master list of stations
|
|
|
|
// // --- AUTHENTICATION & USER INFO (Your existing code is perfect) ---
|
|
// const user = JSON.parse(localStorage.getItem('user'));
|
|
// if (!user) {
|
|
// window.location.href = 'index.html'; // Redirect if not logged in
|
|
// return;
|
|
// }
|
|
|
|
// // User info and logout button logic... (omitted for brevity, no changes needed)
|
|
|
|
// // --- ADMIN FEATURES (Your existing code is perfect) ---
|
|
// // Admin button and add station card logic... (omitted for brevity, no changes needed)
|
|
|
|
|
|
// // --- HELPER FUNCTIONS ---
|
|
// const getStatusAttributes = (status) => {
|
|
// switch (status) {
|
|
// case 'Online': return { color: 'text-green-500', bgColor: 'bg-green-100/60 dark:bg-green-500/10', icon: 'power' };
|
|
// case 'Offline': return { color: 'text-red-500', bgColor: 'bg-red-100/60 dark:bg-red-500/10', icon: 'power-off' };
|
|
// default: return { color: 'text-gray-500', bgColor: 'bg-gray-100/60 dark:bg-gray-500/10', icon: 'help-circle' };
|
|
// }
|
|
// };
|
|
|
|
// const handleStationSelect = (stationId) => {
|
|
// window.location.href = `dashboard.html?station_id=${stationId}`;
|
|
// };
|
|
|
|
// // This function now only renders the initial grid
|
|
// // const renderStations = (stations) => {
|
|
// // stationsGrid.innerHTML = '';
|
|
// // stationCountEl.textContent = `${stations.length} stations found. Select one to monitor.`;
|
|
|
|
// // stations.forEach(station => {
|
|
// // const status = getStatusAttributes(station.status);
|
|
// // const card = document.createElement('div');
|
|
// // card.className = "group bg-gray-900/60 backdrop-blur-xl rounded-2xl shadow-lg border border-gray-700 transition-transform duration-300 ease-out cursor-pointer flex flex-col justify-between hover:-translate-y-1.5 hover:border-emerald-400/60 hover:shadow-[0_0_0_1px_rgba(16,185,129,0.25),0_20px_40px_rgba(0,0,0,0.45)]";
|
|
// // card.id = `station-${station.id}`;
|
|
// // card.onclick = () => handleStationSelect(station.id);
|
|
|
|
// // card.innerHTML = `
|
|
// // <div class="p-5 flex-grow">
|
|
// // <div class="flex justify-between items-start">
|
|
// // <h3 class="text-lg font-bold text-white pr-2">${station.name}</h3>
|
|
// // <div class="status-badge flex items-center text-xs font-semibold px-3 py-1 rounded-full ${status.bgColor} ${status.color}">
|
|
// // <i data-lucide="${status.icon}" class="w-4 h-4 mr-1.5"></i>
|
|
// // <span class="status-text">${station.status}</span>
|
|
// // </div>
|
|
// // </div>
|
|
// // <p class="text-sm text-gray-400 mt-1">${station.id}</p>
|
|
// // </div>
|
|
// // <div class="border-t border-gray-700/50 px-5 py-2 flex justify-end">
|
|
// // <button
|
|
// // class="remove-btn text-xs font-semibold text-red-500 hover:text-red-400 transition"
|
|
// // data-id="${station.id}"
|
|
// // data-name="${station.name}"
|
|
// // >
|
|
// // Remove Station
|
|
// // </button>
|
|
// // </div>
|
|
// // `;
|
|
// // stationsGrid.appendChild(card);
|
|
// // });
|
|
// // lucide.createIcons();
|
|
// // };
|
|
|
|
// const renderStations = (stations) => {
|
|
// stationsGrid.innerHTML = '';
|
|
// stationCountEl.textContent = `${stations.length} stations found. Select one to monitor.`;
|
|
|
|
// stations.forEach(station => {
|
|
// const status = getStatusAttributes(station.status);
|
|
// const card = document.createElement('div');
|
|
// card.className = "group bg-gray-900/60 backdrop-blur-xl rounded-2xl shadow-lg border border-gray-700 transition-transform duration-300 ease-out flex flex-col justify-between hover:-translate-y-1.5 hover:border-emerald-400/60 hover:shadow-[0_0_0_1px_rgba(16,185,129,0.25),0_20px_40px_rgba(0,0,0,0.45)]";
|
|
// card.id = `station-${station.id}`;
|
|
|
|
// // --- Make the main card content clickable ---
|
|
// const mainContent = document.createElement('div');
|
|
// mainContent.className = "p-5 flex-grow cursor-pointer";
|
|
// mainContent.onclick = () => handleStationSelect(station.id);
|
|
// mainContent.innerHTML = `
|
|
// <div class="flex justify-between items-start">
|
|
// <h3 class="text-lg font-bold text-white pr-2">${station.name}</h3>
|
|
// <div class="status-badge flex items-center text-xs font-semibold px-3 py-1 rounded-full ${status.bgColor} ${status.color}">
|
|
// <i data-lucide="${status.icon}" class="w-4 h-4 mr-1.5"></i>
|
|
// <span class="status-text">${station.status}</span>
|
|
// </div>
|
|
// </div>
|
|
// <p class="text-sm text-gray-400 mt-1">${station.id}</p>
|
|
// `;
|
|
|
|
// // --- Create the footer with the remove button ---
|
|
// const footer = document.createElement('div');
|
|
// footer.className = "border-t border-gray-700/50 px-5 py-2 flex justify-end";
|
|
// footer.innerHTML = `
|
|
// <button
|
|
// class="remove-btn text-xs font-semibold text-red-500 hover:text-red-400 transition"
|
|
// data-id="${station.id}"
|
|
// data-name="${station.name}"
|
|
// >
|
|
// Remove Station
|
|
// </button>
|
|
// `;
|
|
|
|
// card.appendChild(mainContent);
|
|
// card.appendChild(footer);
|
|
// stationsGrid.appendChild(card);
|
|
// });
|
|
// lucide.createIcons();
|
|
// };
|
|
|
|
// // --- NEW: Function to update statuses without redrawing everything ---
|
|
// const updateStationStatuses = (stations) => {
|
|
// stations.forEach(station => {
|
|
// const card = document.getElementById(`station-${station.id}`);
|
|
// if (card) {
|
|
// const status = getStatusAttributes(station.status);
|
|
// const statusBadge = card.querySelector('.status-badge');
|
|
// const statusText = card.querySelector('.status-text');
|
|
// const statusIcon = card.querySelector('i[data-lucide]');
|
|
|
|
// if (statusBadge && statusText && statusIcon) {
|
|
// statusBadge.className = `status-badge flex items-center text-xs font-semibold px-3 py-1 rounded-full ${status.bgColor} ${status.color}`;
|
|
// statusText.textContent = station.status;
|
|
// statusIcon.setAttribute('data-lucide', status.icon);
|
|
// }
|
|
// }
|
|
// });
|
|
// lucide.createIcons(); // Re-render icons if any changed
|
|
// };
|
|
|
|
// // --- DATA FETCHING & STATUS POLLING ---
|
|
// const loadAndPollStations = async () => {
|
|
// try {
|
|
// const response = await fetch('http://10.10.1.183:5000/api/stations');
|
|
// if (!response.ok) throw new Error('Failed to fetch stations');
|
|
// const stations = await response.json();
|
|
|
|
// // Check if this is the first time loading data
|
|
// if (allStations.length === 0) {
|
|
// allStations = stations;
|
|
// renderStations(allStations); // Initial full render
|
|
// } else {
|
|
// allStations = stations;
|
|
// updateStationStatuses(allStations); // Subsequent, efficient updates
|
|
// }
|
|
|
|
// } catch (error) {
|
|
// console.error(error);
|
|
// stationCountEl.textContent = 'Could not load stations. Is the backend running?';
|
|
// // Stop polling on error
|
|
// if (pollingInterval) clearInterval(pollingInterval);
|
|
// }
|
|
// };
|
|
|
|
// // In station_selection.js
|
|
// stationsGrid.addEventListener('click', async (event) => {
|
|
// const removeButton = event.target.closest('.remove-btn');
|
|
|
|
// if (!removeButton) return;
|
|
|
|
// // Stop the click from triggering the card's navigation
|
|
// event.stopPropagation();
|
|
|
|
// const stationId = removeButton.dataset.id;
|
|
// const stationName = removeButton.dataset.name;
|
|
|
|
// if (!confirm(`Are you sure you want to permanently remove "${stationName}"?`)) {
|
|
// return;
|
|
// }
|
|
|
|
// try {
|
|
// const response = await fetch(`http://10.10.1.183:5000/api/stations/${stationId}`, {
|
|
// method: 'DELETE',
|
|
// });
|
|
|
|
// if (response.ok) {
|
|
// alert(`Station "${stationName}" removed successfully.`);
|
|
// allStations = [];
|
|
// loadAndPollStations();
|
|
// } else {
|
|
// const error = await response.json();
|
|
// alert(`Failed to remove station: ${error.message}`);
|
|
// }
|
|
// } catch (error) {
|
|
// console.error('Error removing station:', error);
|
|
// alert('An error occurred while trying to remove the station.');
|
|
// }
|
|
// });
|
|
|
|
// // --- INITIALIZATION ---
|
|
// loadAndPollStations(); // Load immediately on page start
|
|
// // Then, set an interval to refresh the statuses every 10 seconds
|
|
// const pollingInterval = setInterval(loadAndPollStations, 10000);
|
|
// });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
// --- DOM ELEMENTS ---
|
|
const stationsGrid = document.getElementById('stations-grid');
|
|
const stationCountEl = document.getElementById('station-count'); // Make sure you have an element with this ID in your HTML
|
|
|
|
// --- CONFIG & STATE ---
|
|
const API_BASE = 'http://10.10.1.183:5000/api';
|
|
let allStations = []; // Master list of stations from the API
|
|
let pollingInterval = null;
|
|
|
|
// --- AUTHENTICATION ---
|
|
const user = JSON.parse(localStorage.getItem('user'));
|
|
if (!user) {
|
|
window.location.href = 'index.html'; // Redirect if not logged in
|
|
return;
|
|
}
|
|
|
|
// (Your other button listeners for logout, add user, etc., can go here)
|
|
// document.getElementById('logoutBtn').onclick = () => { ... };
|
|
|
|
// --- HELPER FUNCTIONS ---
|
|
const getStatusAttributes = (status) => {
|
|
switch (status) {
|
|
case 'Online': return { color: 'text-green-500', bgColor: 'bg-green-100/60 dark:bg-green-500/10', icon: 'power' };
|
|
case 'Offline': return { color: 'text-red-500', bgColor: 'bg-red-100/60 dark:bg-red-500/10', icon: 'power-off' };
|
|
default: return { color: 'text-gray-500', bgColor: 'bg-gray-100/60 dark:bg-gray-500/10', icon: 'help-circle' };
|
|
}
|
|
};
|
|
|
|
const handleStationSelect = (stationId) => {
|
|
window.location.href = `dashboard.html?station_id=${stationId}`;
|
|
};
|
|
|
|
// --- UI RENDERING ---
|
|
// This function's only job is to build the HTML. It does not add event listeners.
|
|
const renderStations = (stations) => {
|
|
stationsGrid.innerHTML = ''; // Clear the grid
|
|
stationCountEl.textContent = `${stations.length} stations found.`;
|
|
|
|
stations.forEach(station => {
|
|
const status = getStatusAttributes(station.status);
|
|
const card = document.createElement('div');
|
|
// Add station ID to the card's dataset for easy access
|
|
card.dataset.stationId = station.id;
|
|
card.dataset.stationName = station.name;
|
|
card.className = "group bg-gray-900/60 backdrop-blur-xl rounded-2xl shadow-lg border border-gray-700 transition-transform duration-300 ease-out flex flex-col justify-between hover:-translate-y-1.5 hover:border-emerald-400/60 hover:shadow-[0_0_0_1px_rgba(16,185,129,0.25),0_20px_40px_rgba(0,0,0,0.45)]";
|
|
|
|
card.innerHTML = `
|
|
<div class="main-content p-5 flex-grow cursor-pointer" data-station-json='${JSON.stringify(station)}'>
|
|
<div class="flex justify-between items-start">
|
|
<div>
|
|
<h3 class="text-lg font-bold text-white pr-2">${station.name}</h3>
|
|
<p class="text-xs text-slate-400 font-mono"># ${station.product_id || 'N/A'}</p>
|
|
</div>
|
|
<div class="status-badge flex items-center text-xs font-semibold px-3 py-1 rounded-full ${status.bgColor} ${status.color}">
|
|
<span class="status-text">${station.status}</span>
|
|
</div>
|
|
</div>
|
|
<p class="text-sm text-gray-400 mt-2 font-mono">${station.id}</p>
|
|
</div>
|
|
|
|
<div class="border-t border-gray-700/50 px-5 pt-3 pb-4">
|
|
<div class="grid grid-cols-3 gap-2 text-center">
|
|
<div>
|
|
<p class="text-xs text-slate-400">Total Starts</p>
|
|
<p class="font-bold text-lg text-white stat-total">0</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs text-slate-400">Completed</p>
|
|
<p class="font-bold text-lg text-emerald-400 stat-completed">0</p>
|
|
</div>
|
|
<div>
|
|
<p class="text-xs text-slate-400">Aborted</p>
|
|
<p class="font-bold text-lg text-rose-400 stat-aborted">0</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="border-t border-gray-700/50 px-5 py-2 flex justify-between items-center bg-black/20 rounded-b-2xl">
|
|
<button class="open-btn text-sm font-bold bg-emerald-500/80 hover:bg-emerald-500 text-white py-1 px-4 rounded-md transition">
|
|
Open
|
|
</button>
|
|
<button class="remove-btn text-gray-400 hover:text-red-500 transition" title="Remove Station">
|
|
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path></svg>
|
|
</button>
|
|
</div>
|
|
`;
|
|
stationsGrid.appendChild(card);
|
|
});
|
|
|
|
if (window.lucide) {
|
|
lucide.createIcons();
|
|
}
|
|
};
|
|
|
|
const updateStationStatuses = (stations) => {
|
|
stations.forEach(station => {
|
|
const card = stationsGrid.querySelector(`[data-station-id="${station.id}"]`);
|
|
if (card) {
|
|
const status = getStatusAttributes(station.status);
|
|
const statusBadge = card.querySelector('.status-badge');
|
|
const statusText = card.querySelector('.status-text');
|
|
const statusIcon = card.querySelector('i[data-lucide]');
|
|
if (statusBadge && statusText && statusIcon) {
|
|
statusBadge.className = `status-badge flex items-center text-xs font-semibold px-3 py-1 rounded-full ${status.bgColor} ${status.color}`;
|
|
statusText.textContent = station.status;
|
|
statusIcon.setAttribute('data-lucide', status.icon);
|
|
}
|
|
}
|
|
});
|
|
if (window.lucide) {
|
|
lucide.createIcons();
|
|
}
|
|
};
|
|
|
|
|
|
//-- NEW: Fetch and apply daily stats to each card ---
|
|
const fetchAndApplyStats = async () => {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/stations/daily-stats`);
|
|
if (!response.ok) return; // Fail silently if stats aren't available
|
|
const stats = await response.json();
|
|
|
|
// Loop through the stats object and update each card
|
|
for (const stationId in stats) {
|
|
const stationCard = stationsGrid.querySelector(`.station-card[data-station-id="${stationId}"]`);
|
|
if (stationCard) {
|
|
const statData = stats[stationId];
|
|
stationCard.querySelector('.stat-total').textContent = statData.total_starts;
|
|
stationCard.querySelector('.stat-completed').textContent = statData.completed;
|
|
stationCard.querySelector('.stat-aborted').textContent = statData.aborted;
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Could not fetch daily stats:", error);
|
|
}
|
|
};
|
|
|
|
// --- MAIN EVENT LISTENER ---
|
|
// This single listener handles all clicks on the grid for efficiency.
|
|
stationsGrid.addEventListener('click', async (event) => {
|
|
const mainContent = event.target.closest('.main-content');
|
|
const removeButton = event.target.closest('.remove-btn');
|
|
|
|
if (mainContent) {
|
|
const card = mainContent.closest('[data-station-id]');
|
|
if (card) {
|
|
handleStationSelect(card.dataset.stationId);
|
|
}
|
|
} else if (removeButton) {
|
|
event.stopPropagation(); // Prevent main content click
|
|
const card = removeButton.closest('[data-station-id]');
|
|
const stationId = card.dataset.stationId;
|
|
const stationName = card.dataset.stationName;
|
|
|
|
if (!confirm(`Are you sure you want to permanently remove "${stationName}"?`)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${API_BASE}/stations/${stationId}`, { method: 'DELETE' });
|
|
if (response.ok) {
|
|
alert(`Station "${stationName}" removed successfully.`);
|
|
allStations = []; // Force a full refresh on next poll
|
|
loadAndPollStations();
|
|
} else {
|
|
const error = await response.json();
|
|
alert(`Failed to remove station: ${error.message}`);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error removing station:', error);
|
|
alert('An error occurred while trying to remove the station.');
|
|
}
|
|
}
|
|
});
|
|
|
|
// --- DATA FETCHING & POLLING ---
|
|
const loadAndPollStations = async () => {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/stations`);
|
|
if (!response.ok) throw new Error('Failed to fetch stations');
|
|
|
|
const newStationList = await response.json();
|
|
|
|
// If the number of stations has changed, we must do a full re-render.
|
|
if (newStationList.length !== allStations.length) {
|
|
allStations = newStationList;
|
|
renderStations(allStations);
|
|
} else {
|
|
// Otherwise, we can do a more efficient status-only update.
|
|
allStations = newStationList;
|
|
updateStationStatuses(allStations);
|
|
fetchAndApplyStats(); // Fetch and update daily stats
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
stationCountEl.textContent = 'Could not load stations. Is the backend running?';
|
|
if (pollingInterval) clearInterval(pollingInterval);
|
|
}
|
|
};
|
|
|
|
// --- INITIALIZATION ---
|
|
loadAndPollStations(); // Load immediately on page start
|
|
pollingInterval = setInterval(loadAndPollStations, 10000);
|
|
}); |