From 4fbf5e10c7df04576a951391ea8f8aa7a988f05b Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 24 Jun 2026 08:08:26 +0200 Subject: [PATCH 1/2] Fix guest disk division by zero Guest disk rendering divided by the reported capacity in both the row and footer percentage output. Automount entries can report a capacity of zero, which made the VM disk usage table fail while rendering. The usage bar had the same zero-total division when calculating its CSS width. Zero-capacity rows and totals now render n/a for the percentage and a zero-width usage bar. --- .../Vspheredb/Web/Table/VmDiskUsageTable.php | 29 +++++++++++++++---- .../Vspheredb/Web/Widget/SimpleUsageBar.php | 14 ++++++++- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/library/Vspheredb/Web/Table/VmDiskUsageTable.php b/library/Vspheredb/Web/Table/VmDiskUsageTable.php index d57b34cb..da5d9edf 100644 --- a/library/Vspheredb/Web/Table/VmDiskUsageTable.php +++ b/library/Vspheredb/Web/Table/VmDiskUsageTable.php @@ -79,15 +79,15 @@ public function renderRow($row) $this->root = $row; } - $free = Format::bytes($row->free_space) - . sprintf(' (%0.3f%%)', ($row->free_space / $row->capacity) * 100); - $tr = $this::tr([ $this::td($caption, [ 'title' => $caption ]), $this::td(Format::bytes($row->capacity), ['class' => 'vm-disk-usage-capacity']), - $this::td($free, ['class' => 'vm-disk-usage-free']), + $this::td( + $this->renderFreeSpace($row->free_space, $row->capacity), + ['class' => 'vm-disk-usage-free'] + ), $this::td($this->makeDisk($row), ['class' => 'vm-disk-usage-usage']) ]); @@ -133,11 +133,13 @@ protected function fetchRows() return; } - $free = Format::bytes($this->totalFree) . sprintf(' (%0.3f%%)', ($this->totalFree / $this->totalSize) * 100); $this->getFooter()->add($this::tr([ $this::th(Html::tag('strong', null, $this->translate('Total'))), $this::th(Format::bytes($this->totalSize), ['class' => 'vm-disk-usage-capacity']), - $this::th($free, ['class' => 'vm-disk-usage-free']), + $this::th( + $this->renderFreeSpace($this->totalFree, $this->totalSize), + ['class' => 'vm-disk-usage-free'] + ), $this::th($this->makeDisk((object) [ 'disk_path' => $this->translate('Total'), 'capacity' => $this->totalSize, @@ -158,6 +160,21 @@ protected function makeDisk($disk) return new SimpleUsageBar($used, $disk->capacity, $disk->disk_path); } + /** + * @param int|float $freeSpace + * @param int|float $capacity + * @return string Formatted free space with percentage, or "(n/a)" when capacity is zero + */ + protected function renderFreeSpace(int|float $freeSpace, int|float $capacity): string + { + $free = Format::bytes($freeSpace); + if ($capacity > 0) { + return $free . sprintf(' (%0.3f%%)', ($freeSpace / $capacity) * 100); + } + + return $free . ' (n/a)'; + } + public function prepareQuery() { return $this->db()->select()->from( diff --git a/library/Vspheredb/Web/Widget/SimpleUsageBar.php b/library/Vspheredb/Web/Widget/SimpleUsageBar.php index 8a049e3e..cc1d40fc 100644 --- a/library/Vspheredb/Web/Widget/SimpleUsageBar.php +++ b/library/Vspheredb/Web/Widget/SimpleUsageBar.php @@ -36,7 +36,7 @@ public function __construct($used, $total, $title) protected function assemble() { - $usedPercent = $this->used / $this->total; + $usedPercent = $this->getUsedPercent(); $bar = Html::tag('span', [ 'href' => '#', @@ -50,4 +50,16 @@ protected function assemble() $this->add([$bar, $style]); } + + /** + * @return float Used fraction in [0, 1]; returns 0.0 when total is zero + */ + protected function getUsedPercent(): float + { + if ($this->total > 0) { + return $this->used / $this->total; + } + + return 0.0; + } } From 4796d1ada515d5a48947b3122d7cb8b7c9800097 Mon Sep 17 00:00:00 2001 From: Eric Lippmann Date: Wed, 24 Jun 2026 08:08:53 +0200 Subject: [PATCH 2/2] Fix parent UUID TypeError in rule inheritance Rule refresh can reach objects whose managed object has no parent UUID. The previous code passed that null value into listParentUuidsFor(), whose string parameter raises a TypeError on PHP 8 instead of loading inherited settings. Missing parent UUIDs now use the existing root rule-set sentinel, so root objects can still inherit global monitoring rules without entering parent traversal. --- .../Monitoring/Rule/MonitoringRulesTree.php | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/library/Vspheredb/Monitoring/Rule/MonitoringRulesTree.php b/library/Vspheredb/Monitoring/Rule/MonitoringRulesTree.php index 6dee4bce..501d9e0a 100644 --- a/library/Vspheredb/Monitoring/Rule/MonitoringRulesTree.php +++ b/library/Vspheredb/Monitoring/Rule/MonitoringRulesTree.php @@ -109,9 +109,27 @@ public function hasConfigurationForUuid(string $uuid): bool public function getInheritedSettingsFor(BaseDbObject $object): InheritedSettings { $uuid = $object->object()->get('parent_uuid'); - $parents = [$uuid, ...$this->listParentUuidsFor($uuid)]; - return InheritedSettings::loadForUuids($parents, $this, $this->db); + return InheritedSettings::loadForUuids( + $this->listInheritedParentUuidsFor($uuid), + $this, + $this->db + ); + } + + /** + * @param string|int|null $uuid + * @return string[] + */ + protected function listInheritedParentUuidsFor(string|int|null $uuid): array + { + if ($uuid === null || $uuid === 0) { + return [MonitoringRuleSet::NO_OBJECT]; + } + + $uuid = (string) $uuid; + + return [$uuid, ...$this->listParentUuidsFor($uuid)]; } /**