Files
portal-api/resources/js/service/vminfo.js
T
2025-07-28 17:02:13 +07:00

437 lines
16 KiB
JavaScript

$(document).ready(function () {
// Initialize dropdown functionality
function initializeDropdown() {
// Toggle dropdown on click
$(document).on("click", ".dropdown-selected", function (e) {
e.stopPropagation();
const $dropdown = $(this).closest(".custom-dropdown");
const $options = $dropdown.find(".dropdown-options");
const $selected = $(this);
// Close other dropdowns
$(".custom-dropdown")
.not($dropdown)
.find(".dropdown-options")
.removeClass("show");
$(".custom-dropdown")
.not($dropdown)
.find(".dropdown-selected")
.removeClass("active");
// Toggle current dropdown
$options.toggleClass("show");
$selected.toggleClass("active");
});
// Handle option selection
$(document).on("click", ".dropdown-option", function (e) {
e.stopPropagation();
const $option = $(this);
const $dropdown = $option.closest(".custom-dropdown");
const $selected = $dropdown.find(".dropdown-selected");
const $options = $dropdown.find(".dropdown-options");
const $selectedText = $selected.find(".selected-text");
// Get data from selected option
const value = $option.data("value");
const months = $option.data("months");
const price = $option.data("price");
// Get text from the first span element to avoid getting badge text
const text = $option.find('span').first().text();
// Update selected text
$selectedText.text(text);
// Update hidden inputs
$dropdown.closest("form").find(".hiddenMonths").val(months);
$dropdown.closest("form").find(".hiddenTotalPrice").val(price);
// Update selected state
$options.find(".dropdown-option").removeClass("selected");
$option.addClass("selected");
// Close dropdown
$options.removeClass("show");
$selected.removeClass("active");
// Trigger custom event for other components
$dropdown.trigger("dropdown:changed", {
value: value,
months: months,
price: price,
text: text,
});
// Update price display using jQuery
const $productContainer = $dropdown.closest('[data-product-id]');
const basePrice = parseFloat($productContainer.data('base-price'));
const selectedMonths = parseInt(months);
// Calculate prices
const originalPrice = basePrice * selectedMonths;
const savings = selectedMonths >= 12 ? Math.round(originalPrice * 0.1) : 0;
const finalPrice = originalPrice - savings;
// Update display
$productContainer.find('.total-price-display').text('₫' + finalPrice.toLocaleString());
$productContainer.find('.months-display').text(selectedMonths + ' tháng');
$productContainer.find('.price-per-month').text('₫' + basePrice.toLocaleString() + '/tháng');
$productContainer.find('.original-price').text('₫' + originalPrice.toLocaleString());
$productContainer.find('.savings-amount').text('₫' + savings.toLocaleString());
// Show/hide savings info
if (savings > 0) {
$productContainer.find('.savings-info').show();
} else {
$productContainer.find('.savings-info').hide();
}
});
// Close dropdown when clicking outside
$(document).on("click", function (e) {
if (!$(e.target).closest(".custom-dropdown").length) {
$(".dropdown-options").removeClass("show");
$(".dropdown-selected").removeClass("active");
}
});
// Close dropdown on escape key
$(document).on("keydown", function (e) {
if (e.key === "Escape") {
$(".dropdown-options").removeClass("show");
$(".dropdown-selected").removeClass("active");
}
});
}
// Initialize on page load
initializeDropdown();
// Optimize form submission to prevent blocking
$(document).on('submit', 'form[action*="them-gio-hang"]', function(e) {
const $form = $(this);
const $submitBtn = $form.find('button[type="submit"]');
// Disable submit button to prevent double submission
$submitBtn.prop('disabled', true).text('Đang xử lý...');
// Add loading state
$form.addClass('submitting');
// Re-enable button after 5 seconds as fallback
setTimeout(() => {
$submitBtn.prop('disabled', false).text('Đăng ký ngay');
$form.removeClass('submitting');
}, 5000);
});
// Cache utility functions - Single JSON approach
const VM_INFO_CACHE_KEY = "vm_info_cache";
function saveVMInfoToCache(templateId, data, ttl = 30 * 60 * 1000) {
try {
const now = new Date().getTime();
let vmInfoCache = getVMInfoCache();
if (!vmInfoCache) {
vmInfoCache = {
lastUpdated: now,
data: {},
};
}
vmInfoCache.data[templateId] = {
value: data,
expiry: now + ttl,
};
vmInfoCache.lastUpdated = now;
localStorage.setItem(
VM_INFO_CACHE_KEY,
JSON.stringify(vmInfoCache)
);
} catch (e) {
console.warn("VM Info cache save failed:", e);
}
}
function getVMInfoFromCache(templateId) {
try {
const vmInfoCache = getVMInfoCache();
if (!vmInfoCache || !vmInfoCache.data[templateId]) {
return null;
}
const item = vmInfoCache.data[templateId];
const now = new Date().getTime();
// Check if expired
if (now > item.expiry) {
removeVMInfoFromCache(templateId);
return null;
}
return item.value;
} catch (e) {
console.warn("VM Info cache get failed:", e);
return null;
}
}
function getVMInfoCache() {
try {
const cacheStr = localStorage.getItem(VM_INFO_CACHE_KEY);
return cacheStr ? JSON.parse(cacheStr) : null;
} catch (e) {
console.warn("VM Info cache parse failed:", e);
return null;
}
}
function removeVMInfoFromCache(templateId) {
try {
const vmInfoCache = getVMInfoCache();
if (vmInfoCache && vmInfoCache.data[templateId]) {
delete vmInfoCache.data[templateId];
vmInfoCache.lastUpdated = new Date().getTime();
localStorage.setItem(
VM_INFO_CACHE_KEY,
JSON.stringify(vmInfoCache)
);
}
} catch (e) {
console.warn("VM Info cache remove failed:", e);
}
}
function clearExpiredVMInfoCache() {
try {
const vmInfoCache = getVMInfoCache();
if (!vmInfoCache) {
return;
}
const now = new Date().getTime();
let hasExpired = false;
Object.keys(vmInfoCache.data).forEach((templateId) => {
const item = vmInfoCache.data[templateId];
if (now > item.expiry) {
delete vmInfoCache.data[templateId];
hasExpired = true;
}
});
if (hasExpired) {
vmInfoCache.lastUpdated = now;
localStorage.setItem(
VM_INFO_CACHE_KEY,
JSON.stringify(vmInfoCache)
);
}
} catch (e) {
console.warn("VM Info cache cleanup failed:", e);
}
}
// Re-initialize if content is dynamically loaded
$(document).on("DOMContentLoaded", initializeDropdown);
// Load VM information for each product with caching (OS, CPU, RAM, Disk) - NON-BLOCKING
function loadGuestOSInfo() {
// Get unique template IDs to avoid duplicate API calls
const templateIds = [];
$(".guest-os").each(function () {
const templateId = $(this).data("template-id");
if (templateId && !templateIds.includes(templateId)) {
templateIds.push(templateId);
}
});
// Load VM info in background without blocking form submission
templateIds.forEach((templateId) => {
// Check cache first
const cachedData = getVMInfoFromCache(templateId);
if (cachedData) {
// Use cached data
updateVMInfoUI(templateId, cachedData);
return;
}
// Add loading indicators
addLoadingIndicators(templateId);
// Call API to get complete VM info - with shorter timeout
$.ajax({
url: `/api/v1/get-vm-info/${templateId}/guest_os,cpu_count,memory_size,disk_capacity`,
method: "GET",
timeout: 5000, // Reduced timeout to 5 seconds
headers: {
"X-CSRF-TOKEN": $('meta[name="csrf-token"]').attr(
"content"
),
Accept: "application/json",
},
success: function (response) {
if (response.error === false && response.data) {
const vmData = response.data;
const processedData = processVMData(vmData);
// Cache the result for 30 minutes
saveVMInfoToCache(templateId, processedData);
// Update UI
updateVMInfoUI(templateId, processedData);
} else {
setFallbackValues(templateId);
}
},
error: function (xhr, status, error) {
console.warn(
"Failed to load VM info for template:",
templateId,
error
);
setFallbackValues(templateId);
}
});
});
}
// Load VM info after a short delay to not block page load
setTimeout(loadGuestOSInfo, 1000);
// Process VM data from API response
function processVMData(vmData) {
const guestOS = vmData.guest_os || "UNKNOWN";
const cpuCount = vmData.cpu_count || 0;
const memoryMB = vmData.memory_size || 0;
const diskBytes = vmData.disk_capacity || 0;
return {
guestOS: guestOS,
formattedOS: formatOSName(guestOS),
cpu: cpuCount,
formattedCPU: `${cpuCount} vCore${cpuCount > 1 ? "s" : ""}`,
ram: Math.round((memoryMB / 1024) * 100) / 100, // Convert MB to GB, 2 decimal places
formattedRAM: `${Math.round((memoryMB / 1024) * 100) / 100} GB`,
disk: Math.round((diskBytes / (1024 * 1024 * 1024)) * 100) / 100, // Convert bytes to GB
formattedDisk: `${
Math.round((diskBytes / (1024 * 1024 * 1024)) * 100) / 100
} GB`,
};
}
// Add loading indicators to all VM info elements
function addLoadingIndicators(templateId) {
const loadingIcon =
'<i class="fas fa-spinner fa-spin text-orange-500 mr-1"></i>Đang tải...';
$(`.guest-os[data-template-id="${templateId}"]`).html(loadingIcon);
$(`.vm-cpu[data-template-id="${templateId}"]`).html(loadingIcon);
$(`.vm-ram[data-template-id="${templateId}"]`).html(loadingIcon);
$(`.vm-disk[data-template-id="${templateId}"]`).html(loadingIcon);
}
// Update all VM info UI elements
function updateVMInfoUI(templateId, data) {
// Update OS
$(`.guest-os[data-template-id="${templateId}"]`).html(data.formattedOS);
updateOSIcon(templateId, data.guestOS);
// Update hardware specs
$(`.vm-cpu[data-template-id="${templateId}"]`).html(data.formattedCPU);
$(`.vm-ram[data-template-id="${templateId}"]`).html(data.formattedRAM);
$(`.vm-disk[data-template-id="${templateId}"]`).html(
data.formattedDisk
);
}
// Set fallback values when API fails
function setFallbackValues(templateId) {
$(`.guest-os[data-template-id="${templateId}"]`).html("Linux");
$(`.vm-cpu[data-template-id="${templateId}"]`).html("N/A");
$(`.vm-ram[data-template-id="${templateId}"]`).html("N/A");
$(`.vm-disk[data-template-id="${templateId}"]`).html("N/A");
}
// Update OS icon based on detected OS
function updateOSIcon(templateId, osName) {
const $iconElement = $(`.os-icon[data-template-id="${templateId}"]`);
if (osName && osName.toLowerCase().includes("windows")) {
$iconElement.removeClass("fab fa-linux").addClass("fab fa-windows");
} else if (osName && osName.toLowerCase().includes("ubuntu")) {
$iconElement.removeClass("fab fa-linux").addClass("fab fa-ubuntu");
} else if (osName && osName.toLowerCase().includes("centos")) {
$iconElement.removeClass("fab fa-linux").addClass("fab fa-centos");
} else if (osName && osName.toLowerCase().includes("debian")) {
$iconElement.removeClass("fab fa-linux").addClass("fab fa-debian");
} else {
// Keep linux icon as default
$iconElement.removeClass().addClass("fab fa-linux text-orange-600");
}
}
// Format OS name for better display
function formatOSName(osName) {
const osMap = {
UBUNTU_64: "Ubuntu 64-bit",
CENTOS_64: "CentOS 64-bit",
CENTOS_7_64: "CentOS 7 64-bit",
CENTOS_8_64: "CentOS 8 64-bit",
ROCKY_LINUX_64: "Rocky Linux 64-bit",
WINDOWS_10_64: "Windows 10 64-bit",
WINDOWS_9_SERVER_64: "Windows Server 2019",
WINDOWS_SERVER_2019: "Windows Server 2019",
WINDOWS_SERVER_2022: "Windows Server 2022",
WINDOWS_2019: "Windows Server 2019",
WINDOWS_2022: "Windows Server 2022",
DEBIAN_10_64: "Debian 10 64-bit",
DEBIAN_11_64: "Debian 11 64-bit",
OTHER_LINUX_64: "Linux 64-bit",
OTHER_64: "Other 64-bit",
};
return osMap[osName] || osName || "Linux";
}
// Clean up expired cache entries
clearExpiredVMInfoCache();
// Load Guest OS info when page loads
loadGuestOSInfo();
// Expose cache management to global scope for debugging
window.vmInfoCache = {
clear: function () {
localStorage.removeItem(VM_INFO_CACHE_KEY);
},
refresh: function () {
this.clear();
loadGuestOSInfo();
},
info: function () {
const vmInfoCache = getVMInfoCache();
if (!vmInfoCache) {
return;
}
const templateIds = Object.keys(vmInfoCache.data);
templateIds.forEach((templateId) => {
const data = getVMInfoFromCache(templateId);
});
},
get: function (templateId) {
return getVMInfoFromCache(templateId);
},
remove: function (templateId) {
removeVMInfoFromCache(templateId);
},
raw: function () {
return getVMInfoCache();
},
};
});