# retoor <retoor@molodetz.nl>
from __future__ import annotations
import logging
from datetime import datetime, timezone
from typing import Annotated
from fastapi import APIRouter, Depends, Form, Request
from fastapi.responses import JSONResponse
from devplacepy.database import get_table
from devplacepy.responses import json_error, respond
from devplacepy.utils import generate_uid, get_current_user, is_admin, require_user
logger = logging.getLogger("routers.monitors")
router = APIRouter(tags=["monitors"])
@router.get("/checks")
async def list_checks(
request: Request,
user: dict = Depends(require_user),
):
checks_table = get_table("monitor_checks")
admin = is_admin(user)
if admin:
checks = list(checks_table.find(deleted_at=None, order_by="-created_at"))
else:
checks = list(
checks_table.find(user_uid=user["uid"], deleted_at=None, order_by="-created_at")
)
# Enrich with items count
items_table = get_table("monitor_check_items")
for check in checks:
try:
items = list(items_table.find(check_uid=check["uid"], deleted_at=None))
check["items_count"] = len(items)
except Exception:
check["items_count"] = 0
return respond(request, "monitors/list.html", {"checks": checks})
@router.post("/checks")
async def create_check(
request: Request,
user: dict = Depends(require_user),
name: Annotated[str, Form(max_length=128)] = ...,
check_type: Annotated[str, Form()] = ...,
target: Annotated[str, Form(max_length=1024)] = ...,
interval_seconds: Annotated[int, Form(ge=10, le=86400)] = 60,
description: Annotated[str, Form(max_length=512)] = "",
group_uid: Annotated[str, Form()] = "",
escalation_policy_uid: Annotated[str, Form()] = "",
):
# Validate target - reject private/internal addresses
if check_type == "http":
from urllib.parse import urlparse
parsed = urlparse(target)
host = parsed.hostname or ""
if host in ("localhost", "127.0.0.1", "0.0.0.0"):
return json_error(400, "Internal/private targets are not allowed")
if host.startswith("10.") or host.startswith("192.168.") or host.startswith("172.16."):
return json_error(400, "Private network targets are not allowed")
uid = generate_uid()
now = datetime.now(timezone.utc).isoformat()
checks_table = get_table("monitor_checks")
checks_table.insert(
{
"uid": uid,
"user_uid": user["uid"],
"name": name,
"description": description,
"check_type": check_type,
"target": target,
"interval_seconds": interval_seconds,
"group_uid": group_uid,
"escalation_policy_uid": escalation_policy_uid,
"enabled": 1,
"created_at": now,
"updated_at": now,
"deleted_at": None,
"deleted_by": None,
}
)
# Add default check item
from devplacepy.config import MOLOUPTIME_BINARY_AVAILABLE
if MOLOUPTIME_BINARY_AVAILABLE:
try:
item_uid = generate_uid()
items_table = get_table("monitor_check_items")
items_table.insert(
{
"uid": item_uid,
"check_uid": uid,
"user_uid": user["uid"],
"check_type": check_type,
"target": target,
"interval_seconds": interval_seconds,
"created_at": now,
"updated_at": now,
"deleted_at": None,
"deleted_by": None,
}
)
except Exception as e:
logger.error("Failed to create check item: %s", e)
return {"uid": uid, "name": name, "status": "created"}
@router.get("/checks/{check_uid}")
async def get_check(
request: Request,
check_uid: str,
user: dict = Depends(require_user),
):
checks_table = get_table("monitor_checks")
check = checks_table.find_one(uid=check_uid)
if not check:
return json_error(404, "Check not found")
if not is_admin(user) and check["user_uid"] != user["uid"]:
return json_error(403, "Access denied")
# Get items
items_table = get_table("monitor_check_items")
items = list(items_table.find(check_uid=check_uid, deleted_at=None))
check["items"] = items
return respond(request, "monitors/detail.html", {"check": check})
@router.delete("/checks/{check_uid}")
async def delete_check(
request: Request,
check_uid: str,
user: dict = Depends(require_user),
):
from devplacepy.database import soft_delete
checks_table = get_table("monitor_checks")
check = checks_table.find_one(uid=check_uid)
if not check:
return json_error(404, "Check not found")
if not is_admin(user) and check["user_uid"] != user["uid"]:
return json_error(403, "Access denied")
soft_delete("monitor_checks", check_uid, user["uid"])
# Also soft-delete items
items_table = get_table("monitor_check_items")
for item in items_table.find(check_uid=check_uid, deleted_at=None):
soft_delete("monitor_check_items", item["uid"], user["uid"])
return {"status": "deleted", "uid": check_uid}
@router.post("/checks/{check_uid}/toggle")
async def toggle_check(
request: Request,
check_uid: str,
user: dict = Depends(require_user),
):
checks_table = get_table("monitor_checks")
check = checks_table.find_one(uid=check_uid)
if not check:
return json_error(404, "Check not found")
if not is_admin(user) and check["user_uid"] != user["uid"]:
return json_error(403, "Access denied")
new_enabled = 0 if check.get("enabled", 1) else 1
checks_table.update(
{"uid": check_uid, "enabled": new_enabled, "updated_at": datetime.now(timezone.utc).isoformat()},
["uid"],
)
return {"status": "toggled", "enabled": new_enabled}
@router.get("/groups")
async def list_groups(
request: Request,
user: dict = Depends(require_user),
):
groups_table = get_table("monitor_groups")
admin = is_admin(user)
if admin:
groups = list(groups_table.find(deleted_at=None, order_by="-created_at"))
else:
groups = list(
groups_table.find(user_uid=user["uid"], deleted_at=None, order_by="-created_at")
)
return respond(request, "monitors/groups.html", {"groups": groups})
@router.post("/groups")
async def create_group(
request: Request,
user: dict = Depends(require_user),
name: Annotated[str, Form(max_length=128)] = ...,
description: Annotated[str, Form(max_length=512)] = "",
):
uid = generate_uid()
now = datetime.now(timezone.utc).isoformat()
groups_table = get_table("monitor_groups")
groups_table.insert(
{
"uid": uid,
"user_uid": user["uid"],
"name": name,
"description": description,
"created_at": now,
"deleted_at": None,
"deleted_by": None,
}
)
return {"uid": uid, "name": name, "status": "created"}
@router.get("/admin/stats")
async def admin_stats(
request: Request,
user: dict = Depends(require_user),
):
if not is_admin(user):
return json_error(403, "Admin access required")
checks_table = get_table("monitor_checks")
total_checks = 0
total_items = 0
by_type = {}
by_user = {}
try:
rows = checks_table.find(deleted_at=None)
for row in rows:
total_checks += 1
ct = row.get("check_type", "unknown")
by_type[ct] = by_type.get(ct, 0) + 1
uu = row.get("user_uid", "unknown")
by_user[uu] = by_user.get(uu, 0) + 1
except Exception:
pass
items_table = get_table("monitor_check_items")
try:
total_items = len(list(items_table.find(deleted_at=None)))
except Exception:
pass
snapshots_table = get_table("monitor_metrics_snapshots")
total_metrics = 0
try:
total_metrics = len(list(snapshots_table.find()))
except Exception:
pass
return respond(
request,
"admin/monitors.html",
{
"total_checks": total_checks,
"total_items": total_items,
"total_metrics": total_metrics,
"by_type": by_type,
"by_user": by_user,
},
)
@router.get("/escalation-policies")
async def list_escalation_policies(
request: Request,
user: dict = Depends(require_user),
):
policies_table = get_table("monitor_escalation_policies")
policies = list(
policies_table.find(user_uid=user["uid"], order_by="-created_at")
)
return respond(request, "monitors/escalation.html", {"policies": policies})