openclaw-agent-dashboard 1.0.10 → 1.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -51,6 +51,9 @@
51
51
  :agent="selectedAgent"
52
52
  @close="selectedAgent = null"
53
53
  />
54
+
55
+ <!-- 版本号显示 -->
56
+ <VersionDisplay />
54
57
  </div>
55
58
  </template>
56
59
 
@@ -65,6 +68,9 @@ import TaskStatusSection from './components/tasks/TaskStatusSection.vue'
65
68
  import PerformancePanel from './components/performance/PerformancePanel.vue'
66
69
  import ErrorCenterPanel from './components/ErrorCenterPanel.vue'
67
70
 
71
+ // 版本显示组件
72
+ import VersionDisplay from './components/common/VersionDisplay.vue'
73
+
68
74
  // 数据管理
69
75
  import { getRealtimeManager, getStateManager, getEventDispatcher } from './managers'
70
76
  import type { ConnectionState } from './types'
@@ -0,0 +1,185 @@
1
+ <template>
2
+ <!-- 版本号显示组件 -->
3
+ <div class="version-display">
4
+ <!-- 加载中状态 -->
5
+ <template v-if="loading">
6
+ <span class="loading-text">加载中...</span>
7
+ </template>
8
+
9
+ <!-- 错误状态 -->
10
+ <template v-else-if="error">
11
+ <span class="error-text">版本信息获取失败</span>
12
+ </template>
13
+
14
+ <!-- 正常显示 -->
15
+ <template v-else>
16
+ <span
17
+ class="version-text"
18
+ @mouseenter="showTooltip = true"
19
+ @mouseleave="showTooltip = false"
20
+ >
21
+ {{ displayText }}
22
+ </span>
23
+
24
+ <!-- hover 提示框 -->
25
+ <div v-if="showTooltip" class="tooltip">
26
+ <div class="tooltip-item"><strong>名称:</strong> {{ versionInfo.name }}</div>
27
+ <div class="tooltip-item"><strong>版本:</strong> {{ versionInfo.version }}</div>
28
+ <div class="tooltip-item"><strong>描述:</strong> {{ versionInfo.description }}</div>
29
+ <div v-if="versionInfo.build_date" class="tooltip-item">
30
+ <strong>构建时间:</strong> {{ formatBuildDate(versionInfo.build_date) }}
31
+ </div>
32
+ <div v-if="versionInfo.git_commit" class="tooltip-item">
33
+ <strong>Git 提交:</strong> {{ versionInfo.git_commit }}
34
+ </div>
35
+ </div>
36
+ </template>
37
+ </div>
38
+ </template>
39
+
40
+ <script setup lang="ts">
41
+ /**
42
+ * 版本号显示组件
43
+ *
44
+ * 功能:
45
+ * 1. 从 /api/version 接口获取版本信息
46
+ * 2. 在界面右下角显示版本号
47
+ * 3. hover 时显示完整的版本信息(名称、描述、构建时间等)
48
+ * 4. 支持加载中、错误等状态显示
49
+ */
50
+ import { ref, onMounted, computed } from 'vue'
51
+
52
+ // 版本信息接口定义
53
+ interface VersionInfo {
54
+ version: string
55
+ name: string
56
+ description: string
57
+ build_date?: string
58
+ git_commit?: string
59
+ }
60
+
61
+ // 状态定义
62
+ const loading = ref(true)
63
+ const error = ref(false)
64
+ const versionInfo = ref<VersionInfo>({
65
+ version: '',
66
+ name: '',
67
+ description: '',
68
+ })
69
+ const showTooltip = ref(false)
70
+
71
+ // 计算显示文本
72
+ const displayText = computed(() => {
73
+ if (versionInfo.value.name && versionInfo.value.version) {
74
+ return `${versionInfo.value.name} v${versionInfo.value.version}`
75
+ }
76
+ return versionInfo.value.version || 'v?'
77
+ })
78
+
79
+ /**
80
+ * 格式化构建时间
81
+ * @param dateStr 时间字符串
82
+ * @returns 格式化后的时间字符串
83
+ */
84
+ const formatBuildDate = (dateStr: string) => {
85
+ try {
86
+ return new Date(dateStr).toLocaleString('zh-CN')
87
+ } catch {
88
+ return dateStr
89
+ }
90
+ }
91
+
92
+ /**
93
+ * 从 API 获取版本信息
94
+ */
95
+ const fetchVersionInfo = async () => {
96
+ try {
97
+ loading.value = true
98
+ error.value = false
99
+
100
+ const response = await fetch('/api/version')
101
+ if (!response.ok) {
102
+ throw new Error(`HTTP ${response.status}`)
103
+ }
104
+
105
+ versionInfo.value = await response.json()
106
+ } catch (err) {
107
+ console.error('获取版本信息失败:', err)
108
+ error.value = true
109
+ } finally {
110
+ loading.value = false
111
+ }
112
+ }
113
+
114
+ // 组件挂载时获取版本信息
115
+ onMounted(() => {
116
+ fetchVersionInfo()
117
+ })
118
+ </script>
119
+
120
+ <style scoped>
121
+ .version-display {
122
+ position: fixed;
123
+ bottom: 16px;
124
+ right: 16px;
125
+ font-size: 12px;
126
+ color: #999;
127
+ display: inline-block;
128
+ position: relative;
129
+ z-index: 1000;
130
+ }
131
+
132
+ .loading-text,
133
+ .error-text {
134
+ color: #aaa;
135
+ }
136
+
137
+ .version-text {
138
+ cursor: pointer;
139
+ transition: color 0.2s;
140
+ user-select: none;
141
+ }
142
+
143
+ .version-text:hover {
144
+ color: #666;
145
+ }
146
+
147
+ .tooltip {
148
+ position: absolute;
149
+ bottom: calc(100% + 8px);
150
+ right: 0;
151
+ background: #fff;
152
+ border: 1px solid #ddd;
153
+ border-radius: 4px;
154
+ padding: 8px 12px;
155
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
156
+ white-space: nowrap;
157
+ z-index: 1001;
158
+ min-width: 200px;
159
+ }
160
+
161
+ .tooltip-item {
162
+ margin: 4px 0;
163
+ font-size: 12px;
164
+ color: #333;
165
+ }
166
+
167
+ .tooltip-item strong {
168
+ color: #666;
169
+ margin-right: 4px;
170
+ }
171
+
172
+ /* 响应式调整 */
173
+ @media (max-width: 640px) {
174
+ .version-display {
175
+ bottom: 8px;
176
+ right: 8px;
177
+ font-size: 11px;
178
+ }
179
+
180
+ .tooltip {
181
+ min-width: 160px;
182
+ font-size: 11px;
183
+ }
184
+ }
185
+ </style>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-agent-dashboard",
3
- "version": "1.0.10",
3
+ "version": "1.0.11",
4
4
  "description": "多 Agent 可视化看板 - 状态、任务、API、工作流、协作流程",
5
5
  "bin": {
6
6
  "openclaw-agent-dashboard": "scripts/install.js"
@@ -2,7 +2,7 @@
2
2
  "id": "openclaw-agent-dashboard",
3
3
  "name": "OpenClaw Agent Dashboard",
4
4
  "description": "多 Agent 可视化看板 - 状态、任务、API、工作流、协作流程",
5
- "version": "1.0.10",
5
+ "version": "1.0.11",
6
6
  "configSchema": {
7
7
  "type": "object",
8
8
  "additionalProperties": false,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-agent-dashboard",
3
- "version": "1.0.10",
3
+ "version": "1.0.11",
4
4
  "description": "多 Agent 可视化看板 - OpenClaw 插件",
5
5
  "main": "index.js",
6
6
  "openclaw": {
@@ -115,15 +115,44 @@ function getVenvPython(venvDir) {
115
115
  // Pip 镜像
116
116
  // ============================================
117
117
 
118
+ // ============================================
119
+ // PyPI 连通性检测
120
+ // ============================================
121
+
122
+ /**
123
+ * 检测 pypi.org 是否可达(超时 3 秒)
124
+ * @returns {boolean}
125
+ */
126
+ function checkPypiReachable() {
127
+ try {
128
+ const https = require('https');
129
+ return new Promise((resolve) => {
130
+ const req = https.get('https://pypi.org/simple/', { timeout: 3000 }, (res) => {
131
+ resolve(res.statusCode === 200);
132
+ req.destroy();
133
+ });
134
+ req.on('timeout', () => { req.destroy(); resolve(false); });
135
+ req.on('error', () => { resolve(false); });
136
+ });
137
+ } catch {
138
+ return false;
139
+ }
140
+ }
141
+
118
142
  /**
119
143
  * 获取 pip 镜像参数
120
- * 支持环境变量 PIP_INDEX_URL PIP_MIRROR
121
- * @returns {string[]}
144
+ * 优先级: 环境变量 > 自动检测(pypi 不通则用清华镜像)> 无镜像
145
+ * @returns {Promise<string[]>}
122
146
  */
123
- function getPipMirrorArgs() {
124
- const mirror = process.env.PIP_INDEX_URL || process.env.PIP_MIRROR || '';
125
- if (mirror) {
126
- return ['-i', mirror, '--trusted-host', new URL(mirror).hostname];
147
+ async function getPipMirrorArgs() {
148
+ const envMirror = process.env.PIP_INDEX_URL || process.env.PIP_MIRROR || '';
149
+ if (envMirror) {
150
+ return ['-i', envMirror, '--trusted-host', new URL(envMirror).hostname];
151
+ }
152
+ const reachable = await checkPypiReachable();
153
+ if (!reachable) {
154
+ logInfo(' pypi.org 不可达,使用清华镜像');
155
+ return ['-i', 'https://pypi.tuna.tsinghua.edu.cn/simple', '--trusted-host', 'pypi.tuna.tsinghua.edu.cn'];
127
156
  }
128
157
  return [];
129
158
  }
@@ -168,7 +197,7 @@ function installWithVenv(reqFile, venvDir, silent) {
168
197
 
169
198
  // 升级 pip(静默,失败不影响)
170
199
  // 如果设了镜像,升级 pip 也用镜像
171
- const pipMirrorArgs = getPipMirrorArgs();
200
+ const pipMirrorArgs = await getPipMirrorArgs();
172
201
  runCommand(venvPython, ['-m', 'pip', 'install', '--upgrade', 'pip', '-q', ...pipMirrorArgs], { silent: true });
173
202
 
174
203
  // 安装依赖
@@ -211,7 +240,7 @@ function installWithVenv(reqFile, venvDir, silent) {
211
240
  function installWithPipUser(reqFile, silent) {
212
241
  logInfo(' 尝试: pip --user(PEP 668 兜底)');
213
242
 
214
- const pipMirrorArgs = getPipMirrorArgs();
243
+ const pipMirrorArgs = await getPipMirrorArgs();
215
244
  const tsinghuaArgs = ['-i', 'https://pypi.tuna.tsinghua.edu.cn/simple', '--trusted-host', 'pypi.tuna.tsinghua.edu.cn'];
216
245
 
217
246
  const pipCommands = [
@@ -0,0 +1,128 @@
1
+ #!/bin/bash
2
+ # 版本号显示功能 - 快速测试脚本
3
+
4
+ set -e
5
+
6
+ PROJECT_DIR="/home/ubuntu/vrt-projects/projects/openclaw-agent-dashboard"
7
+ cd "$PROJECT_DIR"
8
+
9
+ echo "=================================="
10
+ echo "版本号显示功能 - 快速测试"
11
+ echo "=================================="
12
+ echo ""
13
+
14
+ # 颜色定义
15
+ GREEN='\033[0;32m'
16
+ RED='\033[0;31m'
17
+ YELLOW='\033[1;33m'
18
+ NC='\033[0m' # No Color
19
+
20
+ # 测试函数
21
+ test_passed() {
22
+ echo -e "${GREEN}✅ 通过${NC}: $1"
23
+ }
24
+
25
+ test_failed() {
26
+ echo -e "${RED}❌ 失败${NC}: $1"
27
+ }
28
+
29
+ test_info() {
30
+ echo -e "${YELLOW}ℹ️ 信息${NC}: $1"
31
+ }
32
+
33
+ echo "1. 检查文件是否存在"
34
+ echo "----------------------------------"
35
+
36
+ FILES=(
37
+ "src/backend/data/version_info_reader.py"
38
+ "src/backend/api/version.py"
39
+ "frontend/src/components/common/VersionDisplay.vue"
40
+ ".staging/traceability_manifest.json"
41
+ ".staging/VERSION_DISPLAY_implementation_summary.md"
42
+ )
43
+
44
+ for file in "${FILES[@]}"; do
45
+ if [ -f "$file" ]; then
46
+ test_passed "文件存在: $file"
47
+ else
48
+ test_failed "文件缺失: $file"
49
+ fi
50
+ done
51
+
52
+ echo ""
53
+ echo "2. 检查 Python 语法"
54
+ echo "----------------------------------"
55
+
56
+ python3 -m py_compile src/backend/data/version_info_reader.py && \
57
+ test_passed "version_info_reader.py 语法正确" || \
58
+ test_failed "version_info_reader.py 语法错误"
59
+
60
+ python3 -m py_compile src/backend/api/version.py && \
61
+ test_passed "version.py 语法正确" || \
62
+ test_failed "version.py 语法错误"
63
+
64
+ echo ""
65
+ echo "3. 检查 API 路由注册"
66
+ echo "----------------------------------"
67
+
68
+ if grep -q "from api import.*version" src/backend/main.py; then
69
+ test_passed "main.py 已导入 version 模块"
70
+ else
71
+ test_failed "main.py 未导入 version 模块"
72
+ fi
73
+
74
+ if grep -q "app.include_router(version.router" src/backend/main.py; then
75
+ test_passed "main.py 已注册 version 路由"
76
+ else
77
+ test_failed "main.py 未注册 version 路由"
78
+ fi
79
+
80
+ echo ""
81
+ echo "4. 检查前端组件集成"
82
+ echo "----------------------------------"
83
+
84
+ if grep -q "import VersionDisplay from './components/common/VersionDisplay.vue'" frontend/src/App.vue; then
85
+ test_passed "App.vue 已导入 VersionDisplay 组件"
86
+ else
87
+ test_failed "App.vue 未导入 VersionDisplay 组件"
88
+ fi
89
+
90
+ if grep -q "<VersionDisplay />" frontend/src/App.vue; then
91
+ test_passed "App.vue 模板包含 VersionDisplay 标签"
92
+ else
93
+ test_failed "App.vue 模板缺少 VersionDisplay 标签"
94
+ fi
95
+
96
+ echo ""
97
+ echo "5. 检查版本号数据源"
98
+ echo "----------------------------------"
99
+
100
+ if [ -f "package.json" ]; then
101
+ VERSION=$(python3 -c "import json; print(json.load(open('package.json')).get('version', 'unknown'))")
102
+ test_info "当前版本号: $VERSION"
103
+ test_passed "package.json 存在且可解析"
104
+ else
105
+ test_failed "package.json 不存在"
106
+ fi
107
+
108
+ echo ""
109
+ echo "6. 检查组件目录结构"
110
+ echo "----------------------------------"
111
+
112
+ if [ -d "frontend/src/components/common" ]; then
113
+ test_passed "common 目录已创建"
114
+ else
115
+ test_failed "common 目录缺失"
116
+ fi
117
+
118
+ echo ""
119
+ echo "=================================="
120
+ echo "测试完成"
121
+ echo "=================================="
122
+ echo ""
123
+ echo "后续步骤:"
124
+ echo "1. 启动服务: npm start"
125
+ echo "2. 测试 API: curl http://localhost:8000/api/version"
126
+ echo "3. 打开浏览器访问: http://localhost:8000"
127
+ echo "4. 检查右下角是否显示版本号"
128
+ echo ""
@@ -0,0 +1,35 @@
1
+ """
2
+ 版本信息 API 路由
3
+
4
+ 提供 GET /api/version 端点,返回插件版本信息。
5
+ """
6
+ from fastapi import APIRouter
7
+ from pydantic import BaseModel
8
+ from typing import Optional
9
+
10
+ # 导入版本信息读取器
11
+ from data.version_info_reader import get_version_reader
12
+
13
+ router = APIRouter(prefix="/api", tags=["version"])
14
+
15
+
16
+ class VersionInfo(BaseModel):
17
+ """版本信息数据模型"""
18
+ version: str # 版本号,如 "1.0.10"
19
+ name: str # 插件名称
20
+ description: str # 插件描述
21
+ build_date: Optional[str] = None # 构建时间(可选)
22
+ git_commit: Optional[str] = None # Git 提交哈希(可选)
23
+
24
+
25
+ @router.get("/version", response_model=VersionInfo)
26
+ async def get_version_info() -> VersionInfo:
27
+ """
28
+ 获取插件版本信息
29
+
30
+ Returns:
31
+ VersionInfo: 包含版本号、名称、描述等信息的对象
32
+ """
33
+ reader = get_version_reader()
34
+ version_data = reader.read_version_info()
35
+ return VersionInfo(**version_data)
@@ -0,0 +1,110 @@
1
+ """
2
+ 版本信息读取器
3
+
4
+ 负责从 package.json 读取版本信息,支持缓存机制。
5
+ 在应用启动时读取并缓存,后续请求直接返回缓存数据。
6
+ """
7
+ from pathlib import Path
8
+ from typing import Optional
9
+ import json
10
+ import logging
11
+ import os
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class VersionInfoReader:
17
+ """版本信息读取器,支持缓存"""
18
+
19
+ def __init__(self, package_json_path: Optional[Path] = None):
20
+ """
21
+ 初始化版本信息读取器
22
+
23
+ Args:
24
+ package_json_path: package.json 文件路径,默认为项目根目录下的 package.json
25
+ """
26
+ # 默认路径:src/backend/data -> src/backend -> src -> package.json
27
+ self.package_json_path = package_json_path or Path(__file__).parent.parent.parent / "package.json"
28
+ self._cached_info: Optional[dict] = None
29
+
30
+ def read_version_info(self) -> dict:
31
+ """
32
+ 读取版本信息(带缓存)
33
+
34
+ Returns:
35
+ dict: 包含 version, name, description 等字段的字典
36
+ """
37
+ if self._cached_info is not None:
38
+ return self._cached_info
39
+
40
+ try:
41
+ with open(self.package_json_path, 'r', encoding='utf-8') as f:
42
+ package_data = json.load(f)
43
+
44
+ self._cached_info = {
45
+ "version": package_data.get("version", "unknown"),
46
+ "name": package_data.get("name", "openclaw-agent-dashboard"),
47
+ "description": package_data.get("description", ""),
48
+ }
49
+
50
+ # 可选:读取构建时间(从环境变量)
51
+ build_date = self._read_build_date()
52
+ if build_date:
53
+ self._cached_info["build_date"] = build_date
54
+
55
+ # 可选:读取 Git 提交哈希
56
+ git_commit = self._read_git_commit()
57
+ if git_commit:
58
+ self._cached_info["git_commit"] = git_commit
59
+
60
+ logger.info(f"成功读取版本信息: {self._cached_info['version']}")
61
+ return self._cached_info
62
+
63
+ except Exception as e:
64
+ # 降级:返回默认值
65
+ logger.error(f"读取版本信息失败: {e}", exc_info=True)
66
+ self._cached_info = {
67
+ "version": "unknown",
68
+ "name": "openclaw-agent-dashboard",
69
+ "description": "",
70
+ }
71
+ return self._cached_info
72
+
73
+ def _read_build_date(self) -> Optional[str]:
74
+ """
75
+ 读取构建时间(从环境变量)
76
+
77
+ Returns:
78
+ Optional[str]: 构建时间字符串,如果未设置则返回 None
79
+ """
80
+ return os.getenv("DASHBOARD_BUILD_DATE")
81
+
82
+ def _read_git_commit(self) -> Optional[str]:
83
+ """
84
+ 读取 Git 提交哈希(从环境变量)
85
+
86
+ Returns:
87
+ Optional[str]: Git 提交哈希(短格式),如果未设置则返回 None
88
+ """
89
+ return os.getenv("DASHBOARD_GIT_COMMIT")
90
+
91
+ def clear_cache(self):
92
+ """清除缓存(用于测试或强制刷新)"""
93
+ self._cached_info = None
94
+
95
+
96
+ # 全局单例实例
97
+ _version_reader: Optional[VersionInfoReader] = None
98
+
99
+
100
+ def get_version_reader() -> VersionInfoReader:
101
+ """
102
+ 获取全局版本信息读取器实例
103
+
104
+ Returns:
105
+ VersionInfoReader: 版本信息读取器实例
106
+ """
107
+ global _version_reader
108
+ if _version_reader is None:
109
+ _version_reader = VersionInfoReader()
110
+ return _version_reader
@@ -47,7 +47,7 @@ app.add_middleware(
47
47
  import sys
48
48
  sys.path.append(str(Path(__file__).parent))
49
49
 
50
- from api import agents, subagents, websocket, performance, collaboration, agents_config, errors, timeline, chains, agent_config_api, error_analysis, debug_paths
50
+ from api import agents, subagents, websocket, performance, collaboration, agents_config, errors, timeline, chains, agent_config_api, error_analysis, debug_paths, version
51
51
 
52
52
  # 注册 API 路由
53
53
  app.include_router(agents.router, prefix="/api", tags=["agents"])
@@ -62,6 +62,7 @@ app.include_router(chains.router, prefix="/api", tags=["chains"])
62
62
  app.include_router(agent_config_api.router, prefix="/api", tags=["agent-config"])
63
63
  app.include_router(error_analysis.router, prefix="/api", tags=["error-analysis"])
64
64
  app.include_router(debug_paths.router, prefix="/api", tags=["debug"])
65
+ app.include_router(version.router, prefix="/api", tags=["version"])
65
66
 
66
67
 
67
68
  @app.get("/health")