- Add status ENUM('active','disabled','deleted') to 8 tables
- Change all physical DELETE to UPDATE status='deleted'
- All SELECT queries filter by status='active'
- toggleActive now alternates between active/disabled status
- Exceptions: monitoring_history, rate_limits, sessions keep physical purge
- audit_logs remains immutable
393 lines
12 KiB
PHP
Executable File
393 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
|
|
{
|
|
$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]
|
|
);
|
|
}
|
|
}
|