feat: add /api/health endpoint for monitoring

- Returns status, version, uptime, scanner_id
- Component status: database, peer_sync, auto_scanner
- Returns 200 for healthy, 503 for unhealthy
- Tracks app start time for uptime calculation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
User
2026-02-01 12:24:04 +01:00
parent 522174721d
commit 5b9612dfae
3 changed files with 126 additions and 0 deletions

View File

@@ -268,6 +268,7 @@ The web server exposes a REST API:
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/health` | Health check for monitoring |
| POST | `/api/scan` | Trigger new scan |
| GET | `/api/latest` | Get most recent scan |
| GET | `/api/scans` | List all scans |

View File

@@ -6,6 +6,65 @@ REST API documentation for RF Mapper web interface.
---
## System
### Health Check
Returns health status for monitoring and load balancers.
```
GET /api/health
```
**Response:**
```json
{
"status": "healthy",
"version": "1.0.0",
"uptime_seconds": 3600,
"uptime_human": "1h 0m",
"scanner_id": "rpios",
"components": {
"database": {
"status": "ok",
"device_count": 100
},
"peer_sync": {
"status": "ok",
"peer_count": 2
},
"auto_scanner": {
"status": "stopped"
}
}
}
```
**Status Codes:**
- `200` - Healthy
- `503` - Unhealthy (component error)
**Component Status Values:**
- `ok` - Component working normally
- `disabled` - Component not enabled in config
- `error` - Component has errors
- `running` / `stopped` - For auto_scanner
**Example:**
```bash
# Simple health check
curl -s http://localhost:5000/api/health | jq '.status'
# Use in monitoring scripts
if curl -sf http://localhost:5000/api/health > /dev/null; then
echo "RF Mapper is healthy"
fi
```
---
## Scanning
### Trigger Scan

View File

@@ -17,6 +17,7 @@ from ..config import Config, get_config
from ..bluetooth_identify import identify_single_device, identify_device
from ..database import DeviceDatabase, init_database, get_database
from ..homeassistant import HAWebhooks, HAWebhookConfig
from .. import __version__
# Module-level SocketIO instance
socketio = SocketIO()
@@ -237,6 +238,7 @@ def create_app(config: Config | None = None) -> Flask:
# Store config reference
app.config["RF_CONFIG"] = config
app.config["START_TIME"] = datetime.now()
# Initialize SocketIO with threading mode (compatible with existing threads)
socketio.init_app(app, cors_allowed_origins="*", async_mode="threading")
@@ -397,6 +399,70 @@ def create_app(config: Config | None = None) -> Flask:
}
)
@app.route("/api/health")
def api_health():
"""Health check endpoint for monitoring"""
start_time = app.config.get("START_TIME", datetime.now())
uptime_seconds = (datetime.now() - start_time).total_seconds()
# Check component status
db = app.config.get("DATABASE")
peer_sync = app.config.get("PEER_SYNC")
auto_scanner = app.config.get("AUTO_SCANNER")
# Database status
db_status = "disabled"
db_device_count = 0
if db:
try:
db_device_count = len(db.get_all_devices())
db_status = "ok"
except Exception:
db_status = "error"
# Peer sync status
peers_status = "disabled"
peers_count = 0
if peer_sync:
try:
sync_status = peer_sync.get_status()
peers_count = len(sync_status.get("peers", []))
peers_status = "ok"
except Exception:
peers_status = "error"
# Auto scanner status
autoscan_status = "stopped"
if auto_scanner and auto_scanner._enabled:
autoscan_status = "running"
# Overall health
is_healthy = db_status != "error" and peers_status != "error"
response = {
"status": "healthy" if is_healthy else "unhealthy",
"version": __version__,
"uptime_seconds": int(uptime_seconds),
"uptime_human": f"{int(uptime_seconds // 3600)}h {int((uptime_seconds % 3600) // 60)}m",
"scanner_id": app.config.get("SCANNER_IDENTITY", {}).get("id", ""),
"components": {
"database": {
"status": db_status,
"device_count": db_device_count
},
"peer_sync": {
"status": peers_status,
"peer_count": peers_count
},
"auto_scanner": {
"status": autoscan_status
}
}
}
status_code = 200 if is_healthy else 503
return jsonify(response), status_code
@app.route("/api/scan", methods=["POST"])
def api_scan():
"""Trigger a new RF scan"""