452 lines
20 KiB
Python
452 lines
20 KiB
Python
"""MY Network Monitoring Sanic Blueprint - веб-интерфейс мониторинга сети."""
|
||
|
||
import asyncio
|
||
import json
|
||
import logging
|
||
from datetime import datetime, timedelta
|
||
from typing import Dict, List, Any
|
||
from pathlib import Path
|
||
|
||
from sanic import Blueprint, Request
|
||
from sanic.response import json as json_response, html as html_response
|
||
from sanic.exceptions import SanicException
|
||
|
||
from app.core.logging import get_logger
|
||
|
||
logger = get_logger(__name__)
|
||
|
||
# Создать blueprint для мониторинга
|
||
bp = Blueprint("my_monitoring", url_prefix="/api/my/monitor")
|
||
|
||
|
||
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
|
||
|
||
|
||
@bp.get("/")
|
||
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()
|
||
}
|
||
|
||
# Попробовать использовать шаблон
|
||
try:
|
||
from jinja2 import Environment, FileSystemLoader
|
||
|
||
# Настроить Jinja2
|
||
templates_dir = Path(__file__).parent.parent.parent / "templates"
|
||
if templates_dir.exists():
|
||
env = Environment(loader=FileSystemLoader(str(templates_dir)))
|
||
template = env.get_template("my_network_monitor.html")
|
||
|
||
html_content = template.render(monitoring_data=monitoring_data)
|
||
return html_response(html_content)
|
||
|
||
except Exception as e:
|
||
logger.warning(f"Template rendering failed: {e}")
|
||
|
||
# Fallback HTML если шаблоны не работают
|
||
return html_response(generate_fallback_html(monitoring_data))
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error rendering monitoring dashboard: {e}")
|
||
return html_response(generate_fallback_html({"status": "error", "error": str(e)}))
|
||
|
||
|
||
@bp.get("/ascii")
|
||
async def get_ascii_status(request: Request):
|
||
"""Получить ASCII статус сети."""
|
||
try:
|
||
node_service = get_node_service()
|
||
|
||
if not node_service:
|
||
return json_response({"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 json_response({
|
||
"ascii": ascii_art,
|
||
"status": "online",
|
||
"timestamp": datetime.utcnow().isoformat()
|
||
})
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error generating ASCII status: {e}")
|
||
return json_response({"ascii": generate_error_ascii(str(e)), "status": "error"})
|
||
|
||
|
||
@bp.get("/live")
|
||
async def live_monitoring_data(request: Request):
|
||
"""Получить живые данные для мониторинга."""
|
||
try:
|
||
node_service = get_node_service()
|
||
|
||
if not node_service:
|
||
return json_response(
|
||
{"error": "MY Network service unavailable"},
|
||
status=503
|
||
)
|
||
|
||
# Получить свежие данные
|
||
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 json_response({
|
||
"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 Exception as e:
|
||
logger.error(f"Error getting live monitoring data: {e}")
|
||
return json_response({"error": str(e)}, status=500)
|
||
|
||
|
||
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} │
|
||
└──────────────────────────────────────────────────────────────────────────────┘
|
||
""")
|
||
|
||
# Подвал
|
||
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_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(monitoring_data: Dict[str, Any]) -> str:
|
||
"""Генерировать fallback HTML если шаблоны не работают."""
|
||
|
||
status = monitoring_data.get("status", "unknown")
|
||
error_message = monitoring_data.get("error", "")
|
||
|
||
# Генерировать информацию о статусе
|
||
if status == "online":
|
||
node_info = monitoring_data.get("node_info", {})
|
||
peers_info = monitoring_data.get("peers_info", {})
|
||
sync_status = monitoring_data.get("sync_status", {})
|
||
|
||
status_info = f"""
|
||
<div class="status-section">
|
||
<h3>Node Status</h3>
|
||
<ul>
|
||
<li>Node ID: {node_info.get('node_id', 'unknown')[:16]}...</li>
|
||
<li>Status: {node_info.get('status', 'unknown').upper()}</li>
|
||
<li>Uptime: {int(node_info.get('uptime', 0) / 3600)}h {int((node_info.get('uptime', 0) % 3600) / 60)}m</li>
|
||
<li>Version: MY Network {node_info.get('version', '2.0')}</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="status-section">
|
||
<h3>Network Status</h3>
|
||
<ul>
|
||
<li>Connected Peers: {peers_info.get('peer_count', 0)}</li>
|
||
<li>Known Nodes: {len(peers_info.get('peers', []))}</li>
|
||
<li>Network Health: {'CONNECTED' if peers_info.get('peer_count', 0) > 0 else 'ISOLATED'}</li>
|
||
</ul>
|
||
</div>
|
||
|
||
<div class="status-section">
|
||
<h3>Sync Status</h3>
|
||
<ul>
|
||
<li>Sync Engine: {'RUNNING' if sync_status.get('is_running', False) else 'STOPPED'}</li>
|
||
<li>Active Syncs: {sync_status.get('active_syncs', 0)}</li>
|
||
<li>Queue Size: {sync_status.get('queue_size', 0)}</li>
|
||
<li>Workers: {sync_status.get('workers_count', 0)}</li>
|
||
</ul>
|
||
</div>
|
||
"""
|
||
else:
|
||
status_info = f"""
|
||
<div class="error-section">
|
||
<h3>Status: {status.upper()}</h3>
|
||
<p>{error_message if error_message else 'MY Network service not available'}</p>
|
||
</div>
|
||
"""
|
||
|
||
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: linear-gradient(135deg, #0a0a0a 0%, #1a1a2e 50%, #16213e 100%);
|
||
color: #00ff41;
|
||
font-family: 'Courier New', monospace;
|
||
margin: 0;
|
||
padding: 20px;
|
||
min-height: 100vh;
|
||
}}
|
||
|
||
.container {{
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
}}
|
||
|
||
.header {{
|
||
text-align: center;
|
||
margin-bottom: 30px;
|
||
padding: 20px;
|
||
border: 2px solid #00ff41;
|
||
border-radius: 10px;
|
||
background: rgba(0, 255, 65, 0.05);
|
||
}}
|
||
|
||
.header h1 {{
|
||
font-size: 2.5em;
|
||
text-shadow: 0 0 10px #00ff41;
|
||
margin: 0;
|
||
}}
|
||
|
||
.status-section {{
|
||
background: rgba(0, 0, 0, 0.7);
|
||
border: 1px solid #00ff41;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
margin: 20px 0;
|
||
}}
|
||
|
||
.status-section h3 {{
|
||
color: #00ff41;
|
||
margin-bottom: 15px;
|
||
text-transform: uppercase;
|
||
border-bottom: 1px solid #00ff41;
|
||
padding-bottom: 5px;
|
||
}}
|
||
|
||
.status-section ul {{
|
||
list-style: none;
|
||
padding: 0;
|
||
}}
|
||
|
||
.status-section li {{
|
||
margin: 10px 0;
|
||
padding: 5px 0;
|
||
border-bottom: 1px dotted #333;
|
||
}}
|
||
|
||
.error-section {{
|
||
background: rgba(255, 0, 0, 0.1);
|
||
border: 1px solid #ff0000;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
margin: 20px 0;
|
||
text-align: center;
|
||
}}
|
||
|
||
.error-section h3 {{
|
||
color: #ff0000;
|
||
margin-bottom: 15px;
|
||
}}
|
||
|
||
.controls {{
|
||
text-align: center;
|
||
margin: 30px 0;
|
||
}}
|
||
|
||
.btn {{
|
||
background: linear-gradient(45deg, #00ff41, #00cc33);
|
||
color: #000;
|
||
border: none;
|
||
padding: 12px 24px;
|
||
font-family: inherit;
|
||
font-weight: bold;
|
||
cursor: pointer;
|
||
border-radius: 5px;
|
||
text-transform: uppercase;
|
||
margin: 0 10px;
|
||
text-decoration: none;
|
||
display: inline-block;
|
||
}}
|
||
|
||
.btn:hover {{
|
||
background: linear-gradient(45deg, #00cc33, #00ff41);
|
||
}}
|
||
|
||
.footer {{
|
||
text-align: center;
|
||
margin-top: 40px;
|
||
padding: 20px;
|
||
border-top: 1px solid #00ff41;
|
||
color: #888;
|
||
}}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="container">
|
||
<div class="header">
|
||
<h1>MY NETWORK MONITOR</h1>
|
||
<p>Distributed Content Protocol v2.0</p>
|
||
<p>Last Update: {datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S UTC')}</p>
|
||
</div>
|
||
|
||
{status_info}
|
||
|
||
<div class="controls">
|
||
<a href="/api/my/monitor/" class="btn">🔄 REFRESH</a>
|
||
<a href="/api/my/monitor/ascii" class="btn">📊 ASCII VIEW</a>
|
||
<a href="/api/my/node/info" class="btn">ℹ️ NODE INFO</a>
|
||
<a href="/api/my/health" class="btn">❤️ HEALTH</a>
|
||
</div>
|
||
|
||
<div class="footer">
|
||
<p>MY Network Protocol - Decentralized Content Distribution System</p>
|
||
<p>Real-time monitoring dashboard</p>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
// Автообновление каждые 30 секунд
|
||
setTimeout(() => location.reload(), 30000);
|
||
</script>
|
||
</body>
|
||
</html>
|
||
''' |