feat(vm): hoàn tiền khi cấp VM lỗi, theo dõi job queue, uuid dịch vụ cho ticket, chuẩn hoá RAM addon

- VmProvisionException/VmProvisionRefund xử lý lỗi cấp phát VM
- QueueJobInspector + QueueMonitorController xem chi tiết job
- Thêm service uuid cho ticket, thời lượng cho activity_logs
- Migration chuẩn hoá RAM addon sang MB, trạng thái deleting cho snapshot

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
baodev0
2026-10-30 11:54:05 +07:00
co-authored by Claude Sonnet 5.5
parent 7136bec265
commit 62596d9063
32 changed files with 514 additions and 107 deletions
@@ -9,6 +9,7 @@ use App\Jobs\VirtualMachine\Snapshot\CreateSnapshot;
use App\Jobs\VirtualMachine\Snapshot\RevertSnapshot;
use App\Jobs\VirtualMachine\Snapshot\RemoveSnapshot;
use App\Models\ModuleSnapshot;
use App\Models\ModuleVmware;
use App\Services\ActivityLogger;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -25,6 +26,10 @@ class SnapshotController extends Controller
$payload = $request->validate([
'product' => ['required'],
'description' => ['nullable', 'string', 'max:1000'],
'name' => ['nullable', 'string', 'max:80', 'regex:/^[A-Za-z0-9._-]+$/'],
], [
'name.regex' => 'Tên snapshot chỉ gồm chữ không dấu, số và các ký tự . _ -',
'name.max' => 'Tên snapshot tối đa 80 ký tự.',
]);
$vm = $this->resolveVmById($payload['product'], $request);
@@ -32,11 +37,10 @@ class SnapshotController extends Controller
return $vm;
}
$today = now()->format('d-m-Y');
$existingCount = ModuleSnapshot::where('module_vmware_id', $vm->id)
->whereDate('created_at', today())
->count();
$snapshotName = sprintf('%s-%s-%d', $vm->name, $today, $existingCount + 1);
$snapshotName = $payload['name'] ?? $this->suggestName($vm);
if (ModuleSnapshot::where('module_vmware_id', $vm->id)->where('name', $snapshotName)->exists()) {
return response()->json(['error' => true, 'message' => 'Tên snapshot đã tồn tại, vui lòng chọn tên khác.'], 422);
}
$snapshot = ModuleSnapshot::create([
'name' => $snapshotName,
@@ -69,6 +73,18 @@ class SnapshotController extends Controller
]);
}
private function suggestName(ModuleVmware $vm): string
{
$prefix = sprintf('%s-%s-', $vm->name, now()->format('d-m-Y'));
$sequence = ModuleSnapshot::where('module_vmware_id', $vm->id)->whereDate('created_at', today())->count() + 1;
while (ModuleSnapshot::where('module_vmware_id', $vm->id)->where('name', $prefix . $sequence)->exists()) {
$sequence++;
}
return $prefix . $sequence;
}
public function index(Request $request, vCenter $serviceVCenter): JsonResponse
{
$productParam = $request->query('product');
@@ -116,6 +132,9 @@ class SnapshotController extends Controller
];
if ($snapshot) {
if ($snapshot->status === 'deleting') {
unset($payload['status']);
}
$snapshot->update($payload);
} else {
ModuleSnapshot::create($payload);
@@ -142,6 +161,7 @@ class SnapshotController extends Controller
return response()->json([
'error' => false,
'data' => $dbSnapshots,
'suggested_name' => $this->suggestName($vm),
]);
}
@@ -204,7 +224,17 @@ class SnapshotController extends Controller
return response()->json(['error' => true, 'message' => 'Không tìm thấy snapshot'], 404);
}
RemoveSnapshot::dispatch($vm->id, $snapshot->id);
if ($snapshot->status === 'deleting') {
return response()->json(['error' => true, 'message' => 'Snapshot đang được xóa.'], 422);
}
$deletedNow = $snapshot->status === 'failed';
if ($deletedNow) {
$snapshot->delete();
} else {
$snapshot->update(['status' => 'deleting']);
RemoveSnapshot::dispatch($vm->id, $snapshot->id);
}
$this->activityLogger->log(
event: 'deleted',
@@ -215,7 +245,7 @@ class SnapshotController extends Controller
return response()->json([
'error' => false,
'message' => 'Đã gửi yêu cầu xóa snapshot vào hàng đợi',
'message' => $deletedNow ? 'Đã xóa snapshot' : 'Đã gửi yêu cầu xóa snapshot vào hàng đợi',
]);
}
}