// Global variables - NO SENSITIVE DATA (no tokens) let invoiceId; let invoiceOrdernum; 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 metaInvoiceOrdernum = document.querySelector('meta[name="invoice-ordernum"]'); 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; invoiceOrdernum = metaInvoiceOrdernum ? metaInvoiceOrdernum.getAttribute("content") : window.invoiceOrdernum; 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, } ); } // 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() { // Constants const API_ENDPOINTS = { GET_SNAPSHOTS: "/dich-vu-dang-ky/secure/get-snapshots", REMOVE_SNAPSHOT: "/dich-vu-dang-ky/secure/remove-snapshot", REVERT_SNAPSHOT: "/dich-vu-dang-ky/secure/revert-snapshot", }; const MESSAGES = { VM_ID_NOT_FOUND: "Không tìm thấy ID máy ảo", CONNECTION_ERROR: "Không thể kết nối đến máy chủ", GENERIC_ERROR: "Có lỗi xảy ra", DELETE_SUCCESS: "Xóa snapshot thành công", REVERT_SUCCESS: "Khôi phục snapshot thành công", }; return { loading: false, snapshots: [], error: null, // Cache để tránh fetch lại không cần thiết _lastFetchTime: 0, _cacheTimeout: 30000, // 30 seconds async fetchSnapshots(forceRefresh = false) { // Check cache const now = Date.now(); if ( !forceRefresh && this.snapshots.length > 0 && now - this._lastFetchTime < this._cacheTimeout ) { return; } this.loading = true; this.error = null; const vmId = window.vmId?.trim(); if (!vmId) { this._setError(MESSAGES.VM_ID_NOT_FOUND); return; } try { const data = await this._makeRequest( `${ API_ENDPOINTS.GET_SNAPSHOTS }?product_id=${encodeURIComponent(vmId)}` ); this._processSnapshotsResponse(data); this._lastFetchTime = now; } catch (error) { this._handleError("fetching snapshots", error); } finally { this.loading = false; } }, formatSize(sizeGB, sizeMB) { const bytes = this._convertToBytes(sizeGB, sizeMB); if (bytes === 0) return "0 B"; return this._formatBytes(bytes); }, async deleteSnapshot(snapshotName) { if ( !(await this._confirmAction( "Xác nhận xóa", `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.`, "Xóa" )) ) return; await this._executeSnapshotAction( API_ENDPOINTS.REMOVE_SNAPSHOT, snapshotName, MESSAGES.DELETE_SUCCESS, "deleting snapshot" ); }, async revertSnapshot(snapshotName) { if ( !(await this._confirmAction( "Xác nhận khôi phục", `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.`, "Khôi phục" )) ) return; await this._executeSnapshotAction( API_ENDPOINTS.REVERT_SNAPSHOT, snapshotName, MESSAGES.REVERT_SUCCESS, "reverting snapshot", 2000 ); }, // Public helper methods refreshSnapshots() { return this.fetchSnapshots(true); }, getSnapshotCount() { return this.snapshots.length; }, isLoading() { return this.loading; }, hasError() { return this.error !== null; }, // Private helper methods _setError(message) { this.loading = false; this.error = message; this.snapshots = []; }, async _makeRequest(url, options = {}) { const defaultOptions = { method: "GET", headers: { Accept: "application/json", "X-CSRF-TOKEN": csrfToken, "Cache-Control": "no-cache", }, credentials: "same-origin", }; const response = await fetch(url, { ...defaultOptions, ...options, }); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }, _processSnapshotsResponse(data) { // Handle different response structures if (data.error === false && data.data?.snapshots) { this.snapshots = data.data.snapshots; } else if (data.error === true) { throw new Error( data.message || "Có lỗi xảy ra khi tải danh sách snapshot" ); } else if (data.snapshots) { this.snapshots = data.snapshots; } else { this.snapshots = []; } }, _convertToBytes(sizeGB, sizeMB) { if (sizeGB) { return parseFloat(sizeGB) * 1024 ** 3; // GB to bytes } else if (sizeMB) { return parseFloat(sizeMB) * 1024 ** 2; // MB to bytes } return 0; }, _formatBytes(bytes) { 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 _confirmAction(title, text, confirmText) { const result = await Swal.fire({ title, text, icon: "warning", showCancelButton: true, confirmButtonColor: title.includes("xóa") ? "#d33" : "#3085d6", cancelButtonColor: title.includes("xóa") ? "#3085d6" : "#d33", confirmButtonText: confirmText, cancelButtonText: "Hủy bỏ", reverseButtons: true, }); return result.isConfirmed; }, async _executeSnapshotAction( endpoint, snapshotName, successMessage, errorContext, delay = 1000 ) { try { const data = await this._makeRequest(endpoint, { method: "POST", headers: { "Content-Type": "application/json", Accept: "application/json", "X-CSRF-TOKEN": csrfToken, }, body: JSON.stringify({ product_id: window.vmId, snapshot_name: snapshotName, }), }); if (data.error === false) { showNotification(data.message || successMessage, "success"); setTimeout(() => this.refreshSnapshots(), delay); } else { throw new Error( data.message || `${MESSAGES.GENERIC_ERROR} khi ${errorContext}` ); } } catch (error) { this._handleError(errorContext, error); } }, _handleError(context, error) { console.error(`Error ${context}:`, error); const message = error.message || MESSAGES.CONNECTION_ERROR; if (context.includes("fetching")) { this._setError(message); } else { showNotification(message, "error"); } }, }; } // Make snapshotTable available globally for Alpine.js window.snapshotTable = snapshotTable;