diff --git a/backend/app/api/endpoints/aircraft.py b/backend/app/api/endpoints/aircraft.py
index 7cbadfe..6216712 100644
--- a/backend/app/api/endpoints/aircraft.py
+++ b/backend/app/api/endpoints/aircraft.py
@@ -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()
@@ -161,4 +162,66 @@ async def save_user_aircraft(
db.commit()
db.refresh(user_aircraft)
- return {"message": "Aircraft saved successfully", "id": user_aircraft.id}
\ No newline at end of file
+ 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"}
\ No newline at end of file
diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml
index 94d04e8..22d162a 100644
--- a/docker-compose.prod.yml
+++ b/docker-compose.prod.yml
@@ -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
diff --git a/web/admin.html b/web/admin.html
index f75941a..e284f49 100644
--- a/web/admin.html
+++ b/web/admin.html
@@ -30,6 +30,7 @@
@@ -1048,6 +1049,86 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Loading user aircraft...
+
+
+
+
+
+
+ | Registration |
+ Type |
+ Added By |
+ Created |
+ Actions |
+
+
+
+
+
+
+
+
+
No user aircraft found
+
No custom aircraft types have been saved yet.
+
+
+
+
+
+
+
+
@@ -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 = '
| No aircraft match the search criteria |
';
+ } 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 = `
+ ${aircraft.registration} |
+ ${aircraft.type_code} |
+ ${aircraft.created_by} |
+ ${createdDate} |
+
+
+ |
+ `;
+
+ 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';
}
}