forked from Rockachopa/Timmy-time-dashboard
Merge pull request 'feat: JSON status endpoints for briefing, memory, swarm (#49, #50)' (#101) from fix/api-consistency into main
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
"""System-level dashboard routes (ledger, upgrades, etc.)."""
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi import APIRouter, Request
|
||||
from fastapi.responses import HTMLResponse, JSONResponse
|
||||
|
||||
from config import settings
|
||||
from dashboard.templating import templates
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -147,3 +149,82 @@ async def api_notifications():
|
||||
except Exception as exc:
|
||||
logger.debug("System events fetch error: %s", exc)
|
||||
return JSONResponse([])
|
||||
|
||||
|
||||
@router.get("/api/briefing/status", response_class=JSONResponse)
|
||||
async def api_briefing_status():
|
||||
"""Return briefing status including pending approvals and last generated time."""
|
||||
from timmy import approvals
|
||||
from timmy.briefing import engine as briefing_engine
|
||||
|
||||
pending = approvals.list_pending()
|
||||
pending_count = len(pending)
|
||||
|
||||
last_generated = None
|
||||
try:
|
||||
cached = briefing_engine.get_cached()
|
||||
if cached:
|
||||
last_generated = cached.generated_at.isoformat()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return JSONResponse(
|
||||
{
|
||||
"status": "ok",
|
||||
"pending_approvals": pending_count,
|
||||
"last_generated": last_generated,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@router.get("/api/memory/status", response_class=JSONResponse)
|
||||
async def api_memory_status():
|
||||
"""Return memory database status including file info and indexed files count."""
|
||||
from timmy.memory.vector_store import get_memory_stats
|
||||
|
||||
db_path = Path(settings.repo_root) / "data" / "memory.db"
|
||||
db_exists = db_path.exists()
|
||||
db_size = db_path.stat().st_size if db_exists else 0
|
||||
|
||||
try:
|
||||
stats = get_memory_stats()
|
||||
indexed_files = stats.get("total_entries", 0)
|
||||
except Exception:
|
||||
indexed_files = 0
|
||||
|
||||
return JSONResponse(
|
||||
{
|
||||
"status": "ok",
|
||||
"db_exists": db_exists,
|
||||
"db_size_bytes": db_size,
|
||||
"indexed_files": indexed_files,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@router.get("/api/swarm/status", response_class=JSONResponse)
|
||||
async def api_swarm_status():
|
||||
"""Return swarm worker status and pending tasks count."""
|
||||
from dashboard.routes.tasks import _get_db
|
||||
|
||||
pending_tasks = 0
|
||||
try:
|
||||
db = _get_db()
|
||||
try:
|
||||
row = db.execute(
|
||||
"SELECT COUNT(*) as cnt FROM tasks WHERE status IN ('pending_approval','approved')"
|
||||
).fetchone()
|
||||
pending_tasks = row["cnt"] if row else 0
|
||||
finally:
|
||||
db.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return JSONResponse(
|
||||
{
|
||||
"status": "ok",
|
||||
"active_workers": 0,
|
||||
"pending_tasks": pending_tasks,
|
||||
"message": "Swarm monitoring endpoint",
|
||||
}
|
||||
)
|
||||
|
||||
77
tests/dashboard/test_api_status_endpoints.py
Normal file
77
tests/dashboard/test_api_status_endpoints.py
Normal file
@@ -0,0 +1,77 @@
|
||||
"""Tests for the API status endpoints.
|
||||
|
||||
Verifies /api/briefing/status, /api/memory/status, and /api/swarm/status
|
||||
return valid JSON with expected keys.
|
||||
"""
|
||||
|
||||
|
||||
def test_api_briefing_status_returns_ok(client):
|
||||
"""GET /api/briefing/status returns 200 with expected JSON structure."""
|
||||
response = client.get("/api/briefing/status")
|
||||
assert response.status_code == 200
|
||||
|
||||
data = response.json()
|
||||
assert data["status"] == "ok"
|
||||
assert "pending_approvals" in data
|
||||
assert isinstance(data["pending_approvals"], int)
|
||||
assert "last_generated" in data
|
||||
# last_generated can be None or a string
|
||||
assert data["last_generated"] is None or isinstance(data["last_generated"], str)
|
||||
|
||||
|
||||
def test_api_memory_status_returns_ok(client):
|
||||
"""GET /api/memory/status returns 200 with expected JSON structure."""
|
||||
response = client.get("/api/memory/status")
|
||||
assert response.status_code == 200
|
||||
|
||||
data = response.json()
|
||||
assert data["status"] == "ok"
|
||||
assert "db_exists" in data
|
||||
assert isinstance(data["db_exists"], bool)
|
||||
assert "db_size_bytes" in data
|
||||
assert isinstance(data["db_size_bytes"], int)
|
||||
assert data["db_size_bytes"] >= 0
|
||||
assert "indexed_files" in data
|
||||
assert isinstance(data["indexed_files"], int)
|
||||
assert data["indexed_files"] >= 0
|
||||
|
||||
|
||||
def test_api_swarm_status_returns_ok(client):
|
||||
"""GET /api/swarm/status returns 200 with expected JSON structure."""
|
||||
response = client.get("/api/swarm/status")
|
||||
assert response.status_code == 200
|
||||
|
||||
data = response.json()
|
||||
assert data["status"] == "ok"
|
||||
assert "active_workers" in data
|
||||
assert isinstance(data["active_workers"], int)
|
||||
assert "pending_tasks" in data
|
||||
assert isinstance(data["pending_tasks"], int)
|
||||
assert data["pending_tasks"] >= 0
|
||||
assert "message" in data
|
||||
assert isinstance(data["message"], str)
|
||||
assert data["message"] == "Swarm monitoring endpoint"
|
||||
|
||||
|
||||
def test_api_swarm_status_reflects_pending_tasks(client):
|
||||
"""GET /api/swarm/status reflects pending tasks from task queue."""
|
||||
# First create a task
|
||||
client.post("/api/tasks", json={"title": "Swarm status test task"})
|
||||
|
||||
# Now check swarm status
|
||||
response = client.get("/api/swarm/status")
|
||||
assert response.status_code == 200
|
||||
|
||||
data = response.json()
|
||||
assert data["pending_tasks"] >= 1
|
||||
|
||||
|
||||
def test_api_briefing_status_pending_approvals_count(client):
|
||||
"""GET /api/briefing/status returns correct pending approvals count."""
|
||||
response = client.get("/api/briefing/status")
|
||||
assert response.status_code == 200
|
||||
|
||||
data = response.json()
|
||||
assert "pending_approvals" in data
|
||||
assert isinstance(data["pending_approvals"], int)
|
||||
assert data["pending_approvals"] >= 0
|
||||
Reference in New Issue
Block a user