Add comprehensive backend features and mobile UI improvements

Backend:
- Add 2FA authentication with TOTP support
- Add API keys management system
- Add audit logging for security events
- Add file upload/management system
- Add notifications system with preferences
- Add session management
- Add webhooks integration
- Add analytics endpoints
- Add export functionality
- Add password policy enforcement
- Add new database migrations for core tables

Frontend:
- Add module position system (top/bottom sidebar sections)
- Add search and notifications module configuration tabs
- Add mobile logo replacing hamburger menu
- Center page title absolutely when no tabs present
- Align sidebar footer toggles with navigation items
- Add lighter icon color in dark theme for mobile
- Add API keys management page
- Add notifications page with context
- Add admin analytics and audit logs pages

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-17 22:27:32 +01:00
parent f698aa4d51
commit 8c4a555b88
76 changed files with 9751 additions and 323 deletions

View File

@@ -7,6 +7,7 @@ import { useAuth } from '../contexts/AuthContext';
import { useModules } from '../contexts/ModulesContext';
import { useSiteConfig } from '../contexts/SiteConfigContext';
import { useTheme } from '../contexts/ThemeContext';
import { useNotifications } from '../contexts/NotificationsContext';
import { appModules } from '../modules';
import UserMenu from './UserMenu';
import '../styles/Sidebar.css';
@@ -27,7 +28,8 @@ export default function Sidebar() {
} = useSidebar();
const { viewMode, toggleViewMode, isUserModeEnabled } = useViewMode();
const { user } = useAuth();
const { isModuleEnabled, isModuleEnabledForUser, moduleOrder, hasInitialized: modulesInitialized } = useModules();
const { isModuleEnabled, isModuleEnabledForUser, moduleOrder, moduleStates, hasInitialized: modulesInitialized } = useModules();
const { unreadCount } = useNotifications();
// When admin is in "user mode", show only user-permitted modules
// Otherwise, show all globally enabled modules (admin view)
@@ -38,6 +40,8 @@ export default function Sidebar() {
.find((cat) => cat.id === 'main')
?.modules.filter((m) => {
if (!m.enabled) return false;
// Dashboard is always shown
if (m.id === 'dashboard') return true;
if (shouldUseUserPermissions) {
return isModuleEnabledForUser(m.id, user?.permissions, user?.is_superuser || false);
}
@@ -45,7 +49,7 @@ export default function Sidebar() {
}) || []);
// Sort modules based on moduleOrder (dashboard always first, then ordered features)
const mainModules = [...mainModulesFiltered].sort((a, b) => {
const sortedModules = [...mainModulesFiltered].sort((a, b) => {
// Dashboard always comes first
if (a.id === 'dashboard') return -1;
if (b.id === 'dashboard') return 1;
@@ -63,6 +67,19 @@ export default function Sidebar() {
return 0;
});
// Split modules by position (top = main nav, bottom = above footer)
const topModules = sortedModules.filter(m => {
if (m.id === 'dashboard') return true; // Dashboard always at top
const state = moduleStates[m.id as keyof typeof moduleStates];
return !state || state.position === 'top';
});
const bottomModules = sortedModules.filter(m => {
if (m.id === 'dashboard') return false; // Dashboard never at bottom
const state = moduleStates[m.id as keyof typeof moduleStates];
return state && state.position === 'bottom';
});
const handleCollapseClick = () => {
if (isMobileOpen) {
closeMobileMenu();
@@ -222,7 +239,7 @@ export default function Sidebar() {
<nav className="sidebar-nav">
<div className="nav-section">
{mainModules.map((module) => (
{topModules.map((module) => (
<NavLink
key={module.id}
to={module.path}
@@ -233,12 +250,36 @@ export default function Sidebar() {
>
<span className="nav-icon material-symbols-outlined">{module.icon}</span>
<span className="nav-label">{t.sidebar[module.id as keyof typeof t.sidebar]}</span>
{module.id === 'notifications' && unreadCount > 0 && (
<span className="nav-badge" aria-label={`${unreadCount} unread notifications`}>
{unreadCount > 99 ? '99+' : unreadCount}
</span>
)}
</NavLink>
))}
</div>
</nav>
<div className="sidebar-footer">
{bottomModules.map((module) => (
<NavLink
key={module.id}
to={module.path}
className={({ isActive }) => `nav-item ${isActive ? 'active' : ''}`}
onClick={(e) => handleNavClick(e, module.path)}
onMouseEnter={(e) => handleItemMouseEnter(t.sidebar[module.id as keyof typeof t.sidebar], e)}
onMouseLeave={handleItemMouseLeave}
>
<span className="nav-icon material-symbols-outlined">{module.icon}</span>
<span className="nav-label">{t.sidebar[module.id as keyof typeof t.sidebar]}</span>
{module.id === 'notifications' && unreadCount > 0 && (
<span className="nav-badge" aria-label={`${unreadCount} unread notifications`}>
{unreadCount > 99 ? '99+' : unreadCount}
</span>
)}
</NavLink>
))}
{user?.is_superuser && isUserModeEnabled && (
<button
className={`view-mode-toggle ${viewMode === 'user' ? 'user-mode' : 'admin-mode'}`}