fix: nginx reverse proxy for API/wizard, fix Object.entries null crash
- Replace serve with nginx for proper reverse proxy - /api/* proxied to backend:8000, /wizard/* proxied to wizard:8080 - Eliminates CORS issues (same-origin requests) - Fixes SPA fallback returning 200 for API routes (was hiding backend-down state) - Add null guards on Object.entries for dashboard stats - Remove VITE_API_BASE/VITE_WIZARD_PORT build args (no longer needed)
This commit is contained in:
16
Dockerfile
16
Dockerfile
@@ -4,16 +4,12 @@ WORKDIR /app
|
||||
COPY package.json package-lock.json* ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
ARG VITE_API_BASE=/api
|
||||
ARG VITE_WIZARD_PORT=18080
|
||||
ENV VITE_API_BASE=$VITE_API_BASE
|
||||
ENV VITE_WIZARD_PORT=$VITE_WIZARD_PORT
|
||||
# API and wizard are proxied via nginx — no VITE_ env vars needed
|
||||
RUN npm run build
|
||||
|
||||
# Production stage — lightweight static server, no nginx
|
||||
FROM node:20-alpine
|
||||
RUN npm install -g serve@14
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/dist ./dist
|
||||
# Production stage — nginx with reverse proxy
|
||||
FROM nginx:alpine
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
EXPOSE 3000
|
||||
CMD ["serve", "-s", "dist", "-l", "3000"]
|
||||
CMD ["nginx", "-g", "daemon off;"]
|
||||
|
||||
29
nginx.conf
Normal file
29
nginx.conf
Normal file
@@ -0,0 +1,29 @@
|
||||
server {
|
||||
listen 3000;
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
|
||||
# Backend API proxy
|
||||
location /api/ {
|
||||
proxy_pass http://backend:8000/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_connect_timeout 5s;
|
||||
proxy_read_timeout 30s;
|
||||
}
|
||||
|
||||
# Wizard API proxy (setup phase only)
|
||||
location /wizard/ {
|
||||
proxy_pass http://wizard:8080/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_connect_timeout 5s;
|
||||
proxy_read_timeout 10s;
|
||||
}
|
||||
|
||||
# SPA fallback — only for non-API, non-asset routes
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
}
|
||||
@@ -15,8 +15,6 @@ import MilestoneDetailPage from '@/pages/MilestoneDetailPage'
|
||||
import NotificationsPage from '@/pages/NotificationsPage'
|
||||
import api from '@/services/api'
|
||||
|
||||
const WIZARD_PORT = Number(import.meta.env.VITE_WIZARD_PORT) || 18080
|
||||
|
||||
type AppState = 'checking' | 'setup' | 'ready'
|
||||
|
||||
export default function App() {
|
||||
@@ -41,7 +39,7 @@ export default function App() {
|
||||
|
||||
// Backend not ready — show setup wizard
|
||||
if (appState === 'setup') {
|
||||
return <SetupWizardPage wizardPort={WIZARD_PORT} onComplete={checkBackend} />
|
||||
return <SetupWizardPage wizardPort={18080} onComplete={checkBackend} />
|
||||
}
|
||||
|
||||
// Backend ready but auth loading
|
||||
|
||||
@@ -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={{
|
||||
|
||||
@@ -48,7 +48,7 @@ export default function SetupWizardPage({ wizardPort, onComplete }: Props) {
|
||||
})
|
||||
|
||||
const wizardApi = axios.create({
|
||||
baseURL: `http://127.0.0.1:${wizardPort}`,
|
||||
baseURL: '/wizard',
|
||||
timeout: 5000,
|
||||
})
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import axios from 'axios'
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_BASE || '/api',
|
||||
baseURL: '/api',
|
||||
})
|
||||
|
||||
api.interceptors.request.use((config) => {
|
||||
|
||||
Reference in New Issue
Block a user