From 5e84e75fea595716d4d6791243262d8c7525246a Mon Sep 17 00:00:00 2001 From: acidvegas Date: Sun, 12 Apr 2026 19:07:01 -0400 Subject: [PATCH] better labels and handling of usb/sd card drives --- agent/agent.py | 39 +++++++++++++++++++++++++++++++++- dashboard/templates/index.html | 4 ++-- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/agent/agent.py b/agent/agent.py index 259e106..8630943 100644 --- a/agent/agent.py +++ b/agent/agent.py @@ -247,7 +247,7 @@ def collect_smart(device: str): 'user_capacity' : data.get('user_capacity', {}).get('bytes', 0), 'rotation_rate' : data.get('rotation_rate', 0), 'form_factor' : data.get('form_factor', {}).get('name', ''), - 'protocol' : data.get('device', {}).get('protocol', 'Unknown'), + 'protocol' : data.get('device', {}).get('protocol', '') or '', 'sata_version' : data.get('sata_version', {}).get('string', ''), 'smart_attributes' : [], 'sas_error_counters' : None, @@ -276,6 +276,35 @@ def collect_smart(device: str): return info +def collect_udev_info(device: str): + ''' + Query udevadm for device properties as a fallback when smartctl lacks data + (e.g. USB flash drives that aren't in smartctl's drivedb). + + :param device: Block device path (e.g. /dev/sde) + ''' + + out, _, rc = run_cmd(['udevadm', 'info', '--query=property', f'--name={device}'], timeout=5) + if rc != 0: + return {} + props = {} + for line in out.splitlines(): + if '=' in line: + k, v = line.split('=', 1) + props[k] = v + info = {} + vendor = (props.get('ID_VENDOR') or props.get('ID_USB_VENDOR') or '').replace('_', ' ').strip() + model = (props.get('ID_MODEL') or props.get('ID_USB_MODEL') or '').replace('_', ' ').strip() + if vendor and model: + info['model_family'] = f'{vendor} {model}' + elif vendor: + info['model_family'] = vendor + bus = (props.get('ID_BUS') or '').lower() + if bus: + info['protocol'] = bus.upper() + return info + + def collect_disks(): '''Enumerate physical disks via lsblk and collect SMART data in parallel.''' @@ -315,6 +344,14 @@ def collect_disks(): logging.warning('SMART failed for %s: %s', disk['name'], e) for d in devs: + if not d.get('model_family') or not d.get('protocol'): + udev = collect_udev_info(d['path']) + if not d.get('model_family') and udev.get('model_family'): + d['model_family'] = udev['model_family'] + if not d.get('protocol') and udev.get('protocol'): + d['protocol'] = udev['protocol'] + if not d.get('protocol') and d.get('transport'): + d['protocol'] = d['transport'].upper() d['health_score'] = compute_health_score(d) with lock: diff --git a/dashboard/templates/index.html b/dashboard/templates/index.html index e65901a..9c4083c 100644 --- a/dashboard/templates/index.html +++ b/dashboard/templates/index.html @@ -343,13 +343,13 @@ function renderDisks(disks){ if(!disks||!disks.length){el.innerHTML='No disks detected';badge.textContent='0';return} badge.textContent=disks.length; el.innerHTML=disks.map((d,i)=>{ - const proto=(d.protocol||d.transport||'').toUpperCase(); + const proto=((d.transport&&d.transport.toLowerCase()!=='unknown'?d.transport:d.protocol)||'').toUpperCase(); let dtc='dtb';if(proto.includes('SAS')||proto.includes('SCSI'))dtc+=' dtb-sas';else if(proto.includes('NVME'))dtc+=' dtb-nv'; const ht=d.health===true?'PASSED':d.health===false?'FAILED':'N/A'; const hs=d.health_score!=null?d.health_score:'-'; const hasSmart=d.health===true||d.health===false||(d.smart_attributes&&d.smart_attributes.length)||(d.sas_error_counters); const model=d.device_model||d.model||'—',family=d.model_family||'—',serial=d.serial_number||d.serial||'—'; - return`/dev/${d.name}${model.substring(0,30)}${family.substring(0,30)}${serial}${Br(d.user_capacity||d.size)}${d.temperature!=null?cToF(d.temperature)+'°F':'—'}${fmtH(d.power_on_hours)}${d.rotation_rate||'—'}${fmtForm(d.form_factor)}${proto||'?'}${hs}/100`}).join(''); + return`/dev/${d.name}${model}${family}${serial}${Br(d.user_capacity||d.size)}${d.temperature!=null?cToF(d.temperature)+'°F':'—'}${fmtH(d.power_on_hours)}${d.rotation_rate||'—'}${fmtForm(d.form_factor)}${proto||'?'}${hs}/100`}).join(''); if(_expanded.disks.size){disks.forEach((d,i)=>{if(_expanded.disks.has(d.name)){const rows=el.querySelectorAll('.disk-row');if(rows[i])toggleDiskDetail(rows[i],i)}})} }