Merge branch 'sync-sliders' into 'main'
sync the vcpu and memory sliders when moving around See merge request vshn/servala-frontend!7
This commit is contained in:
commit
7f7ffd625b
4 changed files with 251 additions and 11 deletions
|
|
@ -145,6 +145,36 @@ class DOMManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set smart default values based on available plans
|
||||||
|
setSmartDefaults(pricingDataManager) {
|
||||||
|
const { cpuValues, memoryValues } = pricingDataManager.getAvailableSliderValues();
|
||||||
|
|
||||||
|
// Use the smallest available CPU value as default
|
||||||
|
if (cpuValues.length > 0 && this.elements.cpuRange) {
|
||||||
|
const defaultCpu = Math.min(...cpuValues);
|
||||||
|
this.elements.cpuRange.value = defaultCpu;
|
||||||
|
if (this.elements.cpuValue) this.elements.cpuValue.textContent = defaultCpu;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the smallest available memory value as default
|
||||||
|
if (memoryValues.length > 0 && this.elements.memoryRange) {
|
||||||
|
const defaultMemory = Math.min(...memoryValues);
|
||||||
|
this.elements.memoryRange.value = defaultMemory;
|
||||||
|
if (this.elements.memoryValue) this.elements.memoryValue.textContent = defaultMemory;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep existing defaults for storage and instances
|
||||||
|
if (this.elements.storageRange) {
|
||||||
|
this.elements.storageRange.value = '20';
|
||||||
|
if (this.elements.storageValue) this.elements.storageValue.textContent = '20';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.elements.instancesRange) {
|
||||||
|
this.elements.instancesRange.value = '1';
|
||||||
|
if (this.elements.instancesValue) this.elements.instancesValue.textContent = '1';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get current selected service level
|
// Get current selected service level
|
||||||
getSelectedServiceLevel() {
|
getSelectedServiceLevel() {
|
||||||
return document.querySelector('input[name="serviceLevel"]:checked')?.value;
|
return document.querySelector('input[name="serviceLevel"]:checked')?.value;
|
||||||
|
|
|
||||||
|
|
@ -77,9 +77,12 @@ class PriceCalculator {
|
||||||
// Setup service levels based on available data
|
// Setup service levels based on available data
|
||||||
this.uiManager.setupServiceLevels(this.domManager, this.pricingDataManager);
|
this.uiManager.setupServiceLevels(this.domManager, this.pricingDataManager);
|
||||||
|
|
||||||
// Calculate and set slider maximums
|
// Calculate and set slider maximums and ranges
|
||||||
this.uiManager.updateSliderMaximums(this.domManager, this.pricingDataManager);
|
this.uiManager.updateSliderMaximums(this.domManager, this.pricingDataManager);
|
||||||
|
|
||||||
|
// Set smart default values based on available plans
|
||||||
|
this.domManager.setSmartDefaults(this.pricingDataManager);
|
||||||
|
|
||||||
// Populate plan dropdown
|
// Populate plan dropdown
|
||||||
this.planManager.populatePlanDropdown(this.domManager);
|
this.planManager.populatePlanDropdown(this.domManager);
|
||||||
|
|
||||||
|
|
@ -99,11 +102,21 @@ class PriceCalculator {
|
||||||
// Slider event listeners
|
// Slider event listeners
|
||||||
cpuRange.addEventListener('input', () => {
|
cpuRange.addEventListener('input', () => {
|
||||||
this.domManager.get('cpuValue').textContent = cpuRange.value;
|
this.domManager.get('cpuValue').textContent = cpuRange.value;
|
||||||
|
// Only synchronize if in auto-select mode (no manual plan selection)
|
||||||
|
const planSelect = this.domManager.get('planSelect');
|
||||||
|
if (!planSelect?.value) {
|
||||||
|
this.synchronizeMemoryToMatchingPlan(parseFloat(cpuRange.value));
|
||||||
|
}
|
||||||
this.updatePricing();
|
this.updatePricing();
|
||||||
});
|
});
|
||||||
|
|
||||||
memoryRange.addEventListener('input', () => {
|
memoryRange.addEventListener('input', () => {
|
||||||
this.domManager.get('memoryValue').textContent = memoryRange.value;
|
this.domManager.get('memoryValue').textContent = memoryRange.value;
|
||||||
|
// Only synchronize if in auto-select mode (no manual plan selection)
|
||||||
|
const planSelect = this.domManager.get('planSelect');
|
||||||
|
if (!planSelect?.value) {
|
||||||
|
this.synchronizeCpuToMatchingPlan(parseFloat(memoryRange.value));
|
||||||
|
}
|
||||||
this.updatePricing();
|
this.updatePricing();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -136,8 +149,8 @@ class PriceCalculator {
|
||||||
// Update pricing with the selected plan
|
// Update pricing with the selected plan
|
||||||
this.updatePricingWithPlan(selectedPlan);
|
this.updatePricingWithPlan(selectedPlan);
|
||||||
} else {
|
} else {
|
||||||
// Auto-select mode - reset sliders to default values
|
// Auto-select mode - reset sliders to smart default values
|
||||||
this.domManager.resetSlidersToDefaults();
|
this.domManager.setSmartDefaults(this.pricingDataManager);
|
||||||
|
|
||||||
// Auto-select mode - fade sliders back in
|
// Auto-select mode - fade sliders back in
|
||||||
this.uiManager.fadeInSliders(this.domManager, ['cpu', 'memory']);
|
this.uiManager.fadeInSliders(this.domManager, ['cpu', 'memory']);
|
||||||
|
|
@ -275,6 +288,150 @@ class PriceCalculator {
|
||||||
[...addons.mandatory, ...addons.optional]
|
[...addons.mandatory, ...addons.optional]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Synchronize memory slider to match CPU value with best matching plan
|
||||||
|
synchronizeMemoryToMatchingPlan(targetCpu) {
|
||||||
|
const serviceLevel = this.domManager.getSelectedServiceLevel();
|
||||||
|
if (!serviceLevel) return;
|
||||||
|
|
||||||
|
// Get all available plans for the current service level
|
||||||
|
const availablePlans = this.pricingDataManager.getPlansForServiceLevel(serviceLevel);
|
||||||
|
if (!availablePlans || availablePlans.length === 0) return;
|
||||||
|
|
||||||
|
// Snap CPU to nearest available value first
|
||||||
|
const { cpuValues } = this.pricingDataManager.getAvailableSliderValues();
|
||||||
|
const snappedCpu = this.findNearestValue(targetCpu, cpuValues);
|
||||||
|
|
||||||
|
// Update CPU slider to snapped value if different
|
||||||
|
if (snappedCpu !== targetCpu) {
|
||||||
|
const cpuRange = this.domManager.get('cpuRange');
|
||||||
|
const cpuValue = this.domManager.get('cpuValue');
|
||||||
|
if (cpuRange && cpuValue) {
|
||||||
|
cpuRange.value = snappedCpu;
|
||||||
|
cpuValue.textContent = snappedCpu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the plan that best matches the snapped CPU requirement
|
||||||
|
let bestPlan = null;
|
||||||
|
let minDifference = Infinity;
|
||||||
|
|
||||||
|
availablePlans.forEach(plan => {
|
||||||
|
const planCpu = parseFloat(plan.vcpus);
|
||||||
|
// Look for plans that meet or exceed the CPU requirement
|
||||||
|
if (planCpu >= snappedCpu) {
|
||||||
|
const difference = planCpu - snappedCpu;
|
||||||
|
if (difference < minDifference) {
|
||||||
|
minDifference = difference;
|
||||||
|
bestPlan = plan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// If no plan meets the CPU requirement, find the closest one below it
|
||||||
|
if (!bestPlan) {
|
||||||
|
availablePlans.forEach(plan => {
|
||||||
|
const planCpu = parseFloat(plan.vcpus);
|
||||||
|
const difference = Math.abs(planCpu - snappedCpu);
|
||||||
|
if (difference < minDifference) {
|
||||||
|
minDifference = difference;
|
||||||
|
bestPlan = plan;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update memory slider to match the found plan
|
||||||
|
if (bestPlan) {
|
||||||
|
const memoryRange = this.domManager.get('memoryRange');
|
||||||
|
const memoryValue = this.domManager.get('memoryValue');
|
||||||
|
|
||||||
|
if (memoryRange && memoryValue) {
|
||||||
|
memoryRange.value = bestPlan.ram;
|
||||||
|
memoryValue.textContent = bestPlan.ram;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronize CPU slider to match memory value with best matching plan
|
||||||
|
synchronizeCpuToMatchingPlan(targetMemory) {
|
||||||
|
const serviceLevel = this.domManager.getSelectedServiceLevel();
|
||||||
|
if (!serviceLevel) return;
|
||||||
|
|
||||||
|
// Get all available plans for the current service level
|
||||||
|
const availablePlans = this.pricingDataManager.getPlansForServiceLevel(serviceLevel);
|
||||||
|
if (!availablePlans || availablePlans.length === 0) return;
|
||||||
|
|
||||||
|
// Snap memory to nearest available value first
|
||||||
|
const { memoryValues } = this.pricingDataManager.getAvailableSliderValues();
|
||||||
|
const snappedMemory = this.findNearestValue(targetMemory, memoryValues);
|
||||||
|
|
||||||
|
// Update memory slider to snapped value if different
|
||||||
|
if (snappedMemory !== targetMemory) {
|
||||||
|
const memoryRange = this.domManager.get('memoryRange');
|
||||||
|
const memoryValue = this.domManager.get('memoryValue');
|
||||||
|
if (memoryRange && memoryValue) {
|
||||||
|
memoryRange.value = snappedMemory;
|
||||||
|
memoryValue.textContent = snappedMemory;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the plan that best matches the snapped memory requirement
|
||||||
|
let bestPlan = null;
|
||||||
|
let minDifference = Infinity;
|
||||||
|
|
||||||
|
availablePlans.forEach(plan => {
|
||||||
|
const planMemory = parseFloat(plan.ram);
|
||||||
|
// Look for plans that meet or exceed the memory requirement
|
||||||
|
if (planMemory >= snappedMemory) {
|
||||||
|
const difference = planMemory - snappedMemory;
|
||||||
|
if (difference < minDifference) {
|
||||||
|
minDifference = difference;
|
||||||
|
bestPlan = plan;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// If no plan meets the memory requirement, find the closest one below it
|
||||||
|
if (!bestPlan) {
|
||||||
|
availablePlans.forEach(plan => {
|
||||||
|
const planMemory = parseFloat(plan.ram);
|
||||||
|
const difference = Math.abs(planMemory - snappedMemory);
|
||||||
|
if (difference < minDifference) {
|
||||||
|
minDifference = difference;
|
||||||
|
bestPlan = plan;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update CPU slider to match the found plan
|
||||||
|
if (bestPlan) {
|
||||||
|
const cpuRange = this.domManager.get('cpuRange');
|
||||||
|
const cpuValue = this.domManager.get('cpuValue');
|
||||||
|
|
||||||
|
if (cpuRange && cpuValue) {
|
||||||
|
cpuRange.value = bestPlan.vcpus;
|
||||||
|
cpuValue.textContent = bestPlan.vcpus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the nearest value in an array to a target value
|
||||||
|
findNearestValue(target, availableValues) {
|
||||||
|
if (!availableValues || availableValues.length === 0) return target;
|
||||||
|
|
||||||
|
let nearest = availableValues[0];
|
||||||
|
let minDifference = Math.abs(target - nearest);
|
||||||
|
|
||||||
|
for (let i = 1; i < availableValues.length; i++) {
|
||||||
|
const difference = Math.abs(target - availableValues[i]);
|
||||||
|
if (difference < minDifference) {
|
||||||
|
minDifference = difference;
|
||||||
|
nearest = availableValues[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nearest;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export for use in other modules
|
// Export for use in other modules
|
||||||
|
|
|
||||||
|
|
@ -150,6 +150,31 @@ class PricingDataManager {
|
||||||
return { maxCpus, maxMemory };
|
return { maxCpus, maxMemory };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get all unique CPU and memory values from plans
|
||||||
|
getAvailableSliderValues() {
|
||||||
|
if (!this.pricingData) return { cpuValues: [], memoryValues: [] };
|
||||||
|
|
||||||
|
const cpuSet = new Set();
|
||||||
|
const memorySet = new Set();
|
||||||
|
|
||||||
|
// Collect all unique CPU and memory values across all plans
|
||||||
|
Object.keys(this.pricingData).forEach(groupName => {
|
||||||
|
const group = this.pricingData[groupName];
|
||||||
|
Object.keys(group).forEach(serviceLevel => {
|
||||||
|
group[serviceLevel].forEach(plan => {
|
||||||
|
cpuSet.add(parseFloat(plan.vcpus));
|
||||||
|
memorySet.add(parseFloat(plan.ram));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Convert to sorted arrays
|
||||||
|
const cpuValues = Array.from(cpuSet).sort((a, b) => a - b);
|
||||||
|
const memoryValues = Array.from(memorySet).sort((a, b) => a - b);
|
||||||
|
|
||||||
|
return { cpuValues, memoryValues };
|
||||||
|
}
|
||||||
|
|
||||||
// Get all plans for a specific service level
|
// Get all plans for a specific service level
|
||||||
getPlansForServiceLevel(serviceLevel) {
|
getPlansForServiceLevel(serviceLevel) {
|
||||||
if (!this.pricingData || !serviceLevel) return [];
|
if (!this.pricingData || !serviceLevel) return [];
|
||||||
|
|
|
||||||
|
|
@ -245,23 +245,51 @@ class UIManager {
|
||||||
|
|
||||||
if (!cpuRange || !memoryRange) return;
|
if (!cpuRange || !memoryRange) return;
|
||||||
|
|
||||||
const { maxCpus, maxMemory } = pricingDataManager.getSliderMaximums();
|
const { cpuValues, memoryValues } = pricingDataManager.getAvailableSliderValues();
|
||||||
|
|
||||||
// Set slider maximums with some padding
|
// Set CPU slider range based on available plan values
|
||||||
if (maxCpus > 0) {
|
if (cpuValues.length > 0) {
|
||||||
cpuRange.min = "0.25";
|
cpuRange.min = Math.min(...cpuValues);
|
||||||
cpuRange.max = Math.ceil(maxCpus);
|
cpuRange.max = Math.max(...cpuValues);
|
||||||
|
// Calculate step size - use the smallest difference between consecutive values
|
||||||
|
const cpuStep = this.calculateOptimalStep(cpuValues);
|
||||||
|
cpuRange.step = cpuStep;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxMemory > 0) {
|
// Set Memory slider range based on available plan values
|
||||||
memoryRange.min = "0.25";
|
if (memoryValues.length > 0) {
|
||||||
memoryRange.max = Math.ceil(maxMemory);
|
memoryRange.min = Math.min(...memoryValues);
|
||||||
|
memoryRange.max = Math.max(...memoryValues);
|
||||||
|
// Calculate step size - use the smallest difference between consecutive values
|
||||||
|
const memoryStep = this.calculateOptimalStep(memoryValues);
|
||||||
|
memoryRange.step = memoryStep;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update display values after changing min/max
|
// Update display values after changing min/max
|
||||||
domManager.updateSliderDisplayValues();
|
domManager.updateSliderDisplayValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Calculate optimal step size for slider based on available values
|
||||||
|
calculateOptimalStep(values) {
|
||||||
|
if (values.length <= 1) return 0.25; // Default step
|
||||||
|
|
||||||
|
// Find the smallest difference between consecutive values
|
||||||
|
let minDiff = Infinity;
|
||||||
|
for (let i = 1; i < values.length; i++) {
|
||||||
|
const diff = values[i] - values[i - 1];
|
||||||
|
if (diff > 0 && diff < minDiff) {
|
||||||
|
minDiff = diff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use the minimum difference as step, but ensure it's reasonable
|
||||||
|
// Round to common step values (0.25, 0.5, 1, etc.)
|
||||||
|
if (minDiff <= 0.25) return 0.25;
|
||||||
|
if (minDiff <= 0.5) return 0.5;
|
||||||
|
if (minDiff <= 1) return 1;
|
||||||
|
return Math.ceil(minDiff);
|
||||||
|
}
|
||||||
|
|
||||||
// Update instances slider based on service level and replica info
|
// Update instances slider based on service level and replica info
|
||||||
updateInstancesSlider(domManager, pricingDataManager) {
|
updateInstancesSlider(domManager, pricingDataManager) {
|
||||||
const instancesRange = domManager.get('instancesRange');
|
const instancesRange = domManager.get('instancesRange');
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue