diff --git a/src/App.tsx b/src/App.tsx
index f489fed..94ef3f5 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -13,6 +13,7 @@ import ProjectDetailPage from '@/pages/ProjectDetailPage'
import MilestonesPage from '@/pages/MilestonesPage'
import MilestoneDetailPage from '@/pages/MilestoneDetailPage'
import NotificationsPage from '@/pages/NotificationsPage'
+import MonitorPage from '@/pages/MonitorPage'
import axios from 'axios'
const WIZARD_PORT = Number(import.meta.env.VITE_WIZARD_PORT) || 18080
@@ -49,19 +50,28 @@ export default function App() {
}
if (appState === 'checking') {
- return
检查配置状态...
+ return Checking configuration status...
}
if (appState === 'setup') {
return
}
- if (loading) return 加载中...
+ if (loading) return Loading...
if (!user) {
return (
-
+
+
+
+
+ } />
+ } />
+ } />
+
+
+
)
}
@@ -81,6 +91,7 @@ export default function App() {
} />
} />
} />
+ } />
} />
diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx
index 749d8bf..5999a9e 100644
--- a/src/components/Sidebar.tsx
+++ b/src/components/Sidebar.tsx
@@ -1,5 +1,5 @@
import { useState, useEffect } from 'react'
-import { Link, useLocation } from 'react-router-dom'
+import { Link, useLocation, useNavigate } from 'react-router-dom'
import api from '@/services/api'
import type { User } from '@/types'
@@ -10,9 +10,14 @@ interface Props {
export default function Sidebar({ user, onLogout }: Props) {
const { pathname } = useLocation()
+ const navigate = useNavigate()
const [unreadCount, setUnreadCount] = useState(0)
useEffect(() => {
+ if (!user) {
+ setUnreadCount(0)
+ return
+ }
api.get<{ count: number }>('/notifications/count')
.then(({ data }) => setUnreadCount(data.count))
.catch(() => {})
@@ -22,14 +27,17 @@ export default function Sidebar({ user, onLogout }: Props) {
.catch(() => {})
}, 30000)
return () => clearInterval(timer)
- }, [])
+ }, [user])
- const links = [
- { to: '/', icon: '📊', label: '仪表盘' },
+ const links = user ? [
+ { to: '/', icon: '📊', label: 'Dashboard' },
{ to: '/issues', icon: '📋', label: 'Issues' },
- { to: '/projects', icon: '📁', label: '项目' },
- { to: '/milestones', icon: '🏁', label: '里程碑' },
- { to: '/notifications', icon: '🔔', label: `通知${unreadCount > 0 ? ` (${unreadCount})` : ''}` },
+ { to: '/projects', icon: '📁', label: 'Projects' },
+ { to: '/milestones', icon: '🏁', label: 'Milestones' },
+ { to: '/notifications', icon: '🔔', label: 'Notifications' + (unreadCount > 0 ? ' (' + unreadCount + ')' : '') },
+ { to: '/monitor', icon: '📡', label: 'Monitor' },
+ ] : [
+ { to: '/monitor', icon: '📡', label: 'Monitor' },
]
return (
@@ -44,12 +52,14 @@ export default function Sidebar({ user, onLogout }: Props) {
))}
- {user && (
-
- 👤 {user.username}
-
-
- )}
+
+ 👤 {user ? user.username : 'Guest'}
+ {user ? (
+
+ ) : (
+
+ )}
+
)
}
diff --git a/src/index.css b/src/index.css
index adc51c4..1b351bf 100644
--- a/src/index.css
+++ b/src/index.css
@@ -183,3 +183,21 @@ dd { font-size: .9rem; }
.setup-done { text-align: center; }
.setup-done h2 { color: var(--success); margin-bottom: 12px; }
@keyframes fadeIn { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } }
+
+
+/* Monitor */
+.monitor-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); gap: 16px; margin-top: 12px; }
+.monitor-card { background: var(--bg-card); border: 1px solid var(--border); border-radius: 8px; padding: 16px; }
+.monitor-card-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 8px; }
+.monitor-metrics { margin: 8px 0; font-size: .9rem; }
+.monitor-admin { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 16px; }
+
+.status-ok { background: var(--success); }
+.status-error { background: var(--danger); }
+.status-pending { background: var(--warning); }
+.status-online { background: var(--success); }
+.status-offline { background: var(--danger); }
+
+.btn-secondary { background: none; border: 1px solid var(--border); color: var(--text); padding: 6px 12px; border-radius: 6px; cursor: pointer; }
+.btn-danger { background: var(--danger); color: #fff; border: none; padding: 6px 12px; border-radius: 6px; cursor: pointer; }
+.btn-danger:hover { opacity: .9; }
diff --git a/src/pages/CreateIssuePage.tsx b/src/pages/CreateIssuePage.tsx
index 86a175d..4f0cc0f 100644
--- a/src/pages/CreateIssuePage.tsx
+++ b/src/pages/CreateIssuePage.tsx
@@ -27,16 +27,16 @@ export default function CreateIssuePage() {
return (
-
新建 Issue
+ Create Issue
)
diff --git a/src/pages/DashboardPage.tsx b/src/pages/DashboardPage.tsx
index f05f034..38c7491 100644
--- a/src/pages/DashboardPage.tsx
+++ b/src/pages/DashboardPage.tsx
@@ -9,7 +9,7 @@ export default function DashboardPage() {
api.get('/dashboard/stats').then(({ data }) => setStats(data))
}, [])
- if (!stats) return 加载中...
+ if (!stats) return Loading...
const statusColors: Record = {
open: '#3b82f6', in_progress: '#f59e0b', resolved: '#10b981',
@@ -21,12 +21,12 @@ export default function DashboardPage() {
return (
-
📊 仪表盘
+
📊 Dashboard
{stats.total_issues}
- 总 Issues
+ Total Issues
{Object.entries(stats.by_status || {}).map(([k, v]) => (
@@ -37,7 +37,7 @@ export default function DashboardPage() {
-
按优先级
+
By Priority
{Object.entries(stats.by_priority || {}).map(([k, v]) => (
@@ -52,10 +52,10 @@ export default function DashboardPage() {
-
最近 Issues
+
Recent Issues
- | ID | 标题 | 状态 | 优先级 | 类型 |
+ | ID | Title | Status | Priority | Type |
{(stats.recent_issues || []).map((i) => (
diff --git a/src/pages/IssueDetailPage.tsx b/src/pages/IssueDetailPage.tsx
index 5d1263b..da1a713 100644
--- a/src/pages/IssueDetailPage.tsx
+++ b/src/pages/IssueDetailPage.tsx
@@ -30,7 +30,7 @@ export default function IssueDetailPage() {
setIssue(data)
}
- if (!issue) return 加载中...
+ if (!issue) return Loading...
const statusActions: Record = {
open: ['in_progress', 'blocked'],
@@ -42,7 +42,7 @@ export default function IssueDetailPage() {
return (
-
+
#{issue.id} {issue.title}
@@ -56,21 +56,21 @@ export default function IssueDetailPage() {
-
描述
-
{issue.description || '暂无描述'}
+
Description
+
{issue.description || 'No description'}
-
详情
+
Details
- - 创建时间
- {dayjs(issue.created_at).format('YYYY-MM-DD HH:mm')}
- {issue.due_date && <>- 截止日期
- {dayjs(issue.due_date).format('YYYY-MM-DD')}
>}
- {issue.updated_at && <>- 更新时间
- {dayjs(issue.updated_at).format('YYYY-MM-DD HH:mm')}
>}
+ - Created
- {dayjs(issue.created_at).format('YYYY-MM-DD HH:mm')}
+ {issue.due_date && <>- Due date
- {dayjs(issue.due_date).format('YYYY-MM-DD')}
>}
+ {issue.updated_at && <>- Updated
- {dayjs(issue.updated_at).format('YYYY-MM-DD HH:mm')}
>}
-
状态变更
+
Status changes
{(statusActions[issue.status] || []).map((s) => (
@@ -79,16 +79,16 @@ export default function IssueDetailPage() {
-
评论 ({comments.length})
+
Comments ({comments.length})
{comments.map((c) => (
-
用户 #{c.author_id} · {dayjs(c.created_at).format('MM-DD HH:mm')}
+
User #{c.author_id} · {dayjs(c.created_at).format('MM-DD HH:mm')}
{c.content}
))}
-
diff --git a/src/pages/IssuesPage.tsx b/src/pages/IssuesPage.tsx
index 2760c97..dab71d6 100644
--- a/src/pages/IssuesPage.tsx
+++ b/src/pages/IssuesPage.tsx
@@ -33,12 +33,12 @@ export default function IssuesPage() {
📋 Issues ({total})
-
+