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

@@ -11,6 +11,7 @@ import glob
from server.serializers import dict_to_camel_case
client_logs_bp = Blueprint("client_logs", __name__, url_prefix="/api/client-logs")
PRIORITY_SCREENSHOT_TTL_SECONDS = int(os.environ.get("PRIORITY_SCREENSHOT_TTL_SECONDS", "120"))
def _grace_period_seconds():
@@ -90,6 +91,34 @@ def _infer_last_screenshot_ts(client_uuid):
return None
def _load_screenshot_metadata(client_uuid):
screenshots_dir = os.path.join(os.path.dirname(__file__), "..", "screenshots")
metadata_path = os.path.join(screenshots_dir, f"{client_uuid}_meta.json")
if not os.path.exists(metadata_path):
return {}
try:
with open(metadata_path, "r", encoding="utf-8") as metadata_file:
data = json.load(metadata_file)
return data if isinstance(data, dict) else {}
except Exception:
return {}
def _is_priority_screenshot_active(priority_received_at):
if not priority_received_at:
return False
try:
normalized = str(priority_received_at).replace("Z", "+00:00")
parsed = datetime.fromisoformat(normalized)
parsed_utc = _to_utc(parsed)
except Exception:
return False
return (datetime.now(timezone.utc) - parsed_utc) <= timedelta(seconds=PRIORITY_SCREENSHOT_TTL_SECONDS)
@client_logs_bp.route("/test", methods=["GET"])
def test_client_logs():
"""Test endpoint to verify logging infrastructure (no auth required)"""
@@ -326,6 +355,7 @@ def get_monitoring_overview():
"critical_clients": 0,
"error_logs": 0,
"warn_logs": 0,
"active_priority_screenshots": 0,
}
for client, group_name in clients:
@@ -352,6 +382,12 @@ def get_monitoring_overview():
)
screenshot_ts = client.last_screenshot_analyzed or _infer_last_screenshot_ts(client.uuid)
screenshot_meta = _load_screenshot_metadata(client.uuid)
latest_screenshot_type = screenshot_meta.get("latest_screenshot_type") or "periodic"
priority_screenshot_type = screenshot_meta.get("last_priority_screenshot_type")
priority_screenshot_received_at = screenshot_meta.get("last_priority_received_at")
has_active_priority = _is_priority_screenshot_active(priority_screenshot_received_at)
screenshot_url = f"/screenshots/{client.uuid}/priority" if has_active_priority else f"/screenshots/{client.uuid}"
clients_payload.append({
"uuid": client.uuid,
@@ -372,7 +408,11 @@ def get_monitoring_overview():
"screen_health_status": screen_health_status,
"last_screenshot_analyzed": screenshot_ts.isoformat() if screenshot_ts else None,
"last_screenshot_hash": client.last_screenshot_hash,
"screenshot_url": f"/screenshots/{client.uuid}",
"latest_screenshot_type": latest_screenshot_type,
"priority_screenshot_type": priority_screenshot_type,
"priority_screenshot_received_at": priority_screenshot_received_at,
"has_active_priority_screenshot": has_active_priority,
"screenshot_url": screenshot_url,
"log_counts_24h": {
"error": log_counts["ERROR"],
"warn": log_counts["WARN"],
@@ -386,6 +426,8 @@ def get_monitoring_overview():
summary_counts["total_clients"] += 1
summary_counts["error_logs"] += log_counts["ERROR"]
summary_counts["warn_logs"] += log_counts["WARN"]
if has_active_priority:
summary_counts["active_priority_screenshots"] += 1
if is_alive:
summary_counts["online_clients"] += 1
else: