Files
server-manager/src/Models/Server.php
Agent 1bc8236d4d fix: filtrar servidores por usuario/equipo según rol
- Server::getAccessibleServerIds(): super_admin ve todos los servidores;
  admin y operator solo ven propios, asignados directos o por equipo
- DashboardController: unifica lógica usando getAccessibleServerIds()
- ServerController::metrics(): agrega control de acceso requireServerAccess()
- ApiController::status(): filtra estadísticas por servidores accesibles
- Android: bump versionCode 9, versionName 1.7.0
2026-06-11 13:09:53 -04:00

409 lines
12 KiB
PHP
Executable File

<?php
declare(strict_types=1);
namespace ServerManager\Models;
use ServerManager\Core\Database;
use ServerManager\Core\App;
use ServerManager\Core\Encryption;
use ServerManager\Core\Session;
class Server
{
private Database $db;
private Encryption $encryption;
private array $encryptedFields = ['ssh_password', 'ssh_key'];
public function __construct()
{
$this->db = Database::getInstance();
$this->encryption = App::getEncryption();
}
public function findById(int $id): ?array
{
$server = $this->db->fetch('SELECT * FROM servers WHERE id = ? AND status = \'active\'', [$id]);
if ($server) {
$server = $this->encryption->decryptArray($server, $this->encryptedFields);
}
return $server;
}
public function findAll(bool $activeOnly = false): array
{
$sql = 'SELECT * FROM servers WHERE status = \'active\'';
$params = [];
if ($activeOnly) {
$sql .= ' AND is_active = 1';
}
$sql .= ' ORDER BY name ASC';
$servers = $this->db->fetchAll($sql, $params);
return array_map(function ($server) {
return $this->encryption->decryptArray($server, $this->encryptedFields);
}, $servers);
}
public function getAll(int $page = 1, int $perPage = 20, array $filters = []): array
{
$where = ["s.status = 'active'"];
$params = [];
if (!empty($filters['search'])) {
$where[] = '(s.name LIKE ? OR s.ip_address LIKE ? OR s.description LIKE ?)';
$search = '%' . $filters['search'] . '%';
$params[] = $search;
$params[] = $search;
$params[] = $search;
}
if (isset($filters['is_active']) && $filters['is_active'] !== '') {
$where[] = 's.is_active = ?';
$params[] = (int) $filters['is_active'];
}
if (!empty($filters['group_name'])) {
$where[] = 's.group_name = ?';
$params[] = $filters['group_name'];
}
if (!empty($filters['status'])) {
$where[] = 's.current_status = ?';
$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;
$total = $this->db->fetch(
"SELECT COUNT(*) as total FROM servers s {$whereClause}",
$params
);
$servers = $this->db->fetchAll(
"SELECT s.* FROM servers s {$whereClause} ORDER BY s.name ASC LIMIT ? OFFSET ?",
array_merge($params, [$perPage, $offset])
);
$servers = array_map(function ($server) {
return $this->encryption->decryptArray($server, $this->encryptedFields);
}, $servers);
return [
'data' => $servers,
'total' => (int) ($total['total'] ?? 0),
'page' => $page,
'per_page' => $perPage,
'total_pages' => ceil((int) ($total['total'] ?? 0) / $perPage),
];
}
public function create(array $data): int
{
if (!empty($data['ssh_password'])) {
$data['ssh_password'] = $this->encryption->encrypt($data['ssh_password']);
} else {
$data['ssh_password'] = null;
}
if (!empty($data['ssh_key'])) {
$data['ssh_key'] = $this->encryption->encrypt($data['ssh_key']);
} else {
$data['ssh_key'] = null;
}
$data['created_at'] = date('Y-m-d H:i:s');
$data['updated_at'] = date('Y-m-d H:i:s');
if (empty($data['current_status'])) {
$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);
}
public function update(int $id, array $data): bool
{
if (array_key_exists('ssh_password', $data)) {
if (!empty($data['ssh_password'])) {
$data['ssh_password'] = $this->encryption->encrypt($data['ssh_password']);
} else {
unset($data['ssh_password']);
}
}
if (array_key_exists('ssh_key', $data)) {
if (!empty($data['ssh_key'])) {
$data['ssh_key'] = $this->encryption->encrypt($data['ssh_key']);
} else {
unset($data['ssh_key']);
}
}
$data['updated_at'] = date('Y-m-d H:i:s');
return $this->db->update('servers', $data, 'id = ?', [$id]) > 0;
}
public function delete(int $id): bool
{
return $this->db->update('servers', [
'status' => 'deleted',
'is_active' => 0,
'updated_at' => date('Y-m-d H:i:s'),
], 'id = ?', [$id]) > 0;
}
public function toggleActive(int $id): ?bool
{
$server = $this->db->fetch('SELECT is_active, status FROM servers WHERE id = ?', [$id]);
if (!$server) {
return null;
}
$newState = $server['is_active'] ? 0 : 1;
$newStatus = $newState ? 'active' : 'disabled';
$this->db->update('servers', [
'is_active' => $newState,
'status' => $newStatus,
'updated_at' => date('Y-m-d H:i:s'),
], 'id = ?', [$id]);
return (bool) $newState;
}
public function getGroups(): array
{
$groups = $this->db->fetchAll(
'SELECT DISTINCT group_name FROM servers WHERE status = \'active\' AND group_name IS NOT NULL AND group_name != "" ORDER BY group_name'
);
return array_column($groups, 'group_name');
}
public function countAll(): int
{
$result = $this->db->fetch("SELECT COUNT(*) as total FROM servers WHERE status = 'active'");
return (int) ($result['total'] ?? 0);
}
public function countOnline(): int
{
$result = $this->db->fetch(
"SELECT COUNT(*) as total FROM servers WHERE current_status = 'online' AND status = 'active' AND is_active = 1"
);
return (int) ($result['total'] ?? 0);
}
public function getUserRole(int $serverId, int $userId): ?string
{
$server = $this->db->fetch('SELECT created_by FROM servers WHERE id = ? AND status = \'active\'', [$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 = ? AND status = \'active\'',
[$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 = ? AND tu.status = \'active\' AND ts.status = \'active\'
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
{
$user = $this->db->fetch(
'SELECT role FROM users WHERE id = ? AND status = \'active\'',
[$userId]
);
if (!$user) {
return [];
}
if ($user['role'] === 'super_admin') {
$all = $this->db->fetchAll(
'SELECT id FROM servers WHERE status = \'active\''
);
return array_map(fn($r) => (int) $r['id'], $all);
}
$owned = $this->db->fetchAll(
'SELECT id FROM servers WHERE created_by = ? AND status = \'active\'',
[$userId]
);
$assigned = $this->db->fetchAll(
'SELECT server_id FROM server_user WHERE user_id = ? AND status = \'active\'',
[$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 = ? AND tu.status = \'active\' AND ts.status = \'active\'',
[$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 = ? AND status = \'active\'', [$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 = ? AND su.status = \'active\' AND u.status = \'active\'
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 = ? AND s.status = \'active\' AND u.status = \'active\'',
[$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 = ? AND status = \'active\'',
[$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->update(
'server_user',
['status' => 'deleted'],
'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 = ? AND status = \'active\'',
[$serverId, $userId]
) > 0;
}
public function getOwnedServers(int $userId): array
{
return $this->db->fetchAll(
'SELECT * FROM servers WHERE created_by = ? AND status = \'active\' ORDER BY name ASC',
[$userId]
);
}
}