Skip to content

VM edit make RAM values human readable#2331

Merged
limetech merged 4 commits into
unraid:masterfrom
bobbintb:vm-memory-slider
Oct 15, 2025
Merged

VM edit make RAM values human readable#2331
limetech merged 4 commits into
unraid:masterfrom
bobbintb:vm-memory-slider

Conversation

@bobbintb

@bobbintb bobbintb commented Aug 12, 2025

Copy link
Copy Markdown
Contributor

This PR modifies the memory settings in the VM page in a few ways in an attempt to be more user friendly. Below is a list of changes and why I think they should be adopted, either in part or in whole.

  1. The RAM values have been replaced with more human readable values. RAM values are currently displayed in megabytes. I think this tends to look cluttered and it is more pronounced with machines with large amounts of RAM. For example, with 128GB of RAM the drop-down will display every value in MB in 512MB increments. With larger values, there are no commas either so it can be difficult to select the correct value. See the image below for reference.
image
  1. A jQuery slider with two handles for initial and maximum RAM has been added to work with the existing drop-downs. Either can be used and they will dynamically update one another. I think it looks better and is easier to use than just the drop-down, without making the UI too busy or cluttered.
  2. Selecting a value in the drop-down has been changed. Previously, all values were available in each drop-down and the max value would automatically change to match the initial value. I don't think this works as well with the slider and I think the visuals can be confusing. Each drop-down still lists all available values, but disables the ones that are not selectable because the other value has put them out of range. I think visually this is more cohesive than all values being available and automatically changing other values and it is more in line with what a user would expect to see.

The inline xml view still works and the slider is disabled if the VM is running, just as other elements are. No extra libraries are needed as this uses the existing jQuery version. Here are some screenshots to illustrate the changes:

image image image

Summary by CodeRabbit

  • New Features
    • Memory size dropdowns in VM Custom settings now display human-readable values: MB for sizes under 1 GB and GB (with one decimal) for 1 GB and above, improving clarity when selecting Initial and Max memory.
    • Consistent labeling applied to both Initial and Max memory options, ensuring uniform unit presentation across the configuration.

@coderabbitai

coderabbitai Bot commented Aug 12, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

CSS file shows a no-op replacement. The VM Custom form PHP updates memory dropdown option labels: it computes sizes per step, displays MB for values <1024 MB and GB (one decimal) for ≥1024 MB, applied to both Initial and Max memory. No public interfaces changed.

Changes

Cohort / File(s) Summary of changes
VM Manager styles
emhttp/plugins/dynamix.vm.manager/styles/dynamix.vm.manager.css
Replaced a line with an identical .log { cursor: zoom-in; } rule; no functional differences.
VM memory option labeling
emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php
Reworked generation of Initial/Max memory dropdown options to dynamically format labels in MB or GB (one decimal) based on size; underlying values computed from MB to KiB remain unchanged. Minor formatting tweak at script end.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant B as Browser (UI)
  participant S as PHP Template (Custom.form.php)

  U->>B: Open VM Custom form
  B->>S: Request form HTML
  Note right of S: Generate memory options<br/>- Compute sizeMB = i*512<br/>- value = sizeMB*1024 (KiB)<br/>- Label: MB if <1024, else GB (1 decimal)
  S-->>B: HTML with Initial/Max memory dropdowns (new labels)
  B-->>U: Render dropdowns with MB/GB labels
  U->>B: Select memory values (behavior unchanged)
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

I nibbled bytes and sipped some RAM,
From MB meadows to GB glam.
A tidy tweak, a label’s glow,
Now numbers say just what they know.
I thump my feet—so clear to see! 🥕
Dropdowns bloom in memory.

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
Title Check ✅ Passed The title succinctly captures the primary change of converting raw RAM values into human-readable units on the VM edit interface, aligning directly with the diff’s updates to the memory option labels. It is concise, focused on the main feature, and would inform a reviewer of the key enhancement without extraneous detail.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@bobbintb bobbintb marked this pull request as ready for review August 12, 2025 00:21

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php (3)

368-374: Consider enhancing formatMemoryDisplay for fractional GB values.

The function displays clean integer GB values but could be improved to handle fractional GB values (e.g., 1.5 GB) for better precision when needed.

Consider this enhancement:

 function formatMemoryDisplay($value) {
 	if ($value >= 1024 * 1024) {
-		return ($value / (1024 * 1024)) . ' GB';
+		$gb = $value / (1024 * 1024);
+		// Display as integer if whole number, otherwise show one decimal place
+		return (floor($gb) == $gb ? floor($gb) : number_format($gb, 1)) . ' GB';
 	} else {
 		return ($value / 1024) . ' MB';
 	}
 }

807-857: Complex slider handle override may cause maintenance issues.

The custom _mouseCapture override for handling equal slider values is complex and could break with jQuery UI updates. Consider documenting this thoroughly or finding a simpler approach.

Consider these alternatives:

  1. Add comprehensive comments explaining why this override is necessary
  2. Consider using a different slider library that natively supports this behavior
  3. Add version checking for jQuery UI to ensure compatibility

At minimum, add documentation:

+    // IMPORTANT: Custom override to handle slider behavior when both handles have equal values
+    // Default jQuery UI behavior makes it difficult to select specific handles when values overlap
+    // This override ensures the clicked handle is always selected, not the closest by value
     var slider = $("#memory-slider-range").slider("instance");

699-722: Consider extracting magic numbers into named constants.

The updateSelectOptions function has implicit dependencies on the slider configuration that could be made clearer.

Consider defining constants for clarity:

const SLIDER_CONFIG = {
    MIN_INDEX: 0,
    MAX_INDEX: <?=($maxmem*2)+1?>,
    INITIAL_MEM_HANDLE: 0,
    MAX_MEM_HANDLE: 1
};
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 18fdb23 and 16bdf41.

📒 Files selected for processing (2)
  • emhttp/plugins/dynamix.vm.manager/styles/dynamix.vm.manager.css (1 hunks)
  • emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php (2 hunks)
🔇 Additional comments (4)
emhttp/plugins/dynamix.vm.manager/styles/dynamix.vm.manager.css (1)

683-732: LGTM! Clean CSS implementation for the memory slider.

The CSS implementation for the new memory slider UI is well-structured. The styles appropriately handle the dual-handle slider with rounded edges on the appropriate handles and proper positioning using transforms. The flexbox layout for the memory selects ensures proper spacing and alignment.

emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php (3)

865-868: Good implementation of disabled state for running VMs.

The slider is properly disabled when the VM is running, preventing memory changes while the VM is active. This is a good safety measure.


639-663: Clean HTML structure for the memory slider UI.

The table structure properly integrates the new memory slider while maintaining the existing XML preview functionality. The hidden inputs ensure backward compatibility with the form submission.


683-695: Missing null/undefined checks in updateDisplay function.

The function doesn't verify that the DOM elements exist before attempting to update them, which could cause JavaScript errors if the elements are missing.

Add safety checks:

 function updateDisplay(field, value) {
     var hidden = document.getElementById('domain_' + field + '_hidden');
-    if (hidden) hidden.value = value;
+    if (hidden) {
+        hidden.value = value;
+    }
     var select = document.getElementById(field + '_select');
     if (select) {
         for (var i = 0; i < window.memoryValues.length; i++) {
             if (window.memoryValues[i] == value) {
                 select.selectedIndex = i;
                 break;
             }
         }
+    } else {
+        console.warn('Select element not found:', field + '_select');
     }
 }

Likely an incorrect or invalid review comment.

Comment on lines +351 to +366
function getSliderPosition($currentValue) {
global $maxmem;
$values = [128 * 1024, 256 * 1024];
for ($i = 1; $i <= ($maxmem*2); $i++) {
$values[] = $i * 512 * 1024;
}

$position = 0;
foreach ($values as $index => $value) {
if ($currentValue <= $value) {
$position = $index;
break;
}
}
return $position;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix potential infinite loop in getSliderPosition function.

The function lacks a safeguard if $currentValue exceeds all values in the array. The loop will complete without setting $position, leaving it at 0, which may not be the intended behavior for values larger than the maximum.

Apply this fix:

 function getSliderPosition($currentValue) {
 	global $maxmem;
 	$values = [128 * 1024, 256 * 1024];
 	for ($i = 1; $i <= ($maxmem*2); $i++) {
 		$values[] = $i * 512 * 1024;
 	}
 	
 	$position = 0;
 	foreach ($values as $index => $value) {
 		if ($currentValue <= $value) {
 			$position = $index;
 			break;
 		}
 	}
+	// If currentValue exceeds all values, set to maximum position
+	if ($position === 0 && $currentValue > $values[0]) {
+		$position = count($values) - 1;
+	}
 	return $position;
 }
🤖 Prompt for AI Agents
In emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php around lines 351
to 366, getSliderPosition builds a values array then searches for the first
value >= $currentValue but if $currentValue exceeds all entries the loop leaves
$position at 0; change the logic so after the foreach if no match was found you
set $position to the last index (e.g. count($values) - 1) to clamp to the
maximum position; ensure $position is an integer and return that clamped value.

Comment thread emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php Outdated
Comment on lines +776 to +789
var sliderValue = parseInt($(this).val());
var currentValues = $("#memory-slider-range").slider("values");

if (sliderValue <= currentValues[1]) {
$("#memory-slider-range").slider("values", 0, sliderValue);
var memValue = window.memoryValues[sliderValue] || window.memoryValues[0];
updateDisplay('mem', memValue);
updateSelectOptions(); // Update disabled options
} else {
// Reset select to current value
var currentIndex = <?=getSliderPosition($arrConfig['domain']['mem'])?>;
$(this).val(currentIndex);
}
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Potential for inconsistent state when select change validation fails.

When a user selects an invalid value in the dropdowns (lines 786-788 and 802-804), the select reverts to the previous value, but there's no user feedback about why their selection was rejected.

Add user feedback for invalid selections:

         } else {
             // Reset select to current value
             var currentIndex = <?=getSliderPosition($arrConfig['domain']['mem'])?>;
             $(this).val(currentIndex);
+            // Provide feedback to user
+            $(this).addClass('invalid-selection');
+            setTimeout(() => $(this).removeClass('invalid-selection'), 500);
         }

And add corresponding CSS:

.invalid-selection {
    border-color: #ff6b6b !important;
    animation: shake 0.3s;
}

@keyframes shake {
    0%, 100% { transform: translateX(0); }
    25% { transform: translateX(-2px); }
    75% { transform: translateX(2px); }
}

Also applies to: 791-805

@ljm42 ljm42 added the TBD label Aug 15, 2025

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php (3)

351-366: Clamp slider index when current value exceeds the last supported step

If $currentValue is larger than the largest entry, position stays 0, desynchronizing the slider from hidden/select values. Clamp to the last index.

 function getSliderPosition($currentValue) {
   global $maxmem;
   $values = [128 * 1024, 256 * 1024];
   for ($i = 1; $i <= ($maxmem*2); $i++) {
     $values[] = $i * 512 * 1024;
   }
-  
-  $position = 0;
+  $position = 0;
   foreach ($values as $index => $value) {
     if ($currentValue <= $value) {
       $position = $index;
       break;
     }
   }
-  return $position;
+  // Clamp to the maximum position if value exceeds the last step
+  $lastIndex = count($values) - 1;
+  if ($currentValue > $values[$lastIndex]) {
+    $position = $lastIndex;
+  }
+  return (int)$position;
 }

368-374: Unify memory formatting with JS (show one decimal for GB)

Current PHP returns raw float text (potentially many decimals) while JS uses one decimal for GB. Align PHP to 1 decimal and integer MB to match JS and avoid UI inconsistencies.

 function formatMemoryDisplay($value) {
-  if ($value >= 1024 * 1024) {
-    return ($value / (1024 * 1024)) . ' GB';
-  } else {
-    return ($value / 1024) . ' MB';
-  }
+  if ($value >= 1024 * 1024) {
+    $gb = $value / (1024 * 1024);
+    $text = (fmod($gb, 1.0) === 0.0) ? (string)intval($gb) : number_format($gb, 1);
+    return $text . ' GB';
+  }
+  return intval($value / 1024) . ' MB';
 }

774-806: Resetting to the original server index is incorrect; use the current slider index and add lightweight feedback

On invalid dropdown selection, it reverts to the initial PHP-calculated index, not the slider’s current value. This becomes wrong after any user change.

   $('#mem_select').change(function() {
     var sliderValue = parseInt($(this).val());
     var currentValues = $("#memory-slider-range").slider("values");
     
     if (sliderValue <= currentValues[1]) {
       $("#memory-slider-range").slider("values", 0, sliderValue);
       var memValue = window.memoryValues[sliderValue] || window.memoryValues[0];
       updateDisplay('mem', memValue);
       updateSelectOptions(); // Update disabled options
     } else {
-      // Reset select to current value
-      var currentIndex = <?=getSliderPosition($arrConfig['domain']['mem'])?>;
-      $(this).val(currentIndex);
+      // Reset to current slider value and provide subtle feedback
+      $(this).val(currentValues[0]).addClass('invalid-selection');
+      setTimeout(() => $(this).removeClass('invalid-selection'), 500);
     }
   });
 
   $('#maxmem_select').change(function() {
     var sliderValue = parseInt($(this).val()));
     var currentValues = $("#memory-slider-range").slider("values");
     
     if (sliderValue >= currentValues[0]) {
       $("#memory-slider-range").slider("values", 1, sliderValue);
       var maxmemValue = window.memoryValues[sliderValue] || window.memoryValues[0];
       updateDisplay('maxmem', maxmemValue);
       updateSelectOptions(); // Update disabled options
     } else {
-      // Reset select to current value
-      var currentIndex = <?=getSliderPosition($arrConfig['domain']['maxmem'])?>;
-      $(this).val(currentIndex);
+      $(this).val(currentValues[1]).addClass('invalid-selection');
+      setTimeout(() => $(this).removeClass('invalid-selection'), 500);
     }
   });

Optionally add a small CSS rule in the VM manager CSS to highlight the invalid moment:

.invalid-selection { outline: 2px solid #ff6b6b; }
🧹 Nitpick comments (2)
emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php (2)

376-383: Single source of truth for memory steps (avoid duplicating the generator in PHP and JS)

Build $memoryValues once in PHP and JSON‑embed it for JS instead of echoing a PHP for loop in JS. Keeps the arrays in lockstep and simplifies maintenance.

 // PHP
 $memoryValues = [
     128 * 1024,
     256 * 1024,
 ];
 for ($i = 1; $i <= ($maxmem * 2); $i++) {
     $memoryValues[] = $i * 512 * 1024;
 }
 
 // JS
-window.memoryValues = [
-    128 * 1024,   // 128 MB
-    256 * 1024,   // 256 MB
-    <?php for ($i = 1; $i <= ($maxmem*2); $i++) { ?>
-    <?=$i * 512 * 1024?>,  // <?=$i * 512?> MB
-    <?php } ?>
-];
+window.memoryValues = <?= json_encode($memoryValues) ?>;

Also applies to: 666-675


638-659: Make new labels translatable and accessible

Add translations and proper label associations for a11y.

-      <span class="advanced">Initial/Max </span>Memory:
+      <span class="advanced">_(Initial/Max)_ </span>_(Memory)_:
 ...
-        <div>
-            <span>Initial:</span>
-            <select id="mem_select"></select>
-        </div>
+        <div>
+            <label for="mem_select">_(Initial)_:</label>
+            <select id="mem_select" aria-label="<?=_('Initial memory')?>"></select>
+        </div>
 ...
-        <div>
-            <span>Maximum:</span>
-            <select id="maxmem_select"></select>
-        </div>
+        <div>
+            <label for="maxmem_select">_(Maximum)_:</label>
+            <select id="maxmem_select" aria-label="<?=_('Maximum memory')?>"></select>
+        </div>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 16bdf41 and 2c09c21.

📒 Files selected for processing (2)
  • emhttp/plugins/dynamix.vm.manager/styles/dynamix.vm.manager.css (1 hunks)
  • emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • emhttp/plugins/dynamix.vm.manager/styles/dynamix.vm.manager.css
🔇 Additional comments (4)
emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php (4)

351-356: Ensure $maxmem is defined and in the expected units before use

Multiple places assume $maxmem exists and represents GiB (e.g., to build steps and slider max). If undefined/mis-scaled, PHP notices or mismatched arrays vs. slider bounds will occur. Add a defensive init near the first usage.

+// Ensure $maxmem (GiB) is defined; derive from current config if missing
+if (!isset($maxmem)) {
+  // domain[maxmem] is in KiB; convert to GiB and clamp >= 1
+  $maxmem = max(1, (int) floor($arrConfig['domain']['maxmem'] / (1024 * 1024)));
+}

Would you confirm where $maxmem is initialized upstream (includes/templates)? If it’s guaranteed, we can skip the fallback.

Also applies to: 381-383, 758-759, 671-673


807-858: Overriding jQuery UI’s private _mouseCapture can be brittle across upgrades

This works, but it relies on an internal API. If jQuery UI is updated, behavior may change. Consider gating the override with a version check or falling back to a “start” handler strategy that uses ui.handle to pick the active handle.

Would you confirm the bundled jQuery UI version and whether it’s pinned? If not pinned, we should add a guard:

if (slider && typeof slider._mouseCapture === 'function') { /* override */ }

683-697: LGTM: hidden inputs + select sync is correct

Hidden values are kept authoritative; selects reflect indices; lookup by exact step avoids float issues.


698-723: LGTM: constraints are enforced via disabled options

Simple, predictable UX and consistent with the slider.

@github-actions

Copy link
Copy Markdown

🔧 PR Test Plugin Available

A test plugin has been generated for this PR that includes the modified files.

Version: 2025.09.17.1810
Build: View Workflow Run

📥 Installation Instructions:

Install via Unraid Web UI:

  1. Go to Plugins → Install Plugin
  2. Copy and paste this URL:
https://preview.dl.unraid.net/pr-plugins/pr-2331/webgui-pr-2331.plg
  1. Click Install

Alternative: Direct Download

⚠️ Important Notes:

  • Testing only: This plugin is for testing PR changes
  • Backup included: Original files are automatically backed up
  • Easy removal: Files are restored when plugin is removed
  • Conflicts: Remove this plugin before installing production updates

📝 Modified Files:

Click to expand file list
emhttp/plugins/dynamix.vm.manager/styles/dynamix.vm.manager.css
emhttp/plugins/dynamix.vm.manager/templates/Custom.form.php

🔄 To Remove:

Navigate to Plugins → Installed Plugins and remove webgui-pr-2025.09.17.1810, or run:

plugin remove webgui-pr-2025.09.17.1810

🤖 This comment is automatically generated and will be updated with each new push to this PR.

@ljm42

ljm42 commented Sep 29, 2025

Copy link
Copy Markdown
Member

Thanks for this. I think updating the dropdown with with more user friendly values makes a lot of sense. But at this point we don't really want to introduce new controls to the legacy UI. If you would be willing to update the PR to only include the more friendly dropdown settings we should be able to get it in.

Thanks!

@ljm42 ljm42 changed the title VM edit includes a slider for RAM values and the values are more human readable VM edit make RAM values human readable Oct 14, 2025
@ljm42 ljm42 added 7.2 and removed TBD labels Oct 14, 2025
@limetech limetech merged commit 1ede0dd into unraid:master Oct 15, 2025
1 check passed
@limetech

Copy link
Copy Markdown
Contributor

thank you!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants