feat(setup): drop the filesystem type, show capacity as free-of-total + a bar
The card said "911.9G · 808.4G free · ext4". The filesystem type is a
Details row, not something you choose a drive on, so it goes.
On percentage vs size: which one matters depends on the question. This
step asks "will my data fit?", and absolute free space is what decides
that — a 4 GB disk that is 89% free is still useless for a media library.
Percentage answers "is this filling up?", a health signal rather than a
placement one. So the text carries the magnitude ("808.4G free of 911.9G")
and a thin bar carries the proportion, which is what the eye reads
fastest, with no second number competing with the first.
The bar fills with FREE space, not used. Filling by usage made a healthy
7%-full disk render as an almost-empty track that read as a broken widget
— and it pointed the opposite way to the text beside it. Filled = room to
spare, draining = filling up, matching the words. It turns amber below
25% free and red below 10%.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
3b602ddb53
commit
e101764085
@ -1322,3 +1322,25 @@ body.setup-wizard-open .eo-modal { z-index: 10000; }
|
||||
color: #86ecb6;
|
||||
border: 1px solid rgba(90, 220, 150, 0.45);
|
||||
}
|
||||
|
||||
/* Capacity meter on a drive card. Inline and small on purpose: it encodes the
|
||||
proportion the text deliberately doesn't repeat, without adding a row.
|
||||
Filled = free space, so the bar drains as the disk fills. */
|
||||
.setup-storage-meter {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 64px;
|
||||
height: 5px;
|
||||
margin-left: 10px;
|
||||
border-radius: 3px;
|
||||
background: rgba(255, 255, 255, 0.20);
|
||||
overflow: hidden;
|
||||
}
|
||||
.setup-storage-meter > span {
|
||||
display: block;
|
||||
height: 100%;
|
||||
border-radius: 3px;
|
||||
background: rgba(160, 226, 255, 0.85);
|
||||
}
|
||||
.setup-storage-meter-mid > span { background: rgba(255, 206, 110, 0.9); }
|
||||
.setup-storage-meter-high > span { background: rgba(255, 138, 138, 0.95); }
|
||||
|
||||
@ -448,6 +448,29 @@ class SetupWizard {
|
||||
return map[c.id] || c.message;
|
||||
}
|
||||
|
||||
// A thin capacity bar rather than a second number.
|
||||
//
|
||||
// Which figure matters depends on the question. For "will my data fit?" —
|
||||
// the question this step actually asks — absolute free space decides it: a
|
||||
// 4 GB disk that is 89% free is still useless for a media library.
|
||||
// Percentage answers "is this filling up?", a health signal rather than a
|
||||
// placement one. So the text carries the magnitude and the bar the
|
||||
// proportion, which is what the eye reads fastest.
|
||||
//
|
||||
// The bar fills with FREE space, not used. Filling by usage meant a healthy
|
||||
// 7%-full disk rendered as an almost-empty track that read as a broken
|
||||
// widget, and it disagreed with the text right beside it. Filled = room to
|
||||
// spare, draining = filling up, which is the same direction as the words.
|
||||
_storageMeter(c) {
|
||||
const used = Math.max(0, Math.min(100, Number(c.used_pct) || 0));
|
||||
const free = 100 - used;
|
||||
const level = free <= 10 ? ' setup-storage-meter-high'
|
||||
: (free <= 25 ? ' setup-storage-meter-mid' : '');
|
||||
return `<span class="setup-storage-meter${level}" role="img"
|
||||
aria-label="${free}% of this drive free"
|
||||
title="${used}% used, ${free}% free"><span style="width:${free}%"></span></span>`;
|
||||
}
|
||||
|
||||
// One card, one line.
|
||||
//
|
||||
// The badge carries severity and Details carries the explanation, so the card
|
||||
@ -472,7 +495,10 @@ class SetupWizard {
|
||||
<input type="checkbox" ${o.locked ? 'checked disabled data-storage-system="1"' : `data-storage-path="${this.escapeHtml(c.path)}"`} ${refused ? 'disabled' : ''}>
|
||||
<span class="setup-app-body">
|
||||
<span class="setup-app-name">${this.escapeHtml(title)} ${badge}</span>
|
||||
<span class="setup-app-desc">${this.escapeHtml(c.size)} · ${this.escapeHtml(c.free)} free · ${this.escapeHtml(c.fstype)}${c.removable ? ' · removable' : ''}</span>
|
||||
<span class="setup-app-desc">
|
||||
${this.escapeHtml(c.free)} free of ${this.escapeHtml(c.size)}${c.removable ? ' · removable' : ''}
|
||||
${this._storageMeter(c)}
|
||||
</span>
|
||||
</span>
|
||||
<button type="button" class="setup-storage-details" data-storage-details="${this.escapeHtml(key)}">Details</button>
|
||||
</label>`;
|
||||
|
||||
@ -70,6 +70,8 @@ webuiGenerateStorageCandidates()
|
||||
system_json+=",\"free\":\"$(_lpJsonEsc "$sys_free")\""
|
||||
system_json+=",\"uuid\":\"$(_lpJsonEsc "$sys_uuid")\""
|
||||
system_json+=",\"options\":\"$(_lpJsonEsc "$sys_opts")\""
|
||||
local sys_pct; sys_pct=$(df -Pk "$sys_root" 2>/dev/null | awk 'NR==2 {gsub("%","",$5); print $5}')
|
||||
system_json+=",\"used_pct\":${sys_pct:-0}"
|
||||
system_json+=",\"removable\":false"
|
||||
system_json+=",\"apps\":\"$(_lpJsonEsc "$(storageAppsOnRoot "$sys_root" 2>/dev/null | paste -sd, -)")\""
|
||||
system_json+=",\"checks\":$sys_checks}"
|
||||
@ -112,6 +114,8 @@ webuiGenerateStorageCandidates()
|
||||
candidates+=",\"free\":\"$(_lpJsonEsc "$avail")\""
|
||||
candidates+=",\"uuid\":\"$(_lpJsonEsc "$uuid_val")\""
|
||||
candidates+=",\"options\":\"$(_lpJsonEsc "$opts_val")\""
|
||||
local used_pct; used_pct=$(df -Pk "$target" 2>/dev/null | awk 'NR==2 {gsub("%","",$5); print $5}')
|
||||
candidates+=",\"used_pct\":${used_pct:-0}"
|
||||
candidates+=",\"removable\":$([[ "$rm_flag" == "1" ]] && echo true || echo false)"
|
||||
candidates+=",\"verdict\":\"$verdict\""
|
||||
candidates+=",\"fstab_line\":\"$(_lpJsonEsc "$fstab_line")\""
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user