ServerManager project files

This commit is contained in:
2026-06-06 17:58:34 -04:00
parent ce1863d955
commit 9c0bc7f189
502 changed files with 87837 additions and 0 deletions

195
src/Services/AuditService.php Executable file
View File

@@ -0,0 +1,195 @@
<?php
declare(strict_types=1);
namespace ServerManager\Services;
use ServerManager\Core\Database;
use ServerManager\Core\Session;
use ServerManager\Core\Logger;
class AuditService
{
private Database $db;
private Logger $logger;
public function __construct()
{
$this->db = Database::getInstance();
$this->logger = Logger::getInstance();
}
public function log(
string $action,
string $entityType,
?int $entityId,
?array $details = null,
?array $metadata = null
): void {
$userId = Session::get('user_id');
$username = Session::get('username');
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? 'unknown';
$now = date('Y-m-d H:i:s');
$this->db->insert('audit_logs', [
'user_id' => $userId,
'username' => $username ?? 'system',
'action' => $action,
'entity_type' => $entityType,
'entity_id' => $entityId,
'details' => $details ? json_encode($details, JSON_UNESCAPED_SLASHES) : null,
'ip_address' => $ip,
'user_agent' => $userAgent,
'created_at' => $now,
]);
$logMessage = sprintf(
'[AUDIT] %s | %s | %s:%s | User: %s (%d)',
$action,
$entityType,
$entityId ?? 'N/A',
$ip,
$username ?? 'system',
$userId ?? 0
);
$this->logger->info($logMessage);
if (in_array($action, ['credential_access', 'credential_decrypt', 'credential_use'])) {
$this->logger->warning("[CREDENTIAL_ACCESS] {$logMessage}");
}
}
public function logCredentialAccess(int $serverId, string $reason): void
{
$server = $this->db->fetch('SELECT name, ip_address FROM servers WHERE id = ?', [$serverId]);
$serverName = $server['name'] ?? 'Unknown';
$this->log(
'credential_access',
'server_credentials',
$serverId,
[
'server_name' => $serverName,
'ip_address' => $server['ip_address'] ?? 'unknown',
'reason' => $reason,
'timestamp' => date('c'),
]
);
}
public function logLogin(string $username, bool $success, ?string $reason = null): void
{
$this->log(
$success ? 'login_success' : 'login_failed',
'user',
null,
[
'username' => $username,
'success' => $success,
'reason' => $reason,
]
);
if (!$success) {
$this->logger->warning("[SECURITY] Failed login attempt for username: {$username}", [
'ip' => $_SERVER['REMOTE_ADDR'] ?? 'unknown',
]);
}
}
public function logServerAction(int $serverId, string $action, string $command): void
{
$this->log(
'server_action',
'server',
$serverId,
[
'action' => $action,
'command' => $command,
]
);
}
public function logUserManagement(int $userId, string $action): void
{
$this->log(
$action,
'user',
$userId,
[
'performed_by' => Session::get('username'),
]
);
}
public function getRecentActivity(int $limit = 20): array
{
return $this->db->fetchAll(
'SELECT al.*, u.username as actor_name
FROM audit_logs al
LEFT JOIN users u ON al.user_id = u.id
ORDER BY al.created_at DESC
LIMIT ?',
[$limit]
);
}
public function getAuditLogs(int $page = 1, int $perPage = 50, array $filters = []): array
{
$where = [];
$params = [];
if (!empty($filters['action'])) {
$where[] = 'al.action = ?';
$params[] = $filters['action'];
}
if (!empty($filters['user_id'])) {
$where[] = 'al.user_id = ?';
$params[] = $filters['user_id'];
}
if (!empty($filters['entity_type'])) {
$where[] = 'al.entity_type = ?';
$params[] = $filters['entity_type'];
}
if (!empty($filters['date_from'])) {
$where[] = 'al.created_at >= ?';
$params[] = $filters['date_from'];
}
if (!empty($filters['date_to'])) {
$where[] = 'al.created_at <= ?';
$params[] = $filters['date_to'];
}
$whereClause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';
$offset = ($page - 1) * $perPage;
$total = $this->db->fetch(
"SELECT COUNT(*) as total FROM audit_logs al {$whereClause}",
$params
);
$logs = $this->db->fetchAll(
"SELECT al.*, u.username as actor_name
FROM audit_logs al
LEFT JOIN users u ON al.user_id = u.id
{$whereClause}
ORDER BY al.created_at DESC
LIMIT ? OFFSET ?",
array_merge($params, [$perPage, $offset])
);
return [
'data' => $logs,
'total' => (int) ($total['total'] ?? 0),
'page' => $page,
'per_page' => $perPage,
'total_pages' => ceil((int) ($total['total'] ?? 0) / $perPage),
];
}
}

View File

@@ -0,0 +1,201 @@
<?php
declare(strict_types=1);
namespace ServerManager\Services;
use ServerManager\Core\Database;
use ServerManager\Core\Logger;
use ServerManager\Core\App;
use ServerManager\Services\SSHService;
class MonitoringService
{
private Database $db;
private Logger $logger;
public function __construct()
{
$this->db = Database::getInstance();
$this->logger = Logger::getInstance();
}
public function collectMetrics(int $serverId): ?array
{
$server = $this->db->fetch('SELECT * FROM servers WHERE id = ?', [$serverId]);
if (!$server || !$server['is_active']) {
return null;
}
$server = App::getEncryption()->decryptArray($server, ['ssh_password', 'ssh_key']);
try {
$ssh = new SSHService();
if (!$ssh->connect($server, true)) {
return ['status' => 'offline'];
}
$metrics = [
'server_id' => $serverId,
'status' => 'online',
'cpu_usage' => $this->getCPUUsage($ssh),
'ram_usage' => $this->getRAMUsage($ssh),
'disk_usage' => $this->getDiskUsage($ssh),
'load_average' => $this->getLoadAverage($ssh),
'uptime' => $this->getUptime($ssh),
];
$this->saveMetrics($metrics);
$ssh->disconnect();
return $metrics;
} catch (\Throwable $e) {
$this->logger->warning("Failed to collect metrics for server {$serverId}: " . $e->getMessage());
$this->saveMetrics([
'server_id' => $serverId,
'status' => 'offline',
'cpu_usage' => 0,
'ram_usage' => 0,
'disk_usage' => 0,
'load_average' => 0,
'uptime' => '',
]);
return ['status' => 'offline'];
}
}
public function collectAllMetrics(): array
{
$servers = $this->db->fetchAll('SELECT * FROM servers WHERE is_active = 1');
$results = [];
foreach ($servers as $server) {
$results[$server['id']] = $this->collectMetrics($server['id']);
}
return $results;
}
private function getCPUUsage(SSHService $ssh): float
{
$result = $ssh->exec("top -bn1 | grep 'Cpu(s)' | awk '{print $2}' | cut -d'%' -f1");
return (float) trim($result['output']);
}
private function getRAMUsage(SSHService $ssh): float
{
$result = $ssh->exec("free | grep Mem | awk '{printf \"%.2f\", $3/$2 * 100}'");
return (float) trim($result['output']);
}
private function getDiskUsage(SSHService $ssh): float
{
$result = $ssh->exec("df / | tail -1 | awk '{print $5}' | sed 's/%//'");
return (float) trim($result['output']);
}
private function getLoadAverage(SSHService $ssh): float
{
$result = $ssh->exec("cat /proc/loadavg | awk '{print $1}'");
return (float) trim($result['output']);
}
private function getUptime(SSHService $ssh): string
{
$result = $ssh->exec("uptime -p");
return trim(str_replace('up ', '', $result['output']));
}
private function saveMetrics(array $metrics): void
{
$now = date('Y-m-d H:i:s');
$this->db->insert('monitoring_history', [
'server_id' => $metrics['server_id'],
'status' => $metrics['status'],
'cpu_usage' => $metrics['cpu_usage'],
'ram_usage' => $metrics['ram_usage'],
'disk_usage' => $metrics['disk_usage'],
'load_average' => $metrics['load_average'],
'uptime' => $metrics['uptime'] ?? '',
'created_at' => $now,
]);
$this->db->query(
"UPDATE servers SET
last_check_at = ?,
current_status = ?,
cpu_usage = ?,
ram_usage = ?,
disk_usage = ?,
load_average = ?,
uptime = ?
WHERE id = ?",
[
$now,
$metrics['status'],
$metrics['cpu_usage'],
$metrics['ram_usage'],
$metrics['disk_usage'],
$metrics['load_average'],
$metrics['uptime'] ?? '',
$metrics['server_id'],
]
);
$retention = App::getConfig()['monitoring']['retention_days'] ?? 30;
$this->db->query(
"DELETE FROM monitoring_history WHERE created_at < DATE_SUB(NOW(), INTERVAL ? DAY)",
[$retention]
);
}
public function getLatestMetrics(int $serverId): ?array
{
return $this->db->fetch(
'SELECT * FROM monitoring_history WHERE server_id = ? ORDER BY created_at DESC LIMIT 1',
[$serverId]
);
}
public function getHistoricalMetrics(int $serverId, int $hours = 24): array
{
return $this->db->fetchAll(
'SELECT * FROM monitoring_history
WHERE server_id = ? AND created_at >= DATE_SUB(NOW(), INTERVAL ? HOUR)
ORDER BY created_at ASC',
[$serverId, $hours]
);
}
public function getDashboardStats(): 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");
$avgCpu = $this->db->fetch(
"SELECT AVG(cpu_usage) as avg FROM servers WHERE is_active = 1 AND current_status = 'online'"
);
$avgRam = $this->db->fetch(
"SELECT AVG(ram_usage) as avg FROM servers WHERE is_active = 1 AND current_status = 'online'"
);
$avgDisk = $this->db->fetch(
"SELECT AVG(disk_usage) as avg FROM servers WHERE is_active = 1 AND current_status = 'online'"
);
return [
'total_servers' => (int) ($totalServers['total'] ?? 0),
'online_servers' => (int) ($onlineServers['total'] ?? 0),
'offline_servers' => (int) ($offlineServers['total'] ?? 0),
'avg_cpu' => round((float) ($avgCpu['avg'] ?? 0), 1),
'avg_ram' => round((float) ($avgRam['avg'] ?? 0), 1),
'avg_disk' => round((float) ($avgDisk['avg'] ?? 0), 1),
];
}
}

168
src/Services/SSHService.php Executable file
View File

@@ -0,0 +1,168 @@
<?php
declare(strict_types=1);
namespace ServerManager\Services;
use phpseclib3\Net\SSH2;
use phpseclib3\Crypt\PublicKeyLoader;
use ServerManager\Core\App;
use ServerManager\Core\Encryption;
use ServerManager\Core\Logger;
class SSHService
{
private ?SSH2 $connection = null;
private array $serverConfig;
private Logger $logger;
public function __construct()
{
$this->logger = Logger::getInstance();
}
public function connect(array $server, bool $forceReconnect = false): bool
{
if ($this->connection !== null && !$forceReconnect) {
return $this->connection->isConnected();
}
$this->serverConfig = $server;
$config = App::getConfig()['ssh'];
try {
$host = $server['ip_address'] ?? $server['hostname'];
$port = (int) ($server['ssh_port'] ?? 22);
$this->connection = new SSH2($host, $port, $config['timeout'] ?? 30);
if (!empty($server['ssh_key'])) {
$key = PublicKeyLoader::load($server['ssh_key']);
$result = $this->connection->login($server['ssh_user'], $key);
} elseif (!empty($server['ssh_password'])) {
$result = $this->connection->login($server['ssh_user'], $server['ssh_password']);
} else {
throw new \RuntimeException('No authentication method configured');
}
if (!$result) {
throw new \RuntimeException('SSH authentication failed for ' . $host);
}
if ($config['keepalive_interval'] ?? 0 > 0) {
$this->connection->setKeepAlive($config['keepalive_interval']);
}
$this->logger->info("SSH connection established to {$host}");
return true;
} catch (\Throwable $e) {
$this->logger->error("SSH connection failed: " . $e->getMessage(), [
'host' => $host ?? 'unknown',
'port' => $port ?? 22,
]);
$this->connection = null;
return false;
}
}
public function disconnect(): void
{
if ($this->connection !== null) {
$this->connection->disconnect();
$this->connection = null;
}
}
public function exec(string $command, int $timeout = 0): array
{
if ($this->connection === null || !$this->connection->isConnected()) {
throw new \RuntimeException('No active SSH connection');
}
$timeout = $timeout ?: (App::getConfig()['ssh']['command_timeout'] ?? 60);
$this->connection->setTimeout($timeout);
$output = $this->connection->exec($command);
$exitStatus = $this->connection->getExitStatus();
$stderr = $this->connection->getStdError() ?? '';
$this->logger->info("SSH command executed", [
'command' => $command,
'exit_status' => $exitStatus,
]);
return [
'output' => $output,
'exit_status' => $exitStatus,
'stderr' => $stderr,
];
}
public function write(string $data): void
{
if ($this->connection !== null && $this->connection->isConnected()) {
$this->connection->write($data);
}
}
public function read(string $expected = '', int $mode = SSH2::READ_REGEX): string
{
if ($this->connection === null || !$this->connection->isConnected()) {
throw new \RuntimeException('No active SSH connection');
}
return $this->connection->read($expected, $mode);
}
public function getInteractiveShell(): ?SSH2
{
return $this->connection;
}
public function testConnection(array $server): bool
{
try {
$result = $this->connect($server, true);
if ($result) {
$this->disconnect();
return true;
}
return false;
} catch (\Throwable $e) {
$this->logger->warning("SSH connection test failed", ['error' => $e->getMessage()]);
return false;
}
}
public function getSystemInfo(): array
{
$commands = [
'hostname' => 'hostname',
'os' => 'cat /etc/os-release | head -1',
'kernel' => 'uname -r',
'uptime' => 'uptime -p',
'cpu_model' => 'cat /proc/cpuinfo | grep "model name" | head -1',
'cpu_cores' => 'nproc',
'mem_total' => 'free -b | grep Mem | awk \'{print $2}\'',
'mem_used' => "free -b | grep Mem | awk '{print $3}'",
'mem_avail' => "free -b | grep Mem | awk '{print $7}'",
'disk_total' => "df -B1 / | tail -1 | awk '{print $2}'",
'disk_used' => "df -B1 / | tail -1 | awk '{print $3}'",
'disk_avail' => "df -B1 / | tail -1 | awk '{print $4}'",
'disk_percent' => "df / | tail -1 | awk '{print $5}' | sed 's/%//'",
];
$info = [];
foreach ($commands as $key => $cmd) {
$result = $this->exec($cmd);
$info[$key] = trim($result['output']);
}
return $info;
}
public function __destruct()
{
$this->disconnect();
}
}