diff --git a/public/assets/css/style.css b/public/assets/css/style.css index 789c036..92cfe35 100755 --- a/public/assets/css/style.css +++ b/public/assets/css/style.css @@ -301,10 +301,7 @@ code, pre { .sidebar.collapsed .nav-chevron, .sidebar.collapsed .notification-badge, .sidebar.collapsed .nav-divider, -.sidebar.collapsed .nav-section, -.sidebar.collapsed .user-details, -.sidebar.collapsed .user-menu, -.sidebar.collapsed .sidebar-footer { +.sidebar.collapsed .nav-section { display: none; } @@ -317,10 +314,6 @@ code, pre { padding: 0.65rem; } -.sidebar.collapsed .user-info { - justify-content: center; -} - /* ========================================================================== Main Content ========================================================================== */ @@ -374,6 +367,39 @@ code, pre { font-size: 0.88rem; color: var(--text-secondary); white-space: nowrap; + cursor: pointer; + background: none; + border: none; + font-family: inherit; + padding: 0.4rem 0.6rem; + border-radius: var(--radius-sm); + transition: background var(--transition); +} + +.topbar-user:hover { + background: var(--bg-hover); +} + +.topbar-user-dropdown { + position: relative; +} + +.dropdown-menu-right { + right: 0; + left: auto; +} + +.dropdown-header { + padding: 0.55rem 1rem; + font-size: 0.88rem; + display: flex; + flex-direction: column; + gap: 2px; +} + +.dropdown-header small { + font-size: 0.72rem; + color: var(--text-muted); } .btn-icon { @@ -1056,7 +1082,8 @@ textarea.form-input { box-shadow: var(--shadow-lg); } -.dropdown.active .dropdown-menu { +.dropdown.active .dropdown-menu, +.dropdown-menu.open { display: block; } @@ -2302,10 +2329,7 @@ textarea.form-input { .sidebar.mobile-open.collapsed .nav-chevron, .sidebar.mobile-open.collapsed .notification-badge, .sidebar.mobile-open.collapsed .nav-divider, - .sidebar.mobile-open.collapsed .nav-section, - .sidebar.mobile-open.collapsed .user-details, - .sidebar.mobile-open.collapsed .user-menu, - .sidebar.mobile-open.collapsed .sidebar-footer { + .sidebar.mobile-open.collapsed .nav-section { display: flex; } @@ -2318,10 +2342,6 @@ textarea.form-input { padding: 0.65rem 1.25rem; } - .sidebar.mobile-open.collapsed .user-info { - justify-content: flex-start; - } - .main-area { margin-left: 0 !important; } @@ -3280,4 +3300,21 @@ input[type="range"]::-moz-range-track { overflow-y: auto; } +.notif-view-all { + display: flex; + align-items: center; + justify-content: center; + padding: 10px 16px; + font-size: 0.82rem; + color: var(--accent); + text-decoration: none; + border-top: 1px solid var(--border-color); + transition: background var(--transition); +} + +.notif-view-all:hover { + background: var(--bg-hover); + color: var(--accent-hover); +} + diff --git a/public/assets/js/main.js b/public/assets/js/main.js index 2fdaecf..4699df1 100644 --- a/public/assets/js/main.js +++ b/public/assets/js/main.js @@ -17,6 +17,8 @@ function openNotifications() { const dd = document.getElementById('notifDropdown'); dd.classList.add('open'); fetchNotifications(); + const userMenu = document.getElementById('userMenu'); + if (userMenu) userMenu.classList.remove('open'); document.addEventListener('click', notifOutsideClick); } @@ -31,6 +33,11 @@ function notifOutsideClick(e) { if (!wrapper.contains(e.target)) { closeNotifications(); } + const userWrapper = document.querySelector('.topbar-user-dropdown'); + if (userWrapper && !userWrapper.contains(e.target)) { + const userMenu = document.getElementById('userMenu'); + if (userMenu) userMenu.classList.remove('open'); + } } function fetchNotifications() { @@ -48,14 +55,14 @@ function fetchNotifications() { list.innerHTML = '
Could not load notifications.
'; return; } - renderNotifications(d.data || [], d.unread_count || 0); + renderNotifications(d.data || [], d.unread_count || 0, d.pagination?.total || 0); }) .catch(() => { list.innerHTML = '
Failed to load notifications.
'; }); } -function renderNotifications(items, unreadCount) { +function renderNotifications(items, unreadCount, total) { const list = document.getElementById('notifList'); if (!items.length) { @@ -85,6 +92,13 @@ function renderNotifications(items, unreadCount) { + (n.is_read ? '' : '
') + ''; }); + + if (total > items.length) { + html += '' + + 'View all notifications (' + total + ' total)' + + ''; + } + list.innerHTML = html; document.getElementById('markAllReadBtn').style.display = unreadCount > 0 ? 'inline-flex' : 'none'; @@ -200,3 +214,32 @@ function toggleServerList(e) { sub.style.maxHeight = sub.scrollHeight + 'px'; } } + +function toggleUserMenu(e) { + e.stopPropagation(); + const menu = document.getElementById('userMenu'); + if (!menu) return; + const isOpen = menu.classList.contains('open'); + document.querySelectorAll('.dropdown-menu.open').forEach(function(m) { + if (m !== menu) m.classList.remove('open'); + }); + if (!isOpen) { + menu.classList.add('open'); + closeNotifications(); + setTimeout(function() { + document.addEventListener('click', closeUserMenu); + }, 0); + } else { + menu.classList.remove('open'); + } +} + +function closeUserMenu(e) { + const menu = document.getElementById('userMenu'); + if (!menu) return; + const wrapper = menu.closest('.topbar-user-dropdown'); + if (wrapper && !wrapper.contains(e.target)) { + menu.classList.remove('open'); + document.removeEventListener('click', closeUserMenu); + } +} diff --git a/routes/web.php b/routes/web.php index a4b32c9..245b034 100755 --- a/routes/web.php +++ b/routes/web.php @@ -96,10 +96,7 @@ $router->post('/terminal/:id/execute', [TerminalController::class, 'execute'], [ $router->get('/terminal/:id/history', [TerminalController::class, 'history'], [AuthMiddleware::class]); $router->post('/terminal/:id/clear-history', [TerminalController::class, 'clearHistory'], [AuthMiddleware::class, CSRFMiddleware::class]); -$router->get('/admin', function () { - $view = \ServerManager\Core\App::getInstance()->getView(); - $view->redirect('/admin/audit'); -}, [AuthMiddleware::class, RoleMiddleware::class]); +$router->get('/admin', [AdminController::class, 'index'], [AuthMiddleware::class, RoleMiddleware::class]); $router->get('/admin/activity', function () { $view = \ServerManager\Core\App::getInstance()->getView(); diff --git a/src/Controllers/AdminController.php b/src/Controllers/AdminController.php index edf2647..97e7988 100755 --- a/src/Controllers/AdminController.php +++ b/src/Controllers/AdminController.php @@ -37,6 +37,13 @@ class AdminController } } + public function index(): void + { + $this->view->display('admin.index', [ + 'title' => 'Administration - ServerManager', + ]); + } + public function users(): void { $this->requireSuperAdmin(); diff --git a/views/admin/index.php b/views/admin/index.php new file mode 100644 index 0000000..3f61738 --- /dev/null +++ b/views/admin/index.php @@ -0,0 +1,35 @@ + + +
+ + + Teams + + + + Users + + + + Audit Logs + + + + Security + + + + App Version + + + + Notifications + + + + Policies + +
diff --git a/views/layouts/main.php b/views/layouts/main.php index f2ae26a..4c02d6a 100755 --- a/views/layouts/main.php +++ b/views/layouts/main.php @@ -44,12 +44,6 @@ if (!isset($user) || $user === null) { Dashboard - - - - Notifications - - - - - - +