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.
+
+
+ |
+
+
+
+ |
+
+
+
+ |
+
+
+
+
+
+
+ |
+
+ |
+
+
+ | |
+
+
+ |
+
+ |
+
+
+ | |
+
+
+ |
+
+ |
+
+
+
+ |
+
+
+ |
+
+ |
+
+
+
+ |
+
+
+