feat(monitoring): add priority screenshot pipeline with screenshot_type + docs cleanup

Implement end-to-end support for typed screenshots and priority rendering in monitoring.

Added
- Accept and forward screenshot_type from MQTT screenshot/dashboard payloads
  (periodic, event_start, event_stop)
- Extend screenshot upload handling to persist typed screenshots and metadata
- Add dedicated priority screenshot serving endpoint with fallback behavior
- Extend monitoring overview with priority screenshot fields and summary count
- Add configurable PRIORITY_SCREENSHOT_TTL_SECONDS window for active priority state

Fixed
- Ensure screenshot cache-busting updates reliably via screenshot hash updates
- Preserve normal periodic screenshot flow while introducing event_start/event_stop priority path

Improved
- Monitoring dashboard now displays screenshot type badges
- Adaptive polling: faster refresh while priority screenshots are active
- Priority screenshot presentation is surfaced immediately to operators

Docs
- Update README and copilot-instructions to match new screenshot_type behavior,
  priority endpoint, TTL config, monitoring fields, and retention model
- Remove redundant/duplicate documentation blocks and improve troubleshooting section clarity
This commit is contained in:
2026-03-29 13:13:13 +00:00
parent 9c330f984f
commit 24cdf07279
10 changed files with 258 additions and 57 deletions

View File

@@ -158,14 +158,25 @@ def apply_monitoring_update(client_obj, *, event_id=None, process_name=None, pro
def _extract_image_and_timestamp(data):
image_value = None
timestamp_value = None
screenshot_type = None
if not isinstance(data, dict):
return None, None
return None, None, None
screenshot_obj = data.get("screenshot") if isinstance(data.get("screenshot"), dict) else None
metadata_obj = data.get("metadata") if isinstance(data.get("metadata"), dict) else None
screenshot_meta_obj = screenshot_obj.get("metadata") if screenshot_obj and isinstance(screenshot_obj.get("metadata"), dict) else None
for container in (data, screenshot_obj, metadata_obj, screenshot_meta_obj):
if not isinstance(container, dict):
continue
raw_type = container.get("screenshot_type") or container.get("screenshotType")
if raw_type is not None:
normalized_type = str(raw_type).strip().lower()
if normalized_type in ("periodic", "event_start", "event_stop"):
screenshot_type = normalized_type
break
for key in ("image", "data"):
if isinstance(data.get(key), str) and data.get(key):
image_value = data.get(key)
@@ -183,9 +194,9 @@ def _extract_image_and_timestamp(data):
value = container.get(key)
if value is not None:
timestamp_value = value
return image_value, timestamp_value
return image_value, timestamp_value, screenshot_type
return image_value, timestamp_value
return image_value, timestamp_value, screenshot_type
def handle_screenshot(uuid, payload):
@@ -197,12 +208,14 @@ def handle_screenshot(uuid, payload):
# Try to parse as JSON first
try:
data = json.loads(payload.decode())
image_b64, timestamp_value = _extract_image_and_timestamp(data)
image_b64, timestamp_value, screenshot_type = _extract_image_and_timestamp(data)
if image_b64:
# Payload is JSON with base64 image
api_payload = {"image": image_b64}
if timestamp_value is not None:
api_payload["timestamp"] = timestamp_value
if screenshot_type:
api_payload["screenshot_type"] = screenshot_type
headers = {"Content-Type": "application/json"}
logging.debug(f"Forwarding base64 screenshot from {uuid} to API")
else:
@@ -261,12 +274,14 @@ def on_message(client, userdata, msg):
try:
payload_text = msg.payload.decode()
data = json.loads(payload_text)
image_b64, ts_value = _extract_image_and_timestamp(data)
image_b64, ts_value, screenshot_type = _extract_image_and_timestamp(data)
if image_b64:
logging.debug(f"Dashboard enthält Screenshot für {uuid}; Weiterleitung an API")
dashboard_payload = {"image": image_b64}
if ts_value is not None:
dashboard_payload["timestamp"] = ts_value
if screenshot_type:
dashboard_payload["screenshot_type"] = screenshot_type
api_payload = json.dumps(dashboard_payload).encode("utf-8")
handle_screenshot(uuid, api_payload)
# Update last_alive if status present