From 11ae3a77e876bc6efb4fff4b2e126264e496df94 Mon Sep 17 00:00:00 2001 From: Paul Bottein Date: Tue, 4 Feb 2025 16:04:11 +0100 Subject: [PATCH] Add support for add-on update type for backups in the UI (#24044) * Add support for add-on update type for backups in the UI * Add type to backup detail page * Use new model * Fix detail page * Fix type --- src/data/backup.ts | 29 ++++- .../overview/ha-backup-overview-backups.ts | 106 +++++++++--------- .../config/backup/ha-config-backup-backups.ts | 48 ++++---- .../config/backup/ha-config-backup-details.ts | 16 +++ src/translations/en.json | 4 +- 5 files changed, 126 insertions(+), 77 deletions(-) diff --git a/src/data/backup.ts b/src/data/backup.ts index 6bf43ade24..756ca4b47a 100644 --- a/src/data/backup.ts +++ b/src/data/backup.ts @@ -1,6 +1,8 @@ +import { memoize } from "@fullcalendar/core/internal"; import { setHours, setMinutes } from "date-fns"; import type { HassConfig } from "home-assistant-js-websocket"; import memoizeOne from "memoize-one"; +import checkValidDate from "../common/datetime/check_valid_date"; import { formatDateTime, formatDateTimeNumeric, @@ -11,7 +13,6 @@ import type { HomeAssistant } from "../types"; import { fileDownload } from "../util/file_download"; import { domainToName } from "./integration"; import type { FrontendLocaleData } from "./translation"; -import checkValidDate from "../common/datetime/check_valid_date"; export const enum BackupScheduleRecurrence { NEVER = "never", @@ -104,6 +105,9 @@ export interface BackupContent { name: string; agents: Record; failed_agent_ids?: string[]; + extra_metadata?: { + "supervisor.addon_update"?: string; + }; with_automatic_settings: boolean; } @@ -319,6 +323,29 @@ export const computeBackupAgentName = ( export const computeBackupSize = (backup: BackupContent) => Math.max(...Object.values(backup.agents).map((agent) => agent.size)); +export type BackupType = "automatic" | "manual" | "addon_update"; + +const BACKUP_TYPE_ORDER: BackupType[] = ["automatic", "manual", "addon_update"]; + +export const getBackupTypes = memoize((isHassio: boolean) => + isHassio + ? BACKUP_TYPE_ORDER + : BACKUP_TYPE_ORDER.filter((type) => type !== "addon_update") +); + +export const computeBackupType = ( + backup: BackupContent, + isHassio: boolean +): BackupType => { + if (backup.with_automatic_settings) { + return "automatic"; + } + if (isHassio && backup.extra_metadata?.["supervisor.addon_update"] != null) { + return "addon_update"; + } + return "manual"; +}; + export const compareAgents = (a: string, b: string) => { const isLocalA = isLocalAgent(a); const isLocalB = isLocalAgent(b); 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 882902b602..c140713aba 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 @@ -1,16 +1,19 @@ -import { mdiCalendarSync, mdiGestureTap } from "@mdi/js"; +import { mdiCalendarSync, mdiGestureTap, mdiPuzzle } from "@mdi/js"; import type { CSSResultGroup } from "lit"; import { css, html, LitElement } from "lit"; import { customElement, property } from "lit/decorators"; import memoizeOne from "memoize-one"; +import { isComponentLoaded } from "../../../../../common/config/is_component_loaded"; import "../../../../../components/ha-button"; import "../../../../../components/ha-card"; import "../../../../../components/ha-icon-next"; import "../../../../../components/ha-md-list"; import "../../../../../components/ha-md-list-item"; +import type { BackupContent, BackupType } from "../../../../../data/backup"; import { computeBackupSize, - type BackupContent, + computeBackupType, + getBackupTypes, } from "../../../../../data/backup"; import { haStyle } from "../../../../../resources/styles"; import type { HomeAssistant } from "../../../../../types"; @@ -21,6 +24,12 @@ interface BackupStats { size: number; } +const TYPE_ICONS: Record = { + automatic: mdiCalendarSync, + manual: mdiGestureTap, + addon_update: mdiPuzzle, +}; + const computeBackupStats = (backups: BackupContent[]): BackupStats => backups.reduce( (stats, backup) => { @@ -37,23 +46,22 @@ class HaBackupOverviewBackups extends LitElement { @property({ attribute: false }) public backups: BackupContent[] = []; - private _automaticStats = memoizeOne((backups: BackupContent[]) => { - const automaticBackups = backups.filter( - (backup) => backup.with_automatic_settings - ); - return computeBackupStats(automaticBackups); - }); - - private _manualStats = memoizeOne((backups: BackupContent[]) => { - const manualBackups = backups.filter( - (backup) => !backup.with_automatic_settings - ); - return computeBackupStats(manualBackups); - }); + private _stats = memoizeOne( + ( + backups: BackupContent[], + isHassio: boolean + ): [BackupType, BackupStats][] => + getBackupTypes(isHassio).map((type) => { + const backupsOfType = backups.filter( + (backup) => computeBackupType(backup, isHassio) === type + ); + return [type, computeBackupStats(backupsOfType)] as const; + }) + ); render() { - const automaticStats = this._automaticStats(this.backups); - const manualStats = this._manualStats(this.backups); + const isHassio = isComponentLoaded(this.hass, "hassio"); + const stats = this._stats(this.backups, isHassio); return html` @@ -62,44 +70,32 @@ class HaBackupOverviewBackups extends LitElement {
- - -
- ${this.hass.localize( - "ui.panel.config.backup.overview.backups.automatic", - { count: automaticStats.count } - )} -
-
- ${this.hass.localize( - "ui.panel.config.backup.overview.backups.total_size", - { size: bytesToString(automaticStats.size, 1) } - )} -
- -
- - -
- ${this.hass.localize( - "ui.panel.config.backup.overview.backups.manual", - { count: manualStats.count } - )} -
-
- ${this.hass.localize( - "ui.panel.config.backup.overview.backups.total_size", - { size: bytesToString(manualStats.size, 1) } - )} -
- -
+ ${stats.map( + ([type, { count, size }]) => html` + + +
+ ${this.hass.localize( + `ui.panel.config.backup.overview.backups.${type}`, + { count } + )} +
+
+ ${this.hass.localize( + "ui.panel.config.backup.overview.backups.total_size", + { size: bytesToString(size) } + )} +
+ +
+ ` + )}
diff --git a/src/panels/config/backup/ha-config-backup-backups.ts b/src/panels/config/backup/ha-config-backup-backups.ts index 4bab6ee118..6caa2190af 100644 --- a/src/panels/config/backup/ha-config-backup-backups.ts +++ b/src/panels/config/backup/ha-config-backup-backups.ts @@ -11,6 +11,7 @@ import type { CSSResultGroup, TemplateResult } from "lit"; import { html, LitElement, nothing } from "lit"; import { customElement, property, query, state } from "lit/decorators"; import memoizeOne from "memoize-one"; +import { isComponentLoaded } from "../../../common/config/is_component_loaded"; import { relativeTime } from "../../../common/datetime/relative_time"; import { storage } from "../../../common/decorators/storage"; import { fireEvent, type HASSDomEvent } from "../../../common/dom/fire_event"; @@ -42,9 +43,11 @@ import { compareAgents, computeBackupAgentName, computeBackupSize, + computeBackupType, deleteBackup, generateBackup, generateBackupWithAutomaticSettings, + getBackupTypes, isLocalAgent, isNetworkMountAgent, } from "../../../data/backup"; @@ -74,10 +77,6 @@ interface BackupRow extends DataTableRowData, BackupContent { agent_ids: string[]; } -type BackupType = "automatic" | "manual"; - -const TYPE_ORDER: BackupType[] = ["automatic", "manual"]; - @customElement("ha-config-backup-backups") class HaConfigBackupBackups extends SubscribeMixin(LitElement) { @property({ attribute: false }) public hass!: HomeAssistant; @@ -277,9 +276,13 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { ); private _groupOrder = memoizeOne( - (activeGrouping: string | undefined, localize: LocalizeFunc) => + ( + activeGrouping: string | undefined, + localize: LocalizeFunc, + isHassio: boolean + ) => activeGrouping === "formatted_type" - ? TYPE_ORDER.map((type) => + ? getBackupTypes(isHassio).map((type) => localize(`ui.panel.config.backup.type.${type}`) ) : undefined @@ -303,20 +306,19 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { ( backups: BackupContent[], filters: DataTableFiltersValues, - localize: LocalizeFunc + localize: LocalizeFunc, + isHassio: boolean ): BackupRow[] => { const typeFilter = filters["ha-filter-states"] as string[] | undefined; let filteredBackups = backups; if (typeFilter?.length) { - filteredBackups = filteredBackups.filter( - (backup) => - (backup.with_automatic_settings && - typeFilter.includes("automatic")) || - (!backup.with_automatic_settings && typeFilter.includes("manual")) - ); + filteredBackups = filteredBackups.filter((backup) => { + const type = computeBackupType(backup, isHassio); + return typeFilter.includes(type); + }); } return filteredBackups.map((backup) => { - const type = backup.with_automatic_settings ? "automatic" : "manual"; + const type = computeBackupType(backup, isHassio); const agentIds = Object.keys(backup.agents); return { ...backup, @@ -335,8 +337,13 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { protected render(): TemplateResult { const backupInProgress = "state" in this.manager && this.manager.state === "in_progress"; - - const data = this._data(this.backups, this._filters, this.hass.localize); + const isHassio = isComponentLoaded(this.hass, "hassio"); + const data = this._data( + this.backups, + this._filters, + this.hass.localize, + isHassio + ); const maxDisplayedAgents = Math.min( this._maxAgents(data), this.narrow ? 3 : 5 @@ -371,7 +378,8 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { .initialCollapsedGroups=${this._activeCollapsed} .groupOrder=${this._groupOrder( this._activeGrouping, - this.hass.localize + this.hass.localize, + isHassio )} @grouping-changed=${this._handleGroupingChanged} @collapsed-changed=${this._handleCollapseChanged} @@ -435,7 +443,7 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { .hass=${this.hass} .label=${this.hass.localize("ui.panel.config.backup.backup_type")} .value=${this._filters["ha-filter-states"]} - .states=${this._states(this.hass.localize)} + .states=${this._states(this.hass.localize, isHassio)} @data-table-filter-changed=${this._filterChanged} slot="filter-pane" expanded @@ -460,8 +468,8 @@ class HaConfigBackupBackups extends SubscribeMixin(LitElement) { `; } - private _states = memoizeOne((localize: LocalizeFunc) => - TYPE_ORDER.map((type) => ({ + private _states = memoizeOne((localize: LocalizeFunc, isHassio: boolean) => + getBackupTypes(isHassio).map((type) => ({ value: type, label: 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 ee3a701f47..c3bbd93552 100644 --- a/src/panels/config/backup/ha-config-backup-details.ts +++ b/src/panels/config/backup/ha-config-backup-details.ts @@ -31,6 +31,7 @@ import { compareAgents, computeBackupAgentName, computeBackupSize, + computeBackupType, deleteBackup, fetchBackupDetails, isLocalAgent, @@ -46,6 +47,7 @@ import { showRestoreBackupDialog } from "./dialogs/show-dialog-restore-backup"; import { fireEvent } from "../../../common/dom/fire_event"; import { showConfirmationDialog } from "../../../dialogs/generic/show-dialog-box"; import { downloadBackup } from "./helper/download_backup"; +import { isComponentLoaded } from "../../../common/config/is_component_loaded"; interface Agent extends BackupContentAgent { id: string; @@ -110,6 +112,8 @@ class HaConfigBackupDetails extends LitElement { return nothing; } + const isHassio = isComponentLoaded(this.hass, "hassio"); + return html`
+ + + ${this.hass.localize( + "ui.panel.config.backup.backup_type" + )} + + + ${this.hass.localize( + `ui.panel.config.backup.type.${computeBackupType(this._backup, isHassio)}` + )} + + ${this.hass.localize( diff --git a/src/translations/en.json b/src/translations/en.json index 7de53853a1..530eda935c 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -2223,7 +2223,8 @@ "backup_type": "Type", "type": { "manual": "Manual", - "automatic": "Automatic" + "automatic": "Automatic", + "addon_update": "Add-on update" }, "locations": "Locations", "create": { @@ -2566,6 +2567,7 @@ "title": "My backups", "automatic": "{count} automatic {count, plural,\n one {backup}\n other {backups}\n}", "manual": "{count} manual {count, plural,\n one {backup}\n other {backups}\n}", + "addon_update": "{count} add-on update {count, plural,\n one {backup}\n other {backups}\n}", "total_size": "{size} in total", "show_all": "Show all backups" },