Update baremetal HTML page

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Claude
2025-12-07 13:08:40 +04:00
Unverified
parent 7974d56d36
commit 8bf0eb2be0

View File

@@ -3444,6 +3444,7 @@ var wpcf7 = {
let storageSelection = {}; // Track individual drive quantities
let isInstantCustomized = false; // Track customization state for instant servers
let verifiedDefaults = {};
let instantSummaryLocked = false;
// --- Static Fallback Data (Generated from Live Site) ---
const FALLBACK_CONFIG_OPTIONS = [
@@ -4166,6 +4167,7 @@ var wpcf7 = {
// Reset Customization State
isInstantCustomized = false;
instantSummaryLocked = false;
document.getElementById('instantOptions').style.display = 'none';
const instantContainer = document.getElementById('instantDynamicConfigContainer');
if (instantContainer) instantContainer.innerHTML = '';
@@ -4177,6 +4179,7 @@ var wpcf7 = {
// Close Instant Customization
function closeInstantCustomization() {
instantSummaryLocked = false;
isInstantCustomized = false;
document.getElementById('instantOptions').style.display = 'none';
document.getElementById('instantServersGrid').style.display = 'grid';
@@ -4207,6 +4210,8 @@ var wpcf7 = {
document.getElementById('btnCustomizeInstant').addEventListener('click', async function(e) {
e.preventDefault();
isInstantCustomized = true;
instantSummaryLocked = true;
applyBaseStorageSummary();
const instantOptions = document.getElementById('instantOptions');
const serversGrid = document.getElementById('instantServersGrid');
@@ -4259,83 +4264,18 @@ var wpcf7 = {
});
}
// For instant customization, we need to map the actual storage requirements to available options
// Parse storage requirements from description and match them to available options
console.log("Processing instant server with storage:", selectedServer.storage);
// Parse storage requirements from description
const storageReqs = [];
const parts = selectedServer.storage.split('+');
parts.forEach(part => {
const trimmed = part.trim();
if (!trimmed) return;
const match = trimmed.match(/^(\d+)x\s+(.+)$/);
if (match) {
storageReqs.push({
qty: parseInt(match[1]),
spec: match[2].trim()
});
}
});
// Map requirements to available storage options
let reqIndex = 0;
// For instant customization, prioritize the preselected IDs from URL
// These are the exact configurations from WHMCS
options.forEach(opt => {
let selectedValId = null;
let selectedValId = preselected[opt.id]; // Use the ID directly from WHMCS
if (opt.type === 'storage' && reqIndex < storageReqs.length) {
const req = storageReqs[reqIndex];
let bestMatch = null;
let bestScore = 0;
// Find the best matching option for this requirement
opt.values.forEach(val => {
let score = 0;
const valText = val.text.toLowerCase();
const reqSpec = req.spec.toLowerCase();
// Check capacity match (exact match required)
const valCap = valText.match(/(\d+(?:\.\d+)?)\s*tb/i);
const reqCap = reqSpec.match(/(\d+(?:\.\d+)?)\s*tb/i);
if (valCap && reqCap && valCap[1] === reqCap[1]) {
score += 100;
}
// Check brand match
if (valText.includes('crucial') && reqSpec.includes('crucial')) score += 50;
if (valText.includes('kioxia') && reqSpec.includes('kioxia')) score += 50;
if (valText.includes('samsung') && reqSpec.includes('samsung')) score += 50;
// Check model match
if (valText.includes('t705') && reqSpec.includes('t705')) score += 50;
if (valText.includes('cm7') && reqSpec.includes('cm7')) score += 50;
if (score > bestScore) {
bestScore = score;
bestMatch = val;
}
});
if (bestMatch && bestScore >= 100) {
selectedValId = bestMatch.id;
preselected[opt.id] = selectedValId;
reqIndex++;
} else {
// If no match found, try to use URL preselected value
if (preselected[opt.id]) {
const urlMatch = opt.values.find(v => v.id === preselected[opt.id]);
if (urlMatch) {
selectedValId = preselected[opt.id];
}
}
}
} else if (opt.type !== 'storage' && preselected[opt.id]) {
// Use preselected IDs for non-storage options
selectedValId = preselected[opt.id];
// Validate that the preselected ID exists in the options
if (selectedValId && !opt.values.find(v => v.id === selectedValId)) {
console.warn(`Invalid preselected ID ${selectedValId} for option ${opt.id}`);
selectedValId = null;
}
// Apply price adjustment for selected non-storage options
// For non-storage options, apply price adjustment using preselected IDs
if (opt.type !== 'storage' && selectedValId) {
const selectedVal = opt.values.find(v => v.id === selectedValId);
if (selectedVal && selectedVal.price > 0) {
@@ -4348,8 +4288,7 @@ var wpcf7 = {
});
// Render options with the exact WHMCS preselected values
// Also pass the storage requirements for proper pre-filling
renderDynamicOptions(options, true, preselected, storageReqs);
renderDynamicOptions(options, true, preselected);
} else {
renderFallbackOptions(container);
@@ -4704,10 +4643,13 @@ var wpcf7 = {
updateSummary();
};
function renderDynamicOptions(options, isInstant = false, preselected = {}, storageRequirements = []) {
function renderDynamicOptions(options, isInstant = false, preselected = {}) {
const container = isInstant ? document.getElementById('instantDynamicConfigContainer') : document.getElementById('dynamicConfigContainer');
container.innerHTML = '';
const planStorageCounts = isInstant ? parsePlanStorageCounts(selectedServer?.storage) : {};
const planTargets = isInstant ? buildPlanTargets(selectedServer?.storage) : [];
const groups = { core: [], storage: [], network: [], other: [] };
options.forEach(opt => {
@@ -4804,7 +4746,7 @@ var wpcf7 = {
container.appendChild(visualContainer);
// Always use pooled grid for these groups as they are by definition identical
container.appendChild(createPooledStorageGrid(groupOpts, isInstant, preselected, groupIndex, storageRequirements));
container.appendChild(createPooledStorageGrid(groupOpts, isInstant, preselected, planStorageCounts, groupIndex, planTargets));
});
}
@@ -4831,7 +4773,206 @@ var wpcf7 = {
}
// New Pooled Storage Grid
function createPooledStorageGrid(storageOptions, isInstant, preselected = {}, groupIndex = 0, storageRequirements = []) {
function normalizeStorageName(name) {
return String(name || '')
.toLowerCase()
.replace(/[\s\u00A0]+/g, ' ')
.replace(/€|eur|ƒ|ª|[,/\\]/g, ' ')
.replace(/[^a-z0-9 ]/g, ' ')
.trim();
}
function tokenizeStorageLabel(name) {
const stopWords = new Set(['eur', 'nvme']);
return normalizeStorageName(name)
.split(/\s+/)
.filter(token => token && !stopWords.has(token));
}
function parsePlanStorageCounts(raw) {
const counts = {};
if (!raw) return counts;
const parts = raw.split('+').map(part => part.trim()).filter(Boolean);
parts.forEach(part => {
const match = part.match(/(\d+)\s*x?\s*(.+)/i);
const qty = match ? parseInt(match[1], 10) : 1;
const label = match ? match[2].trim() : part;
const tokens = tokenizeStorageLabel(label);
if (!tokens.length) return;
const key = tokens.join(' ');
if (!counts[key]) {
counts[key] = { count: 0, tokens };
}
counts[key].count += qty > 0 ? qty : 1;
});
return counts;
}
function buildPlanTargets(raw) {
const targets = [];
if (!raw) return targets;
const parts = raw.split('+').map(part => part.trim()).filter(Boolean);
parts.forEach(part => {
const match = part.match(/(\d+)\s*x?\s*(.+)/i);
const qty = match ? parseInt(match[1], 10) : 1;
const label = match ? match[2].trim() : part;
const normalized = normalizeStorageName(label);
if (!normalized) return;
for (let i = 0; i < (qty > 0 ? qty : 1); i++) {
targets.push({ normalized, label });
}
});
return targets;
}
function decrementCount(counts, key) {
const entry = counts[key];
if (!entry || entry.count <= 0) return false;
entry.count--;
return true;
}
function consumePlanStorageCount(counts, text) {
const normalized = normalizeStorageName(text);
if (!normalized) return false;
if (counts[normalized] && counts[normalized].count > 0) {
return decrementCount(counts, normalized);
}
for (const key of Object.keys(counts)) {
const entry = counts[key];
if (entry.count <= 0) continue;
if (key.includes(normalized) || normalized.includes(key)) {
entry.count--;
return true;
}
}
return false;
}
function shouldAssignPlanValue(slot, value, counts) {
const valueTokens = tokenizeStorageLabel(value.text);
for (const key of Object.keys(counts)) {
const entry = counts[key];
if (entry.count <= 0) continue;
const hasIntersection = entry.tokens.every(token => valueTokens.includes(token)) ||
valueTokens.every(token => entry.tokens.includes(token));
if (hasIntersection) {
return key;
}
}
return null;
}
function assignInstantSlotValue(slot, value, preselected) {
slot.currentVal = value;
configState[slot.label] = 0;
configIds[slot.id] = value.id;
const displayName = value.text.replace(/\s?\(.*?\)/, '');
storageSelection[slot.id] = { name: displayName, price: 0 };
if (window.originalDrives) {
window.originalDrives.push({
slotId: slot.id,
driveId: value.id,
text: value.text,
price: value.price
});
}
if (preselected) {
preselected[slot.id] = value.id;
}
}
function applyPlanTargets(initializedSlots, targets, preselected) {
let index = 0;
while (index < targets.length) {
const target = targets[index];
let matchedSlot = null;
for (const slot of initializedSlots) {
if (slot.currentVal !== slot.values[0]) continue;
const matched = slot.values.find(value => {
const valueNorm = normalizeStorageName(value.text);
const targetNorm = target.normalized;
return valueNorm.includes(targetNorm) || targetNorm.includes(valueNorm);
});
if (matched) {
matchedSlot = { slot, value: matched };
break;
}
}
if (matchedSlot) {
assignInstantSlotValue(matchedSlot.slot, matchedSlot.value, preselected);
targets.splice(index, 1);
} else {
index++;
}
if (targets.length === 0) break;
}
}
function applyPlanStorageFallback(initializedSlots, counts, preselected) {
initializedSlots.forEach(slot => {
if (slot.currentVal !== slot.values[0]) return;
const matchKey = slot.values.reduce((matched, val) => {
if (matched) return matched;
const hitsKey = shouldAssignPlanValue(slot, val, counts);
return hitsKey ? { key: hitsKey, val } : null;
}, null);
if (matchKey && matchKey.val) {
slot.currentVal = matchKey.val;
consumePlanStorageCount(counts, matchKey.val.text);
assignInstantSlotValue(slot, matchKey.val, preselected);
}
});
}
function verifyStorageSelections(poolKey, counts, preselected) {
if (!counts || Object.keys(counts).length === 0) return;
const pool = window.pooledState[poolKey];
if (!pool) return;
const needs = {};
Object.entries(counts).forEach(([key, entry]) => {
if (entry.count > 0) {
needs[key] = { count: entry.count, tokens: entry.tokens };
}
});
if (!Object.keys(needs).length) return;
pool.slots.forEach(slot => {
if (slot.currentVal !== slot.values[0]) return;
const matchKey = slot.values.reduce((matched, val) => {
if (matched) return matched;
const hitsKey = shouldAssignPlanValue(slot, val, needs);
return hitsKey ? { key: hitsKey, val } : null;
}, null);
if (matchKey && matchKey.val) {
assignInstantSlotValue(slot, matchKey.val, preselected);
consumePlanStorageCount(needs, matchKey.val.text);
}
});
}
function createPooledStorageGrid(storageOptions, isInstant, preselected = {}, planCounts = {}, groupIndex = 0, planTargets = []) {
const grid = document.createElement('div');
grid.className = 'config-grid';
@@ -4843,13 +4984,17 @@ var wpcf7 = {
// Initialize slots with preselected values from WHMCS or defaults
const initializedSlots = storageOptions.map(opt => {
const preId = preselected[opt.id];
const slotKey = opt.id?.toString ? opt.id.toString() : opt.id;
const preId = preselected[slotKey] || preselected[Number(slotKey)];
let initialVal = opt.values[0]; // Default (usually None)
// Use the preselected value from WHMCS if available
if (preId) {
const found = opt.values.find(v => v.id === preId);
if (found) initialVal = found;
const found = opt.values.find(v => `${v.id}` === `${preId}`);
if (found) {
initialVal = found;
consumePlanStorageCount(planCounts, found.text);
}
}
return {
@@ -4862,90 +5007,65 @@ var wpcf7 = {
window.pooledState[poolKey] = { slots: initializedSlots };
// For instant customization, track original drives and pre-fill with requirements
// For instant customization, track original drives and use preselected IDs from URL
if (isInstant) {
window.originalDrives = [];
storageSelection = {};
// First reset all slots to None
// Set up the preselected drives from the URL configoption IDs
initializedSlots.forEach(slot => {
const noneOption = slot.values.find(v =>
v.text.trim() === '-' ||
v.text.toLowerCase().includes('none') ||
v.text.toLowerCase().includes('no hard drive')
);
if (noneOption) {
slot.currentVal = noneOption;
configState[slot.label] = noneOption.price;
configIds[slot.id] = noneOption.id;
}
});
// Use the preselected ID from URL for this slot
const slotKey = slot.id?.toString ? slot.id.toString() : slot.id;
const preselectedId = preselected[slotKey] || preselected[Number(slotKey)];
if (preselectedId) {
const preselectedValue = slot.values.find(v => `${v.id}` === `${preselectedId}`);
if (preselectedValue) {
// Use the preselected value from WHMCS URL
slot.currentVal = preselectedValue;
configState[slot.label] = 0; // Included in base price
configIds[slot.id] = preselectedId;
// Fill slots based on storage requirements
if (storageRequirements && storageRequirements.length > 0) {
// Clear storageSelection to start fresh
storageSelection = {};
// Store in storageSelection for summary
const displayName = preselectedValue.text.replace(/\s?\(.*?\)/, '');
storageSelection[slot.id] = {
name: displayName,
price: 0 // Free for included drives
};
storageRequirements.forEach(req => {
for (let i = 0; i < req.qty; i++) {
// Find an empty slot
const emptySlot = initializedSlots.find(s =>
s.currentVal.text === '-' ||
s.currentVal.text.toLowerCase().includes('none')
// Track as original drive
window.originalDrives.push({
slotId: slot.id,
driveId: preselectedId,
text: preselectedValue.text,
price: preselectedValue.price
});
} else {
// If preselected ID not found, use default (None)
const noneOption = slot.values.find(v =>
v.text.trim() === '-' ||
v.text.toLowerCase().includes('none') ||
v.text.toLowerCase().includes('no hard drive')
);
if (emptySlot) {
console.log(`Filling slot ${emptySlot.id} with ${req.spec}`);
// Find matching drive type
const match = emptySlot.values.find(v => {
const vText = v.text.toLowerCase();
const reqSpec = req.spec.toLowerCase();
// Extract capacity
const valCap = vText.match(/(\d+(?:\.\d+)?)\s*tb/i);
const reqCap = reqSpec.match(/(\d+(?:\.\d+)?)\s*tb/i);
if (valCap && reqCap && valCap[1] === reqCap[1]) {
// Check brand match
if ((vText.includes('crucial') && reqSpec.includes('crucial')) ||
(vText.includes('kioxia') && reqSpec.includes('kioxia')) ||
(vText.includes('t705') && reqSpec.includes('t705')) ||
(vText.includes('cm7') && reqSpec.includes('cm7'))) {
return true;
}
}
return false;
});
if (match) {
emptySlot.currentVal = match;
configState[emptySlot.label] = 0; // Included in base price
configIds[emptySlot.id] = match.id;
// Store in storageSelection for summary
const displayName = match.text.replace(/\s?\(.*?\)/, '');
storageSelection[emptySlot.id] = {
name: displayName,
price: 0 // Free for included drives
};
// Track as original drive
window.originalDrives.push({
slotId: emptySlot.id,
driveId: match.id,
text: match.text,
price: match.price
});
console.log(`✓ Filled with: ${match.text}`);
} else {
console.error(`✗ Could not find matching drive for: ${req.spec}`);
}
} else {
console.error(`✗ No empty slots available for: ${req.spec}`);
if (noneOption) {
slot.currentVal = noneOption;
configState[slot.label] = noneOption.price;
configIds[slot.id] = noneOption.id;
}
}
});
} else {
// If no preselected ID for this slot, use default (None)
const noneOption = slot.values.find(v =>
v.text.trim() === '-' ||
v.text.toLowerCase().includes('none') ||
v.text.toLowerCase().includes('no hard drive')
);
if (noneOption) {
slot.currentVal = noneOption;
configState[slot.label] = noneOption.price;
configIds[slot.id] = noneOption.id;
}
}
});
} else {
// Pre-set configState for custom servers
initializedSlots.forEach(slot => {
@@ -4954,12 +5074,20 @@ var wpcf7 = {
});
}
if (isInstant && Object.keys(planCounts).length > 0) {
applyPlanStorageFallback(initializedSlots, planCounts, preselected);
if (planTargets.length > 0) {
applyPlanTargets(initializedSlots, planTargets, preselected);
}
verifyStorageSelections(poolKey, planCounts, preselected);
}
templateValues.forEach(val => {
if (val.text.toLowerCase().includes('none') || val.text.toLowerCase().includes('no hard drive')) return;
const card = document.createElement('div');
card.className = 'config-card has-stepper';
card.id = `pool-card-${val.text.replace(/\s/g, '')}`;
card.id = `pool-card-${val.id}`;
let priceText = val.price === 0 ? 'Included' : `+€${val.price}`;
let displayName = val.text.replace(/\s?\(.*?\)/, '');
@@ -4968,9 +5096,9 @@ var wpcf7 = {
<div class="option-name">${displayName}</div>
<div class="option-price">${priceText}</div>
<div class="qty-stepper">
<button class="btn-qty" onclick="updatePooledQty('${poolKey}', '${val.text.replace(/'/g, "\\'")}', -1, ${isInstant})">-</button>
<span class="qty-val" id="pool-qty-${poolKey}-${val.text.replace(/\s/g, '')}">0</span>
<button class="btn-qty" onclick="updatePooledQty('${poolKey}', '${val.text.replace(/'/g, "\\'")}', 1, ${isInstant})">+</button>
<button class="btn-qty" onclick="updatePooledQty('${poolKey}', '${val.id}', -1, ${isInstant})">-</button>
<span class="qty-val" id="pool-qty-${poolKey}-${val.id}">0</span>
<button class="btn-qty" onclick="updatePooledQty('${poolKey}', '${val.id}', 1, ${isInstant})">+</button>
</div>
`;
grid.appendChild(card);
@@ -4979,17 +5107,17 @@ var wpcf7 = {
setTimeout(() => {
// Update quantity displays based on current selection
initializedSlots.forEach(slot => {
if (slot.currentVal && slot.currentVal.text !== '-' &&
!slot.currentVal.text.toLowerCase().includes('none') &&
!slot.currentVal.text.toLowerCase().includes('no hard drive')) {
if (slot.currentVal && slot.currentVal.text !== '-' &&
!slot.currentVal.text.toLowerCase().includes('none') &&
!slot.currentVal.text.toLowerCase().includes('no hard drive')) {
// Increment counter for this value type
const qtyEl = document.getElementById(`pool-qty-${poolKey}-${slot.currentVal.text.replace(/\s/g, '')}`);
if (qtyEl) {
const currentQty = parseInt(qtyEl.textContent) || 0;
qtyEl.textContent = currentQty + 1;
// Increment counter for this value type
const qtyEl = document.getElementById(`pool-qty-${poolKey}-${slot.currentVal.id || slot.currentVal.text}`);
if (qtyEl) {
const currentQty = parseInt(qtyEl.textContent) || 0;
qtyEl.textContent = currentQty + 1;
}
}
}
});
updatePooledVisuals(poolKey, isInstant);
@@ -5005,65 +5133,53 @@ var wpcf7 = {
return grid;
}
window.updatePooledQty = function(poolKey, valText, change, isInstant) {
window.updatePooledQty = function(poolKey, optionId, change, isInstant) {
if (instantSummaryLocked) {
instantSummaryLocked = false;
}
const pool = window.pooledState[poolKey];
if (!pool) return;
if (change > 0) {
// Find first slot that is "None" or default
const targetSlot = pool.slots.find(s => s.currentVal === s.values[0]);
const targetSlot = pool.slots.find(s =>
s.currentVal.text === '-' ||
s.currentVal.text.toLowerCase().includes('none') ||
s.currentVal.text.toLowerCase().includes('no hard drive')
);
if (targetSlot) {
// Find the matching value object in this slot's values
const newVal = targetSlot.values.find(v => v.text === valText);
const newVal = targetSlot.values.find(v => v.id === optionId);
if (newVal) {
targetSlot.currentVal = newVal;
// For instant customization, calculate price difference
// For instant customization, handle pricing correctly
if (isInstant && window.originalDrives) {
// Check if this slot had an original drive
const originalDrive = window.originalDrives.find(d => d.slotId === targetSlot.id);
if (originalDrive) {
// Calculate price difference
const priceDiff = newVal.price - originalDrive.price;
configState[targetSlot.label] = priceDiff;
// Update original drives tracking
originalDrive.driveId = newVal.id;
originalDrive.text = newVal.text;
originalDrive.price = newVal.price;
} else {
// This is a new drive, charge full price
configState[targetSlot.label] = newVal.price;
}
// For instant customization, the original drives are included in base price
// When adding a new drive, charge the full price of the new drive
configState[targetSlot.label] = newVal.price;
} else {
// Custom server or no tracking, charge full price
configState[targetSlot.label] = newVal.price;
}
targetSlot.currentVal = newVal;
configIds[targetSlot.id] = newVal.id;
}
}
} else {
// Decrement: Find last slot that matches this value
for (let i = pool.slots.length - 1; i >= 0; i--) {
if (pool.slots[i].currentVal.text === valText) {
if (pool.slots[i].currentVal.id === optionId) {
// Reset to default
const defaultVal = pool.slots[i].values[0];
const originalCurrentPrice = pool.slots[i].currentVal.price;
pool.slots[i].currentVal = defaultVal;
// For instant customization, refund the price if it was an upgrade
if (isInstant && window.originalDrives) {
const originalDrive = window.originalDrives.find(d => d.slotId === pool.slots[i].id);
if (originalDrive && pool.slots[i].currentVal.id !== originalDrive.driveId) {
// We're removing an upgrade, refund the difference
const currentVal = pool.slots[i].values.find(v => v.id === configIds[pool.slots[i].id]);
if (currentVal) {
configState[pool.slots[i].label] = -currentVal.price;
}
} else {
configState[pool.slots[i].label] = defaultVal.price;
}
// For instant customization, reset to default state
if (isInstant) {
// When removing a drive that was added, reset to 0 (no additional charge)
configState[pool.slots[i].label] = 0;
} else {
configState[pool.slots[i].label] = defaultVal.price;
}
@@ -5108,7 +5224,7 @@ var wpcf7 = {
filledCount++;
// Track quantity for stepper numbers
const key = slot.currentVal.text.replace(/\s/g, '');
const key = slot.currentVal.id || slot.currentVal.text.replace(/\s/g, '');
qtyMap[key] = (qtyMap[key] || 0) + 1;
} else {
slots[index].classList.remove('filled');
@@ -5165,12 +5281,46 @@ var wpcf7 = {
if(specStorage) specStorage.innerHTML = pillsHtml;
};
function formatStorageSummaryHtml(storageString) {
if (!storageString) {
return '<span style="color: var(--text-secondary); font-style: italic;">No drives selected</span>';
}
const parts = storageString.split('+').map(part => part.trim()).filter(Boolean);
if (parts.length === 0) {
return '<span style="color: var(--text-secondary); font-style: italic;">No drives selected</span>';
}
return parts.map(part => `
<span class="spec-pill" style="margin-right:4px; margin-bottom:4px; display:inline-block;">${part}</span>
`).join('');
}
function applyBaseStorageSummary() {
const summaryStorage = document.getElementById('summaryStorage');
const specStorage = document.getElementById('spec-storage-config-hero');
const storageDetails = selectedServer?.storage || null;
if (summaryStorage) {
summaryStorage.innerHTML = formatStorageSummaryHtml(storageDetails);
}
if (specStorage) {
specStorage.textContent = storageDetails || '-';
}
}
// Function to update storage summary with enhanced styling
function updateStorageSummary() {
// Collect all selected drives
const storageCount = {};
let storageHtml = '';
if (instantSummaryLocked) {
applyBaseStorageSummary();
return;
}
Object.values(storageSelection).forEach(drive => {
if (drive.name && !drive.name.toLowerCase().includes('none')) {
storageCount[drive.name] = (storageCount[drive.name] || 0) + 1;
@@ -5178,7 +5328,10 @@ var wpcf7 = {
});
if (Object.keys(storageCount).length === 0) {
document.getElementById('summaryStorage').innerHTML = '<span style="color: var(--text-secondary); font-style: italic;">No drives selected</span>';
const summaryStorage = document.getElementById('summaryStorage');
if (summaryStorage) {
summaryStorage.innerHTML = '<span style="color: var(--text-secondary); font-style: italic;">No drives selected</span>';
}
} else {
// Create styled drive pills
Object.entries(storageCount).forEach(([name, count], index) => {
@@ -5215,11 +5368,16 @@ var wpcf7 = {
if (specStorage) {
const storageText = Object.entries(storageCount)
.map(([name, count]) => `${count}x ${name}`)
.join(' + ') || 'None selected';
specStorage.textContent = storageText;
.join(' + ');
specStorage.textContent = storageText || selectedServer?.storage || '-';
}
}
function resolvePreselectedId(optionId, preselected) {
const key = optionId?.toString ? optionId.toString() : optionId;
return preselected[key] || preselected[Number(key)];
}
function createOptionGrid(opt, isWide, storageIndex = -1, isInstant = false, preselected = {}) {
const grid = document.createElement('div');
grid.className = isWide ? 'config-grid wide' : 'config-grid';
@@ -5229,7 +5387,8 @@ var wpcf7 = {
card.className = 'config-card';
// Check if this value is the preselected one
const isPreselected = preselected[opt.id] && preselected[opt.id] === val.id;
const preId = resolvePreselectedId(opt.id, preselected);
const isPreselected = preId && `${preId}` === `${val.id}`;
// Default behavior: Select if preselected, OR if no preselection exists and it's the first item
const isActive = isPreselected || (!preselected[opt.id] && index === 0);
@@ -5273,6 +5432,7 @@ var wpcf7 = {
}
// Always update the storage summary
if (instantSummaryLocked) instantSummaryLocked = false;
updateStorageSummary();
}
}
@@ -5677,17 +5837,37 @@ var wpcf7 = {
});
});
// Initialize
renderInstantServers();
renderCustomServers();
function initializePage() {
try {
renderInstantServers();
renderCustomServers();
// Trigger initial OOS check for all instant servers on page load
prefetchStockData();
fetchInstantPrices();
// Pre-select the first instant server so the UI is active immediately
if(instantServers.length > 0) {
selectInstantServer(instantServers[0].id);
}
} catch (error) {
console.error("Error during initial rendering:", error);
}
// Pre-select the first instant server so the UI is active immediately
if(instantServers.length > 0) {
selectInstantServer(instantServers[0].id);
// Trigger background tasks that could potentially fail
try {
prefetchStockData();
} catch (error) {
console.error("Error in prefetchStockData:", error);
}
try {
fetchInstantPrices();
} catch (error) {
console.error("Error in fetchInstantPrices:", error);
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initializePage);
} else {
initializePage();
}
</script>
</body>