rmi-backend/app/_archive/legacy_2026_07/admin_control.py
cryptorugmunch 628c1d2a10
Some checks failed
CI / build (push) Failing after 3s
refactor(rmi-backend,audit): mount Wave 3 + archive 136 dead-code files (P2.3)
PHASE 2.3 (AUDIT-2026-Q3.md):

Task 1 — Wire-in Wave 3 (1 router mounted, 2 deferred):
  - app.routers.unified_scanner_router mounted at /api/v2/scanner/* (2 routes:
    POST /api/v2/scanner/token/scan, POST /api/v2/scanner/wallet/scan).
    Refactored prefix from /api/v2 -> /api/v2/scanner to avoid future conflicts
    with the v1 /api/v1/scanner/ stub.
  - app.routers.unified_wallet_scanner DEFERRED (no router APIRouter attribute;
    library module consumed by unified_scanner_router via get_wallet_scanner()).
  - app.routers.admin_extensions DEFERRED (DORMANT per audit; 25 routes at
    /api/v1/admin/* would shadow /api/v1/admin/alerts_webhook).

Task 2 — Archive 136 dead-code files to app/_archive/legacy_2026_07/:
  - 73 routers in app/routers/ (reach graph showed zero reach into mount.py).
  - 63 flat app/*.py (domain modules never imported by live code).
  - 1 file RESTORED post-archive: app/routers/x402_bridge_health.py (caught by
    tests/unit/test_bridge_health.py which directly imports it; reach graph
    considered tests/ only as transitive reach — to be patched in next cycle).

Forced-LIVE (NOT archived per user directive):
  - app/ai_pipeline_v3.py  (3 importers in audit window, importers themselves DEAD)
  - app/splade_bm25.py       (LIVE via app.rag_service)
  - app/wallet_manager_v2.py (LIVE via x402_enforcement, x402_tools, sweep_all, sweep_now)
  - app/crypto_embeddings.py (NOT in audit ARCHIVE list; heavy import graph)

Verification (forward-import closure from mount.py + main.py + factory.py + lifespan.py):
  - imports = 348 app.* modules
  - reached = 194 files reachable from roots
  - archive set = audit_dead (186) - reached - forced_live (4) - test_live (1) = 136
  - Net delta: 136 files moved, 44,932 LOC reduction, 293->295 active routes (+2 from Wave 3)

pyproject.toml updates:
  - setuptools.packages.find: added exclude for app._archive*
  - ruff.extend-exclude: added "app/_archive/"
  - mypy.exclude: added "app/_archive/"

Smoke test: pytest tests/ — 817 passed, 3 pre-existing failures unchanged
(0 new failures; 0 routes lost; all 4 forced-LIVE files still importable).

Restoration: git mv app/_archive/legacy_2026_07/<name>.py <original-path>
and add the import to app/mount.py ROUTER_MODULES.

Refs: AUDIT-2026-Q3.md /home/dev/pry/rmi-final-deadcode-2026-07-06.md
2026-07-06 20:52:31 +02:00

263 lines
10 KiB
Python

"""
RMI Backend Control Router
==========================
Admin endpoints for system monitoring, service control, logs,
database management, bot configuration, and alerts.
"""
import logging
import os
from datetime import datetime, timedelta
import psutil
from fastapi import APIRouter, Depends, HTTPException, Request
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/v1/admin", tags=["admin-control"])
# ── Auth helper ──
async def _verify_admin(request: Request):
"""Verify admin access with API key."""
admin_key = os.getenv("ADMIN_API_KEY", "dev-key-change-me")
key = request.headers.get("X-Admin-Key", "")
if key != admin_key:
raise HTTPException(status_code=401, detail="Invalid admin key")
return True
# ═══════════════════════════════════════════════════════════════
# SYSTEM MONITORING
# ═══════════════════════════════════════════════════════════════
@router.get("/system")
async def admin_system(request: Request, _=Depends(_verify_admin)):
"""Get real-time system resource usage."""
try:
cpu_percent = psutil.cpu_percent(interval=0.5)
cpu_count = psutil.cpu_count()
cpu_freq = psutil.cpu_freq()
memory = psutil.virtual_memory()
disk = psutil.disk_usage("/")
net_io = psutil.net_io_counters()
boot_time = psutil.boot_time()
uptime_seconds = int(datetime.now().timestamp() - boot_time)
# Process info for the backend itself
backend_pid = os.getpid()
backend_proc = psutil.Process(backend_pid)
backend_memory_mb = backend_proc.memory_info().rss / (1024 * 1024)
backend_cpu = backend_proc.cpu_percent(interval=0.2)
return {
"timestamp": datetime.utcnow().isoformat(),
"cpu": {
"percent": cpu_percent,
"count": cpu_count,
"frequency_mhz": cpu_freq.current if cpu_freq else None,
},
"memory": {
"total_gb": round(memory.total / (1024**3), 2),
"available_gb": round(memory.available / (1024**3), 2),
"percent": memory.percent,
"used_gb": round(memory.used / (1024**3), 2),
},
"disk": {
"total_gb": round(disk.total / (1024**3), 2),
"used_gb": round(disk.used / (1024**3), 2),
"free_gb": round(disk.free / (1024**3), 2),
"percent": round(disk.used / disk.total * 100, 1),
},
"network": {
"bytes_sent_mb": round(net_io.bytes_sent / (1024**2), 2),
"bytes_recv_mb": round(net_io.bytes_recv / (1024**2), 2),
"packets_sent": net_io.packets_sent,
"packets_recv": net_io.packets_recv,
},
"uptime": {
"seconds": uptime_seconds,
"formatted": str(timedelta(seconds=uptime_seconds)),
},
"backend_process": {
"pid": backend_pid,
"memory_mb": round(backend_memory_mb, 2),
"cpu_percent": backend_cpu,
"threads": backend_proc.num_threads(),
},
"services": {
"redis": _check_redis(),
"supabase": _check_supabase(),
},
}
except Exception as e:
logger.error(f"System check failed: {e}")
return {"error": str(e)}
def _check_redis():
"""Check Redis connection."""
try:
import redis.asyncio as redis
r = redis.Redis(
host=os.getenv("REDIS_HOST", "localhost"),
port=int(os.getenv("REDIS_PORT", "6379")),
password=os.getenv("REDIS_PASSWORD", ""),
decode_responses=True,
)
r.ping()
return {"status": "connected"}
except Exception as e:
return {"status": "error", "error": str(e)}
def _check_supabase():
"""Check Supabase connection."""
supabase_url = os.getenv("SUPABASE_URL")
if not supabase_url:
return {"status": "not_configured"}
return {"status": "connected", "url": supabase_url}
# ═══════════════════════════════════════════════════════════════
# DATABASE MANAGEMENT (stubbed - uses Redis instead of Supabase)
# ═══════════════════════════════════════════════════════════════
@router.get("/db/tables")
async def admin_db_tables(request: Request, _=Depends(_verify_admin)):
"""List available tables (stubbed - uses Redis instead of Supabase)."""
return {
"tables": [
{"name": "wallet_users", "type": "redis", "rows": "dynamic"},
{"name": "agents", "type": "redis", "rows": "dynamic"},
{"name": "alerts", "type": "redis", "rows": "dynamic"},
{"name": "scans", "type": "redis", "rows": "dynamic"},
{"name": "evidence", "type": "redis", "rows": "dynamic"},
]
}
@router.post("/db/query")
async def admin_db_query(request: Request, _=Depends(_verify_admin)):
"""Execute SQL query (stubbed - redirect to Redis operations)."""
return {
"error": "Direct SQL queries not supported. Use Redis operations instead.",
"hint": "For data operations, use Redis endpoints in /api/v1/agents",
}
# ═══════════════════════════════════════════════════════════════
# SERVICE CONTROL
# ═══════════════════════════════════════════════════════════════
@router.post("/service/restart")
async def admin_service_restart(request: Request, _=Depends(_verify_admin)):
"""Restart the backend service."""
return {
"action": "restart",
"status": "queued",
"message": "Service restart queued - use /api/v1/admin/service/status for updates",
}
@router.get("/service/status")
async def admin_service_status(request: Request, _=Depends(_verify_admin)):
"""Get service status."""
return {
"service": "rmi-backend",
"status": "running",
"pid": os.getpid(),
"port": 8013,
"version": "1.0.0",
}
# ═══════════════════════════════════════════════════════════════
# LOGS
# ═══════════════════════════════════════════════════════════════
@router.get("/logs")
async def admin_logs(request: Request, _=Depends(_verify_admin), limit: int = 100):
"""Get recent logs."""
log_file = "/tmp/backend.log"
if not os.path.exists(log_file):
return {"logs": [], "error": "Log file not found"}
try:
with open(log_file) as f:
lines = f.readlines()[-limit:]
return {"logs": [line.rstrip() for line in lines]}
except Exception as e:
return {"error": str(e)}
# ═══════════════════════════════════════════════════════════════
# BOT CONFIGURATION
# ═══════════════════════════════════════════════════════════════
@router.get("/bot/config")
async def admin_bot_config(request: Request, _=Depends(_verify_admin)):
"""Get bot configuration."""
return {
"telegram": {
"enabled": bool(os.getenv("TELEGRAM_BOT_TOKEN")),
"channel": os.getenv("TELEGRAM_ALERTS_CHANNEL", ""),
"rate_limit": 5,
"rate_limit_window": 60,
},
"webhook": {
"urls": [os.getenv("ALERT_WEBHOOK_URL", ""), os.getenv("SLACK_ALERT_WEBHOOK", "")],
},
}
# ═══════════════════════════════════════════════════════════════
# AGENT MANAGEMENT
# ═══════════════════════════════════════════════════════════════
@router.get("/agents")
async def admin_agents(request: Request, _=Depends(_verify_admin)):
"""List available agents."""
return {
"agents": [
{"id": "agent-0", "name": "solana-monitor", "status": "running"},
{"id": "agent-1", "name": "ethereum-monitor", "status": "running"},
{"id": "agent-2", "name": "arbitrum-monitor", "status": "running"},
{"id": "agent-3", "name": "base-monitor", "status": "running"},
]
}
@router.get("/agents/{agent_id}")
async def admin_agent_detail(request: Request, agent_id: str, _=Depends(_verify_admin)):
"""Get agent details."""
return {
"id": agent_id,
"name": f"agent-{agent_id}",
"status": "running",
"stats": {"transactions_processed": 0, "alerts_triggered": 0},
}
# ═══════════════════════════════════════════════════════════════
# HEALTH & READY
# ═══════════════════════════════════════════════════════════════
@router.get("/health")
async def admin_health(request: Request):
"""Check backend health."""
return {"status": "ok"}
@router.get("/ready")
async def admin_ready(request: Request):
"""Check backend readiness."""
return {"status": "ready"}