diff --git a/src/App.tsx b/src/App.tsx index 94ef3f5..d1d5982 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 RoleEditorPage from '@/pages/RoleEditorPage' import MonitorPage from '@/pages/MonitorPage' import axios from 'axios' @@ -66,7 +67,8 @@ export default function App() {
- } /> + } /> + } /> } /> } /> @@ -91,6 +93,7 @@ export default function App() { } /> } /> } /> + } /> } /> } /> diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 1b0ade5..3395448 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -34,6 +34,7 @@ export default function Sidebar({ user, onLogout }: Props) { { to: '/projects', icon: '📁', label: 'Projects' }, { to: '/notifications', icon: '🔔', label: 'Notifications' + (unreadCount > 0 ? ' (' + unreadCount + ')' : '') }, { to: '/monitor', icon: '📡', label: 'Monitor' }, + ...(user.is_admin ? [{ to: '/roles', icon: '🔐', label: 'Roles' }] : []), ] : [ { to: '/monitor', icon: '📡', label: 'Monitor' }, ] diff --git a/src/pages/RoleEditorPage.tsx b/src/pages/RoleEditorPage.tsx new file mode 100644 index 0000000..ba483f8 --- /dev/null +++ b/src/pages/RoleEditorPage.tsx @@ -0,0 +1,184 @@ +import { useState, useEffect } from 'react' +import api from '@/services/api' + +interface Permission { + id: number + name: string + description: string + category: string +} + +interface Role { + id: number + name: string + description: string + is_global: boolean + permission_ids: number[] +} + +export default function RoleEditorPage() { + const [roles, setRoles] = useState([]) + const [permissions, setPermissions] = useState([]) + const [selectedRole, setSelectedRole] = useState(null) + const [loading, setLoading] = useState(true) + const [saving, setSaving] = useState(false) + const [message, setMessage] = useState('') + + useEffect(() => { + fetchData() + }, []) + + const fetchData = async () => { + try { + const [rolesRes, permsRes] = await Promise.all([ + api.get('/roles'), + api.get('/roles/permissions') + ]) + setRoles(rolesRes.data) + setPermissions(permsRes.data) + } catch (err) { + console.error('Failed to fetch data:', err) + } finally { + setLoading(false) + } + } + + const handlePermissionToggle = (permId: number) => { + if (!selectedRole) return + const newPermIds = selectedRole.permission_ids.includes(permId) + ? selectedRole.permission_ids.filter(id => id !== permId) + : [...selectedRole.permission_ids, permId] + setSelectedRole({ ...selectedRole, permission_ids: newPermIds }) + } + + const handleSave = async () => { + if (!selectedRole) return + setSaving(true) + setMessage('') + try { + await api.post(`/roles/${selectedRole.id}/permissions`, { + permission_ids: selectedRole.permission_ids + }) + setMessage('Saved successfully!') + fetchData() + } catch (err: any) { + setMessage(err.response?.data?.detail || 'Failed to save') + } finally { + setSaving(false) + } + } + + const groupedPermissions = permissions.reduce((acc, p) => { + if (!acc[p.category]) acc[p.category] = [] + acc[p.category].push(p) + return acc + }, {} as Record) + + if (loading) return
Loading...
+ + return ( +
+

🔐 Role Editor

+

+ Configure permissions for each role. Only admins can edit roles. +

+ + {message && ( +
+ {message} +
+ )} + +
+ {/* Role List */} +
+

Roles

+
+ {roles.map(role => ( +
setSelectedRole({ ...role })} + style={{ + padding: '12px', + border: selectedRole?.id === role.id ? '2px solid #007bff' : '1px solid #ddd', + borderRadius: '6px', + cursor: 'pointer', + backgroundColor: selectedRole?.id === role.id ? '#f0f8ff' : 'white' + }} + > + {role.name} + {role.is_global && 🌟} +
{role.description}
+
+ {role.permission_ids.length} permissions +
+
+ ))} +
+
+ + {/* Permission Editor */} +
+ {selectedRole ? ( + <> +

Permissions for: {selectedRole.name}

+
+ {Object.entries(groupedPermissions).map(([category, perms]) => ( +
+

+ {category} +

+
+ {perms.map(perm => ( + + ))} +
+
+ ))} +
+ + + ) : ( +
+ Select a role to edit its permissions +
+ )} +
+
+
+ ) +}