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.
- package/frontend/src/App.vue +6 -0
- package/frontend/src/components/common/VersionDisplay.vue +185 -0
- package/package.json +1 -1
- package/plugin/openclaw.plugin.json +1 -1
- package/plugin/package.json +1 -1
- package/scripts/install-python-deps.js +37 -8
- package/scripts/test_version_display.sh +128 -0
- package/src/backend/api/version.py +35 -0
- package/src/backend/data/version_info_reader.py +110 -0
- package/src/backend/main.py +2 -1
package/frontend/src/App.vue
CHANGED
|
@@ -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
package/plugin/package.json
CHANGED
|
@@ -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
|
-
*
|
|
121
|
-
* @returns {string[]}
|
|
144
|
+
* 优先级: 环境变量 > 自动检测(pypi 不通则用清华镜像)> 无镜像
|
|
145
|
+
* @returns {Promise<string[]>}
|
|
122
146
|
*/
|
|
123
|
-
function getPipMirrorArgs() {
|
|
124
|
-
const
|
|
125
|
-
if (
|
|
126
|
-
return ['-i',
|
|
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
|
package/src/backend/main.py
CHANGED
|
@@ -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")
|