validate([ 'host' => 'required|string|max:255', 'user' => 'required|string|max:255', 'password' => 'required|string|max:255', ]); $vc = vCenter::create([ 'host' => $request->input('host'), 'user' => $request->input('user'), 'password' => SignRSA::EncryptFunc($request->input('password')), 'status' => 1, ]); return response()->json(['error' => false, 'data' => $vc], 201); } public function consoleTicket(Request $request, ServiceVCenter $serviceVCenter): JsonResponse { $vmDbId = $request->integer('vm_id'); if (!$vmDbId) { return response()->json(['error' => true, 'message' => 'Thiếu vm_id'], 422); } $vm = VirtualMachines::with('datacenter.vCenter')->find($vmDbId); if (!$vm) { return response()->json(['error' => true, 'message' => 'Không tìm thấy VM'], 404); } $vcenter = $vm->datacenter?->vCenter ?? null; if (!$vcenter) { return response()->json(['error' => true, 'message' => 'Không tìm thấy thông tin vCenter'], 404); } if (!$serviceVCenter->loginServer($vcenter)) { return response()->json(['error' => true, 'message' => 'Không thể đăng nhập vào vCenter'], 500); } $ticket = $serviceVCenter->GetConsoleTickets($vm->vm_id); if ($ticket === 'Login_Again') { if ($serviceVCenter->loginServer($vcenter)) { $ticket = $serviceVCenter->GetConsoleTickets($vm->vm_id); } else { return response()->json(['error' => true, 'message' => 'Không thể đăng nhập lại vào vCenter'], 500); } } if (is_string($ticket)) { return response()->json(['error' => true, 'message' => "Lấy ticket thất bại: $ticket"], 500); } if ($ticket['error']) { return response()->json(['error' => true, 'message' => $ticket['data']], 500); } return response()->json([ 'error' => false, 'data' => preg_replace( '#^wss://[^/]+#', 'wss://' . self::CONSOLE_HOST, $ticket['data'] ), 'host' => $vcenter->host, 'vmware-api-session-id' => $serviceVCenter->token, ])->cookie('vmware-api-session-id', $serviceVCenter->token, 60, '/', null, false, false); } public function sync(): JsonResponse { dispatch(new SyncAllPackage())->delay(now()->addSeconds(5)); return response()->json([ 'error' => false, 'message' => 'Sync job dispatched, running in background.', ]); } public function storeNode(Request $request, string $type): JsonResponse { $rules = match ($type) { 'datacenter' => [ 'vcenter_id' => 'required|integer|exists:module_vcenter,id', 'name' => 'required|string|max:255', 'datacenter_id' => 'nullable|string|max:255', ], 'host' => [ 'module_datacenter_id' => 'required|integer|exists:module_datacenter,id', 'name' => 'required|string|max:255', 'host_id' => 'nullable|string|max:255', 'template' => 'nullable|string|max:500', 'console' => 'nullable|string|max:500', 'iso_path' => 'nullable|string|max:500', ], 'network' => [ 'module_host_id' => 'required|integer|exists:module_host,id', 'name' => 'required|string|max:255', 'network' => 'nullable|string|max:255', 'type' => 'nullable|string|max:255', ], default => abort(422, 'Không hỗ trợ tạo node loại này'), }; $data = $request->validate($rules); $data['status'] = 1; $model = match ($type) { 'datacenter' => ModuleDatacenter::create($data), 'host' => ModuleHost::create($data), 'network' => ModuleNetwork::create($data), }; return response()->json(['error' => false, 'data' => $model], 201); } public function updateNode(Request $request, string $type, int $id): JsonResponse { $model = $this->resolveModel($type, $id); $rules = match ($type) { 'vcenter' => ['host' => 'sometimes|string|max:255', 'user' => 'sometimes|string|max:255', 'password' => 'sometimes|string|max:255', 'status' => 'sometimes|integer|in:0,1'], 'datacenter' => ['name' => 'sometimes|string|max:255', 'datacenter_id' => 'sometimes|string|max:255'], 'host' => ['name' => 'sometimes|string|max:255', 'host_id' => 'sometimes|string|max:255', 'template' => 'sometimes|nullable|string|max:500', 'console' => 'sometimes|nullable|string|max:500', 'iso_path' => 'sometimes|nullable|string|max:500'], 'datastore' => ['name' => 'sometimes|string|max:255', 'datastore' => 'sometimes|string|max:255', 'type' => 'sometimes|nullable|string|max:255', 'space' => 'sometimes|nullable|integer', 'status' => 'sometimes|integer|in:0,1'], 'network' => ['name' => 'sometimes|string|max:255', 'network' => 'sometimes|string|max:255', 'type' => 'sometimes|nullable|string|max:255'], 'folder' => ['name' => 'sometimes|string|max:255', 'folder' => 'sometimes|string|max:255', 'type' => 'sometimes|nullable|string|max:255'], 'virtual_machine' => ['name' => 'sometimes|string|max:255', 'vm_id' => 'sometimes|string|max:255'], default => [], }; $request->validate($rules); $data = $request->only(array_keys($rules)); if ($type === 'vcenter' && isset($data['password'])) { $data['password'] = SignRSA::EncryptFunc($data['password']); } $model->update($data); return response()->json(['error' => false, 'data' => $model]); } public function destroyNode(string $type, int $id): JsonResponse { $model = $this->resolveModel($type, $id); $childCheck = match ($type) { 'vcenter' => $model->datacenters()->exists(), 'datacenter' => ModuleHost::where('module_datacenter_id', $id)->exists(), 'host' => $model->datastore()->exists() || $model->network()->exists(), default => false, }; if ($childCheck) { return response()->json([ 'error' => true, 'message' => 'Không thể xoá vì vẫn còn dữ liệu liên kết bên dưới.', ], 422); } $model->delete(); return response()->json(['error' => false, 'message' => 'Đã xoá thành công.']); } public function tree(Request $request): JsonResponse { $vcenters = vCenter::with(['datacenters'])->get(); $allHosts = ModuleHost::with(['datastore', 'network'])->get()->groupBy('module_datacenter_id'); $allFolders = ModuleFolder::all()->groupBy('module_datacenter_id'); // VMs with a host assignment — grouped by host DB id $allVMs = VirtualMachines::with('vmware')->whereNotNull('host_id')->get()->groupBy('host_id'); // VMs without a host assignment (synced before host_id was tracked) — grouped by datacenter DB id $orphanVMs = VirtualMachines::with('vmware')->whereNull('host_id')->get()->groupBy('datacenter_id'); $tree = $vcenters->map(function ($vc) use ($allHosts, $allFolders, $allVMs, $orphanVMs) { $dcGroups = ($vc->datacenters ?? collect())->groupBy('datacenter_id'); $datacenters = $dcGroups->map(function ($dcGroup) use ($allHosts, $allFolders, $allVMs, $orphanVMs) { $dc = $dcGroup->first(); $dcIds = $dcGroup->pluck('id'); $uniqueHosts = $dcIds->flatMap(fn($id) => $allHosts->get($id) ?? collect()) ->unique('host_id')->values(); // Networks — deduplicated at datacenter level $dcNetworks = $uniqueHosts ->flatMap(fn($host) => $host->network ?? collect()) ->unique('network') ->map(fn($net) => [ 'id' => 'network-' . $net->id, 'name' => $net->name, 'type' => 'network', 'meta' => [ 'db_id' => $net->id, 'network' => $net->network, 'net_type' => $net->type, 'module_host_id' => $net->module_host_id, ], ]); // Hosts — VMs placed under their host, then Datastores $dcHosts = $uniqueHosts->map(function ($host) use ($allVMs) { $hostVMs = ($allVMs->get($host->id) ?? collect()) ->map(fn($vm) => [ 'id' => 'virtual_machine-' . $vm->id, 'name' => $vm->name, 'type' => 'virtual_machine', 'meta' => [ 'db_id' => $vm->id, 'vmware_db_id' => $vm->vmware?->id, 'uuid' => $vm->uuid, 'vm_id' => $vm->vm_id, 'ip' => $vm->ip, 'username' => $vm->vmware?->user, 'password' => $vm->vmware?->plain_password, 'power_state' => $vm->power_state, 'folder' => $vm->folder, 'cpu' => $vm->cpu, 'ram_mb' => $vm->ram_mb, 'is_template' => $vm->is_template, 'is_system' => $vm->is_system, 'last_synced_at' => $vm->last_synced_at, ], 'children' => [], ]); $datastores = ($host->datastore ?? collect()) ->unique('datastore') ->map(fn($ds) => [ 'id' => 'datastore-' . $ds->id, 'name' => $ds->name, 'type' => 'datastore', 'meta' => [ 'db_id' => $ds->id, 'datastore' => $ds->datastore, 'store_type' => $ds->type, 'space' => $ds->space, 'space_gb' => $ds->space ? round($ds->space / 1024, 1) : null, 'status' => $ds->status, 'module_host_id' => $ds->module_host_id, ], ]); return [ 'id' => 'host-' . $host->id, 'name' => $host->name, 'type' => 'host', 'meta' => [ 'db_id' => $host->id, 'host_id' => $host->host_id, 'template' => $host->template, 'console' => $host->console, 'iso_path' => $host->iso_path, 'module_datacenter_id' => $host->module_datacenter_id, 'vm_count' => $hostVMs->count(), ], // VMs first, then Datastores — correct vSphere order 'children' => array_values($hostVMs->concat($datastores)->toArray()), ]; }); // Folders — datacenter level $dcFolders = $dcIds ->flatMap(fn($id) => $allFolders->get($id) ?? collect()) ->unique('folder') ->map(fn($f) => [ 'id' => 'folder-' . $f->id, 'name' => $f->name, 'type' => 'folder', 'meta' => [ 'db_id' => $f->id, 'folder' => $f->folder, 'folder_type' => $f->type, 'module_datacenter_id' => $f->module_datacenter_id, ], ]); // Orphan VMs — no host_id, grouped directly under datacenter $dcOrphanVMs = $dcIds ->flatMap(fn($id) => $orphanVMs->get($id) ?? collect()) ->unique('id') ->map(fn($vm) => [ 'id' => 'virtual_machine-' . $vm->id, 'name' => $vm->name, 'type' => 'virtual_machine', 'meta' => [ 'db_id' => $vm->id, 'vmware_db_id' => $vm->vmware?->id, 'uuid' => $vm->uuid, 'vm_id' => $vm->vm_id, 'ip' => $vm->ip, 'username' => $vm->vmware?->user, 'password' => $vm->vmware?->plain_password, 'power_state' => $vm->power_state, 'folder' => $vm->folder, 'cpu' => $vm->cpu, 'ram_mb' => $vm->ram_mb, 'is_template' => $vm->is_template, 'is_system' => $vm->is_system, 'last_synced_at' => $vm->last_synced_at, ], 'children' => [], ]); return [ 'id' => 'datacenter-' . $dc->id, 'name' => $dc->name, 'type' => 'datacenter', 'meta' => [ 'db_id' => $dc->id, 'datacenter_id' => $dc->datacenter_id, 'vcenter_id' => $dc->vcenter_id, ], // vSphere order: Hosts → Networks → Folders → Orphan VMs 'children' => array_values($dcHosts->concat($dcNetworks)->concat($dcFolders)->concat($dcOrphanVMs)->toArray()), ]; })->values(); return [ 'id' => 'vcenter-' . $vc->id, 'name' => $vc->host, 'type' => 'vcenter', 'meta' => ['db_id' => $vc->id, 'host' => $vc->host, 'status' => $vc->status], 'children' => array_values($datacenters->toArray()), ]; }); return response()->json(['error' => false, 'data' => $tree->values()]); } private function resolveModel(string $type, int $id) { return match ($type) { 'vcenter' => vCenter::findOrFail($id), 'datacenter' => ModuleDatacenter::findOrFail($id), 'host' => ModuleHost::findOrFail($id), 'datastore' => ModuleDatastore::findOrFail($id), 'network' => ModuleNetwork::findOrFail($id), 'folder' => ModuleFolder::findOrFail($id), 'virtual_machine' => VirtualMachines::findOrFail($id), default => abort(404, 'Unknown node type'), }; } }