update develop

This commit is contained in:
pbao9
2026-01-19 09:31:10 +07:00
parent 4f2fdbaaa5
commit b97676d557
8 changed files with 409 additions and 370 deletions
@@ -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.')
+1 -3
View File
@@ -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;
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
));
}
}
+11 -1
View File
@@ -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>