Refactor code structure for improved readability and maintainability
This commit is contained in:
@@ -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)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@@ -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'
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
// ]);
|
||||
}
|
||||
}
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
/Users/baro/Desktop/Workspace/thegioiso/web/portal-infra/portal/storage/app/public
|
||||
@@ -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 |
Reference in New Issue
Block a user