update bug
This commit is contained in:
@@ -2124,6 +2124,114 @@ class vCenter
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign IP to Windows VM using PowerShell script
|
||||
*/
|
||||
public function assignIPWindows($product, $vmware_id = ''): array|string
|
||||
{
|
||||
try {
|
||||
// Kiểm tra null safety
|
||||
if (!$product || !$product->ipAddress || !$product->ipAddress->host || !$product->ipAddress->host->datacenter || !$product->ipAddress->host->datacenter->vCenter) {
|
||||
Log::error('❌ assignIPWindows: Missing required data', [
|
||||
'vmware_id' => $vmware_id,
|
||||
'product_id' => $product?->id ?? 'unknown',
|
||||
'ip_address' => $product?->ipAddress?->ip ?? 'unknown',
|
||||
'host' => $product?->ipAddress?->host?->name ?? 'unknown',
|
||||
'datacenter' => $product?->ipAddress?->host?->datacenter?->name ?? 'unknown',
|
||||
'vcenter' => $product?->ipAddress?->host?->datacenter?->vCenter?->host ?? 'unknown'
|
||||
]);
|
||||
return [
|
||||
'error' => true,
|
||||
'data' => 'Missing required data for IP assignment'
|
||||
];
|
||||
}
|
||||
|
||||
$this->SetConfig($product->ipAddress->host->datacenter->vCenter);
|
||||
|
||||
$vmware_name = (string) $product->name;
|
||||
$ipAddress = $product->ipAddress->ip;
|
||||
$gateway = $product->ipAddress->gateway;
|
||||
$netmask = $product->ipAddress->netmask ?? '255.255.255.0';
|
||||
$dns = $product->ipAddress->dns ?? '8.8.8.8';
|
||||
|
||||
// Convert netmask to prefix length (e.g., 255.255.255.0 = 24)
|
||||
$maskBits = $this->convertNetmaskToPrefixLength($netmask);
|
||||
|
||||
Log::channel('Custom')->info("===================================================================");
|
||||
Log::channel('Custom')->info("Assign IP to Windows VM - $vmware_name");
|
||||
Log::channel('Custom')->info("IP: $ipAddress, Gateway: $gateway, DNS: $dns, MaskBits: $maskBits");
|
||||
Log::channel('Custom')->info("\r\n");
|
||||
|
||||
// Create PowerShell command with dynamic values
|
||||
$Command = "\$vmName = '$vmware_name'; \$IP = '$ipAddress'; \$MaskBits = $maskBits; \$Gateway = '$gateway'; \$DNS = '$dns'; cd /; C:\CLISave\AssignIP\assignIPWindows.ps1;";
|
||||
|
||||
Log::channel('Custom')->info("Executing Command: $Command");
|
||||
|
||||
$output = $this->Command($Command);
|
||||
|
||||
Log::channel('Custom')->info("Script output: " . $output);
|
||||
Log::channel('Custom')->info("===================================================================");
|
||||
|
||||
// Check if execution was successful
|
||||
if (strpos($output, '🎉') !== false || strpos($output, 'successfully') !== false) {
|
||||
return [
|
||||
'error' => false,
|
||||
'data' => 'IP assigned successfully',
|
||||
'output' => $output
|
||||
];
|
||||
} else {
|
||||
return [
|
||||
'error' => true,
|
||||
'data' => 'Failed to assign IP',
|
||||
'output' => $output
|
||||
];
|
||||
}
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::channel('Custom')->error("Exception in assignIPWindows: " . $e->getMessage());
|
||||
return [
|
||||
'error' => true,
|
||||
'data' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert netmask to prefix length (e.g., 255.255.255.0 = 24)
|
||||
*/
|
||||
private function convertNetmaskToPrefixLength($netmask): int
|
||||
{
|
||||
$netmaskMap = [
|
||||
'255.255.255.255' => 32,
|
||||
'255.255.255.254' => 31,
|
||||
'255.255.255.252' => 30,
|
||||
'255.255.255.248' => 29,
|
||||
'255.255.255.240' => 28,
|
||||
'255.255.255.224' => 27,
|
||||
'255.255.255.192' => 26,
|
||||
'255.255.255.128' => 25,
|
||||
'255.255.255.0' => 24,
|
||||
'255.255.254.0' => 23,
|
||||
'255.255.252.0' => 22,
|
||||
'255.255.248.0' => 21,
|
||||
'255.255.240.0' => 20,
|
||||
'255.255.224.0' => 19,
|
||||
'255.255.192.0' => 18,
|
||||
'255.255.128.0' => 17,
|
||||
'255.255.0.0' => 16,
|
||||
'255.254.0.0' => 15,
|
||||
'255.252.0.0' => 14,
|
||||
'255.248.0.0' => 13,
|
||||
'255.240.0.0' => 12,
|
||||
'255.224.0.0' => 11,
|
||||
'255.192.0.0' => 10,
|
||||
'255.128.0.0' => 9,
|
||||
'255.0.0.0' => 8
|
||||
];
|
||||
|
||||
return $netmaskMap[$netmask] ?? 24; // Default to /24 if not found
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse snapshot output from PowerShell text format
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs\VirtualMachine\Create;
|
||||
|
||||
use App\Http\Services\Telegram;
|
||||
use App\Http\Services\vCenter;
|
||||
use App\Models\ModuleVmware;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class AssignIPJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public string $vmId;
|
||||
public int $moduleVmwareId;
|
||||
public string $availableIP;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*/
|
||||
public function __construct(string $vmId, int $moduleVmwareId, string $availableIP)
|
||||
{
|
||||
$this->vmId = $vmId;
|
||||
$this->moduleVmwareId = $moduleVmwareId;
|
||||
$this->availableIP = $availableIP;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(vCenter $vCenter, Telegram $telegram)
|
||||
{
|
||||
try {
|
||||
Log::info('🔧 Bắt đầu AssignIPJob', [
|
||||
'vm_id' => $this->vmId,
|
||||
'module_vmware_id' => $this->moduleVmwareId,
|
||||
'assigned_ip' => $this->availableIP
|
||||
]);
|
||||
|
||||
// Lấy thông tin ModuleVmware
|
||||
$moduleVmware = ModuleVmware::with(['ipAddress.host.datacenter.vCenter'])->find($this->moduleVmwareId);
|
||||
|
||||
if (!$moduleVmware || !$moduleVmware->ipAddress) {
|
||||
Log::error('❌ Không tìm thấy ModuleVmware hoặc IP address', [
|
||||
'vm_id' => $this->vmId,
|
||||
'module_vmware_id' => $this->moduleVmwareId
|
||||
]);
|
||||
|
||||
$telegram->SendErrorMessage([
|
||||
'error' => 'ModuleVmware or IP address not found',
|
||||
'vm_id' => $this->vmId,
|
||||
'module_vmware_id' => $this->moduleVmwareId
|
||||
], 'AssignIPJob - Data Not Found');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Login vào vCenter
|
||||
$vcenter = $moduleVmware->ipAddress->host->datacenter->vCenter;
|
||||
if (!$vCenter->loginServer($vcenter)) {
|
||||
Log::error('❌ Không thể login vào vCenter cho AssignIP', [
|
||||
'vm_id' => $this->vmId,
|
||||
'vcenter_host' => $vcenter->host
|
||||
]);
|
||||
|
||||
$telegram->SendErrorMessage([
|
||||
'error' => 'Cannot login to vCenter',
|
||||
'vm_id' => $this->vmId,
|
||||
'vcenter_host' => $vcenter->host
|
||||
], 'AssignIPJob - vCenter Login Failed');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Kiểm tra OS family và assign IP
|
||||
$this->handleIPAssignment($vCenter, $this->vmId, $moduleVmware, $this->availableIP, $telegram);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('❌ Lỗi trong AssignIPJob: ' . $e->getMessage(), [
|
||||
'vm_id' => $this->vmId,
|
||||
'module_vmware_id' => $this->moduleVmwareId,
|
||||
'error' => $e->getMessage(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
$telegram->SendErrorMessage([
|
||||
'error' => $e->getMessage(),
|
||||
'vm_id' => $this->vmId,
|
||||
'module_vmware_id' => $this->moduleVmwareId,
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine()
|
||||
], 'AssignIPJob - Exception');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle IP assignment based on OS family
|
||||
*/
|
||||
private function handleIPAssignment(vCenter $vCenter, string $vmId, ModuleVmware $moduleVmware, string $availableIP, Telegram $telegram): void
|
||||
{
|
||||
try {
|
||||
// Kiểm tra OS family trước khi assign IP
|
||||
$osFamily = $this->checkVMOSFamily($vCenter, $vmId);
|
||||
|
||||
if ($osFamily === 'WINDOWS') {
|
||||
Log::info('✅ Phát hiện Windows VM, bắt đầu assign IP', [
|
||||
'vm_id' => $vmId,
|
||||
'os_family' => $osFamily,
|
||||
'assigned_ip' => $availableIP
|
||||
]);
|
||||
|
||||
// Assign IP cho Windows VM
|
||||
$assignIPResult = $vCenter->assignIPWindows($moduleVmware, $vmId);
|
||||
|
||||
if (is_array($assignIPResult) && !$assignIPResult['error']) {
|
||||
Log::info('✅ Assign IP Windows thành công', [
|
||||
'vm_id' => $vmId,
|
||||
'result' => $assignIPResult['data']
|
||||
]);
|
||||
|
||||
$telegram->SendJobRunInfo(
|
||||
'AssignIPWindows',
|
||||
'success',
|
||||
[
|
||||
'vm_id' => $vmId,
|
||||
'assigned_ip' => $availableIP,
|
||||
'result' => $assignIPResult['data']
|
||||
]
|
||||
);
|
||||
} else {
|
||||
Log::warning('⚠️ Assign IP Windows thất bại', [
|
||||
'vm_id' => $vmId,
|
||||
'error' => $assignIPResult['data'] ?? 'Unknown error'
|
||||
]);
|
||||
|
||||
$telegram->SendErrorMessage([
|
||||
'error' => 'Assign IP Windows failed',
|
||||
'vm_id' => $vmId,
|
||||
'details' => $assignIPResult['data'] ?? 'Unknown error'
|
||||
], 'AssignIPWindows - Failed');
|
||||
}
|
||||
} elseif ($osFamily === 'LINUX' || $osFamily === 'OTHER') {
|
||||
Log::info('ℹ️ Phát hiện non-Windows VM, hệ thống đang nâng cấp', [
|
||||
'vm_id' => $vmId,
|
||||
'os_family' => $osFamily
|
||||
]);
|
||||
|
||||
$telegram->SendJobRunInfo(
|
||||
'AssignIP',
|
||||
'upgrade',
|
||||
[
|
||||
'vm_id' => $vmId,
|
||||
'os_family' => $osFamily,
|
||||
'message' => 'Hệ thống đang nâng cấp để hỗ trợ IP assignment cho ' . $osFamily
|
||||
]
|
||||
);
|
||||
} else {
|
||||
Log::warning('⚠️ Không thể xác định OS family', [
|
||||
'vm_id' => $vmId,
|
||||
'os_family' => $osFamily
|
||||
]);
|
||||
|
||||
$telegram->SendErrorMessage([
|
||||
'error' => 'Cannot determine OS family',
|
||||
'vm_id' => $vmId,
|
||||
'os_family' => $osFamily
|
||||
], 'AssignIP - Unknown OS');
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('❌ Lỗi khi handle IP assignment: ' . $e->getMessage(), [
|
||||
'vm_id' => $vmId,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
|
||||
$telegram->SendErrorMessage([
|
||||
'error' => 'IP Assignment handling failed',
|
||||
'vm_id' => $vmId,
|
||||
'details' => $e->getMessage()
|
||||
], 'HandleIPAssignment - Exception');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check VM OS family using vCenter API
|
||||
*/
|
||||
private function checkVMOSFamily(vCenter $vCenter, string $vmId): string
|
||||
{
|
||||
try {
|
||||
// Kiểm tra session còn valid không
|
||||
$check = $vCenter->checkApiExits();
|
||||
if (!$check) {
|
||||
Log::warning('vCenter session expired, cannot check OS family');
|
||||
return 'UNKNOWN';
|
||||
}
|
||||
|
||||
// Gọi API để lấy guest identity
|
||||
$response = Http::withoutVerifying()
|
||||
->withHeaders([
|
||||
'vmware-api-session-id' => $vCenter->token
|
||||
])
|
||||
->get("https://{$vCenter->apiUri}/rest/vcenter/vm/{$vmId}/guest/identity");
|
||||
|
||||
if ($response->successful()) {
|
||||
$data = $response->json();
|
||||
|
||||
if (isset($data['value']['family'])) {
|
||||
$family = strtoupper($data['value']['family']);
|
||||
|
||||
Log::info('✅ Phát hiện OS family', [
|
||||
'vm_id' => $vmId,
|
||||
'family' => $family,
|
||||
'full_name' => $data['value']['full_name'] ?? 'Unknown'
|
||||
]);
|
||||
|
||||
return $family;
|
||||
} else {
|
||||
Log::warning('⚠️ Không tìm thấy family trong response', [
|
||||
'vm_id' => $vmId,
|
||||
'response' => $data
|
||||
]);
|
||||
return 'UNKNOWN';
|
||||
}
|
||||
} else {
|
||||
Log::warning('⚠️ API call thất bại khi check OS family', [
|
||||
'vm_id' => $vmId,
|
||||
'status' => $response->status(),
|
||||
'response' => $response->body()
|
||||
]);
|
||||
return 'UNKNOWN';
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('❌ Exception khi check OS family: ' . $e->getMessage(), [
|
||||
'vm_id' => $vmId,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
return 'UNKNOWN';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,23 @@
|
||||
namespace App\Jobs\VirtualMachine\Create;
|
||||
|
||||
use App\Http\Facade\SignRSA;
|
||||
use App\Http\Services\Discord;
|
||||
use App\Http\Services\Telegram;
|
||||
use App\Http\Services\vCenter;
|
||||
use App\Jobs\Mail\Customer\VMCreationFailedJob;
|
||||
use App\Jobs\VirtualMachine\Create\AssignIPJob;
|
||||
use App\Jobs\VirtualMachine\Create\ConfigCLICommand;
|
||||
use App\Models\OrderItem;
|
||||
use App\Models\Os;
|
||||
use App\Services\VMDataService;
|
||||
use App\Models\ModuleVmware;
|
||||
use App\Models\ModuleIpAddress;
|
||||
use App\Models\IpPool;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class CreateVirtualMachineJob implements ShouldQueue
|
||||
{
|
||||
@@ -35,7 +40,7 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(vCenter $vCenter, Discord $discord, VMDataService $vmDataService)
|
||||
public function handle(vCenter $vCenter, Telegram $telegram)
|
||||
{
|
||||
try {
|
||||
$product = $this->item->getProductAttribute(); // dùng helper trong OrderItem
|
||||
@@ -43,7 +48,7 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
if (!$product) {
|
||||
$errorMessage = "Không tìm thấy sản phẩm từ OrderItem #{$this->item->id}";
|
||||
Log::warning($errorMessage);
|
||||
$discord->SendErrorMessage($errorMessage, 'CreateVirtualMachineJob - Missing Product');
|
||||
$telegram->SendErrorMessage($errorMessage, 'CreateVirtualMachineJob - Missing Product');
|
||||
|
||||
if (!$this->isRetry) {
|
||||
$this->sendFailureNotification($errorMessage);
|
||||
@@ -60,7 +65,7 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
if (!$vcenter || !$host || !$datacenter) {
|
||||
$errorMessage = "Thiếu thông tin host/datacenter/vcenter từ sản phẩm ID #{$product->id}";
|
||||
Log::error($errorMessage);
|
||||
$discord->SendErrorMessage([
|
||||
$telegram->SendErrorMessage([
|
||||
'error' => $errorMessage,
|
||||
'product_id' => $product->id,
|
||||
'order_item_id' => $this->item->id,
|
||||
@@ -79,7 +84,7 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
if (!$vCenter->loginServer($vcenter)) {
|
||||
$errorMessage = "Login vào vCenter thất bại với host: {$vcenter->host}";
|
||||
Log::error($errorMessage);
|
||||
$discord->SendErrorMessage([
|
||||
$telegram->SendErrorMessage([
|
||||
'error' => $errorMessage,
|
||||
'vcenter_host' => $vcenter->host,
|
||||
'vcenter_user' => $vcenter->user,
|
||||
@@ -102,70 +107,28 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
|
||||
// Lấy addon từ order item
|
||||
if ($this->item->addons) {
|
||||
Log::info("🔍 Tìm thấy addons cho order item", [
|
||||
'order_item_id' => $this->item->id,
|
||||
'addons_count' => $this->item->addons->count(),
|
||||
'addons' => $this->item->addons->map(function($addon) {
|
||||
return [
|
||||
'id' => $addon->id,
|
||||
'addon_id' => $addon->addon_id,
|
||||
'qty' => $addon->qty,
|
||||
'price' => $addon->price,
|
||||
'addon_name' => $addon->addonItems?->name ?? 'Unknown'
|
||||
];
|
||||
})
|
||||
]);
|
||||
|
||||
foreach ($this->item->addons as $orderAddon) {
|
||||
$addon = $orderAddon->addonItems;
|
||||
if (!$addon) {
|
||||
Log::warning("⚠️ Addon không tìm thấy addonItems", [
|
||||
'order_addon_id' => $orderAddon->id,
|
||||
'addon_id' => $orderAddon->addon_id
|
||||
]);
|
||||
continue;
|
||||
}
|
||||
if (!$addon) continue;
|
||||
|
||||
$addonName = strtolower(trim($addon->name));
|
||||
$quantity = (int) $orderAddon->qty;
|
||||
|
||||
Log::info("🔍 Xử lý addon", [
|
||||
'addon_name' => $addon->name,
|
||||
'addon_name_lower' => $addonName,
|
||||
'quantity' => $quantity,
|
||||
'addon_id' => $addon->id
|
||||
]);
|
||||
|
||||
switch ($addonName) {
|
||||
case 'cpu':
|
||||
$addonData['cpu'] += $quantity;
|
||||
Log::info("✅ Thêm CPU addon: +{$quantity}");
|
||||
break;
|
||||
case 'ram':
|
||||
$addonData['ram'] += $quantity;
|
||||
Log::info("✅ Thêm RAM addon: +{$quantity}GB");
|
||||
break;
|
||||
case 'disk':
|
||||
case 'ssd':
|
||||
$addonData['disk'] += $quantity;
|
||||
Log::info("✅ Thêm Disk addon: +{$quantity}GB");
|
||||
break;
|
||||
default:
|
||||
Log::warning("⚠️ Addon không được nhận diện: {$addonName}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log::info("ℹ️ Không có addons cho order item {$this->item->id}");
|
||||
}
|
||||
|
||||
Log::info('📊 Thông tin addon được xử lý', [
|
||||
'order_item_id' => $this->item->id,
|
||||
'addon_data' => $addonData,
|
||||
'addons_count' => $this->item->addons?->count() ?? 0,
|
||||
'has_addons' => !empty($addonData) && array_sum($addonData) > 0
|
||||
]);
|
||||
|
||||
$result = $vCenter->CreateVirtualMachine([
|
||||
'item' => $this->item,
|
||||
'host' => $host,
|
||||
@@ -194,7 +157,7 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
if (is_array($result) && ($result['error'] ?? false)) {
|
||||
$errorMessage = 'Tạo máy ảo thất bại: ' . json_encode($result);
|
||||
Log::warning($errorMessage);
|
||||
$discord->SendErrorMessage([
|
||||
$telegram->SendErrorMessage([
|
||||
'error' => $result['error'] ?? 'Unknown error',
|
||||
'result' => $result,
|
||||
'product_id' => $product->id,
|
||||
@@ -211,7 +174,7 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
} elseif ($result === 'Login_Again') {
|
||||
$errorMessage = 'Tạo máy ảo thất bại: cần đăng nhập lại.';
|
||||
Log::error($errorMessage);
|
||||
$discord->SendErrorMessage([
|
||||
$telegram->SendErrorMessage([
|
||||
'error' => $errorMessage,
|
||||
'product_id' => $product->id,
|
||||
'order_item_id' => $this->item->id,
|
||||
@@ -234,91 +197,86 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
$invoice = \App\Models\Invoice::where('order_id', $this->item->order->id)->first();
|
||||
if ($invoice) {
|
||||
$invoice->update(['vm_id' => $vmId]);
|
||||
Log::info('✅ Đã lưu vm_id vào bảng invoice', [
|
||||
'vm_id' => $vmId,
|
||||
'invoice_id' => $invoice->id,
|
||||
'order_id' => $this->item->order->id
|
||||
]);
|
||||
} else {
|
||||
Log::warning('⚠️ Không tìm thấy invoice cho order', [
|
||||
'order_id' => $this->item->order->id,
|
||||
'vm_id' => $vmId
|
||||
]);
|
||||
}
|
||||
|
||||
// Start VM ngay sau khi tạo thành công
|
||||
Log::info('🚀 Bắt đầu khởi động VM sau khi tạo thành công', [
|
||||
'vm_id' => $vmId,
|
||||
'order_item_id' => $this->item->id
|
||||
]);
|
||||
|
||||
$startResult = $vCenter->StartVirtualMachine($vmId);
|
||||
|
||||
if ($startResult === 'Login_Again') {
|
||||
// Thử đăng nhập lại và start VM
|
||||
Log::info('🔄 Thử đăng nhập lại vCenter để start VM', [
|
||||
'vm_id' => $vmId,
|
||||
'vcenter_host' => $vcenter->host
|
||||
]);
|
||||
|
||||
if ($vCenter->loginServer($vcenter)) {
|
||||
$startResult = $vCenter->StartVirtualMachine($vmId);
|
||||
Log::info('✅ Đăng nhập lại thành công, thử start VM lần nữa', [
|
||||
'vm_id' => $vmId,
|
||||
'start_result' => $startResult
|
||||
]);
|
||||
} else {
|
||||
Log::error('❌ Không thể đăng nhập lại vCenter để start VM', [
|
||||
'vm_id' => $vmId,
|
||||
'vcenter_host' => $vcenter->host
|
||||
]);
|
||||
}
|
||||
}
|
||||
// Start VM ngay sau khi tạo thành công (sử dụng API trực tiếp)
|
||||
$startResult = $this->startVirtualMachineDirectly($vCenter, $vmId);
|
||||
|
||||
if (is_array($startResult) && $startResult['error'] === false) {
|
||||
Log::info('✅ Khởi động VM thành công', [
|
||||
'vm_id' => $vmId,
|
||||
'order_item_id' => $this->item->id,
|
||||
'result' => $startResult['data']
|
||||
]);
|
||||
|
||||
// Dispatch ConfigCLICommand để cấu hình VM và lấy password
|
||||
$configJob = new ConfigCLICommand($vmId, $this->item, $vCenter);
|
||||
dispatch($configJob)->delay(now()->addMinutes(2)); // Chờ 2 phút để VM boot hoàn toàn
|
||||
}
|
||||
|
||||
// Lưu dữ liệu VM vào database
|
||||
try {
|
||||
$customer = $this->item->order->customer;
|
||||
$serverName = $this->item->server_name ?? 'VM-' . $vmId;
|
||||
|
||||
Log::info('✅ Đã dispatch ConfigCLICommand job', [
|
||||
'vm_id' => $vmId,
|
||||
'order_item_id' => $this->item->id
|
||||
// 1. Lấy IP available từ IPPool
|
||||
$networkId = $product->ipAddress?->module_network_id ?? 1;
|
||||
$availableIP = $this->getAvailableIPFromPool($networkId);
|
||||
|
||||
// 2. Tạo ModuleIpAddress với IP từ pool
|
||||
$moduleIpAddress = ModuleIpAddress::create([
|
||||
'vm_name' => $serverName,
|
||||
'module_host_id' => $host->id,
|
||||
'module_network_id' => $networkId,
|
||||
'ip' => $availableIP,
|
||||
'gateway' => $product->ipAddress?->gateway ?? '0.0.0.0',
|
||||
'netmask' => $product->ipAddress?->netmask ?? '255.255.255.0',
|
||||
'dns' => $product->ipAddress?->dns ?? '8.8.8.8',
|
||||
'status' => 1 // 1 = Assigned
|
||||
]);
|
||||
} else {
|
||||
Log::warning('⚠️ Khởi động VM thất bại', [
|
||||
'vm_id' => $vmId,
|
||||
'order_item_id' => $this->item->id,
|
||||
'error' => $startResult['data']
|
||||
|
||||
// 3. Tạo ModuleVmware với IP address vừa tạo
|
||||
$moduleVmware = ModuleVmware::create([
|
||||
'name' => $serverName,
|
||||
'module_ip_address_id' => $moduleIpAddress->id,
|
||||
'port' => 22,
|
||||
'order_id' => $this->item->order->id,
|
||||
'first_payment' => $this->item->order->first_payment ?? 0,
|
||||
'next_payment' => $this->item->order->next_payment ?? 0,
|
||||
'next_due_date' => $this->item->order->next_due_date ?? null,
|
||||
'next_invoice' => $this->item->order->next_invoice ?? 0,
|
||||
'user' => 'root',
|
||||
'password' => '',
|
||||
'hostname' => $serverName,
|
||||
'vmware_id' => $vmId,
|
||||
'os_id' => $this->item->os_id ?? 1,
|
||||
'customer_id' => $customer->id,
|
||||
'status' => 1
|
||||
]);
|
||||
}
|
||||
|
||||
// Lưu dữ liệu VM và IP address vào database
|
||||
$ipAddresses = [];
|
||||
|
||||
if (is_array($result)) {
|
||||
$ipAddresses = $result['ip_addresses'] ?? [];
|
||||
}
|
||||
|
||||
$saveResult = $vmDataService->saveVMData($this->item, $vmId, $ipAddresses);
|
||||
|
||||
if ($saveResult['success']) {
|
||||
// 4. Cập nhật product với module_vmware_id
|
||||
$product->module_vmware_id = $moduleVmware->id;
|
||||
$product->save();
|
||||
|
||||
Log::info('✅ Lưu dữ liệu VM và IP address thành công', [
|
||||
'vm_id' => $saveResult['vm']->id,
|
||||
'vmware_id' => $vmId,
|
||||
'ip_address_id' => $saveResult['ip_address']->id,
|
||||
'module_vmware_id' => $moduleVmware->id,
|
||||
'module_ip_address_id' => $moduleIpAddress->id,
|
||||
'assigned_ip' => $availableIP,
|
||||
'name' => $serverName,
|
||||
'order_item_id' => $this->item->id
|
||||
]);
|
||||
} else {
|
||||
Log::error('❌ Lỗi khi lưu dữ liệu VM và IP address', [
|
||||
|
||||
// Dispatch AssignIPJob để chạy sau 5 phút
|
||||
AssignIPJob::dispatch($vmId, $moduleVmware->id, $availableIP)
|
||||
->delay(now()->addMinutes(5));
|
||||
|
||||
Log::info('✅ Đã dispatch AssignIPJob để chạy sau 5 phút', [
|
||||
'vm_id' => $vmId,
|
||||
'module_vmware_id' => $moduleVmware->id,
|
||||
'assigned_ip' => $availableIP,
|
||||
'delay_minutes' => 5
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('❌ Lỗi khi lưu dữ liệu VM và IP address: ' . $e->getMessage(), [
|
||||
'vmware_id' => $vmId,
|
||||
'error' => $saveResult['message'],
|
||||
'order_item_id' => $this->item->id
|
||||
'order_item_id' => $this->item->id,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -331,17 +289,8 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
}
|
||||
}
|
||||
|
||||
// Log thông tin addon đã được áp dụng
|
||||
if (!empty($addonData) && array_sum($addonData) > 0) {
|
||||
Log::info('Addon đã được áp dụng thành công', [
|
||||
'vm_id' => $vmId,
|
||||
'addon_data' => $addonData,
|
||||
'order_item_id' => $this->item->id
|
||||
]);
|
||||
}
|
||||
|
||||
// Gửi thông báo thành công
|
||||
$discord->SendJobRunInfo(
|
||||
$telegram->SendJobRunInfo(
|
||||
'CreateVirtualMachineJob',
|
||||
'success',
|
||||
[
|
||||
@@ -364,7 +313,7 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
$discord->SendErrorMessage([
|
||||
$telegram->SendErrorMessage([
|
||||
'error' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
@@ -379,6 +328,88 @@ class CreateVirtualMachineJob implements ShouldQueue
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available IP from IPPool that doesn't exist in ModuleIpAddress
|
||||
*/
|
||||
private function getAvailableIPFromPool(int $networkId): string
|
||||
{
|
||||
// Lấy tất cả IP từ IPPool theo network_id, order by ip_address
|
||||
$ipPoolList = IpPool::where('module_network_id', $networkId)
|
||||
->orderBy('ip_address')
|
||||
->get();
|
||||
|
||||
if ($ipPoolList->isEmpty()) {
|
||||
// Nếu không có IP trong pool, trả về IP default
|
||||
return '0.0.0.0';
|
||||
}
|
||||
|
||||
// Lấy tất cả IP đã được assign trong ModuleIpAddress
|
||||
$assignedIPs = ModuleIpAddress::where('module_network_id', $networkId)
|
||||
->pluck('ip')
|
||||
->toArray();
|
||||
|
||||
// Tìm IP đầu tiên chưa được assign
|
||||
foreach ($ipPoolList as $ipPool) {
|
||||
if (!in_array($ipPool->ip_address, $assignedIPs)) {
|
||||
Log::info('✅ Lấy IP available từ pool', [
|
||||
'ip' => $ipPool->ip_address,
|
||||
'network_id' => $networkId
|
||||
]);
|
||||
return $ipPool->ip_address;
|
||||
}
|
||||
}
|
||||
|
||||
// Nếu tất cả IP đã được assign, log warning và trả về IP default
|
||||
Log::warning('⚠️ Không có IP available trong pool', [
|
||||
'network_id' => $networkId,
|
||||
'total_ips_in_pool' => $ipPoolList->count(),
|
||||
'assigned_ips' => count($assignedIPs)
|
||||
]);
|
||||
|
||||
return '0.0.0.0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Start VM directly using vCenter API
|
||||
*/
|
||||
private function startVirtualMachineDirectly(vCenter $vCenter, string $vmId): array|string
|
||||
{
|
||||
try {
|
||||
// Kiểm tra xem đã có session chưa
|
||||
$check = $vCenter->checkApiExits();
|
||||
if (!$check) {
|
||||
return 'Login_Again';
|
||||
}
|
||||
|
||||
// Gọi API start VM trực tiếp
|
||||
$response = Http::withoutVerifying()
|
||||
->withHeaders([
|
||||
'vmware-api-session-id' => $vCenter->token
|
||||
])
|
||||
->post("https://{$vCenter->apiUri}/rest/vcenter/vm/{$vmId}/power/start");
|
||||
|
||||
if ($response->status() === 200) {
|
||||
return [
|
||||
'error' => false,
|
||||
'data' => 'success'
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'error' => true,
|
||||
'data' => $response->body()
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Lỗi khi start VM: ' . $e->getMessage());
|
||||
return [
|
||||
'error' => true,
|
||||
'data' => $e->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Send failure notification to customer
|
||||
*/
|
||||
|
||||
@@ -4,13 +4,13 @@ namespace App\Jobs\VirtualMachine;
|
||||
|
||||
use App\Enums\Invoice\InvoiceStatus;
|
||||
use App\Enums\Order\OrderStatus;
|
||||
use App\Http\Services\Discord;
|
||||
use App\Http\Services\Telegram;
|
||||
use App\Http\Services\Invoice\InvoiceService;
|
||||
use App\Jobs\VirtualMachine\Create\CreateVirtualMachineJob;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\Order;
|
||||
use App\Models\OrderItem;
|
||||
use App\Services\VMDataService;
|
||||
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
@@ -39,7 +39,7 @@ class ProcessOrderPaymentJob implements ShouldQueue
|
||||
/**
|
||||
* Execute the job.
|
||||
*/
|
||||
public function handle(InvoiceService $invoiceService, Discord $discord): void
|
||||
public function handle(InvoiceService $invoiceService, Telegram $telegram): void
|
||||
{
|
||||
try {
|
||||
$user = $this->order->customer;
|
||||
@@ -57,7 +57,7 @@ class ProcessOrderPaymentJob implements ShouldQueue
|
||||
'user_credit' => $user->credit,
|
||||
'order_amount' => $this->order->amount
|
||||
]);
|
||||
$discord->SendErrorMessage($errorMessage, 'ProcessOrderPaymentJob - Insufficient Credit');
|
||||
$telegram->SendErrorMessage($errorMessage, 'ProcessOrderPaymentJob - Insufficient Credit');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ class ProcessOrderPaymentJob implements ShouldQueue
|
||||
if (!$hasVMCreationAttempt) {
|
||||
$errorMessage = "Không có sản phẩm VPS nào được xử lý trong đơn hàng #{$this->order->order_num}";
|
||||
Log::error($errorMessage);
|
||||
$discord->SendErrorMessage($errorMessage, 'ProcessOrderPaymentJob - No VM Creation Attempt');
|
||||
$telegram->SendErrorMessage($errorMessage, 'ProcessOrderPaymentJob - No VM Creation Attempt');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ class ProcessOrderPaymentJob implements ShouldQueue
|
||||
if (empty($vmCreationResults)) {
|
||||
$errorMessage = "Không có kết quả tạo VM nào được trả về cho đơn hàng #{$this->order->order_num}";
|
||||
Log::error($errorMessage);
|
||||
$discord->SendErrorMessage($errorMessage, 'ProcessOrderPaymentJob - Empty VM Results');
|
||||
$telegram->SendErrorMessage($errorMessage, 'ProcessOrderPaymentJob - Empty VM Results');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -129,8 +129,8 @@ class ProcessOrderPaymentJob implements ShouldQueue
|
||||
$invoiceService->markAsPaid($invoice);
|
||||
$this->order->update(['status' => OrderStatus::ACTIVE->value]);
|
||||
|
||||
// Gửi thông báo Discord
|
||||
$discord->SendOrderPaidNotification($invoice);
|
||||
// Gửi thông báo Telegram
|
||||
$telegram->SendOrderPaidNotification($invoice);
|
||||
|
||||
DB::commit();
|
||||
|
||||
@@ -156,7 +156,7 @@ class ProcessOrderPaymentJob implements ShouldQueue
|
||||
// Kiểm tra trường hợp đặc biệt: có VPS nhưng không có VM nào thành công
|
||||
if (count($vmCreationResults) > 0 && count($successfulVMs) === 0) {
|
||||
Log::error("Có sản phẩm VPS nhưng không có VM nào được tạo thành công", $errorDetails);
|
||||
$discord->SendErrorMessage(array_merge($errorDetails, [
|
||||
$telegram->SendErrorMessage(array_merge($errorDetails, [
|
||||
'error' => 'Tất cả VM đều thất bại - không có VM nào thành công'
|
||||
]), 'ProcessOrderPaymentJob - All VMs Failed');
|
||||
}
|
||||
@@ -172,7 +172,7 @@ class ProcessOrderPaymentJob implements ShouldQueue
|
||||
// Đã thử hết số lần, gửi thông báo lỗi
|
||||
Log::error("Đã thử tạo VM {$this->retryCount} lần nhưng vẫn thất bại", $errorDetails);
|
||||
|
||||
$discord->SendErrorMessage(array_merge($errorDetails, [
|
||||
$telegram->SendErrorMessage(array_merge($errorDetails, [
|
||||
'error' => 'Không thể tạo VM sau nhiều lần thử',
|
||||
'retry_count' => $this->retryCount
|
||||
]), 'ProcessOrderPaymentJob - VM Creation Failed After Retries');
|
||||
@@ -189,7 +189,7 @@ class ProcessOrderPaymentJob implements ShouldQueue
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
$discord->SendErrorMessage([
|
||||
$telegram->SendErrorMessage([
|
||||
'error' => $e->getMessage(),
|
||||
'order_id' => $this->order->id,
|
||||
'order_num' => $this->order->order_num,
|
||||
@@ -206,11 +206,10 @@ class ProcessOrderPaymentJob implements ShouldQueue
|
||||
{
|
||||
try {
|
||||
$vCenter = app(\App\Http\Services\vCenter::class);
|
||||
$discord = app(Discord::class);
|
||||
$vmDataService = app(VMDataService::class);
|
||||
$telegram = app(\App\Http\Services\Telegram::class);
|
||||
|
||||
$vmJob = new CreateVirtualMachineJob($item, $this->retryCount > 0);
|
||||
return $vmJob->handle($vCenter, $discord, $vmDataService);
|
||||
return $vmJob->handle($vCenter, $telegram);
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
Log::error("Lỗi khi tạo VM cho order item #{$item->id}: " . $e->getMessage(), [
|
||||
|
||||
Generated
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "portal_new",
|
||||
"name": "portal",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
var _0x1762dc=_0x57d2;function _0x57d2(_0x5dbdf6,_0x4deba6){var _0x114d2a=_0x114d();return _0x57d2=function(_0x57d2ae,_0x5dd378){_0x57d2ae=_0x57d2ae-0xb2;var _0xc01e33=_0x114d2a[_0x57d2ae];return _0xc01e33;},_0x57d2(_0x5dbdf6,_0x4deba6);}function _0x114d(){var _0x4c0aa5=['216710MewCHs','63mjvrxI','204726ohtGgN','85mZIaQI','1454607NBeLaf','XMLHttpRequest','8xvChiS','262116HWIILm','axios','202936wXyZjM','2079740iYeTTy','9UNSxUP','66714xRPaeO'];_0x114d=function(){return _0x4c0aa5;};return _0x114d();}(function(_0x2878a2,_0x42b4a6){var _0x349482=_0x57d2,_0x5d2eee=_0x2878a2();while(!![]){try{var _0x4362f4=parseInt(_0x349482(0xb8))/0x1+-parseInt(_0x349482(0xbe))/0x2+-parseInt(_0x349482(0xb3))/0x3*(parseInt(_0x349482(0xb7))/0x4)+parseInt(_0x349482(0xb4))/0x5*(-parseInt(_0x349482(0xbd))/0x6)+parseInt(_0x349482(0xb2))/0x7*(parseInt(_0x349482(0xba))/0x8)+parseInt(_0x349482(0xbc))/0x9*(parseInt(_0x349482(0xbb))/0xa)+-parseInt(_0x349482(0xb5))/0xb;if(_0x4362f4===_0x42b4a6)break;else _0x5d2eee['push'](_0x5d2eee['shift']());}catch(_0x3c398f){_0x5d2eee['push'](_0x5d2eee['shift']());}}}(_0x114d,0x204c6),window['_']=require('lodash'),window[_0x1762dc(0xb9)]=require(_0x1762dc(0xb9)),window[_0x1762dc(0xb9)]['defaults']['headers']['common']['X-Requested-With']=_0x1762dc(0xb6));
|
||||
function _0x2c1b(_0x31a4b3,_0x4143f7){var _0x32809c=_0x3280();return _0x2c1b=function(_0x2c1b8c,_0x2e78d4){_0x2c1b8c=_0x2c1b8c-0x161;var _0x5cb83f=_0x32809c[_0x2c1b8c];return _0x5cb83f;},_0x2c1b(_0x31a4b3,_0x4143f7);}var _0x4700e6=_0x2c1b;function _0x3280(){var _0x156d31=['X-Requested-With','XMLHttpRequest','982036kAftIO','1040983ADGZbD','3652080kyWbwr','12764832KhVxvD','2236990sWEKnL','756124wamvcB','axios','defaults','6BtBkrf','lodash','9929241lvmgxe'];_0x3280=function(){return _0x156d31;};return _0x3280();}(function(_0x5bd356,_0x1c5ed6){var _0x800751=_0x2c1b,_0x22fb42=_0x5bd356();while(!![]){try{var _0x24f28d=-parseInt(_0x800751(0x162))/0x1+parseInt(_0x800751(0x166))/0x2*(-parseInt(_0x800751(0x169))/0x3)+-parseInt(_0x800751(0x161))/0x4+parseInt(_0x800751(0x165))/0x5+-parseInt(_0x800751(0x163))/0x6+parseInt(_0x800751(0x16b))/0x7+parseInt(_0x800751(0x164))/0x8;if(_0x24f28d===_0x1c5ed6)break;else _0x22fb42['push'](_0x22fb42['shift']());}catch(_0x52e159){_0x22fb42['push'](_0x22fb42['shift']());}}}(_0x3280,0xc5cb9),window['_']=require(_0x4700e6(0x16a)),window[_0x4700e6(0x167)]=require('axios'),window[_0x4700e6(0x167)][_0x4700e6(0x168)]['headers']['common'][_0x4700e6(0x16c)]=_0x4700e6(0x16d));
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
const _0x296055=_0x29ee;function _0x2c68(){const _0x1f9344=['[data-skeleton-id=\x22','loadingStates','os-selection','loading','defaultLoadingTime','forEach','addons-summary','4wLKhDM','showMultipleSkeletons','simulateOsLoading','plugin','magic','319479kojkdt','simulateAddonsLoading','showSkeleton','updateComponentState','undefined','1239843aCGCuh','set','12TcGlaS','exports','domain-input','3064185gkzkBX','2366FqaXze','alpine:init','9368UrQMuz','1630740SBnQwr','8593002lqbhoT','hideSkeleton','addEventListener','isLoading','querySelector','_x_dataStack','70wWhrIx','cartSkeleton','229345ICFppG','simulateCartLoading'];_0x2c68=function(){return _0x1f9344;};return _0x2c68();}(function(_0x200766,_0xad9ac7){const _0x55fb1d=_0x29ee,_0xf0b472=_0x200766();while(!![]){try{const _0x410d6b=-parseInt(_0x55fb1d(0x8c))/0x1*(-parseInt(_0x55fb1d(0x95))/0x2)+-parseInt(_0x55fb1d(0x9a))/0x3*(-parseInt(_0x55fb1d(0xa1))/0x4)+-parseInt(_0x55fb1d(0xa4))/0x5+parseInt(_0x55fb1d(0x83))/0x6+parseInt(_0x55fb1d(0xa5))/0x7*(parseInt(_0x55fb1d(0x82))/0x8)+-parseInt(_0x55fb1d(0x84))/0x9+-parseInt(_0x55fb1d(0x8a))/0xa*(-parseInt(_0x55fb1d(0x9f))/0xb);if(_0x410d6b===_0xad9ac7)break;else _0xf0b472['push'](_0xf0b472['shift']());}catch(_0x39cf15){_0xf0b472['push'](_0xf0b472['shift']());}}}(_0x2c68,0xa2dfd));function _0x29ee(_0x3d9ac9,_0x3ca5c0){const _0x2c6812=_0x2c68();return _0x29ee=function(_0x29eeaa,_0x2cd70f){_0x29eeaa=_0x29eeaa-0x81;let _0x29663b=_0x2c6812[_0x29eeaa];return _0x29663b;},_0x29ee(_0x3d9ac9,_0x3ca5c0);}class CartSkeletonManager{constructor(){const _0xe426fd=_0x29ee;this[_0xe426fd(0x8f)]=new Map(),this['defaultLoadingTime']=0x3e8;}[_0x296055(0x9c)](_0x491861,_0x3f27ac=this['defaultLoadingTime']){const _0x214ea7=_0x296055;this['loadingStates'][_0x214ea7(0xa0)](_0x491861,!![]),this['updateComponentState'](_0x491861,!![]),setTimeout(()=>{const _0x2db69a=_0x214ea7;this[_0x2db69a(0x85)](_0x491861);},_0x3f27ac);}['hideSkeleton'](_0x5a3579){const _0x2313df=_0x296055;this[_0x2313df(0x8f)][_0x2313df(0xa0)](_0x5a3579,![]),this[_0x2313df(0x9d)](_0x5a3579,![]);}[_0x296055(0x9d)](_0x14c8e2,_0x3114c9){const _0xd1f8cd=_0x296055,_0x140b3a=document[_0xd1f8cd(0x88)](_0xd1f8cd(0x8e)+_0x14c8e2+'\x22]');if(_0x140b3a&&_0x140b3a[_0xd1f8cd(0x89)]){const _0x2452cd=_0x140b3a['_x_dataStack'][0x0];_0x2452cd&&typeof _0x2452cd[_0xd1f8cd(0x91)]!=='undefined'&&(_0x2452cd[_0xd1f8cd(0x91)]=_0x3114c9);}}[_0x296055(0x96)](_0x5a5d5f,_0x117f23=this[_0x296055(0x92)]){const _0x4904b4=_0x296055;_0x5a5d5f[_0x4904b4(0x93)](_0x330247=>this[_0x4904b4(0x9c)](_0x330247,_0x117f23));}['hideMultipleSkeletons'](_0x5b7627){const _0xf8cc03=_0x296055;_0x5b7627[_0xf8cc03(0x93)](_0x39bbf0=>this[_0xf8cc03(0x85)](_0x39bbf0));}['isLoading'](_0x1a4f1a){const _0x20b30d=_0x296055;return this[_0x20b30d(0x8f)]['get'](_0x1a4f1a)||![];}[_0x296055(0x8d)](){const _0x156e0e=_0x296055,_0x44401c=[_0x156e0e(0xa3),'product-details','os-selection',_0x156e0e(0x94)];this[_0x156e0e(0x96)](_0x44401c,0x320);}[_0x296055(0x97)](){const _0x209ea9=_0x296055;this[_0x209ea9(0x9c)](_0x209ea9(0x90),0x258);}[_0x296055(0x9b)](){this['showSkeleton']('addons-summary',0x1f4);}}window[_0x296055(0x8b)]=new CartSkeletonManager(),document[_0x296055(0x86)](_0x296055(0x81),()=>{const _0x3710ca=_0x296055;Alpine[_0x3710ca(0x98)]('skeleton',_0x1d20c2=>{const _0x36b3cb=_0x3710ca;_0x1d20c2[_0x36b3cb(0x99)]('skeleton',()=>({'show':(_0x42c674,_0x18f6f3)=>window['cartSkeleton'][_0x36b3cb(0x9c)](_0x42c674,_0x18f6f3),'hide':_0x2ca03e=>window[_0x36b3cb(0x8b)]['hideSkeleton'](_0x2ca03e),'isLoading':_0x4361f4=>window['cartSkeleton'][_0x36b3cb(0x87)](_0x4361f4)}));});}),document[_0x296055(0x86)]('DOMContentLoaded',()=>{const _0x562c06=_0x296055;document[_0x562c06(0x88)]('[data-cart-page]')&&setTimeout(()=>{window['cartSkeleton']['simulateCartLoading']();},0x64);});typeof module!==_0x296055(0x9e)&&module[_0x296055(0xa2)]&&(module['exports']=CartSkeletonManager);
|
||||
function _0x530d(){const _0x39473f=['querySelector','forEach','[data-cart-page]','simulateOsLoading','96fdVbpo','60906Rtgttq','plugin','508922ZOUlhi','isLoading','3224280TMLjFH','showMultipleSkeletons','2744962jDgLWz','os-selection','updateComponentState','cartSkeleton','set','20VLcaNU','1042384whQxwX','exports','loading','hideMultipleSkeletons','[data-skeleton-id=\x22','product-details','loadingStates','668BFcXPD','203Qwzqth','hideSkeleton','addEventListener','showSkeleton','simulateAddonsLoading','magic','domain-input','addons-summary','simulateCartLoading','DOMContentLoaded','defaultLoadingTime','13075MkPJrU','549630TdNXAb','undefined','_x_dataStack','skeleton'];_0x530d=function(){return _0x39473f;};return _0x530d();}const _0xc69068=_0x4e72;(function(_0xb2e1fb,_0x1deb45){const _0x401ab1=_0x4e72,_0x51607d=_0xb2e1fb();while(!![]){try{const _0x3c6f82=-parseInt(_0x401ab1(0x174))/0x1+-parseInt(_0x401ab1(0x17e))/0x2+parseInt(_0x401ab1(0x176))/0x3+parseInt(_0x401ab1(0x185))/0x4*(parseInt(_0x401ab1(0x191))/0x5)+-parseInt(_0x401ab1(0x172))/0x6*(-parseInt(_0x401ab1(0x186))/0x7)+-parseInt(_0x401ab1(0x171))/0x8*(parseInt(_0x401ab1(0x192))/0x9)+parseInt(_0x401ab1(0x17d))/0xa*(parseInt(_0x401ab1(0x178))/0xb);if(_0x3c6f82===_0x1deb45)break;else _0x51607d['push'](_0x51607d['shift']());}catch(_0x1820ae){_0x51607d['push'](_0x51607d['shift']());}}}(_0x530d,0x84516));function _0x4e72(_0x31f2bd,_0x287d46){const _0x530d28=_0x530d();return _0x4e72=function(_0x4e7201,_0x59af4a){_0x4e7201=_0x4e7201-0x16a;let _0x3fb0f0=_0x530d28[_0x4e7201];return _0x3fb0f0;},_0x4e72(_0x31f2bd,_0x287d46);}class CartSkeletonManager{constructor(){const _0x4fc730=_0x4e72;this[_0x4fc730(0x184)]=new Map(),this['defaultLoadingTime']=0x3e8;}[_0xc69068(0x189)](_0x269eb4,_0x82f271=this['defaultLoadingTime']){const _0x55f7ef=_0xc69068;this['loadingStates'][_0x55f7ef(0x17c)](_0x269eb4,!![]),this['updateComponentState'](_0x269eb4,!![]),setTimeout(()=>{const _0xd19a29=_0x55f7ef;this[_0xd19a29(0x187)](_0x269eb4);},_0x82f271);}[_0xc69068(0x187)](_0x30a016){const _0x1cfb16=_0xc69068;this[_0x1cfb16(0x184)][_0x1cfb16(0x17c)](_0x30a016,![]),this[_0x1cfb16(0x17a)](_0x30a016,![]);}[_0xc69068(0x17a)](_0x3d7c1d,_0x2088dc){const _0x16840a=_0xc69068,_0x2eeca0=document[_0x16840a(0x16d)](_0x16840a(0x182)+_0x3d7c1d+'\x22]');if(_0x2eeca0&&_0x2eeca0[_0x16840a(0x16b)]){const _0x1ed0ae=_0x2eeca0[_0x16840a(0x16b)][0x0];_0x1ed0ae&&typeof _0x1ed0ae[_0x16840a(0x180)]!=='undefined'&&(_0x1ed0ae[_0x16840a(0x180)]=_0x2088dc);}}[_0xc69068(0x177)](_0x26300f,_0x590035=this[_0xc69068(0x190)]){const _0x51fa0c=_0xc69068;_0x26300f[_0x51fa0c(0x16e)](_0x35469d=>this[_0x51fa0c(0x189)](_0x35469d,_0x590035));}[_0xc69068(0x181)](_0x28270f){const _0x3865ff=_0xc69068;_0x28270f[_0x3865ff(0x16e)](_0x5cbaab=>this['hideSkeleton'](_0x5cbaab));}[_0xc69068(0x175)](_0x5edb9a){return this['loadingStates']['get'](_0x5edb9a)||![];}[_0xc69068(0x18e)](){const _0x1d085c=_0xc69068,_0x5e78a5=[_0x1d085c(0x18c),_0x1d085c(0x183),'os-selection',_0x1d085c(0x18d)];this[_0x1d085c(0x177)](_0x5e78a5,0x320);}[_0xc69068(0x170)](){const _0x16f1fb=_0xc69068;this[_0x16f1fb(0x189)](_0x16f1fb(0x179),0x258);}[_0xc69068(0x18a)](){const _0x1abc6d=_0xc69068;this[_0x1abc6d(0x189)]('addons-summary',0x1f4);}}window['cartSkeleton']=new CartSkeletonManager(),document[_0xc69068(0x188)]('alpine:init',()=>{const _0xd1a072=_0xc69068;Alpine[_0xd1a072(0x173)](_0xd1a072(0x16c),_0x45ef1f=>{const _0x5ba265=_0xd1a072;_0x45ef1f[_0x5ba265(0x18b)]('skeleton',()=>({'show':(_0x14650e,_0xbc076d)=>window['cartSkeleton']['showSkeleton'](_0x14650e,_0xbc076d),'hide':_0x50fe25=>window[_0x5ba265(0x17b)][_0x5ba265(0x187)](_0x50fe25),'isLoading':_0x20a6fb=>window[_0x5ba265(0x17b)][_0x5ba265(0x175)](_0x20a6fb)}));});}),document[_0xc69068(0x188)](_0xc69068(0x18f),()=>{const _0x3f278e=_0xc69068;document[_0x3f278e(0x16d)](_0x3f278e(0x16f))&&setTimeout(()=>{const _0x4a84f9=_0x3f278e;window['cartSkeleton'][_0x4a84f9(0x18e)]();},0x64);});typeof module!==_0xc69068(0x16a)&&module[_0xc69068(0x17f)]&&(module['exports']=CartSkeletonManager);
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
|
||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -525,8 +525,8 @@ function snapshotTable() {
|
||||
// Constants
|
||||
const API_ENDPOINTS = {
|
||||
GET_SNAPSHOTS: "/dich-vu-dang-ky/secure/get-snapshots",
|
||||
REMOVE_SNAPSHOT: "/secure/remove-snapshot",
|
||||
REVERT_SNAPSHOT: "/secure/revert-snapshot",
|
||||
REMOVE_SNAPSHOT: "/dich-vu-dang-ky/secure/remove-snapshot",
|
||||
REVERT_SNAPSHOT: "/dich-vu-dang-ky/secure/revert-snapshot",
|
||||
};
|
||||
|
||||
const MESSAGES = {
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<div class="flex justify-between items-center">
|
||||
<h1 class="text-white text-2xl font-bold">Thông tin đơn hàng</h1>
|
||||
<span class="bg-white text-primary px-4 py-2 rounded-full text-sm font-medium">
|
||||
Mã đơn: #{{ $invoice->order_num }}
|
||||
Mã đơn: #{{ $invoice->order_num ?? 'N/A'}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -23,7 +23,7 @@
|
||||
<div class="space-y-2">
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600">Ngày đặt:</span>
|
||||
<span class="font-medium">{{ date('d/m/Y H:i', $invoice->date) }}</span>
|
||||
<span class="font-medium">{{ date('d/m/Y H:i', $invoice->date ?? 'N/A') }}</span>
|
||||
</div>
|
||||
<div class="flex justify-between">
|
||||
<span class="text-gray-600">Trạng thái:</span>
|
||||
|
||||
Reference in New Issue
Block a user