379 lines
20 KiB
Python
379 lines
20 KiB
Python
"""MY Network Monitoring Interface - веб-интерфейс мониторинга сети в хакерском стиле."""
|
||
|
||
import asyncio
|
||
import json
|
||
import logging
|
||
from datetime import datetime, timedelta
|
||
from typing import Dict, List, Any
|
||
from fastapi import APIRouter, Request, HTTPException
|
||
from fastapi.responses import HTMLResponse
|
||
from fastapi.templating import Jinja2Templates
|
||
from pathlib import Path
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# Создать router для мониторинга
|
||
router = APIRouter(prefix="/api/my/monitor", tags=["MY Network Monitoring"])
|
||
|
||
# Настроить шаблоны
|
||
templates_dir = Path(__file__).parent.parent.parent / "templates"
|
||
templates_dir.mkdir(exist_ok=True)
|
||
templates = Jinja2Templates(directory=str(templates_dir))
|
||
|
||
|
||
def get_node_service():
|
||
"""Получить сервис ноды."""
|
||
try:
|
||
from app.core.my_network.node_service import get_node_service
|
||
return get_node_service()
|
||
except Exception as e:
|
||
logger.error(f"Error getting node service: {e}")
|
||
return None
|
||
|
||
|
||
@router.get("/", response_class=HTMLResponse)
|
||
async def monitoring_dashboard(request: Request):
|
||
"""Главная страница мониторинга MY Network."""
|
||
try:
|
||
# Получить данные для дашборда
|
||
node_service = get_node_service()
|
||
|
||
if not node_service:
|
||
monitoring_data = {
|
||
"status": "offline",
|
||
"error": "MY Network service not available"
|
||
}
|
||
else:
|
||
# Собрать данные со всех компонентов
|
||
node_info = await node_service.get_node_info()
|
||
peers_info = await node_service.get_peers_info()
|
||
sync_status = await node_service.sync_manager.get_sync_status()
|
||
|
||
monitoring_data = {
|
||
"status": "online",
|
||
"node_info": node_info,
|
||
"peers_info": peers_info,
|
||
"sync_status": sync_status,
|
||
"timestamp": datetime.utcnow().isoformat()
|
||
}
|
||
|
||
return templates.TemplateResponse("my_network_monitor.html", {
|
||
"request": request,
|
||
"monitoring_data": monitoring_data
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error rendering monitoring dashboard: {e}")
|
||
|
||
# Fallback HTML если шаблоны не работают
|
||
return HTMLResponse(content=generate_fallback_html(str(e)))
|
||
|
||
|
||
@router.get("/ascii")
|
||
async def get_ascii_status():
|
||
"""Получить ASCII статус сети."""
|
||
try:
|
||
node_service = get_node_service()
|
||
|
||
if not node_service:
|
||
return {"ascii": generate_offline_ascii(), "status": "offline"}
|
||
|
||
# Получить данные
|
||
node_info = await node_service.get_node_info()
|
||
peers_info = await node_service.get_peers_info()
|
||
sync_status = await node_service.sync_manager.get_sync_status()
|
||
|
||
# Генерировать ASCII
|
||
ascii_art = await generate_network_ascii(node_info, peers_info, sync_status)
|
||
|
||
return {
|
||
"ascii": ascii_art,
|
||
"status": "online",
|
||
"timestamp": datetime.utcnow().isoformat()
|
||
}
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error generating ASCII status: {e}")
|
||
return {"ascii": generate_error_ascii(str(e)), "status": "error"}
|
||
|
||
|
||
@router.get("/live")
|
||
async def live_monitoring_data():
|
||
"""Получить живые данные для мониторинга."""
|
||
try:
|
||
node_service = get_node_service()
|
||
|
||
if not node_service:
|
||
raise HTTPException(status_code=503, detail="MY Network service unavailable")
|
||
|
||
# Получить свежие данные
|
||
node_info = await node_service.get_node_info()
|
||
peers_info = await node_service.get_peers_info()
|
||
sync_status = await node_service.sync_manager.get_sync_status()
|
||
|
||
# Статистика сети
|
||
network_stats = {
|
||
"connected_peers": peers_info["peer_count"],
|
||
"active_syncs": sync_status["active_syncs"],
|
||
"queue_size": sync_status["queue_size"],
|
||
"uptime": node_info["uptime"],
|
||
"status": node_info["status"]
|
||
}
|
||
|
||
return {
|
||
"success": True,
|
||
"data": {
|
||
"node_info": node_info,
|
||
"network_stats": network_stats,
|
||
"peers": peers_info["peers"][:10], # Показать только первые 10 пиров
|
||
"sync_status": sync_status
|
||
},
|
||
"timestamp": datetime.utcnow().isoformat()
|
||
}
|
||
|
||
except HTTPException:
|
||
raise
|
||
except Exception as e:
|
||
logger.error(f"Error getting live monitoring data: {e}")
|
||
raise HTTPException(status_code=500, detail=str(e))
|
||
|
||
|
||
async def generate_network_ascii(node_info: Dict[str, Any], peers_info: Dict[str, Any], sync_status: Dict[str, Any]) -> str:
|
||
"""Генерировать ASCII представление состояния сети."""
|
||
|
||
ascii_parts = []
|
||
|
||
# Заголовок
|
||
ascii_parts.append("""
|
||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||
║ MY NETWORK v2.0 ║
|
||
║ Distributed Content Protocol ║
|
||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||
""")
|
||
|
||
# Информация о ноде
|
||
status_indicator = "🟢" if node_info.get("status") == "running" else "🔴"
|
||
uptime_hours = int(node_info.get("uptime", 0) / 3600)
|
||
|
||
ascii_parts.append(f"""
|
||
┌─ NODE STATUS ────────────────────────────────────────────────────────────────┐
|
||
│ Node ID: {node_info.get('node_id', 'unknown')[:16]}... │
|
||
│ Status: {status_indicator} {node_info.get('status', 'unknown').upper()} │
|
||
│ Uptime: {uptime_hours}h {int((node_info.get('uptime', 0) % 3600) / 60)}m │
|
||
│ Version: MY Network {node_info.get('version', '2.0')} │
|
||
└──────────────────────────────────────────────────────────────────────────────┘
|
||
""")
|
||
|
||
# Информация о пирах
|
||
peer_count = peers_info.get("peer_count", 0)
|
||
peer_status = "🌐" if peer_count > 0 else "🏝️"
|
||
|
||
ascii_parts.append(f"""
|
||
┌─ NETWORK STATUS ─────────────────────────────────────────────────────────────┐
|
||
│ Connected Peers: {peer_status} {peer_count:>3} │
|
||
│ Known Nodes: {len(peers_info.get('peers', [])):>3} │
|
||
│ Network Health: {'CONNECTED' if peer_count > 0 else 'ISOLATED':>9} │
|
||
└──────────────────────────────────────────────────────────────────────────────┘
|
||
""")
|
||
|
||
# Статус синхронизации
|
||
sync_running = sync_status.get("is_running", False)
|
||
active_syncs = sync_status.get("active_syncs", 0)
|
||
queue_size = sync_status.get("queue_size", 0)
|
||
|
||
sync_indicator = "⚡" if sync_running else "⏸️"
|
||
|
||
ascii_parts.append(f"""
|
||
┌─ SYNC STATUS ────────────────────────────────────────────────────────────────┐
|
||
│ Sync Engine: {sync_indicator} {'RUNNING' if sync_running else 'STOPPED':>7} │
|
||
│ Active Syncs: {active_syncs:>3} │
|
||
│ Queue Size: {queue_size:>3} │
|
||
│ Workers: {sync_status.get('workers_count', 0):>3} │
|
||
└──────────────────────────────────────────────────────────────────────────────┘
|
||
""")
|
||
|
||
# Визуализация сети
|
||
if peer_count > 0:
|
||
ascii_parts.append(generate_network_topology(peers_info.get("peers", [])[:6]))
|
||
|
||
# Недавние события синхронизации
|
||
recent_syncs = sync_status.get("recent_syncs", [])
|
||
if recent_syncs:
|
||
ascii_parts.append(generate_sync_history(recent_syncs[-5:]))
|
||
|
||
# Подвал
|
||
current_time = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S UTC")
|
||
ascii_parts.append(f"""
|
||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||
║ Last Updated: {current_time} ║
|
||
║ MY Network Protocol - Decentralized Content Distribution System ║
|
||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||
""")
|
||
|
||
return "".join(ascii_parts)
|
||
|
||
|
||
def generate_network_topology(peers: List[Dict[str, Any]]) -> str:
|
||
"""Генерировать ASCII топологию сети."""
|
||
|
||
topology = ["""
|
||
┌─ NETWORK TOPOLOGY ───────────────────────────────────────────────────────────┐
|
||
│ │
|
||
│ [THIS NODE] │
|
||
│ │ │"""]
|
||
|
||
if len(peers) == 1:
|
||
topology.append("│ │ │")
|
||
topology.append(f"│ [{peers[0].get('node_id', 'unknown')[:8]}...] │")
|
||
elif len(peers) <= 3:
|
||
topology.append("│ ┌───────┼───────┐ │")
|
||
for i, peer in enumerate(peers):
|
||
spaces = " " if i == 0 else (" " if i == 1 else " ")
|
||
topology.append(f"│{spaces}[{peer.get('node_id', 'unknown')[:8]}...] │")
|
||
else:
|
||
topology.append("│ ┌───────┬───────┼───────┬───────┐ │")
|
||
topology.append("│ │ │ │ │ │ │")
|
||
for i, peer in enumerate(peers[:5]):
|
||
if i < 5:
|
||
spaces = [" ", " ", " ", " ", " "][i]
|
||
topology.append(f"│{spaces}[{peer.get('node_id', 'unknown')[:6]}] │")
|
||
if len(peers) > 5:
|
||
topology.append("│ ... │")
|
||
|
||
topology.append("│ │")
|
||
topology.append("└──────────────────────────────────────────────────────────────────────────────┘")
|
||
|
||
return "\n".join(topology) + "\n"
|
||
|
||
|
||
def generate_sync_history(recent_syncs: List[Dict[str, Any]]) -> str:
|
||
"""Генерировать историю синхронизации."""
|
||
|
||
history = ["""
|
||
┌─ RECENT SYNC ACTIVITY ───────────────────────────────────────────────────────┐"""]
|
||
|
||
if not recent_syncs:
|
||
history.append("│ No recent sync activity │")
|
||
else:
|
||
for sync in recent_syncs:
|
||
content_hash = sync.get("content_hash", "unknown")[:12]
|
||
status = sync.get("status", "unknown")
|
||
status_icon = {"completed": "✅", "failed": "❌", "partial": "⚠️"}.get(status, "❓")
|
||
|
||
history.append(f"│ {status_icon} {content_hash}... - {status.upper():>9} │")
|
||
|
||
history.append("└──────────────────────────────────────────────────────────────────────────────┘")
|
||
|
||
return "\n".join(history) + "\n"
|
||
|
||
|
||
def generate_offline_ascii() -> str:
|
||
"""Генерировать ASCII для офлайн состояния."""
|
||
return """
|
||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||
║ MY NETWORK v2.0 ║
|
||
║ Distributed Content Protocol ║
|
||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||
|
||
┌─ SYSTEM STATUS ──────────────────────────────────────────────────────────────┐
|
||
│ │
|
||
│ 🔴 OFFLINE │
|
||
│ │
|
||
│ MY Network service is not available │
|
||
│ │
|
||
└──────────────────────────────────────────────────────────────────────────────┘
|
||
|
||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||
║ Status: OFFLINE - Service not initialized ║
|
||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||
"""
|
||
|
||
|
||
def generate_error_ascii(error_message: str) -> str:
|
||
"""Генерировать ASCII для ошибки."""
|
||
return f"""
|
||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||
║ MY NETWORK v2.0 ║
|
||
║ Distributed Content Protocol ║
|
||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||
|
||
┌─ ERROR STATE ────────────────────────────────────────────────────────────────┐
|
||
│ │
|
||
│ ❌ ERROR │
|
||
│ │
|
||
│ {error_message[:64]:^64} │
|
||
│ │
|
||
└──────────────────────────────────────────────────────────────────────────────┘
|
||
|
||
╔══════════════════════════════════════════════════════════════════════════════╗
|
||
║ Status: ERROR - Check system logs for details ║
|
||
╚══════════════════════════════════════════════════════════════════════════════╝
|
||
"""
|
||
|
||
|
||
def generate_fallback_html(error_message: str = "") -> str:
|
||
"""Генерировать fallback HTML если шаблоны не работают."""
|
||
return f'''
|
||
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>MY Network Monitor</title>
|
||
<style>
|
||
body {{
|
||
background: #000;
|
||
color: #0f0;
|
||
font-family: 'Courier New', monospace;
|
||
margin: 0;
|
||
padding: 20px;
|
||
overflow-x: auto;
|
||
}}
|
||
.container {{
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
}}
|
||
.ascii-art {{
|
||
white-space: pre;
|
||
font-size: 12px;
|
||
line-height: 1.2;
|
||
}}
|
||
.error {{
|
||
color: #f00;
|
||
text-align: center;
|
||
padding: 20px;
|
||
}}
|
||
.refresh-btn {{
|
||
background: #0f0;
|
||
color: #000;
|
||
border: none;
|
||
padding: 10px 20px;
|
||
font-family: inherit;
|
||
cursor: pointer;
|
||
margin: 20px 0;
|
||
}}
|
||
.refresh-btn:hover {{
|
||
background: #fff;
|
||
}}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="ascii-art">
|
||
{generate_error_ascii(error_message) if error_message else generate_offline_ascii()}
|
||
</div>
|
||
|
||
<button class="refresh-btn" onclick="location.reload()">REFRESH SYSTEM STATUS</button>
|
||
|
||
<div class="error">
|
||
{f"Error: {error_message}" if error_message else "MY Network service not available"}
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// Автообновление каждые 30 секунд
|
||
setTimeout(() => location.reload(), 30000);
|
||
</script>
|
||
</body>
|
||
</html>
|
||
''' |