240 lines
7.0 KiB
Python
240 lines
7.0 KiB
Python
"""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
|