refactor(ResourceLimits): make all params optional and use defaults in DB; adjust UI placeholders for clarity

This commit is contained in:
Dominic 2026-01-11 06:06:20 +01:00 committed by Dominic Schmid
parent 5d21b9fadd
commit 4e643c3226
2 changed files with 82 additions and 51 deletions

View file

@ -9,28 +9,30 @@ class ResourceLimits extends Component
{ {
use AuthorizesRequests; use AuthorizesRequests;
public $resource; // Default values for resource limits
private const DEFAULT_CPU_LIMIT = 0.0;
private const DEFAULT_CPU_SET = '0';
private const DEFAULT_CPU_SHARES = 1024;
private const DEFAULT_MEMORY_SWAPPINESS = 60;
private const DEFAULT_MEMORY_LIMIT = '0';
private const DEFAULT_MEMORY_SWAP = '0';
private const DEFAULT_MEMORY_RESERVATION = '0';
// Explicit properties public mixed $resource;
public ?string $limitsCpus = null;
public ?float $limitsCpus = null;
public ?string $limitsCpuset = null; public ?string $limitsCpuset = null;
public ?int $limitsCpuShares = null; public ?int $limitsCpuShares = null;
public ?string $limitsMemory = null;
public string $limitsMemory; public ?string $limitsMemorySwap = null;
public ?int $limitsMemorySwappiness = null;
public string $limitsMemorySwap; public ?string $limitsMemoryReservation = null;
public int $limitsMemorySwappiness;
public string $limitsMemoryReservation;
protected $rules = [ protected $rules = [
'limitsMemory' => 'required|string', 'limitsMemory' => 'nullable|string',
'limitsMemorySwap' => 'required|string', 'limitsMemorySwap' => 'nullable|string',
'limitsMemorySwappiness' => 'required|integer|min:0|max:100', 'limitsMemorySwappiness' => 'nullable|integer|min:0|max:100',
'limitsMemoryReservation' => 'required|string', 'limitsMemoryReservation' => 'nullable|string',
'limitsCpus' => 'nullable|numeric|min:0|max:1024', 'limitsCpus' => 'nullable|numeric|min:0|max:1024',
'limitsCpuset' => 'nullable|string', 'limitsCpuset' => 'nullable|string',
'limitsCpuShares' => 'nullable|integer|min:0|max:8192', 'limitsCpuShares' => 'nullable|integer|min:0|max:8192',
@ -51,7 +53,7 @@ class ResourceLimits extends Component
* *
* @param bool $toModel If true, sync FROM properties TO model. If false, sync FROM model TO properties. * @param bool $toModel If true, sync FROM properties TO model. If false, sync FROM model TO properties.
*/ */
private function syncData(bool $toModel = false): void private function syncData(bool $toModel): void
{ {
if ($toModel) { if ($toModel) {
// Sync TO model (before save) // Sync TO model (before save)
@ -62,58 +64,87 @@ class ResourceLimits extends Component
$this->resource->limits_memory_swap = $this->limitsMemorySwap; $this->resource->limits_memory_swap = $this->limitsMemorySwap;
$this->resource->limits_memory_swappiness = $this->limitsMemorySwappiness; $this->resource->limits_memory_swappiness = $this->limitsMemorySwappiness;
$this->resource->limits_memory_reservation = $this->limitsMemoryReservation; $this->resource->limits_memory_reservation = $this->limitsMemoryReservation;
} else {
// Sync FROM model (on load/refresh) return;
$this->limitsCpus = $this->resource->limits_cpus; }
$this->limitsCpuset = $this->resource->limits_cpuset;
$this->limitsCpuShares = $this->resource->limits_cpu_shares; // Sync FROM model (on load/refresh)
$this->limitsMemory = $this->resource->limits_memory; $this->limitsCpus = $this->resource->limits_cpus;
$this->limitsMemorySwap = $this->resource->limits_memory_swap; $this->limitsCpuset = $this->resource->limits_cpuset;
$this->limitsMemorySwappiness = $this->resource->limits_memory_swappiness; $this->limitsCpuShares = $this->resource->limits_cpu_shares;
$this->limitsMemoryReservation = $this->resource->limits_memory_reservation; $this->limitsMemory = $this->resource->limits_memory;
$this->limitsMemorySwap = $this->resource->limits_memory_swap;
$this->limitsMemorySwappiness = $this->resource->limits_memory_swappiness;
$this->limitsMemoryReservation = $this->resource->limits_memory_reservation;
// Convert default values to null so UI shows placeholders instead of defaults
if ($this->limitsCpus === self::DEFAULT_CPU_LIMIT) {
$this->limitsCpus = null;
}
if ($this->limitsCpuset === self::DEFAULT_CPU_SET) {
$this->limitsCpuset = null;
}
if ($this->limitsCpuShares === self::DEFAULT_CPU_SHARES) {
$this->limitsCpuShares = null;
}
if ($this->limitsMemorySwappiness === self::DEFAULT_MEMORY_SWAPPINESS) {
$this->limitsMemorySwappiness = null;
}
if ($this->limitsMemory === self::DEFAULT_MEMORY_LIMIT) {
$this->limitsMemory = null;
}
if ($this->limitsMemorySwap === self::DEFAULT_MEMORY_SWAP) {
$this->limitsMemorySwap = null;
}
if ($this->limitsMemoryReservation === self::DEFAULT_MEMORY_RESERVATION) {
$this->limitsMemoryReservation = null;
} }
} }
public function mount() public function mount(): void
{ {
$this->syncData(false); $this->syncData(toModel: false);
} }
public function submit() public function submit(): void
{ {
try { try {
$this->authorize('update', $this->resource); $this->authorize('update', $this->resource);
// Apply default values to properties // Apply defaults for empty fields
if (!$this->limitsMemory) { if (empty($this->limitsMemory)) {
$this->limitsMemory = '0'; $this->limitsMemory = self::DEFAULT_MEMORY_LIMIT;
} }
if (!$this->limitsMemorySwap) { if (empty($this->limitsMemorySwap)) {
$this->limitsMemorySwap = '0'; $this->limitsMemorySwap = self::DEFAULT_MEMORY_SWAP;
} }
if (is_null($this->limitsMemorySwappiness)) { if (empty($this->limitsMemoryReservation)) {
$this->limitsMemorySwappiness = 60; $this->limitsMemoryReservation = self::DEFAULT_MEMORY_RESERVATION;
} }
if (!$this->limitsMemoryReservation) { if ($this->limitsCpus === null) {
$this->limitsMemoryReservation = '0'; $this->limitsCpus = self::DEFAULT_CPU_LIMIT;
} }
if (!$this->limitsCpus) { if (empty($this->limitsCpuset)) {
$this->limitsCpus = '0'; $this->limitsCpuset = self::DEFAULT_CPU_SET;
} }
if ($this->limitsCpuset === '') { if ($this->limitsCpuShares === null) {
$this->limitsCpuset = null; $this->limitsCpuShares = self::DEFAULT_CPU_SHARES;
} }
if (is_null($this->limitsCpuShares)) { if ($this->limitsMemorySwappiness === null) {
$this->limitsCpuShares = 1024; $this->limitsMemorySwappiness = self::DEFAULT_MEMORY_SWAPPINESS;
} }
$this->validate(); $this->validate();
$this->syncData(true); $this->syncData(toModel: true);
$this->resource->save(); $this->resource->save();
// Reload from model to convert defaults back to null for placeholder display
$this->syncData(toModel: false);
$this->dispatch('success', 'Resource limits updated.'); $this->dispatch('success', 'Resource limits updated.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); handleError($e, $this);
} }
} }
} }

View file

@ -11,7 +11,7 @@
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div class="flex flex-col md:flex-row gap-4"> <div class="flex flex-col md:flex-row gap-4">
<x-forms.input canGate="update" :canResource="$resource" type="number" min="0" max="1024" step="0.1" <x-forms.input canGate="update" :canResource="$resource" type="number" min="0" max="1024" step="0.1"
placeholder="1.5" placeholder="0"
helper="Limit how much CPU the container can use. 0 means unlimited (use all available CPUs). Use decimal numbers like 1.5 for one and a half CPUs, or 0.5 for half a CPU.<br>More info <a class='underline dark:text-white' target='_blank' href='https://docs.docker.com/engine/reference/run/#cpu-quota-constraint'>cpu-quota</a>." helper="Limit how much CPU the container can use. 0 means unlimited (use all available CPUs). Use decimal numbers like 1.5 for one and a half CPUs, or 0.5 for half a CPU.<br>More info <a class='underline dark:text-white' target='_blank' href='https://docs.docker.com/engine/reference/run/#cpu-quota-constraint'>cpu-quota</a>."
label="CPU Limit" id="limitsCpus"> label="CPU Limit" id="limitsCpus">
<x-slot:suffix> <x-slot:suffix>
@ -34,7 +34,7 @@
</x-forms.input> </x-forms.input>
</div> </div>
<div class="flex flex-col md:flex-row gap-4"> <div class="flex flex-col md:flex-row gap-4">
<x-forms.input canGate="update" :canResource="$resource" placeholder="0-2" <x-forms.input canGate="update" :canResource="$resource" placeholder="0"
helper="Pin container to specific CPU threads. 0 means use all threads. Example: 0-1,4 results in using threads 0,1,4.<br>More info <a class='underline dark:text-white' target='_blank' href='https://docs.docker.com/engine/reference/run/#cpuset-constraint'>cpuset</a>." helper="Pin container to specific CPU threads. 0 means use all threads. Example: 0-1,4 results in using threads 0,1,4.<br>More info <a class='underline dark:text-white' target='_blank' href='https://docs.docker.com/engine/reference/run/#cpuset-constraint'>cpuset</a>."
label="CPU sets to use" id="limitsCpuset"> label="CPU sets to use" id="limitsCpuset">
<x-slot:suffix> <x-slot:suffix>
@ -74,7 +74,7 @@
<x-forms.input-with-select canGate="update" :canResource="$resource" <x-forms.input-with-select canGate="update" :canResource="$resource"
type="number" type="number"
min="0" min="0"
placeholder="512" placeholder="0"
helper="Hard limit on container memory usage. The container will be killed if it exceeds this limit.<br>More info <a class='underline dark:text-white' target='_blank' href='https://docs.docker.com/compose/compose-file/05-services/#mem_limit'>mem_limit</a>." helper="Hard limit on container memory usage. The container will be killed if it exceeds this limit.<br>More info <a class='underline dark:text-white' target='_blank' href='https://docs.docker.com/compose/compose-file/05-services/#mem_limit'>mem_limit</a>."
label="Memory Limit" id="limitsMemory" label="Memory Limit" id="limitsMemory"
:options="['b' => 'B', 'k' => 'KiB', 'm' => 'MiB', 'g' => 'GiB']" :options="['b' => 'B', 'k' => 'KiB', 'm' => 'MiB', 'g' => 'GiB']"
@ -82,7 +82,7 @@
<x-forms.input-with-select canGate="update" :canResource="$resource" <x-forms.input-with-select canGate="update" :canResource="$resource"
type="number" type="number"
min="0" min="0"
placeholder="256" placeholder="0"
helper="Guaranteed memory reservation for the container. Docker attempts to ensure this amount is always available.<br>More info <a class='underline dark:text-white' target='_blank' href='https://docs.docker.com/compose/compose-file/05-services/#mem_reservation'>mem_reservation</a>." helper="Guaranteed memory reservation for the container. Docker attempts to ensure this amount is always available.<br>More info <a class='underline dark:text-white' target='_blank' href='https://docs.docker.com/compose/compose-file/05-services/#mem_reservation'>mem_reservation</a>."
label="Memory Reservation" id="limitsMemoryReservation" label="Memory Reservation" id="limitsMemoryReservation"
:options="['b' => 'B', 'k' => 'KiB', 'm' => 'MiB', 'g' => 'GiB']" :options="['b' => 'B', 'k' => 'KiB', 'm' => 'MiB', 'g' => 'GiB']"
@ -92,7 +92,7 @@
<x-forms.input-with-select canGate="update" :canResource="$resource" <x-forms.input-with-select canGate="update" :canResource="$resource"
type="number" type="number"
min="0" min="0"
placeholder="512" placeholder="0"
helper="Total limit for memory plus swap space. Combined limit for both RAM and swap usage.<br>More info <a class='underline dark:text-white' target='_blank' href='https://docs.docker.com/compose/compose-file/05-services/#memswap_limit'>memswap_limit</a>." helper="Total limit for memory plus swap space. Combined limit for both RAM and swap usage.<br>More info <a class='underline dark:text-white' target='_blank' href='https://docs.docker.com/compose/compose-file/05-services/#memswap_limit'>memswap_limit</a>."
label="Maximum Swap Limit" id="limitsMemorySwap" label="Maximum Swap Limit" id="limitsMemorySwap"
:options="['b' => 'B', 'k' => 'KiB', 'm' => 'MiB', 'g' => 'GiB']" :options="['b' => 'B', 'k' => 'KiB', 'm' => 'MiB', 'g' => 'GiB']"