// Global variables - NO SENSITIVE DATA (no tokens) let invoiceId; let vmId; let vmHostname; let csrfToken; // Initialize variables from data attributes or global variables function initializeVariables() { // Get data from meta tags or global variables set in Blade template const metaInvoiceId = document.querySelector('meta[name="invoice-id"]'); const metaVmId = document.querySelector('meta[name="vm-id"]'); const metaVmHostname = document.querySelector('meta[name="vm-hostname"]'); const metaCsrfToken = document.querySelector('meta[name="csrf-token"]'); invoiceId = metaInvoiceId ? metaInvoiceId.getAttribute('content') : window.invoiceId; vmId = metaVmId ? metaVmId.getAttribute('content') : window.vmId; vmHostname = metaVmHostname ? metaVmHostname.getAttribute('content') : window.vmHostname; csrfToken = metaCsrfToken ? metaCsrfToken.getAttribute('content') : window.csrfToken; } // Cấu hình toast nếu có thể if (typeof FuiToast !== 'undefined' && FuiToast.config) { FuiToast.config({ position: 'top-center' }); } function showNotification(message, type = 'success') { if (type === 'success') { FuiToast.success(message); } else { FuiToast.error(message); } } async function createSnapshot() { const snapshotBtn = document.getElementById('createSnapshotButton'); if (snapshotBtn && snapshotBtn.disabled) return; const requestBody = { product_id: vmId }; const headers = { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': csrfToken // NO Authorization header - using session auth }; const snapshotPromise = fetch('/secure/create-snapshots', { method: 'POST', headers: headers, body: JSON.stringify(requestBody), credentials: 'same-origin' }).then(async (response) => { if (response.redirected || response.status === 401) { throw new Error('Phiên đăng nhập đã hết hạn, vui lòng đăng nhập lại'); } // Check if response is redirected (typical for Laravel redirect responses) if (response.redirected) { // Extract message from URL if possible, or show generic success return 'Đã gửi yêu cầu tạo snapshot. Quá trình sẽ mất 3-5 phút, vui lòng chờ đợi.'; } let data; try { const text = await response.text(); // Try to parse as JSON first try { data = JSON.parse(text); } catch (parseError) { // If not JSON, check for HTML with success/error messages if (text.includes('success') || response.ok) { setTimeout(() => { location.reload(); }, 2000); return 'Đã gửi yêu cầu tạo snapshot. Quá trình sẽ mất 3-5 phút, vui lòng chờ đợi.'; } else { throw new Error('Có lỗi xảy ra khi gửi yêu cầu tạo snapshot'); } } } catch (parseError) { throw new Error('Lỗi định dạng dữ liệu từ máy chủ'); } // Handle JSON response if (data && !data.error) { setTimeout(() => { location.reload(); }, 2000); return data.message || 'Đã gửi yêu cầu tạo snapshot. Quá trình sẽ mất 3-5 phút, vui lòng chờ đợi.'; } else { let errorMessage = 'Có lỗi xảy ra khi tạo snapshot'; if (data && data.message) { if (typeof data.message === 'object') { if (data.message.messages && data.message.messages.length > 0) { errorMessage = data.message.messages[0].default_message || data.message.messages[0].message || 'Có lỗi xảy ra'; } else if (data.message.error_type) { errorMessage = `Lỗi: ${data.message.error_type}`; } else { errorMessage = 'Có lỗi xảy ra từ hệ thống'; } } else { errorMessage = data.message; } } else if (data && data.value && data.value.messages && data.value.messages.length > 0) { errorMessage = data.value.messages[0].default_message; } else if (typeof data === 'string') { errorMessage = data; } else if (data && data.error) { if (typeof data.error === 'object') { if (data.error.message) { errorMessage = data.error.message; } else if (data.error.toString && data.error.toString() !== '[object Object]') { errorMessage = data.error.toString(); } else { errorMessage = 'Có lỗi xảy ra từ hệ thống'; } } else { errorMessage = data.error; } } throw new Error(errorMessage); } }); FuiToast.promise( snapshotPromise, { loading: 'Đang gửi yêu cầu tạo snapshot... (Quá trình mất 3-5 phút vui lòng giữ trang không tắt!)', success: (data) => { return data; }, error: (error) => { console.error(error); return error.message || 'Có lỗi xảy ra khi tạo snapshot'; } }, { isClose: true } ); } // Handle snapshot form submission with loading notification function handleSnapshotSubmit(event) { event.preventDefault(); const snapshotPromise = new Promise((resolve, reject) => { const form = document.getElementById('snapshotForm'); const formData = new FormData(form); fetch(form.action, { method: 'POST', body: formData, headers: { 'X-CSRF-TOKEN': csrfToken } }) .then(response => { if (response.redirected) { resolve('Đã gửi yêu cầu tạo snapshot! Quá trình sẽ mất 3-5 phút.'); window.location.href = response.url; } else if (response.ok) { resolve('Đã gửi yêu cầu tạo snapshot! Quá trình sẽ mất 3-5 phút.'); setTimeout(() => { window.location.reload(); }, 2000); } else { response.text().then(text => { reject('Có lỗi xảy ra khi gửi yêu cầu tạo snapshot'); }); } }) .catch(error => { reject('Lỗi kết nối: ' + error.message); }); }); if (typeof FuiToast !== 'undefined' && FuiToast.promise) { FuiToast.promise(snapshotPromise, { loading: 'Đang gửi yêu cầu tạo snapshot...', success: (data) => { return data || 'Đã gửi yêu cầu tạo snapshot!'; }, error: (error) => { return error || 'Có lỗi xảy ra khi gửi yêu cầu tạo snapshot'; } }); } else { if (typeof FuiToast !== 'undefined') { FuiToast.loading('Đang gửi yêu cầu tạo snapshot...', { duration: 0, }); } else { alert('Đang gửi yêu cầu tạo snapshot...'); } setTimeout(() => { document.getElementById('snapshotForm').submit(); }, 500); } } async function startVirtualMachine() { const startBtn = document.getElementById('startVMButton'); if (startBtn && startBtn.disabled) return; const requestBody = { vm_id: vmId }; const headers = { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': csrfToken // NO Authorization header - using session auth }; const startPromise = fetch('/api/secure/start-vm', { method: 'POST', headers: headers, body: JSON.stringify(requestBody), credentials: 'same-origin' }).then(async (response) => { if (response.redirected || response.status === 401) { throw new Error('Phiên đăng nhập đã hết hạn, vui lòng đăng nhập lại'); } let data; try { data = await response.json(); } catch (parseError) { throw new Error('Lỗi định dạng dữ liệu từ máy chủ'); } if (!data.error) { setTimeout(() => { location.reload(); }, 2000); return data.message; } else { if (data.type === 'com.vmware.vapi.std.errors.already_in_desired_state' || (data.message && data.message.error_type === 'ALREADY_IN_DESIRED_STATE')) { setTimeout(() => { location.reload(); }, 2000); return 'Máy chủ đã được bật'; } else { let errorMessage = 'Có lỗi xảy ra'; if (data.message) { if (typeof data.message === 'object') { if (data.message.messages && data.message.messages.length > 0) { errorMessage = data.message.messages[0].default_message || data.message.messages[0] .message || 'Có lỗi xảy ra'; } else if (data.message.error_type) { errorMessage = `Lỗi: ${data.message.error_type}`; } else { errorMessage = 'Có lỗi xảy ra từ hệ thống'; } } else { errorMessage = data.message; } } else if (data.value && data.value.messages && data.value.messages.length > 0) { errorMessage = data.value.messages[0].default_message; } else if (typeof data === 'string') { errorMessage = data; } else if (data.error) { if (typeof data.error === 'object') { if (data.error.message) { errorMessage = data.error.message; } else if (data.error.toString && data.error.toString() !== '[object Object]') { errorMessage = data.error.toString(); } else { errorMessage = 'Có lỗi xảy ra từ hệ thống'; } } else { errorMessage = data.error; } } throw new Error(errorMessage); } } }); FuiToast.promise( startPromise, { loading: 'Đang khởi động máy ảo...', success: (data) => { return data; }, error: (error) => { console.error(error); return error.message || 'Có lỗi xảy ra khi khởi động máy ảo'; } }, { isClose: true } ); } async function stopVirtualMachine() { const stopBtn = document.getElementById('stopVMButton'); if (stopBtn && stopBtn.disabled) return; const requestBody = { vm_id: vmId }; const headers = { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': csrfToken // NO Authorization header - using session auth }; const stopPromise = fetch('/api/secure/stop-vm', { method: 'POST', headers: headers, body: JSON.stringify(requestBody), credentials: 'same-origin' }).then(async (response) => { if (response.redirected || response.status === 401) { throw new Error('Phiên đăng nhập đã hết hạn, vui lòng đăng nhập lại'); } let data; try { data = await response.json(); } catch (parseError) { throw new Error('Lỗi định dạng dữ liệu từ máy chủ'); } if (!data.error) { setTimeout(() => { location.reload(); }, 2000); return data.message; } else { if (data.type === 'com.vmware.vapi.std.errors.already_in_desired_state' || (data.message && data.message.error_type === 'ALREADY_IN_DESIRED_STATE')) { setTimeout(() => { location.reload(); }, 2000); return 'Máy chủ đã được tắt'; } else { let errorMessage = 'Có lỗi xảy ra'; if (data.message) { if (typeof data.message === 'object') { if (data.message.messages && data.message.messages.length > 0) { errorMessage = data.message.messages[0].default_message || data.message.messages[0] .message || 'Có lỗi xảy ra'; } else if (data.message.error_type) { errorMessage = `Lỗi: ${data.message.error_type}`; } else { errorMessage = 'Có lỗi xảy ra từ hệ thống'; } } else { errorMessage = data.message; } } else if (data.value && data.value.messages && data.value.messages.length > 0) { errorMessage = data.value.messages[0].default_message; } else if (typeof data === 'string') { errorMessage = data; } else if (data.error) { if (typeof data.error === 'object') { if (data.error.message) { errorMessage = data.error.message; } else if (data.error.toString && data.error.toString() !== '[object Object]') { errorMessage = data.error.toString(); } else { errorMessage = 'Có lỗi xảy ra từ hệ thống'; } } else { errorMessage = data.error; } } throw new Error(errorMessage); } } }); FuiToast.promise( stopPromise, { loading: 'Đang dừng máy ảo...', success: (data) => { return data; }, error: (error) => { console.error(error); return error.message || 'Có lỗi xảy ra khi dừng máy ảo'; } }, { isClose: true } ); } async function restartVirtualMachine() { const restartBtn = document.getElementById('restartVMButton'); if (restartBtn && restartBtn.disabled) return; const restartPromise = fetch(`/service/${invoiceId}/vm/toggle`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken }, body: JSON.stringify({ action: 'restart' }) }).then(async (response) => { let data; try { data = await response.json(); } catch (parseError) { throw new Error('Lỗi định dạng dữ liệu từ máy chủ'); } if (data.success) { setTimeout(() => { location.reload(); }, 2000); return data.message; } else { let errorMessage = 'Có lỗi xảy ra'; if (data.message) { if (typeof data.message === 'object') { if (data.message.messages && data.message.messages.length > 0) { errorMessage = data.message.messages[0].default_message || data.message.messages[0] .message || 'Có lỗi xảy ra'; } else if (data.message.error_type) { errorMessage = `Lỗi: ${data.message.error_type}`; } else { errorMessage = 'Có lỗi xảy ra từ hệ thống'; } } else { errorMessage = data.message; } } else if (data.error) { if (typeof data.error === 'object') { if (data.error.message) { errorMessage = data.error.message; } else if (data.error.toString && data.error.toString() !== '[object Object]') { errorMessage = data.error.toString(); } else { errorMessage = 'Có lỗi xảy ra từ hệ thống'; } } else { errorMessage = data.error; } } else if (typeof data === 'string') { errorMessage = data; } throw new Error(errorMessage); } }); FuiToast.promise( restartPromise, { loading: 'Đang khởi động lại máy ảo...', success: (data) => { return data; }, error: (error) => { console.error(error); return error.message || 'Có lỗi xảy ra khi khởi động lại máy ảo'; } }, { isClose: true } ); } // DOM ready document.addEventListener('DOMContentLoaded', function() { // Initialize variables initializeVariables(); const startBtn = document.getElementById('startVMButton'); const stopBtn = document.getElementById('stopVMButton'); const restartBtn = document.getElementById('restartVMButton'); const snapshotBtn = document.getElementById('createSnapshotButton'); if (startBtn) { startBtn.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); startVirtualMachine(); }); } if (stopBtn) { stopBtn.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); stopVirtualMachine(); }); } if (restartBtn) { restartBtn.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); restartVirtualMachine(); }); } if (snapshotBtn) { snapshotBtn.addEventListener('click', function(e) { e.preventDefault(); e.stopPropagation(); createSnapshot(); }); } }); // Snapshot Table Management function snapshotTable() { return { loading: false, snapshots: [], error: null, async fetchSnapshots() { this.loading = true; this.error = null; const vmId = window.vmId || ''; if (!vmId) { this.loading = false; this.error = 'Không tìm thấy ID máy ảo'; return; } try { const response = await fetch(`/secure/get-snapshots?product_id=${vmId}`, { method: 'GET', headers: { 'Accept': 'application/json', 'X-CSRF-TOKEN': csrfToken, 'Cache-Control': 'no-cache' }, credentials: 'same-origin' }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); // Kiểm tra response structure từ API thực tế if (data.error === false && data.data && data.data.snapshots) { this.snapshots = data.data.snapshots; } else if (data.error === true) { this.error = data.message || 'Có lỗi xảy ra khi tải danh sách snapshot'; this.snapshots = []; } else if (data.snapshots) { // Fallback cho trường hợp data trực tiếp this.snapshots = data.snapshots; } else { this.snapshots = []; } } catch (error) { console.error('Error fetching snapshots:', error); this.error = 'Không thể kết nối đến máy chủ'; this.snapshots = []; } finally { this.loading = false; } }, formatSize(sizeGB, sizeMB) { // Convert to bytes first let bytes = 0; if (sizeGB) { bytes = parseFloat(sizeGB) * 1024 * 1024 * 1024; // GB to bytes } else if (sizeMB) { bytes = parseFloat(sizeMB) * 1024 * 1024; // MB to bytes } if (bytes === 0) return '0 B'; const units = ['B', 'KB', 'MB', 'GB', 'TB']; const k = 1024; const i = Math.floor(Math.log(bytes) / Math.log(k)); const size = parseFloat((bytes / Math.pow(k, i)).toFixed(2)); return size + ' ' + units[i]; }, async deleteSnapshot(snapshotName) { const result = await Swal.fire({ title: 'Xác nhận xóa', text: `Bạn có chắc chắn muốn xóa snapshot "${snapshotName}"? Hành động này không thể hoàn tác.`, icon: 'warning', showCancelButton: true, confirmButtonColor: '#d33', cancelButtonColor: '#3085d6', confirmButtonText: 'Xóa', cancelButtonText: 'Hủy bỏ', reverseButtons: true }); if (!result.isConfirmed) return; try { const response = await fetch('/secure/remove-snapshot', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': csrfToken }, body: JSON.stringify({ product_id: window.vmId, snapshot_name: snapshotName }), credentials: 'same-origin' }); const data = await response.json(); if (data.error === false) { showNotification(data.message || 'Xóa snapshot thành công', 'success'); // Refresh snapshot list after successful deletion setTimeout(() => this.fetchSnapshots(), 1000); } else { throw new Error(data.message || 'Có lỗi xảy ra khi xóa snapshot'); } } catch (error) { console.error('Error deleting snapshot:', error); showNotification(error.message || 'Có lỗi xảy ra khi xóa snapshot', 'error'); } }, async revertSnapshot(snapshotName) { const result = await Swal.fire({ title: 'Xác nhận khôi phục', text: `Bạn có chắc chắn muốn khôi phục về snapshot "${snapshotName}"? Tất cả dữ liệu hiện tại sẽ bị mất.`, icon: 'warning', showCancelButton: true, confirmButtonColor: '#3085d6', cancelButtonColor: '#d33', confirmButtonText: 'Khôi phục', cancelButtonText: 'Hủy bỏ', reverseButtons: true }); if (!result.isConfirmed) return; try { const response = await fetch('/secure/revert-snapshot', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': csrfToken }, body: JSON.stringify({ product_id: window.vmId, snapshot_name: snapshotName }), credentials: 'same-origin' }); const data = await response.json(); if (data.error === false) { showNotification(data.message || 'Khôi phục snapshot thành công', 'success'); // Refresh snapshot list after successful revert setTimeout(() => this.fetchSnapshots(), 2000); } else { throw new Error(data.message || 'Có lỗi xảy ra khi khôi phục snapshot'); } } catch (error) { console.error('Error reverting snapshot:', error); showNotification(error.message || 'Có lỗi xảy ra khi khôi phục snapshot', 'error'); } }, // Helper method to refresh snapshots refreshSnapshots() { this.fetchSnapshots(); }, // Helper method to get snapshot count getSnapshotCount() { return this.snapshots.length; }, // Helper method to check if snapshots are loading isLoading() { return this.loading; }, // Helper method to check if there's an error hasError() { return this.error !== null; } } }