vCenterRepo->filter( [], ['datacenters'], ['datacenters'], ['id', 'host', 'status'] ); return view('administrator.pages.vcenter.manager', compact('vcenters')); } /** * AJAX — Lazy-load children of a tree node. * * GET vcenter-manager/tree/children?type=datacenter&id=1 * Returns JSON array of { id, type, name, icon, has_children, meta } */ public function treeChildren(Request $request) { $type = $request->input('type'); $id = (int) $request->input('id'); return match ($type) { 'vcenter' => $this->vcenterNodes($id), 'datacenter' => $this->hostNodes($id), 'host' => $this->hostChildNodes($id), default => response()->json([]), }; } private function vcenterNodes(int $vcenterId) { $datacenters = $this->datacenterRepo->filter( ['vcenter_id' => $vcenterId], [], [], ['id', 'name'] ); return response()->json($datacenters->map(fn ($dc) => [ 'id' => $dc->id, 'type' => 'datacenter', 'name' => $dc->name, 'icon' => 'fas fa-building', 'has_children' => true, 'meta' => [], ])); } private function hostNodes(int $datacenterId) { $hosts = $this->hostRepo->filter( ['module_datacenter_id' => $datacenterId], [], ['datastore', 'network'], ['id', 'name', 'template'] ); return response()->json($hosts->map(fn ($h) => [ 'id' => $h->id, 'type' => 'host', 'name' => $h->name, 'icon' => 'fas fa-server', 'has_children' => true, 'meta' => [ 'datastores' => $h->datastore_count ?? 0, 'networks' => $h->network_count ?? 0, 'template' => (bool) $h->template, ], ])); } private function hostChildNodes(int $hostId) { $datastores = $this->datastoreRepo->filter( ['module_host_id' => $hostId], [], [], ['id', 'name', 'type', 'space', 'status'] ); $networks = $this->networkRepo->filter( ['module_host_id' => $hostId], [], ['ipAddresses'], ['id', 'name', 'type'] ); $children = []; foreach ($datastores as $ds) { $children[] = [ 'id' => $ds->id, 'type' => 'datastore', 'name' => $ds->name, 'icon' => 'fas fa-hdd', 'has_children' => false, 'meta' => ['space' => $ds->space, 'type' => $ds->type, 'status' => $ds->status], ]; } foreach ($networks as $net) { $children[] = [ 'id' => $net->id, 'type' => 'network', 'name' => $net->name, 'icon' => 'fas fa-network-wired', 'has_children' => false, 'meta' => ['type' => $net->type, 'ips' => $net->ip_addresses_count ?? 0], ]; } return response()->json($children); } /** * AJAX — Return rendered detail panel HTML for a tree node. * * GET vcenter-manager/tree/detail?type=host&id=3 * Returns JSON { html, type, id } */ public function nodeDetail(Request $request) { $type = $request->input('type'); $id = (int) $request->input('id'); $data = match ($type) { 'vcenter' => $this->vCenterDetail($id), 'datacenter' => $this->datacenterDetail($id), 'host' => $this->hostDetail($id), 'datastore' => $this->datastoreDetail($id), 'network' => $this->networkDetail($id), default => null, }; if (! $data) { return response()->json(['error' => 'Not found'], 404); } $html = view("administrator.pages.vcenter.partials.detail-{$type}", $data)->render(); return response()->json(compact('html', 'type', 'id')); } private function vCenterDetail(int $id): array { $item = $this->vCenterRepo->find($id); $datacenterCount = $this->datacenterRepo->filter(['vcenter_id' => $id])->count(); $vmCount = ModuleVmware::where('vcenter_id', $id)->count(); return compact('item', 'datacenterCount', 'vmCount'); } private function datacenterDetail(int $id): array { $item = $this->datacenterRepo->find($id); $hosts = $this->hostRepo->filter( ['module_datacenter_id' => $id], [], ['datastore', 'network'], ['id', 'name', 'template'] ); $folders = $this->folderRepo->filter( ['module_datacenter_id' => $id], [], [], ['id', 'name', 'type', 'folder'] ); return compact('item', 'hosts', 'folders'); } private function hostDetail(int $id): array { $item = $this->hostRepo->find($id); $datastores = $this->datastoreRepo->filter(['module_host_id' => $id]); $networks = $this->networkRepo->filter( ['module_host_id' => $id], [], ['ipAddresses'], ['id', 'name', 'type'] ); $ipTotal = ModuleIpAddress::where('module_host_id', $id)->count(); $ipFree = ModuleIpAddress::where('module_host_id', $id)->where('status', 0)->count(); return compact('item', 'datastores', 'networks', 'ipTotal', 'ipFree'); } private function datastoreDetail(int $id): array { $item = $this->datastoreRepo->find($id); return compact('item'); } private function networkDetail(int $id): array { $item = $this->networkRepo->find($id); $ips = $this->ipRepo->filter( ['module_network_id' => $id], [], [], ['id', 'ip', 'gateway', 'netmask', 'dns', 'status'] ); return compact('item', 'ips'); } /** * AJAX — DataTables JSON for Virtual Machines scoped by tree node. * * GET vcenter-manager/vms/datatable?scope_type=host&scope_id=2 */ public function vmDatatable(Request $request) { $scopeType = $request->input('scope_type', 'all'); $scopeId = (int) $request->input('scope_id', 0); $query = ModuleVmware::with(['customer', 'ipAddress', 'vCenter', 'os']); if ($scopeType === 'vcenter' && $scopeId) { $query->where('vcenter_id', $scopeId); } elseif ($scopeType === 'datacenter' && $scopeId) { $hostIds = $this->hostRepo ->filter(['module_datacenter_id' => $scopeId], [], [], ['id']) ->pluck('id'); $query->whereHas('ipAddress', fn ($q) => $q->whereIn('module_host_id', $hostIds)); } elseif ($scopeType === 'host' && $scopeId) { $query->whereHas('ipAddress', fn ($q) => $q->where('module_host_id', $scopeId)); } return DataTables::of($query) ->addColumn('customer_name', fn ($vm) => $vm->customer?->fullname ?? 'N/A') ->addColumn('ip_address', fn ($vm) => $vm->ipAddress?->ip ?? 'N/A') ->addColumn('vcenter_host', fn ($vm) => $vm->vCenter?->host ?? 'N/A') ->addColumn('os_name', fn ($vm) => $vm->os?->version ?? 'N/A') ->addColumn('status_badge', fn ($vm) => $vm->getStatus()) ->rawColumns(['status_badge']) ->make(true); } public function wizardVcenters() { $vcenters = $this->vCenterRepo->filter(['status' => 1], [], [], ['id', 'host', 'user']); return response()->json($vcenters); } public function wizardStoreVcenter(Request $request) { $data = $request->validate([ 'host' => 'required|string', 'user' => 'required|string', 'password' => 'required|string', 'status' => 'nullable|integer', ]); try { $vcenter = \App\Models\Vcenter::create([ 'host' => $data['host'], 'user' => $data['user'], 'password' => $data['password'], 'status' => $data['status'] ?? 1, ]); return response()->json([ 'success' => true, 'vcenter' => ['id' => $vcenter->id, 'host' => $vcenter->host, 'user' => $vcenter->user] ]); } catch (\Exception $e) { return response()->json(['error' => $e->getMessage()], 500); } } public function wizardDatacenters(Request $request) { $vcenterId = $request->input('vcenter_id'); $vcenter = $this->vCenterRepo->find($vcenterId); if (!$vcenter) { return response()->json(['error' => 'vCenter not found'], 404); } $vcenterService = new \App\Http\Services\vCenter(); $loginResult = $vcenterService->loginServer($vcenter); if (!$loginResult) { return response()->json(['error' => 'Cannot login to vCenter'], 400); } $datacenters = $vcenterService->getAllDatacenter(); if ($datacenters === false || $datacenters === 'Login_Again') { return response()->json(['error' => 'Cannot get datacenters'], 400); } return response()->json(array_map(fn($dc) => [ 'datacenter_id' => $dc['datacenter'] ?? $dc['datacenter_id'] ?? '', 'name' => $dc['name'] ?? $dc['datacenter'] ?? '', ], $datacenters)); } public function wizardHosts(Request $request) { $vcenterId = $request->input('vcenter_id'); $datacenterId = $request->input('datacenter_id'); $vcenter = $this->vCenterRepo->find($vcenterId); if (!$vcenter) { return response()->json(['error' => 'vCenter not found'], 404); } $vcenterService = new \App\Http\Services\vCenter(); $vcenterService->loginServer($vcenter); $hosts = $vcenterService->getAllHost($datacenterId); if ($hosts === false || $hosts === 'Login_Again') { return response()->json(['error' => 'Cannot get hosts'], 400); } return response()->json(array_map(fn($host) => [ 'host_id' => $host['host'] ?? $host['host_id'] ?? '', 'name' => $host['name'] ?? $host['host'] ?? '', ], $hosts)); } public function wizardDatastoreNetwork(Request $request) { $vcenterId = $request->input('vcenter_id'); $hostId = $request->input('host_id'); $vcenter = $this->vCenterRepo->find($vcenterId); if (!$vcenter) { return response()->json(['error' => 'vCenter not found'], 404); } $vcenterService = new \App\Http\Services\vCenter(); $vcenterService->loginServer($vcenter); $datastores = $vcenterService->getAllDataStore($hostId); $networks = $vcenterService->getNetwork($hostId); return response()->json([ 'datastores' => $datastores ? array_map(fn($ds) => [ 'datastore' => $ds['datastore'] ?? $ds['name'] ?? '', 'name' => $ds['name'] ?? $ds['datastore'] ?? '', 'type' => $ds['type'] ?? 'UNKNOWN', ], $datastores) : [], 'networks' => $networks ? array_map(fn($net) => [ 'network_id' => $net['network'] ?? $net['name'] ?? '', 'name' => $net['name'] ?? $net['network'] ?? '', 'type' => $net['type'] ?? 'UNKNOWN', ], $networks) : [], ]); } public function wizardSave(Request $request) { $data = $request->validate([ 'vcenter_id' => 'required|integer', 'datacenter' => 'required|array', 'host' => 'required|array', 'datastores' => 'nullable|array', 'networks' => 'nullable|array', ]); try { $datacenter = \App\Models\ModuleDatacenter::create([ 'name' => $data['datacenter']['name'], 'datacenter_id' => $data['datacenter']['datacenter_id'], 'vcenter_id' => $data['vcenter_id'], ]); $host = \App\Models\ModuleHost::create([ 'name' => $data['host']['name'], 'host_id' => $data['host']['host_id'], 'module_datacenter_id' => $datacenter->id, ]); foreach ($data['datastores'] ?? [] as $ds) { \App\Models\ModuleDatastore::create([ 'name' => $ds['name'], 'datastore' => $ds['datastore'], 'type' => $ds['type'], 'module_host_id' => $host->id, 'status' => 1, ]); } foreach ($data['networks'] ?? [] as $net) { \App\Models\ModuleNetwork::create([ 'name' => $net['name'], 'network' => $net['network_id'], 'type' => $net['type'], 'module_host_id' => $host->id, ]); } return response()->json(['success' => true, 'message' => 'Thêm mới infrastructure thành công!']); } catch (\Exception $e) { return response()->json(['error' => $e->getMessage()], 500); } } public function powerOn(Request $request) { $vmId = (int) $request->input('vm_id'); $vm = VirtualMachines::find($vmId); if (!$vm) { return response()->json(['error' => true, 'message' => 'VM not found'], 404); } $vmwareId = $vm->vm_id; if (!$vmwareId) { return response()->json(['error' => true, 'message' => 'VM VMware ID not found'], 400); } if ($vm->power_state === 'POWERED_ON') { return response()->json(['error' => true, 'message' => 'VM is already powered on'], 400); } $vcenter = null; if ($vm->datacenter?->vCenter) { $vcenter = $vm->datacenter->vCenter; } if (!$vcenter && $vm->host?->datacenter?->vCenter) { $vcenter = $vm->host->datacenter->vCenter; } if (!$vcenter) { return response()->json(['error' => true, 'message' => 'vCenter not found'], 400); } $vCenterService = new \App\Http\Services\vCenter(); if (!$vCenterService->loginServer($vcenter)) { return response()->json(['error' => true, 'message' => 'Cannot login to vCenter'], 400); } $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $vCenterService->token ]) ->post("https://{$vCenterService->apiUri}/rest/vcenter/vm/$vmwareId/power/start"); if ($response->status() === 200) { $vm->update(['power_state' => 'POWERED_ON']); return response()->json(['error' => false, 'message' => 'Power on command sent successfully']); } $errorData = $response->json(); if (isset($errorData['error_type']) && $errorData['error_type'] === 'ALREADY_IN_DESIRED_STATE') { $vm->update(['power_state' => 'POWERED_ON']); return response()->json(['error' => false, 'message' => 'VM is already powered on']); } return response()->json(['error' => true, 'message' => $errorData['value'] ?? $errorData['error_type'] ?? 'Failed to power on VM'], 400); } public function powerOff(Request $request) { $vmId = (int) $request->input('vm_id'); $vm = VirtualMachines::find($vmId); if (!$vm) { return response()->json(['error' => true, 'message' => 'VM not found'], 404); } $vmwareId = $vm->vm_id; if (!$vmwareId) { return response()->json(['error' => true, 'message' => 'VM VMware ID not found'], 400); } if ($vm->power_state === 'POWERED_OFF') { return response()->json(['error' => true, 'message' => 'VM is already powered off'], 400); } $vcenter = null; if ($vm->datacenter?->vCenter) { $vcenter = $vm->datacenter->vCenter; } if (!$vcenter && $vm->host?->datacenter?->vCenter) { $vcenter = $vm->host->datacenter->vCenter; } if (!$vcenter) { return response()->json(['error' => true, 'message' => 'vCenter not found'], 400); } $vCenterService = new \App\Http\Services\vCenter(); if (!$vCenterService->loginServer($vcenter)) { return response()->json(['error' => true, 'message' => 'Cannot login to vCenter'], 400); } $response = Http::withoutVerifying() ->withHeaders([ 'vmware-api-session-id' => $vCenterService->token ]) ->post("https://{$vCenterService->apiUri}/rest/vcenter/vm/$vmwareId/power/stop"); if ($response->status() === 200) { $vm->update(['power_state' => 'POWERED_OFF']); return response()->json(['error' => false, 'message' => 'Power off command sent successfully']); } $errorData = $response->json(); if (isset($errorData['error_type']) && $errorData['error_type'] === 'ALREADY_IN_DESIRED_STATE') { $vm->update(['power_state' => 'POWERED_OFF']); return response()->json(['error' => false, 'message' => 'VM is already powered off']); } return response()->json(['error' => true, 'message' => $errorData['value'] ?? $errorData['error_type'] ?? 'Failed to power off VM'], 400); } public function restart(Request $request) { $vmId = (int) $request->input('vm_id'); $vm = VirtualMachines::find($vmId); if (!$vm) { return response()->json(['error' => true, 'message' => 'VM not found'], 404); } $vmwareId = $vm->vm_id; if (!$vmwareId) { return response()->json(['error' => true, 'message' => 'VM VMware ID not found'], 400); } $vcenter = $vm->datacenter?->vCenter ?? $vm->host?->datacenter?->vCenter; if (!$vcenter) { return response()->json(['error' => true, 'message' => 'vCenter not found'], 400); } $vCenterService = new \App\Http\Services\vCenter(); if (!$vCenterService->loginServer($vcenter)) { return response()->json(['error' => true, 'message' => 'Cannot login to vCenter'], 400); } $response = Http::withoutVerifying() ->withHeaders(['vmware-api-session-id' => $vCenterService->token]) ->post("https://{$vCenterService->apiUri}/rest/vcenter/vm/$vmwareId/power/reset"); if ($response->status() === 200) { return response()->json(['error' => false, 'message' => 'Restart command sent successfully']); } $errorData = $response->json(); return response()->json([ 'error' => true, 'message' => $errorData['value'] ?? $errorData['error_type'] ?? 'Failed to restart VM', ], 400); } public function snapshot(Request $request) { $request->validate([ 'vm_id' => 'required|integer', 'name' => 'required|string|max:255', 'description' => 'nullable|string|max:1000', ]); $vmId = (int) $request->input('vm_id'); $vm = VirtualMachines::find($vmId); if (!$vm) { return response()->json(['error' => true, 'message' => 'VM not found'], 404); } $vmwareId = $vm->vm_id; if (!$vmwareId) { return response()->json(['error' => true, 'message' => 'VM VMware ID not found'], 400); } $vcenter = $vm->datacenter?->vCenter ?? $vm->host?->datacenter?->vCenter; if (!$vcenter) { return response()->json(['error' => true, 'message' => 'vCenter not found'], 400); } $vCenterService = new \App\Http\Services\vCenter(); if (!$vCenterService->loginServer($vcenter)) { return response()->json(['error' => true, 'message' => 'Cannot login to vCenter'], 400); } $response = Http::withoutVerifying() ->withHeaders(['vmware-api-session-id' => $vCenterService->token]) ->post("https://{$vCenterService->apiUri}/rest/vcenter/vm/$vmwareId/snapshot", [ 'spec' => [ 'name' => $request->input('name'), 'description' => $request->input('description', ''), 'memory' => false, 'quiesce' => false, ], ]); if ($response->status() === 200) { return response()->json(['error' => false, 'message' => 'Snapshot created successfully']); } $errorData = $response->json(); return response()->json([ 'error' => true, 'message' => $errorData['value'] ?? $errorData['error_type'] ?? 'Failed to create snapshot', ], 400); } public function getPoweredOffVms() { $vms = VirtualMachines::whereNotNull('vm_id') ->where('power_state', 'POWERED_OFF') ->get() ->map(function ($vm) { return [ 'id' => $vm->id, 'name' => $vm->name, 'vm_id' => $vm->vm_id, 'power_state' => $vm->power_state, ]; }); return response()->json([ 'error' => false, 'data' => $vms ]); } public function suspend(Request $request) { $vmId = (int) $request->input('vm_id'); // vm_id from frontend = services.vm_id = module_vmware.id (FK) $vmware = ModuleVmware::with('vCenter')->find($vmId); if (!$vmware) { return response()->json(['error' => true, 'message' => 'VM not found'], 404); } $vmwareId = $vmware->vmware_id; if (!$vmwareId) { return response()->json(['error' => true, 'message' => 'VM VMware ID not found'], 400); } $vm = VirtualMachines::where('vm_id', $vmwareId)->first(); if ($vm?->power_state === 'SUSPENDED') { return response()->json(['error' => true, 'message' => 'VM is already suspended'], 400); } $vcenter = $vmware->vCenter; if (!$vcenter) { return response()->json(['error' => true, 'message' => 'vCenter not found'], 400); } $vCenterService = new \App\Http\Services\vCenter(); if (!$vCenterService->loginServer($vcenter)) { return response()->json(['error' => true, 'message' => 'Cannot login to vCenter'], 400); } $response = Http::withoutVerifying() ->withHeaders(['vmware-api-session-id' => $vCenterService->token]) ->post("https://{$vCenterService->apiUri}/rest/vcenter/vm/$vmwareId/power/suspend"); if ($response->status() === 200) { $vm?->update(['power_state' => 'SUSPENDED']); $this->sendSuspendNotification($vmware, $vm); return response()->json(['error' => false, 'message' => 'Suspend command sent successfully']); } $errorData = $response->json(); if (isset($errorData['error_type']) && $errorData['error_type'] === 'ALREADY_IN_DESIRED_STATE') { $vm?->update(['power_state' => 'SUSPENDED']); $this->sendSuspendNotification($vmware, $vm); return response()->json(['error' => false, 'message' => 'VM is already suspended']); } return response()->json([ 'error' => true, 'message' => $errorData['value'] ?? $errorData['error_type'] ?? 'Failed to suspend VM', ], 400); } public function updateConfig(Request $request) { $request->validate([ 'vm_name' => 'required|string', 'cpu' => 'required|integer|min:1', 'ram' => 'required|integer|min:1', ]); $vmware = ModuleVmware::where('name', $request->input('vm_name'))->first(); if (!$vmware) { return response()->json(['error' => true, 'message' => 'VM not found'], 404); } \App\Jobs\VirtualMachine\Provision\UpdateVmConfigJob::dispatch( $vmware->id, (string) $vmware->vmware_id, '', (int) $request->input('cpu'), (int) $request->input('ram'), ); return response()->json(['error' => false, 'message' => 'Đã gửi yêu cầu cập nhật cấu hình VM']); } public function deleteVM(Request $request) { $request->validate([ 'vm_id' => 'required|integer', 'confirm_name' => 'required|string', ]); $vmId = (int) $request->input('vm_id'); $vm = VirtualMachines::find($vmId); if (!$vm) { return response()->json(['error' => true, 'message' => 'VM not found'], 404); } if ($request->input('confirm_name') !== $vm->name) { return response()->json(['error' => true, 'message' => "Tên xác nhận không khớp. Vui lòng nhập đúng: {$vm->name}"], 422); } if ($vm->power_state !== 'POWERED_OFF') { return response()->json(['error' => true, 'message' => 'Vui lòng tắt máy ảo trước khi xoá'], 400); } $vcenter = $vm->datacenter?->vCenter ?? $vm->host?->datacenter?->vCenter; if (!$vcenter) { return response()->json(['error' => true, 'message' => 'vCenter not found'], 400); } $powerShellService = app(\App\Http\Services\vCenter\PowerShellService::class); $q = fn(string $v) => str_replace("'", "''", $v); $vmName = $vm->name; $host = $vcenter->host; $user = $vcenter->user; $password = \App\Http\Facade\SignRSA::DecryptFunc($vcenter->password); $command = "& 'C:\\CLISave\\DeleteVM\\deleteVM.ps1' -VmName '" . $q($vmName) . "' -VCenterHost '" . $q($host) . "' -VCenterUser '" . $q($user) . "' -VCenterPassword '" . $q($password) . "'"; $cmdResult = $powerShellService->runPsCommand($command); if (strpos($cmdResult, 'Login_Fail') !== false) { return response()->json(['error' => true, 'message' => 'SSH Login to PowerShell server failed'], 400); } if (strpos($cmdResult, 'SUCCESS') === false) { return response()->json(['error' => true, 'message' => 'Xoá VM thất bại: ' . $cmdResult], 400); } $vm->delete(); return response()->json(['error' => false, 'message' => 'VM đã được xoá thành công']); } private function sendSuspendNotification(ModuleVmware $vmware, ?VirtualMachines $vm = null): void { $vmware->loadMissing('customer'); if (!$vmware->customer || !$vmware->customer->email) { return; } try { Mail::to($vmware->customer->email) ->queue(new VmSuspendedMail($vmware->customer, $vm?->name ?? $vmware->name, $vmware->vmware_id)); } catch (\Throwable) { // Email failure must not block the suspend response } } }