"""Elasticsearch + Kibana health API for Command Center.""" from __future__ import annotations import os from typing import Any import httpx from fastapi import APIRouter, Query from fastapi.responses import JSONResponse ELASTICSEARCH_URL = os.getenv("ELASTICSEARCH_URL", "https://10.0.21.46:9200").rstrip("/") KIBANA_URL = os.getenv("KIBANA_URL", "http://10.0.21.46:5601").rstrip("/") ELASTIC_USER = os.getenv("ELASTIC_USER", "elastic") ELASTIC_PASSWORD = os.getenv("ELASTIC_PASSWORD", "") router = APIRouter(prefix="/api/search", tags=["search"]) async def _probe_es() -> dict[str, Any]: auth = (ELASTIC_USER, ELASTIC_PASSWORD) if ELASTIC_PASSWORD else None try: async with httpx.AsyncClient(timeout=6.0, verify=False) as client: r = await client.get(f"{ELASTICSEARCH_URL}/", auth=auth) if r.status_code >= 400: return {"ok": False, "status_code": r.status_code, "error": r.text[:200]} info = r.json() health_r = await client.get(f"{ELASTICSEARCH_URL}/_cluster/health", auth=auth) health = health_r.json() if health_r.status_code < 400 else {} stats_r = await client.get(f"{ELASTICSEARCH_URL}/_cat/indices?format=json&bytes=b", auth=auth) indices = stats_r.json() if stats_r.status_code < 400 else [] return { "ok": True, "url": ELASTICSEARCH_URL, "cluster_name": info.get("cluster_name"), "version": info.get("version", {}).get("number"), "health": health.get("status", "unknown"), "nodes": health.get("number_of_nodes"), "indices_count": len(indices) if isinstance(indices, list) else 0, "indices": [ { "name": i.get("index"), "docs": i.get("docs.count"), "size": i.get("store.size"), "health": i.get("health"), } for i in (indices[:50] if isinstance(indices, list) else []) ], } except Exception as exc: return {"ok": False, "url": ELASTICSEARCH_URL, "error": str(exc)} async def _probe_kibana() -> dict[str, Any]: try: async with httpx.AsyncClient(timeout=6.0, verify=False) as client: r = await client.get(f"{KIBANA_URL}/api/status") if r.status_code >= 400: return {"ok": False, "status_code": r.status_code} data = r.json() overall = data.get("status", {}).get("overall", {}) return { "ok": overall.get("level") in ("available", "green", "yellow"), "url": KIBANA_URL, "ui_url": KIBANA_URL, "level": overall.get("level", "unknown"), } except Exception as exc: return {"ok": False, "url": KIBANA_URL, "error": str(exc)} @router.get("/health") async def search_health(): es, kb = await _probe_es(), await _probe_kibana() return { "ok": es.get("ok") or kb.get("ok"), "elasticsearch": es, "kibana": kb, } @router.get("/elasticsearch") async def get_elasticsearch(): result = await _probe_es() if not result.get("ok"): return JSONResponse(result, status_code=502) return result @router.get("/kibana") async def get_kibana(): return await _probe_kibana() @router.get("/elasticsearch/query") async def es_search(q: str = Query(..., min_length=1), size: int = Query(10, le=50)): if not ELASTIC_PASSWORD: return JSONResponse({"ok": False, "error": "ELASTIC_PASSWORD not configured"}, status_code=503) try: async with httpx.AsyncClient(timeout=15.0, verify=False) as client: r = await client.post( f"{ELASTICSEARCH_URL}/_search", auth=(ELASTIC_USER, ELASTIC_PASSWORD), json={"query": {"query_string": {"query": q}}, "size": size}, ) if r.status_code >= 400: return JSONResponse({"ok": False, "error": r.text[:300]}, status_code=r.status_code) data = r.json() hits = [ { "index": h.get("_index"), "id": h.get("_id"), "score": h.get("_score"), "source": h.get("_source"), } for h in data.get("hits", {}).get("hits", []) ] return {"ok": True, "total": data.get("hits", {}).get("total", {}), "hits": hits} except Exception as exc: return JSONResponse({"ok": False, "error": str(exc)}, status_code=502)