feat: indie status page MVP -- FastAPI + SQLite
- 8 DB models (services, incidents, monitors, subscribers, etc.) - Full CRUD API for services, incidents, monitors - Public status page with live data - Incident detail page with timeline - API key authentication - Uptime monitoring scheduler - 13 tests passing - TECHNICAL_DESIGN.md with full spec
This commit is contained in:
commit
902133edd3
4655 changed files with 1342691 additions and 0 deletions
59
app/services/uptime.py
Normal file
59
app/services/uptime.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
"""Uptime monitoring service — performs HTTP health checks."""
|
||||
|
||||
import time
|
||||
from datetime import datetime
|
||||
|
||||
import httpx
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.models.models import Monitor, MonitorResult
|
||||
|
||||
|
||||
async def check_monitor(monitor: Monitor, db: AsyncSession) -> MonitorResult:
|
||||
"""Perform a single HTTP health check for a monitor and store the result."""
|
||||
start = time.monotonic()
|
||||
|
||||
try:
|
||||
async with httpx.AsyncClient() as client:
|
||||
response = await client.request(
|
||||
method=monitor.method,
|
||||
url=monitor.url,
|
||||
timeout=monitor.timeout_seconds,
|
||||
follow_redirects=True,
|
||||
)
|
||||
elapsed_ms = int((time.monotonic() - start) * 1000)
|
||||
|
||||
if response.status_code == monitor.expected_status:
|
||||
# Check response time threshold for "degraded"
|
||||
status = "up" if elapsed_ms < 5000 else "degraded"
|
||||
result = MonitorResult(
|
||||
monitor_id=monitor.id,
|
||||
status=status,
|
||||
response_time_ms=elapsed_ms,
|
||||
status_code=response.status_code,
|
||||
error_message=None,
|
||||
checked_at=datetime.utcnow(),
|
||||
)
|
||||
else:
|
||||
result = MonitorResult(
|
||||
monitor_id=monitor.id,
|
||||
status="down",
|
||||
response_time_ms=elapsed_ms,
|
||||
status_code=response.status_code,
|
||||
error_message=f"Expected {monitor.expected_status}, got {response.status_code}",
|
||||
checked_at=datetime.utcnow(),
|
||||
)
|
||||
except Exception as exc:
|
||||
elapsed_ms = int((time.monotonic() - start) * 1000)
|
||||
result = MonitorResult(
|
||||
monitor_id=monitor.id,
|
||||
status="down",
|
||||
response_time_ms=elapsed_ms,
|
||||
status_code=None,
|
||||
error_message=str(exc)[:500],
|
||||
checked_at=datetime.utcnow(),
|
||||
)
|
||||
|
||||
db.add(result)
|
||||
await db.flush()
|
||||
return result
|
||||
Loading…
Add table
Add a link
Reference in a new issue