feat: Add delete button for uploaded CSV files in admin panel

- Added delete button (trash icon) next to each uploaded file in the import section
- Implemented DELETE endpoint at /admin/delete-file/{filename} with authentication and validation
- Added JavaScript function to handle file deletion with confirmation dialog
- Includes security checks for directory traversal and file existence
- Logs file deletion actions with username for audit trail
- UI automatically refreshes after successful deletion
This commit is contained in:
HotSwapp
2025-10-08 13:07:04 -05:00
parent fa4e0b9f62
commit dc1c10f44b
2 changed files with 92 additions and 1 deletions

View File

@@ -1803,6 +1803,61 @@ async def admin_sync_data(
})
@app.delete("/admin/delete-file/{filename}")
async def admin_delete_file(
request: Request,
filename: str,
db: Session = Depends(get_db)
):
"""
Delete an uploaded CSV file from the data-import directory.
Requires authentication and validates that the file exists.
"""
# Check authentication
user = get_current_user_from_session(request.session)
if not user:
raise HTTPException(status_code=401, detail="Unauthorized")
# Validate filename to prevent directory traversal
if ".." in filename or "/" in filename or "\\" in filename:
raise HTTPException(status_code=400, detail="Invalid filename")
# Construct file path
import_dir = "data-import"
file_path = os.path.join(import_dir, filename)
# Check if file exists
if not os.path.exists(file_path):
raise HTTPException(status_code=404, detail="File not found")
# Check if it's actually a file (not a directory)
if not os.path.isfile(file_path):
raise HTTPException(status_code=400, detail="Not a file")
try:
# Delete the file
os.remove(file_path)
# Log the deletion
logger.info(
"admin_delete_file",
filename=filename,
username=user.username,
)
return {"success": True, "message": f"File '{filename}' deleted successfully"}
except Exception as e:
logger.error(
"admin_delete_file_error",
filename=filename,
username=user.username,
error=str(e)
)
raise HTTPException(status_code=500, detail=f"Error deleting file: {str(e)}")
@app.get("/admin")
async def admin_panel(request: Request, db: Session = Depends(get_db)):
"""

View File

@@ -475,13 +475,18 @@
<div class="list-group">
{% for file in files %}
<label class="list-group-item d-flex justify-content-between align-items-center">
<div>
<div class="flex-grow-1">
<input class="form-check-input me-2 file-checkbox" type="checkbox"
name="selected_files" value="{{ file.filename }}" id="{{ file.filename }}">
<small class="text-muted">{{ file.filename }}</small>
<br>
<small class="text-muted">{{ file.size }} bytes • {{ file.modified.strftime('%Y-%m-%d %H:%M') }}</small>
</div>
<button type="button" class="btn btn-sm btn-outline-danger delete-file-btn"
data-filename="{{ file.filename }}"
onclick="deleteFile('{{ file.filename }}', event)">
<i class="bi bi-trash"></i>
</button>
</label>
{% endfor %}
</div>
@@ -747,5 +752,36 @@ function confirmSync() {
document.getElementById('syncForm').submit();
}
}
// Delete file function
async function deleteFile(filename, event) {
// Prevent label click from triggering checkbox
event.preventDefault();
event.stopPropagation();
if (!confirm(`Are you sure you want to delete "${filename}"?\n\nThis action cannot be undone.`)) {
return;
}
try {
const response = await fetch(`/admin/delete-file/${encodeURIComponent(filename)}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json'
}
});
if (response.ok) {
// Reload the page to refresh the file list
window.location.reload();
} else {
const error = await response.json();
alert(`Error deleting file: ${error.detail || 'Unknown error'}`);
}
} catch (error) {
console.error('Error deleting file:', error);
alert(`Error deleting file: ${error.message}`);
}
}
</script>
{% endblock %}