merge storage and disk health into one card with two sections

Volume capacity and physical drive health were two adjacent cards, and the operator reading a full disk is usually the operator reading a dying disk in the same minute. They are now one Storage card with two sections. Visual grouping only - the two tables share no key, storage being per volume and health per physical device, and one drive can back several volumes or none the operator sees. Nothing is reconciled across the sections; this is not the mapped-drive merge where two sources described the same drive and the worse status won. The tabs render type was the only sections-in-one-card mechanism and emitted label/value fields with no table path, so it now takes table sections too. That is the renderer every plugin uses, so both behaviour changes in it are mutation-tested. Two things only surfaced on contact: the empty-section filter had to grow a table branch, because a table section declares columns up front and dropping it when it has no rows takes its tab with it and reads as no disks rather than nothing reported; and full-width span was keyed off the declared render type, but a tabs panel holding a six-column table needs the same span and that is only knowable from fetched data. The diskhealth route stays. It no longer backs a panel, but it is a stable read for callers outside the renderer and removing it for a UI-only change would break them.
This commit is contained in:
benjamin.baker
2026-09-05 07:05:30 -04:00
parent af42bf5abd
commit e00bb44d6b
13 changed files with 321 additions and 77 deletions

View File

@@ -61,5 +61,10 @@ def test_system_monitors_keeps_panels_of_its_own():
def test_disk_health_stayed_with_the_system_health_panels():
"""Disk health is the box's own health, not something running on it."""
assert _panels()['endpoints-diskhealth']['group'] == 'monitors'
"""Disk health is the box's own health, not something running on it.
It is now a section of the Storage card rather than a panel of its own,
so the group assertion follows it there.
"""
assert 'endpoints-diskhealth' not in _panels()
assert _panels()['endpoints-storage']['group'] == 'monitors'

View File

@@ -69,12 +69,12 @@ def test_endpoints_asset_panels_are_computer_scoped():
"""Every asset panel targets computer assets only, with an endpoint."""
plugin = EndpointsPlugin()
panels = plugin.get_asset_panels()
assert len(panels) == 13
assert len(panels) == 12
for panel in panels:
assert panel['assettypes'] == ['computer']
assert panel['endpoint'].startswith('/api/endpoints/')
assert '{assetid}' in panel['endpoint']
assert panel['render'] in ('keyvalue', 'list', 'table', 'operatorreports')
assert panel['render'] in ('keyvalue', 'list', 'table', 'tabs', 'operatorreports')
def test_services_and_processes_are_split_into_two_filtered_cards():

View File

@@ -0,0 +1,51 @@
"""The Storage panel serves volumes and disk health as two sections.
Visual grouping only. The two tables share no key - storage is per volume and
health is per physical device - so nothing is reconciled across them. These
tests pin that the sections stay independent and that neither disappears when
empty, because a dropped section takes its tab with it and reads as "no disks"
rather than "nothing was reported".
"""
import pytest
from plugins.endpoints.api.routes import _storage_rows, _STORAGE_COLUMNS, _DISKHEALTH_COLUMNS
def test_freepercent_is_derived_when_an_older_agent_omits_it():
rows = _storage_rows([{'drive': 'C:', 'sizebytes': 1000, 'freebytes': 250}])
assert rows[0]['freepercent'] == '25.0%'
def test_an_agent_supplied_freepercent_is_preferred_over_recomputing():
rows = _storage_rows([
{'drive': 'C:', 'sizebytes': 1000, 'freebytes': 250, 'freepercent': 24.9},
])
assert rows[0]['freepercent'] == '24.9%'
def test_a_zero_size_volume_does_not_divide_by_zero():
rows = _storage_rows([{'drive': 'C:', 'sizebytes': 0, 'freebytes': 0}])
assert rows[0]['freepercent'] is None
def test_free_percent_is_none_rather_than_zero_when_free_is_unknown():
"""Zero free space and unknown free space must not look identical."""
rows = _storage_rows([{'drive': 'C:', 'sizebytes': 1000}])
assert rows[0]['freepercent'] is None
def test_non_dict_entries_are_skipped():
rows = _storage_rows([{'drive': 'C:', 'sizebytes': 1000, 'freebytes': 500}, 'junk', None])
assert len(rows) == 1
@pytest.mark.parametrize('value', [None, {}, 'x'])
def test_a_missing_snapshot_yields_no_rows(value):
assert _storage_rows(value) == []
def test_the_two_section_column_sets_share_no_key():
"""The point of two sections rather than one merged table."""
storage = {column['key'] for column in _STORAGE_COLUMNS}
health = {column['key'] for column in _DISKHEALTH_COLUMNS}
assert not storage & health