feat: add /public/nodes endpoints for Node Directory

This commit is contained in:
Apple
2025-11-28 04:56:55 -08:00
parent e95673a6e7
commit 319c7e4799
3 changed files with 125 additions and 0 deletions

View File

@@ -200,6 +200,20 @@ class HomeNodeView(BaseModel):
environment: Optional[str] = None
class NodeProfile(BaseModel):
"""Node profile for Node Directory"""
node_id: str
name: str
hostname: Optional[str] = None
roles: List[str] = []
environment: str = "unknown"
status: str = "offline"
gpu_info: Optional[str] = None
agents_total: int = 0
agents_online: int = 0
last_heartbeat: Optional[str] = None
class AgentSummary(BaseModel):
"""Agent summary for Agent Console"""
id: str

View File

@@ -1087,3 +1087,55 @@ async def get_microdao_by_slug(slug: str) -> Optional[dict]:
return result
# =============================================================================
# Nodes Repository
# =============================================================================
async def get_all_nodes() -> List[dict]:
"""Отримати список всіх нод з кількістю агентів"""
pool = await get_pool()
query = """
SELECT
nc.node_id,
nc.node_name AS name,
nc.hostname,
nc.roles,
nc.environment,
nc.status,
nc.gpu,
nc.last_sync AS last_heartbeat,
(SELECT COUNT(*) FROM agents a WHERE a.node_id = nc.node_id) AS agents_total,
(SELECT COUNT(*) FROM agents a WHERE a.node_id = nc.node_id AND a.status = 'online') AS agents_online
FROM node_cache nc
ORDER BY nc.environment DESC, nc.node_name
"""
rows = await pool.fetch(query)
return [dict(row) for row in rows]
async def get_node_by_id(node_id: str) -> Optional[dict]:
"""Отримати ноду по ID"""
pool = await get_pool()
query = """
SELECT
nc.node_id,
nc.node_name AS name,
nc.hostname,
nc.roles,
nc.environment,
nc.status,
nc.gpu,
nc.last_sync AS last_heartbeat,
(SELECT COUNT(*) FROM agents a WHERE a.node_id = nc.node_id) AS agents_total,
(SELECT COUNT(*) FROM agents a WHERE a.node_id = nc.node_id AND a.status = 'online') AS agents_online
FROM node_cache nc
WHERE nc.node_id = $1
"""
row = await pool.fetchrow(query, node_id)
return dict(row) if row else None

View File

@@ -23,6 +23,7 @@ from models_city import (
AgentPresence,
AgentSummary,
HomeNodeView,
NodeProfile,
PublicCitizenSummary,
PublicCitizenProfile,
CitizenInteractionInfo,
@@ -115,6 +116,64 @@ async def list_agents(
raise HTTPException(status_code=500, detail="Failed to list agents")
# =============================================================================
# Nodes API (for Node Directory)
# =============================================================================
@public_router.get("/nodes")
async def list_nodes():
"""Список всіх нод мережі"""
try:
nodes = await repo_city.get_all_nodes()
items: List[NodeProfile] = []
for node in nodes:
items.append(NodeProfile(
node_id=node["node_id"],
name=node["name"],
hostname=node.get("hostname"),
roles=list(node.get("roles") or []),
environment=node.get("environment", "unknown"),
status=node.get("status", "offline"),
gpu_info=node.get("gpu"),
agents_total=node.get("agents_total", 0),
agents_online=node.get("agents_online", 0),
last_heartbeat=str(node["last_heartbeat"]) if node.get("last_heartbeat") else None
))
return {"items": items, "total": len(items)}
except Exception as e:
logger.error(f"Failed to list nodes: {e}")
raise HTTPException(status_code=500, detail="Failed to list nodes")
@public_router.get("/nodes/{node_id}")
async def get_node_profile(node_id: str):
"""Отримати профіль ноди"""
try:
node = await repo_city.get_node_by_id(node_id)
if not node:
raise HTTPException(status_code=404, detail="Node not found")
return NodeProfile(
node_id=node["node_id"],
name=node["name"],
hostname=node.get("hostname"),
roles=list(node.get("roles") or []),
environment=node.get("environment", "unknown"),
status=node.get("status", "offline"),
gpu_info=node.get("gpu"),
agents_total=node.get("agents_total", 0),
agents_online=node.get("agents_online", 0),
last_heartbeat=str(node["last_heartbeat"]) if node.get("last_heartbeat") else None
)
except HTTPException:
raise
except Exception as e:
logger.error(f"Failed to get node {node_id}: {e}")
raise HTTPException(status_code=500, detail="Failed to get node")
# =============================================================================
# Public Citizens API
# =============================================================================