@@ -602,50 +696,122 @@
// Initialize the application
document.addEventListener('DOMContentLoaded', function() {
- authenticate();
+ initializeAuth();
+ setupLoginForm();
});
- // Authentication
- function authenticate() {
- const username = prompt('Username:');
- const password = prompt('Password:');
+ // Authentication management
+ function initializeAuth() {
+ // Try to get cached token
+ const cachedToken = localStorage.getItem('ppr_access_token');
+ const cachedUser = localStorage.getItem('ppr_username');
+ const tokenExpiry = localStorage.getItem('ppr_token_expiry');
- if (!username || !password) {
- alert('Authentication required');
- return;
- }
-
- fetch('/api/v1/auth/login', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/x-www-form-urlencoded',
- },
- body: `username=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}`
- })
- .then(response => response.json())
- .then(data => {
- if (data.access_token) {
- accessToken = data.access_token;
- currentUser = username;
- document.getElementById('current-user').textContent = username;
+ if (cachedToken && cachedUser && tokenExpiry) {
+ const now = new Date().getTime();
+ if (now < parseInt(tokenExpiry)) {
+ // Token is still valid
+ accessToken = cachedToken;
+ currentUser = cachedUser;
+ document.getElementById('current-user').textContent = cachedUser;
loadPPRs();
setDefaultDates();
- } else {
- alert('Authentication failed');
- authenticate();
+ return;
}
- })
- .catch(error => {
- console.error('Auth error:', error);
- alert('Authentication failed');
- authenticate();
+ }
+
+ // No valid cached token, show login
+ showLogin();
+ }
+
+ function setupLoginForm() {
+ document.getElementById('login-form').addEventListener('submit', async function(e) {
+ e.preventDefault();
+ await handleLogin();
});
}
+ function showLogin() {
+ document.getElementById('loginModal').style.display = 'block';
+ document.getElementById('login-username').focus();
+ }
+
+ function hideLogin() {
+ document.getElementById('loginModal').style.display = 'none';
+ document.getElementById('login-error').style.display = 'none';
+ document.getElementById('login-form').reset();
+ }
+
+ async function handleLogin() {
+ const username = document.getElementById('login-username').value;
+ const password = document.getElementById('login-password').value;
+ const loginBtn = document.getElementById('login-btn');
+ const errorDiv = document.getElementById('login-error');
+
+ // Show loading state
+ loginBtn.disabled = true;
+ loginBtn.textContent = '🔄 Logging in...';
+ errorDiv.style.display = 'none';
+
+ try {
+ const response = await fetch('/api/v1/auth/login', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded',
+ },
+ body: `username=${encodeURIComponent(username)}&password=${encodeURIComponent(password)}`
+ });
+
+ const data = await response.json();
+
+ if (response.ok && data.access_token) {
+ // Store token and user info with expiry (30 minutes from now)
+ const expiryTime = new Date().getTime() + (30 * 60 * 1000); // 30 minutes
+
+ localStorage.setItem('ppr_access_token', data.access_token);
+ localStorage.setItem('ppr_username', username);
+ localStorage.setItem('ppr_token_expiry', expiryTime.toString());
+
+ accessToken = data.access_token;
+ currentUser = username;
+ document.getElementById('current-user').textContent = username;
+
+ hideLogin();
+ loadPPRs();
+ setDefaultDates();
+ } else {
+ throw new Error(data.detail || 'Authentication failed');
+ }
+ } catch (error) {
+ console.error('Login error:', error);
+ errorDiv.textContent = error.message || 'Login failed. Please check your credentials.';
+ errorDiv.style.display = 'block';
+ } finally {
+ // Reset button state
+ loginBtn.disabled = false;
+ loginBtn.textContent = '🔐 Login';
+ }
+ }
+
function logout() {
+ // Clear stored token and user info
+ localStorage.removeItem('ppr_access_token');
+ localStorage.removeItem('ppr_username');
+ localStorage.removeItem('ppr_token_expiry');
+
accessToken = null;
currentUser = null;
- location.reload();
+
+ // Close any open modals
+ closePPRModal();
+
+ // Show login again
+ showLogin();
+ }
+
+ // Legacy authenticate function - now redirects to new login
+ function authenticate() {
+ showLogin();
}
function setDefaultDates() {
@@ -653,6 +819,33 @@
document.getElementById('viewDate').value = today.toISOString().split('T')[0];
}
+ // Enhanced fetch wrapper with token expiry handling
+ async function authenticatedFetch(url, options = {}) {
+ if (!accessToken) {
+ showLogin();
+ throw new Error('No access token available');
+ }
+
+ // Add authorization header
+ const headers = {
+ ...options.headers,
+ 'Authorization': `Bearer ${accessToken}`
+ };
+
+ const response = await fetch(url, {
+ ...options,
+ headers
+ });
+
+ // Handle 401 - token expired
+ if (response.status === 401) {
+ logout();
+ throw new Error('Session expired. Please log in again.');
+ }
+
+ return response;
+ }
+
// Load PPR records - now loads both arrivals and departures
async function loadPPRs() {
if (!accessToken) return;
@@ -676,11 +869,7 @@
url += `&date_from=${viewDate}&date_to=${viewDate}`;
}
- const response = await fetch(url, {
- headers: {
- 'Authorization': `Bearer ${accessToken}`
- }
- });
+ const response = await authenticatedFetch(url);
if (!response.ok) {
throw new Error('Failed to fetch arrivals');
@@ -703,7 +892,9 @@
displayArrivals(arrivals);
} catch (error) {
console.error('Error loading arrivals:', error);
- alert('Error loading arrivals');
+ if (error.message !== 'Session expired. Please log in again.') {
+ alert('Error loading arrivals');
+ }
}
document.getElementById('arrivals-loading').style.display = 'none';
@@ -724,11 +915,7 @@
url += `&date_from=${viewDate}&date_to=${viewDate}`;
}
- const response = await fetch(url, {
- headers: {
- 'Authorization': `Bearer ${accessToken}`
- }
- });
+ const response = await authenticatedFetch(url);
if (!response.ok) {
throw new Error('Failed to fetch departures');
@@ -749,7 +936,9 @@
displayDepartures(departures);
} catch (error) {
console.error('Error loading departures:', error);
- alert('Error loading departures');
+ if (error.message !== 'Session expired. Please log in again.') {
+ alert('Error loading departures');
+ }
}
document.getElementById('departures-loading').style.display = 'none';
@@ -773,10 +962,16 @@
const row = document.createElement('tr');
row.onclick = () => openPPRModal(ppr.id);
+ // Create notes indicator if notes exist
+ const notesIndicator = ppr.notes && ppr.notes.trim() ?
+ `
+ 📝
+ ${ppr.notes}
+ ` : '';
+
row.innerHTML = `
-
${ppr.ac_reg} |
+ ${ppr.ac_reg}${notesIndicator} |
${ppr.ac_type} |
- ${ppr.captain} |
${ppr.in_from} |
${formatTimeOnly(ppr.eta)} |
${ppr.pob_in} |
@@ -811,10 +1006,16 @@
const row = document.createElement('tr');
row.onclick = () => openPPRModal(ppr.id);
+ // Create notes indicator if notes exist
+ const notesIndicator = ppr.notes && ppr.notes.trim() ?
+ `
+ 📝
+ ${ppr.notes}
+ ` : '';
+
row.innerHTML = `
- ${ppr.ac_reg} |
+ ${ppr.ac_reg}${notesIndicator} |
${ppr.ac_type} |
- ${ppr.captain} |
${ppr.out_to || '-'} |
${ppr.etd ? formatTimeOnly(ppr.etd) : '-'} |
${ppr.pob_out || ppr.pob_in} |
@@ -875,11 +1076,7 @@
document.querySelector('.quick-actions').style.display = 'flex';
try {
- const response = await fetch(`/api/v1/pprs/${pprId}`, {
- headers: {
- 'Authorization': `Bearer ${accessToken}`
- }
- });
+ const response = await authenticatedFetch(`/api/v1/pprs/${pprId}`);
if (!response.ok) {
throw new Error('Failed to fetch PPR details');