Session timeout issue and Extra tables
This commit is contained in:
@@ -33,7 +33,11 @@ async def login_for_access_token(
|
||||
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)
|
||||
|
||||
@@ -154,6 +154,7 @@ class UserInDB(UserInDBBase):
|
||||
class Token(BaseModel):
|
||||
access_token: str
|
||||
token_type: str
|
||||
expires_in: int # Token expiry in seconds
|
||||
|
||||
|
||||
class TokenData(BaseModel):
|
||||
|
||||
242
web/admin.html
242
web/admin.html
@@ -653,6 +653,72 @@
|
||||
<p>No aircraft currently landed and ready to depart.</p>
|
||||
</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>
|
||||
|
||||
<!-- Login Modal -->
|
||||
@@ -1226,8 +1292,9 @@
|
||||
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
|
||||
// Store token and user info with expiry from server response
|
||||
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_username', username);
|
||||
@@ -1308,12 +1375,12 @@
|
||||
return response;
|
||||
}
|
||||
|
||||
// Load PPR records - now loads both arrivals and departures
|
||||
// Load PPR records - now loads all tables
|
||||
async function loadPPRs() {
|
||||
if (!accessToken) return;
|
||||
|
||||
// Load both arrivals and departures simultaneously
|
||||
await Promise.all([loadArrivals(), loadDepartures()]);
|
||||
// Load all tables simultaneously
|
||||
await Promise.all([loadArrivals(), loadDepartures(), loadDeparted(), loadParked()]);
|
||||
}
|
||||
|
||||
// Load arrivals (NEW and CONFIRMED status)
|
||||
@@ -1394,6 +1461,171 @@
|
||||
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
|
||||
const airportNameCache = {};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user