Refactor code structure for improved readability and maintainability

This commit is contained in:
baodev0
2026-08-10 08:37:52 +07:00
parent d6d63e5152
commit e3d3dea8aa
31 changed files with 290 additions and 319 deletions
+2 -1
View File
@@ -44,7 +44,8 @@
"Bash(python3 *)",
"Bash(pnpm exec *)",
"Bash(echo \"exit: $?\")",
"Bash(node -e \"console.log\\(require.resolve\\('ag-grid-react/package.json'\\)\\)\")"
"Bash(node -e \"console.log\\(require.resolve\\('ag-grid-react/package.json'\\)\\)\")",
"Bash(awk 'NR==1,NR==900' routes/api.php)"
]
}
}
+3
View File
@@ -26,6 +26,9 @@ RSA_PRIVATE="-----BEGIN RSA PRIVATE KEY-----\nMIICXQIBAAKBgQDhm9x8DFf8D4pVBgPi6N
PASSPORT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA1IW8ZeL+cwbatI1sQwt2zEmWwI2ovityTTz3/O+WGXgD85Zz\nEOA0thy91mJMNZv5qyWPexbmROrYpUSe9mQl4HIDlMipuWE+pJImmrJGGCf15QdO\nOVQvoknwVxr3lfSGwEFx2ubPMb1asytawbzQ/pZc6F7VP8gqcdVpI0JnolDtXa/v\niaURTIvYY1WtwPjRa4m98fin4aKMh3sDt88W00RaMjPn00aW8NRo8s/Oa0d/DXEI\ne08RGGUBHuuqRhzB4wHYnpafBqpZDp2LYHvdw+7Vf4dPh63jfBy6ISv+eGSIFuSE\npuiumDt4J7ZjCjh+RDavJQhfGGiG+kzMZ4iAZwIDAQABAoIBACME0nF7gPAVTg54\nS0GU7wNkkNmzvf1wDUqa5EYieHpD42rM/LLoLudfPwzHb8VZqT9BVi4zLMhoZkB9\nWTBCeldMkjh/BDCOzJKznnbZL2GlnAgcNxCNRbgZNOg1glyMSXP+lLM8WyHYEMz7\nNN49rbZ1smN71OnIou3U+5kaxRbir8RNh93fp5yt519a7gRybL8Td2FH64lcSRuX\nHVDtwX95d9m8tY8WoxQPz7jzztUlfNWgRgQhBSYXpOjAEWoJaXnOYo2mxoR6pm9p\npqgkgGvwmfv0Mi97anFoPMymM3HzUFOqqtr5Q7M6DXC+1CSmtkw6hvpED/IV6RJh\nOQtG6hECgYEA/fdnE6m7NMJe4jZxq+mB7+4HYvHLodl8Vb0ncIHwbhAiqaoQNUj2\nfzotHdBeligiives4S3N10RwvKNb1M0iJs41GmfTLcJRoUvyfHbYWLX+M5FRdQ4q\nvihFj8ckontcdXUSoqdZXz0KcVMkHq1hnXt7CayfvXwTeFDdfPoevUkCgYEA1jlg\n6pYg3xGAGNPF5FGiVEMp+BT7h6w41fRmL7c7LFkksUkXmbOIY+M/NLzx2mNUUXvL\nOfMjFUtjr2kehApS6/v7pgJjqqcmFYQJAMnwsVmuBVDw7+OCxK7IrR24jwYLpVMd\nzOiWALZMlLD1bnP0v6sa0hb5/ag8bFElpV+FwC8CgYEAromI3YqYjfnHG9Au7ubb\n1fjQOr/SIrlFKeeFY1zpz7pnGBM4cGZkgauJtiOt3aRXmQn+VgMUpAz1La9XPTh9\ncA3nkO4pxBj34NJBJCjJJq5stSP5x2ilF2q148oI82G0eUSsOfyo/zsEIj3xZOPj\nYkeql0t7vM2408c/cNS0q4ECgYEArDX8HxkI28npvAuiLeUiJbLIblIP0gU0u2p5\nKyqVOxV+VbZ4bmrQ3du01+R8WB85jTeDK9a1AU1VmZFborHZ7zLmHZIWJP2xs+Is\niqHMCAYbytJCndW7LfmEAQa/ctpdSk4CytK+o1vpPC71XhGBUwNt0C4AUVWJHVou\ndT7Q+qkCgYAE0QGNls/EUcCFoX6LUXDAQZFe46rp5hxcGOrjdjbgZl5DiJSrnEvH\n1sh908Q1FchJagA+nUVOm+nbOV3EMf6XlHRyB9XtRcSgEfls9O0HWUi/67YJig9L\ncElLxwyrZpLdzMRYz0JknXSb7GnnsZPG4HG5rhMMz/Ly7lb6f7GZ1w==\n-----END RSA PRIVATE KEY-----"
PASSPORT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1IW8ZeL+cwbatI1sQwt2\nzEmWwI2ovityTTz3/O+WGXgD85ZzEOA0thy91mJMNZv5qyWPexbmROrYpUSe9mQl\n4HIDlMipuWE+pJImmrJGGCf15QdOOVQvoknwVxr3lfSGwEFx2ubPMb1asytawbzQ\n/pZc6F7VP8gqcdVpI0JnolDtXa/viaURTIvYY1WtwPjRa4m98fin4aKMh3sDt88W\n00RaMjPn00aW8NRo8s/Oa0d/DXEIe08RGGUBHuuqRhzB4wHYnpafBqpZDp2LYHvd\nw+7Vf4dPh63jfBy6ISv+eGSIFuSEpuiumDt4J7ZjCjh+RDavJQhfGGiG+kzMZ4iA\nZwIDAQAB\n-----END PUBLIC KEY-----"
#Config VMware template default credential
VMWARE_TEMPLATE_PASSWORD=change-me
#CONFIG CKFINDER
CKFINDER_DOMAIN=client.test
CKFINDER_LICENSE=WS8SXEQQXPN8MYTF51SX1VFFUW3B6
@@ -6,12 +6,14 @@ use App\Http\Controllers\Controller;
use App\Http\Requests\Api\Client\ClientAuthRequest;
use App\Http\Requests\Api\Client\ClientChangePasswordRequest;
use App\Http\Requests\Api\Client\ClientRegisterRequest;
use App\Http\Requests\Api\Client\ClientUpdateCccdRequest;
use App\Http\Requests\Api\Client\ClientUpdateRequest;
use App\Http\Resources\Api\Client\ClientResource;
use App\Services\ActivityLogger;
use App\Services\Api\Client\ClientAuthService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Storage;
use Tymon\JWTAuth\Facades\JWTAuth;
use Tymon\JWTAuth\Exceptions\JWTException;
@@ -467,6 +469,43 @@ class ClientController extends Controller
]);
}
public function updateCccd(ClientUpdateCccdRequest $request)
{
$user = auth('client')->user();
$oldValues = array_intersect_key($user->toArray(), $request->validated());
$user->identity_number = $request->validated('cmnd');
$user->front_identity_number = Storage::disk('public')->url(
$request->file('cmnd_before')->store('cccd', 'public')
);
$user->back_identity_number = Storage::disk('public')->url(
$request->file('cmnd_after')->store('cccd', 'public')
);
$dirty = $user->getDirty();
$user->save();
if (!empty($dirty)) {
$this->activityLogger->log(
event: 'cccd_update',
logName: 'Customer',
description: "Customer {$user->email} cập nhật chứng minh thư",
subject: $user,
oldValues: array_intersect_key($oldValues, $dirty),
newValues: $dirty,
);
}
return response()->json([
'error' => false,
'message' => 'Cập nhật chứng minh thư thành công',
'data' => [
'client' => new ClientResource($user->loadCount(['vps', 'hosting', 'domain'])),
],
]);
}
public function changePassword(ClientChangePasswordRequest $request)
{
$user = auth('client')->user();
@@ -103,7 +103,7 @@ class TicketController extends Controller
return response()->json([
'error' => false,
'message' => 'Ticket đã được tạo thành công.',
'message' => 'Ticket đã được khởi tạo.',
'data' => new TicketResource($ticket),
], 201);
}
@@ -0,0 +1,33 @@
<?php
namespace App\Http\Requests\Api\Client;
use Illuminate\Foundation\Http\FormRequest;
class ClientUpdateCccdRequest extends FormRequest
{
public function authorize(): bool
{
return auth('client')->check();
}
public function rules(): array
{
return [
'cmnd' => 'required|string|max:50',
'cmnd_before' => 'required|image|max:5120',
'cmnd_after' => 'required|image|max:5120',
];
}
public function messages(): array
{
return [
'cmnd.required' => 'Số CMT/CCCD là bắt buộc',
'cmnd_before.required' => 'Ảnh mặt trước là bắt buộc',
'cmnd_before.image' => 'Ảnh mặt trước phải là hình ảnh',
'cmnd_after.required' => 'Ảnh mặt sau là bắt buộc',
'cmnd_after.image' => 'Ảnh mặt sau phải là hình ảnh',
];
}
}
@@ -31,8 +31,8 @@ class ClientResource extends JsonResource
'date' => $this->created_at ? $this->created_at->format('Y-m-d H:i:s') : null,
'company' => $this->company ?? '',
'avatar' => $this->avatar,
'cmnd_after' => $this->front_identity_number,
'cmnd_before' => $this->back_identity_number,
'cmnd_before' => $this->front_identity_number,
'cmnd_after' => $this->back_identity_number,
'cmnd' => $this->identity_number,
'fb_link' => null,
'active_2fa' => 0,
@@ -8,6 +8,8 @@ use Illuminate\Http\Resources\Json\JsonResource;
class InvoiceDetailResource extends JsonResource
{
use ResolvesPaymentMethod;
public function toArray(Request $request): array
{
return [
@@ -20,7 +22,7 @@ class InvoiceDetailResource extends JsonResource
'discount_amount' => (float) ($this->discount_amount ?? 0),
'tax' => (float) $this->tax,
'total' => (float) $this->total,
'payment_method' => $this->payment_method,
'payment_method' => $this->resolvePaymentMethod($this->resource),
'auto_renew' => (bool) $this->auto_renew,
'created_at' => $this->created_at?->toISOString(),
'due_date' => $this->due_date?->toISOString(),
@@ -9,6 +9,8 @@ use Illuminate\Http\Resources\Json\JsonResource;
class OrderDetailResource extends JsonResource
{
use ResolvesPaymentMethod;
public function toArray(Request $request): array
{
return [
@@ -33,6 +35,7 @@ class OrderDetailResource extends JsonResource
'total' => (float) $this->invoice->total,
'due_date' => $this->invoice->due_date?->toISOString(),
'paid_at' => $this->invoice->paid_at?->toISOString(),
'payment_method' => $this->resolvePaymentMethod($this->invoice),
]),
'items' => $this->whenLoaded('orderItems') ? OrderItemResource::collection($this->orderItems) : [],
];
@@ -0,0 +1,43 @@
<?php
namespace App\Http\Resources\Api\Service\Order;
use App\Models\Invoice;
trait ResolvesPaymentMethod
{
/** invoice.payment_method holds a code (balance/credit/transfer/...), not always a Payment row id. */
private const METHOD_LABELS = [
'balance' => 'Số dư tài khoản',
'credit' => 'Số dư tài khoản',
'transfer' => 'Chuyển khoản ngân hàng',
'vnpay' => 'VNPay',
'momo' => 'Momo',
'bank_transfer' => 'Chuyển khoản ngân hàng',
'admin' => 'Xác nhận thủ công',
];
private function resolvePaymentMethod(Invoice $invoice): ?array
{
$method = $invoice->payment_method;
if (! $method) {
return null;
}
if (ctype_digit((string) $method) && $invoice->payment) {
return [
'code' => null,
'name' => $invoice->payment->name,
'bank_name' => $invoice->payment->bankName,
'logo' => $invoice->payment->logo,
];
}
return [
'code' => $method,
'name' => self::METHOD_LABELS[$method] ?? $method,
'bank_name' => null,
'logo' => null,
];
}
}
+50 -14
View File
@@ -343,12 +343,41 @@ class vCenter
// ─── Legacy VPS Direct SSH ────────────────────────────────────────────────
// These methods use direct SSH to guest VM, not via jumphost.
/** Look up host/user/password for a legacy target from infra_management, decrypting the stored password. */
private function resolveLegacyInfra(string $type): ?array
{
$infra = \App\Models\InfraManagement::where('type', $type)
->where('status', true)
->first();
if (!$infra) {
Log::error("Legacy VPS lookup failed: no active infra_management row for type={$type}.");
return null;
}
return [
'ip' => $infra->host,
'user' => $infra->username,
'password' => \App\Http\Facade\SignRSA::DecryptFunc($infra->password),
];
}
protected function loginVPS(array|null $data = []): ?SSH2
{
try {
$user = $data['user'] ?? 'root';
$ip = $data['ip'] ?? '103.42.58.37';
$password = $data['password'] ?? 'TGS@1232';
$ip = $data['ip'] ?? null;
$user = $data['user'] ?? null;
$password = $data['password'] ?? null;
if ($ip === null || $user === null || $password === null) {
$infra = $this->resolveLegacyInfra('legacy_vps');
if (!$infra) {
throw new Exception('No legacy VPS credentials available (data not provided and no active infra_management row).');
}
$ip ??= $infra['ip'];
$user ??= $infra['user'];
$password ??= $infra['password'];
}
$this->process = new SSH2($ip, 22, 10);
if (!$this->process->login($user, $password)) {
@@ -362,13 +391,19 @@ class vCenter
}
}
public function change_password_linux(
array $data = ['ip' => '10.10.10.173', 'user' => 'admin', 'password' => 'TGS@123', 'new_password' => ''],
): bool {
public function change_password_linux(array $data = ['new_password' => '']): bool
{
try {
if (empty($data['new_password'])) {
throw new Exception('New password cannot be empty.');
}
if (!isset($data['ip'], $data['user'], $data['password'])) {
$infra = $this->resolveLegacyInfra('jumphost');
if (!$infra) {
throw new Exception('No jumphost credentials available in infra_management.');
}
$data += $infra;
}
$this->loginVPS($data);
if (!$this->process) {
throw new Exception('Cannot login to VPS.');
@@ -383,18 +418,19 @@ class vCenter
}
}
public function change_password_windows(
array $data = [
'ip' => '103.42.58.37',
'user' => 'administrator',
'password' => 'TGS@123',
'new_password' => '',
],
): bool {
public function change_password_windows(array $data = ['new_password' => '']): bool
{
try {
if (empty($data['new_password'])) {
throw new Exception('New password cannot be empty.');
}
if (!isset($data['ip'], $data['user'], $data['password'])) {
$infra = $this->resolveLegacyInfra('legacy_vps');
if (!$infra) {
throw new Exception('No legacy VPS credentials available in infra_management.');
}
$data += $infra;
}
$this->loginVPS($data);
if (!$this->process) {
throw new Exception('Cannot login to VPS.');
@@ -61,7 +61,7 @@ class AssignIpToGuestJob implements ShouldQueue
public readonly int $moduleVmwareId,
public readonly string $vmId,
public readonly string $osFamily, // VmProvisionService::OS_LINUX | OS_WINDOWS
public readonly string $guestPassword = VmProvisionService::TEMPLATE_PASSWORD,
public readonly string $guestPassword,
public readonly ?int $reservationId = null,
public readonly ?int $reservedIpPoolId = null,
) {}
@@ -157,7 +157,7 @@ class RetryProvisioningOrchestrator implements ShouldQueue
moduleVmwareId: $moduleVmware->id,
vmId: $vmId,
osFamily: $osFamily,
guestPassword: $moduleVmware->plain_password ?? \App\Services\VmProvisionService::TEMPLATE_PASSWORD,
guestPassword: $moduleVmware->plain_password ?? \App\Services\VmProvisionService::templatePassword(),
);
Log::info('RetryProvisioningOrchestrator: dispatched AssignIpToGuestJob', [
'module_vmware_id' => $moduleVmware->id,
@@ -301,8 +301,8 @@ class RetryProvisioningOrchestrator implements ShouldQueue
'next_due_date' => $nextDueDate,
'next_invoice' => $nextDueDate,
'user' => 'Administrator',
'password' => \App\Services\VmProvisionService::TEMPLATE_PASSWORD,
'plain_password' => \App\Services\VmProvisionService::TEMPLATE_PASSWORD,
'password' => \App\Services\VmProvisionService::templatePassword(),
'plain_password' => \App\Services\VmProvisionService::templatePassword(),
'hostname' => $serverName,
'vmware_id' => $vmId,
'vcenter_id' => $vcenterModel->id,
@@ -368,7 +368,7 @@ class UpdateVmConfigJob implements ShouldQueue
$this->moduleVmwareId,
$this->vmId,
$this->osFamily,
VmProvisionService::TEMPLATE_PASSWORD,
VmProvisionService::templatePassword(),
$this->reservationId,
$this->reservedIpPoolId,
);
+5 -1
View File
@@ -41,7 +41,11 @@ class ModuleVmware extends Model
// NOTE: password must NOT use 'hashed' cast — it is a VM guest credential
// used in plaintext by PowerShell scripts (Invoke-VMScript) via SSH/PowerCLI.
protected $casts = [];
// plain_password uses Laravel's 'encrypted' cast (APP_KEY, AES-256-CBC): stored
// encrypted at rest, transparently decrypted on read — no code elsewhere changes.
protected $casts = [
'plain_password' => 'encrypted',
];
public function customer()
{
return $this->belongsTo(\App\Models\Customer::class);
@@ -105,6 +105,7 @@ class OrderQueryService
'invoice.invoiceItems',
'invoice.attachments',
'invoice.customer',
'invoice.payment',
'orderItems.product',
'customer',
'vmWare.ipAddress',
@@ -132,7 +132,7 @@ final class VmRebuildIpService
vcenterHost: $vcenterHost,
vcenterUser: $vcenterUser,
vcenterPassword: $vcenterPass,
guestPassword: VmRebuildService::TEMPLATE_PASSWORD,
guestPassword: VmRebuildService::templatePassword(),
osFamily: $ctx->osFamily,
dns: $dns,
guestUser: $guestUser,
@@ -174,7 +174,7 @@ final class VmRebuildIpService
vcenterHost: $vcenterHost,
vcenterUser: $vcenterUser,
vcenterPassword: $vcenterPass,
guestPassword: VmRebuildService::TEMPLATE_PASSWORD,
guestPassword: VmRebuildService::templatePassword(),
osFamily: $ctx->osFamily,
guestUser: $guestUser,
);
@@ -35,12 +35,11 @@ final class VmRebuildService
public const OS_LINUX = 'LINUX';
public const OS_WINDOWS = 'WINDOWS';
/**
* Default password baked into rebuild templates. Mirrors the create-flow
* template password constant but is duplicated intentionally to keep this
* subsystem standalone.
*/
public const TEMPLATE_PASSWORD = 'TGS@123';
/** Default password baked into rebuild templates — shares config with the create flow. */
public static function templatePassword(): string
{
return \App\Services\VmProvisionService::templatePassword();
}
/**
* Build a fresh RebuildContext from current DB state.
@@ -35,8 +35,8 @@ class VmModuleService
'next_due_date' => $nextDueDate,
'next_invoice' => $nextDueDate,
'user' => $infra->vmUser,
'password' => VmProvisionService::TEMPLATE_PASSWORD,
'plain_password' => VmProvisionService::TEMPLATE_PASSWORD,
'password' => VmProvisionService::templatePassword(),
'plain_password' => VmProvisionService::templatePassword(),
'hostname' => $serverName,
'vmware_id' => $vmId,
'vcenter_id' => $infra->vcenter->id,
+6 -3
View File
@@ -12,12 +12,15 @@ use Illuminate\Support\Facades\Log;
final class VmProvisionService
{
/** Default password baked into every VM template */
public const TEMPLATE_PASSWORD = 'TGS@123';
public const OS_LINUX = 'LINUX';
public const OS_WINDOWS = 'WINDOWS';
/** Default password baked into every VM template */
public static function templatePassword(): string
{
return config('vmware.template_password');
}
// -------------------------------------------------------------------------
// vCenter REST helpers (used by WaitVmReadyJob if still in flow)
// -------------------------------------------------------------------------
+3
View File
@@ -1,6 +1,9 @@
<?php
return [
// Default password baked into every VM template at clone time.
'template_password' => env('VMWARE_TEMPLATE_PASSWORD', 'TGS@123'),
// HAProxy (console.tgs.com.vn) routes WMKS console websockets to the
// ESXi host that owns the ticket via the `?host=` query param — see
// ha-proxy.md. This maps each ESXi IP to the domain HAProxy matches on.
@@ -0,0 +1,52 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
return new class extends Migration {
public function up(): void
{
DB::table('module_vmware')
->whereNotNull('plain_password')
->where('plain_password', '!=', '')
->orderBy('id')
->chunkById(200, function ($rows) {
foreach ($rows as $row) {
// Skip rows already encrypted (re-running the migration, or a value
// that happens to look like ciphertext) — avoid double-encrypting.
try {
Crypt::decryptString($row->plain_password);
continue;
} catch (\Throwable) {
// not yet encrypted — fall through and encrypt it
}
DB::table('module_vmware')
->where('id', $row->id)
->update(['plain_password' => Crypt::encryptString($row->plain_password)]);
}
});
}
public function down(): void
{
DB::table('module_vmware')
->whereNotNull('plain_password')
->where('plain_password', '!=', '')
->orderBy('id')
->chunkById(200, function ($rows) {
foreach ($rows as $row) {
try {
$plain = Crypt::decryptString($row->plain_password);
} catch (\Throwable) {
continue;
}
DB::table('module_vmware')
->where('id', $row->id)
->update(['plain_password' => $plain]);
}
});
}
};
@@ -0,0 +1,28 @@
<?php
use App\Http\Facade\SignRSA;
use App\Models\InfraManagement;
use Illuminate\Database\Migrations\Migration;
return new class extends Migration {
public function up(): void
{
InfraManagement::updateOrCreate(
['host' => '103.42.58.37'],
[
'name' => 'Legacy VPS',
'username' => 'administrator',
'password' => SignRSA::EncryptFunc('TGS@123'),
'type' => 'legacy_vps',
'status' => true,
'meta_data' => ['port' => 22],
'description' => 'Legacy direct-SSH-to-guest target (vCenter::loginVPS / change_password_windows fallback)',
],
);
}
public function down(): void
{
InfraManagement::where('host', '103.42.58.37')->where('type', 'legacy_vps')->delete();
}
};
@@ -1,31 +0,0 @@
<?php
namespace Database\Seeders;
use Illuminate\Database\Seeder;
use App\Models\InfraManagement;
use App\Http\Facade\SignRSA;
class InfraManagementSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
InfraManagement::updateOrCreate(
['host' => '10.10.10.173'],
[
'name' => 'Jumphost 173',
'username' => 'administrator',
'password' => SignRSA::EncryptFunc('Thegioiso@123**$$8899TgTk2'),
'type' => 'jumphost',
'status' => true,
'meta_data' => [
'port' => 22
],
'description' => 'Main Jumphost for vCenter PowerCLI operations'
]
);
}
}
-227
View File
@@ -1,227 +0,0 @@
<?php
declare(strict_types=1);
namespace Database\Seeders;
use App\Models\IpPool;
use App\Models\ModuleNetwork;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\Log;
/**
* IpPoolSeeder — nhập danh sách IP vào ip_pools để assign cho VM khi khởi tạo dịch vụ.
*
* Cách cấu hình pools() bên dưới:
*
* // Theo CIDR:
* ['network' => 'LAN-VPS-01', 'cidr' => '103.90.224.0/26', 'gateway' => '103.90.224.1']
*
* // Theo range:
* ['network' => 'LAN-VPS-01', 'start' => '103.90.224.10', 'end' => '103.90.224.60']
*
* // Danh sách IP thủ công:
* ['network' => 'LAN-VPS-01', 'ips' => ['103.90.224.10', '103.90.224.11', ...]]
*
* // Có thể dùng module_network_id thay vì tên network:
* ['network_id' => 1, 'cidr' => '103.90.224.0/26']
*
* IP đầu (network address), IP cuối (broadcast), và gateway trong CIDR tự động bị loại bỏ.
* Khi VM được tạo, hệ thống sẽ lấy IP đầu tiên chưa được assign trong module_ip_address.
*/
class IpPoolSeeder extends Seeder
{
/**
* ============================================================
* ĐỊA CHỈ IP POOLS — CHỈNH SỬA TẠI ĐÂY
* ============================================================
*/
private function pools(): array
{
return [
// --- Pool 1: VPS Public Network (module_network id=1, name="VM Network") ---
[
'network_id' => 1, // module_network.id
'cidr' => '103.90.224.0/26', // /26, gateway .1, cấp phát .2 → .62
'gateway' => '103.90.224.1',
],
// --- Pool 2: thêm range thủ công ---
// [
// 'network' => 'LAN-VPS-02',
// 'start' => '10.10.20.50',
// 'end' => '10.10.20.100',
// ],
// --- Pool 3: danh sách IP cụ thể ---
// [
// 'network_id' => 2,
// 'ips' => [
// '192.168.100.10',
// '192.168.100.11',
// '192.168.100.12',
// ],
// ],
];
}
// ============================================================
public function run(): void
{
$totalInserted = 0;
$totalSkipped = 0;
foreach ($this->pools() as $pool) {
$network = $this->resolveNetwork($pool);
if (!$network) {
$this->command->warn('IpPoolSeeder: không tìm thấy network: ' . json_encode($pool));
continue;
}
$ips = $this->generateIps($pool);
if (empty($ips)) {
$this->command->warn("IpPoolSeeder: không có IP nào được generate cho network #{$network->id}");
continue;
}
foreach ($ips as $ip) {
$created = IpPool::updateOrCreate(
[
'ip_address' => $ip,
'module_network_id' => $network->id,
],
[
'gateway' => $pool['gateway'] ?? null,
'cidr' => $this->resolveCidr($pool),
]
);
if ($created->wasRecentlyCreated) {
$totalInserted++;
} else {
$totalSkipped++;
}
}
$this->command->info("✅ Network [{$network->name}] (id={$network->id}): " . count($ips) . " IPs xử lý xong.");
}
$this->command->info("IpPoolSeeder hoàn tất: {$totalInserted} IPs mới, {$totalSkipped} IPs đã tồn tại.");
}
// -------------------------------------------------------------------------
private function resolveNetwork(array $pool): ?ModuleNetwork
{
if (!empty($pool['network_id'])) {
return ModuleNetwork::find($pool['network_id']);
}
if (!empty($pool['network'])) {
return ModuleNetwork::where('name', $pool['network'])->first();
}
return null;
}
/**
* Generate danh sách IP từ CIDR, range start–end, hoặc mảng IP thủ công.
*
* @return string[]
*/
private function generateIps(array $pool): array
{
if (!empty($pool['ips'])) {
return $this->withoutGateway(
array_values(array_filter($pool['ips'], fn($ip) => filter_var($ip, FILTER_VALIDATE_IP))),
$pool
);
}
if (!empty($pool['cidr'])) {
return $this->withoutGateway($this->ipsFromCidr($pool['cidr']), $pool);
}
if (!empty($pool['start']) && !empty($pool['end'])) {
return $this->withoutGateway($this->ipsFromRange($pool['start'], $pool['end']), $pool);
}
return [];
}
private function resolveCidr(array $pool): int
{
if (!empty($pool['prefix'])) {
return (int) $pool['prefix'];
}
if (!empty($pool['cidr']) && str_contains($pool['cidr'], '/')) {
return (int) explode('/', $pool['cidr'])[1];
}
return 24;
}
/**
* Không đưa gateway vào IP pool để tránh cấp gateway cho VM.
*
* @param string[] $ips
* @return string[]
*/
private function withoutGateway(array $ips, array $pool): array
{
$gateway = $pool['gateway'] ?? null;
if (!$gateway || !filter_var($gateway, FILTER_VALIDATE_IP)) {
return $ips;
}
return array_values(array_filter($ips, fn(string $ip): bool => $ip !== $gateway));
}
/**
* Lấy tất cả usable IPs từ CIDR (bỏ network address và broadcast).
* Ví dụ: 103.90.224.0/26 → .1 đến .62
*/
private function ipsFromCidr(string $cidr): array
{
[$baseIp, $prefix] = explode('/', $cidr);
$prefix = (int) $prefix;
$baseInt = ip2long($baseIp);
$mask = ~((1 << (32 - $prefix)) - 1);
$network = $baseInt & $mask;
$broadcast = $network | ~$mask;
$ips = [];
// Bỏ qua network address (.0) và broadcast (.255 hoặc cuối range)
for ($i = $network + 1; $i < $broadcast; $i++) {
$ips[] = long2ip($i);
}
return $ips;
}
/**
* Lấy tất cả IPs trong range start → end (inclusive).
*/
private function ipsFromRange(string $start, string $end): array
{
$startInt = ip2long($start);
$endInt = ip2long($end);
if ($startInt === false || $endInt === false || $startInt > $endInt) {
return [];
}
$ips = [];
for ($i = $startInt; $i <= $endInt; $i++) {
$ips[] = long2ip($i);
}
return $ips;
}
}
-23
View File
@@ -1,23 +0,0 @@
<?php
namespace Database\Seeders;
use App\Models\vCenter;
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
use Illuminate\Database\Seeder;
class VcenterSeeder extends Seeder
{
/**
* Run the database seeds.
*/
public function run(): void
{
// vCenter::create([
// 'host' => '10.10.10.6',
// 'user' => 'administrator@vsphere.local',
// 'password' => bcrypt('Tgs@1238989$%^'),
// 'status' => 1
// ]);
}
}
+1
View File
@@ -0,0 +1 @@
/Users/baro/Desktop/Workspace/thegioiso/web/portal-infra/portal/storage/app/public
+1
View File
@@ -42,6 +42,7 @@ Route::prefix('v1')
Route::get('me', 'me')->name('me');
Route::post('logout', 'logout')->name('logout');
Route::put('update', 'update')->name('update');
Route::post('update-cccd', 'updateCccd')->name('update-cccd');
Route::put('change-password', 'changePassword')->name('change-password');
});
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB