update develop
This commit is contained in:
@@ -11,6 +11,7 @@ use App\Repositories\Invoice\InvoiceRepositoryInterface;
|
||||
use App\Http\Services\vCenter;
|
||||
use App\Jobs\VirtualMachine\Action\ChangeRemotePortJob;
|
||||
use App\Models\ModuleSnapshot;
|
||||
use App\Models\ModuleSnapshotLimit;
|
||||
use App\Models\ModuleVmware;
|
||||
use App\Models\Order;
|
||||
use App\Models\Os;
|
||||
@@ -59,7 +60,6 @@ class ServiceController extends Controller
|
||||
$this->dispatchVMInfoUpdateJob($invoice);
|
||||
}
|
||||
$title = "Chi tiết dịch vụ #" . $invoice->id;
|
||||
dd($vmInfo);
|
||||
return view('frontend.service.show', [
|
||||
'title' => $title,
|
||||
'invoice' => $invoice,
|
||||
@@ -354,194 +354,253 @@ class ServiceController extends Controller
|
||||
return view('frontend.service.console', $data);
|
||||
}
|
||||
|
||||
public function createSnapshots(SnapShotRequest $request, vCenter $serviceVCenter)
|
||||
public function createSnapshots(SnapShotRequest $request)
|
||||
{
|
||||
try {
|
||||
$user = customer();
|
||||
if (!$user) {
|
||||
return redirect()->back()->with('error', 'Chưa đăng nhập. Vui lòng đăng nhập để sử dụng tính năng này.');
|
||||
return redirect()->back()->with('error', 'Chưa đăng nhập.');
|
||||
}
|
||||
|
||||
$validator = $request->validated();
|
||||
|
||||
$productId = $validator['product_id'];
|
||||
$userId = $user->id;
|
||||
$data = $request->validated();
|
||||
$productId = $data['product_id'];
|
||||
|
||||
$product = ModuleVmware::with('ipAddress.host.datacenter.vCenter')
|
||||
->where('vmware_id', $productId)
|
||||
->where('customer_id', $userId)
|
||||
->where('customer_id', $user->id)
|
||||
->first();
|
||||
|
||||
if (!$product) {
|
||||
return redirect()->back()->with('error', "Không tìm thấy VPS #{$productId} hoặc bạn không có quyền truy cập");
|
||||
}
|
||||
|
||||
if (!$product->ipAddress) {
|
||||
return redirect()->back()->with('error', "VPS #{$productId} không có thông tin IP được cấu hình");
|
||||
}
|
||||
|
||||
if (!$product->ipAddress->host) {
|
||||
return redirect()->back()->with('error', "VPS #{$productId} không có thông tin Host được cấu hình");
|
||||
}
|
||||
|
||||
if (!$product->ipAddress->host->datacenter) {
|
||||
return redirect()->back()->with('error', "VPS #{$productId} không có thông tin Datacenter được cấu hình");
|
||||
}
|
||||
|
||||
if (!$product->ipAddress->host->datacenter->vCenter) {
|
||||
return redirect()->back()->with('error', "VPS #{$productId} không có thông tin vCenter được cấu hình");
|
||||
return redirect()->back()->with('error', "Không tìm thấy VPS hoặc bạn không có quyền.");
|
||||
}
|
||||
|
||||
if ($product->status != 1) {
|
||||
return redirect()->back()->with('error', 'Chỉ có thể tạo snapshot cho máy ảo đang hoạt động');
|
||||
return redirect()->back()->with('error', 'Chỉ tạo snapshot cho VM đang hoạt động.');
|
||||
}
|
||||
|
||||
// Kiểm tra giới hạn snapshot
|
||||
$snapshotLimit = \App\Models\ModuleSnapshotLimit::where('customer_id', $user->id)->first();
|
||||
if ($snapshotLimit) {
|
||||
$currentCount = ModuleSnapshot::where('customer_id', $user->id)
|
||||
$limit = ModuleSnapshotLimit::where('customer_id', $user->id)->first();
|
||||
if ($limit) {
|
||||
$count = ModuleSnapshot::where('customer_id', $user->id)
|
||||
->where('module_vmware_id', $product->id)
|
||||
->count();
|
||||
|
||||
if ($currentCount >= $snapshotLimit->limit) {
|
||||
return redirect()->back()->with('error', "Đã đạt giới hạn snapshot ({$snapshotLimit->limit}). Vui lòng xóa snapshot cũ.");
|
||||
if ($count >= $limit->limit) {
|
||||
return redirect()->back()->with(
|
||||
'error',
|
||||
"Đã đạt giới hạn snapshot ({$limit->limit})."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Dispatch job để tạo snapshot trong background
|
||||
\App\Jobs\VirtualMachine\Snapshot\CreateSnapshot::dispatch($product->id)
|
||||
->delay(now()->addSeconds(5))
|
||||
->onQueue('snapshots');
|
||||
|
||||
return redirect()->back()->with('success', 'Đã gửi yêu cầu tạo snapshot. Quá trình sẽ mất 3-5 phút, vui lòng chờ đợi.');
|
||||
} catch (\Exception $e) {
|
||||
Log::error('❌ Lỗi khi tạo snapshot', [
|
||||
'product_id' => $request->product_id ?? 'not_provided',
|
||||
'user_id' => $user->id ?? 'guest',
|
||||
'error' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine()
|
||||
$snapshot = ModuleSnapshot::create([
|
||||
'name' => 'snapshot-' . now()->format('Ymd-His'),
|
||||
'customer_id' => $user->id,
|
||||
'module_vmware_id' => $product->id,
|
||||
'status' => 'pending',
|
||||
'created_by' => $user->id,
|
||||
]);
|
||||
|
||||
return redirect()->back()->with('error', 'Lỗi hệ thống khi tạo snapshot: ' . $e->getMessage());
|
||||
\App\Jobs\VirtualMachine\Snapshot\CreateSnapshot::dispatch(
|
||||
$snapshot->id
|
||||
)
|
||||
->onQueue('snapshots')
|
||||
->delay(now()->addSeconds(3));
|
||||
|
||||
return redirect()->back()->with(
|
||||
'success',
|
||||
'Đã gửi yêu cầu tạo snapshot. Hệ thống sẽ xử lý trong nền.'
|
||||
);
|
||||
} catch (\Throwable $e) {
|
||||
Log::error('Lỗi khi tạo snapshot', [
|
||||
'product_id' => $request->product_id ?? null,
|
||||
'user_id' => $user->id ?? null,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return redirect()->back()->with(
|
||||
'error',
|
||||
'Lỗi hệ thống khi tạo snapshot.'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Lấy danh sách snapshot với bảo mật và tối ưu hóa
|
||||
*/
|
||||
public function getSnapshots(Request $request, vCenter $serviceVCenter)
|
||||
// public function getSnapshots(Request $request, vCenter $serviceVCenter)
|
||||
// {
|
||||
// try {
|
||||
// // Kiểm tra authentication
|
||||
// $user = customer();
|
||||
// if (!$user) {
|
||||
// return response()->json([
|
||||
// 'error' => true,
|
||||
// 'message' => __('Chưa đăng nhập. Vui lòng đăng nhập để sử dụng tính năng này.')
|
||||
// ], 401);
|
||||
// }
|
||||
|
||||
// // Validation với sanitization
|
||||
// $validator = Validator::make($request->all(), [
|
||||
// 'product_id' => 'required|string|max:255',
|
||||
// ]);
|
||||
|
||||
// if ($validator->fails()) {
|
||||
// return response()->json([
|
||||
// 'error' => true,
|
||||
// 'message' => __('Dữ liệu không hợp lệ'),
|
||||
// 'errors' => $validator->errors()
|
||||
// ], 422);
|
||||
// }
|
||||
|
||||
// $productId = $this->sanitizeInput($request->product_id);
|
||||
// $userId = $user->id;
|
||||
|
||||
// // Cache key cho snapshot data
|
||||
// $cacheKey = "snapshots_{$userId}_{$productId}";
|
||||
// $cacheDuration = 300; // 5 phút
|
||||
|
||||
// // Thử lấy từ cache trước
|
||||
// if (Cache::has($cacheKey)) {
|
||||
// $cachedData = Cache::get($cacheKey);
|
||||
// if ($cachedData && !$this->isCacheExpired($cachedData)) {
|
||||
// return response()->json([
|
||||
// 'error' => false,
|
||||
// 'message' => __('Lấy danh sách snapshot thành công'),
|
||||
// 'data' => $cachedData,
|
||||
// 'cached' => true
|
||||
// ]);
|
||||
// }
|
||||
// }
|
||||
|
||||
// // Tìm VPS với eager loading tối ưu
|
||||
// $product = $this->findVmwareProduct($productId, $userId);
|
||||
// if (!$product) {
|
||||
// return response()->json([
|
||||
// 'error' => true,
|
||||
// 'message' => __('Không tìm thấy VPS hoặc bạn không có quyền truy cập')
|
||||
// ], 404);
|
||||
// }
|
||||
|
||||
// // Kiểm tra cấu hình vCenter
|
||||
// $vcenter = $this->validateVcenterConfiguration($product);
|
||||
// if (!$vcenter) {
|
||||
// return response()->json([
|
||||
// 'error' => true,
|
||||
// 'message' => __('Cấu hình vCenter không hợp lệ')
|
||||
// ], 400);
|
||||
// }
|
||||
|
||||
// // Lấy snapshot từ vCenter
|
||||
// $snapshots = $this->fetchSnapshotsFromVCenter($serviceVCenter, $product, $vcenter);
|
||||
// if ($snapshots === false) {
|
||||
// return response()->json([
|
||||
// 'error' => true,
|
||||
// 'message' => __('Không thể kết nối đến vCenter')
|
||||
// ], 500);
|
||||
// }
|
||||
|
||||
// // Lấy thông tin bổ sung từ database
|
||||
// $dbSnapshots = $this->getDatabaseSnapshots($userId, $product->id);
|
||||
|
||||
// // Merge và sanitize data
|
||||
// $processedSnapshots = $this->processSnapshotsData($snapshots, $dbSnapshots);
|
||||
|
||||
// // Chuẩn bị response data
|
||||
// $responseData = [
|
||||
// 'snapshots' => $processedSnapshots,
|
||||
// 'total_count' => count($processedSnapshots),
|
||||
// 'vm_name' => $this->sanitizeOutput($product->name),
|
||||
// 'vm_id' => $product->vmware_id,
|
||||
// 'last_updated' => now()->format('Y-m-d H:i:s')
|
||||
// ];
|
||||
|
||||
// // Cache kết quả
|
||||
// Cache::put($cacheKey, $responseData, $cacheDuration);
|
||||
|
||||
// return response()->json([
|
||||
// 'error' => false,
|
||||
// 'message' => __('Lấy danh sách snapshot thành công'),
|
||||
// 'data' => $responseData,
|
||||
// 'cached' => false
|
||||
// ]);
|
||||
// } catch (\Exception $e) {
|
||||
// Log::error('Lỗi khi lấy danh sách snapshot', [
|
||||
// 'user_id' => $user->id ?? 'guest',
|
||||
// 'product_id' => $request->product_id ?? 'not_provided',
|
||||
// 'error' => $e->getMessage(),
|
||||
// 'file' => $e->getFile(),
|
||||
// 'line' => $e->getLine()
|
||||
// ]);
|
||||
|
||||
// return response()->json([
|
||||
// 'error' => true,
|
||||
// 'message' => __('Có lỗi xảy ra khi lấy danh sách snapshot')
|
||||
// ], 500);
|
||||
// }
|
||||
// }
|
||||
|
||||
public function getSnapshots(Request $request)
|
||||
{
|
||||
try {
|
||||
// Kiểm tra authentication
|
||||
$user = customer();
|
||||
if (!$user) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Chưa đăng nhập. Vui lòng đăng nhập để sử dụng tính năng này.')
|
||||
], 401);
|
||||
}
|
||||
|
||||
// Validation với sanitization
|
||||
$validator = Validator::make($request->all(), [
|
||||
'product_id' => 'required|string|max:255',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Dữ liệu không hợp lệ'),
|
||||
'errors' => $validator->errors()
|
||||
], 422);
|
||||
}
|
||||
|
||||
$productId = $this->sanitizeInput($request->product_id);
|
||||
$userId = $user->id;
|
||||
|
||||
// Cache key cho snapshot data
|
||||
$cacheKey = "snapshots_{$userId}_{$productId}";
|
||||
$cacheDuration = 300; // 5 phút
|
||||
|
||||
// Thử lấy từ cache trước
|
||||
if (Cache::has($cacheKey)) {
|
||||
$cachedData = Cache::get($cacheKey);
|
||||
if ($cachedData && !$this->isCacheExpired($cachedData)) {
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => __('Lấy danh sách snapshot thành công'),
|
||||
'data' => $cachedData,
|
||||
'cached' => true
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Tìm VPS với eager loading tối ưu
|
||||
$product = $this->findVmwareProduct($productId, $userId);
|
||||
if (!$product) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Không tìm thấy VPS hoặc bạn không có quyền truy cập')
|
||||
], 404);
|
||||
}
|
||||
|
||||
// Kiểm tra cấu hình vCenter
|
||||
$vcenter = $this->validateVcenterConfiguration($product);
|
||||
if (!$vcenter) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Cấu hình vCenter không hợp lệ')
|
||||
], 400);
|
||||
}
|
||||
|
||||
// Lấy snapshot từ vCenter
|
||||
$snapshots = $this->fetchSnapshotsFromVCenter($serviceVCenter, $product, $vcenter);
|
||||
if ($snapshots === false) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Không thể kết nối đến vCenter')
|
||||
], 500);
|
||||
}
|
||||
|
||||
// Lấy thông tin bổ sung từ database
|
||||
$dbSnapshots = $this->getDatabaseSnapshots($userId, $product->id);
|
||||
|
||||
// Merge và sanitize data
|
||||
$processedSnapshots = $this->processSnapshotsData($snapshots, $dbSnapshots);
|
||||
|
||||
// Chuẩn bị response data
|
||||
$responseData = [
|
||||
'snapshots' => $processedSnapshots,
|
||||
'total_count' => count($processedSnapshots),
|
||||
'vm_name' => $this->sanitizeOutput($product->name),
|
||||
'vm_id' => $product->vmware_id,
|
||||
'last_updated' => now()->format('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
// Cache kết quả
|
||||
Cache::put($cacheKey, $responseData, $cacheDuration);
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => __('Lấy danh sách snapshot thành công'),
|
||||
'data' => $responseData,
|
||||
'cached' => false
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Lỗi khi lấy danh sách snapshot', [
|
||||
'user_id' => $user->id ?? 'guest',
|
||||
'product_id' => $request->product_id ?? 'not_provided',
|
||||
'error' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine()
|
||||
]);
|
||||
|
||||
$user = customer();
|
||||
if (!$user) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Có lỗi xảy ra khi lấy danh sách snapshot')
|
||||
], 500);
|
||||
'message' => __('Chưa đăng nhập')
|
||||
], 401);
|
||||
}
|
||||
|
||||
$validator = Validator::make($request->all(), [
|
||||
'product_id' => 'required|string|max:255',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Dữ liệu không hợp lệ'),
|
||||
'errors' => $validator->errors()
|
||||
], 422);
|
||||
}
|
||||
|
||||
$product = $this->findVmwareProduct(
|
||||
$this->sanitizeInput($request->product_id),
|
||||
$user->id
|
||||
);
|
||||
|
||||
if (!$product) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Không tìm thấy VPS')
|
||||
], 404);
|
||||
}
|
||||
|
||||
$snapshots = ModuleSnapshot::query()
|
||||
->where('customer_id', $user->id)
|
||||
->where('module_vmware_id', $product->id)
|
||||
->orderByDesc('created_at')
|
||||
->get([
|
||||
'id',
|
||||
'name',
|
||||
'status',
|
||||
'snapshot_ref',
|
||||
'error_message',
|
||||
'created_at',
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => __('Lấy danh sách snapshot thành công'),
|
||||
'data' => [
|
||||
'vm_name' => e($product->name),
|
||||
'vm_id' => $product->vmware_id,
|
||||
'snapshots' => $snapshots,
|
||||
'total_count' => $snapshots->count(),
|
||||
'last_updated' => now()->format('Y-m-d H:i:s'),
|
||||
]
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reset VM password (Windows/Linux) via vCenter helpers
|
||||
*/
|
||||
@@ -956,7 +1015,7 @@ class ServiceController extends Controller
|
||||
], 401);
|
||||
}
|
||||
|
||||
$validator = \Validator::make($request->all(), [
|
||||
$validator = Validator::make($request->all(), [
|
||||
'product_id' => 'required|string|max:255',
|
||||
]);
|
||||
if ($validator->fails()) {
|
||||
@@ -983,7 +1042,7 @@ class ServiceController extends Controller
|
||||
$result = $vCenterService->StartVirtualMachine($product, $product->vmware_id);
|
||||
if (is_array($result) && isset($result['error']) && $result['error'] === false) {
|
||||
// Clear VM info cache after starting VM
|
||||
\Cache::forget('vm_info_' . $product->vmware_id);
|
||||
Cache::forget('vm_info_' . $product->vmware_id);
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => __('Yêu cầu khởi động máy ảo đã được gửi thành công.')
|
||||
@@ -996,7 +1055,7 @@ class ServiceController extends Controller
|
||||
], 500);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Lỗi khi khởi động máy ảo', [
|
||||
Log::error('Lỗi khi khởi động máy ảo', [
|
||||
'user_id' => $user->id ?? 'guest',
|
||||
'product_id' => $request->product_id ?? 'not_provided',
|
||||
'error' => $e->getMessage(),
|
||||
@@ -1050,7 +1109,6 @@ class ServiceController extends Controller
|
||||
|
||||
$result = $vCenterService->ShutdownGuestVirtualMachine($product, $product->vmware_id);
|
||||
if (is_array($result) && isset($result['error']) && $result['error'] === false) {
|
||||
// Clear VM info cache after stopping VM
|
||||
Cache::forget('the_gioi_so_database_the_gioi_so_cache:vm_info_' . $product->vmware_id);
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
@@ -1064,7 +1122,7 @@ class ServiceController extends Controller
|
||||
], 500);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Lỗi khi tắt máy ảo', [
|
||||
Log::error('Lỗi khi tắt máy ảo', [
|
||||
'user_id' => $user->id ?? 'guest',
|
||||
'product_id' => $request->product_id ?? 'not_provided',
|
||||
'error' => $e->getMessage(),
|
||||
@@ -1092,7 +1150,7 @@ class ServiceController extends Controller
|
||||
], 401);
|
||||
}
|
||||
|
||||
$validator = \Validator::make($request->all(), [
|
||||
$validator = Validator::make($request->all(), [
|
||||
'product_id' => 'required|string|max:255',
|
||||
]);
|
||||
if ($validator->fails()) {
|
||||
@@ -1130,7 +1188,7 @@ class ServiceController extends Controller
|
||||
], 500);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Lỗi khi reboot VM', [
|
||||
Log::error('Lỗi khi reboot VM', [
|
||||
'user_id' => $user->id ?? 'guest',
|
||||
'product_id' => $request->product_id ?? 'not_provided',
|
||||
'error' => $e->getMessage(),
|
||||
@@ -1148,12 +1206,6 @@ class ServiceController extends Controller
|
||||
public function reinstallVM(Request $request, vCenter $vCenterService)
|
||||
{
|
||||
try {
|
||||
// Debug: Log request data
|
||||
Log::info('reinstallVM called', [
|
||||
'request_data' => $request->all(),
|
||||
'user_id' => customer()?->id ?? 'not_logged_in'
|
||||
]);
|
||||
|
||||
$user = customer();
|
||||
if (!$user) {
|
||||
Log::warning('User not logged in for reinstallVM');
|
||||
@@ -1178,18 +1230,7 @@ class ServiceController extends Controller
|
||||
], 422);
|
||||
}
|
||||
|
||||
// Debug: Log invoice lookup
|
||||
Log::info('Looking for invoice', ['invoice_id' => $request->invoice_id]);
|
||||
|
||||
$invoice = Invoice::find($request->invoice_id);
|
||||
|
||||
// Debug: Log invoice result
|
||||
Log::info('Invoice lookup result', [
|
||||
'invoice_id' => $request->invoice_id,
|
||||
'found' => $invoice ? 'yes' : 'no',
|
||||
'invoice_data' => $invoice ? $invoice->toArray() : null
|
||||
]);
|
||||
|
||||
if (!$invoice) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
@@ -1199,27 +1240,11 @@ class ServiceController extends Controller
|
||||
|
||||
// Debug: Log product lookup
|
||||
$invoiceItem = $invoice->invoiceItem->first();
|
||||
Log::info('Invoice item lookup', [
|
||||
'invoice_item_found' => $invoiceItem ? 'yes' : 'no',
|
||||
'invoice_item_data' => $invoiceItem ? $invoiceItem->toArray() : null
|
||||
]);
|
||||
|
||||
$productId = $invoiceItem?->getProductAttribute()?->id;
|
||||
Log::info('Product lookup', [
|
||||
'product_id' => $productId,
|
||||
'product_data' => $invoiceItem?->getProductAttribute()?->toArray() ?? null
|
||||
]);
|
||||
|
||||
$vmware = $productId;
|
||||
$osId = $request->os_id;
|
||||
$userId = $user->id;
|
||||
|
||||
Log::info('VM lookup parameters', [
|
||||
'vmware_id' => $vmware?->vmware_id ?? 'null',
|
||||
'os_id' => $osId,
|
||||
'user_id' => $userId
|
||||
]);
|
||||
|
||||
// Tìm product và kiểm tra quyền sở hữu
|
||||
$vm = ModuleVmware::where('vmware_id', $vmware->vmware_id)
|
||||
->where('customer_id', $userId)
|
||||
@@ -1254,42 +1279,14 @@ class ServiceController extends Controller
|
||||
], 403);
|
||||
}
|
||||
|
||||
// Debug: Log before vCenter call
|
||||
Log::info('About to call vCenter DeleteVirtualMachine', [
|
||||
'vmware_id' => $vm->vmware_id,
|
||||
'os_id' => $osId
|
||||
]);
|
||||
|
||||
// Gọi vCenter service để xóa VM
|
||||
$result = $vCenterService->DeleteVirtualMachine($vm->vmware_id);
|
||||
|
||||
// Debug: Log vCenter result
|
||||
Log::info('vCenter DeleteVirtualMachine result', [
|
||||
'result' => $result,
|
||||
'is_array' => is_array($result),
|
||||
'has_error_key' => is_array($result) && isset($result['error']),
|
||||
'error_value' => is_array($result) ? ($result['error'] ?? 'not_set') : 'not_array'
|
||||
]);
|
||||
|
||||
if (is_array($result) && isset($result['error']) && $result['error'] === false) {
|
||||
// Cập nhật OS trong database
|
||||
$vm->update([
|
||||
'os_id' => $osId
|
||||
]);
|
||||
|
||||
// Clear VM info cache
|
||||
Cache::forget('vm_info_' . $vm->vmware_id);
|
||||
|
||||
// Log hoạt động
|
||||
Log::info('VM reinstall initiated', [
|
||||
'user_id' => $userId,
|
||||
'product_id' => $vm->id,
|
||||
'old_os_id' => $vm->os_id,
|
||||
'new_os_id' => $osId,
|
||||
'os_name' => $os->version
|
||||
]);
|
||||
|
||||
Log::info('Returning success response');
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => __('Yêu cầu cài lại máy ảo đã được gửi thành công. Quá trình sẽ mất 5-10 phút.')
|
||||
|
||||
@@ -898,6 +898,7 @@ PS;
|
||||
])
|
||||
->patch("https://{$this->apiUri}/rest/vcenter/vm/{$vmId}/hardware/cpu", $payload);
|
||||
|
||||
|
||||
if ($response->successful()) {
|
||||
Log::info("✅ API call cập nhật CPU thành công cho VM {$vmId}");
|
||||
return true;
|
||||
@@ -1627,9 +1628,6 @@ PS;
|
||||
return "Login_Again";
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear snapshot cache for a specific VM
|
||||
*/
|
||||
private function clearSnapshotCache($product): void
|
||||
{
|
||||
$vmware_name = (string) $product->name;
|
||||
|
||||
Vendored
BIN
Binary file not shown.
@@ -19,153 +19,87 @@ class CreateSnapshot implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
protected $productId;
|
||||
public function __construct(
|
||||
public int $snapshotId
|
||||
) {}
|
||||
|
||||
protected $telegram;
|
||||
|
||||
/**
|
||||
* Create a new job instance.
|
||||
*
|
||||
* @param int $productId
|
||||
* @return void
|
||||
*/
|
||||
public function __construct($productId = 0)
|
||||
{
|
||||
$this->productId = $productId;
|
||||
$this->telegram = new Telegram();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the job.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle(ServiceVCenter $serviceVCenter)
|
||||
{
|
||||
$snapshot = ModuleSnapshot::with('vm.ipAddress.host.datacenter.vCenter')
|
||||
->find($this->snapshotId);
|
||||
|
||||
if (!$snapshot) {
|
||||
return;
|
||||
}
|
||||
|
||||
$snapshot->update(['status' => 'processing']);
|
||||
|
||||
$product = $snapshot->vm;
|
||||
$vcenter = $product->ipAddress->host->datacenter->vCenter;
|
||||
|
||||
try {
|
||||
// Load product with necessary relationships
|
||||
$product = ModuleVmware::with('ipAddress.host.datacenter.vCenter')->find($this->productId);
|
||||
|
||||
if (!$product) {
|
||||
throw new \Exception("Không tìm thấy thông tin VPS với ID: {$this->productId}");
|
||||
}
|
||||
|
||||
// Kiểm tra cấu hình vCenter
|
||||
if (!$product->ipAddress?->host?->datacenter?->vCenter) {
|
||||
throw new \Exception("VPS không có thông tin vCenter được cấu hình");
|
||||
}
|
||||
|
||||
$vcenter = $product->ipAddress->host->datacenter->vCenter;
|
||||
|
||||
// Set vCenter connection info
|
||||
// Config vCenter
|
||||
$serviceVCenter->token = $vcenter->token;
|
||||
$serviceVCenter->apiUri = $vcenter->host;
|
||||
|
||||
Log::info('🔄 Bắt đầu tạo snapshot', [
|
||||
'product_id' => $product->id,
|
||||
'vmware_id' => $product->vmware_id,
|
||||
'vcenter_host' => $vcenter->host
|
||||
]);
|
||||
// Call vCenter
|
||||
$result = $serviceVCenter->CreateSnapShot($product);
|
||||
|
||||
// Attempt to create snapshot
|
||||
$snapshot = $serviceVCenter->CreateSnapShot($product);
|
||||
|
||||
// Handle re-login if needed
|
||||
if ($snapshot === 'Login_Again') {
|
||||
Log::info('🔑 Cần đăng nhập lại vCenter');
|
||||
if ($result === 'Login_Again') {
|
||||
$token = $serviceVCenter->loginServer($vcenter);
|
||||
if ($token) {
|
||||
$vcenter->update(['token' => $token]);
|
||||
$serviceVCenter->token = $token;
|
||||
$snapshot = $serviceVCenter->CreateSnapShot($product);
|
||||
} else {
|
||||
throw new \Exception("Không thể đăng nhập lại vào vCenter");
|
||||
if (!$token) {
|
||||
throw new \Exception('Không thể login lại vCenter');
|
||||
}
|
||||
|
||||
$vcenter->update(['token' => $token]);
|
||||
$serviceVCenter->token = $token;
|
||||
$result = $serviceVCenter->CreateSnapShot($product);
|
||||
}
|
||||
|
||||
// Check for errors
|
||||
if (is_array($snapshot) && isset($snapshot['error']) && $snapshot['error']) {
|
||||
$errorMessage = 'Lỗi tạo snapshot';
|
||||
if (isset($snapshot['message'])) {
|
||||
$errorMessage .= ': ' . (is_array($snapshot['message']) ? json_encode($snapshot['message']) : $snapshot['message']);
|
||||
}
|
||||
throw new \Exception($errorMessage);
|
||||
if (is_array($result) && ($result['error'] ?? false)) {
|
||||
throw new \Exception($result['message'] ?? 'Lỗi tạo snapshot');
|
||||
}
|
||||
|
||||
if (is_string($snapshot) && $snapshot !== 'Login_Again') {
|
||||
throw new \Exception("Lỗi tạo snapshot: {$snapshot}");
|
||||
}
|
||||
|
||||
// Get snapshot name from response
|
||||
$snapshotName = null;
|
||||
if (is_array($snapshot) && isset($snapshot['data'])) {
|
||||
$snapshotName = $snapshot['data'];
|
||||
} else {
|
||||
$snapshotName = 'snapshot_' . now()->format('Y_m_d_H_i_s');
|
||||
}
|
||||
|
||||
// Update snapshot limit counter
|
||||
ModuleSnapshotLimit::where('customer_id', $product->customer_id)->increment('used', 1);
|
||||
|
||||
// Save snapshot to database
|
||||
ModuleSnapshot::create([
|
||||
'name' => $snapshotName,
|
||||
'customer_id' => $product->customer_id,
|
||||
'module_vmware_id' => $product->id
|
||||
// Update DB SUCCESS
|
||||
$snapshot->update([
|
||||
'name' => $result['name'] ?? $snapshot->name,
|
||||
'status' => 'success',
|
||||
'snapshot_ref' => $result['snapshot_id'] ?? null,
|
||||
'vcenter_task_id' => $result['task_id'] ?? null,
|
||||
]);
|
||||
|
||||
Log::info('✅ Tạo snapshot thành công', [
|
||||
'product_id' => $product->id,
|
||||
'snapshot_name' => $snapshotName
|
||||
// Increment quota CHỈ khi success
|
||||
ModuleSnapshotLimit::where('customer_id', $snapshot->customer_id)
|
||||
->increment('used');
|
||||
|
||||
event(new NotificationStatus(
|
||||
"Snapshot {$snapshot->name} tạo thành công",
|
||||
true
|
||||
));
|
||||
} catch (\Throwable $e) {
|
||||
$snapshot->update([
|
||||
'status' => 'failed',
|
||||
'error_message' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
// Send success notification
|
||||
Event(new NotificationStatus("Khởi tạo snapshot thành công cho VPS {$product->name}", true));
|
||||
|
||||
// Send Telegram notification
|
||||
$this->telegram->SendMessage(
|
||||
"🎉 <b>SNAPSHOT CREATED SUCCESSFULLY</b>\n\n" .
|
||||
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" .
|
||||
"🖥️ <b>Server:</b> `{$product->name}`\n" .
|
||||
"📸 <b>Snapshot:</b> `{$snapshotName}`\n" .
|
||||
"👤 <b>Customer:</b> `#{$product->customer_id}`\n" .
|
||||
"🆔 <b>VM ID:</b> `{$product->vmware_id}`\n" .
|
||||
"⏰ <b>Created:</b> `" . now()->format('d/m/Y H:i:s') . "`\n" .
|
||||
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" .
|
||||
"💡 <b>Status:</b> ✅ Completed\n" .
|
||||
"🔧 <b>Action:</b> Snapshot Creation\n" .
|
||||
"📊 <b>Queue:</b> snapshots"
|
||||
);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('❌ Lỗi tạo snapshot', [
|
||||
'product_id' => $this->productId,
|
||||
'error' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine()
|
||||
]);
|
||||
|
||||
throw $e; // Re-throw để kích hoạt failed method
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function failed(\Throwable $exception)
|
||||
{
|
||||
Log::error('❌ Job CreateSnapshot failed', [
|
||||
'product_id' => $this->productId,
|
||||
'error' => $exception->getMessage(),
|
||||
'trace' => $exception->getTraceAsString()
|
||||
]);
|
||||
$snapshot = ModuleSnapshot::find($this->snapshotId);
|
||||
|
||||
// Send failure notification
|
||||
Event(new NotificationStatus("Lỗi tạo snapshot: {$exception->getMessage()}", false));
|
||||
if ($snapshot) {
|
||||
$snapshot->update([
|
||||
'status' => 'failed',
|
||||
'error_message' => $exception->getMessage(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Send Telegram error notification
|
||||
$this->telegram->SendErrorMessage([
|
||||
'job_name' => 'CreateSnapshot',
|
||||
'product_id' => $this->productId,
|
||||
'error' => $exception->getMessage(),
|
||||
'trace' => $exception->getTraceAsString()
|
||||
]);
|
||||
event(new NotificationStatus(
|
||||
"Snapshot thất bại: {$exception->getMessage()}",
|
||||
false
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,16 @@ class ModuleSnapshot extends Model
|
||||
protected $fillable = [
|
||||
'name',
|
||||
'customer_id',
|
||||
'module_vmware_id'
|
||||
'module_vmware_id',
|
||||
'status',
|
||||
'description',
|
||||
'snapshot_ref',
|
||||
'vcenter_task_id',
|
||||
'error_message',
|
||||
];
|
||||
|
||||
public function vm()
|
||||
{
|
||||
return $this->belongsTo(ModuleVmware::class, 'module_vmware_id');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('module_snapshot', function (Blueprint $table) {
|
||||
$table->text('description')->nullable()->after('name');
|
||||
|
||||
// Async snapshot status
|
||||
$table->enum('status', [
|
||||
'pending',
|
||||
'processing',
|
||||
'success',
|
||||
'failed'
|
||||
])->default('pending')->index()->after('module_vmware_id');
|
||||
|
||||
// vCenter mapping
|
||||
$table->string('snapshot_ref')->nullable()->comment('Snapshot ID / moRef from vCenter');
|
||||
$table->string('vcenter_task_id')->nullable()->comment('Task ID from vCenter');
|
||||
|
||||
// Error handling
|
||||
$table->text('error_message')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('module_snapshot', function (Blueprint $table) {
|
||||
$table->dropColumn([
|
||||
'description',
|
||||
'status',
|
||||
'snapshot_ref',
|
||||
'vcenter_task_id',
|
||||
'error_message',
|
||||
]);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('module_snapshot', function (Blueprint $table) {
|
||||
//
|
||||
$table->unsignedBigInteger('size_bytes')
|
||||
->nullable()
|
||||
->comment('Snapshot size in bytes');
|
||||
|
||||
$table->decimal('size_gb', 10, 2)
|
||||
->nullable()
|
||||
->comment('Snapshot size in GB');
|
||||
|
||||
$table->timestamp('size_last_synced_at')
|
||||
->nullable()
|
||||
->comment('Last time snapshot size was synced');
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('module_snapshot', function (Blueprint $table) {
|
||||
//
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -7,14 +7,7 @@
|
||||
<flux:heading size="lg">{{ __('Snapshot của máy ảo') }}</flux:heading>
|
||||
<flux:text class="mt-1">{{ __('Xem, tạo mới, khôi phục hoặc xóa snapshot.') }}</flux:text>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<flux:button size="sm" variant="ghost" icon="arrow-path" x-on:click="refreshSnapshots()">{{ __('Tải lại') }}</flux:button>
|
||||
<form method="POST" action="{{ route('service.create.snapshots') }}" id="snapshotForm">
|
||||
@csrf
|
||||
<input type="hidden" name="product_id" value="{{ $invoice->vm_id }}">
|
||||
<flux:button size="sm" variant="primary" color="orange" type="submit" onclick="handleSnapshotSubmit(event)">{{ __('Tạo snapshot') }}</flux:button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div x-show="loading" class="text-center py-6">
|
||||
@@ -26,24 +19,28 @@
|
||||
<table class="min-w-full divide-y divide-gray-200 mt-2">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">{{ __('Tên') }}</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">{{ __('Mô tả') }}</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">{{ __('Ngày tạo') }}</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">{{ __('Trạng thái') }}</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">{{ __('Dung lượng') }}</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">{{ __('Thao tác') }}</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">
|
||||
{{ __('Tên') }}</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">
|
||||
{{ __('Ngày tạo') }}</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">
|
||||
{{ __('Trạng thái') }}</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">
|
||||
{{ __('Dung lượng') }}</th>
|
||||
<th class="px-4 py-2 text-left text-xs font-medium text-gray-500 uppercase">
|
||||
{{ __('Thao tác') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template x-for="snap in snapshots" :key="snap.name">
|
||||
<tr>
|
||||
<td class="px-4 py-2 text-sm text-gray-700" x-text="snap.name"></td>
|
||||
<td class="px-4 py-2 text-sm text-gray-700" x-text="snap.description"></td>
|
||||
<td class="px-4 py-2 text-sm text-gray-700" x-text="snap.created"></td>
|
||||
<td class="px-4 py-2 text-sm text-gray-700">
|
||||
<span x-text="snap.is_current ? '{{ __('Hiện tại') }}' : ''"></span>
|
||||
</td>
|
||||
<td class="px-4 py-2 text-sm text-gray-700" x-text="formatSize(snap.size_gb, snap.size_mb)"></td>
|
||||
<td class="px-4 py-2 text-sm text-gray-700"
|
||||
x-text="formatSize(snap.size_gb, snap.size_mb)"></td>
|
||||
<td class="px-4 py-2 text-sm text-gray-700">
|
||||
<div class="flex space-x-2">
|
||||
<button @click="revertSnapshot(snap.name)"
|
||||
@@ -51,8 +48,10 @@
|
||||
:disabled="snap.is_current"
|
||||
:class="snap.is_current ? 'opacity-50 cursor-not-allowed' : ''"
|
||||
title="{{ __('Khôi phục về snapshot này') }}">
|
||||
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"></path>
|
||||
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M3 10h10a8 8 0 018 8v2M3 10l6 6m-6-6l6-6"></path>
|
||||
</svg>
|
||||
{{ __('Khôi phục') }}
|
||||
</button>
|
||||
@@ -61,8 +60,11 @@
|
||||
:disabled="snap.is_current"
|
||||
:class="snap.is_current ? 'opacity-50 cursor-not-allowed' : ''"
|
||||
title="{{ __('Xóa snapshot này') }}">
|
||||
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
||||
<svg class="w-3 h-3 mr-1" fill="none" stroke="currentColor"
|
||||
viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16">
|
||||
</path>
|
||||
</svg>
|
||||
{{ __('Xóa') }}
|
||||
</button>
|
||||
@@ -73,12 +75,22 @@
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
</template>
|
||||
<div x-cloak x-show="!loading && snapshots.length === 0" class="text-center py-6 text-gray-400">
|
||||
{{ __('Không có snapshot nào.') }}
|
||||
</div>
|
||||
<div class="border-t border-gray-200 pt-4"></div>
|
||||
<div class="flex justify-end gap-2">
|
||||
<flux:button size="sm" variant="ghost" icon="arrow-path" x-on:click="refreshSnapshots()">
|
||||
{{ __('Tải lại') }}</flux:button>
|
||||
<form method="POST" action="{{ route('service.create.snapshots') }}" id="snapshotForm">
|
||||
@csrf
|
||||
<input type="hidden" name="product_id" value="{{ $invoice->vm_id }}">
|
||||
<flux:button size="sm" variant="primary" color="orange" type="submit"
|
||||
onclick="handleSnapshotSubmit(event)">{{ __('Tạo snapshot') }}</flux:button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</flux:modal>
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user