Improved alterting and TZ handling

This commit is contained in:
James Pattinson
2025-10-23 15:56:54 +00:00
parent c1accd82c5
commit ad370f41ed
2 changed files with 103 additions and 51 deletions

View File

@@ -116,13 +116,16 @@ To migrate data from the old system:
2. Transform to new schema format
3. Import into the new system
## Security
## Time Zone Handling
- JWT token authentication
- Password hashing with bcrypt
- Input validation with Pydantic
- SQL injection protection via SQLAlchemy ORM
- CORS configuration for frontend integration
The system uses UTC (Coordinated Universal Time) as the standard for all time storage and display in aviation contexts:
- **Database Storage**: All times are stored in UTC
- **Admin Console Display**: Times are displayed in UTC (with Z suffix)
- **Data Entry**: Times are entered as local time in the New PPR entry form, then automatically converted to UTC for storage
- **API**: All datetime fields in API responses are UTC ISO strings
This ensures consistency across different time zones and complies with aviation standards where UTC is the international standard.
## Performance

View File

@@ -459,29 +459,29 @@
color: #007bff;
}
@media (max-width: 768px) {
.container {
padding: 1rem;
.notification {
position: fixed;
top: 20px;
right: 20px;
background-color: #27ae60;
color: white;
padding: 12px 20px;
border-radius: 5px;
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
z-index: 10000;
opacity: 0;
transform: translateY(-20px);
transition: all 0.3s ease;
font-weight: 500;
}
.top-menu {
flex-direction: column;
gap: 1rem;
padding: 1rem;
.notification.show {
opacity: 1;
transform: translateY(0);
}
.menu-left, .menu-right {
justify-content: center;
}
.form-grid {
grid-template-columns: 1fr;
}
.modal-content {
width: 95%;
margin: 2% auto;
}
.notification.error {
background-color: #e74c3c;
}
</style>
</head>
@@ -659,7 +659,7 @@
<input type="text" id="in_from" name="in_from" required placeholder="ICAO Code">
</div>
<div class="form-group">
<label for="eta">ETA *</label>
<label for="eta">ETA (Local Time) *</label>
<input type="datetime-local" id="eta" name="eta" required>
</div>
<div class="form-group">
@@ -680,7 +680,7 @@
<input type="text" id="out_to" name="out_to" placeholder="ICAO Code">
</div>
<div class="form-group">
<label for="etd">ETD</label>
<label for="etd">ETD (Local Time)</label>
<input type="datetime-local" id="etd" name="etd">
</div>
<div class="form-group">
@@ -724,12 +724,32 @@
</div>
</div>
<!-- Success Notification -->
<div id="notification" class="notification"></div>
<script>
let currentUser = null;
let accessToken = null;
let currentPPRId = null;
let isNewPPR = false;
// Notification system
function showNotification(message, isError = false) {
const notification = document.getElementById('notification');
notification.textContent = message;
notification.className = 'notification' + (isError ? ' error' : '');
// Show notification
setTimeout(() => {
notification.classList.add('show');
}, 10);
// Hide after 3 seconds
setTimeout(() => {
notification.classList.remove('show');
}, 3000);
}
// Initialize the application
document.addEventListener('DOMContentLoaded', function() {
initializeAuth();
@@ -911,7 +931,7 @@
} catch (error) {
console.error('Error loading arrivals:', error);
if (error.message !== 'Session expired. Please log in again.') {
alert('Error loading arrivals');
showNotification('Error loading arrivals', true);
}
}
@@ -944,7 +964,7 @@
} catch (error) {
console.error('Error loading departures:', error);
if (error.message !== 'Session expired. Please log in again.') {
alert('Error loading departures');
showNotification('Error loading departures', true);
}
}
@@ -1041,20 +1061,18 @@
function formatTimeOnly(dateStr) {
if (!dateStr) return '-';
const date = new Date(dateStr);
return date.toLocaleTimeString('en-GB', {
hour: '2-digit',
minute: '2-digit'
});
// Ensure the datetime string is treated as UTC
const utcDateStr = dateStr.includes('Z') ? dateStr : dateStr + 'Z';
const date = new Date(utcDateStr);
return date.toISOString().slice(11, 16) + 'Z';
}
function formatDateTime(dateStr) {
if (!dateStr) return '-';
const date = new Date(dateStr);
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString('en-GB', {
hour: '2-digit',
minute: '2-digit'
});
// Ensure the datetime string is treated as UTC
const utcDateStr = dateStr.includes('Z') ? dateStr : dateStr + 'Z';
const date = new Date(utcDateStr);
return date.toISOString().slice(0, 10) + ' ' + date.toISOString().slice(11, 16) + 'Z';
}
// Modal functions
@@ -1070,6 +1088,24 @@
document.getElementById('ppr-form').reset();
document.getElementById('ppr-id').value = '';
// Set default ETA and ETD
const now = new Date();
const eta = new Date(now.getTime() + 60 * 60 * 1000); // +1 hour
const etd = new Date(now.getTime() + 2 * 60 * 60 * 1000); // +2 hours
// Format as local datetime-local value
function formatLocalDateTime(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day}T${hours}:${minutes}`;
}
document.getElementById('eta').value = formatLocalDateTime(eta);
document.getElementById('etd').value = etd ? formatLocalDateTime(etd) : '';
// Clear aircraft lookup results
clearAircraftLookup();
@@ -1104,7 +1140,7 @@
document.getElementById('pprModal').style.display = 'block';
} catch (error) {
console.error('Error loading PPR details:', error);
alert('Error loading PPR details');
showNotification('Error loading PPR details', true);
}
}
@@ -1114,7 +1150,16 @@
if (field) {
if (key === 'eta' || key === 'etd') {
if (ppr[key]) {
field.value = new Date(ppr[key]).toISOString().slice(0, 16);
// ppr[key] is UTC datetime string from API (naive, assume UTC)
const utcDateStr = ppr[key].includes('Z') ? ppr[key] : ppr[key] + 'Z';
const date = new Date(utcDateStr); // Now correctly parsed as UTC
// Format as local time for datetime-local input
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
field.value = `${year}-${month}-${day}T${hours}:${minutes}`;
}
} else {
field.value = ppr[key] || '';
@@ -1182,6 +1227,9 @@
if (key !== 'id' && value.trim() !== '') {
if (key === 'pob_in' || key === 'pob_out') {
pprData[key] = parseInt(value);
} else if (key === 'eta' || key === 'etd') {
// Convert local datetime-local to UTC ISO string
pprData[key] = new Date(value).toISOString();
} else {
pprData[key] = value;
}
@@ -1214,12 +1262,13 @@
throw new Error('Failed to save PPR');
}
const wasNewPPR = isNewPPR;
closePPRModal();
loadPPRs(); // Refresh both tables
alert(isNewPPR ? 'PPR created successfully!' : 'PPR updated successfully!');
showNotification(wasNewPPR ? 'PPR created successfully!' : 'PPR updated successfully!');
} catch (error) {
console.error('Error saving PPR:', error);
alert('Error saving PPR');
showNotification('Error saving PPR', true);
}
});
@@ -1243,10 +1292,10 @@
await loadJournal(currentPPRId); // Refresh journal
loadPPRs(); // Refresh both tables
alert(`Status updated to ${status}`);
showNotification(`Status updated to ${status}`);
} catch (error) {
console.error('Error updating status:', error);
alert('Error updating status');
showNotification('Error updating status', true);
}
}
@@ -1271,10 +1320,10 @@
closePPRModal();
loadPPRs(); // Refresh both tables
alert('PPR deleted successfully!');
showNotification('PPR deleted successfully!');
} catch (error) {
console.error('Error deleting PPR:', error);
alert('Error deleting PPR');
showNotification('Error deleting PPR', true);
}
}