feat(security-center): add reset container operation alongside update

- system.ts: extract streamOperation helper; add streamSystemReset export
- SecurityCenterView: replace single update button with update/reset descriptions table; shared dialog driven by operationType ref

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
这个提交包含在:
XuqmGroup 2026-05-22 15:33:35 +08:00
父节点 d0b7a51c2c
当前提交 e965c012e6
共有 2 个文件被更改,包括 46 次插入21 次删除

查看文件

@ -12,12 +12,13 @@ export async function getDeploymentStatus(): Promise<DeploymentStatus> {
return json.data as DeploymentStatus return json.data as DeploymentStatus
} }
export async function streamSystemUpdate( async function streamOperation(
path: string,
onLine: (line: string) => void, onLine: (line: string) => void,
signal?: AbortSignal, signal?: AbortSignal,
): Promise<void> { ): Promise<void> {
const token = localStorage.getItem('token') ?? '' const token = localStorage.getItem('token') ?? ''
const res = await fetch(`${BASE}/system/update`, { const res = await fetch(`${BASE}${path}`, {
method: 'POST', method: 'POST',
headers: { Authorization: `Bearer ${token}` }, headers: { Authorization: `Bearer ${token}` },
signal, signal,
@ -41,3 +42,11 @@ export async function streamSystemUpdate(
} }
if (buf) onLine(buf) if (buf) onLine(buf)
} }
export function streamSystemUpdate(onLine: (line: string) => void, signal?: AbortSignal) {
return streamOperation('/system/update', onLine, signal)
}
export function streamSystemReset(onLine: (line: string) => void, signal?: AbortSignal) {
return streamOperation('/system/reset', onLine, signal)
}

查看文件

@ -47,18 +47,24 @@
<el-button type="warning" plain @click="openMigrateDialog">生成迁移密钥</el-button> <el-button type="warning" plain @click="openMigrateDialog">生成迁移密钥</el-button>
</el-card> </el-card>
<!-- 一键更新仅私有化平台显示 --> <!-- 系统运维仅私有化平台显示 -->
<el-card v-if="deploymentMode === 'PRIVATE'" style="margin-bottom: 16px"> <el-card v-if="deploymentMode === 'PRIVATE'" style="margin-bottom: 16px">
<template #header> <template #header>
<div style="display:flex;align-items:center;justify-content:space-between"> <div style="display:flex;align-items:center;justify-content:space-between">
<span>系统更新</span> <span>系统运维</span>
<el-tag type="info" size="small">私有化部署</el-tag> <el-tag type="info" size="small">私有化部署</el-tag>
</div> </div>
</template> </template>
<p style="color: #606266; margin: 0 0 16px;"> <el-descriptions :column="1" border>
拉取最新镜像并逐一重启各服务容器全程无需手动操作tenant-service 重启时页面连接会短暂中断更新后自动恢复 <el-descriptions-item label="一键更新">
</p> <span style="color:#606266;font-size:13px;margin-right:16px">拉取最新镜像并重建所有容器用于升级到新版本</span>
<el-button type="primary" @click="showUpdateDialog = true">立即更新</el-button> <el-button type="primary" size="small" @click="openOperationDialog('update')">立即更新</el-button>
</el-descriptions-item>
<el-descriptions-item label="重置容器">
<span style="color:#606266;font-size:13px;margin-right:16px">用当前本地镜像重建所有容器无需下载新镜像适合修复异常服务</span>
<el-button type="warning" size="small" @click="openOperationDialog('reset')">重置容器</el-button>
</el-descriptions-item>
</el-descriptions>
</el-card> </el-card>
<el-card> <el-card>
@ -158,20 +164,21 @@
</template> </template>
</el-dialog> </el-dialog>
<!-- 一键更新 dialog --> <!-- 一键更新 / 重置容器 dialog -->
<el-dialog <el-dialog
v-model="showUpdateDialog" v-model="showUpdateDialog"
title="一键更新" :title="operationType === 'update' ? '一键更新' : '重置容器'"
width="600px" width="600px"
:close-on-click-modal="!updating" :close-on-click-modal="!updating"
:close-on-press-escape="!updating" :close-on-press-escape="!updating"
@closed="resetUpdateDialog" @closed="resetUpdateDialog"
> >
<div v-if="!updating && !updateDone && !updateError" style="color:#606266;"> <div v-if="!updating && !updateDone && !updateError" style="color:#606266;">
<p>将拉取最新镜像并重启所有运行中的服务容器</p> <p v-if="operationType === 'update'">将拉取最新镜像并重建所有运行中的服务容器用于升级到新版本</p>
<p v-else>将用当前本地镜像重建所有运行中的服务容器无需下载新镜像适合修复异常服务</p>
<el-alert type="warning" :closable="false" show-icon style="margin-top:12px"> <el-alert type="warning" :closable="false" show-icon style="margin-top:12px">
<template #title>tenant-service 重启时页面连接会短暂中断 1030 </template> <template #title>tenant-service 重启时页面连接会短暂中断 1030 </template>
<template #default>更新完成后请刷新页面</template> <template #default>操作完成后请刷新页面</template>
</el-alert> </el-alert>
</div> </div>
@ -185,7 +192,7 @@
</div> </div>
<div v-if="updateDone" style="margin-top:12px"> <div v-if="updateDone" style="margin-top:12px">
<el-alert type="success" :closable="false" show-icon title="更新完成!" /> <el-alert type="success" :closable="false" show-icon :title="operationType === 'update' ? '更新完成!' : '重置完成!'" />
</div> </div>
<div v-if="updateError" style="margin-top:12px"> <div v-if="updateError" style="margin-top:12px">
<el-alert type="error" :closable="false" show-icon :title="updateError" /> <el-alert type="error" :closable="false" show-icon :title="updateError" />
@ -197,10 +204,10 @@
</el-button> </el-button>
<el-button <el-button
v-if="!updating && !updateDone && !selfRestarting" v-if="!updating && !updateDone && !selfRestarting"
type="primary" :type="operationType === 'update' ? 'primary' : 'warning'"
@click="startUpdate" @click="startOperation"
> >
开始更新 {{ operationType === 'update' ? '开始更新' : '开始重置' }}
</el-button> </el-button>
</template> </template>
</el-dialog> </el-dialog>
@ -214,7 +221,7 @@ import { Loading } from '@element-plus/icons-vue'
import { accountApi } from '@/api/account' import { accountApi } from '@/api/account'
import { appApi, type App } from '@/api/app' import { appApi, type App } from '@/api/app'
import { migrateApi } from '@/api/migrate' import { migrateApi } from '@/api/migrate'
import { getDeploymentStatus, streamSystemUpdate } from '@/api/system' import { getDeploymentStatus, streamSystemUpdate, streamSystemReset } from '@/api/system'
import { useAuthStore } from '@/stores/auth' import { useAuthStore } from '@/stores/auth'
import { formatTime } from '@/utils/date' import { formatTime } from '@/utils/date'
@ -349,6 +356,7 @@ async function copyMigrateKey() {
// System Update // System Update
const showUpdateDialog = ref(false) const showUpdateDialog = ref(false)
const operationType = ref<'update' | 'reset'>('update')
const updating = ref(false) const updating = ref(false)
const updateDone = ref(false) const updateDone = ref(false)
const updateError = ref('') const updateError = ref('')
@ -356,6 +364,11 @@ const selfRestarting = ref(false)
const updateLog = ref<string[]>([]) const updateLog = ref<string[]>([])
const logEl = ref<HTMLPreElement | null>(null) const logEl = ref<HTMLPreElement | null>(null)
function openOperationDialog(type: 'update' | 'reset') {
operationType.value = type
showUpdateDialog.value = true
}
function resetUpdateDialog() { function resetUpdateDialog() {
if (updating.value) return if (updating.value) return
updateLog.value = [] updateLog.value = []
@ -364,15 +377,18 @@ function resetUpdateDialog() {
selfRestarting.value = false selfRestarting.value = false
} }
async function startUpdate() { async function startOperation() {
updating.value = true updating.value = true
updateLog.value = [] updateLog.value = []
updateDone.value = false updateDone.value = false
updateError.value = '' updateError.value = ''
selfRestarting.value = false selfRestarting.value = false
const streamFn = operationType.value === 'update' ? streamSystemUpdate : streamSystemReset
const failMsg = operationType.value === 'update' ? '更新失败' : '重置失败'
try { try {
await streamSystemUpdate((line) => { await streamFn((line) => {
if (line === 'RESTART_SELF') { if (line === 'RESTART_SELF') {
selfRestarting.value = true selfRestarting.value = true
updating.value = false updating.value = false
@ -391,7 +407,7 @@ async function startUpdate() {
} catch (e: any) { } catch (e: any) {
if (!selfRestarting.value) { if (!selfRestarting.value) {
updating.value = false updating.value = false
updateError.value = e?.message ?? '更新失败' updateError.value = e?.message ?? failMsg
} }
} }
} }
@ -404,7 +420,7 @@ async function pollForRecovery() {
await getDeploymentStatus() await getDeploymentStatus()
selfRestarting.value = false selfRestarting.value = false
updateDone.value = true updateDone.value = true
updateLog.value.push('>>> tenant-service 已重启,更新完成 ✓') updateLog.value.push(operationType.value === 'update' ? '>>> tenant-service 已重启,更新完成 ✓' : '>>> tenant-service 已重启,重置完成 ✓')
return return
} catch { } catch {
// still restarting // still restarting