Date issues
This commit is contained in:
139
web/admin.html
139
web/admin.html
@@ -927,6 +927,11 @@
|
||||
let isNewPPR = false;
|
||||
let wsConnection = null;
|
||||
let pendingStatusUpdate = null; // Track pending status update for timestamp modal
|
||||
let wsHeartbeatInterval = null;
|
||||
let wsReconnectTimeout = null;
|
||||
let lastHeartbeatResponse = null;
|
||||
let sessionExpiryWarningShown = false;
|
||||
let sessionExpiryCheckInterval = null;
|
||||
|
||||
// WebSocket connection for real-time updates
|
||||
function connectWebSocket() {
|
||||
@@ -934,17 +939,34 @@
|
||||
return; // Already connected
|
||||
}
|
||||
|
||||
// Clear any existing reconnect timeout
|
||||
if (wsReconnectTimeout) {
|
||||
clearTimeout(wsReconnectTimeout);
|
||||
wsReconnectTimeout = null;
|
||||
}
|
||||
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsUrl = `${protocol}//${window.location.host}/ws/tower-updates`;
|
||||
|
||||
console.log('Connecting to WebSocket:', wsUrl);
|
||||
wsConnection = new WebSocket(wsUrl);
|
||||
|
||||
wsConnection.onopen = function(event) {
|
||||
console.log('WebSocket connected for real-time updates');
|
||||
console.log('✅ WebSocket connected for real-time updates');
|
||||
lastHeartbeatResponse = Date.now();
|
||||
startHeartbeat();
|
||||
showNotification('Real-time updates connected');
|
||||
};
|
||||
|
||||
wsConnection.onmessage = function(event) {
|
||||
try {
|
||||
// Check if it's a heartbeat response
|
||||
if (event.data.startsWith('Heartbeat:')) {
|
||||
lastHeartbeatResponse = Date.now();
|
||||
console.log('💓 WebSocket heartbeat received');
|
||||
return;
|
||||
}
|
||||
|
||||
const data = JSON.parse(event.data);
|
||||
console.log('WebSocket message received:', data);
|
||||
|
||||
@@ -952,6 +974,7 @@
|
||||
if (data.type && (data.type.includes('ppr_') || data.type === 'status_update')) {
|
||||
console.log('PPR update detected, refreshing...');
|
||||
loadPPRs();
|
||||
showNotification('Data updated');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error parsing WebSocket message:', error);
|
||||
@@ -959,22 +982,59 @@
|
||||
};
|
||||
|
||||
wsConnection.onclose = function(event) {
|
||||
console.log('WebSocket disconnected');
|
||||
// Attempt to reconnect after 5 seconds
|
||||
setTimeout(() => {
|
||||
if (accessToken) { // Only reconnect if still logged in
|
||||
console.log('⚠️ WebSocket disconnected', event.code, event.reason);
|
||||
stopHeartbeat();
|
||||
|
||||
// Attempt to reconnect after 5 seconds if still logged in
|
||||
if (accessToken) {
|
||||
showNotification('Real-time updates disconnected, reconnecting...', true);
|
||||
wsReconnectTimeout = setTimeout(() => {
|
||||
console.log('Attempting to reconnect WebSocket...');
|
||||
connectWebSocket();
|
||||
}
|
||||
}, 5000);
|
||||
}, 5000);
|
||||
}
|
||||
};
|
||||
|
||||
wsConnection.onerror = function(error) {
|
||||
console.error('WebSocket error:', error);
|
||||
console.error('❌ WebSocket error:', error);
|
||||
};
|
||||
}
|
||||
|
||||
function startHeartbeat() {
|
||||
// Clear any existing heartbeat
|
||||
stopHeartbeat();
|
||||
|
||||
// Send ping every 30 seconds
|
||||
wsHeartbeatInterval = setInterval(() => {
|
||||
if (wsConnection && wsConnection.readyState === WebSocket.OPEN) {
|
||||
wsConnection.send('ping');
|
||||
console.log('💓 Sending WebSocket heartbeat');
|
||||
|
||||
// Check if last heartbeat was more than 60 seconds ago
|
||||
if (lastHeartbeatResponse && (Date.now() - lastHeartbeatResponse > 60000)) {
|
||||
console.warn('⚠️ No heartbeat response for 60 seconds, reconnecting...');
|
||||
wsConnection.close();
|
||||
}
|
||||
} else {
|
||||
console.warn('⚠️ WebSocket not open, stopping heartbeat');
|
||||
stopHeartbeat();
|
||||
}
|
||||
}, 30000);
|
||||
}
|
||||
|
||||
function stopHeartbeat() {
|
||||
if (wsHeartbeatInterval) {
|
||||
clearInterval(wsHeartbeatInterval);
|
||||
wsHeartbeatInterval = null;
|
||||
}
|
||||
}
|
||||
|
||||
function disconnectWebSocket() {
|
||||
stopHeartbeat();
|
||||
if (wsReconnectTimeout) {
|
||||
clearTimeout(wsReconnectTimeout);
|
||||
wsReconnectTimeout = null;
|
||||
}
|
||||
if (wsConnection) {
|
||||
wsConnection.close();
|
||||
wsConnection = null;
|
||||
@@ -1020,6 +1080,52 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Session expiry monitoring
|
||||
function startSessionExpiryCheck() {
|
||||
// Clear any existing interval
|
||||
if (sessionExpiryCheckInterval) {
|
||||
clearInterval(sessionExpiryCheckInterval);
|
||||
}
|
||||
|
||||
sessionExpiryWarningShown = false;
|
||||
|
||||
// Check every 60 seconds
|
||||
sessionExpiryCheckInterval = setInterval(() => {
|
||||
const tokenExpiry = localStorage.getItem('ppr_token_expiry');
|
||||
if (!tokenExpiry || !accessToken) {
|
||||
stopSessionExpiryCheck();
|
||||
return;
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
const expiryTime = parseInt(tokenExpiry);
|
||||
const timeUntilExpiry = expiryTime - now;
|
||||
|
||||
// Warn if less than 5 minutes remaining
|
||||
if (timeUntilExpiry < 5 * 60 * 1000 && !sessionExpiryWarningShown) {
|
||||
sessionExpiryWarningShown = true;
|
||||
const minutesLeft = Math.ceil(timeUntilExpiry / 60000);
|
||||
showNotification(`⚠️ Session expires in ${minutesLeft} minute${minutesLeft !== 1 ? 's' : ''}. Please save your work.`, true);
|
||||
console.warn(`Session expires in ${minutesLeft} minutes`);
|
||||
}
|
||||
|
||||
// Force logout if expired
|
||||
if (timeUntilExpiry <= 0) {
|
||||
console.error('Session has expired');
|
||||
showNotification('Session expired. Please log in again.', true);
|
||||
logout();
|
||||
}
|
||||
}, 60000); // Check every minute
|
||||
}
|
||||
|
||||
function stopSessionExpiryCheck() {
|
||||
if (sessionExpiryCheckInterval) {
|
||||
clearInterval(sessionExpiryCheckInterval);
|
||||
sessionExpiryCheckInterval = null;
|
||||
}
|
||||
sessionExpiryWarningShown = false;
|
||||
}
|
||||
|
||||
// Authentication management
|
||||
async function initializeAuth() {
|
||||
// Try to get cached token
|
||||
@@ -1035,6 +1141,7 @@
|
||||
currentUser = cachedUser;
|
||||
document.getElementById('current-user').textContent = cachedUser;
|
||||
await updateUserRole(); // Update role-based UI
|
||||
startSessionExpiryCheck(); // Start monitoring session expiry
|
||||
connectWebSocket(); // Connect WebSocket for real-time updates
|
||||
loadPPRs();
|
||||
return;
|
||||
@@ -1128,6 +1235,7 @@
|
||||
|
||||
hideLogin();
|
||||
await updateUserRole(); // Update role-based UI
|
||||
startSessionExpiryCheck(); // Start monitoring session expiry
|
||||
connectWebSocket(); // Connect WebSocket for real-time updates
|
||||
loadPPRs();
|
||||
} else {
|
||||
@@ -1153,6 +1261,7 @@
|
||||
accessToken = null;
|
||||
currentUser = null;
|
||||
|
||||
stopSessionExpiryCheck(); // Stop monitoring session
|
||||
disconnectWebSocket(); // Disconnect WebSocket
|
||||
|
||||
// Close any open modals
|
||||
@@ -1298,6 +1407,13 @@
|
||||
document.getElementById('arrivals-no-data').style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort arrivals by ETA (ascending)
|
||||
arrivals.sort((a, b) => {
|
||||
if (!a.eta) return 1;
|
||||
if (!b.eta) return -1;
|
||||
return new Date(a.eta) - new Date(b.eta);
|
||||
});
|
||||
tbody.innerHTML = '';
|
||||
document.getElementById('arrivals-table-content').style.display = 'block';
|
||||
for (const ppr of arrivals) {
|
||||
@@ -1346,6 +1462,13 @@
|
||||
document.getElementById('departures-no-data').style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort departures by ETD (ascending), nulls last
|
||||
departures.sort((a, b) => {
|
||||
if (!a.etd) return 1;
|
||||
if (!b.etd) return -1;
|
||||
return new Date(a.etd) - new Date(b.etd);
|
||||
});
|
||||
tbody.innerHTML = '';
|
||||
document.getElementById('departures-table-content').style.display = 'block';
|
||||
for (const ppr of departures) {
|
||||
|
||||
@@ -237,30 +237,29 @@
|
||||
};
|
||||
}
|
||||
|
||||
// Convert UTC time to local time (Europe/London)
|
||||
function convertToLocalTime(utcTimeString) {
|
||||
if (!utcTimeString) return '';
|
||||
// Convert UTC datetime to local time display
|
||||
function convertToLocalTime(utcDateTimeString) {
|
||||
if (!utcDateTimeString) return '';
|
||||
|
||||
// Parse the time string (format: HH:MM or HH:MM:SS)
|
||||
const timeParts = utcTimeString.split(':');
|
||||
if (timeParts.length < 2) return utcTimeString;
|
||||
|
||||
// Create a date object with today's date and the UTC time
|
||||
const now = new Date();
|
||||
const utcDate = new Date(Date.UTC(
|
||||
now.getUTCFullYear(),
|
||||
now.getUTCMonth(),
|
||||
now.getUTCDate(),
|
||||
parseInt(timeParts[0]),
|
||||
parseInt(timeParts[1]),
|
||||
timeParts.length > 2 ? parseInt(timeParts[2]) : 0
|
||||
));
|
||||
|
||||
// Convert to local time
|
||||
const localHours = utcDate.getHours().toString().padStart(2, '0');
|
||||
const localMinutes = utcDate.getMinutes().toString().padStart(2, '0');
|
||||
|
||||
return `${localHours}:${localMinutes}`;
|
||||
try {
|
||||
// Parse the ISO datetime string
|
||||
const date = new Date(utcDateTimeString);
|
||||
|
||||
// Check if valid date
|
||||
if (isNaN(date.getTime())) {
|
||||
console.error('Invalid date:', utcDateTimeString);
|
||||
return utcDateTimeString;
|
||||
}
|
||||
|
||||
// Format as HH:MM in local time
|
||||
const localHours = date.getHours().toString().padStart(2, '0');
|
||||
const localMinutes = date.getMinutes().toString().padStart(2, '0');
|
||||
|
||||
return `${localHours}:${localMinutes}`;
|
||||
} catch (error) {
|
||||
console.error('Error converting time:', error, utcDateTimeString);
|
||||
return utcDateTimeString;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch and display arrivals
|
||||
|
||||
Reference in New Issue
Block a user