Add bulk update gateway endpoint to vCenter API routes

- Introduced a new POST route 'bulk-update-gateway' for bulk updating gateways.
- This route is protected by the 'module-vcenter.manager' permission middleware.
This commit is contained in:
baodev0
2026-08-06 15:32:51 +07:00
parent 90f2c97bb4
commit 79f06c2fa2
9 changed files with 5555 additions and 19 deletions
@@ -15,8 +15,11 @@ use Maatwebsite\Excel\Validators\ValidationException;
class IpPoolController extends Controller
{
/**
* Descriptive/CSV-import fields — plain pass-through, unlike gateway/cidr/netmask/
* status/service_id which have defaulting + module_ip_address mirror logic below.
*/
private const EDITABLE_FIELDS = [
'gateway', 'netmask', 'service_id', 'status',
'owner_type', 'ip_role', 'parent_ip', 'customer_name', 'service_name',
'allocated_at', 'revoked_at', 'allocation_status',
'contact_email', 'contact_cc', 'contact_phone',
@@ -24,9 +27,25 @@ class IpPoolController extends Controller
'last_email_type', 'last_email_at', 'updated_by', 'note',
];
/**
* contact_email/contact_cc may hold multiple addresses separated by comma/semicolon.
*/
private function multiEmailRule(): \Closure
{
return function (string $attribute, mixed $value, \Closure $fail) {
foreach (preg_split('/[,;]+/', (string) $value) as $email) {
$email = trim($email);
if ($email !== '' && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
$fail("Email không hợp lệ: {$email}");
}
}
};
}
public function index(Request $request): JsonResponse
{
$perPage = min((int) $request->get('per_page', 15), 500);
// 'all' lets the admin grid load the full dataset once and filter/sort client-side.
$perPage = $request->boolean('all') ? 20000 : min((int) $request->get('per_page', 15), 500);
$filter = $request->get('filter');
$networkId = $request->get('network_id');
@@ -133,6 +152,10 @@ class IpPoolController extends Controller
'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;
@@ -174,6 +197,7 @@ class IpPoolController extends Controller
$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 {
@@ -187,6 +211,7 @@ class IpPoolController extends Controller
'netmask' => $netmask,
'status' => $request->status ?: 'free',
'service_id' => $request->service_id,
...$extra,
]);
// Mirror into module_ip_address so both tables stay in sync.
@@ -229,6 +254,10 @@ class IpPoolController extends Controller
'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)) {
@@ -256,6 +285,7 @@ class IpPoolController extends Controller
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,
]);
@@ -312,6 +342,44 @@ class IpPoolController extends Controller
return response()->json(['error' => false, 'message' => 'Đã xoá IP Pool thành công']);
}
public function bulkUpdateGateway(Request $request): JsonResponse
{
$request->validate([
'ids' => 'required|array|min:1',
'ids.*' => 'integer',
'gateway' => 'required|string|max:45',
]);
$pools = IpPool::whereIn('id', $request->ids)->get(['id', 'ip_address', 'module_network_id']);
if ($pools->isEmpty()) {
return response()->json(['error' => true, 'message' => 'Không tìm thấy IP Pool nào'], 404);
}
DB::beginTransaction();
try {
IpPool::whereIn('id', $pools->pluck('id'))->update(['gateway' => $request->gateway]);
// Mirror into module_ip_address, grouped by network to keep this to a handful of queries.
foreach ($pools->groupBy('module_network_id') as $networkId => $group) {
ModuleIpAddress::whereIn('ip', $group->pluck('ip_address'))
->where('module_network_id', $networkId)
->update(['gateway' => $request->gateway]);
}
DB::commit();
} catch (\Exception $e) {
DB::rollBack();
return response()->json(['error' => true, 'message' => $e->getMessage()], 500);
}
return response()->json([
'error' => false,
'message' => "Đã cập nhật gateway cho {$pools->count()} IP",
'updated' => $pools->count(),
]);
}
public function bulkDelete(Request $request): JsonResponse
{
$request->validate([
@@ -354,6 +422,42 @@ class IpPoolController extends Controller
]);
}
public function import(Request $request): JsonResponse
{
$request->validate([
'file' => 'required|file|mimes:csv,txt|max:10240',
]);
$countBefore = IpPool::withTrashed()->count();
try {
Excel::import(new IpPoolImport(), $request->file('file'));
} catch (ValidationException $e) {
$errors = [];
foreach ($e->failures() as $failure) {
$errors[] = 'Dòng ' . $failure->row() . ': ' . implode(', ', $failure->errors());
}
return response()->json([
'error' => true,
'message' => 'Import thất bại',
'errors' => $errors,
], 422);
} catch (\Exception $e) {
return response()->json(['error' => true, 'message' => 'Import thất bại: ' . $e->getMessage()], 500);
}
$countAfter = IpPool::withTrashed()->count();
IpPool::resyncAllContacts();
return response()->json([
'error' => false,
'message' => 'Import thành công',
'created' => max(0, $countAfter - $countBefore),
]);
}
/**
* Backfill/repair the module_ip_address mirror for every ip_pools row.
* Existing mirror rows only get gateway/netmask refreshed — status/vm_name