add dashboard apis

This commit is contained in:
jxxghp 2023-07-10 16:42:20 +08:00
parent 90002b6223
commit 15bb043fe8
10 changed files with 223 additions and 8 deletions

View File

@ -1,7 +1,7 @@
from fastapi import APIRouter
from app.api.endpoints import login, user, site, message, webhook, subscribe, \
media, douban, search, plugin, tmdb, history, system, download
media, douban, search, plugin, tmdb, history, system, download, dashboard
api_router = APIRouter()
api_router.include_router(login.router, tags=["login"])
@ -18,3 +18,4 @@ api_router.include_router(history.router, prefix="/history", tags=["history"])
api_router.include_router(system.router, prefix="/system", tags=["system"])
api_router.include_router(plugin.router, prefix="/plugin", tags=["plugin"])
api_router.include_router(download.router, prefix="/download", tags=["download"])
api_router.include_router(dashboard.router, prefix="/dashboard", tags=["dashboard"])

View File

@ -0,0 +1,46 @@
from pathlib import Path
from typing import Any, List
from fastapi import APIRouter, Depends
from app import schemas
from app.chain.dashboard import DashboardChain
from app.core.config import settings
from app.core.security import verify_token
from app.utils.system import SystemUtils
router = APIRouter()
@router.get("/statistic", summary="媒体数量统计", response_model=schemas.Statistic)
def statistic(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
"""
查询媒体数量统计信息
"""
media_statistic = DashboardChain().media_statistic()
return schemas.Statistic(
movie_count=media_statistic.movie_count,
tv_count=media_statistic.tv_count,
episode_count=media_statistic.episode_count,
user_count=media_statistic.user_count
)
@router.get("/storage", summary="存储空间", response_model=schemas.Storage)
def storage(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
"""
查询存储空间信息
"""
total_storage, used_storage = SystemUtils.space_usage(Path(settings.LIBRARY_PATH))
return schemas.Storage(
total_storage=total_storage,
used_storage=used_storage
)
@router.get("/processes", summary="进程信息", response_model=List[schemas.ProcessInfo])
def processes(_: schemas.TokenPayload = Depends(verify_token)) -> Any:
"""
进程信息
"""
return SystemUtils.processes()

13
app/chain/dashboard.py Normal file
View File

@ -0,0 +1,13 @@
from app import schemas
from app.chain import ChainBase
class DashboardChain(ChainBase):
"""
各类仪表板统计处理链
"""
def media_statistic(self) -> schemas.Statistic:
"""
媒体数量统计
"""
return self.run_module("media_statistic")

View File

@ -1,6 +1,7 @@
from pathlib import Path
from typing import Optional, Tuple, Union, Any
from app import schemas
from app.core.context import MediaInfo
from app.log import logger
from app.modules import _ModuleBase
@ -83,3 +84,16 @@ class EmbyModule(_ModuleBase):
)
]
return self.emby.refresh_library_by_items(items)
def media_statistic(self) -> schemas.Statistic:
"""
媒体数量统计
"""
media_statistic = self.emby.get_medias_count()
user_count = self.emby.get_user_count()
return schemas.Statistic(
movie_count=media_statistic.get("MovieCount") or 0,
tv_count=media_statistic.get("SeriesCount") or 0,
episode_count=media_statistic.get("EpisodeCount") or 0,
user_count=user_count or 0
)

View File

@ -2,6 +2,7 @@ import json
from pathlib import Path
from typing import Optional, Tuple, Union, Any
from app import schemas
from app.core.context import MediaInfo
from app.log import logger
from app.modules import _ModuleBase
@ -75,3 +76,16 @@ class JellyfinModule(_ModuleBase):
:return: 成功或失败
"""
return self.jellyfin.refresh_root_library()
def media_statistic(self) -> schemas.Statistic:
"""
媒体数量统计
"""
media_statistic = self.jellyfin.get_medias_count()
user_count = self.jellyfin.get_user_count()
return schemas.Statistic(
movie_count=media_statistic.get("MovieCount") or 0,
tv_count=media_statistic.get("SeriesCount") or 0,
episode_count=media_statistic.get("EpisodeCount") or 0,
user_count=user_count or 0
)

View File

@ -1,6 +1,7 @@
from pathlib import Path
from typing import Optional, Tuple, Union, Any
from app import schemas
from app.core.context import MediaInfo
from app.log import logger
from app.modules import _ModuleBase
@ -73,3 +74,15 @@ class PlexModule(_ModuleBase):
)
]
return self.plex.refresh_library_by_items(items)
def media_statistic(self) -> schemas.Statistic:
"""
媒体数量统计
"""
media_statistic = self.plex.get_medias_count()
return schemas.Statistic(
movie_count=media_statistic.get("MovieCount") or 0,
tv_count=media_statistic.get("SeriesCount") or 0,
episode_count=media_statistic.get("EpisodeCount") or 0,
user_count=1
)

View File

@ -7,3 +7,4 @@ from .context import *
from .servarr import *
from .plugin import *
from .history import *
from .dashboard import *

38
app/schemas/dashboard.py Normal file
View File

@ -0,0 +1,38 @@
from typing import Optional
from pydantic import BaseModel
class Statistic(BaseModel):
# 电影
movie_count: Optional[int] = 0
# 电视剧数量
tv_count: Optional[int] = 0
# 集数量
episode_count: Optional[int] = 0
# 用户数量
user_count: Optional[int] = 0
class Storage(BaseModel):
# 总存储空间
total_storage: Optional[float] = 0
# 已使用空间
used_storage: Optional[float] = 0
class ProcessInfo(BaseModel):
# 进程ID
pid: Optional[int] = 0
# 进程名称
name: Optional[str] = None
# 进程状态
status: Optional[str] = None
# 进程占用CPU
cpu: Optional[float] = 0.0
# 进程占用内存 MB
memory: Optional[float] = 0.0
# 进程创建时间
create_time: Optional[float] = 0.0
# 进程运行时间 秒
run_time: Optional[float] = 0.0

View File

@ -1,9 +1,12 @@
import datetime
import os
import platform
import re
import shutil
from pathlib import Path
from typing import List
from typing import List, Union, Tuple
import psutil
from app import schemas
class SystemUtils:
@ -39,7 +42,7 @@ class SystemUtils:
return True if platform.system() == 'Darwin' else False
@staticmethod
def copy(src: Path, dest: Path):
def copy(src: Path, dest: Path) -> Tuple[int, str]:
"""
复制
"""
@ -51,7 +54,7 @@ class SystemUtils:
return -1, str(err)
@staticmethod
def move(src: Path, dest: Path):
def move(src: Path, dest: Path) -> Tuple[int, str]:
"""
移动
"""
@ -63,7 +66,7 @@ class SystemUtils:
return -1, str(err)
@staticmethod
def link(src: Path, dest: Path):
def link(src: Path, dest: Path) -> Tuple[int, str]:
"""
硬链接
"""
@ -75,7 +78,7 @@ class SystemUtils:
return -1, str(err)
@staticmethod
def softlink(src: Path, dest: Path):
def softlink(src: Path, dest: Path) -> Tuple[int, str]:
"""
软链接
"""
@ -105,7 +108,7 @@ class SystemUtils:
return files
@staticmethod
def get_directory_size(path: Path):
def get_directory_size(path: Path) -> float:
"""
计算目录的大小
@ -125,3 +128,74 @@ class SystemUtils:
total_size += path.stat().st_size
return total_size
@staticmethod
def space_usage(dir_list: Union[Path, List[Path]]) -> Tuple[float, float]:
"""
计算多个目录的总可用空间/剩余空间单位Byte并去除重复磁盘
"""
if not dir_list:
return 0.0, 0.0
if not isinstance(dir_list, list):
dir_list = [dir_list]
# 存储不重复的磁盘
disk_set = set()
# 存储总剩余空间
total_free_space = 0.0
# 存储总空间
total_space = 0.0
for dir_path in dir_list:
if not dir_path:
continue
if not dir_path.exists():
continue
# 获取目录所在磁盘
if os.name == "nt":
disk = dir_path.drive
else:
disk = os.stat(dir_path).st_dev
# 如果磁盘未出现过,则计算其剩余空间并加入总剩余空间中
if disk not in disk_set:
disk_set.add(disk)
total_space += SystemUtils.total_space(dir_path)
total_free_space += SystemUtils.free_space(dir_path)
return total_space, total_free_space
@staticmethod
def free_space(path: Path) -> float:
"""
获取指定路径的剩余空间单位Byte
"""
if not os.path.exists(path):
return 0.0
return psutil.disk_usage(str(path)).free
@staticmethod
def total_space(path: Path) -> float:
"""
获取指定路径的总空间单位Byte
"""
if not os.path.exists(path):
return 0.0
return psutil.disk_usage(str(path)).total
@staticmethod
def processes() -> List[schemas.ProcessInfo]:
"""
获取所有进程
"""
processes = []
for proc in psutil.process_iter(['pid', 'name', 'create_time', 'memory_info', 'status']):
try:
if proc.status() != psutil.STATUS_ZOMBIE:
runtime = datetime.datetime.now() - datetime.datetime.fromtimestamp(
int(getattr(proc, 'create_time', 0)()))
mem_info = getattr(proc, 'memory_info', None)()
if mem_info is not None:
mem_mb = round(mem_info.rss / (1024 * 1024), 1)
processes.append(schemas.ProcessInfo(
pid=proc.pid, name=proc.name(), run_time=runtime.seconds, memory=mem_mb
))
except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
pass
return processes

View File

@ -43,3 +43,4 @@ starlette~=0.27.0
PyVirtualDisplay~=3.0
Cython~=0.29.35
tvdb_api~=3.1
psutil==5.9.4