/users/signup answers 403 unless SIGNUP_ENABLED is set. A public GET /utils/config/ tells the login page, which hides the sign-up link, and the signup page explains that an administrator creates accounts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013GAeg4Jo6xjZzjwy9QDWMQ
111 lines
3.7 KiB
Python
111 lines
3.7 KiB
Python
import time
|
|
from typing import Any
|
|
|
|
from fastapi import APIRouter, Depends, Request, Response
|
|
from fastapi.concurrency import run_in_threadpool
|
|
from pydantic import BaseModel
|
|
from pydantic.networks import EmailStr
|
|
|
|
from fluksio.api.deps import get_current_active_superuser
|
|
from fluksio.core.config import settings
|
|
from fluksio.flow.state import RedisState
|
|
from fluksio.models import Message
|
|
from fluksio.utils import generate_test_email, send_email
|
|
|
|
router = APIRouter(prefix="/utils", tags=["utils"])
|
|
|
|
|
|
@router.post(
|
|
"/test-email/",
|
|
dependencies=[Depends(get_current_active_superuser)],
|
|
status_code=201,
|
|
)
|
|
def test_email(email_to: EmailStr) -> Message:
|
|
"""
|
|
Test emails.
|
|
"""
|
|
email_data = generate_test_email(email_to=email_to)
|
|
send_email(
|
|
email_to=email_to,
|
|
subject=email_data.subject,
|
|
html_content=email_data.html_content,
|
|
)
|
|
return Message(message="Test email sent")
|
|
|
|
|
|
class PublicConfig(BaseModel):
|
|
"""What a browser needs to know about this instance before signing in."""
|
|
|
|
signup_enabled: bool
|
|
|
|
|
|
@router.get("/config/", response_model=PublicConfig)
|
|
def read_config() -> Any:
|
|
"""The settings the login page reads. Unauthenticated, so nothing secret."""
|
|
return PublicConfig(signup_enabled=settings.SIGNUP_ENABLED)
|
|
|
|
|
|
@router.get("/health-check/")
|
|
async def health_check() -> bool:
|
|
return True
|
|
|
|
|
|
@router.get("/health/")
|
|
async def health(request: Request, response: Response) -> dict[str, Any]:
|
|
"""Deep health: 200 while the engine can serve its purpose, 503 otherwise.
|
|
|
|
"Serve its purpose" means the event loop is responsive and the configured
|
|
state backend answers — the two failure modes a process-alive check never
|
|
sees. The queue and pool sections are filled by the execution service.
|
|
"""
|
|
controller = getattr(request.app.state, "flow_controller", None)
|
|
watchdog = getattr(request.app.state, "watchdog", None)
|
|
problems: list[str] = []
|
|
|
|
loop_lag = watchdog.snapshot() if watchdog else {"ewma": 0.0, "max_60s": 0.0}
|
|
if watchdog is not None and watchdog.degraded:
|
|
problems.append("event loop lagging")
|
|
|
|
redis_info: dict[str, Any] = {
|
|
"configured": False,
|
|
"connected": None,
|
|
"rtt_ms": None,
|
|
}
|
|
engine: dict[str, Any] = {"flows": 0, "nodes": 0, "quarantined": []}
|
|
queue: dict[str, Any] = {}
|
|
if controller is not None:
|
|
state = controller.state
|
|
if isinstance(state, RedisState):
|
|
redis_info["configured"] = True
|
|
start = time.perf_counter()
|
|
connected = await run_in_threadpool(state.ping)
|
|
redis_info["connected"] = connected
|
|
redis_info["rtt_ms"] = round((time.perf_counter() - start) * 1000, 1)
|
|
if not connected:
|
|
problems.append("redis unreachable")
|
|
# `loaded` is keyed by node id, so its length is a node count.
|
|
loaded = getattr(controller, "loaded", {}) or {}
|
|
engine["flows"] = len({entry.flow for entry in loaded.values()})
|
|
engine["nodes"] = len(
|
|
getattr(getattr(controller, "pipeline", None), "nodes", []) or []
|
|
)
|
|
engine["quarantined"] = sorted(getattr(controller, "quarantined", ()) or ())
|
|
stats = getattr(controller, "queue_stats", None)
|
|
if callable(stats):
|
|
queue = await run_in_threadpool(stats)
|
|
if queue.get("oldest_pending_s", 0) > 120:
|
|
problems.append("queue stalled")
|
|
|
|
status = "degraded" if problems else "ok"
|
|
if problems:
|
|
response.status_code = 503
|
|
return {
|
|
"status": status,
|
|
"problems": problems,
|
|
"loop_lag_ms": loop_lag,
|
|
"redis": redis_info,
|
|
"engine": engine,
|
|
"queue": queue,
|
|
"ts": time.time(),
|
|
}
|