6720ca3474
主服务器列表操作列一键登录宝塔;SSH 健康检查写入 Redis 心跳并刷新在线状态。 Co-authored-by: Cursor <cursoragent@cursor.com>
80 lines
3.0 KiB
Python
80 lines
3.0 KiB
Python
"""Nexus — Server Service (Business logic for server management)
|
|
Application layer — orchestrates Repository + SSH pool + Redis cache.
|
|
"""
|
|
|
|
import asyncio
|
|
import json
|
|
import logging
|
|
from typing import Optional, List, Dict
|
|
|
|
from server.domain.models import Server
|
|
from server.domain.repositories import ServerRepository, SyncLogRepository
|
|
|
|
logger = logging.getLogger("nexus.server_service")
|
|
|
|
|
|
class ServerService:
|
|
"""Business logic for server CRUD, health check, and push operations"""
|
|
|
|
def __init__(self, server_repo: ServerRepository, sync_log_repo: SyncLogRepository):
|
|
self.server_repo = server_repo
|
|
self.sync_log_repo = sync_log_repo
|
|
|
|
async def get_server(self, id: int) -> Optional[Server]:
|
|
return await self.server_repo.get_by_id(id)
|
|
|
|
async def create_server(self, server: Server) -> Server:
|
|
return await self.server_repo.create(server)
|
|
|
|
async def update_server(self, server: Server) -> Server:
|
|
return await self.server_repo.update(server)
|
|
|
|
async def delete_server(self, id: int) -> bool:
|
|
return await self.server_repo.delete(id)
|
|
|
|
async def update_heartbeat(self, id: int, data: dict) -> None:
|
|
"""Process Agent heartbeat data"""
|
|
is_online = data.get("is_online", True)
|
|
system_info = json.dumps(data.get("system_info", {}))
|
|
agent_version = data.get("agent_version", "")
|
|
await self.server_repo.update_heartbeat(id, is_online, system_info, agent_version)
|
|
|
|
async def check_all_servers(self, server_ids: List[int]) -> Dict[int, dict]:
|
|
"""Check health of multiple servers via SSH (no Agent port 8601 required)."""
|
|
from server.application.server_connectivity import write_ssh_health_heartbeat
|
|
from server.infrastructure.redis.client import get_redis
|
|
from server.infrastructure.ssh.remote_probe import ssh_health_probe
|
|
|
|
results = {}
|
|
redis = get_redis()
|
|
|
|
async def _check_one(server: Server) -> tuple:
|
|
try:
|
|
probe = await ssh_health_probe(server, timeout=15)
|
|
try:
|
|
last_hb = await write_ssh_health_heartbeat(redis, server, probe)
|
|
probe = {**probe, "last_heartbeat": last_hb}
|
|
except Exception as redis_err:
|
|
logger.warning(
|
|
"health check heartbeat write failed server=%s: %s",
|
|
server.id,
|
|
redis_err,
|
|
)
|
|
return server.id, probe
|
|
except Exception as e:
|
|
return server.id, {"status": "offline", "error": str(e)[:200], "channel": "ssh"}
|
|
|
|
servers = []
|
|
for sid in server_ids:
|
|
server = await self.server_repo.get_by_id(sid)
|
|
if server:
|
|
servers.append(server)
|
|
else:
|
|
results[sid] = {"status": "not_found"}
|
|
|
|
if servers:
|
|
check_results = await asyncio.gather(*[_check_one(s) for s in servers])
|
|
for sid, result in check_results:
|
|
results[sid] = result
|
|
|
|
return results |