"""Dashboard API with caching and optimizations""" from datetime import datetime, timedelta from fastapi import APIRouter, Depends from sqlalchemy import select, func, text from sqlalchemy.ext.asyncio import AsyncSession from app.db.session import get_db from app.models.user import User from app.models.datasource import DataSource from app.models.datasource_config import DataSourceConfig from app.models.alert import Alert, AlertSeverity from app.models.task import CollectionTask from app.core.security import get_current_user from app.core.cache import cache # Built-in collectors info (mirrored from datasources.py) COLLECTOR_INFO = { "top500": { "id": 1, "name": "TOP500 Supercomputers", "module": "L1", "priority": "P0", "frequency_hours": 4, }, "epoch_ai_gpu": { "id": 2, "name": "Epoch AI GPU Clusters", "module": "L1", "priority": "P0", "frequency_hours": 6, }, "huggingface_models": { "id": 3, "name": "HuggingFace Models", "module": "L2", "priority": "P1", "frequency_hours": 12, }, "huggingface_datasets": { "id": 4, "name": "HuggingFace Datasets", "module": "L2", "priority": "P1", "frequency_hours": 12, }, "huggingface_spaces": { "id": 5, "name": "HuggingFace Spaces", "module": "L2", "priority": "P2", "frequency_hours": 24, }, "peeringdb_ixp": { "id": 6, "name": "PeeringDB IXP", "module": "L2", "priority": "P1", "frequency_hours": 24, }, "peeringdb_network": { "id": 7, "name": "PeeringDB Networks", "module": "L2", "priority": "P2", "frequency_hours": 48, }, "peeringdb_facility": { "id": 8, "name": "PeeringDB Facilities", "module": "L2", "priority": "P2", "frequency_hours": 48, }, "telegeography_cables": { "id": 9, "name": "Submarine Cables", "module": "L2", "priority": "P1", "frequency_hours": 168, }, "telegeography_landing": { "id": 10, "name": "Cable Landing Points", "module": "L2", "priority": "P2", "frequency_hours": 168, }, "telegeography_systems": { "id": 11, "name": "Cable Systems", "module": "L2", "priority": "P2", "frequency_hours": 168, }, } router = APIRouter() @router.get("/stats") async def get_stats( current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """Get dashboard statistics with caching""" cache_key = "dashboard:stats" cached_result = cache.get(cache_key) if cached_result: return cached_result today_start = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0) # Count built-in collectors built_in_count = len(COLLECTOR_INFO) built_in_active = built_in_count # Built-in are always "active" for counting purposes # Count custom configs from database result = await db.execute(select(func.count(DataSourceConfig.id))) custom_count = result.scalar() or 0 result = await db.execute( select(func.count(DataSourceConfig.id)).where(DataSourceConfig.is_active == True) ) custom_active = result.scalar() or 0 # Total datasources total_datasources = built_in_count + custom_count active_datasources = built_in_active + custom_active # Tasks today (from database) result = await db.execute( select(func.count(CollectionTask.id)).where(CollectionTask.started_at >= today_start) ) tasks_today = result.scalar() or 0 result = await db.execute( select(func.count(CollectionTask.id)).where( CollectionTask.status == "success", CollectionTask.started_at >= today_start, ) ) success_tasks = result.scalar() or 0 success_rate = (success_tasks / tasks_today * 100) if tasks_today > 0 else 0 # Alerts result = await db.execute( select(func.count(Alert.id)).where( Alert.severity == AlertSeverity.CRITICAL, Alert.status == "active", ) ) critical_alerts = result.scalar() or 0 result = await db.execute( select(func.count(Alert.id)).where( Alert.severity == AlertSeverity.WARNING, Alert.status == "active", ) ) warning_alerts = result.scalar() or 0 result = await db.execute( select(func.count(Alert.id)).where( Alert.severity == AlertSeverity.INFO, Alert.status == "active", ) ) info_alerts = result.scalar() or 0 response = { "total_datasources": total_datasources, "active_datasources": active_datasources, "tasks_today": tasks_today, "success_rate": round(success_rate, 1), "last_updated": datetime.utcnow().isoformat(), "alerts": { "critical": critical_alerts, "warning": warning_alerts, "info": info_alerts, }, } cache.set(cache_key, response, expire_seconds=60) return response @router.get("/summary") async def get_summary( current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """Get dashboard summary by module with caching""" cache_key = "dashboard:summary" cached_result = cache.get(cache_key) if cached_result: return cached_result # Count by module for built-in collectors builtin_by_module = {} for name, info in COLLECTOR_INFO.items(): module = info["module"] if module not in builtin_by_module: builtin_by_module[module] = {"datasources": 0, "sources": []} builtin_by_module[module]["datasources"] += 1 builtin_by_module[module]["sources"].append(info["name"]) # Count custom configs by module (default to L3 for custom) result = await db.execute( select(DataSourceConfig.source_type, func.count(DataSourceConfig.id).label("count")) .where(DataSourceConfig.is_active == True) .group_by(DataSourceConfig.source_type) ) custom_rows = result.fetchall() for row in custom_rows: source_type = row.source_type module = "L3" # Custom configs default to L3 if module not in builtin_by_module: builtin_by_module[module] = {"datasources": 0, "sources": []} builtin_by_module[module]["datasources"] += row.count builtin_by_module[module]["sources"].append(f"自定义 ({source_type})") summary = {} for module, data in builtin_by_module.items(): summary[module] = { "datasources": data["datasources"], "total_records": 0, # Built-in don't track this in dashboard stats "last_updated": datetime.utcnow().isoformat(), } response = {"modules": summary, "last_updated": datetime.utcnow().isoformat()} cache.set(cache_key, response, expire_seconds=300) return response