apiUri = $vcenterModel->host; $this->username = $vcenterModel->user; $this->password = SignRSA::DecryptFunc($vcenterModel->password); // nếu đã mã hóa // Log::info('Đăng nhập với user: ' . $this->username); $response = Http::withoutVerifying() ->withBasicAuth($this->username, $this->password) ->post("https://{$this->apiUri}/rest/com/vmware/cis/session", []); // Log::info('Response login: ' . $response->body()); if ($response->successful()) { $json = $response->json(); if (isset($json['value'])) { $this->token = $json['value']; $vcenterModel->update(['token' => $this->token]); // Log::info('token', ['token' => $this->token]); return $this->token; } $body = $response->body(); if (!empty($body)) { $this->token = $body; $vcenterModel->update(['token' => $this->token]); // Log::info('token raw string', ['token' => $this->token]); return $this->token; } } return false; } public function getAllDatacenter() { $check = $this->checkApiExits(); if ($check) { $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->get("https://$this->apiUri/rest/vcenter/datacenter")->json(); if (!isset($response['type'])) { return $response['value']; } return false; } return "Login_Again"; } public function getAllVirtualMachine($datacenter = '', $host = '') { $check = $this->checkApiExits(); if ($check) { $params = []; if (!empty($datacenter)) { $params['filter.datacenters'] = $datacenter; } if (!empty($host)) { $params['filter.hosts.1'] = $host; } $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->get("https://$this->apiUri/rest/vcenter/vm", $params)->json(); if (!isset($response['type'])) { return $response['value']; } return false; } return "Login_Again"; } public function checkApiExits(): bool { $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->get("https://$this->apiUri/rest/com/vmware/content/library")->json(); if (isset($response['type'])) { return false; } return true; } public function getAllHost($datacenter = '') { $check = $this->checkApiExits(); if ($check) { $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->get("https://$this->apiUri/rest/vcenter/host", [ 'filter.datacenters.1' => $datacenter ])->json(); if (!isset($response['type'])) { return $response['value']; } return false; } return "Login_Again"; } public function getAllFolder($datacenter = '') { $check = $this->checkApiExits(); if ($check) { $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->get("https://$this->apiUri/rest/vcenter/folder", [ 'filter.datacenters.1' => $datacenter, 'filter.type' => "VIRTUAL_MACHINE", ])->json(); if (!isset($response['type'])) { return $response['value']; } return false; } return "Login_Again"; } public function getTemplateFromDatastore($datastore = '') { $allTemplate = $this->getAllTemplate(); if (!$allTemplate) { return false; } foreach ($allTemplate as $template) { $detailTemplate = $this->getTemplatebyID($template); if ($detailTemplate && $detailTemplate === $datastore) { return $template; } } return false; } public function getAllTemplate() { $check = $this->checkApiExits(); if ($check) { $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->get("https://$this->apiUri/rest/com/vmware/content/library")->json(); if (!isset($response['type'])) { return $response['value']; } return false; } return "Login_Again"; } protected function getTemplatebyID($template = '') { $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->get("https://$this->apiUri/rest/com/vmware/content/library/id:$template")->json(); if (!isset($response['type'])) { return $response['value']['storage_backings'][0]['datastore_id']; } return false; } public function getAllVersion($library = ''): bool|array|string { $check = $this->checkApiExits(); if ($check) { $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->get("https://$this->apiUri/rest/com/vmware/content/library/item", [ 'library_id' => $library ])->json(); if (!isset($response['type'])) { $library_item = []; foreach ($response['value'] as $value) { $value_detail = $this->getVersionDetail($value); if ($value_detail) { $library_item[] = [ 'name' => $value_detail['name'], 'id' => $value_detail['id'] ]; } } return $library_item; } return false; } return "Login_Again"; } protected function getVersionDetail($version_id = '') { $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->get("https://$this->apiUri/rest/com/vmware/content/library/item/id:$version_id")->json(); if (!isset($response['type'])) { return $response['value']; } return false; } public function loadNetWork($data = [], $host_id = '') { $this->username = $data->user; $this->password = SignRSA::DecryptFunc($data->password); $this->apiUri = $data->host; // Pass the host_id directly to the API call $allNetwork = $this->getAllNetwork($host_id); return $allNetwork; } private function getAllNetwork($host = '') { $check = $this->checkApiExits(); if ($check) { $params = []; if (!empty($host)) { $params['filter.hosts'] = $host; } $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->get("https://$this->apiUri/rest/vcenter/network", $params)->json(); if (!isset($response['type'])) { return $response['value']; } return false; } return "Login_Again"; } public function getNetwork($host = ''): array|bool|string { return $this->getAllNetwork($host); } protected function Command($cmd) { $auth = $this->LoginAuth(); if (!$auth) { return "Login_Fail"; } try { $psCommand = <<apiUri}' -Credential \$cred; $cmd; Disconnect-VIServer -Confirm:\$false " PS; $out = $auth->exec($psCommand); $err = $auth->getStdError(); return $out . ($err ? "\n[STDERR]\n" . $err : ''); } catch (\Exception $e) { return $e->getMessage(); } } private function LoginAuth() { try { $connection = new SSH2('10.10.10.173', 22); if ($connection->login('administrator', 'Thegioiso@123**$$8899TgTk2')) { $connection->setTimeout(0); // No timeout return $connection; } return false; } catch (Exception $error) { return $error->getCode(); } } // Login SSH public function loginSSH(): bool { $conn = $this->LoginAuth(); if ($conn) { Log::info("✅ Connected to vCenter via SSH successfully"); return true; } Log::error("❌ Failed to connect to vCenter via SSH"); return false; } public function runPsCommand(string $psCommand): string { $auth = $this->LoginAuth(); if (!$auth) return "Login_Fail"; // gọi trực tiếp -Command, thu cả STDERR $action = 'powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass ' . '-Command "' . str_replace('"', '\"', $psCommand) . '"'; try { $out = $auth->exec($action); $errOut = $auth->getStdError(); return $out . ($errOut ? "\n[STDERR]\n" . $errOut : ''); } catch (\Throwable $e) { return "Error: " . $e->getMessage(); } } protected function CommandRSP(string $cmd) { $auth = $this->LoginAuth(); if (!$auth) return "Login_Fail"; // Escape để dùng trong chuỗi single-quoted của PowerShell $esc = fn(string $v) => str_replace("'", "''", $v); $server = $esc($this->apiUri ?? ''); $user = $esc($this->username ?? ''); $pass = $esc($this->password ?? ''); // Ghép script PowerShell hoàn chỉnh, chạy trong 1 scriptblock // Lưu ý: dùng placeholder rồi strtr để tránh lẫn dấu ngoặc $psTemplate = <<<'PS' $ProgressPreference = 'SilentlyContinue' $InformationPreference = 'SilentlyContinue' $WarningPreference = 'SilentlyContinue' $ErrorActionPreference = 'Stop' Import-Module VMware.PowerCLI -ErrorAction Stop Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Scope Session -Confirm:$false | Out-Null Connect-VIServer -Server '' -User '' -Password '' | Out-Null # Thực thi lệnh công việc (đã được xây dựng sẵn, ví dụ: & 'C:\path\script.ps1' -Param 'Value' ... ) & { } | Out-String PS; // Chèn dữ liệu $psScript = strtr($psTemplate, [ '' => $server, '' => $user, '' => $pass, '' => $cmd, // ở đây $cmd KHÔNG cần ngoặc kép bao ngoài ]); // Encode UTF-16LE → Base64 cho -EncodedCommand $encoded = base64_encode(mb_convert_encoding($psScript, 'UTF-16LE', 'UTF-8')); // Dùng -EncodedCommand để tránh mọi lỗi escape/quote $action = "powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand $encoded"; try { $out = $auth->exec($action); $errOut = $auth->getStdError(); return $out . ($errOut ? "\n[STDERR]\n" . $errOut : ''); } catch (\Throwable $e) { return "Error: " . $e->getMessage(); } } protected function CommandChangePort(string $cmd, bool $expectJson = false) { $auth = $this->LoginAuth(); if (!$auth) return "Login_Fail"; $esc = fn(string $v) => str_replace("'", "''", $v); $server = $esc($this->apiUri ?? ''); $user = $esc($this->username ?? ''); $pass = $esc($this->password ?? ''); // PowerShell script template $psTemplate = <<<'PS' $ProgressPreference = 'SilentlyContinue' $InformationPreference = 'SilentlyContinue' $WarningPreference = 'SilentlyContinue' $ErrorActionPreference = 'Stop' [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 Import-Module VMware.PowerCLI -ErrorAction Stop Set-PowerCLIConfiguration -InvalidCertificateAction Ignore -Scope Session -Confirm:$false | Out-Null Connect-VIServer -Server '' -User '' -Password '' | Out-Null & { } PS; $psScript = strtr($psTemplate, [ '' => $server, '' => $user, '' => $pass, '' => $cmd, ]); // Encode UTF-16LE cho -EncodedCommand $encoded = base64_encode(mb_convert_encoding($psScript, 'UTF-16LE', 'UTF-8')); $action = "powershell.exe -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -EncodedCommand $encoded"; try { $out = $auth->exec($action); $errOut = $auth->getStdError(); if ($expectJson) { // CẮT JSON cuối: {...} hoặc [...] $trimmed = trim($out); // đơn giản: bắt khối { ... } hoặc [ ... ] ở cuối chuỗi if (preg_match('~(\{.*\}|\[.*\])\s*$~s', $trimmed, $m)) { return $m[1]; } // không tìm thấy → trả nguyên STDOUT (để caller log raw) return $trimmed; } // Trường hợp không kỳ vọng JSON: giữ nguyên hành vi cũ (append STDERR) return $out . ($errOut ? "\n[STDERR]\n" . $errOut : ''); } catch (\Throwable $e) { return "Error: " . $e->getMessage(); } } public function resetGuestPasswordViaPs1(string $vmName, string $guestUser, string $guestPassword, string $newPassword, string $targetUser = 'Administrator'): array|string { // File ps1 chạy được ngoài PowerShell: bạn đã test ok $psFile = "C:\\CLISave\\AssignIP\\SetGuestPwd.ps1"; // Escape chuỗi cho PowerShell single-quoted $q = fn(string $v) => str_replace("'", "''", $v); // Xây args kiểu: -Key 'Value' $args = " -VMName '" . $q($vmName) . "'"; $args .= " -GuestUser '" . $q($guestUser) . "'"; $args .= " -GuestPassword '" . $q($guestPassword) . "'"; $args .= " -TargetUser '" . $q($targetUser) . "'"; $args .= " -NewPassword '" . $q($newPassword) . "'"; // Gọi file trong cùng phiên PS đã Connect-VIServer (nhờ Command()) // Thêm Out-String để ép JSON nằm trên STDOUT gọn $cmd = "& '$psFile'$args | Out-String"; $raw = $this->CommandRSP($cmd); // <-- dùng lại Command() của bạn if ($raw === 'Login_Fail' || $raw === false || $raw === null) { return "Login_Fail"; } // Thử parse JSON (có thể lẫn banner/log) $trim = trim($raw); $data = json_decode($trim, true); if (json_last_error() !== JSON_ERROR_NONE) { if (preg_match('/(\{.*\}|\[.*\])/s', $trim, $m)) { $data = json_decode($m[1], true); } } return $data ?: $raw; // nếu chưa parse được, trả raw để debug } public function changeRDPPortWindows(string $vmName, string $guestUser, string $guestPassword, string $newPort): array|string { $psFile = "C:\\CLISave\\AssignIP\\change-rdp-port.ps1"; $q = fn(string $v) => str_replace("'", "''", $v); // Dùng -VMName (string). Alias -VMNames cũng ok, nhưng mình set đúng tên tham số. $args = " -VMName '" . $q($vmName) . "'"; $args .= " -GuestUser '" . $q($guestUser) . "'"; $args .= " -GuestPassword '" . $q($guestPassword) . "'"; // khớp tên tham số trong script 1-VM $args .= " -NewPort " . (int) $newPort; $cmd = "& '$psFile'$args"; // Nếu đã có phiên bản CommandRSP($cmd, bool $expectJson) → bật expectJson để không trộn STDERR $raw = method_exists($this, 'CommandRSP') ? ($this->CommandChangePort($cmd, true) ?? '') : ''; if ($raw === 'Login_Fail' || $raw === false || $raw === null) { return "Login_Fail"; } $raw = trim($raw); // CẮT LẤY KHỐI JSON CUỐI (object hoặc array) nếu có noise if (!preg_match('~(\{(?:[^{}]|(?1))*\}|\[(?:[^\[\]]|(?0))*\])\s*$~s', $raw, $m)) { // fallback đơn giản hơn if (preg_match('~(\{.*\}|\[.*\])\s*$~s', $raw, $m)) { $raw = $m[1]; } } else { $raw = $m[1]; } $data = json_decode($raw, true); if (json_last_error() !== JSON_ERROR_NONE) { return [ 'success' => false, 'error' => 'JSON parsing failed', 'raw_output' => $raw, 'json_error' => json_last_error_msg() ]; } // Script 1-VM trả về 1 object, nhưng nếu lỡ trả mảng thì lấy phần tử đầu if (isset($data[0])) { $data = $data[0]; } return $data; } public function loadDatastore($data = [], $host_id = '') { Log::info('[vCenter] loadDatastore()', [ 'host' => $data->host, 'esxi_host' => $host_id ]); $allDatastore = $this->getAllDataStore($host_id); if ($allDatastore === "Login_Again") { Log::warning('[vCenter] Token expired when getAllDataStore'); return "Login_Again"; } if (!$allDatastore) { Log::warning('[vCenter] getAllDataStore return false'); return false; } Log::info('[vCenter] Datastore count from API', [ 'count' => count($allDatastore) ]); return $allDatastore; } public function getAllDataStore($host = '') { Log::info('[vCenter] getAllDataStore call', [ 'api' => $this->apiUri, 'host_filter' => $host ]); $check = $this->checkApiExits(); if (!$check) { Log::warning('[vCenter] checkApiExits = false'); return "Login_Again"; } $params = []; if (!empty($host)) { $params['filter.hosts'] = $host; } $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->get("https://$this->apiUri/rest/vcenter/datastore", $params); Log::info('[vCenter] datastore raw response', [ 'status' => $response->status(), // 'body' => $response->body() // Comment out to reduce log noise if large ]); $json = $response->json(); if (!isset($json['type'])) { return $json['value']; } Log::warning('[vCenter] API returned error', [ 'response' => $json ]); return false; } public function CreateVirtualMachine($product_data): array|string { $vcenterModel = $product_data['vcenter']; Log::info('Vcenter Info', ['data' => $vcenterModel]); if (!$this->loginServer($vcenterModel)) { Log::error('Failed to create VM: API check failed, requires login.'); return 'Login_Again'; } $userName = customer()?->email; $item = $product_data['item']; $host = $product_data['host']; $datacenter = $product_data['datacenter']; $template = $product_data['template']; $addon = $product_data['addon'] ?? ['cpu' => 0, 'ram' => 0, 'disk' => 0]; // Lấy cấu hình gốc từ item hoặc product $base_cpu = $item->cpu ?? ($host->cpu ?? 1); $base_ram = $item->ram ?? ($host->ram ?? 1); $base_disk = $item->disk ?? ($host->disk ?? 20); // Đảm bảo giá trị không bị null hoặc 0 $base_cpu = max(1, (int) $base_cpu); $base_ram = max(1, (int) $base_ram); $base_disk = max(20, (int) $base_disk); Log::info("Cấu hình cơ bản", [ 'base_cpu' => $base_cpu, 'base_ram' => $base_ram, 'base_disk' => $base_disk, 'addon' => $addon ]); // Lấy tất cả datastore liên quan host, không lấy first() $datastores = \App\Models\ModuleDatastore::where('module_host_id', $host->id)->get(); // Nếu muốn chọn 1 datastore để truyền (ví dụ lấy cái đầu tiên) $datastoreId = $datastores->isNotEmpty() ? $datastores->first()->datastore : 'datastore-3004'; // fallback nếu không có $body = [ 'spec' => [ 'name' => $item->server_name ?? ($userName . '-vm-' . $item->id), 'source' => $template, // sử dụng template của host, thay 'vm-3007' cứng thành biến 'placement' => [ 'datacenter' => $datacenter->datacenter_id, 'folder' => 'group-v1008', // hoặc lấy từ DB nếu cần 'host' => $host->host_id, 'resource_pool' => 'resgroup-3002', // nếu có thể lấy động thì tốt hơn 'datastore' => $datastoreId, // chuỗi datastore đúng định dạng ], 'power_on' => false, 'fallback_to_datastore_default_policy' => true, ], ]; Log::info('Data payload', ['data' => $body]); try { Log::info('Bắt đầu clone VM với timeout 300 giây', ['template' => $template, 'target_name' => $body['spec']['name']]); $response = Http::withoutVerifying() ->timeout(300) // Tăng timeout lên 5 phút cho quá trình clone ->connectTimeout(30) // Giữ connect timeout ngắn để phát hiện lỗi kết nối sớm ->withHeaders([ 'vmware-api-session-id' => $this->token, 'Content-Type' => 'application/json', ]) ->post("https://{$this->apiUri}/rest/vcenter/vm?action=clone", $body); $responseData = $response->json(); if ($response->successful() && !isset($responseData['type'])) { $vmId = $responseData['value'] ?? 'UNKNOWN'; Log::info("VM clone thành công, VM ID: {$vmId}", [ 'addon_data' => $addon, 'base_cpu' => $base_cpu, 'base_ram' => $base_ram, 'has_cpu_addon' => $addon['cpu'] > 0, 'has_ram_addon' => $addon['ram'] > 0, 'has_disk_addon' => $addon['disk'] > 0 ]); // Cập nhật cấu hình phần cứng sau khi clone thành công // Thay đổi điều kiện: chạy nếu có bất kỳ addon nào > 0 if ($vmId !== 'UNKNOWN' && ($addon['cpu'] > 0 || $addon['ram'] > 0 || $addon['disk'] > 0)) { Log::info("Bắt đầu cập nhật addon cho VM {$vmId}"); $this->updateVMHardware($vmId, $addon, $base_cpu, $base_ram); } else { Log::info("Không có addon nào cần cập nhật cho VM {$vmId}", [ 'vm_id' => $vmId, 'addon' => $addon, 'condition_check' => $vmId !== 'UNKNOWN' && ($addon['cpu'] > 0 || $addon['ram'] > 0 || $addon['disk'] > 0) ]); } // 🟢 Gửi thông báo Discord $discord = app(\App\Http\Services\Discord::class); // Thông tin addon $addonMsg = ''; if (!empty($addon)) { $addonMsg = "\n**Addon:** " . ($addon['cpu'] ? "CPU +{$addon['cpu']} " : "") . ($addon['ram'] ? "RAM +{$addon['ram']}GB " : "") . ($addon['disk'] ? "Disk +{$addon['disk']}GB " : ""); } $discord->SendMessage( message: "**User:** {$item->server_name}\n**VM ID:** `{$vmId}`{$addonMsg}\n**Thời gian:** " . now()->format('d/m/Y H:i:s'), color: Discord::COLOR_SUCCESS, title: '🎉 Tạo máy ảo thành công' ); return ['error' => false, 'data' => $vmId]; } return ['error' => true, 'data' => $responseData['value'] ?? $response->body()]; } catch (\Exception $e) { Log::error('Exception when creating VM', ['message' => $e->getMessage()]); return ['error' => true, 'data' => $e->getMessage()]; } } /** * Cập nhật cấu hình phần cứng VM sau khi clone */ private function updateVMHardware($vmId, $addon, $base_cpu, $base_ram): void { try { Log::info("=== BẮT ĐẦU CẬP NHẬT ADDON ==="); Log::info("Thông tin VM và Addon", [ 'vm_id' => $vmId, 'addon' => $addon, 'base_cpu' => $base_cpu, 'base_ram' => $base_ram, 'addon_cpu' => $addon['cpu'], 'addon_ram' => $addon['ram'], 'addon_disk' => $addon['disk'] ]); // Tính toán cấu hình mới $new_cpu = $base_cpu + $addon['cpu']; // Sửa: RAM đầu vào là GB, convert sang MiB $new_ram_mib = ($base_ram + $addon['ram']) * 1024; // 1 GB = 1024 MiB Log::info("Tính toán cấu hình mới", [ 'vm_id' => $vmId, 'base_cpu' => $base_cpu, 'addon_cpu' => $addon['cpu'], 'new_cpu' => $new_cpu, 'base_ram_gb' => $base_ram, 'addon_ram_gb' => $addon['ram'], 'new_ram_mib' => $new_ram_mib ]); // Cập nhật CPU nếu có addon if ($addon['cpu'] > 0) { Log::info("🔄 Bắt đầu cập nhật CPU cho VM {$vmId}"); $cpuResult = $this->updateVMCPU($vmId, $new_cpu); if ($cpuResult) { Log::info("✅ Cập nhật CPU thành công: {$base_cpu} → {$new_cpu} cores"); } else { Log::error("❌ Cập nhật CPU thất bại cho VM {$vmId}"); } } else { Log::info("⏭️ Bỏ qua cập nhật CPU (không có addon CPU)"); } // Cập nhật RAM nếu có addon if ($addon['ram'] > 0) { Log::info("🔄 Bắt đầu cập nhật RAM cho VM {$vmId}"); $ramResult = $this->updateVMMemory($vmId, $new_ram_mib); if ($ramResult) { Log::info("✅ Cập nhật RAM thành công: {$base_ram}GB → " . ($base_ram + $addon['ram']) . "GB ({$new_ram_mib}MiB)"); } else { Log::error("❌ Cập nhật RAM thất bại cho VM {$vmId}"); } } else { Log::info("⏭️ Bỏ qua cập nhật RAM (không có addon RAM)"); } Log::info("=== HOÀN THÀNH CẬP NHẬT ADDON ==="); } catch (\Exception $e) { Log::error("❌ Lỗi khi cập nhật cấu hình phần cứng VM {$vmId}: " . $e->getMessage()); Log::error("Stack trace: " . $e->getTraceAsString()); } } /** * Cập nhật CPU cho VM */ private function updateVMCPU($vmId, $cpuCount): bool { try { Log::info("🔄 Gọi API cập nhật CPU", [ 'vm_id' => $vmId, 'new_cpu_count' => $cpuCount ]); // Tính toán cores_per_socket dựa trên count // Đảm bảo count % cores_per_socket == 0 $coresPerSocket = 1; if ($cpuCount > 1) { // Tìm ước số lớn nhất <= cpuCount/2 để tối ưu phân bổ socket $maxCoresPerSocket = floor($cpuCount / 2); for ($i = $maxCoresPerSocket; $i >= 1; $i--) { if ($cpuCount % $i === 0) { $coresPerSocket = $i; break; } } // Nếu không tìm được ước số phù hợp, sử dụng cpuCount if ($coresPerSocket === 1 && $cpuCount > 2) { $coresPerSocket = $cpuCount; } } $payload = [ 'spec' => [ 'count' => $cpuCount, 'cores_per_socket' => $coresPerSocket, 'hot_add_enabled' => true ] ]; $response = Http::withoutVerifying() ->timeout(30) ->withHeaders([ 'vmware-api-session-id' => $this->token, 'Content-Type' => 'application/json', ]) ->patch("https://{$this->apiUri}/rest/vcenter/vm/{$vmId}/hardware/cpu", $payload); if ($response->successful()) { Log::info("✅ API call cập nhật CPU thành công cho VM {$vmId}"); return true; } else { Log::error("❌ API call cập nhật CPU thất bại cho VM {$vmId}", [ 'status' => $response->status(), 'response' => $response->body(), 'url' => "https://{$this->apiUri}/rest/vcenter/vm/{$vmId}/hardware/cpu" ]); return false; } } catch (\Exception $e) { Log::error("❌ Exception khi cập nhật CPU cho VM {$vmId}: " . $e->getMessage()); Log::error("Stack trace: " . $e->getTraceAsString()); return false; } } /** * Cập nhật RAM cho VM */ private function updateVMMemory($vmId, $ramSizeMB): bool { try { Log::info("🔄 Gọi API cập nhật RAM", [ 'vm_id' => $vmId, 'new_ram_mb' => $ramSizeMB ]); $payload = [ 'spec' => [ 'size_MiB' => $ramSizeMB ] ]; $response = Http::withoutVerifying() ->timeout(30) ->withHeaders([ 'vmware-api-session-id' => $this->token, 'Content-Type' => 'application/json', ]) ->patch("https://{$this->apiUri}/rest/vcenter/vm/{$vmId}/hardware/memory", $payload); if ($response->successful()) { Log::info("✅ API call cập nhật RAM thành công cho VM {$vmId}"); return true; } else { Log::error("❌ API call cập nhật RAM thất bại cho VM {$vmId}", [ 'status' => $response->status(), 'response' => $response->body(), 'url' => "https://{$this->apiUri}/rest/vcenter/vm/{$vmId}/hardware/memory" ]); return false; } } catch (\Exception $e) { Log::error("❌ Exception khi cập nhật RAM cho VM {$vmId}: " . $e->getMessage()); Log::error("Stack trace: " . $e->getTraceAsString()); return false; } } /** * Cập nhật Disk cho VM (mở rộng disk) */ private function updateVMDisk($vmId, $diskSizeGB): bool { try { // Tính toán dung lượng disk mới (GB sang bytes) $diskSizeBytes = $diskSizeGB * 1024 * 1024 * 1024; // GB to bytes $payload = [ 'spec' => [ 'capacity' => $diskSizeBytes ] ]; // Lấy thông tin disk hiện tại để biết disk ID $diskResponse = Http::withoutVerifying() ->timeout(30) ->withHeaders([ 'vmware-api-session-id' => $this->token, ]) ->get("https://{$this->apiUri}/rest/vcenter/vm/{$vmId}/hardware/disk"); if (!$diskResponse->successful()) { Log::error("Không thể lấy thông tin disk hiện tại cho VM {$vmId}"); return false; } $diskData = $diskResponse->json(); if (!isset($diskData['value']) || empty($diskData['value'])) { Log::error("Không tìm thấy disk nào cho VM {$vmId}"); return false; } // Lấy disk đầu tiên (thường là disk chính) $firstDisk = $diskData['value'][0]; $diskKey = $firstDisk['key']; $response = Http::withoutVerifying() ->timeout(30) ->withHeaders([ 'vmware-api-session-id' => $this->token, 'Content-Type' => 'application/json', ]) ->patch("https://{$this->apiUri}/rest/vcenter/vm/{$vmId}/hardware/disk/{$diskKey}", $payload); if ($response->successful()) { Log::info("API call cập nhật Disk thành công cho VM {$vmId}"); return true; } else { Log::error("API call cập nhật Disk thất bại cho VM {$vmId}", [ 'status' => $response->status(), 'response' => $response->body() ]); return false; } } catch (\Exception $e) { Log::error("Exception khi cập nhật Disk cho VM {$vmId}: " . $e->getMessage()); return false; } } private function GetFolderByHost($datacenter = '') { return ModuleFolder::where('module_datacenter_id', $datacenter)->first(); } public function CheckVirtualMachine($vmware_id = ''): string|array { $check = $this->checkApiExits(); if ($check) { $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->get("https://$this->apiUri/rest/vcenter/vm/$vmware_id/guest/power")->json(); if (!isset($response['type'])) { return [ 'error' => false, 'data' => $response['value'] ]; } return [ 'error' => true, 'message' => $response['value'] ]; } return "Login_Again"; } public function GetNetworkVirtualMachine($vmware_id = ''): array { $response = $this->GetDetailVirtualMachine($vmware_id); if ($response['error'] === false) { return [ 'error' => false, 'data' => $response['data']['nics'][0]['value']['backing'] ]; } return [ 'error' => true, 'message' => $response['data'] ]; } public function ConfigNetworkVirtualMachine($vmware_id = '', $config = []): int { return Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->patch("https://$this->apiUri/rest/vcenter/vm/$vmware_id/hardware/ethernet/4000", $config) ->status(); } public function ConfigVirtualMachineUbuntu($vmware_id = '', $product = [], $password = ''): void { $this->SetConfig($product->IpAddress->Host->Datacenter->vCenter); $VirtualMachine = "VirtualMachine-$vmware_id"; $IPAddress = $product->IpAddress->ip; $Gateway = $product->IpAddress->gateway; $HostName = strtolower($product->hostname); Log::channel('Custom')->info("\r\n"); try { Log::channel('Custom')->info("==================================================================="); Log::channel('Custom')->info("Get Config information for VirtualMachine with #ID $VirtualMachine"); Log::channel('Custom')->info("\r\n"); $Command = "\$vm = get-vm -Id '$VirtualMachine';\$hostname ='$HostName'; \$ipadd = '$IPAddress'; \$gw = '$Gateway'; \$password = \"'$password'\"; cd /; C:\CLISave\createUbuntu1line.ps1;"; Log::channel('Custom')->info("$Command"); $this->Command($Command); Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("After run Command 1 |" . Carbon::now()); $Command = "\$vm = get-vm -Id '$VirtualMachine'; \$ipadd = '$IPAddress'; \$gw = '$Gateway'; \$password = \"'$password'\"; cd /; C:\CLISave\changeLinux.ps1;"; Log::channel('Custom')->info("$Command"); $this->Command($Command); Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("After run Command 2 |" . Carbon::now()); Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("==================================================================="); } catch (\Exception $error) { Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("==================================================================="); Log::channel('Custom')->info("Error Log: "); Log::channel('Custom')->info($error->getMessage()); Log::channel('Custom')->info("==================================================================="); } } public function GetDetailVirtualMachine($vmware_id = ''): array { $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->get("https://$this->apiUri/rest/vcenter/vm/$vmware_id")->json(); if (!isset($response['type'])) { return [ 'error' => false, 'data' => $response['value'] ]; } return [ 'error' => true, 'message' => $response['value'] ]; } public function GetIPAddressVirtualMachine($vmware_id = ''): array { $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->get("https://$this->apiUri/rest/vcenter/vm/$vmware_id/guest/identity")->json(); if (!isset($response['type'])) { return [ 'error' => false, 'data' => $response['value'] ]; } return [ 'error' => true, 'message' => $response['value'] ]; } public function GetVMStats($vmware_id = ''): array { $stats = [ 'cpu' => null, 'memory' => null, 'disk' => null, ]; try { $hardwareResponse = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->get("https://$this->apiUri/rest/vcenter/vm/$vmware_id/hardware")->json(); if (is_array($hardwareResponse) && !isset($hardwareResponse['type']) && isset($hardwareResponse['value'])) { foreach ($hardwareResponse['value'] as $hardware) { if (isset($hardware['label']) && $hardware['label'] === 'CPU') { $stats['cpu'] = [ 'cores' => $hardware['spec']['num_cores'] ?? 0, 'cores_per_socket' => $hardware['spec']['cores_per_socket'] ?? 0, ]; } if (isset($hardware['label']) && $hardware['label'] === 'Memory') { $stats['memory'] = [ 'size_MiB' => $hardware['spec']['size_MiB'] ?? 0, 'size_GB' => round(($hardware['spec']['size_MiB'] ?? 0) / 1024, 2), ]; } if (isset($hardware['label']) && $hardware['label'] === 'Hard disk') { $stats['disk'][] = [ 'capacity' => $hardware['capacity'] ?? 0, 'capacity_GB' => round(($hardware['capacity'] ?? 0) / (1024 * 1024 * 1024), 2), ]; } } } $guestMemoryResponse = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->get("https://$this->apiUri/rest/vcenter/vm/$vmware_id/guest/memory")->json(); if (is_array($guestMemoryResponse) && !isset($guestMemoryResponse['type']) && isset($guestMemoryResponse['value'])) { $stats['memory']['used_MiB'] = $guestMemoryResponse['value']['used'] ?? 0; $stats['memory']['used_GB'] = round(($guestMemoryResponse['value']['used'] ?? 0) / 1024, 2); $stats['memory']['usage_percent'] = $stats['memory']['size_MiB'] > 0 ? round(($guestMemoryResponse['value']['used'] ?? 0) / $stats['memory']['size_MiB'] * 100, 1) : 0; } $guestCpuResponse = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->get("https://$this->apiUri/rest/vcenter/vm/$vmware_id/guest/cpu")->json(); if (is_array($guestCpuResponse) && !isset($guestCpuResponse['type']) && isset($guestCpuResponse['value'])) { $stats['cpu']['used_percent'] = $guestCpuResponse['value']['used'] ?? 0; } return ['error' => false, 'data' => $stats]; } catch (\Exception $e) { return ['error' => true, 'message' => $e->getMessage(), 'data' => $stats]; } } public function ConfigCentOSVersion8($vmware_id = '', $product = [], $password = ''): void { $this->SetConfig($product->IpAddress->Host->Datacenter->vCenter); $VirtualMachine = "VirtualMachine-$vmware_id"; $IPAddress = $product->IpAddress->ip; $Gateway = $product->IpAddress->gateway; $HostName = strtolower($product->hostname); Log::channel('Custom')->info("\r\n"); try { Log::channel('Custom')->info("==================================================================="); Log::channel('Custom')->info("Get Config information for VirtualMachine with #ID $VirtualMachine"); Log::channel('Custom')->info("\r\n"); $Command = "\$vm = get-vm -Id '$VirtualMachine';\$hostname ='$HostName'; \$ipadd = '$IPAddress'; \$gw = '$Gateway'; \$password = \"'$password'\"; cd /; C:\CLISave\createCentOS81line.ps1;"; Log::channel('Custom')->info("$Command"); $this->Command($Command); Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("After run Command |" . Carbon::now()); Log::channel('Custom')->info("==================================================================="); } catch (\Exception $error) { Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("==================================================================="); Log::channel('Custom')->info("Error Log: "); Log::channel('Custom')->info($error->getMessage()); Log::channel('Custom')->info("==================================================================="); } } public function ConfigCentOSVersion7($vmware_id = '', $product = [], $password = ''): void { $this->SetConfig($product->IpAddress->Host->Datacenter->vCenter); $VirtualMachine = "VirtualMachine-$vmware_id"; $IPAddress = $product->IpAddress->ip; $Gateway = $product->IpAddress->gateway; $HostName = strtolower($product->hostname); Log::channel('Custom')->info("\r\n"); try { Log::channel('Custom')->info("==================================================================="); Log::channel('Custom')->info("Get Config information for VirtualMachine with #ID $VirtualMachine"); Log::channel('Custom')->info("\r\n"); $Command = "\$vm = get-vm -Id '$VirtualMachine'; \$hostname ='$HostName';\$ipadd = '$IPAddress'; \$gw = '$Gateway'; \$password = \"'$password'\"; cd /; C:\CLISave\createCentOS71line.ps1;"; Log::channel('Custom')->info($Command); $this->Command($Command); Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("After run Command |" . Carbon::now()); Log::channel('Custom')->info("==================================================================="); } catch (\Exception $error) { Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("==================================================================="); Log::channel('Custom')->info("Error Log: "); Log::channel('Custom')->info($error->getMessage()); Log::channel('Custom')->info("==================================================================="); } } public function ConfigWindowsVersion10($vmware_id = '', $product = [], $password = ''): void { try { // Kiểm tra null safety if (!$product || !$product->IpAddress || !$product->IpAddress->Host || !$product->IpAddress->Host->Datacenter || !$product->IpAddress->Host->Datacenter->vCenter) { Log::error('❌ ConfigWindowsVersion10: Missing required data', [ 'vmware_id' => $vmware_id, 'product_id' => $product?->id ?? 'unknown', 'ip_address' => $product?->IpAddress?->ip ?? 'unknown', 'host' => $product?->IpAddress?->Host?->name ?? 'unknown', 'datacenter' => $product?->IpAddress?->Host?->Datacenter?->name ?? 'unknown', 'vcenter' => $product?->IpAddress?->Host?->Datacenter?->vCenter?->host ?? 'unknown' ]); return; } $this->SetConfig($product->IpAddress->Host->Datacenter->vCenter); $VirtualMachine = "VirtualMachine-$vmware_id"; $IPAddress = $product->IpAddress->ip; $Gateway = $product->IpAddress->gateway; $HostName = strtolower($product->hostname); Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("==================================================================="); Log::channel('Custom')->info("Get Config information for VirtualMachine with #ID $VirtualMachine"); Log::channel('Custom')->info("\r\n"); $Command = "\$vm = get-vm -Id '$VirtualMachine';\$hostname ='$HostName'; \$ipadd = '$IPAddress'; \$gw = '$Gateway'; \$password = \"'$password'\"; cd /; C:\CLISave\changeWindows10.ps1;"; Log::channel('Custom')->info("$Command"); $this->Command($Command); Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("After run Command |" . Carbon::now()); Log::channel('Custom')->info("==================================================================="); } catch (\Exception $error) { Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("==================================================================="); Log::channel('Custom')->info("Error Log: "); Log::channel('Custom')->info($error->getMessage()); Log::channel('Custom')->info("==================================================================="); } } public function ConfigWindowsServer($vmware_id = '', $product = [], $password = ''): void { try { // Kiểm tra null safety if (!$product || !$product->IpAddress || !$product->IpAddress->Host || !$product->IpAddress->Host->Datacenter || !$product->IpAddress->Host->Datacenter->vCenter) { Log::error('❌ ConfigWindowsServer: Missing required data', [ 'vmware_id' => $vmware_id, 'product_id' => $product?->id ?? 'unknown', 'ip_address' => $product?->IpAddress?->ip ?? 'unknown', 'host' => $product?->IpAddress?->Host?->name ?? 'unknown', 'datacenter' => $product?->IpAddress?->Host?->Datacenter?->name ?? 'unknown', 'vcenter' => $product?->IpAddress?->Host?->Datacenter?->vCenter?->host ?? 'unknown' ]); return; } $this->SetConfig($product->IpAddress->Host->Datacenter->vCenter); $VirtualMachine = "VirtualMachine-$vmware_id"; $IPAddress = $product->IpAddress->ip; $Gateway = $product->IpAddress->gateway; $HostName = strtolower($product->hostname); Log::channel('Custom')->info("==================================================================="); Log::channel('Custom')->info("Get Config information for VirtualMachine with #ID $VirtualMachine"); Log::channel('Custom')->info("\r\n"); $Command = "\$vm = get-vm -Id '$VirtualMachine';\$hostname ='$HostName'; \$ipadd = '$IPAddress'; \$gw = '$Gateway'; \$password = \"'$password'\"; cd /; C:\CLISave\changeWindows1line.ps1;"; Log::channel('Custom')->info("$Command"); $this->Command($Command); Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("After run Command |" . Carbon::now()); Log::channel('Custom')->info("==================================================================="); } catch (\Exception $error) { Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("==================================================================="); Log::channel('Custom')->info("Error Log: "); Log::channel('Custom')->info($error->getMessage()); Log::channel('Custom')->info("==================================================================="); } } public function ConfigFixWindowsClick($vmware_id = '', $product = []): void { try { // Kiểm tra null safety if (!$product || !$product->IpAddress || !$product->IpAddress->Host || !$product->IpAddress->Host->Datacenter || !$product->IpAddress->Host->Datacenter->vCenter) { Log::error('❌ ConfigFixWindowsClick: Missing required data', [ 'vmware_id' => $vmware_id, 'product_id' => $product?->id ?? 'unknown', 'ip_address' => $product?->IpAddress?->ip ?? 'unknown', 'host' => $product?->IpAddress?->Host?->name ?? 'unknown', 'datacenter' => $product?->IpAddress?->Host?->Datacenter?->name ?? 'unknown', 'vcenter' => $product?->IpAddress?->Host?->Datacenter?->vCenter?->host ?? 'unknown' ]); return; } $this->SetConfig($product->IpAddress->Host->Datacenter->vCenter); $VirtualMachine = "VirtualMachine-$vmware_id"; Log::channel('Custom')->info("==================================================================="); Log::channel('Custom')->info("Get Config information for VirtualMachine with #ID $VirtualMachine"); Log::channel('Custom')->info("\r\n"); $Command = "\$vm = get-vm -Id '$VirtualMachine'; cd /; C:\CLISave\FixTwoClickWindows.ps1;"; Log::channel('Custom')->info("$Command"); $this->Command($Command); Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("After run Command |" . Carbon::now()); Log::channel('Custom')->info("==================================================================="); } catch (\Exception $error) { Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("==================================================================="); Log::channel('Custom')->info("Error Log: "); Log::channel('Custom')->info($error->getMessage()); Log::channel('Custom')->info("==================================================================="); } } public function ConfigRockyLinuxVersion9($vmware_id = '', $product = [], $password = ''): void { try { // Kiểm tra null safety if (!$product || !$product->IpAddress || !$product->IpAddress->Host || !$product->IpAddress->Host->Datacenter || !$product->IpAddress->Host->Datacenter->vCenter) { Log::error('❌ ConfigRockyLinuxVersion9: Missing required data', [ 'vmware_id' => $vmware_id, 'product_id' => $product?->id ?? 'unknown', 'ip_address' => $product?->IpAddress?->ip ?? 'unknown', 'host' => $product?->IpAddress?->Host?->name ?? 'unknown', 'datacenter' => $product?->IpAddress?->Host?->Datacenter?->name ?? 'unknown', 'vcenter' => $product?->IpAddress?->Host?->Datacenter?->vCenter?->host ?? 'unknown' ]); return; } $this->SetConfig($product->IpAddress->Host->Datacenter->vCenter); $VirtualMachine = "VirtualMachine-$vmware_id"; $IPAddress = $product->IpAddress->ip; $Gateway = $product->IpAddress->gateway; $HostName = strtolower($product->hostname); Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("==================================================================="); Log::channel('Custom')->info("Get Config information for VirtualMachine with #ID $VirtualMachine"); Log::channel('Custom')->info("\r\n"); $Command = "\$vm = get-vm -Id '$VirtualMachine'; \$hostname ='$HostName';\$ipadd = '$IPAddress'; \$gw = '$Gateway'; \$password = \"'$password'\"; cd /; C:\CLISave\createRocky1line.ps1;"; Log::channel('Custom')->info($Command); $this->Command($Command); Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("After run Command |" . Carbon::now()); Log::channel('Custom')->info("==================================================================="); } catch (\Exception $error) { Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("==================================================================="); Log::channel('Custom')->info("Error Log: "); Log::channel('Custom')->info($error->getMessage()); Log::channel('Custom')->info("==================================================================="); } } public function GetConsoleTickets($vmware_id = ''): array|string { $check = $this->checkApiExits(); if ($check) { $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->post("https://$this->apiUri/api/vcenter/vm/$vmware_id/console/tickets", [ "type" => "WEBMKS" ])->json(); if (!isset($response['error_type'])) { return [ 'error' => false, 'data' => $response['ticket'] ]; } return [ 'error' => true, 'data' => $response['messages'] ]; } return "Login_Again"; } public function SuspendVirtualMachine($vmware_id = ''): array|string { $check = $this->checkApiExits(); if ($check) { $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->post("https://$this->apiUri/rest/vcenter/vm/$vmware_id/power/suspend"); if ($response->status() === 200) { return [ 'error' => false, 'data' => 'success' ]; } return [ 'error' => true, 'data' => $response['value'] ]; } return "Login_Again"; } public function RebootVirtualMachine($product, $vmware_id = ''): array|string { $check = $this->loginServer($product->ipaddress->host->datacenter->vCenter); if ($check) { $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->post("https://$this->apiUri/rest/vcenter/vm/$vmware_id/guest/power?action=reboot"); if ($response->status() === 200) { return [ 'error' => false, 'data' => 'success' ]; } return [ 'error' => true, 'data' => $response['value'] ]; } return "Login_Again"; } public function restartVM($product, $vmware_id = ''): array|string { try { $vmId = $vmware_id ?: ($product->vmware_id ?? null); if (!$vmId) { return [ 'error' => true, 'data' => 'Không tìm thấy vmware_id', ]; } // Ưu tiên quan hệ trực tiếp trên ModuleVmware $vcenter = $product->vCenter ?? null; // Fallback cho dữ liệu cũ chưa có vcenter_id if (!$vcenter && isset($product->ipAddress) && $product->ipAddress?->host?->datacenter?->vCenter) { $vcenter = $product->ipAddress->host->datacenter->vCenter; } if (!$vcenter) { return [ 'error' => true, 'data' => 'Không tìm thấy thông tin vCenter cho VM', ]; } $check = $this->loginServer($vcenter); if (!$check) { return "Login_Again"; } $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->post("https://$this->apiUri/rest/vcenter/vm/$vmId/guest/power?action=reboot"); if ($response->status() === 200) { return [ 'error' => false, 'data' => 'success' ]; } return [ 'error' => true, 'data' => $response->json('value') ?? $response->body(), ]; } catch (\Throwable $e) { return [ 'error' => true, 'data' => $e->getMessage(), ]; } } public function StartVirtualMachine($product, $vmware_id = ''): array|string { $check = $this->loginServer($product->ipaddress->host->datacenter->vCenter); if ($check) { $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->post("https://$this->apiUri/rest/vcenter/vm/$vmware_id/power/start"); if ($response->status() === 200) { return [ 'error' => false, 'data' => 'success' ]; } return [ 'error' => true, 'data' => $response['value'] ]; } return "Login_Again"; } public function ShutdownGuestVirtualMachine($product, $vmware_id = ''): array|string { $check = $this->loginServer($product->ipaddress->host->datacenter->vCenter); if ($check) { $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->post("https://$this->apiUri/rest/vcenter/vm/$vmware_id/guest/power?action=shutdown"); if ($response->status() === 200) { return [ 'error' => false, 'data' => 'success' ]; } return [ 'error' => true, 'data' => $response['value'] ]; } return "Login_Again"; } public function PowerOffVirtualMachine($vmware_id = ''): array|string { $check = $this->checkApiExits(); if ($check) { $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->post("https://$this->apiUri/rest/vcenter/vm/$vmware_id/power/stop"); if ($response->status() === 200) { return [ 'error' => false, 'data' => 'success' ]; } return [ 'error' => true, 'data' => $response['value'] ]; } return "Login_Again"; } public function DeleteVirtualMachine($vmware_id = ''): array|string { $check = $this->checkApiExits(); if ($check) { $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->delete("https://$this->apiUri/rest/vcenter/vm/$vmware_id"); if ($response->status() === 200) { return [ 'error' => false, 'data' => 'success' ]; } return [ 'error' => true, 'data' => $response['value'] ]; } return "Login_Again"; } public function CreateSnapShot($product, ?string $snapshotName = null): string|array { try { $this->username = $product->ipaddress->host->datacenter->vCenter->user; $this->password = SignRSA::DecryptFunc($product->ipaddress->host->datacenter->vCenter->password); $this->apiUri = $product->ipaddress->host->datacenter->vCenter->host; } catch (\Exception $e) { return ['error' => true, 'message' => 'Lỗi setup credentials: ' . $e->getMessage()]; } $vmware_name = (string) $product->name; $check = $this->checkApiExits(); if ($check) { try { $Snapshot = $snapshotName ?: ($vmware_name . "_" . date('d_m_Y_H_i_s')); Log::channel('Custom')->info("==================================================================="); Log::channel('Custom')->info("Create Snapshot with VirtualMachine Name - $vmware_name - Snap #$Snapshot"); Log::channel('Custom')->info("\r\n"); $Command = "\$vm_name = '$vmware_name';\$name = '$Snapshot'; cd /; C:\CLISave\createsnapshot.ps1;"; Log::channel('Custom')->info("$Command"); $this->Command($Command); Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("After run Command |" . Carbon::now()); Log::channel('Custom')->info("==================================================================="); // Clear snapshot cache for this VM $this->clearSnapshotCache($product); return [ 'error' => false, 'data' => $Snapshot ]; } catch (\Exception $error) { Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("==================================================================="); Log::channel('Custom')->info("Error Log: "); Log::channel('Custom')->info($error->getMessage()); Log::channel('Custom')->info("==================================================================="); return [ 'error' => true, 'message' => $error->getMessage() ]; } } return "Login_Again"; } private function clearSnapshotCache($product): void { $vmware_name = (string) $product->name; $cacheKey = "snapshots_{$product->id}_{$vmware_name}"; Cache::forget($cacheKey); Log::channel('Custom')->info("🗑️ Cleared snapshot cache for VM: {$vmware_name}"); } public function getSnapShot($product): string|array { $startTime = microtime(true); try { $this->username = $product->ipaddress->host->datacenter->vCenter->user; $this->password = SignRSA::DecryptFunc($product->ipaddress->host->datacenter->vCenter->password); $this->apiUri = $product->ipaddress->host->datacenter->vCenter->host; } catch (\Exception $e) { return ['error' => true, 'message' => 'Lỗi setup credentials: ' . $e->getMessage()]; } $vmware_name = (string) $product->name; $vmId = $product->vmware_id; // Check cache first $cacheKey = "snapshots_{$product->id}_{$vmware_name}"; $cachedResult = Cache::get($cacheKey); if ($cachedResult !== null) { Log::channel('Custom')->info("📸 Snapshot cache hit"); return $cachedResult; } try { Log::channel('Custom')->info("🚀 Starting SOAP Snapshot Fetch for VM: $vmId ($vmware_name)"); // 1. SOAP Login $sessionToken = $this->soapLogin(); if (!$sessionToken) { return "Login_Again"; } // 2. Retrieve Snapshot Info $snapshotInfo = $this->getVMSnapshotInfoSoap($sessionToken, $vmId); // 3. Flatten Tree $snapshots = []; if (isset($snapshotInfo['rootSnapshotList'])) { // Handle single object vs array in XML to JSON conversion $roots = $snapshotInfo['rootSnapshotList']; // Ensure it's an array if single if (isset($roots['snapshot'])) { // It's a single node structure, allow recurse $snapshots = $this->flattenSnapshotTree($roots); } elseif (is_array($roots)) { foreach ($roots as $root) { $snapshots = array_merge($snapshots, $this->flattenSnapshotTree($root)); } } } // Format similar to old output $formattedSnapshots = []; foreach ($snapshots as $snap) { $formattedSnapshots[] = [ 'name' => $snap['name'], 'description' => $snap['description'] ?? '', 'created' => $snap['createTime'], 'id' => $snap['snapshot']['_'] ?? $snap['snapshot'], // MO Ref Value 'is_current' => false, // Simplification, need logic to check against currentSnapshot 'parent_snapshot_id' => '', // Hard to determining flat parent in simple recursion without passing parent id 'size_gb' => 0 // SOAP doesnt always return size in tree ]; } $response = [ 'error' => false, 'data' => $formattedSnapshots ]; Cache::put($cacheKey, $response, now()->addMinutes(5)); return $response; } catch (\Exception $e) { Log::channel('Custom')->error("❌ SOAP Snapshot Error: " . $e->getMessage()); return [ 'error' => true, 'message' => 'Lỗi lấy snapshot qua SOAP: ' . $e->getMessage() ]; } } private function soapLogin() { $body = ' <_this type="SessionManager">SessionManager ' . htmlspecialchars($this->username) . ' ' . htmlspecialchars($this->password) . ' '; $response = $this->callSoap($body); // Extract session cookie if needed, but Login typically sets checking cookie. // Actually for Http Client, we need to grab the cookie "vmware_soap_session" // But the callSoap helper needs to handle cookies. return $response['cookie'] ?? null; } private function getVMSnapshotInfoSoap($cookie, $vmId) { $body = ' <_this type="PropertyCollector">propertyCollector VirtualMachine false snapshot ' . $vmId . ' '; $result = $this->callSoap($body, $cookie); // Parse simple XML response to finding snapshot info // This is tricky with Regex/SimpleXML. We need to extract the returnval. if (isset($result['body'])) { $xml = simplexml_load_string($result['body']); $xml->registerXPathNamespace('u', 'urn:vim25'); // Navigate to returnval -> objects -> propSet -> val -> rootSnapshotList // Path depends on exact response structure // Simplified return for now: $json = json_encode($xml); $array = json_decode($json, true); // Deep dive into array to find "snapshot" property // This needs robust traversing return $this->findSnapshotPropertyInArray($array); } return []; } private function findSnapshotPropertyInArray($array) { // Recursive search for 'rootSnapshotList' or 'currentSnapshot' if (isset($array['rootSnapshotList'])) return $array; foreach ($array as $key => $value) { if (is_array($value)) { $found = $this->findSnapshotPropertyInArray($value); if ($found) return $found; } } return []; } private function flattenSnapshotTree($node) { $list = []; // Add self $list[] = $node; // Add children if (isset($node['childSnapshotList'])) { $children = $node['childSnapshotList']; // Check if single child or array of children if (isset($children['snapshot'])) { $list = array_merge($list, $this->flattenSnapshotTree($children)); } else { foreach ($children as $child) { $list = array_merge($list, $this->flattenSnapshotTree($child)); } } } return $list; } private function callSoap($soapBody, $cookie = null) { $envelope = ' ' . $soapBody . ' '; $headers = [ 'Content-Type' => 'text/xml; charset=utf-8', 'SOAPAction' => 'urn:vim25/2005' ]; if ($cookie) { $headers['Cookie'] = $cookie; } $response = Http::withoutVerifying() ->withHeaders($headers) ->post("https://{$this->apiUri}/sdk", $envelope); $responseCookie = $response->header('Set-Cookie'); return [ 'body' => $response->body(), 'cookie' => $responseCookie ? $responseCookie : $cookie ]; } public function RemoveSnapShot($product, $snap = ''): string|array { $this->username = $product->IpAddress->Host->Datacenter->vCenter->user; $this->password = SignRSA::DecryptFunc($product->IpAddress->Host->Datacenter->vCenter->password); $vmware_name = (string) $product->name; $check = $this->checkApiExits(); if ($check) { try { Log::channel('Custom')->info("==================================================================="); Log::channel('Custom')->info("Remove Snapshot with VirtualMachine Name - $vmware_name - Snap #$snap"); Log::channel('Custom')->info("\r\n"); $Command = "\$vm_name = '$vmware_name';\$name = '$snap'; cd /; C:\CLISave\deletesnap.ps1;"; Log::channel('Custom')->info("$Command"); $this->Command($Command); Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("After run Command |" . Carbon::now()); Log::channel('Custom')->info("==================================================================="); // Clear snapshot cache for this VM $this->clearSnapshotCache($product); return [ 'error' => false ]; } catch (\Exception $error) { Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("==================================================================="); Log::channel('Custom')->info("Error Log: "); Log::channel('Custom')->info($error->getMessage()); Log::channel('Custom')->info("==================================================================="); return [ 'error' => true, 'message' => $error->getMessage() ]; } } return "Login_Again"; } public function RevertSnapShot($product, $snap = ''): string|array { $this->username = $product->IpAddress->Host->Datacenter->vCenter->user; $this->password = SignRSA::DecryptFunc($product->IpAddress->Host->Datacenter->vCenter->password); $this->apiUri = $product->IpAddress->Host->Datacenter->vCenter->host; $vmware_name = (string) $product->name; $check = $this->checkApiExits(); if ($check) { try { Log::channel('Custom')->info("==================================================================="); Log::channel('Custom')->info("Revert Snapshot with VirtualMachine Name - $vmware_name - Snap #$snap"); Log::channel('Custom')->info("\r\n"); $Command = "\$vm_name = '$vmware_name';\$name = '$snap'; cd /; C:\CLISave\backSnap.ps1;"; Log::channel('Custom')->info("$Command"); $this->Command($Command); Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("After run Command |" . Carbon::now()); Log::channel('Custom')->info("==================================================================="); // Clear snapshot cache for this VM $this->clearSnapshotCache($product); return [ 'error' => false ]; } catch (\Exception $error) { Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("==================================================================="); Log::channel('Custom')->info("Error Log: "); Log::channel('Custom')->info($error->getMessage()); Log::channel('Custom')->info("==================================================================="); return [ 'error' => true, 'message' => $error->getMessage() ]; } } return "Login_Again"; } public function UpgradeMemoryVirtualMachine($vmware_id = '', $ram = 0): array|string { $Check = $this->checkApiExits(); if ($Check) { $RAMConfig = [ "spec" => array( "size_MiB" => $ram * $this->memory, ) ]; $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->patch("https://$this->apiUri/rest/vcenter/vm/$vmware_id/hardware/memory", $RAMConfig); if ($response->status() === 200) { return [ 'error' => false, 'data' => 'success' ]; } return [ 'error' => true, 'data' => $response['value'] ]; } return "Login_Again"; } public function UpgradeCPUVirtualMachine($vmware_id = '', $cpu = 0): array|string { $Check = $this->checkApiExits(); if ($Check) { $CPUConfig = [ "spec" => array( "cores_per_socket" => 1, "count" => $cpu ) ]; $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->patch("https://$this->apiUri/rest/vcenter/vm/$vmware_id/hardware/cpu", $CPUConfig); if ($response->status() === 200) { return [ 'error' => false, 'data' => 'success' ]; } return [ 'error' => true, 'data' => $response['value'] ]; } return "Login_Again"; } public function UpgradeDiskVirtualMachine($product, $disk = 0): array|string { $this->username = $product->IpAddress->Host->Datacenter->vCenter->user; $this->password = SignRSA::DecryptFunc($product->IpAddress->Host->Datacenter->vCenter->password); $vmware_id = $product->vmware_id; $check = $this->checkApiExits(); if ($check) { try { Log::channel('Custom')->info("==================================================================="); Log::channel('Custom')->info("Upgrade Disk with VirtualMachine ID # - $vmware_id"); Log::channel('Custom')->info("\r\n"); $Command = "\$vm = 'VirtualMachine-$vmware_id'; \$disksize = $disk; cd /; C:\CLISave\diskresize.ps1;"; Log::channel('Custom')->info("$Command"); $this->Command($Command); Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("After run Command |" . Carbon::now()); Log::channel('Custom')->info("==================================================================="); return [ 'error' => false ]; } catch (\Exception $error) { Log::channel('Custom')->info("\r\n"); Log::channel('Custom')->info("==================================================================="); Log::channel('Custom')->info("Error Log: "); Log::channel('Custom')->info($error->getMessage()); Log::channel('Custom')->info("==================================================================="); return [ 'error' => true, 'message' => $error->getMessage() ]; } } return "Login_Again"; } public function FindVirtualMachine($name = ''): string|bool { $check = $this->checkApiExits(); if ($check) { try { $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->get( "https://$this->apiUri/rest/vcenter/vm?filter.names.1=$name" )->json(); } catch (ConnectionException $error) { return false; } if ($response['value']) { return $response['value'][0]['vm']; } return false; } return "Login_Again"; } protected function SetConfig($data = []): static { $this->username = $data['user']; $this->password = SignRSA::DecryptFunc($data['password']); return $this; } // New Version Update 07.02.2025 protected function loginVPS(array|null $data = []): ?SSH2 { try { // Lấy thông tin từ $data - Không có thì lấy giá trị mặc định $user = $data['user'] ?? 'root'; $ip = $data['ip'] ?? '103.42.58.37'; $password = $data['password'] ?? 'TGS@1232'; // Kiểm tra hệ điều hành if (str_contains($this->os, "Windows")) { $user = $data['user'] ?? 'administartor'; $this->process = new WinRMClient($ip, $user, $password); ; return $this->process; } // Tạo kết nối SSH $this->process = new SSH2($ip, 22, 10); // Kiểm tra đăng nhập if (!$this->process->login($user, $password)) { throw new Exception("Cannot login to VPS with IP: $ip, User: $user"); } Log::info("Logged into VPS successfully: IP = $ip, User = $user"); return $this->process; } catch (Exception $exception) { // Ghi log lỗi chi tiết Log::error('Login VPS failed: ' . $exception->getMessage()); $this->process = null; return null; } } protected function config_hosname_linux(string $hostname = '') { try { // Xử lý hostname $hostname = preg_replace('/[^a-zA-Z0-9-]/', '', $hostname); $hostname = trim($hostname, '-'); $hostname = substr($hostname, 0, 63); // Đăng nhập VPS $this->loginVPS(); if (!$this->process) { throw new Exception("Cannot login to VPS"); } // Set Host name $this->process->exec("hostnamectl set-hostname " . escapeshellarg($hostname)); Log::info("Hostname changed successfully to: $hostname"); return true; } catch (Exception $exception) { Log::error('Failed to change hostname: ' . $exception->getMessage()); return false; } } protected function set_ip_linux(string $ipaddress = '', string $gateway = ''): bool { try { // Kiểm tra IP address if (!filter_var($ipaddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { throw new Exception("Invalid IP address: $ipaddress"); } // Kiểm tra Gateway if (!filter_var($gateway, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { throw new Exception("Invalid Gateway address: $gateway"); } // Đăng nhập VPS $this->loginVPS(); if (!$this->process) { throw new Exception("Cannot login to VPS"); } // Thực hiện thay đổi IP và Gateway $output1 = $this->process->exec("nmcli con mod ens192 ipv4.addresses $ipaddress/24"); $output2 = $this->process->exec("nmcli con mod ens192 ipv4.gateway $gateway"); $output3 = $this->process->exec("nmcli con up ens192"); if (!$output1 || !$output2 || !$output3) { throw new Exception("Failed to execute one or more network commands."); } Log::info("IP Address changed successfully to: $ipaddress with Gateway: $gateway"); return true; } catch (Exception $exception) { Log::error('Failed to configure IP address: ' . $exception->getMessage()); return false; } } /* * ip = 103.42.58.37 * pwd = TGS@123 */ public function change_password_linux(array $data = ['ip' => '10.10.10.173', 'user' => 'admin', 'password' => 'TGS@123', 'new_password' => '']): bool { try { // Kiểm tra mật khẩu mới if (empty($data['new_password'])) { throw new Exception("New password cannot be empty."); } // Đăng nhập VPS $this->loginVPS($data); if (!$this->process) { throw new Exception("Cannot login to VPS."); } // Lấy thông tin từ $data $username = $data['user'] ?? 'root'; $newPassword = $data['new_password']; // Đảm bảo username không rỗng if (empty($username)) { throw new Exception("Username is invalid."); } // Đổi mật khẩu $this->process->exec("echo " . escapeshellarg("$username:$newPassword") . " | chpasswd"); // Log trạng thái Log::info("Password for user $username changed successfully."); return true; } catch (Exception $exception) { // Ghi log lỗi Log::error('Failed to change password: ' . $exception->getMessage()); return false; } } public function extend_disk_centos_7(): bool { try { // Đăng nhập VPS $this->loginVPS(); if (!$this->process) { throw new Exception("Cannot login to VPS"); } // Kiểm tra xem thiết bị `/dev/sda2` có tồn tại không $checkDisk = $this->process->exec("lsblk | grep -w 'sda2'"); if (empty(trim($checkDisk))) { throw new Exception("Disk /dev/sda2 not found"); } // Thực hiện các lệnh để mở rộng đĩa $this->process->exec("sudo tee /sys/class/block/sda/device/rescan"); $this->process->exec("sudo pvcreate /dev/sda2"); $this->process->exec("sudo vgextend /dev/centos /dev/sda2"); $this->process->exec("sudo lvextend /dev/centos/root /dev/sda2"); $this->process->exec("sudo xfs_growfs /dev/centos/root"); Log::info("Disk extended successfully"); return true; } catch (Exception $exception) { // Ghi log lỗi Log::error('Failed to extend disk: ' . $exception->getMessage()); return false; } } public function extend_disk_centos_8(): bool { try { // Đăng nhập VPS $this->loginVPS(); if (!$this->process) { throw new Exception("Cannot login to VPS"); } // Kiểm tra xem thiết bị `/dev/sda2` có tồn tại không $checkDisk = $this->process->exec("lsblk | grep -w 'sda3'"); if (empty(trim($checkDisk))) { throw new Exception("Disk /dev/sda3 not found"); } // Thực hiện các lệnh để mở rộng đĩa $this->process->exec("sudo pvcreate /dev/sda3"); $this->process->exec("sudo vgextend /dev/cl /dev/sda3"); $this->process->exec("sudo lvextend /dev/cl/root /dev/sd3"); $this->process->exec("sudo xfs_growfs /dev/cl/root"); Log::info("Disk extended successfully"); return true; } catch (Exception $exception) { // Ghi log lỗi Log::error('Failed to extend disk: ' . $exception->getMessage()); return false; } } public function extend_disk_ubuntu(): bool { try { // Đăng nhập VPS $this->loginVPS(); if (!$this->process) { throw new Exception("Cannot login to VPS"); } // Kiểm tra xem thiết bị `/dev/sda3` có tồn tại không $checkDisk = $this->process->exec("lsblk | grep -w 'sda3'"); if (empty(trim($checkDisk))) { throw new Exception("Disk /dev/sda3 not found"); } // Thực hiện các lệnh để mở rộng đĩa $this->process->exec("sudo tee /sys/class/block/sda/device/rescan"); $this->process->exec("sudo growpart /dev/sda 3"); $this->process->exec("sudo pvresize /dev/sda3"); $this->process->exec("sudo lvresize -l+100%FREE --resizefs /dev/mapper/ubuntu--vg-ubuntu--lv"); Log::info("Disk extended successfully"); return true; } catch (Exception $exception) { // Ghi log lỗi Log::error('Failed to extend disk: ' . $exception->getMessage()); return false; } } public function set_ip_window(string $ipaddress = '', string $gateway = '') { try { // Kiểm tra IP address if (!filter_var($ipaddress, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { throw new Exception("Invalid IP address: $ipaddress"); } // Kiểm tra Gateway if (!filter_var($gateway, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) { throw new Exception("Invalid Gateway address: $gateway"); } // Đăng nhập VPS $this->loginVPS(); if (!$this->process) { throw new Exception("Cannot login to VPS"); } // Thực hiện thay đổi IP và Gateway $output1 = $this->process->execute_command("netsh int ip set address Ethernet0 static $ipaddress 255.255.255.0 $gateway"); if (!$output1) { throw new Exception("Failed to execute one or more network commands."); } Log::info("IP Address changed successfully to: $ipaddress with Gateway: $gateway"); return true; } catch (Exception $exception) { Log::error('Failed to configure IP address: ' . $exception->getMessage()); return false; } } public function change_password_windows(array $data = ['ip' => '103.42.58.37', 'user' => 'administrator', 'password' => 'TGS@123', 'new_password' => '']): bool { try { // Kiểm tra mật khẩu mới if (empty($data['new_password'])) { throw new Exception("New password cannot be empty."); } // Đăng nhập VPS $this->loginVPS($data); if (!$this->process) { throw new Exception("Cannot login to VPS."); } // Lấy thông tin từ $data $username = $data['user'] ?? 'administrator'; $newPassword = $data['new_password']; // Đảm bảo username không rỗng if (empty($username)) { throw new Exception("Username is invalid."); } // Đổi mật khẩu $response = $this->process->execute_command("net user \"$username\" \"$newPassword\""); if (stripos($response, 'successfully') === false) { throw new Exception("Failed to change password. Response: $response"); } Log::info("Password changed successfully for user: $username"); return true; } catch (Exception $exception) { // Ghi log lỗi Log::error('Failed to change password: ' . $exception->getMessage()); return false; } } /** * Test method để debug addon updates */ public function testAddonUpdate($vmId, $addon, $base_cpu = 1, $base_ram = 1): array { try { Log::info("🧪 TEST: Bắt đầu test addon update", [ 'vm_id' => $vmId, 'addon' => $addon, 'base_cpu' => $base_cpu, 'base_ram' => $base_ram ]); $this->updateVMHardware($vmId, $addon, $base_cpu, $base_ram); return [ 'error' => false, 'message' => 'Test addon update completed' ]; } catch (\Exception $e) { Log::error("🧪 TEST: Lỗi trong test addon update: " . $e->getMessage()); return [ 'error' => true, 'message' => $e->getMessage() ]; } } /** * Test method để debug getSnapShot function */ public function testGetSnapShot($product): array { try { Log::info("🧪 TEST: Bắt đầu test getSnapShot", [ 'product_id' => $product->id ?? 'unknown', 'product_name' => $product->name ?? 'unknown', 'vmware_id' => $product->vmware_id ?? 'unknown' ]); $result = $this->getSnapShot($product); Log::info("🧪 TEST: Kết quả getSnapShot", [ 'result' => $result, 'product_id' => $product->id ?? 'unknown' ]); return [ 'error' => false, 'message' => 'Test getSnapShot completed', 'result' => $result ]; } catch (\Exception $e) { Log::error("🧪 TEST: Lỗi trong test getSnapShot: " . $e->getMessage()); return [ 'error' => true, 'message' => $e->getMessage() ]; } } /** * Assign IP to Windows VM using PowerShell script */ public function assignIPWindows($product, $vmware_id = ''): array|string { try { // Support both old Product structure and new ModuleVmware structure $ipAddress = null; $gateway = null; $netmask = '255.255.255.0'; $dns = '8.8.8.8'; $vmware_name = ''; $vCenter = null; // Check if it's ModuleVmware (new structure) if ($product && isset($product->ipAddress) && $product->ipAddress) { Log::info('✅ Using ModuleVmware structure for assignIPWindows', [ 'module_vmware_id' => $product->id, 'vmware_id' => $vmware_id ]); // Extract from ModuleVmware->ipAddress relationship $ipAddress = $product->ipAddress->ip ?? null; $gateway = $product->ipAddress->gateway ?? null; $netmask = $product->ipAddress->netmask ?? '255.255.255.0'; $dns = $product->ipAddress->dns ?? '8.8.8.8'; $vmware_name = $product->name ?? $vmware_id; $vCenter = $product->ipAddress->host->datacenter->vCenter ?? null; } elseif ($product && isset($product->IpAddress) && $product->IpAddress) { Log::info('✅ Using old Product structure for assignIPWindows', [ 'product_id' => $product->id, 'vmware_id' => $vmware_id ]); // Extract from old Product->IpAddress relationship $ipAddress = $product->IpAddress->ip ?? null; $gateway = $product->IpAddress->gateway ?? null; $netmask = $product->IpAddress->netmask ?? '255.255.255.0'; $dns = $product->IpAddress->dns ?? '8.8.8.8'; $vmware_name = $product->name ?? $vmware_id; $vCenter = $product->IpAddress->Host->Datacenter->vCenter ?? null; } // Validate required data if (!$ipAddress || !$gateway || !$vCenter) { Log::error('❌ assignIPWindows: Missing required data', [ 'vmware_id' => $vmware_id, 'ip_address' => $ipAddress, 'gateway' => $gateway, 'has_vcenter' => $vCenter ? 'yes' : 'no', 'product_class' => get_class($product) ]); return [ 'error' => true, 'data' => 'Missing required data for IP assignment (IP, Gateway, or vCenter)' ]; } $this->SetConfig($vCenter); // Convert netmask to prefix length (e.g., 255.255.255.0 = 24) $maskBits = $this->convertNetmaskToPrefixLength($netmask); Log::channel('Custom')->info("==================================================================="); Log::channel('Custom')->info("Assign IP to Windows VM - $vmware_name"); Log::channel('Custom')->info("IP: $ipAddress, Gateway: $gateway, DNS: $dns, MaskBits: $maskBits"); Log::channel('Custom')->info("\r\n"); // Create PowerShell command with dynamic values $Command = "\$vmName = '$vmware_name'; \$IP = '$ipAddress'; \$MaskBits = $maskBits; \$Gateway = '$gateway'; \$DNS = '$dns'; cd /; C:\CLISave\AssignIP\assignIPWindows.ps1;"; Log::channel('Custom')->info("Executing Command: $Command"); $output = $this->Command($Command); Log::channel('Custom')->info("Script output: " . $output); Log::channel('Custom')->info("==================================================================="); // Check if execution was successful if (strpos($output, '🎉') !== false || strpos($output, 'successfully') !== false) { return [ 'error' => false, 'data' => 'IP assigned successfully', 'output' => $output ]; } else { return [ 'error' => true, 'data' => 'Failed to assign IP', 'output' => $output ]; } } catch (\Exception $e) { Log::channel('Custom')->error("Exception in assignIPWindows: " . $e->getMessage()); return [ 'error' => true, 'data' => $e->getMessage() ]; } } /** * Convert netmask to prefix length (e.g., 255.255.255.0 = 24) */ private function convertNetmaskToPrefixLength($netmask): int { $netmaskMap = [ '255.255.255.255' => 32, '255.255.255.254' => 31, '255.255.255.252' => 30, '255.255.255.248' => 29, '255.255.255.240' => 28, '255.255.255.224' => 27, '255.255.255.192' => 26, '255.255.255.128' => 25, '255.255.255.0' => 24, '255.255.254.0' => 23, '255.255.252.0' => 22, '255.255.248.0' => 21, '255.255.240.0' => 20, '255.255.224.0' => 19, '255.255.192.0' => 18, '255.255.128.0' => 17, '255.255.0.0' => 16, '255.254.0.0' => 15, '255.252.0.0' => 14, '255.248.0.0' => 13, '255.240.0.0' => 12, '255.224.0.0' => 11, '255.192.0.0' => 10, '255.128.0.0' => 9, '255.0.0.0' => 8 ]; return $netmaskMap[$netmask] ?? 24; // Default to /24 if not found } /** * Parse snapshot output from PowerShell text format */ private function parseSnapshotTextOutput($output): array { $snapshots = []; // Split the output into individual snapshot blocks $blocks = preg_split('/\n\s*\n/', $output); foreach ($blocks as $block) { $block = trim($block); if (empty($block)) continue; // Skip header lines and warnings if ( strpos($block, 'Name Port User') !== false || strpos($block, '---- ---- ----') !== false || strpos($block, 'WARNING:') !== false ) { continue; } $snapshot = []; // Parse each line of the snapshot block $lines = explode("\n", $block); foreach ($lines as $line) { $line = trim($line); if (empty($line)) continue; // Parse key-value pairs if (strpos($line, ' : ') !== false) { list($key, $value) = explode(' : ', $line, 2); $key = trim($key); $value = trim($value); switch ($key) { case 'Name': $snapshot['name'] = $value; break; case 'Description': $snapshot['description'] = $value; break; case 'Created': $snapshot['created'] = $value; break; case 'PowerState': $snapshot['power_state'] = $value; break; case 'VM': $snapshot['vm_name'] = $value; break; case 'VMId': $snapshot['vm_id'] = $value; break; case 'Parent': $snapshot['parent'] = $value; break; case 'ParentSnapshotId': $snapshot['parent_snapshot_id'] = $value; break; case 'ParentSnapshot': $snapshot['parent_snapshot'] = $value; break; case 'Children': $snapshot['children'] = $value; break; case 'SizeMB': $snapshot['size_mb'] = (float) $value; break; case 'SizeGB': $snapshot['size_gb'] = (float) $value; break; case 'IsCurrent': $snapshot['is_current'] = strtolower($value) === 'true'; break; case 'IsReplaySupported': $snapshot['is_replay_supported'] = strtolower($value) === 'true'; break; case 'Id': $snapshot['id'] = $value; break; case 'Uid': $snapshot['uid'] = $value; break; default: // Store any other properties $snapshot[strtolower(str_replace(' ', '_', $key))] = $value; break; } } } // Only add if we have at least a name if (!empty($snapshot['name'])) { $snapshots[] = $snapshot; } } return $snapshots; } /** * Parse snapshot output when JSON parsing fails (legacy method) */ private function parseSnapshotOutput($output): array { $snapshots = []; // Split by lines and look for snapshot information $lines = explode("\n", $output); foreach ($lines as $line) { $line = trim($line); if (empty($line)) continue; // Look for snapshot name patterns if (preg_match('/(\w+_\d{2}_\d{2}_\d{4}_\d{2}_\d{2}_\d{2})/', $line, $matches)) { $snapshots[] = [ 'name' => $matches[1], 'raw_line' => $line ]; } } return $snapshots; } /* * Get Window Remote Port */ public function getWindowsRdpPort(string $vmwareId, string $guestUser, string $guestPassword): ?int { $ps = "(Get-ItemProperty 'HKLM:\\System\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp' -Name PortNumber).PortNumber"; $cmd = "Invoke-VMScript -VM '$vmwareId' " . "-ScriptText \"$ps\" " . "-ScriptType Powershell " . "-GuestUser '$guestUser' " . "-GuestPassword '$guestPassword'"; $output = $this->Command($cmd); if (!$output || str_contains($output, 'Login_Fail')) { return null; } if (preg_match('/\b(\d{2,5})\b/', $output, $m)) { return (int) $m[1]; } return null; } public function reBuildVPS(string $vmName): array|string { if (!$this->checkApiExits()) { return "Login_Again"; } try { $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->get("https://{$this->apiUri}/rest/vcenter/vm", [ 'filter.names' => $vmName ]); if (!$response->successful()) { return [ 'error' => true, 'message' => 'Failed to search for VM', 'details' => $response->json() ]; } $vmList = $response->json()['value'] ?? []; if (empty($vmList)) { return [ 'error' => true, 'message' => "VM not found with name: {$vmName}" ]; } $vmId = $vmList[0]['vm']; $detailResponse = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $this->token ]) ->get("https://{$this->apiUri}/rest/vcenter/vm/{$vmId}"); if (!$detailResponse->successful()) { return [ 'error' => true, 'message' => 'Failed to fetch VM details', 'details' => $detailResponse->json() ]; } $vmData = $detailResponse->json(); $guestOS = $vmData['value']['guest_OS'] ?? 'UNKNOWN'; $scriptPath = ''; $osUpper = strtoupper($guestOS); if (Str::contains($osUpper, 'WINDOWS')) { $scriptPath = 'C:\\CLISave\\Windows\\rebuild.ps1'; } elseif (Str::contains($osUpper, ['UBUNTU', 'LINUX', 'CENTOS', 'DEBIAN'])) { $scriptPath = 'C:\\CLISave\\Ubuntu\\rebuild.ps1'; } else { return [ 'error' => true, 'message' => "Unsupported OS for rebuild: {$guestOS}" ]; } $scriptDir = dirname($scriptPath); $scriptFile = basename($scriptPath); $cmd = " Write-Output 'STARTING_REBUILD'; \$vm = Get-VM -Name '$vmName'; cd '$scriptDir'; if (Test-Path '.\\$scriptFile') { Write-Output 'File found, executing...'; & '.\\$scriptFile' | Out-String } else { Write-Error 'File NOT FOUND: $scriptPath'; } Write-Output 'ENDING_REBUILD'; "; $result = $this->Command($cmd); return [ 'error' => false, 'vm_name' => $vmName, 'vm_id' => $vmId, 'os' => $guestOS, 'script' => $scriptPath, 'result' => $result ]; } catch (\Exception $e) { Log::error("reBuildVPS Exception: " . $e->getMessage()); return [ 'error' => true, 'message' => $e->getMessage() ]; } } }