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:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user