feat: status page enhancements
This commit is contained in:
parent
158a6ee716
commit
44d353a30f
5 changed files with 561 additions and 14 deletions
|
|
@ -1,4 +1,9 @@
|
|||
"""Monitors API endpoints."""
|
||||
"""Monitors API endpoints with tier enforcement.
|
||||
|
||||
When creating a monitor for an organization, tier enforcement checks:
|
||||
- monitors_per_service: max number of monitors per service
|
||||
- check_interval_min: minimum allowed check interval
|
||||
"""
|
||||
|
||||
from uuid import UUID
|
||||
|
||||
|
|
@ -8,7 +13,13 @@ from sqlalchemy import select
|
|||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.dependencies import get_db, verify_api_key
|
||||
from app.models.models import Monitor, MonitorResult
|
||||
from app.models.models import Monitor, MonitorResult, Service
|
||||
from app.models.saas_models import Organization
|
||||
from app.services.tier_enforcement import (
|
||||
enforce_monitor_limit,
|
||||
validate_check_interval,
|
||||
)
|
||||
from app.services.tier_limits import TierLimitExceeded, get_tier_limits
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
|
@ -20,6 +31,7 @@ class MonitorCreate(BaseModel):
|
|||
expected_status: int = 200
|
||||
timeout_seconds: int = Field(10, ge=1, le=60)
|
||||
interval_seconds: int = Field(60, ge=30, le=3600)
|
||||
organization_id: str | None = None # Optional: for tier enforcement
|
||||
|
||||
|
||||
class MonitorUpdate(BaseModel):
|
||||
|
|
@ -41,6 +53,7 @@ def serialize_monitor(m: Monitor) -> dict:
|
|||
"timeout_seconds": m.timeout_seconds,
|
||||
"interval_seconds": m.interval_seconds,
|
||||
"is_active": m.is_active,
|
||||
"organization_id": m.organization_id,
|
||||
"created_at": m.created_at.isoformat() if m.created_at else None,
|
||||
"updated_at": m.updated_at.isoformat() if m.updated_at else None,
|
||||
}
|
||||
|
|
@ -60,7 +73,43 @@ async def create_monitor(
|
|||
db: AsyncSession = Depends(get_db),
|
||||
api_key: str = Depends(verify_api_key),
|
||||
):
|
||||
"""Create a new monitor."""
|
||||
"""Create a new monitor.
|
||||
|
||||
If organization_id is provided, tier enforcement is applied:
|
||||
- check monitors_per_service limit
|
||||
- validate check_interval against minimum for the tier
|
||||
"""
|
||||
# Look up org if provided
|
||||
org = None
|
||||
if data.organization_id:
|
||||
result = await db.execute(
|
||||
select(Organization).where(Organization.id == data.organization_id)
|
||||
)
|
||||
org = result.scalar_one_or_none()
|
||||
if org is None:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Organization '{data.organization_id}' not found",
|
||||
)
|
||||
|
||||
# Tier enforcement when org context is provided
|
||||
if org is not None:
|
||||
# Check monitors_per_service limit
|
||||
await enforce_monitor_limit(db, org, str(data.service_id))
|
||||
# Validate check interval
|
||||
validate_check_interval(org, data.interval_seconds)
|
||||
|
||||
# Verify the service exists
|
||||
service_result = await db.execute(
|
||||
select(Service).where(Service.id == str(data.service_id))
|
||||
)
|
||||
service = service_result.scalar_one_or_none()
|
||||
if not service:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=f"Service '{data.service_id}' not found",
|
||||
)
|
||||
|
||||
monitor = Monitor(
|
||||
service_id=str(data.service_id),
|
||||
url=data.url,
|
||||
|
|
@ -68,6 +117,7 @@ async def create_monitor(
|
|||
expected_status=data.expected_status,
|
||||
timeout_seconds=data.timeout_seconds,
|
||||
interval_seconds=data.interval_seconds,
|
||||
organization_id=data.organization_id or (service.organization_id if service else None),
|
||||
)
|
||||
db.add(monitor)
|
||||
await db.flush()
|
||||
|
|
@ -115,13 +165,26 @@ async def update_monitor(
|
|||
db: AsyncSession = Depends(get_db),
|
||||
api_key: str = Depends(verify_api_key),
|
||||
):
|
||||
"""Update a monitor."""
|
||||
"""Update a monitor.
|
||||
|
||||
If interval_seconds is being changed, validate against the org's tier minimum.
|
||||
"""
|
||||
result = await db.execute(select(Monitor).where(Monitor.id == str(monitor_id)))
|
||||
monitor = result.scalar_one_or_none()
|
||||
if not monitor:
|
||||
raise HTTPException(status_code=404, detail="Monitor not found")
|
||||
|
||||
update_data = data.model_dump(exclude_unset=True)
|
||||
|
||||
# If updating interval_seconds and org context exists, validate
|
||||
if data.interval_seconds is not None and monitor.organization_id:
|
||||
org_result = await db.execute(
|
||||
select(Organization).where(Organization.id == monitor.organization_id)
|
||||
)
|
||||
org = org_result.scalar_one_or_none()
|
||||
if org:
|
||||
validate_check_interval(org, data.interval_seconds)
|
||||
|
||||
for field, value in update_data.items():
|
||||
setattr(monitor, field, value)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue