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 |
|
| Method | Endpoint | Description |
|
||||||
|--------|----------|-------------|
|
|--------|----------|-------------|
|
||||||
|
| GET | `/api/health` | Health check for monitoring |
|
||||||
| POST | `/api/scan` | Trigger new scan |
|
| POST | `/api/scan` | Trigger new scan |
|
||||||
| GET | `/api/latest` | Get most recent scan |
|
| GET | `/api/latest` | Get most recent scan |
|
||||||
| GET | `/api/scans` | List all scans |
|
| 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
|
## Scanning
|
||||||
|
|
||||||
### Trigger Scan
|
### Trigger Scan
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ from ..config import Config, get_config
|
|||||||
from ..bluetooth_identify import identify_single_device, identify_device
|
from ..bluetooth_identify import identify_single_device, identify_device
|
||||||
from ..database import DeviceDatabase, init_database, get_database
|
from ..database import DeviceDatabase, init_database, get_database
|
||||||
from ..homeassistant import HAWebhooks, HAWebhookConfig
|
from ..homeassistant import HAWebhooks, HAWebhookConfig
|
||||||
|
from .. import __version__
|
||||||
|
|
||||||
# Module-level SocketIO instance
|
# Module-level SocketIO instance
|
||||||
socketio = SocketIO()
|
socketio = SocketIO()
|
||||||
@@ -237,6 +238,7 @@ def create_app(config: Config | None = None) -> Flask:
|
|||||||
|
|
||||||
# Store config reference
|
# Store config reference
|
||||||
app.config["RF_CONFIG"] = config
|
app.config["RF_CONFIG"] = config
|
||||||
|
app.config["START_TIME"] = datetime.now()
|
||||||
|
|
||||||
# Initialize SocketIO with threading mode (compatible with existing threads)
|
# Initialize SocketIO with threading mode (compatible with existing threads)
|
||||||
socketio.init_app(app, cors_allowed_origins="*", async_mode="threading")
|
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"])
|
@app.route("/api/scan", methods=["POST"])
|
||||||
def api_scan():
|
def api_scan():
|
||||||
"""Trigger a new RF scan"""
|
"""Trigger a new RF scan"""
|
||||||
|
|||||||
Reference in New Issue
Block a user