feat: add /public/nodes endpoints for Node Directory
This commit is contained in:
@@ -200,6 +200,20 @@ class HomeNodeView(BaseModel):
|
|||||||
environment: Optional[str] = None
|
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):
|
class AgentSummary(BaseModel):
|
||||||
"""Agent summary for Agent Console"""
|
"""Agent summary for Agent Console"""
|
||||||
id: str
|
id: str
|
||||||
|
|||||||
@@ -1087,3 +1087,55 @@ async def get_microdao_by_slug(slug: str) -> Optional[dict]:
|
|||||||
|
|
||||||
return result
|
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
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ from models_city import (
|
|||||||
AgentPresence,
|
AgentPresence,
|
||||||
AgentSummary,
|
AgentSummary,
|
||||||
HomeNodeView,
|
HomeNodeView,
|
||||||
|
NodeProfile,
|
||||||
PublicCitizenSummary,
|
PublicCitizenSummary,
|
||||||
PublicCitizenProfile,
|
PublicCitizenProfile,
|
||||||
CitizenInteractionInfo,
|
CitizenInteractionInfo,
|
||||||
@@ -115,6 +116,64 @@ async def list_agents(
|
|||||||
raise HTTPException(status_code=500, detail="Failed to 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
|
# Public Citizens API
|
||||||
# =============================================================================
|
# =============================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user