feat: add ComponentGrid and ComponentTable components for displaying components in grid and table formats
feat: implement HealthBadge and RoleBadge components for displaying health status and roles feat: create LogViewer component for streaming logs of services feat: develop SecretsEditor for managing secrets with CRUD operations feat: introduce ToolCard component for displaying tool information style: add global CSS variables and styles for consistent theming feat: set up API client for handling requests to the backend feat: implement hooks for fetching components, statuses, and tools feat: create routes for dashboard, component details, and tools feat: add service management endpoints for starting, stopping, and restarting services feat: implement event bus for handling real-time updates via SSE feat: create health check and logs endpoints for monitoring services feat: add tests for health and tools endpoints to ensure functionality chore: update project configuration files and dependencies for better development experience
This commit is contained in:
48
castle-api/src/castle_api/health.py
Normal file
48
castle-api/src/castle_api/health.py
Normal file
@@ -0,0 +1,48 @@
|
||||
"""Async health checker — fans out HTTP requests to service health endpoints."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import time
|
||||
|
||||
import httpx
|
||||
|
||||
from castle_cli.config import CastleConfig
|
||||
|
||||
from castle_api.models import HealthStatus
|
||||
|
||||
|
||||
async def check_all_health(config: CastleConfig) -> list[HealthStatus]:
|
||||
"""Check health of all components with expose.http and a health_path."""
|
||||
targets: list[tuple[str, str]] = []
|
||||
for name, manifest in config.components.items():
|
||||
if not (manifest.expose and manifest.expose.http):
|
||||
continue
|
||||
http = manifest.expose.http
|
||||
if not http.health_path:
|
||||
continue
|
||||
host = http.internal.host or "127.0.0.1"
|
||||
port = http.internal.port
|
||||
url = f"http://{host}:{port}{http.health_path}"
|
||||
targets.append((name, url))
|
||||
|
||||
if not targets:
|
||||
return []
|
||||
|
||||
async with httpx.AsyncClient(timeout=3.0) as client:
|
||||
tasks = [_check_one(client, name, url) for name, url in targets]
|
||||
return await asyncio.gather(*tasks)
|
||||
|
||||
|
||||
async def _check_one(client: httpx.AsyncClient, name: str, url: str) -> HealthStatus:
|
||||
"""Check a single service's health endpoint."""
|
||||
start = time.monotonic()
|
||||
try:
|
||||
resp = await client.get(url)
|
||||
latency = int((time.monotonic() - start) * 1000)
|
||||
if resp.status_code < 300:
|
||||
return HealthStatus(id=name, status="up", latency_ms=latency)
|
||||
return HealthStatus(id=name, status="down", latency_ms=latency)
|
||||
except httpx.HTTPError:
|
||||
latency = int((time.monotonic() - start) * 1000)
|
||||
return HealthStatus(id=name, status="down", latency_ms=latency)
|
||||
Reference in New Issue
Block a user