feat:插件仪表板API
This commit is contained in:
@ -125,6 +125,22 @@ def plugin_page(plugin_id: str, _: schemas.TokenPayload = Depends(verify_token))
|
|||||||
return PluginManager().get_plugin_page(plugin_id)
|
return PluginManager().get_plugin_page(plugin_id)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/dashboards", summary="获取有仪表板的插件清单")
|
||||||
|
def dashboard_plugins(_: schemas.TokenPayload = Depends(verify_token)) -> List[dict]:
|
||||||
|
"""
|
||||||
|
获取所有插件仪表板
|
||||||
|
"""
|
||||||
|
return PluginManager().get_dashboard_plugins()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/dashboard/{plugin_id}", summary="获取插件仪表板配置")
|
||||||
|
def plugin_dashboard(plugin_id: str, _: schemas.TokenPayload = Depends(verify_token)) -> schemas.PluginDashboard:
|
||||||
|
"""
|
||||||
|
根据插件ID获取插件仪表板
|
||||||
|
"""
|
||||||
|
return PluginManager().get_plugin_dashboard(plugin_id)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/reset/{plugin_id}", summary="重置插件配置", response_model=schemas.Response)
|
@router.get("/reset/{plugin_id}", summary="重置插件配置", response_model=schemas.Response)
|
||||||
def reset_plugin(plugin_id: str, _: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
def reset_plugin(plugin_id: str, _: schemas.TokenPayload = Depends(verify_token)) -> Any:
|
||||||
"""
|
"""
|
||||||
|
@ -217,10 +217,11 @@ class PluginManager(metaclass=Singleton):
|
|||||||
获取插件表单
|
获取插件表单
|
||||||
:param pid: 插件ID
|
:param pid: 插件ID
|
||||||
"""
|
"""
|
||||||
if not self._running_plugins.get(pid):
|
plugin = self._running_plugins.get(pid)
|
||||||
|
if not plugin:
|
||||||
return [], {}
|
return [], {}
|
||||||
if hasattr(self._running_plugins[pid], "get_form"):
|
if hasattr(plugin, "get_form"):
|
||||||
return self._running_plugins[pid].get_form() or ([], {})
|
return plugin.get_form() or ([], {})
|
||||||
return [], {}
|
return [], {}
|
||||||
|
|
||||||
def get_plugin_page(self, pid: str) -> List[dict]:
|
def get_plugin_page(self, pid: str) -> List[dict]:
|
||||||
@ -228,12 +229,34 @@ class PluginManager(metaclass=Singleton):
|
|||||||
获取插件页面
|
获取插件页面
|
||||||
:param pid: 插件ID
|
:param pid: 插件ID
|
||||||
"""
|
"""
|
||||||
if not self._running_plugins.get(pid):
|
plugin = self._running_plugins.get(pid)
|
||||||
|
if not plugin:
|
||||||
return []
|
return []
|
||||||
if hasattr(self._running_plugins[pid], "get_page"):
|
if hasattr(plugin, "get_page"):
|
||||||
return self._running_plugins[pid].get_page() or []
|
return plugin.get_page() or []
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
def get_plugin_dashboard(self, pid: str) -> Optional[schemas.PluginDashboard]:
|
||||||
|
"""
|
||||||
|
获取插件仪表盘
|
||||||
|
:param pid: 插件ID
|
||||||
|
"""
|
||||||
|
plugin = self._running_plugins.get(pid)
|
||||||
|
if not plugin:
|
||||||
|
return None
|
||||||
|
if hasattr(plugin, "get_dashboard"):
|
||||||
|
dashboard: Tuple = plugin.get_dashboard()
|
||||||
|
if dashboard:
|
||||||
|
cols, attrs, elements = dashboard
|
||||||
|
return schemas.PluginDashboard(
|
||||||
|
id=pid,
|
||||||
|
name=plugin.plugin_name,
|
||||||
|
cols=cols or {},
|
||||||
|
elements=elements,
|
||||||
|
attrs=attrs or {}
|
||||||
|
)
|
||||||
|
return None
|
||||||
|
|
||||||
def get_plugin_commands(self) -> List[Dict[str, Any]]:
|
def get_plugin_commands(self) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
获取插件命令
|
获取插件命令
|
||||||
@ -301,17 +324,35 @@ class PluginManager(metaclass=Singleton):
|
|||||||
logger.error(f"获取插件 {pid} 服务出错:{str(e)}")
|
logger.error(f"获取插件 {pid} 服务出错:{str(e)}")
|
||||||
return ret_services
|
return ret_services
|
||||||
|
|
||||||
|
def get_dashboard_plugins(self) -> List[dict]:
|
||||||
|
"""
|
||||||
|
获取有仪表盘的插件列表
|
||||||
|
"""
|
||||||
|
dashboards = []
|
||||||
|
for pid, plugin in self._running_plugins.items():
|
||||||
|
if hasattr(plugin, "get_dashboard") \
|
||||||
|
and ObjectUtils.check_method(plugin.get_dashboard):
|
||||||
|
try:
|
||||||
|
dashboards.append({
|
||||||
|
"id": pid,
|
||||||
|
"name": plugin.plugin_name
|
||||||
|
})
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取有仪表盘的插件出错:{str(e)}")
|
||||||
|
return dashboards
|
||||||
|
|
||||||
def get_plugin_attr(self, pid: str, attr: str) -> Any:
|
def get_plugin_attr(self, pid: str, attr: str) -> Any:
|
||||||
"""
|
"""
|
||||||
获取插件属性
|
获取插件属性
|
||||||
:param pid: 插件ID
|
:param pid: 插件ID
|
||||||
:param attr: 属性名
|
:param attr: 属性名
|
||||||
"""
|
"""
|
||||||
if not self._running_plugins.get(pid):
|
plugin = self._running_plugins.get(pid)
|
||||||
|
if not plugin:
|
||||||
return None
|
return None
|
||||||
if not hasattr(self._running_plugins[pid], attr):
|
if not hasattr(plugin, attr):
|
||||||
return None
|
return None
|
||||||
return getattr(self._running_plugins[pid], attr)
|
return getattr(plugin, attr)
|
||||||
|
|
||||||
def run_plugin_method(self, pid: str, method: str, *args, **kwargs) -> Any:
|
def run_plugin_method(self, pid: str, method: str, *args, **kwargs) -> Any:
|
||||||
"""
|
"""
|
||||||
@ -321,11 +362,12 @@ class PluginManager(metaclass=Singleton):
|
|||||||
:param args: 参数
|
:param args: 参数
|
||||||
:param kwargs: 关键字参数
|
:param kwargs: 关键字参数
|
||||||
"""
|
"""
|
||||||
if not self._running_plugins.get(pid):
|
plugin = self._running_plugins.get(pid)
|
||||||
|
if not plugin:
|
||||||
return None
|
return None
|
||||||
if not hasattr(self._running_plugins[pid], method):
|
if not hasattr(plugin, method):
|
||||||
return None
|
return None
|
||||||
return getattr(self._running_plugins[pid], method)(*args, **kwargs)
|
return getattr(plugin, method)(*args, **kwargs)
|
||||||
|
|
||||||
def get_plugin_ids(self) -> List[str]:
|
def get_plugin_ids(self) -> List[str]:
|
||||||
"""
|
"""
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
from abc import ABCMeta, abstractmethod
|
from abc import ABCMeta, abstractmethod
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, List, Dict, Tuple
|
from typing import Any, List, Dict, Tuple, Optional
|
||||||
|
|
||||||
from app.chain import ChainBase
|
from app.chain import ChainBase
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
@ -55,6 +55,13 @@ class _PluginBase(metaclass=ABCMeta):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def get_state(self) -> bool:
|
||||||
|
"""
|
||||||
|
获取插件运行状态
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_command() -> List[Dict[str, Any]]:
|
def get_command() -> List[Dict[str, Any]]:
|
||||||
@ -84,19 +91,6 @@ class _PluginBase(metaclass=ABCMeta):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_service(self) -> List[Dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
注册插件公共服务
|
|
||||||
[{
|
|
||||||
"id": "服务ID",
|
|
||||||
"name": "服务名称",
|
|
||||||
"trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()",
|
|
||||||
"func": self.xxx,
|
|
||||||
"kwargs": {} # 定时器参数
|
|
||||||
}]
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_form(self) -> Tuple[List[dict], Dict[str, Any]]:
|
def get_form(self) -> Tuple[List[dict], Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
@ -113,10 +107,31 @@ class _PluginBase(metaclass=ABCMeta):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
def get_service(self) -> List[Dict[str, Any]]:
|
||||||
def get_state(self) -> bool:
|
|
||||||
"""
|
"""
|
||||||
获取插件运行状态
|
注册插件公共服务
|
||||||
|
[{
|
||||||
|
"id": "服务ID",
|
||||||
|
"name": "服务名称",
|
||||||
|
"trigger": "触发器:cron/interval/date/CronTrigger.from_crontab()",
|
||||||
|
"func": self.xxx,
|
||||||
|
"kwargs": {} # 定时器参数
|
||||||
|
}]
|
||||||
|
"""
|
||||||
|
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/
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from typing import Optional
|
from typing import Optional, List
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
@ -46,3 +46,18 @@ class Plugin(BaseModel):
|
|||||||
history: Optional[dict] = {}
|
history: Optional[dict] = {}
|
||||||
# 添加时间,值越小表示越靠后发布
|
# 添加时间,值越小表示越靠后发布
|
||||||
add_time: Optional[int] = 0
|
add_time: Optional[int] = 0
|
||||||
|
|
||||||
|
|
||||||
|
class PluginDashboard(Plugin):
|
||||||
|
"""
|
||||||
|
插件仪表盘
|
||||||
|
"""
|
||||||
|
id: Optional[str] = None
|
||||||
|
# 名称
|
||||||
|
name: Optional[str] = None
|
||||||
|
# 全局配置
|
||||||
|
attrs: Optional[dict] = {}
|
||||||
|
# col列数
|
||||||
|
cols: Optional[dict] = {}
|
||||||
|
# 页面元素
|
||||||
|
elements: Optional[List[dict]] = []
|
||||||
|
@ -35,7 +35,21 @@ class ObjectUtils:
|
|||||||
"""
|
"""
|
||||||
检查函数是否已实现
|
检查函数是否已实现
|
||||||
"""
|
"""
|
||||||
return func.__code__.co_code not in [b'd\x01S\x00', b'\x97\x00d\x00S\x00']
|
source = inspect.getsource(func)
|
||||||
|
in_comment = False
|
||||||
|
for line in source.split('\n'):
|
||||||
|
line = line.strip()
|
||||||
|
if not line:
|
||||||
|
continue
|
||||||
|
if line.startswith('"""') or line.startswith("'''"):
|
||||||
|
in_comment = not in_comment
|
||||||
|
continue
|
||||||
|
if not in_comment and not (line.startswith('#')
|
||||||
|
or line == "pass"
|
||||||
|
or line.startswith('@')
|
||||||
|
or line.startswith('def ')):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def check_signature(func: FunctionType, *args) -> bool:
|
def check_signature(func: FunctionType, *args) -> bool:
|
||||||
|
Reference in New Issue
Block a user