invoiceRepository->findWithRelationships($invoice_ordernum, [ 'order', 'invoiceItem.addons', 'invoiceItem.os.osGroup', 'customer' ]); if (!$invoice) { return redirect()->route('service.index')->with('error', 'Không tìm thấy dịch vụ.'); } if ($invoice->customer_id !== customer()->id) { return redirect()->route('service.index')->with('error', 'Bạn không có quyền truy cập dịch vụ này.'); } $vmInfo = null; if ($invoice->vm_id) { $vmInfo = $this->getVMInfoFromVCenter($invoice, $vCenterService); $this->dispatchVMInfoUpdateJob($invoice); } $title = "Chi tiết dịch vụ #" . $invoice->id; return view('frontend.service.show', [ 'title' => $title, 'invoice' => $invoice, 'vmInfo' => $vmInfo, 'breadcrumbs' => $this->crums->add(__('Dashboard'))->add('Dịch vụ #' . $invoice->id), ]); } catch (\Exception $e) { Log::error('Error in ServiceController@show: ' . $e->getMessage()); return redirect()->route('service.index')->with('error', 'Có lỗi xảy ra khi tải thông tin dịch vụ.'); } } /** * Lấy thông tin VM từ vCenter API - OPTIMIZED WITH CACHING */ private function getVMInfoFromVCenter($invoice, $vCenterService) { $cacheKey = "vm_info_{$invoice->vm_id}"; $cacheDuration = 300; // 5 minutes if (Cache::has($cacheKey)) { return Cache::get($cacheKey); } try { $orderItem = $invoice->invoiceItem->first(); if (!$orderItem) { Log::warning('Không tìm thấy order item cho invoice', ['invoice_id' => $invoice->id]); $defaultInfo = $this->getDefaultVMInfo($invoice); Cache::put($cacheKey, $defaultInfo, $cacheDuration); return $defaultInfo; } $product = $orderItem->getProductAttribute(); if (!$product) { Log::warning('Không tìm thấy product cho order item', ['order_item_id' => $orderItem->id]); $defaultInfo = $this->getDefaultVMInfo($invoice); Cache::put($cacheKey, $defaultInfo, $cacheDuration); return $defaultInfo; } $host = $product->host; $datacenter = $host?->datacenter; $vcenter = $datacenter?->vcenter; if (!$vcenter) { $defaultInfo = $this->getDefaultVMInfo($invoice); Cache::put($cacheKey, $defaultInfo, $cacheDuration); return $defaultInfo; } $loginSuccess = false; try { $loginSuccess = $vCenterService->loginServer($vcenter); } catch (\Exception $e) { Log::warning('vCenter login failed', ['vm_id' => $invoice->vm_id, 'error' => $e->getMessage()]); } if (!$loginSuccess) { $defaultInfo = $this->getDefaultVMInfo($invoice); Cache::put($cacheKey, $defaultInfo, $cacheDuration); return $defaultInfo; } $vmDetail = null; $ipInfo = null; try { // Set timeout for API calls $vmDetail = $vCenterService->GetDetailVirtualMachine($invoice->vm_id); $ipInfo = $vCenterService->GetIPAddressVirtualMachine($invoice->vm_id); } catch (\Exception $e) { Log::warning('vCenter API calls failed', ['vm_id' => $invoice->vm_id, 'error' => $e->getMessage()]); $defaultInfo = $this->getDefaultVMInfo($invoice); Cache::put($cacheKey, $defaultInfo, $cacheDuration); return $defaultInfo; } if ($vmDetail['error'] === false) { $vmData = $vmDetail['data']; // Lấy thông tin IP $ipAddress = 'N/A'; if ($ipInfo['error'] === false && !empty($ipInfo['data']['ip_address'])) { $ipAddress = $ipInfo['data']['ip_address'] ?? 'N/A'; } elseif ($ipInfo['error'] === false && isset($ipInfo['data']['value']['ip_address'])) { // Fallback for nested structure $ipAddress = $ipInfo['data']['value']['ip_address'] ?? 'N/A'; } // Tính toán thông tin phần cứng $cpuCount = $vmData['cpu']['count'] ?? 1; $memoryMB = $vmData['memory']['size_MiB'] ?? 1024; $memoryGB = round($memoryMB / 1024, 1); $diskSizeGB = 0; if (isset($vmData['disks']) && is_array($vmData['disks'])) { foreach ($vmData['disks'] as $disk) { $diskSizeGB += round(($disk['value']['capacity'] ?? 0) / (1024 * 1024 * 1024), 1); } } $vmInfo = [ 'vm_id' => $invoice->vm_id, 'status' => $vmData['power_state'], 'ip_address' => $ipAddress, 'hostname' => $orderItem->server_name ?? 'Unknown', 'os' => $orderItem->os->osGroup->name ?? 'Unknown', 'cpu' => $cpuCount . ' cores', 'ram' => $memoryGB . ' GB', 'disk' => $diskSizeGB . ' GB', 'uptime' => 'N/A', 'last_updated' => now()->format('Y-m-d H:i:s'), 'vm_name' => $vmData['name'] ?? 'Unknown', 'vm_folder' => $vmData['folder'] ?? 'Unknown', 'vm_resource_pool' => $vmData['resource_pool'] ?? 'Unknown', 'vm_datastore' => $vmData['datastore'] ?? 'Unknown', 'vm_network' => $vmData['nics'][0]['backing']['network'] ?? 'Unknown', 'vm_mac' => $vmData['nics'][0]['mac_address'] ?? 'N/A', 'vm_guest_id' => $vmData['guest_ID'] ?? 'Unknown', 'vm_version' => $vmData['version'] ?? 'Unknown', 'vm_hardware_version' => $vmData['hardware']['version'] ?? 'Unknown', ]; // Cache the successful result Cache::put($cacheKey, $vmInfo, $cacheDuration); return $vmInfo; } else { $defaultInfo = $this->getDefaultVMInfo($invoice); Cache::put($cacheKey, $defaultInfo, $cacheDuration); return $defaultInfo; } } catch (\Exception $e) { Log::error('Error in getVMInfoFromVCenter', ['vm_id' => $invoice->vm_id, 'error' => $e->getMessage()]); $defaultInfo = $this->getDefaultVMInfo($invoice); Cache::put($cacheKey, $defaultInfo, $cacheDuration); return $defaultInfo; } } /** * Lấy thông tin VM mặc định khi không thể kết nối vCenter */ private function getDefaultVMInfo($invoice) { $orderItem = $invoice->invoiceItem->first(); return [ 'vm_id' => $invoice->vm_id, 'status' => 'unknown', 'ip_address' => 'N/A', 'hostname' => $orderItem->server_name ?? 'Unknown', 'os' => $orderItem->os->osGroup->name ?? 'Unknown', 'cpu' => 'N/A', 'ram' => 'N/A', 'disk' => 'N/A', 'uptime' => 'N/A', 'last_updated' => now()->format('Y-m-d H:i:s'), 'vm_name' => 'Unknown', 'vm_folder' => 'Unknown', 'vm_resource_pool' => 'Unknown', 'vm_datastore' => 'Unknown', 'vm_network' => 'Unknown', 'vm_mac' => 'N/A', 'vm_guest_id' => 'Unknown', 'vm_version' => 'Unknown', 'vm_hardware_version' => 'Unknown', ]; } /** * Action để bật/tắt VM (placeholder - chưa implement) */ public function toggleVM(Request $request, $invoice_ordernum) { try { $invoice = Invoice::where('order_num', $invoice_ordernum)->first(); // Kiểm tra quyền truy cập if ($invoice->customer_id !== customer()->id) { return response()->json(['error' => true, 'message' => 'Bạn không có quyền thực hiện thao tác này.']); } $action = $request->input('action'); $response = [ 'success' => true, 'message' => "Đã thực hiện thao tác: {$action}", 'vm_id' => $invoice->vm_id, 'action' => $action ]; // Clear cache when VM state changes if ($invoice->vm_id) { Cache::forget("vm_info_{$invoice->vm_id}"); } return response()->json($response); } catch (\Exception $e) { // Log::error('Error in ServiceController@toggleVM: ' . $e->getMessage()); return response()->json(['error' => true, 'message' => 'Có lỗi xảy ra khi thực hiện thao tác. ' . $e->getMessage()]); } } /** * Lấy thông tin VM từ vCenter (placeholder - chưa implement) */ public function getVMInfo($invoice_id) { try { $invoice = Invoice::findOrFail($invoice_id); // Kiểm tra quyền truy cập if ($invoice->customer_id !== customer()->id) { return response()->json(['error' => true, 'message' => 'Bạn không có quyền truy cập thông tin này.']); } if (!$invoice->vm_id) { return response()->json(['error' => true, 'message' => 'Dịch vụ này chưa có VM ID.']); } // TODO: Implement vCenter API calls here // Hiện tại chỉ trả về data mẫu $vmInfo = [ 'vm_id' => $invoice->vm_id, 'status' => 'running', 'ip_address' => '192.168.1.100', 'hostname' => $invoice->invoiceItem->first()->server_name ?? 'Unknown', 'os' => $invoice->invoiceItem->first()->os->osGroup->name ?? 'Unknown', 'cpu' => '2 cores', 'ram' => '4 GB', 'disk' => '50 GB', 'uptime' => '2 days, 5 hours', 'last_updated' => now()->format('Y-m-d H:i:s') ]; return redirect()->back()->with('success', 'Lấy thông tin VM thành công'); } catch (\Exception $e) { // Log::error('Error in ServiceController@getVMInfo: ' . $e->getMessage()); return response()->json(['error' => true, 'message' => 'Có lỗi xảy ra khi lấy thông tin VM.']); } } /** * Dispatch background job to update VM info */ private function dispatchVMInfoUpdateJob($invoice): void { try { // Check if cache is stale (older than 2 minutes) $cacheKey = "vm_info_{$invoice->vm_id}"; $cachedData = Cache::get($cacheKey); if ( !$cachedData || (isset($cachedData['last_updated']) && now()->diffInMinutes($cachedData['last_updated']) > 2) ) { // Dispatch background job to update VM info \App\Jobs\VirtualMachine\Sync\UpdateVMInfoJob::dispatch($invoice->id, $invoice->vm_id) ->delay(now()->addSeconds(5)) ->onQueue('vm-updates'); } } catch (\Exception $e) { Log::warning('Failed to dispatch VM info update job', [ 'vm_id' => $invoice->vm_id, 'error' => $e->getMessage() ]); } } public function vmConsole(Request $request, $invoice_ordernum) { $cacheKey = 'vm_console_invoice_' . $invoice_ordernum; $data = cache()->remember($cacheKey, 300, function () use ($invoice_ordernum) { $invoice = Invoice::where('order_num', $invoice_ordernum)->first(); if (!$invoice) return null; $order = Order::find($invoice->order_id); if (!$order) return null; $product = ModuleVmware::where('order_id', $order->id)->first(); if (!$product) return null; $title = "VM Console - " . $product->name; return compact('product', 'title', 'invoice'); }); if (!$data) { abort(404, 'Không tìm thấy thông tin VM Console'); } return view('frontend.service.console', $data); } public function createSnapshots(SnapShotRequest $request) { try { $user = customer(); if (!$user) { return redirect()->back()->with('error', 'Chưa đăng nhập.'); } $data = $request->validated(); $productId = $data['product_id']; $product = ModuleVmware::with('ipAddress.host.datacenter.vCenter') ->where('vmware_id', $productId) ->where('customer_id', $user->id) ->first(); if (!$product) { return redirect()->back()->with('error', "Không tìm thấy VPS hoặc bạn không có quyền."); } if ($product->status != 1) { return redirect()->back()->with('error', 'Chỉ tạo snapshot cho VM đang hoạt động.'); } $limit = ModuleSnapshotLimit::where('customer_id', $user->id)->first(); if ($limit) { $count = ModuleSnapshot::where('customer_id', $user->id) ->where('module_vmware_id', $product->id) ->count(); if ($count >= $limit->limit) { return redirect()->back()->with( 'error', "Đã đạt giới hạn snapshot ({$limit->limit})." ); } } $snapshot = ModuleSnapshot::create([ 'name' => 'snapshot-' . now()->format('Ymd-His'), 'customer_id' => $user->id, 'module_vmware_id' => $product->id, 'status' => 'pending', 'created_by' => $user->id, ]); \App\Jobs\VirtualMachine\Snapshot\CreateSnapshot::dispatch( $snapshot->id )->delay(now()->addSeconds(3)); return redirect()->back()->with( 'success', 'Đã gửi yêu cầu tạo snapshot. Hệ thống sẽ xử lý trong nền.' ); } catch (\Throwable $e) { Log::error('Lỗi khi tạo snapshot', [ 'product_id' => $request->product_id ?? null, 'user_id' => $user->id ?? null, 'error' => $e->getMessage(), ]); return redirect()->back()->with( 'error', 'Lỗi hệ thống khi tạo snapshot.' ); } } public function getSnapshots(Request $request) { $user = customer(); if (!$user) { return response()->json([ 'error' => true, 'message' => __('Chưa đăng nhập') ], 401); } $validator = Validator::make($request->all(), [ 'product_id' => 'required|string|max:255', ]); if ($validator->fails()) { return response()->json([ 'error' => true, 'message' => __('Dữ liệu không hợp lệ'), 'errors' => $validator->errors() ], 422); } $product = $this->findVmwareProduct( $this->sanitizeInput($request->product_id), $user->id ); if (!$product) { return response()->json([ 'error' => true, 'message' => __('Không tìm thấy VPS') ], 404); } $snapshots = ModuleSnapshot::query() ->where('customer_id', $user->id) ->where('module_vmware_id', $product->id) ->orderByDesc('created_at') ->get([ 'id', 'name', 'status', 'snapshot_ref', 'error_message', 'created_at', ]); return response()->json([ 'error' => false, 'message' => __('Lấy danh sách snapshot thành công'), 'data' => [ 'vm_name' => e($product->name), 'vm_id' => $product->vmware_id, 'snapshots' => $snapshots, 'total_count' => $snapshots->count(), 'last_updated' => now()->format('Y-m-d H:i:s'), ] ]); } /** * Reset VM password (Windows/Linux) via vCenter helpers */ public function resetVMPassword(Request $request) { $user = customer(); if (!$user) { return response()->json(['error' => true, 'message' => __('Chưa đăng nhập. Vui lòng đăng nhập để sử dụng tính năng này.')], 401); } $validated = Validator::make($request->all(), [ 'product_id' => 'required|string|max:255', 'new_password' => 'required|string|min:8|max:128', 'old_password' => 'required|string|min:8|max:128', ]); if ($validated->fails()) { return response()->json(['error' => true, 'message' => __('Dữ liệu không hợp lệ'), 'errors' => $validated->errors()], 422); } $productId = $this->sanitizeInput($request->product_id); $newPassword = (string) $request->new_password; $oldPassword = (string) $request->old_password; // load & ownership check nhanh (không cần toàn bộ nặng nề vì Job sẽ retry) $product = ModuleVmware::where('vmware_id', $productId) ->where('customer_id', $user->id) ->first(); if (!$product) { return response()->json(['error' => true, 'message' => __('Không tìm thấy VPS hoặc bạn không có quyền truy cập')], 404); } // Dispatch job với delay 5s ResetVmPasswordJob::dispatch( productId: $productId, newPassword: $newPassword, oldPassword: $oldPassword, userId: $user->id, email: $user->email )->delay(now()->addSeconds(5)); // trả accepted để UI biết đang xử lý nền return redirect()->back()->with('success', 'Đã gửi yêu cầu tạo đặt lại mật khẩu. Quá trình sẽ mất 3-5 phút, vui lòng chờ đợi.'); } /** * Change Remote Desktop port (Windows only) */ public function changeRemotePort(Request $request) { $user = customer(); if (!$user) { return response()->json(['error' => true, 'message' => __('Chưa đăng nhập. Vui lòng đăng nhập để sử dụng tính năng này.')], 401); } $validated = Validator::make($request->all(), [ 'product_id' => 'required|string|max:255', 'new_port' => 'required|string|min:4|max:128', 'password' => 'required|string|min:4|max:128', ]); if ($validated->fails()) { return response()->json(['error' => true, 'message' => __('Dữ liệu không hợp lệ'), 'errors' => $validated->errors()], 422); } $productId = $this->sanitizeInput($request->product_id); $newPort = (string) $request->new_port; // load & ownership check nhanh (không cần toàn bộ nặng nề vì Job sẽ retry) $product = ModuleVmware::where('vmware_id', $productId) ->where('customer_id', $user->id) ->first(); if (!$product) { return response()->json(['error' => true, 'message' => __('Không tìm thấy VPS hoặc bạn không có quyền truy cập')], 404); } // Dispatch job với delay 5s ChangeRemotePortJob::dispatch( productId: $productId, newPort: $newPort, password: (string) $request->password, userId: $user->id, email: $user->email )->delay(now()->addSeconds(5)); // trả accepted để UI biết đang xử lý nền return redirect()->back()->with('success', 'Đã gửi yêu cầu thay đổi cổng Remote Desktop. Quá trình sẽ mất 3-5 phút, vui lòng chờ đợi.'); } /** * Change public IP for VM (request-based), logs request. */ public function changeIP(Request $request) { try { $user = customer(); if (!$user) { return response()->json([ 'error' => true, 'message' => __('Chưa đăng nhập. Vui lòng đăng nhập để sử dụng tính năng này.'), ], 401); } $validated = FacadesValidator::make($request->all(), [ 'product_id' => 'required|string|max:255', 'ip' => ['required', 'string', 'regex:/^(?:(?:[0-9]{1,3}\.){3}[0-9]{1,3})$/'], ]); if ($validated->fails()) { return response()->json([ 'error' => true, 'message' => __('Dữ liệu không hợp lệ'), 'errors' => $validated->errors(), ], 422); } $productId = $this->sanitizeInput($request->product_id); $desiredIp = $this->sanitizeInput($request->ip); // Persist an audit log entry for ops to handle allocation if needed Log::info('Khách yêu cầu đổi IP', [ 'user_id' => $user->id, 'product_vmware_id' => $productId, 'desired_ip' => $desiredIp, ]); // Here you could enqueue a Job to validate/assign IP from pool // For now, return accepted message return response()->json([ 'error' => false, 'message' => __('Yêu cầu đổi IP đã được ghi nhận, chúng tôi sẽ xử lý trong thời gian sớm nhất.'), ]); } catch (\Exception $e) { Log::error('Lỗi changeIP', [ 'user_id' => customer()?->id, 'error' => $e->getMessage(), ]); return response()->json([ 'error' => true, 'message' => __('Có lỗi xảy ra khi đổi IP'), ], 500); } } /** * Renew service for selected months */ public function renew(Request $request) { try { $user = customer(); if (!$user) { return response()->json([ 'error' => true, 'message' => __('Chưa đăng nhập. Vui lòng đăng nhập để sử dụng tính năng này.'), ], 401); } $validated = FacadesValidator::make($request->all(), [ 'product_id' => 'required|string|max:255', 'plan' => 'required|string|in:3,6,12,24,other', 'months' => 'nullable|integer|min:1|max:60', ]); if ($validated->fails()) { return response()->json([ 'error' => true, 'message' => __('Dữ liệu không hợp lệ'), 'errors' => $validated->errors(), ], 422); } $months = $request->plan === 'other' ? (int) ($request->months ?? 1) : (int) $request->plan; $productId = $this->sanitizeInput($request->product_id); // Fetch product price based on invoice item link $invoiceItem = InvoiceItem::whereHas('invoice', function ($q) use ($user) { $q->where('customer_id', $user->id); }) ->where('product_type', 4) // VPS ->whereHas('invoice', fn($q) => $q->where('status', '!=', 0)) ->first(); $product = $invoiceItem?->getProductAttribute(); $unitPrice = (int) ($product?->price_sale ?? $product?->price ?? 0); $total = $unitPrice * max($months, 1); Log::info('Khách yêu cầu gia hạn dịch vụ', [ 'user_id' => $user->id, 'product_vmware_id' => $productId, 'months' => $months, 'unit_price' => $unitPrice, 'total' => $total, ]); // TODO: implement actual renewal billing/workflow return response()->json([ 'error' => false, 'message' => __('Yêu cầu gia hạn đã được ghi nhận'), 'data' => [ 'unit_price' => $unitPrice, 'months' => $months, 'total' => $total, ], ]); } catch (\Exception $e) { Log::error('Lỗi renew', [ 'user_id' => customer()?->id, 'error' => $e->getMessage(), ]); return response()->json([ 'error' => true, 'message' => __('Có lỗi xảy ra khi gia hạn'), ], 500); } } /** * Secure remove snapshot using session authentication - NO BEARER TOKEN */ public function removeSnapshot(Request $request) { try { $user = customer(); if (!$user) { return response()->json([ 'error' => true, 'message' => 'Chưa đăng nhập. Vui lòng đăng nhập để sử dụng tính năng này.' ], 401); } $validator = Validator::make($request->all(), [ 'product_id' => 'required|string', 'snapshot_name' => 'required|string', ]); if ($validator->fails()) { return response()->json([ 'error' => true, 'message' => 'Dữ liệu không hợp lệ', 'errors' => $validator->errors() ], 422); } $productId = $request->product_id; $snapshotName = $request->snapshot_name; $userId = $user->id; // Tìm VPS $product = ModuleVmware::with('ipAddress.host.datacenter.vCenter') ->where('vmware_id', $productId) ->where('customer_id', $userId) ->first(); if (!$product) { return response()->json([ 'error' => true, 'message' => "Không tìm thấy VPS #{$productId} hoặc bạn không có quyền truy cập" ], 404); } // Tìm snapshot trong database $snapshot = ModuleSnapshot::where('customer_id', $userId) ->where('module_vmware_id', $product->id) ->where('name', $snapshotName) ->first(); if (!$snapshot) { return response()->json([ 'error' => true, 'message' => "Không tìm thấy snapshot '{$snapshotName}' hoặc bạn không có quyền truy cập" ], 404); } // Kiểm tra trạng thái VPS if ($product->status != 1) { return response()->json([ 'error' => true, 'message' => 'Chỉ có thể xóa snapshot cho máy ảo đang hoạt động' ], 400); } // Dispatch job để xóa snapshot trong background \App\Jobs\VirtualMachine\Snapshot\RemoveSnapshot::dispatch($product->id, $snapshot->id) ->delay(now()->addSeconds(5)) ->onQueue('snapshots'); return response()->json([ 'error' => false, 'message' => 'Đã gửi yêu cầu xóa snapshot. Quá trình sẽ mất 1-2 phút, vui lòng chờ đợi.' ]); } catch (\Exception $e) { Log::error('❌ Lỗi khi xóa snapshot', [ 'product_id' => $request->product_id ?? 'not_provided', 'snapshot_name' => $request->snapshot_name ?? 'not_provided', 'user_id' => $user->id ?? 'guest', 'error' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine() ]); return response()->json([ 'error' => true, 'message' => 'Lỗi hệ thống khi xóa snapshot: ' . $e->getMessage() ], 500); } } /** * Secure revert snapshot using session authentication - NO BEARER TOKEN */ public function revertSnapshot(Request $request) { try { $user = customer(); if (!$user) { return response()->json([ 'error' => true, 'message' => 'Chưa đăng nhập. Vui lòng đăng nhập để sử dụng tính năng này.' ], 401); } $validator = Validator::make($request->all(), [ 'product_id' => 'required|string', 'snapshot_name' => 'required|string', ]); if ($validator->fails()) { return response()->json([ 'error' => true, 'message' => 'Dữ liệu không hợp lệ', 'errors' => $validator->errors() ], 422); } $productId = $request->product_id; $snapshotName = $request->snapshot_name; $userId = $user->id; // Tìm VPS $product = ModuleVmware::with('ipAddress.host.datacenter.vCenter') ->where('vmware_id', $productId) ->where('customer_id', $userId) ->first(); if (!$product) { return response()->json([ 'error' => true, 'message' => "Không tìm thấy VPS #{$productId} hoặc bạn không có quyền truy cập" ], 404); } // Tìm snapshot trong database $snapshot = ModuleSnapshot::where('customer_id', $userId) ->where('module_vmware_id', $product->id) ->where('name', $snapshotName) ->first(); if (!$snapshot) { return response()->json([ 'error' => true, 'message' => "Không tìm thấy snapshot '{$snapshotName}' hoặc bạn không có quyền truy cập" ], 404); } // Kiểm tra trạng thái VPS if ($product->status != 1) { return response()->json([ 'error' => true, 'message' => 'Chỉ có thể khôi phục snapshot cho máy ảo đang hoạt động' ], 400); } // Dispatch job để khôi phục snapshot trong background \App\Jobs\VirtualMachine\Snapshot\RevertSnapshot::dispatch($product->id, $snapshot->id) ->delay(now()->addSeconds(5)) ->onQueue('snapshots'); return response()->json([ 'error' => false, 'message' => 'Đã gửi yêu cầu khôi phục snapshot. Quá trình sẽ mất 2-3 phút, vui lòng chờ đợi.' ]); } catch (\Exception $e) { Log::error('❌ Lỗi khi khôi phục snapshot', [ 'product_id' => $request->product_id ?? 'not_provided', 'snapshot_name' => $request->snapshot_name ?? 'not_provided', 'user_id' => $user->id ?? 'guest', 'error' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine() ]); return response()->json([ 'error' => true, 'message' => 'Lỗi hệ thống khi khôi phục snapshot: ' . $e->getMessage() ], 500); } } /** * Secure start VM using session authentication - NO BEARER TOKEN */ /** * Secure start VM using session authentication - NO BEARER TOKEN */ public function startVM(Request $request) { try { $user = customer(); if (!$user) { return response()->json([ 'error' => true, 'message' => __('Chưa đăng nhập. Vui lòng đăng nhập để sử dụng tính năng này.') ], 401); } $validator = Validator::make($request->all(), [ 'product_id' => 'required|string|max:255', ]); if ($validator->fails()) { return response()->json([ 'error' => true, 'message' => __('Dữ liệu không hợp lệ'), 'errors' => $validator->errors() ], 422); } $productId = $request->product_id; $userId = $user->id; $product = ModuleVmware::where('vmware_id', $productId) ->where('customer_id', $userId) ->first(); if (!$product) { return response()->json([ 'error' => true, 'message' => __('Không tìm thấy VPS hoặc bạn không có quyền truy cập') ], 404); } // Dispatch Job \App\Jobs\VirtualMachine\Power\StartVmJob::dispatch($product->id, $userId); return response()->json([ 'error' => false, 'message' => __('Yêu cầu khởi động máy ảo đã được gửi. Vui lòng chờ trong giây lát.') ]); } catch (\Exception $e) { Log::error('Lỗi khi khởi động máy ảo', [ 'user_id' => $user->id ?? 'guest', 'product_id' => $request->product_id ?? 'not_provided', 'error' => $e->getMessage() ]); return response()->json([ 'error' => true, 'message' => __('Có lỗi xảy ra khi khởi động máy ảo') ], 500); } } /** * Secure stop VM using session authentication - NO BEARER TOKEN */ public function stopVM(Request $request) { try { $user = customer(); if (!$user) { return response()->json([ 'error' => true, 'message' => __('Chưa đăng nhập. Vui lòng đăng nhập để sử dụng tính năng này.') ], 401); } $validator = Validator::make($request->all(), [ 'product_id' => 'required|string|max:255', ]); if ($validator->fails()) { return response()->json([ 'error' => true, 'message' => __('Dữ liệu không hợp lệ'), 'errors' => $validator->errors() ], 422); } $productId = htmlspecialchars(strip_tags(trim($request->product_id)), ENT_QUOTES, 'UTF-8'); $userId = $user->id; $product = ModuleVmware::where('vmware_id', $productId) ->where('customer_id', $userId) ->first(); if (!$product) { return response()->json([ 'error' => true, 'message' => __('Không tìm thấy VPS hoặc bạn không có quyền truy cập') ], 404); } // Dispatch Job \App\Jobs\VirtualMachine\Power\StopVmJob::dispatch($product->id, $userId); return response()->json([ 'error' => false, 'message' => __('Yêu cầu tắt máy ảo đã được gửi. Vui lòng chờ trong giây lát.') ]); } catch (\Exception $e) { Log::error('Lỗi khi tắt máy ảo', [ 'user_id' => $user->id ?? 'guest', 'product_id' => $request->product_id ?? 'not_provided', 'error' => $e->getMessage() ]); return response()->json([ 'error' => true, 'message' => __('Có lỗi xảy ra khi tắt máy ảo') ], 500); } } /** * Secure reboot VM using session authentication - NO BEARER TOKEN */ public function rebootVM(Request $request) { try { $user = customer(); if (!$user) { return response()->json([ 'error' => true, 'message' => __('Chưa đăng nhập. Vui lòng đăng nhập để sử dụng tính năng này.') ], 401); } $validator = Validator::make($request->all(), [ 'product_id' => 'required|string|max:255', ]); if ($validator->fails()) { return response()->json([ 'error' => true, 'message' => __('Dữ liệu không hợp lệ'), 'errors' => $validator->errors() ], 422); } $productId = htmlspecialchars(strip_tags(trim($request->product_id)), ENT_QUOTES, 'UTF-8'); $userId = $user->id; $product = ModuleVmware::where('vmware_id', $productId) ->where('customer_id', $userId) ->first(); if (!$product) { return response()->json([ 'error' => true, 'message' => __('Không tìm thấy VPS hoặc bạn không có quyền truy cập') ], 404); } // Dispatch Job \App\Jobs\VirtualMachine\Power\RebootVmJob::dispatch($product->id, $userId); return response()->json([ 'error' => false, 'message' => __('Yêu cầu khởi động lại máy ảo đã được gửi. Vui lòng chờ trong giây lát.') ]); } catch (\Exception $e) { Log::error('Lỗi khi reboot VM', [ 'user_id' => $user->id ?? 'guest', 'product_id' => $request->product_id ?? 'not_provided', 'error' => $e->getMessage() ]); return response()->json([ 'error' => true, 'message' => __('Có lỗi xảy ra khi khởi động lại máy ảo') ], 500); } } public function reinstallVM(Request $request, vCenter $vCenterService) { try { $user = customer(); if (!$user) { Log::warning('User not logged in for reinstallVM'); return response()->json([ 'error' => true, 'message' => __('Chưa đăng nhập. Vui lòng đăng nhập để sử dụng tính năng này.') ], 401); } $validator = Validator::make($request->all(), [ 'invoice_id' => 'required|integer', ]); if ($validator->fails()) { Log::warning('Validation failed for reinstallVM', [ 'errors' => $validator->errors()->toArray() ]); return response()->json([ 'error' => true, 'message' => __('Dữ liệu không hợp lệ'), 'errors' => $validator->errors() ], 422); } $invoice = Invoice::find($request->invoice_id); if (!$invoice) { return response()->json([ 'error' => true, 'message' => __('Không tìm thấy hóa đơn') ], 404); } // Debug: Log product lookup $invoiceItem = $invoice->invoiceItem->first(); $productId = $invoiceItem?->getProductAttribute()?->id; $vmware = $productId; $osId = $request->os_id; $userId = $user->id; // Tìm product và kiểm tra quyền sở hữu $vm = ModuleVmware::where('vmware_id', $vmware->vmware_id) ->where('customer_id', $userId) ->first(); Log::info('VM lookup result', [ 'vm_found' => $vm ? 'yes' : 'no', 'vm_data' => $vm ? $vm->toArray() : null ]); if (!$vm) { return response()->json([ 'error' => true, 'message' => __('Không tìm thấy VPS hoặc bạn không có quyền truy cập') ], 404); } // Kiểm tra OS có tồn tại và hợp lệ không $os = Os::find($osId); if (!$os) { return response()->json([ 'error' => true, 'message' => __('Hệ điều hành không tồn tại') ], 404); } // Kiểm tra OS có được phép cho VPS này không $allowedOs = $vm->operatingSystems()->where('os.id', $osId)->first(); if (!$allowedOs) { return response()->json([ 'error' => true, 'message' => __('Hệ điều hành này không được phép cho VPS này') ], 403); } $result = $vCenterService->DeleteVirtualMachine($vm->vmware_id); if (is_array($result) && isset($result['error']) && $result['error'] === false) { // Cập nhật OS trong database $vm->update([ 'os_id' => $osId ]); Cache::forget('vm_info_' . $vm->vmware_id); return response()->json([ 'error' => false, 'message' => __('Yêu cầu cài lại máy ảo đã được gửi thành công. Quá trình sẽ mất 5-10 phút.') ]); } else { $msg = is_array($result) && isset($result['data']) ? $result['data'] : __('Không thể cài lại máy ảo'); Log::warning('vCenter call failed', [ 'result' => $result, 'message' => $msg ]); return response()->json([ 'error' => true, 'message' => $msg ], 500); } } catch (\Exception $e) { Log::error('Lỗi khi cài lại máy ảo', [ 'user_id' => $user->id ?? 'guest', 'product_id' => $request->product_id ?? 'not_provided', 'os_id' => $request->os_id ?? 'not_provided', 'error' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine() ]); return response()->json([ 'error' => true, 'message' => __('Có lỗi xảy ra khi cài lại máy ảo') ], 500); } } /** * Sanitize input data */ private function sanitizeInput($input): string { return htmlspecialchars(strip_tags(trim($input)), ENT_QUOTES, 'UTF-8'); } /** * Sanitize output data */ private function sanitizeOutput($output): string { return htmlspecialchars($output, ENT_QUOTES, 'UTF-8'); } /** * Kiểm tra cache có hết hạn chưa */ private function isCacheExpired($cachedData): bool { if (!isset($cachedData['last_updated'])) { return true; } $lastUpdated = \Carbon\Carbon::parse($cachedData['last_updated']); return now()->diffInMinutes($lastUpdated) > 2; // Cache 2 phút } /** * Tìm VPS với eager loading */ private function findVmwareProduct($productId, $userId) { return ModuleVmware::with(['ipAddress.host.datacenter.vCenter']) ->where('vmware_id', $productId) ->where('customer_id', $userId) ->first(); } /** * Validate vCenter configuration */ private function validateVcenterConfiguration($product) { if (!$product->ipAddress?->host?->datacenter?->vCenter) { return null; } return $product->ipAddress->host->datacenter->vCenter; } /** * Lấy snapshot từ vCenter */ private function fetchSnapshotsFromVCenter($serviceVCenter, $product, $vcenter) { try { // Login to vCenter if (!$serviceVCenter->loginServer($vcenter)) { return false; } // Kiểm tra method tồn tại if (!method_exists($serviceVCenter, 'getSnapShot')) { Log::error('getSnapShot method không tồn tại trong ServiceVCenter'); return false; } $result = $serviceVCenter->getSnapShot($product); // Retry logic if ($result === "Login_Again") { if ($serviceVCenter->loginServer($vcenter)) { $result = $serviceVCenter->getSnapShot($product); } else { return false; } } // Validate result if (is_string($result) || (isset($result['error']) && $result['error'])) { return false; } return $result['data'] ?? []; } catch (\Exception $e) { Log::error('Lỗi khi lấy snapshot từ vCenter', [ 'product_id' => $product->id, 'error' => $e->getMessage() ]); return false; } } /** * Lấy snapshot từ database */ private function getDatabaseSnapshots($userId, $productId) { return ModuleSnapshot::where('customer_id', $userId) ->where('module_vmware_id', $productId) ->get() ->keyBy('name'); } /** * Xử lý và sanitize snapshot data */ private function processSnapshotsData($snapshots, $dbSnapshots): array { $processed = []; if (!is_array($snapshots)) { return $processed; } foreach ($snapshots as $snapshot) { $snapshotName = $this->sanitizeOutput($snapshot['name'] ?? $snapshot['Name'] ?? 'Unknown'); $dbSnapshot = $dbSnapshots->get($snapshotName); $processed[] = [ 'name' => $snapshotName, 'description' => $this->sanitizeOutput($snapshot['description'] ?? $snapshot['Description'] ?? ''), 'created' => $this->sanitizeOutput($snapshot['created'] ?? $snapshot['Created'] ?? ''), 'power_state' => $this->sanitizeOutput($snapshot['power_state'] ?? $snapshot['PowerState'] ?? ''), 'size_mb' => (int) ($snapshot['size_mb'] ?? $snapshot['SizeMB'] ?? 0), 'size_gb' => (float) ($snapshot['size_gb'] ?? $snapshot['SizeGB'] ?? 0), 'is_current' => (bool) ($snapshot['is_current'] ?? $snapshot['IsCurrent'] ?? false), 'is_replay_supported' => (bool) ($snapshot['is_replay_supported'] ?? $snapshot['IsReplaySupported'] ?? false), 'in_database' => $dbSnapshot ? true : false, 'db_created_at' => $dbSnapshot ? $dbSnapshot->created_at->format('d/m/Y H:i:s') : null ]; } return $processed; } }