update
This commit is contained in:
@@ -18,9 +18,22 @@ class ButtonCRM extends Component
|
||||
public $variant;
|
||||
public $size;
|
||||
public $params;
|
||||
public $confirm;
|
||||
public $confirmUrl;
|
||||
public $confirmName;
|
||||
|
||||
public function __construct($type = 'button', $route = null, $params = [], $url = null, $icon = null, $variant = 'primary', $size = 'md')
|
||||
{
|
||||
public function __construct(
|
||||
$type = 'button',
|
||||
$route = null,
|
||||
$params = [],
|
||||
$url = null,
|
||||
$icon = null,
|
||||
$variant = 'primary',
|
||||
$size = 'md',
|
||||
$confirm = false,
|
||||
$confirmUrl = null,
|
||||
$confirmName = null
|
||||
) {
|
||||
$this->type = $type;
|
||||
$this->route = $route;
|
||||
$this->params = $params;
|
||||
@@ -28,6 +41,9 @@ class ButtonCRM extends Component
|
||||
$this->icon = $icon;
|
||||
$this->variant = $variant;
|
||||
$this->size = $size;
|
||||
$this->confirm = $confirm;
|
||||
$this->confirmUrl = $confirmUrl;
|
||||
$this->confirmName = $confirmName;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -92,6 +92,8 @@
|
||||
|
||||
@include('administrator.layout.partials.toast')
|
||||
|
||||
<x-modal-confirm-delete />
|
||||
|
||||
@stack('custom-scripts')
|
||||
</body>
|
||||
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
<a href="{{ route('module.vcenter.create') }}" class="btn btn-outline-secondary"><i class="fas fa-plus-circle"></i> Add New Server</a>
|
||||
<x-button-crm type="button" variant="primary" size="sm" route="module.vcenter.create" icon="plus-circle">Add
|
||||
Server</x-button-crm>
|
||||
<form action="{{ route('module.vcenter.import') }}" class="d-lg-inline" enctype="multipart/form-data" method="post">
|
||||
@csrf
|
||||
<input type="file" name="file" class="d-none file-selector" accept=".csv,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" onchange="form.submit()" />
|
||||
<button type="button" class="btn btn-outline-secondary btn-import__from">
|
||||
<i class="fas fa-plus-circle"></i> Import From Excel
|
||||
</button>
|
||||
<input type="file" name="file" class="d-none file-selector"
|
||||
accept=".csv,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" onchange="form.submit()" />
|
||||
<x-button-crm type="button" variant="success" size="sm" route="module.vcenter.import" icon="plus-circle">Import
|
||||
From
|
||||
Excel</x-button-crm>
|
||||
</form>
|
||||
<a href="{{ url('file/vCenter.xlsx') }}" class="btn btn-outline-secondary"><i class="fas fa-download"></i> File Import Demo</a>
|
||||
<x-button-crm type="button" variant="secondary" size="sm" url="file/vCenter.xlsx" icon="download">File Import
|
||||
Demo</x-button-crm>
|
||||
@@ -76,14 +76,15 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-white border-top p-3 d-flex justify-content-end gap-2">
|
||||
{{-- Fixed Bottom Actions --}}
|
||||
<x-fixed-action-bar>
|
||||
<x-button-c-r-m route="module.dns.index" variant="secondary" icon="arrow-left">
|
||||
Cancel
|
||||
</x-button-c-r-m>
|
||||
<x-button-c-r-m type="submit" variant="primary" icon="save">
|
||||
Save DNS Server
|
||||
Create
|
||||
</x-button-c-r-m>
|
||||
</div>
|
||||
</x-fixed-action-bar>
|
||||
</form>
|
||||
</div>
|
||||
@else
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
|
||||
@section('breadcrumb')
|
||||
@php
|
||||
$breadcrumbItems = [
|
||||
['label' => 'Modules'],
|
||||
['label' => 'DNS Server', 'url' => route('module.dns.index')],
|
||||
['label' => 'Edit DNS Server', 'active' => true],
|
||||
];
|
||||
$breadcrumbItems = [
|
||||
['label' => 'Modules'],
|
||||
['label' => 'DNS Server', 'url' => route('module.dns.index')],
|
||||
['label' => 'Edit DNS Server', 'active' => true],
|
||||
];
|
||||
@endphp
|
||||
<x-admin-breadcrumb :items="$breadcrumbItems" />
|
||||
@endsection
|
||||
@@ -79,16 +79,22 @@ $breadcrumbItems = [
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer bg-white border-top p-3 d-flex justify-content-end gap-2">
|
||||
{{-- Fixed Bottom Actions --}}
|
||||
<x-fixed-action-bar>
|
||||
<x-button-c-r-m route="module.dns.index" variant="secondary" icon="arrow-left">
|
||||
Cancel
|
||||
</x-button-c-r-m>
|
||||
|
||||
<x-button-c-r-m type="submit" variant="primary" icon="save">
|
||||
Save Changes
|
||||
Update
|
||||
</x-button-c-r-m>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<x-button-c-r-m type="delete" variant="danger" icon="trash"
|
||||
:confirm-url="route('module.dns.destroy', ['id' => $server->id])" :confirm-name="$server->name">
|
||||
Delete
|
||||
</x-button-c-r-m>
|
||||
</x-fixed-action-bar>
|
||||
</form>
|
||||
</div>
|
||||
@else
|
||||
<div class="alert alert-danger alert-dismissible">
|
||||
<h5 class="mb-0"><i class="icon fas fa-ban"></i> Access Denied - You are not assigned to any Module dns
|
||||
@@ -105,4 +111,4 @@ $breadcrumbItems = [
|
||||
$('.select2').select2();
|
||||
});
|
||||
</script>
|
||||
@endpush
|
||||
@endpush
|
||||
@@ -112,6 +112,10 @@ $breadcrumbItems = [
|
||||
<x-button-c-r-m route="module.vcenter.index" variant="secondary" icon="arrow-left">
|
||||
Cancel
|
||||
</x-button-c-r-m>
|
||||
<x-button-c-r-m type="delete" variant="danger" icon="trash"
|
||||
:confirm-url="route('module.vcenter.destroy', ['id' => $server->id])" :confirm-name="$server->host">
|
||||
Delete
|
||||
</x-button-c-r-m>
|
||||
<x-button-c-r-m type="submit" variant="primary" icon="save">
|
||||
Save Changes
|
||||
</x-button-c-r-m>
|
||||
|
||||
@@ -7,18 +7,17 @@
|
||||
|
||||
@section('breadcrumb')
|
||||
@php
|
||||
$breadcrumbItems = [
|
||||
['label' => 'Modules'],
|
||||
['label' => 'Package vCenter'],
|
||||
['label' => 'Datacenter', 'active' => true],
|
||||
];
|
||||
$breadcrumbItems = [
|
||||
['label' => 'Modules'],
|
||||
['label' => 'Vcenter', 'active' => true],
|
||||
];
|
||||
@endphp
|
||||
<x-admin-breadcrumb :items="$breadcrumbItems" />
|
||||
@endsection
|
||||
@section('content')
|
||||
@if(auth()->guard('admins')->user()->can("manager module vcenter"))
|
||||
<div class="card card-primary card-outline">
|
||||
<div class="card-header">
|
||||
<div class="card-header d-flex justify-content-end gap-2">
|
||||
@include("administrator.pages.block.ModuleVCenter")
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@@ -28,14 +27,14 @@ $breadcrumbItems = [
|
||||
['key' => 'status', 'label' => 'Status'],
|
||||
['key' => 'action', 'label' => 'Action'],
|
||||
]" />
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="alert alert-danger alert-dismissible">
|
||||
<h5 class="mb-0"><i class="icon fas fa-ban"></i> Access Denied - You are not assigned to any Module vCenter
|
||||
management department</h5>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@else
|
||||
<div class="alert alert-danger alert-dismissible">
|
||||
<h5 class="mb-0"><i class="icon fas fa-ban"></i> Access Denied - You are not assigned to any Module vCenter
|
||||
management department</h5>
|
||||
</div>
|
||||
@endif
|
||||
@endsection
|
||||
@push('plugin-scripts')
|
||||
@foreach (config('assets.datatable.js') as $datatable)
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
@php
|
||||
$tag = 'button';
|
||||
$href = null;
|
||||
$buttonType = $type;
|
||||
|
||||
if ($route) {
|
||||
if ($type === 'delete' || $confirm) {
|
||||
$tag = 'button';
|
||||
$buttonType = 'button';
|
||||
$attributes = $attributes->merge([
|
||||
'onclick' => "confirmDelete('$confirmUrl', '$confirmName')",
|
||||
]);
|
||||
} elseif ($route) {
|
||||
$href = route($route, $params);
|
||||
$tag = 'a';
|
||||
} elseif ($url) {
|
||||
@@ -13,7 +20,7 @@
|
||||
$tag = 'a';
|
||||
}
|
||||
|
||||
$baseClasses = 'd-inline-flex gap-2 align-items-center justify-content-center text-white border-0 shadow-lg px-3 py-2 rounded-pill';
|
||||
$baseClasses = 'd-inline-flex gap-2 align-items-center justify-content-center text-white border-0 px-3 py-2 rounded-pill';
|
||||
|
||||
$variants = [
|
||||
'primary' => 'bg-primary',
|
||||
@@ -34,24 +41,24 @@
|
||||
'icon' => 'h-10',
|
||||
];
|
||||
|
||||
$variantClass = $variants[$variant] ?? $variants['primary'];
|
||||
$sizeClass = $sizes[$size] ?? $sizes['default'];
|
||||
$variantClass = $variants[$variant ?? 'primary'] ?? $variants['primary'];
|
||||
$sizeClass = $sizes[$size ?? 'md'] ?? $sizes['default'];
|
||||
|
||||
$classes = "{$baseClasses} {$variantClass} {$sizeClass}";
|
||||
@endphp
|
||||
|
||||
@if($tag === 'a')
|
||||
<a href="{{ $href }}" {{ $attributes->merge(['class' => $classes]) }}>
|
||||
@if($icon)
|
||||
<i data-lucide="{{ $icon }}" class="mr-2" style="height: 15px; width: 15px"></i>
|
||||
@endif
|
||||
{{ $slot }}
|
||||
</a>
|
||||
@else
|
||||
<button type="{{ $type }}" {{ $attributes->merge(['class' => $classes]) }}>
|
||||
@if($icon)
|
||||
<i data-lucide="{{ $icon }}" class="mr-2" style="height: 15px; width: 15px"></i>
|
||||
@endif
|
||||
{{ $slot }}
|
||||
</button>
|
||||
@endif
|
||||
|
||||
@if($tag === 'a')
|
||||
<a href="{{ $href }}" {{ $attributes->merge(['class' => $classes]) }}>
|
||||
@if($icon)
|
||||
<i data-lucide="{{ $icon }}" class="mr-2" style="height: 15px; width: 15px"></i>
|
||||
@endif
|
||||
{{ $slot }}
|
||||
</a>
|
||||
@else
|
||||
<button type="{{ $buttonType }}" {{ $attributes->merge(['class' => $classes]) }}>
|
||||
@if($icon)
|
||||
<i data-lucide="{{ $icon }}" class="mr-2" style="height: 15px; width: 15px"></i>
|
||||
@endif
|
||||
{{ $slot }}
|
||||
</button>
|
||||
@endif
|
||||
@@ -0,0 +1,52 @@
|
||||
<!-- Generic Delete Confirmation Modal -->
|
||||
<div class="modal micromodal-slide" id="deleteConfirmationModal" aria-hidden="true">
|
||||
<div class="modal__overlay" tabindex="-1" data-micromodal-close>
|
||||
<div class="modal__container modal--sm" role="dialog" aria-modal="true" aria-labelledby="deleteConfirmationModalLabel">
|
||||
<header class="modal__header bg-primary text-white">
|
||||
<h6 class="modal__title" id="deleteConfirmationModalLabel">
|
||||
<i data-lucide="trash-2" class="w-15px h-15px me-2"></i>
|
||||
Confirm Delete
|
||||
</h6>
|
||||
<button class="modal__close" aria-label="Close modal" data-micromodal-close>
|
||||
<i data-lucide="x"></i>
|
||||
</button>
|
||||
</header>
|
||||
<main class="modal__content">
|
||||
<div class="text-center">
|
||||
<i data-lucide="alert-triangle" class="text-warning mb-3" style="width: 48px; height: 48px;"></i>
|
||||
<h6 class="mb-2">Are you sure?</h6>
|
||||
<p class="text-muted small mb-1">Delete item:</p>
|
||||
<p class="fw-bold mb-2"><span id="confirmDeleteItemName"></span></p>
|
||||
<p class="text-danger small mb-0">This action cannot be undone!</p>
|
||||
</div>
|
||||
</main>
|
||||
<footer class="modal__footer">
|
||||
<x-button-crm type="button" variant="secondary" size="sm" icon="x" data-micromodal-close>Cancel</x-button-crm>
|
||||
<form id="confirmDeleteForm" method="POST" class="d-inline">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<x-button-crm type="submit" variant="danger" size="sm" icon="trash-2">Confirm Delete</x-button-crm>
|
||||
</form>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@push('custom-scripts')
|
||||
<script>
|
||||
window.confirmDelete = function(url, name) {
|
||||
const nameElement = document.getElementById('confirmDeleteItemName');
|
||||
const formElement = document.getElementById('confirmDeleteForm');
|
||||
|
||||
if (nameElement && formElement) {
|
||||
nameElement.textContent = name;
|
||||
formElement.action = url;
|
||||
if (typeof ModalManager !== 'undefined') {
|
||||
ModalManager.show('deleteConfirmationModal');
|
||||
} else {
|
||||
MicroModal.show('deleteConfirmationModal');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@endpush
|
||||
@@ -1,67 +1,23 @@
|
||||
# Danh Sách Công Việc Cần Xử Lý (Project Todo List)
|
||||
# TODO LIST
|
||||
|
||||
Tài liệu này liệt kê các vấn đề cần khắc phục và cải thiện cho dự án Portal VPS/Hosting, được sắp xếp theo độ ưu tiên từ Cao đến Thấp dựa trên đánh giá rủi ro vận hành (Production Readiness).
|
||||
## 🔴 Critical
|
||||
- [ ] Refactor `routes/api.php`: Move all business logic from closures to Controllers/Services (ID: I001)
|
||||
- [ ] Refactor `OrderController@store`: Move massive business logic to `OrderService` (ID: I002)
|
||||
- [ ] Implement Enums for Product Types and Invoice/Order Statuses (ID: I003)
|
||||
- [ ] Fix logic leakage in `ProductController@store` and `update`: Use FormRequests and Services (ID: I004)
|
||||
- [ ] Remove hardcoded HTML from `Invoice.php` model; move to a Presenter or Blade component (ID: I005)
|
||||
|
||||
## 🚨 Mức Độ 1: Cực Kỳ Quan Trọng (Critical) - Phải Xử Lý Trước Khi Chạy Production
|
||||
*Các vấn đề đe dọa trực tiếp đến khả năng vận hành, bảo mật và hiệu năng của hệ thống.*
|
||||
## 🟠 High
|
||||
- [ ] Replace `$guarded = []` with specific `$fillable` in `Order`, `Invoice`, and other core models (ID: I006)
|
||||
- [ ] Extract caching logic from `ProductController` to a Service or Repository (ID: I007)
|
||||
- [ ] Add FormRequest validation for all Order and Product actions (ID: I008)
|
||||
- [ ] Remove hardcoded IDs (1, 2, 3, 4) from logic and replace with Enums/Constants (ID: I009)
|
||||
|
||||
- [x] **Xử lý cuộc gọi đồng bộ vCenter (Blocking Calls)**
|
||||
- [x] **Mô tả:** Các thao tác bật/tắt/khởi động lại VPS hiện đang chờ phản hồi từ vCenter ngay trong request của người dùng (nếu vCenter chậm 10s, web treo 10s).
|
||||
- [x] **Giải pháp:** Đã chuyển tất cả hành động này vào Laravel Queue (Background Jobs: `StartVmJob`, `StopVmJob`, `RebootVmJob`). API trả về "Đang xử lý".
|
||||
- [x] **File:** `app/Http/Controllers/frontend/ServiceController.php` (Đã xử lý), `App\Http\Controllers\frontend\ManagerService.php` (Đã sử dụng Job từ trước).
|
||||
## 🟡 Medium
|
||||
- [ ] Refactor `calculateBillingDetails` in `OrderController` to a Service or Helper (ID: I010)
|
||||
- [ ] Consolidate hosting/vps logic into the new `Product` table and model (ID: I011)
|
||||
- [ ] Improve Job dispatching: remove hardcoded `delay(now()->addSeconds(10))` (ID: I012)
|
||||
|
||||
- [ ] **Loại bỏ "Jump Host" PowerShell (Kiến trúc mỏng manh)**
|
||||
- [ ] **Mô tả:** Hệ thống phụ thuộc vào việc SSH vào một máy Windows (`10.10.10.173`) để chạy lệnh PowerShell. Nếu máy này chết hoặc lỗi mạng, toàn bộ hệ thống tê liệt.
|
||||
- [ ] **Giải pháp:** Chuyển sang dùng vCenter REST API trực tiếp cho các tác vụ: Snapshot, lấy thông tin VM, đổi cấu hình. Chỉ dùng PowerShell khi **thực sự** không có cách nào khác (ví dụ: chạy script bên trong Guest OS).
|
||||
- [ ] **File:** `app/Http/Services/vCenter.php`.
|
||||
|
||||
- [ ] **Bảo mật SSL (Security)**
|
||||
- [ ] **Mô tả:** Đang sử dụng `Http::withoutVerifying()` ở khắp nơi. Dễ bị tấn công Man-in-the-Middle.
|
||||
- [ ] **Giải pháp:** Thêm chứng chỉ CA của vCenter vào server và bật xác thực SSL, hoặc cấu hình Guzzle client đúng cách.
|
||||
- [ ] **File:** `app/Http/Services/vCenter.php`, `app/Http/Livewire/VPSReinstallModal.php`.
|
||||
|
||||
- [ ] **Hiệu năng Redis & Database (Memory Leak)**
|
||||
- [ ] **Mô tả:** Hàm `getAll()` trong `BaseCachedRepository` cache toàn bộ bảng dữ liệu. Khi dữ liệu lớn sẽ làm tràn RAM Redis/Server.
|
||||
- [ ] **Giải pháp:** Xóa việc cache `getAll()`. Sử dụng phân trang (`paginate`) cho các danh sách.
|
||||
- [ ] **File:** `app/Repositories/BaseCachedRepository.php`.
|
||||
|
||||
- [ ] **Loại bỏ gọi ngược nội bộ (Self-Calling HTTP)**
|
||||
- [ ] **Mô tả:** Livewire `VPSReinstallModal` gọi `Http::post(route('service.reinstall.vm'))`. Đây là một anti-pattern (tốn tài nguyên gấp đôi: 1 request của user + 1 request của server tự gọi mình).
|
||||
- [ ] **Giải pháp:** Gọi trực tiếp Service hoặc Controller method thay vì bắn HTTP request.
|
||||
- [ ] **File:** `app/Http/Livewire/VPSReinstallModal.php`.
|
||||
|
||||
## ⚠️ Mức Độ 2: Quan Trọng (Major) - Cần Xử Lý Sớm
|
||||
*Các tính năng cần thiết để hệ thống hoạt động trơn tru và chuyên nghiệp.*
|
||||
|
||||
- [ ] **Tối ưu Console Access (WebMKS)**
|
||||
- [ ] **Mô tả:** Việc lấy Ticket Console hiện đang đồng bộ.
|
||||
- [ ] **Giải pháp:** Đặt timeout thấp cho request lấy ticket (ví dụ 5s) hoặc hiển thị trạng thái loading rõ ràng hơn nến kết nối chậm. Cải thiện xử lý lỗi khi ticket không lấy được.
|
||||
|
||||
- [ ] **Cải thiện Quy trình Reinstall VPS**
|
||||
- [ ] **Mô tả:** Logic reinstall hiện tại xóa VM (`DeleteVirtualMachine`) rồi mới tạo lại. Nếu bước tạo lại lỗi, khách hàng mất VM và không có gì thay thế.
|
||||
- [ ] **Giải pháp:**
|
||||
1. Tạo VM mới song song (tên tạm clone).
|
||||
2. Nếu thành công -> Xóa VM cũ -> Rename VM mới -> IP Swap.
|
||||
3. Nếu thất bại -> Báo lỗi, giữ nguyên VM cũ.
|
||||
|
||||
- [ ] **Thêm Index Database**
|
||||
- [ ] **Mô tả:** Các khóa ngoại trong bảng `vps` và `module_ip_address` cần được đánh index để tăng tốc độ truy vấn khi số lượng row tăng lên.
|
||||
- [ ] **Giải pháp:** Kiểm tra và thêm index migration.
|
||||
|
||||
## 📝 Mức Độ 3: Cải Thiện (Minor/Enhancement) - Làm Cho Đẹp & Tiện
|
||||
*Tính năng nâng cao trải nghiệm người dùng.*
|
||||
|
||||
- [ ] **Biểu đồ giám sát (Graphs)**
|
||||
- [ ] **Mô tả:** Khách hàng chưa xem được biểu đồ CPU/RAM/Network.
|
||||
- [ ] **Giải pháp:** Viết Scheduled Job (Cron) định kỳ 5 phút/lần lấy stats từ vCenter lưu vào DB (bảng `vm_stats`) và vẽ biểu đồ bằng Chart.js.
|
||||
|
||||
- [ ] **Phân tách Queue**
|
||||
- [ ] **Mô tả:** Hiện tại có thể đang dùng chung queue `default`.
|
||||
- [ ] **Giải pháp:** Tách queue: `high` (thanh toán, mail OTP), `default` (tác vụ thường), `provisioning` (tạo VM - rất lâu, cần worker riêng).
|
||||
|
||||
- [ ] **Logging & Monitoring**
|
||||
- [ ] **Mô tả:** Log đang ghi vào file hoặc DB rải rác.
|
||||
- [ ] **Giải pháp:** Tập trung Log các lỗi quan trọng ra kênh Telegram/Slack (đã có implementation nhưng cần review để không spam tin nhắn rác).
|
||||
|
||||
---
|
||||
*Cập nhật lần cuối: 27/01/2026*
|
||||
## 🟢 Low
|
||||
- [ ] Clean up unused comments and commented-out code in `Order.php` and `ProductController.php` (ID: I013)
|
||||
- [ ] Standardize currency formatting using a specialized Utility/Helper (ID: I014)
|
||||
|
||||
Reference in New Issue
Block a user