1013 lines
32 KiB
PHP
Executable File
1013 lines
32 KiB
PHP
Executable File
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace ServerManager\Controllers;
|
|
|
|
use ServerManager\Core\App;
|
|
use ServerManager\Core\Session;
|
|
use ServerManager\Models\Server;
|
|
use ServerManager\Models\ServerPermission;
|
|
use ServerManager\Models\User;
|
|
use ServerManager\Services\MonitoringService;
|
|
use ServerManager\Services\SSHService;
|
|
use ServerManager\Services\AuditService;
|
|
use ServerManager\Services\PermissionValidator;
|
|
use ServerManager\Services\FCMService;
|
|
use ServerManager\Models\CommandHistory;
|
|
use ServerManager\Models\Notification;
|
|
use ServerManager\Core\Validator;
|
|
|
|
class ApiController
|
|
{
|
|
private \ServerManager\Core\View $view;
|
|
|
|
public function __construct()
|
|
{
|
|
$this->view = App::getInstance()->getView();
|
|
}
|
|
|
|
private function authenticateRequest(): ?array
|
|
{
|
|
$token = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
|
|
$token = str_replace('Bearer ', '', $token);
|
|
|
|
if (empty($token) && isset($_GET['api_token'])) {
|
|
$token = $_GET['api_token'];
|
|
}
|
|
|
|
if (empty($token)) {
|
|
$this->view->json(['error' => 'API token is required'], 401);
|
|
}
|
|
|
|
$userModel = new User();
|
|
$user = $userModel->findByApiToken($token);
|
|
|
|
if (!$user) {
|
|
$this->view->json(['error' => 'Invalid API token'], 401);
|
|
}
|
|
|
|
return $user;
|
|
}
|
|
|
|
public function status(): void
|
|
{
|
|
$user = $this->authenticateRequest();
|
|
|
|
$serverModel = new Server();
|
|
$accessibleIds = $serverModel->getAccessibleServerIds((int) $user['id']);
|
|
|
|
$monitoringService = new MonitoringService();
|
|
$stats = $monitoringService->getDashboardStats($accessibleIds);
|
|
|
|
$this->view->json([
|
|
'success' => true,
|
|
'data' => [
|
|
'application' => 'ServerManager',
|
|
'version' => '1.0.0',
|
|
'stats' => $stats,
|
|
'timestamp' => date('c'),
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function servers(): void
|
|
{
|
|
$user = $this->authenticateRequest();
|
|
|
|
$serverModel = new Server();
|
|
$page = (int) ($_GET['page'] ?? 1);
|
|
$perPage = (int) ($_GET['per_page'] ?? 20);
|
|
$search = $_GET['search'] ?? '';
|
|
|
|
$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([
|
|
'success' => true,
|
|
'data' => $servers['data'],
|
|
'pagination' => [
|
|
'page' => $servers['page'],
|
|
'per_page' => $servers['per_page'],
|
|
'total' => $servers['total'],
|
|
'total_pages' => $servers['total_pages'],
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function serverDetail(int $id): void
|
|
{
|
|
$user = $this->authenticateRequest();
|
|
|
|
$serverModel = new Server();
|
|
$server = $serverModel->findById($id);
|
|
|
|
if (!$server) {
|
|
$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);
|
|
|
|
unset($server['agent_key'], $server['ssh_password'], $server['ssh_key']);
|
|
|
|
$this->view->json([
|
|
'success' => true,
|
|
'data' => [
|
|
'server' => $server,
|
|
'metrics' => $metrics,
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function serverAdd(): void
|
|
{
|
|
$user = $this->authenticateRequest();
|
|
|
|
$input = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
|
|
|
$validator = new Validator();
|
|
$rules = [
|
|
'name' => 'required|min:2|max:100',
|
|
'ip_address' => 'required',
|
|
'ssh_port' => 'required|numeric|port',
|
|
'ssh_user' => 'required|min:2|max:50',
|
|
];
|
|
|
|
if (!$validator->validate($input, $rules)) {
|
|
$this->view->json(['error' => $validator->getFirstError()], 400);
|
|
}
|
|
|
|
$permissions = $this->extractPermissionsFromInput($input);
|
|
|
|
if (!empty($permissions)) {
|
|
$serverConfig = [
|
|
'ip_address' => $input['ip_address'],
|
|
'ssh_port' => (int) $input['ssh_port'],
|
|
'ssh_user' => $input['ssh_user'],
|
|
'ssh_password' => $input['ssh_password'] ?? null,
|
|
'ssh_key' => $input['ssh_key'] ?? null,
|
|
];
|
|
|
|
$permValidator = new PermissionValidator();
|
|
$permResult = $permValidator->validate($serverConfig, $permissions);
|
|
|
|
if (!$permResult['passed']) {
|
|
$failedPermissions = array_filter($permResult['results'], fn($r) => !$r['passed']);
|
|
$errors = [];
|
|
foreach ($failedPermissions as $fp) {
|
|
$label = $fp['description'] ?: ($fp['type'] . ': ' . $fp['value']);
|
|
$errors[] = $label . ' — ' . $fp['message'];
|
|
}
|
|
|
|
$this->view->json([
|
|
'error' => 'Permission validation failed',
|
|
'permission_errors' => $errors,
|
|
], 400);
|
|
}
|
|
}
|
|
|
|
$serverModel = new Server();
|
|
$id = $serverModel->create($input);
|
|
|
|
if (!empty($permissions)) {
|
|
try {
|
|
$permModel = new ServerPermission();
|
|
$permModel->save($id, $permissions);
|
|
} catch (\Throwable $e) {
|
|
// Table may not exist yet
|
|
}
|
|
}
|
|
|
|
$this->view->json([
|
|
'success' => true,
|
|
'message' => 'Server created successfully.',
|
|
'server_id' => $id,
|
|
], 201);
|
|
}
|
|
|
|
public function serverUpdate(int $id): void
|
|
{
|
|
$user = $this->authenticateRequest();
|
|
|
|
$input = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
|
|
|
$serverModel = new Server();
|
|
$server = $serverModel->findById($id);
|
|
|
|
if (!$server) {
|
|
$this->view->json(['error' => 'Server not found'], 404);
|
|
}
|
|
|
|
if (!$serverModel->canManage($id, (int) $user['id'])) {
|
|
$this->view->json(['error' => 'Forbidden'], 403);
|
|
}
|
|
|
|
$permissions = $this->extractPermissionsFromInput($input);
|
|
|
|
if (!empty($permissions)) {
|
|
$serverConfig = [
|
|
'ip_address' => $input['ip_address'] ?? $server['ip_address'],
|
|
'ssh_port' => (int) ($input['ssh_port'] ?? $server['ssh_port']),
|
|
'ssh_user' => $input['ssh_user'] ?? $server['ssh_user'],
|
|
'ssh_password' => $input['ssh_password'] ?? $server['ssh_password'],
|
|
'ssh_key' => $input['ssh_key'] ?? $server['ssh_key'],
|
|
];
|
|
|
|
$permValidator = new PermissionValidator();
|
|
$permResult = $permValidator->validate($serverConfig, $permissions);
|
|
|
|
if (!$permResult['passed']) {
|
|
$failedPermissions = array_filter($permResult['results'], fn($r) => !$r['passed']);
|
|
$errors = [];
|
|
foreach ($failedPermissions as $fp) {
|
|
$label = $fp['description'] ?: ($fp['type'] . ': ' . $fp['value']);
|
|
$errors[] = $label . ' — ' . $fp['message'];
|
|
}
|
|
|
|
$this->view->json([
|
|
'error' => 'Permission validation failed',
|
|
'permission_errors' => $errors,
|
|
], 400);
|
|
}
|
|
}
|
|
|
|
$serverModel->update($id, $input);
|
|
|
|
if (!empty($permissions)) {
|
|
try {
|
|
$permModel = new ServerPermission();
|
|
$permModel->save($id, $permissions);
|
|
} catch (\Throwable $e) {
|
|
// Table may not exist yet
|
|
}
|
|
}
|
|
|
|
$this->view->json([
|
|
'success' => true,
|
|
'message' => 'Server updated successfully.',
|
|
]);
|
|
}
|
|
|
|
private function extractPermissionsFromInput(array $input): array
|
|
{
|
|
$permissions = $input['permissions'] ?? [];
|
|
|
|
if (!is_array($permissions)) {
|
|
return [];
|
|
}
|
|
|
|
$filtered = [];
|
|
foreach ($permissions as $perm) {
|
|
if (!empty($perm['type']) && isset($perm['value']) && $perm['value'] !== '') {
|
|
$filtered[] = [
|
|
'type' => $perm['type'],
|
|
'value' => trim($perm['value']),
|
|
'description' => trim($perm['description'] ?? ''),
|
|
];
|
|
}
|
|
}
|
|
|
|
return $filtered;
|
|
}
|
|
|
|
public function serverDelete(int $id): void
|
|
{
|
|
$user = $this->authenticateRequest();
|
|
|
|
$serverModel = new Server();
|
|
$server = $serverModel->findById($id);
|
|
|
|
if (!$server) {
|
|
$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([
|
|
'success' => true,
|
|
'message' => 'Server deleted successfully.',
|
|
]);
|
|
}
|
|
|
|
public function serverMetrics(int $id): void
|
|
{
|
|
$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);
|
|
if ($hours > 168) $hours = 168;
|
|
|
|
$metrics = $monitoringService->getHistoricalMetrics($id, $hours);
|
|
|
|
$this->view->json([
|
|
'success' => true,
|
|
'data' => $metrics,
|
|
]);
|
|
}
|
|
|
|
public function executeCommand(int $id): void
|
|
{
|
|
$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'] ?? '';
|
|
|
|
if (empty($command)) {
|
|
$this->view->json(['error' => 'Command is required'], 400);
|
|
}
|
|
|
|
$serverModel = new Server();
|
|
$server = $serverModel->findById($id);
|
|
|
|
if (!$server) {
|
|
$this->view->json(['error' => 'Server not found'], 404);
|
|
}
|
|
|
|
try {
|
|
$ssh = new SSHService();
|
|
if (!$ssh->connect($server)) {
|
|
throw new \RuntimeException('Could not establish SSH connection');
|
|
}
|
|
$result = $ssh->exec($command);
|
|
$ssh->disconnect();
|
|
|
|
$commandHistory = new CommandHistory();
|
|
$commandHistory->log(
|
|
$id,
|
|
$user['id'],
|
|
$command,
|
|
$result['output'],
|
|
$result['exit_status'] ?? 0
|
|
);
|
|
|
|
$auditService = new AuditService();
|
|
$auditService->logServerAction($id, 'api_command_executed', $command);
|
|
|
|
$this->view->json([
|
|
'success' => true,
|
|
'data' => [
|
|
'output' => $result['output'],
|
|
'exit_status' => $result['exit_status'],
|
|
'stderr' => $result['stderr'],
|
|
],
|
|
]);
|
|
} catch (\Throwable $e) {
|
|
$this->view->json([
|
|
'error' => 'Command execution failed: ' . $e->getMessage(),
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
public function users(): void
|
|
{
|
|
$currentUser = $this->authenticateRequest();
|
|
|
|
if ($currentUser['role'] !== 'super_admin' && $currentUser['role'] !== 'admin') {
|
|
$this->view->json(['error' => 'Forbidden'], 403);
|
|
}
|
|
|
|
$userModel = new User();
|
|
$users = $userModel->getAll(1, 100);
|
|
|
|
$this->view->json([
|
|
'success' => true,
|
|
'data' => $users['data'],
|
|
]);
|
|
}
|
|
|
|
public function pushMetrics(): void
|
|
{
|
|
$rawBody = file_get_contents('php://input');
|
|
$input = json_decode($rawBody, true);
|
|
|
|
if (!$input || empty($input['agent_key'])) {
|
|
$this->view->json(['error' => 'agent_key is required'], 401);
|
|
}
|
|
|
|
$serverModel = new Server();
|
|
$server = $serverModel->findByAgentKey($input['agent_key']);
|
|
|
|
if (!$server || !$server['is_active']) {
|
|
$this->view->json(['error' => 'Invalid agent key'], 401);
|
|
}
|
|
|
|
$cpu = isset($input['cpu']) ? max(0, min(100, (float) $input['cpu'])) : 0;
|
|
$ram = isset($input['ram']) ? max(0, min(100, (float) $input['ram'])) : 0;
|
|
$disk = isset($input['disk']) ? max(0, min(100, (float) $input['disk'])) : 0;
|
|
$load = (float) ($input['load'] ?? 0);
|
|
$uptime = (string) ($input['uptime'] ?? '');
|
|
|
|
$monitoring = new MonitoringService();
|
|
$monitoring->saveMetrics([
|
|
'server_id' => (int) $server['id'],
|
|
'status' => 'online',
|
|
'cpu_usage' => $cpu,
|
|
'ram_usage' => $ram,
|
|
'disk_usage' => $disk,
|
|
'load_average' => $load,
|
|
'uptime' => $uptime,
|
|
]);
|
|
|
|
$serverModel->updateAgentLastSeen((int) $server['id']);
|
|
|
|
$checks = [
|
|
'cpu' => ['val' => $cpu, 'warn' => $server['threshold_cpu_warning'], 'crit' => $server['threshold_cpu_critical']],
|
|
'ram' => ['val' => $ram, 'warn' => $server['threshold_ram_warning'], 'crit' => $server['threshold_ram_critical']],
|
|
'disk' => ['val' => $disk, 'warn' => $server['threshold_disk_warning'], 'crit' => $server['threshold_disk_critical']],
|
|
];
|
|
|
|
$breaches = [];
|
|
foreach ($checks as $metric => $c) {
|
|
if ($c['crit'] !== null && $c['val'] >= (float) $c['crit']) {
|
|
$breaches[] = ['metric' => $metric, 'level' => 'critical', 'current' => $c['val'], 'threshold' => $c['crit']];
|
|
} elseif ($c['warn'] !== null && $c['val'] >= (float) $c['warn']) {
|
|
$breaches[] = ['metric' => $metric, 'level' => 'warning', 'current' => $c['val'], 'threshold' => $c['warn']];
|
|
}
|
|
}
|
|
|
|
if (!empty($breaches)) {
|
|
$userIds = $serverModel->getAccessibleUserIds((int) $server['id']);
|
|
$notificationModel = new Notification();
|
|
foreach ($breaches as $b) {
|
|
$title = strtoupper($b['metric']) . " {$b['level']} on {$server['name']}";
|
|
$message = "{$server['name']}: {$b['metric']} at {$b['current']}% ({$b['level']} threshold: {$b['threshold']}%)";
|
|
foreach ($userIds as $uid) {
|
|
$notificationModel->create([
|
|
'user_id' => (int) $uid,
|
|
'title' => $title,
|
|
'message' => $message,
|
|
'type' => $b['level'],
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->view->json(['success' => true]);
|
|
}
|
|
|
|
public function refreshAllMetrics(): void
|
|
{
|
|
$user = $this->authenticateRequest();
|
|
|
|
$monitoringService = new MonitoringService();
|
|
$results = $monitoringService->collectAllMetrics();
|
|
|
|
$this->view->json([
|
|
'success' => true,
|
|
'data' => $results,
|
|
]);
|
|
}
|
|
|
|
public function register(): void
|
|
{
|
|
$input = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
|
|
|
$validator = new Validator();
|
|
$rules = [
|
|
'username' => 'required|min:3|max:50',
|
|
'email' => 'required|email',
|
|
'password' => 'required|min:8',
|
|
'confirm_password' => 'required',
|
|
];
|
|
|
|
if (!$validator->validate($input, $rules)) {
|
|
$this->view->json([
|
|
'success' => false,
|
|
'error' => $validator->getFirstError(),
|
|
], 400);
|
|
return;
|
|
}
|
|
|
|
if ($input['password'] !== $input['confirm_password']) {
|
|
$this->view->json([
|
|
'success' => false,
|
|
'error' => 'Passwords do not match.',
|
|
], 400);
|
|
return;
|
|
}
|
|
|
|
$userModel = new User();
|
|
|
|
$existing = $userModel->findByUsername($input['username']);
|
|
if ($existing) {
|
|
$this->view->json([
|
|
'success' => false,
|
|
'error' => 'Username is already taken.',
|
|
], 409);
|
|
return;
|
|
}
|
|
|
|
$existingEmail = $userModel->findByEmail($input['email']);
|
|
if ($existingEmail) {
|
|
$this->view->json([
|
|
'success' => false,
|
|
'error' => 'Email is already registered.',
|
|
], 409);
|
|
return;
|
|
}
|
|
|
|
$userId = $userModel->create([
|
|
'username' => $input['username'],
|
|
'email' => $input['email'],
|
|
'password' => $input['password'],
|
|
'role' => 'admin',
|
|
'is_active' => 1,
|
|
]);
|
|
|
|
$user = $userModel->findById($userId);
|
|
|
|
$auditService = new AuditService();
|
|
$auditService->log('user_registered', 'User registered via API', $userId);
|
|
|
|
$this->view->json([
|
|
'success' => true,
|
|
'data' => [
|
|
'token' => $user['api_token'],
|
|
'user' => [
|
|
'id' => (int) $user['id'],
|
|
'username' => $user['username'],
|
|
'email' => $user['email'],
|
|
'role' => $user['role'],
|
|
],
|
|
],
|
|
], 201);
|
|
}
|
|
|
|
public function profile(): void
|
|
{
|
|
$user = $this->authenticateRequest();
|
|
|
|
$this->view->json([
|
|
'success' => true,
|
|
'data' => [
|
|
'id' => (int) $user['id'],
|
|
'username' => $user['username'],
|
|
'email' => $user['email'],
|
|
'role' => $user['role'],
|
|
'created_at' => $user['created_at'],
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function updateProfile(): void
|
|
{
|
|
$user = $this->authenticateRequest();
|
|
|
|
$input = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
|
|
|
$validator = new Validator();
|
|
$rules = [
|
|
'email' => 'required|email',
|
|
];
|
|
|
|
if (!$validator->validate($input, $rules)) {
|
|
$this->view->json([
|
|
'success' => false,
|
|
'error' => $validator->getFirstError(),
|
|
], 400);
|
|
return;
|
|
}
|
|
|
|
$updateData = ['email' => $input['email']];
|
|
|
|
if (!empty($input['new_password'])) {
|
|
if (strlen($input['new_password']) < 8) {
|
|
$this->view->json([
|
|
'success' => false,
|
|
'error' => 'New password must be at least 8 characters.',
|
|
], 400);
|
|
return;
|
|
}
|
|
if (empty($input['current_password'])) {
|
|
$this->view->json([
|
|
'success' => false,
|
|
'error' => 'Current password is required to set a new password.',
|
|
], 400);
|
|
return;
|
|
}
|
|
if ($input['new_password'] !== ($input['confirm_password'] ?? '')) {
|
|
$this->view->json([
|
|
'success' => false,
|
|
'error' => 'New passwords do not match.',
|
|
], 400);
|
|
return;
|
|
}
|
|
|
|
$userModel = new User();
|
|
$fullUser = $userModel->findById((int) $user['id']);
|
|
if (!$fullUser || !(new \ServerManager\Core\Security())->verifyPassword($input['current_password'], $fullUser['password_hash'])) {
|
|
$this->view->json([
|
|
'success' => false,
|
|
'error' => 'Current password is incorrect.',
|
|
], 403);
|
|
return;
|
|
}
|
|
|
|
$updateData['password'] = $input['new_password'];
|
|
}
|
|
|
|
$userModel = new User();
|
|
$userModel->update((int) $user['id'], $updateData);
|
|
|
|
$auditService = new AuditService();
|
|
$auditService->log('profile_updated', 'Profile updated via API', (int) $user['id']);
|
|
|
|
$this->view->json([
|
|
'success' => true,
|
|
'message' => 'Profile updated successfully.',
|
|
]);
|
|
}
|
|
|
|
public function appVersion(): void
|
|
{
|
|
$db = \ServerManager\Core\Database::getInstance();
|
|
|
|
try {
|
|
$configs = $db->fetchAll('SELECT * FROM app_config');
|
|
} catch (\Throwable $e) {
|
|
$this->view->json([
|
|
'success' => false,
|
|
'error' => 'Database error',
|
|
], 500);
|
|
return;
|
|
}
|
|
|
|
$config = [];
|
|
foreach ($configs as $row) {
|
|
$config[$row['key']] = $row['value'];
|
|
}
|
|
|
|
$this->view->json([
|
|
'success' => true,
|
|
'data' => [
|
|
'version' => $config['app_version'] ?? '1.0.0',
|
|
'apk_url' => $config['apk_url'] ?? '/sysadmin.apk',
|
|
'force_update' => ($config['force_update'] ?? '0') === '1',
|
|
'release_notes' => $config['release_notes'] ?? '',
|
|
],
|
|
]);
|
|
}
|
|
|
|
public function unreadCount(): void
|
|
{
|
|
$user = $this->authenticateRequest();
|
|
|
|
$notificationModel = new \ServerManager\Models\Notification();
|
|
$count = $notificationModel->getUnreadCount((int) $user['id']);
|
|
|
|
$this->view->json([
|
|
'success' => true,
|
|
'unread_count' => $count,
|
|
]);
|
|
}
|
|
|
|
public function notifications(): void
|
|
{
|
|
$user = $this->authenticateRequest();
|
|
|
|
$notificationModel = new \ServerManager\Models\Notification();
|
|
$page = (int) ($_GET['page'] ?? 1);
|
|
$perPage = (int) ($_GET['per_page'] ?? 20);
|
|
|
|
$result = $notificationModel->getForUser((int) $user['id'], $page, $perPage);
|
|
$unreadCount = $notificationModel->getUnreadCount((int) $user['id']);
|
|
|
|
$this->view->json([
|
|
'success' => true,
|
|
'data' => $result['data'],
|
|
'pagination' => [
|
|
'page' => $result['page'],
|
|
'per_page' => $result['per_page'],
|
|
'total' => $result['total'],
|
|
'total_pages' => $result['total_pages'],
|
|
],
|
|
'unread_count' => $unreadCount,
|
|
]);
|
|
}
|
|
|
|
public function markNotificationRead(int $id): void
|
|
{
|
|
$user = $this->authenticateRequest();
|
|
|
|
$notificationModel = new \ServerManager\Models\Notification();
|
|
$notificationModel->markAsRead($id, (int) $user['id']);
|
|
|
|
$this->view->json(['success' => true]);
|
|
}
|
|
|
|
public function markAllNotificationsRead(): void
|
|
{
|
|
$user = $this->authenticateRequest();
|
|
|
|
$notificationModel = new \ServerManager\Models\Notification();
|
|
$notificationModel->markAllAsRead((int) $user['id']);
|
|
|
|
$this->view->json(['success' => true]);
|
|
}
|
|
|
|
public function registerFcmToken(): void
|
|
{
|
|
$user = $this->authenticateRequest();
|
|
|
|
$input = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
|
$token = $input['token'] ?? '';
|
|
|
|
if (empty($token)) {
|
|
$this->view->json(['error' => 'Token is required'], 400);
|
|
}
|
|
|
|
$db = \ServerManager\Core\Database::getInstance();
|
|
$existing = $db->fetch(
|
|
'SELECT id FROM user_fcm_tokens WHERE user_id = ? AND token = ?',
|
|
[(int) $user['id'], $token]
|
|
);
|
|
|
|
if (!$existing) {
|
|
$db->insert('user_fcm_tokens', [
|
|
'user_id' => (int) $user['id'],
|
|
'token' => $token,
|
|
'created_at' => date('Y-m-d H:i:s'),
|
|
]);
|
|
}
|
|
|
|
$this->view->json(['success' => true]);
|
|
}
|
|
|
|
public function serverServices(int $id): void
|
|
{
|
|
$user = $this->authenticateRequest();
|
|
|
|
$serverModel = new Server();
|
|
if (!$serverModel->canView($id, (int) $user['id'])) {
|
|
$this->view->json(['error' => 'Server not found'], 404);
|
|
}
|
|
|
|
$server = $serverModel->findById($id);
|
|
if (!$server) {
|
|
$this->view->json(['error' => 'Server not found'], 404);
|
|
}
|
|
|
|
try {
|
|
$ssh = new SSHService();
|
|
if (!$ssh->connect($server)) {
|
|
throw new \RuntimeException('SSH connection failed');
|
|
}
|
|
|
|
$output = $ssh->exec('systemctl list-units --type=service --all --no-pager --no-legend 2>/dev/null');
|
|
$ssh->disconnect();
|
|
|
|
$services = [];
|
|
if ($output['exit_status'] === 0) {
|
|
foreach (explode("\n", trim($output['output'])) as $line) {
|
|
if (preg_match('/^\s*([^\s]+)\s+([^\s]+)\s+([^\s]+)\s+([^\s]+)/', $line, $m)) {
|
|
$services[] = [
|
|
'name' => trim($m[1]),
|
|
'load' => trim($m[2]),
|
|
'status' => trim($m[3]),
|
|
'description' => trim($m[4] ?? ''),
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
$this->view->json([
|
|
'success' => true,
|
|
'data' => $services,
|
|
]);
|
|
} catch (\Throwable $e) {
|
|
$this->view->json([
|
|
'success' => false,
|
|
'error' => $e->getMessage(),
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
public function serverReboot(int $id): void
|
|
{
|
|
$this->executeServerAction($id, 'reboot', '/sbin/reboot');
|
|
}
|
|
|
|
public function serverShutdown(int $id): void
|
|
{
|
|
$this->executeServerAction($id, 'shutdown', '/sbin/shutdown -h now');
|
|
}
|
|
|
|
public function serverTestConnection(int $id): void
|
|
{
|
|
$user = $this->authenticateRequest();
|
|
|
|
$serverModel = new Server();
|
|
if (!$serverModel->canView($id, (int) $user['id'])) {
|
|
$this->view->json(['error' => 'Server not found'], 404);
|
|
}
|
|
|
|
$server = $serverModel->findById($id);
|
|
if (!$server) {
|
|
$this->view->json(['error' => 'Server not found'], 404);
|
|
}
|
|
|
|
try {
|
|
$ssh = new SSHService();
|
|
$connected = $ssh->connect($server);
|
|
if ($connected) {
|
|
$ssh->disconnect();
|
|
}
|
|
$this->view->json([
|
|
'success' => true,
|
|
'data' => ['connected' => $connected],
|
|
]);
|
|
} catch (\Throwable $e) {
|
|
$this->view->json([
|
|
'success' => true,
|
|
'data' => ['connected' => false, 'error' => $e->getMessage()],
|
|
]);
|
|
}
|
|
}
|
|
|
|
public function serverServiceAction(int $id): void
|
|
{
|
|
$user = $this->authenticateRequest();
|
|
|
|
$serverModel = new Server();
|
|
if (!$serverModel->canManage($id, (int) $user['id'])) {
|
|
$this->view->json(['error' => 'Forbidden'], 403);
|
|
}
|
|
|
|
$server = $serverModel->findById($id);
|
|
if (!$server) {
|
|
$this->view->json(['error' => 'Server not found'], 404);
|
|
}
|
|
|
|
$input = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
|
$service = (string) ($input['service'] ?? '');
|
|
$action = (string) ($input['action'] ?? '');
|
|
$allowed = ['start', 'stop', 'restart', 'reload'];
|
|
|
|
if ($service === '' || !in_array($action, $allowed, true)) {
|
|
$this->view->json(['error' => 'Invalid service or action'], 400);
|
|
}
|
|
|
|
$sanitized = preg_replace('/[^a-zA-Z0-9\-_.]/', '', $service);
|
|
if ($sanitized === '') {
|
|
$this->view->json(['error' => 'Invalid service name'], 400);
|
|
}
|
|
|
|
try {
|
|
$ssh = new SSHService();
|
|
if (!$ssh->connect($server)) {
|
|
throw new \RuntimeException('SSH connection failed');
|
|
}
|
|
|
|
$result = $ssh->exec("sudo systemctl {$action} {$sanitized} 2>&1");
|
|
$ssh->disconnect();
|
|
|
|
$exitCode = isset($result['exit_status']) ? (int) $result['exit_status'] : -1;
|
|
|
|
$auditService = new AuditService();
|
|
$auditService->logServerAction($id, "service_{$action}", "{$sanitized} {$action}");
|
|
|
|
if ($exitCode === 0) {
|
|
$this->view->json([
|
|
'success' => true,
|
|
'data' => [
|
|
'message' => "Service '{$sanitized}' {$action}ed.",
|
|
'output' => trim($result['output'] ?? ''),
|
|
],
|
|
]);
|
|
} else {
|
|
$this->view->json([
|
|
'success' => false,
|
|
'error' => "Failed to {$action} '{$sanitized}'. Exit code: {$exitCode}",
|
|
'data' => ['output' => trim($result['output'] ?? '')],
|
|
], 500);
|
|
}
|
|
} catch (\Throwable $e) {
|
|
$this->view->json([
|
|
'success' => false,
|
|
'error' => $e->getMessage(),
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
private function executeServerAction(int $id, string $action, string $command): void
|
|
{
|
|
$user = $this->authenticateRequest();
|
|
|
|
$serverModel = new Server();
|
|
if (!$serverModel->canManage($id, (int) $user['id'])) {
|
|
$this->view->json(['error' => 'Forbidden'], 403);
|
|
}
|
|
|
|
$server = $serverModel->findById($id);
|
|
if (!$server) {
|
|
$this->view->json(['error' => 'Server not found'], 404);
|
|
}
|
|
|
|
try {
|
|
$ssh = new SSHService();
|
|
if (!$ssh->connect($server)) {
|
|
throw new \RuntimeException('SSH connection failed');
|
|
}
|
|
$result = $ssh->exec($command);
|
|
$ssh->disconnect();
|
|
|
|
$auditService = new AuditService();
|
|
$auditService->log("server_{$action}", 'server', $id, ['name' => $server['name']]);
|
|
|
|
$this->view->json([
|
|
'success' => true,
|
|
'message' => ucfirst($action) . ' command executed.',
|
|
'data' => [
|
|
'output' => $result['output'],
|
|
'exit_status' => $result['exit_status'],
|
|
],
|
|
]);
|
|
} catch (\Throwable $e) {
|
|
$this->view->json([
|
|
'success' => false,
|
|
'error' => $e->getMessage(),
|
|
], 500);
|
|
}
|
|
}
|
|
|
|
public function login(): void
|
|
{
|
|
$input = json_decode(file_get_contents('php://input'), true) ?? $_POST;
|
|
|
|
$validator = new Validator();
|
|
$rules = [
|
|
'username' => 'required',
|
|
'password' => 'required|min:6',
|
|
];
|
|
|
|
if (!$validator->validate($input, $rules)) {
|
|
$this->view->json([
|
|
'success' => false,
|
|
'error' => $validator->getFirstError(),
|
|
], 400);
|
|
return;
|
|
}
|
|
|
|
$userModel = new User();
|
|
$user = $userModel->authenticate($input['username'], $input['password']);
|
|
|
|
if (!$user) {
|
|
$auditService = new AuditService();
|
|
$auditService->logLogin($input['username'], false, 'Invalid credentials');
|
|
$this->view->json([
|
|
'success' => false,
|
|
'error' => 'Invalid username or password.',
|
|
], 401);
|
|
return;
|
|
}
|
|
|
|
$token = $user['api_token'];
|
|
if (empty($token)) {
|
|
$security = new \ServerManager\Core\Security();
|
|
$token = $security->generateApiToken();
|
|
$userModel->update((int) $user['id'], ['api_token' => $token]);
|
|
}
|
|
|
|
$auditService = new AuditService();
|
|
$auditService->logLogin($input['username'], true);
|
|
|
|
$this->view->json([
|
|
'success' => true,
|
|
'data' => [
|
|
'token' => $token,
|
|
'user' => [
|
|
'id' => (int) $user['id'],
|
|
'username' => $user['username'],
|
|
'email' => $user['email'],
|
|
'role' => $user['role'],
|
|
],
|
|
],
|
|
]);
|
|
}
|
|
}
|