fix: sync CSS classes with refactored layout, restore missing sidebar items, add asset versioning, fix routes and auth
This commit is contained in:
@@ -9,6 +9,7 @@ return [
|
||||
'debug' => filter_var($_ENV['APP_DEBUG'] ?? false, FILTER_VALIDATE_BOOLEAN),
|
||||
'url' => $_ENV['APP_URL'] ?? 'http://localhost',
|
||||
'timezone' => $_ENV['APP_TIMEZONE'] ?? 'America/Santo_Domingo',
|
||||
'asset_version' => $_ENV['ASSET_VERSION'] ?? '0.0.0',
|
||||
],
|
||||
|
||||
'database' => [
|
||||
|
||||
@@ -121,7 +121,7 @@ code, pre {
|
||||
}
|
||||
|
||||
/* App Container */
|
||||
.app-container {
|
||||
.app-layout {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
}
|
||||
@@ -159,7 +159,7 @@ code, pre {
|
||||
min-height: var(--topbar-height);
|
||||
}
|
||||
|
||||
.sidebar-logo {
|
||||
.sidebar-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
@@ -169,32 +169,13 @@ code, pre {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sidebar-logo i {
|
||||
.sidebar-brand i {
|
||||
font-size: 1.5rem;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.sidebar-toggle {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
font-size: 1.1rem;
|
||||
padding: 0.5rem;
|
||||
border-radius: var(--radius-sm);
|
||||
transition: color var(--transition);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-toggle:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.sidebar-toggle i,
|
||||
.mobile-toggle i,
|
||||
.nav-link i,
|
||||
.nav-sub-item i,
|
||||
.user-menu a i {
|
||||
.nav-item i,
|
||||
.nav-sub-item i {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -204,61 +185,6 @@ code, pre {
|
||||
padding: 0.75rem 0;
|
||||
}
|
||||
|
||||
.nav-list {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.65rem 1.25rem;
|
||||
color: var(--text-secondary);
|
||||
border-radius: 0;
|
||||
transition: all var(--transition);
|
||||
font-size: 0.92rem;
|
||||
white-space: nowrap;
|
||||
border-left: 3px solid transparent;
|
||||
}
|
||||
|
||||
.nav-link i {
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.nav-link.active {
|
||||
color: var(--accent);
|
||||
background: var(--accent-light);
|
||||
border-left-color: var(--accent);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.nav-divider {
|
||||
height: 1px;
|
||||
background: var(--border-color);
|
||||
margin: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
.nav-section {
|
||||
padding: 0.5rem 1.25rem;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--text-muted);
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.sidebar-footer {
|
||||
border-top: 1px solid var(--border-color);
|
||||
padding: 0.75rem 1rem;
|
||||
@@ -321,21 +247,72 @@ code, pre {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.65rem 1.25rem;
|
||||
color: var(--text-secondary);
|
||||
border-radius: 0;
|
||||
transition: all var(--transition);
|
||||
font-size: 0.92rem;
|
||||
white-space: nowrap;
|
||||
border-left: 3px solid transparent;
|
||||
}
|
||||
|
||||
.nav-item i {
|
||||
width: 20px;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav-item:hover {
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.nav-item.active {
|
||||
color: var(--accent);
|
||||
background: var(--accent-light);
|
||||
border-left-color: var(--accent);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.nav-divider {
|
||||
height: 1px;
|
||||
background: var(--border-color);
|
||||
margin: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
.nav-section {
|
||||
padding: 0.5rem 1.25rem;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: var(--text-muted);
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Collapsed state */
|
||||
.sidebar.collapsed .logo-text,
|
||||
.sidebar.collapsed .nav-link span,
|
||||
.sidebar.collapsed .nav-item span,
|
||||
.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 .nav-divider {
|
||||
.sidebar.collapsed .sidebar-footer {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar.collapsed .sidebar-logo {
|
||||
.sidebar.collapsed .sidebar-brand {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.sidebar.collapsed .nav-link {
|
||||
.sidebar.collapsed .nav-item {
|
||||
justify-content: center;
|
||||
padding: 0.65rem;
|
||||
}
|
||||
@@ -348,7 +325,7 @@ code, pre {
|
||||
Main Content
|
||||
========================================================================== */
|
||||
|
||||
.main-content {
|
||||
.main-area {
|
||||
flex: 1;
|
||||
margin-left: var(--sidebar-width);
|
||||
transition: margin-left var(--transition);
|
||||
@@ -357,7 +334,7 @@ code, pre {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar.collapsed ~ .main-content {
|
||||
.sidebar.collapsed ~ .main-area {
|
||||
margin-left: var(--sidebar-collapsed);
|
||||
}
|
||||
|
||||
@@ -365,7 +342,7 @@ code, pre {
|
||||
Top Bar
|
||||
========================================================================== */
|
||||
|
||||
.top-bar {
|
||||
.topbar {
|
||||
height: var(--topbar-height);
|
||||
background: var(--bg-secondary);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
@@ -378,48 +355,25 @@ code, pre {
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.top-bar-left {
|
||||
.topbar-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.mobile-toggle {
|
||||
display: none;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-secondary);
|
||||
font-size: 1.3rem;
|
||||
cursor: pointer;
|
||||
padding: 0.5rem 0.75rem;
|
||||
border-radius: var(--radius-sm);
|
||||
transition: color var(--transition);
|
||||
}
|
||||
|
||||
.mobile-toggle:hover {
|
||||
color: var(--text-primary);
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.mobile-overlay {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
font-size: 0.92rem;
|
||||
color: var(--text-secondary);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.top-bar-right {
|
||||
.topbar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.top-bar-actions {
|
||||
.topbar-user {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.88rem;
|
||||
color: var(--text-secondary);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
@@ -442,11 +396,15 @@ code, pre {
|
||||
Content Area
|
||||
========================================================================== */
|
||||
|
||||
.content-area {
|
||||
.page-content {
|
||||
flex: 1;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Page Header
|
||||
========================================================================== */
|
||||
@@ -2340,20 +2298,22 @@ textarea.form-input {
|
||||
width: 280px;
|
||||
}
|
||||
|
||||
.sidebar.mobile-open.collapsed .logo-text,
|
||||
.sidebar.mobile-open.collapsed .nav-link span,
|
||||
.sidebar.mobile-open.collapsed .nav-item span,
|
||||
.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 .nav-divider {
|
||||
.sidebar.mobile-open.collapsed .sidebar-footer {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.sidebar.mobile-open.collapsed .sidebar-logo {
|
||||
.sidebar.mobile-open.collapsed .sidebar-brand {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.sidebar.mobile-open.collapsed .nav-link {
|
||||
.sidebar.mobile-open.collapsed .nav-item {
|
||||
justify-content: flex-start;
|
||||
padding: 0.65rem 1.25rem;
|
||||
}
|
||||
@@ -2362,31 +2322,15 @@ textarea.form-input {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
.main-area {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
.mobile-toggle {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mobile-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0,0,0,0.5);
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
.mobile-overlay.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.content-area {
|
||||
.page-content {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.top-bar {
|
||||
.topbar {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
@@ -2414,7 +2358,7 @@ textarea.form-input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.nav-submenu {
|
||||
.nav-sublist {
|
||||
background: rgba(0,0,0,0.1);
|
||||
}
|
||||
}
|
||||
@@ -2481,11 +2425,11 @@ textarea.form-input {
|
||||
========================================================================== */
|
||||
|
||||
@media print {
|
||||
.sidebar, .top-bar {
|
||||
.sidebar, .topbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
.main-area {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
@@ -2780,7 +2724,7 @@ textarea.form-input {
|
||||
}
|
||||
|
||||
/* Sidebar accordion */
|
||||
.nav-item-accordion .nav-link {
|
||||
.nav-item-accordion .nav-item-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
@@ -2797,20 +2741,13 @@ textarea.form-input {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.nav-submenu {
|
||||
.nav-sublist {
|
||||
max-height: 0;
|
||||
overflow: hidden;
|
||||
transition: max-height 0.25s ease;
|
||||
background: rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.nav-submenu-loading {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.78rem;
|
||||
color: var(--text-muted);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.nav-sub-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -3330,7 +3267,7 @@ input[type="range"]::-moz-range-track {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.notif-dropdown-header {
|
||||
.notif-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
@@ -3338,11 +3275,9 @@ input[type="range"]::-moz-range-track {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.notif-dropdown-body {
|
||||
.notif-list {
|
||||
max-height: 360px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.notif-dropdown-footer {
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,14 +12,6 @@
|
||||
|
||||
const sidebar = document.getElementById('sidebar');
|
||||
const sidebarToggle = document.getElementById('sidebarToggle');
|
||||
const mobileToggle = document.getElementById('mobileToggle');
|
||||
const mobileOverlay = document.getElementById('mobileOverlay');
|
||||
|
||||
function closeMobileSidebar() {
|
||||
if (!sidebar) return;
|
||||
sidebar.classList.remove('mobile-open');
|
||||
if (mobileOverlay) mobileOverlay.classList.remove('active');
|
||||
}
|
||||
|
||||
if (sidebarToggle && sidebar) {
|
||||
sidebarToggle.addEventListener('click', function () {
|
||||
@@ -34,35 +26,6 @@
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (mobileToggle && sidebar) {
|
||||
mobileToggle.addEventListener('click', function () {
|
||||
const opening = !sidebar.classList.contains('mobile-open');
|
||||
if (opening) {
|
||||
sidebar.classList.remove('collapsed');
|
||||
sidebar.classList.add('mobile-open');
|
||||
if (mobileOverlay) mobileOverlay.classList.add('active');
|
||||
} else {
|
||||
closeMobileSidebar();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (sidebar) {
|
||||
document.addEventListener('click', function (e) {
|
||||
if (window.innerWidth <= 768 &&
|
||||
sidebar.classList.contains('mobile-open') &&
|
||||
!sidebar.contains(e.target) &&
|
||||
e.target !== mobileToggle &&
|
||||
e.target !== mobileOverlay) {
|
||||
closeMobileSidebar();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (mobileOverlay) {
|
||||
mobileOverlay.addEventListener('click', closeMobileSidebar);
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
Theme Toggle
|
||||
========================================================================== */
|
||||
|
||||
@@ -31,6 +31,7 @@ $router->get('/login', [AuthController::class, 'loginForm']);
|
||||
$router->post('/login', [AuthController::class, 'login'], [CSRFMiddleware::class, RateLimitMiddleware::class]);
|
||||
$router->get('/register', [AuthController::class, 'registerForm']);
|
||||
$router->post('/register', [AuthController::class, 'register'], [CSRFMiddleware::class]);
|
||||
$router->get('/logout', [AuthController::class, 'logout']);
|
||||
$router->post('/logout', [AuthController::class, 'logout'], [CSRFMiddleware::class]);
|
||||
|
||||
$router->get('/profile', [AuthController::class, 'profile'], [AuthMiddleware::class]);
|
||||
@@ -72,6 +73,11 @@ $router->get('/servers/:id/metrics', [ServerController::class, 'metrics'], [Auth
|
||||
$router->post('/servers/:id/thresholds', [ServerController::class, 'saveThresholds'], [AuthMiddleware::class, CSRFMiddleware::class]);
|
||||
$router->get('/sidebar/servers', [ServerController::class, 'sidebarServers'], [AuthMiddleware::class]);
|
||||
|
||||
$router->get('/account', function () {
|
||||
$view = \ServerManager\Core\App::getInstance()->getView();
|
||||
$view->redirect('/profile');
|
||||
});
|
||||
|
||||
$router->get('/teams', [TeamController::class, 'index'], [AuthMiddleware::class, RoleMiddleware::class]);
|
||||
$router->get('/teams/search-users', [TeamController::class, 'searchUsers'], [AuthMiddleware::class, RoleMiddleware::class]);
|
||||
$router->get('/teams/create', [TeamController::class, 'create'], [AuthMiddleware::class, RoleMiddleware::class]);
|
||||
@@ -90,6 +96,16 @@ $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/activity', function () {
|
||||
$view = \ServerManager\Core\App::getInstance()->getView();
|
||||
$view->redirect('/admin/audit');
|
||||
}, [AuthMiddleware::class, RoleMiddleware::class]);
|
||||
|
||||
$router->group(['prefix' => 'admin', 'middleware' => [AuthMiddleware::class, RoleMiddleware::class]], function (ServerManager\Core\Router $router) {
|
||||
$router->get('/users', [AdminController::class, 'users']);
|
||||
$router->post('/users/create', [AdminController::class, 'createUser'], [CSRFMiddleware::class]);
|
||||
|
||||
@@ -400,6 +400,7 @@ class ApiController
|
||||
'error' => 'Command execution failed.',
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function users(): void
|
||||
{
|
||||
|
||||
@@ -67,6 +67,7 @@ class AuthController
|
||||
Session::set('username', $user['username']);
|
||||
Session::set('user_role', $user['role']);
|
||||
Session::set('user_email', $user['email']);
|
||||
Session::set('api_token', $user['api_token']);
|
||||
Session::set('login_time', time());
|
||||
|
||||
$lifetime = App::getConfig()['session']['lifetime'] ?? 1440;
|
||||
|
||||
@@ -47,6 +47,14 @@ if (!function_exists('asset')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('asset_version')) {
|
||||
function asset_version(): string
|
||||
{
|
||||
$config = \ServerManager\Core\App::getConfig();
|
||||
return $config['app']['asset_version'] ?? '0.0.0';
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('url')) {
|
||||
function url(string $path = ''): string
|
||||
{
|
||||
@@ -90,11 +98,23 @@ if (!function_exists('auth')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$apiToken = \ServerManager\Core\Session::get('api_token');
|
||||
|
||||
if (empty($apiToken)) {
|
||||
$userModel = new \ServerManager\Models\User();
|
||||
$user = $userModel->findById((int) \ServerManager\Core\Session::get('user_id'));
|
||||
if ($user && !empty($user['api_token'])) {
|
||||
$apiToken = $user['api_token'];
|
||||
\ServerManager\Core\Session::set('api_token', $apiToken);
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => \ServerManager\Core\Session::get('user_id'),
|
||||
'username' => \ServerManager\Core\Session::get('username'),
|
||||
'role' => \ServerManager\Core\Session::get('user_role'),
|
||||
'email' => \ServerManager\Core\Session::get('user_email'),
|
||||
'api_token' => $apiToken,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,4 +187,4 @@ function selected(string $value, string $compare): string {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/assets/js/audit-log.js"></script>
|
||||
<script src="/assets/js/audit-log.js?v=<?= asset_version() ?>"></script>
|
||||
|
||||
@@ -139,4 +139,4 @@ $data = $notifications['data'] ?? [];
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/assets/js/admin-notifications.js"></script>
|
||||
<script src="/assets/js/admin-notifications.js?v=<?= asset_version() ?>"></script>
|
||||
|
||||
@@ -102,4 +102,4 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/assets/js/admin-security.js"></script>
|
||||
<script src="/assets/js/admin-security.js?v=<?= asset_version() ?>"></script>
|
||||
|
||||
@@ -121,4 +121,4 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/assets/js/admin-users.js"></script>
|
||||
<script src="/assets/js/admin-users.js?v=<?= asset_version() ?>"></script>
|
||||
|
||||
@@ -165,5 +165,5 @@ $aggregatedMetrics = $aggregatedMetrics ?? [];
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.7/dist/chart.umd.min.js"></script>
|
||||
<script>const aggregatedData = <?= json_encode($aggregatedMetrics, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES) ?>;</script>
|
||||
<script src="/assets/js/dashboard-chart.js"></script>
|
||||
<script src="/assets/js/dashboard-chart.js?v=<?= asset_version() ?>"></script>
|
||||
</div>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title><?= htmlspecialchars($title ?? 'ServerManager') ?></title>
|
||||
<link rel="icon" type="image/svg+xml" href="/assets/img/server.svg">
|
||||
<link rel="stylesheet" href="/assets/css/style.css">
|
||||
<link rel="stylesheet" href="/assets/css/style.css?v=<?= asset_version() ?>">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
</head>
|
||||
<body class="auth-page">
|
||||
@@ -13,6 +13,6 @@
|
||||
<?= $content ?? '' ?>
|
||||
</div>
|
||||
<div id="toastContainer" class="toast-container"></div>
|
||||
<script src="/assets/js/app.js"></script>
|
||||
<script src="/assets/js/app.js?v=<?= asset_version() ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
<?php
|
||||
$user = $user ?? null;
|
||||
$notifications = $notifications ?? [];
|
||||
$unreadCount = $unreadCount ?? 0;
|
||||
$teamId = $teamId ?? null;
|
||||
@@ -7,6 +6,10 @@ $title = $title ?? 'ServerManager';
|
||||
$content = $content ?? '';
|
||||
|
||||
$currentRoute = $_SERVER['REQUEST_URI'] ?? '/';
|
||||
|
||||
if (!isset($user) || $user === null) {
|
||||
$user = auth();
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
@@ -16,7 +19,7 @@ $currentRoute = $_SERVER['REQUEST_URI'] ?? '/';
|
||||
<title><?= htmlspecialchars($title) ?> — ServerManager</title>
|
||||
<meta name="api-token" content="<?= htmlspecialchars($user['api_token'] ?? '') ?>">
|
||||
<meta name="csrf-token" content="<?= $this->csrfToken() ?>">
|
||||
<link rel="stylesheet" href="/assets/css/style.css">
|
||||
<link rel="stylesheet" href="/assets/css/style.css?v=<?= asset_version() ?>">
|
||||
<link rel="icon" type="image/svg+xml" href="/assets/img/server.svg">
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
@@ -41,6 +44,12 @@ $currentRoute = $_SERVER['REQUEST_URI'] ?? '/';
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
|
||||
<!-- Notifications -->
|
||||
<a href="/notifications" class="nav-item <?= str_starts_with($currentRoute, '/notifications') ? 'active' : '' ?>">
|
||||
<i class="fas fa-bell"></i>
|
||||
<span>Notifications</span>
|
||||
</a>
|
||||
|
||||
<!-- Servers -->
|
||||
<div class="nav-item-accordion <?= str_starts_with($currentRoute, '/servers') || str_starts_with($currentRoute, '/terminal') ? 'expanded' : '' ?>">
|
||||
<a href="#" class="nav-item nav-item-toggle <?= str_starts_with($currentRoute, '/servers') ? 'active' : '' ?>" onclick="toggleServerList(event)">
|
||||
@@ -53,41 +62,80 @@ $currentRoute = $_SERVER['REQUEST_URI'] ?? '/';
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Teams -->
|
||||
<!-- Administration -->
|
||||
<?php if (is_super_admin()): ?>
|
||||
<hr class="nav-divider">
|
||||
<div class="nav-section">Administration</div>
|
||||
|
||||
<a href="/teams" class="nav-item <?= str_starts_with($currentRoute, '/teams') ? 'active' : '' ?>">
|
||||
<i class="fas fa-users"></i>
|
||||
<i class="fas fa-users-cog"></i>
|
||||
<span>Teams</span>
|
||||
</a>
|
||||
|
||||
<!-- Activity Log (admin+) -->
|
||||
<?php if (is_admin() || is_super_admin()): ?>
|
||||
<a href="/admin/activity" class="nav-item <?= str_starts_with($currentRoute, '/admin/activity') ? 'active' : '' ?>">
|
||||
<a href="/admin/users" class="nav-item <?= str_starts_with($currentRoute, '/admin/users') ? 'active' : '' ?>">
|
||||
<i class="fas fa-users"></i>
|
||||
<span>Users</span>
|
||||
</a>
|
||||
|
||||
<a href="/admin/audit" class="nav-item <?= str_starts_with($currentRoute, '/admin/audit') ? 'active' : '' ?>">
|
||||
<i class="fas fa-history"></i>
|
||||
<span>Activity Log</span>
|
||||
<span>Audit Logs</span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Admin panel (admin+) -->
|
||||
<?php if (is_admin() || is_super_admin()): ?>
|
||||
<a href="/admin" class="nav-item <?= str_starts_with($currentRoute, '/admin') ? 'active' : '' ?>">
|
||||
<i class="fas fa-cog"></i>
|
||||
<span>Administration</span>
|
||||
<a href="/admin/security" class="nav-item <?= str_starts_with($currentRoute, '/admin/security') ? 'active' : '' ?>">
|
||||
<i class="fas fa-shield-alt"></i>
|
||||
<span>Security</span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<a href="/admin/app-version" class="nav-item <?= str_starts_with($currentRoute, '/admin/app-version') ? 'active' : '' ?>">
|
||||
<i class="fas fa-mobile-alt"></i>
|
||||
<span>App Version</span>
|
||||
</a>
|
||||
|
||||
<a href="/admin/notifications" class="nav-item <?= str_starts_with($currentRoute, '/admin/notifications') ? 'active' : '' ?>">
|
||||
<i class="fas fa-bell"></i>
|
||||
<span>Notifications</span>
|
||||
</a>
|
||||
|
||||
<a href="/admin/policies" class="nav-item <?= str_starts_with($currentRoute, '/admin/policies') ? 'active' : '' ?>">
|
||||
<i class="fas fa-file-contract"></i>
|
||||
<span>Policies</span>
|
||||
</a>
|
||||
<?php elseif (is_admin()): ?>
|
||||
<hr class="nav-divider">
|
||||
<div class="nav-section">Administration</div>
|
||||
|
||||
<!-- Account -->
|
||||
<a href="/account" class="nav-item <?= str_starts_with($currentRoute, '/account') ? 'active' : '' ?>">
|
||||
<i class="fas fa-user"></i>
|
||||
<span>My Account</span>
|
||||
<a href="/teams" class="nav-item <?= str_starts_with($currentRoute, '/teams') ? 'active' : '' ?>">
|
||||
<i class="fas fa-users-cog"></i>
|
||||
<span>Teams</span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<a href="/logout" class="nav-item nav-item-danger">
|
||||
<i class="fas fa-sign-out-alt"></i>
|
||||
<span>Log out</span>
|
||||
<!-- Mobile -->
|
||||
<hr class="nav-divider">
|
||||
<div class="nav-section">Mobile</div>
|
||||
<a href="/sysadmin.apk" class="nav-item" download>
|
||||
<i class="fas fa-download"></i>
|
||||
<span>Download APK</span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="sidebar-footer">
|
||||
<div class="user-info">
|
||||
<div class="user-avatar"><?= strtoupper(substr($user['username'] ?? '?', 0, 2)) ?></div>
|
||||
<div class="user-details">
|
||||
<span class="user-name"><?= htmlspecialchars($user['username'] ?? 'Guest') ?></span>
|
||||
<span class="user-role"><?= htmlspecialchars(ucfirst(str_replace('_', ' ', $user['role'] ?? 'guest'))) ?></span>
|
||||
</div>
|
||||
<div class="user-menu">
|
||||
<a href="/profile" title="Profile"><i class="fas fa-user-cog"></i></a>
|
||||
<form method="POST" action="/logout" class="logout-form" style="display:inline">
|
||||
<input type="hidden" name="_csrf_token" value="<?= $this->csrfToken() ?>">
|
||||
<button type="submit" title="Logout" style="background:none;border:none;color:inherit;cursor:pointer;padding:0;font:inherit;line-height:1"><i class="fas fa-sign-out-alt"></i></button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main Content -->
|
||||
@@ -153,12 +201,12 @@ $currentRoute = $_SERVER['REQUEST_URI'] ?? '/';
|
||||
|
||||
<div id="toastContainer" class="toast-container"></div>
|
||||
|
||||
<script src="/assets/js/app.js"></script>
|
||||
<script src="/assets/js/app.js?v=<?= asset_version() ?>"></script>
|
||||
<?php if (isset($scripts)): ?>
|
||||
<?php foreach ($scripts as $script): ?>
|
||||
<script src="<?= htmlspecialchars($script) ?>"></script>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
<script src="/assets/js/main.js"></script>
|
||||
<script src="/assets/js/main.js?v=<?= asset_version() ?>"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -67,4 +67,4 @@ $data = $notifications ?? [];
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<script src="/assets/js/notifications.js"></script>
|
||||
<script src="/assets/js/notifications.js?v=<?= asset_version() ?>"></script>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?php ?><link rel="stylesheet" href="/assets/css/landing.css">
|
||||
<?php ?><link rel="stylesheet" href="/assets/css/landing.css?v=<?= asset_version() ?>">
|
||||
|
||||
<div class="landing-page">
|
||||
<section class="landing-hero">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?php ?><link rel="stylesheet" href="/assets/css/legal.css">
|
||||
<?php ?><link rel="stylesheet" href="/assets/css/legal.css?v=<?= asset_version() ?>">
|
||||
<div class="legal-page">
|
||||
<div class="legal-container">
|
||||
<div class="legal-card" style="width:85%;margin:3rem auto;padding:3rem 3.5rem;background:var(--card-bg);border-radius:12px;box-shadow:0 2px 16px rgba(0,0,0,0.18)">
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?php ?><link rel="stylesheet" href="/assets/css/legal.css">
|
||||
<?php ?><link rel="stylesheet" href="/assets/css/legal.css?v=<?= asset_version() ?>">
|
||||
<div class="legal-page">
|
||||
<div class="legal-container">
|
||||
<div class="legal-card" style="width:85%;margin:3rem auto;padding:3rem 3.5rem;background:var(--card-bg);border-radius:12px;box-shadow:0 2px 16px rgba(0,0,0,0.18)">
|
||||
|
||||
@@ -171,4 +171,4 @@ document.querySelectorAll('.auth-method-tabs .tab').forEach(tab => {
|
||||
|
||||
let permIndex = <?= !empty($savedPerms) && is_array($savedPerms) ? count($savedPerms) : 0 ?>;
|
||||
</script>
|
||||
<script src="/assets/js/server-permissions.js"></script>
|
||||
<script src="/assets/js/server-permissions.js?v=<?= asset_version() ?>"></script>
|
||||
|
||||
@@ -95,5 +95,5 @@ $status = $db['status'] ?? 'inactive';
|
||||
const serverId = <?= json_encode($server['id']) ?>;
|
||||
const databases = <?= json_encode(array_column($databases, 'name')) ?>;
|
||||
</script>
|
||||
<script src="/assets/js/database-manager.js"></script>
|
||||
<script src="/assets/js/database-manager.js?v=<?= asset_version() ?>"></script>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -156,4 +156,4 @@ $hasKey = !empty($server['ssh_key']);
|
||||
<script>
|
||||
let permIndex = <?= count($permissions) ?>;
|
||||
</script>
|
||||
<script src="/assets/js/server-permissions.js"></script>
|
||||
<script src="/assets/js/server-permissions.js?v=<?= asset_version() ?>"></script>
|
||||
|
||||
@@ -141,4 +141,4 @@ $groups = $groups ?? [];
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/assets/js/server-list.js"></script>
|
||||
<script src="/assets/js/server-list.js?v=<?= asset_version() ?>"></script>
|
||||
|
||||
@@ -323,5 +323,5 @@ $configValid = $nginx['config_valid'] ?? false;
|
||||
<script>
|
||||
const serverId = <?= json_encode($server['id']) ?>;
|
||||
</script>
|
||||
<script src="/assets/js/nginx-manager.js"></script>
|
||||
<script src="/assets/js/nginx-manager.js?v=<?= asset_version() ?>"></script>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -103,4 +103,4 @@ $processList = $processList ?? [];
|
||||
<script>
|
||||
const serverId = <?= $server['id'] ?>;
|
||||
</script>
|
||||
<script src="/assets/js/server-processes.js"></script>
|
||||
<script src="/assets/js/server-processes.js?v=<?= asset_version() ?>"></script>
|
||||
|
||||
@@ -51,5 +51,5 @@ const allServices = <?= json_encode($services, JSON_HEX_TAG | JSON_UNESCAPED_SLA
|
||||
const perPage = <?= $perPage ?>;
|
||||
const serverId = <?= $server['id'] ?>;
|
||||
</script>
|
||||
<script src="/assets/js/server-services.js"></script>
|
||||
<script src="/assets/js/server-services.js?v=<?= asset_version() ?>"></script>
|
||||
|
||||
|
||||
@@ -375,4 +375,4 @@ $canManage = $server_role === 'owner' || $server_role === 'manager';
|
||||
const serverId = <?= $server['id'] ?>;
|
||||
const serverName = '<?= addslashes($server['name'] ?? '') ?>';
|
||||
</script>
|
||||
<script src="/assets/js/server-show.js"></script>
|
||||
<script src="/assets/js/server-show.js?v=<?= asset_version() ?>"></script>
|
||||
|
||||
@@ -191,4 +191,4 @@ $certs = $ssl['certificates'] ?? [];
|
||||
<script>
|
||||
const serverId = <?= $server['id'] ?>;
|
||||
</script>
|
||||
<script src="/assets/js/server-ssl.js"></script>
|
||||
<script src="/assets/js/server-ssl.js?v=<?= asset_version() ?>"></script>
|
||||
|
||||
@@ -123,4 +123,4 @@ $lastLogins = $sysusers['last_logins'] ?? '';
|
||||
<script>
|
||||
const serverId = <?= $server['id'] ?>;
|
||||
</script>
|
||||
<script src="/assets/js/system-users.js"></script>
|
||||
<script src="/assets/js/system-users.js?v=<?= asset_version() ?>"></script>
|
||||
|
||||
@@ -71,4 +71,4 @@
|
||||
<input type="hidden" name="_csrf_token" value="<?= $this->csrfToken() ?>">
|
||||
</form>
|
||||
|
||||
<script src="/assets/js/team-index.js"></script>
|
||||
<script src="/assets/js/team-index.js?v=<?= asset_version() ?>"></script>
|
||||
|
||||
@@ -150,4 +150,4 @@ const teamId = <?= $team['id'] ?>;
|
||||
const teamName = '<?= htmlspecialchars(addslashes($team['name'] ?? '')) ?>';
|
||||
const teamDescription = '<?= htmlspecialchars(addslashes($team['description'] ?? '')) ?>';
|
||||
</script>
|
||||
<script src="/assets/js/team-show.js"></script>
|
||||
<script src="/assets/js/team-show.js?v=<?= asset_version() ?>"></script>
|
||||
|
||||
@@ -150,4 +150,4 @@ $commandHistory = $commandHistory ?? [];
|
||||
<script>
|
||||
const serverId = <?= $server['id'] ?>;
|
||||
</script>
|
||||
<script src="/assets/js/terminal.js"></script>
|
||||
<script src="/assets/js/terminal.js?v=<?= asset_version() ?>"></script>
|
||||
|
||||
Reference in New Issue
Block a user