Align storage life time design with used storage (#26724)

This commit is contained in:
Paul Bottein
2025-08-27 09:52:56 +02:00
committed by GitHub
parent ddb224e145
commit 0b11302b1d
3 changed files with 119 additions and 51 deletions

View File

@@ -1,5 +1,5 @@
import type { TemplateResult } from "lit"; import type { TemplateResult } from "lit";
import { css, html, LitElement } from "lit"; import { css, html, LitElement, nothing } from "lit";
import { customElement, property } from "lit/decorators"; import { customElement, property } from "lit/decorators";
import { styleMap } from "lit/directives/style-map"; import { styleMap } from "lit/directives/style-map";
import "./ha-tooltip"; import "./ha-tooltip";
@@ -7,7 +7,7 @@ import "./ha-tooltip";
export interface Segment { export interface Segment {
value: number; value: number;
color: string; color: string;
label: TemplateResult | string; label?: TemplateResult | string;
} }
@customElement("ha-segmented-bar") @customElement("ha-segmented-bar")
@@ -18,6 +18,12 @@ class HaSegmentedBar extends LitElement {
@property({ type: String }) public description?: string; @property({ type: String }) public description?: string;
@property({ type: Boolean, attribute: "hide-legend" })
public hideLegend = false;
@property({ type: Boolean, attribute: "hide-tooltip" })
public hideTooltip = false;
protected render(): TemplateResult { protected render(): TemplateResult {
const totalValue = this.segments.reduce( const totalValue = this.segments.reduce(
(acc, segment) => acc + segment.value, (acc, segment) => acc + segment.value,
@@ -26,39 +32,51 @@ class HaSegmentedBar extends LitElement {
return html` return html`
<div class="container"> <div class="container">
<div class="heading"> <div class="heading">
<span>${this.heading}</span> <div class="title">
<span>${this.description}</span> <span>${this.heading}</span>
<span>${this.description}</span>
</div>
<slot name="extra"></slot>
</div> </div>
<div class="bar"> <div class="bar">
${this.segments.map( ${this.segments.map((segment) => {
(segment) => html` const bar = html`<div
<ha-tooltip> style=${styleMap({
<span slot="content">${segment.label}</span> width: `${(segment.value / totalValue) * 100}%`,
<div backgroundColor: segment.color,
style=${styleMap({ })}
width: `${(segment.value / totalValue) * 100}%`, ></div>`;
backgroundColor: segment.color, return this.hideTooltip && !segment.label
})} ? bar
></div> : html`
</ha-tooltip> <ha-tooltip>
` <span slot="content">${segment.label}</span>
)} ${bar}
</ha-tooltip>
`;
})}
</div> </div>
<ul class="legend"> ${this.hideLegend
${this.segments.map( ? nothing
(segment) => html` : html`
<li> <ul class="legend">
<div ${this.segments.map((segment) =>
class="bullet" segment.label
style=${styleMap({ ? html`
backgroundColor: segment.color, <li>
})} <div
></div> class="bullet"
<span class="label">${segment.label}</span> style=${styleMap({
</li> backgroundColor: segment.color,
` })}
)} ></div>
</ul> <span class="label">${segment.label}</span>
</li>
`
: nothing
)}
</ul>
`}
</div> </div>
`; `;
} }
@@ -67,12 +85,20 @@ class HaSegmentedBar extends LitElement {
.container { .container {
width: 100%; width: 100%;
} }
.heading span { .heading {
display: flex;
flex-direction: row;
gap: 8px;
}
.heading .title {
flex: 1;
}
.heading .title span {
color: var(--secondary-text-color); color: var(--secondary-text-color);
line-height: var(--ha-line-height-expanded); line-height: var(--ha-line-height-expanded);
margin-right: 8px; margin-right: 8px;
} }
.heading span:first-child { .heading .title span:first-child {
color: var(--primary-text-color); color: var(--primary-text-color);
} }
.bar { .bar {
@@ -113,6 +139,9 @@ class HaSegmentedBar extends LitElement {
height: 12px; height: 12px;
border-radius: 50%; border-radius: 50%;
} }
.spacer {
flex: 1;
}
`; `;
} }

View File

@@ -1,6 +1,7 @@
import { import {
mdiBackupRestore, mdiBackupRestore,
mdiFolder, mdiFolder,
mdiInformation,
mdiNas, mdiNas,
mdiPlayBox, mdiPlayBox,
mdiReload, mdiReload,
@@ -18,7 +19,6 @@ import "../../../components/ha-icon-button";
import "../../../components/ha-icon-next"; import "../../../components/ha-icon-next";
import "../../../components/ha-list"; import "../../../components/ha-list";
import "../../../components/ha-list-item"; import "../../../components/ha-list-item";
import "../../../components/ha-metric";
import "../../../components/ha-segmented-bar"; import "../../../components/ha-segmented-bar";
import "../../../components/ha-svg-icon"; import "../../../components/ha-svg-icon";
import { extractApiErrorMessage } from "../../../data/hassio/common"; import { extractApiErrorMessage } from "../../../data/hassio/common";
@@ -47,6 +47,7 @@ import { showMoveDatadiskDialog } from "./show-dialog-move-datadisk";
import { showMountViewDialog } from "./show-dialog-view-mount"; import { showMountViewDialog } from "./show-dialog-view-mount";
import type { Segment } from "../../../components/ha-segmented-bar"; import type { Segment } from "../../../components/ha-segmented-bar";
import { getGraphColorByIndex } from "../../../common/color/colors"; import { getGraphColorByIndex } from "../../../common/color/colors";
import { blankBeforePercent } from "../../../common/translations/blank_before_percent";
@customElement("ha-config-section-storage") @customElement("ha-config-section-storage")
class HaConfigSectionStorage extends LitElement { class HaConfigSectionStorage extends LitElement {
@@ -107,21 +108,7 @@ class HaConfigSectionStorage extends LitElement {
this._hostInfo, this._hostInfo,
this._storageInfo this._storageInfo
)} )}
${this._hostInfo.disk_life_time !== null ${this._renderDiskLifeTime(this._hostInfo.disk_life_time)}
? // prettier-ignore
html`
<ha-metric
.heading=${this.hass.localize(
"ui.panel.config.storage.lifetime_used"
)}
.value=${this._hostInfo.disk_life_time}
.tooltip=${this.hass.localize(
"ui.panel.config.storage.lifetime_used_description"
)}
class="emmc"
></ha-metric>
`
: ""}
</div> </div>
${this._hostInfo ${this._hostInfo
? html`<div class="card-actions"> ? html`<div class="card-actions">
@@ -237,6 +224,51 @@ class HaConfigSectionStorage extends LitElement {
`; `;
} }
private _renderDiskLifeTime(diskLifeTime: number | null) {
if (diskLifeTime === null) {
return nothing;
}
const segments: Segment[] = [
{
color: "var(--primary-color)",
value: diskLifeTime,
},
{
color:
"var(--ha-bar-background-color, var(--secondary-background-color))",
value: 100 - diskLifeTime,
},
];
return html`
<ha-segmented-bar
.heading=${this.hass.localize("ui.panel.config.storage.lifetime")}
.description=${this.hass.localize(
"ui.panel.config.storage.lifetime_description",
{
lifetime: `${diskLifeTime}${blankBeforePercent(this.hass.locale)}%`,
}
)}
.segments=${segments}
hide-legend
hide-tooltip
>
<ha-tooltip slot="extra">
<ha-icon-button
.path=${mdiInformation}
class="help-button"
></ha-icon-button>
<p class="metric-description" slot="content">
${this.hass.localize(
"ui.panel.config.storage.lifetime_used_description"
)}
</p>
</ha-tooltip>
</ha-segmented-bar>
`;
}
private _renderStorageMetrics = memoizeOne( private _renderStorageMetrics = memoizeOne(
(hostInfo?: HassioHostInfo, storageInfo?: HostDisksUsage | null) => { (hostInfo?: HassioHostInfo, storageInfo?: HostDisksUsage | null) => {
if (!hostInfo) { if (!hostInfo) {
@@ -462,6 +494,12 @@ class HaConfigSectionStorage extends LitElement {
inset-inline-start: initial; inset-inline-start: initial;
} }
.help-button {
--mdc-icon-button-size: 20px;
--mdc-icon-size: 20px;
color: var(--secondary-text-color);
}
.no-mounts { .no-mounts {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@@ -6678,7 +6678,8 @@
"homeassistant": "Home Assistant", "homeassistant": "Home Assistant",
"ssl": "SSL" "ssl": "SSL"
}, },
"lifetime_used": "Lifetime used", "lifetime": "Lifetime",
"lifetime_description": "{lifetime} used",
"lifetime_used_description": "The drives wear level is shown as a percentage, based on endurance indicators reported by the device via NVMe SMART or eMMC lifetime estimate fields.", "lifetime_used_description": "The drives wear level is shown as a percentage, based on endurance indicators reported by the device via NVMe SMART or eMMC lifetime estimate fields.",
"disk_metrics": "Disk metrics", "disk_metrics": "Disk metrics",
"datadisk": { "datadisk": {