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:
1
USAGE.md
1
USAGE.md
@@ -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 |
|
||||
|
||||
59
docs/API.md
59
docs/API.md
@@ -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
|
||||
|
||||
@@ -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"""
|
||||
|
||||
Reference in New Issue
Block a user