instant has configurable options, custom too

This commit is contained in:
Gemini AI
2025-12-05 12:57:17 +04:00
Unverified
parent 6f1c8073b0
commit 428c8426c5

View File

@@ -3378,6 +3378,7 @@ var wpcf7 = {
let configIds = {};
let storageSelection = {}; // Track individual drive quantities
let isInstantCustomized = false; // Track customization state for instant servers
let verifiedDefaults = {};
// --- Static Fallback Data (Generated from Live Site) ---
const FALLBACK_CONFIG_OPTIONS = [
@@ -3763,10 +3764,37 @@ var wpcf7 = {
return { id, label, type, values, formName };
}
async checkStock(configUrl) {
let lastError = null;
for (const proxy of this.proxies) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 4000);
const response = await fetch(proxy + encodeURIComponent(configUrl), { signal: controller.signal });
clearTimeout(timeoutId);
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const html = await response.text();
const doc = new DOMParser().parseFromString(html, 'text/html');
const bodyText = (doc.body?.innerText || '').toLowerCase();
const dangerBox = doc.querySelector('.alert-danger');
const dangerText = dangerBox ? dangerBox.innerText.toLowerCase() : '';
if (bodyText.includes('out of stock') || dangerText.includes('out of stock')) return true;
return false;
} catch (err) {
lastError = err;
}
}
return false;
}
}
const scraper = new WHMCSScraper();
// Cache for OOS states to avoid repeated checks
const oosCache = new Map();
let oosCheckInProgress = new Set();
// Update Technical Specs Section (New Smart Card Layout)
function updateSpecs(server) {
const set = (id, val) => {
@@ -3793,8 +3821,10 @@ var wpcf7 = {
set('spec-storage-config-hero', server.storage || 'Configurable');
// Network
const netSpeed = (server.network || '10Gbps').replace('Gbps', '');
set('spec-network-speed-hero', netSpeed);
const netSrc = verifiedDefaults.network || server.network || '';
const netSpeedMatch = netSrc ? netSrc.match(/(\d+)\s*Gbps/i) : null;
const netSpeed = netSpeedMatch ? netSpeedMatch[1] : (netSrc ? netSrc.replace('Gbps','') : '');
set('spec-network-speed-hero', netSpeed || '-');
set('spec-network-bandwidth-hero', server.bandwidth || 'Unmetered');
}
@@ -3804,6 +3834,7 @@ var wpcf7 = {
grid.innerHTML = instantServers.map(server => `
<div class="server-option instant ${selectedServer?.id === server.id ? 'active' : ''}" data-id="${server.id}" onclick="selectInstantServer('${server.id}')">
<div class="server-badge instant">⚡ INSTANT</div>
${server.oos ? '<div class="server-badge" style="background:#ef4444;color:#fff;margin-left:8px;">OOS</div>' : ''}
<div class="server-title">${server.cpu}</div>
<div class="server-specs-row">
<span class="spec-pill">${server.cores} cores</span>
@@ -3817,6 +3848,80 @@ var wpcf7 = {
</div>
</div>
`).join('');
// Pre-fetch stock data in background for all instant servers
prefetchStockData();
}
// Pre-fetch stock data for all instant servers
async function prefetchStockData() {
const batchSize = 3; // Limit concurrent requests
const queue = [...instantServers];
async function processQueue() {
while (queue.length > 0) {
const batch = queue.splice(0, batchSize);
await Promise.all(batch.map(async (server) => {
const url = server.orderUrl;
// Skip if already cached or currently checking
if (oosCache.has(url) || oosCheckInProgress.has(url)) {
return;
}
oosCheckInProgress.add(url);
try {
const isOutOfStock = await scraper.checkStock(url);
if (isOutOfStock !== server.oos) { // Only update if changed
server.oos = isOutOfStock;
oosCache.set(url, isOutOfStock);
updateServerCardOOS(server); // Update specific card
if (selectedServer?.id === server.id) {
updateSummary();
}
} else {
oosCache.set(url, isOutOfStock);
}
} catch (err) {
console.error(`Failed to check stock for ${server.id}:`, err);
oosCache.set(url, false); // Assume in stock on error
} finally {
oosCheckInProgress.delete(url);
}
}));
// Small delay between batches to be nice to proxies
await new Promise(resolve => setTimeout(resolve, 500));
}
}
processQueue();
}
// Helper to update just the OOS badge on a specific card
function updateServerCardOOS(server) {
const card = document.querySelector(`.server-option.instant[data-id="${server.id}"]`);
if (!card) return;
const existingBadge = card.querySelector('.server-badge[style*="background:#ef4444"]');
if (server.oos) {
if (!existingBadge) {
// Insert badge after the INSTANT badge
const instantBadge = card.querySelector('.server-badge.instant');
if (instantBadge) {
const oosBadge = document.createElement('div');
oosBadge.className = 'server-badge';
oosBadge.style.cssText = 'background:#ef4444;color:#fff;margin-left:8px;';
oosBadge.textContent = 'OOS';
instantBadge.after(oosBadge);
}
}
} else {
if (existingBadge) {
existingBadge.remove();
}
}
}
// Render Custom Servers
@@ -3849,6 +3954,8 @@ var wpcf7 = {
// Reset Customization State
isInstantCustomized = false;
document.getElementById('instantOptions').style.display = 'none';
const instantContainer = document.getElementById('instantDynamicConfigContainer');
if (instantContainer) instantContainer.innerHTML = '';
document.getElementById('instantServersGrid').style.display = 'grid'; // Ensure grid is visible
updateSummary();
@@ -3877,59 +3984,43 @@ var wpcf7 = {
serversGrid.style.display = 'none';
instantOptions.style.display = 'block';
// Fetch options if container is empty OR if we want to refresh ensures all options are loaded
// Checking if container is empty is usually enough unless server changed.
// However, to be safe and ensure "all customizable options" are shown as requested:
if (container.innerHTML.trim() === '') {
loader.style.display = 'flex';
loader.style.display = 'flex';
container.innerHTML = '';
configState = {};
storageSelection = {};
try {
let scrapeUrl = selectedServer.orderUrl;
if (scrapeUrl.includes('&configoption')) {
scrapeUrl = scrapeUrl.split('&configoption')[0];
}
// Reset selection state
configState = {};
storageSelection = {};
try {
// 1. CLEAN THE URL: Remove specific config options to get the full configuration page
let scrapeUrl = selectedServer.orderUrl;
if (scrapeUrl.includes('&configoption')) {
scrapeUrl = scrapeUrl.split('&configoption')[0];
}
// 2. Parse Pre-selected Options from the Original URL
const preselected = {};
const urlParams = new URLSearchParams(selectedServer.orderUrl);
for (const [key, value] of urlParams.entries()) {
if (key.startsWith('configoption[')) {
// Extract ID: configoption[34] -> 34
const idMatch = key.match(/\[(\d+)\]/);
if (idMatch) {
preselected[idMatch[1]] = value;
}
const preselected = {};
const urlParams = new URLSearchParams(selectedServer.orderUrl);
for (const [key, value] of urlParams.entries()) {
if (key.startsWith('configoption[')) {
const idMatch = key.match(/\[(\d+)\]/);
if (idMatch) {
preselected[idMatch[1]] = value;
}
}
console.log("Pre-selected options for instant:", preselected);
// Use the scraper with the Clean URL
const optionsData = await scraper.getProductConfig(scrapeUrl, selectedServer);
if (optionsData && optionsData.options.length > 0) {
renderDynamicOptions(optionsData.options, true, preselected);
} else {
// Fallback if optionsData is null or empty
console.warn("No live options found, using fallback.");
renderFallbackOptions(container);
// Add a visual warning
const warning = document.createElement('div');
warning.style.cssText = "background: #fff3cd; color: #856404; padding: 10px; margin-bottom: 15px; border-radius: 4px; font-size: 0.9rem; text-align: center;";
warning.textContent = "Could not load live customization options. Standard configuration shown.";
container.insertBefore(warning, container.firstChild);
}
} catch (err) {
console.error("Error in customize click:", err);
renderFallbackOptions(container);
} finally {
loader.style.display = 'none';
}
const optionsData = await scraper.getProductConfig(scrapeUrl, selectedServer);
if (optionsData && optionsData.options.length > 0) {
renderDynamicOptions(optionsData.options, true, preselected);
} else {
renderFallbackOptions(container);
const warning = document.createElement('div');
warning.style.cssText = "background: #fff3cd; color: #856404; padding: 10px; margin-bottom: 15px; border-radius: 4px; font-size: 0.9rem; text-align: center;";
warning.textContent = "Could not load live customization options. Standard configuration shown.";
container.insertBefore(warning, container.firstChild);
}
} catch (err) {
renderFallbackOptions(container);
} finally {
loader.style.display = 'none';
}
updateSummary();
@@ -3939,7 +4030,6 @@ var wpcf7 = {
async function selectCustomServer(id) {
selectedServer = customServers.find(s => s.id === id);
renderCustomServers();
updateSpecs(selectedServer);
document.getElementById('instantToggleContainer').style.display = 'none';
@@ -3965,6 +4055,16 @@ var wpcf7 = {
loader.style.display = 'none';
if (optionsData && optionsData.options.length > 0) {
const netOpt = optionsData.options.find(o => o.type === 'network' || (o.label && o.label.toLowerCase().includes('network')) || (o.label && o.label.toLowerCase().includes('uplink')));
if (netOpt && netOpt.values && netOpt.values.length > 0) {
const defVal = netOpt.values[0];
verifiedDefaults.network = defVal.text;
configState[netOpt.label] = defVal.price || 0;
configIds[netOpt.id] = defVal.id;
} else {
verifiedDefaults.network = null;
}
updateSpecs(selectedServer);
renderDynamicOptions(optionsData.options);
} else {
// Fallback if scrape fails
@@ -4154,7 +4254,7 @@ var wpcf7 = {
if(type === 'ram') {
configState['RAM Configuration'] = price;
document.getElementById('summaryRam').textContent = name;
const specRam = document.getElementById('spec-ram-capacity');
const specRam = document.getElementById('spec-ram-capacity-hero');
if(specRam) specRam.innerText = name;
} else if(type === 'location') {
configState['Location'] = price;
@@ -4162,7 +4262,7 @@ var wpcf7 = {
} else if(type === 'network') {
configState['Network'] = price;
document.getElementById('summaryNetwork').textContent = name;
const specNetwork = document.getElementById('spec-network-speed');
const specNetwork = document.getElementById('spec-network-speed-hero');
if(specNetwork) specNetwork.innerText = name;
}
@@ -4255,7 +4355,7 @@ var wpcf7 = {
document.getElementById('summaryStorage').innerText = summaryText;
// Also update specs table if visible
const specStorage = document.getElementById('spec-storage-config');
const specStorage = document.getElementById('spec-storage-config-hero');
if(specStorage) specStorage.innerText = summaryParts.length > 0 ? summaryParts.join(' + ') : 'Configurable';
updateSummary();
@@ -4618,7 +4718,7 @@ var wpcf7 = {
const storageText = storageNames.length > 0 ? storageNames.join(' + ') : 'None';
document.getElementById('summaryStorage').textContent = storageText;
const specStorage = document.getElementById('spec-storage-config');
const specStorage = document.getElementById('spec-storage-config-hero');
if(specStorage) specStorage.innerText = storageText;
};
@@ -4645,13 +4745,13 @@ var wpcf7 = {
if (opt.type === 'ram') {
const cleanName = val.text.replace(/\s?\(.*?\)/, '');
document.getElementById('summaryRam').textContent = cleanName;
const specRam = document.getElementById('spec-ram-capacity');
const specRam = document.getElementById('spec-ram-capacity-hero');
if(specRam) specRam.innerText = cleanName;
}
if (opt.type === 'location') document.getElementById('summaryLocation').textContent = val.text;
if (opt.type === 'network') {
document.getElementById('summaryNetwork').textContent = val.text;
const specNet = document.getElementById('spec-network-speed');
const specNet = document.getElementById('spec-network-speed-hero');
if(specNet) specNet.innerText = val.text;
}
@@ -4709,7 +4809,7 @@ var wpcf7 = {
document.getElementById('summaryStorage').textContent = storageText;
// Update Tech Specs
const specStorage = document.getElementById('spec-storage-config');
const specStorage = document.getElementById('spec-storage-config-hero');
if(specStorage) specStorage.innerText = storageText;
}
@@ -4764,6 +4864,22 @@ var wpcf7 = {
if(countEl) countEl.textContent = `${filled}/${slots.length}`;
}
function buildInstantOrderUrl() {
if (!selectedServer || !selectedServer.orderUrl) return null;
try {
const base = selectedServer.orderUrl.split('&configoption')[0];
const u = new URL(base);
const p = new URLSearchParams(u.search);
Object.entries(configIds).forEach(([id, val]) => {
if (id && val) p.set(`configoption[${id}]`, val);
});
p.set('billingcycle', 'monthly');
return u.origin + u.pathname + '?' + p.toString();
} catch (e) {
return selectedServer.orderUrl;
}
}
// Update Summary
function updateSummary() {
const pill = document.getElementById('summaryHeaderPill');
@@ -4811,7 +4927,8 @@ var wpcf7 = {
const totalPrice = selectedServer.price + addonPrice;
document.getElementById('totalPrice').textContent = selectedServer.currency + totalPrice.toFixed(2);
btnDeployInstant.href = selectedServer.orderUrl;
const customUrl = buildInstantOrderUrl();
btnDeployInstant.href = customUrl || selectedServer.orderUrl;
// Update Button Labels for Customized State
const mainLabel = btnDeployInstant.querySelector('.main-label');
@@ -4825,9 +4942,9 @@ var wpcf7 = {
pill.style.color = 'white';
subtext.textContent = 'Ready in 15 minutes';
document.getElementById('totalPrice').textContent = selectedServer.currency + selectedServer.price;
btnDeployInstant.href = selectedServer.orderUrl;
// Update Button Labels for Default State
const mainLabel = btnDeployInstant.querySelector('.main-label');
const subLabel = btnDeployInstant.querySelector('.sub-label');
@@ -4863,9 +4980,42 @@ var wpcf7 = {
if (selectedServer.configUrl) {
configureBtn.href = selectedServer.configUrl;
}
const netEl = document.getElementById('summaryNetwork');
if (netEl && (netEl.textContent === '-' || netEl.textContent.trim() === '')) {
netEl.textContent = verifiedDefaults.network || netEl.textContent;
}
}
}
document.getElementById('btnDeployInstant').addEventListener('click', async (e) => {
if (!selectedServer) return;
e.preventDefault();
const url = isInstantCustomized ? (buildInstantOrderUrl() || selectedServer.orderUrl) : selectedServer.orderUrl;
// Check cache first for instant OOS response
let outOfStock = oosCache.get(url);
// If not cached, check stock (this should rarely happen now due to pre-fetch)
if (outOfStock === undefined) {
try {
outOfStock = await scraper.checkStock(url);
oosCache.set(url, outOfStock);
} catch (err) {
outOfStock = false;
oosCache.set(url, false);
}
}
if (outOfStock) {
selectedServer.oos = true;
renderInstantServers();
updateSummary();
} else {
window.location.href = url;
}
});
// Tab switching
document.querySelectorAll('.config-tab').forEach(tab => {
tab.addEventListener('click', () => {
@@ -4886,10 +5036,13 @@ var wpcf7 = {
renderInstantServers();
renderCustomServers();
// Trigger initial OOS check for all instant servers on page load
prefetchStockData();
// Pre-select the first instant server so the UI is active immediately
if(instantServers.length > 0) {
selectInstantServer(instantServers[0].id);
}
</script>
</body>
</html>
</html>