a52aba25d8
- Updated /api/clients/create to return config and qr_code
- Updated /api/clients/{id}/details to include config and qr_code
- Added new endpoint /api/clients/{id}/qr for getting QR code only
- Added API_EXAMPLES.md with usage examples and integration code
- Updated README.md API documentation
1157 lines
32 KiB
PHP
1157 lines
32 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';
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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,
|
|
]);
|
|
|
|
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);
|
|
|
|
View::render('servers/view.twig', [
|
|
'server' => $serverData,
|
|
'clients' => $clients,
|
|
]);
|
|
} catch (Exception $e) {
|
|
http_response_code(404);
|
|
echo 'Server not found';
|
|
}
|
|
});
|
|
|
|
// 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'] ?? '');
|
|
|
|
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);
|
|
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();
|
|
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: 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'] ?? '');
|
|
|
|
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);
|
|
|
|
$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'],
|
|
'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()]);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* 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']);
|