From 331385794c422a9a5f4fbf5505e0fbc90bf27675 Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Mon, 23 Dec 2024 15:30:17 +0100 Subject: [PATCH 01/89] View background settings: Change radio buttons to dropdowns (#23403) --- .../lovelace/editor/view-editor/hui-view-background-editor.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/panels/lovelace/editor/view-editor/hui-view-background-editor.ts b/src/panels/lovelace/editor/view-editor/hui-view-background-editor.ts index 794e78e5a5..58481f5164 100644 --- a/src/panels/lovelace/editor/view-editor/hui-view-background-editor.ts +++ b/src/panels/lovelace/editor/view-editor/hui-view-background-editor.ts @@ -59,6 +59,7 @@ export class HuiViewBackgroundEditor extends LitElement { translation_key: "ui.panel.lovelace.editor.edit_view.background.size", options: ["auto", "cover", "contain"], + mode: "dropdown", }, }, }, @@ -79,6 +80,7 @@ export class HuiViewBackgroundEditor extends LitElement { "bottom center", "bottom right", ], + mode: "dropdown", }, }, }, @@ -89,6 +91,7 @@ export class HuiViewBackgroundEditor extends LitElement { translation_key: "ui.panel.lovelace.editor.edit_view.background.repeat", options: ["repeat", "no-repeat"], + mode: "dropdown", }, }, }, From 2105db9104a8f83a2b150cd1ed4654d7e20d2683 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Wed, 25 Dec 2024 01:57:05 +0100 Subject: [PATCH 02/89] change default of backup actions card feature to no backup (#23444) --- .../lovelace/card-features/hui-update-actions-card-feature.ts | 2 +- .../config-elements/hui-update-actions-card-feature-editor.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/panels/lovelace/card-features/hui-update-actions-card-feature.ts b/src/panels/lovelace/card-features/hui-update-actions-card-feature.ts index aa0ec42d3d..1956df515a 100644 --- a/src/panels/lovelace/card-features/hui-update-actions-card-feature.ts +++ b/src/panels/lovelace/card-features/hui-update-actions-card-feature.ts @@ -16,7 +16,7 @@ import type { LovelaceCardFeature, LovelaceCardFeatureEditor } from "../types"; import { cardFeatureStyles } from "./common/card-feature-styles"; import type { UpdateActionsCardFeatureConfig } from "./types"; -export const DEFAULT_UPDATE_BACKUP_OPTION = "ask"; +export const DEFAULT_UPDATE_BACKUP_OPTION = "no"; export const supportsUpdateActionsCardFeature = (stateObj: HassEntity) => { const domain = computeDomain(stateObj.entity_id); diff --git a/src/panels/lovelace/editor/config-elements/hui-update-actions-card-feature-editor.ts b/src/panels/lovelace/editor/config-elements/hui-update-actions-card-feature-editor.ts index 201fd343bb..9497c6ae90 100644 --- a/src/panels/lovelace/editor/config-elements/hui-update-actions-card-feature-editor.ts +++ b/src/panels/lovelace/editor/config-elements/hui-update-actions-card-feature-editor.ts @@ -38,7 +38,7 @@ export class HuiUpdateActionsCardFeatureEditor disabled: !supportsBackup, selector: { select: { - default: "yes", + default: "no", mode: "dropdown", options: ["ask", "yes", "no"].map((option) => ({ value: option, From 86133a0696a8c0b158108d858ef3b325e950d7a5 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 24 Dec 2024 13:39:33 -0500 Subject: [PATCH 03/89] Fix typo in backups overview (#23446) --- .../backup/components/overview/ha-backup-overview-summary.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts b/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts index 1d63ac875e..c0457774bd 100644 --- a/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts +++ b/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts @@ -95,7 +95,7 @@ class HaBackupOverviewBackups extends LitElement { const now = new Date(); const lastBackupDescription = lastSuccessfulBackup - ? `Last successful backup ${relativeTime(lastSuccessfulBackupDate, this.hass.locale, now, true)} and stored to ${lastSuccessfulBackup.agent_ids?.length} locations.` + ? `Last successful backup ${relativeTime(lastSuccessfulBackupDate, this.hass.locale, now, true)} and stored in ${lastSuccessfulBackup.agent_ids?.length} locations.` : "You have no successful backups."; if (lastAttempt && lastAttempt > lastSuccessfulBackupDate) { From 205dd3f968c8f03f06e74c601b5998573219c8bb Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Wed, 25 Dec 2024 11:56:26 +0100 Subject: [PATCH 04/89] Fix chip spacing in automation/script save dialog (#23451) --- .../automation-rename-dialog/dialog-automation-rename.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/panels/config/automation/automation-rename-dialog/dialog-automation-rename.ts b/src/panels/config/automation/automation-rename-dialog/dialog-automation-rename.ts index df0b7aa1d0..f9a829d618 100644 --- a/src/panels/config/automation/automation-rename-dialog/dialog-automation-rename.ts +++ b/src/panels/config/automation/automation-rename-dialog/dialog-automation-rename.ts @@ -328,8 +328,7 @@ class DialogAutomationRename extends LitElement implements HassDialog { ha-icon-picker, ha-category-picker, ha-labels-picker, - ha-area-picker, - ha-chip-set { + ha-area-picker { display: block; } ha-icon-picker, From 44e26c925bef0ab0c771953a24220fb58ed29442 Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Wed, 25 Dec 2024 10:26:57 -0800 Subject: [PATCH 05/89] Fix dialog-person-detail tracker selection (#23454) --- src/panels/config/person/dialog-person-detail.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/config/person/dialog-person-detail.ts b/src/panels/config/person/dialog-person-detail.ts index 90dc3eb8dd..a8c9d2638f 100644 --- a/src/panels/config/person/dialog-person-detail.ts +++ b/src/panels/config/person/dialog-person-detail.ts @@ -180,7 +180,7 @@ class DialogPersonDetail extends LitElement implements HassDialog { ${this._renderUserFields()} - ${!this._deviceTrackersAvailable(this.hass) + ${this._deviceTrackersAvailable(this.hass) ? html`

${this.hass.localize( From 5c7fe04562e9610ab545a020df1ffe9fabdc2371 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Thu, 26 Dec 2024 15:18:54 +0100 Subject: [PATCH 06/89] Fix header of config entry system options dialog (#23455) Fix config entry system options dialog header --- .../dialog-config-entry-system-options.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/dialogs/config-entry-system-options/dialog-config-entry-system-options.ts b/src/dialogs/config-entry-system-options/dialog-config-entry-system-options.ts index d6334169e5..85f30279d3 100644 --- a/src/dialogs/config-entry-system-options/dialog-config-entry-system-options.ts +++ b/src/dialogs/config-entry-system-options/dialog-config-entry-system-options.ts @@ -3,7 +3,7 @@ import type { CSSResultGroup } from "lit"; import { css, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { fireEvent } from "../../common/dom/fire_event"; -import "../../components/ha-dialog"; +import { createCloseHeading } from "../../components/ha-dialog"; import "../../components/ha-formfield"; import "../../components/ha-switch"; import type { HaSwitch } from "../../components/ha-switch"; @@ -52,14 +52,14 @@ class DialogConfigEntrySystemOptions extends LitElement { ${this._error ? html`

${this._error}
` : ""} From d3b40141822ff92a2a35926b5aed6cd422c0a463 Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Sat, 28 Dec 2024 09:00:28 -0800 Subject: [PATCH 07/89] Fix backups fab spacer (#23490) --- src/panels/config/backup/ha-config-backup-backups.ts | 2 +- .../config/lovelace/resources/ha-config-lovelace-resources.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/panels/config/backup/ha-config-backup-backups.ts b/src/panels/config/backup/ha-config-backup-backups.ts index f6063ba21c..a0675e70b8 100644 --- a/src/panels/config/backup/ha-config-backup-backups.ts +++ b/src/panels/config/backup/ha-config-backup-backups.ts @@ -301,7 +301,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { return html` Date: Sun, 29 Dec 2024 18:20:14 +0000 Subject: [PATCH 08/89] Bumped version to 20241229.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index acf2d514e8..a46d758acb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20241224.0" +version = "20241229.0" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md" From c9082724a8c01a9ab35cb59addd63b66b7614e54 Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Mon, 30 Dec 2024 11:49:08 +0100 Subject: [PATCH 09/89] View background settings: Change transparancy to opacity (#23450) --- src/data/lovelace/config/view.ts | 2 +- .../editor/view-editor/hui-view-background-editor.ts | 10 +++++----- src/panels/lovelace/views/hui-view-background.ts | 4 ++-- src/translations/en.json | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/data/lovelace/config/view.ts b/src/data/lovelace/config/view.ts index 89a96b1a54..915c5545cb 100644 --- a/src/data/lovelace/config/view.ts +++ b/src/data/lovelace/config/view.ts @@ -9,7 +9,7 @@ export interface ShowViewConfig { export interface LovelaceViewBackgroundConfig { image?: string; - transparency?: number; + opacity?: number; size?: "auto" | "cover" | "contain"; alignment?: | "top left" diff --git a/src/panels/lovelace/editor/view-editor/hui-view-background-editor.ts b/src/panels/lovelace/editor/view-editor/hui-view-background-editor.ts index ec1a102a8d..fe5624f2a7 100644 --- a/src/panels/lovelace/editor/view-editor/hui-view-background-editor.ts +++ b/src/panels/lovelace/editor/view-editor/hui-view-background-editor.ts @@ -37,7 +37,7 @@ export class HuiViewBackgroundEditor extends LitElement { type: "expandable" as const, schema: [ { - name: "transparency", + name: "opacity", selector: { number: { min: 1, max: 100, mode: "slider" }, }, @@ -117,7 +117,7 @@ export class HuiViewBackgroundEditor extends LitElement { if (!background) { background = { - transparency: 33, + opacity: 33, alignment: "center", size: "cover", repeat: "repeat", @@ -125,7 +125,7 @@ export class HuiViewBackgroundEditor extends LitElement { }; } else { background = { - transparency: 100, + opacity: 100, alignment: "center", size: "cover", repeat: "no-repeat", @@ -162,9 +162,9 @@ export class HuiViewBackgroundEditor extends LitElement { return this.hass.localize( "ui.panel.lovelace.editor.edit_view.background.image" ); - case "transparency": + case "opacity": return this.hass.localize( - "ui.panel.lovelace.editor.edit_view.background.transparency" + "ui.panel.lovelace.editor.edit_view.background.opacity" ); case "alignment": return this.hass.localize( diff --git a/src/panels/lovelace/views/hui-view-background.ts b/src/panels/lovelace/views/hui-view-background.ts index fbb5e933c9..5a065d2650 100644 --- a/src/panels/lovelace/views/hui-view-background.ts +++ b/src/panels/lovelace/views/hui-view-background.ts @@ -67,8 +67,8 @@ export class HUIViewBackground extends LitElement { background?: string | LovelaceViewBackgroundConfig ) { if (typeof background === "object" && background.image) { - if (background.transparency) { - return `${background.transparency}%`; + if (background.opacity) { + return `${background.opacity}%`; } } return null; diff --git a/src/translations/en.json b/src/translations/en.json index 0b1477cba9..36e4533c6f 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -5955,7 +5955,7 @@ "bottom right": "Bottom right" } }, - "transparency": "Background transparency", + "opacity": "Background opacity", "repeat": { "name": "Background repeat", "options": { From cb0a48265a5230f805507ec57ece346298c5c7a8 Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Mon, 30 Dec 2024 12:51:21 +0200 Subject: [PATCH 10/89] Fix helper dialog close and add failsafe for similar cases (#23468) --- src/common/navigate.ts | 13 ++++++++++--- src/dialogs/make-dialog-manager.ts | 1 + src/panels/config/helpers/dialog-helper-detail.ts | 2 ++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/common/navigate.ts b/src/common/navigate.ts index a134cf57f1..da636bf58b 100644 --- a/src/common/navigate.ts +++ b/src/common/navigate.ts @@ -14,9 +14,16 @@ export interface NavigateOptions { data?: any; } -export const navigate = async (path: string, options?: NavigateOptions) => { +// max time to wait for dialogs to close before navigating +const DIALOG_WAIT_TIMEOUT = 500; + +export const navigate = async ( + path: string, + options?: NavigateOptions, + timestamp = Date.now() +) => { const { history } = mainWindow; - if (history.state?.dialog) { + if (history.state?.dialog && Date.now() - timestamp < DIALOG_WAIT_TIMEOUT) { const closed = await closeAllDialogs(); if (!closed) { // eslint-disable-next-line no-console @@ -26,7 +33,7 @@ export const navigate = async (path: string, options?: NavigateOptions) => { return new Promise((resolve) => { // need to wait for history state to be updated in case a dialog was closed setTimeout(() => { - navigate(path, options).then(resolve); + navigate(path, options, timestamp).then(resolve); }); }); } diff --git a/src/dialogs/make-dialog-manager.ts b/src/dialogs/make-dialog-manager.ts index 5dd52af166..6a780e0189 100644 --- a/src/dialogs/make-dialog-manager.ts +++ b/src/dialogs/make-dialog-manager.ts @@ -225,6 +225,7 @@ export const makeDialogManager = ( }; const _handleClosedFocus = async (ev: HASSDomEvent) => { + if (!LOADED[ev.detail.dialog]) return; const closedFocusTargets = LOADED[ev.detail.dialog].closedFocusTargets; delete LOADED[ev.detail.dialog].closedFocusTargets; if (!closedFocusTargets) return; diff --git a/src/panels/config/helpers/dialog-helper-detail.ts b/src/panels/config/helpers/dialog-helper-detail.ts index 7bce02c8a3..bcbcfd74a8 100644 --- a/src/panels/config/helpers/dialog-helper-detail.ts +++ b/src/panels/config/helpers/dialog-helper-detail.ts @@ -32,6 +32,7 @@ import { brandsUrl } from "../../../util/brands-url"; import type { Helper, HelperDomain } from "./const"; import { isHelperDomain } from "./const"; import type { ShowDialogHelperDetailParams } from "./show-dialog-helper-detail"; +import { fireEvent } from "../../../common/dom/fire_event"; type HelperCreators = { [domain in HelperDomain]: { @@ -129,6 +130,7 @@ export class DialogHelperDetail extends LitElement { this._error = undefined; this._domain = undefined; this._params = undefined; + fireEvent(this, "dialog-closed", { dialog: this.localName }); } protected render() { From 6d8422513a4a3c77c97f409bcab4ad05d9430478 Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Mon, 30 Dec 2024 12:52:52 +0200 Subject: [PATCH 11/89] Button to reset chart zoom (#23469) --- src/components/chart/ha-chart-base.ts | 32 ++++++++++++++++++++++++++- src/translations/en.json | 3 ++- 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts index 22060e0b42..c3aab89e82 100644 --- a/src/components/chart/ha-chart-base.ts +++ b/src/components/chart/ha-chart-base.ts @@ -10,11 +10,13 @@ import { css, html, nothing, LitElement } from "lit"; import { customElement, property, state } from "lit/decorators"; import { classMap } from "lit/directives/class-map"; import { styleMap } from "lit/directives/style-map"; +import { mdiRestart } from "@mdi/js"; import { fireEvent } from "../../common/dom/fire_event"; import { clamp } from "../../common/number/clamp"; import type { HomeAssistant } from "../../types"; import { debounce } from "../../common/util/debounce"; import { isMac } from "../../util/is_mac"; +import "../ha-icon-button"; export const MIN_TIME_BETWEEN_UPDATES = 60 * 5 * 1000; @@ -300,6 +302,16 @@ export class HaChartBase extends LitElement { : this.hass.localize("ui.components.history_charts.zoom_hint")} + ${this._isZoomed && this.chartType !== "timeline" + ? html`` + : nothing} ${this._tooltip ? html`
{ const isZoomed = this.chart?.isZoomedOrPanned() ?? false; if (this._isZoomed && !isZoomed) { @@ -541,6 +554,10 @@ export class HaChartBase extends LitElement { } } + private _handleZoomReset() { + this.chart?.resetZoom(); + } + static get styles(): CSSResultGroup { return css` :host { @@ -552,6 +569,9 @@ export class HaChartBase extends LitElement { height: 0; transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1); } + .chart-container { + position: relative; + } canvas { max-height: var(--chart-max-height, 400px); } @@ -670,6 +690,16 @@ export class HaChartBase extends LitElement { background: rgba(0, 0, 0, 0.3); box-shadow: 0 0 32px 32px rgba(0, 0, 0, 0.3); } + .zoom-reset { + position: absolute; + top: 16px; + right: 4px; + background: var(--card-background-color); + border-radius: 4px; + --mdc-icon-button-size: 32px; + color: var(--primary-color); + border: 1px solid var(--divider-color); + } `; } } diff --git a/src/translations/en.json b/src/translations/en.json index 36e4533c6f..cd898e6b91 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -830,7 +830,8 @@ "source_history": "Source: History", "source_stats": "Source: Long term statistics", "zoom_hint": "Use ctrl + scroll to zoom in/out", - "zoom_hint_mac": "Use ⌘ + scroll to zoom in/out" + "zoom_hint_mac": "Use ⌘ + scroll to zoom in/out", + "zoom_reset": "Reset zoom" }, "map": { "error": "Unable to load map" From b429ecc376699f70b77ba00d7215ab413f37e933 Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Fri, 27 Dec 2024 14:29:02 +0100 Subject: [PATCH 12/89] Calendar trigger: Handle optional offset better (#23474) Calendar empty offset --- src/data/automation_i18n.ts | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/data/automation_i18n.ts b/src/data/automation_i18n.ts index 616659e682..1542d1c000 100644 --- a/src/data/automation_i18n.ts +++ b/src/data/automation_i18n.ts @@ -737,18 +737,22 @@ const tryDescribeTrigger = ( ? computeStateName(hass.states[trigger.entity_id]) : trigger.entity_id; - let offsetChoice = trigger.offset.startsWith("-") ? "before" : "after"; - let offset: string | string[] = trigger.offset.startsWith("-") - ? trigger.offset.substring(1).split(":") - : trigger.offset.split(":"); - const duration = { - hours: offset.length > 0 ? +offset[0] : 0, - minutes: offset.length > 1 ? +offset[1] : 0, - seconds: offset.length > 2 ? +offset[2] : 0, - }; - offset = formatDurationLong(hass.locale, duration); - if (offset === "") { - offsetChoice = "other"; + let offsetChoice: string = "other"; + let offset: string | string[] = ""; + if (trigger.offset) { + offsetChoice = trigger.offset.startsWith("-") ? "before" : "after"; + offset = trigger.offset.startsWith("-") + ? trigger.offset.substring(1).split(":") + : trigger.offset.split(":"); + const duration = { + hours: offset.length > 0 ? +offset[0] : 0, + minutes: offset.length > 1 ? +offset[1] : 0, + seconds: offset.length > 2 ? +offset[2] : 0, + }; + offset = formatDurationLong(hass.locale, duration); + if (offset === "") { + offsetChoice = "other"; + } } return hass.localize( From f1c360c55047ecc98b88536813b478aeadad448c Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Mon, 30 Dec 2024 12:54:00 +0200 Subject: [PATCH 13/89] Add `getGridOptions` to history and statistics graph cards (#23476) --- src/panels/lovelace/cards/hui-history-graph-card.ts | 10 +++++++++- src/panels/lovelace/cards/hui-statistics-graph-card.ts | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/panels/lovelace/cards/hui-history-graph-card.ts b/src/panels/lovelace/cards/hui-history-graph-card.ts index 490180ac91..95d6004b13 100644 --- a/src/panels/lovelace/cards/hui-history-graph-card.ts +++ b/src/panels/lovelace/cards/hui-history-graph-card.ts @@ -16,7 +16,7 @@ import { getSensorNumericDeviceClasses } from "../../../data/sensor"; import type { HomeAssistant } from "../../../types"; import { hasConfigOrEntitiesChanged } from "../common/has-changed"; import { processConfigEntities } from "../common/process-config-entities"; -import type { LovelaceCard } from "../types"; +import type { LovelaceCard, LovelaceGridOptions } from "../types"; import type { HistoryGraphCardConfig } from "./types"; import { createSearchParam } from "../../../common/url/search-params"; @@ -56,6 +56,14 @@ export class HuiHistoryGraphCard extends LitElement implements LovelaceCard { return this._config?.title ? 2 : 0 + 2 * (this._entityIds?.length || 1); } + getGridOptions(): LovelaceGridOptions { + return { + columns: 12, + min_columns: 6, + min_rows: (this._config?.entities?.length || 1) * 2, + }; + } + public setConfig(config: HistoryGraphCardConfig): void { if (!config.entities || !Array.isArray(config.entities)) { throw new Error("Entities need to be an array"); diff --git a/src/panels/lovelace/cards/hui-statistics-graph-card.ts b/src/panels/lovelace/cards/hui-statistics-graph-card.ts index aafd5b22f1..4e703dafb4 100644 --- a/src/panels/lovelace/cards/hui-statistics-graph-card.ts +++ b/src/panels/lovelace/cards/hui-statistics-graph-card.ts @@ -18,7 +18,7 @@ import type { HomeAssistant } from "../../../types"; import { findEntities } from "../common/find-entities"; import { hasConfigOrEntitiesChanged } from "../common/has-changed"; import { processConfigEntities } from "../common/process-config-entities"; -import type { LovelaceCard } from "../types"; +import type { LovelaceCard, LovelaceGridOptions } from "../types"; import type { StatisticsGraphCardConfig } from "./types"; export const DEFAULT_DAYS_TO_SHOW = 30; @@ -93,6 +93,14 @@ export class HuiStatisticsGraphCard extends LitElement implements LovelaceCard { ); } + getGridOptions(): LovelaceGridOptions { + return { + columns: 12, + min_columns: 9, + min_rows: 4, + }; + } + public setConfig(config: StatisticsGraphCardConfig): void { if (!config.entities || !Array.isArray(config.entities)) { throw new Error("Entities need to be an array"); From 8e8fd89d56154a42dc64c09f090de456ce8ee67d Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Fri, 27 Dec 2024 15:50:55 +0200 Subject: [PATCH 14/89] Fix custom DNS saving (#23477) --- src/panels/config/network/supervisor-network.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/panels/config/network/supervisor-network.ts b/src/panels/config/network/supervisor-network.ts index 50f3afbe98..6141ae5d59 100644 --- a/src/panels/config/network/supervisor-network.ts +++ b/src/panels/config/network/supervisor-network.ts @@ -525,6 +525,9 @@ export class HassioNetwork extends LitElement { IP_VERSIONS.forEach((version) => { interfaceOptions[version] = { method: this._interface![version]?.method || "auto", + nameservers: this._interface![version]?.nameservers?.filter( + (ns: string) => ns.trim() + ), }; if (this._interface![version]?.method === "static") { interfaceOptions[version] = { @@ -533,9 +536,6 @@ export class HassioNetwork extends LitElement { (address: string) => address.trim() ), gateway: this._interface![version]?.gateway, - nameservers: this._interface![version]?.nameservers?.filter( - (ns: string) => ns.trim() - ), }; } }); From c338e9cb30d3693f967022fc2057dd8ac3f2f3b8 Mon Sep 17 00:00:00 2001 From: Timothy Kist Date: Sun, 29 Dec 2024 18:36:24 +0000 Subject: [PATCH 15/89] Remove space at end of link from HAOS storage tip (#23492) --- src/components/media-player/dialog-media-manage.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/media-player/dialog-media-manage.ts b/src/components/media-player/dialog-media-manage.ts index dde5bcc547..a1a57933da 100644 --- a/src/components/media-player/dialog-media-manage.ts +++ b/src/components/media-player/dialog-media-manage.ts @@ -212,8 +212,8 @@ class DialogMediaManage extends LitElement { > ${this.hass.localize( "ui.components.media-browser.file_management.tip_storage_panel" - )} - `, + )}`, } )} ` From cf1df712e426a3cb50a8ead09ff0cd0c9aaee755 Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Mon, 30 Dec 2024 11:12:31 +0100 Subject: [PATCH 16/89] Fix dialog header (#23507) --- src/components/ha-dialog.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/ha-dialog.ts b/src/components/ha-dialog.ts index 863431fc66..ee96bd0f52 100644 --- a/src/components/ha-dialog.ts +++ b/src/components/ha-dialog.ts @@ -102,10 +102,10 @@ export class HaDialog extends DialogBase { align-items: var(--vertical-align-dialog, center); } .mdc-dialog__title { - padding: 12px 12px 0; + padding: 24px 24px 0 24px; } - .mdc-dialog--scrollable .mdc-dialog__title { - padding: 12px; + .mdc-dialog__title:has(span) { + padding: 12px 12px 0; } .mdc-dialog__actions { padding: 12px 24px 12px 24px; From 4686808e53cca6d8fe7fb8340375a3d14535e56d Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Mon, 30 Dec 2024 12:05:00 +0100 Subject: [PATCH 17/89] Fix manual backup disabled with all backup locations (#23511) --- src/panels/config/backup/dialogs/dialog-generate-backup.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/panels/config/backup/dialogs/dialog-generate-backup.ts b/src/panels/config/backup/dialogs/dialog-generate-backup.ts index 95ddb89809..866aaf441d 100644 --- a/src/panels/config/backup/dialogs/dialog-generate-backup.ts +++ b/src/panels/config/backup/dialogs/dialog-generate-backup.ts @@ -200,7 +200,8 @@ class DialogGenerateBackup extends LitElement implements HassDialog { ? html` Create backup From 700690474ccd9d24aa2c9e46f9a0c26c5fdc162e Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Mon, 30 Dec 2024 16:21:22 +0100 Subject: [PATCH 18/89] Add script hide picker again (#23512) --- src/components/ha-service-control.ts | 2 +- src/dialogs/more-info/controls/more-info-script.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ha-service-control.ts b/src/components/ha-service-control.ts index 4bb9449323..5dd4222b23 100644 --- a/src/components/ha-service-control.ts +++ b/src/components/ha-service-control.ts @@ -89,7 +89,7 @@ export class HaServiceControl extends LitElement { @property({ attribute: "show-advanced", type: Boolean }) public showAdvanced = false; - @property({ attribute: false, type: Boolean, reflect: true }) + @property({ attribute: "hide-picker", type: Boolean, reflect: true }) public hidePicker = false; @property({ attribute: "hide-description", type: Boolean }) diff --git a/src/dialogs/more-info/controls/more-info-script.ts b/src/dialogs/more-info/controls/more-info-script.ts index cacaeeca3d..aba8253a0e 100644 --- a/src/dialogs/more-info/controls/more-info-script.ts +++ b/src/dialogs/more-info/controls/more-info-script.ts @@ -99,6 +99,7 @@ class MoreInfoScript extends LitElement { ${this.hass.localize("ui.card.script.run_script")}
Date: Mon, 30 Dec 2024 17:06:26 +0100 Subject: [PATCH 19/89] Add fallback for devices without name (#23513) --- src/components/device/ha-device-picker.ts | 4 +++- src/dialogs/quick-bar/ha-quick-bar.ts | 4 +++- src/translations/en.json | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/device/ha-device-picker.ts b/src/components/device/ha-device-picker.ts index 724dcbbf06..4182ee106a 100644 --- a/src/components/device/ha-device-picker.ts +++ b/src/components/device/ha-device-picker.ts @@ -222,7 +222,9 @@ export class HaDevicePicker extends LitElement { return { id: device.id, - name: name, + name: + name || + this.hass.localize("ui.components.device-picker.unnamed_device"), area: device.area_id && areas[device.area_id] ? areas[device.area_id].name diff --git a/src/dialogs/quick-bar/ha-quick-bar.ts b/src/dialogs/quick-bar/ha-quick-bar.ts index 4b8c56d7ee..8c108e1f5f 100644 --- a/src/dialogs/quick-bar/ha-quick-bar.ts +++ b/src/dialogs/quick-bar/ha-quick-bar.ts @@ -530,7 +530,9 @@ export class QuickBar extends LitElement { ? this.hass.areas[device.area_id] : undefined; const deviceItem = { - primaryText: computeDeviceName(device, this.hass), + primaryText: + computeDeviceName(device, this.hass) || + this.hass.localize("ui.components.device-picker.unnamed_device"), deviceId: device.id, area: area?.name, action: () => navigate(`/config/devices/device/${device.id}`), diff --git a/src/translations/en.json b/src/translations/en.json index cd898e6b91..76c2f6ace4 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -651,6 +651,7 @@ "no_devices": "You don't have any devices", "no_match": "No matching devices found", "device": "Device", + "unnamed_device": "Unnamed device", "no_area": "No area" }, "category-picker": { From d0123b2ccecac684229f18b748867a94431602b7 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 30 Dec 2024 17:43:05 +0100 Subject: [PATCH 20/89] Fix overflow of backup agents (#23514) --- .../config/backup/components/config/ha-backup-config-agents.ts | 3 --- .../config/backup/components/config/ha-backup-config-data.ts | 3 --- .../backup/components/config/ha-backup-config-schedule.ts | 3 --- 3 files changed, 9 deletions(-) diff --git a/src/panels/config/backup/components/config/ha-backup-config-agents.ts b/src/panels/config/backup/components/config/ha-backup-config-agents.ts index 6a15f55c94..de74488dcf 100644 --- a/src/panels/config/backup/components/config/ha-backup-config-agents.ts +++ b/src/panels/config/backup/components/config/ha-backup-config-agents.ts @@ -144,9 +144,6 @@ class HaBackupConfigAgents extends LitElement { --md-list-item-leading-space: 0; --md-list-item-trailing-space: 0; } - ha-md-list-item { - --md-item-overflow: visible; - } ha-md-list-item img { width: 48px; } diff --git a/src/panels/config/backup/components/config/ha-backup-config-data.ts b/src/panels/config/backup/components/config/ha-backup-config-data.ts index bae085447a..be462ff3e5 100644 --- a/src/panels/config/backup/components/config/ha-backup-config-data.ts +++ b/src/panels/config/backup/components/config/ha-backup-config-data.ts @@ -332,9 +332,6 @@ class HaBackupConfigData extends LitElement { ha-md-select { min-width: 210px; } - ha-md-list-item { - --md-item-overflow: visible; - } @media all and (max-width: 450px) { ha-md-select { min-width: 160px; diff --git a/src/panels/config/backup/components/config/ha-backup-config-schedule.ts b/src/panels/config/backup/components/config/ha-backup-config-schedule.ts index f7c9e2e50d..0a48fc9a0e 100644 --- a/src/panels/config/backup/components/config/ha-backup-config-schedule.ts +++ b/src/panels/config/backup/components/config/ha-backup-config-schedule.ts @@ -323,9 +323,6 @@ class HaBackupConfigSchedule extends LitElement { ha-md-select { min-width: 210px; } - ha-md-list-item { - --md-item-overflow: visible; - } @media all and (max-width: 450px) { ha-md-select { min-width: 160px; From f3705a7e1daf7fa4a441c6fdf303bbadf938181f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 30 Dec 2024 17:42:31 +0100 Subject: [PATCH 21/89] Fix copy encryption key (#23515) --- src/common/util/copy-clipboard.ts | 8 +++++--- .../config/backup/dialogs/dialog-backup-onboarding.ts | 5 ++++- .../dialogs/dialog-change-backup-encryption-key.ts | 10 ++++++++-- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/common/util/copy-clipboard.ts b/src/common/util/copy-clipboard.ts index 1708858c85..c50ad02418 100644 --- a/src/common/util/copy-clipboard.ts +++ b/src/common/util/copy-clipboard.ts @@ -1,4 +1,4 @@ -export const copyToClipboard = async (str) => { +export const copyToClipboard = async (str, rootEl?: HTMLElement) => { if (navigator.clipboard) { try { await navigator.clipboard.writeText(str); @@ -8,10 +8,12 @@ export const copyToClipboard = async (str) => { } } + const root = rootEl ?? document.body; + const el = document.createElement("textarea"); el.value = str; - document.body.appendChild(el); + root.appendChild(el); el.select(); document.execCommand("copy"); - document.body.removeChild(el); + root.removeChild(el); }; diff --git a/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts b/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts index 7719ebd066..cb5b798c50 100644 --- a/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts +++ b/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts @@ -396,7 +396,10 @@ class DialogBackupOnboarding extends LitElement implements HassDialog { } private async _copyKeyToClipboard() { - await copyToClipboard(this._config!.create_backup.password!); + await copyToClipboard( + this._config!.create_backup.password!, + this.renderRoot.querySelector("div")! + ); showToast(this, { message: this.hass.localize("ui.common.copied_clipboard"), }); diff --git a/src/panels/config/backup/dialogs/dialog-change-backup-encryption-key.ts b/src/panels/config/backup/dialogs/dialog-change-backup-encryption-key.ts index 1b500b34f9..3e140c60f7 100644 --- a/src/panels/config/backup/dialogs/dialog-change-backup-encryption-key.ts +++ b/src/panels/config/backup/dialogs/dialog-change-backup-encryption-key.ts @@ -206,7 +206,10 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog { } private async _copyKeyToClipboard() { - await copyToClipboard(this._newEncryptionKey); + await copyToClipboard( + this._newEncryptionKey, + this.renderRoot.querySelector("div")! + ); showToast(this, { message: this.hass.localize("ui.common.copied_clipboard"), }); @@ -216,7 +219,10 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog { if (!this._params?.currentKey) { return; } - await copyToClipboard(this._params.currentKey); + await copyToClipboard( + this._params.currentKey, + this.renderRoot.querySelector("div")! + ); showToast(this, { message: this.hass.localize("ui.common.copied_clipboard"), }); From 8b17286fb6bdb8351921c6cf4a939e0c290ba425 Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Fri, 27 Dec 2024 13:44:05 +0100 Subject: [PATCH 22/89] Revert "Automation/Script editor border-radius fix (#23267)" This reverts commit e9b2a8341142543941b0f5c366a8ab354c7d79d4. --- .../config/automation/action/ha-automation-action-row.ts | 5 ++--- .../automation/condition/ha-automation-condition-row.ts | 7 ++++--- .../config/automation/trigger/ha-automation-trigger-row.ts | 7 ++++--- src/panels/config/script/ha-script-field-row.ts | 5 ++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts index ebccfa306e..bd4fa868a1 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -614,9 +614,6 @@ export default class HaAutomationActionRow extends LitElement { ha-icon-button { --mdc-theme-text-primary-on-background: var(--primary-text-color); } - ha-card { - overflow: hidden; - } .disabled { opacity: 0.5; pointer-events: none; @@ -649,6 +646,8 @@ export default class HaAutomationActionRow extends LitElement { .disabled-bar { background: var(--divider-color, #e0e0e0); text-align: center; + border-top-right-radius: var(--ha-card-border-radius); + border-top-left-radius: var(--ha-card-border-radius); } mwc-list-item[disabled] { diff --git a/src/panels/config/automation/condition/ha-automation-condition-row.ts b/src/panels/config/automation/condition/ha-automation-condition-row.ts index 8df41296e8..864a24fed4 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-row.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-row.ts @@ -504,9 +504,6 @@ export default class HaAutomationConditionRow extends LitElement { ha-button-menu { --mdc-theme-text-primary-on-background: var(--primary-text-color); } - ha-card { - overflow: hidden; - } .disabled { opacity: 0.5; pointer-events: none; @@ -539,6 +536,8 @@ export default class HaAutomationConditionRow extends LitElement { .disabled-bar { background: var(--divider-color, #e0e0e0); text-align: center; + border-top-right-radius: var(--ha-card-border-radius); + border-top-left-radius: var(--ha-card-border-radius); } ha-list-item[disabled] { --mdc-theme-text-primary-on-background: var(--disabled-text-color); @@ -560,6 +559,8 @@ export default class HaAutomationConditionRow extends LitElement { overflow: hidden; transition: max-height 0.3s; text-align: center; + border-top-right-radius: var(--ha-card-border-radius, 12px); + border-top-left-radius: var(--ha-card-border-radius, 12px); } .testing.active { max-height: 100px; diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index 13efcbf7e7..b45d776ba0 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -651,9 +651,6 @@ export default class HaAutomationTriggerRow extends LitElement { ha-button-menu { --mdc-theme-text-primary-on-background: var(--primary-text-color); } - ha-card { - overflow: hidden; - } .disabled { opacity: 0.5; pointer-events: none; @@ -686,6 +683,8 @@ export default class HaAutomationTriggerRow extends LitElement { .disabled-bar { background: var(--divider-color, #e0e0e0); text-align: center; + border-top-right-radius: var(--ha-card-border-radius); + border-top-left-radius: var(--ha-card-border-radius); } .triggered { cursor: pointer; @@ -702,6 +701,8 @@ export default class HaAutomationTriggerRow extends LitElement { overflow: hidden; transition: max-height 0.3s; text-align: center; + border-top-right-radius: var(--ha-card-border-radius, 12px); + border-top-left-radius: var(--ha-card-border-radius, 12px); } .triggered.active { max-height: 100px; diff --git a/src/panels/config/script/ha-script-field-row.ts b/src/panels/config/script/ha-script-field-row.ts index e652b1f8cb..2542394ce9 100644 --- a/src/panels/config/script/ha-script-field-row.ts +++ b/src/panels/config/script/ha-script-field-row.ts @@ -295,9 +295,6 @@ export default class HaScriptFieldRow extends LitElement { ha-icon-button { --mdc-theme-text-primary-on-background: var(--primary-text-color); } - ha-card { - overflow: hidden; - } .disabled { opacity: 0.5; pointer-events: none; @@ -330,6 +327,8 @@ export default class HaScriptFieldRow extends LitElement { .disabled-bar { background: var(--divider-color, #e0e0e0); text-align: center; + border-top-right-radius: var(--ha-card-border-radius); + border-top-left-radius: var(--ha-card-border-radius); } ha-list-item[disabled] { From 713b5c7cf7b5f1987826bb34f46c0614be29dd19 Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Fri, 27 Dec 2024 13:47:10 +0100 Subject: [PATCH 23/89] Add default border-radius values to `.disabled-bar` --- .../config/automation/action/ha-automation-action-row.ts | 4 ++-- .../automation/condition/ha-automation-condition-row.ts | 4 ++-- .../config/automation/trigger/ha-automation-trigger-row.ts | 4 ++-- src/panels/config/script/ha-script-field-row.ts | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/panels/config/automation/action/ha-automation-action-row.ts b/src/panels/config/automation/action/ha-automation-action-row.ts index bd4fa868a1..c36f1be56f 100644 --- a/src/panels/config/automation/action/ha-automation-action-row.ts +++ b/src/panels/config/automation/action/ha-automation-action-row.ts @@ -646,8 +646,8 @@ export default class HaAutomationActionRow extends LitElement { .disabled-bar { background: var(--divider-color, #e0e0e0); text-align: center; - border-top-right-radius: var(--ha-card-border-radius); - border-top-left-radius: var(--ha-card-border-radius); + border-top-right-radius: var(--ha-card-border-radius, 12px); + border-top-left-radius: var(--ha-card-border-radius, 12px); } mwc-list-item[disabled] { diff --git a/src/panels/config/automation/condition/ha-automation-condition-row.ts b/src/panels/config/automation/condition/ha-automation-condition-row.ts index 864a24fed4..ded7354eca 100644 --- a/src/panels/config/automation/condition/ha-automation-condition-row.ts +++ b/src/panels/config/automation/condition/ha-automation-condition-row.ts @@ -536,8 +536,8 @@ export default class HaAutomationConditionRow extends LitElement { .disabled-bar { background: var(--divider-color, #e0e0e0); text-align: center; - border-top-right-radius: var(--ha-card-border-radius); - border-top-left-radius: var(--ha-card-border-radius); + border-top-right-radius: var(--ha-card-border-radius, 12px); + border-top-left-radius: var(--ha-card-border-radius, 12px); } ha-list-item[disabled] { --mdc-theme-text-primary-on-background: var(--disabled-text-color); diff --git a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts index b45d776ba0..43c8758e61 100644 --- a/src/panels/config/automation/trigger/ha-automation-trigger-row.ts +++ b/src/panels/config/automation/trigger/ha-automation-trigger-row.ts @@ -683,8 +683,8 @@ export default class HaAutomationTriggerRow extends LitElement { .disabled-bar { background: var(--divider-color, #e0e0e0); text-align: center; - border-top-right-radius: var(--ha-card-border-radius); - border-top-left-radius: var(--ha-card-border-radius); + border-top-right-radius: var(--ha-card-border-radius, 12px); + border-top-left-radius: var(--ha-card-border-radius, 12px); } .triggered { cursor: pointer; diff --git a/src/panels/config/script/ha-script-field-row.ts b/src/panels/config/script/ha-script-field-row.ts index 2542394ce9..f214216c84 100644 --- a/src/panels/config/script/ha-script-field-row.ts +++ b/src/panels/config/script/ha-script-field-row.ts @@ -327,8 +327,8 @@ export default class HaScriptFieldRow extends LitElement { .disabled-bar { background: var(--divider-color, #e0e0e0); text-align: center; - border-top-right-radius: var(--ha-card-border-radius); - border-top-left-radius: var(--ha-card-border-radius); + border-top-right-radius: var(--ha-card-border-radius, 12px); + border-top-left-radius: var(--ha-card-border-radius, 12px); } ha-list-item[disabled] { From e8af4547054115b888e1e974e7a91ce46ec96f96 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 30 Dec 2024 19:44:56 +0100 Subject: [PATCH 24/89] Bumped version to 20241230.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a46d758acb..9aab346d0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20241229.0" +version = "20241230.0" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md" From 220011f15fa0cdbed0e54046e1afccb7594a47b9 Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Tue, 31 Dec 2024 10:49:45 -0800 Subject: [PATCH 25/89] Display an error if saving new automation times out (#23518) --- src/common/util/promise-timeout.ts | 20 +++++++++++- .../config/automation/ha-automation-editor.ts | 31 +++++++++++++++++-- src/translations/en.json | 4 +++ 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/common/util/promise-timeout.ts b/src/common/util/promise-timeout.ts index 43b3359026..c38acce11a 100644 --- a/src/common/util/promise-timeout.ts +++ b/src/common/util/promise-timeout.ts @@ -1,7 +1,25 @@ +class TimeoutError extends Error { + public timeout: number; + + constructor(timeout: number, ...params) { + super(...params); + + // Maintains proper stack trace for where our error was thrown (only available on V8) + if (Error.captureStackTrace) { + Error.captureStackTrace(this, TimeoutError); + } + + this.name = "TimeoutError"; + // Custom debugging information + this.timeout = timeout; + this.message = `Timed out in ${timeout} ms.`; + } +} + export const promiseTimeout = (ms: number, promise: Promise | any) => { const timeout = new Promise((_resolve, reject) => { setTimeout(() => { - reject(`Timed out in ${ms} ms.`); + reject(new TimeoutError(ms)); }, ms); }); diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index 26fe6cc0f6..e6ca397584 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -27,6 +27,7 @@ import { fireEvent } from "../../../common/dom/fire_event"; import { navigate } from "../../../common/navigate"; import { computeRTL } from "../../../common/util/compute_rtl"; import { afterNextRender } from "../../../common/util/render-status"; +import { promiseTimeout } from "../../../common/util/promise-timeout"; import "../../../components/ha-button-menu"; import "../../../components/ha-fab"; import "../../../components/ha-icon"; @@ -944,8 +945,34 @@ export class HaAutomationEditor extends PreventUnsavedMixin( // wait for automation to appear in entity registry when creating a new automation if (entityRegPromise) { - const automation = await entityRegPromise; - entityId = automation.entity_id; + try { + const automation = await promiseTimeout(2000, entityRegPromise); + entityId = automation.entity_id; + } catch (e) { + if (e instanceof Error && e.name === "TimeoutError") { + showAlertDialog(this, { + title: this.hass.localize( + "ui.panel.config.automation.editor.new_automation_setup_failed_title", + { + type: this.hass.localize( + "ui.panel.config.automation.editor.type_automation" + ), + } + ), + text: this.hass.localize( + "ui.panel.config.automation.editor.new_automation_setup_failed_text", + { + type: this.hass.localize( + "ui.panel.config.automation.editor.type_automation" + ), + } + ), + warning: true, + }); + } else { + throw e; + } + } } if (entityId) { diff --git a/src/translations/en.json b/src/translations/en.json index 76c2f6ace4..09a80f5f99 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3065,6 +3065,10 @@ "unknown_entity": "unknown entity", "edit_unknown_device": "Editor not available for unknown device", "switch_ui_yaml_error": "There are currently YAML errors in the automation, and it cannot be parsed. Switching to UI mode may cause pending changes to be lost. Press cancel to correct any errors before proceeding to prevent loss of pending changes, or continue if you are sure.", + "type_automation": "automation", + "type_script": "script", + "new_automation_setup_failed_title": "New {type} setup failed", + "new_automation_setup_failed_text": "Your new {type} has saved, but waiting for it to setup has timed out. This could be due to errors parsing your configuration.yaml, please check the configuration in developer tools. Your {type} will not be visible until this is corrected, and automations are reloaded. Changes to area, category, or labels were not saved and must be reapplied.", "triggers": { "name": "Triggers", "header": "When", From 317a2f5b211b2f7e70aa33cb7b8629227f90dede Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 31 Dec 2024 17:48:34 +0100 Subject: [PATCH 26/89] Fix password incorrect check when restoring backup (#23525) --- src/panels/config/backup/dialogs/dialog-restore-backup.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/panels/config/backup/dialogs/dialog-restore-backup.ts b/src/panels/config/backup/dialogs/dialog-restore-backup.ts index 18a4b68a08..bcc6ecc7bf 100644 --- a/src/panels/config/backup/dialogs/dialog-restore-backup.ts +++ b/src/panels/config/backup/dialogs/dialog-restore-backup.ts @@ -194,8 +194,9 @@ class DialogRestoreBackup extends LitElement implements HassDialog { this._userPassword || this._backupEncryptionKey ); } catch (e: any) { - this._unsubscribe(); + await this._unsubscribe(); if (e.code === "password_incorrect") { + this._error = undefined; this._step = "encryption"; } else { this._error = e.message; @@ -229,9 +230,11 @@ class DialogRestoreBackup extends LitElement implements HassDialog { private _unsubscribe() { window.removeEventListener("connection-status", this._connectionStatus); if (this._unsub) { - this._unsub.then((unsub) => unsub()); + const prom = this._unsub.then((unsub) => unsub()); this._unsub = undefined; + return prom; } + return undefined; } private _restoreState() { From c697843c3441d8d1ac479dfb2e6e42f12a8d7863 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 24 Dec 2024 17:31:02 +0100 Subject: [PATCH 27/89] Update ha-backup-overview-summary.ts --- .../backup/components/overview/ha-backup-overview-summary.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts b/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts index c0457774bd..198797e6a6 100644 --- a/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts +++ b/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts @@ -99,7 +99,7 @@ class HaBackupOverviewBackups extends LitElement { : "You have no successful backups."; if (lastAttempt && lastAttempt > lastSuccessfulBackupDate) { - const lastAttemptDescription = `The last automatic backup trigged ${relativeTime(lastAttempt, this.hass.locale, now, true)} wasn't successful.`; + const lastAttemptDescription = `The last automatic backup triggered ${relativeTime(lastAttempt, this.hass.locale, now, true)} wasn't successful.`; return html` Date: Tue, 31 Dec 2024 18:14:16 +0100 Subject: [PATCH 28/89] =?UTF-8?q?Use=20last=20completed=20automatic=20back?= =?UTF-8?q?up=20time=20instead=20of=20last=20available=20ba=E2=80=A6=20(#2?= =?UTF-8?q?3522)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Use last completed automatic backup time instead of last available backup * Update ha-backup-overview-summary.ts * Update src/panels/config/backup/components/overview/ha-backup-overview-summary.ts * Update ha-config-backup-overview.ts --- .../overview/ha-backup-overview-summary.ts | 28 +++++++++++-------- .../backup/ha-config-backup-overview.ts | 2 +- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts b/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts index 198797e6a6..e6e4d78195 100644 --- a/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts +++ b/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts @@ -84,21 +84,21 @@ class HaBackupOverviewBackups extends LitElement { const lastSuccessfulBackup = this._lastSuccessfulBackup(this.backups); - const lastSuccessfulBackupDate = lastSuccessfulBackup - ? new Date(lastSuccessfulBackup.date) - : new Date(0); - const lastAttempt = this.config.last_attempted_automatic_backup ? new Date(this.config.last_attempted_automatic_backup) : undefined; + const lastCompletedBackupDate = this.config.last_completed_automatic_backup + ? new Date(this.config.last_completed_automatic_backup) + : undefined; + const now = new Date(); const lastBackupDescription = lastSuccessfulBackup - ? `Last successful backup ${relativeTime(lastSuccessfulBackupDate, this.hass.locale, now, true)} and stored in ${lastSuccessfulBackup.agent_ids?.length} locations.` + ? `Last successful backup ${relativeTime(new Date(lastSuccessfulBackup.date), this.hass.locale, now, true)} and stored in ${lastSuccessfulBackup.agent_ids?.length} locations.` : "You have no successful backups."; - if (lastAttempt && lastAttempt > lastSuccessfulBackupDate) { + if (lastAttempt && lastAttempt > (lastCompletedBackupDate || 0)) { const lastAttemptDescription = `The last automatic backup triggered ${relativeTime(lastAttempt, this.hass.locale, now, true)} wasn't successful.`; return html` + + + + ${nextBackupDescription} + + `; } - const nextBackupDescription = this._nextBackupDescription( - this.config.schedule.state - ); - const numberOfDays = differenceInDays( // Subtract a few hours to avoid showing as overdue if it's just a few hours (e.g. daylight saving) addHours(now, -OVERDUE_MARGIN_HOURS), - lastSuccessfulBackupDate + new Date(lastSuccessfulBackup.date) ); const isOverdue = diff --git a/src/panels/config/backup/ha-config-backup-overview.ts b/src/panels/config/backup/ha-config-backup-overview.ts index 0a132b799f..bc86bc8cc2 100644 --- a/src/panels/config/backup/ha-config-backup-overview.ts +++ b/src/panels/config/backup/ha-config-backup-overview.ts @@ -127,7 +127,7 @@ class HaConfigBackupOverview extends LitElement { } private get _needsOnboarding() { - return !this.config?.create_backup.password; + return this.config && !this.config.create_backup.password; } protected render(): TemplateResult { From 806cc2c608dfa1bdee0a43d2917b271d34657643 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 31 Dec 2024 17:02:57 +0100 Subject: [PATCH 29/89] Fix automation traces (#23524) --- src/common/array/ensure-array.ts | 13 +++++++++---- src/components/trace/hat-graph-node.ts | 6 +++--- src/components/trace/hat-script-graph.ts | 6 +++--- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/common/array/ensure-array.ts b/src/common/array/ensure-array.ts index 360024f6ab..dadfa73235 100644 --- a/src/common/array/ensure-array.ts +++ b/src/common/array/ensure-array.ts @@ -1,14 +1,19 @@ -type NonUndefined = T extends undefined ? never : T; +type NonNullUndefined = T extends undefined + ? never + : T extends null + ? never + : T; /** * Ensure that the input is an array or wrap it in an array * @param value - The value to ensure is an array */ export function ensureArray(value: undefined): undefined; -export function ensureArray(value: T | T[]): NonUndefined[]; -export function ensureArray(value: T | readonly T[]): NonUndefined[]; +export function ensureArray(value: null): null; +export function ensureArray(value: T | T[]): NonNullUndefined[]; +export function ensureArray(value: T | readonly T[]): NonNullUndefined[]; export function ensureArray(value) { - if (value === undefined || Array.isArray(value)) { + if (value === undefined || value === null || Array.isArray(value)) { return value; } return [value]; diff --git a/src/components/trace/hat-graph-node.ts b/src/components/trace/hat-graph-node.ts index de9e765607..994f1dc68a 100644 --- a/src/components/trace/hat-graph-node.ts +++ b/src/components/trace/hat-graph-node.ts @@ -20,8 +20,8 @@ export class HatGraphNode extends LitElement { @property({ attribute: false, reflect: true, type: Boolean }) notEnabled = false; - @property({ attribute: false, reflect: true, type: Boolean }) graphStart = - false; + @property({ attribute: "graph-start", reflect: true, type: Boolean }) + graphStart = false; @property({ type: Boolean, attribute: "nofocus" }) noFocus = false; @@ -112,7 +112,7 @@ export class HatGraphNode extends LitElement { var(--hat-graph-node-size) + var(--hat-graph-spacing) + 1px ); } - :host([graphStart]) { + :host([graph-start]) { height: calc(var(--hat-graph-node-size) + 2px); } :host([track]) { diff --git a/src/components/trace/hat-script-graph.ts b/src/components/trace/hat-script-graph.ts index e0e9c1f7d1..1bc7f057c1 100644 --- a/src/components/trace/hat-script-graph.ts +++ b/src/components/trace/hat-script-graph.ts @@ -91,7 +91,7 @@ export class HatScriptGraph extends LitElement { } return html`
Date: Tue, 31 Dec 2024 20:23:30 +0100 Subject: [PATCH 30/89] Bumped version to 20241231.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 9aab346d0b..68253ece5b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20241230.0" +version = "20241231.0" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md" From a7ef498d75ba782ec1690b426bb63a2106355d6e Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 31 Dec 2024 22:39:50 +0100 Subject: [PATCH 31/89] Handle no cloud subscription better in backups (#23523) --- .../config/ha-backup-config-agents.ts | 17 +++++++++++++++-- .../backup/dialogs/dialog-generate-backup.ts | 5 ++++- .../backup/dialogs/dialog-upload-backup.ts | 1 - 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/src/panels/config/backup/components/config/ha-backup-config-agents.ts b/src/panels/config/backup/components/config/ha-backup-config-agents.ts index de74488dcf..3a1706f3d4 100644 --- a/src/panels/config/backup/components/config/ha-backup-config-agents.ts +++ b/src/panels/config/backup/components/config/ha-backup-config-agents.ts @@ -51,6 +51,9 @@ class HaBackupConfigAgents extends LitElement { private _description(agentId: string) { if (agentId === CLOUD_AGENT) { + if (this.cloudStatus.logged_in && !this.cloudStatus.active_subscription) { + return "You currently do not have an active Home Assistant Cloud subscription."; + } return "Note: It stores only one backup with a maximum size of 5 GB, regardless of your settings."; } if (isNetworkMountAgent(agentId)) { @@ -72,6 +75,10 @@ class HaBackupConfigAgents extends LitElement { this._agentIds ); const description = this._description(agentId); + const noCloudSubscription = + agentId === CLOUD_AGENT && + this.cloudStatus.logged_in && + !this.cloudStatus.active_subscription; return html` ${isLocalAgent(agentId) @@ -107,7 +114,9 @@ class HaBackupConfigAgents extends LitElement { @@ -133,7 +142,11 @@ class HaBackupConfigAgents extends LitElement { // Ensure we don't have duplicates, agents exist in the list and cloud is logged in this.value = [...new Set(this.value)] .filter((agent) => this._agentIds.some((id) => id === agent)) - .filter((id) => id !== CLOUD_AGENT || this.cloudStatus.logged_in); + .filter( + (id) => + id !== CLOUD_AGENT || + (this.cloudStatus.logged_in && this.cloudStatus.active_subscription) + ); fireEvent(this, "value-changed", { value: this.value }); } diff --git a/src/panels/config/backup/dialogs/dialog-generate-backup.ts b/src/panels/config/backup/dialogs/dialog-generate-backup.ts index 866aaf441d..e6185d4858 100644 --- a/src/panels/config/backup/dialogs/dialog-generate-backup.ts +++ b/src/panels/config/backup/dialogs/dialog-generate-backup.ts @@ -100,7 +100,10 @@ class DialogGenerateBackup extends LitElement implements HassDialog { this._agentIds = agents .map((agent) => agent.agent_id) .filter( - (id) => id !== CLOUD_AGENT || this._params?.cloudStatus?.logged_in + (id) => + id !== CLOUD_AGENT || + (this._params?.cloudStatus?.logged_in && + this._params?.cloudStatus?.active_subscription) ) .sort(compareAgents); } diff --git a/src/panels/config/backup/dialogs/dialog-upload-backup.ts b/src/panels/config/backup/dialogs/dialog-upload-backup.ts index a9b3e9262c..ff3e454f22 100644 --- a/src/panels/config/backup/dialogs/dialog-upload-backup.ts +++ b/src/panels/config/backup/dialogs/dialog-upload-backup.ts @@ -20,7 +20,6 @@ import type { HassDialog } from "../../../../dialogs/make-dialog-manager"; import { haStyle, haStyleDialog } from "../../../../resources/styles"; import type { HomeAssistant } from "../../../../types"; import { showAlertDialog } from "../../../lovelace/custom-card-helpers"; -import "../components/ha-backup-agents-picker"; import type { UploadBackupDialogParams } from "./show-dialog-upload-backup"; const SUPPORTED_FORMAT = "application/x-tar"; From 7e80eed003f961f1070e40ccebabeaab57fd2964 Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Thu, 2 Jan 2025 04:46:36 -0800 Subject: [PATCH 32/89] Display an error if saving new script times out (#23527) * Display an error if saving new automation times out * changes * update * string tweak * Fix save failed for scripts --- .../config/automation/ha-automation-editor.ts | 7 ++- src/panels/config/script/ha-script-editor.ts | 55 +++++++++++++++---- src/translations/en.json | 4 +- 3 files changed, 52 insertions(+), 14 deletions(-) diff --git a/src/panels/config/automation/ha-automation-editor.ts b/src/panels/config/automation/ha-automation-editor.ts index e6ca397584..05df54facf 100644 --- a/src/panels/config/automation/ha-automation-editor.ts +++ b/src/panels/config/automation/ha-automation-editor.ts @@ -965,6 +965,9 @@ export class HaAutomationEditor extends PreventUnsavedMixin( type: this.hass.localize( "ui.panel.config.automation.editor.type_automation" ), + types: this.hass.localize( + "ui.panel.config.automation.editor.type_automation_plural" + ), } ), warning: true, @@ -992,9 +995,9 @@ export class HaAutomationEditor extends PreventUnsavedMixin( navigate(`/config/automation/edit/${id}`, { replace: true }); } } catch (errors: any) { - this._errors = errors.body.message || errors.error || errors.body; + this._errors = errors.body?.message || errors.error || errors.body; showToast(this, { - message: errors.body.message || errors.error || errors.body, + message: errors.body?.message || errors.error || errors.body, }); throw errors; } finally { diff --git a/src/panels/config/script/ha-script-editor.ts b/src/panels/config/script/ha-script-editor.ts index c1d9bc39ca..8f7a639a32 100644 --- a/src/panels/config/script/ha-script-editor.ts +++ b/src/panels/config/script/ha-script-editor.ts @@ -26,6 +26,7 @@ import { navigate } from "../../../common/navigate"; import { slugify } from "../../../common/string/slugify"; import { computeRTL } from "../../../common/util/compute_rtl"; import { afterNextRender } from "../../../common/util/render-status"; +import { promiseTimeout } from "../../../common/util/promise-timeout"; import "../../../components/ha-button-menu"; import "../../../components/ha-fab"; @@ -915,17 +916,49 @@ export class HaScriptEditor extends SubscribeMixin( // wait for new script to appear in entity registry if (entityRegPromise) { - const script = await entityRegPromise; - entityId = script.entity_id; + try { + const script = await promiseTimeout(2000, entityRegPromise); + entityId = script.entity_id; + } catch (e) { + entityId = undefined; + if (e instanceof Error && e.name === "TimeoutError") { + showAlertDialog(this, { + title: this.hass.localize( + "ui.panel.config.automation.editor.new_automation_setup_failed_title", + { + type: this.hass.localize( + "ui.panel.config.automation.editor.type_script" + ), + } + ), + text: this.hass.localize( + "ui.panel.config.automation.editor.new_automation_setup_failed_text", + { + type: this.hass.localize( + "ui.panel.config.automation.editor.type_script" + ), + types: this.hass.localize( + "ui.panel.config.automation.editor.type_script_plural" + ), + } + ), + warning: true, + }); + } else { + throw e; + } + } } - await updateEntityRegistryEntry(this.hass, entityId!, { - categories: { - script: this._entityRegistryUpdate.category || null, - }, - labels: this._entityRegistryUpdate.labels || [], - area_id: this._entityRegistryUpdate.area || null, - }); + if (entityId) { + await updateEntityRegistryEntry(this.hass, entityId, { + categories: { + script: this._entityRegistryUpdate.category || null, + }, + labels: this._entityRegistryUpdate.labels || [], + area_id: this._entityRegistryUpdate.area || null, + }); + } } this._dirty = false; @@ -934,9 +967,9 @@ export class HaScriptEditor extends SubscribeMixin( navigate(`/config/script/edit/${id}`, { replace: true }); } } catch (errors: any) { - this._errors = errors.body.message || errors.error || errors.body; + this._errors = errors.body?.message || errors.error || errors.body; showToast(this, { - message: errors.body.message || errors.error || errors.body, + message: errors.body?.message || errors.error || errors.body, }); throw errors; } finally { diff --git a/src/translations/en.json b/src/translations/en.json index 09a80f5f99..01f4d1436a 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -3067,8 +3067,10 @@ "switch_ui_yaml_error": "There are currently YAML errors in the automation, and it cannot be parsed. Switching to UI mode may cause pending changes to be lost. Press cancel to correct any errors before proceeding to prevent loss of pending changes, or continue if you are sure.", "type_automation": "automation", "type_script": "script", + "type_automation_plural": "[%key:ui::panel::config::blueprint::overview::types_plural::automation%]", + "type_script_plural": "[%key:ui::panel::config::blueprint::overview::types_plural::script%]", "new_automation_setup_failed_title": "New {type} setup failed", - "new_automation_setup_failed_text": "Your new {type} has saved, but waiting for it to setup has timed out. This could be due to errors parsing your configuration.yaml, please check the configuration in developer tools. Your {type} will not be visible until this is corrected, and automations are reloaded. Changes to area, category, or labels were not saved and must be reapplied.", + "new_automation_setup_failed_text": "Your new {type} has saved, but waiting for it to setup has timed out. This could be due to errors parsing your configuration.yaml, please check the configuration in developer tools. Your {type} will not be visible until this is corrected, and {types} are reloaded. Changes to area, category, or labels were not saved and must be reapplied.", "triggers": { "name": "Triggers", "header": "When", From 3b8bc242fe3b80174c7dbfdd99e0fa51232aa4e5 Mon Sep 17 00:00:00 2001 From: Philipp <84805847+insomniac2305@users.noreply.github.com> Date: Thu, 2 Jan 2025 16:03:04 +0100 Subject: [PATCH 33/89] Fix media management delete button misalignment (#23534) --- src/components/media-player/dialog-media-manage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/media-player/dialog-media-manage.ts b/src/components/media-player/dialog-media-manage.ts index a1a57933da..543adb3aa5 100644 --- a/src/components/media-player/dialog-media-manage.ts +++ b/src/components/media-player/dialog-media-manage.ts @@ -117,7 +117,7 @@ class DialogMediaManage extends LitElement { : html` Date: Thu, 2 Jan 2025 15:56:03 +0100 Subject: [PATCH 34/89] Changes to the valueText should also rescale ha-gauge text (#23536) Changes to the valueText should also recenter ha-gauge text --- src/components/ha-gauge.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ha-gauge.ts b/src/components/ha-gauge.ts index cf38f90c70..9870e30ae7 100644 --- a/src/components/ha-gauge.ts +++ b/src/components/ha-gauge.ts @@ -62,6 +62,7 @@ export class HaGauge extends LitElement { if ( !this._updated || (!changedProperties.has("value") && + !changedProperties.has("valueText") && !changedProperties.has("label") && !changedProperties.has("_segment_label")) ) { From 486038c426f4cfbe5fec96a3e5b96a4de25ead90 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 2 Jan 2025 13:44:04 +0100 Subject: [PATCH 35/89] Add space for the fab on datatable without tabs (#23545) Add space for the fab on backups datatable --- src/components/data-table/ha-data-table.ts | 9 ++++++++- src/layouts/hass-tabs-subpage.ts | 4 ++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/components/data-table/ha-data-table.ts b/src/components/data-table/ha-data-table.ts index 4278562171..af1f1f358b 100644 --- a/src/components/data-table/ha-data-table.ts +++ b/src/components/data-table/ha-data-table.ts @@ -515,7 +515,7 @@ export class HaDataTable extends LitElement { return html`
${row.content}
`; } if (row.empty) { - return html`
`; + return html`
`; } return html`
Date: Thu, 2 Jan 2025 13:44:34 +0100 Subject: [PATCH 36/89] Fix copy on button to clear the selected background image (#23546) --- src/components/ha-picture-upload.ts | 2 +- src/translations/en.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/ha-picture-upload.ts b/src/components/ha-picture-upload.ts index 8053151cdb..09796ea3e1 100644 --- a/src/components/ha-picture-upload.ts +++ b/src/components/ha-picture-upload.ts @@ -95,7 +95,7 @@ export class HaPictureUpload extends LitElement { diff --git a/src/translations/en.json b/src/translations/en.json index 01f4d1436a..e5416b9b6f 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -756,7 +756,7 @@ }, "picture-upload": { "label": "Add picture", - "change_picture": "Change picture", + "clear_picture": "Clear picture", "current_image_alt": "Current picture", "supported_formats": "Supports JPEG, PNG, or GIF image.", "unsupported_format": "Unsupported format, please choose a JPEG, PNG, or GIF image.", From 01bc45c78b89d41f0f4f5c6e648d03d3facc9f7b Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 2 Jan 2025 15:35:22 +0100 Subject: [PATCH 37/89] Backup text updates (#23547) --- .../components/overview/ha-backup-overview-onboarding.ts | 5 +---- .../components/overview/ha-backup-overview-settings.ts | 8 ++++---- .../config/backup/dialogs/dialog-backup-onboarding.ts | 2 +- .../backup/dialogs/show-dialog-backup_onboarding.ts | 1 + src/panels/config/backup/ha-config-backup-backups.ts | 2 +- src/panels/config/backup/ha-config-backup-details.ts | 8 ++++++++ src/panels/config/backup/ha-config-backup-overview.ts | 5 +++-- src/panels/config/backup/ha-config-backup-settings.ts | 2 +- 8 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/panels/config/backup/components/overview/ha-backup-overview-onboarding.ts b/src/panels/config/backup/components/overview/ha-backup-overview-onboarding.ts index ebce3ea554..a094cad22d 100644 --- a/src/panels/config/backup/components/overview/ha-backup-overview-onboarding.ts +++ b/src/panels/config/backup/components/overview/ha-backup-overview-onboarding.ts @@ -37,10 +37,7 @@ class HaBackupOverviewBackups extends LitElement {

Backups are essential for a reliable smart home. They help protect the work you've put into setting up your smart home, and if the - worst happens, you can get back up and running quickly. It is - recommended that you create a backup every day. You should keep - three backups in at least two different locations, one of which - should be off-site. + worst happens, you can get back up and running quickly.

diff --git a/src/panels/config/backup/components/overview/ha-backup-overview-settings.ts b/src/panels/config/backup/components/overview/ha-backup-overview-settings.ts index 5936937b19..3b3f56cdfb 100644 --- a/src/panels/config/backup/components/overview/ha-backup-overview-settings.ts +++ b/src/panels/config/backup/components/overview/ha-backup-overview-settings.ts @@ -34,7 +34,7 @@ class HaBackupBackupsSummary extends LitElement { const { state: schedule } = config.schedule; if (schedule === BackupScheduleState.NEVER) { - return "Automatic backups are disabled"; + return "Automatic backups are not scheduled"; } let copiesText = "and keep all backups"; @@ -116,7 +116,7 @@ class HaBackupBackupsSummary extends LitElement { return html` -
Automatic backups
+
Backup settings
- Schedule and number of backups to keep + Automatic backup schedule and retention
@@ -174,7 +174,7 @@ class HaBackupBackupsSummary extends LitElement {
- Configure automatic backups + Configure backup settings
diff --git a/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts b/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts index cb5b798c50..01f881de58 100644 --- a/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts +++ b/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts @@ -90,7 +90,7 @@ class DialogBackupOnboarding extends LitElement implements HassDialog { public showDialog(params: BackupOnboardingDialogParams): void { this._params = params; - this._step = STEPS[0]; + this._step = params.skipWelcome ? STEPS[1] : STEPS[0]; this._config = RECOMMENDED_CONFIG; const agents: string[] = []; diff --git a/src/panels/config/backup/dialogs/show-dialog-backup_onboarding.ts b/src/panels/config/backup/dialogs/show-dialog-backup_onboarding.ts index c411d6f0d7..0602077fc1 100644 --- a/src/panels/config/backup/dialogs/show-dialog-backup_onboarding.ts +++ b/src/panels/config/backup/dialogs/show-dialog-backup_onboarding.ts @@ -5,6 +5,7 @@ export interface BackupOnboardingDialogParams { submit?: (value: boolean) => void; cancel?: () => void; cloudStatus?: CloudStatus; + skipWelcome?: boolean; } const loadDialog = () => import("./dialog-backup-onboarding"); diff --git a/src/panels/config/backup/ha-config-backup-backups.ts b/src/panels/config/backup/ha-config-backup-backups.ts index a0675e70b8..18d65557f0 100644 --- a/src/panels/config/backup/ha-config-backup-backups.ts +++ b/src/panels/config/backup/ha-config-backup-backups.ts @@ -304,7 +304,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { has-fab .tabs=${[ { - translationKey: "ui.panel.config.backup.caption", + name: "My backups", path: `/config/backup/list`, }, ]} diff --git a/src/panels/config/backup/ha-config-backup-details.ts b/src/panels/config/backup/ha-config-backup-details.ts index 248913fe5e..8c42bfe69f 100644 --- a/src/panels/config/backup/ha-config-backup-details.ts +++ b/src/panels/config/backup/ha-config-backup-details.ts @@ -143,6 +143,14 @@ class HaConfigBackupDetails extends LitElement { )} Created + + + ${this._backup.protected + ? "Encrypted AES-128" + : "Not encrypted"} + + Protected +
diff --git a/src/panels/config/backup/ha-config-backup-overview.ts b/src/panels/config/backup/ha-config-backup-overview.ts index bc86bc8cc2..8080aef77b 100644 --- a/src/panels/config/backup/ha-config-backup-overview.ts +++ b/src/panels/config/backup/ha-config-backup-overview.ts @@ -73,12 +73,13 @@ class HaConfigBackupOverview extends LitElement { private _handleOnboardingButtonClick(ev) { ev.stopPropagation(); - this._setupAutomaticBackup(); + this._setupAutomaticBackup(true); } - private async _setupAutomaticBackup() { + private async _setupAutomaticBackup(skipWelcome = false) { const success = await showBackupOnboardingDialog(this, { cloudStatus: this.cloudStatus, + skipWelcome, }); if (!success) { return; diff --git a/src/panels/config/backup/ha-config-backup-settings.ts b/src/panels/config/backup/ha-config-backup-settings.ts index e4b7326ada..bb3a5f5c5f 100644 --- a/src/panels/config/backup/ha-config-backup-settings.ts +++ b/src/panels/config/backup/ha-config-backup-settings.ts @@ -91,7 +91,7 @@ class HaConfigBackupSettings extends LitElement { back-path="/config/backup" .hass=${this.hass} .narrow=${this.narrow} - .header=${"Automatic backups"} + .header=${"Backup settings"} >
From 64ad37ed6acd41ae28d0234af63ae7540eb1f80f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 2 Jan 2025 15:13:53 +0100 Subject: [PATCH 38/89] Update change encryption key dialog (#23551) --- .../dialog-change-backup-encryption-key.ts | 42 +++++++++---------- 1 file changed, 19 insertions(+), 23 deletions(-) diff --git a/src/panels/config/backup/dialogs/dialog-change-backup-encryption-key.ts b/src/panels/config/backup/dialogs/dialog-change-backup-encryption-key.ts index 3e140c60f7..c95fc077c1 100644 --- a/src/panels/config/backup/dialogs/dialog-change-backup-encryption-key.ts +++ b/src/panels/config/backup/dialogs/dialog-change-backup-encryption-key.ts @@ -92,7 +92,9 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog { ? "Save current encryption key" : this._step === "new" ? "New encryption key" - : ""; + : this._step === "done" + ? "Save new encryption key" + : ""; return html` @@ -166,10 +168,22 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog { case "new": return html`

- Keep this encryption key in a safe place, as you will need it to - access your backup, allowing it to be restored. Either record the + All next backups will use the new encryption key. Encryption keeps + your backups private and secure. +

+
+

${this._newEncryptionKey}

+ +
+ `; + case "done": + return html`

+ Keep this new encryption key in a safe place, as you will need it to + access your backups, allowing it to be restored. Either record the characters below or download them as an emergency kit file. - Encryption keeps your backups private and secure.

${this._newEncryptionKey}

@@ -189,18 +203,7 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog { Download - - `; - case "done": - return html` -
- Casita Home Assistant logo -

Encryption key changed

-
- `; + `; } return nothing; } @@ -303,13 +306,6 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog { p { margin-top: 0; } - .done { - text-align: center; - font-size: 22px; - font-style: normal; - font-weight: 400; - line-height: 28px; - } `, ]; } From be967940a2d967e5c5ecd2a1d6409e80277f238c Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 2 Jan 2025 15:13:42 +0100 Subject: [PATCH 39/89] Add warning when no backup location is selected (#23550) * Add warning when no backup location is selected * Move to bottom --- src/panels/config/backup/ha-config-backup-settings.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/panels/config/backup/ha-config-backup-settings.ts b/src/panels/config/backup/ha-config-backup-settings.ts index bb3a5f5c5f..8ec6ff1257 100644 --- a/src/panels/config/backup/ha-config-backup-settings.ts +++ b/src/panels/config/backup/ha-config-backup-settings.ts @@ -8,6 +8,7 @@ import { nextRender } from "../../../common/util/render-status"; import "../../../components/ha-button"; import "../../../components/ha-card"; import "../../../components/ha-icon-next"; +import "../../../components/ha-alert"; import "../../../components/ha-password-field"; import type { BackupConfig } from "../../../data/backup"; import { updateBackupConfig } from "../../../data/backup"; @@ -134,6 +135,14 @@ class HaConfigBackupSettings extends LitElement { .cloudStatus=${this.cloudStatus} @value-changed=${this._agentsConfigChanged} > + ${!this._config.create_backup.agent_ids.length + ? html`You have to select at least one location to create a + backup.
` + : nothing}
From e03dc2c382a2df259389f5a47fd88201cea53f75 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 2 Jan 2025 16:04:17 +0100 Subject: [PATCH 40/89] Move local location backup setting (#23548) --- .../backup/ha-config-backup-overview.ts | 53 +++++-------------- .../backup/ha-config-backup-settings.ts | 37 +++++++++++++ src/translations/en.json | 2 +- 3 files changed, 50 insertions(+), 42 deletions(-) diff --git a/src/panels/config/backup/ha-config-backup-overview.ts b/src/panels/config/backup/ha-config-backup-overview.ts index 8080aef77b..c48e3f0034 100644 --- a/src/panels/config/backup/ha-config-backup-overview.ts +++ b/src/panels/config/backup/ha-config-backup-overview.ts @@ -1,8 +1,7 @@ -import { mdiDotsVertical, mdiHarddisk, mdiPlus, mdiUpload } from "@mdi/js"; +import { mdiDotsVertical, mdiPlus, mdiUpload } from "@mdi/js"; import type { CSSResultGroup, TemplateResult } from "lit"; import { css, html, LitElement, nothing } from "lit"; import { customElement, property } from "lit/decorators"; -import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { fireEvent } from "../../../common/dom/fire_event"; import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event"; import "../../../components/ha-button"; @@ -33,7 +32,6 @@ import "./components/overview/ha-backup-overview-settings"; import "./components/overview/ha-backup-overview-summary"; import { showBackupOnboardingDialog } from "./dialogs/show-dialog-backup_onboarding"; import { showGenerateBackupDialog } from "./dialogs/show-dialog-generate-backup"; -import { showLocalBackupLocationDialog } from "./dialogs/show-dialog-local-backup-location"; import { showNewBackupDialog } from "./dialogs/show-dialog-new-backup"; import { showUploadBackupDialog } from "./dialogs/show-dialog-upload-backup"; @@ -63,14 +61,6 @@ class HaConfigBackupOverview extends LitElement { await showUploadBackupDialog(this, {}); } - private async _changeLocalLocation(ev) { - if (!shouldHandleRequestSelectedEvent(ev)) { - return; - } - - showLocalBackupLocationDialog(this, {}); - } - private _handleOnboardingButtonClick(ev) { ev.stopPropagation(); this._setupAutomaticBackup(true); @@ -135,8 +125,6 @@ class HaConfigBackupOverview extends LitElement { const backupInProgress = "state" in this.manager && this.manager.state === "in_progress"; - const isHassio = isComponentLoaded(this.hass, "hassio"); - return html` -
- - - ${isHassio - ? html` - - Change local location - ` - : nothing} - - - Upload backup - - -
+ + + + + Upload backup + +
${backupInProgress ? html` diff --git a/src/panels/config/backup/ha-config-backup-settings.ts b/src/panels/config/backup/ha-config-backup-settings.ts index 8ec6ff1257..f581f5cd92 100644 --- a/src/panels/config/backup/ha-config-backup-settings.ts +++ b/src/panels/config/backup/ha-config-backup-settings.ts @@ -1,15 +1,21 @@ +import { mdiDotsVertical, mdiHarddisk } from "@mdi/js"; import type { PropertyValues } from "lit"; import { css, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { fireEvent } from "../../../common/dom/fire_event"; +import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event"; import { debounce } from "../../../common/util/debounce"; import { nextRender } from "../../../common/util/render-status"; import "../../../components/ha-button"; +import "../../../components/ha-button-menu"; import "../../../components/ha-card"; +import "../../../components/ha-icon-button"; import "../../../components/ha-icon-next"; +import "../../../components/ha-list-item"; import "../../../components/ha-alert"; import "../../../components/ha-password-field"; +import "../../../components/ha-svg-icon"; import type { BackupConfig } from "../../../data/backup"; import { updateBackupConfig } from "../../../data/backup"; import type { CloudStatus } from "../../../data/cloud"; @@ -21,6 +27,7 @@ import type { BackupConfigData } from "./components/config/ha-backup-config-data import "./components/config/ha-backup-config-encryption-key"; import "./components/config/ha-backup-config-schedule"; import type { BackupConfigSchedule } from "./components/config/ha-backup-config-schedule"; +import { showLocalBackupLocationDialog } from "./dialogs/show-dialog-local-backup-location"; @customElement("ha-config-backup-settings") class HaConfigBackupSettings extends LitElement { @@ -94,6 +101,28 @@ class HaConfigBackupSettings extends LitElement { .narrow=${this.narrow} .header=${"Backup settings"} > + ${isComponentLoaded(this.hass, "hassio") + ? html` + + + + + Change default action location + + + ` + : nothing} +
Automatic backups
@@ -166,6 +195,14 @@ class HaConfigBackupSettings extends LitElement { `; } + private async _changeLocalLocation(ev) { + if (!shouldHandleRequestSelectedEvent(ev)) { + return; + } + + showLocalBackupLocationDialog(this, {}); + } + private _scheduleConfigChanged(ev) { const value = ev.detail.value as BackupConfigSchedule; this._config = { diff --git a/src/translations/en.json b/src/translations/en.json index e5416b9b6f..688df34b20 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2215,7 +2215,7 @@ }, "dialogs": { "local_backup_location": { - "title": "Change local backup location", + "title": "Change default local backup location", "description": "Change the default location where local backups are stored on your Home Assistant instance.", "note": "This location will be used when you create a backup using the supervisor actions in an automation for example.", "options": { From fcc9da6d85037e9f94d16ac96c1233ee8410ce20 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 2 Jan 2025 15:38:11 +0100 Subject: [PATCH 41/89] Backup with db requires config, disabled next if no data is selected (#23549) --- .../config/ha-backup-config-data.ts | 8 +++---- .../backup/dialogs/dialog-generate-backup.ts | 20 ++++++++++++++++- .../backup/ha-config-backup-overview.ts | 22 ++++++++++--------- 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/panels/config/backup/components/config/ha-backup-config-data.ts b/src/panels/config/backup/components/config/ha-backup-config-data.ts index be462ff3e5..5c3dce968b 100644 --- a/src/panels/config/backup/components/config/ha-backup-config-data.ts +++ b/src/panels/config/backup/components/config/ha-backup-config-data.ts @@ -138,7 +138,8 @@ class HaBackupConfigData extends LitElement { const include_addons = data.addons_mode === "custom" ? data.addons : []; this.value = { - include_homeassistant: data.homeassistant || this.forceHomeAssistant, + include_homeassistant: + data.homeassistant || data.database || this.forceHomeAssistant, include_addons: include_addons.length ? include_addons : undefined, include_all_addons: data.addons_mode === "all", include_database: data.database, @@ -168,7 +169,7 @@ class HaBackupConfigData extends LitElement { slot="end" @change=${this._switchChanged} .checked=${data.homeassistant} - .disabled=${this.forceHomeAssistant} + .disabled=${this.forceHomeAssistant || data.database} > @@ -296,7 +297,6 @@ class HaBackupConfigData extends LitElement { ...data, [target.id]: target.checked, }); - fireEvent(this, "value-changed", { value: this.value }); } private _selectChanged(ev: Event) { @@ -309,7 +309,6 @@ class HaBackupConfigData extends LitElement { if (target.id === "addons_mode") { this._showAddons = target.value === "custom"; } - fireEvent(this, "value-changed", { value: this.value }); } private _addonsChanged(ev: CustomEvent) { @@ -320,7 +319,6 @@ class HaBackupConfigData extends LitElement { ...data, addons, }); - fireEvent(this, "value-changed", { value: this.value }); } static styles = css` diff --git a/src/panels/config/backup/dialogs/dialog-generate-backup.ts b/src/panels/config/backup/dialogs/dialog-generate-backup.ts index e6185d4858..b8c64c1255 100644 --- a/src/panels/config/backup/dialogs/dialog-generate-backup.ts +++ b/src/panels/config/backup/dialogs/dialog-generate-backup.ts @@ -209,12 +209,30 @@ class DialogGenerateBackup extends LitElement implements HassDialog { Create backup ` - : html`Next`} + : html`Next`}
`; } + private get _noDataSelected() { + const hassio = isComponentLoaded(this.hass, "hassio"); + if ( + this._formData?.data.include_homeassistant || + this._formData?.data.include_database || + (hassio && this._formData?.data.include_folders?.length) || + (hassio && this._formData?.data.include_all_addons) || + (hassio && this._formData?.data.include_addons?.length) + ) { + return false; + } + return true; + } + private _renderData() { if (!this._formData) { return nothing; diff --git a/src/panels/config/backup/ha-config-backup-overview.ts b/src/panels/config/backup/ha-config-backup-overview.ts index c48e3f0034..26e86a2105 100644 --- a/src/panels/config/backup/ha-config-backup-overview.ts +++ b/src/panels/config/backup/ha-config-backup-overview.ts @@ -160,22 +160,24 @@ class HaConfigBackupOverview extends LitElement { > ` - : html` - - - `} + : this.config + ? html` + + + ` + : nothing} - ${!this._needsOnboarding + ${!this._needsOnboarding && this.config ? html` Date: Thu, 2 Jan 2025 16:12:31 +0100 Subject: [PATCH 42/89] Add show encryption key dialog (#23552) --- .../config/ha-backup-config-encryption-key.ts | 13 +- .../dialog-show-backup-encryption-key.ts | 174 ++++++++++++++++++ .../show-dialog-show-backup-encryption-key.ts | 17 ++ 3 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 src/panels/config/backup/dialogs/dialog-show-backup-encryption-key.ts create mode 100644 src/panels/config/backup/dialogs/show-dialog-show-backup-encryption-key.ts diff --git a/src/panels/config/backup/components/config/ha-backup-config-encryption-key.ts b/src/panels/config/backup/components/config/ha-backup-config-encryption-key.ts index 0298a6c5bf..4254215d55 100644 --- a/src/panels/config/backup/components/config/ha-backup-config-encryption-key.ts +++ b/src/panels/config/backup/components/config/ha-backup-config-encryption-key.ts @@ -9,6 +9,7 @@ import { showChangeBackupEncryptionKeyDialog } from "../../dialogs/show-dialog-c import { showSetBackupEncryptionKeyDialog } from "../../dialogs/show-dialog-set-backup-encryption-key"; import { downloadEmergencyKit } from "../../../../../data/backup"; +import { showShowBackupEncryptionKeyDialog } from "../../dialogs/show-dialog-show-backup-encryption-key"; @customElement("ha-backup-config-encryption-key") class HaBackupConfigEncryptionKey extends LitElement { @@ -34,7 +35,13 @@ class HaBackupConfigEncryptionKey extends LitElement { Download - + + Show my encryption key + + Please keep your encryption key private. + + Show + Change encryption key @@ -68,6 +75,10 @@ class HaBackupConfigEncryptionKey extends LitElement { downloadEmergencyKit(this.hass, this._value); } + private _show() { + showShowBackupEncryptionKeyDialog(this, { currentKey: this._value }); + } + private _change() { showChangeBackupEncryptionKeyDialog(this, { currentKey: this._value, diff --git a/src/panels/config/backup/dialogs/dialog-show-backup-encryption-key.ts b/src/panels/config/backup/dialogs/dialog-show-backup-encryption-key.ts new file mode 100644 index 0000000000..5861ad35a7 --- /dev/null +++ b/src/panels/config/backup/dialogs/dialog-show-backup-encryption-key.ts @@ -0,0 +1,174 @@ +import { mdiClose, mdiContentCopy, mdiDownload } from "@mdi/js"; +import type { CSSResultGroup } from "lit"; +import { LitElement, css, html, nothing } from "lit"; +import { customElement, property, query, state } from "lit/decorators"; +import { fireEvent } from "../../../../common/dom/fire_event"; +import { copyToClipboard } from "../../../../common/util/copy-clipboard"; +import "../../../../components/ha-button"; +import "../../../../components/ha-dialog-header"; +import "../../../../components/ha-icon-button"; +import "../../../../components/ha-icon-button-prev"; +import "../../../../components/ha-md-dialog"; +import type { HaMdDialog } from "../../../../components/ha-md-dialog"; +import "../../../../components/ha-md-list"; +import "../../../../components/ha-md-list-item"; +import "../../../../components/ha-password-field"; +import { downloadEmergencyKit } from "../../../../data/backup"; +import type { HassDialog } from "../../../../dialogs/make-dialog-manager"; +import { haStyle, haStyleDialog } from "../../../../resources/styles"; +import type { HomeAssistant } from "../../../../types"; +import { showToast } from "../../../../util/toast"; +import type { ShowBackupEncryptionKeyDialogParams } from "./show-dialog-show-backup-encryption-key"; + +@customElement("ha-dialog-show-backup-encryption-key") +class DialogShowBackupEncryptionKey extends LitElement implements HassDialog { + @property({ attribute: false }) public hass!: HomeAssistant; + + @state() private _params?: ShowBackupEncryptionKeyDialogParams; + + @query("ha-md-dialog") private _dialog!: HaMdDialog; + + public showDialog(params: ShowBackupEncryptionKeyDialogParams): void { + this._params = params; + } + + public closeDialog(): void { + this._params = undefined; + fireEvent(this, "dialog-closed", { dialog: this.localName }); + } + + private _closeDialog() { + this._dialog.close(); + } + + protected render() { + if (!this._params) { + return nothing; + } + + return html` + + + + Encryption key + +
+

+ Make sure you save the encryption key in a secure place so always + have access to your backups. +

+
+

${this._params?.currentKey}

+ +
+ + + Download emergency kit + + We recommend saving this encryption key file somewhere secure. + + + + Download + + + +
+
+ Close +
+
+ `; + } + + private async _copyKeyToClipboard() { + if (!this._params?.currentKey) { + return; + } + await copyToClipboard( + this._params?.currentKey, + this.renderRoot.querySelector("div")! + ); + showToast(this, { + message: this.hass.localize("ui.common.copied_clipboard"), + }); + } + + private _download() { + if (!this._params?.currentKey) { + return; + } + downloadEmergencyKit(this.hass, this._params.currentKey, "old"); + } + + static get styles(): CSSResultGroup { + return [ + haStyle, + haStyleDialog, + css` + ha-md-dialog { + width: 90vw; + max-width: 560px; + --dialog-content-padding: 8px 24px; + } + ha-md-list { + background: none; + --md-list-item-leading-space: 0; + --md-list-item-trailing-space: 0; + } + ha-button.danger { + --mdc-theme-primary: var(--error-color); + } + .encryption-key { + border: 1px solid var(--divider-color); + background-color: var(--primary-background-color); + border-radius: 8px; + padding: 16px; + display: flex; + flex-direction: row; + align-items: center; + gap: 24px; + } + .encryption-key p { + margin: 0; + flex: 1; + font-family: "Roboto Mono", "Consolas", "Menlo", monospace; + font-size: 20px; + font-style: normal; + font-weight: 400; + line-height: 28px; + text-align: center; + } + .encryption-key ha-icon-button { + flex: none; + margin: -16px; + } + @media all and (max-width: 450px), all and (max-height: 500px) { + ha-md-dialog { + max-width: none; + } + div[slot="content"] { + margin-top: 0; + } + } + p { + margin-top: 0; + } + `, + ]; + } +} + +declare global { + interface HTMLElementTagNameMap { + "ha-dialog-show-backup-encryption-key": DialogShowBackupEncryptionKey; + } +} diff --git a/src/panels/config/backup/dialogs/show-dialog-show-backup-encryption-key.ts b/src/panels/config/backup/dialogs/show-dialog-show-backup-encryption-key.ts new file mode 100644 index 0000000000..e69d7ae774 --- /dev/null +++ b/src/panels/config/backup/dialogs/show-dialog-show-backup-encryption-key.ts @@ -0,0 +1,17 @@ +import { fireEvent } from "../../../../common/dom/fire_event"; + +export interface ShowBackupEncryptionKeyDialogParams { + currentKey: string; +} + +const loadDialog = () => import("./dialog-show-backup-encryption-key"); + +export const showShowBackupEncryptionKeyDialog = ( + element: HTMLElement, + params?: ShowBackupEncryptionKeyDialogParams +) => + fireEvent(element, "show-dialog", { + dialogTag: "ha-dialog-show-backup-encryption-key", + dialogImport: loadDialog, + dialogParams: params, + }); From 8f58681d83f6243bef1d28c198ebcba25944caa5 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 2 Jan 2025 16:11:50 +0100 Subject: [PATCH 43/89] always zoom timeline charts on x axis (#23554) --- src/components/chart/ha-chart-base.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts index c3aab89e82..017a5dab07 100644 --- a/src/components/chart/ha-chart-base.ts +++ b/src/components/chart/ha-chart-base.ts @@ -433,7 +433,10 @@ export class HaChartBase extends LitElement { speed: 0.05, }, mode: - (this.options?.scales?.y as any)?.type === "category" ? "y" : "x", + this.chartType !== "timeline" && + (this.options?.scales?.y as any)?.type === "category" + ? "y" + : "x", onZoomComplete: () => { const isZoomed = this.chart?.isZoomedOrPanned() ?? false; if (this._isZoomed && !isZoomed) { From 6c9df587e79501851fa42c977eb97f287725b5e0 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 2 Jan 2025 16:18:11 +0100 Subject: [PATCH 44/89] Bumped version to 20250102.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 68253ece5b..bfbc1ef922 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20241231.0" +version = "20250102.0" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md" From 70953788ccb4103a99909e0028a3691249bceb60 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 2 Jan 2025 16:54:35 +0100 Subject: [PATCH 45/89] Add back zopfli compression (#23555) --- build-scripts/gulp/compress.js | 61 +++++++++++++++++++++++------- package.json | 1 + yarn.lock | 69 +++++++++++++++++++++++++++++++--- 3 files changed, 112 insertions(+), 19 deletions(-) diff --git a/build-scripts/gulp/compress.js b/build-scripts/gulp/compress.js index e1fff793a6..81e1c87abe 100644 --- a/build-scripts/gulp/compress.js +++ b/build-scripts/gulp/compress.js @@ -3,6 +3,7 @@ import { constants } from "node:zlib"; import gulp from "gulp"; import brotli from "gulp-brotli"; +import zopfli from "gulp-zopfli-green"; import paths from "../paths.cjs"; const filesGlob = "*.{js,json,css,svg,xml}"; @@ -12,17 +13,18 @@ const brotliOptions = { [constants.BROTLI_PARAM_QUALITY]: constants.BROTLI_MAX_QUALITY, }, }; +const zopfliOptions = { threshold: 150 }; -const compressModern = (rootDir, modernDir) => +const compressModern = (rootDir, modernDir, compress) => gulp .src([`${modernDir}/**/${filesGlob}`, `${rootDir}/sw-modern.js`], { base: rootDir, allowEmpty: true, }) - .pipe(brotli(brotliOptions)) + .pipe(compress === "zopfli" ? zopfli(zopfliOptions) : brotli(brotliOptions)) .pipe(gulp.dest(rootDir)); -const compressOther = (rootDir, modernDir) => +const compressOther = (rootDir, modernDir, compress) => gulp .src( [ @@ -33,21 +35,52 @@ const compressOther = (rootDir, modernDir) => ], { base: rootDir, allowEmpty: true } ) - .pipe(brotli(brotliOptions)) + .pipe(compress === "zopfli" ? zopfli(zopfliOptions) : brotli(brotliOptions)) .pipe(gulp.dest(rootDir)); -const compressAppModern = () => - compressModern(paths.app_output_root, paths.app_output_latest); -const compressHassioModern = () => - compressModern(paths.hassio_output_root, paths.hassio_output_latest); +const compressAppModernBrotli = () => + compressModern(paths.app_output_root, paths.app_output_latest, "brotli"); +const compressAppModernZopfli = () => + compressModern(paths.app_output_root, paths.app_output_latest, "zopfli"); -const compressAppOther = () => - compressOther(paths.app_output_root, paths.app_output_latest); -const compressHassioOther = () => - compressOther(paths.hassio_output_root, paths.hassio_output_latest); +const compressHassioModernBrotli = () => + compressModern( + paths.hassio_output_root, + paths.hassio_output_latest, + "brotli" + ); +const compressHassioModernZopfli = () => + compressModern( + paths.hassio_output_root, + paths.hassio_output_latest, + "zopfli" + ); -gulp.task("compress-app", gulp.parallel(compressAppModern, compressAppOther)); +const compressAppOtherBrotli = () => + compressOther(paths.app_output_root, paths.app_output_latest, "brotli"); +const compressAppOtherZopfli = () => + compressOther(paths.app_output_root, paths.app_output_latest, "zopfli"); + +const compressHassioOtherBrotli = () => + compressOther(paths.hassio_output_root, paths.hassio_output_latest, "brotli"); +const compressHassioOtherZopfli = () => + compressOther(paths.hassio_output_root, paths.hassio_output_latest, "zopfli"); + +gulp.task( + "compress-app", + gulp.parallel( + compressAppModernBrotli, + compressAppOtherBrotli, + compressAppModernZopfli, + compressAppOtherZopfli + ) +); gulp.task( "compress-hassio", - gulp.parallel(compressHassioModern, compressHassioOther) + gulp.parallel( + compressHassioModernBrotli, + compressHassioOtherBrotli, + compressHassioModernZopfli, + compressHassioOtherZopfli + ) ); diff --git a/package.json b/package.json index bb9054c920..8e08c3ecce 100644 --- a/package.json +++ b/package.json @@ -115,6 +115,7 @@ "element-internals-polyfill": "1.3.12", "fuse.js": "7.0.0", "google-timezones-json": "1.2.0", + "gulp-zopfli-green": "6.0.2", "hls.js": "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch", "home-assistant-js-websocket": "9.4.0", "idb-keyval": "6.2.1", diff --git a/yarn.lock b/yarn.lock index 385d2ed0c4..e6e34683df 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1801,6 +1801,15 @@ __metadata: languageName: node linkType: hard +"@gfx/zopfli@npm:^1.0.15": + version: 1.0.15 + resolution: "@gfx/zopfli@npm:1.0.15" + dependencies: + base64-js: "npm:^1.3.0" + checksum: 10/2721ad8c0cbbdac7d5ca9e01ad05f232b4e3cdcecf88f9b0ef9a2bdc7d05e1ca54ea68905430cf36338ef1077acec178aa7b258c67baa7a4c2b6d74067605723 + languageName: node + linkType: hard + "@gulpjs/messages@npm:^1.1.0": version: 1.1.0 resolution: "@gulpjs/messages@npm:1.1.0" @@ -5684,6 +5693,13 @@ __metadata: languageName: node linkType: hard +"any-promise@npm:^1.1.0": + version: 1.3.0 + resolution: "any-promise@npm:1.3.0" + checksum: 10/6737469ba353b5becf29e4dc3680736b9caa06d300bda6548812a8fee63ae7d336d756f88572fa6b5219aed36698d808fa55f62af3e7e6845c7a1dc77d240edb + languageName: node + linkType: hard + "anymatch@npm:^3.1.3, anymatch@npm:~3.1.2": version: 3.1.3 resolution: "anymatch@npm:3.1.3" @@ -6030,7 +6046,7 @@ __metadata: languageName: node linkType: hard -"base64-js@npm:^1.3.1": +"base64-js@npm:^1.3.0, base64-js@npm:^1.3.1": version: 1.5.1 resolution: "base64-js@npm:1.5.1" checksum: 10/669632eb3745404c2f822a18fc3a0122d2f9a7a13f7fb8b5823ee19d1d2ff9ee5b52c53367176ea4ad093c332fd5ab4bd0ebae5a8e27917a4105a4cfc86b1005 @@ -6237,7 +6253,7 @@ __metadata: languageName: node linkType: hard -"bytes@npm:3.1.2": +"bytes@npm:3.1.2, bytes@npm:^3.1.2": version: 3.1.2 resolution: "bytes@npm:3.1.2" checksum: 10/a10abf2ba70c784471d6b4f58778c0beeb2b5d405148e66affa91f23a9f13d07603d0a0354667310ae1d6dc141474ffd44e2a074be0f6e2254edb8fc21445388 @@ -7070,7 +7086,7 @@ __metadata: languageName: node linkType: hard -"defaults@npm:^1.0.3": +"defaults@npm:^1.0.3, defaults@npm:^1.0.4": version: 1.0.4 resolution: "defaults@npm:1.0.4" dependencies: @@ -8180,7 +8196,7 @@ __metadata: languageName: node linkType: hard -"fancy-log@npm:2.0.0": +"fancy-log@npm:2.0.0, fancy-log@npm:^2.0.0": version: 2.0.0 resolution: "fancy-log@npm:2.0.0" dependencies: @@ -8973,6 +8989,21 @@ __metadata: languageName: node linkType: hard +"gulp-zopfli-green@npm:6.0.2": + version: 6.0.2 + resolution: "gulp-zopfli-green@npm:6.0.2" + dependencies: + "@gfx/zopfli": "npm:^1.0.15" + bytes: "npm:^3.1.2" + defaults: "npm:^1.0.4" + fancy-log: "npm:^2.0.0" + plugin-error: "npm:^2.0.1" + stream-to-array: "npm:^2.3.0" + through2: "npm:^4.0.2" + checksum: 10/52e899dfb86777ff8f97a23af99c59e203ea485fbf04d0a8f4f1cfbd4d4c496808a3593ae8dac16584fc4b4d81cf127b2eda5355a61bcc213875c95cc86d41da + languageName: node + linkType: hard + "gulp@npm:5.0.0": version: 5.0.0 resolution: "gulp@npm:5.0.0" @@ -9254,6 +9285,7 @@ __metadata: gulp-brotli: "npm:3.0.0" gulp-json-transform: "npm:0.5.0" gulp-rename: "npm:2.0.0" + gulp-zopfli-green: "npm:6.0.2" hls.js: "patch:hls.js@npm%3A1.5.7#~/.yarn/patches/hls.js-npm-1.5.7-f5bbd3d060.patch" home-assistant-js-websocket: "npm:9.4.0" html-minifier-terser: "npm:7.2.0" @@ -12161,6 +12193,15 @@ __metadata: languageName: node linkType: hard +"plugin-error@npm:^2.0.1": + version: 2.0.1 + resolution: "plugin-error@npm:2.0.1" + dependencies: + ansi-colors: "npm:^1.0.1" + checksum: 10/9a4f91461cd24cce401112098969991d7aa6b4c94f78e0381234280c07da779570a8b21ab143292b534ec0117c09705a67e5d756c1c303d4706fdd7f861bf5bc + languageName: node + linkType: hard + "pngjs@npm:^3.0.0, pngjs@npm:^3.3.3": version: 3.4.0 resolution: "pngjs@npm:3.4.0" @@ -12383,7 +12424,7 @@ __metadata: languageName: node linkType: hard -"readable-stream@npm:2 || 3, readable-stream@npm:^3.0.6, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0": +"readable-stream@npm:2 || 3, readable-stream@npm:3, readable-stream@npm:^3.0.6, readable-stream@npm:^3.4.0, readable-stream@npm:^3.6.0": version: 3.6.2 resolution: "readable-stream@npm:3.6.2" dependencies: @@ -13566,6 +13607,15 @@ __metadata: languageName: node linkType: hard +"stream-to-array@npm:^2.3.0": + version: 2.3.0 + resolution: "stream-to-array@npm:2.3.0" + dependencies: + any-promise: "npm:^1.1.0" + checksum: 10/7feaf63b38399b850615e6ffcaa951e96e4c8f46745dbce4b553a94c5dc43966933813747014935a3ff97793e7f30a65270bde19f82b2932871a1879229a77cf + languageName: node + linkType: hard + "streamx@npm:^2.12.0, streamx@npm:^2.12.5, streamx@npm:^2.13.2, streamx@npm:^2.14.0": version: 2.21.1 resolution: "streamx@npm:2.21.1" @@ -13993,6 +14043,15 @@ __metadata: languageName: node linkType: hard +"through2@npm:^4.0.2": + version: 4.0.2 + resolution: "through2@npm:4.0.2" + dependencies: + readable-stream: "npm:3" + checksum: 10/72c246233d9a989bbebeb6b698ef0b7b9064cb1c47930f79b25d87b6c867e075432811f69b7b2ac8da00ca308191c507bdab913944be8019ac43b036ce88f6ba + languageName: node + linkType: hard + "thunky@npm:^1.0.2": version: 1.1.0 resolution: "thunky@npm:1.1.0" From fc3e99e7946c6796c47f92fe05e5f03b147487b5 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 2 Jan 2025 17:18:34 +0100 Subject: [PATCH 46/89] Update and add backup my links (#23556) * Update and add backup my links --- src/panels/my/ha-panel-my.ts | 30 ++++++++++++++++++------------ src/state/quick-bar-mixin.ts | 4 +--- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/src/panels/my/ha-panel-my.ts b/src/panels/my/ha-panel-my.ts index cab1cfbafb..c2ae6a8e72 100644 --- a/src/panels/my/ha-panel-my.ts +++ b/src/panels/my/ha-panel-my.ts @@ -16,7 +16,7 @@ import "../../layouts/hass-error-screen"; import type { HomeAssistant, Route } from "../../types"; import { documentationUrl } from "../../util/documentation-url"; -export const getMyRedirects = (hasSupervisor: boolean): Redirects => ({ +export const getMyRedirects = (): Redirects => ({ application_credentials: { redirect: "/config/application_credentials", }, @@ -244,16 +244,24 @@ export const getMyRedirects = (hasSupervisor: boolean): Redirects => ({ redirect: "/media-browser", }, backup: { - component: hasSupervisor ? "hassio" : "backup", - redirect: hasSupervisor ? "/hassio/backups" : "/config/backup", + component: "backup", + redirect: "/config/backup", + }, + backup_list: { + component: "backup", + redirect: "/config/backup/backups", + }, + backup_config: { + component: "backup", + redirect: "/config/backup/settings", }, supervisor_snapshots: { - component: hasSupervisor ? "hassio" : "backup", - redirect: hasSupervisor ? "/hassio/backups" : "/config/backup", + component: "backup", + redirect: "/config/backup", }, supervisor_backups: { - component: hasSupervisor ? "hassio" : "backup", - redirect: hasSupervisor ? "/hassio/backups" : "/config/backup", + component: "backup", + redirect: "/config/backup", }, supervisor_system: { // Moved from Supervisor panel in 2022.5 @@ -278,10 +286,8 @@ export const getMyRedirects = (hasSupervisor: boolean): Redirects => ({ }, }); -const getRedirect = ( - path: string, - hasSupervisor: boolean -): Redirect | undefined => getMyRedirects(hasSupervisor)?.[path]; +const getRedirect = (path: string): Redirect | undefined => + getMyRedirects()?.[path]; export type ParamType = "url" | "string" | "string?"; @@ -314,7 +320,7 @@ class HaPanelMy extends LitElement { const path = this.route.path.substring(1); const hasSupervisor = isComponentLoaded(this.hass, "hassio"); - this._redirect = getRedirect(path, hasSupervisor); + this._redirect = getRedirect(path); if (path.startsWith("supervisor") && this._redirect === undefined) { if (!hasSupervisor) { diff --git a/src/state/quick-bar-mixin.ts b/src/state/quick-bar-mixin.ts index 30a96e7e93..daea444079 100644 --- a/src/state/quick-bar-mixin.ts +++ b/src/state/quick-bar-mixin.ts @@ -150,9 +150,7 @@ export default >(superClass: T) => const myPanel = await import("../panels/my/ha-panel-my"); - for (const [slug, redirect] of Object.entries( - myPanel.getMyRedirects(isHassio) - )) { + for (const [slug, redirect] of Object.entries(myPanel.getMyRedirects())) { if (targetPath.startsWith(redirect.redirect)) { myParams.append("redirect", slug); if (redirect.params) { From 102a9eeb619645375edf741fa0e4db51c6f4ecba Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 3 Jan 2025 11:08:00 +0100 Subject: [PATCH 47/89] Fix tabs subpage height on desktop (#23564) fix tabs subpage height on desktop --- src/layouts/hass-tabs-subpage.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/layouts/hass-tabs-subpage.ts b/src/layouts/hass-tabs-subpage.ts index 4c4d1e3cdc..ca930a01b5 100644 --- a/src/layouts/hass-tabs-subpage.ts +++ b/src/layouts/hass-tabs-subpage.ts @@ -314,10 +314,6 @@ class HassTabsSubpage extends LitElement { width: calc( 100% - env(safe-area-inset-left) - env(safe-area-inset-right) ); - height: calc(100% - var(--header-height)); - height: calc( - 100% - var(--header-height) - env(safe-area-inset-bottom) - ); margin-left: env(safe-area-inset-left); margin-right: env(safe-area-inset-right); margin-inline-start: env(safe-area-inset-left); @@ -326,6 +322,13 @@ class HassTabsSubpage extends LitElement { -webkit-overflow-scrolling: touch; } + :host([narrow]) .content { + height: calc(100% - var(--header-height)); + height: calc( + 100% - var(--header-height) - env(safe-area-inset-bottom) + ); + } + :host([narrow]) .content.tabs { height: calc(100% - 2 * var(--header-height)); height: calc( From d126e02747438e638099871c3295747cf06829e6 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 3 Jan 2025 11:17:27 +0100 Subject: [PATCH 48/89] fix error display upload backup (#23565) --- .../config/backup/dialogs/dialog-upload-backup.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/panels/config/backup/dialogs/dialog-upload-backup.ts b/src/panels/config/backup/dialogs/dialog-upload-backup.ts index ff3e454f22..e1258a0f51 100644 --- a/src/panels/config/backup/dialogs/dialog-upload-backup.ts +++ b/src/panels/config/backup/dialogs/dialog-upload-backup.ts @@ -89,6 +89,9 @@ export class DialogUploadBackup Upload backup
+ ${this._error + ? html`${this._error}` + : nothing} - ${this._error - ? html`${this._error}` - : nothing}
Cancel @@ -160,6 +160,10 @@ export class DialogUploadBackup max-width: 500px; max-height: 100%; } + ha-alert { + display: block; + margin-bottom: 16px; + } `, ]; } From 4ea0c83fbe358e6ab80f9a285b48b9572cdf8f95 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 3 Jan 2025 11:17:40 +0100 Subject: [PATCH 49/89] Close restore dialog if done (#23566) --- src/panels/config/backup/dialogs/dialog-restore-backup.ts | 3 +++ src/panels/config/backup/ha-config-backup.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/panels/config/backup/dialogs/dialog-restore-backup.ts b/src/panels/config/backup/dialogs/dialog-restore-backup.ts index bcc6ecc7bf..73555630ac 100644 --- a/src/panels/config/backup/dialogs/dialog-restore-backup.ts +++ b/src/panels/config/backup/dialogs/dialog-restore-backup.ts @@ -212,6 +212,9 @@ class DialogRestoreBackup extends LitElement implements HassDialog { private _subscribeBackupEvents() { this._unsub = subscribeBackupEvents(this.hass!, (event) => { + if (!this._error && event.manager_state === "idle") { + this.closeDialog(); + } if (event.manager_state !== "restore_backup") { return; } diff --git a/src/panels/config/backup/ha-config-backup.ts b/src/panels/config/backup/ha-config-backup.ts index bf8be4d454..7f4a761a48 100644 --- a/src/panels/config/backup/ha-config-backup.ts +++ b/src/panels/config/backup/ha-config-backup.ts @@ -33,7 +33,7 @@ declare global { class HaConfigBackup extends SubscribeMixin(HassRouterPage) { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ attribute: false }) public cloudStatus!: CloudStatus; + @property({ attribute: false }) public cloudStatus?: CloudStatus; @property({ type: Boolean }) public narrow = false; From 0bf64ee7f4b9e204d32e73518628f95dea2e0b4f Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 3 Jan 2025 11:29:03 +0100 Subject: [PATCH 50/89] Backup onboarding: Show close button when welcome is skipped (#23567) --- .../config/backup/dialogs/dialog-backup-onboarding.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts b/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts index 01f881de58..ab66157e46 100644 --- a/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts +++ b/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts @@ -90,7 +90,7 @@ class DialogBackupOnboarding extends LitElement implements HassDialog { public showDialog(params: BackupOnboardingDialogParams): void { this._params = params; - this._step = params.skipWelcome ? STEPS[1] : STEPS[0]; + this._step = this._firstStep; this._config = RECOMMENDED_CONFIG; const agents: string[] = []; @@ -129,6 +129,10 @@ class DialogBackupOnboarding extends LitElement implements HassDialog { this._params = undefined; } + private get _firstStep(): Step { + return this._params?.skipWelcome ? STEPS[1] : STEPS[0]; + } + private async _done() { if (!this._config) { return; @@ -187,7 +191,7 @@ class DialogBackupOnboarding extends LitElement implements HassDialog { } const isLastStep = this._step === STEPS[STEPS.length - 1]; - const isFirstStep = this._step === STEPS[0]; + const isFirstStep = this._step === this._firstStep; return html` From 70541ec966fcc16e57b777262e625fdb3f282635 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 3 Jan 2025 15:02:59 +0100 Subject: [PATCH 51/89] Fix restore progress check logic (#23568) --- .../backup/dialogs/dialog-restore-backup.ts | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/panels/config/backup/dialogs/dialog-restore-backup.ts b/src/panels/config/backup/dialogs/dialog-restore-backup.ts index 73555630ac..7fc8d26426 100644 --- a/src/panels/config/backup/dialogs/dialog-restore-backup.ts +++ b/src/panels/config/backup/dialogs/dialog-restore-backup.ts @@ -23,7 +23,10 @@ import type { HassDialog } from "../../../../dialogs/make-dialog-manager"; import { haStyle, haStyleDialog } from "../../../../resources/styles"; import type { HomeAssistant } from "../../../../types"; import type { RestoreBackupDialogParams } from "./show-dialog-restore-backup"; -import type { RestoreBackupStage } from "../../../../data/backup_manager"; +import type { + RestoreBackupStage, + RestoreBackupState, +} from "../../../../data/backup_manager"; import { subscribeBackupEvents } from "../../../../data/backup_manager"; type FormData = { @@ -54,6 +57,8 @@ class DialogRestoreBackup extends LitElement implements HassDialog { @state() private _error?: string; + @state() private _state?: RestoreBackupState; + @state() private _stage?: RestoreBackupStage | null; @state() private _unsub?: Promise; @@ -64,6 +69,10 @@ class DialogRestoreBackup extends LitElement implements HassDialog { this._params = params; this._formData = INITIAL_DATA; + this._userPassword = undefined; + this._error = undefined; + this._state = undefined; + this._stage = undefined; if (this._params.backup.protected) { this._backupEncryptionKey = await this._fetchEncryptionKey(); if (!this._backupEncryptionKey) { @@ -86,6 +95,7 @@ class DialogRestoreBackup extends LitElement implements HassDialog { this._backupEncryptionKey = undefined; this._userPassword = undefined; this._error = undefined; + this._state = undefined; this._stage = undefined; this._step = undefined; this._unsubscribe(); @@ -188,7 +198,6 @@ class DialogRestoreBackup extends LitElement implements HassDialog { this._unsubscribe(); try { this._step = "progress"; - window.addEventListener("connection-status", this._connectionStatus); this._subscribeBackupEvents(); await this._doRestoreBackup( this._userPassword || this._backupEncryptionKey @@ -204,20 +213,15 @@ class DialogRestoreBackup extends LitElement implements HassDialog { } } - private _connectionStatus = (ev) => { - if (ev.detail === "connected") { - this.closeDialog(); - } - }; - private _subscribeBackupEvents() { this._unsub = subscribeBackupEvents(this.hass!, (event) => { - if (!this._error && event.manager_state === "idle") { + if (event.manager_state === "idle" && this._state === "in_progress") { this.closeDialog(); } if (event.manager_state !== "restore_backup") { return; } + this._state = event.state; if (event.state === "completed") { this.closeDialog(); } @@ -231,7 +235,6 @@ class DialogRestoreBackup extends LitElement implements HassDialog { } private _unsubscribe() { - window.removeEventListener("connection-status", this._connectionStatus); if (this._unsub) { const prom = this._unsub.then((unsub) => unsub()); this._unsub = undefined; From e2ad94469aea56a29f6683c5cef2264cedb250ee Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 3 Jan 2025 15:26:50 +0100 Subject: [PATCH 52/89] Fix restoring backup during onboarding (#23569) --- hassio/src/dialogs/backup/dialog-hassio-backup.ts | 10 +++++----- src/data/hassio/backup.ts | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/hassio/src/dialogs/backup/dialog-hassio-backup.ts b/hassio/src/dialogs/backup/dialog-hassio-backup.ts index 91b6643094..60e7cc2534 100644 --- a/hassio/src/dialogs/backup/dialog-hassio-backup.ts +++ b/hassio/src/dialogs/backup/dialog-hassio-backup.ts @@ -179,8 +179,8 @@ class HassioBackupDialog } private async _restoreClicked() { - this._restoringBackup = true; const backupDetails = this._backupContent.backupDetails(); + this._restoringBackup = true; const supervisor = this._dialogParams?.supervisor; if (supervisor !== undefined && supervisor.info.state !== "running") { @@ -196,12 +196,12 @@ class HassioBackupDialog if ( !(await showConfirmationDialog(this, { title: this._localize( - this._backupContent.backupType === "full" + this._backup!.type === "full" ? "confirm_restore_full_backup_title" : "confirm_restore_partial_backup_title" ), text: this._localize( - this._backupContent.backupType === "full" + this._backup!.type === "full" ? "confirm_restore_full_backup_text" : "confirm_restore_partial_backup_text" ), @@ -216,9 +216,9 @@ class HassioBackupDialog try { await restoreBackup( this.hass, - this._backupContent.backupType, + this._backup!.type, this._backup!.slug, - backupDetails, + { ...backupDetails, background: this._dialogParams?.onboarding }, !!this.hass && atLeastVersion(this.hass.config.version, 2021, 9) ); diff --git a/src/data/hassio/backup.ts b/src/data/hassio/backup.ts index 8a88e1a5b9..0257128bf7 100644 --- a/src/data/hassio/backup.ts +++ b/src/data/hassio/backup.ts @@ -46,6 +46,7 @@ export interface HassioFullBackupCreateParams { name: string; password?: string; confirm_password?: string; + background?: boolean; } export interface HassioPartialBackupCreateParams extends HassioFullBackupCreateParams { From ec1fc0914093c95255d94390a571ba7e3738d2bf Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 3 Jan 2025 15:42:39 +0100 Subject: [PATCH 53/89] Bumped version to 20250103.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index bfbc1ef922..43d693b7b8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20250102.0" +version = "20250103.0" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md" From 47308e7b461c5758d1749a4e59831c059c8b2f34 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Fri, 3 Jan 2025 15:48:20 +0100 Subject: [PATCH 54/89] Add change of encryption key warning (#23570) --- .../backup/dialogs/dialog-restore-backup.ts | 43 +++++++++++++++---- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/src/panels/config/backup/dialogs/dialog-restore-backup.ts b/src/panels/config/backup/dialogs/dialog-restore-backup.ts index 7fc8d26426..ecf6a6f156 100644 --- a/src/panels/config/backup/dialogs/dialog-restore-backup.ts +++ b/src/panels/config/backup/dialogs/dialog-restore-backup.ts @@ -55,6 +55,8 @@ class DialogRestoreBackup extends LitElement implements HassDialog { @state() private _userPassword?: string; + @state() private _usedUserInput = false; + @state() private _error?: string; @state() private _state?: RestoreBackupState; @@ -70,6 +72,7 @@ class DialogRestoreBackup extends LitElement implements HassDialog { this._formData = INITIAL_DATA; this._userPassword = undefined; + this._usedUserInput = false; this._error = undefined; this._state = undefined; this._stage = undefined; @@ -94,6 +97,7 @@ class DialogRestoreBackup extends LitElement implements HassDialog { this._params = undefined; this._backupEncryptionKey = undefined; this._userPassword = undefined; + this._usedUserInput = false; this._error = undefined; this._state = undefined; this._stage = undefined; @@ -159,15 +163,24 @@ class DialogRestoreBackup extends LitElement implements HassDialog { } private _renderEncryption() { - return html`

- ${this._userPassword - ? "The provided encryption key was incorrect, please try again." - : this._backupEncryptionKey - ? "The backup is encrypted with a different key or password than that is saved on this system. Please enter the key for this backup." - : "The backup is encrypted. Provide the encryption key to decrypt the backup."} -

+ return html`${this._usedUserInput + ? "The provided encryption key was incorrect, please try again." + : this._backupEncryptionKey + ? html`The Backup is encrypted with a different encryption key than + that is saved on this system. Please enter the encryption key for + this backup.
+ ${this._params!.selectedData.homeassistant_included + ? html`After restoring the backup, your new backups will be + encrypted with the encryption key that was present during + the time of this backup.` + : nothing}` + : "The backup is encrypted. Provide the encryption key to decrypt the backup."} + `; } @@ -196,6 +209,9 @@ class DialogRestoreBackup extends LitElement implements HassDialog { private async _restoreBackup() { this._unsubscribe(); + this._state = undefined; + this._stage = undefined; + this._error = undefined; try { this._step = "progress"; this._subscribeBackupEvents(); @@ -206,6 +222,9 @@ class DialogRestoreBackup extends LitElement implements HassDialog { await this._unsubscribe(); if (e.code === "password_incorrect") { this._error = undefined; + if (this._userPassword) { + this._usedUserInput = true; + } this._step = "encryption"; } else { this._error = e.message; @@ -315,6 +334,14 @@ class DialogRestoreBackup extends LitElement implements HassDialog { ha-circular-progress { margin-bottom: 16px; } + ha-alert[alert-type="warning"] { + display: block; + margin-top: 16px; + } + ha-password-field { + display: block; + margin-top: 16px; + } `, ]; } From 4b13dde92eac18d9f7cdf028d0dea424182f5c72 Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Mon, 6 Jan 2025 14:42:43 +0200 Subject: [PATCH 55/89] Rename base sankey chart tag so it doesn't conflict with the custom card (#23600) --- .../chart/{sankey-chart.ts => ha-sankey-chart.ts} | 6 +++--- .../lovelace/cards/energy/hui-energy-sankey-card.ts | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) rename src/components/chart/{sankey-chart.ts => ha-sankey-chart.ts} (99%) diff --git a/src/components/chart/sankey-chart.ts b/src/components/chart/ha-sankey-chart.ts similarity index 99% rename from src/components/chart/sankey-chart.ts rename to src/components/chart/ha-sankey-chart.ts index 46a6b5980b..294f170d14 100644 --- a/src/components/chart/sankey-chart.ts +++ b/src/components/chart/ha-sankey-chart.ts @@ -49,8 +49,8 @@ const NODE_WIDTH = 15; const FONT_SIZE = 12; const MIN_DISTANCE = FONT_SIZE / 2; -@customElement("sankey-chart") -export class SankeyChart extends LitElement { +@customElement("ha-sankey-chart") +export class HaSankeyChart extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; @property({ attribute: false }) public data: SankeyChartData = { @@ -539,6 +539,6 @@ export class SankeyChart extends LitElement { declare global { interface HTMLElementTagNameMap { - "sankey-chart": SankeyChart; + "ha-sankey-chart": HaSankeyChart; } } diff --git a/src/panels/lovelace/cards/energy/hui-energy-sankey-card.ts b/src/panels/lovelace/cards/energy/hui-energy-sankey-card.ts index a80f4b480d..d5630916ef 100644 --- a/src/panels/lovelace/cards/energy/hui-energy-sankey-card.ts +++ b/src/panels/lovelace/cards/energy/hui-energy-sankey-card.ts @@ -18,8 +18,8 @@ import { SubscribeMixin } from "../../../../mixins/subscribe-mixin"; import type { HomeAssistant } from "../../../../types"; import type { LovelaceCard, LovelaceGridOptions } from "../../types"; import type { EnergySankeyCardConfig } from "../types"; -import "../../../../components/chart/sankey-chart"; -import type { Link, Node } from "../../../../components/chart/sankey-chart"; +import "../../../../components/chart/ha-sankey-chart"; +import type { Link, Node } from "../../../../components/chart/ha-sankey-chart"; import { getGraphColorByIndex } from "../../../../common/color/colors"; import { formatNumber } from "../../../../common/number/format_number"; @@ -399,13 +399,13 @@ class HuiEnergySankeyCard
${hasData - ? html`` + >` : html`${this.hass.localize( "ui.panel.lovelace.cards.energy.no_data_period" )}`} From a3e8bcf8488f9e6ac13baccc4fd4224552f62bed Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Mon, 6 Jan 2025 16:06:44 +0100 Subject: [PATCH 56/89] Add ICU strings for proper singular / plural in Search fields (#23530) It does not happen that often that the lists of devices, entities, helpers etc. are filtered down to a single item. But in that case the labels currently use incorrect plural which is more irritating in some languages but also wrong in English. This commit fixes this by adding ICU syntax to all six strings so these work properly in English and all derived translations. For languages that need a different wording for `zero` this also helps translators in extending the ICU syntax for that case. --- src/translations/en.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/translations/en.json b/src/translations/en.json index 688df34b20..39dd218774 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2437,7 +2437,7 @@ }, "create_helper": "Create helper", "no_helpers": "Looks like you don't have any helpers yet!", - "search": "Search {number} helpers", + "search": "Search {number} {number, plural,\n one {helper}\n other {helpers}\n}", "error_information": "Error information" }, "dialog": { @@ -2962,7 +2962,7 @@ "assign_category": "Assign category", "no_category_support": "You can't assign a category to this automation", "no_category_entity_reg": "To assign a category to an automation it needs to have a unique ID.", - "search": "Search {number} automations", + "search": "Search {number} {number, plural,\n one {automation}\n other {automations}\n}", "headers": { "toggle": "Enable/disable", "name": "Name", @@ -3883,7 +3883,7 @@ "duplicate": "[%key:ui::common::duplicate%]", "empty_header": "Create your first script", "empty_text": "A script is a sequence of actions that can be run from a dashboard, an automation, or be triggered by voice. For example, a ''Wake-up routine''' script that gradually turns on the light in the bedroom and opens the blinds after a delay.", - "search": "Search {number} scripts", + "search": "Search {number} {number, plural,\n one {script}\n other {scripts}\n}", "migrate_script": "Migrate script?", "migrate_script_description": "You can migrate this script, so it can be edited from the UI. After it is migrated and you have saved it, you will have to manually delete your old script from your configuration. Do you want to migrate this script?" }, @@ -3997,7 +3997,7 @@ "no_category_entity_reg": "To assign an category to an scene it needs to have a unique ID.", "empty_header": "Create your first scene", "empty_text": "Scenes capture entities' states, so you can re-experience the same scene later on. For example, a ''Watching TV'' scene that dims the living room lights, sets a warm white color and turns on the TV.", - "search": "Search {number} scenes" + "search": "Search {number} {number, plural,\n one {scene}\n other {scenes}\n}" }, "editor": { "review_mode": "Review Mode", @@ -4369,7 +4369,7 @@ "confirm_delete_integration": "Are you sure you want to remove this device from {integration}?", "error_delete": "Error deleting device", "picker": { - "search": "Search {number} devices", + "search": "Search {number} {number, plural,\n one {device}\n other {devices}\n}", "state": "Status", "bulk_actions": { "move_area": "Move to area", @@ -4385,7 +4385,7 @@ "header": "Entities", "introduction": "Home Assistant keeps a registry of every entity it has ever seen that can be uniquely identified. Each of these entities will have an entity ID assigned which will be reserved for just this entity.", "introduction2": "Use the entity registry to override the name, change the entity ID or remove the entry from Home Assistant.", - "search": "Search {number} entities", + "search": "Search {number} {number, plural,\n one {entity}\n other {entities}\n}", "unnamed_entity": "Unnamed entity", "status": { "available": "Available", From 046b90ae255af30d423b1f33935c62ad35ac9db4 Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Mon, 6 Jan 2025 08:54:07 +0100 Subject: [PATCH 57/89] Add localizable "Filtering by config entry" for Entities and Devices (#23544) * Add localizable "Filtering by config entry" to en.json This commit adds two strings for localizing "Filtering by config entry" to the Entities panel and, referenced from there, to the Devices panel. * Replace "Filtering by config entry" with localizable key * Replace "Filtering by config entry" with localizable key * Add missing comma * Add missing } * Add missing } --- src/panels/config/devices/ha-config-devices-dashboard.ts | 4 +++- src/panels/config/entities/ha-config-entities.ts | 4 +++- src/translations/en.json | 2 ++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/panels/config/devices/ha-config-devices-dashboard.ts b/src/panels/config/devices/ha-config-devices-dashboard.ts index 71e2a5e610..c0ba1f330e 100644 --- a/src/panels/config/devices/ha-config-devices-dashboard.ts +++ b/src/panels/config/devices/ha-config-devices-dashboard.ts @@ -749,7 +749,9 @@ export class HaConfigDeviceDashboard extends SubscribeMixin(LitElement) { ${Array.isArray(this._filters.config_entry?.value) && this._filters.config_entry?.value.length ? html` - Filtering by config entry + ${this.hass.localize( + "ui.panel.config.devices.filtering_by_config_entry" + )} ${this.entries?.find( (entry) => entry.entry_id === this._filters.config_entry!.value![0] diff --git a/src/panels/config/entities/ha-config-entities.ts b/src/panels/config/entities/ha-config-entities.ts index 937dcfa309..5cdcfa6488 100644 --- a/src/panels/config/entities/ha-config-entities.ts +++ b/src/panels/config/entities/ha-config-entities.ts @@ -904,7 +904,9 @@ ${ Array.isArray(this._filters.config_entry) && this._filters.config_entry?.length ? html` - Filtering by config entry + ${this.hass.localize( + "ui.panel.config.entities.picker.filtering_by_config_entry" + )} ${this._entries?.find( (entry) => entry.entry_id === this._filters.config_entry![0] )?.title || this._filters.config_entry[0]} diff --git a/src/translations/en.json b/src/translations/en.json index 39dd218774..8c27047f2f 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -4241,6 +4241,7 @@ "add_device": "Add device", "caption": "Devices", "description": "Manage configured devices", + "filtering_by_config_entry": "[%key:ui::panel::config::entities::picker::filtering_by_config_entry%]", "device_info": "{type} info", "edit_settings": "Edit settings", "unnamed_device": "Unnamed {type}", @@ -4387,6 +4388,7 @@ "introduction2": "Use the entity registry to override the name, change the entity ID or remove the entry from Home Assistant.", "search": "Search {number} {number, plural,\n one {entity}\n other {entities}\n}", "unnamed_entity": "Unnamed entity", + "filtering_by_config_entry": "Filtering by config entry", "status": { "available": "Available", "unavailable": "Unavailable", From a755af96a6934d7ebbb248a3942f3273a1107391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ville=20Skytt=C3=A4?= Date: Mon, 6 Jan 2025 10:10:34 -0100 Subject: [PATCH 58/89] Spelling and grammar fixes (#23598) --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- .github/ISSUE_TEMPLATE/config.yml | 2 +- cast/README.md | 2 +- cast/src/launcher/layout/hc-connect.ts | 2 +- .../Text/remove-delete-add-create.markdown | 2 +- .../src/pages/components/ha-alert.markdown | 20 +++++++++---------- gallery/src/pages/components/ha-faded.ts | 2 +- .../pages/components/ha-hs-color-picker.ts | 2 +- script/develop_and_serve | 2 +- src/common/decorators/transform.ts | 6 +++--- .../entity/compute_attribute_display.ts | 2 +- src/common/entity/get_states.ts | 2 +- src/common/entity/state_color.ts | 2 +- src/common/string/filter/filter.ts | 2 +- src/components/chart/ha-sankey-chart.ts | 2 +- src/components/ha-hls-player.ts | 2 +- src/components/ha-icon.ts | 2 +- src/components/ha-md-dialog.ts | 4 ++-- src/components/ha-sidebar.ts | 4 ++-- src/data/energy.ts | 2 +- src/data/logbook.ts | 2 +- src/data/zwave_js.ts | 2 +- src/entrypoints/service-worker.ts | 4 ++-- src/fake_data/demo_services.ts | 2 +- .../config/ha-backup-config-schedule.ts | 2 +- .../backup/dialogs/dialog-restore-backup.ts | 2 +- .../integrations/dialog-add-integration.ts | 2 +- .../ha-config-integrations-dashboard.ts | 2 +- .../lovelace/common/validate-condition.ts | 4 ++-- .../create-element/create-element-base.ts | 2 +- .../elements/hui-service-button-element.ts | 2 +- src/panels/lovelace/views/hui-masonry-view.ts | 2 +- src/state/translations-mixin.ts | 4 ++-- src/translations/en.json | 18 ++++++++--------- src/util/common-translation.ts | 2 +- .../entity/attribute_class_names.test.ts | 6 +++--- 36 files changed, 62 insertions(+), 62 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 2db287bfd4..9e20e82633 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -7,7 +7,7 @@ body: value: | Make sure you are running the [latest version of Home Assistant][releases] before reporting an issue. - If you have a feature or enhancement request for the frontend, please [start an discussion][fr] instead of creating an issue. + If you have a feature or enhancement request for the frontend, please [start a discussion][fr] instead of creating an issue. **Please do not report issues for custom cards.** diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 689b0c011d..47563c2286 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,7 +2,7 @@ blank_issues_enabled: false contact_links: - name: Request a feature for the UI / Dashboards url: https://github.com/home-assistant/frontend/discussions/category_choices - about: Request an new feature for the Home Assistant frontend. + about: Request a new feature for the Home Assistant frontend. - name: Report a bug that is NOT related to the UI / Dashboards url: https://github.com/home-assistant/core/issues about: This is the issue tracker for our frontend. Please report other issues in the backend ("core") repository. diff --git a/cast/README.md b/cast/README.md index c1741fc989..bed8b3a2ae 100644 --- a/cast/README.md +++ b/cast/README.md @@ -25,7 +25,7 @@ Home Assistant Cast is made up of two separate applications: ### Setting dev variables -Open `src/cast/dev_const.ts` and change `CAST_DEV_APP_ID` to the ID of the app you just created. And set the `CAST_DEV_HASS_URL` to the url of you development machine. +Open `src/cast/dev_const.ts` and change `CAST_DEV_APP_ID` to the ID of the app you just created. And set the `CAST_DEV_HASS_URL` to the url of your development machine. ### Changing configuration diff --git a/cast/src/launcher/layout/hc-connect.ts b/cast/src/launcher/layout/hc-connect.ts index 207b0dfa53..ae40e3ea38 100644 --- a/cast/src/launcher/layout/hc-connect.ts +++ b/cast/src/launcher/layout/hc-connect.ts @@ -252,7 +252,7 @@ export class HcConnect extends LitElement { this.loading = false; return; } finally { - // Clear url if we have a auth callback in url. + // Clear url if we have an auth callback in url. if (location.search.includes("auth_callback=1")) { history.replaceState(null, "", location.pathname); } diff --git a/gallery/src/pages/Text/remove-delete-add-create.markdown b/gallery/src/pages/Text/remove-delete-add-create.markdown index 3b0bb85c36..98494c0b62 100644 --- a/gallery/src/pages/Text/remove-delete-add-create.markdown +++ b/gallery/src/pages/Text/remove-delete-add-create.markdown @@ -42,7 +42,7 @@ In most cases, Create can be paired with Delete, and Add can be paired with Remo ## Add -An already-exisiting item. +An already-existing item. For example: diff --git a/gallery/src/pages/components/ha-alert.markdown b/gallery/src/pages/components/ha-alert.markdown index 9d2eced805..17816fb7e6 100644 --- a/gallery/src/pages/components/ha-alert.markdown +++ b/gallery/src/pages/components/ha-alert.markdown @@ -19,7 +19,7 @@ The alert offers four severity levels that set a distinctive icon and color. - This is an warning alert — check it out! + This is a warning alert — check it out! @@ -27,7 +27,7 @@ The alert offers four severity levels that set a distinctive icon and color. - This is an success alert — check it out! + This is a success alert — check it out! **Note:** This component is by MUI and is not documented in the Material Design guidelines. @@ -95,7 +95,7 @@ Actions must have a tab index of 0 so that they can be reached by keyboard-only - This is an warning alert — check it out! + This is a warning alert — check it out! @@ -103,7 +103,7 @@ Actions must have a tab index of 0 so that they can be reached by keyboard-only - This is an success alert — check it out! + This is a success alert — check it out! ```html @@ -122,37 +122,37 @@ Actions must have a tab index of 0 so that they can be reached by keyboard-only The `title ` option should not be used without a description. - This is an success alert — check it out! + This is a success alert — check it out! ```html - This is an success alert — check it out! + This is a success alert — check it out! ``` **Dismissable** - This is an success alert — check it out! + This is a success alert — check it out! ```html - This is an success alert — check it out! + This is a success alert — check it out! ``` **Slotted action** - This is an success alert — check it out! + This is a success alert — check it out! ```html - This is an success alert — check it out! + This is a success alert — check it out! ``` diff --git a/gallery/src/pages/components/ha-faded.ts b/gallery/src/pages/components/ha-faded.ts index d053a8f662..e8d66775a5 100644 --- a/gallery/src/pages/components/ha-faded.ts +++ b/gallery/src/pages/components/ha-faded.ts @@ -20,7 +20,7 @@ export class DemoHaFaded extends LitElement { ${LONG_TEXT}

No text

-

Smal text

+

Small text

${SMALL_TEXT}

Long text in markdown

diff --git a/gallery/src/pages/components/ha-hs-color-picker.ts b/gallery/src/pages/components/ha-hs-color-picker.ts index 005ab06c09..fb69faca00 100644 --- a/gallery/src/pages/components/ha-hs-color-picker.ts +++ b/gallery/src/pages/components/ha-hs-color-picker.ts @@ -76,7 +76,7 @@ export class DemoHaHsColorPicker extends LitElement { @change=${this._saturationChanged} > -

Color Brighness : ${this.brightness}

+

Color Brightness : ${this.brightness}

(config: { diff --git a/src/common/entity/compute_attribute_display.ts b/src/common/entity/compute_attribute_display.ts index c6a43a6d58..b3da42c10b 100644 --- a/src/common/entity/compute_attribute_display.ts +++ b/src/common/entity/compute_attribute_display.ts @@ -65,7 +65,7 @@ export const computeAttributeValueDisplay = ( return formattedValue; } - // Special handling in case this is a string with an known format + // Special handling in case this is a string with a known format if (typeof attributeValue === "string") { // Date handling if (isDate(attributeValue, true)) { diff --git a/src/common/entity/get_states.ts b/src/common/entity/get_states.ts index 7637b35e7f..c608e1cc3b 100644 --- a/src/common/entity/get_states.ts +++ b/src/common/entity/get_states.ts @@ -166,7 +166,7 @@ const FIXED_DOMAIN_ATTRIBUTE_STATES = { "channel", "channels", "composer", - "contibuting_artist", + "contributing_artist", "episode", "game", "genre", diff --git a/src/common/entity/state_color.ts b/src/common/entity/state_color.ts index e64d2612c8..474fee0e59 100644 --- a/src/common/entity/state_color.ts +++ b/src/common/entity/state_color.ts @@ -1,4 +1,4 @@ -/** Return an color representing a state. */ +/** Return a color representing a state. */ import type { HassEntity } from "home-assistant-js-websocket"; import { UNAVAILABLE } from "../../data/entity"; import type { GroupEntity } from "../../data/group"; diff --git a/src/common/string/filter/filter.ts b/src/common/string/filter/filter.ts index e7c0103263..ecaf5b6f95 100644 --- a/src/common/string/filter/filter.ts +++ b/src/common/string/filter/filter.ts @@ -406,7 +406,7 @@ function _doScore( // this would be the beginning of a new match (i.e. there would be a gap before this location) score += isGapLocation ? 2 : 0; } else { - // this is part of a contiguous match, so give it a slight bonus, but do so only if it would not be a prefered gap location + // this is part of a contiguous match, so give it a slight bonus, but do so only if it would not be a preferred gap location score += isGapLocation ? 0 : 1; } diff --git a/src/components/chart/ha-sankey-chart.ts b/src/components/chart/ha-sankey-chart.ts index 294f170d14..7dcff4fbc9 100644 --- a/src/components/chart/ha-sankey-chart.ts +++ b/src/components/chart/ha-sankey-chart.ts @@ -315,7 +315,7 @@ export class HaSankeyChart extends LitElement { } else { totalSize = section.nodes.reduce((sum, b) => sum + b.size, 0); } - // calc margin betwee boxes + // calc margin between boxes const emptySpace = sectionSize - totalSize; const spacerSize = emptySpace / (section.nodes.length - 1); diff --git a/src/components/ha-hls-player.ts b/src/components/ha-hls-player.ts index 816aa80cac..f1f7a27e8e 100644 --- a/src/components/ha-hls-player.ts +++ b/src/components/ha-hls-player.ts @@ -288,7 +288,7 @@ class HaHLSPlayer extends LitElement { hls.on(Hls.Events.ERROR, (_event, data: any) => { // Some errors are recovered automatically by the hls player itself, and the others handled // in this function require special actions to recover. Errors retried in this function - // are done with backoff to not cause unecessary failures. + // are done with backoff to not cause unnecessary failures. if (!data.fatal) { return; } diff --git a/src/components/ha-icon.ts b/src/components/ha-icon.ts index 3428deaada..b803b41515 100644 --- a/src/components/ha-icon.ts +++ b/src/components/ha-icon.ts @@ -56,7 +56,7 @@ export class HaIcon extends LitElement { return nothing; } if (this._legacy) { - return html` + return html` `; } return html` { await instance.updateComplete; @@ -197,7 +197,7 @@ export class HaMdDialog extends MdDialog { } // by default the dialog open/close animation will be from/to the top -// but if we have a special mobile dialog which is at the bottom of the screen, an from bottom animation can be used: +// but if we have a special mobile dialog which is at the bottom of the screen, a from bottom animation can be used: const OPEN_FROM_BOTTOM_ANIMATION: DialogAnimation = { ...DIALOG_DEFAULT_OPEN_ANIMATION, dialog: [ diff --git a/src/components/ha-sidebar.ts b/src/components/ha-sidebar.ts index 0420fcc74a..680bd37d7f 100644 --- a/src/components/ha-sidebar.ts +++ b/src/components/ha-sidebar.ts @@ -390,7 +390,7 @@ class HaSidebar extends SubscribeMixin(LitElement) { this.hass.locale ); - // Show the supervisor as beeing part of configuration + // Show the supervisor as being part of configuration const selectedPanel = this.route.path?.startsWith("/hassio/") ? "config" : this.hass.panelUrl; @@ -632,7 +632,7 @@ class HaSidebar extends SubscribeMixin(LitElement) { return html` { // CORS must be forced to work for CSS images fetchOptions: { mode: "cors", credentials: "omit" }, plugins: [ - // Add 404 so we quicly respond to domains with missing images + // Add 404 so we quickly respond to domains with missing images new CacheableResponsePlugin({ statuses: [0, 200, 404] }), new ExpirationPlugin({ maxAgeSeconds: 60 * 60 * 24 * 30, @@ -222,7 +222,7 @@ self.addEventListener("activate", () => { // that didn't have a service worker loaded. // Happens the first time they open the app without any // service worker registered. - // This will serve code splitted bundles from SW. + // This will serve code split bundles from SW. clients.claim(); }); diff --git a/src/fake_data/demo_services.ts b/src/fake_data/demo_services.ts index 04a0e57ef3..7111223ca3 100644 --- a/src/fake_data/demo_services.ts +++ b/src/fake_data/demo_services.ts @@ -644,7 +644,7 @@ export const demoServices: HassServices = { example: "25", }, target_temp_high: { - description: "New target high tempereature for HVAC.", + description: "New target high temperature for HVAC.", example: "26", }, target_temp_low: { diff --git a/src/panels/config/backup/components/config/ha-backup-config-schedule.ts b/src/panels/config/backup/components/config/ha-backup-config-schedule.ts index 0a48fc9a0e..b2402625e8 100644 --- a/src/panels/config/backup/components/config/ha-backup-config-schedule.ts +++ b/src/panels/config/backup/components/config/ha-backup-config-schedule.ts @@ -267,7 +267,7 @@ class HaBackupConfigSchedule extends LitElement { if (value !== RetentionPreset.CUSTOM) { const data = this._getData(this.value); const retention = RETENTION_PRESETS[value]; - // Ensure we have at least 1 in defaut value because user can't select 0 + // Ensure we have at least 1 in default value because user can't select 0 if (value !== RetentionPreset.FOREVER) { retention.value = Math.max(retention.value, 1); } diff --git a/src/panels/config/backup/dialogs/dialog-restore-backup.ts b/src/panels/config/backup/dialogs/dialog-restore-backup.ts index ecf6a6f156..588767b1dc 100644 --- a/src/panels/config/backup/dialogs/dialog-restore-backup.ts +++ b/src/panels/config/backup/dialogs/dialog-restore-backup.ts @@ -198,7 +198,7 @@ class DialogRestoreBackup extends LitElement implements HassDialog {

${this.hass.connected ? this._restoreState() - : "Restarting Home Asssistant"} + : "Restarting Home Assistant"}

`; } diff --git a/src/panels/config/integrations/dialog-add-integration.ts b/src/panels/config/integrations/dialog-add-integration.ts index 5447065e0c..16c1a0615c 100644 --- a/src/panels/config/integrations/dialog-add-integration.ts +++ b/src/panels/config/integrations/dialog-add-integration.ts @@ -555,7 +555,7 @@ class AddIntegrationDialog extends LitElement { if (integration.integrations) { let domains = integration.domains || []; if (integration.domain === "apple") { - // we show discoverd homekit devices in their own brand section, dont show them at apple + // we show discovered homekit devices in their own brand section, dont show them in apple domains = domains.filter((domain) => domain !== "homekit_controller"); } this._fetchFlowsInProgress(domains); diff --git a/src/panels/config/integrations/ha-config-integrations-dashboard.ts b/src/panels/config/integrations/ha-config-integrations-dashboard.ts index ffd3a9471f..c3f7950f3f 100644 --- a/src/panels/config/integrations/ha-config-integrations-dashboard.ts +++ b/src/panels/config/integrations/ha-config-integrations-dashboard.ts @@ -864,7 +864,7 @@ class HaConfigIntegrationsDashboard extends SubscribeMixin(LitElement) { } if (integration?.supported_by) { - // Integration is a alias, so we can just create a flow + // Integration is an alias, so we can just create a flow const localize = await this.hass.loadBackendTranslation( "title", domain, diff --git a/src/panels/lovelace/common/validate-condition.ts b/src/panels/lovelace/common/validate-condition.ts index 382ffdddfe..e0cf6792fb 100644 --- a/src/panels/lovelace/common/validate-condition.ts +++ b/src/panels/lovelace/common/validate-condition.ts @@ -78,7 +78,7 @@ function checkStateCondition( : UNAVAILABLE; let value = condition.state ?? condition.state_not; - // Handle entity_id, UI should be updated for conditionnal card (filters does not have UI for now) + // Handle entity_id, UI should be updated for conditional card (filters does not have UI for now) if (Array.isArray(value)) { const entityValues = value .map((v) => getValueFromEntityId(hass, v)) @@ -106,7 +106,7 @@ function checkStateNumericCondition( let above = condition.above; let below = condition.below; - // Handle entity_id, UI should be updated for conditionnal card (filters does not have UI for now) + // Handle entity_id, UI should be updated for conditional card (filters does not have UI for now) if (typeof above === "string") { above = getValueFromEntityId(hass, above) ?? above; } diff --git a/src/panels/lovelace/create-element/create-element-base.ts b/src/panels/lovelace/create-element/create-element-base.ts index a29cb3ef91..5b01ff4193 100644 --- a/src/panels/lovelace/create-element/create-element-base.ts +++ b/src/panels/lovelace/create-element/create-element-base.ts @@ -218,7 +218,7 @@ const _lazyCreate = ( // @ts-ignore element.setConfig(config); } catch (err: any) { - // We let it rebuild and the error wil be handled by _createElement + // We let it rebuild and the error will be handled by _createElement fireEvent(element, "ll-rebuild"); } }); diff --git a/src/panels/lovelace/elements/hui-service-button-element.ts b/src/panels/lovelace/elements/hui-service-button-element.ts index 7699e3e2f4..21343932af 100644 --- a/src/panels/lovelace/elements/hui-service-button-element.ts +++ b/src/panels/lovelace/elements/hui-service-button-element.ts @@ -49,7 +49,7 @@ export class HuiServiceButtonElement } if (!this._service) { - throw Error("Action does not have a action name"); + throw Error("Action does not have an action name"); } this._config = config; diff --git a/src/panels/lovelace/views/hui-masonry-view.ts b/src/panels/lovelace/views/hui-masonry-view.ts index 697a237a90..d50f42dcac 100644 --- a/src/panels/lovelace/views/hui-masonry-view.ts +++ b/src/panels/lovelace/views/hui-masonry-view.ts @@ -231,7 +231,7 @@ export class MasonryView extends LitElement implements LovelaceViewElement { // An other create columns is started, abort this one return; } - // Calculate in wich column the card should go based on the size and the cards already in there + // Calculate in which column the card should go based on the size and the cards already in there this._addCardToColumn( columnElements[getColumnIndex(columnSizes, cardSize as number)], index, diff --git a/src/state/translations-mixin.ts b/src/state/translations-mixin.ts index 58f732d89e..e7bb4db8e2 100644 --- a/src/state/translations-mixin.ts +++ b/src/state/translations-mixin.ts @@ -298,7 +298,7 @@ export default >(superClass: T) => } const resources = await getHassTranslationsPre109(this.hass!, language); - // Ignore the repsonse if user switched languages before we got response + // Ignore the response if user switched languages before we got response if (this.hass!.language !== language) { return this.hass!.localize; } @@ -359,7 +359,7 @@ export default >(superClass: T) => configFlow ); - // Ignore the repsonse if user switched languages before we got response + // Ignore the response if user switched languages before we got response if (this.hass!.language !== language) { return this.hass!.localize; } diff --git a/src/translations/en.json b/src/translations/en.json index 8c27047f2f..069c869a71 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1054,7 +1054,7 @@ }, "end_after": { "label": "End after", - "ocurrences": "ocurrences" + "ocurrences": "occurrences" } }, "rrule": { @@ -2496,7 +2496,7 @@ "enable_remote": "[%key:ui::common::enable%]", "internal_url_automatic": "Automatic", "internal_url_https_error_title": "Invalid local network URL", - "internal_url_https_error_description": "You have configured an HTTPS certificate in Home Assistant. This means that your internal URL needs to be set to a domain covered by the certficate.", + "internal_url_https_error_description": "You have configured an HTTPS certificate in Home Assistant. This means that your internal URL needs to be set to a domain covered by the certificate.", "internal_url_automatic_description": "Use the configured network settings", "internal_url_placeholder": "http://:8123" }, @@ -3877,8 +3877,8 @@ }, "edit_category": "[%key:ui::panel::config::automation::picker::edit_category%]", "assign_category": "[%key:ui::panel::config::automation::picker::assign_category%]", - "no_category_support": "You can't assign an category to this script", - "no_category_entity_reg": "To assign an category to an script it needs to have a unique ID.", + "no_category_support": "You can't assign a category to this script", + "no_category_entity_reg": "To assign a category to a script it needs to have a unique ID.", "delete": "[%key:ui::common::delete%]", "duplicate": "[%key:ui::common::duplicate%]", "empty_header": "Create your first script", @@ -3993,8 +3993,8 @@ }, "edit_category": "[%key:ui::panel::config::automation::picker::edit_category%]", "assign_category": "[%key:ui::panel::config::automation::picker::assign_category%]", - "no_category_support": "You can't assign an category to this scene", - "no_category_entity_reg": "To assign an category to an scene it needs to have a unique ID.", + "no_category_support": "You can't assign a category to this scene", + "no_category_entity_reg": "To assign a category to an scene it needs to have a unique ID.", "empty_header": "Create your first scene", "empty_text": "Scenes capture entities' states, so you can re-experience the same scene later on. For example, a ''Watching TV'' scene that dims the living room lights, sets a warm white color and turns on the TV.", "search": "Search {number} {number, plural,\n one {scene}\n other {scenes}\n}" @@ -4585,7 +4585,7 @@ "application_credentials": { "delete_title": "Application credentials", "delete_prompt": "Would you like to also delete Application Credentials for this integration?", - "delete_detail": "If you delete them, you will need to enter credentials when setting up the integration again. If you keep them, they will be used automatically when setting up the integration again or may be acccessed from the Application Credentials menu.", + "delete_detail": "If you delete them, you will need to enter credentials when setting up the integration again. If you keep them, they will be used automatically when setting up the integration again or may be accessed from the Application Credentials menu.", "delete_error_title": "Deleting application credentials failed", "dismiss": "Keep", "learn_more": "Learn more about application credentials" @@ -5512,7 +5512,7 @@ "ping_node": { "title": "Ping a Matter device", "introduction": "Perform a (server-side) ping on your Matter device on all its (known) IP-addresses.", - "battery_device_warning": "Note that especially for battery powered devices this can take a a while. You may need to wake up battery powered devices before starting the pinging to speed up the process. Refer to your device's manual for instructions on how to wake the device.", + "battery_device_warning": "Note that especially for battery powered devices this can take a while. You may need to wake up battery powered devices before starting the pinging to speed up the process. Refer to your device's manual for instructions on how to wake the device.", "start_ping": "Start ping", "in_progress": "The device is being pinged. This may take some time.", "ping_failed": "The device ping failed. Additional information may be available in the logs.", @@ -5681,7 +5681,7 @@ }, "version": { "title": "Samba/Windows (CIFS) version", - "description": "This choses the version of the protocol to use" + "description": "This chooses the version of the protocol to use" }, "username": { "title": "Username", diff --git a/src/util/common-translation.ts b/src/util/common-translation.ts index 7cae549c0a..105da9b977 100644 --- a/src/util/common-translation.ts +++ b/src/util/common-translation.ts @@ -42,7 +42,7 @@ export function findAvailableLanguage(language: string) { return language; } - // Perform case-insenstive comparison since browser isn't required to + // Perform case-insensitive comparison since browser isn't required to // report languages with specific cases. const langLower = language.toLowerCase(); diff --git a/test/common/entity/attribute_class_names.test.ts b/test/common/entity/attribute_class_names.test.ts index d3377beddd..51e9339f00 100644 --- a/test/common/entity/attribute_class_names.test.ts +++ b/test/common/entity/attribute_class_names.test.ts @@ -9,7 +9,7 @@ describe("attributeClassNames", () => { assert.strictEqual(attributeClassNames(stateObj, attrs), ""); }); - it("Matches no attrbutes", () => { + it("Matches no attributes", () => { const stateObj: any = { attributes: { other_attr_1: 1, @@ -19,7 +19,7 @@ describe("attributeClassNames", () => { assert.strictEqual(attributeClassNames(stateObj, attrs), ""); }); - it("Matches one attrbute", () => { + it("Matches one attribute", () => { const stateObj: any = { attributes: { other_attr_1: 1, @@ -30,7 +30,7 @@ describe("attributeClassNames", () => { assert.strictEqual(attributeClassNames(stateObj, attrs), "has-mock_attr1"); }); - it("Matches two attrbutes", () => { + it("Matches two attributes", () => { const stateObj: any = { attributes: { other_attr_1: 1, From 760d898de70a2d90a5139f4cc326a30d75580251 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 6 Jan 2025 14:13:07 +0100 Subject: [PATCH 59/89] Remove backup toggle from supervisor addon page when update available (#23602) --- .../update-available/update-available-card.ts | 47 +------------------ src/data/hassio/addon.ts | 7 +-- src/data/supervisor/core.ts | 7 +-- src/translations/en.json | 4 +- 4 files changed, 7 insertions(+), 58 deletions(-) diff --git a/hassio/src/update-available/update-available-card.ts b/hassio/src/update-available/update-available-card.ts index 91185d3a58..f61eb140f5 100644 --- a/hassio/src/update-available/update-available-card.ts +++ b/hassio/src/update-available/update-available-card.ts @@ -18,10 +18,7 @@ import "../../../src/components/ha-checkbox"; import "../../../src/components/ha-faded"; import "../../../src/components/ha-icon-button"; import "../../../src/components/ha-markdown"; -import "../../../src/components/ha-settings-row"; import "../../../src/components/ha-svg-icon"; -import "../../../src/components/ha-switch"; -import type { HaSwitch } from "../../../src/components/ha-switch"; import type { HassioAddonDetails } from "../../../src/data/hassio/addon"; import { fetchHassioAddonChangelog, @@ -163,19 +160,6 @@ class UpdateAvailableCard extends LitElement { )}

- ${["core", "addon"].includes(this._updateType) - ? html` -
- - - ${this.supervisor.localize( - "update_available.create_backup" - )} - - - - ` - : nothing} ` : html` => { if (atLeastVersion(hass.config.version, 2021, 2, 4)) { await hass.callWS({ @@ -322,13 +321,11 @@ export const updateHassioAddon = async ( endpoint: `/store/addons/${slug}/update`, method: "post", timeout: null, - data: { backup }, }); } else { await hass.callApi>( "POST", - `hassio/addons/${slug}/update`, - { backup } + `hassio/addons/${slug}/update` ); } }; diff --git a/src/data/supervisor/core.ts b/src/data/supervisor/core.ts index c3264e5225..8eaf8b91d8 100644 --- a/src/data/supervisor/core.ts +++ b/src/data/supervisor/core.ts @@ -6,18 +6,15 @@ export const restartCore = async (hass: HomeAssistant) => { await hass.callService("homeassistant", "restart"); }; -export const updateCore = async (hass: HomeAssistant, backup: boolean) => { +export const updateCore = async (hass: HomeAssistant) => { if (atLeastVersion(hass.config.version, 2021, 2, 4)) { await hass.callWS({ type: "supervisor/api", endpoint: "/core/update", method: "post", timeout: null, - data: { backup }, }); } else { - await hass.callApi>("POST", `hassio/core/update`, { - backup, - }); + await hass.callApi>("POST", "hassio/core/update"); } }; diff --git a/src/translations/en.json b/src/translations/en.json index 069c869a71..02586cbd70 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1269,7 +1269,6 @@ "clear_skipped": "Clear skipped", "install": "Install", "update": "Update", - "create_backup": "Create backup before updating", "auto_update_enabled_title": "Can not skip version", "auto_update_enabled_text": "Automatic updates for this item have been enabled; skipping it is, therefore, unavailable. You can either install this update now or wait for Home Assistant to do it automatically." }, @@ -8001,8 +8000,7 @@ "update_available": { "update_name": "Update {name}", "open_release_notes": "Open release notes", - "create_backup": "Create backup before updating", - "description": "You have {version} installed. Click update to update to version {newest_version}", + "description": "You have {version} installed. Press update to update to version {newest_version}", "updating": "Updating {name} to version {version}", "no_update": "No update available for {name}" }, From 4d2e9f203ff55444f6b5fe9a862411046164b467 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Mon, 6 Jan 2025 17:30:37 +0100 Subject: [PATCH 60/89] Improve error handling in backup status banner (#23604) * Improve error handling in backup status banner * Fix completion * Fix loading * Check attempt and completion date first --- .../overview/ha-backup-overview-summary.ts | 101 +++++++++++++----- src/panels/config/backup/ha-config-backup.ts | 40 ++++--- 2 files changed, 99 insertions(+), 42 deletions(-) diff --git a/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts b/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts index e6e4d78195..42ed30d5f5 100644 --- a/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts +++ b/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts @@ -31,14 +31,24 @@ class HaBackupOverviewBackups extends LitElement { @property({ type: Boolean }) public fetching = false; - private _lastSuccessfulBackup = memoizeOne((backups: BackupContent[]) => { - const sortedBackups = backups + private _sortedBackups = memoizeOne((backups: BackupContent[]) => + backups .filter((backup) => backup.with_automatic_settings) - .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); + .sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()) + ); + private _lastBackup = memoizeOne((backups: BackupContent[]) => { + const sortedBackups = this._sortedBackups(backups); return sortedBackups[0] as BackupContent | undefined; }); + private _lastUploadedBackup = memoizeOne((backups: BackupContent[]) => { + const sortedBackups = this._sortedBackups(backups); + return sortedBackups.find( + (backup) => backup.failed_agent_ids?.length === 0 + ); + }); + private _nextBackupDescription(schedule: BackupScheduleState) { const time = getFormattedBackupTime(this.hass.locale, this.hass.config); @@ -65,6 +75,8 @@ class HaBackupOverviewBackups extends LitElement { } protected render() { + const now = new Date(); + if (this.fetching) { return html` @@ -82,24 +94,28 @@ class HaBackupOverviewBackups extends LitElement { `; } - const lastSuccessfulBackup = this._lastSuccessfulBackup(this.backups); + const lastBackup = this._lastBackup(this.backups); - const lastAttempt = this.config.last_attempted_automatic_backup + const nextBackupDescription = this._nextBackupDescription( + this.config.schedule.state + ); + + const lastAttemptDate = this.config.last_attempted_automatic_backup ? new Date(this.config.last_attempted_automatic_backup) - : undefined; + : new Date(0); - const lastCompletedBackupDate = this.config.last_completed_automatic_backup + const lastCompletedDate = this.config.last_completed_automatic_backup ? new Date(this.config.last_completed_automatic_backup) - : undefined; + : new Date(0); - const now = new Date(); + // If last attempt is after last completed backup, show error + if (lastAttemptDate > lastCompletedDate) { + const description = `The last automatic backup triggered ${relativeTime(lastAttemptDate, this.hass.locale, now, true)} wasn't successful.`; + const lastUploadedBackup = this._lastUploadedBackup(this.backups); + const secondaryDescription = lastUploadedBackup + ? `Last successful backup ${relativeTime(new Date(lastUploadedBackup.date), this.hass.locale, now, true)} and stored in ${lastUploadedBackup.agent_ids?.length} locations.` + : nextBackupDescription; - const lastBackupDescription = lastSuccessfulBackup - ? `Last successful backup ${relativeTime(new Date(lastSuccessfulBackup.date), this.hass.locale, now, true)} and stored in ${lastSuccessfulBackup.agent_ids?.length} locations.` - : "You have no successful backups."; - - if (lastAttempt && lastAttempt > (lastCompletedBackupDate || 0)) { - const lastAttemptDescription = `The last automatic backup triggered ${relativeTime(lastAttempt, this.hass.locale, now, true)} wasn't successful.`; return html` - ${lastAttemptDescription} + ${description} - ${lastBackupDescription} + ${secondaryDescription} `; } - const nextBackupDescription = this._nextBackupDescription( - this.config.schedule.state - ); - - if (!lastSuccessfulBackup) { + // If no backups yet, show warning + if (!lastBackup) { + const description = "You have no automatic backups yet."; return html` + + + ${description} + ${nextBackupDescription} @@ -140,10 +157,41 @@ class HaBackupOverviewBackups extends LitElement { `; } + const lastBackupDate = new Date(lastBackup.date); + + // If last backup + if (lastBackup.failed_agent_ids?.length) { + const description = `The last automatic backup created ${relativeTime(lastBackupDate, this.hass.locale, now, true)} wasn't stored in all locations.`; + const lastUploadedBackup = this._lastUploadedBackup(this.backups); + const secondaryDescription = lastUploadedBackup + ? `Last successful backup ${relativeTime(new Date(lastUploadedBackup.date), this.hass.locale, now, true)} and stored in ${lastUploadedBackup.agent_ids?.length} locations.` + : nextBackupDescription; + + return html` + + + + + ${description} + + + + ${secondaryDescription} + + + + `; + } + + const description = `Last successful backup ${relativeTime(lastBackupDate, this.hass.locale, now, true)} and stored in ${lastBackup.agent_ids?.length} locations.`; + const numberOfDays = differenceInDays( // Subtract a few hours to avoid showing as overdue if it's just a few hours (e.g. daylight saving) addHours(now, -OVERDUE_MARGIN_HOURS), - new Date(lastSuccessfulBackup.date) + lastBackupDate ); const isOverdue = @@ -160,7 +208,7 @@ class HaBackupOverviewBackups extends LitElement { - ${lastBackupDescription} + ${description} @@ -170,12 +218,13 @@ class HaBackupOverviewBackups extends LitElement { `; } + return html` - ${lastBackupDescription} + ${description} diff --git a/src/panels/config/backup/ha-config-backup.ts b/src/panels/config/backup/ha-config-backup.ts index 7f4a761a48..899c2e448d 100644 --- a/src/panels/config/backup/ha-config-backup.ts +++ b/src/panels/config/backup/ha-config-backup.ts @@ -1,6 +1,12 @@ import type { UnsubscribeFunc } from "home-assistant-js-websocket"; import type { PropertyValues } from "lit"; import { customElement, property, state } from "lit/decorators"; +import type { BackupConfig, BackupContent } from "../../../data/backup"; +import { + compareAgents, + fetchBackupConfig, + fetchBackupInfo, +} from "../../../data/backup"; import type { ManagerStateEvent } from "../../../data/backup_manager"; import { DEFAULT_MANAGER_STATE, @@ -15,12 +21,6 @@ import type { HomeAssistant } from "../../../types"; import { showToast } from "../../../util/toast"; import "./ha-config-backup-backups"; import "./ha-config-backup-overview"; -import type { BackupConfig, BackupContent } from "../../../data/backup"; -import { - compareAgents, - fetchBackupConfig, - fetchBackupInfo, -} from "../../../data/backup"; declare global { interface HASSDomEvents { @@ -47,13 +47,7 @@ class HaConfigBackup extends SubscribeMixin(HassRouterPage) { protected firstUpdated(changedProps: PropertyValues) { super.firstUpdated(changedProps); - this._fetching = true; - Promise.all([this._fetchBackupInfo(), this._fetchBackupConfig()]).finally( - () => { - this._fetching = false; - } - ); - + this._fetchAll(); this.addEventListener("ha-refresh-backup-info", () => { this._fetchBackupInfo(); }); @@ -62,6 +56,15 @@ class HaConfigBackup extends SubscribeMixin(HassRouterPage) { }); } + private _fetchAll() { + this._fetching = true; + Promise.all([this._fetchBackupInfo(), this._fetchBackupConfig()]).finally( + () => { + this._fetching = false; + } + ); + } + public connectedCallback() { super.connectedCallback(); if (this.hasUpdated) { @@ -128,11 +131,16 @@ class HaConfigBackup extends SubscribeMixin(HassRouterPage) { public hassSubscribe(): Promise[] { return [ subscribeBackupEvents(this.hass!, (event) => { + const curState = this._manager.manager_state; + this._manager = event; + if ( + event.manager_state === "idle" && + event.manager_state !== curState + ) { + this._fetchAll(); + } if ("state" in event) { - if (event.state === "completed" || event.state === "failed") { - this._fetchBackupInfo(); - } if (event.state === "failed") { let message = ""; switch (this._manager.manager_state) { From 2019b8992e648e56fa5bb8d92b93c38edcbf2572 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 6 Jan 2025 17:56:38 +0100 Subject: [PATCH 61/89] Fix tooltip more info (#23605) --- src/components/entity/state-info.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/entity/state-info.ts b/src/components/entity/state-info.ts index 8822ca2d02..ec994875b3 100644 --- a/src/components/entity/state-info.ts +++ b/src/components/entity/state-info.ts @@ -38,12 +38,11 @@ class StateInfo extends LitElement { ${this.inDialog ? html`
- +
@@ -99,6 +98,7 @@ class StateInfo extends LitElement { height: 100%; min-width: 0; text-align: var(--float-start); + position: relative; } .name { From befc650f81b5e09d4efefb1d72bb2986c3ba057b Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Mon, 6 Jan 2025 18:10:12 +0100 Subject: [PATCH 62/89] Bumped version to 20250106.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 43d693b7b8..801c103f69 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20250103.0" +version = "20250106.0" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md" From 6d084813d51057ea946240563442d7a80f18f396 Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Mon, 6 Jan 2025 07:49:22 +0100 Subject: [PATCH 63/89] Add missing localizations for Voice Assistants > Expose headers (#23452) * Add missing localizations for Voice Assistants > Expose headers * Add localizable labels to Icon and Remove columns * Variant with all changes on a single line * Prettier? * Revert * Line length limited to 80 chars --- .../voice-assistants/ha-config-voice-assistants-expose.ts | 4 ++++ src/translations/en.json | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/panels/config/voice-assistants/ha-config-voice-assistants-expose.ts b/src/panels/config/voice-assistants/ha-config-voice-assistants-expose.ts index 4241500ee8..2f4a740366 100644 --- a/src/panels/config/voice-assistants/ha-config-voice-assistants-expose.ts +++ b/src/panels/config/voice-assistants/ha-config-voice-assistants-expose.ts @@ -146,6 +146,7 @@ export class VoiceAssistantsExpose extends LitElement { ): DataTableColumnContainer => ({ icon: { title: "", + label: localize("ui.panel.config.voice_assistants.expose.headers.icon"), type: "icon", moveable: false, hidden: narrow, @@ -241,6 +242,9 @@ export class VoiceAssistantsExpose extends LitElement { }, remove: { title: "", + label: localize( + "ui.panel.config.voice_assistants.expose.headers.remove" + ), type: "icon-button", hidden: narrow, template: () => diff --git a/src/translations/en.json b/src/translations/en.json index 02586cbd70..ea4f49db79 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2826,12 +2826,14 @@ "expose": { "caption": "Expose", "headers": { + "icon": "Icon", "name": "Name", "entity_id": "Entity ID", "area": "Area", "domain": "Domain", "assistants": "Assistants", - "aliases": "Aliases" + "aliases": "Aliases", + "remove": "[%key:ui::common::remove%]" }, "aliases": "{count} aliases", "expose": "Expose", From f53ac94e761ae16704b136af3b8478fa5781e573 Mon Sep 17 00:00:00 2001 From: Norbert Rittel Date: Mon, 6 Jan 2025 07:47:37 +0100 Subject: [PATCH 64/89] Add missing `ui.panel.config.labels.headers.description` (#23517) The header "Description" for the Labels list only shows up as optional in a narrow view like on mobile. This commit adds the missing string for proper localization. --- src/translations/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/translations/en.json b/src/translations/en.json index ea4f49db79..b513f6a8b3 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2113,6 +2113,7 @@ "description": "Group devices and entities", "headers": { "name": "Name", + "description": "Description", "icon": "Icon", "color": "Color" }, From 0ee6548650d4dffd6ee00251a3e874c63bc138cc Mon Sep 17 00:00:00 2001 From: Wendelin <12148533+wendevlin@users.noreply.github.com> Date: Wed, 8 Jan 2025 21:05:28 +0100 Subject: [PATCH 65/89] Add backup translations (#23365) * Add translations * Add summary and progress translations * Add backups and settings translations * Add backups page translations * Add onboarding card translations * Add settings translations * Add details translations * Translate delete * Add data picker translations * Use local add-ons * Add encryption key translations * Add new, generate and upload translations * Add translations for restore backup * Fix ts issue * Add missing keys --------- Co-authored-by: Paul Bottein --- src/data/backup.ts | 17 +- .../config/ha-backup-config-agents.ts | 24 +- .../config/ha-backup-config-data.ts | 80 +++- .../config/ha-backup-config-encryption-key.ts | 65 ++- .../config/ha-backup-config-schedule.ts | 117 ++++-- .../components/ha-backup-data-picker.ts | 25 +- .../overview/ha-backup-overview-backups.ts | 32 +- .../overview/ha-backup-overview-onboarding.ts | 16 +- .../overview/ha-backup-overview-progress.ts | 78 +--- .../overview/ha-backup-overview-settings.ts | 130 +++--- .../overview/ha-backup-overview-summary.ts | 159 ++++--- .../dialogs/dialog-backup-onboarding.ts | 86 ++-- .../dialog-change-backup-encryption-key.ts | 103 +++-- .../backup/dialogs/dialog-generate-backup.ts | 72 +++- .../backup/dialogs/dialog-new-backup.ts | 28 +- .../backup/dialogs/dialog-restore-backup.ts | 126 +++--- .../dialog-set-backup-encryption-key.ts | 156 ++++--- .../dialog-show-backup-encryption-key.ts | 32 +- .../backup/dialogs/dialog-upload-backup.ts | 26 +- .../config/backup/ha-config-backup-backups.ts | 120 +++--- .../config/backup/ha-config-backup-details.ts | 91 +++- .../backup/ha-config-backup-overview.ts | 10 +- .../backup/ha-config-backup-settings.ts | 64 ++- src/translations/en.json | 394 +++++++++++++++++- 24 files changed, 1483 insertions(+), 568 deletions(-) diff --git a/src/data/backup.ts b/src/data/backup.ts index d68d74c5ef..5133787f03 100644 --- a/src/data/backup.ts +++ b/src/data/backup.ts @@ -241,7 +241,7 @@ export const computeBackupAgentName = ( agentIds?: string[] ) => { if (isLocalAgent(agentId)) { - return "This system"; + return localize("ui.panel.config.backup.agents.local_agent"); } const [domain, name] = agentId.split("."); @@ -298,23 +298,22 @@ export const generateEmergencyKit = ( encryptionKey: string ) => "data:text/plain;charset=utf-8," + - encodeURIComponent(`Home Assistant Backup Emergency Kit + encodeURIComponent(`${hass.localize("ui.panel.config.backup.emergency_kit_file.title")} -This emergency kit contains your backup encryption key. You need this key -to be able to restore your Home Assistant backups. +${hass.localize("ui.panel.config.backup.emergency_kit_file.description")} -Date: ${formatDateTime(new Date(), hass.locale, hass.config)} +${hass.localize("ui.panel.config.backup.emergency_kit_file.date")} ${formatDateTime(new Date(), hass.locale, hass.config)} -Instance: +${hass.localize("ui.panel.config.backup.emergency_kit_file.instance")} ${hass.config.location_name} -URL: +${hass.localize("ui.panel.config.backup.emergency_kit_file.url")} ${hass.auth.data.hassUrl} -Encryption key: +${hass.localize("ui.panel.config.backup.emergency_kit_file.encryption_key")} ${encryptionKey} -For more information visit: https://www.home-assistant.io/more-info/backup-emergency-kit`); +${hass.localize("ui.panel.config.backup.emergency_kit_file.more_info", { link: "https://www.home-assistant.io/more-info/backup-emergency-kit" })}`); export const geneateEmergencyKitFileName = ( hass: HomeAssistant, diff --git a/src/panels/config/backup/components/config/ha-backup-config-agents.ts b/src/panels/config/backup/components/config/ha-backup-config-agents.ts index 3a1706f3d4..09216f54e3 100644 --- a/src/panels/config/backup/components/config/ha-backup-config-agents.ts +++ b/src/panels/config/backup/components/config/ha-backup-config-agents.ts @@ -52,12 +52,18 @@ class HaBackupConfigAgents extends LitElement { private _description(agentId: string) { if (agentId === CLOUD_AGENT) { if (this.cloudStatus.logged_in && !this.cloudStatus.active_subscription) { - return "You currently do not have an active Home Assistant Cloud subscription."; + return this.hass.localize( + "ui.panel.config.backup.agents.cloud_agent_no_subcription" + ); } - return "Note: It stores only one backup with a maximum size of 5 GB, regardless of your settings."; + return this.hass.localize( + "ui.panel.config.backup.agents.cloud_agent_description" + ); } if (isNetworkMountAgent(agentId)) { - return "Network storage"; + return this.hass.localize( + "ui.panel.config.backup.agents.network_mount_agent_description" + ); } return ""; } @@ -107,7 +113,7 @@ class HaBackupConfigAgents extends LitElement { slot="start" /> `} -
${name}
+
${name}
${description ? html`
${description}
` : nothing} @@ -124,7 +130,9 @@ class HaBackupConfigAgents extends LitElement { })} ` - : html`

No sync agents configured

`} + : html`

+ ${this.hass.localize("ui.panel.config.backup.agents.no_agents")} +

`} `; } @@ -157,6 +165,12 @@ class HaBackupConfigAgents extends LitElement { --md-list-item-leading-space: 0; --md-list-item-trailing-space: 0; } + ha-md-list-item { + --md-item-overflow: visible; + } + ha-md-list-item .name { + word-break: break-word; + } ha-md-list-item img { width: 48px; } diff --git a/src/panels/config/backup/components/config/ha-backup-config-data.ts b/src/panels/config/backup/components/config/ha-backup-config-data.ts index 5c3dce968b..7d48cb6906 100644 --- a/src/panels/config/backup/components/config/ha-backup-config-data.ts +++ b/src/panels/config/backup/components/config/ha-backup-config-data.ts @@ -158,11 +158,17 @@ class HaBackupConfigData extends LitElement { - Home Assistant settings + + ${this.hass.localize("ui.panel.config.backup.data.ha_settings")} + ${this.forceHomeAssistant - ? "The bare minimum needed to restore the system. It is always included in automatic backup data." - : "The bare minimum needed to restore your system."} + ? this.hass.localize( + "ui.panel.config.backup.data.ha_settings_included_description" + ) + : this.hass.localize( + "ui.panel.config.backup.data.ha_settings_description" + )} - History + + ${this.hass.localize("ui.panel.config.backup.data.history")} + - Historical data of your sensors, including your energy dashboard. + ${this.hass.localize( + "ui.panel.config.backup.data.history_description" + )} - Media + + ${this.hass.localize("ui.panel.config.backup.data.media")} + - This can include large filesize camera recordings. + ${this.hass.localize( + "ui.panel.config.backup.data.history_description" + )} - Share folder + + ${this.hass.localize( + "ui.panel.config.backup.data.share_folder" + )} + - Folder that is often used by add-ons for advanced or older - configurations. + ${this.hass.localize( + "ui.panel.config.backup.data.share_folder_description" + )} - Local addons folder + + ${this.hass.localize( + "ui.panel.config.backup.data.local_addons" + )} + - Folder that contains the data of your local add-ons. + ${this.hass.localize( + "ui.panel.config.backup.data.local_addons_description" + )} - Add-ons + + ${this.hass.localize( + "ui.panel.config.backup.data.addons" + )} + - Select what add-ons you want to include. + ${this.hass.localize( + "ui.panel.config.backup.data.addons_description" + )} -
All
+
+ ${this.hass.localize( + "ui.panel.config.backup.data.addons_all" + )} +
-
None
+
+ ${this.hass.localize( + "ui.panel.config.backup.data.addons_none" + )} +
-
Custom
+
+ ${this.hass.localize( + "ui.panel.config.backup.data.addons_custom" + )} +
@@ -327,6 +370,9 @@ class HaBackupConfigData extends LitElement { --md-list-item-leading-space: 0; --md-list-item-trailing-space: 0; } + ha-md-list-item { + --md-item-overflow: visible; + } ha-md-select { min-width: 210px; } diff --git a/src/panels/config/backup/components/config/ha-backup-config-encryption-key.ts b/src/panels/config/backup/components/config/ha-backup-config-encryption-key.ts index 4254215d55..f554a04230 100644 --- a/src/panels/config/backup/components/config/ha-backup-config-encryption-key.ts +++ b/src/panels/config/backup/components/config/ha-backup-config-encryption-key.ts @@ -26,29 +26,55 @@ class HaBackupConfigEncryptionKey extends LitElement { return html` - Download emergency kit + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit" + )} + - We recommend to save this encryption key somewhere secure. + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit_description" + )} - Download + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit_action" + )} - Show my encryption key - - Please keep your encryption key private. + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.show_encryption_key" + )} - Show + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.show_encryption_key_description" + )} + + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.show_encryption_key_action" + )} + - Change encryption key + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.change_encryption_key" + )} + - All next backups will use this encryption key. + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.change_encryption_key_description" + )} - Change + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.change_encryption_key_action" + )} @@ -58,11 +84,21 @@ class HaBackupConfigEncryptionKey extends LitElement { return html` - Set encryption key + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.set_encryption_key" + )} - Set an encryption key for your backups. + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.set_encryption_key_description" + )} - Set + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.set_encryption_key_action" + )} `; @@ -102,6 +138,9 @@ class HaBackupConfigEncryptionKey extends LitElement { --md-list-item-leading-space: 0; --md-list-item-trailing-space: 0; } + ha-md-list-item { + --md-item-overflow: visible; + } .danger { --mdc-theme-primary: var(--error-color); } diff --git a/src/panels/config/backup/components/config/ha-backup-config-schedule.ts b/src/panels/config/backup/components/config/ha-backup-config-schedule.ts index b2402625e8..2e7ba79063 100644 --- a/src/panels/config/backup/components/config/ha-backup-config-schedule.ts +++ b/src/panels/config/backup/components/config/ha-backup-config-schedule.ts @@ -43,6 +43,23 @@ const RETENTION_PRESETS: Record< forever: { type: "days", value: 0 }, }; +const SCHEDULE_OPTIONS = [ + BackupScheduleState.DAILY, + BackupScheduleState.MONDAY, + BackupScheduleState.TUESDAY, + BackupScheduleState.WEDNESDAY, + BackupScheduleState.THURSDAY, + BackupScheduleState.FRIDAY, + BackupScheduleState.SATURDAY, + BackupScheduleState.SUNDAY, +] as const satisfies BackupScheduleState[]; + +const RETENTION_PRESETS_OPTIONS = [ + RetentionPreset.COPIES_3, + RetentionPreset.FOREVER, + RetentionPreset.CUSTOM, +] as const satisfies RetentionPreset[]; + const computeRetentionPreset = ( data: RetentionData ): RetentionPreset | undefined => { @@ -128,7 +145,11 @@ class HaBackupConfigSchedule extends LitElement { return html` - Use automatic backups + + ${this.hass.localize( + "ui.panel.config.backup.schedule.use_automatic_backups" + )} + - Schedule + + ${this.hass.localize( + "ui.panel.config.backup.schedule.schedule" + )} + - How often you want to create a backup. + ${this.hass.localize( + "ui.panel.config.backup.schedule.schedule_description" + )} - -
Daily at ${time}
-
- -
Monday at ${time}
-
- -
Tuesday at ${time}
-
- -
Wednesday at ${time}
-
- -
Thursday at ${time}
-
- -
Friday at ${time}
-
- -
Saturday at ${time}
-
- -
Sunday at ${time}
-
+ ${SCHEDULE_OPTIONS.map( + (option) => html` + +
+ ${this.hass.localize( + `ui.panel.config.backup.schedule.schedule_options.${option}`, + { time } + )} +
+
+ ` + )}
- Backups to keep + + ${this.hass.localize( + `ui.panel.config.backup.schedule.retention` + )} + - Based on the maximum number of backups or how many days they - should be kept. + ${this.hass.localize( + `ui.panel.config.backup.schedule.retention_description` + )} - -
3 backups
-
- -
All backups
-
- -
Custom
-
+ ${RETENTION_PRESETS_OPTIONS.map( + (option) => html` + +
+ ${this.hass.localize( + `ui.panel.config.backup.schedule.retention_presets.${option}` + )} +
+
+ ` + )}
${this._retentionPreset === RetentionPreset.CUSTOM @@ -217,11 +239,17 @@ class HaBackupConfigSchedule extends LitElement { .value=${data.retention.type} id="type" > - -
days
+ +
+ ${this.hass.localize( + "ui.panel.config.backup.schedule.retention_units.days" + )} +
- -
backups
+ + ${this.hass.localize( + "ui.panel.config.backup.schedule.retention_units.copies" + )} @@ -320,6 +348,9 @@ class HaBackupConfigSchedule extends LitElement { --md-list-item-leading-space: 0; --md-list-item-trailing-space: 0; } + ha-md-list-item { + --md-item-overflow: visible; + } ha-md-select { min-width: 210px; } diff --git a/src/panels/config/backup/components/ha-backup-data-picker.ts b/src/panels/config/backup/components/ha-backup-data-picker.ts index ac476bb2d5..57a7bd3e52 100644 --- a/src/panels/config/backup/components/ha-backup-data-picker.ts +++ b/src/panels/config/backup/components/ha-backup-data-picker.ts @@ -77,7 +77,11 @@ export class HaBackupDataPicker extends LitElement { if (data.homeassistant_included) { items.push({ - label: "Settings", + label: data.database_included + ? this.hass.localize( + "ui.panel.config.backup.data_picker.settings_and_history" + ) + : this.hass.localize("ui.panel.config.backup.data_picker.settings"), id: "config", version: data.homeassistant_version, }); @@ -99,8 +103,17 @@ export class HaBackupDataPicker extends LitElement { ); private _localizeFolder(folder: string): string { - if (folder === "addons/local") { - return "Local addons"; + switch (folder) { + case "media": + return this.hass.localize("ui.panel.config.backup.data_picker.media"); + case "share": + return this.hass.localize( + "ui.panel.config.backup.data_picker.share_folder" + ); + case "addons/local": + return this.hass.localize( + "ui.panel.config.backup.data_picker.local_addons" + ); } return capitalizeFirstLetter(folder); } @@ -226,7 +239,7 @@ export class HaBackupDataPicker extends LitElement { @@ -272,7 +285,9 @@ export class HaBackupDataPicker extends LitElement { diff --git a/src/panels/config/backup/components/overview/ha-backup-overview-backups.ts b/src/panels/config/backup/components/overview/ha-backup-overview-backups.ts index a667e1a4bc..43b93be39f 100644 --- a/src/panels/config/backup/components/overview/ha-backup-overview-backups.ts +++ b/src/panels/config/backup/components/overview/ha-backup-overview-backups.ts @@ -54,7 +54,9 @@ class HaBackupOverviewBackups extends LitElement { return html` -
My backups
+
+ ${this.hass.localize("ui.panel.config.backup.overview.backups.title")} +
- ${automaticStats.count} automatic backups + ${this.hass.localize( + "ui.panel.config.backup.overview.backups.automatic", + { count: automaticStats.count } + )}
- ${bytesToString(automaticStats.size, 1)} in total + ${this.hass.localize( + "ui.panel.config.backup.overview.backups.total_size", + { size: bytesToString(automaticStats.size, 1) } + )}
@@ -75,9 +83,17 @@ class HaBackupOverviewBackups extends LitElement { href="/config/backup/backups?type=manual" > -
${manualStats.count} manual backups
+
+ ${this.hass.localize( + "ui.panel.config.backup.overview.backups.automatic", + { count: manualStats.count } + )} +
- ${bytesToString(manualStats.size, 1)} in total + ${this.hass.localize( + "ui.panel.config.backup.overview.backups.total_size", + { size: bytesToString(manualStats.size, 1) } + )}
@@ -85,7 +101,11 @@ class HaBackupOverviewBackups extends LitElement {
diff --git a/src/panels/config/backup/components/overview/ha-backup-overview-onboarding.ts b/src/panels/config/backup/components/overview/ha-backup-overview-onboarding.ts index a094cad22d..277c12a8d1 100644 --- a/src/panels/config/backup/components/overview/ha-backup-overview-onboarding.ts +++ b/src/panels/config/backup/components/overview/ha-backup-overview-onboarding.ts @@ -31,17 +31,23 @@ class HaBackupOverviewBackups extends LitElement {
- Set up backups + ${this.hass.localize( + "ui.panel.config.backup.overview.onboarding.title" + )}

- Backups are essential for a reliable smart home. They help protect - the work you've put into setting up your smart home, and if the - worst happens, you can get back up and running quickly. + ${this.hass.localize( + "ui.panel.config.backup.overview.onboarding.description" + )}

- Set up backups + ${this.hass.localize( + "ui.panel.config.backup.overview.onboarding.setup" + )}
`; diff --git a/src/panels/config/backup/components/overview/ha-backup-overview-progress.ts b/src/panels/config/backup/components/overview/ha-backup-overview-progress.ts index b9bfeffff6..223a351325 100644 --- a/src/panels/config/backup/components/overview/ha-backup-overview-progress.ts +++ b/src/panels/config/backup/components/overview/ha-backup-overview-progress.ts @@ -11,73 +11,39 @@ export class HaBackupOverviewProgress extends LitElement { @property({ attribute: false }) public manager!: ManagerStateEvent; private get _heading() { - switch (this.manager.manager_state) { - case "create_backup": - return "Creating backup"; - case "restore_backup": - return "Restoring backup"; - case "receive_backup": - return "Receiving backup"; - default: - return ""; + const state = this.manager.manager_state; + if (state === "idle") { + return ""; } + return this.hass.localize( + `ui.panel.config.backup.overview.progress.heading.${state}` + ); } private get _description() { switch (this.manager.manager_state) { case "create_backup": - switch (this.manager.stage) { - case "addon_repositories": - case "addons": - return "Backing up add-ons"; - case "await_addon_restarts": - return "Waiting for add-ons to restart"; - case "docker_config": - return "Backing up Docker configuration"; - case "finishing_file": - return "Finishing backup file"; - case "folders": - return "Backing up folders"; - case "home_assistant": - return "Backing up Home Assistant"; - case "upload_to_agents": - return "Uploading to locations"; - default: - return ""; + if (!this.manager.stage) { + return ""; } + return this.hass.localize( + `ui.panel.config.backup.overview.progress.description.create_backup.${this.manager.stage}` + ); case "restore_backup": - switch (this.manager.stage) { - case "addon_repositories": - case "addons": - return "Restoring add-ons"; - case "await_addon_restarts": - return "Waiting for add-ons to restart"; - case "await_home_assistant_restart": - return "Waiting for Home Assistant to restart"; - case "check_home_assistant": - return "Checking Home Assistant"; - case "docker_config": - return "Restoring Docker configuration"; - case "download_from_agent": - return "Downloading from location"; - case "folders": - return "Restoring folders"; - case "home_assistant": - return "Restoring Home Assistant"; - case "remove_delta_addons": - return "Removing delta add-ons"; - default: - return ""; + if (!this.manager.stage) { + return ""; } + return this.hass.localize( + `ui.panel.config.backup.overview.progress.description.restore_backup.${this.manager.stage}` + ); + case "receive_backup": - switch (this.manager.stage) { - case "receive_file": - return "Receiving file"; - case "upload_to_agents": - return "Uploading to locations"; - default: - return ""; + if (!this.manager.stage) { + return ""; } + return this.hass.localize( + `ui.panel.config.backup.overview.progress.description.receive_backup.${this.manager.stage}` + ); default: return ""; } diff --git a/src/panels/config/backup/components/overview/ha-backup-overview-settings.ts b/src/panels/config/backup/components/overview/ha-backup-overview-settings.ts index 3b3f56cdfb..97d13ca84c 100644 --- a/src/panels/config/backup/components/overview/ha-backup-overview-settings.ts +++ b/src/panels/config/backup/components/overview/ha-backup-overview-settings.ts @@ -34,42 +34,32 @@ class HaBackupBackupsSummary extends LitElement { const { state: schedule } = config.schedule; if (schedule === BackupScheduleState.NEVER) { - return "Automatic backups are not scheduled"; - } - - let copiesText = "and keep all backups"; - if (copies) { - copiesText = `and keep the latest ${copies} backup(s)`; - } else if (days) { - copiesText = `and keep backups for ${days} day(s)`; + return this.hass.localize( + "ui.panel.config.backup.overview.settings.schedule_never" + ); } const time = getFormattedBackupTime(this.hass.locale, this.hass.config); - let scheduleText = ""; - if (schedule === BackupScheduleState.DAILY) { - scheduleText = `Daily at ${time}`; - } - if (schedule === BackupScheduleState.MONDAY) { - scheduleText = `Weekly on Mondays at ${time}`; - } - if (schedule === BackupScheduleState.TUESDAY) { - scheduleText = `Weekly on Tuesdays at ${time}`; - } - if (schedule === BackupScheduleState.WEDNESDAY) { - scheduleText = `Weekly on Wednesdays at ${time}`; - } - if (schedule === BackupScheduleState.THURSDAY) { - scheduleText = `Weekly on Thursdays at ${time}`; - } - if (schedule === BackupScheduleState.FRIDAY) { - scheduleText = `Weekly on Fridays at ${time}`; - } - if (schedule === BackupScheduleState.SATURDAY) { - scheduleText = `Weekly on Saturdays at ${time}`; - } - if (schedule === BackupScheduleState.SUNDAY) { - scheduleText = `Weekly on Sundays at ${time}`; + const scheduleText = this.hass.localize( + `ui.panel.config.backup.overview.settings.schedule_${schedule}`, + { time } + ); + + let copiesText = this.hass.localize( + `ui.panel.config.backup.overview.settings.schedule_copies_all`, + { time } + ); + if (copies) { + copiesText = this.hass.localize( + `ui.panel.config.backup.overview.settings.schedule_copies_backups`, + { count: copies } + ); + } else if (days) { + copiesText = this.hass.localize( + `ui.panel.config.backup.overview.settings.schedule_copies_days`, + { count: days } + ); } return scheduleText + " " + copiesText; @@ -77,15 +67,23 @@ class HaBackupBackupsSummary extends LitElement { private _addonsDescription(config: BackupConfig): string { if (config.create_backup.include_all_addons) { - return "All add-ons"; + return this.hass.localize( + "ui.panel.config.backup.overview.settings.addons_all" + ); } - if (config.create_backup.include_addons?.length) { - return `${config.create_backup.include_addons.length} add-ons`; + const count = config.create_backup.include_addons?.length; + if (count) { + return this.hass.localize( + "ui.panel.config.backup.overview.settings.addons_many", + { count } + ); } - return "No add-ons"; + return this.hass.localize( + "ui.panel.config.backup.overview.settings.addons_none" + ); } - private _agentsDescription(config: BackupConfig): string { + private _locationsDescription(config: BackupConfig): string { const hasLocal = config.create_backup.agent_ids.some((a) => isLocalAgent(a) ); @@ -101,14 +99,24 @@ class HaBackupBackupsSummary extends LitElement { offsiteLocations[0], offsiteLocations ); - return `Upload to ${name}`; + return this.hass.localize( + "ui.panel.config.backup.overview.settings.locations_one", + { name } + ); } - return `Upload to ${offsiteLocations.length} off-site locations`; + return this.hass.localize( + "ui.panel.config.backup.overview.settings.locations_many", + { count: offsiteLocations.length } + ); } if (hasLocal) { - return "Local backup only"; + return this.hass.localize( + "ui.panel.config.backup.overview.settings.locations_local_only" + ); } - return "No location configured"; + return this.hass.localize( + "ui.panel.config.backup.overview.settings.locations_none" + ); } render() { @@ -116,7 +124,11 @@ class HaBackupBackupsSummary extends LitElement { return html` -
Backup settings
+
+ ${this.hass.localize( + "ui.panel.config.backup.overview.settings.title" + )} +
- Automatic backup schedule and retention + ${this.hass.localize( + "ui.panel.config.backup.overview.settings.schedule" + )}
@@ -136,11 +150,17 @@ class HaBackupBackupsSummary extends LitElement {
${this.config.create_backup.include_database - ? "Settings and history" - : "Settings only"} + ? this.hass.localize( + "ui.panel.config.backup.overview.settings.data_settings_history" + ) + : this.hass.localize( + "ui.panel.config.backup.overview.settings.data_settings_only" + )}
- Home Assistant data that is included + ${this.hass.localize( + "ui.panel.config.backup.overview.settings.data" + )}
@@ -154,7 +174,11 @@ class HaBackupBackupsSummary extends LitElement {
${this._addonsDescription(this.config)}
-
Add-ons that are included
+
+ ${this.hass.localize( + "ui.panel.config.backup.overview.settings.addons" + )} +
` @@ -164,9 +188,13 @@ class HaBackupBackupsSummary extends LitElement { href="/config/backup/settings#locations" > -
${this._agentsDescription(this.config)}
+
+ ${this._locationsDescription(this.config)} +
- Locations where backup is uploaded to + ${this.hass.localize( + "ui.panel.config.backup.overview.settings.locations" + )}
@@ -174,7 +202,9 @@ class HaBackupBackupsSummary extends LitElement {
- Configure backup settings + ${this.hass.localize( + "ui.panel.config.backup.overview.settings.configure" + )}
diff --git a/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts b/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts index 42ed30d5f5..09c12c0831 100644 --- a/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts +++ b/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts @@ -49,37 +49,17 @@ class HaBackupOverviewBackups extends LitElement { ); }); - private _nextBackupDescription(schedule: BackupScheduleState) { - const time = getFormattedBackupTime(this.hass.locale, this.hass.config); - - switch (schedule) { - case BackupScheduleState.DAILY: - return `Next automatic backup tomorrow at ${time}`; - case BackupScheduleState.MONDAY: - return `Next automatic backup next Monday at ${time}`; - case BackupScheduleState.TUESDAY: - return `Next automatic backup next Thuesday at ${time}`; - case BackupScheduleState.WEDNESDAY: - return `Next automatic backup next Wednesday at ${time}`; - case BackupScheduleState.THURSDAY: - return `Next automatic backup next Thursday at ${time}`; - case BackupScheduleState.FRIDAY: - return `Next automatic backup next Friday at ${time}`; - case BackupScheduleState.SATURDAY: - return `Next automatic backup next Saturday at ${time}`; - case BackupScheduleState.SUNDAY: - return `Next automatic backup next Sunday at ${time}`; - default: - return "No automatic backup scheduled"; - } - } - protected render() { const now = new Date(); if (this.fetching) { return html` - + @@ -96,8 +76,14 @@ class HaBackupOverviewBackups extends LitElement { const lastBackup = this._lastBackup(this.backups); - const nextBackupDescription = this._nextBackupDescription( - this.config.schedule.state + const backupTime = getFormattedBackupTime( + this.hass.locale, + this.hass.config + ); + + const nextBackupDescription = this.hass.localize( + `ui.panel.config.backup.overview.summary.next_backup_description.${this.config.schedule.state}`, + { time: backupTime } ); const lastAttemptDate = this.config.last_attempted_automatic_backup @@ -110,25 +96,50 @@ class HaBackupOverviewBackups extends LitElement { // If last attempt is after last completed backup, show error if (lastAttemptDate > lastCompletedDate) { - const description = `The last automatic backup triggered ${relativeTime(lastAttemptDate, this.hass.locale, now, true)} wasn't successful.`; const lastUploadedBackup = this._lastUploadedBackup(this.backups); - const secondaryDescription = lastUploadedBackup - ? `Last successful backup ${relativeTime(new Date(lastUploadedBackup.date), this.hass.locale, now, true)} and stored in ${lastUploadedBackup.agent_ids?.length} locations.` - : nextBackupDescription; return html` - ${description} + + ${this.hass.localize( + "ui.panel.config.backup.overview.summary.last_backup_failed_description", + { + relative_time: relativeTime( + lastAttemptDate, + this.hass.locale, + now, + true + ), + } + )} + - ${secondaryDescription} + + ${lastUploadedBackup + ? this.hass.localize( + "ui.panel.config.backup.overview.summary.last_successful_backup_description", + { + relative_time: relativeTime( + new Date(lastUploadedBackup.date), + this.hass.locale, + now, + true + ), + count: lastUploadedBackup.agent_ids?.length ?? 0, + } + ) + : nextBackupDescription} + @@ -137,16 +148,21 @@ class HaBackupOverviewBackups extends LitElement { // If no backups yet, show warning if (!lastBackup) { - const description = "You have no automatic backups yet."; return html` - ${description} + + ${this.hass.localize( + "ui.panel.config.backup.overview.summary.no_backup_description" + )} + @@ -161,32 +177,68 @@ class HaBackupOverviewBackups extends LitElement { // If last backup if (lastBackup.failed_agent_ids?.length) { - const description = `The last automatic backup created ${relativeTime(lastBackupDate, this.hass.locale, now, true)} wasn't stored in all locations.`; const lastUploadedBackup = this._lastUploadedBackup(this.backups); - const secondaryDescription = lastUploadedBackup - ? `Last successful backup ${relativeTime(new Date(lastUploadedBackup.date), this.hass.locale, now, true)} and stored in ${lastUploadedBackup.agent_ids?.length} locations.` - : nextBackupDescription; return html` - ${description} + + ${this.hass.localize( + "ui.panel.config.backup.overview.summary.last_backup_failed_locations_description", + { + relative_time: relativeTime( + lastAttemptDate, + this.hass.locale, + now, + true + ), + } + )} + - ${secondaryDescription} + + ${lastUploadedBackup + ? this.hass.localize( + "ui.panel.config.backup.overview.summary.last_successful_backup_description", + { + relative_time: relativeTime( + new Date(lastUploadedBackup.date), + this.hass.locale, + now, + true + ), + count: lastUploadedBackup.agent_ids?.length ?? 0, + } + ) + : nextBackupDescription} + `; } - const description = `Last successful backup ${relativeTime(lastBackupDate, this.hass.locale, now, true)} and stored in ${lastBackup.agent_ids?.length} locations.`; + const lastSuccessfulBackupDescription = this.hass.localize( + "ui.panel.config.backup.overview.summary.last_successful_backup_description", + { + relative_time: relativeTime( + new Date(lastBackup.date), + this.hass.locale, + now, + true + ), + count: lastBackup.agent_ids?.length ?? 0, + } + ); const numberOfDays = differenceInDays( // Subtract a few hours to avoid showing as overdue if it's just a few hours (e.g. daylight saving) @@ -202,13 +254,15 @@ class HaBackupOverviewBackups extends LitElement { if (isOverdue) { return html` - ${description} + ${lastSuccessfulBackupDescription} @@ -220,11 +274,16 @@ class HaBackupOverviewBackups extends LitElement { } return html` - + - ${description} + ${lastSuccessfulBackupDescription} diff --git a/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts b/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts index ab66157e46..3944e1316e 100644 --- a/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts +++ b/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts @@ -224,7 +224,9 @@ class DialogBackupOnboarding extends LitElement implements HassDialog { @click=${this._done} .disabled=${!this._isStepValid()} > - Save and create backup + ${this.hass.localize( + "ui.panel.config.backup.dialogs.onboarding.save_and_create" + )} ` : html` @@ -232,7 +234,7 @@ class DialogBackupOnboarding extends LitElement implements HassDialog { @click=${this._nextStep} .disabled=${!this._isStepValid()} > - Next + ${this.hass.localize("ui.common.next")} `}
@@ -244,18 +246,14 @@ class DialogBackupOnboarding extends LitElement implements HassDialog { private get _stepTitle(): string { switch (this._step) { - case "welcome": - return ""; case "key": - return "Encryption key"; case "setup": - return "Set up your automatic backups"; case "schedule": - return "Automatic backups"; case "data": - return "Backup data"; case "locations": - return "Locations"; + return this.hass.localize( + `ui.panel.config.backup.dialogs.onboarding.${this._step}.title` + ); default: return ""; } @@ -291,23 +289,24 @@ class DialogBackupOnboarding extends LitElement implements HassDialog { src="/static/images/voice-assistant/hi.png" alt="Casita Home Assistant logo" /> -

Set up backups

+

+ ${this.hass.localize( + "ui.panel.config.backup.dialogs.onboarding.welcome.title" + )} +

- Backups are essential for a reliable smart home. They help protect - the work you've put into setting up your smart home, and if the - worst happens, you can get back up and running quickly. It is - recommended that you create a backup every day. You should keep - three backups in at least two different locations, one of which - should be off-site. + ${this.hass.localize( + "ui.panel.config.backup.dialogs.onboarding.welcome.description" + )}

`; case "key": return html`

- All your backups are encrypted to keep your data private and secure. - We recommend to save this key somewhere secure. As you can only - restore your data with the backup encryption key. + ${this.hass.localize( + "ui.panel.config.backup.dialogs.onboarding.key.description" + )}

${this._config.create_backup.password}

@@ -318,13 +317,21 @@ class DialogBackupOnboarding extends LitElement implements HassDialog {
- Download emergency kit + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit" + )} + - We recommend to save this encryption key somewhere secure. + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit_description" + )} - Download + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit_action" + )} @@ -333,16 +340,28 @@ class DialogBackupOnboarding extends LitElement implements HassDialog { return html` - Recommended settings + + ${this.hass.localize( + "ui.panel.config.backup.dialogs.onboarding.setup.recommended_heading" + )} + - Backup everything daily, keeping three days of backups + ${this.hass.localize( + "ui.panel.config.backup.dialogs.onboarding.setup.recommended_description" + )} - Custom settings + + ${this.hass.localize( + "ui.panel.config.backup.dialogs.onboarding.setup.custom_heading" + )} + - Select when, where, and what to backup + ${this.hass.localize( + "ui.panel.config.backup.dialogs.onboarding.setup.custom_description" + )} @@ -351,8 +370,9 @@ class DialogBackupOnboarding extends LitElement implements HassDialog { case "schedule": return html`

- Let Home Assistant take care of your backups by creating a scheduled - backup that also removes older backups. + ${this.hass.localize( + "ui.panel.config.backup.dialogs.onboarding.schedule.description" + )}

- Choose what data to include in your backups. You can always change - this later. + ${this.hass.localize( + "ui.panel.config.backup.dialogs.onboarding.data.description" + )}

- Home Assistant will upload to these locations when an automatic - backup is made. You can use all locations for manual backups. + ${this.hass.localize( + "ui.panel.config.backup.dialogs.onboarding.locations.description" + )}

@@ -119,7 +117,11 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
${this._renderStepContent()}
${this._step === "current" - ? html`Next` + ? html` + + ${this.hass.localize("ui.common.next")} + + ` : this._step === "new" ? html` - Change encryption key + ${this.hass.localize( + "ui.panel.config.backup.dialogs.change_encryption_key.actions.change" + )} ` - : html`Done`} + : html` + + ${this.hass.localize( + "ui.panel.config.backup.dialogs.change_encryption_key.actions.done" + )} + + `}
`; @@ -141,9 +151,9 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog { case "current": return html`

- Make sure you have saved the current encryption key to make sure you - have access to all your current backups. All next backups will use - the new encryption key. + ${this.hass.localize( + "ui.panel.config.backup.dialogs.change_encryption_key.current.description" + )}

${this._params?.currentKey}

@@ -154,13 +164,21 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
- Download old emergency kit + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_old_emergency_kit" + )} + - We recommend saving this encryption key file somewhere secure. + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_old_emergency_kit_description" + )} - Download + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_old_emergency_kit_action" + )} @@ -168,22 +186,9 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog { case "new": return html`

- All next backups will use the new encryption key. Encryption keeps - your backups private and secure. -

-
-

${this._newEncryptionKey}

- -
- `; - case "done": - return html`

- Keep this new encryption key in a safe place, as you will need it to - access your backups, allowing it to be restored. Either record the - characters below or download them as an emergency kit file. + ${this.hass.localize( + "ui.panel.config.backup.dialogs.change_encryption_key.new.description" + )}

${this._newEncryptionKey}

@@ -194,16 +199,39 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog {
- Download new emergency kit + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit" + )} + - We recommend saving this encryption key file somewhere secure. + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit_description" + )} - Download + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit_action" + )} - `; +
+ `; + case "done": + return html` +
+ Casita Home Assistant logo +

+ ${this.hass.localize( + "ui.panel.config.backup.dialogs.change_encryption_key.done.title" + )} +

+
+ `; } return nothing; } @@ -306,6 +334,9 @@ class DialogChangeBackupEncryptionKey extends LitElement implements HassDialog { p { margin-top: 0; } + .done { + text-align: center; + } `, ]; } diff --git a/src/panels/config/backup/dialogs/dialog-generate-backup.ts b/src/panels/config/backup/dialogs/dialog-generate-backup.ts index b8c64c1255..9334d7e191 100644 --- a/src/panels/config/backup/dialogs/dialog-generate-backup.ts +++ b/src/panels/config/backup/dialogs/dialog-generate-backup.ts @@ -164,8 +164,9 @@ class DialogGenerateBackup extends LitElement implements HassDialog { return nothing; } - const dialogTitle = - this._step === "sync" ? "Synchronization" : "Backup data"; + const dialogTitle = this.hass.localize( + `ui.panel.config.backup.dialogs.generate.${this._step}.title` + ); const isFirstStep = this._step === STEPS[0]; const isLastStep = this._step === STEPS[STEPS.length - 1]; @@ -197,7 +198,11 @@ class DialogGenerateBackup extends LitElement implements HassDialog {
${isFirstStep - ? html`Cancel` + ? html` + + ${this.hass.localize("ui.common.cancel")} + + ` : nothing} ${isLastStep ? html` @@ -206,14 +211,19 @@ class DialogGenerateBackup extends LitElement implements HassDialog { .disabled=${this._formData.agents_mode === "custom" && !selectedAgents.length} > - Create backup + ${this.hass.localize( + "ui.panel.config.backup.dialogs.generate.actions.create" + )} ` - : html`Next`} + : html` + + ${this.hass.localize("ui.common.next")} + + `}
`; @@ -266,16 +276,24 @@ class DialogGenerateBackup extends LitElement implements HassDialog { return html` - Backup locations + + ${this.hass.localize( + "ui.panel.config.backup.dialogs.generate.sync.locations" + )} + - What locations you want to automatically backup to. + ${this.hass.localize( + "ui.panel.config.backup.dialogs.generate.sync.locations_description" + )} -
All (${this._agentIds.length})
+
+ ${this.hass.localize( + "ui.panel.config.backup.dialogs.generate.sync.locations_options.all", + { count: this._agentIds.length } + )} +
-
Custom
+
+ ${this.hass.localize( + "ui.panel.config.backup.dialogs.generate.sync.locations_options.custom" + )} +
@@ -299,16 +326,25 @@ class DialogGenerateBackup extends LitElement implements HassDialog { ? html` - Add Home Assistant settings data to synchronize this backup to - Home Assistant Cloud. + ${this.hass.localize( + "ui.panel.config.backup.dialogs.generate.sync.ha_cloud_alert.description" + )} ` : nothing} ${this._formData.agents_mode === "custom" ? html` - + - Backup now + + ${this.hass.localize("ui.panel.config.backup.dialogs.new.title")} +
@@ -76,17 +80,29 @@ class DialogNewBackup extends LitElement implements HassDialog { .disabled=${!this._params.config.create_backup.password} > - Automatic backup + + ${this.hass.localize( + "ui.panel.config.backup.dialogs.new.automatic.description" + )} + - Create a backup with the data and locations you have configured. + ${this.hass.localize( + "ui.panel.config.backup.dialogs.new.automatic.description" + )} - Manual backup + + ${this.hass.localize( + "ui.panel.config.backup.dialogs.new.manual.title" + )} + - Select data and locations for a manual backup. + ${this.hass.localize( + "ui.panel.config.backup.dialogs.new.manual.description" + )} diff --git a/src/panels/config/backup/dialogs/dialog-restore-backup.ts b/src/panels/config/backup/dialogs/dialog-restore-backup.ts index 588767b1dc..2620cf5ec5 100644 --- a/src/panels/config/backup/dialogs/dialog-restore-backup.ts +++ b/src/panels/config/backup/dialogs/dialog-restore-backup.ts @@ -122,7 +122,9 @@ class DialogRestoreBackup extends LitElement implements HassDialog { return nothing; } - const dialogTitle = "Restore backup"; + const dialogTitle = this.hass.localize( + "ui.panel.config.backup.dialogs.restore.title" + ); return html` @@ -146,7 +148,11 @@ class DialogRestoreBackup extends LitElement implements HassDialog {
${this._error - ? html`Close` + ? html` + + ${this.hass.localize("ui.common.close")} + + ` : this._step === "confirm" || this._step === "encryption" ? this._renderConfirmActions() : nothing} @@ -156,40 +162,71 @@ class DialogRestoreBackup extends LitElement implements HassDialog { } private _renderConfirm() { - return html`

- Your backup will be restored and all current data will be overwritten. - Depending on the size of the backup, this can take a while. -

`; + return html` +

+ ${this.hass.localize( + "ui.panel.config.backup.dialogs.restore.confirm.description" + )} +

+ `; + } + + private _renderEncryptionIntro() { + if (this._usedUserInput) { + return html` + ${this.hass.localize( + "ui.panel.config.backup.dialogs.restore.encryption.incorrect_key" + )} + `; + } + if (this._backupEncryptionKey) { + return html` + ${this.hass.localize( + "ui.panel.config.backup.dialogs.restore.encryption.different_key" + )} + ${this._params!.selectedData.homeassistant_included + ? html` + + ${this.hass.localize( + "ui.panel.config.backup.dialogs.restore.encryption.warning" + )} + + ` + : nothing} + `; + } + return html` + ${this.hass.localize( + "ui.panel.config.backup.dialogs.restore.encryption.description" + )} + `; } private _renderEncryption() { - return html`${this._usedUserInput - ? "The provided encryption key was incorrect, please try again." - : this._backupEncryptionKey - ? html`The Backup is encrypted with a different encryption key than - that is saved on this system. Please enter the encryption key for - this backup.
- ${this._params!.selectedData.homeassistant_included - ? html`After restoring the backup, your new backups will be - encrypted with the encryption key that was present during - the time of this backup.` - : nothing}` - : "The backup is encrypted. Provide the encryption key to decrypt the backup."} + return html` + ${this._renderEncryptionIntro()} `; + > + `; } private _renderConfirmActions() { - return html`Cancel - Restore`; + return html` + + ${this.hass.localize("ui.common.cancel")} + + + ${this.hass.localize( + "ui.panel.config.backup.dialogs.restore.actions.restore" + )} + + `; } private _renderProgress() { @@ -198,7 +235,9 @@ class DialogRestoreBackup extends LitElement implements HassDialog {

${this.hass.connected ? this._restoreState() - : "Restarting Home Assistant"} + : this.hass.localize( + "ui.panel.config.backup.dialogs.restore.progress.restarting" + )}

`; } @@ -245,7 +284,9 @@ class DialogRestoreBackup extends LitElement implements HassDialog { this.closeDialog(); } if (event.state === "failed") { - this._error = "Backup restore failed"; + this._error = this.hass.localize( + "ui.panel.config.backup.dialogs.restore.restore_failed" + ); } if (event.state === "in_progress") { this._stage = event.stage; @@ -263,29 +304,14 @@ class DialogRestoreBackup extends LitElement implements HassDialog { } private _restoreState() { - switch (this._stage) { - case "addon_repositories": - return "Restoring add-on repositories"; - case "addons": - return "Restoring add-ons"; - case "await_addon_restarts": - return "Waiting for add-ons to restart"; - case "await_home_assistant_restart": - return "Waiting for Home Assistant to restart"; - case "check_home_assistant": - return "Checking Home Assistant configuration"; - case "docker_config": - return "Restoring Docker configuration"; - case "download_from_agent": - return "Downloading backup"; - case "folders": - return "Restoring folders"; - case "home_assistant": - return "Restoring Home Assistant"; - case "remove_delta_addons": - return "Removing add-ons that are no longer in the backup"; + if (!this._stage) { + return this.hass.localize( + "ui.panel.config.backup.dialogs.restore.progress.restoring" + ); } - return "Restoring backup"; + return this.hass.localize( + `ui.panel.config.backup.overview.progress.description.restore_backup.${this._stage}` + ); } private async _doRestoreBackup(password?: string) { diff --git a/src/panels/config/backup/dialogs/dialog-set-backup-encryption-key.ts b/src/panels/config/backup/dialogs/dialog-set-backup-encryption-key.ts index 956b969dc1..03ed6c0b87 100644 --- a/src/panels/config/backup/dialogs/dialog-set-backup-encryption-key.ts +++ b/src/panels/config/backup/dialogs/dialog-set-backup-encryption-key.ts @@ -1,11 +1,13 @@ -import { mdiClose, mdiDownload, mdiKey } from "@mdi/js"; +import { mdiClose, mdiContentCopy, mdiDownload } from "@mdi/js"; import type { CSSResultGroup } from "lit"; import { LitElement, css, html, nothing } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import { fireEvent } from "../../../../common/dom/fire_event"; +import { copyToClipboard } from "../../../../common/util/copy-clipboard"; import "../../../../components/ha-button"; import "../../../../components/ha-dialog-header"; import "../../../../components/ha-icon-button"; +import "../../../../components/ha-icon-button-prev"; import "../../../../components/ha-md-dialog"; import type { HaMdDialog } from "../../../../components/ha-md-dialog"; import "../../../../components/ha-md-list"; @@ -18,9 +20,12 @@ import { import type { HassDialog } from "../../../../dialogs/make-dialog-manager"; import { haStyle, haStyleDialog } from "../../../../resources/styles"; import type { HomeAssistant } from "../../../../types"; +import { showToast } from "../../../../util/toast"; import type { SetBackupEncryptionKeyDialogParams } from "./show-dialog-set-backup-encryption-key"; -const STEPS = ["new", "save"] as const; +const STEPS = ["key", "done"] as const; + +type Step = (typeof STEPS)[number]; @customElement("ha-dialog-set-backup-encryption-key") class DialogSetBackupEncryptionKey extends LitElement implements HassDialog { @@ -28,7 +33,7 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog { @state() private _opened = false; - @state() private _step?: "new" | "save"; + @state() private _step?: Step; @state() private _params?: SetBackupEncryptionKeyDialogParams; @@ -36,13 +41,11 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog { @state() private _newEncryptionKey?: string; - private _suggestedEncryptionKey?: string; - public showDialog(params: SetBackupEncryptionKeyDialogParams): void { this._params = params; this._step = STEPS[0]; this._opened = true; - this._suggestedEncryptionKey = generateEncryptionKey(); + this._newEncryptionKey = generateEncryptionKey(); } public closeDialog(): void { @@ -56,7 +59,6 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog { this._step = undefined; this._params = undefined; this._newEncryptionKey = undefined; - this._suggestedEncryptionKey = undefined; } private _done() { @@ -78,7 +80,11 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog { } const dialogTitle = - this._step === "new" ? "Encryption key" : "Save new encryption key"; + this._step === "key" + ? this.hass.localize( + `ui.panel.config.backup.dialogs.set_encryption_key.key.title` + ) + : ""; return html` @@ -93,18 +99,24 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog {
${this._renderStepContent()}
- ${this._step === "new" + ${this._step === "key" ? html` - Next + ${this.hass.localize( + "ui.panel.config.backup.dialogs.set_encryption_key.actions.set" + )} ` - : this._step === "save" - ? html`Done` - : nothing} + : html` + + ${this.hass.localize( + "ui.panel.config.backup.dialogs.set_encryption_key.actions.done" + )} + + `}
`; @@ -112,69 +124,76 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog { private _renderStepContent() { switch (this._step) { - case "new": + case "key": return html`

- All your backups are encrypted to keep your data private and secure. - You need this encryption key to restore any backup. + ${this.hass.localize( + "ui.panel.config.backup.dialogs.set_encryption_key.new.description" + )}

- +
+

${this._newEncryptionKey}

+ +
- - Use suggested encryption key - - ${this._suggestedEncryptionKey} + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit" + )} - - Enter + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit_description" + )} + + + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit_action" + )} `; - case "save": + case "done": return html` -

- It’s important that you don’t lose this encryption key. We recommend - to save this key somewhere secure. As you can only restore your data - with the backup encryption key. -

- - - Download emergency kit - - We recommend to save this encryption key somewhere secure. - - - - Download - - - +
+ Casita Home Assistant logo +

+ ${this.hass.localize( + "ui.panel.config.backup.dialogs.set_encryption_key.done.title" + )} +

+
`; } return nothing; } - private _downloadNew() { + private async _copyKeyToClipboard() { + await copyToClipboard( + this._newEncryptionKey, + this.renderRoot.querySelector("div")! + ); + showToast(this, { + message: this.hass.localize("ui.common.copied_clipboard"), + }); + } + + private _download() { if (!this._newEncryptionKey) { return; } downloadEmergencyKit(this.hass, this._newEncryptionKey); } - private _encryptionKeyChanged(ev) { - this._newEncryptionKey = ev.target.value; - } - - private _useSuggestedEncryptionKey() { - this._newEncryptionKey = this._suggestedEncryptionKey; - } - private async _submit() { if (!this._newEncryptionKey) { return; @@ -190,7 +209,7 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog { css` ha-md-dialog { width: 90vw; - max-width: 500px; + max-width: 560px; --dialog-content-padding: 8px 24px; } ha-md-list { @@ -198,6 +217,30 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog { --md-list-item-leading-space: 0; --md-list-item-trailing-space: 0; } + .encryption-key { + border: 1px solid var(--divider-color); + background-color: var(--primary-background-color); + border-radius: 8px; + padding: 16px; + display: flex; + flex-direction: row; + align-items: center; + gap: 24px; + } + .encryption-key p { + margin: 0; + flex: 1; + font-family: "Roboto Mono", "Consolas", "Menlo", monospace; + font-size: 20px; + font-style: normal; + font-weight: 400; + line-height: 28px; + text-align: center; + } + .encryption-key ha-icon-button { + flex: none; + margin: -16px; + } @media all and (max-width: 450px), all and (max-height: 500px) { ha-md-dialog { max-width: none; @@ -209,6 +252,9 @@ class DialogSetBackupEncryptionKey extends LitElement implements HassDialog { p { margin-top: 0; } + .done { + text-align: center; + } `, ]; } diff --git a/src/panels/config/backup/dialogs/dialog-show-backup-encryption-key.ts b/src/panels/config/backup/dialogs/dialog-show-backup-encryption-key.ts index 5861ad35a7..283d4fc082 100644 --- a/src/panels/config/backup/dialogs/dialog-show-backup-encryption-key.ts +++ b/src/panels/config/backup/dialogs/dialog-show-backup-encryption-key.ts @@ -55,12 +55,17 @@ class DialogShowBackupEncryptionKey extends LitElement implements HassDialog { .path=${mdiClose} @click=${this._closeDialog} > - Encryption key + + ${this.hass.localize( + "ui.panel.config.backup.dialogs.show_encryption_key.title" + )} +

- Make sure you save the encryption key in a secure place so always - have access to your backups. + ${this.hass.localize( + "ui.panel.config.backup.dialogs.show_encryption_key.description" + )}

${this._params?.currentKey}

@@ -71,19 +76,29 @@ class DialogShowBackupEncryptionKey extends LitElement implements HassDialog {
- Download emergency kit + + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit" + )} + - We recommend saving this encryption key file somewhere secure. + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit_description" + )} - Download + ${this.hass.localize( + "ui.panel.config.backup.encryption_key.download_emergency_kit_action" + )}
- Close + + ${this.hass.localize("ui.dialogs.generic.close")} +
`; @@ -124,9 +139,6 @@ class DialogShowBackupEncryptionKey extends LitElement implements HassDialog { --md-list-item-leading-space: 0; --md-list-item-trailing-space: 0; } - ha-button.danger { - --mdc-theme-primary: var(--error-color); - } .encryption-key { border: 1px solid var(--divider-color); background-color: var(--primary-background-color); diff --git a/src/panels/config/backup/dialogs/dialog-upload-backup.ts b/src/panels/config/backup/dialogs/dialog-upload-backup.ts index e1258a0f51..09e5e5b5d6 100644 --- a/src/panels/config/backup/dialogs/dialog-upload-backup.ts +++ b/src/panels/config/backup/dialogs/dialog-upload-backup.ts @@ -86,7 +86,9 @@ export class DialogUploadBackup @click=${this.closeDialog} > - Upload backup + + ${this.hass.localize("ui.panel.config.backup.dialogs.upload.title")} +
${this._error @@ -97,15 +99,21 @@ export class DialogUploadBackup .uploading=${this._uploading} .icon=${mdiFolderUpload} accept=${SUPPORTED_FORMAT} - label="Select backup file" - supports="Supports .tar files" + .label=${this.hass.localize( + "ui.panel.config.backup.dialogs.upload.input_label" + )} + .supports=${this.hass.localize( + "ui.panel.config.backup.dialogs.upload.supports_tar" + )} @file-picked=${this._filePicked} >
Cancel - Upload backup + ${this.hass.localize( + "ui.panel.config.backup.dialogs.upload.action" + )}
@@ -126,9 +134,13 @@ export class DialogUploadBackup const { file } = this._formData!; if (!file || file.type !== SUPPORTED_FORMAT) { showAlertDialog(this, { - title: "Unsupported file format", - text: "Please choose a Home Assistant backup file (.tar)", - confirmText: "ok", + title: this.hass.localize( + "ui.panel.config.backup.dialogs.upload.unsupported.title" + ), + text: this.hass.localize( + "ui.panel.config.backup.dialogs.upload.unsupported.text" + ), + confirmText: this.hass.localize("ui.common.ok"), }); return; } diff --git a/src/panels/config/backup/ha-config-backup-backups.ts b/src/panels/config/backup/ha-config-backup-backups.ts index 18d65557f0..18682cd054 100644 --- a/src/panels/config/backup/ha-config-backup-backups.ts +++ b/src/panels/config/backup/ha-config-backup-backups.ts @@ -17,7 +17,6 @@ import { fireEvent, type HASSDomEvent } from "../../../common/dom/fire_event"; import { computeDomain } from "../../../common/entity/compute_domain"; import { shouldHandleRequestSelectedEvent } from "../../../common/mwc/handle-request-selected-event"; import { navigate } from "../../../common/navigate"; -import { capitalizeFirstLetter } from "../../../common/string/capitalize-first-letter"; import type { LocalizeFunc } from "../../../common/translations/localize"; import type { DataTableColumnContainer, @@ -70,9 +69,9 @@ interface BackupRow extends DataTableRowData, BackupContent { formatted_type: string; } -type BackupType = "automatic" | "manual" | "imported"; +type BackupType = "automatic" | "manual"; -const TYPE_ORDER: Array = ["automatic", "manual", "imported"]; +const TYPE_ORDER: Array = ["automatic", "manual"]; @customElement("ha-config-backup-backups") class HaConfigBackupBackups extends SubscribeMixin(LitElement) { @@ -158,13 +157,13 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { relativeTime(new Date(backup.date), this.hass.locale), }, formatted_type: { - title: "Type", + title: localize("ui.panel.config.backup.backup_type"), filterable: true, sortable: true, groupable: true, }, locations: { - title: "Locations", + title: localize("ui.panel.config.backup.locations"), showNarrow: true, minWidth: "60px", template: (backup) => html` @@ -246,10 +245,13 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { }) ); - private _groupOrder = memoizeOne((activeGrouping: string | undefined) => - activeGrouping === "formatted_type" - ? TYPE_ORDER.map((type) => this._formatBackupType(type)) - : undefined + private _groupOrder = memoizeOne( + (activeGrouping: string | undefined, localize: LocalizeFunc) => + activeGrouping === "formatted_type" + ? TYPE_ORDER.map((type) => + localize(`ui.panel.config.backup.type.${type}`) + ) + : undefined ); private _handleGroupingChanged(ev: CustomEvent) { @@ -266,15 +268,11 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { this._selected = ev.detail.value; } - private _formatBackupType(type: BackupType): string { - // Todo translate - return capitalizeFirstLetter(type); - } - private _data = memoizeOne( ( backups: BackupContent[], - filters: DataTableFiltersValues + filters: DataTableFiltersValues, + localize: LocalizeFunc ): BackupRow[] => { const typeFilter = filters["ha-filter-states"] as string[] | undefined; let filteredBackups = backups; @@ -286,12 +284,13 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { (!backup.with_automatic_settings && typeFilter.includes("manual")) ); } - return filteredBackups.map((backup) => ({ - ...backup, - formatted_type: this._formatBackupType( - backup.with_automatic_settings ? "automatic" : "manual" - ), - })); + return filteredBackups.map((backup) => { + const type = backup.with_automatic_settings ? "automatic" : "manual"; + return { + ...backup, + formatted_type: localize(`ui.panel.config.backup.type.${type}`), + }; + }); } ); @@ -304,7 +303,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { has-fab .tabs=${[ { - name: "My backups", + name: this.hass.localize("ui.panel.config.backup.backups.header"), path: `/config/backup/list`, }, ]} @@ -326,14 +325,17 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { .selected=${this._selected.length} .initialGroupColumn=${this._activeGrouping} .initialCollapsedGroups=${this._activeCollapsed} - .groupOrder=${this._groupOrder(this._activeGrouping)} + .groupOrder=${this._groupOrder( + this._activeGrouping, + this.hass.localize + )} @grouping-changed=${this._handleGroupingChanged} @collapsed-changed=${this._handleCollapseChanged} @selection-changed=${this._handleSelectionChanged} .route=${this.route} @row-click=${this._showBackupDetails} .columns=${this._columns(this.hass.localize)} - .data=${this._data(this.backups, this._filters)} + .data=${this._data(this.backups, this._filters, this.hass.localize)} .noDataText=${this.hass.localize("ui.panel.config.backup.no_backups")} .searchLabel=${this.hass.localize( "ui.panel.config.backup.picker.search" @@ -351,7 +353,9 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { @request-selected=${this._uploadBackup} > - Upload backup + ${this.hass.localize( + "ui.panel.config.backup.backups.menu.upload_backup" + )}
@@ -360,26 +364,32 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { ${!this.narrow ? html` - Delete selected + ${this.hass.localize( + "ui.panel.config.backup.backups.delete_selected" + )} ` : html` - Delete selected + ${this.hass.localize( + "ui.panel.config.backup.backups.delete_selected" + )} `} @@ -404,16 +416,12 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { `; } - private _states = memoizeOne((_localize: LocalizeFunc) => [ - { - value: "automatic", - label: "Automatic", - }, - { - value: "manual", - label: "Manual", - }, - ]); + private _states = memoizeOne((localize: LocalizeFunc) => + TYPE_ORDER.map((type) => ({ + value: type, + label: localize(`ui.panel.config.backup.type.${type}`), + })) + ); private _filterChanged(ev) { const type = ev.target.localName; @@ -489,8 +497,8 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { private async _deleteBackup(backup: BackupContent): Promise { const confirm = await showConfirmationDialog(this, { - title: "Delete backup", - text: "This backup will be permanently deleted.", + title: this.hass.localize("ui.panel.config.backup.dialogs.delete.title"), + text: this.hass.localize("ui.panel.config.backup.dialogs.delete.text"), confirmText: this.hass.localize("ui.common.delete"), destructive: true, }); @@ -499,17 +507,31 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { return; } - await deleteBackup(this.hass, backup.backup_id); - if (this._selected.includes(backup.backup_id)) { - this._selected = this._selected.filter((id) => id !== backup.backup_id); + try { + await deleteBackup(this.hass, backup.backup_id); + if (this._selected.includes(backup.backup_id)) { + this._selected = this._selected.filter((id) => id !== backup.backup_id); + } + } catch (err: any) { + showAlertDialog(this, { + title: this.hass.localize( + "ui.panel.config.backup.dialogs.delete.failed" + ), + text: extractApiErrorMessage(err), + }); + return; } fireEvent(this, "ha-refresh-backup-info"); } private async _deleteSelected() { const confirm = await showConfirmationDialog(this, { - title: "Delete selected backups", - text: "These backups will be permanently deleted.", + title: this.hass.localize( + "ui.panel.config.backup.dialogs.delete_selected.title" + ), + text: this.hass.localize( + "ui.panel.config.backup.dialogs.delete_selected.text" + ), confirmText: this.hass.localize("ui.common.delete"), destructive: true, }); @@ -524,7 +546,9 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { ); } catch (err: any) { showAlertDialog(this, { - title: "Failed to delete backups", + title: this.hass.localize( + "ui.panel.config.backup.dialogs.delete_selected.failed" + ), text: extractApiErrorMessage(err), }); return; diff --git a/src/panels/config/backup/ha-config-backup-details.ts b/src/panels/config/backup/ha-config-backup-details.ts index 8c42bfe69f..5c8f2ea510 100644 --- a/src/panels/config/backup/ha-config-backup-details.ts +++ b/src/panels/config/backup/ha-config-backup-details.ts @@ -41,6 +41,7 @@ import { fileDownload } from "../../../util/file_download"; import { showConfirmationDialog } from "../../lovelace/custom-card-helpers"; import "./components/ha-backup-data-picker"; import { showRestoreBackupDialog } from "./dialogs/show-dialog-restore-backup"; +import { fireEvent } from "../../../common/dom/fire_event"; type Agent = { id: string; @@ -96,7 +97,8 @@ class HaConfigBackupDetails extends LitElement { back-path="/config/backup/backups" .hass=${this.hass} .narrow=${this.narrow} - .header=${this._backup?.name || "Backup"} + .header=${this._backup?.name || + this.hass.localize("ui.panel.config.backup.details.header")} > ${this._error}`} ${this._backup === null ? html` - - Backup matching ${this.backupId} not found + + ${this.hass.localize( + "ui.panel.config.backup.details.not_found_description", + { backupId: this.backupId } + )} ` : !this._backup ? html`` : html` -
Backup
+
+ ${this.hass.localize( + "ui.panel.config.backup.details.summary.title" + )} +
${bytesToString(this._backup.size)} - Size + + ${this.hass.localize( + "ui.panel.config.backup.details.summary.size" + )} + ${formatDateTime( @@ -141,21 +159,37 @@ class HaConfigBackupDetails extends LitElement { this.hass.locale, this.hass.config )} - Created + + ${this.hass.localize( + "ui.panel.config.backup.details.summary.created" + )} + ${this._backup.protected - ? "Encrypted AES-128" - : "Not encrypted"} + ? this.hass.localize( + "ui.panel.config.backup.details.summary.protected_encrypted_aes_128" + ) + : this.hass.localize( + "ui.panel.config.backup.details.summary.protected_not_encrypted" + )} + + + ${this.hass.localize( + "ui.panel.config.backup.details.summary.protected" + )} - Protected
-
Select what to restore
+
+ ${this.hass.localize( + "ui.panel.config.backup.details.restore.title" + )} +
- Restore + ${this.hass.localize( + "ui.panel.config.backup.details.restore.action" + )}
-
Locations
+
+ ${this.hass.localize( + "ui.panel.config.backup.details.locations.title" + )} +
${this._agents.map((agent) => { @@ -187,11 +227,9 @@ class HaConfigBackupDetails extends LitElement { const name = computeBackupAgentName( this.hass.localize, agentId, - this._backup!.agent_ids! + this._backup!.agent_ids ); - const isLocal = isLocalAgent(agentId); - return html` ${isLocalAgent(agentId) @@ -232,10 +270,12 @@ class HaConfigBackupDetails extends LitElement { ${success - ? isLocal - ? "Backup created" - : "Backup uploaded" - : "Backup failed"} + ? this.hass.localize( + "ui.panel.config.backup.details.locations.backup_stored" + ) + : this.hass.localize( + "ui.panel.config.backup.details.locations.backup_failed" + )}
${success @@ -257,7 +297,9 @@ class HaConfigBackupDetails extends LitElement { slot="graphic" .path=${mdiDownload} > - Download from this location + ${this.hass.localize( + "ui.panel.config.backup.details.locations.download" + )}
` : nothing} @@ -309,7 +351,9 @@ class HaConfigBackupDetails extends LitElement { response.backup.failed_agent_ids || [] ); } catch (err: any) { - this._error = err?.message || "Could not fetch backup details"; + this._error = + err?.message || + this.hass.localize("ui.panel.config.backup.details.error"); } } @@ -342,8 +386,8 @@ class HaConfigBackupDetails extends LitElement { private async _deleteBackup(): Promise { const confirm = await showConfirmationDialog(this, { - title: "Delete backup", - text: "This backup will be permanently deleted.", + title: this.hass.localize("ui.panel.config.backup.dialogs.delete.title"), + text: this.hass.localize("ui.panel.config.backup.dialogs.delete.text"), confirmText: this.hass.localize("ui.common.delete"), destructive: true, }); @@ -353,6 +397,7 @@ class HaConfigBackupDetails extends LitElement { } await deleteBackup(this.hass, this._backup!.backup_id); + fireEvent(this, "ha-refresh-backup-info"); navigate("/config/backup"); } diff --git a/src/panels/config/backup/ha-config-backup-overview.ts b/src/panels/config/backup/ha-config-backup-overview.ts index 26e86a2105..e121d62b30 100644 --- a/src/panels/config/backup/ha-config-backup-overview.ts +++ b/src/panels/config/backup/ha-config-backup-overview.ts @@ -130,7 +130,7 @@ class HaConfigBackupOverview extends LitElement { back-path="/config/system" .hass=${this.hass} .narrow=${this.narrow} - .header=${"Backup"} + .header=${this.hass.localize("ui.panel.config.backup.overview.header")} > - Upload backup + ${this.hass.localize( + "ui.panel.config.backup.overview.menu.upload_backup" + )}
@@ -190,7 +192,9 @@ class HaConfigBackupOverview extends LitElement { diff --git a/src/panels/config/backup/ha-config-backup-settings.ts b/src/panels/config/backup/ha-config-backup-settings.ts index f581f5cd92..4bed661988 100644 --- a/src/panels/config/backup/ha-config-backup-settings.ts +++ b/src/panels/config/backup/ha-config-backup-settings.ts @@ -99,7 +99,7 @@ class HaConfigBackupSettings extends LitElement { back-path="/config/backup" .hass=${this.hass} .narrow=${this.narrow} - .header=${"Backup settings"} + .header=${this.hass.localize("ui.panel.config.backup.settings.header")} > ${isComponentLoaded(this.hass, "hassio") ? html` @@ -117,7 +117,9 @@ class HaConfigBackupSettings extends LitElement { slot="graphic" .path=${mdiHarddisk} > - Change default action location + ${this.hass.localize( + "ui.panel.config.backup.settings.menu.change_default_location" + )} ` @@ -125,11 +127,16 @@ class HaConfigBackupSettings extends LitElement {
-
Automatic backups
+
+ ${this.hass.localize( + "ui.panel.config.backup.settings.schedule.title" + )} +

- Let Home Assistant take care of your backups by creating a - scheduled backup that also removes older backups. + ${this.hass.localize( + "ui.panel.config.backup.settings.schedule.description" + )}

-
Backup data
+
+ ${this.hass.localize( + "ui.panel.config.backup.settings.data.title" + )} +
-
Locations
+
+ ${this.hass.localize( + "ui.panel.config.backup.settings.locations.title" + )} +

- Your backup will be stored on these locations when this default - backup is created. You can use all locations for custom backups. + ${this.hass.localize( + "ui.panel.config.backup.settings.locations.description" + )}

${!this._config.create_backup.agent_ids.length - ? html`You have to select at least one location to create a - backup.
` + .title=${this.hass.localize( + "ui.panel.config.backup.settings.locations.no_location" + )} + > + ${this.hass.localize( + "ui.panel.config.backup.settings.locations.no_location_description" + )} + +
+ ` : nothing}
-
Encryption key
+
+ ${this.hass.localize( + "ui.panel.config.backup.settings.encryption_key.title" + )} +

- Keep this encryption key in a safe place, as you will need it to - access your backup, allowing it to be restored. Download them as - an emergency kit file and store it somewhere safe. Encryption - keeps your backups private and secure. + ${this.hass.localize( + "ui.panel.config.backup.settings.encryption_key.description" + )}

Date: Tue, 7 Jan 2025 00:10:26 -0800 Subject: [PATCH 66/89] Restore attributes removed from ha-entity-marker in ha-map (#23603) * Restore attributes removed from ha-entity-marker in ha-map * Use Reflect --- src/components/map/ha-entity-marker.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/map/ha-entity-marker.ts b/src/components/map/ha-entity-marker.ts index 22de169418..c6764015bc 100644 --- a/src/components/map/ha-entity-marker.ts +++ b/src/components/map/ha-entity-marker.ts @@ -8,7 +8,7 @@ import "../ha-state-icon"; class HaEntityMarker extends LitElement { @property({ attribute: false }) public hass!: HomeAssistant; - @property({ attribute: "entity-id" }) public entityId?: string; + @property({ attribute: "entity-id", reflect: true }) public entityId?: string; @property({ attribute: "entity-name" }) public entityName?: string; From d0fbba50636cd5358d5530c081743e1038dec2c3 Mon Sep 17 00:00:00 2001 From: Wendelin <12148533+wendevlin@users.noreply.github.com> Date: Tue, 7 Jan 2025 14:48:28 +0100 Subject: [PATCH 67/89] Improve background-editor background-attachment alignment (#23615) --- src/components/ha-button-toggle-group.ts | 19 +++++++++++++++++-- .../ha-selector/ha-selector-button-toggle.ts | 10 ++++++++++ .../view-editor/hui-view-background-editor.ts | 3 +++ 3 files changed, 30 insertions(+), 2 deletions(-) diff --git a/src/components/ha-button-toggle-group.ts b/src/components/ha-button-toggle-group.ts index 7ec98e8e68..c57b5f0d47 100644 --- a/src/components/ha-button-toggle-group.ts +++ b/src/components/ha-button-toggle-group.ts @@ -75,8 +75,10 @@ export class HaButtonToggleGroup extends LitElement { direction: ltr; } mwc-button { + flex: 1; --mdc-shape-small: 0; --mdc-button-outline-width: 1px 0 1px 1px; + --mdc-button-outline-color: var(--primary-color); } ha-icon-button { border: 1px solid var(--primary-color); @@ -94,7 +96,7 @@ export class HaButtonToggleGroup extends LitElement { width: 100%; height: 100%; position: absolute; - background-color: currentColor; + background-color: var(--primary-color); opacity: 0; pointer-events: none; content: ""; @@ -104,12 +106,22 @@ export class HaButtonToggleGroup extends LitElement { } ha-icon-button[active]::before, mwc-button[active]::before { - opacity: var(--mdc-icon-button-ripple-opacity, 0.12); + opacity: 1; + } + ha-icon-button[active] { + --icon-primary-color: var(--text-primary-color); + } + mwc-button[active] { + --mdc-theme-primary: var(--text-primary-color); } ha-icon-button:first-child, mwc-button:first-child { --mdc-shape-small: 4px 0 0 4px; border-radius: 4px 0 0 4px; + --mdc-button-outline-width: 1px; + } + mwc-button:first-child::before { + border-radius: 4px 0 0 4px; } ha-icon-button:last-child, mwc-button:last-child { @@ -118,6 +130,9 @@ export class HaButtonToggleGroup extends LitElement { --mdc-shape-small: 0 4px 4px 0; --mdc-button-outline-width: 1px; } + mwc-button:last-child::before { + border-radius: 0 4px 4px 0; + } ha-icon-button:only-child, mwc-button:only-child { --mdc-shape-small: 4px; diff --git a/src/components/ha-selector/ha-selector-button-toggle.ts b/src/components/ha-selector/ha-selector-button-toggle.ts index 6578cee61f..727dfaef82 100644 --- a/src/components/ha-selector/ha-selector-button-toggle.ts +++ b/src/components/ha-selector/ha-selector-button-toggle.ts @@ -87,6 +87,16 @@ export class HaButtonToggleSelector extends LitElement { static styles = css` :host { position: relative; + display: flex; + justify-content: space-between; + flex-wrap: wrap; + gap: 8px; + align-items: center; + } + @media all and (max-width: 600px) { + ha-button-toggle-group { + flex: 1; + } } `; } diff --git a/src/panels/lovelace/editor/view-editor/hui-view-background-editor.ts b/src/panels/lovelace/editor/view-editor/hui-view-background-editor.ts index fe5624f2a7..41d2a5084f 100644 --- a/src/panels/lovelace/editor/view-editor/hui-view-background-editor.ts +++ b/src/panels/lovelace/editor/view-editor/hui-view-background-editor.ts @@ -54,6 +54,7 @@ export class HuiViewBackgroundEditor extends LitElement { }, { name: "size", + required: true, selector: { select: { translation_key: @@ -65,6 +66,7 @@ export class HuiViewBackgroundEditor extends LitElement { }, { name: "alignment", + required: true, selector: { select: { translation_key: @@ -86,6 +88,7 @@ export class HuiViewBackgroundEditor extends LitElement { }, { name: "repeat", + required: true, selector: { select: { translation_key: From ec1dedcb6b296d1fdf036b417ad2e4b23772e2f7 Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Tue, 7 Jan 2025 14:44:56 +0200 Subject: [PATCH 68/89] Fix tooltip scrolling (#23616) --- src/components/chart/ha-chart-base.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts index 017a5dab07..92c660dcfe 100644 --- a/src/components/chart/ha-chart-base.ts +++ b/src/components/chart/ha-chart-base.ts @@ -83,11 +83,13 @@ export class HaChartBase extends LitElement { public disconnectedCallback() { super.disconnectedCallback(); + window.removeEventListener("scroll", this._handleScroll, true); this._releaseCanvas(); } public connectedCallback() { super.connectedCallback(); + window.addEventListener("scroll", this._handleScroll, true); if (this.hasUpdated) { this._releaseCanvas(); this._setupChart(); @@ -561,6 +563,10 @@ export class HaChartBase extends LitElement { this.chart?.resetZoom(); } + private _handleScroll = () => { + this._tooltip = undefined; + }; + static get styles(): CSSResultGroup { return css` :host { From d55d388046614a2d97e14ff81790a1a618f17a0a Mon Sep 17 00:00:00 2001 From: Jan-Philipp Benecke Date: Tue, 7 Jan 2025 14:13:19 +0100 Subject: [PATCH 69/89] Set fixed width for automation save dialog (#23618) --- .../automation-rename-dialog/dialog-automation-rename.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/panels/config/automation/automation-rename-dialog/dialog-automation-rename.ts b/src/panels/config/automation/automation-rename-dialog/dialog-automation-rename.ts index f9a829d618..d484b75278 100644 --- a/src/panels/config/automation/automation-rename-dialog/dialog-automation-rename.ts +++ b/src/panels/config/automation/automation-rename-dialog/dialog-automation-rename.ts @@ -323,6 +323,14 @@ class DialogAutomationRename extends LitElement implements HassDialog { ha-dialog { --dialog-content-padding: 0 24px 24px 24px; } + + @media all and (min-width: 500px) { + ha-dialog { + --mdc-dialog-min-width: min(500px, 95vw); + --mdc-dialog-max-width: min(500px, 95vw); + } + } + ha-textfield, ha-textarea, ha-icon-picker, From 12b2edaa65c649c943cc9acf611025e4d5f538d7 Mon Sep 17 00:00:00 2001 From: karwosts <32912880+karwosts@users.noreply.github.com> Date: Wed, 8 Jan 2025 06:08:16 -0800 Subject: [PATCH 70/89] Retain event data when moving/resizing schedule item (#23621) * Retain event data when moving/resizing schedule item * update from suggestion --- src/panels/config/helpers/forms/ha-schedule-form.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/panels/config/helpers/forms/ha-schedule-form.ts b/src/panels/config/helpers/forms/ha-schedule-form.ts index 3854c9e742..9fe2ef5c7b 100644 --- a/src/panels/config/helpers/forms/ha-schedule-form.ts +++ b/src/panels/config/helpers/forms/ha-schedule-form.ts @@ -296,6 +296,7 @@ class HaScheduleForm extends LitElement { const endFormatted = formatTime24h(end, this.hass.locale, this.hass.config); newValue[day][index] = { + ...newValue[day][index], from: value.from, to: !isSameDay(start, end) || endFormatted === "0:00" @@ -322,6 +323,7 @@ class HaScheduleForm extends LitElement { const endFormatted = formatTime24h(end, this.hass.locale, this.hass.config); const event = { + ...newValue[day][index], from: formatTime24h(start, this.hass.locale, this.hass.config), to: !isSameDay(start, end) || endFormatted === "0:00" From 9acdd9f903cd058aebfaf64af43f1260d74ca911 Mon Sep 17 00:00:00 2001 From: Wendelin <12148533+wendevlin@users.noreply.github.com> Date: Wed, 8 Jan 2025 16:22:43 +0100 Subject: [PATCH 71/89] Voice assistants config: Filter unavailable assists (#23637) Filter unavailable assists from num of assist devices. --- src/panels/config/voice-assistants/assist-pref.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/panels/config/voice-assistants/assist-pref.ts b/src/panels/config/voice-assistants/assist-pref.ts index 2bd6f1bb35..c719cd4564 100644 --- a/src/panels/config/voice-assistants/assist-pref.ts +++ b/src/panels/config/voice-assistants/assist-pref.ts @@ -84,7 +84,9 @@ export class AssistPref extends LitElement { this._preferred = pipelines.preferred_pipeline; }); this._pipelineEntitiesCount = Object.values(this.hass.entities).filter( - (entity) => computeDomain(entity.entity_id) === "assist_satellite" + (entity) => + computeDomain(entity.entity_id) === "assist_satellite" && + this.hass.states[entity.entity_id].state !== "unavailable" ).length; } From 14c71f436e4ff6786eb25e49831b826c502b4fc1 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Wed, 8 Jan 2025 21:56:06 +0100 Subject: [PATCH 72/89] Remove ! from backup translation (#23648) --- src/translations/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/translations/en.json b/src/translations/en.json index a2cf0f9765..ababc5e566 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2460,7 +2460,7 @@ "menu": { "upload_backup": "Upload backup" }, - "new_backup": "Backup now!", + "new_backup": "Backup now", "onboarding": { "title": "Set up backups", "description": "Backups are essential for a reliable smart home. They help protect the work you've put into setting up your smart home, and if the worst happens, you can get back up and running quickly.", From 6dde7d6945e3304beb1e8e1e70e2cbe741a2b152 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 9 Jan 2025 09:26:34 +0100 Subject: [PATCH 73/89] Fix backup translations key issues (#23654) Co-authored-by: Wendelin --- .../backup/components/overview/ha-backup-overview-backups.ts | 2 +- .../backup/components/overview/ha-backup-overview-summary.ts | 3 ++- src/panels/config/backup/dialogs/dialog-new-backup.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/panels/config/backup/components/overview/ha-backup-overview-backups.ts b/src/panels/config/backup/components/overview/ha-backup-overview-backups.ts index 43b93be39f..3f7ccf5545 100644 --- a/src/panels/config/backup/components/overview/ha-backup-overview-backups.ts +++ b/src/panels/config/backup/components/overview/ha-backup-overview-backups.ts @@ -85,7 +85,7 @@ class HaBackupOverviewBackups extends LitElement {
${this.hass.localize( - "ui.panel.config.backup.overview.backups.automatic", + "ui.panel.config.backup.overview.backups.manual", { count: manualStats.count } )}
diff --git a/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts b/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts index 09c12c0831..df04e12519 100644 --- a/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts +++ b/src/panels/config/backup/components/overview/ha-backup-overview-summary.ts @@ -255,7 +255,8 @@ class HaBackupOverviewBackups extends LitElement { return html` diff --git a/src/panels/config/backup/dialogs/dialog-new-backup.ts b/src/panels/config/backup/dialogs/dialog-new-backup.ts index 42da59043a..7b608fba33 100644 --- a/src/panels/config/backup/dialogs/dialog-new-backup.ts +++ b/src/panels/config/backup/dialogs/dialog-new-backup.ts @@ -82,7 +82,7 @@ class DialogNewBackup extends LitElement implements HassDialog { ${this.hass.localize( - "ui.panel.config.backup.dialogs.new.automatic.description" + "ui.panel.config.backup.dialogs.new.automatic.title" )} From c01d3aee41b22300a481a5055693cec367bb2f0d Mon Sep 17 00:00:00 2001 From: Wendelin <12148533+wendevlin@users.noreply.github.com> Date: Thu, 9 Jan 2025 14:49:49 +0100 Subject: [PATCH 74/89] Fix backup summary label position (#23655) --- .../config/backup/ha-config-backup-details.ts | 39 ++++++++++++------- src/translations/en.json | 2 +- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/panels/config/backup/ha-config-backup-details.ts b/src/panels/config/backup/ha-config-backup-details.ts index 5c8f2ea510..158573bb88 100644 --- a/src/panels/config/backup/ha-config-backup-details.ts +++ b/src/panels/config/backup/ha-config-backup-details.ts @@ -142,31 +142,38 @@ class HaConfigBackupDetails extends LitElement { )}
- + - ${bytesToString(this._backup.size)} - - ${this.hass.localize( "ui.panel.config.backup.details.summary.size" )} + + ${bytesToString(this._backup.size)} + - ${formatDateTime( - new Date(this._backup.date), - this.hass.locale, - this.hass.config - )} - + ${this.hass.localize( "ui.panel.config.backup.details.summary.created" )} + + ${formatDateTime( + new Date(this._backup.date), + this.hass.locale, + this.hass.config + )} + + ${this.hass.localize( + "ui.panel.config.backup.details.summary.protection" + )} + + ${this._backup.protected ? this.hass.localize( "ui.panel.config.backup.details.summary.protected_encrypted_aes_128" @@ -175,11 +182,6 @@ class HaConfigBackupDetails extends LitElement { "ui.panel.config.backup.details.summary.protected_not_encrypted" )} - - ${this.hass.localize( - "ui.panel.config.backup.details.summary.protected" - )} -
@@ -433,6 +435,13 @@ class HaConfigBackupDetails extends LitElement { --mdc-icon-size: 48px; color: var(--primary-text-color); } + ha-md-list.summary ha-md-list-item { + --md-list-item-supporting-text-size: 1rem; + --md-list-item-label-text-size: 0.875rem; + + --md-list-item-label-text-color: var(--secondary-text-color); + --md-list-item-supporting-text-color: var(--primary-text-color); + } .warning { color: var(--error-color); } diff --git a/src/translations/en.json b/src/translations/en.json index ababc5e566..06b7954e62 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2600,7 +2600,7 @@ "title": "Backup", "size": "Size", "created": "Created", - "protected": "Protected", + "protection": "Protection", "protected_encrypted_aes_128": "Encrypted AES-128", "protected_not_encrypted": "Not encrypted" }, From b02d0e58b1ecc048814de6995d32143fc4cc29f2 Mon Sep 17 00:00:00 2001 From: Gord <31004434+googanhiem@users.noreply.github.com> Date: Thu, 9 Jan 2025 11:46:58 +0000 Subject: [PATCH 75/89] Backup text changes for the english translation (#23656) * Update backup text in en.json * fix missing quote en.json --- src/translations/en.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/translations/en.json b/src/translations/en.json index 06b7954e62..6c7587e163 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2293,8 +2293,8 @@ "encryption": { "different_key": "The backup is encrypted. Provide the encryption key to decrypt the backup.", "incorrect_key": "The provided encryption key was incorrect, please try again.", - "description": "The Backup is encrypted with a different encryption key than that is saved on this system. Please enter the encryption key for this backup.", - "warning": "After restoring the backup, your new backups will be encrypted with the encryption key that was present during the time of this backup.", + "description": "The backup is encrypted with a different encryption key than the one stored on this system. Please enter the encryption key for this backup.", + "warning": "After restoring this backup, all new backups will be encrypted using the same key that was used to restore this backup.", "input_label": "Encryption key" }, "progress": { @@ -2312,7 +2312,7 @@ }, "key": { "title": "Encryption key", - "description": "All your backups are encrypted to keep your data private and secure. We recommend to save this key somewhere secure. As you can only restore your data with the backup encryption key." + "description": "All your backups are encrypted to keep your data private and secure. We recommend saving this key somewhere secure. As you can only restore your data with this encryption key." }, "setup": { "title": "Set up automatic backups", @@ -2338,11 +2338,11 @@ "change_encryption_key": { "current": { "title": "Save current encryption key", - "description": "Make sure you have saved the current encryption key to make sure you have access to all your current backups. All next backups will use the new encryption key." + "description": "Backups made before this new encryption key was issued will continue to use the old key. Be sure to save the old key along with the new key to ensure that you can access all backups." }, "new": { "title": "New encryption key", - "description": "All next backups will use the new encryption key. Encryption keeps your backups private and secure." + "description": "All future backups will use the new encryption key. Encryption keeps your backups private and secure." }, "done": { "title": "Encryption key changed" @@ -2355,7 +2355,7 @@ "set_encryption_key": { "key": { "title": "Set encryption key", - "description": "All your backups are encrypted to keep your data private and secure. We recommend to save this key somewhere secure. As you can only restore your data with the backup encryption key." + "description": "All your backups are encrypted to keep your data private and secure. We recommend saving this key somewhere secure. As you can only restore your data with the backup encryption key." }, "done": { "title": "Encryption key set" @@ -2367,11 +2367,11 @@ }, "show_encryption_key": { "title": "Encryption key", - "description": "Make sure you save the encryption key in a secure place so always have access to your backups." + "description": "Make sure you save the encryption key in a secure place so you always have access to your backups." } }, "agents": { - "cloud_agent_description": "Note: It stores only one backup with a maximum size of 5 GB, regardless of your settings.", + "cloud_agent_description": "Note: It stores only the most recent backup, regardless of your retention settings, with a maximum size of 5 GB.", "cloud_agent_no_subcription": "You currently do not have an active Home Assistant Cloud subscription.", "network_mount_agent_description": "Network storage", "no_agents": "No locations configured", @@ -2440,7 +2440,7 @@ "show_encryption_key_description": "Please keep your encryption key private.", "show_encryption_key_action": "Show", "change_encryption_key": "Change encryption key", - "change_encryption_key_description": "All next backups will use this encryption key.", + "change_encryption_key_description": "All future backups will use this encryption key.", "change_encryption_key_action": "Change", "set_encryption_key": "Set encryption key", "set_encryption_key_description": "Set an encryption key for your backups.", @@ -2588,7 +2588,7 @@ }, "encryption_key": { "title": "Encryption key", - "description": "Keep this encryption key in a safe place, as you will need it to access your backup, allowing it to be restored. Download them as an emergency kit file and store it somewhere safe. Encryption keeps your backups private and secure." + "description": "Keep this encryption key in a safe place, as you will need it to access your backup, allowing it to be restored. Download it as an emergency kit file and store it somewhere safe. Encryption keeps your backups private and secure." } }, "details": { From 5e7f3567071fd5f23dd99bac9618ca3c921c274c Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 9 Jan 2025 17:26:42 +0100 Subject: [PATCH 76/89] Fix preferred agent for backup download (#23659) --- src/data/backup.ts | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/data/backup.ts b/src/data/backup.ts index 5133787f03..d1299a2a7e 100644 --- a/src/data/backup.ts +++ b/src/data/backup.ts @@ -1,16 +1,16 @@ import { setHours, setMinutes } from "date-fns"; import type { HassConfig } from "home-assistant-js-websocket"; import memoizeOne from "memoize-one"; -import { formatTime } from "../common/datetime/format_time"; -import type { LocalizeFunc } from "../common/translations/localize"; -import type { HomeAssistant } from "../types"; -import { domainToName } from "./integration"; -import type { FrontendLocaleData } from "./translation"; import { formatDateTime, formatDateTimeNumeric, } from "../common/datetime/format_date_time"; +import { formatTime } from "../common/datetime/format_time"; +import type { LocalizeFunc } from "../common/translations/localize"; +import type { HomeAssistant } from "../types"; import { fileDownload } from "../util/file_download"; +import { domainToName } from "./integration"; +import type { FrontendLocaleData } from "./translation"; export const enum BackupScheduleState { NEVER = "never", @@ -217,10 +217,16 @@ export const uploadBackup = async ( }; export const getPreferredAgentForDownload = (agents: string[]) => { - const localAgents = agents.filter( - (agent) => agent.split(".")[0] === "backup" - ); - return localAgents[0] || agents[0]; + const localAgent = agents.find(isLocalAgent); + if (localAgent) { + return localAgent; + } + const networkMountAgent = agents.find(isNetworkMountAgent); + if (networkMountAgent) { + return networkMountAgent; + } + + return agents[0]; }; export const CORE_LOCAL_AGENT = "backup.local"; From 2e2f39adbd9842b51f0fdf3689caa5f7b2529849 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 9 Jan 2025 21:02:30 +0100 Subject: [PATCH 77/89] Bumped version to 20250109.0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 801c103f69..01d6a21fe9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20250106.0" +version = "20250109.0" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md" From 54b328648a44bbeeb89f5ca67130ab8bc5ff711b Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 2 Jan 2025 15:53:57 +0100 Subject: [PATCH 78/89] =?UTF-8?q?Match=20UI=20with=20core=20and=20don't=20?= =?UTF-8?q?allow=20restore=20config=20without=20db=20and=20vice=20?= =?UTF-8?q?=E2=80=A6=20(#23553)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Match UI with core and dont allow restore config without db and vice versa --- .../backup/components/ha-backup-data-picker.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/panels/config/backup/components/ha-backup-data-picker.ts b/src/panels/config/backup/components/ha-backup-data-picker.ts index 57a7bd3e52..4b5cfa86df 100644 --- a/src/panels/config/backup/components/ha-backup-data-picker.ts +++ b/src/panels/config/backup/components/ha-backup-data-picker.ts @@ -86,12 +86,6 @@ export class HaBackupDataPicker extends LitElement { version: data.homeassistant_version, }); } - if (data.database_included) { - items.push({ - label: "History", - id: "database", - }); - } items.push( ...data.folders.map((folder) => ({ label: this._localizeFolder(folder), @@ -145,9 +139,6 @@ export class HaBackupDataPicker extends LitElement { if (value.homeassistant_included) { homeassistant.push("config"); } - if (value.database_included) { - homeassistant.push("database"); - } const folders = value.folders; homeassistant.push(...folders); @@ -164,7 +155,9 @@ export class HaBackupDataPicker extends LitElement { (selectedItems: SelectedItems, data: BackupData): BackupData => ({ homeassistant_version: data.homeassistant_version, homeassistant_included: selectedItems.homeassistant.includes("config"), - database_included: selectedItems.homeassistant.includes("database"), + database_included: + data.database_included && + selectedItems.homeassistant.includes("config"), addons: data.addons.filter((addon) => selectedItems.addons.includes(addon.slug) ), From 8c52bc3ffbdfd24d401f903bc27809b062dd8837 Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Tue, 14 Jan 2025 12:24:52 +0200 Subject: [PATCH 79/89] Fix more-info chart rendering (#23619) * Fix more-info chart rendering * lint fix * remove animation-container & _chartHeight * don't change height on resize * handle default height in ha-chart-base * fix chart height in energy panel * lint * lint --- src/components/chart/ha-chart-base.ts | 210 +++++++----------- .../chart/state-history-chart-line.ts | 9 +- .../chart/state-history-chart-timeline.ts | 9 +- src/components/chart/state-history-charts.ts | 21 +- src/components/chart/statistics-chart.ts | 14 +- src/dialogs/more-info/ha-more-info-dialog.ts | 12 +- .../ha-more-info-history-and-logbook.ts | 11 +- src/dialogs/more-info/ha-more-info-history.ts | 15 +- src/dialogs/more-info/ha-more-info-info.ts | 11 +- 9 files changed, 91 insertions(+), 221 deletions(-) diff --git a/src/components/chart/ha-chart-base.ts b/src/components/chart/ha-chart-base.ts index 92c660dcfe..1321a7da21 100644 --- a/src/components/chart/ha-chart-base.ts +++ b/src/components/chart/ha-chart-base.ts @@ -4,6 +4,7 @@ import type { ChartData, ChartOptions, TooltipModel, + UpdateMode, } from "chart.js"; import type { CSSResultGroup, PropertyValues } from "lit"; import { css, html, nothing, LitElement } from "lit"; @@ -20,12 +21,6 @@ import "../ha-icon-button"; export const MIN_TIME_BETWEEN_UPDATES = 60 * 5 * 1000; -export interface ChartResizeOptions { - aspectRatio?: number; - height?: number; - width?: number; -} - interface Tooltip extends Omit, "tooltipPosition" | "hasValue" | "getProps"> { top: string; @@ -61,8 +56,6 @@ export class HaChartBase extends LitElement { @property({ attribute: "external-hidden", type: Boolean }) public externalHidden = false; - @state() private _chartHeight?: number; - @state() private _legendHeight?: number; @state() private _tooltip?: Tooltip; @@ -96,36 +89,10 @@ export class HaChartBase extends LitElement { } } - public updateChart = ( - mode: - | "resize" - | "reset" - | "none" - | "hide" - | "show" - | "default" - | "active" - | undefined - ): void => { + public updateChart = (mode?: UpdateMode): void => { this.chart?.update(mode); }; - public resize = (options?: ChartResizeOptions): void => { - if (options?.aspectRatio && !options.height) { - options.height = Math.round( - (options.width ?? this.clientWidth) / options.aspectRatio - ); - } else if (options?.aspectRatio && !options.width) { - options.width = Math.round( - (options.height ?? this.clientHeight) * options.aspectRatio - ); - } - this.chart?.resize( - options?.width ?? this.clientWidth, - options?.height ?? this.clientHeight - ); - }; - protected firstUpdated() { this._setupChart(); this.data.datasets.forEach((dataset, index) => { @@ -267,96 +234,84 @@ export class HaChartBase extends LitElement {
` : ""}
-
+
- -
-
- ${isMac - ? this.hass.localize( - "ui.components.history_charts.zoom_hint_mac" - ) - : this.hass.localize("ui.components.history_charts.zoom_hint")} -
+
+ ${isMac + ? this.hass.localize("ui.components.history_charts.zoom_hint_mac") + : this.hass.localize("ui.components.history_charts.zoom_hint")}
- ${this._isZoomed && this.chartType !== "timeline" - ? html`` - : nothing} - ${this._tooltip - ? html`
-
${this._tooltip.title}
- ${this._tooltip.beforeBody - ? html`
- ${this._tooltip.beforeBody} -
` - : ""} -
-
    - ${this._tooltip.body.map( - (item, i) => - html`
  • -
    - ${item.lines.join("\n")} -
  • ` - )} -
-
- ${this._tooltip.footer.length - ? html`` - : ""} -
` - : ""}
+ ${this._isZoomed && this.chartType !== "timeline" + ? html`` + : nothing} + ${this._tooltip + ? html`
+
${this._tooltip.title}
+ ${this._tooltip.beforeBody + ? html`
+ ${this._tooltip.beforeBody} +
` + : ""} +
+
    + ${this._tooltip.body.map( + (item, i) => + html`
  • +
    + ${item.lines.join("\n")} +
  • ` + )} +
+
+ ${this._tooltip.footer.length + ? html`` + : ""} +
` + : ""}
`; } @@ -471,11 +426,11 @@ export class HaChartBase extends LitElement { ...(this.plugins || []), { id: "resizeHook", - resize: (chart) => { - const change = chart.height - (this._chartHeight ?? 0); - if (!this._chartHeight || change > 12 || change < -12) { - // hysteresis to prevent infinite render loops - this._chartHeight = chart.height; + resize: (chart: Chart) => { + if (!this.height) { + // lock the height + // this removes empty space below the chart + this.height = chart.height; } }, legend: { @@ -486,6 +441,10 @@ export class HaChartBase extends LitElement { ]; } + private _getDefaultHeight() { + return this.clientWidth / 2; + } + private _handleChartScroll(ev: MouseEvent) { const modifier = isMac ? "metaKey" : "ctrlKey"; this._tooltip = undefined; @@ -573,11 +532,6 @@ export class HaChartBase extends LitElement { display: block; position: relative; } - .animation-container { - overflow: hidden; - height: 0; - transition: height 300ms cubic-bezier(0.4, 0, 0.2, 1); - } .chart-container { position: relative; } diff --git a/src/components/chart/state-history-chart-line.ts b/src/components/chart/state-history-chart-line.ts index e2113b7478..887952f3b3 100644 --- a/src/components/chart/state-history-chart-line.ts +++ b/src/components/chart/state-history-chart-line.ts @@ -1,7 +1,7 @@ import type { ChartData, ChartDataset, ChartOptions } from "chart.js"; import type { PropertyValues } from "lit"; import { html, LitElement } from "lit"; -import { property, query, state } from "lit/decorators"; +import { property, state } from "lit/decorators"; import { getGraphColorByIndex } from "../../common/color/colors"; import { fireEvent } from "../../common/dom/fire_event"; import { computeRTL } from "../../common/util/compute_rtl"; @@ -12,7 +12,6 @@ import { } from "../../common/number/format_number"; import type { LineChartEntity, LineChartState } from "../../data/history"; import type { HomeAssistant } from "../../types"; -import type { ChartResizeOptions, HaChartBase } from "./ha-chart-base"; import { MIN_TIME_BETWEEN_UPDATES } from "./ha-chart-base"; import { clickIsTouch } from "./click_is_touch"; @@ -67,12 +66,6 @@ export class StateHistoryChartLine extends LitElement { private _chartTime: Date = new Date(); - @query("ha-chart-base") private _chart?: HaChartBase; - - public resize = (options?: ChartResizeOptions): void => { - this._chart?.resize(options); - }; - protected render() { return html` { - this._chart?.resize(options); - }; - protected render() { return html` { - this._charts?.forEach( - (chart: StateHistoryChartLine | StateHistoryChartTimeline) => - chart.resize(options) - ); - }; - protected render() { if (!isComponentLoaded(this.hass, "history")) { return html`
diff --git a/src/components/chart/statistics-chart.ts b/src/components/chart/statistics-chart.ts index 2873df4d60..ff0dd5d0fe 100644 --- a/src/components/chart/statistics-chart.ts +++ b/src/components/chart/statistics-chart.ts @@ -6,7 +6,7 @@ import type { } from "chart.js"; import type { CSSResultGroup, PropertyValues, TemplateResult } from "lit"; import { css, html, LitElement } from "lit"; -import { customElement, property, state, query } from "lit/decorators"; +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"; @@ -30,11 +30,7 @@ import { } from "../../data/recorder"; import type { HomeAssistant } from "../../types"; import "./ha-chart-base"; -import type { - ChartResizeOptions, - ChartDatasetExtra, - HaChartBase, -} from "./ha-chart-base"; +import type { ChartDatasetExtra } from "./ha-chart-base"; import { clickIsTouch } from "./click_is_touch"; export const supportedStatTypeMap: Record = { @@ -98,14 +94,8 @@ export class StatisticsChart extends LitElement { @state() private _hiddenStats = new Set(); - @query("ha-chart-base") private _chart?: HaChartBase; - private _computedStyle?: CSSStyleDeclaration; - public resize = (options?: ChartResizeOptions): void => { - this._chart?.resize(options); - }; - protected shouldUpdate(changedProps: PropertyValues): boolean { return changedProps.size > 1 || !changedProps.has("hass"); } diff --git a/src/dialogs/more-info/ha-more-info-dialog.ts b/src/dialogs/more-info/ha-more-info-dialog.ts index 51d14f1cce..37ea6841f9 100644 --- a/src/dialogs/more-info/ha-more-info-dialog.ts +++ b/src/dialogs/more-info/ha-more-info-dialog.ts @@ -12,7 +12,7 @@ import { import type { HassEntity } from "home-assistant-js-websocket"; import type { PropertyValues } from "lit"; import { LitElement, css, html, nothing } from "lit"; -import { customElement, property, query, state } from "lit/decorators"; +import { customElement, property, state } from "lit/decorators"; import { cache } from "lit/directives/cache"; import { dynamicElement } from "../../common/dom/dynamic-element-directive"; import { fireEvent } from "../../common/dom/fire_event"; @@ -47,9 +47,7 @@ import { } from "./const"; import "./controls/more-info-default"; import "./ha-more-info-history-and-logbook"; -import type { MoreInfoHistoryAndLogbook } from "./ha-more-info-history-and-logbook"; import "./ha-more-info-info"; -import type { MoreInfoInfo } from "./ha-more-info-info"; import "./ha-more-info-settings"; import "./more-info-content"; @@ -98,9 +96,6 @@ export class MoreInfoDialog extends LitElement { @state() private _infoEditMode = false; - @query("ha-more-info-info, ha-more-info-history-and-logbook") - private _history?: MoreInfoInfo | MoreInfoHistoryAndLogbook; - public showDialog(params: MoreInfoDialogParams) { this._entityId = params.entityId; if (!this._entityId) { @@ -283,7 +278,6 @@ export class MoreInfoDialog extends LitElement { ; - @query("statistics-chart, state-history-charts") private _chart?: - | StateHistoryCharts - | StatisticsChart; - - public resize = (options?: ChartResizeOptions): void => { - if (this._chart) { - this._chart.resize(options); - } - }; - protected render() { if (!this.entityId) { return nothing; diff --git a/src/dialogs/more-info/ha-more-info-info.ts b/src/dialogs/more-info/ha-more-info-info.ts index efee44afbd..be4a09e4a0 100644 --- a/src/dialogs/more-info/ha-more-info-info.ts +++ b/src/dialogs/more-info/ha-more-info-info.ts @@ -1,8 +1,7 @@ import type { HassEntity } from "home-assistant-js-websocket"; import { css, html, LitElement, nothing } from "lit"; -import { customElement, property, query } from "lit/decorators"; +import { customElement, property } from "lit/decorators"; import { computeDomain } from "../../common/entity/compute_domain"; -import type { ChartResizeOptions } from "../../components/chart/ha-chart-base"; import type { ExtEntityRegistryEntry } from "../../data/entity_registry"; import type { HomeAssistant } from "../../types"; import { @@ -14,7 +13,6 @@ import { DOMAINS_WITH_MORE_INFO, } from "./const"; import "./ha-more-info-history"; -import type { MoreInfoHistory } from "./ha-more-info-history"; import "./ha-more-info-logbook"; import "./more-info-content"; @@ -28,13 +26,6 @@ export class MoreInfoInfo extends LitElement { @property({ attribute: false }) public editMode?: boolean; - @query("ha-more-info-history") - private _history?: MoreInfoHistory; - - public resize(options?: ChartResizeOptions) { - this._history?.resize(options); - } - protected render() { const entityId = this.entityId; const stateObj = this.hass.states[entityId] as HassEntity | undefined; From bf471eb8c377fcacce748ad0d3925d9cfbd185b0 Mon Sep 17 00:00:00 2001 From: Simon Lamon <32477463+silamon@users.noreply.github.com> Date: Sun, 12 Jan 2025 08:24:35 +0100 Subject: [PATCH 80/89] Minor fixes for backup translations (#23691) --- .../config/backup/components/config/ha-backup-config-data.ts | 2 +- src/panels/config/backup/dialogs/dialog-upload-backup.ts | 4 +++- src/translations/en.json | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/panels/config/backup/components/config/ha-backup-config-data.ts b/src/panels/config/backup/components/config/ha-backup-config-data.ts index 7d48cb6906..23786dc258 100644 --- a/src/panels/config/backup/components/config/ha-backup-config-data.ts +++ b/src/panels/config/backup/components/config/ha-backup-config-data.ts @@ -209,7 +209,7 @@ class HaBackupConfigData extends LitElement { ${this.hass.localize( - "ui.panel.config.backup.data.history_description" + "ui.panel.config.backup.data.media_description" )}
- Cancel + ${this.hass.localize("ui.common.cancel")} ${this.hass.localize( "ui.panel.config.backup.dialogs.upload.action" diff --git a/src/translations/en.json b/src/translations/en.json index 6c7587e163..d0742548db 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2605,7 +2605,7 @@ "protected_not_encrypted": "Not encrypted" }, "restore": { - "title": "Selected what to restore", + "title": "Select what to restore", "action": "Restore" }, "locations": { From b28e7d2f06f35f540e110416f79cf23eb3eff86d Mon Sep 17 00:00:00 2001 From: Petar Petrov Date: Tue, 14 Jan 2025 10:25:01 +0200 Subject: [PATCH 81/89] Fix navigation from stacked dialogs with the same name (#23698) * Fix navigation from stacked dialogs * lint fix * Keep only 1 instance per dialog tag in the stack --- src/dialogs/make-dialog-manager.ts | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/dialogs/make-dialog-manager.ts b/src/dialogs/make-dialog-manager.ts index 6a780e0189..1c0f954a14 100644 --- a/src/dialogs/make-dialog-manager.ts +++ b/src/dialogs/make-dialog-manager.ts @@ -104,6 +104,12 @@ export const showDialog = async ( addHistory ); } + const dialogIndex = OPEN_DIALOG_STACK.findIndex( + (state) => state.dialogTag === dialogTag + ); + if (dialogIndex !== -1) { + OPEN_DIALOG_STACK.splice(dialogIndex, 1); + } OPEN_DIALOG_STACK.push({ element, root, @@ -173,8 +179,10 @@ export const closeLastDialog = async () => { export const closeAllDialogs = async () => { for (let i = OPEN_DIALOG_STACK.length - 1; i >= 0; i--) { - // eslint-disable-next-line no-await-in-loop - const closed = await closeDialog(OPEN_DIALOG_STACK[i].dialogTag); + const closed = + !OPEN_DIALOG_STACK[i] || + // eslint-disable-next-line no-await-in-loop + (await closeDialog(OPEN_DIALOG_STACK[i].dialogTag)); if (!closed) { return false; } From 6b471ba6e77f4d5976b97101dcbfc211577941c6 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Tue, 14 Jan 2025 11:28:32 +0100 Subject: [PATCH 82/89] Fix background on cast devices (#23731) --- cast/src/html/receiver.html.template | 1 - src/panels/lovelace/views/hui-view-background.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/cast/src/html/receiver.html.template b/cast/src/html/receiver.html.template index d326ce55ff..0cb9154fc9 100644 --- a/cast/src/html/receiver.html.template +++ b/cast/src/html/receiver.html.template @@ -7,7 +7,6 @@ <%= renderTemplate("../../../src/html/_style_base.html.template") %> diff --git a/src/panels/lovelace/views/hui-view-background.ts b/src/panels/lovelace/views/hui-view-background.ts index 5a065d2650..06b7439298 100644 --- a/src/panels/lovelace/views/hui-view-background.ts +++ b/src/panels/lovelace/views/hui-view-background.ts @@ -55,7 +55,7 @@ export class HUIViewBackground extends LitElement { const alignment = background.alignment ?? "center"; const size = background.size ?? "cover"; const repeat = background.repeat ?? "no-repeat"; - return `${alignment} / ${size} ${repeat} url('${background.image}')`; + return `${alignment} / ${size} ${repeat} url('${this.hass.hassUrl(background.image)}')`; } if (typeof background === "string") { return background; From ac98672cb7caeedfcc11a6fc23268576d458d2c5 Mon Sep 17 00:00:00 2001 From: Wendelin <12148533+wendevlin@users.noreply.github.com> Date: Tue, 14 Jan 2025 15:41:43 +0100 Subject: [PATCH 83/89] Fix background (#23736) --- cast/src/receiver/layout/hc-lovelace.ts | 3 ++- src/panels/lovelace/hui-root.ts | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cast/src/receiver/layout/hc-lovelace.ts b/cast/src/receiver/layout/hc-lovelace.ts index 0cf3d5121d..b84b052484 100644 --- a/cast/src/receiver/layout/hc-lovelace.ts +++ b/cast/src/receiver/layout/hc-lovelace.ts @@ -59,7 +59,8 @@ class HcLovelace extends LitElement { return html` - + + - + +
`; From c6f0c62fd6be3e4a1f5670f144f5b813a1b0a0e0 Mon Sep 17 00:00:00 2001 From: Bram Kragten Date: Thu, 16 Jan 2025 12:46:10 +0100 Subject: [PATCH 84/89] Prevent race in dialog box (#23758) Co-authored-by: Petar Petrov Co-authored-by: Wendelin <12148533+wendevlin@users.noreply.github.com> --- src/dialogs/generic/dialog-box.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/dialogs/generic/dialog-box.ts b/src/dialogs/generic/dialog-box.ts index 212c181c27..c24d6d11ea 100644 --- a/src/dialogs/generic/dialog-box.ts +++ b/src/dialogs/generic/dialog-box.ts @@ -27,7 +27,14 @@ class DialogBox extends LitElement { @query("ha-md-dialog") private _dialog?: HaMdDialog; + private _closePromise?: Promise; + + private _closeResolve?: () => void; + public async showDialog(params: DialogBoxParams): Promise { + if (this._closePromise) { + await this._closePromise; + } this._params = params; } @@ -132,21 +139,24 @@ class DialogBox extends LitElement { private _dismiss(): void { this._closeState = "canceled"; - this._closeDialog(); this._cancel(); + this._closeDialog(); } private _confirm(): void { this._closeState = "confirmed"; - this._closeDialog(); if (this._params!.confirm) { this._params!.confirm(this._textField?.value); } + this._closeDialog(); } private _closeDialog() { fireEvent(this, "dialog-closed", { dialog: this.localName }); this._dialog?.close(); + this._closePromise = new Promise((resolve) => { + this._closeResolve = resolve; + }); } private _dialogClosed() { @@ -156,6 +166,8 @@ class DialogBox extends LitElement { } this._closeState = undefined; this._params = undefined; + this._closeResolve?.(); + this._closeResolve = undefined; } static get styles(): CSSResultGroup { From bf1b0ac949d4cb5f46b3d15244620efc12dad7e3 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 23 Jan 2025 14:17:53 +0100 Subject: [PATCH 85/89] Bumped version to 20250109.1 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 01d6a21fe9..4a7e60bbf1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20250109.0" +version = "20250109.1" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md" From dcb74ad2ee8d4912616cd44b80312f3d78937d47 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Tue, 21 Jan 2025 13:17:33 +0100 Subject: [PATCH 86/89] Fix backup data picker translations (#23826) --- .../backup/components/ha-backup-data-picker.ts | 16 +++++++++------- src/translations/en.json | 3 ++- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/panels/config/backup/components/ha-backup-data-picker.ts b/src/panels/config/backup/components/ha-backup-data-picker.ts index 4b5cfa86df..b4e5fbdcfb 100644 --- a/src/panels/config/backup/components/ha-backup-data-picker.ts +++ b/src/panels/config/backup/components/ha-backup-data-picker.ts @@ -4,6 +4,7 @@ import { mdiFolder, mdiPlayBoxMultiple, mdiPuzzle, + mdiShieldCheck, } from "@mdi/js"; import type { CSSResultGroup, PropertyValues } from "lit"; import { css, html, LitElement, nothing } from "lit"; @@ -36,6 +37,7 @@ const ITEM_ICONS = { database: mdiChartBox, media: mdiPlayBoxMultiple, share: mdiFolder, + ssl: mdiShieldCheck, }; type SelectedItems = { @@ -104,6 +106,8 @@ export class HaBackupDataPicker extends LitElement { return this.hass.localize( "ui.panel.config.backup.data_picker.share_folder" ); + case "ssl": + return this.hass.localize("ui.panel.config.backup.data_picker.ssl"); case "addons/local": return this.hass.localize( "ui.panel.config.backup.data_picker.local_addons" @@ -167,15 +171,14 @@ export class HaBackupDataPicker extends LitElement { }) ); - private _itemChanged(ev: Event) { + private _homeassistantChanged(ev: Event) { const itemValues = this._parseValue(this.value); const checkbox = ev.currentTarget as HaCheckbox; - const section = (checkbox as any).section; if (checkbox.checked) { - itemValues[section].push(checkbox.id); + itemValues.homeassistant.push(checkbox.id); } else { - itemValues[section] = itemValues[section].filter( + itemValues.homeassistant = itemValues.homeassistant.filter( (id) => id !== checkbox.id ); } @@ -262,8 +265,7 @@ export class HaBackupDataPicker extends LitElement { .checked=${selectedItems.homeassistant.includes( item.id )} - .section=${"homeassistant"} - @change=${this._itemChanged} + @change=${this._homeassistantChanged} > ` @@ -279,7 +281,7 @@ export class HaBackupDataPicker extends LitElement { diff --git a/src/translations/en.json b/src/translations/en.json index d0742548db..8991ad874a 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2401,7 +2401,8 @@ "media": "Media", "share_folder": "Share folder", "local_addons": "Local add-ons folder", - "addons": "Add-ons" + "addons": "Add-ons", + "ssl": "SSL certificates" }, "schedule": { "use_automatic_backups": "Use automatic backups", From 7a7c204d74e55ff6b903783e32f337689e990e6c Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Wed, 22 Jan 2025 14:16:35 +0100 Subject: [PATCH 87/89] Fix delete button for state content in iOS (#23839) --- .../entity/ha-entity-state-content-picker.ts | 10 +++------- src/components/ha-selector/ha-selector-select.ts | 3 ++- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/components/entity/ha-entity-state-content-picker.ts b/src/components/entity/ha-entity-state-content-picker.ts index b83f490167..2ccc606668 100644 --- a/src/components/entity/ha-entity-state-content-picker.ts +++ b/src/components/entity/ha-entity-state-content-picker.ts @@ -178,7 +178,7 @@ class HaEntityStatePicker extends LitElement { no-style @item-moved=${this._moveItem} .disabled=${this.disabled} - filter="button.trailing.action" + handle-selector="[data-handle]" > ${repeat( @@ -194,13 +194,9 @@ class HaEntityStatePicker extends LitElement { @remove=${this._removeItem} .label=${label} selected + data-handle > - - + ${label} `; diff --git a/src/components/ha-selector/ha-selector-select.ts b/src/components/ha-selector/ha-selector-select.ts index b32c02c6fd..bb5fb989ef 100644 --- a/src/components/ha-selector/ha-selector-select.ts +++ b/src/components/ha-selector/ha-selector-select.ts @@ -156,6 +156,7 @@ export class HaSelectSelector extends LitElement { no-style .disabled=${!this.selector.select.reorder} @item-moved=${this._itemMoved} + handle-selector="[data-handle]" > ${repeat( @@ -171,13 +172,13 @@ export class HaSelectSelector extends LitElement { @remove=${this._removeItem} .label=${label} selected + data-handle > ${this.selector.select?.reorder ? html` ` : nothing} From 77c17861711aa85f3c5bdbdb699af53cf8e7d2fb Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Wed, 22 Jan 2025 15:25:06 +0100 Subject: [PATCH 88/89] Fix delete button for state content in iOS and Android (#23847) --- src/components/entity/ha-entity-state-content-picker.ts | 3 +-- src/components/ha-selector/ha-selector-select.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/components/entity/ha-entity-state-content-picker.ts b/src/components/entity/ha-entity-state-content-picker.ts index 2ccc606668..2e1e25c988 100644 --- a/src/components/entity/ha-entity-state-content-picker.ts +++ b/src/components/entity/ha-entity-state-content-picker.ts @@ -178,7 +178,7 @@ class HaEntityStatePicker extends LitElement { no-style @item-moved=${this._moveItem} .disabled=${this.disabled} - handle-selector="[data-handle]" + handle-selector="button.primary.action" > ${repeat( @@ -194,7 +194,6 @@ class HaEntityStatePicker extends LitElement { @remove=${this._removeItem} .label=${label} selected - data-handle > ${label} diff --git a/src/components/ha-selector/ha-selector-select.ts b/src/components/ha-selector/ha-selector-select.ts index bb5fb989ef..7e0f9ed52d 100644 --- a/src/components/ha-selector/ha-selector-select.ts +++ b/src/components/ha-selector/ha-selector-select.ts @@ -156,7 +156,7 @@ export class HaSelectSelector extends LitElement { no-style .disabled=${!this.selector.select.reorder} @item-moved=${this._itemMoved} - handle-selector="[data-handle]" + handle-selector="button.primary.action" > ${repeat( @@ -172,7 +172,6 @@ export class HaSelectSelector extends LitElement { @remove=${this._removeItem} .label=${label} selected - data-handle > ${this.selector.select?.reorder ? html` From 243a2ce0e23aeb93830d2ad556d38da82f9123c2 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Thu, 23 Jan 2025 16:16:42 +0100 Subject: [PATCH 89/89] Bumped version to 20250109.2 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4a7e60bbf1..c4d13eb16a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "home-assistant-frontend" -version = "20250109.1" +version = "20250109.2" license = {text = "Apache-2.0"} description = "The Home Assistant frontend" readme = "README.md"