mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-05 15:38:19 +08:00
fix plugins
This commit is contained in:
+37
-104
@@ -1,6 +1,5 @@
|
|||||||
import inspect
|
|
||||||
import mimetypes
|
import mimetypes
|
||||||
from typing import Annotated, Any, List, Optional, Callable, Tuple
|
from typing import Annotated, Any, List, Optional
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, Header, HTTPException
|
from fastapi import APIRouter, Depends, Header, HTTPException
|
||||||
from starlette import status
|
from starlette import status
|
||||||
@@ -10,7 +9,7 @@ from app import schemas
|
|||||||
from app.command import Command
|
from app.command import Command
|
||||||
from app.core.config import settings
|
from app.core.config import settings
|
||||||
from app.core.plugin import PluginManager
|
from app.core.plugin import PluginManager
|
||||||
from app.core.security import verify_apikey, verify_token
|
from app.core.security import verify_apikey, verify_token, verify_apitoken
|
||||||
from app.db.systemconfig_oper import SystemConfigOper
|
from app.db.systemconfig_oper import SystemConfigOper
|
||||||
from app.db.user_oper import get_current_active_superuser
|
from app.db.user_oper import get_current_active_superuser
|
||||||
from app.factory import app
|
from app.factory import app
|
||||||
@@ -221,6 +220,16 @@ def install(plugin_id: str,
|
|||||||
return schemas.Response(success=True)
|
return schemas.Response(success=True)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/remotes", summary="获取插件联邦组件列表", response_model=List[dict])
|
||||||
|
def remotes(token: str) -> Any:
|
||||||
|
"""
|
||||||
|
获取插件联邦组件列表
|
||||||
|
"""
|
||||||
|
if token != "moviepilot":
|
||||||
|
raise HTTPException(status_code=403, detail="Forbidden")
|
||||||
|
return PluginManager().get_plugin_remotes()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/form/{plugin_id}", summary="获取插件表单页面")
|
@router.get("/form/{plugin_id}", summary="获取插件表单页面")
|
||||||
def plugin_form(plugin_id: str,
|
def plugin_form(plugin_id: str,
|
||||||
_: schemas.TokenPayload = Depends(get_current_active_superuser)) -> dict:
|
_: schemas.TokenPayload = Depends(get_current_active_superuser)) -> dict:
|
||||||
@@ -232,29 +241,16 @@ def plugin_form(plugin_id: str,
|
|||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"插件 {plugin_id} 不存在或未加载")
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"插件 {plugin_id} 不存在或未加载")
|
||||||
|
|
||||||
# 渲染模式
|
# 渲染模式
|
||||||
render_mode = plugin_instance.get_render_mode()
|
render_mode, _ = plugin_instance.get_render_mode()
|
||||||
if render_mode == "vue":
|
try:
|
||||||
# Vue模式
|
conf, model = plugin_instance.get_form()
|
||||||
try:
|
return {
|
||||||
vue_component_file, model = plugin_instance.get_form_file()
|
"render_mode": render_mode,
|
||||||
return {
|
"conf": conf,
|
||||||
"render_mode": "vue",
|
"model": PluginManager().get_plugin_config(plugin_id) or model
|
||||||
"component_url": f"/plugin/file/{plugin_id.lower()}/{vue_component_file}",
|
}
|
||||||
"model": PluginManager().get_plugin_config(plugin_id) or model
|
except Exception as e:
|
||||||
}
|
logger.error(f"插件 {plugin_id} 调用方法 get_form 出错: {str(e)}")
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"插件 {plugin_id} 调用方法 get_form_file 出错: {str(e)}")
|
|
||||||
else:
|
|
||||||
# Vuetify模式
|
|
||||||
try:
|
|
||||||
conf, model = plugin_instance.get_form()
|
|
||||||
return {
|
|
||||||
"render_mode": "vuetify",
|
|
||||||
"conf": conf,
|
|
||||||
"model": PluginManager().get_plugin_config(plugin_id) or model
|
|
||||||
}
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"插件 {plugin_id} 调用方法 get_form 出错: {str(e)}")
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
@@ -268,27 +264,15 @@ def plugin_page(plugin_id: str, _: schemas.TokenPayload = Depends(get_current_ac
|
|||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"插件 {plugin_id} 不存在或未加载")
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"插件 {plugin_id} 不存在或未加载")
|
||||||
|
|
||||||
# 渲染模式
|
# 渲染模式
|
||||||
render_mode = plugin_instance.get_render_mode()
|
render_mode, _ = plugin_instance.get_render_mode()
|
||||||
if render_mode == "vue":
|
try:
|
||||||
# Vue模式
|
page = plugin_instance.get_page()
|
||||||
try:
|
return {
|
||||||
vue_component_file = plugin_instance.get_page_file()
|
"render_mode": render_mode,
|
||||||
return {
|
"page": page or []
|
||||||
"render_mode": "vue",
|
}
|
||||||
"component_url": f"/plugin/file/{plugin_id.lower()}/{vue_component_file}",
|
except Exception as e:
|
||||||
}
|
logger.error(f"插件 {plugin_id} 调用方法 get_page 出错: {str(e)}")
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"插件 {plugin_id} 调用方法 get_page_file 出错: {str(e)}")
|
|
||||||
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
page = plugin_instance.get_page()
|
|
||||||
return {
|
|
||||||
"render_mode": "vuetify",
|
|
||||||
"page": page or []
|
|
||||||
}
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"插件 {plugin_id} 调用方法 get_page 出错: {str(e)}")
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
@@ -302,62 +286,11 @@ def plugin_dashboard_meta(_: schemas.TokenPayload = Depends(verify_token)) -> Li
|
|||||||
|
|
||||||
@router.get("/dashboard/{plugin_id}/{key}", summary="获取插件仪表板配置")
|
@router.get("/dashboard/{plugin_id}/{key}", summary="获取插件仪表板配置")
|
||||||
def plugin_dashboard_by_key(plugin_id: str, key: str, user_agent: Annotated[str | None, Header()] = None,
|
def plugin_dashboard_by_key(plugin_id: str, key: str, user_agent: Annotated[str | None, Header()] = None,
|
||||||
_: schemas.TokenPayload = Depends(verify_token)) -> Optional[schemas.PluginDashboard]:
|
_: schemas.TokenPayload = Depends(verify_token)) -> Optional[schemas.PluginDashboard]:
|
||||||
"""
|
"""
|
||||||
根据插件ID获取插件仪表板
|
根据插件ID获取插件仪表板
|
||||||
"""
|
"""
|
||||||
|
return PluginManager().get_plugin_dashboard(plugin_id, key, user_agent)
|
||||||
def __get_params_count(func: Callable):
|
|
||||||
"""
|
|
||||||
获取函数的参数信息
|
|
||||||
"""
|
|
||||||
signature = inspect.signature(func)
|
|
||||||
return len(signature.parameters)
|
|
||||||
|
|
||||||
# 获取插件实例
|
|
||||||
plugin_instance = PluginManager().running_plugins.get(plugin_id)
|
|
||||||
if not plugin_instance:
|
|
||||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"插件 {plugin_id} 不存在或未加载")
|
|
||||||
# 渲染模式
|
|
||||||
render_mode = plugin_instance.get_render_mode()
|
|
||||||
if render_mode == "vue":
|
|
||||||
# Vue模式
|
|
||||||
try:
|
|
||||||
cols, attrs, vue_component_file = plugin_instance.get_dashboard_file(key=key, user_agent=user_agent)
|
|
||||||
return schemas.PluginDashboard(
|
|
||||||
id=plugin_id,
|
|
||||||
name=plugin_instance.plugin_name,
|
|
||||||
key=key,
|
|
||||||
render_mode=render_mode,
|
|
||||||
cols=cols or {},
|
|
||||||
attrs=attrs or {},
|
|
||||||
component_url=f"/plugin/file/{plugin_id.lower()}/{vue_component_file}",
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"插件 {plugin_id} 调用方法 get_dashboard_file 出错: {str(e)}")
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
# 检查方法的参数个数
|
|
||||||
params_count = __get_params_count(plugin_instance.get_dashboard)
|
|
||||||
if params_count > 1:
|
|
||||||
dashboard: Tuple = plugin_instance.get_dashboard(key=key, user_agent=user_agent)
|
|
||||||
elif params_count > 0:
|
|
||||||
dashboard: Tuple = plugin_instance.get_dashboard(user_agent=user_agent)
|
|
||||||
else:
|
|
||||||
dashboard: Tuple = plugin_instance.get_dashboard()
|
|
||||||
if dashboard:
|
|
||||||
cols, attrs, elements = dashboard
|
|
||||||
return schemas.PluginDashboard(
|
|
||||||
id=plugin_id,
|
|
||||||
name=plugin_instance.plugin_name,
|
|
||||||
key=key,
|
|
||||||
render_mode="vuetify",
|
|
||||||
cols=cols or {},
|
|
||||||
elements=elements,
|
|
||||||
attrs=attrs or {}
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"插件 {plugin_id} 调用方法 get_dashboard 出错: {str(e)}")
|
|
||||||
|
|
||||||
|
|
||||||
@router.get("/dashboard/{plugin_id}", summary="获取插件仪表板配置")
|
@router.get("/dashboard/{plugin_id}", summary="获取插件仪表板配置")
|
||||||
@@ -390,8 +323,8 @@ def reset_plugin(plugin_id: str,
|
|||||||
return schemas.Response(success=True)
|
return schemas.Response(success=True)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/file/{plugin_id}/{filepath:path}", summary="获取插件静态文件")
|
@router.get("/file/{plugin_id}/{filepath:path}", summary="获取插件静态文件")
|
||||||
def plugin_static_file(plugin_id: str, filepath: str, _: schemas.TokenPayload = Depends(verify_token)):
|
def plugin_static_file(plugin_id: str, filepath: str, _: Annotated[str, Depends(verify_apitoken)]):
|
||||||
"""
|
"""
|
||||||
获取插件静态文件
|
获取插件静态文件
|
||||||
"""
|
"""
|
||||||
@@ -413,9 +346,9 @@ def plugin_static_file(plugin_id: str, filepath: str, _: schemas.TokenPayload =
|
|||||||
# 强制修正 .mjs 和 .js 的 MIME 类型
|
# 强制修正 .mjs 和 .js 的 MIME 类型
|
||||||
if suffix in ['.js', '.mjs']:
|
if suffix in ['.js', '.mjs']:
|
||||||
response_type = 'application/javascript'
|
response_type = 'application/javascript'
|
||||||
elif suffix == '.css' and not response_type: # 如果 guess_type 没猜对 css,也修正
|
elif suffix == '.css' and not response_type: # 如果 guess_type 没猜对 css,也修正
|
||||||
response_type = 'text/css'
|
response_type = 'text/css'
|
||||||
elif not response_type: # 对于其他猜不出的类型
|
elif not response_type: # 对于其他猜不出的类型
|
||||||
response_type = 'application/octet-stream'
|
response_type = 'application/octet-stream'
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
+81
-2
@@ -1,13 +1,16 @@
|
|||||||
import concurrent
|
import concurrent
|
||||||
import concurrent.futures
|
import concurrent.futures
|
||||||
import importlib.util
|
import importlib.util
|
||||||
|
import inspect
|
||||||
import os
|
import os
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
from concurrent.futures import ThreadPoolExecutor, as_completed
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Optional, Type, Union
|
from typing import Any, Dict, List, Optional, Type, Union, Callable, Tuple
|
||||||
|
|
||||||
|
from fastapi import HTTPException
|
||||||
|
from starlette import status
|
||||||
from watchdog.events import FileSystemEventHandler
|
from watchdog.events import FileSystemEventHandler
|
||||||
from watchdog.observers import Observer
|
from watchdog.observers import Observer
|
||||||
|
|
||||||
@@ -526,7 +529,40 @@ class PluginManager(metaclass=Singleton):
|
|||||||
logger.error(f"获取插件 {plugin_id} 模块出错:{str(e)}")
|
logger.error(f"获取插件 {plugin_id} 模块出错:{str(e)}")
|
||||||
return ret_modules
|
return ret_modules
|
||||||
|
|
||||||
def get_plugin_dashboard_meta(self):
|
@staticmethod
|
||||||
|
def get_plugin_remote_entry(plugin_id: str, dist_path: str) -> str:
|
||||||
|
"""
|
||||||
|
获取插件的远程入口地址
|
||||||
|
:param plugin_id: 插件 ID
|
||||||
|
:param dist_path: 插件的分发路径
|
||||||
|
:return: 远程入口地址
|
||||||
|
"""
|
||||||
|
if dist_path.startswith("/"):
|
||||||
|
dist_path = dist_path[1:]
|
||||||
|
if dist_path.endswith("/"):
|
||||||
|
dist_path = dist_path[:-1]
|
||||||
|
return f"/plugin/file/{plugin_id.lower()}/{dist_path}/remoteEntry.js?token={settings.API_TOKEN}"
|
||||||
|
|
||||||
|
def get_plugin_remotes(self, pid: Optional[str] = None) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
获取插件联邦组件列表
|
||||||
|
"""
|
||||||
|
remotes = []
|
||||||
|
for plugin_id, plugin in self._running_plugins.items():
|
||||||
|
if pid and pid != plugin_id:
|
||||||
|
continue
|
||||||
|
if hasattr(plugin, "get_render_mode"):
|
||||||
|
render_mode, dist_path = plugin.get_render_mode()
|
||||||
|
if render_mode != "vue":
|
||||||
|
continue
|
||||||
|
remotes.append({
|
||||||
|
"id": plugin_id,
|
||||||
|
"url": self.get_plugin_remote_entry(plugin_id, dist_path),
|
||||||
|
"name": plugin.plugin_name,
|
||||||
|
})
|
||||||
|
return remotes
|
||||||
|
|
||||||
|
def get_plugin_dashboard_meta(self) -> List[Dict[str, str]]:
|
||||||
"""
|
"""
|
||||||
获取所有插件仪表盘元信息
|
获取所有插件仪表盘元信息
|
||||||
"""
|
"""
|
||||||
@@ -556,6 +592,49 @@ class PluginManager(metaclass=Singleton):
|
|||||||
logger.error(f"获取插件[{plugin_id}]仪表盘元数据出错:{str(e)}")
|
logger.error(f"获取插件[{plugin_id}]仪表盘元数据出错:{str(e)}")
|
||||||
return dashboard_meta
|
return dashboard_meta
|
||||||
|
|
||||||
|
def get_plugin_dashboard(self, pid: str, key: str, user_agent: str = None) -> schemas.PluginDashboard:
|
||||||
|
"""
|
||||||
|
获取插件仪表盘
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __get_params_count(func: Callable):
|
||||||
|
"""
|
||||||
|
获取函数的参数信息
|
||||||
|
"""
|
||||||
|
signature = inspect.signature(func)
|
||||||
|
return len(signature.parameters)
|
||||||
|
|
||||||
|
# 获取插件实例
|
||||||
|
plugin_instance = self.running_plugins.get(pid)
|
||||||
|
if not plugin_instance:
|
||||||
|
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"插件 {pid} 不存在或未加载")
|
||||||
|
|
||||||
|
# 渲染模式
|
||||||
|
render_mode, _ = plugin_instance.get_render_mode()
|
||||||
|
# 获取插件仪表板
|
||||||
|
try:
|
||||||
|
# 检查方法的参数个数
|
||||||
|
params_count = __get_params_count(plugin_instance.get_dashboard)
|
||||||
|
if params_count > 1:
|
||||||
|
dashboard: Tuple = plugin_instance.get_dashboard(key=key, user_agent=user_agent)
|
||||||
|
elif params_count > 0:
|
||||||
|
dashboard: Tuple = plugin_instance.get_dashboard(user_agent=user_agent)
|
||||||
|
else:
|
||||||
|
dashboard: Tuple = plugin_instance.get_dashboard()
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"插件 {pid} 调用方法 get_dashboard 出错: {str(e)}")
|
||||||
|
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||||
|
detail=f"插件 {pid} 调用方法 get_dashboard 出错: {str(e)}")
|
||||||
|
cols, attrs, elements = dashboard
|
||||||
|
return schemas.PluginDashboard(
|
||||||
|
id=pid,
|
||||||
|
name=plugin_instance.plugin_name,
|
||||||
|
key=key,
|
||||||
|
render_mode=render_mode,
|
||||||
|
cols=cols or {},
|
||||||
|
attrs=attrs or {},
|
||||||
|
)
|
||||||
|
|
||||||
def get_plugin_attr(self, pid: str, attr: str) -> Any:
|
def get_plugin_attr(self, pid: str, attr: str) -> Any:
|
||||||
"""
|
"""
|
||||||
获取插件属性
|
获取插件属性
|
||||||
|
|||||||
+10
-32
@@ -84,12 +84,12 @@ class _PluginBase(metaclass=ABCMeta):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_render_mode() -> str:
|
def get_render_mode() -> Tuple[str, Optional[str]]:
|
||||||
"""
|
"""
|
||||||
获取插件渲染模式
|
获取插件渲染模式
|
||||||
:return: 渲染模式,支持:vue/vuetify,默认vuetify
|
:return: 1、渲染模式,支持:vue/vuetify,默认vuetify;2、vue模式下编译后文件的相对路径,默认为`dist`,vuetify模式下为None
|
||||||
"""
|
"""
|
||||||
return "vuetify"
|
return "vuetify", None
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_api(self) -> List[Dict[str, Any]]:
|
def get_api(self) -> List[Dict[str, Any]]:
|
||||||
@@ -106,34 +106,19 @@ class _PluginBase(metaclass=ABCMeta):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_form(self) -> Tuple[List[dict], Dict[str, Any]]:
|
def get_form(self) -> Tuple[Optional[List[dict]], Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
拼装插件配置页面,插件配置页面使用Vuetify组件拼装,参考:https://vuetifyjs.com/
|
拼装插件配置页面,插件配置页面使用Vuetify组件拼装,参考:https://vuetifyjs.com/
|
||||||
:return: 1、页面配置;2、默认数据结构
|
:return: 1、页面配置(vuetify模式)或 None(vue模式);2、默认数据结构
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_form_file() -> Tuple[str, Dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
获取插件配置页面JS代码源文件(与get_from二选一使用)
|
|
||||||
:return: 1、编译后的JS代码插件目录下相对路径;2、默认数据结构
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_page(self) -> List[dict]:
|
def get_page(self) -> Optional[List[dict]]:
|
||||||
"""
|
"""
|
||||||
拼装插件详情页面,需要返回页面配置,同时附带数据
|
拼装插件详情页面,需要返回页面配置,同时附带数据
|
||||||
插件详情页面使用Vuetify组件拼装,参考:https://vuetifyjs.com/
|
插件详情页面使用Vuetify组件拼装,参考:https://vuetifyjs.com/
|
||||||
"""
|
:return: 页面配置(vuetify模式)或 None(vue模式)
|
||||||
pass
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def get_page_file() -> Optional[str]:
|
|
||||||
"""
|
|
||||||
获取插件数据页面JS代码源文件(与get_page二选一使用)
|
|
||||||
:return: 编译后的JS代码插件目录下相对路径
|
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -150,9 +135,9 @@ class _PluginBase(metaclass=ABCMeta):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_dashboard(self, key: str, **kwargs) -> Optional[Tuple[Dict[str, Any], Dict[str, Any], List[dict]]]:
|
def get_dashboard(self, key: str, **kwargs) -> Optional[Tuple[Dict[str, Any], Dict[str, Any], Optional[List[dict]]]]:
|
||||||
"""
|
"""
|
||||||
获取插件仪表盘页面,需要返回:1、仪表板col配置字典;2、全局配置(布局、自动刷新等);3、仪表板页面元素配置json(含数据)
|
获取插件仪表盘页面,需要返回:1、仪表板col配置字典;2、全局配置(布局、自动刷新等);3、仪表板页面元素配置含数据json(vuetify)或 None(vue模式)
|
||||||
1、col配置参考:
|
1、col配置参考:
|
||||||
{
|
{
|
||||||
"cols": 12, "md": 6
|
"cols": 12, "md": 6
|
||||||
@@ -164,7 +149,7 @@ class _PluginBase(metaclass=ABCMeta):
|
|||||||
"title": "组件标题", // 组件标题,如有将显示该标题,否则显示插件名称
|
"title": "组件标题", // 组件标题,如有将显示该标题,否则显示插件名称
|
||||||
"subtitle": "组件子标题", // 组件子标题,缺省时不展示子标题
|
"subtitle": "组件子标题", // 组件子标题,缺省时不展示子标题
|
||||||
}
|
}
|
||||||
3、页面配置使用Vuetify组件拼装,参考:https://vuetifyjs.com/
|
3、vuetify模式页面配置使用Vuetify组件拼装,参考:https://vuetifyjs.com/;vue模式为None
|
||||||
|
|
||||||
kwargs参数可获取的值:1、user_agent:浏览器UA
|
kwargs参数可获取的值:1、user_agent:浏览器UA
|
||||||
|
|
||||||
@@ -172,13 +157,6 @@ class _PluginBase(metaclass=ABCMeta):
|
|||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def get_dashboard_file(self, key: str, **kwargs) -> Optional[Tuple[Dict[str, Any], Dict[str, Any], str]]:
|
|
||||||
"""
|
|
||||||
获取插件仪表盘页面JS代码源文件(与get_dashboard二选一使用)
|
|
||||||
:return: 1、全局配置(布局、自动刷新等);2、仪表板页面元素配置json(含数据);3、编译后的JS代码插件目录下相对路径
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def get_dashboard_meta(self) -> Optional[List[Dict[str, str]]]:
|
def get_dashboard_meta(self) -> Optional[List[Dict[str, str]]]:
|
||||||
"""
|
"""
|
||||||
获取插件仪表盘元信息
|
获取插件仪表盘元信息
|
||||||
|
|||||||
@@ -67,5 +67,3 @@ class PluginDashboard(Plugin):
|
|||||||
cols: Optional[dict] = Field(default_factory=dict)
|
cols: Optional[dict] = Field(default_factory=dict)
|
||||||
# 页面元素
|
# 页面元素
|
||||||
elements: Optional[List[dict]] = Field(default_factory=list)
|
elements: Optional[List[dict]] = Field(default_factory=list)
|
||||||
# 页面地址
|
|
||||||
component_url: Optional[str] = None
|
|
||||||
|
|||||||
Reference in New Issue
Block a user