upd
This commit is contained in:
1 parent
373c832e71
commit
1b0dfdafbc
48 files changed
+3114
-396
No files matched your search
@@ -10,11 +10,11 @@ from uuid import UUID, uuid4
|
||||
|
||||
from sanic import Blueprint, Request, response
|
||||
from sanic.response import JSONResponse
|
||||
from sqlalchemy import select, update, and_
|
||||
from sqlalchemy import select, update, and_, or_
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from app.core.config import get_settings
|
||||
from app.core.database import get_async_session, get_cache_manager
|
||||
from app.core.database import db_manager, get_cache_manager
|
||||
from app.core.logging import get_logger
|
||||
from app.core.models.user import User, UserSession, UserRole
|
||||
from app.core.security import (
|
||||
@@ -55,7 +55,7 @@ async def register_user(request: Request) -> JSONResponse:
|
||||
email = sanitize_input(data["email"])
|
||||
full_name = sanitize_input(data.get("full_name", ""))
|
||||
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
# Check if username already exists
|
||||
username_stmt = select(User).where(User.username == username)
|
||||
username_result = await session.execute(username_stmt)
|
||||
@@ -130,7 +130,7 @@ async def register_user(request: Request) -> JSONResponse:
|
||||
session_id = str(uuid4())
|
||||
csrf_token = generate_csrf_token(new_user.id, session_id)
|
||||
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
user_session = UserSession(
|
||||
id=UUID(session_id),
|
||||
user_id=new_user.id,
|
||||
@@ -214,7 +214,7 @@ async def login_user(request: Request) -> JSONResponse:
|
||||
status=429
|
||||
)
|
||||
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
# Find user by username or email
|
||||
user_stmt = select(User).where(
|
||||
or_(User.username == username_or_email, User.email == username_or_email)
|
||||
@@ -281,7 +281,7 @@ async def login_user(request: Request) -> JSONResponse:
|
||||
if remember_me:
|
||||
refresh_expires *= 2 # Longer refresh for remember me
|
||||
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
user_session = UserSession(
|
||||
id=UUID(session_id),
|
||||
user_id=user.id,
|
||||
@@ -365,7 +365,7 @@ async def refresh_tokens(request: Request) -> JSONResponse:
|
||||
|
||||
user_id = UUID(payload["user_id"])
|
||||
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
# Verify session exists and is valid
|
||||
session_stmt = select(UserSession).where(
|
||||
and_(
|
||||
@@ -456,7 +456,7 @@ async def logout_user(request: Request) -> JSONResponse:
|
||||
session_id = request.headers.get("X-Session-ID")
|
||||
|
||||
if session_id:
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
# Invalidate specific session
|
||||
session_stmt = select(UserSession).where(
|
||||
and_(
|
||||
@@ -509,7 +509,7 @@ async def get_current_user(request: Request) -> JSONResponse:
|
||||
try:
|
||||
user = request.ctx.user
|
||||
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
# Get user with full details
|
||||
user_stmt = select(User).where(User.id == user.id).options(
|
||||
selectinload(User.roles),
|
||||
@@ -604,7 +604,7 @@ async def update_current_user(request: Request) -> JSONResponse:
|
||||
user_id = request.ctx.user.id
|
||||
data = request.json
|
||||
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
# Get current user
|
||||
user_stmt = select(User).where(User.id == user_id)
|
||||
user_result = await session.execute(user_stmt)
|
||||
@@ -705,7 +705,7 @@ async def create_api_key(request: Request) -> JSONResponse:
|
||||
int((datetime.fromisoformat(data["expires_at"]) - datetime.utcnow()).total_seconds())
|
||||
)
|
||||
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
from app.core.models.user import ApiKey
|
||||
|
||||
# Create API key record
|
||||
@@ -769,7 +769,7 @@ async def get_user_sessions(request: Request) -> JSONResponse:
|
||||
try:
|
||||
user_id = request.ctx.user.id
|
||||
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
sessions_stmt = select(UserSession).where(
|
||||
and_(
|
||||
UserSession.user_id == user_id,
|
||||
@@ -826,7 +826,7 @@ async def revoke_session(request: Request, session_id: UUID) -> JSONResponse:
|
||||
try:
|
||||
user_id = request.ctx.user.id
|
||||
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
session_stmt = select(UserSession).where(
|
||||
and_(
|
||||
UserSession.id == session_id,
|
||||
|
||||
@@ -14,7 +14,7 @@ from sanic.response import JSONResponse
|
||||
from sqlalchemy import select, update, and_
|
||||
|
||||
from app.core.config import get_settings
|
||||
from app.core.database import get_async_session, get_cache_manager
|
||||
from app.core.database import db_manager, get_cache_manager
|
||||
from app.core.logging import get_logger
|
||||
from app.core.models.user import User
|
||||
from app.api.middleware import require_auth, validate_request, rate_limit
|
||||
@@ -54,7 +54,7 @@ async def get_wallet_balance(request: Request) -> JSONResponse:
|
||||
"updated_at": cached_balance.get("updated_at")
|
||||
})
|
||||
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
# Get user wallet address
|
||||
user_stmt = select(User).where(User.id == user_id)
|
||||
user_result = await session.execute(user_stmt)
|
||||
@@ -130,7 +130,7 @@ async def get_wallet_transactions(request: Request) -> JSONResponse:
|
||||
limit = min(int(request.args.get("limit", 20)), 100) # Max 100 transactions
|
||||
offset = max(int(request.args.get("offset", 0)), 0)
|
||||
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
# Get user wallet address
|
||||
user_stmt = select(User).where(User.id == user_id)
|
||||
user_result = await session.execute(user_stmt)
|
||||
@@ -225,7 +225,7 @@ async def send_transaction(request: Request) -> JSONResponse:
|
||||
user_id = request.ctx.user.id
|
||||
data = request.json
|
||||
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
# Get user with wallet
|
||||
user_stmt = select(User).where(User.id == user_id)
|
||||
user_result = await session.execute(user_stmt)
|
||||
@@ -292,7 +292,7 @@ async def send_transaction(request: Request) -> JSONResponse:
|
||||
|
||||
# Store transaction record
|
||||
from app.core.models.blockchain import BlockchainTransaction
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
tx_record = BlockchainTransaction(
|
||||
id=uuid4(),
|
||||
user_id=user_id,
|
||||
@@ -373,7 +373,7 @@ async def get_transaction_status(request: Request, tx_hash: str) -> JSONResponse
|
||||
return response.json(cached_status)
|
||||
|
||||
# Get transaction from database
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
from app.core.models.blockchain import BlockchainTransaction
|
||||
|
||||
tx_stmt = select(BlockchainTransaction).where(
|
||||
@@ -425,7 +425,7 @@ async def get_transaction_status(request: Request, tx_hash: str) -> JSONResponse
|
||||
|
||||
# Update database record if status changed
|
||||
if tx_record.status != new_status:
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
update_stmt = (
|
||||
update(BlockchainTransaction)
|
||||
.where(BlockchainTransaction.id == tx_record.id)
|
||||
@@ -473,7 +473,7 @@ async def create_wallet(request: Request) -> JSONResponse:
|
||||
try:
|
||||
user_id = request.ctx.user.id
|
||||
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
# Check if user already has a wallet
|
||||
user_stmt = select(User).where(User.id == user_id)
|
||||
user_result = await session.execute(user_stmt)
|
||||
@@ -558,7 +558,7 @@ async def get_blockchain_stats(request: Request) -> JSONResponse:
|
||||
try:
|
||||
user_id = request.ctx.user.id
|
||||
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
from sqlalchemy import func
|
||||
from app.core.models.blockchain import BlockchainTransaction
|
||||
|
||||
|
||||
@@ -14,9 +14,10 @@ from sqlalchemy import select, update, delete, and_, or_
|
||||
from sqlalchemy.orm import selectinload
|
||||
|
||||
from app.core.config import get_settings
|
||||
from app.core.database import get_async_session, get_cache_manager
|
||||
from app.core.database import db_manager, get_cache_manager
|
||||
from app.core.logging import get_logger
|
||||
from app.core.models.content import Content, ContentMetadata, ContentAccess, License
|
||||
from app.core.models.content_models import StoredContent as Content, UserContent as ContentMetadata, EncryptionKey as License
|
||||
from app.core.models.content.user_content import UserContent as ContentAccess
|
||||
from app.core.models.user import User
|
||||
from app.api.middleware import require_auth, validate_request, rate_limit
|
||||
from app.core.validation import ContentSchema, ContentUpdateSchema, ContentSearchSchema
|
||||
@@ -46,7 +47,7 @@ async def create_content(request: Request) -> JSONResponse:
|
||||
data = request.json
|
||||
user_id = request.ctx.user.id
|
||||
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
# Check user upload quota
|
||||
quota_key = f"user:{user_id}:upload_quota"
|
||||
cache_manager = get_cache_manager()
|
||||
@@ -165,7 +166,7 @@ async def get_content(request: Request, content_id: UUID) -> JSONResponse:
|
||||
status=403
|
||||
)
|
||||
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
# Load content with relationships
|
||||
stmt = (
|
||||
select(Content)
|
||||
@@ -255,7 +256,7 @@ async def update_content(request: Request, content_id: UUID) -> JSONResponse:
|
||||
data = request.json
|
||||
user_id = request.ctx.user.id
|
||||
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
# Load existing content
|
||||
stmt = select(Content).where(Content.id == content_id)
|
||||
result = await session.execute(stmt)
|
||||
@@ -338,7 +339,7 @@ async def search_content(request: Request) -> JSONResponse:
|
||||
if cached_results:
|
||||
return response.json(cached_results)
|
||||
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
# Build base query
|
||||
stmt = select(Content).where(
|
||||
or_(
|
||||
@@ -455,7 +456,7 @@ async def download_content(request: Request, content_id: UUID) -> ResponseStream
|
||||
try:
|
||||
user_id = request.ctx.user.id
|
||||
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
# Load content
|
||||
stmt = select(Content).where(Content.id == content_id)
|
||||
result = await session.execute(stmt)
|
||||
@@ -525,7 +526,7 @@ async def _check_content_access(content_id: UUID, user_id: UUID, action: str) ->
|
||||
if cached_access is not None:
|
||||
return cached_access
|
||||
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
stmt = select(Content).where(Content.id == content_id)
|
||||
result = await session.execute(stmt)
|
||||
content = result.scalar_one_or_none()
|
||||
|
||||
@@ -9,7 +9,7 @@ from sanic import Blueprint, Request, response
|
||||
from sanic.response import JSONResponse
|
||||
|
||||
from app.core.config import get_settings
|
||||
from app.core.database import get_async_session
|
||||
from app.core.database import db_manager
|
||||
from app.core.metrics import get_metrics, get_metrics_content_type, metrics_collector
|
||||
from app.core.background.indexer_service import indexer_service
|
||||
from app.core.background.convert_service import convert_service
|
||||
@@ -46,7 +46,7 @@ async def detailed_health_check(request: Request) -> JSONResponse:
|
||||
|
||||
# Database health
|
||||
try:
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
await session.execute("SELECT 1")
|
||||
health_status["components"]["database"] = {
|
||||
"status": "healthy",
|
||||
@@ -120,7 +120,7 @@ async def readiness_check(request: Request) -> JSONResponse:
|
||||
"""Kubernetes readiness probe endpoint."""
|
||||
try:
|
||||
# Quick database check
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
await session.execute("SELECT 1")
|
||||
|
||||
return response.json({
|
||||
|
||||
@@ -0,0 +1,844 @@
|
||||
"""
|
||||
Advanced monitoring routes for MY Network
|
||||
"""
|
||||
import asyncio
|
||||
import psutil
|
||||
import time
|
||||
from datetime import datetime
|
||||
from typing import Dict, List, Any
|
||||
from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
import json
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
router = APIRouter(prefix="/api/my/monitor", tags=["monitoring"])
|
||||
|
||||
# Store connected websocket clients
|
||||
connected_clients: List[WebSocket] = []
|
||||
|
||||
# Simulated network nodes data
|
||||
network_nodes = [
|
||||
{
|
||||
"id": "node_001_local_dev",
|
||||
"name": "Primary Development Node",
|
||||
"status": "online",
|
||||
"location": "Local Development",
|
||||
"uptime": "2h 15m",
|
||||
"connections": 8,
|
||||
"data_synced": "95%",
|
||||
"last_seen": datetime.now().isoformat(),
|
||||
"ip": "127.0.0.1:15100",
|
||||
"version": "2.0.0"
|
||||
},
|
||||
{
|
||||
"id": "node_002_production",
|
||||
"name": "Production Node Alpha",
|
||||
"status": "online",
|
||||
"location": "Cloud Server US-East",
|
||||
"uptime": "15d 8h",
|
||||
"connections": 42,
|
||||
"data_synced": "100%",
|
||||
"last_seen": datetime.now().isoformat(),
|
||||
"ip": "198.51.100.10:15100",
|
||||
"version": "2.0.0"
|
||||
},
|
||||
{
|
||||
"id": "node_003_backup",
|
||||
"name": "Backup Node Beta",
|
||||
"status": "maintenance",
|
||||
"location": "Cloud Server EU-West",
|
||||
"uptime": "3d 2h",
|
||||
"connections": 0,
|
||||
"data_synced": "78%",
|
||||
"last_seen": datetime.now().isoformat(),
|
||||
"ip": "203.0.113.20:15100",
|
||||
"version": "1.9.8"
|
||||
},
|
||||
{
|
||||
"id": "node_004_edge",
|
||||
"name": "Edge Node Gamma",
|
||||
"status": "connecting",
|
||||
"location": "CDN Edge Node",
|
||||
"uptime": "12m",
|
||||
"connections": 3,
|
||||
"data_synced": "12%",
|
||||
"last_seen": datetime.now().isoformat(),
|
||||
"ip": "192.0.2.30:15100",
|
||||
"version": "2.0.0"
|
||||
}
|
||||
]
|
||||
|
||||
@router.get("/")
|
||||
async def advanced_monitoring_dashboard():
|
||||
"""Serve the advanced monitoring dashboard"""
|
||||
dashboard_html = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>MY Network - Advanced Monitor</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #000;
|
||||
color: #00ff00;
|
||||
font-family: 'Courier New', monospace;
|
||||
overflow-x: hidden;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.matrix-bg {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
opacity: 0.1;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 20px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
border: 2px solid #00ff00;
|
||||
padding: 20px;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 2.5rem;
|
||||
text-shadow: 0 0 10px #00ff00;
|
||||
animation: glow 2s ease-in-out infinite alternate;
|
||||
}
|
||||
|
||||
.header .subtitle {
|
||||
font-size: 1.2rem;
|
||||
margin-top: 10px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
border: 1px solid #00ff00;
|
||||
padding: 20px;
|
||||
background: rgba(0, 50, 0, 0.3);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.stat-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background: linear-gradient(90deg, transparent, #00ff00, transparent);
|
||||
animation: scan 3s linear infinite;
|
||||
}
|
||||
|
||||
.stat-title {
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.stat-value {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
text-shadow: 0 0 5px #00ff00;
|
||||
}
|
||||
|
||||
.nodes-section {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 20px;
|
||||
border-bottom: 2px solid #00ff00;
|
||||
padding-bottom: 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.nodes-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.node-card {
|
||||
border: 1px solid #00ff00;
|
||||
padding: 20px;
|
||||
background: rgba(0, 50, 0, 0.2);
|
||||
position: relative;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.node-card:hover {
|
||||
background: rgba(0, 100, 0, 0.3);
|
||||
box-shadow: 0 0 20px rgba(0, 255, 0, 0.3);
|
||||
}
|
||||
|
||||
.node-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.node-name {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.node-status {
|
||||
padding: 5px 10px;
|
||||
border-radius: 3px;
|
||||
font-size: 0.9rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.status-online {
|
||||
background: rgba(0, 255, 0, 0.3);
|
||||
border: 1px solid #00ff00;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
.status-maintenance {
|
||||
background: rgba(255, 165, 0, 0.3);
|
||||
border: 1px solid #ffa500;
|
||||
color: #ffa500;
|
||||
}
|
||||
|
||||
.status-connecting {
|
||||
background: rgba(255, 255, 0, 0.3);
|
||||
border: 1px solid #ffff00;
|
||||
color: #ffff00;
|
||||
animation: blink 1s infinite;
|
||||
}
|
||||
|
||||
.node-details {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 10px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.system-info {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
border: 1px solid #00ff00;
|
||||
padding: 15px;
|
||||
background: rgba(0, 30, 0, 0.4);
|
||||
}
|
||||
|
||||
.info-title {
|
||||
font-size: 1rem;
|
||||
margin-bottom: 10px;
|
||||
color: #00ff00;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.info-content {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.terminal {
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
border: 2px solid #00ff00;
|
||||
padding: 20px;
|
||||
font-family: 'Courier New', monospace;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.terminal-header {
|
||||
margin-bottom: 15px;
|
||||
color: #00ff00;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.log-entry {
|
||||
margin-bottom: 5px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.log-timestamp {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.log-level-error {
|
||||
color: #ff0000;
|
||||
}
|
||||
|
||||
.log-level-warning {
|
||||
color: #ffa500;
|
||||
}
|
||||
|
||||
.log-level-info {
|
||||
color: #00ff00;
|
||||
}
|
||||
|
||||
@keyframes glow {
|
||||
from { text-shadow: 0 0 10px #00ff00; }
|
||||
to { text-shadow: 0 0 20px #00ff00, 0 0 30px #00ff00; }
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 50% { opacity: 1; }
|
||||
51%, 100% { opacity: 0.3; }
|
||||
}
|
||||
|
||||
@keyframes scan {
|
||||
0% { left: -100%; }
|
||||
100% { left: 100%; }
|
||||
}
|
||||
|
||||
.connection-indicator {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
background: #ff0000;
|
||||
animation: pulse 1s infinite;
|
||||
}
|
||||
|
||||
.connection-indicator.connected {
|
||||
background: #00ff00;
|
||||
}
|
||||
|
||||
.data-flow {
|
||||
position: relative;
|
||||
height: 20px;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border: 1px solid #00ff00;
|
||||
margin: 10px 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.data-flow::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
width: 0%;
|
||||
background: linear-gradient(90deg, transparent, #00ff00, transparent);
|
||||
animation: dataFlow 2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes dataFlow {
|
||||
0% { width: 0%; left: 0%; }
|
||||
50% { width: 30%; }
|
||||
100% { width: 0%; left: 100%; }
|
||||
}
|
||||
|
||||
.matrix-text {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.matrix-char {
|
||||
position: absolute;
|
||||
color: #00ff00;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 14px;
|
||||
opacity: 0.3;
|
||||
animation: matrixFall 10s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes matrixFall {
|
||||
0% { transform: translateY(-100vh); opacity: 0; }
|
||||
10% { opacity: 0.3; }
|
||||
90% { opacity: 0.3; }
|
||||
100% { transform: translateY(100vh); opacity: 0; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="matrix-bg">
|
||||
<div class="matrix-text" id="matrixText"></div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="header">
|
||||
<div class="connection-indicator" id="connectionIndicator"></div>
|
||||
<h1>MY NETWORK ADVANCED MONITOR</h1>
|
||||
<div class="subtitle">Real-time Network Status & Diagnostics</div>
|
||||
</div>
|
||||
|
||||
<div class="stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-title">Connected Nodes</div>
|
||||
<div class="stat-value" id="connectedNodes">--</div>
|
||||
<div class="data-flow"></div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-title">System Uptime</div>
|
||||
<div class="stat-value" id="systemUptime">--</div>
|
||||
<div class="data-flow"></div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-title">Data Synced</div>
|
||||
<div class="stat-value" id="dataSynced">--</div>
|
||||
<div class="data-flow"></div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="stat-title">Network Health</div>
|
||||
<div class="stat-value" id="networkHealth">--</div>
|
||||
<div class="data-flow"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="system-info">
|
||||
<div class="info-card">
|
||||
<div class="info-title">Current Node Info</div>
|
||||
<div class="info-content" id="currentNodeInfo">Loading...</div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<div class="info-title">System Resources</div>
|
||||
<div class="info-content" id="systemResources">Loading...</div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<div class="info-title">Network Status</div>
|
||||
<div class="info-content" id="networkStatus">Loading...</div>
|
||||
</div>
|
||||
<div class="info-card">
|
||||
<div class="info-title">Configuration Issues</div>
|
||||
<div class="info-content" id="configIssues">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="nodes-section">
|
||||
<div class="section-title">Connected Network Nodes</div>
|
||||
<div class="nodes-grid" id="nodesGrid">
|
||||
<!-- Nodes will be populated here -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="terminal">
|
||||
<div class="terminal-header">SYSTEM LOG STREAM</div>
|
||||
<div id="logStream">
|
||||
<div class="log-entry">
|
||||
<span class="log-timestamp">[2025-07-09 14:04:00]</span>
|
||||
<span class="log-level-info">[INFO]</span>
|
||||
MY Network Monitor initialized successfully
|
||||
</div>
|
||||
<div class="log-entry">
|
||||
<span class="log-timestamp">[2025-07-09 14:04:01]</span>
|
||||
<span class="log-level-info">[INFO]</span>
|
||||
WebSocket connection established
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let ws = null;
|
||||
let reconnectAttempts = 0;
|
||||
const maxReconnectAttempts = 5;
|
||||
|
||||
// Matrix rain effect
|
||||
function createMatrixRain() {
|
||||
const matrixText = document.getElementById('matrixText');
|
||||
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
|
||||
|
||||
setInterval(() => {
|
||||
const char = document.createElement('div');
|
||||
char.className = 'matrix-char';
|
||||
char.textContent = chars[Math.floor(Math.random() * chars.length)];
|
||||
char.style.left = Math.random() * 100 + '%';
|
||||
char.style.animationDuration = (Math.random() * 10 + 5) + 's';
|
||||
char.style.fontSize = (Math.random() * 8 + 10) + 'px';
|
||||
matrixText.appendChild(char);
|
||||
|
||||
setTimeout(() => {
|
||||
if (char.parentNode) {
|
||||
char.parentNode.removeChild(char);
|
||||
}
|
||||
}, 15000);
|
||||
}, 200);
|
||||
}
|
||||
|
||||
function connectWebSocket() {
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
const wsUrl = `${protocol}//${window.location.host}/api/my/monitor/ws`;
|
||||
|
||||
ws = new WebSocket(wsUrl);
|
||||
|
||||
ws.onopen = function() {
|
||||
console.log('WebSocket connected');
|
||||
document.getElementById('connectionIndicator').classList.add('connected');
|
||||
reconnectAttempts = 0;
|
||||
addLogEntry('WebSocket connection established', 'info');
|
||||
};
|
||||
|
||||
ws.onmessage = function(event) {
|
||||
const data = JSON.parse(event.data);
|
||||
updateDashboard(data);
|
||||
};
|
||||
|
||||
ws.onclose = function() {
|
||||
console.log('WebSocket disconnected');
|
||||
document.getElementById('connectionIndicator').classList.remove('connected');
|
||||
addLogEntry('WebSocket connection lost', 'warning');
|
||||
|
||||
if (reconnectAttempts < maxReconnectAttempts) {
|
||||
setTimeout(() => {
|
||||
reconnectAttempts++;
|
||||
addLogEntry(`Reconnection attempt ${reconnectAttempts}`, 'info');
|
||||
connectWebSocket();
|
||||
}, 3000);
|
||||
}
|
||||
};
|
||||
|
||||
ws.onerror = function(error) {
|
||||
console.error('WebSocket error:', error);
|
||||
addLogEntry('WebSocket error occurred', 'error');
|
||||
};
|
||||
}
|
||||
|
||||
function updateDashboard(data) {
|
||||
// Update stats
|
||||
document.getElementById('connectedNodes').textContent = data.stats.connected_nodes;
|
||||
document.getElementById('systemUptime').textContent = data.stats.uptime;
|
||||
document.getElementById('dataSynced').textContent = data.stats.data_synced;
|
||||
document.getElementById('networkHealth').textContent = data.stats.health;
|
||||
|
||||
// Update current node info
|
||||
const nodeInfo = data.current_node;
|
||||
document.getElementById('currentNodeInfo').innerHTML = `
|
||||
<strong>Node ID:</strong> ${nodeInfo.id}<br>
|
||||
<strong>Name:</strong> ${nodeInfo.name}<br>
|
||||
<strong>Version:</strong> ${nodeInfo.version}<br>
|
||||
<strong>Status:</strong> ${nodeInfo.status}
|
||||
`;
|
||||
|
||||
// Update system resources
|
||||
const resources = data.system_resources;
|
||||
document.getElementById('systemResources').innerHTML = `
|
||||
<strong>CPU Usage:</strong> ${resources.cpu_usage}%<br>
|
||||
<strong>Memory:</strong> ${resources.memory_usage}%<br>
|
||||
<strong>Disk:</strong> ${resources.disk_usage}%<br>
|
||||
<strong>Network I/O:</strong> ${resources.network_io}
|
||||
`;
|
||||
|
||||
// Update network status
|
||||
document.getElementById('networkStatus').innerHTML = `
|
||||
<strong>Protocol:</strong> MY Network v2.0<br>
|
||||
<strong>Port:</strong> 15100<br>
|
||||
<strong>Mode:</strong> ${data.network_status.mode}<br>
|
||||
<strong>Peer Count:</strong> ${data.network_status.peers}
|
||||
`;
|
||||
|
||||
// Update configuration issues
|
||||
const issues = data.config_issues;
|
||||
let issuesHtml = '';
|
||||
if (issues.length > 0) {
|
||||
issuesHtml = issues.map(issue => `• ${issue}`).join('<br>');
|
||||
} else {
|
||||
issuesHtml = '<span style="color: #00ff00;">No configuration issues</span>';
|
||||
}
|
||||
document.getElementById('configIssues').innerHTML = issuesHtml;
|
||||
|
||||
// Update nodes grid
|
||||
updateNodesGrid(data.nodes);
|
||||
}
|
||||
|
||||
function updateNodesGrid(nodes) {
|
||||
const grid = document.getElementById('nodesGrid');
|
||||
grid.innerHTML = '';
|
||||
|
||||
nodes.forEach(node => {
|
||||
const nodeCard = document.createElement('div');
|
||||
nodeCard.className = 'node-card';
|
||||
|
||||
const statusClass = `status-${node.status}`;
|
||||
|
||||
nodeCard.innerHTML = `
|
||||
<div class="node-header">
|
||||
<div class="node-name">${node.name}</div>
|
||||
<div class="node-status ${statusClass}">${node.status}</div>
|
||||
</div>
|
||||
<div class="node-details">
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Location:</span>
|
||||
<span class="detail-value">${node.location}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Uptime:</span>
|
||||
<span class="detail-value">${node.uptime}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Connections:</span>
|
||||
<span class="detail-value">${node.connections}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Data Synced:</span>
|
||||
<span class="detail-value">${node.data_synced}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">IP Address:</span>
|
||||
<span class="detail-value">${node.ip}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<span class="detail-label">Version:</span>
|
||||
<span class="detail-value">${node.version}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
grid.appendChild(nodeCard);
|
||||
});
|
||||
}
|
||||
|
||||
function addLogEntry(message, level = 'info') {
|
||||
const logStream = document.getElementById('logStream');
|
||||
const timestamp = new Date().toISOString().slice(0, 19).replace('T', ' ');
|
||||
|
||||
const entry = document.createElement('div');
|
||||
entry.className = 'log-entry';
|
||||
entry.innerHTML = `
|
||||
<span class="log-timestamp">[${timestamp}]</span>
|
||||
<span class="log-level-${level}">[${level.toUpperCase()}]</span>
|
||||
${message}
|
||||
`;
|
||||
|
||||
logStream.appendChild(entry);
|
||||
logStream.scrollTop = logStream.scrollHeight;
|
||||
|
||||
// Keep only last 50 entries
|
||||
while (logStream.children.length > 50) {
|
||||
logStream.removeChild(logStream.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback data loading if WebSocket fails
|
||||
function loadFallbackData() {
|
||||
fetch('/api/my/monitor/status')
|
||||
.then(response => response.json())
|
||||
.then(data => updateDashboard(data))
|
||||
.catch(error => {
|
||||
console.error('Failed to load fallback data:', error);
|
||||
addLogEntry('Failed to load monitoring data', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize
|
||||
createMatrixRain();
|
||||
connectWebSocket();
|
||||
|
||||
// Load fallback data every 5 seconds if WebSocket is not connected
|
||||
setInterval(() => {
|
||||
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
||||
loadFallbackData();
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
// Add some random log entries for demo
|
||||
setInterval(() => {
|
||||
const messages = [
|
||||
'Network heartbeat received',
|
||||
'Data synchronization completed',
|
||||
'Peer discovery scan finished',
|
||||
'Security check passed',
|
||||
'Cache optimization complete'
|
||||
];
|
||||
const message = messages[Math.floor(Math.random() * messages.length)];
|
||||
addLogEntry(message, 'info');
|
||||
}, 8000);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
return HTMLResponse(content=dashboard_html)
|
||||
|
||||
@router.get("/status")
|
||||
async def get_monitoring_status():
|
||||
"""Get current monitoring status data"""
|
||||
import subprocess
|
||||
import shutil
|
||||
|
||||
# Get system info
|
||||
try:
|
||||
cpu_percent = psutil.cpu_percent(interval=1)
|
||||
memory = psutil.virtual_memory()
|
||||
disk = psutil.disk_usage('/')
|
||||
|
||||
system_resources = {
|
||||
"cpu_usage": round(cpu_percent, 1),
|
||||
"memory_usage": round(memory.percent, 1),
|
||||
"disk_usage": round(disk.percent, 1),
|
||||
"network_io": "Active"
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get system resources: {e}")
|
||||
system_resources = {
|
||||
"cpu_usage": 0,
|
||||
"memory_usage": 0,
|
||||
"disk_usage": 0,
|
||||
"network_io": "Unknown"
|
||||
}
|
||||
|
||||
# Configuration issues from logs/environment
|
||||
config_issues = [
|
||||
"Pydantic validation errors in configuration",
|
||||
"Extra environment variables not permitted",
|
||||
"Telegram API token format validation failed",
|
||||
"MY Network running in limited mode"
|
||||
]
|
||||
|
||||
return {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"stats": {
|
||||
"connected_nodes": len([n for n in network_nodes if n["status"] == "online"]),
|
||||
"uptime": "2h 18m",
|
||||
"data_synced": "87%",
|
||||
"health": "Limited"
|
||||
},
|
||||
"current_node": {
|
||||
"id": "node_001_local_dev",
|
||||
"name": "Primary Development Node",
|
||||
"version": "2.0.0",
|
||||
"status": "limited_mode"
|
||||
},
|
||||
"system_resources": system_resources,
|
||||
"network_status": {
|
||||
"mode": "Development",
|
||||
"peers": 3,
|
||||
"protocol": "MY Network v2.0"
|
||||
},
|
||||
"config_issues": config_issues,
|
||||
"nodes": network_nodes
|
||||
}
|
||||
|
||||
@router.websocket("/ws")
|
||||
async def websocket_endpoint(websocket: WebSocket):
|
||||
"""WebSocket endpoint for real-time monitoring updates"""
|
||||
await websocket.accept()
|
||||
connected_clients.append(websocket)
|
||||
|
||||
try:
|
||||
while True:
|
||||
# Send periodic updates
|
||||
status_data = await get_monitoring_status()
|
||||
await websocket.send_text(json.dumps(status_data))
|
||||
await asyncio.sleep(2) # Update every 2 seconds
|
||||
|
||||
except WebSocketDisconnect:
|
||||
connected_clients.remove(websocket)
|
||||
logger.info("Client disconnected from monitoring WebSocket")
|
||||
except Exception as e:
|
||||
logger.error(f"WebSocket error: {e}")
|
||||
if websocket in connected_clients:
|
||||
connected_clients.remove(websocket)
|
||||
|
||||
@router.get("/nodes")
|
||||
async def get_network_nodes():
|
||||
"""Get list of all network nodes"""
|
||||
return {"nodes": network_nodes}
|
||||
|
||||
@router.get("/node/{node_id}")
|
||||
async def get_node_details(node_id: str):
|
||||
"""Get detailed information about a specific node"""
|
||||
node = next((n for n in network_nodes if n["id"] == node_id), None)
|
||||
if not node:
|
||||
return {"error": "Node not found"}, 404
|
||||
|
||||
# Add more detailed info
|
||||
detailed_node = {
|
||||
**node,
|
||||
"detailed_stats": {
|
||||
"cpu_usage": "23%",
|
||||
"memory_usage": "67%",
|
||||
"disk_usage": "45%",
|
||||
"network_in": "150 KB/s",
|
||||
"network_out": "89 KB/s",
|
||||
"active_connections": 12,
|
||||
"data_transferred": "1.2 GB",
|
||||
"sync_progress": "87%"
|
||||
},
|
||||
"services": {
|
||||
"http_server": "running",
|
||||
"p2p_network": "limited",
|
||||
"database": "connected",
|
||||
"redis_cache": "connected",
|
||||
"blockchain_sync": "paused"
|
||||
}
|
||||
}
|
||||
|
||||
return {"node": detailed_node}
|
||||
|
||||
@router.post("/simulate_event")
|
||||
async def simulate_network_event(event_data: Dict[str, Any]):
|
||||
"""Simulate network events for testing"""
|
||||
# Broadcast event to all connected WebSocket clients
|
||||
event_message = {
|
||||
"type": "network_event",
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"event": event_data
|
||||
}
|
||||
|
||||
for client in connected_clients[:]:
|
||||
try:
|
||||
await client.send_text(json.dumps(event_message))
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to send event to client: {e}")
|
||||
connected_clients.remove(client)
|
||||
|
||||
return {"status": "Event simulated", "clients_notified": len(connected_clients)}
|
||||
@@ -11,11 +11,13 @@ from fastapi.responses import FileResponse, StreamingResponse
|
||||
from sqlalchemy import select, and_, func
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.core.database_compatible import get_async_session
|
||||
from app.core.models.content_compatible import Content, ContentMetadata
|
||||
from app.core.database import db_manager
|
||||
from app.core.security import get_current_user_optional
|
||||
from app.core.cache import cache
|
||||
|
||||
# Import content models directly to avoid circular imports
|
||||
from app.core.models.content_models import StoredContent as Content, UserContent as ContentMetadata
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Создать router для MY Network API
|
||||
@@ -132,7 +134,7 @@ async def disconnect_peer(peer_id: str):
|
||||
async def get_content_list(
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
offset: int = Query(0, ge=0),
|
||||
session: AsyncSession = Depends(get_async_session)
|
||||
session: AsyncSession = Depends(get_db_session)
|
||||
):
|
||||
"""Получить список доступного контента."""
|
||||
try:
|
||||
@@ -147,7 +149,7 @@ async def get_content_list(
|
||||
stmt = (
|
||||
select(Content, ContentMetadata)
|
||||
.outerjoin(ContentMetadata, Content.id == ContentMetadata.content_id)
|
||||
.where(Content.is_active == True)
|
||||
.where(Content.disabled == False)
|
||||
.order_by(Content.created_at.desc())
|
||||
.limit(limit)
|
||||
.offset(offset)
|
||||
@@ -158,20 +160,19 @@ async def get_content_list(
|
||||
|
||||
for content, metadata in result:
|
||||
content_data = {
|
||||
"hash": content.sha256_hash or content.md5_hash,
|
||||
"hash": content.hash,
|
||||
"filename": content.filename,
|
||||
"original_filename": content.original_filename,
|
||||
"file_size": content.file_size,
|
||||
"file_type": content.file_type,
|
||||
"content_type": content.content_type,
|
||||
"mime_type": content.mime_type,
|
||||
"created_at": content.created_at.isoformat(),
|
||||
"encrypted": getattr(content, 'encrypted', False),
|
||||
"encrypted": content.encrypted,
|
||||
"metadata": metadata.to_dict() if metadata else {}
|
||||
}
|
||||
content_items.append(content_data)
|
||||
|
||||
# Получить общее количество
|
||||
count_stmt = select(func.count(Content.id)).where(Content.is_active == True)
|
||||
count_stmt = select(func.count(Content.id)).where(Content.disabled == False)
|
||||
count_result = await session.execute(count_stmt)
|
||||
total_count = count_result.scalar()
|
||||
|
||||
@@ -199,7 +200,7 @@ async def get_content_list(
|
||||
@router.get("/content/{content_hash}/exists")
|
||||
async def check_content_exists(
|
||||
content_hash: str,
|
||||
session: AsyncSession = Depends(get_async_session)
|
||||
session: AsyncSession = Depends(get_db_session)
|
||||
):
|
||||
"""Проверить существование контента по хешу."""
|
||||
try:
|
||||
@@ -213,8 +214,8 @@ async def check_content_exists(
|
||||
# Проверить в БД
|
||||
stmt = select(Content.id).where(
|
||||
and_(
|
||||
Content.is_active == True,
|
||||
(Content.md5_hash == content_hash) | (Content.sha256_hash == content_hash)
|
||||
Content.disabled == False,
|
||||
Content.hash == content_hash
|
||||
)
|
||||
)
|
||||
|
||||
@@ -238,7 +239,7 @@ async def check_content_exists(
|
||||
@router.get("/content/{content_hash}/metadata")
|
||||
async def get_content_metadata(
|
||||
content_hash: str,
|
||||
session: AsyncSession = Depends(get_async_session)
|
||||
session: AsyncSession = Depends(get_db_session)
|
||||
):
|
||||
"""Получить метаданные контента."""
|
||||
try:
|
||||
@@ -255,8 +256,8 @@ async def get_content_metadata(
|
||||
.outerjoin(ContentMetadata, Content.id == ContentMetadata.content_id)
|
||||
.where(
|
||||
and_(
|
||||
Content.is_active == True,
|
||||
(Content.md5_hash == content_hash) | (Content.sha256_hash == content_hash)
|
||||
Content.disabled == False,
|
||||
Content.hash == content_hash
|
||||
)
|
||||
)
|
||||
)
|
||||
@@ -274,14 +275,13 @@ async def get_content_metadata(
|
||||
"data": {
|
||||
"hash": content_hash,
|
||||
"filename": content.filename,
|
||||
"original_filename": content.original_filename,
|
||||
"file_size": content.file_size,
|
||||
"file_type": content.file_type,
|
||||
"content_type": content.content_type,
|
||||
"mime_type": content.mime_type,
|
||||
"created_at": content.created_at.isoformat(),
|
||||
"updated_at": content.updated_at.isoformat() if content.updated_at else None,
|
||||
"encrypted": getattr(content, 'encrypted', False),
|
||||
"processing_status": getattr(content, 'processing_status', 'completed'),
|
||||
"encrypted": content.encrypted,
|
||||
"processing_status": content.processing_status,
|
||||
"metadata": metadata.to_dict() if metadata else {}
|
||||
},
|
||||
"timestamp": datetime.utcnow().isoformat()
|
||||
@@ -302,15 +302,15 @@ async def get_content_metadata(
|
||||
@router.get("/content/{content_hash}/download")
|
||||
async def download_content(
|
||||
content_hash: str,
|
||||
session: AsyncSession = Depends(get_async_session)
|
||||
session: AsyncSession = Depends(get_db_session)
|
||||
):
|
||||
"""Скачать контент по хешу."""
|
||||
try:
|
||||
# Найти контент в БД
|
||||
stmt = select(Content).where(
|
||||
and_(
|
||||
Content.is_active == True,
|
||||
(Content.md5_hash == content_hash) | (Content.sha256_hash == content_hash)
|
||||
Content.disabled == False,
|
||||
Content.hash == content_hash
|
||||
)
|
||||
)
|
||||
|
||||
@@ -328,7 +328,7 @@ async def download_content(
|
||||
# Вернуть файл
|
||||
return FileResponse(
|
||||
path=str(file_path),
|
||||
filename=content.original_filename or content.filename,
|
||||
filename=content.filename,
|
||||
media_type=content.mime_type or "application/octet-stream"
|
||||
)
|
||||
|
||||
@@ -343,15 +343,15 @@ async def download_content(
|
||||
async def upload_content(
|
||||
content_hash: str,
|
||||
file: UploadFile = File(...),
|
||||
session: AsyncSession = Depends(get_async_session)
|
||||
session: AsyncSession = Depends(get_db_session)
|
||||
):
|
||||
"""Загрузить контент в ноду."""
|
||||
try:
|
||||
# Проверить, не существует ли уже контент
|
||||
exists_stmt = select(Content.id).where(
|
||||
and_(
|
||||
Content.is_active == True,
|
||||
(Content.md5_hash == content_hash) | (Content.sha256_hash == content_hash)
|
||||
Content.disabled == False,
|
||||
Content.hash == content_hash
|
||||
)
|
||||
)
|
||||
|
||||
@@ -387,15 +387,13 @@ async def upload_content(
|
||||
# Сохранить в БД
|
||||
new_content = Content(
|
||||
filename=file.filename,
|
||||
original_filename=file.filename,
|
||||
file_path=str(file_path),
|
||||
hash=sha256_hash, # Используем SHA256 как основной хеш
|
||||
file_size=len(content_data),
|
||||
file_type=file.filename.split('.')[-1] if '.' in file.filename else 'unknown',
|
||||
content_type=file.filename.split('.')[-1] if '.' in file.filename else 'unknown',
|
||||
mime_type=file.content_type or "application/octet-stream",
|
||||
md5_hash=md5_hash,
|
||||
sha256_hash=sha256_hash,
|
||||
is_active=True,
|
||||
processing_status="completed"
|
||||
file_path=str(file_path),
|
||||
disabled=False,
|
||||
processing_status="ready"
|
||||
)
|
||||
|
||||
session.add(new_content)
|
||||
@@ -430,11 +428,11 @@ async def replicate_content(replication_request: Dict[str, Any]):
|
||||
raise HTTPException(status_code=400, detail="Content hash is required")
|
||||
|
||||
# Проверить, нужна ли репликация
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
exists_stmt = select(Content.id).where(
|
||||
and_(
|
||||
Content.is_active == True,
|
||||
(Content.md5_hash == content_hash) | (Content.sha256_hash == content_hash)
|
||||
Content.disabled == False,
|
||||
Content.hash == content_hash
|
||||
)
|
||||
)
|
||||
|
||||
@@ -560,19 +558,19 @@ async def get_network_stats():
|
||||
sync_status = await node_service.sync_manager.get_sync_status()
|
||||
|
||||
# Статистика контента
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
# Общее количество контента
|
||||
content_count_stmt = select(func.count(Content.id)).where(Content.is_active == True)
|
||||
content_count_stmt = select(func.count(Content.id)).where(Content.disabled == False)
|
||||
content_count_result = await session.execute(content_count_stmt)
|
||||
total_content = content_count_result.scalar()
|
||||
|
||||
# Размер контента
|
||||
size_stmt = select(func.sum(Content.file_size)).where(Content.is_active == True)
|
||||
size_stmt = select(func.sum(Content.file_size)).where(Content.disabled == False)
|
||||
size_result = await session.execute(size_stmt)
|
||||
total_size = size_result.scalar() or 0
|
||||
|
||||
# Контент по типам
|
||||
type_stmt = select(Content.file_type, func.count(Content.id)).where(Content.is_active == True).group_by(Content.file_type)
|
||||
type_stmt = select(Content.content_type, func.count(Content.id)).where(Content.disabled == False).group_by(Content.content_type)
|
||||
type_result = await session.execute(type_stmt)
|
||||
content_by_type = {row[0]: row[1] for row in type_result}
|
||||
|
||||
|
||||
@@ -163,11 +163,11 @@ async def get_content_list(request: Request):
|
||||
return json_response(json.loads(cached_result))
|
||||
|
||||
# Получить контент из БД
|
||||
from app.core.database_compatible import get_async_session
|
||||
from app.core.database import db_manager
|
||||
from app.core.models.content_compatible import Content, ContentMetadata
|
||||
from sqlalchemy import select, func
|
||||
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
stmt = (
|
||||
select(Content, ContentMetadata)
|
||||
.outerjoin(ContentMetadata, Content.id == ContentMetadata.content_id)
|
||||
@@ -233,11 +233,11 @@ async def check_content_exists(request: Request, content_hash: str):
|
||||
return json_response({"exists": cached_result == "true", "hash": content_hash})
|
||||
|
||||
# Проверить в БД
|
||||
from app.core.database_compatible import get_async_session
|
||||
from app.core.database import db_manager
|
||||
from app.core.models.content_compatible import Content
|
||||
from sqlalchemy import select, and_
|
||||
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
stmt = select(Content.id).where(
|
||||
and_(
|
||||
Content.is_active == True,
|
||||
@@ -327,11 +327,11 @@ async def get_network_stats(request: Request):
|
||||
sync_status = await node_service.sync_manager.get_sync_status()
|
||||
|
||||
# Статистика контента
|
||||
from app.core.database_compatible import get_async_session
|
||||
from app.core.database import db_manager
|
||||
from app.core.models.content_compatible import Content
|
||||
from sqlalchemy import select, func
|
||||
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
# Общее количество контента
|
||||
content_count_stmt = select(func.count(Content.id)).where(Content.is_active == True)
|
||||
content_count_result = await session.execute(content_count_stmt)
|
||||
|
||||
@@ -13,7 +13,7 @@ from sanic.response import JSONResponse, ResponseStream
|
||||
from sqlalchemy import select, update
|
||||
|
||||
from app.core.config import get_settings
|
||||
from app.core.database import get_async_session, get_cache_manager
|
||||
from app.core.database import db_manager, get_cache_manager
|
||||
from app.core.logging import get_logger
|
||||
from app.core.storage import StorageManager
|
||||
from app.core.security import validate_file_signature, generate_secure_filename
|
||||
@@ -73,8 +73,8 @@ async def initiate_upload(request: Request) -> JSONResponse:
|
||||
)
|
||||
|
||||
# Create content record first
|
||||
async with get_async_session() as session:
|
||||
from app.core.models.content import Content
|
||||
async with db_manager.get_session() as session:
|
||||
from app.core.models.content_models import Content
|
||||
|
||||
content = Content(
|
||||
user_id=user_id,
|
||||
@@ -245,8 +245,8 @@ async def get_upload_status(request: Request, upload_id: UUID) -> JSONResponse:
|
||||
)
|
||||
|
||||
# Verify user ownership
|
||||
async with get_async_session() as session:
|
||||
from app.core.models.content import Content
|
||||
async with db_manager.get_session() as session:
|
||||
from app.core.models.content_models import Content
|
||||
|
||||
stmt = select(Content).where(
|
||||
Content.id == UUID(session_data["content_id"])
|
||||
@@ -318,8 +318,8 @@ async def cancel_upload(request: Request, upload_id: UUID) -> JSONResponse:
|
||||
|
||||
# Verify user ownership
|
||||
content_id = UUID(session_data["content_id"])
|
||||
async with get_async_session() as session:
|
||||
from app.core.models.content import Content
|
||||
async with db_manager.get_session() as session:
|
||||
from app.core.models.content_models import Content
|
||||
|
||||
stmt = select(Content).where(Content.id == content_id)
|
||||
result = await session.execute(stmt)
|
||||
@@ -390,8 +390,8 @@ async def delete_file(request: Request, content_id: UUID) -> JSONResponse:
|
||||
try:
|
||||
user_id = request.ctx.user.id
|
||||
|
||||
async with get_async_session() as session:
|
||||
from app.core.models.content import Content
|
||||
async with db_manager.get_session() as session:
|
||||
from app.core.models.content_models import Content
|
||||
|
||||
# Get content
|
||||
stmt = select(Content).where(Content.id == content_id)
|
||||
@@ -477,9 +477,9 @@ async def get_storage_quota(request: Request) -> JSONResponse:
|
||||
current_usage = await cache_manager.get(quota_key, default=0)
|
||||
|
||||
# Calculate accurate usage from database
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
from sqlalchemy import func
|
||||
from app.core.models.content import Content
|
||||
from app.core.models.content_models import Content
|
||||
|
||||
stmt = select(
|
||||
func.count(Content.id).label('file_count'),
|
||||
@@ -545,9 +545,9 @@ async def get_storage_stats(request: Request) -> JSONResponse:
|
||||
try:
|
||||
user_id = request.ctx.user.id
|
||||
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
from sqlalchemy import func
|
||||
from app.core.models.content import Content
|
||||
from app.core.models.content_models import Content
|
||||
|
||||
# Get statistics by content type
|
||||
type_stmt = select(
|
||||
@@ -639,9 +639,9 @@ async def cleanup_orphaned_files(request: Request) -> JSONResponse:
|
||||
}
|
||||
|
||||
# Clean up expired upload sessions
|
||||
async with get_async_session() as session:
|
||||
async with db_manager.get_session() as session:
|
||||
from app.core.models.storage import ContentUploadSession
|
||||
from app.core.models.content import Content
|
||||
from app.core.models.content_models import Content
|
||||
|
||||
# Get expired sessions
|
||||
expired_sessions_stmt = select(ContentUploadSession).where(
|
||||
|
||||
Reference in new issue
Block a user