v0.2.0: async status polling added.

This commit is contained in:
serversdwn
2026-01-16 06:24:13 +00:00
parent d2b47156d8
commit d43ef7427f
8 changed files with 963 additions and 17 deletions

View File

@@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends, HTTPException, WebSocket, WebSocketDisco
from fastapi.responses import FileResponse
from sqlalchemy.orm import Session
from datetime import datetime
from pydantic import BaseModel, field_validator
from pydantic import BaseModel, field_validator, Field
import logging
import ipaddress
import json
@@ -77,6 +77,64 @@ class ConfigPayload(BaseModel):
return v
class PollingConfigPayload(BaseModel):
"""Payload for updating device polling configuration."""
poll_interval_seconds: int | None = Field(None, ge=10, le=3600, description="Polling interval in seconds (10-3600)")
poll_enabled: bool | None = Field(None, description="Enable or disable background polling for this device")
# ============================================================================
# GLOBAL POLLING STATUS ENDPOINT (must be before /{unit_id} routes)
# ============================================================================
@router.get("/_polling/status")
def get_global_polling_status(db: Session = Depends(get_db)):
"""
Get global background polling status for all devices.
Returns information about which devices are being polled, their
reachability status, failure counts, and last poll times.
Useful for monitoring the health of the background polling system.
Note: Must be defined before /{unit_id} routes to avoid routing conflicts.
"""
from app.background_poller import poller
configs = db.query(NL43Config).filter_by(
tcp_enabled=True,
poll_enabled=True
).all()
device_statuses = []
for cfg in configs:
status = db.query(NL43Status).filter_by(unit_id=cfg.unit_id).first()
device_statuses.append({
"unit_id": cfg.unit_id,
"poll_interval_seconds": cfg.poll_interval_seconds,
"poll_enabled": cfg.poll_enabled,
"is_reachable": status.is_reachable if status else None,
"consecutive_failures": status.consecutive_failures if status else 0,
"last_poll_attempt": status.last_poll_attempt.isoformat() if status and status.last_poll_attempt else None,
"last_success": status.last_success.isoformat() if status and status.last_success else None,
"last_error": status.last_error if status else None
})
return {
"status": "ok",
"data": {
"poller_running": poller._running,
"total_devices": len(configs),
"devices": device_statuses
}
}
# ============================================================================
# DEVICE-SPECIFIC ENDPOINTS
# ============================================================================
@router.get("/{unit_id}/config")
def get_config(unit_id: str, db: Session = Depends(get_db)):
cfg = db.query(NL43Config).filter_by(unit_id=unit_id).first()
@@ -167,6 +225,12 @@ def get_status(unit_id: str, db: Session = Depends(get_db)):
"sd_remaining_mb": status.sd_remaining_mb,
"sd_free_ratio": status.sd_free_ratio,
"raw_payload": status.raw_payload,
# Background polling status
"is_reachable": status.is_reachable,
"consecutive_failures": status.consecutive_failures,
"last_poll_attempt": status.last_poll_attempt.isoformat() if status.last_poll_attempt else None,
"last_success": status.last_success.isoformat() if status.last_success else None,
"last_error": status.last_error,
},
}
@@ -1480,3 +1544,77 @@ async def run_diagnostics(unit_id: str, db: Session = Depends(get_db)):
# All tests passed
diagnostics["overall_status"] = "pass"
return diagnostics
# ============================================================================
# BACKGROUND POLLING CONFIGURATION ENDPOINTS
# ============================================================================
@router.get("/{unit_id}/polling/config")
def get_polling_config(unit_id: str, db: Session = Depends(get_db)):
"""
Get background polling configuration for a device.
Returns the current polling interval and enabled status for automatic
background status polling.
"""
cfg = db.query(NL43Config).filter_by(unit_id=unit_id).first()
if not cfg:
raise HTTPException(status_code=404, detail="Device configuration not found")
return {
"status": "ok",
"data": {
"unit_id": unit_id,
"poll_interval_seconds": cfg.poll_interval_seconds,
"poll_enabled": cfg.poll_enabled
}
}
@router.put("/{unit_id}/polling/config")
def update_polling_config(
unit_id: str,
payload: PollingConfigPayload,
db: Session = Depends(get_db)
):
"""
Update background polling configuration for a device.
Allows configuring the polling interval (10-3600 seconds) and
enabling/disabling automatic background polling per device.
Changes take effect on the next polling cycle.
"""
cfg = db.query(NL43Config).filter_by(unit_id=unit_id).first()
if not cfg:
raise HTTPException(status_code=404, detail="Device configuration not found")
# Update interval if provided
if payload.poll_interval_seconds is not None:
if payload.poll_interval_seconds < 10:
raise HTTPException(
status_code=400,
detail="Polling interval must be at least 10 seconds"
)
cfg.poll_interval_seconds = payload.poll_interval_seconds
# Update enabled status if provided
if payload.poll_enabled is not None:
cfg.poll_enabled = payload.poll_enabled
db.commit()
logger.info(
f"Updated polling config for {unit_id}: "
f"interval={cfg.poll_interval_seconds}s, enabled={cfg.poll_enabled}"
)
return {
"status": "ok",
"data": {
"unit_id": unit_id,
"poll_interval_seconds": cfg.poll_interval_seconds,
"poll_enabled": cfg.poll_enabled
}
}