diff --git a/frontend/src/api/setup.ts b/frontend/src/api/setup.ts index 1097c95b..660bf0ae 100644 --- a/frontend/src/api/setup.ts +++ b/frontend/src/api/setup.ts @@ -57,6 +57,17 @@ export interface InstallResponse { restart: boolean } +export interface HealthComponentStatus { + status: string + healthy: boolean +} + +export interface RuntimeHealth { + status: string + timestamp: string + components: Record +} + /** * Get setup status */ @@ -86,3 +97,18 @@ export async function install(config: InstallRequest): Promise const response = await setupClient.post('/setup/install', config) return response.data.data } + +/** + * Get runtime health status after setup completes + */ +export async function getRuntimeHealth(): Promise { + const response = await setupClient.get('/health', { + validateStatus: () => true + }) + + if (response.status >= 500 && !response.data) { + throw new Error('Health check failed') + } + + return response.data +} diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts index ae366da4..ccd2a969 100644 --- a/frontend/src/i18n/locales/en.ts +++ b/frontend/src/i18n/locales/en.ts @@ -237,8 +237,29 @@ export default { completeInstallation: 'Complete Installation', completed: 'Installation completed!', redirecting: 'Redirecting to login page...', + redirectingHealth: 'Redirecting to the health check page...', restarting: 'Service is restarting, please wait...', timeout: 'Service restart is taking longer than expected. Please refresh the page manually.' + }, + healthCheck: { + title: 'Post-Setup Health Check', + description: 'Confirm the service, database, and Redis are all ready after installation.', + checking: 'Checking runtime health...', + retryNow: 'Retry Check', + continueToLogin: 'Continue to Login', + overallHealthy: 'The system is ready. You can continue to login and verify the deployment.', + overallDegraded: 'The service is up, but some dependencies are not fully healthy yet.', + serviceUnavailable: 'The service is not reachable yet. Confirm the restart completed and try again.', + service: 'Service', + database: 'Database', + redis: 'Redis', + checkedAt: 'Checked at', + reportedAt: 'Backend reported at', + healthy: 'Healthy', + degraded: 'Degraded', + unavailable: 'Unavailable', + unknown: 'Unknown', + fromInstall: 'Initial setup has completed. Review this first-start health result before continuing.' } }, diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts index fd5df39a..bb6c8a95 100644 --- a/frontend/src/i18n/locales/zh.ts +++ b/frontend/src/i18n/locales/zh.ts @@ -237,8 +237,29 @@ export default { completeInstallation: '完成安装', completed: '安装完成!', redirecting: '正在跳转到登录页面...', + redirectingHealth: '正在跳转到健康检查页面...', restarting: '服务正在重启,请稍候...', timeout: '服务重启时间超出预期,请手动刷新页面。' + }, + healthCheck: { + title: '初始化后健康检查', + description: '安装完成后,确认服务、数据库和 Redis 已全部就绪。', + checking: '正在检查服务状态...', + retryNow: '重新检查', + continueToLogin: '继续前往登录', + overallHealthy: '系统已准备就绪,可以继续登录和上线验证。', + overallDegraded: '系统已经启动,但仍有依赖未完全就绪,请先排查后再继续。', + serviceUnavailable: '当前无法连接到服务,请确认服务已完成重启并对外提供访问。', + service: '服务进程', + database: '数据库', + redis: 'Redis', + checkedAt: '本地检查时间', + reportedAt: '后端上报时间', + healthy: '健康', + degraded: '降级', + unavailable: '不可达', + unknown: '未知', + fromInstall: '系统初始化已完成,下面是首次启动健康检查结果。' } }, diff --git a/frontend/src/router/__tests__/guards.spec.ts b/frontend/src/router/__tests__/guards.spec.ts index f597e75e..fc2b6bfa 100644 --- a/frontend/src/router/__tests__/guards.spec.ts +++ b/frontend/src/router/__tests__/guards.spec.ts @@ -340,6 +340,17 @@ describe('路由守卫逻辑', () => { expect(redirect).toBeNull() }) + it('unauthenticated: /setup/health is allowed', () => { + const authState: MockAuthState = { + isAuthenticated: false, + isAdmin: false, + isSimpleMode: false, + backendModeEnabled: true, + } + const redirect = simulateGuard('/setup/health', { requiresAuth: false }, authState) + expect(redirect).toBeNull() + }) + it('admin: /admin/dashboard is allowed', () => { const authState: MockAuthState = { isAuthenticated: true, diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts index 9f2dad5b..e7078a80 100644 --- a/frontend/src/router/index.ts +++ b/frontend/src/router/index.ts @@ -25,6 +25,15 @@ const routes: RouteRecordRaw[] = [ title: 'Setup' } }, + { + path: '/setup/health', + name: 'SetupHealthCheck', + component: () => import('@/views/setup/SetupHealthCheckView.vue'), + meta: { + requiresAuth: false, + title: 'Setup Health Check' + } + }, // ==================== Public Routes ==================== { diff --git a/frontend/src/views/setup/SetupHealthCheckView.vue b/frontend/src/views/setup/SetupHealthCheckView.vue new file mode 100644 index 00000000..d140a7ae --- /dev/null +++ b/frontend/src/views/setup/SetupHealthCheckView.vue @@ -0,0 +1,325 @@ + + + diff --git a/frontend/src/views/setup/SetupWizardView.vue b/frontend/src/views/setup/SetupWizardView.vue index 5774899e..5415c524 100644 --- a/frontend/src/views/setup/SetupWizardView.vue +++ b/frontend/src/views/setup/SetupWizardView.vue @@ -425,7 +425,7 @@

{{ serviceReady - ? t('setup.status.redirecting') + ? t('setup.status.redirectingHealth') : t('setup.status.restarting') }}

@@ -654,9 +654,9 @@ async function waitForServiceRestart() { // If needs_setup is false, service has restarted in normal mode if (data.data && !data.data.needs_setup) { serviceReady.value = true - // Redirect to login page after a short delay + // Redirect to the post-setup health check page after a short delay setTimeout(() => { - window.location.href = '/login' + window.location.href = '/setup/health?from=install' }, 1500) return }