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:
55
app/main.py
55
app/main.py
@@ -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)):
|
||||
"""
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
Reference in New Issue
Block a user