fix: Windows兼容 + Textual 8.x + MD3 SVG图标替代emoji

1. Windows: reuse_port 仅非win32平台启用
2. Textual 8.2.7: refresh() 加 **kwargs 兼容 repaint 参数
3. WebUI: 设置页/插件页 emoji → SVG (fill:currentColor 日夜间适配)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
qinglong
2026-06-13 20:42:22 +08:00
parent 9a9a2975de
commit cc50ecb813
8 changed files with 44 additions and 18 deletions
+22
View File
@@ -526,5 +526,27 @@
"framework.command.execute" "framework.command.execute"
], ],
"timestamp": 35916.627558381 "timestamp": 35916.627558381
},
"b9fd53b0": {
"plugin_name": "example_plugin",
"permissions": [
"plugin.example.read",
"plugin.example.write",
"plugin.example.execute",
"framework.event.subscribe",
"framework.command.execute"
],
"timestamp": 36350.458260715
},
"930a37d0": {
"plugin_name": "sentinel",
"permissions": [
"plugin.sentinel.read",
"plugin.sentinel.write",
"plugin.network.access",
"framework.event.subscribe",
"framework.command.execute"
],
"timestamp": 36350.46484983
} }
} }
+1 -1
View File
@@ -101,7 +101,7 @@ commands:
permissions: permissions:
- framework.command.test - framework.command.test
source: internal source: internal
last_updated: 35916.559043798 last_updated: 36350.43103134
plugin_commands: plugin_commands:
example_plugin: example_plugin:
echo: *id001 echo: *id001
+1 -1
View File
@@ -1,5 +1,5 @@
http_port: 4200 http_port: 4200
last_updated: 35916.617063641 last_updated: 36350.460163059
plugin_routes: plugin_routes:
example_plugin: example_plugin:
- methods: - methods:
+2 -2
View File
@@ -2,10 +2,10 @@ author: SenSu Team
description: 🛡️ 多节点 SenSu 集群性能监控 — 实时仪表盘 + 节点管理 description: 🛡️ 多节点 SenSu 集群性能监控 — 实时仪表盘 + 节点管理
name: Sentinel name: Sentinel
nodes: nodes:
- api_key: sk-035259bb700b17ae51cfd50a717b27281b1a5347b2aa4723 - api_key: ''
enabled: true enabled: true
id: node-3733a070 id: node-3733a070
name: 本地容器 name: PLK110
notes: LocalBian notes: LocalBian
url: http://127.0.0.1:4200 url: http://127.0.0.1:4200
settings: settings:
+9 -5
View File
@@ -48,11 +48,15 @@ class InternetService:
self.http_runner = web.AppRunner(self.http_app) self.http_runner = web.AppRunner(self.http_app)
await self.http_runner.setup() await self.http_runner.setup()
# SO_REUSEPORT: Linux/macOS only, Windows 不支持
import sys as _sys
_reuse_port = _sys.platform != "win32"
# 1. 启动 HTTP 站点 # 1. 启动 HTTP 站点
try: try:
self.site = web.TCPSite( self.site = web.TCPSite(
self.http_runner, self.http_host, self.http_port, self.http_runner, self.http_host, self.http_port,
reuse_address=True, reuse_port=True reuse_address=True, reuse_port=_reuse_port,
) )
await self.site.start() await self.site.start()
logger.info(f"✅ HTTP 服务已绑定: {self.http_host}:{self.http_port}") logger.info(f"✅ HTTP 服务已绑定: {self.http_host}:{self.http_port}")
@@ -60,12 +64,12 @@ class InternetService:
logger.error(f"❌ HTTP 端口 {self.http_port} 绑定失败: {e}") logger.error(f"❌ HTTP 端口 {self.http_port} 绑定失败: {e}")
await self.http_runner.cleanup() await self.http_runner.cleanup()
return False return False
# 2. 启动 WebSocket 站点 (独立端口) # 2. 启动 WebSocket 站点 (独立端口)
try: try:
self.ws_site = web.TCPSite( self.ws_site = web.TCPSite(
self.http_runner, self.ws_host, self.ws_port, self.http_runner, self.ws_host, self.ws_port,
reuse_address=True, reuse_port=True reuse_address=True, reuse_port=_reuse_port,
) )
await self.ws_site.start() await self.ws_site.start()
logger.info(f"✅ WebSocket 服务已绑定: {self.ws_host}:{self.ws_port}") logger.info(f"✅ WebSocket 服务已绑定: {self.ws_host}:{self.ws_port}")
+2 -2
View File
@@ -425,7 +425,7 @@ class SystemMonitor(Static):
def set_collector(self, c): def set_collector(self, c):
self._collector = c self._collector = c
def refresh(self): def refresh(self, **kwargs):
"""由外部 asyncio task 驱动刷新(比 set_interval 更抗 Android 挂起)""" """由外部 asyncio task 驱动刷新(比 set_interval 更抗 Android 挂起)"""
self._refresh_stats() self._refresh_stats()
try: try:
@@ -479,7 +479,7 @@ class PluginStatusPanel(Static):
def set_plugin_service(self, svc): def set_plugin_service(self, svc):
self._plugin_service = svc self._plugin_service = svc
def refresh(self): def refresh(self, **kwargs):
"""由外部 asyncio task 驱动刷新""" """由外部 asyncio task 驱动刷新"""
self._refresh() self._refresh()
+3 -3
View File
@@ -1,5 +1,5 @@
<div class="plugin-page-root" style="padding:16px"> <div class="plugin-page-root" style="padding:16px">
<h2 style="margin:0 0 12px 0">🔌 插件管理</h2> <h2 style="margin:0 0 12px 0"><svg width="22" height="22" viewBox="0 0 24 24" style="vertical-align:-4px;margin-right:6px"><path fill="currentColor" d="M20.5 11H19V7c0-1.1-.9-2-2-2h-4V3.5C13 2.12 11.88 1 10.5 1S8 2.12 8 3.5V5H4c-1.1 0-1.99.9-1.99 2v3.8H3.5c1.49 0 2.7 1.21 2.7 2.7s-1.21 2.7-2.7 2.7H2V20c0 1.1.9 2 2 2h3.8v-1.5c0-1.49 1.21-2.7 2.7-2.7 1.49 0 2.7 1.21 2.7 2.7V22H17c1.1 0 2-.9 2-2v-4h1.5c1.38 0 2.5-1.12 2.5-2.5S21.88 11 20.5 11z"/></svg>插件管理</h2>
<!-- ── 统计卡片 ── --> <!-- ── 统计卡片 ── -->
<div id="stats-card" style="display:flex;gap:12px;margin-bottom:16px;flex-wrap:wrap"> <div id="stats-card" style="display:flex;gap:12px;margin-bottom:16px;flex-wrap:wrap">
@@ -12,7 +12,7 @@
<!-- ── 导入区域 ── --> <!-- ── 导入区域 ── -->
<div style="display:flex;gap:16px;margin-bottom:16px;flex-wrap:wrap;align-items:stretch"> <div style="display:flex;gap:16px;margin-bottom:16px;flex-wrap:wrap;align-items:stretch">
<div class="import-card"> <div class="import-card">
<div class="card-title">📦 导入 ZIP 包</div> <div class="card-title"><svg width="18" height="18" viewBox="0 0 24 24" style="vertical-align:-3px;margin-right:4px"><path fill="currentColor" d="M20 6h-8l-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 12H4V6h5.17l2 2H20v10z"/></svg>导入 ZIP 包</div>
<label class="file-label" for="zip-file">选择 .zip 文件</label> <label class="file-label" for="zip-file">选择 .zip 文件</label>
<input type="file" id="zip-file" accept=".zip" class="file-input"> <input type="file" id="zip-file" accept=".zip" class="file-input">
<div class="card-actions"> <div class="card-actions">
@@ -21,7 +21,7 @@
</div> </div>
</div> </div>
<div class="import-card"> <div class="import-card">
<div class="card-title">📥 Git Clone 安装</div> <div class="card-title"><svg width="18" height="18" viewBox="0 0 24 24" style="vertical-align:-3px;margin-right:4px"><path fill="currentColor" d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"/></svg>Git Clone 安装</div>
<input id="git-url" class="text-input" placeholder="https://github.com/user/repo.git"> <input id="git-url" class="text-input" placeholder="https://github.com/user/repo.git">
<div class="card-actions"> <div class="card-actions">
<button class="btn btn-sm btn-filled" onclick="importGit()">Clone 安装</button> <button class="btn btn-sm btn-filled" onclick="importGit()">Clone 安装</button>
+4 -4
View File
@@ -1,10 +1,10 @@
<div class="plugin-page-root"> <div class="plugin-page-root">
<h2>框架设置</h2> <h2><svg width="22" height="22" viewBox="0 0 24 24" style="vertical-align:-4px;margin-right:6px"><path fill="currentColor" d="M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.49.49 0 00.12-.61l-1.92-3.32a.49.49 0 00-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94L14.4 2.81a.484.484 0 00-.41-.3h-3.98c-.2 0-.38.13-.45.32l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96a.49.49 0 00-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58a.49.49 0 00-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.19.24.31.44.31h3.98c.2 0 .38-.13.45-.32l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6c-1.98 0-3.6-1.62-3.6-3.6s1.62-3.6 3.6-3.6 3.6 1.62 3.6 3.6-1.62 3.6-3.6 3.6z"/></svg>框架设置</h2>
<!-- ── 版本与更新 ── --> <!-- ── 版本与更新 ── -->
<div class="card" style="margin-bottom:24px"> <div class="card" style="margin-bottom:24px">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px"> <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px">
<h3 style="margin:0">🔄 版本与更新</h3> <h3 style="margin:0"><svg width="20" height="20" viewBox="0 0 24 24" style="vertical-align:-4px;margin-right:6px"><path fill="currentColor" d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>版本与更新</h3>
</div> </div>
<div id="update-area"> <div id="update-area">
<div style="display:flex;gap:12px;align-items:center;flex-wrap:wrap"> <div style="display:flex;gap:12px;align-items:center;flex-wrap:wrap">
@@ -17,7 +17,7 @@
</div> </div>
<div style="margin-top:16px;border-top:1px solid var(--border);padding-top:12px"> <div style="margin-top:16px;border-top:1px solid var(--border);padding-top:12px">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px"> <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px">
<h4 style="margin:0">📦 备份管理</h4> <h4 style="margin:0"><svg width="18" height="18" viewBox="0 0 24 24" style="vertical-align:-3px;margin-right:4px"><path fill="currentColor" d="M20 6h-8l-2-2H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 12H4V6h5.17l2 2H20v10z"/></svg>备份管理</h4>
<button class="btn btn-sm btn-outlined" onclick="loadBackups()">刷新列表</button> <button class="btn btn-sm btn-outlined" onclick="loadBackups()">刷新列表</button>
</div> </div>
<div id="backup-list"><span style="color:var(--text-dim);font-size:.85rem">加载中...</span></div> <div id="backup-list"><span style="color:var(--text-dim);font-size:.85rem">加载中...</span></div>
@@ -27,7 +27,7 @@
<!-- ── API Key 管理 ── --> <!-- ── API Key 管理 ── -->
<div class="card" style="margin-bottom:24px"> <div class="card" style="margin-bottom:24px">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px"> <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:16px">
<h3 style="margin:0">🔑 API Key 管理</h3> <h3 style="margin:0"><svg width="20" height="20" viewBox="0 0 24 24" style="vertical-align:-4px;margin-right:6px"><path fill="currentColor" d="M12.65 10C11.83 7.67 9.61 6 7 6c-3.31 0-6 2.69-6 6s2.69 6 6 6c2.61 0 4.83-1.67 5.65-4H17v4h4v-4h2v-4H12.65zM7 14c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z"/></svg>API Key 管理</h3>
<button class="btn btn-filled" onclick="showCreateKey()">+ 创建 Key</button> <button class="btn btn-filled" onclick="showCreateKey()">+ 创建 Key</button>
</div> </div>
<p style="color:var(--text-dim);font-size:.85rem;margin-bottom:12px"> <p style="color:var(--text-dim);font-size:.85rem;margin-bottom:12px">