157 lines
5.0 KiB
Python
157 lines
5.0 KiB
Python
from datetime import UTC, datetime
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from sqlalchemy import func, select
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.auth.dependencies import get_current_user, require_role
|
|
from app.db.session import get_db
|
|
from app.models import AlertRule, Asset, CheckResult, Incident, Monitor, User
|
|
from app.schemas.core import CheckResultRead, MonitorCreate, MonitorRead, MonitorUpdate, WebsiteMonitorCreate
|
|
|
|
router = APIRouter(prefix="/monitors", tags=["monitors"])
|
|
|
|
|
|
@router.get("", response_model=list[MonitorRead])
|
|
def list_monitors(_: User = Depends(get_current_user), db: Session = Depends(get_db)) -> list[Monitor]:
|
|
return list(db.scalars(select(Monitor).order_by(Monitor.name)).all())
|
|
|
|
|
|
@router.post("", response_model=MonitorRead)
|
|
def create_monitor(
|
|
payload: MonitorCreate,
|
|
_: User = Depends(require_role("admin")),
|
|
db: Session = Depends(get_db),
|
|
) -> Monitor:
|
|
monitor = Monitor(**payload.model_dump())
|
|
db.add(monitor)
|
|
db.commit()
|
|
db.refresh(monitor)
|
|
return monitor
|
|
|
|
|
|
@router.post("/website", response_model=MonitorRead)
|
|
def create_website_monitor(
|
|
payload: WebsiteMonitorCreate,
|
|
_: User = Depends(require_role("admin")),
|
|
db: Session = Depends(get_db),
|
|
) -> Monitor:
|
|
asset_id: int | None = None
|
|
if payload.create_asset:
|
|
asset = Asset(name=payload.name, asset_type="website", address=payload.url, status="unknown", extra={})
|
|
db.add(asset)
|
|
db.flush()
|
|
asset_id = asset.id
|
|
|
|
monitor = Monitor(
|
|
asset_id=asset_id,
|
|
name=payload.name,
|
|
monitor_type="http",
|
|
target=payload.url,
|
|
config={
|
|
"expected_status": payload.expected_status,
|
|
"expected_text": payload.expected_text,
|
|
"unexpected_text": payload.unexpected_text,
|
|
"timeout_seconds": payload.timeout_seconds,
|
|
},
|
|
interval_seconds=payload.interval_seconds,
|
|
status="unknown",
|
|
)
|
|
db.add(monitor)
|
|
db.flush()
|
|
|
|
if payload.alert_enabled:
|
|
db.add(
|
|
AlertRule(
|
|
monitor_id=monitor.id,
|
|
name=f"{payload.name} website failure",
|
|
severity=payload.alert_severity,
|
|
condition={"type": "status_not_up"},
|
|
failure_threshold=payload.failure_threshold,
|
|
cooldown_seconds=300,
|
|
is_enabled=True,
|
|
)
|
|
)
|
|
|
|
db.commit()
|
|
db.refresh(monitor)
|
|
return monitor
|
|
|
|
|
|
@router.get("/{monitor_id}", response_model=MonitorRead)
|
|
def get_monitor(monitor_id: int, _: User = Depends(get_current_user), db: Session = Depends(get_db)) -> Monitor:
|
|
monitor = db.get(Monitor, monitor_id)
|
|
if monitor is None:
|
|
raise HTTPException(status_code=404, detail="Monitor not found")
|
|
return monitor
|
|
|
|
|
|
@router.patch("/{monitor_id}", response_model=MonitorRead)
|
|
def update_monitor(
|
|
monitor_id: int,
|
|
payload: MonitorUpdate,
|
|
_: User = Depends(require_role("admin")),
|
|
db: Session = Depends(get_db),
|
|
) -> Monitor:
|
|
monitor = db.get(Monitor, monitor_id)
|
|
if monitor is None:
|
|
raise HTTPException(status_code=404, detail="Monitor not found")
|
|
|
|
for field, value in payload.model_dump(exclude_unset=True).items():
|
|
setattr(monitor, field, value)
|
|
|
|
db.commit()
|
|
db.refresh(monitor)
|
|
return monitor
|
|
|
|
|
|
@router.delete("/{monitor_id}", status_code=204)
|
|
def delete_monitor(
|
|
monitor_id: int,
|
|
cleanup_orphan_website_asset: bool = True,
|
|
_: User = Depends(require_role("admin")),
|
|
db: Session = Depends(get_db),
|
|
) -> None:
|
|
monitor = db.get(Monitor, monitor_id)
|
|
if monitor is None:
|
|
raise HTTPException(status_code=404, detail="Monitor not found")
|
|
|
|
asset_id = monitor.asset_id
|
|
now = datetime.now(UTC)
|
|
open_incidents = db.scalars(select(Incident).where(Incident.monitor_id == monitor_id, Incident.status == "open")).all()
|
|
for incident in open_incidents:
|
|
incident.status = "resolved"
|
|
incident.resolved_at = now
|
|
incident.details = {**(incident.details or {}), "resolution_reason": "monitor_deleted"}
|
|
|
|
db.delete(monitor)
|
|
db.flush()
|
|
|
|
if cleanup_orphan_website_asset and asset_id is not None:
|
|
remaining = db.scalar(select(func.count(Monitor.id)).where(Monitor.asset_id == asset_id))
|
|
asset = db.get(Asset, asset_id)
|
|
if remaining == 0 and asset is not None and asset.asset_type == "website":
|
|
db.delete(asset)
|
|
|
|
db.commit()
|
|
|
|
|
|
@router.get("/{monitor_id}/results", response_model=list[CheckResultRead])
|
|
def list_monitor_results(
|
|
monitor_id: int,
|
|
limit: int = 20,
|
|
_: User = Depends(get_current_user),
|
|
db: Session = Depends(get_db),
|
|
) -> list[CheckResult]:
|
|
monitor = db.get(Monitor, monitor_id)
|
|
if monitor is None:
|
|
raise HTTPException(status_code=404, detail="Monitor not found")
|
|
return list(
|
|
db.scalars(
|
|
select(CheckResult)
|
|
.where(CheckResult.monitor_id == monitor_id)
|
|
.order_by(CheckResult.observed_at.desc())
|
|
.limit(min(limit, 100))
|
|
).all()
|
|
)
|