refactor: 删除版本更新功能 + 报表页自动刷新
删除版本与更新功能: - routes/admin_api/update.py: 删除整个文件 - routes/admin_api/__init__.py: 移除 update 模块注册 - admin-frontend/src/pages/SystemPage.vue: 移除版本更新UI区块 - admin-frontend/src/api/update.js: 删除整个文件 - 删除 static/admin/assets/update-*.js 报表页自动刷新: - admin-frontend/src/pages/ReportPage.vue: 添加 setInterval 每1秒刷新 - 在 onMounted 启动定时器,onUnmounted 清除 - 覆盖统计数据、运行中任务、系统信息等所有动态数据 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,26 +0,0 @@
|
||||
import { api } from './client'
|
||||
|
||||
export async function fetchUpdateStatus() {
|
||||
const { data } = await api.get('/update/status')
|
||||
return data
|
||||
}
|
||||
|
||||
export async function fetchUpdateResult() {
|
||||
const { data } = await api.get('/update/result')
|
||||
return data
|
||||
}
|
||||
|
||||
export async function fetchUpdateLog(params = {}) {
|
||||
const { data } = await api.get('/update/log', { params })
|
||||
return data
|
||||
}
|
||||
|
||||
export async function requestUpdateCheck() {
|
||||
const { data } = await api.post('/update/check', {})
|
||||
return data
|
||||
}
|
||||
|
||||
export async function requestUpdateRun(payload = {}) {
|
||||
const { data } = await api.post('/update/run', payload)
|
||||
return data
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { computed, inject, onMounted, ref } from 'vue'
|
||||
import { computed, inject, onMounted, onUnmounted, ref } from 'vue'
|
||||
import {
|
||||
Calendar,
|
||||
ChatLineSquare,
|
||||
@@ -19,7 +19,6 @@ import { fetchFeedbackStats } from '../api/feedbacks'
|
||||
import { fetchEmailStats } from '../api/email'
|
||||
import { fetchDockerStats, fetchRunningTasks, fetchServerInfo, fetchTaskStats } from '../api/tasks'
|
||||
import { fetchSystemConfig } from '../api/system'
|
||||
import { fetchUpdateResult, fetchUpdateStatus } from '../api/update'
|
||||
|
||||
const refreshStats = inject('refreshStats', null)
|
||||
const adminStats = inject('adminStats', null)
|
||||
@@ -34,9 +33,6 @@ const feedbackStats = ref(null)
|
||||
const serverInfo = ref(null)
|
||||
const dockerStats = ref(null)
|
||||
const systemConfig = ref(null)
|
||||
const updateStatus = ref(null)
|
||||
const updateStatusError = ref('')
|
||||
const updateResult = ref(null)
|
||||
const queueTab = ref('running')
|
||||
|
||||
function recordUpdatedAt() {
|
||||
@@ -63,12 +59,6 @@ function parsePercent(value) {
|
||||
return n
|
||||
}
|
||||
|
||||
function shortCommit(value) {
|
||||
const text = String(value ?? '').trim()
|
||||
if (!text) return '-'
|
||||
return text.length > 12 ? `${text.slice(0, 12)}…` : text
|
||||
}
|
||||
|
||||
function sourceLabel(source) {
|
||||
const raw = String(source ?? '').trim()
|
||||
if (!raw) return '手动'
|
||||
@@ -155,9 +145,6 @@ const runningCountsLabel = computed(() => {
|
||||
return `运行中 ${runningCount} / 排队 ${queuingCount} / 并发上限 ${maxGlobal || maxConcurrentGlobal.value || '-'}`
|
||||
})
|
||||
|
||||
const updateAvailable = computed(() => Boolean(updateStatus.value?.update_available))
|
||||
const updateRunning = computed(() => updateResult.value?.status === 'running')
|
||||
|
||||
async function refreshAll() {
|
||||
if (loading.value) return
|
||||
loading.value = true
|
||||
@@ -170,8 +157,6 @@ async function refreshAll() {
|
||||
serverResult,
|
||||
dockerResult,
|
||||
configResult,
|
||||
updateStatusResult,
|
||||
updateResultResult,
|
||||
] = await Promise.allSettled([
|
||||
fetchTaskStats(),
|
||||
fetchRunningTasks(),
|
||||
@@ -180,8 +165,6 @@ async function refreshAll() {
|
||||
fetchServerInfo(),
|
||||
fetchDockerStats(),
|
||||
fetchSystemConfig(),
|
||||
fetchUpdateStatus(),
|
||||
fetchUpdateResult(),
|
||||
])
|
||||
|
||||
taskStats.value = taskResult.status === 'fulfilled' ? taskResult.value : null
|
||||
@@ -192,22 +175,6 @@ async function refreshAll() {
|
||||
dockerStats.value = dockerResult.status === 'fulfilled' ? dockerResult.value : null
|
||||
systemConfig.value = configResult.status === 'fulfilled' ? configResult.value : null
|
||||
|
||||
if (updateStatusResult.status === 'fulfilled') {
|
||||
const res = updateStatusResult.value
|
||||
if (res?.ok) {
|
||||
updateStatus.value = res.data || null
|
||||
updateStatusError.value = ''
|
||||
} else {
|
||||
updateStatus.value = null
|
||||
updateStatusError.value = res?.error || '未发现更新状态(Update-Agent 可能未运行)'
|
||||
}
|
||||
} else {
|
||||
updateStatus.value = null
|
||||
updateStatusError.value = ''
|
||||
}
|
||||
|
||||
updateResult.value = updateResultResult.status === 'fulfilled' && updateResultResult.value?.ok ? updateResultResult.value.data : null
|
||||
|
||||
await refreshStats?.()
|
||||
recordUpdatedAt()
|
||||
} finally {
|
||||
@@ -215,7 +182,19 @@ async function refreshAll() {
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(refreshAll)
|
||||
let refreshTimer = null
|
||||
|
||||
onMounted(() => {
|
||||
refreshAll()
|
||||
refreshTimer = setInterval(refreshAll, 1000)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (refreshTimer) {
|
||||
clearInterval(refreshTimer)
|
||||
refreshTimer = null
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -225,10 +204,6 @@ onMounted(refreshAll)
|
||||
<div class="hero-title">
|
||||
<div class="hero-title-row">
|
||||
<h2>报表中心</h2>
|
||||
<el-tag v-if="updateStatusError" type="info" effect="dark">更新状态未知</el-tag>
|
||||
<el-tag v-else-if="updateAvailable" type="warning" effect="dark">新版本可更新</el-tag>
|
||||
<el-tag v-else type="success" effect="dark">已是最新</el-tag>
|
||||
<el-tag v-if="updateRunning" type="warning" effect="plain">更新中</el-tag>
|
||||
</div>
|
||||
<div class="hero-meta app-muted">
|
||||
<span v-if="lastUpdatedAt">更新时间:{{ lastUpdatedAt }}</span>
|
||||
@@ -584,21 +559,12 @@ onMounted(refreshAll)
|
||||
<el-icon><Tools /></el-icon>
|
||||
</div>
|
||||
<div class="head-text">
|
||||
<div class="panel-title">配置与更新</div>
|
||||
<div class="panel-sub app-muted">定时/代理/并发与版本</div>
|
||||
<div class="panel-title">配置概览</div>
|
||||
<div class="panel-sub app-muted">定时 / 代理 / 并发</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-tag v-if="updateAvailable" effect="dark" type="warning">可更新</el-tag>
|
||||
</div>
|
||||
|
||||
<el-alert
|
||||
v-if="updateStatusError"
|
||||
type="info"
|
||||
:closable="false"
|
||||
:title="updateStatusError"
|
||||
style="margin-bottom: 12px"
|
||||
/>
|
||||
|
||||
<div class="config-grid">
|
||||
<div class="config-item">
|
||||
<div class="config-k app-muted">定时任务</div>
|
||||
@@ -631,18 +597,6 @@ onMounted(refreshAll)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<div class="sub-title">版本信息</div>
|
||||
<el-descriptions border :column="1" size="small">
|
||||
<el-descriptions-item label="本地版本(commit)">{{ shortCommit(updateStatus?.local_commit) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="远端版本(commit)">{{ shortCommit(updateStatus?.remote_commit) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="最近检查时间">{{ updateStatus?.checked_at || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="updateResult?.job_id" label="最近更新">
|
||||
<span>job {{ updateResult.job_id }} / {{ updateResult?.status || '-' }}</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<script setup>
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
|
||||
import { fetchSystemConfig, updateSystemConfig, executeScheduleNow } from '../api/system'
|
||||
import { fetchProxyConfig, testProxy, updateProxyConfig } from '../api/proxy'
|
||||
import { fetchUpdateLog, fetchUpdateResult, fetchUpdateStatus, requestUpdateCheck, requestUpdateRun } from '../api/update'
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
@@ -29,17 +28,6 @@ const autoApproveEnabled = ref(false)
|
||||
const autoApproveHourlyLimit = ref(10)
|
||||
const autoApproveVipDays = ref(7)
|
||||
|
||||
// 自动更新
|
||||
const updateLoading = ref(false)
|
||||
const updateActionLoading = ref(false)
|
||||
const updateStatus = ref(null)
|
||||
const updateStatusError = ref('')
|
||||
const updateResult = ref(null)
|
||||
const updateLog = ref('')
|
||||
const updateLogTruncated = ref(false)
|
||||
const updateBuildNoCache = ref(false)
|
||||
let updatePollTimer = null
|
||||
|
||||
const weekdaysOptions = [
|
||||
{ label: '周一', value: '1' },
|
||||
{ label: '周二', value: '2' },
|
||||
@@ -71,59 +59,6 @@ function normalizeBrowseType(value) {
|
||||
return '应读'
|
||||
}
|
||||
|
||||
function shortCommit(value) {
|
||||
const text = String(value || '').trim()
|
||||
if (!text) return '-'
|
||||
return text.length > 12 ? `${text.slice(0, 12)}…` : text
|
||||
}
|
||||
|
||||
async function loadUpdateInfo({ withLog = true } = {}) {
|
||||
updateLoading.value = true
|
||||
updateStatusError.value = ''
|
||||
try {
|
||||
const [statusRes, resultRes] = await Promise.all([fetchUpdateStatus(), fetchUpdateResult()])
|
||||
|
||||
if (statusRes?.ok) {
|
||||
updateStatus.value = statusRes.data || null
|
||||
} else {
|
||||
updateStatus.value = null
|
||||
updateStatusError.value = statusRes?.error || '未发现更新状态(Update-Agent 可能未运行)'
|
||||
}
|
||||
|
||||
updateResult.value = resultRes?.ok ? resultRes.data : null
|
||||
|
||||
const jobId = updateResult.value?.job_id
|
||||
if (withLog && jobId) {
|
||||
const logRes = await fetchUpdateLog({ job_id: jobId, max_bytes: 200000 })
|
||||
updateLog.value = logRes?.log || ''
|
||||
updateLogTruncated.value = !!logRes?.truncated
|
||||
} else {
|
||||
updateLog.value = ''
|
||||
updateLogTruncated.value = false
|
||||
}
|
||||
} catch {
|
||||
// handled by interceptor
|
||||
} finally {
|
||||
updateLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function startUpdatePolling() {
|
||||
if (updatePollTimer) return
|
||||
updatePollTimer = setInterval(async () => {
|
||||
if (updateResult.value?.status === 'running') {
|
||||
await loadUpdateInfo()
|
||||
}
|
||||
}, 5000)
|
||||
}
|
||||
|
||||
function stopUpdatePolling() {
|
||||
if (updatePollTimer) {
|
||||
clearInterval(updatePollTimer)
|
||||
updatePollTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
async function loadAll() {
|
||||
loading.value = true
|
||||
try {
|
||||
@@ -150,9 +85,6 @@ async function loadAll() {
|
||||
proxyEnabled.value = (proxy.proxy_enabled ?? 0) === 1
|
||||
proxyApiUrl.value = proxy.proxy_api_url || ''
|
||||
proxyExpireMinutes.value = proxy.proxy_expire_minutes ?? 3
|
||||
|
||||
await loadUpdateInfo({ withLog: false })
|
||||
startUpdatePolling()
|
||||
} catch {
|
||||
// handled by interceptor
|
||||
} finally {
|
||||
@@ -301,49 +233,7 @@ async function saveAutoApprove() {
|
||||
}
|
||||
}
|
||||
|
||||
async function onCheckUpdate() {
|
||||
updateActionLoading.value = true
|
||||
try {
|
||||
const res = await requestUpdateCheck()
|
||||
ElMessage.success(res?.success ? '已触发检查更新' : '已提交检查请求')
|
||||
setTimeout(() => loadUpdateInfo({ withLog: false }), 800)
|
||||
} catch {
|
||||
// handled by interceptor
|
||||
} finally {
|
||||
updateActionLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function onRunUpdate() {
|
||||
const status = updateStatus.value
|
||||
const remote = status?.remote_commit ? shortCommit(status.remote_commit) : '-'
|
||||
const buildFlags = updateBuildNoCache.value ? '\n\n构建选项: 强制重建(--no-cache)' : ''
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确定开始“一键更新”吗?\n\n目标版本: ${remote}${buildFlags}\n\n更新将会重建并重启服务,页面可能短暂不可用;系统会先备份数据库。`,
|
||||
'一键更新确认',
|
||||
{ confirmButtonText: '开始更新', cancelButtonText: '取消', type: 'warning' },
|
||||
)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
updateActionLoading.value = true
|
||||
try {
|
||||
const res = await requestUpdateRun({ build_no_cache: updateBuildNoCache.value ? 1 : 0 })
|
||||
ElMessage.success(res?.message || '已提交更新请求')
|
||||
startUpdatePolling()
|
||||
setTimeout(() => loadUpdateInfo(), 800)
|
||||
} catch {
|
||||
// handled by interceptor
|
||||
} finally {
|
||||
updateActionLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadAll)
|
||||
onBeforeUnmount(stopUpdatePolling)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -457,89 +347,6 @@ onBeforeUnmount(stopUpdatePolling)
|
||||
|
||||
<el-button type="primary" @click="saveAutoApprove">保存注册设置</el-button>
|
||||
</el-card>
|
||||
|
||||
<el-card shadow="never" :body-style="{ padding: '16px' }" class="card" v-loading="updateLoading">
|
||||
<h3 class="section-title">版本与更新</h3>
|
||||
|
||||
<el-alert
|
||||
v-if="updateStatus?.update_available"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
title="检测到新版本:可以在此页面点击“一键更新”升级并自动重启服务。"
|
||||
style="margin-bottom: 10px"
|
||||
/>
|
||||
|
||||
<el-alert
|
||||
v-if="updateStatusError"
|
||||
type="info"
|
||||
:closable="false"
|
||||
:title="updateStatusError"
|
||||
style="margin-bottom: 10px"
|
||||
/>
|
||||
|
||||
<el-descriptions border :column="1" size="small" style="margin-bottom: 10px">
|
||||
<el-descriptions-item label="本地版本(commit)">
|
||||
{{ shortCommit(updateStatus?.local_commit) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="远端版本(commit)">
|
||||
{{ shortCommit(updateStatus?.remote_commit) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="是否有更新">
|
||||
<el-tag v-if="updateStatus?.update_available" type="danger">有</el-tag>
|
||||
<el-tag v-else type="success">无</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="工作区修改">
|
||||
<el-tag v-if="updateStatus?.dirty" type="warning">有未提交修改</el-tag>
|
||||
<el-tag v-else type="info">干净</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="最近检查时间">
|
||||
{{ updateStatus?.checked_at || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="updateStatus?.error" label="检查错误">
|
||||
{{ updateStatus?.error }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div class="row-actions" style="align-items: center">
|
||||
<el-checkbox v-model="updateBuildNoCache">强制重建(--no-cache)</el-checkbox>
|
||||
<div class="help" style="margin-top: 0">依赖变更或构建异常时建议开启(更新会更慢)。</div>
|
||||
</div>
|
||||
|
||||
<div class="row-actions">
|
||||
<el-button @click="loadUpdateInfo" :disabled="updateActionLoading">刷新更新信息</el-button>
|
||||
<el-button @click="onCheckUpdate" :loading="updateActionLoading">检查更新</el-button>
|
||||
<el-button type="danger" @click="onRunUpdate" :loading="updateActionLoading" :disabled="!updateStatus?.update_available">
|
||||
一键更新
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<el-divider content-position="left">最近一次更新结果</el-divider>
|
||||
<el-descriptions v-if="updateResult" border :column="1" size="small" style="margin-bottom: 10px">
|
||||
<el-descriptions-item label="job_id">{{ updateResult.job_id }}</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag v-if="updateResult.status === 'running'" type="warning">运行中</el-tag>
|
||||
<el-tag v-else-if="updateResult.status === 'success'" type="success">成功</el-tag>
|
||||
<el-tag v-else type="danger">失败</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="阶段">{{ updateResult.stage || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="开始时间">{{ updateResult.started_at || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="结束时间">{{ updateResult.finished_at || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="耗时(秒)">{{ updateResult.duration_seconds ?? '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="更新前(commit)">{{ shortCommit(updateResult.from_commit) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="更新后(commit)">{{ shortCommit(updateResult.to_commit) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="健康检查">
|
||||
<span v-if="updateResult.health_ok === true">通过({{ updateResult.health_message }})</span>
|
||||
<span v-else-if="updateResult.health_ok === false">失败({{ updateResult.health_message }})</span>
|
||||
<span v-else>-</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="updateResult.error" label="错误">{{ updateResult.error }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<div v-else class="help">暂无更新记录。</div>
|
||||
|
||||
<el-divider content-position="left">更新日志</el-divider>
|
||||
<div class="help" v-if="updateLogTruncated">日志过长,仅展示末尾内容。</div>
|
||||
<el-input v-model="updateLog" type="textarea" :rows="10" readonly placeholder="暂无日志" />
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user