diff --git a/.DS_Store b/.DS_Store
index 90352ad..af25651 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/.claude/settings.local.json b/.claude/settings.local.json
index 3b4d3de..b7de7ef 100644
--- a/.claude/settings.local.json
+++ b/.claude/settings.local.json
@@ -23,7 +23,15 @@
"Bash(chmod +x *)",
"Bash(composer check-platform-reqs *)",
"Bash(composer config *)",
- "Bash(composer update *)"
+ "Bash(composer update *)",
+ "Bash(redis-cli ping *)",
+ "Bash(redis-cli llen *)",
+ "Bash(redis-cli lrange *)",
+ "Bash(redis-cli keys *)",
+ "Bash(redis-cli --no-auth-warning keys \"*\")",
+ "Bash(redis-cli --no-auth-warning llen \"the_gioi_so_horizon:queue:default\")",
+ "Bash(redis-cli --no-auth-warning lrange \"the_gioi_so_horizon:queue:default\" 0 4)",
+ "Bash(redis-cli *)"
]
}
}
diff --git a/app/Http/Controllers/Api/Admin/Invoice/InvoiceTestController.php b/app/Http/Controllers/Api/Admin/Invoice/InvoiceTestController.php
index beda088..2ea4c58 100644
--- a/app/Http/Controllers/Api/Admin/Invoice/InvoiceTestController.php
+++ b/app/Http/Controllers/Api/Admin/Invoice/InvoiceTestController.php
@@ -124,6 +124,8 @@ class InvoiceTestController extends Controller
'companyAddress' => $settingValue->address ?? '',
'companyPhone' => $settingValue->phone ?? '',
'companyEmail' => $settingValue->email ?? '',
- ])->stream($invoice->invoice_number . '.pdf');
+ ])
+ ->setPaper('a4', 'portrait')
+ ->stream($invoice->invoice_number . '.pdf');
}
-}
\ No newline at end of file
+}
diff --git a/app/Http/Controllers/Api/Admin/Setting/SettingController.php b/app/Http/Controllers/Api/Admin/Setting/SettingController.php
index a3c6eef..67961fc 100644
--- a/app/Http/Controllers/Api/Admin/Setting/SettingController.php
+++ b/app/Http/Controllers/Api/Admin/Setting/SettingController.php
@@ -11,6 +11,7 @@ use App\Support\Cache\CacheKey;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
+use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Validator;
class SettingController extends Controller
@@ -99,4 +100,19 @@ class SettingController extends Controller
'message' => 'Cập nhật cài đặt thành công.',
]);
}
+
+ public function uploadMedia(Request $request): JsonResponse
+ {
+ $request->validate([
+ 'file' => 'required|file|mimes:jpg,jpeg,png,webp,svg|max:5120',
+ ]);
+
+ $path = $request->file('file')->store('system-config', 'public');
+
+ return response()->json([
+ 'error' => false,
+ 'url' => Storage::disk('public')->url($path),
+ 'path' => $path,
+ ]);
+ }
}
diff --git a/app/Http/Controllers/Api/Admin/Transaction/AdminTransactionController.php b/app/Http/Controllers/Api/Admin/Transaction/AdminTransactionController.php
index b2a550c..3486b5c 100644
--- a/app/Http/Controllers/Api/Admin/Transaction/AdminTransactionController.php
+++ b/app/Http/Controllers/Api/Admin/Transaction/AdminTransactionController.php
@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api\Admin\Transaction;
use App\Enums\TransactionStatus;
use App\Http\Controllers\Controller;
+use App\Mail\Customer\ManualDepositStatus;
use App\Models\Notification;
use App\Models\Log;
use App\Models\Transaction;
@@ -11,6 +12,7 @@ use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log as FacadesLog;
+use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
class AdminTransactionController extends Controller
@@ -126,6 +128,28 @@ class AdminTransactionController extends Controller
]);
DB::commit();
+
+ try {
+ Mail::to($customer->email)->send(new ManualDepositStatus([
+ 'customer_id' => $customer->id,
+ 'customer_email' => $customer->email,
+ 'customer_name' => $customer->fullname,
+ 'transaction_code' => $transaction->public_code,
+ 'amount' => $transaction->amount,
+ 'status_label' => 'Đã duyệt',
+ 'status_color' => '#15803d',
+ 'status_bg' => '#f0fdf4',
+ 'subject' => 'Yêu cầu nạp tiền đã được duyệt - ' . $transaction->public_code,
+ 'status_message' => 'Yêu cầu nạp tiền của bạn đã được duyệt. Số tiền đã được cộng vào tài khoản và có thể sử dụng ngay.',
+ 'deposit_date' => $transaction->created_at?->format('d/m/Y H:i:s') ?? now()->format('d/m/Y H:i:s'),
+ 'processed_date' => now()->format('d/m/Y H:i:s'),
+ ]));
+ } catch (\Throwable $e) {
+ FacadesLog::error('Không thể gửi email duyệt nạp tiền thủ công: ' . $e->getMessage(), [
+ 'transaction_code' => $transaction->public_code,
+ 'customer_email' => $customer->email,
+ ]);
+ }
} catch (\Exception $e) {
DB::rollBack();
FacadesLog::error('AdminTransactionController::approve error: ' . $e->getMessage());
@@ -172,6 +196,28 @@ class AdminTransactionController extends Controller
]);
DB::commit();
+
+ try {
+ Mail::to($customer->email)->send(new ManualDepositStatus([
+ 'customer_id' => $customer->id,
+ 'customer_email' => $customer->email,
+ 'customer_name' => $customer->fullname,
+ 'transaction_code' => $transaction->public_code,
+ 'amount' => $transaction->amount,
+ 'status_label' => 'Không phù hợp',
+ 'status_color' => '#b91c1c',
+ 'status_bg' => '#fef2f2',
+ 'subject' => 'Yêu cầu nạp tiền không phù hợp - ' . $transaction->public_code,
+ 'status_message' => 'Yêu cầu nạp tiền của bạn chưa phù hợp để được duyệt. Vui lòng kiểm tra lại chứng từ hoặc liên hệ bộ phận hỗ trợ để được hướng dẫn thêm.',
+ 'deposit_date' => $transaction->created_at?->format('d/m/Y H:i:s') ?? now()->format('d/m/Y H:i:s'),
+ 'processed_date' => now()->format('d/m/Y H:i:s'),
+ ]));
+ } catch (\Throwable $e) {
+ FacadesLog::error('Không thể gửi email từ chối nạp tiền thủ công: ' . $e->getMessage(), [
+ 'transaction_code' => $transaction->public_code,
+ 'customer_email' => $customer->email,
+ ]);
+ }
} catch (\Exception $e) {
DB::rollBack();
FacadesLog::error('AdminTransactionController::reject error: ' . $e->getMessage());
diff --git a/app/Http/Controllers/Api/Service/VmRebuildController.php b/app/Http/Controllers/Api/Service/VmRebuildController.php
index 88d371b..c62d9a3 100644
--- a/app/Http/Controllers/Api/Service/VmRebuildController.php
+++ b/app/Http/Controllers/Api/Service/VmRebuildController.php
@@ -5,7 +5,6 @@ namespace App\Http\Controllers\Api\Service;
use App\Http\Controllers\Controller;
use App\Http\Controllers\Api\Service\Concerns\ResolvesVm;
use App\Http\Services\vCenter;
-use App\Models\ModuleVmware;
use App\Services\ActivityLogger;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -19,20 +18,18 @@ class VmRebuildController extends Controller
public function options(Request $request): JsonResponse
{
- $productId = (int) $request->query('product');
+ $productId = $request->query('product');
if (!$productId) {
return response()->json(['error' => true, 'message' => 'Thiếu product id'], 422);
}
- $vm = ModuleVmware::with(['order.orderItems.product.osVersions.osGroup'])
- ->find($productId);
- if (!$vm) {
- return response()->json(['error' => true, 'message' => 'Không tìm thấy VM'], 404);
- }
- if ((int) $vm->customer_id !== (int) $request->user()->id) {
- return response()->json(['error' => true, 'message' => 'Bạn không có quyền thao tác VM này'], 403);
+ $vm = $this->resolveVmById($productId, $request);
+ if ($vm instanceof JsonResponse) {
+ return $vm;
}
+ $vm->loadMissing(['order.orderItems.product.osVersions.osGroup']);
+
$product = optional($vm->order?->orderItems?->first())->product;
if (!$product) {
return response()->json(['error' => true, 'message' => 'Không tìm thấy product của dịch vụ hiện tại'], 404);
@@ -63,19 +60,17 @@ class VmRebuildController extends Controller
{
$customer = $request->user();
$payload = $request->validate([
- 'product' => ['required', 'integer'],
+ 'product' => ['required'],
'os_id' => ['required', 'integer', 'exists:os,id'],
]);
- $vm = ModuleVmware::with(['order.orderItems.product', 'vCenter', 'ipAddress.host.datacenter.vCenter'])
- ->find($payload['product']);
- if (!$vm) {
- return response()->json(['error' => true, 'message' => 'Không tìm thấy VM'], 404);
- }
- if ((int) $vm->customer_id !== (int) $customer->id) {
- return response()->json(['error' => true, 'message' => 'Bạn không có quyền thao tác VM này'], 403);
+ $vm = $this->resolveVmById($payload['product'], $request);
+ if ($vm instanceof JsonResponse) {
+ return $vm;
}
+ $vm->loadMissing(['order.orderItems.product', 'vCenter', 'ipAddress.host.datacenter.vCenter']);
+
$product = optional($vm->order?->orderItems?->first())->product;
if (!$product) {
return response()->json(['error' => true, 'message' => 'Không tìm thấy product của dịch vụ hiện tại'], 404);
@@ -123,20 +118,18 @@ class VmRebuildController extends Controller
public function addonOptions(Request $request): JsonResponse
{
- $productId = (int) $request->query('product');
+ $productId = $request->query('product');
if (!$productId) {
return response()->json(['error' => true, 'message' => 'Thiếu product id'], 422);
}
- $vm = ModuleVmware::with(['order.orderItems.product.addons'])
- ->find($productId);
- if (!$vm) {
- return response()->json(['error' => true, 'message' => 'Không tìm thấy VM'], 404);
- }
- if ((int) $vm->customer_id !== (int) $request->user()->id) {
- return response()->json(['error' => true, 'message' => 'Bạn không có quyền thao tác VM này'], 403);
+ $vm = $this->resolveVmById($productId, $request);
+ if ($vm instanceof JsonResponse) {
+ return $vm;
}
+ $vm->loadMissing(['order.orderItems.product.addons']);
+
$product = optional($vm->order?->orderItems?->first())->product;
if (!$product) {
return response()->json(['error' => true, 'message' => 'Không tìm thấy product của dịch vụ hiện tại'], 404);
@@ -145,18 +138,23 @@ class VmRebuildController extends Controller
$addons = $product->addons()->where('status', true)->get()->map(function ($addon) {
return [
'id' => $addon->id,
+ 'type' => $addon->type instanceof \BackedEnum ? $addon->type->value : (string) $addon->type,
'code' => strtolower((string) $addon->code),
'name' => $addon->name,
'price' => (float) $addon->price,
'min' => (int) ($addon->min ?? 0),
'max' => (int) ($addon->max ?? 0),
'step' => (int) ($addon->step ?? 1),
+ 'prefix' => $addon->prefix,
+ 'input_type' => $addon->input_type ?? 'number',
+ 'sort_order' => (int) ($addon->sort_order ?? 0),
];
});
return response()->json([
'error' => false,
- 'data' => [
+ 'data' => $addons->values(),
+ 'meta' => [
'product_id' => $product->id,
'product_name' => $product->name,
'base_specs' => [
@@ -166,7 +164,6 @@ class VmRebuildController extends Controller
'bandwidth' => (int) ($product->bandwidth ?? 0),
'gpu' => $product->gpu,
],
- 'addons' => $addons->values(),
],
]);
}
@@ -175,23 +172,21 @@ class VmRebuildController extends Controller
{
$customer = $request->user();
$payload = $request->validate([
- 'product' => ['required', 'integer'],
+ 'product' => ['required'],
'cpu' => ['nullable', 'integer', 'min:0'],
'ram' => ['nullable', 'integer', 'min:0'],
'disk' => ['nullable', 'integer', 'min:0'],
'bandwidth' => ['nullable', 'integer', 'min:0'],
- 'gpu' => ['nullable', 'string', 'max:255'],
+ 'gpu' => ['nullable'],
]);
- $vm = ModuleVmware::with(['vCenter', 'ipAddress.host.datacenter.vCenter'])
- ->find($payload['product']);
- if (!$vm) {
- return response()->json(['error' => true, 'message' => 'Không tìm thấy VM'], 404);
- }
- if ((int) $vm->customer_id !== (int) $customer->id) {
- return response()->json(['error' => true, 'message' => 'Bạn không có quyền thao tác VM này'], 403);
+ $vm = $this->resolveVmById($payload['product'], $request);
+ if ($vm instanceof JsonResponse) {
+ return $vm;
}
+ $vm->loadMissing(['vCenter', 'ipAddress.host.datacenter.vCenter']);
+
$vcenter = $vm->vCenter ?? $vm->ipAddress?->host?->datacenter?->vCenter;
if (!$vcenter || !$serviceVCenter->loginServer($vcenter)) {
return response()->json(['error' => true, 'message' => 'Không thể đăng nhập vCenter'], 400);
@@ -235,4 +230,4 @@ class VmRebuildController extends Controller
],
]);
}
-}
\ No newline at end of file
+}
diff --git a/app/Http/Controllers/Api/Transaction/ManualDepositController.php b/app/Http/Controllers/Api/Transaction/ManualDepositController.php
index e504d30..b4b404b 100644
--- a/app/Http/Controllers/Api/Transaction/ManualDepositController.php
+++ b/app/Http/Controllers/Api/Transaction/ManualDepositController.php
@@ -6,12 +6,16 @@ use App\Enums\TransactionStatus;
use App\Http\Controllers\Controller;
use App\Http\Requests\DepositRequest;
use App\Http\Services\Telegram;
+use App\Mail\Admin\ManualDepositPendingNotification;
+use App\Mail\Customer\ManualDepositStatus;
use App\Models\Log;
+use App\Models\Setting;
use App\Models\Transaction;
use App\Services\ActivityLogger;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
@@ -196,6 +200,7 @@ class ManualDepositController extends Controller
try {
(new Telegram())->SendManualDepositPendingApproval($user, $transaction);
+ $this->sendManualDepositAdminEmail($user, $transaction, $request);
} catch (\Exception) {
// Không để lỗi Telegram ảnh hưởng response
}
@@ -214,6 +219,27 @@ class ManualDepositController extends Controller
newValues: ['public_code' => $transaction->public_code, 'amount' => $transaction->amount, 'has_proof' => true],
);
+ try {
+ Mail::to($user->email)->send(new ManualDepositStatus([
+ 'customer_id' => $user->id,
+ 'customer_email' => $user->email,
+ 'customer_name' => $user->fullname,
+ 'transaction_code' => $transaction->public_code,
+ 'amount' => $transaction->amount,
+ 'status_label' => 'Đang chờ duyệt',
+ 'status_color' => '#f16723',
+ 'status_bg' => '#fff7ed',
+ 'subject' => 'Yêu cầu nạp tiền đang chờ duyệt - ' . $transaction->public_code,
+ 'status_message' => 'Yêu cầu nạp tiền của bạn đã được ghi nhận. Chúng tôi sẽ kiểm tra chứng từ và phản hồi kết quả trong thời gian sớm nhất.',
+ 'deposit_date' => $transaction->created_at?->format('d/m/Y H:i:s') ?? now()->format('d/m/Y H:i:s'),
+ ]));
+ } catch (\Throwable $e) {
+ \Log::error('Không thể gửi email trạng thái nạp tiền thủ công: ' . $e->getMessage(), [
+ 'transaction_code' => $transaction->public_code,
+ 'customer_email' => $user->email,
+ ]);
+ }
+
return response()->json([
'error' => false,
'message' => 'Yêu cầu nạp tiền đã được ghi nhận, vui lòng chờ admin xác nhận',
@@ -239,4 +265,37 @@ class ManualDepositController extends Controller
'path' => $path,
]);
}
+
+ private function sendManualDepositAdminEmail($customer, Transaction $transaction, Request $request): void
+ {
+ try {
+ $setting = Setting::where('key', 'setting')->first();
+ $settingValue = $setting ? json_decode((string) $setting->value, true) : [];
+ $adminEmail = $settingValue['admin_email'] ?? null;
+
+ if (empty($adminEmail)) {
+ \Log::warning('Manual deposit admin email not sent: admin_email not configured.', [
+ 'transaction_code' => $transaction->public_code,
+ ]);
+ return;
+ }
+
+ Mail::to($adminEmail)->send(new ManualDepositPendingNotification([
+ 'company_name' => $settingValue['company'] ?? config('app.name', 'Portal Admin'),
+ 'customer_name' => $customer->fullname ?: trim(($customer->firstname ?? '') . ' ' . ($customer->lastname ?? '')),
+ 'customer_email' => $customer->email,
+ 'customer_phone' => $customer->phone ?? null,
+ 'client_ip' => $request->ip(),
+ 'transaction_code' => $transaction->public_code,
+ 'amount' => $transaction->amount,
+ 'proof_image_url' => $transaction->proof_image ? Storage::disk('public')->url($transaction->proof_image) : null,
+ 'created_at' => now()->format('d/m/Y H:i:s'),
+ ]));
+ } catch (\Throwable $e) {
+ \Log::error('Không thể gửi email admin thông báo nạp tiền chờ duyệt: ' . $e->getMessage(), [
+ 'transaction_code' => $transaction->public_code,
+ 'customer_email' => $customer->email ?? null,
+ ]);
+ }
+ }
}
diff --git a/app/Http/Controllers/Backend/DomainController.php b/app/Http/Controllers/Backend/DomainController.php
deleted file mode 100644
index 9941a7d..0000000
--- a/app/Http/Controllers/Backend/DomainController.php
+++ /dev/null
@@ -1,378 +0,0 @@
-domainRepository->filter([], ['users', 'server'])->map(function ($domain) {
- return [
- 'id' => $domain->id,
- 'name' => $domain->domain_name,
- 'server' => $domain->server ? $domain->server->ip_address : '',
- 'user' => $domain->users->pluck('name')->implode(', '),
- 'checkbox' => '',
- 'created_at' => Carbon::parse($domain->created_at)->format('Y-m-d H:i:s'),
- 'action' => $this->domainActionButtons($domain),
- ];
- });
-
- return response()->json(['data' => $domains]);
- }
-
- public function create()
- {
- $title = 'Add Domain';
- return view('Backend.domains.create', compact('title'));
- }
-
- public function store(DomainRequest $request)
- {
- $validated = $request->validated();
-
- $isCreated = $this->domainRepository->findDomain($validated['domain_name']);
- if (!empty($isCreated)) {
- return response()->json([
- 'success' => false,
- 'message' => "Domain '{$validated['domain_name']}' đã tồn tại trong hệ thống!",
- ]);
- }
-
- DB::beginTransaction();
- try {
- $userIds = $validated['user_id'] ?? [];
- $domainData = collect($validated)->except('user_id')->toArray();
-
- $domain = $this->domainRepository->create($domainData);
- $domain->users()->attach($userIds);
- $domain->save();
-
- try {
- $server = $this->serverService->findServerByID($domain->dns_manager_server_id);
-
- if (!$server) {
- throw new Exception("Không tìm thấy server với ID: {$domain->dns_manager_server_id}");
- }
-
- $existingDomainInfo = $this->serverService->getDomainInfo($domain->domain_name);
-
- if ($existingDomainInfo) {
- $this->domainRepository->update($domain->id, [
- 'is_created' => 1,
- 'plesk_id' => $existingDomainInfo['id'],
- ]);
- } else {
- $response = $this->serverService->createDomain($domain->toArray());
-
- if (!$response || !isset($response['id'])) {
- throw new Exception('Không thể tạo domain trên server. Vui lòng kiểm tra lại thông tin server.');
- }
-
- $this->domainRepository->update($domain->id, [
- 'is_created' => 1,
- 'plesk_id' => $response['id'],
- ]);
- }
- } catch (Exception $domainError) {
- Log::error('Lỗi khi tạo domain: ' . $domainError->getMessage());
- }
-
- DB::commit();
-
- $domain = $domain->refresh();
- $message = $domain->is_created == 1 && $domain->plesk_id
- ? "Domain '{$domain->domain_name}' đã được xử lý thành công trên server!"
- : "Domain '{$domain->domain_name}' đã được lưu vào database nhưng chưa thể xử lý trên server.";
-
- return response()->json(['success' => true, 'message' => $message, 'url' => route('admin.domain.index')]);
- } catch (Exception $error) {
- DB::rollBack();
- return response()->json(['success' => false, 'message' => 'Có lỗi xảy ra: ' . $error->getMessage()]);
- }
- }
-
- public function edit(int $id)
- {
- $domain = $this->domainRepository->find($id);
- if (empty($domain)) {
- return back()->withErrors("Không tìm thấy domain với ID: {$id}");
- }
-
- $title = 'Edit domain';
- return view('Backend.domains.edit', compact('title', 'domain'));
- }
-
- public function update(DomainRequest $request, int $id)
- {
- $domain = $this->domainRepository->find($id);
-
- if (empty($domain)) {
- return response()->json(['success' => false, 'message' => "Không tìm thấy domain với ID: {$id}"]);
- }
-
- $validated = $request->validated();
-
- DB::beginTransaction();
- try {
- $userIds = $validated['user_id'] ?? [];
- $domainData = collect($validated)->except('user_id')->toArray();
-
- $this->domainRepository->update($id, $domainData);
- $domain->users()->sync($userIds);
-
- try {
- $updatedDomain = $this->domainRepository->find($id);
- $server = $this->serverService->findServerByID($updatedDomain->dns_manager_server_id);
-
- if (!$server) {
- throw new Exception("Không tìm thấy server với ID: {$updatedDomain->dns_manager_server_id}");
- }
-
- $existingDomainInfo = $this->serverService->getDomainInfo($updatedDomain->domain_name);
-
- if ($existingDomainInfo) {
- $this->serverService->updateDomain($existingDomainInfo['id'], $updatedDomain->toArray());
- $this->domainRepository->update($updatedDomain->id, [
- 'is_created' => 1,
- 'plesk_id' => $existingDomainInfo['id'],
- ]);
- } else {
- $response = $this->serverService->createDomain($updatedDomain->toArray());
-
- if (!$response || !isset($response['id'])) {
- throw new Exception('Không thể tạo domain trên server.');
- }
-
- $this->domainRepository->update($updatedDomain->id, [
- 'is_created' => 1,
- 'plesk_id' => $response['id'],
- ]);
- }
- } catch (Exception $domainError) {
- Log::error('Lỗi khi cập nhật domain: ' . $domainError->getMessage());
- }
-
- DB::commit();
-
- $updatedDomain = $this->domainRepository->find($id);
- $message = $updatedDomain->is_created == 1 && $updatedDomain->plesk_id
- ? "Domain '{$updatedDomain->domain_name}' đã được cập nhật thành công trên server!"
- : "Domain '{$updatedDomain->domain_name}' đã được cập nhật trong database nhưng chưa thể xử lý trên server.";
-
- return response()->json(['success' => true, 'message' => $message, 'url' => route('admin.domain.index')]);
- } catch (Exception $error) {
- DB::rollBack();
- return response()->json(['success' => false, 'message' => 'Có lỗi xảy ra: ' . $error->getMessage()]);
- }
- }
-
- public function record(int $id = 0)
- {
- $domain = $this->domainRepository->findWithRelations($id, ['record']);
-
- if (empty($domain)) {
- return back()->withErrors("Không tìm thấy domain với ID: {$id}");
- }
-
- $title = strtoupper($domain->domain_name) . ' Records';
- return view('Backend.domains.view', compact('domain', 'title', 'id'));
- }
-
- public function destroy(int $id)
- {
- $domain = $this->domainRepository->find($id);
- if (!$domain) {
- return response()->json(['success' => false, 'message' => "Không tìm thấy domain với ID: {$id}"]);
- }
-
- try {
- if ($domain->is_created !== 1 || !$domain->plesk_id) {
- $this->domainRepository->delete($domain->id);
- return response()->json([
- 'success' => true,
- 'message' => "Domain '{$domain->domain_name}' đã được xóa khỏi database.",
- 'url' => route('admin.domain.index'),
- ]);
- }
-
- $server = $this->serverService->findServerByID($domain->dns_manager_server_id);
-
- if (!$server) {
- return response()->json(['success' => false, 'message' => "Không tìm thấy server với ID: {$domain->dns_manager_server_id}"]);
- }
-
- $removed = $this->serverService->removeDomain($domain->plesk_id);
-
- if (!$removed) {
- return response()->json(['success' => false, 'message' => "Không thể xóa domain '{$domain->domain_name}' trên server."]);
- }
-
- $this->domainRepository->delete($domain->id);
- $this->clearDomainCaches($domain);
-
- return response()->json([
- 'success' => true,
- 'message' => "Domain '{$domain->domain_name}' đã được xóa thành công khỏi server và database!",
- 'url' => route('admin.domain.index'),
- ]);
- } catch (Exception $error) {
- return response()->json(['success' => false, 'message' => 'Có lỗi xảy ra khi xóa domain: ' . $error->getMessage()]);
- }
- }
-
- public function destroyCustom(DomainRemoveRequest $request)
- {
- $request->validated();
-
- $domains = $this->domainRepository->findWhereIn('id', $request->get('ids'));
-
- if (!$domains || $domains->isEmpty()) {
- return response()->json(['success' => false, 'message' => 'Không tìm thấy domain nào.']);
- }
-
- $successCount = 0;
- $errorCount = 0;
- $errors = [];
-
- foreach ($domains as $domain) {
- try {
- if ($domain->is_created !== 1 || !$domain->plesk_id) {
- $this->domainRepository->delete($domain->id);
- $successCount++;
- continue;
- }
-
- $server = $this->serverService->findServerByID($domain->dns_manager_server_id);
-
- if (!$server) {
- $errors[] = "Domain '{$domain->domain_name}': Không tìm thấy server";
- $errorCount++;
- continue;
- }
-
- $removed = $this->serverService->removeDomain($domain->plesk_id);
-
- if (!$removed) {
- $errors[] = "Domain '{$domain->domain_name}': Không thể xóa trên server";
- $errorCount++;
- continue;
- }
-
- $this->domainRepository->delete($domain->id);
- $this->clearDomainCaches($domain);
- $successCount++;
- } catch (Exception $error) {
- $errors[] = "Domain '{$domain->domain_name}': " . $error->getMessage();
- $errorCount++;
- }
- }
-
- if ($successCount > 0 && $errorCount === 0) {
- $message = "Đã xóa thành công {$successCount} domain!";
- } elseif ($successCount > 0) {
- $message = "Đã xóa thành công {$successCount} domain, {$errorCount} domain gặp lỗi.";
- } else {
- $message = 'Không thể xóa domain nào. Vui lòng kiểm tra lại.';
- }
-
- return response()->json([
- 'success' => $errorCount === 0,
- 'message' => $message,
- 'details' => ['success_count' => $successCount, 'error_count' => $errorCount, 'errors' => $errors],
- 'url' => route('admin.domain.index'),
- ]);
- }
-
- private function domainActionButtons($domain): string
- {
- $editUrl = route('admin.domain.edit', ['id' => $domain->id]);
- $recordUrl = route('admin.domain.record', ['id' => $domain->id]);
- $deleteUrl = route('admin.domain.destroy', ['id' => $domain->id]);
-
- return <<
-
- Edit
-
-
-
- Records
-
-
-
- Delete
-
- HTML;
- }
-
- private function clearDomainCaches($domain): void
- {
- try {
- // Clear specific domain keys
- Cache::forget(CacheKey::build('domain', 'detail', ['id' => $domain->id]));
- Cache::forget(CacheKey::build('domain', 'detail', ['name' => $domain->domain_name]));
- Cache::forget(CacheKey::build('domain', 'all'));
-
- if ($domain->users) {
- foreach ($domain->users as $user) {
- Cache::forget(CacheKey::build('domain', 'user-list', ['user_id' => $user->id]));
- }
- }
-
- if ($domain->dns_manager_server_id) {
- Cache::forget(CacheKey::build('domain', 'server-list', ['server_id' => $domain->dns_manager_server_id]));
- }
- } catch (Exception $e) {
- Log::warning('Failed to clear domain caches: ' . $e->getMessage());
- }
- }
-}
diff --git a/app/Http/Controllers/Backend/RecordController.php b/app/Http/Controllers/Backend/RecordController.php
deleted file mode 100644
index a3f45a7..0000000
--- a/app/Http/Controllers/Backend/RecordController.php
+++ /dev/null
@@ -1,312 +0,0 @@
- [
- 'name' => 'required',
- 'type' => 'required',
- 'ttl' => 'required',
- 'value' => 'required|ip|ipv4',
- 'plesk_id' => 'nullable',
- ],
- 'AAAA' => [
- 'name' => 'required',
- 'type' => 'required',
- 'ttl' => 'required',
- 'value' => 'required|ip|ipv6',
- 'plesk_id' => 'nullable',
- ],
- 'CNAME' => [
- 'name' => 'required',
- 'type' => 'required',
- 'ttl' => 'required',
- 'value' => 'required',
- 'plesk_id' => 'nullable',
- ],
- 'MX' => [
- 'name' => 'required',
- 'type' => 'required',
- 'ttl' => 'required',
- 'value' => 'required',
- 'priority' => 'required',
- 'plesk_id' => 'nullable',
- ],
- 'TXT' => [
- 'name' => 'required',
- 'type' => 'required',
- 'ttl' => 'required',
- 'value' => 'required',
- 'plesk_id' => 'nullable',
- ],
- 'CAA' => [
- 'name' => 'required',
- 'type' => 'required',
- 'ttl' => 'required',
- 'value' => 'required',
- 'plesk_id' => 'nullable',
- ],
- 'SRV' => [
- 'type' => 'required',
- 'ttl' => 'required',
- 'value' => 'required',
- 'service_name' => 'required',
- 'protocol' => 'required',
- 'weight' => 'required',
- 'port' => 'required',
- 'priority' => 'required',
- 'plesk_id' => 'nullable',
- ],
- ];
-
- public function __construct(
- public DnsDomainRepositoryInterface $domainRepository,
- public DnsRecordRepositoryInterface $recordRepository,
- public ServerService $serverService
- ) {}
-
- public function index()
- {
- $title = 'All Records';
- return view('Backend.records.index', compact('title'));
- }
-
- public function getAll()
- {
- $records = $this->recordRepository->filter([], ['domain'])->map(function ($record) {
- return [
- 'id' => $record->id,
- 'name' => Str::limit($record->name, 20, '...'),
- 'type' => $record->type,
- 'value' => Str::limit($record->value, 20, '...'),
- 'domain' => $record->domain ? $record->domain->domain_name : '',
- 'checkbox' => '',
- 'created_at' => Carbon::parse($record->created_at)->format('Y-m-d H:i:s'),
- 'action' => $this->recordActionButtons($record),
- ];
- });
-
- return response()->json(['data' => $records]);
- }
-
- public function create(int $id)
- {
- $title = 'Add Records';
- return view('Backend.records.create', compact('title', 'id'));
- }
-
- public function store(Request $request, int $id = 0)
- {
- $domain = $this->domainRepository->find($id);
- if (empty($domain)) {
- return response()->json(['success' => false, 'message' => 'These credentials do not match our records.']);
- }
-
- $validated = Validator::make($request->all(), $this->rules[$request->get('type')] ?? []);
-
- if ($validated->fails()) {
- return response()->json(['success' => false, 'message' => $validated->errors()->first()]);
- }
-
- $request->merge(['dns_manager_domain_id' => $id, 'domain' => $domain->domain_name]);
-
- if (empty($request->get('plesk_id'))) {
- $this->serverService->findServerByID($domain->dns_manager_server_id);
- $this->serverService->handlePayload($request->all());
- $record = $this->serverService->addRecord();
-
- if (!empty($record) && $record['error']) {
- return response()->json(['success' => false, 'message' => $record['message'] ?? 'Failed to add record on server.']);
- }
- }
-
- DB::beginTransaction();
- try {
- if (isset($record)) {
- $request->merge(['plesk_id' => $record['data']['id'] ?? 0]);
- }
-
- $this->recordRepository->create($request->all());
- DB::commit();
- } catch (Exception $error) {
- DB::rollBack();
- return response()->json(['success' => false, 'message' => $error->getMessage()]);
- }
-
- return response()->json([
- 'success' => true,
- 'message' => 'Add record successfully',
- 'url' => route('admin.domain.record', ['id' => $id]),
- ]);
- }
-
- public function edit(int $id = 0, int $record_id = 0)
- {
- $domain = $this->domainRepository->find($id);
- if (empty($domain)) {
- return back()->withErrors('These credentials do not match our records.');
- }
-
- $record = $this->recordRepository->find($record_id);
- if (empty($record)) {
- return back()->withErrors('These credentials do not match our records.');
- }
-
- $title = 'Edit Record';
- return view('Backend.records.edit', compact('title', 'record', 'id'));
- }
-
- public function update(Request $request, int $id = 0, int $record_id = 0)
- {
- $domain = $this->domainRepository->find($id);
- if (empty($domain)) {
- return response()->json(['success' => false, 'message' => 'These credentials do not match our records.']);
- }
-
- $record = $this->recordRepository->find($record_id);
- if (empty($record)) {
- return response()->json(['success' => false, 'message' => 'These credentials do not match our records.']);
- }
-
- $validated = Validator::make($request->all(), $this->rules[$request->get('type')] ?? []);
-
- if ($validated->fails()) {
- return response()->json(['success' => false, 'message' => $validated->errors()->first()]);
- }
-
- $this->serverService->findServerByID($domain->dns_manager_server_id);
- $this->serverService->handlePayload($request->all());
- $updatedRecord = $this->serverService->updateRecord($record->plesk_id);
-
- if (!empty($updatedRecord) && $updatedRecord['error']) {
- return response()->json(['success' => false, 'message' => $updatedRecord['message'] ?? 'Failed to update record on server.']);
- }
-
- DB::beginTransaction();
- try {
- $this->recordRepository->update($record_id, $request->all());
- DB::commit();
- } catch (Exception $error) {
- DB::rollBack();
- return response()->json(['success' => false, 'message' => $error->getMessage()]);
- }
-
- return response()->json([
- 'success' => true,
- 'message' => 'Update record successfully',
- 'url' => route('admin.domain.record', ['id' => $id]),
- ]);
- }
-
- public function destroy(int $id = 0)
- {
- $record = $this->recordRepository->findWithRelations($id, ['domain']);
- if (empty($record) || empty($record->domain)) {
- return response()->json(['success' => false, 'message' => 'These credentials do not match our records.']);
- }
-
- $this->serverService->findServerByID($record->domain->dns_manager_server_id);
- $record_delete = $this->serverService->deleteRecord($record->plesk_id);
-
- if (!empty($record_delete) && $record_delete['error']) {
- return response()->json(['success' => false, 'message' => $record_delete['message'] ?? 'Failed to delete record on server.']);
- }
-
- DB::beginTransaction();
- try {
- $domainId = $record->dns_manager_domain_id;
- $this->recordRepository->delete($id);
- DB::commit();
- } catch (Exception $error) {
- DB::rollBack();
- return response()->json(['success' => false, 'message' => $error->getMessage()]);
- }
-
- return response()->json([
- 'success' => true,
- 'message' => 'Deleted record successfully',
- 'url' => route('admin.domain.record', ['id' => $domainId]),
- ]);
- }
-
- public function destroyCustom(RecordRemoveRequest $request)
- {
- $request->validated();
-
- $records = $this->recordRepository->findWhereIn('id', $request->get('ids'), ['domain']);
-
- if (!$records) {
- return response()->json(['success' => false, 'message' => 'These credentials do not match our records.']);
- }
-
- try {
- foreach ($records->all() as $record) {
- if (empty($record->domain)) continue;
-
- $this->serverService->findServerByID($record->domain->dns_manager_server_id);
- $this->serverService->deleteRecord($record->plesk_id);
- $this->recordRepository->delete($record->id);
- }
- } catch (Exception $error) {
- return response()->json(['success' => false, 'message' => $error->getMessage()]);
- }
-
- return response()->json([
- 'success' => true,
- 'message' => 'Deleted successfully!',
- 'url' => route('admin.record.index'),
- ]);
- }
-
- private function recordActionButtons($record): string
- {
- $domainId = $record->domain ? $record->domain->id : 0;
- $editUrl = route('admin.record.edit', ['id' => $domainId, 'record_id' => $record->id]);
- $deleteUrl = route('admin.record.destroy', ['id' => $record->id]);
-
- return <<
-
- Edit
-
-
-
- Delete
-
- HTML;
- }
-}
diff --git a/app/Http/Controllers/Backend/ServerController.php b/app/Http/Controllers/Backend/ServerController.php
deleted file mode 100644
index 9069468..0000000
--- a/app/Http/Controllers/Backend/ServerController.php
+++ /dev/null
@@ -1,204 +0,0 @@
-serverRepository->getAll()->map(function ($server) {
- return [
- 'id' => $server->id,
- 'name' => $server->name,
- 'ip' => $server->ip_address,
- 'status' => $server->getStatus(),
- 'checkbox' => '',
- 'created_at' => Carbon::parse($server->created_at)->format('Y-m-d H:i:s'),
- 'action' => $this->serverActionButtons($server),
- ];
- });
-
- return response()->json(['data' => $servers]);
- }
-
- public function create()
- {
- $title = 'Add New Server Hosting';
- return view('Backend.servers.create', compact('title'));
- }
-
- public function connect(ServerRequest $request)
- {
- $request->validated();
-
- if (!$request->ajax()) {
- return response()->json(['success' => false, 'message' => 'Method not support']);
- }
-
- $status = $this->serverService->checkConnection($request->all());
-
- if ($status) {
- return response()->json(['success' => true, 'message' => 'Connection established successfully.']);
- }
-
- return response()->json([
- 'success' => false,
- 'message' => 'Connection attempt failed. Please check your credentials or server settings.',
- ]);
- }
-
- public function store(ServerRequest $request)
- {
- $request->validated();
-
- DB::beginTransaction();
- try {
- $data = $request->all();
- $data['password'] = Crypt::encryptString($request->password);
-
- $this->serverRepository->create($data);
- DB::commit();
- } catch (Exception $error) {
- DB::rollBack();
- return response()->json(['success' => false, 'message' => $error->getMessage()]);
- }
-
- return response()->json([
- 'success' => true,
- 'message' => 'Created successfully!',
- 'url' => route('admin.server.index'),
- ]);
- }
-
- public function edit(int $id)
- {
- $server = $this->serverRepository->find($id);
- if (!$server) {
- return back()->withErrors('Invalid Request');
- }
-
- $title = 'Update Server Hosting';
- return view('Backend.servers.edit', compact('title', 'server'));
- }
-
- public function update(ServerRequest $request, int $id)
- {
- $request->validated();
-
- DB::beginTransaction();
- try {
- $data = $request->all();
- $data['password'] = Crypt::encryptString($request->password);
-
- $this->serverRepository->update($id, $data);
- DB::commit();
- } catch (Exception $error) {
- DB::rollBack();
- return response()->json(['success' => false, 'message' => $error->getMessage()]);
- }
-
- return response()->json([
- 'success' => true,
- 'message' => 'Updated successfully!',
- 'url' => route('admin.server.index'),
- ]);
- }
-
- public function destroy(int $id)
- {
- $server = $this->serverRepository->find($id);
- if (!$server) {
- return response()->json(['success' => false, 'message' => 'Invalid Request']);
- }
-
- $this->serverRepository->delete($id);
-
- return response()->json([
- 'success' => true,
- 'message' => 'Deleted successfully!',
- 'url' => route('admin.server.index'),
- ]);
- }
-
- public function destroyCustom(ServerRemoveRequest $request)
- {
- $request->validated();
-
- $servers = $this->serverRepository->findWhereIn('id', $request->get('ids'));
-
- if (!$servers) {
- return response()->json(['success' => false, 'message' => 'Invalid Request']);
- }
-
- foreach ($servers->all() as $server) {
- $this->serverRepository->delete($server->id);
- }
-
- return response()->json([
- 'success' => true,
- 'message' => 'Deleted successfully!',
- 'url' => route('admin.server.index'),
- ]);
- }
-
- private function serverActionButtons($server): string
- {
- $editUrl = route('admin.server.edit', ['id' => $server->id]);
- $deleteUrl = route('admin.server.destroy', ['id' => $server->id]);
-
- return <<
-
- Edit
-
-
-
- Delete
-
- HTML;
- }
-}
diff --git a/app/Http/Controllers/rest/ModuleController.php b/app/Http/Controllers/rest/ModuleController.php
deleted file mode 100644
index 96c7d6e..0000000
--- a/app/Http/Controllers/rest/ModuleController.php
+++ /dev/null
@@ -1,256 +0,0 @@
-json(
- [
- 'error' => false,
- 'data' => $this->vCenterRepository->filter(['status' => 1])
- ]
- );
- }
-
- public function getDatacenter(Request $request)
- {
- $vCenter_id = $request->get('vcenter');
- if (!$vCenter_id) {
- return response()->json(['error' => true, 'message' => 'Please choose vCenter']);
- }
- $vcenter = $this->vCenterRepository->find($vCenter_id);
-
- if (!$vcenter) {
- return response()->json(['error' => true, 'message' => 'Can\'t find vCenter']);
- }
-
- $datacenter = $this->datacenterRepository->filter(
- [
- 'vcenter_id' => $vCenter_id
- ],
- [],
- [],
- ['id', 'name', 'datacenter_id']
- );
-
- if (count($datacenter) > 0) {
- return response()->json(['error' => false, 'data' => $datacenter]);
- }
-
- return response()->json([
- 'error' => true,
- 'message' => 'Can\'t find Datacenter by vCenter ID #' . $vCenter_id
- ]);
- }
-
- public function getHost(Request $request)
- {
- $dataCenter_id = $request->get('datacenter');
- if (!$dataCenter_id) {
- return response()->json(['error' => true, 'message' => 'Please choose Datacenter']);
- }
- $datacenter = $this->datacenterRepository->find($dataCenter_id);
- if (!$datacenter) {
- return response()->json(['error' => true, 'message' => 'Can\'t find Datacenter']);
- }
-
- $host = $this->hostRepository->filter(
- [
- 'module_datacenter_id' => $datacenter->id
- ],
- [],
- [],
- ['id', 'name']
- );
-
- if (count($host) > 0) {
- return response()->json(['error' => false, 'data' => $host]);
- }
-
- return response()->json([
- 'error' => true,
- 'message' => 'Can\'t find Host by Datacenter ID #' . $dataCenter_id
- ]);
- }
-
- public function getDatastore(Request $request)
- {
- $host_id = $request->get('host');
- if (!$host_id) {
- return response()->json(['error' => true, 'message' => 'Please choose Host']);
- }
- $host = $this->hostRepository->find($host_id);
-
- if (!$host) {
- return response()->json(['error' => true, 'message' => 'Can\'t find Datacenter']);
- }
-
- $datastores = $this->datastoreRepository->filter(
- [
- 'module_host_id' => $host_id
- ],
- [],
- [],
- ['id', 'name']
- );
- if (count($datastores) > 0) {
- return response()->json(['error' => false, 'data' => $datastores]);
- }
- return response()->json([
- 'error' => true,
- 'message' => 'Can\'t find Datastore by Host ID #' . $host_id
- ]);
- }
-
- public function getNetwork(Request $request)
- {
- $host_id = $request->get('host');
- if (!$host_id) {
- return response()->json(['error' => true, 'message' => 'Please choose Host']);
- }
- $host = $this->hostRepository->find($host_id);
-
- if (!$host) {
- return response()->json(['error' => true, 'message' => 'Can\'t find Datacenter']);
- }
-
-
- $networks = $this->networkRepository->filter(
- [
- 'module_host_id' => $host_id
- ],
- [],
- [],
- ['id', 'name']
- );
- if (count($networks) > 0) {
- return response()->json(['error' => false, 'data' => $networks]);
- }
- return response()->json(['error' => true, 'message' => 'Can\'t find Network by Host ID #' . $host_id]);
- }
-
- public function getIpAddress(Request $request)
- {
- $network_id = $request->get('network');
- if (!$network_id) {
- return response()->json(['error' => true, 'message' => 'Please choose Network']);
- }
-
- $network = $this->networkRepository->find($network_id);
-
- if (!$network) {
- return response()->json(['error' => true, 'message' => 'Can\'t find Network']);
- }
-
- $ipAddress = $this->ipAddressRepository->filter(
- [
- 'module_network_id' => $network_id
- ],
- [],
- [],
- ['id', 'ip']
- );
- if (count($ipAddress) > 0) {
- return response()->json(
- [
- 'error' => false,
- 'data' => $ipAddress->map(function ($item) {
- $item['name'] = $item['ip'];
- return $item;
- })
- ]
- );
- }
- return response()->json(['error' => true, 'message' => 'Can\'t find IP by Network ID #' . $network_id]);
- }
-
- public function getOsServerVersion(Request $request)
- {
- $host_id = $request->get('host');
- if (!$host_id) {
- return response()->json(['error' => true, 'message' => 'Please choose Host']);
- }
-
- $host = $this->hostRepository->findWithRelationships($host_id, ['datacenter.vCenter']);
- if (!$host) {
- return response()->json(['error' => true, 'message' => 'Can\'t find Datastore']);
- }
- $this->vCenter->token = $host->Datacenter->vCenter->token ?? '';
- $this->vCenter->apiUri = $host->Datacenter->vCenter->host ?? '';
-
- $listVersion = $this->vCenter->getAllVersion($host->template);
-
- if ($listVersion === "Login_Again") {
- $token = $this->vCenter->loginServer($host->datacenter->vCenter);
- if ($token) {
- $this->vCenterRepository->update($host->datacenter->vcenter_id, ['token' => $token]);
- $this->vCenter->token = $token;
- }
- }
- $listVersion = $this->vCenter->getAllVersion($host->template);
- if ($listVersion) {
- return response()->json(['error' => false, 'data' => $listVersion]);
- }
- return response()->json([
- 'error' => true,
- 'message' => 'Can\'t find Version OS by Host ID #' . $host_id
- ]);
- }
-
- public function getOsGroup(Request $request)
- {
- $host_id = $request->get('host');
-
- if (!$host_id) {
- return response()->json(['error' => true, 'message' => 'Please choose Host']);
- }
-
- $host = $this->hostRepository->find($host_id);
-
- if (!$host) {
- return response()->json(['error' => true, 'message' => 'Can\'t find Host']);
- }
- $groups = $this->osGroupRepository->filter(
- [
- 'module_host_id' => $host_id
- ],
- ['host'], // Include host relationship
- [],
- ['id', 'name', 'module_host_id'] // Add module_host_id to select
- );
- if (count($groups) > 0) {
- return response()->json(['error' => false, 'data' => $groups]);
- }
- return response()->json([
- 'error' => true,
- 'message' => 'Can\'t find Os Group by Host ID #' . $host_id
- ]);
- }
-}
diff --git a/app/Http/Resources/Api/Service/VpsServiceResource.php b/app/Http/Resources/Api/Service/VpsServiceResource.php
index 0a8a528..8de3ab8 100644
--- a/app/Http/Resources/Api/Service/VpsServiceResource.php
+++ b/app/Http/Resources/Api/Service/VpsServiceResource.php
@@ -15,12 +15,18 @@ class VpsServiceResource extends JsonResource
public function toArray(Request $request): array
{
$orderItem = $this->orderItem;
+ $invoice = $this->invoice;
+ $order = $orderItem?->order ?? $invoice?->order;
$product = $orderItem?->product;
return [
'id' => $this->id,
'uuid' => $this->uuid,
'server_name' => $this->server_name,
+ 'invoice_id' => $this->invoice_id,
+ 'invoice_num' => $invoice?->invoice_number,
+ 'order_item_id' => $this->order_item_id,
+ 'order_num' => $order?->order_num,
'status' => $this->status,
'status_label' => $this->status_label,
'auto_renew' => (bool) $this->auto_renew,
diff --git a/app/Http/Services/vCenter.php b/app/Http/Services/vCenter.php
index 0659c22..8330054 100644
--- a/app/Http/Services/vCenter.php
+++ b/app/Http/Services/vCenter.php
@@ -3,17 +3,17 @@
namespace App\Http\Services;
use Illuminate\Support\Facades\Log;
-use Illuminate\Support\Facades\Cache;
-use App\Models\ModuleFolder;
-use Carbon\Carbon;
-use Exception;
-use Illuminate\Support\Facades\Http;
-use Illuminate\Http\Client\ConnectionException;
-use App\Http\Facade\SignRSA;
-use App\Models\InfraManagement;
-use Illuminate\Support\Str;
-use phpseclib3\Net\SSH2;
+use App\Http\Services\vCenter\Client;
+use App\Http\Services\vCenter\PowerManagementService;
+use App\Http\Services\vCenter\VirtualMachineService;
+use App\Http\Services\vCenter\SnapshotService;
+use App\Http\Services\vCenter\ResourceService;
+use App\Http\Services\vCenter\TemplateService;
+use App\Http\Services\vCenter\VMConfigurationService;
+use App\Http\Services\vCenter\PowerShellService;
use App\Http\Services\vCenter\Concerns\ManagesSsh;
+use Exception;
+use phpseclib3\Net\SSH2;
class vCenter
{
@@ -25,2457 +25,385 @@ class vCenter
private string $password;
private int $diskCapacity = 1073741824;
private int $memory = 1024;
-
private ?string $os = null;
- private SSH2|WinRMClient|null $process = null;
+ private SSH2|null $process = null;
+
+ private Client $client;
+ private PowerManagementService $power;
+ private VirtualMachineService $vm;
+ private SnapshotService $snapshot;
+ private ResourceService $resource;
+ private TemplateService $template;
+ private VMConfigurationService $vmConfig;
+ private PowerShellService $shell;
+
+ public function __construct()
+ {
+ $this->client = new Client();
+ $this->shell = new PowerShellService($this->client);
+ $this->power = new PowerManagementService($this->client);
+ $this->vm = new VirtualMachineService($this->client, $this->shell);
+ $this->snapshot = new SnapshotService($this->client, $this->shell);
+ $this->resource = new ResourceService($this->client);
+ $this->template = new TemplateService($this->client);
+ $this->vmConfig = new VMConfigurationService($this->client, $this->shell);
+ }
+
+ // ─── Auth & Setup ────────────────────────────────────────────────────────
public function loginServer(\App\Models\Vcenter $vcenterModel): bool|string
{
- $this->apiUri = $vcenterModel->host;
- $this->username = $vcenterModel->user;
- $this->password = SignRSA::DecryptFunc($vcenterModel->password); // nếu đã mã hóa
-
- // Log::info('Đăng nhập với user: ' . $this->username);
-
- $response = Http::withoutVerifying()
- ->withBasicAuth($this->username, $this->password)
- ->post("https://{$this->apiUri}/rest/com/vmware/cis/session", []);
-
- // Log::info('Response login: ' . $response->body());
-
- if ($response->successful()) {
- $json = $response->json();
- if (isset($json['value'])) {
- $this->token = $json['value'];
- $vcenterModel->update(['token' => $this->token]);
- // Log::info('token', ['token' => $this->token]);
- return $this->token;
- }
-
- $body = $response->body();
- if (!empty($body)) {
- $this->token = $body;
- $vcenterModel->update(['token' => $this->token]);
- // Log::info('token raw string', ['token' => $this->token]);
- return $this->token;
- }
- }
-
- return false;
- }
-
- public function getAllDatacenter()
- {
- $check = $this->checkApiExits();
- if ($check) {
- $response = Http::withoutVerifying()
- ->withHeaders([
- 'vmware-api-session-id' => $this->token
- ])
- ->get("https://$this->apiUri/rest/vcenter/datacenter")->json();
- if (!isset($response['type'])) {
- return $response['value'];
- }
- return false;
- }
- return "Login_Again";
- }
-
- public function getAllVirtualMachine($datacenter = '', $host = '')
- {
- $check = $this->checkApiExits();
- if ($check) {
- $params = [];
- if (!empty($datacenter)) {
- $params['filter.datacenters'] = $datacenter;
- }
- if (!empty($host)) {
- $params['filter.hosts.1'] = $host;
- }
-
- $response = Http::withoutVerifying()
- ->withHeaders([
- 'vmware-api-session-id' => $this->token
- ])
- ->get("https://$this->apiUri/rest/vcenter/vm", $params)->json();
-
- if (!isset($response['type'])) {
- return $response['value'];
- }
- return false;
- }
- return "Login_Again";
- }
-
- /**
- * Search VM by exact name in vCenter.
- * Returns the first matching VM array or null if not found.
- */
- public function findVirtualMachineByName(string $vmName): ?array
- {
- $check = $this->checkApiExits();
- if (!$check) {
- Log::warning('findVirtualMachineByName: API check failed');
- return null;
- }
-
- $response = Http::withoutVerifying()
- ->withHeaders([
- 'vmware-api-session-id' => $this->token
- ])
- ->get("https://$this->apiUri/rest/vcenter/vm", [
- 'filter.names.1' => $vmName,
- ]);
-
- if (!$response->successful()) {
- Log::warning('findVirtualMachineByName: HTTP failed', ['status' => $response->status()]);
- return null;
- }
-
- $json = $response->json();
- if (isset($json['type']) || empty($json['value'])) {
- return null;
- }
-
- return $json['value'][0] ?? null;
+ $result = $this->client->loginServer($vcenterModel);
+ $this->token = $this->client->token;
+ $this->apiUri = $this->client->apiUri;
+ $this->username = $this->client->getUsername();
+ $this->password = $this->client->getPassword();
+ return $result;
}
public function checkApiExits(): bool
{
-
- $response = Http::withoutVerifying()
- ->withHeaders([
- 'vmware-api-session-id' => $this->token
- ])
- ->get("https://$this->apiUri/rest/com/vmware/content/library")->json();
-
- if (isset($response['type'])) {
- return false;
- }
- return true;
+ return $this->client->checkApiExits();
}
- /**
- * Lấy thông tin vCenter từ product (ModuleVmware) qua nhiều cách:
- * 1. Direct relation qua vcenter_id
- * 2. Qua ipAddress → host → datacenter → vCenter
- * 3. Qua Service relation (nếu Service có vcenter_id)
- *
- * @param \App\Models\ModuleVmware $product
- * @return \App\Models\vCenter|null
- */
- public function resolveVCenterFromProduct(\App\Models\ModuleVmware $product): ?\App\Models\vCenter
+ public function setConnection(string $apiUri, string $token): static
{
- // Cách 1: Ưu tiên quan hệ trực tiếp trên ModuleVmware
- if ($product->vCenter) {
- Log::channel('Custom')->debug('resolveVCenter: Found via direct vCenter relation', ['vcenter_id' => $product->vCenter->id]);
- return $product->vCenter;
- }
-
- // Cách 2: Fallback qua ipAddress chain
- if ($product->ipAddress?->host?->datacenter?->vCenter) {
- Log::channel('Custom')->debug('resolveVCenter: Found via ipAddress chain');
- return $product->ipAddress->host->datacenter->vCenter;
- }
-
- // Cách 3: Qua Service relation (nếu Service có vcenter_id)
- // Lấy service đầu tiên liên kết với VM này
- $service = $product->services()->first();
- if ($service) {
- // Load vmware relation với vCenter
- $service->load('vmware.vCenter');
- if ($service->vmware?->vCenter) {
- Log::channel('Custom')->debug('resolveVCenter: Found via Service→Vmware→vCenter');
- return $service->vmware->vCenter;
- }
- }
-
- Log::channel('Custom')->warning('resolveVCenter: Could not find vCenter for product', [
- 'product_id' => $product->id,
- 'vmware_id' => $product->vmware_id,
- ]);
-
- return null;
- }
-
- public function getAllHost($datacenter = '')
- {
- $check = $this->checkApiExits();
- if ($check) {
- $response = Http::withoutVerifying()
- ->withHeaders([
- 'vmware-api-session-id' => $this->token
- ])
- ->get("https://$this->apiUri/rest/vcenter/host", [
- 'filter.datacenters.1' => $datacenter
- ])->json();
- if (!isset($response['type'])) {
- return $response['value'];
- }
- return false;
- }
- return "Login_Again";
- }
-
- public function getAllFolder($datacenter = '')
- {
- $check = $this->checkApiExits();
- if ($check) {
- $response = Http::withoutVerifying()
- ->withHeaders([
- 'vmware-api-session-id' => $this->token
- ])
- ->get("https://$this->apiUri/rest/vcenter/folder", [
- 'filter.datacenters.1' => $datacenter,
- 'filter.type' => "VIRTUAL_MACHINE",
- ])->json();
- if (!isset($response['type'])) {
- return $response['value'];
- }
- return false;
- }
- return "Login_Again";
- }
-
- public function getTemplateFromDatastore($datastore = '')
- {
-
- $allTemplate = $this->getAllTemplate();
- if (!$allTemplate) {
- return false;
- }
- foreach ($allTemplate as $template) {
-
- $detailTemplate = $this->getTemplatebyID($template);
- if ($detailTemplate && $detailTemplate === $datastore) {
- return $template;
- }
- }
- return false;
- }
-
- public function getAllTemplate()
- {
- $check = $this->checkApiExits();
- if ($check) {
- $response = Http::withoutVerifying()
- ->withHeaders([
- 'vmware-api-session-id' => $this->token
- ])
- ->get("https://$this->apiUri/rest/com/vmware/content/library")->json();
- if (!isset($response['type'])) {
- return $response['value'];
- }
- return false;
- }
- return "Login_Again";
- }
-
- protected function getTemplatebyID($template = '')
- {
- $response = Http::withoutVerifying()
- ->withHeaders([
- 'vmware-api-session-id' => $this->token
- ])
- ->get("https://$this->apiUri/rest/com/vmware/content/library/id:$template")->json();
- if (!isset($response['type'])) {
- return $response['value']['storage_backings'][0]['datastore_id'];
- }
- return false;
- }
-
- public function getAllVersion($library = ''): bool|array|string
- {
- $check = $this->checkApiExits();
- if ($check) {
- $response = Http::withoutVerifying()
- ->withHeaders([
- 'vmware-api-session-id' => $this->token
- ])
- ->get("https://$this->apiUri/rest/com/vmware/content/library/item", [
- 'library_id' => $library
- ])->json();
- if (!isset($response['type'])) {
- $library_item = [];
- foreach ($response['value'] as $value) {
- $value_detail = $this->getVersionDetail($value);
- if ($value_detail) {
- $library_item[] = [
- 'name' => $value_detail['name'],
- 'id' => $value_detail['id']
- ];
- }
- }
- return $library_item;
- }
- return false;
- }
- return "Login_Again";
- }
-
- protected function getVersionDetail($version_id = '')
- {
- $response = Http::withoutVerifying()
- ->withHeaders([
- 'vmware-api-session-id' => $this->token
- ])
- ->get("https://$this->apiUri/rest/com/vmware/content/library/item/id:$version_id")->json();
- if (!isset($response['type'])) {
- return $response['value'];
- }
- return false;
- }
-
- public function loadNetWork($data = [], $host_id = '')
- {
- $this->username = $data->user;
- $this->password = SignRSA::DecryptFunc($data->password);
- $this->apiUri = $data->host;
-
- // Pass the host_id directly to the API call
- $allNetwork = $this->getAllNetwork($host_id);
-
- return $allNetwork;
- }
-
- private function getAllNetwork($host = '')
- {
- $check = $this->checkApiExits();
- if ($check) {
-
- $params = [];
- if (!empty($host)) {
- $params['filter.hosts'] = $host;
- }
-
- $response = Http::withoutVerifying()
- ->withHeaders([
- 'vmware-api-session-id' => $this->token
- ])
- ->get("https://$this->apiUri/rest/vcenter/network", $params)->json();
-
- if (!isset($response['type'])) {
- return $response['value'];
- }
- return false;
- }
- return "Login_Again";
- }
-
- public function getNetwork($host = ''): array|bool|string
- {
- return $this->getAllNetwork($host);
- }
-
- // Moved to App\Http\Services\vCenter\Concerns\ManagesSsh
-
- public function resetGuestPasswordViaPs1(string $vmName, string $guestUser, string $guestPassword, string $newPassword, string $targetUser = 'Administrator'): array|string
- {
- // File ps1 chạy được ngoài PowerShell: bạn đã test ok
- $psFile = "C:\\CLISave\\AssignIP\\SetGuestPwd.ps1";
-
- // Escape chuỗi cho PowerShell single-quoted
- $q = fn(string $v) => str_replace("'", "''", $v);
-
- // Xây args kiểu: -Key 'Value'
- $args = " -VMName '" . $q($vmName) . "'";
- $args .= " -GuestUser '" . $q($guestUser) . "'";
- $args .= " -GuestPassword '" . $q($guestPassword) . "'";
- $args .= " -TargetUser '" . $q($targetUser) . "'";
- $args .= " -NewPassword '" . $q($newPassword) . "'";
-
- // Gọi file trong cùng phiên PS đã Connect-VIServer (nhờ Command())
- // Thêm Out-String để ép JSON nằm trên STDOUT gọn
- $cmd = "& '$psFile'$args | Out-String";
-
- $raw = $this->CommandRSP($cmd); // <-- dùng lại Command() của bạn
- if ($raw === 'Login_Fail' || $raw === false || $raw === null) {
- return "Login_Fail";
- }
-
- // Thử parse JSON (có thể lẫn banner/log)
- $trim = trim($raw);
- $data = json_decode($trim, true);
- if (json_last_error() !== JSON_ERROR_NONE) {
- if (preg_match('/(\{.*\}|\[.*\])/s', $trim, $m)) {
- $data = json_decode($m[1], true);
- }
- }
-
- return $data ?: $raw; // nếu chưa parse được, trả raw để debug
- }
-
-
- public function changeRDPPortWindows(string $vmName, string $guestUser, string $guestPassword, string $newPort): array|string
- {
- $psFile = "C:\\CLISave\\AssignIP\\change-rdp-port.ps1";
- $q = fn(string $v) => str_replace("'", "''", $v);
-
- // Dùng -VMName (string). Alias -VMNames cũng ok, nhưng mình set đúng tên tham số.
- $args = " -VMName '" . $q($vmName) . "'";
- $args .= " -GuestUser '" . $q($guestUser) . "'";
- $args .= " -GuestPassword '" . $q($guestPassword) . "'"; // khớp tên tham số trong script 1-VM
- $args .= " -NewPort " . (int) $newPort;
-
- $cmd = "& '$psFile'$args";
-
- // Nếu đã có phiên bản CommandRSP($cmd, bool $expectJson) → bật expectJson để không trộn STDERR
- $raw = method_exists($this, 'CommandRSP')
- ? ($this->CommandChangePort($cmd, true) ?? '')
- : '';
-
- if ($raw === 'Login_Fail' || $raw === false || $raw === null) {
- return "Login_Fail";
- }
- $raw = trim($raw);
-
- // CẮT LẤY KHỐI JSON CUỐI (object hoặc array) nếu có noise
- if (!preg_match('~(\{(?:[^{}]|(?1))*\}|\[(?:[^\[\]]|(?0))*\])\s*$~s', $raw, $m)) {
- // fallback đơn giản hơn
- if (preg_match('~(\{.*\}|\[.*\])\s*$~s', $raw, $m)) {
- $raw = $m[1];
- }
- } else {
- $raw = $m[1];
- }
-
- $data = json_decode($raw, true);
- if (json_last_error() !== JSON_ERROR_NONE) {
- return [
- 'success' => false,
- 'error' => 'JSON parsing failed',
- 'raw_output' => $raw,
- 'json_error' => json_last_error_msg()
- ];
- }
-
- // Script 1-VM trả về 1 object, nhưng nếu lỡ trả mảng thì lấy phần tử đầu
- if (isset($data[0])) {
- $data = $data[0];
- }
-
- return $data;
- }
-
- /**
- * Change IP address of a VM via existing PowerShell scripts.
- *
- * Linux → C:\CLISave\changeIPLinux.ps1
- * Params: -VMName -IP -Gateway -DNS -GuestUser -GuestPassword
- * Uses: Get-VM -Name $VMName + Invoke-VMScript (requires guest creds)
- * Returns JSON → parsed via CommandChangePort($cmd, true)
- *
- * Windows → C:\CLISave\changeWindows1line.ps1 (legacy inline variable pattern)
- *
- * loginServer() MUST be called before this so $this->apiUri / username /
- * password are set for the Connect-VIServer call inside CommandChangePort.
- */
- public function changeIpVM(
- string $vmName, // VM display name used with Get-VM -Name
- string $vmwareId, // VMware ID (used for Windows -Id pattern)
- string $ipAddress,
- string $gateway,
- string $osType,
- string $guestUser = '',
- string $guestPassword = '',
- string $dns = '8.8.8.8',
- string $hostname = ''
- ): string {
- $q = fn(string $v) => str_replace("'", "''", $v);
-
- $isLinux = str_contains(strtolower($osType), 'linux')
- || str_contains(strtolower($osType), 'ubuntu')
- || str_contains(strtolower($osType), 'centos')
- || str_contains(strtolower($osType), 'rocky');
-
- if ($isLinux) {
- // changeIPLinux.ps1 param(VMName, IP, Gateway, DNS, GuestUser, GuestPassword)
- $psFile = "C:\\CLISave\\changeIPLinux.ps1";
- $Command = "& '{$psFile}'"
- . " -VMName '" . $q($vmName) . "'"
- . " -IP '" . $q($ipAddress) . "'"
- . " -Gateway '" . $q($gateway) . "'"
- . " -DNS '" . $q($dns) . "'"
- . " -GuestUser '" . $q($guestUser) . "'"
- . " -GuestPassword '" . $q($guestPassword) . "'";
-
- Log::channel('Custom')->info('[changeIpVM:linux] ' . $Command);
-
- // Script returns JSON → use CommandChangePort with expectJson=true
- return (string) $this->CommandChangePort($Command, true);
- }
-
- // Windows: legacy inline variable pattern → changeWindows1line.ps1
- $VirtualMachine = "VirtualMachine-$vmwareId";
- $hn = strtolower($hostname ?: $vmName);
- $Command = "\$vm = get-vm -Id '{$VirtualMachine}';"
- . " \$hostname = '{$q($hn)}';"
- . " \$ipadd = '{$q($ipAddress)}';"
- . " \$gw = '{$q($gateway)}';"
- . " \$password = \"'{$q($guestPassword)}'\"; "
- . " cd /; C:\\CLISave\\changeWindows1line.ps1;";
-
- Log::channel('Custom')->info('[changeIpVM:windows] ' . $Command);
-
- return (string) $this->Command($Command);
- }
-
- public function loadDatastore($data = [], $host_id = '')
- {
- Log::info('[vCenter] loadDatastore()', [
- 'host' => $data->host,
- 'esxi_host' => $host_id
- ]);
-
- $allDatastore = $this->getAllDataStore($host_id);
-
- if ($allDatastore === "Login_Again") {
- Log::warning('[vCenter] Token expired when getAllDataStore');
- return "Login_Again";
- }
-
- if (!$allDatastore) {
- Log::warning('[vCenter] getAllDataStore return false');
- return false;
- }
-
- Log::info('[vCenter] Datastore count from API', [
- 'count' => count($allDatastore)
- ]);
-
- return $allDatastore;
- }
-
-
- public function getAllDataStore($host = '')
- {
- Log::info('[vCenter] getAllDataStore call', [
- 'api' => $this->apiUri,
- 'host_filter' => $host
- ]);
-
- $check = $this->checkApiExits();
-
- if (!$check) {
- Log::warning('[vCenter] checkApiExits = false');
- return "Login_Again";
- }
-
- $params = [];
- if (!empty($host)) {
- $params['filter.hosts'] = $host;
- }
-
- $response = Http::withoutVerifying()
- ->withHeaders([
- 'vmware-api-session-id' => $this->token
- ])
- ->get("https://$this->apiUri/rest/vcenter/datastore", $params);
-
- Log::info('[vCenter] datastore raw response', [
- 'status' => $response->status(),
- // 'body' => $response->body() // Comment out to reduce log noise if large
- ]);
-
- $json = $response->json();
-
- if (!isset($json['type'])) {
- return $json['value'];
- }
-
- Log::warning('[vCenter] API returned error', [
- 'response' => $json
- ]);
-
- return false;
- }
-
-
- public function CreateVirtualMachine($product_data): array|string
- {
- $vcenterModel = $product_data['vcenter'];
-
- if (!$this->loginServer($vcenterModel)) {
- Log::error('Failed to create VM: API check failed, requires login.');
- return 'Login_Again';
- }
-
- $userName = customer()?->email;
-
- $item = $product_data['item'];
- $host = $product_data['host'];
- $datacenter = $product_data['datacenter'];
- $template = $product_data['template'];
-
- $addon = $product_data['addon'] ?? ['cpu' => 0, 'ram' => 0, 'disk' => 0];
-
- // Lấy cấu hình gốc từ item hoặc product
- $base_cpu = $item->cpu ?? ($host->cpu ?? 1);
- $base_ram = $item->ram ?? ($host->ram ?? 1);
- $base_disk = $item->disk ?? ($host->disk ?? 20);
-
- // Đảm bảo giá trị không bị null hoặc 0
- $base_cpu = max(1, (int) $base_cpu);
- $base_ram = max(1, (int) $base_ram);
- $base_disk = max(20, (int) $base_disk);
-
- // Lấy tất cả datastore liên quan host, không lấy first()
- $datastores = \App\Models\ModuleDatastore::where('module_host_id', $host->id)->get();
-
- // Nếu muốn chọn 1 datastore để truyền (ví dụ lấy cái đầu tiên)
- $datastoreId = $datastores->isNotEmpty()
- ? $datastores->first()->datastore
- : 'datastore-3004'; // fallback nếu không có
-
- $body = [
- 'spec' => [
- 'name' => $item->server_name ?? ($userName . '-vm-' . $item->id),
- 'source' => $template, // sử dụng template của host, thay 'vm-3007' cứng thành biến
- 'placement' => [
- 'datacenter' => $datacenter->datacenter_id,
- 'folder' => 'group-v1008', // hoặc lấy từ DB nếu cần
- 'host' => $host->host_id,
- 'resource_pool' => 'resgroup-3002', // nếu có thể lấy động thì tốt hơn
- 'datastore' => $datastoreId, // chuỗi datastore đúng định dạng
- ],
- 'power_on' => false,
- 'fallback_to_datastore_default_policy' => true,
- ],
- ];
-
- Log::info('Data payload', ['data' => $body]);
-
- try {
- Log::info('Bắt đầu clone VM với timeout 300 giây', ['template' => $template, 'target_name' => $body['spec']['name']]);
-
- $response = Http::withoutVerifying()
- ->timeout(300) // Tăng timeout lên 5 phút cho quá trình clone
- ->connectTimeout(30) // Giữ connect timeout ngắn để phát hiện lỗi kết nối sớm
- ->withHeaders([
- 'vmware-api-session-id' => $this->token,
- 'Content-Type' => 'application/json',
- ])
- ->post("https://{$this->apiUri}/rest/vcenter/vm?action=clone", $body);
-
- $responseData = $response->json();
-
- if ($response->successful() && !isset($responseData['type'])) {
- $vmId = $responseData['value'] ?? 'UNKNOWN';
-
- Log::info("VM clone thành công, VM ID: {$vmId}", [
- 'addon_data' => $addon,
- 'base_cpu' => $base_cpu,
- 'base_ram' => $base_ram,
- 'has_cpu_addon' => $addon['cpu'] > 0,
- 'has_ram_addon' => $addon['ram'] > 0,
- 'has_disk_addon' => $addon['disk'] > 0
- ]);
-
- // Cập nhật cấu hình phần cứng sau khi clone thành công
- // Thay đổi điều kiện: chạy nếu có bất kỳ addon nào > 0
- if ($vmId !== 'UNKNOWN' && ($addon['cpu'] > 0 || $addon['ram'] > 0 || $addon['disk'] > 0)) {
- Log::info("Bắt đầu cập nhật addon cho VM {$vmId}");
- $this->updateVMHardware($vmId, $addon, $base_cpu, $base_ram);
- } else {
- Log::info("Không có addon nào cần cập nhật cho VM {$vmId}", [
- 'vm_id' => $vmId,
- 'addon' => $addon,
- 'condition_check' => $vmId !== 'UNKNOWN' && ($addon['cpu'] > 0 || $addon['ram'] > 0 || $addon['disk'] > 0)
- ]);
- }
-
- // 🟢 Gửi thông báo Discord
- $discord = app(\App\Http\Services\Discord::class);
-
- // Thông tin addon
- $addonMsg = '';
- if (!empty($addon)) {
- $addonMsg = "\n**Addon:** " .
- ($addon['cpu'] ? "CPU +{$addon['cpu']} " : "") .
- ($addon['ram'] ? "RAM +{$addon['ram']}GB " : "") .
- ($addon['disk'] ? "Disk +{$addon['disk']}GB " : "");
- }
-
- $discord->SendMessage(
- message: "**User:** {$item->server_name}\n**VM ID:** `{$vmId}`{$addonMsg}\n**Thời gian:** " . now()->format('d/m/Y H:i:s'),
- color: Discord::COLOR_SUCCESS,
- title: '🎉 Tạo máy ảo thành công'
- );
-
- return ['error' => false, 'data' => $vmId];
- }
-
- return ['error' => true, 'data' => $responseData['value'] ?? $response->body()];
- } catch (\Exception $e) {
- Log::error('Exception when creating VM', ['message' => $e->getMessage()]);
- return ['error' => true, 'data' => $e->getMessage()];
- }
- }
-
- /**
- * Cập nhật cấu hình phần cứng VM sau khi clone
- */
- private function updateVMHardware($vmId, $addon, $base_cpu, $base_ram): void
- {
- try {
- Log::info("=== BẮT ĐẦU CẬP NHẬT ADDON ===");
- Log::info("Thông tin VM và Addon", [
- 'vm_id' => $vmId,
- 'addon' => $addon,
- 'base_cpu' => $base_cpu,
- 'base_ram' => $base_ram,
- 'addon_cpu' => $addon['cpu'],
- 'addon_ram' => $addon['ram'],
- 'addon_disk' => $addon['disk']
- ]);
-
- // Tính toán cấu hình mới
- $new_cpu = $base_cpu + $addon['cpu'];
- // Sửa: RAM đầu vào là GB, convert sang MiB
- $new_ram_mib = ($base_ram + $addon['ram']) * 1024; // 1 GB = 1024 MiB
-
- Log::info("Tính toán cấu hình mới", [
- 'vm_id' => $vmId,
- 'base_cpu' => $base_cpu,
- 'addon_cpu' => $addon['cpu'],
- 'new_cpu' => $new_cpu,
- 'base_ram_gb' => $base_ram,
- 'addon_ram_gb' => $addon['ram'],
- 'new_ram_mib' => $new_ram_mib
- ]);
-
- // Cập nhật CPU nếu có addon
- if ($addon['cpu'] > 0) {
- Log::info("🔄 Bắt đầu cập nhật CPU cho VM {$vmId}");
- $cpuResult = $this->updateVMCPU($vmId, $new_cpu);
- if ($cpuResult) {
- Log::info("✅ Cập nhật CPU thành công: {$base_cpu} → {$new_cpu} cores");
- } else {
- Log::error("❌ Cập nhật CPU thất bại cho VM {$vmId}");
- }
- } else {
- Log::info("⏭️ Bỏ qua cập nhật CPU (không có addon CPU)");
- }
-
- // Cập nhật RAM nếu có addon
- if ($addon['ram'] > 0) {
- Log::info("🔄 Bắt đầu cập nhật RAM cho VM {$vmId}");
- $ramResult = $this->updateVMMemory($vmId, $new_ram_mib);
- if ($ramResult) {
- Log::info("✅ Cập nhật RAM thành công: {$base_ram}GB → " . ($base_ram + $addon['ram']) . "GB ({$new_ram_mib}MiB)");
- } else {
- Log::error("❌ Cập nhật RAM thất bại cho VM {$vmId}");
- }
- } else {
- Log::info("⏭️ Bỏ qua cập nhật RAM (không có addon RAM)");
- }
-
- Log::info("=== HOÀN THÀNH CẬP NHẬT ADDON ===");
- } catch (\Exception $e) {
- Log::error("❌ Lỗi khi cập nhật cấu hình phần cứng VM {$vmId}: " . $e->getMessage());
- Log::error("Stack trace: " . $e->getTraceAsString());
- }
- }
-
- /**
- * Cập nhật CPU cho VM
- */
- private function updateVMCPU($vmId, $cpuCount): bool
- {
- try {
- Log::info("🔄 Gọi API cập nhật CPU", [
- 'vm_id' => $vmId,
- 'new_cpu_count' => $cpuCount
- ]);
-
- // Tính toán cores_per_socket dựa trên count
- // Đảm bảo count % cores_per_socket == 0
- $coresPerSocket = 1;
- if ($cpuCount > 1) {
- // Tìm ước số lớn nhất <= cpuCount/2 để tối ưu phân bổ socket
- $maxCoresPerSocket = floor($cpuCount / 2);
- for ($i = $maxCoresPerSocket; $i >= 1; $i--) {
- if ($cpuCount % $i === 0) {
- $coresPerSocket = $i;
- break;
- }
- }
- // Nếu không tìm được ước số phù hợp, sử dụng cpuCount
- if ($coresPerSocket === 1 && $cpuCount > 2) {
- $coresPerSocket = $cpuCount;
- }
- }
-
- $payload = [
- 'spec' => [
- 'count' => $cpuCount,
- 'cores_per_socket' => $coresPerSocket,
- 'hot_add_enabled' => true
- ]
- ];
- $response = Http::withoutVerifying()
- ->timeout(30)
- ->withHeaders([
- 'vmware-api-session-id' => $this->token,
- 'Content-Type' => 'application/json',
- ])
- ->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;
- } else {
- Log::error("❌ API call cập nhật CPU thất bại cho VM {$vmId}", [
- 'status' => $response->status(),
- 'response' => $response->body(),
- 'url' => "https://{$this->apiUri}/rest/vcenter/vm/{$vmId}/hardware/cpu"
- ]);
- return false;
- }
- } catch (\Exception $e) {
- Log::error("❌ Exception khi cập nhật CPU cho VM {$vmId}: " . $e->getMessage());
- Log::error("Stack trace: " . $e->getTraceAsString());
- return false;
- }
- }
-
- /**
- * Cập nhật RAM cho VM
- */
- private function updateVMMemory($vmId, $ramSizeMB): bool
- {
- try {
- Log::info("🔄 Gọi API cập nhật RAM", [
- 'vm_id' => $vmId,
- 'new_ram_mb' => $ramSizeMB
- ]);
-
- $payload = [
- 'spec' => [
- 'size_MiB' => $ramSizeMB
- ]
- ];
-
- $response = Http::withoutVerifying()
- ->timeout(30)
- ->withHeaders([
- 'vmware-api-session-id' => $this->token,
- 'Content-Type' => 'application/json',
- ])
- ->patch("https://{$this->apiUri}/rest/vcenter/vm/{$vmId}/hardware/memory", $payload);
-
- if ($response->successful()) {
- Log::info("✅ API call cập nhật RAM thành công cho VM {$vmId}");
- return true;
- } else {
- Log::error("❌ API call cập nhật RAM thất bại cho VM {$vmId}", [
- 'status' => $response->status(),
- 'response' => $response->body(),
- 'url' => "https://{$this->apiUri}/rest/vcenter/vm/{$vmId}/hardware/memory"
- ]);
- return false;
- }
- } catch (\Exception $e) {
- Log::error("❌ Exception khi cập nhật RAM cho VM {$vmId}: " . $e->getMessage());
- Log::error("Stack trace: " . $e->getTraceAsString());
- return false;
- }
- }
-
- /**
- * Cập nhật Disk cho VM (mở rộng disk)
- */
- private function updateVMDisk($vmId, $diskSizeGB): bool
- {
- try {
- // Tính toán dung lượng disk mới (GB sang bytes)
- $diskSizeBytes = $diskSizeGB * 1024 * 1024 * 1024; // GB to bytes
-
- $payload = [
- 'spec' => [
- 'capacity' => $diskSizeBytes
- ]
- ];
-
- // Lấy thông tin disk hiện tại để biết disk ID
- $diskResponse = Http::withoutVerifying()
- ->timeout(30)
- ->withHeaders([
- 'vmware-api-session-id' => $this->token,
- ])
- ->get("https://{$this->apiUri}/rest/vcenter/vm/{$vmId}/hardware/disk");
-
- if (!$diskResponse->successful()) {
- Log::error("Không thể lấy thông tin disk hiện tại cho VM {$vmId}");
- return false;
- }
-
- $diskData = $diskResponse->json();
- if (!isset($diskData['value']) || empty($diskData['value'])) {
- Log::error("Không tìm thấy disk nào cho VM {$vmId}");
- return false;
- }
-
- // Lấy disk đầu tiên (thường là disk chính)
- $firstDisk = $diskData['value'][0];
- $diskKey = $firstDisk['key'];
-
- $response = Http::withoutVerifying()
- ->timeout(30)
- ->withHeaders([
- 'vmware-api-session-id' => $this->token,
- 'Content-Type' => 'application/json',
- ])
- ->patch("https://{$this->apiUri}/rest/vcenter/vm/{$vmId}/hardware/disk/{$diskKey}", $payload);
-
- if ($response->successful()) {
- Log::info("API call cập nhật Disk thành công cho VM {$vmId}");
- return true;
- } else {
- Log::error("API call cập nhật Disk thất bại cho VM {$vmId}", [
- 'status' => $response->status(),
- 'response' => $response->body()
- ]);
- return false;
- }
- } catch (\Exception $e) {
- Log::error("Exception khi cập nhật Disk cho VM {$vmId}: " . $e->getMessage());
- return false;
- }
- }
-
- private function GetFolderByHost($datacenter = '')
- {
- return ModuleFolder::where('module_datacenter_id', $datacenter)->first();
- }
-
- public function CheckVirtualMachine($vmware_id = ''): string|array
- {
- $check = $this->checkApiExits();
-
- if ($check) {
- $response = Http::withoutVerifying()
- ->withHeaders([
- 'vmware-api-session-id' => $this->token
- ])
- ->get("https://$this->apiUri/rest/vcenter/vm/$vmware_id/guest/power")->json();
- if (!isset($response['type'])) {
- return [
- 'error' => false,
- 'data' => $response['value']
- ];
- }
- return [
- 'error' => true,
- 'message' => $response['value']
- ];
- }
- return "Login_Again";
- }
-
- public function GetNetworkVirtualMachine($vmware_id = ''): array
- {
- $response = $this->GetDetailVirtualMachine($vmware_id);
- if ($response['error'] === false) {
- return [
- 'error' => false,
- 'data' => $response['data']['nics'][0]['value']['backing']
- ];
- }
- return [
- 'error' => true,
- 'message' => $response['data']
- ];
- }
-
- public function ConfigNetworkVirtualMachine($vmware_id = '', $config = []): int
- {
- return Http::withoutVerifying()
- ->withHeaders([
- 'vmware-api-session-id' => $this->token
- ])
- ->patch("https://$this->apiUri/rest/vcenter/vm/$vmware_id/hardware/ethernet/4000", $config)
- ->status();
- }
-
- public function ConfigVirtualMachineUbuntu($vmware_id = '', $product = null, $password = ''): void
- {
- if (!$product) {
- Log::error('ConfigVirtualMachineUbuntu: Product is null', ['vmware_id' => $vmware_id]);
- return;
- }
-
- // Use resolveVCenterFromProduct to get vCenter
- $vcenter = $this->resolveVCenterFromProduct($product);
- if (!$vcenter) {
- Log::error('ConfigVirtualMachineUbuntu: Cannot resolve vCenter', ['vmware_id' => $vmware_id]);
- return;
- }
-
- $this->SetConfig($vcenter);
- $VirtualMachine = "VirtualMachine-$vmware_id";
- $IPAddress = $product->ipAddress?->ip ?? '0.0.0.0';
- $Gateway = $product->ipAddress?->gateway ?? '0.0.0.0';
- $HostName = strtolower($product->hostname ?? 'vm');
-
- Log::channel('Custom')->info("\r\n");
-
- try {
-
- Log::channel('Custom')->info("===================================================================");
-
- Log::channel('Custom')->info("Get Config information for VirtualMachine with #ID $VirtualMachine");
-
- Log::channel('Custom')->info("\r\n");
-
- $Command = "\$vm = get-vm -Id '$VirtualMachine';\$hostname ='$HostName'; \$ipadd = '$IPAddress'; \$gw = '$Gateway'; \$password = \"'$password'\"; cd /; C:\CLISave\createUbuntu1line.ps1;";
-
- Log::channel('Custom')->info("$Command");
-
- $this->Command($Command);
-
- Log::channel('Custom')->info("\r\n");
-
- Log::channel('Custom')->info("After run Command 1 |" . Carbon::now());
-
- $Command = "\$vm = get-vm -Id '$VirtualMachine'; \$ipadd = '$IPAddress'; \$gw = '$Gateway'; \$password = \"'$password'\"; cd /; C:\CLISave\changeLinux.ps1;";
-
- Log::channel('Custom')->info("$Command");
-
- $this->Command($Command);
- Log::channel('Custom')->info("\r\n");
-
- Log::channel('Custom')->info("After run Command 2 |" . Carbon::now());
-
- Log::channel('Custom')->info("\r\n");
-
-
- Log::channel('Custom')->info("===================================================================");
- } catch (\Exception $error) {
- Log::channel('Custom')->info("\r\n");
- Log::channel('Custom')->info("===================================================================");
- Log::channel('Custom')->info("Error Log: ");
- Log::channel('Custom')->info($error->getMessage());
- Log::channel('Custom')->info("===================================================================");
- }
- }
-
- public function GetDetailVirtualMachine($vmware_id = ''): array
- {
- $response = Http::withoutVerifying()
- ->withHeaders([
- 'vmware-api-session-id' => $this->token
- ])
- ->get("https://$this->apiUri/rest/vcenter/vm/$vmware_id")->json();
- if (!isset($response['type'])) {
- return [
- 'error' => false,
- 'data' => $response['value']
- ];
- }
- return [
- 'error' => true,
- 'message' => $response['value']
- ];
- }
-
- public function GetIPAddressVirtualMachine($vmware_id = ''): array
- {
- $response = Http::withoutVerifying()
- ->withHeaders([
- 'vmware-api-session-id' => $this->token
- ])
- ->get("https://$this->apiUri/rest/vcenter/vm/$vmware_id/guest/identity")->json();
- if (!isset($response['type'])) {
- return [
- 'error' => false,
- 'data' => $response['value']
- ];
- }
- return [
- 'error' => true,
- 'message' => $response['value']
- ];
- }
-
- public function GetVMStats($vmware_id = ''): array
- {
- $stats = [
- 'cpu' => null,
- 'memory' => null,
- 'disk' => null,
- ];
-
- try {
- $hardwareResponse = Http::withoutVerifying()
- ->withHeaders([
- 'vmware-api-session-id' => $this->token
- ])
- ->get("https://$this->apiUri/rest/vcenter/vm/$vmware_id/hardware")->json();
-
- if (is_array($hardwareResponse) && !isset($hardwareResponse['type']) && isset($hardwareResponse['value'])) {
- foreach ($hardwareResponse['value'] as $hardware) {
- if (isset($hardware['label']) && $hardware['label'] === 'CPU') {
- $stats['cpu'] = [
- 'cores' => $hardware['spec']['num_cores'] ?? 0,
- 'cores_per_socket' => $hardware['spec']['cores_per_socket'] ?? 0,
- ];
- }
- if (isset($hardware['label']) && $hardware['label'] === 'Memory') {
- $stats['memory'] = [
- 'size_MiB' => $hardware['spec']['size_MiB'] ?? 0,
- 'size_GB' => round(($hardware['spec']['size_MiB'] ?? 0) / 1024, 2),
- ];
- }
- if (isset($hardware['label']) && $hardware['label'] === 'Hard disk') {
- $stats['disk'][] = [
- 'capacity' => $hardware['capacity'] ?? 0,
- 'capacity_GB' => round(($hardware['capacity'] ?? 0) / (1024 * 1024 * 1024), 2),
- ];
- }
- }
- }
-
- $guestMemoryResponse = Http::withoutVerifying()
- ->withHeaders([
- 'vmware-api-session-id' => $this->token
- ])
- ->get("https://$this->apiUri/rest/vcenter/vm/$vmware_id/guest/memory")->json();
-
- if (is_array($guestMemoryResponse) && !isset($guestMemoryResponse['type']) && isset($guestMemoryResponse['value'])) {
- $stats['memory']['used_MiB'] = $guestMemoryResponse['value']['used'] ?? 0;
- $stats['memory']['used_GB'] = round(($guestMemoryResponse['value']['used'] ?? 0) / 1024, 2);
- $stats['memory']['usage_percent'] = $stats['memory']['size_MiB'] > 0
- ? round(($guestMemoryResponse['value']['used'] ?? 0) / $stats['memory']['size_MiB'] * 100, 1)
- : 0;
- }
-
- $guestCpuResponse = Http::withoutVerifying()
- ->withHeaders([
- 'vmware-api-session-id' => $this->token
- ])
- ->get("https://$this->apiUri/rest/vcenter/vm/$vmware_id/guest/cpu")->json();
-
- if (is_array($guestCpuResponse) && !isset($guestCpuResponse['type']) && isset($guestCpuResponse['value'])) {
- $stats['cpu']['used_percent'] = $guestCpuResponse['value']['used'] ?? 0;
- }
-
- return ['error' => false, 'data' => $stats];
- } catch (\Exception $e) {
- return ['error' => true, 'message' => $e->getMessage(), 'data' => $stats];
- }
- }
-
- public function ConfigCentOSVersion8($vmware_id = '', $product = [], $password = ''): void
- {
- // Use resolveVCenterFromProduct to get vCenter
- $vcenter = null;
- if ($product) {
- $vcenter = $this->resolveVCenterFromProduct($product);
- }
- if (!$vcenter) {
- Log::error('Cannot resolve vCenter', ['vmware_id' => $vmware_id]);
- return;
- }
- $this->SetConfig($vcenter);
- $VirtualMachine = "VirtualMachine-$vmware_id";
- $IPAddress = $product->ipAddress?->ip ?? '0.0.0.0';
- $Gateway = $product->ipAddress?->gateway ?? '0.0.0.0';
- $HostName = strtolower($product->hostname ?? 'vm');
- Log::channel('Custom')->info("\r\n");
-
- try {
-
- Log::channel('Custom')->info("===================================================================");
- Log::channel('Custom')->info("Get Config information for VirtualMachine with #ID $VirtualMachine");
- Log::channel('Custom')->info("\r\n");
-
- $Command = "\$vm = get-vm -Id '$VirtualMachine';\$hostname ='$HostName'; \$ipadd = '$IPAddress'; \$gw = '$Gateway'; \$password = \"'$password'\"; cd /; C:\CLISave\createCentOS81line.ps1;";
-
- Log::channel('Custom')->info("$Command");
-
- $this->Command($Command);
- Log::channel('Custom')->info("\r\n");
- Log::channel('Custom')->info("After run Command |" . Carbon::now());
-
- Log::channel('Custom')->info("===================================================================");
- } catch (\Exception $error) {
- Log::channel('Custom')->info("\r\n");
- Log::channel('Custom')->info("===================================================================");
- Log::channel('Custom')->info("Error Log: ");
- Log::channel('Custom')->info($error->getMessage());
- Log::channel('Custom')->info("===================================================================");
- }
- }
-
- public function ConfigCentOSVersion7($vmware_id = '', $product = [], $password = ''): void
- {
- // Use resolveVCenterFromProduct to get vCenter
- $vcenter = null;
- if ($product) {
- $vcenter = $this->resolveVCenterFromProduct($product);
- }
- if (!$vcenter) {
- Log::error('Cannot resolve vCenter', ['vmware_id' => $vmware_id]);
- return;
- }
- $this->SetConfig($vcenter);
- $VirtualMachine = "VirtualMachine-$vmware_id";
- $IPAddress = $product->ipAddress?->ip ?? '0.0.0.0';
- $Gateway = $product->ipAddress?->gateway ?? '0.0.0.0';
- $HostName = strtolower($product->hostname ?? 'vm');
- Log::channel('Custom')->info("\r\n");
-
- try {
-
- Log::channel('Custom')->info("===================================================================");
- Log::channel('Custom')->info("Get Config information for VirtualMachine with #ID $VirtualMachine");
- Log::channel('Custom')->info("\r\n");
-
- $Command = "\$vm = get-vm -Id '$VirtualMachine'; \$hostname ='$HostName';\$ipadd = '$IPAddress'; \$gw = '$Gateway'; \$password = \"'$password'\"; cd /; C:\CLISave\createCentOS71line.ps1;";
-
- Log::channel('Custom')->info($Command);
-
- $this->Command($Command);
- Log::channel('Custom')->info("\r\n");
- Log::channel('Custom')->info("After run Command |" . Carbon::now());
-
- Log::channel('Custom')->info("===================================================================");
- } catch (\Exception $error) {
- Log::channel('Custom')->info("\r\n");
- Log::channel('Custom')->info("===================================================================");
- Log::channel('Custom')->info("Error Log: ");
- Log::channel('Custom')->info($error->getMessage());
- Log::channel('Custom')->info("===================================================================");
- }
- }
-
- public function ConfigWindowsVersion10($vmware_id = '', $product = [], $password = ''): void
- {
- try {
- // Kiểm tra null safety
- if (!$product) {
- Log::error('❌ ConfigWindowsVersion10: Product is null', ['vmware_id' => $vmware_id]);
- return;
- }
-
- // Use resolveVCenterFromProduct to get vCenter
- $vcenter = null;
- if ($product) {
- $vcenter = $this->resolveVCenterFromProduct($product);
- }
- if (!$vcenter) {
- Log::error('Cannot resolve vCenter', ['vmware_id' => $vmware_id]);
- return;
- }
- $this->SetConfig($vcenter);
- $VirtualMachine = "VirtualMachine-$vmware_id";
- $IPAddress = $product->IpAddress->ip;
- $Gateway = $product->IpAddress->gateway;
- $HostName = strtolower($product->hostname);
-
- Log::channel('Custom')->info("\r\n");
- Log::channel('Custom')->info("===================================================================");
- Log::channel('Custom')->info("Get Config information for VirtualMachine with #ID $VirtualMachine");
- Log::channel('Custom')->info("\r\n");
-
- $Command = "\$vm = get-vm -Id '$VirtualMachine';\$hostname ='$HostName'; \$ipadd = '$IPAddress'; \$gw = '$Gateway'; \$password = \"'$password'\"; cd /; C:\CLISave\changeWindows10.ps1;";
-
- Log::channel('Custom')->info("$Command");
- $this->Command($Command);
-
- Log::channel('Custom')->info("\r\n");
- Log::channel('Custom')->info("After run Command |" . Carbon::now());
- Log::channel('Custom')->info("===================================================================");
- } catch (\Exception $error) {
- Log::channel('Custom')->info("\r\n");
- Log::channel('Custom')->info("===================================================================");
- Log::channel('Custom')->info("Error Log: ");
- Log::channel('Custom')->info($error->getMessage());
- Log::channel('Custom')->info("===================================================================");
- }
- }
-
- public function ConfigWindowsServer($vmware_id = '', $product = [], $password = ''): void
- {
- try {
- // Kiểm tra null safety
- if (!$product) {
- Log::error('❌ ConfigWindowsServer: Product is null', ['vmware_id' => $vmware_id]);
- return;
- }
-
- // Use resolveVCenterFromProduct to get vCenter
- $vcenter = null;
- if ($product) {
- $vcenter = $this->resolveVCenterFromProduct($product);
- }
- if (!$vcenter) {
- Log::error('Cannot resolve vCenter', ['vmware_id' => $vmware_id]);
- return;
- }
- $this->SetConfig($vcenter);
- $VirtualMachine = "VirtualMachine-$vmware_id";
- $IPAddress = $product->IpAddress->ip;
- $Gateway = $product->IpAddress->gateway;
- $HostName = strtolower($product->hostname);
-
- Log::channel('Custom')->info("===================================================================");
- Log::channel('Custom')->info("Get Config information for VirtualMachine with #ID $VirtualMachine");
- Log::channel('Custom')->info("\r\n");
-
- $Command = "\$vm = get-vm -Id '$VirtualMachine';\$hostname ='$HostName'; \$ipadd = '$IPAddress'; \$gw = '$Gateway'; \$password = \"'$password'\"; cd /; C:\CLISave\changeWindows1line.ps1;";
-
- Log::channel('Custom')->info("$Command");
-
- $this->Command($Command);
-
- Log::channel('Custom')->info("\r\n");
- Log::channel('Custom')->info("After run Command |" . Carbon::now());
- Log::channel('Custom')->info("===================================================================");
- } catch (\Exception $error) {
- Log::channel('Custom')->info("\r\n");
- Log::channel('Custom')->info("===================================================================");
- Log::channel('Custom')->info("Error Log: ");
- Log::channel('Custom')->info($error->getMessage());
- Log::channel('Custom')->info("===================================================================");
- }
- }
-
- public function ConfigFixWindowsClick($vmware_id = '', $product = []): void
- {
- try {
- // Kiểm tra null safety
- if (!$product) {
- Log::error('❌ ConfigFixWindowsClick: Product is null', ['vmware_id' => $vmware_id]);
- return;
- }
-
- // Use resolveVCenterFromProduct to get vCenter
- $vcenter = $this->resolveVCenterFromProduct($product);
- if (!$vcenter) {
- Log::error('Cannot resolve vCenter', ['vmware_id' => $vmware_id]);
- return;
- }
- $this->SetConfig($vcenter);
- $VirtualMachine = "VirtualMachine-$vmware_id";
-
- Log::channel('Custom')->info("===================================================================");
- Log::channel('Custom')->info("Get Config information for VirtualMachine with #ID $VirtualMachine");
- Log::channel('Custom')->info("\r\n");
-
- $Command = "\$vm = get-vm -Id '$VirtualMachine'; cd /; C:\CLISave\FixTwoClickWindows.ps1;";
-
- Log::channel('Custom')->info("$Command");
- $this->Command($Command);
-
- Log::channel('Custom')->info("\r\n");
- Log::channel('Custom')->info("After run Command |" . Carbon::now());
- Log::channel('Custom')->info("===================================================================");
- } catch (\Exception $error) {
- Log::channel('Custom')->info("\r\n");
- Log::channel('Custom')->info("===================================================================");
- Log::channel('Custom')->info("Error Log: ");
- Log::channel('Custom')->info($error->getMessage());
- Log::channel('Custom')->info("===================================================================");
- }
- }
-
- public function ConfigRockyLinuxVersion9($vmware_id = '', $product = [], $password = ''): void
- {
- try {
- // Kiểm tra null safety
- if (!$product) {
- Log::error('❌ ConfigRockyLinuxVersion9: Product is null', ['vmware_id' => $vmware_id]);
- return;
- }
-
- // Use resolveVCenterFromProduct to get vCenter
- $vcenter = $this->resolveVCenterFromProduct($product);
- if (!$vcenter) {
- Log::error('Cannot resolve vCenter', ['vmware_id' => $vmware_id]);
- return;
- }
- $this->SetConfig($vcenter);
- $VirtualMachine = "VirtualMachine-$vmware_id";
- $IPAddress = $product->ipAddress?->ip ?? '0.0.0.0';
- $Gateway = $product->ipAddress?->gateway ?? '0.0.0.0';
- $HostName = strtolower($product->hostname ?? 'vm');
-
- Log::channel('Custom')->info("\r\n");
- Log::channel('Custom')->info("===================================================================");
- Log::channel('Custom')->info("Get Config information for VirtualMachine with #ID $VirtualMachine");
- Log::channel('Custom')->info("\r\n");
-
- $Command = "\$vm = get-vm -Id '$VirtualMachine'; \$hostname ='$HostName';\$ipadd = '$IPAddress'; \$gw = '$Gateway'; \$password = \"'$password'\"; cd /; C:\CLISave\createRocky1line.ps1;";
-
- Log::channel('Custom')->info($Command);
- $this->Command($Command);
- Log::channel('Custom')->info("\r\n");
- Log::channel('Custom')->info("After run Command |" . Carbon::now());
- Log::channel('Custom')->info("===================================================================");
- } catch (\Exception $error) {
- Log::channel('Custom')->info("\r\n");
- Log::channel('Custom')->info("===================================================================");
- Log::channel('Custom')->info("Error Log: ");
- Log::channel('Custom')->info($error->getMessage());
- Log::channel('Custom')->info("===================================================================");
- }
- }
-
- public function GetConsoleTickets($vmware_id = ''): array|string
- {
- $check = $this->checkApiExits();
- if ($check) {
- $response = Http::withoutVerifying()
- ->withHeaders([
- 'vmware-api-session-id' => $this->token
- ])
- ->post("https://$this->apiUri/api/vcenter/vm/$vmware_id/console/tickets", [
- "type" => "WEBMKS"
- ])->json();
- if (!isset($response['error_type'])) {
- return [
- 'error' => false,
- 'data' => $response['ticket']
- ];
- }
- return [
- 'error' => true,
- 'data' => $response['messages']
- ];
- }
- return "Login_Again";
- }
-
- public function SuspendVirtualMachine($vmware_id = ''): array|string
- {
-
- $check = $this->checkApiExits();
- if ($check) {
- $response = Http::withoutVerifying()
- ->withHeaders([
- 'vmware-api-session-id' => $this->token
- ])
- ->post("https://$this->apiUri/rest/vcenter/vm/$vmware_id/power/suspend");
- if ($response->status() === 200) {
- return [
- 'error' => false,
- 'data' => 'success'
- ];
- }
- return [
- 'error' => true,
- 'data' => $response['value']
- ];
- }
- return "Login_Again";
- }
-
- public function RebootVirtualMachine($product, $vmware_id = ''): array|string
- {
-
- $check = $this->loginServer($product->ipaddress->host->datacenter->vCenter);
- if ($check) {
- $response = Http::withoutVerifying()
- ->withHeaders([
- 'vmware-api-session-id' => $this->token
- ])
- ->post("https://$this->apiUri/rest/vcenter/vm/$vmware_id/guest/power?action=reboot");
- if ($response->status() === 200) {
- return [
- 'error' => false,
- 'data' => 'success'
- ];
- }
- return [
- 'error' => true,
- 'data' => $response['value']
- ];
- }
- return "Login_Again";
- }
-
- public function restartVM($product, $vmware_id = ''): array|string
- {
- try {
- $vmId = $vmware_id ?: ($product->vmware_id ?? null);
- if (!$vmId) {
- return [
- 'error' => true,
- 'data' => 'Không tìm thấy vmware_id',
- ];
- }
-
- // Ưu tiên quan hệ trực tiếp trên ModuleVmware
- $vcenter = $product->vCenter ?? null;
-
- // Fallback cho dữ liệu cũ chưa có vcenter_id
- if (!$vcenter && isset($product->ipAddress) && $product->ipAddress?->host?->datacenter?->vCenter) {
- $vcenter = $product->ipAddress->host->datacenter->vCenter;
- }
-
- if (!$vcenter) {
- return [
- 'error' => true,
- 'data' => 'Không tìm thấy thông tin vCenter cho VM',
- ];
- }
-
- $check = $this->loginServer($vcenter);
- if (!$check) {
- return "Login_Again";
- }
-
- $response = Http::withoutVerifying()
- ->withHeaders([
- 'vmware-api-session-id' => $this->token
- ])
- ->post("https://$this->apiUri/rest/vcenter/vm/$vmId/guest/power?action=reboot");
-
- if ($response->status() === 200) {
- return [
- 'error' => false,
- 'data' => 'success'
- ];
- }
-
- return [
- 'error' => true,
- 'data' => $response->json('value') ?? $response->body(),
- ];
- } catch (\Throwable $e) {
- return [
- 'error' => true,
- 'data' => $e->getMessage(),
- ];
- }
- }
-
- public function StartVirtualMachine($product, $vmware_id = ''): array|string
- {
- try {
- $vmId = $vmware_id ?: ($product->vmware_id ?? null);
- if (!$vmId) {
- return ['error' => true, 'data' => 'Không tìm thấy vmware_id'];
- }
-
- $vcenter = $product->vCenter ?? null;
- if (!$vcenter && $product->ipAddress?->host?->datacenter?->vCenter) {
- $vcenter = $product->ipAddress->host->datacenter->vCenter;
- }
-
- if (!$vcenter) {
- return ['error' => true, 'data' => 'Không tìm thấy thông tin vCenter cho VM'];
- }
-
- if (!$this->loginServer($vcenter)) {
- return 'Login_Again';
- }
-
- $response = Http::withoutVerifying()
- ->withHeaders(['vmware-api-session-id' => $this->token])
- ->post("https://$this->apiUri/rest/vcenter/vm/$vmId/power/start");
-
- if ($response->status() === 200) {
- return ['error' => false, 'data' => 'success'];
- }
-
- return ['error' => true, 'data' => $response->json('value') ?? $response->body()];
- } catch (\Throwable $e) {
- return ['error' => true, 'data' => $e->getMessage()];
- }
- }
-
- public function ShutdownGuestVirtualMachine($product, $vmware_id = ''): array|string
- {
- try {
- $vmId = $vmware_id ?: ($product->vmware_id ?? null);
- if (!$vmId) {
- return ['error' => true, 'data' => 'Không tìm thấy vmware_id'];
- }
-
- $vcenter = $product->vCenter ?? null;
- if (!$vcenter && $product->ipAddress?->host?->datacenter?->vCenter) {
- $vcenter = $product->ipAddress->host->datacenter->vCenter;
- }
-
- if (!$vcenter) {
- return ['error' => true, 'data' => 'Không tìm thấy thông tin vCenter cho VM'];
- }
-
- if (!$this->loginServer($vcenter)) {
- return 'Login_Again';
- }
-
- $response = Http::withoutVerifying()
- ->withHeaders(['vmware-api-session-id' => $this->token])
- ->post("https://$this->apiUri/rest/vcenter/vm/$vmId/guest/power?action=shutdown");
-
- if ($response->status() === 200) {
- return ['error' => false, 'data' => 'success'];
- }
-
- return ['error' => true, 'data' => $response->json('value') ?? $response->body()];
- } catch (\Throwable $e) {
- return ['error' => true, 'data' => $e->getMessage()];
- }
- }
-
- public function PowerOffVirtualMachine($vmware_id = ''): array|string
- {
- $check = $this->checkApiExits();
- if ($check) {
- $response = Http::withoutVerifying()
- ->withHeaders([
- 'vmware-api-session-id' => $this->token
- ])
- ->post("https://$this->apiUri/rest/vcenter/vm/$vmware_id/power/stop");
- if ($response->status() === 200) {
- return [
- 'error' => false,
- 'data' => 'success'
- ];
- }
- return [
- 'error' => true,
- 'data' => $response['value']
- ];
- }
- return "Login_Again";
- }
-
- public function getVmPowerState($product, string $vmware_id = ''): array|string
- {
- try {
- $vmId = $vmware_id ?: ($product->vmware_id ?? null);
- if (!$vmId) {
- return ['error' => true, 'data' => 'Không tìm thấy vmware_id'];
- }
-
- $vcenter = $product->vCenter ?? null;
- if (!$vcenter && $product->ipAddress?->host?->datacenter?->vCenter) {
- $vcenter = $product->ipAddress->host->datacenter->vCenter;
- }
-
- if (!$vcenter) {
- return ['error' => true, 'data' => 'Không tìm thấy thông tin vCenter cho VM'];
- }
-
- if (!$this->loginServer($vcenter)) {
- return 'Login_Again';
- }
-
- $response = Http::withoutVerifying()
- ->withHeaders(['vmware-api-session-id' => $this->token])
- ->timeout(8)
- ->get("https://$this->apiUri/rest/vcenter/vm/$vmId/power");
-
- if ($response->successful()) {
- $data = $response->json();
- return ['error' => false, 'data' => $data['value'] ?? $data];
- }
-
- return ['error' => true, 'data' => $response->json('value') ?? 'Không thể lấy trạng thái VM'];
- } catch (\Throwable $e) {
- return ['error' => true, 'data' => $e->getMessage()];
- }
- }
-
- public function DeleteVirtualMachine($vmware_id = ''): array|string
- {
- $check = $this->checkApiExits();
- if ($check) {
- $response = Http::withoutVerifying()
- ->withHeaders([
- 'vmware-api-session-id' => $this->token
- ])
- ->delete("https://$this->apiUri/rest/vcenter/vm/$vmware_id");
- if ($response->status() === 200) {
- return [
- 'error' => false,
- 'data' => 'success'
- ];
- }
- return [
- 'error' => true,
- 'data' => $response['value']
- ];
- }
- return "Login_Again";
- }
-
- public function CreateSnapShot($product, ?string $snapshotName = null): string|array
- {
- try {
- // Sử dụng helper method để resolve vCenter từ product
- $vcenter = $this->resolveVCenterFromProduct($product);
-
- if (!$vcenter) {
- return ['error' => true, 'message' => 'Không tìm thấy thông tin vCenter cho VM'];
- }
-
- $this->username = $vcenter->user;
- $this->password = SignRSA::DecryptFunc($vcenter->password);
- $this->apiUri = $vcenter->host;
-
- // Login to get token
- $loginResult = $this->loginServer($vcenter);
- if (!$loginResult) {
- return "Login_Again";
- }
- } catch (\Exception $e) {
- return ['error' => true, 'message' => 'Lỗi setup credentials: ' . $e->getMessage()];
- }
- $vmware_name = (string) $product->name;
-
- $check = $this->checkApiExits();
-
- if ($check) {
- try {
- $Snapshot = $snapshotName ?: ($vmware_name . "_" . date('d_m_Y_H_i_s'));
-
- Log::channel('Custom')->info("===================================================================");
-
- Log::channel('Custom')->info("Create Snapshot with VirtualMachine Name - $vmware_name - Snap #$Snapshot");
-
- Log::channel('Custom')->info("\r\n");
-
- $Command = "& 'C:\CLISave\createsnapshot.ps1' -vm_name '$vmware_name' -name '$Snapshot'";
-
- Log::channel('Custom')->info("$Command");
-
- $cmdResult = $this->Command($Command);
-
- Log::channel('Custom')->info("Command Result: " . $cmdResult);
- Log::channel('Custom')->info("\r\n");
-
- Log::channel('Custom')->info("After run Command |" . Carbon::now());
-
- Log::channel('Custom')->info("===================================================================");
-
- // Check if command failed
- if (strpos($cmdResult, 'Login_Fail') !== false) {
- return [
- 'error' => true,
- 'message' => 'SSH Login to PowerShell server failed'
- ];
- }
-
- if (strpos($cmdResult, '[STDERR]') !== false || stripos($cmdResult, 'error') !== false) {
- return [
- 'error' => true,
- 'message' => 'PowerShell command failed: ' . $cmdResult
- ];
- }
-
- // Clear snapshot cache for this VM
- $this->clearSnapshotCache($product);
-
- return [
- 'error' => false,
- 'data' => $Snapshot
- ];
- } catch (\Exception $error) {
- Log::channel('Custom')->info("\r\n");
- Log::channel('Custom')->info("===================================================================");
- Log::channel('Custom')->info("Error Log: ");
- Log::channel('Custom')->info($error->getMessage());
- Log::channel('Custom')->info("===================================================================");
- return [
- 'error' => true,
- 'message' => $error->getMessage()
- ];
- }
- }
- return "Login_Again";
- }
-
- private function clearSnapshotCache($product): void
- {
- $vmware_name = (string) $product->name;
- $cacheKey = "snapshots_{$product->id}_{$vmware_name}";
- Cache::forget($cacheKey);
- Log::channel('Custom')->info("🗑️ Cleared snapshot cache for VM: {$vmware_name}");
- }
-
- public function getSnapShot($product): string|array
- {
- $startTime = microtime(true);
-
- try {
- // Sử dụng helper method để resolve vCenter từ product
- $vcenter = $this->resolveVCenterFromProduct($product);
-
- if (!$vcenter) {
- return ['error' => true, 'message' => 'Không tìm thấy thông tin vCenter cho VM'];
- }
-
- $this->username = $vcenter->user;
- $this->password = SignRSA::DecryptFunc($vcenter->password);
- $this->apiUri = $vcenter->host;
- } catch (\Exception $e) {
- return ['error' => true, 'message' => 'Lỗi setup credentials: ' . $e->getMessage()];
- }
-
- $vmware_name = (string) $product->name;
- $vmId = $product->vmware_id;
-
- // Check cache first
- $cacheKey = "snapshots_{$product->id}_{$vmware_name}";
- $cachedResult = Cache::get($cacheKey);
-
- if ($cachedResult !== null) {
- Log::channel('Custom')->info("📸 Snapshot cache hit");
- return $cachedResult;
- }
-
- try {
- Log::channel('Custom')->info("🚀 Starting SOAP Snapshot Fetch for VM: $vmId ($vmware_name)");
-
- // 1. SOAP Login
- $sessionToken = $this->soapLogin();
- if (!$sessionToken) {
- return "Login_Again";
- }
-
- // 2. Retrieve Snapshot Info
- $snapshotInfo = $this->getVMSnapshotInfoSoap($sessionToken, $vmId);
-
- // 3. Flatten Tree
- $snapshots = [];
- if (isset($snapshotInfo['rootSnapshotList'])) {
- // Handle single object vs array in XML to JSON conversion
- $roots = $snapshotInfo['rootSnapshotList'];
- // Ensure it's an array if single
- if (isset($roots['snapshot'])) {
- // It's a single node structure, allow recurse
- $snapshots = $this->flattenSnapshotTree($roots);
- } elseif (is_array($roots)) {
- foreach ($roots as $root) {
- $snapshots = array_merge($snapshots, $this->flattenSnapshotTree($root));
- }
- }
- }
-
- // Format similar to old output
- $formattedSnapshots = [];
- foreach ($snapshots as $snap) {
- $formattedSnapshots[] = [
- 'name' => $snap['name'],
- 'description' => $snap['description'] ?? '',
- 'created' => $snap['createTime'],
- 'id' => $snap['snapshot']['_'] ?? $snap['snapshot'], // MO Ref Value
- 'is_current' => false, // Simplification, need logic to check against currentSnapshot
- 'parent_snapshot_id' => '', // Hard to determining flat parent in simple recursion without passing parent id
- 'size_gb' => 0 // SOAP doesnt always return size in tree
- ];
- }
-
- $response = [
- 'error' => false,
- 'data' => $formattedSnapshots
- ];
-
- Cache::put($cacheKey, $response, now()->addMinutes(5));
- return $response;
-
- } catch (\Exception $e) {
- Log::channel('Custom')->error("❌ SOAP Snapshot Error: " . $e->getMessage());
- return [
- 'error' => true,
- 'message' => 'Lỗi lấy snapshot qua SOAP: ' . $e->getMessage()
- ];
- }
- }
-
- private function soapLogin()
- {
- $body = '
-
Dang ky va gia han ten mien {$productData['extension']} hang nam.
", + 'thumbnail' => '/images/domain/domain.png', + 'stock' => null, + 'is_unlimited' => true, + 'is_active' => true, + 'sort_order' => $productData['sort_order'], + 'product_categories_id' => $domainCategory->id, + 'published_at' => now(), + ]); + + $product->save(); + + if ($product->trashed()) { + $product->restore(); + } + + ProductPricings::updateOrCreate( + [ + 'product_id' => $product->id, + 'billing_cycle' => 'annually', + ], + [ + 'price' => $productData['price'], + 'price_sale' => $productData['price_sale'], + 'currency' => 'VND', + 'is_active' => true, + ] + ); + + $this->command?->info("Seeded domain product: {$product->name}"); + } + + $this->command?->info('Domain products seeding completed.'); + } +} diff --git a/public/.DS_Store b/public/.DS_Store index 1a9f62e..e55bddd 100644 Binary files a/public/.DS_Store and b/public/.DS_Store differ diff --git a/resources/views/.DS_Store b/resources/views/.DS_Store deleted file mode 100644 index 53aaaae..0000000 Binary files a/resources/views/.DS_Store and /dev/null differ diff --git a/resources/views/emails/admin/manual-deposit-pending.blade.php b/resources/views/emails/admin/manual-deposit-pending.blade.php new file mode 100644 index 0000000..6a08b66 --- /dev/null +++ b/resources/views/emails/admin/manual-deposit-pending.blade.php @@ -0,0 +1,98 @@ + + + + + +{{ $transaction_code ?? 'N/A' }} — {{ $created_at ?? now()->format('d/m/Y H:i:s') }}
+| Tên khách hàng | +{{ $customer_name ?? 'N/A' }} | +
| {{ $customer_email ?? 'N/A' }} | +|
| Số điện thoại | +{{ $customer_phone }} | +
| IP gửi yêu cầu | +{{ $client_ip }} | +
| Mã giao dịch | +{{ $transaction_code ?? 'N/A' }} | +
| Số tiền | +{{ number_format($amount ?? 0, 0, ',', '.') }} VNĐ | +
| Trạng thái | +Đang chờ duyệt | +
| Chứng từ | +Xem chứng từ chuyển khoản | +
| + + | +