feat: add admin dashboard, audit logs, token expiry check and test suite

Frontend Features:
- Add ProtectedRoute component with token expiry validation
- Create AdminDashboardPage with system statistics and user management
- Create AuditLogsPage with filtering and pagination
- Add admin-only navigation (Shield icon) for ymirliu@panjit.com.tw
- Add admin API methods to apiV2 service
- Add admin type definitions (SystemStats, AuditLog, etc.)

Token Management:
- Auto-redirect to login on token expiry
- Check authentication on route change
- Show loading state during auth check
- Admin privilege verification

Backend Testing:
- Add pytest configuration (pytest.ini)
- Create test fixtures (conftest.py)
- Add unit tests for auth, tasks, and admin endpoints
- Add integration tests for complete workflows
- Test user isolation and admin access control

Documentation:
- Add TESTING.md with comprehensive testing guide
- Include test running instructions
- Document fixtures and best practices

Routes:
- /admin - Admin dashboard (admin only)
- /admin/audit-logs - Audit logs viewer (admin only)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
egg
2025-11-16 18:01:50 +08:00
parent fd98018ddd
commit 8f94191914
13 changed files with 1554 additions and 45 deletions

View File

@@ -14,7 +14,8 @@ import {
ChevronRight,
Bell,
Search,
History
History,
Shield
} from 'lucide-react'
export default function Layout() {
@@ -22,6 +23,9 @@ export default function Layout() {
const logout = useAuthStore((state) => state.logout)
const user = useAuthStore((state) => state.user)
// Check if user is admin
const isAdmin = user?.email === 'ymirliu@panjit.com.tw'
const handleLogout = async () => {
try {
// Use V2 API if authenticated with V2
@@ -38,14 +42,18 @@ export default function Layout() {
}
const navLinks = [
{ to: '/upload', label: t('nav.upload'), icon: Upload, description: '上傳檔案' },
{ to: '/processing', label: t('nav.processing'), icon: Activity, description: '處理進度' },
{ to: '/results', label: t('nav.results'), icon: FileText, description: '查看結果' },
{ to: '/tasks', label: '任務歷史', icon: History, description: '查看任務記錄' },
{ to: '/export', label: t('nav.export'), icon: Download, description: '導出文件' },
{ to: '/settings', label: t('nav.settings'), icon: Settings, description: '系統設定' },
{ to: '/upload', label: t('nav.upload'), icon: Upload, description: '上傳檔案', adminOnly: false },
{ to: '/processing', label: t('nav.processing'), icon: Activity, description: '處理進度', adminOnly: false },
{ to: '/results', label: t('nav.results'), icon: FileText, description: '查看結果', adminOnly: false },
{ to: '/tasks', label: '任務歷史', icon: History, description: '查看任務記錄', adminOnly: false },
{ to: '/export', label: t('nav.export'), icon: Download, description: '導出文件', adminOnly: false },
{ to: '/settings', label: t('nav.settings'), icon: Settings, description: '系統設定', adminOnly: false },
{ to: '/admin', label: '管理員儀表板', icon: Shield, description: '系統管理', adminOnly: true },
]
// Filter nav links based on admin status
const visibleNavLinks = navLinks.filter(link => !link.adminOnly || isAdmin)
return (
<div className="flex h-screen bg-background overflow-hidden">
{/* Sidebar */}
@@ -65,7 +73,7 @@ export default function Layout() {
{/* Navigation */}
<nav className="flex-1 px-3 py-6 space-y-1 overflow-y-auto scrollbar-thin">
{navLinks.map((link) => (
{visibleNavLinks.map((link) => (
<NavLink
key={link.to}
to={link.to}