From a2f2d64f5ce8cb9c86fa87a4dc101115dd384d08 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Wed, 29 Jan 2025 14:23:47 +0100 Subject: [PATCH] Allow to change encryption for each backup location (#23861) * Add backup location settings page * Add encryption settings * Display unencrypted locations and backups * Improve cloud detail page * Fix encryption flag * Fix restore backup * Fix lint * Fix translations * Add warning * Use agents in backup locations * Feedback * Use updated * Improve encrypted/unencrypted status * Improve code quality * Remove hardcoded failed id * Extend agent interface * Use willupdate --- src/data/backup.ts | 20 +- .../config/ha-backup-config-agents.ts | 64 ++- .../overview/ha-backup-overview-backups.ts | 7 +- .../overview/ha-backup-overview-summary.ts | 8 +- .../dialogs/dialog-backup-onboarding.ts | 1 + .../backup/dialogs/dialog-restore-backup.ts | 12 +- .../config/backup/ha-config-backup-backups.ts | 6 + .../config/backup/ha-config-backup-details.ts | 209 +++++----- .../backup/ha-config-backup-location.ts | 369 ++++++++++++++++++ .../backup/ha-config-backup-settings.ts | 2 + src/panels/config/backup/ha-config-backup.ts | 24 +- .../config/backup/helper/download_backup.ts | 16 +- src/translations/en.json | 35 +- 13 files changed, 640 insertions(+), 133 deletions(-) create mode 100644 src/panels/config/backup/ha-config-backup-location.ts diff --git a/src/data/backup.ts b/src/data/backup.ts index e8bb18b7f3..6bf43ade24 100644 --- a/src/data/backup.ts +++ b/src/data/backup.ts @@ -57,6 +57,7 @@ export interface BackupConfig { time?: string | null; days: BackupDay[]; }; + agents: BackupAgentsConfig; } export interface BackupMutableConfig { @@ -78,6 +79,13 @@ export interface BackupMutableConfig { time?: string | null; days?: BackupDay[] | null; }; + agents?: BackupAgentsConfig; +} + +export type BackupAgentsConfig = Record; + +export interface BackupAgentConfig { + protected: boolean; } export interface BackupAgent { @@ -85,13 +93,16 @@ export interface BackupAgent { name: string; } +export interface BackupContentAgent { + size: number; + protected: boolean; +} + export interface BackupContent { backup_id: string; date: string; name: string; - protected: boolean; - size: number; - agent_ids?: string[]; + agents: Record; failed_agent_ids?: string[]; with_automatic_settings: boolean; } @@ -305,6 +316,9 @@ export const computeBackupAgentName = ( return showName ? `${domainName}: ${name}` : domainName; }; +export const computeBackupSize = (backup: BackupContent) => + Math.max(...Object.values(backup.agents).map((agent) => agent.size)); + export const compareAgents = (a: string, b: string) => { const isLocalA = isLocalAgent(a); const isLocalB = isLocalAgent(b); 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 fa682f6224..3d6463af8c 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 @@ -1,14 +1,18 @@ -import { mdiHarddisk, mdiNas } from "@mdi/js"; +import { mdiCog, mdiHarddisk, mdiNas } from "@mdi/js"; import { css, html, LitElement, nothing } from "lit"; import { customElement, property, state } from "lit/decorators"; import memoizeOne from "memoize-one"; import { fireEvent } from "../../../../../common/dom/fire_event"; import { computeDomain } from "../../../../../common/entity/compute_domain"; +import "../../../../../components/ha-icon-button"; import "../../../../../components/ha-md-list"; import "../../../../../components/ha-md-list-item"; import "../../../../../components/ha-svg-icon"; import "../../../../../components/ha-switch"; -import type { BackupAgent } from "../../../../../data/backup"; +import type { + BackupAgent, + BackupAgentsConfig, +} from "../../../../../data/backup"; import { CLOUD_AGENT, computeBackupAgentName, @@ -18,6 +22,7 @@ import { import type { CloudStatus } from "../../../../../data/cloud"; import type { HomeAssistant } from "../../../../../types"; import { brandsUrl } from "../../../../../util/brands-url"; +import { navigate } from "../../../../../common/navigate"; const DEFAULT_AGENTS = []; @@ -29,6 +34,11 @@ class HaBackupConfigAgents extends LitElement { @property({ attribute: false }) public agents: BackupAgent[] = []; + @property({ attribute: false }) public agentsConfig?: BackupAgentsConfig; + + @property({ type: Boolean, attribute: "show-settings" }) public showSettings = + false; + @state() private value?: string[]; private _availableAgents = memoizeOne( @@ -53,6 +63,21 @@ class HaBackupConfigAgents extends LitElement { "ui.panel.config.backup.agents.cloud_agent_description" ); } + + const encryptionTurnedOff = + this.agentsConfig?.[agentId]?.protected === false; + + if (encryptionTurnedOff) { + return html` + + + ${this.hass.localize( + "ui.panel.config.backup.agents.encryption_turned_off" + )} + + `; + } + if (isNetworkMountAgent(agentId)) { return this.hass.localize( "ui.panel.config.backup.agents.network_mount_agent_description" @@ -80,6 +105,7 @@ class HaBackupConfigAgents extends LitElement { agentId === CLOUD_AGENT && this.cloudStatus.logged_in && !this.cloudStatus.active_subscription; + return html` ${isLocalAgent(agentId) @@ -112,6 +138,16 @@ class HaBackupConfigAgents extends LitElement { ${description ? html`
${description}
` : nothing} + ${this.showSettings + ? html` + + ` + : nothing} backups.reduce( (stats, backup) => { stats.count++; - stats.size += backup.size; + stats.size += computeBackupSize(backup); return stats; }, { count: 0, size: 0 } 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 f7c817c729..b238ad9767 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 @@ -179,7 +179,8 @@ class HaBackupOverviewBackups extends LitElement { now, true ), - count: lastUploadedBackup.agent_ids?.length ?? 0, + count: Object.keys(lastUploadedBackup.agents) + .length, } ) : nextBackupDescription} @@ -265,7 +266,8 @@ class HaBackupOverviewBackups extends LitElement { now, true ), - count: lastUploadedBackup.agent_ids?.length ?? 0, + count: Object.keys(lastUploadedBackup.agents) + .length, } ) : nextBackupDescription} @@ -286,7 +288,7 @@ class HaBackupOverviewBackups extends LitElement { now, true ), - count: lastBackup.agent_ids?.length ?? 0, + count: Object.keys(lastBackup.agents).length, } ); diff --git a/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts b/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts index f39cd2b270..7fa1e09dd8 100644 --- a/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts +++ b/src/panels/config/backup/dialogs/dialog-backup-onboarding.ts @@ -72,6 +72,7 @@ const RECOMMENDED_CONFIG: BackupConfig = { time: null, days: [], }, + agents: {}, last_attempted_automatic_backup: null, last_completed_automatic_backup: null, next_automatic_backup: null, diff --git a/src/panels/config/backup/dialogs/dialog-restore-backup.ts b/src/panels/config/backup/dialogs/dialog-restore-backup.ts index 7703a7c3e2..5a7ad2cfd9 100644 --- a/src/panels/config/backup/dialogs/dialog-restore-backup.ts +++ b/src/panels/config/backup/dialogs/dialog-restore-backup.ts @@ -78,7 +78,12 @@ class DialogRestoreBackup extends LitElement implements HassDialog { this._error = undefined; this._state = undefined; this._stage = undefined; - if (this._params.backup.protected) { + + const agentIds = Object.keys(this._params.backup.agents); + const preferedAgent = getPreferredAgentForDownload(agentIds); + const isProtected = this._params.backup.agents[preferedAgent]?.protected; + + if (isProtected) { this._backupEncryptionKey = await this._fetchEncryptionKey(); if (!this._backupEncryptionKey) { this._step = STEPS[1]; @@ -322,9 +327,8 @@ class DialogRestoreBackup extends LitElement implements HassDialog { return; } - const preferedAgent = getPreferredAgentForDownload( - this._params.backup.agent_ids! - ); + const agentIds = Object.keys(this._params.backup.agents); + const preferedAgent = getPreferredAgentForDownload(agentIds); const { addons, database_included, homeassistant_included, folders } = this._params.selectedData; diff --git a/src/panels/config/backup/ha-config-backup-backups.ts b/src/panels/config/backup/ha-config-backup-backups.ts index 4ac0dfca81..315403bbed 100644 --- a/src/panels/config/backup/ha-config-backup-backups.ts +++ b/src/panels/config/backup/ha-config-backup-backups.ts @@ -39,7 +39,9 @@ import type { BackupContent, } from "../../../data/backup"; import { + compareAgents, computeBackupAgentName, + computeBackupSize, deleteBackup, generateBackup, generateBackupWithAutomaticSettings, @@ -68,6 +70,8 @@ import { downloadBackup } from "./helper/download_backup"; interface BackupRow extends DataTableRowData, BackupContent { formatted_type: string; + size: number; + agent_ids: string[]; } type BackupType = "automatic" | "manual"; @@ -291,6 +295,8 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { const type = backup.with_automatic_settings ? "automatic" : "manual"; return { ...backup, + size: computeBackupSize(backup), + agent_ids: Object.keys(backup.agents).sort(compareAgents), formatted_type: localize(`ui.panel.config.backup.type.${type}`), }; }); diff --git a/src/panels/config/backup/ha-config-backup-details.ts b/src/panels/config/backup/ha-config-backup-details.ts index 9a80de3bf2..ed6c33cd83 100644 --- a/src/panels/config/backup/ha-config-backup-details.ts +++ b/src/panels/config/backup/ha-config-backup-details.ts @@ -23,12 +23,14 @@ import "../../../components/ha-md-list-item"; import type { BackupAgent, BackupConfig, + BackupContentAgent, BackupContentExtended, BackupData, } from "../../../data/backup"; import { compareAgents, computeBackupAgentName, + computeBackupSize, deleteBackup, fetchBackupDetails, isLocalAgent, @@ -45,21 +47,31 @@ import { fireEvent } from "../../../common/dom/fire_event"; import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; import { downloadBackup } from "./helper/download_backup"; -interface Agent { +interface Agent extends BackupContentAgent { id: string; success: boolean; } -const computeAgents = (agent_ids: string[], failed_agent_ids: string[]) => - [ - ...agent_ids.filter((id) => !failed_agent_ids.includes(id)), - ...failed_agent_ids, +const computeAgents = (backup: BackupContentExtended) => { + const agentIds = Object.keys(backup.agents); + const failedAgentIds = backup.failed_agent_ids ?? []; + return [ + ...agentIds.filter((id) => !failedAgentIds.includes(id)), + ...failedAgentIds, ] - .map((id) => ({ - id, - success: !failed_agent_ids.includes(id), - })) + .map((id) => { + const agent: BackupContentAgent = backup.agents[id] ?? { + protected: false, + size: 0, + }; + return { + ...agent, + id: id, + success: !failedAgentIds.includes(id), + }; + }) .sort((a, b) => compareAgents(a.id, b.id)); +}; @customElement("ha-config-backup-details") class HaConfigBackupDetails extends LitElement { @@ -156,7 +168,7 @@ class HaConfigBackupDetails extends LitElement { )} - ${bytesToString(this._backup.size)} + ${bytesToString(computeBackupSize(this._backup))}
@@ -173,22 +185,6 @@ class HaConfigBackupDetails extends LitElement { )} - - - ${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" - ) - : this.hass.localize( - "ui.panel.config.backup.details.summary.protected_not_encrypted" - )} - - @@ -230,87 +226,108 @@ class HaConfigBackupDetails extends LitElement { ${this._agents.map((agent) => { const agentId = agent.id; - const success = agent.success; + const domain = computeDomain(agentId); const name = computeBackupAgentName( this.hass.localize, agentId, this.agents ); + const success = agent.success; + const failed = !agent.success; + const unencrypted = !agent.protected; return html` - ${isLocalAgent(agentId) - ? html` - - - ` - : isNetworkMountAgent(agentId) + ${ + isLocalAgent(agentId) ? html` + > + ` - : html` - - `} + : isNetworkMountAgent(agentId) + ? html` + + ` + : html` + + ` + }
${name}
-
- - - - ${success - ? this.hass.localize( - "ui.panel.config.backup.details.locations.backup_stored" - ) - : this.hass.localize( - "ui.panel.config.backup.details.locations.backup_failed" - )} - + ${ + failed + ? html` +
+ + + ${this.hass.localize( + "ui.panel.config.backup.details.locations.backup_failed" + )} + +
+ ` + : unencrypted + ? html` +
+ + Unencrypted +
+ ` + : html` +
+ + Encrypted +
+ ` + }
- ${success - ? html` - - - - ${this.hass.localize( - "ui.panel.config.backup.details.locations.download" - )} - - ` - : nothing} + ${ + success + ? html` + + + + + ${this.hass.localize( + "ui.panel.config.backup.details.locations.download" + )} + + + ` + : nothing + }
`; })} @@ -354,10 +371,7 @@ class HaConfigBackupDetails extends LitElement { try { const response = await fetchBackupDetails(this.hass, this.backupId); this._backup = response.backup; - this._agents = computeAgents( - response.backup.agent_ids || [], - response.backup.failed_agent_ids || [] - ); + this._agents = computeAgents(response.backup); } catch (err: any) { this._error = err?.message || @@ -479,6 +493,9 @@ class HaConfigBackupDetails extends LitElement { .dot.success { background-color: var(--success-color); } + .dot.warning { + background-color: var(--warning-color); + } .dot.error { background-color: var(--error-color); } diff --git a/src/panels/config/backup/ha-config-backup-location.ts b/src/panels/config/backup/ha-config-backup-location.ts new file mode 100644 index 0000000000..14118dc332 --- /dev/null +++ b/src/panels/config/backup/ha-config-backup-location.ts @@ -0,0 +1,369 @@ +import type { PropertyValues } from "lit"; +import { css, html, LitElement, nothing } from "lit"; +import { customElement, property, state } from "lit/decorators"; +import "../../../components/ha-alert"; +import "../../../components/ha-button"; +import "../../../components/ha-switch"; +import "../../../components/ha-button-menu"; +import "../../../components/ha-card"; +import "../../../components/ha-circular-progress"; +import "../../../components/ha-icon-button"; +import "../../../components/ha-list-item"; +import "../../../components/ha-md-list"; +import "../../../components/ha-md-list-item"; +import type { + BackupAgent, + BackupAgentConfig, + BackupConfig, +} from "../../../data/backup"; +import { + CLOUD_AGENT, + computeBackupAgentName, + fetchBackupAgentsInfo, + updateBackupConfig, +} from "../../../data/backup"; +import "../../../layouts/hass-subpage"; +import type { HomeAssistant } from "../../../types"; +import "./components/ha-backup-data-picker"; +import { showConfirmationDialog } from "../../lovelace/custom-card-helpers"; +import { fireEvent } from "../../../common/dom/fire_event"; + +@customElement("ha-config-backup-location") +class HaConfigBackupDetails extends LitElement { + @property({ attribute: false }) public hass!: HomeAssistant; + + @property({ type: Boolean }) public narrow = false; + + @property({ attribute: "agent-id" }) public agentId!: string; + + @property({ attribute: false }) public config?: BackupConfig; + + @property({ attribute: false }) public agents: BackupAgent[] = []; + + @state() private _agent?: BackupAgent | null; + + @state() private _error?: string; + + protected willUpdate(changedProps: PropertyValues): void { + if (changedProps.has("agentId")) { + if (this.agentId) { + this._fetchAgent(); + } else { + this._error = "Agent id not defined"; + } + } + } + + protected render() { + if (!this.hass) { + return nothing; + } + + const encrypted = this._isEncryptionTurnedOn(); + + return html` + +
+ ${this._error && + html`${this._error}`} + ${this._agent === null + ? html` + + ${this.hass.localize( + "ui.panel.config.backup.location.not_found_description", + { agentId: this.agentId } + )} + + ` + : !this.agentId + ? html`` + : html` + ${CLOUD_AGENT === this.agentId + ? html` + +
+ ${this.hass.localize( + "ui.panel.config.backup.location.configuration.title" + )} +
+
+

+ ${this.hass.localize( + "ui.panel.config.backup.location.configuration.cloud_description" + )} +

+
+
+ ` + : nothing} + +
+ ${this.hass.localize( + "ui.panel.config.backup.location.encryption.title" + )} +
+
+

+ ${this.hass.localize( + "ui.panel.config.backup.location.encryption.description" + )} +

+ + ${CLOUD_AGENT === this.agentId + ? html` + + + ${this.hass.localize( + "ui.panel.config.backup.location.encryption.location_encrypted" + )} + + + ${this.hass.localize( + "ui.panel.config.backup.location.encryption.location_encrypted_cloud_description" + )} + + + + ${this.hass.localize( + "ui.panel.config.backup.location.encryption.location_encrypted_cloud_learn_more" + )} + + + + ` + : encrypted + ? html` + + + ${this.hass.localize( + "ui.panel.config.backup.location.encryption.location_encrypted" + )} + + + ${this.hass.localize( + `ui.panel.config.backup.location.encryption.location_encrypted_description` + )} + + + + ${this.hass.localize( + "ui.panel.config.backup.location.encryption.encryption_turn_off" + )} + + + ` + : html` + + ${this.hass.localize( + "ui.panel.config.backup.location.encryption.warning_encryption_turn_off_description" + )} + + + + ${this.hass.localize( + "ui.panel.config.backup.location.encryption.location_unencrypted" + )} + + + ${this.hass.localize( + `ui.panel.config.backup.location.encryption.location_unencrypted_description` + )} + + + + ${this.hass.localize( + "ui.panel.config.backup.location.encryption.encryption_turn_on" + )} + + + `} + +
+
+ `} +
+
+ `; + } + + private _isEncryptionTurnedOn() { + const agentConfig = this.config?.agents[this.agentId] as + | BackupAgentConfig + | undefined; + + if (!agentConfig) { + return true; + } + return agentConfig.protected; + } + + private async _fetchAgent() { + try { + // Todo fetch agent details + const { agents } = await fetchBackupAgentsInfo(this.hass); + const agent = agents.find((a) => a.agent_id === this.agentId); + if (!agent) { + throw new Error("Agent not found"); + } + this._agent = agent; + } catch (err: any) { + this._error = + err?.message || + this.hass.localize("ui.panel.config.backup.details.error"); + } + } + + private async _updateAgentEncryption(value: boolean) { + const agentsConfig = { + ...this.config?.agents, + [this.agentId]: { + ...this.config?.agents[this.agentId], + protected: value, + }, + }; + await updateBackupConfig(this.hass, { + agents: agentsConfig, + }); + fireEvent(this, "ha-refresh-backup-config"); + } + + private _turnOnEncryption() { + this._updateAgentEncryption(true); + } + + private async _turnOffEncryption() { + const response = await showConfirmationDialog(this, { + title: this.hass.localize( + "ui.panel.config.backup.location.encryption.encryption_turn_off_confirm_title" + ), + text: this.hass.localize( + "ui.panel.config.backup.location.encryption.encryption_turn_off_confirm_text" + ), + confirmText: this.hass.localize( + "ui.panel.config.backup.location.encryption.encryption_turn_off_confirm_action" + ), + dismissText: this.hass.localize("ui.common.cancel"), + destructive: true, + }); + if (response) { + this._updateAgentEncryption(false); + } + } + + static styles = css` + .content { + padding: 28px 20px 0; + max-width: 690px; + margin: 0 auto; + gap: 24px; + display: grid; + margin-bottom: 24px; + } + .card-content { + padding: 0 20px; + } + .card-actions { + display: flex; + justify-content: flex-end; + } + ha-md-list { + background: none; + padding: 0; + } + ha-md-list-item { + --md-list-item-leading-space: 0; + --md-list-item-trailing-space: 0; + --md-list-item-two-line-container-height: 64px; + } + ha-md-list-item img { + width: 48px; + } + ha-md-list-item ha-svg-icon[slot="start"] { + --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); + } + .warning ha-svg-icon { + color: var(--error-color); + } + ha-button.danger { + --mdc-theme-primary: var(--error-color); + } + ha-backup-data-picker { + display: block; + } + ha-md-list-item [slot="supporting-text"] { + display: flex; + align-items: center; + flex-direction: row; + gap: 8px; + line-height: normal; + } + .dot { + display: block; + position: relative; + width: 8px; + height: 8px; + background-color: var(--disabled-color); + border-radius: 50%; + flex: none; + } + .dot.success { + background-color: var(--success-color); + } + .dot.error { + background-color: var(--error-color); + } + .card-header { + padding-bottom: 8px; + } + `; +} + +declare global { + interface HTMLElementTagNameMap { + "ha-config-backup-location": HaConfigBackupDetails; + } +} diff --git a/src/panels/config/backup/ha-config-backup-settings.ts b/src/panels/config/backup/ha-config-backup-settings.ts index 31bba002a3..6b800b3574 100644 --- a/src/panels/config/backup/ha-config-backup-settings.ts +++ b/src/panels/config/backup/ha-config-backup-settings.ts @@ -179,9 +179,11 @@ class HaConfigBackupSettings extends LitElement { ${!this._config.create_backup.agent_ids.length ? html` diff --git a/src/panels/config/backup/ha-config-backup.ts b/src/panels/config/backup/ha-config-backup.ts index 07cd5ff639..245927dffe 100644 --- a/src/panels/config/backup/ha-config-backup.ts +++ b/src/panels/config/backup/ha-config-backup.ts @@ -88,11 +88,7 @@ class HaConfigBackup extends SubscribeMixin(HassRouterPage) { private async _fetchBackupInfo() { const info = await fetchBackupInfo(this.hass); - this._backups = info.backups.map((backup) => ({ - ...backup, - agent_ids: backup.agent_ids?.sort(compareAgents), - failed_agent_ids: backup.failed_agent_ids?.sort(compareAgents), - })); + this._backups = info.backups; } private async _fetchBackupConfig() { @@ -124,6 +120,10 @@ class HaConfigBackup extends SubscribeMixin(HassRouterPage) { tag: "ha-config-backup-settings", load: () => import("./ha-config-backup-settings"), }, + location: { + tag: "ha-config-backup-location", + load: () => import("./ha-config-backup-location"), + }, }, }; @@ -138,11 +138,15 @@ class HaConfigBackup extends SubscribeMixin(HassRouterPage) { pageEl.agents = this._agents; pageEl.fetching = this._fetching; - if ( - (!changedProps || changedProps.has("route")) && - this._currentPage === "details" - ) { - pageEl.backupId = this.routeTail.path.substr(1); + if (!changedProps || changedProps.has("route")) { + switch (this._currentPage) { + case "details": + pageEl.backupId = this.routeTail.path.substr(1); + break; + case "location": + pageEl.agentId = this.routeTail.path.substr(1); + break; + } } } diff --git a/src/panels/config/backup/helper/download_backup.ts b/src/panels/config/backup/helper/download_backup.ts index 6f8b48258e..e6b2f8d2c7 100644 --- a/src/panels/config/backup/helper/download_backup.ts +++ b/src/panels/config/backup/helper/download_backup.ts @@ -42,11 +42,10 @@ const downloadEncryptedBackup = async ( confirmText: "Download encrypted", }) ) { - triggerDownload( - hass, - backup.backup_id, - agentId ?? getPreferredAgentForDownload(backup.agent_ids!) - ); + const agentIds = Object.keys(backup.agents); + const preferedAgent = agentId ?? getPreferredAgentForDownload(agentIds); + + triggerDownload(hass, backup.backup_id, preferedAgent); } }; @@ -83,10 +82,11 @@ export const downloadBackup = async ( agentId?: string, userProvided = false ): Promise => { - const preferedAgent = - agentId ?? getPreferredAgentForDownload(backup.agent_ids!); + const agentIds = Object.keys(backup.agents); + const preferedAgent = agentId ?? getPreferredAgentForDownload(agentIds); + const isProtected = backup.agents[preferedAgent]?.protected; - if (backup.protected) { + if (isProtected) { if (encryptionKey) { try { await canDecryptBackupOnDownload( diff --git a/src/translations/en.json b/src/translations/en.json index 8762bdad0c..bb4983de91 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2404,6 +2404,7 @@ "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", + "encryption_turned_off": "Encryption turned off", "local_agent": "This system" }, "data": { @@ -2650,10 +2651,7 @@ "summary": { "title": "Backup", "size": "Size", - "created": "Created", - "protection": "Protection", - "protected_encrypted_aes_128": "Encrypted AES-128", - "protected_not_encrypted": "Not encrypted" + "created": "Created" }, "restore": { "title": "Select what to restore", @@ -2661,10 +2659,37 @@ }, "locations": { "title": "Locations", - "backup_stored": "Backup stored", "backup_failed": "Backup failed", + "encryption_turned_off": "Encryption turned off", "download": "Download from this location" } + }, + "location": { + "header": "Location", + "not_found": "Not found", + "not_found_description": "Location matching ''{backupId}'' not found", + "error": "Could not fetch location details", + "configuration": { + "title": "Configuration", + "cloud_description": "Home Assistant Cloud backup stores only one backup. The oldest backups are deleted." + }, + "encryption": { + "title": "Encryption", + "description": "All your backups are encrypted by default to keep your data private and secure.", + "location_encrypted": "This location is encrypted", + "location_unencrypted": "This location is unencrypted", + "location_encrypted_description": "Your data private and secure by securing it with your encryption key.", + "location_encrypted_cloud_description": "Home Assistant Cloud is the privacy-focused cloud. This is why it will only accept encrypted backups and why we don’t store your encryption key.", + "location_encrypted_cloud_learn_more": "Learn more", + "location_unencrypted_description": "Please keep your backups private and secure.", + "encryption_turn_on": "Turn on", + "encryption_turn_off": "Turn off", + "encryption_turn_off_confirm_title": "Turn encryption off?", + "encryption_turn_off_confirm_text": "All your next backups will not be encrypted for this location. Please keep your backups private and secure.", + "encryption_turn_off_confirm_action": "Turn encryption off", + "warning_encryption_turn_off": "Encryption turned off", + "warning_encryption_turn_off_description": "All your next backups will not be encrypted." + } } }, "tag": {