List and edit user aircraft
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
from typing import List, Optional
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import func
|
||||
from app.api.deps import get_db, get_current_active_user
|
||||
from app.models.ppr import Aircraft, UserAircraft
|
||||
from app.schemas.ppr import Aircraft as AircraftSchema, UserAircraftCreate
|
||||
from app.schemas.ppr import Aircraft as AircraftSchema, UserAircraftCreate, UserAircraft as UserAircraftSchema
|
||||
from app.models.ppr import User
|
||||
|
||||
router = APIRouter()
|
||||
@@ -162,3 +163,65 @@ async def save_user_aircraft(
|
||||
db.refresh(user_aircraft)
|
||||
|
||||
return {"message": "Aircraft saved successfully", "id": user_aircraft.id}
|
||||
|
||||
|
||||
@router.get("/user-aircraft", response_model=List[UserAircraftSchema])
|
||||
async def get_user_aircraft(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""
|
||||
Get all user-defined aircraft types.
|
||||
"""
|
||||
user_aircraft = db.query(UserAircraft).order_by(UserAircraft.created_at.desc()).all()
|
||||
return user_aircraft
|
||||
|
||||
|
||||
@router.put("/user-aircraft/{registration}", response_model=dict)
|
||||
async def update_user_aircraft(
|
||||
registration: str,
|
||||
aircraft: UserAircraftCreate,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""
|
||||
Update a user-defined aircraft type.
|
||||
"""
|
||||
# Find the existing user aircraft
|
||||
existing = db.query(UserAircraft).filter(
|
||||
UserAircraft.registration == registration.upper()
|
||||
).first()
|
||||
|
||||
if not existing:
|
||||
raise HTTPException(status_code=404, detail="User aircraft not found")
|
||||
|
||||
# Update the type
|
||||
existing.type_code = aircraft.type_code.upper()
|
||||
existing.updated_at = func.current_timestamp()
|
||||
|
||||
db.commit()
|
||||
|
||||
return {"message": "Aircraft updated successfully"}
|
||||
|
||||
|
||||
@router.delete("/user-aircraft/{registration}", response_model=dict)
|
||||
async def delete_user_aircraft(
|
||||
registration: str,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_active_user)
|
||||
):
|
||||
"""
|
||||
Delete a user-defined aircraft type.
|
||||
"""
|
||||
# Find the existing user aircraft
|
||||
existing = db.query(UserAircraft).filter(
|
||||
UserAircraft.registration == registration.upper()
|
||||
).first()
|
||||
|
||||
if not existing:
|
||||
raise HTTPException(status_code=404, detail="User aircraft not found")
|
||||
|
||||
db.delete(existing)
|
||||
db.commit()
|
||||
|
||||
return {"message": "Aircraft deleted successfully"}
|
||||
@@ -50,7 +50,7 @@ services:
|
||||
cpus: '1'
|
||||
memory: 1G
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
|
||||
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
+272
-4
@@ -30,6 +30,7 @@
|
||||
</button>
|
||||
<div class="dropdown-menu" id="adminDropdownMenu">
|
||||
<a href="#" onclick="window.location.href = '/reports'">📊 Reports</a>
|
||||
<a href="#" onclick="openUserAircraftModal(); closeAdminDropdown()" id="user-aircraft-dropdown">✈️ User Aircraft</a>
|
||||
<a href="#" onclick="openUserManagementModal(); closeAdminDropdown()" id="user-management-dropdown" style="display: none;">👥 User Management</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1048,6 +1049,86 @@
|
||||
<!-- Success Notification -->
|
||||
<div id="notification" class="notification"></div>
|
||||
|
||||
<!-- User Aircraft Management Modal -->
|
||||
<div id="userAircraftModal" class="modal">
|
||||
<div class="modal-content" style="max-width: 1000px;">
|
||||
<div class="modal-header">
|
||||
<h2>User Aircraft Management</h2>
|
||||
<button class="close" onclick="closeModal('userAircraftModal')">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="quick-actions" style="margin-bottom: 1rem;">
|
||||
<div class="form-group" style="display: inline-block; margin-right: 1rem;">
|
||||
<label for="user-aircraft-search" style="display: inline; margin-right: 0.5rem;">Search:</label>
|
||||
<input type="text" id="user-aircraft-search" placeholder="Filter by registration or type..." style="width: 250px;">
|
||||
</div>
|
||||
<button class="btn btn-info" onclick="loadUserAircraft()">
|
||||
🔄 Refresh
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="user-aircraft-loading" class="loading">
|
||||
<div class="spinner"></div>
|
||||
Loading user aircraft...
|
||||
</div>
|
||||
|
||||
<div id="user-aircraft-table-content" style="display: none;">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Registration</th>
|
||||
<th>Type</th>
|
||||
<th>Added By</th>
|
||||
<th>Created</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="user-aircraft-table-body">
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div id="user-aircraft-no-data" class="no-data" style="display: none;">
|
||||
<h3>No user aircraft found</h3>
|
||||
<p>No custom aircraft types have been saved yet.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- User Aircraft Edit Modal -->
|
||||
<div id="userAircraftEditModal" class="modal">
|
||||
<div class="modal-content" style="max-width: 500px;">
|
||||
<div class="modal-header">
|
||||
<h2 id="user-aircraft-edit-title">Edit User Aircraft</h2>
|
||||
<button class="close" onclick="closeModal('userAircraftEditModal')">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="user-aircraft-edit-form">
|
||||
<div class="form-group full-width">
|
||||
<label for="edit-aircraft-registration">Registration *</label>
|
||||
<input type="text" id="edit-aircraft-registration" name="registration" required readonly style="background-color: #f5f5f5;">
|
||||
</div>
|
||||
<div class="form-group full-width">
|
||||
<label for="edit-aircraft-type">Aircraft Type *</label>
|
||||
<input type="text" id="edit-aircraft-type" name="type_code" required>
|
||||
</div>
|
||||
<div class="form-actions">
|
||||
<button type="button" class="btn btn-info" onclick="closeModal('userAircraftEditModal')">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" class="btn btn-warning">
|
||||
💾 Save Changes
|
||||
</button>
|
||||
<button type="button" class="btn btn-danger" onclick="deleteUserAircraft()">
|
||||
🗑️ Delete
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Timestamp Modal for Landing/Departure -->
|
||||
<div id="timestampModal" class="modal">
|
||||
<div class="modal-content" style="max-width: 400px;">
|
||||
@@ -3468,6 +3549,182 @@
|
||||
}
|
||||
});
|
||||
|
||||
// ==================== USER AIRCRAFT MANAGEMENT ====================
|
||||
|
||||
async function openUserAircraftModal() {
|
||||
if (!accessToken) return;
|
||||
|
||||
document.getElementById('userAircraftModal').style.display = 'block';
|
||||
await loadUserAircraft();
|
||||
|
||||
// Set up search functionality
|
||||
const searchInput = document.getElementById('user-aircraft-search');
|
||||
searchInput.addEventListener('input', filterUserAircraft);
|
||||
}
|
||||
|
||||
async function loadUserAircraft() {
|
||||
if (!accessToken) return;
|
||||
|
||||
document.getElementById('user-aircraft-loading').style.display = 'block';
|
||||
document.getElementById('user-aircraft-table-content').style.display = 'none';
|
||||
document.getElementById('user-aircraft-no-data').style.display = 'none';
|
||||
|
||||
try {
|
||||
const response = await authenticatedFetch('/api/v1/aircraft/user-aircraft');
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to fetch user aircraft');
|
||||
}
|
||||
|
||||
const userAircraft = await response.json();
|
||||
displayUserAircraft(userAircraft);
|
||||
} catch (error) {
|
||||
console.error('Error loading user aircraft:', error);
|
||||
if (error.message !== 'Session expired. Please log in again.') {
|
||||
showNotification('Error loading user aircraft', true);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('user-aircraft-loading').style.display = 'none';
|
||||
}
|
||||
|
||||
function displayUserAircraft(userAircraft) {
|
||||
const tbody = document.getElementById('user-aircraft-table-body');
|
||||
const searchInput = document.getElementById('user-aircraft-search');
|
||||
|
||||
// Store original data for filtering
|
||||
tbody._originalData = userAircraft;
|
||||
|
||||
if (userAircraft.length === 0) {
|
||||
document.getElementById('user-aircraft-no-data').style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply current filter if any
|
||||
const filterText = searchInput.value.trim().toLowerCase();
|
||||
const filteredAircraft = filterText ?
|
||||
userAircraft.filter(ua =>
|
||||
ua.registration.toLowerCase().includes(filterText) ||
|
||||
ua.type_code.toLowerCase().includes(filterText) ||
|
||||
ua.created_by.toLowerCase().includes(filterText)
|
||||
) : userAircraft;
|
||||
|
||||
if (filteredAircraft.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="5" style="text-align: center; padding: 2rem;">No aircraft match the search criteria</td></tr>';
|
||||
} else {
|
||||
tbody.innerHTML = '';
|
||||
filteredAircraft.forEach(aircraft => {
|
||||
const row = document.createElement('tr');
|
||||
|
||||
// Format created date
|
||||
const createdDate = aircraft.created_at ? formatDateTime(aircraft.created_at) : '-';
|
||||
|
||||
row.innerHTML = `
|
||||
<td><strong>${aircraft.registration}</strong></td>
|
||||
<td>${aircraft.type_code}</td>
|
||||
<td>${aircraft.created_by}</td>
|
||||
<td>${createdDate}</td>
|
||||
<td>
|
||||
<button class="btn btn-warning btn-icon" onclick="event.stopPropagation(); openUserAircraftEditModal('${aircraft.registration}', '${aircraft.type_code}')" title="Edit Aircraft">
|
||||
✏️
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
document.getElementById('user-aircraft-table-content').style.display = 'block';
|
||||
}
|
||||
|
||||
function filterUserAircraft() {
|
||||
const tbody = document.getElementById('user-aircraft-table-body');
|
||||
if (tbody._originalData) {
|
||||
displayUserAircraft(tbody._originalData);
|
||||
}
|
||||
}
|
||||
|
||||
function openUserAircraftEditModal(registration, typeCode) {
|
||||
document.getElementById('edit-aircraft-registration').value = registration;
|
||||
document.getElementById('edit-aircraft-type').value = typeCode;
|
||||
document.getElementById('user-aircraft-edit-title').textContent = `Edit ${registration}`;
|
||||
document.getElementById('userAircraftEditModal').style.display = 'block';
|
||||
|
||||
// Auto-focus on type field
|
||||
setTimeout(() => {
|
||||
document.getElementById('edit-aircraft-type').focus();
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// User aircraft edit form submission
|
||||
document.getElementById('user-aircraft-edit-form').addEventListener('submit', async function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
if (!accessToken) return;
|
||||
|
||||
const registration = document.getElementById('edit-aircraft-registration').value.trim();
|
||||
const typeCode = document.getElementById('edit-aircraft-type').value.trim();
|
||||
|
||||
if (!registration || !typeCode) {
|
||||
showNotification('Registration and type are required', true);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await authenticatedFetch(`/api/v1/aircraft/user-aircraft/${registration}`, {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
registration: registration,
|
||||
type_code: typeCode
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(errorData.detail || 'Failed to update aircraft');
|
||||
}
|
||||
|
||||
closeModal('userAircraftEditModal');
|
||||
await loadUserAircraft(); // Refresh list
|
||||
showNotification('Aircraft updated successfully!');
|
||||
} catch (error) {
|
||||
console.error('Error updating aircraft:', error);
|
||||
showNotification(`Error updating aircraft: ${error.message}`, true);
|
||||
}
|
||||
});
|
||||
|
||||
async function deleteUserAircraft() {
|
||||
if (!accessToken) return;
|
||||
|
||||
const registration = document.getElementById('edit-aircraft-registration').value.trim();
|
||||
|
||||
if (!confirm(`Are you sure you want to delete the aircraft entry for ${registration}? This action cannot be undone.`)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await authenticatedFetch(`/api/v1/aircraft/user-aircraft/${registration}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(errorData.detail || 'Failed to delete aircraft');
|
||||
}
|
||||
|
||||
closeModal('userAircraftEditModal');
|
||||
await loadUserAircraft(); // Refresh list
|
||||
showNotification('Aircraft deleted successfully!');
|
||||
} catch (error) {
|
||||
console.error('Error deleting aircraft:', error);
|
||||
showNotification(`Error deleting aircraft: ${error.message}`, true);
|
||||
}
|
||||
}
|
||||
|
||||
// Update user role detection and UI visibility
|
||||
async function updateUserRole() {
|
||||
if (!accessToken) {
|
||||
@@ -3485,16 +3742,27 @@
|
||||
|
||||
// Show user management in dropdown only for administrators
|
||||
const userManagementDropdown = document.getElementById('user-management-dropdown');
|
||||
// Show user aircraft for operators and administrators
|
||||
const userAircraftDropdown = document.getElementById('user-aircraft-dropdown');
|
||||
|
||||
if (currentUserRole && currentUserRole.toUpperCase() === 'ADMINISTRATOR') {
|
||||
userManagementDropdown.style.display = 'block';
|
||||
if (userManagementDropdown) userManagementDropdown.style.display = 'block';
|
||||
if (userAircraftDropdown) userAircraftDropdown.style.display = 'block';
|
||||
} else if (currentUserRole && currentUserRole.toUpperCase() === 'OPERATOR') {
|
||||
if (userManagementDropdown) userManagementDropdown.style.display = 'none';
|
||||
if (userAircraftDropdown) userAircraftDropdown.style.display = 'block';
|
||||
} else {
|
||||
userManagementDropdown.style.display = 'none';
|
||||
if (userManagementDropdown) userManagementDropdown.style.display = 'none';
|
||||
if (userAircraftDropdown) userAircraftDropdown.style.display = 'none';
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating user role:', error);
|
||||
// Hide user management by default on error
|
||||
document.getElementById('user-management-dropdown').style.display = 'none';
|
||||
// Hide admin features by default on error
|
||||
const userManagementDropdown = document.getElementById('user-management-dropdown');
|
||||
const userAircraftDropdown = document.getElementById('user-aircraft-dropdown');
|
||||
if (userManagementDropdown) userManagementDropdown.style.display = 'none';
|
||||
if (userAircraftDropdown) userAircraftDropdown.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user