mirror of
https://github.com/home-assistant/frontend.git
synced 2025-12-03 14:47:22 +00:00
Compare commits
5 Commits
shortcut-n
...
storage-ch
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2864fbdcf1 | ||
|
|
fae2ea21ac | ||
|
|
b21a6a0ce3 | ||
|
|
d69bc121cb | ||
|
|
f3127ba1f6 |
178
src/components/chart/ha-sunburst-chart.ts
Normal file
178
src/components/chart/ha-sunburst-chart.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
import type { EChartsType } from "echarts/core";
|
||||
import type { SunburstSeriesOption } from "echarts/types/dist/echarts";
|
||||
import type { CallbackDataParams } from "echarts/types/src/util/types";
|
||||
import { css, html, LitElement, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { getGraphColorByIndex } from "../../common/color/colors";
|
||||
import { filterXSS } from "../../common/util/xss";
|
||||
import type { ECOption } from "../../resources/echarts/echarts";
|
||||
import type { HomeAssistant } from "../../types";
|
||||
import "./ha-chart-base";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/consistent-type-imports
|
||||
let SunburstChart: typeof import("echarts/lib/chart/sunburst/install");
|
||||
|
||||
export interface SunburstNode {
|
||||
id: string;
|
||||
name?: string;
|
||||
value: number;
|
||||
itemStyle?: {
|
||||
color?: string;
|
||||
};
|
||||
children?: SunburstNode[];
|
||||
}
|
||||
|
||||
@customElement("ha-sunburst-chart")
|
||||
export class HaSunburstChart extends LitElement {
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false }) public data?: SunburstNode;
|
||||
|
||||
@property({ type: String, attribute: false }) public valueFormatter?: (
|
||||
value: number
|
||||
) => string;
|
||||
|
||||
public chart?: EChartsType;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
if (!SunburstChart) {
|
||||
import("echarts/lib/chart/sunburst/install").then((module) => {
|
||||
SunburstChart = module;
|
||||
this.requestUpdate();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
if (!SunburstChart || !this.data) {
|
||||
return nothing;
|
||||
}
|
||||
|
||||
const options = {
|
||||
tooltip: {
|
||||
trigger: "item",
|
||||
formatter: this._renderTooltip,
|
||||
appendTo: document.body,
|
||||
},
|
||||
} as ECOption;
|
||||
|
||||
return html`<ha-chart-base
|
||||
.data=${this._createData(this.data)}
|
||||
.options=${options}
|
||||
height="100%"
|
||||
.extraComponents=${[SunburstChart]}
|
||||
></ha-chart-base>`;
|
||||
}
|
||||
|
||||
private _renderTooltip = (params: CallbackDataParams) => {
|
||||
const data = params.data as Record<string, any>;
|
||||
const value = this.valueFormatter
|
||||
? this.valueFormatter(data.value)
|
||||
: data.value;
|
||||
return `${params.marker} ${filterXSS(data.name)}<br>${value}`;
|
||||
};
|
||||
|
||||
private _createData = memoizeOne(
|
||||
(data: SunburstNode): SunburstSeriesOption => {
|
||||
const computedStyles = getComputedStyle(this);
|
||||
|
||||
// Transform to echarts format (uses 'name' instead of 'id')
|
||||
const transformNode = (
|
||||
node: SunburstNode,
|
||||
index: number,
|
||||
depth: number,
|
||||
parentColor?: string
|
||||
) => {
|
||||
const result = {
|
||||
...node,
|
||||
name: node.name || node.id,
|
||||
};
|
||||
|
||||
if (depth > 0 && !node.itemStyle?.color) {
|
||||
// Don't assign color to root node
|
||||
result.itemStyle = {
|
||||
color: parentColor ?? getGraphColorByIndex(index, computedStyles),
|
||||
};
|
||||
}
|
||||
|
||||
if (node.children && node.children.length > 0) {
|
||||
result.children = node.children.map((child, i) =>
|
||||
transformNode(child, i, depth + 1, result.itemStyle?.color)
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const transformedData = transformNode(data, 0, 0);
|
||||
|
||||
return {
|
||||
type: "sunburst",
|
||||
data: transformedData.children || [transformedData],
|
||||
radius: [0, "90%"],
|
||||
sort: undefined, // Keep original order
|
||||
label: {
|
||||
show: false,
|
||||
align: "center",
|
||||
rotate: "radial",
|
||||
minAngle: 15,
|
||||
hideOverlap: true,
|
||||
},
|
||||
emphasis: {
|
||||
focus: "ancestor",
|
||||
label: {
|
||||
show: false,
|
||||
},
|
||||
},
|
||||
itemStyle: {
|
||||
borderRadius: 2,
|
||||
},
|
||||
levels: [
|
||||
{
|
||||
// Root level (center)
|
||||
r0: "0%",
|
||||
r: "15%",
|
||||
itemStyle: {
|
||||
color: "transparent",
|
||||
},
|
||||
},
|
||||
{
|
||||
// First level
|
||||
r0: "15%",
|
||||
r: "55%",
|
||||
label: { show: true },
|
||||
},
|
||||
{
|
||||
// Second level
|
||||
r0: "55%",
|
||||
r: "80%",
|
||||
},
|
||||
{
|
||||
// Third level
|
||||
r0: "80%",
|
||||
r: "95%",
|
||||
},
|
||||
],
|
||||
} as SunburstSeriesOption;
|
||||
}
|
||||
);
|
||||
|
||||
static styles = css`
|
||||
:host {
|
||||
display: block;
|
||||
flex: 1;
|
||||
}
|
||||
ha-chart-base {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"ha-sunburst-chart": HaSunburstChart;
|
||||
}
|
||||
}
|
||||
@@ -196,6 +196,7 @@ export const fetchHostDisksUsage = async (hass: HomeAssistant) => {
|
||||
endpoint: "/host/disks/default/usage",
|
||||
method: "get",
|
||||
timeout: 3600, // seconds. This can take a while
|
||||
data: { max_depth: 3 },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,6 @@ import {
|
||||
import type { PropertyValues, TemplateResult } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { getGraphColorByIndex } from "../../../common/color/colors";
|
||||
import { isComponentLoaded } from "../../../common/config/is_component_loaded";
|
||||
import { navigate } from "../../../common/navigate";
|
||||
import { blankBeforePercent } from "../../../common/translations/blank_before_percent";
|
||||
@@ -44,10 +42,10 @@ import {
|
||||
import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box";
|
||||
import "../../../layouts/hass-subpage";
|
||||
import type { HomeAssistant, Route } from "../../../types";
|
||||
import { roundWithOneDecimal } from "../../../util/calculate";
|
||||
import "../core/ha-config-analytics";
|
||||
import { showMoveDatadiskDialog } from "./show-dialog-move-datadisk";
|
||||
import { showMountViewDialog } from "./show-dialog-view-mount";
|
||||
import "./storage-breakdown-chart";
|
||||
|
||||
@customElement("ha-config-section-storage")
|
||||
class HaConfigSectionStorage extends LitElement {
|
||||
@@ -104,10 +102,11 @@ class HaConfigSectionStorage extends LitElement {
|
||||
)}
|
||||
>
|
||||
<div class="card-content">
|
||||
${this._renderStorageMetrics(
|
||||
this._hostInfo,
|
||||
this._storageInfo
|
||||
)}
|
||||
<storage-breakdown-chart
|
||||
.hass=${this.hass}
|
||||
.hostInfo=${this._hostInfo}
|
||||
.storageInfo=${this._storageInfo}
|
||||
></storage-breakdown-chart>
|
||||
${this._renderDiskLifeTime(this._hostInfo.disk_life_time)}
|
||||
</div>
|
||||
${this._hostInfo
|
||||
@@ -269,95 +268,6 @@ class HaConfigSectionStorage extends LitElement {
|
||||
`;
|
||||
}
|
||||
|
||||
private _renderStorageMetrics = memoizeOne(
|
||||
(hostInfo?: HassioHostInfo, storageInfo?: HostDisksUsage | null) => {
|
||||
if (!hostInfo) {
|
||||
return nothing;
|
||||
}
|
||||
const computedStyles = getComputedStyle(this);
|
||||
let totalSpaceGB = hostInfo.disk_total;
|
||||
let usedSpaceGB = hostInfo.disk_used;
|
||||
// hostInfo.disk_free is sometimes 0, so we may need to calculate it
|
||||
let freeSpaceGB =
|
||||
hostInfo.disk_free || hostInfo.disk_total - hostInfo.disk_used;
|
||||
const segments: Segment[] = [];
|
||||
if (storageInfo) {
|
||||
const totalSpace =
|
||||
storageInfo.total_bytes ?? this._gbToBytes(hostInfo.disk_total);
|
||||
totalSpaceGB = this._bytesToGB(totalSpace);
|
||||
usedSpaceGB = this._bytesToGB(storageInfo.used_bytes);
|
||||
freeSpaceGB = this._bytesToGB(totalSpace - storageInfo.used_bytes);
|
||||
storageInfo.children?.forEach((child, index) => {
|
||||
if (child.used_bytes > 0) {
|
||||
const space = this._bytesToGB(child.used_bytes);
|
||||
segments.push({
|
||||
value: space,
|
||||
color: getGraphColorByIndex(index, computedStyles),
|
||||
label: html`${this.hass.localize(
|
||||
`ui.panel.config.storage.segments.${child.id}`
|
||||
) ||
|
||||
child.label ||
|
||||
child.id}
|
||||
<span style="color: var(--secondary-text-color)"
|
||||
>${roundWithOneDecimal(space)} GB</span
|
||||
>`,
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
segments.push({
|
||||
value: usedSpaceGB,
|
||||
color: "var(--primary-color)",
|
||||
label: html`${this.hass.localize(
|
||||
"ui.panel.config.storage.segments.used"
|
||||
)}
|
||||
<span style="color: var(--secondary-text-color)"
|
||||
>${roundWithOneDecimal(usedSpaceGB)} GB</span
|
||||
>`,
|
||||
});
|
||||
}
|
||||
segments.push({
|
||||
value: freeSpaceGB,
|
||||
color:
|
||||
"var(--ha-bar-background-color, var(--secondary-background-color))",
|
||||
label: html`${this.hass.localize(
|
||||
"ui.panel.config.storage.segments.free"
|
||||
)}
|
||||
<span style="color: var(--secondary-text-color)"
|
||||
>${roundWithOneDecimal(freeSpaceGB)} GB</span
|
||||
>`,
|
||||
});
|
||||
return html`<ha-segmented-bar
|
||||
.heading=${this.hass.localize("ui.panel.config.storage.used_space")}
|
||||
.description=${this.hass.localize(
|
||||
"ui.panel.config.storage.detailed_description",
|
||||
{
|
||||
used: `${roundWithOneDecimal(usedSpaceGB)} GB`,
|
||||
total: `${roundWithOneDecimal(totalSpaceGB)} GB`,
|
||||
}
|
||||
)}
|
||||
.segments=${segments}
|
||||
></ha-segmented-bar>
|
||||
|
||||
${!storageInfo || storageInfo === null
|
||||
? html`<ha-alert alert-type="info">
|
||||
<ha-spinner slot="icon"></ha-spinner>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.storage.loading_detailed"
|
||||
)}</ha-alert
|
||||
>`
|
||||
: nothing}`;
|
||||
}
|
||||
);
|
||||
|
||||
private _bytesToGB(bytes: number) {
|
||||
return bytes / 1024 / 1024 / 1024;
|
||||
}
|
||||
|
||||
private _gbToBytes(GB: number) {
|
||||
return GB * 1024 * 1024 * 1024;
|
||||
}
|
||||
|
||||
private async _load() {
|
||||
this._loadStorageInfo();
|
||||
try {
|
||||
@@ -523,10 +433,6 @@ class HaConfigSectionStorage extends LitElement {
|
||||
ha-alert {
|
||||
--ha-alert-icon-size: 24px;
|
||||
}
|
||||
|
||||
ha-alert ha-spinner {
|
||||
--ha-spinner-size: 24px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
|
||||
293
src/panels/config/storage/storage-breakdown-chart.ts
Normal file
293
src/panels/config/storage/storage-breakdown-chart.ts
Normal file
@@ -0,0 +1,293 @@
|
||||
import { mdiChartDonutVariant, mdiViewArray } from "@mdi/js";
|
||||
import type { TemplateResult } from "lit";
|
||||
import { LitElement, css, html, nothing } from "lit";
|
||||
import { customElement, property, state } from "lit/decorators";
|
||||
import memoizeOne from "memoize-one";
|
||||
import { getGraphColorByIndex } from "../../../common/color/colors";
|
||||
import "../../../components/chart/ha-sunburst-chart";
|
||||
import type { SunburstNode } from "../../../components/chart/ha-sunburst-chart";
|
||||
import "../../../components/ha-alert";
|
||||
import "../../../components/ha-icon-button";
|
||||
import "../../../components/ha-segmented-bar";
|
||||
import "../../../components/ha-spinner";
|
||||
import type { Segment } from "../../../components/ha-segmented-bar";
|
||||
import type { HassioHostInfo, HostDisksUsage } from "../../../data/hassio/host";
|
||||
import type { HomeAssistant } from "../../../types";
|
||||
import { roundWithOneDecimal } from "../../../util/calculate";
|
||||
|
||||
@customElement("storage-breakdown-chart")
|
||||
export class StorageBreakdownChart extends LitElement {
|
||||
@property({ attribute: false })
|
||||
public hass!: HomeAssistant;
|
||||
|
||||
@property({ attribute: false })
|
||||
public hostInfo?: HassioHostInfo;
|
||||
|
||||
@property({ attribute: false })
|
||||
public storageInfo?: HostDisksUsage | null;
|
||||
|
||||
@state()
|
||||
private _chartType: "bar" | "sunburst" = "bar";
|
||||
|
||||
protected render(): TemplateResult | typeof nothing {
|
||||
if (!this.hostInfo) {
|
||||
return nothing;
|
||||
}
|
||||
const { totalSpaceGB, usedSpaceGB, freeSpaceGB } = this._computeSpaceValues(
|
||||
this.hostInfo,
|
||||
this.storageInfo
|
||||
);
|
||||
|
||||
const hasChildren = Boolean(this.storageInfo?.children?.length);
|
||||
const heading = this.hass.localize("ui.panel.config.storage.used_space");
|
||||
const description = this.hass.localize(
|
||||
"ui.panel.config.storage.detailed_description",
|
||||
{
|
||||
used: `${roundWithOneDecimal(usedSpaceGB)} GB`,
|
||||
total: `${roundWithOneDecimal(totalSpaceGB)} GB`,
|
||||
}
|
||||
);
|
||||
const showBarChart = this._chartType === "bar" || !hasChildren;
|
||||
|
||||
return html`
|
||||
<div class="header">
|
||||
<div class="heading-text">
|
||||
<span class="heading">${heading}</span>
|
||||
<span class="description">${description}</span>
|
||||
</div>
|
||||
${hasChildren
|
||||
? html`<ha-icon-button
|
||||
.path=${this._chartType === "sunburst"
|
||||
? mdiViewArray
|
||||
: mdiChartDonutVariant}
|
||||
.label=${this.hass.localize(
|
||||
"ui.panel.config.storage.change_chart_type"
|
||||
)}
|
||||
@click=${this._handleChartTypeChange}
|
||||
></ha-icon-button>`
|
||||
: nothing}
|
||||
</div>
|
||||
|
||||
<div class="chart-container ${this._chartType}">
|
||||
${showBarChart
|
||||
? html`<ha-segmented-bar
|
||||
.heading=${""}
|
||||
.segments=${this._computeSegments(
|
||||
this.storageInfo,
|
||||
usedSpaceGB,
|
||||
freeSpaceGB
|
||||
)}
|
||||
></ha-segmented-bar>`
|
||||
: html`<ha-sunburst-chart
|
||||
.hass=${this.hass}
|
||||
.data=${this._transformToSunburstData(this.storageInfo!)}
|
||||
.valueFormatter=${this._formatBytes}
|
||||
></ha-sunburst-chart>`}
|
||||
</div>
|
||||
|
||||
${!this.storageInfo || this.storageInfo === null
|
||||
? html`<ha-alert alert-type="info">
|
||||
<ha-spinner slot="icon"></ha-spinner>
|
||||
${this.hass.localize(
|
||||
"ui.panel.config.storage.loading_detailed"
|
||||
)}</ha-alert
|
||||
>`
|
||||
: nothing}
|
||||
`;
|
||||
}
|
||||
|
||||
private _handleChartTypeChange(): void {
|
||||
this._chartType = this._chartType === "bar" ? "sunburst" : "bar";
|
||||
}
|
||||
|
||||
private _computeSpaceValues = memoizeOne(
|
||||
(
|
||||
hostInfo: HassioHostInfo,
|
||||
storageInfo: HostDisksUsage | null | undefined
|
||||
) => {
|
||||
let totalSpaceGB = hostInfo.disk_total;
|
||||
let usedSpaceGB = hostInfo.disk_used;
|
||||
let freeSpaceGB =
|
||||
hostInfo.disk_free || hostInfo.disk_total - hostInfo.disk_used;
|
||||
|
||||
if (storageInfo) {
|
||||
const totalSpace =
|
||||
storageInfo.total_bytes ?? this._gbToBytes(hostInfo.disk_total);
|
||||
totalSpaceGB = this._bytesToGB(totalSpace);
|
||||
usedSpaceGB = this._bytesToGB(storageInfo.used_bytes);
|
||||
freeSpaceGB = this._bytesToGB(totalSpace - storageInfo.used_bytes);
|
||||
}
|
||||
|
||||
return { totalSpaceGB, usedSpaceGB, freeSpaceGB };
|
||||
}
|
||||
);
|
||||
|
||||
private _computeSegments = memoizeOne(
|
||||
(
|
||||
storageInfo: HostDisksUsage | null | undefined,
|
||||
usedSpaceGB: number,
|
||||
freeSpaceGB: number
|
||||
): Segment[] => {
|
||||
const computedStyles = getComputedStyle(this);
|
||||
const segments: Segment[] = [];
|
||||
|
||||
if (storageInfo) {
|
||||
storageInfo.children?.forEach((child, index) => {
|
||||
if (child.used_bytes > 0) {
|
||||
const space = this._bytesToGB(child.used_bytes);
|
||||
segments.push({
|
||||
value: space,
|
||||
color: getGraphColorByIndex(index, computedStyles),
|
||||
label: html`${this.hass.localize(
|
||||
`ui.panel.config.storage.segments.${child.id}`
|
||||
) ||
|
||||
child.label ||
|
||||
child.id}
|
||||
<span style="color: var(--secondary-text-color)"
|
||||
>${roundWithOneDecimal(space)} GB</span
|
||||
>`,
|
||||
});
|
||||
}
|
||||
});
|
||||
} else {
|
||||
segments.push({
|
||||
value: usedSpaceGB,
|
||||
color: "var(--primary-color)",
|
||||
label: html`${this.hass.localize(
|
||||
"ui.panel.config.storage.segments.used"
|
||||
)}
|
||||
<span style="color: var(--secondary-text-color)"
|
||||
>${roundWithOneDecimal(usedSpaceGB)} GB</span
|
||||
>`,
|
||||
});
|
||||
}
|
||||
|
||||
segments.push({
|
||||
value: freeSpaceGB,
|
||||
color:
|
||||
"var(--ha-bar-background-color, var(--secondary-background-color))",
|
||||
label: html`${this.hass.localize(
|
||||
"ui.panel.config.storage.segments.free"
|
||||
)}
|
||||
<span style="color: var(--secondary-text-color)"
|
||||
>${roundWithOneDecimal(freeSpaceGB)} GB</span
|
||||
>`,
|
||||
});
|
||||
|
||||
return segments;
|
||||
}
|
||||
);
|
||||
|
||||
private _transformToSunburstData = memoizeOne(
|
||||
(storageInfo: HostDisksUsage): SunburstNode => {
|
||||
const transform = (
|
||||
node: HostDisksUsage,
|
||||
parentNode?: HostDisksUsage
|
||||
): SunburstNode => ({
|
||||
// prefix with parent id to avoid duplicate ids
|
||||
id: parentNode ? `${parentNode.id}.${node.id}` : node.id,
|
||||
name: this._formatLabel(node.id) || node.label,
|
||||
value: node.used_bytes,
|
||||
children: node.children?.map((child) => transform(child, node)),
|
||||
});
|
||||
return transform(storageInfo);
|
||||
}
|
||||
);
|
||||
|
||||
private _formatBytes = (bytes: number): string => {
|
||||
const gb = this._bytesToGB(bytes);
|
||||
return `${roundWithOneDecimal(gb)} GB`;
|
||||
};
|
||||
|
||||
private _formatLabel = (id: string): string =>
|
||||
this.hass.localize(`ui.panel.config.storage.segments.${id}`) || id;
|
||||
|
||||
private _bytesToGB(bytes: number): number {
|
||||
return bytes / 1024 / 1024 / 1024;
|
||||
}
|
||||
|
||||
private _gbToBytes(GB: number): number {
|
||||
return GB * 1024 * 1024 * 1024;
|
||||
}
|
||||
|
||||
static styles = css`
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.heading-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.heading {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 12px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
ha-icon-button {
|
||||
--mdc-icon-button-size: 36px;
|
||||
--mdc-icon-size: 20px;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
.chart-container {
|
||||
transition: height 0.3s ease;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chart-container.bar {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.chart-container.sunburst {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
ha-segmented-bar {
|
||||
display: block;
|
||||
}
|
||||
|
||||
ha-sunburst-chart {
|
||||
height: 400px;
|
||||
}
|
||||
|
||||
ha-segmented-bar,
|
||||
ha-sunburst-chart {
|
||||
animation: fade-in 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
ha-alert {
|
||||
--ha-alert-icon-size: 24px;
|
||||
}
|
||||
|
||||
ha-alert ha-spinner {
|
||||
--ha-spinner-size: 24px;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface HTMLElementTagNameMap {
|
||||
"storage-breakdown-chart": StorageBreakdownChart;
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import type {
|
||||
CustomSeriesOption,
|
||||
SankeySeriesOption,
|
||||
GraphSeriesOption,
|
||||
SunburstSeriesOption,
|
||||
} from "echarts/charts";
|
||||
import type {
|
||||
// The component option types are defined with the ComponentOption suffix
|
||||
@@ -55,6 +56,7 @@ export type ECOption = ComposeOption<
|
||||
| VisualMapComponentOption
|
||||
| SankeySeriesOption
|
||||
| GraphSeriesOption
|
||||
| SunburstSeriesOption
|
||||
>;
|
||||
|
||||
// Register the required components
|
||||
|
||||
@@ -6934,6 +6934,7 @@
|
||||
"lifetime_description": "{lifetime} used",
|
||||
"lifetime_used_description": "The drive’s 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",
|
||||
"change_chart_type": "Change chart type",
|
||||
"datadisk": {
|
||||
"title": "Move data disk",
|
||||
"description": "You are currently using ''{current_path}'' as data disk. Moving the data disk will reboot your device and it's estimated to take {time} minutes. Your Home Assistant installation will not be accessible during this period. Do not disconnect the power during the move!",
|
||||
|
||||
Reference in New Issue
Block a user