feat: implement group-based access control (Admin, Standard_User, Leadership, Read_Only)
- Add user_group migration and created_by column migration - Replace requireRole middleware with requireGroup - Update all backend routes to use group-based authorization - Add Standard_User conditional delete with ownership, state, and compliance checks - Add cascade impact check for CVE deletes - Update AuthContext with group-based permission helpers - Update all frontend components for group-based rendering - Update UserManagement UI with group dropdown, confirmation dialogs, self-demotion prevention
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import React from 'react';
|
||||
import { X, Home, BarChart2, BookOpen, Download, ShieldCheck } from 'lucide-react';
|
||||
import { X, Home, BarChart2, BookOpen, Download, ShieldCheck, Settings } from 'lucide-react';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
|
||||
const NAV_ITEMS = [
|
||||
{ id: 'home', label: 'Home', icon: Home, color: '#0EA5E9', description: 'Main dashboard' },
|
||||
@@ -9,7 +10,11 @@ const NAV_ITEMS = [
|
||||
{ id: 'exports', label: 'Exports', icon: Download, color: '#8B5CF6', description: 'Export data & reports' },
|
||||
];
|
||||
|
||||
const ADMIN_ITEM = { id: 'admin', label: 'Admin Panel', icon: Settings, color: '#EF4444', description: 'User management & audit' };
|
||||
|
||||
export default function NavDrawer({ isOpen, onClose, currentPage, onNavigate }) {
|
||||
const { isAdmin } = useAuth();
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
@@ -110,6 +115,60 @@ export default function NavDrawer({ isOpen, onClose, currentPage, onNavigate })
|
||||
</button>
|
||||
);
|
||||
})}
|
||||
|
||||
{/* Admin panel link — visible only to Admin group */}
|
||||
{isAdmin() && (() => {
|
||||
const { id, label, icon: Icon, color, description } = ADMIN_ITEM;
|
||||
const active = currentPage === id;
|
||||
return (
|
||||
<button
|
||||
key={id}
|
||||
onClick={() => { onNavigate(id); onClose(); }}
|
||||
style={{
|
||||
display: 'flex', alignItems: 'center', gap: '0.875rem',
|
||||
padding: '0.75rem 0.875rem',
|
||||
borderRadius: '0.5rem',
|
||||
border: active ? `1px solid ${color}50` : '1px solid transparent',
|
||||
background: active ? `${color}18` : 'transparent',
|
||||
cursor: 'pointer', textAlign: 'left', width: '100%',
|
||||
marginTop: '0.5rem',
|
||||
borderTop: '1px solid rgba(255, 255, 255, 0.05)',
|
||||
paddingTop: '1rem',
|
||||
transition: 'background 0.15s, border-color 0.15s'
|
||||
}}
|
||||
onMouseEnter={e => { if (!active) e.currentTarget.style.background = 'rgba(255,255,255,0.04)'; }}
|
||||
onMouseLeave={e => { if (!active) e.currentTarget.style.background = 'transparent'; }}
|
||||
>
|
||||
<div style={{
|
||||
width: '36px', height: '36px', flexShrink: 0,
|
||||
borderRadius: '0.375rem',
|
||||
background: `${color}18`,
|
||||
border: `1px solid ${color}40`,
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center'
|
||||
}}>
|
||||
<Icon style={{ width: '17px', height: '17px', color }} />
|
||||
</div>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{
|
||||
fontFamily: 'monospace', fontSize: '0.8rem', fontWeight: '600',
|
||||
color: active ? color : '#CBD5E1',
|
||||
textTransform: 'uppercase', letterSpacing: '0.06em'
|
||||
}}>
|
||||
{label}
|
||||
</div>
|
||||
<div style={{ fontSize: '0.68rem', color: '#475569', marginTop: '1px' }}>
|
||||
{description}
|
||||
</div>
|
||||
</div>
|
||||
{active && (
|
||||
<div style={{
|
||||
width: '6px', height: '6px', borderRadius: '50%',
|
||||
background: color, boxShadow: `0 0 8px ${color}`, flexShrink: 0
|
||||
}} />
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
})()}
|
||||
</nav>
|
||||
|
||||
{/* Footer */}
|
||||
|
||||
Reference in New Issue
Block a user