diff --git a/database/migrations/002_server_access.sql b/database/migrations/002_server_access.sql new file mode 100644 index 0000000..cb4c054 --- /dev/null +++ b/database/migrations/002_server_access.sql @@ -0,0 +1,24 @@ +-- ============================================================================ +-- ServerManager Server Access Control +-- Adds ownership + team-based permissions per server +-- ============================================================================ + +ALTER TABLE `servers` + ADD COLUMN `created_by` INT UNSIGNED DEFAULT NULL AFTER `group_name`, + ADD KEY `idx_created_by` (`created_by`); + +CREATE TABLE IF NOT EXISTS `server_user` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `server_id` INT UNSIGNED NOT NULL, + `user_id` INT UNSIGNED NOT NULL, + `role` ENUM('viewer', 'manager') NOT NULL DEFAULT 'viewer', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_server_user` (`server_id`, `user_id`), + KEY `idx_server_id` (`server_id`), + KEY `idx_user_id` (`user_id`), + CONSTRAINT `fk_server_user_server` FOREIGN KEY (`server_id`) + REFERENCES `servers` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_server_user_user` FOREIGN KEY (`user_id`) + REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/database/migrations/003_teams.sql b/database/migrations/003_teams.sql new file mode 100644 index 0000000..db90e81 --- /dev/null +++ b/database/migrations/003_teams.sql @@ -0,0 +1,47 @@ +-- ============================================================================ +-- ServerManager Teams +-- Team-based access control: teams group users and servers together +-- ============================================================================ + +CREATE TABLE IF NOT EXISTS `teams` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `name` VARCHAR(100) NOT NULL, + `description` TEXT DEFAULT NULL, + `created_by` INT UNSIGNED NOT NULL, + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_name` (`name`), + KEY `idx_created_by` (`created_by`), + CONSTRAINT `fk_teams_created_by` FOREIGN KEY (`created_by`) + REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `team_user` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `team_id` INT UNSIGNED NOT NULL, + `user_id` INT UNSIGNED NOT NULL, + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_team_user` (`team_id`, `user_id`), + KEY `idx_user_id` (`user_id`), + CONSTRAINT `fk_team_user_team` FOREIGN KEY (`team_id`) + REFERENCES `teams` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_team_user_user` FOREIGN KEY (`user_id`) + REFERENCES `users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; + +CREATE TABLE IF NOT EXISTS `team_server` ( + `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, + `team_id` INT UNSIGNED NOT NULL, + `server_id` INT UNSIGNED NOT NULL, + `role` ENUM('viewer', 'manager') NOT NULL DEFAULT 'viewer', + `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_team_server` (`team_id`, `server_id`), + KEY `idx_server_id` (`server_id`), + CONSTRAINT `fk_team_server_team` FOREIGN KEY (`team_id`) + REFERENCES `teams` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `fk_team_server_server` FOREIGN KEY (`server_id`) + REFERENCES `servers` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/routes/web.php b/routes/web.php index 13892ec..5988f68 100755 --- a/routes/web.php +++ b/routes/web.php @@ -9,6 +9,7 @@ use ServerManager\Controllers\ServerController; use ServerManager\Controllers\TerminalController; use ServerManager\Controllers\AdminController; use ServerManager\Controllers\ApiController; +use ServerManager\Controllers\TeamController; use ServerManager\Middleware\AuthMiddleware; use ServerManager\Middleware\CSRFMiddleware; use ServerManager\Middleware\RateLimitMiddleware; @@ -31,8 +32,8 @@ $router->get('/profile', [AuthController::class, 'profile'], [AuthMiddleware::cl $router->post('/profile', [AuthController::class, 'updateProfile'], [AuthMiddleware::class, CSRFMiddleware::class]); $router->get('/servers', [ServerController::class, 'index'], [AuthMiddleware::class]); -$router->get('/servers/create', [ServerController::class, 'create'], [AuthMiddleware::class]); -$router->post('/servers/create', [ServerController::class, 'store'], [AuthMiddleware::class, CSRFMiddleware::class]); +$router->get('/servers/create', [ServerController::class, 'create'], [AuthMiddleware::class, RoleMiddleware::class]); +$router->post('/servers/create', [ServerController::class, 'store'], [AuthMiddleware::class, CSRFMiddleware::class, RoleMiddleware::class]); $router->get('/servers/:id', [ServerController::class, 'show'], [AuthMiddleware::class]); $router->get('/servers/:id/edit', [ServerController::class, 'edit'], [AuthMiddleware::class]); $router->post('/servers/:id/edit', [ServerController::class, 'update'], [AuthMiddleware::class, CSRFMiddleware::class]); @@ -65,12 +66,24 @@ $router->get('/servers/:id/logs', [ServerController::class, 'logs'], [AuthMiddle $router->get('/servers/:id/metrics', [ServerController::class, 'metrics'], [AuthMiddleware::class]); $router->get('/sidebar/servers', [ServerController::class, 'sidebarServers'], [AuthMiddleware::class]); +$router->get('/teams', [TeamController::class, 'index'], [AuthMiddleware::class, RoleMiddleware::class]); +$router->get('/teams/create', [TeamController::class, 'create'], [AuthMiddleware::class, RoleMiddleware::class]); +$router->post('/teams/create', [TeamController::class, 'store'], [AuthMiddleware::class, CSRFMiddleware::class, RoleMiddleware::class]); +$router->get('/teams/:id', [TeamController::class, 'show'], [AuthMiddleware::class, RoleMiddleware::class]); +$router->post('/teams/:id/update', [TeamController::class, 'update'], [AuthMiddleware::class, CSRFMiddleware::class]); +$router->post('/teams/:id/delete', [TeamController::class, 'delete'], [AuthMiddleware::class, CSRFMiddleware::class]); +$router->post('/teams/:id/add-user', [TeamController::class, 'addUser'], [AuthMiddleware::class, CSRFMiddleware::class]); +$router->post('/teams/:id/remove-user', [TeamController::class, 'removeUser'], [AuthMiddleware::class, CSRFMiddleware::class]); +$router->post('/teams/:id/add-server', [TeamController::class, 'addServer'], [AuthMiddleware::class, CSRFMiddleware::class]); +$router->post('/teams/:id/remove-server', [TeamController::class, 'removeServer'], [AuthMiddleware::class, CSRFMiddleware::class]); +$router->post('/teams/:id/update-server-role', [TeamController::class, 'updateServerRole'], [AuthMiddleware::class, CSRFMiddleware::class]); + $router->get('/terminal/:id', [TerminalController::class, 'index'], [AuthMiddleware::class]); $router->post('/terminal/:id/execute', [TerminalController::class, 'execute'], [AuthMiddleware::class, CSRFMiddleware::class]); $router->get('/terminal/:id/history', [TerminalController::class, 'history'], [AuthMiddleware::class]); $router->post('/terminal/:id/clear-history', [TerminalController::class, 'clearHistory'], [AuthMiddleware::class, CSRFMiddleware::class]); -$router->group(['prefix' => 'admin', 'middleware' => [AuthMiddleware::class]], function (ServerManager\Core\Router $router) { +$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]); $router->post('/users/:id/update', [AdminController::class, 'updateUser'], [CSRFMiddleware::class]); diff --git a/src/Controllers/AdminController.php b/src/Controllers/AdminController.php index 084dfdb..9f276e5 100755 --- a/src/Controllers/AdminController.php +++ b/src/Controllers/AdminController.php @@ -23,8 +23,18 @@ class AdminController $this->auditService = new AuditService(); } + private function requireSuperAdmin(): void + { + if (Session::get('user_role') !== 'super_admin') { + Session::setFlash('error', 'You do not have permission to access this page.'); + $this->view->redirect('/dashboard'); + } + } + public function users(): void { + $this->requireSuperAdmin(); + $page = (int) ($_GET['page'] ?? 1); $users = $this->userModel->getAll($page, 20); @@ -36,6 +46,10 @@ class AdminController public function createUser(): void { + if (Session::get('user_role') !== 'super_admin') { + $this->view->json(['success' => false, 'message' => 'Forbidden'], 403); + } + $validator = new Validator(); $rules = [ 'username' => 'required|min:3|max:50|alphaNum', @@ -62,7 +76,7 @@ class AdminController 'username' => $_POST['username'], 'email' => $_POST['email'], 'password' => $_POST['password'], - 'role' => $_POST['role'], + 'role' => $_POST['role'] ?? 'admin', 'is_active' => 1, ]); @@ -77,6 +91,10 @@ class AdminController public function updateUser(int $id): void { + if (Session::get('user_role') !== 'super_admin') { + $this->view->json(['success' => false, 'message' => 'Forbidden'], 403); + } + $user = $this->userModel->findById($id); if (!$user) { @@ -117,6 +135,10 @@ class AdminController public function deleteUser(int $id): void { + if (Session::get('user_role') !== 'super_admin') { + $this->view->json(['success' => false, 'message' => 'Forbidden'], 403); + } + $user = $this->userModel->findById($id); if (!$user) { @@ -139,6 +161,11 @@ class AdminController public function auditLogs(): void { + if (Session::get('user_role') !== 'super_admin') { + Session::setFlash('error', 'You do not have permission to access this page.'); + $this->view->redirect('/dashboard'); + } + $page = (int) ($_GET['page'] ?? 1); $action = $_GET['action'] ?? ''; $userId = $_GET['user_id'] ?? ''; @@ -159,6 +186,11 @@ class AdminController public function securitySettings(): void { + if (Session::get('user_role') !== 'super_admin') { + Session::setFlash('error', 'You do not have permission to access this page.'); + $this->view->redirect('/dashboard'); + } + $this->view->display('admin.security', [ 'title' => 'Security Settings - ServerManager', ]); diff --git a/src/Controllers/ApiController.php b/src/Controllers/ApiController.php index 80fa9de..a922b12 100755 --- a/src/Controllers/ApiController.php +++ b/src/Controllers/ApiController.php @@ -76,6 +76,9 @@ class ApiController $filters = []; if ($search) $filters['search'] = $search; + $accessibleIds = $serverModel->getAccessibleServerIds((int) $user['id']); + $filters['accessible_ids'] = !empty($accessibleIds) ? $accessibleIds : [-1]; + $servers = $serverModel->getAll($page, $perPage, $filters); $this->view->json([ @@ -101,6 +104,10 @@ class ApiController $this->view->json(['error' => 'Server not found'], 404); } + if (!$serverModel->canView($id, (int) $user['id'])) { + $this->view->json(['error' => 'Server not found'], 404); + } + $monitoringService = new MonitoringService(); $metrics = $monitoringService->getLatestMetrics($id); @@ -154,6 +161,10 @@ class ApiController $this->view->json(['error' => 'Server not found'], 404); } + if (!$serverModel->canManage($id, (int) $user['id'])) { + $this->view->json(['error' => 'Forbidden'], 403); + } + $serverModel->update($id, $input); $this->view->json([ @@ -173,6 +184,10 @@ class ApiController $this->view->json(['error' => 'Server not found'], 404); } + if (!$serverModel->canManage($id, (int) $user['id'])) { + $this->view->json(['error' => 'Forbidden'], 403); + } + $serverModel->delete($id); $this->view->json([ @@ -185,6 +200,11 @@ class ApiController { $user = $this->authenticateRequest(); + $serverModel = new Server(); + if (!$serverModel->canView($id, (int) $user['id'])) { + $this->view->json(['error' => 'Server not found'], 404); + } + $monitoringService = new MonitoringService(); $hours = (int) ($_GET['hours'] ?? 24); @@ -202,6 +222,11 @@ class ApiController { $user = $this->authenticateRequest(); + $serverModel = new Server(); + if (!$serverModel->canView($id, (int) $user['id'])) { + $this->view->json(['error' => 'Server not found'], 404); + } + $input = json_decode(file_get_contents('php://input'), true) ?? $_POST; $command = $input['command'] ?? ''; diff --git a/src/Controllers/AuthController.php b/src/Controllers/AuthController.php index 85939f4..9239e2b 100755 --- a/src/Controllers/AuthController.php +++ b/src/Controllers/AuthController.php @@ -126,13 +126,13 @@ class AuthController 'username' => $username, 'email' => $email, 'password' => $_POST['password'], - 'role' => 'operator', + 'role' => 'admin', 'is_active' => 1, ]); $this->auditService->log('user_registered', 'user', $id, [ 'username' => $username, - 'role' => 'operator', + 'role' => 'admin', ]); Session::setFlash('success', 'Account created. You can now log in.'); diff --git a/src/Controllers/DashboardController.php b/src/Controllers/DashboardController.php index e096c15..a1c852e 100755 --- a/src/Controllers/DashboardController.php +++ b/src/Controllers/DashboardController.php @@ -5,6 +5,7 @@ declare(strict_types=1); namespace ServerManager\Controllers; use ServerManager\Core\App; +use ServerManager\Core\Session; use ServerManager\Models\Server; use ServerManager\Services\MonitoringService; use ServerManager\Services\AuditService; @@ -24,11 +25,22 @@ class DashboardController public function index(): void { - $stats = $this->monitoringService->getDashboardStats(); - $recentActivity = $this->auditService->getRecentActivity(10); - + $role = Session::get('user_role'); $serverModel = new Server(); - $servers = $serverModel->findAll(true); + $userId = (int) Session::get('user_id'); + + if ($role === 'super_admin') { + $stats = $this->monitoringService->getDashboardStats(); + $servers = $serverModel->findAll(true); + } else { + $accessibleIds = $serverModel->getAccessibleServerIds($userId); + $stats = $this->monitoringService->getDashboardStats($accessibleIds); + $servers = !empty($accessibleIds) ? array_values(array_filter($serverModel->findAll(true), function ($s) use ($accessibleIds) { + return in_array((int) $s['id'], $accessibleIds, true); + })) : []; + } + + $recentActivity = $this->auditService->getRecentActivity(10); $this->view->display('dashboard.index', [ 'title' => 'Dashboard - ServerManager', @@ -40,7 +52,15 @@ class DashboardController public function stats(): void { - $stats = $this->monitoringService->getDashboardStats(); + $role = Session::get('user_role'); + + if ($role === 'super_admin') { + $stats = $this->monitoringService->getDashboardStats(); + } else { + $serverModel = new Server(); + $accessibleIds = $serverModel->getAccessibleServerIds((int) Session::get('user_id')); + $stats = $this->monitoringService->getDashboardStats($accessibleIds); + } $this->view->json([ 'success' => true, diff --git a/src/Controllers/ServerController.php b/src/Controllers/ServerController.php index 3f384a7..74e02cf 100755 --- a/src/Controllers/ServerController.php +++ b/src/Controllers/ServerController.php @@ -8,6 +8,7 @@ use ServerManager\Core\App; use ServerManager\Core\Session; use ServerManager\Core\Validator; use ServerManager\Models\Server; +use ServerManager\Models\User; use ServerManager\Services\SSHService; use ServerManager\Services\MonitoringService; use ServerManager\Services\AuditService; @@ -26,18 +27,45 @@ class ServerController $this->auditService = new AuditService(); } + private function requireServerAccess(int $serverId, string $minRole = 'viewer'): ?array + { + $userId = (int) Session::get('user_id'); + $server = $this->serverModel->findById($serverId); + + if (!$server) { + $this->view->error(404); + } + + $role = $this->serverModel->getUserRole($serverId, $userId); + + if ($role === null) { + $this->view->error(404); + } + + if ($minRole === 'manager' && $role !== 'owner' && $role !== 'manager') { + Session::setFlash('error', 'You do not have permission to perform this action.'); + $this->view->redirect("/servers/{$serverId}"); + } + + return $server; + } + public function index(): void { $page = (int) ($_GET['page'] ?? 1); $search = $_GET['search'] ?? ''; $status = $_GET['status'] ?? ''; $group = $_GET['group'] ?? ''; + $userId = (int) Session::get('user_id'); $filters = []; if ($search) $filters['search'] = $search; if ($status) $filters['status'] = $status; if ($group) $filters['group_name'] = $group; + $accessibleIds = $this->serverModel->getAccessibleServerIds($userId); + $filters['accessible_ids'] = !empty($accessibleIds) ? $accessibleIds : [-1]; + $servers = $this->serverModel->getAll($page, 15, $filters); $groups = $this->serverModel->getGroups(); @@ -107,11 +135,9 @@ class ServerController public function show(int $id): void { - $server = $this->serverModel->findById($id); + $server = $this->requireServerAccess($id, 'viewer'); - if (!$server) { - $this->view->error(404); - } + $role = $this->serverModel->getUserRole($id, (int) Session::get('user_id')); $monitoringService = new MonitoringService(); $latestMetrics = $monitoringService->getLatestMetrics($id); @@ -122,16 +148,13 @@ class ServerController 'server' => $server, 'metrics' => $latestMetrics, 'historicalMetrics' => $historicalMetrics, + 'server_role' => $role, ]); } public function edit(int $id): void { - $server = $this->serverModel->findById($id); - - if (!$server) { - $this->view->error(404); - } + $server = $this->requireServerAccess($id, 'manager'); $groups = $this->serverModel->getGroups(); @@ -144,11 +167,7 @@ class ServerController public function update(int $id): void { - $server = $this->serverModel->findById($id); - - if (!$server) { - $this->view->error(404); - } + $server = $this->requireServerAccess($id, 'manager'); $validator = new Validator(); $rules = [ @@ -194,11 +213,7 @@ class ServerController public function delete(int $id): void { - $server = $this->serverModel->findById($id); - - if (!$server) { - $this->view->error(404); - } + $server = $this->requireServerAccess($id, 'manager'); $this->serverModel->delete($id); @@ -218,6 +233,12 @@ class ServerController $this->view->json(['success' => false, 'message' => 'Server not found'], 404); } + $userId = (int) Session::get('user_id'); + $role = $this->serverModel->getUserRole($id, $userId); + if ($role !== 'owner' && $role !== 'manager') { + $this->view->json(['success' => false, 'message' => 'Forbidden'], 403); + } + $newState = $this->serverModel->toggleActive($id); $this->auditService->log( @@ -236,11 +257,7 @@ class ServerController public function testConnection(int $id): void { - $server = $this->serverModel->findById($id); - - if (!$server) { - $this->view->json(['success' => false, 'message' => 'Server not found'], 404); - } + $server = $this->requireServerAccess($id, 'viewer'); $ssh = new SSHService(); $result = $ssh->testConnection($server); @@ -255,11 +272,7 @@ class ServerController public function processes(int $id): void { - $server = $this->serverModel->findById($id); - - if (!$server) { - $this->view->error(404); - } + $server = $this->requireServerAccess($id, 'viewer'); try { $ssh = new SSHService(); @@ -306,11 +319,7 @@ class ServerController public function systemInfo(int $id): void { - $server = $this->serverModel->findById($id); - - if (!$server) { - $this->view->error(404); - } + $server = $this->requireServerAccess($id, 'viewer'); try { $ssh = new SSHService(); @@ -335,11 +344,7 @@ class ServerController public function logs(int $id): void { - $server = $this->serverModel->findById($id); - - if (!$server) { - $this->view->error(404); - } + $server = $this->requireServerAccess($id, 'viewer'); $logType = $_GET['type'] ?? 'syslog'; $lines = (int) ($_GET['lines'] ?? 100); @@ -393,6 +398,12 @@ class ServerController $this->view->json(['success' => false, 'message' => 'Server not found'], 404); } + $userId = (int) Session::get('user_id'); + $role = $this->serverModel->getUserRole($id, $userId); + if ($role !== 'owner' && $role !== 'manager') { + $this->view->json(['success' => false, 'message' => 'Forbidden'], 403); + } + try { $ssh = new SSHService(); if (!$ssh->connect($server)) { @@ -425,6 +436,12 @@ class ServerController $this->view->json(['success' => false, 'message' => 'Server not found'], 404); } + $userId = (int) Session::get('user_id'); + $role = $this->serverModel->getUserRole($id, $userId); + if ($role !== 'owner' && $role !== 'manager') { + $this->view->json(['success' => false, 'message' => 'Forbidden'], 403); + } + try { $ssh = new SSHService(); if (!$ssh->connect($server)) { @@ -457,6 +474,12 @@ class ServerController $this->view->json(['success' => false, 'message' => 'Server not found'], 404); } + $userId = (int) Session::get('user_id'); + $role = $this->serverModel->getUserRole($id, $userId); + if ($role !== 'owner' && $role !== 'manager') { + $this->view->json(['success' => false, 'message' => 'Forbidden'], 403); + } + $service = $_POST['service'] ?? ''; if (empty($service)) { $this->view->json(['success' => false, 'message' => 'Service name is required']); @@ -501,6 +524,12 @@ class ServerController $this->view->json(['success' => false, 'message' => 'Server not found'], 404); } + $userId = (int) Session::get('user_id'); + $role = $this->serverModel->getUserRole($id, $userId); + if ($role !== 'owner' && $role !== 'manager') { + $this->view->json(['success' => false, 'message' => 'Forbidden'], 403); + } + $pid = (int) ($_POST['pid'] ?? 0); $signal = (int) ($_POST['signal'] ?? 15); @@ -542,11 +571,7 @@ class ServerController public function nginx(int $id): void { - $server = $this->serverModel->findById($id); - - if (!$server) { - $this->view->error(404); - } + $server = $this->requireServerAccess($id, 'viewer'); try { $ssh = new SSHService(); @@ -601,11 +626,7 @@ class ServerController public function nginxGetFile(int $id): void { - $server = $this->serverModel->findById($id); - - if (!$server) { - $this->view->json(['success' => false, 'message' => 'Server not found'], 404); - } + $server = $this->requireServerAccess($id, 'viewer'); $file = $_GET['file'] ?? ''; if (empty($file) || str_contains($file, '..')) { @@ -658,6 +679,12 @@ class ServerController $this->view->json(['success' => false, 'message' => 'Server not found'], 404); } + $userId = (int) Session::get('user_id'); + $role = $this->serverModel->getUserRole($id, $userId); + if ($role !== 'owner' && $role !== 'manager') { + $this->view->json(['success' => false, 'message' => 'Forbidden'], 403); + } + $file = $_POST['file'] ?? ''; $content = $_POST['content'] ?? ''; @@ -712,6 +739,12 @@ class ServerController $this->view->json(['success' => false, 'message' => 'Server not found'], 404); } + $userId = (int) Session::get('user_id'); + $role = $this->serverModel->getUserRole($id, $userId); + if ($role !== 'owner' && $role !== 'manager') { + $this->view->json(['success' => false, 'message' => 'Forbidden'], 403); + } + $site = $_POST['site'] ?? ''; if (empty($site) || str_contains($site, '..') || str_contains($site, '/')) { $this->view->json(['success' => false, 'message' => 'Invalid site name']); @@ -750,6 +783,12 @@ class ServerController $this->view->json(['success' => false, 'message' => 'Server not found'], 404); } + $userId = (int) Session::get('user_id'); + $role = $this->serverModel->getUserRole($id, $userId); + if ($role !== 'owner' && $role !== 'manager') { + $this->view->json(['success' => false, 'message' => 'Forbidden'], 403); + } + $siteName = trim($_POST['site_name'] ?? ''); $siteRoot = trim($_POST['site_root'] ?? ''); $serverName = trim($_POST['server_name'] ?? ''); @@ -836,6 +875,12 @@ class ServerController $this->view->json(['success' => false, 'message' => 'Server not found'], 404); } + $userId = (int) Session::get('user_id'); + $role = $this->serverModel->getUserRole($id, $userId); + if ($role !== 'owner' && $role !== 'manager') { + $this->view->json(['success' => false, 'message' => 'Forbidden'], 403); + } + $site = $_POST['site'] ?? ''; $enable = (bool) ($_POST['enable'] ?? false); @@ -879,6 +924,12 @@ class ServerController $this->view->json(['success' => false, 'message' => 'Server not found'], 404); } + $userId = (int) Session::get('user_id'); + $role = $this->serverModel->getUserRole($id, $userId); + if ($role !== 'owner' && $role !== 'manager') { + $this->view->json(['success' => false, 'message' => 'Forbidden'], 403); + } + $action = $_POST['action'] ?? ''; $allowed = ['restart', 'reload', 'test', 'start', 'stop']; if (!in_array($action, $allowed, true)) { @@ -921,11 +972,7 @@ class ServerController public function ssl(int $id): void { - $server = $this->serverModel->findById($id); - - if (!$server) { - $this->view->error(404); - } + $server = $this->requireServerAccess($id, 'viewer'); try { $ssh = new SSHService(); @@ -994,6 +1041,12 @@ class ServerController $this->view->json(['success' => false, 'message' => 'Server not found'], 404); } + $userId = (int) Session::get('user_id'); + $role = $this->serverModel->getUserRole($id, $userId); + if ($role !== 'owner' && $role !== 'manager') { + $this->view->json(['success' => false, 'message' => 'Forbidden'], 403); + } + $action = $_POST['action'] ?? ''; $allowed = ['install', 'request', 'renew', 'delete']; if (!in_array($action, $allowed, true)) { @@ -1086,11 +1139,7 @@ class ServerController public function database(int $id): void { - $server = $this->serverModel->findById($id); - - if (!$server) { - $this->view->error(404); - } + $server = $this->requireServerAccess($id, 'viewer'); try { $ssh = new SSHService(); @@ -1198,6 +1247,12 @@ class ServerController $this->view->json(['success' => false, 'message' => 'Server not found'], 404); } + $userId = (int) Session::get('user_id'); + $role = $this->serverModel->getUserRole($id, $userId); + if ($role !== 'owner' && $role !== 'manager') { + $this->view->json(['success' => false, 'message' => 'Forbidden'], 403); + } + $action = $_POST['action'] ?? ''; $allowed = ['query', 'tables', 'structure', 'browse', 'users', 'create_user', 'grant', 'revoke', 'drop_user', 'change_pass']; if (!in_array($action, $allowed, true)) { @@ -1462,9 +1517,15 @@ class ServerController public function sidebarServers(): void { + $userId = (int) Session::get('user_id'); + $accessibleIds = $this->serverModel->getAccessibleServerIds($userId); + $servers = $this->serverModel->findAll(); $result = []; foreach ($servers as $s) { + if (!in_array((int) $s['id'], $accessibleIds, true)) { + continue; + } $result[] = [ 'id' => $s['id'], 'name' => $s['name'], @@ -1477,8 +1538,7 @@ class ServerController public function services(int $id): void { - $server = $this->serverModel->findById($id); - if (!$server) { $this->view->error(404); } + $server = $this->requireServerAccess($id, 'viewer'); try { $ssh = new SSHService(); @@ -1527,6 +1587,12 @@ class ServerController $this->view->json(['success' => false, 'message' => 'Server not found'], 404); } + $userId = (int) Session::get('user_id'); + $role = $this->serverModel->getUserRole($id, $userId); + if ($role !== 'owner' && $role !== 'manager') { + $this->view->json(['success' => false, 'message' => 'Forbidden'], 403); + } + $action = $_POST['action'] ?? ''; $service = $_POST['service'] ?? ''; $allowed = ['start', 'stop', 'restart', 'reload', 'enable', 'disable']; @@ -1558,8 +1624,7 @@ class ServerController public function systemUsers(int $id): void { - $server = $this->serverModel->findById($id); - if (!$server) { $this->view->error(404); } + $server = $this->requireServerAccess($id, 'viewer'); try { $ssh = new SSHService(); @@ -1614,6 +1679,12 @@ class ServerController $this->view->json(['success' => false, 'message' => 'Server not found'], 404); } + $userId = (int) Session::get('user_id'); + $role = $this->serverModel->getUserRole($id, $userId); + if ($role !== 'owner' && $role !== 'manager') { + $this->view->json(['success' => false, 'message' => 'Forbidden'], 403); + } + $action = $_POST['action'] ?? ''; $allowed = ['add', 'delete', 'lock', 'unlock', 'addgroup']; if (!in_array($action, $allowed, true)) { @@ -1723,4 +1794,5 @@ class ServerController 'data' => $metrics, ]); } + } diff --git a/src/Controllers/TeamController.php b/src/Controllers/TeamController.php new file mode 100644 index 0000000..bf47522 --- /dev/null +++ b/src/Controllers/TeamController.php @@ -0,0 +1,292 @@ +view = App::getInstance()->getView(); + $this->teamModel = new Team(); + $this->serverModel = new Server(); + $this->auditService = new AuditService(); + } + + public function index(): void + { + $userId = (int) Session::get('user_id'); + $teams = $this->teamModel->findByCreator($userId); + + $this->view->display('teams.index', [ + 'title' => 'Teams - ServerManager', + 'teams' => $teams, + ]); + } + + public function create(): void + { + $this->view->display('teams.create', [ + 'title' => 'Create Team - ServerManager', + ]); + } + + public function store(): void + { + $validator = new Validator(); + $rules = [ + 'name' => 'required|min:2|max:100', + 'description' => 'max:500', + ]; + + if (!$validator->validate($_POST, $rules)) { + Session::setFlash('error', $validator->getFirstError()); + $this->view->redirect('/teams/create'); + } + + $id = $this->teamModel->create([ + 'name' => $_POST['name'], + 'description' => $_POST['description'] ?? '', + ]); + + $this->auditService->log('team_created', 'team', $id, [ + 'name' => $_POST['name'], + ]); + + Session::setFlash('success', 'Team created successfully.'); + $this->view->redirect('/teams'); + } + + public function show(int $id): void + { + $userId = (int) Session::get('user_id'); + $team = $this->teamModel->findById($id); + + if (!$team || (int) $team['created_by'] !== $userId) { + $this->view->error(404); + } + + $members = $this->teamModel->getMembers($id); + $servers = $this->teamModel->getServers($id); + + $userModel = new User(); + $allUsers = $userModel->getAll(1, 999); + $memberIds = array_map(fn($m) => $m['user_id'], $members); + $availableUsers = array_filter($allUsers['data'], fn($u) => !in_array((int) $u['id'], $memberIds, true)); + + $userId = (int) Session::get('user_id'); + $ownedServers = $this->serverModel->getOwnedServers($userId); + $serverIds = array_map(fn($s) => (int) $s['id'], $servers); + $availableServers = array_filter($ownedServers, fn($s) => !in_array((int) $s['id'], $serverIds, true)); + + $this->view->display('teams.show', [ + 'title' => $team['name'] . ' - Teams - ServerManager', + 'team' => $team, + 'members' => $members, + 'servers' => $servers, + 'availableUsers' => $availableUsers, + 'availableServers' => $availableServers, + ]); + } + + public function update(int $id): void + { + $userId = (int) Session::get('user_id'); + $team = $this->teamModel->findById($id); + + if (!$team || (int) $team['created_by'] !== $userId) { + $this->view->json(['success' => false, 'message' => 'Team not found'], 404); + } + + $validator = new Validator(); + $rules = [ + 'name' => 'required|min:2|max:100', + 'description' => 'max:500', + ]; + + if (!$validator->validate($_POST, $rules)) { + $this->view->json(['success' => false, 'message' => $validator->getFirstError()]); + } + + $this->teamModel->update($id, [ + 'name' => $_POST['name'], + 'description' => $_POST['description'] ?? '', + ]); + + $this->auditService->log('team_updated', 'team', $id, [ + 'name' => $_POST['name'], + ]); + + $this->view->json(['success' => true, 'message' => 'Team updated.']); + } + + public function delete(int $id): void + { + $userId = (int) Session::get('user_id'); + $team = $this->teamModel->findById($id); + + if (!$team || (int) $team['created_by'] !== $userId) { + $this->view->json(['success' => false, 'message' => 'Team not found'], 404); + } + + $this->teamModel->delete($id); + + $this->auditService->log('team_deleted', 'team', $id, [ + 'name' => $team['name'], + ]); + + Session::setFlash('success', 'Team deleted.'); + $this->view->redirect('/teams'); + } + + public function addUser(int $id): void + { + $userId = (int) Session::get('user_id'); + $team = $this->teamModel->findById($id); + + if (!$team || (int) $team['created_by'] !== $userId) { + $this->view->json(['success' => false, 'message' => 'Team not found'], 404); + } + + $userId = (int) ($_POST['user_id'] ?? 0); + if ($userId <= 0) { + $this->view->json(['success' => false, 'message' => 'Invalid user']); + } + + if ($this->teamModel->addMember($id, $userId)) { + $this->auditService->log('team_user_added', 'team', $id, [ + 'user_id' => $userId, + ]); + $this->view->json(['success' => true, 'message' => 'User added to team.']); + } + + $this->view->json(['success' => false, 'message' => 'User is already in this team.']); + } + + public function removeUser(int $id): void + { + $userId = (int) Session::get('user_id'); + $team = $this->teamModel->findById($id); + + if (!$team || (int) $team['created_by'] !== $userId) { + $this->view->json(['success' => false, 'message' => 'Team not found'], 404); + } + + $userId = (int) ($_POST['user_id'] ?? 0); + if ($userId <= 0) { + $this->view->json(['success' => false, 'message' => 'Invalid user']); + } + + $this->teamModel->removeMember($id, $userId); + + $this->auditService->log('team_user_removed', 'team', $id, [ + 'user_id' => $userId, + ]); + + $this->view->json(['success' => true, 'message' => 'User removed from team.']); + } + + public function addServer(int $id): void + { + $userId = (int) Session::get('user_id'); + $team = $this->teamModel->findById($id); + + if (!$team || (int) $team['created_by'] !== $userId) { + $this->view->json(['success' => false, 'message' => 'Team not found'], 404); + } + + $serverId = (int) ($_POST['server_id'] ?? 0); + $role = $_POST['role'] ?? 'viewer'; + + if ($serverId <= 0) { + $this->view->json(['success' => false, 'message' => 'Invalid server']); + } + + if (!in_array($role, ['viewer', 'manager'], true)) { + $this->view->json(['success' => false, 'message' => 'Invalid role']); + } + + $userId = (int) Session::get('user_id'); + $server = $this->serverModel->findById($serverId); + if (!$server || (int) $server['created_by'] !== $userId) { + $this->view->json(['success' => false, 'message' => 'Server not found or not owned by you'], 404); + } + + if ($this->teamModel->addServer($id, $serverId, $role)) { + $this->auditService->log('team_server_added', 'team', $id, [ + 'server_id' => $serverId, + 'role' => $role, + ]); + $this->view->json(['success' => true, 'message' => 'Server assigned to team.']); + } + + $this->view->json(['success' => false, 'message' => 'Server is already assigned to this team.']); + } + + public function removeServer(int $id): void + { + $userId = (int) Session::get('user_id'); + $team = $this->teamModel->findById($id); + + if (!$team || (int) $team['created_by'] !== $userId) { + $this->view->json(['success' => false, 'message' => 'Team not found'], 404); + } + + $serverId = (int) ($_POST['server_id'] ?? 0); + if ($serverId <= 0) { + $this->view->json(['success' => false, 'message' => 'Invalid server']); + } + + $this->teamModel->removeServer($id, $serverId); + + $this->auditService->log('team_server_removed', 'team', $id, [ + 'server_id' => $serverId, + ]); + + $this->view->json(['success' => true, 'message' => 'Server removed from team.']); + } + + public function updateServerRole(int $id): void + { + $userId = (int) Session::get('user_id'); + $team = $this->teamModel->findById($id); + + if (!$team || (int) $team['created_by'] !== $userId) { + $this->view->json(['success' => false, 'message' => 'Team not found'], 404); + } + + $serverId = (int) ($_POST['server_id'] ?? 0); + $role = $_POST['role'] ?? 'viewer'; + + if ($serverId <= 0) { + $this->view->json(['success' => false, 'message' => 'Invalid server']); + } + + if (!in_array($role, ['viewer', 'manager'], true)) { + $this->view->json(['success' => false, 'message' => 'Invalid role']); + } + + $this->teamModel->updateServerRole($id, $serverId, $role); + + $this->auditService->log('team_server_role_updated', 'team', $id, [ + 'server_id' => $serverId, + 'role' => $role, + ]); + + $this->view->json(['success' => true, 'message' => 'Server role updated.']); + } +} diff --git a/src/Controllers/TerminalController.php b/src/Controllers/TerminalController.php index 8c7884c..668bfbf 100755 --- a/src/Controllers/TerminalController.php +++ b/src/Controllers/TerminalController.php @@ -32,6 +32,11 @@ class TerminalController $this->view->error(404); } + $userId = (int) Session::get('user_id'); + if (!$this->serverModel->canView($id, $userId)) { + $this->view->error(404); + } + $commandHistory = new CommandHistory(); $history = $commandHistory->getByServer($id, 50); @@ -50,6 +55,11 @@ class TerminalController $this->view->json(['success' => false, 'message' => 'Server not found'], 404); } + $userId = (int) Session::get('user_id'); + if (!$this->serverModel->canView($id, $userId)) { + $this->view->json(['success' => false, 'message' => 'Forbidden'], 403); + } + $command = $_POST['command'] ?? ''; if (empty($command)) { diff --git a/src/Models/Server.php b/src/Models/Server.php index e292b2d..8f7b260 100755 --- a/src/Models/Server.php +++ b/src/Models/Server.php @@ -7,6 +7,7 @@ namespace ServerManager\Models; use ServerManager\Core\Database; use ServerManager\Core\App; use ServerManager\Core\Encryption; +use ServerManager\Core\Session; class Server { @@ -74,6 +75,13 @@ class Server $params[] = $filters['status']; } + if (!empty($filters['accessible_ids'])) { + $ids = array_map('intval', $filters['accessible_ids']); + $placeholders = implode(',', array_fill(0, count($ids), '?')); + $where[] = "s.id IN ({$placeholders})"; + $params = array_merge($params, $ids); + } + $whereClause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : ''; $offset = ($page - 1) * $perPage; @@ -121,6 +129,11 @@ class Server $data['current_status'] = 'unknown'; } + $userId = Session::get('user_id'); + if ($userId && empty($data['created_by'])) { + $data['created_by'] = $userId; + } + return $this->db->insert('servers', $data); } @@ -186,4 +199,184 @@ class Server ); return (int) ($result['total'] ?? 0); } + + public function getUserRole(int $serverId, int $userId): ?string + { + $server = $this->db->fetch('SELECT created_by FROM servers WHERE id = ?', [$serverId]); + if (!$server) { + return null; + } + + if ((int) $server['created_by'] === $userId) { + return 'owner'; + } + + $direct = $this->db->fetch( + 'SELECT role FROM server_user WHERE server_id = ? AND user_id = ?', + [$serverId, $userId] + ); + + $teamRole = $this->db->fetch( + 'SELECT ts.role FROM team_user tu + JOIN team_server ts ON tu.team_id = ts.team_id + WHERE tu.user_id = ? AND ts.server_id = ? + ORDER BY FIELD(ts.role, \'manager\', \'viewer\') + LIMIT 1', + [$userId, $serverId] + ); + + $directRole = $direct ? $direct['role'] : null; + $teamRoleValue = $teamRole ? $teamRole['role'] : null; + + if ($directRole === 'manager' || $teamRoleValue === 'manager') { + return 'manager'; + } + if ($directRole === 'viewer' || $teamRoleValue === 'viewer') { + return 'viewer'; + } + + return null; + } + + public function getAccessibleServerIds(int $userId): array + { + $owned = $this->db->fetchAll( + 'SELECT id FROM servers WHERE created_by = ?', + [$userId] + ); + + $assigned = $this->db->fetchAll( + 'SELECT server_id FROM server_user WHERE user_id = ?', + [$userId] + ); + + $teamServerIds = $this->db->fetchAll( + 'SELECT DISTINCT ts.server_id FROM team_user tu + JOIN team_server ts ON tu.team_id = ts.team_id + WHERE tu.user_id = ?', + [$userId] + ); + + $ids = []; + foreach ($owned as $row) { + $ids[] = (int) $row['id']; + } + foreach ($assigned as $row) { + $ids[] = (int) $row['server_id']; + } + foreach ($teamServerIds as $row) { + $ids[] = (int) $row['server_id']; + } + + return array_unique($ids); + } + + public function isOwner(int $serverId, int $userId): bool + { + $server = $this->db->fetch('SELECT created_by FROM servers WHERE id = ?', [$serverId]); + return $server && (int) $server['created_by'] === $userId; + } + + public function canManage(int $serverId, int $userId): bool + { + $role = $this->getUserRole($serverId, $userId); + return $role === 'owner' || $role === 'manager'; + } + + public function canView(int $serverId, int $userId): bool + { + return $this->getUserRole($serverId, $userId) !== null; + } + + public function getTeam(int $serverId): array + { + $members = $this->db->fetchAll( + 'SELECT su.*, u.username, u.email + FROM server_user su + JOIN users u ON su.user_id = u.id + WHERE su.server_id = ? + ORDER BY su.created_at ASC', + [$serverId] + ); + + $owner = $this->db->fetch( + 'SELECT u.id, u.username, u.email + FROM servers s + JOIN users u ON s.created_by = u.id + WHERE s.id = ?', + [$serverId] + ); + + $result = []; + if ($owner) { + $result[] = [ + 'user_id' => (int) $owner['id'], + 'username' => $owner['username'], + 'email' => $owner['email'], + 'role' => 'owner', + ]; + } + foreach ($members as $m) { + $result[] = [ + 'user_id' => (int) $m['user_id'], + 'username' => $m['username'], + 'email' => $m['email'], + 'role' => $m['role'], + ]; + } + return $result; + } + + public function addUser(int $serverId, int $userId, string $role): bool + { + if (!in_array($role, ['viewer', 'manager'], true)) { + return false; + } + + $existing = $this->db->fetch( + 'SELECT id FROM server_user WHERE server_id = ? AND user_id = ?', + [$serverId, $userId] + ); + if ($existing) { + return false; + } + + $this->db->insert('server_user', [ + 'server_id' => $serverId, + 'user_id' => $userId, + 'role' => $role, + ]); + return true; + } + + public function removeUser(int $serverId, int $userId): bool + { + return $this->db->delete( + 'server_user', + 'server_id = ? AND user_id = ?', + [$serverId, $userId] + ) > 0; + } + + public function updateUserRole(int $serverId, int $userId, string $role): bool + { + if (!in_array($role, ['viewer', 'manager'], true)) { + return false; + } + + return $this->db->update( + 'server_user', + ['role' => $role], + 'server_id = ? AND user_id = ?', + [$serverId, $userId] + ) > 0; + } + + public function getOwnedServers(int $userId): array + { + return $this->db->fetchAll( + 'SELECT * FROM servers WHERE created_by = ? ORDER BY name ASC', + [$userId] + ); + } } diff --git a/src/Models/Team.php b/src/Models/Team.php new file mode 100644 index 0000000..b512be6 --- /dev/null +++ b/src/Models/Team.php @@ -0,0 +1,164 @@ +db = Database::getInstance(); + } + + public function findAll(?int $userId = null): array + { + $where = $userId ? ' WHERE t.created_by = ?' : ''; + $params = $userId ? [$userId] : []; + + return $this->db->fetchAll( + "SELECT t.*, + (SELECT COUNT(*) FROM team_user WHERE team_id = t.id) AS member_count, + (SELECT COUNT(*) FROM team_server WHERE team_id = t.id) AS server_count + FROM teams t{$where} + ORDER BY t.name ASC", + $params + ); + } + + public function findByCreator(int $userId): array + { + return $this->findAll($userId); + } + + public function findById(int $id): ?array + { + $team = $this->db->fetch( + 'SELECT t.*, + (SELECT COUNT(*) FROM team_user WHERE team_id = t.id) AS member_count, + (SELECT COUNT(*) FROM team_server WHERE team_id = t.id) AS server_count + FROM teams t WHERE t.id = ?', + [$id] + ); + return $team ?: null; + } + + public function create(array $data): int + { + $data['created_by'] = (int) Session::get('user_id'); + $data['created_at'] = date('Y-m-d H:i:s'); + $data['updated_at'] = date('Y-m-d H:i:s'); + return $this->db->insert('teams', $data); + } + + public function update(int $id, array $data): bool + { + $data['updated_at'] = date('Y-m-d H:i:s'); + return $this->db->update('teams', $data, 'id = ?', [$id]) > 0; + } + + public function delete(int $id): bool + { + return $this->db->delete('teams', 'id = ?', [$id]) > 0; + } + + public function getMembers(int $teamId): array + { + return $this->db->fetchAll( + 'SELECT tu.*, u.username, u.email + FROM team_user tu + JOIN users u ON tu.user_id = u.id + WHERE tu.team_id = ? + ORDER BY u.username ASC', + [$teamId] + ); + } + + public function addMember(int $teamId, int $userId): bool + { + $existing = $this->db->fetch( + 'SELECT id FROM team_user WHERE team_id = ? AND user_id = ?', + [$teamId, $userId] + ); + if ($existing) { + return false; + } + + $this->db->insert('team_user', [ + 'team_id' => $teamId, + 'user_id' => $userId, + ]); + return true; + } + + public function removeMember(int $teamId, int $userId): bool + { + return $this->db->delete( + 'team_user', + 'team_id = ? AND user_id = ?', + [$teamId, $userId] + ) > 0; + } + + public function getServers(int $teamId): array + { + return $this->db->fetchAll( + 'SELECT ts.*, s.name AS server_name, s.ip_address + FROM team_server ts + JOIN servers s ON ts.server_id = s.id + WHERE ts.team_id = ? + ORDER BY s.name ASC', + [$teamId] + ); + } + + public function addServer(int $teamId, int $serverId, string $role): bool + { + if (!in_array($role, ['viewer', 'manager'], true)) { + return false; + } + + $existing = $this->db->fetch( + 'SELECT id FROM team_server WHERE team_id = ? AND server_id = ?', + [$teamId, $serverId] + ); + if ($existing) { + return false; + } + + $this->db->insert('team_server', [ + 'team_id' => $teamId, + 'server_id' => $serverId, + 'role' => $role, + ]); + return true; + } + + public function removeServer(int $teamId, int $serverId): bool + { + return $this->db->delete( + 'team_server', + 'team_id = ? AND server_id = ?', + [$teamId, $serverId] + ) > 0; + } + + public function updateServerRole(int $teamId, int $serverId, string $role): bool + { + if (!in_array($role, ['viewer', 'manager'], true)) { + return false; + } + + return $this->db->update( + 'team_server', + ['role' => $role], + 'team_id = ? AND server_id = ?', + [$teamId, $serverId] + ) > 0; + } +} diff --git a/src/Services/MonitoringService.php b/src/Services/MonitoringService.php index 21b95b8..b73fe59 100755 --- a/src/Services/MonitoringService.php +++ b/src/Services/MonitoringService.php @@ -173,20 +173,41 @@ class MonitoringService ); } - public function getDashboardStats(): array + public function getDashboardStats(?array $serverIds = null): array { - $totalServers = $this->db->fetch("SELECT COUNT(*) as total FROM servers"); - $onlineServers = $this->db->fetch("SELECT COUNT(*) as total FROM servers WHERE current_status = 'online' AND is_active = 1"); - $offlineServers = $this->db->fetch("SELECT COUNT(*) as total FROM servers WHERE current_status = 'offline' AND is_active = 1"); + $where = ''; + $params = []; + + if ($serverIds !== null) { + if (empty($serverIds)) { + $serverIds = [-1]; + } + $placeholders = implode(',', array_fill(0, count($serverIds), '?')); + $where = " AND s.id IN ({$placeholders})"; + $params = $serverIds; + } + + $totalServers = $this->db->fetch("SELECT COUNT(*) as total FROM servers s WHERE 1=1{$where}", $params); + $onlineServers = $this->db->fetch( + "SELECT COUNT(*) as total FROM servers s WHERE s.current_status = 'online' AND s.is_active = 1{$where}", + $params + ); + $offlineServers = $this->db->fetch( + "SELECT COUNT(*) as total FROM servers s WHERE s.current_status = 'offline' AND s.is_active = 1{$where}", + $params + ); $avgCpu = $this->db->fetch( - "SELECT AVG(cpu_usage) as avg FROM servers WHERE is_active = 1 AND current_status = 'online'" + "SELECT AVG(s.cpu_usage) as avg FROM servers s WHERE s.is_active = 1 AND s.current_status = 'online'{$where}", + $params ); $avgRam = $this->db->fetch( - "SELECT AVG(ram_usage) as avg FROM servers WHERE is_active = 1 AND current_status = 'online'" + "SELECT AVG(s.ram_usage) as avg FROM servers s WHERE s.is_active = 1 AND s.current_status = 'online'{$where}", + $params ); $avgDisk = $this->db->fetch( - "SELECT AVG(disk_usage) as avg FROM servers WHERE is_active = 1 AND current_status = 'online'" + "SELECT AVG(s.disk_usage) as avg FROM servers s WHERE s.is_active = 1 AND s.current_status = 'online'{$where}", + $params ); return [ diff --git a/views/admin/users.php b/views/admin/users.php index 9e6cd27..d2cf2a3 100755 --- a/views/admin/users.php +++ b/views/admin/users.php @@ -100,8 +100,8 @@
@@ -130,7 +130,7 @@ function showCreateUserModal() { document.getElementById('modalPassword').value = ''; document.getElementById('modalPassword').required = true; document.getElementById('passwordHint').style.display = 'block'; - document.getElementById('modalRole').value = 'operator'; + document.getElementById('modalRole').value = 'admin'; document.getElementById('modalActive').checked = true; document.getElementById('saveUserBtn').textContent = 'Create'; document.getElementById('userModal').style.display = 'flex'; diff --git a/views/dashboard/index.php b/views/dashboard/index.php index 660f904..d1fed38 100755 --- a/views/dashboard/index.php +++ b/views/dashboard/index.php @@ -65,7 +65,9 @@ $recentActivity = $recentActivity ?? [];

No servers configured yet.

+ Add Server +
diff --git a/views/layouts/main.php b/views/layouts/main.php index 48824e1..75e4a87 100755 --- a/views/layouts/main.php +++ b/views/layouts/main.php @@ -39,9 +39,15 @@
+ - + - - + + + + diff --git a/views/servers/index.php b/views/servers/index.php index 85343f5..a6eb12f 100755 --- a/views/servers/index.php +++ b/views/servers/index.php @@ -9,9 +9,11 @@ $groups = $groups ?? [];

Manage your Linux servers

+ Add Server +
@@ -45,7 +47,9 @@ $groups = $groups ?? [];

No servers found.

+ Add Your First Server +
@@ -106,6 +110,7 @@ $groups = $groups ?? []; + @@ -113,6 +118,7 @@ $groups = $groups ?? []; onclick="deleteServer(, '')"> +
diff --git a/views/servers/show.php b/views/servers/show.php index 4d8fd96..1f4d995 100755 --- a/views/servers/show.php +++ b/views/servers/show.php @@ -2,6 +2,8 @@ $server = $server ?? []; $metrics = $metrics ?? null; $historicalMetrics = $historicalMetrics ?? []; +$server_role = $server_role ?? null; +$canManage = $server_role === 'owner' || $server_role === 'manager'; ?>
+ + + + + + + + Terminal + Edit + +
+
@@ -154,6 +168,7 @@ $historicalMetrics = $historicalMetrics ?? []; System Logs + @@ -180,6 +195,7 @@ $historicalMetrics = $historicalMetrics ?? []; Restart Service + diff --git a/views/teams/create.php b/views/teams/create.php new file mode 100644 index 0000000..379a469 --- /dev/null +++ b/views/teams/create.php @@ -0,0 +1,36 @@ + + +
+
+
+ + +
+ + +
+ +
+ + +
+ +
+ + Cancel +
+
+
+
diff --git a/views/teams/index.php b/views/teams/index.php new file mode 100644 index 0000000..b617480 --- /dev/null +++ b/views/teams/index.php @@ -0,0 +1,78 @@ + + + + +
+
+ +
+ +

No teams created yet.

+ Create Your First Team +
+ +
+ + + + + + + + + + + + + + + + + + + + + +
NameDescriptionMembersServersActions
+ + + + +
+ + + + +
+
+
+ +
+
+ + + + diff --git a/views/teams/show.php b/views/teams/show.php new file mode 100644 index 0000000..9937a58 --- /dev/null +++ b/views/teams/show.php @@ -0,0 +1,289 @@ + + + + +
+
+
+

Members

+ +
+
+
+ + + + + + + + + + + + + + + +
UserActions
+ +
+
+ +
+
+ + +
+ +
+ +
+ +
+ +
+
+ +
+
+

Servers

+ +
+
+
+ + + + + + + + + + + + + + + + + +
ServerRoleActions
+ + + + + + + +
+
+ + +
+ +
+ +
+
+ +
+ +
+ +
+
+
+ + + +