update optimize code
This commit is contained in:
@@ -1,246 +0,0 @@
|
||||
# Tóm tắt thay đổi - Xử lý Addon sau khi Clone VM
|
||||
|
||||
## Tổng quan
|
||||
Tài liệu này mô tả các thay đổi đã thực hiện để cải thiện hệ thống quản lý vCenter virtual machines trong Laravel application.
|
||||
|
||||
## Các thay đổi chính
|
||||
|
||||
### 1. Cải thiện vCenter Service (app/Http/Services/vCenter.php)
|
||||
|
||||
#### 1.1 Thêm các method cập nhật phần cứng VM
|
||||
- **updateVMHardware()**: Method chính để cập nhật cấu hình phần cứng VM
|
||||
- **updateVMCPU()**: Cập nhật số lượng CPU cores
|
||||
- **updateVMMemory()**: Cập nhật RAM (convert GB sang MiB)
|
||||
- **updateVMDisk()**: Cập nhật dung lượng disk (mở rộng disk)
|
||||
|
||||
#### 1.2 Cải thiện CreateVirtualMachine()
|
||||
- Thêm logic để áp dụng addon sau khi clone VM thành công
|
||||
- Convert RAM từ GB sang MiB trước khi gọi API
|
||||
- Thêm logging chi tiết cho quá trình tạo VM và cập nhật addon
|
||||
- Gửi thông báo Discord khi tạo VM thành công
|
||||
|
||||
#### 1.3 Thêm method test
|
||||
- **testAddonUpdate()**: Method để test việc cập nhật addon
|
||||
|
||||
### 2. Cập nhật CreateVirtualMachineJob (app/Jobs/VirtualMachine/Create/CreateVirtualMachineJob.php)
|
||||
|
||||
#### 2.1 Cải thiện xử lý addon
|
||||
- Parse đúng số lượng addon từ order items
|
||||
- Truyền thông tin addon vào vCenter service
|
||||
- Thêm logging chi tiết cho quá trình xử lý
|
||||
|
||||
#### 2.2 Lưu vm_id vào invoice
|
||||
- Lưu VM ID vào bảng `invoice` sau khi tạo thành công
|
||||
- Cập nhật trạng thái order và gửi email thông báo
|
||||
|
||||
### 3. Tạo ServiceController (app/Http/Controllers/frontend/ServiceController.php)
|
||||
|
||||
#### 3.1 Chức năng chính
|
||||
- **show()**: Hiển thị chi tiết service dựa trên invoice_id
|
||||
- **toggleVM()**: Điều khiển VM (start/stop/restart) - placeholder
|
||||
- **getVMInfo()**: Lấy thông tin VM từ vCenter - placeholder
|
||||
|
||||
#### 3.2 Tích hợp vCenter API
|
||||
- **getVMInfoFromVCenter()**: Lấy thông tin VM thực từ vCenter API
|
||||
- **getDefaultVMInfo()**: Thông tin mặc định khi không thể kết nối vCenter
|
||||
- Tự động login vào vCenter và lấy thông tin chi tiết VM
|
||||
|
||||
#### 3.3 Thông tin VM được lấy
|
||||
- Trạng thái power (POWERED_ON, POWERED_OFF, SUSPENDED)
|
||||
- IP address từ guest info
|
||||
- Thông tin phần cứng (CPU, RAM, Disk)
|
||||
- Thông tin chi tiết (folder, resource pool, datastore, network, etc.)
|
||||
|
||||
### 4. Tạo Service Detail View (resources/views/frontend/service/show.blade.php)
|
||||
|
||||
#### 4.1 Giao diện hiển thị
|
||||
- Thông tin cơ bản của service (invoice, customer, billing)
|
||||
- Thông tin chi tiết VM từ vCenter API
|
||||
- Danh sách addon đã mua
|
||||
- Nút điều khiển VM (placeholder)
|
||||
|
||||
#### 4.2 Thông tin VM hiển thị
|
||||
- VM ID, tên VM, trạng thái
|
||||
- IP address, hostname, MAC address
|
||||
- CPU, RAM, Disk, OS
|
||||
- Thông tin chi tiết: folder, resource pool, datastore, network, version
|
||||
|
||||
### 5. Cập nhật Routes (routes/web.php)
|
||||
|
||||
#### 5.1 Thêm routes cho service
|
||||
```php
|
||||
Route::get('/service/{invoice_id}', [ServiceController::class, 'show'])->name('service.show');
|
||||
Route::post('/service/{invoice_id}/toggle-vm', [ServiceController::class, 'toggleVM'])->name('service.toggle-vm');
|
||||
Route::get('/service/{invoice_id}/vm-info', [ServiceController::class, 'getVMInfo'])->name('service.vm-info');
|
||||
```
|
||||
|
||||
### 6. Cập nhật My Service View (resources/views/frontend/my-service.blade.php)
|
||||
|
||||
#### 6.1 Thêm link chi tiết
|
||||
- Cập nhật link "Xem chi tiết" để trỏ đến route mới
|
||||
- Hiển thị VM ID nếu có
|
||||
|
||||
### 7. Cập nhật Admin Invoice Views
|
||||
|
||||
#### 7.1 Hiển thị VM ID
|
||||
- **resources/views/admin/invoice/show.blade.php**: Thêm cột VM ID
|
||||
- **resources/views/admin/invoice/index.blade.php**: Thêm cột VM ID
|
||||
|
||||
### 8. Tạo Test Commands
|
||||
|
||||
#### 8.1 TestVMInfo Command (app/Console/Commands/TestVMInfo.php)
|
||||
- Test việc lấy thông tin VM từ vCenter API
|
||||
- Kiểm tra login, VM details, power status, IP info
|
||||
- Hiển thị thông tin chi tiết để debug
|
||||
|
||||
#### 8.2 TestSaveVMId Command (app/Console/Commands/TestSaveVMId.php)
|
||||
- Test việc lưu VM ID vào invoice
|
||||
- Simulate quá trình tạo VM và lưu thông tin
|
||||
|
||||
#### 8.3 TestCreateVMWithAddon Command (app/Console/Commands/TestCreateVMWithAddon.php)
|
||||
- Test toàn bộ quá trình tạo VM với addon
|
||||
- Từ order đến tạo VM và cập nhật addon
|
||||
|
||||
## Các vấn đề đã giải quyết
|
||||
|
||||
### 1. Vấn đề addon không áp dụng
|
||||
- **Nguyên nhân**: Logic cũ không cập nhật phần cứng sau khi clone VM
|
||||
- **Giải pháp**: Thêm logic cập nhật phần cứng sau khi clone thành công
|
||||
|
||||
### 2. Vấn đề convert RAM
|
||||
- **Nguyên nhân**: RAM được truyền dưới dạng GB nhưng API yêu cầu MiB
|
||||
- **Giải pháp**: Convert GB sang MiB (1 GB = 1024 MiB) trước khi gọi API
|
||||
|
||||
### 3. Vấn đề hiển thị thông tin VM
|
||||
- **Nguyên nhân**: Không có cách để xem thông tin VM thực từ vCenter
|
||||
- **Giải pháp**: Tạo service detail page tích hợp với vCenter API
|
||||
|
||||
### 4. Vấn đề lưu VM ID
|
||||
- **Nguyên nhân**: VM ID không được lưu vào database
|
||||
- **Giải pháp**: Lưu VM ID vào bảng `invoice` sau khi tạo thành công
|
||||
|
||||
## Cách sử dụng
|
||||
|
||||
### 1. Test lấy thông tin VM
|
||||
```bash
|
||||
php artisan test:vm-info {invoice_id}
|
||||
```
|
||||
|
||||
### 2. Test lưu VM ID
|
||||
```bash
|
||||
php artisan test:save-vm-id {invoice_id}
|
||||
```
|
||||
|
||||
### 3. Test tạo VM với addon
|
||||
```bash
|
||||
php artisan test:create-vm-with-addon {order_id}
|
||||
```
|
||||
|
||||
### 4. Truy cập service detail
|
||||
- URL: `/service/{invoice_id}`
|
||||
- Yêu cầu đăng nhập và quyền sở hữu invoice
|
||||
|
||||
## Lưu ý quan trọng
|
||||
|
||||
### 1. Bảo mật
|
||||
- Kiểm tra quyền truy cập: chỉ customer sở hữu mới được xem service detail
|
||||
- Validate input và handle exception cẩn thận
|
||||
|
||||
### 2. Performance
|
||||
- Cache thông tin VM nếu cần thiết
|
||||
- Timeout cho vCenter API calls
|
||||
- Fallback khi không thể kết nối vCenter
|
||||
|
||||
### 3. Monitoring
|
||||
- Log chi tiết tất cả operations
|
||||
- Gửi thông báo Discord cho các events quan trọng
|
||||
- Track performance của vCenter API calls
|
||||
|
||||
## Các bước tiếp theo
|
||||
|
||||
### 1. Implement VM Control Actions
|
||||
- Start/Stop/Restart VM thực sự
|
||||
- Console access
|
||||
- Snapshot management
|
||||
|
||||
### 2. Cải thiện UI/UX
|
||||
- Real-time status updates
|
||||
- Progress indicators
|
||||
- Better error handling
|
||||
|
||||
### 3. Monitoring & Alerting
|
||||
- VM health monitoring
|
||||
- Resource usage tracking
|
||||
- Automated alerts
|
||||
|
||||
### 4. Backup & Recovery
|
||||
- Automated backup scheduling
|
||||
- Disaster recovery procedures
|
||||
- Backup verification
|
||||
|
||||
## Database Changes
|
||||
|
||||
### 1. Invoice Table
|
||||
- Thêm cột `vm_id` để lưu VM ID từ vCenter
|
||||
- Cột này được populate sau khi tạo VM thành công
|
||||
|
||||
### 2. Không có migration cần thiết
|
||||
- Cột `vm_id` đã tồn tại trong database
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### 1. vCenter API Integration
|
||||
- `GET /rest/vcenter/vm/{vm_id}` - Lấy thông tin VM
|
||||
- `GET /rest/vcenter/vm/{vm_id}/guest/power` - Lấy trạng thái power
|
||||
- `GET /rest/vcenter/vm/{vm_id}/guest/identity` - Lấy thông tin guest
|
||||
- `PATCH /rest/vcenter/vm/{vm_id}/hardware/cpu` - Cập nhật CPU
|
||||
- `PATCH /rest/vcenter/vm/{vm_id}/hardware/memory` - Cập nhật RAM
|
||||
|
||||
### 2. Application API
|
||||
- `GET /service/{invoice_id}` - Service detail page
|
||||
- `POST /service/{invoice_id}/toggle-vm` - Điều khiển VM
|
||||
- `GET /service/{invoice_id}/vm-info` - Lấy thông tin VM
|
||||
|
||||
## Testing
|
||||
|
||||
### 1. Unit Tests
|
||||
- Test vCenter service methods
|
||||
- Test VM creation workflow
|
||||
- Test addon application logic
|
||||
|
||||
### 2. Integration Tests
|
||||
- Test vCenter API integration
|
||||
- Test complete VM creation flow
|
||||
- Test service detail page
|
||||
|
||||
### 3. Manual Testing
|
||||
- Test với VM thực trên vCenter
|
||||
- Test các trường hợp lỗi
|
||||
- Test performance với nhiều VM
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### 1. VM Creation Issues
|
||||
- Kiểm tra vCenter connectivity
|
||||
- Verify template availability
|
||||
- Check resource availability
|
||||
|
||||
### 2. Addon Application Issues
|
||||
- Verify addon quantities
|
||||
- Check API responses
|
||||
- Review logs for errors
|
||||
|
||||
### 3. Service Detail Page Issues
|
||||
- Check vCenter login
|
||||
- Verify VM ID exists
|
||||
- Review API responses
|
||||
|
||||
## Conclusion
|
||||
|
||||
Các thay đổi này đã cải thiện đáng kể hệ thống quản lý VM:
|
||||
- Tự động áp dụng addon sau khi tạo VM
|
||||
- Hiển thị thông tin VM thực từ vCenter
|
||||
- Giao diện quản lý service chi tiết
|
||||
- Monitoring và logging tốt hơn
|
||||
|
||||
Hệ thống hiện tại đã sẵn sàng cho production với các tính năng cơ bản hoạt động ổn định.
|
||||
@@ -48,8 +48,6 @@ class ServiceController extends Controller
|
||||
return redirect()->route('service.index')->with('error', 'Bạn không có quyền truy cập dịch vụ này.');
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Lấy thông tin VM nếu có - WITH CACHING AND BACKGROUND UPDATE
|
||||
$vmInfo = null;
|
||||
if ($invoice->vm_id) {
|
||||
@@ -234,10 +232,10 @@ class ServiceController extends Controller
|
||||
/**
|
||||
* Action để bật/tắt VM (placeholder - chưa implement)
|
||||
*/
|
||||
public function toggleVM(Request $request, $invoice_id)
|
||||
public function toggleVM(Request $request, $invoice_ordernum)
|
||||
{
|
||||
try {
|
||||
$invoice = Invoice::findOrFail($invoice_id);
|
||||
$invoice = Invoice::where('order_num', $invoice_ordernum)->first();
|
||||
|
||||
// Kiểm tra quyền truy cập
|
||||
if ($invoice->customer_id !== customer()->id) {
|
||||
@@ -261,7 +259,7 @@ class ServiceController extends Controller
|
||||
return response()->json($response);
|
||||
} catch (\Exception $e) {
|
||||
// Log::error('Error in ServiceController@toggleVM: ' . $e->getMessage());
|
||||
return response()->json(['error' => true, 'message' => 'Có lỗi xảy ra khi thực hiện thao tác.']);
|
||||
return response()->json(['error' => true, 'message' => 'Có lỗi xảy ra khi thực hiện thao tác. ' . $e->getMessage()]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,199 +431,110 @@ class ServiceController extends Controller
|
||||
/**
|
||||
* Secure get snapshots using session authentication - NO BEARER TOKEN
|
||||
*/
|
||||
/**
|
||||
* Lấy danh sách snapshot với bảo mật và tối ưu hóa
|
||||
*/
|
||||
public function getSnapshots(Request $request, vCenter $serviceVCenter)
|
||||
{
|
||||
try {
|
||||
// Kiểm tra authentication
|
||||
$user = customer();
|
||||
if (!$user) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => 'Chưa đăng nhập. Vui lòng đăng nhập để sử dụng tính năng này.',
|
||||
'debug' => 'User not authenticated via session'
|
||||
'message' => __('Chưa đăng nhập. Vui lòng đăng nhập để sử dụng tính năng này.')
|
||||
], 401);
|
||||
}
|
||||
|
||||
// Validation với sanitization
|
||||
$validator = Validator::make($request->all(), [
|
||||
'product_id' => 'required|string',
|
||||
'product_id' => 'required|string|max:255',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => 'Dữ liệu không hợp lệ',
|
||||
'message' => __('Dữ liệu không hợp lệ'),
|
||||
'errors' => $validator->errors()
|
||||
], 422);
|
||||
}
|
||||
|
||||
$productId = $request->product_id;
|
||||
$productId = $this->sanitizeInput($request->product_id);
|
||||
$userId = $user->id;
|
||||
|
||||
Log::info('🔍 Debug - Tìm kiếm VPS để lấy snapshot', [
|
||||
'product_id' => $productId,
|
||||
'user_id' => $userId
|
||||
]);
|
||||
// Cache key cho snapshot data
|
||||
$cacheKey = "snapshots_{$userId}_{$productId}";
|
||||
$cacheDuration = 300; // 5 phút
|
||||
|
||||
$product = ModuleVmware::with('ipAddress.host.datacenter.vCenter')
|
||||
->where('vmware_id', $productId)
|
||||
->where('customer_id', $userId)
|
||||
->first();
|
||||
// Thử lấy từ cache trước
|
||||
if (Cache::has($cacheKey)) {
|
||||
$cachedData = Cache::get($cacheKey);
|
||||
if ($cachedData && !$this->isCacheExpired($cachedData)) {
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => __('Lấy danh sách snapshot thành công'),
|
||||
'data' => $cachedData,
|
||||
'cached' => true
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Tìm VPS với eager loading tối ưu
|
||||
$product = $this->findVmwareProduct($productId, $userId);
|
||||
if (!$product) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => "Không tìm thấy VPS #{$productId} hoặc bạn không có quyền truy cập",
|
||||
'debug' => 'product not found or unauthorized'
|
||||
'message' => __('Không tìm thấy VPS hoặc bạn không có quyền truy cập')
|
||||
], 404);
|
||||
}
|
||||
|
||||
if (!$product->ipAddress) {
|
||||
// Kiểm tra cấu hình vCenter
|
||||
$vcenter = $this->validateVcenterConfiguration($product);
|
||||
if (!$vcenter) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => "VPS #{$productId} không có thông tin IP được cấu hình",
|
||||
'debug' => 'product->ipAddress is null'
|
||||
'message' => __('Cấu hình vCenter không hợp lệ')
|
||||
], 400);
|
||||
}
|
||||
|
||||
if (!$product->ipAddress->host) {
|
||||
// Lấy snapshot từ vCenter
|
||||
$snapshots = $this->fetchSnapshotsFromVCenter($serviceVCenter, $product, $vcenter);
|
||||
if ($snapshots === false) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => "VPS #{$productId} không có thông tin Host được cấu hình",
|
||||
'debug' => 'product->ipAddress->host is null'
|
||||
], 400);
|
||||
}
|
||||
|
||||
if (!$product->ipAddress->host->datacenter) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => "VPS #{$productId} không có thông tin Datacenter được cấu hình",
|
||||
'debug' => 'product->ipAddress->host->datacenter is null'
|
||||
], 400);
|
||||
}
|
||||
|
||||
if (!$product->ipAddress->host->datacenter->vCenter) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => "VPS #{$productId} không có thông tin vCenter được cấu hình",
|
||||
'debug' => 'product->ipAddress->host->datacenter->vCenter is null'
|
||||
], 400);
|
||||
}
|
||||
|
||||
// Get vCenter configuration
|
||||
$vcenter = $product->ipAddress->host->datacenter->vCenter;
|
||||
|
||||
// Login to vCenter
|
||||
if (!$serviceVCenter->loginServer($vcenter)) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => 'Không thể đăng nhập vào vCenter'
|
||||
'message' => __('Không thể kết nối đến vCenter')
|
||||
], 500);
|
||||
}
|
||||
|
||||
Log::info('�� Bắt đầu lấy danh sách snapshot', [
|
||||
'product_id' => $productId,
|
||||
'product_name' => $product->name,
|
||||
'vmware_id' => $product->vmware_id,
|
||||
'vcenter_host' => $product->ipAddress->host->datacenter->vCenter->host ?? 'null',
|
||||
'vcenter_user' => $product->ipAddress->host->datacenter->vCenter->user ?? 'null'
|
||||
]);
|
||||
// Lấy thông tin bổ sung từ database
|
||||
$dbSnapshots = $this->getDatabaseSnapshots($userId, $product->id);
|
||||
|
||||
// Check if getSnapShot method exists
|
||||
if (!method_exists($serviceVCenter, 'getSnapShot')) {
|
||||
Log::error('❌ getSnapShot method không tồn tại trong ServiceVCenter');
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => 'getSnapShot method không tồn tại'
|
||||
], 500);
|
||||
}
|
||||
// Merge và sanitize data
|
||||
$processedSnapshots = $this->processSnapshotsData($snapshots, $dbSnapshots);
|
||||
|
||||
$result = $serviceVCenter->getSnapShot($product);
|
||||
Log::info('✅ getSnapShot function đã được gọi');
|
||||
// Chuẩn bị response data
|
||||
$responseData = [
|
||||
'snapshots' => $processedSnapshots,
|
||||
'total_count' => count($processedSnapshots),
|
||||
'vm_name' => $this->sanitizeOutput($product->name),
|
||||
'vm_id' => $product->vmware_id,
|
||||
'last_updated' => now()->format('Y-m-d H:i:s')
|
||||
];
|
||||
|
||||
Log::info('📸 Kết quả getSnapShot', [
|
||||
'result' => $result,
|
||||
'product_id' => $productId
|
||||
]);
|
||||
|
||||
if ($result === "Login_Again") {
|
||||
if ($serviceVCenter->loginServer($vcenter)) {
|
||||
$result = $serviceVCenter->getSnapShot($product);
|
||||
} else {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => 'Không thể đăng nhập lại vào vCenter'
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_string($result)) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => "Lỗi lấy snapshot: $result"
|
||||
], 500);
|
||||
}
|
||||
|
||||
if (isset($result['error']) && $result['error']) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => "Lấy snapshot thất bại: " . ($result['message'] ?? 'Unknown error')
|
||||
], 500);
|
||||
}
|
||||
|
||||
// Get snapshots from database for additional info
|
||||
$dbSnapshots = ModuleSnapshot::where('customer_id', $user->id)
|
||||
->where('module_vmware_id', $product->id)
|
||||
->get()
|
||||
->keyBy('name');
|
||||
|
||||
// Merge vCenter data with database data
|
||||
$snapshots = [];
|
||||
if (isset($result['data']) && is_array($result['data'])) {
|
||||
foreach ($result['data'] as $snapshot) {
|
||||
$snapshotName = $snapshot['name'] ?? $snapshot['Name'] ?? 'Unknown';
|
||||
$dbSnapshot = $dbSnapshots->get($snapshotName);
|
||||
|
||||
$snapshots[] = [
|
||||
'name' => $snapshotName,
|
||||
'description' => $snapshot['description'] ?? $snapshot['Description'] ?? '',
|
||||
'created' => $snapshot['created'] ?? $snapshot['Created'] ?? '',
|
||||
'power_state' => $snapshot['power_state'] ?? $snapshot['PowerState'] ?? '',
|
||||
'size_mb' => $snapshot['size_mb'] ?? $snapshot['SizeMB'] ?? 0,
|
||||
'size_gb' => $snapshot['size_gb'] ?? $snapshot['SizeGB'] ?? 0,
|
||||
'vm_name' => $snapshot['vm_name'] ?? $snapshot['VM'] ?? '',
|
||||
'vm_id' => $snapshot['vm_id'] ?? $snapshot['VMId'] ?? '',
|
||||
'parent' => $snapshot['parent'] ?? $snapshot['Parent'] ?? '',
|
||||
'parent_snapshot_id' => $snapshot['parent_snapshot_id'] ?? $snapshot['ParentSnapshotId'] ?? '',
|
||||
'parent_snapshot' => $snapshot['parent_snapshot'] ?? $snapshot['ParentSnapshot'] ?? '',
|
||||
'children' => $snapshot['children'] ?? $snapshot['Children'] ?? '',
|
||||
'is_current' => $snapshot['is_current'] ?? $snapshot['IsCurrent'] ?? false,
|
||||
'is_replay_supported' => $snapshot['is_replay_supported'] ?? $snapshot['IsReplaySupported'] ?? false,
|
||||
'snapshot_id' => $snapshot['id'] ?? $snapshot['Id'] ?? '',
|
||||
'uid' => $snapshot['uid'] ?? $snapshot['Uid'] ?? '',
|
||||
'in_database' => $dbSnapshot ? true : false,
|
||||
'db_created_at' => $dbSnapshot ? $dbSnapshot->created_at->format('d/m/Y H:i:s') : null,
|
||||
'raw_data' => $snapshot
|
||||
];
|
||||
}
|
||||
}
|
||||
// Cache kết quả
|
||||
Cache::put($cacheKey, $responseData, $cacheDuration);
|
||||
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => 'Lấy danh sách snapshot thành công',
|
||||
'data' => [
|
||||
'snapshots' => $snapshots,
|
||||
'total_count' => count($snapshots),
|
||||
'vm_name' => $product->name,
|
||||
'vm_id' => $product->vmware_id,
|
||||
'cache_info' => [
|
||||
'cached' => false,
|
||||
'cache_key' => "snapshots_{$product->id}_{$product->name}"
|
||||
]
|
||||
]
|
||||
'message' => __('Lấy danh sách snapshot thành công'),
|
||||
'data' => $responseData,
|
||||
'cached' => false
|
||||
]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('❌ Lỗi khi lấy danh sách snapshot', [
|
||||
'product_id' => $request->product_id ?? 'not_provided',
|
||||
Log::error('Lỗi khi lấy danh sách snapshot', [
|
||||
'user_id' => $user->id ?? 'guest',
|
||||
'product_id' => $request->product_id ?? 'not_provided',
|
||||
'error' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine()
|
||||
@@ -633,73 +542,7 @@ class ServiceController extends Controller
|
||||
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => 'Lỗi hệ thống khi lấy danh sách snapshot: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Secure start VM using session authentication - NO BEARER TOKEN
|
||||
*/
|
||||
public function startVM(Request $request, vCenter $vCenterService)
|
||||
{
|
||||
try {
|
||||
// Use session authentication - NO Bearer token required
|
||||
$user = customer(); // This uses session
|
||||
if (!$user) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => 'Chưa đăng nhập. Vui lòng đăng nhập để sử dụng tính năng này.'
|
||||
], 401);
|
||||
}
|
||||
|
||||
// Implement start VM logic here
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => 'Chức năng start VM sẽ được thêm vào'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('❌ Lỗi trong secure startVM', [
|
||||
'user_id' => $user->id ?? 'guest',
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => 'Lỗi hệ thống: ' . $e->getMessage()
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Secure stop VM using session authentication - NO BEARER TOKEN
|
||||
*/
|
||||
public function stopVM(Request $request, vCenter $vCenterService)
|
||||
{
|
||||
try {
|
||||
// Use session authentication - NO Bearer token required
|
||||
$user = customer(); // This uses session
|
||||
if (!$user) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => 'Chưa đăng nhập. Vui lòng đăng nhập để sử dụng tính năng này.'
|
||||
], 401);
|
||||
}
|
||||
|
||||
// Implement stop VM logic here
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => 'Chức năng stop VM sẽ được thêm vào'
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
Log::error('❌ Lỗi trong secure stopVM', [
|
||||
'user_id' => $user->id ?? 'guest',
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => 'Lỗi hệ thống: ' . $e->getMessage()
|
||||
'message' => __('Có lỗi xảy ra khi lấy danh sách snapshot')
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
@@ -887,4 +730,345 @@ class ServiceController extends Controller
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Secure start VM using session authentication - NO BEARER TOKEN
|
||||
*/
|
||||
public function startVM(Request $request, vCenter $vCenterService)
|
||||
{
|
||||
try {
|
||||
$user = customer();
|
||||
if (!$user) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Chưa đăng nhập. Vui lòng đăng nhập để sử dụng tính năng này.')
|
||||
], 401);
|
||||
}
|
||||
|
||||
$validator = \Validator::make($request->all(), [
|
||||
'product_id' => 'required|string|max:255',
|
||||
]);
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Dữ liệu không hợp lệ'),
|
||||
'errors' => $validator->errors()
|
||||
], 422);
|
||||
}
|
||||
|
||||
$productId = htmlspecialchars(strip_tags(trim($request->product_id)), ENT_QUOTES, 'UTF-8');
|
||||
$userId = $user->id;
|
||||
|
||||
$product = ModuleVmware::where('vmware_id', $productId)
|
||||
->where('customer_id', $userId)
|
||||
->first();
|
||||
if (!$product) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Không tìm thấy VPS hoặc bạn không có quyền truy cập')
|
||||
], 404);
|
||||
}
|
||||
|
||||
$result = $vCenterService->StartVirtualMachine($product, $product->vmware_id);
|
||||
if (is_array($result) && isset($result['error']) && $result['error'] === false) {
|
||||
// Clear VM info cache after starting VM
|
||||
\Cache::forget('vm_info_' . $product->vmware_id);
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => __('Yêu cầu khởi động máy ảo đã được gửi thành công.')
|
||||
]);
|
||||
} else {
|
||||
$msg = is_array($result) && isset($result['data']) ? $result['data'] : __('Không thể khởi động máy ảo');
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => $msg
|
||||
], 500);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Lỗi khi khởi động máy ảo', [
|
||||
'user_id' => $user->id ?? 'guest',
|
||||
'product_id' => $request->product_id ?? 'not_provided',
|
||||
'error' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine()
|
||||
]);
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Có lỗi xảy ra khi khởi động máy ảo')
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Secure stop VM using session authentication - NO BEARER TOKEN
|
||||
*/
|
||||
public function stopVM(Request $request, vCenter $vCenterService)
|
||||
{
|
||||
try {
|
||||
$user = customer();
|
||||
if (!$user) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Chưa đăng nhập. Vui lòng đăng nhập để sử dụng tính năng này.')
|
||||
], 401);
|
||||
}
|
||||
|
||||
$validator = \Validator::make($request->all(), [
|
||||
'product_id' => 'required|string|max:255',
|
||||
]);
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Dữ liệu không hợp lệ'),
|
||||
'errors' => $validator->errors()
|
||||
], 422);
|
||||
}
|
||||
|
||||
$productId = htmlspecialchars(strip_tags(trim($request->product_id)), ENT_QUOTES, 'UTF-8');
|
||||
$userId = $user->id;
|
||||
|
||||
$product = ModuleVmware::where('vmware_id', $productId)
|
||||
->where('customer_id', $userId)
|
||||
->first();
|
||||
if (!$product) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Không tìm thấy VPS hoặc bạn không có quyền truy cập')
|
||||
], 404);
|
||||
}
|
||||
|
||||
$result = $vCenterService->ShutdownGuestVirtualMachine($product, $product->vmware_id);
|
||||
if (is_array($result) && isset($result['error']) && $result['error'] === false) {
|
||||
// Clear VM info cache after stopping VM
|
||||
\Cache::forget('vm_info_' . $product->vmware_id);
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => __('Yêu cầu tắt máy ảo đã được gửi thành công.')
|
||||
]);
|
||||
} else {
|
||||
$msg = is_array($result) && isset($result['data']) ? $result['data'] : __('Không thể tắt máy ảo');
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => $msg
|
||||
], 500);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Lỗi khi tắt máy ảo', [
|
||||
'user_id' => $user->id ?? 'guest',
|
||||
'product_id' => $request->product_id ?? 'not_provided',
|
||||
'error' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine()
|
||||
]);
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Có lỗi xảy ra khi tắt máy ảo')
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Secure reboot VM using session authentication - NO BEARER TOKEN
|
||||
*/
|
||||
public function rebootVM(Request $request, vCenter $vCenterService)
|
||||
{
|
||||
try {
|
||||
$user = customer();
|
||||
if (!$user) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Chưa đăng nhập. Vui lòng đăng nhập để sử dụng tính năng này.')
|
||||
], 401);
|
||||
}
|
||||
|
||||
$validator = \Validator::make($request->all(), [
|
||||
'product_id' => 'required|string|max:255',
|
||||
]);
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Dữ liệu không hợp lệ'),
|
||||
'errors' => $validator->errors()
|
||||
], 422);
|
||||
}
|
||||
|
||||
$productId = htmlspecialchars(strip_tags(trim($request->product_id)), ENT_QUOTES, 'UTF-8');
|
||||
$userId = $user->id;
|
||||
|
||||
$product = ModuleVmware::where('vmware_id', $productId)
|
||||
->where('customer_id', $userId)
|
||||
->first();
|
||||
if (!$product) {
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Không tìm thấy VPS hoặc bạn không có quyền truy cập')
|
||||
], 404);
|
||||
}
|
||||
|
||||
$result = $vCenterService->RebootVirtualMachine($product, $product->vmware_id);
|
||||
if (is_array($result) && isset($result['error']) && $result['error'] === false) {
|
||||
return response()->json([
|
||||
'error' => false,
|
||||
'message' => __('Yêu cầu khởi động lại máy ảo đã được gửi thành công.')
|
||||
]);
|
||||
} else {
|
||||
$msg = is_array($result) && isset($result['data']) ? $result['data'] : __('Không thể khởi động lại máy ảo');
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => $msg
|
||||
], 500);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
\Log::error('Lỗi khi reboot VM', [
|
||||
'user_id' => $user->id ?? 'guest',
|
||||
'product_id' => $request->product_id ?? 'not_provided',
|
||||
'error' => $e->getMessage(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine()
|
||||
]);
|
||||
return response()->json([
|
||||
'error' => true,
|
||||
'message' => __('Có lỗi xảy ra khi khởi động lại máy ảo')
|
||||
], 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize input data
|
||||
*/
|
||||
private function sanitizeInput($input): string
|
||||
{
|
||||
return htmlspecialchars(strip_tags(trim($input)), ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize output data
|
||||
*/
|
||||
private function sanitizeOutput($output): string
|
||||
{
|
||||
return htmlspecialchars($output, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Kiểm tra cache có hết hạn chưa
|
||||
*/
|
||||
private function isCacheExpired($cachedData): bool
|
||||
{
|
||||
if (!isset($cachedData['last_updated'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$lastUpdated = \Carbon\Carbon::parse($cachedData['last_updated']);
|
||||
return now()->diffInMinutes($lastUpdated) > 2; // Cache 2 phút
|
||||
}
|
||||
|
||||
/**
|
||||
* Tìm VPS với eager loading
|
||||
*/
|
||||
private function findVmwareProduct($productId, $userId)
|
||||
{
|
||||
return ModuleVmware::with(['ipAddress.host.datacenter.vCenter'])
|
||||
->where('vmware_id', $productId)
|
||||
->where('customer_id', $userId)
|
||||
->first();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate vCenter configuration
|
||||
*/
|
||||
private function validateVcenterConfiguration($product)
|
||||
{
|
||||
if (!$product->ipAddress?->host?->datacenter?->vCenter) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $product->ipAddress->host->datacenter->vCenter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lấy snapshot từ vCenter
|
||||
*/
|
||||
private function fetchSnapshotsFromVCenter($serviceVCenter, $product, $vcenter)
|
||||
{
|
||||
try {
|
||||
// Login to vCenter
|
||||
if (!$serviceVCenter->loginServer($vcenter)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Kiểm tra method tồn tại
|
||||
if (!method_exists($serviceVCenter, 'getSnapShot')) {
|
||||
Log::error('getSnapShot method không tồn tại trong ServiceVCenter');
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = $serviceVCenter->getSnapShot($product);
|
||||
|
||||
// Retry logic
|
||||
if ($result === "Login_Again") {
|
||||
if ($serviceVCenter->loginServer($vcenter)) {
|
||||
$result = $serviceVCenter->getSnapShot($product);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Validate result
|
||||
if (is_string($result) || (isset($result['error']) && $result['error'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $result['data'] ?? [];
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Lỗi khi lấy snapshot từ vCenter', [
|
||||
'product_id' => $product->id,
|
||||
'error' => $e->getMessage()
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lấy snapshot từ database
|
||||
*/
|
||||
private function getDatabaseSnapshots($userId, $productId)
|
||||
{
|
||||
return ModuleSnapshot::where('customer_id', $userId)
|
||||
->where('module_vmware_id', $productId)
|
||||
->get()
|
||||
->keyBy('name');
|
||||
}
|
||||
|
||||
/**
|
||||
* Xử lý và sanitize snapshot data
|
||||
*/
|
||||
private function processSnapshotsData($snapshots, $dbSnapshots): array
|
||||
{
|
||||
$processed = [];
|
||||
|
||||
if (!is_array($snapshots)) {
|
||||
return $processed;
|
||||
}
|
||||
|
||||
foreach ($snapshots as $snapshot) {
|
||||
$snapshotName = $this->sanitizeOutput($snapshot['name'] ?? $snapshot['Name'] ?? 'Unknown');
|
||||
$dbSnapshot = $dbSnapshots->get($snapshotName);
|
||||
|
||||
$processed[] = [
|
||||
'name' => $snapshotName,
|
||||
'description' => $this->sanitizeOutput($snapshot['description'] ?? $snapshot['Description'] ?? ''),
|
||||
'created' => $this->sanitizeOutput($snapshot['created'] ?? $snapshot['Created'] ?? ''),
|
||||
'power_state' => $this->sanitizeOutput($snapshot['power_state'] ?? $snapshot['PowerState'] ?? ''),
|
||||
'size_mb' => (int) ($snapshot['size_mb'] ?? $snapshot['SizeMB'] ?? 0),
|
||||
'size_gb' => (float) ($snapshot['size_gb'] ?? $snapshot['SizeGB'] ?? 0),
|
||||
'is_current' => (bool) ($snapshot['is_current'] ?? $snapshot['IsCurrent'] ?? false),
|
||||
'is_replay_supported' => (bool) ($snapshot['is_replay_supported'] ?? $snapshot['IsReplaySupported'] ?? false),
|
||||
'in_database' => $dbSnapshot ? true : false,
|
||||
'db_created_at' => $dbSnapshot ? $dbSnapshot->created_at->format('d/m/Y H:i:s') : null
|
||||
];
|
||||
}
|
||||
|
||||
return $processed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1201,10 +1201,10 @@ class vCenter
|
||||
return "Login_Again";
|
||||
}
|
||||
|
||||
public function RebootVirtualMachine($vmware_id = ''): array|string
|
||||
public function RebootVirtualMachine($product, $vmware_id = ''): array|string
|
||||
{
|
||||
|
||||
$check = $this->checkApiExits();
|
||||
$check = $this->loginServer($product->ipaddress->host->datacenter->vCenter);
|
||||
if ($check) {
|
||||
$response = Http::withoutVerifying()
|
||||
->withHeaders([
|
||||
@@ -1225,10 +1225,10 @@ class vCenter
|
||||
return "Login_Again";
|
||||
}
|
||||
|
||||
public function StartVirtualMachine($vmware_id = ''): array|string
|
||||
public function StartVirtualMachine($product, $vmware_id = ''): array|string
|
||||
{
|
||||
|
||||
$check = $this->checkApiExits();
|
||||
$check = $this->loginServer($product->ipaddress->host->datacenter->vCenter);
|
||||
if ($check) {
|
||||
$response = Http::withoutVerifying()
|
||||
->withHeaders([
|
||||
@@ -1249,9 +1249,9 @@ class vCenter
|
||||
return "Login_Again";
|
||||
}
|
||||
|
||||
public function ShutdownGuestVirtualMachine($vmware_id = ''): array|string
|
||||
public function ShutdownGuestVirtualMachine($product, $vmware_id = ''): array|string
|
||||
{
|
||||
$check = $this->checkApiExits();
|
||||
$check = $this->loginServer($product->ipaddress->host->datacenter->vCenter);
|
||||
|
||||
if ($check) {
|
||||
$response = Http::withoutVerifying()
|
||||
@@ -1391,7 +1391,7 @@ class vCenter
|
||||
public function getSnapShot($product): string|array
|
||||
{
|
||||
$startTime = microtime(true);
|
||||
|
||||
|
||||
try {
|
||||
$this->username = $product->ipaddress->host->datacenter->vCenter->user;
|
||||
$this->password = SignRSA::DecryptFunc($product->ipaddress->host->datacenter->vCenter->password);
|
||||
@@ -1399,21 +1399,21 @@ class vCenter
|
||||
} catch (\Exception $e) {
|
||||
return ['error' => true, 'message' => 'Lỗi setup credentials: ' . $e->getMessage()];
|
||||
}
|
||||
|
||||
|
||||
$vmware_name = (string) $product->name;
|
||||
|
||||
|
||||
// Check cache first
|
||||
$cacheKey = "snapshots_{$product->id}_{$vmware_name}";
|
||||
$cachedResult = Cache::get($cacheKey);
|
||||
|
||||
|
||||
if ($cachedResult !== null) {
|
||||
$executionTime = round((microtime(true) - $startTime) * 1000, 2);
|
||||
Log::channel('Custom')->info("📸 Snapshot cache hit - Execution time: {$executionTime}ms");
|
||||
return $cachedResult;
|
||||
}
|
||||
|
||||
|
||||
$check = $this->checkApiExits();
|
||||
|
||||
|
||||
if ($check) {
|
||||
try {
|
||||
Log::channel('Custom')->info("===================================================================");
|
||||
@@ -1440,22 +1440,22 @@ class vCenter
|
||||
try {
|
||||
// Clean up the result - remove any PowerShell formatting
|
||||
$cleanResult = trim($result);
|
||||
|
||||
|
||||
// Parse the text output from the script
|
||||
$snapshots = $this->parseSnapshotTextOutput($cleanResult);
|
||||
|
||||
|
||||
if (!empty($snapshots)) {
|
||||
$response = [
|
||||
'error' => false,
|
||||
'data' => $snapshots
|
||||
];
|
||||
|
||||
|
||||
// Cache the result for 5 minutes
|
||||
Cache::put($cacheKey, $response, now()->addMinutes(5));
|
||||
|
||||
|
||||
$totalTime = round((microtime(true) - $startTime) * 1000, 2);
|
||||
Log::channel('Custom')->info("📸 Snapshot retrieved successfully - Total time: {$totalTime}ms, Command time: {$commandTime}ms");
|
||||
|
||||
|
||||
return $response;
|
||||
} else {
|
||||
$response = [
|
||||
@@ -1463,10 +1463,10 @@ class vCenter
|
||||
'message' => 'Không tìm thấy snapshot nào hoặc lỗi parse dữ liệu',
|
||||
'raw_result' => $result
|
||||
];
|
||||
|
||||
|
||||
$totalTime = round((microtime(true) - $startTime) * 1000, 2);
|
||||
Log::channel('Custom')->info("📸 No snapshots found - Total time: {$totalTime}ms");
|
||||
|
||||
|
||||
return $response;
|
||||
}
|
||||
} catch (\Exception $parseError) {
|
||||
@@ -1476,10 +1476,10 @@ class vCenter
|
||||
'message' => 'Lỗi parse kết quả snapshot: ' . $parseError->getMessage(),
|
||||
'raw_result' => $result
|
||||
];
|
||||
|
||||
|
||||
$totalTime = round((microtime(true) - $startTime) * 1000, 2);
|
||||
Log::channel('Custom')->info("📸 Parse error - Total time: {$totalTime}ms");
|
||||
|
||||
|
||||
return $response;
|
||||
}
|
||||
} else {
|
||||
@@ -1488,10 +1488,10 @@ class vCenter
|
||||
'message' => 'Không thể thực thi lệnh hoặc đăng nhập thất bại',
|
||||
'raw_result' => $result
|
||||
];
|
||||
|
||||
|
||||
$totalTime = round((microtime(true) - $startTime) * 1000, 2);
|
||||
Log::channel('Custom')->info("📸 Command failed - Total time: {$totalTime}ms");
|
||||
|
||||
|
||||
return $response;
|
||||
}
|
||||
} catch (\Exception $error) {
|
||||
@@ -1500,20 +1500,20 @@ class vCenter
|
||||
Log::channel('Custom')->info("Error Log: ");
|
||||
Log::channel('Custom')->info($error->getMessage());
|
||||
Log::channel('Custom')->info("===================================================================");
|
||||
|
||||
|
||||
$totalTime = round((microtime(true) - $startTime) * 1000, 2);
|
||||
Log::channel('Custom')->info("📸 Exception occurred - Total time: {$totalTime}ms");
|
||||
|
||||
|
||||
return [
|
||||
'error' => true,
|
||||
'message' => $error->getMessage()
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$totalTime = round((microtime(true) - $startTime) * 1000, 2);
|
||||
Log::channel('Custom')->info("📸 Login again required - Total time: {$totalTime}ms");
|
||||
|
||||
|
||||
return "Login_Again";
|
||||
}
|
||||
|
||||
@@ -1767,7 +1767,8 @@ class vCenter
|
||||
// Kiểm tra hệ điều hành
|
||||
if (str_contains($this->os, "Windows")) {
|
||||
$user = $data['user'] ?? 'administartor';
|
||||
$this->process = new WinRMClient($ip, $user, $password);;
|
||||
$this->process = new WinRMClient($ip, $user, $password);
|
||||
;
|
||||
return $this->process;
|
||||
}
|
||||
|
||||
@@ -2129,35 +2130,39 @@ class vCenter
|
||||
private function parseSnapshotTextOutput($output): array
|
||||
{
|
||||
$snapshots = [];
|
||||
|
||||
|
||||
// Split the output into individual snapshot blocks
|
||||
$blocks = preg_split('/\n\s*\n/', $output);
|
||||
|
||||
|
||||
foreach ($blocks as $block) {
|
||||
$block = trim($block);
|
||||
if (empty($block)) continue;
|
||||
|
||||
if (empty($block))
|
||||
continue;
|
||||
|
||||
// Skip header lines and warnings
|
||||
if (strpos($block, 'Name Port User') !== false ||
|
||||
if (
|
||||
strpos($block, 'Name Port User') !== false ||
|
||||
strpos($block, '---- ---- ----') !== false ||
|
||||
strpos($block, 'WARNING:') !== false) {
|
||||
strpos($block, 'WARNING:') !== false
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
$snapshot = [];
|
||||
|
||||
|
||||
// Parse each line of the snapshot block
|
||||
$lines = explode("\n", $block);
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if (empty($line)) continue;
|
||||
|
||||
if (empty($line))
|
||||
continue;
|
||||
|
||||
// Parse key-value pairs
|
||||
if (strpos($line, ' : ') !== false) {
|
||||
list($key, $value) = explode(' : ', $line, 2);
|
||||
$key = trim($key);
|
||||
$value = trim($value);
|
||||
|
||||
|
||||
switch ($key) {
|
||||
case 'Name':
|
||||
$snapshot['name'] = $value;
|
||||
@@ -2214,13 +2219,13 @@ class vCenter
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Only add if we have at least a name
|
||||
if (!empty($snapshot['name'])) {
|
||||
$snapshots[] = $snapshot;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $snapshots;
|
||||
}
|
||||
|
||||
@@ -2230,14 +2235,15 @@ class vCenter
|
||||
private function parseSnapshotOutput($output): array
|
||||
{
|
||||
$snapshots = [];
|
||||
|
||||
|
||||
// Split by lines and look for snapshot information
|
||||
$lines = explode("\n", $output);
|
||||
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$line = trim($line);
|
||||
if (empty($line)) continue;
|
||||
|
||||
if (empty($line))
|
||||
continue;
|
||||
|
||||
// Look for snapshot name patterns
|
||||
if (preg_match('/(\w+_\d{2}_\d{2}_\d{4}_\d{2}_\d{2}_\d{2})/', $line, $matches)) {
|
||||
$snapshots[] = [
|
||||
@@ -2246,7 +2252,7 @@ class vCenter
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return $snapshots;
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -765,5 +765,8 @@
|
||||
"Balance": "Số dư",
|
||||
"Invoices": "Hoá đơn",
|
||||
"Support": "Hỗ trợ",
|
||||
"Domains": "Tên miền"
|
||||
"Domains": "Tên miền",
|
||||
"RUNNING": "Đang chạy",
|
||||
"STOPPED": "Đã tắt",
|
||||
"UNKNOWN": "Không xác định"
|
||||
}
|
||||
|
||||
+1
-17254
File diff suppressed because one or more lines are too long
+2
-317
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -1 +1 @@
|
||||
var _0x3a4a2a=_0x5239;(function(_0xa04da8,_0x337dc2){var _0x113f1b=_0x5239,_0x3b2d4b=_0xa04da8();while(!![]){try{var _0x3f5860=parseInt(_0x113f1b(0x185))/0x1*(-parseInt(_0x113f1b(0x181))/0x2)+-parseInt(_0x113f1b(0x17b))/0x3*(-parseInt(_0x113f1b(0x189))/0x4)+-parseInt(_0x113f1b(0x188))/0x5*(parseInt(_0x113f1b(0x186))/0x6)+parseInt(_0x113f1b(0x182))/0x7*(parseInt(_0x113f1b(0x183))/0x8)+parseInt(_0x113f1b(0x17f))/0x9*(-parseInt(_0x113f1b(0x17d))/0xa)+-parseInt(_0x113f1b(0x184))/0xb+parseInt(_0x113f1b(0x179))/0xc;if(_0x3f5860===_0x337dc2)break;else _0x3b2d4b['push'](_0x3b2d4b['shift']());}catch(_0x30908f){_0x3b2d4b['push'](_0x3b2d4b['shift']());}}}(_0x5b67,0x45b72),window['_']=require(_0x3a4a2a(0x17e)),window[_0x3a4a2a(0x18a)]=require(_0x3a4a2a(0x18a)),window['axios'][_0x3a4a2a(0x17c)][_0x3a4a2a(0x17a)][_0x3a4a2a(0x187)][_0x3a4a2a(0x180)]='XMLHttpRequest');function _0x5239(_0x5a866e,_0x13eede){var _0x5b67eb=_0x5b67();return _0x5239=function(_0x52397b,_0x171c15){_0x52397b=_0x52397b-0x179;var _0x3a3b5f=_0x5b67eb[_0x52397b];return _0x3a3b5f;},_0x5239(_0x5a866e,_0x13eede);}function _0x5b67(){var _0x32445e=['lodash','690786kGILbI','X-Requested-With','13532cIZrfY','1890KtrMWS','2984geXXUG','4867918JEyRIg','5PEHzrQ','33186gDMqiO','common','120iNLXsd','4HweXxB','axios','8710980oQTXiY','headers','664647ExfpZF','defaults','20fqXJnL'];_0x5b67=function(){return _0x32445e;};return _0x5b67();}
|
||||
var _0x1762dc=_0x57d2;function _0x57d2(_0x5dbdf6,_0x4deba6){var _0x114d2a=_0x114d();return _0x57d2=function(_0x57d2ae,_0x5dd378){_0x57d2ae=_0x57d2ae-0xb2;var _0xc01e33=_0x114d2a[_0x57d2ae];return _0xc01e33;},_0x57d2(_0x5dbdf6,_0x4deba6);}function _0x114d(){var _0x4c0aa5=['216710MewCHs','63mjvrxI','204726ohtGgN','85mZIaQI','1454607NBeLaf','XMLHttpRequest','8xvChiS','262116HWIILm','axios','202936wXyZjM','2079740iYeTTy','9UNSxUP','66714xRPaeO'];_0x114d=function(){return _0x4c0aa5;};return _0x114d();}(function(_0x2878a2,_0x42b4a6){var _0x349482=_0x57d2,_0x5d2eee=_0x2878a2();while(!![]){try{var _0x4362f4=parseInt(_0x349482(0xb8))/0x1+-parseInt(_0x349482(0xbe))/0x2+-parseInt(_0x349482(0xb3))/0x3*(parseInt(_0x349482(0xb7))/0x4)+parseInt(_0x349482(0xb4))/0x5*(-parseInt(_0x349482(0xbd))/0x6)+parseInt(_0x349482(0xb2))/0x7*(parseInt(_0x349482(0xba))/0x8)+parseInt(_0x349482(0xbc))/0x9*(parseInt(_0x349482(0xbb))/0xa)+-parseInt(_0x349482(0xb5))/0xb;if(_0x4362f4===_0x42b4a6)break;else _0x5d2eee['push'](_0x5d2eee['shift']());}catch(_0x3c398f){_0x5d2eee['push'](_0x5d2eee['shift']());}}}(_0x114d,0x204c6),window['_']=require('lodash'),window[_0x1762dc(0xb9)]=require(_0x1762dc(0xb9)),window[_0x1762dc(0xb9)]['defaults']['headers']['common']['X-Requested-With']=_0x1762dc(0xb6));
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
const _0x28ab78=_0x3610;(function(_0xc6d945,_0x26b57c){const _0x50079e=_0x3610,_0x338e88=_0xc6d945();while(!![]){try{const _0x478eab=-parseInt(_0x50079e(0x144))/0x1*(parseInt(_0x50079e(0x155))/0x2)+parseInt(_0x50079e(0x13e))/0x3*(parseInt(_0x50079e(0x142))/0x4)+-parseInt(_0x50079e(0x140))/0x5+parseInt(_0x50079e(0x150))/0x6+-parseInt(_0x50079e(0x159))/0x7+parseInt(_0x50079e(0x15c))/0x8*(parseInt(_0x50079e(0x148))/0x9)+-parseInt(_0x50079e(0x146))/0xa*(-parseInt(_0x50079e(0x153))/0xb);if(_0x478eab===_0x26b57c)break;else _0x338e88['push'](_0x338e88['shift']());}catch(_0x2aa3a2){_0x338e88['push'](_0x338e88['shift']());}}}(_0x15a1,0x88c9d));function _0x15a1(){const _0x72825a=['os-selection','3915306ASCeYr','simulateAddonsLoading','querySelector','[data-skeleton-id=\x22','set','DOMContentLoaded','showMultipleSkeletons','loading','2228304XKWxwE','showSkeleton','skeleton','11hSzEBs','defaultLoadingTime','1382MIkyTX','exports','updateComponentState','undefined','6757786RbuaxP','domain-input','simulateCartLoading','8KPyKnH','product-details','addons-summary','forEach','alpine:init','_x_dataStack','cartSkeleton','plugin','isLoading','hideMultipleSkeletons','addEventListener','1297617PHiwXQ','magic','4811020xMbGvj','get','4YQRIQJ','loadingStates','283ETGvkB','hideSkeleton','14444830iVoUmt'];_0x15a1=function(){return _0x72825a;};return _0x15a1();}class CartSkeletonManager{constructor(){const _0x5be434=_0x3610;this[_0x5be434(0x143)]=new Map(),this[_0x5be434(0x154)]=0x3e8;}[_0x28ab78(0x151)](_0x52005a,_0x55c8ce=this['defaultLoadingTime']){const _0x39431c=_0x28ab78;this[_0x39431c(0x143)][_0x39431c(0x14c)](_0x52005a,!![]),this[_0x39431c(0x157)](_0x52005a,!![]),setTimeout(()=>{const _0x5aa2a7=_0x39431c;this[_0x5aa2a7(0x145)](_0x52005a);},_0x55c8ce);}['hideSkeleton'](_0x49f658){const _0x43ee4e=_0x28ab78;this['loadingStates'][_0x43ee4e(0x14c)](_0x49f658,![]),this[_0x43ee4e(0x157)](_0x49f658,![]);}['updateComponentState'](_0x194cb8,_0x492cb7){const _0x51c42f=_0x28ab78,_0x5b58e3=document['querySelector'](_0x51c42f(0x14b)+_0x194cb8+'\x22]');if(_0x5b58e3&&_0x5b58e3[_0x51c42f(0x161)]){const _0xb88ead=_0x5b58e3[_0x51c42f(0x161)][0x0];_0xb88ead&&typeof _0xb88ead[_0x51c42f(0x14f)]!==_0x51c42f(0x158)&&(_0xb88ead[_0x51c42f(0x14f)]=_0x492cb7);}}[_0x28ab78(0x14e)](_0x377227,_0x5e08d5=this['defaultLoadingTime']){const _0xc04339=_0x28ab78;_0x377227[_0xc04339(0x15f)](_0x5a7f86=>this['showSkeleton'](_0x5a7f86,_0x5e08d5));}[_0x28ab78(0x13c)](_0x48663c){const _0x845110=_0x28ab78;_0x48663c[_0x845110(0x15f)](_0x23da23=>this[_0x845110(0x145)](_0x23da23));}[_0x28ab78(0x164)](_0x393398){const _0x21d928=_0x28ab78;return this['loadingStates'][_0x21d928(0x141)](_0x393398)||![];}[_0x28ab78(0x15b)](){const _0x4bab8a=_0x28ab78,_0x4e8e0b=[_0x4bab8a(0x15a),_0x4bab8a(0x15d),_0x4bab8a(0x147),_0x4bab8a(0x15e)];this[_0x4bab8a(0x14e)](_0x4e8e0b,0x320);}['simulateOsLoading'](){const _0x47f504=_0x28ab78;this[_0x47f504(0x151)](_0x47f504(0x147),0x258);}[_0x28ab78(0x149)](){const _0x84f69a=_0x28ab78;this[_0x84f69a(0x151)]('addons-summary',0x1f4);}}window[_0x28ab78(0x162)]=new CartSkeletonManager(),document[_0x28ab78(0x13d)](_0x28ab78(0x160),()=>{const _0x240781=_0x28ab78;Alpine[_0x240781(0x163)](_0x240781(0x152),_0x13c9d7=>{const _0x48cb9d=_0x240781;_0x13c9d7[_0x48cb9d(0x13f)](_0x48cb9d(0x152),()=>({'show':(_0x1d5316,_0x40bbdb)=>window['cartSkeleton'][_0x48cb9d(0x151)](_0x1d5316,_0x40bbdb),'hide':_0x1aeb8e=>window['cartSkeleton'][_0x48cb9d(0x145)](_0x1aeb8e),'isLoading':_0x1d58c3=>window[_0x48cb9d(0x162)]['isLoading'](_0x1d58c3)}));});}),document[_0x28ab78(0x13d)](_0x28ab78(0x14d),()=>{const _0x46f35c=_0x28ab78;document[_0x46f35c(0x14a)]('[data-cart-page]')&&setTimeout(()=>{const _0x5ccaab=_0x46f35c;window['cartSkeleton'][_0x5ccaab(0x15b)]();},0x64);});function _0x3610(_0x5dd64f,_0x12b1f5){const _0x15a19b=_0x15a1();return _0x3610=function(_0x36106d,_0x31eef4){_0x36106d=_0x36106d-0x13c;let _0x57d2ee=_0x15a19b[_0x36106d];return _0x57d2ee;},_0x3610(_0x5dd64f,_0x12b1f5);}typeof module!=='undefined'&&module[_0x28ab78(0x156)]&&(module[_0x28ab78(0x156)]=CartSkeletonManager);
|
||||
const _0x296055=_0x29ee;function _0x2c68(){const _0x1f9344=['[data-skeleton-id=\x22','loadingStates','os-selection','loading','defaultLoadingTime','forEach','addons-summary','4wLKhDM','showMultipleSkeletons','simulateOsLoading','plugin','magic','319479kojkdt','simulateAddonsLoading','showSkeleton','updateComponentState','undefined','1239843aCGCuh','set','12TcGlaS','exports','domain-input','3064185gkzkBX','2366FqaXze','alpine:init','9368UrQMuz','1630740SBnQwr','8593002lqbhoT','hideSkeleton','addEventListener','isLoading','querySelector','_x_dataStack','70wWhrIx','cartSkeleton','229345ICFppG','simulateCartLoading'];_0x2c68=function(){return _0x1f9344;};return _0x2c68();}(function(_0x200766,_0xad9ac7){const _0x55fb1d=_0x29ee,_0xf0b472=_0x200766();while(!![]){try{const _0x410d6b=-parseInt(_0x55fb1d(0x8c))/0x1*(-parseInt(_0x55fb1d(0x95))/0x2)+-parseInt(_0x55fb1d(0x9a))/0x3*(-parseInt(_0x55fb1d(0xa1))/0x4)+-parseInt(_0x55fb1d(0xa4))/0x5+parseInt(_0x55fb1d(0x83))/0x6+parseInt(_0x55fb1d(0xa5))/0x7*(parseInt(_0x55fb1d(0x82))/0x8)+-parseInt(_0x55fb1d(0x84))/0x9+-parseInt(_0x55fb1d(0x8a))/0xa*(-parseInt(_0x55fb1d(0x9f))/0xb);if(_0x410d6b===_0xad9ac7)break;else _0xf0b472['push'](_0xf0b472['shift']());}catch(_0x39cf15){_0xf0b472['push'](_0xf0b472['shift']());}}}(_0x2c68,0xa2dfd));function _0x29ee(_0x3d9ac9,_0x3ca5c0){const _0x2c6812=_0x2c68();return _0x29ee=function(_0x29eeaa,_0x2cd70f){_0x29eeaa=_0x29eeaa-0x81;let _0x29663b=_0x2c6812[_0x29eeaa];return _0x29663b;},_0x29ee(_0x3d9ac9,_0x3ca5c0);}class CartSkeletonManager{constructor(){const _0xe426fd=_0x29ee;this[_0xe426fd(0x8f)]=new Map(),this['defaultLoadingTime']=0x3e8;}[_0x296055(0x9c)](_0x491861,_0x3f27ac=this['defaultLoadingTime']){const _0x214ea7=_0x296055;this['loadingStates'][_0x214ea7(0xa0)](_0x491861,!![]),this['updateComponentState'](_0x491861,!![]),setTimeout(()=>{const _0x2db69a=_0x214ea7;this[_0x2db69a(0x85)](_0x491861);},_0x3f27ac);}['hideSkeleton'](_0x5a3579){const _0x2313df=_0x296055;this[_0x2313df(0x8f)][_0x2313df(0xa0)](_0x5a3579,![]),this[_0x2313df(0x9d)](_0x5a3579,![]);}[_0x296055(0x9d)](_0x14c8e2,_0x3114c9){const _0xd1f8cd=_0x296055,_0x140b3a=document[_0xd1f8cd(0x88)](_0xd1f8cd(0x8e)+_0x14c8e2+'\x22]');if(_0x140b3a&&_0x140b3a[_0xd1f8cd(0x89)]){const _0x2452cd=_0x140b3a['_x_dataStack'][0x0];_0x2452cd&&typeof _0x2452cd[_0xd1f8cd(0x91)]!=='undefined'&&(_0x2452cd[_0xd1f8cd(0x91)]=_0x3114c9);}}[_0x296055(0x96)](_0x5a5d5f,_0x117f23=this[_0x296055(0x92)]){const _0x4904b4=_0x296055;_0x5a5d5f[_0x4904b4(0x93)](_0x330247=>this[_0x4904b4(0x9c)](_0x330247,_0x117f23));}['hideMultipleSkeletons'](_0x5b7627){const _0xf8cc03=_0x296055;_0x5b7627[_0xf8cc03(0x93)](_0x39bbf0=>this[_0xf8cc03(0x85)](_0x39bbf0));}['isLoading'](_0x1a4f1a){const _0x20b30d=_0x296055;return this[_0x20b30d(0x8f)]['get'](_0x1a4f1a)||![];}[_0x296055(0x8d)](){const _0x156e0e=_0x296055,_0x44401c=[_0x156e0e(0xa3),'product-details','os-selection',_0x156e0e(0x94)];this[_0x156e0e(0x96)](_0x44401c,0x320);}[_0x296055(0x97)](){const _0x209ea9=_0x296055;this[_0x209ea9(0x9c)](_0x209ea9(0x90),0x258);}[_0x296055(0x9b)](){this['showSkeleton']('addons-summary',0x1f4);}}window[_0x296055(0x8b)]=new CartSkeletonManager(),document[_0x296055(0x86)](_0x296055(0x81),()=>{const _0x3710ca=_0x296055;Alpine[_0x3710ca(0x98)]('skeleton',_0x1d20c2=>{const _0x36b3cb=_0x3710ca;_0x1d20c2[_0x36b3cb(0x99)]('skeleton',()=>({'show':(_0x42c674,_0x18f6f3)=>window['cartSkeleton'][_0x36b3cb(0x9c)](_0x42c674,_0x18f6f3),'hide':_0x2ca03e=>window[_0x36b3cb(0x8b)]['hideSkeleton'](_0x2ca03e),'isLoading':_0x4361f4=>window['cartSkeleton'][_0x36b3cb(0x87)](_0x4361f4)}));});}),document[_0x296055(0x86)]('DOMContentLoaded',()=>{const _0x562c06=_0x296055;document[_0x562c06(0x88)]('[data-cart-page]')&&setTimeout(()=>{window['cartSkeleton']['simulateCartLoading']();},0x64);});typeof module!==_0x296055(0x9e)&&module[_0x296055(0xa2)]&&(module['exports']=CartSkeletonManager);
|
||||
Vendored
+1
-32
File diff suppressed because one or more lines are too long
Vendored
+1
-32
File diff suppressed because one or more lines are too long
Vendored
+1
-32
File diff suppressed because one or more lines are too long
Vendored
+1
-32
File diff suppressed because one or more lines are too long
Vendored
+1
-32
File diff suppressed because one or more lines are too long
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"/js/app.js": "/js/app.js",
|
||||
"/js/service/service.min.js": "/js/service/service.min.js",
|
||||
"/js/whois.min.js": "/js/whois.min.js",
|
||||
"/js/core.min.js": "/js/core.min.js",
|
||||
"/js/main.min.js": "/js/main.min.js",
|
||||
"/js/service/vminfo.min.js": "/js/service/vminfo.min.js",
|
||||
"/css/app.css": "/css/app.css"
|
||||
"/js/app.js": "/js/app.js?id=645842ce104c86946d040a01cc66af0c",
|
||||
"/js/service/service.min.js": "/js/service/service.min.js?id=5ce7499ae918dde413efa8e3d89c377b",
|
||||
"/js/whois.min.js": "/js/whois.min.js?id=d7cb6fec6aae98615a6968942d9b8f06",
|
||||
"/js/core.min.js": "/js/core.min.js?id=82a51324b447e9f2cd5190b7aaa92798",
|
||||
"/js/main.min.js": "/js/main.min.js?id=be6e21aee9553f8ce9fbd239bb8b9c75",
|
||||
"/js/service/vminfo.min.js": "/js/service/vminfo.min.js?id=6ac8b675d60d10f63147e73030bd9ca2",
|
||||
"/css/app.css": "/css/app.css?id=20d8e0d07d0d87a3b755e6c57a8b20c2"
|
||||
}
|
||||
|
||||
+419
-363
File diff suppressed because it is too large
Load Diff
@@ -1,116 +0,0 @@
|
||||
# Service Views Optimization
|
||||
|
||||
## Overview
|
||||
This module contains optimized service-related views following the established coding rules and best practices.
|
||||
|
||||
## Structure
|
||||
|
||||
### Main Files
|
||||
- `show.blade.php` - Main service detail view (optimized and modularized)
|
||||
- `list-snapshot.blade.php` - Snapshot management component
|
||||
|
||||
### Partials Directory (`partials/`)
|
||||
- `service-header.blade.php` - Service name and status display
|
||||
- `configuration-info.blade.php` - VM configuration details
|
||||
- `transaction-info.blade.php` - Billing and transaction information
|
||||
- `vm-control-panel.blade.php` - VM control interface
|
||||
- `no-vm-info.blade.php` - Error state when VM info is unavailable
|
||||
- `control-notes.blade.php` - User guidance and warnings
|
||||
|
||||
### Control Buttons (`partials/control-buttons/`)
|
||||
- `renewal-button.blade.php` - Service renewal control
|
||||
- `restart-button.blade.php` - VM restart control
|
||||
- `pause-button.blade.php` - VM pause control
|
||||
- `start-button.blade.php` - VM start control
|
||||
- `stop-button.blade.php` - VM stop control
|
||||
- `config-button.blade.php` - Configuration management
|
||||
- `reinstall-button.blade.php` - VM reinstallation
|
||||
- `snapshot-button.blade.php` - Snapshot creation
|
||||
- `console-button.blade.php` - Console access
|
||||
- `reset-password-button.blade.php` - Password reset
|
||||
|
||||
## Optimizations Applied
|
||||
|
||||
### 1. Component Extraction
|
||||
- Separated large monolithic view into reusable components
|
||||
- Each component has a single responsibility
|
||||
- Improved maintainability and reusability
|
||||
|
||||
### 2. Performance Improvements
|
||||
- Pre-computed variables in PHP blocks to reduce template processing
|
||||
- Optimized conditional logic with early returns
|
||||
- Reduced database queries through efficient data access
|
||||
|
||||
### 3. Code Organization
|
||||
- Consistent naming conventions (camelCase for functions)
|
||||
- Proper separation of concerns
|
||||
- Clear component hierarchy
|
||||
|
||||
### 4. Internationalization
|
||||
- All user-facing text wrapped with `__()` helper
|
||||
- Consistent translation keys
|
||||
- Support for multiple languages
|
||||
|
||||
### 5. Caching Strategy
|
||||
- Helper service for frequently accessed data
|
||||
- Optimized data processing in service layer
|
||||
- Reduced template complexity
|
||||
|
||||
### 6. UI/UX Improvements
|
||||
- Clean, minimalistic design following Tailwind CSS
|
||||
- Consistent color scheme and spacing
|
||||
- Responsive design for mobile and desktop
|
||||
- Proper accessibility attributes
|
||||
|
||||
## Helper Service
|
||||
|
||||
### VMHelperService
|
||||
Located at `app/Services/VMHelperService.php`
|
||||
|
||||
**Key Methods:**
|
||||
- `getVmStatusInfo()` - VM status information
|
||||
- `getVmConnectionInfo()` - Connection details (IP, port, user)
|
||||
- `getVmSpecs()` - VM specifications
|
||||
- `formatDate()` - Date formatting
|
||||
- `formatCurrency()` - Currency formatting
|
||||
- `getServiceStatus()` - Service status information
|
||||
|
||||
## Usage Example
|
||||
|
||||
```php
|
||||
// In controller
|
||||
use App\Services\VMHelperService;
|
||||
|
||||
public function show($invoiceId)
|
||||
{
|
||||
$invoice = $this->invoiceRepository->findById($invoiceId);
|
||||
$vmInfo = $this->vmService->getVmInfo($invoice->vm_id);
|
||||
|
||||
// Pre-process data for better performance
|
||||
$vmStatus = VMHelperService::getVmStatusInfo($vmInfo);
|
||||
$connectionInfo = VMHelperService::getVmConnectionInfo($vmInfo, $invoice);
|
||||
|
||||
return view('frontend.service.show', compact('invoice', 'vmInfo', 'vmStatus', 'connectionInfo'));
|
||||
}
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always use partials for reusable components**
|
||||
2. **Pre-compute variables in PHP blocks**
|
||||
3. **Use helper services for complex logic**
|
||||
4. **Implement proper caching strategies**
|
||||
5. **Follow internationalization guidelines**
|
||||
6. **Maintain consistent UI/UX patterns**
|
||||
7. **Use semantic HTML and accessibility features**
|
||||
|
||||
## Cache Keys
|
||||
- `service.vm.info.{vm_id}` - VM information cache
|
||||
- `service.invoice.{invoice_id}` - Invoice data cache
|
||||
- `service.config.{service_id}` - Service configuration cache
|
||||
|
||||
## Dependencies
|
||||
- Laravel Framework
|
||||
- Tailwind CSS
|
||||
- Alpine.js (for interactive components)
|
||||
- SweetAlert (for notifications)
|
||||
@@ -0,0 +1,13 @@
|
||||
<div class="bg-gray-50 hover:bg-gray-100 rounded-lg p-4 text-center cursor-pointer transition-colors duration-200 border border-gray-200 hover:border-green-300"
|
||||
@click="showNotification('{{ __('Chức năng đang phát triển') }}', 'info')">
|
||||
<div class="flex flex-col items-center">
|
||||
<div class="w-12 h-12 bg-green-100 rounded-full flex items-center justify-center mb-3">
|
||||
<svg class="w-6 h-6 text-green-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-sm font-medium text-gray-700">{{ __('Cài lại') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1,13 +1,29 @@
|
||||
{{-- Restart Button Component --}}
|
||||
<div id="restartVMButton"
|
||||
<div
|
||||
class="bg-gray-50 hover:bg-gray-100 rounded-lg p-4 text-center cursor-pointer transition-colors duration-200 border border-gray-200 hover:border-red-300">
|
||||
<div class="flex flex-col items-center">
|
||||
|
||||
<button x-data
|
||||
@click="
|
||||
fetch('{{ route('service.reboot.vm') }}', {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}' },
|
||||
body: new URLSearchParams({ product_id: '{{ $invoice->vm_id }}' })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
showNotification(data.message);
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 300);
|
||||
});
|
||||
"
|
||||
class="flex flex-col items-center w-full cursor-pointer">
|
||||
<div class="w-12 h-12 bg-red-100 rounded-full flex items-center justify-center mb-3">
|
||||
<svg class="w-6 h-6 text-red-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-sm font-medium text-gray-700">{{ __('Khởi động lại') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ __('Khởi động lại VM') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,29 @@
|
||||
<div id="startVMButton"
|
||||
class="bg-green-50 hover:bg-green-100 rounded-lg p-4 text-center cursor-pointer transition-colors duration-200 border border-green-200 hover:border-green-400">
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
{{-- Stop Button Component --}}
|
||||
<div
|
||||
class="bg-gray-50 hover:bg-gray-100 rounded-lg p-4 text-center cursor-pointer transition-colors duration-200 border border-gray-200 hover:border-green-300">
|
||||
|
||||
<button x-data
|
||||
@click="
|
||||
fetch('{{ route('service.start.vm') }}', {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}' },
|
||||
body: new URLSearchParams({ product_id: '{{ $invoice->vm_id }}' })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
showNotification(data.message);
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 300);
|
||||
});
|
||||
"
|
||||
class="flex flex-col items-center w-full cursor-pointer">
|
||||
<div class="w-12 h-12 bg-green-100 rounded-full flex items-center justify-center mb-3">
|
||||
<svg class="w-6 h-6 text-green-600" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M13 3h-2v10h2V3zm4.83 2.17l-1.42 1.42C17.99 7.86 19 9.81 19 12c0 3.87-3.13 7-7 7s-7-3.13-7-7c0-2.19 1.01-4.14 2.58-5.42L6.17 5.17C4.23 6.82 3 9.26 3 12c0 4.97 4.03 9 9 9s9-4.03 9-9c0-2.74-1.23-5.18-3.17-6.83z" />
|
||||
</svg>
|
||||
</div>
|
||||
<span class="font-medium text-green-700">{{ __('Khởi động máy ảo') }}</span>
|
||||
</div>
|
||||
{{ __('Khởi động máy ảo') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,12 +1,29 @@
|
||||
<div id="stopVMButton"
|
||||
{{-- Stop Button Component --}}
|
||||
<div
|
||||
class="bg-gray-50 hover:bg-gray-100 rounded-lg p-4 text-center cursor-pointer transition-colors duration-200 border border-gray-200 hover:border-red-300">
|
||||
<div class="flex flex-col items-center">
|
||||
|
||||
<button x-data
|
||||
@click="
|
||||
fetch('{{ route('service.stop.vm') }}', {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRF-TOKEN': '{{ csrf_token() }}' },
|
||||
body: new URLSearchParams({ product_id: '{{ $invoice->vm_id }}' })
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
showNotification(data.message);
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 300);
|
||||
});
|
||||
"
|
||||
class="flex flex-col items-center w-full cursor-pointer">
|
||||
<div class="w-12 h-12 bg-red-100 rounded-full flex items-center justify-center mb-3">
|
||||
<svg class="w-6 h-6 text-red-600" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
d="M13 3h-2v10h2V3zm4.83 2.17l-1.42 1.42C17.99 7.86 19 9.81 19 12c0 3.87-3.13 7-7 7s-7-3.13-7-7c0-2.19 1.01-4.14 2.58-5.42L6.17 5.17C4.23 6.82 3 9.26 3 12c0 4.97 4.03 9 9 9s9-4.03 9-9c0-2.74-1.23-5.18-3.17-6.83z" />
|
||||
</svg>
|
||||
</div>
|
||||
<span class="text-sm font-medium text-gray-700">{{ __('Tắt nguồn') }}</span>
|
||||
</div>
|
||||
{{ __('Tắt nguồn') }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -2,9 +2,15 @@
|
||||
@php
|
||||
$serverName = $invoice->invoiceItem->first()->server_name ?? 'Cloud VPS';
|
||||
$vmStatus = $vmInfo['status'] ?? null;
|
||||
$statusBg = match ($vmStatus) {
|
||||
'POWERED_ON' => 'bg-green-600',
|
||||
'POWERED_OFF' => 'bg-red-600',
|
||||
default => 'bg-gray-600',
|
||||
};
|
||||
@endphp
|
||||
|
||||
<div class="bg-gray-100 rounded-lg shadow-lg p-8 flex flex-col items-center justify-center">
|
||||
<div class="bg-gray-100 rounded-xl shadow-lg p-8 flex flex-col items-center justify-center relative">
|
||||
<span class="text-sm text-gray-600">{{ $invoice->invoiceItem->first()?->getProductAttribute()?->name }}</span>
|
||||
<div class="bg-white rounded-lg p-6 mb-4 shadow-md">
|
||||
<svg class="w-16 h-16 text-gray-700" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5"
|
||||
@@ -18,12 +24,19 @@
|
||||
{{ $serverName }}
|
||||
</p>
|
||||
<div class="mt-4">
|
||||
@if ($vmStatus == 'POWERED_ON')
|
||||
<span class="bg-green-500 text-white px-4 py-2 rounded-full text-sm font-medium">{{ __('RUNNING') }}</span>
|
||||
@elseif($vmStatus == 'POWERED_OFF')
|
||||
<span class="bg-red-500 text-white px-4 py-2 rounded-full text-sm font-medium">{{ __('STOPPED') }}</span>
|
||||
@else
|
||||
<span class="bg-gray-500 text-white px-4 py-2 rounded-full text-sm font-medium">{{ __('UNKNOWN') }}</span>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="h-10 w-full rounded-b-lg absolute bottom-0 left-0 flex items-center justify-center {{ $statusBg }}">
|
||||
<span class="text-center text-white">
|
||||
@if ($vmStatus == 'POWERED_ON')
|
||||
{{ __('RUNNING') }}
|
||||
@elseif($vmStatus == 'POWERED_OFF')
|
||||
{{ __('STOPPED') }}
|
||||
@else
|
||||
{{ __('UNKNOWN') }}
|
||||
@endif
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -19,23 +19,23 @@
|
||||
<div class="grid grid-cols-6 gap-4">
|
||||
{{-- Row 1: Basic Controls --}}
|
||||
@include('frontend.service.partials.control-buttons.renewal-button')
|
||||
@include('frontend.service.partials.control-buttons.restart-button')
|
||||
{{-- @include('frontend.service.partials.control-buttons.pause-button') --}}
|
||||
|
||||
@if ($isPoweredOff)
|
||||
@include('frontend.service.partials.control-buttons.start-button')
|
||||
@elseif ($isPoweredOn)
|
||||
@include('frontend.service.partials.control-buttons.restart-button')
|
||||
@include('frontend.service.partials.control-buttons.stop-button')
|
||||
@include('frontend.service.partials.control-buttons.console-button', ['invoice' => $invoice])
|
||||
@endif
|
||||
|
||||
{{-- @include('frontend.service.partials.control-buttons.config-button') --}}
|
||||
{{-- @include('frontend.service.partials.control-buttons.reinstall-button') --}}
|
||||
@include('frontend.service.partials.control-buttons.reinstall-button')
|
||||
|
||||
@if ($isPoweredOn)
|
||||
@include('frontend.service.partials.control-buttons.snapshot-button')
|
||||
@endif
|
||||
|
||||
@include('frontend.service.partials.control-buttons.console-button', ['invoice' => $invoice])
|
||||
|
||||
{{-- @include('frontend.service.partials.control-buttons.reset-password-button') --}}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
@push('viewStyles')
|
||||
<link rel="stylesheet" href="{{ asset('lib/sweetalert/sweetalert.css') }}">
|
||||
<meta name="invoice-ordernum" content="{{ $invoice->order_num }}">
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
|
||||
@@ -509,6 +509,7 @@ Route::middleware("CustomerLoginCheck")->group(function () {
|
||||
Route::post('secure/revert-snapshot', 'revertSnapshot')->name('service.revert.snapshot');
|
||||
Route::post('secure/start-vm', 'startVM')->name('service.start.vm');
|
||||
Route::post('secure/stop-vm', 'stopVM')->name('service.stop.vm');
|
||||
Route::post('secure/reboot-vm', 'rebootVM')->name('service.reboot.vm');
|
||||
});
|
||||
|
||||
Route::controller(CustomerDomainController::class)->group(function () {
|
||||
|
||||
Reference in New Issue
Block a user