diff --git a/package.json b/package.json index 99c7cee..5164bfd 100644 --- a/package.json +++ b/package.json @@ -684,5 +684,14 @@ "history": { "v1.1": "修复下载Bing每日壁纸时文件名错乱的问题" } + }, + "MPServerStatus": { + "name": "MoviePilot服务器状态", + "description": "在仪表板中实时显示MoviePilot公共服务器状态(https://movie-pilot.org)", + "labels": "工具", + "version": "1.0", + "icon": "Duplicati_A.png", + "author": "jxxghp", + "level": 1 } } diff --git a/plugins/mpserverstatus/__init__.py b/plugins/mpserverstatus/__init__.py new file mode 100644 index 0000000..1abe414 --- /dev/null +++ b/plugins/mpserverstatus/__init__.py @@ -0,0 +1,455 @@ +import time +from typing import List, Tuple, Dict, Any, Optional + +from app.log import logger +from app.plugins import _PluginBase +from app.utils.http import RequestUtils + + +class MPServerStatus(_PluginBase): + # 插件名称 + plugin_name = "MoviePilot服务器状态" + # 插件描述 + plugin_desc = "在仪表板中实时显示MoviePilot公共服务器状态(https://movie-pilot.org)" + # 插件图标 + plugin_icon = "Duplicati_A.png" + # 插件版本 + plugin_version = "1.0" + # 插件作者 + plugin_author = "jxxghp" + # 作者主页 + author_url = "https://github.com/jxxghp" + # 插件配置项ID前缀 + plugin_config_prefix = "MPServer_" + # 加载顺序 + plugin_order = 99 + # 可使用的用户级别 + auth_level = 1 + + _enable: bool = False + _server_base = "https://movie-pilot.org/status" + + def init_plugin(self, config: dict = None): + self._enable = config.get("enable") if config.get("enable") else False + + @staticmethod + def get_command() -> List[Dict[str, Any]]: + pass + + def get_api(self) -> List[Dict[str, Any]]: + pass + + def get_form(self) -> Tuple[List[dict], Dict[str, Any]]: + return [ + { + 'component': 'VForm', + 'content': [ + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 6 + }, + 'content': [ + { + 'component': 'VSwitch', + 'props': { + 'model': 'enable', + 'label': '启用插件', + } + } + ] + } + ] + } + ] + } + ], { + "enable": self._enable, + } + + def get_page(self) -> List[dict]: + pass + + def get_dashboard(self) -> Optional[Tuple[Dict[str, Any], Dict[str, Any], List[dict]]]: + """ + 获取插件仪表盘页面,需要返回:1、仪表板col配置字典;2、全局配置(自动刷新等);3、仪表板页面元素配置json(含数据) + 1、col配置参考: + { + "cols": 12, "md": 6 + } + 2、全局配置参考: + { + "refresh": 10 // 自动刷新时间,单位秒 + } + 3、页面配置使用Vuetify组件拼装,参考:https://vuetifyjs.com/ + """ + # 列配置 + cols = { + "cols": 12 + } + # 全局配置 + attrs = { + "refresh": 10 + } + # 读取服务器文本 + start_time = time.time() + logger.info(f"请求服务器状态 {self._server_base}...") + res = RequestUtils().get_res(self._server_base) + seconds = time.time() - start_time + logger.info(f"请求耗时:{seconds}秒") + if not res: + logger.warn(f"请求服务器状态失败:{res.status_code if res is not None else '网络错误'}") + elements = [ + { + 'component': 'div', + 'text': '无法连接服务器', + 'props': { + 'class': 'text-center', + } + } + ] + else: + """ + Active connections: 62 + server accepts handled requests + 468843 468843 1368256 + Reading: 0 Writing: 1 Waiting: 61 + """ + status_lines = res.text.strip().split('\n') + active_connections = int(status_lines[0].split(':')[1].strip()) + accepts, handled, requests = map(int, status_lines[1].split()) + reading, writing, waiting = map(int, status_lines[2].split(':')[1].split()) + elements = [ + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 6, + 'md': 3 + }, + 'content': [ + { + 'component': 'VCard', + 'props': { + 'variant': 'tonal', + }, + 'content': [ + { + 'component': 'VCardText', + 'props': { + 'class': 'd-flex align-center', + }, + 'content': [ + { + 'component': 'div', + 'content': [ + { + 'component': 'span', + 'props': { + 'class': 'text-caption' + }, + 'text': '连接耗时' + }, + { + 'component': 'div', + 'props': { + 'class': 'd-flex align-center flex-wrap' + }, + 'content': [ + { + 'component': 'span', + 'props': { + 'class': 'text-h6' + }, + 'text': f"{seconds:.2f} 秒" + } + ] + } + ] + } + ] + } + ] + }, + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 6, + 'md': 3 + }, + 'content': [ + { + 'component': 'VCard', + 'props': { + 'variant': 'tonal', + }, + 'content': [ + { + 'component': 'VCardText', + 'props': { + 'class': 'd-flex align-center', + }, + 'content': [ + { + 'component': 'div', + 'content': [ + { + 'component': 'span', + 'props': { + 'class': 'text-caption' + }, + 'text': '活跃连接' + }, + { + 'component': 'div', + 'props': { + 'class': 'd-flex align-center flex-wrap' + }, + 'content': [ + { + 'component': 'span', + 'props': { + 'class': 'text-h6' + }, + 'text': active_connections + } + ] + } + ] + } + ] + } + ] + }, + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 3, + 'sm': 6 + }, + 'content': [ + { + 'component': 'VCard', + 'props': { + 'variant': 'tonal', + }, + 'content': [ + { + 'component': 'VCardText', + 'props': { + 'class': 'd-flex align-center', + }, + 'content': [ + { + 'component': 'div', + 'content': [ + { + 'component': 'span', + 'props': { + 'class': 'text-caption' + }, + 'text': '等待连接' + }, + { + 'component': 'div', + 'props': { + 'class': 'd-flex align-center flex-wrap' + }, + 'content': [ + { + 'component': 'span', + 'props': { + 'class': 'text-h6' + }, + 'text': waiting + } + ] + } + ] + } + ] + } + ] + }, + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 3, + 'sm': 6 + }, + 'content': [ + { + 'component': 'VCard', + 'props': { + 'variant': 'tonal', + }, + 'content': [ + { + 'component': 'VCardText', + 'props': { + 'class': 'd-flex align-center', + }, + 'content': [ + { + 'component': 'div', + 'content': [ + { + 'component': 'span', + 'props': { + 'class': 'text-caption' + }, + 'text': '处理中连接' + }, + { + 'component': 'div', + 'props': { + 'class': 'd-flex align-center flex-wrap' + }, + 'content': [ + { + 'component': 'span', + 'props': { + 'class': 'text-h6' + }, + 'text': reading + writing + } + ] + } + ] + } + ] + } + ] + }, + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 3, + 'sm': 6 + }, + 'content': [ + { + 'component': 'VCard', + 'props': { + 'variant': 'tonal', + }, + 'content': [ + { + 'component': 'VCardText', + 'props': { + 'class': 'd-flex align-center', + }, + 'content': [ + { + 'component': 'div', + 'content': [ + { + 'component': 'span', + 'props': { + 'class': 'text-caption' + }, + 'text': '总请求数' + }, + { + 'component': 'div', + 'props': { + 'class': 'd-flex align-center flex-wrap' + }, + 'content': [ + { + 'component': 'span', + 'props': { + 'class': 'text-h6' + }, + 'text': requests + } + ] + } + ] + } + ] + } + ] + }, + ] + }, + { + 'component': 'VCol', + 'props': { + 'cols': 12, + 'md': 3, + 'sm': 6 + }, + 'content': [ + { + 'component': 'VCard', + 'props': { + 'variant': 'tonal', + }, + 'content': [ + { + 'component': 'VCardText', + 'props': { + 'class': 'd-flex align-center', + }, + 'content': [ + { + 'component': 'div', + 'content': [ + { + 'component': 'span', + 'props': { + 'class': 'text-caption' + }, + 'text': '总连接数' + }, + { + 'component': 'div', + 'props': { + 'class': 'd-flex align-center flex-wrap' + }, + 'content': [ + { + 'component': 'span', + 'props': { + 'class': 'text-h6' + }, + 'text': accepts + } + ] + } + ] + } + ] + } + ] + }, + ] + } + ] + }] + + return cols, attrs, elements + + def get_state(self) -> bool: + return self._enable + + def stop_service(self): + pass