feat: dashboard screenshot upload & retention (last 20 per client)
- Listener: subscribe to dashboard topic, forward screenshots to API - API: store latest + last 20 timestamped screenshots per client, auto-delete older files - Docs: updated README, TECH-CHANGELOG, and copilot-instructions for screenshot upload and retention policy
This commit is contained in:
@@ -273,6 +273,85 @@ def restart_client(uuid):
|
||||
return jsonify({"error": f"Failed to send MQTT message: {str(e)}"}), 500
|
||||
|
||||
|
||||
@clients_bp.route("/<uuid>/screenshot", methods=["POST"])
|
||||
def upload_screenshot(uuid):
|
||||
"""
|
||||
Route to receive and store a screenshot from a client.
|
||||
Expected payload: base64-encoded image data in JSON or binary image data.
|
||||
Screenshots are stored as {uuid}.jpg in the screenshots folder.
|
||||
Keeps last 20 screenshots per client (auto-cleanup).
|
||||
"""
|
||||
import os
|
||||
import base64
|
||||
import glob
|
||||
from datetime import datetime
|
||||
|
||||
session = Session()
|
||||
client = session.query(Client).filter_by(uuid=uuid).first()
|
||||
if not client:
|
||||
session.close()
|
||||
return jsonify({"error": "Client nicht gefunden"}), 404
|
||||
session.close()
|
||||
|
||||
try:
|
||||
# Handle JSON payload with base64-encoded image
|
||||
if request.is_json:
|
||||
data = request.get_json()
|
||||
if "image" not in data:
|
||||
return jsonify({"error": "Missing 'image' field in JSON payload"}), 400
|
||||
|
||||
# Decode base64 image
|
||||
image_data = base64.b64decode(data["image"])
|
||||
else:
|
||||
# Handle raw binary image data
|
||||
image_data = request.get_data()
|
||||
|
||||
if not image_data:
|
||||
return jsonify({"error": "No image data received"}), 400
|
||||
|
||||
# Ensure screenshots directory exists
|
||||
screenshots_dir = os.path.join(os.path.dirname(__file__), "..", "screenshots")
|
||||
os.makedirs(screenshots_dir, exist_ok=True)
|
||||
|
||||
# Store screenshot with timestamp to track latest
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = f"{uuid}_{timestamp}.jpg"
|
||||
filepath = os.path.join(screenshots_dir, filename)
|
||||
|
||||
with open(filepath, "wb") as f:
|
||||
f.write(image_data)
|
||||
|
||||
# Also create/update a symlink or copy to {uuid}.jpg for easy retrieval
|
||||
latest_filepath = os.path.join(screenshots_dir, f"{uuid}.jpg")
|
||||
with open(latest_filepath, "wb") as f:
|
||||
f.write(image_data)
|
||||
|
||||
# Cleanup: keep only last 20 timestamped screenshots per client
|
||||
pattern = os.path.join(screenshots_dir, f"{uuid}_*.jpg")
|
||||
existing_screenshots = sorted(glob.glob(pattern))
|
||||
|
||||
# Keep last 20, delete older ones
|
||||
max_screenshots = 20
|
||||
if len(existing_screenshots) > max_screenshots:
|
||||
for old_file in existing_screenshots[:-max_screenshots]:
|
||||
try:
|
||||
os.remove(old_file)
|
||||
except Exception as cleanup_error:
|
||||
# Log but don't fail the request if cleanup fails
|
||||
import logging
|
||||
logging.warning(f"Failed to cleanup old screenshot {old_file}: {cleanup_error}")
|
||||
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"message": f"Screenshot received for client {uuid}",
|
||||
"filename": filename,
|
||||
"size": len(image_data)
|
||||
}), 200
|
||||
|
||||
except Exception as e:
|
||||
return jsonify({"error": f"Failed to process screenshot: {str(e)}"}), 500
|
||||
|
||||
|
||||
@clients_bp.route("/<uuid>", methods=["DELETE"])
|
||||
@admin_or_higher
|
||||
def delete_client(uuid):
|
||||
|
||||
Reference in New Issue
Block a user