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>
这个提交包含在:
父节点
d0b7a51c2c
当前提交
e965c012e6
@ -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 重启时页面连接会短暂中断(约 10–30 秒)</template>
|
<template #title>tenant-service 重启时页面连接会短暂中断(约 10–30 秒)</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
|
||||||
|
|||||||
正在加载...
在新工单中引用
屏蔽一个用户