新增简易的8080面板😊

This commit is contained in:
Falck
2026-04-17 23:15:15 +08:00
parent c38d2f66d1
commit 9d19d09821
465 changed files with 9235 additions and 35285 deletions

View File

@@ -0,0 +1,8 @@
{
"signature": "SizmRKKsPO3WuOYi+GtSOvKwZb5UrwRbSlJNJ26RF7l7811PLQlrBPJ7Awx1SUwy50TLrDpwtqbRIdCnGVqI9yzghBhdkwz7dpaAQ//lZK6SM9ygMMtS4ADJ839/AHTuB4USQM5FlqOwTIBE6QGAMgQw+w4di7Rpyh/6VD4Fg3GoiLJi7Pte0Upuglr4oIfZwpEt1liAi0ZlnE+Qb1GkmEGfQYyNYDYQkLKS0KG113YxqMj7sef9WcRCaKJSm+FZ8rV7dA0pCj1jY5sKOdXO/3PYH9g6O/BdgP0XuAoAUgGWshB0Z/D4WwHyykOIRM3jRHmU8kUB4PjxCzFVoDnkYfvN7wBojMjb0F9POjfbSv40jjC3EDjeDusbAP1FGv+F7QaJyAWhNUBSlRUBcHZZ8icSqRAStwX9MHsBVZa5EGrvHFK4SP8b6X6gm01+3JuKpiSRPGkxyDuxlFLNNDipmUNuHh1byofE/oD48yLNh7nGofVIvaDdOn6bhnc3ZDd54onncDNEBaWAHrLvly1nzkP5VN1bFEax/jZPWbSrcntmQ0Ua+11D0Ot/FVFhhrJo1dBBECM9zkVBUkpYAAf1RN7f9IglBVhi5iK+LmbGXzTSUX695tMvnufwXEJsH4fu3Jkom/PUkEggWNHEgb4qm4IsO2wzMWns+ZbZi3PzXP0=",
"signer": "Falck",
"algorithm": "RSA-SHA256",
"timestamp": 1775964953.1502125,
"plugin_hash": "84d69d65913b62d156e13a22e09dfcc3a5b36e052ae0532c569ced1fb269bb11",
"author": "Falck"
}

View File

@@ -1,9 +1,24 @@
"""HTML 渲染服务 - 通过 config.json 配置,统一文件入口"""
import json
import sys
from pathlib import Path
from oss.plugin.types import Plugin, register_plugin_type, Response
class _Log:
_TTY = sys.stdout.isatty()
_C = {"reset": "\033[0m", "white": "\033[0;37m", "yellow": "\033[1;33m", "blue": "\033[1;34m", "red": "\033[1;31m"}
@classmethod
def _c(cls, t, c):
return f"{cls._C.get(c,'')}{t}{cls._C['reset']}" if cls._TTY else t
@classmethod
def info(cls, m): print(f"{cls._c('[html-render]', 'white')} {cls._c(m, 'white')}")
@classmethod
def warn(cls, m): print(f"{cls._c('[html-render]', 'yellow')} {cls._c('', 'yellow')} {cls._c(m, 'yellow')}")
@classmethod
def error(cls, m): print(f"{cls._c('[html-render]', 'red')} {cls._c('', 'red')} {cls._c(m, 'red')}")
class HtmlRenderPlugin(Plugin):
"""HTML 渲染插件 - 渲染服务由 html-render 提供"""
@@ -16,16 +31,16 @@ class HtmlRenderPlugin(Plugin):
def init(self, deps: dict = None):
"""初始化 - 读取 config.json 并解析网站根目录"""
self._load_config()
print(f"[html-render] 配置加载完成: root_dir={self.root_dir}")
_Log.info(f"配置加载完成: root_dir={self.root_dir}")
def start(self):
"""启动 - 注册路由到 http-api共享配置给 web-toolkit"""
# 注册首页路由
if self.http_api and hasattr(self.http_api, 'router'):
self.http_api.router.get("/", self._serve_html)
print("[html-render] 已注册路由到 http-api")
_Log.info("已注册路由到 http-api")
else:
print("[html-render] http-api 未加载")
_Log.warn("http-api 未加载")
# 将配置共享给 web-toolkit通过 plugin-storage 的 DCIM 共享存储)
if self.storage:
@@ -35,7 +50,7 @@ class HtmlRenderPlugin(Plugin):
"index_file": self.config.get("index_file", "index.html"),
"static_prefix": self.config.get("static_prefix", "/static"),
})
print("[html-render] 配置已共享到 DCIM")
_Log.info("配置已共享到 DCIM")
def stop(self):
"""停止"""
@@ -53,7 +68,7 @@ class HtmlRenderPlugin(Plugin):
"""读取 config.json解析根目录"""
config_path = Path("./data/html-render/config.json")
if not config_path.exists():
print("[html-render] 警告: config.json 不存在,使用默认配置")
_Log.warn("config.json 不存在,使用默认配置")
self.config = {"root_dir": "../website", "index_file": "index.html"}
else:
with open(config_path, "r", encoding="utf-8") as f:
@@ -66,6 +81,11 @@ class HtmlRenderPlugin(Plugin):
def _serve_html(self, request):
"""提供 HTML 页面 - 通过 plugin-storage 读取并注入静态资源路径"""
index_file = self.config.get("index_file", "index.html")
# 安全检查:防止路径穿越
if ".." in index_file or index_file.startswith("/"):
return Response(status=403, body="Forbidden")
if self.storage:
storage = self.storage.get_storage("html-render")
if storage.file_exists(index_file):

View File

@@ -0,0 +1,8 @@
{
"signature": "GYBKpyVNgNFbpeoGlkXNY+wvt5wrJFHeP06At2h3SPsZUX3sXCtUL8RoidfzkqrfphBKAaKYvRnXaZdi3hyaDfXNQ88Ik18U+K7Usx+/o/rrQqzMKqh1pT75UZgZtJpXHu7CiIEjNIQ0pbujRHVfnRFe/4K3E2IClpJLcrziyrvn0fUBcUytt/WCTGBJ8pnyWB+ybcIDTJJQ+l4E69vsy2YmJHZBbBreyOo+TN5AQHDAlZ851dxI1K9euCNtdnlufbW6QSshnQ7DSS94KYZEUgTYFGON4Qi1RiVTFJK4iJEkTExEmohc3AuFJtEoIBBJzbUj/yCmfGcyWrbK7wchdwdGuNxGbexB97FONGm0WFS/z6OM08ljMJUAgvDRZtpInpQHFWJfxBfH+wzBx0AvhkgiJeeUApeofOxlggveOLDYDEH8P858sf0sjHHL0qgE17alvn0Fi8rArOI40wrh420SF7p4VlXE7fufXoue+yAhlSt68zaXOJHAtK5CuMh2ytVFKonRJgF5TAXvXYJeOZgujHyUUTtVqje+thIaBzqtGhEt9xp5N6Ikky2sutKRMgXx34As3hvx0U6a2CHuVykcX9neoB8XtJNlE1+AT24wnWw8LBqm6OjCTeJtAOFWFkliHNID9b1xfq69rZBp/L4Djj1bzy8WNLM7QLbjAvc=",
"signer": "Falck",
"algorithm": "RSA-SHA256",
"timestamp": 1775964953.1846428,
"plugin_hash": "eab1e047be16fe50b9c46f26570924f2975fac71a45af7f6c0b1f9c16ac8b096",
"author": "Falck"
}

View File

@@ -1,5 +1,6 @@
"""Web 工具包 - 路由注册、静态文件服务、前端事件(不负责渲染)"""
import json
import sys
from pathlib import Path
from oss.plugin.types import Plugin, register_plugin_type, Response
from .router import WebRouter
@@ -7,6 +8,20 @@ from .static import StaticFileHandler
from .template import TemplateEngine
class _Log:
_TTY = sys.stdout.isatty()
_C = {"reset": "\033[0m", "white": "\033[0;37m", "yellow": "\033[1;33m", "blue": "\033[1;34m", "red": "\033[1;31m"}
@classmethod
def _c(cls, t, c):
return f"{cls._C.get(c,'')}{t}{cls._C['reset']}" if cls._TTY else t
@classmethod
def info(cls, m): print(f"{cls._c('[web-toolkit]', 'white')} {cls._c(m, 'white')}")
@classmethod
def warn(cls, m): print(f"{cls._c('[web-toolkit]', 'yellow')} {cls._c('', 'yellow')} {cls._c(m, 'yellow')}")
@classmethod
def error(cls, m): print(f"{cls._c('[web-toolkit]', 'red')} {cls._c('', 'red')} {cls._c(m, 'red')}")
class WebToolkitPlugin(Plugin):
"""Web 工具包插件 - 提供网站前端所有服务"""
@@ -26,7 +41,7 @@ class WebToolkitPlugin(Plugin):
self.template_engine = TemplateEngine()
self._load_config()
self.static_handler = StaticFileHandler(root=str(self.root_dir))
print(f"[web-toolkit] 配置加载完成: root_dir={self.root_dir}")
_Log.info(f"配置加载完成: root_dir={self.root_dir}")
def start(self):
"""启动"""
@@ -65,7 +80,7 @@ class WebToolkitPlugin(Plugin):
self._serve_static
)
print("[web-toolkit] Web 工具包已启动")
_Log.info("Web 工具包已启动")
def stop(self):
"""停止"""
@@ -97,7 +112,7 @@ class WebToolkitPlugin(Plugin):
"""读取 config.json解析网站根目录"""
config_path = Path("./data/web-toolkit/config.json")
if not config_path.exists():
print("[web-toolkit] 警告: config.json 不存在,使用默认配置")
_Log.warn("config.json 不存在,使用默认配置")
self.config = {
"root_dir": "../website",
"index_file": "index.html",
@@ -146,6 +161,10 @@ class WebToolkitPlugin(Plugin):
else:
filename = path.lstrip("/")
# 安全检查:防止路径穿越
if ".." in filename or filename.startswith("/"):
return Response(status=403, body="Forbidden")
if not filename:
return self._serve_website_index(request)
return self.static_handler.serve(filename)

View File

@@ -43,27 +43,74 @@ class TemplateEngine:
return content
def _safe_eval(self, expression: str, context: dict) -> Any:
"""安全评估表达式(仅允许简单的属性访问和比较"""
# 只允许访问 context 中的变量
# 支持的运算符: and, or, not, ==, !=, <, >, <=, >=, in
# 不允许函数调用、导入、属性访问等
# 使用 AST 解析并验证
"""安全评估表达式(使用 AST 验证,不使用 eval"""
try:
tree = ast.parse(expression, mode='eval')
except SyntaxError:
return False
# 验证 AST 节点
if not self._validate_ast(tree.body[0].value, set(context.keys())):
return False
# 在受限环境中评估
# 使用安全的 AST 解释器,不使用 eval
try:
return eval(expression, {"__builtins__": {}}, context)
return self._eval_ast(tree.body[0].value, context)
except Exception:
return False
def _eval_ast(self, node: ast.AST, context: dict) -> Any:
"""安全地评估 AST 节点"""
if isinstance(node, ast.Constant):
return node.value
elif isinstance(node, ast.Name):
return context.get(node.id, False)
elif isinstance(node, ast.BoolOp):
if isinstance(node.op, ast.And):
return all(self._eval_ast(v, context) for v in node.values)
elif isinstance(node.op, ast.Or):
return any(self._eval_ast(v, context) for v in node.values)
elif isinstance(node, ast.Compare):
return self._eval_compare(node, context)
elif isinstance(node, ast.UnaryOp):
if isinstance(node.op, ast.Not):
return not self._eval_ast(node.operand, context)
elif isinstance(node, ast.Subscript):
return self._eval_subscript(node, context)
return False
def _eval_compare(self, node: ast.Compare, context: dict) -> bool:
"""评估比较表达式"""
left = self._eval_ast(node.left, context)
for op, comp in zip(node.ops, node.comparators):
right = self._eval_ast(comp, context)
if isinstance(op, ast.Eq):
if not (left == right): return False
elif isinstance(op, ast.NotEq):
if not (left != right): return False
elif isinstance(op, ast.Lt):
if not (left < right): return False
elif isinstance(op, ast.Gt):
if not (left > right): return False
elif isinstance(op, ast.LtE):
if not (left <= right): return False
elif isinstance(op, ast.GtE):
if not (left >= right): return False
elif isinstance(op, ast.In):
if not (left in right): return False
elif isinstance(op, ast.NotIn):
if not (left not in right): return False
left = right
return True
def _eval_subscript(self, node: ast.Subscript, context: dict) -> Any:
"""评估下标访问"""
value = self._eval_ast(node.value, context)
key = self._eval_ast(node.slice, context)
if isinstance(value, (dict, list, str)):
return value[key]
return None
def _validate_ast(self, node: ast.AST, allowed_names: set) -> bool:
"""验证 AST 只包含安全的操作"""
if isinstance(node, ast.Name):