Add multilingual support with translations for German, Russian, French, and Chinese
Added time limits and backup functions for servers
This commit is contained in:
@@ -16,6 +16,10 @@ class DB {
|
||||
PDO::ATTR_EMULATE_PREPARES => false,
|
||||
];
|
||||
self::$pdo = new PDO($dsn, $user, $pass, $options);
|
||||
|
||||
// Explicitly set UTF-8 encoding for connection
|
||||
self::$pdo->exec("SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci");
|
||||
|
||||
return self::$pdo;
|
||||
}
|
||||
}
|
||||
+9
-7
@@ -196,11 +196,11 @@ class Translator {
|
||||
* Translate text using AI with model fallback
|
||||
*/
|
||||
private static function translateWithAI(string $text, string $targetLanguage): ?string {
|
||||
// Try multiple free models for reliability
|
||||
// Use reliable paid models with fallback
|
||||
$models = [
|
||||
'google/gemini-2.0-flash-exp:free',
|
||||
'meta-llama/llama-3.2-3b-instruct:free',
|
||||
'qwen/qwen-2-7b-instruct:free'
|
||||
'anthropic/claude-3.5-sonnet',
|
||||
'openai/gpt-4o-mini',
|
||||
'google/gemini-pro-1.5'
|
||||
];
|
||||
|
||||
foreach ($models as $model) {
|
||||
@@ -350,9 +350,10 @@ class Translator {
|
||||
foreach ($missingKeys as $key => $value) {
|
||||
if (self::autoTranslate($targetLang, $key, $value)) {
|
||||
$stats['translated']++;
|
||||
usleep(500000); // 500ms delay between requests
|
||||
sleep(3); // 3 second delay between requests to avoid rate limits
|
||||
} else {
|
||||
$stats['failed']++;
|
||||
sleep(2); // Also delay on failure
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,8 +391,9 @@ class Translator {
|
||||
$jsonTexts = json_encode($textsForJson, JSON_UNESCAPED_UNICODE);
|
||||
|
||||
$models = [
|
||||
'google/gemini-2.0-flash-exp:free',
|
||||
'meta-llama/llama-3.2-3b-instruct:free'
|
||||
'anthropic/claude-3.5-sonnet',
|
||||
'openai/gpt-4o-mini',
|
||||
'google/gemini-pro-1.5'
|
||||
];
|
||||
|
||||
foreach ($models as $model) {
|
||||
|
||||
+145
-4
@@ -30,8 +30,14 @@ class VpnClient {
|
||||
|
||||
/**
|
||||
* Create new VPN client
|
||||
*
|
||||
* @param int $serverId Server ID
|
||||
* @param int $userId User ID
|
||||
* @param string $name Client name
|
||||
* @param int|null $expiresInDays Days until expiration (null = never expires)
|
||||
* @return int Client ID
|
||||
*/
|
||||
public static function create(int $serverId, int $userId, string $name): int {
|
||||
public static function create(int $serverId, int $userId, string $name, ?int $expiresInDays = null): int {
|
||||
$pdo = DB::conn();
|
||||
|
||||
// Get server data
|
||||
@@ -69,11 +75,14 @@ class VpnClient {
|
||||
// Generate QR code
|
||||
$qrCode = self::generateQRCode($config);
|
||||
|
||||
// Calculate expiration date
|
||||
$expiresAt = $expiresInDays ? date('Y-m-d H:i:s', strtotime("+{$expiresInDays} days")) : null;
|
||||
|
||||
// Insert into database
|
||||
$stmt = $pdo->prepare('
|
||||
INSERT INTO vpn_clients
|
||||
(server_id, user_id, name, client_ip, public_key, private_key, preshared_key, config, qr_code, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
(server_id, user_id, name, client_ip, public_key, private_key, preshared_key, config, qr_code, status, expires_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
');
|
||||
|
||||
$stmt->execute([
|
||||
@@ -86,7 +95,8 @@ class VpnClient {
|
||||
$serverData['preshared_key'],
|
||||
$config,
|
||||
$qrCode,
|
||||
'active'
|
||||
'active',
|
||||
$expiresAt
|
||||
]);
|
||||
|
||||
return (int)$pdo->lastInsertId();
|
||||
@@ -685,4 +695,135 @@ class VpnClient {
|
||||
|
||||
return round($bytes / pow(1024, $i), 2) . ' ' . $units[$i];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set client expiration date
|
||||
*
|
||||
* @param int $clientId Client ID
|
||||
* @param string|null $expiresAt Expiration date (Y-m-d H:i:s) or null for never expires
|
||||
* @return bool Success
|
||||
*/
|
||||
public static function setExpiration(int $clientId, ?string $expiresAt): bool {
|
||||
$pdo = DB::conn();
|
||||
$stmt = $pdo->prepare('UPDATE vpn_clients SET expires_at = ? WHERE id = ?');
|
||||
return $stmt->execute([$expiresAt, $clientId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend client expiration by days
|
||||
*
|
||||
* @param int $clientId Client ID
|
||||
* @param int $days Days to extend
|
||||
* @return bool Success
|
||||
*/
|
||||
public static function extendExpiration(int $clientId, int $days): bool {
|
||||
$pdo = DB::conn();
|
||||
|
||||
// Get current expiration
|
||||
$stmt = $pdo->prepare('SELECT expires_at FROM vpn_clients WHERE id = ?');
|
||||
$stmt->execute([$clientId]);
|
||||
$client = $stmt->fetch();
|
||||
|
||||
if (!$client) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate new expiration from current or now
|
||||
$baseDate = $client['expires_at'] ? strtotime($client['expires_at']) : time();
|
||||
$newExpiration = date('Y-m-d H:i:s', strtotime("+{$days} days", $baseDate));
|
||||
|
||||
return self::setExpiration($clientId, $newExpiration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get clients expiring soon
|
||||
*
|
||||
* @param int $days Check for clients expiring within N days
|
||||
* @return array List of expiring clients
|
||||
*/
|
||||
public static function getExpiringClients(int $days = 7): array {
|
||||
$pdo = DB::conn();
|
||||
$stmt = $pdo->prepare('
|
||||
SELECT c.*, s.name as server_name, s.host, u.name as user_name, u.email
|
||||
FROM vpn_clients c
|
||||
JOIN vpn_servers s ON c.server_id = s.id
|
||||
JOIN users u ON c.user_id = u.id
|
||||
WHERE c.expires_at IS NOT NULL
|
||||
AND c.expires_at <= DATE_ADD(NOW(), INTERVAL ? DAY)
|
||||
AND c.expires_at > NOW()
|
||||
AND c.status = "active"
|
||||
ORDER BY c.expires_at ASC
|
||||
');
|
||||
$stmt->execute([$days]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get expired clients
|
||||
*
|
||||
* @return array List of expired clients
|
||||
*/
|
||||
public static function getExpiredClients(): array {
|
||||
$pdo = DB::conn();
|
||||
$stmt = $pdo->query('
|
||||
SELECT c.*, s.name as server_name, s.host
|
||||
FROM vpn_clients c
|
||||
JOIN vpn_servers s ON c.server_id = s.id
|
||||
WHERE c.expires_at IS NOT NULL
|
||||
AND c.expires_at <= NOW()
|
||||
AND c.status = "active"
|
||||
ORDER BY c.expires_at DESC
|
||||
');
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable expired clients automatically
|
||||
*
|
||||
* @return int Number of clients disabled
|
||||
*/
|
||||
public static function disableExpiredClients(): int {
|
||||
$expiredClients = self::getExpiredClients();
|
||||
$count = 0;
|
||||
|
||||
foreach ($expiredClients as $clientData) {
|
||||
try {
|
||||
$client = new self($clientData['id']);
|
||||
$client->revoke();
|
||||
$count++;
|
||||
} catch (Exception $e) {
|
||||
error_log("Failed to disable expired client {$clientData['id']}: " . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if client is expired
|
||||
*
|
||||
* @return bool True if expired
|
||||
*/
|
||||
public function isExpired(): bool {
|
||||
if (!$this->data) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->data['expires_at'] !== null && strtotime($this->data['expires_at']) <= time();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get days until expiration
|
||||
*
|
||||
* @return int|null Days until expiration (negative if expired, null if never expires)
|
||||
*/
|
||||
public function getDaysUntilExpiration(): ?int {
|
||||
if (!$this->data || $this->data['expires_at'] === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$diff = strtotime($this->data['expires_at']) - time();
|
||||
return (int)floor($diff / 86400);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -446,4 +446,266 @@ BASH;
|
||||
public function getData(): ?array {
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create backup of server configuration and all clients
|
||||
*
|
||||
* @param int $userId User who creates the backup
|
||||
* @param string $backupType Type: 'manual' or 'automatic'
|
||||
* @return int Backup ID
|
||||
*/
|
||||
public function createBackup(int $userId, string $backupType = 'manual'): int {
|
||||
if (!$this->data) {
|
||||
throw new Exception('Server not loaded');
|
||||
}
|
||||
|
||||
$pdo = DB::conn();
|
||||
$backupName = 'backup_' . $this->serverId . '_' . date('Y-m-d_His') . '.json';
|
||||
$backupDir = '/var/www/html/backups';
|
||||
$backupPath = $backupDir . '/' . $backupName;
|
||||
|
||||
// Create backups directory if not exists
|
||||
if (!is_dir($backupDir)) {
|
||||
mkdir($backupDir, 0755, true);
|
||||
}
|
||||
|
||||
try {
|
||||
// Get all clients for this server
|
||||
$stmt = $pdo->prepare('
|
||||
SELECT id, name, client_ip, public_key, private_key, preshared_key,
|
||||
config, status, expires_at, created_at
|
||||
FROM vpn_clients
|
||||
WHERE server_id = ?
|
||||
');
|
||||
$stmt->execute([$this->serverId]);
|
||||
$clients = $stmt->fetchAll();
|
||||
|
||||
// Prepare backup data
|
||||
$backupData = [
|
||||
'server' => [
|
||||
'name' => $this->data['name'],
|
||||
'host' => $this->data['host'],
|
||||
'port' => $this->data['port'],
|
||||
'vpn_port' => $this->data['vpn_port'],
|
||||
'vpn_subnet' => $this->data['vpn_subnet'],
|
||||
'container_name' => $this->data['container_name'],
|
||||
'server_public_key' => $this->data['server_public_key'],
|
||||
'preshared_key' => $this->data['preshared_key'],
|
||||
'awg_params' => $this->data['awg_params'],
|
||||
],
|
||||
'clients' => $clients,
|
||||
'backup_date' => date('Y-m-d H:i:s'),
|
||||
'version' => '1.0'
|
||||
];
|
||||
|
||||
// Write backup to file
|
||||
$json = json_encode($backupData, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
|
||||
file_put_contents($backupPath, $json);
|
||||
|
||||
$backupSize = filesize($backupPath);
|
||||
|
||||
// Insert backup record
|
||||
$stmt = $pdo->prepare('
|
||||
INSERT INTO server_backups
|
||||
(server_id, backup_name, backup_path, backup_size, clients_count, backup_type, status, created_by)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
');
|
||||
|
||||
$stmt->execute([
|
||||
$this->serverId,
|
||||
$backupName,
|
||||
$backupPath,
|
||||
$backupSize,
|
||||
count($clients),
|
||||
$backupType,
|
||||
'completed',
|
||||
$userId
|
||||
]);
|
||||
|
||||
return (int)$pdo->lastInsertId();
|
||||
|
||||
} catch (Exception $e) {
|
||||
// Mark backup as failed
|
||||
if (isset($stmt)) {
|
||||
$stmt = $pdo->prepare('
|
||||
INSERT INTO server_backups
|
||||
(server_id, backup_name, backup_path, backup_type, status, error_message, created_by)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
');
|
||||
|
||||
$stmt->execute([
|
||||
$this->serverId,
|
||||
$backupName,
|
||||
$backupPath,
|
||||
$backupType,
|
||||
'failed',
|
||||
$e->getMessage(),
|
||||
$userId
|
||||
]);
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List all backups for this server
|
||||
*
|
||||
* @return array List of backups
|
||||
*/
|
||||
public function listBackups(): array {
|
||||
if (!$this->data) {
|
||||
throw new Exception('Server not loaded');
|
||||
}
|
||||
|
||||
$pdo = DB::conn();
|
||||
$stmt = $pdo->prepare('
|
||||
SELECT b.*, u.name as created_by_name, u.email as created_by_email
|
||||
FROM server_backups b
|
||||
LEFT JOIN users u ON b.created_by = u.id
|
||||
WHERE b.server_id = ?
|
||||
ORDER BY b.created_at DESC
|
||||
');
|
||||
$stmt->execute([$this->serverId]);
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore server from backup
|
||||
* Note: This only restores client configurations to database
|
||||
* Server must already be deployed
|
||||
*
|
||||
* @param int $backupId Backup ID
|
||||
* @return array Restoration results
|
||||
*/
|
||||
public function restoreBackup(int $backupId): array {
|
||||
if (!$this->data) {
|
||||
throw new Exception('Server not loaded');
|
||||
}
|
||||
|
||||
if ($this->data['status'] !== 'active') {
|
||||
throw new Exception('Server must be active to restore backup');
|
||||
}
|
||||
|
||||
$pdo = DB::conn();
|
||||
|
||||
// Get backup record
|
||||
$stmt = $pdo->prepare('SELECT * FROM server_backups WHERE id = ? AND server_id = ?');
|
||||
$stmt->execute([$backupId, $this->serverId]);
|
||||
$backup = $stmt->fetch();
|
||||
|
||||
if (!$backup) {
|
||||
throw new Exception('Backup not found');
|
||||
}
|
||||
|
||||
if (!file_exists($backup['backup_path'])) {
|
||||
throw new Exception('Backup file not found');
|
||||
}
|
||||
|
||||
// Read backup data
|
||||
$backupData = json_decode(file_get_contents($backup['backup_path']), true);
|
||||
|
||||
if (!$backupData || !isset($backupData['clients'])) {
|
||||
throw new Exception('Invalid backup format');
|
||||
}
|
||||
|
||||
$restored = 0;
|
||||
$failed = 0;
|
||||
$errors = [];
|
||||
|
||||
foreach ($backupData['clients'] as $clientData) {
|
||||
try {
|
||||
// Check if client already exists by IP
|
||||
$stmt = $pdo->prepare('SELECT id FROM vpn_clients WHERE server_id = ? AND client_ip = ?');
|
||||
$stmt->execute([$this->serverId, $clientData['client_ip']]);
|
||||
$existing = $stmt->fetch();
|
||||
|
||||
if ($existing) {
|
||||
$errors[] = "Client {$clientData['name']} already exists";
|
||||
$failed++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Insert client
|
||||
$stmt = $pdo->prepare('
|
||||
INSERT INTO vpn_clients
|
||||
(server_id, user_id, name, client_ip, public_key, private_key, preshared_key,
|
||||
config, status, expires_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
');
|
||||
|
||||
$stmt->execute([
|
||||
$this->serverId,
|
||||
$this->data['user_id'],
|
||||
$clientData['name'],
|
||||
$clientData['client_ip'],
|
||||
$clientData['public_key'],
|
||||
$clientData['private_key'],
|
||||
$clientData['preshared_key'],
|
||||
$clientData['config'],
|
||||
'disabled', // Restore as disabled for safety
|
||||
$clientData['expires_at']
|
||||
]);
|
||||
|
||||
// Add client to server container
|
||||
VpnClient::addClientToServer($this->data, $clientData['public_key'], $clientData['client_ip']);
|
||||
|
||||
$restored++;
|
||||
|
||||
} catch (Exception $e) {
|
||||
$failed++;
|
||||
$errors[] = "Failed to restore {$clientData['name']}: " . $e->getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true, // Always success if process completed
|
||||
'restored' => $restored,
|
||||
'failed' => $failed,
|
||||
'total' => count($backupData['clients']),
|
||||
'errors' => $errors,
|
||||
'message' => $restored > 0 ? "Restored $restored clients" : "No clients restored"
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete backup
|
||||
*
|
||||
* @param int $backupId Backup ID
|
||||
* @return bool Success
|
||||
*/
|
||||
public static function deleteBackup(int $backupId): bool {
|
||||
$pdo = DB::conn();
|
||||
|
||||
// Get backup path
|
||||
$stmt = $pdo->prepare('SELECT backup_path FROM server_backups WHERE id = ?');
|
||||
$stmt->execute([$backupId]);
|
||||
$backup = $stmt->fetch();
|
||||
|
||||
if (!$backup) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Delete file
|
||||
if (file_exists($backup['backup_path'])) {
|
||||
unlink($backup['backup_path']);
|
||||
}
|
||||
|
||||
// Delete record
|
||||
$stmt = $pdo->prepare('DELETE FROM server_backups WHERE id = ?');
|
||||
return $stmt->execute([$backupId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get backup by ID
|
||||
*
|
||||
* @param int $backupId Backup ID
|
||||
* @return array|null Backup data
|
||||
*/
|
||||
public static function getBackup(int $backupId): ?array {
|
||||
$pdo = DB::conn();
|
||||
$stmt = $pdo->prepare('SELECT * FROM server_backups WHERE id = ?');
|
||||
$stmt->execute([$backupId]);
|
||||
return $stmt->fetch() ?: null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user