Files
amneziavpnphp/public/index.php
T
infosave2007 fc39346240 feat: добавлена возможность импорта из wg-easy и 3x-ui панелей
Основные изменения:
- Создан класс PanelImporter для парсинга и импорта клиентов
- Добавлена поддержка wg-easy (db.json)
- Добавлена поддержка 3x-ui (export JSON)
- Создана таблица panel_imports для отслеживания истории
- Добавлен UI для загрузки backup файлов при создании сервера
- Добавлены API endpoints: POST /api/servers/{id}/import и GET /api/servers/{id}/imports
- Автоматический импорт после деплоя сервера
- Переводы на всех 6 языках (EN, RU, ES, DE, FR, ZH)
- Обновлена документация в README

Функционал:
- Импорт клиентов с сохранением ключей и IP (wg-easy)
- Импорт клиентов с автогенерацией ключей (3x-ui)
- Поддержка экспирации и лимитов трафика из исходных панелей
- История импортов с информацией о количестве клиентов
- Обработка ошибок с детальным логированием
2025-11-08 12:40:43 +03:00

1723 lines
49 KiB
PHP

<?php
/**
* Amnezia VPN Web Panel
* Main entry point
*/
session_name(getenv('SESSION_NAME') ?: 'amnezia_panel_session');
session_start();
// Load dependencies
require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../inc/Config.php';
require_once __DIR__ . '/../inc/DB.php';
require_once __DIR__ . '/../inc/Auth.php';
require_once __DIR__ . '/../inc/Router.php';
require_once __DIR__ . '/../inc/View.php';
require_once __DIR__ . '/../inc/VpnServer.php';
require_once __DIR__ . '/../inc/VpnClient.php';
require_once __DIR__ . '/../inc/Translator.php';
require_once __DIR__ . '/../inc/JWT.php';
require_once __DIR__ . '/../inc/PanelImporter.php';
// Load environment configuration
Config::load(__DIR__ . '/../.env');
// Test database connection
try {
DB::conn();
} catch (Throwable $e) {
die('Database connection error: ' . $e->getMessage());
}
// Seed admin user if not exists
try {
$adminEmail = Config::get('ADMIN_EMAIL');
$adminPass = Config::get('ADMIN_PASSWORD');
if ($adminEmail && $adminPass) {
Auth::seedAdmin($adminEmail, $adminPass);
}
} catch (Throwable $e) {
// Ignore errors
}
// Initialize translator
Translator::init();
// Initialize template engine
$user = Auth::user();
$appName = Config::get('APP_NAME', 'Amnezia VPN Panel');
View::init(__DIR__ . '/../templates', [
'app_name' => $appName,
'user' => $user,
'current_language' => Translator::getCurrentLanguage(),
'languages' => Translator::getSupportedLanguages(),
'current_uri' => $_SERVER['REQUEST_URI'] ?? '/dashboard',
't' => function($key, $params = []) {
return Translator::t($key, $params);
}
]);
// Helper function for redirects
function redirect(string $to): void {
header('Location: ' . $to);
exit;
}
// Helper function to require authentication
function requireAuth(): void {
if (!Auth::check()) {
redirect('/login');
}
}
// Helper function to require admin
function requireAdmin(): void {
requireAuth();
if (!Auth::isAdmin()) {
http_response_code(403);
echo 'Forbidden: Admin access required';
exit;
}
}
// Helper function to get authenticated user (JWT or session)
function getAuthUser(): ?array {
// Try JWT first
$token = JWT::getTokenFromHeader();
if ($token !== null) {
$user = JWT::verify($token);
if ($user !== null) {
return $user;
}
}
// Fall back to session
if (Auth::check()) {
return Auth::user();
}
return null;
}
// Helper function to require authentication (JWT or session) for API
function requireApiAuth(): ?array {
$user = getAuthUser();
if ($user === null) {
http_response_code(401);
header('Content-Type: application/json');
echo json_encode(['error' => 'Authentication required']);
return null;
}
return $user;
}
/**
* PUBLIC ROUTES
*/
// Home page
Router::get('/', function () {
if (!Auth::check()) {
redirect('/login');
}
redirect('/dashboard');
});
// Login page
Router::get('/login', function () {
if (Auth::check()) {
redirect('/dashboard');
}
View::render('login.twig');
});
Router::post('/login', function () {
$email = trim($_POST['email'] ?? '');
$password = $_POST['password'] ?? '';
if (Auth::login($email, $password)) {
redirect('/dashboard');
}
View::render('login.twig', ['error' => 'Invalid credentials']);
});
// Register page
Router::get('/register', function () {
if (Auth::check()) {
redirect('/dashboard');
}
View::render('register.twig');
});
Router::post('/register', function () {
$name = trim($_POST['name'] ?? '');
$email = trim($_POST['email'] ?? '');
$password = $_POST['password'] ?? '';
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
View::render('register.twig', ['error' => 'Invalid email address']);
return;
}
if (strlen($password) < 6) {
View::render('register.twig', ['error' => 'Password must be at least 6 characters']);
return;
}
try {
$success = Auth::register($name, $email, $password);
if ($success) {
Auth::login($email, $password);
redirect('/dashboard');
}
} catch (Throwable $e) {
// Email already exists or other error
}
View::render('register.twig', ['error' => 'Registration failed. Email may already be in use.']);
});
// Logout
Router::get('/logout', function () {
Auth::logout();
redirect('/login');
});
/**
* AUTHENTICATED ROUTES
*/
// Dashboard
Router::get('/dashboard', function () {
requireAuth();
$user = Auth::user();
// Get user's servers
$servers = VpnServer::listByUser($user['id']);
// Get user's clients
$clients = VpnClient::listByUser($user['id']);
View::render('dashboard.twig', [
'servers' => $servers,
'clients' => $clients,
]);
});
// Servers list
Router::get('/servers', function () {
requireAuth();
$user = Auth::user();
$servers = Auth::isAdmin()
? VpnServer::listAll()
: VpnServer::listByUser($user['id']);
View::render('servers/index.twig', ['servers' => $servers]);
});
// Create server page
Router::get('/servers/create', function () {
requireAuth();
View::render('servers/create.twig');
});
// Create server action
Router::post('/servers/create', function () {
requireAuth();
$user = Auth::user();
$name = trim($_POST['name'] ?? '');
$host = trim($_POST['host'] ?? '');
$port = (int)($_POST['port'] ?? 22);
$username = trim($_POST['username'] ?? 'root');
$password = $_POST['password'] ?? '';
if (empty($name) || empty($host) || empty($password)) {
View::render('servers/create.twig', ['error' => 'All fields are required']);
return;
}
try {
$serverId = VpnServer::create([
'user_id' => $user['id'],
'name' => $name,
'host' => $host,
'port' => $port,
'username' => $username,
'password' => $password,
]);
// Handle import if enabled
if (!empty($_POST['enable_import']) && !empty($_POST['panel_type']) && isset($_FILES['backup_file'])) {
$panelType = $_POST['panel_type'];
if (in_array($panelType, ['wg-easy', '3x-ui']) && $_FILES['backup_file']['error'] === UPLOAD_ERR_OK) {
// Store import info in session for processing after deployment
$_SESSION['pending_import'] = [
'server_id' => $serverId,
'panel_type' => $panelType,
'backup_file' => $_FILES['backup_file']['tmp_name'],
'backup_name' => $_FILES['backup_file']['name']
];
}
}
redirect('/servers/' . $serverId . '/deploy');
} catch (Exception $e) {
View::render('servers/create.twig', ['error' => $e->getMessage()]);
}
});
// Delete server action
Router::post('/servers/{id}/delete', function ($params) {
requireAuth();
$user = Auth::user();
$serverId = (int)$params['id'];
try {
$server = new VpnServer($serverId);
$serverData = $server->getData();
// Check ownership or admin
if ($serverData['user_id'] != $user['id'] && !Auth::isAdmin()) {
http_response_code(403);
echo 'Forbidden';
return;
}
$server->delete();
$_SESSION['success_message'] = 'Server deleted successfully';
redirect('/servers');
} catch (Exception $e) {
$_SESSION['error_message'] = $e->getMessage();
redirect('/servers');
}
});
// Deploy server page
Router::get('/servers/{id}/deploy', function ($params) {
requireAuth();
$serverId = (int)$params['id'];
try {
$server = new VpnServer($serverId);
$serverData = $server->getData();
// Check ownership
$user = Auth::user();
if ($serverData['user_id'] != $user['id'] && !Auth::isAdmin()) {
http_response_code(403);
echo 'Forbidden';
return;
}
View::render('servers/deploy.twig', ['server' => $serverData]);
} catch (Exception $e) {
http_response_code(404);
echo 'Server not found';
}
});
// Deploy server action (AJAX)
Router::post('/servers/{id}/deploy', function ($params) {
requireAuth();
header('Content-Type: application/json');
$serverId = (int)$params['id'];
try {
$server = new VpnServer($serverId);
$serverData = $server->getData();
// Check ownership
$user = Auth::user();
if ($serverData['user_id'] != $user['id'] && !Auth::isAdmin()) {
http_response_code(403);
echo json_encode(['error' => 'Forbidden']);
return;
}
$result = $server->deploy();
echo json_encode($result);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
});
// View server
Router::get('/servers/{id}', function ($params) {
requireAuth();
$serverId = (int)$params['id'];
try {
$server = new VpnServer($serverId);
$serverData = $server->getData();
// Check ownership
$user = Auth::user();
if ($serverData['user_id'] != $user['id'] && !Auth::isAdmin()) {
http_response_code(403);
echo 'Forbidden';
return;
}
// Get clients for this server
$clients = VpnClient::listByServer($serverId);
// Check for pending import
$importMessage = null;
if (!empty($_SESSION['pending_import']) && $_SESSION['pending_import']['server_id'] == $serverId) {
$pendingImport = $_SESSION['pending_import'];
// Only process import if server is active
if ($serverData['status'] === 'active') {
try {
$backupContent = file_get_contents($pendingImport['backup_file']);
$importer = new PanelImporter($serverId, $user['id'], $pendingImport['panel_type']);
$importer->parseBackupFile($backupContent);
$result = $importer->import();
if ($result['success']) {
$importMessage = [
'type' => 'success',
'text' => "Successfully imported {$result['imported_count']} clients"
];
}
// Clean up
@unlink($pendingImport['backup_file']);
unset($_SESSION['pending_import']);
} catch (Exception $e) {
$importMessage = [
'type' => 'error',
'text' => 'Import failed: ' . $e->getMessage()
];
unset($_SESSION['pending_import']);
}
// Refresh clients list after import
$clients = VpnClient::listByServer($serverId);
}
}
View::render('servers/view.twig', [
'server' => $serverData,
'clients' => $clients,
'import_message' => $importMessage,
]);
} catch (Exception $e) {
error_log('Server view error: ' . $e->getMessage() . ' at ' . $e->getFile() . ':' . $e->getLine());
http_response_code(404);
echo 'Server not found: ' . htmlspecialchars($e->getMessage());
}
});
// Delete server
Router::post('/servers/{id}/delete', function ($params) {
requireAuth();
$serverId = (int)$params['id'];
try {
$server = new VpnServer($serverId);
$serverData = $server->getData();
// Check ownership
$user = Auth::user();
if ($serverData['user_id'] != $user['id'] && !Auth::isAdmin()) {
http_response_code(403);
echo 'Forbidden';
return;
}
$server->delete();
redirect('/servers');
} catch (Exception $e) {
redirect('/servers');
}
});
// Create client for server
Router::post('/servers/{id}/clients/create', function ($params) {
requireAuth();
$serverId = (int)$params['id'];
$clientName = trim($_POST['name'] ?? '');
// Handle expiration: either from dropdown (days) or custom input (seconds)
$expiresInDays = null;
if (!empty($_POST['expires_in_seconds'])) {
// Convert seconds to days (round up)
$expiresInDays = (int)ceil((int)$_POST['expires_in_seconds'] / 86400);
} elseif (!empty($_POST['expires_in_days']) && $_POST['expires_in_days'] !== 'custom') {
$expiresInDays = (int)$_POST['expires_in_days'];
}
// Handle traffic limit: either from dropdown (GB) or custom input (MB)
$trafficLimitBytes = null;
if (!empty($_POST['traffic_limit_mb'])) {
// Convert MB to bytes
$trafficLimitBytes = (int)((float)$_POST['traffic_limit_mb'] * 1048576);
} elseif (!empty($_POST['traffic_limit_gb']) && $_POST['traffic_limit_gb'] !== 'custom') {
// Convert GB to bytes
$trafficLimitBytes = (int)((float)$_POST['traffic_limit_gb'] * 1073741824);
}
if (empty($clientName)) {
redirect('/servers/' . $serverId . '?error=Client+name+is+required');
return;
}
try {
$server = new VpnServer($serverId);
$serverData = $server->getData();
// Check ownership
$user = Auth::user();
if ($serverData['user_id'] != $user['id'] && !Auth::isAdmin()) {
http_response_code(403);
echo 'Forbidden';
return;
}
$clientId = VpnClient::create($serverId, $user['id'], $clientName, $expiresInDays);
// Set traffic limit if specified
if ($trafficLimitBytes !== null && $trafficLimitBytes > 0) {
$client = new VpnClient($clientId);
$client->setTrafficLimit($trafficLimitBytes);
}
redirect('/clients/' . $clientId);
} catch (Exception $e) {
redirect('/servers/' . $serverId . '?error=' . urlencode($e->getMessage()));
}
});
// View client
Router::get('/clients/{id}', function ($params) {
requireAuth();
$clientId = (int)$params['id'];
try {
$client = new VpnClient($clientId);
$clientData = $client->getData();
// Check ownership
$user = Auth::user();
if ($clientData['user_id'] != $user['id'] && !Auth::isAdmin()) {
http_response_code(403);
echo 'Forbidden';
return;
}
View::render('clients/view.twig', ['client' => $clientData]);
} catch (Exception $e) {
http_response_code(404);
echo 'Client not found';
}
});
// Download client config
Router::get('/clients/{id}/download', function ($params) {
requireAuth();
$clientId = (int)$params['id'];
try {
$client = new VpnClient($clientId);
$clientData = $client->getData();
// Check ownership
$user = Auth::user();
if ($clientData['user_id'] != $user['id'] && !Auth::isAdmin()) {
http_response_code(403);
echo 'Forbidden';
return;
}
$config = $client->getConfig();
$filename = preg_replace('/[^a-zA-Z0-9_-]/', '_', $clientData['name']) . '.conf';
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Content-Length: ' . strlen($config));
echo $config;
} catch (Exception $e) {
http_response_code(404);
echo 'Client not found';
}
});
// Revoke client access
Router::post('/clients/{id}/revoke', function ($params) {
requireAuth();
$clientId = (int)$params['id'];
try {
$client = new VpnClient($clientId);
$clientData = $client->getData();
// Check ownership
$user = Auth::user();
if ($clientData['user_id'] != $user['id'] && !Auth::isAdmin()) {
http_response_code(403);
echo 'Forbidden';
return;
}
if ($client->revoke()) {
redirect('/servers/' . $clientData['server_id'] . '?success=Client+revoked');
} else {
redirect('/servers/' . $clientData['server_id'] . '?error=Failed+to+revoke+client');
}
} catch (Exception $e) {
redirect('/dashboard?error=' . urlencode($e->getMessage()));
}
});
// Restore client access
Router::post('/clients/{id}/restore', function ($params) {
requireAuth();
$clientId = (int)$params['id'];
try {
$client = new VpnClient($clientId);
$clientData = $client->getData();
// Check ownership
$user = Auth::user();
if ($clientData['user_id'] != $user['id'] && !Auth::isAdmin()) {
http_response_code(403);
echo 'Forbidden';
return;
}
if ($client->restore()) {
redirect('/servers/' . $clientData['server_id'] . '?success=Client+restored');
} else {
redirect('/servers/' . $clientData['server_id'] . '?error=Failed+to+restore+client');
}
} catch (Exception $e) {
redirect('/dashboard?error=' . urlencode($e->getMessage()));
}
});
// Delete client
Router::post('/clients/{id}/delete', function ($params) {
requireAuth();
$clientId = (int)$params['id'];
try {
$client = new VpnClient($clientId);
$clientData = $client->getData();
// Check ownership
$user = Auth::user();
if ($clientData['user_id'] != $user['id'] && !Auth::isAdmin()) {
http_response_code(403);
echo 'Forbidden';
return;
}
$serverId = $clientData['server_id'];
if ($client->delete()) {
redirect('/servers/' . $serverId . '?success=Client+deleted');
} else {
redirect('/servers/' . $serverId . '?error=Failed+to+delete+client');
}
} catch (Exception $e) {
redirect('/dashboard?error=' . urlencode($e->getMessage()));
}
});
// Sync client stats
Router::post('/clients/{id}/sync-stats', function ($params) {
requireAuth();
$clientId = (int)$params['id'];
header('Content-Type: application/json');
try {
$client = new VpnClient($clientId);
$clientData = $client->getData();
// Check ownership
$user = Auth::user();
if ($clientData['user_id'] != $user['id'] && !Auth::isAdmin()) {
http_response_code(403);
echo json_encode(['error' => 'Forbidden']);
return;
}
if ($client->syncStats()) {
// Reload client data
$client = new VpnClient($clientId);
$stats = $client->getFormattedStats();
echo json_encode(['success' => true, 'stats' => $stats]);
} else {
echo json_encode(['success' => false, 'error' => 'Failed to sync stats']);
}
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
});
// Sync all stats for server
Router::post('/servers/{id}/sync-stats', function ($params) {
requireAuth();
$serverId = (int)$params['id'];
header('Content-Type: application/json');
try {
$server = new VpnServer($serverId);
$serverData = $server->getData();
// Check ownership
$user = Auth::user();
if ($serverData['user_id'] != $user['id'] && !Auth::isAdmin()) {
http_response_code(403);
echo json_encode(['error' => 'Forbidden']);
return;
}
$synced = VpnClient::syncAllStatsForServer($serverId);
echo json_encode(['success' => true, 'synced' => $synced]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
});
/**
* API ROUTES (for Telegram bot integration)
*/
// API: Generate JWT token
Router::post('/api/auth/token', function () {
header('Content-Type: application/json');
$email = $_POST['email'] ?? '';
$password = $_POST['password'] ?? '';
if (empty($email) || empty($password)) {
http_response_code(400);
echo json_encode(['error' => 'Email and password are required']);
return;
}
$user = Auth::getUserByEmail($email);
if (!$user || !password_verify($password, $user['password_hash'])) {
http_response_code(401);
echo json_encode(['error' => 'Invalid credentials']);
return;
}
try {
$token = JWT::generate($user['id']);
echo json_encode([
'success' => true,
'token' => $token,
'type' => 'Bearer',
'expires_in' => 30 * 24 * 3600 // 30 days
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => 'Token generation failed']);
}
});
// API: Create persistent API token
Router::post('/api/tokens', function () {
header('Content-Type: application/json');
$user = JWT::requireAuth();
if (!$user) return;
$name = $_POST['name'] ?? 'API Token';
$expiresIn = isset($_POST['expires_in']) ? (int)$_POST['expires_in'] : 2592000; // 30 days default
try {
$tokenData = JWT::createApiToken($user['id'], $name, $expiresIn);
echo json_encode([
'success' => true,
'token' => $tokenData
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
});
// API: List user's API tokens
Router::get('/api/tokens', function () {
header('Content-Type: application/json');
$user = JWT::requireAuth();
if (!$user) return;
$stmt = DB::get()->prepare("
SELECT id, name, token, expires_at, created_at, last_used_at
FROM api_tokens
WHERE user_id = ? AND revoked_at IS NULL
ORDER BY created_at DESC
");
$stmt->execute([$user['id']]);
$tokens = $stmt->fetchAll();
// Don't expose full token in list
foreach ($tokens as &$token) {
$token['token'] = substr($token['token'], 0, 10) . '...';
}
echo json_encode(['tokens' => $tokens]);
});
// API: Revoke API token
Router::delete('/api/tokens/{id}', function ($params) {
header('Content-Type: application/json');
$user = JWT::requireAuth();
if (!$user) return;
try {
JWT::revokeApiToken($params['id'], $user['id']);
echo json_encode(['success' => true]);
} catch (Exception $e) {
http_response_code(404);
echo json_encode(['error' => $e->getMessage()]);
}
});
// API: List servers
Router::get('/api/servers', function () {
header('Content-Type: application/json');
$user = JWT::requireAuth();
if (!$user) return;
$servers = VpnServer::listByUser($user['id']);
echo json_encode(['servers' => $servers]);
});
// API: Create server
Router::post('/api/servers/create', function () {
header('Content-Type: application/json');
$user = JWT::requireAuth();
if (!$user) return;
$input = json_decode(file_get_contents('php://input'), true);
$name = trim($input['name'] ?? '');
$host = trim($input['host'] ?? '');
$port = (int)($input['port'] ?? 22);
$username = trim($input['username'] ?? 'root');
$password = $input['password'] ?? '';
if (empty($name) || empty($host) || empty($password)) {
http_response_code(400);
echo json_encode(['error' => 'Missing required fields: name, host, password']);
return;
}
try {
$serverId = VpnServer::create([
'user_id' => $user['id'],
'name' => $name,
'host' => $host,
'port' => $port,
'username' => $username,
'password' => $password,
]);
http_response_code(201);
echo json_encode([
'success' => true,
'server_id' => $serverId,
'message' => 'Server created successfully'
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
});
// API: Delete server
Router::delete('/api/servers/{id}/delete', function ($params) {
header('Content-Type: application/json');
$user = JWT::requireAuth();
// API: Import from existing panel
Router::post('/api/servers/{id}/import', function ($params) {
header('Content-Type: application/json');
$user = JWT::requireAuth();
if (!$user) return;
$serverId = (int)$params['id'];
// Validate server ownership
$server = VpnServer::getById($serverId);
if (!$server || $server['user_id'] != $user['id']) {
http_response_code(404);
echo json_encode(['error' => 'Server not found']);
return;
}
$panelType = $_POST['panel_type'] ?? '';
if (!in_array($panelType, ['wg-easy', '3x-ui'])) {
http_response_code(400);
echo json_encode(['error' => 'Invalid panel type. Supported: wg-easy, 3x-ui']);
return;
}
// Handle file upload
if (!isset($_FILES['backup_file']) || $_FILES['backup_file']['error'] !== UPLOAD_ERR_OK) {
http_response_code(400);
echo json_encode(['error' => 'No backup file uploaded']);
return;
}
$backupContent = file_get_contents($_FILES['backup_file']['tmp_name']);
try {
$importer = new PanelImporter($serverId, $user['id'], $panelType);
if (!$importer->parseBackupFile($backupContent)) {
http_response_code(400);
echo json_encode(['error' => 'Invalid backup file format']);
return;
}
$result = $importer->import();
echo json_encode($result);
} catch (Exception $e) {
http_response_code(500);
echo json_encode([
'success' => false,
'error' => $e->getMessage()
]);
}
});
// API: Get import history
Router::get('/api/servers/{id}/imports', function ($params) {
header('Content-Type: application/json');
$user = JWT::requireAuth();
if (!$user) return;
$serverId = (int)$params['id'];
// Validate server ownership
$server = VpnServer::getById($serverId);
if (!$server || $server['user_id'] != $user['id']) {
http_response_code(404);
echo json_encode(['error' => 'Server not found']);
return;
}
$imports = PanelImporter::getImportHistory($serverId);
echo json_encode([
'success' => true,
'imports' => $imports
]);
});
if (!$user) return;
$serverId = (int)$params['id'];
try {
$server = new VpnServer($serverId);
$serverData = $server->getData();
// Check ownership or admin
if ($serverData['user_id'] != $user['id'] && $user['role'] !== 'admin') {
http_response_code(403);
echo json_encode(['error' => 'Forbidden']);
return;
}
$server->delete();
echo json_encode([
'success' => true,
'message' => 'Server deleted successfully'
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
});
// API: Create backup
Router::post('/api/servers/{id}/backup', function ($params) {
header('Content-Type: application/json');
$user = requireApiAuth();
if (!$user) return;
$serverId = (int)$params['id'];
try {
$server = new VpnServer($serverId);
$serverData = $server->getData();
// Check ownership or admin
if ($serverData['user_id'] != $user['id'] && $user['role'] !== 'admin') {
http_response_code(403);
echo json_encode(['error' => 'Forbidden']);
return;
}
$backupId = $server->createBackup($user['id'], 'manual');
$backup = VpnServer::getBackup($backupId);
echo json_encode([
'success' => true,
'backup' => $backup
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
});
// API: List backups
Router::get('/api/servers/{id}/backups', function ($params) {
header('Content-Type: application/json');
$user = requireApiAuth();
if (!$user) return;
$serverId = (int)$params['id'];
try {
$server = new VpnServer($serverId);
$serverData = $server->getData();
// Check ownership or admin
if ($serverData['user_id'] != $user['id'] && $user['role'] !== 'admin') {
http_response_code(403);
echo json_encode(['error' => 'Forbidden']);
return;
}
$backups = $server->listBackups();
echo json_encode([
'success' => true,
'backups' => $backups,
'count' => count($backups)
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
});
// API: Restore backup
Router::post('/api/servers/{id}/restore', function ($params) {
header('Content-Type: application/json');
$user = requireApiAuth();
if (!$user) return;
$serverId = (int)$params['id'];
$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
$backupId = (int)($data['backup_id'] ?? 0);
if ($backupId <= 0) {
http_response_code(400);
echo json_encode(['error' => 'backup_id is required']);
return;
}
try {
$server = new VpnServer($serverId);
$serverData = $server->getData();
// Check ownership or admin
if ($serverData['user_id'] != $user['id'] && $user['role'] !== 'admin') {
http_response_code(403);
echo json_encode(['error' => 'Forbidden']);
return;
}
$result = $server->restoreBackup($backupId);
// Log the result for debugging
error_log('Restore backup result: ' . json_encode($result));
// Always return the result, even if success is false
echo json_encode($result);
} catch (Exception $e) {
error_log('Restore backup exception: ' . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => $e->getMessage(), 'success' => false]);
}
});
// API: Delete backup
Router::delete('/api/backups/{id}', function ($params) {
header('Content-Type: application/json');
$user = requireApiAuth();
if (!$user) return;
$backupId = (int)$params['id'];
try {
$backup = VpnServer::getBackup($backupId);
if (!$backup) {
http_response_code(404);
echo json_encode(['error' => 'Backup not found']);
return;
}
// Get server to check ownership
$server = new VpnServer($backup['server_id']);
$serverData = $server->getData();
// Check ownership or admin
if ($serverData['user_id'] != $user['id'] && $user['role'] !== 'admin') {
http_response_code(403);
echo json_encode(['error' => 'Forbidden']);
return;
}
VpnServer::deleteBackup($backupId);
echo json_encode([
'success' => true,
'message' => 'Backup deleted successfully'
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
});
// API: List clients
Router::get('/api/clients', function () {
header('Content-Type: application/json');
$user = JWT::requireAuth();
if (!$user) return;
$clients = VpnClient::listByUser($user['id']);
echo json_encode(['clients' => $clients]);
});
// API: Get client details with stats
Router::get('/api/clients/{id}/details', function ($params) {
header('Content-Type: application/json');
$user = JWT::requireAuth();
if (!$user) return;
$clientId = (int)$params['id'];
try {
$client = new VpnClient($clientId);
$clientData = $client->getData();
// Check ownership
if ($clientData['user_id'] != $user['id']) {
http_response_code(403);
echo json_encode(['error' => 'Forbidden']);
return;
}
// Sync stats before returning
$client->syncStats();
// Reload data
$client = new VpnClient($clientId);
$clientData = $client->getData();
$stats = $client->getFormattedStats();
echo json_encode([
'success' => true,
'client' => [
'id' => $clientData['id'],
'name' => $clientData['name'],
'server_id' => $clientData['server_id'],
'client_ip' => $clientData['client_ip'],
'status' => $clientData['status'],
'created_at' => $clientData['created_at'],
'stats' => $stats,
'bytes_sent' => $clientData['bytes_sent'],
'bytes_received' => $clientData['bytes_received'],
'last_handshake' => $clientData['last_handshake'],
'config' => $clientData['config'],
'qr_code' => $clientData['qr_code'],
]
]);
} catch (Exception $e) {
http_response_code(404);
echo json_encode(['error' => 'Client not found']);
}
});
// API: Get client QR code
Router::get('/api/clients/{id}/qr', function ($params) {
header('Content-Type: application/json');
$user = JWT::requireAuth();
if (!$user) return;
$clientId = (int)$params['id'];
try {
$client = new VpnClient($clientId);
$clientData = $client->getData();
// Check ownership
if ($clientData['user_id'] != $user['id']) {
http_response_code(403);
echo json_encode(['error' => 'Forbidden']);
return;
}
echo json_encode([
'success' => true,
'qr_code' => $clientData['qr_code'],
'client_name' => $clientData['name']
]);
} catch (Exception $e) {
http_response_code(404);
echo json_encode(['error' => 'Client not found']);
}
});
// API: Revoke client
Router::post('/api/clients/{id}/revoke', function ($params) {
header('Content-Type: application/json');
$user = JWT::requireAuth();
if (!$user) return;
$clientId = (int)$params['id'];
try {
$client = new VpnClient($clientId);
$clientData = $client->getData();
// Check ownership
if ($clientData['user_id'] != $user['id']) {
http_response_code(403);
echo json_encode(['error' => 'Forbidden']);
return;
}
if ($client->revoke()) {
echo json_encode(['success' => true, 'message' => 'Client revoked']);
} else {
http_response_code(500);
echo json_encode(['error' => 'Failed to revoke client']);
}
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
});
// API: Restore client
Router::post('/api/clients/{id}/restore', function ($params) {
header('Content-Type: application/json');
$user = JWT::requireAuth();
if (!$user) return;
$clientId = (int)$params['id'];
try {
$client = new VpnClient($clientId);
$clientData = $client->getData();
// Check ownership
if ($clientData['user_id'] != $user['id']) {
http_response_code(403);
echo json_encode(['error' => 'Forbidden']);
return;
}
if ($client->restore()) {
echo json_encode(['success' => true, 'message' => 'Client restored']);
} else {
http_response_code(500);
echo json_encode(['error' => 'Failed to restore client']);
}
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
});
// API: Get server clients
Router::get('/api/servers/{id}/clients', function ($params) {
header('Content-Type: application/json');
$user = JWT::requireAuth();
if (!$user) return;
$serverId = (int)$params['id'];
try {
$server = new VpnServer($serverId);
$serverData = $server->getData();
// Check ownership
$user = Auth::user();
if ($serverData['user_id'] != $user['id'] && !Auth::isAdmin()) {
http_response_code(403);
echo json_encode(['error' => 'Forbidden']);
return;
}
// Sync all stats first
VpnClient::syncAllStatsForServer($serverId);
$clients = VpnClient::listByServer($serverId);
$clientsData = [];
foreach ($clients as $clientData) {
$client = new VpnClient($clientData['id']);
$stats = $client->getFormattedStats();
$clientsData[] = [
'id' => $clientData['id'],
'name' => $clientData['name'],
'client_ip' => $clientData['client_ip'],
'status' => $clientData['status'],
'created_at' => $clientData['created_at'],
'stats' => $stats,
'bytes_sent' => $clientData['bytes_sent'],
'bytes_received' => $clientData['bytes_received'],
'last_handshake' => $clientData['last_handshake'],
];
}
echo json_encode(['success' => true, 'clients' => $clientsData]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
});
// API: Create client
Router::post('/api/clients/create', function () {
header('Content-Type: application/json');
$user = JWT::requireAuth();
if (!$user) return;
$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
$serverId = (int)($data['server_id'] ?? 0);
$name = trim($data['name'] ?? '');
$expiresInDays = isset($data['expires_in_days']) ? (int)$data['expires_in_days'] : null;
if ($serverId <= 0 || empty($name)) {
http_response_code(400);
echo json_encode(['error' => 'server_id and name are required']);
return;
}
try {
$clientId = VpnClient::create($serverId, $user['id'], $name, $expiresInDays);
$client = new VpnClient($clientId);
$clientData = $client->getData();
// Return client data with config and QR code
echo json_encode([
'success' => true,
'client' => [
'id' => $clientData['id'],
'name' => $clientData['name'],
'server_id' => $clientData['server_id'],
'client_ip' => $clientData['client_ip'],
'status' => $clientData['status'],
'expires_at' => $clientData['expires_at'],
'created_at' => $clientData['created_at'],
'config' => $clientData['config'],
'qr_code' => $clientData['qr_code'],
]
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
});
// Set client expiration
Router::post('/api/clients/{id}/set-expiration', function ($params) {
header('Content-Type: application/json');
$user = JWT::requireAuth();
if (!$user) return;
$clientId = (int)$params['id'];
$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
$expiresAt = $data['expires_at'] ?? null; // Y-m-d H:i:s format or null
try {
$client = new VpnClient($clientId);
$clientData = $client->getData();
// Check ownership
if ($clientData['user_id'] != $user['id'] && $user['role'] !== 'admin') {
http_response_code(403);
echo json_encode(['error' => 'Forbidden']);
return;
}
VpnClient::setExpiration($clientId, $expiresAt);
echo json_encode([
'success' => true,
'expires_at' => $expiresAt
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
});
// Extend client expiration
Router::post('/api/clients/{id}/extend', function ($params) {
header('Content-Type: application/json');
$user = JWT::requireAuth();
if (!$user) return;
$clientId = (int)$params['id'];
$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
$days = (int)($data['days'] ?? 30);
if ($days <= 0) {
http_response_code(400);
echo json_encode(['error' => 'days must be positive']);
return;
}
try {
$client = new VpnClient($clientId);
$clientData = $client->getData();
// Check ownership
if ($clientData['user_id'] != $user['id'] && $user['role'] !== 'admin') {
http_response_code(403);
echo json_encode(['error' => 'Forbidden']);
return;
}
VpnClient::extendExpiration($clientId, $days);
// Get updated expiration
$client = new VpnClient($clientId);
$updated = $client->getData();
echo json_encode([
'success' => true,
'expires_at' => $updated['expires_at'],
'extended_days' => $days
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
});
// Get expiring clients
Router::get('/api/clients/expiring', function () {
header('Content-Type: application/json');
$user = JWT::requireAuth();
if (!$user) return;
$days = (int)($_GET['days'] ?? 7);
try {
$clients = VpnClient::getExpiringClients($days);
// Filter by user if not admin
if ($user['role'] !== 'admin') {
$clients = array_filter($clients, function($c) use ($user) {
return $c['user_id'] == $user['id'];
});
}
echo json_encode([
'success' => true,
'clients' => array_values($clients),
'count' => count($clients)
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
});
// Set client traffic limit
Router::post('/api/clients/{id}/set-traffic-limit', function ($params) {
header('Content-Type: application/json');
$user = JWT::requireAuth();
if (!$user) return;
$clientId = (int)$params['id'];
$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
// limit_bytes can be null (unlimited) or positive integer
$limitBytes = isset($data['limit_bytes']) ? (int)$data['limit_bytes'] : null;
if ($limitBytes !== null && $limitBytes < 0) {
http_response_code(400);
echo json_encode(['error' => 'limit_bytes must be positive or null for unlimited']);
return;
}
try {
$client = new VpnClient($clientId);
$clientData = $client->getData();
// Check ownership
if ($clientData['user_id'] != $user['id'] && $user['role'] !== 'admin') {
http_response_code(403);
echo json_encode(['error' => 'Forbidden']);
return;
}
$client->setTrafficLimit($limitBytes);
echo json_encode([
'success' => true,
'limit_bytes' => $limitBytes,
'limit_gb' => $limitBytes ? round($limitBytes / 1073741824, 2) : null
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
});
// Check client traffic limit status
Router::get('/api/clients/{id}/traffic-limit-status', function ($params) {
header('Content-Type: application/json');
$user = JWT::requireAuth();
if (!$user) return;
$clientId = (int)$params['id'];
try {
$client = new VpnClient($clientId);
$clientData = $client->getData();
// Check ownership
if ($clientData['user_id'] != $user['id'] && $user['role'] !== 'admin') {
http_response_code(403);
echo json_encode(['error' => 'Forbidden']);
return;
}
$status = $client->getTrafficLimitStatus();
echo json_encode([
'success' => true,
'status' => $status
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
});
// Get clients over traffic limit
Router::get('/api/clients/overlimit', function () {
header('Content-Type: application/json');
$user = JWT::requireAuth();
if (!$user) return;
try {
$clients = VpnClient::getClientsOverLimit();
// Filter by user if not admin
if ($user['role'] !== 'admin') {
$clients = array_filter($clients, function($c) use ($user) {
return $c['user_id'] == $user['id'];
});
}
echo json_encode([
'success' => true,
'clients' => array_values($clients),
'count' => count($clients)
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
});
/**
* SETTINGS ROUTES
*/
// Settings page
Router::get('/settings', function () {
requireAuth();
require_once __DIR__ . '/../controllers/SettingsController.php';
$controller = new SettingsController();
$controller->index();
});
// Save API key
Router::post('/settings/api-key', function () {
requireAdmin();
require_once __DIR__ . '/../controllers/SettingsController.php';
$controller = new SettingsController();
$controller->saveApiKey();
});
// Change password
Router::post('/settings/change-password', function () {
requireAuth();
require_once __DIR__ . '/../controllers/SettingsController.php';
$controller = new SettingsController();
$controller->changePassword();
});
// Add user
Router::post('/settings/add-user', function () {
requireAdmin();
require_once __DIR__ . '/../controllers/SettingsController.php';
$controller = new SettingsController();
$controller->addUser();
});
// Delete user
Router::post('/settings/delete-user/{id}', function ($params) {
requireAdmin();
require_once __DIR__ . '/../controllers/SettingsController.php';
$controller = new SettingsController();
$controller->deleteUser($params['id']);
});
/**
* LANGUAGE ROUTES
*/
// Change language
Router::post('/language/change', function () {
$lang = $_POST['language'] ?? '';
if (Translator::setLanguage($lang)) {
$_SESSION['success'] = 'Language changed successfully';
} else {
$_SESSION['error'] = 'Invalid language';
}
$redirect = $_POST['redirect'] ?? '/dashboard';
redirect($redirect);
});
Router::get('/language/change', function () {
redirect('/dashboard');
});
// API: Get translation statistics
Router::get('/api/translations/stats', function () {
header('Content-Type: application/json');
$user = JWT::requireAuth();
if (!$user) return;
$stats = Translator::getStatistics();
echo json_encode(['stats' => $stats]);
});
// API: Auto-translate missing keys
Router::post('/api/translations/auto-translate', function () {
header('Content-Type: application/json');
$user = JWT::requireAuth();
if (!$user) return;
$raw = file_get_contents('php://input');
$data = json_decode($raw, true);
$targetLang = $data['language'] ?? '';
if (empty($targetLang)) {
http_response_code(400);
echo json_encode(['error' => 'Language is required']);
return;
}
try {
$stats = Translator::translateMissingKeys($targetLang);
echo json_encode([
'success' => true,
'stats' => $stats
]);
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
});
// API: Export translations
Router::get('/api/translations/export/{lang}', function ($params) {
header('Content-Type: application/json');
$user = JWT::requireAuth();
if (!$user) return;
$lang = $params['lang'];
try {
$json = Translator::exportToJson($lang);
header('Content-Disposition: attachment; filename="translations_' . $lang . '.json"');
echo $json;
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}
});
// Dispatch router
Router::dispatch($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI']);