update ippool

This commit is contained in:
baodev0
2026-08-06 11:13:42 +07:00
parent 520f21cb11
commit a5e342f0f5
6 changed files with 271 additions and 1 deletions
@@ -3,15 +3,27 @@
namespace App\Http\Controllers\Api\Admin\IpPool;
use App\Http\Controllers\Controller;
use App\Imports\IpPoolImport;
use App\Models\IpPool;
use App\Models\ModuleIpAddress;
use App\Models\ModuleNetwork;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Maatwebsite\Excel\Facades\Excel;
use Maatwebsite\Excel\Validators\ValidationException;
class IpPoolController extends Controller
{
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',
'monthly_cost', 'selling_price', 'additional_info',
'last_email_type', 'last_email_at', 'updated_by', 'note',
];
public function index(Request $request): JsonResponse
{
$perPage = min((int) $request->get('per_page', 15), 500);
@@ -116,6 +128,9 @@ class IpPoolController extends Controller
$request->validate([
'ip_address' => 'required|string',
'module_network_id' => 'required|integer|exists:module_network,id',
'contact_email' => 'nullable|email',
'monthly_cost' => 'nullable|numeric',
'selling_price' => 'nullable|numeric',
]);
$networkId = (int) $request->module_network_id;
@@ -154,6 +169,8 @@ class IpPoolController extends Controller
], 422);
}
$extra = $request->only(self::EDITABLE_FIELDS);
DB::beginTransaction();
try {
$created = [];
@@ -161,6 +178,7 @@ class IpPoolController extends Controller
$created[] = IpPool::create([
'ip_address' => $ip,
'module_network_id' => $networkId,
...$extra,
]);
}
DB::commit();
@@ -183,6 +201,9 @@ class IpPoolController extends Controller
$request->validate([
'ip_address' => 'required|string|max:45',
'module_network_id' => 'required|integer|exists:module_network,id',
'contact_email' => 'nullable|email',
'monthly_cost' => 'nullable|numeric',
'selling_price' => 'nullable|numeric',
]);
if (!filter_var($request->ip_address, FILTER_VALIDATE_IP)) {
@@ -204,7 +225,7 @@ class IpPoolController extends Controller
DB::beginTransaction();
try {
$pool->update($request->only(['ip_address', 'module_network_id']));
$pool->update($request->only(['ip_address', 'module_network_id', ...self::EDITABLE_FIELDS]));
DB::commit();
return response()->json(['error' => false, 'data' => $pool->fresh('network')]);
@@ -276,6 +297,40 @@ 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();
return response()->json([
'error' => false,
'message' => 'Import thành công',
'created' => max(0, $countAfter - $countBefore),
]);
}
public function networks(): JsonResponse
{
$networks = ModuleNetwork::select(['id', 'name', 'type', 'network'])
+104
View File
@@ -0,0 +1,104 @@
<?php
namespace App\Imports;
use App\Models\IpPool;
use App\Models\ModuleNetwork;
use Illuminate\Support\Str;
use Maatwebsite\Excel\Concerns\ToModel;
use Maatwebsite\Excel\Concerns\WithHeadingRow;
use Maatwebsite\Excel\Concerns\WithUpsertColumns;
use Maatwebsite\Excel\Concerns\WithUpserts;
use Maatwebsite\Excel\Concerns\WithValidation;
class IpPoolImport implements ToModel, WithHeadingRow, WithValidation, WithUpserts, WithUpsertColumns
{
/** @var array<string, int> */
private array $networkCache = [];
public function model(array $row)
{
return new IpPool([
// uuid has no DB default and must be set on insert; excluded from upsertColumns() so it's never overwritten
'uuid' => (string) Str::uuid(),
'ip_address' => $row['ip_address'],
'module_network_id' => $this->resolveNetworkId($row['ten_mang'] ?? null, $row['cidr'] ?? null),
'cidr' => $this->parsePrefixLength($row['cidr'] ?? null),
'gateway' => $row['gateway'] ?? null,
'netmask' => $row['subnet_mask'] ?? null,
'owner_type' => $row['owner_type'] ?? null,
'ip_role' => $row['ip_role'] ?? null,
'parent_ip' => $row['parent_ip'] ?? null,
'customer_name' => $row['ten_khach_hang'] ?? null,
'service_name' => $row['ten_dich_vu'] ?? null,
'allocated_at' => $row['ngay_cap_phat'] ?: null,
'revoked_at' => $row['ngay_thu_hoi'] ?: null,
'allocation_status' => $row['trang_thai'] ?? null,
'contact_email' => $row['contact_email'] ?? null,
'contact_cc' => $row['contact_cc'] ?? null,
'contact_phone' => $row['contact_phone'] ?? null,
'monthly_cost' => $row['monthly_cost'] ?: null,
'selling_price' => $row['selling_price'] ?: null,
'additional_info' => $row['thong_tin_bo_sung'] ?? null,
'last_email_type' => $row['last_email_type'] ?? null,
'last_email_at' => $row['last_email_at'] ?: null,
'updated_by' => $row['updated_by'] ?? null,
'note' => $row['ghi_chu'] ?? null,
]);
}
public function rules(): array
{
return [
'ten_mang' => 'required|string|max:255',
'cidr' => 'nullable|string|max:45',
'ip_address' => 'required|ip',
'contact_email' => 'nullable|email',
'monthly_cost' => 'nullable|numeric',
'selling_price' => 'nullable|numeric',
];
}
/**
* CSV gives CIDR as "/22"; the ip_pools.cidr column only stores the prefix length.
*/
private function parsePrefixLength(?string $cidr): ?int
{
// "/22" or "111.111.111.0/22" -> 22
return preg_match('/(\d+)$/', (string) $cidr, $m) ? (int) $m[1] : null;
}
public function uniqueBy(): array
{
return ['ip_address', 'module_network_id'];
}
public function upsertColumns(): array
{
return [
'cidr', 'gateway', 'netmask', 'owner_type', 'ip_role', 'parent_ip',
'customer_name', 'service_name', 'allocated_at', 'revoked_at', 'allocation_status',
'contact_email', 'contact_cc', 'contact_phone', 'monthly_cost', 'selling_price',
'additional_info', 'last_email_type', 'last_email_at', 'updated_by', 'note',
];
}
private function resolveNetworkId(?string $name, ?string $cidr): ?int
{
if (!$name) {
return null;
}
$key = $name . '|' . $cidr;
if (isset($this->networkCache[$key])) {
return $this->networkCache[$key];
}
$network = ModuleNetwork::where('name', $name)->first()
?? ($cidr ? ModuleNetwork::where('network', $cidr)->first() : null)
?? ModuleNetwork::create(['name' => $name, 'network' => $cidr]);
return $this->networkCache[$key] = $network->id;
}
}
+31
View File
@@ -12,8 +12,39 @@ class IpPool extends Model
use HasFactory, SoftDeletes, LogsActivity;
protected $fillable = [
'uuid',
'ip_address',
'module_network_id',
'gateway',
'cidr',
'netmask',
'status',
'owner_type',
'ip_role',
'parent_ip',
'customer_name',
'service_name',
'allocated_at',
'revoked_at',
'allocation_status',
'contact_email',
'contact_cc',
'contact_phone',
'monthly_cost',
'selling_price',
'additional_info',
'last_email_type',
'last_email_at',
'updated_by',
'note',
];
protected $casts = [
'allocated_at' => 'date',
'revoked_at' => 'date',
'last_email_at' => 'datetime',
'monthly_cost' => 'decimal:2',
'selling_price' => 'decimal:2',
];
public function network()
@@ -0,0 +1,51 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('ip_pools', function (Blueprint $table) {
// gateway, cidr, netmask, status already exist on this table
$table->string('owner_type')->nullable()->after('module_network_id');
$table->string('ip_role')->nullable()->after('owner_type');
$table->string('parent_ip')->nullable()->after('ip_role');
$table->string('customer_name')->nullable()->after('parent_ip');
$table->string('service_name')->nullable()->after('customer_name');
$table->date('allocated_at')->nullable()->after('service_name');
$table->date('revoked_at')->nullable()->after('allocated_at');
$table->string('allocation_status')->nullable()->after('revoked_at');
$table->string('contact_email')->nullable()->after('allocation_status');
$table->string('contact_cc')->nullable()->after('contact_email');
$table->string('contact_phone')->nullable()->after('contact_cc');
$table->decimal('monthly_cost', 14, 2)->nullable()->after('contact_phone');
$table->decimal('selling_price', 14, 2)->nullable()->after('monthly_cost');
$table->text('additional_info')->nullable()->after('selling_price');
$table->string('last_email_type')->nullable()->after('additional_info');
$table->dateTime('last_email_at')->nullable()->after('last_email_type');
$table->string('updated_by')->nullable()->after('last_email_at');
$table->text('note')->nullable()->after('updated_by');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('ip_pools', function (Blueprint $table) {
$table->dropColumn([
'owner_type', 'ip_role', 'parent_ip',
'customer_name', 'service_name', 'allocated_at', 'revoked_at', 'allocation_status',
'contact_email', 'contact_cc', 'contact_phone', 'monthly_cost', 'selling_price',
'additional_info', 'last_email_type', 'last_email_at', 'updated_by', 'note',
]);
});
}
};
@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('ip_pools', function (Blueprint $table) {
$table->unique(['ip_address', 'module_network_id'], 'ip_pools_ip_network_unique');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('ip_pools', function (Blueprint $table) {
$table->dropUnique('ip_pools_ip_network_unique');
});
}
};
+1
View File
@@ -449,6 +449,7 @@ Route::prefix('v1')
Route::get('networks', 'networks')->middleware('perm:module-vcenter.view');
Route::get('/', 'index')->middleware('perm:module-vcenter.view');
Route::post('/', 'store')->middleware('perm:module-vcenter.manager');
Route::post('import', 'import')->middleware('perm:module-vcenter.manager');
Route::delete('bulk', 'bulkDelete')->middleware('perm:module-vcenter.manager');
Route::get('{id}', 'show')->middleware('perm:module-vcenter.view');
Route::put('{id}', 'update')->middleware('perm:module-vcenter.manager');