feat: heartbeat detection and sensor metrics
- Heartbeat service: check_sensor_status (online/stale/offline) - GET /sensors/heartbeat: status summary for all sensors - POST /sensors/heartbeat: refresh heartbeat status - GET /sensors/<hostname>/metrics: activity counts, recent events - CLI command: flask check-heartbeats - Added 7 new tests (34 total)
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
"""Sensor endpoints."""
|
||||
import json
|
||||
import socket
|
||||
from datetime import datetime, timedelta, UTC
|
||||
from flask import request, current_app
|
||||
from . import bp
|
||||
from ..models import Sensor
|
||||
from ..models import Sensor, Event, Sighting, Alert
|
||||
from ..extensions import db
|
||||
from ..services.heartbeat import get_heartbeat_summary, update_all_heartbeats
|
||||
|
||||
|
||||
@bp.route('/sensors')
|
||||
@@ -213,3 +215,73 @@ def trigger_calibrate(hostname):
|
||||
return {'error': f'Socket error: {e}'}, 500
|
||||
|
||||
return {'status': 'calibration_started', 'seconds': seconds}
|
||||
|
||||
|
||||
@bp.route('/sensors/heartbeat')
|
||||
def get_heartbeat_status():
|
||||
"""Get heartbeat status for all sensors."""
|
||||
return get_heartbeat_summary()
|
||||
|
||||
|
||||
@bp.route('/sensors/heartbeat', methods=['POST'])
|
||||
def refresh_heartbeats():
|
||||
"""Update heartbeat status for all sensors."""
|
||||
counts = update_all_heartbeats()
|
||||
return {
|
||||
'status': 'updated',
|
||||
'online': counts['online'],
|
||||
'stale': counts['stale'],
|
||||
'offline': counts['offline']
|
||||
}
|
||||
|
||||
|
||||
@bp.route('/sensors/<hostname>/metrics')
|
||||
def get_sensor_metrics(hostname):
|
||||
"""Get sensor activity metrics and recent events."""
|
||||
sensor = db.session.scalar(db.select(Sensor).where(Sensor.hostname == hostname))
|
||||
if not sensor:
|
||||
return {'error': 'Sensor not found'}, 404
|
||||
|
||||
# Time range (default: last 24 hours)
|
||||
hours = request.args.get('hours', 24, type=int)
|
||||
if hours < 1 or hours > 168: # max 1 week
|
||||
hours = 24
|
||||
since = datetime.now(UTC) - timedelta(hours=hours)
|
||||
|
||||
# Count activity
|
||||
sightings_count = db.session.scalar(
|
||||
db.select(db.func.count(Sighting.id))
|
||||
.where(Sighting.sensor_id == sensor.id)
|
||||
.where(Sighting.timestamp >= since)
|
||||
) or 0
|
||||
|
||||
alerts_count = db.session.scalar(
|
||||
db.select(db.func.count(Alert.id))
|
||||
.where(Alert.sensor_id == sensor.id)
|
||||
.where(Alert.timestamp >= since)
|
||||
) or 0
|
||||
|
||||
events_count = db.session.scalar(
|
||||
db.select(db.func.count(Event.id))
|
||||
.where(Event.sensor_id == sensor.id)
|
||||
.where(Event.timestamp >= since)
|
||||
) or 0
|
||||
|
||||
# Recent events (last 20)
|
||||
recent_events = db.session.scalars(
|
||||
db.select(Event)
|
||||
.where(Event.sensor_id == sensor.id)
|
||||
.order_by(Event.timestamp.desc())
|
||||
.limit(20)
|
||||
).all()
|
||||
|
||||
return {
|
||||
'hostname': sensor.hostname,
|
||||
'hours': hours,
|
||||
'activity': {
|
||||
'sightings': sightings_count,
|
||||
'alerts': alerts_count,
|
||||
'events': events_count,
|
||||
},
|
||||
'recent_events': [e.to_dict() for e in recent_events]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user