Add the disk health panel
Its own card rather than folded into Storage: Storage is per volume and this is per physical device, and one drive can back several volumes or none the operator sees, so the two do not share a key. Rows sort failure-first, then unknown, then OK. A failing drive must not sit below the fold on a multi-disk box, and a drive that stopped answering is a weaker claim of health than one that said OK. An unrecognised verdict sorts as unknown rather than last, so a future verdict string cannot bury itself under the healthy drives. Both agent sources are shown rather than merged, matching the mapped-drive card: when two sources disagree about one drive, that disagreement is the thing worth looking at.
This commit is contained in:
@@ -640,6 +640,66 @@ def panel_storage(asset_id: int):
|
||||
return success_response(rows)
|
||||
|
||||
|
||||
def _diskhealth_rows(drives):
|
||||
"""Turn the agent's disk health list into display rows, worst first.
|
||||
|
||||
Both agent sources are kept rather than merged. MSFT_PhysicalDisk is the
|
||||
only source that reports NVMe at all, and Win32_DiskDrive covers hardware
|
||||
and hosts where the Storage namespace is unavailable. When both answer for
|
||||
the same hardware, two rows that disagree is the useful signal; collapsing
|
||||
them would hide exactly the case worth looking at.
|
||||
"""
|
||||
if not isinstance(drives, list):
|
||||
return []
|
||||
|
||||
rows = []
|
||||
for drive in drives:
|
||||
if not isinstance(drive, dict):
|
||||
continue
|
||||
predictfailure = drive.get('predictfailure')
|
||||
if predictfailure is None:
|
||||
verdict = 'Unknown'
|
||||
elif predictfailure:
|
||||
verdict = 'Failure predicted'
|
||||
else:
|
||||
verdict = 'OK'
|
||||
rows.append({
|
||||
'deviceid': drive.get('deviceid'),
|
||||
'model': drive.get('model') or '',
|
||||
'mediatype': drive.get('mediatype') or '',
|
||||
'bustype': drive.get('bustype') or '',
|
||||
'healthstatus': drive.get('healthstatus') or 'Unknown',
|
||||
'verdict': verdict,
|
||||
'source': drive.get('source') or '',
|
||||
})
|
||||
|
||||
# Failure first, then unknown, then OK. Unknown outranks OK because a drive
|
||||
# that stopped answering is a weaker claim of health than one that said OK.
|
||||
# An unrecognised verdict sorts as unknown rather than last, so a future
|
||||
# verdict string cannot bury itself under the healthy drives.
|
||||
order = {'Failure predicted': 0, 'Unknown': 1, 'OK': 2}
|
||||
rows.sort(key=lambda row: (order.get(row['verdict'], 1),
|
||||
row['deviceid'] or ''))
|
||||
return rows
|
||||
|
||||
|
||||
@endpoints_bp.route('/asset/<int:asset_id>/diskhealth', methods=['GET'])
|
||||
@require_permission('endpoints.view')
|
||||
def panel_diskhealth(asset_id: int):
|
||||
"""Table panel: physical drive health.
|
||||
|
||||
Separate from the storage panel because the two do not share a key. Storage
|
||||
is per volume (C:, D:), this is per physical device, and one physical drive
|
||||
can back several volumes or none the operator can see.
|
||||
|
||||
Rows are ordered so anything predicting failure sorts first: a failing
|
||||
drive must not be below the fold on a machine with several disks.
|
||||
"""
|
||||
state = EndpointState.query.filter_by(assetid=asset_id).first()
|
||||
drives = _state_json(state, 'diskhealthjson') if state else None
|
||||
return success_response(_diskhealth_rows(drives))
|
||||
|
||||
|
||||
@endpoints_bp.route('/asset/<int:asset_id>/listeners', methods=['GET'])
|
||||
@require_permission('endpoints.view')
|
||||
def panel_listeners(asset_id: int):
|
||||
|
||||
Reference in New Issue
Block a user