update ippool
This commit is contained in:
@@ -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'])
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user