This commit is contained in:
@@ -29,30 +29,40 @@
|
||||
<div class="update-actions">
|
||||
<t-button
|
||||
variant="outline"
|
||||
:loading="fetchingUpdate"
|
||||
:disabled="!canFetchUpdate || updateBusy || restarting"
|
||||
@click="fetchUpdate"
|
||||
:loading="checkingUpdate"
|
||||
:disabled="!canCheckUpdate || checkingUpdate || downloadingUpdate || installingUpdate"
|
||||
@click="checkLatest"
|
||||
>
|
||||
<template #icon><t-icon name="cloud-download" /></template>
|
||||
获取更新
|
||||
<template #icon><t-icon name="refresh" /></template>
|
||||
获取版本
|
||||
</t-button>
|
||||
<t-button
|
||||
v-if="canRestart"
|
||||
v-if="canDownloadUpdate"
|
||||
theme="primary"
|
||||
:loading="restarting"
|
||||
:disabled="updateBusy || fetchingUpdate"
|
||||
@click="restartNow"
|
||||
:loading="downloadingUpdate"
|
||||
:disabled="checkingUpdate || downloadingUpdate || installingUpdate"
|
||||
@click="downloadNow"
|
||||
>
|
||||
<template #icon><t-icon name="cloud-download" /></template>
|
||||
立即更新
|
||||
</t-button>
|
||||
<t-button
|
||||
v-if="canInstall"
|
||||
theme="primary"
|
||||
:loading="installingUpdate"
|
||||
:disabled="checkingUpdate || downloadingUpdate || installingUpdate"
|
||||
@click="installNow"
|
||||
>
|
||||
<template #icon><t-icon name="poweroff" /></template>
|
||||
立即重启
|
||||
立即更新并重启
|
||||
</t-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<t-steps :current="updateStep" :layout="stepLayout" status="process" class="update-steps">
|
||||
<t-step-item title="获取版本" content="检查最新发布" />
|
||||
<t-step-item title="下载校验" content="下载并验证签名" />
|
||||
<t-step-item title="立即重启" content="备份并切换版本" />
|
||||
<t-step-item title="下载更新" content="下载并验证签名" />
|
||||
<t-step-item title="安装重启" content="备份并切换版本" />
|
||||
<t-step-item title="完成更新" content="健康检查后刷新" />
|
||||
</t-steps>
|
||||
|
||||
@@ -66,7 +76,11 @@
|
||||
新版本已通过健康检查,页面将在 {{ updateRefreshSeconds }} 秒后自动刷新。
|
||||
</t-alert>
|
||||
<t-alert v-else :theme="updateStatus?.enabled === false ? 'warning' : 'info'" :close-btn="false">
|
||||
{{ updateStatus?.message || '正在读取系统版本状态' }}
|
||||
<div class="update-status-message">
|
||||
<strong>{{ updateStateLabel }}</strong>
|
||||
<span>{{ updateStatus?.message || '正在读取系统版本状态' }}</span>
|
||||
<small v-if="updateActionHint">{{ updateActionHint }}</small>
|
||||
</div>
|
||||
</t-alert>
|
||||
|
||||
<div v-if="updateStatus?.releaseNotes" class="release-notes">
|
||||
@@ -133,8 +147,9 @@ type TagTheme = 'default' | 'primary' | 'success' | 'warning' | 'danger';
|
||||
const updateStatus = ref<SystemUpdateView | null>(null);
|
||||
const isNarrow = ref(false);
|
||||
const statusLoading = ref(false);
|
||||
const fetchingUpdate = ref(false);
|
||||
const restarting = ref(false);
|
||||
const checkingUpdate = ref(false);
|
||||
const downloadingUpdate = ref(false);
|
||||
const installingUpdate = ref(false);
|
||||
const statusError = ref('');
|
||||
const historyRows = ref<AuditLog[]>([]);
|
||||
const historyLoading = ref(false);
|
||||
@@ -167,14 +182,27 @@ const currentIsLatest = computed(
|
||||
!updateBusy.value &&
|
||||
!['FAILED', 'UNKNOWN'].includes(updateStatus.value.state),
|
||||
);
|
||||
const canFetchUpdate = computed(
|
||||
const canCheckUpdate = computed(
|
||||
() =>
|
||||
updateStatus.value?.enabled !== false &&
|
||||
updateStatus.value?.state !== 'READY' &&
|
||||
(updateStatus.value?.allowedActions.includes('CHECK') === true ||
|
||||
updateStatus.value?.allowedActions.includes('DOWNLOAD') === true),
|
||||
!updateBusy.value &&
|
||||
updateStatus.value?.allowedActions.includes('CHECK') === true,
|
||||
);
|
||||
const canDownloadUpdate = computed(
|
||||
() =>
|
||||
updateStatus.value?.enabled !== false &&
|
||||
!updateBusy.value &&
|
||||
Boolean(updateStatus.value?.latestVersion) &&
|
||||
updateStatus.value?.updateAvailable === true &&
|
||||
updateStatus.value?.allowedActions.includes('DOWNLOAD') === true,
|
||||
);
|
||||
const canInstall = computed(
|
||||
() =>
|
||||
updateStatus.value?.enabled !== false &&
|
||||
!updateBusy.value &&
|
||||
updateStatus.value?.state === 'READY' &&
|
||||
updateStatus.value?.allowedActions.includes('INSTALL') === true,
|
||||
);
|
||||
const canRestart = computed(() => updateStatus.value?.allowedActions.includes('INSTALL') === true);
|
||||
const updateStep = computed(() => {
|
||||
const state = updateStatus.value?.state || 'IDLE';
|
||||
if (state === 'SUCCEEDED' || state === 'CURRENT' || currentIsLatest.value) return 3;
|
||||
@@ -190,11 +218,11 @@ const updateStateLabel = computed(() => {
|
||||
DISABLED: '未启用',
|
||||
CURRENT: '已是最新',
|
||||
QUEUED: '已排队',
|
||||
DOWNLOAD_QUEUED: '等待下载',
|
||||
DOWNLOAD_QUEUED: '下载已排队',
|
||||
INSTALL_QUEUED: '等待重启',
|
||||
VERIFYING: '校验中',
|
||||
DOWNLOADING: '下载中',
|
||||
READY: '等待重启',
|
||||
READY: '下载完成',
|
||||
BACKING_UP: '备份中',
|
||||
INSTALLING: '安装中',
|
||||
RUNNING: '重启中',
|
||||
@@ -206,6 +234,20 @@ const updateStateLabel = computed(() => {
|
||||
'待获取'
|
||||
);
|
||||
});
|
||||
const updateActionHint = computed(() => {
|
||||
const state = updateStatus.value?.state || 'IDLE';
|
||||
if (state === 'READY') {
|
||||
return canInstall.value ? '下载完成,请点击“立即更新并重启”安装新版本。' : '下载完成,但当前账号没有安装权限。';
|
||||
}
|
||||
if (canDownloadUpdate.value) return '已发现新版本,请点击“立即更新”开始下载。';
|
||||
if (updateStatus.value?.updateAvailable) return '已发现新版本,但当前账号没有下载权限。';
|
||||
if (['DOWNLOAD_QUEUED', 'VERIFYING', 'DOWNLOADING'].includes(state)) return '更新包正在下载和校验,请稍候。';
|
||||
if (['INSTALL_QUEUED', 'BACKING_UP', 'INSTALLING', 'RUNNING'].includes(state)) {
|
||||
return '更新包已确认,系统正在备份、切换并重启。';
|
||||
}
|
||||
if (state === 'IDLE' && !updateStatus.value?.latestVersion) return '点击“获取版本”检查最新发布。';
|
||||
return '';
|
||||
});
|
||||
const updateStateTheme = computed<TagTheme>(() => {
|
||||
const state = updateStatus.value?.state || 'IDLE';
|
||||
if (['SUCCEEDED', 'CURRENT', 'READY'].includes(state) || currentIsLatest.value) return 'success';
|
||||
@@ -233,7 +275,7 @@ async function loadStatus(silent = false) {
|
||||
updateStatus.value = status;
|
||||
if (installStates.has(status.state)) awaitingRefresh.value = true;
|
||||
handleCompletion(status);
|
||||
if (busyStates.has(status.state)) startPolling();
|
||||
if (busyStates.has(status.state) || downloadingUpdate.value || installingUpdate.value) startPolling();
|
||||
else stopPolling();
|
||||
} catch (error) {
|
||||
if (!silent) statusError.value = friendlyError(error, '系统版本状态读取失败');
|
||||
@@ -266,9 +308,9 @@ async function refreshPage() {
|
||||
await Promise.all([loadStatus(), loadHistory()]);
|
||||
}
|
||||
|
||||
async function fetchUpdate() {
|
||||
if (fetchingUpdate.value || updateBusy.value || !canFetchUpdate.value) return;
|
||||
fetchingUpdate.value = true;
|
||||
async function checkLatest() {
|
||||
if (checkingUpdate.value || updateBusy.value || !canCheckUpdate.value) return;
|
||||
checkingUpdate.value = true;
|
||||
statusError.value = '';
|
||||
try {
|
||||
const checked = await checkSystemUpdate();
|
||||
@@ -278,36 +320,61 @@ async function fetchUpdate() {
|
||||
MessagePlugin.info('当前已是最新版本');
|
||||
return;
|
||||
}
|
||||
if (!checked.latestVersion || !checked.allowedActions.includes('DOWNLOAD')) {
|
||||
throw new Error('发现新版本,但当前账号没有下载更新包的权限');
|
||||
if (checked.state === 'READY') {
|
||||
MessagePlugin.success(
|
||||
checked.allowedActions.includes('INSTALL')
|
||||
? '更新包已下载完成,请点击“立即更新并重启”安装新版本'
|
||||
: '更新包已下载完成,但当前账号没有安装权限',
|
||||
);
|
||||
} else if (!checked.allowedActions.includes('DOWNLOAD')) {
|
||||
MessagePlugin.info('发现新版本,但当前账号没有下载权限');
|
||||
} else {
|
||||
MessagePlugin.success(`发现新版本 ${checked.latestVersion},请点击“立即更新”开始下载`);
|
||||
}
|
||||
updateStatus.value = await downloadSystemUpdate(checked.latestVersion);
|
||||
await loadHistory(true);
|
||||
MessagePlugin.success('更新包已开始下载,下载和签名校验完成后可立即重启');
|
||||
startPolling();
|
||||
} catch (error) {
|
||||
statusError.value = friendlyError(error, '获取更新失败');
|
||||
statusError.value = friendlyError(error, '获取版本失败');
|
||||
} finally {
|
||||
fetchingUpdate.value = false;
|
||||
checkingUpdate.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function restartNow() {
|
||||
async function downloadNow() {
|
||||
const version = updateStatus.value?.latestVersion;
|
||||
if (!version || restarting.value || !canRestart.value) return;
|
||||
restarting.value = true;
|
||||
if (!version || downloadingUpdate.value || !canDownloadUpdate.value) return;
|
||||
downloadingUpdate.value = true;
|
||||
statusError.value = '';
|
||||
startPolling();
|
||||
try {
|
||||
updateStatus.value = await downloadSystemUpdate(version);
|
||||
MessagePlugin.success('更新包已开始下载,完成后可点击“立即更新并重启”');
|
||||
void loadHistory(true);
|
||||
} catch (error) {
|
||||
statusError.value = friendlyError(error, '下载更新包失败');
|
||||
void loadStatus(true);
|
||||
} finally {
|
||||
downloadingUpdate.value = false;
|
||||
startPolling();
|
||||
}
|
||||
}
|
||||
|
||||
async function installNow() {
|
||||
const version = updateStatus.value?.latestVersion;
|
||||
if (!version || installingUpdate.value || !canInstall.value) return;
|
||||
installingUpdate.value = true;
|
||||
statusError.value = '';
|
||||
stopRefreshCountdown();
|
||||
awaitingRefresh.value = true;
|
||||
startPolling();
|
||||
try {
|
||||
updateStatus.value = await installSystemUpdate(version, '管理员确认立即重启');
|
||||
awaitingRefresh.value = true;
|
||||
MessagePlugin.success('重启任务已提交,系统恢复后页面将自动刷新');
|
||||
await loadHistory(true);
|
||||
startPolling();
|
||||
updateStatus.value = await installSystemUpdate(version, '管理员确认立即更新并重启');
|
||||
MessagePlugin.success('更新任务已提交,系统完成切换并重启后页面将自动刷新');
|
||||
void loadHistory(true);
|
||||
} catch (error) {
|
||||
statusError.value = friendlyError(error, '立即重启失败');
|
||||
statusError.value = friendlyError(error, '立即更新并重启失败');
|
||||
void loadStatus(true);
|
||||
} finally {
|
||||
restarting.value = false;
|
||||
installingUpdate.value = false;
|
||||
startPolling();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -324,6 +391,7 @@ function stopPolling() {
|
||||
function handleCompletion(status: SystemUpdateView) {
|
||||
if (!awaitingRefresh.value) return;
|
||||
if (status.state === 'SUCCEEDED') {
|
||||
statusError.value = '';
|
||||
awaitingRefresh.value = false;
|
||||
startRefreshCountdown();
|
||||
void loadHistory(true);
|
||||
@@ -374,7 +442,7 @@ function actionLabel(action: string) {
|
||||
{
|
||||
SYSTEM_UPDATE_CHECK: '获取版本',
|
||||
SYSTEM_UPDATE_DOWNLOAD_REQUEST: '下载更新包',
|
||||
SYSTEM_UPDATE_REQUEST: '立即重启',
|
||||
SYSTEM_UPDATE_REQUEST: '立即更新并重启',
|
||||
}[action] || action
|
||||
);
|
||||
}
|
||||
@@ -503,6 +571,17 @@ onBeforeUnmount(() => {
|
||||
border-top: 1px solid var(--td-component-border);
|
||||
}
|
||||
|
||||
.update-status-message {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
gap: 4px 10px;
|
||||
}
|
||||
|
||||
.update-status-message small {
|
||||
color: var(--td-text-color-secondary);
|
||||
}
|
||||
|
||||
.release-notes span,
|
||||
.section-heading p,
|
||||
.section-heading > span {
|
||||
|
||||
Reference in New Issue
Block a user