fix: harden first-run database setup
Release / release (push) Canceled after 0s

This commit is contained in:
Qiufeng
2026-08-17 22:05:10 +08:00
parent 52403c6f95
commit 271eac382e
10 changed files with 429 additions and 50 deletions
+61 -11
View File
@@ -162,6 +162,10 @@
</t-form-item>
</div>
<t-alert v-if="completing" class="setup-progress" theme="info" :close-btn="false">
{{ completionProgressMessage }}
</t-alert>
<div class="setup-actions">
<t-button variant="outline" :disabled="completing" @click="currentStep = 0">
<template #icon><t-icon name="chevron-left" /></template>
@@ -223,8 +227,13 @@ const errorMessage = ref('');
const connectionResult = ref<SetupConnection | null>(null);
const verifiedFingerprint = ref('');
const completionMessage = ref('系统正在重启,请稍候。');
const completionProgressMessage = ref('正在初始化数据库结构并创建管理员,请稍候。');
const countdown = ref(10);
let countdownTimer: number | undefined;
let unmounted = false;
const SETUP_CONFIRMATION_TIMEOUT_MS = 60_000;
const SETUP_CONFIRMATION_INTERVAL_MS = 2_000;
const databaseForm = reactive<SetupDatabaseRequest>({
setupCode: '',
@@ -317,21 +326,51 @@ async function finishSetup(context?: SubmitContext) {
return;
}
completing.value = true;
completionProgressMessage.value = '正在初始化数据库结构并创建管理员,请稍候。';
errorMessage.value = '';
try {
const result = await completeSetup({ ...databaseForm, ...adminForm });
if (!result.completed) throw new Error('安装服务未确认完成,请稍后重试');
completionMessage.value = result.message;
countdown.value = Math.max(1, result.restartAfterSeconds || 10);
currentStep.value = 2;
startCountdown();
showCompleted(result.message, result.restartAfterSeconds);
} catch (error) {
errorMessage.value = (error as Error).message || '安装初始化失败';
const requestError = error as Error & { status?: number };
if (requestError.status === undefined || requestError.status >= 500) {
completionProgressMessage.value = '连接暂时中断,正在确认安装结果。';
if (await waitForSetupCompletion()) {
showCompleted('安装已完成,系统正在切换到正式模式', 10);
return;
}
errorMessage.value = '安装结果暂未确认,请稍后重新打开安装页面;若问题持续,请检查服务日志。';
} else {
errorMessage.value = requestError.message || '安装初始化失败';
}
} finally {
completing.value = false;
}
}
async function waitForSetupCompletion() {
const deadline = Date.now() + SETUP_CONFIRMATION_TIMEOUT_MS;
while (Date.now() < deadline) {
if (unmounted) return false;
try {
const status = await getSetupStatus();
if (status.ready) return true;
} catch {
// A short connection failure is expected while systemd restarts the service.
}
await new Promise((resolve) => window.setTimeout(resolve, SETUP_CONFIRMATION_INTERVAL_MS));
}
return false;
}
function showCompleted(message: string, restartAfterSeconds: number) {
completionMessage.value = message;
countdown.value = Math.max(1, restartAfterSeconds || 10);
currentStep.value = 2;
startCountdown();
}
function confirmSetup() {
void finishSetup();
}
@@ -340,13 +379,18 @@ function startCountdown() {
window.clearInterval(countdownTimer);
countdownTimer = window.setInterval(() => {
countdown.value -= 1;
if (countdown.value <= 0) openLogin();
if (countdown.value <= 0) void openLogin();
}, 1000);
}
function openLogin() {
async function openLogin() {
window.clearInterval(countdownTimer);
window.location.assign('/login');
completionMessage.value = '系统正在启动,请稍候。';
if (await waitForSetupCompletion()) {
window.location.assign('/login');
return;
}
completionMessage.value = '系统启动时间较长,请稍后刷新页面。';
}
onMounted(async () => {
@@ -364,7 +408,10 @@ onMounted(async () => {
}
});
onBeforeUnmount(() => window.clearInterval(countdownTimer));
onBeforeUnmount(() => {
unmounted = true;
window.clearInterval(countdownTimer);
});
</script>
<style scoped>
.setup-page {
@@ -382,8 +429,7 @@ onBeforeUnmount(() => window.clearInterval(countdownTimer));
}
.setup-panel {
align-self: center;
justify-self: center;
place-self: center;
width: min(760px, 100%);
min-width: 0;
padding: 32px;
@@ -445,6 +491,10 @@ h1 {
margin-top: 8px;
}
.setup-progress {
margin-top: 20px;
}
.setup-complete {
min-height: 280px;
display: flex;