Files
MoviePilot/app/api/endpoints/dashboard.py
T
jxxghp 7d0cf76d9c fix(storage): 修复通用管理契约下存储页报未知错误
- usage 动作返回的 StorageUsage pydantic 模型无法透过
  Response[Dict[str, Any]] 开放映射校验导致 500,统一转为 dict
- support_transtype 返回值恢复 transtype 包装结构,与前端期望一致
- 空结果(无用量/无整理方式)恢复成功空结构语义,
  不再因 bool(结果) 被前端客户端当作业务失败弹窗
- dashboard 用量聚合改为 dict 取值;新增响应校验与空结果回归测试
2026-08-16 07:50:29 +08:00

304 lines
9.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from pathlib import Path
from typing import Any, List, Optional, Annotated
from fastapi import Depends
from sqlalchemy.orm import Session
from app import schemas
from app.api.response import ResponseAPIRouter
from app.chain.dashboard import DashboardChain
from app.chain.storage import StorageChain
from app.runtime.config import settings
from app.application.security.access import verify_apitoken
from app.db import get_db
from app.db.models.transferhistory import TransferHistory
from app.api.deps import get_current_active_superuser
from app.schemas.types import StorageAction
from app.application.directory import DirectoryHelper
from app.scheduler import Scheduler
from app.adapters.system.host import SystemUtils
router = ResponseAPIRouter()
def _build_statistic(db: Session, name: Optional[str] = None) -> schemas.Statistic:
"""
构建媒体数量统计信息。
"""
media_statistics: Optional[List[schemas.Statistic]] = (
DashboardChain().media_statistic(name)
)
if media_statistics:
# 汇总各媒体库统计信息
ret_statistic = schemas.Statistic()
has_episode_count = False
for media_statistic in media_statistics:
ret_statistic.movie_count += media_statistic.movie_count or 0
ret_statistic.tv_count += media_statistic.tv_count or 0
ret_statistic.music_count += media_statistic.music_count or 0
ret_statistic.user_count += media_statistic.user_count or 0
if media_statistic.episode_count is not None:
ret_statistic.episode_count += media_statistic.episode_count or 0
has_episode_count = True
if not has_episode_count:
# 所有媒体服务都未提供剧集统计时,返回 None 供前端展示“未获取”。
ret_statistic.episode_count = None
else:
ret_statistic = schemas.Statistic()
movie_count_month, tv_count_month, episode_count_month, music_count_month = (
TransferHistory.monthly_media_statistics(db)
)
ret_statistic.movie_count_month = movie_count_month
ret_statistic.tv_count_month = tv_count_month
ret_statistic.episode_count_month = episode_count_month
ret_statistic.music_count_month = music_count_month
return ret_statistic
def _build_storage() -> schemas.Storage:
"""
构建本地存储空间信息。
"""
total, available = 0, 0
dirs = DirectoryHelper().get_dirs()
if not dirs:
return schemas.Storage(total_storage=total, used_storage=total - available)
storages = set([d.library_storage for d in dirs if d.library_storage])
for _storage in storages:
_result = StorageChain().manage_storage(storage=_storage, action=StorageAction.USAGE.value)
_usage = _result.get("data") if _result.get("success") else None
if _usage:
total += _usage.get("total") or 0
available += _usage.get("available") or 0
return schemas.Storage(total_storage=total, used_storage=total - available)
def _build_downloader(name: Optional[str] = None) -> schemas.DownloaderInfo:
"""
构建下载器统计信息。
"""
# 下载目录空间
download_dirs = DirectoryHelper().get_local_download_dirs()
_, free_space = SystemUtils.space_usage(
[Path(d.download_path) for d in download_dirs],
btrfs_fsid_dedup=settings.BTRFS_FSID_DEDUP,
)
# 下载器信息
downloader_info = schemas.DownloaderInfo()
transfer_infos = DashboardChain().downloader_info(name)
if transfer_infos:
for transfer_info in transfer_infos:
downloader_info.download_speed += transfer_info.download_speed
downloader_info.upload_speed += transfer_info.upload_speed
downloader_info.download_size += transfer_info.download_size
downloader_info.upload_size += transfer_info.upload_size
downloader_info.free_space = free_space
return downloader_info
@router.get("/statistic", summary="媒体数量统计", response_model=schemas.Statistic)
def statistic(
name: Optional[str] = None,
db: Session = Depends(get_db),
_: Any = Depends(get_current_active_superuser),
) -> Any:
"""
查询媒体数量统计信息
"""
return _build_statistic(db, name)
@router.get(
"/statistic2", summary="媒体数量统计(API_TOKEN", response_model=schemas.Statistic
)
def statistic2(
_: Annotated[str, Depends(verify_apitoken)],
db: Session = Depends(get_db),
) -> Any:
"""
查询媒体数量统计信息 API_TOKEN认证(?token=xxx
"""
return _build_statistic(db)
@router.get("/storage", summary="本地存储空间", response_model=schemas.Storage)
def storage(_: Any = Depends(get_current_active_superuser)) -> Any:
"""
查询本地存储空间信息
"""
return _build_storage()
@router.get(
"/storage2", summary="本地存储空间(API_TOKEN", response_model=schemas.Storage
)
def storage2(_: Annotated[str, Depends(verify_apitoken)]) -> Any:
"""
查询本地存储空间信息 API_TOKEN认证(?token=xxx
"""
return _build_storage()
@router.get("/processes", summary="进程信息", response_model=List[schemas.ProcessInfo])
def processes(_: Any = Depends(get_current_active_superuser)) -> Any:
"""
查询进程信息
"""
return SystemUtils.processes()
@router.get("/system", summary="系统摘要信息", response_model=schemas.DashboardSystemInfo)
def system_info(_: Any = Depends(get_current_active_superuser)) -> Any:
"""
查询仪表板系统摘要信息
"""
return SystemUtils.dashboard_system_info()
@router.get("/downloader", summary="下载器信息", response_model=schemas.DownloaderInfo)
def downloader(
name: Optional[str] = None, _: Any = Depends(get_current_active_superuser)
) -> Any:
"""
查询下载器信息
"""
return _build_downloader(name)
@router.get(
"/downloader2",
summary="下载器信息(API_TOKEN",
response_model=schemas.DownloaderInfo,
)
def downloader2(_: Annotated[str, Depends(verify_apitoken)]) -> Any:
"""
查询下载器信息 API_TOKEN认证(?token=xxx
"""
return _build_downloader()
@router.get("/schedule", summary="后台服务", response_model=List[schemas.ScheduleInfo])
async def schedule(_: Any = Depends(get_current_active_superuser)) -> Any:
"""
查询后台服务信息
"""
return Scheduler().list()
@router.get(
"/schedule/{job_id}/progress",
summary="后台服务进度",
response_model=schemas.Response[schemas.ScheduleProgress],
)
async def schedule_progress(
job_id: str, _: Any = Depends(get_current_active_superuser)
) -> Any:
"""
查询指定后台服务的执行进度。
"""
progress = Scheduler().get_progress(job_id)
if not progress:
return schemas.Response(success=False, message="后台服务不存在")
return schemas.Response(success=True, data=progress.model_dump())
@router.get(
"/schedule2",
summary="后台服务(API_TOKEN",
response_model=List[schemas.ScheduleInfo],
)
async def schedule2(_: Annotated[str, Depends(verify_apitoken)]) -> Any:
"""
查询下载器信息 API_TOKEN认证(?token=xxx
"""
return Scheduler().list()
@router.get(
"/schedule2/{job_id}/progress",
summary="后台服务进度(API_TOKEN",
response_model=schemas.Response[schemas.ScheduleProgress],
)
async def schedule_progress2(
job_id: str, _: Annotated[str, Depends(verify_apitoken)]
) -> Any:
"""
查询指定后台服务的执行进度 API_TOKEN认证(?token=xxx
"""
progress = Scheduler().get_progress(job_id)
if not progress:
return schemas.Response(success=False, message="后台服务不存在")
return schemas.Response(success=True, data=progress.model_dump())
@router.get("/transfer", summary="文件整理统计", response_model=List[int])
async def transfer(
days: Optional[int] = 7,
db: Session = Depends(get_db),
_: Any = Depends(get_current_active_superuser),
) -> Any:
"""
查询文件整理统计信息
"""
transfer_stat = await TransferHistory.async_statistic(db, days)
return [stat[1] for stat in transfer_stat]
@router.get("/cpu", summary="获取当前CPU使用率", response_model=float)
def cpu(_: Any = Depends(get_current_active_superuser)) -> Any:
"""
获取当前CPU使用率
"""
return SystemUtils.cpu_usage()
@router.get("/cpu2", summary="获取当前CPU使用率(API_TOKEN", response_model=float)
def cpu2(_: Annotated[str, Depends(verify_apitoken)]) -> Any:
"""
获取当前CPU使用率 API_TOKEN认证(?token=xxx
"""
return SystemUtils.cpu_usage()
@router.get(
"/memory",
summary="获取当前应用与系统内存信息",
response_model=schemas.DashboardMemoryInfo,
)
def memory(_: Any = Depends(get_current_active_superuser)) -> Any:
"""
获取当前应用与系统内存信息
"""
return SystemUtils.memory_usage()
@router.get(
"/memory2",
summary="获取当前应用与系统内存信息(API_TOKEN",
response_model=schemas.DashboardMemoryInfo,
)
def memory2(_: Annotated[str, Depends(verify_apitoken)]) -> Any:
"""
获取当前应用与系统内存信息 API_TOKEN认证(?token=xxx
"""
return SystemUtils.memory_usage()
@router.get("/network", summary="获取当前网络流量", response_model=List[int])
def network(_: Any = Depends(get_current_active_superuser)) -> Any:
"""
获取当前网络流量(上行和下行流量,单位:bytes/s)
"""
return SystemUtils.network_usage()
@router.get(
"/network2", summary="获取当前网络流量(API_TOKEN", response_model=List[int]
)
def network2(_: Annotated[str, Depends(verify_apitoken)]) -> Any:
"""
获取当前网络流量 API_TOKEN认证(?token=xxx
"""
return SystemUtils.network_usage()