boolean('all') ? 20000 : min((int) $request->get('per_page', 15), 500); $filter = $request->get('filter'); $networkId = $request->get('network_id'); $query = IpPool::with(['network:id,name,type,network', 'service:id,server_name,status,customer_id']); if ($filter) { $query->where('ip_address', 'like', "%{$filter}%"); } if ($networkId) { $query->where('module_network_id', $networkId); } $paginator = $query->paginate($perPage); // Enrich each item with usage info $usedIps = ModuleIpAddress::whereIn('ip', $paginator->pluck('ip_address')->toArray()) ->where('status', 1) ->with('host:id,name') ->select(['id', 'ip', 'vm_name', 'module_host_id', 'module_network_id', 'status']) ->get() ->keyBy('ip'); // Get vmware info for used IPs $vmwareMap = []; if ($usedIps->isNotEmpty()) { $vmwares = DB::table('module_vmware as mv') ->join('module_ip_address as mia', 'mv.module_ip_address', '=', 'mia.id') ->whereIn('mia.ip', $usedIps->keys()->toArray()) ->leftJoin('services as s', 's.id', '=', 'mv.service_id') ->leftJoin('customers as c', 'c.id', '=', 'mv.customer_id') ->select([ 'mia.ip', 'mv.name as vm_name', 'mv.id as vm_id', 's.id as service_id', 's.status as service_status', 'c.fullname', 'c.email', ]) ->get(); foreach ($vmwares as $vm) { $vmwareMap[$vm->ip] = $vm; } } $items = $paginator->map(function (IpPool $pool) use ($usedIps, $vmwareMap) { $data = $pool->toArray(); $ip = $pool->ip_address; if (isset($usedIps[$ip])) { $mia = $usedIps[$ip]; $vm = $vmwareMap[$ip] ?? null; $data['usage'] = [ 'assigned' => true, 'vm_name' => $vm?->vm_name ?? $mia->vm_name, 'vm_id' => $vm?->vm_id ?? null, 'service_id' => $vm?->service_id ?? null, 'service_status' => $vm?->service_status ?? null, 'customer_name' => $vm?->fullname ?? null, 'customer_email' => $vm?->email ?? null, ]; } else { $data['usage'] = ['assigned' => false]; } return $data; }); return response()->json([ 'error' => false, 'data' => [ 'items' => $items->values(), 'total' => $paginator->total(), 'per_page' => $paginator->perPage(), 'current_page' => $paginator->currentPage(), 'last_page' => $paginator->lastPage(), ], ]); } public function show(int $id): JsonResponse { $pool = IpPool::with(['network:id,name,type,network', 'service:id,server_name,status,customer_id'])->find($id); if (!$pool) { return response()->json(['error' => true, 'message' => "Không tìm thấy IP Pool #{$id}"], 404); } return response()->json(['error' => false, 'data' => $pool]); } /** * Bulk create: ip_address may be separated by commas and/or whitespace (e.g. "10.0.0.1, 10.0.0.2 10.0.0.3"). */ public function store(Request $request): JsonResponse { $request->validate([ 'ip_address' => 'required|string', 'module_network_id' => 'required|integer|exists:module_network,id', 'gateway' => 'nullable|string|max:45', 'cidr' => 'nullable|integer|min:0|max:32', 'netmask' => 'nullable|string|max:45', 'status' => 'nullable|in:free,assigned,reserved', 'service_id' => 'nullable|integer|exists:services,id', 'contact_email' => ['nullable', 'string', $this->multiEmailRule()], 'contact_cc' => ['nullable', 'string', $this->multiEmailRule()], 'monthly_cost' => 'nullable|numeric', 'selling_price' => 'nullable|numeric', ]); $networkId = (int) $request->module_network_id; $rawIps = $request->ip_address; $ips = array_filter(array_map('trim', preg_split('/[\s,]+/', $rawIps))); if (empty($ips)) { return response()->json(['error' => true, 'message' => 'Không có địa chỉ IP hợp lệ'], 422); } // Validate each IP $invalid = []; foreach ($ips as $ip) { if (!filter_var($ip, FILTER_VALIDATE_IP)) { $invalid[] = $ip; } } if (!empty($invalid)) { return response()->json([ 'error' => true, 'message' => 'Địa chỉ IP không hợp lệ: ' . implode(', ', $invalid), ], 422); } // Check duplicates already in pool $existing = IpPool::where('module_network_id', $networkId) ->whereIn('ip_address', $ips) ->pluck('ip_address') ->toArray(); if (!empty($existing)) { return response()->json([ 'error' => true, 'message' => 'Địa chỉ IP đã tồn tại trong pool: ' . implode(', ', $existing), ], 422); } $hostId = ModuleNetwork::find($networkId)?->module_host_id; $netmask = $request->netmask ?: '255.255.255.0'; $cidr = $request->cidr ?? 24; $extra = $request->only(self::EDITABLE_FIELDS); DB::beginTransaction(); try { $created = []; foreach ($ips as $ip) { $pool = IpPool::create([ 'ip_address' => $ip, 'module_network_id' => $networkId, 'gateway' => $request->gateway, 'cidr' => $cidr, 'netmask' => $netmask, 'status' => $request->status ?: 'free', 'service_id' => $request->service_id, ...$extra, ]); // Mirror into module_ip_address so both tables stay in sync. // Reservation logic (VmIpReservationService/AssignIpToGuestJob) treats // ip_pools.status as the source of truth and just updates this row. ModuleIpAddress::updateOrCreate( ['ip' => $ip, 'module_network_id' => $networkId], [ 'module_host_id' => $hostId, 'gateway' => $request->gateway, 'netmask' => $netmask, 'status' => 0, ] ); $created[] = $pool; } DB::commit(); return response()->json(['error' => false, 'data' => $created, 'count' => count($created)], 201); } catch (\Exception $e) { DB::rollBack(); return response()->json(['error' => true, 'message' => $e->getMessage()], 500); } } public function update(int $id, Request $request): JsonResponse { $pool = IpPool::find($id); if (!$pool) { return response()->json(['error' => true, 'message' => "Không tìm thấy IP Pool #{$id}"], 404); } $request->validate([ 'ip_address' => 'required|string|max:45', 'module_network_id' => 'required|integer|exists:module_network,id', 'gateway' => 'nullable|string|max:45', 'cidr' => 'nullable|integer|min:0|max:32', 'netmask' => 'nullable|string|max:45', 'status' => 'nullable|in:free,assigned,reserved', 'service_id' => 'nullable|integer|exists:services,id', 'contact_email' => ['nullable', 'string', $this->multiEmailRule()], 'contact_cc' => ['nullable', 'string', $this->multiEmailRule()], 'monthly_cost' => 'nullable|numeric', 'selling_price' => 'nullable|numeric', ]); if (!filter_var($request->ip_address, FILTER_VALIDATE_IP)) { return response()->json(['error' => true, 'message' => 'Địa chỉ IP không hợp lệ'], 422); } // Check duplicate (excluding self) $duplicate = IpPool::where('module_network_id', $request->module_network_id) ->where('ip_address', $request->ip_address) ->where('id', '!=', $id) ->exists(); if ($duplicate) { return response()->json([ 'error' => true, 'message' => 'Địa chỉ IP đã tồn tại trong pool này', ], 422); } $oldIp = $pool->ip_address; $oldNetworkId = $pool->module_network_id; $netmask = $request->netmask ?: '255.255.255.0'; DB::beginTransaction(); try { $pool->update([ ...$request->only(['ip_address', 'module_network_id', 'gateway', 'cidr', 'service_id']), ...$request->only(self::EDITABLE_FIELDS), 'netmask' => $netmask, 'status' => $request->status ?: $pool->status, ]); $hostId = ModuleNetwork::find($pool->module_network_id)?->module_host_id; // Keep the module_ip_address mirror in sync with the new ip/network/host/gateway/netmask. ModuleIpAddress::where('ip', $oldIp) ->where('module_network_id', $oldNetworkId) ->update([ 'ip' => $pool->ip_address, 'module_network_id' => $pool->module_network_id, 'module_host_id' => $hostId, 'gateway' => $pool->gateway, 'netmask' => $pool->netmask, ]); DB::commit(); return response()->json(['error' => false, 'data' => $pool->fresh(['network', 'service:id,server_name,status,customer_id'])]); } catch (\Exception $e) { DB::rollBack(); return response()->json(['error' => true, 'message' => $e->getMessage()], 500); } } public function destroy(int $id): JsonResponse { $pool = IpPool::find($id); if (!$pool) { return response()->json(['error' => true, 'message' => "Không tìm thấy IP Pool #{$id}"], 404); } // Check if IP is currently assigned $inUse = ModuleIpAddress::where('ip', $pool->ip_address) ->where('module_network_id', $pool->module_network_id) ->where('status', 1) ->exists(); if ($inUse) { return response()->json([ 'error' => true, 'message' => 'Không thể xoá — IP này đang được gán cho một máy chủ', ], 422); } ModuleIpAddress::where('ip', $pool->ip_address) ->where('module_network_id', $pool->module_network_id) ->delete(); $pool->delete(); return response()->json(['error' => false, 'message' => 'Đã xoá IP Pool thành công']); } }