fix: check wizard config for initialized flag instead of backend health
- App checks wizard API for harborforge.json config with initialized=true - If not initialized, show embedded setup wizard (talks to wizard API via CORS) - Setup saves config with initialized:true to wizard config volume - After restart, backend reads config and starts, frontend sees initialized=true - Remove VITE_API_BASE build arg (not needed, api.ts uses /api relative path) - Fix Object.entries null crash in DashboardPage
This commit is contained in:
30
src/App.tsx
30
src/App.tsx
@@ -13,9 +13,10 @@ import ProjectDetailPage from '@/pages/ProjectDetailPage'
|
||||
import MilestonesPage from '@/pages/MilestonesPage'
|
||||
import MilestoneDetailPage from '@/pages/MilestoneDetailPage'
|
||||
import NotificationsPage from '@/pages/NotificationsPage'
|
||||
import api from '@/services/api'
|
||||
import axios from 'axios'
|
||||
|
||||
const WIZARD_PORT = Number(import.meta.env.VITE_WIZARD_PORT) || 18080
|
||||
const WIZARD_BASE = `http://127.0.0.1:${WIZARD_PORT}`
|
||||
|
||||
type AppState = 'checking' | 'setup' | 'ready'
|
||||
|
||||
@@ -23,31 +24,36 @@ export default function App() {
|
||||
const [appState, setAppState] = useState<AppState>('checking')
|
||||
const { user, loading, login, logout } = useAuth()
|
||||
|
||||
const checkBackend = async () => {
|
||||
useEffect(() => {
|
||||
checkInitialized()
|
||||
}, [])
|
||||
|
||||
const checkInitialized = async () => {
|
||||
try {
|
||||
await api.get('/health')
|
||||
setAppState('ready')
|
||||
const res = await axios.get(`${WIZARD_BASE}/api/v1/config/harborforge.json`, {
|
||||
timeout: 5000,
|
||||
})
|
||||
if (res.data && res.data.initialized === true) {
|
||||
setAppState('ready')
|
||||
} else {
|
||||
setAppState('setup')
|
||||
}
|
||||
} catch {
|
||||
// Wizard unreachable or config doesn't exist → setup needed
|
||||
setAppState('setup')
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => { checkBackend() }, [])
|
||||
|
||||
// Checking backend availability
|
||||
if (appState === 'checking') {
|
||||
return <div className="loading">检查后端状态...</div>
|
||||
return <div className="loading">检查配置状态...</div>
|
||||
}
|
||||
|
||||
// Backend not ready — show setup wizard
|
||||
if (appState === 'setup') {
|
||||
return <SetupWizardPage wizardPort={WIZARD_PORT} onComplete={checkBackend} />
|
||||
return <SetupWizardPage wizardBase={WIZARD_BASE} onComplete={checkInitialized} />
|
||||
}
|
||||
|
||||
// Backend ready but auth loading
|
||||
if (loading) return <div className="loading">加载中...</div>
|
||||
|
||||
// Not logged in
|
||||
if (!user) {
|
||||
return (
|
||||
<BrowserRouter>
|
||||
|
||||
@@ -28,7 +28,7 @@ export default function DashboardPage() {
|
||||
<span className="stat-number">{stats.total_issues}</span>
|
||||
<span className="stat-label">总 Issues</span>
|
||||
</div>
|
||||
{Object.entries(stats.by_status).map(([k, v]) => (
|
||||
{Object.entries(stats.by_status || {}).map(([k, v]) => (
|
||||
<div className="stat-card" key={k} style={{ borderLeftColor: statusColors[k] || '#ccc' }}>
|
||||
<span className="stat-number">{v}</span>
|
||||
<span className="stat-label">{k}</span>
|
||||
@@ -39,7 +39,7 @@ export default function DashboardPage() {
|
||||
<div className="section">
|
||||
<h3>按优先级</h3>
|
||||
<div className="bar-chart">
|
||||
{Object.entries(stats.by_priority).map(([k, v]) => (
|
||||
{Object.entries(stats.by_priority || {}).map(([k, v]) => (
|
||||
<div className="bar-row" key={k}>
|
||||
<span className="bar-label">{k}</span>
|
||||
<div className="bar" style={{
|
||||
|
||||
@@ -2,32 +2,27 @@ import { useState } from 'react'
|
||||
import axios from 'axios'
|
||||
|
||||
interface Props {
|
||||
wizardPort: number
|
||||
wizardBase: string
|
||||
onComplete: () => void
|
||||
}
|
||||
|
||||
interface SetupForm {
|
||||
// Admin
|
||||
admin_username: string
|
||||
admin_password: string
|
||||
admin_email: string
|
||||
admin_full_name: string
|
||||
// Database
|
||||
db_host: string
|
||||
db_port: number
|
||||
db_user: string
|
||||
db_password: string
|
||||
db_database: string
|
||||
// Backend
|
||||
backend_url: string
|
||||
// Default project
|
||||
project_name: string
|
||||
project_description: string
|
||||
}
|
||||
|
||||
const STEPS = ['欢迎', '数据库', '管理员', '项目', '完成']
|
||||
|
||||
export default function SetupWizardPage({ wizardPort, onComplete }: Props) {
|
||||
export default function SetupWizardPage({ wizardBase, onComplete }: Props) {
|
||||
const [step, setStep] = useState(0)
|
||||
const [error, setError] = useState('')
|
||||
const [saving, setSaving] = useState(false)
|
||||
@@ -42,13 +37,12 @@ export default function SetupWizardPage({ wizardPort, onComplete }: Props) {
|
||||
db_user: 'harborforge',
|
||||
db_password: 'harborforge_pass',
|
||||
db_database: 'harborforge',
|
||||
backend_url: '',
|
||||
project_name: 'Default',
|
||||
project_description: '默认项目',
|
||||
})
|
||||
|
||||
const wizardApi = axios.create({
|
||||
baseURL: `http://127.0.0.1:${wizardPort}`,
|
||||
baseURL: wizardBase,
|
||||
timeout: 5000,
|
||||
})
|
||||
|
||||
@@ -63,7 +57,7 @@ export default function SetupWizardPage({ wizardPort, onComplete }: Props) {
|
||||
setStep(1)
|
||||
} catch {
|
||||
setWizardOk(false)
|
||||
setError(`无法连接 AbstractWizard (127.0.0.1:${wizardPort})。请确认已通过 SSH 隧道映射端口:\nssh -L ${wizardPort}:127.0.0.1:${wizardPort} user@server`)
|
||||
setError(`无法连接 AbstractWizard (${wizardBase})。\n请确认已通过 SSH 隧道映射端口:\nssh -L <wizard_port>:127.0.0.1:<wizard_port> user@server`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +66,7 @@ export default function SetupWizardPage({ wizardPort, onComplete }: Props) {
|
||||
setSaving(true)
|
||||
try {
|
||||
const config = {
|
||||
initialized: true,
|
||||
admin: {
|
||||
username: form.admin_username,
|
||||
password: form.admin_password,
|
||||
@@ -85,7 +80,6 @@ export default function SetupWizardPage({ wizardPort, onComplete }: Props) {
|
||||
password: form.db_password,
|
||||
database: form.db_database,
|
||||
},
|
||||
backend_url: form.backend_url || undefined,
|
||||
default_project: form.project_name
|
||||
? { name: form.project_name, description: form.project_description }
|
||||
: undefined,
|
||||
@@ -95,9 +89,6 @@ export default function SetupWizardPage({ wizardPort, onComplete }: Props) {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
|
||||
// Switch wizard to readonly after setup
|
||||
await wizardApi.put('/api/v1/mode', { mode: 'readonly' }).catch(() => {})
|
||||
|
||||
setStep(4)
|
||||
} catch (err: any) {
|
||||
setError(`保存配置失败: ${err.message}`)
|
||||
@@ -129,7 +120,7 @@ export default function SetupWizardPage({ wizardPort, onComplete }: Props) {
|
||||
<p>Agent/人类协同任务管理平台</p>
|
||||
<div className="setup-info">
|
||||
<p>⚠️ 初始化向导通过 SSH 隧道连接 AbstractWizard,请确保已映射端口:</p>
|
||||
<code>ssh -L {wizardPort}:127.0.0.1:{wizardPort} user@your-server</code>
|
||||
<code>ssh -L <wizard_port>:127.0.0.1:<wizard_port> user@your-server</code>
|
||||
</div>
|
||||
<button className="btn-primary" onClick={checkWizard}>
|
||||
连接 Wizard 开始配置
|
||||
@@ -204,10 +195,11 @@ export default function SetupWizardPage({ wizardPort, onComplete }: Props) {
|
||||
<div className="setup-step-content">
|
||||
<div className="setup-done">
|
||||
<h2>✅ 初始化完成!</h2>
|
||||
<p>配置已保存到 AbstractWizard,Wizard 已切换为只读模式。</p>
|
||||
<p>后端将在检测到配置后自动启动。</p>
|
||||
<p>配置已保存到 AbstractWizard。</p>
|
||||
<div className="setup-info">
|
||||
<p>请等待后端启动完成,然后刷新页面进入登录界面。</p>
|
||||
<p>请在服务器上重启服务:</p>
|
||||
<code>docker compose restart</code>
|
||||
<p style={{ marginTop: '1rem' }}>后端启动完成后,刷新本页面进入登录界面。</p>
|
||||
<p>管理员账号: <strong>{form.admin_username}</strong></p>
|
||||
</div>
|
||||
<button className="btn-primary" onClick={onComplete}>
|
||||
|
||||
Reference in New Issue
Block a user