diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..3b4d3de --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,29 @@ +{ + "permissions": { + "allow": [ + "Bash(ls:*)", + "Bash(php artisan:*)", + "Bash(composer show:*)", + "Bash(php:*)", + "Bash(do php:*)", + "Bash(/usr/bin/php:*)", + "Bash(xargs cat:*)", + "Bash(composer require:*)", + "Bash(grep -rn \"reinstall\\\\|vm\" /Users/baro/Desktop/Workspace/thegioiso/web/portal-infra/portal/routes/*.php)", + "Bash(grep -l \"module_vmware\\\\|ip_pool\\\\|module_ip_address\" /Users/baro/Desktop/Workspace/thegioiso/web/portal-infra/portal/database/migrations/*.php)", + "Bash(composer why-not *)", + "Bash(npx skills *)", + "Bash(xargs -I {} basename {})", + "Read(//Users/baro/Desktop/Workspace/thegioiso/web/portal-infra/portal-admin/src/**)", + "Read(//Users/baro/Desktop/Workspace/thegioiso/web/portal-infra/portal-admin/**)", + "Bash(mkdir -p /Users/baro/Desktop/Workspace/thegioiso/web/portal-infra/portal-admin/src/features/activity-logs/components)", + "Bash(mkdir -p /Users/baro/Desktop/Workspace/thegioiso/web/portal-infra/portal-admin/src/routes/_authenticated/activity-logs)", + "Bash(npx tsr *)", + "Bash(npx tsc *)", + "Bash(chmod +x *)", + "Bash(composer check-platform-reqs *)", + "Bash(composer config *)", + "Bash(composer update *)" + ] + } +} diff --git a/app/Http/Controllers/admin/VCenterManagerController.php b/app/Http/Controllers/admin/VCenterManagerController.php index cabe47c..043251b 100644 --- a/app/Http/Controllers/admin/VCenterManagerController.php +++ b/app/Http/Controllers/admin/VCenterManagerController.php @@ -3,6 +3,7 @@ namespace App\Http\Controllers\admin; use App\Http\Controllers\Controller; +use App\Mail\VmSuspendedMail; use App\Models\ModuleIpAddress; use App\Models\ModuleVmware; use App\Models\VirtualMachines; @@ -15,6 +16,7 @@ use App\Repositories\Module\Network\NetworkRepositoryInterface; use App\Repositories\vCenter\vCenterRepositoryInterface; use Illuminate\Http\Request; use Illuminate\Support\Facades\Http; +use Illuminate\Support\Facades\Mail; use Yajra\DataTables\Facades\DataTables; class VCenterManagerController extends Controller @@ -678,4 +680,71 @@ class VCenterManagerController extends Controller 'data' => $vms ]); } + + public function suspend(Request $request) + { + $vmId = $request->input('vm_id'); + + $vm = VirtualMachines::where('vm_id', $vmId)->first(); + + if (!$vm) { + return response()->json(['error' => true, 'message' => 'VM not found'], 404); + } + + if ($vm->power_state === 'SUSPENDED') { + return response()->json(['error' => true, 'message' => 'VM is already suspended'], 400); + } + + $vcenter = $vm->datacenter?->vCenter ?? $vm->host?->datacenter?->vCenter; + + if (!$vcenter) { + return response()->json(['error' => true, 'message' => 'vCenter not found'], 400); + } + + $vCenterService = new \App\Http\Services\vCenter(); + + if (!$vCenterService->loginServer($vcenter)) { + return response()->json(['error' => true, 'message' => 'Cannot login to vCenter'], 400); + } + + $response = Http::withoutVerifying() + ->withHeaders(['vmware-api-session-id' => $vCenterService->token]) + ->post("https://{$vCenterService->apiUri}/rest/vcenter/vm/$vmId/power/suspend"); + + if ($response->status() === 200) { + $vm->update(['power_state' => 'SUSPENDED']); + $this->sendSuspendNotification($vm); + return response()->json(['error' => false, 'message' => 'Suspend command sent successfully']); + } + + $errorData = $response->json(); + if (isset($errorData['error_type']) && $errorData['error_type'] === 'ALREADY_IN_DESIRED_STATE') { + $vm->update(['power_state' => 'SUSPENDED']); + $this->sendSuspendNotification($vm); + return response()->json(['error' => false, 'message' => 'VM is already suspended']); + } + + return response()->json([ + 'error' => true, + 'message' => $errorData['value'] ?? $errorData['error_type'] ?? 'Failed to suspend VM', + ], 400); + } + + private function sendSuspendNotification(VirtualMachines $vm): void + { + $vmware = ModuleVmware::with('customer') + ->where('vmware_id', $vm->vm_id) + ->first(); + + if (!$vmware || !$vmware->customer || !$vmware->customer->email) { + return; + } + + try { + Mail::to($vmware->customer->email) + ->queue(new VmSuspendedMail($vmware->customer, $vm->name, $vm->vm_id)); + } catch (\Throwable) { + // Email failure must not block the suspend response + } + } } diff --git a/app/Http/Livewire/Cart/SummaryAddons.php b/app/Http/Livewire/Cart/SummaryAddons.php deleted file mode 100644 index 11c5d65..0000000 --- a/app/Http/Livewire/Cart/SummaryAddons.php +++ /dev/null @@ -1,133 +0,0 @@ -product = $product; - $this->months = $months; - - // Lấy tất cả addon từ database - $addonsFromDb = Addon::all()->toArray(); - - // Chuyển đổi dữ liệu addon từ database sang định dạng phù hợp với component - $this->addons = collect($addonsFromDb)->map(function ($addon) { - return [ - 'id' => $addon['id'], - 'name' => $addon['name'], - 'price' => $addon['price'], - 'min' => $addon['min'], - 'max' => $addon['max'], - 'step' => $addon['step'], - 'prefix' => $addon['prefix'], - 'type_id' => $addon['type_id'], - 'qty' => 0, // Khởi tạo số lượng ban đầu là 0 - ]; - })->toArray(); - - $this->index = $index; - } - - public function increment($key) - { - if ($this->addons[$key]['qty'] < ($this->addons[$key]['max'] ?? PHP_INT_MAX)) { - $this->addons[$key]['qty'] += ($this->addons[$key]['step'] ?? 1); - } - - $this->calculateTotal(); - $this->dispatch('addonUpdated', addons: $this->addons); - } - - public function decrement($key) - { - if ($this->addons[$key]['qty'] > ($this->addons[$key]['min'] ?? 0)) { - $this->addons[$key]['qty'] -= ($this->addons[$key]['step'] ?? 1); - } - - $this->calculateTotal(); - $this->dispatch('addonUpdated', addons: $this->addons); - } - - // Triggered when input fields are updated directly - public function updated($name, $value) - { - // Check if the updated property is related to addon quantity - if (strpos($name, 'addons.') !== false && strpos($name, '.qty') !== false) { - $parts = explode('.', $name); - $key = $parts[1] ?? null; - - if ($key !== null && isset($this->addons[$key])) { - // Ensure quantity is within bounds - $min = $this->addons[$key]['min'] ?? 0; - $max = $this->addons[$key]['max'] ?? PHP_INT_MAX; - $step = $this->addons[$key]['step'] ?? 1; - - // Round to the nearest step - $this->addons[$key]['qty'] = round($value / $step) * $step; - - // Apply min/max constraints - $this->addons[$key]['qty'] = max($min, $this->addons[$key]['qty']); - $this->addons[$key]['qty'] = min($max, $this->addons[$key]['qty']); - - // Ensure total is recalculated immediately - $this->calculateTotal(); - - // Notify listeners that addon has been updated - $this->dispatch('addonUpdated', addons: $this->addons); - } - } - } - - public function hydrate() - { - // This method is called on every Livewire request - // Perfect place to make sure our computed properties are up-to-date - $this->calculateTotal(); - } - - protected function calculateTotal() - { - // Lấy giá sản phẩm ưu tiên sale nếu có - $basePrice = $this->product->price_sale ?: $this->product->price; - - // Nhân với số tháng - $total = $basePrice * $this->months; - - foreach ($this->addons as $addon) { - $total += $addon['qty'] * $addon['price'] * $this->months; - } - - $this->cachedTotalPrice = $total; - - return $total; - } - - public function getTotalAddonPriceProperty() - { - return collect($this->addons)->sum(function ($addon) { - return $addon['qty'] * ($addon['price'] ?? 0) * $this->months; - }); - } - - public function getTotalPriceProperty() - { - // Use cached value if available, otherwise calculate - return $this->cachedTotalPrice ?? $this->calculateTotal(); - } - - public function render() - { - return view('livewire.cart.summary-addons'); - } -} diff --git a/app/Http/Livewire/CustomerEmailHistory.php b/app/Http/Livewire/CustomerEmailHistory.php deleted file mode 100644 index d6cba2c..0000000 --- a/app/Http/Livewire/CustomerEmailHistory.php +++ /dev/null @@ -1,21 +0,0 @@ - Mail::where('customer_id',auth('users')->id()) - ->where('subject', 'like',"%$this->search%") - ->latest('id') - ->paginate(10) - ]); - } -} diff --git a/app/Http/Livewire/Invoice.php b/app/Http/Livewire/Invoice.php deleted file mode 100644 index ad201a2..0000000 --- a/app/Http/Livewire/Invoice.php +++ /dev/null @@ -1,32 +0,0 @@ - CustomerInvoice::where(function ($query) { - switch ($this->status) { - case "0": - case "1": - case "2": - $query->where('status', $this->status); - break; - } - }) - ->where('customer_id', customer()->id) - ->latest() - ->paginate(10) - ]); - } -} diff --git a/app/Http/Livewire/Module/Vcenter.php b/app/Http/Livewire/Module/Vcenter.php deleted file mode 100644 index 10dd391..0000000 --- a/app/Http/Livewire/Module/Vcenter.php +++ /dev/null @@ -1,36 +0,0 @@ -vCenterRepository = $vCenterRepository; - } - - public function mount($model = null) - { - $this->model = $model; - - $this->vcenters = $this->vCenterRepository->filter( - ['status' => 1], - [], - [], - ['id', 'host'] - ); - } - - public function render() - { - return view('livewire.module.vcenter'); - } -} diff --git a/app/Http/Livewire/MyService.php b/app/Http/Livewire/MyService.php deleted file mode 100644 index e157b05..0000000 --- a/app/Http/Livewire/MyService.php +++ /dev/null @@ -1,68 +0,0 @@ -invoiceRepository = $invoiceRepository; - } - - public function updatedSearch() - { - $this->resetPage(); - } - - public function updatedStatus() - { - $this->resetPage(); - } - - public function updatedType() - { - $this->resetPage(); - } - - public function render() - { - $query = Invoice::where('customer_id', customer()->id) - ->with([ - 'invoiceItem.hosting', - 'invoiceItem.vps', - 'invoiceItem.os.osGroup', - 'invoiceItem.addons.addonItems' - ]); - - // Filter by type if specified - if (!empty($this->type)) { - $query->whereHas('invoiceItem', function($q) { - $q->where('product_type', $this->type); - }); - } - - // Filter by server_name search if specified - if (!empty($this->search)) { - $query->whereHas('invoiceItem', function($q) { - $q->where('server_name', 'like', '%' . $this->search . '%'); - }); - } - - return view('livewire.my-service', [ - 'services' => $query->orderBy('created_at', 'desc')->paginate(10) - ]); - } -} diff --git a/app/Http/Livewire/Order/OrderList.php b/app/Http/Livewire/Order/OrderList.php deleted file mode 100644 index eb5ae1b..0000000 --- a/app/Http/Livewire/Order/OrderList.php +++ /dev/null @@ -1,22 +0,0 @@ - CustomerOrder::where('customer_id', customer()->id) - ->latest() - ->paginate(10) - ]); - } -} diff --git a/app/Http/Livewire/Ticket.php b/app/Http/Livewire/Ticket.php deleted file mode 100644 index 022c730..0000000 --- a/app/Http/Livewire/Ticket.php +++ /dev/null @@ -1,24 +0,0 @@ - CustomerSupport::where('customer_id',$request->user('users')->id) - ->where('subject', 'like',"%$this->search%") - ->where('customer_id',auth('users')->id()) - ->paginate(10) - ]); - } -} diff --git a/app/Http/Livewire/Transaction.php b/app/Http/Livewire/Transaction.php deleted file mode 100644 index d555f9a..0000000 --- a/app/Http/Livewire/Transaction.php +++ /dev/null @@ -1,29 +0,0 @@ - Trans::where(function ($query) use ($request) { - $query->where('customer_id', $request->user('users')->id); - if (! empty($this->search)) - { - $query->where('trans','LIKE',"%$this->search%"); - } - }) - ->paginate(10) - ]); - } -} diff --git a/app/Http/Livewire/UserSettings.php b/app/Http/Livewire/UserSettings.php deleted file mode 100644 index 660e57d..0000000 --- a/app/Http/Livewire/UserSettings.php +++ /dev/null @@ -1,39 +0,0 @@ -user(); - - // Gán giá trị bool từ database tinyInteger - $this->email_marketing = (bool) $user->email_marketing; - $this->receive_deposit = (bool) $user->receive_deposit; - } - - public function updated($propertyName) - { - $user = auth('users')->user(); - - // Cập nhật database với giá trị int 0 hoặc 1 - $user->update([ - $propertyName => $this->$propertyName ? 1 : 0, - ]); - } - - public function render() - { - return view('livewire.user-settings', [ - 'user' => auth('users')->user(), // Truyền user để hiển thị thông tin - ]); - } -} diff --git a/app/Http/Livewire/VPSReinstallModal.php b/app/Http/Livewire/VPSReinstallModal.php deleted file mode 100644 index abb4c5f..0000000 --- a/app/Http/Livewire/VPSReinstallModal.php +++ /dev/null @@ -1,326 +0,0 @@ - 'loadOsData']; - - public function mount($productId = null, $invoiceId = null) - { - $this->productId = $productId; - $this->invoiceId = $invoiceId; - $this->operatingSystems = collect(); // Initialize as empty collection - if ($productId) { - $this->loadOsData($productId); - } - } - - public function loadOsData($productId) - { - $this->productId = $productId; - $this->isLoading = true; - - try { - // Get the VPS product - $vps = VPS::with('operatingSystems.osGroup')->find($productId); - - if (!$vps) { - session()->flash('error', __('Không tìm thấy thông tin VPS.')); - return; - } - - // Store VPS info for confirmation message - $this->vpsInfo = $vps; - - // Get OS groups that are assigned to this VPS product - $osGroupIds = $vps->operatingSystems->pluck('osGroup.id')->filter()->unique(); - - $this->osGroups = OsGroup::with(['os' => function ($query) use ($vps) { - // Only get OS versions that are assigned to this specific VPS product - $query->whereHas('vpsProducts', function ($q) use ($vps) { - $q->where('product_id', $vps->id) - ->where('product_type', 4); - })->where('status', 1); - }]) - ->whereIn('id', $osGroupIds) - ->where('status', 1) - ->get(); - - // Get current OS and ModuleVmware info from invoice_item - if ($this->invoiceId) { - $this->invoiceItem = InvoiceItem::where('invoice_id', $this->invoiceId) - ->where('product_id', $productId) - ->where('product_type', 4) - ->with('os.osGroup') - ->first(); - - $this->currentOs = $this->invoiceItem?->os; - - // Debug InvoiceItem info first - Log::info('InvoiceItem debug info', [ - 'invoice_id' => $this->invoiceId, - 'product_id' => $productId, - 'product_type' => 4, - 'invoice_item_found' => $this->invoiceItem ? 'yes' : 'no', - 'invoice_item_id' => $this->invoiceItem?->id ?? 'null', - 'invoice_item_relid' => $this->invoiceItem?->relid ?? 'null', - 'invoice_item_type_id' => $this->invoiceItem?->type_id ?? 'null', - 'invoice_item_product_id' => $this->invoiceItem?->product_id ?? 'null', - 'invoice_item_product_type' => $this->invoiceItem?->product_type ?? 'null' - ]); - - // Get ModuleVmware info to access IP address - if ($this->invoiceItem) { - // Try to get ModuleVmware directly using invoice_id and customer_id - $this->invoice = Invoice::find($this->invoiceId); - if ($this->invoice && $this->invoice->customer_id) { - $this->moduleVmware = ModuleVmware::where('customer_id', $this->invoice->customer_id) - ->where('order_id', $this->invoiceId) - ->with('ipAddress') - ->first(); - - // If not found by order_id, try to find by customer_id and product info - if (!$this->moduleVmware) { - $this->moduleVmware = ModuleVmware::where('customer_id', $this->invoice->customer_id) - ->with('ipAddress') - ->first(); - } - } - - // Try to get IP from vCenter API first, fallback to database - $this->vpsIpAddress = $this->getIPFromVCenter() ?? $this->moduleVmware?->ipAddress?->ip ?? null; - } else { - Log::warning('InvoiceItem not found', [ - 'invoice_id' => $this->invoiceId, - 'product_id' => $productId, - 'product_type' => 4 - ]); - } - } - } catch (\Exception $e) { - Log::error('Error loading OS data', [ - 'product_id' => $productId, - 'error' => $e->getMessage(), - 'trace' => $e->getTraceAsString() - ]); - session()->flash('error', __('Có lỗi xảy ra khi tải dữ liệu hệ điều hành.')); - } finally { - $this->isLoading = false; - } - } - - public function updatedSelectedOsGroupId($value) - { - // Reset selected OS when changing group - $this->selectedOsId = null; - $this->selectedOsDetails = null; - - $this->isLoading = true; - - try { - if ($value) { - $this->operatingSystems = Os::where('os_group_id', $value) - ->where('status', 1) - ->whereHas('vpsProducts', function ($query) { - $query->where('product_id', $this->productId) - ->where('product_type', 4); - }) - ->with('osGroup') - ->get(); - - Log::info('Operating systems loaded for group', [ - 'os_group_id' => $value, - 'os_count' => is_object($this->operatingSystems) ? $this->operatingSystems->count() : count($this->operatingSystems), - 'selected_os_id' => $this->selectedOsId - ]); - } else { - $this->operatingSystems = collect(); - } - } catch (\Exception $e) { - Log::error('Error loading operating systems', [ - 'os_group_id' => $value, - 'error' => $e->getMessage() - ]); - session()->flash('error', __('Có lỗi xảy ra khi tải danh sách phiên bản hệ điều hành.')); - $this->operatingSystems = collect(); - } finally { - $this->isLoading = false; - } - } - - public function updatedSelectedOsId($value) - { - Log::info('Selected OS ID updated', [ - 'selected_os_id' => $value, - 'is_loading' => $this->isLoading - ]); - - // Store selected OS details for confirmation message - if ($value) { - $this->selectedOsDetails = Os::with('osGroup')->find($value); - - Log::info('OS details loaded', [ - 'os_id' => $value, - 'os_name' => $this->selectedOsDetails?->osGroup?->name ?? 'null', - 'os_version' => $this->selectedOsDetails?->version ?? 'null' - ]); - } else { - $this->selectedOsDetails = null; - } - } - - public function confirmReinstall() - { - Log::info('confirmReinstall called', [ - 'selected_os_id' => $this->selectedOsId, - 'invoice_id' => $this->invoiceId, - 'is_loading' => $this->isLoading - ]); - - if (!$this->selectedOsId) { - session()->flash('error', __('Vui lòng chọn hệ điều hành.')); - return; - } - - if (!$this->invoiceId) { - session()->flash('error', __('Thiếu thông tin hóa đơn.')); - return; - } - - $this->isLoading = true; - - try { - $response = Http::withoutVerifying() - ->withHeaders([ - 'X-CSRF-TOKEN' => csrf_token(), - 'Accept' => 'application/json', - 'Content-Type' => 'application/json', - ]) - ->post(route('service.reinstall.vm'), [ - 'invoice_id' => $this->invoiceId, - 'os_id' => $this->selectedOsId, - ]); - - Log::info('Reinstall response', [ - 'status' => $response->status(), - 'body' => $response->body() - ]); - - if ($response->successful()) { - session()->flash('success', __('Yêu cầu cài lại hệ điều hành đã được gửi thành công.')); - $this->dispatch('close-reinstall-modal'); - } else { - $errorMessage = $response->json('message') ?? __('Có lỗi xảy ra khi gửi yêu cầu.'); - session()->flash('error', $errorMessage); - } - } catch (\Exception $e) { - Log::error('Error during reinstall request', [ - 'invoice_id' => $this->invoiceId, - 'os_id' => $this->selectedOsId, - 'error' => $e->getMessage() - ]); - session()->flash('error', __('Có lỗi xảy ra khi gửi yêu cầu cài lại.')); - } finally { - $this->isLoading = false; - } - } - - /** - * Get IP address from vCenter API - */ - private function getIPFromVCenter() - { - try { - if (!$this->moduleVmware || !$this->moduleVmware->vmware_id) { - Log::warning('No ModuleVmware or vmware_id found for vCenter IP lookup'); - return null; - } - - // Get vCenter configuration from ModuleVmware - $vcenter = $this->moduleVmware->ipAddress?->host?->datacenter?->vCenter; - if (!$vcenter) { - Log::warning('No vCenter configuration found for ModuleVmware', [ - 'module_vmware_id' => $this->moduleVmware->id, - 'vmware_id' => $this->moduleVmware->vmware_id - ]); - return null; - } - - // Initialize vCenter service - $vCenterService = new \App\Http\Services\vCenter(); - - // Login to vCenter - $loginSuccess = $vCenterService->loginServer($vcenter); - if (!$loginSuccess) { - Log::warning('Failed to login to vCenter for IP lookup', [ - 'vmware_id' => $this->moduleVmware->vmware_id, - 'vcenter_id' => $vcenter->id - ]); - return null; - } - - // Get IP address from vCenter - $ipInfo = $vCenterService->GetIPAddressVirtualMachine($this->moduleVmware->vmware_id); - - Log::info('vCenter IP lookup result', [ - 'vmware_id' => $this->moduleVmware->vmware_id, - 'ip_info' => $ipInfo - ]); - - if (is_array($ipInfo) && isset($ipInfo['error']) && $ipInfo['error'] === false) { - // Try different possible structures - if (isset($ipInfo['data']['ip_address'])) { - return $ipInfo['data']['ip_address']; - } elseif (isset($ipInfo['data']['value']['ip_address'])) { - return $ipInfo['data']['value']['ip_address']; - } - } - - Log::warning('No valid IP address found in vCenter response', [ - 'vmware_id' => $this->moduleVmware->vmware_id, - 'ip_info' => $ipInfo - ]); - return null; - - } catch (\Exception $e) { - Log::error('Error getting IP from vCenter', [ - 'vmware_id' => $this->moduleVmware?->vmware_id ?? 'unknown', - 'error' => $e->getMessage(), - 'file' => $e->getFile(), - 'line' => $e->getLine() - ]); - return null; - } - } - - public function render() - { - return view('livewire.v-p-s-reinstall-modal'); - } -} diff --git a/app/Http/Livewire/VcenterDatacenterSelect.php b/app/Http/Livewire/VcenterDatacenterSelect.php deleted file mode 100644 index 2ddc3fa..0000000 --- a/app/Http/Livewire/VcenterDatacenterSelect.php +++ /dev/null @@ -1,55 +0,0 @@ -vCenterRepository = $vCenterRepository; - $this->datacenterRepository = $datacenterRepository; - } - - public function mount() - { - $this->vcenters = $this->vCenterRepository->filter(['status' => 1], [], [], ['id', 'host']); - } - - public function updatedVcenterId($value) - { - if (!empty($value)) { - $this->datacenters = $this->datacenterRepository->filter( - ['vcenter_id' => $value], - [], - [], - ['id', 'name'] - ); - } else { - $this->datacenters = []; - } - $this->datacenter_id = null; // reset datacenter nếu vcenter đổi - } - - public function render() - { - return view('livewire.vcenter-datacenter-select', - [ - 'vcenters' => $this->vcenters - ]); - } -} diff --git a/app/Mail/VmSuspendedMail.php b/app/Mail/VmSuspendedMail.php new file mode 100644 index 0000000..715f96e --- /dev/null +++ b/app/Mail/VmSuspendedMail.php @@ -0,0 +1,46 @@ + $this->customer->fullname, + 'vmName' => $this->vmName, + 'vmwareId' => $this->vmwareId, + ], + ); + } + + public function attachments(): array + { + return []; + } +} diff --git a/config/cors.php b/config/cors.php index 3e71aa6..9b6c369 100644 --- a/config/cors.php +++ b/config/cors.php @@ -22,6 +22,7 @@ return [ 'http://localhost:3000', 'http://localhost:5173', 'http://localhost:5174', + 'http://localhost:4173', 'https://portal.tgs.com.vn', 'https://demo.client.tgs.com.vn', 'https://demo.admin.tgs.com.vn', diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 737e76d..bacaea6 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -16,13 +16,14 @@ class DatabaseSeeder extends Seeder public function run(): void { $this->call(BankTransferPaymentSeeder::class); + $this->call(AdminSeeder::class); $this->call(RoleSeeder::class); $this->call(RolesAndPermissions::class); $user = User::find(1); $user->assignRole('Full Administrator'); $this->call(TemplateSeeder::class); $this->call(Setting::class); - $this->call(ControlPanelSeeder::class); - $this->call(VPSProductSeeder::class); + // $this->call(ControlPanelSeeder::class); + // $this->call(VPSProductSeeder::class); } } diff --git a/resources/views/emails/customer/vm-suspended.blade.php b/resources/views/emails/customer/vm-suspended.blade.php new file mode 100644 index 0000000..ba29bb9 --- /dev/null +++ b/resources/views/emails/customer/vm-suspended.blade.php @@ -0,0 +1,77 @@ + + + + + + Dịch vụ Cloud VPS bị tạm ngưng + + + +
+
+
+
⚠️
+

Dịch vụ Cloud VPS đã bị tạm ngưng

+

Vui lòng thanh toán để khôi phục dịch vụ

+
+ +
+

+ Xin chào {{ $customerName ?? 'Quý khách' }},
+ Dịch vụ Cloud VPS của bạn hiện đã bị tạm ngưng (Suspend) do chưa thanh toán gia hạn. + Vui lòng nhanh chóng liên hệ hoặc thanh toán để tránh mất dữ liệu. +

+ + + + + + +
Tên dịch vụ{{ $vmName }}
VM ID{{ $vmwareId }}
Thời gian tạm ngưng{{ now()->setTimezone('Asia/Ho_Chi_Minh')->format('d/m/Y H:i') }}
+ +
+

⏰ Sau 30 ngày kể từ ngày tạm ngưng, nếu không có thanh toán, dịch vụ của bạn sẽ bị xóa vĩnh viễn và toàn bộ dữ liệu sẽ không thể khôi phục.

+

💡 Vui lòng đăng nhập vào cổng khách hàng để gia hạn dịch vụ hoặc liên hệ bộ phận hỗ trợ để được giải quyết kịp thời.

+
+ +
+ 🚨 CẢNH BÁO: Sau 30 ngày không thanh toán, toàn bộ dữ liệu trên máy chủ ảo sẽ bị XÓA VĨNH VIỄN và không thể phục hồi. +
+ + +
+ + +
+
+ + diff --git a/routes/api.php b/routes/api.php index 2a98aeb..7162dc9 100644 --- a/routes/api.php +++ b/routes/api.php @@ -45,15 +45,6 @@ use Illuminate\Support\Facades\Route; */ Route::prefix('v1') - ->group(function () { - Route::get('/debug-customers', function () { - $customers = \App\Models\Customer::orderByDesc('created_at') - ->limit(5) - ->get(['id', 'uuid', 'fullname', 'email', 'total', 'created_at']); - - return response()->json($customers); - }); - }) ->group(function () { // Invoice Test — Send invoice email with PDF attachment Route::prefix('invoice-test') @@ -398,6 +389,7 @@ Route::prefix('v1') Route::post('vm/power-off', 'powerOff')->middleware('perm:module-vcenter.edit'); Route::post('vm/restart', 'restart')->middleware('perm:module-vcenter.edit'); Route::post('vm/snapshot', 'snapshot')->middleware('perm:module-vcenter.edit'); + Route::post('vm/suspend', 'suspend')->middleware('perm:module-vcenter.edit'); Route::get('vms/powered-off', 'getPoweredOffVms')->middleware('perm:module-vcenter.view'); });