Report disk sizes in the right unit and drop parity from the card

Two fixes to the Storage card.

Unraid's GraphQL reports fsSize/fsFree in KB, not bytes — the schema is
explicit about it ("(KB) Total Size of the FS"). We were passing those
straight to fmt_bytes as if they were bytes, so every figure came out
1024x too small and picked the unit below the right one: a 13 TB disk
rendered as "13.0 GB" and a 64.8 TB array as "64.8 GB". _fs_bytes() now
does the conversion at the point of reading, so everything downstream
still deals in bytes. The shutil fallback already returns real bytes and
deliberately doesn't go through it.

Parity is gone from the table and is no longer requested at all. It
mirrors the array rather than holding a filesystem — the schema notes
fsSize is "Not present on Parity type drive" — so it could only ever
render as a name, three em-dashes and a temperature, which earned little
for a whole row. Worth knowing: its temperature is no longer surfaced
anywhere, so a hot parity drive won't show up here.

Verified against the raw KB shape the API actually returns: an array of
3.7/11.2/13.0 TB disks now totals 27.9 TB rather than 27.9 GB, the sub-TB
cache still correctly reads in GB, and the local fallback still reports
this machine's real 228.3 GB rather than a 1024x-inflated figure.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Barely Removable 2026-08-27 13:15:42 -07:00
parent 6f4958ba89
commit 5ef2502d4e
3 changed files with 24 additions and 31 deletions

39
app.py
View file

@ -876,17 +876,27 @@ def get_unraid_stats():
if b >= 1024**2: return f"{b/1024**2:.1f} MB"
return f"{b/1024:.0f} KB"
def _fs_bytes(v):
"""Unraid's GraphQL reports fsSize/fsFree/fsUsed/size in KB, not bytes
— the schema says so explicitly ('(KB) Total Size of the FS'). Reading
them as bytes understated every figure by 1024x, so a 13 TB disk
rendered as '13.0 GB'. The shutil fallback below already returns real
bytes and must not go through this."""
return int(v or 0) * 1024
if UNRAID_HOST and UNRAID_API_KEY:
# HTTPS on 443 with SSL verification disabled (Unraid uses a self-signed cert)
url = f"https://{UNRAID_HOST}:443/graphql"
headers = {"x-api-key": UNRAID_API_KEY, "Content-Type": "application/json"}
# Parity is deliberately not requested: it mirrors the array rather
# than holding a filesystem, so it has no size or usage to report and
# only ever rendered as a name and a temperature.
query = """{
vars { version }
array {
state
disks { name device fsSize fsFree type temp isSpinning }
caches { name device fsSize fsFree type temp isSpinning }
parities { name device temp isSpinning }
}
}"""
try:
@ -898,29 +908,25 @@ def get_unraid_stats():
arr = d.get("array", {})
disks = arr.get("disks", [])
data_disks = [dk for dk in disks if dk.get("type") == "DATA"]
total_bytes = sum(int(dk.get("fsSize", 0) or 0) for dk in data_disks)
free_bytes = sum(int(dk.get("fsFree", 0) or 0) for dk in data_disks)
total_bytes = sum(_fs_bytes(dk.get("fsSize")) for dk in data_disks)
free_bytes = sum(_fs_bytes(dk.get("fsFree")) for dk in data_disks)
used_bytes = total_bytes - free_bytes
used_pct = round(used_bytes / total_bytes * 100) if total_bytes else 0
def disk_row(dk, with_usage=True):
row = {
def disk_row(dk):
dtotal = _fs_bytes(dk.get("fsSize"))
dfree = _fs_bytes(dk.get("fsFree"))
dused = dtotal - dfree
return {
"name": dk.get("name") or dk.get("device") or "disk",
"device": dk.get("device", ""),
"temp": dk.get("temp"),
"spinning": dk.get("isSpinning"),
"total": fmt_bytes(dtotal),
"used": fmt_bytes(dused),
"free": fmt_bytes(dfree),
"used_pct": round(dused / dtotal * 100) if dtotal else 0,
}
if with_usage:
dtotal = int(dk.get("fsSize", 0) or 0)
dfree = int(dk.get("fsFree", 0) or 0)
dused = dtotal - dfree
row.update({
"total": fmt_bytes(dtotal),
"used": fmt_bytes(dused),
"free": fmt_bytes(dfree),
"used_pct": round(dused / dtotal * 100) if dtotal else 0,
})
return row
return {
"status": "ok",
@ -934,7 +940,6 @@ def get_unraid_stats():
"array_used_pct": used_pct,
"disks": [disk_row(dk) for dk in data_disks],
"caches": [disk_row(dk) for dk in arr.get("caches", [])],
"parities": [disk_row(dk, with_usage=False) for dk in arr.get("parities", [])],
}
except Exception:
pass