diff --git a/src/panels/config/ha-panel-config.ts b/src/panels/config/ha-panel-config.ts index a5b23c5af6..74836d5d34 100644 --- a/src/panels/config/ha-panel-config.ts +++ b/src/panels/config/ha-panel-config.ts @@ -444,7 +444,7 @@ class HaPanelConfig extends HassRouterPage { }, storage: { tag: "ha-config-section-storage", - load: () => import("./core/ha-config-section-storage"), + load: () => import("./storage/ha-config-section-storage"), }, system_health: { tag: "ha-config-system-health", diff --git a/src/panels/config/storage/dialog-move-datadisk.ts b/src/panels/config/storage/dialog-move-datadisk.ts new file mode 100644 index 0000000000..ad9dec13f4 --- /dev/null +++ b/src/panels/config/storage/dialog-move-datadisk.ts @@ -0,0 +1,200 @@ +import "@material/mwc-list/mwc-list-item"; +import { css, CSSResultGroup, html, LitElement, TemplateResult } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import memoizeOne from "memoize-one"; +import { fireEvent } from "../../../common/dom/fire_event"; +import "../../../components/ha-circular-progress"; +import "../../../components/ha-markdown"; +import "../../../components/ha-select"; +import { + extractApiErrorMessage, + ignoreSupervisorError, +} from "../../../data/hassio/common"; +import { + DatadiskList, + fetchHassioHassOsInfo, + HassioHassOSInfo, + HassioHostInfo, + listDatadisks, + moveDatadisk, +} from "../../../data/hassio/host"; +import { showAlertDialog } from "../../../dialogs/generic/show-dialog-box"; +import { haStyle, haStyleDialog } from "../../../resources/styles"; +import { HomeAssistant } from "../../../types"; +import { MoveDatadiskDialogParams } from "./show-dialog-move-datadisk"; + +const calculateMoveTime = memoizeOne((hostInfo: HassioHostInfo): number => { + const speed = hostInfo.disk_life_time !== "" ? 30 : 10; + const moveTime = (hostInfo.disk_used * 1000) / 60 / speed; + const rebootTime = (hostInfo.startup_time * 4) / 60; + return Math.ceil((moveTime + rebootTime) / 10) * 10; +}); + +@customElement("dialog-move-datadisk") +class MoveDatadiskDialog extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _hostInfo?: HassioHostInfo; + + @state() private _selectedDevice?: string; + + @state() private _devices?: DatadiskList["devices"]; + + @state() private _osInfo?: HassioHassOSInfo; + + @state() private _moving = false; + + public async showDialog( + dialogParams: MoveDatadiskDialogParams + ): Promise> { + this._hostInfo = dialogParams.hostInfo; + + try { + this._osInfo = await fetchHassioHassOsInfo(this.hass); + } catch (err: any) { + await showAlertDialog(this, { + title: this.hass.localize( + "ui.panel.config.hardware.available_hardware.failed_to_get" + ), + text: extractApiErrorMessage(err), + }); + } + + listDatadisks(this.hass).then((data) => { + this._devices = data.devices; + }); + } + + public closeDialog(): void { + this._selectedDevice = undefined; + this._devices = undefined; + this._moving = false; + this._hostInfo = undefined; + this._osInfo = undefined; + fireEvent(this, "dialog-closed", { dialog: this.localName }); + } + + protected render(): TemplateResult { + if (!this._hostInfo || !this._osInfo) { + return html``; + } + return html` + + ${this._moving + ? html` + +

+ ${this.hass.localize( + "ui.panel.config.storage.datadisk.moving_desc" + )} +

` + : html` ${this._devices?.length + ? html` + ${this.hass.localize( + "ui.panel.config.storage.datadisk.description", + { + current_path: this._osInfo.data_disk, + time: calculateMoveTime(this._hostInfo), + } + )} +

+ + + ${this._devices.map( + (device) => + html`${device}` + )} + + ` + : this._devices === undefined + ? this.hass.localize( + "ui.panel.config.storage.datadisk.loading_devices" + ) + : this.hass.localize( + "ui.panel.config.storage.datadisk.no_devices" + )} + + + ${this.hass.localize("ui.panel.config.storage.datadisk.cancel")} + + + + ${this.hass.localize("ui.panel.config.storage.datadisk.move")} + `} +
+ `; + } + + private _select_device(ev) { + this._selectedDevice = ev.target.value; + } + + private async _moveDatadisk() { + this._moving = true; + try { + await moveDatadisk(this.hass, this._selectedDevice!); + } catch (err: any) { + if (this.hass.connection.connected && !ignoreSupervisorError(err)) { + showAlertDialog(this, { + title: this.hass.localize( + "ui.panel.config.storage.datadisk.failed_to_move" + ), + text: extractApiErrorMessage(err), + }); + this.closeDialog(); + } + } + } + + static get styles(): CSSResultGroup { + return [ + haStyle, + haStyleDialog, + css` + ha-select { + width: 100%; + } + ha-circular-progress { + display: block; + margin: 32px; + text-align: center; + } + + .progress-text { + text-align: center; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "dialog-move-datadisk": MoveDatadiskDialog; + } +} diff --git a/src/panels/config/core/ha-config-section-storage.ts b/src/panels/config/storage/ha-config-section-storage.ts similarity index 51% rename from src/panels/config/core/ha-config-section-storage.ts rename to src/panels/config/storage/ha-config-section-storage.ts index 7899861d87..3fe07a365d 100644 --- a/src/panels/config/core/ha-config-section-storage.ts +++ b/src/panels/config/storage/ha-config-section-storage.ts @@ -2,7 +2,6 @@ import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import "../../../components/ha-alert"; -import "../../../components/ha-bar"; import "../../../components/ha-metric"; import { fetchHassioHostInfo, HassioHostInfo } from "../../../data/hassio/host"; import "../../../layouts/hass-subpage"; @@ -11,7 +10,8 @@ import { getValueInPercentage, roundWithOneDecimal, } from "../../../util/calculate"; -import "./ha-config-analytics"; +import "../core/ha-config-analytics"; +import { showMoveDatadiskDialog } from "./show-dialog-move-datadisk"; @customElement("ha-config-section-storage") class HaConfigSectionStorage extends LitElement { @@ -23,7 +23,7 @@ class HaConfigSectionStorage extends LitElement { @state() private _error?: { code: string; message: string }; - @state() private _storageData?: HassioHostInfo; + @state() private _hostInfo?: HassioHostInfo; protected firstUpdated(changedProps: PropertyValues) { super.firstUpdated(changedProps); @@ -48,35 +48,44 @@ class HaConfigSectionStorage extends LitElement { > ` : ""} - ${this._storageData + ${this._hostInfo ? html` - - ${this._storageData.disk_life_time !== "" && - this._storageData.disk_life_time >= 10 - ? html` - - ` - : ""} +
+ + ${this._hostInfo.disk_life_time !== "" && + this._hostInfo.disk_life_time >= 10 + ? html` + + ` + : ""} +
+
+ + ${this.hass.localize( + "ui.panel.config.storage.datadisk.title" + )} + +
` : ""} @@ -87,12 +96,18 @@ class HaConfigSectionStorage extends LitElement { private async _load() { try { - this._storageData = await fetchHassioHostInfo(this.hass); + this._hostInfo = await fetchHassioHostInfo(this.hass); } catch (err: any) { this._error = err.message || err; } } + private _moveDatadisk(): void { + showMoveDatadiskDialog(this, { + hostInfo: this._hostInfo!, + }); + } + private _getUsedSpace = (used: number, total: number) => roundWithOneDecimal(getValueInPercentage(used, 0, total)); @@ -103,7 +118,6 @@ class HaConfigSectionStorage extends LitElement { margin: 0 auto; } ha-card { - padding: 16px; max-width: 500px; margin: 0 auto; height: 100%; @@ -111,6 +125,20 @@ class HaConfigSectionStorage extends LitElement { flex-direction: column; display: flex; } + .card-actions { + height: 48px; + border-top: none; + display: flex; + justify-content: space-between; + align-items: center; + } + + .card-content { + display: flex; + justify-content: space-between; + flex-direction: column; + padding: 16px 16px 0 16px; + } `; } diff --git a/src/panels/config/storage/show-dialog-move-datadisk.ts b/src/panels/config/storage/show-dialog-move-datadisk.ts new file mode 100644 index 0000000000..11a3b296c1 --- /dev/null +++ b/src/panels/config/storage/show-dialog-move-datadisk.ts @@ -0,0 +1,17 @@ +import { fireEvent } from "../../../common/dom/fire_event"; +import { HassioHostInfo } from "../../../data/hassio/host"; + +export interface MoveDatadiskDialogParams { + hostInfo: HassioHostInfo; +} + +export const showMoveDatadiskDialog = ( + element: HTMLElement, + dialogParams: MoveDatadiskDialogParams +): void => { + fireEvent(element, "show-dialog", { + dialogTag: "dialog-move-datadisk", + dialogImport: () => import("./dialog-move-datadisk"), + dialogParams, + }); +}; diff --git a/src/translations/en.json b/src/translations/en.json index e74e2aa172..2b4b05c119 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3152,7 +3152,19 @@ "storage": { "caption": "Storage", "used_space": "Used Space", - "emmc_lifetime_used": "eMMC Lifetime Used" + "emmc_lifetime_used": "eMMC Lifetime Used", + "datadisk": { + "title": "Move datadisk", + "description": "You are currently using ''{current_path}'' as datadisk. Moving data disks 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!", + "select_device": "Select new datadisk", + "no_devices": "No suitable attached devices found", + "moving_desc": "Rebooting and moving datadisk. Please have patience", + "moving": "Moving datadisk", + "loading_devices": "Loading devices", + "cancel": "[%key:ui::common::cancel%]", + "failed_to_move": "Failed to move datadisk", + "move": "Move" + } }, "system_health": { "caption": "System Health"