diff --git a/Documents/Vibe Coding Projects/dedicatednodes-redesign/dedicatednodes-bare-metal/new-baremetal.html b/Documents/Vibe Coding Projects/dedicatednodes-redesign/dedicatednodes-bare-metal/new-baremetal.html
index 146cbf6..85ef124 100644
--- a/Documents/Vibe Coding Projects/dedicatednodes-redesign/dedicatednodes-bare-metal/new-baremetal.html
+++ b/Documents/Vibe Coding Projects/dedicatednodes-redesign/dedicatednodes-bare-metal/new-baremetal.html
@@ -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 => `
⚡ INSTANT
+ ${server.oos ? '
OOS
' : ''}
${server.cpu}
${server.cores} cores
@@ -3817,6 +3848,80 @@ var wpcf7 = {
`).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);
}