Session timeout issue and Extra tables

This commit is contained in:
James Pattinson
2025-12-10 14:02:31 +00:00
parent f4b69aace0
commit bd1200f377
3 changed files with 243 additions and 6 deletions

View File

@@ -33,7 +33,11 @@ async def login_for_access_token(
subject=user.username, expires_delta=access_token_expires subject=user.username, expires_delta=access_token_expires
) )
return {"access_token": access_token, "token_type": "bearer"} return {
"access_token": access_token,
"token_type": "bearer",
"expires_in": settings.access_token_expire_minutes * 60 # seconds
}
@router.post("/test-token", response_model=User) @router.post("/test-token", response_model=User)

View File

@@ -154,6 +154,7 @@ class UserInDB(UserInDBBase):
class Token(BaseModel): class Token(BaseModel):
access_token: str access_token: str
token_type: str token_type: str
expires_in: int # Token expiry in seconds
class TokenData(BaseModel): class TokenData(BaseModel):

View File

@@ -653,6 +653,72 @@
<p>No aircraft currently landed and ready to depart.</p> <p>No aircraft currently landed and ready to depart.</p>
</div> </div>
</div> </div>
<br>
<!-- Departed and Parked Tables -->
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-top: 1rem;">
<!-- Departed Today -->
<div class="ppr-table">
<div class="table-header" style="padding: 0.3rem 0.5rem; font-size: 0.85rem;">
✈️ Departed Today - <span id="departed-count">0</span>
</div>
<div id="departed-loading" class="loading" style="display: none;">
<div class="spinner"></div>
Loading departed aircraft...
</div>
<div id="departed-table-content" style="display: none;">
<table>
<thead>
<tr style="font-size: 0.85rem !important;">
<th style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">Registration</th>
<th style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">Callsign</th>
<th style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">Destination</th>
<th style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">Departed</th>
</tr>
</thead>
<tbody id="departed-table-body">
</tbody>
</table>
</div>
<div id="departed-no-data" class="no-data" style="display: none; padding: 1rem; font-size: 0.9rem;">
<p>No departures today.</p>
</div>
</div>
<!-- Parked Visitors -->
<div class="ppr-table">
<div class="table-header" style="padding: 0.3rem 0.5rem; font-size: 0.85rem;">
🅿️ Parked Visitors - <span id="parked-count">0</span>
</div>
<div id="parked-loading" class="loading" style="display: none;">
<div class="spinner"></div>
Loading parked visitors...
</div>
<div id="parked-table-content" style="display: none;">
<table>
<thead>
<tr style="font-size: 0.85rem !important;">
<th style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">Registration</th>
<th style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">Type</th>
<th style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">From</th>
<th style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">Arrived</th>
<th style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">ETD</th>
</tr>
</thead>
<tbody id="parked-table-body">
</tbody>
</table>
</div>
<div id="parked-no-data" class="no-data" style="display: none; padding: 1rem; font-size: 0.9rem;">
<p>No parked visitors.</p>
</div>
</div>
</div>
</div> </div>
<!-- Login Modal --> <!-- Login Modal -->
@@ -1226,8 +1292,9 @@
const data = await response.json(); const data = await response.json();
if (response.ok && data.access_token) { if (response.ok && data.access_token) {
// Store token and user info with expiry (30 minutes from now) // Store token and user info with expiry from server response
const expiryTime = new Date().getTime() + (30 * 60 * 1000); // 30 minutes const expiresInMs = (data.expires_in || 1800) * 1000; // Use server value or default to 30 min
const expiryTime = new Date().getTime() + expiresInMs;
localStorage.setItem('ppr_access_token', data.access_token); localStorage.setItem('ppr_access_token', data.access_token);
localStorage.setItem('ppr_username', username); localStorage.setItem('ppr_username', username);
@@ -1308,12 +1375,12 @@
return response; return response;
} }
// Load PPR records - now loads both arrivals and departures // Load PPR records - now loads all tables
async function loadPPRs() { async function loadPPRs() {
if (!accessToken) return; if (!accessToken) return;
// Load both arrivals and departures simultaneously // Load all tables simultaneously
await Promise.all([loadArrivals(), loadDepartures()]); await Promise.all([loadArrivals(), loadDepartures(), loadDeparted(), loadParked()]);
} }
// Load arrivals (NEW and CONFIRMED status) // Load arrivals (NEW and CONFIRMED status)
@@ -1394,6 +1461,171 @@
document.getElementById('departures-loading').style.display = 'none'; document.getElementById('departures-loading').style.display = 'none';
} }
// Load departed aircraft (DEPARTED status with departed_dt today)
async function loadDeparted() {
document.getElementById('departed-loading').style.display = 'block';
document.getElementById('departed-table-content').style.display = 'none';
document.getElementById('departed-no-data').style.display = 'none';
try {
const response = await authenticatedFetch('/api/v1/pprs/?limit=1000');
if (!response.ok) {
throw new Error('Failed to fetch departed aircraft');
}
const allPPRs = await response.json();
const today = new Date().toISOString().split('T')[0];
// Filter for aircraft departed today
const departed = allPPRs.filter(ppr => {
if (!ppr.departed_dt || ppr.status !== 'DEPARTED') {
return false;
}
const departedDate = ppr.departed_dt.split('T')[0];
return departedDate === today;
});
displayDeparted(departed);
} catch (error) {
console.error('Error loading departed aircraft:', error);
if (error.message !== 'Session expired. Please log in again.') {
showNotification('Error loading departed aircraft', true);
}
}
document.getElementById('departed-loading').style.display = 'none';
}
function displayDeparted(departed) {
const tbody = document.getElementById('departed-table-body');
document.getElementById('departed-count').textContent = departed.length;
if (departed.length === 0) {
document.getElementById('departed-no-data').style.display = 'block';
return;
}
// Sort by departed time
departed.sort((a, b) => new Date(a.departed_dt) - new Date(b.departed_dt));
tbody.innerHTML = '';
document.getElementById('departed-table-content').style.display = 'block';
for (const ppr of departed) {
const row = document.createElement('tr');
row.onclick = () => openPPRModal(ppr.id);
row.style.cssText = 'font-size: 0.85rem !important; font-style: italic;';
row.innerHTML = `
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${ppr.ac_reg || '-'}</td>
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${ppr.ac_call || '-'}</td>
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${ppr.out_to || '-'}</td>
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${formatTimeOnly(ppr.departed_dt)}</td>
`;
tbody.appendChild(row);
}
}
// Load parked visitors (LANDED status with no ETD today or ETD not today)
async function loadParked() {
document.getElementById('parked-loading').style.display = 'block';
document.getElementById('parked-table-content').style.display = 'none';
document.getElementById('parked-no-data').style.display = 'none';
try {
const response = await authenticatedFetch('/api/v1/pprs/?limit=1000');
if (!response.ok) {
throw new Error('Failed to fetch parked visitors');
}
const allPPRs = await response.json();
const today = new Date().toISOString().split('T')[0];
// Filter for parked visitors: LANDED status and (no ETD or ETD not today)
// Show all parked aircraft regardless of when they arrived
const parked = allPPRs.filter(ppr => {
if (ppr.status !== 'LANDED') {
return false;
}
// No ETD means parked
if (!ppr.etd) {
return true;
}
// ETD exists but is not today
const etdDate = ppr.etd.split('T')[0];
return etdDate !== today;
});
displayParked(parked);
} catch (error) {
console.error('Error loading parked visitors:', error);
if (error.message !== 'Session expired. Please log in again.') {
showNotification('Error loading parked visitors', true);
}
}
document.getElementById('parked-loading').style.display = 'none';
}
function displayParked(parked) {
const tbody = document.getElementById('parked-table-body');
document.getElementById('parked-count').textContent = parked.length;
if (parked.length === 0) {
document.getElementById('parked-no-data').style.display = 'block';
return;
}
// Sort by landed time
parked.sort((a, b) => {
if (!a.landed_dt) return 1;
if (!b.landed_dt) return -1;
return new Date(a.landed_dt) - new Date(b.landed_dt);
});
tbody.innerHTML = '';
document.getElementById('parked-table-content').style.display = 'block';
for (const ppr of parked) {
const row = document.createElement('tr');
row.onclick = () => openPPRModal(ppr.id);
row.style.cssText = 'font-size: 0.85rem !important; font-style: italic;';
// Format arrival: time if today, date if not
const today = new Date().toISOString().split('T')[0];
let arrivedDisplay = '-';
if (ppr.landed_dt) {
const landedDate = ppr.landed_dt.split('T')[0];
if (landedDate === today) {
// Today - show time only
arrivedDisplay = formatTimeOnly(ppr.landed_dt);
} else {
// Not today - show date (DD/MM)
const date = new Date(ppr.landed_dt);
arrivedDisplay = date.toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit' });
}
}
// Format ETD as just the date (DD/MM)
let etdDisplay = '-';
if (ppr.etd) {
const etdDate = new Date(ppr.etd);
etdDisplay = etdDate.toLocaleDateString('en-GB', { day: '2-digit', month: '2-digit' });
}
row.innerHTML = `
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${ppr.ac_reg || '-'}</td>
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${ppr.ac_type || '-'}</td>
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${ppr.in_from || '-'}</td>
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${arrivedDisplay}</td>
<td style="padding: 0.3rem 0.4rem !important; font-size: 0.85rem !important;">${etdDisplay}</td>
`;
tbody.appendChild(row);
}
}
// ICAO code to airport name cache // ICAO code to airport name cache
const airportNameCache = {}; const airportNameCache = {};