$scenarios, 'section' => 'scenarios' ]); } /** * View single scenario details * GET /admin/scenario/:id */ public function viewScenario($id) { requireAdmin(); $scenario = InstallProtocolManager::getById((int)$id); if (!$scenario) { http_response_code(404); View::render('404.twig'); return; } $definition = $scenario['definition'] ?? []; View::render('settings/scenario_view.twig', [ 'scenario' => $scenario, 'definition' => $definition, 'section' => 'scenarios' ]); } /** * Show form to create new scenario * GET /admin/scenario/create */ public function createScenarioForm() { requireAdmin(); $templateDefinition = [ 'engine' => 'shell', 'metadata' => [ 'container_name' => 'custom-container', 'config_path' => '/opt/amnezia/custom' ], 'scripts' => [ 'detect' => 'echo \'{"status":"absent","message":"Custom protocol not found"}\'', 'install' => 'echo \'{"success":true,"message":"Custom protocol installed"}\'', 'restore' => 'echo \'{"success":true,"message":"Custom protocol restored"}\'' ] ]; View::render('settings/scenario_form.twig', [ 'scenario' => null, 'templateDefinition' => json_encode($templateDefinition, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), 'section' => 'scenarios' ]); } /** * Show form to edit existing scenario * GET /admin/scenario/:id/edit */ public function editScenarioForm($id) { requireAdmin(); $scenario = InstallProtocolManager::getById((int)$id); if (!$scenario) { http_response_code(404); View::render('404.twig'); return; } $definitionJson = json_encode($scenario['definition'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); View::render('settings/scenario_form.twig', [ 'scenario' => $scenario, 'templateDefinition' => $definitionJson, 'section' => 'scenarios' ]); } /** * Save scenario (create or update) * POST /admin/scenario */ public function saveScenario() { requireAdmin(); $data = $_POST; // Validate required fields if (empty($data['slug']) || empty($data['name'])) { header('Content-Type: application/json'); http_response_code(400); echo json_encode([ 'success' => false, 'message' => 'Slug and name are required' ]); return; } // Parse definition JSON if (!empty($data['definition'])) { if (is_string($data['definition'])) { $definition = json_decode($data['definition'], true); if (json_last_error() !== JSON_ERROR_NONE) { header('Content-Type: application/json'); http_response_code(400); echo json_encode([ 'success' => false, 'message' => 'Invalid JSON in definition: ' . json_last_error_msg() ]); return; } } else { $definition = $data['definition']; } } else { $definition = []; } // Validate definition structure if (empty($definition['engine'])) { header('Content-Type: application/json'); http_response_code(400); echo json_encode([ 'success' => false, 'message' => 'Definition must have "engine" field' ]); return; } $saveData = [ 'slug' => $data['slug'], 'name' => $data['name'], 'description' => $data['description'] ?? null, 'definition' => $definition, 'is_active' => isset($data['is_active']) ? (int)$data['is_active'] : 1 ]; if (!empty($data['id'])) { $saveData['id'] = (int)$data['id']; } try { $id = InstallProtocolManager::save($saveData); header('Content-Type: application/json'); echo json_encode([ 'success' => true, 'id' => $id, 'message' => 'Scenario saved successfully', 'redirect' => '/admin/scenarios' ]); } catch (Exception $e) { header('Content-Type: application/json'); http_response_code(500); echo json_encode([ 'success' => false, 'message' => 'Error saving scenario: ' . $e->getMessage() ]); } } /** * Delete scenario * DELETE /admin/scenario/:id or POST /admin/scenario/:id/delete */ public function deleteScenario($id) { requireAdmin(); $id = (int)$id; // Prevent deletion of default scenario $scenario = InstallProtocolManager::getById($id); if (!$scenario) { header('Content-Type: application/json'); http_response_code(404); echo json_encode([ 'success' => false, 'message' => 'Scenario not found' ]); return; } if ($scenario['slug'] === InstallProtocolManager::getDefaultSlug()) { header('Content-Type: application/json'); http_response_code(400); echo json_encode([ 'success' => false, 'message' => 'Cannot delete default scenario' ]); return; } try { InstallProtocolManager::delete($id); header('Content-Type: application/json'); echo json_encode([ 'success' => true, 'message' => 'Scenario deleted successfully', 'redirect' => '/admin/scenarios' ]); } catch (Exception $e) { header('Content-Type: application/json'); http_response_code(500); echo json_encode([ 'success' => false, 'message' => 'Error deleting scenario: ' . $e->getMessage() ]); } } /** * Test scenario script (detection) * POST /admin/scenario/:id/test */ public function testScenario($id) { requireAdmin(); $scenario = InstallProtocolManager::getById((int)$id); if (!$scenario) { header('Content-Type: application/json'); http_response_code(404); echo json_encode([ 'success' => false, 'message' => 'Scenario not found' ]); return; } $serverId = (int)($_POST['server_id'] ?? 0); if (!$serverId) { header('Content-Type: application/json'); http_response_code(400); echo json_encode([ 'success' => false, 'message' => 'Server ID required' ]); return; } $server = new VpnServer($serverId); if (!$server->getData()) { header('Content-Type: application/json'); http_response_code(404); echo json_encode([ 'success' => false, 'message' => 'Server not found' ]); return; } try { // Test SSH connection first if (!$server->testConnection()) { throw new Exception('SSH connection to server failed'); } // Run detection script $result = InstallProtocolManager::runDetection($server, $scenario); header('Content-Type: application/json'); echo json_encode([ 'success' => true, 'result' => $result ]); } catch (Exception $e) { header('Content-Type: application/json'); http_response_code(500); echo json_encode([ 'success' => false, 'message' => 'Error testing scenario: ' . $e->getMessage() ]); } } /** * Export scenario as JSON * GET /admin/scenario/:id/export */ public function exportScenario($id) { requireAdmin(); $scenario = InstallProtocolManager::getById((int)$id); if (!$scenario) { http_response_code(404); return; } header('Content-Type: application/json'); header('Content-Disposition: attachment; filename="scenario-' . $scenario['slug'] . '-' . date('Y-m-d') . '.json"'); // Remove database IDs from export unset($scenario['id']); echo json_encode($scenario, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); } /** * Import scenario from JSON * POST /admin/scenario/import */ public function importScenario() { requireAdmin(); $file = $_FILES['file'] ?? null; if (!$file || $file['error'] !== UPLOAD_ERR_OK) { header('Content-Type: application/json'); http_response_code(400); echo json_encode([ 'success' => false, 'message' => 'No file provided' ]); return; } $contents = file_get_contents($file['tmp_name']); $scenario = json_decode($contents, true); if (!is_array($scenario)) { header('Content-Type: application/json'); http_response_code(400); echo json_encode([ 'success' => false, 'message' => 'Invalid JSON file' ]); return; } try { // Remove ID so it creates a new entry unset($scenario['id']); // Ensure required fields exist if (empty($scenario['slug']) || empty($scenario['name'])) { throw new Exception('Imported scenario must have slug and name'); } $id = InstallProtocolManager::save($scenario); header('Content-Type: application/json'); echo json_encode([ 'success' => true, 'id' => $id, 'message' => 'Scenario imported successfully', 'redirect' => '/admin/scenarios' ]); } catch (Exception $e) { header('Content-Type: application/json'); http_response_code(500); echo json_encode([ 'success' => false, 'message' => 'Error importing scenario: ' . $e->getMessage() ]); } } }