From d9da3b5bb3aec1f1de8366e8ebd9a3e5d1f42b29 Mon Sep 17 00:00:00 2001 From: baodev0 Date: Fri, 17 Jul 2026 10:17:52 +0700 Subject: [PATCH] Add SnapshotActionMail class and email template for snapshot actions; implement email notifications for create, remove, and revert actions --- .../Api/Service/SnapshotController.php | 17 +- .../Snapshot/CreateSnapshot.php | 56 +++-- .../Snapshot/RemoveSnapshot.php | 13 ++ .../Snapshot/RevertSnapshot.php | 13 ++ app/Mail/Customer/SnapshotActionMail.php | 73 +++++++ .../emails/customer/snapshot-action.blade.php | 202 ++++++++++++++++++ 6 files changed, 353 insertions(+), 21 deletions(-) create mode 100644 app/Mail/Customer/SnapshotActionMail.php create mode 100644 resources/views/emails/customer/snapshot-action.blade.php diff --git a/app/Http/Controllers/Api/Service/SnapshotController.php b/app/Http/Controllers/Api/Service/SnapshotController.php index 7b26e08..489a71f 100644 --- a/app/Http/Controllers/Api/Service/SnapshotController.php +++ b/app/Http/Controllers/Api/Service/SnapshotController.php @@ -9,7 +9,6 @@ use App\Jobs\VirtualMachine\Snapshot\CreateSnapshot; use App\Jobs\VirtualMachine\Snapshot\RevertSnapshot; use App\Jobs\VirtualMachine\Snapshot\RemoveSnapshot; use App\Models\ModuleSnapshot; -use App\Models\ModuleVmware; use App\Services\ActivityLogger; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -40,12 +39,12 @@ class SnapshotController extends Controller $snapshotName = sprintf('%s-%s-%d', $vm->name, $today, $existingCount + 1); $snapshot = ModuleSnapshot::create([ - 'name' => $snapshotName, - 'description' => $payload['description'] ?? null, - 'customer_id' => $customer->id, + 'name' => $snapshotName, + 'description' => $payload['description'] ?? null, + 'customer_id' => $customer->id, 'module_vmware_id' => $vm->id, - 'status' => 'pending', - 'snapshot_ref' => null, + 'status' => 'pending', + 'snapshot_ref' => null, ]); CreateSnapshot::dispatch($snapshot->id); @@ -62,10 +61,10 @@ class SnapshotController extends Controller 'error' => false, 'message' => 'Đã gửi yêu cầu tạo snapshot vào hàng đợi', 'data' => [ - 'id' => $snapshot->id, - 'name' => $snapshot->name, + 'id' => $snapshot->id, + 'name' => $snapshot->name, 'description' => $snapshot->description, - 'status' => 'pending', + 'status' => 'pending', ], ]); } diff --git a/app/Jobs/VirtualMachine/Snapshot/CreateSnapshot.php b/app/Jobs/VirtualMachine/Snapshot/CreateSnapshot.php index 3e41290..877e0f1 100644 --- a/app/Jobs/VirtualMachine/Snapshot/CreateSnapshot.php +++ b/app/Jobs/VirtualMachine/Snapshot/CreateSnapshot.php @@ -5,23 +5,28 @@ namespace App\Jobs\VirtualMachine\Snapshot; use App\Events\NotificationStatus; use App\Http\Services\Telegram; use App\Http\Services\vCenter as ServiceVCenter; +use App\Mail\Customer\SnapshotActionMail; use App\Models\ModuleSnapshot; use App\Models\ModuleSnapshotLimit; -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\Mail; class CreateSnapshot implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; + protected Telegram $telegram; + public function __construct( public int $snapshotId - ) {} + ) { + $this->telegram = new Telegram(); + } public function handle(ServiceVCenter $serviceVCenter) { @@ -37,23 +42,20 @@ class CreateSnapshot implements ShouldQueue $product = $snapshot->vm; - // Resolve vCenter using helper method $vcenter = $serviceVCenter->resolveVCenterFromProduct($product); if (!$vcenter) { $snapshot->update([ - 'status' => 'failed', + 'status' => 'failed', 'error_message' => 'Không tìm thấy thông tin vCenter', ]); throw new \Exception('Không tìm thấy thông tin vCenter cho VM'); } try { - // Config vCenter - $serviceVCenter->token = $vcenter->token; + $serviceVCenter->token = $vcenter->token; $serviceVCenter->apiUri = $vcenter->host; - // Call vCenter with snapshot name from DB $result = $serviceVCenter->CreateSnapShot($product, $snapshot->name); if ($result === 'Login_Again') { @@ -71,13 +73,11 @@ class CreateSnapshot implements ShouldQueue throw new \Exception($result['message'] ?? 'Lỗi tạo snapshot'); } - // Update DB SUCCESS $snapshot->update([ - 'status' => 'success', + 'status' => 'success', 'snapshot_ref' => $result['data'] ?? $snapshot->name, ]); - // Increment quota CHỈ khi success ModuleSnapshotLimit::where('customer_id', $snapshot->customer_id) ->increment('used'); @@ -85,9 +85,34 @@ class CreateSnapshot implements ShouldQueue "Snapshot {$snapshot->name} tạo thành công", true )); + + $product->loadMissing('customer'); + if ($product->customer?->email) { + Mail::to($product->customer->email)->queue(new SnapshotActionMail([ + 'action' => 'create', + 'fullname' => $product->customer->fullname, + 'vm_name' => $product->name, + 'snapshot_name' => $snapshot->name, + 'action_at' => now()->format('d/m/Y H:i:s'), + ])); + } + + $this->telegram->SendMessage( + "📸 SNAPSHOT CREATED SUCCESSFULLY\n\n" . + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" . + "🖥️ Server: `{$product->name}`\n" . + "📸 Snapshot: `{$snapshot->name}`\n" . + "👤 Customer: `#{$snapshot->customer_id}`\n" . + "🆔 VM ID: `{$product->vmware_id}`\n" . + "⏰ Created: `" . now()->format('d/m/Y H:i:s') . "`\n" . + "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n" . + "💡 Status: ✅ Completed\n" . + "🔧 Action: Snapshot Creation\n" . + "📊 Queue: snapshots" + ); } catch (\Throwable $e) { $snapshot->update([ - 'status' => 'failed', + 'status' => 'failed', 'error_message' => $e->getMessage(), ]); @@ -101,7 +126,7 @@ class CreateSnapshot implements ShouldQueue if ($snapshot) { $snapshot->update([ - 'status' => 'failed', + 'status' => 'failed', 'error_message' => $exception->getMessage(), ]); } @@ -110,5 +135,12 @@ class CreateSnapshot implements ShouldQueue "Snapshot thất bại: {$exception->getMessage()}", false )); + + $this->telegram->SendErrorMessage([ + 'job_name' => 'CreateSnapshot', + 'snapshot_id' => $this->snapshotId, + 'error' => $exception->getMessage(), + 'trace' => $exception->getTraceAsString(), + ]); } } diff --git a/app/Jobs/VirtualMachine/Snapshot/RemoveSnapshot.php b/app/Jobs/VirtualMachine/Snapshot/RemoveSnapshot.php index fae1fe4..4a83d87 100644 --- a/app/Jobs/VirtualMachine/Snapshot/RemoveSnapshot.php +++ b/app/Jobs/VirtualMachine/Snapshot/RemoveSnapshot.php @@ -5,6 +5,7 @@ namespace App\Jobs\VirtualMachine\Snapshot; use App\Events\NotificationStatus; use App\Http\Services\Telegram; use App\Http\Services\vCenter as ServiceVCenter; +use App\Mail\Customer\SnapshotActionMail; use App\Models\ModuleSnapshot; use App\Models\ModuleSnapshotLimit; use App\Models\ModuleVmware; @@ -14,6 +15,7 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Mail; class RemoveSnapshot implements ShouldQueue { @@ -117,6 +119,17 @@ class RemoveSnapshot implements ShouldQueue // Send success notification Event(new NotificationStatus("Xóa snapshot thành công cho VPS {$product->name}", true)); + $product->loadMissing('customer'); + if ($product->customer?->email) { + Mail::to($product->customer->email)->queue(new SnapshotActionMail([ + 'action' => 'remove', + 'fullname' => $product->customer->fullname, + 'vm_name' => $product->name, + 'snapshot_name' => $snapshotName, + 'action_at' => now()->format('d/m/Y H:i:s'), + ])); + } + // Send Telegram notification $this->telegram->SendMessage( "🗑️ SNAPSHOT REMOVED SUCCESSFULLY\n\n" . diff --git a/app/Jobs/VirtualMachine/Snapshot/RevertSnapshot.php b/app/Jobs/VirtualMachine/Snapshot/RevertSnapshot.php index dff4e67..59baea3 100644 --- a/app/Jobs/VirtualMachine/Snapshot/RevertSnapshot.php +++ b/app/Jobs/VirtualMachine/Snapshot/RevertSnapshot.php @@ -5,6 +5,7 @@ namespace App\Jobs\VirtualMachine\Snapshot; use App\Events\NotificationStatus; use App\Http\Services\Telegram; use App\Http\Services\vCenter as ServiceVCenter; +use App\Mail\Customer\SnapshotActionMail; use App\Models\ModuleSnapshot; use App\Models\ModuleVmware; use Illuminate\Bus\Queueable; @@ -13,6 +14,7 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Mail; class RevertSnapshot implements ShouldQueue { @@ -107,6 +109,17 @@ class RevertSnapshot implements ShouldQueue // Send success notification Event(new NotificationStatus("Khôi phục snapshot thành công cho VPS {$product->name}", true)); + $product->loadMissing('customer'); + if ($product->customer?->email) { + Mail::to($product->customer->email)->queue(new SnapshotActionMail([ + 'action' => 'revert', + 'fullname' => $product->customer->fullname, + 'vm_name' => $product->name, + 'snapshot_name' => $snapshot->name, + 'action_at' => now()->format('d/m/Y H:i:s'), + ])); + } + // Send Telegram notification $this->telegram->SendMessage( "🔄 SNAPSHOT REVERTED SUCCESSFULLY\n\n" . diff --git a/app/Mail/Customer/SnapshotActionMail.php b/app/Mail/Customer/SnapshotActionMail.php new file mode 100644 index 0000000..d4100cc --- /dev/null +++ b/app/Mail/Customer/SnapshotActionMail.php @@ -0,0 +1,73 @@ + 'Tạo Snapshot thành công', + 'remove' => 'Xóa Snapshot thành công', + 'revert' => 'Khôi phục Snapshot thành công', + ]; + + $appName = $this->payload()['company_name'] ?? config('app.name', 'The Gioi So'); + $subject = $subjects[$this->data['action'] ?? 'create'] ?? 'Thông báo Snapshot'; + + return new Envelope(subject: "{$subject} — {$appName}"); + } + + public function content(): Content + { + $data = $this->payload(); + + return new Content( + view: 'emails.customer.snapshot-action', + with: [ + 'appName' => $data['company_name'] ?? config('app.name', 'The Gioi So'), + 'fullName' => $data['fullname'] ?? 'Quý khách', + 'vmName' => $data['vm_name'] ?? '', + 'snapshotName' => $data['snapshot_name'] ?? '', + 'action' => $data['action'] ?? 'create', + 'actionAt' => $data['action_at'] ?? now()->format('d/m/Y H:i:s'), + 'loginUrl' => $data['login_url'] ?? rtrim(config('app.frontend_url', ''), '/') . '/dang-nhap', + 'supportEmail' => $data['support_email'] ?? config('mail.support_email', 'support@thegioiso.com'), + 'supportPhone' => $data['support_phone'] ?? config('mail.support_phone', '1900.xxxx'), + ], + ); + } + + public function attachments(): array + { + return []; + } + + private function payload(): array + { + try { + $setting = Setting::where('key', 'setting')->first(); + $settingValue = $setting ? json_decode((string) $setting->value, true) : []; + } catch (\Throwable) { + $settingValue = []; + } + + return array_merge([ + 'company_name' => $settingValue['company'] ?? config('app.name', 'The Gioi So'), + 'support_email' => $settingValue['email'] ?? config('mail.support_email', 'support@thegioiso.com'), + 'support_phone' => $settingValue['hotline'] ?? ($settingValue['phone'] ?? config('mail.support_phone', '1900.xxxx')), + 'login_url' => rtrim(config('app.frontend_url', ''), '/') . '/dang-nhap', + ], $this->data); + } +} diff --git a/resources/views/emails/customer/snapshot-action.blade.php b/resources/views/emails/customer/snapshot-action.blade.php new file mode 100644 index 0000000..f74157c --- /dev/null +++ b/resources/views/emails/customer/snapshot-action.blade.php @@ -0,0 +1,202 @@ +@php + $appName = $appName ?? config('app.name', 'Thế Giới Số'); + $fullName = $fullName ?? 'Quý khách'; + $vmName = $vmName ?? ''; + $snapshotName = $snapshotName ?? ''; + $action = $action ?? 'create'; + $actionAt = $actionAt ?? now()->format('d/m/Y H:i:s'); + $loginUrl = $loginUrl ?? rtrim(config('app.frontend_url', ''), '/') . '/dang-nhap'; + $supportEmail = $supportEmail ?? 'support@tgs.com.vn'; + $supportPhone = $supportPhone ?? '1900. 6119'; + + $actionLabels = [ + 'create' => ['icon' => '📸', 'title' => 'Tạo Snapshot thành công', 'desc' => 'Snapshot máy chủ ảo của bạn đã được tạo thành công', 'status' => 'Tạo thành công ✓'], + 'remove' => ['icon' => '🗑️', 'title' => 'Xóa Snapshot thành công', 'desc' => 'Snapshot máy chủ ảo của bạn đã được xóa thành công', 'status' => 'Xóa thành công ✓'], + 'revert' => ['icon' => '🔄', 'title' => 'Khôi phục Snapshot thành công', 'desc' => 'Máy chủ ảo của bạn đã được khôi phục về snapshot thành công', 'status' => 'Khôi phục thành công ✓'], + ]; + $label = $actionLabels[$action] ?? $actionLabels['create']; +@endphp + + + + + + + +
+ + + + + + + + + + + + +
+ + + + + + + + + +
+
{{ $label['icon'] }}
+

+ {{ $label['title'] }} +

+
+ {{ $label['desc'] }} +
+
+ + + + + + +
+
 
+
Xin chào {{ $fullName }},
+
 
+
+ Thao tác snapshot máy chủ ảo của bạn tại {{ $appName }} đã hoàn tất. +
+
 
+ + + + + + +
+
Máy chủ: {{ $vmName }}
+
Snapshot: {{ $snapshotName }}
+
Thời gian: {{ $actionAt }}
+
Trạng thái: {{ $label['status'] }}
+
+
 
+
+ Bạn có thể đăng nhập vào cổng quản lý để kiểm tra trạng thái dịch vụ. +
+
 
+ +
 
+
+ Nếu bạn có thắc mắc, vui lòng liên hệ với chúng tôi qua {{ $supportEmail }} hoặc {{ $supportPhone }}. +
+
 
+
+ Email này được gửi tự động. Vui lòng không trả lời email này. +
+
 
+
+
+
+ + + + + + + + + + + + + + + + + + +
+ + + + + + +
 
+
 
+ + + + + + + + + + + + + + +
  + Premium HOSTING +   + Cloud SERVER +   + EMAIL DOANH NGHIỆP +   + Thiết Kế WEBSITE +  
+
 
+ + + + + + +
 
+
+
+ + + + + + + + + + + + +
 
+

+ CÔNG TY CỔ PHẦN GIẢI PHÁP CÔNG NGHỆ THẾ GIỚI SỐ
+ Địa chỉ: 128/38 Tân Hương, Phường Phú Thọ Hòa, Thành Phố Hồ Chí Minh
+ Tel: (028) 7309. 7379 - Tổng đài: {{ $supportPhone }}
+ Email: {{ $supportEmail }}
+ + www.TGS.com.vn & + www.thegioiso.CLOUD & + www.thegioiSERVER.com & + www.CloudEmail.vn + +

+

+ + + + 🥇Thế Giới Số🥇- Premium HOSTING • CLOUD • SERVER & WEBSITE | 15+ năm Phụng Sự 🚀 + + + +

+
 
+
+