feat(dashboard): optimize dashboard layout and add new charts
This commit is contained in:
@@ -108,3 +108,21 @@ async def batch_delete(
|
||||
async def clean_invalid(service: ProxyService = Depends(get_proxy_service)):
|
||||
count = await service.clean_invalid()
|
||||
return success_response(f"清理了 {count} 个无效代理", {"deleted_count": count})
|
||||
|
||||
|
||||
@router.get("/latency-distribution")
|
||||
async def get_latency_distribution(
|
||||
service: ProxyService = Depends(get_proxy_service),
|
||||
):
|
||||
"""获取延迟分布数据,用于直方图展示"""
|
||||
distribution = await service.get_latency_distribution()
|
||||
return success_response("获取延迟分布成功", distribution)
|
||||
|
||||
|
||||
@router.get("/score-distribution")
|
||||
async def get_score_distribution(
|
||||
service: ProxyService = Depends(get_proxy_service),
|
||||
):
|
||||
"""获取评分分布数据,用于柱状图展示"""
|
||||
distribution = await service.get_score_distribution()
|
||||
return success_response("获取评分分布成功", distribution)
|
||||
|
||||
@@ -526,3 +526,73 @@ class ProxyRepository:
|
||||
except Exception as e:
|
||||
logger.error(f"clean_expired failed: {e}", exc_info=True)
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
async def get_latency_distribution(db: aiosqlite.Connection) -> dict:
|
||||
"""获取延迟分布数据(仅已验证可用的代理)"""
|
||||
try:
|
||||
async with db.execute(
|
||||
"""
|
||||
SELECT response_time_ms FROM proxies
|
||||
WHERE validated = 1 AND score > 0 AND response_time_ms IS NOT NULL AND response_time_ms > 0
|
||||
"""
|
||||
) as cursor:
|
||||
rows = await cursor.fetchall()
|
||||
if not rows:
|
||||
return {"ranges": [], "counts": []}
|
||||
|
||||
latencies = [row[0] for row in rows]
|
||||
ranges = ["<500ms", "500-1s", "1-2s", "2-3s", ">3s"]
|
||||
counts = [0, 0, 0, 0, 0]
|
||||
|
||||
for lat in latencies:
|
||||
if lat < 500:
|
||||
counts[0] += 1
|
||||
elif lat < 1000:
|
||||
counts[1] += 1
|
||||
elif lat < 2000:
|
||||
counts[2] += 1
|
||||
elif lat < 3000:
|
||||
counts[3] += 1
|
||||
else:
|
||||
counts[4] += 1
|
||||
|
||||
return {"ranges": ranges, "counts": counts}
|
||||
except Exception as e:
|
||||
logger.error(f"get_latency_distribution failed: {e}", exc_info=True)
|
||||
return {"ranges": [], "counts": []}
|
||||
|
||||
@staticmethod
|
||||
async def get_score_distribution(db: aiosqlite.Connection) -> dict:
|
||||
"""获取评分分布数据(仅已验证可用的代理)"""
|
||||
try:
|
||||
async with db.execute(
|
||||
"""
|
||||
SELECT score FROM proxies
|
||||
WHERE validated = 1 AND score > 0
|
||||
"""
|
||||
) as cursor:
|
||||
rows = await cursor.fetchall()
|
||||
if not rows:
|
||||
return {"ranges": [], "counts": []}
|
||||
|
||||
scores = [row[0] for row in rows]
|
||||
ranges = ["80-100", "60-80", "40-60", "20-40", "0-20"]
|
||||
counts = [0, 0, 0, 0, 0]
|
||||
|
||||
for score in scores:
|
||||
if score >= 80:
|
||||
counts[0] += 1
|
||||
elif score >= 60:
|
||||
counts[1] += 1
|
||||
elif score >= 40:
|
||||
counts[2] += 1
|
||||
elif score >= 20:
|
||||
counts[3] += 1
|
||||
else:
|
||||
counts[4] += 1
|
||||
|
||||
return {"ranges": ranges, "counts": counts}
|
||||
except Exception as e:
|
||||
logger.error(f"get_score_distribution failed: {e}", exc_info=True)
|
||||
return {"ranges": [], "counts": []}
|
||||
|
||||
@@ -147,3 +147,11 @@ class ProxyService:
|
||||
if isinstance(dt, str):
|
||||
return dt
|
||||
return dt.isoformat()
|
||||
|
||||
async def get_latency_distribution(self) -> dict:
|
||||
async with get_db() as db:
|
||||
return await self.proxy_repo.get_latency_distribution(db)
|
||||
|
||||
async def get_score_distribution(self) -> dict:
|
||||
async with get_db() as db:
|
||||
return await self.proxy_repo.get_score_distribution(db)
|
||||
|
||||
Reference in New Issue
Block a user